summaryrefslogtreecommitdiffstats
path: root/contrib/mdocml
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/mdocml')
-rw-r--r--contrib/mdocml/INSTALL187
-rw-r--r--contrib/mdocml/LICENSE44
-rw-r--r--contrib/mdocml/Makefile505
-rw-r--r--contrib/mdocml/Makefile.depend70
-rw-r--r--contrib/mdocml/NEWS450
-rw-r--r--contrib/mdocml/TODO437
-rw-r--r--contrib/mdocml/apropos.1387
-rw-r--r--contrib/mdocml/apropos.c123
-rw-r--r--contrib/mdocml/arch.c37
-rw-r--r--contrib/mdocml/arch.in112
-rw-r--r--contrib/mdocml/att.c37
-rw-r--r--contrib/mdocml/att.in40
-rw-r--r--contrib/mdocml/cgi.c1150
-rw-r--r--contrib/mdocml/cgi.h.example9
-rw-r--r--contrib/mdocml/chars.c180
-rw-r--r--contrib/mdocml/chars.in402
-rw-r--r--contrib/mdocml/compat_fgetln.c93
-rw-r--r--contrib/mdocml/compat_getsubopt.c104
-rw-r--r--contrib/mdocml/compat_ohash.c339
-rw-r--r--contrib/mdocml/compat_ohash.h73
-rw-r--r--contrib/mdocml/compat_reallocarray.c45
-rw-r--r--contrib/mdocml/compat_sqlite3_errstr.c18
-rw-r--r--contrib/mdocml/compat_strcasestr.c74
-rw-r--r--contrib/mdocml/compat_strlcat.c67
-rw-r--r--contrib/mdocml/compat_strlcpy.c63
-rw-r--r--contrib/mdocml/compat_strsep.c80
-rw-r--r--contrib/mdocml/config.h62
-rw-r--r--contrib/mdocml/config.h.post42
-rw-r--r--contrib/mdocml/config.h.pre9
-rwxr-xr-xcontrib/mdocml/configure49
-rw-r--r--contrib/mdocml/demandoc.c257
-rw-r--r--contrib/mdocml/eqn.7279
-rw-r--r--contrib/mdocml/eqn.c948
-rw-r--r--contrib/mdocml/eqn_html.c81
-rw-r--r--contrib/mdocml/eqn_term.c77
-rw-r--r--contrib/mdocml/example.style.css110
-rw-r--r--contrib/mdocml/gmdiff38
-rw-r--r--contrib/mdocml/html.c776
-rw-r--r--contrib/mdocml/html.h169
-rw-r--r--contrib/mdocml/lib.c36
-rw-r--r--contrib/mdocml/lib.in123
-rw-r--r--contrib/mdocml/libman.h77
-rw-r--r--contrib/mdocml/libmandoc.h92
-rw-r--r--contrib/mdocml/libmdoc.h143
-rw-r--r--contrib/mdocml/libroff.h84
-rw-r--r--contrib/mdocml/main.c430
-rw-r--r--contrib/mdocml/main.h61
-rw-r--r--contrib/mdocml/makewhatis.8217
-rw-r--r--contrib/mdocml/man.7953
-rw-r--r--contrib/mdocml/man.c704
-rw-r--r--contrib/mdocml/man.cgi.8409
-rw-r--r--contrib/mdocml/man.h121
-rw-r--r--contrib/mdocml/man_hash.c105
-rw-r--r--contrib/mdocml/man_html.c698
-rw-r--r--contrib/mdocml/man_macro.c492
-rw-r--r--contrib/mdocml/man_term.c1190
-rw-r--r--contrib/mdocml/man_validate.c546
-rw-r--r--contrib/mdocml/mandoc.11502
-rw-r--r--contrib/mdocml/mandoc.3661
-rw-r--r--contrib/mdocml/mandoc.c600
-rw-r--r--contrib/mdocml/mandoc.db.5144
-rw-r--r--contrib/mdocml/mandoc.h438
-rw-r--r--contrib/mdocml/mandoc_aux.c121
-rw-r--r--contrib/mdocml/mandoc_aux.h33
-rw-r--r--contrib/mdocml/mandoc_char.7747
-rw-r--r--contrib/mdocml/mandoc_escape.3362
-rw-r--r--contrib/mdocml/mandoc_html.3249
-rw-r--r--contrib/mdocml/mandoc_malloc.3197
-rw-r--r--contrib/mdocml/mandocdb.c2491
-rw-r--r--contrib/mdocml/manpage.c190
-rw-r--r--contrib/mdocml/manpath.c222
-rw-r--r--contrib/mdocml/manpath.h38
-rw-r--r--contrib/mdocml/mansearch.3228
-rw-r--r--contrib/mdocml/mansearch.c861
-rw-r--r--contrib/mdocml/mansearch.h101
-rw-r--r--contrib/mdocml/mansearch_const.c35
-rw-r--r--contrib/mdocml/mchars_alloc.3224
-rw-r--r--contrib/mdocml/mdoc.73321
-rw-r--r--contrib/mdocml/mdoc.c981
-rw-r--r--contrib/mdocml/mdoc.h399
-rw-r--r--contrib/mdocml/mdoc_argv.c701
-rw-r--r--contrib/mdocml/mdoc_hash.c94
-rw-r--r--contrib/mdocml/mdoc_html.c2199
-rw-r--r--contrib/mdocml/mdoc_macro.c1822
-rw-r--r--contrib/mdocml/mdoc_man.c1753
-rw-r--r--contrib/mdocml/mdoc_term.c2239
-rw-r--r--contrib/mdocml/mdoc_validate.c2488
-rw-r--r--contrib/mdocml/msec.c36
-rw-r--r--contrib/mdocml/msec.in40
-rw-r--r--contrib/mdocml/out.c283
-rw-r--r--contrib/mdocml/out.h71
-rw-r--r--contrib/mdocml/predefs.in65
-rw-r--r--contrib/mdocml/read.c923
-rw-r--r--contrib/mdocml/roff.71429
-rw-r--r--contrib/mdocml/roff.c2343
-rw-r--r--contrib/mdocml/st.c36
-rw-r--r--contrib/mdocml/st.in83
-rw-r--r--contrib/mdocml/style.css144
-rw-r--r--contrib/mdocml/tbl.7340
-rw-r--r--contrib/mdocml/tbl.c176
-rw-r--r--contrib/mdocml/tbl_data.c275
-rw-r--r--contrib/mdocml/tbl_html.c142
-rw-r--r--contrib/mdocml/tbl_layout.c400
-rw-r--r--contrib/mdocml/tbl_opts.c271
-rw-r--r--contrib/mdocml/tbl_term.c426
-rw-r--r--contrib/mdocml/term.c808
-rw-r--r--contrib/mdocml/term.h136
-rw-r--r--contrib/mdocml/term_ascii.c302
-rw-r--r--contrib/mdocml/term_ps.c1167
-rw-r--r--contrib/mdocml/test-fgetln.c11
-rw-r--r--contrib/mdocml/test-getsubopt.c19
-rw-r--r--contrib/mdocml/test-mmap.c9
-rw-r--r--contrib/mdocml/test-ohash.c21
-rw-r--r--contrib/mdocml/test-reallocarray.c7
-rw-r--r--contrib/mdocml/test-sqlite3_errstr.c8
-rw-r--r--contrib/mdocml/test-strcasestr.c13
-rw-r--r--contrib/mdocml/test-strlcat.c9
-rw-r--r--contrib/mdocml/test-strlcpy.c9
-rw-r--r--contrib/mdocml/test-strsep.c10
-rw-r--r--contrib/mdocml/tree.c349
-rw-r--r--contrib/mdocml/vol.c36
-rw-r--r--contrib/mdocml/vol.in35
122 files changed, 50377 insertions, 0 deletions
diff --git a/contrib/mdocml/INSTALL b/contrib/mdocml/INSTALL
new file mode 100644
index 0000000..da8eeab
--- /dev/null
+++ b/contrib/mdocml/INSTALL
@@ -0,0 +1,187 @@
+$Id: INSTALL,v 1.2 2014/08/10 17:22:26 schwarze Exp $
+
+About mdocml, the portable mandoc distribution
+----------------------------------------------
+The mandoc manpage compiler toolset is a suite of tools compiling
+mdoc(7), the roff(7) macro language of choice for BSD manual pages,
+and man(7), the predominant historical language for UNIX manuals.
+The toolset does not yet implement man(1); that is only scheduled
+for the next release, 1.13.2. It can, however, already serve to
+translate source manpages to the output displayed by man(1).
+For general information, see <http://mdocml.bsd.lv/>.
+
+In this document, we describe the installation and deployment of
+mandoc(1), first as a simple, standalone formatter, and then as part of
+the man(1) system.
+
+In case you have questions or want to provide feedback, read
+<http://mdocml.bsd.lv/contact.html>. Consider subscribing to the
+discuss@ mailing list mentioned on that page. If you intend to
+help with the development of mandoc, consider subscribing to the
+tech@ mailing list, too.
+
+Enjoy using the mandoc toolset!
+
+Ingo Schwarze, Karlsruhe, August 2014
+
+
+Installation
+------------
+Before manually installing mandoc on your system, please check
+whether the newest version of mandoc is already installed by default
+or available via a binary package or a ports system. A list of the
+latest bundled and ported versions of mandoc for various operating
+systems is maintained at <http://mdocml.bsd.lv/ports.html>.
+
+If mandoc is installed, you can check the version by running "mandoc -V".
+The version contained in this distribution tarball is listed near
+the beginning of the file "Makefile".
+
+Regarding how packages and ports are maintained for your operating
+system, please consult your operating system documentation.
+To install mandoc manually, the following steps are needed:
+
+1. Decide whether you want to build the base tools mandoc(1),
+preconv(1) and demandoc(1) only or whether you also want to build the
+database tools apropos(1) and makewhatis(8). For the latter,
+the following dependencies are required:
+
+1.1. The SQLite database system, see <http://sqlite.org/>.
+The recommended version of SQLite is 3.8.4.3 or newer. The mandoc
+toolset is known to work with version 3.7.5 or newer. Versions
+older than 3.8.3 may not achieve full performance due to the
+missing SQLITE_DETERMINISTIC optimization flag. Versions older
+than 3.8.0 may not show full error information if opening a database
+fails due to the missing sqlite3_errstr() API. Both are very minor
+problems, apropos(1) is fully usable with SQLite 3.7.5. Versions
+older than 3.7.5 may or may not work, they have not been tested.
+
+1.2. The fts(3) directory traversion functions.
+A compatibility version will be bundled for 1.13.2 but is not available
+yet. If you want apropos(1) and makewhatis(8) but do not have fts(3),
+please stay with mandoc 1.12.3 for now and upgrade first to 1.12.4,
+then to 1.13.2 when these versionns are released. Be careful: the
+glibc version of fts(3) is known to be broken on 32bit platforms,
+see <https://sourceware.org/bugzilla/show_bug.cgi?id=15838>.
+
+1.3. Marc Espie's ohash(3) library.
+If your system does not have it, the bundled compatibility version
+will be used, so you probably need not worry about it.
+
+2. If you choose to build the database tools, too, decide whether
+you also want to build the CGI program, man.cgi(8).
+
+3. Read the beginning of the file "Makefile" from "USER SETTINGS"
+to "END OF USER SETTINGS" and edit it as required. In particular,
+disable "BUILD_TARGETS += db-build" if you do not want database
+support or enable "BUILD_TARGETS += cgi-build" if you do want
+the CGI program.
+
+4. Run "make". No separate "./configure" or "make depend" steps
+are needed. The former is run automatically by "make". The latter
+is a maintainer target. If you merely want to build the released
+version as opposed to doing active development, there is no need
+to regenerate the dependency specifications. Any POSIX-compatible
+make, in particular both BSD make and GNU make, should work.
+
+5. Run "make -n install" and check whether everything will be
+installed to the intended places. Otherwise, edit the *DIR variables
+in the Makefile until it is.
+
+6. Run "sudo make install". If you intend to build a binary
+package using some kind of fake root mechanism, you may need a
+command like "make DESTDIR=... install". Read the *-install targets
+in the "Makefile" to understand how DESTDIR is used.
+
+7. To set up a man.cgi(8) server, read its manual page.
+
+8. To use mandoc(1) as your man(1) formatter, read the "Deployment"
+section below.
+
+
+Checking autoconfiguration quality
+----------------------------------
+If you want to check whether automatic configuration works well
+on your platform, consider the following:
+
+The mandoc package intentionally does not use GNU autoconf because
+we consider that toolset a blatant example of overengineering that
+is obsolete nowadays, since all modern operating systems are now
+reasonably close to POSIX and do not need arcane shell magic any
+longer. If your system does need such magic, consider upgrading
+to reasonably modern POSIX-compliant tools rather than asking for
+autoconf-style workarounds.
+
+As far as mandoc is using any features not mandated by ANSI X3.159-1989
+("ANSI C") or IEEE Std 1003.1-2008 ("POSIX") that some modern systems
+do not have, we intend to provide autoconfiguration tests and
+compat_*.c implementations. Please report any that turn out to be
+missing. Note that while we do strive to produce portable code,
+we do not slavishly restrict ourselves to POSIX-only interfaces.
+For improved security and readability, we do use well-designed,
+modern interfaces like reallocarray(3) even if they are still rather
+uncommon, of course bundling compat_*.c implementations as needed.
+
+Where mandoc is using ANSI C or POSIX features that some systems
+still lack and that compat_*.c implementations can be provided for
+without too much hassle, we will consider adding them, too, so
+please report whatever is missing on your platform.
+
+The following steps can be used to manually check the automatic
+configuration on your platform:
+
+1. Run "make clean".
+
+2. Run "make config.h"
+
+3. Read the file "config.log". It shows the compiler commands used
+to test the libraries installed on your system and the standard
+output and standard error output these commands produce. Watch out
+for unexpected failures. Those are most likely to happen if headers
+or libraries are installed in unusual places or interfaces defined
+in unusual headers. You can also look at the file "config.h" and
+check that no expected "#define HAVE_*" lines are missing. The
+list of tests run can be found in the file "configure".
+
+
+Deployment
+----------
+If you want to integrate the mandoc(1) tools with your existing
+man(1) system as a formatter, then contact us first: on systems without
+mandoc(1) as the default, you may have your work cut out for you!
+Usually, you can have your default installation and mandoc(1) work right
+alongside each other by using user-specific versions of the files
+mentioned below.
+
+0. Back up each file you want to change!
+
+1. First see whether your system has "/etc/man.conf" or "/etc/manpath.conf"
+(if it has neither, but man(1) is functional, then let us know) or,
+if running as your own user, a per-user override file. In either
+case, find where man(1) is executing nroff(1) or groff(1) to format
+manuals. Replace these calls with mandoc(1).
+
+2. Then make sure that man(1) isn't running preprocessors, so you may
+need to replace tbl(1), eqn(1), and similar references with cat(1).
+Some man(1) implementations, like that on Mac OSX, let you run "man -d"
+to see how the formatter is invoked. Use this to test your changes. On
+Mac OS X, for instance, man(1) will prepend all files with ".ll" and
+".nr" to set the terminal size, so you need to pass "tail -n+2 |
+mandoc(1)" to disregard them.
+
+3. Finally, make sure that mandoc(1) is actually being invoked instead
+of cached pages being pulled up. You can usually do this by commenting
+out NOCACHE or similar.
+
+mandoc(1) still has a long way to go in understanding non-trivial
+low-level roff(7) markup embedded in some man(7) pages. On the BSD
+systems using mandoc(1), third-party software is generally vetted
+on whether it may be formatted with mandoc(1). If not, groff(1)
+is pulled in as a dependency and used to install a pre-formatted
+"catpage" intead of directly as manual page source.
+
+For more background on switching operating systems to use mandoc(1)
+instead of groff(1) to format manuals, see the two BSDCan presentations
+by Ingo Schwarze:
+<http://www.openbsd.org/papers/bsdcan11-mandoc-openbsd.html>
+<http://www.openbsd.org/papers/bsdcan14-mandoc.pdf>
diff --git a/contrib/mdocml/LICENSE b/contrib/mdocml/LICENSE
new file mode 100644
index 0000000..35072fb
--- /dev/null
+++ b/contrib/mdocml/LICENSE
@@ -0,0 +1,44 @@
+$Id: LICENSE,v 1.2 2014/04/23 21:06:41 schwarze Exp $
+
+With the exceptions noted below, all code and documentation
+contained in the mdocml toolkit is protected by the Copyright
+of the following developers:
+
+Copyright (c) 2008, 2009, 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+Copyright (c) 2010, 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+Copyright (c) 2009, 2010, 2011, 2012 Joerg Sonnenberger <joerg@netbsd.org>
+Copyright (c) 2013 Franco Fichtner <franco@lastsummer.de>
+Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+Copyright (c) 1998, 2010 Todd C. Miller <Todd.Miller@courtesan.com>
+Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+Copyright (c) 2003 Jason McIntyre <jmc@openbsd.org>
+
+See the individual source files for information about who contributed
+to which file during which years.
+
+
+The mdocml distribution as a whole is distributed by its developers
+under the following license:
+
+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 following files included from outside sources are protected by
+other people's Copyright and are distributed under a 3-clause BSD
+license; see these individual files for details.
+
+compat_getsubopt.c, compat_strcasestr.c, compat_strsep.c:
+Copyright (c) 1990, 1993 The Regents of the University of California
+
+compat_fgetln.c:
+Copyright (c) 1998 The NetBSD Foundation, Inc.
diff --git a/contrib/mdocml/Makefile b/contrib/mdocml/Makefile
new file mode 100644
index 0000000..47f37a7
--- /dev/null
+++ b/contrib/mdocml/Makefile
@@ -0,0 +1,505 @@
+# $Id: Makefile,v 1.435 2014/08/10 02:45:04 schwarze Exp $
+#
+# Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+# Copyright (c) 2011, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+VERSION = 1.13.1
+
+# === USER SETTINGS ====================================================
+
+# --- user settings relevant for all builds ----------------------------
+
+# Specify this if you want to hard-code the operating system to appear
+# in the lower-left hand corner of -mdoc manuals.
+#
+# CFLAGS += -DOSNAME="\"OpenBSD 5.5\""
+
+# IFF your system supports multi-byte functions (setlocale(), wcwidth(),
+# putwchar()) AND has __STDC_ISO_10646__ (that is, wchar_t is simply a
+# UCS-4 value) should you define USE_WCHAR. If you define it and your
+# system DOESN'T support this, -Tlocale will produce garbage.
+# If you don't define it, -Tlocale is a synonym for -Tacsii.
+#
+CFLAGS += -DUSE_WCHAR
+
+CFLAGS += -g -DHAVE_CONFIG_H
+CFLAGS += -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings
+PREFIX = /usr/local
+BINDIR = $(PREFIX)/bin
+INCLUDEDIR = $(PREFIX)/include/mandoc
+LIBDIR = $(PREFIX)/lib/mandoc
+MANDIR = $(PREFIX)/man
+EXAMPLEDIR = $(PREFIX)/share/examples/mandoc
+
+INSTALL = install
+INSTALL_PROGRAM = $(INSTALL) -m 0555
+INSTALL_DATA = $(INSTALL) -m 0444
+INSTALL_LIB = $(INSTALL) -m 0444
+INSTALL_SOURCE = $(INSTALL) -m 0644
+INSTALL_MAN = $(INSTALL_DATA)
+
+# --- user settings related to database support ------------------------
+
+# Building apropos(1) and makewhatis(8) requires both SQLite3 and fts(3).
+# To avoid those dependencies, comment the following line.
+# Be careful: the fts(3) implementation in glibc is broken on 32bit
+# machines, see: https://sourceware.org/bugzilla/show_bug.cgi?id=15838
+#
+BUILD_TARGETS += db-build
+
+# The remaining settings in this section
+# are only relevant if db-build is enabled.
+# Otherwise, they have no effect either way.
+
+# If your system has manpath(1), uncomment this. This is most any
+# system that's not OpenBSD or NetBSD. If uncommented, apropos(1)
+# and makewhatis(8) will use manpath(1) to get the MANPATH variable.
+#
+#CFLAGS += -DUSE_MANPATH
+
+# On some systems, SQLite3 may be installed below /usr/local.
+# In that case, uncomment the following two lines.
+#
+#CFLAGS += -I/usr/local/include
+#DBLIB += -L/usr/local/lib
+
+# OpenBSD has the ohash functions in libutil.
+# Comment the following line if your system doesn't.
+#
+DBLIB += -lutil
+
+SBINDIR = $(PREFIX)/sbin
+
+# --- user settings related to man.cgi ---------------------------------
+
+# To build man.cgi, copy cgi.h.example to cgi.h, edit it,
+# and enable the following line.
+# Obviously, this requires that db-build is enabled, too.
+#
+#BUILD_TARGETS += cgi-build
+
+# The remaining settings in this section
+# are only relevant if cgi-build is enabled.
+# Otherwise, they have no effect either way.
+
+# If your system does not support static binaries, comment this,
+# for example on Mac OS X.
+#
+STATIC = -static
+
+# Linux requires -pthread for statical linking.
+#
+#STATIC += -pthread
+
+WWWPREFIX = /var/www
+HTDOCDIR = $(WWWPREFIX)/htdocs
+CGIBINDIR = $(WWWPREFIX)/cgi-bin
+
+# === END OF USER SETTINGS =============================================
+
+INSTALL_TARGETS = $(BUILD_TARGETS:-build=-install)
+
+BASEBIN = mandoc preconv demandoc
+DBBIN = apropos makewhatis
+CGIBIN = man.cgi
+
+DBLIB += -lsqlite3
+
+TESTSRCS = test-fgetln.c \
+ test-getsubopt.c \
+ test-mmap.c \
+ test-ohash.c \
+ test-reallocarray.c \
+ test-sqlite3_errstr.c \
+ test-strcasestr.c \
+ test-strlcat.c \
+ test-strlcpy.c \
+ test-strptime.c \
+ test-strsep.c
+
+SRCS = apropos.c \
+ arch.c \
+ att.c \
+ cgi.c \
+ chars.c \
+ compat_fgetln.c \
+ compat_getsubopt.c \
+ compat_ohash.c \
+ compat_reallocarray.c \
+ compat_sqlite3_errstr.c \
+ compat_strcasestr.c \
+ compat_strlcat.c \
+ compat_strlcpy.c \
+ compat_strsep.c \
+ demandoc.c \
+ eqn.c \
+ eqn_html.c \
+ eqn_term.c \
+ html.c \
+ lib.c \
+ main.c \
+ man.c \
+ man_hash.c \
+ man_html.c \
+ man_macro.c \
+ man_term.c \
+ man_validate.c \
+ mandoc.c \
+ mandoc_aux.c \
+ mandocdb.c \
+ manpage.c \
+ manpath.c \
+ mansearch.c \
+ mansearch_const.c \
+ mdoc.c \
+ mdoc_argv.c \
+ mdoc_hash.c \
+ mdoc_html.c \
+ mdoc_macro.c \
+ mdoc_man.c \
+ mdoc_term.c \
+ mdoc_validate.c \
+ msec.c \
+ out.c \
+ preconv.c \
+ read.c \
+ roff.c \
+ st.c \
+ tbl.c \
+ tbl_data.c \
+ tbl_html.c \
+ tbl_layout.c \
+ tbl_opts.c \
+ tbl_term.c \
+ term.c \
+ term_ascii.c \
+ term_ps.c \
+ tree.c \
+ vol.c \
+ $(TESTSRCS)
+
+DISTFILES = INSTALL \
+ LICENSE \
+ Makefile \
+ Makefile.depend \
+ NEWS \
+ TODO \
+ apropos.1 \
+ arch.in \
+ att.in \
+ cgi.h.example \
+ chars.in \
+ compat_ohash.h \
+ config.h.post \
+ config.h.pre \
+ configure \
+ demandoc.1 \
+ eqn.7 \
+ example.style.css \
+ gmdiff \
+ html.h \
+ lib.in \
+ libman.h \
+ libmandoc.h \
+ libmdoc.h \
+ libroff.h \
+ main.h \
+ makewhatis.8 \
+ man-cgi.css \
+ man.7 \
+ man.cgi.8 \
+ man.h \
+ mandoc.1 \
+ mandoc.3 \
+ mandoc.db.5 \
+ mandoc.h \
+ mandoc_aux.h \
+ mandoc_char.7 \
+ mandoc_escape.3 \
+ mandoc_html.3 \
+ mandoc_malloc.3 \
+ manpath.h \
+ mansearch.3 \
+ mansearch.h \
+ mchars_alloc.3 \
+ mdoc.7 \
+ mdoc.h \
+ msec.in \
+ out.h \
+ preconv.1 \
+ predefs.in \
+ roff.7 \
+ st.in \
+ style.css \
+ tbl.3 \
+ tbl.7 \
+ term.h \
+ vol.in \
+ $(SRCS)
+
+LIBMAN_OBJS = man.o \
+ man_hash.o \
+ man_macro.o \
+ man_validate.o
+
+LIBMDOC_OBJS = arch.o \
+ att.o \
+ lib.o \
+ mdoc.o \
+ mdoc_argv.o \
+ mdoc_hash.o \
+ mdoc_macro.o \
+ mdoc_validate.o \
+ st.o \
+ vol.o
+
+LIBROFF_OBJS = eqn.o \
+ roff.o \
+ tbl.o \
+ tbl_data.o \
+ tbl_layout.o \
+ tbl_opts.o
+
+LIBMANDOC_OBJS = $(LIBMAN_OBJS) \
+ $(LIBMDOC_OBJS) \
+ $(LIBROFF_OBJS) \
+ chars.o \
+ mandoc.o \
+ mandoc_aux.o \
+ msec.o \
+ read.o
+
+COMPAT_OBJS = compat_fgetln.o \
+ compat_getsubopt.o \
+ compat_ohash.o \
+ compat_reallocarray.o \
+ compat_sqlite3_errstr.o \
+ compat_strcasestr.o \
+ compat_strlcat.o \
+ compat_strlcpy.o \
+ compat_strsep.o
+
+MANDOC_HTML_OBJS = eqn_html.o \
+ html.o \
+ man_html.o \
+ mdoc_html.o \
+ tbl_html.o
+
+MANDOC_MAN_OBJS = mdoc_man.o
+
+MANDOC_TERM_OBJS = eqn_term.o \
+ man_term.o \
+ mdoc_term.o \
+ term.o \
+ term_ascii.o \
+ term_ps.o \
+ tbl_term.o
+
+MANDOC_OBJS = $(MANDOC_HTML_OBJS) \
+ $(MANDOC_MAN_OBJS) \
+ $(MANDOC_TERM_OBJS) \
+ main.o \
+ out.o \
+ tree.o
+
+MAKEWHATIS_OBJS = mandocdb.o mansearch_const.o manpath.o
+
+PRECONV_OBJS = preconv.o
+
+APROPOS_OBJS = apropos.o mansearch.o mansearch_const.o manpath.o
+
+CGI_OBJS = $(MANDOC_HTML_OBJS) \
+ cgi.o \
+ mansearch.o \
+ mansearch_const.o \
+ out.o
+
+MANPAGE_OBJS = manpage.o mansearch.o mansearch_const.o manpath.o
+
+DEMANDOC_OBJS = demandoc.o
+
+WWW_MANS = apropos.1.html \
+ demandoc.1.html \
+ mandoc.1.html \
+ preconv.1.html \
+ mandoc.3.html \
+ mandoc_escape.3.html \
+ mandoc_html.3.html \
+ mandoc_malloc.3.html \
+ mansearch.3.html \
+ mchars_alloc.3.html \
+ tbl.3.html \
+ mandoc.db.5.html \
+ eqn.7.html \
+ man.7.html \
+ mandoc_char.7.html \
+ mdoc.7.html \
+ roff.7.html \
+ tbl.7.html \
+ makewhatis.8.html \
+ man.cgi.8.html \
+ man.h.html \
+ mandoc.h.html \
+ mandoc_aux.h.html \
+ manpath.h.html \
+ mansearch.h.html \
+ mdoc.h.html
+
+WWW_OBJS = mdocml.tar.gz \
+ mdocml.sha256
+
+# === DEPENDENCY HANDLING ==============================================
+
+all: base-build $(BUILD_TARGETS)
+
+base-build: $(BASEBIN)
+
+db-build: $(DBBIN)
+
+cgi-build: $(CGIBIN)
+
+install: base-install $(INSTALL_TARGETS)
+
+www: $(WWW_OBJS) $(WWW_MANS)
+
+include Makefile.depend
+
+# === TARGETS CONTAINING SHELL COMMANDS ================================
+
+clean:
+ rm -f libmandoc.a $(LIBMANDOC_OBJS)
+ rm -f apropos $(APROPOS_OBJS)
+ rm -f makewhatis $(MAKEWHATIS_OBJS)
+ rm -f preconv $(PRECONV_OBJS)
+ rm -f man.cgi $(CGI_OBJS)
+ rm -f manpage $(MANPAGE_OBJS)
+ rm -f demandoc $(DEMANDOC_OBJS)
+ rm -f mandoc $(MANDOC_OBJS)
+ rm -f config.h config.log $(COMPAT_OBJS)
+ rm -f $(WWW_MANS) $(WWW_OBJS)
+ rm -rf *.dSYM
+
+base-install: base-build
+ mkdir -p $(DESTDIR)$(BINDIR)
+ mkdir -p $(DESTDIR)$(EXAMPLEDIR)
+ mkdir -p $(DESTDIR)$(LIBDIR)
+ mkdir -p $(DESTDIR)$(INCLUDEDIR)
+ mkdir -p $(DESTDIR)$(MANDIR)/man1
+ mkdir -p $(DESTDIR)$(MANDIR)/man3
+ mkdir -p $(DESTDIR)$(MANDIR)/man7
+ $(INSTALL_PROGRAM) $(BASEBIN) $(DESTDIR)$(BINDIR)
+ $(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR)
+ $(INSTALL_LIB) man.h mandoc.h mandoc_aux.h mdoc.h \
+ $(DESTDIR)$(INCLUDEDIR)
+ $(INSTALL_MAN) mandoc.1 preconv.1 demandoc.1 $(DESTDIR)$(MANDIR)/man1
+ $(INSTALL_MAN) mandoc.3 mandoc_escape.3 mandoc_malloc.3 \
+ mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3
+ $(INSTALL_MAN) man.7 mdoc.7 roff.7 eqn.7 tbl.7 mandoc_char.7 \
+ $(DESTDIR)$(MANDIR)/man7
+ $(INSTALL_DATA) example.style.css $(DESTDIR)$(EXAMPLEDIR)
+
+db-install: db-build
+ mkdir -p $(DESTDIR)$(BINDIR)
+ mkdir -p $(DESTDIR)$(SBINDIR)
+ mkdir -p $(DESTDIR)$(MANDIR)/man1
+ mkdir -p $(DESTDIR)$(MANDIR)/man3
+ mkdir -p $(DESTDIR)$(MANDIR)/man5
+ mkdir -p $(DESTDIR)$(MANDIR)/man8
+ $(INSTALL_PROGRAM) apropos $(DESTDIR)$(BINDIR)
+ ln -f $(DESTDIR)$(BINDIR)/apropos $(DESTDIR)$(BINDIR)/whatis
+ $(INSTALL_PROGRAM) makewhatis $(DESTDIR)$(SBINDIR)
+ $(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1
+ ln -f $(DESTDIR)$(MANDIR)/man1/apropos.1 \
+ $(DESTDIR)$(MANDIR)/man1/whatis.1
+ $(INSTALL_MAN) mansearch.3 $(DESTDIR)$(MANDIR)/man3
+ $(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5
+ $(INSTALL_MAN) makewhatis.8 $(DESTDIR)$(MANDIR)/man8
+
+cgi-install: cgi-build
+ mkdir -p $(DESTDIR)$(CGIBINDIR)
+ mkdir -p $(DESTDIR)$(HTDOCDIR)
+ mkdir -p $(DESTDIR)$(WWWPREFIX)/man/mandoc/man1
+ mkdir -p $(DESTDIR)$(WWWPREFIX)/man/mandoc/man8
+ $(INSTALL_PROGRAM) man.cgi $(DESTDIR)$(CGIBINDIR)
+ $(INSTALL_DATA) example.style.css $(DESTDIR)$(HTDOCDIR)/man.css
+ $(INSTALL_DATA) man-cgi.css $(DESTDIR)$(HTDOCDIR)
+ $(INSTALL_MAN) apropos.1 $(DESTDIR)$(WWWPREFIX)/man/mandoc/man1/
+ $(INSTALL_MAN) man.cgi.8 $(DESTDIR)$(WWWPREFIX)/man/mandoc/man8/
+
+www-install: www
+ mkdir -p $(DESTDIR)$(HTDOCDIR)/snapshots
+ $(INSTALL_DATA) $(WWW_MANS) style.css $(DESTDIR)$(HTDOCDIR)
+ $(INSTALL_DATA) $(WWW_OBJS) $(DESTDIR)$(HTDOCDIR)/snapshots
+ $(INSTALL_DATA) mdocml.tar.gz \
+ $(DESTDIR)$(HTDOCDIR)/snapshots/mdocml-$(VERSION).tar.gz
+ $(INSTALL_DATA) mdocml.sha256 \
+ $(DESTDIR)$(HTDOCDIR)/snapshots/mdocml-$(VERSION).sha256
+
+depend: config.h
+ mkdep -f Makefile.depend $(CFLAGS) $(SRCS)
+ perl -e 'undef $$/; $$_ = <>; s|/usr/include/\S+||g; \
+ s|\\\n||g; s| +| |g; print;' Makefile.depend > Makefile.tmp
+ mv Makefile.tmp Makefile.depend
+
+libmandoc.a: $(COMPAT_OBJS) $(LIBMANDOC_OBJS)
+ $(AR) rs $@ $(COMPAT_OBJS) $(LIBMANDOC_OBJS)
+
+mandoc: $(MANDOC_OBJS) libmandoc.a
+ $(CC) $(LDFLAGS) -o $@ $(MANDOC_OBJS) libmandoc.a
+
+makewhatis: $(MAKEWHATIS_OBJS) libmandoc.a
+ $(CC) $(LDFLAGS) -o $@ $(MAKEWHATIS_OBJS) libmandoc.a $(DBLIB)
+
+preconv: $(PRECONV_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(PRECONV_OBJS)
+
+manpage: $(MANPAGE_OBJS) libmandoc.a
+ $(CC) $(LDFLAGS) -o $@ $(MANPAGE_OBJS) libmandoc.a $(DBLIB)
+
+apropos: $(APROPOS_OBJS) libmandoc.a
+ $(CC) $(LDFLAGS) -o $@ $(APROPOS_OBJS) libmandoc.a $(DBLIB)
+
+man.cgi: $(CGI_OBJS) libmandoc.a
+ $(CC) $(LDFLAGS) $(STATIC) -o $@ $(CGI_OBJS) libmandoc.a $(DBLIB)
+
+demandoc: $(DEMANDOC_OBJS) libmandoc.a
+ $(CC) $(LDFLAGS) -o $@ $(DEMANDOC_OBJS) libmandoc.a
+
+mdocml.sha256: mdocml.tar.gz
+ sha256 mdocml.tar.gz > $@
+
+mdocml.tar.gz: $(DISTFILES)
+ mkdir -p .dist/mdocml-$(VERSION)/
+ $(INSTALL_SOURCE) $(DISTFILES) .dist/mdocml-$(VERSION)
+ chmod 755 .dist/mdocml-$(VERSION)/configure
+ ( cd .dist/ && tar zcf ../$@ mdocml-$(VERSION) )
+ rm -rf .dist/
+
+config.h: configure config.h.pre config.h.post $(TESTSRCS)
+ rm -f config.log
+ CC="$(CC)" CFLAGS="$(CFLAGS)" DBLIB="$(DBLIB)" \
+ VERSION="$(VERSION)" ./configure
+
+.PHONY: base-install cgi-install db-install install www-install
+.PHONY: clean depend
+.SUFFIXES: .1 .3 .5 .7 .8 .h
+.SUFFIXES: .1.html .3.html .5.html .7.html .8.html .h.html
+
+.h.h.html:
+ highlight -I $< > $@
+
+.1.1.html .3.3.html .5.5.html .7.7.html .8.8.html: mandoc
+ ./mandoc -Thtml -Wall,stop \
+ -Ostyle=style.css,man=%N.%S.html,includes=%I.html $< > $@
diff --git a/contrib/mdocml/Makefile.depend b/contrib/mdocml/Makefile.depend
new file mode 100644
index 0000000..dc49310
--- /dev/null
+++ b/contrib/mdocml/Makefile.depend
@@ -0,0 +1,70 @@
+apropos.o: apropos.c config.h manpath.h mansearch.h
+arch.o: arch.c config.h mdoc.h libmdoc.h arch.in
+att.o: att.c config.h mdoc.h libmdoc.h att.in
+cgi.o: cgi.c config.h mandoc.h mandoc_aux.h main.h manpath.h mansearch.h cgi.h
+chars.o: chars.c config.h mandoc.h mandoc_aux.h libmandoc.h chars.in
+compat_fgetln.o: compat_fgetln.c config.h
+compat_getsubopt.o: compat_getsubopt.c config.h
+compat_ohash.o: compat_ohash.c config.h
+compat_reallocarray.o: compat_reallocarray.c config.h
+compat_sqlite3_errstr.o: compat_sqlite3_errstr.c config.h
+compat_strcasestr.o: compat_strcasestr.c config.h
+compat_strlcat.o: compat_strlcat.c config.h
+compat_strlcpy.o: compat_strlcpy.c config.h
+compat_strsep.o: compat_strsep.c config.h
+demandoc.o: demandoc.c config.h man.h mdoc.h mandoc.h
+eqn.o: eqn.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h
+eqn_html.o: eqn_html.c config.h mandoc.h out.h html.h
+eqn_term.o: eqn_term.c config.h mandoc.h out.h term.h
+html.o: html.c config.h mandoc.h mandoc_aux.h libmandoc.h out.h html.h main.h
+lib.o: lib.c config.h mdoc.h libmdoc.h lib.in
+main.o: main.c config.h mandoc.h mandoc_aux.h main.h mdoc.h man.h
+man.o: man.c config.h man.h mandoc.h mandoc_aux.h libman.h libmandoc.h
+man_hash.o: man_hash.c config.h man.h mandoc.h libman.h
+man_html.o: man_html.c config.h mandoc.h mandoc_aux.h out.h html.h man.h main.h
+man_macro.o: man_macro.c config.h man.h mandoc.h libmandoc.h libman.h
+man_term.o: man_term.c config.h mandoc.h mandoc_aux.h out.h man.h term.h main.h
+man_validate.o: man_validate.c config.h man.h mandoc.h mandoc_aux.h libman.h libmandoc.h
+mandoc.o: mandoc.c config.h mandoc.h mandoc_aux.h libmandoc.h
+mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h
+mandocdb.o: mandocdb.c config.h mdoc.h man.h mandoc.h mandoc_aux.h manpath.h mansearch.h
+manpage.o: manpage.c config.h manpath.h mansearch.h
+manpath.o: manpath.c config.h mandoc_aux.h manpath.h
+mansearch.o: mansearch.c config.h mandoc.h mandoc_aux.h manpath.h mansearch.h
+mansearch_const.o: mansearch_const.c config.h manpath.h mansearch.h
+mdoc.o: mdoc.c config.h mdoc.h mandoc.h mandoc_aux.h libmdoc.h libmandoc.h
+mdoc_argv.o: mdoc_argv.c config.h mdoc.h mandoc.h mandoc_aux.h libmdoc.h libmandoc.h
+mdoc_hash.o: mdoc_hash.c config.h mdoc.h libmdoc.h
+mdoc_html.o: mdoc_html.c config.h mandoc.h mandoc_aux.h out.h html.h mdoc.h main.h
+mdoc_macro.o: mdoc_macro.c config.h mdoc.h mandoc.h libmdoc.h libmandoc.h
+mdoc_man.o: mdoc_man.c config.h mandoc.h mandoc_aux.h out.h man.h mdoc.h main.h
+mdoc_term.o: mdoc_term.c config.h mandoc.h mandoc_aux.h out.h term.h mdoc.h main.h
+mdoc_validate.o: mdoc_validate.c config.h mdoc.h mandoc.h mandoc_aux.h libmdoc.h libmandoc.h
+msec.o: msec.c config.h mandoc.h libmandoc.h msec.in
+out.o: out.c config.h mandoc_aux.h mandoc.h out.h
+preconv.o: preconv.c config.h
+read.o: read.c config.h mandoc.h mandoc_aux.h libmandoc.h mdoc.h man.h main.h
+roff.o: roff.c config.h mandoc.h mandoc_aux.h libroff.h libmandoc.h predefs.in
+st.o: st.c config.h mdoc.h libmdoc.h st.in
+tbl.o: tbl.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h
+tbl_data.o: tbl_data.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h
+tbl_html.o: tbl_html.c config.h mandoc.h out.h html.h
+tbl_layout.o: tbl_layout.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h
+tbl_opts.o: tbl_opts.c config.h mandoc.h libmandoc.h libroff.h
+tbl_term.o: tbl_term.c config.h mandoc.h out.h term.h
+term.o: term.c config.h mandoc.h mandoc_aux.h out.h term.h main.h
+term_ascii.o: term_ascii.c config.h mandoc.h mandoc_aux.h out.h term.h main.h
+term_ps.o: term_ps.c config.h mandoc.h mandoc_aux.h out.h main.h term.h
+tree.o: tree.c config.h mandoc.h mdoc.h man.h main.h
+vol.o: vol.c config.h mdoc.h libmdoc.h vol.in
+test-fgetln.o: test-fgetln.c
+test-getsubopt.o: test-getsubopt.c
+test-mmap.o: test-mmap.c
+test-ohash.o: test-ohash.c
+test-reallocarray.o: test-reallocarray.c
+test-sqlite3_errstr.o: test-sqlite3_errstr.c
+test-strcasestr.o: test-strcasestr.c
+test-strlcat.o: test-strlcat.c
+test-strlcpy.o: test-strlcpy.c
+test-strptime.o: test-strptime.c
+test-strsep.o: test-strsep.c
diff --git a/contrib/mdocml/NEWS b/contrib/mdocml/NEWS
new file mode 100644
index 0000000..61006b5
--- /dev/null
+++ b/contrib/mdocml/NEWS
@@ -0,0 +1,450 @@
+$Id: NEWS,v 1.5 2014/08/10 16:32:57 schwarze Exp $
+
+This file lists the most important changes in the mdocml.bsd.lv distribution.
+
+Changes in version 1.13.1, released on August 10, 2014
+
+ --- MAJOR NEW FEATURES ---
+ * A complete apropos(1)/makewhatis(8)/man.cgi(8) suite
+ based on SQLite3 is now included.
+ CAVEAT: This also requires a working fts(3) implementation.
+ If your system lacks that *and* you want apropos(1)/makewhatis(8),
+ stay with 1.12.3 for now, then go to 1.12.4 and 1.13.2.
+ * The roff(7) parser now provides an almost complete implementation
+ of numerical expressions.
+ * Warning and error messages have been improved in many ways.
+ Almost all fatal errors were downgraded to normal errors and some
+ even to warnings. Almost all messages now mention the macro where
+ the issue is detected and many indicate the workaround employed.
+ The mandoc(1) manual now includes a list explaining all messages.
+ --- MINOR NEW FEATURES ---
+ * The roff(7) parser now supports the .ami (append to macro with
+ indirectly specified name), .as (append to user-defined
+ string), .dei (define macro with indirectly specified name),
+ .ll (line length), and .rr (remove register) requests.
+ * The roff(7) parser now supports string comparison and numerical
+ conditionals in the .if and .ie requests.
+ * The roff parser now fully supports the \B (validate numerical
+ expression) and partially supports the \w (measure text width)
+ escape sequences.
+ * The terminal formatter now supports the \: (optional line break)
+ escape sequence.
+ * The roff parser now supports expansion of user-defined strings
+ involving indirect references.
+ * The roff(7) parser now handles some pre-defined read-only
+ number registers that occur in the pod2man(1) preamble.
+ * For backward compatibility, the mdoc(7) parser and formatters
+ now support the obsolete macros .En, .Es, .Fr, and .Ot.
+ * The mdoc(7) formatter non partially supports .Bd -centered.
+ * tbl(7) now handles leading and trailing vertical lines.
+ * The build system now provides fallback versions of strcasestr(3)
+ and strsep(3) for systems lacking them.
+ * The mdoc(7) manual now explains how various standards
+ supported by the .St macro are related to each other.
+ --- BUGFIXES ---
+ * In the roff(7) parser, several bugs were fixed with respect
+ to closing conditional blocks on macro lines.
+ * Parsing of roff(7) identifiers and escape sequences was improved
+ in multiple respects.
+ * In the mdoc(7) parser, the handling of defective document
+ prologues was improved in multiple ways.
+ * The mdoc(7) parser no longer skips content before the first section
+ header, and it no longer deletes non-.% content from .Rs blocks.
+ * In the mdoc(7) parser, a crash was fixed related to weird .Sh headers.
+ * In the mdoc(7) parser, handling of .Sm with missing or invalid
+ arguments was corrected.
+ * In the mdoc(7) parser, trailing punctuation at the end of partial
+ implicit macros no longer triggers end-of-sentence spacing.
+ * In the terminal formatter, two crashes were fixed: one triggered by
+ excessive indentation and another by excessively long .Nm arguments.
+ * In the terminal formatter, a floating point rounding bug was
+ fixed that sometimes caused an off-by-one error in indentation.
+ * In the UTF-8 formatter, rendering of accents, breakable hyphens,
+ and non-breakable spaces was corrected.
+ * In the HTML formatter, encoding of special characters was
+ corrected in multiple respects.
+ * In the mdoc(7) formatter, rendering of .Ex and .Rv was
+ improved for various edge cases.
+ * In the mdoc(7) formatter, handling of empty .Bl -inset item
+ heads was improved.
+ * In the man(7) formatter, some bugs were fixed with respect
+ to same-line detection in the context of .TP and .nf macros,
+ and the indentation of .IP and .TP blocks was improved.
+ * The mandoc(3) library no longer prints to stderr.
+ --- THANKS TO ---
+ Abhinav Upadhyay (NetBSD), Andreas Voegele, Anthony Bentley (OpenBSD),
+ Christian Weisgerber (OpenBSD), Havard Eidnes (NetBSD), Jan Stary,
+ Jason McIntyre (OpenBSD), Jeremie Courreges-Anglas (OpenBSD),
+ Joerg Sonnenberger (NetBSD), Juan Francisco Cantero Hurtado (OpenBSD),
+ Marc Espie (OpenBSD), Matthias Scheler (NetBSD), Pascal Stumpf (OpenBSD),
+ Paul Onyschuk (Alpine Linux), Sebastien Marie, Steffen Nurpmeso,
+ Stuart Henderson (OpenBSD), Ted Unangst (OpenBSD), Theo de Raadt (OpenBSD),
+ Thomas Klausner (NetBSD), and Ulrich Spoerlein (FreeBSD)
+ for reporting bugs and missing features.
+
+Changes in version 1.12.3, released on December 31, 2013
+
+ * In the mdoc(7) SYNOPSIS, line breaks and hanging indentation
+ now work correctly for .Fo/.Fa/.Fc and .Fn blocks.
+ Thanks to Franco Fichtner for doing part of the work.
+ * The mdoc(7) .Bk macro got some addititonal bugfixes.
+ * In mdoc(7) macro arguments, double quotes can now be quoted
+ by doubling them, just like in man(7).
+ Thanks to Tsugutomo ENAMI for the patch.
+ * At the end of man(7) macro lines, end-of-sentence spacing
+ now works. Thanks to Franco Fichtner for the patch.
+ * For backward compatibility, the man(7) parser now supports the
+ man-ext .UR/.UE (uniform resource identifier) block macros.
+ * The man(7) parser now handles closing blocks that are not open
+ more gracefully.
+ * The man(7) parser now ignores blank lines right after .SH and .SS.
+ * In the man(7) formatter, reset indentation when leaving a block,
+ not just when entering the next one.
+ * The roff(7) .nr request now supports incrementing and decrementing
+ number registers and stops parsing the number right before the
+ first non-digit character.
+ * The roff(7) parser now supports the alternative escape sequence
+ syntax \C'uXXXX' for Unicode characters.
+ * The roff(7) parser now parses and ignores the .fam (font family)
+ and .hw (hyphenation points) requests and the \d and \u escape
+ sequences.
+ * The roff(7) manual got a new ESCAPE SEQUENCE REFERENCE.
+
+Changes in version 1.12.2, released on Oktober 5, 2013
+
+ * The mdoc(7) to man(7) converter, to be called as mandoc -Tman,
+ is now fully functional.
+ * The mandoc(1) utility now supports the -Ios (default operating system)
+ input option, and the -Tutf8 output mode now actually works.
+ * The mandocdb(8) utility no longer truncates existing databases when
+ starting to build new ones, but only replaces them when the build
+ actually succeeds.
+ * The man(7) parser now supports the PD macro (paragraph distance),
+ and (for GNU man-ext compatibility only) EX (example block) and EE
+ (example end). Plus several bugfixes regarding indentation, line
+ breaks, and vertical spacing, and regarding RS following TP.
+ * The roff(7) parser now supports the \f(BI (bold+italic) font escape,
+ the \z (zero cursor advance) escape and the cc (change control
+ character) and it (input line trap) requests. Plus bugfixes regarding
+ the \t (tab) escape, nested escape sequences, and conditional requests.
+ * In mdoc(7), several bugs were fixed related to UTF-8 output of quoting
+ enclosures, delimiter handling, list indentation and horizontal and
+ vertical spacing, formatting of the Lk, %U, and %C macros, plus some
+ bugfixes related to the handling of syntax errors like badly nested
+ font blocks, stray Ta macros outside column lists, unterminated It Xo
+ blocks, and non-text children of Nm blocks.
+ * In tbl(7), the width of horizontal spans and the vertical spacing
+ around tables was corrected, and in man(7) files, a crash was fixed
+ that was triggered by some particular unclosed T{ macros.
+ * For mandoc developers, we now provide a tbl(3) library manual and
+ gmdiff, a very small, very simplistic groff-versus-mandoc output
+ comparison tool.
+ * Provide this NEWS file.
+
+Changes in version 1.12.1, released on March 23, 2012
+
+ * Significant work on apropos(1) and mandocdb(8). These tools are now
+ much more robust. A whatis(1) implementation is now handled as an
+ apropos(1) mode. These tools are also able to minimally handle
+ pre-formatted pages, that is, those already formatted by another
+ utility such as GNU troff.
+ * The man.cgi(7) script is also now available for wider testing.
+ It interfaces with mandocdb(8) manuals cached by catman(8).
+ HTML output is generated on-the-fly by libmandoc or internal
+ methods to convert pre-formatted pages.
+ * The mailing list archive for the discuss and tech lists are being
+ hosted by Gmane at gmane.comp.tools.mdocml.user and
+ gmane.comp.tools.mdocml.devel, respectively.
+
+Changes in version 1.12.0, released on October 8, 2011
+
+ * This version features a new, work-in-progress mandoc(1) output mode:
+ -Tman. This mode allows a system maintainer to distribute man(7)
+ media for older systems that may not natively support mdoc(7), such
+ as old Solaris systems.
+ * The -Ofragment option was added to mandoc(1)'s -Thtml and -Txhtml modes.
+ * While adding features, an apropos(1) utility has been merged from the
+ mandoc-tools sandbox. This interfaces with mandocdb(8) for semantic
+ search of manual content. apropos(1) is different from the traditional
+ apropos primarily in allowing keyword search (such as for functions,
+ utilities, etc.) and regular expressions. Note that the calling
+ syntax for apropos is likely to change as it settles down.
+ * In documentation news, the mdoc(7) and man(7) manuals have been
+ made considerably more readable by adding MACRO OVERVIEW sections, by
+ moving the gory details of the LANGUAGE SYNTAX to the roff(7) manual,
+ and by moving the very technical MACRO SYNTAX sections down to the
+ bottom of the page.
+ * Furthermore, for tbl(7), the -Tascii mode horizontal spacing of tables
+ was rewritten completely. It is now compatible with groff(1), both
+ with and without frames and rulers.
+ * Nesting of indented blocks is now supported in man(7), and several
+ bugs were fixed regarding indentation and alignment.
+ * The page headers in mdoc(7) are now nicer for very long titles.
+
+Changes in version 1.11.7, released on September 2, 2011
+
+ * Added demandoc(1) utility for stripping away macros and escapes.
+ This replaces the historical deroff(1) utility.
+ * Also improved the mdoc(7) and man(7) manuals.
+
+Changes in version 1.11.6, released on August 16, 2011
+
+ * Handling of tr macro in roff(7) implemented. This makes Perl
+ documentation much more readable. Hyphenation is also now enabled in
+ man(7) format documents. Many other general improvements have been
+ implemented.
+
+Changes in version 1.11.5, released on July 24, 2011
+
+ * Significant eqn(7) improvements. mdocml can now parse arbitrary eqn
+ input (although few GNU extensions are accepted, nor is mixing
+ low-level roff with eqn). See the eqn(7) manual for details.
+ For the time being, equations are rendered as simple in-line text.
+ The equation parser satisfies the language specified in the
+ Second Edition User's Guide:
+ http://www.kohala.com/start/troff/v7man/eqn/eqn2e.ps
+
+Changes in version 1.11.4, released on July 12, 2011
+
+ * Bug-fixes and clean-ups across all systems, especially in mandocdb(8)
+ and the man(7) parser. This release was significantly assisted by
+ participants in OpenBSD's c2k11. Thanks!
+
+Changes in version 1.11.3, released on May 26, 2011
+
+ * Introduce locale-encoding of output with the -Tlocale output option and
+ Unicode escaped-character input. See mandoc(1) and mandoc_char(7),
+ respectively, for details. This allows for non-ASCII characters (e.g.,
+ \[u5000]) to be rendered in the locale's encoding, if said environment
+ supports wide-character encoding (if it does not, -Tascii is used
+ instead). Locale support can be turned off at compile time by removing
+ -DUSE_WCHAR in the Makefile, in which case -Tlocale is always a synonym
+ for -Tascii.
+ * Furthermore, multibyte-encoded documents, such as those in UTF-8, may
+ be on-the-fly recoded into mandoc(1) input by using the newly-added
+ preconv(1) utility. Note: in the future, this feature may be
+ integrated into mandoc(1).
+
+Changes in version 1.11.2, released on May 12, 2011
+
+ * Corrected some installation issues in version 1.11.1.
+ * Further migration to libmandoc.
+ * Initial public release (this utility is very much under development)
+ of mandocdb(8). This utility produces keyword databases of manual
+ content, which features semantic querying of manual content.
+
+Changes in version 1.11.1, released on April 4, 2011
+
+ * The earlier libroff, libmdoc, and libman soup have been merged into
+ a single library, libmandoc, which manages all aspects of parsing
+ real manuals, from line-handling to tbl(7) parsing.
+ * As usual, many general fixes and improvements have also occurred.
+ In particular, a great deal of redundancy and superfluous code has
+ been removed with the merging of the backend libraries.
+ * see also the changes in 1.10.10
+
+Changes in version 1.10.10, March 20, 2011, NOT released
+
+ * Initial eqn(7) functionality is in place. For the time being,
+ this is limited to the recognition of equation blocks;
+ future version of mdocml will expand upon this framework.
+
+Changes in version 1.10.9, released on January 7, 2011
+
+ * Many back-end fixes have been implemented: argument handling (quoting),
+ man(7) improvements, error/warning classes, and many more.
+ * Initial tbl(7) functionality (see the "TS", "TE", and "T&" macros in
+ the roff(7) manual) has been merged from tbl.bsd.lv. Output is still
+ minimal, especially for -Thtml and -Txhtml, but manages to at least
+ display data. This means that mandoc(1) now has built-in support
+ for two troff preprocessors via libroff: soelim(1) and tbl(1).
+
+Changes in version 1.10.8, released on December 24, 2010
+
+ * Overhauled the -Thtml and -Txhtml output modes. They now display
+ readable output in arbitrary browsers, including text-based ones like
+ lynx(1). See HTML and XHTML manuals in the DOCUMENTATION section
+ for examples. Attention: available style-sheet classes have been
+ considerably changed! See the example.style.css file for details.
+ Lastly, libmdoc and libman have been cleaned up and reduced in size
+ and complexity.
+ * see also the changes in 1.10.7
+
+Changes in version 1.10.7, December 6, 2010, NOT released
+
+ Significant improvements merged from OpenBSD downstream, including:
+ * many new roff(7) components,
+ * in-line implementation of troff's soelim(1),
+ * broken-block handling,
+ * overhauled error classifications, and
+ * cleaned up handling of error conditions.
+
+Changes in version 1.10.6, released on September 27, 2010
+
+ * Calling conventions for mandoc(1) have changed: -W improved and -f
+ deprecated.
+ * Non-ASCII characters are also now uniformly discarded.
+ * Lots of documentation improvements.
+ * Many incremental fixes accomodating for groff's more interesting
+ productions.
+ * Lastly, pod2man(1) preambles are now fully accepted after some
+ considerable roff(7) and special character support.
+
+Changes in version 1.10.5, released on July 27, 2010
+
+ * Primarily a bug-fix and polish release, but including -Tpdf support
+ in mandoc(1) by way of "Summer of Code". Highlights:
+ * fix "Sm" and "Bd" handling
+ * fix end-of-sentence handling for embedded sentences
+ * polish man(7) documentation
+ * document all mdoc(7) macros
+ * polish mandoc(1) -Tps output
+ * lots of internal clean-ups in character escapes
+ * un-break literal contexts in man(7) documents
+ * improve -Thtml output for -man
+ * add mandoc(1) -Tpdf support
+
+Changes in version 1.10.4, released on July 12, 2010
+
+ * Lots of features developed during both "Summer of Code" and the
+ OpenBSD c2k10 hackathon:
+ * minimal "ds" roff(7) symbols are supported
+ * beautified SYNOPSIS section output
+ * acceptance of scope-block breakage in mdoc(7)
+ * clarify error message status
+ * many minor bug-fixes and formatting issues resolved
+ * see also changes in 1.10.3
+
+Changes in version 1.10.3, June 29, 2010, NOT released
+
+ * variable font-width and paper-size support in mandoc(1) -Tps output
+ * "Bk" mdoc(7) support
+
+Changes in version 1.10.2, released on June 19, 2010
+
+ * Small release featuring text-decoration in -Tps output,
+ a few minor relaxations of errors, and some optimisations.
+
+Changes in version 1.10.1, released on June 7, 2010
+
+ * This primarily focusses on the "Bl" and "It" macros described in
+ mdoc(7). Multi-line column support is now fully compatible with groff,
+ as are implicit list entries for columns.
+ * Removed manuals(7) in favour of http://manpages.bsd.lv.
+ * The way we handle the SYNOPSIS section (see the SYNOPSIS documentation
+ in MANUAL STRUCTURE) has also been considerably simplified compared
+ to groff's method.
+ * Furthermore, the -Owidth=width output option has been added to -Tascii,
+ see mandoc(1).
+ * Lastly, initial PostScript output has been added with the -Tps option
+ to mandoc(1). It's brutally simple at the moment: fixed-font, with no
+ font decorations.
+
+Changes in version 1.10.0, released on May 29, 2010
+
+ * Release consisting of the results from the m2k10 hackathon and up-merge
+ from OpenBSD. This requires a significant note of thanks to Ingo
+ Schwarze (OpenBSD) and Joerg Sonnenberger (NetBSD) for their hard work,
+ and again to Joerg for hosting m2k10. Highlights (mostly cribbed from
+ Ingo's m2k10 report) follow in no particular order:
+ * a libroff preprocessor in front of libmdoc and libman stripping out
+ roff(7) instructions;
+ * end-of-sentence (EOS) detection in free-form and macro lines;
+ * correct handling of tab-separated columnar lists in mdoc(7);
+ * improved main calling routines to optionally use mmap(3) for better
+ performance;
+ * cleaned up exiting when invoked as -Tlint or over multiple files
+ with -fign-errors;
+ * error and warning message handling re-written to be unified for
+ libroff, libmdoc, and libman;
+ * handling of badly-nested explicit-scoped macros;
+ * improved free-form text parsing in libman and libmdoc;
+ * significant GNU troff compatibility improvements in -Tascii,
+ largely in terms of spacing;
+ * a regression framework for making sure the many fragilities of GNU
+ troff aren't trampled in subsequent work;
+ * support for -Tascii breaking at hyphens encountered in free-form text;
+ * and many more minor fixes and improvements
+
+Changes in version 1.9.25, released on May 13, 2010
+
+ * Fixed handling of "\*(Ba" escape.
+ * Backed out -fno-ign-chars (pointless complexity).
+ * Fixed erroneous breaking of literal lines.
+ * Fixed SYNOPSIS breaking lines before non-initial macros.
+ * Changed default section ordering.
+ * Most importantly, the framework for end-of-sentence double-spacing is
+ in place, now implemented for the "end-of-sentence, end-of-line" rule.
+ * This is a stable roll-back point before the mandoc hackathon in Rostock!
+
+Changes in version 1.9.24, released on May 9, 2010
+
+ * Rolled back break-at-hyphen.
+ * -DUGLY is now the default (no feature splits!).
+ * Free-form text is not de-chunked any more: lines are passed
+ whole-sale into the front-end, including whitespace.
+ * Added mailing lists.
+
+Changes in version 1.9.23, released on April 7, 2010
+
+ * mdocml has been linked to the OpenBSD build.
+ * This version incorporates many small changes, mostly from patches
+ by OpenBSD, allowing crufty manuals to slip by with warnings instead
+ of erroring-out.
+ * Some subtle semantic issues, such as punctuation scope, have also
+ been fixed.
+ * Lastly, some issues with -Thtml have been fixed, which prompted an
+ update to the online manual pages style layout.
+
+Changes in version 1.9.22, released on March 31, 2010
+
+ * Adjusted merge of the significant work by Ingo Schwarze
+ in getting "Xo" blocks (block full implicit, e.g., "It"
+ for non-columnar lists) to work properly. This isn't
+ enabled by default: you must specify -DUGLY as a compiler
+ flag (see the Makefile for details).
+
+Changes in version 1.9.20, released on March 30, 2010
+
+ * More efforts to get roff instructions in man(7) documents under
+ control. Note that roff instructions embedded in line-scoped,
+ next-line macros (e.g. "B") are not supported.
+ * Leading punctuation for mdoc(7) macros, such as "Fl ( ( a",
+ are now correctly handled.
+
+Changes in version 1.9.18, released on March 27, 2010
+
+ * Many fixes (largely pertaining to scope)
+ and improvements (e.g., handling of apostrophe-control macros,
+ which fixes the strange "BR" seen in some macro output)
+ to handling roff instructions in man(7) documents.
+
+Changes in version 1.9.17, released on March 25, 2010
+
+ * Accept perlpod(1) standard preamble.
+ * Also accept (and discard) "de", "dei", "am", "ami", and "ig"
+ roff macro blocks.
+
+Changes in version 1.9.16, released on March 22, 2010
+
+ * Inspired by patches and bug reports by Ingo Schwarze,
+ allowed man(7) to accept non-printing elements to be nested
+ within next-line scopes, such as "br" within "B" or "TH",
+ which is valid roff.
+ * Longsoon architecture also noted and Makefile cleaned up.
+
+Changes in version 1.9.15, released on February 18, 2010
+
+ * Moved to our new BSD.lv home.
+ * XHTML is now an acceptable output mode for mandoc(1);
+ * "Xr" made more compatible with groff;
+ * "Vt" fixed when invoked in SYNOPSIS;
+ * "\\" escape removed;
+ * end-of-line white-space detected for all lines;
+ * subtle bug fixed in list display for some modes;
+ * compatibility layer checked in for compilation in diverse
+ UNIX systems;
+ * and column lengths handled correctly.
+
+For older releases, see the ChangeLog files
+in http://mdocml.bsd.lv/snapshots/ .
diff --git a/contrib/mdocml/TODO b/contrib/mdocml/TODO
new file mode 100644
index 0000000..a41df29
--- /dev/null
+++ b/contrib/mdocml/TODO
@@ -0,0 +1,437 @@
+************************************************************************
+* Official mandoc TODO.
+* $Id: TODO,v 1.176 2014/08/09 14:24:53 schwarze Exp $
+************************************************************************
+
+************************************************************************
+* crashes
+************************************************************************
+
+- The abort() in bufcat(), html.c, can be triggered via buffmt_includes()
+ by running -Thtml -Oincludes on a file containing a long .In argument.
+ Fixing this will probably require reworking the whole bufcat() concept.
+
+************************************************************************
+* missing features
+************************************************************************
+
+--- missing roff features ----------------------------------------------
+
+- .ad (adjust margins)
+ .ad l -- adjust left margin only (flush left)
+ .ad r -- adjust right margin only (flush right)
+ .ad c -- center text on line
+ .ad b -- adjust both margins (alias: .ad n)
+ .na -- temporarily disable adjustment without changing the mode
+ .ad -- re-enable adjustment without changing the mode
+ Adjustment mode is ignored while in no-fill mode (.nf).
+
+- .fc (field control)
+ found by naddy@ in xloadimage(1)
+
+- .nr third argument (auto-increment step size, requires \n+)
+ found by bentley@ in sbcl(1) Mon, 9 Dec 2013 18:36:57 -0700
+
+- .ns (no-space mode) occurs in xine-config(1)
+ reported by brad@ Sat, 15 Jan 2011 15:45:23 -0500
+
+- .ta (tab settings) occurs in ircbug(1) and probably gnats(1)
+ reported by brad@ Sat, 15 Jan 2011 15:50:51 -0500
+ also Tcl_NewStringObj(3) via wiz@ Wed, 5 Mar 2014 22:27:43 +0100
+
+- .ti (temporary indent)
+ found by naddy@ in xloadimage(1)
+ found by bentley@ in nmh(1) Mon, 23 Apr 2012 13:38:28 -0600
+
+- .while and .shift
+ found by jca@ in ratpoison(1) Sun, 30 Jun 2013 12:01:09 +0200
+
+- \c (interrupted text) should prevent the line break
+ even inside .Bd literal; that occurs in chat(8)
+ also found in cclive(1) - DocBook output
+
+- \h horizontal move
+ found in cclive(1) DocBook output
+ Anthony J. Bentley on discuss@ Sat, 21 Sep 2013 22:29:34 -0600
+
+- \n+ and \n- numerical register increment and decrement
+ found by bentley@ in sbcl(1) Mon, 9 Dec 2013 18:36:57 -0700
+
+- \w'' width measurements
+ would not be very useful without an expression parser, see below
+ needed for Tcl_NewStringObj(3) via wiz@ Wed, 5 Mar 2014 22:27:43 +0100
+
+- using undefined strings or macros defines them to be empty
+ wl@ Mon, 14 Nov 2011 14:37:01 +0000
+
+--- missing mdoc features ----------------------------------------------
+
+- fix bad block nesting involving multiple identical explicit blocks
+ see the OpenBSD mdoc_macro.c 1.47 commit message
+
+- .Bl -column .Xo support is missing
+ ultimate goal:
+ restore .Xr and .Dv to
+ lib/libc/compat-43/sigvec.3
+ lib/libc/gen/signal.3
+ lib/libc/sys/sigaction.2
+
+- edge case: decide how to deal with blk_full bad nesting, e.g.
+ .Sh .Nm .Bk .Nm .Ek .Sh found by jmc@ in ssh-keygen(1)
+ from jmc@ Wed, 14 Jul 2010 18:10:32 +0100
+
+- \\ is now implemented correctly
+ * when defining strings and macros using .ds and .de
+ * when parsing roff(7) and man(7) macro arguments
+ It does not yet work in mdoc(7) macro arguments
+ because libmdoc does not yet use mandoc_getarg().
+ Also check what happens in plain text, it must be identical to \e.
+
+- .Bd -centered implies -filled, not -unfilled, which is not
+ easy to implement; it requires code similar to .ce, which
+ we don't have either.
+ Besides, groff has bug causing text right *before* .Bd -centered
+ to be centered as well.
+
+- .Bd -filled should not be the same as .Bd -ragged, but align both
+ the left and right margin. In groff, it is implemented in terms
+ of .ad b, which we don't have either. Found in cksum(1).
+
+- implement blank `Bl -column', such as
+ .Bl -column
+ .It foo Ta bar
+ .El
+
+- explicitly disallow nested `Bl -column', which would clobber internal
+ flags defined for struct mdoc_macro
+
+- In .Bl -column .It, the end of the line probably has to be regarded
+ as an implicit .Ta, if there could be one, see the following mildly
+ ugly code from login.conf(5):
+ .Bl -column minpasswordlen program xetcxmotd
+ .It path Ta path Ta value of Dv _PATH_DEFPATH
+ .br
+ Default search path.
+ reported by Michal Mazurek <akfaew at jasminek dot net>
+ via jmc@ Thu, 7 Apr 2011 16:00:53 +0059
+
+- inside `.Bl -column' phrases, punctuation is handled like normal
+ text, e.g. `.Bl -column .It Fl x . Ta ...' should give "-x -."
+
+- inside `.Bl -column' phrases, TERMP_IGNDELIM handling by `Pf'
+ is not safe, e.g. `.Bl -column .It Pf a b .' gives "ab."
+ but should give "ab ."
+
+- set a meaningful default if no `Bl' list type is assigned
+
+- have a blank `It' head for `Bl -tag' not puke
+
+- check whether it is correct that `D1' uses INDENT+1;
+ does it need its own constant?
+
+- prohibit `Nm' from having non-text HEAD children
+ (e.g., NetBSD mDNSShared/dns-sd.1)
+ (mdoc_html.c and mdoc_term.c `Nm' handlers can be slightly simplified)
+
+- support translated section names
+ e.g. x11/scrotwm scrotwm_es.1:21:2: error: NAME section must be first
+ that one uses NOMBRE because it is spanish...
+ deraadt tends to think that section-dependent macro behaviour
+ is a bad idea in the first place, so this may be irrelevant
+
+- When there is free text in the SYNOPSIS and that free text contains
+ the .Nm macro, groff somehow understands to treat the .Nm as an in-line
+ macro, while mandoc treats it as a block macro and breaks the line.
+ No idea how the logic for distinguishing in-line and block instances
+ should be, needs investigation.
+ uqs@ Thu, 2 Jun 2011 11:03:51 +0200
+ uqs@ Thu, 2 Jun 2011 11:33:35 +0200
+
+--- missing man features -----------------------------------------------
+
+- -T[x]html doesn't stipulate non-collapsing spaces in literal mode
+
+--- missing tbl features -----------------------------------------------
+
+- look at the POSIX manuals in the books/man-pages-posix port,
+ they use some unsupported tbl(7) features.
+
+- investigate tbl(1) errors in sox(1)
+ see also naddy@ Sat, 16 Oct 2010 23:51:57 +0200
+
+- allow standalone `.' to be interpreted as an end-of-layout
+ delimiter instead of being thrown away as a no-op roff line
+ reported by Yuri Pankov, Wed 18 May 2011 11:34:59 CEST
+
+--- missing misc features ----------------------------------------------
+
+- italic correction (\/) in PostScript mode
+ Werner LEMBERG on groff at gnu dot org Sun, 10 Nov 2013 12:47:46
+
+- When makewhatis(8) encounters a FATAL parse error,
+ it silently treats the file as formatted, which makes no sense
+ at all for paths like man1/foo.1 - and which also contradicts
+ what the manual says at the end of the description.
+ The end result will be ENOENT for file names returned
+ by mansearch() in manpage.file.
+
+- makewhatis(8) for preformatted pages:
+ parse the section number from the header line
+ and compare to the section number from the directory name
+
+- Does makewhatis(8) detect missing NAME sections, missing names,
+ and missing descriptions in all the file formats?
+
+- clean up escape sequence handling, creating three classes:
+ (1) fully implemented, or parsed and ignored without loss of content
+ (2) unimplemented, potentially causing loss of content
+ or serious mangling of formatting (e.g. \n) -> ERROR
+ see textproc/mgdiff(1) for nice examples
+ (3) undefined, just output the character -> perhaps WARNING
+
+- kettenis wants base roff, ms, and me Fri, 1 Jan 2010 22:13:15 +0100 (CET)
+
+--- compatibility checks -----------------------------------------------
+
+- is .Bk implemented correctly in modern groff?
+ sobrado@ Tue, 19 Apr 2011 22:12:55 +0200
+
+- compare output to Heirloom roff, Solaris roff, and
+ http://repo.or.cz/w/neatroff.git http://litcave.rudi.ir/
+
+- look at pages generated from reStructeredText, e.g. devel/mercurial hg(1)
+ These are a weird mixture of man(7) and custom autogenerated low-level
+ roff stuff. Figure out to what extent we can cope.
+ For details, see http://docutils.sourceforge.net/rst.html
+ noted by stsp@ Sat, 24 Apr 2010 09:17:55 +0200
+ reminded by nicm@ Mon, 3 May 2010 09:52:41 +0100
+
+- look at pages generated from ronn(1) github.com/rtomayko/ronn
+ (based on markdown)
+
+- look at pages generated from Texinfo source by yat2m, e.g. security/gnupg
+ First impression is not that bad.
+
+- look at pages generated by pandoc; see
+ https://github.com/jgm/pandoc/blob/master/src/Text/Pandoc/Writers/Man.hs
+ porting planned by kili@ Thu, 19 Jun 2014 19:46:28 +0200
+
+- check compatibility with Plan9:
+ http://swtch.com/usr/local/plan9/tmac/tmac.an
+ http://swtch.com/plan9port/man/man7/man.html
+ "Anthony J. Bentley" <anthonyjbentley@gmail.com> 28 Dec 2010 21:58:40 -0700
+
+- check compatibility with the man(7) formatter
+ https://raw.githubusercontent.com/rofl0r/hardcore-utils/master/man.c
+
+************************************************************************
+* formatting issues: ugly output
+************************************************************************
+
+- a column list with blank `Ta' cells triggers a spurrious
+ start-with-whitespace printing of a newline
+
+- In .Bl -column,
+ .It Em Authentication<tab>Key Length
+ ought to render "Key Length" with emphasis, too,
+ see OpenBSD iked.conf(5).
+ reported again Nicolas Joly via wiz@ Wed, 12 Oct 2011 00:20:00 +0200
+
+- empty phrases in .Bl column produce too few blanks
+ try e.g. .Bl -column It Ta Ta
+ reported by millert Fri, 02 Apr 2010 16:13:46 -0400
+
+- .%T can have trailing punctuation. Currently, it puts the trailing
+ punctuation into a trailing MDOC_TEXT element inside its own scope.
+ That element should rather be outside its scope, such that the
+ punctuation does not get underlines. This is not trivial to
+ implement because .%T then needs some features of in_line_eoln() -
+ slurp all arguments into one single text element - and one feature
+ of in_line() - put trailing punctuation out of scope.
+ Found in mount_nfs(8) and exports(5), search for "Appendix".
+
+- Trailing punctuation after .%T triggers EOS spacing, at least
+ outside .Rs (eek!). Simply setting ARGSFL_DELIM for .%T is not
+ the right solution, it sends mandoc into an endless loop.
+ reported by Nicolas Joly Sat, 17 Nov 2012 11:49:54 +0100
+
+- global variables in the SYNOPSIS of section 3 pages
+ .Vt vs .Vt/.Va vs .Ft/.Va vs .Ft/.Fa ...
+ from kristaps@ Tue, 08 Jun 2010 11:13:32 +0200
+
+- in enclosures, mandoc sometimes fancies a bogus end of sentence
+ reminded by jmc@ Thu, 23 Sep 2010 18:13:39 +0059
+
+- formatting /usr/local/man/man1/latex2man.1 with groff and mandoc
+ reveals lots of bugs both in groff and mandoc...
+ reported by bentley@ Wed, 22 May 2013 23:49:30 -0600
+
+--- PDF issues ---------------------------------------------------------
+
+- PDF output doesn't use a monospaced font for .Bd -literal
+ Example: "mandoc -Tpdf afterboot.8 > output.pdf && pdfviewer output.pdf".
+ Search the text "Routing tables".
+ Also check what PostScript mode does when fixing this.
+ reported by juanfra@ Wed, 04 Jun 2014 21:44:58 +0200
+
+--- HTML issues --------------------------------------------------------
+
+- <dl><dt><dd> formatting is ugly
+ hints are easy to find on the web, e.g.
+ http://stackoverflow.com/questions/1713048/
+ see also matthew@ Fri, 18 Jul 2014 19:25:12 -0700
+
+- check https://github.com/trentm/mdocml
+
+************************************************************************
+* formatting issues: gratuitous differences
+************************************************************************
+
+- .Rv (and probably .Ex) print different text if an `Nm' has been named
+ or not (run a manual without `Nm blah' to see this). I'm not sure
+ that this exists in the wild, but it's still an error.
+
+- In .Bl -bullet, the groff bullet is "+\b+\bo\bo", the mandoc bullet
+ is just "o\bo".
+ see for example OpenBSD ksh(1)
+
+- In .Bl -enum -width 0n, groff continues one the same line after
+ the number, mandoc breaks the line.
+ mail to kristaps@ Mon, 20 Jul 2009 02:21:39 +0200
+
+- .Pp between two .It in .Bl -column should produce one,
+ not two blank lines, see e.g. login.conf(5).
+ reported by jmc@ Sun, 17 Apr 2011 14:04:58 +0059
+ reported again by sthen@ Wed, 18 Jan 2012 02:09:39 +0000 (UTC)
+
+- If the *first* line after .It is .Pp, break the line right after
+ the tag, do not pad with space characters before breaking.
+ See the description of the a, c, and i commands in sed(1).
+
+- If the first line after .It is .D1, do not assert a blank line
+ in between, see for example tmux(1).
+ reported by nicm@ 13 Jan 2011 00:18:57 +0000
+
+- Trailing punctuation after .It should trigger EOS spacing.
+ reported by Nicolas Joly Sat, 17 Nov 2012 11:49:54 +0100
+ Probably, this should be fixed somewhere in termp_it_pre(), not sure.
+
+- .Nx 1.0a
+ should be "NetBSD 1.0A", not "NetBSD 1.0a",
+ see OpenBSD ccdconfig(8).
+
+- In .Bl -tag, if a tag exceeds the right margin and must be continued
+ on the next line, it must be indented by -width, not width+1;
+ see "rule block|pass" in OpenBSD ifconfig(8).
+
+- When the -width string contains macros, the macros must be rendered
+ before measuring the width, for example
+ .Bl -tag -width ".Dv message"
+ in magic(5), located in src/usr.bin/file, is the same
+ as -width 7n, not -width 11n.
+ The same applies to .Bl -column column widths;
+ reported again by Nicolas Joly Thu, 1 Mar 2012 13:41:26 +0100 via wiz@ 5 Mar
+ reported again by Franco Fichtner Fri, 27 Sep 2013 21:02:28 +0200
+ An easy partial fix would be to just skip the first word if it starts
+ with a dot, including any following white space, when measuring.
+
+- The \& zero-width character counts as output.
+ That is, when it is alone on a line between two .Pp,
+ we want three blank lines, not two as in mandoc.
+
+- Header lines of excessive length:
+ Port OpenBSD man_term.c rev. 1.25 to mdoc_term.c
+ and document it in mdoc(7) and man(7) COMPATIBILITY
+ found while talking to Chris Bennett
+
+- trailing whitespace must be ignored even when followed by a font escape,
+ see for example
+ makes
+ \fBdig \fR
+ operate in batch mode
+ in dig(1).
+
+************************************************************************
+* warning issues
+************************************************************************
+
+- check that MANDOCERR_BADTAB is thrown in the right cases,
+ i.e. when finding a literal tab character in fill mode,
+ and possibly change the wording of the warning message
+ to refer to fill mode, not literal mode
+ See the mail from Werner LEMBERG on the groff list,
+ Fri, 14 Feb 2014 18:54:42 +0100 (CET)
+
+- warn about "new sentence, new line"
+
+- mandoc_special does not really check the escape sequence,
+ but just the overall format
+
+- integrate mdoclint into mandoc ("end-of-line whitespace" thread)
+ from jmc@ Mon, 13 Jul 2009 17:12:09 +0100
+ from kristaps@ Mon, 13 Jul 2009 18:34:53 +0200
+ from jmc@ Mon, 13 Jul 2009 17:45:37 +0059
+ from kristaps@ Mon, 13 Jul 2009 19:02:03 +0200
+
+- -Tlint parser errors and warnings to stdout
+ to tech@mdocml, naddy@ Wed, 28 Sep 2011 11:21:46 +0200
+ wait! kristaps@ Sun, 02 Oct 2011 17:12:52 +0200
+
+- for system errors, use errno/strerror/warn/err
+
+************************************************************************
+* documentation issues
+************************************************************************
+
+- mention hyphenation rules:
+ breaking at letter-letter in text mode (not macro args)
+ proper hyphenation is unimplemented
+
+- talk about spacing around delimiters
+ to jmc@, kristaps@ Sat, 23 Apr 2011 17:41:27 +0200
+
+- mark macros as: page structure domain, manual domain, general text domain
+ is this useful?
+
+- mention /usr/share/misc/mdoc.template in mdoc(7)?
+
+************************************************************************
+* performance issues
+************************************************************************
+
+- Why are we using MAP_SHARED, not MAP_PRIVATE for mmap(2)?
+ How does SQLITE_CONFIG_PAGECACHE actually work? Document it!
+ from kristaps@ Sat, 09 Aug 2014 13:51:36 +0200
+
+Several areas can be cleaned up to make mandoc even faster. These are
+
+- improve hashing mechanism for macros (quite important: performance)
+
+- improve hashing mechanism for characters (not as important)
+
+- the PDF file is HUGE: this can be reduced by using relative offsets
+
+- instead of re-initialising the roff predefined-strings set before each
+ parse, create a read-only version the first time and copy it
+
+************************************************************************
+* structural issues
+************************************************************************
+
+- We use the input line number at several places to distinguish
+ same-line from different-line input. That plainly doesn't work
+ with user-defined macros, leading to random breakage.
+
+- Find better ways to prevent endless loops
+ in roff(7) macro and string expansion.
+
+- Finish cleanup of date handling.
+ Decide which formats should be recognized where.
+ Update both mdoc(7) and man(7) documentation.
+ Triggered by Tim van der Molen Tue, 22 Feb 2011 20:30:45 +0100
+
+- Consider creating some views that will make the database more
+ readable from the sqlite3 shell. Consider using them to
+ abstract from the database structure, too.
+ suggested by espie@ Sat, 19 Apr 2014 14:52:57 +0200
+
diff --git a/contrib/mdocml/apropos.1 b/contrib/mdocml/apropos.1
new file mode 100644
index 0000000..1468242
--- /dev/null
+++ b/contrib/mdocml/apropos.1
@@ -0,0 +1,387 @@
+.\" $Id: apropos.1,v 1.29 2014/04/24 00:28:19 schwarze Exp $
+.\"
+.\" Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011, 2012, 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: April 24 2014 $
+.Dt APROPOS 1
+.Os
+.Sh NAME
+.Nm apropos ,
+.Nm whatis
+.Nd search manual page databases
+.Sh SYNOPSIS
+.Nm
+.Op Fl C Ar file
+.Op Fl M Ar path
+.Op Fl m Ar path
+.Op Fl O Ar outkey
+.Op Fl S Ar arch
+.Op Fl s Ar section
+.Ar expression ...
+.Sh DESCRIPTION
+The
+.Nm apropos
+and
+.Nm whatis
+utilities query manual page databases generated by
+.Xr makewhatis 8 ,
+evaluating
+.Ar expression
+for each file in each database.
+By default, it displays the names, section numbers, and description lines
+of all matching manuals.
+.Pp
+By default,
+.Nm
+searches for
+.Xr makewhatis 8
+databases in the default paths stipulated by
+.Xr man 1
+and uses case-insensitive substring matching
+.Pq the Cm = No operator
+over manual names and descriptions
+.Pq the Li \&Nm No and Li \&Nd No macro keys .
+Multiple terms imply pairwise
+.Fl o .
+.Nm whatis
+maps terms only to case-sensitive manual names.
+.Pp
+Its arguments are as follows:
+.Bl -tag -width Ds
+.It Fl C Ar file
+Specify an alternative configuration
+.Ar file
+in
+.Xr man.conf 5
+format.
+.It Fl M Ar path
+Use the colon-separated path instead of the default list of paths
+searched for
+.Xr makewhatis 8
+databases.
+Invalid paths, or paths without manual databases, are ignored.
+.It Fl m Ar path
+Prepend the colon-separated paths to the list of paths searched
+for
+.Xr makewhatis 8
+databases.
+Invalid paths, or paths without manual databases, are ignored.
+.It Fl O Ar outkey
+Show the values associated with the key
+.Ar outkey
+instead of the manual descriptions.
+.It Fl S Ar arch
+Restrict the search to pages for the specified
+.Xr machine 1
+architecture.
+.Ar arch
+is case insensitive.
+By default, pages for all architectures are shown.
+.It Fl s Ar section
+Restrict the search to the specified section of the manual.
+By default, pages from all sections are shown.
+See
+.Xr man 1
+for a listing of sections.
+.El
+.Pp
+An
+.Ar expression
+consists of search terms joined by logical operators
+.Fl a
+.Pq and
+and
+.Fl o
+.Pq or .
+The
+.Fl a
+operator has precedence over
+.Fl o
+and both are evaluated left-to-right.
+.Bl -tag -width Ds
+.It \&( Ar expr No \&)
+True if the subexpression
+.Ar expr
+is true.
+.It Ar expr1 Fl a Ar expr2
+True if both
+.Ar expr1
+and
+.Ar expr2
+are true (logical
+.Sq and ) .
+.It Ar expr1 Oo Fl o Oc Ar expr2
+True if
+.Ar expr1
+and/or
+.Ar expr2
+evaluate to true (logical
+.Sq or ) .
+.It Ar term
+True if
+.Ar term
+is satisfied.
+This has syntax
+.Sm off
+.Oo
+.Op Ar key Op , Ar key ...
+.Pq Cm = | ~
+.Oc
+.Ar val ,
+.Sm on
+where
+.Ar key
+is an
+.Xr mdoc 7
+macro to query and
+.Ar val
+is its value.
+See
+.Sx Macro Keys
+for a list of available keys.
+Operator
+.Cm =
+evaluates a substring, while
+.Cm ~
+evaluates a regular expression.
+.It Fl i Ar term
+If
+.Ar term
+is a regular expression, it
+is evaluated case-insensitively.
+Has no effect on substring terms.
+.El
+.Pp
+.Nm whatis
+considers an
+.Ar expression
+to consist of an opaque keyword.
+.Pp
+Results are sorted by manual sections and names, with output formatted as
+.Pp
+.D1 name[, name...](sec) \- description
+.Pp
+Where
+.Dq name
+is the manual's name,
+.Dq sec
+is the manual section, and
+.Dq description
+is the manual's short description.
+If an architecture is specified for the manual, it is displayed as
+.Pp
+.D1 name(sec/arch) \- description
+.Pp
+Resulting manuals may be accessed as
+.Pp
+.Dl $ man \-s sec name
+.Pp
+If an architecture is specified in the output, use
+.Pp
+.Dl $ man \-s sec \-S arch name
+.Ss Macro Keys
+Queries evaluate over a subset of
+.Xr mdoc 7
+macros indexed by
+.Xr makewhatis 8 .
+In addition to the macro keys listed below, the special key
+.Cm any
+may be used to match any available macro key.
+.Pp
+Names and description:
+.Bl -column "xLix" description -offset indent -compact
+.It Li \&Nm Ta manual name
+.It Li \&Nd Ta one-line manual description
+.It Li arch Ta machine architecture (case-insensitive)
+.It Li sec Ta manual section number
+.El
+.Pp
+Sections and cross references:
+.Bl -column "xLix" description -offset indent -compact
+.It Li \&Sh Ta section header (excluding standard sections)
+.It Li \&Ss Ta subsection header
+.It Li \&Xr Ta cross reference to another manual page
+.It Li \&Rs Ta bibliographic reference
+.El
+.Pp
+Semantic markup for command line utilities:
+.Bl -column "xLix" description -offset indent -compact
+.It Li \&Fl Ta command line options (flags)
+.It Li \&Cm Ta command modifier
+.It Li \&Ar Ta command argument
+.It Li \&Ic Ta internal or interactive command
+.It Li \&Ev Ta environmental variable
+.It Li \&Pa Ta file system path
+.El
+.Pp
+Semantic markup for function libraries:
+.Bl -column "xLix" description -offset indent -compact
+.It Li \&Lb Ta function library name
+.It Li \&In Ta include file
+.It Li \&Ft Ta function return type
+.It Li \&Fn Ta function name
+.It Li \&Fa Ta function argument type and name
+.It Li \&Vt Ta variable type
+.It Li \&Va Ta variable name
+.It Li \&Dv Ta defined variable or preprocessor constant
+.It Li \&Er Ta error constant
+.It Li \&Ev Ta environmental variable
+.El
+.Pp
+Various semantic markup:
+.Bl -column "xLix" description -offset indent -compact
+.It Li \&An Ta author name
+.It Li \&Lk Ta hyperlink
+.It Li \&Mt Ta Do mailto Dc hyperlink
+.It Li \&Cd Ta kernel configuration declaration
+.It Li \&Ms Ta mathematical symbol
+.It Li \&Tn Ta tradename
+.El
+.Pp
+Physical markup:
+.Bl -column "xLix" description -offset indent -compact
+.It Li \&Em Ta italic font or underline
+.It Li \&Sy Ta boldface font
+.It Li \&Li Ta typewriter font
+.El
+.Pp
+Text production:
+.Bl -column "xLix" description -offset indent -compact
+.It Li \&St Ta reference to a standards document
+.It Li \&At Ta At No version reference
+.It Li \&Bx Ta Bx No version reference
+.It Li \&Bsx Ta Bsx No version reference
+.It Li \&Nx Ta Nx No version reference
+.It Li \&Fx Ta Fx No version reference
+.It Li \&Ox Ta Ox No version reference
+.It Li \&Dx Ta Dx No version reference
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width MANPATH
+.It Ev MANPATH
+The standard search path used by
+.Xr man 1
+may be changed by specifying a path in the
+.Ev MANPATH
+environment variable.
+Invalid paths, or paths without manual databases, are ignored.
+Overridden by
+.Fl M .
+If
+.Ev MANPATH
+begins with a colon, it is appended to the default list;
+if it ends with a colon, it is prepended to the default list;
+or if it contains two adjacent colons,
+the standard search path is inserted between the colons.
+If none of these conditions are met, it overrides the
+standard search path.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/man.conf" -compact
+.It Pa mandoc.db
+name of the
+.Xr makewhatis 8
+keyword database
+.It Pa /etc/man.conf
+default
+.Xr man 1
+configuration file
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Search for
+.Qq .cf
+as a substring of manual names and descriptions:
+.Pp
+.Dl $ apropos .cf
+.Pp
+Include matches for
+.Qq .cnf
+and
+.Qq .conf
+as well:
+.Pp
+.Dl $ apropos .cf .cnf .conf
+.Pp
+Search in names and descriptions using a regular expression:
+.Pp
+.Dl $ apropos '~set.?[ug]id'
+.Pp
+Search for manuals in the library section mentioning both the
+.Qq optind
+and the
+.Qq optarg
+variables:
+.Pp
+.Dl $ apropos \-s 3 Va=optind \-a Va=optarg
+.Pp
+Do exactly the same as calling
+.Xr whatis 1
+with the argument
+.Qq ssh :
+.Pp
+.Dl $ apropos \-\- \-i 'Nm~[[:<:]]ssh[[:>:]]'
+.Pp
+The following two invocations are equivalent:
+.Pp
+.D1 Li $ apropos -S Ar arch Li -s Ar section expression
+.Bd -ragged -offset indent
+.Li $ apropos \e( Ar expression Li \e)
+.Li -a arch~^( Ns Ar arch Ns Li |any)$
+.Li -a sec~^ Ns Ar section Ns Li $
+.Ed
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr re_format 7 ,
+.Xr makewhatis 8
+.Sh HISTORY
+An
+.Nm
+utility first appeared in
+.Bx 2 .
+It was rewritten from scratch for
+.Ox 5.6 .
+.Pp
+The
+.Fl M
+option and the
+.Ev MANPATH
+variable first appeared in
+.Bx 4.3 ;
+.Fl m
+in
+.Bx 4.3 Reno ;
+.Fl C
+in
+.Bx 4.4 Lite1 ;
+and
+.Fl S
+and
+.Fl s
+in
+.Ox 4.5 .
+.Sh AUTHORS
+.An -nosplit
+.An Bill Joy
+wrote the original
+.Bx
+.Nm
+in February 1979.
+The current version was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/apropos.c b/contrib/mdocml/apropos.c
new file mode 100644
index 0000000..80b6bc6
--- /dev/null
+++ b/contrib/mdocml/apropos.c
@@ -0,0 +1,123 @@
+/* $Id: apropos.c,v 1.39 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <sys/param.h>
+
+#include <assert.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "manpath.h"
+#include "mansearch.h"
+
+
+int
+main(int argc, char *argv[])
+{
+ int ch, whatis;
+ struct mansearch search;
+ size_t i, sz;
+ struct manpage *res;
+ struct manpaths paths;
+ char *defpaths, *auxpaths;
+ char *conf_file;
+ char *progname;
+ const char *outkey;
+ extern char *optarg;
+ extern int optind;
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ whatis = (0 == strncmp(progname, "whatis", 6));
+
+ memset(&paths, 0, sizeof(struct manpaths));
+ memset(&search, 0, sizeof(struct mansearch));
+
+ auxpaths = defpaths = NULL;
+ conf_file = NULL;
+ outkey = "Nd";
+
+ while (-1 != (ch = getopt(argc, argv, "C:M:m:O:S:s:")))
+ switch (ch) {
+ case 'C':
+ conf_file = optarg;
+ break;
+ case 'M':
+ defpaths = optarg;
+ break;
+ case 'm':
+ auxpaths = optarg;
+ break;
+ case 'O':
+ outkey = optarg;
+ break;
+ case 'S':
+ search.arch = optarg;
+ break;
+ case 's':
+ search.sec = optarg;
+ break;
+ default:
+ goto usage;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (0 == argc)
+ goto usage;
+
+ search.deftype = whatis ? TYPE_Nm : TYPE_Nm | TYPE_Nd;
+ search.flags = whatis ? MANSEARCH_WHATIS : 0;
+
+ manpath_parse(&paths, conf_file, defpaths, auxpaths);
+ mansearch_setup(1);
+ ch = mansearch(&search, &paths, argc, argv, outkey, &res, &sz);
+ manpath_free(&paths);
+
+ if (0 == ch)
+ goto usage;
+
+ for (i = 0; i < sz; i++) {
+ printf("%s - %s\n", res[i].names,
+ NULL == res[i].output ? "" : res[i].output);
+ free(res[i].file);
+ free(res[i].names);
+ free(res[i].output);
+ }
+
+ free(res);
+ mansearch_setup(0);
+ return(sz ? EXIT_SUCCESS : EXIT_FAILURE);
+usage:
+ fprintf(stderr, "usage: %s [-C file] [-M path] [-m path] "
+ "[-O outkey] "
+ "[-S arch] [-s section]%s ...\n", progname,
+ whatis ? " name" : "\n expression");
+ return(EXIT_FAILURE);
+}
diff --git a/contrib/mdocml/arch.c b/contrib/mdocml/arch.c
new file mode 100644
index 0000000..3e746d8
--- /dev/null
+++ b/contrib/mdocml/arch.c
@@ -0,0 +1,37 @@
+/* $Id: arch.c,v 1.11 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "mdoc.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/contrib/mdocml/arch.in b/contrib/mdocml/arch.in
new file mode 100644
index 0000000..a22ffd5
--- /dev/null
+++ b/contrib/mdocml/arch.in
@@ -0,0 +1,112 @@
+/* $Id: arch.in,v 1.15 2014/04/27 22:42:15 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ *
+ * REMEMBER TO ADD NEW ARCHITECTURES TO MDOC.7!
+ */
+
+LINE("acorn26", "Acorn26")
+LINE("acorn32", "Acorn32")
+LINE("algor", "Algor")
+LINE("alpha", "Alpha")
+LINE("amd64", "AMD64")
+LINE("amiga", "Amiga")
+LINE("amigappc", "AmigaPPC")
+LINE("arc", "ARC")
+LINE("arm", "ARM")
+LINE("arm26", "ARM26")
+LINE("arm32", "ARM32")
+LINE("armish", "ARMISH")
+LINE("armv7", "ARMv7")
+LINE("aviion", "AViiON")
+LINE("atari", "ATARI")
+LINE("bebox", "BeBox")
+LINE("cats", "cats")
+LINE("cesfic", "CESFIC")
+LINE("cobalt", "Cobalt")
+LINE("dreamcast", "Dreamcast")
+LINE("emips", "EMIPS")
+LINE("evbarm", "evbARM")
+LINE("evbmips", "evbMIPS")
+LINE("evbppc", "evbPPC")
+LINE("evbsh3", "evbSH3")
+LINE("ews4800mips", "EWS4800MIPS")
+LINE("hp300", "HP300")
+LINE("hp700", "HP700")
+LINE("hpcarm", "HPCARM")
+LINE("hpcmips", "HPCMIPS")
+LINE("hpcsh", "HPCSH")
+LINE("hppa", "HPPA")
+LINE("hppa64", "HPPA64")
+LINE("ia64", "ia64")
+LINE("i386", "i386")
+LINE("ibmnws", "IBMNWS")
+LINE("iyonix", "Iyonix")
+LINE("landisk", "LANDISK")
+LINE("loongson", "Loongson")
+LINE("luna68k", "LUNA68K")
+LINE("luna88k", "LUNA88K")
+LINE("m68k", "m68k")
+LINE("mac68k", "Mac68k")
+LINE("macppc", "MacPPC")
+LINE("mips", "MIPS")
+LINE("mips64", "MIPS64")
+LINE("mipsco", "MIPSCo")
+LINE("mmeye", "mmEye")
+LINE("mvme68k", "MVME68k")
+LINE("mvme88k", "MVME88k")
+LINE("mvmeppc", "MVMEPPC")
+LINE("netwinder", "NetWinder")
+LINE("news68k", "NeWS68k")
+LINE("newsmips", "NeWSMIPS")
+LINE("next68k", "NeXT68k")
+LINE("octeon", "OCTEON")
+LINE("ofppc", "OFPPC")
+LINE("palm", "Palm")
+LINE("pc532", "PC532")
+LINE("playstation2", "PlayStation2")
+LINE("pmax", "PMAX")
+LINE("pmppc", "pmPPC")
+LINE("powerpc", "PowerPC")
+LINE("prep", "PReP")
+LINE("rs6000", "RS6000")
+LINE("sandpoint", "Sandpoint")
+LINE("sbmips", "SBMIPS")
+LINE("sgi", "SGI")
+LINE("sgimips", "SGIMIPS")
+LINE("sh3", "SH3")
+LINE("shark", "Shark")
+LINE("socppc", "SOCPPC")
+LINE("solbourne", "Solbourne")
+LINE("sparc", "SPARC")
+LINE("sparc64", "SPARC64")
+LINE("sun2", "Sun2")
+LINE("sun3", "Sun3")
+LINE("tahoe", "Tahoe")
+LINE("vax", "VAX")
+LINE("x68k", "X68k")
+LINE("x86", "x86")
+LINE("x86_64", "x86_64")
+LINE("xen", "Xen")
+LINE("zaurus", "Zaurus")
diff --git a/contrib/mdocml/att.c b/contrib/mdocml/att.c
new file mode 100644
index 0000000..059639a
--- /dev/null
+++ b/contrib/mdocml/att.c
@@ -0,0 +1,37 @@
+/* $Id: att.c,v 1.11 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "mdoc.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/contrib/mdocml/att.in b/contrib/mdocml/att.in
new file mode 100644
index 0000000..b4ef822
--- /dev/null
+++ b/contrib/mdocml/att.in
@@ -0,0 +1,40 @@
+/* $Id: att.in,v 1.8 2011/07/31 17:30:33 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ * The non-breaking blanks prevent ending an output line right before
+ * a number. Groff prevent line breaks at the same places.
+ */
+
+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("III", "AT&T System\\~III UNIX")
+LINE("V", "AT&T System\\~V UNIX")
+LINE("V.1", "AT&T System\\~V Release\\~1 UNIX")
+LINE("V.2", "AT&T System\\~V Release\\~2 UNIX")
+LINE("V.3", "AT&T System\\~V Release\\~3 UNIX")
+LINE("V.4", "AT&T System\\~V Release\\~4 UNIX")
diff --git a/contrib/mdocml/cgi.c b/contrib/mdocml/cgi.c
new file mode 100644
index 0000000..1e38e3d
--- /dev/null
+++ b/contrib/mdocml/cgi.c
@@ -0,0 +1,1150 @@
+/* $Id: cgi.c,v 1.92 2014/08/05 15:29:30 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "main.h"
+#include "manpath.h"
+#include "mansearch.h"
+#include "cgi.h"
+
+/*
+ * A query as passed to the search function.
+ */
+struct query {
+ char *manpath; /* desired manual directory */
+ char *arch; /* architecture */
+ char *sec; /* manual section */
+ char *query; /* unparsed query expression */
+ int equal; /* match whole names, not substrings */
+};
+
+struct req {
+ struct query q;
+ char **p; /* array of available manpaths */
+ size_t psz; /* number of available manpaths */
+};
+
+static void catman(const struct req *, const char *);
+static void format(const struct req *, const char *);
+static void html_print(const char *);
+static void html_putchar(char);
+static int http_decode(char *);
+static void http_parse(struct req *, const char *);
+static void http_print(const char *);
+static void http_putchar(char);
+static void http_printquery(const struct req *, const char *);
+static void pathgen(struct req *);
+static void pg_error_badrequest(const char *);
+static void pg_error_internal(void);
+static void pg_index(const struct req *);
+static void pg_noresult(const struct req *, const char *);
+static void pg_search(const struct req *);
+static void pg_searchres(const struct req *,
+ struct manpage *, size_t);
+static void pg_show(struct req *, const char *);
+static void resp_begin_html(int, const char *);
+static void resp_begin_http(int, const char *);
+static void resp_end_html(void);
+static void resp_searchform(const struct req *);
+static void resp_show(const struct req *, const char *);
+static void set_query_attr(char **, char **);
+static int validate_filename(const char *);
+static int validate_manpath(const struct req *, const char *);
+static int validate_urifrag(const char *);
+
+static const char *scriptname; /* CGI script name */
+
+static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
+static const char *const sec_numbers[] = {
+ "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
+};
+static const char *const sec_names[] = {
+ "All Sections",
+ "1 - General Commands",
+ "2 - System Calls",
+ "3 - Subroutines",
+ "3p - Perl Subroutines",
+ "4 - Special Files",
+ "5 - File Formats",
+ "6 - Games",
+ "7 - Macros and Conventions",
+ "8 - Maintenance Commands",
+ "9 - Kernel Interface"
+};
+static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
+
+static const char *const arch_names[] = {
+ "amd64", "alpha", "armish", "armv7",
+ "aviion", "hppa", "hppa64", "i386",
+ "ia64", "landisk", "loongson", "luna88k",
+ "macppc", "mips64", "octeon", "sgi",
+ "socppc", "solbourne", "sparc", "sparc64",
+ "vax", "zaurus",
+ "amiga", "arc", "arm32", "atari",
+ "beagle", "cats", "hp300", "mac68k",
+ "mvme68k", "mvme88k", "mvmeppc", "palm",
+ "pc532", "pegasos", "pmax", "powerpc",
+ "sun3", "wgrisc", "x68k"
+};
+static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
+
+/*
+ * Print a character, escaping HTML along the way.
+ * This will pass non-ASCII straight to output: be warned!
+ */
+static void
+html_putchar(char c)
+{
+
+ switch (c) {
+ case ('"'):
+ printf("&quote;");
+ break;
+ case ('&'):
+ printf("&amp;");
+ break;
+ case ('>'):
+ printf("&gt;");
+ break;
+ case ('<'):
+ printf("&lt;");
+ break;
+ default:
+ putchar((unsigned char)c);
+ break;
+ }
+}
+
+static void
+http_printquery(const struct req *req, const char *sep)
+{
+
+ if (NULL != req->q.query) {
+ printf("query=");
+ http_print(req->q.query);
+ }
+ if (0 == req->q.equal)
+ printf("%sapropos=1", sep);
+ if (NULL != req->q.sec) {
+ printf("%ssec=", sep);
+ http_print(req->q.sec);
+ }
+ if (NULL != req->q.arch) {
+ printf("%sarch=", sep);
+ http_print(req->q.arch);
+ }
+ if (NULL != req->q.manpath &&
+ strcmp(req->q.manpath, req->p[0])) {
+ printf("%smanpath=", sep);
+ http_print(req->q.manpath);
+ }
+}
+
+static void
+http_print(const char *p)
+{
+
+ if (NULL == p)
+ return;
+ while ('\0' != *p)
+ http_putchar(*p++);
+}
+
+/*
+ * Call through to html_putchar().
+ * Accepts NULL strings.
+ */
+static void
+html_print(const char *p)
+{
+
+ if (NULL == p)
+ return;
+ while ('\0' != *p)
+ html_putchar(*p++);
+}
+
+/*
+ * Transfer the responsibility for the allocated string *val
+ * to the query structure.
+ */
+static void
+set_query_attr(char **attr, char **val)
+{
+
+ free(*attr);
+ if (**val == '\0') {
+ *attr = NULL;
+ free(*val);
+ } else
+ *attr = *val;
+ *val = NULL;
+}
+
+/*
+ * Parse the QUERY_STRING for key-value pairs
+ * and store the values into the query structure.
+ */
+static void
+http_parse(struct req *req, const char *qs)
+{
+ char *key, *val;
+ size_t keysz, valsz;
+
+ req->q.manpath = NULL;
+ req->q.arch = NULL;
+ req->q.sec = NULL;
+ req->q.query = NULL;
+ req->q.equal = 1;
+
+ key = val = NULL;
+ while (*qs != '\0') {
+
+ /* Parse one key. */
+
+ keysz = strcspn(qs, "=;&");
+ key = mandoc_strndup(qs, keysz);
+ qs += keysz;
+ if (*qs != '=')
+ goto next;
+
+ /* Parse one value. */
+
+ valsz = strcspn(++qs, ";&");
+ val = mandoc_strndup(qs, valsz);
+ qs += valsz;
+
+ /* Decode and catch encoding errors. */
+
+ if ( ! (http_decode(key) && http_decode(val)))
+ goto next;
+
+ /* Handle key-value pairs. */
+
+ if ( ! strcmp(key, "query"))
+ set_query_attr(&req->q.query, &val);
+
+ else if ( ! strcmp(key, "apropos"))
+ req->q.equal = !strcmp(val, "0");
+
+ else if ( ! strcmp(key, "manpath")) {
+#ifdef COMPAT_OLDURI
+ if ( ! strncmp(val, "OpenBSD ", 8)) {
+ val[7] = '-';
+ if ('C' == val[8])
+ val[8] = 'c';
+ }
+#endif
+ set_query_attr(&req->q.manpath, &val);
+ }
+
+ else if ( ! (strcmp(key, "sec")
+#ifdef COMPAT_OLDURI
+ && strcmp(key, "sektion")
+#endif
+ )) {
+ if ( ! strcmp(val, "0"))
+ *val = '\0';
+ set_query_attr(&req->q.sec, &val);
+ }
+
+ else if ( ! strcmp(key, "arch")) {
+ if ( ! strcmp(val, "default"))
+ *val = '\0';
+ set_query_attr(&req->q.arch, &val);
+ }
+
+ /*
+ * The key must be freed in any case.
+ * The val may have been handed over to the query
+ * structure, in which case it is now NULL.
+ */
+next:
+ free(key);
+ key = NULL;
+ free(val);
+ val = NULL;
+
+ if (*qs != '\0')
+ qs++;
+ }
+
+ /* Fall back to the default manpath. */
+
+ if (req->q.manpath == NULL)
+ req->q.manpath = mandoc_strdup(req->p[0]);
+}
+
+static void
+http_putchar(char c)
+{
+
+ if (isalnum((unsigned char)c)) {
+ putchar((unsigned char)c);
+ return;
+ } else if (' ' == c) {
+ putchar('+');
+ return;
+ }
+ printf("%%%.2x", c);
+}
+
+/*
+ * HTTP-decode a string. The standard explanation is that this turns
+ * "%4e+foo" into "n foo" in the regular way. This is done in-place
+ * over the allocated string.
+ */
+static int
+http_decode(char *p)
+{
+ char hex[3];
+ char *q;
+ int c;
+
+ hex[2] = '\0';
+
+ q = p;
+ for ( ; '\0' != *p; p++, q++) {
+ if ('%' == *p) {
+ if ('\0' == (hex[0] = *(p + 1)))
+ return(0);
+ if ('\0' == (hex[1] = *(p + 2)))
+ return(0);
+ if (1 != sscanf(hex, "%x", &c))
+ return(0);
+ if ('\0' == c)
+ return(0);
+
+ *q = (char)c;
+ p += 2;
+ } else
+ *q = '+' == *p ? ' ' : *p;
+ }
+
+ *q = '\0';
+ return(1);
+}
+
+static void
+resp_begin_http(int code, const char *msg)
+{
+
+ if (200 != code)
+ printf("Status: %d %s\r\n", code, msg);
+
+ printf("Content-Type: text/html; charset=utf-8\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n");
+
+ fflush(stdout);
+}
+
+static void
+resp_begin_html(int code, const char *msg)
+{
+
+ resp_begin_http(code, msg);
+
+ printf("<!DOCTYPE HTML PUBLIC "
+ " \"-//W3C//DTD HTML 4.01//EN\""
+ " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
+ "<HTML>\n"
+ "<HEAD>\n"
+ "<META HTTP-EQUIV=\"Content-Type\""
+ " CONTENT=\"text/html; charset=utf-8\">\n"
+ "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
+ " TYPE=\"text/css\" media=\"all\">\n"
+ "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
+ " TYPE=\"text/css\" media=\"all\">\n"
+ "<TITLE>%s</TITLE>\n"
+ "</HEAD>\n"
+ "<BODY>\n"
+ "<!-- Begin page content. //-->\n",
+ CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE);
+}
+
+static void
+resp_end_html(void)
+{
+
+ puts("</BODY>\n"
+ "</HTML>");
+}
+
+static void
+resp_searchform(const struct req *req)
+{
+ int i;
+
+ puts(CUSTOMIZE_BEGIN);
+ puts("<!-- Begin search form. //-->");
+ printf("<DIV ID=\"mancgi\">\n"
+ "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
+ "<FIELDSET>\n"
+ "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
+ scriptname);
+
+ /* Write query input box. */
+
+ printf( "<TABLE><TR><TD>\n"
+ "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
+ if (NULL != req->q.query)
+ html_print(req->q.query);
+ puts("\" SIZE=\"40\">");
+
+ /* Write submission and reset buttons. */
+
+ printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
+ "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
+
+ /* Write show radio button */
+
+ printf( "</TD><TD>\n"
+ "<INPUT TYPE=\"radio\" ");
+ if (req->q.equal)
+ printf("CHECKED=\"checked\" ");
+ printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
+ "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
+
+ /* Write section selector. */
+
+ puts( "</TD></TR><TR><TD>\n"
+ "<SELECT NAME=\"sec\">");
+ for (i = 0; i < sec_MAX; i++) {
+ printf("<OPTION VALUE=\"%s\"", sec_numbers[i]);
+ if (NULL != req->q.sec &&
+ 0 == strcmp(sec_numbers[i], req->q.sec))
+ printf(" SELECTED=\"selected\"");
+ printf(">%s</OPTION>\n", sec_names[i]);
+ }
+ puts("</SELECT>");
+
+ /* Write architecture selector. */
+
+ printf( "<SELECT NAME=\"arch\">\n"
+ "<OPTION VALUE=\"default\"");
+ if (NULL == req->q.arch)
+ printf(" SELECTED=\"selected\"");
+ puts(">All Architectures</OPTION>");
+ for (i = 0; i < arch_MAX; i++) {
+ printf("<OPTION VALUE=\"%s\"", arch_names[i]);
+ if (NULL != req->q.arch &&
+ 0 == strcmp(arch_names[i], req->q.arch))
+ printf(" SELECTED=\"selected\"");
+ printf(">%s</OPTION>\n", arch_names[i]);
+ }
+ puts("</SELECT>");
+
+ /* Write manpath selector. */
+
+ if (req->psz > 1) {
+ puts("<SELECT NAME=\"manpath\">");
+ for (i = 0; i < (int)req->psz; i++) {
+ printf("<OPTION ");
+ if (NULL == req->q.manpath ? 0 == i :
+ 0 == strcmp(req->q.manpath, req->p[i]))
+ printf("SELECTED=\"selected\" ");
+ printf("VALUE=\"");
+ html_print(req->p[i]);
+ printf("\">");
+ html_print(req->p[i]);
+ puts("</OPTION>");
+ }
+ puts("</SELECT>");
+ }
+
+ /* Write search radio button */
+
+ printf( "</TD><TD>\n"
+ "<INPUT TYPE=\"radio\" ");
+ if (0 == req->q.equal)
+ printf("CHECKED=\"checked\" ");
+ printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
+ "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
+
+ puts("</TD></TR></TABLE>\n"
+ "</FIELDSET>\n"
+ "</FORM>\n"
+ "</DIV>");
+ puts("<!-- End search form. //-->");
+}
+
+static int
+validate_urifrag(const char *frag)
+{
+
+ while ('\0' != *frag) {
+ if ( ! (isalnum((unsigned char)*frag) ||
+ '-' == *frag || '.' == *frag ||
+ '/' == *frag || '_' == *frag))
+ return(0);
+ frag++;
+ }
+ return(1);
+}
+
+static int
+validate_manpath(const struct req *req, const char* manpath)
+{
+ size_t i;
+
+ if ( ! strcmp(manpath, "mandoc"))
+ return(1);
+
+ for (i = 0; i < req->psz; i++)
+ if ( ! strcmp(manpath, req->p[i]))
+ return(1);
+
+ return(0);
+}
+
+static int
+validate_filename(const char *file)
+{
+
+ if ('.' == file[0] && '/' == file[1])
+ file += 2;
+
+ return ( ! (strstr(file, "../") || strstr(file, "/..") ||
+ (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
+}
+
+static void
+pg_index(const struct req *req)
+{
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ printf("<P>\n"
+ "This web interface is documented in the\n"
+ "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A>\n"
+ "manual, and the\n"
+ "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A>\n"
+ "manual explains the query syntax.\n"
+ "</P>\n",
+ scriptname, scriptname);
+ resp_end_html();
+}
+
+static void
+pg_noresult(const struct req *req, const char *msg)
+{
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ puts("<P>");
+ puts(msg);
+ puts("</P>");
+ resp_end_html();
+}
+
+static void
+pg_error_badrequest(const char *msg)
+{
+
+ resp_begin_html(400, "Bad Request");
+ puts("<H1>Bad Request</H1>\n"
+ "<P>\n");
+ puts(msg);
+ printf("Try again from the\n"
+ "<A HREF=\"%s\">main page</A>.\n"
+ "</P>", scriptname);
+ resp_end_html();
+}
+
+static void
+pg_error_internal(void)
+{
+ resp_begin_html(500, "Internal Server Error");
+ puts("<P>Internal Server Error</P>");
+ resp_end_html();
+}
+
+static void
+pg_searchres(const struct req *req, struct manpage *r, size_t sz)
+{
+ char *arch, *archend;
+ size_t i, iuse, isec;
+ int archprio, archpriouse;
+ int prio, priouse;
+ char sec;
+
+ for (i = 0; i < sz; i++) {
+ if (validate_filename(r[i].file))
+ continue;
+ fprintf(stderr, "invalid filename %s in %s database\n",
+ r[i].file, req->q.manpath);
+ pg_error_internal();
+ return;
+ }
+
+ if (1 == sz) {
+ /*
+ * If we have just one result, then jump there now
+ * without any delay.
+ */
+ printf("Status: 303 See Other\r\n");
+ printf("Location: http://%s%s/%s/%s?",
+ HTTP_HOST, scriptname, req->q.manpath, r[0].file);
+ http_printquery(req, "&");
+ printf("\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "\r\n");
+ return;
+ }
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ puts("<DIV CLASS=\"results\">");
+ puts("<TABLE>");
+
+ for (i = 0; i < sz; i++) {
+ printf("<TR>\n"
+ "<TD CLASS=\"title\">\n"
+ "<A HREF=\"%s/%s/%s?",
+ scriptname, req->q.manpath, r[i].file);
+ http_printquery(req, "&amp;");
+ printf("\">");
+ html_print(r[i].names);
+ printf("</A>\n"
+ "</TD>\n"
+ "<TD CLASS=\"desc\">");
+ html_print(r[i].output);
+ puts("</TD>\n"
+ "</TR>");
+ }
+
+ puts("</TABLE>\n"
+ "</DIV>");
+
+ /*
+ * In man(1) mode, show one of the pages
+ * even if more than one is found.
+ */
+
+ if (req->q.equal) {
+ puts("<HR>");
+ iuse = 0;
+ priouse = 10;
+ archpriouse = 3;
+ for (i = 0; i < sz; i++) {
+ isec = strcspn(r[i].file, "123456789");
+ sec = r[i].file[isec];
+ if ('\0' == sec)
+ continue;
+ prio = sec_prios[sec - '1'];
+ if (NULL == req->q.arch) {
+ archprio =
+ (NULL == (arch = strchr(
+ r[i].file + isec, '/'))) ? 3 :
+ (NULL == (archend = strchr(
+ arch + 1, '/'))) ? 0 :
+ strncmp(arch, "amd64/",
+ archend - arch) ? 2 : 1;
+ if (archprio < archpriouse) {
+ archpriouse = archprio;
+ priouse = prio;
+ iuse = i;
+ continue;
+ }
+ if (archprio > archpriouse)
+ continue;
+ }
+ if (prio >= priouse)
+ continue;
+ priouse = prio;
+ iuse = i;
+ }
+ resp_show(req, r[iuse].file);
+ }
+
+ resp_end_html();
+}
+
+static void
+catman(const struct req *req, const char *file)
+{
+ FILE *f;
+ size_t len;
+ int i;
+ char *p;
+ int italic, bold;
+
+ if (NULL == (f = fopen(file, "r"))) {
+ puts("<P>You specified an invalid manual file.</P>");
+ return;
+ }
+
+ puts("<DIV CLASS=\"catman\">\n"
+ "<PRE>");
+
+ while (NULL != (p = fgetln(f, &len))) {
+ bold = italic = 0;
+ for (i = 0; i < (int)len - 1; i++) {
+ /*
+ * This means that the catpage is out of state.
+ * Ignore it and keep going (although the
+ * catpage is bogus).
+ */
+
+ if ('\b' == p[i] || '\n' == p[i])
+ continue;
+
+ /*
+ * Print a regular character.
+ * Close out any bold/italic scopes.
+ * If we're in back-space mode, make sure we'll
+ * have something to enter when we backspace.
+ */
+
+ if ('\b' != p[i + 1]) {
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+ italic = bold = 0;
+ html_putchar(p[i]);
+ continue;
+ } else if (i + 2 >= (int)len)
+ continue;
+
+ /* Italic mode. */
+
+ if ('_' == p[i]) {
+ if (bold)
+ printf("</B>");
+ if ( ! italic)
+ printf("<I>");
+ bold = 0;
+ italic = 1;
+ i += 2;
+ html_putchar(p[i]);
+ continue;
+ }
+
+ /*
+ * Handle funny behaviour troff-isms.
+ * These grok'd from the original man2html.c.
+ */
+
+ if (('+' == p[i] && 'o' == p[i + 2]) ||
+ ('o' == p[i] && '+' == p[i + 2]) ||
+ ('|' == p[i] && '=' == p[i + 2]) ||
+ ('=' == p[i] && '|' == p[i + 2]) ||
+ ('*' == p[i] && '=' == p[i + 2]) ||
+ ('=' == p[i] && '*' == p[i + 2]) ||
+ ('*' == p[i] && '|' == p[i + 2]) ||
+ ('|' == p[i] && '*' == p[i + 2])) {
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+ italic = bold = 0;
+ putchar('*');
+ i += 2;
+ continue;
+ } else if (('|' == p[i] && '-' == p[i + 2]) ||
+ ('-' == p[i] && '|' == p[i + 1]) ||
+ ('+' == p[i] && '-' == p[i + 1]) ||
+ ('-' == p[i] && '+' == p[i + 1]) ||
+ ('+' == p[i] && '|' == p[i + 1]) ||
+ ('|' == p[i] && '+' == p[i + 1])) {
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+ italic = bold = 0;
+ putchar('+');
+ i += 2;
+ continue;
+ }
+
+ /* Bold mode. */
+
+ if (italic)
+ printf("</I>");
+ if ( ! bold)
+ printf("<B>");
+ bold = 1;
+ italic = 0;
+ i += 2;
+ html_putchar(p[i]);
+ }
+
+ /*
+ * Clean up the last character.
+ * We can get to a newline; don't print that.
+ */
+
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+
+ if (i == (int)len - 1 && '\n' != p[i])
+ html_putchar(p[i]);
+
+ putchar('\n');
+ }
+
+ puts("</PRE>\n"
+ "</DIV>");
+
+ fclose(f);
+}
+
+static void
+format(const struct req *req, const char *file)
+{
+ struct mparse *mp;
+ struct mdoc *mdoc;
+ struct man *man;
+ void *vp;
+ char *opts;
+ enum mandoclevel rc;
+ int fd;
+ int usepath;
+
+ if (-1 == (fd = open(file, O_RDONLY, 0))) {
+ puts("<P>You specified an invalid manual file.</P>");
+ return;
+ }
+
+ mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
+ req->q.manpath);
+ rc = mparse_readfd(mp, fd, file);
+ close(fd);
+
+ if (rc >= MANDOCLEVEL_FATAL) {
+ fprintf(stderr, "fatal mandoc error: %s/%s\n",
+ req->q.manpath, file);
+ pg_error_internal();
+ return;
+ }
+
+ usepath = strcmp(req->q.manpath, req->p[0]);
+ mandoc_asprintf(&opts,
+ "fragment,man=%s?query=%%N&sec=%%S%s%s%s%s",
+ scriptname,
+ req->q.arch ? "&arch=" : "",
+ req->q.arch ? req->q.arch : "",
+ usepath ? "&manpath=" : "",
+ usepath ? req->q.manpath : "");
+
+ mparse_result(mp, &mdoc, &man, NULL);
+ if (NULL == man && NULL == mdoc) {
+ fprintf(stderr, "fatal mandoc error: %s/%s\n",
+ req->q.manpath, file);
+ pg_error_internal();
+ mparse_free(mp);
+ return;
+ }
+
+ vp = html_alloc(opts);
+
+ if (NULL != mdoc)
+ html_mdoc(vp, mdoc);
+ else
+ html_man(vp, man);
+
+ html_free(vp);
+ mparse_free(mp);
+ free(opts);
+}
+
+static void
+resp_show(const struct req *req, const char *file)
+{
+
+ if ('.' == file[0] && '/' == file[1])
+ file += 2;
+
+ if ('c' == *file)
+ catman(req, file);
+ else
+ format(req, file);
+}
+
+static void
+pg_show(struct req *req, const char *fullpath)
+{
+ char *manpath;
+ const char *file;
+
+ if ((file = strchr(fullpath, '/')) == NULL) {
+ pg_error_badrequest(
+ "You did not specify a page to show.");
+ return;
+ }
+ manpath = mandoc_strndup(fullpath, file - fullpath);
+ file++;
+
+ if ( ! validate_manpath(req, manpath)) {
+ pg_error_badrequest(
+ "You specified an invalid manpath.");
+ free(manpath);
+ return;
+ }
+
+ /*
+ * Begin by chdir()ing into the manpath.
+ * This way we can pick up the database files, which are
+ * relative to the manpath root.
+ */
+
+ if (chdir(manpath) == -1) {
+ fprintf(stderr, "chdir %s: %s\n",
+ manpath, strerror(errno));
+ pg_error_internal();
+ free(manpath);
+ return;
+ }
+
+ if (strcmp(manpath, "mandoc")) {
+ free(req->q.manpath);
+ req->q.manpath = manpath;
+ } else
+ free(manpath);
+
+ if ( ! validate_filename(file)) {
+ pg_error_badrequest(
+ "You specified an invalid manual file.");
+ return;
+ }
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ resp_show(req, file);
+ resp_end_html();
+}
+
+static void
+pg_search(const struct req *req)
+{
+ struct mansearch search;
+ struct manpaths paths;
+ struct manpage *res;
+ char **cp;
+ const char *ep, *start;
+ size_t ressz;
+ int i, sz;
+
+ /*
+ * Begin by chdir()ing into the root of the manpath.
+ * This way we can pick up the database files, which are
+ * relative to the manpath root.
+ */
+
+ if (-1 == (chdir(req->q.manpath))) {
+ fprintf(stderr, "chdir %s: %s\n",
+ req->q.manpath, strerror(errno));
+ pg_error_internal();
+ return;
+ }
+
+ search.arch = req->q.arch;
+ search.sec = req->q.sec;
+ search.deftype = req->q.equal ? TYPE_Nm : (TYPE_Nm | TYPE_Nd);
+ search.flags = req->q.equal ? MANSEARCH_MAN : 0;
+
+ paths.sz = 1;
+ paths.paths = mandoc_malloc(sizeof(char *));
+ paths.paths[0] = mandoc_strdup(".");
+
+ /*
+ * Poor man's tokenisation: just break apart by spaces.
+ * Yes, this is half-ass. But it works for now.
+ */
+
+ ep = req->q.query;
+ while (ep && isspace((unsigned char)*ep))
+ ep++;
+
+ sz = 0;
+ cp = NULL;
+ while (ep && '\0' != *ep) {
+ cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *));
+ start = ep;
+ while ('\0' != *ep && ! isspace((unsigned char)*ep))
+ ep++;
+ cp[sz] = mandoc_malloc((ep - start) + 1);
+ memcpy(cp[sz], start, ep - start);
+ cp[sz++][ep - start] = '\0';
+ while (isspace((unsigned char)*ep))
+ ep++;
+ }
+
+ if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
+ pg_noresult(req, "You entered an invalid query.");
+ else if (0 == ressz)
+ pg_noresult(req, "No results found.");
+ else
+ pg_searchres(req, res, ressz);
+
+ for (i = 0; i < sz; i++)
+ free(cp[i]);
+ free(cp);
+
+ for (i = 0; i < (int)ressz; i++) {
+ free(res[i].file);
+ free(res[i].names);
+ free(res[i].output);
+ }
+ free(res);
+
+ free(paths.paths[0]);
+ free(paths.paths);
+}
+
+int
+main(void)
+{
+ struct req req;
+ const char *path;
+ const char *querystring;
+ int i;
+
+ /* Scan our run-time environment. */
+
+ if (NULL == (scriptname = getenv("SCRIPT_NAME")))
+ scriptname = "";
+
+ if ( ! validate_urifrag(scriptname)) {
+ fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
+ scriptname);
+ pg_error_internal();
+ return(EXIT_FAILURE);
+ }
+
+ /*
+ * First we change directory into the MAN_DIR so that
+ * subsequent scanning for manpath directories is rooted
+ * relative to the same position.
+ */
+
+ if (-1 == chdir(MAN_DIR)) {
+ fprintf(stderr, "MAN_DIR: %s: %s\n",
+ MAN_DIR, strerror(errno));
+ pg_error_internal();
+ return(EXIT_FAILURE);
+ }
+
+ memset(&req, 0, sizeof(struct req));
+ pathgen(&req);
+
+ /* Next parse out the query string. */
+
+ if (NULL != (querystring = getenv("QUERY_STRING")))
+ http_parse(&req, querystring);
+
+ if ( ! (NULL == req.q.manpath ||
+ validate_manpath(&req, req.q.manpath))) {
+ pg_error_badrequest(
+ "You specified an invalid manpath.");
+ return(EXIT_FAILURE);
+ }
+
+ if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
+ pg_error_badrequest(
+ "You specified an invalid architecture.");
+ return(EXIT_FAILURE);
+ }
+
+ /* Dispatch to the three different pages. */
+
+ path = getenv("PATH_INFO");
+ if (NULL == path)
+ path = "";
+ else if ('/' == *path)
+ path++;
+
+ if ('\0' != *path)
+ pg_show(&req, path);
+ else if (NULL != req.q.query)
+ pg_search(&req);
+ else
+ pg_index(&req);
+
+ free(req.q.manpath);
+ free(req.q.arch);
+ free(req.q.sec);
+ free(req.q.query);
+ for (i = 0; i < (int)req.psz; i++)
+ free(req.p[i]);
+ free(req.p);
+ return(EXIT_SUCCESS);
+}
+
+/*
+ * Scan for indexable paths.
+ */
+static void
+pathgen(struct req *req)
+{
+ FILE *fp;
+ char *dp;
+ size_t dpsz;
+
+ if (NULL == (fp = fopen("manpath.conf", "r"))) {
+ fprintf(stderr, "%s/manpath.conf: %s\n",
+ MAN_DIR, strerror(errno));
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+
+ while (NULL != (dp = fgetln(fp, &dpsz))) {
+ if ('\n' == dp[dpsz - 1])
+ dpsz--;
+ req->p = mandoc_realloc(req->p,
+ (req->psz + 1) * sizeof(char *));
+ dp = mandoc_strndup(dp, dpsz);
+ if ( ! validate_urifrag(dp)) {
+ fprintf(stderr, "%s/manpath.conf contains "
+ "unsafe path \"%s\"\n", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ if (NULL != strchr(dp, '/')) {
+ fprintf(stderr, "%s/manpath.conf contains "
+ "path with slash \"%s\"\n", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ req->p[req->psz++] = dp;
+ }
+
+ if ( req->p == NULL ) {
+ fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+}
diff --git a/contrib/mdocml/cgi.h.example b/contrib/mdocml/cgi.h.example
new file mode 100644
index 0000000..f4c7831
--- /dev/null
+++ b/contrib/mdocml/cgi.h.example
@@ -0,0 +1,9 @@
+/* Example compile-time configuration file for man.cgi(8). */
+
+#define HTTP_HOST "mdocml.bsd.lv"
+#define MAN_DIR "/var/www/man"
+#define CSS_DIR ""
+#define CUSTOMIZE_TITLE "Manual pages with mandoc"
+#define CUSTOMIZE_BEGIN "<H2>\nManual pages with " \
+ "<A HREF=\"http://mdocml.bsd.lv/\">mandoc</A>\n</H2>"
+#define COMPAT_OLDURI Yes
diff --git a/contrib/mdocml/chars.c b/contrib/mdocml/chars.c
new file mode 100644
index 0000000..d758d0c
--- /dev/null
+++ b/contrib/mdocml/chars.c
@@ -0,0 +1,180 @@
+/* $Id: chars.c,v 1.58 2014/07/23 15:00:08 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+
+#define PRINT_HI 126
+#define PRINT_LO 32
+
+struct ln {
+ struct ln *next;
+ const char *code;
+ const char *ascii;
+ int unicode;
+};
+
+#define LINES_MAX 330
+
+#define CHAR(in, ch, code) \
+ { NULL, (in), (ch), (code) },
+
+#define CHAR_TBL_START static struct ln lines[LINES_MAX] = {
+#define CHAR_TBL_END };
+
+#include "chars.in"
+
+struct mchars {
+ struct ln **htab;
+};
+
+static const struct ln *find(const struct mchars *,
+ const char *, size_t);
+
+
+void
+mchars_free(struct mchars *arg)
+{
+
+ free(arg->htab);
+ free(arg);
+}
+
+struct mchars *
+mchars_alloc(void)
+{
+ struct mchars *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.
+ */
+
+ tab = mandoc_malloc(sizeof(struct mchars));
+ htab = mandoc_calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln *));
+
+ 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);
+}
+
+int
+mchars_spec2cp(const struct mchars *arg, const char *p, size_t sz)
+{
+ const struct ln *ln;
+
+ ln = find(arg, p, sz);
+ if (NULL == ln)
+ return(-1);
+ return(ln->unicode);
+}
+
+char
+mchars_num2char(const char *p, size_t sz)
+{
+ int i;
+
+ if ((i = mandoc_strntoi(p, sz, 10)) < 0)
+ return('\0');
+
+ return(i > 0 && i < 256 && isprint(i) ? i : '\0');
+}
+
+int
+mchars_num2uc(const char *p, size_t sz)
+{
+ int i;
+
+ if ((i = mandoc_strntoi(p, sz, 16)) < 0)
+ return('\0');
+
+ /*
+ * Security warning:
+ * Never extend the range of accepted characters
+ * to overlap with the ASCII range, 0x00-0x7F
+ * without re-auditing the callers of this function.
+ * Some callers might relay on the fact that we never
+ * return ASCII characters for their escaping decisions.
+ *
+ * XXX Code is missing here to exclude bogus ranges.
+ */
+
+ return(i > 0x80 && i <= 0x10FFFF ? i : '\0');
+}
+
+const char *
+mchars_spec2str(const struct mchars *arg,
+ const char *p, size_t sz, size_t *rsz)
+{
+ const struct ln *ln;
+
+ ln = find(arg, p, sz);
+ if (NULL == ln) {
+ *rsz = 1;
+ return(NULL);
+ }
+
+ *rsz = strlen(ln->ascii);
+ return(ln->ascii);
+}
+
+static const struct ln *
+find(const struct mchars *tab, const char *p, size_t sz)
+{
+ const struct ln *pp;
+ int hash;
+
+ assert(p);
+
+ if (0 == sz || p[0] < PRINT_LO || p[0] > PRINT_HI)
+ return(NULL);
+
+ hash = (int)p[0] - PRINT_LO;
+
+ for (pp = tab->htab[hash]; pp; pp = pp->next)
+ if (0 == strncmp(pp->code, p, sz) &&
+ '\0' == pp->code[(int)sz])
+ return(pp);
+
+ return(NULL);
+}
diff --git a/contrib/mdocml/chars.in b/contrib/mdocml/chars.in
new file mode 100644
index 0000000..098504f
--- /dev/null
+++ b/contrib/mdocml/chars.in
@@ -0,0 +1,402 @@
+/* $Id: chars.in,v 1.46 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * The ASCII translation tables.
+ *
+ * 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!
+ */
+
+/* Special break control characters. */
+static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' };
+static const char ascii_break[2] = { ASCII_BREAK, '\0' };
+
+CHAR_TBL_START
+
+/* Spacing. */
+CHAR(" ", ascii_nbrsp, 160)
+CHAR("~", ascii_nbrsp, 160)
+CHAR("0", " ", 8194)
+CHAR("|", "", 0)
+CHAR("^", "", 0)
+CHAR("&", "", 0)
+CHAR("%", "", 0)
+CHAR(":", ascii_break, 0)
+/* XXX The following three do not really belong into this file. */
+CHAR("t", "", 0)
+CHAR("c", "", 0)
+CHAR("}", "", 0)
+
+/* Accents. */
+CHAR("a\"", "\"", 733)
+CHAR("a-", "-", 175)
+CHAR("a.", ".", 729)
+CHAR("a^", "^", 94)
+CHAR("\'", "\'", 180)
+CHAR("aa", "\'", 180)
+CHAR("ga", "`", 96)
+CHAR("`", "`", 96)
+CHAR("ab", "`", 728)
+CHAR("ac", ",", 184)
+CHAR("ad", "\"", 168)
+CHAR("ah", "v", 711)
+CHAR("ao", "o", 730)
+CHAR("a~", "~", 126)
+CHAR("ho", ",", 731)
+CHAR("ha", "^", 94)
+CHAR("ti", "~", 126)
+
+/* Quotes. */
+CHAR("Bq", ",,", 8222)
+CHAR("bq", ",", 8218)
+CHAR("lq", "``", 8220)
+CHAR("rq", "\'\'", 8221)
+CHAR("oq", "`", 8216)
+CHAR("cq", "\'", 8217)
+CHAR("aq", "\'", 39)
+CHAR("dq", "\"", 34)
+CHAR("Fo", "<<", 171)
+CHAR("Fc", ">>", 187)
+CHAR("fo", "<", 8249)
+CHAR("fc", ">", 8250)
+
+/* Brackets. */
+CHAR("lB", "[", 91)
+CHAR("rB", "]", 93)
+CHAR("lC", "{", 123)
+CHAR("rC", "}", 125)
+CHAR("la", "<", 60)
+CHAR("ra", ">", 62)
+CHAR("bv", "|", 9130)
+CHAR("braceex", "|", 9130)
+CHAR("bracketlefttp", "|", 9121)
+CHAR("bracketleftbp", "|", 9123)
+CHAR("bracketleftex", "|", 9122)
+CHAR("bracketrighttp", "|", 9124)
+CHAR("bracketrightbp", "|", 9126)
+CHAR("bracketrightex", "|", 9125)
+CHAR("lt", ",-", 9127)
+CHAR("bracelefttp", ",-", 9127)
+CHAR("lk", "{", 9128)
+CHAR("braceleftmid", "{", 9128)
+CHAR("lb", ",-", 9129)
+CHAR("braceleftbp", "`-", 9129)
+CHAR("braceleftex", "|", 9130)
+CHAR("rt", "-.", 9131)
+CHAR("bracerighttp", "-.", 9131)
+CHAR("rk", "}", 9132)
+CHAR("bracerightmid", "}", 9132)
+CHAR("rb", "-\'", 9133)
+CHAR("bracerightbp", "-\'", 9133)
+CHAR("bracerightex", "|", 9130)
+CHAR("parenlefttp", "/", 9115)
+CHAR("parenleftbp", "\\", 9117)
+CHAR("parenleftex", "|", 9116)
+CHAR("parenrighttp", "\\", 9118)
+CHAR("parenrightbp", "/", 9120)
+CHAR("parenrightex", "|", 9119)
+
+/* Greek characters. */
+CHAR("*A", "A", 913)
+CHAR("*B", "B", 914)
+CHAR("*G", "|", 915)
+CHAR("*D", "/\\", 916)
+CHAR("*E", "E", 917)
+CHAR("*Z", "Z", 918)
+CHAR("*Y", "H", 919)
+CHAR("*H", "O", 920)
+CHAR("*I", "I", 921)
+CHAR("*K", "K", 922)
+CHAR("*L", "/\\", 923)
+CHAR("*M", "M", 924)
+CHAR("*N", "N", 925)
+CHAR("*C", "H", 926)
+CHAR("*O", "O", 927)
+CHAR("*P", "TT", 928)
+CHAR("*R", "P", 929)
+CHAR("*S", ">", 931)
+CHAR("*T", "T", 932)
+CHAR("*U", "Y", 933)
+CHAR("*F", "O_", 934)
+CHAR("*X", "X", 935)
+CHAR("*Q", "Y", 936)
+CHAR("*W", "O", 937)
+CHAR("*a", "a", 945)
+CHAR("*b", "B", 946)
+CHAR("*g", "y", 947)
+CHAR("*d", "d", 948)
+CHAR("*e", "e", 949)
+CHAR("*z", "C", 950)
+CHAR("*y", "n", 951)
+CHAR("*h", "0", 952)
+CHAR("*i", "i", 953)
+CHAR("*k", "k", 954)
+CHAR("*l", "\\", 955)
+CHAR("*m", "u", 956)
+CHAR("*n", "v", 957)
+CHAR("*c", "E", 958)
+CHAR("*o", "o", 959)
+CHAR("*p", "n", 960)
+CHAR("*r", "p", 961)
+CHAR("*s", "o", 963)
+CHAR("*t", "t", 964)
+CHAR("*u", "u", 965)
+CHAR("*f", "o", 981)
+CHAR("*x", "x", 967)
+CHAR("*q", "u", 968)
+CHAR("*w", "w", 969)
+CHAR("+h", "0", 977)
+CHAR("+f", "o", 966)
+CHAR("+p", "w", 982)
+CHAR("+e", "e", 1013)
+CHAR("ts", "s", 962)
+
+/* Accented letters. */
+CHAR(",C", "C", 199)
+CHAR(",c", "c", 231)
+CHAR("/L", "L", 321)
+CHAR("/O", "O", 216)
+CHAR("/l", "l", 322)
+CHAR("/o", "o", 248)
+CHAR("oA", "A", 197)
+CHAR("oa", "a", 229)
+CHAR(":A", "A", 196)
+CHAR(":E", "E", 203)
+CHAR(":I", "I", 207)
+CHAR(":O", "O", 214)
+CHAR(":U", "U", 220)
+CHAR(":a", "a", 228)
+CHAR(":e", "e", 235)
+CHAR(":i", "i", 239)
+CHAR(":o", "o", 246)
+CHAR(":u", "u", 252)
+CHAR(":y", "y", 255)
+CHAR("\'A", "A", 193)
+CHAR("\'E", "E", 201)
+CHAR("\'I", "I", 205)
+CHAR("\'O", "O", 211)
+CHAR("\'U", "U", 218)
+CHAR("\'a", "a", 225)
+CHAR("\'e", "e", 233)
+CHAR("\'i", "i", 237)
+CHAR("\'o", "o", 243)
+CHAR("\'u", "u", 250)
+CHAR("^A", "A", 194)
+CHAR("^E", "E", 202)
+CHAR("^I", "I", 206)
+CHAR("^O", "O", 212)
+CHAR("^U", "U", 219)
+CHAR("^a", "a", 226)
+CHAR("^e", "e", 234)
+CHAR("^i", "i", 238)
+CHAR("^o", "o", 244)
+CHAR("^u", "u", 251)
+CHAR("`A", "A", 192)
+CHAR("`E", "E", 200)
+CHAR("`I", "I", 204)
+CHAR("`O", "O", 210)
+CHAR("`U", "U", 217)
+CHAR("`a", "a", 224)
+CHAR("`e", "e", 232)
+CHAR("`i", "i", 236)
+CHAR("`o", "o", 242)
+CHAR("`u", "u", 249)
+CHAR("~A", "A", 195)
+CHAR("~N", "N", 209)
+CHAR("~O", "O", 213)
+CHAR("~a", "a", 227)
+CHAR("~n", "n", 241)
+CHAR("~o", "o", 245)
+
+/* Arrows and lines. */
+CHAR("<-", "<-", 8592)
+CHAR("->", "->", 8594)
+CHAR("<>", "<>", 8596)
+CHAR("da", "v", 8595)
+CHAR("ua", "^", 8593)
+CHAR("va", "^v", 8597)
+CHAR("lA", "<=", 8656)
+CHAR("rA", "=>", 8658)
+CHAR("hA", "<=>", 8660)
+CHAR("dA", "v", 8659)
+CHAR("uA", "^", 8657)
+CHAR("vA", "^=v", 8661)
+
+/* Logic. */
+CHAR("AN", "^", 8743)
+CHAR("OR", "v", 8744)
+CHAR("no", "~", 172)
+CHAR("tno", "~", 172)
+CHAR("te", "3", 8707)
+CHAR("fa", "V", 8704)
+CHAR("st", "-)", 8715)
+CHAR("tf", ".:.", 8756)
+CHAR("3d", ".:.", 8756)
+CHAR("or", "|", 124)
+
+/* Mathematicals. */
+CHAR("pl", "+", 43)
+CHAR("mi", "-", 8722)
+CHAR("-", "-", 45)
+CHAR("-+", "-+", 8723)
+CHAR("+-", "+-", 177)
+CHAR("t+-", "+-", 177)
+CHAR("pc", ".", 183)
+CHAR("md", ".", 8901)
+CHAR("mu", "x", 215)
+CHAR("tmu", "x", 215)
+CHAR("c*", "x", 8855)
+CHAR("c+", "+", 8853)
+CHAR("di", "-:-", 247)
+CHAR("tdi", "-:-", 247)
+CHAR("f/", "/", 8260)
+CHAR("**", "*", 8727)
+CHAR("<=", "<=", 8804)
+CHAR(">=", ">=", 8805)
+CHAR("<<", "<<", 8810)
+CHAR(">>", ">>", 8811)
+CHAR("eq", "=", 61)
+CHAR("!=", "!=", 8800)
+CHAR("==", "==", 8801)
+CHAR("ne", "!==", 8802)
+CHAR("=~", "=~", 8773)
+CHAR("-~", "-~", 8771)
+CHAR("ap", "~", 8764)
+CHAR("~~", "~~", 8776)
+CHAR("~=", "~=", 8780)
+CHAR("pt", "oc", 8733)
+CHAR("es", "{}", 8709)
+CHAR("mo", "E", 8712)
+CHAR("nm", "!E", 8713)
+CHAR("sb", "(=", 8834)
+CHAR("nb", "(!=", 8836)
+CHAR("sp", "=)", 8835)
+CHAR("nc", "!=)", 8837)
+CHAR("ib", "(=", 8838)
+CHAR("ip", "=)", 8839)
+CHAR("ca", "(^)", 8745)
+CHAR("cu", "U", 8746)
+CHAR("/_", "/_", 8736)
+CHAR("pp", "_|_", 8869)
+CHAR("is", "I", 8747)
+CHAR("integral", "I", 8747)
+CHAR("sum", "E", 8721)
+CHAR("product", "TT", 8719)
+CHAR("coproduct", "U", 8720)
+CHAR("gr", "V", 8711)
+CHAR("sr", "\\/", 8730)
+CHAR("sqrt", "\\/", 8730)
+CHAR("lc", "|~", 8968)
+CHAR("rc", "~|", 8969)
+CHAR("lf", "|_", 8970)
+CHAR("rf", "_|", 8971)
+CHAR("if", "oo", 8734)
+CHAR("Ah", "N", 8501)
+CHAR("Im", "I", 8465)
+CHAR("Re", "R", 8476)
+CHAR("pd", "a", 8706)
+CHAR("-h", "/h", 8463)
+CHAR("12", "1/2", 189)
+CHAR("14", "1/4", 188)
+CHAR("34", "3/4", 190)
+
+/* Ligatures. */
+CHAR("ff", "ff", 64256)
+CHAR("fi", "fi", 64257)
+CHAR("fl", "fl", 64258)
+CHAR("Fi", "ffi", 64259)
+CHAR("Fl", "ffl", 64260)
+CHAR("AE", "AE", 198)
+CHAR("ae", "ae", 230)
+CHAR("OE", "OE", 338)
+CHAR("oe", "oe", 339)
+CHAR("ss", "ss", 223)
+CHAR("IJ", "IJ", 306)
+CHAR("ij", "ij", 307)
+
+/* Special letters. */
+CHAR("-D", "D", 208)
+CHAR("Sd", "o", 240)
+CHAR("TP", "b", 222)
+CHAR("Tp", "b", 254)
+CHAR(".i", "i", 305)
+CHAR(".j", "j", 567)
+
+/* Currency. */
+CHAR("Do", "$", 36)
+CHAR("ct", "c", 162)
+CHAR("Eu", "EUR", 8364)
+CHAR("eu", "EUR", 8364)
+CHAR("Ye", "Y", 165)
+CHAR("Po", "L", 163)
+CHAR("Cs", "x", 164)
+CHAR("Fn", "f", 402)
+
+/* Lines. */
+CHAR("ba", "|", 124)
+CHAR("br", "|", 9474)
+CHAR("ul", "_", 95)
+CHAR("rl", "-", 8254)
+CHAR("bb", "|", 166)
+CHAR("sl", "/", 47)
+CHAR("rs", "\\", 92)
+
+/* Text markers. */
+CHAR("ci", "o", 9675)
+CHAR("bu", "o", 8226)
+CHAR("dd", "=", 8225)
+CHAR("dg", "-", 8224)
+CHAR("lz", "<>", 9674)
+CHAR("sq", "[]", 9633)
+CHAR("ps", "9|", 182)
+CHAR("sc", "S", 167)
+CHAR("lh", "<=", 9756)
+CHAR("rh", "=>", 9758)
+CHAR("at", "@", 64)
+CHAR("sh", "#", 35)
+CHAR("CR", "_|", 8629)
+CHAR("OK", "\\/", 10003)
+
+/* Legal symbols. */
+CHAR("co", "(C)", 169)
+CHAR("rg", "(R)", 174)
+CHAR("tm", "tm", 8482)
+
+/* Punctuation. */
+CHAR(".", ".", 46)
+CHAR("r!", "i", 161)
+CHAR("r?", "c", 191)
+CHAR("em", "--", 8212)
+CHAR("en", "-", 8211)
+CHAR("hy", "-", 8208)
+CHAR("e", "\\", 92)
+
+/* Units. */
+CHAR("de", "o", 176)
+CHAR("%0", "%o", 8240)
+CHAR("fm", "\'", 8242)
+CHAR("sd", "\"", 8243)
+CHAR("mc", "mu", 181)
+
+CHAR_TBL_END
diff --git a/contrib/mdocml/compat_fgetln.c b/contrib/mdocml/compat_fgetln.c
new file mode 100644
index 0000000..49c9985
--- /dev/null
+++ b/contrib/mdocml/compat_fgetln.c
@@ -0,0 +1,93 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_FGETLN
+
+int dummy;
+
+#else
+
+/* $NetBSD: fgetln.c,v 1.3 2006/09/25 07:18:17 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+char *
+fgetln(fp, len)
+ FILE *fp;
+ size_t *len;
+{
+ static char *buf = NULL;
+ static size_t bufsiz = 0;
+ char *ptr;
+
+
+ if (buf == NULL) {
+ bufsiz = BUFSIZ;
+ if ((buf = malloc(bufsiz)) == NULL)
+ return NULL;
+ }
+
+ if (fgets(buf, bufsiz, fp) == NULL)
+ return NULL;
+
+ *len = 0;
+ while ((ptr = strchr(&buf[*len], '\n')) == NULL) {
+ size_t nbufsiz = bufsiz + BUFSIZ;
+ char *nbuf = realloc(buf, nbufsiz);
+
+ if (nbuf == NULL) {
+ int oerrno = errno;
+ free(buf);
+ errno = oerrno;
+ buf = NULL;
+ return NULL;
+ } else
+ buf = nbuf;
+
+ *len = bufsiz;
+ if (fgets(&buf[bufsiz], BUFSIZ, fp) == NULL)
+ return buf;
+
+ bufsiz = nbufsiz;
+ }
+
+ *len = (ptr - buf) + 1;
+ return buf;
+}
+
+#endif
diff --git a/contrib/mdocml/compat_getsubopt.c b/contrib/mdocml/compat_getsubopt.c
new file mode 100644
index 0000000..9cd4153
--- /dev/null
+++ b/contrib/mdocml/compat_getsubopt.c
@@ -0,0 +1,104 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_GETSUBOPT
+
+int dummy;
+
+#else
+
+/* $OpenBSD: getsubopt.c,v 1.4 2005/08/08 08:05:36 espie Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * The SVID interface to getsubopt provides no way of figuring out which
+ * part of the suboptions list wasn't matched. This makes error messages
+ * tricky... The extern variable suboptarg is a pointer to the token
+ * which didn't match.
+ */
+char *suboptarg;
+
+int
+getsubopt(char **optionp, char * const *tokens, char **valuep)
+{
+ int cnt;
+ char *p;
+
+ suboptarg = *valuep = NULL;
+
+ if (!optionp || !*optionp)
+ return(-1);
+
+ /* skip leading white-space, commas */
+ for (p = *optionp; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p);
+
+ if (!*p) {
+ *optionp = p;
+ return(-1);
+ }
+
+ /* save the start of the token, and skip the rest of the token. */
+ for (suboptarg = p;
+ *++p && *p != ',' && *p != '=' && *p != ' ' && *p != '\t';);
+
+ if (*p) {
+ /*
+ * If there's an equals sign, set the value pointer, and
+ * skip over the value part of the token. Terminate the
+ * token.
+ */
+ if (*p == '=') {
+ *p = '\0';
+ for (*valuep = ++p;
+ *p && *p != ',' && *p != ' ' && *p != '\t'; ++p);
+ if (*p)
+ *p++ = '\0';
+ } else
+ *p++ = '\0';
+ /* Skip any whitespace or commas after this token. */
+ for (; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p);
+ }
+
+ /* set optionp for next round. */
+ *optionp = p;
+
+ for (cnt = 0; *tokens; ++tokens, ++cnt)
+ if (!strcmp(suboptarg, *tokens))
+ return(cnt);
+ return(-1);
+}
+
+#endif
diff --git a/contrib/mdocml/compat_ohash.c b/contrib/mdocml/compat_ohash.c
new file mode 100644
index 0000000..0992b36
--- /dev/null
+++ b/contrib/mdocml/compat_ohash.c
@@ -0,0 +1,339 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_OHASH
+
+int dummy;
+
+#else
+
+/* $OpenBSD: ohash.c,v 1.1 2014/06/02 18:52:03 deraadt Exp $ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include "compat_ohash.h"
+
+struct _ohash_record {
+ uint32_t hv;
+ const char *p;
+};
+
+#define DELETED ((const char *)h)
+#define NONE (h->size)
+
+/* Don't bother changing the hash table if the change is small enough. */
+#define MINSIZE (1UL << 4)
+#define MINDELETED 4
+
+static void ohash_resize(struct ohash *);
+
+
+/* This handles the common case of variable length keys, where the
+ * key is stored at the end of the record.
+ */
+void *
+ohash_create_entry(struct ohash_info *i, const char *start, const char **end)
+{
+ char *p;
+
+ if (!*end)
+ *end = start + strlen(start);
+ p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data);
+ if (p) {
+ memcpy(p+i->key_offset, start, *end-start);
+ p[i->key_offset + (*end - start)] = '\0';
+ }
+ return (void *)p;
+}
+
+/* hash_delete only frees the hash structure. Use hash_first/hash_next
+ * to free entries as well. */
+void
+ohash_delete(struct ohash *h)
+{
+ (h->info.free)(h->t, h->info.data);
+#ifndef NDEBUG
+ h->t = NULL;
+#endif
+}
+
+static void
+ohash_resize(struct ohash *h)
+{
+ struct _ohash_record *n;
+ size_t ns;
+ unsigned int j;
+ unsigned int i, incr;
+
+ if (4 * h->deleted < h->total) {
+ if (h->size >= (UINT_MAX >> 1U))
+ ns = UINT_MAX;
+ else
+ ns = h->size << 1U;
+ } else if (3 * h->deleted > 2 * h->total)
+ ns = h->size >> 1U;
+ else
+ ns = h->size;
+ if (ns < MINSIZE)
+ ns = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_EXPAND++;
+ STAT_HASH_SIZE += ns - h->size;
+#endif
+
+ n = (h->info.calloc)(ns, sizeof(struct _ohash_record), h->info.data);
+ if (!n)
+ return;
+
+ for (j = 0; j < h->size; j++) {
+ if (h->t[j].p != NULL && h->t[j].p != DELETED) {
+ i = h->t[j].hv % ns;
+ incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1;
+ while (n[i].p != NULL) {
+ i += incr;
+ if (i >= ns)
+ i -= ns;
+ }
+ n[i].hv = h->t[j].hv;
+ n[i].p = h->t[j].p;
+ }
+ }
+ (h->info.free)(h->t, h->info.data);
+ h->t = n;
+ h->size = ns;
+ h->total -= h->deleted;
+ h->deleted = 0;
+}
+
+void *
+ohash_remove(struct ohash *h, unsigned int i)
+{
+ void *result = (void *)h->t[i].p;
+
+ if (result == NULL || result == DELETED)
+ return NULL;
+
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES--;
+#endif
+ h->t[i].p = DELETED;
+ h->deleted++;
+ if (h->deleted >= MINDELETED && 4 * h->deleted > h->total)
+ ohash_resize(h);
+ return result;
+}
+
+void *
+ohash_find(struct ohash *h, unsigned int i)
+{
+ if (h->t[i].p == DELETED)
+ return NULL;
+ else
+ return (void *)h->t[i].p;
+}
+
+void *
+ohash_insert(struct ohash *h, unsigned int i, void *p)
+{
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES++;
+#endif
+ if (h->t[i].p == DELETED) {
+ h->deleted--;
+ h->t[i].p = p;
+ } else {
+ h->t[i].p = p;
+ /* Arbitrary resize boundary. Tweak if not efficient enough. */
+ if (++h->total * 4 > h->size * 3)
+ ohash_resize(h);
+ }
+ return p;
+}
+
+unsigned int
+ohash_entries(struct ohash *h)
+{
+ return h->total - h->deleted;
+}
+
+void *
+ohash_first(struct ohash *h, unsigned int *pos)
+{
+ *pos = 0;
+ return ohash_next(h, pos);
+}
+
+void *
+ohash_next(struct ohash *h, unsigned int *pos)
+{
+ for (; *pos < h->size; (*pos)++)
+ if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL)
+ return (void *)h->t[(*pos)++].p;
+ return NULL;
+}
+
+void
+ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info)
+{
+ h->size = 1UL << size;
+ if (h->size < MINSIZE)
+ h->size = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_CREATION++;
+ STAT_HASH_SIZE += h->size;
+#endif
+ /* Copy info so that caller may free it. */
+ h->info.key_offset = info->key_offset;
+ h->info.calloc = info->calloc;
+ h->info.free = info->free;
+ h->info.alloc = info->alloc;
+ h->info.data = info->data;
+ h->t = (h->info.calloc)(h->size, sizeof(struct _ohash_record),
+ h->info.data);
+ h->total = h->deleted = 0;
+}
+
+uint32_t
+ohash_interval(const char *s, const char **e)
+{
+ uint32_t k;
+
+ if (!*e)
+ *e = s + strlen(s);
+ if (s == *e)
+ k = 0;
+ else
+ k = *s++;
+ while (s != *e)
+ k = ((k << 2) | (k >> 30)) ^ *s++;
+ return k;
+}
+
+unsigned int
+ohash_lookup_interval(struct ohash *h, const char *start, const char *end,
+ uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ strncmp(h->t[i].p+h->info.key_offset, start,
+ end - start) == 0 &&
+ (h->t[i].p+h->info.key_offset)[end-start] == '\0') {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ return i;
+ }
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
+
+unsigned int
+ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ } return i;
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
+
+unsigned int
+ohash_qlookup(struct ohash *h, const char *s)
+{
+ const char *e = NULL;
+ return ohash_qlookupi(h, s, &e);
+}
+
+unsigned int
+ohash_qlookupi(struct ohash *h, const char *s, const char **e)
+{
+ uint32_t hv;
+
+ hv = ohash_interval(s, e);
+ return ohash_lookup_interval(h, s, *e, hv);
+}
+
+#endif /*!HAVE_OHASH*/
diff --git a/contrib/mdocml/compat_ohash.h b/contrib/mdocml/compat_ohash.h
new file mode 100644
index 0000000..e3124c9
--- /dev/null
+++ b/contrib/mdocml/compat_ohash.h
@@ -0,0 +1,73 @@
+/* $OpenBSD: ohash.h,v 1.2 2014/06/02 18:52:03 deraadt Exp $ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef OHASH_H
+#define OHASH_H
+
+/* Open hashing support.
+ * Open hashing was chosen because it is much lighter than other hash
+ * techniques, and more efficient in most cases.
+ */
+
+/* user-visible data structure */
+struct ohash_info {
+ ptrdiff_t key_offset;
+ void *data; /* user data */
+ void *(*calloc)(size_t, size_t, void *);
+ void (*free)(void *, void *);
+ void *(*alloc)(size_t, void *);
+};
+
+struct _ohash_record;
+
+/* private structure. It's there just so you can do a sizeof */
+struct ohash {
+ struct _ohash_record *t;
+ struct ohash_info info;
+ unsigned int size;
+ unsigned int total;
+ unsigned int deleted;
+};
+
+/* For this to be tweakable, we use small primitives, and leave part of the
+ * logic to the client application. e.g., hashing is left to the client
+ * application. We also provide a simple table entry lookup that yields
+ * a hashing table index (opaque) to be used in find/insert/remove.
+ * The keys are stored at a known position in the client data.
+ */
+__BEGIN_DECLS
+void ohash_init(struct ohash *, unsigned, struct ohash_info *);
+void ohash_delete(struct ohash *);
+
+unsigned int ohash_lookup_interval(struct ohash *, const char *,
+ const char *, uint32_t);
+unsigned int ohash_lookup_memory(struct ohash *, const char *,
+ size_t, uint32_t);
+void *ohash_find(struct ohash *, unsigned int);
+void *ohash_remove(struct ohash *, unsigned int);
+void *ohash_insert(struct ohash *, unsigned int, void *);
+void *ohash_first(struct ohash *, unsigned int *);
+void *ohash_next(struct ohash *, unsigned int *);
+unsigned int ohash_entries(struct ohash *);
+
+void *ohash_create_entry(struct ohash_info *, const char *, const char **);
+uint32_t ohash_interval(const char *, const char **);
+
+unsigned int ohash_qlookupi(struct ohash *, const char *, const char **);
+unsigned int ohash_qlookup(struct ohash *, const char *);
+__END_DECLS
+#endif
diff --git a/contrib/mdocml/compat_reallocarray.c b/contrib/mdocml/compat_reallocarray.c
new file mode 100644
index 0000000..e25d837
--- /dev/null
+++ b/contrib/mdocml/compat_reallocarray.c
@@ -0,0 +1,45 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_REALLOCARRAY
+
+int dummy;
+
+#else
+
+/* $OpenBSD: malloc.c,v 1.158 2014/04/23 15:07:27 tedu Exp $ */
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * 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 <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
+
+#endif /*!HAVE_REALLOCARRAY*/
diff --git a/contrib/mdocml/compat_sqlite3_errstr.c b/contrib/mdocml/compat_sqlite3_errstr.c
new file mode 100644
index 0000000..b8d6eb5
--- /dev/null
+++ b/contrib/mdocml/compat_sqlite3_errstr.c
@@ -0,0 +1,18 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SQLITE3_ERRSTR
+
+int dummy;
+
+#else
+
+const char *
+sqlite3_errstr(int rc)
+{
+
+ return(rc ? "unknown error" : "not an error");
+}
+
+#endif
diff --git a/contrib/mdocml/compat_strcasestr.c b/contrib/mdocml/compat_strcasestr.c
new file mode 100644
index 0000000..5216d02
--- /dev/null
+++ b/contrib/mdocml/compat_strcasestr.c
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRCASESTR
+
+int dummy;
+
+#else
+
+/* ($)NetBSD: strcasestr.c,v 1.2 2005/02/09 21:35:47 kleink Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+
+#define __UNCONST(a) ((void *)(unsigned long)(const void *)(a))
+
+/*
+ * Find the first occurrence of find in s, ignore case.
+ */
+char *
+strcasestr(const char *s, const char *find)
+{
+ char c, sc;
+ size_t len;
+
+ if ((c = *find++) != 0) {
+ c = tolower((unsigned char)c);
+ len = strlen(find);
+ do {
+ do {
+ if ((sc = *s++) == 0)
+ return (NULL);
+ } while ((char)tolower((unsigned char)sc) != c);
+ } while (strncasecmp(s, find, len) != 0);
+ s--;
+ }
+ return __UNCONST(s);
+}
+
+#endif
diff --git a/contrib/mdocml/compat_strlcat.c b/contrib/mdocml/compat_strlcat.c
new file mode 100644
index 0000000..543d40b
--- /dev/null
+++ b/contrib/mdocml/compat_strlcat.c
@@ -0,0 +1,67 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRLCAT
+
+int dummy;
+
+#else
+
+/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 <string.h>
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
+
+#endif
diff --git a/contrib/mdocml/compat_strlcpy.c b/contrib/mdocml/compat_strlcpy.c
new file mode 100644
index 0000000..a7c64ff
--- /dev/null
+++ b/contrib/mdocml/compat_strlcpy.c
@@ -0,0 +1,63 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRLCPY
+
+int dummy;
+
+#else
+
+/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+#endif
diff --git a/contrib/mdocml/compat_strsep.c b/contrib/mdocml/compat_strsep.c
new file mode 100644
index 0000000..a5c58c6
--- /dev/null
+++ b/contrib/mdocml/compat_strsep.c
@@ -0,0 +1,80 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRSEP
+
+int dummy;
+
+#else
+
+/* ($)OpenBSD: strsep.c,v 1.6 2005/08/08 08:05:37 espie Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Get next token from string *stringp, where tokens are possibly-empty
+ * strings separated by characters from delim.
+ *
+ * Writes NULs into the string at *stringp to end tokens.
+ * delim need not remain constant from call to call.
+ * On return, *stringp points past the last NUL written (if there might
+ * be further tokens), or is NULL (if there are definitely no more tokens).
+ *
+ * If *stringp is NULL, strsep returns NULL.
+ */
+char *
+strsep(char **stringp, const char *delim)
+{
+ char *s;
+ const char *spanp;
+ int c, sc;
+ char *tok;
+
+ if ((s = *stringp) == NULL)
+ return (NULL);
+ for (tok = s;;) {
+ c = *s++;
+ spanp = delim;
+ do {
+ if ((sc = *spanp++) == c) {
+ if (c == 0)
+ s = NULL;
+ else
+ s[-1] = 0;
+ *stringp = s;
+ return (tok);
+ }
+ } while (sc != 0);
+ }
+ /* NOTREACHED */
+}
+
+#endif
diff --git a/contrib/mdocml/config.h b/contrib/mdocml/config.h
new file mode 100644
index 0000000..c1d5951
--- /dev/null
+++ b/contrib/mdocml/config.h
@@ -0,0 +1,62 @@
+#ifndef MANDOC_CONFIG_H
+#define MANDOC_CONFIG_H
+
+#if defined(__linux__) || defined(__MINT__)
+# define _GNU_SOURCE /* getsubopt(), strcasestr(), strptime() */
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+
+#define VERSION "1.13.1"
+#define HAVE_FGETLN
+#define HAVE_GETSUBOPT
+#define HAVE_MMAP
+#define HAVE_STRCASESTR
+#define HAVE_STRLCAT
+#define HAVE_STRLCPY
+#define HAVE_STRPTIME
+#define HAVE_STRSEP
+
+#if !defined(__BEGIN_DECLS)
+# ifdef __cplusplus
+# define __BEGIN_DECLS extern "C" {
+# else
+# define __BEGIN_DECLS
+# endif
+#endif
+#if !defined(__END_DECLS)
+# ifdef __cplusplus
+# define __END_DECLS }
+# else
+# define __END_DECLS
+# endif
+#endif
+
+#ifndef HAVE_FGETLN
+extern char *fgetln(FILE *, size_t *);
+#endif
+#ifndef HAVE_GETSUBOPT
+extern int getsubopt(char **, char * const *, char **);
+extern char *suboptarg;
+#endif
+#ifndef HAVE_REALLOCARRAY
+extern void *reallocarray(void *, size_t, size_t);
+#endif
+#ifndef HAVE_SQLITE3_ERRSTR
+extern const char *sqlite3_errstr(int);
+#endif
+#ifndef HAVE_STRCASESTR
+extern char *strcasestr(const char *, const char *);
+#endif
+#ifndef HAVE_STRLCAT
+extern size_t strlcat(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRLCPY
+extern size_t strlcpy(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRSEP
+extern char *strsep(char **, const char *);
+#endif
+
+#endif /* MANDOC_CONFIG_H */
diff --git a/contrib/mdocml/config.h.post b/contrib/mdocml/config.h.post
new file mode 100644
index 0000000..e95f5f5
--- /dev/null
+++ b/contrib/mdocml/config.h.post
@@ -0,0 +1,42 @@
+#if !defined(__BEGIN_DECLS)
+# ifdef __cplusplus
+# define __BEGIN_DECLS extern "C" {
+# else
+# define __BEGIN_DECLS
+# endif
+#endif
+#if !defined(__END_DECLS)
+# ifdef __cplusplus
+# define __END_DECLS }
+# else
+# define __END_DECLS
+# endif
+#endif
+
+#ifndef HAVE_FGETLN
+extern char *fgetln(FILE *, size_t *);
+#endif
+#ifndef HAVE_GETSUBOPT
+extern int getsubopt(char **, char * const *, char **);
+extern char *suboptarg;
+#endif
+#ifndef HAVE_REALLOCARRAY
+extern void *reallocarray(void *, size_t, size_t);
+#endif
+#ifndef HAVE_SQLITE3_ERRSTR
+extern const char *sqlite3_errstr(int);
+#endif
+#ifndef HAVE_STRCASESTR
+extern char *strcasestr(const char *, const char *);
+#endif
+#ifndef HAVE_STRLCAT
+extern size_t strlcat(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRLCPY
+extern size_t strlcpy(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRSEP
+extern char *strsep(char **, const char *);
+#endif
+
+#endif /* MANDOC_CONFIG_H */
diff --git a/contrib/mdocml/config.h.pre b/contrib/mdocml/config.h.pre
new file mode 100644
index 0000000..1c3940d
--- /dev/null
+++ b/contrib/mdocml/config.h.pre
@@ -0,0 +1,9 @@
+#ifndef MANDOC_CONFIG_H
+#define MANDOC_CONFIG_H
+
+#if defined(__linux__) || defined(__MINT__)
+# define _GNU_SOURCE /* getsubopt(), strcasestr(), strptime() */
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
diff --git a/contrib/mdocml/configure b/contrib/mdocml/configure
new file mode 100755
index 0000000..5b987eb
--- /dev/null
+++ b/contrib/mdocml/configure
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+echo "/* RUNNING ./CONFIGURE - SHOULD BE USED ONLY VIA MAKE, READ INSTALL */"
+
+set -e
+exec > config.h 2> config.log
+
+CFLAGS="${CFLAGS} -Wno-unused -Werror"
+
+runtest() {
+ echo ${CC} ${CFLAGS} ${3} -o test-${1} test-${1}.c 1>&2
+ ${CC} ${CFLAGS} ${3} -o "test-${1}" "test-${1}.c" 1>&2 || return 0
+ "./test-${1}" && echo "#define HAVE_${2}" \
+ || echo FAILURE: test-${1} returned $? 1>&2
+ rm "test-${1}"
+}
+
+cat config.h.pre
+echo
+echo "#define VERSION \"${VERSION}\""
+runtest fgetln FGETLN
+runtest getsubopt GETSUBOPT
+runtest mmap MMAP
+runtest ohash OHASH "${DBLIB}"
+runtest reallocarray REALLOCARRAY
+runtest sqlite3_errstr SQLITE3_ERRSTR "${DBLIB}"
+runtest strcasestr STRCASESTR
+runtest strlcat STRLCAT
+runtest strlcpy STRLCPY
+runtest strptime STRPTIME
+runtest strsep STRSEP
+echo
+cat config.h.post
+
+exit 0
diff --git a/contrib/mdocml/demandoc.c b/contrib/mdocml/demandoc.c
new file mode 100644
index 0000000..4a7b979
--- /dev/null
+++ b/contrib/mdocml/demandoc.c
@@ -0,0 +1,257 @@
+/* $Id: demandoc.c,v 1.10 2014/03/19 22:20:43 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "man.h"
+#include "mdoc.h"
+#include "mandoc.h"
+
+static void pline(int, int *, int *, int);
+static void pman(const struct man_node *, int *, int *, int);
+static void pmandoc(struct mparse *, int, const char *, int);
+static void pmdoc(const struct mdoc_node *, int *, int *, int);
+static void pstring(const char *, int, int *, int);
+static void usage(void);
+
+static const char *progname;
+
+int
+main(int argc, char *argv[])
+{
+ struct mparse *mp;
+ int ch, i, list;
+ extern int optind;
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ mp = NULL;
+ list = 0;
+
+ while (-1 != (ch = getopt(argc, argv, "ikm:pw")))
+ switch (ch) {
+ case ('i'):
+ /* FALLTHROUGH */
+ case ('k'):
+ /* FALLTHROUGH */
+ case ('m'):
+ /* FALLTHROUGH */
+ case ('p'):
+ break;
+ case ('w'):
+ list = 1;
+ break;
+ default:
+ usage();
+ return((int)MANDOCLEVEL_BADARG);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL, NULL);
+ assert(mp);
+
+ if (0 == argc)
+ pmandoc(mp, STDIN_FILENO, "<stdin>", list);
+
+ for (i = 0; i < argc; i++) {
+ mparse_reset(mp);
+ pmandoc(mp, -1, argv[i], list);
+ }
+
+ mparse_free(mp);
+ return((int)MANDOCLEVEL_OK);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s [-w] [files...]\n", progname);
+}
+
+static void
+pmandoc(struct mparse *mp, int fd, const char *fn, int list)
+{
+ struct mdoc *mdoc;
+ struct man *man;
+ int line, col;
+
+ if (mparse_readfd(mp, fd, fn) >= MANDOCLEVEL_FATAL) {
+ fprintf(stderr, "%s: Parse failure\n", fn);
+ return;
+ }
+
+ mparse_result(mp, &mdoc, &man, NULL);
+ line = 1;
+ col = 0;
+
+ if (mdoc)
+ pmdoc(mdoc_node(mdoc), &line, &col, list);
+ else if (man)
+ pman(man_node(man), &line, &col, list);
+ else
+ return;
+
+ if ( ! list)
+ putchar('\n');
+}
+
+/*
+ * Strip the escapes out of a string, emitting the results.
+ */
+static void
+pstring(const char *p, int col, int *colp, int list)
+{
+ enum mandoc_esc esc;
+ const char *start, *end;
+ int emit;
+
+ /*
+ * Print as many column spaces til we achieve parity with the
+ * input document.
+ */
+
+again:
+ if (list && '\0' != *p) {
+ while (isspace((unsigned char)*p))
+ p++;
+
+ while ('\'' == *p || '(' == *p || '"' == *p)
+ p++;
+
+ emit = isalpha((unsigned char)p[0]) &&
+ isalpha((unsigned char)p[1]);
+
+ for (start = p; '\0' != *p; p++)
+ if ('\\' == *p) {
+ p++;
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc)
+ return;
+ emit = 0;
+ } else if (isspace((unsigned char)*p))
+ break;
+
+ end = p - 1;
+
+ while (end > start)
+ if ('.' == *end || ',' == *end ||
+ '\'' == *end || '"' == *end ||
+ ')' == *end || '!' == *end ||
+ '?' == *end || ':' == *end ||
+ ';' == *end)
+ end--;
+ else
+ break;
+
+ if (emit && end - start >= 1) {
+ for ( ; start <= end; start++)
+ if (ASCII_HYPH == *start)
+ putchar('-');
+ else
+ putchar((unsigned char)*start);
+ putchar('\n');
+ }
+
+ if (isspace((unsigned char)*p))
+ goto again;
+
+ return;
+ }
+
+ while (*colp < col) {
+ putchar(' ');
+ (*colp)++;
+ }
+
+ /*
+ * Print the input word, skipping any special characters.
+ */
+ while ('\0' != *p)
+ if ('\\' == *p) {
+ p++;
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc)
+ break;
+ } else {
+ putchar((unsigned char )*p++);
+ (*colp)++;
+ }
+}
+
+static void
+pline(int line, int *linep, int *col, int list)
+{
+
+ if (list)
+ return;
+
+ /*
+ * Print out as many lines as needed to reach parity with the
+ * original input.
+ */
+
+ while (*linep < line) {
+ putchar('\n');
+ (*linep)++;
+ }
+
+ *col = 0;
+}
+
+static void
+pmdoc(const struct mdoc_node *p, int *line, int *col, int list)
+{
+
+ for ( ; p; p = p->next) {
+ if (MDOC_LINE & p->flags)
+ pline(p->line, line, col, list);
+ if (MDOC_TEXT == p->type)
+ pstring(p->string, p->pos, col, list);
+ if (p->child)
+ pmdoc(p->child, line, col, list);
+ }
+}
+
+static void
+pman(const struct man_node *p, int *line, int *col, int list)
+{
+
+ for ( ; p; p = p->next) {
+ if (MAN_LINE & p->flags)
+ pline(p->line, line, col, list);
+ if (MAN_TEXT == p->type)
+ pstring(p->string, p->pos, col, list);
+ if (p->child)
+ pman(p->child, line, col, list);
+ }
+}
diff --git a/contrib/mdocml/eqn.7 b/contrib/mdocml/eqn.7
new file mode 100644
index 0000000..dcbad41
--- /dev/null
+++ b/contrib/mdocml/eqn.7
@@ -0,0 +1,279 @@
+.\" $Id: eqn.7,v 1.29 2013/07/13 19:41:16 schwarze Exp $
+.\"
+.\" Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\"
+.\" 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 13 2013 $
+.Dt EQN 7
+.Os
+.Sh NAME
+.Nm eqn
+.Nd eqn language reference for mandoc
+.Sh DESCRIPTION
+The
+.Nm eqn
+language is an equation-formatting language.
+It is used within
+.Xr mdoc 7
+and
+.Xr man 7
+.Ux
+manual pages.
+It describes the
+.Em structure
+of an equation, not its mathematical meaning.
+This manual describes the
+.Nm
+language accepted by the
+.Xr mandoc 1
+utility, which corresponds to the Second Edition eqn specification (see
+.Sx SEE ALSO
+for references).
+.Pp
+Equations within
+.Xr mdoc 7
+or
+.Xr man 7
+documents are enclosed by the standalone
+.Sq \&.EQ
+and
+.Sq \&.EN
+tags.
+Equations are multi-line blocks consisting of formulas and control
+statements.
+.Sh EQUATION STRUCTURE
+Each equation is bracketed by
+.Sq \&.EQ
+and
+.Sq \&.EN
+strings.
+.Em Note :
+these are not the same as
+.Xr roff 7
+macros, and may only be invoked as
+.Sq \&.EQ .
+.Pp
+The equation grammar is as follows, where quoted strings are
+case-sensitive literals in the input:
+.Bd -literal -offset indent
+eqn : box | eqn box
+box : text
+ | \*q{\*q eqn \*q}\*q
+ | \*qdefine\*q text text
+ | \*qndefine\*q text text
+ | \*qtdefine\*q text text
+ | \*qgfont\*q text
+ | \*qgsize\*q text
+ | \*qset\*q text text
+ | \*qundef\*q text
+ | box pos box
+ | box mark
+ | \*qmatrix\*q \*q{\*q [col \*q{\*q list \*q}\*q ]*
+ | pile \*q{\*q list \*q}\*q
+ | font box
+ | \*qsize\*q text box
+ | \*qleft\*q text eqn [\*qright\*q text]
+col : \*qlcol\*q | \*qrcol\*q | \*qccol\*q | \*qcol\*q
+text : [^space\e\*q]+ | \e\*q.*\e\*q
+pile : \*qlpile\*q | \*qcpile\*q | \*qrpile\*q | \*qpile\*q
+pos : \*qover\*q | \*qsup\*q | \*qsub\*q | \*qto\*q | \*qfrom\*q
+mark : \*qdot\*q | \*qdotdot\*q | \*qhat\*q | \*qtilde\*q | \*qvec\*q
+ | \*qdyad\*q | \*qbar\*q | \*qunder\*q
+font : \*qroman\*q | \*qitalic\*q | \*qbold\*q | \*qfat\*q
+list : eqn
+ | list \*qabove\*q eqn
+space : [\e^~ \et]
+.Ed
+.Pp
+White-space consists of the space, tab, circumflex, and tilde
+characters.
+If within a quoted string, these space characters are retained.
+Quoted strings are also not scanned for replacement definitions.
+.Pp
+The following text terms are translated into a rendered glyph, if
+available: alpha, beta, chi, delta, epsilon, eta, gamma, iota, kappa,
+lambda, mu, nu, omega, omicron, phi, pi, psi, rho, sigma, tau, theta,
+upsilon, xi, zeta, DELTA, GAMMA, LAMBDA, OMEGA, PHI, PI, PSI, SIGMA,
+THETA, UPSILON, XI, inter (intersection), union (union), prod (product),
+int (integral), sum (summation), grad (gradient), del (vector
+differential), times (multiply), cdot (centre-dot), nothing (zero-width
+space), approx (approximately equals), prime (prime), half (one-half),
+partial (partial differential), inf (infinity), >> (much greater), <<
+(much less), \-> (left arrow), <\- (right arrow), += (plus-minus), !=
+(not equal), == (equivalence), <= (less-than-equal), and >=
+(more-than-equal).
+.Pp
+The following control statements are available:
+.Bl -tag -width Ds
+.It Cm define
+Replace all occurrences of a key with a value.
+Its syntax is as follows:
+.Pp
+.D1 define Ar key cvalc
+.Pp
+The first character of the value string,
+.Ar c ,
+is used as the delimiter for the value
+.Ar val .
+This allows for arbitrary enclosure of terms (not just quotes), such as
+.Pp
+.D1 define Ar foo 'bar baz'
+.D1 define Ar foo cbar bazc
+.Pp
+It is an error to have an empty
+.Ar key
+or
+.Ar val .
+Note that a quoted
+.Ar key
+causes errors in some
+.Nm
+implementations and should not be considered portable.
+It is not expanded for replacements.
+Definitions may refer to other definitions; these are evaluated
+recursively when text replacement occurs and not when the definition is
+created.
+.Pp
+Definitions can create arbitrary strings, for example, the following is
+a legal construction.
+.Bd -literal -offset indent
+define foo 'define'
+foo bar 'baz'
+.Ed
+.Pp
+Self-referencing definitions will raise an error.
+The
+.Cm ndefine
+statement is a synonym for
+.Cm define ,
+while
+.Cm tdefine
+is discarded.
+.It Cm gfont
+Set the default font of subsequent output.
+Its syntax is as follows:
+.Pp
+.D1 gfont Ar font
+.Pp
+In mandoc, this value is discarded.
+.It Cm gsize
+Set the default size of subsequent output.
+Its syntax is as follows:
+.Pp
+.D1 gsize Ar size
+.Pp
+The
+.Ar size
+value should be an integer.
+.It Cm set
+Set an equation mode.
+In mandoc, both arguments are thrown away.
+Its syntax is as follows:
+.Pp
+.D1 set Ar key val
+.Pp
+The
+.Ar key
+and
+.Ar val
+are not expanded for replacements.
+This statement is a GNU extension.
+.It Cm undef
+Unset a previously-defined key.
+Its syntax is as follows:
+.Pp
+.D1 define Ar key
+.Pp
+Once invoked, the definition for
+.Ar key
+is discarded.
+The
+.Ar key
+is not expanded for replacements.
+This statement is a GNU extension.
+.El
+.Sh COMPATIBILITY
+This section documents the compatibility of mandoc
+.Nm
+and the troff
+.Nm
+implementation (including GNU troff).
+.Pp
+.Bl -dash -compact
+.It
+The text string
+.Sq \e\*q
+is interpreted as a literal quote in troff.
+In mandoc, this is interpreted as a comment.
+.It
+In troff, The circumflex and tilde white-space symbols map to
+fixed-width spaces.
+In mandoc, these characters are synonyms for the space character.
+.It
+The troff implementation of
+.Nm
+allows for equation alignment with the
+.Cm mark
+and
+.Cm lineup
+tokens.
+mandoc discards these tokens.
+The
+.Cm back Ar n ,
+.Cm fwd Ar n ,
+.Cm up Ar n ,
+and
+.Cm down Ar n
+commands are also ignored.
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr man 7 ,
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr roff 7
+.Rs
+.%A Brian W. Kernighan
+.%A Lorinda L. Cherry
+.%T System for Typesetting Mathematics
+.%J Communications of the ACM
+.%V 18
+.%P 151\(en157
+.%D March, 1975
+.Re
+.Rs
+.%A Brian W. Kernighan
+.%A Lorinda L. Cherry
+.%T Typesetting Mathematics, User's Guide
+.%D 1976
+.Re
+.Rs
+.%A Brian W. Kernighan
+.%A Lorinda L. Cherry
+.%T Typesetting Mathematics, User's Guide (Second Edition)
+.%D 1978
+.Re
+.Sh HISTORY
+The eqn utility, a preprocessor for troff, was originally written by
+Brian W. Kernighan and Lorinda L. Cherry in 1975.
+The GNU reimplementation of eqn, part of the GNU troff package, was
+released in 1989 by James Clark.
+The eqn component of
+.Xr mandoc 1
+was added in 2011.
+.Sh AUTHORS
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
diff --git a/contrib/mdocml/eqn.c b/contrib/mdocml/eqn.c
new file mode 100644
index 0000000..cda0db5
--- /dev/null
+++ b/contrib/mdocml/eqn.c
@@ -0,0 +1,948 @@
+/* $Id: eqn.c,v 1.44 2014/07/06 19:09:00 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+#define EQN_NEST_MAX 128 /* maximum nesting of defines */
+#define EQN_MSG(t, x) mandoc_msg((t), (x)->parse, (x)->eqn.ln, (x)->eqn.pos, NULL)
+
+enum eqn_rest {
+ EQN_DESCOPE,
+ EQN_ERR,
+ EQN_OK,
+ EQN_EOF
+};
+
+enum eqn_symt {
+ EQNSYM_alpha,
+ EQNSYM_beta,
+ EQNSYM_chi,
+ EQNSYM_delta,
+ EQNSYM_epsilon,
+ EQNSYM_eta,
+ EQNSYM_gamma,
+ EQNSYM_iota,
+ EQNSYM_kappa,
+ EQNSYM_lambda,
+ EQNSYM_mu,
+ EQNSYM_nu,
+ EQNSYM_omega,
+ EQNSYM_omicron,
+ EQNSYM_phi,
+ EQNSYM_pi,
+ EQNSYM_ps,
+ EQNSYM_rho,
+ EQNSYM_sigma,
+ EQNSYM_tau,
+ EQNSYM_theta,
+ EQNSYM_upsilon,
+ EQNSYM_xi,
+ EQNSYM_zeta,
+ EQNSYM_DELTA,
+ EQNSYM_GAMMA,
+ EQNSYM_LAMBDA,
+ EQNSYM_OMEGA,
+ EQNSYM_PHI,
+ EQNSYM_PI,
+ EQNSYM_PSI,
+ EQNSYM_SIGMA,
+ EQNSYM_THETA,
+ EQNSYM_UPSILON,
+ EQNSYM_XI,
+ EQNSYM_inter,
+ EQNSYM_union,
+ EQNSYM_prod,
+ EQNSYM_int,
+ EQNSYM_sum,
+ EQNSYM_grad,
+ EQNSYM_del,
+ EQNSYM_times,
+ EQNSYM_cdot,
+ EQNSYM_nothing,
+ EQNSYM_approx,
+ EQNSYM_prime,
+ EQNSYM_half,
+ EQNSYM_partial,
+ EQNSYM_inf,
+ EQNSYM_muchgreat,
+ EQNSYM_muchless,
+ EQNSYM_larrow,
+ EQNSYM_rarrow,
+ EQNSYM_pm,
+ EQNSYM_nequal,
+ EQNSYM_equiv,
+ EQNSYM_lessequal,
+ EQNSYM_moreequal,
+ EQNSYM__MAX
+};
+
+enum eqnpartt {
+ EQN_DEFINE = 0,
+ EQN_NDEFINE,
+ EQN_TDEFINE,
+ EQN_SET,
+ EQN_UNDEF,
+ EQN_GFONT,
+ EQN_GSIZE,
+ EQN_BACK,
+ EQN_FWD,
+ EQN_UP,
+ EQN_DOWN,
+ EQN__MAX
+};
+
+struct eqnstr {
+ const char *name;
+ size_t sz;
+};
+
+#define STRNEQ(p1, sz1, p2, sz2) \
+ ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1)))
+#define EQNSTREQ(x, p, sz) \
+ STRNEQ((x)->name, (x)->sz, (p), (sz))
+
+struct eqnpart {
+ struct eqnstr str;
+ int (*fp)(struct eqn_node *);
+};
+
+struct eqnsym {
+ struct eqnstr str;
+ const char *sym;
+};
+
+static enum eqn_rest eqn_box(struct eqn_node *, struct eqn_box *);
+static struct eqn_box *eqn_box_alloc(struct eqn_node *,
+ struct eqn_box *);
+static void eqn_box_free(struct eqn_box *);
+static struct eqn_def *eqn_def_find(struct eqn_node *,
+ const char *, size_t);
+static int eqn_do_gfont(struct eqn_node *);
+static int eqn_do_gsize(struct eqn_node *);
+static int eqn_do_define(struct eqn_node *);
+static int eqn_do_ign1(struct eqn_node *);
+static int eqn_do_ign2(struct eqn_node *);
+static int eqn_do_tdefine(struct eqn_node *);
+static int eqn_do_undef(struct eqn_node *);
+static enum eqn_rest eqn_eqn(struct eqn_node *, struct eqn_box *);
+static enum eqn_rest eqn_list(struct eqn_node *, struct eqn_box *);
+static enum eqn_rest eqn_matrix(struct eqn_node *, struct eqn_box *);
+static const char *eqn_nexttok(struct eqn_node *, size_t *);
+static const char *eqn_nextrawtok(struct eqn_node *, size_t *);
+static const char *eqn_next(struct eqn_node *,
+ char, size_t *, int);
+static void eqn_rewind(struct eqn_node *);
+
+static const struct eqnpart eqnparts[EQN__MAX] = {
+ { { "define", 6 }, eqn_do_define }, /* EQN_DEFINE */
+ { { "ndefine", 7 }, eqn_do_define }, /* EQN_NDEFINE */
+ { { "tdefine", 7 }, eqn_do_tdefine }, /* EQN_TDEFINE */
+ { { "set", 3 }, eqn_do_ign2 }, /* EQN_SET */
+ { { "undef", 5 }, eqn_do_undef }, /* EQN_UNDEF */
+ { { "gfont", 5 }, eqn_do_gfont }, /* EQN_GFONT */
+ { { "gsize", 5 }, eqn_do_gsize }, /* EQN_GSIZE */
+ { { "back", 4 }, eqn_do_ign1 }, /* EQN_BACK */
+ { { "fwd", 3 }, eqn_do_ign1 }, /* EQN_FWD */
+ { { "up", 2 }, eqn_do_ign1 }, /* EQN_UP */
+ { { "down", 4 }, eqn_do_ign1 }, /* EQN_DOWN */
+};
+
+static const struct eqnstr eqnmarks[EQNMARK__MAX] = {
+ { "", 0 }, /* EQNMARK_NONE */
+ { "dot", 3 }, /* EQNMARK_DOT */
+ { "dotdot", 6 }, /* EQNMARK_DOTDOT */
+ { "hat", 3 }, /* EQNMARK_HAT */
+ { "tilde", 5 }, /* EQNMARK_TILDE */
+ { "vec", 3 }, /* EQNMARK_VEC */
+ { "dyad", 4 }, /* EQNMARK_DYAD */
+ { "bar", 3 }, /* EQNMARK_BAR */
+ { "under", 5 }, /* EQNMARK_UNDER */
+};
+
+static const struct eqnstr eqnfonts[EQNFONT__MAX] = {
+ { "", 0 }, /* EQNFONT_NONE */
+ { "roman", 5 }, /* EQNFONT_ROMAN */
+ { "bold", 4 }, /* EQNFONT_BOLD */
+ { "fat", 3 }, /* EQNFONT_FAT */
+ { "italic", 6 }, /* EQNFONT_ITALIC */
+};
+
+static const struct eqnstr eqnposs[EQNPOS__MAX] = {
+ { "", 0 }, /* EQNPOS_NONE */
+ { "over", 4 }, /* EQNPOS_OVER */
+ { "sup", 3 }, /* EQNPOS_SUP */
+ { "sub", 3 }, /* EQNPOS_SUB */
+ { "to", 2 }, /* EQNPOS_TO */
+ { "from", 4 }, /* EQNPOS_FROM */
+};
+
+static const struct eqnstr eqnpiles[EQNPILE__MAX] = {
+ { "", 0 }, /* EQNPILE_NONE */
+ { "pile", 4 }, /* EQNPILE_PILE */
+ { "cpile", 5 }, /* EQNPILE_CPILE */
+ { "rpile", 5 }, /* EQNPILE_RPILE */
+ { "lpile", 5 }, /* EQNPILE_LPILE */
+ { "col", 3 }, /* EQNPILE_COL */
+ { "ccol", 4 }, /* EQNPILE_CCOL */
+ { "rcol", 4 }, /* EQNPILE_RCOL */
+ { "lcol", 4 }, /* EQNPILE_LCOL */
+};
+
+static const struct eqnsym eqnsyms[EQNSYM__MAX] = {
+ { { "alpha", 5 }, "*a" }, /* EQNSYM_alpha */
+ { { "beta", 4 }, "*b" }, /* EQNSYM_beta */
+ { { "chi", 3 }, "*x" }, /* EQNSYM_chi */
+ { { "delta", 5 }, "*d" }, /* EQNSYM_delta */
+ { { "epsilon", 7 }, "*e" }, /* EQNSYM_epsilon */
+ { { "eta", 3 }, "*y" }, /* EQNSYM_eta */
+ { { "gamma", 5 }, "*g" }, /* EQNSYM_gamma */
+ { { "iota", 4 }, "*i" }, /* EQNSYM_iota */
+ { { "kappa", 5 }, "*k" }, /* EQNSYM_kappa */
+ { { "lambda", 6 }, "*l" }, /* EQNSYM_lambda */
+ { { "mu", 2 }, "*m" }, /* EQNSYM_mu */
+ { { "nu", 2 }, "*n" }, /* EQNSYM_nu */
+ { { "omega", 5 }, "*w" }, /* EQNSYM_omega */
+ { { "omicron", 7 }, "*o" }, /* EQNSYM_omicron */
+ { { "phi", 3 }, "*f" }, /* EQNSYM_phi */
+ { { "pi", 2 }, "*p" }, /* EQNSYM_pi */
+ { { "psi", 2 }, "*q" }, /* EQNSYM_psi */
+ { { "rho", 3 }, "*r" }, /* EQNSYM_rho */
+ { { "sigma", 5 }, "*s" }, /* EQNSYM_sigma */
+ { { "tau", 3 }, "*t" }, /* EQNSYM_tau */
+ { { "theta", 5 }, "*h" }, /* EQNSYM_theta */
+ { { "upsilon", 7 }, "*u" }, /* EQNSYM_upsilon */
+ { { "xi", 2 }, "*c" }, /* EQNSYM_xi */
+ { { "zeta", 4 }, "*z" }, /* EQNSYM_zeta */
+ { { "DELTA", 5 }, "*D" }, /* EQNSYM_DELTA */
+ { { "GAMMA", 5 }, "*G" }, /* EQNSYM_GAMMA */
+ { { "LAMBDA", 6 }, "*L" }, /* EQNSYM_LAMBDA */
+ { { "OMEGA", 5 }, "*W" }, /* EQNSYM_OMEGA */
+ { { "PHI", 3 }, "*F" }, /* EQNSYM_PHI */
+ { { "PI", 2 }, "*P" }, /* EQNSYM_PI */
+ { { "PSI", 3 }, "*Q" }, /* EQNSYM_PSI */
+ { { "SIGMA", 5 }, "*S" }, /* EQNSYM_SIGMA */
+ { { "THETA", 5 }, "*H" }, /* EQNSYM_THETA */
+ { { "UPSILON", 7 }, "*U" }, /* EQNSYM_UPSILON */
+ { { "XI", 2 }, "*C" }, /* EQNSYM_XI */
+ { { "inter", 5 }, "ca" }, /* EQNSYM_inter */
+ { { "union", 5 }, "cu" }, /* EQNSYM_union */
+ { { "prod", 4 }, "product" }, /* EQNSYM_prod */
+ { { "int", 3 }, "integral" }, /* EQNSYM_int */
+ { { "sum", 3 }, "sum" }, /* EQNSYM_sum */
+ { { "grad", 4 }, "gr" }, /* EQNSYM_grad */
+ { { "del", 3 }, "gr" }, /* EQNSYM_del */
+ { { "times", 5 }, "mu" }, /* EQNSYM_times */
+ { { "cdot", 4 }, "pc" }, /* EQNSYM_cdot */
+ { { "nothing", 7 }, "&" }, /* EQNSYM_nothing */
+ { { "approx", 6 }, "~~" }, /* EQNSYM_approx */
+ { { "prime", 5 }, "aq" }, /* EQNSYM_prime */
+ { { "half", 4 }, "12" }, /* EQNSYM_half */
+ { { "partial", 7 }, "pd" }, /* EQNSYM_partial */
+ { { "inf", 3 }, "if" }, /* EQNSYM_inf */
+ { { ">>", 2 }, ">>" }, /* EQNSYM_muchgreat */
+ { { "<<", 2 }, "<<" }, /* EQNSYM_muchless */
+ { { "<-", 2 }, "<-" }, /* EQNSYM_larrow */
+ { { "->", 2 }, "->" }, /* EQNSYM_rarrow */
+ { { "+-", 2 }, "+-" }, /* EQNSYM_pm */
+ { { "!=", 2 }, "!=" }, /* EQNSYM_nequal */
+ { { "==", 2 }, "==" }, /* EQNSYM_equiv */
+ { { "<=", 2 }, "<=" }, /* EQNSYM_lessequal */
+ { { ">=", 2 }, ">=" }, /* EQNSYM_moreequal */
+};
+
+
+enum rofferr
+eqn_read(struct eqn_node **epp, int ln,
+ const char *p, int pos, int *offs)
+{
+ size_t sz;
+ struct eqn_node *ep;
+ enum rofferr er;
+
+ ep = *epp;
+
+ /*
+ * If we're the terminating mark, unset our equation status and
+ * validate the full equation.
+ */
+
+ if (0 == strncmp(p, ".EN", 3)) {
+ er = eqn_end(epp);
+ p += 3;
+ while (' ' == *p || '\t' == *p)
+ p++;
+ if ('\0' == *p)
+ return(er);
+ mandoc_vmsg(MANDOCERR_ARG_SKIP, ep->parse,
+ ln, pos, "EN %s", p);
+ return(er);
+ }
+
+ /*
+ * Build up the full string, replacing all newlines with regular
+ * whitespace.
+ */
+
+ sz = strlen(p + pos) + 1;
+ ep->data = mandoc_realloc(ep->data, ep->sz + sz + 1);
+
+ /* First invocation: nil terminate the string. */
+
+ if (0 == ep->sz)
+ *ep->data = '\0';
+
+ ep->sz += sz;
+ strlcat(ep->data, p + pos, ep->sz + 1);
+ strlcat(ep->data, " ", ep->sz + 1);
+ return(ROFF_IGN);
+}
+
+struct eqn_node *
+eqn_alloc(const char *name, int pos, int line, struct mparse *parse)
+{
+ struct eqn_node *p;
+ size_t sz;
+ const char *end;
+
+ p = mandoc_calloc(1, sizeof(struct eqn_node));
+
+ if (name && '\0' != *name) {
+ sz = strlen(name);
+ assert(sz);
+ do {
+ sz--;
+ end = name + (int)sz;
+ } while (' ' == *end || '\t' == *end);
+ p->eqn.name = mandoc_strndup(name, sz + 1);
+ }
+
+ p->parse = parse;
+ p->eqn.ln = line;
+ p->eqn.pos = pos;
+ p->gsize = EQN_DEFSIZE;
+
+ return(p);
+}
+
+enum rofferr
+eqn_end(struct eqn_node **epp)
+{
+ struct eqn_node *ep;
+ struct eqn_box *root;
+ enum eqn_rest c;
+
+ ep = *epp;
+ *epp = NULL;
+
+ ep->eqn.root = mandoc_calloc(1, sizeof(struct eqn_box));
+
+ root = ep->eqn.root;
+ root->type = EQN_ROOT;
+
+ if (0 == ep->sz)
+ return(ROFF_IGN);
+
+ if (EQN_DESCOPE == (c = eqn_eqn(ep, root))) {
+ EQN_MSG(MANDOCERR_EQNNSCOPE, ep);
+ c = EQN_ERR;
+ }
+
+ return(EQN_EOF == c ? ROFF_EQN : ROFF_IGN);
+}
+
+static enum eqn_rest
+eqn_eqn(struct eqn_node *ep, struct eqn_box *last)
+{
+ struct eqn_box *bp;
+ enum eqn_rest c;
+
+ bp = eqn_box_alloc(ep, last);
+ bp->type = EQN_SUBEXPR;
+
+ while (EQN_OK == (c = eqn_box(ep, bp)))
+ /* Spin! */ ;
+
+ return(c);
+}
+
+static enum eqn_rest
+eqn_matrix(struct eqn_node *ep, struct eqn_box *last)
+{
+ struct eqn_box *bp;
+ const char *start;
+ size_t sz;
+ enum eqn_rest c;
+
+ bp = eqn_box_alloc(ep, last);
+ bp->type = EQN_MATRIX;
+
+ if (NULL == (start = eqn_nexttok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+ if ( ! STRNEQ(start, sz, "{", 1)) {
+ EQN_MSG(MANDOCERR_EQNSYNT, ep);
+ return(EQN_ERR);
+ }
+
+ while (EQN_OK == (c = eqn_box(ep, bp)))
+ switch (bp->last->pile) {
+ case EQNPILE_LCOL:
+ /* FALLTHROUGH */
+ case EQNPILE_CCOL:
+ /* FALLTHROUGH */
+ case EQNPILE_RCOL:
+ continue;
+ default:
+ EQN_MSG(MANDOCERR_EQNSYNT, ep);
+ return(EQN_ERR);
+ };
+
+ if (EQN_DESCOPE != c) {
+ if (EQN_EOF == c)
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+
+ eqn_rewind(ep);
+ start = eqn_nexttok(ep, &sz);
+ assert(start);
+ if (STRNEQ(start, sz, "}", 1))
+ return(EQN_OK);
+
+ EQN_MSG(MANDOCERR_EQNBADSCOPE, ep);
+ return(EQN_ERR);
+}
+
+static enum eqn_rest
+eqn_list(struct eqn_node *ep, struct eqn_box *last)
+{
+ struct eqn_box *bp;
+ const char *start;
+ size_t sz;
+ enum eqn_rest c;
+
+ bp = eqn_box_alloc(ep, last);
+ bp->type = EQN_LIST;
+
+ if (NULL == (start = eqn_nexttok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+ if ( ! STRNEQ(start, sz, "{", 1)) {
+ EQN_MSG(MANDOCERR_EQNSYNT, ep);
+ return(EQN_ERR);
+ }
+
+ while (EQN_DESCOPE == (c = eqn_eqn(ep, bp))) {
+ eqn_rewind(ep);
+ start = eqn_nexttok(ep, &sz);
+ assert(start);
+ if ( ! STRNEQ(start, sz, "above", 5))
+ break;
+ }
+
+ if (EQN_DESCOPE != c) {
+ if (EQN_ERR != c)
+ EQN_MSG(MANDOCERR_EQNSCOPE, ep);
+ return(EQN_ERR);
+ }
+
+ eqn_rewind(ep);
+ start = eqn_nexttok(ep, &sz);
+ assert(start);
+ if (STRNEQ(start, sz, "}", 1))
+ return(EQN_OK);
+
+ EQN_MSG(MANDOCERR_EQNBADSCOPE, ep);
+ return(EQN_ERR);
+}
+
+static enum eqn_rest
+eqn_box(struct eqn_node *ep, struct eqn_box *last)
+{
+ size_t sz;
+ const char *start;
+ char *left;
+ char sym[64];
+ enum eqn_rest c;
+ int i, size;
+ struct eqn_box *bp;
+
+ if (NULL == (start = eqn_nexttok(ep, &sz)))
+ return(EQN_EOF);
+
+ if (STRNEQ(start, sz, "}", 1))
+ return(EQN_DESCOPE);
+ else if (STRNEQ(start, sz, "right", 5))
+ return(EQN_DESCOPE);
+ else if (STRNEQ(start, sz, "above", 5))
+ return(EQN_DESCOPE);
+ else if (STRNEQ(start, sz, "mark", 4))
+ return(EQN_OK);
+ else if (STRNEQ(start, sz, "lineup", 6))
+ return(EQN_OK);
+
+ for (i = 0; i < (int)EQN__MAX; i++) {
+ if ( ! EQNSTREQ(&eqnparts[i].str, start, sz))
+ continue;
+ return((*eqnparts[i].fp)(ep) ? EQN_OK : EQN_ERR);
+ }
+
+ if (STRNEQ(start, sz, "{", 1)) {
+ if (EQN_DESCOPE != (c = eqn_eqn(ep, last))) {
+ if (EQN_ERR != c)
+ EQN_MSG(MANDOCERR_EQNSCOPE, ep);
+ return(EQN_ERR);
+ }
+ eqn_rewind(ep);
+ start = eqn_nexttok(ep, &sz);
+ assert(start);
+ if (STRNEQ(start, sz, "}", 1))
+ return(EQN_OK);
+ EQN_MSG(MANDOCERR_EQNBADSCOPE, ep);
+ return(EQN_ERR);
+ }
+
+ for (i = 0; i < (int)EQNPILE__MAX; i++) {
+ if ( ! EQNSTREQ(&eqnpiles[i], start, sz))
+ continue;
+ if (EQN_OK == (c = eqn_list(ep, last)))
+ last->last->pile = (enum eqn_pilet)i;
+ return(c);
+ }
+
+ if (STRNEQ(start, sz, "matrix", 6))
+ return(eqn_matrix(ep, last));
+
+ if (STRNEQ(start, sz, "left", 4)) {
+ if (NULL == (start = eqn_nexttok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+ left = mandoc_strndup(start, sz);
+ c = eqn_eqn(ep, last);
+ if (last->last)
+ last->last->left = left;
+ else
+ free(left);
+ if (EQN_DESCOPE != c)
+ return(c);
+ assert(last->last);
+ eqn_rewind(ep);
+ start = eqn_nexttok(ep, &sz);
+ assert(start);
+ if ( ! STRNEQ(start, sz, "right", 5))
+ return(EQN_DESCOPE);
+ if (NULL == (start = eqn_nexttok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+ last->last->right = mandoc_strndup(start, sz);
+ return(EQN_OK);
+ }
+
+ for (i = 0; i < (int)EQNPOS__MAX; i++) {
+ if ( ! EQNSTREQ(&eqnposs[i], start, sz))
+ continue;
+ if (NULL == last->last) {
+ EQN_MSG(MANDOCERR_EQNSYNT, ep);
+ return(EQN_ERR);
+ }
+ last->last->pos = (enum eqn_post)i;
+ if (EQN_EOF == (c = eqn_box(ep, last))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+ return(c);
+ }
+
+ for (i = 0; i < (int)EQNMARK__MAX; i++) {
+ if ( ! EQNSTREQ(&eqnmarks[i], start, sz))
+ continue;
+ if (NULL == last->last) {
+ EQN_MSG(MANDOCERR_EQNSYNT, ep);
+ return(EQN_ERR);
+ }
+ last->last->mark = (enum eqn_markt)i;
+ if (EQN_EOF == (c = eqn_box(ep, last))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+ return(c);
+ }
+
+ for (i = 0; i < (int)EQNFONT__MAX; i++) {
+ if ( ! EQNSTREQ(&eqnfonts[i], start, sz))
+ continue;
+ if (EQN_EOF == (c = eqn_box(ep, last))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ } else if (EQN_OK == c)
+ last->last->font = (enum eqn_fontt)i;
+ return(c);
+ }
+
+ if (STRNEQ(start, sz, "size", 4)) {
+ if (NULL == (start = eqn_nexttok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ }
+ size = mandoc_strntoi(start, sz, 10);
+ if (EQN_EOF == (c = eqn_box(ep, last))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(EQN_ERR);
+ } else if (EQN_OK != c)
+ return(c);
+ last->last->size = size;
+ }
+
+ bp = eqn_box_alloc(ep, last);
+ bp->type = EQN_TEXT;
+ for (i = 0; i < (int)EQNSYM__MAX; i++)
+ if (EQNSTREQ(&eqnsyms[i].str, start, sz)) {
+ sym[63] = '\0';
+ (void)snprintf(sym, 62, "\\[%s]", eqnsyms[i].sym);
+ bp->text = mandoc_strdup(sym);
+ return(EQN_OK);
+ }
+
+ bp->text = mandoc_strndup(start, sz);
+ return(EQN_OK);
+}
+
+void
+eqn_free(struct eqn_node *p)
+{
+ int i;
+
+ eqn_box_free(p->eqn.root);
+
+ for (i = 0; i < (int)p->defsz; i++) {
+ free(p->defs[i].key);
+ free(p->defs[i].val);
+ }
+
+ free(p->eqn.name);
+ free(p->data);
+ free(p->defs);
+ free(p);
+}
+
+static struct eqn_box *
+eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent)
+{
+ struct eqn_box *bp;
+
+ bp = mandoc_calloc(1, sizeof(struct eqn_box));
+ bp->parent = parent;
+ bp->size = ep->gsize;
+
+ if (NULL == parent->first)
+ parent->first = bp;
+ else
+ parent->last->next = bp;
+
+ parent->last = bp;
+ return(bp);
+}
+
+static void
+eqn_box_free(struct eqn_box *bp)
+{
+
+ if (bp->first)
+ eqn_box_free(bp->first);
+ if (bp->next)
+ eqn_box_free(bp->next);
+
+ free(bp->text);
+ free(bp->left);
+ free(bp->right);
+ free(bp);
+}
+
+static const char *
+eqn_nextrawtok(struct eqn_node *ep, size_t *sz)
+{
+
+ return(eqn_next(ep, '"', sz, 0));
+}
+
+static const char *
+eqn_nexttok(struct eqn_node *ep, size_t *sz)
+{
+
+ return(eqn_next(ep, '"', sz, 1));
+}
+
+static void
+eqn_rewind(struct eqn_node *ep)
+{
+
+ ep->cur = ep->rew;
+}
+
+static const char *
+eqn_next(struct eqn_node *ep, char quote, size_t *sz, int repl)
+{
+ char *start, *next;
+ int q, diff, lim;
+ size_t ssz, dummy;
+ struct eqn_def *def;
+
+ if (NULL == sz)
+ sz = &dummy;
+
+ lim = 0;
+ ep->rew = ep->cur;
+again:
+ /* Prevent self-definitions. */
+
+ if (lim >= EQN_NEST_MAX) {
+ EQN_MSG(MANDOCERR_ROFFLOOP, ep);
+ return(NULL);
+ }
+
+ ep->cur = ep->rew;
+ start = &ep->data[(int)ep->cur];
+ q = 0;
+
+ if ('\0' == *start)
+ return(NULL);
+
+ if (quote == *start) {
+ ep->cur++;
+ q = 1;
+ }
+
+ start = &ep->data[(int)ep->cur];
+
+ if ( ! q) {
+ if ('{' == *start || '}' == *start)
+ ssz = 1;
+ else
+ ssz = strcspn(start + 1, " ^~\"{}\t") + 1;
+ next = start + (int)ssz;
+ if ('\0' == *next)
+ next = NULL;
+ } else
+ next = strchr(start, quote);
+
+ if (NULL != next) {
+ *sz = (size_t)(next - start);
+ ep->cur += *sz;
+ if (q)
+ ep->cur++;
+ while (' ' == ep->data[(int)ep->cur] ||
+ '\t' == ep->data[(int)ep->cur] ||
+ '^' == ep->data[(int)ep->cur] ||
+ '~' == ep->data[(int)ep->cur])
+ ep->cur++;
+ } else {
+ if (q)
+ EQN_MSG(MANDOCERR_ARG_QUOTE, ep);
+ next = strchr(start, '\0');
+ *sz = (size_t)(next - start);
+ ep->cur += *sz;
+ }
+
+ /* Quotes aren't expanded for values. */
+
+ if (q || ! repl)
+ return(start);
+
+ if (NULL != (def = eqn_def_find(ep, start, *sz))) {
+ diff = def->valsz - *sz;
+
+ if (def->valsz > *sz) {
+ ep->sz += diff;
+ ep->data = mandoc_realloc(ep->data, ep->sz + 1);
+ ep->data[ep->sz] = '\0';
+ start = &ep->data[(int)ep->rew];
+ }
+
+ diff = def->valsz - *sz;
+ memmove(start + *sz + diff, start + *sz,
+ (strlen(start) - *sz) + 1);
+ memcpy(start, def->val, def->valsz);
+ goto again;
+ }
+
+ return(start);
+}
+
+static int
+eqn_do_ign1(struct eqn_node *ep)
+{
+
+ if (NULL == eqn_nextrawtok(ep, NULL))
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ else
+ return(1);
+
+ return(0);
+}
+
+static int
+eqn_do_ign2(struct eqn_node *ep)
+{
+
+ if (NULL == eqn_nextrawtok(ep, NULL))
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ else if (NULL == eqn_nextrawtok(ep, NULL))
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ else
+ return(1);
+
+ return(0);
+}
+
+static int
+eqn_do_tdefine(struct eqn_node *ep)
+{
+
+ if (NULL == eqn_nextrawtok(ep, NULL))
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ else if (NULL == eqn_next(ep, ep->data[(int)ep->cur], NULL, 0))
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ else
+ return(1);
+
+ return(0);
+}
+
+static int
+eqn_do_define(struct eqn_node *ep)
+{
+ const char *start;
+ size_t sz;
+ struct eqn_def *def;
+ int i;
+
+ if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(0);
+ }
+
+ /*
+ * Search for a key that already exists.
+ * Create a new key if none is found.
+ */
+
+ if (NULL == (def = eqn_def_find(ep, start, sz))) {
+ /* Find holes in string array. */
+ for (i = 0; i < (int)ep->defsz; i++)
+ if (0 == ep->defs[i].keysz)
+ break;
+
+ if (i == (int)ep->defsz) {
+ ep->defsz++;
+ ep->defs = mandoc_reallocarray(ep->defs,
+ ep->defsz, sizeof(struct eqn_def));
+ ep->defs[i].key = ep->defs[i].val = NULL;
+ }
+
+ ep->defs[i].keysz = sz;
+ ep->defs[i].key = mandoc_realloc(
+ ep->defs[i].key, sz + 1);
+
+ memcpy(ep->defs[i].key, start, sz);
+ ep->defs[i].key[(int)sz] = '\0';
+ def = &ep->defs[i];
+ }
+
+ start = eqn_next(ep, ep->data[(int)ep->cur], &sz, 0);
+
+ if (NULL == start) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(0);
+ }
+
+ def->valsz = sz;
+ def->val = mandoc_realloc(def->val, sz + 1);
+ memcpy(def->val, start, sz);
+ def->val[(int)sz] = '\0';
+ return(1);
+}
+
+static int
+eqn_do_gfont(struct eqn_node *ep)
+{
+
+ if (NULL == eqn_nextrawtok(ep, NULL)) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(0);
+ }
+ return(1);
+}
+
+static int
+eqn_do_gsize(struct eqn_node *ep)
+{
+ const char *start;
+ size_t sz;
+
+ if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(0);
+ }
+ ep->gsize = mandoc_strntoi(start, sz, 10);
+ return(1);
+}
+
+static int
+eqn_do_undef(struct eqn_node *ep)
+{
+ const char *start;
+ struct eqn_def *def;
+ size_t sz;
+
+ if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
+ EQN_MSG(MANDOCERR_EQNEOF, ep);
+ return(0);
+ } else if (NULL != (def = eqn_def_find(ep, start, sz)))
+ def->keysz = 0;
+
+ return(1);
+}
+
+static struct eqn_def *
+eqn_def_find(struct eqn_node *ep, const char *key, size_t sz)
+{
+ int i;
+
+ for (i = 0; i < (int)ep->defsz; i++)
+ if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key,
+ ep->defs[i].keysz, key, sz))
+ return(&ep->defs[i]);
+
+ return(NULL);
+}
diff --git a/contrib/mdocml/eqn_html.c b/contrib/mdocml/eqn_html.c
new file mode 100644
index 0000000..3e58ab5
--- /dev/null
+++ b/contrib/mdocml/eqn_html.c
@@ -0,0 +1,81 @@
+/* $Id: eqn_html.c,v 1.3 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "html.h"
+
+static const enum htmltag fontmap[EQNFONT__MAX] = {
+ TAG_SPAN, /* EQNFONT_NONE */
+ TAG_SPAN, /* EQNFONT_ROMAN */
+ TAG_B, /* EQNFONT_BOLD */
+ TAG_B, /* EQNFONT_FAT */
+ TAG_I /* EQNFONT_ITALIC */
+};
+
+static void eqn_box(struct html *, const struct eqn_box *);
+
+
+void
+print_eqn(struct html *p, const struct eqn *ep)
+{
+ struct htmlpair tag;
+ struct tag *t;
+
+ PAIR_CLASS_INIT(&tag, "eqn");
+ t = print_otag(p, TAG_SPAN, 1, &tag);
+
+ p->flags |= HTML_NONOSPACE;
+ eqn_box(p, ep->root);
+ p->flags &= ~HTML_NONOSPACE;
+
+ print_tagq(p, t);
+}
+
+static void
+eqn_box(struct html *p, const struct eqn_box *bp)
+{
+ struct tag *t;
+
+ t = EQNFONT_NONE == bp->font ? NULL :
+ print_otag(p, fontmap[(int)bp->font], 0, NULL);
+
+ if (bp->left)
+ print_text(p, bp->left);
+
+ if (bp->text)
+ print_text(p, bp->text);
+
+ if (bp->first)
+ eqn_box(p, bp->first);
+
+ if (NULL != t)
+ print_tagq(p, t);
+ if (bp->right)
+ print_text(p, bp->right);
+
+ if (bp->next)
+ eqn_box(p, bp->next);
+}
diff --git a/contrib/mdocml/eqn_term.c b/contrib/mdocml/eqn_term.c
new file mode 100644
index 0000000..889c5c6
--- /dev/null
+++ b/contrib/mdocml/eqn_term.c
@@ -0,0 +1,77 @@
+/* $Id: eqn_term.c,v 1.5 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "term.h"
+
+static const enum termfont fontmap[EQNFONT__MAX] = {
+ TERMFONT_NONE, /* EQNFONT_NONE */
+ TERMFONT_NONE, /* EQNFONT_ROMAN */
+ TERMFONT_BOLD, /* EQNFONT_BOLD */
+ TERMFONT_BOLD, /* EQNFONT_FAT */
+ TERMFONT_UNDER /* EQNFONT_ITALIC */
+};
+
+static void eqn_box(struct termp *, const struct eqn_box *);
+
+
+void
+term_eqn(struct termp *p, const struct eqn *ep)
+{
+
+ p->flags |= TERMP_NONOSPACE;
+ eqn_box(p, ep->root);
+ term_word(p, " ");
+ p->flags &= ~TERMP_NONOSPACE;
+}
+
+static void
+eqn_box(struct termp *p, const struct eqn_box *bp)
+{
+
+ if (EQNFONT_NONE != bp->font)
+ term_fontpush(p, fontmap[(int)bp->font]);
+ if (bp->left)
+ term_word(p, bp->left);
+ if (EQN_SUBEXPR == bp->type)
+ term_word(p, "(");
+
+ if (bp->text)
+ term_word(p, bp->text);
+
+ if (bp->first)
+ eqn_box(p, bp->first);
+
+ if (EQN_SUBEXPR == bp->type)
+ term_word(p, ")");
+ if (bp->right)
+ term_word(p, bp->right);
+ if (EQNFONT_NONE != bp->font)
+ term_fontpop(p);
+
+ if (bp->next)
+ eqn_box(p, bp->next);
+}
diff --git a/contrib/mdocml/example.style.css b/contrib/mdocml/example.style.css
new file mode 100644
index 0000000..660f4d1
--- /dev/null
+++ b/contrib/mdocml/example.style.css
@@ -0,0 +1,110 @@
+/* $Id: example.style.css,v 1.49 2011/12/15 12:18:57 kristaps Exp $ */
+/*
+ * This is an example style-sheet provided for mandoc(1) and the -Thtml
+ * or -Txhtml output mode.
+ * It mimics the appearance of the legacy man.cgi output.
+ * See mdoc(7) and man(7) for macro explanations.
+ */
+
+div.mandoc { min-width: 102ex;
+ width: 102ex;
+ font-family: monospace; } /* This is the outer node of all mandoc -T[x]html documents. */
+div.mandoc h1 { margin-bottom: 0ex; font-size: inherit; margin-left: -4ex; } /* Section header (Sh, SH). */
+div.mandoc h2 { margin-bottom: 0ex; font-size: inherit; margin-left: -2ex; } /* Sub-section header (Ss, SS). */
+div.mandoc table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; } /* All tables. */
+div.mandoc td { vertical-align: top; } /* All table cells. */
+div.mandoc p { } /* Paragraph: Pp, Lp. */
+div.mandoc blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; } /* D1, Dl. */
+div.mandoc div.section { margin-bottom: 2ex; margin-left: 5ex; } /* Sections (Sh, SH). */
+div.mandoc div.subsection { } /* Sub-sections (Ss, SS). */
+div.mandoc table.synopsis { } /* SYNOPSIS section table. */
+div.mandoc table.foot { } /* Document footer. */
+div.mandoc td.foot-date { width: 50%; } /* Document footer: date. */
+div.mandoc td.foot-os { width: 50%; text-align: right; } /* Document footer: OS/source. */
+div.mandoc table.head { } /* Document header. */
+div.mandoc td.head-ltitle { width: 10%; } /* Document header: left-title. */
+div.mandoc td.head-vol { width: 80%; text-align: center; } /* Document header: volume. */
+div.mandoc td.head-rtitle { width: 10%; text-align: right; } /* Document header: right-title. */
+div.mandoc .display { } /* All Bd, D1, Dl. */
+div.mandoc .list { } /* All Bl. */
+div.mandoc i { } /* Italic: BI, IB, I, (implicit). */
+div.mandoc b { } /* Bold: SB, BI, IB, BR, RB, B, (implicit). */
+div.mandoc small { } /* Small: SB, SM. */
+div.mandoc .emph { font-style: italic; font-weight: normal; } /* Emphasis: Em, Bl -emphasis. */
+div.mandoc .symb { font-style: normal; font-weight: bold; } /* Symbolic: Sy, Ms, Bf -symbolic. */
+div.mandoc .lit { font-style: normal; font-weight: normal; font-family: monospace; } /* Literal: Dl, Li, Ql, Bf -literal, Bl -literal, Bl -unfilled. */
+div.mandoc i.addr { font-weight: normal; } /* Address (Ad). */
+div.mandoc i.arg { font-weight: normal; } /* Command argument (Ar). */
+div.mandoc span.author { } /* Author name (An). */
+div.mandoc b.cmd { font-style: normal; } /* Command (Cm). */
+div.mandoc b.config { font-style: normal; } /* Config statement (Cd). */
+div.mandoc span.define { } /* Defines (Dv). */
+div.mandoc span.desc { } /* Nd. After em-dash. */
+div.mandoc b.diag { font-style: normal; } /* Diagnostic (Bl -diag). */
+div.mandoc span.env { } /* Environment variables (Ev). */
+div.mandoc span.errno { } /* Error string (Er). */
+div.mandoc i.farg { font-weight: normal; } /* Function argument (Fa, Fn). */
+div.mandoc i.file { font-weight: normal; } /* File (Pa). */
+div.mandoc b.flag { font-style: normal; } /* Flag (Fl, Cm). */
+div.mandoc b.fname { font-style: normal; } /* Function name (Fa, Fn, Rv). */
+div.mandoc i.ftype { font-weight: normal; } /* Function types (Ft, Fn). */
+div.mandoc b.includes { font-style: normal; } /* Header includes (In). */
+div.mandoc span.lib { } /* Library (Lb). */
+div.mandoc i.link-sec { font-weight: normal; } /* Section links (Sx). */
+div.mandoc b.macro { font-style: normal; } /* Macro-ish thing (Fd). */
+div.mandoc b.name { font-style: normal; } /* Name of utility (Nm). */
+div.mandoc span.opt { } /* Options (Op, Oo/Oc). */
+div.mandoc span.ref { } /* Citations (Rs). */
+div.mandoc span.ref-auth { } /* Reference author (%A). */
+div.mandoc i.ref-book { font-weight: normal; } /* Reference book (%B). */
+div.mandoc span.ref-city { } /* Reference city (%C). */
+div.mandoc span.ref-date { } /* Reference date (%D). */
+div.mandoc i.ref-issue { font-weight: normal; } /* Reference issuer/publisher (%I). */
+div.mandoc i.ref-jrnl { font-weight: normal; } /* Reference journal (%J). */
+div.mandoc span.ref-num { } /* Reference number (%N). */
+div.mandoc span.ref-opt { } /* Reference optionals (%O). */
+div.mandoc span.ref-page { } /* Reference page (%P). */
+div.mandoc span.ref-corp { } /* Reference corporate/foreign author (%Q). */
+div.mandoc span.ref-rep { } /* Reference report (%R). */
+div.mandoc span.ref-title { text-decoration: underline; } /* Reference title (%T). */
+div.mandoc span.ref-vol { } /* Reference volume (%V). */
+div.mandoc span.type { font-style: italic; font-weight: normal; } /* Variable types (Vt). */
+div.mandoc span.unix { } /* Unices (Ux, Ox, Nx, Fx, Bx, Bsx, Dx). */
+div.mandoc b.utility { font-style: normal; } /* Name of utility (Ex). */
+div.mandoc b.var { font-style: normal; } /* Variables (Rv). */
+div.mandoc a.link-ext { } /* Off-site link (Lk). */
+div.mandoc a.link-includes { } /* Include-file link (In). */
+div.mandoc a.link-mail { } /* Mailto links (Mt). */
+div.mandoc a.link-man { } /* Manual links (Xr). */
+div.mandoc a.link-ref { } /* Reference section links (%Q). */
+div.mandoc a.link-sec { } /* Section links (Sx). */
+div.mandoc dl.list-diag { } /* Formatting for lists. See mdoc(7). */
+div.mandoc dt.list-diag { }
+div.mandoc dd.list-diag { }
+div.mandoc dl.list-hang { }
+div.mandoc dt.list-hang { }
+div.mandoc dd.list-hang { }
+div.mandoc dl.list-inset { }
+div.mandoc dt.list-inset { }
+div.mandoc dd.list-inset { }
+div.mandoc dl.list-ohang { }
+div.mandoc dt.list-ohang { }
+div.mandoc dd.list-ohang { margin-left: 0ex; }
+div.mandoc dl.list-tag { }
+div.mandoc dt.list-tag { }
+div.mandoc dd.list-tag { }
+div.mandoc table.list-col { }
+div.mandoc tr.list-col { }
+div.mandoc td.list-col { }
+div.mandoc ul.list-bul { list-style-type: disc; padding-left: 1em; }
+div.mandoc li.list-bul { }
+div.mandoc ul.list-dash { list-style-type: none; padding-left: 0em; }
+div.mandoc li.list-dash:before { content: "\2014 "; }
+div.mandoc ul.list-hyph { list-style-type: none; padding-left: 0em; }
+div.mandoc li.list-hyph:before { content: "\2013 "; }
+div.mandoc ul.list-item { list-style-type: none; padding-left: 0em; }
+div.mandoc li.list-item { }
+div.mandoc ol.list-enum { padding-left: 2em; }
+div.mandoc li.list-enum { }
+div.mandoc span.eqn { } /* Equation modes. See eqn(7). */
+div.mandoc table.tbl { } /* Table modes. See tbl(7). */
diff --git a/contrib/mdocml/gmdiff b/contrib/mdocml/gmdiff
new file mode 100644
index 0000000..2c7ba4b
--- /dev/null
+++ b/contrib/mdocml/gmdiff
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+if [ `id -u` -eq 0 ]; then
+ echo "$0: do not run me as root"
+ exit 1
+fi
+
+if [ $# -eq 0 ]; then
+ echo "usage: $0 manual_source_file ..."
+ exit 1
+fi
+
+while [ -n "$1" ]; do
+ file=$1
+ shift
+ echo " ========== $file ========== "
+ tbl $file | groff -mandoc -Tascii -P -c 2> /tmp/groff.err > /tmp/groff.out
+ mandoc -Ios='OpenBSD ports' -Werror $file 2> /tmp/mandoc.err > /tmp/mandoc.out
+ for i in groff mandoc; do
+ [[ -s /tmp/$i.err ]] && echo "$i errors:" && cat /tmp/$i.err
+ done
+ diff -au /tmp/groff.out /tmp/mandoc.out 2>&1
+done
+
+exit 0
diff --git a/contrib/mdocml/html.c b/contrib/mdocml/html.c
new file mode 100644
index 0000000..d4783ee
--- /dev/null
+++ b/contrib/mdocml/html.c
@@ -0,0 +1,776 @@
+/* $Id: html.c,v 1.159 2014/07/23 15:00:08 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+#include "out.h"
+#include "html.h"
+#include "main.h"
+
+struct htmldata {
+ const char *name;
+ int flags;
+#define HTML_CLRLINE (1 << 0)
+#define HTML_NOSTACK (1 << 1)
+#define HTML_AUTOCLOSE (1 << 2) /* Tag has auto-closure. */
+};
+
+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 | HTML_AUTOCLOSE}, /* TAG_META */
+ {"title", HTML_CLRLINE}, /* TAG_TITLE */
+ {"div", HTML_CLRLINE}, /* TAG_DIV */
+ {"h1", 0}, /* TAG_H1 */
+ {"h2", 0}, /* TAG_H2 */
+ {"span", 0}, /* TAG_SPAN */
+ {"link", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */
+ {"br", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */
+ {"a", 0}, /* TAG_A */
+ {"table", HTML_CLRLINE}, /* TAG_TABLE */
+ {"tbody", HTML_CLRLINE}, /* TAG_TBODY */
+ {"col", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* 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 */
+ {"dl", HTML_CLRLINE}, /* TAG_DL */
+ {"dt", HTML_CLRLINE}, /* TAG_DT */
+ {"dd", HTML_CLRLINE}, /* TAG_DD */
+ {"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */
+ {"p", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */
+ {"pre", HTML_CLRLINE }, /* TAG_PRE */
+ {"b", 0 }, /* TAG_B */
+ {"i", 0 }, /* TAG_I */
+ {"code", 0 }, /* TAG_CODE */
+ {"small", 0 }, /* TAG_SMALL */
+};
+
+static const char *const htmlattrs[ATTR_MAX] = {
+ "http-equiv", /* ATTR_HTTPEQUIV */
+ "content", /* ATTR_CONTENT */
+ "name", /* ATTR_NAME */
+ "rel", /* ATTR_REL */
+ "href", /* ATTR_HREF */
+ "type", /* ATTR_TYPE */
+ "media", /* ATTR_MEDIA */
+ "class", /* ATTR_CLASS */
+ "style", /* ATTR_STYLE */
+ "width", /* ATTR_WIDTH */
+ "id", /* ATTR_ID */
+ "summary", /* ATTR_SUMMARY */
+ "align", /* ATTR_ALIGN */
+ "colspan", /* ATTR_COLSPAN */
+};
+
+static const char *const roffscales[SCALE_MAX] = {
+ "cm", /* SCALE_CM */
+ "in", /* SCALE_IN */
+ "pc", /* SCALE_PC */
+ "pt", /* SCALE_PT */
+ "em", /* SCALE_EM */
+ "em", /* SCALE_MM */
+ "ex", /* SCALE_EN */
+ "ex", /* SCALE_BU */
+ "em", /* SCALE_VS */
+ "ex", /* SCALE_FS */
+};
+
+static void bufncat(struct html *, const char *, size_t);
+static void print_ctag(struct html *, enum htmltag);
+static int print_escape(char);
+static int print_encode(struct html *, const char *, int);
+static void print_metaf(struct html *, enum mandoc_esc);
+static void print_attr(struct html *, const char *, const char *);
+static void *ml_alloc(char *, enum htmltype);
+
+
+static void *
+ml_alloc(char *outopts, enum htmltype type)
+{
+ struct html *h;
+ const char *toks[5];
+ char *v;
+
+ toks[0] = "style";
+ toks[1] = "man";
+ toks[2] = "includes";
+ toks[3] = "fragment";
+ toks[4] = NULL;
+
+ h = mandoc_calloc(1, sizeof(struct html));
+
+ h->type = type;
+ h->tags.head = NULL;
+ h->symtab = mchars_alloc();
+
+ 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;
+ case 3:
+ h->oflags |= HTML_FRAGMENT;
+ break;
+ default:
+ break;
+ }
+
+ return(h);
+}
+
+void *
+html_alloc(char *outopts)
+{
+
+ return(ml_alloc(outopts, HTML_HTML_4_01_STRICT));
+}
+
+void *
+xhtml_alloc(char *outopts)
+{
+
+ return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT));
+}
+
+void
+html_free(void *p)
+{
+ struct tag *tag;
+ struct html *h;
+
+ h = (struct html *)p;
+
+ while ((tag = h->tags.head) != NULL) {
+ h->tags.head = tag->next;
+ free(tag);
+ }
+
+ if (h->symtab)
+ mchars_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_metaf(struct html *h, enum mandoc_esc deco)
+{
+ enum htmlfont font;
+
+ switch (deco) {
+ case ESCAPE_FONTPREV:
+ font = h->metal;
+ break;
+ case ESCAPE_FONTITALIC:
+ font = HTMLFONT_ITALIC;
+ break;
+ case ESCAPE_FONTBOLD:
+ font = HTMLFONT_BOLD;
+ break;
+ case ESCAPE_FONTBI:
+ font = HTMLFONT_BI;
+ break;
+ case ESCAPE_FONT:
+ /* FALLTHROUGH */
+ case ESCAPE_FONTROMAN:
+ font = HTMLFONT_NONE;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ if (h->metaf) {
+ print_tagq(h, h->metaf);
+ h->metaf = NULL;
+ }
+
+ h->metal = h->metac;
+ h->metac = font;
+
+ switch (font) {
+ case HTMLFONT_ITALIC:
+ h->metaf = print_otag(h, TAG_I, 0, NULL);
+ break;
+ case HTMLFONT_BOLD:
+ h->metaf = print_otag(h, TAG_B, 0, NULL);
+ break;
+ case HTMLFONT_BI:
+ h->metaf = print_otag(h, TAG_B, 0, NULL);
+ print_otag(h, TAG_I, 0, NULL);
+ break;
+ default:
+ break;
+ }
+}
+
+int
+html_strlen(const char *cp)
+{
+ size_t rsz;
+ int skip, sz;
+
+ /*
+ * Account for escaped sequences within string length
+ * calculations. This follows the logic in term_strlen() as we
+ * must calculate the width of produced strings.
+ * Assume that characters are always width of "1". This is
+ * hacky, but it gets the job done for approximation of widths.
+ */
+
+ sz = 0;
+ skip = 0;
+ while (1) {
+ rsz = strcspn(cp, "\\");
+ if (rsz) {
+ cp += rsz;
+ if (skip) {
+ skip = 0;
+ rsz--;
+ }
+ sz += rsz;
+ }
+ if ('\0' == *cp)
+ break;
+ cp++;
+ switch (mandoc_escape(&cp, NULL, NULL)) {
+ case ESCAPE_ERROR:
+ return(sz);
+ case ESCAPE_UNICODE:
+ /* FALLTHROUGH */
+ case ESCAPE_NUMBERED:
+ /* FALLTHROUGH */
+ case ESCAPE_SPECIAL:
+ if (skip)
+ skip = 0;
+ else
+ sz++;
+ break;
+ case ESCAPE_SKIPCHAR:
+ skip = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return(sz);
+}
+
+static int
+print_escape(char c)
+{
+
+ switch (c) {
+ case '<':
+ printf("&lt;");
+ break;
+ case '>':
+ printf("&gt;");
+ break;
+ case '&':
+ printf("&amp;");
+ break;
+ case '"':
+ printf("&quot;");
+ break;
+ case ASCII_NBRSP:
+ putchar('-');
+ break;
+ case ASCII_HYPH:
+ putchar('-');
+ /* FALLTHROUGH */
+ case ASCII_BREAK:
+ break;
+ default:
+ return(0);
+ }
+ return(1);
+}
+
+static int
+print_encode(struct html *h, const char *p, int norecurse)
+{
+ size_t sz;
+ int c, len, nospace;
+ const char *seq;
+ enum mandoc_esc esc;
+ static const char rejs[9] = { '\\', '<', '>', '&', '"',
+ ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
+
+ nospace = 0;
+
+ while ('\0' != *p) {
+ if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
+ h->flags &= ~HTML_SKIPCHAR;
+ p++;
+ continue;
+ }
+
+ sz = strcspn(p, rejs);
+
+ fwrite(p, 1, sz, stdout);
+ p += (int)sz;
+
+ if ('\0' == *p)
+ break;
+
+ if (print_escape(*p++))
+ continue;
+
+ esc = mandoc_escape(&p, &seq, &len);
+ if (ESCAPE_ERROR == esc)
+ break;
+
+ switch (esc) {
+ case ESCAPE_FONT:
+ /* FALLTHROUGH */
+ case ESCAPE_FONTPREV:
+ /* FALLTHROUGH */
+ case ESCAPE_FONTBOLD:
+ /* FALLTHROUGH */
+ case ESCAPE_FONTITALIC:
+ /* FALLTHROUGH */
+ case ESCAPE_FONTBI:
+ /* FALLTHROUGH */
+ case ESCAPE_FONTROMAN:
+ if (0 == norecurse)
+ print_metaf(h, esc);
+ continue;
+ case ESCAPE_SKIPCHAR:
+ h->flags |= HTML_SKIPCHAR;
+ continue;
+ default:
+ break;
+ }
+
+ if (h->flags & HTML_SKIPCHAR) {
+ h->flags &= ~HTML_SKIPCHAR;
+ continue;
+ }
+
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ /* Skip past "u" header. */
+ c = mchars_num2uc(seq + 1, len - 1);
+ if ('\0' != c)
+ printf("&#x%x;", c);
+ break;
+ case ESCAPE_NUMBERED:
+ c = mchars_num2char(seq, len);
+ if ( ! ('\0' == c || print_escape(c)))
+ putchar(c);
+ break;
+ case ESCAPE_SPECIAL:
+ c = mchars_spec2cp(h->symtab, seq, len);
+ if (c > 0)
+ printf("&#%d;", c);
+ else if (-1 == c && 1 == len &&
+ !print_escape(*seq))
+ putchar((int)*seq);
+ break;
+ case ESCAPE_NOSPACE:
+ if ('\0' == *p)
+ nospace = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return(nospace);
+}
+
+static void
+print_attr(struct html *h, const char *key, const char *val)
+{
+ printf(" %s=\"", key);
+ (void)print_encode(h, val, 1);
+ putchar('\"');
+}
+
+struct tag *
+print_otag(struct html *h, enum htmltag tag,
+ int sz, const struct htmlpair *p)
+{
+ int i;
+ struct tag *t;
+
+ /* Push this tags onto the stack of open scopes. */
+
+ if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
+ t = mandoc_malloc(sizeof(struct tag));
+ t->tag = tag;
+ t->next = h->tags.head;
+ h->tags.head = t;
+ } else
+ t = NULL;
+
+ if ( ! (HTML_NOSPACE & h->flags))
+ if ( ! (HTML_CLRLINE & htmltags[tag].flags)) {
+ /* Manage keeps! */
+ if ( ! (HTML_KEEP & h->flags)) {
+ if (HTML_PREKEEP & h->flags)
+ h->flags |= HTML_KEEP;
+ putchar(' ');
+ } else
+ printf("&#160;");
+ }
+
+ if ( ! (h->flags & HTML_NONOSPACE))
+ h->flags &= ~HTML_NOSPACE;
+ else
+ h->flags |= HTML_NOSPACE;
+
+ /* Print out the tag name and attributes. */
+
+ printf("<%s", htmltags[tag].name);
+ for (i = 0; i < sz; i++)
+ print_attr(h, htmlattrs[p[i].key], p[i].val);
+
+ /* Add non-overridable attributes. */
+
+ if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) {
+ print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml");
+ print_attr(h, "xml:lang", "en");
+ print_attr(h, "lang", "en");
+ }
+
+ /* Accommodate for XML "well-formed" singleton escaping. */
+
+ if (HTML_AUTOCLOSE & htmltags[tag].flags)
+ switch (h->type) {
+ case HTML_XHTML_1_0_STRICT:
+ putchar('/');
+ break;
+ default:
+ break;
+ }
+
+ putchar('>');
+
+ h->flags |= HTML_NOSPACE;
+
+ if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags)
+ putchar('\n');
+
+ return(t);
+}
+
+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;
+ putchar('\n');
+ }
+}
+
+void
+print_gen_decls(struct html *h)
+{
+ const char *doctype;
+ const char *dtd;
+ const char *name;
+
+ switch (h->type) {
+ case HTML_HTML_4_01_STRICT:
+ name = "HTML";
+ doctype = "-//W3C//DTD HTML 4.01//EN";
+ dtd = "http://www.w3.org/TR/html4/strict.dtd";
+ break;
+ default:
+ puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ name = "html";
+ doctype = "-//W3C//DTD XHTML 1.0 Strict//EN";
+ dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";
+ break;
+ }
+
+ printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n",
+ name, doctype, dtd);
+}
+
+void
+print_text(struct html *h, const char *word)
+{
+
+ if ( ! (HTML_NOSPACE & h->flags)) {
+ /* Manage keeps! */
+ if ( ! (HTML_KEEP & h->flags)) {
+ if (HTML_PREKEEP & h->flags)
+ h->flags |= HTML_KEEP;
+ putchar(' ');
+ } else
+ printf("&#160;");
+ }
+
+ assert(NULL == h->metaf);
+ switch (h->metac) {
+ case HTMLFONT_ITALIC:
+ h->metaf = print_otag(h, TAG_I, 0, NULL);
+ break;
+ case HTMLFONT_BOLD:
+ h->metaf = print_otag(h, TAG_B, 0, NULL);
+ break;
+ case HTMLFONT_BI:
+ h->metaf = print_otag(h, TAG_B, 0, NULL);
+ print_otag(h, TAG_I, 0, NULL);
+ break;
+ default:
+ break;
+ }
+
+ assert(word);
+ if ( ! print_encode(h, word, 0)) {
+ if ( ! (h->flags & HTML_NONOSPACE))
+ h->flags &= ~HTML_NOSPACE;
+ } else
+ h->flags |= HTML_NOSPACE;
+
+ if (h->metaf) {
+ print_tagq(h, h->metaf);
+ h->metaf = NULL;
+ }
+
+ h->flags &= ~HTML_IGNDELIM;
+}
+
+void
+print_tagq(struct html *h, const struct tag *until)
+{
+ struct tag *tag;
+
+ while ((tag = h->tags.head) != NULL) {
+ /*
+ * Remember to close out and nullify the current
+ * meta-font and table, if applicable.
+ */
+ if (tag == h->metaf)
+ h->metaf = NULL;
+ if (tag == h->tblt)
+ h->tblt = NULL;
+ print_ctag(h, tag->tag);
+ h->tags.head = tag->next;
+ free(tag);
+ if (until && tag == until)
+ return;
+ }
+}
+
+void
+print_stagq(struct html *h, const struct tag *suntil)
+{
+ struct tag *tag;
+
+ while ((tag = h->tags.head) != NULL) {
+ if (suntil && tag == suntil)
+ return;
+ /*
+ * Remember to close out and nullify the current
+ * meta-font and table, if applicable.
+ */
+ if (tag == h->metaf)
+ h->metaf = NULL;
+ if (tag == h->tblt)
+ h->tblt = NULL;
+ print_ctag(h, tag->tag);
+ h->tags.head = tag->next;
+ 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);
+ bufcat(h, ":");
+ bufcat(h, val);
+ bufcat(h, ";");
+}
+
+void
+bufcat(struct html *h, const char *p)
+{
+
+ /*
+ * XXX This is broken and not easy to fix.
+ * When using the -Oincludes option, buffmt_includes()
+ * may pass in strings overrunning BUFSIZ, causing a crash.
+ */
+
+ h->buflen = strlcat(h->buf, p, BUFSIZ);
+ assert(h->buflen < BUFSIZ);
+}
+
+void
+bufcat_fmt(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);
+}
+
+static void
+bufncat(struct html *h, const char *p, size_t sz)
+{
+
+ assert(h->buflen + sz + 1 < BUFSIZ);
+ 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;
+
+ bufinit(h);
+ 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;
+
+ bufinit(h);
+ 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':
+ bufcat_fmt(h, "%s", 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;
+
+ v = su->scale;
+ if (SCALE_MM == su->unit && 0.0 == (v /= 100.0))
+ v = 1.0;
+
+ bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]);
+}
+
+void
+bufcat_id(struct html *h, const char *src)
+{
+
+ /* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */
+
+ while ('\0' != *src)
+ bufcat_fmt(h, "%.2x", *src++);
+}
diff --git a/contrib/mdocml/html.h b/contrib/mdocml/html.h
new file mode 100644
index 0000000..ca15f0f
--- /dev/null
+++ b/contrib/mdocml/html.h
@@ -0,0 +1,169 @@
+/* $Id: html.h,v 1.51 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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_SPAN,
+ TAG_LINK,
+ TAG_BR,
+ TAG_A,
+ TAG_TABLE,
+ TAG_TBODY,
+ TAG_COL,
+ TAG_TR,
+ TAG_TD,
+ TAG_LI,
+ TAG_UL,
+ TAG_OL,
+ TAG_DL,
+ TAG_DT,
+ TAG_DD,
+ TAG_BLOCKQUOTE,
+ TAG_P,
+ TAG_PRE,
+ TAG_B,
+ TAG_I,
+ TAG_CODE,
+ TAG_SMALL,
+ 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_ID,
+ ATTR_SUMMARY,
+ ATTR_ALIGN,
+ ATTR_COLSPAN,
+ ATTR_MAX
+};
+
+enum htmlfont {
+ HTMLFONT_NONE = 0,
+ HTMLFONT_BOLD,
+ HTMLFONT_ITALIC,
+ HTMLFONT_BI,
+ HTMLFONT_MAX
+};
+
+struct tag {
+ struct tag *next;
+ enum htmltag tag;
+};
+
+struct tagq {
+ struct tag *head;
+};
+
+struct htmlpair {
+ enum htmlattr key;
+ const char *val;
+};
+
+#define PAIR_INIT(p, t, v) \
+ do { \
+ (p)->key = (t); \
+ (p)->val = (v); \
+ } while (/* CONSTCOND */ 0)
+
+#define PAIR_ID_INIT(p, v) PAIR_INIT(p, ATTR_ID, v)
+#define PAIR_CLASS_INIT(p, v) PAIR_INIT(p, ATTR_CLASS, v)
+#define PAIR_HREF_INIT(p, v) PAIR_INIT(p, ATTR_HREF, v)
+#define PAIR_STYLE_INIT(p, h) PAIR_INIT(p, ATTR_STYLE, (h)->buf)
+#define PAIR_SUMMARY_INIT(p, v) PAIR_INIT(p, ATTR_SUMMARY, v)
+
+enum htmltype {
+ HTML_HTML_4_01_STRICT,
+ HTML_XHTML_1_0_STRICT
+};
+
+struct html {
+ int flags;
+#define HTML_NOSPACE (1 << 0) /* suppress next space */
+#define HTML_IGNDELIM (1 << 1)
+#define HTML_KEEP (1 << 2)
+#define HTML_PREKEEP (1 << 3)
+#define HTML_NONOSPACE (1 << 4) /* never add spaces */
+#define HTML_LITERAL (1 << 5) /* literal (e.g., <PRE>) context */
+#define HTML_SKIPCHAR (1 << 6) /* skip the next character */
+ struct tagq tags; /* stack of open tags */
+ struct rofftbl tbl; /* current table */
+ struct tag *tblt; /* current open table scope */
+ struct mchars *symtab; /* character-escapes */
+ char *base_man; /* base for manpage href */
+ char *base_includes; /* base for include href */
+ char *style; /* style-sheet URI */
+ char buf[BUFSIZ]; /* see bufcat and friends */
+ size_t buflen;
+ struct tag *metaf; /* current open font scope */
+ enum htmlfont metal; /* last used font */
+ enum htmlfont metac; /* current font mode */
+ enum htmltype type; /* output media type */
+ int oflags; /* output options */
+#define HTML_FRAGMENT (1 << 0) /* don't emit HTML/HEAD/BODY */
+};
+
+void print_gen_decls(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 print_tblclose(struct html *);
+void print_tbl(struct html *, const struct tbl_span *);
+void print_eqn(struct html *, const struct eqn *);
+
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 2, 3)))
+#endif
+void bufcat_fmt(struct html *, const char *, ...);
+void bufcat(struct html *, const char *);
+void bufcat_id(struct html *, const char *);
+void bufcat_style(struct html *,
+ const char *, const char *);
+void bufcat_su(struct html *, const char *,
+ const struct roffsu *);
+void bufinit(struct html *);
+void buffmt_man(struct html *,
+ const char *, const char *);
+void buffmt_includes(struct html *, const char *);
+
+int html_strlen(const char *);
+
+__END_DECLS
+
+#endif /*!HTML_H*/
diff --git a/contrib/mdocml/lib.c b/contrib/mdocml/lib.c
new file mode 100644
index 0000000..8cc8a77
--- /dev/null
+++ b/contrib/mdocml/lib.c
@@ -0,0 +1,36 @@
+/* $Id: lib.c,v 1.10 2014/03/23 11:25:26 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "mdoc.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/contrib/mdocml/lib.in b/contrib/mdocml/lib.in
new file mode 100644
index 0000000..a785c72
--- /dev/null
+++ b/contrib/mdocml/lib.in
@@ -0,0 +1,123 @@
+/* $Id: lib.in,v 1.18 2014/01/06 00:53:33 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2009, 2012 Joerg Sonnenberger <joerg@netbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * These are all possible .Lb strings. When a new library is added, add
+ * its short-string to the left-hand side and formatted string to the
+ * right-hand side.
+ *
+ * Be sure to escape strings.
+ */
+
+LINE("libarchive", "Streaming Archive Library (libarchive, \\-larchive)")
+LINE("libarm", "ARM Architecture Library (libarm, \\-larm)")
+LINE("libarm32", "ARM32 Architecture Library (libarm32, \\-larm32)")
+LINE("libbluetooth", "Bluetooth Library (libbluetooth, \\-lbluetooth)")
+LINE("libbsm", "Basic Security Module Library (libbsm, \\-lbsm)")
+LINE("libc", "Standard C\\~Library (libc, \\-lc)")
+LINE("libc_r", "Reentrant C\\~Library (libc_r, \\-lc_r)")
+LINE("libcalendar", "Calendar Arithmetic Library (libcalendar, \\-lcalendar)")
+LINE("libcam", "Common Access Method User Library (libcam, \\-lcam)")
+LINE("libcapsicum", "Capsicum Library (libcapsicum, \\-lcapsicum)")
+LINE("libcdk", "Curses Development Kit Library (libcdk, \\-lcdk)")
+LINE("libcipher", "FreeSec Crypt Library (libcipher, \\-lcipher)")
+LINE("libcompat", "Compatibility Library (libcompat, \\-lcompat)")
+LINE("libcrypt", "Crypt Library (libcrypt, \\-lcrypt)")
+LINE("libcurses", "Curses Library (libcurses, \\-lcurses)")
+LINE("libdevattr", "Device attribute and event library (libdevattr, \\-ldevattr)")
+LINE("libdevinfo", "Device and Resource Information Utility Library (libdevinfo, \\-ldevinfo)")
+LINE("libdevstat", "Device Statistics Library (libdevstat, \\-ldevstat)")
+LINE("libdisk", "Interface to Slice and Partition Labels Library (libdisk, \\-ldisk)")
+LINE("libdm", "Device Mapper Library (libdm, \\-ldm)")
+LINE("libdwarf", "DWARF Access Library (libdwarf, \\-ldwarf)")
+LINE("libedit", "Command Line Editor Library (libedit, \\-ledit)")
+LINE("libefi", "EFI Runtime Services Library (libefi, \\-lefi)")
+LINE("libelf", "ELF Access Library (libelf, \\-lelf)")
+LINE("libevent", "Event Notification Library (libevent, \\-levent)")
+LINE("libexecinfo", "Backtrace Information Library (libexecinfo, \\-lexecinfo)")
+LINE("libfetch", "File Transfer Library (libfetch, \\-lfetch)")
+LINE("libfsid", "Filesystem Identification Library (libfsid, \\-lfsid)")
+LINE("libftpio", "FTP Connection Management Library (libftpio, \\-lftpio)")
+LINE("libform", "Curses Form Library (libform, \\-lform)")
+LINE("libgeom", "Userland API Library for Kernel GEOM subsystem (libgeom, \\-lgeom)")
+LINE("libgpib", "General-Purpose Instrument Bus (GPIB) library (libgpib, \\-lgpib)")
+LINE("libhammer", "HAMMER Filesystem Userland Library (libhammer, \\-lhammer)")
+LINE("libi386", "i386 Architecture Library (libi386, \\-li386)")
+LINE("libintl", "Internationalized Message Handling Library (libintl, \\-lintl)")
+LINE("libipsec", "IPsec Policy Control Library (libipsec, \\-lipsec)")
+LINE("libiscsi", "iSCSI protocol library (libiscsi, \\-liscsi)")
+LINE("libisns", "Internet Storage Name Service Library (libisns, \\-lisns)")
+LINE("libjail", "Jail Library (libjail, \\-ljail)")
+LINE("libkcore", "Kernel Memory Core Access Library (libkcore, \\-lkcore)")
+LINE("libkiconv", "Kernel-side iconv Library (libkiconv, \\-lkiconv)")
+LINE("libkse", "N:M Threading Library (libkse, \\-lkse)")
+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("libmandoc", "Mandoc Macro Compiler Library (libmandoc, \\-lmandoc)")
+LINE("libmd", "Message Digest (MD4, MD5, etc.) Support Library (libmd, \\-lmd)")
+LINE("libmemstat", "Kernel Memory Allocator Statistics Library (libmemstat, \\-lmemstat)")
+LINE("libmenu", "Curses Menu Library (libmenu, \\-lmenu)")
+LINE("libmj", "Minimalist JSON library (libmj, \\-lmj)")
+LINE("libnetgraph", "Netgraph User Library (libnetgraph, \\-lnetgraph)")
+LINE("libnetpgp", "Netpgp Signing, Verification, Encryption and Decryption (libnetpgp, \\-lnetpgp)")
+LINE("libnetpgpverify", "Netpgp Verification (libnetpgpverify, \\-lnetpgpverify)")
+LINE("libnpf", "NPF Packet Filter Library (libnpf, \\-lnpf)")
+LINE("libnv", "Name/value pairs library (libnv, \\-lnv)")
+LINE("libossaudio", "OSS Audio Emulation Library (libossaudio, \\-lossaudio)")
+LINE("libpam", "Pluggable Authentication Module Library (libpam, \\-lpam)")
+LINE("libpcap", "Packet Capture Library (libpcap, \\-lpcap)")
+LINE("libpci", "PCI Bus Access Library (libpci, \\-lpci)")
+LINE("libpmc", "Performance Counters Library (libpmc, \\-lpmc)")
+LINE("libppath", "Property-List Paths Library (libppath, \\-lppath)")
+LINE("libposix", "POSIX Compatibility Library (libposix, \\-lposix)")
+LINE("libposix1e", "POSIX.1e Security API Library (libposix1e, \\-lposix1e)")
+LINE("libppath", "Property-List Paths Library (libppath, \\-lppath)")
+LINE("libproc", "Processor Monitoring and Analysis Library (libproc, \\-lproc)")
+LINE("libprocstat", "Process and Files Information Retrieval (libprocstat, \\-lprocstat)")
+LINE("libprop", "Property Container Object Library (libprop, \\-lprop)")
+LINE("libpthread", "POSIX Threads Library (libpthread, \\-lpthread)")
+LINE("libpuffs", "puffs Convenience Library (libpuffs, \\-lpuffs)")
+LINE("libquota", "Disk Quota Access and Control Library (libquota, \\-lquota)")
+LINE("libradius", "RADIUS Client Library (libradius, \\-lradius)")
+LINE("librefuse", "File System in Userspace Convenience Library (librefuse, \\-lrefuse)")
+LINE("libresolv", "DNS Resolver Library (libresolv, \\-lresolv)")
+LINE("librpcsec_gss", "RPC GSS-API Authentication Library (librpcsec_gss, \\-lrpcsec_gss)")
+LINE("librpcsvc", "RPC Service Library (librpcsvc, \\-lrpcsvc)")
+LINE("librt", "POSIX Real\\-time Library (librt, \\-lrt)")
+LINE("librtld_db", "Run-time Linker Debugging Library (librtld_db, \\-lrtld_db)")
+LINE("libsaslc", "Simple Authentication and Security Layer client library (libsaslc, \\-lsaslc)")
+LINE("libsbuf", "Safe String Composition Library (libsbuf, \\-lsbuf)")
+LINE("libsdp", "Bluetooth Service Discovery Protocol User Library (libsdp, \\-lsdp)")
+LINE("libssp", "Buffer Overflow Protection Library (libssp, \\-lssp)")
+LINE("libstdthreads", "C11 Threads Library (libstdthreads, \\-lstdthreads)")
+LINE("libSystem", "System Library (libSystem, \\-lSystem)")
+LINE("libtacplus", "TACACS+ Client Library (libtacplus, \\-ltacplus)")
+LINE("libtcplay", "TrueCrypt-compatible API library (libtcplay, \\-ltcplay)")
+LINE("libtermcap", "Termcap Access Library (libtermcap, \\-ltermcap)")
+LINE("libterminfo", "Terminal Information Library (libterminfo, \\-lterminfo)")
+LINE("libthr", "1:1 Threading Library (libthr, \\-lthr)")
+LINE("libufs", "UFS File System Access Library (libufs, \\-lufs)")
+LINE("libugidfw", "File System Firewall Interface Library (libugidfw, \\-lugidfw)")
+LINE("libulog", "User Login Record Library (libulog, \\-lulog)")
+LINE("libusbhid", "USB Human Interface Devices Library (libusbhid, \\-lusbhid)")
+LINE("libutil", "System Utilities Library (libutil, \\-lutil)")
+LINE("libvgl", "Video Graphics Library (libvgl, \\-lvgl)")
+LINE("libx86_64", "x86_64 Architecture Library (libx86_64, \\-lx86_64)")
+LINE("libz", "Compression Library (libz, \\-lz)")
diff --git a/contrib/mdocml/libman.h b/contrib/mdocml/libman.h
new file mode 100644
index 0000000..e852927
--- /dev/null
+++ b/contrib/mdocml/libman.h
@@ -0,0 +1,77 @@
+/* $Id: libman.h,v 1.63 2014/08/01 21:24:17 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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
+
+enum man_next {
+ MAN_NEXT_SIBLING = 0,
+ MAN_NEXT_CHILD
+};
+
+struct man {
+ struct mparse *parse; /* parse pointer */
+ int quick; /* abort parse early */
+ int flags; /* parse flags */
+#define MAN_ELINE (1 << 1) /* Next-line element scope. */
+#define MAN_BLINE (1 << 2) /* Next-line block scope. */
+#define MAN_LITERAL (1 << 4) /* Literal input. */
+#define MAN_NEWLINE (1 << 6) /* first macro/text in a line */
+ enum man_next next; /* where to put the next node */
+ struct man_node *last; /* the last parsed node */
+ struct man_node *first; /* the first parsed node */
+ struct man_meta meta; /* document meta-data */
+ struct roff *roff;
+};
+
+#define MACRO_PROT_ARGS struct man *man, \
+ enum mant 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(). */
+#define MAN_NSCOPED (1 << 3) /* See in_line_eoln(). */
+#define MAN_NOCLOSE (1 << 4) /* See blk_exp(). */
+#define MAN_BSCOPE (1 << 5) /* Break BLINE scope. */
+};
+
+extern const struct man_macro *const man_macros;
+
+__BEGIN_DECLS
+
+int man_word_alloc(struct man *, int, int, const char *);
+int man_block_alloc(struct man *, int, int, enum mant);
+int man_head_alloc(struct man *, int, int, enum mant);
+int man_tail_alloc(struct man *, int, int, enum mant);
+int man_body_alloc(struct man *, int, int, enum mant);
+int man_elem_alloc(struct man *, int, int, enum mant);
+void man_node_delete(struct man *, struct man_node *);
+void man_hash_init(void);
+enum mant man_hash_find(const char *);
+int man_macroend(struct man *);
+int man_valid_post(struct man *);
+int man_unscope(struct man *, const struct man_node *);
+
+__END_DECLS
+
+#endif /*!LIBMAN_H*/
diff --git a/contrib/mdocml/libmandoc.h b/contrib/mdocml/libmandoc.h
new file mode 100644
index 0000000..1011cc50
--- /dev/null
+++ b/contrib/mdocml/libmandoc.h
@@ -0,0 +1,92 @@
+/* $Id: libmandoc.h,v 1.42 2014/07/09 11:31:43 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef LIBMANDOC_H
+#define LIBMANDOC_H
+
+enum rofferr {
+ ROFF_CONT, /* continue processing line */
+ ROFF_RERUN, /* re-run roff interpreter with offset */
+ ROFF_APPEND, /* re-run main parser, appending next line */
+ ROFF_REPARSE, /* re-run main parser on the result */
+ ROFF_SO, /* include another file */
+ ROFF_IGN, /* ignore current line */
+ ROFF_TBL, /* a table row was successfully parsed */
+ ROFF_EQN, /* an equation was successfully parsed */
+ ROFF_ERR /* badness: puke and stop */
+};
+
+__BEGIN_DECLS
+
+struct roff;
+struct mdoc;
+struct man;
+
+void mandoc_msg(enum mandocerr, struct mparse *,
+ int, int, const char *);
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 5, 6)))
+#endif
+void mandoc_vmsg(enum mandocerr, struct mparse *,
+ int, int, const char *, ...);
+char *mandoc_getarg(struct mparse *, char **, int, int *);
+char *mandoc_normdate(struct mparse *, char *, int, int);
+int mandoc_eos(const char *, size_t);
+int mandoc_strntoi(const char *, size_t, int);
+const char *mandoc_a2msec(const char*);
+
+void mdoc_free(struct mdoc *);
+struct mdoc *mdoc_alloc(struct roff *, struct mparse *,
+ const char *, int);
+void mdoc_reset(struct mdoc *);
+int mdoc_parseln(struct mdoc *, int, char *, int);
+int mdoc_endparse(struct mdoc *);
+int mdoc_addspan(struct mdoc *, const struct tbl_span *);
+int mdoc_addeqn(struct mdoc *, const struct eqn *);
+
+void man_free(struct man *);
+struct man *man_alloc(struct roff *, struct mparse *, int);
+void man_reset(struct man *);
+int man_parseln(struct man *, int, char *, int);
+int man_endparse(struct man *);
+int man_addspan(struct man *, const struct tbl_span *);
+int man_addeqn(struct man *, const struct eqn *);
+
+void roff_free(struct roff *);
+struct roff *roff_alloc(struct mparse *, int);
+void roff_reset(struct roff *);
+enum rofferr roff_parseln(struct roff *, int,
+ char **, size_t *, int, int *);
+void roff_endparse(struct roff *);
+void roff_setreg(struct roff *, const char *, int, char sign);
+int roff_getreg(const struct roff *, const char *);
+char *roff_strdup(const struct roff *, const char *);
+int roff_getcontrol(const struct roff *,
+ const char *, int *);
+#if 0
+char roff_eqndelim(const struct roff *);
+void roff_openeqn(struct roff *, const char *,
+ int, int, const char *);
+int roff_closeeqn(struct roff *);
+#endif
+
+const struct tbl_span *roff_span(const struct roff *);
+const struct eqn *roff_eqn(const struct roff *);
+
+__END_DECLS
+
+#endif /*!LIBMANDOC_H*/
diff --git a/contrib/mdocml/libmdoc.h b/contrib/mdocml/libmdoc.h
new file mode 100644
index 0000000..1507a8c
--- /dev/null
+++ b/contrib/mdocml/libmdoc.h
@@ -0,0 +1,143 @@
+/* $Id: libmdoc.h,v 1.88 2014/08/01 17:40:34 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef LIBMDOC_H
+#define LIBMDOC_H
+
+enum mdoc_next {
+ MDOC_NEXT_SIBLING = 0,
+ MDOC_NEXT_CHILD
+};
+
+struct mdoc {
+ struct mparse *parse; /* parse pointer */
+ const char *defos; /* default argument for .Os */
+ int quick; /* abort parse early */
+ int flags; /* parse flags */
+#define MDOC_LITERAL (1 << 1) /* in a literal scope */
+#define MDOC_PBODY (1 << 2) /* in the document body */
+#define MDOC_NEWLINE (1 << 3) /* first macro/text in a line */
+#define MDOC_PHRASELIT (1 << 4) /* literal within a partila phrase */
+#define MDOC_PPHRASE (1 << 5) /* within a partial phrase */
+#define MDOC_FREECOL (1 << 6) /* `It' invocation should close */
+#define MDOC_SYNOPSIS (1 << 7) /* SYNOPSIS-style formatting */
+#define MDOC_KEEP (1 << 8) /* in a word keep */
+#define MDOC_SMOFF (1 << 9) /* spacing is off */
+ enum mdoc_next next; /* where to put the next node */
+ struct mdoc_node *last; /* the last node parsed */
+ struct mdoc_node *first; /* the first node parsed */
+ struct mdoc_node *last_es; /* the most recent Es node */
+ struct mdoc_meta meta; /* document meta-data */
+ enum mdoc_sec lastnamed;
+ enum mdoc_sec lastsec;
+ struct roff *roff;
+};
+
+#define MACRO_PROT_ARGS struct mdoc *mdoc, \
+ enum mdoct 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)
+#define MDOC_JOIN (1 << 5)
+};
+
+enum margserr {
+ ARGS_ERROR,
+ ARGS_EOLN, /* end-of-line */
+ ARGS_WORD, /* normal word */
+ ARGS_PUNCT, /* series of punctuation */
+ ARGS_QWORD, /* quoted word */
+ ARGS_PHRASE, /* Ta'd phrase (-column) */
+ ARGS_PPHRASE, /* tabbed phrase (-column) */
+ ARGS_PEND /* last phrase (-column) */
+};
+
+enum margverr {
+ ARGV_ERROR,
+ ARGV_EOLN, /* end of line */
+ ARGV_ARG, /* valid argument */
+ ARGV_WORD /* normal word (or bad argument---same thing) */
+};
+
+/*
+ * A punctuation delimiter is opening, closing, or "middle mark"
+ * punctuation. These govern spacing.
+ * Opening punctuation (e.g., the opening parenthesis) suppresses the
+ * following space; closing punctuation (e.g., the closing parenthesis)
+ * suppresses the leading space; middle punctuation (e.g., the vertical
+ * bar) can do either. The middle punctuation delimiter bends the rules
+ * depending on usage.
+ */
+enum mdelim {
+ DELIM_NONE = 0,
+ DELIM_OPEN,
+ DELIM_MIDDLE,
+ DELIM_CLOSE,
+ DELIM_MAX
+};
+
+extern const struct mdoc_macro *const mdoc_macros;
+
+__BEGIN_DECLS
+
+int mdoc_macro(MACRO_PROT_ARGS);
+int mdoc_word_alloc(struct mdoc *,
+ int, int, const char *);
+void mdoc_word_append(struct mdoc *, const char *);
+int mdoc_elem_alloc(struct mdoc *, int, int,
+ enum mdoct, struct mdoc_arg *);
+int mdoc_block_alloc(struct mdoc *, int, int,
+ enum mdoct, struct mdoc_arg *);
+int mdoc_head_alloc(struct mdoc *, int, int, enum mdoct);
+int mdoc_tail_alloc(struct mdoc *, int, int, enum mdoct);
+int mdoc_body_alloc(struct mdoc *, int, int, enum mdoct);
+int mdoc_endbody_alloc(struct mdoc *, int, int, enum mdoct,
+ struct mdoc_node *, enum mdoc_endbody);
+void mdoc_node_delete(struct mdoc *, struct mdoc_node *);
+int mdoc_node_relink(struct mdoc *, struct mdoc_node *);
+void mdoc_hash_init(void);
+enum mdoct mdoc_hash_find(const char *);
+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 *);
+int mdoc_valid_pre(struct mdoc *, struct mdoc_node *);
+int mdoc_valid_post(struct mdoc *);
+enum margverr mdoc_argv(struct mdoc *, int, enum mdoct,
+ struct mdoc_arg **, int *, char *);
+void mdoc_argv_free(struct mdoc_arg *);
+enum margserr mdoc_args(struct mdoc *, int,
+ int *, char *, enum mdoct, char **);
+enum margserr mdoc_zargs(struct mdoc *, int,
+ int *, char *, char **);
+int mdoc_macroend(struct mdoc *);
+enum mdelim mdoc_isdelim(const char *);
+
+__END_DECLS
+
+#endif /*!LIBMDOC_H*/
diff --git a/contrib/mdocml/libroff.h b/contrib/mdocml/libroff.h
new file mode 100644
index 0000000..f917b14
--- /dev/null
+++ b/contrib/mdocml/libroff.h
@@ -0,0 +1,84 @@
+/* $Id: libroff.h,v 1.29 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 LIBROFF_H
+#define LIBROFF_H
+
+__BEGIN_DECLS
+
+enum tbl_part {
+ TBL_PART_OPTS, /* in options (first line) */
+ TBL_PART_LAYOUT, /* describing layout */
+ TBL_PART_DATA, /* creating data rows */
+ TBL_PART_CDATA /* continue previous row */
+};
+
+struct tbl_node {
+ struct mparse *parse; /* parse point */
+ int pos; /* invocation column */
+ int line; /* invocation line */
+ enum tbl_part part;
+ struct tbl_opts opts;
+ struct tbl_row *first_row;
+ struct tbl_row *last_row;
+ struct tbl_span *first_span;
+ struct tbl_span *current_span;
+ struct tbl_span *last_span;
+ struct tbl_head *first_head;
+ struct tbl_head *last_head;
+ struct tbl_node *next;
+};
+
+struct eqn_node {
+ struct eqn_def *defs;
+ size_t defsz;
+ char *data;
+ size_t rew;
+ size_t cur;
+ size_t sz;
+ int gsize;
+ struct eqn eqn;
+ struct mparse *parse;
+ struct eqn_node *next;
+};
+
+struct eqn_def {
+ char *key;
+ size_t keysz;
+ char *val;
+ size_t valsz;
+};
+
+struct tbl_node *tbl_alloc(int, int, struct mparse *);
+void tbl_restart(int, int, struct tbl_node *);
+void tbl_free(struct tbl_node *);
+void tbl_reset(struct tbl_node *);
+enum rofferr tbl_read(struct tbl_node *, int, const char *, int);
+int tbl_option(struct tbl_node *, int, const char *);
+int tbl_layout(struct tbl_node *, int, const char *);
+int tbl_data(struct tbl_node *, int, const char *);
+int tbl_cdata(struct tbl_node *, int, const char *);
+const struct tbl_span *tbl_span(struct tbl_node *);
+void tbl_end(struct tbl_node **);
+struct eqn_node *eqn_alloc(const char *, int, int, struct mparse *);
+enum rofferr eqn_end(struct eqn_node **);
+void eqn_free(struct eqn_node *);
+enum rofferr eqn_read(struct eqn_node **, int,
+ const char *, int, int *);
+
+__END_DECLS
+
+#endif /*LIBROFF_H*/
diff --git a/contrib/mdocml/main.c b/contrib/mdocml/main.c
new file mode 100644
index 0000000..d97c8e2
--- /dev/null
+++ b/contrib/mdocml/main.c
@@ -0,0 +1,430 @@
+/* $Id: main.c,v 1.177 2014/06/21 22:24:01 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011, 2012, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "main.h"
+#include "mdoc.h"
+#include "man.h"
+
+#if !defined(__GNUC__) || (__GNUC__ < 2)
+# if !defined(lint)
+# define __attribute__(x)
+# endif
+#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
+
+typedef void (*out_mdoc)(void *, const struct mdoc *);
+typedef void (*out_man)(void *, const struct man *);
+typedef void (*out_free)(void *);
+
+enum outt {
+ OUTT_ASCII = 0, /* -Tascii */
+ OUTT_LOCALE, /* -Tlocale */
+ OUTT_UTF8, /* -Tutf8 */
+ OUTT_TREE, /* -Ttree */
+ OUTT_MAN, /* -Tman */
+ OUTT_HTML, /* -Thtml */
+ OUTT_XHTML, /* -Txhtml */
+ OUTT_LINT, /* -Tlint */
+ OUTT_PS, /* -Tps */
+ OUTT_PDF /* -Tpdf */
+};
+
+struct curparse {
+ struct mparse *mp;
+ enum mandoclevel wlevel; /* ignore messages below this */
+ int wstop; /* stop after a file with a warning */
+ enum outt outtype; /* which output to use */
+ out_mdoc outmdoc; /* mdoc output ptr */
+ out_man outman; /* man output ptr */
+ out_free outfree; /* free output ptr */
+ void *outdata; /* data for output */
+ char outopts[BUFSIZ]; /* buf of output opts */
+};
+
+static int moptions(int *, char *);
+static void mmsg(enum mandocerr, enum mandoclevel,
+ const char *, int, int, const char *);
+static void parse(struct curparse *, int,
+ const char *, enum mandoclevel *);
+static int toptions(struct curparse *, char *);
+static void usage(void) __attribute__((noreturn));
+static void version(void) __attribute__((noreturn));
+static int woptions(struct curparse *, char *);
+
+static const char *progname;
+
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ struct curparse curp;
+ int options;
+ enum mandoclevel rc;
+ char *defos;
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ memset(&curp, 0, sizeof(struct curparse));
+
+ options = MPARSE_SO;
+ curp.outtype = OUTT_ASCII;
+ curp.wlevel = MANDOCLEVEL_FATAL;
+ defos = NULL;
+
+ while (-1 != (c = getopt(argc, argv, "I:m:O:T:VW:")))
+ switch (c) {
+ case 'I':
+ if (strncmp(optarg, "os=", 3)) {
+ fprintf(stderr,
+ "%s: -I%s: Bad argument\n",
+ progname, optarg);
+ return((int)MANDOCLEVEL_BADARG);
+ }
+ if (defos) {
+ fprintf(stderr,
+ "%s: -I%s: Duplicate argument\n",
+ progname, optarg);
+ return((int)MANDOCLEVEL_BADARG);
+ }
+ defos = mandoc_strdup(optarg + 3);
+ break;
+ case 'm':
+ if ( ! moptions(&options, optarg))
+ return((int)MANDOCLEVEL_BADARG);
+ break;
+ case 'O':
+ (void)strlcat(curp.outopts, optarg, BUFSIZ);
+ (void)strlcat(curp.outopts, ",", BUFSIZ);
+ break;
+ case 'T':
+ if ( ! toptions(&curp, optarg))
+ return((int)MANDOCLEVEL_BADARG);
+ break;
+ case 'W':
+ if ( ! woptions(&curp, optarg))
+ return((int)MANDOCLEVEL_BADARG);
+ break;
+ case 'V':
+ version();
+ /* NOTREACHED */
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
+
+ /*
+ * Conditionally start up the lookaside buffer before parsing.
+ */
+ if (OUTT_MAN == curp.outtype)
+ mparse_keep(curp.mp);
+
+ argc -= optind;
+ argv += optind;
+
+ rc = MANDOCLEVEL_OK;
+
+ if (NULL == *argv)
+ parse(&curp, STDIN_FILENO, "<stdin>", &rc);
+
+ while (*argv) {
+ parse(&curp, -1, *argv, &rc);
+ if (MANDOCLEVEL_OK != rc && curp.wstop)
+ break;
+ ++argv;
+ }
+
+ if (curp.outfree)
+ (*curp.outfree)(curp.outdata);
+ if (curp.mp)
+ mparse_free(curp.mp);
+ free(defos);
+
+ return((int)rc);
+}
+
+static void
+version(void)
+{
+
+ printf("%s %s\n", progname, VERSION);
+ exit((int)MANDOCLEVEL_OK);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s "
+ "[-V] "
+ "[-Ios=name] "
+ "[-mformat] "
+ "[-Ooption] "
+ "[-Toutput] "
+ "[-Wlevel]\n"
+ "\t [file ...]\n",
+ progname);
+
+ exit((int)MANDOCLEVEL_BADARG);
+}
+
+static void
+parse(struct curparse *curp, int fd, const char *file,
+ enum mandoclevel *level)
+{
+ enum mandoclevel rc;
+ struct mdoc *mdoc;
+ struct man *man;
+
+ /* Begin by parsing the file itself. */
+
+ assert(file);
+ assert(fd >= -1);
+
+ rc = mparse_readfd(curp->mp, fd, file);
+
+ /* Stop immediately if the parse has failed. */
+
+ if (MANDOCLEVEL_FATAL <= rc)
+ goto cleanup;
+
+ /*
+ * With -Wstop and warnings or errors of at least the requested
+ * level, do not produce output.
+ */
+
+ if (MANDOCLEVEL_OK != rc && curp->wstop)
+ goto cleanup;
+
+ /* If unset, allocate output dev now (if applicable). */
+
+ if ( ! (curp->outman && curp->outmdoc)) {
+ switch (curp->outtype) {
+ case OUTT_XHTML:
+ curp->outdata = xhtml_alloc(curp->outopts);
+ curp->outfree = html_free;
+ break;
+ case OUTT_HTML:
+ curp->outdata = html_alloc(curp->outopts);
+ curp->outfree = html_free;
+ break;
+ case OUTT_UTF8:
+ curp->outdata = utf8_alloc(curp->outopts);
+ curp->outfree = ascii_free;
+ break;
+ case OUTT_LOCALE:
+ curp->outdata = locale_alloc(curp->outopts);
+ curp->outfree = ascii_free;
+ break;
+ case OUTT_ASCII:
+ curp->outdata = ascii_alloc(curp->outopts);
+ curp->outfree = ascii_free;
+ break;
+ case OUTT_PDF:
+ curp->outdata = pdf_alloc(curp->outopts);
+ curp->outfree = pspdf_free;
+ break;
+ case OUTT_PS:
+ curp->outdata = ps_alloc(curp->outopts);
+ curp->outfree = pspdf_free;
+ break;
+ default:
+ break;
+ }
+
+ switch (curp->outtype) {
+ case OUTT_HTML:
+ /* FALLTHROUGH */
+ case OUTT_XHTML:
+ curp->outman = html_man;
+ curp->outmdoc = html_mdoc;
+ break;
+ case OUTT_TREE:
+ curp->outman = tree_man;
+ curp->outmdoc = tree_mdoc;
+ break;
+ case OUTT_MAN:
+ curp->outmdoc = man_mdoc;
+ curp->outman = man_man;
+ break;
+ case OUTT_PDF:
+ /* FALLTHROUGH */
+ case OUTT_ASCII:
+ /* FALLTHROUGH */
+ case OUTT_UTF8:
+ /* FALLTHROUGH */
+ case OUTT_LOCALE:
+ /* FALLTHROUGH */
+ case OUTT_PS:
+ curp->outman = terminal_man;
+ curp->outmdoc = terminal_mdoc;
+ break;
+ default:
+ break;
+ }
+ }
+
+ mparse_result(curp->mp, &mdoc, &man, NULL);
+
+ /* 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);
+
+ cleanup:
+
+ mparse_reset(curp->mp);
+
+ if (*level < rc)
+ *level = rc;
+}
+
+static int
+moptions(int *options, char *arg)
+{
+
+ if (0 == strcmp(arg, "doc"))
+ *options |= MPARSE_MDOC;
+ else if (0 == strcmp(arg, "andoc"))
+ /* nothing to do */;
+ else if (0 == strcmp(arg, "an"))
+ *options |= MPARSE_MAN;
+ else {
+ fprintf(stderr, "%s: -m%s: Bad argument\n",
+ progname, arg);
+ return(0);
+ }
+
+ return(1);
+}
+
+static int
+toptions(struct curparse *curp, char *arg)
+{
+
+ if (0 == strcmp(arg, "ascii"))
+ curp->outtype = OUTT_ASCII;
+ else if (0 == strcmp(arg, "lint")) {
+ curp->outtype = OUTT_LINT;
+ curp->wlevel = MANDOCLEVEL_WARNING;
+ } else if (0 == strcmp(arg, "tree"))
+ curp->outtype = OUTT_TREE;
+ else if (0 == strcmp(arg, "man"))
+ curp->outtype = OUTT_MAN;
+ else if (0 == strcmp(arg, "html"))
+ curp->outtype = OUTT_HTML;
+ else if (0 == strcmp(arg, "utf8"))
+ curp->outtype = OUTT_UTF8;
+ else if (0 == strcmp(arg, "locale"))
+ curp->outtype = OUTT_LOCALE;
+ else if (0 == strcmp(arg, "xhtml"))
+ curp->outtype = OUTT_XHTML;
+ else if (0 == strcmp(arg, "ps"))
+ curp->outtype = OUTT_PS;
+ else if (0 == strcmp(arg, "pdf"))
+ curp->outtype = OUTT_PDF;
+ else {
+ fprintf(stderr, "%s: -T%s: Bad argument\n",
+ progname, arg);
+ return(0);
+ }
+
+ return(1);
+}
+
+static int
+woptions(struct curparse *curp, char *arg)
+{
+ char *v, *o;
+ const char *toks[6];
+
+ toks[0] = "stop";
+ toks[1] = "all";
+ toks[2] = "warning";
+ toks[3] = "error";
+ toks[4] = "fatal";
+ toks[5] = NULL;
+
+ while (*arg) {
+ o = arg;
+ switch (getsubopt(&arg, UNCONST(toks), &v)) {
+ case 0:
+ curp->wstop = 1;
+ break;
+ case 1:
+ /* FALLTHROUGH */
+ case 2:
+ curp->wlevel = MANDOCLEVEL_WARNING;
+ break;
+ case 3:
+ curp->wlevel = MANDOCLEVEL_ERROR;
+ break;
+ case 4:
+ curp->wlevel = MANDOCLEVEL_FATAL;
+ break;
+ default:
+ fprintf(stderr, "%s: -W%s: Bad argument\n",
+ progname, o);
+ return(0);
+ }
+ }
+
+ return(1);
+}
+
+static void
+mmsg(enum mandocerr t, enum mandoclevel lvl,
+ const char *file, int line, int col, const char *msg)
+{
+ const char *mparse_msg;
+
+ fprintf(stderr, "%s: %s:", progname, file);
+
+ if (line)
+ fprintf(stderr, "%d:%d:", line, col + 1);
+
+ fprintf(stderr, " %s", mparse_strlevel(lvl));
+
+ if (NULL != (mparse_msg = mparse_strerror(t)))
+ fprintf(stderr, ": %s", mparse_msg);
+
+ if (msg)
+ fprintf(stderr, ": %s", msg);
+
+ fputc('\n', stderr);
+}
diff --git a/contrib/mdocml/main.h b/contrib/mdocml/main.h
new file mode 100644
index 0000000..beb0481
--- /dev/null
+++ b/contrib/mdocml/main.h
@@ -0,0 +1,61 @@
+/* $Id: main.h,v 1.16 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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;
+
+#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a))
+
+
+/*
+ * 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 *xhtml_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 man_mdoc(void *, const struct mdoc *);
+void man_man(void *, const struct man *);
+
+void *locale_alloc(char *);
+void *utf8_alloc(char *);
+void *ascii_alloc(char *);
+void ascii_free(void *);
+
+void *pdf_alloc(char *);
+void *ps_alloc(char *);
+void pspdf_free(void *);
+
+void terminal_mdoc(void *, const struct mdoc *);
+void terminal_man(void *, const struct man *);
+
+__END_DECLS
+
+#endif /*!MAIN_H*/
diff --git a/contrib/mdocml/makewhatis.8 b/contrib/mdocml/makewhatis.8
new file mode 100644
index 0000000..02c4cc3
--- /dev/null
+++ b/contrib/mdocml/makewhatis.8
@@ -0,0 +1,217 @@
+.\" $Id: makewhatis.8,v 1.2 2014/04/25 12:13:15 schwarze Exp $
+.\"
+.\" Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011, 2012 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: April 25 2014 $
+.Dt MAKEWHATIS 8
+.Os
+.Sh NAME
+.Nm makewhatis
+.Nd index UNIX manuals
+.Sh SYNOPSIS
+.Nm
+.Op Fl aDnpQ
+.Op Fl T Cm utf8
+.Op Fl C Ar file
+.Nm
+.Op Fl aDnpQ
+.Op Fl T Cm utf8
+.Ar dir ...
+.Nm
+.Op Fl DnpQ
+.Op Fl T Cm utf8
+.Fl d Ar dir
+.Op Ar
+.Nm
+.Op Fl Dnp
+.Op Fl T Cm utf8
+.Fl u Ar dir
+.Op Ar
+.Nm
+.Op Fl DQ
+.Fl t Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility extracts keywords from
+.Ux
+manuals and indexes them in a database for fast retrieval by
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+and
+.Xr man 1 Ns 's
+.Fl k
+option.
+.Pp
+By default,
+.Nm
+creates a database in each
+.Ar dir
+using the files
+.Sm off
+.Sy man Ar section Li /
+.Op Ar arch Li /
+.Ar title . section
+.Sm on
+and
+.Sm off
+.Sy cat Ar section Li /
+.Op Ar arch Li /
+.Ar title . Sy 0
+.Sm on
+in that directory.
+Existing databases are replaced.
+If
+.Ar dir
+is not provided,
+.Nm
+uses the default paths stipulated by
+.Xr manpath 1 ,
+or
+.Xr man.conf 5 .
+.Pp
+The arguments are as follows:
+.Bl -tag -width "-C file"
+.It Fl a
+Use all directories and files found below
+.Ar dir ... .
+.It Fl C Ar file
+Specify an alternative configuration
+.Ar file
+in
+.Xr man.conf 5
+format.
+.It Fl D
+Display all files added or removed to the index.
+With a second
+.Fl D ,
+also show all keyswords added for each file.
+.It Fl d Ar dir
+Merge (remove and re-add)
+.Ar
+to the database in
+.Ar dir .
+.It Fl n
+Do not create or modify any database; scan and parse only,
+and print manual page names and descriptions to standard output.
+.It Fl p
+Print warnings about potential problems with manual pages
+to the standard error output.
+.It Fl Q
+Quickly build reduced-size databases
+by reading only the NAME sections of manuals.
+The resulting databases will usually contain names and descriptions only.
+.It Fl T Cm utf8
+Use UTF-8 encoding instead of ASCII for strings stored in the databases.
+.It Fl t Ar
+Check the given
+.Ar files
+for potential problems.
+Implies
+.Fl a ,
+.Fl n ,
+and
+.Fl p .
+All diagnostic messages are printed to the standard output;
+the standard error output is not used.
+.It Fl u Ar dir
+Remove
+.Ar
+from the database in
+.Ar dir .
+.El
+.Pp
+If fatal parse errors are encountered while parsing, the offending file
+is printed to stderr, omitted from the index, and the parse continues
+with the next input file.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa mandoc.db
+A database of manpages relative to the directory of the file.
+This file is portable across architectures and systems, so long as the
+manpage hierarchy it indexes does not change.
+.It Pa /etc/man.conf
+The default
+.Xr man 1
+configuration file.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width Ds -compact
+.It 0
+No errors occurred.
+.It 5
+Invalid command line arguments were specified.
+No input files have been read.
+.It 6
+An operating system error occurred, for example memory exhaustion or an
+error accessing input files.
+Such errors cause
+.Nm
+to exit at once, possibly in the middle of parsing or formatting a file.
+The output databases are corrupt and should be removed.
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr man 1 ,
+.Xr whatis 1 ,
+.Xr man.conf 5
+.Sh HISTORY
+A
+.Nm
+utility first appeared in
+.Bx 2 .
+It was rewritten in
+.Xr perl 1
+for
+.Ox 2.7
+and in C for
+.Ox 5.6 .
+.Pp
+The
+.Ar dir
+argument first appeared in
+.Nx 1.0 ;
+the options
+.Fl dpt
+in
+.Ox 2.7 ;
+the option
+.Fl u
+in
+.Ox 3.4 ;
+and the options
+.Fl aCDnQT
+in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+.An Bill Joy
+wrote the original
+.Bx
+.Nm
+in February 1979,
+.An Marc Espie
+started the Perl version in 2000,
+and the current version of
+.Nm
+was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/man.7 b/contrib/mdocml/man.7
new file mode 100644
index 0000000..4b64d7f
--- /dev/null
+++ b/contrib/mdocml/man.7
@@ -0,0 +1,953 @@
+.\" $Id: man.7,v 1.127 2014/06/22 16:39:45 schwarze Exp $
+.\"
+.\" Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011, 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
+.\" Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: June 22 2014 $
+.Dt MAN 7
+.Os
+.Sh NAME
+.Nm man
+.Nd legacy formatting language for manual pages
+.Sh DESCRIPTION
+Traditionally, the
+.Nm man
+language has been used to write
+.Ux
+manuals for the
+.Xr man 1
+utility.
+It supports limited control of presentational details like fonts,
+indentation and spacing.
+This reference document describes the structure of manual pages
+and the syntax and usage of the man language.
+.Pp
+.Bf -emphasis
+Do not use
+.Nm
+to write your manuals:
+.Ef
+It lacks support for semantic markup.
+Use the
+.Xr mdoc 7
+language, instead.
+.Pp
+In a
+.Nm
+document, lines beginning with the control character
+.Sq \&.
+are called
+.Dq macro lines .
+The first word is the macro name.
+It usually consists of two capital letters.
+For a list of available macros, see
+.Sx MACRO OVERVIEW .
+The words following the macro name are arguments to the macro.
+.Pp
+Lines not beginning with the control character are called
+.Dq text lines .
+They provide free-form text to be printed; the formatting of the text
+depends on the respective processing context:
+.Bd -literal -offset indent
+\&.SH Macro lines change control state.
+Text lines are interpreted within the current state.
+.Ed
+.Pp
+Many aspects of the basic syntax of the
+.Nm
+language are based on the
+.Xr roff 7
+language; see the
+.Em LANGUAGE SYNTAX
+and
+.Em MACRO SYNTAX
+sections in the
+.Xr roff 7
+manual for details, in particular regarding
+comments, escape sequences, whitespace, and quoting.
+.Sh MANUAL STRUCTURE
+Each
+.Nm
+document must contain 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 line must appear in the document.
+.Pp
+The following is a well-formed skeleton
+.Nm
+file for a utility
+.Qq progname :
+.Bd -literal -offset indent
+\&.TH PROGNAME 1 2009-10-10
+\&.SH NAME
+\efBprogname\efR \e(en one line about what it does
+\&.\e\(dq .SH LIBRARY
+\&.\e\(dq For sections 2, 3, and 9 only.
+\&.\e\(dq Not used in OpenBSD.
+\&.SH SYNOPSIS
+\efBprogname\efR [\efB\e-options\efR] \efIfile ...\efR
+\&.SH DESCRIPTION
+The \efBfoo\efR utility processes files ...
+\&.\e\(dq .Sh CONTEXT
+\&.\e\(dq For section 9 functions only.
+\&.\e\(dq .SH IMPLEMENTATION NOTES
+\&.\e\(dq Not used in OpenBSD.
+\&.\e\(dq .SH RETURN VALUES
+\&.\e\(dq For sections 2, 3, and 9 function return values only.
+\&.\e\(dq .SH ENVIRONMENT
+\&.\e\(dq For sections 1, 6, 7, and 8 only.
+\&.\e\(dq .SH FILES
+\&.\e\(dq .SH EXIT STATUS
+\&.\e\(dq For sections 1, 6, and 8 only.
+\&.\e\(dq .SH EXAMPLES
+\&.\e\(dq .SH DIAGNOSTICS
+\&.\e\(dq For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only.
+\&.\e\(dq .SH ERRORS
+\&.\e\(dq For sections 2, 3, 4, and 9 errno settings only.
+\&.\e\(dq .SH SEE ALSO
+\&.\e\(dq .BR foobar ( 1 )
+\&.\e\(dq .SH STANDARDS
+\&.\e\(dq .SH HISTORY
+\&.\e\(dq .SH AUTHORS
+\&.\e\(dq .SH CAVEATS
+\&.\e\(dq .SH BUGS
+\&.\e\(dq .SH SECURITY CONSIDERATIONS
+\&.\e\(dq Not used in OpenBSD.
+.Ed
+.Pp
+The sections in a
+.Nm
+document are conventionally ordered as they appear above.
+Sections should be composed as follows:
+.Bl -ohang -offset indent
+.It Em NAME
+The name(s) and a short description of the documented material.
+The syntax for this is generally as follows:
+.Pp
+.D1 \efBname\efR \e(en description
+.It Em LIBRARY
+The name of the library containing the documented material, which is
+assumed to be a function in a section 2 or 3 manual.
+For functions in the C library, this may be as follows:
+.Pp
+.D1 Standard C Library (libc, -lc)
+.It Em SYNOPSIS
+Documents the utility invocation syntax, function call syntax, or device
+configuration.
+.Pp
+For the first, utilities (sections 1, 6, and 8), this is
+generally structured as follows:
+.Pp
+.D1 \efBname\efR [-\efBab\efR] [-\efBc\efR\efIarg\efR] \efBpath\efR...
+.Pp
+For the second, function calls (sections 2, 3, 9):
+.Pp
+.D1 \&.B char *name(char *\efIarg\efR);
+.Pp
+And for the third, configurations (section 4):
+.Pp
+.D1 \&.B name* at cardbus ? function ?
+.Pp
+Manuals not in these sections generally don't need a
+.Em SYNOPSIS .
+.It Em DESCRIPTION
+This expands upon the brief, one-line description in
+.Em NAME .
+It usually contains a break-down of the options (if documenting a
+command).
+.It Em CONTEXT
+This section lists the contexts in which functions can be called in section 9.
+The contexts are autoconf, process, or interrupt.
+.It Em IMPLEMENTATION NOTES
+Implementation-specific notes should be kept here.
+This is useful when implementing standard functions that may have side
+effects or notable algorithmic implications.
+.It Em RETURN VALUES
+This section documents the return values of functions in sections 2, 3, and 9.
+.It Em ENVIRONMENT
+Documents any usages of environment variables, e.g.,
+.Xr environ 7 .
+.It Em FILES
+Documents files used.
+It's helpful to document both the file name and a short description of how
+the file is used (created, modified, etc.).
+.It Em EXIT STATUS
+This section documents the command exit status for
+section 1, 6, and 8 utilities.
+Historically, this information was described in
+.Em DIAGNOSTICS ,
+a practise that is now discouraged.
+.It Em EXAMPLES
+Example usages.
+This often contains snippets of well-formed,
+well-tested invocations.
+Make sure that examples work properly!
+.It Em DIAGNOSTICS
+Documents error conditions.
+In section 4 and 9 manuals, these are usually messages
+printed by the kernel to the console and to the kernel log.
+In section 1, 6, 7, and 8, these are usually messages
+printed by userland programs to the standard error output.
+.Pp
+Historically, this section was used in place of
+.Em EXIT STATUS
+for manuals in sections 1, 6, and 8; however, this practise is
+discouraged.
+.It Em ERRORS
+Documents
+.Xr errno 2
+settings in sections 2, 3, 4, and 9.
+.It Em SEE ALSO
+References other manuals with related topics.
+This section should exist for most manuals.
+.Pp
+.D1 \&.BR bar \&( 1 \&),
+.Pp
+Cross-references should conventionally be ordered
+first by section, then alphabetically.
+.It Em STANDARDS
+References any standards implemented or used, such as
+.Pp
+.D1 IEEE Std 1003.2 (\e(lqPOSIX.2\e(rq)
+.Pp
+If not adhering to any standards, the
+.Em HISTORY
+section should be used.
+.It Em HISTORY
+A brief history of the subject, including where support first appeared.
+.It Em AUTHORS
+Credits to the person or persons who wrote the code and/or documentation.
+Authors should generally be noted by both name and email address.
+.It Em CAVEATS
+Common misuses and misunderstandings should be explained
+in this section.
+.It Em BUGS
+Known bugs, limitations, and work-arounds should be described
+in this section.
+.It Em SECURITY CONSIDERATIONS
+Documents any security precautions that operators should consider.
+.El
+.Sh MACRO OVERVIEW
+This overview is sorted such that macros of similar purpose are listed
+together, to help find the best macro for any given purpose.
+Deprecated macros are not included in the overview, but can be found
+in the alphabetical reference below.
+.Ss Page header and footer meta-data
+.Bl -column "PP, LP, P" description
+.It Sx TH Ta set the title: Ar title section date Op Ar source Op Ar volume
+.It Sx AT Ta display AT&T UNIX version in the page footer (<= 1 argument)
+.It Sx UC Ta display BSD version in the page footer (<= 1 argument)
+.El
+.Ss Sections and paragraphs
+.Bl -column "PP, LP, P" description
+.It Sx SH Ta section header (one line)
+.It Sx SS Ta subsection header (one line)
+.It Sx PP , LP , P Ta start an undecorated paragraph (no arguments)
+.It Sx RS , RE Ta reset the left margin: Op Ar width
+.It Sx IP Ta indented paragraph: Op Ar head Op Ar width
+.It Sx TP Ta tagged paragraph: Op Ar width
+.It Sx HP Ta hanged paragraph: Op Ar width
+.It Sx PD Ta set vertical paragraph distance: Op Ar height
+.It Sx \&br Ta force output line break in text mode (no arguments)
+.It Sx \&sp Ta force vertical space: Op Ar height
+.It Sx fi , nf Ta fill mode and no-fill mode (no arguments)
+.It Sx in Ta additional indent: Op Ar width
+.El
+.Ss Physical markup
+.Bl -column "PP, LP, P" description
+.It Sx B Ta boldface font
+.It Sx I Ta italic font
+.It Sx R Ta roman (default) font
+.It Sx SB Ta small boldface font
+.It Sx SM Ta small roman font
+.It Sx BI Ta alternate between boldface and italic fonts
+.It Sx BR Ta alternate between boldface and roman fonts
+.It Sx IB Ta alternate between italic and boldface fonts
+.It Sx IR Ta alternate between italic and roman fonts
+.It Sx RB Ta alternate between roman and boldface fonts
+.It Sx RI Ta alternate between roman and italic fonts
+.El
+.Sh MACRO REFERENCE
+This section is a canonical reference to all macros, arranged
+alphabetically.
+For the scoping of individual macros, see
+.Sx MACRO SYNTAX .
+.Ss \&AT
+Sets the volume for the footer for compatibility with man pages from
+.At
+releases.
+The optional arguments specify which release it is from.
+.Ss \&B
+Text is rendered in bold face.
+.Pp
+See also
+.Sx \&I
+and
+.Sx \&R .
+.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.
+.Pp
+Examples:
+.Pp
+.Dl \&.BI bold italic bold italic
+.Pp
+The output of this example will be emboldened
+.Dq bold
+and italicised
+.Dq italic ,
+with spaces stripped between arguments.
+.Pp
+See also
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RB ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&BR
+Text is rendered alternately in bold face and roman (the default font).
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&RB ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&DT
+Has no effect.
+Included for compatibility.
+.Ss \&EE
+This is a non-standard GNU extension, included only for compatibility.
+In
+.Xr mandoc 1 ,
+it does the same as
+.Sx \&fi .
+.Ss \&EX
+This is a non-standard GNU extension, included only for compatibility.
+In
+.Xr mandoc 1 ,
+it does the same as
+.Sx \&nf .
+.Ss \&HP
+Begin a paragraph whose initial output line is left-justified, but
+subsequent output lines are indented, with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&HP
+.Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument is a
+.Xr roff 7
+scaling width.
+If specified, it's saved for later paragraph left-margins; if unspecified, the
+saved or default width is used.
+.Pp
+See also
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&I
+Text is rendered in italics.
+.Pp
+See also
+.Sx \&B
+and
+.Sx \&R .
+.Ss \&IB
+Text is rendered alternately in italics and bold face.
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&BR ,
+.Sx \&RB ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&IP
+Begin an indented paragraph with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&IP
+.Op Cm head Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument is a
+.Xr roff 7
+scaling width defining the left margin.
+It's saved for later paragraph left-margins; if unspecified, the saved or
+default width is used.
+.Pp
+The
+.Cm head
+argument is used as a leading term, flushed to the left margin.
+This is useful for bulleted paragraphs and so on.
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&LP ,
+.Sx \&P ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&IR
+Text is rendered alternately in italics and roman (the default font).
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RB ,
+and
+.Sx \&RI .
+.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 reset to the default.
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&P ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&OP
+Optional command-line argument.
+This is a non-standard GNU extension, included only for compatibility.
+It has the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&OP
+.Cm key Op Cm value
+.Ed
+.Pp
+The
+.Cm key
+is usually a command-line flag and
+.Cm value
+its argument.
+.Ss \&P
+Synonym for
+.Sx \&LP .
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&PD
+Specify the vertical space to be inserted before each new paragraph.
+.br
+The syntax is as follows:
+.Bd -filled -offset indent
+.Pf \. Sx \&PD
+.Op Cm height
+.Ed
+.Pp
+The
+.Cm height
+argument is a
+.Xr roff 7
+scaling width.
+It defaults to
+.Cm 1v .
+If the unit is omitted,
+.Cm v
+is assumed.
+.Pp
+This macro affects the spacing before any subsequent instances of
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+.Sx \&PP ,
+.Sx \&SH ,
+.Sx \&SS ,
+and
+.Sx \&TP .
+.Ss \&PP
+Synonym for
+.Sx \&LP .
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+and
+.Sx \&TP .
+.Ss \&R
+Text is rendered in roman (the default font).
+.Pp
+See also
+.Sx \&I
+and
+.Sx \&B .
+.Ss \&RB
+Text is rendered alternately in roman (the default font) and bold face.
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&RE
+Explicitly close out the scope of a prior
+.Sx \&RS .
+The default left margin is restored to the state of the original
+.Sx \&RS
+invocation.
+.Ss \&RI
+Text is rendered alternately in roman (the default font) and italics.
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RB ,
+and
+.Sx \&IR .
+.Ss \&RS
+Temporarily reset the default left margin.
+This has the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&RS
+.Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument is a
+.Xr roff 7
+scaling width.
+If not specified, the saved or default width is used.
+.Pp
+See also
+.Sx \&RE .
+.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 reset 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 reset to the default.
+.Ss \&TH
+Sets the title of the manual page with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&TH
+.Ar title section date
+.Op Ar source Op Ar volume
+.Ed
+.Pp
+Conventionally, the document
+.Ar title
+is given in all caps.
+The recommended
+.Ar date
+format is
+.Sy YYYY-MM-DD
+as specified in the ISO-8601 standard;
+if the argument does not conform, it is printed verbatim.
+If the
+.Ar date
+is empty or not specified, the current date is used.
+The optional
+.Ar source
+string specifies the organisation providing the utility.
+The
+.Ar volume
+string replaces the default rendered volume, which is dictated by the
+manual section.
+.Pp
+Examples:
+.Pp
+.Dl \&.TH CVS 5 "1992-02-12" GNU
+.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.
+The syntax is as follows:
+.Bd -filled -offset indent
+.Pf \. Sx \&TP
+.Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument is a
+.Xr roff 7
+scaling width.
+If specified, it's saved for later paragraph left-margins; if
+unspecified, the saved or default width is used.
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+and
+.Sx \&PP .
+.Ss \&UC
+Sets the volume for the footer for compatibility with man pages from
+.Bx
+releases.
+The optional first argument specifies which release it is from.
+.Ss \&UE
+End a uniform resource identifier block.
+This is a non-standard GNU extension, included only for compatibility.
+See
+.Sx \&UE .
+.Ss \&UR
+Begin a uniform resource identifier block.
+This is a non-standard GNU extension, included only for compatibility.
+It has the following syntax:
+.Bd -literal -offset indent
+.Pf \. Sx \&UR Ar uri
+link description to be shown
+.Pf \. Sx UE
+.Ed
+.Ss \&br
+Breaks the current line.
+Consecutive invocations have no further effect.
+.Pp
+See also
+.Sx \&sp .
+.Ss \&fi
+End literal mode begun by
+.Sx \&nf .
+.Ss \&in
+Indent relative to the current indentation:
+.Pp
+.D1 Pf \. Sx \&in Op Cm width
+.Pp
+If
+.Cm width
+is signed, the new offset is relative.
+Otherwise, it is absolute.
+This value is reset upon the next paragraph, section, or sub-section.
+.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 .
+Literal mode is implicitly ended by
+.Sx \&SH
+or
+.Sx \&SS .
+.Ss \&sp
+Insert vertical spaces into output with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&sp
+.Op Cm height
+.Ed
+.Pp
+The
+.Cm height
+argument is a scaling width as described in
+.Xr roff 7 .
+If 0, this is equivalent to the
+.Sx \&br
+macro.
+Defaults to 1, if unspecified.
+.Pp
+See also
+.Sx \&br .
+.Sh MACRO SYNTAX
+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, which must be text, is used instead.
+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 followed by a non-next-line macro, an error is
+raised, except for
+.Sx \&br ,
+.Sx \&sp ,
+and
+.Sx \&na .
+.Pp
+The syntax is as follows:
+.Bd -literal -offset indent
+\&.YO \(lBbody...\(rB
+\(lBbody...\(rB
+.Ed
+.Bl -column "MacroX" "ArgumentsX" "ScopeXXXXX" "CompatX" -offset indent
+.It Em Macro Ta Em Arguments Ta Em Scope Ta Em Notes
+.It Sx \&AT Ta <=1 Ta current Ta \&
+.It Sx \&B Ta n Ta next-line Ta \&
+.It Sx \&BI Ta n Ta current Ta \&
+.It Sx \&BR Ta n Ta current Ta \&
+.It Sx \&DT Ta 0 Ta current Ta \&
+.It Sx \&EE Ta 0 Ta current Ta compat
+.It Sx \&EX Ta 0 Ta current Ta compat
+.It Sx \&I Ta n Ta next-line Ta \&
+.It Sx \&IB Ta n Ta current Ta \&
+.It Sx \&IR Ta n Ta current Ta \&
+.It Sx \&OP Ta 0, 1 Ta current Ta compat
+.It Sx \&PD Ta 1 Ta current Ta \&
+.It Sx \&R Ta n Ta next-line Ta \&
+.It Sx \&RB Ta n Ta current Ta \&
+.It Sx \&RI Ta n Ta current Ta \&
+.It Sx \&SB Ta n Ta next-line Ta \&
+.It Sx \&SM Ta n Ta next-line Ta \&
+.It Sx \&TH Ta >1, <6 Ta current Ta \&
+.It Sx \&UC Ta <=1 Ta current Ta \&
+.It Sx \&br Ta 0 Ta current Ta compat
+.It Sx \&fi Ta 0 Ta current Ta compat
+.It Sx \&in Ta 1 Ta current Ta compat
+.It Sx \&na Ta 0 Ta current Ta compat
+.It Sx \&nf Ta 0 Ta current Ta compat
+.It Sx \&sp Ta 1 Ta current Ta compat
+.El
+.Pp
+Macros marked as
+.Qq compat
+are included for compatibility with the significant corpus of existing
+manuals that mix dialects of roff.
+These macros should not be used for portable
+.Nm
+manuals.
+.Ss Block Macros
+Block macros comprise a head and body.
+As with in-line macros, the head is scoped to the current line and, in
+one circumstance, the next line (the next-line stipulations as in
+.Sx Line Macros
+apply here as well).
+.Pp
+The syntax is as follows:
+.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
+As a rule, block macros may not be nested; thus, calling a block macro
+while another block macro scope is open, and the open scope is not
+implicitly closed, is syntactically incorrect.
+.Bl -column "MacroX" "ArgumentsX" "Head ScopeX" "sub-sectionX" "compatX" -offset indent
+.It Em Macro Ta Em Arguments Ta Em Head Scope Ta Em Body Scope Ta Em Notes
+.It Sx \&HP Ta <2 Ta current Ta paragraph Ta \&
+.It Sx \&IP Ta <3 Ta current Ta paragraph Ta \&
+.It Sx \&LP Ta 0 Ta current Ta paragraph Ta \&
+.It Sx \&P Ta 0 Ta current Ta paragraph Ta \&
+.It Sx \&PP Ta 0 Ta current Ta paragraph Ta \&
+.It Sx \&RE Ta 0 Ta current Ta none Ta compat
+.It Sx \&RS Ta 1 Ta current Ta part Ta compat
+.It Sx \&SH Ta >0 Ta next-line Ta section Ta \&
+.It Sx \&SS Ta >0 Ta next-line Ta sub-section Ta \&
+.It Sx \&TP Ta n Ta next-line Ta paragraph Ta \&
+.It Sx \&UE Ta 0 Ta current Ta none Ta compat
+.It Sx \&UR Ta 1 Ta current Ta part Ta compat
+.El
+.Pp
+Macros marked
+.Qq compat
+are as mentioned in
+.Sx Line Macros .
+.Pp
+If a block macro is next-line scoped, it may only be followed by in-line
+macros for decorating text.
+.Ss Font handling
+In
+.Nm
+documents, both
+.Sx Physical markup
+macros and
+.Xr roff 7
+.Ql \ef
+font escape sequences can be used to choose fonts.
+In text lines, the effect of manual font selection by escape sequences
+only lasts until the next macro invocation; in macro lines, it only lasts
+until the end of the macro scope.
+Note that macros like
+.Sx \&BR
+open and close a font scope for each argument.
+.Sh COMPATIBILITY
+This section mentions some areas of questionable portability between
+implementations of the
+.Nm
+language.
+More incompatibilities exist.
+.Pp
+.Bl -dash -compact
+.It
+Do not depend on
+.Sx \&SH
+or
+.Sx \&SS
+to close out a literal context opened with
+.Sx \&nf .
+This behaviour may not be portable.
+.It
+troff suppresses a newline before
+.Sq \(aq
+macro output; in mandoc, it is an alias for the standard
+.Sq \&.
+control character.
+.It
+In page header lines, GNU troff versions up to and including 1.21
+only print
+.Ar volume
+names explicitly specified in the
+.Sx \&TH
+macro; mandoc and newer groff print the default volume name
+corresponding to the
+.Ar section
+number when no
+.Ar volume
+is given, like in
+.Xr mdoc 7 .
+.El
+.Pp
+The
+.Sx EE ,
+.Sx EX ,
+.Sx OP ,
+.Sx UE ,
+and
+.Sx UR
+macros are part of the GNU extended
+.Nm
+macro set, and may not be portable to non-GNU troff implementations.
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr mandoc 1 ,
+.Xr eqn 7 ,
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr roff 7 ,
+.Xr tbl 7
+.Sh HISTORY
+The
+.Nm
+language first appeared as a macro package for the roff typesetting
+system in
+.At v7 .
+It was later rewritten by James Clark as a macro package for groff.
+Eric S. Raymond wrote the extended
+.Nm
+macros for groff in 2007.
+The stand-alone implementation that is part of the
+.Xr mandoc 1
+utility written by Kristaps Dzonsons appeared in
+.Ox 4.6 .
+.Sh AUTHORS
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
+.Sh CAVEATS
+Do not use this language.
+Use
+.Xr mdoc 7 ,
+instead.
diff --git a/contrib/mdocml/man.c b/contrib/mdocml/man.c
new file mode 100644
index 0000000..75a6350
--- /dev/null
+++ b/contrib/mdocml/man.c
@@ -0,0 +1,704 @@
+/* $Id: man.c,v 1.137 2014/08/01 21:24:17 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2011 Joerg Sonnenberger <joerg@netbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libman.h"
+#include "libmandoc.h"
+
+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", "sp", "nf",
+ "fi", "RE", "RS", "DT",
+ "UC", "PD", "AT", "in",
+ "ft", "OP", "EX", "EE",
+ "UR", "UE", "ll"
+ };
+
+const char * const *man_macronames = __man_macronames;
+
+static struct man_node *man_node_alloc(struct man *, int, int,
+ enum man_type, enum mant);
+static int man_node_append(struct man *,
+ struct man_node *);
+static void man_node_free(struct man_node *);
+static void man_node_unlink(struct man *,
+ struct man_node *);
+static int man_ptext(struct man *, int, char *, int);
+static int man_pmacro(struct man *, int, char *, int);
+static void man_free1(struct man *);
+static void man_alloc1(struct man *);
+static int man_descope(struct man *, int, int);
+
+
+const struct man_node *
+man_node(const struct man *man)
+{
+
+ return(man->first);
+}
+
+const struct man_meta *
+man_meta(const struct man *man)
+{
+
+ return(&man->meta);
+}
+
+void
+man_reset(struct man *man)
+{
+
+ man_free1(man);
+ man_alloc1(man);
+}
+
+void
+man_free(struct man *man)
+{
+
+ man_free1(man);
+ free(man);
+}
+
+struct man *
+man_alloc(struct roff *roff, struct mparse *parse, int quick)
+{
+ struct man *p;
+
+ p = mandoc_calloc(1, sizeof(struct man));
+
+ man_hash_init();
+ p->parse = parse;
+ p->quick = quick;
+ p->roff = roff;
+
+ man_alloc1(p);
+ return(p);
+}
+
+int
+man_endparse(struct man *man)
+{
+
+ return(man_macroend(man));
+}
+
+int
+man_parseln(struct man *man, int ln, char *buf, int offs)
+{
+
+ man->flags |= MAN_NEWLINE;
+
+ return (roff_getcontrol(man->roff, buf, &offs) ?
+ man_pmacro(man, ln, buf, offs) :
+ man_ptext(man, ln, buf, offs));
+}
+
+static void
+man_free1(struct man *man)
+{
+
+ if (man->first)
+ man_node_delete(man, man->first);
+ if (man->meta.title)
+ free(man->meta.title);
+ if (man->meta.source)
+ free(man->meta.source);
+ if (man->meta.date)
+ free(man->meta.date);
+ if (man->meta.vol)
+ free(man->meta.vol);
+ if (man->meta.msec)
+ free(man->meta.msec);
+}
+
+static void
+man_alloc1(struct man *man)
+{
+
+ memset(&man->meta, 0, sizeof(struct man_meta));
+ man->flags = 0;
+ man->last = mandoc_calloc(1, sizeof(struct man_node));
+ man->first = man->last;
+ man->last->type = MAN_ROOT;
+ man->last->tok = MAN_MAX;
+ man->next = MAN_NEXT_CHILD;
+}
+
+
+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 */
+ }
+
+ assert(p->parent);
+ p->parent->nchild++;
+
+ switch (p->type) {
+ case MAN_BLOCK:
+ if (p->tok == MAN_SH || p->tok == MAN_SS)
+ man->flags &= ~MAN_LITERAL;
+ break;
+ case MAN_HEAD:
+ assert(MAN_BLOCK == p->parent->type);
+ p->parent->head = p;
+ break;
+ case MAN_TAIL:
+ assert(MAN_BLOCK == p->parent->type);
+ p->parent->tail = 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_TBL:
+ /* FALLTHROUGH */
+ case MAN_TEXT:
+ if ( ! man_valid_post(man))
+ return(0);
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static struct man_node *
+man_node_alloc(struct man *man, int line, int pos,
+ enum man_type type, enum mant tok)
+{
+ struct man_node *p;
+
+ p = mandoc_calloc(1, sizeof(struct man_node));
+ p->line = line;
+ p->pos = pos;
+ p->type = type;
+ p->tok = tok;
+
+ if (MAN_NEWLINE & man->flags)
+ p->flags |= MAN_LINE;
+ man->flags &= ~MAN_NEWLINE;
+ return(p);
+}
+
+int
+man_elem_alloc(struct man *man, int line, int pos, enum mant tok)
+{
+ struct man_node *p;
+
+ p = man_node_alloc(man, line, pos, MAN_ELEM, tok);
+ if ( ! man_node_append(man, p))
+ return(0);
+ man->next = MAN_NEXT_CHILD;
+ return(1);
+}
+
+int
+man_tail_alloc(struct man *man, int line, int pos, enum mant tok)
+{
+ struct man_node *p;
+
+ p = man_node_alloc(man, line, pos, MAN_TAIL, tok);
+ if ( ! man_node_append(man, p))
+ return(0);
+ man->next = MAN_NEXT_CHILD;
+ return(1);
+}
+
+int
+man_head_alloc(struct man *man, int line, int pos, enum mant tok)
+{
+ struct man_node *p;
+
+ p = man_node_alloc(man, line, pos, MAN_HEAD, tok);
+ if ( ! man_node_append(man, p))
+ return(0);
+ man->next = MAN_NEXT_CHILD;
+ return(1);
+}
+
+int
+man_body_alloc(struct man *man, int line, int pos, enum mant tok)
+{
+ struct man_node *p;
+
+ p = man_node_alloc(man, line, pos, MAN_BODY, tok);
+ if ( ! man_node_append(man, p))
+ return(0);
+ man->next = MAN_NEXT_CHILD;
+ return(1);
+}
+
+int
+man_block_alloc(struct man *man, int line, int pos, enum mant tok)
+{
+ struct man_node *p;
+
+ p = man_node_alloc(man, line, pos, MAN_BLOCK, tok);
+ if ( ! man_node_append(man, p))
+ return(0);
+ man->next = MAN_NEXT_CHILD;
+ return(1);
+}
+
+int
+man_word_alloc(struct man *man, int line, int pos, const char *word)
+{
+ struct man_node *n;
+
+ n = man_node_alloc(man, line, pos, MAN_TEXT, MAN_MAX);
+ n->string = roff_strdup(man->roff, word);
+
+ if ( ! man_node_append(man, n))
+ return(0);
+
+ man->next = MAN_NEXT_SIBLING;
+ return(1);
+}
+
+/*
+ * Free all of the resources held by a node. This does NOT unlink a
+ * node from its context; for that, see man_node_unlink().
+ */
+static void
+man_node_free(struct man_node *p)
+{
+
+ if (p->string)
+ free(p->string);
+ free(p);
+}
+
+void
+man_node_delete(struct man *man, struct man_node *p)
+{
+
+ while (p->child)
+ man_node_delete(man, p->child);
+
+ man_node_unlink(man, p);
+ man_node_free(p);
+}
+
+int
+man_addeqn(struct man *man, const struct eqn *ep)
+{
+ struct man_node *n;
+
+ n = man_node_alloc(man, ep->ln, ep->pos, MAN_EQN, MAN_MAX);
+ n->eqn = ep;
+
+ if ( ! man_node_append(man, n))
+ return(0);
+
+ man->next = MAN_NEXT_SIBLING;
+ return(man_descope(man, ep->ln, ep->pos));
+}
+
+int
+man_addspan(struct man *man, const struct tbl_span *sp)
+{
+ struct man_node *n;
+
+ n = man_node_alloc(man, sp->line, 0, MAN_TBL, MAN_MAX);
+ n->span = sp;
+
+ if ( ! man_node_append(man, n))
+ return(0);
+
+ man->next = MAN_NEXT_SIBLING;
+ return(man_descope(man, sp->line, 0));
+}
+
+static int
+man_descope(struct man *man, int line, int offs)
+{
+ /*
+ * 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 & man->flags) {
+ man->flags &= ~MAN_ELINE;
+ if ( ! man_unscope(man, man->last->parent))
+ return(0);
+ }
+
+ if ( ! (MAN_BLINE & man->flags))
+ return(1);
+ man->flags &= ~MAN_BLINE;
+
+ if ( ! man_unscope(man, man->last->parent))
+ return(0);
+ return(man_body_alloc(man, line, offs, man->last->tok));
+}
+
+static int
+man_ptext(struct man *man, int line, char *buf, int offs)
+{
+ int i;
+
+ /* Literal free-form text whitespace is preserved. */
+
+ if (MAN_LITERAL & man->flags) {
+ if ( ! man_word_alloc(man, line, offs, buf + offs))
+ return(0);
+ return(man_descope(man, line, offs));
+ }
+
+ for (i = offs; ' ' == buf[i]; i++)
+ /* Skip leading whitespace. */ ;
+
+ /*
+ * Blank lines are ignored right after headings
+ * but add a single vertical space elsewhere.
+ */
+
+ if ('\0' == buf[i]) {
+ /* Allocate a blank entry. */
+ if (MAN_SH != man->last->tok &&
+ MAN_SS != man->last->tok) {
+ if ( ! man_elem_alloc(man, line, offs, MAN_sp))
+ return(0);
+ man->next = MAN_NEXT_SIBLING;
+ }
+ return(1);
+ }
+
+ /*
+ * Warn if the last un-escaped character is whitespace. Then
+ * strip away the remaining spaces (tabs stay!).
+ */
+
+ i = (int)strlen(buf);
+ assert(i);
+
+ if (' ' == buf[i - 1] || '\t' == buf[i - 1]) {
+ if (i > 1 && '\\' != buf[i - 2])
+ mandoc_msg(MANDOCERR_SPACE_EOL, man->parse,
+ line, i - 1, NULL);
+
+ for (--i; i && ' ' == buf[i]; i--)
+ /* Spin back to non-space. */ ;
+
+ /* Jump ahead of escaped whitespace. */
+ i += '\\' == buf[i] ? 2 : 1;
+
+ buf[i] = '\0';
+ }
+
+ if ( ! man_word_alloc(man, line, offs, buf + offs))
+ return(0);
+
+ /*
+ * End-of-sentence check. If the last character is an unescaped
+ * EOS character, then flag the node as being the end of a
+ * sentence. The front-end will know how to interpret this.
+ */
+
+ assert(i);
+ if (mandoc_eos(buf, (size_t)i))
+ man->last->flags |= MAN_EOS;
+
+ return(man_descope(man, line, offs));
+}
+
+static int
+man_pmacro(struct man *man, int ln, char *buf, int offs)
+{
+ char mac[5];
+ struct man_node *n;
+ enum mant tok;
+ int i, ppos;
+ int bline;
+
+ if ('"' == buf[offs]) {
+ mandoc_msg(MANDOCERR_COMMENT_BAD, man->parse,
+ ln, offs, NULL);
+ return(1);
+ } else if ('\0' == buf[offs])
+ return(1);
+
+ ppos = offs;
+
+ /*
+ * Copy the first word into a nil-terminated buffer.
+ * Stop copying when a tab, space, or eoln is encountered.
+ */
+
+ i = 0;
+ while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] &&
+ '\t' != buf[offs])
+ mac[i++] = buf[offs++];
+
+ mac[i] = '\0';
+
+ tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX;
+
+ if (MAN_MAX == tok) {
+ mandoc_msg(MANDOCERR_MACRO, man->parse,
+ ln, ppos, buf + ppos - 1);
+ return(1);
+ }
+
+ /* The macro is sane. Jump to the next word. */
+
+ while (buf[offs] && ' ' == buf[offs])
+ offs++;
+
+ /*
+ * Trailing whitespace. Note that tabs are allowed to be passed
+ * into the parser as "text", so we only warn about spaces here.
+ */
+
+ if ('\0' == buf[offs] && ' ' == buf[offs - 1])
+ mandoc_msg(MANDOCERR_SPACE_EOL, man->parse,
+ ln, offs - 1, NULL);
+
+ /*
+ * Remove prior ELINE macro, as it's being clobbered by a new
+ * macro. Note that NSCOPED macros do not close out ELINE
+ * macros---they don't print text---so we let those slip by.
+ */
+
+ if ( ! (MAN_NSCOPED & man_macros[tok].flags) &&
+ man->flags & MAN_ELINE) {
+ n = man->last;
+ assert(MAN_TEXT != n->type);
+
+ /* Remove repeated NSCOPED macros causing ELINE. */
+
+ if (MAN_NSCOPED & man_macros[n->tok].flags)
+ n = n->parent;
+
+ mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line,
+ n->pos, "%s breaks %s", man_macronames[tok],
+ man_macronames[n->tok]);
+
+ man_node_delete(man, n);
+ man->flags &= ~MAN_ELINE;
+ }
+
+ /*
+ * Remove prior BLINE macro that is being clobbered.
+ */
+ if ((man->flags & MAN_BLINE) &&
+ (MAN_BSCOPE & man_macros[tok].flags)) {
+ n = man->last;
+
+ /* Might be a text node like 8 in
+ * .TP 8
+ * .SH foo
+ */
+ if (MAN_TEXT == n->type)
+ n = n->parent;
+
+ /* Remove element that didn't end BLINE, if any. */
+ if ( ! (MAN_BSCOPE & man_macros[n->tok].flags))
+ n = n->parent;
+
+ assert(MAN_HEAD == n->type);
+ n = n->parent;
+ assert(MAN_BLOCK == n->type);
+ assert(MAN_SCOPED & man_macros[n->tok].flags);
+
+ mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line,
+ n->pos, "%s breaks %s", man_macronames[tok],
+ man_macronames[n->tok]);
+
+ man_node_delete(man, n);
+ man->flags &= ~MAN_BLINE;
+ }
+
+ /* Remember whether we are in next-line scope for a block head. */
+
+ bline = man->flags & MAN_BLINE;
+
+ /* Call to handler... */
+
+ assert(man_macros[tok].fp);
+ if ( ! (*man_macros[tok].fp)(man, tok, ln, ppos, &offs, buf))
+ return(0);
+
+ /* In quick mode (for mandocdb), abort after the NAME section. */
+
+ if (man->quick && MAN_SH == tok) {
+ n = man->last;
+ if (MAN_BODY == n->type &&
+ strcmp(n->prev->child->string, "NAME"))
+ return(2);
+ }
+
+ /*
+ * If we are in a next-line scope for a block head,
+ * close it out now and switch to the body,
+ * unless the next-line scope is allowed to continue.
+ */
+
+ if ( ! bline || man->flags & MAN_ELINE ||
+ man_macros[tok].flags & MAN_NSCOPED)
+ return(1);
+
+ assert(MAN_BLINE & man->flags);
+ man->flags &= ~MAN_BLINE;
+
+ if ( ! man_unscope(man, man->last->parent))
+ return(0);
+ return(man_body_alloc(man, ln, ppos, man->last->tok));
+}
+
+/*
+ * Unlink a node from its context. If "man" is provided, the last parse
+ * point will also be adjusted accordingly.
+ */
+static void
+man_node_unlink(struct man *man, struct man_node *n)
+{
+
+ /* Adjust siblings. */
+
+ if (n->prev)
+ n->prev->next = n->next;
+ if (n->next)
+ n->next->prev = n->prev;
+
+ /* Adjust parent. */
+
+ if (n->parent) {
+ n->parent->nchild--;
+ if (n->parent->child == n)
+ n->parent->child = n->prev ? n->prev : n->next;
+ }
+
+ /* Adjust parse point, if applicable. */
+
+ if (man && man->last == n) {
+ /*XXX: this can occur when bailing from validation. */
+ /*assert(NULL == n->next);*/
+ if (n->prev) {
+ man->last = n->prev;
+ man->next = MAN_NEXT_SIBLING;
+ } else {
+ man->last = n->parent;
+ man->next = MAN_NEXT_CHILD;
+ }
+ }
+
+ if (man && man->first == n)
+ man->first = NULL;
+}
+
+const struct mparse *
+man_mparse(const struct man *man)
+{
+
+ assert(man && man->parse);
+ return(man->parse);
+}
+
+void
+man_deroff(char **dest, const struct man_node *n)
+{
+ char *cp;
+ size_t sz;
+
+ if (MAN_TEXT != n->type) {
+ for (n = n->child; n; n = n->next)
+ man_deroff(dest, n);
+ return;
+ }
+
+ /* Skip leading whitespace and escape sequences. */
+
+ cp = n->string;
+ while ('\0' != *cp) {
+ if ('\\' == *cp) {
+ cp++;
+ mandoc_escape((const char **)&cp, NULL, NULL);
+ } else if (isspace((unsigned char)*cp))
+ cp++;
+ else
+ break;
+ }
+
+ /* Skip trailing whitespace. */
+
+ for (sz = strlen(cp); sz; sz--)
+ if (0 == isspace((unsigned char)cp[sz-1]))
+ break;
+
+ /* Skip empty strings. */
+
+ if (0 == sz)
+ return;
+
+ if (NULL == *dest) {
+ *dest = mandoc_strndup(cp, sz);
+ return;
+ }
+
+ mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
+ free(*dest);
+ *dest = cp;
+}
diff --git a/contrib/mdocml/man.cgi.8 b/contrib/mdocml/man.cgi.8
new file mode 100644
index 0000000..69335e6
--- /dev/null
+++ b/contrib/mdocml/man.cgi.8
@@ -0,0 +1,409 @@
+.\" $Id: man.cgi.8,v 1.9 2014/07/22 18:14:13 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 22 2014 $
+.Dt MAN.CGI 8
+.Os
+.Sh NAME
+.Nm man.cgi
+.Nd CGI program to search and display manual pages
+.Sh DESCRIPTION
+The
+.Nm
+CGI program searches for manual pages on a WWW server
+and displays them to HTTP clients,
+providing functionality equivalent to the
+.Xr apropos 1
+and
+.Xr man 1
+utilities.
+It can use multiple manual trees in parallel.
+.Ss HTML search interface
+At the top of each generated HTML page,
+.Nm
+displays a search form containing these elements:
+.Bl -enum
+.It
+An input box for search queries, expecting
+either a name of a manual page or an
+.Ar expression
+using the syntax described in the
+.Xr apropos 1
+manual; filling this in is required for each search.
+.It
+A
+.Dq Submit
+button to send a search request from the client to the server.
+.It
+A
+.Dq Reset
+button to undo any changes to the input boxes and the dropdown menus
+and reset them to the values contained in the
+.Ev QUERY_STRING .
+.It
+Radio buttons to select pages either by name like in
+.Xr man 1
+or using
+.Xr apropos 1
+queries.
+.It
+A dropdown menu to optionally select a manual section.
+If one is provided, it has the same effect as the
+.Xr man 1
+and
+.Xr apropos 1
+.Fl s
+option.
+Otherwise, pages from all sections are shown.
+.It
+A dropdown menu to optionally select an architecture.
+If one is provided, it has the same effect as the
+.Xr man 1
+and
+.Xr apropos 1
+.Fl S
+option.
+By default, pages for all architectures are shown.
+.It
+A dropdown menu to select a manual tree.
+If the configuration file
+.Pa /var/www/man/manpath.conf
+contains only one manpath, the dropdown menu is not shown.
+By default, the first manpath given in the file is used.
+.El
+.Ss Program output
+The
+.Nm
+program generates five kinds of output pages:
+.Bl -tag -width Ds
+.It The index page.
+This is returned when calling
+.Nm
+without
+.Ev PATH_INFO
+and without a
+.Ev QUERY_STRING .
+It serves as a starting point for using the program
+and shows the search form only.
+.It A list page.
+Lists are returned when searches match more than one manual page.
+The first column shows the names and section numbers of manuals
+as clickable links.
+The second column shows the one-line descriptions of the manuals.
+.It A manual page.
+This output format is used when a search matches exactly one
+manual page, or when a link on a list page or an
+.Ic \&Xr
+link on another manual page is followed.
+.It A no-result page.
+This is shown when a search request returns no results -
+eiher because it violates the query syntax, or because
+the search does not match any manual pages.
+.It \&An error page.
+This cannot happen by merely clicking the
+.Dq Search
+button, but only by manually entering an invalid URI.
+It does not show the search form, but only an error message
+and a link back to the index page.
+.El
+.Ss Setup
+For each manual tree, create one first-level subdirectory below
+.Pa /var/www/man .
+The name of one of these directories is called a
+.Dq manpath
+in the context of
+.Nm .
+Create a single ASCII text file
+.Pa /var/www/man/manpath.conf
+containing the names of these directories, one per line.
+The directory given first is used as the default manpath.
+.Pp
+Inside each of these directories, use the same directory and file
+structure as found below
+.Pa /usr/share/man ,
+that is, second-level subdirectories
+.Pa /var/www/man/*/man1 , /var/www/man/*/man2
+etc. containing source
+.Xr mdoc 7
+and
+.Xr man 7
+manuals with file name extensions matching the section numbers,
+second-level subdirectories
+.Pa /var/www/man/*/cat1 , /var/www/man/*/cat2
+etc. containing preformatted manuals with the file name extension
+.Sq 0 ,
+and optional third-level subdirectories for architectures.
+Use
+.Xr makewhatis 8
+to create a
+.Xr mandoc.db 5
+database inside each manpath.
+.Pp
+Configure your web server to execute CGI programs located in
+.Pa /cgi-bin .
+When using
+.Xr nginx 8 ,
+the
+.Xr slowcgi 8
+proxy daemon is needed to translate FastCGI requests to plain old CGI.
+.Pp
+To compile
+.Nm ,
+first copy
+.Pa cgi.h.example
+to
+.Pa cgi.h
+and edit it according to your needs.
+It contains the following compile-time definitions:
+.Bl -tag -width Ds
+.It Ev COMPAT_OLDURI
+Only useful for running on www.openbsd.org to deal with old URIs containing
+.Qq "manpath=OpenBSD "
+where the blank character has to be translated to a hyphen.
+When compiling for other sites, this definition can be deleted.
+.It Ev CSS_DIR
+An optional path to the directory containing the CSS files,
+to be specified relative to the server's document root,
+and to be specified without a trailing slash.
+When not specified, the CSS files
+are assumed to be in the document root.
+This is used in generated HTML code.
+.It Ev CUSTOMIZE_BEGIN
+A HTML string to be inserted right after opening the
+.Aq BODY
+element.
+.It Ev CUSTOMIZE_TITLE
+An ASCII string to be used for the HTML
+.Aq TITLE
+element.
+.It Ev HTTP_HOST
+The FQDN of the (possibly virtual) host the HTTP server is running on.
+This is used for
+.Ic Location:
+headers in HTTP 303 responses.
+.It Ev MAN_DIR
+A path to the
+.Nm
+data directory to be used instead of
+.Pa /var/www/man ,
+relative to the web server
+.Xr chroot 2
+directory, to be specified without a trailing slash.
+This is prepended to the manpath when opening
+.Xr mandoc.db 5
+and manual page files.
+.El
+.Pp
+After editing
+.Pa cgi.h ,
+run
+.Pp
+.Dl make man.cgi
+.Pp
+and copy the files to the proper locations.
+Reading the
+.Cm installcgi
+target in the
+.Pa Makefile
+can help with that, but do not run it without carefully checking it
+because the directory layouts of web servers vary greatly.
+.Ss URI interface
+.Nm
+uniform resource identifiers are not needed for interactive use,
+but can be useful for deep linking.
+They consist of:
+.Bl -enum
+.It
+The
+.Cm http://
+protocol specifier.
+.It
+The host name and a following slash.
+.It
+The path to the program, normally
+.Pa cgi-bin/man.cgi/ .
+.It
+To show a single page, a slash, the manpath, another slash,
+and the name of the requested file, for example
+.Pa /OpenBSD-current/man1/mandoc.1 .
+.It
+For searches, a query string starting with a question mark
+and consisting of
+.Ar key Ns = Ns Ar value
+pairs, separated by ampersands, for example
+.Pa ?manpath=OpenBSD-current&query=mandoc .
+Supported keys are
+.Cm manpath ,
+.Cm query ,
+.Cm sec ,
+.Cm arch ,
+corresponding to
+.Xr apropos 1
+.Fl M ,
+.Ar expression ,
+.Fl s ,
+.Fl S ,
+respectively, and
+.Cm apropos ,
+which is a boolean parameter to select or deselect the
+.Xr apropos 1
+query mode.
+For backward compatibility with the traditional
+.Nm ,
+.Cm sektion
+is supported as an alias for
+.Cm sec .
+.El
+.Ss Restricted character set
+For security reasons, in particular to prevent cross site scripting
+attacks, some strings used by
+.Nm
+can only contain the following characters:
+.Pp
+.Bl -dash -compact -offset indent
+.It
+lower case and upper case ASCII letters
+.It
+the ten decimal digits
+.It
+the dash
+.Pq Sq -
+.It
+the dot
+.Pq Sq \&.
+.It
+the slash
+.Pq Sq /
+.It
+the underscore
+.Pq Sq _
+.El
+.Pp
+In particular, this applies to the
+.Ev SCRIPT_NAME ,
+to all manpaths, and to all architecture names.
+.Sh ENVIRONMENT
+The web server may pass the following CGI variables to
+.Nm :
+.Bl -tag -width Ds
+.It Ev PATH_INFO
+The final part of the URI path passed from the client to the server,
+starting after the
+.Ev SCRIPT_NAME
+and ending before the
+.Ev QUERY_STRING .
+It is used by the
+.Cm show
+page to aquire the manpath and filename it needs.
+.It Ev QUERY_STRING
+The HTTP query string passed from the client to the server.
+It is the final part of the URI, after the question mark.
+It is used by the
+.Cm search
+page to acquire the named parameters it needs.
+.It Ev SCRIPT_NAME
+The path to the
+.Nm
+binary relative to the server root, usually
+.Pa /cgi-bin/man.cgi .
+This is used for generating URIs to be embedded
+in generated HTML code and HTTP headers.
+If this contains any character not contained in the
+.Sx Restricted character set ,
+.Nm
+reports an internal server error and exits without doing anything.
+.El
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa /var/www
+Default web server
+.Xr chroot 2
+directory.
+All the following paths are specified relative to this directory.
+.It Pa /cgi-bin/man.cgi
+The path to the
+.Nm
+program relative to the server root.
+Can be overridden by
+.Ev SCRIPT_NAME .
+.It Pa /htdocs
+The path to the server document root relative to the server root.
+This is part of the web server configuration and not specific to
+.Nm .
+.It Pa /htdocs/man-cgi.css
+A style sheet for general
+.Nm
+styling, referenced from each generated HTML page.
+.It Pa /htdocs/man.css
+A style sheet for
+.Xr mandoc 1
+HTML styling, referenced from each generated HTML page after
+.Pa man-cgi.css .
+.It Pa /man
+Default
+.Nm
+data directory containing all the manual trees.
+Can be overridden by
+.Ev MAN_DIR .
+.It Pa /man/mandoc/man1/apropos.1 , /man/mandoc/man8/man.cgi.8
+Manual pages documenting
+.Nm
+itself, linked from the index page.
+.It Pa /man/manpath.conf
+The list of available manpaths, one per line.
+If any of the lines in this file contains a slash
+.Pq Sq /
+or any character not contained in the
+.Sx Restricted character set ,
+.Nm
+reports an internal server error and exits without doing anything.
+.It Pa /man/OpenBSD-current/man1/mandoc.1
+An example
+.Xr mdoc 7
+source file located below the
+.Dq OpenBSD-current
+manpath.
+.El
+.Sh COMPATIBILITY
+The
+.Nm
+CGI program is call-compatible with queries from the traditional
+.Pa man.cgi
+script by Wolfram Schneider.
+However, the output may not be quite the same.
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr mandoc.db 5 ,
+.Xr makewhatis 8 ,
+.Xr slowcgi 8
+.Sh HISTORY
+A version of
+.Nm
+based on
+.Xr mandoc 1
+first appeared in mdocml-1.12.1 (March 2012).
+The current SQLite3-based version first appeared in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and ported to the SQLite3-based
+.Xr mandoc.db 5
+backend by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/man.h b/contrib/mdocml/man.h
new file mode 100644
index 0000000..aa80b67
--- /dev/null
+++ b/contrib/mdocml/man.h
@@ -0,0 +1,121 @@
+/* $Id: man.h,v 1.65 2014/06/20 23:02:31 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef MAN_H
+#define MAN_H
+
+enum mant {
+ MAN_br = 0,
+ MAN_TH,
+ MAN_SH,
+ MAN_SS,
+ MAN_TP,
+ MAN_LP,
+ MAN_PP,
+ MAN_P,
+ MAN_IP,
+ MAN_HP,
+ MAN_SM,
+ MAN_SB,
+ MAN_BI,
+ MAN_IB,
+ MAN_BR,
+ MAN_RB,
+ MAN_R,
+ MAN_B,
+ MAN_I,
+ MAN_IR,
+ MAN_RI,
+ MAN_na,
+ MAN_sp,
+ MAN_nf,
+ MAN_fi,
+ MAN_RE,
+ MAN_RS,
+ MAN_DT,
+ MAN_UC,
+ MAN_PD,
+ MAN_AT,
+ MAN_in,
+ MAN_ft,
+ MAN_OP,
+ MAN_EX,
+ MAN_EE,
+ MAN_UR,
+ MAN_UE,
+ MAN_ll,
+ MAN_MAX
+};
+
+enum man_type {
+ MAN_TEXT,
+ MAN_ELEM,
+ MAN_ROOT,
+ MAN_BLOCK,
+ MAN_HEAD,
+ MAN_BODY,
+ MAN_TAIL,
+ MAN_TBL,
+ MAN_EQN
+};
+
+struct man_meta {
+ char *msec; /* `TH' section (1, 3p, etc.) */
+ char *date; /* `TH' normalised date */
+ char *vol; /* `TH' volume */
+ char *title; /* `TH' title (e.g., FOO) */
+ char *source; /* `TH' source (e.g., GNU) */
+ int hasbody; /* document is not empty */
+};
+
+struct man_node {
+ struct man_node *parent; /* parent AST node */
+ struct man_node *child; /* first child AST node */
+ struct man_node *next; /* sibling AST node */
+ struct man_node *prev; /* prior sibling AST node */
+ int nchild; /* number children */
+ int line;
+ int pos;
+ enum mant tok; /* tok or MAN__MAX if none */
+ int flags;
+#define MAN_VALID (1 << 0) /* has been validated */
+#define MAN_EOS (1 << 2) /* at sentence boundary */
+#define MAN_LINE (1 << 3) /* first macro/text on line */
+ enum man_type type; /* AST node type */
+ char *string; /* TEXT node argument */
+ struct man_node *head; /* BLOCK node HEAD ptr */
+ struct man_node *tail; /* BLOCK node TAIL ptr */
+ struct man_node *body; /* BLOCK node BODY ptr */
+ const struct tbl_span *span; /* TBL */
+ const struct eqn *eqn; /* EQN */
+};
+
+/* Names of macros. Index is enum mant. */
+extern const char *const *man_macronames;
+
+__BEGIN_DECLS
+
+struct man;
+
+const struct man_node *man_node(const struct man *);
+const struct man_meta *man_meta(const struct man *);
+const struct mparse *man_mparse(const struct man *);
+void man_deroff(char **, const struct man_node *);
+
+__END_DECLS
+
+#endif /*!MAN_H*/
diff --git a/contrib/mdocml/man_hash.c b/contrib/mdocml/man_hash.c
new file mode 100644
index 0000000..ab88722
--- /dev/null
+++ b/contrib/mdocml/man_hash.c
@@ -0,0 +1,105 @@
+/* $Id: man_hash.c,v 1.27 2014/04/20 16:46:04 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libman.h"
+
+#define HASH_DEPTH 6
+
+#define HASH_ROW(x) do { \
+ if (isupper((unsigned char)(x))) \
+ (x) -= 65; \
+ else \
+ (x) -= 97; \
+ (x) *= HASH_DEPTH; \
+ } while (/* CONSTCOND */ 0)
+
+/*
+ * Lookup table is indexed first by lower-case first letter (plus one
+ * for the period, which is stored in the last row), then by lower or
+ * uppercase second letter. Buckets correspond to the index of the
+ * macro (the integer value of the enum stored as a char to save a bit
+ * of space).
+ */
+static unsigned char table[26 * HASH_DEPTH];
+
+
+/*
+ * 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));
+
+ assert(MAN_MAX < UCHAR_MAX);
+
+ for (i = 0; i < (int)MAN_MAX; i++) {
+ x = man_macronames[i][0];
+
+ assert(isalpha((unsigned char)x));
+
+ HASH_ROW(x);
+
+ for (j = 0; j < HASH_DEPTH; j++)
+ if (UCHAR_MAX == table[x + j]) {
+ table[x + j] = (unsigned char)i;
+ break;
+ }
+
+ assert(j < HASH_DEPTH);
+ }
+}
+
+enum mant
+man_hash_find(const char *tmp)
+{
+ int x, y, i;
+ enum mant tok;
+
+ if ('\0' == (x = tmp[0]))
+ return(MAN_MAX);
+ if ( ! (isalpha((unsigned char)x)))
+ return(MAN_MAX);
+
+ HASH_ROW(x);
+
+ for (i = 0; i < HASH_DEPTH; i++) {
+ if (UCHAR_MAX == (y = table[x + i]))
+ return(MAN_MAX);
+
+ tok = (enum mant)y;
+ if (0 == strcmp(tmp, man_macronames[tok]))
+ return(tok);
+ }
+
+ return(MAN_MAX);
+}
diff --git a/contrib/mdocml/man_html.c b/contrib/mdocml/man_html.c
new file mode 100644
index 0000000..9689cc2
--- /dev/null
+++ b/contrib/mdocml/man_html.c
@@ -0,0 +1,698 @@
+/* $Id: man_html.c,v 1.96 2014/08/01 19:25:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "html.h"
+#include "man.h"
+#include "main.h"
+
+/* TODO: preserve ident widths. */
+/* FIXME: have PD set the default vspace width. */
+
+#define INDENT 5
+
+#define MAN_ARGS const struct man_meta *man, \
+ const struct man_node *n, \
+ struct mhtml *mh, \
+ struct html *h
+
+struct mhtml {
+ int fl;
+#define MANH_LITERAL (1 << 0) /* literal context */
+};
+
+struct htmlman {
+ int (*pre)(MAN_ARGS);
+ int (*post)(MAN_ARGS);
+};
+
+static void print_bvspace(struct html *,
+ const struct man_node *);
+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_B_pre(MAN_ARGS);
+static int man_HP_pre(MAN_ARGS);
+static int man_IP_pre(MAN_ARGS);
+static int man_I_pre(MAN_ARGS);
+static int man_OP_pre(MAN_ARGS);
+static int man_PP_pre(MAN_ARGS);
+static int man_RS_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 int man_UR_pre(MAN_ARGS);
+static int man_alt_pre(MAN_ARGS);
+static int man_br_pre(MAN_ARGS);
+static int man_ign_pre(MAN_ARGS);
+static int man_in_pre(MAN_ARGS);
+static int man_literal_pre(MAN_ARGS);
+static void man_root_post(MAN_ARGS);
+static void man_root_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_SM_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 */
+ { man_ign_pre, NULL }, /* na */
+ { man_br_pre, NULL }, /* sp */
+ { man_literal_pre, NULL }, /* nf */
+ { man_literal_pre, NULL }, /* fi */
+ { NULL, NULL }, /* RE */
+ { man_RS_pre, NULL }, /* RS */
+ { man_ign_pre, NULL }, /* DT */
+ { man_ign_pre, NULL }, /* UC */
+ { man_ign_pre, NULL }, /* PD */
+ { man_ign_pre, NULL }, /* AT */
+ { man_in_pre, NULL }, /* in */
+ { man_ign_pre, NULL }, /* ft */
+ { man_OP_pre, NULL }, /* OP */
+ { man_literal_pre, NULL }, /* EX */
+ { man_literal_pre, NULL }, /* EE */
+ { man_UR_pre, NULL }, /* UR */
+ { NULL, NULL }, /* UE */
+ { man_ign_pre, NULL }, /* ll */
+};
+
+
+/*
+ * Printing leading vertical space before a block.
+ * This is used for the paragraph macros.
+ * The rules are pretty simple, since there's very little nesting going
+ * on here. Basically, if we're the first within another block (SS/SH),
+ * then don't emit vertical space. If we are (RS), then do. If not the
+ * first, print it.
+ */
+static void
+print_bvspace(struct html *h, const struct man_node *n)
+{
+
+ if (n->body && n->body->child)
+ if (MAN_TBL == n->body->child->type)
+ return;
+
+ if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
+ if (NULL == n->prev)
+ return;
+
+ print_otag(h, TAG_P, 0, NULL);
+}
+
+void
+html_man(void *arg, const struct man *man)
+{
+ struct mhtml mh;
+
+ memset(&mh, 0, sizeof(struct mhtml));
+ print_man(man_meta(man), man_node(man), &mh, (struct html *)arg);
+ putchar('\n');
+}
+
+static void
+print_man(MAN_ARGS)
+{
+ struct tag *t, *tt;
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "mandoc");
+
+ if ( ! (HTML_FRAGMENT & h->oflags)) {
+ print_gen_decls(h);
+ t = print_otag(h, TAG_HTML, 0, NULL);
+ tt = print_otag(h, TAG_HEAD, 0, NULL);
+ print_man_head(man, n, mh, h);
+ print_tagq(h, tt);
+ print_otag(h, TAG_BODY, 0, NULL);
+ print_otag(h, TAG_DIV, 1, &tag);
+ } else
+ t = print_otag(h, TAG_DIV, 1, &tag);
+
+ print_man_nodelist(man, n, mh, h);
+ print_tagq(h, t);
+}
+
+static void
+print_man_head(MAN_ARGS)
+{
+
+ print_gen_head(h);
+ assert(man->title);
+ assert(man->msec);
+ bufcat_fmt(h, "%s(%s)", man->title, man->msec);
+ print_otag(h, TAG_TITLE, 0, NULL);
+ print_text(h, h->buf);
+}
+
+static void
+print_man_nodelist(MAN_ARGS)
+{
+
+ print_man_node(man, n, mh, h);
+ if (n->next)
+ print_man_nodelist(man, n->next, mh, h);
+}
+
+static void
+print_man_node(MAN_ARGS)
+{
+ int child;
+ struct tag *t;
+
+ child = 1;
+ t = h->tags.head;
+
+ switch (n->type) {
+ case MAN_ROOT:
+ man_root_pre(man, n, mh, h);
+ break;
+ case MAN_TEXT:
+ /*
+ * If we have a blank line, output a vertical space.
+ * If we have a space as the first character, break
+ * before printing the line's data.
+ */
+ if ('\0' == *n->string) {
+ print_otag(h, TAG_P, 0, NULL);
+ return;
+ }
+
+ if (' ' == *n->string && MAN_LINE & n->flags)
+ print_otag(h, TAG_BR, 0, NULL);
+ else if (MANH_LITERAL & mh->fl && n->prev)
+ print_otag(h, TAG_BR, 0, NULL);
+
+ print_text(h, n->string);
+ return;
+ case MAN_EQN:
+ print_eqn(h, n->eqn);
+ break;
+ case MAN_TBL:
+ /*
+ * This will take care of initialising all of the table
+ * state data for the first table, then tearing it down
+ * for the last one.
+ */
+ print_tbl(h, n->span);
+ return;
+ default:
+ /*
+ * Close out scope of font prior to opening a macro
+ * scope.
+ */
+ if (HTMLFONT_NONE != h->metac) {
+ h->metal = h->metac;
+ h->metac = HTMLFONT_NONE;
+ }
+
+ /*
+ * Close out the current table, if it's open, and unset
+ * the "meta" table state. This will be reopened on the
+ * next table element.
+ */
+ if (h->tblt) {
+ print_tblclose(h);
+ t = h->tags.head;
+ }
+ if (mans[n->tok].pre)
+ child = (*mans[n->tok].pre)(man, n, mh, h);
+ break;
+ }
+
+ if (child && n->child)
+ print_man_nodelist(man, n->child, mh, h);
+
+ /* This will automatically close out any font scope. */
+ print_stagq(h, t);
+
+ switch (n->type) {
+ case MAN_ROOT:
+ man_root_post(man, n, mh, h);
+ break;
+ case MAN_EQN:
+ break;
+ default:
+ if (mans[n->tok].post)
+ (*mans[n->tok].post)(man, n, mh, 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);
+}
+
+static void
+man_root_pre(MAN_ARGS)
+{
+ struct htmlpair tag[3];
+ struct tag *t, *tt;
+ char *title;
+
+ assert(man->title);
+ assert(man->msec);
+ mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
+
+ PAIR_SUMMARY_INIT(&tag[0], "Document Header");
+ PAIR_CLASS_INIT(&tag[1], "head");
+ PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
+ t = print_otag(h, TAG_TABLE, 3, tag);
+ PAIR_INIT(&tag[0], ATTR_WIDTH, "30%");
+ print_otag(h, TAG_COL, 1, tag);
+ print_otag(h, TAG_COL, 1, tag);
+ print_otag(h, TAG_COL, 1, tag);
+
+ print_otag(h, TAG_TBODY, 0, NULL);
+
+ tt = print_otag(h, TAG_TR, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag[0], "head-ltitle");
+ print_otag(h, TAG_TD, 1, tag);
+ print_text(h, title);
+ print_stagq(h, tt);
+
+ PAIR_CLASS_INIT(&tag[0], "head-vol");
+ PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
+ print_otag(h, TAG_TD, 2, tag);
+ if (NULL != man->vol)
+ print_text(h, man->vol);
+ print_stagq(h, tt);
+
+ PAIR_CLASS_INIT(&tag[0], "head-rtitle");
+ PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
+ print_otag(h, TAG_TD, 2, tag);
+ print_text(h, title);
+ print_tagq(h, t);
+ free(title);
+}
+
+static void
+man_root_post(MAN_ARGS)
+{
+ struct htmlpair tag[3];
+ struct tag *t, *tt;
+
+ PAIR_SUMMARY_INIT(&tag[0], "Document Footer");
+ PAIR_CLASS_INIT(&tag[1], "foot");
+ PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
+ t = print_otag(h, TAG_TABLE, 3, tag);
+ PAIR_INIT(&tag[0], ATTR_WIDTH, "50%");
+ print_otag(h, TAG_COL, 1, tag);
+ print_otag(h, TAG_COL, 1, tag);
+
+ tt = print_otag(h, TAG_TR, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag[0], "foot-date");
+ print_otag(h, TAG_TD, 1, tag);
+
+ assert(man->date);
+ print_text(h, man->date);
+ print_stagq(h, tt);
+
+ PAIR_CLASS_INIT(&tag[0], "foot-os");
+ PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
+ print_otag(h, TAG_TD, 2, tag);
+
+ if (man->source)
+ print_text(h, man->source);
+ print_tagq(h, t);
+}
+
+
+static int
+man_br_pre(MAN_ARGS)
+{
+ struct roffsu su;
+ struct htmlpair tag;
+
+ SCALE_VS_INIT(&su, 1);
+
+ if (MAN_sp == n->tok) {
+ if (NULL != (n = n->child))
+ if ( ! a2roffsu(n->string, &su, SCALE_VS))
+ SCALE_VS_INIT(&su, atoi(n->string));
+ } else
+ su.scale = 0.0;
+
+ bufinit(h);
+ bufcat_su(h, "height", &su);
+ PAIR_STYLE_INIT(&tag, h);
+ print_otag(h, TAG_DIV, 1, &tag);
+
+ /* So the div isn't empty: */
+ print_text(h, "\\~");
+
+ return(0);
+}
+
+static int
+man_SH_pre(MAN_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MAN_BLOCK == n->type) {
+ mh->fl &= ~MANH_LITERAL;
+ PAIR_CLASS_INIT(&tag, "section");
+ print_otag(h, TAG_DIV, 1, &tag);
+ return(1);
+ } else if (MAN_BODY == n->type)
+ return(1);
+
+ print_otag(h, TAG_H1, 0, NULL);
+ return(1);
+}
+
+static int
+man_alt_pre(MAN_ARGS)
+{
+ const struct man_node *nn;
+ int i, savelit;
+ enum htmltag fp;
+ struct tag *t;
+
+ if ((savelit = mh->fl & MANH_LITERAL))
+ print_otag(h, TAG_BR, 0, NULL);
+
+ mh->fl &= ~MANH_LITERAL;
+
+ for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+ t = NULL;
+ switch (n->tok) {
+ case MAN_BI:
+ fp = i % 2 ? TAG_I : TAG_B;
+ break;
+ case MAN_IB:
+ fp = i % 2 ? TAG_B : TAG_I;
+ break;
+ case MAN_RI:
+ fp = i % 2 ? TAG_I : TAG_MAX;
+ break;
+ case MAN_IR:
+ fp = i % 2 ? TAG_MAX : TAG_I;
+ break;
+ case MAN_BR:
+ fp = i % 2 ? TAG_MAX : TAG_B;
+ break;
+ case MAN_RB:
+ fp = i % 2 ? TAG_B : TAG_MAX;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ if (i)
+ h->flags |= HTML_NOSPACE;
+
+ if (TAG_MAX != fp)
+ t = print_otag(h, fp, 0, NULL);
+
+ print_man_node(man, nn, mh, h);
+
+ if (t)
+ print_tagq(h, t);
+ }
+
+ if (savelit)
+ mh->fl |= MANH_LITERAL;
+
+ return(0);
+}
+
+static int
+man_SM_pre(MAN_ARGS)
+{
+
+ print_otag(h, TAG_SMALL, 0, NULL);
+ if (MAN_SB == n->tok)
+ print_otag(h, TAG_B, 0, NULL);
+ return(1);
+}
+
+static int
+man_SS_pre(MAN_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MAN_BLOCK == n->type) {
+ mh->fl &= ~MANH_LITERAL;
+ PAIR_CLASS_INIT(&tag, "subsection");
+ print_otag(h, TAG_DIV, 1, &tag);
+ return(1);
+ } else if (MAN_BODY == n->type)
+ return(1);
+
+ print_otag(h, TAG_H2, 0, NULL);
+ return(1);
+}
+
+static int
+man_PP_pre(MAN_ARGS)
+{
+
+ if (MAN_HEAD == n->type)
+ return(0);
+ else if (MAN_BLOCK == n->type)
+ print_bvspace(h, n);
+
+ return(1);
+}
+
+static int
+man_IP_pre(MAN_ARGS)
+{
+ const struct man_node *nn;
+
+ if (MAN_BODY == n->type) {
+ print_otag(h, TAG_DD, 0, NULL);
+ return(1);
+ } else if (MAN_HEAD != n->type) {
+ print_otag(h, TAG_DL, 0, NULL);
+ return(1);
+ }
+
+ /* FIXME: width specification. */
+
+ print_otag(h, TAG_DT, 0, NULL);
+
+ /* For IP, only print the first header element. */
+
+ if (MAN_IP == n->tok && n->child)
+ print_man_node(man, n->child, mh, h);
+
+ /* For TP, only print next-line header elements. */
+
+ if (MAN_TP == n->tok) {
+ nn = n->child;
+ while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+ nn = nn->next;
+ while (NULL != nn) {
+ print_man_node(man, nn, mh, h);
+ nn = nn->next;
+ }
+ }
+
+ return(0);
+}
+
+static int
+man_HP_pre(MAN_ARGS)
+{
+ struct htmlpair tag;
+ struct roffsu su;
+ const struct man_node *np;
+
+ if (MAN_HEAD == n->type)
+ return(0);
+ else if (MAN_BLOCK != n->type)
+ return(1);
+
+ np = n->head->child;
+
+ if (NULL == np || ! a2width(np, &su))
+ SCALE_HS_INIT(&su, INDENT);
+
+ bufinit(h);
+
+ print_bvspace(h, n);
+ bufcat_su(h, "margin-left", &su);
+ su.scale = -su.scale;
+ bufcat_su(h, "text-indent", &su);
+ PAIR_STYLE_INIT(&tag, h);
+ print_otag(h, TAG_P, 1, &tag);
+ return(1);
+}
+
+static int
+man_OP_pre(MAN_ARGS)
+{
+ struct tag *tt;
+ struct htmlpair tag;
+
+ print_text(h, "[");
+ h->flags |= HTML_NOSPACE;
+ PAIR_CLASS_INIT(&tag, "opt");
+ tt = print_otag(h, TAG_SPAN, 1, &tag);
+
+ if (NULL != (n = n->child)) {
+ print_otag(h, TAG_B, 0, NULL);
+ print_text(h, n->string);
+ }
+
+ print_stagq(h, tt);
+
+ if (NULL != n && NULL != n->next) {
+ print_otag(h, TAG_I, 0, NULL);
+ print_text(h, n->next->string);
+ }
+
+ print_stagq(h, tt);
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "]");
+ return(0);
+}
+
+static int
+man_B_pre(MAN_ARGS)
+{
+
+ print_otag(h, TAG_B, 0, NULL);
+ return(1);
+}
+
+static int
+man_I_pre(MAN_ARGS)
+{
+
+ print_otag(h, TAG_I, 0, NULL);
+ return(1);
+}
+
+static int
+man_literal_pre(MAN_ARGS)
+{
+
+ if (MAN_fi == n->tok || MAN_EE == n->tok) {
+ print_otag(h, TAG_BR, 0, NULL);
+ mh->fl &= ~MANH_LITERAL;
+ } else
+ mh->fl |= MANH_LITERAL;
+
+ return(0);
+}
+
+static int
+man_in_pre(MAN_ARGS)
+{
+
+ print_otag(h, TAG_BR, 0, NULL);
+ return(0);
+}
+
+static int
+man_ign_pre(MAN_ARGS)
+{
+
+ return(0);
+}
+
+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);
+ if (n->head->child)
+ a2width(n->head->child, &su);
+
+ bufinit(h);
+ bufcat_su(h, "margin-left", &su);
+ PAIR_STYLE_INIT(&tag, h);
+ print_otag(h, TAG_DIV, 1, &tag);
+ return(1);
+}
+
+static int
+man_UR_pre(MAN_ARGS)
+{
+ struct htmlpair tag[2];
+
+ n = n->child;
+ assert(MAN_HEAD == n->type);
+ if (n->nchild) {
+ assert(MAN_TEXT == n->child->type);
+ PAIR_CLASS_INIT(&tag[0], "link-ext");
+ PAIR_HREF_INIT(&tag[1], n->child->string);
+ print_otag(h, TAG_A, 2, tag);
+ }
+
+ assert(MAN_BODY == n->next->type);
+ if (n->next->nchild)
+ n = n->next;
+
+ print_man_nodelist(man, n->child, mh, h);
+
+ return(0);
+}
diff --git a/contrib/mdocml/man_macro.c b/contrib/mdocml/man_macro.c
new file mode 100644
index 0000000..ea45a50
--- /dev/null
+++ b/contrib/mdocml/man_macro.c
@@ -0,0 +1,492 @@
+/* $Id: man_macro.c,v 1.87 2014/07/30 23:01:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2013 Franco Fichtner <franco@lastsummer.de>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libman.h"
+
+enum rew {
+ REW_REWIND,
+ REW_NOHALT,
+ REW_HALT
+};
+
+static int blk_close(MACRO_PROT_ARGS);
+static int blk_exp(MACRO_PROT_ARGS);
+static int blk_imp(MACRO_PROT_ARGS);
+static int in_line_eoln(MACRO_PROT_ARGS);
+static int man_args(struct man *, int,
+ int *, char *, char **);
+
+static int rew_scope(enum man_type,
+ struct man *, enum mant);
+static enum rew rew_dohalt(enum mant, enum man_type,
+ const struct man_node *);
+static enum rew rew_block(enum mant, enum man_type,
+ const struct man_node *);
+
+const struct man_macro __man_macros[MAN_MAX] = {
+ { in_line_eoln, MAN_NSCOPED }, /* br */
+ { in_line_eoln, MAN_BSCOPE }, /* TH */
+ { blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SH */
+ { blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SS */
+ { blk_imp, MAN_BSCOPE | MAN_SCOPED | MAN_FSCOPED }, /* TP */
+ { blk_imp, MAN_BSCOPE }, /* LP */
+ { blk_imp, MAN_BSCOPE }, /* PP */
+ { blk_imp, MAN_BSCOPE }, /* P */
+ { blk_imp, MAN_BSCOPE }, /* IP */
+ { blk_imp, MAN_BSCOPE }, /* 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, MAN_NSCOPED }, /* na */
+ { in_line_eoln, MAN_NSCOPED }, /* sp */
+ { in_line_eoln, MAN_BSCOPE }, /* nf */
+ { in_line_eoln, MAN_BSCOPE }, /* fi */
+ { blk_close, 0 }, /* RE */
+ { blk_exp, MAN_BSCOPE | MAN_EXPLICIT }, /* RS */
+ { in_line_eoln, 0 }, /* DT */
+ { in_line_eoln, 0 }, /* UC */
+ { in_line_eoln, 0 }, /* PD */
+ { in_line_eoln, 0 }, /* AT */
+ { in_line_eoln, 0 }, /* in */
+ { in_line_eoln, 0 }, /* ft */
+ { in_line_eoln, 0 }, /* OP */
+ { in_line_eoln, MAN_BSCOPE }, /* EX */
+ { in_line_eoln, MAN_BSCOPE }, /* EE */
+ { blk_exp, MAN_BSCOPE | MAN_EXPLICIT }, /* UR */
+ { blk_close, 0 }, /* UE */
+ { in_line_eoln, 0 }, /* ll */
+};
+
+const struct man_macro * const man_macros = __man_macros;
+
+
+int
+man_unscope(struct man *man, const struct man_node *to)
+{
+ struct man_node *n;
+
+ man->next = MAN_NEXT_SIBLING;
+ to = to->parent;
+ n = man->last;
+ while (n != to) {
+
+ /* Reached the end of the document? */
+
+ if (to == NULL && ! (n->flags & MAN_VALID)) {
+ if (man->flags & (MAN_BLINE | MAN_ELINE) &&
+ man_macros[n->tok].flags & MAN_SCOPED) {
+ mandoc_vmsg(MANDOCERR_BLK_LINE,
+ man->parse, n->line, n->pos,
+ "EOF breaks %s",
+ man_macronames[n->tok]);
+ if (man->flags & MAN_ELINE)
+ man->flags &= ~MAN_ELINE;
+ else {
+ assert(n->type == MAN_HEAD);
+ n = n->parent;
+ man->flags &= ~MAN_BLINE;
+ }
+ man->last = n;
+ n = n->parent;
+ man_node_delete(man, man->last);
+ continue;
+ }
+ if (n->type == MAN_BLOCK &&
+ man_macros[n->tok].flags & MAN_EXPLICIT)
+ mandoc_msg(MANDOCERR_BLK_NOEND,
+ man->parse, n->line, n->pos,
+ man_macronames[n->tok]);
+ }
+
+ /*
+ * We might delete the man->last node
+ * in the post-validation phase.
+ * Save a pointer to the parent such that
+ * we know where to continue the iteration.
+ */
+ man->last = n;
+ n = n->parent;
+ if ( ! man_valid_post(man))
+ return(0);
+ }
+ return(1);
+}
+
+static enum rew
+rew_block(enum mant 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 enum rew
+rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
+{
+ enum rew c;
+
+ /* We cannot progress beyond the root ever. */
+ if (MAN_ROOT == n->type)
+ return(REW_HALT);
+
+ assert(n->parent);
+
+ /* Normal nodes shouldn't go to the level of the root. */
+ if (MAN_ROOT == n->parent->type)
+ return(REW_REWIND);
+
+ /* Already-validated nodes should be closed out. */
+ if (MAN_VALID & n->flags)
+ return(REW_NOHALT);
+
+ /* First: rewind to ourselves. */
+ if (type == n->type && tok == n->tok) {
+ if (MAN_EXPLICIT & man_macros[n->tok].flags)
+ return(REW_HALT);
+ else
+ return(REW_REWIND);
+ }
+
+ /*
+ * Next follow the implicit scope-smashings as defined by man.7:
+ * section, sub-section, etc.
+ */
+
+ 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:
+ /* Preserve empty paragraphs before RS. */
+ if (0 == n->nchild && (MAN_P == n->tok ||
+ MAN_PP == n->tok || MAN_LP == n->tok))
+ return(REW_HALT);
+ /* 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 *man, enum mant tok)
+{
+ struct man_node *n;
+ enum rew c;
+
+ for (n = man->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. Warn if we're a roff
+ * instruction that's mowing over explicit scopes.
+ */
+ assert(n);
+
+ return(man_unscope(man, n));
+}
+
+
+/*
+ * Close out a generic explicit macro.
+ */
+int
+blk_close(MACRO_PROT_ARGS)
+{
+ enum mant ntok;
+ const struct man_node *nn;
+
+ switch (tok) {
+ case MAN_RE:
+ ntok = MAN_RS;
+ break;
+ case MAN_UE:
+ ntok = MAN_UR;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ for (nn = man->last->parent; nn; nn = nn->parent)
+ if (ntok == nn->tok && MAN_BLOCK == nn->type)
+ break;
+
+ if (NULL == nn) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, man->parse,
+ line, ppos, man_macronames[tok]);
+ if ( ! rew_scope(MAN_BLOCK, man, MAN_PP))
+ return(0);
+ } else
+ man_unscope(man, nn);
+
+ return(1);
+}
+
+int
+blk_exp(MACRO_PROT_ARGS)
+{
+ struct man_node *n;
+ int la;
+ char *p;
+
+ /* Close out prior implicit scopes. */
+
+ if ( ! rew_scope(MAN_BLOCK, man, tok))
+ return(0);
+
+ if ( ! man_block_alloc(man, line, ppos, tok))
+ return(0);
+ if ( ! man_head_alloc(man, line, ppos, tok))
+ return(0);
+
+ for (;;) {
+ la = *pos;
+ if ( ! man_args(man, line, pos, buf, &p))
+ break;
+ if ( ! man_word_alloc(man, line, la, p))
+ return(0);
+ }
+
+ assert(man);
+ assert(tok != MAN_MAX);
+
+ for (n = man->last; n; n = n->parent) {
+ if (n->tok != tok)
+ continue;
+ assert(MAN_HEAD == n->type);
+ man_unscope(man, n);
+ break;
+ }
+
+ return(man_body_alloc(man, line, ppos, tok));
+}
+
+/*
+ * 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 la;
+ char *p;
+ struct man_node *n;
+
+ /* Close out prior scopes. */
+
+ if ( ! rew_scope(MAN_BODY, man, tok))
+ return(0);
+ if ( ! rew_scope(MAN_BLOCK, man, tok))
+ return(0);
+
+ /* Allocate new block & head scope. */
+
+ if ( ! man_block_alloc(man, line, ppos, tok))
+ return(0);
+ if ( ! man_head_alloc(man, line, ppos, tok))
+ return(0);
+
+ n = man->last;
+
+ /* Add line arguments. */
+
+ for (;;) {
+ la = *pos;
+ if ( ! man_args(man, line, pos, buf, &p))
+ break;
+ if ( ! man_word_alloc(man, 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) {
+ man->flags |= MAN_BLINE;
+ return(1);
+ } else if (n == man->last) {
+ man->flags |= MAN_BLINE;
+ return(1);
+ }
+ }
+
+ if ( ! rew_scope(MAN_HEAD, man, tok))
+ return(0);
+ return(man_body_alloc(man, line, ppos, tok));
+}
+
+int
+in_line_eoln(MACRO_PROT_ARGS)
+{
+ int la;
+ char *p;
+ struct man_node *n;
+
+ if ( ! man_elem_alloc(man, line, ppos, tok))
+ return(0);
+
+ n = man->last;
+
+ for (;;) {
+ la = *pos;
+ if ( ! man_args(man, line, pos, buf, &p))
+ break;
+ if ( ! man_word_alloc(man, line, la, p))
+ return(0);
+ }
+
+ /*
+ * Append MAN_EOS in case the last snipped argument
+ * ends with a dot, e.g. `.IR syslog (3).'
+ */
+
+ if (n != man->last &&
+ mandoc_eos(man->last->string, strlen(man->last->string)))
+ man->last->flags |= MAN_EOS;
+
+ /*
+ * If no arguments are specified and this is MAN_SCOPED (i.e.,
+ * next-line scoped), then set our mode to indicate that we're
+ * waiting for terms to load into our context.
+ */
+
+ if (n == man->last && MAN_SCOPED & man_macros[tok].flags) {
+ assert( ! (MAN_NSCOPED & man_macros[tok].flags));
+ man->flags |= MAN_ELINE;
+ return(1);
+ }
+
+ assert(MAN_ROOT != man->last->type);
+ man->next = MAN_NEXT_SIBLING;
+
+ /*
+ * Rewind our element scope. Note that when TH is pruned, we'll
+ * be back at the root, so make sure that we don't clobber as
+ * its sibling.
+ */
+
+ for ( ; man->last; man->last = man->last->parent) {
+ if (man->last == n)
+ break;
+ if (man->last->type == MAN_ROOT)
+ break;
+ if ( ! man_valid_post(man))
+ return(0);
+ }
+
+ assert(man->last);
+
+ /*
+ * Same here regarding whether we're back at the root.
+ */
+
+ if (man->last->type != MAN_ROOT && ! man_valid_post(man))
+ return(0);
+
+ return(1);
+}
+
+
+int
+man_macroend(struct man *man)
+{
+
+ return(man_unscope(man, man->first));
+}
+
+static int
+man_args(struct man *man, int line, int *pos, char *buf, char **v)
+{
+ char *start;
+
+ assert(*pos);
+ *v = start = buf + *pos;
+ assert(' ' != *start);
+
+ if ('\0' == *start)
+ return(0);
+
+ *v = mandoc_getarg(man->parse, v, line, pos);
+ return(1);
+}
diff --git a/contrib/mdocml/man_term.c b/contrib/mdocml/man_term.c
new file mode 100644
index 0000000..c91c074
--- /dev/null
+++ b/contrib/mdocml/man_term.c
@@ -0,0 +1,1190 @@
+/* $Id: man_term.c,v 1.149 2014/06/20 23:02:31 schwarze Exp $ */
+/*
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "man.h"
+#include "term.h"
+#include "main.h"
+
+#define MAXMARGINS 64 /* maximum number of indented scopes */
+
+struct mtermp {
+ int fl;
+#define MANT_LITERAL (1 << 0)
+ size_t lmargin[MAXMARGINS]; /* margins (incl. visible page) */
+ int lmargincur; /* index of current margin */
+ int lmarginsz; /* actual number of nested margins */
+ size_t offset; /* default offset to visible page */
+ int pardist; /* vert. space before par., unit: [v] */
+};
+
+#define DECL_ARGS struct termp *p, \
+ struct mtermp *mt, \
+ const struct man_node *n, \
+ const struct man_meta *meta
+
+struct termact {
+ int (*pre)(DECL_ARGS);
+ void (*post)(DECL_ARGS);
+ int flags;
+#define MAN_NOTEXT (1 << 0) /* Never has text children. */
+};
+
+static int a2width(const struct termp *, const char *);
+static size_t a2height(const struct termp *, const char *);
+
+static void print_man_nodelist(DECL_ARGS);
+static void print_man_node(DECL_ARGS);
+static void print_man_head(struct termp *, const void *);
+static void print_man_foot(struct termp *, const void *);
+static void print_bvspace(struct termp *,
+ const struct man_node *, int);
+
+static int pre_B(DECL_ARGS);
+static int pre_HP(DECL_ARGS);
+static int pre_I(DECL_ARGS);
+static int pre_IP(DECL_ARGS);
+static int pre_OP(DECL_ARGS);
+static int pre_PD(DECL_ARGS);
+static int pre_PP(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_UR(DECL_ARGS);
+static int pre_alternate(DECL_ARGS);
+static int pre_ft(DECL_ARGS);
+static int pre_ign(DECL_ARGS);
+static int pre_in(DECL_ARGS);
+static int pre_literal(DECL_ARGS);
+static int pre_ll(DECL_ARGS);
+static int pre_sp(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_UR(DECL_ARGS);
+
+static const struct termact termacts[MAN_MAX] = {
+ { pre_sp, NULL, MAN_NOTEXT }, /* br */
+ { NULL, NULL, 0 }, /* TH */
+ { pre_SH, post_SH, 0 }, /* SH */
+ { pre_SS, post_SS, 0 }, /* SS */
+ { pre_TP, post_TP, 0 }, /* TP */
+ { pre_PP, NULL, 0 }, /* LP */
+ { pre_PP, NULL, 0 }, /* PP */
+ { pre_PP, NULL, 0 }, /* P */
+ { pre_IP, post_IP, 0 }, /* IP */
+ { pre_HP, post_HP, 0 }, /* HP */
+ { NULL, NULL, 0 }, /* SM */
+ { pre_B, NULL, 0 }, /* SB */
+ { pre_alternate, NULL, 0 }, /* BI */
+ { pre_alternate, NULL, 0 }, /* IB */
+ { pre_alternate, NULL, 0 }, /* BR */
+ { pre_alternate, NULL, 0 }, /* RB */
+ { NULL, NULL, 0 }, /* R */
+ { pre_B, NULL, 0 }, /* B */
+ { pre_I, NULL, 0 }, /* I */
+ { pre_alternate, NULL, 0 }, /* IR */
+ { pre_alternate, NULL, 0 }, /* RI */
+ { pre_ign, NULL, MAN_NOTEXT }, /* na */
+ { pre_sp, NULL, MAN_NOTEXT }, /* sp */
+ { pre_literal, NULL, 0 }, /* nf */
+ { pre_literal, NULL, 0 }, /* fi */
+ { NULL, NULL, 0 }, /* RE */
+ { pre_RS, post_RS, 0 }, /* RS */
+ { pre_ign, NULL, 0 }, /* DT */
+ { pre_ign, NULL, 0 }, /* UC */
+ { pre_PD, NULL, MAN_NOTEXT }, /* PD */
+ { pre_ign, NULL, 0 }, /* AT */
+ { pre_in, NULL, MAN_NOTEXT }, /* in */
+ { pre_ft, NULL, MAN_NOTEXT }, /* ft */
+ { pre_OP, NULL, 0 }, /* OP */
+ { pre_literal, NULL, 0 }, /* EX */
+ { pre_literal, NULL, 0 }, /* EE */
+ { pre_UR, post_UR, 0 }, /* UR */
+ { NULL, NULL, 0 }, /* UE */
+ { pre_ll, NULL, MAN_NOTEXT }, /* ll */
+};
+
+
+void
+terminal_man(void *arg, const struct man *man)
+{
+ struct termp *p;
+ const struct man_node *n;
+ const struct man_meta *meta;
+ struct mtermp mt;
+
+ p = (struct termp *)arg;
+
+ if (0 == p->defindent)
+ p->defindent = 7;
+
+ p->overstep = 0;
+ p->maxrmargin = p->defrmargin;
+ p->tabwidth = term_len(p, 5);
+
+ if (NULL == p->symtab)
+ p->symtab = mchars_alloc();
+
+ n = man_node(man);
+ meta = man_meta(man);
+
+ term_begin(p, print_man_head, print_man_foot, meta);
+ p->flags |= TERMP_NOSPACE;
+
+ memset(&mt, 0, sizeof(struct mtermp));
+
+ mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
+ mt.offset = term_len(p, p->defindent);
+ mt.pardist = 1;
+
+ if (n->child)
+ print_man_nodelist(p, &mt, n->child, meta);
+
+ term_end(p);
+}
+
+
+static size_t
+a2height(const struct termp *p, const char *cp)
+{
+ struct roffsu su;
+
+ if ( ! a2roffsu(cp, &su, SCALE_VS))
+ SCALE_VS_INIT(&su, atoi(cp));
+
+ return(term_vspan(p, &su));
+}
+
+static int
+a2width(const struct termp *p, const char *cp)
+{
+ struct roffsu su;
+
+ if ( ! a2roffsu(cp, &su, SCALE_BU))
+ return(-1);
+
+ return((int)term_hspan(p, &su));
+}
+
+/*
+ * Printing leading vertical space before a block.
+ * This is used for the paragraph macros.
+ * The rules are pretty simple, since there's very little nesting going
+ * on here. Basically, if we're the first within another block (SS/SH),
+ * then don't emit vertical space. If we are (RS), then do. If not the
+ * first, print it.
+ */
+static void
+print_bvspace(struct termp *p, const struct man_node *n, int pardist)
+{
+ int i;
+
+ term_newln(p);
+
+ if (n->body && n->body->child)
+ if (MAN_TBL == n->body->child->type)
+ return;
+
+ if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
+ if (NULL == n->prev)
+ return;
+
+ for (i = 0; i < pardist; i++)
+ term_vspace(p);
+}
+
+
+static int
+pre_ign(DECL_ARGS)
+{
+
+ return(0);
+}
+
+static int
+pre_ll(DECL_ARGS)
+{
+
+ term_setwidth(p, n->nchild ? n->child->string : NULL);
+ return(0);
+}
+
+static int
+pre_I(DECL_ARGS)
+{
+
+ term_fontrepl(p, TERMFONT_UNDER);
+ return(1);
+}
+
+static int
+pre_literal(DECL_ARGS)
+{
+
+ term_newln(p);
+
+ if (MAN_nf == n->tok || MAN_EX == n->tok)
+ mt->fl |= MANT_LITERAL;
+ else
+ mt->fl &= ~MANT_LITERAL;
+
+ /*
+ * Unlike .IP and .TP, .HP does not have a HEAD.
+ * So in case a second call to term_flushln() is needed,
+ * indentation has to be set up explicitly.
+ */
+ if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) {
+ p->offset = p->rmargin;
+ p->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
+ p->flags |= TERMP_NOSPACE;
+ }
+
+ return(0);
+}
+
+static int
+pre_PD(DECL_ARGS)
+{
+
+ n = n->child;
+ if (0 == n) {
+ mt->pardist = 1;
+ return(0);
+ }
+ assert(MAN_TEXT == n->type);
+ mt->pardist = atoi(n->string);
+ return(0);
+}
+
+static int
+pre_alternate(DECL_ARGS)
+{
+ enum termfont font[2];
+ const struct man_node *nn;
+ int savelit, i;
+
+ switch (n->tok) {
+ case MAN_RB:
+ font[0] = TERMFONT_NONE;
+ font[1] = TERMFONT_BOLD;
+ break;
+ case MAN_RI:
+ font[0] = TERMFONT_NONE;
+ font[1] = TERMFONT_UNDER;
+ break;
+ case MAN_BR:
+ font[0] = TERMFONT_BOLD;
+ font[1] = TERMFONT_NONE;
+ break;
+ case MAN_BI:
+ font[0] = TERMFONT_BOLD;
+ font[1] = TERMFONT_UNDER;
+ break;
+ case MAN_IR:
+ font[0] = TERMFONT_UNDER;
+ font[1] = TERMFONT_NONE;
+ break;
+ case MAN_IB:
+ font[0] = TERMFONT_UNDER;
+ font[1] = TERMFONT_BOLD;
+ break;
+ default:
+ abort();
+ }
+
+ savelit = MANT_LITERAL & mt->fl;
+ mt->fl &= ~MANT_LITERAL;
+
+ for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
+ term_fontrepl(p, font[i]);
+ if (savelit && NULL == nn->next)
+ mt->fl |= MANT_LITERAL;
+ print_man_node(p, mt, nn, meta);
+ if (nn->next)
+ p->flags |= TERMP_NOSPACE;
+ }
+
+ return(0);
+}
+
+static int
+pre_B(DECL_ARGS)
+{
+
+ term_fontrepl(p, TERMFONT_BOLD);
+ return(1);
+}
+
+static int
+pre_OP(DECL_ARGS)
+{
+
+ term_word(p, "[");
+ p->flags |= TERMP_NOSPACE;
+
+ if (NULL != (n = n->child)) {
+ term_fontrepl(p, TERMFONT_BOLD);
+ term_word(p, n->string);
+ }
+ if (NULL != n && NULL != n->next) {
+ term_fontrepl(p, TERMFONT_UNDER);
+ term_word(p, n->next->string);
+ }
+
+ term_fontrepl(p, TERMFONT_NONE);
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "]");
+ return(0);
+}
+
+static int
+pre_ft(DECL_ARGS)
+{
+ const char *cp;
+
+ if (NULL == n->child) {
+ term_fontlast(p);
+ return(0);
+ }
+
+ cp = n->child->string;
+ switch (*cp) {
+ case '4':
+ /* FALLTHROUGH */
+ case '3':
+ /* FALLTHROUGH */
+ case 'B':
+ term_fontrepl(p, TERMFONT_BOLD);
+ break;
+ case '2':
+ /* FALLTHROUGH */
+ case 'I':
+ term_fontrepl(p, TERMFONT_UNDER);
+ break;
+ case 'P':
+ term_fontlast(p);
+ break;
+ case '1':
+ /* FALLTHROUGH */
+ case 'C':
+ /* FALLTHROUGH */
+ case 'R':
+ term_fontrepl(p, TERMFONT_NONE);
+ break;
+ default:
+ break;
+ }
+ return(0);
+}
+
+static int
+pre_in(DECL_ARGS)
+{
+ int len, less;
+ size_t v;
+ const char *cp;
+
+ term_newln(p);
+
+ if (NULL == n->child) {
+ p->offset = mt->offset;
+ return(0);
+ }
+
+ cp = n->child->string;
+ less = 0;
+
+ if ('-' == *cp)
+ less = -1;
+ else if ('+' == *cp)
+ less = 1;
+ else
+ cp--;
+
+ if ((len = a2width(p, ++cp)) < 0)
+ return(0);
+
+ v = (size_t)len;
+
+ if (less < 0)
+ p->offset -= p->offset > v ? v : p->offset;
+ else if (less > 0)
+ p->offset += v;
+ else
+ p->offset = v;
+
+ /* Don't let this creep beyond the right margin. */
+
+ if (p->offset > p->rmargin)
+ p->offset = p->rmargin;
+
+ return(0);
+}
+
+static int
+pre_sp(DECL_ARGS)
+{
+ char *s;
+ size_t i, len;
+ int neg;
+
+ if ((NULL == n->prev && n->parent)) {
+ switch (n->parent->tok) {
+ case MAN_SH:
+ /* FALLTHROUGH */
+ case MAN_SS:
+ /* FALLTHROUGH */
+ case MAN_PP:
+ /* FALLTHROUGH */
+ case MAN_LP:
+ /* FALLTHROUGH */
+ case MAN_P:
+ /* FALLTHROUGH */
+ return(0);
+ default:
+ break;
+ }
+ }
+
+ neg = 0;
+ switch (n->tok) {
+ case MAN_br:
+ len = 0;
+ break;
+ default:
+ if (NULL == n->child) {
+ len = 1;
+ break;
+ }
+ s = n->child->string;
+ if ('-' == *s) {
+ neg = 1;
+ s++;
+ }
+ len = a2height(p, s);
+ break;
+ }
+
+ if (0 == len)
+ term_newln(p);
+ else if (neg)
+ p->skipvsp += len;
+ else
+ for (i = 0; i < len; i++)
+ term_vspace(p);
+
+ return(0);
+}
+
+static int
+pre_HP(DECL_ARGS)
+{
+ size_t len, one;
+ int ival;
+ const struct man_node *nn;
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ print_bvspace(p, n, mt->pardist);
+ return(1);
+ case MAN_BODY:
+ break;
+ default:
+ return(0);
+ }
+
+ if ( ! (MANT_LITERAL & mt->fl)) {
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND;
+ p->trailspace = 2;
+ }
+
+ len = mt->lmargin[mt->lmargincur];
+ ival = -1;
+
+ /* Calculate offset. */
+
+ if (NULL != (nn = n->parent->head->child))
+ if ((ival = a2width(p, nn->string)) >= 0)
+ len = (size_t)ival;
+
+ one = term_len(p, 1);
+ if (len < one)
+ len = one;
+
+ p->offset = mt->offset;
+ p->rmargin = mt->offset + len;
+
+ if (ival >= 0)
+ mt->lmargin[mt->lmargincur] = (size_t)ival;
+
+ return(1);
+}
+
+static void
+post_HP(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MAN_BODY:
+ term_newln(p);
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
+ p->trailspace = 0;
+ p->offset = mt->offset;
+ p->rmargin = p->maxrmargin;
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_PP(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+ print_bvspace(p, n, mt->pardist);
+ break;
+ default:
+ p->offset = mt->offset;
+ break;
+ }
+
+ return(MAN_HEAD != n->type);
+}
+
+static int
+pre_IP(DECL_ARGS)
+{
+ const struct man_node *nn;
+ size_t len;
+ int savelit, ival;
+
+ switch (n->type) {
+ case MAN_BODY:
+ p->flags |= TERMP_NOSPACE;
+ break;
+ case MAN_HEAD:
+ p->flags |= TERMP_NOBREAK;
+ p->trailspace = 1;
+ break;
+ case MAN_BLOCK:
+ print_bvspace(p, n, mt->pardist);
+ /* FALLTHROUGH */
+ default:
+ return(1);
+ }
+
+ len = mt->lmargin[mt->lmargincur];
+ ival = -1;
+
+ /* Calculate the offset from the optional second argument. */
+ if (NULL != (nn = n->parent->head->child))
+ if (NULL != (nn = nn->next))
+ if ((ival = a2width(p, nn->string)) >= 0)
+ len = (size_t)ival;
+
+ switch (n->type) {
+ case MAN_HEAD:
+ /* Handle zero-width lengths. */
+ if (0 == len)
+ len = term_len(p, 1);
+
+ p->offset = mt->offset;
+ p->rmargin = mt->offset + len;
+ if (ival < 0)
+ break;
+
+ /* Set the saved left-margin. */
+ mt->lmargin[mt->lmargincur] = (size_t)ival;
+
+ savelit = MANT_LITERAL & mt->fl;
+ mt->fl &= ~MANT_LITERAL;
+
+ if (n->child)
+ print_man_node(p, mt, n->child, meta);
+
+ if (savelit)
+ mt->fl |= MANT_LITERAL;
+
+ return(0);
+ case MAN_BODY:
+ p->offset = mt->offset + len;
+ p->rmargin = p->maxrmargin > p->offset ?
+ p->maxrmargin : p->offset;
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static void
+post_IP(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MAN_HEAD:
+ term_flushln(p);
+ p->flags &= ~TERMP_NOBREAK;
+ p->trailspace = 0;
+ p->rmargin = p->maxrmargin;
+ break;
+ case MAN_BODY:
+ term_newln(p);
+ p->offset = mt->offset;
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_TP(DECL_ARGS)
+{
+ const struct man_node *nn;
+ size_t len;
+ int savelit, ival;
+
+ switch (n->type) {
+ case MAN_HEAD:
+ p->flags |= TERMP_NOBREAK;
+ p->trailspace = 1;
+ break;
+ case MAN_BODY:
+ p->flags |= TERMP_NOSPACE;
+ break;
+ case MAN_BLOCK:
+ print_bvspace(p, n, mt->pardist);
+ /* FALLTHROUGH */
+ default:
+ return(1);
+ }
+
+ len = (size_t)mt->lmargin[mt->lmargincur];
+ ival = -1;
+
+ /* Calculate offset. */
+
+ if (NULL != (nn = n->parent->head->child))
+ if (nn->string && 0 == (MAN_LINE & nn->flags))
+ if ((ival = a2width(p, nn->string)) >= 0)
+ len = (size_t)ival;
+
+ switch (n->type) {
+ case MAN_HEAD:
+ /* Handle zero-length properly. */
+ if (0 == len)
+ len = term_len(p, 1);
+
+ p->offset = mt->offset;
+ p->rmargin = mt->offset + len;
+
+ savelit = MANT_LITERAL & mt->fl;
+ mt->fl &= ~MANT_LITERAL;
+
+ /* Don't print same-line elements. */
+ nn = n->child;
+ while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+ nn = nn->next;
+
+ while (NULL != nn) {
+ print_man_node(p, mt, nn, meta);
+ nn = nn->next;
+ }
+
+ if (savelit)
+ mt->fl |= MANT_LITERAL;
+ if (ival >= 0)
+ mt->lmargin[mt->lmargincur] = (size_t)ival;
+
+ return(0);
+ case MAN_BODY:
+ p->offset = mt->offset + len;
+ p->rmargin = p->maxrmargin > p->offset ?
+ p->maxrmargin : p->offset;
+ p->trailspace = 0;
+ p->flags &= ~TERMP_NOBREAK;
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static void
+post_TP(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MAN_HEAD:
+ term_flushln(p);
+ break;
+ case MAN_BODY:
+ term_newln(p);
+ p->offset = mt->offset;
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_SS(DECL_ARGS)
+{
+ int i;
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ mt->fl &= ~MANT_LITERAL;
+ mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+ mt->offset = term_len(p, p->defindent);
+ /* 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;
+ for (i = 0; i < mt->pardist; i++)
+ term_vspace(p);
+ break;
+ case MAN_HEAD:
+ term_fontrepl(p, TERMFONT_BOLD);
+ p->offset = term_len(p, 3);
+ break;
+ case MAN_BODY:
+ p->offset = mt->offset;
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static void
+post_SS(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MAN_HEAD:
+ term_newln(p);
+ break;
+ case MAN_BODY:
+ term_newln(p);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_SH(DECL_ARGS)
+{
+ int i;
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ mt->fl &= ~MANT_LITERAL;
+ mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+ mt->offset = term_len(p, p->defindent);
+ /* If following a prior empty `SH', no vspace. */
+ if (n->prev && MAN_SH == n->prev->tok)
+ if (NULL == n->prev->body->child)
+ break;
+ /* If the first macro, no vspae. */
+ if (NULL == n->prev)
+ break;
+ for (i = 0; i < mt->pardist; i++)
+ term_vspace(p);
+ break;
+ case MAN_HEAD:
+ term_fontrepl(p, TERMFONT_BOLD);
+ p->offset = 0;
+ break;
+ case MAN_BODY:
+ p->offset = mt->offset;
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static void
+post_SH(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MAN_HEAD:
+ term_newln(p);
+ break;
+ case MAN_BODY:
+ term_newln(p);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_RS(DECL_ARGS)
+{
+ int ival;
+ size_t sz;
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ term_newln(p);
+ return(1);
+ case MAN_HEAD:
+ return(0);
+ default:
+ break;
+ }
+
+ sz = term_len(p, p->defindent);
+
+ if (NULL != (n = n->parent->head->child))
+ if ((ival = a2width(p, n->string)) >= 0)
+ sz = (size_t)ival;
+
+ mt->offset += sz;
+ p->offset = mt->offset;
+ p->rmargin = p->maxrmargin > p->offset ?
+ p->maxrmargin : p->offset;
+
+ if (++mt->lmarginsz < MAXMARGINS)
+ mt->lmargincur = mt->lmarginsz;
+
+ mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1];
+ return(1);
+}
+
+static void
+post_RS(DECL_ARGS)
+{
+ int ival;
+ size_t sz;
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ return;
+ case MAN_HEAD:
+ return;
+ default:
+ term_newln(p);
+ break;
+ }
+
+ sz = term_len(p, p->defindent);
+
+ if (NULL != (n = n->parent->head->child))
+ if ((ival = a2width(p, n->string)) >= 0)
+ sz = (size_t)ival;
+
+ mt->offset = mt->offset < sz ? 0 : mt->offset - sz;
+ p->offset = mt->offset;
+
+ if (--mt->lmarginsz < MAXMARGINS)
+ mt->lmargincur = mt->lmarginsz;
+}
+
+static int
+pre_UR(DECL_ARGS)
+{
+
+ return (MAN_HEAD != n->type);
+}
+
+static void
+post_UR(DECL_ARGS)
+{
+
+ if (MAN_BLOCK != n->type)
+ return;
+
+ term_word(p, "<");
+ p->flags |= TERMP_NOSPACE;
+
+ if (NULL != n->child->child)
+ print_man_node(p, mt, n->child->child, meta);
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ">");
+}
+
+static void
+print_man_node(DECL_ARGS)
+{
+ size_t rm, rmax;
+ int c;
+
+ switch (n->type) {
+ case MAN_TEXT:
+ /*
+ * If we have a blank line, output a vertical space.
+ * If we have a space as the first character, break
+ * before printing the line's data.
+ */
+ if ('\0' == *n->string) {
+ term_vspace(p);
+ return;
+ } else if (' ' == *n->string && MAN_LINE & n->flags)
+ term_newln(p);
+
+ term_word(p, n->string);
+ goto out;
+
+ case MAN_EQN:
+ term_eqn(p, n->eqn);
+ return;
+ case MAN_TBL:
+ /*
+ * Tables are preceded by a newline. Then process a
+ * table line, which will cause line termination,
+ */
+ if (TBL_SPAN_FIRST & n->span->flags)
+ term_newln(p);
+ term_tbl(p, n->span);
+ return;
+ default:
+ break;
+ }
+
+ if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
+ term_fontrepl(p, TERMFONT_NONE);
+
+ c = 1;
+ if (termacts[n->tok].pre)
+ c = (*termacts[n->tok].pre)(p, mt, n, meta);
+
+ if (c && n->child)
+ print_man_nodelist(p, mt, n->child, meta);
+
+ if (termacts[n->tok].post)
+ (*termacts[n->tok].post)(p, mt, n, meta);
+ if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
+ term_fontrepl(p, TERMFONT_NONE);
+
+out:
+ /*
+ * If we're in a literal context, make sure that words
+ * together on the same line stay together. This is a
+ * POST-printing call, so we check the NEXT word. Since
+ * -man doesn't have nested macros, we don't need to be
+ * more specific than this.
+ */
+ if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
+ (NULL == n->next || MAN_LINE & n->next->flags)) {
+ rm = p->rmargin;
+ rmax = p->maxrmargin;
+ p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
+ p->flags |= TERMP_NOSPACE;
+ if (NULL != n->string && '\0' != *n->string)
+ term_flushln(p);
+ else
+ term_newln(p);
+ if (rm < rmax && n->parent->tok == MAN_HP) {
+ p->offset = rm;
+ p->rmargin = rmax;
+ } else
+ p->rmargin = rm;
+ p->maxrmargin = rmax;
+ }
+ if (MAN_EOS & n->flags)
+ p->flags |= TERMP_SENTENCE;
+}
+
+
+static void
+print_man_nodelist(DECL_ARGS)
+{
+
+ print_man_node(p, mt, n, meta);
+ if ( ! n->next)
+ return;
+ print_man_nodelist(p, mt, n->next, meta);
+}
+
+static void
+print_man_foot(struct termp *p, const void *arg)
+{
+ const struct man_meta *meta;
+ char *title;
+ size_t datelen;
+
+ meta = (const struct man_meta *)arg;
+ assert(meta->title);
+ assert(meta->msec);
+ assert(meta->date);
+
+ term_fontrepl(p, TERMFONT_NONE);
+
+ if (meta->hasbody)
+ term_vspace(p);
+
+ /*
+ * Temporary, undocumented option to imitate mdoc(7) output.
+ * In the bottom right corner, use the source instead of
+ * the title.
+ */
+
+ if ( ! p->mdocstyle) {
+ if (meta->hasbody) {
+ term_vspace(p);
+ term_vspace(p);
+ }
+ mandoc_asprintf(&title, "%s(%s)",
+ meta->title, meta->msec);
+ } else if (meta->source) {
+ title = mandoc_strdup(meta->source);
+ } else {
+ title = mandoc_strdup("");
+ }
+ datelen = term_strlen(p, meta->date);
+
+ /* Bottom left corner: manual source. */
+
+ p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+ p->trailspace = 1;
+ p->offset = 0;
+ p->rmargin = (p->maxrmargin - datelen + term_len(p, 1)) / 2;
+
+ if (meta->source)
+ term_word(p, meta->source);
+ term_flushln(p);
+
+ /* At the bottom in the middle: manual date. */
+
+ p->flags |= TERMP_NOSPACE;
+ p->offset = p->rmargin;
+ p->rmargin = p->maxrmargin - term_strlen(p, title);
+ if (p->offset + datelen >= p->rmargin)
+ p->rmargin = p->offset + datelen;
+
+ term_word(p, meta->date);
+ term_flushln(p);
+
+ /* Bottom right corner: manual title and section. */
+
+ p->flags &= ~TERMP_NOBREAK;
+ p->flags |= TERMP_NOSPACE;
+ p->trailspace = 0;
+ p->offset = p->rmargin;
+ p->rmargin = p->maxrmargin;
+
+ term_word(p, title);
+ term_flushln(p);
+ free(title);
+}
+
+static void
+print_man_head(struct termp *p, const void *arg)
+{
+ const struct man_meta *meta;
+ const char *volume;
+ char *title;
+ size_t vollen, titlen;
+
+ meta = (const struct man_meta *)arg;
+ assert(meta->title);
+ assert(meta->msec);
+
+ volume = NULL == meta->vol ? "" : meta->vol;
+ vollen = term_strlen(p, volume);
+
+ /* Top left corner: manual title and section. */
+
+ mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
+ titlen = term_strlen(p, title);
+
+ p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
+ p->trailspace = 1;
+ p->offset = 0;
+ p->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
+ (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
+ p->maxrmargin - vollen;
+
+ term_word(p, title);
+ term_flushln(p);
+
+ /* At the top in the middle: manual volume. */
+
+ p->flags |= TERMP_NOSPACE;
+ p->offset = p->rmargin;
+ p->rmargin = p->offset + vollen + titlen < p->maxrmargin ?
+ p->maxrmargin - titlen : p->maxrmargin;
+
+ term_word(p, volume);
+ term_flushln(p);
+
+ /* Top right corner: title and section, again. */
+
+ p->flags &= ~TERMP_NOBREAK;
+ p->trailspace = 0;
+ if (p->rmargin + titlen <= p->maxrmargin) {
+ p->flags |= TERMP_NOSPACE;
+ p->offset = p->rmargin;
+ p->rmargin = p->maxrmargin;
+ term_word(p, title);
+ term_flushln(p);
+ }
+
+ p->flags &= ~TERMP_NOSPACE;
+ p->offset = 0;
+ p->rmargin = p->maxrmargin;
+
+ /*
+ * Groff prints three blank lines before the content.
+ * Do the same, except in the temporary, undocumented
+ * mode imitating mdoc(7) output.
+ */
+
+ term_vspace(p);
+ if ( ! p->mdocstyle) {
+ term_vspace(p);
+ term_vspace(p);
+ }
+ free(title);
+}
diff --git a/contrib/mdocml/man_validate.c b/contrib/mdocml/man_validate.c
new file mode 100644
index 0000000..c17eb9e
--- /dev/null
+++ b/contrib/mdocml/man_validate.c
@@ -0,0 +1,546 @@
+/* $Id: man_validate.c,v 1.105 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libman.h"
+#include "libmandoc.h"
+
+#define CHKARGS struct man *man, struct man_node *n
+
+typedef int (*v_check)(CHKARGS);
+
+static int check_eq0(CHKARGS);
+static int check_eq2(CHKARGS);
+static int check_le1(CHKARGS);
+static int check_le5(CHKARGS);
+static int check_par(CHKARGS);
+static int check_part(CHKARGS);
+static int check_root(CHKARGS);
+static int check_text(CHKARGS);
+
+static int post_AT(CHKARGS);
+static int post_IP(CHKARGS);
+static int post_vs(CHKARGS);
+static int post_fi(CHKARGS);
+static int post_ft(CHKARGS);
+static int post_nf(CHKARGS);
+static int post_TH(CHKARGS);
+static int post_UC(CHKARGS);
+static int post_UR(CHKARGS);
+
+static v_check man_valids[MAN_MAX] = {
+ post_vs, /* br */
+ post_TH, /* TH */
+ NULL, /* SH */
+ NULL, /* SS */
+ NULL, /* TP */
+ check_par, /* LP */
+ check_par, /* PP */
+ check_par, /* P */
+ post_IP, /* 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 */
+ check_eq0, /* na */
+ post_vs, /* sp */
+ post_nf, /* nf */
+ post_fi, /* fi */
+ NULL, /* RE */
+ check_part, /* RS */
+ NULL, /* DT */
+ post_UC, /* UC */
+ check_le1, /* PD */
+ post_AT, /* AT */
+ NULL, /* in */
+ post_ft, /* ft */
+ check_eq2, /* OP */
+ post_nf, /* EX */
+ post_fi, /* EE */
+ post_UR, /* UR */
+ NULL, /* UE */
+ NULL, /* ll */
+};
+
+
+int
+man_valid_post(struct man *man)
+{
+ struct man_node *n;
+ v_check *cp;
+
+ n = man->last;
+ if (n->flags & MAN_VALID)
+ return(1);
+ n->flags |= MAN_VALID;
+
+ switch (n->type) {
+ case MAN_TEXT:
+ return(check_text(man, n));
+ case MAN_ROOT:
+ return(check_root(man, n));
+ case MAN_EQN:
+ /* FALLTHROUGH */
+ case MAN_TBL:
+ return(1);
+ default:
+ cp = man_valids + n->tok;
+ return(*cp ? (*cp)(man, n) : 1);
+ }
+}
+
+static int
+check_root(CHKARGS)
+{
+
+ assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
+
+ if (NULL == man->first->child)
+ mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
+ n->line, n->pos, NULL);
+ else
+ man->meta.hasbody = 1;
+
+ if (NULL == man->meta.title) {
+ mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
+ n->line, n->pos, NULL);
+
+ /*
+ * If a title hasn't been set, do so now (by
+ * implication, date and section also aren't set).
+ */
+
+ man->meta.title = mandoc_strdup("");
+ man->meta.msec = mandoc_strdup("");
+ man->meta.date = man->quick ? mandoc_strdup("") :
+ mandoc_normdate(man->parse, NULL, n->line, n->pos);
+ }
+
+ return(1);
+}
+
+static int
+check_text(CHKARGS)
+{
+ char *cp, *p;
+
+ if (MAN_LITERAL & man->flags)
+ return(1);
+
+ cp = n->string;
+ for (p = cp; NULL != (p = strchr(p, '\t')); p++)
+ mandoc_msg(MANDOCERR_FI_TAB, man->parse,
+ n->line, n->pos + (p - cp), NULL);
+ return(1);
+}
+
+#define INEQ_DEFINE(x, ineq, name) \
+static int \
+check_##name(CHKARGS) \
+{ \
+ if (n->nchild ineq (x)) \
+ return(1); \
+ mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line, n->pos, \
+ "line arguments %s %d (have %d)", \
+ #ineq, (x), n->nchild); \
+ return(1); \
+}
+
+INEQ_DEFINE(0, ==, eq0)
+INEQ_DEFINE(2, ==, eq2)
+INEQ_DEFINE(1, <=, le1)
+INEQ_DEFINE(5, <=, le5)
+
+static int
+post_UR(CHKARGS)
+{
+
+ if (MAN_HEAD == n->type && 1 != n->nchild)
+ mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
+ n->pos, "line arguments eq 1 (have %d)", n->nchild);
+
+ return(check_part(man, n));
+}
+
+static int
+post_ft(CHKARGS)
+{
+ char *cp;
+ int ok;
+
+ if (0 == n->nchild)
+ return(1);
+
+ ok = 0;
+ cp = n->child->string;
+ switch (*cp) {
+ case '1':
+ /* FALLTHROUGH */
+ case '2':
+ /* FALLTHROUGH */
+ case '3':
+ /* FALLTHROUGH */
+ case '4':
+ /* FALLTHROUGH */
+ case 'I':
+ /* FALLTHROUGH */
+ case 'P':
+ /* FALLTHROUGH */
+ case 'R':
+ if ('\0' == cp[1])
+ ok = 1;
+ break;
+ case 'B':
+ if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
+ ok = 1;
+ break;
+ case 'C':
+ if ('W' == cp[1] && '\0' == cp[2])
+ ok = 1;
+ break;
+ default:
+ break;
+ }
+
+ if (0 == ok) {
+ mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
+ n->line, n->pos, "ft %s", cp);
+ *cp = '\0';
+ }
+
+ if (1 < n->nchild)
+ mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
+ n->pos, "want one child (have %d)", n->nchild);
+
+ return(1);
+}
+
+static int
+check_part(CHKARGS)
+{
+
+ if (MAN_BODY == n->type && 0 == n->nchild)
+ mandoc_msg(MANDOCERR_ARGCWARN, man->parse, n->line,
+ n->pos, "want children (have none)");
+
+ return(1);
+}
+
+static int
+check_par(CHKARGS)
+{
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ if (0 == n->body->nchild)
+ man_node_delete(man, n);
+ break;
+ case MAN_BODY:
+ if (0 == n->nchild)
+ mandoc_vmsg(MANDOCERR_PAR_SKIP,
+ man->parse, n->line, n->pos,
+ "%s empty", man_macronames[n->tok]);
+ break;
+ case MAN_HEAD:
+ if (n->nchild)
+ mandoc_vmsg(MANDOCERR_ARG_SKIP,
+ man->parse, n->line, n->pos,
+ "%s %s%s", man_macronames[n->tok],
+ n->child->string,
+ n->nchild > 1 ? " ..." : "");
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static int
+post_IP(CHKARGS)
+{
+
+ switch (n->type) {
+ case MAN_BLOCK:
+ if (0 == n->head->nchild && 0 == n->body->nchild)
+ man_node_delete(man, n);
+ break;
+ case MAN_BODY:
+ if (0 == n->parent->head->nchild && 0 == n->nchild)
+ mandoc_vmsg(MANDOCERR_PAR_SKIP,
+ man->parse, n->line, n->pos,
+ "%s empty", man_macronames[n->tok]);
+ break;
+ default:
+ break;
+ }
+ return(1);
+}
+
+static int
+post_TH(CHKARGS)
+{
+ struct man_node *nb;
+ const char *p;
+
+ check_le5(man, n);
+
+ free(man->meta.title);
+ free(man->meta.vol);
+ free(man->meta.source);
+ free(man->meta.msec);
+ free(man->meta.date);
+
+ man->meta.title = man->meta.vol = man->meta.date =
+ man->meta.msec = man->meta.source = NULL;
+
+ nb = n;
+
+ /* ->TITLE<- MSEC DATE SOURCE VOL */
+
+ n = n->child;
+ if (n && n->string) {
+ for (p = n->string; '\0' != *p; p++) {
+ /* Only warn about this once... */
+ if (isalpha((unsigned char)*p) &&
+ ! isupper((unsigned char)*p)) {
+ mandoc_vmsg(MANDOCERR_TITLE_CASE,
+ man->parse, n->line,
+ n->pos + (p - n->string),
+ "TH %s", n->string);
+ break;
+ }
+ }
+ man->meta.title = mandoc_strdup(n->string);
+ } else {
+ man->meta.title = mandoc_strdup("");
+ mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
+ nb->line, nb->pos, "TH");
+ }
+
+ /* TITLE ->MSEC<- DATE SOURCE VOL */
+
+ if (n)
+ n = n->next;
+ if (n && n->string)
+ man->meta.msec = mandoc_strdup(n->string);
+ else {
+ man->meta.msec = mandoc_strdup("");
+ mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
+ nb->line, nb->pos, "TH %s", man->meta.title);
+ }
+
+ /* TITLE MSEC ->DATE<- SOURCE VOL */
+
+ if (n)
+ n = n->next;
+ if (n && n->string && '\0' != n->string[0]) {
+ man->meta.date = man->quick ?
+ mandoc_strdup(n->string) :
+ mandoc_normdate(man->parse, n->string,
+ n->line, n->pos);
+ } else {
+ man->meta.date = mandoc_strdup("");
+ mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
+ n ? n->line : nb->line,
+ n ? n->pos : nb->pos, "TH");
+ }
+
+ /* TITLE MSEC DATE ->SOURCE<- VOL */
+
+ if (n && (n = n->next))
+ man->meta.source = mandoc_strdup(n->string);
+
+ /* TITLE MSEC DATE SOURCE ->VOL<- */
+ /* If missing, use the default VOL name for MSEC. */
+
+ if (n && (n = n->next))
+ man->meta.vol = mandoc_strdup(n->string);
+ else if ('\0' != man->meta.msec[0] &&
+ (NULL != (p = mandoc_a2msec(man->meta.msec))))
+ man->meta.vol = mandoc_strdup(p);
+
+ /*
+ * Remove the `TH' node after we've processed it for our
+ * meta-data.
+ */
+ man_node_delete(man, man->last);
+ return(1);
+}
+
+static int
+post_nf(CHKARGS)
+{
+
+ check_eq0(man, n);
+
+ if (MAN_LITERAL & man->flags)
+ mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
+ n->line, n->pos, "nf");
+
+ man->flags |= MAN_LITERAL;
+ return(1);
+}
+
+static int
+post_fi(CHKARGS)
+{
+
+ check_eq0(man, n);
+
+ if ( ! (MAN_LITERAL & man->flags))
+ mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
+ n->line, n->pos, "fi");
+
+ man->flags &= ~MAN_LITERAL;
+ return(1);
+}
+
+static int
+post_UC(CHKARGS)
+{
+ static const char * const bsd_versions[] = {
+ "3rd Berkeley Distribution",
+ "4th Berkeley Distribution",
+ "4.2 Berkeley Distribution",
+ "4.3 Berkeley Distribution",
+ "4.4 Berkeley Distribution",
+ };
+
+ const char *p, *s;
+
+ n = n->child;
+
+ if (NULL == n || MAN_TEXT != n->type)
+ p = bsd_versions[0];
+ else {
+ s = n->string;
+ if (0 == strcmp(s, "3"))
+ p = bsd_versions[0];
+ else if (0 == strcmp(s, "4"))
+ p = bsd_versions[1];
+ else if (0 == strcmp(s, "5"))
+ p = bsd_versions[2];
+ else if (0 == strcmp(s, "6"))
+ p = bsd_versions[3];
+ else if (0 == strcmp(s, "7"))
+ p = bsd_versions[4];
+ else
+ p = bsd_versions[0];
+ }
+
+ free(man->meta.source);
+ man->meta.source = mandoc_strdup(p);
+ return(1);
+}
+
+static int
+post_AT(CHKARGS)
+{
+ static const char * const unix_versions[] = {
+ "7th Edition",
+ "System III",
+ "System V",
+ "System V Release 2",
+ };
+
+ const char *p, *s;
+ struct man_node *nn;
+
+ n = n->child;
+
+ if (NULL == n || MAN_TEXT != n->type)
+ p = unix_versions[0];
+ else {
+ s = n->string;
+ if (0 == strcmp(s, "3"))
+ p = unix_versions[0];
+ else if (0 == strcmp(s, "4"))
+ p = unix_versions[1];
+ else if (0 == strcmp(s, "5")) {
+ nn = n->next;
+ if (nn && MAN_TEXT == nn->type && nn->string[0])
+ p = unix_versions[3];
+ else
+ p = unix_versions[2];
+ } else
+ p = unix_versions[0];
+ }
+
+ free(man->meta.source);
+ man->meta.source = mandoc_strdup(p);
+ return(1);
+}
+
+static int
+post_vs(CHKARGS)
+{
+
+ if (n->tok == MAN_br)
+ check_eq0(man, n);
+ else
+ check_le1(man, n);
+
+ if (NULL != n->prev)
+ return(1);
+
+ switch (n->parent->tok) {
+ case MAN_SH:
+ /* FALLTHROUGH */
+ case MAN_SS:
+ mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
+ "%s after %s", man_macronames[n->tok],
+ man_macronames[n->parent->tok]);
+ /* FALLTHROUGH */
+ case MAN_MAX:
+ /*
+ * Don't warn about this because it occurs in pod2man
+ * and would cause considerable (unfixable) warnage.
+ */
+ man_node_delete(man, n);
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
diff --git a/contrib/mdocml/mandoc.1 b/contrib/mdocml/mandoc.1
new file mode 100644
index 0000000..e9f7375
--- /dev/null
+++ b/contrib/mdocml/mandoc.1
@@ -0,0 +1,1502 @@
+.\" $Id: mandoc.1,v 1.106 2014/08/08 01:50:59 schwarze Exp $
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2012, 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 8 2014 $
+.Dt MANDOC 1
+.Os
+.Sh NAME
+.Nm mandoc
+.Nd format and display UNIX manuals
+.Sh SYNOPSIS
+.Nm mandoc
+.Op Fl V
+.Sm off
+.Op Fl I Cm os Li = Ar name
+.Sm on
+.Op Fl m Ns Ar format
+.Op Fl O Ns Ar option
+.Op Fl T Ns Ar output
+.Op Fl W Ns Ar level
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility formats
+.Ux
+manual pages for display.
+.Pp
+By default,
+.Nm
+reads
+.Xr mdoc 7
+or
+.Xr man 7
+text from stdin, implying
+.Fl m Ns Cm andoc ,
+and produces
+.Fl T Ns Cm ascii
+output.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.Sm off
+.It Fl I Cm os Li = Ar name
+.Sm on
+Override the default operating system
+.Ar name
+for the
+.Xr mdoc 7
+.Sq \&Os
+macro.
+.It Fl m Ns Ar format
+Input format.
+See
+.Sx Input Formats
+for available formats.
+Defaults to
+.Fl m Ns Cm andoc .
+.It Fl O Ns Ar option
+Comma-separated output options.
+.It Fl T Ns Ar output
+Output format.
+See
+.Sx Output Formats
+for available formats.
+Defaults to
+.Fl T Ns Cm ascii .
+.It Fl V
+Print version and exit.
+.It Fl W Ns Ar level
+Specify the minimum message
+.Ar level
+to be reported on the standard error output and to affect the exit status.
+The
+.Ar level
+can be
+.Cm warning ,
+.Cm error ,
+or
+.Cm fatal .
+The default is
+.Fl W Ns Cm fatal ;
+.Fl W Ns Cm all
+is an alias for
+.Fl W Ns Cm warning .
+See
+.Sx EXIT STATUS
+and
+.Sx DIAGNOSTICS
+for details.
+.Pp
+The special option
+.Fl W Ns Cm stop
+tells
+.Nm
+to exit after parsing a file that causes warnings or errors of at least
+the requested level.
+No formatted output will be produced from that file.
+If both a
+.Ar level
+and
+.Cm stop
+are requested, they can be joined with a comma, for example
+.Fl W Ns Cm error , Ns Cm stop .
+.It Ar file
+Read input from zero or more files.
+If unspecified, reads from stdin.
+If multiple files are specified,
+.Nm
+will halt with the first failed parse.
+.El
+.Ss Input Formats
+The
+.Nm
+utility accepts
+.Xr mdoc 7
+and
+.Xr man 7
+input with
+.Fl m Ns Cm doc
+and
+.Fl m Ns Cm 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 Cm 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 Cm andoc ,
+each has its file-type determined this way.
+If multiple files are
+specified and
+.Fl m Ns Cm doc
+or
+.Fl m Ns Cm an
+is specified, then this format is used exclusively.
+.Ss Output Formats
+The
+.Nm
+utility accepts the following
+.Fl T
+arguments, which correspond to output modes:
+.Bl -tag -width "-Tlocale"
+.It Fl T Ns Cm ascii
+Produce 7-bit ASCII output.
+This is the default.
+See
+.Sx ASCII Output .
+.It Fl T Ns Cm html
+Produce strict CSS1/HTML-4.01 output.
+See
+.Sx HTML Output .
+.It Fl T Ns Cm lint
+Parse only: produce no output.
+Implies
+.Fl W Ns Cm warning .
+.It Fl T Ns Cm locale
+Encode output using the current locale.
+See
+.Sx Locale Output .
+.It Fl T Ns Cm man
+Produce
+.Xr man 7
+format output.
+See
+.Sx Man Output .
+.It Fl T Ns Cm pdf
+Produce PDF output.
+See
+.Sx PDF Output .
+.It Fl T Ns Cm ps
+Produce PostScript output.
+See
+.Sx PostScript Output .
+.It Fl T Ns Cm tree
+Produce an indented parse tree.
+.It Fl T Ns Cm utf8
+Encode output in the UTF\-8 multi-byte format.
+See
+.Sx UTF\-8 Output .
+.It Fl T Ns Cm xhtml
+Produce strict CSS1/XHTML-1.0 output.
+See
+.Sx XHTML Output .
+.El
+.Pp
+If multiple input files are specified, these will be processed by the
+corresponding filter in-order.
+.Ss ASCII Output
+Output produced by
+.Fl T Ns Cm ascii ,
+which is the default, is rendered in standard 7-bit ASCII documented in
+.Xr ascii 7 .
+.Pp
+Font styles are applied by using back-spaced encoding such that an
+underlined character
+.Sq c
+is rendered as
+.Sq _ Ns \e[bs] Ns c ,
+where
+.Sq \e[bs]
+is the back-space character number 8.
+Emboldened characters are rendered as
+.Sq c Ns \e[bs] Ns c .
+.Pp
+The special characters documented in
+.Xr mandoc_char 7
+are rendered best-effort in an ASCII equivalent.
+If no equivalent is found,
+.Sq \&?
+is used instead.
+.Pp
+Output width is limited to 78 visible columns unless literal input lines
+exceed this limit.
+.Pp
+The following
+.Fl O
+arguments are accepted:
+.Bl -tag -width Ds
+.It Cm indent Ns = Ns Ar indent
+The left margin for normal text is set to
+.Ar indent
+blank characters instead of the default of five for
+.Xr mdoc 7
+and seven for
+.Xr man 7 .
+Increasing this is not recommended; it may result in degraded formatting,
+for example overfull lines or ugly line breaks.
+.It Cm width Ns = Ns Ar width
+The output width is set to
+.Ar width ,
+which will normalise to \(>=60.
+.El
+.Ss HTML Output
+Output produced by
+.Fl T Ns Cm html
+conforms to HTML-4.01 strict.
+.Pp
+The
+.Pa example.style.css
+file documents style-sheet classes available for customising output.
+If a style-sheet is not specified with
+.Fl O Ns Ar style ,
+.Fl T Ns Cm html
+defaults to simple output readable in any graphical or text-based web
+browser.
+.Pp
+Special characters are rendered in decimal-encoded UTF\-8.
+.Pp
+The following
+.Fl O
+arguments are accepted:
+.Bl -tag -width Ds
+.It Cm fragment
+Omit the
+.Aq !DOCTYPE
+declaration and the
+.Aq html ,
+.Aq head ,
+and
+.Aq body
+elements and only emit the subtree below the
+.Aq body
+element.
+The
+.Cm style
+argument will be ignored.
+This is useful when embedding manual content within existing documents.
+.It Cm includes Ns = Ns Ar 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 Cm man Ns = Ns Ar 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.
+.It Cm style Ns = Ns Ar style.css
+The file
+.Ar style.css
+is used for an external style-sheet.
+This must be a valid absolute or
+relative URI.
+.El
+.Ss Locale Output
+Locale-depending output encoding is triggered with
+.Fl T Ns Cm locale .
+This option is not available on all systems: systems without locale
+support, or those whose internal representation is not natively UCS-4,
+will fall back to
+.Fl T Ns Cm ascii .
+See
+.Sx ASCII Output
+for font style specification and available command-line arguments.
+.Ss Man Output
+Translate input format into
+.Xr man 7
+output format.
+This is useful for distributing manual sources to legacy systems
+lacking
+.Xr mdoc 7
+formatters.
+.Pp
+If
+.Xr mdoc 7
+is passed as input, it is translated into
+.Xr man 7 .
+If the input format is
+.Xr man 7 ,
+the input is copied to the output, expanding any
+.Xr roff 7
+.Sq so
+requests.
+The parser is also run, and as usual, the
+.Fl W
+level controls which
+.Sx DIAGNOSTICS
+are displayed before copying the input to the output.
+.Ss PDF Output
+PDF-1.1 output may be generated by
+.Fl T Ns Cm pdf .
+See
+.Sx PostScript Output
+for
+.Fl O
+arguments and defaults.
+.Ss PostScript Output
+PostScript
+.Qq Adobe-3.0
+Level-2 pages may be generated by
+.Fl T Ns Cm ps .
+Output pages default to letter sized and are rendered in the Times font
+family, 11-point.
+Margins are calculated as 1/9 the page length and width.
+Line-height is 1.4m.
+.Pp
+Special characters are rendered as in
+.Sx ASCII Output .
+.Pp
+The following
+.Fl O
+arguments are accepted:
+.Bl -tag -width Ds
+.It Cm paper Ns = Ns Ar name
+The paper size
+.Ar name
+may be one of
+.Ar a3 ,
+.Ar a4 ,
+.Ar a5 ,
+.Ar legal ,
+or
+.Ar letter .
+You may also manually specify dimensions as
+.Ar NNxNN ,
+width by height in millimetres.
+If an unknown value is encountered,
+.Ar letter
+is used.
+.El
+.Ss UTF\-8 Output
+Use
+.Fl T Ns Cm utf8
+to force a UTF\-8 locale.
+See
+.Sx Locale Output
+for details and options.
+.Ss XHTML Output
+Output produced by
+.Fl T Ns Cm xhtml
+conforms to XHTML-1.0 strict.
+.Pp
+See
+.Sx HTML Output
+for details; beyond generating XHTML tags instead of HTML tags, these
+output modes are identical.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values, controlled by the message
+.Ar level
+associated with the
+.Fl W
+option:
+.Pp
+.Bl -tag -width Ds -compact
+.It 0
+No warnings or errors occurred, or those that did were ignored because
+they were lower than the requested
+.Ar level .
+.It 2
+At least one warning occurred, but no error, and
+.Fl W Ns Cm warning
+was specified.
+.It 3
+At least one parsing error occurred, but no fatal error, and
+.Fl W Ns Cm error
+or
+.Fl W Ns Cm warning
+was specified.
+.It 4
+A fatal parsing error occurred.
+.It 5
+Invalid command line arguments were specified.
+No input files have been read.
+.It 6
+An operating system error occurred, for example memory exhaustion or an
+error accessing input files.
+Such errors cause
+.Nm
+to exit at once, possibly in the middle of parsing or formatting a file.
+.El
+.Pp
+Note that selecting
+.Fl T Ns Cm lint
+output mode implies
+.Fl W Ns Cm warning .
+.Sh EXAMPLES
+To page manuals to the terminal:
+.Pp
+.Dl $ mandoc \-Wall,stop mandoc.1 2\*(Gt&1 | less
+.Dl $ mandoc mandoc.1 mdoc.3 mdoc.7 | less
+.Pp
+To produce HTML manuals with
+.Ar style.css
+as the style-sheet:
+.Pp
+.Dl $ mandoc \-Thtml -Ostyle=style.css mdoc.7 \*(Gt mdoc.7.html
+.Pp
+To check over a large set of manuals:
+.Pp
+.Dl $ mandoc \-Tlint `find /usr/src -name \e*\e.[1-9]`
+.Pp
+To produce a series of PostScript manuals for A4 paper:
+.Pp
+.Dl $ mandoc \-Tps \-Opaper=a4 mdoc.7 man.7 \*(Gt manuals.ps
+.Pp
+Convert a modern
+.Xr mdoc 7
+manual to the older
+.Xr man 7
+format, for use on systems lacking an
+.Xr mdoc 7
+parser:
+.Pp
+.Dl $ mandoc \-Tman foo.mdoc \*(Gt foo.man
+.Sh DIAGNOSTICS
+Messages displayed by
+.Nm
+follow this format:
+.Pp
+.D1 Nm Ns : Ar file : Ns Ar line : Ns Ar column : level : message : macro args
+.Pp
+Line and column numbers start at 1.
+Both are omitted for messages referring to an input file as a whole.
+Macro names and arguments are omitted where meaningless.
+Fatal messages about invalid command line arguments
+or operating system errors, for example when memory is exhausted,
+may also omit the
+.Ar file
+and
+.Ar level
+fields.
+.Pp
+Message levels have the following meanings:
+.Bl -tag -width "warning"
+.It Cm syserr
+Opening or reading an input file failed, so the parser cannot
+even be started and no output is produced from that input file.
+.It Cm fatal
+The parser is unable to parse a given input file at all.
+No formatted output is produced from that input file.
+.It Cm error
+An input file contains syntax that cannot be safely interpreted,
+either because it is invalid or because
+.Nm
+does not implement it yet.
+By discarding part of the input or inserting missing tokens,
+the parser is able to continue, and the error does not prevent
+generation of formatted output, but typically, preparing that
+output involves information loss, broken document structure
+or unintended formatting.
+.It Cm warning
+An input file uses obsolete, discouraged or non-portable syntax.
+All the same, the meaning of the input is unambiguous and a correct
+rendering can be produced.
+Documents causing warnings may render poorly when using other
+formatting tools instead of
+.Nm .
+.El
+.Pp
+Messages of the
+.Cm warning
+and
+.Cm error
+levels are hidden unless their level, or a lower level, is requested using a
+.Fl W
+option or
+.Fl T Ns Cm lint
+output mode.
+.Ss Warnings related to the document prologue
+.Bl -ohang
+.It Sy "missing manual title, using UNTITLED"
+.Pq mdoc
+A
+.Ic \&Dt
+macro has no arguments, or there is no
+.Ic \&Dt
+macro before the first non-prologue macro.
+.It Sy "missing manual title, using \(dq\(dq"
+.Pq man
+There is no
+.Ic \&TH
+macro, or it has no arguments.
+.It Sy "lower case character in document title"
+.Pq mdoc , man
+The title is still used as given in the
+.Ic \&Dt
+or
+.Ic \&TH
+macro.
+.It Sy "missing manual section, using \(dq\(dq"
+.Pq mdoc , man
+A
+.Ic \&Dt
+or
+.Ic \&TH
+macro lacks the mandatory section argument.
+.It Sy "unknown manual section"
+.Pq mdoc
+The section number in a
+.Ic \&Dt
+line is invalid, but still used.
+.It Sy "unknown manual volume or arch"
+.Pq mdoc
+The volume name in a
+.Ic \&Dt
+line is invalid, but still used.
+The manual is assumed to be architecture-independent.
+.It Sy "missing date, using today's date"
+.Pq mdoc, man
+The document was parsed as
+.Xr mdoc 7
+and it has no
+.Ic \&Dd
+macro, or the
+.Ic \&Dd
+macro has no arguments or only empty arguments;
+or the document was parsed as
+.Xr man 7
+and it has no
+.Ic \&TH
+macro, or the
+.Ic \&TH
+macro has less than three arguments or its third argument is empty.
+.It Sy "cannot parse date, using it verbatim"
+.Pq mdoc , man
+The date given in a
+.Ic \&Dd
+or
+.Ic \&TH
+macro does not follow the conventional format.
+.It Sy "missing Os macro, using \(dq\(dq"
+.Pq mdoc
+The default or current system is not shown in this case.
+.It Sy "duplicate prologue macro"
+.Pq mdoc
+One of the prologue macros occurs more than once.
+The last instance overrides all previous ones.
+.It Sy "late prologue macro"
+.Pq mdoc
+A
+.Ic \&Dd
+or
+.Ic \&Os
+macro occurs after some non-prologue macro, but still takes effect.
+.It Sy "skipping late title macro"
+.Pq mdoc
+The
+.Ic \&Dt
+macro can only occur before the first non-prologue macro
+because traditional formatters write the page header
+before parsing the document body.
+Even though this technical restriction does not apply to
+.Nm ,
+traditional semantics is preserved.
+The late macro is discarded including its arguments.
+.It Sy "prologue macros out of order"
+.Pq mdoc
+The prologue macros are not given in the conventional order
+.Ic \&Dd ,
+.Ic \&Dt ,
+.Ic \&Os .
+All three macros are used even when given in another order.
+.El
+.Ss Warnings regarding document structure
+.Bl -ohang
+.It Sy ".so is fragile, better use ln(1)"
+.Pq roff
+Including files only works when the parser program runs with the correct
+current working directory.
+.It Sy "no document body"
+.Pq mdoc , man
+The document body contains neither text nor macros.
+An empty document is shown, consisting only of a header and a footer line.
+.It Sy "content before first section header"
+.Pq mdoc , man
+Some macros or text precede the first
+.Ic \&Sh
+or
+.Ic \&SH
+section header.
+The offending macros and text are parsed and added to the top level
+of the syntax tree, outside any section block.
+.It Sy "first section is not NAME"
+.Pq mdoc
+The argument of the first
+.Ic \&Sh
+macro is not
+.Sq NAME .
+This may confuse
+.Xr makewhatis 8
+and
+.Xr apropos 1 .
+.It Sy "bad NAME section contents"
+.Pq mdoc
+The last node in the NAME section is not an
+.Ic \&Nd
+macro, or any preceding macro is not
+.Ic \&Nm ,
+or the NAME section is completely empty.
+This may confuse
+.Xr makewhatis 8
+and
+.Xr apropos 1 .
+.It Sy "sections out of conventional order"
+.Pq mdoc
+A standard section occurs after another section it usually precedes.
+All section titles are used as given,
+and the order of sections is not changed.
+.It Sy "duplicate section title"
+.Pq mdoc
+The same standard section title occurs more than once.
+.It Sy "unexpected section"
+.Pq mdoc
+A standard section header occurs in a section of the manual
+where it normally isn't useful.
+.El
+.Ss "Warnings related to macros and nesting"
+.Bl -ohang
+.It Sy "obsolete macro"
+.Pq mdoc
+See the
+.Xr mdoc 7
+manual for replacements.
+.It Sy "skipping paragraph macro"
+In
+.Xr mdoc 7
+documents, this happens
+.Bl -dash -compact
+.It
+at the beginning and end of sections and subsections
+.It
+right before non-compact lists and displays
+.It
+at the end of items in non-column, non-compact lists
+.It
+and for multiple consecutive paragraph macros.
+.El
+In
+.Xr man 7
+documents, it happens
+.Bl -dash -compact
+.It
+for empty
+.Ic \&P ,
+.Ic \&PP ,
+and
+.Ic \&LP
+macros
+.It
+for
+.Ic \&IP
+macros having neither head nor body arguments
+.It
+for
+.Ic \&br
+or
+.Ic \&sp
+right after
+.Ic \&SH
+or
+.Ic \&SS
+.El
+.It Sy "moving paragraph macro out of list"
+.Pq mdoc
+A list item in a
+.Ic \&Bl
+list contains a trailing paragraph macro.
+The paragraph macro is moved after the end of the list.
+.It Sy "skipping no-space macro"
+.Pq mdoc
+An input line begins with an
+.Ic \&Ns
+macro.
+The macro is ignored.
+.It Sy "blocks badly nested"
+.Pq mdoc
+If two blocks intersect, one should completely contain the other.
+Otherwise, rendered output is likely to look strange in any output
+format, and rendering in SGML-based output formats is likely to be
+outright wrong because such languages do not support badly nested
+blocks at all.
+Typical examples of badly nested blocks are
+.Qq Ic \&Ao \&Bo \&Ac \&Bc
+and
+.Qq Ic \&Ao \&Bq \&Ac .
+In these examples,
+.Ic \&Ac
+breaks
+.Ic \&Bo
+and
+.Ic \&Bq ,
+respectively.
+.It Sy "nested displays are not portable"
+.Pq mdoc
+A
+.Ic \&Bd ,
+.Ic \&D1 ,
+or
+.Ic \&Dl
+display occurs nested inside another
+.Ic \&Bd
+display.
+This works with
+.Nm ,
+but fails with most other implementations.
+.It Sy "moving content out of list"
+.Pq mdoc
+A
+.Ic \&Bl
+list block contains text or macros before the first
+.Ic \&It
+macro.
+The offending children are moved before the beginning of the list.
+.It Sy ".Vt block has child macro"
+.Pq mdoc
+The
+.Ic \&Vt
+macro supports plain text arguments only.
+Formatting may be ugly and semantic searching
+for the affected content might not work.
+.It Sy "fill mode already enabled, skipping"
+.Pq man
+A
+.Ic \&fi
+request occurs even though the document is still in fill mode,
+or already switched back to fill mode.
+It has no effect.
+.It Sy "fill mode already disabled, skipping"
+.Pq man
+An
+.Ic \&nf
+request occurs even though the document already switched to no-fill mode
+and did not switch back to fill mode yet.
+It has no effect.
+.It Sy "line scope broken"
+.Pq man
+While parsing the next-line scope of the previous macro,
+another macro is found that prematurely terminates the previous one.
+The previous, interrupted macro is deleted from the parse tree.
+.El
+.Ss "Warnings related to missing arguments"
+.Bl -ohang
+.It Sy "skipping empty request"
+.Pq roff
+The macro name is missing from a macro definition request.
+.It Sy "conditional request controls empty scope"
+.Pq roff
+A conditional request is only useful if any of the following
+follows it on the same logical input line:
+.Bl -dash -compact
+.It
+The
+.Sq \e{
+keyword to open a multi-line scope.
+.It
+A request or macro or some text, resulting in a single-line scope.
+.It
+The immediate end of the logical line without any intervening whitespace,
+resulting in next-line scope.
+.El
+Here, a conditional request is followed by trailing whitespace only,
+and there is no other content on its logical input line.
+Note that it doesn't matter whether the logical input line is split
+across multiple physical input lines using
+.Sq \e
+line continuation characters.
+This is one of the rare cases
+where trailing whitespace is syntactically significant.
+The conditional request controls a scope containing whitespace only,
+so it is unlikely to have a significant effect,
+except that it may control a following
+.Ic \&el
+clause.
+.It Sy "skipping empty macro"
+.Pq mdoc
+The indicated macro has no arguments and hence no effect.
+.It Sy "empty argument, using 0n"
+.Pq mdoc
+The required width is missing after
+.Ic \&Bd
+or
+.Ic \&Bl
+.Fl offset
+or
+.Fl width.
+.It Sy "argument count wrong"
+.Pq mdoc , man
+The indicated macro has too few or too many arguments.
+The syntax tree will contain the wrong number of arguments as given.
+Formatting behaviour depends on the specific macro in question.
+Note that the same message may also occur as an ERROR, see below.
+.It Sy "missing display type, using -ragged"
+.Pq mdoc
+The
+.Ic \&Bd
+macro is invoked without the required display type.
+.It Sy "list type is not the first argument"
+.Pq mdoc
+In a
+.Ic \&Bl
+macro, at least one other argument precedes the type argument.
+The
+.Nm
+utility copes with any argument order, but some other
+.Xr mdoc 7
+implementations do not.
+.It Sy "missing -width in -tag list, using 8n"
+.Pq mdoc
+Every
+.Ic \&Bl
+macro having the
+.Fl tag
+argument requires
+.Fl width ,
+too.
+.It Sy "missing utility name, using \(dq\(dq"
+.Pq mdoc
+The
+.Ic \&Ex Fl std
+macro is called without an argument before
+.Ic \&Nm
+has first been called with an argument.
+.It Sy "empty head in list item"
+.Pq mdoc
+In a
+.Ic \&Bl
+.Fl diag ,
+.Fl hang ,
+.Fl inset ,
+.Fl ohang ,
+or
+.Fl tag
+list, an
+.Ic \&It
+macro lacks the required argument.
+The item head is left empty.
+.It Sy "empty list item"
+.Pq mdoc
+In a
+.Ic \&Bl
+.Fl bullet ,
+.Fl dash ,
+.Fl enum ,
+or
+.Fl hyphen
+list, an
+.Ic \&It
+block is empty.
+An empty list item is shown.
+.It Sy "missing font type"
+.Pq mdoc
+A
+.Ic \&Bf
+macro has no argument.
+It switches to the default font,
+.Cm \efR .
+.It Sy "unknown font type"
+.Pq mdoc
+The
+.Ic \&Bf
+argument is invalid.
+The default font
+.Cm \efR
+is used instead.
+.It Sy "missing -std argument, adding it"
+.Pq mdoc
+An
+.Ic \&Ex
+or
+.Ic \&Rv
+macro lacks the required
+.Fl std
+argument.
+The
+.Nm
+utility assumes
+.Fl std
+even when it is not specified, but other implementations may not.
+.El
+.Ss "Warnings related to bad macro arguments"
+.Bl -ohang
+.It Sy "unterminated quoted argument"
+.Pq roff
+Macro arguments can be enclosed in double quote characters
+such that space characters and macro names contained in the quoted
+argument need not be escaped.
+The closing quote of the last argument of a macro can be omitted.
+However, omitting it is not recommended because it makes the code
+harder to read.
+.It Sy "duplicate argument"
+.Pq mdoc
+A
+.Ic \&Bd
+or
+.Ic \&Bl
+macro has more than one
+.Fl compact ,
+more than one
+.Fl offset ,
+or more than one
+.Fl width
+argument.
+All but the last instances of these arguments are ignored.
+.It Sy "skipping duplicate argument"
+.Pq mdoc
+An
+.Ic \&An
+macro has more than one
+.Fl split
+or
+.Fl nosplit
+argument.
+All but the first of these arguments are ignored.
+.It Sy "skipping duplicate display type"
+.Pq mdoc
+A
+.Ic \&Bd
+macro has more than one type argument; the first one is used.
+.It Sy "skipping duplicate list type"
+.Pq mdoc
+A
+.Ic \&Bl
+macro has more than one type argument; the first one is used.
+.It Sy "skipping -width argument"
+.Pq mdoc
+A
+.Ic \&Bl
+.Fl column ,
+.Fl diag ,
+.Fl ohang ,
+.Fl inset ,
+or
+.Fl item
+list has a
+.Fl width
+argument.
+That has no effect.
+.It Sy "unknown AT&T UNIX version"
+.Pq mdoc
+An
+.Ic \&At
+macro has an invalid argument.
+It is used verbatim, with
+.Qq "AT&T UNIX "
+prefixed to it.
+.It Sy "invalid content in Rs block"
+.Pq mdoc
+An
+.Ic \&Rs
+block contains plain text or non-% macros.
+The bogus content is left in the syntax tree.
+Formatting may be poor.
+.It Sy "invalid Boolean argument"
+.Pq mdoc
+An
+.Ic \&Sm
+macro has an argument other than
+.Cm on
+or
+.Cm off .
+The invalid argument is moved out of the macro, which leaves the macro
+empty, causing it to toggle the spacing mode.
+.It Sy "unknown font, skipping request"
+.Pq man
+A
+.Xr roff 7
+.Ic \&ft
+request has an invalid argument.
+.El
+.Ss "Warnings related to plain text"
+.Bl -ohang
+.It Sy "blank line in fill mode, using .sp"
+.Pq mdoc
+The meaning of blank input lines is only well-defined in non-fill mode:
+In fill mode, line breaks of text input lines are not supposed to be
+significant.
+However, for compatibility with groff, blank lines in fill mode
+are replaced with
+.Ic \&sp
+requests.
+.It Sy "tab in filled text"
+.Pq mdoc , man
+The meaning of tab characters is only well-defined in non-fill mode:
+In fill mode, whitespace is not supposed to be significant
+on text input lines.
+As an implementation dependent choice, tab characters on text lines
+are passed through to the formatters in any case.
+Given that the text before the tab character will be filled,
+it is hard to predict which tab stop position the tab will advance to.
+.It Sy "whitespace at end of input line"
+.Pq mdoc , man , roff
+Whitespace at the end of input lines is almost never semantically
+significant \(em but in the odd case where it might be, it is
+extremely confusing when reviewing and maintaining documents.
+.It Sy "bad comment style"
+.Pq roff
+Comment lines start with a dot, a backslash, and a double-quote character.
+The
+.Nm
+utility treats the line as a comment line even without the backslash,
+but leaving out the backslash might not be portable.
+.It Sy "invalid escape sequence"
+.Pq roff
+An escape sequence has an invalid opening argument delimiter, lacks the
+closing argument delimiter, or the argument has too few characters.
+If the argument is incomplete,
+.Ic \e*
+and
+.Ic \en
+expand to an empty string,
+.Ic \eB
+to the digit
+.Sq 0 ,
+and
+.Ic \ew
+to the length of the incomplete argument.
+All other invalid escape sequences are ignored.
+.It Sy "undefined string, using \(dq\(dq"
+.Pq roff
+If a string is used without being defined before,
+its value is implicitly set to the empty string.
+However, defining strings explicitly before use
+keeps the code more readable.
+.El
+.Ss "Errors related to equations"
+.Bl -inset -compact
+.It "unexpected equation scope closure"
+.It "equation scope open on exit"
+.It "overlapping equation scopes"
+.It "unexpected end of equation"
+.It "equation syntax error"
+.El
+.Ss "Errors related to tables"
+.Bl -inset -compact
+.It "bad table syntax"
+.It "bad table option"
+.It "bad table layout"
+.It "no table layout cells specified"
+.It "no table data cells specified"
+.It "ignore data in cell"
+.It "data block still open"
+.It "ignoring extra data cells"
+.El
+.Ss "Errors related to roff, mdoc, and man code"
+.Bl -ohang
+.It Sy "input stack limit exceeded, infinite loop?"
+.Pq roff
+Explicit recursion limits are implemented for the following features,
+in order to prevent infinite loops:
+.Bl -dash -compact
+.It
+expansion of nested escape sequences
+including expansion of strings and number registers,
+.It
+expansion of nested user-defined macros,
+.It
+and
+.Ic \&so
+file inclusion.
+.El
+When a limit is hit, the output is incorrect, typically losing
+some content, but the parser can continue.
+.It Sy "skipping bad character"
+.Pq mdoc , man , roff
+The input file contains a byte that is not a printable
+.Xr ascii 7
+character.
+The message mentions the character number.
+The offending byte is replaced with a question mark
+.Pq Sq \&? .
+Consider editing the input file to replace the byte with an ASCII
+transliteration of the intended character.
+.It Sy "skipping unknown macro"
+.Pq mdoc , man , roff
+The first identifier on a request or macro line is neither recognized as a
+.Xr roff 7
+request, nor as a user-defined macro, nor, respectively, as an
+.Xr mdoc 7
+or
+.Xr man 7
+macro.
+It may be mistyped or unsupported.
+The request or macro is discarded including its arguments.
+.It Sy "skipping item outside list"
+.Pq mdoc
+An
+.Ic \&It
+macro occurs outside any
+.Ic \&Bl
+list.
+It is discarded including its arguments.
+.It Sy "skipping column outside column list"
+.Pq mdoc
+A
+.Ic \&Ta
+macro occurs outside any
+.Ic \&Bl Fl column
+block.
+It is discarded including its arguments.
+.It Sy "skipping end of block that is not open"
+.Pq mdoc , man , eqn , tbl , roff
+Various syntax elements can only be used to explicitly close blocks
+that have previously been opened.
+An
+.Xr mdoc 7
+block closing macro, a
+.Xr man 7
+.Ic \&RE
+or
+.Ic \&UE
+macro, or the end of an equation, table, or
+.Xr roff 7
+conditional request is encountered but no matching block is open.
+The offending request or macro is discarded.
+.It Sy "inserting missing end of block"
+.Pq mdoc , tbl
+Various
+.Xr mdoc 7
+macros as well as tables require explicit closing by dedicated macros.
+A block that doesn't support bad nesting
+ends before all of its children are properly closed.
+The open child nodes are closed implicitly.
+.It Sy "scope open on exit"
+.Pq mdoc , man , eqn , tbl , roff
+At the end of the document, an explicit
+.Xr mdoc 7
+block, a
+.Xr man 7
+next-line scope or
+.Ic \&RS
+or
+.Ic \&UR
+block, an equation, table, or
+.Xr roff 7
+conditional or ignore block is still open.
+The open block is closed implicitly.
+.It Sy "escaped character not allowed in a name"
+.Pq roff
+Macro, string and register identifiers consist of printable,
+non-whitespace ASCII characters.
+Escape sequences and characters and strings expressed in terms of them
+cannot form part of a name.
+The first argument of an
+.Ic \&am ,
+.Ic \&as ,
+.Ic \&de ,
+.Ic \&ds ,
+.Ic \&nr ,
+or
+.Ic \&rr
+request, or any argument of an
+.Ic \&rm
+request, or the name of a request or user defined macro being called,
+is terminated by an escape sequence.
+In the cases of
+.Ic \&as ,
+.Ic \&ds ,
+and
+.Ic \&nr ,
+the request has no effect at all.
+In the cases of
+.Ic \&am ,
+.Ic \&de ,
+.Ic \&rr ,
+and
+.Ic \&rm ,
+what was parsed up to this point is used as the arguments to the request,
+and the rest of the input line is discarded including the escape sequence.
+When parsing for a request or a user-defined macro name to be called,
+only the escape sequence is discarded.
+The characters preceding it are used as the request or macro name,
+the characters following it are used as the arguments to the request or macro.
+.It Sy "argument count wrong"
+.Pq mdoc , man , roff
+The indicated request or macro has too few or too many arguments.
+The syntax tree will contain the wrong number of arguments as given.
+Formatting behaviour depends on the specific request or macro in question.
+Note that the same message may also occur as a WARNING, see above.
+.It Sy "missing list type, using -item"
+.Pq mdoc
+A
+.Ic \&Bl
+macro fails to specify the list type.
+.It Sy "missing manual name, using \(dq\(dq"
+.Pq mdoc
+The first call to
+.Ic \&Nm
+lacks the required argument.
+.It Sy "uname(3) system call failed, using UNKNOWN"
+.Pq mdoc
+The
+.Ic \&Os
+macro is called without arguments, and the
+.Xr uname 3
+system call failed.
+As a workaround,
+.Nm
+can be compiled with
+.Sm off
+.Fl D Cm OSNAME=\(dq\e\(dq Ar string Cm \e\(dq\(dq .
+.Sm on
+.It Sy "unknown standard specifier"
+.Pq mdoc
+An
+.Ic \&St
+macro has an unknown argument and is discarded.
+.It Sy "skipping request without numeric argument"
+.Pq roff
+An
+.Ic \&it
+request has a non-numeric or negative argument or no argument at all.
+The invalid request is ignored.
+.It Sy "skipping all arguments"
+.Pq mdoc , man , eqn , roff
+An
+.Xr mdoc 7
+.Ic \&Bt ,
+.Ic \&Ed ,
+.Ic \&Ef ,
+.Ic \&Ek ,
+.Ic \&El ,
+.Ic \&Re ,
+or
+.Ic \&Ud
+macro, an
+.Ic \&It
+macro in a list that don't support item heads, a
+.Xr man 7
+.Ic \&LP ,
+.Ic \&P ,
+or
+.Ic \&PP
+macro, an
+.Xr eqn 7
+.Ic \&EN
+macro, or a
+.Xr roff 7
+.Sq \&..
+block closing request is invoked with at least one argument.
+All arguments are ignored.
+.It Sy "skipping excess arguments"
+.Pq mdoc , roff
+The
+.Ic \&Bf
+macro is invoked with more than one argument, or a request of the
+.Ic \&de
+family is invoked with more than two arguments.
+The excess arguments are ignored.
+.El
+.Ss FATAL errors
+.Bl -ohang
+.It Sy "input too large"
+.Pq mdoc , man
+Currently,
+.Nm
+cannot handle input files larger than its arbitrary size limit
+of 2^31 bytes (2 Gigabytes).
+Since useful manuals are always small, this is not a problem in practice.
+Parsing is aborted as soon as the condition is detected.
+.It Sy "NOT IMPLEMENTED: Bd -file"
+.Pq mdoc
+For security reasons, the
+.Ic \&Bd
+macro does not support the
+.Fl file
+argument.
+By requesting the inclusion of a sensitive file, a malicious document
+might otherwise trick a privileged user into inadvertently displaying
+the file on the screen, revealing the file content to bystanders.
+The parser exits immediately.
+.It Sy "NOT IMPLEMENTED: .so with absolute path or \(dq..\(dq"
+.Pq roff
+For security reasons,
+.Nm
+allows
+.Ic \&so
+file inclusion requests only with relative paths
+and only without ascending to any parent directory.
+By requesting the inclusion of a sensitive file, a malicious document
+might otherwise trick a privileged user into inadvertently displaying
+the file on the screen, revealing the file content to bystanders.
+The parser exits immediately.
+.It Sy ".so request failed"
+.Pq roff
+Servicing a
+.Ic \&so
+request requires reading an external file.
+While trying to do so, an
+.Xr open 2 ,
+.Xr stat 2 ,
+or
+.Xr read 2
+system call failed.
+The parser exits immediately.
+Before showing this message,
+.Nm
+always shows another message explaining why the system call failed.
+.El
+.Sh COMPATIBILITY
+This section summarises
+.Nm
+compatibility with GNU troff.
+Each input and output format is separately noted.
+.Ss ASCII Compatibility
+.Bl -bullet -compact
+.It
+Unrenderable unicode codepoints specified with
+.Sq \e[uNNNN]
+escapes are printed as
+.Sq \&?
+in mandoc.
+In GNU troff, these raise an error.
+.It
+The
+.Sq \&Bd \-literal
+and
+.Sq \&Bd \-unfilled
+macros of
+.Xr mdoc 7
+in
+.Fl T Ns Cm ascii
+are synonyms, as are \-filled and \-ragged.
+.It
+In historic GNU troff, 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 the
+.Sq \&Ss
+.Xr mdoc 7
+macro in
+.Fl T Ns Cm 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 Cm ascii
+has no effect.
+.It
+Words aren't hyphenated.
+.El
+.Ss HTML/XHTML Compatibility
+.Bl -bullet -compact
+.It
+The
+.Sq \efP
+escape will revert the font to the previous
+.Sq \ef
+escape, not to the last rendered decoration, which is now dictated by
+CSS instead of hard-coded.
+It also will not span past the current scope,
+for the same reason.
+Note that in
+.Sx ASCII Output
+mode, this will work fine.
+.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
+.Sh SEE ALSO
+.Xr eqn 7 ,
+.Xr man 7 ,
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr roff 7 ,
+.Xr tbl 7
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
+.Sh CAVEATS
+In
+.Fl T Ns Cm html
+and
+.Fl T Ns Cm xhtml ,
+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 such as
+.Fl O Ns Cm style Ns = Ns Ar really/long/link .
+.Pp
+Nesting elements within next-line element scopes of
+.Fl m Ns Cm an ,
+such as
+.Sq br
+within an empty
+.Sq B ,
+will confuse
+.Fl T Ns Cm html
+and
+.Fl T Ns Cm xhtml
+and cause them to forget the formatting of the prior next-line scope.
+.Pp
+The
+.Sq \(aq
+control character is an alias for the standard macro control character
+and does not emit a line-break as stipulated in GNU troff.
diff --git a/contrib/mdocml/mandoc.3 b/contrib/mdocml/mandoc.3
new file mode 100644
index 0000000..8f76ad2
--- /dev/null
+++ b/contrib/mdocml/mandoc.3
@@ -0,0 +1,661 @@
+.\" $Id: mandoc.3,v 1.25 2014/08/05 05:48:56 schwarze Exp $
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MANDOC 3
+.Os
+.Sh NAME
+.Nm mandoc ,
+.Nm man_deroff ,
+.Nm man_meta ,
+.Nm man_mparse ,
+.Nm man_node ,
+.Nm mdoc_deroff ,
+.Nm mdoc_meta ,
+.Nm mdoc_node ,
+.Nm mparse_alloc ,
+.Nm mparse_free ,
+.Nm mparse_getkeep ,
+.Nm mparse_keep ,
+.Nm mparse_readfd ,
+.Nm mparse_reset ,
+.Nm mparse_result ,
+.Nm mparse_strerror ,
+.Nm mparse_strlevel
+.Nd mandoc macro compiler library
+.Sh LIBRARY
+.Lb libmandoc
+.Sh SYNOPSIS
+.In sys/types.h
+.In mandoc.h
+.Fd "#define ASCII_NBRSP"
+.Fd "#define ASCII_HYPH"
+.Fd "#define ASCII_BREAK"
+.Ft struct mparse *
+.Fo mparse_alloc
+.Fa "int options"
+.Fa "enum mandoclevel wlevel"
+.Fa "mandocmsg mmsg"
+.Fa "char *defos"
+.Fc
+.Ft void
+.Fo (*mandocmsg)
+.Fa "enum mandocerr errtype"
+.Fa "enum mandoclevel level"
+.Fa "const char *file"
+.Fa "int line"
+.Fa "int col"
+.Fa "const char *msg"
+.Fc
+.Ft void
+.Fo mparse_free
+.Fa "struct mparse *parse"
+.Fc
+.Ft const char *
+.Fo mparse_getkeep
+.Fa "const struct mparse *parse"
+.Fc
+.Ft void
+.Fo mparse_keep
+.Fa "struct mparse *parse"
+.Fc
+.Ft "enum mandoclevel"
+.Fo mparse_readfd
+.Fa "struct mparse *parse"
+.Fa "int fd"
+.Fa "const char *fname"
+.Fc
+.Ft void
+.Fo mparse_reset
+.Fa "struct mparse *parse"
+.Fc
+.Ft void
+.Fo mparse_result
+.Fa "struct mparse *parse"
+.Fa "struct mdoc **mdoc"
+.Fa "struct man **man"
+.Fa "char **sodest"
+.Fc
+.Ft "const char *"
+.Fo mparse_strerror
+.Fa "enum mandocerr"
+.Fc
+.Ft "const char *"
+.Fo mparse_strlevel
+.Fa "enum mandoclevel"
+.Fc
+.In sys/types.h
+.In mandoc.h
+.In mdoc.h
+.Ft void
+.Fo mdoc_deroff
+.Fa "char **dest"
+.Fa "const struct mdoc_node *node"
+.Fc
+.Ft "const struct mdoc_meta *"
+.Fo mdoc_meta
+.Fa "const struct mdoc *mdoc"
+.Fc
+.Ft "const struct mdoc_node *"
+.Fo mdoc_node
+.Fa "const struct mdoc *mdoc"
+.Fc
+.Vt extern const char * const * mdoc_argnames;
+.Vt extern const char * const * mdoc_macronames;
+.In sys/types.h
+.In mandoc.h
+.In man.h
+.Ft void
+.Fo man_deroff
+.Fa "char **dest"
+.Fa "const struct man_node *node"
+.Fc
+.Ft "const struct man_meta *"
+.Fo man_meta
+.Fa "const struct man *man"
+.Fc
+.Ft "const struct mparse *"
+.Fo man_mparse
+.Fa "const struct man *man"
+.Fc
+.Ft "const struct man_node *"
+.Fo man_node
+.Fa "const struct man *man"
+.Fc
+.Vt extern const char * const * man_macronames;
+.Sh DESCRIPTION
+The
+.Nm mandoc
+library parses a
+.Ux
+manual into an abstract syntax tree (AST).
+.Ux
+manuals are composed of
+.Xr mdoc 7
+or
+.Xr man 7 ,
+and may be mixed with
+.Xr roff 7 ,
+.Xr tbl 7 ,
+and
+.Xr eqn 7
+invocations.
+.Pp
+The following describes a general parse sequence:
+.Bl -enum
+.It
+initiate a parsing sequence with
+.Fn mparse_alloc ;
+.It
+parse files or file descriptors with
+.Fn mparse_readfd ;
+.It
+retrieve a parsed syntax tree, if the parse was successful, with
+.Fn mparse_result ;
+.It
+iterate over parse nodes with
+.Fn mdoc_node
+or
+.Fn man_node ;
+.It
+free all allocated memory with
+.Fn mparse_free ,
+or invoke
+.Fn mparse_reset
+and parse new files.
+.El
+.Sh REFERENCE
+This section documents the functions, types, and variables available
+via
+.In mandoc.h ,
+with the exception of those documented in
+.Xr mandoc_escape 3
+and
+.Xr mchars_alloc 3 .
+.Ss Types
+.Bl -ohang
+.It Vt "enum mandocerr"
+A fatal error, error, or warning message during parsing.
+.It Vt "enum mandoclevel"
+A classification of an
+.Vt "enum mandocerr"
+as regards system operation.
+.It Vt "struct mparse"
+An opaque pointer to a running parse sequence.
+Created with
+.Fn mparse_alloc
+and freed with
+.Fn mparse_free .
+This may be used across parsed input if
+.Fn mparse_reset
+is called between parses.
+.It Vt "mandocmsg"
+A prototype for a function to handle fatal error, error, and warning
+messages emitted by the parser.
+.El
+.Ss Functions
+.Bl -ohang
+.It Fn man_deroff
+Obtain a text-only representation of a
+.Vt struct man_node ,
+including text contained in its child nodes.
+To be used on children of the pointer returned from
+.Fn man_node .
+When it is no longer needed, the pointer returned from
+.Fn man_deroff
+can be passed to
+.Xr free 3 .
+.It Fn man_meta
+Obtain the meta-data of a successful
+.Xr man 7
+parse.
+This may only be used on a pointer returned by
+.Fn mparse_result .
+Declared in
+.In man.h ,
+implemented in
+.Pa man.c .
+.It Fn man_mparse
+Get the parser used for the current output.
+Declared in
+.In man.h ,
+implemented in
+.Pa man.c .
+.It Fn man_node
+Obtain the root node of a successful
+.Xr man 7
+parse.
+This may only be used on a pointer returned by
+.Fn mparse_result .
+Declared in
+.In man.h ,
+implemented in
+.Pa man.c .
+.It Fn mdoc_deroff
+Obtain a text-only representation of a
+.Vt struct mdoc_node ,
+including text contained in its child nodes.
+To be used on children of the pointer returned from
+.Fn mdoc_node .
+When it is no longer needed, the pointer returned from
+.Fn mdoc_deroff
+can be passed to
+.Xr free 3 .
+.It Fn mdoc_meta
+Obtain the meta-data of a successful
+.Xr mdoc
+parse.
+This may only be used on a pointer returned by
+.Fn mparse_result .
+Declared in
+.In mdoc.h ,
+implemented in
+.Pa mdoc.c .
+.It Fn mdoc_node
+Obtain the root node of a successful
+.Xr mdoc
+parse.
+This may only be used on a pointer returned by
+.Fn mparse_result .
+Declared in
+.In mdoc.h ,
+implemented in
+.Pa mdoc.c .
+.It Fn mparse_alloc
+Allocate a parser.
+The arguments have the following effect:
+.Bl -tag -offset 5n -width inttype
+.It Ar options
+When the
+.Dv MPARSE_MDOC
+or
+.Dv MPARSE_MAN
+bit is set, only that parser is used.
+Otherwise, the document type is automatically detected.
+.Pp
+When the
+.Dv MPARSE_SO
+bit is set,
+.Xr roff 7
+.Ic \&so
+file inclusion requests are always honoured.
+Otherwise, if the request is the only content in an input file,
+only the file name is remembered, to be returned in the
+.Fa sodest
+argument of
+.Fn mparse_result .
+.Pp
+When the
+.Dv MPARSE_QUICK
+bit is set, parsing is aborted after the NAME section.
+This is for example useful in
+.Xr makewhatis 8
+.Fl Q
+to quickly build minimal databases.
+.It Ar wlevel
+Can be set to
+.Dv MANDOCLEVEL_FATAL ,
+.Dv MANDOCLEVEL_ERROR ,
+or
+.Dv MANDOCLEVEL_WARNING .
+Messages below the selected level will be suppressed.
+.It Ar mmsg
+A callback function to handle errors and warnings.
+See
+.Pa main.c
+for an example.
+.It Ar defos
+A default string for the
+.Xr mdoc 7
+.Sq \&Os
+macro, overriding the
+.Dv OSNAME
+preprocessor definition and the results of
+.Xr uname 3 .
+.El
+.Pp
+The same parser may be used for multiple files so long as
+.Fn mparse_reset
+is called between parses.
+.Fn mparse_free
+must be called to free the memory allocated by this function.
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_free
+Free all memory allocated by
+.Fn mparse_alloc .
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_getkeep
+Acquire the keep buffer.
+Must follow a call of
+.Fn mparse_keep .
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_keep
+Instruct the parser to retain a copy of its parsed input.
+This can be acquired with subsequent
+.Fn mparse_getkeep
+calls.
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_readfd
+Parse a file or file descriptor.
+If
+.Va fd
+is -1,
+.Va fname
+is opened for reading.
+Otherwise,
+.Va fname
+is assumed to be the name associated with
+.Va fd .
+This may be called multiple times with different parameters; however,
+.Fn mparse_reset
+should be invoked between parses.
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_reset
+Reset a parser so that
+.Fn mparse_readfd
+may be used again.
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_result
+Obtain the result of a parse.
+Only successful parses
+.Po
+i.e., those where
+.Fn mparse_readfd
+returned less than MANDOCLEVEL_FATAL
+.Pc
+should invoke this function, in which case one of the three pointers will
+be filled in.
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_strerror
+Return a statically-allocated string representation of an error code.
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.It Fn mparse_strlevel
+Return a statically-allocated string representation of a level code.
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
+.El
+.Ss Variables
+.Bl -ohang
+.It Va man_macronames
+The string representation of a man macro as indexed by
+.Vt "enum mant" .
+.It Va mdoc_argnames
+The string representation of a mdoc macro argument as indexed by
+.Vt "enum mdocargt" .
+.It Va mdoc_macronames
+The string representation of a mdoc macro as indexed by
+.Vt "enum mdoct" .
+.El
+.Sh IMPLEMENTATION NOTES
+This section consists of structural documentation for
+.Xr mdoc 7
+and
+.Xr man 7
+syntax trees and strings.
+.Ss Man and Mdoc Strings
+Strings may be extracted from mdoc and man meta-data, or from text
+nodes (MDOC_TEXT and MAN_TEXT, respectively).
+These strings have special non-printing formatting cues embedded in the
+text itself, as well as
+.Xr roff 7
+escapes preserved from input.
+Implementing systems will need to handle both situations to produce
+human-readable text.
+In general, strings may be assumed to consist of 7-bit ASCII characters.
+.Pp
+The following non-printing characters may be embedded in text strings:
+.Bl -tag -width Ds
+.It Dv ASCII_NBRSP
+A non-breaking space character.
+.It Dv ASCII_HYPH
+A soft hyphen.
+.It Dv ASCII_BREAK
+A breakable zero-width space.
+.El
+.Pp
+Escape characters are also passed verbatim into text strings.
+An escape character is a sequence of characters beginning with the
+backslash
+.Pq Sq \e .
+To construct human-readable text, these should be intercepted with
+.Xr mandoc_escape 3
+and converted with one the functions described in
+.Xr mchars_alloc 3 .
+.Ss Man Abstract Syntax Tree
+This AST is governed by the ontological rules dictated in
+.Xr man 7
+and derives its terminology accordingly.
+.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.
+.Pp
+The tree itself is arranged according to the following normal form,
+where capitalised non-terminals represent nodes.
+.Pp
+.Bl -tag -width "ELEMENTXX" -compact
+.It ROOT
+\(<- mnode+
+.It mnode
+\(<- ELEMENT | TEXT | BLOCK
+.It BLOCK
+\(<- HEAD BODY
+.It HEAD
+\(<- mnode*
+.It BODY
+\(<- mnode*
+.It ELEMENT
+\(<- ELEMENT | TEXT*
+.It TEXT
+\(<- [[:ascii:]]*
+.El
+.Pp
+The only elements capable of nesting other elements are those with
+next-line scope as documented in
+.Xr man 7 .
+.Ss Mdoc Abstract Syntax Tree
+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 .
+.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 nchild ,
+.Va next
+and
+.Va prev
+fields) and some type-specific data, in particular, for nodes generated
+from macros, the generating macro in the
+.Va tok
+field.
+.Pp
+The tree itself is arranged according to the following normal form,
+where capitalised non-terminals represent nodes.
+.Pp
+.Bl -tag -width "ELEMENTXX" -compact
+.It ROOT
+\(<- mnode+
+.It mnode
+\(<- BLOCK | ELEMENT | TEXT
+.It BLOCK
+\(<- HEAD [TEXT] (BODY [TEXT])+ [TAIL [TEXT]]
+.It ELEMENT
+\(<- TEXT*
+.It HEAD
+\(<- mnode*
+.It BODY
+\(<- mnode* [ENDBODY mnode*]
+.It TAIL
+\(<- mnode*
+.It TEXT
+\(<- [[:ascii:]]*
+.El
+.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.
+Multiple body parts are only found in invocations of
+.Sq \&Bl \-column ,
+where a new body introduces a new phrase.
+.Pp
+The
+.Xr mdoc 7
+syntax tree accommodates for broken block structures as well.
+The ENDBODY node is available to end the formatting associated
+with a given block before the physical end of that block.
+It has a non-null
+.Va end
+field, is of the BODY
+.Va type ,
+has the same
+.Va tok
+as the BLOCK it is ending, and has a
+.Va pending
+field pointing to that BLOCK's BODY node.
+It is an indirect child of that BODY node
+and has no children of its own.
+.Pp
+An ENDBODY node is generated when a block ends while one of its child
+blocks is still open, like in the following example:
+.Bd -literal -offset indent
+\&.Ao ao
+\&.Bo bo ac
+\&.Ac bc
+\&.Bc end
+.Ed
+.Pp
+This example results in the following block structure:
+.Bd -literal -offset indent
+BLOCK Ao
+ HEAD Ao
+ BODY Ao
+ TEXT ao
+ BLOCK Bo, pending -> Ao
+ HEAD Bo
+ BODY Bo
+ TEXT bo
+ TEXT ac
+ ENDBODY Ao, pending -> Ao
+ TEXT bc
+TEXT end
+.Ed
+.Pp
+Here, the formatting of the
+.Sq \&Ao
+block extends from TEXT ao to TEXT ac,
+while the formatting of the
+.Sq \&Bo
+block extends from TEXT bo to TEXT bc.
+It renders as follows in
+.Fl T Ns Cm ascii
+mode:
+.Pp
+.Dl <ao [bo ac> bc] end
+.Pp
+Support for badly-nested blocks is only provided for backward
+compatibility with some older
+.Xr mdoc 7
+implementations.
+Using badly-nested blocks is
+.Em strongly discouraged ;
+for example, the
+.Fl T Ns Cm html
+and
+.Fl T Ns Cm xhtml
+front-ends to
+.Xr mandoc 1
+are unable to render them in any meaningful way.
+Furthermore, behaviour when encountering badly-nested blocks is not
+consistent across troff implementations, especially when using multiple
+levels of badly-nested blocks.
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr mandoc_escape 3 ,
+.Xr mandoc_malloc 3 ,
+.Xr mchars_alloc 3 ,
+.Xr eqn 7 ,
+.Xr man 7 ,
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr roff 7 ,
+.Xr tbl 7
+.Sh AUTHORS
+The
+.Nm
+library was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
diff --git a/contrib/mdocml/mandoc.c b/contrib/mdocml/mandoc.c
new file mode 100644
index 0000000..dd22272
--- /dev/null
+++ b/contrib/mdocml/mandoc.c
@@ -0,0 +1,600 @@
+/* $Id: mandoc.c,v 1.83 2014/07/06 19:09:00 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+
+#define DATESIZE 32
+
+static int a2time(time_t *, const char *, const char *);
+static char *time2a(time_t);
+
+
+enum mandoc_esc
+mandoc_escape(const char **end, const char **start, int *sz)
+{
+ const char *local_start;
+ int local_sz;
+ char term;
+ enum mandoc_esc gly;
+
+ /*
+ * When the caller doesn't provide return storage,
+ * use local storage.
+ */
+
+ if (NULL == start)
+ start = &local_start;
+ if (NULL == sz)
+ sz = &local_sz;
+
+ /*
+ * Beyond the backslash, at least one input character
+ * is part of the escape sequence. With one exception
+ * (see below), that character won't be returned.
+ */
+
+ gly = ESCAPE_ERROR;
+ *start = ++*end;
+ *sz = 0;
+ term = '\0';
+
+ switch ((*start)[-1]) {
+ /*
+ * First the glyphs. There are several different forms of
+ * these, but each eventually returns a substring of the glyph
+ * name.
+ */
+ case '(':
+ gly = ESCAPE_SPECIAL;
+ *sz = 2;
+ break;
+ case '[':
+ gly = ESCAPE_SPECIAL;
+ /*
+ * Unicode escapes are defined in groff as \[uXXXX] to
+ * \[u10FFFF], where the contained value must be a valid
+ * Unicode codepoint. Here, however, only check whether
+ * it's not a zero-width escape.
+ */
+ if ('u' == (*start)[0] && ']' != (*start)[1])
+ gly = ESCAPE_UNICODE;
+ term = ']';
+ break;
+ case 'C':
+ if ('\'' != **start)
+ return(ESCAPE_ERROR);
+ *start = ++*end;
+ if ('u' == (*start)[0] && '\'' != (*start)[1])
+ gly = ESCAPE_UNICODE;
+ else
+ gly = ESCAPE_SPECIAL;
+ term = '\'';
+ break;
+
+ /*
+ * Escapes taking no arguments at all.
+ */
+ case 'd':
+ /* FALLTHROUGH */
+ case 'u':
+ return(ESCAPE_IGNORE);
+
+ /*
+ * The \z escape is supposed to output the following
+ * character without advancing the cursor position.
+ * Since we are mostly dealing with terminal mode,
+ * let us just skip the next character.
+ */
+ case 'z':
+ return(ESCAPE_SKIPCHAR);
+
+ /*
+ * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
+ * 'X' is the trigger. These have opaque sub-strings.
+ */
+ case 'F':
+ /* FALLTHROUGH */
+ case 'g':
+ /* FALLTHROUGH */
+ case 'k':
+ /* FALLTHROUGH */
+ case 'M':
+ /* FALLTHROUGH */
+ case 'm':
+ /* FALLTHROUGH */
+ case 'n':
+ /* FALLTHROUGH */
+ case 'V':
+ /* FALLTHROUGH */
+ case 'Y':
+ gly = ESCAPE_IGNORE;
+ /* FALLTHROUGH */
+ case 'f':
+ if (ESCAPE_ERROR == gly)
+ gly = ESCAPE_FONT;
+ switch (**start) {
+ case '(':
+ *start = ++*end;
+ *sz = 2;
+ break;
+ case '[':
+ *start = ++*end;
+ term = ']';
+ break;
+ default:
+ *sz = 1;
+ break;
+ }
+ break;
+
+ /*
+ * These escapes are of the form \X'Y', where 'X' is the trigger
+ * and 'Y' is any string. These have opaque sub-strings.
+ * The \B and \w escapes are handled in roff.c, roff_res().
+ */
+ case 'A':
+ /* FALLTHROUGH */
+ case 'b':
+ /* FALLTHROUGH */
+ case 'D':
+ /* FALLTHROUGH */
+ case 'o':
+ /* FALLTHROUGH */
+ case 'R':
+ /* FALLTHROUGH */
+ case 'X':
+ /* FALLTHROUGH */
+ case 'Z':
+ if ('\0' == **start)
+ return(ESCAPE_ERROR);
+ gly = ESCAPE_IGNORE;
+ term = **start;
+ *start = ++*end;
+ break;
+
+ /*
+ * These escapes are of the form \X'N', where 'X' is the trigger
+ * and 'N' resolves to a numerical expression.
+ */
+ case 'h':
+ /* FALLTHROUGH */
+ case 'H':
+ /* FALLTHROUGH */
+ case 'L':
+ /* FALLTHROUGH */
+ case 'l':
+ /* FALLTHROUGH */
+ case 'S':
+ /* FALLTHROUGH */
+ case 'v':
+ /* FALLTHROUGH */
+ case 'x':
+ if (strchr(" %&()*+-./0123456789:<=>", **start)) {
+ ++*end;
+ return(ESCAPE_ERROR);
+ }
+ gly = ESCAPE_IGNORE;
+ term = **start;
+ *start = ++*end;
+ break;
+
+ /*
+ * Special handling for the numbered character escape.
+ * XXX Do any other escapes need similar handling?
+ */
+ case 'N':
+ if ('\0' == **start)
+ return(ESCAPE_ERROR);
+ (*end)++;
+ if (isdigit((unsigned char)**start)) {
+ *sz = 1;
+ return(ESCAPE_IGNORE);
+ }
+ (*start)++;
+ while (isdigit((unsigned char)**end))
+ (*end)++;
+ *sz = *end - *start;
+ if ('\0' != **end)
+ (*end)++;
+ return(ESCAPE_NUMBERED);
+
+ /*
+ * Sizes get a special category of their own.
+ */
+ case 's':
+ gly = ESCAPE_IGNORE;
+
+ /* See +/- counts as a sign. */
+ if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
+ (*end)++;
+
+ switch (**end) {
+ case '(':
+ *start = ++*end;
+ *sz = 2;
+ break;
+ case '[':
+ *start = ++*end;
+ term = ']';
+ break;
+ case '\'':
+ *start = ++*end;
+ term = '\'';
+ break;
+ default:
+ *sz = 1;
+ break;
+ }
+
+ break;
+
+ /*
+ * Anything else is assumed to be a glyph.
+ * In this case, pass back the character after the backslash.
+ */
+ default:
+ gly = ESCAPE_SPECIAL;
+ *start = --*end;
+ *sz = 1;
+ break;
+ }
+
+ assert(ESCAPE_ERROR != gly);
+
+ /*
+ * Read up to the terminating character,
+ * paying attention to nested escapes.
+ */
+
+ if ('\0' != term) {
+ while (**end != term) {
+ switch (**end) {
+ case '\0':
+ return(ESCAPE_ERROR);
+ case '\\':
+ (*end)++;
+ if (ESCAPE_ERROR ==
+ mandoc_escape(end, NULL, NULL))
+ return(ESCAPE_ERROR);
+ break;
+ default:
+ (*end)++;
+ break;
+ }
+ }
+ *sz = (*end)++ - *start;
+ } else {
+ assert(*sz > 0);
+ if ((size_t)*sz > strlen(*start))
+ return(ESCAPE_ERROR);
+ *end += *sz;
+ }
+
+ /* Run post-processors. */
+
+ switch (gly) {
+ case ESCAPE_FONT:
+ if (2 == *sz) {
+ if ('C' == **start) {
+ /*
+ * Treat constant-width font modes
+ * just like regular font modes.
+ */
+ (*start)++;
+ (*sz)--;
+ } else {
+ if ('B' == (*start)[0] && 'I' == (*start)[1])
+ gly = ESCAPE_FONTBI;
+ break;
+ }
+ } else if (1 != *sz)
+ break;
+
+ switch (**start) {
+ case '3':
+ /* FALLTHROUGH */
+ case 'B':
+ gly = ESCAPE_FONTBOLD;
+ break;
+ case '2':
+ /* FALLTHROUGH */
+ case 'I':
+ gly = ESCAPE_FONTITALIC;
+ break;
+ case 'P':
+ gly = ESCAPE_FONTPREV;
+ break;
+ case '1':
+ /* FALLTHROUGH */
+ case 'R':
+ gly = ESCAPE_FONTROMAN;
+ break;
+ }
+ break;
+ case ESCAPE_SPECIAL:
+ if (1 == *sz && 'c' == **start)
+ gly = ESCAPE_NOSPACE;
+ break;
+ default:
+ break;
+ }
+
+ return(gly);
+}
+
+/*
+ * Parse a quoted or unquoted roff-style request or macro argument.
+ * Return a pointer to the parsed argument, which is either the original
+ * pointer or advanced by one byte in case the argument is quoted.
+ * NUL-terminate the argument in place.
+ * Collapse pairs of quotes inside quoted arguments.
+ * Advance the argument pointer to the next argument,
+ * or to the NUL byte terminating the argument line.
+ */
+char *
+mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
+{
+ char *start, *cp;
+ int quoted, pairs, white;
+
+ /* Quoting can only start with a new word. */
+ start = *cpp;
+ quoted = 0;
+ if ('"' == *start) {
+ quoted = 1;
+ start++;
+ }
+
+ pairs = 0;
+ white = 0;
+ for (cp = start; '\0' != *cp; cp++) {
+
+ /*
+ * Move the following text left
+ * after quoted quotes and after "\\" and "\t".
+ */
+ if (pairs)
+ cp[-pairs] = cp[0];
+
+ if ('\\' == cp[0]) {
+ /*
+ * In copy mode, translate double to single
+ * backslashes and backslash-t to literal tabs.
+ */
+ switch (cp[1]) {
+ case 't':
+ cp[0] = '\t';
+ /* FALLTHROUGH */
+ case '\\':
+ pairs++;
+ cp++;
+ break;
+ case ' ':
+ /* Skip escaped blanks. */
+ if (0 == quoted)
+ cp++;
+ break;
+ default:
+ break;
+ }
+ } else if (0 == quoted) {
+ if (' ' == cp[0]) {
+ /* Unescaped blanks end unquoted args. */
+ white = 1;
+ break;
+ }
+ } else if ('"' == cp[0]) {
+ if ('"' == cp[1]) {
+ /* Quoted quotes collapse. */
+ pairs++;
+ cp++;
+ } else {
+ /* Unquoted quotes end quoted args. */
+ quoted = 2;
+ break;
+ }
+ }
+ }
+
+ /* Quoted argument without a closing quote. */
+ if (1 == quoted)
+ mandoc_msg(MANDOCERR_ARG_QUOTE, parse, ln, *pos, NULL);
+
+ /* NUL-terminate this argument and move to the next one. */
+ if (pairs)
+ cp[-pairs] = '\0';
+ if ('\0' != *cp) {
+ *cp++ = '\0';
+ while (' ' == *cp)
+ cp++;
+ }
+ *pos += (int)(cp - start) + (quoted ? 1 : 0);
+ *cpp = cp;
+
+ if ('\0' == *cp && (white || ' ' == cp[-1]))
+ mandoc_msg(MANDOCERR_SPACE_EOL, parse, ln, *pos, NULL);
+
+ return(start);
+}
+
+static int
+a2time(time_t *t, const char *fmt, const char *p)
+{
+ struct tm tm;
+ char *pp;
+
+ memset(&tm, 0, sizeof(struct tm));
+
+ pp = NULL;
+#ifdef HAVE_STRPTIME
+ pp = strptime(p, fmt, &tm);
+#endif
+ if (NULL != pp && '\0' == *pp) {
+ *t = mktime(&tm);
+ return(1);
+ }
+
+ return(0);
+}
+
+static char *
+time2a(time_t t)
+{
+ struct tm *tm;
+ char *buf, *p;
+ size_t ssz;
+ int isz;
+
+ tm = localtime(&t);
+
+ /*
+ * Reserve space:
+ * up to 9 characters for the month (September) + blank
+ * up to 2 characters for the day + comma + blank
+ * 4 characters for the year and a terminating '\0'
+ */
+ p = buf = mandoc_malloc(10 + 4 + 4 + 1);
+
+ if (0 == (ssz = strftime(p, 10 + 1, "%B ", tm)))
+ goto fail;
+ p += (int)ssz;
+
+ if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)))
+ goto fail;
+ p += isz;
+
+ if (0 == strftime(p, 4 + 1, "%Y", tm))
+ goto fail;
+ return(buf);
+
+fail:
+ free(buf);
+ return(NULL);
+}
+
+char *
+mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
+{
+ char *out;
+ time_t t;
+
+ if (NULL == in || '\0' == *in ||
+ 0 == strcmp(in, "$" "Mdocdate$")) {
+ mandoc_msg(MANDOCERR_DATE_MISSING, parse, ln, pos, NULL);
+ time(&t);
+ }
+ else if (a2time(&t, "%Y-%m-%d", in))
+ t = 0;
+ else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) &&
+ !a2time(&t, "%b %d, %Y", in)) {
+ mandoc_msg(MANDOCERR_DATE_BAD, parse, ln, pos, in);
+ t = 0;
+ }
+ out = t ? time2a(t) : NULL;
+ return(out ? out : mandoc_strdup(in));
+}
+
+int
+mandoc_eos(const char *p, size_t sz)
+{
+ const char *q;
+ int enclosed, found;
+
+ if (0 == sz)
+ return(0);
+
+ /*
+ * End-of-sentence recognition must include situations where
+ * some symbols, such as `)', allow prior EOS punctuation to
+ * propagate outward.
+ */
+
+ enclosed = found = 0;
+ for (q = p + (int)sz - 1; q >= p; q--) {
+ switch (*q) {
+ case '\"':
+ /* FALLTHROUGH */
+ case '\'':
+ /* FALLTHROUGH */
+ case ']':
+ /* FALLTHROUGH */
+ case ')':
+ if (0 == found)
+ enclosed = 1;
+ break;
+ case '.':
+ /* FALLTHROUGH */
+ case '!':
+ /* FALLTHROUGH */
+ case '?':
+ found = 1;
+ break;
+ default:
+ return(found && (!enclosed || isalnum((unsigned char)*q)));
+ }
+ }
+
+ return(found && !enclosed);
+}
+
+/*
+ * Convert a string to a long that may not be <0.
+ * If the string is invalid, or is less than 0, return -1.
+ */
+int
+mandoc_strntoi(const char *p, size_t sz, int base)
+{
+ char buf[32];
+ char *ep;
+ long v;
+
+ if (sz > 31)
+ return(-1);
+
+ memcpy(buf, p, sz);
+ buf[(int)sz] = '\0';
+
+ errno = 0;
+ v = strtol(buf, &ep, base);
+
+ if (buf[0] == '\0' || *ep != '\0')
+ return(-1);
+
+ if (v > INT_MAX)
+ v = INT_MAX;
+ if (v < INT_MIN)
+ v = INT_MIN;
+
+ return((int)v);
+}
diff --git a/contrib/mdocml/mandoc.db.5 b/contrib/mdocml/mandoc.db.5
new file mode 100644
index 0000000..8d56495
--- /dev/null
+++ b/contrib/mdocml/mandoc.db.5
@@ -0,0 +1,144 @@
+.\" $Id: mandoc.db.5,v 1.1 2014/04/15 20:18:26 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: April 15 2014 $
+.Dt MANDOC.DB 5
+.Os
+.Sh NAME
+.Nm mandoc.db
+.Nd manual page database
+.Sh DESCRIPTION
+The
+.Nm
+SQLite3 file format is used to store information about installed manual
+pages to facilitate semantic searching for manuals.
+Each manual page tree contains its own
+.Nm
+file; see
+.Sx FILES
+for examples.
+.Pp
+Such database files are generated by
+.Xr makewhatis 8
+and used by
+.Xr apropos 1
+and
+.Xr whatis 1 .
+.Pp
+One line in the following tables describes:
+.Bl -tag -width Ds
+.It Sy mpages
+One physical manual page file, no matter how many times and under which
+names it may appear in the file system.
+.It Sy mlinks
+One entry in the file system, no matter which content it points to.
+.It Sy names
+One manual page name, no matter whether it appears in a page header,
+in a NAME or SYNOPSIS section, or as a file name.
+.It Sy keys
+One chunk of text from some macro invocation.
+.El
+.Pp
+Each record in the latter three tables uses its
+.Va pageid
+column to point to a record in the
+.Sy mpages
+table.
+.Pp
+The other columns are as follows; unless stated otherwise, they are
+of type
+.Vt TEXT .
+.Bl -tag -width mpages.desc
+.It Sy mpages.desc
+The description line
+.Pq Sq \&Nd
+of the page.
+.It Sy mpages.form
+The
+.Vt INTEGER
+1 if the page is unformatted, i.e. in
+.Xr mdoc 7
+or
+.Xr man 7
+format, and 2 if it is formatted, i.e. a
+.Sq cat
+page.
+.It Sy mlinks.sec
+The manual section as found in the subdirectory name.
+.It Sy mlinks.arch
+The manual architecture as found in the subdirectory name, or
+.Qq any .
+.It Sy mlinks.name
+The manual name as found in the file name.
+.It Sy names.bits
+An
+.Vt INTEGER
+bit mask telling whether the name came from a header line, from the
+NAME or SYNOPSIS section, or from a file name.
+Bits are defined in
+.In mansearch.h .
+.It Sy names.name
+The name itself.
+.It Sy keys.bits
+An
+.Vt INTEGER
+bit mask telling which semantic contexts the key was found in;
+defined in
+.In mansearch.h ,
+documented in
+.Xr apropos 1 .
+.It Sy keys.key
+The string found in those contexts.
+.El
+.Sh FILES
+.Bl -tag -width /usr/share/mandoc.db -compact
+.It Pa /usr/share/mandoc.db
+The manual page database for the base system.
+.It Pa /usr/X11R6/mandoc.db
+The same for the
+.Xr X 7
+Window System.
+.It Pa /usr/local/mandoc.db
+The same for
+.Xr packages 7 .
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr man 1 ,
+.Xr sqlite3 1 ,
+.Xr whatis 1 ,
+.Xr mansearch 3 ,
+.Xr makewhatis 8
+.Sh HISTORY
+A manual page database
+.Pa /usr/lib/whatis
+first appeared in
+.Bx 2 .
+The present format first appeared in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+The original version of
+.Xr makewhatis 8
+was written by
+.An Bill Joy
+in 1979.
+An SQLite3 version was first implemented by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+in 2012.
+The present database format was designed by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
+in 2014.
diff --git a/contrib/mdocml/mandoc.h b/contrib/mdocml/mandoc.h
new file mode 100644
index 0000000..fd91314
--- /dev/null
+++ b/contrib/mdocml/mandoc.h
@@ -0,0 +1,438 @@
+/* $Id: mandoc.h,v 1.152 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef MANDOC_H
+#define MANDOC_H
+
+#define ASCII_NBRSP 31 /* non-breaking space */
+#define ASCII_HYPH 30 /* breakable hyphen */
+#define ASCII_BREAK 29 /* breakable zero-width space */
+
+/*
+ * Status level. This refers to both internal status (i.e., whilst
+ * running, when warnings/errors are reported) and an indicator of a
+ * threshold of when to halt (when said internal state exceeds the
+ * threshold).
+ */
+enum mandoclevel {
+ MANDOCLEVEL_OK = 0,
+ MANDOCLEVEL_RESERVED,
+ MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */
+ MANDOCLEVEL_ERROR, /* input has been thrown away */
+ MANDOCLEVEL_FATAL, /* input is borked */
+ MANDOCLEVEL_BADARG, /* bad argument in invocation */
+ MANDOCLEVEL_SYSERR, /* system error */
+ MANDOCLEVEL_MAX
+};
+
+/*
+ * All possible things that can go wrong within a parse, be it libroff,
+ * libmdoc, or libman.
+ */
+enum mandocerr {
+ MANDOCERR_OK,
+
+ MANDOCERR_WARNING, /* ===== start of warnings ===== */
+
+ /* related to the prologue */
+ MANDOCERR_DT_NOTITLE, /* missing manual title, using UNTITLED: line */
+ MANDOCERR_TH_NOTITLE, /* missing manual title, using "": [macro] */
+ MANDOCERR_TITLE_CASE, /* lower case character in document title */
+ MANDOCERR_MSEC_MISSING, /* missing manual section, using "": macro */
+ MANDOCERR_MSEC_BAD, /* unknown manual section: Dt ... section */
+ MANDOCERR_ARCH_BAD, /* unknown manual volume or arch: Dt ... volume */
+ MANDOCERR_DATE_MISSING, /* missing date, using today's date */
+ MANDOCERR_DATE_BAD, /* cannot parse date, using it verbatim: date */
+ MANDOCERR_OS_MISSING, /* missing Os macro, using "" */
+ MANDOCERR_PROLOG_REP, /* duplicate prologue macro: macro */
+ MANDOCERR_PROLOG_LATE, /* late prologue macro: macro */
+ MANDOCERR_DT_LATE, /* skipping late title macro: Dt args */
+ MANDOCERR_PROLOG_ORDER, /* prologue macros out of order: macros */
+
+ /* related to document structure */
+ MANDOCERR_SO, /* .so is fragile, better use ln(1): so path */
+ MANDOCERR_DOC_EMPTY, /* no document body */
+ MANDOCERR_SEC_BEFORE, /* content before first section header: macro */
+ MANDOCERR_NAMESEC_FIRST, /* first section is not NAME: Sh title */
+ MANDOCERR_NAMESEC_BAD, /* bad NAME section contents: macro */
+ MANDOCERR_SEC_ORDER, /* sections out of conventional order: Sh title */
+ MANDOCERR_SEC_REP, /* duplicate section title: Sh title */
+ MANDOCERR_SEC_MSEC, /* unexpected section: Sh title for ... only */
+
+ /* related to macros and nesting */
+ MANDOCERR_MACRO_OBS, /* obsolete macro: macro */
+ MANDOCERR_PAR_SKIP, /* skipping paragraph macro: macro ... */
+ MANDOCERR_PAR_MOVE, /* moving paragraph macro out of list: macro */
+ MANDOCERR_NS_SKIP, /* skipping no-space macro */
+ MANDOCERR_BLK_NEST, /* blocks badly nested: macro ... */
+ MANDOCERR_BD_NEST, /* nested displays are not portable: macro ... */
+ MANDOCERR_BL_MOVE, /* moving content out of list: macro */
+ MANDOCERR_VT_CHILD, /* .Vt block has child macro: macro */
+ MANDOCERR_FI_SKIP, /* fill mode already enabled, skipping: fi */
+ MANDOCERR_NF_SKIP, /* fill mode already disabled, skipping: nf */
+ MANDOCERR_BLK_LINE, /* line scope broken: macro breaks macro */
+
+ /* related to missing arguments */
+ MANDOCERR_REQ_EMPTY, /* skipping empty request: request */
+ MANDOCERR_COND_EMPTY, /* conditional request controls empty scope */
+ MANDOCERR_MACRO_EMPTY, /* skipping empty macro: macro */
+ MANDOCERR_ARG_EMPTY, /* empty argument, using 0n: macro arg */
+ MANDOCERR_ARGCWARN, /* argument count wrong */
+ MANDOCERR_BD_NOTYPE, /* missing display type, using -ragged: Bd */
+ MANDOCERR_BL_LATETYPE, /* list type is not the first argument: Bl arg */
+ MANDOCERR_BL_NOWIDTH, /* missing -width in -tag list, using 8n */
+ MANDOCERR_EX_NONAME, /* missing utility name, using "": Ex */
+ MANDOCERR_IT_NOHEAD, /* empty head in list item: Bl -type It */
+ MANDOCERR_IT_NOBODY, /* empty list item: Bl -type It */
+ MANDOCERR_BF_NOFONT, /* missing font type, using \fR: Bf */
+ MANDOCERR_BF_BADFONT, /* unknown font type, using \fR: Bf font */
+ MANDOCERR_ARG_STD, /* missing -std argument, adding it: macro */
+
+ /* related to bad arguments */
+ MANDOCERR_ARG_QUOTE, /* unterminated quoted argument */
+ MANDOCERR_ARG_REP, /* duplicate argument: macro arg */
+ MANDOCERR_AN_REP, /* skipping duplicate argument: An -arg */
+ MANDOCERR_BD_REP, /* skipping duplicate display type: Bd -type */
+ MANDOCERR_BL_REP, /* skipping duplicate list type: Bl -type */
+ MANDOCERR_BL_SKIPW, /* skipping -width argument: Bl -type */
+ MANDOCERR_AT_BAD, /* unknown AT&T UNIX version: At version */
+ MANDOCERR_RS_BAD, /* invalid content in Rs block: macro */
+ MANDOCERR_SM_BAD, /* invalid Boolean argument: macro arg */
+ MANDOCERR_FT_BAD, /* unknown font, skipping request: ft font */
+
+ /* related to plain text */
+ MANDOCERR_FI_BLANK, /* blank line in fill mode, using .sp */
+ MANDOCERR_FI_TAB, /* tab in filled text */
+ MANDOCERR_SPACE_EOL, /* whitespace at end of input line */
+ MANDOCERR_COMMENT_BAD, /* bad comment style */
+ MANDOCERR_ESC_BAD, /* invalid escape sequence: esc */
+ MANDOCERR_STR_UNDEF, /* undefined string, using "": name */
+
+ MANDOCERR_ERROR, /* ===== start of errors ===== */
+
+ /* related to equations */
+ MANDOCERR_EQNNSCOPE, /* unexpected equation scope closure*/
+ MANDOCERR_EQNSCOPE, /* equation scope open on exit */
+ MANDOCERR_EQNBADSCOPE, /* overlapping equation scopes */
+ MANDOCERR_EQNEOF, /* unexpected end of equation */
+ MANDOCERR_EQNSYNT, /* equation syntax error */
+
+ /* related to tables */
+ MANDOCERR_TBL, /* bad table syntax */
+ MANDOCERR_TBLOPT, /* bad table option */
+ MANDOCERR_TBLLAYOUT, /* bad table layout */
+ MANDOCERR_TBLNOLAYOUT, /* no table layout cells specified */
+ MANDOCERR_TBLNODATA, /* no table data cells specified */
+ MANDOCERR_TBLIGNDATA, /* ignore data in cell */
+ MANDOCERR_TBLBLOCK, /* data block still open */
+ MANDOCERR_TBLEXTRADAT, /* ignoring extra data cells */
+
+ /* related to document structure and macros */
+ MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
+ MANDOCERR_BADCHAR, /* skipping bad character: number */
+ MANDOCERR_MACRO, /* skipping unknown macro: macro */
+ MANDOCERR_IT_STRAY, /* skipping item outside list: It ... */
+ MANDOCERR_TA_STRAY, /* skipping column outside column list: Ta */
+ MANDOCERR_BLK_NOTOPEN, /* skipping end of block that is not open */
+ MANDOCERR_BLK_BROKEN, /* inserting missing end of block: macro ... */
+ MANDOCERR_BLK_NOEND, /* appending missing end of block: macro */
+
+ /* related to request and macro arguments */
+ MANDOCERR_NAMESC, /* escaped character not allowed in a name: name */
+ MANDOCERR_ARGCOUNT, /* argument count wrong */
+ MANDOCERR_BL_NOTYPE, /* missing list type, using -item: Bl */
+ MANDOCERR_NM_NONAME, /* missing manual name, using "": Nm */
+ MANDOCERR_OS_UNAME, /* uname(3) system call failed, using UNKNOWN */
+ MANDOCERR_ST_BAD, /* unknown standard specifier: St standard */
+ MANDOCERR_IT_NONUM, /* skipping request without numeric argument */
+ MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */
+ MANDOCERR_ARG_EXCESS, /* skipping excess arguments: macro ... args */
+
+ MANDOCERR_FATAL, /* ===== start of fatal errors ===== */
+
+ MANDOCERR_TOOLARGE, /* input too large */
+ MANDOCERR_BD_FILE, /* NOT IMPLEMENTED: Bd -file */
+ MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
+ MANDOCERR_SO_FAIL, /* .so request failed */
+
+ /* ===== system errors ===== */
+
+ MANDOCERR_SYSOPEN, /* cannot open file */
+ MANDOCERR_SYSSTAT, /* cannot stat file */
+ MANDOCERR_SYSREAD, /* cannot read file */
+
+ MANDOCERR_MAX
+};
+
+struct tbl_opts {
+ char tab; /* cell-separator */
+ char decimal; /* decimal point */
+ int linesize;
+ int opts;
+#define TBL_OPT_CENTRE (1 << 0)
+#define TBL_OPT_EXPAND (1 << 1)
+#define TBL_OPT_BOX (1 << 2)
+#define TBL_OPT_DBOX (1 << 3)
+#define TBL_OPT_ALLBOX (1 << 4)
+#define TBL_OPT_NOKEEP (1 << 5)
+#define TBL_OPT_NOSPACE (1 << 6)
+ int cols; /* number of columns */
+};
+
+/*
+ * The head of a table specifies all of its columns. When formatting a
+ * tbl_span, iterate over these and plug in data from the tbl_span when
+ * appropriate, using tbl_cell as a guide to placement.
+ */
+struct tbl_head {
+ int ident; /* 0 <= unique id < cols */
+ int vert; /* width of preceding vertical line */
+ struct tbl_head *next;
+ struct tbl_head *prev;
+};
+
+enum tbl_cellt {
+ TBL_CELL_CENTRE, /* c, C */
+ TBL_CELL_RIGHT, /* r, R */
+ TBL_CELL_LEFT, /* l, L */
+ TBL_CELL_NUMBER, /* n, N */
+ TBL_CELL_SPAN, /* s, S */
+ TBL_CELL_LONG, /* a, A */
+ TBL_CELL_DOWN, /* ^ */
+ TBL_CELL_HORIZ, /* _, - */
+ TBL_CELL_DHORIZ, /* = */
+ TBL_CELL_MAX
+};
+
+/*
+ * A cell in a layout row.
+ */
+struct tbl_cell {
+ struct tbl_cell *next;
+ int vert; /* width of preceding vertical line */
+ enum tbl_cellt pos;
+ size_t spacing;
+ int flags;
+#define TBL_CELL_TALIGN (1 << 0) /* t, T */
+#define TBL_CELL_BALIGN (1 << 1) /* d, D */
+#define TBL_CELL_BOLD (1 << 2) /* fB, B, b */
+#define TBL_CELL_ITALIC (1 << 3) /* fI, I, i */
+#define TBL_CELL_EQUAL (1 << 4) /* e, E */
+#define TBL_CELL_UP (1 << 5) /* u, U */
+#define TBL_CELL_WIGN (1 << 6) /* z, Z */
+ struct tbl_head *head;
+};
+
+/*
+ * A layout row.
+ */
+struct tbl_row {
+ struct tbl_row *next;
+ struct tbl_cell *first;
+ struct tbl_cell *last;
+ int vert; /* trailing vertical line */
+};
+
+enum tbl_datt {
+ TBL_DATA_NONE, /* has no data */
+ TBL_DATA_DATA, /* consists of data/string */
+ TBL_DATA_HORIZ, /* horizontal line */
+ TBL_DATA_DHORIZ, /* double-horizontal line */
+ TBL_DATA_NHORIZ, /* squeezed horizontal line */
+ TBL_DATA_NDHORIZ /* squeezed double-horizontal line */
+};
+
+/*
+ * A cell within a row of data. The "string" field contains the actual
+ * string value that's in the cell. The rest is layout.
+ */
+struct tbl_dat {
+ struct tbl_cell *layout; /* layout cell */
+ int spans; /* how many spans follow */
+ struct tbl_dat *next;
+ char *string; /* data (NULL if not TBL_DATA_DATA) */
+ enum tbl_datt pos;
+};
+
+enum tbl_spant {
+ TBL_SPAN_DATA, /* span consists of data */
+ TBL_SPAN_HORIZ, /* span is horizontal line */
+ TBL_SPAN_DHORIZ /* span is double horizontal line */
+};
+
+/*
+ * A row of data in a table.
+ */
+struct tbl_span {
+ struct tbl_opts *opts;
+ struct tbl_head *head;
+ struct tbl_row *layout; /* layout row */
+ struct tbl_dat *first;
+ struct tbl_dat *last;
+ int line; /* parse line */
+ int flags;
+#define TBL_SPAN_FIRST (1 << 0)
+#define TBL_SPAN_LAST (1 << 1)
+ enum tbl_spant pos;
+ struct tbl_span *next;
+};
+
+enum eqn_boxt {
+ EQN_ROOT, /* root of parse tree */
+ EQN_TEXT, /* text (number, variable, whatever) */
+ EQN_SUBEXPR, /* nested `eqn' subexpression */
+ EQN_LIST, /* subexpressions list */
+ EQN_MATRIX /* matrix subexpression */
+};
+
+enum eqn_markt {
+ EQNMARK_NONE = 0,
+ EQNMARK_DOT,
+ EQNMARK_DOTDOT,
+ EQNMARK_HAT,
+ EQNMARK_TILDE,
+ EQNMARK_VEC,
+ EQNMARK_DYAD,
+ EQNMARK_BAR,
+ EQNMARK_UNDER,
+ EQNMARK__MAX
+};
+
+enum eqn_fontt {
+ EQNFONT_NONE = 0,
+ EQNFONT_ROMAN,
+ EQNFONT_BOLD,
+ EQNFONT_FAT,
+ EQNFONT_ITALIC,
+ EQNFONT__MAX
+};
+
+enum eqn_post {
+ EQNPOS_NONE = 0,
+ EQNPOS_OVER,
+ EQNPOS_SUP,
+ EQNPOS_SUB,
+ EQNPOS_TO,
+ EQNPOS_FROM,
+ EQNPOS__MAX
+};
+
+enum eqn_pilet {
+ EQNPILE_NONE = 0,
+ EQNPILE_PILE,
+ EQNPILE_CPILE,
+ EQNPILE_RPILE,
+ EQNPILE_LPILE,
+ EQNPILE_COL,
+ EQNPILE_CCOL,
+ EQNPILE_RCOL,
+ EQNPILE_LCOL,
+ EQNPILE__MAX
+};
+
+ /*
+ * A "box" is a parsed mathematical expression as defined by the eqn.7
+ * grammar.
+ */
+struct eqn_box {
+ int size; /* font size of expression */
+#define EQN_DEFSIZE INT_MIN
+ enum eqn_boxt type; /* type of node */
+ struct eqn_box *first; /* first child node */
+ struct eqn_box *last; /* last child node */
+ struct eqn_box *next; /* node sibling */
+ struct eqn_box *parent; /* node sibling */
+ char *text; /* text (or NULL) */
+ char *left;
+ char *right;
+ enum eqn_post pos; /* position of next box */
+ enum eqn_markt mark; /* a mark about the box */
+ enum eqn_fontt font; /* font of box */
+ enum eqn_pilet pile; /* equation piling */
+};
+
+/*
+ * An equation consists of a tree of expressions starting at a given
+ * line and position.
+ */
+struct eqn {
+ char *name; /* identifier (or NULL) */
+ struct eqn_box *root; /* root mathematical expression */
+ int ln; /* invocation line */
+ int pos; /* invocation position */
+};
+
+/*
+ * Parse options.
+ */
+#define MPARSE_MDOC 1 /* assume -mdoc */
+#define MPARSE_MAN 2 /* assume -man */
+#define MPARSE_SO 4 /* honour .so requests */
+#define MPARSE_QUICK 8 /* abort the parse early */
+
+enum mandoc_esc {
+ ESCAPE_ERROR = 0, /* bail! unparsable escape */
+ ESCAPE_IGNORE, /* escape to be ignored */
+ ESCAPE_SPECIAL, /* a regular special character */
+ ESCAPE_FONT, /* a generic font mode */
+ ESCAPE_FONTBOLD, /* bold font mode */
+ ESCAPE_FONTITALIC, /* italic font mode */
+ ESCAPE_FONTBI, /* bold italic font mode */
+ ESCAPE_FONTROMAN, /* roman font mode */
+ ESCAPE_FONTPREV, /* previous font mode */
+ ESCAPE_NUMBERED, /* a numbered glyph */
+ ESCAPE_UNICODE, /* a unicode codepoint */
+ ESCAPE_NOSPACE, /* suppress space if the last on a line */
+ ESCAPE_SKIPCHAR /* skip the next character */
+};
+
+typedef void (*mandocmsg)(enum mandocerr, enum mandoclevel,
+ const char *, int, int, const char *);
+
+struct mparse;
+struct mchars;
+struct mdoc;
+struct man;
+
+__BEGIN_DECLS
+
+enum mandoc_esc mandoc_escape(const char **, const char **, int *);
+struct mchars *mchars_alloc(void);
+void mchars_free(struct mchars *);
+char mchars_num2char(const char *, size_t);
+int mchars_num2uc(const char *, size_t);
+int mchars_spec2cp(const struct mchars *,
+ const char *, size_t);
+const char *mchars_spec2str(const struct mchars *,
+ const char *, size_t, size_t *);
+struct mparse *mparse_alloc(int, enum mandoclevel, mandocmsg,
+ const char *);
+void mparse_free(struct mparse *);
+void mparse_keep(struct mparse *);
+enum mandoclevel mparse_readfd(struct mparse *, int, const char *);
+enum mandoclevel mparse_readmem(struct mparse *, const void *, size_t,
+ const char *);
+void mparse_reset(struct mparse *);
+void mparse_result(struct mparse *,
+ struct mdoc **, struct man **, char **);
+const char *mparse_getkeep(const struct mparse *);
+const char *mparse_strerror(enum mandocerr);
+const char *mparse_strlevel(enum mandoclevel);
+
+__END_DECLS
+
+#endif /*!MANDOC_H*/
diff --git a/contrib/mdocml/mandoc_aux.c b/contrib/mdocml/mandoc_aux.c
new file mode 100644
index 0000000..b537673
--- /dev/null
+++ b/contrib/mdocml/mandoc_aux.c
@@ -0,0 +1,121 @@
+/* $Id: mandoc_aux.c,v 1.3 2014/07/09 08:20:34 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+
+int
+mandoc_asprintf(char **dest, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vasprintf(dest, fmt, ap);
+ va_end(ap);
+
+ if (-1 == ret) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ret);
+}
+
+void *
+mandoc_calloc(size_t num, size_t size)
+{
+ void *ptr;
+
+ ptr = calloc(num, size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+void *
+mandoc_malloc(size_t size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+void *
+mandoc_realloc(void *ptr, size_t size)
+{
+
+ ptr = realloc(ptr, size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+void *
+mandoc_reallocarray(void *ptr, size_t num, size_t size)
+{
+
+ ptr = reallocarray(ptr, num, size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+char *
+mandoc_strdup(const char *ptr)
+{
+ char *p;
+
+ p = strdup(ptr);
+ if (NULL == p) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(p);
+}
+
+char *
+mandoc_strndup(const char *ptr, size_t sz)
+{
+ char *p;
+
+ p = mandoc_malloc(sz + 1);
+ memcpy(p, ptr, sz);
+ p[(int)sz] = '\0';
+ return(p);
+}
diff --git a/contrib/mdocml/mandoc_aux.h b/contrib/mdocml/mandoc_aux.h
new file mode 100644
index 0000000..04f4baf
--- /dev/null
+++ b/contrib/mdocml/mandoc_aux.h
@@ -0,0 +1,33 @@
+/* $Id: mandoc_aux.h,v 1.2 2014/04/23 21:06:41 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef MANDOC_AUX_H
+#define MANDOC_AUX_H
+
+__BEGIN_DECLS
+
+int mandoc_asprintf(char **, const char *, ...);
+void *mandoc_calloc(size_t, size_t);
+void *mandoc_malloc(size_t);
+void *mandoc_realloc(void *, size_t);
+void *mandoc_reallocarray(void *, size_t, size_t);
+char *mandoc_strdup(const char *);
+char *mandoc_strndup(const char *, size_t);
+
+__END_DECLS
+
+#endif /*!MANDOC_AUX_H*/
diff --git a/contrib/mdocml/mandoc_char.7 b/contrib/mdocml/mandoc_char.7
new file mode 100644
index 0000000..8d83566
--- /dev/null
+++ b/contrib/mdocml/mandoc_char.7
@@ -0,0 +1,747 @@
+.\" $Id: mandoc_char.7,v 1.56 2013/12/26 17:23:42 schwarze Exp $
+.\"
+.\" Copyright (c) 2003 Jason McIntyre <jmc@openbsd.org>
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: December 26 2013 $
+.Dt MANDOC_CHAR 7
+.Os
+.Sh NAME
+.Nm mandoc_char
+.Nd mandoc special characters
+.Sh DESCRIPTION
+This page documents the
+.Xr roff 7
+escape sequences accepted by
+.Xr mandoc 1
+to represent special characters in
+.Xr mdoc 7
+and
+.Xr man 7
+documents.
+.Pp
+The rendering depends on the
+.Xr mandoc 1
+output mode; in ASCII output, most characters are completely
+unintelligible.
+For that reason, using any of the special characters documented here,
+except those discussed in the
+.Sx DESCRIPTION ,
+is strongly discouraged; they are supported merely for backwards
+compatibility with existing documents.
+.Pp
+In particular, in English manual pages, do not use special-character
+escape sequences to represent national language characters in author
+names; instead, provide ASCII transcriptions of the names.
+.Ss Dashes and Hyphens
+In typography there are different types of dashes of various width:
+the hyphen (-),
+the minus sign (\-),
+the en-dash (\(en),
+and the em-dash (\(em).
+.Pp
+Hyphens are used for adjectives;
+to separate the two parts of a compound word;
+or to separate a word across two successive lines of text.
+The hyphen does not need to be escaped:
+.Bd -unfilled -offset indent
+blue-eyed
+lorry-driver
+.Ed
+.Pp
+The mathematical minus sign is used for negative numbers or subtraction.
+It should be written as
+.Sq \e- :
+.Bd -unfilled -offset indent
+a = 3 \e- 1;
+b = \e-2;
+.Ed
+.Pp
+The en-dash is used to separate the two elements of a range,
+or can be used the same way as an em-dash.
+It should be written as
+.Sq \e(en :
+.Bd -unfilled -offset indent
+pp. 95\e(en97.
+Go away \e(en or else!
+.Ed
+.Pp
+The em-dash can be used to show an interruption
+or can be used the same way as colons, semi-colons, or parentheses.
+It should be written as
+.Sq \e(em :
+.Bd -unfilled -offset indent
+Three things \e(em apples, oranges, and bananas.
+This is not that \e(em rather, this is that.
+.Ed
+.Pp
+Note:
+hyphens, minus signs, and en-dashes look identical under normal ASCII output.
+Other formats, such as PostScript, render them correctly,
+with differing widths.
+.Ss Spaces
+To separate words in normal text, for indenting and alignment
+in literal context, and when none of the following special cases apply,
+just use the normal space character
+.Pq Sq \ .
+.Pp
+When filling text, output lines may be broken between words, i.e. at space
+characters.
+To prevent a line break between two particular words,
+use the unpaddable non-breaking space escape sequence
+.Pq Sq \e\ \&
+instead of the normal space character.
+For example, the input string
+.Dq number\e\ 1
+will be kept together as
+.Dq number\ 1
+on the same output line.
+.Pp
+On request and macro lines, the normal space character serves as an
+argument delimiter.
+To include whitespace into arguments, quoting is usually the best choice;
+see the MACRO SYNTAX section in
+.Xr roff 7 .
+In some cases, using the non-breaking space escape sequence
+.Pq Sq \e\ \&
+may be preferable.
+.Pp
+To escape macro names and to protect whitespace at the end
+of input lines, the zero-width space
+.Pq Sq \e&
+is often useful.
+For example, in
+.Xr mdoc 7 ,
+a normal space character can be displayed in single quotes in either
+of the following ways:
+.Pp
+.Dl .Sq \(dq \(dq
+.Dl .Sq \e \e&
+.Ss Quotes
+On request and macro lines, the double-quote character
+.Pq Sq \(dq
+is handled specially to allow quoting.
+One way to prevent this special handling is by using the
+.Sq \e(dq
+escape sequence.
+.Pp
+Note that on text lines, literal double-quote characters can be used
+verbatim.
+All other quote-like characters can be used verbatim as well,
+even on request and macro lines.
+.Ss Periods
+The period
+.Pq Sq \&.
+is handled specially at the beginning of an input line,
+where it introduces a
+.Xr roff 7
+request or a macro, and when appearing alone as a macro argument in
+.Xr mdoc 7 .
+In such situations, prepend a zero-width space
+.Pq Sq \e&.
+to make it behave like normal text.
+.Pp
+Do not use the
+.Sq \e.
+escape sequence.
+It does not prevent special handling of the period.
+.Ss Backslashes
+To include a literal backslash
+.Pq Sq \e
+into the output, use the
+.Pq Sq \ee
+escape sequence.
+.Pp
+Note that doubling it
+.Pq Sq \e\e
+is not the right way to output a backslash.
+Because
+.Xr mandoc 1
+does not implement full
+.Xr roff 7
+functionality, it may work with
+.Xr mandoc 1 ,
+but it may have weird effects on complete
+.Xr roff 7
+implementations.
+.Sh SPECIAL CHARACTERS
+Special characters are encoded as
+.Sq \eX
+.Pq for a one-character escape ,
+.Sq \e(XX
+.Pq two-character ,
+and
+.Sq \e[N]
+.Pq N-character .
+For details, see the
+.Em Special Characters
+subsection of the
+.Xr roff 7
+manual.
+.Pp
+Spacing:
+.Bl -column "Input" "Description" -offset indent -compact
+.It Em Input Ta Em Description
+.It Sq \e\ \& Ta unpaddable non-breaking space
+.It \e~ Ta paddable non-breaking space
+.It \e0 Ta unpaddable, breaking digit-width space
+.It \e| Ta one-sixth \e(em narrow space, zero width in nroff mode
+.It \e^ Ta one-twelfth \e(em half-narrow space, zero width in nroff
+.It \e& Ta zero-width space
+.It \e% Ta zero-width space allowing hyphenation
+.El
+.Pp
+Lines:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 \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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "xxbracketrightbpx" Rendered Description -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "xxcoproductxx" "Rendered" "Description" -offset indent -compact
+.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
+.It \e[12] Ta \[12] Ta one-half
+.It \e[14] Ta \[14] Ta one-fourth
+.It \e[34] Ta \[34] Ta three-fourths
+.El
+.Pp
+Ligatures:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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 "Input" "Rendered" "Description" -offset indent -compact
+.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
+Predefined strings are inherited from the macro packages of historical
+troff implementations.
+They are
+.Em not recommended
+for use, as they differ across implementations.
+Manuals using these predefined strings are almost certainly not
+portable.
+.Pp
+Their syntax is 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 .
+For details, see the
+.Em Predefined Strings
+subsection of the
+.Xr roff 7
+manual.
+.Bl -column "Input" "Rendered" "Description" -offset indent
+.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
+.It \e*(Px Ta \*(Px Ta POSIX standard name
+.It \e*(Ai Ta \*(Ai Ta ANSI standard name
+.El
+.Sh UNICODE CHARACTERS
+The escape sequences
+.Pp
+.Dl \e[uXXXX] and \eC'uXXXX'
+.Pp
+are interpreted as Unicode codepoints.
+The codepoint must be in the range above U+0080 and less than U+10FFFF.
+For compatibility, the hexadecimal digits
+.Sq A
+to
+.Sq F
+must be given as uppercase characters,
+and points must be zero-padded to four characters; if
+greater than four characters, no zero padding is allowed.
+Unicode surrogates are not allowed.
+.\" .Pp
+.\" Unicode glyphs attenuate to the
+.\" .Sq \&?
+.\" character if invalid or not rendered by current output media.
+.Sh NUMBERED CHARACTERS
+For backward compatibility with existing manuals,
+.Xr mandoc 1
+also supports the
+.Pp
+.Dl \eN\(aq Ns Ar number Ns \(aq
+.Pp
+escape sequence, inserting the character
+.Ar number
+from the current character set into the output.
+Of course, this is inherently non-portable and is already marked
+as deprecated in the Heirloom roff manual.
+For example, do not use \eN'34', use \e(dq, or even the plain
+.Sq \(dq
+character where possible.
+.Sh COMPATIBILITY
+This section documents compatibility between mandoc and other
+troff implementations, at this time limited to GNU troff
+.Pq Qq groff .
+.Pp
+.Bl -dash -compact
+.It
+The \eN\(aq\(aq escape sequence is limited to printable characters; in
+groff, it accepts arbitrary character numbers.
+.It
+In
+.Fl T Ns Cm ascii ,
+the
+\e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product],
+\e[coproduct], \e(gr, \e(\-h, and \e(a. special characters render
+differently between mandoc and groff.
+.It
+In
+.Fl T Ns Cm html
+and
+.Fl T Ns Cm xhtml ,
+the \e(~=, \e(nb, and \e(nc special characters render differently
+between mandoc and groff.
+.It
+The
+.Fl T Ns Cm ps
+and
+.Fl T Ns Cm pdf
+modes format like
+.Fl T Ns Cm ascii
+instead of rendering glyphs as in groff.
+.It
+The \e[radicalex], \e[sqrtex], and \e(ru special characters have been omitted
+from mandoc either because they are poorly documented or they have no
+known representation.
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr man 7 ,
+.Xr mdoc 7 ,
+.Xr roff 7
+.Sh AUTHORS
+The
+.Nm
+manual page was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
+.Sh CAVEATS
+The predefined string
+.Sq \e*(Ba
+mimics the behaviour of the
+.Sq \&|
+character in
+.Xr mdoc 7 ;
+thus, if you wish to render a vertical bar with no side effects, use
+the
+.Sq \e(ba
+escape.
diff --git a/contrib/mdocml/mandoc_escape.3 b/contrib/mdocml/mandoc_escape.3
new file mode 100644
index 0000000..84243fd
--- /dev/null
+++ b/contrib/mdocml/mandoc_escape.3
@@ -0,0 +1,362 @@
+.\" $Id: mandoc_escape.3,v 1.1 2014/08/05 05:48:56 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MANDOC_ESCAPE 3
+.Os
+.Sh NAME
+.Nm mandoc_escape
+.Nd parse roff escape sequences
+.Sh LIBRARY
+.Lb libmandoc
+.Sh SYNOPSIS
+.In sys/types.h
+.In mandoc.h
+.Ft "enum mandoc_esc"
+.Fo mandoc_escape
+.Fa "const char **end"
+.Fa "const char **start"
+.Fa "int *sz"
+.Fc
+.Sh DESCRIPTION
+This function scans a
+.Xr roff 7
+escape sequence.
+.Pp
+An escape sequence consists of
+.Bl -dash -compact -width 2n
+.It
+an initial backslash character
+.Pq Sq \e ,
+.It
+a single ASCII character called the escape sequence identifier,
+.It
+and, with only a few exceptions, an argument.
+.El
+.Pp
+Arguments can be given in the following forms; some escape sequence
+identifiers only accept some of these forms as specified below.
+The first three forms are called the standard forms.
+.Bl -tag -width 2n
+.It \&In brackets: Ic \&[ Ns Ar argument Ns Ic \&]
+The argument starts after the initial
+.Sq \&[ ,
+ends before the final
+.Sq \&] ,
+and the escape sequence ends with the final
+.Sq \&] .
+.It Two-character argument short form: Ic \&( Ns Ar ar
+This form can only be used for arguments
+consisting of exactly two characters.
+It has the same effect as
+.Ic \&[ Ns Ar ar Ns Ic \&] .
+.It One-character argument short form: Ar a
+This form can only be used for arguments
+consisting of exactly one character.
+It has the same effect as
+.Ic \&[ Ns Ar a Ns Ic \&] .
+.It Delimited form: Ar C Ns Ar argument Ns Ar C
+The argument starts after the initial delimiter character
+.Ar C ,
+ends before the next occurrence of the delimiter character
+.Ar C ,
+and the escape sequence ends with that second
+.Ar C .
+Some escape sequences allow arbitrary characters
+.Ar C
+as quoting characters, some restrict the range of characters
+that can be used as quoting characters.
+.El
+.Pp
+Upon function entry,
+.Fa end
+is expected to point to the escape sequence identifier.
+The values passed in as
+.Fa start
+and
+.Fa sz
+are ignored and overwritten.
+.Pp
+By design, this function cannot handle those
+.Xr roff 7
+escape sequences that require in-place expansion, in particular
+user-defined strings
+.Ic \e* ,
+number registers
+.Ic \en ,
+width measurements
+.Ic \ew ,
+and numerical expression control
+.Ic \eB .
+These are handled by
+.Fn roff_res ,
+a private preprocessor function called from
+.Fn roff_parseln ,
+see the file
+.Pa roff.c .
+.Pp
+The function
+.Fn mandoc_escape
+is used
+.Bl -dash -compact -width 2n
+.It
+recursively by itself, because some escape sequence arguments can
+in turn contain other escape sequences,
+.It
+for error detection internally by the
+.Xr roff 7
+parser part of the
+.Lb libmandoc ,
+see the file
+.Pa roff.c ,
+.It
+above all externally by the
+.Xr mandoc
+formatting modules, in particular
+.Fl Tascii
+and
+.Fl Thtml ,
+for formatting purposes, see the files
+.Pa term.c
+and
+.Pa html.c ,
+.It
+and rarely externally by high-level utilities using the mandoc library,
+for example
+.Xr makewhatis 8 ,
+to purge escape sequences from text.
+.El
+.Sh RETURN VALUES
+Upon function return, the pointer
+.Fa end
+is set to the character after the end of the escape sequence,
+such that the calling higher-level parser can easily continue.
+.Pp
+For escape sequences taking an argument, the pointer
+.Fa start
+is set to the beginning of the argument and
+.Fa sz
+is set to the length of the argument.
+For escape sequences not taking an argument,
+.Fa start
+is set to the character after the end of the sequence and
+.Fa sz
+is set to 0.
+Both
+.Fa start
+and
+.Fa sz
+may be
+.Dv NULL ;
+in that case, the argument and the length are not returned.
+.Pp
+For sequences taking an argument, the function
+.Fn mandoc_escape
+returns one of the following values:
+.Bl -tag -width 2n
+.It Dv ESCAPE_FONT
+The escape sequence
+.Ic \ef
+taking an argument in standard form:
+.Ic \ef[ , \ef( , \ef Ns Ar a .
+Two-character arguments starting with the character
+.Sq C
+are reduced to one-character arguments by skipping the
+.Sq C .
+More specific values are returned for the most commonly used arguments:
+.Bl -column "argument" "ESCAPE_FONTITALIC"
+.It argument Ta return value
+.It Cm R No or Cm 1 Ta Dv ESCAPE_FONTROMAN
+.It Cm I No or Cm 2 Ta Dv ESCAPE_FONTITALIC
+.It Cm B No or Cm 3 Ta Dv ESCAPE_FONTBOLD
+.It Cm P Ta Dv ESCAPE_FONTPREV
+.It Cm BI Ta Dv ESCAPE_FONTBI
+.El
+.It Dv ESCAPE_SPECIAL
+The escape sequence
+.Ic \eC
+taking an argument delimited with the single quote character
+and, as a special exception, the escape sequences
+.Em not
+having an identifier, that is, those where the argument, in standard
+form, directly follows the initial backslash:
+.Ic \eC' , \e[ , \e( , \e Ns Ar a .
+Note that the one-character argument short form can only be used for
+argument characters that do not clash with escape sequence identifiers.
+.Pp
+If the argument consists of more than one character
+and starts with the character
+.Sq u ,
+.Dv ESCAPE_UNICODE
+is returned as described below.
+If the argument is just the single character
+.Sq u ,
+.Dv ESCAPE_ERROR
+is returned.
+.Pp
+The
+.Dv ESCAPE_SPECIAL
+special character escape sequences can be rendered using the functions
+.Fn mchars_spec2cp
+and
+.Fn mchars_spec2str
+described in the
+.Xr mchars_alloc 3
+manual.
+.It Dv ESCAPE_UNICODE
+Escape sequences of the same format as described above under
+.Dv ESCAPE_SPECIAL ,
+but with an argument starting with the character
+.Sq u :
+.Ic \eC'u , \e[u .
+As a special exception,
+.Fa start
+is set to the character after the
+.Sq u ,
+and the
+.Fa sz
+return value does not include the
+.Sq u
+either.
+.Pp
+Such Unicode character escape sequences can be rendered using the function
+.Fn mchars_num2uc
+described in the
+.Xr mchars_alloc 3
+manual.
+.It Dv ESCAPE_NUMBERED
+The escape sequence
+.Ic \eN
+followed by a delimited argument.
+The delimiter character is arbitrary except that digits cannot be used.
+If a digit is encountered instead of the opening delimiter, that
+digit is considered to be the argument and the end of the sequence, and
+.Dv ESCAPE_IGNORE
+is returned.
+.Pp
+Such ASCII character escape sequences can be rendered using the function
+.Fn mchars_num2char
+described in the
+.Xr mchars_alloc 3
+manual.
+.It Dv ESCAPE_IGNORE
+.Bl -bullet -width 2n
+.It
+The escape sequence
+.Ic \es
+followed by an argument in standard form or by an argument delimited
+by the single quote character:
+.Ic \es' , \es[ , \es( , \es Ns Ar a .
+As a special exception, an optional
+.Sq +
+or
+.Sq \-
+character is allowed after the
+.Sq s
+for all forms.
+.It
+The escape sequences
+.Ic \eF ,
+.Ic \eg ,
+.Ic \ek ,
+.Ic \eM ,
+.Ic \em ,
+.Ic \en ,
+.Ic \eV ,
+and
+.Ic \eY
+followed by an argument in standard form.
+.It
+The escape sequences
+.Ic \eA ,
+.Ic \eb ,
+.Ic \eD ,
+.Ic \eo ,
+.Ic \eR ,
+.Ic \eX ,
+and
+.Ic \eZ
+followed by an argument delimited by an arbitrary character.
+.It
+The escape sequences
+.Ic \eH ,
+.Ic \eh ,
+.Ic \eL ,
+.Ic \el ,
+.Ic \eS ,
+.Ic \ev ,
+and
+.Ic \ex
+followed by an argument delimited by a character that cannot occur
+in numerical expressions.
+However, if any character that can occur in numerical expressions
+is found instead of a delimiter, the sequence is considered to end
+with that character, and
+.Dv ESCAPE_ERROR
+is returned.
+.El
+.It Dv ESCAPE_ERROR
+Escape sequences taking an argument but not matching any of the above patterns.
+In particular, that happens if the end of the logical input line
+is reached before the end of the argument.
+.El
+.Pp
+For sequences that do not take an argument, the function
+.Fn mandoc_escape
+returns one of the following values:
+.Bl -tag -width 2n
+.It Dv ESCAPE_SKIPCHAR
+The escape sequence
+.Qq \ez .
+.It Dv ESCAPE_NOSPACE
+The escape sequence
+.Qq \ec .
+.It Dv ESCAPE_IGNORE
+The escape sequences
+.Qq \ed
+and
+.Qq \eu .
+.El
+.Sh FILES
+This function is implemented in
+.Pa mandoc.c .
+.Sh SEE ALSO
+.Xr mchars_alloc 3 ,
+.Xr mandoc_char 7 ,
+.Xr roff 7
+.Sh HISTORY
+This function has been available since mandoc 1.11.2.
+.Sh AUTHORS
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
+.Sh BUGS
+The function doesn't cleanly distinguish between sequences that are
+valid and supported, valid and ignored, valid and unsupported,
+syntactically invalid, or undefined.
+For sequences that are ignored or unsupported, it doesn't tell
+whether that deficiency is likely to cause major formatting problems
+and/or loss of document content.
+The function is already rather complicated and still parses some
+sequences incorrectly.
+.
+.ig
+For these sequences, the list given below specifies a starting string
+and either the length of the argument or an ending character.
+The argument starts after the starting string.
+In the former case, the sequence ends with the end of the argument.
+In the latter case, the argument ends before the ending character,
+and the sequence ends with the ending character.
+..
diff --git a/contrib/mdocml/mandoc_html.3 b/contrib/mdocml/mandoc_html.3
new file mode 100644
index 0000000..994eb3a
--- /dev/null
+++ b/contrib/mdocml/mandoc_html.3
@@ -0,0 +1,249 @@
+.\" $Id: mandoc_html.3,v 1.1 2014/07/23 18:13:09 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 23 2014 $
+.Dt MANDOC_HTML 3
+.Os
+.Sh NAME
+.Nm mandoc_html
+.Nd internals of the mandoc HTML formatter
+.Sh SYNOPSIS
+.In "html.h"
+.Ft void
+.Fn print_gen_decls "struct html *h"
+.Ft void
+.Fn print_gen_head "struct html *h"
+.Ft struct tag *
+.Fo print_otag
+.Fa "struct html *h"
+.Fa "enum htmltag tag"
+.Fa "int sz"
+.Fa "const struct htmlpair *p"
+.Fc
+.Ft void
+.Fo print_tagq
+.Fa "struct html *h"
+.Fa "const struct tag *until"
+.Fc
+.Ft void
+.Fo print_stagq
+.Fa "struct html *h"
+.Fa "const struct tag *suntil"
+.Fc
+.Ft void
+.Fo print_text
+.Fa "struct html *h"
+.Fa "const char *word"
+.Fc
+.Sh DESCRIPTION
+The mandoc HTML formatter is not a formal library.
+However, as it is compiled into more than one program, in particular
+.Xr mandoc 1
+and
+.Xr man.cgi 8 ,
+and because it may be security-critical in some contexts,
+some documentation is useful to help to use it correctly and
+to prevent XSS vulnerabilities.
+.Pp
+The formatter produces HTML output on the standard output.
+Since proper escaping is usually required and best taken care of
+at one central place, the language-specific formatters
+.Po
+.Pa *_html.c ,
+see
+.Sx FILES
+.Pc
+are not supposed to print directly to
+.Dv stdout
+using functions like
+.Xr printf 3 ,
+.Xr putc 3 ,
+.Xr puts 3 ,
+or
+.Xr write 2 .
+Instead, they are expected to use the output functions declared in
+.Pa html.h
+and implemented as part of the main HTML formatting engine in
+.Pa html.c .
+.Ss Data structures
+These structures are declared in
+.Pa html.h .
+.Bl -tag -width Ds
+.It Vt struct html
+Internal state of the HTML formatter.
+.It Vt struct htmlpair
+Holds one HTML attribute.
+Members are
+.Fa "enum htmlattr key"
+and
+.Fa "const char *val" .
+Helper macros
+.Fn PAIR_*
+are provided to support initialization of such structures.
+.It Vt struct tag
+One entry for the LIFO stack of HTML elements.
+Members are
+.Fa "enum htmltag tag"
+and
+.Fa "struct tag *next" .
+.El
+.Ss Private interface functions
+The function
+.Fn print_gen_decls
+prints the opening
+.Ao Pf \&? Ic xml ? Ac
+and
+.Aq Pf \&! Ic DOCTYPE
+declarations required for the current document type.
+.Pp
+The function
+.Fn print_gen_head
+prints the opening
+.Aq Ic META
+and
+.Aq Ic LINK
+elements for the document
+.Aq Ic HEAD ,
+using the
+.Fa style
+member of
+.Fa h
+unless that is
+.Dv NULL .
+It uses
+.Fn print_otag
+which takes care of properly encoding attributes,
+which is relevant for the
+.Fa style
+link in particular.
+.Pp
+The function
+.Fn print_otag
+prints the start tag of an HTML element with the name
+.Fa tag ,
+including the
+.Fa sz
+attributes that can optionally be provided in the
+.Fa p
+array.
+It uses the private function
+.Fn print_attr
+which in turn uses the private function
+.Fn print_encode
+to take care of HTML encoding.
+If required by the element type, it remembers in
+.Fa h
+that the element is open.
+The function
+.Fn print_tagq
+is used to close out all open elements up to and including
+.Fa until ;
+.Fn print_stagq
+is a variant to close out all open elements up to but excluding
+.Fa suntil .
+.Pp
+The function
+.Fn print_text
+prints HTML element content.
+It uses the private function
+.Fn print_encode
+to take care of HTML encoding.
+If the document has requested a non-standard font, for example using a
+.Xr roff 7
+.Ic \ef
+font escape sequence,
+.Fn print_text
+wraps
+.Fa word
+in an HTML font selection element using the
+.Fn print_otag
+and
+.Fn print_tagq
+functions.
+.Pp
+The functions
+.Fn bufinit ,
+.Fn bufcat* ,
+and
+.Fn buffmt*
+do not directly produce output but buffer text in the
+.Fa buf
+member of
+.Fa h .
+They are not used internally by
+.Pa html.c
+but intended for use by the language-specific formatters
+to ease preparation of strings for the
+.Fa p
+argument of
+.Fn print_otag
+and for the
+.Fa word
+argument of
+.Fn print_text .
+Consequently, these functions do not do any HTML encoding.
+.Pp
+The functions
+.Fn html_strlen ,
+.Fn print_eqn ,
+.Fn print_tbl ,
+and
+.Fn print_tblclose
+are not yet documented.
+.Sh FILES
+.Bl -tag -width mandoc_aux.c -compact
+.It Pa main.h
+declarations of public functions for use by the main program,
+not yet documented
+.It Pa html.h
+declarations of data types and private functions
+for use by language-specific HTML formatters
+.It Pa html.c
+main HTML formatting engine and utility functions
+.It Pa mdoc_html.c
+.Xr mdoc 7
+HTML formatter
+.It Pa man_html.c
+.Xr man 7
+HTML formatter
+.It Pa tbl_html.c
+.Xr tbl 7
+HTML formatter
+.It Pa eqn_html.c
+.Xr eqn 7
+HTML formatter
+.It Pa out.h
+declarations of data types and private functions
+for shared use by all mandoc formatters,
+not yet documented
+.It Pa out.c
+private functions for shared use by all mandoc formatters
+.It Pa mandoc_aux.h
+declarations of common mandoc utility functions, see
+.Xr mandoc 3
+.It Pa mandoc_aux.c
+implementation of common mandoc utility functions
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr mandoc 3 ,
+.Xr man.cgi 8
+.Sh AUTHORS
+.An -nosplit
+The mandoc HTML formatter was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
+This manual was written by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/mandoc_malloc.3 b/contrib/mdocml/mandoc_malloc.3
new file mode 100644
index 0000000..c167984
--- /dev/null
+++ b/contrib/mdocml/mandoc_malloc.3
@@ -0,0 +1,197 @@
+.\" $Id: mandoc_malloc.3,v 1.1 2014/08/05 05:48:56 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MANDOC_MALLOC 3
+.Os
+.Sh NAME
+.Nm mandoc_malloc ,
+.Nm mandoc_realloc ,
+.Nm mandoc_reallocarray ,
+.Nm mandoc_calloc ,
+.Nm mandoc_strdup ,
+.Nm mandoc_strndup ,
+.Nm mandoc_asprintf
+.Nd memory allocation function wrappers used in the mandoc library
+.Sh LIBRARY
+.Lb libmandoc
+.Sh SYNOPSIS
+.In sys/types.h
+.In mandoc_aux.h
+.Ft "void *"
+.Fo mandoc_malloc
+.Fa "size_t size"
+.Fc
+.Ft "void *"
+.Fo mandoc_realloc
+.Fa "void *ptr"
+.Fa "size_t size"
+.Fc
+.Ft "void *"
+.Fo mandoc_reallocarray
+.Fa "void *ptr"
+.Fa "size_t nmemb"
+.Fa "size_t size"
+.Fc
+.Ft "void *"
+.Fo mandoc_calloc
+.Fa "size_t nmemb"
+.Fa "size_t size"
+.Fc
+.Ft "char *"
+.Fo mandoc_strdup
+.Fa "const char *s"
+.Fc
+.Ft "char *"
+.Fo mandoc_strndup
+.Fa "const char *s"
+.Fa "size_t maxlen"
+.Fc
+.Ft int
+.Fo mandoc_asprintf
+.Fa "char **ret"
+.Fa "const char *format"
+.Fa "..."
+.Fc
+.Sh DESCRIPTION
+These functions call the
+.Lb libc
+functions of the same names, passing through their return values when
+successful.
+In case of failure, they do not return, but instead call
+.Xr perror 3
+and
+.Xr exit 3 .
+They can be used both internally by any code in the
+.Lb libmandoc
+and externally by programs using that library, for example
+.Xr mandoc 1 ,
+.Xr apropos 1 ,
+and
+.Xr makewhatis 8 .
+.Pp
+The function
+.Fn mandoc_malloc
+allocates one new object, leaving the memory uninitialized.
+The functions
+.Fn mandoc_realloc
+and
+.Fn mandoc_reallocarray
+change the size of an existing object or array, possibly moving it.
+When shrinking the size, existing data is truncated; when growing,
+the additional memory is not initialized.
+The function
+.Fn mandoc_calloc
+allocates a new array, initializing it to zero.
+.Pp
+The argument
+.Fa size
+is the size of each object.
+The argument
+.Fa nmemb
+is the new number of objects in the array.
+The argument
+.Fa ptr
+is a pointer to the existing object or array to be resized; if it is
+.Dv NULL ,
+a new object or array is allocated.
+.Pp
+The functions
+.Fn mandoc_strdup
+and
+.Fn mandoc_strndup
+copy a string into newly allocated memory.
+For
+.Fn mandoc_strdup ,
+the string pointed to by
+.Fa s
+needs to be NUL-terminated.
+For
+.Fn mandoc_strndup ,
+at most
+.Fa maxlen
+bytes are copied.
+The function
+.Fn mandoc_asprintf
+writes output formatted according to
+.Fa format
+into newly allocated memory and returns a pointer to the result in
+.Fa ret .
+For all three string functions, the result is always NUL-terminated.
+.Pp
+When the objects and strings are no longer needed,
+the pointers returned by these functions can be passed to
+.Xr free 3 .
+.Sh RETURN VALUES
+The function
+.Fn mandoc_asprintf
+always returns the number of characters written, excluding the
+final NUL byte.
+It never returns -1.
+.Pp
+The other functions always return a valid pointer; they never return
+.Dv NULL .
+.Sh FILES
+These functions are implemented in
+.Pa mandoc_aux.c .
+.Sh SEE ALSO
+.Xr asprintf 3 ,
+.Xr exit 3 ,
+.Xr malloc 3 ,
+.Xr perror 3 ,
+.Xr strdup 3
+.Sh STANDARDS
+The functions
+.Fn malloc ,
+.Fn realloc ,
+and
+.Fn calloc
+are required by
+.St -ansiC .
+The functions
+.Fn strdup
+and
+.Fn strndup
+are required by
+.St -p1003.1-2008 .
+The function
+.Fn asprintf
+is a widespread extension that first appeared in the GNU C library.
+.Pp
+The function
+.Fn reallocarray
+is an extension that first appeared in
+.Ox 5.6 .
+If it is not provided by the operating system, the mandoc build system
+uses a bundled portable implementation.
+.Sh HISTORY
+The functions
+.Fn mandoc_malloc ,
+.Fn mandoc_realloc ,
+.Fn mandoc_calloc ,
+and
+.Fn mandoc_strdup
+have been available since mandoc 1.9.12,
+.Fn mandoc_strndup
+since 1.11.5,
+and
+.Fn mandoc_asprintf
+and
+.Fn mandoc_reallocarray
+since 1.12.4 and 1.13.0.
+.Sh AUTHORS
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
diff --git a/contrib/mdocml/mandocdb.c b/contrib/mdocml/mandocdb.c
new file mode 100644
index 0000000..a604b46
--- /dev/null
+++ b/contrib/mdocml/mandocdb.c
@@ -0,0 +1,2491 @@
+/* $Id: mandocdb.c,v 1.155 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_OHASH
+#include <ohash.h>
+#else
+#include "compat_ohash.h"
+#endif
+#include <sqlite3.h>
+
+#include "mdoc.h"
+#include "man.h"
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "manpath.h"
+#include "mansearch.h"
+
+extern int mansearch_keymax;
+extern const char *const mansearch_keynames[];
+
+#define SQL_EXEC(_v) \
+ if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \
+ say("", "%s: %s", (_v), sqlite3_errmsg(db))
+#define SQL_BIND_TEXT(_s, _i, _v) \
+ if (SQLITE_OK != sqlite3_bind_text \
+ ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+#define SQL_BIND_INT(_s, _i, _v) \
+ if (SQLITE_OK != sqlite3_bind_int \
+ ((_s), (_i)++, (_v))) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+#define SQL_BIND_INT64(_s, _i, _v) \
+ if (SQLITE_OK != sqlite3_bind_int64 \
+ ((_s), (_i)++, (_v))) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+#define SQL_STEP(_s) \
+ if (SQLITE_DONE != sqlite3_step((_s))) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+
+enum op {
+ OP_DEFAULT = 0, /* new dbs from dir list or default config */
+ OP_CONFFILE, /* new databases from custom config file */
+ OP_UPDATE, /* delete/add entries in existing database */
+ OP_DELETE, /* delete entries from existing database */
+ OP_TEST /* change no databases, report potential problems */
+};
+
+enum form {
+ FORM_NONE, /* format is unknown */
+ FORM_SRC, /* format is -man or -mdoc */
+ FORM_CAT /* format is cat */
+};
+
+struct str {
+ char *rendered; /* key in UTF-8 or ASCII form */
+ const struct mpage *mpage; /* if set, the owning parse */
+ uint64_t mask; /* bitmask in sequence */
+ char key[]; /* may contain escape sequences */
+};
+
+struct inodev {
+ ino_t st_ino;
+ dev_t st_dev;
+};
+
+struct mpage {
+ struct inodev inodev; /* used for hashing routine */
+ int64_t pageid; /* pageid in mpages SQL table */
+ enum form form; /* format from file content */
+ char *sec; /* section from file content */
+ char *arch; /* architecture from file content */
+ char *title; /* title from file content */
+ char *desc; /* description from file content */
+ struct mlink *mlinks; /* singly linked list */
+};
+
+struct mlink {
+ char file[PATH_MAX]; /* filename rel. to manpath */
+ enum form dform; /* format from directory */
+ enum form fform; /* format from file name suffix */
+ char *dsec; /* section from directory */
+ char *arch; /* architecture from directory */
+ char *name; /* name from file name (not empty) */
+ char *fsec; /* section from file name suffix */
+ struct mlink *next; /* singly linked list */
+ struct mpage *mpage; /* parent */
+ int gzip; /* filename has a .gz suffix */
+};
+
+enum stmt {
+ STMT_DELETE_PAGE = 0, /* delete mpage */
+ STMT_INSERT_PAGE, /* insert mpage */
+ STMT_INSERT_LINK, /* insert mlink */
+ STMT_INSERT_NAME, /* insert name */
+ STMT_INSERT_KEY, /* insert parsed key */
+ STMT__MAX
+};
+
+typedef int (*mdoc_fp)(struct mpage *, const struct mdoc_node *);
+
+struct mdoc_handler {
+ mdoc_fp fp; /* optional handler */
+ uint64_t mask; /* set unless handler returns 0 */
+};
+
+static void dbclose(int);
+static void dbadd(struct mpage *, struct mchars *);
+static void dbadd_mlink(const struct mlink *mlink);
+static int dbopen(int);
+static void dbprune(void);
+static void filescan(const char *);
+static void *hash_alloc(size_t, void *);
+static void hash_free(void *, void *);
+static void *hash_calloc(size_t, size_t, void *);
+static void mlink_add(struct mlink *, const struct stat *);
+static void mlink_check(struct mpage *, struct mlink *);
+static void mlink_free(struct mlink *);
+static void mlinks_undupe(struct mpage *);
+static void mpages_free(void);
+static void mpages_merge(struct mchars *, struct mparse *);
+static void names_check(void);
+static void parse_cat(struct mpage *, int);
+static void parse_man(struct mpage *, const struct man_node *);
+static void parse_mdoc(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_body(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_head(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Fd(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Fn(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Nd(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Nm(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Sh(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Xr(struct mpage *, const struct mdoc_node *);
+static void putkey(const struct mpage *, char *, uint64_t);
+static void putkeys(const struct mpage *,
+ const char *, size_t, uint64_t);
+static void putmdockey(const struct mpage *,
+ const struct mdoc_node *, uint64_t);
+static void render_key(struct mchars *, struct str *);
+static void say(const char *, const char *, ...);
+static int set_basedir(const char *);
+static int treescan(void);
+static size_t utf8(unsigned int, char [7]);
+
+static char tempfilename[32];
+static char *progname;
+static int nodb; /* no database changes */
+static int mparse_options; /* abort the parse early */
+static int use_all; /* use all found files */
+static int debug; /* print what we're doing */
+static int warnings; /* warn about crap */
+static int write_utf8; /* write UTF-8 output; else ASCII */
+static int exitcode; /* to be returned by main */
+static enum op op; /* operational mode */
+static char basedir[PATH_MAX]; /* current base directory */
+static struct ohash mpages; /* table of distinct manual pages */
+static struct ohash mlinks; /* table of directory entries */
+static struct ohash names; /* table of all names */
+static struct ohash strings; /* table of all strings */
+static sqlite3 *db = NULL; /* current database */
+static sqlite3_stmt *stmts[STMT__MAX]; /* current statements */
+static uint64_t name_mask;
+
+static const struct mdoc_handler mdocs[MDOC_MAX] = {
+ { NULL, 0 }, /* Ap */
+ { NULL, 0 }, /* Dd */
+ { NULL, 0 }, /* Dt */
+ { NULL, 0 }, /* Os */
+ { parse_mdoc_Sh, TYPE_Sh }, /* Sh */
+ { parse_mdoc_head, TYPE_Ss }, /* Ss */
+ { NULL, 0 }, /* Pp */
+ { NULL, 0 }, /* D1 */
+ { NULL, 0 }, /* Dl */
+ { NULL, 0 }, /* Bd */
+ { NULL, 0 }, /* Ed */
+ { NULL, 0 }, /* Bl */
+ { NULL, 0 }, /* El */
+ { NULL, 0 }, /* It */
+ { NULL, 0 }, /* Ad */
+ { NULL, TYPE_An }, /* An */
+ { NULL, TYPE_Ar }, /* Ar */
+ { NULL, TYPE_Cd }, /* Cd */
+ { NULL, TYPE_Cm }, /* Cm */
+ { NULL, TYPE_Dv }, /* Dv */
+ { NULL, TYPE_Er }, /* Er */
+ { NULL, TYPE_Ev }, /* Ev */
+ { NULL, 0 }, /* Ex */
+ { NULL, TYPE_Fa }, /* Fa */
+ { parse_mdoc_Fd, 0 }, /* Fd */
+ { NULL, TYPE_Fl }, /* Fl */
+ { parse_mdoc_Fn, 0 }, /* Fn */
+ { NULL, TYPE_Ft }, /* Ft */
+ { NULL, TYPE_Ic }, /* Ic */
+ { NULL, TYPE_In }, /* In */
+ { NULL, TYPE_Li }, /* Li */
+ { parse_mdoc_Nd, 0 }, /* Nd */
+ { parse_mdoc_Nm, 0 }, /* Nm */
+ { NULL, 0 }, /* Op */
+ { NULL, 0 }, /* Ot */
+ { NULL, TYPE_Pa }, /* Pa */
+ { NULL, 0 }, /* Rv */
+ { NULL, TYPE_St }, /* St */
+ { NULL, TYPE_Va }, /* Va */
+ { parse_mdoc_body, TYPE_Va }, /* Vt */
+ { parse_mdoc_Xr, 0 }, /* Xr */
+ { NULL, 0 }, /* %A */
+ { NULL, 0 }, /* %B */
+ { NULL, 0 }, /* %D */
+ { NULL, 0 }, /* %I */
+ { NULL, 0 }, /* %J */
+ { NULL, 0 }, /* %N */
+ { NULL, 0 }, /* %O */
+ { NULL, 0 }, /* %P */
+ { NULL, 0 }, /* %R */
+ { NULL, 0 }, /* %T */
+ { NULL, 0 }, /* %V */
+ { NULL, 0 }, /* Ac */
+ { NULL, 0 }, /* Ao */
+ { NULL, 0 }, /* Aq */
+ { NULL, TYPE_At }, /* At */
+ { NULL, 0 }, /* Bc */
+ { NULL, 0 }, /* Bf */
+ { NULL, 0 }, /* Bo */
+ { NULL, 0 }, /* Bq */
+ { NULL, TYPE_Bsx }, /* Bsx */
+ { NULL, TYPE_Bx }, /* Bx */
+ { NULL, 0 }, /* Db */
+ { NULL, 0 }, /* Dc */
+ { NULL, 0 }, /* Do */
+ { NULL, 0 }, /* Dq */
+ { NULL, 0 }, /* Ec */
+ { NULL, 0 }, /* Ef */
+ { NULL, TYPE_Em }, /* Em */
+ { NULL, 0 }, /* Eo */
+ { NULL, TYPE_Fx }, /* Fx */
+ { NULL, TYPE_Ms }, /* Ms */
+ { NULL, 0 }, /* No */
+ { NULL, 0 }, /* Ns */
+ { NULL, TYPE_Nx }, /* Nx */
+ { NULL, TYPE_Ox }, /* Ox */
+ { NULL, 0 }, /* Pc */
+ { NULL, 0 }, /* Pf */
+ { NULL, 0 }, /* Po */
+ { NULL, 0 }, /* Pq */
+ { NULL, 0 }, /* Qc */
+ { NULL, 0 }, /* Ql */
+ { NULL, 0 }, /* Qo */
+ { NULL, 0 }, /* Qq */
+ { NULL, 0 }, /* Re */
+ { NULL, 0 }, /* Rs */
+ { NULL, 0 }, /* Sc */
+ { NULL, 0 }, /* So */
+ { NULL, 0 }, /* Sq */
+ { NULL, 0 }, /* Sm */
+ { NULL, 0 }, /* Sx */
+ { NULL, TYPE_Sy }, /* Sy */
+ { NULL, TYPE_Tn }, /* Tn */
+ { NULL, 0 }, /* Ux */
+ { NULL, 0 }, /* Xc */
+ { NULL, 0 }, /* Xo */
+ { parse_mdoc_head, 0 }, /* Fo */
+ { NULL, 0 }, /* Fc */
+ { NULL, 0 }, /* Oo */
+ { NULL, 0 }, /* Oc */
+ { NULL, 0 }, /* Bk */
+ { NULL, 0 }, /* Ek */
+ { NULL, 0 }, /* Bt */
+ { NULL, 0 }, /* Hf */
+ { NULL, 0 }, /* Fr */
+ { NULL, 0 }, /* Ud */
+ { NULL, TYPE_Lb }, /* Lb */
+ { NULL, 0 }, /* Lp */
+ { NULL, TYPE_Lk }, /* Lk */
+ { NULL, TYPE_Mt }, /* Mt */
+ { NULL, 0 }, /* Brq */
+ { NULL, 0 }, /* Bro */
+ { NULL, 0 }, /* Brc */
+ { NULL, 0 }, /* %C */
+ { NULL, 0 }, /* Es */
+ { NULL, 0 }, /* En */
+ { NULL, TYPE_Dx }, /* Dx */
+ { NULL, 0 }, /* %Q */
+ { NULL, 0 }, /* br */
+ { NULL, 0 }, /* sp */
+ { NULL, 0 }, /* %U */
+ { NULL, 0 }, /* Ta */
+};
+
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i;
+ size_t j, sz;
+ const char *path_arg;
+ struct mchars *mc;
+ struct manpaths dirs;
+ struct mparse *mp;
+ struct ohash_info mpages_info, mlinks_info;
+
+ memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *));
+ memset(&dirs, 0, sizeof(struct manpaths));
+
+ mpages_info.alloc = mlinks_info.alloc = hash_alloc;
+ mpages_info.calloc = mlinks_info.calloc = hash_calloc;
+ mpages_info.free = mlinks_info.free = hash_free;
+
+ mpages_info.key_offset = offsetof(struct mpage, inodev);
+ mlinks_info.key_offset = offsetof(struct mlink, file);
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ /*
+ * We accept a few different invocations.
+ * The CHECKOP macro makes sure that invocation styles don't
+ * clobber each other.
+ */
+#define CHECKOP(_op, _ch) do \
+ if (OP_DEFAULT != (_op)) { \
+ fprintf(stderr, "%s: -%c: Conflicting option\n", \
+ progname, (_ch)); \
+ goto usage; \
+ } while (/*CONSTCOND*/0)
+
+ path_arg = NULL;
+ op = OP_DEFAULT;
+
+ while (-1 != (ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")))
+ switch (ch) {
+ case 'a':
+ use_all = 1;
+ break;
+ case 'C':
+ CHECKOP(op, ch);
+ path_arg = optarg;
+ op = OP_CONFFILE;
+ break;
+ case 'D':
+ debug++;
+ break;
+ case 'd':
+ CHECKOP(op, ch);
+ path_arg = optarg;
+ op = OP_UPDATE;
+ break;
+ case 'n':
+ nodb = 1;
+ break;
+ case 'p':
+ warnings = 1;
+ break;
+ case 'Q':
+ mparse_options |= MPARSE_QUICK;
+ break;
+ case 'T':
+ if (strcmp(optarg, "utf8")) {
+ fprintf(stderr, "%s: -T%s: "
+ "Unsupported output format\n",
+ progname, optarg);
+ goto usage;
+ }
+ write_utf8 = 1;
+ break;
+ case 't':
+ CHECKOP(op, ch);
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+ op = OP_TEST;
+ nodb = warnings = 1;
+ break;
+ case 'u':
+ CHECKOP(op, ch);
+ path_arg = optarg;
+ op = OP_DELETE;
+ break;
+ case 'v':
+ /* Compatibility with espie@'s makewhatis. */
+ break;
+ default:
+ goto usage;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (OP_CONFFILE == op && argc > 0) {
+ fprintf(stderr, "%s: -C: Too many arguments\n",
+ progname);
+ goto usage;
+ }
+
+ exitcode = (int)MANDOCLEVEL_OK;
+ mp = mparse_alloc(mparse_options, MANDOCLEVEL_FATAL, NULL, NULL);
+ mc = mchars_alloc();
+
+ ohash_init(&mpages, 6, &mpages_info);
+ ohash_init(&mlinks, 6, &mlinks_info);
+
+ if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
+
+ /*
+ * Most of these deal with a specific directory.
+ * Jump into that directory first.
+ */
+ if (OP_TEST != op && 0 == set_basedir(path_arg))
+ goto out;
+
+ if (dbopen(1)) {
+ /*
+ * The existing database is usable. Process
+ * all files specified on the command-line.
+ */
+ use_all = 1;
+ for (i = 0; i < argc; i++)
+ filescan(argv[i]);
+ if (OP_TEST != op)
+ dbprune();
+ } else {
+ /*
+ * Database missing or corrupt.
+ * Recreate from scratch.
+ */
+ exitcode = (int)MANDOCLEVEL_OK;
+ op = OP_DEFAULT;
+ if (0 == treescan())
+ goto out;
+ if (0 == dbopen(0))
+ goto out;
+ }
+ if (OP_DELETE != op)
+ mpages_merge(mc, mp);
+ dbclose(OP_DEFAULT == op ? 0 : 1);
+ } else {
+ /*
+ * If we have arguments, use them as our manpaths.
+ * If we don't, grok from manpath(1) or however else
+ * manpath_parse() wants to do it.
+ */
+ if (argc > 0) {
+ dirs.paths = mandoc_reallocarray(NULL,
+ argc, sizeof(char *));
+ dirs.sz = (size_t)argc;
+ for (i = 0; i < argc; i++)
+ dirs.paths[i] = mandoc_strdup(argv[i]);
+ } else
+ manpath_parse(&dirs, path_arg, NULL, NULL);
+
+ if (0 == dirs.sz) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "Empty manpath");
+ }
+
+ /*
+ * First scan the tree rooted at a base directory, then
+ * build a new database and finally move it into place.
+ * Ignore zero-length directories and strip trailing
+ * slashes.
+ */
+ for (j = 0; j < dirs.sz; j++) {
+ sz = strlen(dirs.paths[j]);
+ if (sz && '/' == dirs.paths[j][sz - 1])
+ dirs.paths[j][--sz] = '\0';
+ if (0 == sz)
+ continue;
+
+ if (j) {
+ ohash_init(&mpages, 6, &mpages_info);
+ ohash_init(&mlinks, 6, &mlinks_info);
+ }
+
+ if (0 == set_basedir(dirs.paths[j]))
+ goto out;
+ if (0 == treescan())
+ goto out;
+ if (0 == dbopen(0))
+ goto out;
+
+ mpages_merge(mc, mp);
+ if (warnings && !nodb &&
+ ! (MPARSE_QUICK & mparse_options))
+ names_check();
+ dbclose(0);
+
+ if (j + 1 < dirs.sz) {
+ mpages_free();
+ ohash_delete(&mpages);
+ ohash_delete(&mlinks);
+ }
+ }
+ }
+out:
+ manpath_free(&dirs);
+ mchars_free(mc);
+ mparse_free(mp);
+ mpages_free();
+ ohash_delete(&mpages);
+ ohash_delete(&mlinks);
+ return(exitcode);
+usage:
+ fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n"
+ " %s [-aDnpQ] [-Tutf8] dir ...\n"
+ " %s [-DnpQ] [-Tutf8] -d dir [file ...]\n"
+ " %s [-Dnp] -u dir [file ...]\n"
+ " %s [-Q] -t file ...\n",
+ progname, progname, progname,
+ progname, progname);
+
+ return((int)MANDOCLEVEL_BADARG);
+}
+
+/*
+ * Scan a directory tree rooted at "basedir" for manpages.
+ * We use fts(), scanning directory parts along the way for clues to our
+ * section and architecture.
+ *
+ * If use_all has been specified, grok all files.
+ * If not, sanitise paths to the following:
+ *
+ * [./]man*[/<arch>]/<name>.<section>
+ * or
+ * [./]cat<section>[/<arch>]/<name>.0
+ *
+ * TODO: accomodate for multi-language directories.
+ */
+static int
+treescan(void)
+{
+ char buf[PATH_MAX];
+ FTS *f;
+ FTSENT *ff;
+ struct mlink *mlink;
+ int dform, gzip;
+ char *dsec, *arch, *fsec, *cp;
+ const char *path;
+ const char *argv[2];
+
+ argv[0] = ".";
+ argv[1] = (char *)NULL;
+
+ f = fts_open((char * const *)argv,
+ FTS_PHYSICAL | FTS_NOCHDIR, NULL);
+ if (NULL == f) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fts_open");
+ return(0);
+ }
+
+ dsec = arch = NULL;
+ dform = FORM_NONE;
+
+ while (NULL != (ff = fts_read(f))) {
+ path = ff->fts_path + 2;
+ switch (ff->fts_info) {
+
+ /*
+ * Symbolic links require various sanity checks,
+ * then get handled just like regular files.
+ */
+ case FTS_SL:
+ if (NULL == realpath(path, buf)) {
+ if (warnings)
+ say(path, "&realpath");
+ continue;
+ }
+ if (strstr(buf, basedir) != buf) {
+ if (warnings) say("",
+ "%s: outside base directory", buf);
+ continue;
+ }
+ /* Use logical inode to avoid mpages dupe. */
+ if (-1 == stat(path, ff->fts_statp)) {
+ if (warnings)
+ say(path, "&stat");
+ continue;
+ }
+ /* FALLTHROUGH */
+
+ /*
+ * If we're a regular file, add an mlink by using the
+ * stored directory data and handling the filename.
+ */
+ case FTS_F:
+ if (0 == strcmp(path, MANDOC_DB))
+ continue;
+ if ( ! use_all && ff->fts_level < 2) {
+ if (warnings)
+ say(path, "Extraneous file");
+ continue;
+ }
+ gzip = 0;
+ fsec = NULL;
+ while (NULL == fsec) {
+ fsec = strrchr(ff->fts_name, '.');
+ if (NULL == fsec || strcmp(fsec+1, "gz"))
+ break;
+ gzip = 1;
+ *fsec = '\0';
+ fsec = NULL;
+ }
+ if (NULL == fsec) {
+ if ( ! use_all) {
+ if (warnings)
+ say(path,
+ "No filename suffix");
+ continue;
+ }
+ } else if (0 == strcmp(++fsec, "html")) {
+ if (warnings)
+ say(path, "Skip html");
+ continue;
+ } else if (0 == strcmp(fsec, "ps")) {
+ if (warnings)
+ say(path, "Skip ps");
+ continue;
+ } else if (0 == strcmp(fsec, "pdf")) {
+ if (warnings)
+ say(path, "Skip pdf");
+ continue;
+ } else if ( ! use_all &&
+ ((FORM_SRC == dform && strcmp(fsec, dsec)) ||
+ (FORM_CAT == dform && strcmp(fsec, "0")))) {
+ if (warnings)
+ say(path, "Wrong filename suffix");
+ continue;
+ } else
+ fsec[-1] = '\0';
+
+ mlink = mandoc_calloc(1, sizeof(struct mlink));
+ if (strlcpy(mlink->file, path,
+ sizeof(mlink->file)) >=
+ sizeof(mlink->file)) {
+ say(path, "Filename too long");
+ free(mlink);
+ continue;
+ }
+ mlink->dform = dform;
+ mlink->dsec = dsec;
+ mlink->arch = arch;
+ mlink->name = ff->fts_name;
+ mlink->fsec = fsec;
+ mlink->gzip = gzip;
+ mlink_add(mlink, ff->fts_statp);
+ continue;
+
+ case FTS_D:
+ /* FALLTHROUGH */
+ case FTS_DP:
+ break;
+
+ default:
+ if (warnings)
+ say(path, "Not a regular file");
+ continue;
+ }
+
+ switch (ff->fts_level) {
+ case 0:
+ /* Ignore the root directory. */
+ break;
+ case 1:
+ /*
+ * This might contain manX/ or catX/.
+ * Try to infer this from the name.
+ * If we're not in use_all, enforce it.
+ */
+ cp = ff->fts_name;
+ if (FTS_DP == ff->fts_info)
+ break;
+
+ if (0 == strncmp(cp, "man", 3)) {
+ dform = FORM_SRC;
+ dsec = cp + 3;
+ } else if (0 == strncmp(cp, "cat", 3)) {
+ dform = FORM_CAT;
+ dsec = cp + 3;
+ } else {
+ dform = FORM_NONE;
+ dsec = NULL;
+ }
+
+ if (NULL != dsec || use_all)
+ break;
+
+ if (warnings)
+ say(path, "Unknown directory part");
+ fts_set(f, ff, FTS_SKIP);
+ break;
+ case 2:
+ /*
+ * Possibly our architecture.
+ * If we're descending, keep tabs on it.
+ */
+ if (FTS_DP != ff->fts_info && NULL != dsec)
+ arch = ff->fts_name;
+ else
+ arch = NULL;
+ break;
+ default:
+ if (FTS_DP == ff->fts_info || use_all)
+ break;
+ if (warnings)
+ say(path, "Extraneous directory part");
+ fts_set(f, ff, FTS_SKIP);
+ break;
+ }
+ }
+
+ fts_close(f);
+ return(1);
+}
+
+/*
+ * Add a file to the mlinks table.
+ * Do not verify that it's a "valid" looking manpage (we'll do that
+ * later).
+ *
+ * Try to infer the manual section, architecture, and page name from the
+ * path, assuming it looks like
+ *
+ * [./]man*[/<arch>]/<name>.<section>
+ * or
+ * [./]cat<section>[/<arch>]/<name>.0
+ *
+ * See treescan() for the fts(3) version of this.
+ */
+static void
+filescan(const char *file)
+{
+ char buf[PATH_MAX];
+ struct stat st;
+ struct mlink *mlink;
+ char *p, *start;
+
+ assert(use_all);
+
+ if (0 == strncmp(file, "./", 2))
+ file += 2;
+
+ /*
+ * We have to do lstat(2) before realpath(3) loses
+ * the information whether this is a symbolic link.
+ * We need to know that because for symbolic links,
+ * we want to use the orginal file name, while for
+ * regular files, we want to use the real path.
+ */
+ if (-1 == lstat(file, &st)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "&lstat");
+ return;
+ } else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "Not a regular file");
+ return;
+ }
+
+ /*
+ * We have to resolve the file name to the real path
+ * in any case for the base directory check.
+ */
+ if (NULL == realpath(file, buf)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "&realpath");
+ return;
+ }
+
+ if (OP_TEST == op)
+ start = buf;
+ else if (strstr(buf, basedir) == buf)
+ start = buf + strlen(basedir);
+ else {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "%s: outside base directory", buf);
+ return;
+ }
+
+ /*
+ * Now we are sure the file is inside our tree.
+ * If it is a symbolic link, ignore the real path
+ * and use the original name.
+ * This implies passing stuff like "cat1/../man1/foo.1"
+ * on the command line won't work. So don't do that.
+ * Note the stat(2) can still fail if the link target
+ * doesn't exist.
+ */
+ if (S_IFLNK & st.st_mode) {
+ if (-1 == stat(buf, &st)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "&stat");
+ return;
+ }
+ if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) {
+ say(file, "Filename too long");
+ return;
+ }
+ start = buf;
+ if (OP_TEST != op && strstr(buf, basedir) == buf)
+ start += strlen(basedir);
+ }
+
+ mlink = mandoc_calloc(1, sizeof(struct mlink));
+ if (strlcpy(mlink->file, start, sizeof(mlink->file)) >=
+ sizeof(mlink->file)) {
+ say(start, "Filename too long");
+ return;
+ }
+
+ /*
+ * First try to guess our directory structure.
+ * If we find a separator, try to look for man* or cat*.
+ * If we find one of these and what's underneath is a directory,
+ * assume it's an architecture.
+ */
+ if (NULL != (p = strchr(start, '/'))) {
+ *p++ = '\0';
+ if (0 == strncmp(start, "man", 3)) {
+ mlink->dform = FORM_SRC;
+ mlink->dsec = start + 3;
+ } else if (0 == strncmp(start, "cat", 3)) {
+ mlink->dform = FORM_CAT;
+ mlink->dsec = start + 3;
+ }
+
+ start = p;
+ if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) {
+ *p++ = '\0';
+ mlink->arch = start;
+ start = p;
+ }
+ }
+
+ /*
+ * Now check the file suffix.
+ * Suffix of `.0' indicates a catpage, `.1-9' is a manpage.
+ */
+ p = strrchr(start, '\0');
+ while (p-- > start && '/' != *p && '.' != *p)
+ /* Loop. */ ;
+
+ if ('.' == *p) {
+ *p++ = '\0';
+ mlink->fsec = p;
+ }
+
+ /*
+ * Now try to parse the name.
+ * Use the filename portion of the path.
+ */
+ mlink->name = start;
+ if (NULL != (p = strrchr(start, '/'))) {
+ mlink->name = p + 1;
+ *p = '\0';
+ }
+ mlink_add(mlink, &st);
+}
+
+static void
+mlink_add(struct mlink *mlink, const struct stat *st)
+{
+ struct inodev inodev;
+ struct mpage *mpage;
+ unsigned int slot;
+
+ assert(NULL != mlink->file);
+
+ mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : "");
+ mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : "");
+ mlink->name = mandoc_strdup(mlink->name ? mlink->name : "");
+ mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : "");
+
+ if ('0' == *mlink->fsec) {
+ free(mlink->fsec);
+ mlink->fsec = mandoc_strdup(mlink->dsec);
+ mlink->fform = FORM_CAT;
+ } else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec)
+ mlink->fform = FORM_SRC;
+ else
+ mlink->fform = FORM_NONE;
+
+ slot = ohash_qlookup(&mlinks, mlink->file);
+ assert(NULL == ohash_find(&mlinks, slot));
+ ohash_insert(&mlinks, slot, mlink);
+
+ inodev.st_ino = st->st_ino;
+ inodev.st_dev = st->st_dev;
+ slot = ohash_lookup_memory(&mpages, (char *)&inodev,
+ sizeof(struct inodev), inodev.st_ino);
+ mpage = ohash_find(&mpages, slot);
+ if (NULL == mpage) {
+ mpage = mandoc_calloc(1, sizeof(struct mpage));
+ mpage->inodev.st_ino = inodev.st_ino;
+ mpage->inodev.st_dev = inodev.st_dev;
+ ohash_insert(&mpages, slot, mpage);
+ } else
+ mlink->next = mpage->mlinks;
+ mpage->mlinks = mlink;
+ mlink->mpage = mpage;
+}
+
+static void
+mlink_free(struct mlink *mlink)
+{
+
+ free(mlink->dsec);
+ free(mlink->arch);
+ free(mlink->name);
+ free(mlink->fsec);
+ free(mlink);
+}
+
+static void
+mpages_free(void)
+{
+ struct mpage *mpage;
+ struct mlink *mlink;
+ unsigned int slot;
+
+ mpage = ohash_first(&mpages, &slot);
+ while (NULL != mpage) {
+ while (NULL != (mlink = mpage->mlinks)) {
+ mpage->mlinks = mlink->next;
+ mlink_free(mlink);
+ }
+ free(mpage->sec);
+ free(mpage->arch);
+ free(mpage->title);
+ free(mpage->desc);
+ free(mpage);
+ mpage = ohash_next(&mpages, &slot);
+ }
+}
+
+/*
+ * For each mlink to the mpage, check whether the path looks like
+ * it is formatted, and if it does, check whether a source manual
+ * exists by the same name, ignoring the suffix.
+ * If both conditions hold, drop the mlink.
+ */
+static void
+mlinks_undupe(struct mpage *mpage)
+{
+ char buf[PATH_MAX];
+ struct mlink **prev;
+ struct mlink *mlink;
+ char *bufp;
+
+ mpage->form = FORM_CAT;
+ prev = &mpage->mlinks;
+ while (NULL != (mlink = *prev)) {
+ if (FORM_CAT != mlink->dform) {
+ mpage->form = FORM_NONE;
+ goto nextlink;
+ }
+ (void)strlcpy(buf, mlink->file, sizeof(buf));
+ bufp = strstr(buf, "cat");
+ assert(NULL != bufp);
+ memcpy(bufp, "man", 3);
+ if (NULL != (bufp = strrchr(buf, '.')))
+ *++bufp = '\0';
+ (void)strlcat(buf, mlink->dsec, sizeof(buf));
+ if (NULL == ohash_find(&mlinks,
+ ohash_qlookup(&mlinks, buf)))
+ goto nextlink;
+ if (warnings)
+ say(mlink->file, "Man source exists: %s", buf);
+ if (use_all)
+ goto nextlink;
+ *prev = mlink->next;
+ mlink_free(mlink);
+ continue;
+nextlink:
+ prev = &(*prev)->next;
+ }
+}
+
+static void
+mlink_check(struct mpage *mpage, struct mlink *mlink)
+{
+ struct str *str;
+ unsigned int slot;
+
+ /*
+ * Check whether the manual section given in a file
+ * agrees with the directory where the file is located.
+ * Some manuals have suffixes like (3p) on their
+ * section number either inside the file or in the
+ * directory name, some are linked into more than one
+ * section, like encrypt(1) = makekey(8).
+ */
+
+ if (FORM_SRC == mpage->form &&
+ strcasecmp(mpage->sec, mlink->dsec))
+ say(mlink->file, "Section \"%s\" manual in %s directory",
+ mpage->sec, mlink->dsec);
+
+ /*
+ * Manual page directories exist for each kernel
+ * architecture as returned by machine(1).
+ * However, many manuals only depend on the
+ * application architecture as returned by arch(1).
+ * For example, some (2/ARM) manuals are shared
+ * across the "armish" and "zaurus" kernel
+ * architectures.
+ * A few manuals are even shared across completely
+ * different architectures, for example fdformat(1)
+ * on amd64, i386, sparc, and sparc64.
+ */
+
+ if (strcasecmp(mpage->arch, mlink->arch))
+ say(mlink->file, "Architecture \"%s\" manual in "
+ "\"%s\" directory", mpage->arch, mlink->arch);
+
+ /*
+ * XXX
+ * parse_cat() doesn't set NAME_TITLE yet.
+ */
+
+ if (FORM_CAT == mpage->form)
+ return;
+
+ /*
+ * Check whether this mlink
+ * appears as a name in the NAME section.
+ */
+
+ slot = ohash_qlookup(&names, mlink->name);
+ str = ohash_find(&names, slot);
+ assert(NULL != str);
+ if ( ! (NAME_TITLE & str->mask))
+ say(mlink->file, "Name missing in NAME section");
+}
+
+/*
+ * Run through the files in the global vector "mpages"
+ * and add them to the database specified in "basedir".
+ *
+ * This handles the parsing scheme itself, using the cues of directory
+ * and filename to determine whether the file is parsable or not.
+ */
+static void
+mpages_merge(struct mchars *mc, struct mparse *mp)
+{
+ char any[] = "any";
+ struct ohash_info str_info;
+ int fd[2];
+ struct mpage *mpage, *mpage_dest;
+ struct mlink *mlink, *mlink_dest;
+ struct mdoc *mdoc;
+ struct man *man;
+ char *sodest;
+ char *cp;
+ pid_t child_pid;
+ int status;
+ unsigned int pslot;
+ enum mandoclevel lvl;
+
+ str_info.alloc = hash_alloc;
+ str_info.calloc = hash_calloc;
+ str_info.free = hash_free;
+ str_info.key_offset = offsetof(struct str, key);
+
+ if (0 == nodb)
+ SQL_EXEC("BEGIN TRANSACTION");
+
+ mpage = ohash_first(&mpages, &pslot);
+ while (NULL != mpage) {
+ mlinks_undupe(mpage);
+ if (NULL == mpage->mlinks) {
+ mpage = ohash_next(&mpages, &pslot);
+ continue;
+ }
+
+ name_mask = NAME_MASK;
+ ohash_init(&names, 4, &str_info);
+ ohash_init(&strings, 6, &str_info);
+ mparse_reset(mp);
+ mdoc = NULL;
+ man = NULL;
+ sodest = NULL;
+ child_pid = 0;
+ fd[0] = -1;
+ fd[1] = -1;
+
+ if (mpage->mlinks->gzip) {
+ if (-1 == pipe(fd)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file, "&pipe gunzip");
+ goto nextpage;
+ }
+ switch (child_pid = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file, "&fork gunzip");
+ child_pid = 0;
+ close(fd[1]);
+ close(fd[0]);
+ goto nextpage;
+ case 0:
+ close(fd[0]);
+ if (-1 == dup2(fd[1], STDOUT_FILENO)) {
+ say(mpage->mlinks->file,
+ "&dup gunzip");
+ exit(1);
+ }
+ execlp("gunzip", "gunzip", "-c",
+ mpage->mlinks->file, NULL);
+ say(mpage->mlinks->file, "&exec gunzip");
+ exit(1);
+ default:
+ close(fd[1]);
+ break;
+ }
+ }
+
+ /*
+ * Try interpreting the file as mdoc(7) or man(7)
+ * source code, unless it is already known to be
+ * formatted. Fall back to formatted mode.
+ */
+ if (FORM_CAT != mpage->mlinks->dform ||
+ FORM_CAT != mpage->mlinks->fform) {
+ lvl = mparse_readfd(mp, fd[0], mpage->mlinks->file);
+ if (lvl < MANDOCLEVEL_FATAL)
+ mparse_result(mp, &mdoc, &man, &sodest);
+ }
+
+ if (NULL != sodest) {
+ mlink_dest = ohash_find(&mlinks,
+ ohash_qlookup(&mlinks, sodest));
+ if (NULL != mlink_dest) {
+
+ /* The .so target exists. */
+
+ mpage_dest = mlink_dest->mpage;
+ mlink = mpage->mlinks;
+ while (1) {
+ mlink->mpage = mpage_dest;
+
+ /*
+ * If the target was already
+ * processed, add the links
+ * to the database now.
+ * Otherwise, this will
+ * happen when we come
+ * to the target.
+ */
+
+ if (mpage_dest->pageid)
+ dbadd_mlink(mlink);
+
+ if (NULL == mlink->next)
+ break;
+ mlink = mlink->next;
+ }
+
+ /* Move all links to the target. */
+
+ mlink->next = mlink_dest->next;
+ mlink_dest->next = mpage->mlinks;
+ mpage->mlinks = NULL;
+ }
+ goto nextpage;
+ } else if (NULL != mdoc) {
+ mpage->form = FORM_SRC;
+ mpage->sec = mdoc_meta(mdoc)->msec;
+ mpage->sec = mandoc_strdup(
+ NULL == mpage->sec ? "" : mpage->sec);
+ mpage->arch = mdoc_meta(mdoc)->arch;
+ mpage->arch = mandoc_strdup(
+ NULL == mpage->arch ? "" : mpage->arch);
+ mpage->title =
+ mandoc_strdup(mdoc_meta(mdoc)->title);
+ } else if (NULL != man) {
+ mpage->form = FORM_SRC;
+ mpage->sec =
+ mandoc_strdup(man_meta(man)->msec);
+ mpage->arch =
+ mandoc_strdup(mpage->mlinks->arch);
+ mpage->title =
+ mandoc_strdup(man_meta(man)->title);
+ } else {
+ mpage->form = FORM_CAT;
+ mpage->sec =
+ mandoc_strdup(mpage->mlinks->dsec);
+ mpage->arch =
+ mandoc_strdup(mpage->mlinks->arch);
+ mpage->title =
+ mandoc_strdup(mpage->mlinks->name);
+ }
+ putkey(mpage, mpage->sec, TYPE_sec);
+ putkey(mpage, '\0' == *mpage->arch ?
+ any : mpage->arch, TYPE_arch);
+
+ for (mlink = mpage->mlinks; mlink; mlink = mlink->next) {
+ if ('\0' != *mlink->dsec)
+ putkey(mpage, mlink->dsec, TYPE_sec);
+ if ('\0' != *mlink->fsec)
+ putkey(mpage, mlink->fsec, TYPE_sec);
+ putkey(mpage, '\0' == *mlink->arch ?
+ any : mlink->arch, TYPE_arch);
+ putkey(mpage, mlink->name, NAME_FILE);
+ }
+
+ assert(NULL == mpage->desc);
+ if (NULL != mdoc) {
+ if (NULL != (cp = mdoc_meta(mdoc)->name))
+ putkey(mpage, cp, NAME_HEAD);
+ parse_mdoc(mpage, mdoc_node(mdoc));
+ } else if (NULL != man)
+ parse_man(mpage, man_node(man));
+ else
+ parse_cat(mpage, fd[0]);
+ if (NULL == mpage->desc)
+ mpage->desc = mandoc_strdup(mpage->mlinks->name);
+
+ if (warnings && !use_all)
+ for (mlink = mpage->mlinks; mlink;
+ mlink = mlink->next)
+ mlink_check(mpage, mlink);
+
+ dbadd(mpage, mc);
+
+nextpage:
+ if (child_pid) {
+ if (-1 == waitpid(child_pid, &status, 0)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file, "&wait gunzip");
+ } else if (WIFSIGNALED(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file,
+ "gunzip died from signal %d",
+ WTERMSIG(status));
+ } else if (WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file,
+ "gunzip failed with code %d",
+ WEXITSTATUS(status));
+ }
+ }
+ ohash_delete(&strings);
+ ohash_delete(&names);
+ mpage = ohash_next(&mpages, &pslot);
+ }
+
+ if (0 == nodb)
+ SQL_EXEC("END TRANSACTION");
+}
+
+static void
+names_check(void)
+{
+ sqlite3_stmt *stmt;
+ const char *name, *sec, *arch, *key;
+ int irc;
+
+ sqlite3_prepare_v2(db,
+ "SELECT name, sec, arch, key FROM ("
+ "SELECT name AS key, pageid FROM names "
+ "WHERE bits & ? AND NOT EXISTS ("
+ "SELECT pageid FROM mlinks "
+ "WHERE mlinks.pageid == names.pageid "
+ "AND mlinks.name == names.name"
+ ")"
+ ") JOIN ("
+ "SELECT sec, arch, name, pageid FROM mlinks "
+ "GROUP BY pageid"
+ ") USING (pageid);",
+ -1, &stmt, NULL);
+
+ if (SQLITE_OK != sqlite3_bind_int64(stmt, 1, NAME_TITLE))
+ say("", "%s", sqlite3_errmsg(db));
+
+ while (SQLITE_ROW == (irc = sqlite3_step(stmt))) {
+ name = (const char *)sqlite3_column_text(stmt, 0);
+ sec = (const char *)sqlite3_column_text(stmt, 1);
+ arch = (const char *)sqlite3_column_text(stmt, 2);
+ key = (const char *)sqlite3_column_text(stmt, 3);
+ say("", "%s(%s%s%s) lacks mlink \"%s\"", name, sec,
+ '\0' == *arch ? "" : "/",
+ '\0' == *arch ? "" : arch, key);
+ }
+ sqlite3_finalize(stmt);
+}
+
+static void
+parse_cat(struct mpage *mpage, int fd)
+{
+ FILE *stream;
+ char *line, *p, *title;
+ size_t len, plen, titlesz;
+
+ stream = (-1 == fd) ?
+ fopen(mpage->mlinks->file, "r") :
+ fdopen(fd, "r");
+ if (NULL == stream) {
+ if (warnings)
+ say(mpage->mlinks->file, "&fopen");
+ return;
+ }
+
+ /* Skip to first blank line. */
+
+ while (NULL != (line = fgetln(stream, &len)))
+ if ('\n' == *line)
+ break;
+
+ /*
+ * Assume the first line that is not indented
+ * is the first section header. Skip to it.
+ */
+
+ while (NULL != (line = fgetln(stream, &len)))
+ if ('\n' != *line && ' ' != *line)
+ break;
+
+ /*
+ * Read up until the next section into a buffer.
+ * Strip the leading and trailing newline from each read line,
+ * appending a trailing space.
+ * Ignore empty (whitespace-only) lines.
+ */
+
+ titlesz = 0;
+ title = NULL;
+
+ while (NULL != (line = fgetln(stream, &len))) {
+ if (' ' != *line || '\n' != line[len - 1])
+ break;
+ while (len > 0 && isspace((unsigned char)*line)) {
+ line++;
+ len--;
+ }
+ if (1 == len)
+ continue;
+ title = mandoc_realloc(title, titlesz + len);
+ memcpy(title + titlesz, line, len);
+ titlesz += len;
+ title[titlesz - 1] = ' ';
+ }
+
+ /*
+ * If no page content can be found, or the input line
+ * is already the next section header, or there is no
+ * trailing newline, reuse the page title as the page
+ * description.
+ */
+
+ if (NULL == title || '\0' == *title) {
+ if (warnings)
+ say(mpage->mlinks->file,
+ "Cannot find NAME section");
+ fclose(stream);
+ free(title);
+ return;
+ }
+
+ title = mandoc_realloc(title, titlesz + 1);
+ title[titlesz] = '\0';
+
+ /*
+ * Skip to the first dash.
+ * Use the remaining line as the description (no more than 70
+ * bytes).
+ */
+
+ if (NULL != (p = strstr(title, "- "))) {
+ for (p += 2; ' ' == *p || '\b' == *p; p++)
+ /* Skip to next word. */ ;
+ } else {
+ if (warnings)
+ say(mpage->mlinks->file,
+ "No dash in title line");
+ p = title;
+ }
+
+ plen = strlen(p);
+
+ /* Strip backspace-encoding from line. */
+
+ while (NULL != (line = memchr(p, '\b', plen))) {
+ len = line - p;
+ if (0 == len) {
+ memmove(line, line + 1, plen--);
+ continue;
+ }
+ memmove(line - 1, line + 1, plen - len);
+ plen -= 2;
+ }
+
+ mpage->desc = mandoc_strdup(p);
+ fclose(stream);
+ free(title);
+}
+
+/*
+ * Put a type/word pair into the word database for this particular file.
+ */
+static void
+putkey(const struct mpage *mpage, char *value, uint64_t type)
+{
+ char *cp;
+
+ assert(NULL != value);
+ if (TYPE_arch == type)
+ for (cp = value; *cp; cp++)
+ if (isupper((unsigned char)*cp))
+ *cp = _tolower((unsigned char)*cp);
+ putkeys(mpage, value, strlen(value), type);
+}
+
+/*
+ * Grok all nodes at or below a certain mdoc node into putkey().
+ */
+static void
+putmdockey(const struct mpage *mpage,
+ const struct mdoc_node *n, uint64_t m)
+{
+
+ for ( ; NULL != n; n = n->next) {
+ if (NULL != n->child)
+ putmdockey(mpage, n->child, m);
+ if (MDOC_TEXT == n->type)
+ putkey(mpage, n->string, m);
+ }
+}
+
+static void
+parse_man(struct mpage *mpage, const struct man_node *n)
+{
+ const struct man_node *head, *body;
+ char *start, *title;
+ char byte;
+ size_t sz;
+
+ if (NULL == n)
+ return;
+
+ /*
+ * We're only searching for one thing: the first text child in
+ * the BODY of a NAME section. Since we don't keep track of
+ * sections in -man, run some hoops to find out whether we're in
+ * the correct section or not.
+ */
+
+ if (MAN_BODY == n->type && MAN_SH == n->tok) {
+ body = n;
+ assert(body->parent);
+ if (NULL != (head = body->parent->head) &&
+ 1 == head->nchild &&
+ NULL != (head = (head->child)) &&
+ MAN_TEXT == head->type &&
+ 0 == strcmp(head->string, "NAME") &&
+ NULL != body->child) {
+
+ /*
+ * Suck the entire NAME section into memory.
+ * Yes, we might run away.
+ * But too many manuals have big, spread-out
+ * NAME sections over many lines.
+ */
+
+ title = NULL;
+ man_deroff(&title, body);
+ if (NULL == title)
+ return;
+
+ /*
+ * Go through a special heuristic dance here.
+ * Conventionally, one or more manual names are
+ * comma-specified prior to a whitespace, then a
+ * dash, then a description. Try to puzzle out
+ * the name parts here.
+ */
+
+ start = title;
+ for ( ;; ) {
+ sz = strcspn(start, " ,");
+ if ('\0' == start[sz])
+ break;
+
+ byte = start[sz];
+ start[sz] = '\0';
+
+ /*
+ * Assume a stray trailing comma in the
+ * name list if a name begins with a dash.
+ */
+
+ if ('-' == start[0] ||
+ ('\\' == start[0] && '-' == start[1]))
+ break;
+
+ putkey(mpage, start, NAME_TITLE);
+
+ if (' ' == byte) {
+ start += sz + 1;
+ break;
+ }
+
+ assert(',' == byte);
+ start += sz + 1;
+ while (' ' == *start)
+ start++;
+ }
+
+ if (start == title) {
+ putkey(mpage, start, NAME_TITLE);
+ free(title);
+ return;
+ }
+
+ while (isspace((unsigned char)*start))
+ start++;
+
+ if (0 == strncmp(start, "-", 1))
+ start += 1;
+ else if (0 == strncmp(start, "\\-\\-", 4))
+ start += 4;
+ else if (0 == strncmp(start, "\\-", 2))
+ start += 2;
+ else if (0 == strncmp(start, "\\(en", 4))
+ start += 4;
+ else if (0 == strncmp(start, "\\(em", 4))
+ start += 4;
+
+ while (' ' == *start)
+ start++;
+
+ mpage->desc = mandoc_strdup(start);
+ free(title);
+ return;
+ }
+ }
+
+ for (n = n->child; n; n = n->next) {
+ if (NULL != mpage->desc)
+ break;
+ parse_man(mpage, n);
+ }
+}
+
+static void
+parse_mdoc(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ assert(NULL != n);
+ for (n = n->child; NULL != n; n = n->next) {
+ switch (n->type) {
+ case MDOC_ELEM:
+ /* FALLTHROUGH */
+ case MDOC_BLOCK:
+ /* FALLTHROUGH */
+ case MDOC_HEAD:
+ /* FALLTHROUGH */
+ case MDOC_BODY:
+ /* FALLTHROUGH */
+ case MDOC_TAIL:
+ if (NULL != mdocs[n->tok].fp)
+ if (0 == (*mdocs[n->tok].fp)(mpage, n))
+ break;
+ if (mdocs[n->tok].mask)
+ putmdockey(mpage, n->child,
+ mdocs[n->tok].mask);
+ break;
+ default:
+ assert(MDOC_ROOT != n->type);
+ continue;
+ }
+ if (NULL != n->child)
+ parse_mdoc(mpage, n);
+ }
+}
+
+static int
+parse_mdoc_Fd(struct mpage *mpage, const struct mdoc_node *n)
+{
+ const char *start, *end;
+ size_t sz;
+
+ if (SEC_SYNOPSIS != n->sec ||
+ NULL == (n = n->child) ||
+ MDOC_TEXT != n->type)
+ return(0);
+
+ /*
+ * Only consider those `Fd' macro fields that begin with an
+ * "inclusion" token (versus, e.g., #define).
+ */
+
+ if (strcmp("#include", n->string))
+ return(0);
+
+ if (NULL == (n = n->next) || MDOC_TEXT != n->type)
+ return(0);
+
+ /*
+ * Strip away the enclosing angle brackets and make sure we're
+ * not zero-length.
+ */
+
+ start = n->string;
+ if ('<' == *start || '"' == *start)
+ start++;
+
+ if (0 == (sz = strlen(start)))
+ return(0);
+
+ end = &start[(int)sz - 1];
+ if ('>' == *end || '"' == *end)
+ end--;
+
+ if (end > start)
+ putkeys(mpage, start, end - start + 1, TYPE_In);
+ return(0);
+}
+
+static int
+parse_mdoc_Fn(struct mpage *mpage, const struct mdoc_node *n)
+{
+ char *cp;
+
+ if (NULL == (n = n->child) || MDOC_TEXT != n->type)
+ return(0);
+
+ /*
+ * Parse: .Fn "struct type *name" "char *arg".
+ * First strip away pointer symbol.
+ * Then store the function name, then type.
+ * Finally, store the arguments.
+ */
+
+ if (NULL == (cp = strrchr(n->string, ' ')))
+ cp = n->string;
+
+ while ('*' == *cp)
+ cp++;
+
+ putkey(mpage, cp, TYPE_Fn);
+
+ if (n->string < cp)
+ putkeys(mpage, n->string, cp - n->string, TYPE_Ft);
+
+ for (n = n->next; NULL != n; n = n->next)
+ if (MDOC_TEXT == n->type)
+ putkey(mpage, n->string, TYPE_Fa);
+
+ return(0);
+}
+
+static int
+parse_mdoc_Xr(struct mpage *mpage, const struct mdoc_node *n)
+{
+ char *cp;
+
+ if (NULL == (n = n->child))
+ return(0);
+
+ if (NULL == n->next) {
+ putkey(mpage, n->string, TYPE_Xr);
+ return(0);
+ }
+
+ mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string);
+ putkey(mpage, cp, TYPE_Xr);
+ free(cp);
+ return(0);
+}
+
+static int
+parse_mdoc_Nd(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ if (MDOC_BODY == n->type)
+ mdoc_deroff(&mpage->desc, n);
+ return(0);
+}
+
+static int
+parse_mdoc_Nm(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ if (SEC_NAME == n->sec)
+ putmdockey(mpage, n->child, NAME_TITLE);
+ else if (SEC_SYNOPSIS == n->sec && MDOC_HEAD == n->type)
+ putmdockey(mpage, n->child, NAME_SYN);
+ return(0);
+}
+
+static int
+parse_mdoc_Sh(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ return(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type);
+}
+
+static int
+parse_mdoc_head(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ return(MDOC_HEAD == n->type);
+}
+
+static int
+parse_mdoc_body(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ return(MDOC_BODY == n->type);
+}
+
+/*
+ * Add a string to the hash table for the current manual.
+ * Each string has a bitmask telling which macros it belongs to.
+ * When we finish the manual, we'll dump the table.
+ */
+static void
+putkeys(const struct mpage *mpage,
+ const char *cp, size_t sz, uint64_t v)
+{
+ struct ohash *htab;
+ struct str *s;
+ const char *end;
+ unsigned int slot;
+ int i;
+
+ if (0 == sz)
+ return;
+
+ if (TYPE_Nm & v) {
+ htab = &names;
+ v &= name_mask;
+ name_mask &= ~NAME_FIRST;
+ if (debug > 1)
+ say(mpage->mlinks->file,
+ "Adding name %*s", sz, cp);
+ } else {
+ htab = &strings;
+ if (debug > 1)
+ for (i = 0; i < mansearch_keymax; i++)
+ if (1 << i & v)
+ say(mpage->mlinks->file,
+ "Adding key %s=%*s",
+ mansearch_keynames[i], sz, cp);
+ }
+
+ end = cp + sz;
+ slot = ohash_qlookupi(htab, cp, &end);
+ s = ohash_find(htab, slot);
+
+ if (NULL != s && mpage == s->mpage) {
+ s->mask |= v;
+ return;
+ } else if (NULL == s) {
+ s = mandoc_calloc(1, sizeof(struct str) + sz + 1);
+ memcpy(s->key, cp, sz);
+ ohash_insert(htab, slot, s);
+ }
+ s->mpage = mpage;
+ s->mask = v;
+}
+
+/*
+ * Take a Unicode codepoint and produce its UTF-8 encoding.
+ * This isn't the best way to do this, but it works.
+ * The magic numbers are from the UTF-8 packaging.
+ * They're not as scary as they seem: read the UTF-8 spec for details.
+ */
+static size_t
+utf8(unsigned int cp, char out[7])
+{
+ size_t rc;
+
+ rc = 0;
+ if (cp <= 0x0000007F) {
+ rc = 1;
+ out[0] = (char)cp;
+ } else if (cp <= 0x000007FF) {
+ rc = 2;
+ out[0] = (cp >> 6 & 31) | 192;
+ out[1] = (cp & 63) | 128;
+ } else if (cp <= 0x0000FFFF) {
+ rc = 3;
+ out[0] = (cp >> 12 & 15) | 224;
+ out[1] = (cp >> 6 & 63) | 128;
+ out[2] = (cp & 63) | 128;
+ } else if (cp <= 0x001FFFFF) {
+ rc = 4;
+ out[0] = (cp >> 18 & 7) | 240;
+ out[1] = (cp >> 12 & 63) | 128;
+ out[2] = (cp >> 6 & 63) | 128;
+ out[3] = (cp & 63) | 128;
+ } else if (cp <= 0x03FFFFFF) {
+ rc = 5;
+ out[0] = (cp >> 24 & 3) | 248;
+ out[1] = (cp >> 18 & 63) | 128;
+ out[2] = (cp >> 12 & 63) | 128;
+ out[3] = (cp >> 6 & 63) | 128;
+ out[4] = (cp & 63) | 128;
+ } else if (cp <= 0x7FFFFFFF) {
+ rc = 6;
+ out[0] = (cp >> 30 & 1) | 252;
+ out[1] = (cp >> 24 & 63) | 128;
+ out[2] = (cp >> 18 & 63) | 128;
+ out[3] = (cp >> 12 & 63) | 128;
+ out[4] = (cp >> 6 & 63) | 128;
+ out[5] = (cp & 63) | 128;
+ } else
+ return(0);
+
+ out[rc] = '\0';
+ return(rc);
+}
+
+/*
+ * Store the rendered version of a key, or alias the pointer
+ * if the key contains no escape sequences.
+ */
+static void
+render_key(struct mchars *mc, struct str *key)
+{
+ size_t sz, bsz, pos;
+ char utfbuf[7], res[6];
+ char *buf;
+ const char *seq, *cpp, *val;
+ int len, u;
+ enum mandoc_esc esc;
+
+ assert(NULL == key->rendered);
+
+ res[0] = '\\';
+ res[1] = '\t';
+ res[2] = ASCII_NBRSP;
+ res[3] = ASCII_HYPH;
+ res[4] = ASCII_BREAK;
+ res[5] = '\0';
+
+ val = key->key;
+ bsz = strlen(val);
+
+ /*
+ * Pre-check: if we have no stop-characters, then set the
+ * pointer as ourselvse and get out of here.
+ */
+ if (strcspn(val, res) == bsz) {
+ key->rendered = key->key;
+ return;
+ }
+
+ /* Pre-allocate by the length of the input */
+
+ buf = mandoc_malloc(++bsz);
+ pos = 0;
+
+ while ('\0' != *val) {
+ /*
+ * Halt on the first escape sequence.
+ * This also halts on the end of string, in which case
+ * we just copy, fallthrough, and exit the loop.
+ */
+ if ((sz = strcspn(val, res)) > 0) {
+ memcpy(&buf[pos], val, sz);
+ pos += sz;
+ val += sz;
+ }
+
+ switch (*val) {
+ case ASCII_HYPH:
+ buf[pos++] = '-';
+ val++;
+ continue;
+ case '\t':
+ /* FALLTHROUGH */
+ case ASCII_NBRSP:
+ buf[pos++] = ' ';
+ val++;
+ /* FALLTHROUGH */
+ case ASCII_BREAK:
+ continue;
+ default:
+ break;
+ }
+ if ('\\' != *val)
+ break;
+
+ /* Read past the slash. */
+
+ val++;
+
+ /*
+ * Parse the escape sequence and see if it's a
+ * predefined character or special character.
+ */
+
+ esc = mandoc_escape((const char **)&val,
+ &seq, &len);
+ if (ESCAPE_ERROR == esc)
+ break;
+ if (ESCAPE_SPECIAL != esc)
+ continue;
+
+ /*
+ * Render the special character
+ * as either UTF-8 or ASCII.
+ */
+
+ if (write_utf8) {
+ if (0 == (u = mchars_spec2cp(mc, seq, len)))
+ continue;
+ cpp = utfbuf;
+ if (0 == (sz = utf8(u, utfbuf)))
+ continue;
+ sz = strlen(cpp);
+ } else {
+ cpp = mchars_spec2str(mc, seq, len, &sz);
+ if (NULL == cpp)
+ continue;
+ if (ASCII_NBRSP == *cpp) {
+ cpp = " ";
+ sz = 1;
+ }
+ }
+
+ /* Copy the rendered glyph into the stream. */
+
+ bsz += sz;
+ buf = mandoc_realloc(buf, bsz);
+ memcpy(&buf[pos], cpp, sz);
+ pos += sz;
+ }
+
+ buf[pos] = '\0';
+ key->rendered = buf;
+}
+
+static void
+dbadd_mlink(const struct mlink *mlink)
+{
+ size_t i;
+
+ i = 1;
+ SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name);
+ SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid);
+ SQL_STEP(stmts[STMT_INSERT_LINK]);
+ sqlite3_reset(stmts[STMT_INSERT_LINK]);
+}
+
+/*
+ * Flush the current page's terms (and their bits) into the database.
+ * Wrap the entire set of additions in a transaction to make sqlite be a
+ * little faster.
+ * Also, handle escape sequences at the last possible moment.
+ */
+static void
+dbadd(struct mpage *mpage, struct mchars *mc)
+{
+ struct mlink *mlink;
+ struct str *key;
+ size_t i;
+ unsigned int slot;
+
+ mlink = mpage->mlinks;
+
+ if (nodb) {
+ for (key = ohash_first(&names, &slot); NULL != key;
+ key = ohash_next(&names, &slot)) {
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+ for (key = ohash_first(&strings, &slot); NULL != key;
+ key = ohash_next(&strings, &slot)) {
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+ if (0 == debug)
+ return;
+ while (NULL != mlink) {
+ fputs(mlink->name, stdout);
+ if (NULL == mlink->next ||
+ strcmp(mlink->dsec, mlink->next->dsec) ||
+ strcmp(mlink->fsec, mlink->next->fsec) ||
+ strcmp(mlink->arch, mlink->next->arch)) {
+ putchar('(');
+ if ('\0' == *mlink->dsec)
+ fputs(mlink->fsec, stdout);
+ else
+ fputs(mlink->dsec, stdout);
+ if ('\0' != *mlink->arch)
+ printf("/%s", mlink->arch);
+ putchar(')');
+ }
+ mlink = mlink->next;
+ if (NULL != mlink)
+ fputs(", ", stdout);
+ }
+ printf(" - %s\n", mpage->desc);
+ return;
+ }
+
+ if (debug)
+ say(mlink->file, "Adding to database");
+
+ i = strlen(mpage->desc) + 1;
+ key = mandoc_calloc(1, sizeof(struct str) + i);
+ memcpy(key->key, mpage->desc, i);
+ render_key(mc, key);
+
+ i = 1;
+ SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, key->rendered);
+ SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, FORM_SRC == mpage->form);
+ SQL_STEP(stmts[STMT_INSERT_PAGE]);
+ mpage->pageid = sqlite3_last_insert_rowid(db);
+ sqlite3_reset(stmts[STMT_INSERT_PAGE]);
+
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+
+ while (NULL != mlink) {
+ dbadd_mlink(mlink);
+ mlink = mlink->next;
+ }
+ mlink = mpage->mlinks;
+
+ for (key = ohash_first(&names, &slot); NULL != key;
+ key = ohash_next(&names, &slot)) {
+ assert(key->mpage == mpage);
+ if (NULL == key->rendered)
+ render_key(mc, key);
+ i = 1;
+ SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->rendered);
+ SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid);
+ SQL_STEP(stmts[STMT_INSERT_NAME]);
+ sqlite3_reset(stmts[STMT_INSERT_NAME]);
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+ for (key = ohash_first(&strings, &slot); NULL != key;
+ key = ohash_next(&strings, &slot)) {
+ assert(key->mpage == mpage);
+ if (NULL == key->rendered)
+ render_key(mc, key);
+ i = 1;
+ SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->rendered);
+ SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid);
+ SQL_STEP(stmts[STMT_INSERT_KEY]);
+ sqlite3_reset(stmts[STMT_INSERT_KEY]);
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+}
+
+static void
+dbprune(void)
+{
+ struct mpage *mpage;
+ struct mlink *mlink;
+ size_t i;
+ unsigned int slot;
+
+ if (0 == nodb)
+ SQL_EXEC("BEGIN TRANSACTION");
+
+ for (mpage = ohash_first(&mpages, &slot); NULL != mpage;
+ mpage = ohash_next(&mpages, &slot)) {
+ mlink = mpage->mlinks;
+ if (debug)
+ say(mlink->file, "Deleting from database");
+ if (nodb)
+ continue;
+ for ( ; NULL != mlink; mlink = mlink->next) {
+ i = 1;
+ SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
+ i, mlink->dsec);
+ SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
+ i, mlink->arch);
+ SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
+ i, mlink->name);
+ SQL_STEP(stmts[STMT_DELETE_PAGE]);
+ sqlite3_reset(stmts[STMT_DELETE_PAGE]);
+ }
+ }
+
+ if (0 == nodb)
+ SQL_EXEC("END TRANSACTION");
+}
+
+/*
+ * Close an existing database and its prepared statements.
+ * If "real" is not set, rename the temporary file into the real one.
+ */
+static void
+dbclose(int real)
+{
+ size_t i;
+ int status;
+ pid_t child;
+
+ if (nodb)
+ return;
+
+ for (i = 0; i < STMT__MAX; i++) {
+ sqlite3_finalize(stmts[i]);
+ stmts[i] = NULL;
+ }
+
+ sqlite3_close(db);
+ db = NULL;
+
+ if (real)
+ return;
+
+ if ('\0' == *tempfilename) {
+ if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "&rename");
+ }
+ return;
+ }
+
+ switch (child = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fork cmp");
+ return;
+ case 0:
+ execlp("cmp", "cmp", "-s",
+ tempfilename, MANDOC_DB, NULL);
+ say("", "&exec cmp");
+ exit(0);
+ default:
+ break;
+ }
+ if (-1 == waitpid(child, &status, 0)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&wait cmp");
+ } else if (WIFSIGNALED(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "cmp died from signal %d", WTERMSIG(status));
+ } else if (WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB,
+ "Data changed, but cannot replace database");
+ }
+
+ *strrchr(tempfilename, '/') = '\0';
+ switch (child = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fork rm");
+ return;
+ case 0:
+ execlp("rm", "rm", "-rf", tempfilename, NULL);
+ say("", "&exec rm");
+ exit((int)MANDOCLEVEL_SYSERR);
+ default:
+ break;
+ }
+ if (-1 == waitpid(child, &status, 0)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&wait rm");
+ } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "%s: Cannot remove temporary directory",
+ tempfilename);
+ }
+}
+
+/*
+ * This is straightforward stuff.
+ * Open a database connection to a "temporary" database, then open a set
+ * of prepared statements we'll use over and over again.
+ * If "real" is set, we use the existing database; if not, we truncate a
+ * temporary one.
+ * Must be matched by dbclose().
+ */
+static int
+dbopen(int real)
+{
+ const char *sql;
+ int rc, ofl;
+
+ if (nodb)
+ return(1);
+
+ *tempfilename = '\0';
+ ofl = SQLITE_OPEN_READWRITE;
+
+ if (real) {
+ rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL);
+ if (SQLITE_OK != rc) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ if (SQLITE_CANTOPEN != rc)
+ say(MANDOC_DB, "%s", sqlite3_errstr(rc));
+ return(0);
+ }
+ goto prepare_statements;
+ }
+
+ ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE;
+
+ remove(MANDOC_DB "~");
+ rc = sqlite3_open_v2(MANDOC_DB "~", &db, ofl, NULL);
+ if (SQLITE_OK == rc)
+ goto create_tables;
+ if (MPARSE_QUICK & mparse_options) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB "~", "%s", sqlite3_errstr(rc));
+ return(0);
+ }
+
+ (void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX",
+ sizeof(tempfilename));
+ if (NULL == mkdtemp(tempfilename)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&%s", tempfilename);
+ return(0);
+ }
+ (void)strlcat(tempfilename, "/" MANDOC_DB,
+ sizeof(tempfilename));
+ rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL);
+ if (SQLITE_OK != rc) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "%s: %s", tempfilename, sqlite3_errstr(rc));
+ return(0);
+ }
+
+create_tables:
+ sql = "CREATE TABLE \"mpages\" (\n"
+ " \"desc\" TEXT NOT NULL,\n"
+ " \"form\" INTEGER NOT NULL,\n"
+ " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n"
+ ");\n"
+ "\n"
+ "CREATE TABLE \"mlinks\" (\n"
+ " \"sec\" TEXT NOT NULL,\n"
+ " \"arch\" TEXT NOT NULL,\n"
+ " \"name\" TEXT NOT NULL,\n"
+ " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
+ "ON DELETE CASCADE\n"
+ ");\n"
+ "CREATE INDEX mlinks_pageid_idx ON mlinks (pageid);\n"
+ "\n"
+ "CREATE TABLE \"names\" (\n"
+ " \"bits\" INTEGER NOT NULL,\n"
+ " \"name\" TEXT NOT NULL,\n"
+ " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
+ "ON DELETE CASCADE\n"
+ ");\n"
+ "\n"
+ "CREATE TABLE \"keys\" (\n"
+ " \"bits\" INTEGER NOT NULL,\n"
+ " \"key\" TEXT NOT NULL,\n"
+ " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
+ "ON DELETE CASCADE\n"
+ ");\n"
+ "CREATE INDEX keys_pageid_idx ON keys (pageid);\n";
+
+ if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "%s", sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return(0);
+ }
+
+prepare_statements:
+ if (SQLITE_OK != sqlite3_exec(db,
+ "PRAGMA foreign_keys = ON", NULL, NULL, NULL)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "PRAGMA foreign_keys: %s",
+ sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return(0);
+ }
+
+ sql = "DELETE FROM mpages WHERE pageid IN "
+ "(SELECT pageid FROM mlinks WHERE "
+ "sec=? AND arch=? AND name=?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL);
+ sql = "INSERT INTO mpages "
+ "(desc,form) VALUES (?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL);
+ sql = "INSERT INTO mlinks "
+ "(sec,arch,name,pageid) VALUES (?,?,?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL);
+ sql = "INSERT INTO names "
+ "(bits,name,pageid) VALUES (?,?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_NAME], NULL);
+ sql = "INSERT INTO keys "
+ "(bits,key,pageid) VALUES (?,?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL);
+
+#ifndef __APPLE__
+ /*
+ * When opening a new database, we can turn off
+ * synchronous mode for much better performance.
+ */
+
+ if (real && SQLITE_OK != sqlite3_exec(db,
+ "PRAGMA synchronous = OFF", NULL, NULL, NULL)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "PRAGMA synchronous: %s",
+ sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return(0);
+ }
+#endif
+
+ return(1);
+}
+
+static void *
+hash_calloc(size_t n, size_t sz, void *arg)
+{
+
+ return(mandoc_calloc(n, sz));
+}
+
+static void *
+hash_alloc(size_t sz, void *arg)
+{
+
+ return(mandoc_malloc(sz));
+}
+
+static void
+hash_free(void *p, void *arg)
+{
+
+ free(p);
+}
+
+static int
+set_basedir(const char *targetdir)
+{
+ static char startdir[PATH_MAX];
+ static int getcwd_status; /* 1 = ok, 2 = failure */
+ static int chdir_status; /* 1 = changed directory */
+ char *cp;
+
+ /*
+ * Remember the original working directory, if possible.
+ * This will be needed if the second or a later directory
+ * on the command line is given as a relative path.
+ * Do not error out if the current directory is not
+ * searchable: Maybe it won't be needed after all.
+ */
+ if (0 == getcwd_status) {
+ if (NULL == getcwd(startdir, sizeof(startdir))) {
+ getcwd_status = 2;
+ (void)strlcpy(startdir, strerror(errno),
+ sizeof(startdir));
+ } else
+ getcwd_status = 1;
+ }
+
+ /*
+ * We are leaving the old base directory.
+ * Do not use it any longer, not even for messages.
+ */
+ *basedir = '\0';
+
+ /*
+ * If and only if the directory was changed earlier and
+ * the next directory to process is given as a relative path,
+ * first go back, or bail out if that is impossible.
+ */
+ if (chdir_status && '/' != *targetdir) {
+ if (2 == getcwd_status) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "getcwd: %s", startdir);
+ return(0);
+ }
+ if (-1 == chdir(startdir)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&chdir %s", startdir);
+ return(0);
+ }
+ }
+
+ /*
+ * Always resolve basedir to the canonicalized absolute
+ * pathname and append a trailing slash, such that
+ * we can reliably check whether files are inside.
+ */
+ if (NULL == realpath(targetdir, basedir)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&%s: realpath", targetdir);
+ return(0);
+ } else if (-1 == chdir(basedir)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&chdir");
+ return(0);
+ }
+ chdir_status = 1;
+ cp = strchr(basedir, '\0');
+ if ('/' != cp[-1]) {
+ if (cp - basedir >= PATH_MAX - 1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "Filename too long");
+ return(0);
+ }
+ *cp++ = '/';
+ *cp = '\0';
+ }
+ return(1);
+}
+
+static void
+say(const char *file, const char *format, ...)
+{
+ va_list ap;
+ int use_errno;
+
+ if ('\0' != *basedir)
+ fprintf(stderr, "%s", basedir);
+ if ('\0' != *basedir && '\0' != *file)
+ fputc('/', stderr);
+ if ('\0' != *file)
+ fprintf(stderr, "%s", file);
+
+ use_errno = 1;
+ if (NULL != format) {
+ switch (*format) {
+ case '&':
+ format++;
+ break;
+ case '\0':
+ format = NULL;
+ break;
+ default:
+ use_errno = 0;
+ break;
+ }
+ }
+ if (NULL != format) {
+ if ('\0' != *basedir || '\0' != *file)
+ fputs(": ", stderr);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ }
+ if (use_errno) {
+ if ('\0' != *basedir || '\0' != *file || NULL != format)
+ fputs(": ", stderr);
+ perror(NULL);
+ } else
+ fputc('\n', stderr);
+}
diff --git a/contrib/mdocml/manpage.c b/contrib/mdocml/manpage.c
new file mode 100644
index 0000000..00793cc
--- /dev/null
+++ b/contrib/mdocml/manpage.c
@@ -0,0 +1,190 @@
+/* $Id: manpage.c,v 1.7 2014/01/06 03:02:46 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "manpath.h"
+#include "mansearch.h"
+
+static void show(const char *, const char *);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, term;
+ size_t i, sz, len;
+ struct mansearch search;
+ struct manpage *res;
+ char *conf_file, *defpaths, *auxpaths, *cp;
+ char buf[PATH_MAX];
+ const char *cmd;
+ struct manpaths paths;
+ char *progname;
+ extern char *optarg;
+ extern int optind;
+
+ term = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ auxpaths = defpaths = conf_file = NULL;
+ memset(&paths, 0, sizeof(struct manpaths));
+ memset(&search, 0, sizeof(struct mansearch));
+
+ while (-1 != (ch = getopt(argc, argv, "C:M:m:S:s:")))
+ switch (ch) {
+ case ('C'):
+ conf_file = optarg;
+ break;
+ case ('M'):
+ defpaths = optarg;
+ break;
+ case ('m'):
+ auxpaths = optarg;
+ break;
+ case ('S'):
+ search.arch = optarg;
+ break;
+ case ('s'):
+ search.sec = optarg;
+ break;
+ default:
+ goto usage;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (0 == argc)
+ goto usage;
+
+ search.deftype = TYPE_Nm | TYPE_Nd;
+
+ manpath_parse(&paths, conf_file, defpaths, auxpaths);
+ ch = mansearch(&search, &paths, argc, argv, "Nd", &res, &sz);
+ manpath_free(&paths);
+
+ if (0 == ch)
+ goto usage;
+
+ if (0 == sz) {
+ free(res);
+ return(EXIT_FAILURE);
+ } else if (1 == sz && term) {
+ i = 1;
+ goto show;
+ } else if (NULL == res)
+ return(EXIT_FAILURE);
+
+ for (i = 0; i < sz; i++) {
+ printf("%6zu %s: %s\n",
+ i + 1, res[i].names, res[i].output);
+ free(res[i].names);
+ free(res[i].output);
+ }
+
+ if (0 == term) {
+ for (i = 0; i < sz; i++)
+ free(res[i].file);
+ free(res);
+ return(EXIT_SUCCESS);
+ }
+
+ i = 1;
+ printf("Enter a choice [1]: ");
+ fflush(stdout);
+
+ if (NULL != (cp = fgetln(stdin, &len)))
+ if ('\n' == cp[--len] && len > 0) {
+ cp[len] = '\0';
+ if ((i = atoi(cp)) < 1 || i > sz)
+ i = 0;
+ }
+
+ if (0 == i) {
+ for (i = 0; i < sz; i++)
+ free(res[i].file);
+ free(res);
+ return(EXIT_SUCCESS);
+ }
+show:
+ cmd = res[i - 1].form ? "mandoc" : "cat";
+ strlcpy(buf, res[i - 1].file, PATH_MAX);
+ for (i = 0; i < sz; i++)
+ free(res[i].file);
+ free(res);
+
+ show(cmd, buf);
+ /* NOTREACHED */
+usage:
+ fprintf(stderr, "usage: %s [-C conf] "
+ "[-M paths] "
+ "[-m paths] "
+ "[-S arch] "
+ "[-s section] "
+ "expr ...\n",
+ progname);
+ return(EXIT_FAILURE);
+}
+
+static void
+show(const char *cmd, const char *file)
+{
+ int fds[2];
+ pid_t pid;
+
+ if (-1 == pipe(fds)) {
+ perror(NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ if (-1 == (pid = fork())) {
+ perror(NULL);
+ exit(EXIT_FAILURE);
+ } else if (pid > 0) {
+ dup2(fds[0], STDIN_FILENO);
+ close(fds[1]);
+ cmd = NULL != getenv("MANPAGER") ?
+ getenv("MANPAGER") :
+ (NULL != getenv("PAGER") ?
+ getenv("PAGER") : "more");
+ execlp(cmd, cmd, (char *)NULL);
+ perror(cmd);
+ exit(EXIT_FAILURE);
+ }
+
+ dup2(fds[1], STDOUT_FILENO);
+ close(fds[0]);
+ execlp(cmd, cmd, file, (char *)NULL);
+ perror(cmd);
+ exit(EXIT_FAILURE);
+}
diff --git a/contrib/mdocml/manpath.c b/contrib/mdocml/manpath.c
new file mode 100644
index 0000000..61b2c7e0
--- /dev/null
+++ b/contrib/mdocml/manpath.c
@@ -0,0 +1,222 @@
+/* $Id: manpath.c,v 1.15 2014/04/23 21:06:41 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "manpath.h"
+
+#define MAN_CONF_FILE "/etc/man.conf"
+#define MAN_CONF_KEY "_whatdb"
+
+static void manpath_add(struct manpaths *, const char *);
+static void manpath_parseline(struct manpaths *, char *);
+
+void
+manpath_parse(struct manpaths *dirs, const char *file,
+ char *defp, char *auxp)
+{
+#ifdef USE_MANPATH
+ char cmd[(PATH_MAX * 3) + 20];
+ FILE *stream;
+ char *buf;
+ size_t sz, bsz;
+
+ strlcpy(cmd, "manpath", sizeof(cmd));
+ if (file) {
+ strlcat(cmd, " -C ", sizeof(cmd));
+ strlcat(cmd, file, sizeof(cmd));
+ }
+ if (auxp) {
+ strlcat(cmd, " -m ", sizeof(cmd));
+ strlcat(cmd, auxp, sizeof(cmd));
+ }
+ if (defp) {
+ strlcat(cmd, " -M ", sizeof(cmd));
+ strlcat(cmd, defp, sizeof(cmd));
+ }
+
+ /* Open manpath(1). Ignore errors. */
+
+ stream = popen(cmd, "r");
+ if (NULL == stream)
+ return;
+
+ buf = NULL;
+ bsz = 0;
+
+ /* Read in as much output as we can. */
+
+ do {
+ buf = mandoc_realloc(buf, bsz + 1024);
+ sz = fread(buf + bsz, 1, 1024, stream);
+ bsz += sz;
+ } while (sz > 0);
+
+ if ( ! ferror(stream) && feof(stream) &&
+ bsz && '\n' == buf[bsz - 1]) {
+ buf[bsz - 1] = '\0';
+ manpath_parseline(dirs, buf);
+ }
+
+ free(buf);
+ pclose(stream);
+#else
+ char *insert;
+
+ /* Always prepend -m. */
+ manpath_parseline(dirs, auxp);
+
+ /* If -M is given, it overrides everything else. */
+ if (NULL != defp) {
+ manpath_parseline(dirs, defp);
+ return;
+ }
+
+ /* MANPATH and man.conf(5) cooperate. */
+ defp = getenv("MANPATH");
+ if (NULL == file)
+ file = MAN_CONF_FILE;
+
+ /* No MANPATH; use man.conf(5) only. */
+ if (NULL == defp || '\0' == defp[0]) {
+ manpath_manconf(dirs, file);
+ return;
+ }
+
+ /* Prepend man.conf(5) to MANPATH. */
+ if (':' == defp[0]) {
+ manpath_manconf(dirs, file);
+ manpath_parseline(dirs, defp);
+ return;
+ }
+
+ /* Append man.conf(5) to MANPATH. */
+ if (':' == defp[strlen(defp) - 1]) {
+ manpath_parseline(dirs, defp);
+ manpath_manconf(dirs, file);
+ return;
+ }
+
+ /* Insert man.conf(5) into MANPATH. */
+ insert = strstr(defp, "::");
+ if (NULL != insert) {
+ *insert++ = '\0';
+ manpath_parseline(dirs, defp);
+ manpath_manconf(dirs, file);
+ manpath_parseline(dirs, insert + 1);
+ return;
+ }
+
+ /* MANPATH overrides man.conf(5) completely. */
+ manpath_parseline(dirs, defp);
+#endif
+}
+
+/*
+ * Parse a FULL pathname from a colon-separated list of arrays.
+ */
+static void
+manpath_parseline(struct manpaths *dirs, char *path)
+{
+ char *dir;
+
+ if (NULL == path)
+ return;
+
+ for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
+ manpath_add(dirs, dir);
+}
+
+/*
+ * Add a directory to the array, ignoring bad directories.
+ * Grow the array one-by-one for simplicity's sake.
+ */
+static void
+manpath_add(struct manpaths *dirs, const char *dir)
+{
+ char buf[PATH_MAX];
+ char *cp;
+ size_t i;
+
+ if (NULL == (cp = realpath(dir, buf)))
+ return;
+
+ for (i = 0; i < dirs->sz; i++)
+ if (0 == strcmp(dirs->paths[i], dir))
+ return;
+
+ dirs->paths = mandoc_reallocarray(dirs->paths,
+ dirs->sz + 1, sizeof(char *));
+
+ dirs->paths[dirs->sz++] = mandoc_strdup(cp);
+}
+
+void
+manpath_free(struct manpaths *p)
+{
+ size_t i;
+
+ for (i = 0; i < p->sz; i++)
+ free(p->paths[i]);
+
+ free(p->paths);
+}
+
+void
+manpath_manconf(struct manpaths *dirs, const char *file)
+{
+ FILE *stream;
+ char *p, *q;
+ size_t len, keysz;
+
+ keysz = strlen(MAN_CONF_KEY);
+ assert(keysz > 0);
+
+ if (NULL == (stream = fopen(file, "r")))
+ return;
+
+ while (NULL != (p = fgetln(stream, &len))) {
+ if (0 == len || '\n' != p[--len])
+ break;
+ p[len] = '\0';
+ while (isspace((unsigned char)*p))
+ p++;
+ if (strncmp(MAN_CONF_KEY, p, keysz))
+ continue;
+ p += keysz;
+ while (isspace((unsigned char)*p))
+ p++;
+ if ('\0' == *p)
+ continue;
+ if (NULL == (q = strrchr(p, '/')))
+ continue;
+ *q = '\0';
+ manpath_add(dirs, p);
+ }
+
+ fclose(stream);
+}
diff --git a/contrib/mdocml/manpath.h b/contrib/mdocml/manpath.h
new file mode 100644
index 0000000..2fe1b36
--- /dev/null
+++ b/contrib/mdocml/manpath.h
@@ -0,0 +1,38 @@
+/* $Id: manpath.h,v 1.6 2012/06/08 10:32:40 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 MANPATH_H
+#define MANPATH_H
+
+/*
+ * Unsorted list of unique, absolute paths to be searched for manual
+ * databases.
+ */
+struct manpaths {
+ size_t sz;
+ char **paths;
+};
+
+__BEGIN_DECLS
+
+void manpath_manconf(struct manpaths *, const char *);
+void manpath_parse(struct manpaths *, const char *, char *, char *);
+void manpath_free(struct manpaths *);
+
+__END_DECLS
+
+#endif /*!MANPATH_H*/
diff --git a/contrib/mdocml/mansearch.3 b/contrib/mdocml/mansearch.3
new file mode 100644
index 0000000..28590e5
--- /dev/null
+++ b/contrib/mdocml/mansearch.3
@@ -0,0 +1,228 @@
+.\" $Id: mansearch.3,v 1.2 2014/08/05 15:29:30 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MANSEARCH 3
+.Os
+.Sh NAME
+.Nm mansearch ,
+.Nm mansearch_setup
+.Nd search manual page databases
+.Sh SYNOPSIS
+.In stdint.h
+.In manpath.h
+.In mansearch.h
+.Ft int
+.Fo mansearch_setup
+.Fa "int start"
+.Fc
+.Ft int
+.Fo mansearch
+.Fa "const struct mansearch *search"
+.Fa "const struct manpaths *paths"
+.Fa "int argc"
+.Fa "char *argv[]"
+.Fa "const char *outkey"
+.Fa "struct manpage **res"
+.Fa "size_t *sz"
+.Fc
+.Sh DESCRIPTION
+The
+.Fn mansearch
+function returns information about manuals matching a search query from a
+.Xr mandoc.db 5
+SQLite3 database.
+.Pp
+The query arguments are as follows:
+.Bl -tag -width Ds
+.It Fa "const struct mansearch *search"
+Search options, defined in
+.In mansearch.h .
+.It Fa "const struct manpaths *paths"
+Directories to be searched, defined in
+.In manpath.h .
+.It Fa "int argc" , "char *argv[]"
+Search criteria, usually taken from the command line.
+.El
+.Pp
+The
+.Fa "const char *outkey"
+selects which data to return in the
+.Va output
+field of the
+.Fa res
+structures.
+It takes any of the macro keys defined in
+.Pa mansearch_const.c
+and described in
+.Xr apropos 1 .
+.Pp
+The output arguments are as follows:
+.Bl -tag -width Ds
+.It Fa "struct manpage **res"
+Returns a pointer to an array of result structures defined in
+.In mansearch.h .
+The user is expected to call
+.Xr free 3
+on the
+.Va file ,
+.Va names ,
+and
+.Va output
+fields of all structures, as well as the
+.Fa res
+array itself.
+.It Fa "size_t *sz"
+Returns the number of result structures contained in
+.Fa res .
+.El
+.Pp
+To speed up searches, the
+.Fn mansearch_setup
+function can optionally be called with a
+.Fa start
+argument of 1 before
+.Fn mansearch
+to set up an SQLite3 pagecache.
+If it was called, it has to be called again with a
+.Fa start
+argument of 0 after the last call to
+.Fn mansearch
+to release the memory used for the pagecache.
+.Sh IMPLEMENTATION NOTES
+For each manual page tree, the search is done in two steps.
+In the first step, a list of pages matching the search criteria is built.
+In the second step, the requested information about these pages is
+retrieved from the database and assembled into the
+.Fa res
+array.
+.Pp
+All function mentioned here are defined in the file
+.Pa mansearch.c .
+No functions except
+.Fn mansearch
+and
+.Fn sql_statement
+build any SQL code, and no functions except
+.Fn mansearch ,
+.Fn buildnames ,
+and
+.Fn buildoutput
+execute it.
+.Ss Finding matches
+The query is built using the following grammar:
+.Bd -literal -offset indent
+<query> ::= "SELECT * FROM mpages WHERE" <condition>
+<condition> ::= "(" <condition> ")" |
+ <condition> "OR" <condition> |
+ <condition> "AND" <condition> |
+ "desc" <operator> "?" |
+ "id IN (SELECT pageid FROM" <subquery> ")"
+<subquery> ::= "names WHERE name" <operator> "?" |
+ "keys WHERE key" <operator> "? AND bits & ?"
+<operator> ::= "MATCH" | "REGEXP"
+.Ed
+.Pp
+The MATCH and REGEXP operators are implemented by the functions
+.Fn sql_match
+and
+.Fn sql_regexp ,
+respectively.
+This is required because SQLite3 natively neither supports
+case-insensitive substring matching nor regular expression matching,
+but only string identity, shell globbing, and the weird home-brewed
+LIKE operator.
+.Pp
+Command line parsing is done by the function
+.Fn exprcomp
+building a singly linked list of
+.Vt expr
+structures, using the helper functions
+.Fn exprterm
+and
+.Fn exprspec .
+The resulting SQL statement is assembled by the function
+.Fn sql_statement
+and evaluated in the main loop of the
+.Fn mansearch
+function.
+.Ss Assembling the results
+The names, sections, and architectures of the manuals found
+are assembled into the
+.Va names
+field of the result structure by the function
+.Fn buildnames ,
+using the following query:
+.Pp
+.Dl "SELECT * FROM mlinks WHERE pageid=? ORDER BY sec, arch, name"
+.Pp
+If the
+.Fa outkey
+differs from
+.Qq Nd ,
+the requested output data is assembled into the
+.Va output
+field of the result structure by the function
+.Fn buildoutput ,
+using the following query:
+.Pp
+.Dl "SELECT * FROM keys WHERE pageid=? AND bits & ?"
+.Sh FILES
+.Bl -tag -width mandoc.db -compact
+.It Pa mandoc.db
+The manual page database.
+.El
+.Sh EXAMPLES
+The simplest invocation
+.Pp
+.Dl apropos keyword
+.Pp
+results in the following SQL query:
+.Bd -literal
+SELECT * FROM mpages WHERE (
+ id IN (SELECT pageid FROM names WHERE name MATCH 'keyword') OR
+ desc MATCH 'keyword'
+);
+.Ed
+.Pp
+A more complicated request like
+.Pp
+.Dl apropos -s 2 Nm,Xr=getuid
+.Pp
+results in:
+.Bd -literal
+SELECT * FROM mpages WHERE (
+ id IN (SELECT pageid FROM names WHERE name MATCH 'getuid') OR
+ id IN (SELECT pageid FROM keys WHERE key MATCH 'getuid' AND bits & 4)
+) AND id IN (SELECT pageid FROM keys WHERE key REGEXP '^2$' AND bits & 2);
+.Ed
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr mandoc.db 5 ,
+.Xr makewhatis 8
+.Sh HISTORY
+The
+.Fn mansearch
+subsystem first appeared in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+A module to search manual page databases was first written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+in 2011, at first using the Berkeley DB;
+he rewrote it for SQLite3 in 2012.
+The current version received major changes from
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/mansearch.c b/contrib/mdocml/mansearch.c
new file mode 100644
index 0000000..e1226b1
--- /dev/null
+++ b/contrib/mdocml/mansearch.c
@@ -0,0 +1,861 @@
+/* $Id: mansearch.c,v 1.42 2014/08/09 14:24:53 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/mman.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_OHASH
+#include <ohash.h>
+#else
+#include "compat_ohash.h"
+#endif
+#include <sqlite3.h>
+#ifndef SQLITE_DETERMINISTIC
+#define SQLITE_DETERMINISTIC 0
+#endif
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "manpath.h"
+#include "mansearch.h"
+
+extern int mansearch_keymax;
+extern const char *const mansearch_keynames[];
+
+#define SQL_BIND_TEXT(_db, _s, _i, _v) \
+ do { if (SQLITE_OK != sqlite3_bind_text \
+ ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
+ fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+ } while (0)
+#define SQL_BIND_INT64(_db, _s, _i, _v) \
+ do { if (SQLITE_OK != sqlite3_bind_int64 \
+ ((_s), (_i)++, (_v))) \
+ fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+ } while (0)
+#define SQL_BIND_BLOB(_db, _s, _i, _v) \
+ do { if (SQLITE_OK != sqlite3_bind_blob \
+ ((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
+ fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+ } while (0)
+
+struct expr {
+ regex_t regexp; /* compiled regexp, if applicable */
+ const char *substr; /* to search for, if applicable */
+ struct expr *next; /* next in sequence */
+ uint64_t bits; /* type-mask */
+ int equal; /* equality, not subsring match */
+ int open; /* opening parentheses before */
+ int and; /* logical AND before */
+ int close; /* closing parentheses after */
+};
+
+struct match {
+ uint64_t pageid; /* identifier in database */
+ char *desc; /* manual page description */
+ int form; /* 0 == catpage */
+};
+
+static void buildnames(struct manpage *, sqlite3 *,
+ sqlite3_stmt *, uint64_t,
+ const char *, int form);
+static char *buildoutput(sqlite3 *, sqlite3_stmt *,
+ uint64_t, uint64_t);
+static void *hash_alloc(size_t, void *);
+static void hash_free(void *, void *);
+static void *hash_calloc(size_t, size_t, void *);
+static struct expr *exprcomp(const struct mansearch *,
+ int, char *[]);
+static void exprfree(struct expr *);
+static struct expr *exprspec(struct expr *, uint64_t,
+ const char *, const char *);
+static struct expr *exprterm(const struct mansearch *, char *, int);
+static int manpage_compare(const void *, const void *);
+static void sql_append(char **sql, size_t *sz,
+ const char *newstr, int count);
+static void sql_match(sqlite3_context *context,
+ int argc, sqlite3_value **argv);
+static void sql_regexp(sqlite3_context *context,
+ int argc, sqlite3_value **argv);
+static char *sql_statement(const struct expr *);
+
+
+int
+mansearch_setup(int start)
+{
+ static void *pagecache;
+ int c;
+
+#define PC_PAGESIZE 1280
+#define PC_NUMPAGES 256
+
+ if (start) {
+ if (NULL != pagecache) {
+ fprintf(stderr, "pagecache already enabled\n");
+ return((int)MANDOCLEVEL_BADARG);
+ }
+
+ pagecache = mmap(NULL, PC_PAGESIZE * PC_NUMPAGES,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANON, -1, 0);
+
+ if (MAP_FAILED == pagecache) {
+ perror("mmap");
+ pagecache = NULL;
+ return((int)MANDOCLEVEL_SYSERR);
+ }
+
+ c = sqlite3_config(SQLITE_CONFIG_PAGECACHE,
+ pagecache, PC_PAGESIZE, PC_NUMPAGES);
+
+ if (SQLITE_OK == c)
+ return((int)MANDOCLEVEL_OK);
+
+ fprintf(stderr, "pagecache: %s\n", sqlite3_errstr(c));
+
+ } else if (NULL == pagecache) {
+ fprintf(stderr, "pagecache missing\n");
+ return((int)MANDOCLEVEL_BADARG);
+ }
+
+ if (-1 == munmap(pagecache, PC_PAGESIZE * PC_NUMPAGES)) {
+ perror("munmap");
+ pagecache = NULL;
+ return((int)MANDOCLEVEL_SYSERR);
+ }
+
+ pagecache = NULL;
+ return((int)MANDOCLEVEL_OK);
+}
+
+int
+mansearch(const struct mansearch *search,
+ const struct manpaths *paths,
+ int argc, char *argv[],
+ const char *outkey,
+ struct manpage **res, size_t *sz)
+{
+ int fd, rc, c, indexbit;
+ int64_t pageid;
+ uint64_t outbit, iterbit;
+ char buf[PATH_MAX];
+ char *sql;
+ struct manpage *mpage;
+ struct expr *e, *ep;
+ sqlite3 *db;
+ sqlite3_stmt *s, *s2;
+ struct match *mp;
+ struct ohash_info info;
+ struct ohash htab;
+ unsigned int idx;
+ size_t i, j, cur, maxres;
+
+ info.calloc = hash_calloc;
+ info.alloc = hash_alloc;
+ info.free = hash_free;
+ info.key_offset = offsetof(struct match, pageid);
+
+ *sz = cur = maxres = 0;
+ sql = NULL;
+ *res = NULL;
+ fd = -1;
+ e = NULL;
+ rc = 0;
+
+ if (0 == argc)
+ goto out;
+ if (NULL == (e = exprcomp(search, argc, argv)))
+ goto out;
+
+ outbit = 0;
+ if (NULL != outkey) {
+ for (indexbit = 0, iterbit = 1;
+ indexbit < mansearch_keymax;
+ indexbit++, iterbit <<= 1) {
+ if (0 == strcasecmp(outkey,
+ mansearch_keynames[indexbit])) {
+ outbit = iterbit;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Save a descriptor to the current working directory.
+ * Since pathnames in the "paths" variable might be relative,
+ * and we'll be chdir()ing into them, we need to keep a handle
+ * on our current directory from which to start the chdir().
+ */
+
+ if (NULL == getcwd(buf, PATH_MAX)) {
+ perror("getcwd");
+ goto out;
+ } else if (-1 == (fd = open(buf, O_RDONLY, 0))) {
+ perror(buf);
+ goto out;
+ }
+
+ sql = sql_statement(e);
+
+ /*
+ * Loop over the directories (containing databases) for us to
+ * search.
+ * Don't let missing/bad databases/directories phase us.
+ * In each, try to open the resident database and, if it opens,
+ * scan it for our match expression.
+ */
+
+ for (i = 0; i < paths->sz; i++) {
+ if (-1 == fchdir(fd)) {
+ perror(buf);
+ free(*res);
+ break;
+ } else if (-1 == chdir(paths->paths[i])) {
+ perror(paths->paths[i]);
+ continue;
+ }
+
+ c = sqlite3_open_v2(MANDOC_DB, &db,
+ SQLITE_OPEN_READONLY, NULL);
+
+ if (SQLITE_OK != c) {
+ perror(MANDOC_DB);
+ sqlite3_close(db);
+ continue;
+ }
+
+ /*
+ * Define the SQL functions for substring
+ * and regular expression matching.
+ */
+
+ c = sqlite3_create_function(db, "match", 2,
+ SQLITE_UTF8 | SQLITE_DETERMINISTIC,
+ NULL, sql_match, NULL, NULL);
+ assert(SQLITE_OK == c);
+ c = sqlite3_create_function(db, "regexp", 2,
+ SQLITE_UTF8 | SQLITE_DETERMINISTIC,
+ NULL, sql_regexp, NULL, NULL);
+ assert(SQLITE_OK == c);
+
+ j = 1;
+ c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
+ if (SQLITE_OK != c)
+ fprintf(stderr, "%s\n", sqlite3_errmsg(db));
+
+ for (ep = e; NULL != ep; ep = ep->next) {
+ if (NULL == ep->substr) {
+ SQL_BIND_BLOB(db, s, j, ep->regexp);
+ } else
+ SQL_BIND_TEXT(db, s, j, ep->substr);
+ if (0 == ((TYPE_Nd | TYPE_Nm) & ep->bits))
+ SQL_BIND_INT64(db, s, j, ep->bits);
+ }
+
+ memset(&htab, 0, sizeof(struct ohash));
+ ohash_init(&htab, 4, &info);
+
+ /*
+ * Hash each entry on its [unique] document identifier.
+ * This is a uint64_t.
+ * Instead of using a hash function, simply convert the
+ * uint64_t to a uint32_t, the hash value's type.
+ * This gives good performance and preserves the
+ * distribution of buckets in the table.
+ */
+ while (SQLITE_ROW == (c = sqlite3_step(s))) {
+ pageid = sqlite3_column_int64(s, 2);
+ idx = ohash_lookup_memory(&htab,
+ (char *)&pageid, sizeof(uint64_t),
+ (uint32_t)pageid);
+
+ if (NULL != ohash_find(&htab, idx))
+ continue;
+
+ mp = mandoc_calloc(1, sizeof(struct match));
+ mp->pageid = pageid;
+ mp->form = sqlite3_column_int(s, 1);
+ if (TYPE_Nd == outbit)
+ mp->desc = mandoc_strdup((const char *)
+ sqlite3_column_text(s, 0));
+ ohash_insert(&htab, idx, mp);
+ }
+
+ if (SQLITE_DONE != c)
+ fprintf(stderr, "%s\n", sqlite3_errmsg(db));
+
+ sqlite3_finalize(s);
+
+ c = sqlite3_prepare_v2(db,
+ "SELECT sec, arch, name, pageid FROM mlinks "
+ "WHERE pageid=? ORDER BY sec, arch, name",
+ -1, &s, NULL);
+ if (SQLITE_OK != c)
+ fprintf(stderr, "%s\n", sqlite3_errmsg(db));
+
+ c = sqlite3_prepare_v2(db,
+ "SELECT bits, key, pageid FROM keys "
+ "WHERE pageid=? AND bits & ?",
+ -1, &s2, NULL);
+ if (SQLITE_OK != c)
+ fprintf(stderr, "%s\n", sqlite3_errmsg(db));
+
+ for (mp = ohash_first(&htab, &idx);
+ NULL != mp;
+ mp = ohash_next(&htab, &idx)) {
+ if (cur + 1 > maxres) {
+ maxres += 1024;
+ *res = mandoc_reallocarray(*res,
+ maxres, sizeof(struct manpage));
+ }
+ mpage = *res + cur;
+ mpage->sec = 10;
+ mpage->form = mp->form;
+ buildnames(mpage, db, s, mp->pageid,
+ paths->paths[i], mp->form);
+ mpage->output = TYPE_Nd & outbit ?
+ mp->desc : outbit ?
+ buildoutput(db, s2, mp->pageid, outbit) : NULL;
+
+ free(mp);
+ cur++;
+ }
+
+ sqlite3_finalize(s);
+ sqlite3_finalize(s2);
+ sqlite3_close(db);
+ ohash_delete(&htab);
+ }
+ qsort(*res, cur, sizeof(struct manpage), manpage_compare);
+ rc = 1;
+out:
+ if (-1 != fd) {
+ if (-1 == fchdir(fd))
+ perror(buf);
+ close(fd);
+ }
+ exprfree(e);
+ free(sql);
+ *sz = cur;
+ return(rc);
+}
+
+static int
+manpage_compare(const void *vp1, const void *vp2)
+{
+ const struct manpage *mp1, *mp2;
+ int diff;
+
+ mp1 = vp1;
+ mp2 = vp2;
+ diff = mp1->sec - mp2->sec;
+ return(diff ? diff : strcasecmp(mp1->names, mp2->names));
+}
+
+static void
+buildnames(struct manpage *mpage, sqlite3 *db, sqlite3_stmt *s,
+ uint64_t pageid, const char *path, int form)
+{
+ char *newnames, *prevsec, *prevarch;
+ const char *oldnames, *sep1, *name, *sec, *sep2, *arch, *fsec;
+ size_t i;
+ int c;
+
+ mpage->file = NULL;
+ mpage->names = NULL;
+ prevsec = prevarch = NULL;
+ i = 1;
+ SQL_BIND_INT64(db, s, i, pageid);
+ while (SQLITE_ROW == (c = sqlite3_step(s))) {
+
+ /* Decide whether we already have some names. */
+
+ if (NULL == mpage->names) {
+ oldnames = "";
+ sep1 = "";
+ } else {
+ oldnames = mpage->names;
+ sep1 = ", ";
+ }
+
+ /* Fetch the next name. */
+
+ sec = (const char *)sqlite3_column_text(s, 0);
+ arch = (const char *)sqlite3_column_text(s, 1);
+ name = (const char *)sqlite3_column_text(s, 2);
+
+ /* Remember the first section found. */
+
+ if (9 < mpage->sec && '1' <= *sec && '9' >= *sec)
+ mpage->sec = (*sec - '1') + 1;
+
+ /* If the section changed, append the old one. */
+
+ if (NULL != prevsec &&
+ (strcmp(sec, prevsec) ||
+ strcmp(arch, prevarch))) {
+ sep2 = '\0' == *prevarch ? "" : "/";
+ mandoc_asprintf(&newnames, "%s(%s%s%s)",
+ oldnames, prevsec, sep2, prevarch);
+ free(mpage->names);
+ oldnames = mpage->names = newnames;
+ free(prevsec);
+ free(prevarch);
+ prevsec = prevarch = NULL;
+ }
+
+ /* Save the new section, to append it later. */
+
+ if (NULL == prevsec) {
+ prevsec = mandoc_strdup(sec);
+ prevarch = mandoc_strdup(arch);
+ }
+
+ /* Append the new name. */
+
+ mandoc_asprintf(&newnames, "%s%s%s",
+ oldnames, sep1, name);
+ free(mpage->names);
+ mpage->names = newnames;
+
+ /* Also save the first file name encountered. */
+
+ if (NULL != mpage->file)
+ continue;
+
+ if (form) {
+ sep1 = "man";
+ fsec = sec;
+ } else {
+ sep1 = "cat";
+ fsec = "0";
+ }
+ sep2 = '\0' == *arch ? "" : "/";
+ mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.%s",
+ path, sep1, sec, sep2, arch, name, fsec);
+ }
+ if (SQLITE_DONE != c)
+ fprintf(stderr, "%s\n", sqlite3_errmsg(db));
+ sqlite3_reset(s);
+
+ /* Append one final section to the names. */
+
+ if (NULL != prevsec) {
+ sep2 = '\0' == *prevarch ? "" : "/";
+ mandoc_asprintf(&newnames, "%s(%s%s%s)",
+ mpage->names, prevsec, sep2, prevarch);
+ free(mpage->names);
+ mpage->names = newnames;
+ free(prevsec);
+ free(prevarch);
+ }
+}
+
+static char *
+buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t pageid, uint64_t outbit)
+{
+ char *output, *newoutput;
+ const char *oldoutput, *sep1, *data;
+ size_t i;
+ int c;
+
+ output = NULL;
+ i = 1;
+ SQL_BIND_INT64(db, s, i, pageid);
+ SQL_BIND_INT64(db, s, i, outbit);
+ while (SQLITE_ROW == (c = sqlite3_step(s))) {
+ if (NULL == output) {
+ oldoutput = "";
+ sep1 = "";
+ } else {
+ oldoutput = output;
+ sep1 = " # ";
+ }
+ data = (const char *)sqlite3_column_text(s, 1);
+ mandoc_asprintf(&newoutput, "%s%s%s",
+ oldoutput, sep1, data);
+ free(output);
+ output = newoutput;
+ }
+ if (SQLITE_DONE != c)
+ fprintf(stderr, "%s\n", sqlite3_errmsg(db));
+ sqlite3_reset(s);
+ return(output);
+}
+
+/*
+ * Implement substring match as an application-defined SQL function.
+ * Using the SQL LIKE or GLOB operators instead would be a bad idea
+ * because that would require escaping metacharacters in the string
+ * being searched for.
+ */
+static void
+sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+
+ assert(2 == argc);
+ sqlite3_result_int(context, NULL != strcasestr(
+ (const char *)sqlite3_value_text(argv[1]),
+ (const char *)sqlite3_value_text(argv[0])));
+}
+
+/*
+ * Implement regular expression match
+ * as an application-defined SQL function.
+ */
+static void
+sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+
+ assert(2 == argc);
+ sqlite3_result_int(context, !regexec(
+ (regex_t *)sqlite3_value_blob(argv[0]),
+ (const char *)sqlite3_value_text(argv[1]),
+ 0, NULL, 0));
+}
+
+static void
+sql_append(char **sql, size_t *sz, const char *newstr, int count)
+{
+ size_t newsz;
+
+ newsz = 1 < count ? (size_t)count : strlen(newstr);
+ *sql = mandoc_realloc(*sql, *sz + newsz + 1);
+ if (1 < count)
+ memset(*sql + *sz, *newstr, (size_t)count);
+ else
+ memcpy(*sql + *sz, newstr, newsz);
+ *sz += newsz;
+ (*sql)[*sz] = '\0';
+}
+
+/*
+ * Prepare the search SQL statement.
+ */
+static char *
+sql_statement(const struct expr *e)
+{
+ char *sql;
+ size_t sz;
+ int needop;
+
+ sql = mandoc_strdup(
+ "SELECT desc, form, pageid FROM mpages WHERE ");
+ sz = strlen(sql);
+
+ for (needop = 0; NULL != e; e = e->next) {
+ if (e->and)
+ sql_append(&sql, &sz, " AND ", 1);
+ else if (needop)
+ sql_append(&sql, &sz, " OR ", 1);
+ if (e->open)
+ sql_append(&sql, &sz, "(", e->open);
+ sql_append(&sql, &sz,
+ TYPE_Nd & e->bits
+ ? (NULL == e->substr
+ ? "desc REGEXP ?"
+ : "desc MATCH ?")
+ : TYPE_Nm == e->bits
+ ? (NULL == e->substr
+ ? "pageid IN (SELECT pageid FROM names "
+ "WHERE name REGEXP ?)"
+ : e->equal
+ ? "pageid IN (SELECT pageid FROM names "
+ "WHERE name = ?)"
+ : "pageid IN (SELECT pageid FROM names "
+ "WHERE name MATCH ?)")
+ : (NULL == e->substr
+ ? "pageid IN (SELECT pageid FROM keys "
+ "WHERE key REGEXP ? AND bits & ?)"
+ : "pageid IN (SELECT pageid FROM keys "
+ "WHERE key MATCH ? AND bits & ?)"), 1);
+ if (e->close)
+ sql_append(&sql, &sz, ")", e->close);
+ needop = 1;
+ }
+
+ return(sql);
+}
+
+/*
+ * Compile a set of string tokens into an expression.
+ * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
+ * "(", "foo=bar", etc.).
+ */
+static struct expr *
+exprcomp(const struct mansearch *search, int argc, char *argv[])
+{
+ uint64_t mask;
+ int i, toopen, logic, igncase, toclose;
+ struct expr *first, *prev, *cur, *next;
+
+ first = cur = NULL;
+ logic = igncase = toclose = 0;
+ toopen = NULL != search->sec || NULL != search->arch;
+
+ for (i = 0; i < argc; i++) {
+ if (0 == strcmp("(", argv[i])) {
+ if (igncase)
+ goto fail;
+ toopen++;
+ toclose++;
+ continue;
+ } else if (0 == strcmp(")", argv[i])) {
+ if (toopen || logic || igncase || NULL == cur)
+ goto fail;
+ cur->close++;
+ if (0 > --toclose)
+ goto fail;
+ continue;
+ } else if (0 == strcmp("-a", argv[i])) {
+ if (toopen || logic || igncase || NULL == cur)
+ goto fail;
+ logic = 1;
+ continue;
+ } else if (0 == strcmp("-o", argv[i])) {
+ if (toopen || logic || igncase || NULL == cur)
+ goto fail;
+ logic = 2;
+ continue;
+ } else if (0 == strcmp("-i", argv[i])) {
+ if (igncase)
+ goto fail;
+ igncase = 1;
+ continue;
+ }
+ next = exprterm(search, argv[i], !igncase);
+ if (NULL == next)
+ goto fail;
+ if (NULL == first)
+ first = next;
+ else
+ cur->next = next;
+ prev = cur = next;
+
+ /*
+ * Searching for descriptions must be split out
+ * because they are stored in the mpages table,
+ * not in the keys table.
+ */
+
+ for (mask = TYPE_Nm; mask <= TYPE_Nd; mask <<= 1) {
+ if (mask & cur->bits && ~mask & cur->bits) {
+ next = mandoc_calloc(1,
+ sizeof(struct expr));
+ memcpy(next, cur, sizeof(struct expr));
+ prev->open = 1;
+ cur->bits = mask;
+ cur->next = next;
+ cur = next;
+ cur->bits &= ~mask;
+ }
+ }
+ prev->and = (1 == logic);
+ prev->open += toopen;
+ if (cur != prev)
+ cur->close = 1;
+
+ toopen = logic = igncase = 0;
+ }
+ if (toopen || logic || igncase || toclose)
+ goto fail;
+
+ if (NULL != search->sec || NULL != search->arch)
+ cur->close++;
+ if (NULL != search->arch)
+ cur = exprspec(cur, TYPE_arch, search->arch, "^(%s|any)$");
+ if (NULL != search->sec)
+ exprspec(cur, TYPE_sec, search->sec, "^%s$");
+
+ return(first);
+
+fail:
+ if (NULL != first)
+ exprfree(first);
+ return(NULL);
+}
+
+static struct expr *
+exprspec(struct expr *cur, uint64_t key, const char *value,
+ const char *format)
+{
+ char errbuf[BUFSIZ];
+ char *cp;
+ int irc;
+
+ mandoc_asprintf(&cp, format, value);
+ cur->next = mandoc_calloc(1, sizeof(struct expr));
+ cur = cur->next;
+ cur->and = 1;
+ cur->bits = key;
+ if (0 != (irc = regcomp(&cur->regexp, cp,
+ REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
+ regerror(irc, &cur->regexp, errbuf, sizeof(errbuf));
+ fprintf(stderr, "regcomp: %s\n", errbuf);
+ cur->substr = value;
+ }
+ free(cp);
+ return(cur);
+}
+
+static struct expr *
+exprterm(const struct mansearch *search, char *buf, int cs)
+{
+ char errbuf[BUFSIZ];
+ struct expr *e;
+ char *key, *val;
+ uint64_t iterbit;
+ int i, irc;
+
+ if ('\0' == *buf)
+ return(NULL);
+
+ e = mandoc_calloc(1, sizeof(struct expr));
+
+ if (MANSEARCH_MAN & search->flags) {
+ e->bits = search->deftype;
+ e->substr = buf;
+ e->equal = 1;
+ return(e);
+ }
+
+ /*
+ * Look for an '=' or '~' operator,
+ * unless forced to some fixed macro keys.
+ */
+
+ if (MANSEARCH_WHATIS & search->flags)
+ val = NULL;
+ else
+ val = strpbrk(buf, "=~");
+
+ if (NULL == val) {
+ e->bits = search->deftype;
+ e->substr = buf;
+
+ /*
+ * Found an operator.
+ * Regexp search is requested by !e->substr.
+ */
+
+ } else {
+ if (val == buf)
+ e->bits = search->deftype;
+ if ('=' == *val)
+ e->substr = val + 1;
+ *val++ = '\0';
+ if (NULL != strstr(buf, "arch"))
+ cs = 0;
+ }
+
+ /* Compile regular expressions. */
+
+ if (MANSEARCH_WHATIS & search->flags) {
+ e->substr = NULL;
+ mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", buf);
+ }
+
+ if (NULL == e->substr) {
+ irc = regcomp(&e->regexp, val,
+ REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
+ if (MANSEARCH_WHATIS & search->flags)
+ free(val);
+ if (irc) {
+ regerror(irc, &e->regexp, errbuf, sizeof(errbuf));
+ fprintf(stderr, "regcomp: %s\n", errbuf);
+ free(e);
+ return(NULL);
+ }
+ }
+
+ if (e->bits)
+ return(e);
+
+ /*
+ * Parse out all possible fields.
+ * If the field doesn't resolve, bail.
+ */
+
+ while (NULL != (key = strsep(&buf, ","))) {
+ if ('\0' == *key)
+ continue;
+ for (i = 0, iterbit = 1;
+ i < mansearch_keymax;
+ i++, iterbit <<= 1) {
+ if (0 == strcasecmp(key,
+ mansearch_keynames[i])) {
+ e->bits |= iterbit;
+ break;
+ }
+ }
+ if (i == mansearch_keymax) {
+ if (strcasecmp(key, "any")) {
+ free(e);
+ return(NULL);
+ }
+ e->bits |= ~0ULL;
+ }
+ }
+
+ return(e);
+}
+
+static void
+exprfree(struct expr *p)
+{
+ struct expr *pp;
+
+ while (NULL != p) {
+ pp = p->next;
+ free(p);
+ p = pp;
+ }
+}
+
+static void *
+hash_calloc(size_t nmemb, size_t sz, void *arg)
+{
+
+ return(mandoc_calloc(nmemb, sz));
+}
+
+static void *
+hash_alloc(size_t sz, void *arg)
+{
+
+ return(mandoc_malloc(sz));
+}
+
+static void
+hash_free(void *p, void *arg)
+{
+
+ free(p);
+}
diff --git a/contrib/mdocml/mansearch.h b/contrib/mdocml/mansearch.h
new file mode 100644
index 0000000..eff5e37
--- /dev/null
+++ b/contrib/mdocml/mansearch.h
@@ -0,0 +1,101 @@
+/* $Id: mansearch.h,v 1.15 2014/07/24 20:30:45 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef MANSEARCH_H
+#define MANSEARCH_H
+
+#define MANDOC_DB "mandoc.db"
+
+#define TYPE_arch 0x0000000000000001ULL
+#define TYPE_sec 0x0000000000000002ULL
+#define TYPE_Xr 0x0000000000000004ULL
+#define TYPE_Ar 0x0000000000000008ULL
+#define TYPE_Fa 0x0000000000000010ULL
+#define TYPE_Fl 0x0000000000000020ULL
+#define TYPE_Dv 0x0000000000000040ULL
+#define TYPE_Fn 0x0000000000000080ULL
+#define TYPE_Ic 0x0000000000000100ULL
+#define TYPE_Pa 0x0000000000000200ULL
+#define TYPE_Cm 0x0000000000000400ULL
+#define TYPE_Li 0x0000000000000800ULL
+#define TYPE_Em 0x0000000000001000ULL
+#define TYPE_Cd 0x0000000000002000ULL
+#define TYPE_Va 0x0000000000004000ULL
+#define TYPE_Ft 0x0000000000008000ULL
+#define TYPE_Tn 0x0000000000010000ULL
+#define TYPE_Er 0x0000000000020000ULL
+#define TYPE_Ev 0x0000000000040000ULL
+#define TYPE_Sy 0x0000000000080000ULL
+#define TYPE_Sh 0x0000000000100000ULL
+#define TYPE_In 0x0000000000200000ULL
+#define TYPE_Ss 0x0000000000400000ULL
+#define TYPE_Ox 0x0000000000800000ULL
+#define TYPE_An 0x0000000001000000ULL
+#define TYPE_Mt 0x0000000002000000ULL
+#define TYPE_St 0x0000000004000000ULL
+#define TYPE_Bx 0x0000000008000000ULL
+#define TYPE_At 0x0000000010000000ULL
+#define TYPE_Nx 0x0000000020000000ULL
+#define TYPE_Fx 0x0000000040000000ULL
+#define TYPE_Lk 0x0000000080000000ULL
+#define TYPE_Ms 0x0000000100000000ULL
+#define TYPE_Bsx 0x0000000200000000ULL
+#define TYPE_Dx 0x0000000400000000ULL
+#define TYPE_Rs 0x0000000800000000ULL
+#define TYPE_Vt 0x0000001000000000ULL
+#define TYPE_Lb 0x0000002000000000ULL
+#define TYPE_Nm 0x0000004000000000ULL
+#define TYPE_Nd 0x0000008000000000ULL
+
+#define NAME_SYN 0x0000004000000001ULL
+#define NAME_FILE 0x0000004000000002ULL
+#define NAME_TITLE 0x000000400000000cULL
+#define NAME_FIRST 0x0000004000000008ULL
+#define NAME_HEAD 0x0000004000000010ULL
+#define NAME_MASK 0x000000000000001fULL
+
+__BEGIN_DECLS
+
+struct manpage {
+ char *file; /* to be prefixed by manpath */
+ char *names; /* a list of names with sections */
+ char *output; /* user-defined additional output */
+ int sec; /* section number, 10 means invalid */
+ int form; /* 0 == catpage */
+};
+
+struct mansearch {
+ const char *arch; /* architecture/NULL */
+ const char *sec; /* mansection/NULL */
+ uint64_t deftype; /* type if no key */
+ int flags;
+#define MANSEARCH_WHATIS 0x01 /* whatis(1) mode: whole words, no keys */
+#define MANSEARCH_MAN 0x02 /* man(1) mode: string equality, no keys */
+};
+
+int mansearch_setup(int);
+int mansearch(const struct mansearch *cfg, /* options */
+ const struct manpaths *paths, /* manpaths */
+ int argc, /* size of argv */
+ char *argv[], /* search terms */
+ const char *outkey, /* name of additional output key */
+ struct manpage **res, /* results */
+ size_t *ressz); /* results returned */
+
+__END_DECLS
+
+#endif /*!MANSEARCH_H*/
diff --git a/contrib/mdocml/mansearch_const.c b/contrib/mdocml/mansearch_const.c
new file mode 100644
index 0000000..1533f92
--- /dev/null
+++ b/contrib/mdocml/mansearch_const.c
@@ -0,0 +1,35 @@
+/* $Id: mansearch_const.c,v 1.5 2014/08/09 14:05:21 schwarze Exp $ */
+/*
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include "manpath.h"
+#include "mansearch.h"
+
+const int mansearch_keymax = 40;
+
+const char *const mansearch_keynames[40] = {
+ "arch", "sec", "Xr", "Ar", "Fa", "Fl", "Dv", "Fn",
+ "Ic", "Pa", "Cm", "Li", "Em", "Cd", "Va", "Ft",
+ "Tn", "Er", "Ev", "Sy", "Sh", "In", "Ss", "Ox",
+ "An", "Mt", "St", "Bx", "At", "Nx", "Fx", "Lk",
+ "Ms", "Bsx", "Dx", "Rs", "Vt", "Lb", "Nm", "Nd"
+};
diff --git a/contrib/mdocml/mchars_alloc.3 b/contrib/mdocml/mchars_alloc.3
new file mode 100644
index 0000000..8c3f853
--- /dev/null
+++ b/contrib/mdocml/mchars_alloc.3
@@ -0,0 +1,224 @@
+.\" $Id: mchars_alloc.3,v 1.1 2014/08/05 05:48:56 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MCHARS_ALLOC 3
+.Os
+.Sh NAME
+.Nm mchars_alloc ,
+.Nm mchars_free ,
+.Nm mchars_num2char ,
+.Nm mchars_num2uc ,
+.Nm mchars_spec2cp ,
+.Nm mchars_spec2str
+.Nd character table for mandoc
+.Sh LIBRARY
+.Lb libmandoc
+.Sh SYNOPSIS
+.In sys/types.h
+.In mandoc.h
+.Ft "struct mchars *"
+.Fn mchars_alloc "void"
+.Ft void
+.Fo mchars_free
+.Fa "struct mchars *table"
+.Fc
+.Ft char
+.Fo mchars_num2char
+.Fa "const char *decimal"
+.Fa "size_t sz"
+.Fc
+.Ft int
+.Fo mchars_num2uc
+.Fa "const char *hexadecimal"
+.Fa "size_t sz"
+.Fc
+.Ft int
+.Fo mchars_spec2cp
+.Fa "const struct mchars *table"
+.Fa "const char *name"
+.Fa "size_t sz"
+.Fc
+.Ft "const char *"
+.Fo mchars_spec2str
+.Fa "const struct mchars *table"
+.Fa "const char *name"
+.Fa "size_t sz"
+.Fa "size_t *rsz"
+.Fc
+.Sh DESCRIPTION
+These functions translate Unicode character numbers and
+.Xr roff 7
+character names into glyphs.
+See
+.Xr mandoc_char 7
+for a list of
+.Xr roff 7
+special characters.
+These functions are intended for external use by programs formatting
+.Xr mdoc 7
+and
+.Xr man 7
+pages for output, for example the
+.Xr mandoc 1
+output formatter modules and
+.Xr makewhatis 8 .
+The
+.Fa decimal ,
+.Fa hexadecimal ,
+.Fa name ,
+and
+.Fa size
+input arguments are usually obtained from the
+.Xr mandoc_escape 3
+parser function.
+.Pp
+The function
+.Fn mchars_num2char
+converts a
+.Fa decimal
+string representation of a character number consisting of
+.Fa sz
+digits into a printable ASCII character.
+If the input string is non-numeric or does not represent a printable
+ASCII character, the NUL character
+.Pq Sq \e0
+is returned.
+For example, the
+.Xr mandoc 1
+.Fl Tascii ,
+.Fl Tutf8 ,
+and
+.Fl Thtml
+output modules use this function to render
+.Xr roff 7
+.Ic \eN
+escape sequences.
+.Pp
+The function
+.Fn mchars_num2uc
+converts a
+.Fa hexadecimal
+string representation of a Unicode codepoint consisting of
+.Fa sz
+digits into an integer representation.
+If the input string is non-numeric or represents an ASCII character,
+the NUL character
+.Pq Sq \e0
+is returned.
+For example, the
+.Xr mandoc 1
+.Fl Tutf8
+and
+.Fl Thtml
+output modules use this function to render
+.Xr roff 7
+.Ic \e[u Ns Ar XXXX Ns Ic \&]
+and
+.Ic \eC\(aqu Ns Ar XXXX Ns Ic \(aq
+escape sequences.
+.Pp
+The function
+.Fn mchars_alloc
+allocates an opaque
+.Vt "struct mchars *"
+table object for subsequent use by the following two lookup functions.
+When no longer needed, this object can be destroyed with
+.Fn mchars_free .
+.Pp
+The function
+.Fn mchars_spec2cp
+looks up a
+.Xr roff 7
+special character
+.Fa name
+consisting of
+.Fa sz
+characters in the
+.Fa table
+and returns the corresponding Unicode codepoint.
+If the
+.Ar name
+is not recognized, \-1 is returned.
+For example, the
+.Xr mandoc 1
+.Fl Tutf8
+and
+.Fl Thtml
+output modules use this function to render
+.Xr roff 7
+.Ic \e[ Ns Ar name Ns Ic \&]
+and
+.Ic \eC\(aq Ns Ar name Ns Ic \(aq
+escape sequences.
+.Pp
+The function
+.Fn mchars_spec2str
+looks up a
+.Xr roff 7
+special character
+.Fa name
+consisting of
+.Fa sz
+characters in the
+.Fa table
+and returns an ASCII string representation.
+The length of the representation is returned in
+.Fa rsz .
+In many cases, the meaning of such ASCII representations
+is not quite obvious, so using
+.Xr roff 7
+special characters in documents intended for ASCII rendering
+is usually a bad idea.
+If the
+.Ar name
+is not recognized,
+.Dv NULL
+is returned.
+For example,
+.Xr makewhatis 8
+and the
+.Xr mandoc 1
+.Fl Tascii
+output module use this function to render
+.Xr roff 7
+.Ic \e[ Ns Ar name Ns Ic \&]
+and
+.Ic \eC\(aq Ns Ar name Ns Ic \(aq
+escape sequences.
+.Sh FILES
+These funtions are implemented in the file
+.Pa chars.c .
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr mandoc_escape 3 ,
+.Xr mandoc_char 7 ,
+.Xr roff 7
+.Sh HISTORY
+These functions and their predecessors have been available since the
+following mandoc versions:
+.Bl -column "mchars_num2char()" "1.11.3" "chars_num2char()" "1.10.10"
+.It Sy function Ta since Ta Sy predecessor Ta since
+.It Fn mchars_alloc Ta 1.11.3 Ta Fn ascii2htab Ta 1.5.3
+.It Fn mchars_free Ta 1.11.2 Ta Fn asciifree Ta 1.6.0
+.It Fn mchars_num2char Ta 1.11.2 Ta Fn chars_num2char Ta 1.10.10
+.It Fn mchars_num2uc Ta 1.11.3 Ta \(em Ta \(em
+.It Fn mchars_spec2cp Ta 1.11.2 Ta Fn chars_spec2cp Ta 1.10.5
+.It Fn mchars_spec2str Ta 1.11.2 Ta Fn a2ascii Ta 1.5.3
+.El
+.Sh AUTHORS
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
diff --git a/contrib/mdocml/mdoc.7 b/contrib/mdocml/mdoc.7
new file mode 100644
index 0000000..a8c60d5
--- /dev/null
+++ b/contrib/mdocml/mdoc.7
@@ -0,0 +1,3321 @@
+.\" $Id: mdoc.7,v 1.234 2014/08/08 16:38:06 schwarze Exp $
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2010, 2011, 2013 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 8 2014 $
+.Dt MDOC 7
+.Os
+.Sh NAME
+.Nm mdoc
+.Nd semantic markup language for formatting manual pages
+.Sh DESCRIPTION
+The
+.Nm mdoc
+language supports authoring of manual pages for the
+.Xr man 1
+utility by allowing semantic annotations of words, phrases,
+page sections and complete manual pages.
+Such annotations are used by formatting tools to achieve a uniform
+presentation across all manuals written in
+.Nm ,
+and to support hyperlinking if supported by the output medium.
+.Pp
+This reference document describes the structure of manual pages
+and the syntax and usage of the
+.Nm
+language.
+The reference implementation of a parsing and formatting tool is
+.Xr mandoc 1 ;
+the
+.Sx COMPATIBILITY
+section describes compatibility with other implementations.
+.Pp
+In an
+.Nm
+document, lines beginning with the control character
+.Sq \&.
+are called
+.Dq macro lines .
+The first word is the macro name.
+It consists of two or three letters.
+Most macro names begin with a capital letter.
+For a list of available macros, see
+.Sx MACRO OVERVIEW .
+The words following the macro name are arguments to the macro, optionally
+including the names of other, callable macros; see
+.Sx MACRO SYNTAX
+for details.
+.Pp
+Lines not beginning with the control character are called
+.Dq text lines .
+They provide free-form text to be printed; the formatting of the text
+depends on the respective processing context:
+.Bd -literal -offset indent
+\&.Sh Macro lines change control state.
+Text lines are interpreted within the current state.
+.Ed
+.Pp
+Many aspects of the basic syntax of the
+.Nm
+language are based on the
+.Xr roff 7
+language; see the
+.Em LANGUAGE SYNTAX
+and
+.Em MACRO SYNTAX
+sections in the
+.Xr roff 7
+manual for details, in particular regarding
+comments, escape sequences, whitespace, and quoting.
+However, using
+.Xr roff 7
+requests in
+.Nm
+documents is discouraged;
+.Xr mandoc 1
+supports some of them merely for backward compatibility.
+.Sh MANUAL STRUCTURE
+A well-formed
+.Nm
+document consists of a document prologue followed by one or more
+sections.
+.Pp
+The prologue, which consists of the
+.Sx \&Dd ,
+.Sx \&Dt ,
+and
+.Sx \&Os
+macros in that order, is required for every document.
+.Pp
+The first section (sections are denoted by
+.Sx \&Sh )
+must be the NAME section, consisting of at least one
+.Sx \&Nm
+followed by
+.Sx \&Nd .
+.Pp
+Following that, convention dictates specifying at least the
+.Em SYNOPSIS
+and
+.Em DESCRIPTION
+sections, although this varies between manual sections.
+.Pp
+The following is a well-formed skeleton
+.Nm
+file for a utility
+.Qq progname :
+.Bd -literal -offset indent
+\&.Dd $\&Mdocdate$
+\&.Dt PROGNAME section
+\&.Os
+\&.Sh NAME
+\&.Nm progname
+\&.Nd one line about what it does
+\&.\e\(dq .Sh LIBRARY
+\&.\e\(dq For sections 2, 3, and 9 only.
+\&.\e\(dq Not used in OpenBSD.
+\&.Sh SYNOPSIS
+\&.Nm progname
+\&.Op Fl options
+\&.Ar
+\&.Sh DESCRIPTION
+The
+\&.Nm
+utility processes files ...
+\&.\e\(dq .Sh CONTEXT
+\&.\e\(dq For section 9 functions only.
+\&.\e\(dq .Sh IMPLEMENTATION NOTES
+\&.\e\(dq Not used in OpenBSD.
+\&.\e\(dq .Sh RETURN VALUES
+\&.\e\(dq For sections 2, 3, and 9 function return values only.
+\&.\e\(dq .Sh ENVIRONMENT
+\&.\e\(dq For sections 1, 6, 7, and 8 only.
+\&.\e\(dq .Sh FILES
+\&.\e\(dq .Sh EXIT STATUS
+\&.\e\(dq For sections 1, 6, and 8 only.
+\&.\e\(dq .Sh EXAMPLES
+\&.\e\(dq .Sh DIAGNOSTICS
+\&.\e\(dq For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only.
+\&.\e\(dq .Sh ERRORS
+\&.\e\(dq For sections 2, 3, 4, and 9 errno settings only.
+\&.\e\(dq .Sh SEE ALSO
+\&.\e\(dq .Xr foobar 1
+\&.\e\(dq .Sh STANDARDS
+\&.\e\(dq .Sh HISTORY
+\&.\e\(dq .Sh AUTHORS
+\&.\e\(dq .Sh CAVEATS
+\&.\e\(dq .Sh BUGS
+\&.\e\(dq .Sh SECURITY CONSIDERATIONS
+\&.\e\(dq Not used in OpenBSD.
+.Ed
+.Pp
+The sections in an
+.Nm
+document are conventionally ordered as they appear above.
+Sections should be composed as follows:
+.Bl -ohang -offset Ds
+.It Em NAME
+The name(s) and a one line description of the documented material.
+The syntax for this as follows:
+.Bd -literal -offset indent
+\&.Nm name0 ,
+\&.Nm name1 ,
+\&.Nm name2
+\&.Nd a one line description
+.Ed
+.Pp
+Multiple
+.Sq \&Nm
+names should be separated by commas.
+.Pp
+The
+.Sx \&Nm
+macro(s) must precede the
+.Sx \&Nd
+macro.
+.Pp
+See
+.Sx \&Nm
+and
+.Sx \&Nd .
+.It Em LIBRARY
+The name of the library containing the documented material, which is
+assumed to be a function in a section 2, 3, or 9 manual.
+The syntax for this is as follows:
+.Bd -literal -offset indent
+\&.Lb libarm
+.Ed
+.Pp
+See
+.Sx \&Lb .
+.It Em SYNOPSIS
+Documents the utility invocation syntax, function call syntax, or device
+configuration.
+.Pp
+For the first, utilities (sections 1, 6, and 8), this is
+generally structured as follows:
+.Bd -literal -offset indent
+\&.Nm bar
+\&.Op Fl v
+\&.Op Fl o Ar file
+\&.Op Ar
+\&.Nm foo
+\&.Op Fl v
+\&.Op Fl o Ar file
+\&.Op Ar
+.Ed
+.Pp
+Commands should be ordered alphabetically.
+.Pp
+For the second, function calls (sections 2, 3, 9):
+.Bd -literal -offset indent
+\&.In header.h
+\&.Vt extern const char *global;
+\&.Ft "char *"
+\&.Fn foo "const char *src"
+\&.Ft "char *"
+\&.Fn bar "const char *src"
+.Ed
+.Pp
+Ordering of
+.Sx \&In ,
+.Sx \&Vt ,
+.Sx \&Fn ,
+and
+.Sx \&Fo
+macros should follow C header-file conventions.
+.Pp
+And for the third, configurations (section 4):
+.Bd -literal -offset indent
+\&.Cd \(dqit* at isa? port 0x2e\(dq
+\&.Cd \(dqit* at isa? port 0x4e\(dq
+.Ed
+.Pp
+Manuals not in these sections generally don't need a
+.Em SYNOPSIS .
+.Pp
+Some macros are displayed differently in the
+.Em SYNOPSIS
+section, particularly
+.Sx \&Nm ,
+.Sx \&Cd ,
+.Sx \&Fd ,
+.Sx \&Fn ,
+.Sx \&Fo ,
+.Sx \&In ,
+.Sx \&Vt ,
+and
+.Sx \&Ft .
+All of these macros are output on their own line.
+If two such dissimilar macros are pairwise invoked (except for
+.Sx \&Ft
+before
+.Sx \&Fo
+or
+.Sx \&Fn ) ,
+they are separated by a vertical space, unless in the case of
+.Sx \&Fo ,
+.Sx \&Fn ,
+and
+.Sx \&Ft ,
+which are always separated by vertical space.
+.Pp
+When text and macros following an
+.Sx \&Nm
+macro starting an input line span multiple output lines,
+all output lines but the first will be indented to align
+with the text immediately following the
+.Sx \&Nm
+macro, up to the next
+.Sx \&Nm ,
+.Sx \&Sh ,
+or
+.Sx \&Ss
+macro or the end of an enclosing block, whichever comes first.
+.It Em DESCRIPTION
+This begins with an expansion of the brief, one line description in
+.Em NAME :
+.Bd -literal -offset indent
+The
+\&.Nm
+utility does this, that, and the other.
+.Ed
+.Pp
+It usually follows with a breakdown of the options (if documenting a
+command), such as:
+.Bd -literal -offset indent
+The arguments are as follows:
+\&.Bl \-tag \-width Ds
+\&.It Fl v
+Print verbose information.
+\&.El
+.Ed
+.Pp
+Manuals not documenting a command won't include the above fragment.
+.Pp
+Since the
+.Em DESCRIPTION
+section usually contains most of the text of a manual, longer manuals
+often use the
+.Sx \&Ss
+macro to form subsections.
+In very long manuals, the
+.Em DESCRIPTION
+may be split into multiple sections, each started by an
+.Sx \&Sh
+macro followed by a non-standard section name, and each having
+several subsections, like in the present
+.Nm
+manual.
+.It Em CONTEXT
+This section lists the contexts in which functions can be called in section 9.
+The contexts are autoconf, process, or interrupt.
+.It Em IMPLEMENTATION NOTES
+Implementation-specific notes should be kept here.
+This is useful when implementing standard functions that may have side
+effects or notable algorithmic implications.
+.It Em RETURN VALUES
+This section documents the
+return values of functions in sections 2, 3, and 9.
+.Pp
+See
+.Sx \&Rv .
+.It Em ENVIRONMENT
+Lists the environment variables used by the utility,
+and explains the syntax and semantics of their values.
+The
+.Xr environ 7
+manual provides examples of typical content and formatting.
+.Pp
+See
+.Sx \&Ev .
+.It Em FILES
+Documents files used.
+It's helpful to document both the file name and a short description of how
+the file is used (created, modified, etc.).
+.Pp
+See
+.Sx \&Pa .
+.It Em EXIT STATUS
+This section documents the
+command exit status for section 1, 6, and 8 utilities.
+Historically, this information was described in
+.Em DIAGNOSTICS ,
+a practise that is now discouraged.
+.Pp
+See
+.Sx \&Ex .
+.It Em EXAMPLES
+Example usages.
+This often contains snippets of well-formed, well-tested invocations.
+Make sure that examples work properly!
+.It Em DIAGNOSTICS
+Documents error messages.
+In section 4 and 9 manuals, these are usually messages printed by the
+kernel to the console and to the kernel log.
+In section 1, 6, 7, and 8, these are usually messages printed by
+userland programs to the standard error output.
+.Pp
+Historically, this section was used in place of
+.Em EXIT STATUS
+for manuals in sections 1, 6, and 8; however, this practise is
+discouraged.
+.Pp
+See
+.Sx \&Bl
+.Fl diag .
+.It Em ERRORS
+Documents
+.Xr errno 2
+settings in sections 2, 3, 4, and 9.
+.Pp
+See
+.Sx \&Er .
+.It Em SEE ALSO
+References other manuals with related topics.
+This section should exist for most manuals.
+Cross-references should conventionally be ordered first by section, then
+alphabetically.
+.Pp
+References to other documentation concerning the topic of the manual page,
+for example authoritative books or journal articles, may also be
+provided in this section.
+.Pp
+See
+.Sx \&Rs
+and
+.Sx \&Xr .
+.It Em STANDARDS
+References any standards implemented or used.
+If not adhering to any standards, the
+.Em HISTORY
+section should be used instead.
+.Pp
+See
+.Sx \&St .
+.It Em HISTORY
+A brief history of the subject, including where it was first implemented,
+and when it was ported to or reimplemented for the operating system at hand.
+.It Em AUTHORS
+Credits to the person or persons who wrote the code and/or documentation.
+Authors should generally be noted by both name and email address.
+.Pp
+See
+.Sx \&An .
+.It Em CAVEATS
+Common misuses and misunderstandings should be explained
+in this section.
+.It Em BUGS
+Known bugs, limitations, and work-arounds should be described
+in this section.
+.It Em SECURITY CONSIDERATIONS
+Documents any security precautions that operators should consider.
+.El
+.Sh MACRO OVERVIEW
+This overview is sorted such that macros of similar purpose are listed
+together, to help find the best macro for any given purpose.
+Deprecated macros are not included in the overview, but can be found below
+in the alphabetical
+.Sx MACRO REFERENCE .
+.Ss Document preamble and NAME section macros
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Dd Ta document date: Cm $\&Mdocdate$ | Ar month day , year
+.It Sx \&Dt Ta document title: Ar TITLE section Op Ar volume | arch
+.It Sx \&Os Ta operating system version: Op Ar system Op Ar version
+.It Sx \&Nm Ta document name (one argument)
+.It Sx \&Nd Ta document description (one line)
+.El
+.Ss Sections and cross references
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Sh Ta section header (one line)
+.It Sx \&Ss Ta subsection header (one line)
+.It Sx \&Sx Ta internal cross reference to a section or subsection
+.It Sx \&Xr Ta cross reference to another manual page: Ar name section
+.It Sx \&Pp , \&Lp Ta start a text paragraph (no arguments)
+.El
+.Ss Displays and lists
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Bd , \&Ed Ta display block:
+.Fl Ar type
+.Op Fl offset Ar width
+.Op Fl compact
+.It Sx \&D1 Ta indented display (one line)
+.It Sx \&Dl Ta indented literal display (one line)
+.It Sx \&Bl , \&El Ta list block:
+.Fl Ar type
+.Op Fl width Ar val
+.Op Fl offset Ar val
+.Op Fl compact
+.It Sx \&It Ta list item (syntax depends on Fl Ar type )
+.It Sx \&Ta Ta table cell separator in Sx \&Bl Fl column No lists
+.It Sx \&Rs , \&%* , \&Re Ta bibliographic block (references)
+.El
+.Ss Spacing control
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Pf Ta prefix, no following horizontal space (one argument)
+.It Sx \&Ns Ta roman font, no preceding horizontal space (no arguments)
+.It Sx \&Ap Ta apostrophe without surrounding whitespace (no arguments)
+.It Sx \&Sm Ta switch horizontal spacing mode: Op Cm on | off
+.It Sx \&Bk , \&Ek Ta keep block: Fl words
+.It Sx \&br Ta force output line break in text mode (no arguments)
+.It Sx \&sp Ta force vertical space: Op Ar height
+.El
+.Ss Semantic markup for command line utilities:
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Nm Ta start a SYNOPSIS block with the name of a utility
+.It Sx \&Fl Ta command line options (flags) (>=0 arguments)
+.It Sx \&Cm Ta command modifier (>0 arguments)
+.It Sx \&Ar Ta command arguments (>=0 arguments)
+.It Sx \&Op , \&Oo , \&Oc Ta optional syntax elements (enclosure)
+.It Sx \&Ic Ta internal or interactive command (>0 arguments)
+.It Sx \&Ev Ta environmental variable (>0 arguments)
+.It Sx \&Pa Ta file system path (>=0 arguments)
+.El
+.Ss Semantic markup for function libraries:
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Lb Ta function library (one argument)
+.It Sx \&In Ta include file (one argument)
+.It Sx \&Fd Ta other preprocessor directive (>0 arguments)
+.It Sx \&Ft Ta function type (>0 arguments)
+.It Sx \&Fo , \&Fc Ta function block: Ar funcname
+.It Sx \&Fn Ta function name:
+.Op Ar functype
+.Ar funcname
+.Oo
+.Op Ar argtype
+.Ar argname
+.Oc
+.It Sx \&Fa Ta function argument (>0 arguments)
+.It Sx \&Vt Ta variable type (>0 arguments)
+.It Sx \&Va Ta variable name (>0 arguments)
+.It Sx \&Dv Ta defined variable or preprocessor constant (>0 arguments)
+.It Sx \&Er Ta error constant (>0 arguments)
+.It Sx \&Ev Ta environmental variable (>0 arguments)
+.El
+.Ss Various semantic markup:
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&An Ta author name (>0 arguments)
+.It Sx \&Lk Ta hyperlink: Ar uri Op Ar name
+.It Sx \&Mt Ta Do mailto Dc hyperlink: Ar address
+.It Sx \&Cd Ta kernel configuration declaration (>0 arguments)
+.It Sx \&Ad Ta memory address (>0 arguments)
+.It Sx \&Ms Ta mathematical symbol (>0 arguments)
+.El
+.Ss Physical markup
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Em Ta italic font or underline (emphasis) (>0 arguments)
+.It Sx \&Sy Ta boldface font (symbolic) (>0 arguments)
+.It Sx \&Li Ta typewriter font (literal) (>0 arguments)
+.It Sx \&No Ta return to roman font (normal) (no arguments)
+.It Sx \&Bf , \&Ef Ta font block:
+.Op Fl Ar type | Cm \&Em | \&Li | \&Sy
+.El
+.Ss Physical enclosures
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Dq , \&Do , \&Dc Ta enclose in typographic double quotes: Dq text
+.It Sx \&Qq , \&Qo , \&Qc Ta enclose in typewriter double quotes: Qq text
+.It Sx \&Sq , \&So , \&Sc Ta enclose in single quotes: Sq text
+.It Sx \&Ql Ta single-quoted literal text: Ql text
+.It Sx \&Pq , \&Po , \&Pc Ta enclose in parentheses: Pq text
+.It Sx \&Bq , \&Bo , \&Bc Ta enclose in square brackets: Bq text
+.It Sx \&Brq , \&Bro , \&Brc Ta enclose in curly braces: Brq text
+.It Sx \&Aq , \&Ao , \&Ac Ta enclose in angle brackets: Aq text
+.It Sx \&Eo , \&Ec Ta generic enclosure
+.El
+.Ss Text production
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Ex Fl std Ta standard command exit values: Op Ar utility ...
+.It Sx \&Rv Fl std Ta standard function return values: Op Ar function ...
+.It Sx \&St Ta reference to a standards document (one argument)
+.It Sx \&At Ta At
+.It Sx \&Bx Ta Bx
+.It Sx \&Bsx Ta Bsx
+.It Sx \&Nx Ta Nx
+.It Sx \&Fx Ta Fx
+.It Sx \&Ox Ta Ox
+.It Sx \&Dx Ta Dx
+.El
+.Sh MACRO REFERENCE
+This section is a canonical reference of all macros, arranged
+alphabetically.
+For the scoping of individual macros, see
+.Sx MACRO SYNTAX .
+.Ss \&%A
+Author name of an
+.Sx \&Rs
+block.
+Multiple authors should each be accorded their own
+.Sx \%%A
+line.
+Author names should be ordered with full or abbreviated forename(s)
+first, then full surname.
+.Ss \&%B
+Book title of an
+.Sx \&Rs
+block.
+This macro may also be used in a non-bibliographic context when
+referring to book titles.
+.Ss \&%C
+Publication city or location of an
+.Sx \&Rs
+block.
+.Ss \&%D
+Publication date of an
+.Sx \&Rs
+block.
+Recommended formats of arguments are
+.Ar month day , year
+or just
+.Ar year .
+.Ss \&%I
+Publisher or issuer name of an
+.Sx \&Rs
+block.
+.Ss \&%J
+Journal name of an
+.Sx \&Rs
+block.
+.Ss \&%N
+Issue number (usually for journals) of an
+.Sx \&Rs
+block.
+.Ss \&%O
+Optional information of an
+.Sx \&Rs
+block.
+.Ss \&%P
+Book or journal page number of an
+.Sx \&Rs
+block.
+.Ss \&%Q
+Institutional author (school, government, etc.) of an
+.Sx \&Rs
+block.
+Multiple institutional authors should each be accorded their own
+.Sx \&%Q
+line.
+.Ss \&%R
+Technical report name of an
+.Sx \&Rs
+block.
+.Ss \&%T
+Article title of an
+.Sx \&Rs
+block.
+This macro may also be used in a non-bibliographical context when
+referring to article titles.
+.Ss \&%U
+URI of reference document.
+.Ss \&%V
+Volume number of an
+.Sx \&Rs
+block.
+.Ss \&Ac
+Close an
+.Sx \&Ao
+block.
+Does not have any tail arguments.
+.Ss \&Ad
+Memory address.
+Do not use this for postal addresses.
+.Pp
+Examples:
+.Dl \&.Ad [0,$]
+.Dl \&.Ad 0x00000000
+.Ss \&An
+Author name.
+Can be used both for the authors of the program, function, or driver
+documented in the manual, or for the authors of the manual itself.
+Requires either the name of an author or one of the following arguments:
+.Pp
+.Bl -tag -width "-nosplitX" -offset indent -compact
+.It Fl split
+Start a new output line before each subsequent invocation of
+.Sx \&An .
+.It Fl nosplit
+The opposite of
+.Fl split .
+.El
+.Pp
+The default is
+.Fl nosplit .
+The effect of selecting either of the
+.Fl split
+modes ends at the beginning of the
+.Em AUTHORS
+section.
+In the
+.Em AUTHORS
+section, the default is
+.Fl nosplit
+for the first author listing and
+.Fl split
+for all other author listings.
+.Pp
+Examples:
+.Dl \&.An -nosplit
+.Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv
+.Ss \&Ao
+Begin a block enclosed by angle brackets.
+Does not have any head arguments.
+.Pp
+Examples:
+.Dl \&.Fl -key= \&Ns \&Ao \&Ar val \&Ac
+.Pp
+See also
+.Sx \&Aq .
+.Ss \&Ap
+Inserts an apostrophe without any surrounding whitespace.
+This is generally used as a grammatical device when referring to the verb
+form of a function.
+.Pp
+Examples:
+.Dl \&.Fn execve \&Ap d
+.Ss \&Aq
+Encloses its arguments in angle brackets.
+.Pp
+Examples:
+.Dl \&.Fl -key= \&Ns \&Aq \&Ar val
+.Pp
+.Em Remarks :
+this macro is often abused for rendering URIs, which should instead use
+.Sx \&Lk
+or
+.Sx \&Mt ,
+or to note pre-processor
+.Dq Li #include
+statements, which should use
+.Sx \&In .
+.Pp
+See also
+.Sx \&Ao .
+.Ss \&Ar
+Command arguments.
+If an argument is not provided, the string
+.Dq file ...\&
+is used as a default.
+.Pp
+Examples:
+.Dl ".Fl o Ar file"
+.Dl ".Ar"
+.Dl ".Ar arg1 , arg2 ."
+.Pp
+The arguments to the
+.Sx \&Ar
+macro are names and placeholders for command arguments;
+for fixed strings to be passed verbatim as arguments, use
+.Sx \&Fl
+or
+.Sx \&Cm .
+.Ss \&At
+Formats an
+.At
+version.
+Accepts one optional argument:
+.Pp
+.Bl -tag -width "v[1-7] | 32vX" -offset indent -compact
+.It Cm v[1-7] | 32v
+A version of
+.At .
+.It Cm III
+.At III .
+.It Cm V[.[1-4]]?
+A version of
+.At V .
+.El
+.Pp
+Note that these arguments do not begin with a hyphen.
+.Pp
+Examples:
+.Dl \&.At
+.Dl \&.At III
+.Dl \&.At V.1
+.Pp
+See also
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+and
+.Sx \&Ox .
+.Ss \&Bc
+Close a
+.Sx \&Bo
+block.
+Does not have any tail arguments.
+.Ss \&Bd
+Begin a display block.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Bd
+.Fl Ns Ar type
+.Op Fl offset Ar width
+.Op Fl compact
+.Ed
+.Pp
+Display blocks are used to select a different indentation and
+justification than the one used by the surrounding text.
+They may contain both macro lines and text lines.
+By default, a display block is preceded by a vertical space.
+.Pp
+The
+.Ar type
+must be one of the following:
+.Bl -tag -width 13n -offset indent
+.It Fl centered
+Produce one output line from each input line, and centre-justify each line.
+Using this display type is not recommended; many
+.Nm
+implementations render it poorly.
+.It Fl filled
+Change the positions of line breaks to fill each line, and left- and
+right-justify the resulting block.
+.It Fl literal
+Produce one output line from each input line,
+and do not justify the block at all.
+Preserve white space as it appears in the input.
+Always use a constant-width font.
+Use this for displaying source code.
+.It Fl ragged
+Change the positions of line breaks to fill each line, and left-justify
+the resulting block.
+.It Fl unfilled
+The same as
+.Fl literal ,
+but using the same font as for normal text, which is a variable width font
+if supported by the output device.
+.El
+.Pp
+The
+.Ar type
+must be provided first.
+Additional arguments may follow:
+.Bl -tag -width 13n -offset indent
+.It Fl offset Ar width
+Indent the display by the
+.Ar width ,
+which may be one of the following:
+.Bl -item
+.It
+One of the pre-defined strings
+.Cm indent ,
+the width of a standard indentation (six constant width characters);
+.Cm indent-two ,
+twice
+.Cm indent ;
+.Cm left ,
+which has no effect;
+.Cm right ,
+which justifies to the right margin; or
+.Cm center ,
+which aligns around an imagined centre axis.
+.It
+A macro invocation, which selects a predefined width
+associated with that macro.
+The most popular is the imaginary macro
+.Ar \&Ds ,
+which resolves to
+.Sy 6n .
+.It
+A scaling width as described in
+.Xr roff 7 .
+.It
+An arbitrary string, which indents by the length of this string.
+.El
+.Pp
+When the argument is missing,
+.Fl offset
+is ignored.
+.It Fl compact
+Do not assert vertical space before the display.
+.El
+.Pp
+Examples:
+.Bd -literal -offset indent
+\&.Bd \-literal \-offset indent \-compact
+ Hello world.
+\&.Ed
+.Ed
+.Pp
+See also
+.Sx \&D1
+and
+.Sx \&Dl .
+.Ss \&Bf
+Change the font mode for a scoped block of text.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Bf
+.Oo
+.Fl emphasis | literal | symbolic |
+.Cm \&Em | \&Li | \&Sy
+.Oc
+.Ed
+.Pp
+The
+.Fl emphasis
+and
+.Cm \&Em
+argument are equivalent, as are
+.Fl symbolic
+and
+.Cm \&Sy ,
+and
+.Fl literal
+and
+.Cm \&Li .
+Without an argument, this macro does nothing.
+The font mode continues until broken by a new font mode in a nested
+scope or
+.Sx \&Ef
+is encountered.
+.Pp
+See also
+.Sx \&Li ,
+.Sx \&Ef ,
+.Sx \&Em ,
+and
+.Sx \&Sy .
+.Ss \&Bk
+For each macro, keep its output together on the same output line,
+until the end of the macro or the end of the input line is reached,
+whichever comes first.
+Line breaks in text lines are unaffected.
+The syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Bk Fl words
+.Pp
+The
+.Fl words
+argument is required; additional arguments are ignored.
+.Pp
+The following example will not break within each
+.Sx \&Op
+macro line:
+.Bd -literal -offset indent
+\&.Bk \-words
+\&.Op Fl f Ar flags
+\&.Op Fl o Ar output
+\&.Ek
+.Ed
+.Pp
+Be careful in using over-long lines within a keep block!
+Doing so will clobber the right margin.
+.Ss \&Bl
+Begin a list.
+Lists consist of items specified using the
+.Sx \&It
+macro, containing a head or a body or both.
+The list syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Bl
+.Fl Ns Ar type
+.Op Fl width Ar val
+.Op Fl offset Ar val
+.Op Fl compact
+.Op HEAD ...
+.Ed
+.Pp
+The list
+.Ar type
+is mandatory and must be specified first.
+The
+.Fl width
+and
+.Fl offset
+arguments accept scaling widths as described in
+.Xr roff 7
+or use the length of the given string.
+The
+.Fl offset
+is a global indentation for the whole list, affecting both item heads
+and bodies.
+For those list types supporting it, the
+.Fl width
+argument requests an additional indentation of item bodies,
+to be added to the
+.Fl offset .
+Unless the
+.Fl compact
+argument is specified, list entries are separated by vertical space.
+.Pp
+A list must specify one of the following list types:
+.Bl -tag -width 12n -offset indent
+.It Fl bullet
+No item heads can be specified, but a bullet will be printed at the head
+of each item.
+Item bodies start on the same output line as the bullet
+and are indented according to the
+.Fl width
+argument.
+.It Fl column
+A columnated list.
+The
+.Fl width
+argument has no effect; instead, each argument specifies the width
+of one column, using either the scaling width syntax described in
+.Xr roff 7
+or the string length of the argument.
+If the first line of the body of a
+.Fl column
+list is not an
+.Sx \&It
+macro line,
+.Sx \&It
+contexts spanning one input line each are implied until an
+.Sx \&It
+macro line is encountered, at which point items start being interpreted as
+described in the
+.Sx \&It
+documentation.
+.It Fl dash
+Like
+.Fl bullet ,
+except that dashes are used in place of bullets.
+.It Fl diag
+Like
+.Fl inset ,
+except that item heads are not parsed for macro invocations.
+Most often used in the
+.Em DIAGNOSTICS
+section with error constants in the item heads.
+.It Fl enum
+A numbered list.
+No item heads can be specified.
+Formatted like
+.Fl bullet ,
+except that cardinal numbers are used in place of bullets,
+starting at 1.
+.It Fl hang
+Like
+.Fl tag ,
+except that the first lines of item bodies are not indented, but follow
+the item heads like in
+.Fl inset
+lists.
+.It Fl hyphen
+Synonym for
+.Fl dash .
+.It Fl inset
+Item bodies follow items heads on the same line, using normal inter-word
+spacing.
+Bodies are not indented, and the
+.Fl width
+argument is ignored.
+.It Fl item
+No item heads can be specified, and none are printed.
+Bodies are not indented, and the
+.Fl width
+argument is ignored.
+.It Fl ohang
+Item bodies start on the line following item heads and are not indented.
+The
+.Fl width
+argument is ignored.
+.It Fl tag
+Item bodies are indented according to the
+.Fl width
+argument.
+When an item head fits inside the indentation, the item body follows
+this head on the same output line.
+Otherwise, the body starts on the output line following the head.
+.El
+.Pp
+Lists may be nested within lists and displays.
+Nesting of
+.Fl column
+and
+.Fl enum
+lists may not be portable.
+.Pp
+See also
+.Sx \&El
+and
+.Sx \&It .
+.Ss \&Bo
+Begin a block enclosed by square brackets.
+Does not have any head arguments.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Bo 1 ,
+\&.Dv BUFSIZ \&Bc
+.Ed
+.Pp
+See also
+.Sx \&Bq .
+.Ss \&Bq
+Encloses its arguments in square brackets.
+.Pp
+Examples:
+.Dl \&.Bq 1 , \&Dv BUFSIZ
+.Pp
+.Em Remarks :
+this macro is sometimes abused to emulate optional arguments for
+commands; the correct macros to use for this purpose are
+.Sx \&Op ,
+.Sx \&Oo ,
+and
+.Sx \&Oc .
+.Pp
+See also
+.Sx \&Bo .
+.Ss \&Brc
+Close a
+.Sx \&Bro
+block.
+Does not have any tail arguments.
+.Ss \&Bro
+Begin a block enclosed by curly braces.
+Does not have any head arguments.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Bro 1 , ... ,
+\&.Va n \&Brc
+.Ed
+.Pp
+See also
+.Sx \&Brq .
+.Ss \&Brq
+Encloses its arguments in curly braces.
+.Pp
+Examples:
+.Dl \&.Brq 1 , ... , \&Va n
+.Pp
+See also
+.Sx \&Bro .
+.Ss \&Bsx
+Format the
+.Bsx
+version provided as an argument, or a default value if
+no argument is provided.
+.Pp
+Examples:
+.Dl \&.Bsx 1.0
+.Dl \&.Bsx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+and
+.Sx \&Ox .
+.Ss \&Bt
+Supported only for compatibility, do not use this in new manuals.
+Prints
+.Dq is currently in beta test.
+.Ss \&Bx
+Format the
+.Bx
+version provided as an argument, or a default value if no
+argument is provided.
+.Pp
+Examples:
+.Dl \&.Bx 4.3 Tahoe
+.Dl \&.Bx 4.4
+.Dl \&.Bx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+and
+.Sx \&Ox .
+.Ss \&Cd
+Kernel configuration declaration.
+This denotes strings accepted by
+.Xr config 8 .
+It is most often used in section 4 manual pages.
+.Pp
+Examples:
+.Dl \&.Cd device le0 at scode?
+.Pp
+.Em Remarks :
+this macro is commonly abused by using quoted literals to retain
+whitespace and align consecutive
+.Sx \&Cd
+declarations.
+This practise is discouraged.
+.Ss \&Cm
+Command modifiers.
+Typically used for fixed strings passed as arguments, unless
+.Sx \&Fl
+is more appropriate.
+Also useful when specifying configuration options or keys.
+.Pp
+Examples:
+.Dl ".Nm mt Fl f Ar device Cm rewind"
+.Dl ".Nm ps Fl o Cm pid , Ns Cm command"
+.Dl ".Nm dd Cm if= Ns Ar file1 Cm of= Ns Ar file2"
+.Dl ".Cm IdentityFile Pa ~/.ssh/id_rsa"
+.Dl ".Cm LogLevel Dv DEBUG"
+.Ss \&D1
+One-line indented display.
+This is formatted by the default rules and is useful for simple indented
+statements.
+It is followed by a newline.
+.Pp
+Examples:
+.Dl \&.D1 \&Fl abcdefgh
+.Pp
+See also
+.Sx \&Bd
+and
+.Sx \&Dl .
+.Ss \&Db
+Switch debugging mode.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Db Cm on | off
+.Pp
+This macro is ignored by
+.Xr mandoc 1 .
+.Ss \&Dc
+Close a
+.Sx \&Do
+block.
+Does not have any tail arguments.
+.Ss \&Dd
+Document date for display in the page footer.
+This is the mandatory first macro of any
+.Nm
+manual.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Dd Ar month day , year
+.Pp
+The
+.Ar month
+is the full English month name, the
+.Ar day
+is an optionally zero-padded numeral, and the
+.Ar year
+is the full four-digit year.
+.Pp
+Other arguments are not portable; the
+.Xr mandoc 1
+utility handles them as follows:
+.Bl -dash -offset 3n -compact
+.It
+To have the date automatically filled in by the
+.Ox
+version of
+.Xr cvs 1 ,
+the special string
+.Dq $\&Mdocdate$
+can be given as an argument.
+.It
+The traditional, purely numeric
+.Xr man 7
+format
+.Ar year Ns \(en Ns Ar month Ns \(en Ns Ar day
+is accepted, too.
+.It
+If a date string cannot be parsed, it is used verbatim.
+.It
+If no date string is given, the current date is used.
+.El
+.Pp
+Examples:
+.Dl \&.Dd $\&Mdocdate$
+.Dl \&.Dd $\&Mdocdate: July 21 2007$
+.Dl \&.Dd July 21, 2007
+.Pp
+See also
+.Sx \&Dt
+and
+.Sx \&Os .
+.Ss \&Dl
+One-line intended display.
+This is formatted as literal text and is useful for commands and
+invocations.
+It is followed by a newline.
+.Pp
+Examples:
+.Dl \&.Dl % mandoc mdoc.7 \e(ba less
+.Pp
+See also
+.Sx \&Bd
+and
+.Sx \&D1 .
+.Ss \&Do
+Begin a block enclosed by double quotes.
+Does not have any head arguments.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Do
+April is the cruellest month
+\&.Dc
+\e(em T.S. Eliot
+.Ed
+.Pp
+See also
+.Sx \&Dq .
+.Ss \&Dq
+Encloses its arguments in
+.Dq typographic
+double-quotes.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Dq April is the cruellest month
+\e(em T.S. Eliot
+.Ed
+.Pp
+See also
+.Sx \&Qq ,
+.Sx \&Sq ,
+and
+.Sx \&Do .
+.Ss \&Dt
+Document title for display in the page header.
+This is the mandatory second macro of any
+.Nm
+file.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Dt
+.Ar TITLE
+.Ar section
+.Op Ar volume | arch
+.Ed
+.Pp
+Its arguments are as follows:
+.Bl -tag -width section -offset 2n
+.It Ar TITLE
+The document's title (name), defaulting to
+.Dq UNTITLED
+if unspecified.
+To achieve a uniform appearance of page header lines,
+it should by convention be all caps.
+.It Ar section
+The manual section.
+This may be one of
+.Cm 1
+.Pq utilities ,
+.Cm 2
+.Pq system calls ,
+.Cm 3
+.Pq libraries ,
+.Cm 3p
+.Pq Perl libraries ,
+.Cm 4
+.Pq devices ,
+.Cm 5
+.Pq file formats ,
+.Cm 6
+.Pq games ,
+.Cm 7
+.Pq miscellaneous ,
+.Cm 8
+.Pq system utilities ,
+.Cm 9
+.Pq kernel functions ,
+.Cm X11
+.Pq X Window System ,
+.Cm X11R6
+.Pq X Window System ,
+.Cm unass
+.Pq unassociated ,
+.Cm local
+.Pq local system ,
+.Cm draft
+.Pq draft manual ,
+or
+.Cm paper
+.Pq paper .
+It should correspond to the manual's filename suffix and defaults to
+the empty string if unspecified.
+.It Ar volume
+This overrides the volume inferred from
+.Ar section .
+This field is optional, and if specified, must be one of
+.Cm USD
+.Pq users' supplementary documents ,
+.Cm PS1
+.Pq programmers' supplementary documents ,
+.Cm AMD
+.Pq administrators' supplementary documents ,
+.Cm SMM
+.Pq system managers' manuals ,
+.Cm URM
+.Pq users' reference manuals ,
+.Cm PRM
+.Pq programmers' reference manuals ,
+.Cm KM
+.Pq kernel manuals ,
+.Cm IND
+.Pq master index ,
+.Cm MMI
+.Pq master index ,
+.Cm LOCAL
+.Pq local manuals ,
+.Cm LOC
+.Pq local manuals ,
+or
+.Cm CON
+.Pq contributed manuals .
+.It Ar arch
+This specifies the machine architecture a manual page applies to,
+where relevant, for example
+.Cm alpha ,
+.Cm amd64 ,
+.Cm i386 ,
+or
+.Cm sparc64 .
+The list of supported architectures varies by operating system.
+For the full list of all architectures recognized by
+.Xr mandoc 1 ,
+see the file
+.Pa arch.in
+in the source distribution.
+.El
+.Pp
+Examples:
+.Dl \&.Dt FOO 1
+.Dl \&.Dt FOO 4 KM
+.Dl \&.Dt FOO 9 i386
+.Pp
+See also
+.Sx \&Dd
+and
+.Sx \&Os .
+.Ss \&Dv
+Defined variables such as preprocessor constants, constant symbols,
+enumeration values, and so on.
+.Pp
+Examples:
+.Dl \&.Dv NULL
+.Dl \&.Dv BUFSIZ
+.Dl \&.Dv STDOUT_FILENO
+.Pp
+See also
+.Sx \&Er
+and
+.Sx \&Ev
+for special-purpose constants,
+.Sx \&Va
+for variable symbols, and
+.Sx \&Fd
+for listing preprocessor variable definitions in the
+.Em SYNOPSIS .
+.Ss \&Dx
+Format the
+.Dx
+version provided as an argument, or a default
+value if no argument is provided.
+.Pp
+Examples:
+.Dl \&.Dx 2.4.1
+.Dl \&.Dx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+and
+.Sx \&Ox .
+.Ss \&Ec
+Close a scope started by
+.Sx \&Eo .
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ec Op Ar TERM
+.Pp
+The
+.Ar TERM
+argument is used as the enclosure tail, for example, specifying \e(rq
+will emulate
+.Sx \&Dc .
+.Ss \&Ed
+End a display context started by
+.Sx \&Bd .
+.Ss \&Ef
+End a font mode context started by
+.Sx \&Bf .
+.Ss \&Ek
+End a keep context started by
+.Sx \&Bk .
+.Ss \&El
+End a list context started by
+.Sx \&Bl .
+.Pp
+See also
+.Sx \&Bl
+and
+.Sx \&It .
+.Ss \&Em
+Denotes text that should be
+.Em emphasised .
+Note that this is a presentation term and should not be used for
+stylistically decorating technical terms.
+Depending on the output device, this is usually represented
+using an italic font or underlined characters.
+.Pp
+Examples:
+.Dl \&.Em Warnings!
+.Dl \&.Em Remarks :
+.Pp
+See also
+.Sx \&Bf ,
+.Sx \&Li ,
+.Sx \&No ,
+and
+.Sx \&Sy .
+.Ss \&En
+This macro is obsolete.
+Use
+.Sx \&Eo
+or any of the other enclosure macros.
+.Pp
+It encloses its argument in the delimiters specified by the last
+.Sx \&Es
+macro.
+.Ss \&Eo
+An arbitrary enclosure.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Eo Op Ar TERM
+.Pp
+The
+.Ar TERM
+argument is used as the enclosure head, for example, specifying \e(lq
+will emulate
+.Sx \&Do .
+.Ss \&Er
+Error constants for definitions of the
+.Va errno
+libc global variable.
+This is most often used in section 2 and 3 manual pages.
+.Pp
+Examples:
+.Dl \&.Er EPERM
+.Dl \&.Er ENOENT
+.Pp
+See also
+.Sx \&Dv
+for general constants.
+.Ss \&Es
+This macro is obsolete.
+Use
+.Sx \&Eo
+or any of the other enclosure macros.
+.Pp
+It takes two arguments, defining the delimiters to be used by subsequent
+.Sx \&En
+macros.
+.Ss \&Ev
+Environmental variables such as those specified in
+.Xr environ 7 .
+.Pp
+Examples:
+.Dl \&.Ev DISPLAY
+.Dl \&.Ev PATH
+.Pp
+See also
+.Sx \&Dv
+for general constants.
+.Ss \&Ex
+Insert a standard sentence regarding command exit values of 0 on success
+and >0 on failure.
+This is most often used in section 1, 6, and 8 manual pages.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ex Fl std Op Ar utility ...
+.Pp
+If
+.Ar utility
+is not specified, the document's name set by
+.Sx \&Nm
+is used.
+Multiple
+.Ar utility
+arguments are treated as separate utilities.
+.Pp
+See also
+.Sx \&Rv .
+.Ss \&Fa
+Function argument.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Fa
+.Qo
+.Op Ar argtype
+.Op Ar argname
+.Qc Ar \&...
+.Ed
+.Pp
+Each argument may be a name and a type (recommended for the
+.Em SYNOPSIS
+section), a name alone (for function invocations),
+or a type alone (for function prototypes).
+If both a type and a name are given or if the type consists of multiple
+words, all words belonging to the same function argument have to be
+given in a single argument to the
+.Sx \&Fa
+macro.
+.Pp
+This macro is also used to specify the field name of a structure.
+.Pp
+Most often, the
+.Sx \&Fa
+macro is used in the
+.Em SYNOPSIS
+within
+.Sx \&Fo
+blocks when documenting multi-line function prototypes.
+If invoked with multiple arguments, the arguments are separated by a
+comma.
+Furthermore, if the following macro is another
+.Sx \&Fa ,
+the last argument will also have a trailing comma.
+.Pp
+Examples:
+.Dl \&.Fa \(dqconst char *p\(dq
+.Dl \&.Fa \(dqint a\(dq \(dqint b\(dq \(dqint c\(dq
+.Dl \&.Fa \(dqchar *\(dq size_t
+.Pp
+See also
+.Sx \&Fo .
+.Ss \&Fc
+End a function context started by
+.Sx \&Fo .
+.Ss \&Fd
+Preprocessor directive, in particular for listing it in the
+.Em SYNOPSIS .
+Historically, it was also used to document include files.
+The latter usage has been deprecated in favour of
+.Sx \&In .
+.Pp
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Fd
+.Li # Ns Ar directive
+.Op Ar argument ...
+.Ed
+.Pp
+Examples:
+.Dl \&.Fd #define sa_handler __sigaction_u.__sa_handler
+.Dl \&.Fd #define SIO_MAXNFDS
+.Dl \&.Fd #ifdef FS_DEBUG
+.Dl \&.Ft void
+.Dl \&.Fn dbg_open \(dqconst char *\(dq
+.Dl \&.Fd #endif
+.Pp
+See also
+.Sx MANUAL STRUCTURE ,
+.Sx \&In ,
+and
+.Sx \&Dv .
+.Ss \&Fl
+Command-line flag or option.
+Used when listing arguments to command-line utilities.
+Prints a fixed-width hyphen
+.Sq \-
+directly followed by each argument.
+If no arguments are provided, a hyphen is printed followed by a space.
+If the argument is a macro, a hyphen is prefixed to the subsequent macro
+output.
+.Pp
+Examples:
+.Dl ".Fl R Op Fl H | L | P"
+.Dl ".Op Fl 1AaCcdFfgHhikLlmnopqRrSsTtux"
+.Dl ".Fl type Cm d Fl name Pa CVS"
+.Dl ".Fl Ar signal_number"
+.Dl ".Fl o Fl"
+.Pp
+See also
+.Sx \&Cm .
+.Ss \&Fn
+A function name.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Ns Sx \&Fn
+.Op Ar functype
+.Ar funcname
+.Op Oo Ar argtype Oc Ar argname
+.Ed
+.Pp
+Function arguments are surrounded in parenthesis and
+are delimited by commas.
+If no arguments are specified, blank parenthesis are output.
+In the
+.Em SYNOPSIS
+section, this macro starts a new output line,
+and a blank line is automatically inserted between function definitions.
+.Pp
+Examples:
+.Dl \&.Fn \(dqint funcname\(dq \(dqint arg0\(dq \(dqint arg1\(dq
+.Dl \&.Fn funcname \(dqint arg0\(dq
+.Dl \&.Fn funcname arg0
+.Pp
+.Bd -literal -offset indent -compact
+\&.Ft functype
+\&.Fn funcname
+.Ed
+.Pp
+When referring to a function documented in another manual page, use
+.Sx \&Xr
+instead.
+See also
+.Sx MANUAL STRUCTURE ,
+.Sx \&Fo ,
+and
+.Sx \&Ft .
+.Ss \&Fo
+Begin a function block.
+This is a multi-line version of
+.Sx \&Fn .
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Fo Ar funcname
+.Pp
+Invocations usually occur in the following context:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Ft Ar functype
+.br
+.Pf \. Sx \&Fo Ar funcname
+.br
+.Pf \. Sx \&Fa Qq Ar argtype Ar argname
+.br
+\&.\.\.
+.br
+.Pf \. Sx \&Fc
+.Ed
+.Pp
+A
+.Sx \&Fo
+scope is closed by
+.Sx \&Fc .
+.Pp
+See also
+.Sx MANUAL STRUCTURE ,
+.Sx \&Fa ,
+.Sx \&Fc ,
+and
+.Sx \&Ft .
+.Ss \&Fr
+This macro is obsolete.
+No replacement markup is needed.
+.Pp
+It was used to show numerical function return values in an italic font.
+.Ss \&Ft
+A function type.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ft Ar functype
+.Pp
+In the
+.Em SYNOPSIS
+section, a new output line is started after this macro.
+.Pp
+Examples:
+.Dl \&.Ft int
+.Bd -literal -offset indent -compact
+\&.Ft functype
+\&.Fn funcname
+.Ed
+.Pp
+See also
+.Sx MANUAL STRUCTURE ,
+.Sx \&Fn ,
+and
+.Sx \&Fo .
+.Ss \&Fx
+Format the
+.Fx
+version provided as an argument, or a default value
+if no argument is provided.
+.Pp
+Examples:
+.Dl \&.Fx 7.1
+.Dl \&.Fx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Nx ,
+and
+.Sx \&Ox .
+.Ss \&Hf
+This macro is not implemented in
+.Xr mandoc 1 .
+.Pp
+It was used to include the contents of a (header) file literally.
+The syntax was:
+.Pp
+.Dl Pf . Sx \&Hf Ar filename
+.Ss \&Ic
+Designate an internal or interactive command.
+This is similar to
+.Sx \&Cm
+but used for instructions rather than values.
+.Pp
+Examples:
+.Dl \&.Ic :wq
+.Dl \&.Ic hash
+.Dl \&.Ic alias
+.Pp
+Note that using
+.Sx \&Bd Fl literal
+or
+.Sx \&D1
+is preferred for displaying code; the
+.Sx \&Ic
+macro is used when referring to specific instructions.
+.Ss \&In
+An
+.Dq include
+file.
+When invoked as the first macro on an input line in the
+.Em SYNOPSIS
+section, the argument is displayed in angle brackets
+and preceded by
+.Dq #include ,
+and a blank line is inserted in front if there is a preceding
+function declaration.
+This is most often used in section 2, 3, and 9 manual pages.
+.Pp
+Examples:
+.Dl \&.In sys/types.h
+.Pp
+See also
+.Sx MANUAL STRUCTURE .
+.Ss \&It
+A list item.
+The syntax of this macro depends on the list type.
+.Pp
+Lists
+of type
+.Fl hang ,
+.Fl ohang ,
+.Fl inset ,
+and
+.Fl diag
+have the following syntax:
+.Pp
+.D1 Pf \. Sx \&It Ar args
+.Pp
+Lists of type
+.Fl bullet ,
+.Fl dash ,
+.Fl enum ,
+.Fl hyphen
+and
+.Fl item
+have the following syntax:
+.Pp
+.D1 Pf \. Sx \&It
+.Pp
+with subsequent lines interpreted within the scope of the
+.Sx \&It
+until either a closing
+.Sx \&El
+or another
+.Sx \&It .
+.Pp
+The
+.Fl tag
+list has the following syntax:
+.Pp
+.D1 Pf \. Sx \&It Op Cm args
+.Pp
+Subsequent lines are interpreted as with
+.Fl bullet
+and family.
+The line arguments correspond to the list's left-hand side; body
+arguments correspond to the list's contents.
+.Pp
+The
+.Fl column
+list is the most complicated.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&It Ar cell Op <TAB> Ar cell ...
+.D1 Pf \. Sx \&It Ar cell Op Sx \&Ta Ar cell ...
+.Pp
+The arguments consist of one or more lines of text and macros
+representing a complete table line.
+Cells within the line are delimited by tabs or by the special
+.Sx \&Ta
+block macro.
+The tab cell delimiter may only be used within the
+.Sx \&It
+line itself; on following lines, only the
+.Sx \&Ta
+macro can be used to delimit cells, and
+.Sx \&Ta
+is only recognised as a macro when called by other macros,
+not as the first macro on a line.
+.Pp
+Note that quoted strings may span tab-delimited cells on an
+.Sx \&It
+line.
+For example,
+.Pp
+.Dl .It \(dqcol1 ; <TAB> col2 ;\(dq \&;
+.Pp
+will preserve the semicolon whitespace except for the last.
+.Pp
+See also
+.Sx \&Bl .
+.Ss \&Lb
+Specify a library.
+The syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Lb Ar library
+.Pp
+The
+.Ar library
+parameter may be a system library, such as
+.Cm libz
+or
+.Cm libpam ,
+in which case a small library description is printed next to the linker
+invocation; or a custom library, in which case the library name is
+printed in quotes.
+This is most commonly used in the
+.Em SYNOPSIS
+section as described in
+.Sx MANUAL STRUCTURE .
+.Pp
+Examples:
+.Dl \&.Lb libz
+.Dl \&.Lb libmandoc
+.Ss \&Li
+Denotes text that should be in a
+.Li literal
+font mode.
+Note that this is a presentation term and should not be used for
+stylistically decorating technical terms.
+.Pp
+On terminal output devices, this is often indistinguishable from
+normal text.
+.Pp
+See also
+.Sx \&Bf ,
+.Sx \&Em ,
+.Sx \&No ,
+and
+.Sx \&Sy .
+.Ss \&Lk
+Format a hyperlink.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Lk Ar uri Op Ar name
+.Pp
+Examples:
+.Dl \&.Lk http://bsd.lv \(dqThe BSD.lv Project\(dq
+.Dl \&.Lk http://bsd.lv
+.Pp
+See also
+.Sx \&Mt .
+.Ss \&Lp
+Synonym for
+.Sx \&Pp .
+.Ss \&Ms
+Display a mathematical symbol.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ms Ar symbol
+.Pp
+Examples:
+.Dl \&.Ms sigma
+.Dl \&.Ms aleph
+.Ss \&Mt
+Format a
+.Dq mailto:
+hyperlink.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Mt Ar address
+.Pp
+Examples:
+.Dl \&.Mt discuss@manpages.bsd.lv
+.Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv
+.Ss \&Nd
+A one line description of the manual's content.
+This may only be invoked in the
+.Em SYNOPSIS
+section subsequent the
+.Sx \&Nm
+macro.
+.Pp
+Examples:
+.Dl Pf . Sx \&Nd mdoc language reference
+.Dl Pf . Sx \&Nd format and display UNIX manuals
+.Pp
+The
+.Sx \&Nd
+macro technically accepts child macros and terminates with a subsequent
+.Sx \&Sh
+invocation.
+Do not assume this behaviour: some
+.Xr whatis 1
+database generators are not smart enough to parse more than the line
+arguments and will display macros verbatim.
+.Pp
+See also
+.Sx \&Nm .
+.Ss \&Nm
+The name of the manual page, or \(em in particular in section 1, 6,
+and 8 pages \(em of an additional command or feature documented in
+the manual page.
+When first invoked, the
+.Sx \&Nm
+macro expects a single argument, the name of the manual page.
+Usually, the first invocation happens in the
+.Em NAME
+section of the page.
+The specified name will be remembered and used whenever the macro is
+called again without arguments later in the page.
+The
+.Sx \&Nm
+macro uses
+.Sx Block full-implicit
+semantics when invoked as the first macro on an input line in the
+.Em SYNOPSIS
+section; otherwise, it uses ordinary
+.Sx In-line
+semantics.
+.Pp
+Examples:
+.Bd -literal -offset indent
+\&.Sh SYNOPSIS
+\&.Nm cat
+\&.Op Fl benstuv
+\&.Op Ar
+.Ed
+.Pp
+In the
+.Em SYNOPSIS
+of section 2, 3 and 9 manual pages, use the
+.Sx \&Fn
+macro rather than
+.Sx \&Nm
+to mark up the name of the manual page.
+.Ss \&No
+Normal text.
+Closes the scope of any preceding in-line macro.
+When used after physical formatting macros like
+.Sx \&Em
+or
+.Sx \&Sy ,
+switches back to the standard font face and weight.
+Can also be used to embed plain text strings in macro lines
+using semantic annotation macros.
+.Pp
+Examples:
+.Dl ".Em italic , Sy bold , No and roman"
+.Pp
+.Bd -literal -offset indent -compact
+\&.Sm off
+\&.Cm :C No / Ar pattern No / Ar replacement No /
+\&.Sm on
+.Ed
+.Pp
+See also
+.Sx \&Em ,
+.Sx \&Li ,
+and
+.Sx \&Sy .
+.Ss \&Ns
+Suppress a space between the output of the preceding macro
+and the following text or macro.
+Following invocation, input is interpreted as normal text
+just like after an
+.Sx \&No
+macro.
+.Pp
+This has no effect when invoked at the start of a macro line.
+.Pp
+Examples:
+.Dl ".Ar name Ns = Ns Ar value"
+.Dl ".Cm :M Ns Ar pattern"
+.Dl ".Fl o Ns Ar output"
+.Pp
+See also
+.Sx \&No
+and
+.Sx \&Sm .
+.Ss \&Nx
+Format the
+.Nx
+version provided as an argument, or a default value if
+no argument is provided.
+.Pp
+Examples:
+.Dl \&.Nx 5.01
+.Dl \&.Nx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+and
+.Sx \&Ox .
+.Ss \&Oc
+Close multi-line
+.Sx \&Oo
+context.
+.Ss \&Oo
+Multi-line version of
+.Sx \&Op .
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Oo
+\&.Op Fl flag Ns Ar value
+\&.Oc
+.Ed
+.Ss \&Op
+Optional part of a command line.
+Prints the argument(s) in brackets.
+This is most often used in the
+.Em SYNOPSIS
+section of section 1 and 8 manual pages.
+.Pp
+Examples:
+.Dl \&.Op \&Fl a \&Ar b
+.Dl \&.Op \&Ar a | b
+.Pp
+See also
+.Sx \&Oo .
+.Ss \&Os
+Operating system version for display in the page footer.
+This is the mandatory third macro of
+any
+.Nm
+file.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Os Op Ar system Op Ar version
+.Pp
+The optional
+.Ar system
+parameter specifies the relevant operating system or environment.
+Left unspecified, it defaults to the local operating system version.
+This is the suggested form.
+.Pp
+Examples:
+.Dl \&.Os
+.Dl \&.Os KTH/CSC/TCS
+.Dl \&.Os BSD 4.3
+.Pp
+See also
+.Sx \&Dd
+and
+.Sx \&Dt .
+.Ss \&Ot
+This macro is obsolete.
+Use
+.Sx \&Ft
+instead; with
+.Xr mandoc 1 ,
+both have the same effect.
+.Pp
+Historical
+.Nm
+packages described it as
+.Dq "old function type (FORTRAN)" .
+.Ss \&Ox
+Format the
+.Ox
+version provided as an argument, or a default value
+if no argument is provided.
+.Pp
+Examples:
+.Dl \&.Ox 4.5
+.Dl \&.Ox
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+and
+.Sx \&Nx .
+.Ss \&Pa
+An absolute or relative file system path, or a file or directory name.
+If an argument is not provided, the character
+.Sq \(ti
+is used as a default.
+.Pp
+Examples:
+.Dl \&.Pa /usr/bin/mandoc
+.Dl \&.Pa /usr/share/man/man7/mdoc.7
+.Pp
+See also
+.Sx \&Lk .
+.Ss \&Pc
+Close parenthesised context opened by
+.Sx \&Po .
+.Ss \&Pf
+Removes the space between its argument
+.Pq Dq prefix
+and the following macro.
+Its syntax is as follows:
+.Pp
+.D1 .Pf Ar prefix macro arguments ...
+.Pp
+This is equivalent to:
+.Pp
+.D1 .No Ar prefix No \&Ns Ar macro arguments ...
+.Pp
+Examples:
+.Dl ".Pf $ Ar variable_name"
+.Dl ".Pf 0x Ar hex_digits"
+.Pp
+See also
+.Sx \&Ns
+and
+.Sx \&Sm .
+.Ss \&Po
+Multi-line version of
+.Sx \&Pq .
+.Ss \&Pp
+Break a paragraph.
+This will assert vertical space between prior and subsequent macros
+and/or text.
+.Pp
+Paragraph breaks are not needed before or after
+.Sx \&Sh
+or
+.Sx \&Ss
+macros or before displays
+.Pq Sx \&Bd
+or lists
+.Pq Sx \&Bl
+unless the
+.Fl compact
+flag is given.
+.Ss \&Pq
+Parenthesised enclosure.
+.Pp
+See also
+.Sx \&Po .
+.Ss \&Qc
+Close quoted context opened by
+.Sx \&Qo .
+.Ss \&Ql
+Format a single-quoted literal.
+See also
+.Sx \&Qq
+and
+.Sx \&Sq .
+.Ss \&Qo
+Multi-line version of
+.Sx \&Qq .
+.Ss \&Qq
+Encloses its arguments in
+.Qq typewriter
+double-quotes.
+Consider using
+.Sx \&Dq .
+.Pp
+See also
+.Sx \&Dq ,
+.Sx \&Sq ,
+and
+.Sx \&Qo .
+.Ss \&Re
+Close an
+.Sx \&Rs
+block.
+Does not have any tail arguments.
+.Ss \&Rs
+Begin a bibliographic
+.Pq Dq reference
+block.
+Does not have any head arguments.
+The block macro may only contain
+.Sx \&%A ,
+.Sx \&%B ,
+.Sx \&%C ,
+.Sx \&%D ,
+.Sx \&%I ,
+.Sx \&%J ,
+.Sx \&%N ,
+.Sx \&%O ,
+.Sx \&%P ,
+.Sx \&%Q ,
+.Sx \&%R ,
+.Sx \&%T ,
+.Sx \&%U ,
+and
+.Sx \&%V
+child macros (at least one must be specified).
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Rs
+\&.%A J. E. Hopcroft
+\&.%A J. D. Ullman
+\&.%B Introduction to Automata Theory, Languages, and Computation
+\&.%I Addison-Wesley
+\&.%C Reading, Massachusettes
+\&.%D 1979
+\&.Re
+.Ed
+.Pp
+If an
+.Sx \&Rs
+block is used within a SEE ALSO section, a vertical space is asserted
+before the rendered output, else the block continues on the current
+line.
+.Ss \&Rv
+Insert a standard sentence regarding a function call's return value of 0
+on success and \-1 on error, with the
+.Va errno
+libc global variable set on error.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Rv Fl std Op Ar function ...
+.Pp
+If
+.Ar function
+is not specified, the document's name set by
+.Sx \&Nm
+is used.
+Multiple
+.Ar function
+arguments are treated as separate functions.
+.Pp
+See also
+.Sx \&Ex .
+.Ss \&Sc
+Close single-quoted context opened by
+.Sx \&So .
+.Ss \&Sh
+Begin a new section.
+For a list of conventional manual sections, see
+.Sx MANUAL STRUCTURE .
+These sections should be used unless it's absolutely necessary that
+custom sections be used.
+.Pp
+Section names should be unique so that they may be keyed by
+.Sx \&Sx .
+Although this macro is parsed, it should not consist of child node or it
+may not be linked with
+.Sx \&Sx .
+.Pp
+See also
+.Sx \&Pp ,
+.Sx \&Ss ,
+and
+.Sx \&Sx .
+.Ss \&Sm
+Switches the spacing mode for output generated from macros.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Sm Op Cm on | off
+.Pp
+By default, spacing is
+.Cm on .
+When switched
+.Cm off ,
+no white space is inserted between macro arguments and between the
+output generated from adjacent macros, but text lines
+still get normal spacing between words and sentences.
+.Pp
+When called without an argument, the
+.Sx \&Sm
+macro toggles the spacing mode.
+Using this is not recommended because it makes the code harder to read.
+.Ss \&So
+Multi-line version of
+.Sx \&Sq .
+.Ss \&Sq
+Encloses its arguments in
+.Sq typewriter
+single-quotes.
+.Pp
+See also
+.Sx \&Dq ,
+.Sx \&Qq ,
+and
+.Sx \&So .
+.Ss \&Ss
+Begin a new subsection.
+Unlike with
+.Sx \&Sh ,
+there is no convention for the naming of subsections.
+Except
+.Em DESCRIPTION ,
+the conventional sections described in
+.Sx MANUAL STRUCTURE
+rarely have subsections.
+.Pp
+Sub-section names should be unique so that they may be keyed by
+.Sx \&Sx .
+Although this macro is parsed, it should not consist of child node or it
+may not be linked with
+.Sx \&Sx .
+.Pp
+See also
+.Sx \&Pp ,
+.Sx \&Sh ,
+and
+.Sx \&Sx .
+.Ss \&St
+Replace an abbreviation for a standard with the full form.
+The following standards are recognised.
+Where multiple lines are given without a blank line in between,
+they all refer to the same standard, and using the first form
+is recommended.
+.Bl -tag -width 1n
+.It C language standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-ansiC
+.St -ansiC
+.It \-ansiC-89
+.St -ansiC-89
+.It \-isoC
+.St -isoC
+.It \-isoC-90
+.St -isoC-90
+.br
+The original C standard.
+.Pp
+.It \-isoC-amd1
+.St -isoC-amd1
+.Pp
+.It \-isoC-tcor1
+.St -isoC-tcor1
+.Pp
+.It \-isoC-tcor2
+.St -isoC-tcor2
+.Pp
+.It \-isoC-99
+.St -isoC-99
+.It \-ansiC-99
+.St -ansiC-99
+.br
+The second major version of the C language standard.
+.Pp
+.It \-isoC-2011
+.St -isoC-2011
+.br
+The third major version of the C language standard.
+.El
+.It POSIX.1 before the Single UNIX Specification
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-p1003.1-88
+.St -p1003.1-88
+.It \-p1003.1
+.St -p1003.1
+.br
+The original POSIX standard, based on ANSI C.
+.Pp
+.It \-p1003.1-90
+.St -p1003.1-90
+.It \-iso9945-1-90
+.St -iso9945-1-90
+.br
+The first update of POSIX.1.
+.Pp
+.It \-p1003.1b-93
+.St -p1003.1b-93
+.It \-p1003.1b
+.St -p1003.1b
+.br
+Real-time extensions.
+.Pp
+.It \-p1003.1c-95
+.St -p1003.1c-95
+.br
+POSIX thread interfaces.
+.Pp
+.It \-p1003.1i-95
+.St -p1003.1i-95
+.br
+Technical Corrigendum.
+.Pp
+.It \-p1003.1-96
+.St -p1003.1-96
+.It \-iso9945-1-96
+.St -iso9945-1-96
+.br
+Includes POSIX.1-1990, 1b, 1c, and 1i.
+.El
+.It X/Open Portability Guide version 4 and related standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-xpg3
+.St -xpg3
+.br
+An XPG4 precursor, published in 1989.
+.Pp
+.It \-p1003.2
+.St -p1003.2
+.It \-p1003.2-92
+.St -p1003.2-92
+.It \-iso9945-2-93
+.St -iso9945-2-93
+.br
+An XCU4 precursor.
+.Pp
+.It \-p1003.2a-92
+.St -p1003.2a-92
+.br
+Updates to POSIX.2.
+.Pp
+.It \-xpg4
+.St -xpg4
+.br
+Based on POSIX.1 and POSIX.2, published in 1992.
+.El
+.It Single UNIX Specification version 1 and related standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-xpg4.2
+.St -xpg4.2
+.br
+This standard was published in 1994 and is also called SUSv1.
+It was used as the basis for UNIX 95 certification.
+The following three refer to parts of it.
+.Pp
+.It \-xsh4.2
+.St -xsh4.2
+.Pp
+.It \-xcurses4.2
+.St -xcurses4.2
+.Pp
+.It \-p1003.1g-2000
+.St -p1003.1g-2000
+.br
+Networking APIs, including sockets.
+.Pp
+.It \-xpg4.3
+.St -xpg4.3
+.Pp
+.It \-svid4
+.St -svid4 ,
+.br
+Published in 1995.
+.El
+.It Single UNIX Specification version 2 and related standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-susv2
+.St -susv2
+This Standard was published in 1997
+and is also called X/Open Portability Guide version 5.
+It was used as the basis for UNIX 98 certification.
+The following refer to parts of it.
+.Pp
+.It \-xbd5
+.St -xbd5
+.Pp
+.It \-xsh5
+.St -xsh5
+.Pp
+.It \-xcu5
+.St -xcu5
+.Pp
+.It \-xns5
+.St -xns5
+.It \-xns5.2d2.0
+.St -xns5.2d2.0
+.It \-xns5.2
+.St -xns5.2
+.Pp
+.It \-p1387.2
+.St -p1387.2
+.It \-p1387.2-95
+.St -p1387.2-95
+.br
+POSIX software administration.
+.El
+.It Single UNIX Specification version 3 and related standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000X" -compact
+.It \-p1003.1d-99
+.St -p1003.1d-99
+.br
+Additional real-time extensions.
+.Pp
+.It \-p1003.1j-2000
+.St -p1003.1j-2000
+.br
+Advanced real-time extensions.
+.Pp
+.It \-p1003.1q-2000
+.St -p1003.1q-2000
+.br
+Amendment 7: Tracing [C Language].
+.Pp
+.It \-p1003.1-2001
+.St -p1003.1-2001
+.It \-susv3
+.St -susv3
+.br
+This standard is based on C99, SUSv2, POSIX.1-1996, 1d, and 1j.
+It is also called X/Open Portability Guide version 6.
+It is used as the basis for UNIX 03 certification.
+.Pp
+.It \-p1003.1-2004
+.St -p1003.1-2004
+.br
+The second and last Technical Corrigendum.
+.El
+.It Single UNIX Specification version 4
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-p1003.1-2008
+.St -p1003.1-2008
+.br
+This standard is also called SUSv4 and
+X/Open Portability Guide version 7.
+.Pp
+.It \-p1003.1-2013
+.St -p1003.1-2013
+.br
+This is the first Technical Corrigendum.
+.El
+.It Other standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-ieee754
+.St -ieee754
+.br
+Floating-point arithmetic.
+.Pp
+.It \-iso8601
+.St -iso8601
+.br
+Representation of dates and times, published in 1988.
+.Pp
+.It \-iso8802-3
+.St -iso8802-3
+.br
+Ethernet local area networks.
+.Pp
+.It \-ieee1275-94
+.St -ieee1275-94
+.El
+.El
+.Ss \&Sx
+Reference a section or subsection in the same manual page.
+The referenced section or subsection name must be identical to the
+enclosed argument, including whitespace.
+.Pp
+Examples:
+.Dl \&.Sx MANUAL STRUCTURE
+.Pp
+See also
+.Sx \&Sh
+and
+.Sx \&Ss .
+.Ss \&Sy
+Format enclosed arguments in symbolic
+.Pq Dq boldface .
+Note that this is a presentation term and should not be used for
+stylistically decorating technical terms.
+.Pp
+See also
+.Sx \&Bf ,
+.Sx \&Em ,
+.Sx \&Li ,
+and
+.Sx \&No .
+.Ss \&Ta
+Table cell separator in
+.Sx \&Bl Fl column
+lists; can only be used below
+.Sx \&It .
+.Ss \&Tn
+Supported only for compatibility, do not use this in new manuals.
+Even though the macro name
+.Pq Dq tradename
+suggests a semantic function, historic usage is inconsistent, mostly
+using it as a presentation-level macro to request a small caps font.
+.Ss \&Ud
+Supported only for compatibility, do not use this in new manuals.
+Prints out
+.Dq currently under development.
+.Ss \&Ux
+Supported only for compatibility, do not use this in new manuals.
+Prints out
+.Dq Ux .
+.Ss \&Va
+A variable name.
+.Pp
+Examples:
+.Dl \&.Va foo
+.Dl \&.Va const char *bar ;
+.Ss \&Vt
+A variable type.
+This is also used for indicating global variables in the
+.Em SYNOPSIS
+section, in which case a variable name is also specified.
+Note that it accepts
+.Sx Block partial-implicit
+syntax when invoked as the first macro on an input line in the
+.Em SYNOPSIS
+section, else it accepts ordinary
+.Sx In-line
+syntax.
+In the former case, this macro starts a new output line,
+and a blank line is inserted in front if there is a preceding
+function definition or include directive.
+.Pp
+Note that this should not be confused with
+.Sx \&Ft ,
+which is used for function return types.
+.Pp
+Examples:
+.Dl \&.Vt unsigned char
+.Dl \&.Vt extern const char * const sys_signame[] \&;
+.Pp
+See also
+.Sx MANUAL STRUCTURE
+and
+.Sx \&Va .
+.Ss \&Xc
+Close a scope opened by
+.Sx \&Xo .
+.Ss \&Xo
+Extend the header of an
+.Sx \&It
+macro or the body of a partial-implicit block macro
+beyond the end of the input line.
+This macro originally existed to work around the 9-argument limit
+of historic
+.Xr roff 7 .
+.Ss \&Xr
+Link to another manual
+.Pq Qq cross-reference .
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Xr Ar name Op section
+.Pp
+Cross reference the
+.Ar name
+and
+.Ar section
+number of another man page;
+omitting the section number is rarely useful.
+.Pp
+Examples:
+.Dl \&.Xr mandoc 1
+.Dl \&.Xr mandoc 1 \&;
+.Dl \&.Xr mandoc 1 \&Ns s behaviour
+.Ss \&br
+Emits a line-break.
+This macro should not be used; it is implemented for compatibility with
+historical manuals.
+.Pp
+Consider using
+.Sx \&Pp
+in the event of natural paragraph breaks.
+.Ss \&sp
+Emits vertical space.
+This macro should not be used; it is implemented for compatibility with
+historical manuals.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&sp Op Ar height
+.Pp
+The
+.Ar height
+argument is a scaling width as described in
+.Xr roff 7 .
+If unspecified,
+.Sx \&sp
+asserts a single vertical space.
+.Sh MACRO SYNTAX
+The syntax of a macro depends on its classification.
+In this section,
+.Sq \-arg
+refers to macro arguments, which may be followed by zero or more
+.Sq parm
+parameters;
+.Sq \&Yo
+opens the scope of a macro; and if specified,
+.Sq \&Yc
+closes it out.
+.Pp
+The
+.Em Callable
+column indicates that the macro may also be called by passing its name
+as an argument to another macro.
+For example,
+.Sq \&.Op \&Fl O \&Ar file
+produces
+.Sq Op Fl O Ar file .
+To prevent a macro call and render the macro name literally,
+escape it by prepending a zero-width space,
+.Sq \e& .
+For example,
+.Sq \&Op \e&Fl O
+produces
+.Sq Op \&Fl O .
+If a macro is not callable but its name appears as an argument
+to another macro, it is interpreted as opaque text.
+For example,
+.Sq \&.Fl \&Sh
+produces
+.Sq Fl \&Sh .
+.Pp
+The
+.Em Parsed
+column indicates whether the macro may call other macros by receiving
+their names as arguments.
+If a macro is not parsed but the name of another macro appears
+as an argument, it is interpreted as opaque text.
+.Pp
+The
+.Em Scope
+column, if applicable, describes closure rules.
+.Ss Block full-explicit
+Multi-line scope closed by an explicit closing macro.
+All macros contains bodies; only
+.Sx \&Bf
+and
+.Pq optionally
+.Sx \&Bl
+contain a head.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB
+\(lBbody...\(rB
+\&.Yc
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&Bd Ta \&No Ta \&No Ta closed by Sx \&Ed
+.It Sx \&Bf Ta \&No Ta \&No Ta closed by Sx \&Ef
+.It Sx \&Bk Ta \&No Ta \&No Ta closed by Sx \&Ek
+.It Sx \&Bl Ta \&No Ta \&No Ta closed by Sx \&El
+.It Sx \&Ed Ta \&No Ta \&No Ta opened by Sx \&Bd
+.It Sx \&Ef Ta \&No Ta \&No Ta opened by Sx \&Bf
+.It Sx \&Ek Ta \&No Ta \&No Ta opened by Sx \&Bk
+.It Sx \&El Ta \&No Ta \&No Ta opened by Sx \&Bl
+.El
+.Ss Block full-implicit
+Multi-line scope closed by end-of-file or implicitly by another macro.
+All macros have bodies; some
+.Po
+.Sx \&It Fl bullet ,
+.Fl hyphen ,
+.Fl dash ,
+.Fl enum ,
+.Fl item
+.Pc
+don't have heads; only one
+.Po
+.Sx \&It
+in
+.Sx \&Bl Fl column
+.Pc
+has multiple heads.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB
+\(lBbody...\(rB
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXXXXXXXXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&It Ta \&No Ta Yes Ta closed by Sx \&It , Sx \&El
+.It Sx \&Nd Ta \&No Ta \&No Ta closed by Sx \&Sh
+.It Sx \&Nm Ta \&No Ta Yes Ta closed by Sx \&Nm , Sx \&Sh , Sx \&Ss
+.It Sx \&Sh Ta \&No Ta Yes Ta closed by Sx \&Sh
+.It Sx \&Ss Ta \&No Ta Yes Ta closed by Sx \&Sh , Sx \&Ss
+.El
+.Pp
+Note that the
+.Sx \&Nm
+macro is a
+.Sx Block full-implicit
+macro only when invoked as the first macro
+in a
+.Em SYNOPSIS
+section line, else it is
+.Sx In-line .
+.Ss Block partial-explicit
+Like block full-explicit, but also with single-line scope.
+Each has at least a body and, in limited circumstances, a head
+.Po
+.Sx \&Fo ,
+.Sx \&Eo
+.Pc
+and/or tail
+.Pq Sx \&Ec .
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB
+\(lBbody...\(rB
+\&.Yc \(lBtail...\(rB
+
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \
+\(lBbody...\(rB \&Yc \(lBtail...\(rB
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&Ac Ta Yes Ta Yes Ta opened by Sx \&Ao
+.It Sx \&Ao Ta Yes Ta Yes Ta closed by Sx \&Ac
+.It Sx \&Bc Ta Yes Ta Yes Ta closed by Sx \&Bo
+.It Sx \&Bo Ta Yes Ta Yes Ta opened by Sx \&Bc
+.It Sx \&Brc Ta Yes Ta Yes Ta opened by Sx \&Bro
+.It Sx \&Bro Ta Yes Ta Yes Ta closed by Sx \&Brc
+.It Sx \&Dc Ta Yes Ta Yes Ta opened by Sx \&Do
+.It Sx \&Do Ta Yes Ta Yes Ta closed by Sx \&Dc
+.It Sx \&Ec Ta Yes Ta Yes Ta opened by Sx \&Eo
+.It Sx \&Eo Ta Yes Ta Yes Ta closed by Sx \&Ec
+.It Sx \&Fc Ta Yes Ta Yes Ta opened by Sx \&Fo
+.It Sx \&Fo Ta \&No Ta \&No Ta closed by Sx \&Fc
+.It Sx \&Oc Ta Yes Ta Yes Ta closed by Sx \&Oo
+.It Sx \&Oo Ta Yes Ta Yes Ta opened by Sx \&Oc
+.It Sx \&Pc Ta Yes Ta Yes Ta closed by Sx \&Po
+.It Sx \&Po Ta Yes Ta Yes Ta opened by Sx \&Pc
+.It Sx \&Qc Ta Yes Ta Yes Ta opened by Sx \&Oo
+.It Sx \&Qo Ta Yes Ta Yes Ta closed by Sx \&Oc
+.It Sx \&Re Ta \&No Ta \&No Ta opened by Sx \&Rs
+.It Sx \&Rs Ta \&No Ta \&No Ta closed by Sx \&Re
+.It Sx \&Sc Ta Yes Ta Yes Ta opened by Sx \&So
+.It Sx \&So Ta Yes Ta Yes Ta closed by Sx \&Sc
+.It Sx \&Xc Ta Yes Ta Yes Ta opened by Sx \&Xo
+.It Sx \&Xo Ta Yes Ta Yes Ta closed by Sx \&Xc
+.El
+.Ss Block partial-implicit
+Like block full-implicit, but with single-line scope closed by the
+end of the line.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed
+.It Sx \&Aq Ta Yes Ta Yes
+.It Sx \&Bq Ta Yes Ta Yes
+.It Sx \&Brq Ta Yes Ta Yes
+.It Sx \&D1 Ta \&No Ta \&Yes
+.It Sx \&Dl Ta \&No Ta Yes
+.It Sx \&Dq Ta Yes Ta Yes
+.It Sx \&En Ta Yes Ta Yes
+.It Sx \&Op Ta Yes Ta Yes
+.It Sx \&Pq Ta Yes Ta Yes
+.It Sx \&Ql Ta Yes Ta Yes
+.It Sx \&Qq Ta Yes Ta Yes
+.It Sx \&Sq Ta Yes Ta Yes
+.It Sx \&Vt Ta Yes Ta Yes
+.El
+.Pp
+Note that the
+.Sx \&Vt
+macro is a
+.Sx Block partial-implicit
+only when invoked as the first macro
+in a
+.Em SYNOPSIS
+section line, else it is
+.Sx In-line .
+.Ss Special block macro
+The
+.Sx \&Ta
+macro can only be used below
+.Sx \&It
+in
+.Sx \&Bl Fl column
+lists.
+It delimits blocks representing table cells;
+these blocks have bodies, but no heads.
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&Ta Ta Yes Ta Yes Ta closed by Sx \&Ta , Sx \&It
+.El
+.Ss In-line
+Closed by the end of the line, fixed argument lengths,
+and/or subsequent macros.
+In-line macros have only text children.
+If a number (or inequality) of arguments is
+.Pq n ,
+then the macro accepts an arbitrary number of arguments.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB \(lBres...\(rB
+
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB Yc...
+
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "Arguments" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Arguments
+.It Sx \&%A Ta \&No Ta \&No Ta >0
+.It Sx \&%B Ta \&No Ta \&No Ta >0
+.It Sx \&%C Ta \&No Ta \&No Ta >0
+.It Sx \&%D Ta \&No Ta \&No Ta >0
+.It Sx \&%I Ta \&No Ta \&No Ta >0
+.It Sx \&%J Ta \&No Ta \&No Ta >0
+.It Sx \&%N Ta \&No Ta \&No Ta >0
+.It Sx \&%O Ta \&No Ta \&No Ta >0
+.It Sx \&%P Ta \&No Ta \&No Ta >0
+.It Sx \&%Q Ta \&No Ta \&No Ta >0
+.It Sx \&%R Ta \&No Ta \&No Ta >0
+.It Sx \&%T Ta \&No Ta \&No Ta >0
+.It Sx \&%U Ta \&No Ta \&No Ta >0
+.It Sx \&%V Ta \&No Ta \&No Ta >0
+.It Sx \&Ad Ta Yes Ta Yes Ta >0
+.It Sx \&An Ta Yes Ta Yes Ta >0
+.It Sx \&Ap Ta Yes Ta Yes Ta 0
+.It Sx \&Ar Ta Yes Ta Yes Ta n
+.It Sx \&At Ta Yes Ta Yes Ta 1
+.It Sx \&Bsx Ta Yes Ta Yes Ta n
+.It Sx \&Bt Ta \&No Ta \&No Ta 0
+.It Sx \&Bx Ta Yes Ta Yes Ta n
+.It Sx \&Cd Ta Yes Ta Yes Ta >0
+.It Sx \&Cm Ta Yes Ta Yes Ta >0
+.It Sx \&Db Ta \&No Ta \&No Ta 1
+.It Sx \&Dd Ta \&No Ta \&No Ta n
+.It Sx \&Dt Ta \&No Ta \&No Ta n
+.It Sx \&Dv Ta Yes Ta Yes Ta >0
+.It Sx \&Dx Ta Yes Ta Yes Ta n
+.It Sx \&Em Ta Yes Ta Yes Ta >0
+.It Sx \&Er Ta Yes Ta Yes Ta >0
+.It Sx \&Es Ta Yes Ta Yes Ta 2
+.It Sx \&Ev Ta Yes Ta Yes Ta >0
+.It Sx \&Ex Ta \&No Ta \&No Ta n
+.It Sx \&Fa Ta Yes Ta Yes Ta >0
+.It Sx \&Fd Ta \&No Ta \&No Ta >0
+.It Sx \&Fl Ta Yes Ta Yes Ta n
+.It Sx \&Fn Ta Yes Ta Yes Ta >0
+.It Sx \&Fr Ta Yes Ta Yes Ta >0
+.It Sx \&Ft Ta Yes Ta Yes Ta >0
+.It Sx \&Fx Ta Yes Ta Yes Ta n
+.It Sx \&Hf Ta \&No Ta \&No Ta n
+.It Sx \&Ic Ta Yes Ta Yes Ta >0
+.It Sx \&In Ta \&No Ta \&No Ta 1
+.It Sx \&Lb Ta \&No Ta \&No Ta 1
+.It Sx \&Li Ta Yes Ta Yes Ta >0
+.It Sx \&Lk Ta Yes Ta Yes Ta >0
+.It Sx \&Lp Ta \&No Ta \&No Ta 0
+.It Sx \&Ms Ta Yes Ta Yes Ta >0
+.It Sx \&Mt Ta Yes Ta Yes Ta >0
+.It Sx \&Nm Ta Yes Ta Yes Ta n
+.It Sx \&No Ta Yes Ta Yes Ta 0
+.It Sx \&Ns Ta Yes Ta Yes Ta 0
+.It Sx \&Nx Ta Yes Ta Yes Ta n
+.It Sx \&Os Ta \&No Ta \&No Ta n
+.It Sx \&Ot Ta Yes Ta Yes Ta >0
+.It Sx \&Ox Ta Yes Ta Yes Ta n
+.It Sx \&Pa Ta Yes Ta Yes Ta n
+.It Sx \&Pf Ta Yes Ta Yes Ta 1
+.It Sx \&Pp Ta \&No Ta \&No Ta 0
+.It Sx \&Rv Ta \&No Ta \&No Ta n
+.It Sx \&Sm Ta \&No Ta \&No Ta <2
+.It Sx \&St Ta \&No Ta Yes Ta 1
+.It Sx \&Sx Ta Yes Ta Yes Ta >0
+.It Sx \&Sy Ta Yes Ta Yes Ta >0
+.It Sx \&Tn Ta Yes Ta Yes Ta >0
+.It Sx \&Ud Ta \&No Ta \&No Ta 0
+.It Sx \&Ux Ta Yes Ta Yes Ta n
+.It Sx \&Va Ta Yes Ta Yes Ta n
+.It Sx \&Vt Ta Yes Ta Yes Ta >0
+.It Sx \&Xr Ta Yes Ta Yes Ta >0
+.It Sx \&br Ta \&No Ta \&No Ta 0
+.It Sx \&sp Ta \&No Ta \&No Ta 1
+.El
+.Ss Delimiters
+When a macro argument consists of one single input character
+considered as a delimiter, the argument gets special handling.
+This does not apply when delimiters appear in arguments containing
+more than one character.
+Consequently, to prevent special handling and just handle it
+like any other argument, a delimiter can be escaped by prepending
+a zero-width space
+.Pq Sq \e& .
+In text lines, delimiters never need escaping, but may be used
+as normal punctuation.
+.Pp
+For many macros, when the leading arguments are opening delimiters,
+these delimiters are put before the macro scope,
+and when the trailing arguments are closing delimiters,
+these delimiters are put after the macro scope.
+For example,
+.Pp
+.D1 Pf \. \&Aq "( [ word ] ) ."
+.Pp
+renders as:
+.Pp
+.D1 Aq ( [ word ] ) .
+.Pp
+Opening delimiters are:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It \&(
+left parenthesis
+.It \&[
+left bracket
+.El
+.Pp
+Closing delimiters are:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It \&.
+period
+.It \&,
+comma
+.It \&:
+colon
+.It \&;
+semicolon
+.It \&)
+right parenthesis
+.It \&]
+right bracket
+.It \&?
+question mark
+.It \&!
+exclamation mark
+.El
+.Pp
+Note that even a period preceded by a backslash
+.Pq Sq \e.\&
+gets this special handling; use
+.Sq \e&.
+to prevent that.
+.Pp
+Many in-line macros interrupt their scope when they encounter
+delimiters, and resume their scope when more arguments follow that
+are not delimiters.
+For example,
+.Pp
+.D1 Pf \. \&Fl "a ( b | c \e*(Ba d ) e"
+.Pp
+renders as:
+.Pp
+.D1 Fl a ( b | c \*(Ba d ) e
+.Pp
+This applies to both opening and closing delimiters,
+and also to the middle delimiter:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It \&|
+vertical bar
+.El
+.Pp
+As a special case, the predefined string \e*(Ba is handled and rendered
+in the same way as a plain
+.Sq \&|
+character.
+Using this predefined string is not recommended in new manuals.
+.Ss Font handling
+In
+.Nm
+documents, usage of semantic markup is recommended in order to have
+proper fonts automatically selected; only when no fitting semantic markup
+is available, consider falling back to
+.Sx Physical markup
+macros.
+Whenever any
+.Nm
+macro switches the
+.Xr roff 7
+font mode, it will automatically restore the previous font when exiting
+its scope.
+Manually switching the font using the
+.Xr roff 7
+.Ql \ef
+font escape sequences is never required.
+.Sh COMPATIBILITY
+This section provides an incomplete list of compatibility issues
+between mandoc and other troff implementations, at this time limited
+to GNU troff
+.Pq Qq groff .
+The term
+.Qq historic groff
+refers to groff versions before 1.17,
+which featured a significant update of the
+.Pa doc.tmac
+file.
+.Pp
+Heirloom troff, the other significant troff implementation accepting
+\-mdoc, is similar to historic groff.
+.Pp
+The following problematic behaviour is found in groff:
+.ds hist (Historic groff only.)
+.Pp
+.Bl -dash -compact
+.It
+Display macros
+.Po
+.Sx \&Bd ,
+.Sx \&Dl ,
+and
+.Sx \&D1
+.Pc
+may not be nested.
+\*[hist]
+.It
+.Sx \&At
+with unknown arguments produces no output at all.
+\*[hist]
+Newer groff and mandoc print
+.Qq AT&T UNIX
+and the arguments.
+.It
+.Sx \&Bl Fl column
+does not recognise trailing punctuation characters when they immediately
+precede tabulator characters, but treats them as normal text and
+outputs a space before them.
+.It
+.Sx \&Bd Fl ragged compact
+does not start a new line.
+\*[hist]
+.It
+.Sx \&Dd
+with non-standard arguments behaves very strangely.
+When there are three arguments, they are printed verbatim.
+Any other number of arguments is replaced by the current date,
+but without any arguments the string
+.Dq Epoch
+is printed.
+.It
+.Sx \&Fl
+does not print a dash for an empty argument.
+\*[hist]
+.It
+.Sx \&Fn
+does not start a new line unless invoked as the line macro in the
+.Em SYNOPSIS
+section.
+\*[hist]
+.It
+.Sx \&Fo
+with
+.Pf non- Sx \&Fa
+children causes inconsistent spacing between arguments.
+In mandoc, a single space is always inserted between arguments.
+.It
+.Sx \&Ft
+in the
+.Em SYNOPSIS
+causes inconsistent vertical spacing, depending on whether a prior
+.Sx \&Fn
+has been invoked.
+See
+.Sx \&Ft
+and
+.Sx \&Fn
+for the normalised behaviour in mandoc.
+.It
+.Sx \&In
+ignores additional arguments and is not treated specially in the
+.Em SYNOPSIS .
+\*[hist]
+.It
+.Sx \&It
+sometimes requires a
+.Fl nested
+flag.
+\*[hist]
+In new groff and mandoc, any list may be nested by default and
+.Fl enum
+lists will restart the sequence only for the sub-list.
+.It
+.Sx \&Li
+followed by a delimiter is incorrectly used in some manuals
+instead of properly quoting that character, which sometimes works with
+historic groff.
+.It
+.Sx \&Lk
+only accepts a single link-name argument; the remainder is misformatted.
+.It
+.Sx \&Pa
+does not format its arguments when used in the FILES section under
+certain list types.
+.It
+.Sx \&Ta
+can only be called by other macros, but not at the beginning of a line.
+.It
+.Sx \&%C
+is not implemented (up to and including groff-1.22.2).
+.It
+Historic groff only allows up to eight or nine arguments per macro input
+line, depending on the exact situation.
+Providing more arguments causes garbled output.
+The number of arguments on one input line is not limited with mandoc.
+.It
+Historic groff has many un-callable macros.
+Most of these (excluding some block-level macros) are callable
+in new groff and mandoc.
+.It
+.Sq \(ba
+(vertical bar) is not fully supported as a delimiter.
+\*[hist]
+.It
+.Sq \ef
+.Pq font face
+and
+.Sq \eF
+.Pq font family face
+.Sx Text Decoration
+escapes behave irregularly when specified within line-macro scopes.
+.It
+Negative scaling units return to prior lines.
+Instead, mandoc truncates them to zero.
+.El
+.Pp
+The following features are unimplemented in mandoc:
+.Pp
+.Bl -dash -compact
+.It
+.Sx \&Bd
+.Fl file Ar file .
+.It
+.Sx \&Bd
+.Fl offset Cm center
+and
+.Fl offset Cm right .
+Groff does not implement centred and flush-right rendering either,
+but produces large indentations.
+.El
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr mandoc 1 ,
+.Xr eqn 7 ,
+.Xr man 7 ,
+.Xr mandoc_char 7 ,
+.Xr roff 7 ,
+.Xr tbl 7
+.Sh HISTORY
+The
+.Nm
+language first appeared as a troff macro package in
+.Bx 4.4 .
+It was later significantly updated by Werner Lemberg and Ruslan Ermilov
+in groff-1.17.
+The standalone implementation that is part of the
+.Xr mandoc 1
+utility written by Kristaps Dzonsons appeared in
+.Ox 4.6 .
+.Sh AUTHORS
+The
+.Nm
+reference was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
diff --git a/contrib/mdocml/mdoc.c b/contrib/mdocml/mdoc.c
new file mode 100644
index 0000000..e8143bf
--- /dev/null
+++ b/contrib/mdocml/mdoc.c
@@ -0,0 +1,981 @@
+/* $Id: mdoc.c,v 1.223 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+const char *const __mdoc_macronames[MDOC_MAX + 1] = {
+ "Ap", "Dd", "Dt", "Os",
+ "Sh", "Ss", "Pp", "D1",
+ "Dl", "Bd", "Ed", "Bl",
+ "El", "It", "Ad", "An",
+ "Ar", "Cd", "Cm", "Dv",
+ "Er", "Ev", "Ex", "Fa",
+ "Fd", "Fl", "Fn", "Ft",
+ "Ic", "In", "Li", "Nd",
+ "Nm", "Op", "Ot", "Pa",
+ "Rv", "St", "Va", "Vt",
+ "Xr", "%A", "%B", "%D",
+ "%I", "%J", "%N", "%O",
+ "%P", "%R", "%T", "%V",
+ "Ac", "Ao", "Aq", "At",
+ "Bc", "Bf", "Bo", "Bq",
+ "Bsx", "Bx", "Db", "Dc",
+ "Do", "Dq", "Ec", "Ef",
+ "Em", "Eo", "Fx", "Ms",
+ "No", "Ns", "Nx", "Ox",
+ "Pc", "Pf", "Po", "Pq",
+ "Qc", "Ql", "Qo", "Qq",
+ "Re", "Rs", "Sc", "So",
+ "Sq", "Sm", "Sx", "Sy",
+ "Tn", "Ux", "Xc", "Xo",
+ "Fo", "Fc", "Oo", "Oc",
+ "Bk", "Ek", "Bt", "Hf",
+ "Fr", "Ud", "Lb", "Lp",
+ "Lk", "Mt", "Brq", "Bro",
+ "Brc", "%C", "Es", "En",
+ "Dx", "%Q", "br", "sp",
+ "%U", "Ta", "ll", "text",
+ };
+
+const char *const __mdoc_argnames[MDOC_ARG_MAX] = {
+ "split", "nosplit", "ragged",
+ "unfilled", "literal", "file",
+ "offset", "bullet", "dash",
+ "hyphen", "item", "enum",
+ "tag", "diag", "hang",
+ "ohang", "inset", "column",
+ "width", "compact", "std",
+ "filled", "words", "emphasis",
+ "symbolic", "nested", "centered"
+ };
+
+const char * const *mdoc_macronames = __mdoc_macronames;
+const char * const *mdoc_argnames = __mdoc_argnames;
+
+static void mdoc_node_free(struct mdoc_node *);
+static void mdoc_node_unlink(struct mdoc *,
+ struct mdoc_node *);
+static void mdoc_free1(struct mdoc *);
+static void mdoc_alloc1(struct mdoc *);
+static struct mdoc_node *node_alloc(struct mdoc *, int, int,
+ enum mdoct, enum mdoc_type);
+static int node_append(struct mdoc *,
+ struct mdoc_node *);
+#if 0
+static int mdoc_preptext(struct mdoc *, int, char *, int);
+#endif
+static int mdoc_ptext(struct mdoc *, int, char *, int);
+static int mdoc_pmacro(struct mdoc *, int, char *, int);
+
+
+const struct mdoc_node *
+mdoc_node(const struct mdoc *mdoc)
+{
+
+ return(mdoc->first);
+}
+
+const struct mdoc_meta *
+mdoc_meta(const struct mdoc *mdoc)
+{
+
+ return(&mdoc->meta);
+}
+
+/*
+ * Frees volatile resources (parse tree, meta-data, fields).
+ */
+static void
+mdoc_free1(struct mdoc *mdoc)
+{
+
+ if (mdoc->first)
+ mdoc_node_delete(mdoc, mdoc->first);
+ free(mdoc->meta.msec);
+ free(mdoc->meta.vol);
+ free(mdoc->meta.arch);
+ free(mdoc->meta.date);
+ free(mdoc->meta.title);
+ free(mdoc->meta.os);
+ free(mdoc->meta.name);
+}
+
+/*
+ * Allocate all volatile resources (parse tree, meta-data, fields).
+ */
+static void
+mdoc_alloc1(struct mdoc *mdoc)
+{
+
+ memset(&mdoc->meta, 0, sizeof(struct mdoc_meta));
+ mdoc->flags = 0;
+ mdoc->lastnamed = mdoc->lastsec = SEC_NONE;
+ mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node));
+ mdoc->first = mdoc->last;
+ mdoc->last->type = MDOC_ROOT;
+ mdoc->last->tok = MDOC_MAX;
+ mdoc->next = MDOC_NEXT_CHILD;
+}
+
+/*
+ * Free up volatile resources (see mdoc_free1()) then re-initialises the
+ * data with mdoc_alloc1(). After invocation, parse data has been reset
+ * and the parser is ready for re-invocation on a new tree; however,
+ * cross-parse non-volatile data is kept intact.
+ */
+void
+mdoc_reset(struct mdoc *mdoc)
+{
+
+ mdoc_free1(mdoc);
+ mdoc_alloc1(mdoc);
+}
+
+/*
+ * Completely free up all volatile and non-volatile parse resources.
+ * After invocation, the pointer is no longer usable.
+ */
+void
+mdoc_free(struct mdoc *mdoc)
+{
+
+ mdoc_free1(mdoc);
+ free(mdoc);
+}
+
+/*
+ * Allocate volatile and non-volatile parse resources.
+ */
+struct mdoc *
+mdoc_alloc(struct roff *roff, struct mparse *parse,
+ const char *defos, int quick)
+{
+ struct mdoc *p;
+
+ p = mandoc_calloc(1, sizeof(struct mdoc));
+
+ p->parse = parse;
+ p->defos = defos;
+ p->quick = quick;
+ p->roff = roff;
+
+ mdoc_hash_init();
+ mdoc_alloc1(p);
+ return(p);
+}
+
+int
+mdoc_endparse(struct mdoc *mdoc)
+{
+
+ return(mdoc_macroend(mdoc));
+}
+
+int
+mdoc_addeqn(struct mdoc *mdoc, const struct eqn *ep)
+{
+ struct mdoc_node *n;
+
+ n = node_alloc(mdoc, ep->ln, ep->pos, MDOC_MAX, MDOC_EQN);
+ n->eqn = ep;
+
+ if ( ! node_append(mdoc, n))
+ return(0);
+
+ mdoc->next = MDOC_NEXT_SIBLING;
+ return(1);
+}
+
+int
+mdoc_addspan(struct mdoc *mdoc, const struct tbl_span *sp)
+{
+ struct mdoc_node *n;
+
+ n = node_alloc(mdoc, sp->line, 0, MDOC_MAX, MDOC_TBL);
+ n->span = sp;
+
+ if ( ! node_append(mdoc, n))
+ return(0);
+
+ mdoc->next = MDOC_NEXT_SIBLING;
+ return(1);
+}
+
+/*
+ * Main parse routine. Parses a single line -- really just hands off to
+ * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()).
+ */
+int
+mdoc_parseln(struct mdoc *mdoc, int ln, char *buf, int offs)
+{
+
+ mdoc->flags |= MDOC_NEWLINE;
+
+ /*
+ * Let the roff nS register switch SYNOPSIS mode early,
+ * such that the parser knows at all times
+ * whether this mode is on or off.
+ * Note that this mode is also switched by the Sh macro.
+ */
+ if (roff_getreg(mdoc->roff, "nS"))
+ mdoc->flags |= MDOC_SYNOPSIS;
+ else
+ mdoc->flags &= ~MDOC_SYNOPSIS;
+
+ return(roff_getcontrol(mdoc->roff, buf, &offs) ?
+ mdoc_pmacro(mdoc, ln, buf, offs) :
+ mdoc_ptext(mdoc, ln, buf, offs));
+}
+
+int
+mdoc_macro(MACRO_PROT_ARGS)
+{
+ assert(tok < MDOC_MAX);
+
+ if (mdoc->flags & MDOC_PBODY) {
+ if (tok == MDOC_Dt) {
+ mandoc_vmsg(MANDOCERR_DT_LATE,
+ mdoc->parse, line, ppos,
+ "Dt %s", buf + *pos);
+ return(1);
+ }
+ } else if ( ! (mdoc_macros[tok].flags & MDOC_PROLOGUE)) {
+ if (mdoc->meta.title == NULL) {
+ mandoc_vmsg(MANDOCERR_DT_NOTITLE,
+ mdoc->parse, line, ppos, "%s %s",
+ mdoc_macronames[tok], buf + *pos);
+ mdoc->meta.title = mandoc_strdup("UNTITLED");
+ }
+ if (NULL == mdoc->meta.vol)
+ mdoc->meta.vol = mandoc_strdup("LOCAL");
+ mdoc->flags |= MDOC_PBODY;
+ }
+
+ return((*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf));
+}
+
+
+static int
+node_append(struct mdoc *mdoc, struct mdoc_node *p)
+{
+
+ assert(mdoc->last);
+ assert(mdoc->first);
+ assert(MDOC_ROOT != p->type);
+
+ switch (mdoc->next) {
+ case MDOC_NEXT_SIBLING:
+ mdoc->last->next = p;
+ p->prev = mdoc->last;
+ p->parent = mdoc->last->parent;
+ break;
+ case MDOC_NEXT_CHILD:
+ mdoc->last->child = p;
+ p->parent = mdoc->last;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ p->parent->nchild++;
+
+ /*
+ * Copy over the normalised-data pointer of our parent. Not
+ * everybody has one, but copying a null pointer is fine.
+ */
+
+ switch (p->type) {
+ case MDOC_BODY:
+ if (ENDBODY_NOT != p->end)
+ break;
+ /* FALLTHROUGH */
+ case MDOC_TAIL:
+ /* FALLTHROUGH */
+ case MDOC_HEAD:
+ p->norm = p->parent->norm;
+ break;
+ default:
+ break;
+ }
+
+ if ( ! mdoc_valid_pre(mdoc, p))
+ return(0);
+
+ switch (p->type) {
+ case MDOC_HEAD:
+ assert(MDOC_BLOCK == p->parent->type);
+ p->parent->head = p;
+ break;
+ case MDOC_TAIL:
+ assert(MDOC_BLOCK == p->parent->type);
+ p->parent->tail = p;
+ break;
+ case MDOC_BODY:
+ if (p->end)
+ break;
+ assert(MDOC_BLOCK == p->parent->type);
+ p->parent->body = p;
+ break;
+ default:
+ break;
+ }
+
+ mdoc->last = p;
+
+ switch (p->type) {
+ case MDOC_TBL:
+ /* FALLTHROUGH */
+ case MDOC_TEXT:
+ if ( ! mdoc_valid_post(mdoc))
+ return(0);
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static struct mdoc_node *
+node_alloc(struct mdoc *mdoc, int line, int pos,
+ enum mdoct tok, enum mdoc_type type)
+{
+ struct mdoc_node *p;
+
+ p = mandoc_calloc(1, sizeof(struct mdoc_node));
+ p->sec = mdoc->lastsec;
+ p->line = line;
+ p->pos = pos;
+ p->lastline = line;
+ p->tok = tok;
+ p->type = type;
+
+ /* Flag analysis. */
+
+ if (MDOC_SYNOPSIS & mdoc->flags)
+ p->flags |= MDOC_SYNPRETTY;
+ else
+ p->flags &= ~MDOC_SYNPRETTY;
+ if (MDOC_NEWLINE & mdoc->flags)
+ p->flags |= MDOC_LINE;
+ mdoc->flags &= ~MDOC_NEWLINE;
+
+ return(p);
+}
+
+int
+mdoc_tail_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok)
+{
+ struct mdoc_node *p;
+
+ p = node_alloc(mdoc, line, pos, tok, MDOC_TAIL);
+ if ( ! node_append(mdoc, p))
+ return(0);
+ mdoc->next = MDOC_NEXT_CHILD;
+ return(1);
+}
+
+int
+mdoc_head_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok)
+{
+ struct mdoc_node *p;
+
+ assert(mdoc->first);
+ assert(mdoc->last);
+
+ p = node_alloc(mdoc, line, pos, tok, MDOC_HEAD);
+ if ( ! node_append(mdoc, p))
+ return(0);
+ mdoc->next = MDOC_NEXT_CHILD;
+ return(1);
+}
+
+int
+mdoc_body_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok)
+{
+ struct mdoc_node *p;
+
+ p = node_alloc(mdoc, line, pos, tok, MDOC_BODY);
+ if ( ! node_append(mdoc, p))
+ return(0);
+ mdoc->next = MDOC_NEXT_CHILD;
+ return(1);
+}
+
+int
+mdoc_endbody_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok,
+ struct mdoc_node *body, enum mdoc_endbody end)
+{
+ struct mdoc_node *p;
+
+ p = node_alloc(mdoc, line, pos, tok, MDOC_BODY);
+ p->pending = body;
+ p->norm = body->norm;
+ p->end = end;
+ if ( ! node_append(mdoc, p))
+ return(0);
+ mdoc->next = MDOC_NEXT_SIBLING;
+ return(1);
+}
+
+int
+mdoc_block_alloc(struct mdoc *mdoc, int line, int pos,
+ enum mdoct tok, struct mdoc_arg *args)
+{
+ struct mdoc_node *p;
+
+ p = node_alloc(mdoc, line, pos, tok, MDOC_BLOCK);
+ p->args = args;
+ if (p->args)
+ (args->refcnt)++;
+
+ switch (tok) {
+ case MDOC_Bd:
+ /* FALLTHROUGH */
+ case MDOC_Bf:
+ /* FALLTHROUGH */
+ case MDOC_Bl:
+ /* FALLTHROUGH */
+ case MDOC_En:
+ /* FALLTHROUGH */
+ case MDOC_Rs:
+ p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
+ break;
+ default:
+ break;
+ }
+
+ if ( ! node_append(mdoc, p))
+ return(0);
+ mdoc->next = MDOC_NEXT_CHILD;
+ return(1);
+}
+
+int
+mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos,
+ enum mdoct tok, struct mdoc_arg *args)
+{
+ struct mdoc_node *p;
+
+ p = node_alloc(mdoc, line, pos, tok, MDOC_ELEM);
+ p->args = args;
+ if (p->args)
+ (args->refcnt)++;
+
+ switch (tok) {
+ case MDOC_An:
+ p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
+ break;
+ default:
+ break;
+ }
+
+ if ( ! node_append(mdoc, p))
+ return(0);
+ mdoc->next = MDOC_NEXT_CHILD;
+ return(1);
+}
+
+int
+mdoc_word_alloc(struct mdoc *mdoc, int line, int pos, const char *p)
+{
+ struct mdoc_node *n;
+
+ n = node_alloc(mdoc, line, pos, MDOC_MAX, MDOC_TEXT);
+ n->string = roff_strdup(mdoc->roff, p);
+
+ if ( ! node_append(mdoc, n))
+ return(0);
+
+ mdoc->next = MDOC_NEXT_SIBLING;
+ return(1);
+}
+
+void
+mdoc_word_append(struct mdoc *mdoc, const char *p)
+{
+ struct mdoc_node *n;
+ char *addstr, *newstr;
+
+ n = mdoc->last;
+ addstr = roff_strdup(mdoc->roff, p);
+ mandoc_asprintf(&newstr, "%s %s", n->string, addstr);
+ free(addstr);
+ free(n->string);
+ n->string = newstr;
+ mdoc->next = MDOC_NEXT_SIBLING;
+}
+
+static void
+mdoc_node_free(struct mdoc_node *p)
+{
+
+ if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type)
+ free(p->norm);
+ if (p->string)
+ free(p->string);
+ if (p->args)
+ mdoc_argv_free(p->args);
+ free(p);
+}
+
+static void
+mdoc_node_unlink(struct mdoc *mdoc, struct mdoc_node *n)
+{
+
+ /* Adjust siblings. */
+
+ if (n->prev)
+ n->prev->next = n->next;
+ if (n->next)
+ n->next->prev = n->prev;
+
+ /* Adjust parent. */
+
+ if (n->parent) {
+ n->parent->nchild--;
+ if (n->parent->child == n)
+ n->parent->child = n->prev ? n->prev : n->next;
+ if (n->parent->last == n)
+ n->parent->last = n->prev ? n->prev : NULL;
+ }
+
+ /* Adjust parse point, if applicable. */
+
+ if (mdoc && mdoc->last == n) {
+ if (n->prev) {
+ mdoc->last = n->prev;
+ mdoc->next = MDOC_NEXT_SIBLING;
+ } else {
+ mdoc->last = n->parent;
+ mdoc->next = MDOC_NEXT_CHILD;
+ }
+ }
+
+ if (mdoc && mdoc->first == n)
+ mdoc->first = NULL;
+}
+
+void
+mdoc_node_delete(struct mdoc *mdoc, struct mdoc_node *p)
+{
+
+ while (p->child) {
+ assert(p->nchild);
+ mdoc_node_delete(mdoc, p->child);
+ }
+ assert(0 == p->nchild);
+
+ mdoc_node_unlink(mdoc, p);
+ mdoc_node_free(p);
+}
+
+int
+mdoc_node_relink(struct mdoc *mdoc, struct mdoc_node *p)
+{
+
+ mdoc_node_unlink(mdoc, p);
+ return(node_append(mdoc, p));
+}
+
+#if 0
+/*
+ * Pre-treat a text line.
+ * Text lines can consist of equations, which must be handled apart from
+ * the regular text.
+ * Thus, use this function to step through a line checking if it has any
+ * equations embedded in it.
+ * This must handle multiple equations AND equations that do not end at
+ * the end-of-line, i.e., will re-enter in the next roff parse.
+ */
+static int
+mdoc_preptext(struct mdoc *mdoc, int line, char *buf, int offs)
+{
+ char *start, *end;
+ char delim;
+
+ while ('\0' != buf[offs]) {
+ /* Mark starting position if eqn is set. */
+ start = NULL;
+ if ('\0' != (delim = roff_eqndelim(mdoc->roff)))
+ if (NULL != (start = strchr(buf + offs, delim)))
+ *start++ = '\0';
+
+ /* Parse text as normal. */
+ if ( ! mdoc_ptext(mdoc, line, buf, offs))
+ return(0);
+
+ /* Continue only if an equation exists. */
+ if (NULL == start)
+ break;
+
+ /* Read past the end of the equation. */
+ offs += start - (buf + offs);
+ assert(start == &buf[offs]);
+ if (NULL != (end = strchr(buf + offs, delim))) {
+ *end++ = '\0';
+ while (' ' == *end)
+ end++;
+ }
+
+ /* Parse the equation itself. */
+ roff_openeqn(mdoc->roff, NULL, line, offs, buf);
+
+ /* Process a finished equation? */
+ if (roff_closeeqn(mdoc->roff))
+ if ( ! mdoc_addeqn(mdoc, roff_eqn(mdoc->roff)))
+ return(0);
+ offs += (end - (buf + offs));
+ }
+
+ return(1);
+}
+#endif
+
+/*
+ * Parse free-form text, that is, a line that does not begin with the
+ * control character.
+ */
+static int
+mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs)
+{
+ char *c, *ws, *end;
+ struct mdoc_node *n;
+
+ assert(mdoc->last);
+ n = mdoc->last;
+
+ /*
+ * Divert directly to list processing if we're encountering a
+ * columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry
+ * (a MDOC_BODY means it's already open, in which case we should
+ * process within its context in the normal way).
+ */
+
+ if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
+ LIST_column == n->norm->Bl.type) {
+ /* `Bl' is open without any children. */
+ mdoc->flags |= MDOC_FREECOL;
+ return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf));
+ }
+
+ if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
+ NULL != n->parent &&
+ MDOC_Bl == n->parent->tok &&
+ LIST_column == n->parent->norm->Bl.type) {
+ /* `Bl' has block-level `It' children. */
+ mdoc->flags |= MDOC_FREECOL;
+ return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf));
+ }
+
+ /*
+ * Search for the beginning of unescaped trailing whitespace (ws)
+ * and for the first character not to be output (end).
+ */
+
+ /* FIXME: replace with strcspn(). */
+ ws = NULL;
+ for (c = end = buf + offs; *c; c++) {
+ switch (*c) {
+ case ' ':
+ if (NULL == ws)
+ ws = c;
+ continue;
+ case '\t':
+ /*
+ * Always warn about trailing tabs,
+ * even outside literal context,
+ * where they should be put on the next line.
+ */
+ if (NULL == ws)
+ ws = c;
+ /*
+ * Strip trailing tabs in literal context only;
+ * outside, they affect the next line.
+ */
+ if (MDOC_LITERAL & mdoc->flags)
+ continue;
+ break;
+ case '\\':
+ /* Skip the escaped character, too, if any. */
+ if (c[1])
+ c++;
+ /* FALLTHROUGH */
+ default:
+ ws = NULL;
+ break;
+ }
+ end = c + 1;
+ }
+ *end = '\0';
+
+ if (ws)
+ mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+ line, (int)(ws-buf), NULL);
+
+ if ('\0' == buf[offs] && ! (MDOC_LITERAL & mdoc->flags)) {
+ mandoc_msg(MANDOCERR_FI_BLANK, mdoc->parse,
+ line, (int)(c - buf), NULL);
+
+ /*
+ * Insert a `sp' in the case of a blank line. Technically,
+ * blank lines aren't allowed, but enough manuals assume this
+ * behaviour that we want to work around it.
+ */
+ if ( ! mdoc_elem_alloc(mdoc, line, offs, MDOC_sp, NULL))
+ return(0);
+
+ mdoc->next = MDOC_NEXT_SIBLING;
+
+ return(mdoc_valid_post(mdoc));
+ }
+
+ if ( ! mdoc_word_alloc(mdoc, line, offs, buf+offs))
+ return(0);
+
+ if (MDOC_LITERAL & mdoc->flags)
+ return(1);
+
+ /*
+ * End-of-sentence check. If the last character is an unescaped
+ * EOS character, then flag the node as being the end of a
+ * sentence. The front-end will know how to interpret this.
+ */
+
+ assert(buf < end);
+
+ if (mandoc_eos(buf+offs, (size_t)(end-buf-offs)))
+ mdoc->last->flags |= MDOC_EOS;
+
+ return(1);
+}
+
+/*
+ * Parse a macro line, that is, a line beginning with the control
+ * character.
+ */
+static int
+mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs)
+{
+ enum mdoct tok;
+ int i, sv;
+ char mac[5];
+ struct mdoc_node *n;
+
+ /* Empty post-control lines are ignored. */
+
+ if ('"' == buf[offs]) {
+ mandoc_msg(MANDOCERR_COMMENT_BAD, mdoc->parse,
+ ln, offs, NULL);
+ return(1);
+ } else if ('\0' == buf[offs])
+ return(1);
+
+ sv = offs;
+
+ /*
+ * Copy the first word into a nil-terminated buffer.
+ * Stop copying when a tab, space, or eoln is encountered.
+ */
+
+ i = 0;
+ while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] &&
+ '\t' != buf[offs])
+ mac[i++] = buf[offs++];
+
+ mac[i] = '\0';
+
+ tok = (i > 1 && i < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
+
+ if (MDOC_MAX == tok) {
+ mandoc_msg(MANDOCERR_MACRO, mdoc->parse,
+ ln, sv, buf + sv - 1);
+ return(1);
+ }
+
+ /* Disregard the first trailing tab, if applicable. */
+
+ if ('\t' == buf[offs])
+ offs++;
+
+ /* Jump to the next non-whitespace word. */
+
+ while (buf[offs] && ' ' == buf[offs])
+ offs++;
+
+ /*
+ * Trailing whitespace. Note that tabs are allowed to be passed
+ * into the parser as "text", so we only warn about spaces here.
+ */
+
+ if ('\0' == buf[offs] && ' ' == buf[offs - 1])
+ mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+ ln, offs - 1, NULL);
+
+ /*
+ * If an initial macro or a list invocation, divert directly
+ * into macro processing.
+ */
+
+ if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok)
+ return(mdoc_macro(mdoc, tok, ln, sv, &offs, buf));
+
+ n = mdoc->last;
+ assert(mdoc->last);
+
+ /*
+ * If the first macro of a `Bl -column', open an `It' block
+ * context around the parsed macro.
+ */
+
+ if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
+ LIST_column == n->norm->Bl.type) {
+ mdoc->flags |= MDOC_FREECOL;
+ return(mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf));
+ }
+
+ /*
+ * If we're following a block-level `It' within a `Bl -column'
+ * context (perhaps opened in the above block or in ptext()),
+ * then open an `It' block context around the parsed macro.
+ */
+
+ if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
+ NULL != n->parent &&
+ MDOC_Bl == n->parent->tok &&
+ LIST_column == n->parent->norm->Bl.type) {
+ mdoc->flags |= MDOC_FREECOL;
+ return(mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf));
+ }
+
+ /* Normal processing of a macro. */
+
+ if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf))
+ return(0);
+
+ /* In quick mode (for mandocdb), abort after the NAME section. */
+
+ if (mdoc->quick && MDOC_Sh == tok &&
+ SEC_NAME != mdoc->last->sec)
+ return(2);
+
+ return(1);
+}
+
+enum mdelim
+mdoc_isdelim(const char *p)
+{
+
+ if ('\0' == p[0])
+ return(DELIM_NONE);
+
+ if ('\0' == p[1])
+ switch (p[0]) {
+ case '(':
+ /* FALLTHROUGH */
+ case '[':
+ return(DELIM_OPEN);
+ case '|':
+ return(DELIM_MIDDLE);
+ case '.':
+ /* FALLTHROUGH */
+ case ',':
+ /* FALLTHROUGH */
+ case ';':
+ /* FALLTHROUGH */
+ case ':':
+ /* FALLTHROUGH */
+ case '?':
+ /* FALLTHROUGH */
+ case '!':
+ /* FALLTHROUGH */
+ case ')':
+ /* FALLTHROUGH */
+ case ']':
+ return(DELIM_CLOSE);
+ default:
+ return(DELIM_NONE);
+ }
+
+ if ('\\' != p[0])
+ return(DELIM_NONE);
+
+ if (0 == strcmp(p + 1, "."))
+ return(DELIM_CLOSE);
+ if (0 == strcmp(p + 1, "fR|\\fP"))
+ return(DELIM_MIDDLE);
+
+ return(DELIM_NONE);
+}
+
+void
+mdoc_deroff(char **dest, const struct mdoc_node *n)
+{
+ char *cp;
+ size_t sz;
+
+ if (MDOC_TEXT != n->type) {
+ for (n = n->child; n; n = n->next)
+ mdoc_deroff(dest, n);
+ return;
+ }
+
+ /* Skip leading whitespace. */
+
+ for (cp = n->string; '\0' != *cp; cp++)
+ if (0 == isspace((unsigned char)*cp))
+ break;
+
+ /* Skip trailing whitespace. */
+
+ for (sz = strlen(cp); sz; sz--)
+ if (0 == isspace((unsigned char)cp[sz-1]))
+ break;
+
+ /* Skip empty strings. */
+
+ if (0 == sz)
+ return;
+
+ if (NULL == *dest) {
+ *dest = mandoc_strndup(cp, sz);
+ return;
+ }
+
+ mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
+ free(*dest);
+ *dest = cp;
+}
diff --git a/contrib/mdocml/mdoc.h b/contrib/mdocml/mdoc.h
new file mode 100644
index 0000000..58ad813
--- /dev/null
+++ b/contrib/mdocml/mdoc.h
@@ -0,0 +1,399 @@
+/* $Id: mdoc.h,v 1.131 2014/07/29 13:58:18 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef MDOC_H
+#define MDOC_H
+
+enum mdoct {
+ MDOC_Ap = 0,
+ MDOC_Dd,
+ MDOC_Dt,
+ MDOC_Os,
+ MDOC_Sh,
+ MDOC_Ss,
+ MDOC_Pp,
+ MDOC_D1,
+ MDOC_Dl,
+ MDOC_Bd,
+ MDOC_Ed,
+ MDOC_Bl,
+ MDOC_El,
+ MDOC_It,
+ MDOC_Ad,
+ MDOC_An,
+ MDOC_Ar,
+ MDOC_Cd,
+ MDOC_Cm,
+ MDOC_Dv,
+ MDOC_Er,
+ MDOC_Ev,
+ MDOC_Ex,
+ MDOC_Fa,
+ MDOC_Fd,
+ MDOC_Fl,
+ MDOC_Fn,
+ MDOC_Ft,
+ MDOC_Ic,
+ MDOC_In,
+ MDOC_Li,
+ MDOC_Nd,
+ MDOC_Nm,
+ MDOC_Op,
+ MDOC_Ot,
+ MDOC_Pa,
+ MDOC_Rv,
+ MDOC_St,
+ MDOC_Va,
+ MDOC_Vt,
+ MDOC_Xr,
+ MDOC__A,
+ MDOC__B,
+ MDOC__D,
+ MDOC__I,
+ MDOC__J,
+ MDOC__N,
+ MDOC__O,
+ MDOC__P,
+ MDOC__R,
+ MDOC__T,
+ MDOC__V,
+ MDOC_Ac,
+ MDOC_Ao,
+ MDOC_Aq,
+ MDOC_At,
+ MDOC_Bc,
+ MDOC_Bf,
+ MDOC_Bo,
+ MDOC_Bq,
+ MDOC_Bsx,
+ MDOC_Bx,
+ MDOC_Db,
+ MDOC_Dc,
+ MDOC_Do,
+ MDOC_Dq,
+ MDOC_Ec,
+ MDOC_Ef,
+ MDOC_Em,
+ MDOC_Eo,
+ MDOC_Fx,
+ MDOC_Ms,
+ MDOC_No,
+ MDOC_Ns,
+ MDOC_Nx,
+ MDOC_Ox,
+ MDOC_Pc,
+ MDOC_Pf,
+ MDOC_Po,
+ MDOC_Pq,
+ MDOC_Qc,
+ MDOC_Ql,
+ MDOC_Qo,
+ MDOC_Qq,
+ MDOC_Re,
+ MDOC_Rs,
+ MDOC_Sc,
+ MDOC_So,
+ MDOC_Sq,
+ MDOC_Sm,
+ MDOC_Sx,
+ MDOC_Sy,
+ MDOC_Tn,
+ MDOC_Ux,
+ MDOC_Xc,
+ MDOC_Xo,
+ MDOC_Fo,
+ MDOC_Fc,
+ MDOC_Oo,
+ MDOC_Oc,
+ MDOC_Bk,
+ MDOC_Ek,
+ MDOC_Bt,
+ MDOC_Hf,
+ MDOC_Fr,
+ MDOC_Ud,
+ MDOC_Lb,
+ MDOC_Lp,
+ MDOC_Lk,
+ MDOC_Mt,
+ MDOC_Brq,
+ MDOC_Bro,
+ MDOC_Brc,
+ MDOC__C,
+ MDOC_Es,
+ MDOC_En,
+ MDOC_Dx,
+ MDOC__Q,
+ MDOC_br,
+ MDOC_sp,
+ MDOC__U,
+ MDOC_Ta,
+ MDOC_ll,
+ MDOC_MAX
+};
+
+enum mdocargt {
+ MDOC_Split, /* -split */
+ MDOC_Nosplit, /* -nospli */
+ MDOC_Ragged, /* -ragged */
+ MDOC_Unfilled, /* -unfilled */
+ MDOC_Literal, /* -literal */
+ MDOC_File, /* -file */
+ MDOC_Offset, /* -offset */
+ MDOC_Bullet, /* -bullet */
+ MDOC_Dash, /* -dash */
+ MDOC_Hyphen, /* -hyphen */
+ MDOC_Item, /* -item */
+ MDOC_Enum, /* -enum */
+ MDOC_Tag, /* -tag */
+ MDOC_Diag, /* -diag */
+ MDOC_Hang, /* -hang */
+ MDOC_Ohang, /* -ohang */
+ MDOC_Inset, /* -inset */
+ MDOC_Column, /* -column */
+ MDOC_Width, /* -width */
+ MDOC_Compact, /* -compact */
+ MDOC_Std, /* -std */
+ MDOC_Filled, /* -filled */
+ MDOC_Words, /* -words */
+ MDOC_Emphasis, /* -emphasis */
+ MDOC_Symbolic, /* -symbolic */
+ MDOC_Nested, /* -nested */
+ MDOC_Centred, /* -centered */
+ MDOC_ARG_MAX
+};
+
+enum mdoc_type {
+ MDOC_TEXT,
+ MDOC_ELEM,
+ MDOC_HEAD,
+ MDOC_TAIL,
+ MDOC_BODY,
+ MDOC_BLOCK,
+ MDOC_TBL,
+ MDOC_EQN,
+ MDOC_ROOT
+};
+
+/*
+ * Section (named/unnamed) of `Sh'. Note that these appear in the
+ * conventional order imposed by mdoc.7. In the case of SEC_NONE, no
+ * section has been invoked (this shouldn't happen). SEC_CUSTOM refers
+ * to other sections.
+ */
+enum mdoc_sec {
+ SEC_NONE = 0,
+ SEC_NAME, /* NAME */
+ SEC_LIBRARY, /* LIBRARY */
+ SEC_SYNOPSIS, /* SYNOPSIS */
+ SEC_DESCRIPTION, /* DESCRIPTION */
+ SEC_CONTEXT, /* CONTEXT */
+ SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */
+ SEC_RETURN_VALUES, /* RETURN VALUES */
+ SEC_ENVIRONMENT, /* ENVIRONMENT */
+ SEC_FILES, /* FILES */
+ SEC_EXIT_STATUS, /* EXIT STATUS */
+ SEC_EXAMPLES, /* EXAMPLES */
+ SEC_DIAGNOSTICS, /* DIAGNOSTICS */
+ SEC_COMPATIBILITY, /* COMPATIBILITY */
+ SEC_ERRORS, /* ERRORS */
+ SEC_SEE_ALSO, /* SEE ALSO */
+ SEC_STANDARDS, /* STANDARDS */
+ SEC_HISTORY, /* HISTORY */
+ SEC_AUTHORS, /* AUTHORS */
+ SEC_CAVEATS, /* CAVEATS */
+ SEC_BUGS, /* BUGS */
+ SEC_SECURITY, /* SECURITY */
+ SEC_CUSTOM,
+ SEC__MAX
+};
+
+struct mdoc_meta {
+ char *msec; /* `Dt' section (1, 3p, etc.) */
+ char *vol; /* `Dt' volume (implied) */
+ char *arch; /* `Dt' arch (i386, etc.) */
+ char *date; /* `Dd' normalised date */
+ char *title; /* `Dt' title (FOO, etc.) */
+ char *os; /* `Os' system (OpenBSD, etc.) */
+ char *name; /* leading `Nm' name */
+};
+
+/*
+ * An argument to a macro (multiple values = `-column xxx yyy').
+ */
+struct mdoc_argv {
+ enum mdocargt arg; /* type of argument */
+ int line;
+ int pos;
+ size_t sz; /* elements in "value" */
+ char **value; /* argument strings */
+};
+
+/*
+ * Reference-counted macro arguments. These are refcounted because
+ * blocks have multiple instances of the same arguments spread across
+ * the HEAD, BODY, TAIL, and BLOCK node types.
+ */
+struct mdoc_arg {
+ size_t argc;
+ struct mdoc_argv *argv;
+ unsigned int refcnt;
+};
+
+/*
+ * Indicates that a BODY's formatting has ended, but the scope is still
+ * open. Used for syntax-broken blocks.
+ */
+enum mdoc_endbody {
+ ENDBODY_NOT = 0,
+ ENDBODY_SPACE, /* is broken: append a space */
+ ENDBODY_NOSPACE /* is broken: don't append a space */
+};
+
+enum mdoc_list {
+ LIST__NONE = 0,
+ LIST_bullet, /* -bullet */
+ LIST_column, /* -column */
+ LIST_dash, /* -dash */
+ LIST_diag, /* -diag */
+ LIST_enum, /* -enum */
+ LIST_hang, /* -hang */
+ LIST_hyphen, /* -hyphen */
+ LIST_inset, /* -inset */
+ LIST_item, /* -item */
+ LIST_ohang, /* -ohang */
+ LIST_tag, /* -tag */
+ LIST_MAX
+};
+
+enum mdoc_disp {
+ DISP__NONE = 0,
+ DISP_centered, /* -centered */
+ DISP_ragged, /* -ragged */
+ DISP_unfilled, /* -unfilled */
+ DISP_filled, /* -filled */
+ DISP_literal /* -literal */
+};
+
+enum mdoc_auth {
+ AUTH__NONE = 0,
+ AUTH_split, /* -split */
+ AUTH_nosplit /* -nosplit */
+};
+
+enum mdoc_font {
+ FONT__NONE = 0,
+ FONT_Em, /* Em, -emphasis */
+ FONT_Li, /* Li, -literal */
+ FONT_Sy /* Sy, -symbolic */
+};
+
+struct mdoc_bd {
+ const char *offs; /* -offset */
+ enum mdoc_disp type; /* -ragged, etc. */
+ int comp; /* -compact */
+};
+
+struct mdoc_bl {
+ const char *width; /* -width */
+ const char *offs; /* -offset */
+ enum mdoc_list type; /* -tag, -enum, etc. */
+ int comp; /* -compact */
+ size_t ncols; /* -column arg count */
+ const char **cols; /* -column val ptr */
+ int count; /* -enum counter */
+};
+
+struct mdoc_bf {
+ enum mdoc_font font; /* font */
+};
+
+struct mdoc_an {
+ enum mdoc_auth auth; /* -split, etc. */
+};
+
+struct mdoc_rs {
+ int quote_T; /* whether to quote %T */
+};
+
+/*
+ * Consists of normalised node arguments. These should be used instead
+ * of iterating through the mdoc_arg pointers of a node: defaults are
+ * provided, etc.
+ */
+union mdoc_data {
+ struct mdoc_an An;
+ struct mdoc_bd Bd;
+ struct mdoc_bf Bf;
+ struct mdoc_bl Bl;
+ struct mdoc_node *Es;
+ struct mdoc_rs Rs;
+};
+
+/*
+ * Single node in tree-linked AST.
+ */
+struct mdoc_node {
+ struct mdoc_node *parent; /* parent AST node */
+ struct mdoc_node *child; /* first child AST node */
+ struct mdoc_node *last; /* last child AST node */
+ struct mdoc_node *next; /* sibling AST node */
+ struct mdoc_node *prev; /* prior sibling AST node */
+ int nchild; /* number children */
+ int line; /* parse line */
+ int pos; /* parse column */
+ int lastline; /* the node ends on this line */
+ enum mdoct tok; /* tok or MDOC__MAX if none */
+ int flags;
+#define MDOC_VALID (1 << 0) /* has been validated */
+#define MDOC_EOS (1 << 2) /* at sentence boundary */
+#define MDOC_LINE (1 << 3) /* first macro/text on line */
+#define MDOC_SYNPRETTY (1 << 4) /* SYNOPSIS-style formatting */
+#define MDOC_ENDED (1 << 5) /* rendering has been ended */
+#define MDOC_DELIMO (1 << 6)
+#define MDOC_DELIMC (1 << 7)
+ enum mdoc_type type; /* AST node type */
+ enum mdoc_sec sec; /* current named section */
+ union mdoc_data *norm; /* normalised args */
+ const void *prev_font; /* before entering this node */
+ /* FIXME: these can be union'd to shave a few bytes. */
+ struct mdoc_arg *args; /* BLOCK/ELEM */
+ struct mdoc_node *pending; /* BLOCK */
+ struct mdoc_node *head; /* BLOCK */
+ struct mdoc_node *body; /* BLOCK */
+ struct mdoc_node *tail; /* BLOCK */
+ char *string; /* TEXT */
+ const struct tbl_span *span; /* TBL */
+ const struct eqn *eqn; /* EQN */
+ enum mdoc_endbody end; /* BODY */
+};
+
+/* Names of macros. Index is enum mdoct. */
+extern const char *const *mdoc_macronames;
+
+/* Names of macro args. Index is enum mdocargt. */
+extern const char *const *mdoc_argnames;
+
+__BEGIN_DECLS
+
+struct mdoc;
+
+const struct mdoc_node *mdoc_node(const struct mdoc *);
+const struct mdoc_meta *mdoc_meta(const struct mdoc *);
+void mdoc_deroff(char **, const struct mdoc_node *);
+
+__END_DECLS
+
+#endif /*!MDOC_H*/
diff --git a/contrib/mdocml/mdoc_argv.c b/contrib/mdocml/mdoc_argv.c
new file mode 100644
index 0000000..e48516c
--- /dev/null
+++ b/contrib/mdocml/mdoc_argv.c
@@ -0,0 +1,701 @@
+/* $Id: mdoc_argv.c,v 1.95 2014/07/06 19:09:00 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2012 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+#define MULTI_STEP 5 /* pre-allocate argument values */
+#define DELIMSZ 6 /* max possible size of a delimiter */
+
+enum argsflag {
+ ARGSFL_NONE = 0,
+ ARGSFL_DELIM, /* handle delimiters of [[::delim::][ ]+]+ */
+ ARGSFL_TABSEP /* handle tab/`Ta' separated phrases */
+};
+
+enum argvflag {
+ ARGV_NONE, /* no args to flag (e.g., -split) */
+ ARGV_SINGLE, /* one arg to flag (e.g., -file xxx) */
+ ARGV_MULTI /* multiple args (e.g., -column xxx yyy) */
+};
+
+struct mdocarg {
+ enum argsflag flags;
+ const enum mdocargt *argvs;
+};
+
+static void argn_free(struct mdoc_arg *, int);
+static enum margserr args(struct mdoc *, int, int *,
+ char *, enum argsflag, char **);
+static int args_checkpunct(const char *, int);
+static int argv_multi(struct mdoc *, int,
+ struct mdoc_argv *, int *, char *);
+static int argv_single(struct mdoc *, int,
+ struct mdoc_argv *, int *, char *);
+
+static const enum argvflag argvflags[MDOC_ARG_MAX] = {
+ ARGV_NONE, /* MDOC_Split */
+ ARGV_NONE, /* MDOC_Nosplit */
+ ARGV_NONE, /* MDOC_Ragged */
+ ARGV_NONE, /* MDOC_Unfilled */
+ ARGV_NONE, /* MDOC_Literal */
+ ARGV_SINGLE, /* MDOC_File */
+ ARGV_SINGLE, /* MDOC_Offset */
+ ARGV_NONE, /* MDOC_Bullet */
+ ARGV_NONE, /* MDOC_Dash */
+ ARGV_NONE, /* MDOC_Hyphen */
+ ARGV_NONE, /* MDOC_Item */
+ ARGV_NONE, /* MDOC_Enum */
+ ARGV_NONE, /* MDOC_Tag */
+ ARGV_NONE, /* MDOC_Diag */
+ ARGV_NONE, /* MDOC_Hang */
+ ARGV_NONE, /* MDOC_Ohang */
+ ARGV_NONE, /* MDOC_Inset */
+ ARGV_MULTI, /* MDOC_Column */
+ ARGV_SINGLE, /* MDOC_Width */
+ ARGV_NONE, /* MDOC_Compact */
+ ARGV_NONE, /* MDOC_Std */
+ ARGV_NONE, /* MDOC_Filled */
+ ARGV_NONE, /* MDOC_Words */
+ ARGV_NONE, /* MDOC_Emphasis */
+ ARGV_NONE, /* MDOC_Symbolic */
+ ARGV_NONE /* MDOC_Symbolic */
+};
+
+static const enum mdocargt args_Ex[] = {
+ MDOC_Std,
+ MDOC_ARG_MAX
+};
+
+static const enum mdocargt args_An[] = {
+ MDOC_Split,
+ MDOC_Nosplit,
+ MDOC_ARG_MAX
+};
+
+static const enum mdocargt args_Bd[] = {
+ MDOC_Ragged,
+ MDOC_Unfilled,
+ MDOC_Filled,
+ MDOC_Literal,
+ MDOC_File,
+ MDOC_Offset,
+ MDOC_Compact,
+ MDOC_Centred,
+ MDOC_ARG_MAX
+};
+
+static const enum mdocargt args_Bf[] = {
+ MDOC_Emphasis,
+ MDOC_Literal,
+ MDOC_Symbolic,
+ MDOC_ARG_MAX
+};
+
+static const enum mdocargt args_Bk[] = {
+ MDOC_Words,
+ MDOC_ARG_MAX
+};
+
+static const enum mdocargt args_Bl[] = {
+ MDOC_Bullet,
+ MDOC_Dash,
+ MDOC_Hyphen,
+ MDOC_Item,
+ MDOC_Enum,
+ MDOC_Tag,
+ MDOC_Diag,
+ MDOC_Hang,
+ MDOC_Ohang,
+ MDOC_Inset,
+ MDOC_Column,
+ MDOC_Width,
+ MDOC_Offset,
+ MDOC_Compact,
+ MDOC_Nested,
+ MDOC_ARG_MAX
+};
+
+static const struct mdocarg mdocargs[MDOC_MAX] = {
+ { ARGSFL_DELIM, NULL }, /* Ap */
+ { ARGSFL_NONE, NULL }, /* Dd */
+ { ARGSFL_NONE, NULL }, /* Dt */
+ { ARGSFL_NONE, NULL }, /* Os */
+ { ARGSFL_NONE, NULL }, /* Sh */
+ { ARGSFL_NONE, NULL }, /* Ss */
+ { ARGSFL_NONE, NULL }, /* Pp */
+ { ARGSFL_DELIM, NULL }, /* D1 */
+ { ARGSFL_DELIM, NULL }, /* Dl */
+ { ARGSFL_NONE, args_Bd }, /* Bd */
+ { ARGSFL_NONE, NULL }, /* Ed */
+ { ARGSFL_NONE, args_Bl }, /* Bl */
+ { ARGSFL_NONE, NULL }, /* El */
+ { ARGSFL_NONE, NULL }, /* It */
+ { ARGSFL_DELIM, NULL }, /* Ad */
+ { ARGSFL_DELIM, args_An }, /* An */
+ { ARGSFL_DELIM, NULL }, /* Ar */
+ { ARGSFL_DELIM, NULL }, /* Cd */
+ { ARGSFL_DELIM, NULL }, /* Cm */
+ { ARGSFL_DELIM, NULL }, /* Dv */
+ { ARGSFL_DELIM, NULL }, /* Er */
+ { ARGSFL_DELIM, NULL }, /* Ev */
+ { ARGSFL_NONE, args_Ex }, /* Ex */
+ { ARGSFL_DELIM, NULL }, /* Fa */
+ { ARGSFL_NONE, NULL }, /* Fd */
+ { ARGSFL_DELIM, NULL }, /* Fl */
+ { ARGSFL_DELIM, NULL }, /* Fn */
+ { ARGSFL_DELIM, NULL }, /* Ft */
+ { ARGSFL_DELIM, NULL }, /* Ic */
+ { ARGSFL_DELIM, NULL }, /* In */
+ { ARGSFL_DELIM, NULL }, /* Li */
+ { ARGSFL_NONE, NULL }, /* Nd */
+ { ARGSFL_DELIM, NULL }, /* Nm */
+ { ARGSFL_DELIM, NULL }, /* Op */
+ { ARGSFL_DELIM, NULL }, /* Ot */
+ { ARGSFL_DELIM, NULL }, /* Pa */
+ { ARGSFL_NONE, args_Ex }, /* Rv */
+ { ARGSFL_DELIM, NULL }, /* St */
+ { ARGSFL_DELIM, NULL }, /* Va */
+ { ARGSFL_DELIM, NULL }, /* Vt */
+ { ARGSFL_DELIM, NULL }, /* Xr */
+ { ARGSFL_NONE, NULL }, /* %A */
+ { ARGSFL_NONE, NULL }, /* %B */
+ { ARGSFL_NONE, NULL }, /* %D */
+ { ARGSFL_NONE, NULL }, /* %I */
+ { ARGSFL_NONE, NULL }, /* %J */
+ { ARGSFL_NONE, NULL }, /* %N */
+ { ARGSFL_NONE, NULL }, /* %O */
+ { ARGSFL_NONE, NULL }, /* %P */
+ { ARGSFL_NONE, NULL }, /* %R */
+ { ARGSFL_NONE, NULL }, /* %T */
+ { ARGSFL_NONE, NULL }, /* %V */
+ { ARGSFL_DELIM, NULL }, /* Ac */
+ { ARGSFL_NONE, NULL }, /* Ao */
+ { ARGSFL_DELIM, NULL }, /* Aq */
+ { ARGSFL_DELIM, NULL }, /* At */
+ { ARGSFL_DELIM, NULL }, /* Bc */
+ { ARGSFL_NONE, args_Bf }, /* Bf */
+ { ARGSFL_NONE, NULL }, /* Bo */
+ { ARGSFL_DELIM, NULL }, /* Bq */
+ { ARGSFL_DELIM, NULL }, /* Bsx */
+ { ARGSFL_DELIM, NULL }, /* Bx */
+ { ARGSFL_NONE, NULL }, /* Db */
+ { ARGSFL_DELIM, NULL }, /* Dc */
+ { ARGSFL_NONE, NULL }, /* Do */
+ { ARGSFL_DELIM, NULL }, /* Dq */
+ { ARGSFL_DELIM, NULL }, /* Ec */
+ { ARGSFL_NONE, NULL }, /* Ef */
+ { ARGSFL_DELIM, NULL }, /* Em */
+ { ARGSFL_NONE, NULL }, /* Eo */
+ { ARGSFL_DELIM, NULL }, /* Fx */
+ { ARGSFL_DELIM, NULL }, /* Ms */
+ { ARGSFL_DELIM, NULL }, /* No */
+ { ARGSFL_DELIM, NULL }, /* Ns */
+ { ARGSFL_DELIM, NULL }, /* Nx */
+ { ARGSFL_DELIM, NULL }, /* Ox */
+ { ARGSFL_DELIM, NULL }, /* Pc */
+ { ARGSFL_DELIM, NULL }, /* Pf */
+ { ARGSFL_NONE, NULL }, /* Po */
+ { ARGSFL_DELIM, NULL }, /* Pq */
+ { ARGSFL_DELIM, NULL }, /* Qc */
+ { ARGSFL_DELIM, NULL }, /* Ql */
+ { ARGSFL_NONE, NULL }, /* Qo */
+ { ARGSFL_DELIM, NULL }, /* Qq */
+ { ARGSFL_NONE, NULL }, /* Re */
+ { ARGSFL_NONE, NULL }, /* Rs */
+ { ARGSFL_DELIM, NULL }, /* Sc */
+ { ARGSFL_NONE, NULL }, /* So */
+ { ARGSFL_DELIM, NULL }, /* Sq */
+ { ARGSFL_NONE, NULL }, /* Sm */
+ { ARGSFL_DELIM, NULL }, /* Sx */
+ { ARGSFL_DELIM, NULL }, /* Sy */
+ { ARGSFL_DELIM, NULL }, /* Tn */
+ { ARGSFL_DELIM, NULL }, /* Ux */
+ { ARGSFL_DELIM, NULL }, /* Xc */
+ { ARGSFL_NONE, NULL }, /* Xo */
+ { ARGSFL_NONE, NULL }, /* Fo */
+ { ARGSFL_DELIM, NULL }, /* Fc */
+ { ARGSFL_NONE, NULL }, /* Oo */
+ { ARGSFL_DELIM, NULL }, /* Oc */
+ { ARGSFL_NONE, args_Bk }, /* Bk */
+ { ARGSFL_NONE, NULL }, /* Ek */
+ { ARGSFL_NONE, NULL }, /* Bt */
+ { ARGSFL_NONE, NULL }, /* Hf */
+ { ARGSFL_DELIM, NULL }, /* Fr */
+ { ARGSFL_NONE, NULL }, /* Ud */
+ { ARGSFL_DELIM, NULL }, /* Lb */
+ { ARGSFL_NONE, NULL }, /* Lp */
+ { ARGSFL_DELIM, NULL }, /* Lk */
+ { ARGSFL_DELIM, NULL }, /* Mt */
+ { ARGSFL_DELIM, NULL }, /* Brq */
+ { ARGSFL_NONE, NULL }, /* Bro */
+ { ARGSFL_DELIM, NULL }, /* Brc */
+ { ARGSFL_NONE, NULL }, /* %C */
+ { ARGSFL_NONE, NULL }, /* Es */
+ { ARGSFL_DELIM, NULL }, /* En */
+ { ARGSFL_DELIM, NULL }, /* Dx */
+ { ARGSFL_NONE, NULL }, /* %Q */
+ { ARGSFL_NONE, NULL }, /* br */
+ { ARGSFL_NONE, NULL }, /* sp */
+ { ARGSFL_NONE, NULL }, /* %U */
+ { ARGSFL_NONE, NULL }, /* Ta */
+ { ARGSFL_NONE, NULL }, /* ll */
+};
+
+
+/*
+ * Parse an argument from line text. This comes in the form of -key
+ * [value0...], which may either have a single mandatory value, at least
+ * one mandatory value, an optional single value, or no value.
+ */
+enum margverr
+mdoc_argv(struct mdoc *mdoc, int line, enum mdoct tok,
+ struct mdoc_arg **v, int *pos, char *buf)
+{
+ char *p, sv;
+ struct mdoc_argv tmp;
+ struct mdoc_arg *arg;
+ const enum mdocargt *ap;
+
+ if ('\0' == buf[*pos])
+ return(ARGV_EOLN);
+ else if (NULL == (ap = mdocargs[tok].argvs))
+ return(ARGV_WORD);
+ else if ('-' != buf[*pos])
+ return(ARGV_WORD);
+
+ /* Seek to the first unescaped space. */
+
+ p = &buf[++(*pos)];
+
+ assert(*pos > 0);
+
+ for ( ; buf[*pos] ; (*pos)++)
+ if (' ' == buf[*pos] && '\\' != buf[*pos - 1])
+ break;
+
+ /*
+ * We want to nil-terminate the word to look it up (it's easier
+ * that way). But we may not have a flag, in which case we need
+ * to restore the line as-is. So keep around the stray byte,
+ * which we'll reset upon exiting (if necessary).
+ */
+
+ if ('\0' != (sv = buf[*pos]))
+ buf[(*pos)++] = '\0';
+
+ /*
+ * Now look up the word as a flag. Use temporary storage that
+ * we'll copy into the node's flags, if necessary.
+ */
+
+ memset(&tmp, 0, sizeof(struct mdoc_argv));
+
+ tmp.line = line;
+ tmp.pos = *pos;
+ tmp.arg = MDOC_ARG_MAX;
+
+ while (MDOC_ARG_MAX != (tmp.arg = *ap++))
+ if (0 == strcmp(p, mdoc_argnames[tmp.arg]))
+ break;
+
+ if (MDOC_ARG_MAX == tmp.arg) {
+ /*
+ * The flag was not found.
+ * Restore saved zeroed byte and return as a word.
+ */
+ if (sv)
+ buf[*pos - 1] = sv;
+ return(ARGV_WORD);
+ }
+
+ /* Read to the next word (the argument). */
+
+ while (buf[*pos] && ' ' == buf[*pos])
+ (*pos)++;
+
+ switch (argvflags[tmp.arg]) {
+ case ARGV_SINGLE:
+ if ( ! argv_single(mdoc, line, &tmp, pos, buf))
+ return(ARGV_ERROR);
+ break;
+ case ARGV_MULTI:
+ if ( ! argv_multi(mdoc, line, &tmp, pos, buf))
+ return(ARGV_ERROR);
+ break;
+ case ARGV_NONE:
+ break;
+ }
+
+ if (NULL == (arg = *v))
+ arg = *v = mandoc_calloc(1, sizeof(struct mdoc_arg));
+
+ arg->argc++;
+ arg->argv = mandoc_reallocarray(arg->argv,
+ arg->argc, sizeof(struct mdoc_argv));
+
+ memcpy(&arg->argv[(int)arg->argc - 1], &tmp,
+ sizeof(struct mdoc_argv));
+
+ return(ARGV_ARG);
+}
+
+void
+mdoc_argv_free(struct mdoc_arg *p)
+{
+ int i;
+
+ if (NULL == p)
+ return;
+
+ if (p->refcnt) {
+ --(p->refcnt);
+ if (p->refcnt)
+ return;
+ }
+ assert(p->argc);
+
+ for (i = (int)p->argc - 1; i >= 0; i--)
+ argn_free(p, i);
+
+ free(p->argv);
+ free(p);
+}
+
+static void
+argn_free(struct mdoc_arg *p, int iarg)
+{
+ struct mdoc_argv *arg;
+ int j;
+
+ arg = &p->argv[iarg];
+
+ if (arg->sz && arg->value) {
+ for (j = (int)arg->sz - 1; j >= 0; j--)
+ free(arg->value[j]);
+ free(arg->value);
+ }
+
+ for (--p->argc; iarg < (int)p->argc; iarg++)
+ p->argv[iarg] = p->argv[iarg+1];
+}
+
+enum margserr
+mdoc_zargs(struct mdoc *mdoc, int line, int *pos, char *buf, char **v)
+{
+
+ return(args(mdoc, line, pos, buf, ARGSFL_NONE, v));
+}
+
+enum margserr
+mdoc_args(struct mdoc *mdoc, int line, int *pos,
+ char *buf, enum mdoct tok, char **v)
+{
+ enum argsflag fl;
+ struct mdoc_node *n;
+
+ fl = mdocargs[tok].flags;
+
+ if (MDOC_It != tok)
+ return(args(mdoc, line, pos, buf, fl, v));
+
+ /*
+ * We know that we're in an `It', so it's reasonable to expect
+ * us to be sitting in a `Bl'. Someday this may not be the case
+ * (if we allow random `It's sitting out there), so provide a
+ * safe fall-back into the default behaviour.
+ */
+
+ for (n = mdoc->last; n; n = n->parent)
+ if (MDOC_Bl == n->tok)
+ if (LIST_column == n->norm->Bl.type) {
+ fl = ARGSFL_TABSEP;
+ break;
+ }
+
+ return(args(mdoc, line, pos, buf, fl, v));
+}
+
+static enum margserr
+args(struct mdoc *mdoc, int line, int *pos,
+ char *buf, enum argsflag fl, char **v)
+{
+ char *p, *pp;
+ int pairs;
+ enum margserr rc;
+
+ if ('\0' == buf[*pos]) {
+ if (MDOC_PPHRASE & mdoc->flags)
+ return(ARGS_EOLN);
+ /*
+ * If we're not in a partial phrase and the flag for
+ * being a phrase literal is still set, the punctuation
+ * is unterminated.
+ */
+ if (MDOC_PHRASELIT & mdoc->flags)
+ mandoc_msg(MANDOCERR_ARG_QUOTE,
+ mdoc->parse, line, *pos, NULL);
+
+ mdoc->flags &= ~MDOC_PHRASELIT;
+ return(ARGS_EOLN);
+ }
+
+ *v = &buf[*pos];
+
+ if (ARGSFL_DELIM == fl)
+ if (args_checkpunct(buf, *pos))
+ return(ARGS_PUNCT);
+
+ /*
+ * First handle TABSEP items, restricted to `Bl -column'. This
+ * ignores conventional token parsing and instead uses tabs or
+ * `Ta' macros to separate phrases. Phrases are parsed again
+ * for arguments at a later phase.
+ */
+
+ if (ARGSFL_TABSEP == fl) {
+ /* Scan ahead to tab (can't be escaped). */
+ p = strchr(*v, '\t');
+ pp = NULL;
+
+ /* Scan ahead to unescaped `Ta'. */
+ if ( ! (MDOC_PHRASELIT & mdoc->flags))
+ for (pp = *v; ; pp++) {
+ if (NULL == (pp = strstr(pp, "Ta")))
+ break;
+ if (pp > *v && ' ' != *(pp - 1))
+ continue;
+ if (' ' == *(pp + 2) || '\0' == *(pp + 2))
+ break;
+ }
+
+ /* By default, assume a phrase. */
+ rc = ARGS_PHRASE;
+
+ /*
+ * Adjust new-buffer position to be beyond delimiter
+ * mark (e.g., Ta -> end + 2).
+ */
+ if (p && pp) {
+ *pos += pp < p ? 2 : 1;
+ rc = pp < p ? ARGS_PHRASE : ARGS_PPHRASE;
+ p = pp < p ? pp : p;
+ } else if (p && ! pp) {
+ rc = ARGS_PPHRASE;
+ *pos += 1;
+ } else if (pp && ! p) {
+ p = pp;
+ *pos += 2;
+ } else {
+ rc = ARGS_PEND;
+ p = strchr(*v, 0);
+ }
+
+ /* Whitespace check for eoln case... */
+ if ('\0' == *p && ' ' == *(p - 1))
+ mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+ line, *pos, NULL);
+
+ *pos += (int)(p - *v);
+
+ /* Strip delimiter's preceding whitespace. */
+ pp = p - 1;
+ while (pp > *v && ' ' == *pp) {
+ if (pp > *v && '\\' == *(pp - 1))
+ break;
+ pp--;
+ }
+ *(pp + 1) = 0;
+
+ /* Strip delimiter's proceeding whitespace. */
+ for (pp = &buf[*pos]; ' ' == *pp; pp++, (*pos)++)
+ /* Skip ahead. */ ;
+
+ return(rc);
+ }
+
+ /*
+ * Process a quoted literal. A quote begins with a double-quote
+ * and ends with a double-quote NOT preceded by a double-quote.
+ * NUL-terminate the literal in place.
+ * Collapse pairs of quotes inside quoted literals.
+ * Whitespace is NOT involved in literal termination.
+ */
+
+ if (MDOC_PHRASELIT & mdoc->flags || '\"' == buf[*pos]) {
+ if ( ! (MDOC_PHRASELIT & mdoc->flags))
+ *v = &buf[++(*pos)];
+
+ if (MDOC_PPHRASE & mdoc->flags)
+ mdoc->flags |= MDOC_PHRASELIT;
+
+ pairs = 0;
+ for ( ; buf[*pos]; (*pos)++) {
+ /* Move following text left after quoted quotes. */
+ if (pairs)
+ buf[*pos - pairs] = buf[*pos];
+ if ('\"' != buf[*pos])
+ continue;
+ /* Unquoted quotes end quoted args. */
+ if ('\"' != buf[*pos + 1])
+ break;
+ /* Quoted quotes collapse. */
+ pairs++;
+ (*pos)++;
+ }
+ if (pairs)
+ buf[*pos - pairs] = '\0';
+
+ if ('\0' == buf[*pos]) {
+ if (MDOC_PPHRASE & mdoc->flags)
+ return(ARGS_QWORD);
+ mandoc_msg(MANDOCERR_ARG_QUOTE,
+ mdoc->parse, line, *pos, NULL);
+ return(ARGS_QWORD);
+ }
+
+ mdoc->flags &= ~MDOC_PHRASELIT;
+ buf[(*pos)++] = '\0';
+
+ if ('\0' == buf[*pos])
+ return(ARGS_QWORD);
+
+ while (' ' == buf[*pos])
+ (*pos)++;
+
+ if ('\0' == buf[*pos])
+ mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+ line, *pos, NULL);
+
+ return(ARGS_QWORD);
+ }
+
+ p = &buf[*pos];
+ *v = mandoc_getarg(mdoc->parse, &p, line, pos);
+
+ return(ARGS_WORD);
+}
+
+/*
+ * Check if the string consists only of space-separated closing
+ * delimiters. This is a bit of a dance: the first must be a close
+ * delimiter, but it may be followed by middle delimiters. Arbitrary
+ * whitespace may separate these tokens.
+ */
+static int
+args_checkpunct(const char *buf, int i)
+{
+ int j;
+ char dbuf[DELIMSZ];
+ enum mdelim d;
+
+ /* First token must be a close-delimiter. */
+
+ for (j = 0; buf[i] && ' ' != buf[i] && j < DELIMSZ; j++, i++)
+ dbuf[j] = buf[i];
+
+ if (DELIMSZ == j)
+ return(0);
+
+ dbuf[j] = '\0';
+ if (DELIM_CLOSE != mdoc_isdelim(dbuf))
+ return(0);
+
+ while (' ' == buf[i])
+ i++;
+
+ /* Remaining must NOT be open/none. */
+
+ while (buf[i]) {
+ j = 0;
+ while (buf[i] && ' ' != buf[i] && j < DELIMSZ)
+ dbuf[j++] = buf[i++];
+
+ if (DELIMSZ == j)
+ return(0);
+
+ dbuf[j] = '\0';
+ d = mdoc_isdelim(dbuf);
+ if (DELIM_NONE == d || DELIM_OPEN == d)
+ return(0);
+
+ while (' ' == buf[i])
+ i++;
+ }
+
+ return('\0' == buf[i]);
+}
+
+static int
+argv_multi(struct mdoc *mdoc, int line,
+ struct mdoc_argv *v, int *pos, char *buf)
+{
+ enum margserr ac;
+ char *p;
+
+ for (v->sz = 0; ; v->sz++) {
+ if ('-' == buf[*pos])
+ break;
+ ac = args(mdoc, line, pos, buf, ARGSFL_NONE, &p);
+ if (ARGS_ERROR == ac)
+ return(0);
+ else if (ARGS_EOLN == ac)
+ break;
+
+ if (0 == v->sz % MULTI_STEP)
+ v->value = mandoc_reallocarray(v->value,
+ v->sz + MULTI_STEP, sizeof(char *));
+
+ v->value[(int)v->sz] = mandoc_strdup(p);
+ }
+
+ return(1);
+}
+
+static int
+argv_single(struct mdoc *mdoc, int line,
+ struct mdoc_argv *v, int *pos, char *buf)
+{
+ enum margserr ac;
+ char *p;
+
+ ac = args(mdoc, line, pos, buf, ARGSFL_NONE, &p);
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_EOLN == ac)
+ return(1);
+
+ v->sz = 1;
+ v->value = mandoc_malloc(sizeof(char *));
+ v->value[0] = mandoc_strdup(p);
+
+ return(1);
+}
diff --git a/contrib/mdocml/mdoc_hash.c b/contrib/mdocml/mdoc_hash.c
new file mode 100644
index 0000000..db30ee6
--- /dev/null
+++ b/contrib/mdocml/mdoc_hash.c
@@ -0,0 +1,94 @@
+/* $Id: mdoc_hash.c,v 1.20 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mdoc.h"
+#include "libmdoc.h"
+
+static unsigned char table[27 * 12];
+
+
+/*
+ * XXX - this hash has global scope, so if intended for use as a library
+ * with multiple callers, it will need re-invocation protection.
+ */
+void
+mdoc_hash_init(void)
+{
+ int i, j, major;
+ const char *p;
+
+ memset(table, UCHAR_MAX, sizeof(table));
+
+ for (i = 0; i < (int)MDOC_MAX; i++) {
+ p = mdoc_macronames[i];
+
+ if (isalpha((unsigned char)p[1]))
+ major = 12 * (tolower((unsigned char)p[1]) - 97);
+ else
+ major = 12 * 26;
+
+ for (j = 0; j < 12; j++)
+ if (UCHAR_MAX == table[major + j]) {
+ table[major + j] = (unsigned char)i;
+ break;
+ }
+
+ assert(j < 12);
+ }
+}
+
+enum mdoct
+mdoc_hash_find(const char *p)
+{
+ int major, i, j;
+
+ if (0 == p[0])
+ return(MDOC_MAX);
+ if ( ! isalpha((unsigned char)p[0]) && '%' != p[0])
+ return(MDOC_MAX);
+
+ if (isalpha((unsigned char)p[1]))
+ major = 12 * (tolower((unsigned char)p[1]) - 97);
+ else if ('1' == p[1])
+ major = 12 * 26;
+ else
+ return(MDOC_MAX);
+
+ if (p[2] && p[3])
+ return(MDOC_MAX);
+
+ for (j = 0; j < 12; j++) {
+ if (UCHAR_MAX == (i = table[major + j]))
+ break;
+ if (0 == strcmp(p, mdoc_macronames[i]))
+ return((enum mdoct)i);
+ }
+
+ return(MDOC_MAX);
+}
diff --git a/contrib/mdocml/mdoc_html.c b/contrib/mdocml/mdoc_html.c
new file mode 100644
index 0000000..9fe241e
--- /dev/null
+++ b/contrib/mdocml/mdoc_html.c
@@ -0,0 +1,2199 @@
+/* $Id: mdoc_html.c,v 1.195 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "html.h"
+#include "mdoc.h"
+#include "main.h"
+
+#define INDENT 5
+
+#define MDOC_ARGS const struct mdoc_meta *meta, \
+ const struct mdoc_node *n, \
+ struct html *h
+
+#ifndef MIN
+#define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b))
+#endif
+
+struct htmlmdoc {
+ int (*pre)(MDOC_ARGS);
+ void (*post)(MDOC_ARGS);
+};
+
+static void print_mdoc(MDOC_ARGS);
+static void print_mdoc_head(MDOC_ARGS);
+static void print_mdoc_node(MDOC_ARGS);
+static void print_mdoc_nodelist(MDOC_ARGS);
+static void synopsis_pre(struct html *,
+ const struct mdoc_node *);
+
+static void a2width(const char *, struct roffsu *);
+static void a2offs(const char *, struct roffsu *);
+
+static void mdoc_root_post(MDOC_ARGS);
+static int mdoc_root_pre(MDOC_ARGS);
+
+static void mdoc__x_post(MDOC_ARGS);
+static int mdoc__x_pre(MDOC_ARGS);
+static int mdoc_ad_pre(MDOC_ARGS);
+static int mdoc_an_pre(MDOC_ARGS);
+static int mdoc_ap_pre(MDOC_ARGS);
+static int mdoc_ar_pre(MDOC_ARGS);
+static int mdoc_bd_pre(MDOC_ARGS);
+static int mdoc_bf_pre(MDOC_ARGS);
+static void mdoc_bk_post(MDOC_ARGS);
+static int mdoc_bk_pre(MDOC_ARGS);
+static int mdoc_bl_pre(MDOC_ARGS);
+static int mdoc_bt_pre(MDOC_ARGS);
+static int mdoc_bx_pre(MDOC_ARGS);
+static int mdoc_cd_pre(MDOC_ARGS);
+static int mdoc_d1_pre(MDOC_ARGS);
+static int mdoc_dv_pre(MDOC_ARGS);
+static int mdoc_fa_pre(MDOC_ARGS);
+static int mdoc_fd_pre(MDOC_ARGS);
+static int mdoc_fl_pre(MDOC_ARGS);
+static int mdoc_fn_pre(MDOC_ARGS);
+static int mdoc_ft_pre(MDOC_ARGS);
+static int mdoc_em_pre(MDOC_ARGS);
+static int mdoc_er_pre(MDOC_ARGS);
+static int mdoc_ev_pre(MDOC_ARGS);
+static int mdoc_ex_pre(MDOC_ARGS);
+static void mdoc_fo_post(MDOC_ARGS);
+static int mdoc_fo_pre(MDOC_ARGS);
+static int mdoc_ic_pre(MDOC_ARGS);
+static int mdoc_igndelim_pre(MDOC_ARGS);
+static int mdoc_in_pre(MDOC_ARGS);
+static int mdoc_it_pre(MDOC_ARGS);
+static int mdoc_lb_pre(MDOC_ARGS);
+static int mdoc_li_pre(MDOC_ARGS);
+static int mdoc_lk_pre(MDOC_ARGS);
+static int mdoc_mt_pre(MDOC_ARGS);
+static int mdoc_ms_pre(MDOC_ARGS);
+static int mdoc_nd_pre(MDOC_ARGS);
+static int mdoc_nm_pre(MDOC_ARGS);
+static int mdoc_ns_pre(MDOC_ARGS);
+static int mdoc_pa_pre(MDOC_ARGS);
+static void mdoc_pf_post(MDOC_ARGS);
+static int mdoc_pp_pre(MDOC_ARGS);
+static void mdoc_quote_post(MDOC_ARGS);
+static int mdoc_quote_pre(MDOC_ARGS);
+static int mdoc_rs_pre(MDOC_ARGS);
+static int mdoc_rv_pre(MDOC_ARGS);
+static int mdoc_sh_pre(MDOC_ARGS);
+static int mdoc_skip_pre(MDOC_ARGS);
+static int mdoc_sm_pre(MDOC_ARGS);
+static int mdoc_sp_pre(MDOC_ARGS);
+static int mdoc_ss_pre(MDOC_ARGS);
+static int mdoc_sx_pre(MDOC_ARGS);
+static int mdoc_sy_pre(MDOC_ARGS);
+static int mdoc_ud_pre(MDOC_ARGS);
+static int mdoc_va_pre(MDOC_ARGS);
+static int mdoc_vt_pre(MDOC_ARGS);
+static int mdoc_xr_pre(MDOC_ARGS);
+static int mdoc_xx_pre(MDOC_ARGS);
+
+static const struct htmlmdoc mdocs[MDOC_MAX] = {
+ {mdoc_ap_pre, NULL}, /* Ap */
+ {NULL, NULL}, /* Dd */
+ {NULL, NULL}, /* Dt */
+ {NULL, NULL}, /* Os */
+ {mdoc_sh_pre, NULL }, /* Sh */
+ {mdoc_ss_pre, NULL }, /* Ss */
+ {mdoc_pp_pre, NULL}, /* Pp */
+ {mdoc_d1_pre, NULL}, /* D1 */
+ {mdoc_d1_pre, NULL}, /* Dl */
+ {mdoc_bd_pre, NULL}, /* Bd */
+ {NULL, NULL}, /* Ed */
+ {mdoc_bl_pre, NULL}, /* Bl */
+ {NULL, NULL}, /* El */
+ {mdoc_it_pre, NULL}, /* It */
+ {mdoc_ad_pre, NULL}, /* Ad */
+ {mdoc_an_pre, NULL}, /* An */
+ {mdoc_ar_pre, NULL}, /* Ar */
+ {mdoc_cd_pre, NULL}, /* Cd */
+ {mdoc_fl_pre, NULL}, /* Cm */
+ {mdoc_dv_pre, NULL}, /* Dv */
+ {mdoc_er_pre, NULL}, /* Er */
+ {mdoc_ev_pre, NULL}, /* Ev */
+ {mdoc_ex_pre, NULL}, /* Ex */
+ {mdoc_fa_pre, NULL}, /* Fa */
+ {mdoc_fd_pre, NULL}, /* Fd */
+ {mdoc_fl_pre, NULL}, /* Fl */
+ {mdoc_fn_pre, NULL}, /* Fn */
+ {mdoc_ft_pre, NULL}, /* Ft */
+ {mdoc_ic_pre, NULL}, /* Ic */
+ {mdoc_in_pre, NULL}, /* In */
+ {mdoc_li_pre, NULL}, /* Li */
+ {mdoc_nd_pre, NULL}, /* Nd */
+ {mdoc_nm_pre, NULL}, /* Nm */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Op */
+ {mdoc_ft_pre, NULL}, /* Ot */
+ {mdoc_pa_pre, NULL}, /* Pa */
+ {mdoc_rv_pre, NULL}, /* Rv */
+ {NULL, NULL}, /* St */
+ {mdoc_va_pre, NULL}, /* Va */
+ {mdoc_vt_pre, NULL}, /* Vt */
+ {mdoc_xr_pre, NULL}, /* Xr */
+ {mdoc__x_pre, mdoc__x_post}, /* %A */
+ {mdoc__x_pre, mdoc__x_post}, /* %B */
+ {mdoc__x_pre, mdoc__x_post}, /* %D */
+ {mdoc__x_pre, mdoc__x_post}, /* %I */
+ {mdoc__x_pre, mdoc__x_post}, /* %J */
+ {mdoc__x_pre, mdoc__x_post}, /* %N */
+ {mdoc__x_pre, mdoc__x_post}, /* %O */
+ {mdoc__x_pre, mdoc__x_post}, /* %P */
+ {mdoc__x_pre, mdoc__x_post}, /* %R */
+ {mdoc__x_pre, mdoc__x_post}, /* %T */
+ {mdoc__x_pre, mdoc__x_post}, /* %V */
+ {NULL, NULL}, /* Ac */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Ao */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Aq */
+ {NULL, NULL}, /* At */
+ {NULL, NULL}, /* Bc */
+ {mdoc_bf_pre, NULL}, /* Bf */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Bo */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Bq */
+ {mdoc_xx_pre, NULL}, /* Bsx */
+ {mdoc_bx_pre, NULL}, /* Bx */
+ {NULL, NULL}, /* Db */
+ {NULL, NULL}, /* Dc */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Do */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Dq */
+ {NULL, NULL}, /* Ec */ /* FIXME: no space */
+ {NULL, NULL}, /* Ef */
+ {mdoc_em_pre, NULL}, /* Em */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Eo */
+ {mdoc_xx_pre, NULL}, /* Fx */
+ {mdoc_ms_pre, NULL}, /* Ms */
+ {mdoc_igndelim_pre, NULL}, /* No */
+ {mdoc_ns_pre, NULL}, /* Ns */
+ {mdoc_xx_pre, NULL}, /* Nx */
+ {mdoc_xx_pre, NULL}, /* Ox */
+ {NULL, NULL}, /* Pc */
+ {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Po */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Pq */
+ {NULL, NULL}, /* Qc */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Ql */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Qo */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Qq */
+ {NULL, NULL}, /* Re */
+ {mdoc_rs_pre, NULL}, /* Rs */
+ {NULL, NULL}, /* Sc */
+ {mdoc_quote_pre, mdoc_quote_post}, /* So */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Sq */
+ {mdoc_sm_pre, NULL}, /* Sm */
+ {mdoc_sx_pre, NULL}, /* Sx */
+ {mdoc_sy_pre, NULL}, /* Sy */
+ {NULL, NULL}, /* Tn */
+ {mdoc_xx_pre, NULL}, /* Ux */
+ {NULL, NULL}, /* Xc */
+ {NULL, NULL}, /* Xo */
+ {mdoc_fo_pre, mdoc_fo_post}, /* Fo */
+ {NULL, NULL}, /* Fc */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Oo */
+ {NULL, NULL}, /* Oc */
+ {mdoc_bk_pre, mdoc_bk_post}, /* Bk */
+ {NULL, NULL}, /* Ek */
+ {mdoc_bt_pre, NULL}, /* Bt */
+ {NULL, NULL}, /* Hf */
+ {mdoc_em_pre, NULL}, /* Fr */
+ {mdoc_ud_pre, NULL}, /* Ud */
+ {mdoc_lb_pre, NULL}, /* Lb */
+ {mdoc_pp_pre, NULL}, /* Lp */
+ {mdoc_lk_pre, NULL}, /* Lk */
+ {mdoc_mt_pre, NULL}, /* Mt */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Brq */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Bro */
+ {NULL, NULL}, /* Brc */
+ {mdoc__x_pre, mdoc__x_post}, /* %C */
+ {mdoc_skip_pre, NULL}, /* Es */
+ {mdoc_quote_pre, mdoc_quote_post}, /* En */
+ {mdoc_xx_pre, NULL}, /* Dx */
+ {mdoc__x_pre, mdoc__x_post}, /* %Q */
+ {mdoc_sp_pre, NULL}, /* br */
+ {mdoc_sp_pre, NULL}, /* sp */
+ {mdoc__x_pre, mdoc__x_post}, /* %U */
+ {NULL, NULL}, /* Ta */
+ {mdoc_skip_pre, NULL}, /* ll */
+};
+
+static const char * const lists[LIST_MAX] = {
+ NULL,
+ "list-bul",
+ "list-col",
+ "list-dash",
+ "list-diag",
+ "list-enum",
+ "list-hang",
+ "list-hyph",
+ "list-inset",
+ "list-item",
+ "list-ohang",
+ "list-tag"
+};
+
+
+void
+html_mdoc(void *arg, const struct mdoc *mdoc)
+{
+
+ print_mdoc(mdoc_meta(mdoc), mdoc_node(mdoc),
+ (struct html *)arg);
+ putchar('\n');
+}
+
+/*
+ * Calculate the scaling unit passed in a `-width' argument. This uses
+ * either a native scaling unit (e.g., 1i, 2m) or the string length of
+ * the value.
+ */
+static void
+a2width(const char *p, struct roffsu *su)
+{
+
+ if ( ! a2roffsu(p, su, SCALE_MAX)) {
+ su->unit = SCALE_BU;
+ su->scale = html_strlen(p);
+ }
+}
+
+/*
+ * See the same function in mdoc_term.c for documentation.
+ */
+static void
+synopsis_pre(struct html *h, const struct mdoc_node *n)
+{
+
+ if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
+ return;
+
+ if (n->prev->tok == n->tok &&
+ MDOC_Fo != n->tok &&
+ MDOC_Ft != n->tok &&
+ MDOC_Fn != n->tok) {
+ print_otag(h, TAG_BR, 0, NULL);
+ return;
+ }
+
+ switch (n->prev->tok) {
+ case MDOC_Fd:
+ /* FALLTHROUGH */
+ case MDOC_Fn:
+ /* FALLTHROUGH */
+ case MDOC_Fo:
+ /* FALLTHROUGH */
+ case MDOC_In:
+ /* FALLTHROUGH */
+ case MDOC_Vt:
+ print_otag(h, TAG_P, 0, NULL);
+ break;
+ case MDOC_Ft:
+ if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
+ print_otag(h, TAG_P, 0, NULL);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ print_otag(h, TAG_BR, 0, NULL);
+ break;
+ }
+}
+
+/*
+ * Calculate the scaling unit passed in an `-offset' argument. This
+ * uses either a native scaling unit (e.g., 1i, 2m), one of a set of
+ * predefined strings (indent, etc.), or the string length of the value.
+ */
+static void
+a2offs(const char *p, struct roffsu *su)
+{
+
+ /* FIXME: "right"? */
+
+ if (0 == strcmp(p, "left"))
+ SCALE_HS_INIT(su, 0);
+ else if (0 == strcmp(p, "indent"))
+ SCALE_HS_INIT(su, INDENT);
+ else if (0 == strcmp(p, "indent-two"))
+ SCALE_HS_INIT(su, INDENT * 2);
+ else if ( ! a2roffsu(p, su, SCALE_MAX))
+ SCALE_HS_INIT(su, html_strlen(p));
+}
+
+static void
+print_mdoc(MDOC_ARGS)
+{
+ struct tag *t, *tt;
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "mandoc");
+
+ if ( ! (HTML_FRAGMENT & h->oflags)) {
+ print_gen_decls(h);
+ t = print_otag(h, TAG_HTML, 0, NULL);
+ tt = print_otag(h, TAG_HEAD, 0, NULL);
+ print_mdoc_head(meta, n, h);
+ print_tagq(h, tt);
+ print_otag(h, TAG_BODY, 0, NULL);
+ print_otag(h, TAG_DIV, 1, &tag);
+ } else
+ t = print_otag(h, TAG_DIV, 1, &tag);
+
+ print_mdoc_nodelist(meta, n, h);
+ print_tagq(h, t);
+}
+
+static void
+print_mdoc_head(MDOC_ARGS)
+{
+
+ print_gen_head(h);
+ bufinit(h);
+ bufcat(h, meta->title);
+ if (meta->msec)
+ bufcat_fmt(h, "(%s)", meta->msec);
+ if (meta->arch)
+ bufcat_fmt(h, " (%s)", meta->arch);
+
+ print_otag(h, TAG_TITLE, 0, NULL);
+ print_text(h, h->buf);
+}
+
+static void
+print_mdoc_nodelist(MDOC_ARGS)
+{
+
+ print_mdoc_node(meta, n, h);
+ if (n->next)
+ print_mdoc_nodelist(meta, n->next, h);
+}
+
+static void
+print_mdoc_node(MDOC_ARGS)
+{
+ int child;
+ struct tag *t;
+
+ child = 1;
+ t = h->tags.head;
+
+ switch (n->type) {
+ case MDOC_ROOT:
+ child = mdoc_root_pre(meta, n, h);
+ break;
+ case MDOC_TEXT:
+ /* No tables in this mode... */
+ assert(NULL == h->tblt);
+
+ /*
+ * Make sure that if we're in a literal mode already
+ * (i.e., within a <PRE>) don't print the newline.
+ */
+ if (' ' == *n->string && MDOC_LINE & n->flags)
+ if ( ! (HTML_LITERAL & h->flags))
+ print_otag(h, TAG_BR, 0, NULL);
+ if (MDOC_DELIMC & n->flags)
+ h->flags |= HTML_NOSPACE;
+ print_text(h, n->string);
+ if (MDOC_DELIMO & n->flags)
+ h->flags |= HTML_NOSPACE;
+ return;
+ case MDOC_EQN:
+ print_eqn(h, n->eqn);
+ break;
+ case MDOC_TBL:
+ /*
+ * This will take care of initialising all of the table
+ * state data for the first table, then tearing it down
+ * for the last one.
+ */
+ print_tbl(h, n->span);
+ return;
+ default:
+ /*
+ * Close out the current table, if it's open, and unset
+ * the "meta" table state. This will be reopened on the
+ * next table element.
+ */
+ if (h->tblt) {
+ print_tblclose(h);
+ t = h->tags.head;
+ }
+
+ assert(NULL == h->tblt);
+ if (mdocs[n->tok].pre && ENDBODY_NOT == n->end)
+ child = (*mdocs[n->tok].pre)(meta, n, h);
+ break;
+ }
+
+ if (HTML_KEEP & h->flags) {
+ if (n->prev ? (n->prev->lastline != n->line) :
+ (n->parent && n->parent->line != n->line)) {
+ h->flags &= ~HTML_KEEP;
+ h->flags |= HTML_PREKEEP;
+ }
+ }
+
+ if (child && n->child)
+ print_mdoc_nodelist(meta, n->child, h);
+
+ print_stagq(h, t);
+
+ switch (n->type) {
+ case MDOC_ROOT:
+ mdoc_root_post(meta, n, h);
+ break;
+ case MDOC_EQN:
+ break;
+ default:
+ if (mdocs[n->tok].post && ENDBODY_NOT == n->end)
+ (*mdocs[n->tok].post)(meta, n, h);
+ break;
+ }
+}
+
+static void
+mdoc_root_post(MDOC_ARGS)
+{
+ struct htmlpair tag[3];
+ struct tag *t, *tt;
+
+ PAIR_SUMMARY_INIT(&tag[0], "Document Footer");
+ PAIR_CLASS_INIT(&tag[1], "foot");
+ PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
+ t = print_otag(h, TAG_TABLE, 3, tag);
+ PAIR_INIT(&tag[0], ATTR_WIDTH, "50%");
+ print_otag(h, TAG_COL, 1, tag);
+ print_otag(h, TAG_COL, 1, tag);
+
+ print_otag(h, TAG_TBODY, 0, NULL);
+
+ tt = print_otag(h, TAG_TR, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag[0], "foot-date");
+ print_otag(h, TAG_TD, 1, tag);
+ print_text(h, meta->date);
+ print_stagq(h, tt);
+
+ PAIR_CLASS_INIT(&tag[0], "foot-os");
+ PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
+ print_otag(h, TAG_TD, 2, tag);
+ print_text(h, meta->os);
+ print_tagq(h, t);
+}
+
+static int
+mdoc_root_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[3];
+ struct tag *t, *tt;
+ char *volume, *title;
+
+ if (NULL == meta->arch)
+ volume = mandoc_strdup(meta->vol);
+ else
+ mandoc_asprintf(&volume, "%s (%s)",
+ meta->vol, meta->arch);
+
+ if (NULL == meta->msec)
+ title = mandoc_strdup(meta->title);
+ else
+ mandoc_asprintf(&title, "%s(%s)",
+ meta->title, meta->msec);
+
+ PAIR_SUMMARY_INIT(&tag[0], "Document Header");
+ PAIR_CLASS_INIT(&tag[1], "head");
+ PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
+ t = print_otag(h, TAG_TABLE, 3, tag);
+ PAIR_INIT(&tag[0], ATTR_WIDTH, "30%");
+ print_otag(h, TAG_COL, 1, tag);
+ print_otag(h, TAG_COL, 1, tag);
+ print_otag(h, TAG_COL, 1, tag);
+
+ print_otag(h, TAG_TBODY, 0, NULL);
+
+ tt = print_otag(h, TAG_TR, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag[0], "head-ltitle");
+ print_otag(h, TAG_TD, 1, tag);
+ print_text(h, title);
+ print_stagq(h, tt);
+
+ PAIR_CLASS_INIT(&tag[0], "head-vol");
+ PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
+ print_otag(h, TAG_TD, 2, tag);
+ print_text(h, volume);
+ print_stagq(h, tt);
+
+ PAIR_CLASS_INIT(&tag[0], "head-rtitle");
+ PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
+ print_otag(h, TAG_TD, 2, tag);
+ print_text(h, title);
+ print_tagq(h, t);
+
+ free(title);
+ free(volume);
+ return(1);
+}
+
+static int
+mdoc_sh_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MDOC_BLOCK == n->type) {
+ PAIR_CLASS_INIT(&tag, "section");
+ print_otag(h, TAG_DIV, 1, &tag);
+ return(1);
+ } else if (MDOC_BODY == n->type)
+ return(1);
+
+ bufinit(h);
+ bufcat(h, "x");
+
+ for (n = n->child; n && MDOC_TEXT == n->type; ) {
+ bufcat_id(h, n->string);
+ if (NULL != (n = n->next))
+ bufcat_id(h, " ");
+ }
+
+ if (NULL == n) {
+ PAIR_ID_INIT(&tag, h->buf);
+ print_otag(h, TAG_H1, 1, &tag);
+ } else
+ print_otag(h, TAG_H1, 0, NULL);
+
+ return(1);
+}
+
+static int
+mdoc_ss_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MDOC_BLOCK == n->type) {
+ PAIR_CLASS_INIT(&tag, "subsection");
+ print_otag(h, TAG_DIV, 1, &tag);
+ return(1);
+ } else if (MDOC_BODY == n->type)
+ return(1);
+
+ bufinit(h);
+ bufcat(h, "x");
+
+ for (n = n->child; n && MDOC_TEXT == n->type; ) {
+ bufcat_id(h, n->string);
+ if (NULL != (n = n->next))
+ bufcat_id(h, " ");
+ }
+
+ if (NULL == n) {
+ PAIR_ID_INIT(&tag, h->buf);
+ print_otag(h, TAG_H2, 1, &tag);
+ } else
+ print_otag(h, TAG_H2, 0, NULL);
+
+ return(1);
+}
+
+static int
+mdoc_fl_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "flag");
+ print_otag(h, TAG_B, 1, &tag);
+
+ /* `Cm' has no leading hyphen. */
+
+ if (MDOC_Cm == n->tok)
+ return(1);
+
+ print_text(h, "\\-");
+
+ if (n->child)
+ h->flags |= HTML_NOSPACE;
+ else if (n->next && n->next->line == n->line)
+ h->flags |= HTML_NOSPACE;
+
+ return(1);
+}
+
+static int
+mdoc_nd_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MDOC_BODY != n->type)
+ return(1);
+
+ /* XXX: this tag in theory can contain block elements. */
+
+ print_text(h, "\\(em");
+ PAIR_CLASS_INIT(&tag, "desc");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_nm_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+ struct roffsu su;
+ int len;
+
+ switch (n->type) {
+ case MDOC_ELEM:
+ synopsis_pre(h, n);
+ PAIR_CLASS_INIT(&tag, "name");
+ print_otag(h, TAG_B, 1, &tag);
+ if (NULL == n->child && meta->name)
+ print_text(h, meta->name);
+ return(1);
+ case MDOC_HEAD:
+ print_otag(h, TAG_TD, 0, NULL);
+ if (NULL == n->child && meta->name)
+ print_text(h, meta->name);
+ return(1);
+ case MDOC_BODY:
+ print_otag(h, TAG_TD, 0, NULL);
+ return(1);
+ default:
+ break;
+ }
+
+ synopsis_pre(h, n);
+ PAIR_CLASS_INIT(&tag, "synopsis");
+ print_otag(h, TAG_TABLE, 1, &tag);
+
+ for (len = 0, n = n->child; n; n = n->next)
+ if (MDOC_TEXT == n->type)
+ len += html_strlen(n->string);
+
+ if (0 == len && meta->name)
+ len = html_strlen(meta->name);
+
+ SCALE_HS_INIT(&su, len);
+ bufinit(h);
+ bufcat_su(h, "width", &su);
+ PAIR_STYLE_INIT(&tag, h);
+ print_otag(h, TAG_COL, 1, &tag);
+ print_otag(h, TAG_COL, 0, NULL);
+ print_otag(h, TAG_TBODY, 0, NULL);
+ print_otag(h, TAG_TR, 0, NULL);
+ return(1);
+}
+
+static int
+mdoc_xr_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+
+ if (NULL == n->child)
+ return(0);
+
+ PAIR_CLASS_INIT(&tag[0], "link-man");
+
+ if (h->base_man) {
+ buffmt_man(h, n->child->string,
+ n->child->next ?
+ n->child->next->string : NULL);
+ PAIR_HREF_INIT(&tag[1], h->buf);
+ print_otag(h, TAG_A, 2, tag);
+ } else
+ print_otag(h, TAG_A, 1, tag);
+
+ n = n->child;
+ print_text(h, n->string);
+
+ if (NULL == (n = n->next))
+ return(0);
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "(");
+ h->flags |= HTML_NOSPACE;
+ print_text(h, n->string);
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ")");
+ return(0);
+}
+
+static int
+mdoc_ns_pre(MDOC_ARGS)
+{
+
+ if ( ! (MDOC_LINE & n->flags))
+ h->flags |= HTML_NOSPACE;
+ return(1);
+}
+
+static int
+mdoc_ar_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "arg");
+ print_otag(h, TAG_I, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_xx_pre(MDOC_ARGS)
+{
+ const char *pp;
+ struct htmlpair tag;
+ int flags;
+
+ switch (n->tok) {
+ case MDOC_Bsx:
+ pp = "BSD/OS";
+ break;
+ case MDOC_Dx:
+ pp = "DragonFly";
+ break;
+ case MDOC_Fx:
+ pp = "FreeBSD";
+ break;
+ case MDOC_Nx:
+ pp = "NetBSD";
+ break;
+ case MDOC_Ox:
+ pp = "OpenBSD";
+ break;
+ case MDOC_Ux:
+ pp = "UNIX";
+ break;
+ default:
+ return(1);
+ }
+
+ PAIR_CLASS_INIT(&tag, "unix");
+ print_otag(h, TAG_SPAN, 1, &tag);
+
+ print_text(h, pp);
+ if (n->child) {
+ flags = h->flags;
+ h->flags |= HTML_KEEP;
+ print_text(h, n->child->string);
+ h->flags = flags;
+ }
+ return(0);
+}
+
+static int
+mdoc_bx_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "unix");
+ print_otag(h, TAG_SPAN, 1, &tag);
+
+ if (NULL != (n = n->child)) {
+ print_text(h, n->string);
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "BSD");
+ } else {
+ print_text(h, "BSD");
+ return(0);
+ }
+
+ if (NULL != (n = n->next)) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "-");
+ h->flags |= HTML_NOSPACE;
+ print_text(h, n->string);
+ }
+
+ return(0);
+}
+
+static int
+mdoc_it_pre(MDOC_ARGS)
+{
+ struct roffsu su;
+ enum mdoc_list type;
+ struct htmlpair tag[2];
+ const struct mdoc_node *bl;
+
+ bl = n->parent;
+ while (bl && MDOC_Bl != bl->tok)
+ bl = bl->parent;
+
+ assert(bl);
+
+ type = bl->norm->Bl.type;
+
+ assert(lists[type]);
+ PAIR_CLASS_INIT(&tag[0], lists[type]);
+
+ bufinit(h);
+
+ if (MDOC_HEAD == n->type) {
+ switch (type) {
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_item:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ /* FALLTHROUGH */
+ case LIST_enum:
+ return(0);
+ case LIST_diag:
+ /* FALLTHROUGH */
+ case LIST_hang:
+ /* FALLTHROUGH */
+ case LIST_inset:
+ /* FALLTHROUGH */
+ case LIST_ohang:
+ /* FALLTHROUGH */
+ case LIST_tag:
+ SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
+ bufcat_su(h, "margin-top", &su);
+ PAIR_STYLE_INIT(&tag[1], h);
+ print_otag(h, TAG_DT, 2, tag);
+ if (LIST_diag != type)
+ break;
+ PAIR_CLASS_INIT(&tag[0], "diag");
+ print_otag(h, TAG_B, 1, tag);
+ break;
+ case LIST_column:
+ break;
+ default:
+ break;
+ }
+ } else if (MDOC_BODY == n->type) {
+ switch (type) {
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_enum:
+ /* FALLTHROUGH */
+ case LIST_item:
+ SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
+ bufcat_su(h, "margin-top", &su);
+ PAIR_STYLE_INIT(&tag[1], h);
+ print_otag(h, TAG_LI, 2, tag);
+ break;
+ case LIST_diag:
+ /* FALLTHROUGH */
+ case LIST_hang:
+ /* FALLTHROUGH */
+ case LIST_inset:
+ /* FALLTHROUGH */
+ case LIST_ohang:
+ /* FALLTHROUGH */
+ case LIST_tag:
+ if (NULL == bl->norm->Bl.width) {
+ print_otag(h, TAG_DD, 1, tag);
+ break;
+ }
+ a2width(bl->norm->Bl.width, &su);
+ bufcat_su(h, "margin-left", &su);
+ PAIR_STYLE_INIT(&tag[1], h);
+ print_otag(h, TAG_DD, 2, tag);
+ break;
+ case LIST_column:
+ SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
+ bufcat_su(h, "margin-top", &su);
+ PAIR_STYLE_INIT(&tag[1], h);
+ print_otag(h, TAG_TD, 2, tag);
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (type) {
+ case LIST_column:
+ print_otag(h, TAG_TR, 1, tag);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return(1);
+}
+
+static int
+mdoc_bl_pre(MDOC_ARGS)
+{
+ int i;
+ struct htmlpair tag[3];
+ struct roffsu su;
+ char buf[BUFSIZ];
+
+ if (MDOC_BODY == n->type) {
+ if (LIST_column == n->norm->Bl.type)
+ print_otag(h, TAG_TBODY, 0, NULL);
+ return(1);
+ }
+
+ if (MDOC_HEAD == n->type) {
+ if (LIST_column != n->norm->Bl.type)
+ return(0);
+
+ /*
+ * For each column, print out the <COL> tag with our
+ * suggested width. The last column gets min-width, as
+ * in terminal mode it auto-sizes to the width of the
+ * screen and we want to preserve that behaviour.
+ */
+
+ for (i = 0; i < (int)n->norm->Bl.ncols; i++) {
+ bufinit(h);
+ a2width(n->norm->Bl.cols[i], &su);
+ if (i < (int)n->norm->Bl.ncols - 1)
+ bufcat_su(h, "width", &su);
+ else
+ bufcat_su(h, "min-width", &su);
+ PAIR_STYLE_INIT(&tag[0], h);
+ print_otag(h, TAG_COL, 1, tag);
+ }
+
+ return(0);
+ }
+
+ SCALE_VS_INIT(&su, 0);
+ bufinit(h);
+ bufcat_su(h, "margin-top", &su);
+ bufcat_su(h, "margin-bottom", &su);
+ PAIR_STYLE_INIT(&tag[0], h);
+
+ assert(lists[n->norm->Bl.type]);
+ (void)strlcpy(buf, "list ", BUFSIZ);
+ (void)strlcat(buf, lists[n->norm->Bl.type], BUFSIZ);
+ PAIR_INIT(&tag[1], ATTR_CLASS, buf);
+
+ /* Set the block's left-hand margin. */
+
+ if (n->norm->Bl.offs) {
+ a2offs(n->norm->Bl.offs, &su);
+ bufcat_su(h, "margin-left", &su);
+ }
+
+ switch (n->norm->Bl.type) {
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ /* FALLTHROUGH */
+ case LIST_item:
+ print_otag(h, TAG_UL, 2, tag);
+ break;
+ case LIST_enum:
+ print_otag(h, TAG_OL, 2, tag);
+ break;
+ case LIST_diag:
+ /* FALLTHROUGH */
+ case LIST_hang:
+ /* FALLTHROUGH */
+ case LIST_inset:
+ /* FALLTHROUGH */
+ case LIST_ohang:
+ /* FALLTHROUGH */
+ case LIST_tag:
+ print_otag(h, TAG_DL, 2, tag);
+ break;
+ case LIST_column:
+ print_otag(h, TAG_TABLE, 2, tag);
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ return(1);
+}
+
+static int
+mdoc_ex_pre(MDOC_ARGS)
+{
+ struct tag *t;
+ struct htmlpair tag;
+ int nchild;
+
+ if (n->prev)
+ print_otag(h, TAG_BR, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag, "utility");
+
+ print_text(h, "The");
+
+ nchild = n->nchild;
+ for (n = n->child; n; n = n->next) {
+ assert(MDOC_TEXT == n->type);
+
+ t = print_otag(h, TAG_B, 1, &tag);
+ print_text(h, n->string);
+ print_tagq(h, t);
+
+ if (nchild > 2 && n->next) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ",");
+ }
+
+ if (n->next && NULL == n->next->next)
+ print_text(h, "and");
+ }
+
+ if (nchild > 1)
+ print_text(h, "utilities exit\\~0");
+ else
+ print_text(h, "utility exits\\~0");
+
+ print_text(h, "on success, and\\~>0 if an error occurs.");
+ return(0);
+}
+
+static int
+mdoc_em_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "emph");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_d1_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+ struct roffsu su;
+
+ if (MDOC_BLOCK != n->type)
+ return(1);
+
+ SCALE_VS_INIT(&su, 0);
+ bufinit(h);
+ bufcat_su(h, "margin-top", &su);
+ bufcat_su(h, "margin-bottom", &su);
+ PAIR_STYLE_INIT(&tag[0], h);
+ print_otag(h, TAG_BLOCKQUOTE, 1, tag);
+
+ /* BLOCKQUOTE needs a block body. */
+
+ PAIR_CLASS_INIT(&tag[0], "display");
+ print_otag(h, TAG_DIV, 1, tag);
+
+ if (MDOC_Dl == n->tok) {
+ PAIR_CLASS_INIT(&tag[0], "lit");
+ print_otag(h, TAG_CODE, 1, tag);
+ }
+
+ return(1);
+}
+
+static int
+mdoc_sx_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+
+ bufinit(h);
+ bufcat(h, "#x");
+
+ for (n = n->child; n; ) {
+ bufcat_id(h, n->string);
+ if (NULL != (n = n->next))
+ bufcat_id(h, " ");
+ }
+
+ PAIR_CLASS_INIT(&tag[0], "link-sec");
+ PAIR_HREF_INIT(&tag[1], h->buf);
+
+ print_otag(h, TAG_I, 1, tag);
+ print_otag(h, TAG_A, 2, tag);
+ return(1);
+}
+
+static int
+mdoc_bd_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+ int comp, sv;
+ const struct mdoc_node *nn;
+ struct roffsu su;
+
+ if (MDOC_HEAD == n->type)
+ return(0);
+
+ if (MDOC_BLOCK == n->type) {
+ comp = n->norm->Bd.comp;
+ for (nn = n; nn && ! comp; nn = nn->parent) {
+ if (MDOC_BLOCK != nn->type)
+ continue;
+ if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
+ comp = 1;
+ if (nn->prev)
+ break;
+ }
+ if ( ! comp)
+ print_otag(h, TAG_P, 0, NULL);
+ return(1);
+ }
+
+ SCALE_HS_INIT(&su, 0);
+ if (n->norm->Bd.offs)
+ a2offs(n->norm->Bd.offs, &su);
+
+ bufinit(h);
+ bufcat_su(h, "margin-left", &su);
+ PAIR_STYLE_INIT(&tag[0], h);
+
+ if (DISP_unfilled != n->norm->Bd.type &&
+ DISP_literal != n->norm->Bd.type) {
+ PAIR_CLASS_INIT(&tag[1], "display");
+ print_otag(h, TAG_DIV, 2, tag);
+ return(1);
+ }
+
+ PAIR_CLASS_INIT(&tag[1], "lit display");
+ print_otag(h, TAG_PRE, 2, tag);
+
+ /* This can be recursive: save & set our literal state. */
+
+ sv = h->flags & HTML_LITERAL;
+ h->flags |= HTML_LITERAL;
+
+ for (nn = n->child; nn; nn = nn->next) {
+ print_mdoc_node(meta, nn, h);
+ /*
+ * If the printed node flushes its own line, then we
+ * needn't do it here as well. This is hacky, but the
+ * notion of selective eoln whitespace is pretty dumb
+ * anyway, so don't sweat it.
+ */
+ switch (nn->tok) {
+ case MDOC_Sm:
+ /* FALLTHROUGH */
+ case MDOC_br:
+ /* FALLTHROUGH */
+ case MDOC_sp:
+ /* FALLTHROUGH */
+ case MDOC_Bl:
+ /* FALLTHROUGH */
+ case MDOC_D1:
+ /* FALLTHROUGH */
+ case MDOC_Dl:
+ /* FALLTHROUGH */
+ case MDOC_Lp:
+ /* FALLTHROUGH */
+ case MDOC_Pp:
+ continue;
+ default:
+ break;
+ }
+ if (nn->next && nn->next->line == nn->line)
+ continue;
+ else if (nn->next)
+ print_text(h, "\n");
+
+ h->flags |= HTML_NOSPACE;
+ }
+
+ if (0 == sv)
+ h->flags &= ~HTML_LITERAL;
+
+ return(0);
+}
+
+static int
+mdoc_pa_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "file");
+ print_otag(h, TAG_I, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_ad_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "addr");
+ print_otag(h, TAG_I, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_an_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ /* TODO: -split and -nosplit (see termp_an_pre()). */
+
+ PAIR_CLASS_INIT(&tag, "author");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_cd_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ synopsis_pre(h, n);
+ PAIR_CLASS_INIT(&tag, "config");
+ print_otag(h, TAG_B, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_dv_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "define");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_ev_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "env");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_er_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "errno");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_fa_pre(MDOC_ARGS)
+{
+ const struct mdoc_node *nn;
+ struct htmlpair tag;
+ struct tag *t;
+
+ PAIR_CLASS_INIT(&tag, "farg");
+ if (n->parent->tok != MDOC_Fo) {
+ print_otag(h, TAG_I, 1, &tag);
+ return(1);
+ }
+
+ for (nn = n->child; nn; nn = nn->next) {
+ t = print_otag(h, TAG_I, 1, &tag);
+ print_text(h, nn->string);
+ print_tagq(h, t);
+ if (nn->next) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ",");
+ }
+ }
+
+ if (n->child && n->next && n->next->tok == MDOC_Fa) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ",");
+ }
+
+ return(0);
+}
+
+static int
+mdoc_fd_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+ char buf[BUFSIZ];
+ size_t sz;
+ int i;
+ struct tag *t;
+
+ synopsis_pre(h, n);
+
+ if (NULL == (n = n->child))
+ return(0);
+
+ assert(MDOC_TEXT == n->type);
+
+ if (strcmp(n->string, "#include")) {
+ PAIR_CLASS_INIT(&tag[0], "macro");
+ print_otag(h, TAG_B, 1, tag);
+ return(1);
+ }
+
+ PAIR_CLASS_INIT(&tag[0], "includes");
+ print_otag(h, TAG_B, 1, tag);
+ print_text(h, n->string);
+
+ if (NULL != (n = n->next)) {
+ assert(MDOC_TEXT == n->type);
+
+ /*
+ * XXX This is broken and not easy to fix.
+ * When using -Oincludes, truncation may occur.
+ * Dynamic allocation wouldn't help because
+ * passing long strings to buffmt_includes()
+ * does not work either.
+ */
+
+ strlcpy(buf, '<' == *n->string || '"' == *n->string ?
+ n->string + 1 : n->string, BUFSIZ);
+
+ sz = strlen(buf);
+ if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1]))
+ buf[sz - 1] = '\0';
+
+ PAIR_CLASS_INIT(&tag[0], "link-includes");
+
+ i = 1;
+ if (h->base_includes) {
+ buffmt_includes(h, buf);
+ PAIR_HREF_INIT(&tag[i], h->buf);
+ i++;
+ }
+
+ t = print_otag(h, TAG_A, i, tag);
+ print_text(h, n->string);
+ print_tagq(h, t);
+
+ n = n->next;
+ }
+
+ for ( ; n; n = n->next) {
+ assert(MDOC_TEXT == n->type);
+ print_text(h, n->string);
+ }
+
+ return(0);
+}
+
+static int
+mdoc_vt_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MDOC_BLOCK == n->type) {
+ synopsis_pre(h, n);
+ return(1);
+ } else if (MDOC_ELEM == n->type) {
+ synopsis_pre(h, n);
+ } else if (MDOC_HEAD == n->type)
+ return(0);
+
+ PAIR_CLASS_INIT(&tag, "type");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_ft_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ synopsis_pre(h, n);
+ PAIR_CLASS_INIT(&tag, "ftype");
+ print_otag(h, TAG_I, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_fn_pre(MDOC_ARGS)
+{
+ struct tag *t;
+ struct htmlpair tag[2];
+ char nbuf[BUFSIZ];
+ const char *sp, *ep;
+ int sz, i, pretty;
+
+ pretty = MDOC_SYNPRETTY & n->flags;
+ synopsis_pre(h, n);
+
+ /* Split apart into type and name. */
+ assert(n->child->string);
+ sp = n->child->string;
+
+ ep = strchr(sp, ' ');
+ if (NULL != ep) {
+ PAIR_CLASS_INIT(&tag[0], "ftype");
+ t = print_otag(h, TAG_I, 1, tag);
+
+ while (ep) {
+ sz = MIN((int)(ep - sp), BUFSIZ - 1);
+ (void)memcpy(nbuf, sp, (size_t)sz);
+ nbuf[sz] = '\0';
+ print_text(h, nbuf);
+ sp = ++ep;
+ ep = strchr(sp, ' ');
+ }
+ print_tagq(h, t);
+ }
+
+ PAIR_CLASS_INIT(&tag[0], "fname");
+
+ /*
+ * FIXME: only refer to IDs that we know exist.
+ */
+
+#if 0
+ if (MDOC_SYNPRETTY & n->flags) {
+ nbuf[0] = '\0';
+ html_idcat(nbuf, sp, BUFSIZ);
+ PAIR_ID_INIT(&tag[1], nbuf);
+ } else {
+ strlcpy(nbuf, "#", BUFSIZ);
+ html_idcat(nbuf, sp, BUFSIZ);
+ PAIR_HREF_INIT(&tag[1], nbuf);
+ }
+#endif
+
+ t = print_otag(h, TAG_B, 1, tag);
+
+ if (sp)
+ print_text(h, sp);
+
+ print_tagq(h, t);
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "(");
+ h->flags |= HTML_NOSPACE;
+
+ PAIR_CLASS_INIT(&tag[0], "farg");
+ bufinit(h);
+ bufcat_style(h, "white-space", "nowrap");
+ PAIR_STYLE_INIT(&tag[1], h);
+
+ for (n = n->child->next; n; n = n->next) {
+ i = 1;
+ if (MDOC_SYNPRETTY & n->flags)
+ i = 2;
+ t = print_otag(h, TAG_I, i, tag);
+ print_text(h, n->string);
+ print_tagq(h, t);
+ if (n->next) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ",");
+ }
+ }
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ")");
+
+ if (pretty) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ";");
+ }
+
+ return(0);
+}
+
+static int
+mdoc_sm_pre(MDOC_ARGS)
+{
+
+ if (NULL == n->child)
+ h->flags ^= HTML_NONOSPACE;
+ else if (0 == strcmp("on", n->child->string))
+ h->flags &= ~HTML_NONOSPACE;
+ else
+ h->flags |= HTML_NONOSPACE;
+
+ if ( ! (HTML_NONOSPACE & h->flags))
+ h->flags &= ~HTML_NOSPACE;
+
+ return(0);
+}
+
+static int
+mdoc_skip_pre(MDOC_ARGS)
+{
+
+ return(0);
+}
+
+static int
+mdoc_pp_pre(MDOC_ARGS)
+{
+
+ print_otag(h, TAG_P, 0, NULL);
+ return(0);
+}
+
+static int
+mdoc_sp_pre(MDOC_ARGS)
+{
+ struct roffsu su;
+ struct htmlpair tag;
+
+ SCALE_VS_INIT(&su, 1);
+
+ if (MDOC_sp == n->tok) {
+ if (NULL != (n = n->child))
+ if ( ! a2roffsu(n->string, &su, SCALE_VS))
+ SCALE_VS_INIT(&su, atoi(n->string));
+ } else
+ su.scale = 0.0;
+
+ bufinit(h);
+ bufcat_su(h, "height", &su);
+ PAIR_STYLE_INIT(&tag, h);
+ print_otag(h, TAG_DIV, 1, &tag);
+
+ /* So the div isn't empty: */
+ print_text(h, "\\~");
+
+ return(0);
+
+}
+
+static int
+mdoc_lk_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+
+ if (NULL == (n = n->child))
+ return(0);
+
+ assert(MDOC_TEXT == n->type);
+
+ PAIR_CLASS_INIT(&tag[0], "link-ext");
+ PAIR_HREF_INIT(&tag[1], n->string);
+
+ print_otag(h, TAG_A, 2, tag);
+
+ if (NULL == n->next)
+ print_text(h, n->string);
+
+ for (n = n->next; n; n = n->next)
+ print_text(h, n->string);
+
+ return(0);
+}
+
+static int
+mdoc_mt_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+ struct tag *t;
+
+ PAIR_CLASS_INIT(&tag[0], "link-mail");
+
+ for (n = n->child; n; n = n->next) {
+ assert(MDOC_TEXT == n->type);
+
+ bufinit(h);
+ bufcat(h, "mailto:");
+ bufcat(h, n->string);
+
+ PAIR_HREF_INIT(&tag[1], h->buf);
+ t = print_otag(h, TAG_A, 2, tag);
+ print_text(h, n->string);
+ print_tagq(h, t);
+ }
+
+ return(0);
+}
+
+static int
+mdoc_fo_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+ struct tag *t;
+
+ if (MDOC_BODY == n->type) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "(");
+ h->flags |= HTML_NOSPACE;
+ return(1);
+ } else if (MDOC_BLOCK == n->type) {
+ synopsis_pre(h, n);
+ return(1);
+ }
+
+ /* XXX: we drop non-initial arguments as per groff. */
+
+ assert(n->child);
+ assert(n->child->string);
+
+ PAIR_CLASS_INIT(&tag, "fname");
+ t = print_otag(h, TAG_B, 1, &tag);
+ print_text(h, n->child->string);
+ print_tagq(h, t);
+ return(0);
+}
+
+static void
+mdoc_fo_post(MDOC_ARGS)
+{
+
+ if (MDOC_BODY != n->type)
+ return;
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ")");
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ";");
+}
+
+static int
+mdoc_in_pre(MDOC_ARGS)
+{
+ struct tag *t;
+ struct htmlpair tag[2];
+ int i;
+
+ synopsis_pre(h, n);
+
+ PAIR_CLASS_INIT(&tag[0], "includes");
+ print_otag(h, TAG_B, 1, tag);
+
+ /*
+ * The first argument of the `In' gets special treatment as
+ * being a linked value. Subsequent values are printed
+ * afterward. groff does similarly. This also handles the case
+ * of no children.
+ */
+
+ if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags)
+ print_text(h, "#include");
+
+ print_text(h, "<");
+ h->flags |= HTML_NOSPACE;
+
+ if (NULL != (n = n->child)) {
+ assert(MDOC_TEXT == n->type);
+
+ PAIR_CLASS_INIT(&tag[0], "link-includes");
+
+ i = 1;
+ if (h->base_includes) {
+ buffmt_includes(h, n->string);
+ PAIR_HREF_INIT(&tag[i], h->buf);
+ i++;
+ }
+
+ t = print_otag(h, TAG_A, i, tag);
+ print_text(h, n->string);
+ print_tagq(h, t);
+
+ n = n->next;
+ }
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ">");
+
+ for ( ; n; n = n->next) {
+ assert(MDOC_TEXT == n->type);
+ print_text(h, n->string);
+ }
+
+ return(0);
+}
+
+static int
+mdoc_ic_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "cmd");
+ print_otag(h, TAG_B, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_rv_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+ struct tag *t;
+ int nchild;
+
+ if (n->prev)
+ print_otag(h, TAG_BR, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag, "fname");
+
+ nchild = n->nchild;
+ if (nchild > 0) {
+ print_text(h, "The");
+
+ for (n = n->child; n; n = n->next) {
+ t = print_otag(h, TAG_B, 1, &tag);
+ print_text(h, n->string);
+ print_tagq(h, t);
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "()");
+
+ if (n->next == NULL)
+ continue;
+
+ if (nchild > 2) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ",");
+ }
+ if (n->next->next == NULL)
+ print_text(h, "and");
+ }
+
+ if (nchild > 1)
+ print_text(h, "functions return");
+ else
+ print_text(h, "function returns");
+
+ print_text(h, "the value\\~0 if successful;");
+ } else
+ print_text(h, "Upon successful completion,"
+ " the value\\~0 is returned;");
+
+ print_text(h, "otherwise the value\\~\\-1 is returned"
+ " and the global variable");
+
+ PAIR_CLASS_INIT(&tag, "var");
+ t = print_otag(h, TAG_B, 1, &tag);
+ print_text(h, "errno");
+ print_tagq(h, t);
+ print_text(h, "is set to indicate the error.");
+ return(0);
+}
+
+static int
+mdoc_va_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "var");
+ print_otag(h, TAG_B, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_ap_pre(MDOC_ARGS)
+{
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "\\(aq");
+ h->flags |= HTML_NOSPACE;
+ return(1);
+}
+
+static int
+mdoc_bf_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+ struct roffsu su;
+
+ if (MDOC_HEAD == n->type)
+ return(0);
+ else if (MDOC_BODY != n->type)
+ return(1);
+
+ if (FONT_Em == n->norm->Bf.font)
+ PAIR_CLASS_INIT(&tag[0], "emph");
+ else if (FONT_Sy == n->norm->Bf.font)
+ PAIR_CLASS_INIT(&tag[0], "symb");
+ else if (FONT_Li == n->norm->Bf.font)
+ PAIR_CLASS_INIT(&tag[0], "lit");
+ else
+ PAIR_CLASS_INIT(&tag[0], "none");
+
+ /*
+ * We want this to be inline-formatted, but needs to be div to
+ * accept block children.
+ */
+ bufinit(h);
+ bufcat_style(h, "display", "inline");
+ SCALE_HS_INIT(&su, 1);
+ /* Needs a left-margin for spacing. */
+ bufcat_su(h, "margin-left", &su);
+ PAIR_STYLE_INIT(&tag[1], h);
+ print_otag(h, TAG_DIV, 2, tag);
+ return(1);
+}
+
+static int
+mdoc_ms_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "symb");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_igndelim_pre(MDOC_ARGS)
+{
+
+ h->flags |= HTML_IGNDELIM;
+ return(1);
+}
+
+static void
+mdoc_pf_post(MDOC_ARGS)
+{
+
+ h->flags |= HTML_NOSPACE;
+}
+
+static int
+mdoc_rs_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MDOC_BLOCK != n->type)
+ return(1);
+
+ if (n->prev && SEC_SEE_ALSO == n->sec)
+ print_otag(h, TAG_P, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag, "ref");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_li_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "lit");
+ print_otag(h, TAG_CODE, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_sy_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ PAIR_CLASS_INIT(&tag, "symb");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc_bt_pre(MDOC_ARGS)
+{
+
+ print_text(h, "is currently in beta test.");
+ return(0);
+}
+
+static int
+mdoc_ud_pre(MDOC_ARGS)
+{
+
+ print_text(h, "currently under development.");
+ return(0);
+}
+
+static int
+mdoc_lb_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev)
+ print_otag(h, TAG_BR, 0, NULL);
+
+ PAIR_CLASS_INIT(&tag, "lib");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ return(1);
+}
+
+static int
+mdoc__x_pre(MDOC_ARGS)
+{
+ struct htmlpair tag[2];
+ enum htmltag t;
+
+ t = TAG_SPAN;
+
+ switch (n->tok) {
+ case MDOC__A:
+ PAIR_CLASS_INIT(&tag[0], "ref-auth");
+ if (n->prev && MDOC__A == n->prev->tok)
+ if (NULL == n->next || MDOC__A != n->next->tok)
+ print_text(h, "and");
+ break;
+ case MDOC__B:
+ PAIR_CLASS_INIT(&tag[0], "ref-book");
+ t = TAG_I;
+ break;
+ case MDOC__C:
+ PAIR_CLASS_INIT(&tag[0], "ref-city");
+ break;
+ case MDOC__D:
+ PAIR_CLASS_INIT(&tag[0], "ref-date");
+ break;
+ case MDOC__I:
+ PAIR_CLASS_INIT(&tag[0], "ref-issue");
+ t = TAG_I;
+ break;
+ case MDOC__J:
+ PAIR_CLASS_INIT(&tag[0], "ref-jrnl");
+ t = TAG_I;
+ break;
+ case MDOC__N:
+ PAIR_CLASS_INIT(&tag[0], "ref-num");
+ break;
+ case MDOC__O:
+ PAIR_CLASS_INIT(&tag[0], "ref-opt");
+ break;
+ case MDOC__P:
+ PAIR_CLASS_INIT(&tag[0], "ref-page");
+ break;
+ case MDOC__Q:
+ PAIR_CLASS_INIT(&tag[0], "ref-corp");
+ break;
+ case MDOC__R:
+ PAIR_CLASS_INIT(&tag[0], "ref-rep");
+ break;
+ case MDOC__T:
+ PAIR_CLASS_INIT(&tag[0], "ref-title");
+ break;
+ case MDOC__U:
+ PAIR_CLASS_INIT(&tag[0], "link-ref");
+ break;
+ case MDOC__V:
+ PAIR_CLASS_INIT(&tag[0], "ref-vol");
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ if (MDOC__U != n->tok) {
+ print_otag(h, t, 1, tag);
+ return(1);
+ }
+
+ PAIR_HREF_INIT(&tag[1], n->child->string);
+ print_otag(h, TAG_A, 2, tag);
+
+ return(1);
+}
+
+static void
+mdoc__x_post(MDOC_ARGS)
+{
+
+ if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
+ if (NULL == n->next->next || MDOC__A != n->next->next->tok)
+ if (NULL == n->prev || MDOC__A != n->prev->tok)
+ return;
+
+ /* TODO: %U */
+
+ if (NULL == n->parent || MDOC_Rs != n->parent->tok)
+ return;
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, n->next ? "," : ".");
+}
+
+static int
+mdoc_bk_pre(MDOC_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ break;
+ case MDOC_HEAD:
+ return(0);
+ case MDOC_BODY:
+ if (n->parent->args || 0 == n->prev->nchild)
+ h->flags |= HTML_PREKEEP;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ return(1);
+}
+
+static void
+mdoc_bk_post(MDOC_ARGS)
+{
+
+ if (MDOC_BODY == n->type)
+ h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
+}
+
+static int
+mdoc_quote_pre(MDOC_ARGS)
+{
+ struct htmlpair tag;
+
+ if (MDOC_BODY != n->type)
+ return(1);
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ /* FALLTHROUGH */
+ case MDOC_Aq:
+ print_text(h, "\\(la");
+ break;
+ case MDOC_Bro:
+ /* FALLTHROUGH */
+ case MDOC_Brq:
+ print_text(h, "\\(lC");
+ break;
+ case MDOC_Bo:
+ /* FALLTHROUGH */
+ case MDOC_Bq:
+ print_text(h, "\\(lB");
+ break;
+ case MDOC_Oo:
+ /* FALLTHROUGH */
+ case MDOC_Op:
+ print_text(h, "\\(lB");
+ h->flags |= HTML_NOSPACE;
+ PAIR_CLASS_INIT(&tag, "opt");
+ print_otag(h, TAG_SPAN, 1, &tag);
+ break;
+ case MDOC_En:
+ if (NULL == n->norm->Es ||
+ NULL == n->norm->Es->child)
+ return(1);
+ print_text(h, n->norm->Es->child->string);
+ break;
+ case MDOC_Eo:
+ break;
+ case MDOC_Do:
+ /* FALLTHROUGH */
+ case MDOC_Dq:
+ /* FALLTHROUGH */
+ case MDOC_Qo:
+ /* FALLTHROUGH */
+ case MDOC_Qq:
+ print_text(h, "\\(lq");
+ break;
+ case MDOC_Po:
+ /* FALLTHROUGH */
+ case MDOC_Pq:
+ print_text(h, "(");
+ break;
+ case MDOC_Ql:
+ print_text(h, "\\(oq");
+ h->flags |= HTML_NOSPACE;
+ PAIR_CLASS_INIT(&tag, "lit");
+ print_otag(h, TAG_CODE, 1, &tag);
+ break;
+ case MDOC_So:
+ /* FALLTHROUGH */
+ case MDOC_Sq:
+ print_text(h, "\\(oq");
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ h->flags |= HTML_NOSPACE;
+ return(1);
+}
+
+static void
+mdoc_quote_post(MDOC_ARGS)
+{
+
+ if (MDOC_BODY != n->type)
+ return;
+
+ if (MDOC_En != n->tok)
+ h->flags |= HTML_NOSPACE;
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ /* FALLTHROUGH */
+ case MDOC_Aq:
+ print_text(h, "\\(ra");
+ break;
+ case MDOC_Bro:
+ /* FALLTHROUGH */
+ case MDOC_Brq:
+ print_text(h, "\\(rC");
+ break;
+ case MDOC_Oo:
+ /* FALLTHROUGH */
+ case MDOC_Op:
+ /* FALLTHROUGH */
+ case MDOC_Bo:
+ /* FALLTHROUGH */
+ case MDOC_Bq:
+ print_text(h, "\\(rB");
+ break;
+ case MDOC_En:
+ if (NULL != n->norm->Es &&
+ NULL != n->norm->Es->child &&
+ NULL != n->norm->Es->child->next) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, n->norm->Es->child->next->string);
+ }
+ break;
+ case MDOC_Eo:
+ break;
+ case MDOC_Qo:
+ /* FALLTHROUGH */
+ case MDOC_Qq:
+ /* FALLTHROUGH */
+ case MDOC_Do:
+ /* FALLTHROUGH */
+ case MDOC_Dq:
+ print_text(h, "\\(rq");
+ break;
+ case MDOC_Po:
+ /* FALLTHROUGH */
+ case MDOC_Pq:
+ print_text(h, ")");
+ break;
+ case MDOC_Ql:
+ /* FALLTHROUGH */
+ case MDOC_So:
+ /* FALLTHROUGH */
+ case MDOC_Sq:
+ print_text(h, "\\(cq");
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+}
diff --git a/contrib/mdocml/mdoc_macro.c b/contrib/mdocml/mdoc_macro.c
new file mode 100644
index 0000000..0f5b557
--- /dev/null
+++ b/contrib/mdocml/mdoc_macro.c
@@ -0,0 +1,1822 @@
+/* $Id: mdoc_macro.c,v 1.139 2014/08/01 17:27:44 schwarze Exp $ */
+/*
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+enum rew { /* see rew_dohalt() */
+ REWIND_NONE,
+ REWIND_THIS,
+ REWIND_MORE,
+ REWIND_FORCE,
+ REWIND_LATER,
+ REWIND_ERROR
+};
+
+static int blk_full(MACRO_PROT_ARGS);
+static int blk_exp_close(MACRO_PROT_ARGS);
+static int blk_part_exp(MACRO_PROT_ARGS);
+static int blk_part_imp(MACRO_PROT_ARGS);
+static int ctx_synopsis(MACRO_PROT_ARGS);
+static int in_line_eoln(MACRO_PROT_ARGS);
+static int in_line_argn(MACRO_PROT_ARGS);
+static int in_line(MACRO_PROT_ARGS);
+static int phrase_ta(MACRO_PROT_ARGS);
+
+static int dword(struct mdoc *, int, int, const char *,
+ enum mdelim, int);
+static int append_delims(struct mdoc *,
+ int, int *, char *);
+static enum mdoct lookup(enum mdoct, const char *);
+static enum mdoct lookup_raw(const char *);
+static int make_pending(struct mdoc_node *, enum mdoct,
+ struct mdoc *, int, int);
+static int phrase(struct mdoc *, int, int, char *);
+static enum mdoct rew_alt(enum mdoct);
+static enum rew rew_dohalt(enum mdoct, enum mdoc_type,
+ const struct mdoc_node *);
+static int rew_elem(struct mdoc *, enum mdoct);
+static int rew_last(struct mdoc *,
+ const struct mdoc_node *);
+static int rew_sub(enum mdoc_type, struct mdoc *,
+ enum mdoct, int, int);
+
+const struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ap */
+ { in_line_eoln, MDOC_PROLOGUE }, /* Dd */
+ { in_line_eoln, MDOC_PROLOGUE }, /* Dt */
+ { in_line_eoln, MDOC_PROLOGUE }, /* Os */
+ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* Sh */
+ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* Ss */
+ { in_line_eoln, 0 }, /* Pp */
+ { blk_part_imp, MDOC_PARSED | MDOC_JOIN }, /* D1 */
+ { blk_part_imp, MDOC_PARSED | MDOC_JOIN }, /* Dl */
+ { blk_full, MDOC_EXPLICIT }, /* Bd */
+ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ed */
+ { blk_full, MDOC_EXPLICIT }, /* Bl */
+ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* El */
+ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* It */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* An */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Cd */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Er */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ev */
+ { in_line_eoln, 0 }, /* Ex */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fa */
+ { in_line_eoln, 0 }, /* Fd */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fl */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fn */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ft */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ic */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Li */
+ { blk_full, MDOC_JOIN }, /* Nd */
+ { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Op */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ot */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Pa */
+ { in_line_eoln, 0 }, /* Rv */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Va */
+ { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */
+ { in_line_eoln, MDOC_JOIN }, /* %A */
+ { in_line_eoln, MDOC_JOIN }, /* %B */
+ { in_line_eoln, MDOC_JOIN }, /* %D */
+ { in_line_eoln, MDOC_JOIN }, /* %I */
+ { in_line_eoln, MDOC_JOIN }, /* %J */
+ { in_line_eoln, 0 }, /* %N */
+ { in_line_eoln, MDOC_JOIN }, /* %O */
+ { in_line_eoln, 0 }, /* %P */
+ { in_line_eoln, MDOC_JOIN }, /* %R */
+ { in_line_eoln, MDOC_JOIN }, /* %T */
+ { in_line_eoln, 0 }, /* %V */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Ac */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Ao */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Aq */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* At */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Bc */
+ { blk_full, MDOC_EXPLICIT }, /* Bf */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Bo */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Bq */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bsx */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bx */
+ { in_line_eoln, 0 }, /* Db */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Dc */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Do */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Dq */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ec */
+ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ef */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Em */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Fx */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ms */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_IGNDELIM | MDOC_JOIN }, /* No */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_IGNDELIM | MDOC_JOIN }, /* Ns */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ox */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Pc */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Pf */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Po */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Pq */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Qc */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ql */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Qo */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Qq */
+ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Re */
+ { blk_full, MDOC_EXPLICIT }, /* Rs */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Sc */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* So */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sq */
+ { in_line_eoln, 0 }, /* Sm */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sx */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sy */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ux */
+ { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Xc */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Xo */
+ { blk_full, MDOC_EXPLICIT | MDOC_CALLABLE }, /* Fo */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Fc */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Oo */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Oc */
+ { blk_full, MDOC_EXPLICIT }, /* Bk */
+ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ek */
+ { in_line_eoln, 0 }, /* Bt */
+ { in_line_eoln, 0 }, /* Hf */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fr */
+ { in_line_eoln, 0 }, /* Ud */
+ { in_line, 0 }, /* Lb */
+ { in_line_eoln, 0 }, /* Lp */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Lk */
+ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Mt */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Brq */
+ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Bro */
+ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_EXPLICIT | MDOC_JOIN }, /* Brc */
+ { in_line_eoln, MDOC_JOIN }, /* %C */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Es */
+ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* En */
+ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */
+ { in_line_eoln, MDOC_JOIN }, /* %Q */
+ { in_line_eoln, 0 }, /* br */
+ { in_line_eoln, 0 }, /* sp */
+ { in_line_eoln, 0 }, /* %U */
+ { phrase_ta, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ta */
+ { in_line_eoln, 0 }, /* ll */
+};
+
+const struct mdoc_macro * const mdoc_macros = __mdoc_macros;
+
+
+/*
+ * This is called at the end of parsing. It must traverse up the tree,
+ * closing out open [implicit] scopes. Obviously, open explicit scopes
+ * are errors.
+ */
+int
+mdoc_macroend(struct mdoc *mdoc)
+{
+ struct mdoc_node *n;
+
+ /* Scan for open explicit scopes. */
+
+ n = MDOC_VALID & mdoc->last->flags ?
+ mdoc->last->parent : mdoc->last;
+
+ for ( ; n; n = n->parent)
+ if (MDOC_BLOCK == n->type &&
+ MDOC_EXPLICIT & mdoc_macros[n->tok].flags)
+ mandoc_msg(MANDOCERR_BLK_NOEND, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+
+ /* Rewind to the first. */
+
+ return(rew_last(mdoc, mdoc->first));
+}
+
+/*
+ * Look up a macro from within a subsequent context.
+ */
+static enum mdoct
+lookup(enum mdoct from, const char *p)
+{
+
+ if ( ! (MDOC_PARSED & mdoc_macros[from].flags))
+ return(MDOC_MAX);
+ return(lookup_raw(p));
+}
+
+/*
+ * Lookup a macro following the initial line macro.
+ */
+static enum mdoct
+lookup_raw(const char *p)
+{
+ enum mdoct res;
+
+ if (MDOC_MAX == (res = mdoc_hash_find(p)))
+ return(MDOC_MAX);
+ if (MDOC_CALLABLE & mdoc_macros[res].flags)
+ return(res);
+ return(MDOC_MAX);
+}
+
+static int
+rew_last(struct mdoc *mdoc, const struct mdoc_node *to)
+{
+ struct mdoc_node *n, *np;
+
+ assert(to);
+ mdoc->next = MDOC_NEXT_SIBLING;
+
+
+ while (mdoc->last != to) {
+ /*
+ * Save the parent here, because we may delete the
+ * mdoc->last node in the post-validation phase and reset
+ * it to mdoc->last->parent, causing a step in the closing
+ * out to be lost.
+ */
+ np = mdoc->last->parent;
+ if ( ! mdoc_valid_post(mdoc))
+ return(0);
+ n = mdoc->last;
+ mdoc->last = np;
+ assert(mdoc->last);
+ mdoc->last->last = n;
+ }
+
+ return(mdoc_valid_post(mdoc));
+}
+
+/*
+ * For a block closing macro, return the corresponding opening one.
+ * Otherwise, return the macro itself.
+ */
+static enum mdoct
+rew_alt(enum mdoct tok)
+{
+ switch (tok) {
+ case MDOC_Ac:
+ return(MDOC_Ao);
+ case MDOC_Bc:
+ return(MDOC_Bo);
+ case MDOC_Brc:
+ return(MDOC_Bro);
+ case MDOC_Dc:
+ return(MDOC_Do);
+ case MDOC_Ec:
+ return(MDOC_Eo);
+ case MDOC_Ed:
+ return(MDOC_Bd);
+ case MDOC_Ef:
+ return(MDOC_Bf);
+ case MDOC_Ek:
+ return(MDOC_Bk);
+ case MDOC_El:
+ return(MDOC_Bl);
+ case MDOC_Fc:
+ return(MDOC_Fo);
+ case MDOC_Oc:
+ return(MDOC_Oo);
+ case MDOC_Pc:
+ return(MDOC_Po);
+ case MDOC_Qc:
+ return(MDOC_Qo);
+ case MDOC_Re:
+ return(MDOC_Rs);
+ case MDOC_Sc:
+ return(MDOC_So);
+ case MDOC_Xc:
+ return(MDOC_Xo);
+ default:
+ return(tok);
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * Rewinding to tok, how do we have to handle *p?
+ * REWIND_NONE: *p would delimit tok, but no tok scope is open
+ * inside *p, so there is no need to rewind anything at all.
+ * REWIND_THIS: *p matches tok, so rewind *p and nothing else.
+ * REWIND_MORE: *p is implicit, rewind it and keep searching for tok.
+ * REWIND_FORCE: *p is explicit, but tok is full, force rewinding *p.
+ * REWIND_LATER: *p is explicit and still open, postpone rewinding.
+ * REWIND_ERROR: No tok block is open at all.
+ */
+static enum rew
+rew_dohalt(enum mdoct tok, enum mdoc_type type,
+ const struct mdoc_node *p)
+{
+
+ /*
+ * No matching token, no delimiting block, no broken block.
+ * This can happen when full implicit macros are called for
+ * the first time but try to rewind their previous
+ * instance anyway.
+ */
+ if (MDOC_ROOT == p->type)
+ return(MDOC_BLOCK == type &&
+ MDOC_EXPLICIT & mdoc_macros[tok].flags ?
+ REWIND_ERROR : REWIND_NONE);
+
+ /*
+ * When starting to rewind, skip plain text
+ * and nodes that have already been rewound.
+ */
+ if (MDOC_TEXT == p->type || MDOC_VALID & p->flags)
+ return(REWIND_MORE);
+
+ /*
+ * The easiest case: Found a matching token.
+ * This applies to both blocks and elements.
+ */
+ tok = rew_alt(tok);
+ if (tok == p->tok)
+ return(p->end ? REWIND_NONE :
+ type == p->type ? REWIND_THIS : REWIND_MORE);
+
+ /*
+ * While elements do require rewinding for themselves,
+ * they never affect rewinding of other nodes.
+ */
+ if (MDOC_ELEM == p->type)
+ return(REWIND_MORE);
+
+ /*
+ * Blocks delimited by our target token get REWIND_MORE.
+ * Blocks delimiting our target token get REWIND_NONE.
+ */
+ switch (tok) {
+ case MDOC_Bl:
+ if (MDOC_It == p->tok)
+ return(REWIND_MORE);
+ break;
+ case MDOC_It:
+ if (MDOC_BODY == p->type && MDOC_Bl == p->tok)
+ return(REWIND_NONE);
+ break;
+ /*
+ * XXX Badly nested block handling still fails badly
+ * when one block is breaking two blocks of the same type.
+ * This is an incomplete and extremely ugly workaround,
+ * required to let the OpenBSD tree build.
+ */
+ case MDOC_Oo:
+ if (MDOC_Op == p->tok)
+ return(REWIND_MORE);
+ break;
+ case MDOC_Nm:
+ return(REWIND_NONE);
+ case MDOC_Nd:
+ /* FALLTHROUGH */
+ case MDOC_Ss:
+ if (MDOC_BODY == p->type && MDOC_Sh == p->tok)
+ return(REWIND_NONE);
+ /* FALLTHROUGH */
+ case MDOC_Sh:
+ if (MDOC_ROOT == p->parent->type)
+ return(REWIND_THIS);
+ if (MDOC_Nd == p->tok || MDOC_Ss == p->tok ||
+ MDOC_Sh == p->tok)
+ return(REWIND_MORE);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Default block rewinding rules.
+ * In particular, always skip block end markers,
+ * and let all blocks rewind Nm children.
+ */
+ if (ENDBODY_NOT != p->end || MDOC_Nm == p->tok ||
+ (MDOC_BLOCK == p->type &&
+ ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)))
+ return(REWIND_MORE);
+
+ /*
+ * By default, closing out full blocks
+ * forces closing of broken explicit blocks,
+ * while closing out partial blocks
+ * allows delayed rewinding by default.
+ */
+ return (&blk_full == mdoc_macros[tok].fp ?
+ REWIND_FORCE : REWIND_LATER);
+}
+
+static int
+rew_elem(struct mdoc *mdoc, enum mdoct tok)
+{
+ struct mdoc_node *n;
+
+ n = mdoc->last;
+ if (MDOC_ELEM != n->type)
+ n = n->parent;
+ assert(MDOC_ELEM == n->type);
+ assert(tok == n->tok);
+
+ return(rew_last(mdoc, n));
+}
+
+/*
+ * We are trying to close a block identified by tok,
+ * but the child block *broken is still open.
+ * Thus, postpone closing the tok block
+ * until the rew_sub call closing *broken.
+ */
+static int
+make_pending(struct mdoc_node *broken, enum mdoct tok,
+ struct mdoc *mdoc, int line, int ppos)
+{
+ struct mdoc_node *breaker;
+
+ /*
+ * Iterate backwards, searching for the block matching tok,
+ * that is, the block breaking the *broken block.
+ */
+ for (breaker = broken->parent; breaker; breaker = breaker->parent) {
+
+ /*
+ * If the *broken block had already been broken before
+ * and we encounter its breaker, make the tok block
+ * pending on the inner breaker.
+ * Graphically, "[A breaker=[B broken=[C->B B] tok=A] C]"
+ * becomes "[A broken=[B [C->B B] tok=A] C]"
+ * and finally "[A [B->A [C->B B] A] C]".
+ */
+ if (breaker == broken->pending) {
+ broken = breaker;
+ continue;
+ }
+
+ if (REWIND_THIS != rew_dohalt(tok, MDOC_BLOCK, breaker))
+ continue;
+ if (MDOC_BODY == broken->type)
+ broken = broken->parent;
+
+ /*
+ * Found the breaker.
+ * If another, outer breaker is already pending on
+ * the *broken block, we must not clobber the link
+ * to the outer breaker, but make it pending on the
+ * new, now inner breaker.
+ * Graphically, "[A breaker=[B broken=[C->A A] tok=B] C]"
+ * becomes "[A breaker=[B->A broken=[C A] tok=B] C]"
+ * and finally "[A [B->A [C->B A] B] C]".
+ */
+ if (broken->pending) {
+ struct mdoc_node *taker;
+
+ /*
+ * If the breaker had also been broken before,
+ * it cannot take on the outer breaker itself,
+ * but must hand it on to its own breakers.
+ * Graphically, this is the following situation:
+ * "[A [B breaker=[C->B B] broken=[D->A A] tok=C] D]"
+ * "[A taker=[B->A breaker=[C->B B] [D->C A] C] D]"
+ */
+ taker = breaker;
+ while (taker->pending)
+ taker = taker->pending;
+ taker->pending = broken->pending;
+ }
+ broken->pending = breaker;
+ mandoc_vmsg(MANDOCERR_BLK_NEST, mdoc->parse, line, ppos,
+ "%s breaks %s", mdoc_macronames[tok],
+ mdoc_macronames[broken->tok]);
+ return(1);
+ }
+
+ /*
+ * Found no matching block for tok.
+ * Are you trying to close a block that is not open?
+ */
+ return(0);
+}
+
+static int
+rew_sub(enum mdoc_type t, struct mdoc *mdoc,
+ enum mdoct tok, int line, int ppos)
+{
+ struct mdoc_node *n;
+
+ n = mdoc->last;
+ while (n) {
+ switch (rew_dohalt(tok, t, n)) {
+ case REWIND_NONE:
+ return(1);
+ case REWIND_THIS:
+ n->lastline = line -
+ (MDOC_NEWLINE & mdoc->flags &&
+ ! (MDOC_EXPLICIT & mdoc_macros[tok].flags));
+ break;
+ case REWIND_FORCE:
+ mandoc_vmsg(MANDOCERR_BLK_BROKEN, mdoc->parse,
+ line, ppos, "%s breaks %s",
+ mdoc_macronames[tok],
+ mdoc_macronames[n->tok]);
+ /* FALLTHROUGH */
+ case REWIND_MORE:
+ n->lastline = line -
+ (MDOC_NEWLINE & mdoc->flags ? 1 : 0);
+ n = n->parent;
+ continue;
+ case REWIND_LATER:
+ if (make_pending(n, tok, mdoc, line, ppos) ||
+ MDOC_BLOCK != t)
+ return(1);
+ /* FALLTHROUGH */
+ case REWIND_ERROR:
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN,
+ mdoc->parse, line, ppos,
+ mdoc_macronames[tok]);
+ return(1);
+ }
+ break;
+ }
+
+ assert(n);
+ if ( ! rew_last(mdoc, n))
+ return(0);
+
+ /*
+ * The current block extends an enclosing block.
+ * Now that the current block ends, close the enclosing block, too.
+ */
+ while (NULL != (n = n->pending)) {
+ if ( ! rew_last(mdoc, n))
+ return(0);
+ if (MDOC_HEAD == n->type &&
+ ! mdoc_body_alloc(mdoc, n->line, n->pos, n->tok))
+ return(0);
+ }
+
+ return(1);
+}
+
+/*
+ * Allocate a word and check whether it's punctuation or not.
+ * Punctuation consists of those tokens found in mdoc_isdelim().
+ */
+static int
+dword(struct mdoc *mdoc, int line, int col, const char *p,
+ enum mdelim d, int may_append)
+{
+
+ if (DELIM_MAX == d)
+ d = mdoc_isdelim(p);
+
+ if (may_append &&
+ ! ((MDOC_SYNOPSIS | MDOC_KEEP | MDOC_SMOFF) & mdoc->flags) &&
+ DELIM_NONE == d && MDOC_TEXT == mdoc->last->type &&
+ DELIM_NONE == mdoc_isdelim(mdoc->last->string)) {
+ mdoc_word_append(mdoc, p);
+ return(1);
+ }
+
+ if ( ! mdoc_word_alloc(mdoc, line, col, p))
+ return(0);
+
+ if (DELIM_OPEN == d)
+ mdoc->last->flags |= MDOC_DELIMO;
+
+ /*
+ * Closing delimiters only suppress the preceding space
+ * when they follow something, not when they start a new
+ * block or element, and not when they follow `No'.
+ *
+ * XXX Explicitly special-casing MDOC_No here feels
+ * like a layering violation. Find a better way
+ * and solve this in the code related to `No'!
+ */
+
+ else if (DELIM_CLOSE == d && mdoc->last->prev &&
+ mdoc->last->prev->tok != MDOC_No &&
+ mdoc->last->parent->tok != MDOC_Fd)
+ mdoc->last->flags |= MDOC_DELIMC;
+
+ return(1);
+}
+
+static int
+append_delims(struct mdoc *mdoc, int line, int *pos, char *buf)
+{
+ int la;
+ enum margserr ac;
+ char *p;
+
+ if ('\0' == buf[*pos])
+ return(1);
+
+ for (;;) {
+ la = *pos;
+ ac = mdoc_zargs(mdoc, line, pos, buf, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ else if (ARGS_EOLN == ac)
+ break;
+
+ dword(mdoc, line, la, p, DELIM_MAX, 1);
+
+ /*
+ * If we encounter end-of-sentence symbols, then trigger
+ * the double-space.
+ *
+ * XXX: it's easy to allow this to propagate outward to
+ * the last symbol, such that `. )' will cause the
+ * correct double-spacing. However, (1) groff isn't
+ * smart enough to do this and (2) it would require
+ * knowing which symbols break this behaviour, for
+ * example, `. ;' shouldn't propagate the double-space.
+ */
+ if (mandoc_eos(p, strlen(p)))
+ mdoc->last->flags |= MDOC_EOS;
+ }
+
+ return(1);
+}
+
+/*
+ * Close out block partial/full explicit.
+ */
+static int
+blk_exp_close(MACRO_PROT_ARGS)
+{
+ struct mdoc_node *body; /* Our own body. */
+ struct mdoc_node *later; /* A sub-block starting later. */
+ struct mdoc_node *n; /* For searching backwards. */
+
+ int j, lastarg, maxargs, flushed, nl;
+ enum margserr ac;
+ enum mdoct atok, ntok;
+ char *p;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ switch (tok) {
+ case MDOC_Ec:
+ maxargs = 1;
+ break;
+ case MDOC_Ek:
+ mdoc->flags &= ~MDOC_KEEP;
+ default:
+ maxargs = 0;
+ break;
+ }
+
+ /*
+ * Search backwards for beginnings of blocks,
+ * both of our own and of pending sub-blocks.
+ */
+ atok = rew_alt(tok);
+ body = later = NULL;
+ for (n = mdoc->last; n; n = n->parent) {
+ if (MDOC_VALID & n->flags)
+ continue;
+
+ /* Remember the start of our own body. */
+ if (MDOC_BODY == n->type && atok == n->tok) {
+ if (ENDBODY_NOT == n->end)
+ body = n;
+ continue;
+ }
+
+ if (MDOC_BLOCK != n->type || MDOC_Nm == n->tok)
+ continue;
+ if (atok == n->tok) {
+ assert(body);
+
+ /*
+ * Found the start of our own block.
+ * When there is no pending sub block,
+ * just proceed to closing out.
+ */
+ if (NULL == later)
+ break;
+
+ /*
+ * When there is a pending sub block,
+ * postpone closing out the current block
+ * until the rew_sub() closing out the sub-block.
+ */
+ make_pending(later, tok, mdoc, line, ppos);
+
+ /*
+ * Mark the place where the formatting - but not
+ * the scope - of the current block ends.
+ */
+ if ( ! mdoc_endbody_alloc(mdoc, line, ppos,
+ atok, body, ENDBODY_SPACE))
+ return(0);
+ break;
+ }
+
+ /*
+ * When finding an open sub block, remember the last
+ * open explicit block, or, in case there are only
+ * implicit ones, the first open implicit block.
+ */
+ if (later &&
+ MDOC_EXPLICIT & mdoc_macros[later->tok].flags)
+ continue;
+ if (MDOC_It != n->tok)
+ later = n;
+ }
+
+ if ( ! (MDOC_PARSED & mdoc_macros[tok].flags)) {
+ if ('\0' != buf[*pos])
+ mandoc_vmsg(MANDOCERR_ARG_SKIP,
+ mdoc->parse, line, ppos,
+ "%s %s", mdoc_macronames[tok],
+ buf + *pos);
+ if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
+ return(0);
+ return(rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos));
+ }
+
+ if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
+ return(0);
+
+ if (NULL == later && maxargs > 0)
+ if ( ! mdoc_tail_alloc(mdoc, line, ppos, rew_alt(tok)))
+ return(0);
+
+ for (flushed = j = 0; ; j++) {
+ lastarg = *pos;
+
+ if (j == maxargs && ! flushed) {
+ if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
+ return(0);
+ flushed = 1;
+ }
+
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_PUNCT == ac)
+ break;
+ if (ARGS_EOLN == ac)
+ break;
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+ if (MDOC_MAX == ntok) {
+ if ( ! dword(mdoc, line, lastarg, p, DELIM_MAX,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+ continue;
+ }
+
+ if ( ! flushed) {
+ if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
+ return(0);
+ flushed = 1;
+ }
+
+ mdoc->flags &= ~MDOC_NEWLINE;
+
+ if ( ! mdoc_macro(mdoc, ntok, line, lastarg, pos, buf))
+ return(0);
+ break;
+ }
+
+ if ( ! flushed && ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
+ return(0);
+
+ if ( ! nl)
+ return(1);
+ return(append_delims(mdoc, line, pos, buf));
+}
+
+static int
+in_line(MACRO_PROT_ARGS)
+{
+ int la, scope, cnt, nc, nl;
+ enum margverr av;
+ enum mdoct ntok;
+ enum margserr ac;
+ enum mdelim d;
+ struct mdoc_arg *arg;
+ char *p;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ /*
+ * Whether we allow ignored elements (those without content,
+ * usually because of reserved words) to squeak by.
+ */
+
+ switch (tok) {
+ case MDOC_An:
+ /* FALLTHROUGH */
+ case MDOC_Ar:
+ /* FALLTHROUGH */
+ case MDOC_Fl:
+ /* FALLTHROUGH */
+ case MDOC_Mt:
+ /* FALLTHROUGH */
+ case MDOC_Nm:
+ /* FALLTHROUGH */
+ case MDOC_Pa:
+ nc = 1;
+ break;
+ default:
+ nc = 0;
+ break;
+ }
+
+ for (arg = NULL;; ) {
+ la = *pos;
+ av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+
+ if (ARGV_WORD == av) {
+ *pos = la;
+ break;
+ }
+ if (ARGV_EOLN == av)
+ break;
+ if (ARGV_ARG == av)
+ continue;
+
+ mdoc_argv_free(arg);
+ return(0);
+ }
+
+ for (cnt = scope = 0;; ) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_EOLN == ac)
+ break;
+ if (ARGS_PUNCT == ac)
+ break;
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+ /*
+ * In this case, we've located a submacro and must
+ * execute it. Close out scope, if open. If no
+ * elements have been generated, either create one (nc)
+ * or raise a warning.
+ */
+
+ if (MDOC_MAX != ntok) {
+ if (scope && ! rew_elem(mdoc, tok))
+ return(0);
+ if (nc && 0 == cnt) {
+ if ( ! mdoc_elem_alloc(mdoc,
+ line, ppos, tok, arg))
+ return(0);
+ if ( ! rew_last(mdoc, mdoc->last))
+ return(0);
+ } else if ( ! nc && 0 == cnt) {
+ mdoc_argv_free(arg);
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ mdoc->parse, line, ppos,
+ mdoc_macronames[tok]);
+ }
+
+ if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
+ return(0);
+ if ( ! nl)
+ return(1);
+ return(append_delims(mdoc, line, pos, buf));
+ }
+
+ /*
+ * Non-quote-enclosed punctuation. Set up our scope, if
+ * a word; rewind the scope, if a delimiter; then append
+ * the word.
+ */
+
+ d = ARGS_QWORD == ac ? DELIM_NONE : mdoc_isdelim(p);
+
+ if (DELIM_NONE != d) {
+ /*
+ * If we encounter closing punctuation, no word
+ * has been omitted, no scope is open, and we're
+ * allowed to have an empty element, then start
+ * a new scope. `Ar', `Fl', and `Li', only do
+ * this once per invocation. There may be more
+ * of these (all of them?).
+ */
+ if (0 == cnt && (nc || MDOC_Li == tok) &&
+ DELIM_CLOSE == d && ! scope) {
+ if ( ! mdoc_elem_alloc(mdoc,
+ line, ppos, tok, arg))
+ return(0);
+ if (MDOC_Ar == tok || MDOC_Li == tok ||
+ MDOC_Fl == tok)
+ cnt++;
+ scope = 1;
+ }
+ /*
+ * Close out our scope, if one is open, before
+ * any punctuation.
+ */
+ if (scope && ! rew_elem(mdoc, tok))
+ return(0);
+ scope = 0;
+ } else if ( ! scope) {
+ if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
+ return(0);
+ scope = 1;
+ }
+
+ if (DELIM_NONE == d)
+ cnt++;
+
+ if ( ! dword(mdoc, line, la, p, d,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+
+ /*
+ * `Fl' macros have their scope re-opened with each new
+ * word so that the `-' can be added to each one without
+ * having to parse out spaces.
+ */
+ if (scope && MDOC_Fl == tok) {
+ if ( ! rew_elem(mdoc, tok))
+ return(0);
+ scope = 0;
+ }
+ }
+
+ if (scope && ! rew_elem(mdoc, tok))
+ return(0);
+
+ /*
+ * If no elements have been collected and we're allowed to have
+ * empties (nc), open a scope and close it out. Otherwise,
+ * raise a warning.
+ */
+
+ if (nc && 0 == cnt) {
+ if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
+ return(0);
+ if ( ! rew_last(mdoc, mdoc->last))
+ return(0);
+ } else if ( ! nc && 0 == cnt) {
+ mdoc_argv_free(arg);
+ mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+ line, ppos, mdoc_macronames[tok]);
+ }
+
+ if ( ! nl)
+ return(1);
+ return(append_delims(mdoc, line, pos, buf));
+}
+
+static int
+blk_full(MACRO_PROT_ARGS)
+{
+ int la, nl, nparsed;
+ struct mdoc_arg *arg;
+ struct mdoc_node *head; /* save of head macro */
+ struct mdoc_node *body; /* save of body macro */
+ struct mdoc_node *n;
+ enum mdoc_type mtt;
+ enum mdoct ntok;
+ enum margserr ac, lac;
+ enum margverr av;
+ char *p;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ /* Skip items outside lists. */
+
+ if (tok == MDOC_It) {
+ for (n = mdoc->last; n; n = n->parent)
+ if (n->tok == MDOC_Bl)
+ break;
+ if (n == NULL) {
+ mandoc_vmsg(MANDOCERR_IT_STRAY, mdoc->parse,
+ line, ppos, "It %s", buf + *pos);
+ if ( ! mdoc_elem_alloc(mdoc, line, ppos,
+ MDOC_br, NULL))
+ return(0);
+ return(rew_elem(mdoc, MDOC_br));
+ }
+ }
+
+ /* Close out prior implicit scope. */
+
+ if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) {
+ if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
+ return(0);
+ if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
+ return(0);
+ }
+
+ /*
+ * This routine accommodates implicitly- and explicitly-scoped
+ * macro openings. Implicit ones first close out prior scope
+ * (seen above). Delay opening the head until necessary to
+ * allow leading punctuation to print. Special consideration
+ * for `It -column', which has phrase-part syntax instead of
+ * regular child nodes.
+ */
+
+ for (arg = NULL;; ) {
+ la = *pos;
+ av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+
+ if (ARGV_WORD == av) {
+ *pos = la;
+ break;
+ }
+
+ if (ARGV_EOLN == av)
+ break;
+ if (ARGV_ARG == av)
+ continue;
+
+ mdoc_argv_free(arg);
+ return(0);
+ }
+
+ if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, arg))
+ return(0);
+
+ head = body = NULL;
+
+ /*
+ * Exception: Heads of `It' macros in `-diag' lists are not
+ * parsed, even though `It' macros in general are parsed.
+ */
+ nparsed = MDOC_It == tok &&
+ MDOC_Bl == mdoc->last->parent->tok &&
+ LIST_diag == mdoc->last->parent->norm->Bl.type;
+
+ /*
+ * The `Nd' macro has all arguments in its body: it's a hybrid
+ * of block partial-explicit and full-implicit. Stupid.
+ */
+
+ if (MDOC_Nd == tok) {
+ if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+ return(0);
+ head = mdoc->last;
+ if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
+ return(0);
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+ body = mdoc->last;
+ }
+
+ if (MDOC_Bk == tok)
+ mdoc->flags |= MDOC_KEEP;
+
+ ac = ARGS_ERROR;
+
+ for ( ; ; ) {
+ la = *pos;
+ /* Initialise last-phrase-type with ARGS_PEND. */
+ lac = ARGS_ERROR == ac ? ARGS_PEND : ac;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if (ARGS_PUNCT == ac)
+ break;
+
+ if (ARGS_ERROR == ac)
+ return(0);
+
+ if (ARGS_EOLN == ac) {
+ if (ARGS_PPHRASE != lac && ARGS_PHRASE != lac)
+ break;
+ /*
+ * This is necessary: if the last token on a
+ * line is a `Ta' or tab, then we'll get
+ * ARGS_EOLN, so we must be smart enough to
+ * reopen our scope if the last parse was a
+ * phrase or partial phrase.
+ */
+ if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
+ return(0);
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+ body = mdoc->last;
+ break;
+ }
+
+ /*
+ * Emit leading punctuation (i.e., punctuation before
+ * the MDOC_HEAD) for non-phrase types.
+ */
+
+ if (NULL == head &&
+ ARGS_PEND != ac &&
+ ARGS_PHRASE != ac &&
+ ARGS_PPHRASE != ac &&
+ ARGS_QWORD != ac &&
+ DELIM_OPEN == mdoc_isdelim(p)) {
+ if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
+ return(0);
+ continue;
+ }
+
+ /* Open a head if one hasn't been opened. */
+
+ if (NULL == head) {
+ if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+ return(0);
+ head = mdoc->last;
+ }
+
+ if (ARGS_PHRASE == ac ||
+ ARGS_PEND == ac ||
+ ARGS_PPHRASE == ac) {
+ /*
+ * If we haven't opened a body yet, rewind the
+ * head; if we have, rewind that instead.
+ */
+
+ mtt = body ? MDOC_BODY : MDOC_HEAD;
+ if ( ! rew_sub(mtt, mdoc, tok, line, ppos))
+ return(0);
+
+ /* Then allocate our body context. */
+
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+ body = mdoc->last;
+
+ /*
+ * Process phrases: set whether we're in a
+ * partial-phrase (this effects line handling)
+ * then call down into the phrase parser.
+ */
+
+ if (ARGS_PPHRASE == ac)
+ mdoc->flags |= MDOC_PPHRASE;
+ if (ARGS_PEND == ac && ARGS_PPHRASE == lac)
+ mdoc->flags |= MDOC_PPHRASE;
+
+ if ( ! phrase(mdoc, line, la, buf))
+ return(0);
+
+ mdoc->flags &= ~MDOC_PPHRASE;
+ continue;
+ }
+
+ ntok = nparsed || ARGS_QWORD == ac ?
+ MDOC_MAX : lookup(tok, p);
+
+ if (MDOC_MAX == ntok) {
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+ continue;
+ }
+
+ if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
+ return(0);
+ break;
+ }
+
+ if (NULL == head) {
+ if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+ return(0);
+ head = mdoc->last;
+ }
+
+ if (nl && ! append_delims(mdoc, line, pos, buf))
+ return(0);
+
+ /* If we've already opened our body, exit now. */
+
+ if (NULL != body)
+ goto out;
+
+ /*
+ * If there is an open (i.e., unvalidated) sub-block requiring
+ * explicit close-out, postpone switching the current block from
+ * head to body until the rew_sub() call closing out that
+ * sub-block.
+ */
+ for (n = mdoc->last; n && n != head; n = n->parent) {
+ if (MDOC_BLOCK == n->type &&
+ MDOC_EXPLICIT & mdoc_macros[n->tok].flags &&
+ ! (MDOC_VALID & n->flags)) {
+ n->pending = head;
+ return(1);
+ }
+ }
+
+ /* Close out scopes to remain in a consistent state. */
+
+ if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
+ return(0);
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+
+out:
+ if ( ! (MDOC_FREECOL & mdoc->flags))
+ return(1);
+
+ if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
+ return(0);
+ if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
+ return(0);
+
+ mdoc->flags &= ~MDOC_FREECOL;
+ return(1);
+}
+
+static int
+blk_part_imp(MACRO_PROT_ARGS)
+{
+ int la, nl;
+ enum mdoct ntok;
+ enum margserr ac;
+ char *p;
+ struct mdoc_node *blk; /* saved block context */
+ struct mdoc_node *body; /* saved body context */
+ struct mdoc_node *n;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ /*
+ * A macro that spans to the end of the line. This is generally
+ * (but not necessarily) called as the first macro. The block
+ * has a head as the immediate child, which is always empty,
+ * followed by zero or more opening punctuation nodes, then the
+ * body (which may be empty, depending on the macro), then zero
+ * or more closing punctuation nodes.
+ */
+
+ if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL))
+ return(0);
+
+ blk = mdoc->last;
+
+ if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+ return(0);
+ if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
+ return(0);
+
+ /*
+ * Open the body scope "on-demand", that is, after we've
+ * processed all our the leading delimiters (open parenthesis,
+ * etc.).
+ */
+
+ for (body = NULL; ; ) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_EOLN == ac)
+ break;
+ if (ARGS_PUNCT == ac)
+ break;
+
+ if (NULL == body && ARGS_QWORD != ac &&
+ DELIM_OPEN == mdoc_isdelim(p)) {
+ if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
+ return(0);
+ continue;
+ }
+
+ if (NULL == body) {
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+ body = mdoc->last;
+ }
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+ if (MDOC_MAX == ntok) {
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+ continue;
+ }
+
+ if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
+ return(0);
+ break;
+ }
+
+ /* Clean-ups to leave in a consistent state. */
+
+ if (NULL == body) {
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+ body = mdoc->last;
+ }
+
+ /*
+ * If there is an open sub-block requiring explicit close-out,
+ * postpone closing out the current block
+ * until the rew_sub() call closing out the sub-block.
+ */
+ for (n = mdoc->last; n && n != body && n != blk->parent;
+ n = n->parent) {
+ if (MDOC_BLOCK == n->type &&
+ MDOC_EXPLICIT & mdoc_macros[n->tok].flags &&
+ ! (MDOC_VALID & n->flags)) {
+ make_pending(n, tok, mdoc, line, ppos);
+ if ( ! mdoc_endbody_alloc(mdoc, line, ppos,
+ tok, body, ENDBODY_NOSPACE))
+ return(0);
+ return(1);
+ }
+ }
+ assert(n == body);
+
+ if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
+ return(0);
+
+ /* Standard appending of delimiters. */
+
+ if (nl && ! append_delims(mdoc, line, pos, buf))
+ return(0);
+
+ /* Rewind scope, if applicable. */
+
+ if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
+ return(0);
+
+ /* Move trailing .Ns out of scope. */
+
+ for (n = body->child; n && n->next; n = n->next)
+ /* Do nothing. */ ;
+ if (n && MDOC_Ns == n->tok)
+ mdoc_node_relink(mdoc, n);
+
+ return(1);
+}
+
+static int
+blk_part_exp(MACRO_PROT_ARGS)
+{
+ int la, nl;
+ enum margserr ac;
+ struct mdoc_node *head; /* keep track of head */
+ struct mdoc_node *body; /* keep track of body */
+ char *p;
+ enum mdoct ntok;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ /*
+ * The opening of an explicit macro having zero or more leading
+ * punctuation nodes; a head with optional single element (the
+ * case of `Eo'); and a body that may be empty.
+ */
+
+ if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL))
+ return(0);
+
+ for (head = body = NULL; ; ) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_PUNCT == ac)
+ break;
+ if (ARGS_EOLN == ac)
+ break;
+
+ /* Flush out leading punctuation. */
+
+ if (NULL == head && ARGS_QWORD != ac &&
+ DELIM_OPEN == mdoc_isdelim(p)) {
+ assert(NULL == body);
+ if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
+ return(0);
+ continue;
+ }
+
+ if (NULL == head) {
+ assert(NULL == body);
+ if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+ return(0);
+ head = mdoc->last;
+ }
+
+ /*
+ * `Eo' gobbles any data into the head, but most other
+ * macros just immediately close out and begin the body.
+ */
+
+ if (NULL == body) {
+ assert(head);
+ /* No check whether it's a macro! */
+ if (MDOC_Eo == tok)
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX, 0))
+ return(0);
+
+ if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
+ return(0);
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+ body = mdoc->last;
+
+ if (MDOC_Eo == tok)
+ continue;
+ }
+
+ assert(NULL != head && NULL != body);
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+ if (MDOC_MAX == ntok) {
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+ continue;
+ }
+
+ if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
+ return(0);
+ break;
+ }
+
+ /* Clean-up to leave in a consistent state. */
+
+ if (NULL == head)
+ if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+ return(0);
+
+ if (NULL == body) {
+ if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
+ return(0);
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+ return(0);
+ }
+
+ /* Standard appending of delimiters. */
+
+ if ( ! nl)
+ return(1);
+ return(append_delims(mdoc, line, pos, buf));
+}
+
+static int
+in_line_argn(MACRO_PROT_ARGS)
+{
+ int la, flushed, j, maxargs, nl;
+ enum margserr ac;
+ enum margverr av;
+ struct mdoc_arg *arg;
+ char *p;
+ enum mdoct ntok;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ /*
+ * A line macro that has a fixed number of arguments (maxargs).
+ * Only open the scope once the first non-leading-punctuation is
+ * found (unless MDOC_IGNDELIM is noted, like in `Pf'), then
+ * keep it open until the maximum number of arguments are
+ * exhausted.
+ */
+
+ switch (tok) {
+ case MDOC_Ap:
+ /* FALLTHROUGH */
+ case MDOC_No:
+ /* FALLTHROUGH */
+ case MDOC_Ns:
+ /* FALLTHROUGH */
+ case MDOC_Ux:
+ maxargs = 0;
+ break;
+ case MDOC_Bx:
+ /* FALLTHROUGH */
+ case MDOC_Es:
+ /* FALLTHROUGH */
+ case MDOC_Xr:
+ maxargs = 2;
+ break;
+ default:
+ maxargs = 1;
+ break;
+ }
+
+ for (arg = NULL; ; ) {
+ la = *pos;
+ av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+
+ if (ARGV_WORD == av) {
+ *pos = la;
+ break;
+ }
+
+ if (ARGV_EOLN == av)
+ break;
+ if (ARGV_ARG == av)
+ continue;
+
+ mdoc_argv_free(arg);
+ return(0);
+ }
+
+ for (flushed = j = 0; ; ) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_PUNCT == ac)
+ break;
+ if (ARGS_EOLN == ac)
+ break;
+
+ if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) &&
+ ARGS_QWORD != ac && 0 == j &&
+ DELIM_OPEN == mdoc_isdelim(p)) {
+ if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
+ return(0);
+ continue;
+ } else if (0 == j)
+ if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
+ return(0);
+
+ if (j == maxargs && ! flushed) {
+ if ( ! rew_elem(mdoc, tok))
+ return(0);
+ flushed = 1;
+ }
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+ if (MDOC_MAX != ntok) {
+ if ( ! flushed && ! rew_elem(mdoc, tok))
+ return(0);
+ flushed = 1;
+ if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
+ return(0);
+ j++;
+ break;
+ }
+
+ if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) &&
+ ARGS_QWORD != ac &&
+ ! flushed &&
+ DELIM_NONE != mdoc_isdelim(p)) {
+ if ( ! rew_elem(mdoc, tok))
+ return(0);
+ flushed = 1;
+ }
+
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+ j++;
+ }
+
+ if (0 == j && ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
+ return(0);
+
+ /* Close out in a consistent state. */
+
+ if ( ! flushed && ! rew_elem(mdoc, tok))
+ return(0);
+ if ( ! nl)
+ return(1);
+ return(append_delims(mdoc, line, pos, buf));
+}
+
+static int
+in_line_eoln(MACRO_PROT_ARGS)
+{
+ int la;
+ enum margserr ac;
+ enum margverr av;
+ struct mdoc_arg *arg;
+ char *p;
+ enum mdoct ntok;
+
+ assert( ! (MDOC_PARSED & mdoc_macros[tok].flags));
+
+ if (tok == MDOC_Pp)
+ rew_sub(MDOC_BLOCK, mdoc, MDOC_Nm, line, ppos);
+
+ /* Parse macro arguments. */
+
+ for (arg = NULL; ; ) {
+ la = *pos;
+ av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+
+ if (ARGV_WORD == av) {
+ *pos = la;
+ break;
+ }
+ if (ARGV_EOLN == av)
+ break;
+ if (ARGV_ARG == av)
+ continue;
+
+ mdoc_argv_free(arg);
+ return(0);
+ }
+
+ /* Open element scope. */
+
+ if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
+ return(0);
+
+ /* Parse argument terms. */
+
+ for (;;) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_EOLN == ac)
+ break;
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+ if (MDOC_MAX == ntok) {
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+ continue;
+ }
+
+ if ( ! rew_elem(mdoc, tok))
+ return(0);
+ return(mdoc_macro(mdoc, ntok, line, la, pos, buf));
+ }
+
+ /* Close out (no delimiters). */
+
+ return(rew_elem(mdoc, tok));
+}
+
+static int
+ctx_synopsis(MACRO_PROT_ARGS)
+{
+ int nl;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ /* If we're not in the SYNOPSIS, go straight to in-line. */
+ if ( ! (MDOC_SYNOPSIS & mdoc->flags))
+ return(in_line(mdoc, tok, line, ppos, pos, buf));
+
+ /* If we're a nested call, same place. */
+ if ( ! nl)
+ return(in_line(mdoc, tok, line, ppos, pos, buf));
+
+ /*
+ * XXX: this will open a block scope; however, if later we end
+ * up formatting the block scope, then child nodes will inherit
+ * the formatting. Be careful.
+ */
+ if (MDOC_Nm == tok)
+ return(blk_full(mdoc, tok, line, ppos, pos, buf));
+ assert(MDOC_Vt == tok);
+ return(blk_part_imp(mdoc, tok, line, ppos, pos, buf));
+}
+
+/*
+ * Phrases occur within `Bl -column' entries, separated by `Ta' or tabs.
+ * They're unusual because they're basically free-form text until a
+ * macro is encountered.
+ */
+static int
+phrase(struct mdoc *mdoc, int line, int ppos, char *buf)
+{
+ int la, pos;
+ enum margserr ac;
+ enum mdoct ntok;
+ char *p;
+
+ for (pos = ppos; ; ) {
+ la = pos;
+
+ ac = mdoc_zargs(mdoc, line, &pos, buf, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_EOLN == ac)
+ break;
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p);
+
+ if (MDOC_MAX == ntok) {
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX, 1))
+ return(0);
+ continue;
+ }
+
+ if ( ! mdoc_macro(mdoc, ntok, line, la, &pos, buf))
+ return(0);
+ return(append_delims(mdoc, line, &pos, buf));
+ }
+
+ return(1);
+}
+
+static int
+phrase_ta(MACRO_PROT_ARGS)
+{
+ struct mdoc_node *n;
+ int la;
+ enum mdoct ntok;
+ enum margserr ac;
+ char *p;
+
+ /* Make sure we are in a column list or ignore this macro. */
+ n = mdoc->last;
+ while (NULL != n && MDOC_Bl != n->tok)
+ n = n->parent;
+ if (NULL == n || LIST_column != n->norm->Bl.type) {
+ mandoc_msg(MANDOCERR_TA_STRAY, mdoc->parse,
+ line, ppos, "Ta");
+ return(1);
+ }
+
+ /* Advance to the next column. */
+ if ( ! rew_sub(MDOC_BODY, mdoc, MDOC_It, line, ppos))
+ return(0);
+ if ( ! mdoc_body_alloc(mdoc, line, ppos, MDOC_It))
+ return(0);
+
+ for (;;) {
+ la = *pos;
+ ac = mdoc_zargs(mdoc, line, pos, buf, &p);
+
+ if (ARGS_ERROR == ac)
+ return(0);
+ if (ARGS_EOLN == ac)
+ break;
+
+ ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p);
+
+ if (MDOC_MAX == ntok) {
+ if ( ! dword(mdoc, line, la, p, DELIM_MAX,
+ MDOC_JOIN & mdoc_macros[tok].flags))
+ return(0);
+ continue;
+ }
+
+ if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
+ return(0);
+ return(append_delims(mdoc, line, pos, buf));
+ }
+
+ return(1);
+}
diff --git a/contrib/mdocml/mdoc_man.c b/contrib/mdocml/mdoc_man.c
new file mode 100644
index 0000000..8ef6908
--- /dev/null
+++ b/contrib/mdocml/mdoc_man.c
@@ -0,0 +1,1753 @@
+/* $Id: mdoc_man.c,v 1.68 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "man.h"
+#include "mdoc.h"
+#include "main.h"
+
+#define DECL_ARGS const struct mdoc_meta *meta, \
+ const struct mdoc_node *n
+
+struct manact {
+ int (*cond)(DECL_ARGS); /* DON'T run actions */
+ int (*pre)(DECL_ARGS); /* pre-node action */
+ void (*post)(DECL_ARGS); /* post-node action */
+ const char *prefix; /* pre-node string constant */
+ const char *suffix; /* post-node string constant */
+};
+
+static int cond_body(DECL_ARGS);
+static int cond_head(DECL_ARGS);
+static void font_push(char);
+static void font_pop(void);
+static void mid_it(void);
+static void post__t(DECL_ARGS);
+static void post_bd(DECL_ARGS);
+static void post_bf(DECL_ARGS);
+static void post_bk(DECL_ARGS);
+static void post_bl(DECL_ARGS);
+static void post_dl(DECL_ARGS);
+static void post_en(DECL_ARGS);
+static void post_enc(DECL_ARGS);
+static void post_eo(DECL_ARGS);
+static void post_fa(DECL_ARGS);
+static void post_fd(DECL_ARGS);
+static void post_fl(DECL_ARGS);
+static void post_fn(DECL_ARGS);
+static void post_fo(DECL_ARGS);
+static void post_font(DECL_ARGS);
+static void post_in(DECL_ARGS);
+static void post_it(DECL_ARGS);
+static void post_lb(DECL_ARGS);
+static void post_nm(DECL_ARGS);
+static void post_percent(DECL_ARGS);
+static void post_pf(DECL_ARGS);
+static void post_sect(DECL_ARGS);
+static void post_sp(DECL_ARGS);
+static void post_vt(DECL_ARGS);
+static int pre__t(DECL_ARGS);
+static int pre_an(DECL_ARGS);
+static int pre_ap(DECL_ARGS);
+static int pre_bd(DECL_ARGS);
+static int pre_bf(DECL_ARGS);
+static int pre_bk(DECL_ARGS);
+static int pre_bl(DECL_ARGS);
+static int pre_br(DECL_ARGS);
+static int pre_bx(DECL_ARGS);
+static int pre_dl(DECL_ARGS);
+static int pre_en(DECL_ARGS);
+static int pre_enc(DECL_ARGS);
+static int pre_em(DECL_ARGS);
+static int pre_es(DECL_ARGS);
+static int pre_ex(DECL_ARGS);
+static int pre_fa(DECL_ARGS);
+static int pre_fd(DECL_ARGS);
+static int pre_fl(DECL_ARGS);
+static int pre_fn(DECL_ARGS);
+static int pre_fo(DECL_ARGS);
+static int pre_ft(DECL_ARGS);
+static int pre_in(DECL_ARGS);
+static int pre_it(DECL_ARGS);
+static int pre_lk(DECL_ARGS);
+static int pre_li(DECL_ARGS);
+static int pre_ll(DECL_ARGS);
+static int pre_nm(DECL_ARGS);
+static int pre_no(DECL_ARGS);
+static int pre_ns(DECL_ARGS);
+static int pre_pp(DECL_ARGS);
+static int pre_rs(DECL_ARGS);
+static int pre_rv(DECL_ARGS);
+static int pre_sm(DECL_ARGS);
+static int pre_sp(DECL_ARGS);
+static int pre_sect(DECL_ARGS);
+static int pre_sy(DECL_ARGS);
+static void pre_syn(const struct mdoc_node *);
+static int pre_vt(DECL_ARGS);
+static int pre_ux(DECL_ARGS);
+static int pre_xr(DECL_ARGS);
+static void print_word(const char *);
+static void print_line(const char *, int);
+static void print_block(const char *, int);
+static void print_offs(const char *);
+static void print_width(const char *,
+ const struct mdoc_node *, size_t);
+static void print_count(int *);
+static void print_node(DECL_ARGS);
+
+static const struct manact manacts[MDOC_MAX + 1] = {
+ { NULL, pre_ap, NULL, NULL, NULL }, /* Ap */
+ { NULL, NULL, NULL, NULL, NULL }, /* Dd */
+ { NULL, NULL, NULL, NULL, NULL }, /* Dt */
+ { NULL, NULL, NULL, NULL, NULL }, /* Os */
+ { NULL, pre_sect, post_sect, ".SH", NULL }, /* Sh */
+ { NULL, pre_sect, post_sect, ".SS", NULL }, /* Ss */
+ { NULL, pre_pp, NULL, NULL, NULL }, /* Pp */
+ { cond_body, pre_dl, post_dl, NULL, NULL }, /* D1 */
+ { cond_body, pre_dl, post_dl, NULL, NULL }, /* Dl */
+ { cond_body, pre_bd, post_bd, NULL, NULL }, /* Bd */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ed */
+ { cond_body, pre_bl, post_bl, NULL, NULL }, /* Bl */
+ { NULL, NULL, NULL, NULL, NULL }, /* El */
+ { NULL, pre_it, post_it, NULL, NULL }, /* It */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Ad */
+ { NULL, pre_an, NULL, NULL, NULL }, /* An */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Ar */
+ { NULL, pre_sy, post_font, NULL, NULL }, /* Cd */
+ { NULL, pre_sy, post_font, NULL, NULL }, /* Cm */
+ { NULL, pre_li, post_font, NULL, NULL }, /* Dv */
+ { NULL, pre_li, post_font, NULL, NULL }, /* Er */
+ { NULL, pre_li, post_font, NULL, NULL }, /* Ev */
+ { NULL, pre_ex, NULL, NULL, NULL }, /* Ex */
+ { NULL, pre_fa, post_fa, NULL, NULL }, /* Fa */
+ { NULL, pre_fd, post_fd, NULL, NULL }, /* Fd */
+ { NULL, pre_fl, post_fl, NULL, NULL }, /* Fl */
+ { NULL, pre_fn, post_fn, NULL, NULL }, /* Fn */
+ { NULL, pre_ft, post_font, NULL, NULL }, /* Ft */
+ { NULL, pre_sy, post_font, NULL, NULL }, /* Ic */
+ { NULL, pre_in, post_in, NULL, NULL }, /* In */
+ { NULL, pre_li, post_font, NULL, NULL }, /* Li */
+ { cond_head, pre_enc, NULL, "\\- ", NULL }, /* Nd */
+ { NULL, pre_nm, post_nm, NULL, NULL }, /* Nm */
+ { cond_body, pre_enc, post_enc, "[", "]" }, /* Op */
+ { NULL, pre_ft, post_font, NULL, NULL }, /* Ot */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Pa */
+ { NULL, pre_rv, NULL, NULL, NULL }, /* Rv */
+ { NULL, NULL, NULL, NULL, NULL }, /* St */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Va */
+ { NULL, pre_vt, post_vt, NULL, NULL }, /* Vt */
+ { NULL, pre_xr, NULL, NULL, NULL }, /* Xr */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %A */
+ { NULL, pre_em, post_percent, NULL, NULL }, /* %B */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %D */
+ { NULL, pre_em, post_percent, NULL, NULL }, /* %I */
+ { NULL, pre_em, post_percent, NULL, NULL }, /* %J */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %N */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %O */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %P */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %R */
+ { NULL, pre__t, post__t, NULL, NULL }, /* %T */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %V */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ac */
+ { cond_body, pre_enc, post_enc, "<", ">" }, /* Ao */
+ { cond_body, pre_enc, post_enc, "<", ">" }, /* Aq */
+ { NULL, NULL, NULL, NULL, NULL }, /* At */
+ { NULL, NULL, NULL, NULL, NULL }, /* Bc */
+ { NULL, pre_bf, post_bf, NULL, NULL }, /* Bf */
+ { cond_body, pre_enc, post_enc, "[", "]" }, /* Bo */
+ { cond_body, pre_enc, post_enc, "[", "]" }, /* Bq */
+ { NULL, pre_ux, NULL, "BSD/OS", NULL }, /* Bsx */
+ { NULL, pre_bx, NULL, NULL, NULL }, /* Bx */
+ { NULL, NULL, NULL, NULL, NULL }, /* Db */
+ { NULL, NULL, NULL, NULL, NULL }, /* Dc */
+ { cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Do */
+ { cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Dq */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ec */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ef */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Em */
+ { NULL, NULL, post_eo, NULL, NULL }, /* Eo */
+ { NULL, pre_ux, NULL, "FreeBSD", NULL }, /* Fx */
+ { NULL, pre_sy, post_font, NULL, NULL }, /* Ms */
+ { NULL, pre_no, NULL, NULL, NULL }, /* No */
+ { NULL, pre_ns, NULL, NULL, NULL }, /* Ns */
+ { NULL, pre_ux, NULL, "NetBSD", NULL }, /* Nx */
+ { NULL, pre_ux, NULL, "OpenBSD", NULL }, /* Ox */
+ { NULL, NULL, NULL, NULL, NULL }, /* Pc */
+ { NULL, NULL, post_pf, NULL, NULL }, /* Pf */
+ { cond_body, pre_enc, post_enc, "(", ")" }, /* Po */
+ { cond_body, pre_enc, post_enc, "(", ")" }, /* Pq */
+ { NULL, NULL, NULL, NULL, NULL }, /* Qc */
+ { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* Ql */
+ { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qo */
+ { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qq */
+ { NULL, NULL, NULL, NULL, NULL }, /* Re */
+ { cond_body, pre_rs, NULL, NULL, NULL }, /* Rs */
+ { NULL, NULL, NULL, NULL, NULL }, /* Sc */
+ { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* So */
+ { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* Sq */
+ { NULL, pre_sm, NULL, NULL, NULL }, /* Sm */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Sx */
+ { NULL, pre_sy, post_font, NULL, NULL }, /* Sy */
+ { NULL, pre_li, post_font, NULL, NULL }, /* Tn */
+ { NULL, pre_ux, NULL, "UNIX", NULL }, /* Ux */
+ { NULL, NULL, NULL, NULL, NULL }, /* Xc */
+ { NULL, NULL, NULL, NULL, NULL }, /* Xo */
+ { NULL, pre_fo, post_fo, NULL, NULL }, /* Fo */
+ { NULL, NULL, NULL, NULL, NULL }, /* Fc */
+ { cond_body, pre_enc, post_enc, "[", "]" }, /* Oo */
+ { NULL, NULL, NULL, NULL, NULL }, /* Oc */
+ { NULL, pre_bk, post_bk, NULL, NULL }, /* Bk */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ek */
+ { NULL, pre_ux, NULL, "is currently in beta test.", NULL }, /* Bt */
+ { NULL, NULL, NULL, NULL, NULL }, /* Hf */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Fr */
+ { NULL, pre_ux, NULL, "currently under development.", NULL }, /* Ud */
+ { NULL, NULL, post_lb, NULL, NULL }, /* Lb */
+ { NULL, pre_pp, NULL, NULL, NULL }, /* Lp */
+ { NULL, pre_lk, NULL, NULL, NULL }, /* Lk */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Mt */
+ { cond_body, pre_enc, post_enc, "{", "}" }, /* Brq */
+ { cond_body, pre_enc, post_enc, "{", "}" }, /* Bro */
+ { NULL, NULL, NULL, NULL, NULL }, /* Brc */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %C */
+ { NULL, pre_es, NULL, NULL, NULL }, /* Es */
+ { cond_body, pre_en, post_en, NULL, NULL }, /* En */
+ { NULL, pre_ux, NULL, "DragonFly", NULL }, /* Dx */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %Q */
+ { NULL, pre_br, NULL, NULL, NULL }, /* br */
+ { NULL, pre_sp, post_sp, NULL, NULL }, /* sp */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %U */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ta */
+ { NULL, pre_ll, post_sp, NULL, NULL }, /* ll */
+ { NULL, NULL, NULL, NULL, NULL }, /* ROOT */
+};
+
+static int outflags;
+#define MMAN_spc (1 << 0) /* blank character before next word */
+#define MMAN_spc_force (1 << 1) /* even before trailing punctuation */
+#define MMAN_nl (1 << 2) /* break man(7) code line */
+#define MMAN_br (1 << 3) /* break output line */
+#define MMAN_sp (1 << 4) /* insert a blank output line */
+#define MMAN_PP (1 << 5) /* reset indentation etc. */
+#define MMAN_Sm (1 << 6) /* horizontal spacing mode */
+#define MMAN_Bk (1 << 7) /* word keep mode */
+#define MMAN_Bk_susp (1 << 8) /* suspend this (after a macro) */
+#define MMAN_An_split (1 << 9) /* author mode is "split" */
+#define MMAN_An_nosplit (1 << 10) /* author mode is "nosplit" */
+#define MMAN_PD (1 << 11) /* inter-paragraph spacing disabled */
+#define MMAN_nbrword (1 << 12) /* do not break the next word */
+
+#define BL_STACK_MAX 32
+
+static size_t Bl_stack[BL_STACK_MAX]; /* offsets [chars] */
+static int Bl_stack_post[BL_STACK_MAX]; /* add final .RE */
+static int Bl_stack_len; /* number of nested Bl blocks */
+static int TPremain; /* characters before tag is full */
+
+static struct {
+ char *head;
+ char *tail;
+ size_t size;
+} fontqueue;
+
+
+static void
+font_push(char newfont)
+{
+
+ if (fontqueue.head + fontqueue.size <= ++fontqueue.tail) {
+ fontqueue.size += 8;
+ fontqueue.head = mandoc_realloc(fontqueue.head,
+ fontqueue.size);
+ }
+ *fontqueue.tail = newfont;
+ print_word("");
+ printf("\\f");
+ putchar(newfont);
+ outflags &= ~MMAN_spc;
+}
+
+static void
+font_pop(void)
+{
+
+ if (fontqueue.tail > fontqueue.head)
+ fontqueue.tail--;
+ outflags &= ~MMAN_spc;
+ print_word("");
+ printf("\\f");
+ putchar(*fontqueue.tail);
+}
+
+static void
+print_word(const char *s)
+{
+
+ if ((MMAN_PP | MMAN_sp | MMAN_br | MMAN_nl) & outflags) {
+ /*
+ * If we need a newline, print it now and start afresh.
+ */
+ if (MMAN_PP & outflags) {
+ if (MMAN_sp & outflags) {
+ if (MMAN_PD & outflags) {
+ printf("\n.PD");
+ outflags &= ~MMAN_PD;
+ }
+ } else if ( ! (MMAN_PD & outflags)) {
+ printf("\n.PD 0");
+ outflags |= MMAN_PD;
+ }
+ printf("\n.PP\n");
+ } else if (MMAN_sp & outflags)
+ printf("\n.sp\n");
+ else if (MMAN_br & outflags)
+ printf("\n.br\n");
+ else if (MMAN_nl & outflags)
+ putchar('\n');
+ outflags &= ~(MMAN_PP|MMAN_sp|MMAN_br|MMAN_nl|MMAN_spc);
+ if (1 == TPremain)
+ printf(".br\n");
+ TPremain = 0;
+ } else if (MMAN_spc & outflags) {
+ /*
+ * If we need a space, only print it if
+ * (1) it is forced by `No' or
+ * (2) what follows is not terminating punctuation or
+ * (3) what follows is longer than one character.
+ */
+ if (MMAN_spc_force & outflags || '\0' == s[0] ||
+ NULL == strchr(".,:;)]?!", s[0]) || '\0' != s[1]) {
+ if (MMAN_Bk & outflags &&
+ ! (MMAN_Bk_susp & outflags))
+ putchar('\\');
+ putchar(' ');
+ if (TPremain)
+ TPremain--;
+ }
+ }
+
+ /*
+ * Reassign needing space if we're not following opening
+ * punctuation.
+ */
+ if (MMAN_Sm & outflags && ('\0' == s[0] ||
+ (('(' != s[0] && '[' != s[0]) || '\0' != s[1])))
+ outflags |= MMAN_spc;
+ else
+ outflags &= ~MMAN_spc;
+ outflags &= ~(MMAN_spc_force | MMAN_Bk_susp);
+
+ for ( ; *s; s++) {
+ switch (*s) {
+ case ASCII_NBRSP:
+ printf("\\ ");
+ break;
+ case ASCII_HYPH:
+ putchar('-');
+ break;
+ case ASCII_BREAK:
+ printf("\\:");
+ break;
+ case ' ':
+ if (MMAN_nbrword & outflags) {
+ printf("\\ ");
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ putchar((unsigned char)*s);
+ break;
+ }
+ if (TPremain)
+ TPremain--;
+ }
+ outflags &= ~MMAN_nbrword;
+}
+
+static void
+print_line(const char *s, int newflags)
+{
+
+ outflags &= ~MMAN_br;
+ outflags |= MMAN_nl;
+ print_word(s);
+ outflags |= newflags;
+}
+
+static void
+print_block(const char *s, int newflags)
+{
+
+ outflags &= ~MMAN_PP;
+ if (MMAN_sp & outflags) {
+ outflags &= ~(MMAN_sp | MMAN_br);
+ if (MMAN_PD & outflags) {
+ print_line(".PD", 0);
+ outflags &= ~MMAN_PD;
+ }
+ } else if (! (MMAN_PD & outflags))
+ print_line(".PD 0", MMAN_PD);
+ outflags |= MMAN_nl;
+ print_word(s);
+ outflags |= MMAN_Bk_susp | newflags;
+}
+
+static void
+print_offs(const char *v)
+{
+ char buf[24];
+ struct roffsu su;
+ size_t sz;
+
+ print_line(".RS", MMAN_Bk_susp);
+
+ /* Convert v into a number (of characters). */
+ if (NULL == v || '\0' == *v || 0 == strcmp(v, "left"))
+ sz = 0;
+ else if (0 == strcmp(v, "indent"))
+ sz = 6;
+ else if (0 == strcmp(v, "indent-two"))
+ sz = 12;
+ else if (a2roffsu(v, &su, SCALE_MAX)) {
+ if (SCALE_EN == su.unit)
+ sz = su.scale;
+ else {
+ /*
+ * XXX
+ * If we are inside an enclosing list,
+ * there is no easy way to add the two
+ * indentations because they are provided
+ * in terms of different units.
+ */
+ print_word(v);
+ outflags |= MMAN_nl;
+ return;
+ }
+ } else
+ sz = strlen(v);
+
+ /*
+ * We are inside an enclosing list.
+ * Add the two indentations.
+ */
+ if (Bl_stack_len)
+ sz += Bl_stack[Bl_stack_len - 1];
+
+ (void)snprintf(buf, sizeof(buf), "%zun", sz);
+ print_word(buf);
+ outflags |= MMAN_nl;
+}
+
+/*
+ * Set up the indentation for a list item; used from pre_it().
+ */
+static void
+print_width(const char *v, const struct mdoc_node *child, size_t defsz)
+{
+ char buf[24];
+ struct roffsu su;
+ size_t sz, chsz;
+ int numeric, remain;
+
+ numeric = 1;
+ remain = 0;
+
+ /* Convert v into a number (of characters). */
+ if (NULL == v)
+ sz = defsz;
+ else if (a2roffsu(v, &su, SCALE_MAX)) {
+ if (SCALE_EN == su.unit)
+ sz = su.scale;
+ else {
+ sz = 0;
+ numeric = 0;
+ }
+ } else
+ sz = strlen(v);
+
+ /* XXX Rough estimation, might have multiple parts. */
+ chsz = (NULL != child && MDOC_TEXT == child->type) ?
+ strlen(child->string) : 0;
+
+ /* Maybe we are inside an enclosing list? */
+ mid_it();
+
+ /*
+ * Save our own indentation,
+ * such that child lists can use it.
+ */
+ Bl_stack[Bl_stack_len++] = sz + 2;
+
+ /* Set up the current list. */
+ if (defsz && chsz > sz)
+ print_block(".HP", 0);
+ else {
+ print_block(".TP", 0);
+ remain = sz + 2;
+ }
+ if (numeric) {
+ (void)snprintf(buf, sizeof(buf), "%zun", sz + 2);
+ print_word(buf);
+ } else
+ print_word(v);
+ TPremain = remain;
+}
+
+static void
+print_count(int *count)
+{
+ char buf[24];
+
+ (void)snprintf(buf, sizeof(buf), "%d.", ++*count);
+ print_word(buf);
+}
+
+void
+man_man(void *arg, const struct man *man)
+{
+
+ /*
+ * Dump the keep buffer.
+ * We're guaranteed by now that this exists (is non-NULL).
+ * Flush stdout afterward, just in case.
+ */
+ fputs(mparse_getkeep(man_mparse(man)), stdout);
+ fflush(stdout);
+}
+
+void
+man_mdoc(void *arg, const struct mdoc *mdoc)
+{
+ const struct mdoc_meta *meta;
+ const struct mdoc_node *n;
+
+ meta = mdoc_meta(mdoc);
+ n = mdoc_node(mdoc);
+
+ printf(".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n",
+ meta->title,
+ (meta->msec == NULL ? "" : meta->msec),
+ meta->date, meta->os, meta->vol);
+
+ /* Disable hyphenation and if nroff, disable justification. */
+ printf(".nh\n.if n .ad l");
+
+ outflags = MMAN_nl | MMAN_Sm;
+ if (0 == fontqueue.size) {
+ fontqueue.size = 8;
+ fontqueue.head = fontqueue.tail = mandoc_malloc(8);
+ *fontqueue.tail = 'R';
+ }
+ print_node(meta, n);
+ putchar('\n');
+}
+
+static void
+print_node(DECL_ARGS)
+{
+ const struct mdoc_node *sub;
+ const struct manact *act;
+ int cond, do_sub;
+
+ /*
+ * Break the line if we were parsed subsequent the current node.
+ * This makes the page structure be more consistent.
+ */
+ if (MMAN_spc & outflags && MDOC_LINE & n->flags)
+ outflags |= MMAN_nl;
+
+ act = NULL;
+ cond = 0;
+ do_sub = 1;
+
+ if (MDOC_TEXT == n->type) {
+ /*
+ * Make sure that we don't happen to start with a
+ * control character at the start of a line.
+ */
+ if (MMAN_nl & outflags &&
+ ('.' == *n->string || '\'' == *n->string)) {
+ print_word("");
+ printf("\\&");
+ outflags &= ~MMAN_spc;
+ }
+ print_word(n->string);
+ } else {
+ /*
+ * Conditionally run the pre-node action handler for a
+ * node.
+ */
+ act = manacts + n->tok;
+ cond = NULL == act->cond || (*act->cond)(meta, n);
+ if (cond && act->pre && ENDBODY_NOT == n->end)
+ do_sub = (*act->pre)(meta, n);
+ }
+
+ /*
+ * Conditionally run all child nodes.
+ * Note that this iterates over children instead of using
+ * recursion. This prevents unnecessary depth in the stack.
+ */
+ if (do_sub)
+ for (sub = n->child; sub; sub = sub->next)
+ print_node(meta, sub);
+
+ /*
+ * Lastly, conditionally run the post-node handler.
+ */
+ if (MDOC_ENDED & n->flags)
+ return;
+
+ if (cond && act->post)
+ (*act->post)(meta, n);
+
+ if (ENDBODY_NOT != n->end)
+ n->pending->flags |= MDOC_ENDED;
+
+ if (ENDBODY_NOSPACE == n->end)
+ outflags &= ~(MMAN_spc | MMAN_nl);
+}
+
+static int
+cond_head(DECL_ARGS)
+{
+
+ return(MDOC_HEAD == n->type);
+}
+
+static int
+cond_body(DECL_ARGS)
+{
+
+ return(MDOC_BODY == n->type);
+}
+
+static int
+pre_enc(DECL_ARGS)
+{
+ const char *prefix;
+
+ prefix = manacts[n->tok].prefix;
+ if (NULL == prefix)
+ return(1);
+ print_word(prefix);
+ outflags &= ~MMAN_spc;
+ return(1);
+}
+
+static void
+post_enc(DECL_ARGS)
+{
+ const char *suffix;
+
+ suffix = manacts[n->tok].suffix;
+ if (NULL == suffix)
+ return;
+ outflags &= ~(MMAN_spc | MMAN_nl);
+ print_word(suffix);
+}
+
+static int
+pre_ex(DECL_ARGS)
+{
+ int nchild;
+
+ outflags |= MMAN_br | MMAN_nl;
+
+ print_word("The");
+
+ nchild = n->nchild;
+ for (n = n->child; n; n = n->next) {
+ font_push('B');
+ print_word(n->string);
+ font_pop();
+
+ if (n->next == NULL)
+ continue;
+
+ if (nchild > 2) {
+ outflags &= ~MMAN_spc;
+ print_word(",");
+ }
+ if (n->next->next == NULL)
+ print_word("and");
+ }
+
+ if (nchild > 1)
+ print_word("utilities exit\\~0");
+ else
+ print_word("utility exits\\~0");
+
+ print_word("on success, and\\~>0 if an error occurs.");
+ outflags |= MMAN_nl;
+ return(0);
+}
+
+static void
+post_font(DECL_ARGS)
+{
+
+ font_pop();
+}
+
+static void
+post_percent(DECL_ARGS)
+{
+
+ if (pre_em == manacts[n->tok].pre)
+ font_pop();
+ if (n->next) {
+ print_word(",");
+ if (n->prev && n->prev->tok == n->tok &&
+ n->next->tok == n->tok)
+ print_word("and");
+ } else {
+ print_word(".");
+ outflags |= MMAN_nl;
+ }
+}
+
+static int
+pre__t(DECL_ARGS)
+{
+
+ if (n->parent && MDOC_Rs == n->parent->tok &&
+ n->parent->norm->Rs.quote_T) {
+ print_word("");
+ putchar('\"');
+ outflags &= ~MMAN_spc;
+ } else
+ font_push('I');
+ return(1);
+}
+
+static void
+post__t(DECL_ARGS)
+{
+
+ if (n->parent && MDOC_Rs == n->parent->tok &&
+ n->parent->norm->Rs.quote_T) {
+ outflags &= ~MMAN_spc;
+ print_word("");
+ putchar('\"');
+ } else
+ font_pop();
+ post_percent(meta, n);
+}
+
+/*
+ * Print before a section header.
+ */
+static int
+pre_sect(DECL_ARGS)
+{
+
+ if (MDOC_HEAD == n->type) {
+ outflags |= MMAN_sp;
+ print_block(manacts[n->tok].prefix, 0);
+ print_word("");
+ putchar('\"');
+ outflags &= ~MMAN_spc;
+ }
+ return(1);
+}
+
+/*
+ * Print subsequent a section header.
+ */
+static void
+post_sect(DECL_ARGS)
+{
+
+ if (MDOC_HEAD != n->type)
+ return;
+ outflags &= ~MMAN_spc;
+ print_word("");
+ putchar('\"');
+ outflags |= MMAN_nl;
+ if (MDOC_Sh == n->tok && SEC_AUTHORS == n->sec)
+ outflags &= ~(MMAN_An_split | MMAN_An_nosplit);
+}
+
+/* See mdoc_term.c, synopsis_pre() for comments. */
+static void
+pre_syn(const struct mdoc_node *n)
+{
+
+ if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
+ return;
+
+ if (n->prev->tok == n->tok &&
+ MDOC_Ft != n->tok &&
+ MDOC_Fo != n->tok &&
+ MDOC_Fn != n->tok) {
+ outflags |= MMAN_br;
+ return;
+ }
+
+ switch (n->prev->tok) {
+ case MDOC_Fd:
+ /* FALLTHROUGH */
+ case MDOC_Fn:
+ /* FALLTHROUGH */
+ case MDOC_Fo:
+ /* FALLTHROUGH */
+ case MDOC_In:
+ /* FALLTHROUGH */
+ case MDOC_Vt:
+ outflags |= MMAN_sp;
+ break;
+ case MDOC_Ft:
+ if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
+ outflags |= MMAN_sp;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ outflags |= MMAN_br;
+ break;
+ }
+}
+
+static int
+pre_an(DECL_ARGS)
+{
+
+ switch (n->norm->An.auth) {
+ case AUTH_split:
+ outflags &= ~MMAN_An_nosplit;
+ outflags |= MMAN_An_split;
+ return(0);
+ case AUTH_nosplit:
+ outflags &= ~MMAN_An_split;
+ outflags |= MMAN_An_nosplit;
+ return(0);
+ default:
+ if (MMAN_An_split & outflags)
+ outflags |= MMAN_br;
+ else if (SEC_AUTHORS == n->sec &&
+ ! (MMAN_An_nosplit & outflags))
+ outflags |= MMAN_An_split;
+ return(1);
+ }
+}
+
+static int
+pre_ap(DECL_ARGS)
+{
+
+ outflags &= ~MMAN_spc;
+ print_word("'");
+ outflags &= ~MMAN_spc;
+ return(0);
+}
+
+static int
+pre_bd(DECL_ARGS)
+{
+
+ outflags &= ~(MMAN_PP | MMAN_sp | MMAN_br);
+
+ if (DISP_unfilled == n->norm->Bd.type ||
+ DISP_literal == n->norm->Bd.type)
+ print_line(".nf", 0);
+ if (0 == n->norm->Bd.comp && NULL != n->parent->prev)
+ outflags |= MMAN_sp;
+ print_offs(n->norm->Bd.offs);
+ return(1);
+}
+
+static void
+post_bd(DECL_ARGS)
+{
+
+ /* Close out this display. */
+ print_line(".RE", MMAN_nl);
+ if (DISP_unfilled == n->norm->Bd.type ||
+ DISP_literal == n->norm->Bd.type)
+ print_line(".fi", MMAN_nl);
+
+ /* Maybe we are inside an enclosing list? */
+ if (NULL != n->parent->next)
+ mid_it();
+}
+
+static int
+pre_bf(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ return(1);
+ case MDOC_BODY:
+ break;
+ default:
+ return(0);
+ }
+ switch (n->norm->Bf.font) {
+ case FONT_Em:
+ font_push('I');
+ break;
+ case FONT_Sy:
+ font_push('B');
+ break;
+ default:
+ font_push('R');
+ break;
+ }
+ return(1);
+}
+
+static void
+post_bf(DECL_ARGS)
+{
+
+ if (MDOC_BODY == n->type)
+ font_pop();
+}
+
+static int
+pre_bk(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ return(1);
+ case MDOC_BODY:
+ outflags |= MMAN_Bk;
+ return(1);
+ default:
+ return(0);
+ }
+}
+
+static void
+post_bk(DECL_ARGS)
+{
+
+ if (MDOC_BODY == n->type)
+ outflags &= ~MMAN_Bk;
+}
+
+static int
+pre_bl(DECL_ARGS)
+{
+ size_t icol;
+
+ /*
+ * print_offs() will increase the -offset to account for
+ * a possible enclosing .It, but any enclosed .It blocks
+ * just nest and do not add up their indentation.
+ */
+ if (n->norm->Bl.offs) {
+ print_offs(n->norm->Bl.offs);
+ Bl_stack[Bl_stack_len++] = 0;
+ }
+
+ switch (n->norm->Bl.type) {
+ case LIST_enum:
+ n->norm->Bl.count = 0;
+ return(1);
+ case LIST_column:
+ break;
+ default:
+ return(1);
+ }
+
+ print_line(".TS", MMAN_nl);
+ for (icol = 0; icol < n->norm->Bl.ncols; icol++)
+ print_word("l");
+ print_word(".");
+ outflags |= MMAN_nl;
+ return(1);
+}
+
+static void
+post_bl(DECL_ARGS)
+{
+
+ switch (n->norm->Bl.type) {
+ case LIST_column:
+ print_line(".TE", 0);
+ break;
+ case LIST_enum:
+ n->norm->Bl.count = 0;
+ break;
+ default:
+ break;
+ }
+
+ if (n->norm->Bl.offs) {
+ print_line(".RE", MMAN_nl);
+ assert(Bl_stack_len);
+ Bl_stack_len--;
+ assert(0 == Bl_stack[Bl_stack_len]);
+ } else {
+ outflags |= MMAN_PP | MMAN_nl;
+ outflags &= ~(MMAN_sp | MMAN_br);
+ }
+
+ /* Maybe we are inside an enclosing list? */
+ if (NULL != n->parent->next)
+ mid_it();
+
+}
+
+static int
+pre_br(DECL_ARGS)
+{
+
+ outflags |= MMAN_br;
+ return(0);
+}
+
+static int
+pre_bx(DECL_ARGS)
+{
+
+ n = n->child;
+ if (n) {
+ print_word(n->string);
+ outflags &= ~MMAN_spc;
+ n = n->next;
+ }
+ print_word("BSD");
+ if (NULL == n)
+ return(0);
+ outflags &= ~MMAN_spc;
+ print_word("-");
+ outflags &= ~MMAN_spc;
+ print_word(n->string);
+ return(0);
+}
+
+static int
+pre_dl(DECL_ARGS)
+{
+
+ print_offs("6n");
+ return(1);
+}
+
+static void
+post_dl(DECL_ARGS)
+{
+
+ print_line(".RE", MMAN_nl);
+
+ /* Maybe we are inside an enclosing list? */
+ if (NULL != n->parent->next)
+ mid_it();
+}
+
+static int
+pre_em(DECL_ARGS)
+{
+
+ font_push('I');
+ return(1);
+}
+
+static int
+pre_en(DECL_ARGS)
+{
+
+ if (NULL == n->norm->Es ||
+ NULL == n->norm->Es->child)
+ return(1);
+
+ print_word(n->norm->Es->child->string);
+ outflags &= ~MMAN_spc;
+ return(1);
+}
+
+static void
+post_en(DECL_ARGS)
+{
+
+ if (NULL == n->norm->Es ||
+ NULL == n->norm->Es->child ||
+ NULL == n->norm->Es->child->next)
+ return;
+
+ outflags &= ~MMAN_spc;
+ print_word(n->norm->Es->child->next->string);
+ return;
+}
+
+static void
+post_eo(DECL_ARGS)
+{
+
+ if (MDOC_HEAD == n->type || MDOC_BODY == n->type)
+ outflags &= ~MMAN_spc;
+}
+
+static int
+pre_es(DECL_ARGS)
+{
+
+ return(0);
+}
+
+static int
+pre_fa(DECL_ARGS)
+{
+ int am_Fa;
+
+ am_Fa = MDOC_Fa == n->tok;
+
+ if (am_Fa)
+ n = n->child;
+
+ while (NULL != n) {
+ font_push('I');
+ if (am_Fa || MDOC_SYNPRETTY & n->flags)
+ outflags |= MMAN_nbrword;
+ print_node(meta, n);
+ font_pop();
+ if (NULL != (n = n->next))
+ print_word(",");
+ }
+ return(0);
+}
+
+static void
+post_fa(DECL_ARGS)
+{
+
+ if (NULL != n->next && MDOC_Fa == n->next->tok)
+ print_word(",");
+}
+
+static int
+pre_fd(DECL_ARGS)
+{
+
+ pre_syn(n);
+ font_push('B');
+ return(1);
+}
+
+static void
+post_fd(DECL_ARGS)
+{
+
+ font_pop();
+ outflags |= MMAN_br;
+}
+
+static int
+pre_fl(DECL_ARGS)
+{
+
+ font_push('B');
+ print_word("\\-");
+ outflags &= ~MMAN_spc;
+ return(1);
+}
+
+static void
+post_fl(DECL_ARGS)
+{
+
+ font_pop();
+ if (0 == n->nchild && NULL != n->next &&
+ n->next->line == n->line)
+ outflags &= ~MMAN_spc;
+}
+
+static int
+pre_fn(DECL_ARGS)
+{
+
+ pre_syn(n);
+
+ n = n->child;
+ if (NULL == n)
+ return(0);
+
+ if (MDOC_SYNPRETTY & n->flags)
+ print_block(".HP 4n", MMAN_nl);
+
+ font_push('B');
+ print_node(meta, n);
+ font_pop();
+ outflags &= ~MMAN_spc;
+ print_word("(");
+ outflags &= ~MMAN_spc;
+
+ n = n->next;
+ if (NULL != n)
+ pre_fa(meta, n);
+ return(0);
+}
+
+static void
+post_fn(DECL_ARGS)
+{
+
+ print_word(")");
+ if (MDOC_SYNPRETTY & n->flags) {
+ print_word(";");
+ outflags |= MMAN_PP;
+ }
+}
+
+static int
+pre_fo(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ pre_syn(n);
+ break;
+ case MDOC_HEAD:
+ if (MDOC_SYNPRETTY & n->flags)
+ print_block(".HP 4n", MMAN_nl);
+ font_push('B');
+ break;
+ case MDOC_BODY:
+ outflags &= ~MMAN_spc;
+ print_word("(");
+ outflags &= ~MMAN_spc;
+ break;
+ default:
+ break;
+ }
+ return(1);
+}
+
+static void
+post_fo(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_HEAD:
+ font_pop();
+ break;
+ case MDOC_BODY:
+ post_fn(meta, n);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_ft(DECL_ARGS)
+{
+
+ pre_syn(n);
+ font_push('I');
+ return(1);
+}
+
+static int
+pre_in(DECL_ARGS)
+{
+
+ if (MDOC_SYNPRETTY & n->flags) {
+ pre_syn(n);
+ font_push('B');
+ print_word("#include <");
+ outflags &= ~MMAN_spc;
+ } else {
+ print_word("<");
+ outflags &= ~MMAN_spc;
+ font_push('I');
+ }
+ return(1);
+}
+
+static void
+post_in(DECL_ARGS)
+{
+
+ if (MDOC_SYNPRETTY & n->flags) {
+ outflags &= ~MMAN_spc;
+ print_word(">");
+ font_pop();
+ outflags |= MMAN_br;
+ } else {
+ font_pop();
+ outflags &= ~MMAN_spc;
+ print_word(">");
+ }
+}
+
+static int
+pre_it(DECL_ARGS)
+{
+ const struct mdoc_node *bln;
+
+ switch (n->type) {
+ case MDOC_HEAD:
+ outflags |= MMAN_PP | MMAN_nl;
+ bln = n->parent->parent;
+ if (0 == bln->norm->Bl.comp ||
+ (NULL == n->parent->prev &&
+ NULL == bln->parent->prev))
+ outflags |= MMAN_sp;
+ outflags &= ~MMAN_br;
+ switch (bln->norm->Bl.type) {
+ case LIST_item:
+ return(0);
+ case LIST_inset:
+ /* FALLTHROUGH */
+ case LIST_diag:
+ /* FALLTHROUGH */
+ case LIST_ohang:
+ if (bln->norm->Bl.type == LIST_diag)
+ print_line(".B \"", 0);
+ else
+ print_line(".R \"", 0);
+ outflags &= ~MMAN_spc;
+ return(1);
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ print_width(bln->norm->Bl.width, NULL, 0);
+ TPremain = 0;
+ outflags |= MMAN_nl;
+ font_push('B');
+ if (LIST_bullet == bln->norm->Bl.type)
+ print_word("o");
+ else
+ print_word("-");
+ font_pop();
+ outflags |= MMAN_nl;
+ return(0);
+ case LIST_enum:
+ print_width(bln->norm->Bl.width, NULL, 0);
+ TPremain = 0;
+ outflags |= MMAN_nl;
+ print_count(&bln->norm->Bl.count);
+ outflags |= MMAN_nl;
+ return(0);
+ case LIST_hang:
+ print_width(bln->norm->Bl.width, n->child, 6);
+ TPremain = 0;
+ outflags |= MMAN_nl;
+ return(1);
+ case LIST_tag:
+ print_width(bln->norm->Bl.width, n->child, 0);
+ putchar('\n');
+ outflags &= ~MMAN_spc;
+ return(1);
+ default:
+ return(1);
+ }
+ default:
+ break;
+ }
+ return(1);
+}
+
+/*
+ * This function is called after closing out an indented block.
+ * If we are inside an enclosing list, restore its indentation.
+ */
+static void
+mid_it(void)
+{
+ char buf[24];
+
+ /* Nothing to do outside a list. */
+ if (0 == Bl_stack_len || 0 == Bl_stack[Bl_stack_len - 1])
+ return;
+
+ /* The indentation has already been set up. */
+ if (Bl_stack_post[Bl_stack_len - 1])
+ return;
+
+ /* Restore the indentation of the enclosing list. */
+ print_line(".RS", MMAN_Bk_susp);
+ (void)snprintf(buf, sizeof(buf), "%zun",
+ Bl_stack[Bl_stack_len - 1]);
+ print_word(buf);
+
+ /* Remeber to close out this .RS block later. */
+ Bl_stack_post[Bl_stack_len - 1] = 1;
+}
+
+static void
+post_it(DECL_ARGS)
+{
+ const struct mdoc_node *bln;
+
+ bln = n->parent->parent;
+
+ switch (n->type) {
+ case MDOC_HEAD:
+ switch (bln->norm->Bl.type) {
+ case LIST_diag:
+ outflags &= ~MMAN_spc;
+ print_word("\\ ");
+ break;
+ case LIST_ohang:
+ outflags |= MMAN_br;
+ break;
+ default:
+ break;
+ }
+ break;
+ case MDOC_BODY:
+ switch (bln->norm->Bl.type) {
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ /* FALLTHROUGH */
+ case LIST_enum:
+ /* FALLTHROUGH */
+ case LIST_hang:
+ /* FALLTHROUGH */
+ case LIST_tag:
+ assert(Bl_stack_len);
+ Bl_stack[--Bl_stack_len] = 0;
+
+ /*
+ * Our indentation had to be restored
+ * after a child display or child list.
+ * Close out that indentation block now.
+ */
+ if (Bl_stack_post[Bl_stack_len]) {
+ print_line(".RE", MMAN_nl);
+ Bl_stack_post[Bl_stack_len] = 0;
+ }
+ break;
+ case LIST_column:
+ if (NULL != n->next) {
+ putchar('\t');
+ outflags &= ~MMAN_spc;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+post_lb(DECL_ARGS)
+{
+
+ if (SEC_LIBRARY == n->sec)
+ outflags |= MMAN_br;
+}
+
+static int
+pre_lk(DECL_ARGS)
+{
+ const struct mdoc_node *link, *descr;
+
+ if (NULL == (link = n->child))
+ return(0);
+
+ if (NULL != (descr = link->next)) {
+ font_push('I');
+ while (NULL != descr) {
+ print_word(descr->string);
+ descr = descr->next;
+ }
+ print_word(":");
+ font_pop();
+ }
+
+ font_push('B');
+ print_word(link->string);
+ font_pop();
+ return(0);
+}
+
+static int
+pre_ll(DECL_ARGS)
+{
+
+ print_line(".ll", 0);
+ return(1);
+}
+
+static int
+pre_li(DECL_ARGS)
+{
+
+ font_push('R');
+ return(1);
+}
+
+static int
+pre_nm(DECL_ARGS)
+{
+ char *name;
+
+ if (MDOC_BLOCK == n->type) {
+ outflags |= MMAN_Bk;
+ pre_syn(n);
+ }
+ if (MDOC_ELEM != n->type && MDOC_HEAD != n->type)
+ return(1);
+ name = n->child ? n->child->string : meta->name;
+ if (NULL == name)
+ return(0);
+ if (MDOC_HEAD == n->type) {
+ if (NULL == n->parent->prev)
+ outflags |= MMAN_sp;
+ print_block(".HP", 0);
+ printf(" %zun", strlen(name) + 1);
+ outflags |= MMAN_nl;
+ }
+ font_push('B');
+ if (NULL == n->child)
+ print_word(meta->name);
+ return(1);
+}
+
+static void
+post_nm(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ outflags &= ~MMAN_Bk;
+ break;
+ case MDOC_HEAD:
+ /* FALLTHROUGH */
+ case MDOC_ELEM:
+ if (n->child != NULL || meta->name != NULL)
+ font_pop();
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_no(DECL_ARGS)
+{
+
+ outflags |= MMAN_spc_force;
+ return(1);
+}
+
+static int
+pre_ns(DECL_ARGS)
+{
+
+ outflags &= ~MMAN_spc;
+ return(0);
+}
+
+static void
+post_pf(DECL_ARGS)
+{
+
+ outflags &= ~MMAN_spc;
+}
+
+static int
+pre_pp(DECL_ARGS)
+{
+
+ if (MDOC_It != n->parent->tok)
+ outflags |= MMAN_PP;
+ outflags |= MMAN_sp | MMAN_nl;
+ outflags &= ~MMAN_br;
+ return(0);
+}
+
+static int
+pre_rs(DECL_ARGS)
+{
+
+ if (SEC_SEE_ALSO == n->sec) {
+ outflags |= MMAN_PP | MMAN_sp | MMAN_nl;
+ outflags &= ~MMAN_br;
+ }
+ return(1);
+}
+
+static int
+pre_rv(DECL_ARGS)
+{
+ int nchild;
+
+ outflags |= MMAN_br | MMAN_nl;
+
+ nchild = n->nchild;
+ if (nchild > 0) {
+ print_word("The");
+
+ for (n = n->child; n; n = n->next) {
+ font_push('B');
+ print_word(n->string);
+ font_pop();
+
+ outflags &= ~MMAN_spc;
+ print_word("()");
+
+ if (n->next == NULL)
+ continue;
+
+ if (nchild > 2) {
+ outflags &= ~MMAN_spc;
+ print_word(",");
+ }
+ if (n->next->next == NULL)
+ print_word("and");
+ }
+
+ if (nchild > 1)
+ print_word("functions return");
+ else
+ print_word("function returns");
+
+ print_word("the value\\~0 if successful;");
+ } else
+ print_word("Upon successful completion, "
+ "the value\\~0 is returned;");
+
+ print_word("otherwise the value\\~\\-1 is returned"
+ " and the global variable");
+
+ font_push('I');
+ print_word("errno");
+ font_pop();
+
+ print_word("is set to indicate the error.");
+ outflags |= MMAN_nl;
+ return(0);
+}
+
+static int
+pre_sm(DECL_ARGS)
+{
+
+ if (NULL == n->child)
+ outflags ^= MMAN_Sm;
+ else if (0 == strcmp("on", n->child->string))
+ outflags |= MMAN_Sm;
+ else
+ outflags &= ~MMAN_Sm;
+
+ if (MMAN_Sm & outflags)
+ outflags |= MMAN_spc;
+
+ return(0);
+}
+
+static int
+pre_sp(DECL_ARGS)
+{
+
+ if (MMAN_PP & outflags) {
+ outflags &= ~MMAN_PP;
+ print_line(".PP", 0);
+ } else
+ print_line(".sp", 0);
+ return(1);
+}
+
+static void
+post_sp(DECL_ARGS)
+{
+
+ outflags |= MMAN_nl;
+}
+
+static int
+pre_sy(DECL_ARGS)
+{
+
+ font_push('B');
+ return(1);
+}
+
+static int
+pre_vt(DECL_ARGS)
+{
+
+ if (MDOC_SYNPRETTY & n->flags) {
+ switch (n->type) {
+ case MDOC_BLOCK:
+ pre_syn(n);
+ return(1);
+ case MDOC_BODY:
+ break;
+ default:
+ return(0);
+ }
+ }
+ font_push('I');
+ return(1);
+}
+
+static void
+post_vt(DECL_ARGS)
+{
+
+ if (MDOC_SYNPRETTY & n->flags && MDOC_BODY != n->type)
+ return;
+ font_pop();
+}
+
+static int
+pre_xr(DECL_ARGS)
+{
+
+ n = n->child;
+ if (NULL == n)
+ return(0);
+ print_node(meta, n);
+ n = n->next;
+ if (NULL == n)
+ return(0);
+ outflags &= ~MMAN_spc;
+ print_word("(");
+ print_node(meta, n);
+ print_word(")");
+ return(0);
+}
+
+static int
+pre_ux(DECL_ARGS)
+{
+
+ print_word(manacts[n->tok].prefix);
+ if (NULL == n->child)
+ return(0);
+ outflags &= ~MMAN_spc;
+ print_word("\\ ");
+ outflags &= ~MMAN_spc;
+ return(1);
+}
diff --git a/contrib/mdocml/mdoc_term.c b/contrib/mdocml/mdoc_term.c
new file mode 100644
index 0000000..eda3622
--- /dev/null
+++ b/contrib/mdocml/mdoc_term.c
@@ -0,0 +1,2239 @@
+/* $Id: mdoc_term.c,v 1.275 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2013 Franco Fichtner <franco@lastsummer.de>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "term.h"
+#include "mdoc.h"
+#include "main.h"
+
+struct termpair {
+ struct termpair *ppair;
+ int count;
+};
+
+#define DECL_ARGS struct termp *p, \
+ struct termpair *pair, \
+ const struct mdoc_meta *meta, \
+ struct mdoc_node *n
+
+struct termact {
+ int (*pre)(DECL_ARGS);
+ void (*post)(DECL_ARGS);
+};
+
+static size_t a2width(const struct termp *, const char *);
+static size_t a2height(const struct termp *, const char *);
+static size_t a2offs(const struct termp *, const char *);
+
+static void print_bvspace(struct termp *,
+ const struct mdoc_node *,
+ const struct mdoc_node *);
+static void print_mdoc_node(DECL_ARGS);
+static void print_mdoc_nodelist(DECL_ARGS);
+static void print_mdoc_head(struct termp *, const void *);
+static void print_mdoc_foot(struct termp *, const void *);
+static void synopsis_pre(struct termp *,
+ const struct mdoc_node *);
+
+static void termp____post(DECL_ARGS);
+static void termp__t_post(DECL_ARGS);
+static void termp_an_post(DECL_ARGS);
+static void termp_bd_post(DECL_ARGS);
+static void termp_bk_post(DECL_ARGS);
+static void termp_bl_post(DECL_ARGS);
+static void termp_fd_post(DECL_ARGS);
+static void termp_fo_post(DECL_ARGS);
+static void termp_in_post(DECL_ARGS);
+static void termp_it_post(DECL_ARGS);
+static void termp_lb_post(DECL_ARGS);
+static void termp_nm_post(DECL_ARGS);
+static void termp_pf_post(DECL_ARGS);
+static void termp_quote_post(DECL_ARGS);
+static void termp_sh_post(DECL_ARGS);
+static void termp_ss_post(DECL_ARGS);
+
+static int termp__a_pre(DECL_ARGS);
+static int termp__t_pre(DECL_ARGS);
+static int termp_an_pre(DECL_ARGS);
+static int termp_ap_pre(DECL_ARGS);
+static int termp_bd_pre(DECL_ARGS);
+static int termp_bf_pre(DECL_ARGS);
+static int termp_bk_pre(DECL_ARGS);
+static int termp_bl_pre(DECL_ARGS);
+static int termp_bold_pre(DECL_ARGS);
+static int termp_bt_pre(DECL_ARGS);
+static int termp_bx_pre(DECL_ARGS);
+static int termp_cd_pre(DECL_ARGS);
+static int termp_d1_pre(DECL_ARGS);
+static int termp_es_pre(DECL_ARGS);
+static int termp_ex_pre(DECL_ARGS);
+static int termp_fa_pre(DECL_ARGS);
+static int termp_fd_pre(DECL_ARGS);
+static int termp_fl_pre(DECL_ARGS);
+static int termp_fn_pre(DECL_ARGS);
+static int termp_fo_pre(DECL_ARGS);
+static int termp_ft_pre(DECL_ARGS);
+static int termp_in_pre(DECL_ARGS);
+static int termp_it_pre(DECL_ARGS);
+static int termp_li_pre(DECL_ARGS);
+static int termp_ll_pre(DECL_ARGS);
+static int termp_lk_pre(DECL_ARGS);
+static int termp_nd_pre(DECL_ARGS);
+static int termp_nm_pre(DECL_ARGS);
+static int termp_ns_pre(DECL_ARGS);
+static int termp_quote_pre(DECL_ARGS);
+static int termp_rs_pre(DECL_ARGS);
+static int termp_rv_pre(DECL_ARGS);
+static int termp_sh_pre(DECL_ARGS);
+static int termp_sm_pre(DECL_ARGS);
+static int termp_sp_pre(DECL_ARGS);
+static int termp_ss_pre(DECL_ARGS);
+static int termp_under_pre(DECL_ARGS);
+static int termp_ud_pre(DECL_ARGS);
+static int termp_vt_pre(DECL_ARGS);
+static int termp_xr_pre(DECL_ARGS);
+static int termp_xx_pre(DECL_ARGS);
+
+static const struct termact termacts[MDOC_MAX] = {
+ { termp_ap_pre, NULL }, /* Ap */
+ { NULL, NULL }, /* Dd */
+ { NULL, NULL }, /* Dt */
+ { NULL, NULL }, /* Os */
+ { termp_sh_pre, termp_sh_post }, /* Sh */
+ { termp_ss_pre, termp_ss_post }, /* Ss */
+ { termp_sp_pre, NULL }, /* Pp */
+ { termp_d1_pre, termp_bl_post }, /* D1 */
+ { termp_d1_pre, termp_bl_post }, /* Dl */
+ { termp_bd_pre, termp_bd_post }, /* Bd */
+ { NULL, NULL }, /* Ed */
+ { termp_bl_pre, termp_bl_post }, /* Bl */
+ { NULL, NULL }, /* El */
+ { termp_it_pre, termp_it_post }, /* It */
+ { termp_under_pre, NULL }, /* Ad */
+ { termp_an_pre, termp_an_post }, /* An */
+ { termp_under_pre, NULL }, /* Ar */
+ { termp_cd_pre, NULL }, /* Cd */
+ { termp_bold_pre, NULL }, /* Cm */
+ { NULL, NULL }, /* Dv */
+ { NULL, NULL }, /* Er */
+ { NULL, NULL }, /* Ev */
+ { termp_ex_pre, NULL }, /* Ex */
+ { termp_fa_pre, NULL }, /* Fa */
+ { termp_fd_pre, termp_fd_post }, /* Fd */
+ { termp_fl_pre, NULL }, /* Fl */
+ { termp_fn_pre, NULL }, /* Fn */
+ { termp_ft_pre, NULL }, /* Ft */
+ { termp_bold_pre, NULL }, /* Ic */
+ { termp_in_pre, termp_in_post }, /* In */
+ { termp_li_pre, NULL }, /* Li */
+ { termp_nd_pre, NULL }, /* Nd */
+ { termp_nm_pre, termp_nm_post }, /* Nm */
+ { termp_quote_pre, termp_quote_post }, /* Op */
+ { termp_ft_pre, NULL }, /* Ot */
+ { termp_under_pre, NULL }, /* Pa */
+ { termp_rv_pre, NULL }, /* Rv */
+ { NULL, NULL }, /* St */
+ { termp_under_pre, NULL }, /* Va */
+ { termp_vt_pre, NULL }, /* Vt */
+ { termp_xr_pre, NULL }, /* Xr */
+ { termp__a_pre, termp____post }, /* %A */
+ { termp_under_pre, termp____post }, /* %B */
+ { NULL, termp____post }, /* %D */
+ { termp_under_pre, termp____post }, /* %I */
+ { termp_under_pre, termp____post }, /* %J */
+ { NULL, termp____post }, /* %N */
+ { NULL, termp____post }, /* %O */
+ { NULL, termp____post }, /* %P */
+ { NULL, termp____post }, /* %R */
+ { termp__t_pre, termp__t_post }, /* %T */
+ { NULL, termp____post }, /* %V */
+ { NULL, NULL }, /* Ac */
+ { termp_quote_pre, termp_quote_post }, /* Ao */
+ { termp_quote_pre, termp_quote_post }, /* Aq */
+ { NULL, NULL }, /* At */
+ { NULL, NULL }, /* Bc */
+ { termp_bf_pre, NULL }, /* Bf */
+ { termp_quote_pre, termp_quote_post }, /* Bo */
+ { termp_quote_pre, termp_quote_post }, /* Bq */
+ { termp_xx_pre, NULL }, /* Bsx */
+ { termp_bx_pre, NULL }, /* Bx */
+ { NULL, NULL }, /* Db */
+ { NULL, NULL }, /* Dc */
+ { termp_quote_pre, termp_quote_post }, /* Do */
+ { termp_quote_pre, termp_quote_post }, /* Dq */
+ { NULL, NULL }, /* Ec */ /* FIXME: no space */
+ { NULL, NULL }, /* Ef */
+ { termp_under_pre, NULL }, /* Em */
+ { termp_quote_pre, termp_quote_post }, /* Eo */
+ { termp_xx_pre, NULL }, /* Fx */
+ { termp_bold_pre, NULL }, /* Ms */
+ { NULL, NULL }, /* No */
+ { termp_ns_pre, NULL }, /* Ns */
+ { termp_xx_pre, NULL }, /* Nx */
+ { termp_xx_pre, NULL }, /* Ox */
+ { NULL, NULL }, /* Pc */
+ { NULL, termp_pf_post }, /* Pf */
+ { termp_quote_pre, termp_quote_post }, /* Po */
+ { termp_quote_pre, termp_quote_post }, /* Pq */
+ { NULL, NULL }, /* Qc */
+ { termp_quote_pre, termp_quote_post }, /* Ql */
+ { termp_quote_pre, termp_quote_post }, /* Qo */
+ { termp_quote_pre, termp_quote_post }, /* Qq */
+ { NULL, NULL }, /* Re */
+ { termp_rs_pre, NULL }, /* Rs */
+ { NULL, NULL }, /* Sc */
+ { termp_quote_pre, termp_quote_post }, /* So */
+ { termp_quote_pre, termp_quote_post }, /* Sq */
+ { termp_sm_pre, NULL }, /* Sm */
+ { termp_under_pre, NULL }, /* Sx */
+ { termp_bold_pre, NULL }, /* Sy */
+ { NULL, NULL }, /* Tn */
+ { termp_xx_pre, NULL }, /* Ux */
+ { NULL, NULL }, /* Xc */
+ { NULL, NULL }, /* Xo */
+ { termp_fo_pre, termp_fo_post }, /* Fo */
+ { NULL, NULL }, /* Fc */
+ { termp_quote_pre, termp_quote_post }, /* Oo */
+ { NULL, NULL }, /* Oc */
+ { termp_bk_pre, termp_bk_post }, /* Bk */
+ { NULL, NULL }, /* Ek */
+ { termp_bt_pre, NULL }, /* Bt */
+ { NULL, NULL }, /* Hf */
+ { termp_under_pre, NULL }, /* Fr */
+ { termp_ud_pre, NULL }, /* Ud */
+ { NULL, termp_lb_post }, /* Lb */
+ { termp_sp_pre, NULL }, /* Lp */
+ { termp_lk_pre, NULL }, /* Lk */
+ { termp_under_pre, NULL }, /* Mt */
+ { termp_quote_pre, termp_quote_post }, /* Brq */
+ { termp_quote_pre, termp_quote_post }, /* Bro */
+ { NULL, NULL }, /* Brc */
+ { NULL, termp____post }, /* %C */
+ { termp_es_pre, NULL }, /* Es */
+ { termp_quote_pre, termp_quote_post }, /* En */
+ { termp_xx_pre, NULL }, /* Dx */
+ { NULL, termp____post }, /* %Q */
+ { termp_sp_pre, NULL }, /* br */
+ { termp_sp_pre, NULL }, /* sp */
+ { NULL, termp____post }, /* %U */
+ { NULL, NULL }, /* Ta */
+ { termp_ll_pre, NULL }, /* ll */
+};
+
+
+void
+terminal_mdoc(void *arg, const struct mdoc *mdoc)
+{
+ const struct mdoc_node *n;
+ const struct mdoc_meta *meta;
+ struct termp *p;
+
+ p = (struct termp *)arg;
+
+ if (0 == p->defindent)
+ p->defindent = 5;
+
+ p->overstep = 0;
+ p->maxrmargin = p->defrmargin;
+ p->tabwidth = term_len(p, 5);
+
+ if (NULL == p->symtab)
+ p->symtab = mchars_alloc();
+
+ n = mdoc_node(mdoc);
+ meta = mdoc_meta(mdoc);
+
+ term_begin(p, print_mdoc_head, print_mdoc_foot, meta);
+
+ if (n->child) {
+ if (MDOC_Sh != n->child->tok)
+ term_vspace(p);
+ print_mdoc_nodelist(p, NULL, meta, n->child);
+ }
+
+ term_end(p);
+}
+
+static void
+print_mdoc_nodelist(DECL_ARGS)
+{
+
+ print_mdoc_node(p, pair, meta, n);
+ if (n->next)
+ print_mdoc_nodelist(p, pair, meta, n->next);
+}
+
+static void
+print_mdoc_node(DECL_ARGS)
+{
+ int chld;
+ struct termpair npair;
+ size_t offset, rmargin;
+
+ chld = 1;
+ offset = p->offset;
+ rmargin = p->rmargin;
+ n->prev_font = term_fontq(p);
+
+ memset(&npair, 0, sizeof(struct termpair));
+ npair.ppair = pair;
+
+ /*
+ * Keeps only work until the end of a line. If a keep was
+ * invoked in a prior line, revert it to PREKEEP.
+ */
+
+ if (TERMP_KEEP & p->flags) {
+ if (n->prev ? (n->prev->lastline != n->line) :
+ (n->parent && n->parent->line != n->line)) {
+ p->flags &= ~TERMP_KEEP;
+ p->flags |= TERMP_PREKEEP;
+ }
+ }
+
+ /*
+ * After the keep flags have been set up, we may now
+ * produce output. Note that some pre-handlers do so.
+ */
+
+ switch (n->type) {
+ case MDOC_TEXT:
+ if (' ' == *n->string && MDOC_LINE & n->flags)
+ term_newln(p);
+ if (MDOC_DELIMC & n->flags)
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, n->string);
+ if (MDOC_DELIMO & n->flags)
+ p->flags |= TERMP_NOSPACE;
+ break;
+ case MDOC_EQN:
+ term_eqn(p, n->eqn);
+ break;
+ case MDOC_TBL:
+ term_tbl(p, n->span);
+ break;
+ default:
+ if (termacts[n->tok].pre && ENDBODY_NOT == n->end)
+ chld = (*termacts[n->tok].pre)
+ (p, &npair, meta, n);
+ break;
+ }
+
+ if (chld && n->child)
+ print_mdoc_nodelist(p, &npair, meta, n->child);
+
+ term_fontpopq(p,
+ (ENDBODY_NOT == n->end ? n : n->pending)->prev_font);
+
+ switch (n->type) {
+ case MDOC_TEXT:
+ break;
+ case MDOC_TBL:
+ break;
+ case MDOC_EQN:
+ break;
+ default:
+ if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
+ break;
+ (void)(*termacts[n->tok].post)(p, &npair, meta, n);
+
+ /*
+ * Explicit end tokens not only call the post
+ * handler, but also tell the respective block
+ * that it must not call the post handler again.
+ */
+ if (ENDBODY_NOT != n->end)
+ n->pending->flags |= MDOC_ENDED;
+
+ /*
+ * End of line terminating an implicit block
+ * while an explicit block is still open.
+ * Continue the explicit block without spacing.
+ */
+ if (ENDBODY_NOSPACE == n->end)
+ p->flags |= TERMP_NOSPACE;
+ break;
+ }
+
+ if (MDOC_EOS & n->flags)
+ p->flags |= TERMP_SENTENCE;
+
+ if (MDOC_ll != n->tok) {
+ p->offset = offset;
+ p->rmargin = rmargin;
+ }
+}
+
+static void
+print_mdoc_foot(struct termp *p, const void *arg)
+{
+ const struct mdoc_meta *meta;
+
+ meta = (const struct mdoc_meta *)arg;
+
+ term_fontrepl(p, TERMFONT_NONE);
+
+ /*
+ * Output the footer in new-groff style, that is, three columns
+ * with the middle being the manual date and flanking columns
+ * being the operating system:
+ *
+ * SYSTEM DATE SYSTEM
+ */
+
+ term_vspace(p);
+
+ p->offset = 0;
+ p->rmargin = (p->maxrmargin -
+ term_strlen(p, meta->date) + term_len(p, 1)) / 2;
+ p->trailspace = 1;
+ p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+
+ term_word(p, meta->os);
+ term_flushln(p);
+
+ p->offset = p->rmargin;
+ p->rmargin = p->maxrmargin - term_strlen(p, meta->os);
+ p->flags |= TERMP_NOSPACE;
+
+ term_word(p, meta->date);
+ term_flushln(p);
+
+ p->offset = p->rmargin;
+ p->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~TERMP_NOBREAK;
+ p->flags |= TERMP_NOSPACE;
+
+ term_word(p, meta->os);
+ term_flushln(p);
+
+ p->offset = 0;
+ p->rmargin = p->maxrmargin;
+ p->flags = 0;
+}
+
+static void
+print_mdoc_head(struct termp *p, const void *arg)
+{
+ const struct mdoc_meta *meta;
+ char *volume, *title;
+ size_t vollen, titlen;
+
+ meta = (const struct mdoc_meta *)arg;
+
+ /*
+ * The header is strange. It has three components, which are
+ * really two with the first duplicated. It goes like this:
+ *
+ * IDENTIFIER TITLE IDENTIFIER
+ *
+ * The IDENTIFIER is NAME(SECTION), which is the command-name
+ * (if given, or "unknown" if not) followed by the manual page
+ * section. These are given in `Dt'. The TITLE is a free-form
+ * string depending on the manual volume. If not specified, it
+ * switches on the manual section.
+ */
+
+ p->offset = 0;
+ p->rmargin = p->maxrmargin;
+
+ assert(meta->vol);
+ if (NULL == meta->arch)
+ volume = mandoc_strdup(meta->vol);
+ else
+ mandoc_asprintf(&volume, "%s (%s)",
+ meta->vol, meta->arch);
+ vollen = term_strlen(p, volume);
+
+ if (NULL == meta->msec)
+ title = mandoc_strdup(meta->title);
+ else
+ mandoc_asprintf(&title, "%s(%s)",
+ meta->title, meta->msec);
+ titlen = term_strlen(p, title);
+
+ p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
+ p->trailspace = 1;
+ p->offset = 0;
+ p->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
+ (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
+ p->maxrmargin - vollen;
+
+ term_word(p, title);
+ term_flushln(p);
+
+ p->flags |= TERMP_NOSPACE;
+ p->offset = p->rmargin;
+ p->rmargin = p->offset + vollen + titlen < p->maxrmargin ?
+ p->maxrmargin - titlen : p->maxrmargin;
+
+ term_word(p, volume);
+ term_flushln(p);
+
+ p->flags &= ~TERMP_NOBREAK;
+ p->trailspace = 0;
+ if (p->rmargin + titlen <= p->maxrmargin) {
+ p->flags |= TERMP_NOSPACE;
+ p->offset = p->rmargin;
+ p->rmargin = p->maxrmargin;
+ term_word(p, title);
+ term_flushln(p);
+ }
+
+ p->flags &= ~TERMP_NOSPACE;
+ p->offset = 0;
+ p->rmargin = p->maxrmargin;
+ free(title);
+ free(volume);
+}
+
+static size_t
+a2height(const struct termp *p, const char *v)
+{
+ struct roffsu su;
+
+
+ assert(v);
+ if ( ! a2roffsu(v, &su, SCALE_VS))
+ SCALE_VS_INIT(&su, atoi(v));
+
+ return(term_vspan(p, &su));
+}
+
+static size_t
+a2width(const struct termp *p, const char *v)
+{
+ struct roffsu su;
+
+ assert(v);
+ if ( ! a2roffsu(v, &su, SCALE_MAX))
+ SCALE_HS_INIT(&su, term_strlen(p, v));
+
+ return(term_hspan(p, &su));
+}
+
+static size_t
+a2offs(const struct termp *p, const char *v)
+{
+ struct roffsu su;
+
+ if ('\0' == *v)
+ return(0);
+ else if (0 == strcmp(v, "left"))
+ return(0);
+ else if (0 == strcmp(v, "indent"))
+ return(term_len(p, p->defindent + 1));
+ else if (0 == strcmp(v, "indent-two"))
+ return(term_len(p, (p->defindent + 1) * 2));
+ else if ( ! a2roffsu(v, &su, SCALE_MAX))
+ SCALE_HS_INIT(&su, term_strlen(p, v));
+
+ return(term_hspan(p, &su));
+}
+
+/*
+ * Determine how much space to print out before block elements of `It'
+ * (and thus `Bl') and `Bd'. And then go ahead and print that space,
+ * too.
+ */
+static void
+print_bvspace(struct termp *p,
+ const struct mdoc_node *bl,
+ const struct mdoc_node *n)
+{
+ const struct mdoc_node *nn;
+
+ assert(n);
+
+ term_newln(p);
+
+ if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
+ return;
+ if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
+ return;
+
+ /* Do not vspace directly after Ss/Sh. */
+
+ for (nn = n; nn; nn = nn->parent) {
+ if (MDOC_BLOCK != nn->type)
+ continue;
+ if (MDOC_Ss == nn->tok)
+ return;
+ if (MDOC_Sh == nn->tok)
+ return;
+ if (NULL == nn->prev)
+ continue;
+ break;
+ }
+
+ /* A `-column' does not assert vspace within the list. */
+
+ if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
+ if (n->prev && MDOC_It == n->prev->tok)
+ return;
+
+ /* A `-diag' without body does not vspace. */
+
+ if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
+ if (n->prev && MDOC_It == n->prev->tok) {
+ assert(n->prev->body);
+ if (NULL == n->prev->body->child)
+ return;
+ }
+
+ term_vspace(p);
+}
+
+
+static int
+termp_ll_pre(DECL_ARGS)
+{
+
+ term_setwidth(p, n->nchild ? n->child->string : NULL);
+ return(0);
+}
+
+static int
+termp_it_pre(DECL_ARGS)
+{
+ const struct mdoc_node *bl, *nn;
+ char buf[24];
+ int i;
+ size_t width, offset, ncols, dcol;
+ enum mdoc_list type;
+
+ if (MDOC_BLOCK == n->type) {
+ print_bvspace(p, n->parent->parent, n);
+ return(1);
+ }
+
+ bl = n->parent->parent->parent;
+ type = bl->norm->Bl.type;
+
+ /*
+ * First calculate width and offset. This is pretty easy unless
+ * we're a -column list, in which case all prior columns must
+ * be accounted for.
+ */
+
+ width = offset = 0;
+
+ if (bl->norm->Bl.offs)
+ offset = a2offs(p, bl->norm->Bl.offs);
+
+ switch (type) {
+ case LIST_column:
+ if (MDOC_HEAD == n->type)
+ break;
+
+ /*
+ * Imitate groff's column handling:
+ * - For each earlier column, add its width.
+ * - For less than 5 columns, add four more blanks per
+ * column.
+ * - For exactly 5 columns, add three more blank per
+ * column.
+ * - For more than 5 columns, add only one column.
+ */
+ ncols = bl->norm->Bl.ncols;
+ dcol = ncols < 5 ? term_len(p, 4) :
+ ncols == 5 ? term_len(p, 3) : term_len(p, 1);
+
+ /*
+ * Calculate the offset by applying all prior MDOC_BODY,
+ * so we stop at the MDOC_HEAD (NULL == nn->prev).
+ */
+
+ for (i = 0, nn = n->prev;
+ nn->prev && i < (int)ncols;
+ nn = nn->prev, i++)
+ offset += dcol + a2width(p,
+ bl->norm->Bl.cols[i]);
+
+ /*
+ * When exceeding the declared number of columns, leave
+ * the remaining widths at 0. This will later be
+ * adjusted to the default width of 10, or, for the last
+ * column, stretched to the right margin.
+ */
+ if (i >= (int)ncols)
+ break;
+
+ /*
+ * Use the declared column widths, extended as explained
+ * in the preceding paragraph.
+ */
+ width = a2width(p, bl->norm->Bl.cols[i]) + dcol;
+ break;
+ default:
+ if (NULL == bl->norm->Bl.width)
+ break;
+
+ /*
+ * Note: buffer the width by 2, which is groff's magic
+ * number for buffering single arguments. See the above
+ * handling for column for how this changes.
+ */
+ assert(bl->norm->Bl.width);
+ width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
+ break;
+ }
+
+ /*
+ * List-type can override the width in the case of fixed-head
+ * values (bullet, dash/hyphen, enum). Tags need a non-zero
+ * offset.
+ */
+
+ switch (type) {
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ /* FALLTHROUGH */
+ case LIST_enum:
+ if (width < term_len(p, 2))
+ width = term_len(p, 2);
+ break;
+ case LIST_hang:
+ if (0 == width)
+ width = term_len(p, 8);
+ break;
+ case LIST_column:
+ /* FALLTHROUGH */
+ case LIST_tag:
+ if (0 == width)
+ width = term_len(p, 10);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Whitespace control. Inset bodies need an initial space,
+ * while diagonal bodies need two.
+ */
+
+ p->flags |= TERMP_NOSPACE;
+
+ switch (type) {
+ case LIST_diag:
+ if (MDOC_BODY == n->type)
+ term_word(p, "\\ \\ ");
+ break;
+ case LIST_inset:
+ if (MDOC_BODY == n->type && n->parent->head->nchild)
+ term_word(p, "\\ ");
+ break;
+ default:
+ break;
+ }
+
+ p->flags |= TERMP_NOSPACE;
+
+ switch (type) {
+ case LIST_diag:
+ if (MDOC_HEAD == n->type)
+ term_fontpush(p, TERMFONT_BOLD);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Pad and break control. This is the tricky part. These flags
+ * are documented in term_flushln() in term.c. Note that we're
+ * going to unset all of these flags in termp_it_post() when we
+ * exit.
+ */
+
+ switch (type) {
+ case LIST_enum:
+ /*
+ * Weird special case.
+ * Very narrow enum lists actually hang.
+ */
+ if (width == term_len(p, 2))
+ p->flags |= TERMP_HANG;
+ /* FALLTHROUGH */
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ if (MDOC_HEAD != n->type)
+ break;
+ p->flags |= TERMP_NOBREAK;
+ p->trailspace = 1;
+ break;
+ case LIST_hang:
+ if (MDOC_HEAD != n->type)
+ break;
+
+ /*
+ * This is ugly. If `-hang' is specified and the body
+ * is a `Bl' or `Bd', then we want basically to nullify
+ * the "overstep" effect in term_flushln() and treat
+ * this as a `-ohang' list instead.
+ */
+ if (n->next->child &&
+ (MDOC_Bl == n->next->child->tok ||
+ MDOC_Bd == n->next->child->tok))
+ break;
+
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
+ p->trailspace = 1;
+ break;
+ case LIST_tag:
+ if (MDOC_HEAD != n->type)
+ break;
+
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND;
+ p->trailspace = 2;
+
+ if (NULL == n->next || NULL == n->next->child)
+ p->flags |= TERMP_DANGLE;
+ break;
+ case LIST_column:
+ if (MDOC_HEAD == n->type)
+ break;
+
+ if (NULL == n->next) {
+ p->flags &= ~TERMP_NOBREAK;
+ p->trailspace = 0;
+ } else {
+ p->flags |= TERMP_NOBREAK;
+ p->trailspace = 1;
+ }
+
+ break;
+ case LIST_diag:
+ if (MDOC_HEAD != n->type)
+ break;
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND;
+ p->trailspace = 1;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Margin control. Set-head-width lists have their right
+ * margins shortened. The body for these lists has the offset
+ * necessarily lengthened. Everybody gets the offset.
+ */
+
+ p->offset += offset;
+
+ switch (type) {
+ case LIST_hang:
+ /*
+ * Same stipulation as above, regarding `-hang'. We
+ * don't want to recalculate rmargin and offsets when
+ * using `Bd' or `Bl' within `-hang' overstep lists.
+ */
+ if (MDOC_HEAD == n->type && n->next->child &&
+ (MDOC_Bl == n->next->child->tok ||
+ MDOC_Bd == n->next->child->tok))
+ break;
+ /* FALLTHROUGH */
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_enum:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ /* FALLTHROUGH */
+ case LIST_tag:
+ assert(width);
+ if (MDOC_HEAD == n->type)
+ p->rmargin = p->offset + width;
+ else {
+ p->offset += width;
+ if (p->rmargin < p->offset)
+ p->rmargin = p->offset;
+ }
+ break;
+ case LIST_column:
+ assert(width);
+ p->rmargin = p->offset + width;
+ /*
+ * XXX - this behaviour is not documented: the
+ * right-most column is filled to the right margin.
+ */
+ if (MDOC_HEAD == n->type)
+ break;
+ if (NULL == n->next && p->rmargin < p->maxrmargin)
+ p->rmargin = p->maxrmargin;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * The dash, hyphen, bullet and enum lists all have a special
+ * HEAD character (temporarily bold, in some cases).
+ */
+
+ if (MDOC_HEAD == n->type)
+ switch (type) {
+ case LIST_bullet:
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, "\\[bu]");
+ term_fontpop(p);
+ break;
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, "\\(hy");
+ term_fontpop(p);
+ break;
+ case LIST_enum:
+ (pair->ppair->ppair->count)++;
+ (void)snprintf(buf, sizeof(buf), "%d.",
+ pair->ppair->ppair->count);
+ term_word(p, buf);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If we're not going to process our children, indicate so here.
+ */
+
+ switch (type) {
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_item:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ /* FALLTHROUGH */
+ case LIST_enum:
+ if (MDOC_HEAD == n->type)
+ return(0);
+ break;
+ case LIST_column:
+ if (MDOC_HEAD == n->type)
+ return(0);
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static void
+termp_it_post(DECL_ARGS)
+{
+ enum mdoc_list type;
+
+ if (MDOC_BLOCK == n->type)
+ return;
+
+ type = n->parent->parent->parent->norm->Bl.type;
+
+ switch (type) {
+ case LIST_item:
+ /* FALLTHROUGH */
+ case LIST_diag:
+ /* FALLTHROUGH */
+ case LIST_inset:
+ if (MDOC_BODY == n->type)
+ term_newln(p);
+ break;
+ case LIST_column:
+ if (MDOC_BODY == n->type)
+ term_flushln(p);
+ break;
+ default:
+ term_newln(p);
+ break;
+ }
+
+ /*
+ * Now that our output is flushed, we can reset our tags. Since
+ * only `It' sets these flags, we're free to assume that nobody
+ * has munged them in the meanwhile.
+ */
+
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
+ TERMP_DANGLE | TERMP_HANG);
+ p->trailspace = 0;
+}
+
+static int
+termp_nm_pre(DECL_ARGS)
+{
+
+ if (MDOC_BLOCK == n->type) {
+ p->flags |= TERMP_PREKEEP;
+ return(1);
+ }
+
+ if (MDOC_BODY == n->type) {
+ if (NULL == n->child)
+ return(0);
+ p->flags |= TERMP_NOSPACE;
+ p->offset += term_len(p, 1) +
+ (NULL == n->prev->child ?
+ term_strlen(p, meta->name) :
+ MDOC_TEXT == n->prev->child->type ?
+ term_strlen(p, n->prev->child->string) :
+ term_len(p, 5));
+ if (p->rmargin < p->offset)
+ p->rmargin = p->offset;
+ return(1);
+ }
+
+ if (NULL == n->child && NULL == meta->name)
+ return(0);
+
+ if (MDOC_HEAD == n->type)
+ synopsis_pre(p, n->parent);
+
+ if (MDOC_HEAD == n->type && n->next->child) {
+ p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND;
+ p->trailspace = 1;
+ p->rmargin = p->offset + term_len(p, 1);
+ if (NULL == n->child) {
+ p->rmargin += term_strlen(p, meta->name);
+ } else if (MDOC_TEXT == n->child->type) {
+ p->rmargin += term_strlen(p, n->child->string);
+ if (n->child->next)
+ p->flags |= TERMP_HANG;
+ } else {
+ p->rmargin += term_len(p, 5);
+ p->flags |= TERMP_HANG;
+ }
+ }
+
+ term_fontpush(p, TERMFONT_BOLD);
+ if (NULL == n->child)
+ term_word(p, meta->name);
+ return(1);
+}
+
+static void
+termp_nm_post(DECL_ARGS)
+{
+
+ if (MDOC_BLOCK == n->type) {
+ p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
+ } else if (MDOC_HEAD == n->type && n->next->child) {
+ term_flushln(p);
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
+ p->trailspace = 0;
+ } else if (MDOC_BODY == n->type && n->child)
+ term_flushln(p);
+}
+
+static int
+termp_fl_pre(DECL_ARGS)
+{
+
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, "\\-");
+
+ if (n->child)
+ p->flags |= TERMP_NOSPACE;
+ else if (n->next && n->next->line == n->line)
+ p->flags |= TERMP_NOSPACE;
+
+ return(1);
+}
+
+static int
+termp__a_pre(DECL_ARGS)
+{
+
+ if (n->prev && MDOC__A == n->prev->tok)
+ if (NULL == n->next || MDOC__A != n->next->tok)
+ term_word(p, "and");
+
+ return(1);
+}
+
+static int
+termp_an_pre(DECL_ARGS)
+{
+
+ if (NULL == n->child)
+ return(1);
+
+ /*
+ * If not in the AUTHORS section, `An -split' will cause
+ * newlines to occur before the author name. If in the AUTHORS
+ * section, by default, the first `An' invocation is nosplit,
+ * then all subsequent ones, regardless of whether interspersed
+ * with other macros/text, are split. -split, in this case,
+ * will override the condition of the implied first -nosplit.
+ */
+
+ if (n->sec == SEC_AUTHORS) {
+ if ( ! (TERMP_ANPREC & p->flags)) {
+ if (TERMP_SPLIT & p->flags)
+ term_newln(p);
+ return(1);
+ }
+ if (TERMP_NOSPLIT & p->flags)
+ return(1);
+ term_newln(p);
+ return(1);
+ }
+
+ if (TERMP_SPLIT & p->flags)
+ term_newln(p);
+
+ return(1);
+}
+
+static void
+termp_an_post(DECL_ARGS)
+{
+
+ if (n->child) {
+ if (SEC_AUTHORS == n->sec)
+ p->flags |= TERMP_ANPREC;
+ return;
+ }
+
+ if (AUTH_split == n->norm->An.auth) {
+ p->flags &= ~TERMP_NOSPLIT;
+ p->flags |= TERMP_SPLIT;
+ } else if (AUTH_nosplit == n->norm->An.auth) {
+ p->flags &= ~TERMP_SPLIT;
+ p->flags |= TERMP_NOSPLIT;
+ }
+
+}
+
+static int
+termp_ns_pre(DECL_ARGS)
+{
+
+ if ( ! (MDOC_LINE & n->flags))
+ p->flags |= TERMP_NOSPACE;
+ return(1);
+}
+
+static int
+termp_rs_pre(DECL_ARGS)
+{
+
+ if (SEC_SEE_ALSO != n->sec)
+ return(1);
+ if (MDOC_BLOCK == n->type && n->prev)
+ term_vspace(p);
+ return(1);
+}
+
+static int
+termp_rv_pre(DECL_ARGS)
+{
+ int nchild;
+
+ term_newln(p);
+
+ nchild = n->nchild;
+ if (nchild > 0) {
+ term_word(p, "The");
+
+ for (n = n->child; n; n = n->next) {
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, n->string);
+ term_fontpop(p);
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "()");
+
+ if (n->next == NULL)
+ continue;
+
+ if (nchild > 2) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ",");
+ }
+ if (n->next->next == NULL)
+ term_word(p, "and");
+ }
+
+ if (nchild > 1)
+ term_word(p, "functions return");
+ else
+ term_word(p, "function returns");
+
+ term_word(p, "the value\\~0 if successful;");
+ } else
+ term_word(p, "Upon successful completion,"
+ " the value\\~0 is returned;");
+
+ term_word(p, "otherwise the value\\~\\-1 is returned"
+ " and the global variable");
+
+ term_fontpush(p, TERMFONT_UNDER);
+ term_word(p, "errno");
+ term_fontpop(p);
+
+ term_word(p, "is set to indicate the error.");
+ p->flags |= TERMP_SENTENCE;
+
+ return(0);
+}
+
+static int
+termp_ex_pre(DECL_ARGS)
+{
+ int nchild;
+
+ term_newln(p);
+ term_word(p, "The");
+
+ nchild = n->nchild;
+ for (n = n->child; n; n = n->next) {
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, n->string);
+ term_fontpop(p);
+
+ if (nchild > 2 && n->next) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ",");
+ }
+
+ if (n->next && NULL == n->next->next)
+ term_word(p, "and");
+ }
+
+ if (nchild > 1)
+ term_word(p, "utilities exit\\~0");
+ else
+ term_word(p, "utility exits\\~0");
+
+ term_word(p, "on success, and\\~>0 if an error occurs.");
+
+ p->flags |= TERMP_SENTENCE;
+ return(0);
+}
+
+static int
+termp_nd_pre(DECL_ARGS)
+{
+
+ if (MDOC_BODY != n->type)
+ return(1);
+
+#if defined(__OpenBSD__) || defined(__linux__)
+ term_word(p, "\\(en");
+#else
+ term_word(p, "\\(em");
+#endif
+ return(1);
+}
+
+static int
+termp_bl_pre(DECL_ARGS)
+{
+
+ return(MDOC_HEAD != n->type);
+}
+
+static void
+termp_bl_post(DECL_ARGS)
+{
+
+ if (MDOC_BLOCK == n->type)
+ term_newln(p);
+}
+
+static int
+termp_xr_pre(DECL_ARGS)
+{
+
+ if (NULL == (n = n->child))
+ return(0);
+
+ assert(MDOC_TEXT == n->type);
+ term_word(p, n->string);
+
+ if (NULL == (n = n->next))
+ return(0);
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "(");
+ p->flags |= TERMP_NOSPACE;
+
+ assert(MDOC_TEXT == n->type);
+ term_word(p, n->string);
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ")");
+
+ return(0);
+}
+
+/*
+ * This decides how to assert whitespace before any of the SYNOPSIS set
+ * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
+ * macro combos).
+ */
+static void
+synopsis_pre(struct termp *p, const struct mdoc_node *n)
+{
+ /*
+ * Obviously, if we're not in a SYNOPSIS or no prior macros
+ * exist, do nothing.
+ */
+ if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
+ return;
+
+ /*
+ * If we're the second in a pair of like elements, emit our
+ * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which
+ * case we soldier on.
+ */
+ if (n->prev->tok == n->tok &&
+ MDOC_Ft != n->tok &&
+ MDOC_Fo != n->tok &&
+ MDOC_Fn != n->tok) {
+ term_newln(p);
+ return;
+ }
+
+ /*
+ * If we're one of the SYNOPSIS set and non-like pair-wise after
+ * another (or Fn/Fo, which we've let slip through) then assert
+ * vertical space, else only newline and move on.
+ */
+ switch (n->prev->tok) {
+ case MDOC_Fd:
+ /* FALLTHROUGH */
+ case MDOC_Fn:
+ /* FALLTHROUGH */
+ case MDOC_Fo:
+ /* FALLTHROUGH */
+ case MDOC_In:
+ /* FALLTHROUGH */
+ case MDOC_Vt:
+ term_vspace(p);
+ break;
+ case MDOC_Ft:
+ if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
+ term_vspace(p);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ term_newln(p);
+ break;
+ }
+}
+
+static int
+termp_vt_pre(DECL_ARGS)
+{
+
+ if (MDOC_ELEM == n->type) {
+ synopsis_pre(p, n);
+ return(termp_under_pre(p, pair, meta, n));
+ } else if (MDOC_BLOCK == n->type) {
+ synopsis_pre(p, n);
+ return(1);
+ } else if (MDOC_HEAD == n->type)
+ return(0);
+
+ return(termp_under_pre(p, pair, meta, n));
+}
+
+static int
+termp_bold_pre(DECL_ARGS)
+{
+
+ term_fontpush(p, TERMFONT_BOLD);
+ return(1);
+}
+
+static int
+termp_fd_pre(DECL_ARGS)
+{
+
+ synopsis_pre(p, n);
+ return(termp_bold_pre(p, pair, meta, n));
+}
+
+static void
+termp_fd_post(DECL_ARGS)
+{
+
+ term_newln(p);
+}
+
+static int
+termp_sh_pre(DECL_ARGS)
+{
+
+ /* No vspace between consecutive `Sh' calls. */
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ if (n->prev && MDOC_Sh == n->prev->tok)
+ if (NULL == n->prev->body->child)
+ break;
+ term_vspace(p);
+ break;
+ case MDOC_HEAD:
+ term_fontpush(p, TERMFONT_BOLD);
+ break;
+ case MDOC_BODY:
+ p->offset = term_len(p, p->defindent);
+ if (SEC_AUTHORS == n->sec)
+ p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
+ break;
+ default:
+ break;
+ }
+ return(1);
+}
+
+static void
+termp_sh_post(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_HEAD:
+ term_newln(p);
+ break;
+ case MDOC_BODY:
+ term_newln(p);
+ p->offset = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+termp_bt_pre(DECL_ARGS)
+{
+
+ term_word(p, "is currently in beta test.");
+ p->flags |= TERMP_SENTENCE;
+ return(0);
+}
+
+static void
+termp_lb_post(DECL_ARGS)
+{
+
+ if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
+ term_newln(p);
+}
+
+static int
+termp_ud_pre(DECL_ARGS)
+{
+
+ term_word(p, "currently under development.");
+ p->flags |= TERMP_SENTENCE;
+ return(0);
+}
+
+static int
+termp_d1_pre(DECL_ARGS)
+{
+
+ if (MDOC_BLOCK != n->type)
+ return(1);
+ term_newln(p);
+ p->offset += term_len(p, p->defindent + 1);
+ return(1);
+}
+
+static int
+termp_ft_pre(DECL_ARGS)
+{
+
+ /* NB: MDOC_LINE does not effect this! */
+ synopsis_pre(p, n);
+ term_fontpush(p, TERMFONT_UNDER);
+ return(1);
+}
+
+static int
+termp_fn_pre(DECL_ARGS)
+{
+ size_t rmargin = 0;
+ int pretty;
+
+ pretty = MDOC_SYNPRETTY & n->flags;
+
+ synopsis_pre(p, n);
+
+ if (NULL == (n = n->child))
+ return(0);
+
+ if (pretty) {
+ rmargin = p->rmargin;
+ p->rmargin = p->offset + term_len(p, 4);
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
+ }
+
+ assert(MDOC_TEXT == n->type);
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, n->string);
+ term_fontpop(p);
+
+ if (pretty) {
+ term_flushln(p);
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
+ p->offset = p->rmargin;
+ p->rmargin = rmargin;
+ }
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "(");
+ p->flags |= TERMP_NOSPACE;
+
+ for (n = n->next; n; n = n->next) {
+ assert(MDOC_TEXT == n->type);
+ term_fontpush(p, TERMFONT_UNDER);
+ if (pretty)
+ p->flags |= TERMP_NBRWORD;
+ term_word(p, n->string);
+ term_fontpop(p);
+
+ if (n->next) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ",");
+ }
+ }
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ")");
+
+ if (pretty) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ";");
+ term_flushln(p);
+ }
+
+ return(0);
+}
+
+static int
+termp_fa_pre(DECL_ARGS)
+{
+ const struct mdoc_node *nn;
+
+ if (n->parent->tok != MDOC_Fo) {
+ term_fontpush(p, TERMFONT_UNDER);
+ return(1);
+ }
+
+ for (nn = n->child; nn; nn = nn->next) {
+ term_fontpush(p, TERMFONT_UNDER);
+ p->flags |= TERMP_NBRWORD;
+ term_word(p, nn->string);
+ term_fontpop(p);
+
+ if (nn->next || (n->next && n->next->tok == MDOC_Fa)) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ",");
+ }
+ }
+
+ return(0);
+}
+
+static int
+termp_bd_pre(DECL_ARGS)
+{
+ size_t tabwidth, lm, len, rm, rmax;
+ struct mdoc_node *nn;
+
+ if (MDOC_BLOCK == n->type) {
+ print_bvspace(p, n, n);
+ return(1);
+ } else if (MDOC_HEAD == n->type)
+ return(0);
+
+ if (n->norm->Bd.offs)
+ p->offset += a2offs(p, n->norm->Bd.offs);
+
+ /*
+ * If -ragged or -filled are specified, the block does nothing
+ * but change the indentation. If -unfilled or -literal are
+ * specified, text is printed exactly as entered in the display:
+ * for macro lines, a newline is appended to the line. Blank
+ * lines are allowed.
+ */
+
+ if (DISP_literal != n->norm->Bd.type &&
+ DISP_unfilled != n->norm->Bd.type &&
+ DISP_centered != n->norm->Bd.type)
+ return(1);
+
+ tabwidth = p->tabwidth;
+ if (DISP_literal == n->norm->Bd.type)
+ p->tabwidth = term_len(p, 8);
+
+ lm = p->offset;
+ rm = p->rmargin;
+ rmax = p->maxrmargin;
+ p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
+
+ for (nn = n->child; nn; nn = nn->next) {
+ if (DISP_centered == n->norm->Bd.type) {
+ if (MDOC_TEXT == nn->type) {
+ len = term_strlen(p, nn->string);
+ p->offset = len >= rm ? 0 :
+ lm + len >= rm ? rm - len :
+ (lm + rm - len) / 2;
+ } else
+ p->offset = lm;
+ }
+ print_mdoc_node(p, pair, meta, nn);
+ /*
+ * If the printed node flushes its own line, then we
+ * needn't do it here as well. This is hacky, but the
+ * notion of selective eoln whitespace is pretty dumb
+ * anyway, so don't sweat it.
+ */
+ switch (nn->tok) {
+ case MDOC_Sm:
+ /* FALLTHROUGH */
+ case MDOC_br:
+ /* FALLTHROUGH */
+ case MDOC_sp:
+ /* FALLTHROUGH */
+ case MDOC_Bl:
+ /* FALLTHROUGH */
+ case MDOC_D1:
+ /* FALLTHROUGH */
+ case MDOC_Dl:
+ /* FALLTHROUGH */
+ case MDOC_Lp:
+ /* FALLTHROUGH */
+ case MDOC_Pp:
+ continue;
+ default:
+ break;
+ }
+ if (nn->next && nn->next->line == nn->line)
+ continue;
+ term_flushln(p);
+ p->flags |= TERMP_NOSPACE;
+ }
+
+ p->tabwidth = tabwidth;
+ p->rmargin = rm;
+ p->maxrmargin = rmax;
+ return(0);
+}
+
+static void
+termp_bd_post(DECL_ARGS)
+{
+ size_t rm, rmax;
+
+ if (MDOC_BODY != n->type)
+ return;
+
+ rm = p->rmargin;
+ rmax = p->maxrmargin;
+
+ if (DISP_literal == n->norm->Bd.type ||
+ DISP_unfilled == n->norm->Bd.type)
+ p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
+
+ p->flags |= TERMP_NOSPACE;
+ term_newln(p);
+
+ p->rmargin = rm;
+ p->maxrmargin = rmax;
+}
+
+static int
+termp_bx_pre(DECL_ARGS)
+{
+
+ if (NULL != (n = n->child)) {
+ term_word(p, n->string);
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "BSD");
+ } else {
+ term_word(p, "BSD");
+ return(0);
+ }
+
+ if (NULL != (n = n->next)) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "-");
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, n->string);
+ }
+
+ return(0);
+}
+
+static int
+termp_xx_pre(DECL_ARGS)
+{
+ const char *pp;
+ int flags;
+
+ pp = NULL;
+ switch (n->tok) {
+ case MDOC_Bsx:
+ pp = "BSD/OS";
+ break;
+ case MDOC_Dx:
+ pp = "DragonFly";
+ break;
+ case MDOC_Fx:
+ pp = "FreeBSD";
+ break;
+ case MDOC_Nx:
+ pp = "NetBSD";
+ break;
+ case MDOC_Ox:
+ pp = "OpenBSD";
+ break;
+ case MDOC_Ux:
+ pp = "UNIX";
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ term_word(p, pp);
+ if (n->child) {
+ flags = p->flags;
+ p->flags |= TERMP_KEEP;
+ term_word(p, n->child->string);
+ p->flags = flags;
+ }
+ return(0);
+}
+
+static void
+termp_pf_post(DECL_ARGS)
+{
+
+ p->flags |= TERMP_NOSPACE;
+}
+
+static int
+termp_ss_pre(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ term_newln(p);
+ if (n->prev)
+ term_vspace(p);
+ break;
+ case MDOC_HEAD:
+ term_fontpush(p, TERMFONT_BOLD);
+ p->offset = term_len(p, (p->defindent+1)/2);
+ break;
+ case MDOC_BODY:
+ p->offset = term_len(p, p->defindent);
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static void
+termp_ss_post(DECL_ARGS)
+{
+
+ if (n->type == MDOC_HEAD || n->type == MDOC_BODY)
+ term_newln(p);
+}
+
+static int
+termp_cd_pre(DECL_ARGS)
+{
+
+ synopsis_pre(p, n);
+ term_fontpush(p, TERMFONT_BOLD);
+ return(1);
+}
+
+static int
+termp_in_pre(DECL_ARGS)
+{
+
+ synopsis_pre(p, n);
+
+ if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, "#include");
+ term_word(p, "<");
+ } else {
+ term_word(p, "<");
+ term_fontpush(p, TERMFONT_UNDER);
+ }
+
+ p->flags |= TERMP_NOSPACE;
+ return(1);
+}
+
+static void
+termp_in_post(DECL_ARGS)
+{
+
+ if (MDOC_SYNPRETTY & n->flags)
+ term_fontpush(p, TERMFONT_BOLD);
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ">");
+
+ if (MDOC_SYNPRETTY & n->flags)
+ term_fontpop(p);
+}
+
+static int
+termp_sp_pre(DECL_ARGS)
+{
+ size_t i, len;
+
+ switch (n->tok) {
+ case MDOC_sp:
+ len = n->child ? a2height(p, n->child->string) : 1;
+ break;
+ case MDOC_br:
+ len = 0;
+ break;
+ default:
+ len = 1;
+ break;
+ }
+
+ if (0 == len)
+ term_newln(p);
+ for (i = 0; i < len; i++)
+ term_vspace(p);
+
+ return(0);
+}
+
+static int
+termp_es_pre(DECL_ARGS)
+{
+
+ return(0);
+}
+
+static int
+termp_quote_pre(DECL_ARGS)
+{
+
+ if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
+ return(1);
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ /* FALLTHROUGH */
+ case MDOC_Aq:
+ term_word(p, "<");
+ break;
+ case MDOC_Bro:
+ /* FALLTHROUGH */
+ case MDOC_Brq:
+ term_word(p, "{");
+ break;
+ case MDOC_Oo:
+ /* FALLTHROUGH */
+ case MDOC_Op:
+ /* FALLTHROUGH */
+ case MDOC_Bo:
+ /* FALLTHROUGH */
+ case MDOC_Bq:
+ term_word(p, "[");
+ break;
+ case MDOC_Do:
+ /* FALLTHROUGH */
+ case MDOC_Dq:
+ term_word(p, "\\(lq");
+ break;
+ case MDOC_En:
+ if (NULL == n->norm->Es ||
+ NULL == n->norm->Es->child)
+ return(1);
+ term_word(p, n->norm->Es->child->string);
+ break;
+ case MDOC_Eo:
+ break;
+ case MDOC_Po:
+ /* FALLTHROUGH */
+ case MDOC_Pq:
+ term_word(p, "(");
+ break;
+ case MDOC__T:
+ /* FALLTHROUGH */
+ case MDOC_Qo:
+ /* FALLTHROUGH */
+ case MDOC_Qq:
+ term_word(p, "\"");
+ break;
+ case MDOC_Ql:
+ /* FALLTHROUGH */
+ case MDOC_So:
+ /* FALLTHROUGH */
+ case MDOC_Sq:
+ term_word(p, "\\(oq");
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ p->flags |= TERMP_NOSPACE;
+ return(1);
+}
+
+static void
+termp_quote_post(DECL_ARGS)
+{
+
+ if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
+ return;
+
+ if (MDOC_En != n->tok)
+ p->flags |= TERMP_NOSPACE;
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ /* FALLTHROUGH */
+ case MDOC_Aq:
+ term_word(p, ">");
+ break;
+ case MDOC_Bro:
+ /* FALLTHROUGH */
+ case MDOC_Brq:
+ term_word(p, "}");
+ break;
+ case MDOC_Oo:
+ /* FALLTHROUGH */
+ case MDOC_Op:
+ /* FALLTHROUGH */
+ case MDOC_Bo:
+ /* FALLTHROUGH */
+ case MDOC_Bq:
+ term_word(p, "]");
+ break;
+ case MDOC_Do:
+ /* FALLTHROUGH */
+ case MDOC_Dq:
+ term_word(p, "\\(rq");
+ break;
+ case MDOC_En:
+ if (NULL != n->norm->Es &&
+ NULL != n->norm->Es->child &&
+ NULL != n->norm->Es->child->next) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, n->norm->Es->child->next->string);
+ }
+ break;
+ case MDOC_Eo:
+ break;
+ case MDOC_Po:
+ /* FALLTHROUGH */
+ case MDOC_Pq:
+ term_word(p, ")");
+ break;
+ case MDOC__T:
+ /* FALLTHROUGH */
+ case MDOC_Qo:
+ /* FALLTHROUGH */
+ case MDOC_Qq:
+ term_word(p, "\"");
+ break;
+ case MDOC_Ql:
+ /* FALLTHROUGH */
+ case MDOC_So:
+ /* FALLTHROUGH */
+ case MDOC_Sq:
+ term_word(p, "\\(cq");
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+}
+
+static int
+termp_fo_pre(DECL_ARGS)
+{
+ size_t rmargin = 0;
+ int pretty;
+
+ pretty = MDOC_SYNPRETTY & n->flags;
+
+ if (MDOC_BLOCK == n->type) {
+ synopsis_pre(p, n);
+ return(1);
+ } else if (MDOC_BODY == n->type) {
+ if (pretty) {
+ rmargin = p->rmargin;
+ p->rmargin = p->offset + term_len(p, 4);
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND |
+ TERMP_HANG;
+ }
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "(");
+ p->flags |= TERMP_NOSPACE;
+ if (pretty) {
+ term_flushln(p);
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
+ TERMP_HANG);
+ p->offset = p->rmargin;
+ p->rmargin = rmargin;
+ }
+ return(1);
+ }
+
+ if (NULL == n->child)
+ return(0);
+
+ /* XXX: we drop non-initial arguments as per groff. */
+
+ assert(n->child->string);
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, n->child->string);
+ return(0);
+}
+
+static void
+termp_fo_post(DECL_ARGS)
+{
+
+ if (MDOC_BODY != n->type)
+ return;
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ")");
+
+ if (MDOC_SYNPRETTY & n->flags) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ";");
+ term_flushln(p);
+ }
+}
+
+static int
+termp_bf_pre(DECL_ARGS)
+{
+
+ if (MDOC_HEAD == n->type)
+ return(0);
+ else if (MDOC_BODY != n->type)
+ return(1);
+
+ if (FONT_Em == n->norm->Bf.font)
+ term_fontpush(p, TERMFONT_UNDER);
+ else if (FONT_Sy == n->norm->Bf.font)
+ term_fontpush(p, TERMFONT_BOLD);
+ else
+ term_fontpush(p, TERMFONT_NONE);
+
+ return(1);
+}
+
+static int
+termp_sm_pre(DECL_ARGS)
+{
+
+ if (NULL == n->child)
+ p->flags ^= TERMP_NONOSPACE;
+ else if (0 == strcmp("on", n->child->string))
+ p->flags &= ~TERMP_NONOSPACE;
+ else
+ p->flags |= TERMP_NONOSPACE;
+
+ if (p->col && ! (TERMP_NONOSPACE & p->flags))
+ p->flags &= ~TERMP_NOSPACE;
+
+ return(0);
+}
+
+static int
+termp_ap_pre(DECL_ARGS)
+{
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "'");
+ p->flags |= TERMP_NOSPACE;
+ return(1);
+}
+
+static void
+termp____post(DECL_ARGS)
+{
+
+ /*
+ * Handle lists of authors. In general, print each followed by
+ * a comma. Don't print the comma if there are only two
+ * authors.
+ */
+ if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
+ if (NULL == n->next->next || MDOC__A != n->next->next->tok)
+ if (NULL == n->prev || MDOC__A != n->prev->tok)
+ return;
+
+ /* TODO: %U. */
+
+ if (NULL == n->parent || MDOC_Rs != n->parent->tok)
+ return;
+
+ p->flags |= TERMP_NOSPACE;
+ if (NULL == n->next) {
+ term_word(p, ".");
+ p->flags |= TERMP_SENTENCE;
+ } else
+ term_word(p, ",");
+}
+
+static int
+termp_li_pre(DECL_ARGS)
+{
+
+ term_fontpush(p, TERMFONT_NONE);
+ return(1);
+}
+
+static int
+termp_lk_pre(DECL_ARGS)
+{
+ const struct mdoc_node *link, *descr;
+
+ if (NULL == (link = n->child))
+ return(0);
+
+ if (NULL != (descr = link->next)) {
+ term_fontpush(p, TERMFONT_UNDER);
+ while (NULL != descr) {
+ term_word(p, descr->string);
+ descr = descr->next;
+ }
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ":");
+ term_fontpop(p);
+ }
+
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, link->string);
+ term_fontpop(p);
+
+ return(0);
+}
+
+static int
+termp_bk_pre(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case MDOC_BLOCK:
+ break;
+ case MDOC_HEAD:
+ return(0);
+ case MDOC_BODY:
+ if (n->parent->args || 0 == n->prev->nchild)
+ p->flags |= TERMP_PREKEEP;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ return(1);
+}
+
+static void
+termp_bk_post(DECL_ARGS)
+{
+
+ if (MDOC_BODY == n->type)
+ p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
+}
+
+static void
+termp__t_post(DECL_ARGS)
+{
+
+ /*
+ * If we're in an `Rs' and there's a journal present, then quote
+ * us instead of underlining us (for disambiguation).
+ */
+ if (n->parent && MDOC_Rs == n->parent->tok &&
+ n->parent->norm->Rs.quote_T)
+ termp_quote_post(p, pair, meta, n);
+
+ termp____post(p, pair, meta, n);
+}
+
+static int
+termp__t_pre(DECL_ARGS)
+{
+
+ /*
+ * If we're in an `Rs' and there's a journal present, then quote
+ * us instead of underlining us (for disambiguation).
+ */
+ if (n->parent && MDOC_Rs == n->parent->tok &&
+ n->parent->norm->Rs.quote_T)
+ return(termp_quote_pre(p, pair, meta, n));
+
+ term_fontpush(p, TERMFONT_UNDER);
+ return(1);
+}
+
+static int
+termp_under_pre(DECL_ARGS)
+{
+
+ term_fontpush(p, TERMFONT_UNDER);
+ return(1);
+}
diff --git a/contrib/mdocml/mdoc_validate.c b/contrib/mdocml/mdoc_validate.c
new file mode 100644
index 0000000..78b64cf
--- /dev/null
+++ b/contrib/mdocml/mdoc_validate.c
@@ -0,0 +1,2488 @@
+/* $Id: mdoc_validate.c,v 1.243 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef OSNAME
+#include <sys/utsname.h>
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+/* FIXME: .Bl -diag can't have non-text children in HEAD. */
+
+#define PRE_ARGS struct mdoc *mdoc, struct mdoc_node *n
+#define POST_ARGS struct mdoc *mdoc
+
+enum check_ineq {
+ CHECK_LT,
+ CHECK_GT,
+ CHECK_EQ
+};
+
+enum check_lvl {
+ CHECK_WARN,
+ CHECK_ERROR,
+};
+
+typedef int (*v_pre)(PRE_ARGS);
+typedef int (*v_post)(POST_ARGS);
+
+struct valids {
+ v_pre pre;
+ v_post post;
+};
+
+static int check_count(struct mdoc *, enum mdoc_type,
+ enum check_lvl, enum check_ineq, int);
+static void check_text(struct mdoc *, int, int, char *);
+static void check_argv(struct mdoc *,
+ struct mdoc_node *, struct mdoc_argv *);
+static void check_args(struct mdoc *, struct mdoc_node *);
+static enum mdoc_sec a2sec(const char *);
+static size_t macro2len(enum mdoct);
+
+static int ebool(POST_ARGS);
+static int berr_ge1(POST_ARGS);
+static int bwarn_ge1(POST_ARGS);
+static int ewarn_eq0(POST_ARGS);
+static int ewarn_eq1(POST_ARGS);
+static int ewarn_ge1(POST_ARGS);
+static int ewarn_le1(POST_ARGS);
+static int hwarn_eq0(POST_ARGS);
+static int hwarn_eq1(POST_ARGS);
+static int hwarn_ge1(POST_ARGS);
+
+static int post_an(POST_ARGS);
+static int post_at(POST_ARGS);
+static int post_bf(POST_ARGS);
+static int post_bk(POST_ARGS);
+static int post_bl(POST_ARGS);
+static int post_bl_block(POST_ARGS);
+static int post_bl_block_width(POST_ARGS);
+static int post_bl_block_tag(POST_ARGS);
+static int post_bl_head(POST_ARGS);
+static int post_bx(POST_ARGS);
+static int post_d1(POST_ARGS);
+static int post_defaults(POST_ARGS);
+static int post_dd(POST_ARGS);
+static int post_dt(POST_ARGS);
+static int post_en(POST_ARGS);
+static int post_es(POST_ARGS);
+static int post_eoln(POST_ARGS);
+static int post_ex(POST_ARGS);
+static int post_fo(POST_ARGS);
+static int post_hyph(POST_ARGS);
+static int post_hyphtext(POST_ARGS);
+static int post_ignpar(POST_ARGS);
+static int post_it(POST_ARGS);
+static int post_lb(POST_ARGS);
+static int post_literal(POST_ARGS);
+static int post_nd(POST_ARGS);
+static int post_nm(POST_ARGS);
+static int post_ns(POST_ARGS);
+static int post_os(POST_ARGS);
+static int post_par(POST_ARGS);
+static int post_root(POST_ARGS);
+static int post_rs(POST_ARGS);
+static int post_sh(POST_ARGS);
+static int post_sh_body(POST_ARGS);
+static int post_sh_head(POST_ARGS);
+static int post_st(POST_ARGS);
+static int post_vt(POST_ARGS);
+static int pre_an(PRE_ARGS);
+static int pre_bd(PRE_ARGS);
+static int pre_bl(PRE_ARGS);
+static int pre_dd(PRE_ARGS);
+static int pre_display(PRE_ARGS);
+static int pre_dt(PRE_ARGS);
+static int pre_literal(PRE_ARGS);
+static int pre_obsolete(PRE_ARGS);
+static int pre_os(PRE_ARGS);
+static int pre_par(PRE_ARGS);
+static int pre_std(PRE_ARGS);
+
+static const struct valids mdoc_valids[MDOC_MAX] = {
+ { NULL, NULL }, /* Ap */
+ { pre_dd, post_dd }, /* Dd */
+ { pre_dt, post_dt }, /* Dt */
+ { pre_os, post_os }, /* Os */
+ { NULL, post_sh }, /* Sh */
+ { NULL, post_ignpar }, /* Ss */
+ { pre_par, post_par }, /* Pp */
+ { pre_display, post_d1 }, /* D1 */
+ { pre_literal, post_literal }, /* Dl */
+ { pre_bd, post_literal }, /* Bd */
+ { NULL, NULL }, /* Ed */
+ { pre_bl, post_bl }, /* Bl */
+ { NULL, NULL }, /* El */
+ { pre_par, post_it }, /* It */
+ { NULL, NULL }, /* Ad */
+ { pre_an, post_an }, /* An */
+ { NULL, post_defaults }, /* Ar */
+ { NULL, NULL }, /* Cd */
+ { NULL, NULL }, /* Cm */
+ { NULL, NULL }, /* Dv */
+ { NULL, NULL }, /* Er */
+ { NULL, NULL }, /* Ev */
+ { pre_std, post_ex }, /* Ex */
+ { NULL, NULL }, /* Fa */
+ { NULL, ewarn_ge1 }, /* Fd */
+ { NULL, NULL }, /* Fl */
+ { NULL, NULL }, /* Fn */
+ { NULL, NULL }, /* Ft */
+ { NULL, NULL }, /* Ic */
+ { NULL, ewarn_eq1 }, /* In */
+ { NULL, post_defaults }, /* Li */
+ { NULL, post_nd }, /* Nd */
+ { NULL, post_nm }, /* Nm */
+ { NULL, NULL }, /* Op */
+ { pre_obsolete, NULL }, /* Ot */
+ { NULL, post_defaults }, /* Pa */
+ { pre_std, NULL }, /* Rv */
+ { NULL, post_st }, /* St */
+ { NULL, NULL }, /* Va */
+ { NULL, post_vt }, /* Vt */
+ { NULL, ewarn_ge1 }, /* Xr */
+ { NULL, ewarn_ge1 }, /* %A */
+ { NULL, post_hyphtext }, /* %B */ /* FIXME: can be used outside Rs/Re. */
+ { NULL, ewarn_ge1 }, /* %D */
+ { NULL, ewarn_ge1 }, /* %I */
+ { NULL, ewarn_ge1 }, /* %J */
+ { NULL, post_hyphtext }, /* %N */
+ { NULL, post_hyphtext }, /* %O */
+ { NULL, ewarn_ge1 }, /* %P */
+ { NULL, post_hyphtext }, /* %R */
+ { NULL, post_hyphtext }, /* %T */ /* FIXME: can be used outside Rs/Re. */
+ { NULL, ewarn_ge1 }, /* %V */
+ { NULL, NULL }, /* Ac */
+ { NULL, NULL }, /* Ao */
+ { NULL, NULL }, /* Aq */
+ { NULL, post_at }, /* At */
+ { NULL, NULL }, /* Bc */
+ { NULL, post_bf }, /* Bf */
+ { NULL, NULL }, /* Bo */
+ { NULL, NULL }, /* Bq */
+ { NULL, NULL }, /* Bsx */
+ { NULL, post_bx }, /* Bx */
+ { NULL, ebool }, /* Db */
+ { NULL, NULL }, /* Dc */
+ { NULL, NULL }, /* Do */
+ { NULL, NULL }, /* Dq */
+ { NULL, NULL }, /* Ec */
+ { NULL, NULL }, /* Ef */
+ { NULL, NULL }, /* Em */
+ { NULL, NULL }, /* Eo */
+ { NULL, NULL }, /* Fx */
+ { NULL, NULL }, /* Ms */
+ { NULL, ewarn_eq0 }, /* No */
+ { NULL, post_ns }, /* Ns */
+ { NULL, NULL }, /* Nx */
+ { NULL, NULL }, /* Ox */
+ { NULL, NULL }, /* Pc */
+ { NULL, ewarn_eq1 }, /* Pf */
+ { NULL, NULL }, /* Po */
+ { NULL, NULL }, /* Pq */
+ { NULL, NULL }, /* Qc */
+ { NULL, NULL }, /* Ql */
+ { NULL, NULL }, /* Qo */
+ { NULL, NULL }, /* Qq */
+ { NULL, NULL }, /* Re */
+ { NULL, post_rs }, /* Rs */
+ { NULL, NULL }, /* Sc */
+ { NULL, NULL }, /* So */
+ { NULL, NULL }, /* Sq */
+ { NULL, ebool }, /* Sm */
+ { NULL, post_hyph }, /* Sx */
+ { NULL, NULL }, /* Sy */
+ { NULL, NULL }, /* Tn */
+ { NULL, NULL }, /* Ux */
+ { NULL, NULL }, /* Xc */
+ { NULL, NULL }, /* Xo */
+ { NULL, post_fo }, /* Fo */
+ { NULL, NULL }, /* Fc */
+ { NULL, NULL }, /* Oo */
+ { NULL, NULL }, /* Oc */
+ { NULL, post_bk }, /* Bk */
+ { NULL, NULL }, /* Ek */
+ { NULL, post_eoln }, /* Bt */
+ { NULL, NULL }, /* Hf */
+ { pre_obsolete, NULL }, /* Fr */
+ { NULL, post_eoln }, /* Ud */
+ { NULL, post_lb }, /* Lb */
+ { pre_par, post_par }, /* Lp */
+ { NULL, NULL }, /* Lk */
+ { NULL, post_defaults }, /* Mt */
+ { NULL, NULL }, /* Brq */
+ { NULL, NULL }, /* Bro */
+ { NULL, NULL }, /* Brc */
+ { NULL, ewarn_ge1 }, /* %C */
+ { pre_obsolete, post_es }, /* Es */
+ { pre_obsolete, post_en }, /* En */
+ { NULL, NULL }, /* Dx */
+ { NULL, ewarn_ge1 }, /* %Q */
+ { NULL, post_par }, /* br */
+ { NULL, post_par }, /* sp */
+ { NULL, ewarn_eq1 }, /* %U */
+ { NULL, NULL }, /* Ta */
+ { NULL, NULL }, /* ll */
+};
+
+#define RSORD_MAX 14 /* Number of `Rs' blocks. */
+
+static const enum mdoct rsord[RSORD_MAX] = {
+ MDOC__A,
+ MDOC__T,
+ MDOC__B,
+ MDOC__I,
+ MDOC__J,
+ MDOC__R,
+ MDOC__N,
+ MDOC__V,
+ MDOC__U,
+ MDOC__P,
+ MDOC__Q,
+ MDOC__C,
+ MDOC__D,
+ MDOC__O
+};
+
+static const char * const secnames[SEC__MAX] = {
+ NULL,
+ "NAME",
+ "LIBRARY",
+ "SYNOPSIS",
+ "DESCRIPTION",
+ "CONTEXT",
+ "IMPLEMENTATION NOTES",
+ "RETURN VALUES",
+ "ENVIRONMENT",
+ "FILES",
+ "EXIT STATUS",
+ "EXAMPLES",
+ "DIAGNOSTICS",
+ "COMPATIBILITY",
+ "ERRORS",
+ "SEE ALSO",
+ "STANDARDS",
+ "HISTORY",
+ "AUTHORS",
+ "CAVEATS",
+ "BUGS",
+ "SECURITY CONSIDERATIONS",
+ NULL
+};
+
+
+int
+mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
+{
+ v_pre p;
+
+ switch (n->type) {
+ case MDOC_TEXT:
+ check_text(mdoc, n->line, n->pos, n->string);
+ /* FALLTHROUGH */
+ case MDOC_TBL:
+ /* FALLTHROUGH */
+ case MDOC_EQN:
+ /* FALLTHROUGH */
+ case MDOC_ROOT:
+ return(1);
+ default:
+ break;
+ }
+
+ check_args(mdoc, n);
+ p = mdoc_valids[n->tok].pre;
+ return(*p ? (*p)(mdoc, n) : 1);
+}
+
+int
+mdoc_valid_post(struct mdoc *mdoc)
+{
+ struct mdoc_node *n;
+ v_post p;
+
+ n = mdoc->last;
+ if (n->flags & MDOC_VALID)
+ return(1);
+ n->flags |= MDOC_VALID;
+
+ switch (n->type) {
+ case MDOC_TEXT:
+ /* FALLTHROUGH */
+ case MDOC_EQN:
+ /* FALLTHROUGH */
+ case MDOC_TBL:
+ return(1);
+ case MDOC_ROOT:
+ return(post_root(mdoc));
+ default:
+ p = mdoc_valids[n->tok].post;
+ return(*p ? (*p)(mdoc) : 1);
+ }
+}
+
+static int
+check_count(struct mdoc *mdoc, enum mdoc_type type,
+ enum check_lvl lvl, enum check_ineq ineq, int val)
+{
+ const char *p;
+ enum mandocerr t;
+
+ if (mdoc->last->type != type)
+ return(1);
+
+ switch (ineq) {
+ case CHECK_LT:
+ p = "less than ";
+ if (mdoc->last->nchild < val)
+ return(1);
+ break;
+ case CHECK_GT:
+ p = "more than ";
+ if (mdoc->last->nchild > val)
+ return(1);
+ break;
+ case CHECK_EQ:
+ p = "";
+ if (val == mdoc->last->nchild)
+ return(1);
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ t = lvl == CHECK_WARN ? MANDOCERR_ARGCWARN : MANDOCERR_ARGCOUNT;
+ mandoc_vmsg(t, mdoc->parse, mdoc->last->line,
+ mdoc->last->pos, "want %s%d children (have %d)",
+ p, val, mdoc->last->nchild);
+ return(1);
+}
+
+static int
+berr_ge1(POST_ARGS)
+{
+
+ return(check_count(mdoc, MDOC_BODY, CHECK_ERROR, CHECK_GT, 0));
+}
+
+static int
+bwarn_ge1(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0));
+}
+
+static int
+ewarn_eq0(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0));
+}
+
+static int
+ewarn_eq1(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1));
+}
+
+static int
+ewarn_ge1(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0));
+}
+
+static int
+ewarn_le1(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2));
+}
+
+static int
+hwarn_eq0(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0));
+}
+
+static int
+hwarn_eq1(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 1));
+}
+
+static int
+hwarn_ge1(POST_ARGS)
+{
+ return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_GT, 0));
+}
+
+static void
+check_args(struct mdoc *mdoc, struct mdoc_node *n)
+{
+ int i;
+
+ if (NULL == n->args)
+ return;
+
+ assert(n->args->argc);
+ for (i = 0; i < (int)n->args->argc; i++)
+ check_argv(mdoc, n, &n->args->argv[i]);
+}
+
+static void
+check_argv(struct mdoc *mdoc, struct mdoc_node *n, struct mdoc_argv *v)
+{
+ int i;
+
+ for (i = 0; i < (int)v->sz; i++)
+ check_text(mdoc, v->line, v->pos, v->value[i]);
+}
+
+static void
+check_text(struct mdoc *mdoc, int ln, int pos, char *p)
+{
+ char *cp;
+
+ if (MDOC_LITERAL & mdoc->flags)
+ return;
+
+ for (cp = p; NULL != (p = strchr(p, '\t')); p++)
+ mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
+ ln, pos + (int)(p - cp), NULL);
+}
+
+static int
+pre_display(PRE_ARGS)
+{
+ struct mdoc_node *node;
+
+ if (MDOC_BLOCK != n->type)
+ return(1);
+
+ for (node = mdoc->last->parent; node; node = node->parent)
+ if (MDOC_BLOCK == node->type)
+ if (MDOC_Bd == node->tok)
+ break;
+
+ if (node)
+ mandoc_vmsg(MANDOCERR_BD_NEST,
+ mdoc->parse, n->line, n->pos,
+ "%s in Bd", mdoc_macronames[n->tok]);
+
+ return(1);
+}
+
+static int
+pre_bl(PRE_ARGS)
+{
+ struct mdoc_node *np;
+ struct mdoc_argv *argv, *wa;
+ int i;
+ enum mdocargt mdoclt;
+ enum mdoc_list lt;
+
+ if (MDOC_BLOCK != n->type) {
+ if (ENDBODY_NOT != n->end) {
+ assert(n->pending);
+ np = n->pending->parent;
+ } else
+ np = n->parent;
+
+ assert(np);
+ assert(MDOC_BLOCK == np->type);
+ assert(MDOC_Bl == np->tok);
+ return(1);
+ }
+
+ /*
+ * First figure out which kind of list to use: bind ourselves to
+ * the first mentioned list type and warn about any remaining
+ * ones. If we find no list type, we default to LIST_item.
+ */
+
+ wa = n->args->argv;
+ mdoclt = MDOC_ARG_MAX;
+ for (i = 0; n->args && i < (int)n->args->argc; i++) {
+ argv = n->args->argv + i;
+ lt = LIST__NONE;
+ switch (argv->arg) {
+ /* Set list types. */
+ case MDOC_Bullet:
+ lt = LIST_bullet;
+ break;
+ case MDOC_Dash:
+ lt = LIST_dash;
+ break;
+ case MDOC_Enum:
+ lt = LIST_enum;
+ break;
+ case MDOC_Hyphen:
+ lt = LIST_hyphen;
+ break;
+ case MDOC_Item:
+ lt = LIST_item;
+ break;
+ case MDOC_Tag:
+ lt = LIST_tag;
+ break;
+ case MDOC_Diag:
+ lt = LIST_diag;
+ break;
+ case MDOC_Hang:
+ lt = LIST_hang;
+ break;
+ case MDOC_Ohang:
+ lt = LIST_ohang;
+ break;
+ case MDOC_Inset:
+ lt = LIST_inset;
+ break;
+ case MDOC_Column:
+ lt = LIST_column;
+ break;
+ /* Set list arguments. */
+ case MDOC_Compact:
+ if (n->norm->Bl.comp)
+ mandoc_msg(MANDOCERR_ARG_REP,
+ mdoc->parse, argv->line,
+ argv->pos, "Bl -compact");
+ n->norm->Bl.comp = 1;
+ break;
+ case MDOC_Width:
+ wa = argv;
+ if (0 == argv->sz) {
+ mandoc_msg(MANDOCERR_ARG_EMPTY,
+ mdoc->parse, argv->line,
+ argv->pos, "Bl -width");
+ n->norm->Bl.width = "0n";
+ break;
+ }
+ if (NULL != n->norm->Bl.width)
+ mandoc_vmsg(MANDOCERR_ARG_REP,
+ mdoc->parse, argv->line,
+ argv->pos, "Bl -width %s",
+ argv->value[0]);
+ n->norm->Bl.width = argv->value[0];
+ break;
+ case MDOC_Offset:
+ if (0 == argv->sz) {
+ mandoc_msg(MANDOCERR_ARG_EMPTY,
+ mdoc->parse, argv->line,
+ argv->pos, "Bl -offset");
+ break;
+ }
+ if (NULL != n->norm->Bl.offs)
+ mandoc_vmsg(MANDOCERR_ARG_REP,
+ mdoc->parse, argv->line,
+ argv->pos, "Bl -offset %s",
+ argv->value[0]);
+ n->norm->Bl.offs = argv->value[0];
+ break;
+ default:
+ continue;
+ }
+ if (LIST__NONE == lt)
+ continue;
+ mdoclt = argv->arg;
+
+ /* Check: multiple list types. */
+
+ if (LIST__NONE != n->norm->Bl.type) {
+ mandoc_vmsg(MANDOCERR_BL_REP,
+ mdoc->parse, n->line, n->pos,
+ "Bl -%s", mdoc_argnames[argv->arg]);
+ continue;
+ }
+
+ /* The list type should come first. */
+
+ if (n->norm->Bl.width ||
+ n->norm->Bl.offs ||
+ n->norm->Bl.comp)
+ mandoc_vmsg(MANDOCERR_BL_LATETYPE,
+ mdoc->parse, n->line, n->pos, "Bl -%s",
+ mdoc_argnames[n->args->argv[0].arg]);
+
+ n->norm->Bl.type = lt;
+ if (LIST_column == lt) {
+ n->norm->Bl.ncols = argv->sz;
+ n->norm->Bl.cols = (void *)argv->value;
+ }
+ }
+
+ /* Allow lists to default to LIST_item. */
+
+ if (LIST__NONE == n->norm->Bl.type) {
+ mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
+ n->line, n->pos, "Bl");
+ n->norm->Bl.type = LIST_item;
+ }
+
+ /*
+ * Validate the width field. Some list types don't need width
+ * types and should be warned about them. Others should have it
+ * and must also be warned. Yet others have a default and need
+ * no warning.
+ */
+
+ switch (n->norm->Bl.type) {
+ case LIST_tag:
+ if (NULL == n->norm->Bl.width)
+ mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
+ n->line, n->pos, "Bl -tag");
+ break;
+ case LIST_column:
+ /* FALLTHROUGH */
+ case LIST_diag:
+ /* FALLTHROUGH */
+ case LIST_ohang:
+ /* FALLTHROUGH */
+ case LIST_inset:
+ /* FALLTHROUGH */
+ case LIST_item:
+ if (n->norm->Bl.width)
+ mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
+ wa->line, wa->pos, "Bl -%s",
+ mdoc_argnames[mdoclt]);
+ break;
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ if (NULL == n->norm->Bl.width)
+ n->norm->Bl.width = "2n";
+ break;
+ case LIST_enum:
+ if (NULL == n->norm->Bl.width)
+ n->norm->Bl.width = "3n";
+ break;
+ default:
+ break;
+ }
+
+ return(pre_par(mdoc, n));
+}
+
+static int
+pre_bd(PRE_ARGS)
+{
+ struct mdoc_node *np;
+ struct mdoc_argv *argv;
+ int i;
+ enum mdoc_disp dt;
+
+ pre_literal(mdoc, n);
+
+ if (MDOC_BLOCK != n->type) {
+ if (ENDBODY_NOT != n->end) {
+ assert(n->pending);
+ np = n->pending->parent;
+ } else
+ np = n->parent;
+
+ assert(np);
+ assert(MDOC_BLOCK == np->type);
+ assert(MDOC_Bd == np->tok);
+ return(1);
+ }
+
+ for (i = 0; n->args && i < (int)n->args->argc; i++) {
+ argv = n->args->argv + i;
+ dt = DISP__NONE;
+
+ switch (argv->arg) {
+ case MDOC_Centred:
+ dt = DISP_centered;
+ break;
+ case MDOC_Ragged:
+ dt = DISP_ragged;
+ break;
+ case MDOC_Unfilled:
+ dt = DISP_unfilled;
+ break;
+ case MDOC_Filled:
+ dt = DISP_filled;
+ break;
+ case MDOC_Literal:
+ dt = DISP_literal;
+ break;
+ case MDOC_File:
+ mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
+ n->line, n->pos, NULL);
+ return(0);
+ case MDOC_Offset:
+ if (0 == argv->sz) {
+ mandoc_msg(MANDOCERR_ARG_EMPTY,
+ mdoc->parse, argv->line,
+ argv->pos, "Bd -offset");
+ break;
+ }
+ if (NULL != n->norm->Bd.offs)
+ mandoc_vmsg(MANDOCERR_ARG_REP,
+ mdoc->parse, argv->line,
+ argv->pos, "Bd -offset %s",
+ argv->value[0]);
+ n->norm->Bd.offs = argv->value[0];
+ break;
+ case MDOC_Compact:
+ if (n->norm->Bd.comp)
+ mandoc_msg(MANDOCERR_ARG_REP,
+ mdoc->parse, argv->line,
+ argv->pos, "Bd -compact");
+ n->norm->Bd.comp = 1;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+ if (DISP__NONE == dt)
+ continue;
+
+ if (DISP__NONE == n->norm->Bd.type)
+ n->norm->Bd.type = dt;
+ else
+ mandoc_vmsg(MANDOCERR_BD_REP,
+ mdoc->parse, n->line, n->pos,
+ "Bd -%s", mdoc_argnames[argv->arg]);
+ }
+
+ if (DISP__NONE == n->norm->Bd.type) {
+ mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
+ n->line, n->pos, "Bd");
+ n->norm->Bd.type = DISP_ragged;
+ }
+
+ return(pre_par(mdoc, n));
+}
+
+static int
+pre_an(PRE_ARGS)
+{
+ struct mdoc_argv *argv;
+ size_t i;
+
+ if (n->args == NULL)
+ return(1);
+
+ for (i = 1; i < n->args->argc; i++) {
+ argv = n->args->argv + i;
+ mandoc_vmsg(MANDOCERR_AN_REP,
+ mdoc->parse, argv->line, argv->pos,
+ "An -%s", mdoc_argnames[argv->arg]);
+ }
+
+ argv = n->args->argv;
+ if (argv->arg == MDOC_Split)
+ n->norm->An.auth = AUTH_split;
+ else if (argv->arg == MDOC_Nosplit)
+ n->norm->An.auth = AUTH_nosplit;
+ else
+ abort();
+
+ return(1);
+}
+
+static int
+pre_std(PRE_ARGS)
+{
+
+ if (n->args && 1 == n->args->argc)
+ if (MDOC_Std == n->args->argv[0].arg)
+ return(1);
+
+ mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ return(1);
+}
+
+static int
+pre_obsolete(PRE_ARGS)
+{
+
+ if (MDOC_ELEM == n->type || MDOC_BLOCK == n->type)
+ mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ return(1);
+}
+
+static int
+pre_dt(PRE_ARGS)
+{
+
+ if (mdoc->meta.title != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+ n->line, n->pos, "Dt");
+ else if (mdoc->meta.os != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+ n->line, n->pos, "Dt after Os");
+ return(1);
+}
+
+static int
+pre_os(PRE_ARGS)
+{
+
+ if (mdoc->meta.os != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+ n->line, n->pos, "Os");
+ else if (mdoc->flags & MDOC_PBODY)
+ mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
+ n->line, n->pos, "Os");
+ return(1);
+}
+
+static int
+pre_dd(PRE_ARGS)
+{
+
+ if (mdoc->meta.date != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+ n->line, n->pos, "Dd");
+ else if (mdoc->flags & MDOC_PBODY)
+ mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
+ n->line, n->pos, "Dd");
+ else if (mdoc->meta.title != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+ n->line, n->pos, "Dd after Dt");
+ else if (mdoc->meta.os != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+ n->line, n->pos, "Dd after Os");
+ return(1);
+}
+
+static int
+post_bf(POST_ARGS)
+{
+ struct mdoc_node *np, *nch;
+ enum mdocargt arg;
+
+ /*
+ * Unlike other data pointers, these are "housed" by the HEAD
+ * element, which contains the goods.
+ */
+
+ if (MDOC_HEAD != mdoc->last->type) {
+ if (ENDBODY_NOT != mdoc->last->end) {
+ assert(mdoc->last->pending);
+ np = mdoc->last->pending->parent->head;
+ } else if (MDOC_BLOCK != mdoc->last->type) {
+ np = mdoc->last->parent->head;
+ } else
+ np = mdoc->last->head;
+
+ assert(np);
+ assert(MDOC_HEAD == np->type);
+ assert(MDOC_Bf == np->tok);
+ return(1);
+ }
+
+ np = mdoc->last;
+ assert(MDOC_BLOCK == np->parent->type);
+ assert(MDOC_Bf == np->parent->tok);
+
+ /* Check the number of arguments. */
+
+ nch = np->child;
+ if (NULL == np->parent->args) {
+ if (NULL == nch) {
+ mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
+ np->line, np->pos, "Bf");
+ return(1);
+ }
+ nch = nch->next;
+ }
+ if (NULL != nch)
+ mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
+ nch->line, nch->pos, "Bf ... %s", nch->string);
+
+ /* Extract argument into data. */
+
+ if (np->parent->args) {
+ arg = np->parent->args->argv[0].arg;
+ if (MDOC_Emphasis == arg)
+ np->norm->Bf.font = FONT_Em;
+ else if (MDOC_Literal == arg)
+ np->norm->Bf.font = FONT_Li;
+ else if (MDOC_Symbolic == arg)
+ np->norm->Bf.font = FONT_Sy;
+ else
+ abort();
+ return(1);
+ }
+
+ /* Extract parameter into data. */
+
+ if (0 == strcmp(np->child->string, "Em"))
+ np->norm->Bf.font = FONT_Em;
+ else if (0 == strcmp(np->child->string, "Li"))
+ np->norm->Bf.font = FONT_Li;
+ else if (0 == strcmp(np->child->string, "Sy"))
+ np->norm->Bf.font = FONT_Sy;
+ else
+ mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
+ np->child->line, np->child->pos,
+ "Bf %s", np->child->string);
+
+ return(1);
+}
+
+static int
+post_lb(POST_ARGS)
+{
+ struct mdoc_node *n;
+ const char *stdlibname;
+ char *libname;
+
+ check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1);
+
+ n = mdoc->last->child;
+
+ assert(n);
+ assert(MDOC_TEXT == n->type);
+
+ if (NULL == (stdlibname = mdoc_a2lib(n->string)))
+ mandoc_asprintf(&libname,
+ "library \\(lq%s\\(rq", n->string);
+ else
+ libname = mandoc_strdup(stdlibname);
+
+ free(n->string);
+ n->string = libname;
+ return(1);
+}
+
+static int
+post_eoln(POST_ARGS)
+{
+ const struct mdoc_node *n;
+
+ n = mdoc->last;
+ if (n->child)
+ mandoc_vmsg(MANDOCERR_ARG_SKIP,
+ mdoc->parse, n->line, n->pos,
+ "%s %s", mdoc_macronames[n->tok],
+ n->child->string);
+ return(1);
+}
+
+static int
+post_fo(POST_ARGS)
+{
+
+ hwarn_eq1(mdoc);
+ bwarn_ge1(mdoc);
+ return(1);
+}
+
+static int
+post_vt(POST_ARGS)
+{
+ const struct mdoc_node *n;
+
+ /*
+ * The Vt macro comes in both ELEM and BLOCK form, both of which
+ * have different syntaxes (yet more context-sensitive
+ * behaviour). ELEM types must have a child, which is already
+ * guaranteed by the in_line parsing routine; BLOCK types,
+ * specifically the BODY, should only have TEXT children.
+ */
+
+ if (MDOC_BODY != mdoc->last->type)
+ return(1);
+
+ for (n = mdoc->last->child; n; n = n->next)
+ if (MDOC_TEXT != n->type)
+ mandoc_msg(MANDOCERR_VT_CHILD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+
+ return(1);
+}
+
+static int
+post_nm(POST_ARGS)
+{
+
+ if (NULL != mdoc->meta.name)
+ return(1);
+
+ mdoc_deroff(&mdoc->meta.name, mdoc->last);
+
+ if (NULL == mdoc->meta.name)
+ mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, "Nm");
+ return(1);
+}
+
+static int
+post_nd(POST_ARGS)
+{
+
+ berr_ge1(mdoc);
+ return(post_hyph(mdoc));
+}
+
+static int
+post_d1(POST_ARGS)
+{
+
+ bwarn_ge1(mdoc);
+ return(post_hyph(mdoc));
+}
+
+static int
+post_literal(POST_ARGS)
+{
+
+ if (mdoc->last->tok == MDOC_Bd)
+ hwarn_eq0(mdoc);
+ bwarn_ge1(mdoc);
+
+ /*
+ * The `Dl' (note "el" not "one") and `Bd' macros unset the
+ * MDOC_LITERAL flag as they leave. Note that `Bd' only sets
+ * this in literal mode, but it doesn't hurt to just switch it
+ * off in general since displays can't be nested.
+ */
+
+ if (MDOC_BODY == mdoc->last->type)
+ mdoc->flags &= ~MDOC_LITERAL;
+
+ return(1);
+}
+
+static int
+post_defaults(POST_ARGS)
+{
+ struct mdoc_node *nn;
+
+ /*
+ * The `Ar' defaults to "file ..." if no value is provided as an
+ * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
+ * gets an empty string.
+ */
+
+ if (mdoc->last->child)
+ return(1);
+
+ nn = mdoc->last;
+ mdoc->next = MDOC_NEXT_CHILD;
+
+ switch (nn->tok) {
+ case MDOC_Ar:
+ if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "file"))
+ return(0);
+ if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "..."))
+ return(0);
+ break;
+ case MDOC_Li:
+ if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, ""))
+ return(0);
+ break;
+ case MDOC_Pa:
+ /* FALLTHROUGH */
+ case MDOC_Mt:
+ if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "~"))
+ return(0);
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ mdoc->last = nn;
+ return(1);
+}
+
+static int
+post_at(POST_ARGS)
+{
+ struct mdoc_node *n;
+ const char *std_att;
+ char *att;
+
+ n = mdoc->last;
+ if (n->child == NULL) {
+ mdoc->next = MDOC_NEXT_CHILD;
+ if ( ! mdoc_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX"))
+ return(0);
+ mdoc->last = n;
+ return(1);
+ }
+
+ /*
+ * If we have a child, look it up in the standard keys. If a
+ * key exist, use that instead of the child; if it doesn't,
+ * prefix "AT&T UNIX " to the existing data.
+ */
+
+ n = n->child;
+ assert(MDOC_TEXT == n->type);
+ if (NULL == (std_att = mdoc_a2att(n->string))) {
+ mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
+ n->line, n->pos, "At %s", n->string);
+ mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
+ } else
+ att = mandoc_strdup(std_att);
+
+ free(n->string);
+ n->string = att;
+ return(1);
+}
+
+static int
+post_an(POST_ARGS)
+{
+ struct mdoc_node *np;
+
+ np = mdoc->last;
+ if (AUTH__NONE == np->norm->An.auth) {
+ if (0 == np->child)
+ check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0);
+ } else if (np->child)
+ check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0);
+
+ return(1);
+}
+
+static int
+post_en(POST_ARGS)
+{
+
+ if (MDOC_BLOCK == mdoc->last->type)
+ mdoc->last->norm->Es = mdoc->last_es;
+ return(1);
+}
+
+static int
+post_es(POST_ARGS)
+{
+
+ mdoc->last_es = mdoc->last;
+ return(1);
+}
+
+static int
+post_it(POST_ARGS)
+{
+ int i, cols;
+ enum mdoc_list lt;
+ struct mdoc_node *nbl, *nit, *nch;
+
+ nit = mdoc->last;
+ if (MDOC_BLOCK != nit->type)
+ return(1);
+
+ nbl = nit->parent->parent;
+ lt = nbl->norm->Bl.type;
+
+ switch (lt) {
+ case LIST_tag:
+ /* FALLTHROUGH */
+ case LIST_hang:
+ /* FALLTHROUGH */
+ case LIST_ohang:
+ /* FALLTHROUGH */
+ case LIST_inset:
+ /* FALLTHROUGH */
+ case LIST_diag:
+ if (NULL == nit->head->child)
+ mandoc_vmsg(MANDOCERR_IT_NOHEAD,
+ mdoc->parse, nit->line, nit->pos,
+ "Bl -%s It",
+ mdoc_argnames[nbl->args->argv[0].arg]);
+ break;
+ case LIST_bullet:
+ /* FALLTHROUGH */
+ case LIST_dash:
+ /* FALLTHROUGH */
+ case LIST_enum:
+ /* FALLTHROUGH */
+ case LIST_hyphen:
+ if (NULL == nit->body->child)
+ mandoc_vmsg(MANDOCERR_IT_NOBODY,
+ mdoc->parse, nit->line, nit->pos,
+ "Bl -%s It",
+ mdoc_argnames[nbl->args->argv[0].arg]);
+ /* FALLTHROUGH */
+ case LIST_item:
+ if (NULL != nit->head->child)
+ mandoc_vmsg(MANDOCERR_ARG_SKIP,
+ mdoc->parse, nit->line, nit->pos,
+ "It %s", nit->head->child->string);
+ break;
+ case LIST_column:
+ cols = (int)nbl->norm->Bl.ncols;
+
+ assert(NULL == nit->head->child);
+
+ for (i = 0, nch = nit->child; nch; nch = nch->next)
+ if (MDOC_BODY == nch->type)
+ i++;
+
+ if (i < cols || i > cols + 1)
+ mandoc_vmsg(MANDOCERR_ARGCOUNT,
+ mdoc->parse, nit->line, nit->pos,
+ "columns == %d (have %d)", cols, i);
+ break;
+ default:
+ abort();
+ }
+
+ return(1);
+}
+
+static int
+post_bl_block(POST_ARGS)
+{
+ struct mdoc_node *n, *ni, *nc;
+
+ /*
+ * These are fairly complicated, so we've broken them into two
+ * functions. post_bl_block_tag() is called when a -tag is
+ * specified, but no -width (it must be guessed). The second
+ * when a -width is specified (macro indicators must be
+ * rewritten into real lengths).
+ */
+
+ n = mdoc->last;
+
+ if (LIST_tag == n->norm->Bl.type &&
+ NULL == n->norm->Bl.width) {
+ if ( ! post_bl_block_tag(mdoc))
+ return(0);
+ assert(n->norm->Bl.width);
+ } else if (NULL != n->norm->Bl.width) {
+ if ( ! post_bl_block_width(mdoc))
+ return(0);
+ assert(n->norm->Bl.width);
+ }
+
+ for (ni = n->body->child; ni; ni = ni->next) {
+ if (NULL == ni->body)
+ continue;
+ nc = ni->body->last;
+ while (NULL != nc) {
+ switch (nc->tok) {
+ case MDOC_Pp:
+ /* FALLTHROUGH */
+ case MDOC_Lp:
+ /* FALLTHROUGH */
+ case MDOC_br:
+ break;
+ default:
+ nc = NULL;
+ continue;
+ }
+ if (NULL == ni->next) {
+ mandoc_msg(MANDOCERR_PAR_MOVE,
+ mdoc->parse, nc->line, nc->pos,
+ mdoc_macronames[nc->tok]);
+ if ( ! mdoc_node_relink(mdoc, nc))
+ return(0);
+ } else if (0 == n->norm->Bl.comp &&
+ LIST_column != n->norm->Bl.type) {
+ mandoc_vmsg(MANDOCERR_PAR_SKIP,
+ mdoc->parse, nc->line, nc->pos,
+ "%s before It",
+ mdoc_macronames[nc->tok]);
+ mdoc_node_delete(mdoc, nc);
+ } else
+ break;
+ nc = ni->body->last;
+ }
+ }
+ return(1);
+}
+
+static int
+post_bl_block_width(POST_ARGS)
+{
+ size_t width;
+ int i;
+ enum mdoct tok;
+ struct mdoc_node *n;
+ char buf[24];
+
+ n = mdoc->last;
+
+ /*
+ * Calculate the real width of a list from the -width string,
+ * which may contain a macro (with a known default width), a
+ * literal string, or a scaling width.
+ *
+ * If the value to -width is a macro, then we re-write it to be
+ * the macro's width as set in share/tmac/mdoc/doc-common.
+ */
+
+ if (0 == strcmp(n->norm->Bl.width, "Ds"))
+ width = 6;
+ else if (MDOC_MAX == (tok = mdoc_hash_find(n->norm->Bl.width)))
+ return(1);
+ else
+ width = macro2len(tok);
+
+ /* The value already exists: free and reallocate it. */
+
+ assert(n->args);
+
+ for (i = 0; i < (int)n->args->argc; i++)
+ if (MDOC_Width == n->args->argv[i].arg)
+ break;
+
+ assert(i < (int)n->args->argc);
+
+ (void)snprintf(buf, sizeof(buf), "%un", (unsigned int)width);
+ free(n->args->argv[i].value[0]);
+ n->args->argv[i].value[0] = mandoc_strdup(buf);
+
+ /* Set our width! */
+ n->norm->Bl.width = n->args->argv[i].value[0];
+ return(1);
+}
+
+static int
+post_bl_block_tag(POST_ARGS)
+{
+ struct mdoc_node *n, *nn;
+ size_t sz, ssz;
+ int i;
+ char buf[24];
+
+ /*
+ * Calculate the -width for a `Bl -tag' list if it hasn't been
+ * provided. Uses the first head macro. NOTE AGAIN: this is
+ * ONLY if the -width argument has NOT been provided. See
+ * post_bl_block_width() for converting the -width string.
+ */
+
+ sz = 10;
+ n = mdoc->last;
+
+ for (nn = n->body->child; nn; nn = nn->next) {
+ if (MDOC_It != nn->tok)
+ continue;
+
+ assert(MDOC_BLOCK == nn->type);
+ nn = nn->head->child;
+
+ if (nn == NULL)
+ break;
+
+ if (MDOC_TEXT == nn->type) {
+ sz = strlen(nn->string) + 1;
+ break;
+ }
+
+ if (0 != (ssz = macro2len(nn->tok)))
+ sz = ssz;
+
+ break;
+ }
+
+ /* Defaults to ten ens. */
+
+ (void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
+
+ /*
+ * We have to dynamically add this to the macro's argument list.
+ * We're guaranteed that a MDOC_Width doesn't already exist.
+ */
+
+ assert(n->args);
+ i = (int)(n->args->argc)++;
+
+ n->args->argv = mandoc_reallocarray(n->args->argv,
+ n->args->argc, sizeof(struct mdoc_argv));
+
+ n->args->argv[i].arg = MDOC_Width;
+ n->args->argv[i].line = n->line;
+ n->args->argv[i].pos = n->pos;
+ n->args->argv[i].sz = 1;
+ n->args->argv[i].value = mandoc_malloc(sizeof(char *));
+ n->args->argv[i].value[0] = mandoc_strdup(buf);
+
+ /* Set our width! */
+ n->norm->Bl.width = n->args->argv[i].value[0];
+ return(1);
+}
+
+static int
+post_bl_head(POST_ARGS)
+{
+ struct mdoc_node *np, *nn, *nnp;
+ struct mdoc_argv *argv;
+ int i, j;
+
+ if (LIST_column != mdoc->last->norm->Bl.type)
+ /* FIXME: this should be ERROR class... */
+ return(hwarn_eq0(mdoc));
+
+ /*
+ * Append old-style lists, where the column width specifiers
+ * trail as macro parameters, to the new-style ("normal-form")
+ * lists where they're argument values following -column.
+ */
+
+ if (mdoc->last->child == NULL)
+ return(1);
+
+ np = mdoc->last->parent;
+ assert(np->args);
+
+ for (j = 0; j < (int)np->args->argc; j++)
+ if (MDOC_Column == np->args->argv[j].arg)
+ break;
+
+ assert(j < (int)np->args->argc);
+
+ /*
+ * Accommodate for new-style groff column syntax. Shuffle the
+ * child nodes, all of which must be TEXT, as arguments for the
+ * column field. Then, delete the head children.
+ */
+
+ argv = np->args->argv + j;
+ i = argv->sz;
+ argv->sz += mdoc->last->nchild;
+ argv->value = mandoc_reallocarray(argv->value,
+ argv->sz, sizeof(char *));
+
+ mdoc->last->norm->Bl.ncols = argv->sz;
+ mdoc->last->norm->Bl.cols = (void *)argv->value;
+
+ for (nn = mdoc->last->child; nn; i++) {
+ argv->value[i] = nn->string;
+ nn->string = NULL;
+ nnp = nn;
+ nn = nn->next;
+ mdoc_node_delete(NULL, nnp);
+ }
+
+ mdoc->last->nchild = 0;
+ mdoc->last->child = NULL;
+
+ return(1);
+}
+
+static int
+post_bl(POST_ARGS)
+{
+ struct mdoc_node *nparent, *nprev; /* of the Bl block */
+ struct mdoc_node *nblock, *nbody; /* of the Bl */
+ struct mdoc_node *nchild, *nnext; /* of the Bl body */
+
+ nbody = mdoc->last;
+ switch (nbody->type) {
+ case MDOC_BLOCK:
+ return(post_bl_block(mdoc));
+ case MDOC_HEAD:
+ return(post_bl_head(mdoc));
+ case MDOC_BODY:
+ break;
+ default:
+ return(1);
+ }
+
+ bwarn_ge1(mdoc);
+
+ nchild = nbody->child;
+ while (NULL != nchild) {
+ if (MDOC_It == nchild->tok || MDOC_Sm == nchild->tok) {
+ nchild = nchild->next;
+ continue;
+ }
+
+ mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
+ nchild->line, nchild->pos,
+ mdoc_macronames[nchild->tok]);
+
+ /*
+ * Move the node out of the Bl block.
+ * First, collect all required node pointers.
+ */
+
+ nblock = nbody->parent;
+ nprev = nblock->prev;
+ nparent = nblock->parent;
+ nnext = nchild->next;
+
+ /*
+ * Unlink this child.
+ */
+
+ assert(NULL == nchild->prev);
+ if (0 == --nbody->nchild) {
+ nbody->child = NULL;
+ nbody->last = NULL;
+ assert(NULL == nnext);
+ } else {
+ nbody->child = nnext;
+ nnext->prev = NULL;
+ }
+
+ /*
+ * Relink this child.
+ */
+
+ nchild->parent = nparent;
+ nchild->prev = nprev;
+ nchild->next = nblock;
+
+ nblock->prev = nchild;
+ nparent->nchild++;
+ if (NULL == nprev)
+ nparent->child = nchild;
+ else
+ nprev->next = nchild;
+
+ nchild = nnext;
+ }
+
+ return(1);
+}
+
+static int
+post_bk(POST_ARGS)
+{
+
+ hwarn_eq0(mdoc);
+ bwarn_ge1(mdoc);
+ return(1);
+}
+
+static int
+ebool(struct mdoc *mdoc)
+{
+ struct mdoc_node *nch;
+ enum mdoct tok;
+
+ tok = mdoc->last->tok;
+ nch = mdoc->last->child;
+
+ if (NULL == nch) {
+ if (MDOC_Sm == tok)
+ mdoc->flags ^= MDOC_SMOFF;
+ return(1);
+ }
+
+ check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2);
+
+ assert(MDOC_TEXT == nch->type);
+
+ if (0 == strcmp(nch->string, "on")) {
+ if (MDOC_Sm == tok)
+ mdoc->flags &= ~MDOC_SMOFF;
+ return(1);
+ }
+ if (0 == strcmp(nch->string, "off")) {
+ if (MDOC_Sm == tok)
+ mdoc->flags |= MDOC_SMOFF;
+ return(1);
+ }
+
+ mandoc_vmsg(MANDOCERR_SM_BAD,
+ mdoc->parse, nch->line, nch->pos,
+ "%s %s", mdoc_macronames[tok], nch->string);
+ return(mdoc_node_relink(mdoc, nch));
+}
+
+static int
+post_root(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ /* Add missing prologue data. */
+
+ if (mdoc->meta.date == NULL)
+ mdoc->meta.date = mdoc->quick ?
+ mandoc_strdup("") :
+ mandoc_normdate(mdoc->parse, NULL, 0, 0);
+
+ if (mdoc->meta.title == NULL) {
+ mandoc_msg(MANDOCERR_DT_NOTITLE,
+ mdoc->parse, 0, 0, "EOF");
+ mdoc->meta.title = mandoc_strdup("UNTITLED");
+ }
+
+ if (mdoc->meta.vol == NULL)
+ mdoc->meta.vol = mandoc_strdup("LOCAL");
+
+ if (mdoc->meta.os == NULL) {
+ mandoc_msg(MANDOCERR_OS_MISSING,
+ mdoc->parse, 0, 0, NULL);
+ mdoc->meta.os = mandoc_strdup("");
+ }
+
+ n = mdoc->first;
+ assert(n);
+
+ /* Check that we begin with a proper `Sh'. */
+
+ if (NULL == n->child)
+ mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse,
+ n->line, n->pos, NULL);
+ else if (MDOC_Sh != n->child->tok)
+ mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
+ n->child->line, n->child->pos,
+ mdoc_macronames[n->child->tok]);
+
+ return(1);
+}
+
+static int
+post_st(POST_ARGS)
+{
+ struct mdoc_node *n, *nch;
+ const char *p;
+
+ n = mdoc->last;
+ nch = n->child;
+
+ if (NULL == nch) {
+ mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ mdoc_node_delete(mdoc, n);
+ return(1);
+ }
+
+ assert(MDOC_TEXT == nch->type);
+
+ if (NULL == (p = mdoc_a2st(nch->string))) {
+ mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
+ nch->line, nch->pos, "St %s", nch->string);
+ mdoc_node_delete(mdoc, n);
+ } else {
+ free(nch->string);
+ nch->string = mandoc_strdup(p);
+ }
+
+ return(1);
+}
+
+static int
+post_rs(POST_ARGS)
+{
+ struct mdoc_node *nn, *next, *prev;
+ int i, j;
+
+ switch (mdoc->last->type) {
+ case MDOC_HEAD:
+ check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0);
+ return(1);
+ case MDOC_BODY:
+ if (mdoc->last->child)
+ break;
+ check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0);
+ return(1);
+ default:
+ return(1);
+ }
+
+ /*
+ * The full `Rs' block needs special handling to order the
+ * sub-elements according to `rsord'. Pick through each element
+ * and correctly order it. This is an insertion sort.
+ */
+
+ next = NULL;
+ for (nn = mdoc->last->child->next; nn; nn = next) {
+ /* Determine order of `nn'. */
+ for (i = 0; i < RSORD_MAX; i++)
+ if (rsord[i] == nn->tok)
+ break;
+
+ if (i == RSORD_MAX) {
+ mandoc_msg(MANDOCERR_RS_BAD,
+ mdoc->parse, nn->line, nn->pos,
+ mdoc_macronames[nn->tok]);
+ i = -1;
+ } else if (MDOC__J == nn->tok || MDOC__B == nn->tok)
+ mdoc->last->norm->Rs.quote_T++;
+
+ /*
+ * Remove `nn' from the chain. This somewhat
+ * repeats mdoc_node_unlink(), but since we're
+ * just re-ordering, there's no need for the
+ * full unlink process.
+ */
+
+ if (NULL != (next = nn->next))
+ next->prev = nn->prev;
+
+ if (NULL != (prev = nn->prev))
+ prev->next = nn->next;
+
+ nn->prev = nn->next = NULL;
+
+ /*
+ * Scan back until we reach a node that's
+ * ordered before `nn'.
+ */
+
+ for ( ; prev ; prev = prev->prev) {
+ /* Determine order of `prev'. */
+ for (j = 0; j < RSORD_MAX; j++)
+ if (rsord[j] == prev->tok)
+ break;
+ if (j == RSORD_MAX)
+ j = -1;
+
+ if (j <= i)
+ break;
+ }
+
+ /*
+ * Set `nn' back into its correct place in front
+ * of the `prev' node.
+ */
+
+ nn->prev = prev;
+
+ if (prev) {
+ if (prev->next)
+ prev->next->prev = nn;
+ nn->next = prev->next;
+ prev->next = nn;
+ } else {
+ mdoc->last->child->prev = nn;
+ nn->next = mdoc->last->child;
+ mdoc->last->child = nn;
+ }
+ }
+
+ return(1);
+}
+
+/*
+ * For some arguments of some macros,
+ * convert all breakable hyphens into ASCII_HYPH.
+ */
+static int
+post_hyph(POST_ARGS)
+{
+ struct mdoc_node *n, *nch;
+ char *cp;
+
+ n = mdoc->last;
+ switch (n->type) {
+ case MDOC_HEAD:
+ if (MDOC_Sh == n->tok || MDOC_Ss == n->tok)
+ break;
+ return(1);
+ case MDOC_BODY:
+ if (MDOC_D1 == n->tok || MDOC_Nd == n->tok)
+ break;
+ return(1);
+ case MDOC_ELEM:
+ break;
+ default:
+ return(1);
+ }
+
+ for (nch = n->child; nch; nch = nch->next) {
+ if (MDOC_TEXT != nch->type)
+ continue;
+ cp = nch->string;
+ if ('\0' == *cp)
+ continue;
+ while ('\0' != *(++cp))
+ if ('-' == *cp &&
+ isalpha((unsigned char)cp[-1]) &&
+ isalpha((unsigned char)cp[1]))
+ *cp = ASCII_HYPH;
+ }
+ return(1);
+}
+
+static int
+post_hyphtext(POST_ARGS)
+{
+
+ ewarn_ge1(mdoc);
+ return(post_hyph(mdoc));
+}
+
+static int
+post_ns(POST_ARGS)
+{
+
+ if (MDOC_LINE & mdoc->last->flags)
+ mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, NULL);
+ return(1);
+}
+
+static int
+post_sh(POST_ARGS)
+{
+
+ post_ignpar(mdoc);
+
+ if (MDOC_HEAD == mdoc->last->type)
+ return(post_sh_head(mdoc));
+ if (MDOC_BODY == mdoc->last->type)
+ return(post_sh_body(mdoc));
+
+ return(1);
+}
+
+static int
+post_sh_body(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ if (SEC_NAME != mdoc->lastsec)
+ return(1);
+
+ /*
+ * Warn if the NAME section doesn't contain the `Nm' and `Nd'
+ * macros (can have multiple `Nm' and one `Nd'). Note that the
+ * children of the BODY declaration can also be "text".
+ */
+
+ if (NULL == (n = mdoc->last->child)) {
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, "empty");
+ return(1);
+ }
+
+ for ( ; n && n->next; n = n->next) {
+ if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
+ continue;
+ if (MDOC_TEXT == n->type)
+ continue;
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ }
+
+ assert(n);
+ if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
+ return(1);
+
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ return(1);
+}
+
+static int
+post_sh_head(POST_ARGS)
+{
+ struct mdoc_node *n;
+ const char *goodsec;
+ char *secname;
+ enum mdoc_sec sec;
+
+ /*
+ * Process a new section. Sections are either "named" or
+ * "custom". Custom sections are user-defined, while named ones
+ * follow a conventional order and may only appear in certain
+ * manual sections.
+ */
+
+ secname = NULL;
+ sec = SEC_CUSTOM;
+ mdoc_deroff(&secname, mdoc->last);
+ sec = NULL == secname ? SEC_CUSTOM : a2sec(secname);
+
+ /* The NAME should be first. */
+
+ if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
+ mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s", secname);
+
+ /* The SYNOPSIS gets special attention in other areas. */
+
+ if (SEC_SYNOPSIS == sec) {
+ roff_setreg(mdoc->roff, "nS", 1, '=');
+ mdoc->flags |= MDOC_SYNOPSIS;
+ } else {
+ roff_setreg(mdoc->roff, "nS", 0, '=');
+ mdoc->flags &= ~MDOC_SYNOPSIS;
+ }
+
+ /* Mark our last section. */
+
+ mdoc->lastsec = sec;
+
+ /*
+ * Set the section attribute for the current HEAD, for its
+ * parent BLOCK, and for the HEAD children; the latter can
+ * only be TEXT nodes, so no recursion is needed.
+ * For other blocks and elements, including .Sh BODY, this is
+ * done when allocating the node data structures, but for .Sh
+ * BLOCK and HEAD, the section is still unknown at that time.
+ */
+
+ mdoc->last->parent->sec = sec;
+ mdoc->last->sec = sec;
+ for (n = mdoc->last->child; n; n = n->next)
+ n->sec = sec;
+
+ /* We don't care about custom sections after this. */
+
+ if (SEC_CUSTOM == sec) {
+ free(secname);
+ return(1);
+ }
+
+ /*
+ * Check whether our non-custom section is being repeated or is
+ * out of order.
+ */
+
+ if (sec == mdoc->lastnamed)
+ mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s", secname);
+
+ if (sec < mdoc->lastnamed)
+ mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s", secname);
+
+ /* Mark the last named section. */
+
+ mdoc->lastnamed = sec;
+
+ /* Check particular section/manual conventions. */
+
+ if (mdoc->meta.msec == NULL) {
+ free(secname);
+ return(1);
+ }
+
+ goodsec = NULL;
+ switch (sec) {
+ case SEC_ERRORS:
+ if (*mdoc->meta.msec == '4')
+ break;
+ goodsec = "2, 3, 4, 9";
+ /* FALLTHROUGH */
+ case SEC_RETURN_VALUES:
+ /* FALLTHROUGH */
+ case SEC_LIBRARY:
+ if (*mdoc->meta.msec == '2')
+ break;
+ if (*mdoc->meta.msec == '3')
+ break;
+ if (NULL == goodsec)
+ goodsec = "2, 3, 9";
+ /* FALLTHROUGH */
+ case SEC_CONTEXT:
+ if (*mdoc->meta.msec == '9')
+ break;
+ if (NULL == goodsec)
+ goodsec = "9";
+ mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s for %s only", secname, goodsec);
+ break;
+ default:
+ break;
+ }
+
+ free(secname);
+ return(1);
+}
+
+static int
+post_ignpar(POST_ARGS)
+{
+ struct mdoc_node *np;
+
+ hwarn_ge1(mdoc);
+ post_hyph(mdoc);
+
+ if (MDOC_BODY != mdoc->last->type)
+ return(1);
+
+ if (NULL != (np = mdoc->last->child))
+ if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
+ mandoc_vmsg(MANDOCERR_PAR_SKIP,
+ mdoc->parse, np->line, np->pos,
+ "%s after %s", mdoc_macronames[np->tok],
+ mdoc_macronames[mdoc->last->tok]);
+ mdoc_node_delete(mdoc, np);
+ }
+
+ if (NULL != (np = mdoc->last->last))
+ if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
+ mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+ np->line, np->pos, "%s at the end of %s",
+ mdoc_macronames[np->tok],
+ mdoc_macronames[mdoc->last->tok]);
+ mdoc_node_delete(mdoc, np);
+ }
+
+ return(1);
+}
+
+static int
+pre_par(PRE_ARGS)
+{
+
+ if (NULL == mdoc->last)
+ return(1);
+ if (MDOC_ELEM != n->type && MDOC_BLOCK != n->type)
+ return(1);
+
+ /*
+ * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
+ * block: `Lp', `Pp', or non-compact `Bd' or `Bl'.
+ */
+
+ if (MDOC_Pp != mdoc->last->tok &&
+ MDOC_Lp != mdoc->last->tok &&
+ MDOC_br != mdoc->last->tok)
+ return(1);
+ if (MDOC_Bl == n->tok && n->norm->Bl.comp)
+ return(1);
+ if (MDOC_Bd == n->tok && n->norm->Bd.comp)
+ return(1);
+ if (MDOC_It == n->tok && n->parent->norm->Bl.comp)
+ return(1);
+
+ mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "%s before %s", mdoc_macronames[mdoc->last->tok],
+ mdoc_macronames[n->tok]);
+ mdoc_node_delete(mdoc, mdoc->last);
+ return(1);
+}
+
+static int
+post_par(POST_ARGS)
+{
+ struct mdoc_node *np;
+
+ if (mdoc->last->tok == MDOC_sp)
+ ewarn_le1(mdoc);
+ else
+ ewarn_eq0(mdoc);
+
+ if (MDOC_ELEM != mdoc->last->type &&
+ MDOC_BLOCK != mdoc->last->type)
+ return(1);
+
+ if (NULL == (np = mdoc->last->prev)) {
+ np = mdoc->last->parent;
+ if (MDOC_Sh != np->tok && MDOC_Ss != np->tok)
+ return(1);
+ } else {
+ if (MDOC_Pp != np->tok && MDOC_Lp != np->tok &&
+ (MDOC_br != mdoc->last->tok ||
+ (MDOC_sp != np->tok && MDOC_br != np->tok)))
+ return(1);
+ }
+
+ mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos,
+ "%s after %s", mdoc_macronames[mdoc->last->tok],
+ mdoc_macronames[np->tok]);
+ mdoc_node_delete(mdoc, mdoc->last);
+ return(1);
+}
+
+static int
+pre_literal(PRE_ARGS)
+{
+
+ pre_display(mdoc, n);
+
+ if (MDOC_BODY != n->type)
+ return(1);
+
+ /*
+ * The `Dl' (note "el" not "one") and `Bd -literal' and `Bd
+ * -unfilled' macros set MDOC_LITERAL on entrance to the body.
+ */
+
+ switch (n->tok) {
+ case MDOC_Dl:
+ mdoc->flags |= MDOC_LITERAL;
+ break;
+ case MDOC_Bd:
+ if (DISP_literal == n->norm->Bd.type)
+ mdoc->flags |= MDOC_LITERAL;
+ if (DISP_unfilled == n->norm->Bd.type)
+ mdoc->flags |= MDOC_LITERAL;
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ return(1);
+}
+
+static int
+post_dd(POST_ARGS)
+{
+ struct mdoc_node *n;
+ char *datestr;
+
+ if (mdoc->meta.date)
+ free(mdoc->meta.date);
+
+ n = mdoc->last;
+ if (NULL == n->child || '\0' == n->child->string[0]) {
+ mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
+ mandoc_normdate(mdoc->parse, NULL, n->line, n->pos);
+ goto out;
+ }
+
+ datestr = NULL;
+ mdoc_deroff(&datestr, n);
+ if (mdoc->quick)
+ mdoc->meta.date = datestr;
+ else {
+ mdoc->meta.date = mandoc_normdate(mdoc->parse,
+ datestr, n->line, n->pos);
+ free(datestr);
+ }
+out:
+ mdoc_node_delete(mdoc, n);
+ return(1);
+}
+
+static int
+post_dt(POST_ARGS)
+{
+ struct mdoc_node *nn, *n;
+ const char *cp;
+ char *p;
+
+ n = mdoc->last;
+
+ free(mdoc->meta.title);
+ free(mdoc->meta.msec);
+ free(mdoc->meta.vol);
+ free(mdoc->meta.arch);
+
+ mdoc->meta.title = NULL;
+ mdoc->meta.msec = NULL;
+ mdoc->meta.vol = NULL;
+ mdoc->meta.arch = NULL;
+
+ /* First check that all characters are uppercase. */
+
+ if (NULL != (nn = n->child))
+ for (p = nn->string; *p; p++) {
+ if (toupper((unsigned char)*p) == *p)
+ continue;
+ mandoc_vmsg(MANDOCERR_TITLE_CASE,
+ mdoc->parse, nn->line,
+ nn->pos + (p - nn->string),
+ "Dt %s", nn->string);
+ break;
+ }
+
+ /* No argument: msec and arch remain NULL. */
+
+ if (NULL == (nn = n->child)) {
+ mandoc_msg(MANDOCERR_DT_NOTITLE,
+ mdoc->parse, n->line, n->pos, "Dt");
+ mdoc->meta.title = mandoc_strdup("UNTITLED");
+ mdoc->meta.vol = mandoc_strdup("LOCAL");
+ goto out;
+ }
+
+ /* One argument: msec and arch remain NULL. */
+
+ mdoc->meta.title = mandoc_strdup(
+ '\0' == nn->string[0] ? "UNTITLED" : nn->string);
+
+ if (NULL == (nn = nn->next)) {
+ mandoc_vmsg(MANDOCERR_MSEC_MISSING,
+ mdoc->parse, n->line, n->pos,
+ "Dt %s", mdoc->meta.title);
+ mdoc->meta.vol = mandoc_strdup("LOCAL");
+ goto out;
+ }
+
+ /* Handles: `.Dt TITLE SEC'
+ * title = TITLE,
+ * volume = SEC is msec ? format(msec) : SEC,
+ * msec = SEC is msec ? atoi(msec) : 0,
+ * arch = NULL
+ */
+
+ cp = mandoc_a2msec(nn->string);
+ if (cp) {
+ mdoc->meta.vol = mandoc_strdup(cp);
+ mdoc->meta.msec = mandoc_strdup(nn->string);
+ } else {
+ mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
+ nn->line, nn->pos, "Dt ... %s", nn->string);
+ mdoc->meta.vol = mandoc_strdup(nn->string);
+ mdoc->meta.msec = mandoc_strdup(nn->string);
+ }
+
+ if (NULL == (nn = nn->next))
+ goto out;
+
+ /* Handles: `.Dt TITLE SEC VOL'
+ * title = TITLE,
+ * volume = VOL is vol ? format(VOL) :
+ * VOL is arch ? format(arch) :
+ * VOL
+ */
+
+ cp = mdoc_a2vol(nn->string);
+ if (cp) {
+ free(mdoc->meta.vol);
+ mdoc->meta.vol = mandoc_strdup(cp);
+ } else {
+ cp = mdoc_a2arch(nn->string);
+ if (NULL == cp) {
+ mandoc_vmsg(MANDOCERR_ARCH_BAD, mdoc->parse,
+ nn->line, nn->pos, "Dt ... %s", nn->string);
+ free(mdoc->meta.vol);
+ mdoc->meta.vol = mandoc_strdup(nn->string);
+ } else
+ mdoc->meta.arch = mandoc_strdup(cp);
+ }
+
+ /* Ignore any subsequent parameters... */
+ /* FIXME: warn about subsequent parameters. */
+out:
+ mdoc_node_delete(mdoc, n);
+ return(1);
+}
+
+static int
+post_bx(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ /*
+ * Make `Bx's second argument always start with an uppercase
+ * letter. Groff checks if it's an "accepted" term, but we just
+ * uppercase blindly.
+ */
+
+ n = mdoc->last->child;
+ if (n && NULL != (n = n->next))
+ *n->string = (char)toupper((unsigned char)*n->string);
+
+ return(1);
+}
+
+static int
+post_os(POST_ARGS)
+{
+#ifndef OSNAME
+ struct utsname utsname;
+ static char *defbuf;
+#endif
+ struct mdoc_node *n;
+
+ n = mdoc->last;
+
+ /*
+ * Set the operating system by way of the `Os' macro.
+ * The order of precedence is:
+ * 1. the argument of the `Os' macro, unless empty
+ * 2. the -Ios=foo command line argument, if provided
+ * 3. -DOSNAME="\"foo\"", if provided during compilation
+ * 4. "sysname release" from uname(3)
+ */
+
+ free(mdoc->meta.os);
+ mdoc->meta.os = NULL;
+ mdoc_deroff(&mdoc->meta.os, n);
+ if (mdoc->meta.os)
+ goto out;
+
+ if (mdoc->defos) {
+ mdoc->meta.os = mandoc_strdup(mdoc->defos);
+ goto out;
+ }
+
+#ifdef OSNAME
+ mdoc->meta.os = mandoc_strdup(OSNAME);
+#else /*!OSNAME */
+ if (NULL == defbuf) {
+ if (-1 == uname(&utsname)) {
+ mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
+ n->line, n->pos, "Os");
+ defbuf = mandoc_strdup("UNKNOWN");
+ } else
+ mandoc_asprintf(&defbuf, "%s %s",
+ utsname.sysname, utsname.release);
+ }
+ mdoc->meta.os = mandoc_strdup(defbuf);
+#endif /*!OSNAME*/
+
+out:
+ mdoc_node_delete(mdoc, n);
+ return(1);
+}
+
+/*
+ * If no argument is provided,
+ * fill in the name of the current manual page.
+ */
+static int
+post_ex(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ n = mdoc->last;
+
+ if (n->child)
+ return(1);
+
+ if (mdoc->meta.name == NULL) {
+ mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
+ n->line, n->pos, "Ex");
+ return(1);
+ }
+
+ mdoc->next = MDOC_NEXT_CHILD;
+
+ if ( ! mdoc_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name))
+ return(0);
+
+ mdoc->last = n;
+ return(1);
+}
+
+static enum mdoc_sec
+a2sec(const char *p)
+{
+ int i;
+
+ for (i = 0; i < (int)SEC__MAX; i++)
+ if (secnames[i] && 0 == strcmp(p, secnames[i]))
+ return((enum mdoc_sec)i);
+
+ return(SEC_CUSTOM);
+}
+
+static size_t
+macro2len(enum mdoct macro)
+{
+
+ switch (macro) {
+ case MDOC_Ad:
+ return(12);
+ case MDOC_Ao:
+ return(12);
+ case MDOC_An:
+ return(12);
+ case MDOC_Aq:
+ return(12);
+ case MDOC_Ar:
+ return(12);
+ case MDOC_Bo:
+ return(12);
+ case MDOC_Bq:
+ return(12);
+ case MDOC_Cd:
+ return(12);
+ case MDOC_Cm:
+ return(10);
+ case MDOC_Do:
+ return(10);
+ case MDOC_Dq:
+ return(12);
+ case MDOC_Dv:
+ return(12);
+ case MDOC_Eo:
+ return(12);
+ case MDOC_Em:
+ return(10);
+ case MDOC_Er:
+ return(17);
+ case MDOC_Ev:
+ return(15);
+ case MDOC_Fa:
+ return(12);
+ case MDOC_Fl:
+ return(10);
+ case MDOC_Fo:
+ return(16);
+ case MDOC_Fn:
+ return(16);
+ case MDOC_Ic:
+ return(10);
+ case MDOC_Li:
+ return(16);
+ case MDOC_Ms:
+ return(6);
+ case MDOC_Nm:
+ return(10);
+ case MDOC_No:
+ return(12);
+ case MDOC_Oo:
+ return(10);
+ case MDOC_Op:
+ return(14);
+ case MDOC_Pa:
+ return(32);
+ case MDOC_Pf:
+ return(12);
+ case MDOC_Po:
+ return(12);
+ case MDOC_Pq:
+ return(12);
+ case MDOC_Ql:
+ return(16);
+ case MDOC_Qo:
+ return(12);
+ case MDOC_So:
+ return(12);
+ case MDOC_Sq:
+ return(12);
+ case MDOC_Sy:
+ return(6);
+ case MDOC_Sx:
+ return(16);
+ case MDOC_Tn:
+ return(10);
+ case MDOC_Va:
+ return(12);
+ case MDOC_Vt:
+ return(12);
+ case MDOC_Xr:
+ return(10);
+ default:
+ break;
+ };
+ return(0);
+}
diff --git a/contrib/mdocml/msec.c b/contrib/mdocml/msec.c
new file mode 100644
index 0000000..3db04d6
--- /dev/null
+++ b/contrib/mdocml/msec.c
@@ -0,0 +1,36 @@
+/* $Id: msec.c,v 1.11 2014/03/23 11:25:26 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+
+#define LINE(x, y) \
+ if (0 == strcmp(p, x)) return(y);
+
+const char *
+mandoc_a2msec(const char *p)
+{
+
+#include "msec.in"
+
+ return(NULL);
+}
diff --git a/contrib/mdocml/msec.in b/contrib/mdocml/msec.in
new file mode 100644
index 0000000..86d0dd8
--- /dev/null
+++ b/contrib/mdocml/msec.in
@@ -0,0 +1,40 @@
+/* $Id: msec.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * These are all possible manual-section macros and what they correspond
+ * to when rendered as the volume title.
+ *
+ * Be sure to escape strings.
+ */
+
+LINE("1", "FreeBSD General Commands Manual")
+LINE("2", "FreeBSD System Calls Manual")
+LINE("3", "FreeBSD Library Functions Manual")
+LINE("3p", "Perl Library Functions Manual")
+LINE("4", "FreeBSD Kernel Interfaces Manual")
+LINE("5", "FreeBSD File Formats Manual")
+LINE("6", "FreeBSD Games Manual")
+LINE("7", "FreeBSD Miscellaneous Information Manual")
+LINE("8", "FreeBSD System Manager\'s Manual")
+LINE("9", "FreeBSD Kernel Developer\'s Manual")
+LINE("X11", "X11 Developer\'s Manual")
+LINE("X11R6", "X11 Developer\'s Manual")
+LINE("unass", "Unassociated")
+LINE("local", "Local")
+LINE("draft", "Draft")
+LINE("paper", "Paper")
diff --git a/contrib/mdocml/out.c b/contrib/mdocml/out.c
new file mode 100644
index 0000000..0e5a63c
--- /dev/null
+++ b/contrib/mdocml/out.c
@@ -0,0 +1,283 @@
+/* $Id: out.c,v 1.49 2014/08/01 19:25:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "out.h"
+
+static void tblcalc_data(struct rofftbl *, struct roffcol *,
+ const struct tbl_opts *, const struct tbl_dat *);
+static void tblcalc_literal(struct rofftbl *, struct roffcol *,
+ const struct tbl_dat *);
+static void tblcalc_number(struct rofftbl *, struct roffcol *,
+ const struct tbl_opts *, const struct tbl_dat *);
+
+
+/*
+ * Convert a `scaling unit' to a consistent form, or fail. Scaling
+ * units are documented in groff.7, mdoc.7, man.7.
+ */
+int
+a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
+{
+ char buf[BUFSIZ], hasd;
+ int i;
+ enum roffscale unit;
+
+ if ('\0' == *src)
+ return(0);
+
+ i = hasd = 0;
+
+ switch (*src) {
+ case '+':
+ src++;
+ break;
+ case '-':
+ buf[i++] = *src++;
+ break;
+ default:
+ break;
+ }
+
+ if ('\0' == *src)
+ return(0);
+
+ while (i < BUFSIZ) {
+ if ( ! isdigit((unsigned char)*src)) {
+ if ('.' != *src)
+ break;
+ else if (hasd)
+ break;
+ else
+ hasd = 1;
+ }
+ buf[i++] = *src++;
+ }
+
+ if (BUFSIZ == i || (*src && *(src + 1)))
+ return(0);
+
+ buf[i] = '\0';
+
+ switch (*src) {
+ case 'c':
+ unit = SCALE_CM;
+ break;
+ case 'i':
+ unit = SCALE_IN;
+ break;
+ case 'P':
+ unit = SCALE_PC;
+ break;
+ case 'p':
+ unit = SCALE_PT;
+ break;
+ case 'f':
+ unit = SCALE_FS;
+ break;
+ case 'v':
+ unit = SCALE_VS;
+ break;
+ case 'm':
+ unit = SCALE_EM;
+ break;
+ case '\0':
+ if (SCALE_MAX == def)
+ return(0);
+ unit = SCALE_BU;
+ break;
+ case 'u':
+ unit = SCALE_BU;
+ break;
+ case 'M':
+ unit = SCALE_MM;
+ break;
+ case 'n':
+ unit = SCALE_EN;
+ break;
+ default:
+ return(0);
+ }
+
+ /* FIXME: do this in the caller. */
+ if ((dst->scale = atof(buf)) < 0.0)
+ dst->scale = 0.0;
+ dst->unit = unit;
+ return(1);
+}
+
+/*
+ * Calculate the abstract widths and decimal positions of columns in a
+ * table. This routine allocates the columns structures then runs over
+ * all rows and cells in the table. The function pointers in "tbl" are
+ * used for the actual width calculations.
+ */
+void
+tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
+{
+ const struct tbl_dat *dp;
+ struct roffcol *col;
+ int spans;
+
+ /*
+ * Allocate the master column specifiers. These will hold the
+ * widths and decimal positions for all cells in the column. It
+ * must be freed and nullified by the caller.
+ */
+
+ assert(NULL == tbl->cols);
+ tbl->cols = mandoc_calloc((size_t)sp->opts->cols,
+ sizeof(struct roffcol));
+
+ for ( ; sp; sp = sp->next) {
+ if (TBL_SPAN_DATA != sp->pos)
+ continue;
+ spans = 1;
+ /*
+ * Account for the data cells in the layout, matching it
+ * to data cells in the data section.
+ */
+ for (dp = sp->first; dp; dp = dp->next) {
+ /* Do not used spanned cells in the calculation. */
+ if (0 < --spans)
+ continue;
+ spans = dp->spans;
+ if (1 < spans)
+ continue;
+ assert(dp->layout);
+ col = &tbl->cols[dp->layout->head->ident];
+ tblcalc_data(tbl, col, sp->opts, dp);
+ }
+ }
+}
+
+static void
+tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
+ const struct tbl_opts *opts, const struct tbl_dat *dp)
+{
+ size_t sz;
+
+ /* Branch down into data sub-types. */
+
+ switch (dp->layout->pos) {
+ case TBL_CELL_HORIZ:
+ /* FALLTHROUGH */
+ case TBL_CELL_DHORIZ:
+ sz = (*tbl->len)(1, tbl->arg);
+ if (col->width < sz)
+ col->width = sz;
+ break;
+ case TBL_CELL_LONG:
+ /* FALLTHROUGH */
+ case TBL_CELL_CENTRE:
+ /* FALLTHROUGH */
+ case TBL_CELL_LEFT:
+ /* FALLTHROUGH */
+ case TBL_CELL_RIGHT:
+ tblcalc_literal(tbl, col, dp);
+ break;
+ case TBL_CELL_NUMBER:
+ tblcalc_number(tbl, col, opts, dp);
+ break;
+ case TBL_CELL_DOWN:
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+}
+
+static void
+tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
+ const struct tbl_dat *dp)
+{
+ size_t sz;
+ const char *str;
+
+ str = dp->string ? dp->string : "";
+ sz = (*tbl->slen)(str, tbl->arg);
+
+ if (col->width < sz)
+ col->width = sz;
+}
+
+static void
+tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
+ const struct tbl_opts *opts, const struct tbl_dat *dp)
+{
+ int i;
+ size_t sz, psz, ssz, d;
+ const char *str;
+ char *cp;
+ char buf[2];
+
+ /*
+ * First calculate number width and decimal place (last + 1 for
+ * non-decimal numbers). If the stored decimal is subsequent to
+ * ours, make our size longer by that difference
+ * (right-"shifting"); similarly, if ours is subsequent the
+ * stored, then extend the stored size by the difference.
+ * Finally, re-assign the stored values.
+ */
+
+ str = dp->string ? dp->string : "";
+ sz = (*tbl->slen)(str, tbl->arg);
+
+ /* FIXME: TBL_DATA_HORIZ et al.? */
+
+ buf[0] = opts->decimal;
+ buf[1] = '\0';
+
+ psz = (*tbl->slen)(buf, tbl->arg);
+
+ if (NULL != (cp = strrchr(str, opts->decimal))) {
+ buf[1] = '\0';
+ for (ssz = 0, i = 0; cp != &str[i]; i++) {
+ buf[0] = str[i];
+ ssz += (*tbl->slen)(buf, tbl->arg);
+ }
+ d = ssz + psz;
+ } else
+ d = sz + psz;
+
+ /* Adjust the settings for this column. */
+
+ if (col->decimal > d) {
+ sz += col->decimal - d;
+ d = col->decimal;
+ } else
+ col->width += d - col->decimal;
+
+ if (sz > col->width)
+ col->width = sz;
+ if (d > col->decimal)
+ col->decimal = d;
+}
diff --git a/contrib/mdocml/out.h b/contrib/mdocml/out.h
new file mode 100644
index 0000000..514db3f
--- /dev/null
+++ b/contrib/mdocml/out.h
@@ -0,0 +1,71 @@
+/* $Id: out.h,v 1.22 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef OUT_H
+#define OUT_H
+
+enum roffscale {
+ SCALE_CM, /* centimeters (c) */
+ SCALE_IN, /* inches (i) */
+ SCALE_PC, /* pica (P) */
+ SCALE_PT, /* points (p) */
+ SCALE_EM, /* ems (m) */
+ SCALE_MM, /* mini-ems (M) */
+ SCALE_EN, /* ens (n) */
+ SCALE_BU, /* default horizontal (u) */
+ SCALE_VS, /* default vertical (v) */
+ SCALE_FS, /* syn. for u (f) */
+ SCALE_MAX
+};
+
+struct roffcol {
+ size_t width; /* width of cell */
+ size_t decimal; /* decimal position in cell */
+};
+
+struct roffsu {
+ enum roffscale unit;
+ double scale;
+};
+
+typedef size_t (*tbl_strlen)(const char *, void *);
+typedef size_t (*tbl_len)(size_t, void *);
+
+struct rofftbl {
+ tbl_strlen slen; /* calculate string length */
+ tbl_len len; /* produce width of empty space */
+ struct roffcol *cols; /* master column specifiers */
+ void *arg; /* passed to slen and len */
+};
+
+__BEGIN_DECLS
+
+#define SCALE_VS_INIT(p, v) \
+ do { (p)->unit = SCALE_VS; \
+ (p)->scale = (v); } \
+ while (/* CONSTCOND */ 0)
+
+#define SCALE_HS_INIT(p, v) \
+ do { (p)->unit = SCALE_BU; \
+ (p)->scale = (v); } \
+ while (/* CONSTCOND */ 0)
+
+int a2roffsu(const char *, struct roffsu *, enum roffscale);
+void tblcalc(struct rofftbl *tbl, const struct tbl_span *);
+
+__END_DECLS
+
+#endif /*!OUT_H*/
diff --git a/contrib/mdocml/predefs.in b/contrib/mdocml/predefs.in
new file mode 100644
index 0000000..d1690e3
--- /dev/null
+++ b/contrib/mdocml/predefs.in
@@ -0,0 +1,65 @@
+/* $Id: predefs.in,v 1.4 2012/07/18 10:39:19 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 predefined-string translation tables. Each corresponds to a
+ * predefined strings from (e.g.) tmac/mdoc/doc-nroff. The left-hand
+ * side corresponds to the input sequence (\*x, \*(xx and so on). The
+ * right-hand side is what's produced by libroff.
+ *
+ * XXX - C-escape strings!
+ * XXX - update PREDEF_MAX in roff.c if adding more!
+ */
+
+PREDEF("Am", "&")
+PREDEF("Ba", "\\fR|\\fP")
+PREDEF("Ge", "\\(>=")
+PREDEF("Gt", ">")
+PREDEF("If", "infinity")
+PREDEF("Le", "\\(<=")
+PREDEF("Lq", "\\(lq")
+PREDEF("Lt", "<")
+PREDEF("Na", "NaN")
+PREDEF("Ne", "\\(!=")
+PREDEF("Pi", "pi")
+PREDEF("Pm", "\\(+-")
+PREDEF("Rq", "\\(rq")
+PREDEF("left-bracket", "[")
+PREDEF("left-parenthesis", "(")
+PREDEF("lp", "(")
+PREDEF("left-singlequote", "\\(oq")
+PREDEF("q", "\\(dq")
+PREDEF("quote-left", "\\(oq")
+PREDEF("quote-right", "\\(cq")
+PREDEF("R", "\\(rg")
+PREDEF("right-bracket", "]")
+PREDEF("right-parenthesis", ")")
+PREDEF("rp", ")")
+PREDEF("right-singlequote", "\\(cq")
+PREDEF("Tm", "(Tm)")
+PREDEF("Px", "POSIX")
+PREDEF("Ai", "ANSI")
+PREDEF("\'", "\\\'")
+PREDEF("aa", "\\(aa")
+PREDEF("ga", "\\(ga")
+PREDEF("`", "\\`")
+PREDEF("lq", "\\(lq")
+PREDEF("rq", "\\(rq")
+PREDEF("ua", "\\(ua")
+PREDEF("va", "\\(va")
+PREDEF("<=", "\\(<=")
+PREDEF(">=", "\\(>=")
diff --git a/contrib/mdocml/read.c b/contrib/mdocml/read.c
new file mode 100644
index 0000000..9466838
--- /dev/null
+++ b/contrib/mdocml/read.c
@@ -0,0 +1,923 @@
+/* $Id: read.c,v 1.79 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010, 2012 Joerg Sonnenberger <joerg@netbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_MMAP
+# include <sys/stat.h>
+# include <sys/mman.h>
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+#include "mdoc.h"
+#include "man.h"
+#include "main.h"
+
+#define REPARSE_LIMIT 1000
+
+struct buf {
+ char *buf; /* binary input buffer */
+ size_t sz; /* size of binary buffer */
+};
+
+struct mparse {
+ enum mandoclevel file_status; /* status of current parse */
+ enum mandoclevel wlevel; /* ignore messages below this */
+ int line; /* line number in the file */
+ int options; /* parser options */
+ struct man *pman; /* persistent man parser */
+ struct mdoc *pmdoc; /* persistent mdoc parser */
+ struct man *man; /* man parser */
+ struct mdoc *mdoc; /* mdoc parser */
+ struct roff *roff; /* roff parser (!NULL) */
+ char *sodest; /* filename pointed to by .so */
+ int reparse_count; /* finite interp. stack */
+ mandocmsg mmsg; /* warning/error message handler */
+ const char *file;
+ struct buf *secondary;
+ const char *defos; /* default operating system */
+};
+
+static void resize_buf(struct buf *, size_t);
+static void mparse_buf_r(struct mparse *, struct buf, int);
+static void pset(const char *, int, struct mparse *);
+static int read_whole_file(struct mparse *, const char *, int,
+ struct buf *, int *);
+static void mparse_end(struct mparse *);
+static void mparse_parse_buffer(struct mparse *, struct buf,
+ const char *);
+
+static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = {
+ MANDOCERR_OK,
+ MANDOCERR_WARNING,
+ MANDOCERR_WARNING,
+ MANDOCERR_ERROR,
+ MANDOCERR_FATAL,
+ MANDOCERR_MAX,
+ MANDOCERR_MAX
+};
+
+static const char * const mandocerrs[MANDOCERR_MAX] = {
+ "ok",
+
+ "generic warning",
+
+ /* related to the prologue */
+ "missing manual title, using UNTITLED",
+ "missing manual title, using \"\"",
+ "lower case character in document title",
+ "missing manual section, using \"\"",
+ "unknown manual section",
+ "unknown manual volume or arch",
+ "missing date, using today's date",
+ "cannot parse date, using it verbatim",
+ "missing Os macro, using \"\"",
+ "duplicate prologue macro",
+ "late prologue macro",
+ "skipping late title macro",
+ "prologue macros out of order",
+
+ /* related to document structure */
+ ".so is fragile, better use ln(1)",
+ "no document body",
+ "content before first section header",
+ "first section is not \"NAME\"",
+ "bad NAME section contents",
+ "sections out of conventional order",
+ "duplicate section title",
+ "unexpected section",
+
+ /* related to macros and nesting */
+ "obsolete macro",
+ "skipping paragraph macro",
+ "moving paragraph macro out of list",
+ "skipping no-space macro",
+ "blocks badly nested",
+ "nested displays are not portable",
+ "moving content out of list",
+ ".Vt block has child macro",
+ "fill mode already enabled, skipping",
+ "fill mode already disabled, skipping",
+ "line scope broken",
+
+ /* related to missing macro arguments */
+ "skipping empty request",
+ "conditional request controls empty scope",
+ "skipping empty macro",
+ "empty argument, using 0n",
+ "argument count wrong",
+ "missing display type, using -ragged",
+ "list type is not the first argument",
+ "missing -width in -tag list, using 8n",
+ "missing utility name, using \"\"",
+ "empty head in list item",
+ "empty list item",
+ "missing font type, using \\fR",
+ "unknown font type, using \\fR",
+ "missing -std argument, adding it",
+
+ /* related to bad macro arguments */
+ "unterminated quoted argument",
+ "duplicate argument",
+ "skipping duplicate argument",
+ "skipping duplicate display type",
+ "skipping duplicate list type",
+ "skipping -width argument",
+ "unknown AT&T UNIX version",
+ "invalid content in Rs block",
+ "invalid Boolean argument",
+ "unknown font, skipping request",
+
+ /* related to plain text */
+ "blank line in fill mode, using .sp",
+ "tab in filled text",
+ "whitespace at end of input line",
+ "bad comment style",
+ "invalid escape sequence",
+ "undefined string, using \"\"",
+
+ "generic error",
+
+ /* related to equations */
+ "unexpected equation scope closure",
+ "equation scope open on exit",
+ "overlapping equation scopes",
+ "unexpected end of equation",
+ "equation syntax error",
+
+ /* related to tables */
+ "bad table syntax",
+ "bad table option",
+ "bad table layout",
+ "no table layout cells specified",
+ "no table data cells specified",
+ "ignore data in cell",
+ "data block still open",
+ "ignoring extra data cells",
+
+ /* related to document structure and macros */
+ "input stack limit exceeded, infinite loop?",
+ "skipping bad character",
+ "skipping unknown macro",
+ "skipping item outside list",
+ "skipping column outside column list",
+ "skipping end of block that is not open",
+ "inserting missing end of block",
+ "appending missing end of block",
+
+ /* related to request and macro arguments */
+ "escaped character not allowed in a name",
+ "argument count wrong",
+ "missing list type, using -item",
+ "missing manual name, using \"\"",
+ "uname(3) system call failed, using UNKNOWN",
+ "unknown standard specifier",
+ "skipping request without numeric argument",
+ "skipping all arguments",
+ "skipping excess arguments",
+
+ "generic fatal error",
+
+ "input too large",
+ "NOT IMPLEMENTED: Bd -file",
+ "NOT IMPLEMENTED: .so with absolute path or \"..\"",
+ ".so request failed",
+
+ /* system errors */
+ NULL,
+ "cannot stat file",
+ "cannot read file",
+};
+
+static const char * const mandoclevels[MANDOCLEVEL_MAX] = {
+ "SUCCESS",
+ "RESERVED",
+ "WARNING",
+ "ERROR",
+ "FATAL",
+ "BADARG",
+ "SYSERR"
+};
+
+
+static void
+resize_buf(struct buf *buf, size_t initial)
+{
+
+ buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial;
+ buf->buf = mandoc_realloc(buf->buf, buf->sz);
+}
+
+static void
+pset(const char *buf, int pos, struct mparse *curp)
+{
+ 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.
+ *
+ * Separate out pmdoc/pman from mdoc/man: the first persists
+ * through all parsers, while the latter is used per-parse.
+ */
+
+ if ('.' == buf[0] || '\'' == buf[0]) {
+ for (i = 1; buf[i]; i++)
+ if (' ' != buf[i] && '\t' != buf[i])
+ break;
+ if ('\0' == buf[i])
+ return;
+ }
+
+ if (MPARSE_MDOC & curp->options) {
+ curp->mdoc = curp->pmdoc;
+ return;
+ } else if (MPARSE_MAN & curp->options) {
+ curp->man = curp->pman;
+ return;
+ }
+
+ if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) {
+ if (NULL == curp->pmdoc)
+ curp->pmdoc = mdoc_alloc(
+ curp->roff, curp, curp->defos,
+ MPARSE_QUICK & curp->options ? 1 : 0);
+ assert(curp->pmdoc);
+ curp->mdoc = curp->pmdoc;
+ return;
+ }
+
+ if (NULL == curp->pman)
+ curp->pman = man_alloc(curp->roff, curp,
+ MPARSE_QUICK & curp->options ? 1 : 0);
+ assert(curp->pman);
+ curp->man = curp->pman;
+}
+
+/*
+ * Main parse routine for an opened file. This is called for each
+ * opened file and simply loops around the full input file, possibly
+ * nesting (i.e., with `so').
+ */
+static void
+mparse_buf_r(struct mparse *curp, struct buf blk, int start)
+{
+ const struct tbl_span *span;
+ struct buf ln;
+ enum rofferr rr;
+ int i, of, rc;
+ int pos; /* byte number in the ln buffer */
+ int lnn; /* line number in the real file */
+ unsigned char c;
+
+ memset(&ln, 0, sizeof(struct buf));
+
+ lnn = curp->line;
+ pos = 0;
+
+ for (i = 0; i < (int)blk.sz; ) {
+ if (0 == pos && '\0' == blk.buf[i])
+ break;
+
+ if (start) {
+ curp->line = lnn;
+ curp->reparse_count = 0;
+ }
+
+ while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) {
+
+ /*
+ * When finding an unescaped newline character,
+ * leave the character loop to process the line.
+ * Skip a preceding carriage return, if any.
+ */
+
+ if ('\r' == blk.buf[i] && i + 1 < (int)blk.sz &&
+ '\n' == blk.buf[i + 1])
+ ++i;
+ if ('\n' == blk.buf[i]) {
+ ++i;
+ ++lnn;
+ break;
+ }
+
+ /*
+ * Make sure we have space for at least
+ * one backslash and one other character
+ * and the trailing NUL byte.
+ */
+
+ if (pos + 2 >= (int)ln.sz)
+ resize_buf(&ln, 256);
+
+ /*
+ * Warn about bogus characters. If you're using
+ * non-ASCII encoding, you're screwing your
+ * readers. Since I'd rather this not happen,
+ * I'll be helpful and replace these characters
+ * with "?", so we don't display gibberish.
+ * Note to manual writers: use special characters.
+ */
+
+ c = (unsigned char) blk.buf[i];
+
+ if ( ! (isascii(c) &&
+ (isgraph(c) || isblank(c)))) {
+ mandoc_vmsg(MANDOCERR_BADCHAR, curp,
+ curp->line, pos, "0x%x", c);
+ i++;
+ ln.buf[pos++] = '?';
+ continue;
+ }
+
+ /* Trailing backslash = a plain char. */
+
+ if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) {
+ ln.buf[pos++] = blk.buf[i++];
+ continue;
+ }
+
+ /*
+ * Found escape and at least one other character.
+ * When it's a newline character, skip it.
+ * When there is a carriage return in between,
+ * skip that one as well.
+ */
+
+ if ('\r' == blk.buf[i + 1] && i + 2 < (int)blk.sz &&
+ '\n' == blk.buf[i + 2])
+ ++i;
+ if ('\n' == blk.buf[i + 1]) {
+ i += 2;
+ ++lnn;
+ continue;
+ }
+
+ if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) {
+ i += 2;
+ /* Comment, skip to end of line */
+ for (; i < (int)blk.sz; ++i) {
+ if ('\n' == blk.buf[i]) {
+ ++i;
+ ++lnn;
+ break;
+ }
+ }
+
+ /* Backout trailing whitespaces */
+ for (; pos > 0; --pos) {
+ if (ln.buf[pos - 1] != ' ')
+ break;
+ if (pos > 2 && ln.buf[pos - 2] == '\\')
+ break;
+ }
+ break;
+ }
+
+ /* Catch escaped bogus characters. */
+
+ c = (unsigned char) blk.buf[i+1];
+
+ if ( ! (isascii(c) &&
+ (isgraph(c) || isblank(c)))) {
+ mandoc_vmsg(MANDOCERR_BADCHAR, curp,
+ curp->line, pos, "0x%x", c);
+ i += 2;
+ ln.buf[pos++] = '?';
+ continue;
+ }
+
+ /* Some other escape sequence, copy & cont. */
+
+ ln.buf[pos++] = blk.buf[i++];
+ ln.buf[pos++] = blk.buf[i++];
+ }
+
+ if (pos >= (int)ln.sz)
+ resize_buf(&ln, 256);
+
+ ln.buf[pos] = '\0';
+
+ /*
+ * A significant amount of complexity is contained by
+ * the roff preprocessor. It's line-oriented but can be
+ * expressed on one line, so we need at times to
+ * readjust our starting point and re-run it. The roff
+ * preprocessor can also readjust the buffers with new
+ * data, so we pass them in wholesale.
+ */
+
+ of = 0;
+
+ /*
+ * Maintain a lookaside buffer of all parsed lines. We
+ * only do this if mparse_keep() has been invoked (the
+ * buffer may be accessed with mparse_getkeep()).
+ */
+
+ if (curp->secondary) {
+ curp->secondary->buf = mandoc_realloc(
+ curp->secondary->buf,
+ curp->secondary->sz + pos + 2);
+ memcpy(curp->secondary->buf +
+ curp->secondary->sz,
+ ln.buf, pos);
+ curp->secondary->sz += pos;
+ curp->secondary->buf
+ [curp->secondary->sz] = '\n';
+ curp->secondary->sz++;
+ curp->secondary->buf
+ [curp->secondary->sz] = '\0';
+ }
+rerun:
+ rr = roff_parseln(curp->roff, curp->line,
+ &ln.buf, &ln.sz, of, &of);
+
+ switch (rr) {
+ case ROFF_REPARSE:
+ if (REPARSE_LIMIT >= ++curp->reparse_count)
+ mparse_buf_r(curp, ln, 0);
+ else
+ mandoc_msg(MANDOCERR_ROFFLOOP, curp,
+ curp->line, pos, NULL);
+ pos = 0;
+ continue;
+ case ROFF_APPEND:
+ pos = (int)strlen(ln.buf);
+ continue;
+ case ROFF_RERUN:
+ goto rerun;
+ case ROFF_IGN:
+ pos = 0;
+ continue;
+ case ROFF_ERR:
+ assert(MANDOCLEVEL_FATAL <= curp->file_status);
+ break;
+ case ROFF_SO:
+ if (0 == (MPARSE_SO & curp->options) &&
+ (i >= (int)blk.sz || '\0' == blk.buf[i])) {
+ curp->sodest = mandoc_strdup(ln.buf + of);
+ free(ln.buf);
+ return;
+ }
+ /*
+ * We remove `so' clauses from our lookaside
+ * buffer because we're going to descend into
+ * the file recursively.
+ */
+ if (curp->secondary)
+ curp->secondary->sz -= pos + 1;
+ mparse_readfd(curp, -1, ln.buf + of);
+ if (MANDOCLEVEL_FATAL <= curp->file_status) {
+ mandoc_vmsg(MANDOCERR_SO_FAIL,
+ curp, curp->line, pos,
+ ".so %s", ln.buf + of);
+ break;
+ }
+ pos = 0;
+ continue;
+ default:
+ break;
+ }
+
+ /*
+ * If we encounter errors in the recursive parse, make
+ * sure we don't continue parsing.
+ */
+
+ if (MANDOCLEVEL_FATAL <= curp->file_status)
+ break;
+
+ /*
+ * If input parsers have not been allocated, do so now.
+ * We keep these instanced between parsers, but set them
+ * locally per parse routine since we can use different
+ * parsers with each one.
+ */
+
+ if ( ! (curp->man || curp->mdoc))
+ pset(ln.buf + of, pos - of, curp);
+
+ /*
+ * Lastly, push down into the parsers themselves. One
+ * of these will have already been set in the pset()
+ * routine.
+ * If libroff returns ROFF_TBL, then add it to the
+ * currently open parse. Since we only get here if
+ * there does exist data (see tbl_data.c), we're
+ * guaranteed that something's been allocated.
+ * Do the same for ROFF_EQN.
+ */
+
+ rc = -1;
+
+ if (ROFF_TBL == rr)
+ while (NULL != (span = roff_span(curp->roff))) {
+ rc = curp->man ?
+ man_addspan(curp->man, span) :
+ mdoc_addspan(curp->mdoc, span);
+ if (0 == rc)
+ break;
+ }
+ else if (ROFF_EQN == rr)
+ rc = curp->mdoc ?
+ mdoc_addeqn(curp->mdoc,
+ roff_eqn(curp->roff)) :
+ man_addeqn(curp->man,
+ roff_eqn(curp->roff));
+ else if (curp->man || curp->mdoc)
+ rc = curp->man ?
+ man_parseln(curp->man,
+ curp->line, ln.buf, of) :
+ mdoc_parseln(curp->mdoc,
+ curp->line, ln.buf, of);
+
+ if (0 == rc) {
+ assert(MANDOCLEVEL_FATAL <= curp->file_status);
+ break;
+ } else if (2 == rc)
+ break;
+
+ /* Temporary buffers typically are not full. */
+
+ if (0 == start && '\0' == blk.buf[i])
+ break;
+
+ /* Start the next input line. */
+
+ pos = 0;
+ }
+
+ free(ln.buf);
+}
+
+static int
+read_whole_file(struct mparse *curp, const char *file, int fd,
+ struct buf *fb, int *with_mmap)
+{
+ size_t off;
+ ssize_t ssz;
+
+#ifdef HAVE_MMAP
+ struct stat st;
+ if (-1 == fstat(fd, &st)) {
+ curp->file_status = MANDOCLEVEL_SYSERR;
+ if (curp->mmsg)
+ (*curp->mmsg)(MANDOCERR_SYSSTAT, curp->file_status,
+ file, 0, 0, strerror(errno));
+ return(0);
+ }
+
+ /*
+ * If we're a regular file, try just reading in the whole entry
+ * via mmap(). This is faster than reading it into blocks, and
+ * since each file is only a few bytes to begin with, I'm not
+ * concerned that this is going to tank any machines.
+ */
+
+ if (S_ISREG(st.st_mode)) {
+ if (st.st_size >= (1U << 31)) {
+ curp->file_status = MANDOCLEVEL_FATAL;
+ if (curp->mmsg)
+ (*curp->mmsg)(MANDOCERR_TOOLARGE,
+ curp->file_status, file, 0, 0, NULL);
+ return(0);
+ }
+ *with_mmap = 1;
+ fb->sz = (size_t)st.st_size;
+ fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0);
+ if (fb->buf != MAP_FAILED)
+ return(1);
+ }
+#endif
+
+ /*
+ * If this isn't a regular file (like, say, stdin), then we must
+ * go the old way and just read things in bit by bit.
+ */
+
+ *with_mmap = 0;
+ off = 0;
+ fb->sz = 0;
+ fb->buf = NULL;
+ for (;;) {
+ if (off == fb->sz) {
+ if (fb->sz == (1U << 31)) {
+ curp->file_status = MANDOCLEVEL_FATAL;
+ if (curp->mmsg)
+ (*curp->mmsg)(MANDOCERR_TOOLARGE,
+ curp->file_status,
+ file, 0, 0, NULL);
+ break;
+ }
+ resize_buf(fb, 65536);
+ }
+ ssz = read(fd, fb->buf + (int)off, fb->sz - off);
+ if (ssz == 0) {
+ fb->sz = off;
+ return(1);
+ }
+ if (ssz == -1) {
+ curp->file_status = MANDOCLEVEL_SYSERR;
+ if (curp->mmsg)
+ (*curp->mmsg)(MANDOCERR_SYSREAD,
+ curp->file_status, file, 0, 0,
+ strerror(errno));
+ break;
+ }
+ off += (size_t)ssz;
+ }
+
+ free(fb->buf);
+ fb->buf = NULL;
+ return(0);
+}
+
+static void
+mparse_end(struct mparse *curp)
+{
+
+ if (MANDOCLEVEL_FATAL <= curp->file_status)
+ return;
+
+ if (curp->mdoc == NULL &&
+ curp->man == NULL &&
+ curp->sodest == NULL) {
+ if (curp->options & MPARSE_MDOC)
+ curp->mdoc = curp->pmdoc;
+ else {
+ if (curp->pman == NULL)
+ curp->pman = man_alloc(curp->roff, curp,
+ curp->options & MPARSE_QUICK ? 1 : 0);
+ curp->man = curp->pman;
+ }
+ }
+
+ if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
+ assert(MANDOCLEVEL_FATAL <= curp->file_status);
+ return;
+ }
+
+ if (curp->man && ! man_endparse(curp->man)) {
+ assert(MANDOCLEVEL_FATAL <= curp->file_status);
+ return;
+ }
+
+ roff_endparse(curp->roff);
+}
+
+static void
+mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file)
+{
+ const char *svfile;
+ static int recursion_depth;
+
+ if (64 < recursion_depth) {
+ mandoc_msg(MANDOCERR_ROFFLOOP, curp, curp->line, 0, NULL);
+ return;
+ }
+
+ /* Line number is per-file. */
+ svfile = curp->file;
+ curp->file = file;
+ curp->line = 1;
+ recursion_depth++;
+
+ mparse_buf_r(curp, blk, 1);
+
+ if (0 == --recursion_depth && MANDOCLEVEL_FATAL > curp->file_status)
+ mparse_end(curp);
+
+ curp->file = svfile;
+}
+
+enum mandoclevel
+mparse_readmem(struct mparse *curp, const void *buf, size_t len,
+ const char *file)
+{
+ struct buf blk;
+
+ blk.buf = UNCONST(buf);
+ blk.sz = len;
+
+ mparse_parse_buffer(curp, blk, file);
+ return(curp->file_status);
+}
+
+enum mandoclevel
+mparse_readfd(struct mparse *curp, int fd, const char *file)
+{
+ struct buf blk;
+ int with_mmap;
+
+ if (-1 == fd && -1 == (fd = open(file, O_RDONLY, 0))) {
+ curp->file_status = MANDOCLEVEL_SYSERR;
+ if (curp->mmsg)
+ (*curp->mmsg)(MANDOCERR_SYSOPEN,
+ curp->file_status,
+ file, 0, 0, strerror(errno));
+ goto out;
+ }
+
+ /*
+ * Run for each opened file; may be called more than once for
+ * each full parse sequence if the opened file is nested (i.e.,
+ * from `so'). Simply sucks in the whole file and moves into
+ * the parse phase for the file.
+ */
+
+ if ( ! read_whole_file(curp, file, fd, &blk, &with_mmap))
+ goto out;
+
+ mparse_parse_buffer(curp, blk, file);
+
+#ifdef HAVE_MMAP
+ if (with_mmap)
+ munmap(blk.buf, blk.sz);
+ else
+#endif
+ free(blk.buf);
+
+ if (STDIN_FILENO != fd && -1 == close(fd))
+ perror(file);
+out:
+ return(curp->file_status);
+}
+
+struct mparse *
+mparse_alloc(int options, enum mandoclevel wlevel,
+ mandocmsg mmsg, const char *defos)
+{
+ struct mparse *curp;
+
+ assert(wlevel <= MANDOCLEVEL_FATAL);
+
+ curp = mandoc_calloc(1, sizeof(struct mparse));
+
+ curp->options = options;
+ curp->wlevel = wlevel;
+ curp->mmsg = mmsg;
+ curp->defos = defos;
+
+ curp->roff = roff_alloc(curp, options);
+ if (curp->options & MPARSE_MDOC)
+ curp->pmdoc = mdoc_alloc(
+ curp->roff, curp, curp->defos,
+ curp->options & MPARSE_QUICK ? 1 : 0);
+ if (curp->options & MPARSE_MAN)
+ curp->pman = man_alloc(curp->roff, curp,
+ curp->options & MPARSE_QUICK ? 1 : 0);
+
+ return(curp);
+}
+
+void
+mparse_reset(struct mparse *curp)
+{
+
+ roff_reset(curp->roff);
+
+ if (curp->mdoc)
+ mdoc_reset(curp->mdoc);
+ if (curp->man)
+ man_reset(curp->man);
+ if (curp->secondary)
+ curp->secondary->sz = 0;
+
+ curp->file_status = MANDOCLEVEL_OK;
+ curp->mdoc = NULL;
+ curp->man = NULL;
+
+ free(curp->sodest);
+ curp->sodest = NULL;
+}
+
+void
+mparse_free(struct mparse *curp)
+{
+
+ if (curp->pmdoc)
+ mdoc_free(curp->pmdoc);
+ if (curp->pman)
+ man_free(curp->pman);
+ if (curp->roff)
+ roff_free(curp->roff);
+ if (curp->secondary)
+ free(curp->secondary->buf);
+
+ free(curp->secondary);
+ free(curp->sodest);
+ free(curp);
+}
+
+void
+mparse_result(struct mparse *curp,
+ struct mdoc **mdoc, struct man **man, char **sodest)
+{
+
+ if (sodest && NULL != (*sodest = curp->sodest)) {
+ *mdoc = NULL;
+ *man = NULL;
+ return;
+ }
+ if (mdoc)
+ *mdoc = curp->mdoc;
+ if (man)
+ *man = curp->man;
+}
+
+void
+mandoc_vmsg(enum mandocerr t, struct mparse *m,
+ int ln, int pos, const char *fmt, ...)
+{
+ char buf[256];
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ mandoc_msg(t, m, ln, pos, buf);
+}
+
+void
+mandoc_msg(enum mandocerr er, struct mparse *m,
+ int ln, int col, const char *msg)
+{
+ enum mandoclevel level;
+
+ level = MANDOCLEVEL_FATAL;
+ while (er < mandoclimits[level])
+ level--;
+
+ if (level < m->wlevel)
+ return;
+
+ if (m->mmsg)
+ (*m->mmsg)(er, level, m->file, ln, col, msg);
+
+ if (m->file_status < level)
+ m->file_status = level;
+}
+
+const char *
+mparse_strerror(enum mandocerr er)
+{
+
+ return(mandocerrs[er]);
+}
+
+const char *
+mparse_strlevel(enum mandoclevel lvl)
+{
+ return(mandoclevels[lvl]);
+}
+
+void
+mparse_keep(struct mparse *p)
+{
+
+ assert(NULL == p->secondary);
+ p->secondary = mandoc_calloc(1, sizeof(struct buf));
+}
+
+const char *
+mparse_getkeep(const struct mparse *p)
+{
+
+ assert(p->secondary);
+ return(p->secondary->sz ? p->secondary->buf : NULL);
+}
diff --git a/contrib/mdocml/roff.7 b/contrib/mdocml/roff.7
new file mode 100644
index 0000000..bc2f24e
--- /dev/null
+++ b/contrib/mdocml/roff.7
@@ -0,0 +1,1429 @@
+.\" $Id: roff.7,v 1.55 2014/07/07 11:35:06 schwarze Exp $
+.\"
+.\" Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2010, 2011, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 7 2014 $
+.Dt ROFF 7
+.Os
+.Sh NAME
+.Nm roff
+.Nd roff language reference for mandoc
+.Sh DESCRIPTION
+The
+.Nm roff
+language is a general purpose text formatting language.
+Since traditional implementations of the
+.Xr mdoc 7
+and
+.Xr man 7
+manual formatting languages are based on it,
+many real-world manuals use small numbers of
+.Nm
+requests and escape sequences intermixed with their
+.Xr mdoc 7
+or
+.Xr man 7
+code.
+To properly format such manuals, the
+.Xr mandoc 1
+utility supports a tiny subset of
+.Nm
+requests and escapes.
+Only these requests and escapes supported by
+.Xr mandoc 1
+are documented in the present manual,
+together with the basic language syntax shared by
+.Nm ,
+.Xr mdoc 7 ,
+and
+.Xr man 7 .
+For complete
+.Nm
+manuals, consult the
+.Sx SEE ALSO
+section.
+.Pp
+Input lines beginning with the control character
+.Sq \&.
+are parsed for requests and macros.
+Such lines are called
+.Dq request lines
+or
+.Dq macro lines ,
+respectively.
+Requests change the processing state and manipulate the formatting;
+some macros also define the document structure and produce formatted
+output.
+The single quote
+.Pq Qq \(aq
+is accepted as an alternative control character,
+treated by
+.Xr mandoc 1
+just like
+.Ql \&.
+.Pp
+Lines not beginning with control characters are called
+.Dq text lines .
+They provide free-form text to be printed; the formatting of the text
+depends on the respective processing context.
+.Sh LANGUAGE SYNTAX
+.Nm
+documents may contain only graphable 7-bit ASCII characters, the space
+character, and, in certain circumstances, the tab character.
+The backslash character
+.Sq \e
+indicates the start of an escape sequence, used for example for
+.Sx Comments ,
+.Sx Special Characters ,
+.Sx Predefined Strings ,
+and
+user-defined strings defined using the
+.Sx ds
+request.
+For a listing of escape sequences, consult the
+.Sx ESCAPE SEQUENCE REFERENCE
+below.
+.Ss Comments
+Text following an escaped double-quote
+.Sq \e\(dq ,
+whether in a request, macro, or text line, is ignored to the end of the line.
+A request line beginning with a control character and comment escape
+.Sq \&.\e\(dq
+is also ignored.
+Furthermore, request lines with only a control character and optional
+trailing whitespace are stripped from input.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.\e\(dq This is a comment line.
+\&.\e\(dq The next line is ignored:
+\&.
+\&.Sh EXAMPLES \e\(dq This is a comment, too.
+\&example text \e\(dq And so is this.
+.Ed
+.Ss Special Characters
+Special characters are used to encode special glyphs and are rendered
+differently across output media.
+They may occur in request, macro, and text 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.
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li \e(em
+Two-letter em dash escape.
+.It Li \ee
+One-letter backslash escape.
+.El
+.Pp
+See
+.Xr mandoc_char 7
+for a complete list.
+.Ss Text Decoration
+Terms may be text-decorated using the
+.Sq \ef
+escape followed by an indicator: B (bold), I (italic), R (regular), or P
+(revert to previous mode).
+A numerical representation 3, 2, or 1 (bold, italic, and regular,
+respectively) may be used instead.
+The indicator or numerical representative may be preceded by C
+(constant-width), which is ignored.
+.Pp
+The two-character indicator
+.Sq BI
+requests a font that is both bold and italic.
+It may not be portable to old roff implementations.
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li \efBbold\efR
+Write in \fBbold\fP, then switch to regular font mode.
+.It Li \efIitalic\efP
+Write in \fIitalic\fP, then return to previous font mode.
+.It Li \ef(BIbold italic\efP
+Write in \f(BIbold italic\fP, then return to previous font mode.
+.El
+.Pp
+Text decoration is
+.Em not
+recommended for
+.Xr mdoc 7 ,
+which encourages semantic annotation.
+.Ss Predefined Strings
+Predefined strings, like
+.Sx Special Characters ,
+mark special output glyphs.
+Predefined strings are escaped with the slash-asterisk,
+.Sq \e* :
+single-character
+.Sq \e*X ,
+two-character
+.Sq \e*(XX ,
+and N-character
+.Sq \e*[N] .
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li \e*(Am
+Two-letter ampersand predefined string.
+.It Li \e*q
+One-letter double-quote predefined string.
+.El
+.Pp
+Predefined strings are not recommended for use,
+as they differ across implementations.
+Those supported by
+.Xr mandoc 1
+are listed in
+.Xr mandoc_char 7 .
+Manuals using these predefined strings are almost certainly not portable.
+.Ss Whitespace
+Whitespace consists of the space character.
+In text lines, whitespace is preserved within a line.
+In request and macro lines, whitespace delimits arguments and is discarded.
+.Pp
+Unescaped trailing spaces are stripped from text line input unless in a
+literal context.
+In general, trailing whitespace on any input line is discouraged for
+reasons of portability.
+In the rare case that a blank character is needed at the end of an
+input line, it may be forced by
+.Sq \e\ \e& .
+.Pp
+Literal space characters can be produced in the output
+using escape sequences.
+In macro lines, they can also be included in arguments using quotation; see
+.Sx MACRO SYNTAX
+for details.
+.Pp
+Blank text lines, which may include whitespace, are only permitted
+within literal contexts.
+If the first character of a text line is a space, that line is printed
+with a leading newline.
+.Ss Scaling Widths
+Many requests and macros support scaled widths for their arguments.
+The syntax for a scaled width is
+.Sq Li [+-]?[0-9]*.[0-9]*[:unit:] ,
+where a decimal must be preceded or followed by at least one digit.
+Negative numbers, while accepted, are truncated to zero.
+.Pp
+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.
+.Pp
+Examples:
+.Bl -tag -width ".Bl -tag -width 2i" -offset indent -compact
+.It Li \&.Bl -tag -width 2i
+two-inch tagged list indentation in
+.Xr mdoc 7
+.It Li \&.HP 2i
+two-inch tagged list indentation in
+.Xr man 7
+.It Li \&.sp 2v
+two vertical spaces
+.El
+.Ss Sentence Spacing
+Each sentence should terminate at the end of an input line.
+By doing this, a formatter will be able to apply the proper amount of
+spacing after the end of sentence (unescaped) period, exclamation mark,
+or question mark followed by zero or more non-sentence closing
+delimiters
+.Po
+.Sq \&) ,
+.Sq \&] ,
+.Sq \&' ,
+.Sq \&"
+.Pc .
+.Pp
+The proper spacing is also intelligently preserved if a sentence ends at
+the boundary of a macro line.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+Do not end sentences mid-line like this. Instead,
+end a sentence like this.
+A macro would end like this:
+\&.Xr mandoc 1 \&.
+.Ed
+.Sh REQUEST SYNTAX
+A request or macro line consists of:
+.Pp
+.Bl -enum -compact
+.It
+the control character
+.Sq \&.
+or
+.Sq \(aq
+at the beginning of the line,
+.It
+optionally an arbitrary amount of whitespace,
+.It
+the name of the request or the macro, which is one word of arbitrary
+length, terminated by whitespace,
+.It
+and zero or more arguments delimited by whitespace.
+.El
+.Pp
+Thus, the following request lines are all equivalent:
+.Bd -literal -offset indent
+\&.ig end
+\&.ig end
+\&. ig end
+.Ed
+.Sh MACRO SYNTAX
+Macros are provided by the
+.Xr mdoc 7
+and
+.Xr man 7
+languages and can be defined by the
+.Sx \&de
+request.
+When called, they follow the same syntax as requests, except that
+macro arguments may optionally be quoted by enclosing them
+in double quote characters
+.Pq Sq \(dq .
+Quoted text, even if it contains whitespace or would cause
+a macro invocation when unquoted, is always considered literal text.
+Inside quoted text, pairs of double quote characters
+.Pq Sq Qq
+resolve to single double quote characters.
+.Pp
+To be recognised as the beginning of a quoted argument, the opening
+quote character must be preceded by a space character.
+A quoted argument extends to the next double quote character that is not
+part of a pair, or to the end of the input line, whichever comes earlier.
+Leaving out the terminating double quote character at the end of the line
+is discouraged.
+For clarity, if more arguments follow on the same input line,
+it is recommended to follow the terminating double quote character
+by a space character; in case the next character after the terminating
+double quote character is anything else, it is regarded as the beginning
+of the next, unquoted argument.
+.Pp
+Both in quoted and unquoted arguments, pairs of backslashes
+.Pq Sq \e\e
+resolve to single backslashes.
+In unquoted arguments, space characters can alternatively be included
+by preceding them with a backslash
+.Pq Sq \e\~ ,
+but quoting is usually better for clarity.
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li .Fn strlen \(dqconst char *s\(dq
+Group arguments
+.Qq const char *s
+into one function argument.
+If unspecified,
+.Qq const ,
+.Qq char ,
+and
+.Qq *s
+would be considered separate arguments.
+.It Li .Op \(dqFl a\(dq
+Consider
+.Qq \&Fl a
+as literal text instead of a flag macro.
+.El
+.Sh REQUEST REFERENCE
+The
+.Xr mandoc 1
+.Nm
+parser recognises the following requests.
+Note that the
+.Nm
+language defines many more requests not implemented in
+.Xr mandoc 1 .
+.Ss \&ad
+Set line adjustment mode.
+This line-scoped request is intended to have one argument to select
+normal, left, right, or centre adjustment for subsequent text.
+Currently, it is ignored including its arguments,
+and the number of arguments is not checked.
+.Ss \&am
+Append to a macro definition.
+The syntax of this request is the same as that of
+.Sx \&de .
+.Ss \&ami
+Append to a macro definition, specifying the macro name indirectly.
+The syntax of this request is the same as that of
+.Sx \&dei .
+.Ss \&am1
+Append to a macro definition, switching roff compatibility mode off
+during macro execution.
+The syntax of this request is the same as that of
+.Sx \&de1 .
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&am .
+.Ss \&as
+Append to a user-defined string.
+The syntax of this request is the same as that of
+.Sx \&ds .
+If a user-defined string with the specified name does not yet exist,
+it is set to the empty string before appending.
+.Ss \&cc
+Changes the control character.
+Its syntax is as follows:
+.Bd -literal -offset indent
+.Pf . Cm \&cc Op Ar c
+.Ed
+.Pp
+If
+.Ar c
+is not specified, the control character is reset to
+.Sq \&. .
+Trailing characters are ignored.
+.Ss \&ce
+Center some lines.
+This line-scoped request is intended to take one integer argument,
+specifying how many lines to center.
+Currently, it is ignored including its arguments, and the number
+of arguments is not checked.
+.Ss \&de
+Define a
+.Nm
+macro.
+Its syntax can be either
+.Bd -literal -offset indent
+.Pf . Cm \&de Ar name
+.Ar macro definition
+\&..
+.Ed
+.Pp
+or
+.Bd -literal -offset indent
+.Pf . Cm \&de Ar name Ar end
+.Ar macro definition
+.Pf . Ar end
+.Ed
+.Pp
+Both forms define or redefine the macro
+.Ar name
+to represent the
+.Ar macro definition ,
+which may consist of one or more input lines, including the newline
+characters terminating each line, optionally containing calls to
+.Nm
+requests,
+.Nm
+macros or high-level macros like
+.Xr man 7
+or
+.Xr mdoc 7
+macros, whichever applies to the document in question.
+.Pp
+Specifying a custom
+.Ar end
+macro works in the same way as for
+.Sx \&ig ;
+namely, the call to
+.Sq Pf . Ar end
+first ends the
+.Ar macro definition ,
+and after that, it is also evaluated as a
+.Nm
+request or
+.Nm
+macro, but not as a high-level macro.
+.Pp
+The macro can be invoked later using the syntax
+.Pp
+.D1 Pf . Ar name Op Ar argument Op Ar argument ...
+.Pp
+Regarding argument parsing, see
+.Sx MACRO SYNTAX
+above.
+.Pp
+The line invoking the macro will be replaced
+in the input stream by the
+.Ar macro definition ,
+replacing all occurrences of
+.No \e\e$ Ns Ar N ,
+where
+.Ar N
+is a digit, by the
+.Ar N Ns th Ar argument .
+For example,
+.Bd -literal -offset indent
+\&.de ZN
+\efI\e^\e\e$1\e^\efP\e\e$2
+\&..
+\&.ZN XtFree .
+.Ed
+.Pp
+produces
+.Pp
+.D1 \efI\e^XtFree\e^\efP.
+.Pp
+in the input stream, and thus in the output: \fI\^XtFree\^\fP.
+.Pp
+Since macros and user-defined strings share a common string table,
+defining a macro
+.Ar name
+clobbers the user-defined string
+.Ar name ,
+and the
+.Ar macro definition
+can also be printed using the
+.Sq \e*
+string interpolation syntax described below
+.Sx ds ,
+but this is rarely useful because every macro definition contains at least
+one explicit newline character.
+.Pp
+In order to prevent endless recursion, both groff and
+.Xr mandoc 1
+limit the stack depth for expanding macros and strings
+to a large, but finite number.
+Do not rely on the exact value of this limit.
+.Ss \&dei
+Define a
+.Nm
+macro, specifying the macro name indirectly.
+The syntax of this request is the same as that of
+.Sx \&de .
+The request
+.Pp
+.D1 Pf . Cm \&dei Ar name Op Ar end
+.Pp
+has the same effect as:
+.Pp
+.D1 Pf . Cm \&de No \e* Ns Bo Ar name Bc Op \e* Ns Bq Ar end
+.Ss \&de1
+Define a
+.Nm
+macro that will be executed with
+.Nm
+compatibility mode switched off during macro execution.
+This is a GNU extension not available in traditional
+.Nm
+implementations and not even in older versions of groff.
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&de .
+.Ss \&ds
+Define a user-defined string.
+Its syntax is as follows:
+.Pp
+.D1 Pf . Cm \&ds Ar name Oo \(dq Oc Ns Ar string
+.Pp
+The
+.Ar name
+and
+.Ar string
+arguments are space-separated.
+If the
+.Ar string
+begins with a double-quote character, that character will not be part
+of the string.
+All remaining characters on the input line form the
+.Ar string ,
+including whitespace and double-quote characters, even trailing ones.
+.Pp
+The
+.Ar string
+can be interpolated into subsequent text by using
+.No \e* Ns Bq Ar name
+for a
+.Ar name
+of arbitrary length, or \e*(NN or \e*N if the length of
+.Ar name
+is two or one characters, respectively.
+Interpolation can be prevented by escaping the leading backslash;
+that is, an asterisk preceded by an even number of backslashes
+does not trigger string interpolation.
+.Pp
+Since user-defined strings and macros share a common string table,
+defining a string
+.Ar name
+clobbers the macro
+.Ar name ,
+and the
+.Ar name
+used for defining a string can also be invoked as a macro,
+in which case the following input line will be appended to the
+.Ar string ,
+forming a new input line passed to the
+.Nm
+parser.
+For example,
+.Bd -literal -offset indent
+\&.ds badidea .S
+\&.badidea
+H SYNOPSIS
+.Ed
+.Pp
+invokes the
+.Cm SH
+macro when used in a
+.Xr man 7
+document.
+Such abuse is of course strongly discouraged.
+.Ss \&el
+The
+.Qq else
+half of an if/else conditional.
+Pops a result off the stack of conditional evaluations pushed by
+.Sx \&ie
+and uses it as its conditional.
+If no stack entries are present (e.g., due to no prior
+.Sx \&ie
+calls)
+then false is assumed.
+The syntax of this request is similar to
+.Sx \&if
+except that the conditional is missing.
+.Ss \&EN
+End an equation block.
+See
+.Sx \&EQ .
+.Ss \&EQ
+Begin an equation block.
+See
+.Xr eqn 7
+for a description of the equation language.
+.Ss \&fam
+Change the font family.
+This line-scoped request is intended to have one argument specifying
+the font family to be selected.
+It is a groff extension, and currently, it is ignored including its
+arguments, and the number of arguments is not checked.
+.Ss \&ft
+Change the font.
+Its syntax is as follows:
+.Pp
+.D1 Pf . Cm \&ft Op Ar font
+.Pp
+The following
+.Ar font
+arguments are supported:
+.Bl -tag -width 4n -offset indent
+.It Cm B , BI , 3 , 4
+switches to
+.Sy bold
+font
+.It Cm I , 2
+switches to
+.Em underlined
+font
+.It Cm R , CW , 1
+switches to normal font
+.It Cm P No "or no argument"
+switches back to the previous font
+.El
+.Pp
+This request takes effect only locally, may be overridden by macros
+and escape sequences, and is only supported in
+.Xr man 7
+for now.
+.Ss \&hw
+Specify hyphenation points in words.
+This line-scoped request is currently ignored.
+.Ss \&hy
+Set automatic hyphenation mode.
+This line-scoped request is currently ignored.
+.Ss \&ie
+The
+.Qq if
+half of an if/else conditional.
+The result of the conditional is pushed into a stack used by subsequent
+invocations of
+.Sx \&el ,
+which may be separated by any intervening input (or not exist at all).
+Its syntax is equivalent to
+.Sx \&if .
+.Ss \&if
+Begins a conditional.
+This request has the following syntax:
+.Bd -literal -offset indent
+\&.if COND BODY
+.Ed
+.Bd -literal -offset indent
+\&.if COND \e{BODY
+BODY...\e}
+.Ed
+.Bd -literal -offset indent
+\&.if COND \e{\e
+BODY...
+\&.\e}
+.Ed
+.Pp
+COND is a conditional statement.
+Currently,
+.Xr mandoc 1
+supports the following subset of roff conditionals:
+.Bl -bullet
+.It
+If
+.Sq \&!
+is prefixed to COND, the condition is logically inverted.
+.It
+If the first character of COND is
+.Sq n
+.Pq nroff mode
+or
+.Sq o
+.Pq odd page ,
+COND evaluates to true.
+.It
+If the first character of COND is
+.Sq c
+.Pq character available ,
+.Sq d
+.Pq string defined ,
+.Sq e
+.Pq even page ,
+.Sq r
+.Pq register accessed ,
+or
+.Sq t
+.Pq troff mode ,
+COND evaluates to false.
+.It
+If COND starts with a parenthesis or with an optionally signed
+integer number, it is evaluated according to the rules of
+.Sx Numerical expressions
+explained below.
+It evaluates to true if the the result is positive,
+or to false if the result is zero or negative.
+.It
+Otherwise, the first character of COND is regarded as a delimiter
+and COND evaluates to true if the string extending from its first
+to its second occurrence is equal to the string extending from its
+second to its third occurrence.
+.It
+If COND cannot be parsed, it evaluates to false.
+.El
+.Pp
+If a conditional is false, its children are not processed, but are
+syntactically interpreted to preserve the integrity of the input
+document.
+Thus,
+.Pp
+.D1 \&.if t .ig
+.Pp
+will discard the
+.Sq \&.ig ,
+which may lead to interesting results, but
+.Pp
+.D1 \&.if t .if t \e{\e
+.Pp
+will continue to syntactically interpret to the block close of the final
+conditional.
+Sub-conditionals, in this case, obviously inherit the truth value of
+the parent.
+.Pp
+If the BODY section is begun by an escaped brace
+.Sq \e{ ,
+scope continues until the end of the input line containing the
+matching closing-brace escape sequence
+.Sq \e} .
+If the BODY is not enclosed in braces, scope continues until
+the end of the line.
+If the COND is followed by a BODY on the same line, whether after a
+brace or not, then requests and macros
+.Em must
+begin with a control character.
+It is generally more intuitive, in this case, to write
+.Bd -literal -offset indent
+\&.if COND \e{\e
+\&.foo
+bar
+\&.\e}
+.Ed
+.Pp
+than having the request or macro follow as
+.Pp
+.D1 \&.if COND \e{ .foo
+.Pp
+The scope of a conditional is always parsed, but only executed if the
+conditional evaluates to true.
+.Pp
+Note that the
+.Sq \e}
+is converted into a zero-width escape sequence if not passed as a
+standalone macro
+.Sq \&.\e} .
+For example,
+.Pp
+.D1 \&.Fl a \e} b
+.Pp
+will result in
+.Sq \e}
+being considered an argument of the
+.Sq \&Fl
+macro.
+.Ss \&ig
+Ignore input.
+Its syntax can be either
+.Bd -literal -offset indent
+.Pf . Cm \&ig
+.Ar ignored text
+\&..
+.Ed
+.Pp
+or
+.Bd -literal -offset indent
+.Pf . Cm \&ig Ar end
+.Ar ignored text
+.Pf . Ar end
+.Ed
+.Pp
+In the first case, input is ignored until a
+.Sq \&..
+request is encountered on its own line.
+In the second case, input is ignored until the specified
+.Sq Pf . Ar end
+macro is encountered.
+Do not use the escape character
+.Sq \e
+anywhere in the definition of
+.Ar end ;
+it would cause very strange behaviour.
+.Pp
+When the
+.Ar end
+macro is a roff request or a roff macro, like in
+.Pp
+.D1 \&.ig if
+.Pp
+the subsequent invocation of
+.Sx \&if
+will first terminate the
+.Ar ignored text ,
+then be invoked as usual.
+Otherwise, it only terminates the
+.Ar ignored text ,
+and arguments following it or the
+.Sq \&..
+request are discarded.
+.Ss \&ll
+Change the output line length.
+Its syntax is as follows:
+.Pp
+.D1 Pf . Cm \&ll Op Oo +|- Oc Ns Ar width
+.Pp
+If the
+.Ar width
+argument is omitted, the line length is reset to its previous value.
+The default setting for terminal output is 78n.
+If a sign is given, the line length is added to or subtracted from;
+otherwise, it is set to the provided value.
+Using this request in new manuals is discouraged for several reasons,
+among others because it overrides the
+.Xr mandoc 1
+.Fl O Cm width
+command line option.
+.Ss \&ne
+Declare the need for the specified minimum vertical space
+before the next trap or the bottom of the page.
+This line-scoped request is currently ignored.
+.Ss \&nh
+Turn off automatic hyphenation mode.
+This line-scoped request is currently ignored.
+.Ss \&nr
+Define or change a register.
+A register is an arbitrary string value that defines some sort of state,
+which influences parsing and/or formatting.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&nr Ar name Oo +|- Oc Ns Ar expression
+.Pp
+For the syntax of
+.Ar expression ,
+see
+.Sx Numerical expressions
+below.
+If it is prefixed by a sign, the register will be
+incremented or decremented instead of assigned to.
+.Pp
+The following register
+.Ar name
+is handled specially:
+.Bl -tag -width Ds
+.It Cm nS
+If set to a positive integer value, certain
+.Xr mdoc 7
+macros will behave in the same way as in the
+.Em SYNOPSIS
+section.
+If set to 0, these macros will behave in the same way as outside the
+.Em SYNOPSIS
+section, even when called within the
+.Em SYNOPSIS
+section itself.
+Note that starting a new
+.Xr mdoc 7
+section with the
+.Cm \&Sh
+macro will reset this register.
+.El
+.Ss \&ns
+Turn on no-space mode.
+This line-scoped request is intended to take no arguments.
+Currently, it is ignored including its arguments,
+and the number of arguments is not checked.
+.Ss \&ps
+Change point size.
+This line-scoped request is intended to take one numerical argument.
+Currently, it is ignored including its arguments,
+and the number of arguments is not checked.
+.Ss \&rm
+Remove a request, macro or string.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&rm Ar name
+.Ss \&rr
+Remove a register.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&rr Ar name
+.Ss \&so
+Include a source file.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&so Ar file
+.Pp
+The
+.Ar file
+will be read and its contents processed as input in place of the
+.Sq \&.so
+request line.
+To avoid inadvertent inclusion of unrelated files,
+.Xr mandoc 1
+only accepts relative paths not containing the strings
+.Qq ../
+and
+.Qq /.. .
+.Pp
+This request requires
+.Xr man 1
+to change to the right directory before calling
+.Xr mandoc 1 ,
+per convention to the root of the manual tree.
+Typical usage looks like:
+.Pp
+.Dl \&.so man3/Xcursor.3
+.Pp
+As the whole concept is rather fragile, the use of
+.Sx \&so
+is discouraged.
+Use
+.Xr ln 1
+instead.
+.Ss \&ta
+Set tab stops.
+This line-scoped request can take an arbitrary number of arguments.
+Currently, it is ignored including its arguments.
+.Ss \&tr
+Output character translation.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&tr Ar [ab]+
+.Pp
+Pairs of
+.Ar ab
+characters are replaced
+.Ar ( a
+for
+.Ar b ) .
+Replacement (or origin) characters may also be character escapes; thus,
+.Pp
+.Dl tr \e(xx\e(yy
+.Pp
+replaces all invocations of \e(xx with \e(yy.
+.Ss \&T&
+Re-start a table layout, retaining the options of the prior table
+invocation.
+See
+.Sx \&TS .
+.Ss \&TE
+End a table context.
+See
+.Sx \&TS .
+.Ss \&TS
+Begin a table, which formats input in aligned rows and columns.
+See
+.Xr tbl 7
+for a description of the tbl language.
+.Ss Numerical expressions
+The
+.Sx \&nr ,
+.Sx \&if ,
+and
+.Sx \&ie
+requests accept integer numerical expressions as arguments.
+These are always evaluated using the C
+.Vt int
+type; integer overflow works the same way as in the C language.
+Numbers consist of an arbitrary number of digits
+.Sq 0
+to
+.Sq 9
+prefixed by an optional sign
+.Sq +
+or
+.Sq - .
+.Pp
+The following binary operators are implemented.
+Unless otherwise stated, they behave as in the C language:
+.Pp
+.Bl -tag -width 2n -compact
+.It Ic +
+addition
+.It Ic -
+subtraction
+.It Ic *
+multiplication
+.It Ic /
+division
+.It Ic %
+remainder of division
+.It Ic <
+less than
+.It Ic >
+greater than
+.It Ic ==
+equal to
+.It Ic =
+equal to, same effect as
+.Ic ==
+(this differs from C)
+.It Ic <=
+less than or equal to
+.It Ic >=
+greater than or equal to
+.It Ic <>
+not equal to (corresponds to C
+.Ic != ;
+this one is of limited portability, it is supported by Heirloom roff,
+but not by groff)
+.It Ic &
+logical and (corresponds to C
+.Ic && )
+.It Ic \&:
+logical or (corresponds to C
+.Ic \&|| )
+.It Ic <?
+minimum (not available in C)
+.It Ic >?
+maximum (not available in C)
+.El
+.Pp
+There is no concept of precendence; evaluation proceeds from left to right,
+except when subexpressions are enclosed in parantheses.
+Inside parentheses, whitespace is ignored.
+.Sh ESCAPE SEQUENCE REFERENCE
+The
+.Xr mandoc 1
+.Nm
+parser recognises the following escape sequences.
+Note that the
+.Nm
+language defines more escape sequences not implemented in
+.Xr mandoc 1 .
+In
+.Xr mdoc 7
+and
+.Xr man 7
+documents, using escape sequences is discouraged except for those
+described in the
+.Sx LANGUAGE SYNTAX
+section above.
+.Pp
+A backslash followed by any character not listed here
+simply prints that character itself.
+.Ss \e<newline>
+A backslash at the end of an input line can be used to continue the
+logical input line on the next physical input line, joining the text
+on both lines together as if it were on a single input line.
+.Ss \e<space>
+The escape sequence backslash-space
+.Pq Sq \e\ \&
+is an unpaddable space-sized non-breaking space character; see
+.Sx Whitespace .
+.Ss \e\(dq
+The rest of the input line is treated as
+.Sx Comments .
+.Ss \e%
+Hyphenation allowed at this point of the word; ignored by
+.Xr mandoc 1 .
+.Ss \e&
+Non-printing zero-width character; see
+.Sx Whitespace .
+.Ss \e\(aq
+Acute accent special character; use
+.Sq \e(aa
+instead.
+.Ss \e( Ns Ar cc
+.Sx Special Characters
+with two-letter names, see
+.Xr mandoc_char 7 .
+.Ss \e*[ Ns Ar name ]
+Interpolate the string with the
+.Ar name ;
+see
+.Sx Predefined Strings
+and
+.Sx ds .
+For short names, there are variants
+.No \e* Ns Ar c
+and
+.No \e*( Ns Ar cc .
+.Ss \e-
+Special character
+.Dq mathematical minus sign .
+.Ss \e[ Ns Ar name ]
+.Sx Special Characters
+with names of arbitrary length, see
+.Xr mandoc_char 7 .
+.Ss \e^
+One-twelfth em half-narrow space character, effectively zero-width in
+.Xr mandoc 1 .
+.Ss \e`
+Grave accent special character; use
+.Sq \e(ga
+instead.
+.Ss \e{
+Begin conditional input; see
+.Sx if .
+.Ss \e\(ba
+One-sixth em narrow space character, effectively zero-width in
+.Xr mandoc 1 .
+.Ss \e}
+End conditional input; see
+.Sx if .
+.Ss \e~
+Paddable non-breaking space character.
+.Ss \e0
+Digit width space character.
+.Ss \eA\(aq Ns Ar string Ns \(aq
+Anchor definition; ignored by
+.Xr mandoc 1 .
+.Ss \eB\(aq Ns Ar string Ns \(aq
+Interpolate
+.Sq 1
+if
+.Ar string
+conforms to the syntax of
+.Sx Numerical expressions
+explained above and
+.Sq 0
+otherwise.
+.Ss \eb\(aq Ns Ar string Ns \(aq
+Bracket building function; ignored by
+.Xr mandoc 1 .
+.Ss \eC\(aq Ns Ar name Ns \(aq
+.Sx Special Characters
+with names of arbitrary length.
+.Ss \ec
+Interrupt text processing to insert requests or macros; ignored by
+.Xr mandoc 1 .
+.Ss \eD\(aq Ns Ar string Ns \(aq
+Draw graphics function; ignored by
+.Xr mandoc 1 .
+.Ss \ed
+Move down by half a line; ignored by
+.Xr mandoc 1 .
+.Ss \ee
+Backslash special character.
+.Ss \eF[ Ns Ar name ]
+Switch font family (groff extension); ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eF Ns Ar c
+and
+.No \eF( Ns Ar cc .
+.Ss \ef[ Ns Ar name ]
+Switch to the font
+.Ar name ,
+see
+.Sx Text Decoration .
+For short names, there are variants
+.No \ef Ns Ar c
+and
+.No \ef( Ns Ar cc .
+.Ss \eg[ Ns Ar name ]
+Interpolate the format of a number register; ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eg Ns Ar c
+and
+.No \eg( Ns Ar cc .
+.Ss \eH\(aq Ns Oo +|- Oc Ns Ar number Ns \(aq
+Set the height of the current font; ignored by
+.Xr mandoc 1 .
+.Ss \eh\(aq Ns Ar number Ns \(aq
+Horizontal motion; ignored by
+.Xr mandoc 1 .
+.Ss \ek[ Ns Ar name ]
+Mark horizontal input place in register; ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \ek Ns Ar c
+and
+.No \ek( Ns Ar cc .
+.Ss \eL\(aq Ns Ar number Ns Oo Ar c Oc Ns \(aq
+Vertical line drawing function; ignored by
+.Xr mandoc 1 .
+.Ss \el\(aq Ns Ar number Ns Oo Ar c Oc Ns \(aq
+Horizontal line drawing function; ignored by
+.Xr mandoc 1 .
+.Ss \eM[ Ns Ar name ]
+Set fill (background) color (groff extension); ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eM Ns Ar c
+and
+.No \eM( Ns Ar cc .
+.Ss \em[ Ns Ar name ]
+Set glyph drawing color (groff extension); ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \em Ns Ar c
+and
+.No \em( Ns Ar cc .
+.Ss \eN\(aq Ns Ar number Ns \(aq
+Character
+.Ar number
+on the current font.
+.Ss \en[ Ns Ar name ]
+Interpolate the number register
+.Ar name .
+For short names, there are variants
+.No \en Ns Ar c
+and
+.No \en( Ns Ar cc .
+.Ss \eo\(aq Ns Ar string Ns \(aq
+Overstrike
+.Ar string ;
+ignored by
+.Xr mandoc 1 .
+.Ss \eR\(aq Ns Ar name Oo +|- Oc Ns Ar number Ns \(aq
+Set number register; ignored by
+.Xr mandoc 1 .
+.Ss \eS\(aq Ns Ar number Ns \(aq
+Slant output; ignored by
+.Xr mandoc 1 .
+.Ss \es\(aq Ns Oo +|- Oc Ns Ar number Ns \(aq
+Change point size; ignored by
+.Xr mandoc 1 .
+Alternative forms
+.No \es Ns Oo +|- Oc Ns Ar n ,
+.No \es Ns Oo +|- Oc Ns \(aq Ns Ar number Ns \(aq ,
+.No \es Ns [ Oo +|- Oc Ns Ar number ] ,
+and
+.No \es Ns Oo +|- Oc Ns [ Ar number Ns ]
+are also parsed and ignored.
+.Ss \et
+Horizontal tab; ignored by
+.Xr mandoc 1 .
+.Ss \eu
+Move up by half a line; ignored by
+.Xr mandoc 1 .
+.Ss \eV[ Ns Ar name ]
+Interpolate an environment variable; ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eV Ns Ar c
+and
+.No \eV( Ns Ar cc .
+.Ss \ev\(aq Ns Ar number Ns \(aq
+Vertical motion; ignored by
+.Xr mandoc 1 .
+.Ss \ew\(aq Ns Ar string Ns \(aq
+Interpolate the width of the
+.Ar string .
+The
+.Xr mandoc 1
+implementation assumes that after expansion of user-defined strings, the
+.Ar string
+only contains normal characters, no escape sequences, and that each
+character has a width of 24 basic units.
+.Ss \eX\(aq Ns Ar string Ns \(aq
+Output
+.Ar string
+as device control function; ignored in nroff mode and by
+.Xr mandoc 1 .
+.Ss \ex\(aq Ns Ar number Ns \(aq
+Extra line space function; ignored by
+.Xr mandoc 1 .
+.Ss \eY[ Ns Ar name ]
+Output a string as a device control function; ignored in nroff mode and by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eY Ns Ar c
+and
+.No \eY( Ns Ar cc .
+.Ss \eZ\(aq Ns Ar string Ns \(aq
+Print
+.Ar string
+with zero width and height; ignored by
+.Xr mandoc 1 .
+.Ss \ez
+Output the next character without advancing the cursor position;
+approximated in
+.Xr mandoc 1
+by simply skipping the next character.
+.Sh COMPATIBILITY
+This section documents compatibility between mandoc and other
+.Nm
+implementations, at this time limited to GNU troff
+.Pq Qq groff .
+The term
+.Qq historic groff
+refers to groff version 1.15.
+.Pp
+.Bl -dash -compact
+.It
+In mandoc, the
+.Sx \&EQ ,
+.Sx \&TE ,
+.Sx \&TS ,
+and
+.Sx \&T& ,
+macros are considered regular macros.
+In all other
+.Nm
+implementations, these are special macros that must be specified without
+spacing between the control character (which must be a period) and the
+macro name.
+.It
+The
+.Cm nS
+register is only compatible with OpenBSD's groff-1.15.
+.It
+Historic groff did not accept white-space before a custom
+.Ar end
+macro for the
+.Sx \&ig
+request.
+.It
+The
+.Sx \&if
+and family would print funny white-spaces with historic groff when
+using the next-line syntax.
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr eqn 7 ,
+.Xr man 7 ,
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr tbl 7
+.Rs
+.%A Joseph F. Ossanna
+.%A Brian W. Kernighan
+.%I AT&T Bell Laboratories
+.%T Troff User's Manual
+.%R Computing Science Technical Report
+.%N 54
+.%C Murray Hill, New Jersey
+.%D 1976 and 1992
+.%U http://www.kohala.com/start/troff/cstr54.ps
+.Re
+.Rs
+.%A Joseph F. Ossanna
+.%A Brian W. Kernighan
+.%A Gunnar Ritter
+.%T Heirloom Documentation Tools Nroff/Troff User's Manual
+.%D September 17, 2007
+.%U http://heirloom.sourceforge.net/doctools/troff.pdf
+.Re
+.Sh HISTORY
+The RUNOFF typesetting system, whose input forms the basis for
+.Nm ,
+was written in MAD and FAP for the CTSS operating system by Jerome E.
+Saltzer in 1964.
+Doug McIlroy rewrote it in BCPL in 1969, renaming it
+.Nm .
+Dennis M. Ritchie rewrote McIlroy's
+.Nm
+in PDP-11 assembly for
+.At v1 ,
+Joseph F. Ossanna improved roff and renamed it nroff
+for
+.At v2 ,
+then ported nroff to C as troff, which Brian W. Kernighan released with
+.At v7 .
+In 1989, James Clarke re-implemented troff in C++, naming it groff.
+.Sh AUTHORS
+.An -nosplit
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/roff.c b/contrib/mdocml/roff.c
new file mode 100644
index 0000000..4903ee8
--- /dev/null
+++ b/contrib/mdocml/roff.c
@@ -0,0 +1,2343 @@
+/* $Id: roff.c,v 1.224 2014/08/01 17:27:44 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libroff.h"
+#include "libmandoc.h"
+
+/* Maximum number of nested if-else conditionals. */
+#define RSTACK_MAX 128
+
+/* Maximum number of string expansions per line, to break infinite loops. */
+#define EXPAND_LIMIT 1000
+
+enum rofft {
+ ROFF_ad,
+ ROFF_am,
+ ROFF_ami,
+ ROFF_am1,
+ ROFF_as,
+ ROFF_cc,
+ ROFF_ce,
+ ROFF_de,
+ ROFF_dei,
+ ROFF_de1,
+ ROFF_ds,
+ ROFF_el,
+ ROFF_fam,
+ ROFF_hw,
+ ROFF_hy,
+ ROFF_ie,
+ ROFF_if,
+ ROFF_ig,
+ ROFF_it,
+ ROFF_ne,
+ ROFF_nh,
+ ROFF_nr,
+ ROFF_ns,
+ ROFF_ps,
+ ROFF_rm,
+ ROFF_rr,
+ ROFF_so,
+ ROFF_ta,
+ ROFF_tr,
+ ROFF_Dd,
+ ROFF_TH,
+ ROFF_TS,
+ ROFF_TE,
+ ROFF_T_,
+ ROFF_EQ,
+ ROFF_EN,
+ ROFF_cblock,
+ ROFF_USERDEF,
+ ROFF_MAX
+};
+
+/*
+ * An incredibly-simple string buffer.
+ */
+struct roffstr {
+ char *p; /* nil-terminated buffer */
+ size_t sz; /* saved strlen(p) */
+};
+
+/*
+ * A key-value roffstr pair as part of a singly-linked list.
+ */
+struct roffkv {
+ struct roffstr key;
+ struct roffstr val;
+ struct roffkv *next; /* next in list */
+};
+
+/*
+ * A single number register as part of a singly-linked list.
+ */
+struct roffreg {
+ struct roffstr key;
+ int val;
+ struct roffreg *next;
+};
+
+struct roff {
+ struct mparse *parse; /* parse point */
+ struct roffnode *last; /* leaf of stack */
+ int *rstack; /* stack of inverted `ie' values */
+ struct roffreg *regtab; /* number registers */
+ struct roffkv *strtab; /* user-defined strings & macros */
+ struct roffkv *xmbtab; /* multi-byte trans table (`tr') */
+ struct roffstr *xtab; /* single-byte trans table (`tr') */
+ const char *current_string; /* value of last called user macro */
+ struct tbl_node *first_tbl; /* first table parsed */
+ struct tbl_node *last_tbl; /* last table parsed */
+ struct tbl_node *tbl; /* current table being parsed */
+ struct eqn_node *last_eqn; /* last equation parsed */
+ struct eqn_node *first_eqn; /* first equation parsed */
+ struct eqn_node *eqn; /* current equation being parsed */
+ int options; /* parse options */
+ int rstacksz; /* current size limit of rstack */
+ int rstackpos; /* position in rstack */
+ char control; /* control character */
+};
+
+struct roffnode {
+ enum rofft tok; /* type of node */
+ struct roffnode *parent; /* up one in stack */
+ int line; /* parse line */
+ int col; /* parse col */
+ char *name; /* node name, e.g. macro name */
+ char *end; /* end-rules: custom token */
+ int endspan; /* end-rules: next-line or infty */
+ int rule; /* current evaluation rule */
+};
+
+#define ROFF_ARGS struct roff *r, /* parse ctx */ \
+ enum rofft tok, /* tok of macro */ \
+ char **bufp, /* input buffer */ \
+ size_t *szp, /* size of input buffer */ \
+ int ln, /* parse line */ \
+ int ppos, /* original pos in buffer */ \
+ int pos, /* current pos in buffer */ \
+ int *offs /* reset offset of buffer data */
+
+typedef enum rofferr (*roffproc)(ROFF_ARGS);
+
+struct roffmac {
+ const char *name; /* macro name */
+ roffproc proc; /* process new macro */
+ roffproc text; /* process as child text of macro */
+ roffproc sub; /* process as child of macro */
+ int flags;
+#define ROFFMAC_STRUCT (1 << 0) /* always interpret */
+ struct roffmac *next;
+};
+
+struct predef {
+ const char *name; /* predefined input name */
+ const char *str; /* replacement symbol */
+};
+
+#define PREDEF(__name, __str) \
+ { (__name), (__str) },
+
+static enum rofft roffhash_find(const char *, size_t);
+static void roffhash_init(void);
+static void roffnode_cleanscope(struct roff *);
+static void roffnode_pop(struct roff *);
+static void roffnode_push(struct roff *, enum rofft,
+ const char *, int, int);
+static enum rofferr roff_block(ROFF_ARGS);
+static enum rofferr roff_block_text(ROFF_ARGS);
+static enum rofferr roff_block_sub(ROFF_ARGS);
+static enum rofferr roff_cblock(ROFF_ARGS);
+static enum rofferr roff_cc(ROFF_ARGS);
+static void roff_ccond(struct roff *, int, int);
+static enum rofferr roff_cond(ROFF_ARGS);
+static enum rofferr roff_cond_text(ROFF_ARGS);
+static enum rofferr roff_cond_sub(ROFF_ARGS);
+static enum rofferr roff_ds(ROFF_ARGS);
+static int roff_evalcond(const char *, int *);
+static int roff_evalnum(const char *, int *, int *, int);
+static int roff_evalpar(const char *, int *, int *);
+static int roff_evalstrcond(const char *, int *);
+static void roff_free1(struct roff *);
+static void roff_freereg(struct roffreg *);
+static void roff_freestr(struct roffkv *);
+static size_t roff_getname(struct roff *, char **, int, int);
+static int roff_getnum(const char *, int *, int *);
+static int roff_getop(const char *, int *, char *);
+static int roff_getregn(const struct roff *,
+ const char *, size_t);
+static int roff_getregro(const char *name);
+static const char *roff_getstrn(const struct roff *,
+ const char *, size_t);
+static enum rofferr roff_it(ROFF_ARGS);
+static enum rofferr roff_line_ignore(ROFF_ARGS);
+static enum rofferr roff_nr(ROFF_ARGS);
+static void roff_openeqn(struct roff *, const char *,
+ int, int, const char *);
+static enum rofft roff_parse(struct roff *, char *, int *,
+ int, int);
+static enum rofferr roff_parsetext(char **, size_t *, int, int *);
+static enum rofferr roff_res(struct roff *,
+ char **, size_t *, int, int);
+static enum rofferr roff_rm(ROFF_ARGS);
+static enum rofferr roff_rr(ROFF_ARGS);
+static void roff_setstr(struct roff *,
+ const char *, const char *, int);
+static void roff_setstrn(struct roffkv **, const char *,
+ size_t, const char *, size_t, int);
+static enum rofferr roff_so(ROFF_ARGS);
+static enum rofferr roff_tr(ROFF_ARGS);
+static enum rofferr roff_Dd(ROFF_ARGS);
+static enum rofferr roff_TH(ROFF_ARGS);
+static enum rofferr roff_TE(ROFF_ARGS);
+static enum rofferr roff_TS(ROFF_ARGS);
+static enum rofferr roff_EQ(ROFF_ARGS);
+static enum rofferr roff_EN(ROFF_ARGS);
+static enum rofferr roff_T_(ROFF_ARGS);
+static enum rofferr roff_userdef(ROFF_ARGS);
+
+/* See roffhash_find() */
+
+#define ASCII_HI 126
+#define ASCII_LO 33
+#define HASHWIDTH (ASCII_HI - ASCII_LO + 1)
+
+static struct roffmac *hash[HASHWIDTH];
+
+static struct roffmac roffs[ROFF_MAX] = {
+ { "ad", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "am", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+ { "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+ { "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+ { "as", roff_ds, NULL, NULL, 0, NULL },
+ { "cc", roff_cc, NULL, NULL, 0, NULL },
+ { "ce", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "de", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+ { "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+ { "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+ { "ds", roff_ds, NULL, NULL, 0, NULL },
+ { "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
+ { "fam", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "hw", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "hy", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
+ { "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
+ { "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+ { "it", roff_it, NULL, NULL, 0, NULL },
+ { "ne", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "nh", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "nr", roff_nr, NULL, NULL, 0, NULL },
+ { "ns", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "ps", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "rm", roff_rm, NULL, NULL, 0, NULL },
+ { "rr", roff_rr, NULL, NULL, 0, NULL },
+ { "so", roff_so, NULL, NULL, 0, NULL },
+ { "ta", roff_line_ignore, NULL, NULL, 0, NULL },
+ { "tr", roff_tr, NULL, NULL, 0, NULL },
+ { "Dd", roff_Dd, NULL, NULL, 0, NULL },
+ { "TH", roff_TH, NULL, NULL, 0, NULL },
+ { "TS", roff_TS, NULL, NULL, 0, NULL },
+ { "TE", roff_TE, NULL, NULL, 0, NULL },
+ { "T&", roff_T_, NULL, NULL, 0, NULL },
+ { "EQ", roff_EQ, NULL, NULL, 0, NULL },
+ { "EN", roff_EN, NULL, NULL, 0, NULL },
+ { ".", roff_cblock, NULL, NULL, 0, NULL },
+ { NULL, roff_userdef, NULL, NULL, 0, NULL },
+};
+
+/* not currently implemented: Ds em Eq LP Me PP pp Or Rd Sf SH */
+const char *const __mdoc_reserved[] = {
+ "Ac", "Ad", "An", "Ao", "Ap", "Aq", "Ar", "At",
+ "Bc", "Bd", "Bf", "Bk", "Bl", "Bo", "Bq",
+ "Brc", "Bro", "Brq", "Bsx", "Bt", "Bx",
+ "Cd", "Cm", "Db", "Dc", "Dd", "Dl", "Do", "Dq",
+ "Dt", "Dv", "Dx", "D1",
+ "Ec", "Ed", "Ef", "Ek", "El", "Em",
+ "En", "Eo", "Er", "Es", "Ev", "Ex",
+ "Fa", "Fc", "Fd", "Fl", "Fn", "Fo", "Fr", "Ft", "Fx",
+ "Hf", "Ic", "In", "It", "Lb", "Li", "Lk", "Lp",
+ "Ms", "Mt", "Nd", "Nm", "No", "Ns", "Nx",
+ "Oc", "Oo", "Op", "Os", "Ot", "Ox",
+ "Pa", "Pc", "Pf", "Po", "Pp", "Pq",
+ "Qc", "Ql", "Qo", "Qq", "Re", "Rs", "Rv",
+ "Sc", "Sh", "Sm", "So", "Sq",
+ "Ss", "St", "Sx", "Sy",
+ "Ta", "Tn", "Ud", "Ux", "Va", "Vt", "Xc", "Xo", "Xr",
+ "%A", "%B", "%C", "%D", "%I", "%J", "%N", "%O",
+ "%P", "%Q", "%R", "%T", "%U", "%V",
+ NULL
+};
+
+/* not currently implemented: BT DE DS ME MT PT SY TQ YS */
+const char *const __man_reserved[] = {
+ "AT", "B", "BI", "BR", "DT",
+ "EE", "EN", "EQ", "EX", "HP", "I", "IB", "IP", "IR",
+ "LP", "OP", "P", "PD", "PP",
+ "R", "RB", "RE", "RI", "RS", "SB", "SH", "SM", "SS",
+ "TE", "TH", "TP", "TS", "T&", "UC", "UE", "UR",
+ NULL
+};
+
+/* Array of injected predefined strings. */
+#define PREDEFS_MAX 38
+static const struct predef predefs[PREDEFS_MAX] = {
+#include "predefs.in"
+};
+
+/* See roffhash_find() */
+#define ROFF_HASH(p) (p[0] - ASCII_LO)
+
+static int roffit_lines; /* number of lines to delay */
+static char *roffit_macro; /* nil-terminated macro line */
+
+
+static void
+roffhash_init(void)
+{
+ struct roffmac *n;
+ int buc, i;
+
+ for (i = 0; i < (int)ROFF_USERDEF; i++) {
+ assert(roffs[i].name[0] >= ASCII_LO);
+ assert(roffs[i].name[0] <= ASCII_HI);
+
+ buc = ROFF_HASH(roffs[i].name);
+
+ if (NULL != (n = hash[buc])) {
+ for ( ; n->next; n = n->next)
+ /* Do nothing. */ ;
+ n->next = &roffs[i];
+ } else
+ hash[buc] = &roffs[i];
+ }
+}
+
+/*
+ * Look up a roff token by its name. Returns ROFF_MAX if no macro by
+ * the nil-terminated string name could be found.
+ */
+static enum rofft
+roffhash_find(const char *p, size_t s)
+{
+ int buc;
+ struct roffmac *n;
+
+ /*
+ * libroff has an extremely simple hashtable, for the time
+ * being, which simply keys on the first character, which must
+ * be printable, then walks a chain. It works well enough until
+ * optimised.
+ */
+
+ if (p[0] < ASCII_LO || p[0] > ASCII_HI)
+ return(ROFF_MAX);
+
+ buc = ROFF_HASH(p);
+
+ if (NULL == (n = hash[buc]))
+ return(ROFF_MAX);
+ for ( ; n; n = n->next)
+ if (0 == strncmp(n->name, p, s) && '\0' == n->name[(int)s])
+ return((enum rofft)(n - roffs));
+
+ return(ROFF_MAX);
+}
+
+/*
+ * Pop the current node off of the stack of roff instructions currently
+ * pending.
+ */
+static void
+roffnode_pop(struct roff *r)
+{
+ struct roffnode *p;
+
+ assert(r->last);
+ p = r->last;
+
+ r->last = r->last->parent;
+ free(p->name);
+ free(p->end);
+ free(p);
+}
+
+/*
+ * Push a roff node onto the instruction stack. This must later be
+ * removed with roffnode_pop().
+ */
+static void
+roffnode_push(struct roff *r, enum rofft tok, const char *name,
+ int line, int col)
+{
+ struct roffnode *p;
+
+ p = mandoc_calloc(1, sizeof(struct roffnode));
+ p->tok = tok;
+ if (name)
+ p->name = mandoc_strdup(name);
+ p->parent = r->last;
+ p->line = line;
+ p->col = col;
+ p->rule = p->parent ? p->parent->rule : 0;
+
+ r->last = p;
+}
+
+static void
+roff_free1(struct roff *r)
+{
+ struct tbl_node *tbl;
+ struct eqn_node *e;
+ int i;
+
+ while (NULL != (tbl = r->first_tbl)) {
+ r->first_tbl = tbl->next;
+ tbl_free(tbl);
+ }
+ r->first_tbl = r->last_tbl = r->tbl = NULL;
+
+ while (NULL != (e = r->first_eqn)) {
+ r->first_eqn = e->next;
+ eqn_free(e);
+ }
+ r->first_eqn = r->last_eqn = r->eqn = NULL;
+
+ while (r->last)
+ roffnode_pop(r);
+
+ free (r->rstack);
+ r->rstack = NULL;
+ r->rstacksz = 0;
+ r->rstackpos = -1;
+
+ roff_freereg(r->regtab);
+ r->regtab = NULL;
+
+ roff_freestr(r->strtab);
+ roff_freestr(r->xmbtab);
+ r->strtab = r->xmbtab = NULL;
+
+ if (r->xtab)
+ for (i = 0; i < 128; i++)
+ free(r->xtab[i].p);
+ free(r->xtab);
+ r->xtab = NULL;
+}
+
+void
+roff_reset(struct roff *r)
+{
+
+ roff_free1(r);
+ r->control = 0;
+}
+
+void
+roff_free(struct roff *r)
+{
+
+ roff_free1(r);
+ free(r);
+}
+
+struct roff *
+roff_alloc(struct mparse *parse, int options)
+{
+ struct roff *r;
+
+ r = mandoc_calloc(1, sizeof(struct roff));
+ r->parse = parse;
+ r->options = options;
+ r->rstackpos = -1;
+
+ roffhash_init();
+
+ return(r);
+}
+
+/*
+ * In the current line, expand escape sequences that tend to get
+ * used in numerical expressions and conditional requests.
+ * Also check the syntax of the remaining escape sequences.
+ */
+static enum rofferr
+roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos)
+{
+ char ubuf[24]; /* buffer to print the number */
+ const char *start; /* start of the string to process */
+ char *stesc; /* start of an escape sequence ('\\') */
+ const char *stnam; /* start of the name, after "[(*" */
+ const char *cp; /* end of the name, e.g. before ']' */
+ const char *res; /* the string to be substituted */
+ char *nbuf; /* new buffer to copy bufp to */
+ size_t maxl; /* expected length of the escape name */
+ size_t naml; /* actual length of the escape name */
+ int expand_count; /* to avoid infinite loops */
+ int npos; /* position in numeric expression */
+ int arg_complete; /* argument not interrupted by eol */
+ char term; /* character terminating the escape */
+
+ expand_count = 0;
+ start = *bufp + pos;
+ stesc = strchr(start, '\0') - 1;
+ while (stesc-- > start) {
+
+ /* Search backwards for the next backslash. */
+
+ if ('\\' != *stesc)
+ continue;
+
+ /* If it is escaped, skip it. */
+
+ for (cp = stesc - 1; cp >= start; cp--)
+ if ('\\' != *cp)
+ break;
+
+ if (0 == (stesc - cp) % 2) {
+ stesc = (char *)cp;
+ continue;
+ }
+
+ /* Decide whether to expand or to check only. */
+
+ term = '\0';
+ cp = stesc + 1;
+ switch (*cp) {
+ case '*':
+ res = NULL;
+ break;
+ case 'B':
+ /* FALLTHROUGH */
+ case 'w':
+ term = cp[1];
+ /* FALLTHROUGH */
+ case 'n':
+ res = ubuf;
+ break;
+ default:
+ if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
+ mandoc_vmsg(MANDOCERR_ESC_BAD,
+ r->parse, ln, (int)(stesc - *bufp),
+ "%.*s", (int)(cp - stesc), stesc);
+ continue;
+ }
+
+ if (EXPAND_LIMIT < ++expand_count) {
+ mandoc_msg(MANDOCERR_ROFFLOOP, r->parse,
+ ln, (int)(stesc - *bufp), NULL);
+ return(ROFF_IGN);
+ }
+
+ /*
+ * The third character decides the length
+ * of the name of the string or register.
+ * Save a pointer to the name.
+ */
+
+ if ('\0' == term) {
+ switch (*++cp) {
+ case '\0':
+ maxl = 0;
+ break;
+ case '(':
+ cp++;
+ maxl = 2;
+ break;
+ case '[':
+ cp++;
+ term = ']';
+ maxl = 0;
+ break;
+ default:
+ maxl = 1;
+ break;
+ }
+ } else {
+ cp += 2;
+ maxl = 0;
+ }
+ stnam = cp;
+
+ /* Advance to the end of the name. */
+
+ arg_complete = 1;
+ for (naml = 0; 0 == maxl || naml < maxl; naml++, cp++) {
+ if ('\0' == *cp) {
+ mandoc_msg(MANDOCERR_ESC_BAD, r->parse,
+ ln, (int)(stesc - *bufp), stesc);
+ arg_complete = 0;
+ break;
+ }
+ if (0 == maxl && *cp == term) {
+ cp++;
+ break;
+ }
+ }
+
+ /*
+ * Retrieve the replacement string; if it is
+ * undefined, resume searching for escapes.
+ */
+
+ switch (stesc[1]) {
+ case '*':
+ if (arg_complete)
+ res = roff_getstrn(r, stnam, naml);
+ break;
+ case 'B':
+ npos = 0;
+ ubuf[0] = arg_complete &&
+ roff_evalnum(stnam, &npos, NULL, 0) &&
+ stnam + npos + 1 == cp ? '1' : '0';
+ ubuf[1] = '\0';
+ break;
+ case 'n':
+ if (arg_complete)
+ (void)snprintf(ubuf, sizeof(ubuf), "%d",
+ roff_getregn(r, stnam, naml));
+ else
+ ubuf[0] = '\0';
+ break;
+ case 'w':
+ /* use even incomplete args */
+ (void)snprintf(ubuf, sizeof(ubuf), "%d",
+ 24 * (int)naml);
+ break;
+ }
+
+ if (NULL == res) {
+ mandoc_vmsg(MANDOCERR_STR_UNDEF,
+ r->parse, ln, (int)(stesc - *bufp),
+ "%.*s", (int)naml, stnam);
+ res = "";
+ }
+
+ /* Replace the escape sequence by the string. */
+
+ *stesc = '\0';
+ *szp = mandoc_asprintf(&nbuf, "%s%s%s",
+ *bufp, res, cp) + 1;
+
+ /* Prepare for the next replacement. */
+
+ start = nbuf + pos;
+ stesc = nbuf + (stesc - *bufp) + strlen(res);
+ free(*bufp);
+ *bufp = nbuf;
+ }
+ return(ROFF_CONT);
+}
+
+/*
+ * Process text streams:
+ * Convert all breakable hyphens into ASCII_HYPH.
+ * Decrement and spring input line trap.
+ */
+static enum rofferr
+roff_parsetext(char **bufp, size_t *szp, int pos, int *offs)
+{
+ size_t sz;
+ const char *start;
+ char *p;
+ int isz;
+ enum mandoc_esc esc;
+
+ start = p = *bufp + pos;
+
+ while ('\0' != *p) {
+ sz = strcspn(p, "-\\");
+ p += sz;
+
+ if ('\0' == *p)
+ break;
+
+ if ('\\' == *p) {
+ /* Skip over escapes. */
+ p++;
+ esc = mandoc_escape((const char **)&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc)
+ break;
+ continue;
+ } else if (p == start) {
+ p++;
+ continue;
+ }
+
+ if (isalpha((unsigned char)p[-1]) &&
+ isalpha((unsigned char)p[1]))
+ *p = ASCII_HYPH;
+ p++;
+ }
+
+ /* Spring the input line trap. */
+ if (1 == roffit_lines) {
+ isz = mandoc_asprintf(&p, "%s\n.%s", *bufp, roffit_macro);
+ free(*bufp);
+ *bufp = p;
+ *szp = isz + 1;
+ *offs = 0;
+ free(roffit_macro);
+ roffit_lines = 0;
+ return(ROFF_REPARSE);
+ } else if (1 < roffit_lines)
+ --roffit_lines;
+ return(ROFF_CONT);
+}
+
+enum rofferr
+roff_parseln(struct roff *r, int ln, char **bufp,
+ size_t *szp, int pos, int *offs)
+{
+ enum rofft t;
+ enum rofferr e;
+ int ppos, ctl;
+
+ /*
+ * Run the reserved-word filter only if we have some reserved
+ * words to fill in.
+ */
+
+ e = roff_res(r, bufp, szp, ln, pos);
+ if (ROFF_IGN == e)
+ return(e);
+ assert(ROFF_CONT == e);
+
+ ppos = pos;
+ ctl = roff_getcontrol(r, *bufp, &pos);
+
+ /*
+ * First, if a scope is open and we're not a macro, pass the
+ * text through the macro's filter. If a scope isn't open and
+ * we're not a macro, just let it through.
+ * Finally, if there's an equation scope open, divert it into it
+ * no matter our state.
+ */
+
+ if (r->last && ! ctl) {
+ t = r->last->tok;
+ assert(roffs[t].text);
+ e = (*roffs[t].text)(r, t, bufp, szp, ln, pos, pos, offs);
+ assert(ROFF_IGN == e || ROFF_CONT == e);
+ if (ROFF_CONT != e)
+ return(e);
+ }
+ if (r->eqn)
+ return(eqn_read(&r->eqn, ln, *bufp, ppos, offs));
+ if ( ! ctl) {
+ if (r->tbl)
+ return(tbl_read(r->tbl, ln, *bufp, pos));
+ return(roff_parsetext(bufp, szp, pos, offs));
+ }
+
+ /*
+ * If a scope is open, go to the child handler for that macro,
+ * as it may want to preprocess before doing anything with it.
+ * Don't do so if an equation is open.
+ */
+
+ if (r->last) {
+ t = r->last->tok;
+ assert(roffs[t].sub);
+ return((*roffs[t].sub)(r, t, bufp, szp,
+ ln, ppos, pos, offs));
+ }
+
+ /*
+ * Lastly, as we've no scope open, try to look up and execute
+ * the new macro. If no macro is found, simply return and let
+ * the compilers handle it.
+ */
+
+ if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos, ln, ppos)))
+ return(ROFF_CONT);
+
+ assert(roffs[t].proc);
+ return((*roffs[t].proc)(r, t, bufp, szp, ln, ppos, pos, offs));
+}
+
+void
+roff_endparse(struct roff *r)
+{
+
+ if (r->last)
+ mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+ r->last->line, r->last->col,
+ roffs[r->last->tok].name);
+
+ if (r->eqn) {
+ mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+ r->eqn->eqn.ln, r->eqn->eqn.pos, "EQ");
+ eqn_end(&r->eqn);
+ }
+
+ if (r->tbl) {
+ mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+ r->tbl->line, r->tbl->pos, "TS");
+ tbl_end(&r->tbl);
+ }
+}
+
+/*
+ * Parse a roff node's type from the input buffer. This must be in the
+ * form of ".foo xxx" in the usual way.
+ */
+static enum rofft
+roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos)
+{
+ char *cp;
+ const char *mac;
+ size_t maclen;
+ enum rofft t;
+
+ cp = buf + *pos;
+
+ if ('\0' == *cp || '"' == *cp || '\t' == *cp || ' ' == *cp)
+ return(ROFF_MAX);
+
+ mac = cp;
+ maclen = roff_getname(r, &cp, ln, ppos);
+
+ t = (r->current_string = roff_getstrn(r, mac, maclen))
+ ? ROFF_USERDEF : roffhash_find(mac, maclen);
+
+ if (ROFF_MAX != t)
+ *pos = cp - buf;
+
+ return(t);
+}
+
+static enum rofferr
+roff_cblock(ROFF_ARGS)
+{
+
+ /*
+ * A block-close `..' should only be invoked as a child of an
+ * ignore macro, otherwise raise a warning and just ignore it.
+ */
+
+ if (NULL == r->last) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "..");
+ return(ROFF_IGN);
+ }
+
+ switch (r->last->tok) {
+ case ROFF_am:
+ /* ROFF_am1 is remapped to ROFF_am in roff_block(). */
+ /* FALLTHROUGH */
+ case ROFF_ami:
+ /* FALLTHROUGH */
+ case ROFF_de:
+ /* ROFF_de1 is remapped to ROFF_de in roff_block(). */
+ /* FALLTHROUGH */
+ case ROFF_dei:
+ /* FALLTHROUGH */
+ case ROFF_ig:
+ break;
+ default:
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "..");
+ return(ROFF_IGN);
+ }
+
+ if ((*bufp)[pos])
+ mandoc_vmsg(MANDOCERR_ARG_SKIP, r->parse, ln, pos,
+ ".. %s", *bufp + pos);
+
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+ return(ROFF_IGN);
+
+}
+
+static void
+roffnode_cleanscope(struct roff *r)
+{
+
+ while (r->last) {
+ if (--r->last->endspan != 0)
+ break;
+ roffnode_pop(r);
+ }
+}
+
+static void
+roff_ccond(struct roff *r, int ln, int ppos)
+{
+
+ if (NULL == r->last) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "\\}");
+ return;
+ }
+
+ switch (r->last->tok) {
+ case ROFF_el:
+ /* FALLTHROUGH */
+ case ROFF_ie:
+ /* FALLTHROUGH */
+ case ROFF_if:
+ break;
+ default:
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "\\}");
+ return;
+ }
+
+ if (r->last->endspan > -1) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "\\}");
+ return;
+ }
+
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+ return;
+}
+
+static enum rofferr
+roff_block(ROFF_ARGS)
+{
+ const char *name;
+ char *iname, *cp;
+ size_t namesz;
+
+ /* Ignore groff compatibility mode for now. */
+
+ if (ROFF_de1 == tok)
+ tok = ROFF_de;
+ else if (ROFF_am1 == tok)
+ tok = ROFF_am;
+
+ /* Parse the macro name argument. */
+
+ cp = *bufp + pos;
+ if (ROFF_ig == tok) {
+ iname = NULL;
+ namesz = 0;
+ } else {
+ iname = cp;
+ namesz = roff_getname(r, &cp, ln, ppos);
+ iname[namesz] = '\0';
+ }
+
+ /* Resolve the macro name argument if it is indirect. */
+
+ if (namesz && (ROFF_dei == tok || ROFF_ami == tok)) {
+ if (NULL == (name = roff_getstrn(r, iname, namesz))) {
+ mandoc_vmsg(MANDOCERR_STR_UNDEF,
+ r->parse, ln, (int)(iname - *bufp),
+ "%.*s", (int)namesz, iname);
+ namesz = 0;
+ } else
+ namesz = strlen(name);
+ } else
+ name = iname;
+
+ if (0 == namesz && ROFF_ig != tok) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY, r->parse,
+ ln, ppos, roffs[tok].name);
+ return(ROFF_IGN);
+ }
+
+ roffnode_push(r, tok, name, ln, ppos);
+
+ /*
+ * At the beginning of a `de' macro, clear the existing string
+ * with the same name, if there is one. New content will be
+ * appended from roff_block_text() in multiline mode.
+ */
+
+ if (ROFF_de == tok || ROFF_dei == tok)
+ roff_setstrn(&r->strtab, name, namesz, "", 0, 0);
+
+ if ('\0' == *cp)
+ return(ROFF_IGN);
+
+ /* Get the custom end marker. */
+
+ iname = cp;
+ namesz = roff_getname(r, &cp, ln, ppos);
+
+ /* Resolve the end marker if it is indirect. */
+
+ if (namesz && (ROFF_dei == tok || ROFF_ami == tok)) {
+ if (NULL == (name = roff_getstrn(r, iname, namesz))) {
+ mandoc_vmsg(MANDOCERR_STR_UNDEF,
+ r->parse, ln, (int)(iname - *bufp),
+ "%.*s", (int)namesz, iname);
+ namesz = 0;
+ } else
+ namesz = strlen(name);
+ } else
+ name = iname;
+
+ if (namesz)
+ r->last->end = mandoc_strndup(name, namesz);
+
+ if ('\0' != *cp)
+ mandoc_vmsg(MANDOCERR_ARG_EXCESS, r->parse,
+ ln, pos, ".%s ... %s", roffs[tok].name, cp);
+
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_block_sub(ROFF_ARGS)
+{
+ enum rofft t;
+ int i, j;
+
+ /*
+ * First check whether a custom macro exists at this level. If
+ * it does, then check against it. This is some of groff's
+ * stranger behaviours. If we encountered a custom end-scope
+ * tag and that tag also happens to be a "real" macro, then we
+ * need to try interpreting it again as a real macro. If it's
+ * not, then return ignore. Else continue.
+ */
+
+ if (r->last->end) {
+ for (i = pos, j = 0; r->last->end[j]; j++, i++)
+ if ((*bufp)[i] != r->last->end[j])
+ break;
+
+ if ('\0' == r->last->end[j] &&
+ ('\0' == (*bufp)[i] ||
+ ' ' == (*bufp)[i] ||
+ '\t' == (*bufp)[i])) {
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+
+ while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
+ i++;
+
+ pos = i;
+ if (ROFF_MAX != roff_parse(r, *bufp, &pos, ln, ppos))
+ return(ROFF_RERUN);
+ return(ROFF_IGN);
+ }
+ }
+
+ /*
+ * If we have no custom end-query or lookup failed, then try
+ * pulling it out of the hashtable.
+ */
+
+ t = roff_parse(r, *bufp, &pos, ln, ppos);
+
+ if (ROFF_cblock != t) {
+ if (ROFF_ig != tok)
+ roff_setstr(r, r->last->name, *bufp + ppos, 2);
+ return(ROFF_IGN);
+ }
+
+ assert(roffs[t].proc);
+ return((*roffs[t].proc)(r, t, bufp, szp, ln, ppos, pos, offs));
+}
+
+static enum rofferr
+roff_block_text(ROFF_ARGS)
+{
+
+ if (ROFF_ig != tok)
+ roff_setstr(r, r->last->name, *bufp + pos, 2);
+
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_cond_sub(ROFF_ARGS)
+{
+ enum rofft t;
+ char *ep;
+ int rr;
+
+ rr = r->last->rule;
+ roffnode_cleanscope(r);
+ t = roff_parse(r, *bufp, &pos, ln, ppos);
+
+ /*
+ * Fully handle known macros when they are structurally
+ * required or when the conditional evaluated to true.
+ */
+
+ if ((ROFF_MAX != t) &&
+ (rr || ROFFMAC_STRUCT & roffs[t].flags)) {
+ assert(roffs[t].proc);
+ return((*roffs[t].proc)(r, t, bufp, szp,
+ ln, ppos, pos, offs));
+ }
+
+ /*
+ * If `\}' occurs on a macro line without a preceding macro,
+ * drop the line completely.
+ */
+
+ ep = *bufp + pos;
+ if ('\\' == ep[0] && '}' == ep[1])
+ rr = 0;
+
+ /* Always check for the closing delimiter `\}'. */
+
+ while (NULL != (ep = strchr(ep, '\\'))) {
+ if ('}' == *(++ep)) {
+ *ep = '&';
+ roff_ccond(r, ln, ep - *bufp - 1);
+ }
+ ++ep;
+ }
+ return(rr ? ROFF_CONT : ROFF_IGN);
+}
+
+static enum rofferr
+roff_cond_text(ROFF_ARGS)
+{
+ char *ep;
+ int rr;
+
+ rr = r->last->rule;
+ roffnode_cleanscope(r);
+
+ ep = *bufp + pos;
+ while (NULL != (ep = strchr(ep, '\\'))) {
+ if ('}' == *(++ep)) {
+ *ep = '&';
+ roff_ccond(r, ln, ep - *bufp - 1);
+ }
+ ++ep;
+ }
+ return(rr ? ROFF_CONT : ROFF_IGN);
+}
+
+/*
+ * Parse a single signed integer number. Stop at the first non-digit.
+ * If there is at least one digit, return success and advance the
+ * parse point, else return failure and let the parse point unchanged.
+ * Ignore overflows, treat them just like the C language.
+ */
+static int
+roff_getnum(const char *v, int *pos, int *res)
+{
+ int myres, n, p;
+
+ if (NULL == res)
+ res = &myres;
+
+ p = *pos;
+ n = v[p] == '-';
+ if (n)
+ p++;
+
+ for (*res = 0; isdigit((unsigned char)v[p]); p++)
+ *res = 10 * *res + v[p] - '0';
+ if (p == *pos + n)
+ return 0;
+
+ if (n)
+ *res = -*res;
+
+ *pos = p;
+ return 1;
+}
+
+/*
+ * Evaluate a string comparison condition.
+ * The first character is the delimiter.
+ * Succeed if the string up to its second occurrence
+ * matches the string up to its third occurence.
+ * Advance the cursor after the third occurrence
+ * or lacking that, to the end of the line.
+ */
+static int
+roff_evalstrcond(const char *v, int *pos)
+{
+ const char *s1, *s2, *s3;
+ int match;
+
+ match = 0;
+ s1 = v + *pos; /* initial delimiter */
+ s2 = s1 + 1; /* for scanning the first string */
+ s3 = strchr(s2, *s1); /* for scanning the second string */
+
+ if (NULL == s3) /* found no middle delimiter */
+ goto out;
+
+ while ('\0' != *++s3) {
+ if (*s2 != *s3) { /* mismatch */
+ s3 = strchr(s3, *s1);
+ break;
+ }
+ if (*s3 == *s1) { /* found the final delimiter */
+ match = 1;
+ break;
+ }
+ s2++;
+ }
+
+out:
+ if (NULL == s3)
+ s3 = strchr(s2, '\0');
+ else
+ s3++;
+ *pos = s3 - v;
+ return(match);
+}
+
+/*
+ * Evaluate an optionally negated single character, numerical,
+ * or string condition.
+ */
+static int
+roff_evalcond(const char *v, int *pos)
+{
+ int wanttrue, number;
+
+ if ('!' == v[*pos]) {
+ wanttrue = 0;
+ (*pos)++;
+ } else
+ wanttrue = 1;
+
+ switch (v[*pos]) {
+ case 'n':
+ /* FALLTHROUGH */
+ case 'o':
+ (*pos)++;
+ return(wanttrue);
+ case 'c':
+ /* FALLTHROUGH */
+ case 'd':
+ /* FALLTHROUGH */
+ case 'e':
+ /* FALLTHROUGH */
+ case 'r':
+ /* FALLTHROUGH */
+ case 't':
+ (*pos)++;
+ return(!wanttrue);
+ default:
+ break;
+ }
+
+ if (roff_evalnum(v, pos, &number, 0))
+ return((number > 0) == wanttrue);
+ else
+ return(roff_evalstrcond(v, pos) == wanttrue);
+}
+
+static enum rofferr
+roff_line_ignore(ROFF_ARGS)
+{
+
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_cond(ROFF_ARGS)
+{
+
+ roffnode_push(r, tok, NULL, ln, ppos);
+
+ /*
+ * An `.el' has no conditional body: it will consume the value
+ * of the current rstack entry set in prior `ie' calls or
+ * defaults to DENY.
+ *
+ * If we're not an `el', however, then evaluate the conditional.
+ */
+
+ r->last->rule = ROFF_el == tok ?
+ (r->rstackpos < 0 ? 0 : r->rstack[r->rstackpos--]) :
+ roff_evalcond(*bufp, &pos);
+
+ /*
+ * An if-else will put the NEGATION of the current evaluated
+ * conditional into the stack of rules.
+ */
+
+ if (ROFF_ie == tok) {
+ if (r->rstackpos + 1 == r->rstacksz) {
+ r->rstacksz += 16;
+ r->rstack = mandoc_reallocarray(r->rstack,
+ r->rstacksz, sizeof(int));
+ }
+ r->rstack[++r->rstackpos] = !r->last->rule;
+ }
+
+ /* If the parent has false as its rule, then so do we. */
+
+ if (r->last->parent && !r->last->parent->rule)
+ r->last->rule = 0;
+
+ /*
+ * Determine scope.
+ * If there is nothing on the line after the conditional,
+ * not even whitespace, use next-line scope.
+ */
+
+ if ('\0' == (*bufp)[pos]) {
+ r->last->endspan = 2;
+ goto out;
+ }
+
+ while (' ' == (*bufp)[pos])
+ pos++;
+
+ /* An opening brace requests multiline scope. */
+
+ if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) {
+ r->last->endspan = -1;
+ pos += 2;
+ goto out;
+ }
+
+ /*
+ * Anything else following the conditional causes
+ * single-line scope. Warn if the scope contains
+ * nothing but trailing whitespace.
+ */
+
+ if ('\0' == (*bufp)[pos])
+ mandoc_msg(MANDOCERR_COND_EMPTY, r->parse,
+ ln, ppos, roffs[tok].name);
+
+ r->last->endspan = 1;
+
+out:
+ *offs = pos;
+ return(ROFF_RERUN);
+}
+
+static enum rofferr
+roff_ds(ROFF_ARGS)
+{
+ char *string;
+ const char *name;
+ size_t namesz;
+
+ /*
+ * The first word is the name of the string.
+ * If it is empty or terminated by an escape sequence,
+ * abort the `ds' request without defining anything.
+ */
+
+ name = string = *bufp + pos;
+ if ('\0' == *name)
+ return(ROFF_IGN);
+
+ namesz = roff_getname(r, &string, ln, pos);
+ if ('\\' == name[namesz])
+ return(ROFF_IGN);
+
+ /* Read past the initial double-quote, if any. */
+ if ('"' == *string)
+ string++;
+
+ /* The rest is the value. */
+ roff_setstrn(&r->strtab, name, namesz, string, strlen(string),
+ ROFF_as == tok);
+ return(ROFF_IGN);
+}
+
+/*
+ * Parse a single operator, one or two characters long.
+ * If the operator is recognized, return success and advance the
+ * parse point, else return failure and let the parse point unchanged.
+ */
+static int
+roff_getop(const char *v, int *pos, char *res)
+{
+
+ *res = v[*pos];
+
+ switch (*res) {
+ case '+':
+ /* FALLTHROUGH */
+ case '-':
+ /* FALLTHROUGH */
+ case '*':
+ /* FALLTHROUGH */
+ case '/':
+ /* FALLTHROUGH */
+ case '%':
+ /* FALLTHROUGH */
+ case '&':
+ /* FALLTHROUGH */
+ case ':':
+ break;
+ case '<':
+ switch (v[*pos + 1]) {
+ case '=':
+ *res = 'l';
+ (*pos)++;
+ break;
+ case '>':
+ *res = '!';
+ (*pos)++;
+ break;
+ case '?':
+ *res = 'i';
+ (*pos)++;
+ break;
+ default:
+ break;
+ }
+ break;
+ case '>':
+ switch (v[*pos + 1]) {
+ case '=':
+ *res = 'g';
+ (*pos)++;
+ break;
+ case '?':
+ *res = 'a';
+ (*pos)++;
+ break;
+ default:
+ break;
+ }
+ break;
+ case '=':
+ if ('=' == v[*pos + 1])
+ (*pos)++;
+ break;
+ default:
+ return(0);
+ }
+ (*pos)++;
+
+ return(*res);
+}
+
+/*
+ * Evaluate either a parenthesized numeric expression
+ * or a single signed integer number.
+ */
+static int
+roff_evalpar(const char *v, int *pos, int *res)
+{
+
+ if ('(' != v[*pos])
+ return(roff_getnum(v, pos, res));
+
+ (*pos)++;
+ if ( ! roff_evalnum(v, pos, res, 1))
+ return(0);
+
+ /*
+ * Omission of the closing parenthesis
+ * is an error in validation mode,
+ * but ignored in evaluation mode.
+ */
+
+ if (')' == v[*pos])
+ (*pos)++;
+ else if (NULL == res)
+ return(0);
+
+ return(1);
+}
+
+/*
+ * Evaluate a complete numeric expression.
+ * Proceed left to right, there is no concept of precedence.
+ */
+static int
+roff_evalnum(const char *v, int *pos, int *res, int skipwhite)
+{
+ int mypos, operand2;
+ char operator;
+
+ if (NULL == pos) {
+ mypos = 0;
+ pos = &mypos;
+ }
+
+ if (skipwhite)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_evalpar(v, pos, res))
+ return(0);
+
+ while (1) {
+ if (skipwhite)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_getop(v, pos, &operator))
+ break;
+
+ if (skipwhite)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_evalpar(v, pos, &operand2))
+ return(0);
+
+ if (skipwhite)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if (NULL == res)
+ continue;
+
+ switch (operator) {
+ case '+':
+ *res += operand2;
+ break;
+ case '-':
+ *res -= operand2;
+ break;
+ case '*':
+ *res *= operand2;
+ break;
+ case '/':
+ *res /= operand2;
+ break;
+ case '%':
+ *res %= operand2;
+ break;
+ case '<':
+ *res = *res < operand2;
+ break;
+ case '>':
+ *res = *res > operand2;
+ break;
+ case 'l':
+ *res = *res <= operand2;
+ break;
+ case 'g':
+ *res = *res >= operand2;
+ break;
+ case '=':
+ *res = *res == operand2;
+ break;
+ case '!':
+ *res = *res != operand2;
+ break;
+ case '&':
+ *res = *res && operand2;
+ break;
+ case ':':
+ *res = *res || operand2;
+ break;
+ case 'i':
+ if (operand2 < *res)
+ *res = operand2;
+ break;
+ case 'a':
+ if (operand2 > *res)
+ *res = operand2;
+ break;
+ default:
+ abort();
+ }
+ }
+ return(1);
+}
+
+void
+roff_setreg(struct roff *r, const char *name, int val, char sign)
+{
+ struct roffreg *reg;
+
+ /* Search for an existing register with the same name. */
+ reg = r->regtab;
+
+ while (reg && strcmp(name, reg->key.p))
+ reg = reg->next;
+
+ if (NULL == reg) {
+ /* Create a new register. */
+ reg = mandoc_malloc(sizeof(struct roffreg));
+ reg->key.p = mandoc_strdup(name);
+ reg->key.sz = strlen(name);
+ reg->val = 0;
+ reg->next = r->regtab;
+ r->regtab = reg;
+ }
+
+ if ('+' == sign)
+ reg->val += val;
+ else if ('-' == sign)
+ reg->val -= val;
+ else
+ reg->val = val;
+}
+
+/*
+ * Handle some predefined read-only number registers.
+ * For now, return -1 if the requested register is not predefined;
+ * in case a predefined read-only register having the value -1
+ * were to turn up, another special value would have to be chosen.
+ */
+static int
+roff_getregro(const char *name)
+{
+
+ switch (*name) {
+ case 'A': /* ASCII approximation mode is always off. */
+ return(0);
+ case 'g': /* Groff compatibility mode is always on. */
+ return(1);
+ case 'H': /* Fixed horizontal resolution. */
+ return (24);
+ case 'j': /* Always adjust left margin only. */
+ return(0);
+ case 'T': /* Some output device is always defined. */
+ return(1);
+ case 'V': /* Fixed vertical resolution. */
+ return (40);
+ default:
+ return (-1);
+ }
+}
+
+int
+roff_getreg(const struct roff *r, const char *name)
+{
+ struct roffreg *reg;
+ int val;
+
+ if ('.' == name[0] && '\0' != name[1] && '\0' == name[2]) {
+ val = roff_getregro(name + 1);
+ if (-1 != val)
+ return (val);
+ }
+
+ for (reg = r->regtab; reg; reg = reg->next)
+ if (0 == strcmp(name, reg->key.p))
+ return(reg->val);
+
+ return(0);
+}
+
+static int
+roff_getregn(const struct roff *r, const char *name, size_t len)
+{
+ struct roffreg *reg;
+ int val;
+
+ if ('.' == name[0] && 2 == len) {
+ val = roff_getregro(name + 1);
+ if (-1 != val)
+ return (val);
+ }
+
+ for (reg = r->regtab; reg; reg = reg->next)
+ if (len == reg->key.sz &&
+ 0 == strncmp(name, reg->key.p, len))
+ return(reg->val);
+
+ return(0);
+}
+
+static void
+roff_freereg(struct roffreg *reg)
+{
+ struct roffreg *old_reg;
+
+ while (NULL != reg) {
+ free(reg->key.p);
+ old_reg = reg;
+ reg = reg->next;
+ free(old_reg);
+ }
+}
+
+static enum rofferr
+roff_nr(ROFF_ARGS)
+{
+ char *key, *val;
+ size_t keysz;
+ int iv;
+ char sign;
+
+ key = val = *bufp + pos;
+ if ('\0' == *key)
+ return(ROFF_IGN);
+
+ keysz = roff_getname(r, &val, ln, pos);
+ if ('\\' == key[keysz])
+ return(ROFF_IGN);
+ key[keysz] = '\0';
+
+ sign = *val;
+ if ('+' == sign || '-' == sign)
+ val++;
+
+ if (roff_evalnum(val, NULL, &iv, 0))
+ roff_setreg(r, key, iv, sign);
+
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_rr(ROFF_ARGS)
+{
+ struct roffreg *reg, **prev;
+ char *name, *cp;
+ size_t namesz;
+
+ name = cp = *bufp + pos;
+ if ('\0' == *name)
+ return(ROFF_IGN);
+ namesz = roff_getname(r, &cp, ln, pos);
+ name[namesz] = '\0';
+
+ prev = &r->regtab;
+ while (1) {
+ reg = *prev;
+ if (NULL == reg || !strcmp(name, reg->key.p))
+ break;
+ prev = &reg->next;
+ }
+ if (NULL != reg) {
+ *prev = reg->next;
+ free(reg->key.p);
+ free(reg);
+ }
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_rm(ROFF_ARGS)
+{
+ const char *name;
+ char *cp;
+ size_t namesz;
+
+ cp = *bufp + pos;
+ while ('\0' != *cp) {
+ name = cp;
+ namesz = roff_getname(r, &cp, ln, (int)(cp - *bufp));
+ roff_setstrn(&r->strtab, name, namesz, NULL, 0, 0);
+ if ('\\' == name[namesz])
+ break;
+ }
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_it(ROFF_ARGS)
+{
+ char *cp;
+ size_t len;
+ int iv;
+
+ /* Parse the number of lines. */
+ cp = *bufp + pos;
+ len = strcspn(cp, " \t");
+ cp[len] = '\0';
+ if ((iv = mandoc_strntoi(cp, len, 10)) <= 0) {
+ mandoc_msg(MANDOCERR_IT_NONUM, r->parse,
+ ln, ppos, *bufp + 1);
+ return(ROFF_IGN);
+ }
+ cp += len + 1;
+
+ /* Arm the input line trap. */
+ roffit_lines = iv;
+ roffit_macro = mandoc_strdup(cp);
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_Dd(ROFF_ARGS)
+{
+ const char *const *cp;
+
+ if (0 == ((MPARSE_MDOC | MPARSE_QUICK) & r->options))
+ for (cp = __mdoc_reserved; *cp; cp++)
+ roff_setstr(r, *cp, NULL, 0);
+
+ return(ROFF_CONT);
+}
+
+static enum rofferr
+roff_TH(ROFF_ARGS)
+{
+ const char *const *cp;
+
+ if (0 == (MPARSE_QUICK & r->options))
+ for (cp = __man_reserved; *cp; cp++)
+ roff_setstr(r, *cp, NULL, 0);
+
+ return(ROFF_CONT);
+}
+
+static enum rofferr
+roff_TE(ROFF_ARGS)
+{
+
+ if (NULL == r->tbl)
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "TE");
+ else
+ tbl_end(&r->tbl);
+
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_T_(ROFF_ARGS)
+{
+
+ if (NULL == r->tbl)
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "T&");
+ else
+ tbl_restart(ppos, ln, r->tbl);
+
+ return(ROFF_IGN);
+}
+
+#if 0
+static int
+roff_closeeqn(struct roff *r)
+{
+
+ return(r->eqn && ROFF_EQN == eqn_end(&r->eqn) ? 1 : 0);
+}
+#endif
+
+static void
+roff_openeqn(struct roff *r, const char *name, int line,
+ int offs, const char *buf)
+{
+ struct eqn_node *e;
+ int poff;
+
+ assert(NULL == r->eqn);
+ e = eqn_alloc(name, offs, line, r->parse);
+
+ if (r->last_eqn)
+ r->last_eqn->next = e;
+ else
+ r->first_eqn = r->last_eqn = e;
+
+ r->eqn = r->last_eqn = e;
+
+ if (buf) {
+ poff = 0;
+ eqn_read(&r->eqn, line, buf, offs, &poff);
+ }
+}
+
+static enum rofferr
+roff_EQ(ROFF_ARGS)
+{
+
+ roff_openeqn(r, *bufp + pos, ln, ppos, NULL);
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_EN(ROFF_ARGS)
+{
+
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse, ln, ppos, "EN");
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_TS(ROFF_ARGS)
+{
+ struct tbl_node *tbl;
+
+ if (r->tbl) {
+ mandoc_msg(MANDOCERR_BLK_BROKEN, r->parse,
+ ln, ppos, "TS breaks TS");
+ tbl_end(&r->tbl);
+ }
+
+ tbl = tbl_alloc(ppos, ln, r->parse);
+
+ if (r->last_tbl)
+ r->last_tbl->next = tbl;
+ else
+ r->first_tbl = r->last_tbl = tbl;
+
+ r->tbl = r->last_tbl = tbl;
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_cc(ROFF_ARGS)
+{
+ const char *p;
+
+ p = *bufp + pos;
+
+ if ('\0' == *p || '.' == (r->control = *p++))
+ r->control = 0;
+
+ if ('\0' != *p)
+ mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL);
+
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_tr(ROFF_ARGS)
+{
+ const char *p, *first, *second;
+ size_t fsz, ssz;
+ enum mandoc_esc esc;
+
+ p = *bufp + pos;
+
+ if ('\0' == *p) {
+ mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL);
+ return(ROFF_IGN);
+ }
+
+ while ('\0' != *p) {
+ fsz = ssz = 1;
+
+ first = p++;
+ if ('\\' == *first) {
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc) {
+ mandoc_msg(MANDOCERR_ESC_BAD, r->parse,
+ ln, (int)(p - *bufp), first);
+ return(ROFF_IGN);
+ }
+ fsz = (size_t)(p - first);
+ }
+
+ second = p++;
+ if ('\\' == *second) {
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc) {
+ mandoc_msg(MANDOCERR_ESC_BAD, r->parse,
+ ln, (int)(p - *bufp), second);
+ return(ROFF_IGN);
+ }
+ ssz = (size_t)(p - second);
+ } else if ('\0' == *second) {
+ mandoc_msg(MANDOCERR_ARGCOUNT, r->parse,
+ ln, (int)(p - *bufp), NULL);
+ second = " ";
+ p--;
+ }
+
+ if (fsz > 1) {
+ roff_setstrn(&r->xmbtab, first, fsz,
+ second, ssz, 0);
+ continue;
+ }
+
+ if (NULL == r->xtab)
+ r->xtab = mandoc_calloc(128,
+ sizeof(struct roffstr));
+
+ free(r->xtab[(int)*first].p);
+ r->xtab[(int)*first].p = mandoc_strndup(second, ssz);
+ r->xtab[(int)*first].sz = ssz;
+ }
+
+ return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_so(ROFF_ARGS)
+{
+ char *name;
+
+ name = *bufp + pos;
+ mandoc_vmsg(MANDOCERR_SO, r->parse, ln, ppos, "so %s", name);
+
+ /*
+ * Handle `so'. Be EXTREMELY careful, as we shouldn't be
+ * opening anything that's not in our cwd or anything beneath
+ * it. Thus, explicitly disallow traversing up the file-system
+ * or using absolute paths.
+ */
+
+ if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) {
+ mandoc_vmsg(MANDOCERR_SO_PATH, r->parse, ln, ppos,
+ ".so %s", name);
+ return(ROFF_ERR);
+ }
+
+ *offs = pos;
+ return(ROFF_SO);
+}
+
+static enum rofferr
+roff_userdef(ROFF_ARGS)
+{
+ const char *arg[9];
+ char *cp, *n1, *n2;
+ int i;
+
+ /*
+ * Collect pointers to macro argument strings
+ * and NUL-terminate them.
+ */
+ cp = *bufp + pos;
+ for (i = 0; i < 9; i++)
+ arg[i] = '\0' == *cp ? "" :
+ mandoc_getarg(r->parse, &cp, ln, &pos);
+
+ /*
+ * Expand macro arguments.
+ */
+ *szp = 0;
+ n1 = cp = mandoc_strdup(r->current_string);
+ while (NULL != (cp = strstr(cp, "\\$"))) {
+ i = cp[2] - '1';
+ if (0 > i || 8 < i) {
+ /* Not an argument invocation. */
+ cp += 2;
+ continue;
+ }
+ *cp = '\0';
+ *szp = mandoc_asprintf(&n2, "%s%s%s",
+ n1, arg[i], cp + 3) + 1;
+ cp = n2 + (cp - n1);
+ free(n1);
+ n1 = n2;
+ }
+
+ /*
+ * Replace the macro invocation
+ * by the expanded macro.
+ */
+ free(*bufp);
+ *bufp = n1;
+ if (0 == *szp)
+ *szp = strlen(*bufp) + 1;
+
+ return(*szp > 1 && '\n' == (*bufp)[(int)*szp - 2] ?
+ ROFF_REPARSE : ROFF_APPEND);
+}
+
+static size_t
+roff_getname(struct roff *r, char **cpp, int ln, int pos)
+{
+ char *name, *cp;
+ size_t namesz;
+
+ name = *cpp;
+ if ('\0' == *name)
+ return(0);
+
+ /* Read until end of name and terminate it with NUL. */
+ for (cp = name; 1; cp++) {
+ if ('\0' == *cp || ' ' == *cp) {
+ namesz = cp - name;
+ break;
+ }
+ if ('\\' != *cp)
+ continue;
+ namesz = cp - name;
+ if ('{' == cp[1] || '}' == cp[1])
+ break;
+ cp++;
+ if ('\\' == *cp)
+ continue;
+ mandoc_vmsg(MANDOCERR_NAMESC, r->parse, ln, pos,
+ "%.*s", (int)(cp - name + 1), name);
+ mandoc_escape((const char **)&cp, NULL, NULL);
+ break;
+ }
+
+ /* Read past spaces. */
+ while (' ' == *cp)
+ cp++;
+
+ *cpp = cp;
+ return(namesz);
+}
+
+/*
+ * Store *string into the user-defined string called *name.
+ * To clear an existing entry, call with (*r, *name, NULL, 0).
+ * append == 0: replace mode
+ * append == 1: single-line append mode
+ * append == 2: multiline append mode, append '\n' after each call
+ */
+static void
+roff_setstr(struct roff *r, const char *name, const char *string,
+ int append)
+{
+
+ roff_setstrn(&r->strtab, name, strlen(name), string,
+ string ? strlen(string) : 0, append);
+}
+
+static void
+roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
+ const char *string, size_t stringsz, int append)
+{
+ struct roffkv *n;
+ char *c;
+ int i;
+ size_t oldch, newch;
+
+ /* Search for an existing string with the same name. */
+ n = *r;
+
+ while (n && (namesz != n->key.sz ||
+ strncmp(n->key.p, name, namesz)))
+ n = n->next;
+
+ if (NULL == n) {
+ /* Create a new string table entry. */
+ n = mandoc_malloc(sizeof(struct roffkv));
+ n->key.p = mandoc_strndup(name, namesz);
+ n->key.sz = namesz;
+ n->val.p = NULL;
+ n->val.sz = 0;
+ n->next = *r;
+ *r = n;
+ } else if (0 == append) {
+ free(n->val.p);
+ n->val.p = NULL;
+ n->val.sz = 0;
+ }
+
+ if (NULL == string)
+ return;
+
+ /*
+ * One additional byte for the '\n' in multiline mode,
+ * and one for the terminating '\0'.
+ */
+ newch = stringsz + (1 < append ? 2u : 1u);
+
+ if (NULL == n->val.p) {
+ n->val.p = mandoc_malloc(newch);
+ *n->val.p = '\0';
+ oldch = 0;
+ } else {
+ oldch = n->val.sz;
+ n->val.p = mandoc_realloc(n->val.p, oldch + newch);
+ }
+
+ /* Skip existing content in the destination buffer. */
+ c = n->val.p + (int)oldch;
+
+ /* Append new content to the destination buffer. */
+ i = 0;
+ while (i < (int)stringsz) {
+ /*
+ * Rudimentary roff copy mode:
+ * Handle escaped backslashes.
+ */
+ if ('\\' == string[i] && '\\' == string[i + 1])
+ i++;
+ *c++ = string[i++];
+ }
+
+ /* Append terminating bytes. */
+ if (1 < append)
+ *c++ = '\n';
+
+ *c = '\0';
+ n->val.sz = (int)(c - n->val.p);
+}
+
+static const char *
+roff_getstrn(const struct roff *r, const char *name, size_t len)
+{
+ const struct roffkv *n;
+ int i;
+
+ for (n = r->strtab; n; n = n->next)
+ if (0 == strncmp(name, n->key.p, len) &&
+ '\0' == n->key.p[(int)len])
+ return(n->val.p);
+
+ for (i = 0; i < PREDEFS_MAX; i++)
+ if (0 == strncmp(name, predefs[i].name, len) &&
+ '\0' == predefs[i].name[(int)len])
+ return(predefs[i].str);
+
+ return(NULL);
+}
+
+static void
+roff_freestr(struct roffkv *r)
+{
+ struct roffkv *n, *nn;
+
+ for (n = r; n; n = nn) {
+ free(n->key.p);
+ free(n->val.p);
+ nn = n->next;
+ free(n);
+ }
+}
+
+const struct tbl_span *
+roff_span(const struct roff *r)
+{
+
+ return(r->tbl ? tbl_span(r->tbl) : NULL);
+}
+
+const struct eqn *
+roff_eqn(const struct roff *r)
+{
+
+ return(r->last_eqn ? &r->last_eqn->eqn : NULL);
+}
+
+/*
+ * Duplicate an input string, making the appropriate character
+ * conversations (as stipulated by `tr') along the way.
+ * Returns a heap-allocated string with all the replacements made.
+ */
+char *
+roff_strdup(const struct roff *r, const char *p)
+{
+ const struct roffkv *cp;
+ char *res;
+ const char *pp;
+ size_t ssz, sz;
+ enum mandoc_esc esc;
+
+ if (NULL == r->xmbtab && NULL == r->xtab)
+ return(mandoc_strdup(p));
+ else if ('\0' == *p)
+ return(mandoc_strdup(""));
+
+ /*
+ * Step through each character looking for term matches
+ * (remember that a `tr' can be invoked with an escape, which is
+ * a glyph but the escape is multi-character).
+ * We only do this if the character hash has been initialised
+ * and the string is >0 length.
+ */
+
+ res = NULL;
+ ssz = 0;
+
+ while ('\0' != *p) {
+ if ('\\' != *p && r->xtab && r->xtab[(int)*p].p) {
+ sz = r->xtab[(int)*p].sz;
+ res = mandoc_realloc(res, ssz + sz + 1);
+ memcpy(res + ssz, r->xtab[(int)*p].p, sz);
+ ssz += sz;
+ p++;
+ continue;
+ } else if ('\\' != *p) {
+ res = mandoc_realloc(res, ssz + 2);
+ res[ssz++] = *p++;
+ continue;
+ }
+
+ /* Search for term matches. */
+ for (cp = r->xmbtab; cp; cp = cp->next)
+ if (0 == strncmp(p, cp->key.p, cp->key.sz))
+ break;
+
+ if (NULL != cp) {
+ /*
+ * A match has been found.
+ * Append the match to the array and move
+ * forward by its keysize.
+ */
+ res = mandoc_realloc(res,
+ ssz + cp->val.sz + 1);
+ memcpy(res + ssz, cp->val.p, cp->val.sz);
+ ssz += cp->val.sz;
+ p += (int)cp->key.sz;
+ continue;
+ }
+
+ /*
+ * Handle escapes carefully: we need to copy
+ * over just the escape itself, or else we might
+ * do replacements within the escape itself.
+ * Make sure to pass along the bogus string.
+ */
+ pp = p++;
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc) {
+ sz = strlen(pp);
+ res = mandoc_realloc(res, ssz + sz + 1);
+ memcpy(res + ssz, pp, sz);
+ break;
+ }
+ /*
+ * We bail out on bad escapes.
+ * No need to warn: we already did so when
+ * roff_res() was called.
+ */
+ sz = (int)(p - pp);
+ res = mandoc_realloc(res, ssz + sz + 1);
+ memcpy(res + ssz, pp, sz);
+ ssz += sz;
+ }
+
+ res[(int)ssz] = '\0';
+ return(res);
+}
+
+/*
+ * Find out whether a line is a macro line or not.
+ * If it is, adjust the current position and return one; if it isn't,
+ * return zero and don't change the current position.
+ * If the control character has been set with `.cc', then let that grain
+ * precedence.
+ * This is slighly contrary to groff, where using the non-breaking
+ * control character when `cc' has been invoked will cause the
+ * non-breaking macro contents to be printed verbatim.
+ */
+int
+roff_getcontrol(const struct roff *r, const char *cp, int *ppos)
+{
+ int pos;
+
+ pos = *ppos;
+
+ if (0 != r->control && cp[pos] == r->control)
+ pos++;
+ else if (0 != r->control)
+ return(0);
+ else if ('\\' == cp[pos] && '.' == cp[pos + 1])
+ pos += 2;
+ else if ('.' == cp[pos] || '\'' == cp[pos])
+ pos++;
+ else
+ return(0);
+
+ while (' ' == cp[pos] || '\t' == cp[pos])
+ pos++;
+
+ *ppos = pos;
+ return(1);
+}
diff --git a/contrib/mdocml/st.c b/contrib/mdocml/st.c
new file mode 100644
index 0000000..ab6f0f2
--- /dev/null
+++ b/contrib/mdocml/st.c
@@ -0,0 +1,36 @@
+/* $Id: st.c,v 1.10 2014/03/23 11:25:26 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "mdoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+ if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2st(const char *p)
+{
+
+#include "st.in"
+
+ return(NULL);
+}
diff --git a/contrib/mdocml/st.in b/contrib/mdocml/st.in
new file mode 100644
index 0000000..481e44e
--- /dev/null
+++ b/contrib/mdocml/st.in
@@ -0,0 +1,83 @@
+/* $Id: st.in,v 1.24 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This file defines the .St macro arguments. If you add a new
+ * standard, make sure that the left-and side corresponds to the .St
+ * argument (like .St -p1003.1) and the right-hand side corresponds to
+ * the formatted output string.
+ *
+ * Be sure to escape strings.
+ * The non-breaking blanks prevent ending an output line right before
+ * a number. Groff prevent line breaks at the same places.
+ *
+ * REMEMBER TO ADD NEW STANDARDS TO MDOC.7!
+ */
+
+LINE("-p1003.1-88", "IEEE Std 1003.1-1988 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1-90", "IEEE Std 1003.1-1990 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1-2001", "IEEE Std 1003.1-2001 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1-2004", "IEEE Std 1003.1-2004 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1-2008", "IEEE Std 1003.1-2008 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1-2013", "IEEE Std 1003.1-2008/Cor 1-2013 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1", "IEEE Std 1003.1 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1b", "IEEE Std 1003.1b (\\(lqPOSIX.1b\\(rq)")
+LINE("-p1003.1b-93", "IEEE Std 1003.1b-1993 (\\(lqPOSIX.1b\\(rq)")
+LINE("-p1003.1c-95", "IEEE Std 1003.1c-1995 (\\(lqPOSIX.1c\\(rq)")
+LINE("-p1003.1d-99", "IEEE Std 1003.1d-1999 (\\(lqPOSIX.1d\\(rq)")
+LINE("-p1003.1g-2000", "IEEE Std 1003.1g-2000 (\\(lqPOSIX.1g\\(rq)")
+LINE("-p1003.1i-95", "IEEE Std 1003.1i-1995 (\\(lqPOSIX.1i\\(rq)")
+LINE("-p1003.1j-2000", "IEEE Std 1003.1j-2000 (\\(lqPOSIX.1j\\(rq)")
+LINE("-p1003.1q-2000", "IEEE Std 1003.1q-2000 (\\(lqPOSIX.1q\\(rq)")
+LINE("-p1003.2", "IEEE Std 1003.2 (\\(lqPOSIX.2\\(rq)")
+LINE("-p1003.2-92", "IEEE Std 1003.2-1992 (\\(lqPOSIX.2\\(rq)")
+LINE("-p1003.2a-92", "IEEE Std 1003.2a-1992 (\\(lqPOSIX.2\\(rq)")
+LINE("-p1387.2", "IEEE Std 1387.2 (\\(lqPOSIX.7.2\\(rq)")
+LINE("-p1387.2-95", "IEEE Std 1387.2-1995 (\\(lqPOSIX.7.2\\(rq)")
+LINE("-isoC", "ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)")
+LINE("-isoC-90", "ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)")
+LINE("-isoC-amd1", "ISO/IEC 9899/AMD1:1995 (\\(lqISO\\~C90, Amendment 1\\(rq)")
+LINE("-isoC-tcor1", "ISO/IEC 9899/TCOR1:1994 (\\(lqISO\\~C90, Technical Corrigendum 1\\(rq)")
+LINE("-isoC-tcor2", "ISO/IEC 9899/TCOR2:1995 (\\(lqISO\\~C90, Technical Corrigendum 2\\(rq)")
+LINE("-isoC-99", "ISO/IEC 9899:1999 (\\(lqISO\\~C99\\(rq)")
+LINE("-isoC-2011", "ISO/IEC 9899:2011 (\\(lqISO\\~C11\\(rq)")
+LINE("-iso9945-1-90", "ISO/IEC 9945-1:1990 (\\(lqPOSIX.1\\(rq)")
+LINE("-iso9945-1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)")
+LINE("-iso9945-2-93", "ISO/IEC 9945-2:1993 (\\(lqPOSIX.2\\(rq)")
+LINE("-ansiC", "ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)")
+LINE("-ansiC-89", "ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)")
+LINE("-ansiC-99", "ANSI/ISO/IEC 9899-1999 (\\(lqANSI\\~C99\\(rq)")
+LINE("-ieee754", "IEEE Std 754-1985")
+LINE("-iso8802-3", "ISO 8802-3: 1989")
+LINE("-iso8601", "ISO 8601")
+LINE("-ieee1275-94", "IEEE Std 1275-1994 (\\(lqOpen Firmware\\(rq)")
+LINE("-xpg3", "X/Open Portability Guide Issue\\~3 (\\(lqXPG3\\(rq)")
+LINE("-xpg4", "X/Open Portability Guide Issue\\~4 (\\(lqXPG4\\(rq)")
+LINE("-xpg4.2", "X/Open Portability Guide Issue\\~4, Version\\~2 (\\(lqXPG4.2\\(rq)")
+LINE("-xpg4.3", "X/Open Portability Guide Issue\\~4, Version\\~3 (\\(lqXPG4.3\\(rq)")
+LINE("-xbd5", "X/Open Base Definitions Issue\\~5 (\\(lqXBD5\\(rq)")
+LINE("-xcu5", "X/Open Commands and Utilities Issue\\~5 (\\(lqXCU5\\(rq)")
+LINE("-xsh4.2", "X/Open System Interfaces and Headers Issue\\~4, Version\\~2 (\\(lqXSH4.2\\(rq)")
+LINE("-xsh5", "X/Open System Interfaces and Headers Issue\\~5 (\\(lqXSH5\\(rq)")
+LINE("-xns5", "X/Open Networking Services Issue\\~5 (\\(lqXNS5\\(rq)")
+LINE("-xns5.2", "X/Open Networking Services Issue\\~5.2 (\\(lqXNS5.2\\(rq)")
+LINE("-xns5.2d2.0", "X/Open Networking Services Issue\\~5.2 Draft\\~2.0 (\\(lqXNS5.2D2.0\\(rq)")
+LINE("-xcurses4.2", "X/Open Curses Issue\\~4, Version\\~2 (\\(lqXCURSES4.2\\(rq)")
+LINE("-susv2", "Version\\~2 of the Single UNIX Specification (\\(lqSUSv2\\(rq)")
+LINE("-susv3", "Version\\~3 of the Single UNIX Specification (\\(lqSUSv3\\(rq)")
+LINE("-svid4", "System\\~V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)")
diff --git a/contrib/mdocml/style.css b/contrib/mdocml/style.css
new file mode 100644
index 0000000..ee891f4
--- /dev/null
+++ b/contrib/mdocml/style.css
@@ -0,0 +1,144 @@
+/* $Id: style.css,v 1.25 2011/08/26 09:03:17 kristaps Exp $ */
+
+/*
+ * This is an example style-sheet provided for mandoc(1) and the -Thtml
+ * or -Txhtml output mode.
+ *
+ * It mimics the appearance of the traditional cvsweb output.
+ *
+ * See mdoc(7) and man(7) for macro explanations.
+ */
+
+html { max-width: 880px; margin-left: 1em; }
+body { font-size: smaller; font-family: Helvetica,Arial,sans-serif; }
+h1 { margin-bottom: 1ex; font-size: 110%; margin-left: -4ex; } /* Section header (Sh, SH). */
+h2 { margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; } /* Sub-section header (Ss, SS). */
+table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; } /* All tables. */
+td { vertical-align: top; } /* All table cells. */
+p { } /* Paragraph: Pp, Lp. */
+blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; } /* D1. */
+div.section { margin-bottom: 2ex; margin-left: 5ex; } /* Sections (Sh, SH). */
+div.subsection { } /* Sub-sections (Ss, SS). */
+table.synopsis { } /* SYNOPSIS section table. */
+
+/* Preamble structure. */
+
+table.foot { font-size: smaller; margin-top: 1em; border-top: 1px dotted #dddddd; } /* Document footer. */
+td.foot-date { width: 50%; } /* Document footer: date. */
+td.foot-os { width: 50%; text-align: right; } /* Document footer: OS/source. */
+table.head { font-size: smaller; margin-bottom: 1em; border-bottom: 1px dotted #dddddd; } /* Document header. */
+td.head-ltitle { width: 10%; } /* Document header: left-title. */
+td.head-vol { width: 80%; text-align: center; } /* Document header: volume. */
+td.head-rtitle { width: 10%; text-align: right; } /* Document header: right-title. */
+
+/* General font modes. */
+
+i { } /* Italic: BI, IB, I, (implicit). */
+.emph { font-style: italic; font-weight: normal; } /* Emphasis: Em, Bl -emphasis. */
+b { } /* Bold: SB, BI, IB, BR, RB, B, (implicit). */
+.symb { font-style: normal; font-weight: bold; } /* Symbolic: Sy, Ms, Bf -symbolic. */
+small { } /* Small: SB, SM. */
+.lit { font-style: normal; font-weight: normal; font-family: monospace; } /* Literal: Dl, Li, Ql, Bf -literal, Bl -literal, Bl -unfilled. */
+
+/* Block modes. */
+
+.display { } /* Top of all Bd, D1, Dl. */
+.list { } /* Top of all Bl. */
+
+/* Context-specific modes. */
+
+i.addr { font-weight: normal; } /* Address (Ad). */
+i.arg { font-weight: normal; } /* Command argument (Ar). */
+span.author { } /* Author name (An). */
+b.cmd { font-style: normal; } /* Command (Cm). */
+b.config { font-style: normal; } /* Config statement (Cd). */
+span.define { } /* Defines (Dv). */
+span.desc { } /* Nd. After em-dash. */
+b.diag { font-style: normal; } /* Diagnostic (Bl -diag). */
+span.env { } /* Environment variables (Ev). */
+span.errno { } /* Error string (Er). */
+i.farg { font-weight: normal; } /* Function argument (Fa, Fn). */
+i.file { font-weight: normal; } /* File (Pa). */
+b.flag { font-style: normal; } /* Flag (Fl, Cm). */
+b.fname { font-style: normal; } /* Function name (Fa, Fn, Rv). */
+i.ftype { font-weight: normal; } /* Function types (Ft, Fn). */
+b.includes { font-style: normal; } /* Header includes (In). */
+span.lib { } /* Library (Lb). */
+i.link-sec { font-weight: normal; } /* Section links (Sx). */
+b.macro { font-style: normal; } /* Macro-ish thing (Fd). */
+b.name { font-style: normal; } /* Name of utility (Nm). */
+span.opt { } /* Options (Op, Oo/Oc). */
+span.ref { } /* Citations (Rs). */
+span.ref-auth { } /* Reference author (%A). */
+i.ref-book { font-weight: normal; } /* Reference book (%B). */
+span.ref-city { } /* Reference city (%C). */
+span.ref-date { } /* Reference date (%D). */
+i.ref-issue { font-weight: normal; } /* Reference issuer/publisher (%I). */
+i.ref-jrnl { font-weight: normal; } /* Reference journal (%J). */
+span.ref-num { } /* Reference number (%N). */
+span.ref-opt { } /* Reference optionals (%O). */
+span.ref-page { } /* Reference page (%P). */
+span.ref-corp { } /* Reference corporate/foreign author (%Q). */
+span.ref-rep { } /* Reference report (%R). */
+span.ref-title { text-decoration: underline; } /* Reference title (%T). */
+span.ref-vol { } /* Reference volume (%V). */
+span.type { font-style: italic; font-weight: normal; } /* Variable types (Vt). */
+span.unix { } /* Unices (Ux, Ox, Nx, Fx, Bx, Bsx, Dx). */
+b.utility { font-style: normal; } /* Name of utility (Ex). */
+b.var { font-style: normal; } /* Variables (Rv). */
+
+a.link-ext { } /* Off-site link (Lk). */
+a.link-includes { } /* Include-file link (In). */
+a.link-mail { } /* Mailto links (Mt). */
+a.link-man { } /* Manual links (Xr). */
+a.link-ref { } /* Reference section links (%Q). */
+a.link-sec { } /* Section links (Sx). */
+
+/* Formatting for lists. See mdoc(7). */
+
+dl.list-diag { }
+dt.list-diag { }
+dd.list-diag { }
+
+dl.list-hang { }
+dt.list-hang { }
+dd.list-hang { }
+
+dl.list-inset { }
+dt.list-inset { }
+dd.list-inset { }
+
+dl.list-ohang { }
+dt.list-ohang { }
+dd.list-ohang { margin-left: 0ex; }
+
+dl.list-tag { }
+dt.list-tag { }
+dd.list-tag { }
+
+table.list-col { }
+tr.list-col { }
+td.list-col { }
+
+ul.list-bul { list-style-type: disc; padding-left: 1em; }
+li.list-bul { }
+
+ul.list-dash { list-style-type: none; padding-left: 0em; }
+li.list-dash:before { content: "\2014 "; }
+
+ul.list-hyph { list-style-type: none; padding-left: 0em; }
+li.list-hyph:before { content: "\2013 "; }
+
+ul.list-item { list-style-type: none; padding-left: 0em; }
+li.list-item { }
+
+ol.list-enum { padding-left: 2em; }
+li.list-enum { }
+
+/* Equation modes. See eqn(7). */
+
+span.eqn { }
+
+/* Table modes. See tbl(7). */
+
+table.tbl { }
diff --git a/contrib/mdocml/tbl.7 b/contrib/mdocml/tbl.7
new file mode 100644
index 0000000..d4c829b
--- /dev/null
+++ b/contrib/mdocml/tbl.7
@@ -0,0 +1,340 @@
+.\" $Id: tbl.7,v 1.18 2013/09/16 22:39:19 schwarze Exp $
+.\"
+.\" Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\"
+.\" 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 16 2013 $
+.Dt TBL 7
+.Os
+.Sh NAME
+.Nm tbl
+.Nd tbl language reference for mandoc
+.Sh DESCRIPTION
+The
+.Nm tbl
+language is a table-formatting language.
+It is used within
+.Xr mdoc 7
+and
+.Xr man 7
+.Ux
+manual pages.
+This manual describes the subset of the
+.Nm
+language accepted by the
+.Xr mandoc 1
+utility.
+.Pp
+Tables within
+.Xr mdoc 7
+or
+.Xr man 7
+are enclosed by the
+.Sq TS
+and
+.Sq TE
+macro tags, whose precise syntax is documented in
+.Xr roff 7 .
+Tables consist of a series of options on a single line, followed by the
+table layout, followed by data.
+.Pp
+For example, the following creates a boxed table with digits centred in
+the cells.
+.Bd -literal -offset indent
+\&.TS
+tab(:) box;
+c5 c5 c5.
+1:2:3
+4:5:6
+\&.TE
+.Ed
+.Pp
+When formatted, the following output is produced:
+.Bd -filled -offset indent -compact
+.TS
+tab(:) box;
+c5 c5 c5.
+1:2:3
+4:5:6
+.TE
+.Ed
+.Sh TABLE STRUCTURE
+Tables are enclosed by the
+.Sq TS
+and
+.Sq TE
+.Xr roff 7
+macros.
+A table consists of an optional single line of table
+.Sx Options
+terminated by a semicolon, followed by one or more lines of
+.Sx Layout
+specifications terminated by a period, then
+.Sx Data .
+All input must be 7-bit ASCII.
+Example:
+.Bd -literal -offset indent
+\&.TS
+box tab(:);
+c | c
+| c | c.
+1:2
+3:4
+\&.TE
+.Ed
+.Pp
+Table data is
+.Em pre-processed ,
+that is, data rows are parsed then inserted into the underlying stream
+of input data.
+This allows data rows to be interspersed by arbitrary
+.Xr roff 7 ,
+.Xr mdoc 7 ,
+and
+.Xr man 7
+macros such as
+.Bd -literal -offset indent
+\&.TS
+tab(:);
+c c c.
+1:2:3
+\&.Ao
+3:2:1
+\&.Ac
+\&.TE
+.Ed
+.Pp
+in the case of
+.Xr mdoc 7
+or
+.Bd -literal -offset indent
+\&.TS
+tab(:);
+c c c.
+\&.ds ab 2
+1:\e*(ab:3
+\&.I
+3:2:1
+\&.TE
+.Ed
+.Pp
+in the case of
+.Xr man 7 .
+.Ss Options
+The first line of a table consists of space-separated option keys and
+modifiers terminated by a semicolon.
+If the first line does not have a terminating semicolon, it is assumed
+that no options are specified and instead a
+.Sx Layout
+is processed.
+Some options accept arguments enclosed by parenthesis.
+The following case-insensitive options are available:
+.Bl -tag -width Ds
+.It Cm center
+This option is not supported by
+.Xr mandoc 1 .
+This may also be invoked with
+.Cm centre .
+.It Cm delim
+Accepts a two-character argument.
+This option is not supported by
+.Xr mandoc 1 .
+.It Cm expand
+This option is not supported by
+.Xr mandoc 1 .
+.It Cm box
+Draw a single-line box around the table.
+This may also be invoked with
+.Cm frame .
+.It Cm doublebox
+Draw a double-line box around the table.
+This may also be invoked with
+.Cm doubleframe .
+.It Cm allbox
+This option is not supported by
+.Xr mandoc 1 .
+.It Cm tab
+Accepts a single-character argument.
+This character is used as a delimiter between data cells, which otherwise
+defaults to the tab character.
+.It Cm linesize
+Accepts a natural number (all digits).
+This option is not supported by
+.Xr mandoc 1 .
+.It Cm nokeep
+This option is not supported by
+.Xr mandoc 1 .
+.It Cm decimalpoint
+Accepts a single-character argument.
+This character will be used as the decimal point with the
+.Cm n
+layout key.
+.It Cm nospaces
+This option is not supported by
+.Xr mandoc 1 .
+.El
+.Ss Layout
+The table layout follows
+.Sx Options
+or a
+.Sq \&T&
+macro invocation.
+Layout specifies how data rows are displayed on output.
+Each layout line corresponds to a line of data; the last layout line
+applies to all remaining data lines.
+Layout lines may also be separated by a comma.
+Each layout cell consists of one of the following case-insensitive keys:
+.Bl -tag -width Ds
+.It Cm c
+Centre a literal string within its column.
+.It Cm r
+Right-justify a literal string within its column.
+.It Cm l
+Left-justify a literal string within its column.
+.It Cm n
+Justify a number around its last decimal point.
+If the decimal point is not found on the number, it's assumed to trail
+the number.
+.It Cm s
+Horizontally span columns from the last
+.No non- Ns Cm s
+data cell.
+It is an error if spanning columns follow a
+.Cm \-
+or
+.Cm \(ba
+cell, or come first.
+This option is not supported by
+.Xr mandoc 1 .
+.It Cm a
+Left-justify a literal string and pad with one space.
+.It Cm ^
+Vertically span rows from the last
+.No non- Ns Cm ^
+data cell.
+It is an error to invoke a vertical span on the first layout row.
+Unlike a horizontal spanner, you must specify an empty cell (if it not
+empty, the data is discarded) in the corresponding data cell.
+.It Cm \-
+Replace the data cell (its contents will be lost) with a single
+horizontal line.
+This may also be invoked with
+.Cm _ .
+.It Cm =
+Replace the data cell (its contents will be lost) with a double
+horizontal line.
+.It Cm \(ba
+Emit a vertical bar instead of data.
+.It Cm \(ba\(ba
+Emit a double-vertical bar instead of data.
+.El
+.Pp
+Keys may be followed by a set of modifiers.
+A modifier is either a modifier key or a natural number for specifying
+the minimum width of a column.
+The following case-insensitive modifier keys are available:
+.Cm z ,
+.Cm u ,
+.Cm e ,
+.Cm t ,
+.Cm d ,
+.Cm b ,
+.Cm i ,
+.Cm r ,
+and
+.Cm f
+.Po
+followed by
+.Cm b ,
+.Cm i ,
+.Cm r ,
+.Cm 3 ,
+.Cm 2 ,
+or
+.Cm 1
+.Pc .
+All of these are ignored by
+.Xr mandoc 1 .
+.Pp
+For example, the following layout specifies a centre-justified column of
+minimum width 10, followed by vertical bar, followed by a left-justified
+column of minimum width 10, another vertical bar, then a column
+justified about the decimal point in numbers:
+.Pp
+.Dl c10 | l10 | n
+.Ss Data
+The data section follows the last layout row.
+By default, cells in a data section are delimited by a tab.
+This behaviour may be changed with the
+.Cm tab
+option.
+If
+.Cm _
+or
+.Cm =
+is specified, a single or double line, respectively, is drawn across the
+data field.
+If
+.Cm \e-
+or
+.Cm \e=
+is specified, a line is drawn within the data field (i.e. terminating
+within the cell and not draw to the border).
+If the last cell of a line is
+.Cm T{ ,
+all subsequent lines are included as part of the cell until
+.Cm T}
+is specified as its own data cell.
+It may then be followed by a tab
+.Pq or as designated by Cm tab
+or an end-of-line to terminate the row.
+.Sh COMPATIBILITY
+This section documents compatibility between mandoc and other
+.Nm
+implementations, at this time limited to GNU tbl.
+.Pp
+.Bl -dash -compact
+.It
+In GNU tbl, comments and macros are disallowed prior to the data block
+of a table.
+The
+.Xr mandoc 1
+implementation allows them.
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr man 7 ,
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr roff 7
+.Rs
+.%A M. E. Lesk
+.%T Tbl\(emA Program to Format Tables
+.%D June 11, 1976
+.Re
+.Sh HISTORY
+The tbl utility, a preprocessor for troff, was originally written by M.
+E. Lesk at Bell Labs in 1975.
+The GNU reimplementation of tbl, part of the groff package, was released
+in 1990 by James Clark.
+A standalone tbl implementation was written by Kristaps Dzonsons in
+2010.
+This formed the basis of the implementation that is part of the
+.Xr mandoc 1
+utility.
+.Sh AUTHORS
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
diff --git a/contrib/mdocml/tbl.c b/contrib/mdocml/tbl.c
new file mode 100644
index 0000000..3c94d7e
--- /dev/null
+++ b/contrib/mdocml/tbl.c
@@ -0,0 +1,176 @@
+/* $Id: tbl.c,v 1.29 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+
+enum rofferr
+tbl_read(struct tbl_node *tbl, int ln, const char *p, int offs)
+{
+ int len;
+ const char *cp;
+
+ cp = &p[offs];
+ len = (int)strlen(cp);
+
+ /*
+ * If we're in the options section and we don't have a
+ * terminating semicolon, assume we've moved directly into the
+ * layout section. No need to report a warning: this is,
+ * apparently, standard behaviour.
+ */
+
+ if (TBL_PART_OPTS == tbl->part && len)
+ if (';' != cp[len - 1])
+ tbl->part = TBL_PART_LAYOUT;
+
+ /* Now process each logical section of the table. */
+
+ switch (tbl->part) {
+ case TBL_PART_OPTS:
+ return(tbl_option(tbl, ln, p) ? ROFF_IGN : ROFF_ERR);
+ case TBL_PART_LAYOUT:
+ return(tbl_layout(tbl, ln, p) ? ROFF_IGN : ROFF_ERR);
+ case TBL_PART_CDATA:
+ return(tbl_cdata(tbl, ln, p) ? ROFF_TBL : ROFF_IGN);
+ default:
+ break;
+ }
+
+ /*
+ * This only returns zero if the line is empty, so we ignore it
+ * and continue on.
+ */
+ return(tbl_data(tbl, ln, p) ? ROFF_TBL : ROFF_IGN);
+}
+
+struct tbl_node *
+tbl_alloc(int pos, int line, struct mparse *parse)
+{
+ struct tbl_node *tbl;
+
+ tbl = mandoc_calloc(1, sizeof(struct tbl_node));
+ tbl->line = line;
+ tbl->pos = pos;
+ tbl->parse = parse;
+ tbl->part = TBL_PART_OPTS;
+ tbl->opts.tab = '\t';
+ tbl->opts.linesize = 12;
+ tbl->opts.decimal = '.';
+ return(tbl);
+}
+
+void
+tbl_free(struct tbl_node *tbl)
+{
+ struct tbl_row *rp;
+ struct tbl_cell *cp;
+ struct tbl_span *sp;
+ struct tbl_dat *dp;
+ struct tbl_head *hp;
+
+ while (NULL != (rp = tbl->first_row)) {
+ tbl->first_row = rp->next;
+ while (rp->first) {
+ cp = rp->first;
+ rp->first = cp->next;
+ free(cp);
+ }
+ free(rp);
+ }
+
+ while (NULL != (sp = tbl->first_span)) {
+ tbl->first_span = sp->next;
+ while (sp->first) {
+ dp = sp->first;
+ sp->first = dp->next;
+ if (dp->string)
+ free(dp->string);
+ free(dp);
+ }
+ free(sp);
+ }
+
+ while (NULL != (hp = tbl->first_head)) {
+ tbl->first_head = hp->next;
+ free(hp);
+ }
+
+ free(tbl);
+}
+
+void
+tbl_restart(int line, int pos, struct tbl_node *tbl)
+{
+ if (TBL_PART_CDATA == tbl->part)
+ mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse,
+ tbl->line, tbl->pos, NULL);
+
+ tbl->part = TBL_PART_LAYOUT;
+ tbl->line = line;
+ tbl->pos = pos;
+
+ if (NULL == tbl->first_span || NULL == tbl->first_span->first)
+ mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse,
+ tbl->line, tbl->pos, NULL);
+}
+
+const struct tbl_span *
+tbl_span(struct tbl_node *tbl)
+{
+ struct tbl_span *span;
+
+ assert(tbl);
+ span = tbl->current_span ? tbl->current_span->next
+ : tbl->first_span;
+ if (span)
+ tbl->current_span = span;
+ return(span);
+}
+
+void
+tbl_end(struct tbl_node **tblp)
+{
+ struct tbl_node *tbl;
+
+ tbl = *tblp;
+ *tblp = NULL;
+
+ if (NULL == tbl->first_span || NULL == tbl->first_span->first)
+ mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse,
+ tbl->line, tbl->pos, NULL);
+
+ if (tbl->last_span)
+ tbl->last_span->flags |= TBL_SPAN_LAST;
+
+ if (TBL_PART_CDATA == tbl->part)
+ mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse,
+ tbl->line, tbl->pos, NULL);
+}
diff --git a/contrib/mdocml/tbl_data.c b/contrib/mdocml/tbl_data.c
new file mode 100644
index 0000000..1db24a6
--- /dev/null
+++ b/contrib/mdocml/tbl_data.c
@@ -0,0 +1,275 @@
+/* $Id: tbl_data.c,v 1.31 2014/04/23 16:08:33 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+static int getdata(struct tbl_node *, struct tbl_span *,
+ int, const char *, int *);
+static struct tbl_span *newspan(struct tbl_node *, int,
+ struct tbl_row *);
+
+
+static int
+getdata(struct tbl_node *tbl, struct tbl_span *dp,
+ int ln, const char *p, int *pos)
+{
+ struct tbl_dat *dat;
+ struct tbl_cell *cp;
+ int sv, spans;
+
+ cp = NULL;
+ if (dp->last && dp->last->layout)
+ cp = dp->last->layout->next;
+ else if (NULL == dp->last)
+ cp = dp->layout->first;
+
+ /*
+ * Skip over spanners, since
+ * we want to match data with data layout cells in the header.
+ */
+
+ while (cp && TBL_CELL_SPAN == cp->pos)
+ cp = cp->next;
+
+ /*
+ * Stop processing when we reach the end of the available layout
+ * cells. This means that we have extra input.
+ */
+
+ if (NULL == cp) {
+ mandoc_msg(MANDOCERR_TBLEXTRADAT, tbl->parse,
+ ln, *pos, NULL);
+ /* Skip to the end... */
+ while (p[*pos])
+ (*pos)++;
+ return(1);
+ }
+
+ dat = mandoc_calloc(1, sizeof(struct tbl_dat));
+ dat->layout = cp;
+ dat->pos = TBL_DATA_NONE;
+
+ assert(TBL_CELL_SPAN != cp->pos);
+
+ for (spans = 0, cp = cp->next; cp; cp = cp->next)
+ if (TBL_CELL_SPAN == cp->pos)
+ spans++;
+ else
+ break;
+
+ dat->spans = spans;
+
+ if (dp->last) {
+ dp->last->next = dat;
+ dp->last = dat;
+ } else
+ dp->last = dp->first = dat;
+
+ sv = *pos;
+ while (p[*pos] && p[*pos] != tbl->opts.tab)
+ (*pos)++;
+
+ /*
+ * Check for a continued-data scope opening. This consists of a
+ * trailing `T{' at the end of the line. Subsequent lines,
+ * until a standalone `T}', are included in our cell.
+ */
+
+ if (*pos - sv == 2 && 'T' == p[sv] && '{' == p[sv + 1]) {
+ tbl->part = TBL_PART_CDATA;
+ return(1);
+ }
+
+ assert(*pos - sv >= 0);
+
+ dat->string = mandoc_malloc((size_t)(*pos - sv + 1));
+ memcpy(dat->string, &p[sv], (size_t)(*pos - sv));
+ dat->string[*pos - sv] = '\0';
+
+ if (p[*pos])
+ (*pos)++;
+
+ if ( ! strcmp(dat->string, "_"))
+ dat->pos = TBL_DATA_HORIZ;
+ else if ( ! strcmp(dat->string, "="))
+ dat->pos = TBL_DATA_DHORIZ;
+ else if ( ! strcmp(dat->string, "\\_"))
+ dat->pos = TBL_DATA_NHORIZ;
+ else if ( ! strcmp(dat->string, "\\="))
+ dat->pos = TBL_DATA_NDHORIZ;
+ else
+ dat->pos = TBL_DATA_DATA;
+
+ if (TBL_CELL_HORIZ == dat->layout->pos ||
+ TBL_CELL_DHORIZ == dat->layout->pos ||
+ TBL_CELL_DOWN == dat->layout->pos)
+ if (TBL_DATA_DATA == dat->pos && '\0' != *dat->string)
+ mandoc_msg(MANDOCERR_TBLIGNDATA,
+ tbl->parse, ln, sv, NULL);
+
+ return(1);
+}
+
+int
+tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
+{
+ struct tbl_dat *dat;
+ size_t sz;
+ int pos;
+
+ pos = 0;
+
+ dat = tbl->last_span->last;
+
+ if (p[pos] == 'T' && p[pos + 1] == '}') {
+ pos += 2;
+ if (p[pos] == tbl->opts.tab) {
+ tbl->part = TBL_PART_DATA;
+ pos++;
+ return(getdata(tbl, tbl->last_span, ln, p, &pos));
+ } else if ('\0' == p[pos]) {
+ tbl->part = TBL_PART_DATA;
+ return(1);
+ }
+
+ /* Fallthrough: T} is part of a word. */
+ }
+
+ dat->pos = TBL_DATA_DATA;
+
+ if (dat->string) {
+ sz = strlen(p) + strlen(dat->string) + 2;
+ dat->string = mandoc_realloc(dat->string, sz);
+ (void)strlcat(dat->string, " ", sz);
+ (void)strlcat(dat->string, p, sz);
+ } else
+ dat->string = mandoc_strdup(p);
+
+ if (TBL_CELL_DOWN == dat->layout->pos)
+ mandoc_msg(MANDOCERR_TBLIGNDATA, tbl->parse,
+ ln, pos, NULL);
+
+ return(0);
+}
+
+static struct tbl_span *
+newspan(struct tbl_node *tbl, int line, struct tbl_row *rp)
+{
+ struct tbl_span *dp;
+
+ dp = mandoc_calloc(1, sizeof(struct tbl_span));
+ dp->line = line;
+ dp->opts = &tbl->opts;
+ dp->layout = rp;
+ dp->head = tbl->first_head;
+
+ if (tbl->last_span) {
+ tbl->last_span->next = dp;
+ tbl->last_span = dp;
+ } else {
+ tbl->last_span = tbl->first_span = dp;
+ tbl->current_span = NULL;
+ dp->flags |= TBL_SPAN_FIRST;
+ }
+
+ return(dp);
+}
+
+int
+tbl_data(struct tbl_node *tbl, int ln, const char *p)
+{
+ struct tbl_span *dp;
+ struct tbl_row *rp;
+ int pos;
+
+ pos = 0;
+
+ if ('\0' == p[pos]) {
+ mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, pos, NULL);
+ return(0);
+ }
+
+ /*
+ * Choose a layout row: take the one following the last parsed
+ * span's. If that doesn't exist, use the last parsed span's.
+ * If there's no last parsed span, use the first row. Lastly,
+ * if the last span was a horizontal line, use the same layout
+ * (it doesn't "consume" the layout).
+ */
+
+ if (tbl->last_span) {
+ assert(tbl->last_span->layout);
+ if (tbl->last_span->pos == TBL_SPAN_DATA) {
+ for (rp = tbl->last_span->layout->next;
+ rp && rp->first; rp = rp->next) {
+ switch (rp->first->pos) {
+ case TBL_CELL_HORIZ:
+ dp = newspan(tbl, ln, rp);
+ dp->pos = TBL_SPAN_HORIZ;
+ continue;
+ case TBL_CELL_DHORIZ:
+ dp = newspan(tbl, ln, rp);
+ dp->pos = TBL_SPAN_DHORIZ;
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ } else
+ rp = tbl->last_span->layout;
+
+ if (NULL == rp)
+ rp = tbl->last_span->layout;
+ } else
+ rp = tbl->first_row;
+
+ assert(rp);
+
+ dp = newspan(tbl, ln, rp);
+
+ if ( ! strcmp(p, "_")) {
+ dp->pos = TBL_SPAN_HORIZ;
+ return(1);
+ } else if ( ! strcmp(p, "=")) {
+ dp->pos = TBL_SPAN_DHORIZ;
+ return(1);
+ }
+
+ dp->pos = TBL_SPAN_DATA;
+
+ /* This returns 0 when TBL_PART_CDATA is entered. */
+
+ while ('\0' != p[pos])
+ if ( ! getdata(tbl, dp, ln, p, &pos))
+ return(0);
+
+ return(1);
+}
diff --git a/contrib/mdocml/tbl_html.c b/contrib/mdocml/tbl_html.c
new file mode 100644
index 0000000..4b85d06
--- /dev/null
+++ b/contrib/mdocml/tbl_html.c
@@ -0,0 +1,142 @@
+/* $Id: tbl_html.c,v 1.11 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "html.h"
+
+static void html_tblopen(struct html *, const struct tbl_span *);
+static size_t html_tbl_len(size_t, void *);
+static size_t html_tbl_strlen(const char *, void *);
+
+
+static size_t
+html_tbl_len(size_t sz, void *arg)
+{
+
+ return(sz);
+}
+
+static size_t
+html_tbl_strlen(const char *p, void *arg)
+{
+
+ return(strlen(p));
+}
+
+static void
+html_tblopen(struct html *h, const struct tbl_span *sp)
+{
+ const struct tbl_head *hp;
+ struct htmlpair tag;
+ struct roffsu su;
+ struct roffcol *col;
+
+ if (TBL_SPAN_FIRST & sp->flags) {
+ h->tbl.len = html_tbl_len;
+ h->tbl.slen = html_tbl_strlen;
+ tblcalc(&h->tbl, sp);
+ }
+
+ assert(NULL == h->tblt);
+ PAIR_CLASS_INIT(&tag, "tbl");
+ h->tblt = print_otag(h, TAG_TABLE, 1, &tag);
+
+ for (hp = sp->head; hp; hp = hp->next) {
+ bufinit(h);
+ col = &h->tbl.cols[hp->ident];
+ SCALE_HS_INIT(&su, col->width);
+ bufcat_su(h, "width", &su);
+ PAIR_STYLE_INIT(&tag, h);
+ print_otag(h, TAG_COL, 1, &tag);
+ }
+
+ print_otag(h, TAG_TBODY, 0, NULL);
+}
+
+void
+print_tblclose(struct html *h)
+{
+
+ assert(h->tblt);
+ print_tagq(h, h->tblt);
+ h->tblt = NULL;
+}
+
+void
+print_tbl(struct html *h, const struct tbl_span *sp)
+{
+ const struct tbl_head *hp;
+ const struct tbl_dat *dp;
+ struct htmlpair tag;
+ struct tag *tt;
+
+ /* Inhibit printing of spaces: we do padding ourselves. */
+
+ if (NULL == h->tblt)
+ html_tblopen(h, sp);
+
+ assert(h->tblt);
+
+ h->flags |= HTML_NONOSPACE;
+ h->flags |= HTML_NOSPACE;
+
+ tt = print_otag(h, TAG_TR, 0, NULL);
+
+ switch (sp->pos) {
+ case TBL_SPAN_HORIZ:
+ /* FALLTHROUGH */
+ case TBL_SPAN_DHORIZ:
+ PAIR_INIT(&tag, ATTR_COLSPAN, "0");
+ print_otag(h, TAG_TD, 1, &tag);
+ break;
+ default:
+ dp = sp->first;
+ for (hp = sp->head; hp; hp = hp->next) {
+ print_stagq(h, tt);
+ print_otag(h, TAG_TD, 0, NULL);
+
+ if (NULL == dp)
+ break;
+ if (TBL_CELL_DOWN != dp->layout->pos)
+ if (dp->string)
+ print_text(h, dp->string);
+ dp = dp->next;
+ }
+ break;
+ }
+
+ print_tagq(h, tt);
+
+ h->flags &= ~HTML_NONOSPACE;
+
+ if (TBL_SPAN_LAST & sp->flags) {
+ assert(h->tbl.cols);
+ free(h->tbl.cols);
+ h->tbl.cols = NULL;
+ print_tblclose(h);
+ }
+
+}
diff --git a/contrib/mdocml/tbl_layout.c b/contrib/mdocml/tbl_layout.c
new file mode 100644
index 0000000..f025823
--- /dev/null
+++ b/contrib/mdocml/tbl_layout.c
@@ -0,0 +1,400 @@
+/* $Id: tbl_layout.c,v 1.26 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2012, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+struct tbl_phrase {
+ char name;
+ enum tbl_cellt key;
+};
+
+/*
+ * FIXME: we can make this parse a lot nicer by, when an error is
+ * encountered in a layout key, bailing to the next key (i.e. to the
+ * next whitespace then continuing).
+ */
+
+#define KEYS_MAX 11
+
+static const struct tbl_phrase keys[KEYS_MAX] = {
+ { 'c', TBL_CELL_CENTRE },
+ { 'r', TBL_CELL_RIGHT },
+ { 'l', TBL_CELL_LEFT },
+ { 'n', TBL_CELL_NUMBER },
+ { 's', TBL_CELL_SPAN },
+ { 'a', TBL_CELL_LONG },
+ { '^', TBL_CELL_DOWN },
+ { '-', TBL_CELL_HORIZ },
+ { '_', TBL_CELL_HORIZ },
+ { '=', TBL_CELL_DHORIZ }
+};
+
+static int mods(struct tbl_node *, struct tbl_cell *,
+ int, const char *, int *);
+static int cell(struct tbl_node *, struct tbl_row *,
+ int, const char *, int *);
+static void row(struct tbl_node *, int, const char *, int *);
+static struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
+ enum tbl_cellt, int vert);
+
+
+static int
+mods(struct tbl_node *tbl, struct tbl_cell *cp,
+ int ln, const char *p, int *pos)
+{
+ char buf[5];
+ int i;
+
+ /* Not all types accept modifiers. */
+
+ switch (cp->pos) {
+ case TBL_CELL_DOWN:
+ /* FALLTHROUGH */
+ case TBL_CELL_HORIZ:
+ /* FALLTHROUGH */
+ case TBL_CELL_DHORIZ:
+ return(1);
+ default:
+ break;
+ }
+
+mod:
+ /*
+ * XXX: since, at least for now, modifiers are non-conflicting
+ * (are separable by value, regardless of position), we let
+ * modifiers come in any order. The existing tbl doesn't let
+ * this happen.
+ */
+ switch (p[*pos]) {
+ case '\0':
+ /* FALLTHROUGH */
+ case ' ':
+ /* FALLTHROUGH */
+ case '\t':
+ /* FALLTHROUGH */
+ case ',':
+ /* FALLTHROUGH */
+ case '.':
+ /* FALLTHROUGH */
+ case '|':
+ return(1);
+ default:
+ break;
+ }
+
+ /* Throw away parenthesised expression. */
+
+ if ('(' == p[*pos]) {
+ (*pos)++;
+ while (p[*pos] && ')' != p[*pos])
+ (*pos)++;
+ if (')' == p[*pos]) {
+ (*pos)++;
+ goto mod;
+ }
+ mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+ ln, *pos, NULL);
+ return(0);
+ }
+
+ /* Parse numerical spacing from modifier string. */
+
+ if (isdigit((unsigned char)p[*pos])) {
+ for (i = 0; i < 4; i++) {
+ if ( ! isdigit((unsigned char)p[*pos + i]))
+ break;
+ buf[i] = p[*pos + i];
+ }
+ buf[i] = '\0';
+
+ /* No greater than 4 digits. */
+
+ if (4 == i) {
+ mandoc_msg(MANDOCERR_TBLLAYOUT,
+ tbl->parse, ln, *pos, NULL);
+ return(0);
+ }
+
+ *pos += i;
+ cp->spacing = (size_t)atoi(buf);
+
+ goto mod;
+ /* NOTREACHED */
+ }
+
+ /* TODO: GNU has many more extensions. */
+
+ switch (tolower((unsigned char)p[(*pos)++])) {
+ case 'z':
+ cp->flags |= TBL_CELL_WIGN;
+ goto mod;
+ case 'u':
+ cp->flags |= TBL_CELL_UP;
+ goto mod;
+ case 'e':
+ cp->flags |= TBL_CELL_EQUAL;
+ goto mod;
+ case 't':
+ cp->flags |= TBL_CELL_TALIGN;
+ goto mod;
+ case 'd':
+ cp->flags |= TBL_CELL_BALIGN;
+ goto mod;
+ case 'w': /* XXX for now, ignore minimal column width */
+ goto mod;
+ case 'f':
+ break;
+ case 'r':
+ /* FALLTHROUGH */
+ case 'b':
+ /* FALLTHROUGH */
+ case 'i':
+ (*pos)--;
+ break;
+ default:
+ mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+ ln, *pos - 1, NULL);
+ return(0);
+ }
+
+ switch (tolower((unsigned char)p[(*pos)++])) {
+ case '3':
+ /* FALLTHROUGH */
+ case 'b':
+ cp->flags |= TBL_CELL_BOLD;
+ goto mod;
+ case '2':
+ /* FALLTHROUGH */
+ case 'i':
+ cp->flags |= TBL_CELL_ITALIC;
+ goto mod;
+ case '1':
+ /* FALLTHROUGH */
+ case 'r':
+ goto mod;
+ default:
+ break;
+ }
+
+ mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+ ln, *pos - 1, NULL);
+ return(0);
+}
+
+static int
+cell(struct tbl_node *tbl, struct tbl_row *rp,
+ int ln, const char *p, int *pos)
+{
+ int vert, i;
+ enum tbl_cellt c;
+
+ /* Handle vertical lines. */
+
+ for (vert = 0; '|' == p[*pos]; ++*pos)
+ vert++;
+ while (' ' == p[*pos])
+ (*pos)++;
+
+ /* Handle trailing vertical lines */
+
+ if ('.' == p[*pos] || '\0' == p[*pos]) {
+ rp->vert = vert;
+ return(1);
+ }
+
+ /* Parse the column position (`c', `l', `r', ...). */
+
+ for (i = 0; i < KEYS_MAX; i++)
+ if (tolower((unsigned char)p[*pos]) == keys[i].name)
+ break;
+
+ if (KEYS_MAX == i) {
+ mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+ ln, *pos, NULL);
+ return(0);
+ }
+
+ c = keys[i].key;
+
+ /*
+ * If a span cell is found first, raise a warning and abort the
+ * parse. If a span cell is found and the last layout element
+ * isn't a "normal" layout, bail.
+ *
+ * FIXME: recover from this somehow?
+ */
+
+ if (TBL_CELL_SPAN == c) {
+ if (NULL == rp->first) {
+ mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+ ln, *pos, NULL);
+ return(0);
+ } else if (rp->last)
+ switch (rp->last->pos) {
+ case TBL_CELL_HORIZ:
+ /* FALLTHROUGH */
+ case TBL_CELL_DHORIZ:
+ mandoc_msg(MANDOCERR_TBLLAYOUT,
+ tbl->parse, ln, *pos, NULL);
+ return(0);
+ default:
+ break;
+ }
+ }
+
+ /*
+ * If a vertical spanner is found, we may not be in the first
+ * row.
+ */
+
+ if (TBL_CELL_DOWN == c && rp == tbl->first_row) {
+ mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL);
+ return(0);
+ }
+
+ (*pos)++;
+
+ /* Disallow adjacent spacers. */
+
+ if (vert > 2) {
+ mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL);
+ return(0);
+ }
+
+ /* Allocate cell then parse its modifiers. */
+
+ return(mods(tbl, cell_alloc(tbl, rp, c, vert), ln, p, pos));
+}
+
+static void
+row(struct tbl_node *tbl, int ln, const char *p, int *pos)
+{
+ struct tbl_row *rp;
+
+row: /*
+ * EBNF describing this section:
+ *
+ * row ::= row_list [:space:]* [.]?[\n]
+ * row_list ::= [:space:]* row_elem row_tail
+ * row_tail ::= [:space:]*[,] row_list |
+ * epsilon
+ * row_elem ::= [\t\ ]*[:alpha:]+
+ */
+
+ rp = mandoc_calloc(1, sizeof(struct tbl_row));
+ if (tbl->last_row)
+ tbl->last_row->next = rp;
+ else
+ tbl->first_row = rp;
+ tbl->last_row = rp;
+
+cell:
+ while (isspace((unsigned char)p[*pos]))
+ (*pos)++;
+
+ /* Safely exit layout context. */
+
+ if ('.' == p[*pos]) {
+ tbl->part = TBL_PART_DATA;
+ if (NULL == tbl->first_row)
+ mandoc_msg(MANDOCERR_TBLNOLAYOUT,
+ tbl->parse, ln, *pos, NULL);
+ (*pos)++;
+ return;
+ }
+
+ /* End (and possibly restart) a row. */
+
+ if (',' == p[*pos]) {
+ (*pos)++;
+ goto row;
+ } else if ('\0' == p[*pos])
+ return;
+
+ if ( ! cell(tbl, rp, ln, p, pos))
+ return;
+
+ goto cell;
+ /* NOTREACHED */
+}
+
+int
+tbl_layout(struct tbl_node *tbl, int ln, const char *p)
+{
+ int pos;
+
+ pos = 0;
+ row(tbl, ln, p, &pos);
+
+ /* Always succeed. */
+ return(1);
+}
+
+static struct tbl_cell *
+cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos,
+ int vert)
+{
+ struct tbl_cell *p, *pp;
+ struct tbl_head *h, *hp;
+
+ p = mandoc_calloc(1, sizeof(struct tbl_cell));
+
+ if (NULL != (pp = rp->last)) {
+ pp->next = p;
+ h = pp->head->next;
+ } else {
+ rp->first = p;
+ h = tbl->first_head;
+ }
+ rp->last = p;
+
+ p->pos = pos;
+ p->vert = vert;
+
+ /* Re-use header. */
+
+ if (h) {
+ p->head = h;
+ return(p);
+ }
+
+ hp = mandoc_calloc(1, sizeof(struct tbl_head));
+ hp->ident = tbl->opts.cols++;
+ hp->vert = vert;
+
+ if (tbl->last_head) {
+ hp->prev = tbl->last_head;
+ tbl->last_head->next = hp;
+ } else
+ tbl->first_head = hp;
+ tbl->last_head = hp;
+
+ p->head = hp;
+ return(p);
+}
diff --git a/contrib/mdocml/tbl_opts.c b/contrib/mdocml/tbl_opts.c
new file mode 100644
index 0000000..ae4df4a
--- /dev/null
+++ b/contrib/mdocml/tbl_opts.c
@@ -0,0 +1,271 @@
+/* $Id: tbl_opts.c,v 1.13 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+enum tbl_ident {
+ KEY_CENTRE = 0,
+ KEY_DELIM,
+ KEY_EXPAND,
+ KEY_BOX,
+ KEY_DBOX,
+ KEY_ALLBOX,
+ KEY_TAB,
+ KEY_LINESIZE,
+ KEY_NOKEEP,
+ KEY_DPOINT,
+ KEY_NOSPACE,
+ KEY_FRAME,
+ KEY_DFRAME,
+ KEY_MAX
+};
+
+struct tbl_phrase {
+ const char *name;
+ int key;
+ enum tbl_ident ident;
+};
+
+/* Handle Commonwealth/American spellings. */
+#define KEY_MAXKEYS 14
+
+/* Maximum length of key name string. */
+#define KEY_MAXNAME 13
+
+/* Maximum length of key number size. */
+#define KEY_MAXNUMSZ 10
+
+static const struct tbl_phrase keys[KEY_MAXKEYS] = {
+ { "center", TBL_OPT_CENTRE, KEY_CENTRE},
+ { "centre", TBL_OPT_CENTRE, KEY_CENTRE},
+ { "delim", 0, KEY_DELIM},
+ { "expand", TBL_OPT_EXPAND, KEY_EXPAND},
+ { "box", TBL_OPT_BOX, KEY_BOX},
+ { "doublebox", TBL_OPT_DBOX, KEY_DBOX},
+ { "allbox", TBL_OPT_ALLBOX, KEY_ALLBOX},
+ { "frame", TBL_OPT_BOX, KEY_FRAME},
+ { "doubleframe", TBL_OPT_DBOX, KEY_DFRAME},
+ { "tab", 0, KEY_TAB},
+ { "linesize", 0, KEY_LINESIZE},
+ { "nokeep", TBL_OPT_NOKEEP, KEY_NOKEEP},
+ { "decimalpoint", 0, KEY_DPOINT},
+ { "nospaces", TBL_OPT_NOSPACE, KEY_NOSPACE},
+};
+
+static int arg(struct tbl_node *, int,
+ const char *, int *, enum tbl_ident);
+static void opt(struct tbl_node *, int,
+ const char *, int *);
+
+
+static int
+arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
+{
+ int i;
+ char buf[KEY_MAXNUMSZ];
+
+ while (isspace((unsigned char)p[*pos]))
+ (*pos)++;
+
+ /* Arguments always begin with a parenthesis. */
+
+ if ('(' != p[*pos]) {
+ mandoc_msg(MANDOCERR_TBL, tbl->parse,
+ ln, *pos, NULL);
+ return(0);
+ }
+
+ (*pos)++;
+
+ /*
+ * The arguments can be ANY value, so we can't just stop at the
+ * next close parenthesis (the argument can be a closed
+ * parenthesis itself).
+ */
+
+ switch (key) {
+ case KEY_DELIM:
+ if ('\0' == p[(*pos)++]) {
+ mandoc_msg(MANDOCERR_TBL, tbl->parse,
+ ln, *pos - 1, NULL);
+ return(0);
+ }
+
+ if ('\0' == p[(*pos)++]) {
+ mandoc_msg(MANDOCERR_TBL, tbl->parse,
+ ln, *pos - 1, NULL);
+ return(0);
+ }
+ break;
+ case KEY_TAB:
+ if ('\0' != (tbl->opts.tab = p[(*pos)++]))
+ break;
+
+ mandoc_msg(MANDOCERR_TBL, tbl->parse,
+ ln, *pos - 1, NULL);
+ return(0);
+ case KEY_LINESIZE:
+ for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) {
+ buf[i] = p[*pos];
+ if ( ! isdigit((unsigned char)buf[i]))
+ break;
+ }
+
+ if (i < KEY_MAXNUMSZ) {
+ buf[i] = '\0';
+ tbl->opts.linesize = atoi(buf);
+ break;
+ }
+
+ mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
+ return(0);
+ case KEY_DPOINT:
+ if ('\0' != (tbl->opts.decimal = p[(*pos)++]))
+ break;
+
+ mandoc_msg(MANDOCERR_TBL, tbl->parse,
+ ln, *pos - 1, NULL);
+ return(0);
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ /* End with a close parenthesis. */
+
+ if (')' == p[(*pos)++])
+ return(1);
+
+ mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos - 1, NULL);
+ return(0);
+}
+
+static void
+opt(struct tbl_node *tbl, int ln, const char *p, int *pos)
+{
+ int i, sv;
+ char buf[KEY_MAXNAME];
+
+ /*
+ * Parse individual options from the stream as surrounded by
+ * this goto. Each pass through the routine parses out a single
+ * option and registers it. Option arguments are processed in
+ * the arg() function.
+ */
+
+again: /*
+ * EBNF describing this section:
+ *
+ * options ::= option_list [:space:]* [;][\n]
+ * option_list ::= option option_tail
+ * option_tail ::= [:space:]+ option_list |
+ * ::= epsilon
+ * option ::= [:alpha:]+ args
+ * args ::= [:space:]* [(] [:alpha:]+ [)]
+ */
+
+ while (isspace((unsigned char)p[*pos]))
+ (*pos)++;
+
+ /* Safe exit point. */
+
+ if (';' == p[*pos])
+ return;
+
+ /* Copy up to first non-alpha character. */
+
+ for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) {
+ buf[i] = (char)tolower((unsigned char)p[*pos]);
+ if ( ! isalpha((unsigned char)buf[i]))
+ break;
+ }
+
+ /* Exit if buffer is empty (or overrun). */
+
+ if (KEY_MAXNAME == i || 0 == i) {
+ mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
+ return;
+ }
+
+ buf[i] = '\0';
+
+ while (isspace((unsigned char)p[*pos]))
+ (*pos)++;
+
+ /*
+ * Look through all of the available keys to find one that
+ * matches the input. FIXME: hashtable this.
+ */
+
+ for (i = 0; i < KEY_MAXKEYS; i++) {
+ if (strcmp(buf, keys[i].name))
+ continue;
+
+ /*
+ * Note: this is more difficult to recover from, as we
+ * can be anywhere in the option sequence and it's
+ * harder to jump to the next. Meanwhile, just bail out
+ * of the sequence altogether.
+ */
+
+ if (keys[i].key)
+ tbl->opts.opts |= keys[i].key;
+ else if ( ! arg(tbl, ln, p, pos, keys[i].ident))
+ return;
+
+ break;
+ }
+
+ /*
+ * Allow us to recover from bad options by continuing to another
+ * parse sequence.
+ */
+
+ if (KEY_MAXKEYS == i)
+ mandoc_msg(MANDOCERR_TBLOPT, tbl->parse, ln, sv, NULL);
+
+ goto again;
+ /* NOTREACHED */
+}
+
+int
+tbl_option(struct tbl_node *tbl, int ln, const char *p)
+{
+ int pos;
+
+ /*
+ * Table options are always on just one line, so automatically
+ * switch into the next input mode here.
+ */
+ tbl->part = TBL_PART_LAYOUT;
+
+ pos = 0;
+ opt(tbl, ln, p, &pos);
+
+ /* Always succeed. */
+ return(1);
+}
diff --git a/contrib/mdocml/tbl_term.c b/contrib/mdocml/tbl_term.c
new file mode 100644
index 0000000..e52831b
--- /dev/null
+++ b/contrib/mdocml/tbl_term.c
@@ -0,0 +1,426 @@
+/* $Id: tbl_term.c,v 1.27 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2012, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "term.h"
+
+static size_t term_tbl_len(size_t, void *);
+static size_t term_tbl_strlen(const char *, void *);
+static void tbl_char(struct termp *, char, size_t);
+static void tbl_data(struct termp *, const struct tbl_opts *,
+ const struct tbl_dat *,
+ const struct roffcol *);
+static size_t tbl_rulewidth(struct termp *, const struct tbl_head *);
+static void tbl_hframe(struct termp *, const struct tbl_span *, int);
+static void tbl_literal(struct termp *, const struct tbl_dat *,
+ const struct roffcol *);
+static void tbl_number(struct termp *, const struct tbl_opts *,
+ const struct tbl_dat *,
+ const struct roffcol *);
+static void tbl_hrule(struct termp *, const struct tbl_span *);
+static void tbl_vrule(struct termp *, const struct tbl_head *);
+
+
+static size_t
+term_tbl_strlen(const char *p, void *arg)
+{
+
+ return(term_strlen((const struct termp *)arg, p));
+}
+
+static size_t
+term_tbl_len(size_t sz, void *arg)
+{
+
+ return(term_len((const struct termp *)arg, sz));
+}
+
+void
+term_tbl(struct termp *tp, const struct tbl_span *sp)
+{
+ const struct tbl_head *hp;
+ const struct tbl_dat *dp;
+ struct roffcol *col;
+ int spans;
+ size_t rmargin, maxrmargin;
+
+ rmargin = tp->rmargin;
+ maxrmargin = tp->maxrmargin;
+
+ tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
+
+ /* Inhibit printing of spaces: we do padding ourselves. */
+
+ tp->flags |= TERMP_NONOSPACE;
+ tp->flags |= TERMP_NOSPACE;
+
+ /*
+ * The first time we're invoked for a given table block,
+ * calculate the table widths and decimal positions.
+ */
+
+ if (TBL_SPAN_FIRST & sp->flags) {
+ term_flushln(tp);
+
+ tp->tbl.len = term_tbl_len;
+ tp->tbl.slen = term_tbl_strlen;
+ tp->tbl.arg = tp;
+
+ tblcalc(&tp->tbl, sp);
+ }
+
+ /* Horizontal frame at the start of boxed tables. */
+
+ if (TBL_SPAN_FIRST & sp->flags) {
+ if (TBL_OPT_DBOX & sp->opts->opts)
+ tbl_hframe(tp, sp, 1);
+ if (TBL_OPT_DBOX & sp->opts->opts ||
+ TBL_OPT_BOX & sp->opts->opts)
+ tbl_hframe(tp, sp, 0);
+ }
+
+ /* Vertical frame at the start of each row. */
+
+ if ((TBL_OPT_BOX | TBL_OPT_DBOX) & sp->opts->opts ||
+ sp->head->vert)
+ term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
+ TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
+
+ /*
+ * Now print the actual data itself depending on the span type.
+ * Spanner spans get a horizontal rule; data spanners have their
+ * data printed by matching data to header.
+ */
+
+ switch (sp->pos) {
+ case TBL_SPAN_HORIZ:
+ /* FALLTHROUGH */
+ case TBL_SPAN_DHORIZ:
+ tbl_hrule(tp, sp);
+ break;
+ case TBL_SPAN_DATA:
+ /* Iterate over template headers. */
+ dp = sp->first;
+ spans = 0;
+ for (hp = sp->head; hp; hp = hp->next) {
+
+ /*
+ * If the current data header is invoked during
+ * a spanner ("spans" > 0), don't emit anything
+ * at all.
+ */
+
+ if (--spans >= 0)
+ continue;
+
+ /* Separate columns. */
+
+ if (NULL != hp->prev)
+ tbl_vrule(tp, hp);
+
+ col = &tp->tbl.cols[hp->ident];
+ tbl_data(tp, sp->opts, dp, col);
+
+ /*
+ * Go to the next data cell and assign the
+ * number of subsequent spans, if applicable.
+ */
+
+ if (dp) {
+ spans = dp->spans;
+ dp = dp->next;
+ }
+ }
+ break;
+ }
+
+ /* Vertical frame at the end of each row. */
+
+ if ((TBL_OPT_BOX | TBL_OPT_DBOX) & sp->opts->opts ||
+ sp->layout->vert)
+ term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
+ TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
+ term_flushln(tp);
+
+ /*
+ * If we're the last row, clean up after ourselves: clear the
+ * existing table configuration and set it to NULL.
+ */
+
+ if (TBL_SPAN_LAST & sp->flags) {
+ if (TBL_OPT_DBOX & sp->opts->opts ||
+ TBL_OPT_BOX & sp->opts->opts) {
+ tbl_hframe(tp, sp, 0);
+ tp->skipvsp = 1;
+ }
+ if (TBL_OPT_DBOX & sp->opts->opts) {
+ tbl_hframe(tp, sp, 1);
+ tp->skipvsp = 2;
+ }
+ assert(tp->tbl.cols);
+ free(tp->tbl.cols);
+ tp->tbl.cols = NULL;
+ }
+
+ tp->flags &= ~TERMP_NONOSPACE;
+ tp->rmargin = rmargin;
+ tp->maxrmargin = maxrmargin;
+
+}
+
+/*
+ * Horizontal rules extend across the entire table.
+ * Calculate the width by iterating over columns.
+ */
+static size_t
+tbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
+{
+ size_t width;
+
+ width = tp->tbl.cols[hp->ident].width;
+
+ /* Account for leading blanks. */
+ if (hp->prev)
+ width += 2 - hp->vert;
+
+ /* Account for trailing blank. */
+ width++;
+
+ return(width);
+}
+
+/*
+ * Rules inside the table can be single or double
+ * and have crossings with vertical rules marked with pluses.
+ */
+static void
+tbl_hrule(struct termp *tp, const struct tbl_span *sp)
+{
+ const struct tbl_head *hp;
+ char c;
+
+ c = '-';
+ if (TBL_SPAN_DHORIZ == sp->pos)
+ c = '=';
+
+ for (hp = sp->head; hp; hp = hp->next) {
+ if (hp->prev && hp->vert)
+ tbl_char(tp, '+', hp->vert);
+ tbl_char(tp, c, tbl_rulewidth(tp, hp));
+ }
+}
+
+/*
+ * Rules above and below the table are always single
+ * and have an additional plus at the beginning and end.
+ * For double frames, this function is called twice,
+ * and the outer one does not have crossings.
+ */
+static void
+tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
+{
+ const struct tbl_head *hp;
+
+ term_word(tp, "+");
+ for (hp = sp->head; hp; hp = hp->next) {
+ if (hp->prev && hp->vert)
+ tbl_char(tp, (outer ? '-' : '+'), hp->vert);
+ tbl_char(tp, '-', tbl_rulewidth(tp, hp));
+ }
+ term_word(tp, "+");
+ term_flushln(tp);
+}
+
+static void
+tbl_data(struct termp *tp, const struct tbl_opts *opts,
+ const struct tbl_dat *dp,
+ const struct roffcol *col)
+{
+
+ if (NULL == dp) {
+ tbl_char(tp, ASCII_NBRSP, col->width);
+ return;
+ }
+ assert(dp->layout);
+
+ switch (dp->pos) {
+ case TBL_DATA_NONE:
+ tbl_char(tp, ASCII_NBRSP, col->width);
+ return;
+ case TBL_DATA_HORIZ:
+ /* FALLTHROUGH */
+ case TBL_DATA_NHORIZ:
+ tbl_char(tp, '-', col->width);
+ return;
+ case TBL_DATA_NDHORIZ:
+ /* FALLTHROUGH */
+ case TBL_DATA_DHORIZ:
+ tbl_char(tp, '=', col->width);
+ return;
+ default:
+ break;
+ }
+
+ switch (dp->layout->pos) {
+ case TBL_CELL_HORIZ:
+ tbl_char(tp, '-', col->width);
+ break;
+ case TBL_CELL_DHORIZ:
+ tbl_char(tp, '=', col->width);
+ break;
+ case TBL_CELL_LONG:
+ /* FALLTHROUGH */
+ case TBL_CELL_CENTRE:
+ /* FALLTHROUGH */
+ case TBL_CELL_LEFT:
+ /* FALLTHROUGH */
+ case TBL_CELL_RIGHT:
+ tbl_literal(tp, dp, col);
+ break;
+ case TBL_CELL_NUMBER:
+ tbl_number(tp, opts, dp, col);
+ break;
+ case TBL_CELL_DOWN:
+ tbl_char(tp, ASCII_NBRSP, col->width);
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+}
+
+static void
+tbl_vrule(struct termp *tp, const struct tbl_head *hp)
+{
+
+ tbl_char(tp, ASCII_NBRSP, 1);
+ if (0 < hp->vert)
+ tbl_char(tp, '|', hp->vert);
+ if (2 > hp->vert)
+ tbl_char(tp, ASCII_NBRSP, 2 - hp->vert);
+}
+
+static void
+tbl_char(struct termp *tp, char c, size_t len)
+{
+ size_t i, sz;
+ char cp[2];
+
+ cp[0] = c;
+ cp[1] = '\0';
+
+ sz = term_strlen(tp, cp);
+
+ for (i = 0; i < len; i += sz)
+ term_word(tp, cp);
+}
+
+static void
+tbl_literal(struct termp *tp, const struct tbl_dat *dp,
+ const struct roffcol *col)
+{
+ struct tbl_head *hp;
+ size_t width, len, padl, padr;
+ int spans;
+
+ assert(dp->string);
+ len = term_strlen(tp, dp->string);
+
+ hp = dp->layout->head->next;
+ width = col->width;
+ for (spans = dp->spans; spans--; hp = hp->next)
+ width += tp->tbl.cols[hp->ident].width + 3;
+
+ padr = width > len ? width - len : 0;
+ padl = 0;
+
+ switch (dp->layout->pos) {
+ case TBL_CELL_LONG:
+ padl = term_len(tp, 1);
+ padr = padr > padl ? padr - padl : 0;
+ break;
+ case TBL_CELL_CENTRE:
+ if (2 > padr)
+ break;
+ padl = padr / 2;
+ padr -= padl;
+ break;
+ case TBL_CELL_RIGHT:
+ padl = padr;
+ padr = 0;
+ break;
+ default:
+ break;
+ }
+
+ tbl_char(tp, ASCII_NBRSP, padl);
+ term_word(tp, dp->string);
+ tbl_char(tp, ASCII_NBRSP, padr);
+}
+
+static void
+tbl_number(struct termp *tp, const struct tbl_opts *opts,
+ const struct tbl_dat *dp,
+ const struct roffcol *col)
+{
+ char *cp;
+ char buf[2];
+ size_t sz, psz, ssz, d, padl;
+ int i;
+
+ /*
+ * See calc_data_number(). Left-pad by taking the offset of our
+ * and the maximum decimal; right-pad by the remaining amount.
+ */
+
+ assert(dp->string);
+
+ sz = term_strlen(tp, dp->string);
+
+ buf[0] = opts->decimal;
+ buf[1] = '\0';
+
+ psz = term_strlen(tp, buf);
+
+ if (NULL != (cp = strrchr(dp->string, opts->decimal))) {
+ buf[1] = '\0';
+ for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
+ buf[0] = dp->string[i];
+ ssz += term_strlen(tp, buf);
+ }
+ d = ssz + psz;
+ } else
+ d = sz + psz;
+
+ padl = col->decimal - d;
+
+ tbl_char(tp, ASCII_NBRSP, padl);
+ term_word(tp, dp->string);
+ if (col->width > sz + padl)
+ tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
+}
+
diff --git a/contrib/mdocml/term.c b/contrib/mdocml/term.c
new file mode 100644
index 0000000..4d50856
--- /dev/null
+++ b/contrib/mdocml/term.c
@@ -0,0 +1,808 @@
+/* $Id: term.c,v 1.226 2014/08/01 19:38:29 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "term.h"
+#include "main.h"
+
+static size_t cond_width(const struct termp *, int, int *);
+static void adjbuf(struct termp *p, size_t);
+static void bufferc(struct termp *, char);
+static void encode(struct termp *, const char *, size_t);
+static void encode1(struct termp *, int);
+
+
+void
+term_free(struct termp *p)
+{
+
+ if (p->buf)
+ free(p->buf);
+ if (p->symtab)
+ mchars_free(p->symtab);
+
+ free(p);
+}
+
+void
+term_begin(struct termp *p, term_margin head,
+ term_margin foot, const void *arg)
+{
+
+ p->headf = head;
+ p->footf = foot;
+ p->argf = arg;
+ (*p->begin)(p);
+}
+
+void
+term_end(struct termp *p)
+{
+
+ (*p->end)(p);
+}
+
+/*
+ * Flush a chunk of text. By default, break the output line each time
+ * the right margin is reached, and continue output on the next line
+ * at the same offset as the chunk itself. By default, also break the
+ * output line at the end of the chunk.
+ * The following flags may be specified:
+ *
+ * - TERMP_NOBREAK: Do not break the output line at the right margin,
+ * but only at the max right margin. Also, do not break the output
+ * line at the end of the chunk, such that the next call can pad to
+ * the next column. However, if less than p->trailspace blanks,
+ * which can be 0, 1, or 2, remain to the right margin, the line
+ * will be broken.
+ * - TERMP_BRIND: If the chunk does not fit and the output line has
+ * to be broken, start the next line at the right margin instead
+ * of at the offset. Used together with TERMP_NOBREAK for the tags
+ * in various kinds of tagged lists.
+ * - TERMP_DANGLE: Do not break the output line at the right margin,
+ * append the next chunk after it even if this one is too long.
+ * To be used together with TERMP_NOBREAK.
+ * - TERMP_HANG: Like TERMP_DANGLE, and also suppress padding before
+ * the next chunk if this column is not full.
+ */
+void
+term_flushln(struct termp *p)
+{
+ size_t i; /* current input position in p->buf */
+ int ntab; /* number of tabs to prepend */
+ size_t vis; /* current visual position on output */
+ size_t vbl; /* number of blanks to prepend to output */
+ size_t vend; /* end of word visual position on output */
+ size_t bp; /* visual right border position */
+ size_t dv; /* temporary for visual pos calculations */
+ size_t j; /* temporary loop index for p->buf */
+ size_t jhy; /* last hyph before overflow w/r/t j */
+ size_t maxvis; /* output position of visible boundary */
+ size_t mmax; /* used in calculating bp */
+
+ /*
+ * First, establish the maximum columns of "visible" content.
+ * This is usually the difference between the right-margin and
+ * an indentation, but can be, for tagged lists or columns, a
+ * small set of values.
+ *
+ * The following unsigned-signed subtractions look strange,
+ * but they are actually correct. If the int p->overstep
+ * is negative, it gets sign extended. Subtracting that
+ * very large size_t effectively adds a small number to dv.
+ */
+ assert (p->rmargin >= p->offset);
+ dv = p->rmargin - p->offset;
+ maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
+ dv = p->maxrmargin - p->offset;
+ mmax = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
+
+ bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
+
+ /*
+ * Calculate the required amount of padding.
+ */
+ vbl = p->offset + p->overstep > p->viscol ?
+ p->offset + p->overstep - p->viscol : 0;
+
+ vis = vend = 0;
+ i = 0;
+
+ while (i < p->col) {
+ /*
+ * Handle literal tab characters: collapse all
+ * subsequent tabs into a single huge set of spaces.
+ */
+ ntab = 0;
+ while (i < p->col && '\t' == p->buf[i]) {
+ vend = (vis / p->tabwidth + 1) * p->tabwidth;
+ vbl += vend - vis;
+ vis = vend;
+ ntab++;
+ i++;
+ }
+
+ /*
+ * Count up visible word characters. Control sequences
+ * (starting with the CSI) aren't counted. A space
+ * generates a non-printing word, which is valid (the
+ * space is printed according to regular spacing rules).
+ */
+
+ for (j = i, jhy = 0; j < p->col; j++) {
+ if (' ' == p->buf[j] || '\t' == p->buf[j])
+ break;
+
+ /* Back over the the last printed character. */
+ if (8 == p->buf[j]) {
+ assert(j);
+ vend -= (*p->width)(p, p->buf[j - 1]);
+ continue;
+ }
+
+ /* Regular word. */
+ /* Break at the hyphen point if we overrun. */
+ if (vend > vis && vend < bp &&
+ (ASCII_HYPH == p->buf[j] ||
+ ASCII_BREAK == p->buf[j]))
+ jhy = j;
+
+ /*
+ * Hyphenation now decided, put back a real
+ * hyphen such that we get the correct width.
+ */
+ if (ASCII_HYPH == p->buf[j])
+ p->buf[j] = '-';
+
+ vend += (*p->width)(p, p->buf[j]);
+ }
+
+ /*
+ * Find out whether we would exceed the right margin.
+ * If so, break to the next line.
+ */
+ if (vend > bp && 0 == jhy && vis > 0) {
+ vend -= vis;
+ (*p->endline)(p);
+ p->viscol = 0;
+ if (TERMP_BRIND & p->flags) {
+ vbl = p->rmargin;
+ vend += p->rmargin - p->offset;
+ } else
+ vbl = p->offset;
+
+ /* use pending tabs on the new line */
+
+ if (0 < ntab)
+ vbl += ntab * p->tabwidth;
+
+ /*
+ * Remove the p->overstep width.
+ * Again, if p->overstep is negative,
+ * sign extension does the right thing.
+ */
+
+ bp += (size_t)p->overstep;
+ p->overstep = 0;
+ }
+
+ /* Write out the [remaining] word. */
+ for ( ; i < p->col; i++) {
+ if (vend > bp && jhy > 0 && i > jhy)
+ break;
+ if ('\t' == p->buf[i])
+ break;
+ if (' ' == p->buf[i]) {
+ j = i;
+ while (' ' == p->buf[i])
+ i++;
+ dv = (i - j) * (*p->width)(p, ' ');
+ vbl += dv;
+ vend += dv;
+ break;
+ }
+ if (ASCII_NBRSP == p->buf[i]) {
+ vbl += (*p->width)(p, ' ');
+ continue;
+ }
+ if (ASCII_BREAK == p->buf[i])
+ continue;
+
+ /*
+ * Now we definitely know there will be
+ * printable characters to output,
+ * so write preceding white space now.
+ */
+ if (vbl) {
+ (*p->advance)(p, vbl);
+ p->viscol += vbl;
+ vbl = 0;
+ }
+
+ (*p->letter)(p, p->buf[i]);
+ if (8 == p->buf[i])
+ p->viscol -= (*p->width)(p, p->buf[i-1]);
+ else
+ p->viscol += (*p->width)(p, p->buf[i]);
+ }
+ vis = vend;
+ }
+
+ /*
+ * If there was trailing white space, it was not printed;
+ * so reset the cursor position accordingly.
+ */
+ if (vis)
+ vis -= vbl;
+
+ p->col = 0;
+ p->overstep = 0;
+
+ if ( ! (TERMP_NOBREAK & p->flags)) {
+ p->viscol = 0;
+ (*p->endline)(p);
+ return;
+ }
+
+ if (TERMP_HANG & p->flags) {
+ p->overstep = (int)(vis - maxvis +
+ p->trailspace * (*p->width)(p, ' '));
+
+ /*
+ * If we have overstepped the margin, temporarily move
+ * it to the right and flag the rest of the line to be
+ * shorter.
+ * If there is a request to keep the columns together,
+ * allow negative overstep when the column is not full.
+ */
+ if (p->trailspace && p->overstep < 0)
+ p->overstep = 0;
+ return;
+
+ } else if (TERMP_DANGLE & p->flags)
+ return;
+
+ /* If the column was overrun, break the line. */
+ if (maxvis < vis + p->trailspace * (*p->width)(p, ' ')) {
+ (*p->endline)(p);
+ p->viscol = 0;
+ }
+}
+
+/*
+ * A newline only breaks an existing line; it won't assert vertical
+ * space. All data in the output buffer is flushed prior to the newline
+ * assertion.
+ */
+void
+term_newln(struct termp *p)
+{
+
+ p->flags |= TERMP_NOSPACE;
+ if (p->col || p->viscol)
+ term_flushln(p);
+}
+
+/*
+ * Asserts a vertical space (a full, empty line-break between lines).
+ * Note that if used twice, this will cause two blank spaces and so on.
+ * All data in the output buffer is flushed prior to the newline
+ * assertion.
+ */
+void
+term_vspace(struct termp *p)
+{
+
+ term_newln(p);
+ p->viscol = 0;
+ if (0 < p->skipvsp)
+ p->skipvsp--;
+ else
+ (*p->endline)(p);
+}
+
+void
+term_fontlast(struct termp *p)
+{
+ enum termfont f;
+
+ f = p->fontl;
+ p->fontl = p->fontq[p->fonti];
+ p->fontq[p->fonti] = f;
+}
+
+void
+term_fontrepl(struct termp *p, enum termfont f)
+{
+
+ p->fontl = p->fontq[p->fonti];
+ p->fontq[p->fonti] = f;
+}
+
+void
+term_fontpush(struct termp *p, enum termfont f)
+{
+
+ assert(p->fonti + 1 < 10);
+ p->fontl = p->fontq[p->fonti];
+ p->fontq[++p->fonti] = f;
+}
+
+const void *
+term_fontq(struct termp *p)
+{
+
+ return(&p->fontq[p->fonti]);
+}
+
+enum termfont
+term_fonttop(struct termp *p)
+{
+
+ return(p->fontq[p->fonti]);
+}
+
+void
+term_fontpopq(struct termp *p, const void *key)
+{
+
+ while (p->fonti >= 0 && key < (void *)(p->fontq + p->fonti))
+ p->fonti--;
+ assert(p->fonti >= 0);
+}
+
+void
+term_fontpop(struct termp *p)
+{
+
+ assert(p->fonti);
+ p->fonti--;
+}
+
+/*
+ * Handle pwords, partial words, which may be either a single word or a
+ * phrase that cannot be broken down (such as a literal string). This
+ * handles word styling.
+ */
+void
+term_word(struct termp *p, const char *word)
+{
+ const char nbrsp[2] = { ASCII_NBRSP, 0 };
+ const char *seq, *cp;
+ char c;
+ int sz, uc;
+ size_t ssz;
+ enum mandoc_esc esc;
+
+ if ( ! (TERMP_NOSPACE & p->flags)) {
+ if ( ! (TERMP_KEEP & p->flags)) {
+ bufferc(p, ' ');
+ if (TERMP_SENTENCE & p->flags)
+ bufferc(p, ' ');
+ } else
+ bufferc(p, ASCII_NBRSP);
+ }
+ if (TERMP_PREKEEP & p->flags)
+ p->flags |= TERMP_KEEP;
+
+ if ( ! (p->flags & TERMP_NONOSPACE))
+ p->flags &= ~TERMP_NOSPACE;
+ else
+ p->flags |= TERMP_NOSPACE;
+
+ p->flags &= ~TERMP_SENTENCE;
+
+ while ('\0' != *word) {
+ if ('\\' != *word) {
+ if (TERMP_SKIPCHAR & p->flags) {
+ p->flags &= ~TERMP_SKIPCHAR;
+ word++;
+ continue;
+ }
+ if (TERMP_NBRWORD & p->flags) {
+ if (' ' == *word) {
+ encode(p, nbrsp, 1);
+ word++;
+ continue;
+ }
+ ssz = strcspn(word, "\\ ");
+ } else
+ ssz = strcspn(word, "\\");
+ encode(p, word, ssz);
+ word += (int)ssz;
+ continue;
+ }
+
+ word++;
+ esc = mandoc_escape(&word, &seq, &sz);
+ if (ESCAPE_ERROR == esc)
+ continue;
+
+ if (TERMENC_ASCII != p->enc)
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ uc = mchars_num2uc(seq + 1, sz - 1);
+ if ('\0' == uc)
+ break;
+ encode1(p, uc);
+ continue;
+ case ESCAPE_SPECIAL:
+ uc = mchars_spec2cp(p->symtab, seq, sz);
+ if (uc <= 0)
+ break;
+ encode1(p, uc);
+ continue;
+ default:
+ break;
+ }
+
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ encode1(p, '?');
+ break;
+ case ESCAPE_NUMBERED:
+ c = mchars_num2char(seq, sz);
+ if ('\0' != c)
+ encode(p, &c, 1);
+ break;
+ case ESCAPE_SPECIAL:
+ cp = mchars_spec2str(p->symtab, seq, sz, &ssz);
+ if (NULL != cp)
+ encode(p, cp, ssz);
+ else if (1 == ssz)
+ encode(p, seq, sz);
+ break;
+ case ESCAPE_FONTBOLD:
+ term_fontrepl(p, TERMFONT_BOLD);
+ break;
+ case ESCAPE_FONTITALIC:
+ term_fontrepl(p, TERMFONT_UNDER);
+ break;
+ case ESCAPE_FONTBI:
+ term_fontrepl(p, TERMFONT_BI);
+ break;
+ case ESCAPE_FONT:
+ /* FALLTHROUGH */
+ case ESCAPE_FONTROMAN:
+ term_fontrepl(p, TERMFONT_NONE);
+ break;
+ case ESCAPE_FONTPREV:
+ term_fontlast(p);
+ break;
+ case ESCAPE_NOSPACE:
+ if (TERMP_SKIPCHAR & p->flags)
+ p->flags &= ~TERMP_SKIPCHAR;
+ else if ('\0' == *word)
+ p->flags |= TERMP_NOSPACE;
+ break;
+ case ESCAPE_SKIPCHAR:
+ p->flags |= TERMP_SKIPCHAR;
+ break;
+ default:
+ break;
+ }
+ }
+ p->flags &= ~TERMP_NBRWORD;
+}
+
+static void
+adjbuf(struct termp *p, size_t sz)
+{
+
+ if (0 == p->maxcols)
+ p->maxcols = 1024;
+ while (sz >= p->maxcols)
+ p->maxcols <<= 2;
+
+ p->buf = mandoc_reallocarray(p->buf, p->maxcols, sizeof(int));
+}
+
+static void
+bufferc(struct termp *p, char c)
+{
+
+ if (p->col + 1 >= p->maxcols)
+ adjbuf(p, p->col + 1);
+
+ p->buf[p->col++] = c;
+}
+
+/*
+ * See encode().
+ * Do this for a single (probably unicode) value.
+ * Does not check for non-decorated glyphs.
+ */
+static void
+encode1(struct termp *p, int c)
+{
+ enum termfont f;
+
+ if (TERMP_SKIPCHAR & p->flags) {
+ p->flags &= ~TERMP_SKIPCHAR;
+ return;
+ }
+
+ if (p->col + 6 >= p->maxcols)
+ adjbuf(p, p->col + 6);
+
+ f = term_fonttop(p);
+
+ if (TERMFONT_UNDER == f || TERMFONT_BI == f) {
+ p->buf[p->col++] = '_';
+ p->buf[p->col++] = 8;
+ }
+ if (TERMFONT_BOLD == f || TERMFONT_BI == f) {
+ if (ASCII_HYPH == c)
+ p->buf[p->col++] = '-';
+ else
+ p->buf[p->col++] = c;
+ p->buf[p->col++] = 8;
+ }
+ p->buf[p->col++] = c;
+}
+
+static void
+encode(struct termp *p, const char *word, size_t sz)
+{
+ size_t i;
+
+ if (TERMP_SKIPCHAR & p->flags) {
+ p->flags &= ~TERMP_SKIPCHAR;
+ return;
+ }
+
+ /*
+ * Encode and buffer a string of characters. If the current
+ * font mode is unset, buffer directly, else encode then buffer
+ * character by character.
+ */
+
+ if (TERMFONT_NONE == term_fonttop(p)) {
+ if (p->col + sz >= p->maxcols)
+ adjbuf(p, p->col + sz);
+ for (i = 0; i < sz; i++)
+ p->buf[p->col++] = word[i];
+ return;
+ }
+
+ /* Pre-buffer, assuming worst-case. */
+
+ if (p->col + 1 + (sz * 5) >= p->maxcols)
+ adjbuf(p, p->col + 1 + (sz * 5));
+
+ for (i = 0; i < sz; i++) {
+ if (ASCII_HYPH == word[i] ||
+ isgraph((unsigned char)word[i]))
+ encode1(p, word[i]);
+ else
+ p->buf[p->col++] = word[i];
+ }
+}
+
+void
+term_setwidth(struct termp *p, const char *wstr)
+{
+ struct roffsu su;
+ size_t width;
+ int iop;
+
+ iop = 0;
+ width = 0;
+ if (NULL != wstr) {
+ switch (*wstr) {
+ case '+':
+ iop = 1;
+ wstr++;
+ break;
+ case '-':
+ iop = -1;
+ wstr++;
+ break;
+ default:
+ break;
+ }
+ if (a2roffsu(wstr, &su, SCALE_MAX))
+ width = term_hspan(p, &su);
+ else
+ iop = 0;
+ }
+ (*p->setwidth)(p, iop, width);
+}
+
+size_t
+term_len(const struct termp *p, size_t sz)
+{
+
+ return((*p->width)(p, ' ') * sz);
+}
+
+static size_t
+cond_width(const struct termp *p, int c, int *skip)
+{
+
+ if (*skip) {
+ (*skip) = 0;
+ return(0);
+ } else
+ return((*p->width)(p, c));
+}
+
+size_t
+term_strlen(const struct termp *p, const char *cp)
+{
+ size_t sz, rsz, i;
+ int ssz, skip, c;
+ const char *seq, *rhs;
+ enum mandoc_esc esc;
+ static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
+ ASCII_BREAK, '\0' };
+
+ /*
+ * Account for escaped sequences within string length
+ * calculations. This follows the logic in term_word() as we
+ * must calculate the width of produced strings.
+ */
+
+ sz = 0;
+ skip = 0;
+ while ('\0' != *cp) {
+ rsz = strcspn(cp, rej);
+ for (i = 0; i < rsz; i++)
+ sz += cond_width(p, *cp++, &skip);
+
+ switch (*cp) {
+ case '\\':
+ cp++;
+ esc = mandoc_escape(&cp, &seq, &ssz);
+ if (ESCAPE_ERROR == esc)
+ continue;
+
+ if (TERMENC_ASCII != p->enc)
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ c = mchars_num2uc(seq + 1,
+ ssz - 1);
+ if ('\0' == c)
+ break;
+ sz += cond_width(p, c, &skip);
+ continue;
+ case ESCAPE_SPECIAL:
+ c = mchars_spec2cp(p->symtab,
+ seq, ssz);
+ if (c <= 0)
+ break;
+ sz += cond_width(p, c, &skip);
+ continue;
+ default:
+ break;
+ }
+
+ rhs = NULL;
+
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ sz += cond_width(p, '?', &skip);
+ break;
+ case ESCAPE_NUMBERED:
+ c = mchars_num2char(seq, ssz);
+ if ('\0' != c)
+ sz += cond_width(p, c, &skip);
+ break;
+ case ESCAPE_SPECIAL:
+ rhs = mchars_spec2str(p->symtab,
+ seq, ssz, &rsz);
+
+ if (ssz != 1 || rhs)
+ break;
+
+ rhs = seq;
+ rsz = ssz;
+ break;
+ case ESCAPE_SKIPCHAR:
+ skip = 1;
+ break;
+ default:
+ break;
+ }
+
+ if (NULL == rhs)
+ break;
+
+ if (skip) {
+ skip = 0;
+ break;
+ }
+
+ for (i = 0; i < rsz; i++)
+ sz += (*p->width)(p, *rhs++);
+ break;
+ case ASCII_NBRSP:
+ sz += cond_width(p, ' ', &skip);
+ cp++;
+ break;
+ case ASCII_HYPH:
+ sz += cond_width(p, '-', &skip);
+ cp++;
+ /* FALLTHROUGH */
+ case ASCII_BREAK:
+ break;
+ default:
+ break;
+ }
+ }
+
+ return(sz);
+}
+
+size_t
+term_vspan(const struct termp *p, const struct roffsu *su)
+{
+ double r;
+
+ switch (su->unit) {
+ case SCALE_CM:
+ r = su->scale * 2.0;
+ break;
+ case SCALE_IN:
+ r = su->scale * 6.0;
+ break;
+ case SCALE_PC:
+ r = su->scale;
+ break;
+ case SCALE_PT:
+ r = su->scale / 8.0;
+ break;
+ case SCALE_MM:
+ r = su->scale / 1000.0;
+ break;
+ case SCALE_VS:
+ r = su->scale;
+ break;
+ default:
+ r = su->scale - 1.0;
+ break;
+ }
+
+ if (r < 0.0)
+ r = 0.0;
+ return((size_t)(r + 0.0005));
+}
+
+size_t
+term_hspan(const struct termp *p, const struct roffsu *su)
+{
+ double v;
+
+ v = (*p->hspan)(p, su);
+ if (v < 0.0)
+ v = 0.0;
+ return((size_t)(v + 0.0005));
+}
diff --git a/contrib/mdocml/term.h b/contrib/mdocml/term.h
new file mode 100644
index 0000000..decd7f4
--- /dev/null
+++ b/contrib/mdocml/term.h
@@ -0,0 +1,136 @@
+/* $Id: term.h,v 1.101 2014/04/20 16:46:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef TERM_H
+#define TERM_H
+
+__BEGIN_DECLS
+
+struct termp;
+
+enum termenc {
+ TERMENC_ASCII,
+ TERMENC_LOCALE,
+ TERMENC_UTF8
+};
+
+enum termtype {
+ TERMTYPE_CHAR,
+ TERMTYPE_PS,
+ TERMTYPE_PDF
+};
+
+enum termfont {
+ TERMFONT_NONE = 0,
+ TERMFONT_BOLD,
+ TERMFONT_UNDER,
+ TERMFONT_BI,
+ TERMFONT__MAX
+};
+
+#define TERM_MAXMARGIN 100000 /* FIXME */
+
+typedef void (*term_margin)(struct termp *, const void *);
+
+struct termp_tbl {
+ int width; /* width in fixed chars */
+ int decimal; /* decimal point position */
+};
+
+struct termp {
+ enum termtype type;
+ struct rofftbl tbl; /* table configuration */
+ int mdocstyle; /* imitate mdoc(7) output */
+ size_t defindent; /* Default indent for text. */
+ size_t defrmargin; /* Right margin of the device. */
+ size_t lastrmargin; /* Right margin before the last ll. */
+ size_t rmargin; /* Current right margin. */
+ size_t maxrmargin; /* Max right margin. */
+ size_t maxcols; /* Max size of buf. */
+ size_t offset; /* Margin offest. */
+ size_t tabwidth; /* Distance of tab positions. */
+ size_t col; /* Bytes in buf. */
+ size_t viscol; /* Chars on current line. */
+ size_t trailspace; /* See termp_flushln(). */
+ int overstep; /* See termp_flushln(). */
+ int skipvsp; /* Vertical space to skip. */
+ int flags;
+#define TERMP_SENTENCE (1 << 1) /* Space before a sentence. */
+#define TERMP_NOSPACE (1 << 2) /* No space before words. */
+#define TERMP_NONOSPACE (1 << 3) /* No space (no autounset). */
+#define TERMP_NBRWORD (1 << 4) /* Make next word nonbreaking. */
+#define TERMP_KEEP (1 << 5) /* Keep words together. */
+#define TERMP_PREKEEP (1 << 6) /* ...starting with the next one. */
+#define TERMP_SKIPCHAR (1 << 7) /* Skip the next character. */
+#define TERMP_NOBREAK (1 << 8) /* See term_flushln(). */
+#define TERMP_BRIND (1 << 9) /* See term_flushln(). */
+#define TERMP_DANGLE (1 << 10) /* See term_flushln(). */
+#define TERMP_HANG (1 << 11) /* See term_flushln(). */
+#define TERMP_NOSPLIT (1 << 12) /* See termp_an_pre/post(). */
+#define TERMP_SPLIT (1 << 13) /* See termp_an_pre/post(). */
+#define TERMP_ANPREC (1 << 14) /* See termp_an_pre(). */
+ int *buf; /* Output buffer. */
+ enum termenc enc; /* Type of encoding. */
+ struct mchars *symtab; /* Encoded-symbol table. */
+ enum termfont fontl; /* Last font set. */
+ enum termfont fontq[10]; /* Symmetric fonts. */
+ int fonti; /* Index of font stack. */
+ term_margin headf; /* invoked to print head */
+ term_margin footf; /* invoked to print foot */
+ void (*letter)(struct termp *, int);
+ void (*begin)(struct termp *);
+ void (*end)(struct termp *);
+ void (*endline)(struct termp *);
+ void (*advance)(struct termp *, size_t);
+ void (*setwidth)(struct termp *, int, size_t);
+ size_t (*width)(const struct termp *, int);
+ double (*hspan)(const struct termp *,
+ const struct roffsu *);
+ const void *argf; /* arg for headf/footf */
+ struct termp_ps *ps;
+};
+
+void term_eqn(struct termp *, const struct eqn *);
+void term_tbl(struct termp *, const struct tbl_span *);
+void term_free(struct termp *);
+void term_newln(struct termp *);
+void term_vspace(struct termp *);
+void term_word(struct termp *, const char *);
+void term_flushln(struct termp *);
+void term_begin(struct termp *, term_margin,
+ term_margin, const void *);
+void term_end(struct termp *);
+
+void term_setwidth(struct termp *, const char *);
+size_t term_hspan(const struct termp *,
+ const struct roffsu *);
+size_t term_vspan(const struct termp *,
+ const struct roffsu *);
+size_t term_strlen(const struct termp *, const char *);
+size_t term_len(const struct termp *, size_t);
+
+enum termfont term_fonttop(struct termp *);
+const void *term_fontq(struct termp *);
+void term_fontpush(struct termp *, enum termfont);
+void term_fontpop(struct termp *);
+void term_fontpopq(struct termp *, const void *);
+void term_fontrepl(struct termp *, enum termfont);
+void term_fontlast(struct termp *);
+
+__END_DECLS
+
+#endif /*!TERM_H*/
diff --git a/contrib/mdocml/term_ascii.c b/contrib/mdocml/term_ascii.c
new file mode 100644
index 0000000..b1e328c
--- /dev/null
+++ b/contrib/mdocml/term_ascii.c
@@ -0,0 +1,302 @@
+/* $Id: term_ascii.c,v 1.27 2014/08/01 19:25:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#ifdef USE_WCHAR
+# include <locale.h>
+#endif
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef USE_WCHAR
+# include <wchar.h>
+#endif
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "term.h"
+#include "main.h"
+
+/*
+ * Sadly, this doesn't seem to be defined on systems even when they
+ * support it. For the time being, remove it and let those compiling
+ * the software decide for themselves what to use.
+ */
+#if 0
+#if ! defined(__STDC_ISO_10646__)
+# undef USE_WCHAR
+#endif
+#endif
+
+static struct termp *ascii_init(enum termenc, char *);
+static double ascii_hspan(const struct termp *,
+ const struct roffsu *);
+static size_t ascii_width(const struct termp *, int);
+static void ascii_advance(struct termp *, size_t);
+static void ascii_begin(struct termp *);
+static void ascii_end(struct termp *);
+static void ascii_endline(struct termp *);
+static void ascii_letter(struct termp *, int);
+static void ascii_setwidth(struct termp *, int, size_t);
+
+#ifdef USE_WCHAR
+static void locale_advance(struct termp *, size_t);
+static void locale_endline(struct termp *);
+static void locale_letter(struct termp *, int);
+static size_t locale_width(const struct termp *, int);
+#endif
+
+
+static struct termp *
+ascii_init(enum termenc enc, char *outopts)
+{
+ const char *toks[4];
+ char *v;
+ struct termp *p;
+
+ p = mandoc_calloc(1, sizeof(struct termp));
+
+ p->tabwidth = 5;
+ p->defrmargin = p->lastrmargin = 78;
+
+ p->begin = ascii_begin;
+ p->end = ascii_end;
+ p->hspan = ascii_hspan;
+ p->type = TERMTYPE_CHAR;
+
+ p->enc = TERMENC_ASCII;
+ p->advance = ascii_advance;
+ p->endline = ascii_endline;
+ p->letter = ascii_letter;
+ p->setwidth = ascii_setwidth;
+ p->width = ascii_width;
+
+#ifdef USE_WCHAR
+ if (TERMENC_ASCII != enc) {
+ v = TERMENC_LOCALE == enc ?
+ setlocale(LC_ALL, "") :
+ setlocale(LC_CTYPE, "en_US.UTF-8");
+ if (NULL != v && MB_CUR_MAX > 1) {
+ p->enc = enc;
+ p->advance = locale_advance;
+ p->endline = locale_endline;
+ p->letter = locale_letter;
+ p->width = locale_width;
+ }
+ }
+#endif
+
+ toks[0] = "indent";
+ toks[1] = "width";
+ toks[2] = "mdoc";
+ toks[3] = NULL;
+
+ while (outopts && *outopts)
+ switch (getsubopt(&outopts, UNCONST(toks), &v)) {
+ case 0:
+ p->defindent = (size_t)atoi(v);
+ break;
+ case 1:
+ p->defrmargin = (size_t)atoi(v);
+ break;
+ case 2:
+ /*
+ * Temporary, undocumented mode
+ * to imitate mdoc(7) output style.
+ */
+ p->mdocstyle = 1;
+ p->defindent = 5;
+ break;
+ default:
+ break;
+ }
+
+ /* Enforce a lower boundary. */
+ if (p->defrmargin < 58)
+ p->defrmargin = 58;
+
+ return(p);
+}
+
+void *
+ascii_alloc(char *outopts)
+{
+
+ return(ascii_init(TERMENC_ASCII, outopts));
+}
+
+void *
+utf8_alloc(char *outopts)
+{
+
+ return(ascii_init(TERMENC_UTF8, outopts));
+}
+
+void *
+locale_alloc(char *outopts)
+{
+
+ return(ascii_init(TERMENC_LOCALE, outopts));
+}
+
+static void
+ascii_setwidth(struct termp *p, int iop, size_t width)
+{
+
+ p->rmargin = p->defrmargin;
+ if (0 < iop)
+ p->defrmargin += width;
+ else if (0 > iop)
+ p->defrmargin -= width;
+ else
+ p->defrmargin = width ? width : p->lastrmargin;
+ p->lastrmargin = p->rmargin;
+ p->rmargin = p->maxrmargin = p->defrmargin;
+}
+
+static size_t
+ascii_width(const struct termp *p, int c)
+{
+
+ return(1);
+}
+
+void
+ascii_free(void *arg)
+{
+
+ term_free((struct termp *)arg);
+}
+
+static void
+ascii_letter(struct termp *p, int c)
+{
+
+ putchar(c);
+}
+
+static void
+ascii_begin(struct termp *p)
+{
+
+ (*p->headf)(p, p->argf);
+}
+
+static void
+ascii_end(struct termp *p)
+{
+
+ (*p->footf)(p, p->argf);
+}
+
+static void
+ascii_endline(struct termp *p)
+{
+
+ putchar('\n');
+}
+
+static void
+ascii_advance(struct termp *p, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ putchar(' ');
+}
+
+static double
+ascii_hspan(const struct termp *p, const struct roffsu *su)
+{
+ double r;
+
+ /*
+ * Approximate based on character width. These are generated
+ * entirely by eyeballing the screen, but appear to be correct.
+ */
+
+ switch (su->unit) {
+ case SCALE_CM:
+ r = su->scale * 4.0;
+ break;
+ case SCALE_IN:
+ r = su->scale * 10.0;
+ break;
+ case SCALE_PC:
+ r = (su->scale * 10.0) / 6.0;
+ break;
+ case SCALE_PT:
+ r = (su->scale * 10.0) / 72.0;
+ break;
+ case SCALE_MM:
+ r = su->scale / 1000.0;
+ break;
+ case SCALE_VS:
+ r = su->scale * 2.0 - 1.0;
+ break;
+ default:
+ r = su->scale;
+ break;
+ }
+
+ return(r);
+}
+
+#ifdef USE_WCHAR
+static size_t
+locale_width(const struct termp *p, int c)
+{
+ int rc;
+
+ if (c == ASCII_NBRSP)
+ c = ' ';
+ rc = wcwidth(c);
+ if (rc < 0)
+ rc = 0;
+ return(rc);
+}
+
+static void
+locale_advance(struct termp *p, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ putwchar(L' ');
+}
+
+static void
+locale_endline(struct termp *p)
+{
+
+ putwchar(L'\n');
+}
+
+static void
+locale_letter(struct termp *p, int c)
+{
+
+ putwchar(c);
+}
+#endif
diff --git a/contrib/mdocml/term_ps.c b/contrib/mdocml/term_ps.c
new file mode 100644
index 0000000..96edc6d
--- /dev/null
+++ b/contrib/mdocml/term_ps.c
@@ -0,0 +1,1167 @@
+/* $Id: term_ps.c,v 1.62 2014/08/01 19:25:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "main.h"
+#include "term.h"
+
+/* These work the buffer used by the header and footer. */
+#define PS_BUFSLOP 128
+
+/* Convert PostScript point "x" to an AFM unit. */
+#define PNT2AFM(p, x) \
+ (size_t)((double)(x) * (1000.0 / (double)(p)->ps->scale))
+
+/* Convert an AFM unit "x" to a PostScript points */
+#define AFM2PNT(p, x) \
+ ((double)(x) / (1000.0 / (double)(p)->ps->scale))
+
+struct glyph {
+ unsigned short wx; /* WX in AFM */
+};
+
+struct font {
+ const char *name; /* FontName in AFM */
+#define MAXCHAR 95 /* total characters we can handle */
+ struct glyph gly[MAXCHAR]; /* glyph metrics */
+};
+
+struct termp_ps {
+ int flags;
+#define PS_INLINE (1 << 0) /* we're in a word */
+#define PS_MARGINS (1 << 1) /* we're in the margins */
+#define PS_NEWPAGE (1 << 2) /* new page, no words yet */
+ size_t pscol; /* visible column (AFM units) */
+ size_t psrow; /* visible row (AFM units) */
+ char *psmarg; /* margin buf */
+ size_t psmargsz; /* margin buf size */
+ size_t psmargcur; /* cur index in margin buf */
+ char last; /* character buffer */
+ enum termfont lastf; /* last set font */
+ size_t scale; /* font scaling factor */
+ size_t pages; /* number of pages shown */
+ size_t lineheight; /* line height (AFM units) */
+ size_t top; /* body top (AFM units) */
+ size_t bottom; /* body bottom (AFM units) */
+ size_t height; /* page height (AFM units */
+ size_t width; /* page width (AFM units) */
+ size_t lastwidth; /* page width before last ll */
+ size_t left; /* body left (AFM units) */
+ size_t header; /* header pos (AFM units) */
+ size_t footer; /* footer pos (AFM units) */
+ size_t pdfbytes; /* current output byte */
+ size_t pdflastpg; /* byte of last page mark */
+ size_t pdfbody; /* start of body object */
+ size_t *pdfobjs; /* table of object offsets */
+ size_t pdfobjsz; /* size of pdfobjs */
+};
+
+static double ps_hspan(const struct termp *,
+ const struct roffsu *);
+static size_t ps_width(const struct termp *, int);
+static void ps_advance(struct termp *, size_t);
+static void ps_begin(struct termp *);
+static void ps_closepage(struct termp *);
+static void ps_end(struct termp *);
+static void ps_endline(struct termp *);
+static void ps_fclose(struct termp *);
+static void ps_growbuf(struct termp *, size_t);
+static void ps_letter(struct termp *, int);
+static void ps_pclose(struct termp *);
+static void ps_pletter(struct termp *, int);
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 2, 3)))
+#endif
+static void ps_printf(struct termp *, const char *, ...);
+static void ps_putchar(struct termp *, char);
+static void ps_setfont(struct termp *, enum termfont);
+static void ps_setwidth(struct termp *, int, size_t);
+static struct termp *pspdf_alloc(char *);
+static void pdf_obj(struct termp *, size_t);
+
+/*
+ * We define, for the time being, three fonts: bold, oblique/italic, and
+ * normal (roman). The following table hard-codes the font metrics for
+ * ASCII, i.e., 32--127.
+ */
+
+static const struct font fonts[TERMFONT__MAX] = {
+ { "Times-Roman", {
+ { 250 },
+ { 333 },
+ { 408 },
+ { 500 },
+ { 500 },
+ { 833 },
+ { 778 },
+ { 333 },
+ { 333 },
+ { 333 },
+ { 500 },
+ { 564 },
+ { 250 },
+ { 333 },
+ { 250 },
+ { 278 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 278 },
+ { 278 },
+ { 564 },
+ { 564 },
+ { 564 },
+ { 444 },
+ { 921 },
+ { 722 },
+ { 667 },
+ { 667 },
+ { 722 },
+ { 611 },
+ { 556 },
+ { 722 },
+ { 722 },
+ { 333 },
+ { 389 },
+ { 722 },
+ { 611 },
+ { 889 },
+ { 722 },
+ { 722 },
+ { 556 },
+ { 722 },
+ { 667 },
+ { 556 },
+ { 611 },
+ { 722 },
+ { 722 },
+ { 944 },
+ { 722 },
+ { 722 },
+ { 611 },
+ { 333 },
+ { 278 },
+ { 333 },
+ { 469 },
+ { 500 },
+ { 333 },
+ { 444 },
+ { 500 },
+ { 444 },
+ { 500},
+ { 444},
+ { 333},
+ { 500},
+ { 500},
+ { 278},
+ { 278},
+ { 500},
+ { 278},
+ { 778},
+ { 500},
+ { 500},
+ { 500},
+ { 500},
+ { 333},
+ { 389},
+ { 278},
+ { 500},
+ { 500},
+ { 722},
+ { 500},
+ { 500},
+ { 444},
+ { 480},
+ { 200},
+ { 480},
+ { 541},
+ } },
+ { "Times-Bold", {
+ { 250 },
+ { 333 },
+ { 555 },
+ { 500 },
+ { 500 },
+ { 1000 },
+ { 833 },
+ { 333 },
+ { 333 },
+ { 333 },
+ { 500 },
+ { 570 },
+ { 250 },
+ { 333 },
+ { 250 },
+ { 278 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 333 },
+ { 333 },
+ { 570 },
+ { 570 },
+ { 570 },
+ { 500 },
+ { 930 },
+ { 722 },
+ { 667 },
+ { 722 },
+ { 722 },
+ { 667 },
+ { 611 },
+ { 778 },
+ { 778 },
+ { 389 },
+ { 500 },
+ { 778 },
+ { 667 },
+ { 944 },
+ { 722 },
+ { 778 },
+ { 611 },
+ { 778 },
+ { 722 },
+ { 556 },
+ { 667 },
+ { 722 },
+ { 722 },
+ { 1000 },
+ { 722 },
+ { 722 },
+ { 667 },
+ { 333 },
+ { 278 },
+ { 333 },
+ { 581 },
+ { 500 },
+ { 333 },
+ { 500 },
+ { 556 },
+ { 444 },
+ { 556 },
+ { 444 },
+ { 333 },
+ { 500 },
+ { 556 },
+ { 278 },
+ { 333 },
+ { 556 },
+ { 278 },
+ { 833 },
+ { 556 },
+ { 500 },
+ { 556 },
+ { 556 },
+ { 444 },
+ { 389 },
+ { 333 },
+ { 556 },
+ { 500 },
+ { 722 },
+ { 500 },
+ { 500 },
+ { 444 },
+ { 394 },
+ { 220 },
+ { 394 },
+ { 520 },
+ } },
+ { "Times-Italic", {
+ { 250 },
+ { 333 },
+ { 420 },
+ { 500 },
+ { 500 },
+ { 833 },
+ { 778 },
+ { 333 },
+ { 333 },
+ { 333 },
+ { 500 },
+ { 675 },
+ { 250 },
+ { 333 },
+ { 250 },
+ { 278 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 333 },
+ { 333 },
+ { 675 },
+ { 675 },
+ { 675 },
+ { 500 },
+ { 920 },
+ { 611 },
+ { 611 },
+ { 667 },
+ { 722 },
+ { 611 },
+ { 611 },
+ { 722 },
+ { 722 },
+ { 333 },
+ { 444 },
+ { 667 },
+ { 556 },
+ { 833 },
+ { 667 },
+ { 722 },
+ { 611 },
+ { 722 },
+ { 611 },
+ { 500 },
+ { 556 },
+ { 722 },
+ { 611 },
+ { 833 },
+ { 611 },
+ { 556 },
+ { 556 },
+ { 389 },
+ { 278 },
+ { 389 },
+ { 422 },
+ { 500 },
+ { 333 },
+ { 500 },
+ { 500 },
+ { 444 },
+ { 500 },
+ { 444 },
+ { 278 },
+ { 500 },
+ { 500 },
+ { 278 },
+ { 278 },
+ { 444 },
+ { 278 },
+ { 722 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 389 },
+ { 389 },
+ { 278 },
+ { 500 },
+ { 444 },
+ { 667 },
+ { 444 },
+ { 444 },
+ { 389 },
+ { 400 },
+ { 275 },
+ { 400 },
+ { 541 },
+ } },
+};
+
+void *
+pdf_alloc(char *outopts)
+{
+ struct termp *p;
+
+ if (NULL != (p = pspdf_alloc(outopts)))
+ p->type = TERMTYPE_PDF;
+
+ return(p);
+}
+
+void *
+ps_alloc(char *outopts)
+{
+ struct termp *p;
+
+ if (NULL != (p = pspdf_alloc(outopts)))
+ p->type = TERMTYPE_PS;
+
+ return(p);
+}
+
+static struct termp *
+pspdf_alloc(char *outopts)
+{
+ struct termp *p;
+ unsigned int pagex, pagey;
+ size_t marginx, marginy, lineheight;
+ const char *toks[2];
+ const char *pp;
+ char *v;
+
+ p = mandoc_calloc(1, sizeof(struct termp));
+ p->enc = TERMENC_ASCII;
+ p->ps = mandoc_calloc(1, sizeof(struct termp_ps));
+
+ p->advance = ps_advance;
+ p->begin = ps_begin;
+ p->end = ps_end;
+ p->endline = ps_endline;
+ p->hspan = ps_hspan;
+ p->letter = ps_letter;
+ p->setwidth = ps_setwidth;
+ p->width = ps_width;
+
+ toks[0] = "paper";
+ toks[1] = NULL;
+
+ pp = NULL;
+
+ while (outopts && *outopts)
+ switch (getsubopt(&outopts, UNCONST(toks), &v)) {
+ case 0:
+ pp = v;
+ break;
+ default:
+ break;
+ }
+
+ /* Default to US letter (millimetres). */
+
+ pagex = 216;
+ pagey = 279;
+
+ /*
+ * The ISO-269 paper sizes can be calculated automatically, but
+ * it would require bringing in -lm for pow() and I'd rather not
+ * do that. So just do it the easy way for now. Since this
+ * only happens once, I'm not terribly concerned.
+ */
+
+ if (pp && strcasecmp(pp, "letter")) {
+ if (0 == strcasecmp(pp, "a3")) {
+ pagex = 297;
+ pagey = 420;
+ } else if (0 == strcasecmp(pp, "a4")) {
+ pagex = 210;
+ pagey = 297;
+ } else if (0 == strcasecmp(pp, "a5")) {
+ pagex = 148;
+ pagey = 210;
+ } else if (0 == strcasecmp(pp, "legal")) {
+ pagex = 216;
+ pagey = 356;
+ } else if (2 != sscanf(pp, "%ux%u", &pagex, &pagey))
+ fprintf(stderr, "%s: Unknown paper\n", pp);
+ }
+
+ /*
+ * This MUST be defined before any PNT2AFM or AFM2PNT
+ * calculations occur.
+ */
+
+ p->ps->scale = 11;
+
+ /* Remember millimetres -> AFM units. */
+
+ pagex = PNT2AFM(p, ((double)pagex * 2.834));
+ pagey = PNT2AFM(p, ((double)pagey * 2.834));
+
+ /* Margins are 1/9 the page x and y. */
+
+ marginx = (size_t)((double)pagex / 9.0);
+ marginy = (size_t)((double)pagey / 9.0);
+
+ /* Line-height is 1.4em. */
+
+ lineheight = PNT2AFM(p, ((double)p->ps->scale * 1.4));
+
+ p->ps->width = p->ps->lastwidth = (size_t)pagex;
+ p->ps->height = (size_t)pagey;
+ p->ps->header = pagey - (marginy / 2) - (lineheight / 2);
+ p->ps->top = pagey - marginy;
+ p->ps->footer = (marginy / 2) - (lineheight / 2);
+ p->ps->bottom = marginy;
+ p->ps->left = marginx;
+ p->ps->lineheight = lineheight;
+
+ p->defrmargin = pagex - (marginx * 2);
+ return(p);
+}
+
+static void
+ps_setwidth(struct termp *p, int iop, size_t width)
+{
+ size_t lastwidth;
+
+ lastwidth = p->ps->width;
+ if (0 < iop)
+ p->ps->width += width;
+ else if (0 > iop)
+ p->ps->width -= width;
+ else
+ p->ps->width = width ? width : p->ps->lastwidth;
+ p->ps->lastwidth = lastwidth;
+}
+
+void
+pspdf_free(void *arg)
+{
+ struct termp *p;
+
+ p = (struct termp *)arg;
+
+ if (p->ps->psmarg)
+ free(p->ps->psmarg);
+ if (p->ps->pdfobjs)
+ free(p->ps->pdfobjs);
+
+ free(p->ps);
+ term_free(p);
+}
+
+static void
+ps_printf(struct termp *p, const char *fmt, ...)
+{
+ va_list ap;
+ int pos, len;
+
+ va_start(ap, fmt);
+
+ /*
+ * If we're running in regular mode, then pipe directly into
+ * vprintf(). If we're processing margins, then push the data
+ * into our growable margin buffer.
+ */
+
+ if ( ! (PS_MARGINS & p->ps->flags)) {
+ len = vprintf(fmt, ap);
+ va_end(ap);
+ p->ps->pdfbytes += len < 0 ? 0 : (size_t)len;
+ return;
+ }
+
+ /*
+ * XXX: I assume that the in-margin print won't exceed
+ * PS_BUFSLOP (128 bytes), which is reasonable but still an
+ * assumption that will cause pukeage if it's not the case.
+ */
+
+ ps_growbuf(p, PS_BUFSLOP);
+
+ pos = (int)p->ps->psmargcur;
+ vsnprintf(&p->ps->psmarg[pos], PS_BUFSLOP, fmt, ap);
+
+ va_end(ap);
+
+ p->ps->psmargcur = strlen(p->ps->psmarg);
+}
+
+static void
+ps_putchar(struct termp *p, char c)
+{
+ int pos;
+
+ /* See ps_printf(). */
+
+ if ( ! (PS_MARGINS & p->ps->flags)) {
+ putchar(c);
+ p->ps->pdfbytes++;
+ return;
+ }
+
+ ps_growbuf(p, 2);
+
+ pos = (int)p->ps->psmargcur++;
+ p->ps->psmarg[pos++] = c;
+ p->ps->psmarg[pos] = '\0';
+}
+
+static void
+pdf_obj(struct termp *p, size_t obj)
+{
+
+ assert(obj > 0);
+
+ if ((obj - 1) >= p->ps->pdfobjsz) {
+ p->ps->pdfobjsz = obj + 128;
+ p->ps->pdfobjs = mandoc_reallocarray(p->ps->pdfobjs,
+ p->ps->pdfobjsz, sizeof(size_t));
+ }
+
+ p->ps->pdfobjs[(int)obj - 1] = p->ps->pdfbytes;
+ ps_printf(p, "%zu 0 obj\n", obj);
+}
+
+static void
+ps_closepage(struct termp *p)
+{
+ int i;
+ size_t len, base;
+
+ /*
+ * Close out a page that we've already flushed to output. In
+ * PostScript, we simply note that the page must be showed. In
+ * PDF, we must now create the Length, Resource, and Page node
+ * for the page contents.
+ */
+
+ assert(p->ps->psmarg && p->ps->psmarg[0]);
+ ps_printf(p, "%s", p->ps->psmarg);
+
+ if (TERMTYPE_PS != p->type) {
+ ps_printf(p, "ET\n");
+
+ len = p->ps->pdfbytes - p->ps->pdflastpg;
+ base = p->ps->pages * 4 + p->ps->pdfbody;
+
+ ps_printf(p, "endstream\nendobj\n");
+
+ /* Length of content. */
+ pdf_obj(p, base + 1);
+ ps_printf(p, "%zu\nendobj\n", len);
+
+ /* Resource for content. */
+ pdf_obj(p, base + 2);
+ ps_printf(p, "<<\n/ProcSet [/PDF /Text]\n");
+ ps_printf(p, "/Font <<\n");
+ for (i = 0; i < (int)TERMFONT__MAX; i++)
+ ps_printf(p, "/F%d %d 0 R\n", i, 3 + i);
+ ps_printf(p, ">>\n>>\n");
+
+ /* Page node. */
+ pdf_obj(p, base + 3);
+ ps_printf(p, "<<\n");
+ ps_printf(p, "/Type /Page\n");
+ ps_printf(p, "/Parent 2 0 R\n");
+ ps_printf(p, "/Resources %zu 0 R\n", base + 2);
+ ps_printf(p, "/Contents %zu 0 R\n", base);
+ ps_printf(p, ">>\nendobj\n");
+ } else
+ ps_printf(p, "showpage\n");
+
+ p->ps->pages++;
+ p->ps->psrow = p->ps->top;
+ assert( ! (PS_NEWPAGE & p->ps->flags));
+ p->ps->flags |= PS_NEWPAGE;
+}
+
+static void
+ps_end(struct termp *p)
+{
+ size_t i, xref, base;
+
+ /*
+ * At the end of the file, do one last showpage. This is the
+ * same behaviour as groff(1) and works for multiple pages as
+ * well as just one.
+ */
+
+ if ( ! (PS_NEWPAGE & p->ps->flags)) {
+ assert(0 == p->ps->flags);
+ assert('\0' == p->ps->last);
+ ps_closepage(p);
+ }
+
+ if (TERMTYPE_PS == p->type) {
+ ps_printf(p, "%%%%Trailer\n");
+ ps_printf(p, "%%%%Pages: %zu\n", p->ps->pages);
+ ps_printf(p, "%%%%EOF\n");
+ return;
+ }
+
+ pdf_obj(p, 2);
+ ps_printf(p, "<<\n/Type /Pages\n");
+ ps_printf(p, "/MediaBox [0 0 %zu %zu]\n",
+ (size_t)AFM2PNT(p, p->ps->width),
+ (size_t)AFM2PNT(p, p->ps->height));
+
+ ps_printf(p, "/Count %zu\n", p->ps->pages);
+ ps_printf(p, "/Kids [");
+
+ for (i = 0; i < p->ps->pages; i++)
+ ps_printf(p, " %zu 0 R", i * 4 + p->ps->pdfbody + 3);
+
+ base = (p->ps->pages - 1) * 4 + p->ps->pdfbody + 4;
+
+ ps_printf(p, "]\n>>\nendobj\n");
+ pdf_obj(p, base);
+ ps_printf(p, "<<\n");
+ ps_printf(p, "/Type /Catalog\n");
+ ps_printf(p, "/Pages 2 0 R\n");
+ ps_printf(p, ">>\n");
+ xref = p->ps->pdfbytes;
+ ps_printf(p, "xref\n");
+ ps_printf(p, "0 %zu\n", base + 1);
+ ps_printf(p, "0000000000 65535 f \n");
+
+ for (i = 0; i < base; i++)
+ ps_printf(p, "%.10zu 00000 n \n",
+ p->ps->pdfobjs[(int)i]);
+
+ ps_printf(p, "trailer\n");
+ ps_printf(p, "<<\n");
+ ps_printf(p, "/Size %zu\n", base + 1);
+ ps_printf(p, "/Root %zu 0 R\n", base);
+ ps_printf(p, "/Info 1 0 R\n");
+ ps_printf(p, ">>\n");
+ ps_printf(p, "startxref\n");
+ ps_printf(p, "%zu\n", xref);
+ ps_printf(p, "%%%%EOF\n");
+}
+
+static void
+ps_begin(struct termp *p)
+{
+ time_t t;
+ int i;
+
+ /*
+ * Print margins into margin buffer. Nothing gets output to the
+ * screen yet, so we don't need to initialise the primary state.
+ */
+
+ if (p->ps->psmarg) {
+ assert(p->ps->psmargsz);
+ p->ps->psmarg[0] = '\0';
+ }
+
+ /*p->ps->pdfbytes = 0;*/
+ p->ps->psmargcur = 0;
+ p->ps->flags = PS_MARGINS;
+ p->ps->pscol = p->ps->left;
+ p->ps->psrow = p->ps->header;
+
+ ps_setfont(p, TERMFONT_NONE);
+
+ (*p->headf)(p, p->argf);
+ (*p->endline)(p);
+
+ p->ps->pscol = p->ps->left;
+ p->ps->psrow = p->ps->footer;
+
+ (*p->footf)(p, p->argf);
+ (*p->endline)(p);
+
+ p->ps->flags &= ~PS_MARGINS;
+
+ assert(0 == p->ps->flags);
+ assert(p->ps->psmarg);
+ assert('\0' != p->ps->psmarg[0]);
+
+ /*
+ * Print header and initialise page state. Following this,
+ * stuff gets printed to the screen, so make sure we're sane.
+ */
+
+ t = time(NULL);
+
+ if (TERMTYPE_PS == p->type) {
+ ps_printf(p, "%%!PS-Adobe-3.0\n");
+ ps_printf(p, "%%%%CreationDate: %s", ctime(&t));
+ ps_printf(p, "%%%%DocumentData: Clean7Bit\n");
+ ps_printf(p, "%%%%Orientation: Portrait\n");
+ ps_printf(p, "%%%%Pages: (atend)\n");
+ ps_printf(p, "%%%%PageOrder: Ascend\n");
+ ps_printf(p, "%%%%DocumentMedia: "
+ "Default %zu %zu 0 () ()\n",
+ (size_t)AFM2PNT(p, p->ps->width),
+ (size_t)AFM2PNT(p, p->ps->height));
+ ps_printf(p, "%%%%DocumentNeededResources: font");
+
+ for (i = 0; i < (int)TERMFONT__MAX; i++)
+ ps_printf(p, " %s", fonts[i].name);
+
+ ps_printf(p, "\n%%%%EndComments\n");
+ } else {
+ ps_printf(p, "%%PDF-1.1\n");
+ pdf_obj(p, 1);
+ ps_printf(p, "<<\n");
+ ps_printf(p, ">>\n");
+ ps_printf(p, "endobj\n");
+
+ for (i = 0; i < (int)TERMFONT__MAX; i++) {
+ pdf_obj(p, (size_t)i + 3);
+ ps_printf(p, "<<\n");
+ ps_printf(p, "/Type /Font\n");
+ ps_printf(p, "/Subtype /Type1\n");
+ ps_printf(p, "/Name /F%d\n", i);
+ ps_printf(p, "/BaseFont /%s\n", fonts[i].name);
+ ps_printf(p, ">>\n");
+ }
+ }
+
+ p->ps->pdfbody = (size_t)TERMFONT__MAX + 3;
+ p->ps->pscol = p->ps->left;
+ p->ps->psrow = p->ps->top;
+ p->ps->flags |= PS_NEWPAGE;
+ ps_setfont(p, TERMFONT_NONE);
+}
+
+static void
+ps_pletter(struct termp *p, int c)
+{
+ int f;
+
+ /*
+ * If we haven't opened a page context, then output that we're
+ * in a new page and make sure the font is correctly set.
+ */
+
+ if (PS_NEWPAGE & p->ps->flags) {
+ if (TERMTYPE_PS == p->type) {
+ ps_printf(p, "%%%%Page: %zu %zu\n",
+ p->ps->pages + 1, p->ps->pages + 1);
+ ps_printf(p, "/%s %zu selectfont\n",
+ fonts[(int)p->ps->lastf].name,
+ p->ps->scale);
+ } else {
+ pdf_obj(p, p->ps->pdfbody +
+ p->ps->pages * 4);
+ ps_printf(p, "<<\n");
+ ps_printf(p, "/Length %zu 0 R\n",
+ p->ps->pdfbody + 1 + p->ps->pages * 4);
+ ps_printf(p, ">>\nstream\n");
+ }
+ p->ps->pdflastpg = p->ps->pdfbytes;
+ p->ps->flags &= ~PS_NEWPAGE;
+ }
+
+ /*
+ * If we're not in a PostScript "word" context, then open one
+ * now at the current cursor.
+ */
+
+ if ( ! (PS_INLINE & p->ps->flags)) {
+ if (TERMTYPE_PS != p->type) {
+ ps_printf(p, "BT\n/F%d %zu Tf\n",
+ (int)p->ps->lastf, p->ps->scale);
+ ps_printf(p, "%.3f %.3f Td\n(",
+ AFM2PNT(p, p->ps->pscol),
+ AFM2PNT(p, p->ps->psrow));
+ } else
+ ps_printf(p, "%.3f %.3f moveto\n(",
+ AFM2PNT(p, p->ps->pscol),
+ AFM2PNT(p, p->ps->psrow));
+ p->ps->flags |= PS_INLINE;
+ }
+
+ assert( ! (PS_NEWPAGE & p->ps->flags));
+
+ /*
+ * We need to escape these characters as per the PostScript
+ * specification. We would also escape non-graphable characters
+ * (like tabs), but none of them would get to this point and
+ * it's superfluous to abort() on them.
+ */
+
+ switch (c) {
+ case '(':
+ /* FALLTHROUGH */
+ case ')':
+ /* FALLTHROUGH */
+ case '\\':
+ ps_putchar(p, '\\');
+ break;
+ default:
+ break;
+ }
+
+ /* Write the character and adjust where we are on the page. */
+
+ f = (int)p->ps->lastf;
+
+ if (c <= 32 || c - 32 >= MAXCHAR)
+ c = 32;
+
+ ps_putchar(p, (char)c);
+ c -= 32;
+ p->ps->pscol += (size_t)fonts[f].gly[c].wx;
+}
+
+static void
+ps_pclose(struct termp *p)
+{
+
+ /*
+ * Spit out that we're exiting a word context (this is a
+ * "partial close" because we don't check the last-char buffer
+ * or anything).
+ */
+
+ if ( ! (PS_INLINE & p->ps->flags))
+ return;
+
+ if (TERMTYPE_PS != p->type) {
+ ps_printf(p, ") Tj\nET\n");
+ } else
+ ps_printf(p, ") show\n");
+
+ p->ps->flags &= ~PS_INLINE;
+}
+
+static void
+ps_fclose(struct termp *p)
+{
+
+ /*
+ * Strong closure: if we have a last-char, spit it out after
+ * checking that we're in the right font mode. This will of
+ * course open a new scope, if applicable.
+ *
+ * Following this, close out any scope that's open.
+ */
+
+ if ('\0' != p->ps->last) {
+ if (p->ps->lastf != TERMFONT_NONE) {
+ ps_pclose(p);
+ ps_setfont(p, TERMFONT_NONE);
+ }
+ ps_pletter(p, p->ps->last);
+ p->ps->last = '\0';
+ }
+
+ if ( ! (PS_INLINE & p->ps->flags))
+ return;
+
+ ps_pclose(p);
+}
+
+static void
+ps_letter(struct termp *p, int arg)
+{
+ char cc, c;
+
+ c = arg >= 128 || arg <= 0 ? '?' : arg;
+
+ /*
+ * State machine dictates whether to buffer the last character
+ * or not. Basically, encoded words are detected by checking if
+ * we're an "8" and switching on the buffer. Then we put "8" in
+ * our buffer, and on the next charater, flush both character
+ * and buffer. Thus, "regular" words are detected by having a
+ * regular character and a regular buffer character.
+ */
+
+ if ('\0' == p->ps->last) {
+ assert(8 != c);
+ p->ps->last = c;
+ return;
+ } else if (8 == p->ps->last) {
+ assert(8 != c);
+ p->ps->last = '\0';
+ } else if (8 == c) {
+ assert(8 != p->ps->last);
+ if ('_' == p->ps->last) {
+ if (p->ps->lastf != TERMFONT_UNDER) {
+ ps_pclose(p);
+ ps_setfont(p, TERMFONT_UNDER);
+ }
+ } else if (p->ps->lastf != TERMFONT_BOLD) {
+ ps_pclose(p);
+ ps_setfont(p, TERMFONT_BOLD);
+ }
+ p->ps->last = c;
+ return;
+ } else {
+ if (p->ps->lastf != TERMFONT_NONE) {
+ ps_pclose(p);
+ ps_setfont(p, TERMFONT_NONE);
+ }
+ cc = p->ps->last;
+ p->ps->last = c;
+ c = cc;
+ }
+
+ ps_pletter(p, c);
+}
+
+static void
+ps_advance(struct termp *p, size_t len)
+{
+
+ /*
+ * Advance some spaces. This can probably be made smarter,
+ * i.e., to have multiple space-separated words in the same
+ * scope, but this is easier: just close out the current scope
+ * and readjust our column settings.
+ */
+
+ ps_fclose(p);
+ p->ps->pscol += len;
+}
+
+static void
+ps_endline(struct termp *p)
+{
+
+ /* Close out any scopes we have open: we're at eoln. */
+
+ ps_fclose(p);
+
+ /*
+ * If we're in the margin, don't try to recalculate our current
+ * row. XXX: if the column tries to be fancy with multiple
+ * lines, we'll do nasty stuff.
+ */
+
+ if (PS_MARGINS & p->ps->flags)
+ return;
+
+ /* Left-justify. */
+
+ p->ps->pscol = p->ps->left;
+
+ /* If we haven't printed anything, return. */
+
+ if (PS_NEWPAGE & p->ps->flags)
+ return;
+
+ /*
+ * Put us down a line. If we're at the page bottom, spit out a
+ * showpage and restart our row.
+ */
+
+ if (p->ps->psrow >= p->ps->lineheight + p->ps->bottom) {
+ p->ps->psrow -= p->ps->lineheight;
+ return;
+ }
+
+ ps_closepage(p);
+}
+
+static void
+ps_setfont(struct termp *p, enum termfont f)
+{
+
+ assert(f < TERMFONT__MAX);
+ p->ps->lastf = f;
+
+ /*
+ * If we're still at the top of the page, let the font-setting
+ * be delayed until we actually have stuff to print.
+ */
+
+ if (PS_NEWPAGE & p->ps->flags)
+ return;
+
+ if (TERMTYPE_PS == p->type)
+ ps_printf(p, "/%s %zu selectfont\n",
+ fonts[(int)f].name, p->ps->scale);
+ else
+ ps_printf(p, "/F%d %zu Tf\n",
+ (int)f, p->ps->scale);
+}
+
+static size_t
+ps_width(const struct termp *p, int c)
+{
+
+ if (c <= 32 || c - 32 >= MAXCHAR)
+ c = 0;
+ else
+ c -= 32;
+
+ return((size_t)fonts[(int)TERMFONT_NONE].gly[c].wx);
+}
+
+static double
+ps_hspan(const struct termp *p, const struct roffsu *su)
+{
+ double r;
+
+ /*
+ * All of these measurements are derived by converting from the
+ * native measurement to AFM units.
+ */
+
+ switch (su->unit) {
+ case SCALE_CM:
+ r = PNT2AFM(p, su->scale * 28.34);
+ break;
+ case SCALE_IN:
+ r = PNT2AFM(p, su->scale * 72.0);
+ break;
+ case SCALE_PC:
+ r = PNT2AFM(p, su->scale * 12.0);
+ break;
+ case SCALE_PT:
+ r = PNT2AFM(p, su->scale * 100.0);
+ break;
+ case SCALE_EM:
+ r = su->scale *
+ fonts[(int)TERMFONT_NONE].gly[109 - 32].wx;
+ break;
+ case SCALE_MM:
+ r = PNT2AFM(p, su->scale * 2.834);
+ break;
+ case SCALE_EN:
+ r = su->scale *
+ fonts[(int)TERMFONT_NONE].gly[110 - 32].wx;
+ break;
+ case SCALE_VS:
+ r = su->scale * p->ps->lineheight;
+ break;
+ default:
+ r = su->scale;
+ break;
+ }
+
+ return(r);
+}
+
+static void
+ps_growbuf(struct termp *p, size_t sz)
+{
+ if (p->ps->psmargcur + sz <= p->ps->psmargsz)
+ return;
+
+ if (sz < PS_BUFSLOP)
+ sz = PS_BUFSLOP;
+
+ p->ps->psmargsz += sz;
+ p->ps->psmarg = mandoc_realloc(p->ps->psmarg, p->ps->psmargsz);
+}
diff --git a/contrib/mdocml/test-fgetln.c b/contrib/mdocml/test-fgetln.c
new file mode 100644
index 0000000..ac225a1
--- /dev/null
+++ b/contrib/mdocml/test-fgetln.c
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+ size_t sz;
+ fclose(stdin);
+ return(NULL != fgetln(stdin, &sz));
+}
diff --git a/contrib/mdocml/test-getsubopt.c b/contrib/mdocml/test-getsubopt.c
new file mode 100644
index 0000000..2da9823
--- /dev/null
+++ b/contrib/mdocml/test-getsubopt.c
@@ -0,0 +1,19 @@
+#if defined(__linux__) || defined(__MINT__)
+# define _GNU_SOURCE /* getsubopt() */
+#endif
+
+#include <stdlib.h>
+
+extern char *suboptarg;
+
+int
+main(void)
+{
+ char buf[] = "k=v";
+ char *options = buf;
+ char token0[] = "k";
+ char *const tokens[] = { token0, NULL };
+ char *value = NULL;
+ return( ! (0 == getsubopt(&options, tokens, &value)
+ && suboptarg == buf && value == buf+2 && options == buf+3));
+}
diff --git a/contrib/mdocml/test-mmap.c b/contrib/mdocml/test-mmap.c
new file mode 100644
index 0000000..c322a34
--- /dev/null
+++ b/contrib/mdocml/test-mmap.c
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <stddef.h>
+
+int
+main(void)
+{
+ return(MAP_FAILED != mmap(NULL, 1, PROT_READ, MAP_SHARED, -1, 0));
+}
diff --git a/contrib/mdocml/test-ohash.c b/contrib/mdocml/test-ohash.c
new file mode 100644
index 0000000..522ad70
--- /dev/null
+++ b/contrib/mdocml/test-ohash.c
@@ -0,0 +1,21 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <ohash.h>
+
+void *xmalloc(size_t sz, void *arg) { return(calloc(1,sz)); }
+void *xcalloc(size_t nmemb, size_t sz, void *arg) { return(calloc(nmemb,sz)); }
+void xfree(void *p, void *arg) { free(p); }
+
+int
+main(void)
+{
+ struct ohash h;
+ struct ohash_info i;
+ i.alloc = xmalloc;
+ i.calloc = xcalloc;
+ i.free = xfree;
+ ohash_init(&h, 2, &i);
+ ohash_delete(&h);
+ return 0;
+}
diff --git a/contrib/mdocml/test-reallocarray.c b/contrib/mdocml/test-reallocarray.c
new file mode 100644
index 0000000..9a514e8
--- /dev/null
+++ b/contrib/mdocml/test-reallocarray.c
@@ -0,0 +1,7 @@
+#include <stdlib.h>
+
+int
+main(void)
+{
+ return( ! reallocarray(NULL, 2, 2));
+}
diff --git a/contrib/mdocml/test-sqlite3_errstr.c b/contrib/mdocml/test-sqlite3_errstr.c
new file mode 100644
index 0000000..041bf62
--- /dev/null
+++ b/contrib/mdocml/test-sqlite3_errstr.c
@@ -0,0 +1,8 @@
+#include <string.h>
+#include <sqlite3.h>
+
+int
+main(void)
+{
+ return(strcmp(sqlite3_errstr(SQLITE_OK), "not an error"));
+}
diff --git a/contrib/mdocml/test-strcasestr.c b/contrib/mdocml/test-strcasestr.c
new file mode 100644
index 0000000..479ba91
--- /dev/null
+++ b/contrib/mdocml/test-strcasestr.c
@@ -0,0 +1,13 @@
+#if defined(__linux__) || defined(__MINT__)
+# define _GNU_SOURCE /* strcasestr() */
+#endif
+
+#include <string.h>
+
+int
+main(void)
+{
+ const char *big = "BigString";
+ char *cp = strcasestr(big, "Gst");
+ return(big + 2 != cp);
+}
diff --git a/contrib/mdocml/test-strlcat.c b/contrib/mdocml/test-strlcat.c
new file mode 100644
index 0000000..b74ce6f
--- /dev/null
+++ b/contrib/mdocml/test-strlcat.c
@@ -0,0 +1,9 @@
+#include <string.h>
+
+int
+main(void)
+{
+ char buf[3] = "a";
+ return( ! (2 == strlcat(buf, "b", sizeof(buf)) &&
+ 'a' == buf[0] && 'b' == buf[1] && '\0' == buf[2]));
+}
diff --git a/contrib/mdocml/test-strlcpy.c b/contrib/mdocml/test-strlcpy.c
new file mode 100644
index 0000000..05fa509
--- /dev/null
+++ b/contrib/mdocml/test-strlcpy.c
@@ -0,0 +1,9 @@
+#include <string.h>
+
+int
+main(void)
+{
+ char buf[2] = "";
+ return( ! (1 == strlcpy(buf, "a", sizeof(buf)) &&
+ 'a' == buf[0] && '\0' == buf[1]));
+}
diff --git a/contrib/mdocml/test-strsep.c b/contrib/mdocml/test-strsep.c
new file mode 100644
index 0000000..4c57c13
--- /dev/null
+++ b/contrib/mdocml/test-strsep.c
@@ -0,0 +1,10 @@
+#include <string.h>
+
+int
+main(void)
+{
+ char buf[6] = "aybxc";
+ char *workp = buf;
+ char *retp = strsep(&workp, "xy");
+ return( ! (retp == buf && '\0' == buf[1] && buf + 2 == workp));
+}
diff --git a/contrib/mdocml/tree.c b/contrib/mdocml/tree.c
new file mode 100644
index 0000000..7574209
--- /dev/null
+++ b/contrib/mdocml/tree.c
@@ -0,0 +1,349 @@
+/* $Id: tree.c,v 1.53 2014/07/02 07:10:38 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "mdoc.h"
+#include "man.h"
+#include "main.h"
+
+static void print_box(const struct eqn_box *, int);
+static void print_man(const struct man_node *, int);
+static void print_mdoc(const struct mdoc_node *, int);
+static void print_span(const struct tbl_span *, int);
+
+
+void
+tree_mdoc(void *arg, const struct mdoc *mdoc)
+{
+
+ print_mdoc(mdoc_node(mdoc), 0);
+}
+
+void
+tree_man(void *arg, const struct man *man)
+{
+
+ print_man(man_node(man), 0);
+}
+
+static void
+print_mdoc(const struct mdoc_node *n, int indent)
+{
+ const char *p, *t;
+ int i, j;
+ size_t argc;
+ struct mdoc_argv *argv;
+
+ argv = NULL;
+ argc = 0;
+ t = p = NULL;
+
+ switch (n->type) {
+ case MDOC_ROOT:
+ t = "root";
+ break;
+ case MDOC_BLOCK:
+ t = "block";
+ break;
+ case MDOC_HEAD:
+ t = "block-head";
+ break;
+ case MDOC_BODY:
+ if (n->end)
+ t = "body-end";
+ else
+ t = "block-body";
+ break;
+ case MDOC_TAIL:
+ t = "block-tail";
+ break;
+ case MDOC_ELEM:
+ t = "elem";
+ break;
+ case MDOC_TEXT:
+ t = "text";
+ break;
+ case MDOC_TBL:
+ /* FALLTHROUGH */
+ case MDOC_EQN:
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ switch (n->type) {
+ case MDOC_TEXT:
+ p = n->string;
+ break;
+ case MDOC_BODY:
+ p = mdoc_macronames[n->tok];
+ break;
+ case MDOC_HEAD:
+ p = mdoc_macronames[n->tok];
+ break;
+ case MDOC_TAIL:
+ p = mdoc_macronames[n->tok];
+ break;
+ case MDOC_ELEM:
+ p = mdoc_macronames[n->tok];
+ if (n->args) {
+ argv = n->args->argv;
+ argc = n->args->argc;
+ }
+ break;
+ case MDOC_BLOCK:
+ p = mdoc_macronames[n->tok];
+ if (n->args) {
+ argv = n->args->argv;
+ argc = n->args->argc;
+ }
+ break;
+ case MDOC_TBL:
+ /* FALLTHROUGH */
+ case MDOC_EQN:
+ break;
+ case MDOC_ROOT:
+ p = "root";
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ if (n->span) {
+ assert(NULL == p && NULL == t);
+ print_span(n->span, indent);
+ } else if (n->eqn) {
+ assert(NULL == p && NULL == t);
+ print_box(n->eqn->root, indent);
+ } else {
+ for (i = 0; i < indent; i++)
+ putchar('\t');
+
+ printf("%s (%s)", p, t);
+
+ for (i = 0; i < (int)argc; i++) {
+ printf(" -%s", mdoc_argnames[argv[i].arg]);
+ if (argv[i].sz > 0)
+ printf(" [");
+ for (j = 0; j < (int)argv[i].sz; j++)
+ printf(" [%s]", argv[i].value[j]);
+ if (argv[i].sz > 0)
+ printf(" ]");
+ }
+
+ putchar(' ');
+ if (MDOC_LINE & n->flags)
+ putchar('*');
+ printf("%d:%d", n->line, n->pos + 1);
+ if (n->lastline != n->line)
+ printf("-%d", n->lastline);
+ putchar('\n');
+ }
+
+ if (n->child)
+ print_mdoc(n->child, indent + 1);
+ if (n->next)
+ print_mdoc(n->next, indent);
+}
+
+static void
+print_man(const struct man_node *n, int indent)
+{
+ const char *p, *t;
+ int i;
+
+ t = p = NULL;
+
+ switch (n->type) {
+ case MAN_ROOT:
+ t = "root";
+ break;
+ case MAN_ELEM:
+ t = "elem";
+ break;
+ case MAN_TEXT:
+ t = "text";
+ break;
+ case MAN_BLOCK:
+ t = "block";
+ break;
+ case MAN_HEAD:
+ t = "block-head";
+ break;
+ case MAN_BODY:
+ t = "block-body";
+ break;
+ case MAN_TAIL:
+ t = "block-tail";
+ break;
+ case MAN_TBL:
+ /* FALLTHROUGH */
+ case MAN_EQN:
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ switch (n->type) {
+ case MAN_TEXT:
+ p = n->string;
+ break;
+ case MAN_ELEM:
+ /* FALLTHROUGH */
+ case MAN_BLOCK:
+ /* FALLTHROUGH */
+ case MAN_HEAD:
+ /* FALLTHROUGH */
+ case MAN_TAIL:
+ /* FALLTHROUGH */
+ case MAN_BODY:
+ p = man_macronames[n->tok];
+ break;
+ case MAN_ROOT:
+ p = "root";
+ break;
+ case MAN_TBL:
+ /* FALLTHROUGH */
+ case MAN_EQN:
+ break;
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+
+ if (n->span) {
+ assert(NULL == p && NULL == t);
+ print_span(n->span, indent);
+ } else if (n->eqn) {
+ assert(NULL == p && NULL == t);
+ print_box(n->eqn->root, indent);
+ } else {
+ for (i = 0; i < indent; i++)
+ putchar('\t');
+ printf("%s (%s) ", p, t);
+ if (MAN_LINE & n->flags)
+ putchar('*');
+ printf("%d:%d\n", n->line, n->pos + 1);
+ }
+
+ if (n->child)
+ print_man(n->child, indent + 1);
+ if (n->next)
+ print_man(n->next, indent);
+}
+
+static void
+print_box(const struct eqn_box *ep, int indent)
+{
+ int i;
+ const char *t;
+
+ if (NULL == ep)
+ return;
+ for (i = 0; i < indent; i++)
+ putchar('\t');
+
+ t = NULL;
+ switch (ep->type) {
+ case EQN_ROOT:
+ t = "eqn-root";
+ break;
+ case EQN_LIST:
+ t = "eqn-list";
+ break;
+ case EQN_SUBEXPR:
+ t = "eqn-expr";
+ break;
+ case EQN_TEXT:
+ t = "eqn-text";
+ break;
+ case EQN_MATRIX:
+ t = "eqn-matrix";
+ break;
+ }
+
+ assert(t);
+ printf("%s(%d, %d, %d, %d, %d, \"%s\", \"%s\") %s\n",
+ t, EQN_DEFSIZE == ep->size ? 0 : ep->size,
+ ep->pos + 1, ep->font, ep->mark, ep->pile,
+ ep->left ? ep->left : "",
+ ep->right ? ep->right : "",
+ ep->text ? ep->text : "");
+
+ print_box(ep->first, indent + 1);
+ print_box(ep->next, indent);
+}
+
+static void
+print_span(const struct tbl_span *sp, int indent)
+{
+ const struct tbl_dat *dp;
+ int i;
+
+ for (i = 0; i < indent; i++)
+ putchar('\t');
+
+ switch (sp->pos) {
+ case TBL_SPAN_HORIZ:
+ putchar('-');
+ return;
+ case TBL_SPAN_DHORIZ:
+ putchar('=');
+ return;
+ default:
+ break;
+ }
+
+ for (dp = sp->first; dp; dp = dp->next) {
+ switch (dp->pos) {
+ case TBL_DATA_HORIZ:
+ /* FALLTHROUGH */
+ case TBL_DATA_NHORIZ:
+ putchar('-');
+ continue;
+ case TBL_DATA_DHORIZ:
+ /* FALLTHROUGH */
+ case TBL_DATA_NDHORIZ:
+ putchar('=');
+ continue;
+ default:
+ break;
+ }
+ printf("[\"%s\"", dp->string ? dp->string : "");
+ if (dp->spans)
+ printf("(%d)", dp->spans);
+ if (NULL == dp->layout)
+ putchar('*');
+ putchar(']');
+ putchar(' ');
+ }
+
+ printf("(tbl) %d:1\n", sp->line);
+}
diff --git a/contrib/mdocml/vol.c b/contrib/mdocml/vol.c
new file mode 100644
index 0000000..7658ea4
--- /dev/null
+++ b/contrib/mdocml/vol.c
@@ -0,0 +1,36 @@
+/* $Id: vol.c,v 1.10 2014/03/23 11:25:26 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "mdoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+ if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2vol(const char *p)
+{
+
+#include "vol.in"
+
+ return(NULL);
+}
diff --git a/contrib/mdocml/vol.in b/contrib/mdocml/vol.in
new file mode 100644
index 0000000..7650b57
--- /dev/null
+++ b/contrib/mdocml/vol.in
@@ -0,0 +1,35 @@
+/* $Id: vol.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This file defines volume titles for .Dt.
+ *
+ * Be sure to escape strings.
+ */
+
+LINE("USD", "User\'s Supplementary Documents")
+LINE("PS1", "Programmer\'s Supplementary Documents")
+LINE("AMD", "Ancestral Manual Documents")
+LINE("SMM", "System Manager\'s Manual")
+LINE("URM", "User\'s Reference Manual")
+LINE("PRM", "Programmer\'s Manual")
+LINE("KM", "Kernel Manual")
+LINE("IND", "Manual Master Index")
+LINE("MMI", "Manual Master Index")
+LINE("LOCAL", "Local Manual")
+LINE("LOC", "Local Manual")
+LINE("CON", "Contributed Software Manual")
OpenPOWER on IntegriCloud