aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/mandoc
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/mandoc')
-rw-r--r--usr.bin/mandoc/CVS/Entries101
-rw-r--r--usr.bin/mandoc/CVS/Repository1
-rw-r--r--usr.bin/mandoc/CVS/Root1
-rw-r--r--usr.bin/mandoc/Makefile78
-rw-r--r--usr.bin/mandoc/apropos.1510
-rw-r--r--usr.bin/mandoc/arch.c52
-rw-r--r--usr.bin/mandoc/att.c47
-rw-r--r--usr.bin/mandoc/cgi.c1255
-rw-r--r--usr.bin/mandoc/cgi.h.example7
-rw-r--r--usr.bin/mandoc/chars.c506
-rw-r--r--usr.bin/mandoc/dba.c501
-rw-r--r--usr.bin/mandoc/dba.h50
-rw-r--r--usr.bin/mandoc/dba_array.c188
-rw-r--r--usr.bin/mandoc/dba_array.h47
-rw-r--r--usr.bin/mandoc/dba_read.c72
-rw-r--r--usr.bin/mandoc/dba_write.c117
-rw-r--r--usr.bin/mandoc/dba_write.h30
-rw-r--r--usr.bin/mandoc/dbm.c474
-rw-r--r--usr.bin/mandoc/dbm.h68
-rw-r--r--usr.bin/mandoc/dbm_map.c188
-rw-r--r--usr.bin/mandoc/dbm_map.h29
-rw-r--r--usr.bin/mandoc/eqn.c1130
-rw-r--r--usr.bin/mandoc/eqn.h72
-rw-r--r--usr.bin/mandoc/eqn_html.c244
-rw-r--r--usr.bin/mandoc/eqn_parse.h48
-rw-r--r--usr.bin/mandoc/eqn_term.c172
-rw-r--r--usr.bin/mandoc/html.c1085
-rw-r--r--usr.bin/mandoc/html.h141
-rw-r--r--usr.bin/mandoc/libman.h42
-rw-r--r--usr.bin/mandoc/libmandoc.h85
-rw-r--r--usr.bin/mandoc/libmdoc.h86
-rw-r--r--usr.bin/mandoc/main.c1255
-rw-r--r--usr.bin/mandoc/main.h53
-rw-r--r--usr.bin/mandoc/makewhatis.8228
-rw-r--r--usr.bin/mandoc/man.1431
-rw-r--r--usr.bin/mandoc/man.c343
-rw-r--r--usr.bin/mandoc/man.cgi.8426
-rw-r--r--usr.bin/mandoc/man.conf.5133
-rw-r--r--usr.bin/mandoc/man.h21
-rw-r--r--usr.bin/mandoc/man_html.c640
-rw-r--r--usr.bin/mandoc/man_macro.c466
-rw-r--r--usr.bin/mandoc/man_term.c1149
-rw-r--r--usr.bin/mandoc/man_validate.c656
-rw-r--r--usr.bin/mandoc/manconf.h56
-rw-r--r--usr.bin/mandoc/mandoc.12336
-rw-r--r--usr.bin/mandoc/mandoc.c657
-rw-r--r--usr.bin/mandoc/mandoc.css360
-rw-r--r--usr.bin/mandoc/mandoc.h322
-rw-r--r--usr.bin/mandoc/mandoc_aux.c113
-rw-r--r--usr.bin/mandoc/mandoc_aux.h27
-rw-r--r--usr.bin/mandoc/mandoc_msg.c368
-rw-r--r--usr.bin/mandoc/mandoc_ohash.c64
-rw-r--r--usr.bin/mandoc/mandoc_ohash.h19
-rw-r--r--usr.bin/mandoc/mandoc_parse.h44
-rw-r--r--usr.bin/mandoc/mandoc_xr.c122
-rw-r--r--usr.bin/mandoc/mandoc_xr.h31
-rw-r--r--usr.bin/mandoc/mandocdb.c2386
-rw-r--r--usr.bin/mandoc/manpath.c342
-rw-r--r--usr.bin/mandoc/mansearch.c842
-rw-r--r--usr.bin/mandoc/mansearch.h118
-rw-r--r--usr.bin/mandoc/mdoc.c431
-rw-r--r--usr.bin/mandoc/mdoc.h158
-rw-r--r--usr.bin/mandoc/mdoc_argv.c680
-rw-r--r--usr.bin/mandoc/mdoc_html.c1758
-rw-r--r--usr.bin/mandoc/mdoc_macro.c1598
-rw-r--r--usr.bin/mandoc/mdoc_man.c1836
-rw-r--r--usr.bin/mandoc/mdoc_markdown.c1606
-rw-r--r--usr.bin/mandoc/mdoc_state.c254
-rw-r--r--usr.bin/mandoc/mdoc_term.c1962
-rw-r--r--usr.bin/mandoc/mdoc_validate.c3047
-rw-r--r--usr.bin/mandoc/msec.c35
-rw-r--r--usr.bin/mandoc/msec.in34
-rw-r--r--usr.bin/mandoc/out.c563
-rw-r--r--usr.bin/mandoc/out.h70
-rw-r--r--usr.bin/mandoc/preconv.c177
-rw-r--r--usr.bin/mandoc/predefs.in65
-rw-r--r--usr.bin/mandoc/read.c727
-rw-r--r--usr.bin/mandoc/roff.c4372
-rw-r--r--usr.bin/mandoc/roff.h561
-rw-r--r--usr.bin/mandoc/roff_html.c117
-rw-r--r--usr.bin/mandoc/roff_int.h94
-rw-r--r--usr.bin/mandoc/roff_term.c244
-rw-r--r--usr.bin/mandoc/roff_validate.c149
-rw-r--r--usr.bin/mandoc/st.c80
-rw-r--r--usr.bin/mandoc/tag.c326
-rw-r--r--usr.bin/mandoc/tag.h35
-rw-r--r--usr.bin/mandoc/tbl.c181
-rw-r--r--usr.bin/mandoc/tbl.h122
-rw-r--r--usr.bin/mandoc/tbl_data.c300
-rw-r--r--usr.bin/mandoc/tbl_html.c255
-rw-r--r--usr.bin/mandoc/tbl_int.h47
-rw-r--r--usr.bin/mandoc/tbl_layout.c371
-rw-r--r--usr.bin/mandoc/tbl_opts.c171
-rw-r--r--usr.bin/mandoc/tbl_parse.h30
-rw-r--r--usr.bin/mandoc/tbl_term.c943
-rw-r--r--usr.bin/mandoc/term.c1112
-rw-r--r--usr.bin/mandoc/term.h158
-rw-r--r--usr.bin/mandoc/term_ascii.c392
-rw-r--r--usr.bin/mandoc/term_ps.c1355
-rw-r--r--usr.bin/mandoc/term_tab.c128
-rw-r--r--usr.bin/mandoc/term_tag.c199
-rw-r--r--usr.bin/mandoc/term_tag.h34
-rw-r--r--usr.bin/mandoc/tree.c514
103 files changed, 48726 insertions, 0 deletions
diff --git a/usr.bin/mandoc/CVS/Entries b/usr.bin/mandoc/CVS/Entries
new file mode 100644
index 0000000..30e23be
--- /dev/null
+++ b/usr.bin/mandoc/CVS/Entries
@@ -0,0 +1,101 @@
+/Makefile/1.118/Fri Mar 13 00:31:04 2020//
+/apropos.1/1.41/Thu Nov 22 12:32:10 2018//
+/arch.c/1.11/Sat May 11 07:18:17 2019//
+/att.c/1.14/Thu Dec 13 11:55:14 2018//
+/cgi.c/1.110/Fri Apr 3 11:34:19 2020//
+/cgi.h.example/1.6/Sat Mar 18 16:48:07 2017//
+/chars.c/1.49/Thu Feb 13 16:16:03 2020//
+/dba.c/1.7/Thu Feb 9 18:26:17 2017//
+/dba.h/1.2/Wed Aug 17 20:46:06 2016//
+/dba_array.c/1.1/Mon Aug 1 10:32:39 2016//
+/dba_array.h/1.1/Mon Aug 1 10:32:39 2016//
+/dba_read.c/1.4/Wed Aug 17 20:46:06 2016//
+/dba_write.c/1.1/Mon Aug 1 10:32:39 2016//
+/dba_write.h/1.1/Mon Aug 1 10:32:39 2016//
+/dbm.c/1.5/Mon Jul 1 22:43:03 2019//
+/dbm.h/1.1/Mon Aug 1 10:32:39 2016//
+/dbm_map.c/1.6/Thu Feb 9 18:26:17 2017//
+/dbm_map.h/1.2/Mon Jul 1 22:43:03 2019//
+/eqn.c/1.47/Wed Jan 8 12:09:14 2020//
+/eqn.h/1.1/Thu Dec 13 05:13:15 2018//
+/eqn_html.c/1.15/Sun Mar 17 18:20:07 2019//
+/eqn_parse.h/1.3/Fri Dec 14 06:33:03 2018//
+/eqn_term.c/1.15/Thu Dec 13 05:13:15 2018//
+/html.c/1.141/Mon Apr 20 12:59:24 2020//
+/html.h/1.70/Sat Apr 18 20:28:46 2020//
+/libman.h/1.61/Mon Dec 31 10:03:38 2018//
+/libmandoc.h/1.64/Fri Apr 3 11:34:19 2020//
+/libmdoc.h/1.88/Mon Dec 31 04:55:42 2018//
+/main.c/1.251/Thu Apr 2 22:10:27 2020//
+/main.h/1.25/Sun Mar 3 13:01:47 2019//
+/makewhatis.8/1.14/Wed May 17 22:26:52 2017//
+/man.1/1.36/Mon Feb 10 13:49:04 2020//
+/man.c/1.135/Sat Jan 5 00:36:46 2019//
+/man.cgi.8/1.22/Sun May 20 21:48:23 2018//
+/man.conf.5/1.8/Mon Feb 10 14:42:03 2020//
+/man.h/1.59/Thu Aug 23 19:32:03 2018//
+/man_html.c/1.131/Sat Apr 4 20:23:06 2020//
+/man_macro.c/1.106/Sat Jan 5 18:59:37 2019//
+/man_term.c/1.188/Fri Mar 13 00:31:05 2020//
+/man_validate.c/1.124/Fri Apr 24 11:58:02 2020//
+/manconf.h/1.8/Thu Apr 2 22:10:27 2020//
+/mandoc.1/1.167/Fri Apr 24 11:58:02 2020//
+/mandoc.c/1.85/Sun Jan 19 16:16:32 2020//
+/mandoc.css/1.33/Sun Jun 2 16:50:46 2019//
+/mandoc.h/1.210/Fri Apr 24 11:58:02 2020//
+/mandoc_aux.c/1.9/Wed Feb 7 20:04:33 2018//
+/mandoc_aux.h/1.9/Mon Jun 12 18:55:42 2017//
+/mandoc_msg.c/1.9/Fri Apr 24 11:58:02 2020//
+/mandoc_ohash.c/1.2/Mon Oct 19 18:58:20 2015//
+/mandoc_ohash.h/1.2/Sat Nov 7 13:57:55 2015//
+/mandoc_parse.h/1.4/Sat Nov 9 14:39:42 2019//
+/mandoc_xr.c/1.3/Sun Jul 2 21:17:12 2017//
+/mandoc_xr.h/1.3/Sun Jul 2 21:17:12 2017//
+/mandocdb.c/1.216/Fri Apr 3 11:34:19 2020//
+/manpath.c/1.28/Mon Feb 10 14:42:03 2020//
+/mansearch.c/1.65/Mon Jul 1 22:43:03 2019//
+/mansearch.h/1.24/Tue Apr 30 18:48:26 2019//
+/mdoc.c/1.164/Mon Apr 6 09:55:49 2020//
+/mdoc.h/1.71/Sun Dec 30 00:48:47 2018//
+/mdoc_argv.c/1.76/Thu Jul 11 16:56:52 2019//
+/mdoc_html.c/1.215/Sun Apr 19 15:15:54 2020//
+/mdoc_macro.c/1.191/Sun Jan 19 17:59:01 2020//
+/mdoc_man.c/1.134/Thu Feb 27 01:25:57 2020//
+/mdoc_markdown.c/1.35/Fri Apr 3 11:34:19 2020//
+/mdoc_state.c/1.16/Sun Jan 19 17:59:01 2020//
+/mdoc_term.c/1.279/Mon Apr 6 09:55:49 2020//
+/mdoc_validate.c/1.302/Sun Apr 26 21:29:45 2020//
+/msec.c/1.13/Fri Dec 14 01:17:46 2018//
+/msec.in/1.6/Sat Jun 24 17:36:50 2017//
+/out.c/1.51/Tue Dec 31 22:49:17 2019//
+/out.h/1.25/Fri Apr 3 11:34:19 2020//
+/preconv.c/1.9/Thu Dec 13 11:55:14 2018//
+/predefs.in/1.4/Fri Nov 28 19:25:03 2014//
+/read.c/1.190/Fri Apr 24 11:58:02 2020//
+/roff.c/1.246/Wed Apr 8 11:54:14 2020//
+/roff.h/1.56/Wed Apr 8 11:54:14 2020//
+/roff_html.c/1.20/Tue Apr 30 15:52:42 2019//
+/roff_int.h/1.17/Fri Apr 24 11:58:02 2020//
+/roff_term.c/1.19/Fri Jan 4 03:24:30 2019//
+/roff_validate.c/1.19/Thu Feb 27 01:25:58 2020//
+/st.c/1.13/Fri Dec 14 01:17:46 2018//
+/tag.c/1.36/Sun Apr 19 16:26:11 2020//
+/tag.h/1.14/Sat Apr 18 20:28:46 2020//
+/tbl.c/1.27/Fri Dec 14 06:33:03 2018//
+/tbl.h/1.5/Wed Dec 12 21:54:30 2018//
+/tbl_data.c/1.40/Sat Jan 11 20:48:13 2020//
+/tbl_html.c/1.28/Sun Mar 17 18:20:07 2019//
+/tbl_int.h/1.2/Fri Dec 14 06:33:03 2018//
+/tbl_layout.c/1.35/Fri Dec 14 05:17:45 2018//
+/tbl_opts.c/1.16/Fri Dec 14 05:17:45 2018//
+/tbl_parse.h/1.2/Fri Dec 14 06:33:03 2018//
+/tbl_term.c/1.61/Sat Jan 11 16:24:33 2020//
+/term.c/1.141/Mon Jun 3 20:23:39 2019//
+/term.h/1.75/Fri Jan 4 03:20:44 2019//
+/term_ascii.c/1.50/Fri Jul 19 21:45:37 2019//
+/term_ps.c/1.55/Fri Nov 10 14:16:28 2017//
+/term_tab.c/1.4/Sat Jun 17 14:55:02 2017//
+/term_tag.c/1.4/Sat Apr 18 20:28:46 2020//
+/term_tag.h/1.2/Thu Apr 2 22:10:27 2020//
+/tree.c/1.56/Wed Apr 8 11:54:14 2020//
+D
diff --git a/usr.bin/mandoc/CVS/Repository b/usr.bin/mandoc/CVS/Repository
new file mode 100644
index 0000000..3f4a4d0
--- /dev/null
+++ b/usr.bin/mandoc/CVS/Repository
@@ -0,0 +1 @@
+src/usr.bin/mandoc
diff --git a/usr.bin/mandoc/CVS/Root b/usr.bin/mandoc/CVS/Root
new file mode 100644
index 0000000..3811072
--- /dev/null
+++ b/usr.bin/mandoc/CVS/Root
@@ -0,0 +1 @@
+/cvs
diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile
new file mode 100644
index 0000000..8d6fec5
--- /dev/null
+++ b/usr.bin/mandoc/Makefile
@@ -0,0 +1,78 @@
+# $OpenBSD: Makefile,v 1.118 2020/03/13 00:31:04 schwarze Exp $
+
+.include <bsd.own.mk>
+
+CFLAGS += -W -Wall -Wstrict-prototypes -Wno-unused-parameter
+DPADD += ${LIBUTIL}
+LDADD += -lutil -lz
+
+SRCS= mandoc_aux.c mandoc_ohash.c mandoc.c mandoc_msg.c mandoc_xr.c \
+ arch.c chars.c msec.c preconv.c read.c tag.c
+SRCS+= roff.c roff_validate.c tbl.c tbl_opts.c tbl_layout.c tbl_data.c eqn.c
+SRCS+= mdoc.c mdoc_argv.c mdoc_macro.c mdoc_state.c mdoc_validate.c \
+ att.c st.c
+SRCS+= man_macro.c man.c man_validate.c
+SRCS+= main.c out.c tree.c
+SRCS+= term.c term_ascii.c term_ps.c term_tab.c term_tag.c
+SRCS+= roff_term.c mdoc_term.c man_term.c eqn_term.c tbl_term.c
+SRCS+= mdoc_man.c
+SRCS+= html.c roff_html.c mdoc_html.c man_html.c eqn_html.c tbl_html.c
+SRCS+= mdoc_markdown.c
+SRCS+= dbm_map.c dbm.c dba_write.c dba_array.c dba.c dba_read.c
+SRCS+= manpath.c mandocdb.c mansearch.c
+
+PROG= mandoc
+
+LINKS = ${BINDIR}/mandoc ${BINDIR}/apropos \
+ ${BINDIR}/mandoc ${BINDIR}/help \
+ ${BINDIR}/mandoc ${BINDIR}/man \
+ ${BINDIR}/mandoc ${BINDIR}/whatis \
+ ${BINDIR}/mandoc /usr/sbin/makewhatis \
+ ${BINDIR}/mandoc /usr/libexec/makewhatis
+
+MAN = apropos.1 man.1 mandoc.1 man.conf.5 makewhatis.8
+
+CLEANFILES += man.cgi cgi.o
+
+afterinstall:
+ install -o ${BINOWN} -g ${BINGRP} -m 444 \
+ ${.CURDIR}/mandoc.css ${DESTDIR}/usr/share/misc
+
+
+# ----------------------------------------------------------------------
+# Variables and targets to build and install man.cgi(8),
+# not used during make build and make release.
+
+# To configure, run: cp cgi.h.example cgi.h; vi cgi.h
+# To build, run: make man.cgi
+# To install, run: sudo make installcgi
+# After that, read: man man.cgi.8
+
+LIBMDOC_OBJS = mdoc_argv.o mdoc_macro.o mdoc_state.o \
+ mdoc_validate.o mdoc.o att.o st.o
+LIBMAN_OBJS = man.o man_macro.o man_validate.o
+LIBROFF_OBJS = roff.o roff_validate.o eqn.o \
+ tbl.o tbl_data.o tbl_layout.o tbl_opts.o
+LIBMANDOC_OBJS = ${LIBMDOC_OBJS} ${LIBMAN_OBJS} ${LIBROFF_OBJS} \
+ arch.o mandoc.o mandoc_aux.o mandoc_msg.o mandoc_ohash.o \
+ mandoc_xr.o chars.o msec.o preconv.o read.o tag.o
+HTML_OBJS = html.o roff_html.o mdoc_html.o man_html.o \
+ tbl_html.o eqn_html.o out.o
+CGI_OBJS = ${LIBMANDOC_OBJS} ${HTML_OBJS} \
+ dbm_map.o dbm.o mansearch.o cgi.o
+
+cgi.o: cgi.h main.h manconf.h mandoc.h mandoc_aux.h mandoc_parse.h \
+ mansearch.h man.h mdoc.h roff.h
+
+man.cgi: ${CGI_OBJS}
+ ${CC} ${LDFLAGS} ${STATIC} -o ${.TARGET} ${CGI_OBJS} ${LDADD}
+
+installcgi: man.cgi
+ ${INSTALL} -d -o root -g wheel -m 755 ${DESTDIR}/var/www/cgi-bin
+ ${INSTALL} ${INSTALL_COPY} ${INSTALL_STRIP} \
+ -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \
+ man.cgi ${DESTDIR}/var/www/cgi-bin/man.cgi
+ ${INSTALL} ${INSTALL_COPY} -o root -g wheel -m 644 \
+ ${.CURDIR}/mandoc.css ${DESTDIR}/var/www/htdocs/
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/mandoc/apropos.1 b/usr.bin/mandoc/apropos.1
new file mode 100644
index 0000000..401976f
--- /dev/null
+++ b/usr.bin/mandoc/apropos.1
@@ -0,0 +1,510 @@
+.\" $OpenBSD: apropos.1,v 1.41 2018/11/22 12:32:10 schwarze Exp $
+.\"
+.\" Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011,2012,2014,2017,2018 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: November 22 2018 $
+.Dt APROPOS 1
+.Os
+.Sh NAME
+.Nm apropos ,
+.Nm whatis
+.Nd search manual page databases
+.Sh SYNOPSIS
+.Nm
+.Op Fl afk
+.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, they display 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 extended regular expression matching
+over manual names and descriptions
+.Pq the Li \&Nm No and Li \&Nd No macro keys .
+Multiple terms imply pairwise
+.Fl o .
+.Pp
+.Nm whatis
+is a synonym for
+.Nm
+.Fl f .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Instead of showing only the title lines, show the complete manual pages,
+just like
+.Xr man 1
+.Fl a
+would.
+If the standard output is a terminal device and
+.Fl c
+is not specified, use
+.Xr more 1
+to paginate them.
+In
+.Fl a
+mode, the options
+.Fl IKOTW
+described in the
+.Xr mandoc 1
+manual are also available.
+.It Fl C Ar file
+Specify an alternative configuration
+.Ar file
+in
+.Xr man.conf 5
+format.
+.It Fl f
+Search for all words in
+.Ar expression
+in manual page names only.
+The search is case-insensitive and matches whole words only.
+In this mode, macro keys, comparison operators, and logical operators
+are not available.
+.It Fl k
+Support the full
+.Ar expression
+syntax.
+It is the default for
+.Nm .
+.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
+The options
+.Fl chlw
+are also supported and are documented in
+.Xr man 1 .
+The options
+.Fl fkl
+are mutually exclusive and override each other.
+.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 = | \(ti
+.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 \(ti
+evaluates a case-sensitive extended 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
+Results are sorted first according to the section number in ascending
+numerical order, then by the page name in ascending
+.Xr ascii 7
+alphabetical order, case-insensitive.
+.Pp
+Each output line is 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
+.Pp
+In general, macro keys are supposed to yield complete results without
+expecting the user to consider actual macro usage.
+For example, results include:
+.Pp
+.Bl -tag -width 3n -offset 3n -compact
+.It Li \&Fa
+function arguments appearing on
+.Ic \&Fn
+lines
+.It Li \&Fn
+function names marked up with
+.Ic \&Fo
+macros
+.It Li \&In
+include file names marked up with
+.Ic \&Fd
+macros
+.It Li \&Vt
+types appearing as function return types and
+.It \&
+types appearing in function arguments in the SYNOPSIS
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width MANPAGER
+.It Ev MANPAGER
+Any non-empty value of the environment variable
+.Ev MANPAGER
+is used instead of the standard pagination program,
+.Xr more 1 ;
+see
+.Xr man 1
+for details.
+Only used if
+.Fl a
+or
+.Fl l
+is specified.
+.It Ev MANPATH
+A colon-separated list of directories to search for manual pages; see
+.Xr man 1
+for details.
+Overridden by
+.Fl M ,
+ignored if
+.Fl l
+is specified.
+.It Ev PAGER
+Specifies the pagination program to use when
+.Ev MANPAGER
+is not defined.
+If neither PAGER nor MANPAGER is defined,
+.Xr more 1
+.Fl s
+is used.
+Only used if
+.Fl a
+or
+.Fl l
+is specified.
+.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 case-sensitive regular expression:
+.Pp
+.Dl $ apropos \(aq\(tiset.?[ug]id\(aq
+.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
+.Nm whatis
+with the argument
+.Qq ssh :
+.Pp
+.Dl $ apropos \-\- \-i \(aqNm\(ti[[:<:]]ssh[[:>:]]\(aq
+.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\(ti^( Ns Ar arch Ns Li |any)$
+.Li -a sec\(ti^ Ns Ar section Ns Li $
+.Ed
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr re_format 7 ,
+.Xr makewhatis 8
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification of
+.Xr man 1
+.Fl k .
+.Pp
+All options, the
+.Nm whatis
+command, support for logical operators, macro keys,
+substring matching, sorting of results, the environment variables
+.Ev MANPAGER
+and
+.Ev MANPATH ,
+the database format, and the configuration file
+are extensions to that specification.
+.Sh HISTORY
+Part of the functionality of
+.Nm whatis
+was already provided by the former
+.Nm manwhere
+utility in
+.Bx 1 .
+The
+.Nm
+and
+.Nm whatis
+utilities first appeared in
+.Bx 2 .
+They were 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
+for
+.Nm
+and in
+.Ox 5.6
+for
+.Nm whatis .
+The options
+.Fl acfhIKklOTWw
+appeared in
+.Ox 5.7 .
+.Sh AUTHORS
+.An -nosplit
+.An Bill Joy
+wrote
+.Nm manwhere
+in 1977 and the original
+.Bx
+.Nm
+and
+.Nm whatis
+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/usr.bin/mandoc/arch.c b/usr.bin/mandoc/arch.c
new file mode 100644
index 0000000..68a20bb
--- /dev/null
+++ b/usr.bin/mandoc/arch.c
@@ -0,0 +1,52 @@
+/* $OpenBSD: arch.c,v 1.11 2019/05/11 07:18:17 deraadt Exp $ */
+/*
+ * Copyright (c) 2017, 2019 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.
+ */
+#include <string.h>
+
+#include "roff.h"
+
+int
+arch_valid(const char *arch, enum mandoc_os os)
+{
+ const char *openbsd_arch[] = {
+ "alpha", "amd64", "arm64", "armv7", "hppa", "i386",
+ "landisk", "loongson", "luna88k", "macppc", "mips64",
+ "octeon", "sgi", "sparc64", NULL
+ };
+ const char *netbsd_arch[] = {
+ "acorn26", "acorn32", "algor", "alpha", "amiga",
+ "arc", "atari",
+ "bebox", "cats", "cesfic", "cobalt", "dreamcast",
+ "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5",
+ "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa",
+ "i386", "ibmnws", "luna68k",
+ "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc",
+ "netwinder", "news68k", "newsmips", "next68k",
+ "pc532", "playstation2", "pmax", "pmppc", "prep",
+ "sandpoint", "sbmips", "sgimips", "shark",
+ "sparc", "sparc64", "sun2", "sun3",
+ "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL
+ };
+ const char **arches[] = { NULL, netbsd_arch, openbsd_arch };
+ const char **arch_p;
+
+ if ((arch_p = arches[os]) == NULL)
+ return 1;
+ for (; *arch_p != NULL; arch_p++)
+ if (strcmp(*arch_p, arch) == 0)
+ return 1;
+ return 0;
+}
diff --git a/usr.bin/mandoc/att.c b/usr.bin/mandoc/att.c
new file mode 100644
index 0000000..85c184e
--- /dev/null
+++ b/usr.bin/mandoc/att.c
@@ -0,0 +1,47 @@
+/* $OpenBSD: att.c,v 1.14 2018/12/13 11:55:14 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.
+ */
+#include <sys/types.h>
+#include <string.h>
+
+#include "roff.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+ if (0 == strcmp(p, x)) return(y)
+
+
+const char *
+mdoc_a2att(const char *p)
+{
+
+ 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");
+
+ return NULL;
+}
diff --git a/usr.bin/mandoc/cgi.c b/usr.bin/mandoc/cgi.c
new file mode 100644
index 0000000..766ac06
--- /dev/null
+++ b/usr.bin/mandoc/cgi.c
@@ -0,0 +1,1255 @@
+/* $OpenBSD: cgi.c,v 1.110 2020/04/03 11:34:19 schwarze Exp $ */
+/*
+ * Copyright (c) 2014-2019 Ingo Schwarze <schwarze@usta.de>
+ * Copyright (c) 2011, 2012 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 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.
+ *
+ * Implementation of the man.cgi(8) program.
+ */
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <err.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_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "man.h"
+#include "mandoc_parse.h"
+#include "main.h"
+#include "manconf.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 */
+ int isquery; /* QUERY_STRING used, not PATH_INFO */
+};
+
+enum focus {
+ FOCUS_NONE = 0,
+ FOCUS_QUERY
+};
+
+static void html_print(const char *);
+static void html_putchar(char);
+static int http_decode(char *);
+static void http_encode(const char *);
+static void parse_manpath_conf(struct req *);
+static void parse_path_info(struct req *, const char *);
+static void parse_query_string(struct req *, const char *);
+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 *, int, const char *,
+ const char *);
+static void pg_redirect(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 *, const char *);
+static void resp_begin_http(int, const char *);
+static void resp_catman(const struct req *, const char *);
+static void resp_copy(const char *);
+static void resp_end_html(void);
+static void resp_format(const struct req *, const char *);
+static void resp_searchform(const struct req *, enum focus);
+static void resp_show(const struct req *, const char *);
+static void set_query_attr(char **, char **);
+static int validate_arch(const 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 = 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 - Library Functions",
+ "3p - Perl Library",
+ "4 - Device Drivers",
+ "5 - File Formats",
+ "6 - Games",
+ "7 - Miscellaneous Information",
+ "8 - System Manager\'s Manual",
+ "9 - Kernel Developer\'s Manual"
+};
+static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
+
+static const char *const arch_names[] = {
+ "amd64", "alpha", "armv7", "arm64",
+ "hppa", "i386", "landisk",
+ "loongson", "luna88k", "macppc", "mips64",
+ "octeon", "sgi", "socppc", "sparc64",
+ "amiga", "arc", "armish", "arm32",
+ "atari", "aviion", "beagle", "cats",
+ "hppa64", "hp300",
+ "ia64", "mac68k", "mvme68k", "mvme88k",
+ "mvmeppc", "palm", "pc532", "pegasos",
+ "pmax", "powerpc", "solbourne", "sparc",
+ "sun3", "vax", "wgrisc", "x68k",
+ "zaurus"
+};
+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("&quot;");
+ break;
+ case '&':
+ printf("&amp;");
+ break;
+ case '>':
+ printf("&gt;");
+ break;
+ case '<':
+ printf("&lt;");
+ break;
+ default:
+ putchar((unsigned char)c);
+ break;
+ }
+}
+
+/*
+ * 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
+parse_query_string(struct req *req, const char *qs)
+{
+ char *key, *val;
+ size_t keysz, valsz;
+
+ req->isquery = 1;
+ 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++;
+ }
+}
+
+/*
+ * 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
+http_encode(const char *p)
+{
+ for (; *p != '\0'; p++) {
+ if (isalnum((unsigned char)*p) == 0 &&
+ strchr("-._~", *p) == NULL)
+ printf("%%%2.2X", (unsigned char)*p);
+ else
+ putchar(*p);
+ }
+}
+
+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"
+ "Content-Security-Policy: default-src 'none'; "
+ "style-src 'self' 'unsafe-inline'\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n");
+
+ fflush(stdout);
+}
+
+static void
+resp_copy(const char *filename)
+{
+ char buf[4096];
+ ssize_t sz;
+ int fd;
+
+ if ((fd = open(filename, O_RDONLY)) != -1) {
+ fflush(stdout);
+ while ((sz = read(fd, buf, sizeof(buf))) > 0)
+ write(STDOUT_FILENO, buf, sz);
+ close(fd);
+ }
+}
+
+static void
+resp_begin_html(int code, const char *msg, const char *file)
+{
+ char *cp;
+
+ resp_begin_http(code, msg);
+
+ printf("<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <meta charset=\"UTF-8\"/>\n"
+ " <meta name=\"viewport\""
+ " content=\"width=device-width, initial-scale=1.0\">\n"
+ " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
+ " type=\"text/css\" media=\"all\">\n"
+ " <title>",
+ CSS_DIR);
+ if (file != NULL) {
+ if ((cp = strrchr(file, '/')) != NULL)
+ file = cp + 1;
+ if ((cp = strrchr(file, '.')) != NULL) {
+ printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1);
+ } else
+ printf("%s - ", file);
+ }
+ printf("%s</title>\n"
+ "</head>\n"
+ "<body>\n",
+ CUSTOMIZE_TITLE);
+
+ resp_copy(MAN_DIR "/header.html");
+}
+
+static void
+resp_end_html(void)
+{
+
+ resp_copy(MAN_DIR "/footer.html");
+
+ puts("</body>\n"
+ "</html>");
+}
+
+static void
+resp_searchform(const struct req *req, enum focus focus)
+{
+ int i;
+
+ printf("<form action=\"/%s\" method=\"get\" "
+ "autocomplete=\"off\" autocapitalize=\"none\">\n"
+ " <fieldset>\n"
+ " <legend>Manual Page Search Parameters</legend>\n",
+ scriptname);
+
+ /* Write query input box. */
+
+ printf(" <input type=\"search\" name=\"query\" value=\"");
+ if (req->q.query != NULL)
+ html_print(req->q.query);
+ printf( "\" size=\"40\"");
+ if (focus == FOCUS_QUERY)
+ printf(" autofocus");
+ puts(">");
+
+ /* Write submission buttons. */
+
+ printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
+ "man</button>\n"
+ " <button type=\"submit\" name=\"apropos\" value=\"1\">"
+ "apropos</button>\n"
+ " <br/>\n");
+
+ /* Write section selector. */
+
+ puts(" <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");
+ 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 (strcmp(req->q.manpath, req->p[i]) == 0)
+ printf(" selected=\"selected\"");
+ printf(">");
+ html_print(req->p[i]);
+ puts("</option>");
+ }
+ puts(" </select>");
+ }
+
+ puts(" </fieldset>\n"
+ "</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;
+
+ for (i = 0; i < req->psz; i++)
+ if ( ! strcmp(manpath, req->p[i]))
+ return 1;
+
+ return 0;
+}
+
+static int
+validate_arch(const char *arch)
+{
+ int i;
+
+ for (i = 0; i < arch_MAX; i++)
+ if (strcmp(arch, arch_names[i]) == 0)
+ 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, NULL);
+ resp_searchform(req, FOCUS_QUERY);
+ printf("<p>\n"
+ "This web interface is documented in the\n"
+ "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
+ "manual, and the\n"
+ "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
+ "manual explains the query syntax.\n"
+ "</p>\n",
+ scriptname, *scriptname == '\0' ? "" : "/",
+ scriptname, *scriptname == '\0' ? "" : "/");
+ resp_end_html();
+}
+
+static void
+pg_noresult(const struct req *req, int code, const char *http_msg,
+ const char *user_msg)
+{
+ resp_begin_html(code, http_msg, NULL);
+ resp_searchform(req, FOCUS_QUERY);
+ puts("<p>");
+ puts(user_msg);
+ puts("</p>");
+ resp_end_html();
+}
+
+static void
+pg_error_badrequest(const char *msg)
+{
+
+ resp_begin_html(400, "Bad Request", NULL);
+ 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", NULL);
+ puts("<p>Internal Server Error</p>");
+ resp_end_html();
+}
+
+static void
+pg_redirect(const struct req *req, const char *name)
+{
+ printf("Status: 303 See Other\r\n"
+ "Location: /");
+ if (*scriptname != '\0')
+ printf("%s/", scriptname);
+ if (strcmp(req->q.manpath, req->p[0]))
+ printf("%s/", req->q.manpath);
+ if (req->q.arch != NULL)
+ printf("%s/", req->q.arch);
+ http_encode(name);
+ if (req->q.sec != NULL) {
+ putchar('.');
+ http_encode(req->q.sec);
+ }
+ printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
+}
+
+static void
+pg_searchres(const struct req *req, struct manpage *r, size_t sz)
+{
+ char *arch, *archend;
+ const char *sec;
+ size_t i, iuse;
+ int archprio, archpriouse;
+ int prio, priouse;
+
+ for (i = 0; i < sz; i++) {
+ if (validate_filename(r[i].file))
+ continue;
+ warnx("invalid filename %s in %s database",
+ r[i].file, req->q.manpath);
+ pg_error_internal();
+ return;
+ }
+
+ if (req->isquery && sz == 1) {
+ /*
+ * If we have just one result, then jump there now
+ * without any delay.
+ */
+ printf("Status: 303 See Other\r\n"
+ "Location: /");
+ if (*scriptname != '\0')
+ printf("%s/", scriptname);
+ if (strcmp(req->q.manpath, req->p[0]))
+ printf("%s/", req->q.manpath);
+ printf("%s\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n\r\n",
+ r[0].file);
+ return;
+ }
+
+ /*
+ * In man(1) mode, show one of the pages
+ * even if more than one is found.
+ */
+
+ iuse = 0;
+ if (req->q.equal || sz == 1) {
+ priouse = 20;
+ archpriouse = 3;
+ for (i = 0; i < sz; i++) {
+ sec = r[i].file;
+ sec += strcspn(sec, "123456789");
+ if (sec[0] == '\0')
+ continue;
+ prio = sec_prios[sec[0] - '1'];
+ if (sec[1] != '/')
+ prio += 10;
+ if (req->q.arch == NULL) {
+ archprio =
+ ((arch = strchr(sec + 1, '/'))
+ == NULL) ? 3 :
+ ((archend = strchr(arch + 1, '/'))
+ == NULL) ? 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_begin_html(200, NULL, r[iuse].file);
+ } else
+ resp_begin_html(200, NULL, NULL);
+
+ resp_searchform(req,
+ req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
+
+ if (sz > 1) {
+ puts("<table class=\"results\">");
+ for (i = 0; i < sz; i++) {
+ printf(" <tr>\n"
+ " <td>"
+ "<a class=\"Xr\" href=\"/");
+ if (*scriptname != '\0')
+ printf("%s/", scriptname);
+ if (strcmp(req->q.manpath, req->p[0]))
+ printf("%s/", req->q.manpath);
+ printf("%s\">", r[i].file);
+ html_print(r[i].names);
+ printf("</a></td>\n"
+ " <td><span class=\"Nd\">");
+ html_print(r[i].output);
+ puts("</span></td>\n"
+ " </tr>");
+ }
+ puts("</table>");
+ }
+
+ if (req->q.equal || sz == 1) {
+ puts("<hr>");
+ resp_show(req, r[iuse].file);
+ }
+
+ resp_end_html();
+}
+
+static void
+resp_catman(const struct req *req, const char *file)
+{
+ FILE *f;
+ char *p;
+ size_t sz;
+ ssize_t len;
+ int i;
+ int italic, bold;
+
+ if ((f = fopen(file, "r")) == NULL) {
+ puts("<p>You specified an invalid manual file.</p>");
+ return;
+ }
+
+ puts("<div class=\"catman\">\n"
+ "<pre>");
+
+ p = NULL;
+ sz = 0;
+
+ while ((len = getline(&p, &sz, f)) != -1) {
+ bold = italic = 0;
+ for (i = 0; i < 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 >= 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 == len - 1 && p[i] != '\n')
+ html_putchar(p[i]);
+
+ putchar('\n');
+ }
+ free(p);
+
+ puts("</pre>\n"
+ "</div>");
+
+ fclose(f);
+}
+
+static void
+resp_format(const struct req *req, const char *file)
+{
+ struct manoutput conf;
+ struct mparse *mp;
+ struct roff_meta *meta;
+ void *vp;
+ int fd;
+ int usepath;
+
+ if (-1 == (fd = open(file, O_RDONLY, 0))) {
+ puts("<p>You specified an invalid manual file.</p>");
+ return;
+ }
+
+ mchars_alloc();
+ mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
+ MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
+ mparse_readfd(mp, fd, file);
+ close(fd);
+ meta = mparse_result(mp);
+
+ memset(&conf, 0, sizeof(conf));
+ conf.fragment = 1;
+ conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
+ usepath = strcmp(req->q.manpath, req->p[0]);
+ mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
+ scriptname, *scriptname == '\0' ? "" : "/",
+ usepath ? req->q.manpath : "", usepath ? "/" : "");
+
+ vp = html_alloc(&conf);
+ if (meta->macroset == MACROSET_MDOC)
+ html_mdoc(vp, meta);
+ else
+ html_man(vp, meta);
+
+ html_free(vp);
+ mparse_free(mp);
+ mchars_free();
+ free(conf.man);
+ free(conf.style);
+}
+
+static void
+resp_show(const struct req *req, const char *file)
+{
+
+ if ('.' == file[0] && '/' == file[1])
+ file += 2;
+
+ if ('c' == *file)
+ resp_catman(req, file);
+ else
+ resp_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) {
+ warn("chdir %s", manpath);
+ pg_error_internal();
+ free(manpath);
+ return;
+ }
+ free(manpath);
+
+ if ( ! validate_filename(file)) {
+ pg_error_badrequest(
+ "You specified an invalid manual file.");
+ return;
+ }
+
+ resp_begin_html(200, NULL, file);
+ resp_searchform(req, FOCUS_NONE);
+ 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 **argv;
+ char *query, *rp, *wp;
+ size_t ressz;
+ int argc;
+
+ /*
+ * 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 (chdir(req->q.manpath) == -1) {
+ warn("chdir %s", req->q.manpath);
+ pg_error_internal();
+ return;
+ }
+
+ search.arch = req->q.arch;
+ search.sec = req->q.sec;
+ search.outkey = "Nd";
+ search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
+ search.firstmatch = 1;
+
+ paths.sz = 1;
+ paths.paths = mandoc_malloc(sizeof(char *));
+ paths.paths[0] = mandoc_strdup(".");
+
+ /*
+ * Break apart at spaces with backslash-escaping.
+ */
+
+ argc = 0;
+ argv = NULL;
+ rp = query = mandoc_strdup(req->q.query);
+ for (;;) {
+ while (isspace((unsigned char)*rp))
+ rp++;
+ if (*rp == '\0')
+ break;
+ argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
+ argv[argc++] = wp = rp;
+ for (;;) {
+ if (isspace((unsigned char)*rp)) {
+ *wp = '\0';
+ rp++;
+ break;
+ }
+ if (rp[0] == '\\' && rp[1] != '\0')
+ rp++;
+ if (wp != rp)
+ *wp = *rp;
+ if (*rp == '\0')
+ break;
+ wp++;
+ rp++;
+ }
+ }
+
+ res = NULL;
+ ressz = 0;
+ if (req->isquery && req->q.equal && argc == 1)
+ pg_redirect(req, argv[0]);
+ else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
+ pg_noresult(req, 400, "Bad Request",
+ "You entered an invalid query.");
+ else if (ressz == 0)
+ pg_noresult(req, 404, "Not Found", "No results found.");
+ else
+ pg_searchres(req, res, ressz);
+
+ free(query);
+ mansearch_free(res, ressz);
+ free(paths.paths[0]);
+ free(paths.paths);
+}
+
+int
+main(void)
+{
+ struct req req;
+ struct itimerval itimer;
+ const char *path;
+ const char *querystring;
+ int i;
+
+ /*
+ * The "rpath" pledge could be revoked after mparse_readfd()
+ * if the file desciptor to "/footer.html" would be opened
+ * up front, but it's probably not worth the complication
+ * of the code it would cause: it would require scattering
+ * pledge() calls in multiple low-level resp_*() functions.
+ */
+
+ if (pledge("stdio rpath", NULL) == -1) {
+ warn("pledge");
+ pg_error_internal();
+ return EXIT_FAILURE;
+ }
+
+ /* Poor man's ReDoS mitigation. */
+
+ itimer.it_value.tv_sec = 2;
+ itimer.it_value.tv_usec = 0;
+ itimer.it_interval.tv_sec = 2;
+ itimer.it_interval.tv_usec = 0;
+ if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
+ warn("setitimer");
+ 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 (chdir(MAN_DIR) == -1) {
+ warn("MAN_DIR: %s", MAN_DIR);
+ pg_error_internal();
+ return EXIT_FAILURE;
+ }
+
+ memset(&req, 0, sizeof(struct req));
+ req.q.equal = 1;
+ parse_manpath_conf(&req);
+
+ /* Parse the path info and the query string. */
+
+ if ((path = getenv("PATH_INFO")) == NULL)
+ path = "";
+ else if (*path == '/')
+ path++;
+
+ if (*path != '\0') {
+ parse_path_info(&req, path);
+ if (req.q.manpath == NULL || req.q.sec == NULL ||
+ *req.q.query == '\0' || access(path, F_OK) == -1)
+ path = "";
+ } else if ((querystring = getenv("QUERY_STRING")) != NULL)
+ parse_query_string(&req, querystring);
+
+ /* Validate parsed data and add defaults. */
+
+ if (req.q.manpath == NULL)
+ req.q.manpath = mandoc_strdup(req.p[0]);
+ else if ( ! validate_manpath(&req, req.q.manpath)) {
+ pg_error_badrequest(
+ "You specified an invalid manpath.");
+ return EXIT_FAILURE;
+ }
+
+ if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
+ pg_error_badrequest(
+ "You specified an invalid architecture.");
+ return EXIT_FAILURE;
+ }
+
+ /* Dispatch to the three different pages. */
+
+ 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;
+}
+
+/*
+ * Translate PATH_INFO to a query.
+ */
+static void
+parse_path_info(struct req *req, const char *path)
+{
+ const char *name, *sec, *end;
+
+ req->isquery = 0;
+ req->q.equal = 1;
+ req->q.manpath = NULL;
+ req->q.arch = NULL;
+
+ /* Mandatory manual page name. */
+ if ((name = strrchr(path, '/')) == NULL)
+ name = path;
+ else
+ name++;
+
+ /* Optional trailing section. */
+ sec = strrchr(name, '.');
+ if (sec != NULL && isdigit((unsigned char)*++sec)) {
+ req->q.query = mandoc_strndup(name, sec - name - 1);
+ req->q.sec = mandoc_strdup(sec);
+ } else {
+ req->q.query = mandoc_strdup(name);
+ req->q.sec = NULL;
+ }
+
+ /* Handle the case of name[.section] only. */
+ if (name == path)
+ return;
+
+ /* Optional manpath. */
+ end = strchr(path, '/');
+ req->q.manpath = mandoc_strndup(path, end - path);
+ if (validate_manpath(req, req->q.manpath)) {
+ path = end + 1;
+ if (name == path)
+ return;
+ } else {
+ free(req->q.manpath);
+ req->q.manpath = NULL;
+ }
+
+ /* Optional section. */
+ if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
+ path += 3;
+ end = strchr(path, '/');
+ free(req->q.sec);
+ req->q.sec = mandoc_strndup(path, end - path);
+ path = end + 1;
+ if (name == path)
+ return;
+ }
+
+ /* Optional architecture. */
+ end = strchr(path, '/');
+ if (end + 1 != name) {
+ pg_error_badrequest(
+ "You specified too many directory components.");
+ exit(EXIT_FAILURE);
+ }
+ req->q.arch = mandoc_strndup(path, end - path);
+ if (validate_arch(req->q.arch) == 0) {
+ pg_error_badrequest(
+ "You specified an invalid directory component.");
+ exit(EXIT_FAILURE);
+ }
+}
+
+/*
+ * Scan for indexable paths.
+ */
+static void
+parse_manpath_conf(struct req *req)
+{
+ FILE *fp;
+ char *dp;
+ size_t dpsz;
+ ssize_t len;
+
+ if ((fp = fopen("manpath.conf", "r")) == NULL) {
+ warn("%s/manpath.conf", MAN_DIR);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+
+ dp = NULL;
+ dpsz = 0;
+
+ while ((len = getline(&dp, &dpsz, fp)) != -1) {
+ if (dp[len - 1] == '\n')
+ dp[--len] = '\0';
+ req->p = mandoc_realloc(req->p,
+ (req->psz + 1) * sizeof(char *));
+ if ( ! validate_urifrag(dp)) {
+ warnx("%s/manpath.conf contains "
+ "unsafe path \"%s\"", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ if (strchr(dp, '/') != NULL) {
+ warnx("%s/manpath.conf contains "
+ "path with slash \"%s\"", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ req->p[req->psz++] = dp;
+ dp = NULL;
+ dpsz = 0;
+ }
+ free(dp);
+
+ if (req->p == NULL) {
+ warnx("%s/manpath.conf is empty", MAN_DIR);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+}
diff --git a/usr.bin/mandoc/cgi.h.example b/usr.bin/mandoc/cgi.h.example
new file mode 100644
index 0000000..2ccbe25
--- /dev/null
+++ b/usr.bin/mandoc/cgi.h.example
@@ -0,0 +1,7 @@
+/* Example compile-time configuration file for man.cgi(8). */
+
+#define SCRIPT_NAME "cgi-bin/man.cgi"
+#define MAN_DIR "/man"
+#define CSS_DIR ""
+#define CUSTOMIZE_TITLE "Manual pages with mandoc"
+#define COMPAT_OLDURI Yes
diff --git a/usr.bin/mandoc/chars.c b/usr.bin/mandoc/chars.c
new file mode 100644
index 0000000..d091ab2
--- /dev/null
+++ b/usr.bin/mandoc/chars.c
@@ -0,0 +1,506 @@
+/* $OpenBSD: chars.c,v 1.49 2020/02/13 16:16:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2020
+ * 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.
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "libmandoc.h"
+
+struct ln {
+ const char roffcode[16];
+ const char *ascii;
+ int unicode;
+};
+
+/* Special break control characters. */
+static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' };
+static const char ascii_break[2] = { ASCII_BREAK, '\0' };
+
+static struct ln lines[] = {
+
+ /* Spacing. */
+ { " ", ascii_nbrsp, 0x00a0 },
+ { "~", ascii_nbrsp, 0x00a0 },
+ { "0", ascii_nbrsp, 0x00a0 },
+ { ":", ascii_break, 0 },
+
+ /* Lines. */
+ { "ba", "|", 0x007c },
+ { "br", "|", 0x2502 },
+ { "ul", "_", 0x005f },
+ { "_", "_", 0x005f },
+ { "ru", "_", 0x005f },
+ { "rn", "-", 0x203e },
+ { "bb", "|", 0x00a6 },
+ { "sl", "/", 0x002f },
+ { "rs", "\\", 0x005c },
+
+ /* Text markers. */
+ { "ci", "O", 0x25cb },
+ { "bu", "+\bo", 0x2022 },
+ { "dd", "<**>", 0x2021 },
+ { "dg", "<*>", 0x2020 },
+ { "lz", "<>", 0x25ca },
+ { "sq", "[]", 0x25a1 },
+ { "ps", "<paragraph>", 0x00b6 },
+ { "sc", "<section>", 0x00a7 },
+ { "lh", "<=", 0x261c },
+ { "rh", "=>", 0x261e },
+ { "at", "@", 0x0040 },
+ { "sh", "#", 0x0023 },
+ { "CR", "<cr>", 0x21b5 },
+ { "OK", "\\/", 0x2713 },
+ { "CL", "C", 0x2663 },
+ { "SP", "S", 0x2660 },
+ { "HE", "H", 0x2665 },
+ { "DI", "D", 0x2666 },
+
+ /* Legal symbols. */
+ { "co", "(C)", 0x00a9 },
+ { "rg", "(R)", 0x00ae },
+ { "tm", "tm", 0x2122 },
+
+ /* Punctuation. */
+ { "em", "--", 0x2014 },
+ { "en", "-", 0x2013 },
+ { "hy", "-", 0x2010 },
+ { "e", "\\", 0x005c },
+ { ".", ".", 0x002e },
+ { "r!", "!", 0x00a1 },
+ { "r?", "?", 0x00bf },
+
+ /* Quotes. */
+ { "Bq", ",,", 0x201e },
+ { "bq", ",", 0x201a },
+ { "lq", "\"", 0x201c },
+ { "rq", "\"", 0x201d },
+ { "Lq", "\"", 0x201c },
+ { "Rq", "\"", 0x201d },
+ { "oq", "`", 0x2018 },
+ { "cq", "\'", 0x2019 },
+ { "aq", "\'", 0x0027 },
+ { "dq", "\"", 0x0022 },
+ { "Fo", "<<", 0x00ab },
+ { "Fc", ">>", 0x00bb },
+ { "fo", "<", 0x2039 },
+ { "fc", ">", 0x203a },
+
+ /* Brackets. */
+ { "lB", "[", 0x005b },
+ { "rB", "]", 0x005d },
+ { "lC", "{", 0x007b },
+ { "rC", "}", 0x007d },
+ { "la", "<", 0x27e8 },
+ { "ra", ">", 0x27e9 },
+ { "bv", "|", 0x23aa },
+ { "braceex", "|", 0x23aa },
+ { "bracketlefttp", "|", 0x23a1 },
+ { "bracketleftbt", "|", 0x23a3 },
+ { "bracketleftex", "|", 0x23a2 },
+ { "bracketrighttp", "|", 0x23a4 },
+ { "bracketrightbt", "|", 0x23a6 },
+ { "bracketrightex", "|", 0x23a5 },
+ { "lt", ",-", 0x23a7 },
+ { "bracelefttp", ",-", 0x23a7 },
+ { "lk", "{", 0x23a8 },
+ { "braceleftmid", "{", 0x23a8 },
+ { "lb", "`-", 0x23a9 },
+ { "braceleftbt", "`-", 0x23a9 },
+ { "braceleftex", "|", 0x23aa },
+ { "rt", "-.", 0x23ab },
+ { "bracerighttp", "-.", 0x23ab },
+ { "rk", "}", 0x23ac },
+ { "bracerightmid", "}", 0x23ac },
+ { "rb", "-\'", 0x23ad },
+ { "bracerightbt", "-\'", 0x23ad },
+ { "bracerightex", "|", 0x23aa },
+ { "parenlefttp", "/", 0x239b },
+ { "parenleftbt", "\\", 0x239d },
+ { "parenleftex", "|", 0x239c },
+ { "parenrighttp", "\\", 0x239e },
+ { "parenrightbt", "/", 0x23a0 },
+ { "parenrightex", "|", 0x239f },
+
+ /* Arrows and lines. */
+ { "<-", "<-", 0x2190 },
+ { "->", "->", 0x2192 },
+ { "<>", "<->", 0x2194 },
+ { "da", "|\bv", 0x2193 },
+ { "ua", "|\b^", 0x2191 },
+ { "va", "^v", 0x2195 },
+ { "lA", "<=", 0x21d0 },
+ { "rA", "=>", 0x21d2 },
+ { "hA", "<=>", 0x21d4 },
+ { "uA", "=\b^", 0x21d1 },
+ { "dA", "=\bv", 0x21d3 },
+ { "vA", "^=v", 0x21d5 },
+ { "an", "-", 0x23af },
+
+ /* Logic. */
+ { "AN", "^", 0x2227 },
+ { "OR", "v", 0x2228 },
+ { "no", "~", 0x00ac },
+ { "tno", "~", 0x00ac },
+ { "te", "<there\037exists>", 0x2203 },
+ { "fa", "<for\037all>", 0x2200 },
+ { "st", "<such\037that>", 0x220b },
+ { "tf", "<therefore>", 0x2234 },
+ { "3d", "<therefore>", 0x2234 },
+ { "or", "|", 0x007c },
+
+ /* Mathematicals. */
+ { "pl", "+", 0x002b },
+ { "mi", "-", 0x2212 },
+ { "-", "-", 0x002d },
+ { "-+", "-+", 0x2213 },
+ { "+-", "+-", 0x00b1 },
+ { "t+-", "+-", 0x00b1 },
+ { "pc", ".", 0x00b7 },
+ { "md", ".", 0x22c5 },
+ { "mu", "x", 0x00d7 },
+ { "tmu", "x", 0x00d7 },
+ { "c*", "O\bx", 0x2297 },
+ { "c+", "O\b+", 0x2295 },
+ { "di", "/", 0x00f7 },
+ { "tdi", "/", 0x00f7 },
+ { "f/", "/", 0x2044 },
+ { "**", "*", 0x2217 },
+ { "<=", "<=", 0x2264 },
+ { ">=", ">=", 0x2265 },
+ { "<<", "<<", 0x226a },
+ { ">>", ">>", 0x226b },
+ { "eq", "=", 0x003d },
+ { "!=", "!=", 0x2260 },
+ { "==", "==", 0x2261 },
+ { "ne", "!==", 0x2262 },
+ { "ap", "~", 0x223c },
+ { "|=", "-~", 0x2243 },
+ { "=~", "=~", 0x2245 },
+ { "~~", "~~", 0x2248 },
+ { "~=", "~=", 0x2248 },
+ { "pt", "<proportional\037to>", 0x221d },
+ { "es", "{}", 0x2205 },
+ { "mo", "<element\037of>", 0x2208 },
+ { "nm", "<not\037element\037of>", 0x2209 },
+ { "sb", "<proper\037subset>", 0x2282 },
+ { "nb", "<not\037subset>", 0x2284 },
+ { "sp", "<proper\037superset>", 0x2283 },
+ { "nc", "<not\037superset>", 0x2285 },
+ { "ib", "<subset\037or\037equal>", 0x2286 },
+ { "ip", "<superset\037or\037equal>", 0x2287 },
+ { "ca", "<intersection>", 0x2229 },
+ { "cu", "<union>", 0x222a },
+ { "/_", "<angle>", 0x2220 },
+ { "pp", "<perpendicular>", 0x22a5 },
+ { "is", "<integral>", 0x222b },
+ { "integral", "<integral>", 0x222b },
+ { "sum", "<sum>", 0x2211 },
+ { "product", "<product>", 0x220f },
+ { "coproduct", "<coproduct>", 0x2210 },
+ { "gr", "<nabla>", 0x2207 },
+ { "sr", "<sqrt>", 0x221a },
+ { "sqrt", "<sqrt>", 0x221a },
+ { "lc", "|~", 0x2308 },
+ { "rc", "~|", 0x2309 },
+ { "lf", "|_", 0x230a },
+ { "rf", "_|", 0x230b },
+ { "if", "<infinity>", 0x221e },
+ { "Ah", "<Aleph>", 0x2135 },
+ { "Im", "<Im>", 0x2111 },
+ { "Re", "<Re>", 0x211c },
+ { "wp", "p", 0x2118 },
+ { "pd", "<del>", 0x2202 },
+ { "-h", "/h", 0x210f },
+ { "hbar", "/h", 0x210f },
+ { "12", "1/2", 0x00bd },
+ { "14", "1/4", 0x00bc },
+ { "34", "3/4", 0x00be },
+ { "18", "1/8", 0x215B },
+ { "38", "3/8", 0x215C },
+ { "58", "5/8", 0x215D },
+ { "78", "7/8", 0x215E },
+ { "S1", "^1", 0x00B9 },
+ { "S2", "^2", 0x00B2 },
+ { "S3", "^3", 0x00B3 },
+
+ /* Ligatures. */
+ { "ff", "ff", 0xfb00 },
+ { "fi", "fi", 0xfb01 },
+ { "fl", "fl", 0xfb02 },
+ { "Fi", "ffi", 0xfb03 },
+ { "Fl", "ffl", 0xfb04 },
+ { "AE", "AE", 0x00c6 },
+ { "ae", "ae", 0x00e6 },
+ { "OE", "OE", 0x0152 },
+ { "oe", "oe", 0x0153 },
+ { "ss", "ss", 0x00df },
+ { "IJ", "IJ", 0x0132 },
+ { "ij", "ij", 0x0133 },
+
+ /* Accents. */
+ { "a\"", "\"", 0x02dd },
+ { "a-", "-", 0x00af },
+ { "a.", ".", 0x02d9 },
+ { "a^", "^", 0x005e },
+ { "aa", "\'", 0x00b4 },
+ { "\'", "\'", 0x00b4 },
+ { "ga", "`", 0x0060 },
+ { "`", "`", 0x0060 },
+ { "ab", "'\b`", 0x02d8 },
+ { "ac", ",", 0x00b8 },
+ { "ad", "\"", 0x00a8 },
+ { "ah", "v", 0x02c7 },
+ { "ao", "o", 0x02da },
+ { "a~", "~", 0x007e },
+ { "ho", ",", 0x02db },
+ { "ha", "^", 0x005e },
+ { "ti", "~", 0x007e },
+ { "u02DC", "~", 0x02dc },
+
+ /* Accented letters. */
+ { "'A", "'\bA", 0x00c1 },
+ { "'E", "'\bE", 0x00c9 },
+ { "'I", "'\bI", 0x00cd },
+ { "'O", "'\bO", 0x00d3 },
+ { "'U", "'\bU", 0x00da },
+ { "'Y", "'\bY", 0x00dd },
+ { "'a", "'\ba", 0x00e1 },
+ { "'e", "'\be", 0x00e9 },
+ { "'i", "'\bi", 0x00ed },
+ { "'o", "'\bo", 0x00f3 },
+ { "'u", "'\bu", 0x00fa },
+ { "'y", "'\by", 0x00fd },
+ { "`A", "`\bA", 0x00c0 },
+ { "`E", "`\bE", 0x00c8 },
+ { "`I", "`\bI", 0x00cc },
+ { "`O", "`\bO", 0x00d2 },
+ { "`U", "`\bU", 0x00d9 },
+ { "`a", "`\ba", 0x00e0 },
+ { "`e", "`\be", 0x00e8 },
+ { "`i", "`\bi", 0x00ec },
+ { "`o", "`\bo", 0x00f2 },
+ { "`u", "`\bu", 0x00f9 },
+ { "~A", "~\bA", 0x00c3 },
+ { "~N", "~\bN", 0x00d1 },
+ { "~O", "~\bO", 0x00d5 },
+ { "~a", "~\ba", 0x00e3 },
+ { "~n", "~\bn", 0x00f1 },
+ { "~o", "~\bo", 0x00f5 },
+ { ":A", "\"\bA", 0x00c4 },
+ { ":E", "\"\bE", 0x00cb },
+ { ":I", "\"\bI", 0x00cf },
+ { ":O", "\"\bO", 0x00d6 },
+ { ":U", "\"\bU", 0x00dc },
+ { ":a", "\"\ba", 0x00e4 },
+ { ":e", "\"\be", 0x00eb },
+ { ":i", "\"\bi", 0x00ef },
+ { ":o", "\"\bo", 0x00f6 },
+ { ":u", "\"\bu", 0x00fc },
+ { ":y", "\"\by", 0x00ff },
+ { "^A", "^\bA", 0x00c2 },
+ { "^E", "^\bE", 0x00ca },
+ { "^I", "^\bI", 0x00ce },
+ { "^O", "^\bO", 0x00d4 },
+ { "^U", "^\bU", 0x00db },
+ { "^a", "^\ba", 0x00e2 },
+ { "^e", "^\be", 0x00ea },
+ { "^i", "^\bi", 0x00ee },
+ { "^o", "^\bo", 0x00f4 },
+ { "^u", "^\bu", 0x00fb },
+ { ",C", ",\bC", 0x00c7 },
+ { ",c", ",\bc", 0x00e7 },
+ { "/L", "/\bL", 0x0141 },
+ { "/l", "/\bl", 0x0142 },
+ { "/O", "/\bO", 0x00d8 },
+ { "/o", "/\bo", 0x00f8 },
+ { "oA", "o\bA", 0x00c5 },
+ { "oa", "o\ba", 0x00e5 },
+
+ /* Special letters. */
+ { "-D", "Dh", 0x00d0 },
+ { "Sd", "dh", 0x00f0 },
+ { "TP", "Th", 0x00de },
+ { "Tp", "th", 0x00fe },
+ { ".i", "i", 0x0131 },
+ { ".j", "j", 0x0237 },
+
+ /* Currency. */
+ { "Do", "$", 0x0024 },
+ { "ct", "/\bc", 0x00a2 },
+ { "Eu", "EUR", 0x20ac },
+ { "eu", "EUR", 0x20ac },
+ { "Ye", "=\bY", 0x00a5 },
+ { "Po", "-\bL", 0x00a3 },
+ { "Cs", "o\bx", 0x00a4 },
+ { "Fn", ",\bf", 0x0192 },
+
+ /* Units. */
+ { "de", "<degree>", 0x00b0 },
+ { "%0", "<permille>", 0x2030 },
+ { "fm", "\'", 0x2032 },
+ { "sd", "''", 0x2033 },
+ { "mc", "<micro>", 0x00b5 },
+ { "Of", "_\ba", 0x00aa },
+ { "Om", "_\bo", 0x00ba },
+
+ /* Greek characters. */
+ { "*A", "A", 0x0391 },
+ { "*B", "B", 0x0392 },
+ { "*G", "<Gamma>", 0x0393 },
+ { "*D", "<Delta>", 0x0394 },
+ { "*E", "E", 0x0395 },
+ { "*Z", "Z", 0x0396 },
+ { "*Y", "H", 0x0397 },
+ { "*H", "<Theta>", 0x0398 },
+ { "*I", "I", 0x0399 },
+ { "*K", "K", 0x039a },
+ { "*L", "<Lambda>", 0x039b },
+ { "*M", "M", 0x039c },
+ { "*N", "N", 0x039d },
+ { "*C", "<Xi>", 0x039e },
+ { "*O", "O", 0x039f },
+ { "*P", "<Pi>", 0x03a0 },
+ { "*R", "P", 0x03a1 },
+ { "*S", "<Sigma>", 0x03a3 },
+ { "*T", "T", 0x03a4 },
+ { "*U", "Y", 0x03a5 },
+ { "*F", "<Phi>", 0x03a6 },
+ { "*X", "X", 0x03a7 },
+ { "*Q", "<Psi>", 0x03a8 },
+ { "*W", "<Omega>", 0x03a9 },
+ { "*a", "<alpha>", 0x03b1 },
+ { "*b", "<beta>", 0x03b2 },
+ { "*g", "<gamma>", 0x03b3 },
+ { "*d", "<delta>", 0x03b4 },
+ { "*e", "<epsilon>", 0x03b5 },
+ { "*z", "<zeta>", 0x03b6 },
+ { "*y", "<eta>", 0x03b7 },
+ { "*h", "<theta>", 0x03b8 },
+ { "*i", "<iota>", 0x03b9 },
+ { "*k", "<kappa>", 0x03ba },
+ { "*l", "<lambda>", 0x03bb },
+ { "*m", "<mu>", 0x03bc },
+ { "*n", "<nu>", 0x03bd },
+ { "*c", "<xi>", 0x03be },
+ { "*o", "o", 0x03bf },
+ { "*p", "<pi>", 0x03c0 },
+ { "*r", "<rho>", 0x03c1 },
+ { "*s", "<sigma>", 0x03c3 },
+ { "*t", "<tau>", 0x03c4 },
+ { "*u", "<upsilon>", 0x03c5 },
+ { "*f", "<phi>", 0x03d5 },
+ { "*x", "<chi>", 0x03c7 },
+ { "*q", "<psi>", 0x03c8 },
+ { "*w", "<omega>", 0x03c9 },
+ { "+h", "<theta>", 0x03d1 },
+ { "+f", "<phi>", 0x03c6 },
+ { "+p", "<pi>", 0x03d6 },
+ { "+e", "<epsilon>", 0x03f5 },
+ { "ts", "<sigma>", 0x03c2 },
+};
+
+static struct ohash mchars;
+
+
+void
+mchars_free(void)
+{
+
+ ohash_delete(&mchars);
+}
+
+void
+mchars_alloc(void)
+{
+ size_t i;
+ unsigned int slot;
+
+ mandoc_ohash_init(&mchars, 9, offsetof(struct ln, roffcode));
+ for (i = 0; i < sizeof(lines)/sizeof(lines[0]); i++) {
+ slot = ohash_qlookup(&mchars, lines[i].roffcode);
+ assert(ohash_find(&mchars, slot) == NULL);
+ ohash_insert(&mchars, slot, lines + i);
+ }
+}
+
+int
+mchars_spec2cp(const char *p, size_t sz)
+{
+ const struct ln *ln;
+ const char *end;
+
+ end = p + sz;
+ ln = ohash_find(&mchars, ohash_qlookupi(&mchars, p, &end));
+ return ln != NULL ? ln->unicode : -1;
+}
+
+int
+mchars_num2char(const char *p, size_t sz)
+{
+ int i;
+
+ i = mandoc_strntoi(p, sz, 10);
+ return i >= 0 && i < 256 ? i : -1;
+}
+
+int
+mchars_num2uc(const char *p, size_t sz)
+{
+ int i;
+
+ i = mandoc_strntoi(p, sz, 16);
+ assert(i >= 0 && i <= 0x10FFFF);
+ return i;
+}
+
+const char *
+mchars_spec2str(const char *p, size_t sz, size_t *rsz)
+{
+ const struct ln *ln;
+ const char *end;
+
+ end = p + sz;
+ ln = ohash_find(&mchars, ohash_qlookupi(&mchars, p, &end));
+ if (ln == NULL)
+ return NULL;
+
+ *rsz = strlen(ln->ascii);
+ return ln->ascii;
+}
+
+const char *
+mchars_uc2str(int uc)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(lines)/sizeof(lines[0]); i++)
+ if (uc == lines[i].unicode)
+ return lines[i].ascii;
+ return "<?>";
+}
diff --git a/usr.bin/mandoc/dba.c b/usr.bin/mandoc/dba.c
new file mode 100644
index 0000000..36d4bb7
--- /dev/null
+++ b/usr.bin/mandoc/dba.c
@@ -0,0 +1,501 @@
+/* $OpenBSD: dba.c,v 1.7 2017/02/09 18:26:17 schwarze Exp $ */
+/*
+ * Copyright (c) 2016, 2017 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.
+ *
+ * Allocation-based version of the mandoc database, for read-write access.
+ * The interface is defined in "dba.h".
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <endian.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "mansearch.h"
+#include "dba_write.h"
+#include "dba_array.h"
+#include "dba.h"
+
+struct macro_entry {
+ struct dba_array *pages;
+ char value[];
+};
+
+static void *prepend(const char *, char);
+static void dba_pages_write(struct dba_array *);
+static int compare_names(const void *, const void *);
+static int compare_strings(const void *, const void *);
+
+static struct macro_entry
+ *get_macro_entry(struct ohash *, const char *, int32_t);
+static void dba_macros_write(struct dba_array *);
+static void dba_macro_write(struct ohash *);
+static int compare_entries(const void *, const void *);
+
+
+/*** top-level functions **********************************************/
+
+struct dba *
+dba_new(int32_t npages)
+{
+ struct dba *dba;
+ struct ohash *macro;
+ int32_t im;
+
+ dba = mandoc_malloc(sizeof(*dba));
+ dba->pages = dba_array_new(npages, DBA_GROW);
+ dba->macros = dba_array_new(MACRO_MAX, 0);
+ for (im = 0; im < MACRO_MAX; im++) {
+ macro = mandoc_malloc(sizeof(*macro));
+ mandoc_ohash_init(macro, 4,
+ offsetof(struct macro_entry, value));
+ dba_array_set(dba->macros, im, macro);
+ }
+ return dba;
+}
+
+void
+dba_free(struct dba *dba)
+{
+ struct dba_array *page;
+ struct ohash *macro;
+ struct macro_entry *entry;
+ unsigned int slot;
+
+ dba_array_FOREACH(dba->macros, macro) {
+ for (entry = ohash_first(macro, &slot); entry != NULL;
+ entry = ohash_next(macro, &slot)) {
+ dba_array_free(entry->pages);
+ free(entry);
+ }
+ ohash_delete(macro);
+ free(macro);
+ }
+ dba_array_free(dba->macros);
+
+ dba_array_undel(dba->pages);
+ dba_array_FOREACH(dba->pages, page) {
+ dba_array_free(dba_array_get(page, DBP_NAME));
+ dba_array_free(dba_array_get(page, DBP_SECT));
+ dba_array_free(dba_array_get(page, DBP_ARCH));
+ free(dba_array_get(page, DBP_DESC));
+ dba_array_free(dba_array_get(page, DBP_FILE));
+ dba_array_free(page);
+ }
+ dba_array_free(dba->pages);
+
+ free(dba);
+}
+
+/*
+ * Write the complete mandoc database to disk; the format is:
+ * - One integer each for magic and version.
+ * - One pointer each to the macros table and to the final magic.
+ * - The pages table.
+ * - The macros table.
+ * - And at the very end, the magic integer again.
+ */
+int
+dba_write(const char *fname, struct dba *dba)
+{
+ int save_errno;
+ int32_t pos_end, pos_macros, pos_macros_ptr;
+
+ if (dba_open(fname) == -1)
+ return -1;
+ dba_int_write(MANDOCDB_MAGIC);
+ dba_int_write(MANDOCDB_VERSION);
+ pos_macros_ptr = dba_skip(1, 2);
+ dba_pages_write(dba->pages);
+ pos_macros = dba_tell();
+ dba_macros_write(dba->macros);
+ pos_end = dba_tell();
+ dba_int_write(MANDOCDB_MAGIC);
+ dba_seek(pos_macros_ptr);
+ dba_int_write(pos_macros);
+ dba_int_write(pos_end);
+ if (dba_close() == -1) {
+ save_errno = errno;
+ unlink(fname);
+ errno = save_errno;
+ return -1;
+ }
+ return 0;
+}
+
+
+/*** functions for handling pages *************************************/
+
+/*
+ * Create a new page and append it to the pages table.
+ */
+struct dba_array *
+dba_page_new(struct dba_array *pages, const char *arch,
+ const char *desc, const char *file, enum form form)
+{
+ struct dba_array *page, *entry;
+
+ page = dba_array_new(DBP_MAX, 0);
+ entry = dba_array_new(1, DBA_STR | DBA_GROW);
+ dba_array_add(page, entry);
+ entry = dba_array_new(1, DBA_STR | DBA_GROW);
+ dba_array_add(page, entry);
+ if (arch != NULL && *arch != '\0') {
+ entry = dba_array_new(1, DBA_STR | DBA_GROW);
+ dba_array_add(entry, (void *)arch);
+ } else
+ entry = NULL;
+ dba_array_add(page, entry);
+ dba_array_add(page, mandoc_strdup(desc));
+ entry = dba_array_new(1, DBA_STR | DBA_GROW);
+ dba_array_add(entry, prepend(file, form));
+ dba_array_add(page, entry);
+ dba_array_add(pages, page);
+ return page;
+}
+
+/*
+ * Add a section, architecture, or file name to an existing page.
+ * Passing the NULL pointer for the architecture makes the page MI.
+ * In that case, any earlier or later architectures are ignored.
+ */
+void
+dba_page_add(struct dba_array *page, int32_t ie, const char *str)
+{
+ struct dba_array *entries;
+ char *entry;
+
+ entries = dba_array_get(page, ie);
+ if (ie == DBP_ARCH) {
+ if (entries == NULL)
+ return;
+ if (str == NULL || *str == '\0') {
+ dba_array_free(entries);
+ dba_array_set(page, DBP_ARCH, NULL);
+ return;
+ }
+ }
+ if (*str == '\0')
+ return;
+ dba_array_FOREACH(entries, entry) {
+ if (ie == DBP_FILE && *entry < ' ')
+ entry++;
+ if (strcmp(entry, str) == 0)
+ return;
+ }
+ dba_array_add(entries, (void *)str);
+}
+
+/*
+ * Add an additional name to an existing page.
+ */
+void
+dba_page_alias(struct dba_array *page, const char *name, uint64_t mask)
+{
+ struct dba_array *entries;
+ char *entry;
+ char maskbyte;
+
+ if (*name == '\0')
+ return;
+ maskbyte = mask & NAME_MASK;
+ entries = dba_array_get(page, DBP_NAME);
+ dba_array_FOREACH(entries, entry) {
+ if (strcmp(entry + 1, name) == 0) {
+ *entry |= maskbyte;
+ return;
+ }
+ }
+ dba_array_add(entries, prepend(name, maskbyte));
+}
+
+/*
+ * Return a pointer to a temporary copy of instr with inbyte prepended.
+ */
+static void *
+prepend(const char *instr, char inbyte)
+{
+ static char *outstr = NULL;
+ static size_t outlen = 0;
+ size_t newlen;
+
+ newlen = strlen(instr) + 1;
+ if (newlen > outlen) {
+ outstr = mandoc_realloc(outstr, newlen + 1);
+ outlen = newlen;
+ }
+ *outstr = inbyte;
+ memcpy(outstr + 1, instr, newlen);
+ return outstr;
+}
+
+/*
+ * Write the pages table to disk; the format is:
+ * - One integer containing the number of pages.
+ * - For each page, five pointers to the names, sections,
+ * architectures, description, and file names of the page.
+ * MI pages write 0 instead of the architecture pointer.
+ * - One list each for names, sections, architectures, descriptions and
+ * file names. The description for each page ends with a NUL byte.
+ * For all the other lists, each string ends with a NUL byte,
+ * and the last string for a page ends with two NUL bytes.
+ * - To assure alignment of following integers,
+ * the end is padded with NUL bytes up to a multiple of four bytes.
+ */
+static void
+dba_pages_write(struct dba_array *pages)
+{
+ struct dba_array *page, *entry;
+ int32_t pos_pages, pos_end;
+
+ pos_pages = dba_array_writelen(pages, 5);
+ dba_array_FOREACH(pages, page) {
+ dba_array_setpos(page, DBP_NAME, dba_tell());
+ entry = dba_array_get(page, DBP_NAME);
+ dba_array_sort(entry, compare_names);
+ dba_array_writelst(entry);
+ }
+ dba_array_FOREACH(pages, page) {
+ dba_array_setpos(page, DBP_SECT, dba_tell());
+ entry = dba_array_get(page, DBP_SECT);
+ dba_array_sort(entry, compare_strings);
+ dba_array_writelst(entry);
+ }
+ dba_array_FOREACH(pages, page) {
+ if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) {
+ dba_array_setpos(page, DBP_ARCH, dba_tell());
+ dba_array_sort(entry, compare_strings);
+ dba_array_writelst(entry);
+ } else
+ dba_array_setpos(page, DBP_ARCH, 0);
+ }
+ dba_array_FOREACH(pages, page) {
+ dba_array_setpos(page, DBP_DESC, dba_tell());
+ dba_str_write(dba_array_get(page, DBP_DESC));
+ }
+ dba_array_FOREACH(pages, page) {
+ dba_array_setpos(page, DBP_FILE, dba_tell());
+ dba_array_writelst(dba_array_get(page, DBP_FILE));
+ }
+ pos_end = dba_align();
+ dba_seek(pos_pages);
+ dba_array_FOREACH(pages, page)
+ dba_array_writepos(page);
+ dba_seek(pos_end);
+}
+
+static int
+compare_names(const void *vp1, const void *vp2)
+{
+ const char *cp1, *cp2;
+ int diff;
+
+ cp1 = *(const char * const *)vp1;
+ cp2 = *(const char * const *)vp2;
+ return (diff = *cp2 - *cp1) ? diff :
+ strcasecmp(cp1 + 1, cp2 + 1);
+}
+
+static int
+compare_strings(const void *vp1, const void *vp2)
+{
+ const char *cp1, *cp2;
+
+ cp1 = *(const char * const *)vp1;
+ cp2 = *(const char * const *)vp2;
+ return strcmp(cp1, cp2);
+}
+
+/*** functions for handling macros ************************************/
+
+/*
+ * In the hash table for a single macro, look up an entry by
+ * the macro value or add an empty one if it doesn't exist yet.
+ */
+static struct macro_entry *
+get_macro_entry(struct ohash *macro, const char *value, int32_t np)
+{
+ struct macro_entry *entry;
+ size_t len;
+ unsigned int slot;
+
+ slot = ohash_qlookup(macro, value);
+ if ((entry = ohash_find(macro, slot)) == NULL) {
+ len = strlen(value) + 1;
+ entry = mandoc_malloc(sizeof(*entry) + len);
+ memcpy(&entry->value, value, len);
+ entry->pages = dba_array_new(np, DBA_GROW);
+ ohash_insert(macro, slot, entry);
+ }
+ return entry;
+}
+
+/*
+ * In addition to get_macro_entry(), add multiple page references,
+ * converting them from the on-disk format (byte offsets in the file)
+ * to page pointers in memory.
+ */
+void
+dba_macro_new(struct dba *dba, int32_t im, const char *value,
+ const int32_t *pp)
+{
+ struct macro_entry *entry;
+ const int32_t *ip;
+ int32_t np;
+
+ np = 0;
+ for (ip = pp; *ip; ip++)
+ np++;
+
+ entry = get_macro_entry(dba_array_get(dba->macros, im), value, np);
+ for (ip = pp; *ip; ip++)
+ dba_array_add(entry->pages, dba_array_get(dba->pages,
+ be32toh(*ip) / 5 / sizeof(*ip) - 1));
+}
+
+/*
+ * In addition to get_macro_entry(), add one page reference,
+ * directly taking the in-memory page pointer as an argument.
+ */
+void
+dba_macro_add(struct dba_array *macros, int32_t im, const char *value,
+ struct dba_array *page)
+{
+ struct macro_entry *entry;
+
+ if (*value == '\0')
+ return;
+ entry = get_macro_entry(dba_array_get(macros, im), value, 1);
+ dba_array_add(entry->pages, page);
+}
+
+/*
+ * Write the macros table to disk; the format is:
+ * - The number of macro tables (actually, MACRO_MAX).
+ * - That number of pointers to the individual macro tables.
+ * - The individual macro tables.
+ */
+static void
+dba_macros_write(struct dba_array *macros)
+{
+ struct ohash *macro;
+ int32_t im, pos_macros, pos_end;
+
+ pos_macros = dba_array_writelen(macros, 1);
+ im = 0;
+ dba_array_FOREACH(macros, macro) {
+ dba_array_setpos(macros, im++, dba_tell());
+ dba_macro_write(macro);
+ }
+ pos_end = dba_tell();
+ dba_seek(pos_macros);
+ dba_array_writepos(macros);
+ dba_seek(pos_end);
+}
+
+/*
+ * Write one individual macro table to disk; the format is:
+ * - The number of entries in the table.
+ * - For each entry, two pointers, the first one to the value
+ * and the second one to the list of pages.
+ * - A list of values, each ending in a NUL byte.
+ * - To assure alignment of following integers,
+ * padding with NUL bytes up to a multiple of four bytes.
+ * - A list of pointers to pages, each list ending in a 0 integer.
+ */
+static void
+dba_macro_write(struct ohash *macro)
+{
+ struct macro_entry **entries, *entry;
+ struct dba_array *page;
+ int32_t *kpos, *dpos;
+ unsigned int ie, ne, slot;
+ int use;
+ int32_t addr, pos_macro, pos_end;
+
+ /* Temporary storage for filtering and sorting. */
+
+ ne = ohash_entries(macro);
+ entries = mandoc_reallocarray(NULL, ne, sizeof(*entries));
+ kpos = mandoc_reallocarray(NULL, ne, sizeof(*kpos));
+ dpos = mandoc_reallocarray(NULL, ne, sizeof(*dpos));
+
+ /* Build a list of non-empty entries and sort it. */
+
+ ne = 0;
+ for (entry = ohash_first(macro, &slot); entry != NULL;
+ entry = ohash_next(macro, &slot)) {
+ use = 0;
+ dba_array_FOREACH(entry->pages, page)
+ if (dba_array_getpos(page))
+ use = 1;
+ if (use)
+ entries[ne++] = entry;
+ }
+ qsort(entries, ne, sizeof(*entries), compare_entries);
+
+ /* Number of entries, and space for the pointer pairs. */
+
+ dba_int_write(ne);
+ pos_macro = dba_skip(2, ne);
+
+ /* String table. */
+
+ for (ie = 0; ie < ne; ie++) {
+ kpos[ie] = dba_tell();
+ dba_str_write(entries[ie]->value);
+ }
+ dba_align();
+
+ /* Pages table. */
+
+ for (ie = 0; ie < ne; ie++) {
+ dpos[ie] = dba_tell();
+ dba_array_FOREACH(entries[ie]->pages, page)
+ if ((addr = dba_array_getpos(page)))
+ dba_int_write(addr);
+ dba_int_write(0);
+ }
+ pos_end = dba_tell();
+
+ /* Fill in the pointer pairs. */
+
+ dba_seek(pos_macro);
+ for (ie = 0; ie < ne; ie++) {
+ dba_int_write(kpos[ie]);
+ dba_int_write(dpos[ie]);
+ }
+ dba_seek(pos_end);
+
+ free(entries);
+ free(kpos);
+ free(dpos);
+}
+
+static int
+compare_entries(const void *vp1, const void *vp2)
+{
+ const struct macro_entry *ep1, *ep2;
+
+ ep1 = *(const struct macro_entry * const *)vp1;
+ ep2 = *(const struct macro_entry * const *)vp2;
+ return strcmp(ep1->value, ep2->value);
+}
diff --git a/usr.bin/mandoc/dba.h b/usr.bin/mandoc/dba.h
new file mode 100644
index 0000000..7787958
--- /dev/null
+++ b/usr.bin/mandoc/dba.h
@@ -0,0 +1,50 @@
+/* $OpenBSD: dba.h,v 1.2 2016/08/17 20:46:06 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Public interface of the allocation-based version
+ * of the mandoc database, for read-write access.
+ * To be used by dba.c, dba_read.c, and makewhatis(8).
+ */
+
+#define DBP_NAME 0
+#define DBP_SECT 1
+#define DBP_ARCH 2
+#define DBP_DESC 3
+#define DBP_FILE 4
+#define DBP_MAX 5
+
+struct dba_array;
+
+struct dba {
+ struct dba_array *pages;
+ struct dba_array *macros;
+};
+
+
+struct dba *dba_new(int32_t);
+void dba_free(struct dba *);
+struct dba *dba_read(const char *);
+int dba_write(const char *, struct dba *);
+
+struct dba_array *dba_page_new(struct dba_array *, const char *,
+ const char *, const char *, enum form);
+void dba_page_add(struct dba_array *, int32_t, const char *);
+void dba_page_alias(struct dba_array *, const char *, uint64_t);
+
+void dba_macro_new(struct dba *, int32_t,
+ const char *, const int32_t *);
+void dba_macro_add(struct dba_array *, int32_t,
+ const char *, struct dba_array *);
diff --git a/usr.bin/mandoc/dba_array.c b/usr.bin/mandoc/dba_array.c
new file mode 100644
index 0000000..dd08a32
--- /dev/null
+++ b/usr.bin/mandoc/dba_array.c
@@ -0,0 +1,188 @@
+/* $OpenBSD: dba_array.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Allocation-based arrays for the mandoc database, for read-write access.
+ * The interface is defined in "dba_array.h".
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "dba_write.h"
+#include "dba_array.h"
+
+struct dba_array {
+ void **ep; /* Array of entries. */
+ int32_t *em; /* Array of map positions. */
+ int flags;
+ int32_t ea; /* Entries allocated. */
+ int32_t eu; /* Entries used (including deleted). */
+ int32_t ed; /* Entries deleted. */
+ int32_t ec; /* Currently active entry. */
+ int32_t pos; /* Map position of this array. */
+};
+
+
+struct dba_array *
+dba_array_new(int32_t ea, int flags)
+{
+ struct dba_array *array;
+
+ assert(ea > 0);
+ array = mandoc_malloc(sizeof(*array));
+ array->ep = mandoc_reallocarray(NULL, ea, sizeof(*array->ep));
+ array->em = mandoc_reallocarray(NULL, ea, sizeof(*array->em));
+ array->ea = ea;
+ array->eu = 0;
+ array->ed = 0;
+ array->ec = 0;
+ array->flags = flags;
+ array->pos = 0;
+ return array;
+}
+
+void
+dba_array_free(struct dba_array *array)
+{
+ int32_t ie;
+
+ if (array == NULL)
+ return;
+ if (array->flags & DBA_STR)
+ for (ie = 0; ie < array->eu; ie++)
+ free(array->ep[ie]);
+ free(array->ep);
+ free(array->em);
+ free(array);
+}
+
+void
+dba_array_set(struct dba_array *array, int32_t ie, void *entry)
+{
+ assert(ie >= 0);
+ assert(ie < array->ea);
+ assert(ie <= array->eu);
+ if (ie == array->eu)
+ array->eu++;
+ if (array->flags & DBA_STR)
+ entry = mandoc_strdup(entry);
+ array->ep[ie] = entry;
+ array->em[ie] = 0;
+}
+
+void
+dba_array_add(struct dba_array *array, void *entry)
+{
+ if (array->eu == array->ea) {
+ assert(array->flags & DBA_GROW);
+ array->ep = mandoc_reallocarray(array->ep,
+ 2, sizeof(*array->ep) * array->ea);
+ array->em = mandoc_reallocarray(array->em,
+ 2, sizeof(*array->em) * array->ea);
+ array->ea *= 2;
+ }
+ dba_array_set(array, array->eu, entry);
+}
+
+void *
+dba_array_get(struct dba_array *array, int32_t ie)
+{
+ if (ie < 0 || ie >= array->eu || array->em[ie] == -1)
+ return NULL;
+ return array->ep[ie];
+}
+
+void
+dba_array_start(struct dba_array *array)
+{
+ array->ec = array->eu;
+}
+
+void *
+dba_array_next(struct dba_array *array)
+{
+ if (array->ec < array->eu)
+ array->ec++;
+ else
+ array->ec = 0;
+ while (array->ec < array->eu && array->em[array->ec] == -1)
+ array->ec++;
+ return array->ec < array->eu ? array->ep[array->ec] : NULL;
+}
+
+void
+dba_array_del(struct dba_array *array)
+{
+ if (array->ec < array->eu && array->em[array->ec] != -1) {
+ array->em[array->ec] = -1;
+ array->ed++;
+ }
+}
+
+void
+dba_array_undel(struct dba_array *array)
+{
+ memset(array->em, 0, sizeof(*array->em) * array->eu);
+}
+
+void
+dba_array_setpos(struct dba_array *array, int32_t ie, int32_t pos)
+{
+ array->em[ie] = pos;
+}
+
+int32_t
+dba_array_getpos(struct dba_array *array)
+{
+ return array->pos;
+}
+
+void
+dba_array_sort(struct dba_array *array, dba_compare_func func)
+{
+ assert(array->ed == 0);
+ qsort(array->ep, array->eu, sizeof(*array->ep), func);
+}
+
+int32_t
+dba_array_writelen(struct dba_array *array, int32_t nmemb)
+{
+ dba_int_write(array->eu - array->ed);
+ return dba_skip(nmemb, array->eu - array->ed);
+}
+
+void
+dba_array_writepos(struct dba_array *array)
+{
+ int32_t ie;
+
+ array->pos = dba_tell();
+ for (ie = 0; ie < array->eu; ie++)
+ if (array->em[ie] != -1)
+ dba_int_write(array->em[ie]);
+}
+
+void
+dba_array_writelst(struct dba_array *array)
+{
+ const char *str;
+
+ dba_array_FOREACH(array, str)
+ dba_str_write(str);
+ dba_char_write('\0');
+}
diff --git a/usr.bin/mandoc/dba_array.h b/usr.bin/mandoc/dba_array.h
new file mode 100644
index 0000000..167f68f
--- /dev/null
+++ b/usr.bin/mandoc/dba_array.h
@@ -0,0 +1,47 @@
+/* $OpenBSD: dba_array.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Public interface for allocation-based arrays
+ * for the mandoc database, for read-write access.
+ * To be used by dba*.c and by makewhatis(8).
+ */
+
+struct dba_array;
+
+#define DBA_STR 0x01 /* Map contains strings, not pointers. */
+#define DBA_GROW 0x02 /* Allow the array to grow. */
+
+#define dba_array_FOREACH(a, e) \
+ dba_array_start(a); \
+ while (((e) = dba_array_next(a)) != NULL)
+
+typedef int dba_compare_func(const void *, const void *);
+
+struct dba_array *dba_array_new(int32_t, int);
+void dba_array_free(struct dba_array *);
+void dba_array_set(struct dba_array *, int32_t, void *);
+void dba_array_add(struct dba_array *, void *);
+void *dba_array_get(struct dba_array *, int32_t);
+void dba_array_start(struct dba_array *);
+void *dba_array_next(struct dba_array *);
+void dba_array_del(struct dba_array *);
+void dba_array_undel(struct dba_array *);
+void dba_array_setpos(struct dba_array *, int32_t, int32_t);
+int32_t dba_array_getpos(struct dba_array *);
+void dba_array_sort(struct dba_array *, dba_compare_func);
+int32_t dba_array_writelen(struct dba_array *, int32_t);
+void dba_array_writepos(struct dba_array *);
+void dba_array_writelst(struct dba_array *);
diff --git a/usr.bin/mandoc/dba_read.c b/usr.bin/mandoc/dba_read.c
new file mode 100644
index 0000000..3ab4a39
--- /dev/null
+++ b/usr.bin/mandoc/dba_read.c
@@ -0,0 +1,72 @@
+/* $OpenBSD: dba_read.c,v 1.4 2016/08/17 20:46:06 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Function to read the mandoc database from disk into RAM,
+ * such that data can be added or removed.
+ * The interface is defined in "dba.h".
+ * This file is seperate from dba.c because this also uses "dbm.h".
+ */
+#include <regex.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mansearch.h"
+#include "dba_array.h"
+#include "dba.h"
+#include "dbm.h"
+
+
+struct dba *
+dba_read(const char *fname)
+{
+ struct dba *dba;
+ struct dba_array *page;
+ struct dbm_page *pdata;
+ struct dbm_macro *mdata;
+ const char *cp;
+ int32_t im, ip, iv, npages;
+
+ if (dbm_open(fname) == -1)
+ return NULL;
+ npages = dbm_page_count();
+ dba = dba_new(npages < 128 ? 128 : npages);
+ for (ip = 0; ip < npages; ip++) {
+ pdata = dbm_page_get(ip);
+ page = dba_page_new(dba->pages, pdata->arch,
+ pdata->desc, pdata->file + 1, *pdata->file);
+ for (cp = pdata->name; *cp != '\0'; cp = strchr(cp, '\0') + 1)
+ dba_page_add(page, DBP_NAME, cp);
+ for (cp = pdata->sect; *cp != '\0'; cp = strchr(cp, '\0') + 1)
+ dba_page_add(page, DBP_SECT, cp);
+ if ((cp = pdata->arch) != NULL)
+ while (*(cp = strchr(cp, '\0') + 1) != '\0')
+ dba_page_add(page, DBP_ARCH, cp);
+ cp = pdata->file;
+ while (*(cp = strchr(cp, '\0') + 1) != '\0')
+ dba_page_add(page, DBP_FILE, cp);
+ }
+ for (im = 0; im < MACRO_MAX; im++) {
+ for (iv = 0; iv < dbm_macro_count(im); iv++) {
+ mdata = dbm_macro_get(im, iv);
+ dba_macro_new(dba, im, mdata->value, mdata->pp);
+ }
+ }
+ dbm_close();
+ return dba;
+}
diff --git a/usr.bin/mandoc/dba_write.c b/usr.bin/mandoc/dba_write.c
new file mode 100644
index 0000000..ef15dbe
--- /dev/null
+++ b/usr.bin/mandoc/dba_write.c
@@ -0,0 +1,117 @@
+/* $OpenBSD: dba_write.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Low-level functions for serializing allocation-based data to disk.
+ * The interface is defined in "dba_write.h".
+ */
+#include <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "dba_write.h"
+
+static FILE *ofp;
+
+
+int
+dba_open(const char *fname)
+{
+ ofp = fopen(fname, "w");
+ return ofp == NULL ? -1 : 0;
+}
+
+int
+dba_close(void)
+{
+ return fclose(ofp) == EOF ? -1 : 0;
+}
+
+int32_t
+dba_tell(void)
+{
+ long pos;
+
+ if ((pos = ftell(ofp)) == -1)
+ err(1, "ftell");
+ if (pos >= INT32_MAX) {
+ errno = EOVERFLOW;
+ err(1, "ftell = %ld", pos);
+ }
+ return pos;
+}
+
+void
+dba_seek(int32_t pos)
+{
+ if (fseek(ofp, pos, SEEK_SET) == -1)
+ err(1, "fseek(%d)", pos);
+}
+
+int32_t
+dba_align(void)
+{
+ int32_t pos;
+
+ pos = dba_tell();
+ while (pos & 3) {
+ dba_char_write('\0');
+ pos++;
+ }
+ return pos;
+}
+
+int32_t
+dba_skip(int32_t nmemb, int32_t sz)
+{
+ const int32_t out[5] = {0, 0, 0, 0, 0};
+ int32_t i, pos;
+
+ assert(sz >= 0);
+ assert(nmemb > 0);
+ assert(nmemb <= 5);
+ pos = dba_tell();
+ for (i = 0; i < sz; i++)
+ if (nmemb - fwrite(&out, sizeof(out[0]), nmemb, ofp))
+ err(1, "fwrite");
+ return pos;
+}
+
+void
+dba_char_write(int c)
+{
+ if (putc(c, ofp) == EOF)
+ err(1, "fputc");
+}
+
+void
+dba_str_write(const char *str)
+{
+ if (fputs(str, ofp) == EOF)
+ err(1, "fputs");
+ dba_char_write('\0');
+}
+
+void
+dba_int_write(int32_t i)
+{
+ i = htobe32(i);
+ if (fwrite(&i, sizeof(i), 1, ofp) != 1)
+ err(1, "fwrite");
+}
diff --git a/usr.bin/mandoc/dba_write.h b/usr.bin/mandoc/dba_write.h
new file mode 100644
index 0000000..bbbaa5e
--- /dev/null
+++ b/usr.bin/mandoc/dba_write.h
@@ -0,0 +1,30 @@
+/* $OpenBSD: dba_write.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Internal interface to low-level functions
+ * for serializing allocation-based data to disk.
+ * For use by dba_array.c and dba.c only.
+ */
+
+int dba_open(const char *);
+int dba_close(void);
+int32_t dba_tell(void);
+void dba_seek(int32_t);
+int32_t dba_align(void);
+int32_t dba_skip(int32_t, int32_t);
+void dba_char_write(int);
+void dba_str_write(const char *);
+void dba_int_write(int32_t);
diff --git a/usr.bin/mandoc/dbm.c b/usr.bin/mandoc/dbm.c
new file mode 100644
index 0000000..261321e
--- /dev/null
+++ b/usr.bin/mandoc/dbm.c
@@ -0,0 +1,474 @@
+/* $OpenBSD: dbm.c,v 1.5 2019/07/01 22:43:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Map-based version of the mandoc database, for read-only access.
+ * The interface is defined in "dbm.h".
+ */
+#include <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mansearch.h"
+#include "dbm_map.h"
+#include "dbm.h"
+
+#ifndef EFTYPE
+#define EFTYPE 79
+#endif
+
+struct macro {
+ int32_t value;
+ int32_t pages;
+};
+
+struct page {
+ int32_t name;
+ int32_t sect;
+ int32_t arch;
+ int32_t desc;
+ int32_t file;
+};
+
+enum iter {
+ ITER_NONE = 0,
+ ITER_NAME,
+ ITER_SECT,
+ ITER_ARCH,
+ ITER_DESC,
+ ITER_MACRO
+};
+
+static struct macro *macros[MACRO_MAX];
+static int32_t nvals[MACRO_MAX];
+static struct page *pages;
+static int32_t npages;
+static enum iter iteration;
+
+static struct dbm_res page_bytitle(enum iter, const struct dbm_match *);
+static struct dbm_res page_byarch(const struct dbm_match *);
+static struct dbm_res page_bymacro(int32_t, const struct dbm_match *);
+static char *macro_bypage(int32_t, int32_t);
+
+
+/*** top level functions **********************************************/
+
+/*
+ * Open a disk-based mandoc database for read-only access.
+ * Map the pages and macros[] arrays.
+ * Return 0 on success. Return -1 and set errno on failure.
+ */
+int
+dbm_open(const char *fname)
+{
+ const int32_t *mp, *ep;
+ int32_t im;
+
+ if (dbm_map(fname) == -1)
+ return -1;
+
+ if ((npages = be32toh(*dbm_getint(4))) < 0) {
+ warnx("dbm_open(%s): Invalid number of pages: %d",
+ fname, npages);
+ goto fail;
+ }
+ pages = (struct page *)dbm_getint(5);
+
+ if ((mp = dbm_get(*dbm_getint(2))) == NULL) {
+ warnx("dbm_open(%s): Invalid offset of macros array", fname);
+ goto fail;
+ }
+ if (be32toh(*mp) != MACRO_MAX) {
+ warnx("dbm_open(%s): Invalid number of macros: %d",
+ fname, be32toh(*mp));
+ goto fail;
+ }
+ for (im = 0; im < MACRO_MAX; im++) {
+ if ((ep = dbm_get(*++mp)) == NULL) {
+ warnx("dbm_open(%s): Invalid offset of macro %d",
+ fname, im);
+ goto fail;
+ }
+ nvals[im] = be32toh(*ep);
+ macros[im] = (struct macro *)++ep;
+ }
+ return 0;
+
+fail:
+ dbm_unmap();
+ errno = EFTYPE;
+ return -1;
+}
+
+void
+dbm_close(void)
+{
+ dbm_unmap();
+}
+
+
+/*** functions for handling pages *************************************/
+
+int32_t
+dbm_page_count(void)
+{
+ return npages;
+}
+
+/*
+ * Give the caller pointers to the data for one manual page.
+ */
+struct dbm_page *
+dbm_page_get(int32_t ip)
+{
+ static struct dbm_page res;
+
+ assert(ip >= 0);
+ assert(ip < npages);
+ res.name = dbm_get(pages[ip].name);
+ if (res.name == NULL)
+ res.name = "(NULL)\0";
+ res.sect = dbm_get(pages[ip].sect);
+ if (res.sect == NULL)
+ res.sect = "(NULL)\0";
+ res.arch = pages[ip].arch ? dbm_get(pages[ip].arch) : NULL;
+ res.desc = dbm_get(pages[ip].desc);
+ if (res.desc == NULL)
+ res.desc = "(NULL)";
+ res.file = dbm_get(pages[ip].file);
+ if (res.file == NULL)
+ res.file = " (NULL)\0";
+ res.addr = dbm_addr(pages + ip);
+ return &res;
+}
+
+/*
+ * Functions to start filtered iterations over manual pages.
+ */
+void
+dbm_page_byname(const struct dbm_match *match)
+{
+ assert(match != NULL);
+ page_bytitle(ITER_NAME, match);
+}
+
+void
+dbm_page_bysect(const struct dbm_match *match)
+{
+ assert(match != NULL);
+ page_bytitle(ITER_SECT, match);
+}
+
+void
+dbm_page_byarch(const struct dbm_match *match)
+{
+ assert(match != NULL);
+ page_byarch(match);
+}
+
+void
+dbm_page_bydesc(const struct dbm_match *match)
+{
+ assert(match != NULL);
+ page_bytitle(ITER_DESC, match);
+}
+
+void
+dbm_page_bymacro(int32_t im, const struct dbm_match *match)
+{
+ assert(im >= 0);
+ assert(im < MACRO_MAX);
+ assert(match != NULL);
+ page_bymacro(im, match);
+}
+
+/*
+ * Return the number of the next manual page in the current iteration.
+ */
+struct dbm_res
+dbm_page_next(void)
+{
+ struct dbm_res res = {-1, 0};
+
+ switch(iteration) {
+ case ITER_NONE:
+ return res;
+ case ITER_ARCH:
+ return page_byarch(NULL);
+ case ITER_MACRO:
+ return page_bymacro(0, NULL);
+ default:
+ return page_bytitle(iteration, NULL);
+ }
+}
+
+/*
+ * Functions implementing the iteration over manual pages.
+ */
+static struct dbm_res
+page_bytitle(enum iter arg_iter, const struct dbm_match *arg_match)
+{
+ static const struct dbm_match *match;
+ static const char *cp;
+ static int32_t ip;
+ struct dbm_res res = {-1, 0};
+
+ assert(arg_iter == ITER_NAME || arg_iter == ITER_DESC ||
+ arg_iter == ITER_SECT);
+
+ /* Initialize for a new iteration. */
+
+ if (arg_match != NULL) {
+ iteration = arg_iter;
+ match = arg_match;
+ switch (iteration) {
+ case ITER_NAME:
+ cp = dbm_get(pages[0].name);
+ break;
+ case ITER_SECT:
+ cp = dbm_get(pages[0].sect);
+ break;
+ case ITER_DESC:
+ cp = dbm_get(pages[0].desc);
+ break;
+ default:
+ abort();
+ }
+ if (cp == NULL) {
+ iteration = ITER_NONE;
+ match = NULL;
+ cp = NULL;
+ ip = npages;
+ } else
+ ip = 0;
+ return res;
+ }
+
+ /* Search for a name. */
+
+ while (ip < npages) {
+ if (iteration == ITER_NAME)
+ cp++;
+ if (dbm_match(match, cp))
+ break;
+ cp = strchr(cp, '\0') + 1;
+ if (iteration == ITER_DESC)
+ ip++;
+ else if (*cp == '\0') {
+ cp++;
+ ip++;
+ }
+ }
+
+ /* Reached the end without a match. */
+
+ if (ip == npages) {
+ iteration = ITER_NONE;
+ match = NULL;
+ cp = NULL;
+ return res;
+ }
+
+ /* Found a match; save the quality for later retrieval. */
+
+ res.page = ip;
+ res.bits = iteration == ITER_NAME ? cp[-1] : 0;
+
+ /* Skip the remaining names of this page. */
+
+ if (++ip < npages) {
+ do {
+ cp++;
+ } while (cp[-1] != '\0' ||
+ (iteration != ITER_DESC && cp[-2] != '\0'));
+ }
+ return res;
+}
+
+static struct dbm_res
+page_byarch(const struct dbm_match *arg_match)
+{
+ static const struct dbm_match *match;
+ struct dbm_res res = {-1, 0};
+ static int32_t ip;
+ const char *cp;
+
+ /* Initialize for a new iteration. */
+
+ if (arg_match != NULL) {
+ iteration = ITER_ARCH;
+ match = arg_match;
+ ip = 0;
+ return res;
+ }
+
+ /* Search for an architecture. */
+
+ for ( ; ip < npages; ip++)
+ if (pages[ip].arch)
+ for (cp = dbm_get(pages[ip].arch);
+ *cp != '\0';
+ cp = strchr(cp, '\0') + 1)
+ if (dbm_match(match, cp)) {
+ res.page = ip++;
+ return res;
+ }
+
+ /* Reached the end without a match. */
+
+ iteration = ITER_NONE;
+ match = NULL;
+ return res;
+}
+
+static struct dbm_res
+page_bymacro(int32_t arg_im, const struct dbm_match *arg_match)
+{
+ static const struct dbm_match *match;
+ static const int32_t *pp;
+ static const char *cp;
+ static int32_t im, iv;
+ struct dbm_res res = {-1, 0};
+
+ assert(im >= 0);
+ assert(im < MACRO_MAX);
+
+ /* Initialize for a new iteration. */
+
+ if (arg_match != NULL) {
+ iteration = ITER_MACRO;
+ match = arg_match;
+ im = arg_im;
+ cp = nvals[im] ? dbm_get(macros[im]->value) : NULL;
+ pp = NULL;
+ iv = -1;
+ return res;
+ }
+ if (iteration != ITER_MACRO)
+ return res;
+
+ /* Find the next matching macro value. */
+
+ while (pp == NULL || *pp == 0) {
+ if (++iv == nvals[im]) {
+ iteration = ITER_NONE;
+ return res;
+ }
+ if (iv)
+ cp = strchr(cp, '\0') + 1;
+ if (dbm_match(match, cp))
+ pp = dbm_get(macros[im][iv].pages);
+ }
+
+ /* Found a matching page. */
+
+ res.page = (struct page *)dbm_get(*pp++) - pages;
+ return res;
+}
+
+
+/*** functions for handling macros ************************************/
+
+int32_t
+dbm_macro_count(int32_t im)
+{
+ assert(im >= 0);
+ assert(im < MACRO_MAX);
+ return nvals[im];
+}
+
+struct dbm_macro *
+dbm_macro_get(int32_t im, int32_t iv)
+{
+ static struct dbm_macro macro;
+
+ assert(im >= 0);
+ assert(im < MACRO_MAX);
+ assert(iv >= 0);
+ assert(iv < nvals[im]);
+ macro.value = dbm_get(macros[im][iv].value);
+ macro.pp = dbm_get(macros[im][iv].pages);
+ return &macro;
+}
+
+/*
+ * Filtered iteration over macro entries.
+ */
+void
+dbm_macro_bypage(int32_t im, int32_t ip)
+{
+ assert(im >= 0);
+ assert(im < MACRO_MAX);
+ assert(ip != 0);
+ macro_bypage(im, ip);
+}
+
+char *
+dbm_macro_next(void)
+{
+ return macro_bypage(MACRO_MAX, 0);
+}
+
+static char *
+macro_bypage(int32_t arg_im, int32_t arg_ip)
+{
+ static const int32_t *pp;
+ static int32_t im, ip, iv;
+
+ /* Initialize for a new iteration. */
+
+ if (arg_im < MACRO_MAX && arg_ip != 0) {
+ im = arg_im;
+ ip = arg_ip;
+ pp = dbm_get(macros[im]->pages);
+ iv = 0;
+ return NULL;
+ }
+ if (im >= MACRO_MAX)
+ return NULL;
+
+ /* Search for the next value. */
+
+ while (iv < nvals[im]) {
+ if (*pp == ip)
+ break;
+ if (*pp == 0)
+ iv++;
+ pp++;
+ }
+
+ /* Reached the end without a match. */
+
+ if (iv == nvals[im]) {
+ im = MACRO_MAX;
+ ip = 0;
+ pp = NULL;
+ return NULL;
+ }
+
+ /* Found a match; skip the remaining pages of this entry. */
+
+ if (++iv < nvals[im])
+ while (*pp++ != 0)
+ continue;
+
+ return dbm_get(macros[im][iv - 1].value);
+}
diff --git a/usr.bin/mandoc/dbm.h b/usr.bin/mandoc/dbm.h
new file mode 100644
index 0000000..0f12ee1
--- /dev/null
+++ b/usr.bin/mandoc/dbm.h
@@ -0,0 +1,68 @@
+/* $OpenBSD: dbm.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Public interface for the map-based version
+ * of the mandoc database, for read-only access.
+ * To be used by dbm*.c, dba_read.c, and man(1) and apropos(1).
+ */
+
+enum dbm_mtype {
+ DBM_EXACT = 0,
+ DBM_SUB,
+ DBM_REGEX
+};
+
+struct dbm_match {
+ regex_t *re;
+ const char *str;
+ enum dbm_mtype type;
+};
+
+struct dbm_res {
+ int32_t page;
+ int32_t bits;
+};
+
+struct dbm_page {
+ const char *name;
+ const char *sect;
+ const char *arch;
+ const char *desc;
+ const char *file;
+ int32_t addr;
+};
+
+struct dbm_macro {
+ const char *value;
+ const int32_t *pp;
+};
+
+int dbm_open(const char *);
+void dbm_close(void);
+
+int32_t dbm_page_count(void);
+struct dbm_page *dbm_page_get(int32_t);
+void dbm_page_byname(const struct dbm_match *);
+void dbm_page_bysect(const struct dbm_match *);
+void dbm_page_byarch(const struct dbm_match *);
+void dbm_page_bydesc(const struct dbm_match *);
+void dbm_page_bymacro(int32_t, const struct dbm_match *);
+struct dbm_res dbm_page_next(void);
+
+int32_t dbm_macro_count(int32_t);
+struct dbm_macro *dbm_macro_get(int32_t, int32_t);
+void dbm_macro_bypage(int32_t, int32_t);
+char *dbm_macro_next(void);
diff --git a/usr.bin/mandoc/dbm_map.c b/usr.bin/mandoc/dbm_map.c
new file mode 100644
index 0000000..72b1220
--- /dev/null
+++ b/usr.bin/mandoc/dbm_map.c
@@ -0,0 +1,188 @@
+/* $OpenBSD: dbm_map.c,v 1.6 2017/02/09 18:26:17 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Low-level routines for the map-based version
+ * of the mandoc database, for read-only access.
+ * The interface is defined in "dbm_map.h".
+ */
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mansearch.h"
+#include "dbm_map.h"
+#include "dbm.h"
+
+#ifndef EFTYPE
+#define EFTYPE 79
+#endif
+
+static struct stat st;
+static char *dbm_base;
+static int ifd;
+static int32_t max_offset;
+
+/*
+ * Open a disk-based database for read-only access.
+ * Validate the file format as far as it is not mandoc-specific.
+ * Return 0 on success. Return -1 and set errno on failure.
+ */
+int
+dbm_map(const char *fname)
+{
+ int save_errno;
+ const int32_t *magic;
+
+ if ((ifd = open(fname, O_RDONLY)) == -1)
+ return -1;
+ if (fstat(ifd, &st) == -1)
+ goto fail;
+ if (st.st_size < 5) {
+ warnx("dbm_map(%s): File too short", fname);
+ errno = EFTYPE;
+ goto fail;
+ }
+ if (st.st_size > INT32_MAX) {
+ errno = EFBIG;
+ goto fail;
+ }
+ if ((dbm_base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED,
+ ifd, 0)) == MAP_FAILED)
+ goto fail;
+ magic = dbm_getint(0);
+ if (be32toh(*magic) != MANDOCDB_MAGIC) {
+ if (strncmp(dbm_base, "SQLite format 3", 15))
+ warnx("dbm_map(%s): "
+ "Bad initial magic %x (expected %x)",
+ fname, be32toh(*magic), MANDOCDB_MAGIC);
+ else
+ warnx("dbm_map(%s): "
+ "Obsolete format based on SQLite 3",
+ fname);
+ errno = EFTYPE;
+ goto fail;
+ }
+ magic = dbm_getint(1);
+ if (be32toh(*magic) != MANDOCDB_VERSION) {
+ warnx("dbm_map(%s): Bad version number %d (expected %d)",
+ fname, be32toh(*magic), MANDOCDB_VERSION);
+ errno = EFTYPE;
+ goto fail;
+ }
+ max_offset = be32toh(*dbm_getint(3)) + sizeof(int32_t);
+ if (st.st_size != max_offset) {
+ warnx("dbm_map(%s): Inconsistent file size %lld (expected %d)",
+ fname, (long long)st.st_size, max_offset);
+ errno = EFTYPE;
+ goto fail;
+ }
+ if ((magic = dbm_get(*dbm_getint(3))) == NULL) {
+ errno = EFTYPE;
+ goto fail;
+ }
+ if (be32toh(*magic) != MANDOCDB_MAGIC) {
+ warnx("dbm_map(%s): Bad final magic %x (expected %x)",
+ fname, be32toh(*magic), MANDOCDB_MAGIC);
+ errno = EFTYPE;
+ goto fail;
+ }
+ return 0;
+
+fail:
+ save_errno = errno;
+ close(ifd);
+ errno = save_errno;
+ return -1;
+}
+
+void
+dbm_unmap(void)
+{
+ if (munmap(dbm_base, st.st_size) == -1)
+ warn("dbm_unmap: munmap");
+ if (close(ifd) == -1)
+ warn("dbm_unmap: close");
+ dbm_base = (char *)-1;
+}
+
+/*
+ * Take a raw integer as it was read from the database.
+ * Interpret it as an offset into the database file
+ * and return a pointer to that place in the file.
+ */
+void *
+dbm_get(int32_t offset)
+{
+ offset = be32toh(offset);
+ if (offset < 0) {
+ warnx("dbm_get: Database corrupt: offset %d", offset);
+ return NULL;
+ }
+ if (offset >= max_offset) {
+ warnx("dbm_get: Database corrupt: offset %d > %d",
+ offset, max_offset);
+ return NULL;
+ }
+ return dbm_base + offset;
+}
+
+/*
+ * Assume the database starts with some integers.
+ * Assume they are numbered starting from 0, increasing.
+ * Get a pointer to one with the number "offset".
+ */
+int32_t *
+dbm_getint(int32_t offset)
+{
+ return (int32_t *)dbm_base + offset;
+}
+
+/*
+ * The reverse of dbm_get().
+ * Take pointer into the database file
+ * and convert it to the raw integer
+ * that would be used to refer to that place in the file.
+ */
+int32_t
+dbm_addr(const void *p)
+{
+ return htobe32((const char *)p - dbm_base);
+}
+
+int
+dbm_match(const struct dbm_match *match, const char *str)
+{
+ switch (match->type) {
+ case DBM_EXACT:
+ return strcmp(str, match->str) == 0;
+ case DBM_SUB:
+ return strcasestr(str, match->str) != NULL;
+ case DBM_REGEX:
+ return regexec(match->re, str, 0, NULL, 0) == 0;
+ default:
+ abort();
+ }
+}
diff --git a/usr.bin/mandoc/dbm_map.h b/usr.bin/mandoc/dbm_map.h
new file mode 100644
index 0000000..94ee82f
--- /dev/null
+++ b/usr.bin/mandoc/dbm_map.h
@@ -0,0 +1,29 @@
+/* $OpenBSD: dbm_map.h,v 1.2 2019/07/01 22:43:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 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.
+ *
+ * Private interface for low-level routines for the map-based version
+ * of the mandoc database, for read-only access.
+ * To be used by dbm*.c only.
+ */
+
+struct dbm_match;
+
+int dbm_map(const char *);
+void dbm_unmap(void);
+void *dbm_get(int32_t);
+int32_t *dbm_getint(int32_t);
+int32_t dbm_addr(const void *);
+int dbm_match(const struct dbm_match *, const char *);
diff --git a/usr.bin/mandoc/eqn.c b/usr.bin/mandoc/eqn.c
new file mode 100644
index 0000000..ad32067
--- /dev/null
+++ b/usr.bin/mandoc/eqn.c
@@ -0,0 +1,1130 @@
+/* $OpenBSD: eqn.c,v 1.47 2020/01/08 12:09:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014,2015,2017,2018,2020 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.
+ */
+#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 "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "eqn.h"
+#include "libmandoc.h"
+#include "eqn_parse.h"
+
+#define EQN_NEST_MAX 128 /* maximum nesting of defines */
+#define STRNEQ(p1, sz1, p2, sz2) \
+ ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1)))
+
+enum eqn_tok {
+ EQN_TOK_DYAD = 0,
+ EQN_TOK_VEC,
+ EQN_TOK_UNDER,
+ EQN_TOK_BAR,
+ EQN_TOK_TILDE,
+ EQN_TOK_HAT,
+ EQN_TOK_DOT,
+ EQN_TOK_DOTDOT,
+ EQN_TOK_FWD,
+ EQN_TOK_BACK,
+ EQN_TOK_DOWN,
+ EQN_TOK_UP,
+ EQN_TOK_FAT,
+ EQN_TOK_ROMAN,
+ EQN_TOK_ITALIC,
+ EQN_TOK_BOLD,
+ EQN_TOK_SIZE,
+ EQN_TOK_SUB,
+ EQN_TOK_SUP,
+ EQN_TOK_SQRT,
+ EQN_TOK_OVER,
+ EQN_TOK_FROM,
+ EQN_TOK_TO,
+ EQN_TOK_BRACE_OPEN,
+ EQN_TOK_BRACE_CLOSE,
+ EQN_TOK_GSIZE,
+ EQN_TOK_GFONT,
+ EQN_TOK_MARK,
+ EQN_TOK_LINEUP,
+ EQN_TOK_LEFT,
+ EQN_TOK_RIGHT,
+ EQN_TOK_PILE,
+ EQN_TOK_LPILE,
+ EQN_TOK_RPILE,
+ EQN_TOK_CPILE,
+ EQN_TOK_MATRIX,
+ EQN_TOK_CCOL,
+ EQN_TOK_LCOL,
+ EQN_TOK_RCOL,
+ EQN_TOK_DELIM,
+ EQN_TOK_DEFINE,
+ EQN_TOK_TDEFINE,
+ EQN_TOK_NDEFINE,
+ EQN_TOK_UNDEF,
+ EQN_TOK_ABOVE,
+ EQN_TOK__MAX,
+ EQN_TOK_FUNC,
+ EQN_TOK_QUOTED,
+ EQN_TOK_SYM,
+ EQN_TOK_EOF
+};
+
+static const char *eqn_toks[EQN_TOK__MAX] = {
+ "dyad", /* EQN_TOK_DYAD */
+ "vec", /* EQN_TOK_VEC */
+ "under", /* EQN_TOK_UNDER */
+ "bar", /* EQN_TOK_BAR */
+ "tilde", /* EQN_TOK_TILDE */
+ "hat", /* EQN_TOK_HAT */
+ "dot", /* EQN_TOK_DOT */
+ "dotdot", /* EQN_TOK_DOTDOT */
+ "fwd", /* EQN_TOK_FWD * */
+ "back", /* EQN_TOK_BACK */
+ "down", /* EQN_TOK_DOWN */
+ "up", /* EQN_TOK_UP */
+ "fat", /* EQN_TOK_FAT */
+ "roman", /* EQN_TOK_ROMAN */
+ "italic", /* EQN_TOK_ITALIC */
+ "bold", /* EQN_TOK_BOLD */
+ "size", /* EQN_TOK_SIZE */
+ "sub", /* EQN_TOK_SUB */
+ "sup", /* EQN_TOK_SUP */
+ "sqrt", /* EQN_TOK_SQRT */
+ "over", /* EQN_TOK_OVER */
+ "from", /* EQN_TOK_FROM */
+ "to", /* EQN_TOK_TO */
+ "{", /* EQN_TOK_BRACE_OPEN */
+ "}", /* EQN_TOK_BRACE_CLOSE */
+ "gsize", /* EQN_TOK_GSIZE */
+ "gfont", /* EQN_TOK_GFONT */
+ "mark", /* EQN_TOK_MARK */
+ "lineup", /* EQN_TOK_LINEUP */
+ "left", /* EQN_TOK_LEFT */
+ "right", /* EQN_TOK_RIGHT */
+ "pile", /* EQN_TOK_PILE */
+ "lpile", /* EQN_TOK_LPILE */
+ "rpile", /* EQN_TOK_RPILE */
+ "cpile", /* EQN_TOK_CPILE */
+ "matrix", /* EQN_TOK_MATRIX */
+ "ccol", /* EQN_TOK_CCOL */
+ "lcol", /* EQN_TOK_LCOL */
+ "rcol", /* EQN_TOK_RCOL */
+ "delim", /* EQN_TOK_DELIM */
+ "define", /* EQN_TOK_DEFINE */
+ "tdefine", /* EQN_TOK_TDEFINE */
+ "ndefine", /* EQN_TOK_NDEFINE */
+ "undef", /* EQN_TOK_UNDEF */
+ "above", /* EQN_TOK_ABOVE */
+};
+
+static const char *const eqn_func[] = {
+ "acos", "acsc", "and", "arc", "asec", "asin", "atan",
+ "cos", "cosh", "coth", "csc", "det", "exp", "for",
+ "if", "lim", "ln", "log", "max", "min",
+ "sec", "sin", "sinh", "tan", "tanh", "Im", "Re",
+};
+
+enum eqn_symt {
+ EQNSYM_alpha = 0,
+ 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_minus,
+ EQNSYM__MAX
+};
+
+struct eqnsym {
+ const char *str;
+ const char *sym;
+};
+
+static const struct eqnsym eqnsyms[EQNSYM__MAX] = {
+ { "alpha", "*a" }, /* EQNSYM_alpha */
+ { "beta", "*b" }, /* EQNSYM_beta */
+ { "chi", "*x" }, /* EQNSYM_chi */
+ { "delta", "*d" }, /* EQNSYM_delta */
+ { "epsilon", "*e" }, /* EQNSYM_epsilon */
+ { "eta", "*y" }, /* EQNSYM_eta */
+ { "gamma", "*g" }, /* EQNSYM_gamma */
+ { "iota", "*i" }, /* EQNSYM_iota */
+ { "kappa", "*k" }, /* EQNSYM_kappa */
+ { "lambda", "*l" }, /* EQNSYM_lambda */
+ { "mu", "*m" }, /* EQNSYM_mu */
+ { "nu", "*n" }, /* EQNSYM_nu */
+ { "omega", "*w" }, /* EQNSYM_omega */
+ { "omicron", "*o" }, /* EQNSYM_omicron */
+ { "phi", "*f" }, /* EQNSYM_phi */
+ { "pi", "*p" }, /* EQNSYM_pi */
+ { "psi", "*q" }, /* EQNSYM_psi */
+ { "rho", "*r" }, /* EQNSYM_rho */
+ { "sigma", "*s" }, /* EQNSYM_sigma */
+ { "tau", "*t" }, /* EQNSYM_tau */
+ { "theta", "*h" }, /* EQNSYM_theta */
+ { "upsilon", "*u" }, /* EQNSYM_upsilon */
+ { "xi", "*c" }, /* EQNSYM_xi */
+ { "zeta", "*z" }, /* EQNSYM_zeta */
+ { "DELTA", "*D" }, /* EQNSYM_DELTA */
+ { "GAMMA", "*G" }, /* EQNSYM_GAMMA */
+ { "LAMBDA", "*L" }, /* EQNSYM_LAMBDA */
+ { "OMEGA", "*W" }, /* EQNSYM_OMEGA */
+ { "PHI", "*F" }, /* EQNSYM_PHI */
+ { "PI", "*P" }, /* EQNSYM_PI */
+ { "PSI", "*Q" }, /* EQNSYM_PSI */
+ { "SIGMA", "*S" }, /* EQNSYM_SIGMA */
+ { "THETA", "*H" }, /* EQNSYM_THETA */
+ { "UPSILON", "*U" }, /* EQNSYM_UPSILON */
+ { "XI", "*C" }, /* EQNSYM_XI */
+ { "inter", "ca" }, /* EQNSYM_inter */
+ { "union", "cu" }, /* EQNSYM_union */
+ { "prod", "product" }, /* EQNSYM_prod */
+ { "int", "integral" }, /* EQNSYM_int */
+ { "sum", "sum" }, /* EQNSYM_sum */
+ { "grad", "gr" }, /* EQNSYM_grad */
+ { "del", "gr" }, /* EQNSYM_del */
+ { "times", "mu" }, /* EQNSYM_times */
+ { "cdot", "pc" }, /* EQNSYM_cdot */
+ { "nothing", "&" }, /* EQNSYM_nothing */
+ { "approx", "~~" }, /* EQNSYM_approx */
+ { "prime", "fm" }, /* EQNSYM_prime */
+ { "half", "12" }, /* EQNSYM_half */
+ { "partial", "pd" }, /* EQNSYM_partial */
+ { "inf", "if" }, /* EQNSYM_inf */
+ { ">>", ">>" }, /* EQNSYM_muchgreat */
+ { "<<", "<<" }, /* EQNSYM_muchless */
+ { "<-", "<-" }, /* EQNSYM_larrow */
+ { "->", "->" }, /* EQNSYM_rarrow */
+ { "+-", "+-" }, /* EQNSYM_pm */
+ { "!=", "!=" }, /* EQNSYM_nequal */
+ { "==", "==" }, /* EQNSYM_equiv */
+ { "<=", "<=" }, /* EQNSYM_lessequal */
+ { ">=", ">=" }, /* EQNSYM_moreequal */
+ { "-", "mi" }, /* EQNSYM_minus */
+};
+
+enum parse_mode {
+ MODE_QUOTED,
+ MODE_NOSUB,
+ MODE_SUB,
+ MODE_TOK
+};
+
+struct eqn_def {
+ char *key;
+ size_t keysz;
+ char *val;
+ size_t valsz;
+};
+
+static struct eqn_box *eqn_box_alloc(struct eqn_node *, struct eqn_box *);
+static struct eqn_box *eqn_box_makebinary(struct eqn_node *,
+ struct eqn_box *);
+static void eqn_def(struct eqn_node *);
+static struct eqn_def *eqn_def_find(struct eqn_node *);
+static void eqn_delim(struct eqn_node *);
+static enum eqn_tok eqn_next(struct eqn_node *, enum parse_mode);
+static void eqn_undef(struct eqn_node *);
+
+
+struct eqn_node *
+eqn_alloc(void)
+{
+ struct eqn_node *ep;
+
+ ep = mandoc_calloc(1, sizeof(*ep));
+ ep->gsize = EQN_DEFSIZE;
+ return ep;
+}
+
+void
+eqn_reset(struct eqn_node *ep)
+{
+ free(ep->data);
+ ep->data = ep->start = ep->end = NULL;
+ ep->sz = ep->toksz = 0;
+}
+
+void
+eqn_read(struct eqn_node *ep, const char *p)
+{
+ char *cp;
+
+ if (ep->data == NULL) {
+ ep->sz = strlen(p);
+ ep->data = mandoc_strdup(p);
+ } else {
+ ep->sz = mandoc_asprintf(&cp, "%s %s", ep->data, p);
+ free(ep->data);
+ ep->data = cp;
+ }
+ ep->sz += 1;
+}
+
+/*
+ * Find the key "key" of the give size within our eqn-defined values.
+ */
+static struct eqn_def *
+eqn_def_find(struct eqn_node *ep)
+{
+ int i;
+
+ for (i = 0; i < (int)ep->defsz; i++)
+ if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key,
+ ep->defs[i].keysz, ep->start, ep->toksz))
+ return &ep->defs[i];
+
+ return NULL;
+}
+
+/*
+ * Parse a token from the input text. The modes are:
+ * MODE_QUOTED: Use *ep->start as the delimiter; the token ends
+ * before its next occurence. Do not interpret the token in any
+ * way and return EQN_TOK_QUOTED. All other modes behave like
+ * MODE_QUOTED when *ep->start is '"'.
+ * MODE_NOSUB: If *ep->start is a curly brace, the token ends after it;
+ * otherwise, it ends before the next whitespace or brace.
+ * Do not interpret the token and return EQN_TOK__MAX.
+ * MODE_SUB: Like MODE_NOSUB, but try to interpret the token as an
+ * alias created with define. If it is an alias, replace it with
+ * its string value and reparse.
+ * MODE_TOK: Like MODE_SUB, but also check the token against the list
+ * of tokens, and if there is a match, return that token. Otherwise,
+ * if the token matches a symbol, return EQN_TOK_SYM; if it matches
+ * a function name, EQN_TOK_FUNC, or else EQN_TOK__MAX. Except for
+ * a token match, *ep->start is set to an allocated string that the
+ * caller is expected to free.
+ * All modes skip whitespace following the end of the token.
+ */
+static enum eqn_tok
+eqn_next(struct eqn_node *ep, enum parse_mode mode)
+{
+ static int last_len, lim;
+
+ struct eqn_def *def;
+ size_t start;
+ int diff, i, quoted;
+ enum eqn_tok tok;
+
+ /*
+ * Reset the recursion counter after advancing
+ * beyond the end of the previous substitution.
+ */
+ if (ep->end - ep->data >= last_len)
+ lim = 0;
+
+ ep->start = ep->end;
+ quoted = mode == MODE_QUOTED;
+ for (;;) {
+ switch (*ep->start) {
+ case '\0':
+ ep->toksz = 0;
+ return EQN_TOK_EOF;
+ case '"':
+ quoted = 1;
+ break;
+ case ' ':
+ case '\t':
+ case '~':
+ case '^':
+ if (quoted)
+ break;
+ ep->start++;
+ continue;
+ default:
+ break;
+ }
+ if (quoted) {
+ ep->end = strchr(ep->start + 1, *ep->start);
+ ep->start++; /* Skip opening quote. */
+ if (ep->end == NULL) {
+ mandoc_msg(MANDOCERR_ARG_QUOTE,
+ ep->node->line, ep->node->pos, NULL);
+ ep->end = strchr(ep->start, '\0');
+ }
+ } else {
+ ep->end = ep->start + 1;
+ if (*ep->start != '{' && *ep->start != '}')
+ ep->end += strcspn(ep->end, " ^~\"{}\t");
+ }
+ ep->toksz = ep->end - ep->start;
+ if (quoted && *ep->end != '\0')
+ ep->end++; /* Skip closing quote. */
+ while (*ep->end != '\0' && strchr(" \t^~", *ep->end) != NULL)
+ ep->end++;
+ if (quoted) /* Cannot return, may have to strndup. */
+ break;
+ if (mode == MODE_NOSUB)
+ return EQN_TOK__MAX;
+ if ((def = eqn_def_find(ep)) == NULL)
+ break;
+ if (++lim > EQN_NEST_MAX) {
+ mandoc_msg(MANDOCERR_ROFFLOOP,
+ ep->node->line, ep->node->pos, NULL);
+ return EQN_TOK_EOF;
+ }
+
+ /* Replace a defined name with its string value. */
+ if ((diff = def->valsz - ep->toksz) > 0) {
+ start = ep->start - ep->data;
+ ep->sz += diff;
+ ep->data = mandoc_realloc(ep->data, ep->sz + 1);
+ ep->start = ep->data + start;
+ }
+ if (diff)
+ memmove(ep->start + def->valsz, ep->start + ep->toksz,
+ strlen(ep->start + ep->toksz) + 1);
+ memcpy(ep->start, def->val, def->valsz);
+ last_len = ep->start - ep->data + def->valsz;
+ }
+ if (mode != MODE_TOK)
+ return quoted ? EQN_TOK_QUOTED : EQN_TOK__MAX;
+ if (quoted) {
+ ep->start = mandoc_strndup(ep->start, ep->toksz);
+ return EQN_TOK_QUOTED;
+ }
+ for (tok = 0; tok < EQN_TOK__MAX; tok++)
+ if (STRNEQ(ep->start, ep->toksz,
+ eqn_toks[tok], strlen(eqn_toks[tok])))
+ return tok;
+
+ for (i = 0; i < EQNSYM__MAX; i++) {
+ if (STRNEQ(ep->start, ep->toksz,
+ eqnsyms[i].str, strlen(eqnsyms[i].str))) {
+ mandoc_asprintf(&ep->start,
+ "\\[%s]", eqnsyms[i].sym);
+ return EQN_TOK_SYM;
+ }
+ }
+ ep->start = mandoc_strndup(ep->start, ep->toksz);
+ for (i = 0; i < (int)(sizeof(eqn_func)/sizeof(*eqn_func)); i++)
+ if (STRNEQ(ep->start, ep->toksz,
+ eqn_func[i], strlen(eqn_func[i])))
+ return EQN_TOK_FUNC;
+ return EQN_TOK__MAX;
+}
+
+void
+eqn_box_free(struct eqn_box *bp)
+{
+ if (bp == NULL)
+ return;
+
+ 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->top);
+ free(bp->bottom);
+ free(bp);
+}
+
+struct eqn_box *
+eqn_box_new(void)
+{
+ struct eqn_box *bp;
+
+ bp = mandoc_calloc(1, sizeof(*bp));
+ bp->expectargs = UINT_MAX;
+ return bp;
+}
+
+/*
+ * Allocate a box as the last child of the parent node.
+ */
+static struct eqn_box *
+eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent)
+{
+ struct eqn_box *bp;
+
+ bp = eqn_box_new();
+ bp->parent = parent;
+ bp->parent->args++;
+ bp->font = bp->parent->font;
+ bp->size = ep->gsize;
+
+ if (NULL != parent->first) {
+ parent->last->next = bp;
+ bp->prev = parent->last;
+ } else
+ parent->first = bp;
+
+ parent->last = bp;
+ return bp;
+}
+
+/*
+ * Reparent the current last node (of the current parent) under a new
+ * EQN_SUBEXPR as the first element.
+ * Then return the new parent.
+ * The new EQN_SUBEXPR will have a two-child limit.
+ */
+static struct eqn_box *
+eqn_box_makebinary(struct eqn_node *ep, struct eqn_box *parent)
+{
+ struct eqn_box *b, *newb;
+
+ assert(NULL != parent->last);
+ b = parent->last;
+ if (parent->last == parent->first)
+ parent->first = NULL;
+ parent->args--;
+ parent->last = b->prev;
+ b->prev = NULL;
+ newb = eqn_box_alloc(ep, parent);
+ newb->type = EQN_SUBEXPR;
+ newb->expectargs = 2;
+ newb->args = 1;
+ newb->first = newb->last = b;
+ newb->first->next = NULL;
+ b->parent = newb;
+ return newb;
+}
+
+/*
+ * Parse the "delim" control statement.
+ */
+static void
+eqn_delim(struct eqn_node *ep)
+{
+ if (ep->end[0] == '\0' || ep->end[1] == '\0') {
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "delim");
+ if (ep->end[0] != '\0')
+ ep->end++;
+ } else if (strncmp(ep->end, "off", 3) == 0) {
+ ep->delim = 0;
+ ep->end += 3;
+ } else if (strncmp(ep->end, "on", 2) == 0) {
+ if (ep->odelim && ep->cdelim)
+ ep->delim = 1;
+ ep->end += 2;
+ } else {
+ ep->odelim = *ep->end++;
+ ep->cdelim = *ep->end++;
+ ep->delim = 1;
+ }
+}
+
+/*
+ * Undefine a previously-defined string.
+ */
+static void
+eqn_undef(struct eqn_node *ep)
+{
+ struct eqn_def *def;
+
+ if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "undef");
+ return;
+ }
+ if ((def = eqn_def_find(ep)) == NULL)
+ return;
+ free(def->key);
+ free(def->val);
+ def->key = def->val = NULL;
+ def->keysz = def->valsz = 0;
+}
+
+static void
+eqn_def(struct eqn_node *ep)
+{
+ struct eqn_def *def;
+ int i;
+
+ if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "define");
+ return;
+ }
+
+ /*
+ * Search for a key that already exists.
+ * Create a new key if none is found.
+ */
+ if ((def = eqn_def_find(ep)) == NULL) {
+ /* 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;
+ }
+
+ def = ep->defs + i;
+ free(def->key);
+ def->key = mandoc_strndup(ep->start, ep->toksz);
+ def->keysz = ep->toksz;
+ }
+
+ if (eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "define %s", def->key);
+ free(def->key);
+ free(def->val);
+ def->key = def->val = NULL;
+ def->keysz = def->valsz = 0;
+ return;
+ }
+ free(def->val);
+ def->val = mandoc_strndup(ep->start, ep->toksz);
+ def->valsz = ep->toksz;
+}
+
+void
+eqn_parse(struct eqn_node *ep)
+{
+ struct eqn_box *cur, *nbox, *parent, *split;
+ const char *cp, *cpn;
+ char *p;
+ enum eqn_tok tok;
+ enum { CCL_LET, CCL_DIG, CCL_PUN } ccl, ccln;
+ int size;
+
+ parent = ep->node->eqn;
+ assert(parent != NULL);
+
+ /*
+ * Empty equation.
+ * Do not add it to the high-level syntax tree.
+ */
+
+ if (ep->data == NULL)
+ return;
+
+ ep->start = ep->end = ep->data;
+
+next_tok:
+ tok = eqn_next(ep, MODE_TOK);
+ switch (tok) {
+ case EQN_TOK_UNDEF:
+ eqn_undef(ep);
+ break;
+ case EQN_TOK_NDEFINE:
+ case EQN_TOK_DEFINE:
+ eqn_def(ep);
+ break;
+ case EQN_TOK_TDEFINE:
+ if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF ||
+ eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF)
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "tdefine");
+ break;
+ case EQN_TOK_DELIM:
+ eqn_delim(ep);
+ break;
+ case EQN_TOK_GFONT:
+ if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ break;
+ case EQN_TOK_MARK:
+ case EQN_TOK_LINEUP:
+ /* Ignore these. */
+ break;
+ case EQN_TOK_DYAD:
+ case EQN_TOK_VEC:
+ case EQN_TOK_UNDER:
+ case EQN_TOK_BAR:
+ case EQN_TOK_TILDE:
+ case EQN_TOK_HAT:
+ case EQN_TOK_DOT:
+ case EQN_TOK_DOTDOT:
+ if (parent->last == NULL) {
+ mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ cur = eqn_box_alloc(ep, parent);
+ cur->type = EQN_TEXT;
+ cur->text = mandoc_strdup("");
+ }
+ parent = eqn_box_makebinary(ep, parent);
+ parent->type = EQN_LIST;
+ parent->expectargs = 1;
+ parent->font = EQNFONT_ROMAN;
+ switch (tok) {
+ case EQN_TOK_DOTDOT:
+ parent->top = mandoc_strdup("\\[ad]");
+ break;
+ case EQN_TOK_VEC:
+ parent->top = mandoc_strdup("\\[->]");
+ break;
+ case EQN_TOK_DYAD:
+ parent->top = mandoc_strdup("\\[<>]");
+ break;
+ case EQN_TOK_TILDE:
+ parent->top = mandoc_strdup("\\[a~]");
+ break;
+ case EQN_TOK_UNDER:
+ parent->bottom = mandoc_strdup("\\[ul]");
+ break;
+ case EQN_TOK_BAR:
+ parent->top = mandoc_strdup("\\[rn]");
+ break;
+ case EQN_TOK_DOT:
+ parent->top = mandoc_strdup("\\[a.]");
+ break;
+ case EQN_TOK_HAT:
+ parent->top = mandoc_strdup("\\[ha]");
+ break;
+ default:
+ abort();
+ }
+ parent = parent->parent;
+ break;
+ case EQN_TOK_FWD:
+ case EQN_TOK_BACK:
+ case EQN_TOK_DOWN:
+ case EQN_TOK_UP:
+ if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ break;
+ case EQN_TOK_FAT:
+ case EQN_TOK_ROMAN:
+ case EQN_TOK_ITALIC:
+ case EQN_TOK_BOLD:
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ /*
+ * These values apply to the next word or sequence of
+ * words; thus, we mark that we'll have a child with
+ * exactly one of those.
+ */
+ parent = eqn_box_alloc(ep, parent);
+ parent->type = EQN_LIST;
+ parent->expectargs = 1;
+ switch (tok) {
+ case EQN_TOK_FAT:
+ parent->font = EQNFONT_FAT;
+ break;
+ case EQN_TOK_ROMAN:
+ parent->font = EQNFONT_ROMAN;
+ break;
+ case EQN_TOK_ITALIC:
+ parent->font = EQNFONT_ITALIC;
+ break;
+ case EQN_TOK_BOLD:
+ parent->font = EQNFONT_BOLD;
+ break;
+ default:
+ abort();
+ }
+ break;
+ case EQN_TOK_SIZE:
+ case EQN_TOK_GSIZE:
+ /* Accept two values: integral size and a single. */
+ if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ break;
+ }
+ size = mandoc_strntoi(ep->start, ep->toksz, 10);
+ if (-1 == size) {
+ mandoc_msg(MANDOCERR_IT_NONUM, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ break;
+ }
+ if (EQN_TOK_GSIZE == tok) {
+ ep->gsize = size;
+ break;
+ }
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ parent = eqn_box_alloc(ep, parent);
+ parent->type = EQN_LIST;
+ parent->expectargs = 1;
+ parent->size = size;
+ break;
+ case EQN_TOK_FROM:
+ case EQN_TOK_TO:
+ case EQN_TOK_SUB:
+ case EQN_TOK_SUP:
+ /*
+ * We have a left-right-associative expression.
+ * Repivot under a positional node, open a child scope
+ * and keep on reading.
+ */
+ if (parent->last == NULL) {
+ mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ cur = eqn_box_alloc(ep, parent);
+ cur->type = EQN_TEXT;
+ cur->text = mandoc_strdup("");
+ }
+ while (parent->expectargs == 1 && parent->args == 1)
+ parent = parent->parent;
+ if (tok == EQN_TOK_FROM || tok == EQN_TOK_TO) {
+ for (cur = parent; cur != NULL; cur = cur->parent)
+ if (cur->pos == EQNPOS_SUB ||
+ cur->pos == EQNPOS_SUP ||
+ cur->pos == EQNPOS_SUBSUP ||
+ cur->pos == EQNPOS_SQRT ||
+ cur->pos == EQNPOS_OVER)
+ break;
+ if (cur != NULL)
+ parent = cur->parent;
+ }
+ if (tok == EQN_TOK_SUP && parent->pos == EQNPOS_SUB) {
+ parent->expectargs = 3;
+ parent->pos = EQNPOS_SUBSUP;
+ break;
+ }
+ if (tok == EQN_TOK_TO && parent->pos == EQNPOS_FROM) {
+ parent->expectargs = 3;
+ parent->pos = EQNPOS_FROMTO;
+ break;
+ }
+ parent = eqn_box_makebinary(ep, parent);
+ switch (tok) {
+ case EQN_TOK_FROM:
+ parent->pos = EQNPOS_FROM;
+ break;
+ case EQN_TOK_TO:
+ parent->pos = EQNPOS_TO;
+ break;
+ case EQN_TOK_SUP:
+ parent->pos = EQNPOS_SUP;
+ break;
+ case EQN_TOK_SUB:
+ parent->pos = EQNPOS_SUB;
+ break;
+ default:
+ abort();
+ }
+ break;
+ case EQN_TOK_SQRT:
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ /*
+ * Accept a left-right-associative set of arguments just
+ * like sub and sup and friends but without rebalancing
+ * under a pivot.
+ */
+ parent = eqn_box_alloc(ep, parent);
+ parent->type = EQN_SUBEXPR;
+ parent->pos = EQNPOS_SQRT;
+ parent->expectargs = 1;
+ break;
+ case EQN_TOK_OVER:
+ /*
+ * We have a right-left-associative fraction.
+ * Close out anything that's currently open, then
+ * rebalance and continue reading.
+ */
+ if (parent->last == NULL) {
+ mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ cur = eqn_box_alloc(ep, parent);
+ cur->type = EQN_TEXT;
+ cur->text = mandoc_strdup("");
+ }
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ while (EQN_SUBEXPR == parent->type)
+ parent = parent->parent;
+ parent = eqn_box_makebinary(ep, parent);
+ parent->pos = EQNPOS_OVER;
+ break;
+ case EQN_TOK_RIGHT:
+ case EQN_TOK_BRACE_CLOSE:
+ /*
+ * Close out the existing brace.
+ * FIXME: this is a shitty sentinel: we should really
+ * have a native EQN_BRACE type or whatnot.
+ */
+ for (cur = parent; cur != NULL; cur = cur->parent)
+ if (cur->type == EQN_LIST &&
+ cur->expectargs > 1 &&
+ (tok == EQN_TOK_BRACE_CLOSE ||
+ cur->left != NULL))
+ break;
+ if (cur == NULL) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ break;
+ }
+ parent = cur;
+ if (EQN_TOK_RIGHT == tok) {
+ if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos,
+ "%s", eqn_toks[tok]);
+ break;
+ }
+ /* Handling depends on right/left. */
+ if (STRNEQ(ep->start, ep->toksz, "ceiling", 7))
+ parent->right = mandoc_strdup("\\[rc]");
+ else if (STRNEQ(ep->start, ep->toksz, "floor", 5))
+ parent->right = mandoc_strdup("\\[rf]");
+ else
+ parent->right =
+ mandoc_strndup(ep->start, ep->toksz);
+ }
+ parent = parent->parent;
+ if (tok == EQN_TOK_BRACE_CLOSE &&
+ (parent->type == EQN_PILE ||
+ parent->type == EQN_MATRIX))
+ parent = parent->parent;
+ /* Close out any "singleton" lists. */
+ while (parent->type == EQN_LIST &&
+ parent->expectargs == 1 &&
+ parent->args == 1)
+ parent = parent->parent;
+ break;
+ case EQN_TOK_BRACE_OPEN:
+ case EQN_TOK_LEFT:
+ /*
+ * If we already have something in the stack and we're
+ * in an expression, then rewind til we're not any more
+ * (just like with the text node).
+ */
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ if (EQN_TOK_LEFT == tok &&
+ eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ break;
+ }
+ parent = eqn_box_alloc(ep, parent);
+ parent->type = EQN_LIST;
+ if (EQN_TOK_LEFT == tok) {
+ if (STRNEQ(ep->start, ep->toksz, "ceiling", 7))
+ parent->left = mandoc_strdup("\\[lc]");
+ else if (STRNEQ(ep->start, ep->toksz, "floor", 5))
+ parent->left = mandoc_strdup("\\[lf]");
+ else
+ parent->left =
+ mandoc_strndup(ep->start, ep->toksz);
+ }
+ break;
+ case EQN_TOK_PILE:
+ case EQN_TOK_LPILE:
+ case EQN_TOK_RPILE:
+ case EQN_TOK_CPILE:
+ case EQN_TOK_CCOL:
+ case EQN_TOK_LCOL:
+ case EQN_TOK_RCOL:
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ parent = eqn_box_alloc(ep, parent);
+ parent->type = EQN_PILE;
+ parent->expectargs = 1;
+ break;
+ case EQN_TOK_ABOVE:
+ for (cur = parent; cur != NULL; cur = cur->parent)
+ if (cur->type == EQN_PILE)
+ break;
+ if (cur == NULL) {
+ mandoc_msg(MANDOCERR_IT_STRAY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
+ break;
+ }
+ parent = eqn_box_alloc(ep, cur);
+ parent->type = EQN_LIST;
+ break;
+ case EQN_TOK_MATRIX:
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ parent = eqn_box_alloc(ep, parent);
+ parent->type = EQN_MATRIX;
+ parent->expectargs = 1;
+ break;
+ case EQN_TOK_EOF:
+ return;
+ case EQN_TOK__MAX:
+ case EQN_TOK_FUNC:
+ case EQN_TOK_QUOTED:
+ case EQN_TOK_SYM:
+ p = ep->start;
+ assert(p != NULL);
+ /*
+ * If we already have something in the stack and we're
+ * in an expression, then rewind til we're not any more.
+ */
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
+ cur = eqn_box_alloc(ep, parent);
+ cur->type = EQN_TEXT;
+ cur->text = p;
+ switch (tok) {
+ case EQN_TOK_FUNC:
+ cur->font = EQNFONT_ROMAN;
+ break;
+ case EQN_TOK_QUOTED:
+ if (cur->font == EQNFONT_NONE)
+ cur->font = EQNFONT_ITALIC;
+ break;
+ case EQN_TOK_SYM:
+ break;
+ default:
+ if (cur->font != EQNFONT_NONE || *p == '\0')
+ break;
+ cpn = p - 1;
+ ccln = CCL_LET;
+ split = NULL;
+ for (;;) {
+ /* Advance to next character. */
+ cp = cpn++;
+ ccl = ccln;
+ ccln = isalpha((unsigned char)*cpn) ? CCL_LET :
+ isdigit((unsigned char)*cpn) ||
+ (*cpn == '.' && (ccl == CCL_DIG ||
+ isdigit((unsigned char)cpn[1]))) ?
+ CCL_DIG : CCL_PUN;
+ /* No boundary before first character. */
+ if (cp < p)
+ continue;
+ cur->font = ccl == CCL_LET ?
+ EQNFONT_ITALIC : EQNFONT_ROMAN;
+ if (*cp == '\\')
+ mandoc_escape(&cpn, NULL, NULL);
+ /* No boundary after last character. */
+ if (*cpn == '\0')
+ break;
+ if (ccln == ccl && *cp != ',' && *cpn != ',')
+ continue;
+ /* Boundary found, split the text. */
+ if (parent->args == parent->expectargs) {
+ /* Remove the text from the tree. */
+ if (cur->prev == NULL)
+ parent->first = cur->next;
+ else
+ cur->prev->next = NULL;
+ parent->last = cur->prev;
+ parent->args--;
+ /* Set up a list instead. */
+ split = eqn_box_alloc(ep, parent);
+ split->type = EQN_LIST;
+ /* Insert the word into the list. */
+ split->first = split->last = cur;
+ cur->parent = split;
+ cur->prev = NULL;
+ parent = split;
+ }
+ /* Append a new text box. */
+ nbox = eqn_box_alloc(ep, parent);
+ nbox->type = EQN_TEXT;
+ nbox->text = mandoc_strdup(cpn);
+ /* Truncate the old box. */
+ p = mandoc_strndup(cur->text,
+ cpn - cur->text);
+ free(cur->text);
+ cur->text = p;
+ /* Setup to process the new box. */
+ cur = nbox;
+ p = nbox->text;
+ cpn = p - 1;
+ ccln = CCL_LET;
+ }
+ if (split != NULL)
+ parent = split->parent;
+ break;
+ }
+ break;
+ default:
+ abort();
+ }
+ goto next_tok;
+}
+
+void
+eqn_free(struct eqn_node *p)
+{
+ int i;
+
+ if (p == NULL)
+ return;
+
+ for (i = 0; i < (int)p->defsz; i++) {
+ free(p->defs[i].key);
+ free(p->defs[i].val);
+ }
+
+ free(p->data);
+ free(p->defs);
+ free(p);
+}
diff --git a/usr.bin/mandoc/eqn.h b/usr.bin/mandoc/eqn.h
new file mode 100644
index 0000000..0eff4a4
--- /dev/null
+++ b/usr.bin/mandoc/eqn.h
@@ -0,0 +1,72 @@
+/* $OpenBSD: eqn.h,v 1.1 2018/12/13 05:13:15 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2014 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 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.
+ *
+ * Public data types for eqn(7) syntax trees.
+ */
+
+enum eqn_boxt {
+ EQN_TEXT, /* Text, e.g. number, variable, operator, ... */
+ EQN_SUBEXPR, /* Nested eqn(7) subexpression. */
+ EQN_LIST, /* List, for example in braces. */
+ EQN_PILE, /* Vertical pile. */
+ EQN_MATRIX /* List of columns. */
+};
+
+enum eqn_fontt {
+ EQNFONT_NONE = 0,
+ EQNFONT_ROMAN,
+ EQNFONT_BOLD,
+ EQNFONT_FAT,
+ EQNFONT_ITALIC,
+ EQNFONT__MAX
+};
+
+enum eqn_post {
+ EQNPOS_NONE = 0,
+ EQNPOS_SUP,
+ EQNPOS_SUBSUP,
+ EQNPOS_SUB,
+ EQNPOS_TO,
+ EQNPOS_FROM,
+ EQNPOS_FROMTO,
+ EQNPOS_OVER,
+ EQNPOS_SQRT,
+ EQNPOS__MAX
+};
+
+ /*
+ * A "box" is a parsed mathematical expression as defined by the eqn.7
+ * grammar.
+ */
+struct eqn_box {
+ struct eqn_box *parent;
+ struct eqn_box *prev;
+ struct eqn_box *next;
+ struct eqn_box *first; /* First child node. */
+ struct eqn_box *last; /* Last child node. */
+ char *text; /* Text (or NULL). */
+ char *left; /* Left-hand fence. */
+ char *right; /* Right-hand fence. */
+ char *top; /* Symbol above. */
+ char *bottom; /* Symbol below. */
+ size_t expectargs; /* Maximal number of arguments. */
+ size_t args; /* Actual number of arguments. */
+ int size; /* Font size. */
+#define EQN_DEFSIZE INT_MIN
+ enum eqn_boxt type; /* Type of node. */
+ enum eqn_fontt font; /* Font in this box. */
+ enum eqn_post pos; /* Position of the next box. */
+};
diff --git a/usr.bin/mandoc/eqn_html.c b/usr.bin/mandoc/eqn_html.c
new file mode 100644
index 0000000..049bbf9
--- /dev/null
+++ b/usr.bin/mandoc/eqn_html.c
@@ -0,0 +1,244 @@
+/* $OpenBSD: eqn_html.c,v 1.15 2019/03/17 18:20:07 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2017 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "eqn.h"
+#include "out.h"
+#include "html.h"
+
+static void
+eqn_box(struct html *p, const struct eqn_box *bp)
+{
+ struct tag *post, *row, *cell, *t;
+ const struct eqn_box *child, *parent;
+ const char *cp;
+ size_t i, j, rows;
+ enum htmltag tag;
+ enum eqn_fontt font;
+
+ if (NULL == bp)
+ return;
+
+ post = NULL;
+
+ /*
+ * Special handling for a matrix, which is presented to us in
+ * column order, but must be printed in row-order.
+ */
+ if (EQN_MATRIX == bp->type) {
+ if (NULL == bp->first)
+ goto out;
+ if (bp->first->type != EQN_LIST ||
+ bp->first->expectargs == 1) {
+ eqn_box(p, bp->first);
+ goto out;
+ }
+ if (NULL == (parent = bp->first->first))
+ goto out;
+ /* Estimate the number of rows, first. */
+ if (NULL == (child = parent->first))
+ goto out;
+ for (rows = 0; NULL != child; rows++)
+ child = child->next;
+ /* Print row-by-row. */
+ post = print_otag(p, TAG_MTABLE, "");
+ for (i = 0; i < rows; i++) {
+ parent = bp->first->first;
+ row = print_otag(p, TAG_MTR, "");
+ while (NULL != parent) {
+ child = parent->first;
+ for (j = 0; j < i; j++) {
+ if (NULL == child)
+ break;
+ child = child->next;
+ }
+ cell = print_otag(p, TAG_MTD, "");
+ /*
+ * If we have no data for this
+ * particular cell, then print a
+ * placeholder and continue--don't puke.
+ */
+ if (NULL != child)
+ eqn_box(p, child->first);
+ print_tagq(p, cell);
+ parent = parent->next;
+ }
+ print_tagq(p, row);
+ }
+ goto out;
+ }
+
+ switch (bp->pos) {
+ case EQNPOS_TO:
+ post = print_otag(p, TAG_MOVER, "");
+ break;
+ case EQNPOS_SUP:
+ post = print_otag(p, TAG_MSUP, "");
+ break;
+ case EQNPOS_FROM:
+ post = print_otag(p, TAG_MUNDER, "");
+ break;
+ case EQNPOS_SUB:
+ post = print_otag(p, TAG_MSUB, "");
+ break;
+ case EQNPOS_OVER:
+ post = print_otag(p, TAG_MFRAC, "");
+ break;
+ case EQNPOS_FROMTO:
+ post = print_otag(p, TAG_MUNDEROVER, "");
+ break;
+ case EQNPOS_SUBSUP:
+ post = print_otag(p, TAG_MSUBSUP, "");
+ break;
+ case EQNPOS_SQRT:
+ post = print_otag(p, TAG_MSQRT, "");
+ break;
+ default:
+ break;
+ }
+
+ if (bp->top || bp->bottom) {
+ assert(NULL == post);
+ if (bp->top && NULL == bp->bottom)
+ post = print_otag(p, TAG_MOVER, "");
+ else if (bp->top && bp->bottom)
+ post = print_otag(p, TAG_MUNDEROVER, "");
+ else if (bp->bottom)
+ post = print_otag(p, TAG_MUNDER, "");
+ }
+
+ if (EQN_PILE == bp->type) {
+ assert(NULL == post);
+ if (bp->first != NULL &&
+ bp->first->type == EQN_LIST &&
+ bp->first->expectargs > 1)
+ post = print_otag(p, TAG_MTABLE, "");
+ } else if (bp->type == EQN_LIST && bp->expectargs > 1 &&
+ bp->parent && bp->parent->type == EQN_PILE) {
+ assert(NULL == post);
+ post = print_otag(p, TAG_MTR, "");
+ print_otag(p, TAG_MTD, "");
+ }
+
+ if (bp->text != NULL) {
+ assert(post == NULL);
+ tag = TAG_MI;
+ cp = bp->text;
+ if (isdigit((unsigned char)cp[0]) ||
+ (cp[0] == '.' && isdigit((unsigned char)cp[1]))) {
+ tag = TAG_MN;
+ while (*++cp != '\0') {
+ if (*cp != '.' &&
+ isdigit((unsigned char)*cp) == 0) {
+ tag = TAG_MI;
+ break;
+ }
+ }
+ } else if (*cp != '\0' && isalpha((unsigned char)*cp) == 0) {
+ tag = TAG_MO;
+ while (*cp != '\0') {
+ if (cp[0] == '\\' && cp[1] != '\0') {
+ cp++;
+ mandoc_escape(&cp, NULL, NULL);
+ } else if (isalnum((unsigned char)*cp)) {
+ tag = TAG_MI;
+ break;
+ } else
+ cp++;
+ }
+ }
+ font = bp->font;
+ if (bp->text[0] != '\0' &&
+ (((tag == TAG_MN || tag == TAG_MO) &&
+ font == EQNFONT_ROMAN) ||
+ (tag == TAG_MI && font == (bp->text[1] == '\0' ?
+ EQNFONT_ITALIC : EQNFONT_ROMAN))))
+ font = EQNFONT_NONE;
+ switch (font) {
+ case EQNFONT_NONE:
+ post = print_otag(p, tag, "");
+ break;
+ case EQNFONT_ROMAN:
+ post = print_otag(p, tag, "?", "fontstyle", "normal");
+ break;
+ case EQNFONT_BOLD:
+ case EQNFONT_FAT:
+ post = print_otag(p, tag, "?", "fontweight", "bold");
+ break;
+ case EQNFONT_ITALIC:
+ post = print_otag(p, tag, "?", "fontstyle", "italic");
+ break;
+ default:
+ abort();
+ }
+ print_text(p, bp->text);
+ } else if (NULL == post) {
+ if (NULL != bp->left || NULL != bp->right)
+ post = print_otag(p, TAG_MFENCED, "??",
+ "open", bp->left == NULL ? "" : bp->left,
+ "close", bp->right == NULL ? "" : bp->right);
+ if (NULL == post)
+ post = print_otag(p, TAG_MROW, "");
+ else
+ print_otag(p, TAG_MROW, "");
+ }
+
+ eqn_box(p, bp->first);
+
+out:
+ if (NULL != bp->bottom) {
+ t = print_otag(p, TAG_MO, "");
+ print_text(p, bp->bottom);
+ print_tagq(p, t);
+ }
+ if (NULL != bp->top) {
+ t = print_otag(p, TAG_MO, "");
+ print_text(p, bp->top);
+ print_tagq(p, t);
+ }
+
+ if (NULL != post)
+ print_tagq(p, post);
+
+ eqn_box(p, bp->next);
+}
+
+void
+print_eqn(struct html *p, const struct eqn_box *bp)
+{
+ struct tag *t;
+
+ if (bp->first == NULL)
+ return;
+
+ t = print_otag(p, TAG_MATH, "c", "eqn");
+
+ p->flags |= HTML_NONOSPACE;
+ eqn_box(p, bp);
+ p->flags &= ~HTML_NONOSPACE;
+
+ print_tagq(p, t);
+}
diff --git a/usr.bin/mandoc/eqn_parse.h b/usr.bin/mandoc/eqn_parse.h
new file mode 100644
index 0000000..0a8e619
--- /dev/null
+++ b/usr.bin/mandoc/eqn_parse.h
@@ -0,0 +1,48 @@
+/* $OpenBSD: eqn_parse.h,v 1.3 2018/12/14 06:33:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2017, 2018 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.
+ *
+ * External interface of the eqn(7) parser.
+ * For use in the roff(7) and eqn(7) parsers only.
+ */
+
+struct roff_node;
+struct eqn_box;
+struct eqn_def;
+
+struct eqn_node {
+ struct roff_node *node; /* Syntax tree of this equation. */
+ struct eqn_def *defs; /* Array of definitions. */
+ char *data; /* Source code of this equation. */
+ char *start; /* First byte of the current token. */
+ char *end; /* First byte of the next token. */
+ size_t defsz; /* Number of definitions. */
+ size_t sz; /* Length of the source code. */
+ size_t toksz; /* Length of the current token. */
+ int gsize; /* Default point size. */
+ int delim; /* In-line delimiters enabled. */
+ char odelim; /* In-line opening delimiter. */
+ char cdelim; /* In-line closing delimiter. */
+};
+
+
+struct eqn_node *eqn_alloc(void);
+struct eqn_box *eqn_box_new(void);
+void eqn_box_free(struct eqn_box *);
+void eqn_free(struct eqn_node *);
+void eqn_parse(struct eqn_node *);
+void eqn_read(struct eqn_node *, const char *);
+void eqn_reset(struct eqn_node *);
diff --git a/usr.bin/mandoc/eqn_term.c b/usr.bin/mandoc/eqn_term.c
new file mode 100644
index 0000000..7bb1aae
--- /dev/null
+++ b/usr.bin/mandoc/eqn_term.c
@@ -0,0 +1,172 @@
+/* $OpenBSD: eqn_term.c,v 1.15 2018/12/13 05:13:15 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2017 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "eqn.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_box *bp)
+{
+
+ eqn_box(p, bp);
+ p->flags &= ~TERMP_NOSPACE;
+}
+
+static void
+eqn_box(struct termp *p, const struct eqn_box *bp)
+{
+ const struct eqn_box *child;
+ const char *cp;
+ int delim;
+
+ /* Delimiters around this box? */
+
+ if ((bp->type == EQN_LIST && bp->expectargs > 1) ||
+ (bp->type == EQN_PILE && (bp->prev || bp->next)) ||
+ (bp->parent != NULL && (bp->parent->pos == EQNPOS_SQRT ||
+ /* Diacritic followed by ^ or _. */
+ ((bp->top != NULL || bp->bottom != NULL) &&
+ bp->parent->type == EQN_SUBEXPR &&
+ bp->parent->pos != EQNPOS_OVER && bp->next != NULL) ||
+ /* Nested over, sub, sup, from, to. */
+ (bp->type == EQN_SUBEXPR && bp->pos != EQNPOS_SQRT &&
+ ((bp->parent->type == EQN_LIST && bp->expectargs == 1) ||
+ (bp->parent->type == EQN_SUBEXPR &&
+ bp->pos != EQNPOS_SQRT)))))) {
+ if ((bp->parent->type == EQN_SUBEXPR && bp->prev != NULL) ||
+ (bp->type == EQN_LIST &&
+ bp->first != NULL &&
+ bp->first->type != EQN_PILE &&
+ bp->first->type != EQN_MATRIX &&
+ bp->prev != NULL &&
+ (bp->prev->type == EQN_LIST ||
+ (bp->prev->type == EQN_TEXT &&
+ (*bp->prev->text == '\\' ||
+ isalpha((unsigned char)*bp->prev->text))))))
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, bp->left != NULL ? bp->left : "(");
+ p->flags |= TERMP_NOSPACE;
+ delim = 1;
+ } else
+ delim = 0;
+
+ /* Handle Fonts and text. */
+
+ if (bp->font != EQNFONT_NONE)
+ term_fontpush(p, fontmap[(int)bp->font]);
+
+ if (bp->text != NULL) {
+ if (strchr("!\"'),.:;?]}", *bp->text) != NULL)
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, bp->text);
+ if ((cp = strchr(bp->text, '\0')) > bp->text &&
+ (strchr("\"'([{", cp[-1]) != NULL ||
+ (bp->prev == NULL && (cp[-1] == '-' ||
+ (cp >= bp->text + 5 &&
+ strcmp(cp - 5, "\\[mi]") == 0)))))
+ p->flags |= TERMP_NOSPACE;
+ }
+
+ /* Special box types. */
+
+ if (bp->pos == EQNPOS_SQRT) {
+ term_word(p, "\\(sr");
+ if (bp->first != NULL) {
+ p->flags |= TERMP_NOSPACE;
+ eqn_box(p, bp->first);
+ }
+ } else if (bp->type == EQN_SUBEXPR) {
+ child = bp->first;
+ eqn_box(p, child);
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, bp->pos == EQNPOS_OVER ? "/" :
+ (bp->pos == EQNPOS_SUP ||
+ bp->pos == EQNPOS_TO) ? "^" : "_");
+ child = child->next;
+ if (child != NULL) {
+ p->flags |= TERMP_NOSPACE;
+ eqn_box(p, child);
+ if (bp->pos == EQNPOS_FROMTO ||
+ bp->pos == EQNPOS_SUBSUP) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "^");
+ p->flags |= TERMP_NOSPACE;
+ child = child->next;
+ if (child != NULL)
+ eqn_box(p, child);
+ }
+ }
+ } else {
+ child = bp->first;
+ if (bp->type == EQN_MATRIX &&
+ child != NULL &&
+ child->type == EQN_LIST &&
+ child->expectargs > 1)
+ child = child->first;
+ while (child != NULL) {
+ eqn_box(p,
+ bp->type == EQN_PILE &&
+ child->type == EQN_LIST &&
+ child->expectargs > 1 &&
+ child->args == 1 ?
+ child->first : child);
+ child = child->next;
+ }
+ }
+
+ /* Handle Fonts and diacritics. */
+
+ if (bp->font != EQNFONT_NONE)
+ term_fontpop(p);
+ if (bp->top != NULL) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, bp->top);
+ }
+ if (bp->bottom != NULL) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "_");
+ }
+
+ /* Right delimiter after this box? */
+
+ if (delim) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, bp->right != NULL ? bp->right : ")");
+ if (bp->parent->type == EQN_SUBEXPR && bp->next != NULL)
+ p->flags |= TERMP_NOSPACE;
+ }
+}
diff --git a/usr.bin/mandoc/html.c b/usr.bin/mandoc/html.c
new file mode 100644
index 0000000..0225e66
--- /dev/null
+++ b/usr.bin/mandoc/html.c
@@ -0,0 +1,1085 @@
+/* $OpenBSD: html.c,v 1.141 2020/04/20 12:59:24 schwarze Exp $ */
+/*
+ * Copyright (c) 2011-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2011, 2014 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 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.
+ *
+ * Common functions for mandoc(1) HTML formatters.
+ * For use by individual formatters and by the main program.
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "out.h"
+#include "html.h"
+#include "manconf.h"
+#include "main.h"
+
+struct htmldata {
+ const char *name;
+ int flags;
+#define HTML_INPHRASE (1 << 0) /* Can appear in phrasing context. */
+#define HTML_TOPHRASE (1 << 1) /* Establishes phrasing context. */
+#define HTML_NOSTACK (1 << 2) /* Does not have an end tag. */
+#define HTML_NLBEFORE (1 << 3) /* Output line break before opening. */
+#define HTML_NLBEGIN (1 << 4) /* Output line break after opening. */
+#define HTML_NLEND (1 << 5) /* Output line break before closing. */
+#define HTML_NLAFTER (1 << 6) /* Output line break after closing. */
+#define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER)
+#define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND)
+#define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE)
+#define HTML_INDENT (1 << 7) /* Indent content by two spaces. */
+#define HTML_NOINDENT (1 << 8) /* Exception: never indent content. */
+};
+
+static const struct htmldata htmltags[TAG_MAX] = {
+ {"html", HTML_NLALL},
+ {"head", HTML_NLALL | HTML_INDENT},
+ {"meta", HTML_NOSTACK | HTML_NLALL},
+ {"link", HTML_NOSTACK | HTML_NLALL},
+ {"style", HTML_NLALL | HTML_INDENT},
+ {"title", HTML_NLAROUND},
+ {"body", HTML_NLALL},
+ {"div", HTML_NLAROUND},
+ {"section", HTML_NLALL},
+ {"table", HTML_NLALL | HTML_INDENT},
+ {"tr", HTML_NLALL | HTML_INDENT},
+ {"td", HTML_NLAROUND},
+ {"li", HTML_NLAROUND | HTML_INDENT},
+ {"ul", HTML_NLALL | HTML_INDENT},
+ {"ol", HTML_NLALL | HTML_INDENT},
+ {"dl", HTML_NLALL | HTML_INDENT},
+ {"dt", HTML_NLAROUND},
+ {"dd", HTML_NLAROUND | HTML_INDENT},
+ {"h1", HTML_TOPHRASE | HTML_NLAROUND},
+ {"h2", HTML_TOPHRASE | HTML_NLAROUND},
+ {"p", HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT},
+ {"pre", HTML_TOPHRASE | HTML_NLALL | HTML_NOINDENT},
+ {"a", HTML_INPHRASE | HTML_TOPHRASE},
+ {"b", HTML_INPHRASE | HTML_TOPHRASE},
+ {"cite", HTML_INPHRASE | HTML_TOPHRASE},
+ {"code", HTML_INPHRASE | HTML_TOPHRASE},
+ {"i", HTML_INPHRASE | HTML_TOPHRASE},
+ {"small", HTML_INPHRASE | HTML_TOPHRASE},
+ {"span", HTML_INPHRASE | HTML_TOPHRASE},
+ {"var", HTML_INPHRASE | HTML_TOPHRASE},
+ {"br", HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL},
+ {"mark", HTML_INPHRASE },
+ {"math", HTML_INPHRASE | HTML_NLALL | HTML_INDENT},
+ {"mrow", 0},
+ {"mi", 0},
+ {"mn", 0},
+ {"mo", 0},
+ {"msup", 0},
+ {"msub", 0},
+ {"msubsup", 0},
+ {"mfrac", 0},
+ {"msqrt", 0},
+ {"mfenced", 0},
+ {"mtable", 0},
+ {"mtr", 0},
+ {"mtd", 0},
+ {"munderover", 0},
+ {"munder", 0},
+ {"mover", 0},
+};
+
+/* Avoid duplicate HTML id= attributes. */
+
+struct id_entry {
+ int ord; /* Ordinal number of the latest occurrence. */
+ char id[]; /* The id= attribute without any ordinal suffix. */
+};
+static struct ohash id_unique;
+
+static void html_reset_internal(struct html *);
+static void print_byte(struct html *, char);
+static void print_endword(struct html *);
+static void print_indent(struct html *);
+static void print_word(struct html *, const char *);
+
+static void print_ctag(struct html *, struct tag *);
+static int print_escape(struct html *, char);
+static int print_encode(struct html *, const char *, const char *, int);
+static void print_href(struct html *, const char *, const char *, int);
+static void print_metaf(struct html *);
+
+
+void *
+html_alloc(const struct manoutput *outopts)
+{
+ struct html *h;
+
+ h = mandoc_calloc(1, sizeof(struct html));
+
+ h->tag = NULL;
+ h->style = outopts->style;
+ if ((h->base_man1 = outopts->man) == NULL)
+ h->base_man2 = NULL;
+ else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL)
+ *h->base_man2++ = '\0';
+ h->base_includes = outopts->includes;
+ if (outopts->fragment)
+ h->oflags |= HTML_FRAGMENT;
+ if (outopts->toc)
+ h->oflags |= HTML_TOC;
+
+ mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
+
+ return h;
+}
+
+static void
+html_reset_internal(struct html *h)
+{
+ struct tag *tag;
+ struct id_entry *entry;
+ unsigned int slot;
+
+ while ((tag = h->tag) != NULL) {
+ h->tag = tag->next;
+ free(tag);
+ }
+ entry = ohash_first(&id_unique, &slot);
+ while (entry != NULL) {
+ free(entry);
+ entry = ohash_next(&id_unique, &slot);
+ }
+ ohash_delete(&id_unique);
+}
+
+void
+html_reset(void *p)
+{
+ html_reset_internal(p);
+ mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
+}
+
+void
+html_free(void *p)
+{
+ html_reset_internal(p);
+ free(p);
+}
+
+void
+print_gen_head(struct html *h)
+{
+ struct tag *t;
+
+ print_otag(h, TAG_META, "?", "charset", "utf-8");
+ if (h->style != NULL) {
+ print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
+ h->style, "type", "text/css", "media", "all");
+ return;
+ }
+
+ /*
+ * Print a minimal embedded style sheet.
+ */
+
+ t = print_otag(h, TAG_STYLE, "");
+ print_text(h, "table.head, table.foot { width: 100%; }");
+ print_endline(h);
+ print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
+ print_endline(h);
+ print_text(h, "td.head-vol { text-align: center; }");
+ print_endline(h);
+ print_text(h, ".Nd, .Bf, .Op { display: inline; }");
+ print_endline(h);
+ print_text(h, ".Pa, .Ad { font-style: italic; }");
+ print_endline(h);
+ print_text(h, ".Ms { font-weight: bold; }");
+ print_endline(h);
+ print_text(h, ".Bl-diag ");
+ print_byte(h, '>');
+ print_text(h, " dt { font-weight: bold; }");
+ print_endline(h);
+ print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd "
+ "{ font-weight: bold; font-family: inherit; }");
+ print_tagq(h, t);
+}
+
+int
+html_setfont(struct html *h, enum mandoc_esc font)
+{
+ switch (font) {
+ case ESCAPE_FONTPREV:
+ font = h->metal;
+ break;
+ case ESCAPE_FONTITALIC:
+ case ESCAPE_FONTBOLD:
+ case ESCAPE_FONTBI:
+ case ESCAPE_FONTCW:
+ case ESCAPE_FONTROMAN:
+ break;
+ case ESCAPE_FONT:
+ font = ESCAPE_FONTROMAN;
+ break;
+ default:
+ return 0;
+ }
+ h->metal = h->metac;
+ h->metac = font;
+ return 1;
+}
+
+static void
+print_metaf(struct html *h)
+{
+ if (h->metaf) {
+ print_tagq(h, h->metaf);
+ h->metaf = NULL;
+ }
+ switch (h->metac) {
+ case ESCAPE_FONTITALIC:
+ h->metaf = print_otag(h, TAG_I, "");
+ break;
+ case ESCAPE_FONTBOLD:
+ h->metaf = print_otag(h, TAG_B, "");
+ break;
+ case ESCAPE_FONTBI:
+ h->metaf = print_otag(h, TAG_B, "");
+ print_otag(h, TAG_I, "");
+ break;
+ case ESCAPE_FONTCW:
+ h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
+ break;
+ default:
+ break;
+ }
+}
+
+void
+html_close_paragraph(struct html *h)
+{
+ struct tag *this, *next;
+ int flags;
+
+ this = h->tag;
+ for (;;) {
+ next = this->next;
+ flags = htmltags[this->tag].flags;
+ if (flags & (HTML_INPHRASE | HTML_TOPHRASE))
+ print_ctag(h, this);
+ if ((flags & HTML_INPHRASE) == 0)
+ break;
+ this = next;
+ }
+}
+
+/*
+ * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode.
+ * TOKEN_NONE does not switch. The old mode is returned.
+ */
+enum roff_tok
+html_fillmode(struct html *h, enum roff_tok want)
+{
+ struct tag *t;
+ enum roff_tok had;
+
+ for (t = h->tag; t != NULL; t = t->next)
+ if (t->tag == TAG_PRE)
+ break;
+
+ had = t == NULL ? ROFF_fi : ROFF_nf;
+
+ if (want != had) {
+ switch (want) {
+ case ROFF_fi:
+ print_tagq(h, t);
+ break;
+ case ROFF_nf:
+ html_close_paragraph(h);
+ print_otag(h, TAG_PRE, "");
+ break;
+ case TOKEN_NONE:
+ break;
+ default:
+ abort();
+ }
+ }
+ return had;
+}
+
+/*
+ * Allocate a string to be used for the "id=" attribute of an HTML
+ * element and/or as a segment identifier for a URI in an <a> element.
+ * The function may fail and return NULL if the node lacks text data
+ * to create the attribute from.
+ * The caller is responsible for free(3)ing the returned string.
+ *
+ * If the "unique" argument is non-zero, the "id_unique" ohash table
+ * is used for de-duplication. If the "unique" argument is 1,
+ * it is the first time the function is called for this tag and
+ * location, so if an ordinal suffix is needed, it is incremented.
+ * If the "unique" argument is 2, it is the second time the function
+ * is called for this tag and location, so the ordinal suffix
+ * remains unchanged.
+ */
+char *
+html_make_id(const struct roff_node *n, int unique)
+{
+ const struct roff_node *nch;
+ struct id_entry *entry;
+ char *buf, *cp;
+ size_t len;
+ unsigned int slot;
+
+ if (n->tag != NULL)
+ buf = mandoc_strdup(n->tag);
+ else {
+ switch (n->tok) {
+ case MDOC_Sh:
+ case MDOC_Ss:
+ case MDOC_Sx:
+ case MAN_SH:
+ case MAN_SS:
+ for (nch = n->child; nch != NULL; nch = nch->next)
+ if (nch->type != ROFFT_TEXT)
+ return NULL;
+ buf = NULL;
+ deroff(&buf, n);
+ if (buf == NULL)
+ return NULL;
+ break;
+ default:
+ if (n->child == NULL || n->child->type != ROFFT_TEXT)
+ return NULL;
+ buf = mandoc_strdup(n->child->string);
+ break;
+ }
+ }
+
+ /*
+ * In ID attributes, only use ASCII characters that are
+ * permitted in URL-fragment strings according to the
+ * explicit list at:
+ * https://url.spec.whatwg.org/#url-fragment-string
+ * In addition, reserve '~' for ordinal suffixes.
+ */
+
+ for (cp = buf; *cp != '\0'; cp++)
+ if (isalnum((unsigned char)*cp) == 0 &&
+ strchr("!$&'()*+,-./:;=?@_", *cp) == NULL)
+ *cp = '_';
+
+ if (unique == 0)
+ return buf;
+
+ /* Avoid duplicate HTML id= attributes. */
+
+ slot = ohash_qlookup(&id_unique, buf);
+ if ((entry = ohash_find(&id_unique, slot)) == NULL) {
+ len = strlen(buf) + 1;
+ entry = mandoc_malloc(sizeof(*entry) + len);
+ entry->ord = 1;
+ memcpy(entry->id, buf, len);
+ ohash_insert(&id_unique, slot, entry);
+ } else if (unique == 1)
+ entry->ord++;
+
+ if (entry->ord > 1) {
+ cp = buf;
+ mandoc_asprintf(&buf, "%s~%d", cp, entry->ord);
+ free(cp);
+ }
+ return buf;
+}
+
+static int
+print_escape(struct html *h, char c)
+{
+
+ switch (c) {
+ case '<':
+ print_word(h, "&lt;");
+ break;
+ case '>':
+ print_word(h, "&gt;");
+ break;
+ case '&':
+ print_word(h, "&amp;");
+ break;
+ case '"':
+ print_word(h, "&quot;");
+ break;
+ case ASCII_NBRSP:
+ print_word(h, "&nbsp;");
+ break;
+ case ASCII_HYPH:
+ print_byte(h, '-');
+ break;
+ case ASCII_BREAK:
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+static int
+print_encode(struct html *h, const char *p, const char *pend, int norecurse)
+{
+ char numbuf[16];
+ const char *seq;
+ size_t sz;
+ int c, len, breakline, nospace;
+ enum mandoc_esc esc;
+ static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
+ ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
+
+ if (pend == NULL)
+ pend = strchr(p, '\0');
+
+ breakline = 0;
+ nospace = 0;
+
+ while (p < pend) {
+ if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
+ h->flags &= ~HTML_SKIPCHAR;
+ p++;
+ continue;
+ }
+
+ for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
+ print_byte(h, *p);
+
+ if (breakline &&
+ (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
+ print_otag(h, TAG_BR, "");
+ breakline = 0;
+ while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
+ p++;
+ continue;
+ }
+
+ if (p >= pend)
+ break;
+
+ if (*p == ' ') {
+ print_endword(h);
+ p++;
+ continue;
+ }
+
+ if (print_escape(h, *p++))
+ continue;
+
+ esc = mandoc_escape(&p, &seq, &len);
+ switch (esc) {
+ case ESCAPE_FONT:
+ case ESCAPE_FONTPREV:
+ case ESCAPE_FONTBOLD:
+ case ESCAPE_FONTITALIC:
+ case ESCAPE_FONTBI:
+ case ESCAPE_FONTCW:
+ case ESCAPE_FONTROMAN:
+ if (0 == norecurse) {
+ h->flags |= HTML_NOSPACE;
+ if (html_setfont(h, esc))
+ print_metaf(h);
+ h->flags &= ~HTML_NOSPACE;
+ }
+ continue;
+ case ESCAPE_SKIPCHAR:
+ h->flags |= HTML_SKIPCHAR;
+ continue;
+ case ESCAPE_ERROR:
+ 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);
+ break;
+ case ESCAPE_NUMBERED:
+ c = mchars_num2char(seq, len);
+ if (c < 0)
+ continue;
+ break;
+ case ESCAPE_SPECIAL:
+ c = mchars_spec2cp(seq, len);
+ if (c <= 0)
+ continue;
+ break;
+ case ESCAPE_UNDEF:
+ c = *seq;
+ break;
+ case ESCAPE_DEVICE:
+ print_word(h, "html");
+ continue;
+ case ESCAPE_BREAK:
+ breakline = 1;
+ continue;
+ case ESCAPE_NOSPACE:
+ if ('\0' == *p)
+ nospace = 1;
+ continue;
+ case ESCAPE_OVERSTRIKE:
+ if (len == 0)
+ continue;
+ c = seq[len - 1];
+ break;
+ default:
+ continue;
+ }
+ if ((c < 0x20 && c != 0x09) ||
+ (c > 0x7E && c < 0xA0))
+ c = 0xFFFD;
+ if (c > 0x7E) {
+ (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
+ print_word(h, numbuf);
+ } else if (print_escape(h, c) == 0)
+ print_byte(h, c);
+ }
+
+ return nospace;
+}
+
+static void
+print_href(struct html *h, const char *name, const char *sec, int man)
+{
+ struct stat sb;
+ const char *p, *pp;
+ char *filename;
+
+ if (man) {
+ pp = h->base_man1;
+ if (h->base_man2 != NULL) {
+ mandoc_asprintf(&filename, "%s.%s", name, sec);
+ if (stat(filename, &sb) == -1)
+ pp = h->base_man2;
+ free(filename);
+ }
+ } else
+ pp = h->base_includes;
+
+ while ((p = strchr(pp, '%')) != NULL) {
+ print_encode(h, pp, p, 1);
+ if (man && p[1] == 'S') {
+ if (sec == NULL)
+ print_byte(h, '1');
+ else
+ print_encode(h, sec, NULL, 1);
+ } else if ((man && p[1] == 'N') ||
+ (man == 0 && p[1] == 'I'))
+ print_encode(h, name, NULL, 1);
+ else
+ print_encode(h, p, p + 2, 1);
+ pp = p + 2;
+ }
+ if (*pp != '\0')
+ print_encode(h, pp, NULL, 1);
+}
+
+struct tag *
+print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
+{
+ va_list ap;
+ struct tag *t;
+ const char *attr;
+ char *arg1, *arg2;
+ int style_written, tflags;
+
+ tflags = htmltags[tag].flags;
+
+ /* Flow content is not allowed in phrasing context. */
+
+ if ((tflags & HTML_INPHRASE) == 0) {
+ for (t = h->tag; t != NULL; t = t->next) {
+ if (t->closed)
+ continue;
+ assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0);
+ break;
+ }
+
+ /*
+ * Always wrap phrasing elements in a paragraph
+ * unless already contained in some flow container;
+ * never put them directly into a section.
+ */
+
+ } else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION)
+ print_otag(h, TAG_P, "c", "Pp");
+
+ /* Push this tag onto the stack of open scopes. */
+
+ if ((tflags & HTML_NOSTACK) == 0) {
+ t = mandoc_malloc(sizeof(struct tag));
+ t->tag = tag;
+ t->next = h->tag;
+ t->refcnt = 0;
+ t->closed = 0;
+ h->tag = t;
+ } else
+ t = NULL;
+
+ if (tflags & HTML_NLBEFORE)
+ print_endline(h);
+ if (h->col == 0)
+ print_indent(h);
+ else if ((h->flags & HTML_NOSPACE) == 0) {
+ if (h->flags & HTML_KEEP)
+ print_word(h, "&#x00A0;");
+ else {
+ if (h->flags & HTML_PREKEEP)
+ h->flags |= HTML_KEEP;
+ print_endword(h);
+ }
+ }
+
+ if ( ! (h->flags & HTML_NONOSPACE))
+ h->flags &= ~HTML_NOSPACE;
+ else
+ h->flags |= HTML_NOSPACE;
+
+ /* Print out the tag name and attributes. */
+
+ print_byte(h, '<');
+ print_word(h, htmltags[tag].name);
+
+ va_start(ap, fmt);
+
+ while (*fmt != '\0' && *fmt != 's') {
+
+ /* Parse attributes and arguments. */
+
+ arg1 = va_arg(ap, char *);
+ arg2 = NULL;
+ switch (*fmt++) {
+ case 'c':
+ attr = "class";
+ break;
+ case 'h':
+ attr = "href";
+ break;
+ case 'i':
+ attr = "id";
+ break;
+ case '?':
+ attr = arg1;
+ arg1 = va_arg(ap, char *);
+ break;
+ default:
+ abort();
+ }
+ if (*fmt == 'M')
+ arg2 = va_arg(ap, char *);
+ if (arg1 == NULL)
+ continue;
+
+ /* Print the attributes. */
+
+ print_byte(h, ' ');
+ print_word(h, attr);
+ print_byte(h, '=');
+ print_byte(h, '"');
+ switch (*fmt) {
+ case 'I':
+ print_href(h, arg1, NULL, 0);
+ fmt++;
+ break;
+ case 'M':
+ print_href(h, arg1, arg2, 1);
+ fmt++;
+ break;
+ case 'R':
+ print_byte(h, '#');
+ print_encode(h, arg1, NULL, 1);
+ fmt++;
+ break;
+ default:
+ print_encode(h, arg1, NULL, 1);
+ break;
+ }
+ print_byte(h, '"');
+ }
+
+ style_written = 0;
+ while (*fmt++ == 's') {
+ arg1 = va_arg(ap, char *);
+ arg2 = va_arg(ap, char *);
+ if (arg2 == NULL)
+ continue;
+ print_byte(h, ' ');
+ if (style_written == 0) {
+ print_word(h, "style=\"");
+ style_written = 1;
+ }
+ print_word(h, arg1);
+ print_byte(h, ':');
+ print_byte(h, ' ');
+ print_word(h, arg2);
+ print_byte(h, ';');
+ }
+ if (style_written)
+ print_byte(h, '"');
+
+ va_end(ap);
+
+ /* Accommodate for "well-formed" singleton escaping. */
+
+ if (htmltags[tag].flags & HTML_NOSTACK)
+ print_byte(h, '/');
+
+ print_byte(h, '>');
+
+ if (tflags & HTML_NLBEGIN)
+ print_endline(h);
+ else
+ h->flags |= HTML_NOSPACE;
+
+ if (tflags & HTML_INDENT)
+ h->indent++;
+ if (tflags & HTML_NOINDENT)
+ h->noindent++;
+
+ return t;
+}
+
+/*
+ * Print an element with an optional "id=" attribute.
+ * If the element has phrasing content and an "id=" attribute,
+ * also add a permalink: outside if it can be in phrasing context,
+ * inside otherwise.
+ */
+struct tag *
+print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr,
+ struct roff_node *n)
+{
+ struct roff_node *nch;
+ struct tag *ret, *t;
+ char *id, *href;
+
+ ret = NULL;
+ id = href = NULL;
+ if (n->flags & NODE_ID)
+ id = html_make_id(n, 1);
+ if (n->flags & NODE_HREF)
+ href = id == NULL ? html_make_id(n, 2) : id;
+ if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE)
+ ret = print_otag(h, TAG_A, "chR", "permalink", href);
+ t = print_otag(h, elemtype, "ci", cattr, id);
+ if (ret == NULL) {
+ ret = t;
+ if (href != NULL && (nch = n->child) != NULL) {
+ /* man(7) is safe, it tags phrasing content only. */
+ if (n->tok > MDOC_MAX ||
+ htmltags[elemtype].flags & HTML_TOPHRASE)
+ nch = NULL;
+ else /* For mdoc(7), beware of nested blocks. */
+ while (nch != NULL && nch->type == ROFFT_TEXT)
+ nch = nch->next;
+ if (nch == NULL)
+ print_otag(h, TAG_A, "chR", "permalink", href);
+ }
+ }
+ free(id);
+ if (id == NULL)
+ free(href);
+ return ret;
+}
+
+static void
+print_ctag(struct html *h, struct tag *tag)
+{
+ int tflags;
+
+ if (tag->closed == 0) {
+ tag->closed = 1;
+ if (tag == h->metaf)
+ h->metaf = NULL;
+ if (tag == h->tblt)
+ h->tblt = NULL;
+
+ tflags = htmltags[tag->tag].flags;
+ if (tflags & HTML_INDENT)
+ h->indent--;
+ if (tflags & HTML_NOINDENT)
+ h->noindent--;
+ if (tflags & HTML_NLEND)
+ print_endline(h);
+ print_indent(h);
+ print_byte(h, '<');
+ print_byte(h, '/');
+ print_word(h, htmltags[tag->tag].name);
+ print_byte(h, '>');
+ if (tflags & HTML_NLAFTER)
+ print_endline(h);
+ }
+ if (tag->refcnt == 0) {
+ h->tag = tag->next;
+ free(tag);
+ }
+}
+
+void
+print_gen_decls(struct html *h)
+{
+ print_word(h, "<!DOCTYPE html>");
+ print_endline(h);
+}
+
+void
+print_gen_comment(struct html *h, struct roff_node *n)
+{
+ int wantblank;
+
+ print_word(h, "<!-- This is an automatically generated file."
+ " Do not edit.");
+ h->indent = 1;
+ wantblank = 0;
+ while (n != NULL && n->type == ROFFT_COMMENT) {
+ if (strstr(n->string, "-->") == NULL &&
+ (wantblank || *n->string != '\0')) {
+ print_endline(h);
+ print_indent(h);
+ print_word(h, n->string);
+ wantblank = *n->string != '\0';
+ }
+ n = n->next;
+ }
+ if (wantblank)
+ print_endline(h);
+ print_word(h, " -->");
+ print_endline(h);
+ h->indent = 0;
+}
+
+void
+print_text(struct html *h, const char *word)
+{
+ print_tagged_text(h, word, NULL);
+}
+
+void
+print_tagged_text(struct html *h, const char *word, struct roff_node *n)
+{
+ struct tag *t;
+ char *href;
+
+ /*
+ * Always wrap text in a paragraph unless already contained in
+ * some flow container; never put it directly into a section.
+ */
+
+ if (h->tag->tag == TAG_SECTION)
+ print_otag(h, TAG_P, "c", "Pp");
+
+ /* Output whitespace before this text? */
+
+ if (h->col && (h->flags & HTML_NOSPACE) == 0) {
+ if ( ! (HTML_KEEP & h->flags)) {
+ if (HTML_PREKEEP & h->flags)
+ h->flags |= HTML_KEEP;
+ print_endword(h);
+ } else
+ print_word(h, "&#x00A0;");
+ }
+
+ /*
+ * Optionally switch fonts, optionally write a permalink, then
+ * print the text, optionally surrounded by HTML whitespace.
+ */
+
+ assert(h->metaf == NULL);
+ print_metaf(h);
+ print_indent(h);
+
+ if (n != NULL && (href = html_make_id(n, 2)) != NULL) {
+ t = print_otag(h, TAG_A, "chR", "permalink", href);
+ free(href);
+ } else
+ t = NULL;
+
+ if ( ! print_encode(h, word, NULL, 0)) {
+ if ( ! (h->flags & HTML_NONOSPACE))
+ h->flags &= ~HTML_NOSPACE;
+ h->flags &= ~HTML_NONEWLINE;
+ } else
+ h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
+
+ if (h->metaf != NULL) {
+ print_tagq(h, h->metaf);
+ h->metaf = NULL;
+ } else if (t != NULL)
+ print_tagq(h, t);
+
+ h->flags &= ~HTML_IGNDELIM;
+}
+
+void
+print_tagq(struct html *h, const struct tag *until)
+{
+ struct tag *this, *next;
+
+ for (this = h->tag; this != NULL; this = next) {
+ next = this == until ? NULL : this->next;
+ print_ctag(h, this);
+ }
+}
+
+/*
+ * Close out all open elements up to but excluding suntil.
+ * Note that a paragraph just inside stays open together with it
+ * because paragraphs include subsequent phrasing content.
+ */
+void
+print_stagq(struct html *h, const struct tag *suntil)
+{
+ struct tag *this, *next;
+
+ for (this = h->tag; this != NULL; this = next) {
+ next = this->next;
+ if (this == suntil || (next == suntil &&
+ (this->tag == TAG_P || this->tag == TAG_PRE)))
+ break;
+ print_ctag(h, this);
+ }
+}
+
+
+/***********************************************************************
+ * Low level output functions.
+ * They implement line breaking using a short static buffer.
+ ***********************************************************************/
+
+/*
+ * Buffer one HTML output byte.
+ * If the buffer is full, flush and deactivate it and start a new line.
+ * If the buffer is inactive, print directly.
+ */
+static void
+print_byte(struct html *h, char c)
+{
+ if ((h->flags & HTML_BUFFER) == 0) {
+ putchar(c);
+ h->col++;
+ return;
+ }
+
+ if (h->col + h->bufcol < sizeof(h->buf)) {
+ h->buf[h->bufcol++] = c;
+ return;
+ }
+
+ putchar('\n');
+ h->col = 0;
+ print_indent(h);
+ putchar(' ');
+ putchar(' ');
+ fwrite(h->buf, h->bufcol, 1, stdout);
+ putchar(c);
+ h->col = (h->indent + 1) * 2 + h->bufcol + 1;
+ h->bufcol = 0;
+ h->flags &= ~HTML_BUFFER;
+}
+
+/*
+ * If something was printed on the current output line, end it.
+ * Not to be called right after print_indent().
+ */
+void
+print_endline(struct html *h)
+{
+ if (h->col == 0)
+ return;
+
+ if (h->bufcol) {
+ putchar(' ');
+ fwrite(h->buf, h->bufcol, 1, stdout);
+ h->bufcol = 0;
+ }
+ putchar('\n');
+ h->col = 0;
+ h->flags |= HTML_NOSPACE;
+ h->flags &= ~HTML_BUFFER;
+}
+
+/*
+ * Flush the HTML output buffer.
+ * If it is inactive, activate it.
+ */
+static void
+print_endword(struct html *h)
+{
+ if (h->noindent) {
+ print_byte(h, ' ');
+ return;
+ }
+
+ if ((h->flags & HTML_BUFFER) == 0) {
+ h->col++;
+ h->flags |= HTML_BUFFER;
+ } else if (h->bufcol) {
+ putchar(' ');
+ fwrite(h->buf, h->bufcol, 1, stdout);
+ h->col += h->bufcol + 1;
+ }
+ h->bufcol = 0;
+}
+
+/*
+ * If at the beginning of a new output line,
+ * perform indentation and mark the line as containing output.
+ * Make sure to really produce some output right afterwards,
+ * but do not use print_otag() for producing it.
+ */
+static void
+print_indent(struct html *h)
+{
+ size_t i;
+
+ if (h->col || h->noindent)
+ return;
+
+ h->col = h->indent * 2;
+ for (i = 0; i < h->col; i++)
+ putchar(' ');
+}
+
+/*
+ * Print or buffer some characters
+ * depending on the current HTML output buffer state.
+ */
+static void
+print_word(struct html *h, const char *cp)
+{
+ while (*cp != '\0')
+ print_byte(h, *cp++);
+}
diff --git a/usr.bin/mandoc/html.h b/usr.bin/mandoc/html.h
new file mode 100644
index 0000000..dcce339
--- /dev/null
+++ b/usr.bin/mandoc/html.h
@@ -0,0 +1,141 @@
+/* $OpenBSD: html.h,v 1.70 2020/04/18 20:28:46 schwarze Exp $ */
+/*
+ * Copyright (c) 2017, 2018, 2019, 2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2011, 2014 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.
+ *
+ * Internal interfaces for mandoc(1) HTML formatters.
+ * For use by the individual HTML formatters only.
+ */
+
+enum htmltag {
+ TAG_HTML,
+ TAG_HEAD,
+ TAG_META,
+ TAG_LINK,
+ TAG_STYLE,
+ TAG_TITLE,
+ TAG_BODY,
+ TAG_DIV,
+ TAG_SECTION,
+ TAG_TABLE,
+ TAG_TR,
+ TAG_TD,
+ TAG_LI,
+ TAG_UL,
+ TAG_OL,
+ TAG_DL,
+ TAG_DT,
+ TAG_DD,
+ TAG_H1,
+ TAG_H2,
+ TAG_P,
+ TAG_PRE,
+ TAG_A,
+ TAG_B,
+ TAG_CITE,
+ TAG_CODE,
+ TAG_I,
+ TAG_SMALL,
+ TAG_SPAN,
+ TAG_VAR,
+ TAG_BR,
+ TAG_MARK,
+ TAG_MATH,
+ TAG_MROW,
+ TAG_MI,
+ TAG_MN,
+ TAG_MO,
+ TAG_MSUP,
+ TAG_MSUB,
+ TAG_MSUBSUP,
+ TAG_MFRAC,
+ TAG_MSQRT,
+ TAG_MFENCED,
+ TAG_MTABLE,
+ TAG_MTR,
+ TAG_MTD,
+ TAG_MUNDEROVER,
+ TAG_MUNDER,
+ TAG_MOVER,
+ TAG_MAX
+};
+
+struct tag {
+ struct tag *next;
+ int refcnt;
+ int closed;
+ enum htmltag tag;
+};
+
+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_SKIPCHAR (1 << 6) /* skip the next character */
+#define HTML_NOSPLIT (1 << 7) /* do not break line before .An */
+#define HTML_SPLIT (1 << 8) /* break line before .An */
+#define HTML_NONEWLINE (1 << 9) /* No line break in nofill mode. */
+#define HTML_BUFFER (1 << 10) /* Collect a word to see if it fits. */
+#define HTML_TOCDONE (1 << 11) /* The TOC was already written. */
+ size_t indent; /* current output indentation level */
+ int noindent; /* indent disabled by <pre> */
+ size_t col; /* current output byte position */
+ size_t bufcol; /* current buf byte position */
+ char buf[80]; /* output buffer */
+ struct tag *tag; /* last open tag */
+ struct rofftbl tbl; /* current table */
+ struct tag *tblt; /* current open table scope */
+ char *base_man1; /* bases for manpage href */
+ char *base_man2;
+ char *base_includes; /* base for include href */
+ char *style; /* style-sheet URI */
+ struct tag *metaf; /* current open font scope */
+ enum mandoc_esc metal; /* last used font */
+ enum mandoc_esc metac; /* current font mode */
+ int oflags; /* output options */
+#define HTML_FRAGMENT (1 << 0) /* don't emit HTML/HEAD/BODY */
+#define HTML_TOC (1 << 1) /* emit a table of contents */
+};
+
+
+struct roff_node;
+struct tbl_span;
+struct eqn_box;
+
+void roff_html_pre(struct html *, const struct roff_node *);
+
+void print_gen_comment(struct html *, struct roff_node *);
+void print_gen_decls(struct html *);
+void print_gen_head(struct html *);
+struct tag *print_otag(struct html *, enum htmltag, const char *, ...);
+struct tag *print_otag_id(struct html *, enum htmltag, const char *,
+ struct roff_node *);
+void print_tagq(struct html *, const struct tag *);
+void print_stagq(struct html *, const struct tag *);
+void print_tagged_text(struct html *, const char *,
+ struct roff_node *);
+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_box *);
+void print_endline(struct html *);
+
+void html_close_paragraph(struct html *);
+enum roff_tok html_fillmode(struct html *, enum roff_tok);
+char *html_make_id(const struct roff_node *, int);
+int html_setfont(struct html *, enum mandoc_esc);
diff --git a/usr.bin/mandoc/libman.h b/usr.bin/mandoc/libman.h
new file mode 100644
index 0000000..daffd76
--- /dev/null
+++ b/usr.bin/mandoc/libman.h
@@ -0,0 +1,42 @@
+/* $OpenBSD: libman.h,v 1.61 2018/12/31 10:03:38 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2018 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.
+ */
+
+struct roff_node;
+struct roff_man;
+
+#define MACRO_PROT_ARGS struct roff_man *man, \
+ enum roff_tok tok, \
+ int line, \
+ int ppos, \
+ int *pos, \
+ char *buf
+
+struct man_macro {
+ void (*fp)(MACRO_PROT_ARGS);
+ int flags;
+#define MAN_BSCOPED (1 << 0) /* Optional next-line block scope. */
+#define MAN_ESCOPED (1 << 1) /* Optional next-line element scope. */
+#define MAN_NSCOPED (1 << 2) /* Allowed in next-line element scope. */
+#define MAN_XSCOPE (1 << 3) /* Exit next-line block scope. */
+#define MAN_JOIN (1 << 4) /* Join arguments together. */
+};
+
+const struct man_macro *man_macro(enum roff_tok);
+
+void man_descope(struct roff_man *, int, int, char *);
+void man_unscope(struct roff_man *, const struct roff_node *);
diff --git a/usr.bin/mandoc/libmandoc.h b/usr.bin/mandoc/libmandoc.h
new file mode 100644
index 0000000..b291631
--- /dev/null
+++ b/usr.bin/mandoc/libmandoc.h
@@ -0,0 +1,85 @@
+/* $OpenBSD: libmandoc.h,v 1.64 2020/04/03 11:34:19 schwarze Exp $ */
+/*
+ * Copyright (c) 2013-2015,2017,2018,2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2009, 2010, 2011, 2012 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 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.
+ *
+ * Internal interfaces for parser utilities needed by multiple parsers
+ * and the top-level functions to call the mdoc, man, and roff parsers.
+ */
+
+/*
+ * Return codes passed from the roff parser to the main parser.
+ */
+
+/* Main instruction: what to do with the returned line. */
+#define ROFF_IGN 0x000 /* Don't do anything with it. */
+#define ROFF_CONT 0x001 /* Give it to the high-level parser. */
+#define ROFF_RERUN 0x002 /* Re-run the roff parser with an offset. */
+#define ROFF_REPARSE 0x004 /* Recursively run the main parser on it. */
+#define ROFF_SO 0x008 /* Include the named file. */
+#define ROFF_MASK 0x00f /* Only one of these bits should be set. */
+
+/* Options for further parsing, to be OR'ed with the above. */
+#define ROFF_APPEND 0x010 /* Append the next line to this one. */
+#define ROFF_USERCALL 0x020 /* Start execution of a new macro. */
+#define ROFF_USERRET 0x040 /* Abort execution of the current macro. */
+#define ROFF_WHILE 0x100 /* Start a new .while loop. */
+#define ROFF_LOOPCONT 0x200 /* Iterate the current .while loop. */
+#define ROFF_LOOPEXIT 0x400 /* Exit the current .while loop. */
+#define ROFF_LOOPMASK 0xf00
+
+
+struct buf {
+ char *buf;
+ size_t sz;
+ struct buf *next;
+};
+
+
+struct roff;
+struct roff_man;
+struct roff_node;
+
+char *mandoc_normdate(struct roff_node *, struct roff_node *);
+int mandoc_eos(const char *, size_t);
+int mandoc_strntoi(const char *, size_t, int);
+const char *mandoc_a2msec(const char*);
+
+int mdoc_parseln(struct roff_man *, int, char *, int);
+void mdoc_endparse(struct roff_man *);
+
+int man_parseln(struct roff_man *, int, char *, int);
+void man_endparse(struct roff_man *);
+
+int preconv_cue(const struct buf *, size_t);
+int preconv_encode(const struct buf *, size_t *,
+ struct buf *, size_t *, int *);
+
+void roff_free(struct roff *);
+struct roff *roff_alloc(int);
+void roff_reset(struct roff *);
+void roff_man_free(struct roff_man *);
+struct roff_man *roff_man_alloc(struct roff *, const char *, int);
+void roff_man_reset(struct roff_man *);
+int roff_parseln(struct roff *, int, struct buf *, int *);
+void roff_userret(struct roff *);
+void roff_endparse(struct roff *);
+void roff_setreg(struct roff *, const char *, int, char);
+int roff_getreg(struct roff *, const char *);
+char *roff_strdup(const struct roff *, const char *);
+char *roff_getarg(struct roff *, char **, int, int *);
+int roff_getcontrol(const struct roff *,
+ const char *, int *);
+int roff_getformat(const struct roff *);
diff --git a/usr.bin/mandoc/libmdoc.h b/usr.bin/mandoc/libmdoc.h
new file mode 100644
index 0000000..4cdea31
--- /dev/null
+++ b/usr.bin/mandoc/libmdoc.h
@@ -0,0 +1,86 @@
+/* $OpenBSD: libmdoc.h,v 1.88 2018/12/31 04:55:42 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013,2014,2015,2017,2018 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.
+ */
+
+struct roff_node;
+struct roff_man;
+struct mdoc_arg;
+
+#define MACRO_PROT_ARGS struct roff_man *mdoc, \
+ enum roff_tok tok, \
+ int line, \
+ int ppos, \
+ int *pos, \
+ char *buf
+
+struct mdoc_macro {
+ void (*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_ALLOC, /* normal word from roff_getarg() */
+ ARGS_PUNCT, /* series of punctuation */
+ ARGS_PHRASE /* Bl -column phrase */
+};
+
+/*
+ * 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
+};
+
+const struct mdoc_macro *mdoc_macro(enum roff_tok);
+
+void mdoc_elem_alloc(struct roff_man *, int, int,
+ enum roff_tok, struct mdoc_arg *);
+struct roff_node *mdoc_block_alloc(struct roff_man *, int, int,
+ enum roff_tok, struct mdoc_arg *);
+void mdoc_tail_alloc(struct roff_man *, int, int,
+ enum roff_tok);
+struct roff_node *mdoc_endbody_alloc(struct roff_man *, int, int,
+ enum roff_tok, struct roff_node *);
+void mdoc_state(struct roff_man *, struct roff_node *);
+const char *mdoc_a2arch(const char *);
+const char *mdoc_a2att(const char *);
+enum roff_sec mdoc_a2sec(const char *);
+const char *mdoc_a2st(const char *);
+void mdoc_argv(struct roff_man *, int, enum roff_tok,
+ struct mdoc_arg **, int *, char *);
+enum margserr mdoc_args(struct roff_man *, int,
+ int *, char *, enum roff_tok, char **);
+enum mdelim mdoc_isdelim(const char *);
diff --git a/usr.bin/mandoc/main.c b/usr.bin/mandoc/main.c
new file mode 100644
index 0000000..6f2174a
--- /dev/null
+++ b/usr.bin/mandoc/main.c
@@ -0,0 +1,1255 @@
+/* $OpenBSD: main.c,v 1.251 2020/04/02 22:10:27 schwarze Exp $ */
+/*
+ * Copyright (c) 2010-2012, 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * 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 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.
+ *
+ * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1).
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/param.h> /* MACHINE */
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "mandoc_xr.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "man.h"
+#include "mandoc_parse.h"
+#include "tag.h"
+#include "term_tag.h"
+#include "main.h"
+#include "manconf.h"
+#include "mansearch.h"
+
+#define BINM_APROPOS "apropos"
+#define BINM_MAN "man"
+#define BINM_MAKEWHATIS "makewhatis"
+#define BINM_WHATIS "whatis"
+#define OSENUM MANDOC_OS_OPENBSD
+
+enum outmode {
+ OUTMODE_DEF = 0,
+ OUTMODE_FLN,
+ OUTMODE_LST,
+ OUTMODE_ALL,
+ OUTMODE_ONE
+};
+
+enum outt {
+ OUTT_ASCII = 0, /* -Tascii */
+ OUTT_LOCALE, /* -Tlocale */
+ OUTT_UTF8, /* -Tutf8 */
+ OUTT_TREE, /* -Ttree */
+ OUTT_MAN, /* -Tman */
+ OUTT_HTML, /* -Thtml */
+ OUTT_MARKDOWN, /* -Tmarkdown */
+ OUTT_LINT, /* -Tlint */
+ OUTT_PS, /* -Tps */
+ OUTT_PDF /* -Tpdf */
+};
+
+struct outstate {
+ struct tag_files *tag_files; /* Tagging state variables. */
+ void *outdata; /* data for output */
+ int use_pager;
+ int wstop; /* stop after a file with a warning */
+ int had_output; /* Some output was generated. */
+ enum outt outtype; /* which output to use */
+};
+
+
+int mandocdb(int, char *[]);
+
+static void check_xr(void);
+static int fs_lookup(const struct manpaths *,
+ size_t ipath, const char *,
+ const char *, const char *,
+ struct manpage **, size_t *);
+static int fs_search(const struct mansearch *,
+ const struct manpaths *, const char *,
+ struct manpage **, size_t *);
+static void glob_esc(char **, const char *, const char *);
+static void outdata_alloc(struct outstate *, struct manoutput *);
+static void parse(struct mparse *, int, const char *,
+ struct outstate *, struct manoutput *);
+static void passthrough(int, int);
+static void process_onefile(struct mparse *, struct manpage *,
+ int, struct outstate *, struct manconf *);
+static void run_pager(struct tag_files *, char *);
+static pid_t spawn_pager(struct tag_files *, char *);
+static void usage(enum argmode) __attribute__((__noreturn__));
+static int woptions(char *, enum mandoc_os *, int *);
+
+static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
+static char help_arg[] = "help";
+static char *help_argv[] = {help_arg, NULL};
+
+
+int
+main(int argc, char *argv[])
+{
+ struct manconf conf; /* Manpaths and output options. */
+ struct outstate outst; /* Output state. */
+ struct winsize ws; /* Result of ioctl(TIOCGWINSZ). */
+ struct mansearch search; /* Search options. */
+ struct manpage *res; /* Complete list of search results. */
+ struct manpage *resn; /* Search results for one name. */
+ struct mparse *mp; /* Opaque parser object. */
+ const char *conf_file; /* -C: alternate config file. */
+ const char *os_s; /* -I: Operating system for display. */
+ const char *progname, *sec;
+ char *defpaths; /* -M: override manpaths. */
+ char *auxpaths; /* -m: additional manpaths. */
+ char *oarg; /* -O: output option string. */
+ char *tagarg; /* -O tag: default value. */
+ unsigned char *uc;
+ size_t ressz; /* Number of elements in res[]. */
+ size_t resnsz; /* Number of elements in resn[]. */
+ size_t i, ib, ssz;
+ int options; /* Parser options. */
+ int show_usage; /* Invalid argument: give up. */
+ int prio, best_prio;
+ int startdir;
+ int c;
+ enum mandoc_os os_e; /* Check base system conventions. */
+ enum outmode outmode; /* According to command line. */
+
+ progname = getprogname();
+ mandoc_msg_setoutfile(stderr);
+ if (strncmp(progname, "mandocdb", 8) == 0 ||
+ strcmp(progname, BINM_MAKEWHATIS) == 0)
+ return mandocdb(argc, argv);
+
+ if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) {
+ mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
+ return mandoc_msg_getrc();
+ }
+
+ /* Search options. */
+
+ memset(&conf, 0, sizeof(conf));
+ conf_file = NULL;
+ defpaths = auxpaths = NULL;
+
+ memset(&search, 0, sizeof(struct mansearch));
+ search.outkey = "Nd";
+ oarg = NULL;
+
+ if (strcmp(progname, BINM_MAN) == 0)
+ search.argmode = ARG_NAME;
+ else if (strcmp(progname, BINM_APROPOS) == 0)
+ search.argmode = ARG_EXPR;
+ else if (strcmp(progname, BINM_WHATIS) == 0)
+ search.argmode = ARG_WORD;
+ else if (strncmp(progname, "help", 4) == 0)
+ search.argmode = ARG_NAME;
+ else
+ search.argmode = ARG_FILE;
+
+ /* Parser options. */
+
+ options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
+ os_e = MANDOC_OS_OTHER;
+ os_s = NULL;
+
+ /* Formatter options. */
+
+ memset(&outst, 0, sizeof(outst));
+ outst.tag_files = NULL;
+ outst.outtype = OUTT_LOCALE;
+ outst.use_pager = 1;
+
+ show_usage = 0;
+ outmode = OUTMODE_DEF;
+
+ while ((c = getopt(argc, argv,
+ "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
+ if (c == 'i' && search.argmode == ARG_EXPR) {
+ optind--;
+ break;
+ }
+ switch (c) {
+ case 'a':
+ outmode = OUTMODE_ALL;
+ break;
+ case 'C':
+ conf_file = optarg;
+ break;
+ case 'c':
+ outst.use_pager = 0;
+ break;
+ case 'f':
+ search.argmode = ARG_WORD;
+ break;
+ case 'h':
+ conf.output.synopsisonly = 1;
+ outst.use_pager = 0;
+ outmode = OUTMODE_ALL;
+ break;
+ case 'I':
+ if (strncmp(optarg, "os=", 3) != 0) {
+ mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
+ "-I %s", optarg);
+ return mandoc_msg_getrc();
+ }
+ if (os_s != NULL) {
+ mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0,
+ "-I %s", optarg);
+ return mandoc_msg_getrc();
+ }
+ os_s = optarg + 3;
+ break;
+ case 'K':
+ options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
+ if (strcmp(optarg, "utf-8") == 0)
+ options |= MPARSE_UTF8;
+ else if (strcmp(optarg, "iso-8859-1") == 0)
+ options |= MPARSE_LATIN1;
+ else if (strcmp(optarg, "us-ascii") != 0) {
+ mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
+ "-K %s", optarg);
+ return mandoc_msg_getrc();
+ }
+ break;
+ case 'k':
+ search.argmode = ARG_EXPR;
+ break;
+ case 'l':
+ search.argmode = ARG_FILE;
+ outmode = OUTMODE_ALL;
+ break;
+ case 'M':
+ defpaths = optarg;
+ break;
+ case 'm':
+ auxpaths = optarg;
+ break;
+ case 'O':
+ oarg = optarg;
+ break;
+ case 'S':
+ search.arch = optarg;
+ break;
+ case 's':
+ search.sec = optarg;
+ break;
+ case 'T':
+ if (strcmp(optarg, "ascii") == 0)
+ outst.outtype = OUTT_ASCII;
+ else if (strcmp(optarg, "lint") == 0) {
+ outst.outtype = OUTT_LINT;
+ mandoc_msg_setoutfile(stdout);
+ mandoc_msg_setmin(MANDOCERR_BASE);
+ } else if (strcmp(optarg, "tree") == 0)
+ outst.outtype = OUTT_TREE;
+ else if (strcmp(optarg, "man") == 0)
+ outst.outtype = OUTT_MAN;
+ else if (strcmp(optarg, "html") == 0)
+ outst.outtype = OUTT_HTML;
+ else if (strcmp(optarg, "markdown") == 0)
+ outst.outtype = OUTT_MARKDOWN;
+ else if (strcmp(optarg, "utf8") == 0)
+ outst.outtype = OUTT_UTF8;
+ else if (strcmp(optarg, "locale") == 0)
+ outst.outtype = OUTT_LOCALE;
+ else if (strcmp(optarg, "ps") == 0)
+ outst.outtype = OUTT_PS;
+ else if (strcmp(optarg, "pdf") == 0)
+ outst.outtype = OUTT_PDF;
+ else {
+ mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
+ "-T %s", optarg);
+ return mandoc_msg_getrc();
+ }
+ break;
+ case 'W':
+ if (woptions(optarg, &os_e, &outst.wstop) == -1)
+ return mandoc_msg_getrc();
+ break;
+ case 'w':
+ outmode = OUTMODE_FLN;
+ break;
+ default:
+ show_usage = 1;
+ break;
+ }
+ }
+
+ if (show_usage)
+ usage(search.argmode);
+
+ /* Postprocess options. */
+
+ switch (outmode) {
+ case OUTMODE_DEF:
+ switch (search.argmode) {
+ case ARG_FILE:
+ outmode = OUTMODE_ALL;
+ outst.use_pager = 0;
+ break;
+ case ARG_NAME:
+ outmode = OUTMODE_ONE;
+ break;
+ default:
+ outmode = OUTMODE_LST;
+ break;
+ }
+ break;
+ case OUTMODE_FLN:
+ if (search.argmode == ARG_FILE)
+ outmode = OUTMODE_ALL;
+ break;
+ case OUTMODE_ALL:
+ break;
+ case OUTMODE_LST:
+ case OUTMODE_ONE:
+ abort();
+ }
+
+ if (oarg != NULL) {
+ if (outmode == OUTMODE_LST)
+ search.outkey = oarg;
+ else {
+ while (oarg != NULL) {
+ if (manconf_output(&conf.output,
+ strsep(&oarg, ","), 0) == -1)
+ return mandoc_msg_getrc();
+ }
+ }
+ }
+
+ if (outst.outtype != OUTT_TREE || conf.output.noval == 0)
+ options |= MPARSE_VALIDATE;
+
+ if (outmode == OUTMODE_FLN ||
+ outmode == OUTMODE_LST ||
+ !isatty(STDOUT_FILENO))
+ outst.use_pager = 0;
+
+ if (outst.use_pager &&
+ (conf.output.width == 0 || conf.output.indent == 0) &&
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
+ ws.ws_col > 1) {
+ if (conf.output.width == 0 && ws.ws_col < 79)
+ conf.output.width = ws.ws_col - 1;
+ if (conf.output.indent == 0 && ws.ws_col < 66)
+ conf.output.indent = 3;
+ }
+
+ if (outst.use_pager == 0) {
+ if (pledge("stdio rpath", NULL) == -1) {
+ mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
+ "%s", strerror(errno));
+ return mandoc_msg_getrc();
+ }
+ }
+
+ /* Parse arguments. */
+
+ if (argc > 0) {
+ argc -= optind;
+ argv += optind;
+ }
+
+ /*
+ * Quirks for help(1) and man(1),
+ * in particular for a section argument without -s.
+ */
+
+ if (search.argmode == ARG_NAME) {
+ if (*progname == 'h') {
+ if (argc == 0) {
+ argv = help_argv;
+ argc = 1;
+ }
+ } else if (argc > 1 &&
+ ((uc = (unsigned char *)argv[0]) != NULL) &&
+ ((isdigit(uc[0]) && (uc[1] == '\0' ||
+ isalpha(uc[1]))) ||
+ (uc[0] == 'n' && uc[1] == '\0'))) {
+ search.sec = (char *)uc;
+ argv++;
+ argc--;
+ }
+ if (search.arch == NULL)
+ search.arch = getenv("MACHINE");
+#ifdef MACHINE
+ if (search.arch == NULL)
+ search.arch = MACHINE;
+#endif
+ if (outmode == OUTMODE_ONE)
+ search.firstmatch = 1;
+ }
+
+ /*
+ * Use the first argument for -O tag in addition to
+ * using it as a search term for man(1) or apropos(1).
+ */
+
+ if (conf.output.tag != NULL && *conf.output.tag == '\0') {
+ tagarg = argc > 0 && search.argmode == ARG_EXPR ?
+ strchr(*argv, '=') : NULL;
+ conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
+ }
+
+ /* Read the configuration file. */
+
+ if (search.argmode != ARG_FILE)
+ manconf_parse(&conf, conf_file, defpaths, auxpaths);
+
+ /* man(1): Resolve each name individually. */
+
+ if (search.argmode == ARG_NAME) {
+ if (argc < 1) {
+ if (outmode != OUTMODE_FLN)
+ usage(ARG_NAME);
+ if (conf.manpath.sz == 0) {
+ warnx("The manpath is empty.");
+ mandoc_msg_setrc(MANDOCLEVEL_BADARG);
+ } else {
+ for (i = 0; i + 1 < conf.manpath.sz; i++)
+ printf("%s:", conf.manpath.paths[i]);
+ printf("%s\n", conf.manpath.paths[i]);
+ }
+ manconf_free(&conf);
+ return (int)mandoc_msg_getrc();
+ }
+ for (res = NULL, ressz = 0; argc > 0; argc--, argv++) {
+ (void)mansearch(&search, &conf.manpath,
+ 1, argv, &resn, &resnsz);
+ if (resnsz == 0)
+ (void)fs_search(&search, &conf.manpath,
+ *argv, &resn, &resnsz);
+ if (resnsz == 0 && strchr(*argv, '/') == NULL) {
+ if (search.arch != NULL &&
+ arch_valid(search.arch, OSENUM) == 0)
+ warnx("Unknown architecture \"%s\".",
+ search.arch);
+ else if (search.sec != NULL)
+ warnx("No entry for %s in "
+ "section %s of the manual.",
+ *argv, search.sec);
+ else
+ warnx("No entry for %s in "
+ "the manual.", *argv);
+ mandoc_msg_setrc(MANDOCLEVEL_BADARG);
+ continue;
+ }
+ if (resnsz == 0) {
+ if (access(*argv, R_OK) == -1) {
+ mandoc_msg_setinfilename(*argv);
+ mandoc_msg(MANDOCERR_BADARG_BAD,
+ 0, 0, "%s", strerror(errno));
+ mandoc_msg_setinfilename(NULL);
+ continue;
+ }
+ resnsz = 1;
+ resn = mandoc_calloc(resnsz, sizeof(*res));
+ resn->file = mandoc_strdup(*argv);
+ resn->ipath = SIZE_MAX;
+ resn->form = FORM_SRC;
+ }
+ if (outmode != OUTMODE_ONE || resnsz == 1) {
+ res = mandoc_reallocarray(res,
+ ressz + resnsz, sizeof(*res));
+ memcpy(res + ressz, resn,
+ sizeof(*resn) * resnsz);
+ ressz += resnsz;
+ continue;
+ }
+
+ /* Search for the best section. */
+
+ best_prio = 40;
+ for (ib = i = 0; i < resnsz; i++) {
+ sec = resn[i].file;
+ sec += strcspn(sec, "123456789");
+ if (sec[0] == '\0')
+ continue; /* No section at all. */
+ prio = sec_prios[sec[0] - '1'];
+ if (search.sec != NULL) {
+ ssz = strlen(search.sec);
+ if (strncmp(sec, search.sec, ssz) == 0)
+ sec += ssz;
+ } else
+ sec++; /* Prefer without suffix. */
+ if (*sec != '/')
+ prio += 10; /* Wrong dir name. */
+ if (search.sec != NULL &&
+ (strlen(sec) <= ssz + 3 ||
+ strcmp(sec + strlen(sec) - ssz,
+ search.sec) != 0))
+ prio += 20; /* Wrong file ext. */
+ if (prio >= best_prio)
+ continue;
+ best_prio = prio;
+ ib = i;
+ }
+ res = mandoc_reallocarray(res, ressz + 1,
+ sizeof(*res));
+ memcpy(res + ressz++, resn + ib, sizeof(*resn));
+ }
+
+ /* apropos(1), whatis(1): Process the full search expression. */
+
+ } else if (search.argmode != ARG_FILE) {
+ if (mansearch(&search, &conf.manpath,
+ argc, argv, &res, &ressz) == 0)
+ usage(search.argmode);
+
+ if (ressz == 0) {
+ warnx("nothing appropriate");
+ mandoc_msg_setrc(MANDOCLEVEL_BADARG);
+ goto out;
+ }
+
+ /* mandoc(1): Take command line arguments as file names. */
+
+ } else {
+ ressz = argc > 0 ? argc : 1;
+ res = mandoc_calloc(ressz, sizeof(*res));
+ for (i = 0; i < ressz; i++) {
+ if (argc > 0)
+ res[i].file = mandoc_strdup(argv[i]);
+ res[i].ipath = SIZE_MAX;
+ res[i].form = FORM_SRC;
+ }
+ }
+
+ switch (outmode) {
+ case OUTMODE_FLN:
+ for (i = 0; i < ressz; i++)
+ puts(res[i].file);
+ goto out;
+ case OUTMODE_LST:
+ for (i = 0; i < ressz; i++)
+ printf("%s - %s\n", res[i].names,
+ res[i].output == NULL ? "" :
+ res[i].output);
+ goto out;
+ default:
+ break;
+ }
+
+ if (search.argmode == ARG_FILE && auxpaths != NULL) {
+ if (strcmp(auxpaths, "doc") == 0)
+ options |= MPARSE_MDOC;
+ else if (strcmp(auxpaths, "an") == 0)
+ options |= MPARSE_MAN;
+ }
+
+ mchars_alloc();
+ mp = mparse_alloc(options, os_e, os_s);
+
+ /*
+ * Remember the original working directory, if possible.
+ * This will be needed if some names on the command line
+ * are page names and some are relative file names.
+ * Do not error out if the current directory is not
+ * readable: Maybe it won't be needed after all.
+ */
+ startdir = open(".", O_RDONLY | O_DIRECTORY);
+ for (i = 0; i < ressz; i++) {
+ process_onefile(mp, res + i, startdir, &outst, &conf);
+ if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
+ break;
+ }
+ if (startdir != -1) {
+ (void)fchdir(startdir);
+ close(startdir);
+ }
+ if (conf.output.tag != NULL && conf.output.tag_found == 0) {
+ mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag);
+ conf.output.tag = NULL;
+ }
+ if (outst.outdata != NULL) {
+ switch (outst.outtype) {
+ case OUTT_HTML:
+ html_free(outst.outdata);
+ break;
+ case OUTT_UTF8:
+ case OUTT_LOCALE:
+ case OUTT_ASCII:
+ ascii_free(outst.outdata);
+ break;
+ case OUTT_PDF:
+ case OUTT_PS:
+ pspdf_free(outst.outdata);
+ break;
+ default:
+ break;
+ }
+ }
+ mandoc_xr_free();
+ mparse_free(mp);
+ mchars_free();
+
+out:
+ mansearch_free(res, ressz);
+ if (search.argmode != ARG_FILE)
+ manconf_free(&conf);
+
+ if (outst.tag_files != NULL) {
+ if (term_tag_close() != -1)
+ run_pager(outst.tag_files, conf.output.tag);
+ term_tag_unlink();
+ } else if (outst.had_output && outst.outtype != OUTT_LINT)
+ mandoc_msg_summary();
+
+ return (int)mandoc_msg_getrc();
+}
+
+static void
+usage(enum argmode argmode)
+{
+ switch (argmode) {
+ case ARG_FILE:
+ fputs("usage: mandoc [-ac] [-I os=name] "
+ "[-K encoding] [-mdoc | -man] [-O options]\n"
+ "\t [-T output] [-W level] [file ...]\n", stderr);
+ break;
+ case ARG_NAME:
+ fputs("usage: man [-acfhklw] [-C file] [-M path] "
+ "[-m path] [-S subsection]\n"
+ "\t [[-s] section] name ...\n", stderr);
+ break;
+ case ARG_WORD:
+ fputs("usage: whatis [-afk] [-C file] "
+ "[-M path] [-m path] [-O outkey] [-S arch]\n"
+ "\t [-s section] name ...\n", stderr);
+ break;
+ case ARG_EXPR:
+ fputs("usage: apropos [-afk] [-C file] "
+ "[-M path] [-m path] [-O outkey] [-S arch]\n"
+ "\t [-s section] expression ...\n", stderr);
+ break;
+ }
+ exit((int)MANDOCLEVEL_BADARG);
+}
+
+static void
+glob_esc(char **dst, const char *src, const char *suffix)
+{
+ while (*src != '\0') {
+ if (strchr("*?[", *src) != NULL)
+ *(*dst)++ = '\\';
+ *(*dst)++ = *src++;
+ }
+ while (*suffix != '\0')
+ *(*dst)++ = *suffix++;
+}
+
+static int
+fs_lookup(const struct manpaths *paths, size_t ipath,
+ const char *sec, const char *arch, const char *name,
+ struct manpage **res, size_t *ressz)
+{
+ struct stat sb;
+ glob_t globinfo;
+ struct manpage *page;
+ char *file, *cp;
+ int globres;
+ enum form form;
+
+ const char *const slman = "/man";
+ const char *const slash = "/";
+ const char *const sglob = ".[01-9]*";
+
+ form = FORM_SRC;
+ mandoc_asprintf(&file, "%s/man%s/%s.%s",
+ paths->paths[ipath], sec, name, sec);
+ if (stat(file, &sb) != -1)
+ goto found;
+ free(file);
+
+ mandoc_asprintf(&file, "%s/cat%s/%s.0",
+ paths->paths[ipath], sec, name);
+ if (stat(file, &sb) != -1) {
+ form = FORM_CAT;
+ goto found;
+ }
+ free(file);
+
+ if (arch != NULL) {
+ mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
+ paths->paths[ipath], sec, arch, name, sec);
+ if (stat(file, &sb) != -1)
+ goto found;
+ free(file);
+ }
+
+ cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
+ strlen(slman) + strlen(sec) * 2 + strlen(slash) +
+ strlen(name) * 2 + strlen(sglob) + 1);
+ glob_esc(&cp, paths->paths[ipath], slman);
+ glob_esc(&cp, sec, slash);
+ glob_esc(&cp, name, sglob);
+ *cp = '\0';
+ globres = glob(file, 0, NULL, &globinfo);
+ if (globres != 0 && globres != GLOB_NOMATCH)
+ mandoc_msg(MANDOCERR_GLOB, 0, 0,
+ "%s: %s", file, strerror(errno));
+ free(file);
+ if (globres == 0)
+ file = mandoc_strdup(*globinfo.gl_pathv);
+ globfree(&globinfo);
+ if (globres == 0) {
+ if (stat(file, &sb) != -1)
+ goto found;
+ free(file);
+ }
+ if (res != NULL || ipath + 1 != paths->sz)
+ return -1;
+
+ mandoc_asprintf(&file, "%s.%s", name, sec);
+ globres = stat(file, &sb);
+ free(file);
+ return globres;
+
+found:
+ warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
+ name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
+ if (res == NULL) {
+ free(file);
+ return 0;
+ }
+ *res = mandoc_reallocarray(*res, ++*ressz, sizeof(**res));
+ page = *res + (*ressz - 1);
+ page->file = file;
+ page->names = NULL;
+ page->output = NULL;
+ page->bits = NAME_FILE & NAME_MASK;
+ page->ipath = ipath;
+ page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
+ page->form = form;
+ return 0;
+}
+
+static int
+fs_search(const struct mansearch *cfg, const struct manpaths *paths,
+ const char *name, struct manpage **res, size_t *ressz)
+{
+ const char *const sections[] =
+ {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
+ const size_t nsec = sizeof(sections)/sizeof(sections[0]);
+
+ size_t ipath, isec;
+
+ assert(cfg->argmode == ARG_NAME);
+ if (res != NULL)
+ *res = NULL;
+ *ressz = 0;
+ for (ipath = 0; ipath < paths->sz; ipath++) {
+ if (cfg->sec != NULL) {
+ if (fs_lookup(paths, ipath, cfg->sec, cfg->arch,
+ name, res, ressz) != -1 && cfg->firstmatch)
+ return 0;
+ } else {
+ for (isec = 0; isec < nsec; isec++)
+ if (fs_lookup(paths, ipath, sections[isec],
+ cfg->arch, name, res, ressz) != -1 &&
+ cfg->firstmatch)
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void
+process_onefile(struct mparse *mp, struct manpage *resp, int startdir,
+ struct outstate *outst, struct manconf *conf)
+{
+ int fd;
+
+ /*
+ * Changing directories is not needed in ARG_FILE mode.
+ * Do it on a best-effort basis. Even in case of
+ * failure, some functionality may still work.
+ */
+ if (resp->ipath != SIZE_MAX)
+ (void)chdir(conf->manpath.paths[resp->ipath]);
+ else if (startdir != -1)
+ (void)fchdir(startdir);
+
+ mandoc_msg_setinfilename(resp->file);
+ if (resp->file != NULL) {
+ if ((fd = mparse_open(mp, resp->file)) == -1) {
+ mandoc_msg(resp->ipath == SIZE_MAX ?
+ MANDOCERR_BADARG_BAD : MANDOCERR_OPEN,
+ 0, 0, "%s", strerror(errno));
+ mandoc_msg_setinfilename(NULL);
+ return;
+ }
+ } else
+ fd = STDIN_FILENO;
+
+ if (outst->use_pager) {
+ outst->use_pager = 0;
+ outst->tag_files = term_tag_init();
+ }
+ if (outst->had_output && outst->outtype <= OUTT_UTF8) {
+ if (outst->outdata == NULL)
+ outdata_alloc(outst, &conf->output);
+ terminal_sepline(outst->outdata);
+ }
+
+ if (resp->form == FORM_SRC)
+ parse(mp, fd, resp->file, outst, &conf->output);
+ else {
+ passthrough(fd, conf->output.synopsisonly);
+ outst->had_output = 1;
+ }
+
+ if (ferror(stdout)) {
+ if (outst->tag_files != NULL) {
+ mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s",
+ outst->tag_files->ofn, strerror(errno));
+ term_tag_unlink();
+ outst->tag_files = NULL;
+ } else
+ mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s",
+ strerror(errno));
+ }
+ mandoc_msg_setinfilename(NULL);
+}
+
+static void
+parse(struct mparse *mp, int fd, const char *file,
+ struct outstate *outst, struct manoutput *outconf)
+{
+ static int previous;
+ struct roff_meta *meta;
+
+ assert(fd >= 0);
+ if (file == NULL)
+ file = "<stdin>";
+
+ if (previous)
+ mparse_reset(mp);
+ else
+ previous = 1;
+
+ mparse_readfd(mp, fd, file);
+ if (fd != STDIN_FILENO)
+ close(fd);
+
+ /*
+ * With -Wstop and warnings or errors of at least the requested
+ * level, do not produce output.
+ */
+
+ if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
+ return;
+
+ if (outst->outdata == NULL)
+ outdata_alloc(outst, outconf);
+ else if (outst->outtype == OUTT_HTML)
+ html_reset(outst);
+
+ mandoc_xr_reset();
+ meta = mparse_result(mp);
+
+ /* Execute the out device, if it exists. */
+
+ outst->had_output = 1;
+ if (meta->macroset == MACROSET_MDOC) {
+ switch (outst->outtype) {
+ case OUTT_HTML:
+ html_mdoc(outst->outdata, meta);
+ break;
+ case OUTT_TREE:
+ tree_mdoc(outst->outdata, meta);
+ break;
+ case OUTT_MAN:
+ man_mdoc(outst->outdata, meta);
+ break;
+ case OUTT_PDF:
+ case OUTT_ASCII:
+ case OUTT_UTF8:
+ case OUTT_LOCALE:
+ case OUTT_PS:
+ terminal_mdoc(outst->outdata, meta);
+ break;
+ case OUTT_MARKDOWN:
+ markdown_mdoc(outst->outdata, meta);
+ break;
+ default:
+ break;
+ }
+ }
+ if (meta->macroset == MACROSET_MAN) {
+ switch (outst->outtype) {
+ case OUTT_HTML:
+ html_man(outst->outdata, meta);
+ break;
+ case OUTT_TREE:
+ tree_man(outst->outdata, meta);
+ break;
+ case OUTT_MAN:
+ mparse_copy(mp);
+ break;
+ case OUTT_PDF:
+ case OUTT_ASCII:
+ case OUTT_UTF8:
+ case OUTT_LOCALE:
+ case OUTT_PS:
+ terminal_man(outst->outdata, meta);
+ break;
+ default:
+ break;
+ }
+ }
+ if (outconf->tag != NULL && outconf->tag_found == 0 &&
+ tag_exists(outconf->tag))
+ outconf->tag_found = 1;
+ if (mandoc_msg_getmin() < MANDOCERR_STYLE)
+ check_xr();
+}
+
+static void
+check_xr(void)
+{
+ static struct manpaths paths;
+ struct mansearch search;
+ struct mandoc_xr *xr;
+ size_t sz;
+
+ if (paths.sz == 0)
+ manpath_base(&paths);
+
+ for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
+ if (xr->line == -1)
+ continue;
+ search.arch = NULL;
+ search.sec = xr->sec;
+ search.outkey = NULL;
+ search.argmode = ARG_NAME;
+ search.firstmatch = 1;
+ if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz))
+ continue;
+ if (fs_search(&search, &paths, xr->name, NULL, &sz) != -1)
+ continue;
+ if (xr->count == 1)
+ mandoc_msg(MANDOCERR_XR_BAD, xr->line,
+ xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
+ else
+ mandoc_msg(MANDOCERR_XR_BAD, xr->line,
+ xr->pos + 1, "Xr %s %s (%d times)",
+ xr->name, xr->sec, xr->count);
+ }
+}
+
+static void
+outdata_alloc(struct outstate *outst, struct manoutput *outconf)
+{
+ switch (outst->outtype) {
+ case OUTT_HTML:
+ outst->outdata = html_alloc(outconf);
+ break;
+ case OUTT_UTF8:
+ outst->outdata = utf8_alloc(outconf);
+ break;
+ case OUTT_LOCALE:
+ outst->outdata = locale_alloc(outconf);
+ break;
+ case OUTT_ASCII:
+ outst->outdata = ascii_alloc(outconf);
+ break;
+ case OUTT_PDF:
+ outst->outdata = pdf_alloc(outconf);
+ break;
+ case OUTT_PS:
+ outst->outdata = ps_alloc(outconf);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+passthrough(int fd, int synopsis_only)
+{
+ const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
+ const char synr[] = "SYNOPSIS";
+
+ FILE *stream;
+ char *line, *cp;
+ size_t linesz;
+ ssize_t len, written;
+ int lno, print;
+
+ stream = NULL;
+ line = NULL;
+ linesz = 0;
+
+ if (fflush(stdout) == EOF) {
+ mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno));
+ goto done;
+ }
+ if ((stream = fdopen(fd, "r")) == NULL) {
+ close(fd);
+ mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
+ goto done;
+ }
+
+ lno = print = 0;
+ while ((len = getline(&line, &linesz, stream)) != -1) {
+ lno++;
+ cp = line;
+ if (synopsis_only) {
+ if (print) {
+ if ( ! isspace((unsigned char)*cp))
+ goto done;
+ while (isspace((unsigned char)*cp)) {
+ cp++;
+ len--;
+ }
+ } else {
+ if (strcmp(cp, synb) == 0 ||
+ strcmp(cp, synr) == 0)
+ print = 1;
+ continue;
+ }
+ }
+ for (; len > 0; len -= written) {
+ if ((written = write(STDOUT_FILENO, cp, len)) == -1) {
+ mandoc_msg(MANDOCERR_WRITE, 0, 0,
+ "%s", strerror(errno));
+ goto done;
+ }
+ }
+ }
+ if (ferror(stream))
+ mandoc_msg(MANDOCERR_GETLINE, lno, 0, "%s", strerror(errno));
+
+done:
+ free(line);
+ if (stream != NULL)
+ fclose(stream);
+}
+
+static int
+woptions(char *arg, enum mandoc_os *os_e, int *wstop)
+{
+ char *v, *o;
+ const char *toks[11];
+
+ toks[0] = "stop";
+ toks[1] = "all";
+ toks[2] = "base";
+ toks[3] = "style";
+ toks[4] = "warning";
+ toks[5] = "error";
+ toks[6] = "unsupp";
+ toks[7] = "fatal";
+ toks[8] = "openbsd";
+ toks[9] = "netbsd";
+ toks[10] = NULL;
+
+ while (*arg) {
+ o = arg;
+ switch (getsubopt(&arg, (char * const *)toks, &v)) {
+ case 0:
+ *wstop = 1;
+ break;
+ case 1:
+ case 2:
+ mandoc_msg_setmin(MANDOCERR_BASE);
+ break;
+ case 3:
+ mandoc_msg_setmin(MANDOCERR_STYLE);
+ break;
+ case 4:
+ mandoc_msg_setmin(MANDOCERR_WARNING);
+ break;
+ case 5:
+ mandoc_msg_setmin(MANDOCERR_ERROR);
+ break;
+ case 6:
+ mandoc_msg_setmin(MANDOCERR_UNSUPP);
+ break;
+ case 7:
+ mandoc_msg_setmin(MANDOCERR_BADARG);
+ break;
+ case 8:
+ mandoc_msg_setmin(MANDOCERR_BASE);
+ *os_e = MANDOC_OS_OPENBSD;
+ break;
+ case 9:
+ mandoc_msg_setmin(MANDOCERR_BASE);
+ *os_e = MANDOC_OS_NETBSD;
+ break;
+ default:
+ mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Wait until moved to the foreground,
+ * then fork the pager and wait for the user to close it.
+ */
+static void
+run_pager(struct tag_files *tag_files, char *tag_target)
+{
+ int signum, status;
+ pid_t man_pgid, tc_pgid;
+ pid_t pager_pid, wait_pid;
+
+ man_pgid = getpgid(0);
+ tag_files->tcpgid = man_pgid == getpid() ? getpgid(getppid()) :
+ man_pgid;
+ pager_pid = 0;
+ signum = SIGSTOP;
+
+ for (;;) {
+ /* Stop here until moved to the foreground. */
+
+ tc_pgid = tcgetpgrp(STDOUT_FILENO);
+ if (tc_pgid != man_pgid) {
+ if (tc_pgid == pager_pid) {
+ (void)tcsetpgrp(STDOUT_FILENO, man_pgid);
+ if (signum == SIGTTIN)
+ continue;
+ } else
+ tag_files->tcpgid = tc_pgid;
+ kill(0, signum);
+ continue;
+ }
+
+ /* Once in the foreground, activate the pager. */
+
+ if (pager_pid) {
+ (void)tcsetpgrp(STDOUT_FILENO, pager_pid);
+ kill(pager_pid, SIGCONT);
+ } else
+ pager_pid = spawn_pager(tag_files, tag_target);
+
+ /* Wait for the pager to stop or exit. */
+
+ while ((wait_pid = waitpid(pager_pid, &status,
+ WUNTRACED)) == -1 && errno == EINTR)
+ continue;
+
+ if (wait_pid == -1) {
+ mandoc_msg(MANDOCERR_WAIT, 0, 0,
+ "%s", strerror(errno));
+ break;
+ }
+ if (!WIFSTOPPED(status))
+ break;
+
+ signum = WSTOPSIG(status);
+ }
+}
+
+static pid_t
+spawn_pager(struct tag_files *tag_files, char *tag_target)
+{
+ const struct timespec timeout = { 0, 100000000 }; /* 0.1s */
+#define MAX_PAGER_ARGS 16
+ char *argv[MAX_PAGER_ARGS];
+ const char *pager;
+ char *cp;
+ int argc, use_ofn;
+ pid_t pager_pid;
+
+ assert(tag_files->ofd == -1);
+ assert(tag_files->tfs == NULL);
+
+ pager = getenv("MANPAGER");
+ if (pager == NULL || *pager == '\0')
+ pager = getenv("PAGER");
+ if (pager == NULL || *pager == '\0')
+ pager = "more -s";
+ cp = mandoc_strdup(pager);
+
+ /*
+ * Parse the pager command into words.
+ * Intentionally do not do anything fancy here.
+ */
+
+ argc = 0;
+ while (argc + 5 < MAX_PAGER_ARGS) {
+ argv[argc++] = cp;
+ cp = strchr(cp, ' ');
+ if (cp == NULL)
+ break;
+ *cp++ = '\0';
+ while (*cp == ' ')
+ cp++;
+ if (*cp == '\0')
+ break;
+ }
+
+ /* For more(1) and less(1), use the tag file. */
+
+ use_ofn = 1;
+ if (use_ofn)
+ argv[argc++] = tag_files->ofn;
+ argv[argc] = NULL;
+
+ switch (pager_pid = fork()) {
+ case -1:
+ mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno));
+ exit(mandoc_msg_getrc());
+ case 0:
+ break;
+ default:
+ (void)setpgid(pager_pid, 0);
+ (void)tcsetpgrp(STDOUT_FILENO, pager_pid);
+ if (pledge("stdio rpath tmppath tty proc", NULL) == -1) {
+ mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
+ "%s", strerror(errno));
+ exit(mandoc_msg_getrc());
+ }
+ tag_files->pager_pid = pager_pid;
+ return pager_pid;
+ }
+
+ /*
+ * The child process becomes the pager.
+ * Do not start it before controlling the terminal.
+ */
+
+ while (tcgetpgrp(STDOUT_FILENO) != getpid())
+ nanosleep(&timeout, NULL);
+
+ execvp(argv[0], argv);
+ mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno));
+ _exit(mandoc_msg_getrc());
+}
diff --git a/usr.bin/mandoc/main.h b/usr.bin/mandoc/main.h
new file mode 100644
index 0000000..ee8e10d
--- /dev/null
+++ b/usr.bin/mandoc/main.h
@@ -0,0 +1,53 @@
+/* $OpenBSD: main.h,v 1.25 2019/03/03 13:01:47 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2019 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.
+ */
+
+struct roff_meta;
+struct manoutput;
+
+/*
+ * 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(const struct manoutput *);
+void html_mdoc(void *, const struct roff_meta *);
+void html_man(void *, const struct roff_meta *);
+void html_reset(void *);
+void html_free(void *);
+
+void tree_mdoc(void *, const struct roff_meta *);
+void tree_man(void *, const struct roff_meta *);
+
+void man_mdoc(void *, const struct roff_meta *);
+
+void *locale_alloc(const struct manoutput *);
+void *utf8_alloc(const struct manoutput *);
+void *ascii_alloc(const struct manoutput *);
+void ascii_free(void *);
+
+void *pdf_alloc(const struct manoutput *);
+void *ps_alloc(const struct manoutput *);
+void pspdf_free(void *);
+
+void terminal_mdoc(void *, const struct roff_meta *);
+void terminal_man(void *, const struct roff_meta *);
+void terminal_sepline(void *);
+
+void markdown_mdoc(void *, const struct roff_meta *);
diff --git a/usr.bin/mandoc/makewhatis.8 b/usr.bin/mandoc/makewhatis.8
new file mode 100644
index 0000000..c42bcc4
--- /dev/null
+++ b/usr.bin/mandoc/makewhatis.8
@@ -0,0 +1,228 @@
+.\" $OpenBSD: makewhatis.8,v 1.14 2017/05/17 22:26:52 schwarze Exp $
+.\"
+.\" Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011, 2012, 2014, 2017 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: May 17 2017 $
+.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 a directory contains no manual pages, no database is created in that
+directory.
+If
+.Ar dir
+is not provided,
+.Nm
+uses the default paths stipulated by
+.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 keywords 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 .
+If that causes the database to become empty, also delete the database file.
+.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 ENVIRONMENT
+.Bl -tag -width MANPATH
+.It Ev MANPATH
+A colon-separated list of directories to create databases in.
+Ignored if a
+.Ar dir
+argument or the
+.Fl t
+option is specified.
+.El
+.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/usr.bin/mandoc/man.1 b/usr.bin/mandoc/man.1
new file mode 100644
index 0000000..2d4d33a
--- /dev/null
+++ b/usr.bin/mandoc/man.1
@@ -0,0 +1,431 @@
+.\" $OpenBSD: man.1,v 1.36 2020/02/10 13:49:04 schwarze Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 2003, 2007, 2008, 2014 Jason McIntyre <jmc@openbsd.org>
+.\" Copyright (c) 2010, 2011, 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" 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.
+.\"
+.\" @(#)man.1 8.2 (Berkeley) 1/2/94
+.\"
+.Dd $Mdocdate: February 10 2020 $
+.Dt MAN 1
+.Os
+.Sh NAME
+.Nm man
+.Nd display manual pages
+.Sh SYNOPSIS
+.Nm man
+.Op Fl acfhklw
+.Op Fl C Ar file
+.Op Fl M Ar path
+.Op Fl m Ar path
+.Op Fl S Ar subsection
+.Op Oo Fl s Oc Ar section
+.Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility
+displays the
+manual page entitled
+.Ar name .
+Pages may be selected according to
+a specific category
+.Pq Ar section
+or
+machine architecture
+.Pq Ar subsection .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Display all matching manual pages.
+.It Fl C Ar file
+Use the specified
+.Ar file
+instead of the default configuration file.
+This permits users to configure their own manual environment.
+See
+.Xr man.conf 5
+for a description of the contents of this file.
+.It Fl c
+Copy the manual page to the standard output instead of using
+.Xr more 1
+to paginate it.
+This is done by default if the standard output is not a terminal device.
+.Pp
+When using
+.Fl c ,
+most terminal devices are unable to show the markup.
+To print the output of
+.Nm
+to the terminal with markup but without using a pager, pipe it to
+.Xr ul 1 .
+To remove the markup, pipe the output to
+.Xr col 1
+.Fl b
+instead.
+.It Fl f
+A synonym for
+.Xr whatis 1 .
+It searches for
+.Ar name
+in manual page names and displays the header lines from all matching pages.
+The search is case insensitive and matches whole words only.
+.It Fl h
+Display only the SYNOPSIS lines of the requested manual pages.
+Implies
+.Fl a
+and
+.Fl c .
+.It Fl k
+A synonym for
+.Xr apropos 1 .
+Instead of
+.Ar name ,
+an expression can be provided using the syntax described in the
+.Xr apropos 1
+manual.
+By default, it displays the header lines of all matching pages.
+.It Fl l
+A synonym for
+.Xr mandoc 1 .
+The
+.Ar name
+arguments are interpreted as filenames.
+No search is done and
+.Ar file ,
+.Ar path ,
+.Ar section ,
+.Ar subsection ,
+and
+.Fl w
+are ignored.
+This option implies
+.Fl a .
+.It Fl M Ar path
+Override the list of directories to search for manual pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Ql \&:
+separated list of directories.
+This option also overrides the environment variable
+.Ev MANPATH
+and any directories specified in the
+.Xr man.conf 5
+file.
+.It Fl m Ar path
+Augment the list of directories to search for manual pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Ql \&:
+separated list of directories.
+These directories will be searched before those specified using the
+.Fl M
+option, the
+.Ev MANPATH
+environment variable, the
+.Xr man.conf 5
+file, or the default directories.
+.It Fl S Ar subsection
+Only show pages for the specified
+.Xr machine 1
+architecture.
+.Ar subsection
+is case insensitive.
+.Pp
+By default manual pages for all architectures are installed.
+Therefore this option can be used to view pages for one
+architecture whilst using another.
+.Pp
+This option overrides the
+.Ev MACHINE
+environment variable.
+.It Oo Fl s Oc Ar section
+Only select manuals from the specified
+.Ar section .
+The currently available sections are:
+.Pp
+.Bl -tag -width "localXXX" -offset indent -compact
+.It 1
+General commands
+.Pq tools and utilities .
+.It 2
+System calls and error numbers.
+.It 3
+Library functions.
+.It 3p
+.Xr perl 1
+programmer's reference guide.
+.It 4
+Device drivers.
+.It 5
+File formats.
+.It 6
+Games.
+.It 7
+Miscellaneous information.
+.It 8
+System maintenance and operation commands.
+.It 9
+Kernel internals.
+.El
+.It Fl w
+List the pathnames of all matching manual pages instead of displaying
+any of them.
+If no
+.Ar name
+is given, list the directories that would be searched.
+.El
+.Pp
+The options
+.Fl IKOTW
+are also supported and are documented in
+.Xr mandoc 1 .
+The options
+.Fl fkl
+are mutually exclusive and override each other.
+.Pp
+The search starts with the
+.Fl m
+argument if provided, then continues with the
+.Fl M
+argument, the
+.Ev MANPATH
+variable, the
+.Ic manpath
+entries in the
+.Xr man.conf 5
+file, or with
+.Pa /usr/share/man : Ns Pa /usr/X11R6/man : Ns Pa /usr/local/man
+by default.
+Within each of these, directories are searched in the order provided.
+Within each directory, the search proceeds according to the following
+list of sections: 1, 8, 6, 2, 3, 5, 7, 4, 9, 3p.
+The first match found is shown.
+.Pp
+The
+.Xr mandoc.db 5
+database is used for looking up manual page entries.
+In cases where the database is absent, outdated, or corrupt,
+.Nm
+falls back to looking for files called
+.Ar name . Ns Ar section .
+If both a formatted and an unformatted version of the same manual page,
+for example
+.Pa cat1/foo.0
+and
+.Pa man1/foo.1 ,
+exist in the same directory, only the unformatted version is used.
+The database is kept up to date with
+.Xr makewhatis 8 ,
+which is run by the
+.Xr weekly 8
+maintenance script.
+.Pp
+Guidelines for writing
+man pages can be found in
+.Xr mdoc 7 .
+.Sh ENVIRONMENT
+.Bl -tag -width MANPATHX
+.It Ev MACHINE
+As some manual pages are intended only for specific architectures,
+.Nm
+searches any subdirectories,
+with the same name as the current architecture,
+in every directory which it searches.
+Machine specific areas are checked before general areas.
+The current machine type may be overridden by setting the environment
+variable
+.Ev MACHINE
+to the name of a specific architecture,
+or with the
+.Fl S
+option.
+.Ev MACHINE
+is case insensitive.
+.It Ev MANPAGER
+Any non-empty value of the environment variable
+.Ev MANPAGER
+is used instead of the standard pagination program,
+.Xr more 1 .
+If
+.Xr less 1
+is used, the interactive
+.Ic :t
+command can be used to go to the definitions of various terms, for
+example command line options, command modifiers, internal commands,
+environment variables, function names, preprocessor macros,
+.Xr errno 2
+values, and some other emphasized words.
+Some terms may have defining text at more than one place.
+In that case, the
+.Xr less 1
+interactive commands
+.Ic t
+and
+.Ic T
+can be used to move to the next and to the previous place providing
+information about the term last searched for with
+.Ic :t .
+The
+.Fl O Cm tag Ns Op = Ns Ar term
+option documented in the
+.Xr mandoc 1
+manual opens a manual page at the definition of a specific
+.Ar term
+rather than at the beginning.
+.It Ev MANPATH
+Override the standard search path which is either specified in
+.Xr man.conf 5
+or the default path.
+The format of
+.Ev MANPATH
+is a colon
+.Pq Ql \&:
+separated list of directories.
+Invalid directories are ignored.
+Overridden by
+.Fl M ,
+ignored if
+.Fl l
+is specified.
+.Pp
+If
+.Ev MANPATH
+begins with a colon, it is appended to the standard path;
+if it ends with a colon, it is prepended to the standard path;
+or if it contains two adjacent colons,
+the standard path is inserted between the colons.
+.It Ev PAGER
+Specifies the pagination program to use when
+.Ev MANPAGER
+is not defined.
+If neither PAGER nor MANPAGER is defined,
+.Xr more 1
+.Fl s
+is used.
+.El
+.Sh FILES
+.Bl -tag -width /etc/man.conf -compact
+.It Pa /etc/man.conf
+default
+.Nm
+configuration file
+.El
+.Sh EXIT STATUS
+.Ex -std man
+See
+.Xr mandoc 1
+for details.
+.Sh EXAMPLES
+Format a page for pasting extracts into an email message \(em
+avoid printing any UTF-8 characters, reduce the width to ease
+quoting in replies, and remove markup:
+.Pp
+.Dl $ man -T ascii -O width=65 pledge | col -b
+.Pp
+Read a typeset page in a PDF viewer:
+.Pp
+.Dl $ MANPAGER=mupdf man -T pdf lpd
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr col 1 ,
+.Xr mandoc 1 ,
+.Xr ul 1 ,
+.Xr whereis 1 ,
+.Xr man.conf 5 ,
+.Xr mdoc 7
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl aCcfhIKlMmOSsTWw ,
+as well as the environment variables
+.Ev MACHINE ,
+.Ev MANPAGER ,
+and
+.Ev MANPATH ,
+are extensions to that specification.
+.Sh HISTORY
+A
+.Nm
+command first appeared in
+.At v2 .
+.Pp
+The
+.Fl w
+option first appeared in
+.At v7 ;
+.Fl f
+and
+.Fl k
+in
+.Bx 4 ;
+.Fl M
+in
+.Bx 4.3 ;
+.Fl a
+in
+.Bx 4.3 Tahoe ;
+.Fl c
+and
+.Fl m
+in
+.Bx 4.3 Reno ;
+.Fl h
+in
+.Bx 4.3 Net/2 ;
+.Fl C
+in
+.Nx 1.0 ;
+.Fl s
+and
+.Fl S
+in
+.Ox 2.3 ;
+and
+.Fl I ,
+.Fl K ,
+.Fl l ,
+.Fl O ,
+and
+.Fl W
+in
+.Ox 5.7 .
+The
+.Fl T
+option first appeared in
+.At III
+and was also added in
+.Ox 5.7 .
diff --git a/usr.bin/mandoc/man.c b/usr.bin/mandoc/man.c
new file mode 100644
index 0000000..934f2b3
--- /dev/null
+++ b/usr.bin/mandoc/man.c
@@ -0,0 +1,343 @@
+/* $OpenBSD: man.c,v 1.135 2019/01/05 00:36:46 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013-2015, 2017-2019 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 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "man.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libman.h"
+
+static char *man_hasc(char *);
+static int man_ptext(struct roff_man *, int, char *, int);
+static int man_pmacro(struct roff_man *, int, char *, int);
+
+
+int
+man_parseln(struct roff_man *man, int ln, char *buf, int offs)
+{
+
+ if (man->last->type != ROFFT_EQN || ln > man->last->line)
+ man->flags |= MAN_NEWLINE;
+
+ return roff_getcontrol(man->roff, buf, &offs) ?
+ man_pmacro(man, ln, buf, offs) :
+ man_ptext(man, ln, buf, offs);
+}
+
+/*
+ * If the string ends with \c, return a pointer to the backslash.
+ * Otherwise, return NULL.
+ */
+static char *
+man_hasc(char *start)
+{
+ char *cp, *ep;
+
+ ep = strchr(start, '\0') - 2;
+ if (ep < start || ep[0] != '\\' || ep[1] != 'c')
+ return NULL;
+ for (cp = ep; cp > start; cp--)
+ if (cp[-1] != '\\')
+ break;
+ return (ep - cp) % 2 ? NULL : ep;
+}
+
+void
+man_descope(struct roff_man *man, int line, int offs, char *start)
+{
+ /* Trailing \c keeps next-line scope open. */
+
+ if (start != NULL && man_hasc(start) != NULL)
+ return;
+
+ /*
+ * Co-ordinate what happens with having a next-line scope open:
+ * first close out the element scopes (if applicable),
+ * then close out the block scope (also if applicable).
+ */
+
+ if (man->flags & MAN_ELINE) {
+ while (man->last->parent->type != ROFFT_ROOT &&
+ man_macro(man->last->parent->tok)->flags & MAN_ESCOPED)
+ man_unscope(man, man->last->parent);
+ man->flags &= ~MAN_ELINE;
+ }
+ if ( ! (man->flags & MAN_BLINE))
+ return;
+ man_unscope(man, man->last->parent);
+ roff_body_alloc(man, line, offs, man->last->tok);
+ man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
+}
+
+static int
+man_ptext(struct roff_man *man, int line, char *buf, int offs)
+{
+ int i;
+ char *ep;
+
+ /* In no-fill mode, whitespace is preserved on text lines. */
+
+ if (man->flags & ROFF_NOFILL) {
+ roff_word_alloc(man, line, offs, buf + offs);
+ man_descope(man, line, offs, buf + offs);
+ return 1;
+ }
+
+ for (i = offs; buf[i] == ' '; i++)
+ /* Skip leading whitespace. */ ;
+
+ /*
+ * Blank lines are ignored in next line scope
+ * and right after headings and cancel preceding \c,
+ * but add a single vertical space elsewhere.
+ */
+
+ if (buf[i] == '\0') {
+ if (man->flags & (MAN_ELINE | MAN_BLINE)) {
+ mandoc_msg(MANDOCERR_BLK_BLANK, line, 0, NULL);
+ return 1;
+ }
+ if (man->last->tok == MAN_SH || man->last->tok == MAN_SS)
+ return 1;
+ if (man->last->type == ROFFT_TEXT &&
+ ((ep = man_hasc(man->last->string)) != NULL)) {
+ *ep = '\0';
+ return 1;
+ }
+ roff_elem_alloc(man, line, offs, ROFF_sp);
+ man->next = ROFF_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, 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';
+ }
+ roff_word_alloc(man, line, offs, buf + offs);
+
+ /*
+ * 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 |= NODE_EOS;
+
+ man_descope(man, line, offs, buf + offs);
+ return 1;
+}
+
+static int
+man_pmacro(struct roff_man *man, int ln, char *buf, int offs)
+{
+ struct roff_node *n;
+ const char *cp;
+ size_t sz;
+ enum roff_tok tok;
+ int ppos;
+ int bline;
+
+ /* Determine the line macro. */
+
+ ppos = offs;
+ tok = TOKEN_NONE;
+ for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++)
+ offs++;
+ if (sz > 0 && sz < 4)
+ tok = roffhash_find(man->manmac, buf + ppos, sz);
+ if (tok == TOKEN_NONE) {
+ mandoc_msg(MANDOCERR_MACRO, ln, ppos, "%s", buf + ppos - 1);
+ return 1;
+ }
+
+ /* Skip a leading escape sequence or tab. */
+
+ switch (buf[offs]) {
+ case '\\':
+ cp = buf + offs + 1;
+ mandoc_escape(&cp, NULL, NULL);
+ offs = cp - buf;
+ break;
+ case '\t':
+ offs++;
+ break;
+ default:
+ break;
+ }
+
+ /* Jump to the next non-whitespace word. */
+
+ while (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 (buf[offs] == '\0' && buf[offs - 1] == ' ')
+ mandoc_msg(MANDOCERR_SPACE_EOL, ln, offs - 1, NULL);
+
+ /*
+ * Some macros break next-line scopes; otherwise, remember
+ * whether we are in next-line scope for a block head.
+ */
+
+ man_breakscope(man, tok);
+ bline = man->flags & MAN_BLINE;
+
+ /*
+ * If the line in next-line scope ends with \c, keep the
+ * next-line scope open for the subsequent input line.
+ * That is not at all portable, only groff >= 1.22.4
+ * does it, but *if* this weird idiom occurs in a manual
+ * page, that's very likely what the author intended.
+ */
+
+ if (bline && man_hasc(buf + offs))
+ bline = 0;
+
+ /* Call to handler... */
+
+ (*man_macro(tok)->fp)(man, tok, ln, ppos, &offs, buf);
+
+ /* In quick mode (for mandocdb), abort after the NAME section. */
+
+ if (man->quick && tok == MAN_SH) {
+ n = man->last;
+ if (n->type == ROFFT_BODY &&
+ 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 == 0 ||
+ (man->flags & MAN_BLINE) == 0 ||
+ man->flags & MAN_ELINE ||
+ man_macro(tok)->flags & MAN_NSCOPED)
+ return 1;
+
+ man_unscope(man, man->last->parent);
+ roff_body_alloc(man, ln, ppos, man->last->tok);
+ man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
+ return 1;
+}
+
+void
+man_breakscope(struct roff_man *man, int tok)
+{
+ struct roff_node *n;
+
+ /*
+ * An element next line scope is open,
+ * and the new macro is not allowed inside elements.
+ * Delete the element that is being broken.
+ */
+
+ if (man->flags & MAN_ELINE && (tok < MAN_TH ||
+ (man_macro(tok)->flags & MAN_NSCOPED) == 0)) {
+ n = man->last;
+ if (n->type == ROFFT_TEXT)
+ n = n->parent;
+ if (n->tok < MAN_TH ||
+ (man_macro(n->tok)->flags & (MAN_NSCOPED | MAN_ESCOPED))
+ == MAN_NSCOPED)
+ n = n->parent;
+
+ mandoc_msg(MANDOCERR_BLK_LINE, n->line, n->pos,
+ "%s breaks %s", roff_name[tok], roff_name[n->tok]);
+
+ roff_node_delete(man, n);
+ man->flags &= ~MAN_ELINE;
+ }
+
+ /*
+ * Weird special case:
+ * Switching fill mode closes section headers.
+ */
+
+ if (man->flags & MAN_BLINE &&
+ (tok == ROFF_nf || tok == ROFF_fi) &&
+ (man->last->tok == MAN_SH || man->last->tok == MAN_SS)) {
+ n = man->last;
+ man_unscope(man, n);
+ roff_body_alloc(man, n->line, n->pos, n->tok);
+ man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
+ }
+
+ /*
+ * A block header next line scope is open,
+ * and the new macro is not allowed inside block headers.
+ * Delete the block that is being broken.
+ */
+
+ if (man->flags & MAN_BLINE && tok != ROFF_nf && tok != ROFF_fi &&
+ (tok < MAN_TH || man_macro(tok)->flags & MAN_XSCOPE)) {
+ n = man->last;
+ if (n->type == ROFFT_TEXT)
+ n = n->parent;
+ if (n->tok < MAN_TH ||
+ (man_macro(n->tok)->flags & MAN_XSCOPE) == 0)
+ n = n->parent;
+
+ assert(n->type == ROFFT_HEAD);
+ n = n->parent;
+ assert(n->type == ROFFT_BLOCK);
+ assert(man_macro(n->tok)->flags & MAN_BSCOPED);
+
+ mandoc_msg(MANDOCERR_BLK_LINE, n->line, n->pos,
+ "%s breaks %s", roff_name[tok], roff_name[n->tok]);
+
+ roff_node_delete(man, n);
+ man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
+ }
+}
diff --git a/usr.bin/mandoc/man.cgi.8 b/usr.bin/mandoc/man.cgi.8
new file mode 100644
index 0000000..a524f58
--- /dev/null
+++ b/usr.bin/mandoc/man.cgi.8
@@ -0,0 +1,426 @@
+.\" $OpenBSD: man.cgi.8,v 1.22 2018/05/20 21:48:23 schwarze Exp $
+.\"
+.\" Copyright (c) 2014, 2015, 2016 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: May 20 2018 $
+.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 man 1
+and
+.Xr apropos 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.
+.Pp
+The expression is broken into words at whitespace.
+Whitespace characters and backslashes can be escaped
+by prepending a backslash.
+The effect of prepending a backslash to another character is undefined;
+in the current implementation, it has no effect.
+.It
+A
+.Xr man 1
+submit button.
+The string in the input box is interpreted as the name of a manual page.
+.It
+An
+.Xr apropos 1
+submit button.
+The string in the input box is interpreted as a search
+.Ar expression .
+.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.
+For
+.Xr man 1
+style searches, the content of the first manual page follows the list.
+.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 -
+either 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
+.Ox
+.Xr httpd 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 Dv CSS_DIR
+An optional file system path to the directory containing the file
+.Pa mandoc.css ,
+to be specified relative to the server's document root,
+and to be specified without a trailing slash.
+When empty, the CSS file is assumed to be in the document root.
+Otherwise, a leading slash is needed.
+This is used in generated HTML code.
+.It Dv CUSTOMIZE_TITLE
+An ASCII string to be used for the HTML <TITLE> element.
+.It Dv MAN_DIR
+A file system path to the
+.Nm
+data directory relative to the web server
+.Xr chroot 2
+directory, to be specified with a leading slash and without a trailing slash.
+It needs to have at least one component; the root directory cannot be used
+for this purpose.
+The files
+.Pa manpath.conf ,
+.Pa header.html ,
+and
+.Pa footer.html
+are looked up in this directory.
+It is also prepended to the manpath when opening
+.Xr mandoc.db 5
+and manual page files.
+.It Dv SCRIPT_NAME
+The initial component of URIs, to be specified without leading
+and trailing slashes.
+It can be empty.
+.El
+.Pp
+After editing
+.Pa cgi.h ,
+run
+.Pp
+.Dl make man.cgi
+.Pp
+and copy the resulting binary to the proper location,
+for example using the command:
+.Pp
+.Dl make installcgi
+.Pp
+In addition to that, make sure the default manpath contains the files
+.Pa man1/apropos.1
+and
+.Pa man8/man.cgi.8 ,
+or the documentation links at the bottom of the index page will not work.
+.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://
+or
+.Cm https://
+protocol specifier.
+.It
+The host name.
+.It
+The
+.Dv SCRIPT_NAME ,
+preceded by a slash unless empty.
+.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 .
+This can be abbreviated according to the following syntax:
+.Sm off
+.Op / Ar manpath
+.Op / Cm man Ar sec
+.Op / Ar arch
+.Pf / Ar name Op \&. Ar sec
+.Sm on
+.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 all manpaths and architecture names.
+.Sh ENVIRONMENT
+The web server may pass the following CGI variables to
+.Nm :
+.Bl -tag -width Ds
+.It Ev SCRIPT_NAME
+The initial part of the URI passed from the client to the server,
+starting after the server's host name and ending before
+.Ev PATH_INFO .
+This is ignored by
+.Nm .
+When constructing URIs for links and redirections, the
+.Dv SCRIPT_NAME
+preprocessor constant is used instead.
+.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 acquire 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.
+.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 usual file system path to the
+.Nm
+program inside the web server
+.Xr chroot 2
+directory.
+A different name can be chosen, but in any case, it needs to be configured in
+.Xr httpd.conf 5 .
+.It Pa /htdocs
+The file system path to the server document root directory
+relative to the server
+.Xr chroot 2
+directory.
+This is part of the web server configuration and not specific to
+.Nm .
+.It Pa /htdocs/mandoc.css
+A style sheet for
+.Xr mandoc 1
+HTML styling, referenced from each generated HTML page.
+.It Pa /man
+Default
+.Nm
+data directory containing all the manual trees.
+Can be overridden by
+.Dv MAN_DIR .
+.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/header.html
+An optional file containing static HTML code to be inserted right
+after opening the <BODY> element.
+.It Pa /man/footer.html
+An optional file containing static HTML code to be inserted right
+before closing the <BODY> element.
+.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 looks quite different.
+.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
+.Xr mandoc.db 5
+database format first appeared in
+.Ox 6.1 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and is maintained by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org ,
+who also designed and implemented the database format.
diff --git a/usr.bin/mandoc/man.conf.5 b/usr.bin/mandoc/man.conf.5
new file mode 100644
index 0000000..2ba8cde
--- /dev/null
+++ b/usr.bin/mandoc/man.conf.5
@@ -0,0 +1,133 @@
+.\" $OpenBSD: man.conf.5,v 1.8 2020/02/10 14:42:03 schwarze Exp $
+.\"
+.\" Copyright (c) 2015, 2017 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: February 10 2020 $
+.Dt MAN.CONF 5
+.Os
+.Sh NAME
+.Nm man.conf
+.Nd configuration file for man
+.Sh DESCRIPTION
+This is the configuration file
+for the
+.Xr man 1 ,
+.Xr apropos 1 ,
+and
+.Xr makewhatis 8
+utilities.
+Its presence, and all directives, are optional.
+.Pp
+This file is an ASCII text file.
+Leading whitespace on lines, lines starting with
+.Sq # ,
+and blank lines are ignored.
+Words are separated by whitespace.
+The first word on each line is the name of a configuration directive.
+.Pp
+The following directives are supported:
+.Bl -tag -width Ds
+.It Ic manpath Ar path
+Override the default search
+.Ar path
+for
+.Xr man 1 ,
+.Xr apropos 1 ,
+and
+.Xr makewhatis 8 .
+It can be used multiple times to specify multiple paths,
+with the order determining the manual page search order.
+.Pp
+Each path is a tree containing subdirectories
+whose names consist of the strings
+.Sq man
+and/or
+.Sq cat
+followed by the names of sections, usually single digits.
+The former are supposed to contain unformatted manual pages in
+.Xr mdoc 7
+and/or
+.Xr man 7
+format; file names should end with the name of the section
+preceded by a dot.
+The latter should contain preformatted manual pages;
+file names should end with
+.Ql .0 .
+.Pp
+Creating a
+.Xr mandoc.db 5
+database with
+.Xr makewhatis 8
+in each directory configured with
+.Ic manpath
+is recommended and necessary for
+.Xr apropos 1
+to work, and also for
+.Xr man 1
+on operating systems like
+.Ox
+that install each manual page with only one file name in the file system,
+even if it documents multiple utilities or functions.
+.It Ic output Ar option Op Ar value
+Configure the default value of an output option.
+These directives are overridden by the
+.Fl O
+command line options of the same names.
+For details, see the
+.Xr mandoc 1
+manual.
+.Pp
+.Bl -column fragment integer "ascii, utf8" -compact
+.It Ar option Ta Ar value Ta used by Fl T Ta purpose
+.It Ta Ta Ta
+.It Ic fragment Ta none Ta Cm html Ta print only body
+.It Ic includes Ta string Ta Cm html Ta path to header files
+.It Ic indent Ta integer Ta Cm ascii , utf8 Ta left margin
+.It Ic man Ta string Ta Cm html Ta path for \&Xr links
+.It Ic paper Ta string Ta Cm ps , pdf Ta paper size
+.It Ic style Ta string Ta Cm html Ta CSS file
+.It Ic toc Ta none Ta Cm html Ta print table of contents
+.It Ic width Ta integer Ta Cm ascii , utf8 Ta right margin
+.El
+.El
+.Sh FILES
+.Bl -tag -width /etc/examples/man.conf -compact
+.It Pa /etc/man.conf
+.It Pa /etc/examples/man.conf
+.El
+.Sh EXAMPLES
+The following configuration file reproduces the defaults:
+installing it is equivalent to not having a
+.Nm
+file at all.
+.Bd -literal -offset indent
+manpath /usr/share/man
+manpath /usr/X11R6/man
+manpath /usr/local/man
+.Ed
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr man 1 ,
+.Xr makewhatis 8
+.Sh HISTORY
+A relatively complicated
+.Nm
+file format first appeared in
+.Bx 4.3 Reno .
+For
+.Ox 5.8 ,
+it was redesigned from scratch, aiming for simplicity.
+.Sh AUTHORS
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
diff --git a/usr.bin/mandoc/man.h b/usr.bin/mandoc/man.h
new file mode 100644
index 0000000..eda8c6c
--- /dev/null
+++ b/usr.bin/mandoc/man.h
@@ -0,0 +1,21 @@
+/* $OpenBSD: man.h,v 1.59 2018/08/23 19:32:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015 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.
+ */
+
+struct roff_man;
+
+void man_validate(struct roff_man *);
diff --git a/usr.bin/mandoc/man_html.c b/usr.bin/mandoc/man_html.c
new file mode 100644
index 0000000..a2a1b4d
--- /dev/null
+++ b/usr.bin/mandoc/man_html.c
@@ -0,0 +1,640 @@
+/* $OpenBSD: man_html.c,v 1.131 2020/04/04 20:23:06 schwarze Exp $ */
+/*
+ * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012, 2014 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 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.
+ *
+ * HTML formatter for man(7) used by mandoc(1).
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "man.h"
+#include "out.h"
+#include "html.h"
+#include "main.h"
+
+#define MAN_ARGS const struct roff_meta *man, \
+ struct roff_node *n, \
+ struct html *h
+
+struct man_html_act {
+ int (*pre)(MAN_ARGS);
+ int (*post)(MAN_ARGS);
+};
+
+static void print_man_head(const struct roff_meta *,
+ struct html *);
+static void print_man_nodelist(MAN_ARGS);
+static void print_man_node(MAN_ARGS);
+static char list_continues(const struct roff_node *,
+ const struct roff_node *);
+static int man_B_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_SY_pre(MAN_ARGS);
+static int man_UR_pre(MAN_ARGS);
+static int man_abort_pre(MAN_ARGS);
+static int man_alt_pre(MAN_ARGS);
+static int man_ign_pre(MAN_ARGS);
+static int man_in_pre(MAN_ARGS);
+static void man_root_post(const struct roff_meta *,
+ struct html *);
+static void man_root_pre(const struct roff_meta *,
+ struct html *);
+
+static const struct man_html_act man_html_acts[MAN_MAX - MAN_TH] = {
+ { NULL, NULL }, /* TH */
+ { man_SH_pre, NULL }, /* SH */
+ { man_SH_pre, NULL }, /* SS */
+ { man_IP_pre, NULL }, /* TP */
+ { man_IP_pre, NULL }, /* TQ */
+ { man_abort_pre, NULL }, /* LP */
+ { man_PP_pre, NULL }, /* PP */
+ { man_abort_pre, NULL }, /* P */
+ { man_IP_pre, NULL }, /* IP */
+ { man_PP_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 */
+ { 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_SY_pre, NULL }, /* SY */
+ { NULL, NULL }, /* YS */
+ { man_OP_pre, NULL }, /* OP */
+ { NULL, NULL }, /* EX */
+ { NULL, NULL }, /* EE */
+ { man_UR_pre, NULL }, /* UR */
+ { NULL, NULL }, /* UE */
+ { man_UR_pre, NULL }, /* MT */
+ { NULL, NULL }, /* ME */
+};
+
+
+void
+html_man(void *arg, const struct roff_meta *man)
+{
+ struct html *h;
+ struct roff_node *n;
+ struct tag *t;
+
+ h = (struct html *)arg;
+ n = man->first->child;
+
+ if ((h->oflags & HTML_FRAGMENT) == 0) {
+ print_gen_decls(h);
+ print_otag(h, TAG_HTML, "");
+ if (n != NULL && n->type == ROFFT_COMMENT)
+ print_gen_comment(h, n);
+ t = print_otag(h, TAG_HEAD, "");
+ print_man_head(man, h);
+ print_tagq(h, t);
+ print_otag(h, TAG_BODY, "");
+ }
+
+ man_root_pre(man, h);
+ t = print_otag(h, TAG_DIV, "c", "manual-text");
+ print_man_nodelist(man, n, h);
+ print_tagq(h, t);
+ man_root_post(man, h);
+ print_tagq(h, NULL);
+}
+
+static void
+print_man_head(const struct roff_meta *man, struct html *h)
+{
+ char *cp;
+
+ print_gen_head(h);
+ mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
+ print_otag(h, TAG_TITLE, "");
+ print_text(h, cp);
+ free(cp);
+}
+
+static void
+print_man_nodelist(MAN_ARGS)
+{
+ while (n != NULL) {
+ print_man_node(man, n, h);
+ n = n->next;
+ }
+}
+
+static void
+print_man_node(MAN_ARGS)
+{
+ struct tag *t;
+ int child;
+
+ if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
+ return;
+
+ html_fillmode(h, n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi);
+
+ child = 1;
+ switch (n->type) {
+ case ROFFT_TEXT:
+ if (*n->string == '\0') {
+ print_endline(h);
+ return;
+ }
+ if (*n->string == ' ' && n->flags & NODE_LINE &&
+ (h->flags & HTML_NONEWLINE) == 0)
+ print_otag(h, TAG_BR, "");
+ else if (n->flags & NODE_DELIMC)
+ h->flags |= HTML_NOSPACE;
+ t = h->tag;
+ t->refcnt++;
+ print_text(h, n->string);
+ break;
+ case ROFFT_EQN:
+ t = h->tag;
+ t->refcnt++;
+ print_eqn(h, n->eqn);
+ break;
+ case ROFFT_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 (h->metac != ESCAPE_FONTROMAN) {
+ h->metal = h->metac;
+ h->metac = ESCAPE_FONTROMAN;
+ }
+
+ /*
+ * 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 != NULL)
+ print_tblclose(h);
+ t = h->tag;
+ t->refcnt++;
+ if (n->tok < ROFF_MAX) {
+ roff_html_pre(h, n);
+ t->refcnt--;
+ print_stagq(h, t);
+ return;
+ }
+ assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
+ if (man_html_acts[n->tok - MAN_TH].pre != NULL)
+ child = (*man_html_acts[n->tok - MAN_TH].pre)(man,
+ n, h);
+ break;
+ }
+
+ if (child && n->child != NULL)
+ print_man_nodelist(man, n->child, h);
+
+ /* This will automatically close out any font scope. */
+ t->refcnt--;
+ if (n->type == ROFFT_BLOCK &&
+ (n->tok == MAN_IP || n->tok == MAN_TP || n->tok == MAN_TQ)) {
+ t = h->tag;
+ while (t->tag != TAG_DL && t->tag != TAG_UL)
+ t = t->next;
+ /*
+ * Close the list if no further item of the same type
+ * follows; otherwise, close the item only.
+ */
+ if (list_continues(n, roff_node_next(n)) == '\0') {
+ print_tagq(h, t);
+ t = NULL;
+ }
+ }
+ if (t != NULL)
+ print_stagq(h, t);
+
+ if (n->flags & NODE_NOFILL && n->tok != MAN_YS &&
+ (n->next != NULL && n->next->flags & NODE_LINE)) {
+ /* In .nf = <pre>, print even empty lines. */
+ h->col++;
+ print_endline(h);
+ }
+}
+
+static void
+man_root_pre(const struct roff_meta *man, struct html *h)
+{
+ struct tag *t, *tt;
+ char *title;
+
+ assert(man->title);
+ assert(man->msec);
+ mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
+
+ t = print_otag(h, TAG_TABLE, "c", "head");
+ tt = print_otag(h, TAG_TR, "");
+
+ print_otag(h, TAG_TD, "c", "head-ltitle");
+ print_text(h, title);
+ print_stagq(h, tt);
+
+ print_otag(h, TAG_TD, "c", "head-vol");
+ if (man->vol != NULL)
+ print_text(h, man->vol);
+ print_stagq(h, tt);
+
+ print_otag(h, TAG_TD, "c", "head-rtitle");
+ print_text(h, title);
+ print_tagq(h, t);
+ free(title);
+}
+
+static void
+man_root_post(const struct roff_meta *man, struct html *h)
+{
+ struct tag *t, *tt;
+
+ t = print_otag(h, TAG_TABLE, "c", "foot");
+ tt = print_otag(h, TAG_TR, "");
+
+ print_otag(h, TAG_TD, "c", "foot-date");
+ print_text(h, man->date);
+ print_stagq(h, tt);
+
+ print_otag(h, TAG_TD, "c", "foot-os");
+ if (man->os != NULL)
+ print_text(h, man->os);
+ print_tagq(h, t);
+}
+
+static int
+man_SH_pre(MAN_ARGS)
+{
+ const char *class;
+ enum htmltag tag;
+
+ if (n->tok == MAN_SH) {
+ tag = TAG_H1;
+ class = "Sh";
+ } else {
+ tag = TAG_H2;
+ class = "Ss";
+ }
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ print_otag(h, TAG_SECTION, "c", class);
+ break;
+ case ROFFT_HEAD:
+ print_otag_id(h, tag, class, n);
+ break;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static int
+man_alt_pre(MAN_ARGS)
+{
+ const struct roff_node *nn;
+ struct tag *t;
+ int i;
+ enum htmltag fp;
+
+ for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) {
+ 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();
+ }
+
+ if (i)
+ h->flags |= HTML_NOSPACE;
+
+ if (fp != TAG_MAX)
+ t = print_otag(h, fp, "");
+
+ print_text(h, nn->string);
+
+ if (fp != TAG_MAX)
+ print_tagq(h, t);
+ }
+ return 0;
+}
+
+static int
+man_SM_pre(MAN_ARGS)
+{
+ print_otag(h, TAG_SMALL, "");
+ if (n->tok == MAN_SB)
+ print_otag(h, TAG_B, "");
+ return 1;
+}
+
+static int
+man_PP_pre(MAN_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ break;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ if (n->child != NULL &&
+ (n->child->flags & NODE_NOFILL) == 0)
+ print_otag(h, TAG_P, "c",
+ n->tok == MAN_PP ? "Pp" : "Pp HP");
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static char
+list_continues(const struct roff_node *n1, const struct roff_node *n2)
+{
+ const char *s1, *s2;
+ char c1, c2;
+
+ if (n1 == NULL || n1->type != ROFFT_BLOCK ||
+ n2 == NULL || n2->type != ROFFT_BLOCK)
+ return '\0';
+ if ((n1->tok == MAN_TP || n1->tok == MAN_TQ) &&
+ (n2->tok == MAN_TP || n2->tok == MAN_TQ))
+ return ' ';
+ if (n1->tok != MAN_IP || n2->tok != MAN_IP)
+ return '\0';
+ n1 = n1->head->child;
+ n2 = n2->head->child;
+ s1 = n1 == NULL ? "" : n1->string;
+ s2 = n2 == NULL ? "" : n2->string;
+ c1 = strcmp(s1, "*") == 0 ? '*' :
+ strcmp(s1, "\\-") == 0 ? '-' :
+ strcmp(s1, "\\(bu") == 0 ? 'b' : ' ';
+ c2 = strcmp(s2, "*") == 0 ? '*' :
+ strcmp(s2, "\\-") == 0 ? '-' :
+ strcmp(s2, "\\(bu") == 0 ? 'b' : ' ';
+ return c1 != c2 ? '\0' : c1 == 'b' ? '*' : c1;
+}
+
+static int
+man_IP_pre(MAN_ARGS)
+{
+ struct roff_node *nn;
+ const char *list_class;
+ enum htmltag list_elem, body_elem;
+ char list_type;
+
+ nn = n->type == ROFFT_BLOCK ? n : n->parent;
+ list_type = list_continues(roff_node_prev(nn), nn);
+ if (list_type == '\0') {
+ /* Start a new list. */
+ list_type = list_continues(nn, roff_node_next(nn));
+ if (list_type == '\0')
+ list_type = ' ';
+ switch (list_type) {
+ case ' ':
+ list_class = "Bl-tag";
+ list_elem = TAG_DL;
+ break;
+ case '*':
+ list_class = "Bl-bullet";
+ list_elem = TAG_UL;
+ break;
+ case '-':
+ list_class = "Bl-dash";
+ list_elem = TAG_UL;
+ break;
+ default:
+ abort();
+ }
+ } else {
+ /* Continue a list that was started earlier. */
+ list_class = NULL;
+ list_elem = TAG_MAX;
+ }
+ body_elem = list_type == ' ' ? TAG_DD : TAG_LI;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ if (list_elem != TAG_MAX)
+ print_otag(h, list_elem, "c", list_class);
+ return 1;
+ case ROFFT_HEAD:
+ if (body_elem == TAG_LI)
+ return 0;
+ print_otag_id(h, TAG_DT, NULL, n);
+ break;
+ case ROFFT_BODY:
+ print_otag(h, body_elem, "");
+ return 1;
+ default:
+ abort();
+ }
+ switch(n->tok) {
+ case MAN_IP: /* Only print the first header element. */
+ if (n->child != NULL)
+ print_man_node(man, n->child, h);
+ break;
+ case MAN_TP: /* Only print next-line header elements. */
+ case MAN_TQ:
+ nn = n->child;
+ while (nn != NULL && (NODE_LINE & nn->flags) == 0)
+ nn = nn->next;
+ while (nn != NULL) {
+ print_man_node(man, nn, h);
+ nn = nn->next;
+ }
+ break;
+ default:
+ abort();
+ }
+ return 0;
+}
+
+static int
+man_OP_pre(MAN_ARGS)
+{
+ struct tag *tt;
+
+ print_text(h, "[");
+ h->flags |= HTML_NOSPACE;
+ tt = print_otag(h, TAG_SPAN, "c", "Op");
+
+ if ((n = n->child) != NULL) {
+ print_otag(h, TAG_B, "");
+ print_text(h, n->string);
+ }
+
+ print_stagq(h, tt);
+
+ if (n != NULL && n->next != NULL) {
+ print_otag(h, TAG_I, "");
+ 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, "");
+ return 1;
+}
+
+static int
+man_I_pre(MAN_ARGS)
+{
+ print_otag(h, TAG_I, "");
+ return 1;
+}
+
+static int
+man_in_pre(MAN_ARGS)
+{
+ print_otag(h, TAG_BR, "");
+ return 0;
+}
+
+static int
+man_ign_pre(MAN_ARGS)
+{
+ return 0;
+}
+
+static int
+man_RS_pre(MAN_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ break;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ print_otag(h, TAG_DIV, "c", "Bd-indent");
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static int
+man_SY_pre(MAN_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ print_otag(h, TAG_TABLE, "c", "Nm");
+ print_otag(h, TAG_TR, "");
+ break;
+ case ROFFT_HEAD:
+ print_otag(h, TAG_TD, "");
+ print_otag(h, TAG_CODE, "c", "Nm");
+ break;
+ case ROFFT_BODY:
+ print_otag(h, TAG_TD, "");
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static int
+man_UR_pre(MAN_ARGS)
+{
+ char *cp;
+
+ n = n->child;
+ assert(n->type == ROFFT_HEAD);
+ if (n->child != NULL) {
+ assert(n->child->type == ROFFT_TEXT);
+ if (n->tok == MAN_MT) {
+ mandoc_asprintf(&cp, "mailto:%s", n->child->string);
+ print_otag(h, TAG_A, "ch", "Mt", cp);
+ free(cp);
+ } else
+ print_otag(h, TAG_A, "ch", "Lk", n->child->string);
+ }
+
+ assert(n->next->type == ROFFT_BODY);
+ if (n->next->child != NULL)
+ n = n->next;
+
+ print_man_nodelist(man, n->child, h);
+ return 0;
+}
+
+static int
+man_abort_pre(MAN_ARGS)
+{
+ abort();
+}
diff --git a/usr.bin/mandoc/man_macro.c b/usr.bin/mandoc/man_macro.c
new file mode 100644
index 0000000..dc0bcfd
--- /dev/null
+++ b/usr.bin/mandoc/man_macro.c
@@ -0,0 +1,466 @@
+/* $OpenBSD: man_macro.c,v 1.106 2019/01/05 18:59:37 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2012-2015, 2017-2019 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 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "man.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libman.h"
+
+static void blk_close(MACRO_PROT_ARGS);
+static void blk_exp(MACRO_PROT_ARGS);
+static void blk_imp(MACRO_PROT_ARGS);
+static void in_line_eoln(MACRO_PROT_ARGS);
+static int man_args(struct roff_man *, int,
+ int *, char *, char **);
+static void rew_scope(struct roff_man *, enum roff_tok);
+
+static const struct man_macro man_macros[MAN_MAX - MAN_TH] = {
+ { in_line_eoln, MAN_XSCOPE }, /* TH */
+ { blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* SH */
+ { blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* SS */
+ { blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* TP */
+ { blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* TQ */
+ { blk_imp, MAN_XSCOPE }, /* LP */
+ { blk_imp, MAN_XSCOPE }, /* PP */
+ { blk_imp, MAN_XSCOPE }, /* P */
+ { blk_imp, MAN_XSCOPE }, /* IP */
+ { blk_imp, MAN_XSCOPE }, /* HP */
+ { in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* SM */
+ { in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* 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_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* R */
+ { in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* B */
+ { in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* I */
+ { in_line_eoln, 0 }, /* IR */
+ { in_line_eoln, 0 }, /* RI */
+ { blk_close, MAN_XSCOPE }, /* RE */
+ { blk_exp, MAN_XSCOPE }, /* RS */
+ { in_line_eoln, 0 }, /* DT */
+ { in_line_eoln, 0 }, /* UC */
+ { in_line_eoln, MAN_NSCOPED }, /* PD */
+ { in_line_eoln, 0 }, /* AT */
+ { in_line_eoln, MAN_NSCOPED }, /* in */
+ { blk_imp, MAN_XSCOPE }, /* SY */
+ { blk_close, MAN_XSCOPE }, /* YS */
+ { in_line_eoln, 0 }, /* OP */
+ { in_line_eoln, MAN_XSCOPE }, /* EX */
+ { in_line_eoln, MAN_XSCOPE }, /* EE */
+ { blk_exp, MAN_XSCOPE }, /* UR */
+ { blk_close, MAN_XSCOPE }, /* UE */
+ { blk_exp, MAN_XSCOPE }, /* MT */
+ { blk_close, MAN_XSCOPE }, /* ME */
+};
+
+
+const struct man_macro *
+man_macro(enum roff_tok tok)
+{
+ assert(tok >= MAN_TH && tok <= MAN_MAX);
+ return man_macros + (tok - MAN_TH);
+}
+
+void
+man_unscope(struct roff_man *man, const struct roff_node *to)
+{
+ struct roff_node *n;
+
+ to = to->parent;
+ n = man->last;
+ while (n != to) {
+
+ /* Reached the end of the document? */
+
+ if (to == NULL && ! (n->flags & NODE_VALID)) {
+ if (man->flags & (MAN_BLINE | MAN_ELINE) &&
+ man_macro(n->tok)->flags &
+ (MAN_BSCOPED | MAN_NSCOPED)) {
+ mandoc_msg(MANDOCERR_BLK_LINE,
+ n->line, n->pos,
+ "EOF breaks %s", roff_name[n->tok]);
+ if (man->flags & MAN_ELINE)
+ man->flags &= ~MAN_ELINE;
+ else {
+ assert(n->type == ROFFT_HEAD);
+ n = n->parent;
+ man->flags &= ~MAN_BLINE;
+ }
+ man->last = n;
+ n = n->parent;
+ roff_node_delete(man, man->last);
+ continue;
+ }
+ if (n->type == ROFFT_BLOCK &&
+ man_macro(n->tok)->fp == blk_exp)
+ mandoc_msg(MANDOCERR_BLK_NOEND,
+ n->line, n->pos, "%s",
+ roff_name[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;
+ man->last->flags |= NODE_VALID;
+ }
+
+ /*
+ * If we ended up at the parent of the node we were
+ * supposed to rewind to, that means the target node
+ * got deleted, so add the next node we parse as a child
+ * of the parent instead of as a sibling of the target.
+ */
+
+ man->next = (man->last == to) ?
+ ROFF_NEXT_CHILD : ROFF_NEXT_SIBLING;
+}
+
+/*
+ * 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 void
+rew_scope(struct roff_man *man, enum roff_tok tok)
+{
+ struct roff_node *n;
+
+ /* Preserve empty paragraphs before RS. */
+
+ n = man->last;
+ if (tok == MAN_RS && n->child == NULL &&
+ (n->tok == MAN_P || n->tok == MAN_PP || n->tok == MAN_LP))
+ return;
+
+ for (;;) {
+ if (n->type == ROFFT_ROOT)
+ return;
+ if (n->flags & NODE_VALID) {
+ n = n->parent;
+ continue;
+ }
+ if (n->type != ROFFT_BLOCK) {
+ if (n->parent->type == ROFFT_ROOT) {
+ man_unscope(man, n);
+ return;
+ } else {
+ n = n->parent;
+ continue;
+ }
+ }
+ if (tok != MAN_SH && (n->tok == MAN_SH ||
+ (tok != MAN_SS && (n->tok == MAN_SS ||
+ man_macro(n->tok)->fp == blk_exp))))
+ return;
+ man_unscope(man, n);
+ n = man->last;
+ }
+}
+
+
+/*
+ * Close out a generic explicit macro.
+ */
+void
+blk_close(MACRO_PROT_ARGS)
+{
+ enum roff_tok ctok, ntok;
+ const struct roff_node *nn;
+ char *p, *ep;
+ int cline, cpos, la, nrew, target;
+
+ nrew = 1;
+ switch (tok) {
+ case MAN_RE:
+ ntok = MAN_RS;
+ la = *pos;
+ if ( ! man_args(man, line, pos, buf, &p))
+ break;
+ for (nn = man->last->parent; nn; nn = nn->parent)
+ if (nn->tok == ntok && nn->type == ROFFT_BLOCK)
+ nrew++;
+ target = strtol(p, &ep, 10);
+ if (*ep != '\0')
+ mandoc_msg(MANDOCERR_ARG_EXCESS, line,
+ la + (buf[la] == '"') + (int)(ep - p),
+ "RE ... %s", ep);
+ free(p);
+ if (target == 0)
+ target = 1;
+ nrew -= target;
+ if (nrew < 1) {
+ mandoc_msg(MANDOCERR_RE_NOTOPEN,
+ line, ppos, "RE %d", target);
+ return;
+ }
+ break;
+ case MAN_YS:
+ ntok = MAN_SY;
+ break;
+ case MAN_UE:
+ ntok = MAN_UR;
+ break;
+ case MAN_ME:
+ ntok = MAN_MT;
+ break;
+ default:
+ abort();
+ }
+
+ for (nn = man->last->parent; nn; nn = nn->parent)
+ if (nn->tok == ntok && nn->type == ROFFT_BLOCK && ! --nrew)
+ break;
+
+ if (nn == NULL) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN,
+ line, ppos, "%s", roff_name[tok]);
+ rew_scope(man, MAN_PP);
+ if (tok == MAN_RE) {
+ roff_elem_alloc(man, line, ppos, ROFF_br);
+ man->last->flags |= NODE_LINE |
+ NODE_VALID | NODE_ENDED;
+ man->next = ROFF_NEXT_SIBLING;
+ }
+ return;
+ }
+
+ cline = man->last->line;
+ cpos = man->last->pos;
+ ctok = man->last->tok;
+ man_unscope(man, nn);
+
+ if (tok == MAN_RE && nn->head->aux > 0)
+ roff_setreg(man->roff, "an-margin", nn->head->aux, '-');
+
+ /* Trailing text. */
+
+ if (buf[*pos] != '\0') {
+ roff_word_alloc(man, line, ppos, buf + *pos);
+ man->last->flags |= NODE_DELIMC;
+ if (mandoc_eos(man->last->string, strlen(man->last->string)))
+ man->last->flags |= NODE_EOS;
+ }
+
+ /* Move a trailing paragraph behind the block. */
+
+ if (ctok == MAN_LP || ctok == MAN_PP || ctok == MAN_P) {
+ *pos = strlen(buf);
+ blk_imp(man, ctok, cline, cpos, pos, buf);
+ }
+
+ /* Synopsis blocks need an explicit end marker for spacing. */
+
+ if (tok == MAN_YS && man->last == nn) {
+ roff_elem_alloc(man, line, ppos, tok);
+ man_unscope(man, man->last);
+ }
+}
+
+void
+blk_exp(MACRO_PROT_ARGS)
+{
+ struct roff_node *head;
+ char *p;
+ int la;
+
+ if (tok == MAN_RS) {
+ rew_scope(man, tok);
+ man->flags |= ROFF_NONOFILL;
+ }
+ roff_block_alloc(man, line, ppos, tok);
+ head = roff_head_alloc(man, line, ppos, tok);
+
+ la = *pos;
+ if (man_args(man, line, pos, buf, &p)) {
+ roff_word_alloc(man, line, la, p);
+ if (tok == MAN_RS) {
+ if (roff_getreg(man->roff, "an-margin") == 0)
+ roff_setreg(man->roff, "an-margin",
+ 7 * 24, '=');
+ if ((head->aux = strtod(p, NULL) * 24.0) > 0)
+ roff_setreg(man->roff, "an-margin",
+ head->aux, '+');
+ }
+ free(p);
+ }
+
+ if (buf[*pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_EXCESS, line, *pos,
+ "%s ... %s", roff_name[tok], buf + *pos);
+
+ man_unscope(man, head);
+ roff_body_alloc(man, line, ppos, tok);
+ man->flags &= ~ROFF_NONOFILL;
+}
+
+/*
+ * Parse an implicit-block macro. These contain a ROFFT_HEAD and a
+ * ROFFT_BODY contained within a ROFFT_BLOCK. Rules for closing out other
+ * scopes, such as `SH' closing out an `SS', are defined in the rew
+ * routines.
+ */
+void
+blk_imp(MACRO_PROT_ARGS)
+{
+ int la;
+ char *p;
+ struct roff_node *n;
+
+ rew_scope(man, tok);
+ man->flags |= ROFF_NONOFILL;
+ if (tok == MAN_SH || tok == MAN_SS)
+ man->flags &= ~ROFF_NOFILL;
+ roff_block_alloc(man, line, ppos, tok);
+ n = roff_head_alloc(man, line, ppos, tok);
+
+ /* Add line arguments. */
+
+ for (;;) {
+ la = *pos;
+ if ( ! man_args(man, line, pos, buf, &p))
+ break;
+ roff_word_alloc(man, line, la, p);
+ free(p);
+ }
+
+ /*
+ * For macros having optional next-line scope,
+ * keep the head open if there were no arguments.
+ * For `TP' and `TQ', always keep the head open.
+ */
+
+ if (man_macro(tok)->flags & MAN_BSCOPED &&
+ (tok == MAN_TP || tok == MAN_TQ || n == man->last)) {
+ man->flags |= MAN_BLINE;
+ return;
+ }
+
+ /* Close out the head and open the body. */
+
+ man_unscope(man, n);
+ roff_body_alloc(man, line, ppos, tok);
+ man->flags &= ~ROFF_NONOFILL;
+}
+
+void
+in_line_eoln(MACRO_PROT_ARGS)
+{
+ int la;
+ char *p;
+ struct roff_node *n;
+
+ roff_elem_alloc(man, line, ppos, tok);
+ n = man->last;
+
+ if (tok == MAN_EX)
+ man->flags |= ROFF_NOFILL;
+ else if (tok == MAN_EE)
+ man->flags &= ~ROFF_NOFILL;
+
+ for (;;) {
+ if (buf[*pos] != '\0' && man->last != n && tok == MAN_PD) {
+ mandoc_msg(MANDOCERR_ARG_EXCESS, line, *pos,
+ "%s ... %s", roff_name[tok], buf + *pos);
+ break;
+ }
+ la = *pos;
+ if ( ! man_args(man, line, pos, buf, &p))
+ break;
+ if (man_macro(tok)->flags & MAN_JOIN &&
+ man->last->type == ROFFT_TEXT)
+ roff_word_append(man, p);
+ else
+ roff_word_alloc(man, line, la, p);
+ free(p);
+ }
+
+ /*
+ * Append NODE_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 |= NODE_EOS;
+
+ /*
+ * If no arguments are specified and this is MAN_ESCOPED (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_macro(tok)->flags & MAN_ESCOPED) {
+ man->flags |= MAN_ELINE;
+ return;
+ }
+
+ assert(man->last->type != ROFFT_ROOT);
+ man->next = ROFF_NEXT_SIBLING;
+
+ /* Rewind our element scope. */
+
+ for ( ; man->last; man->last = man->last->parent) {
+ man->last->flags |= NODE_VALID;
+ if (man->last == n)
+ break;
+ }
+
+ /* Rewind next-line scoped ancestors, if any. */
+
+ if (man_macro(tok)->flags & MAN_ESCOPED)
+ man_descope(man, line, ppos, NULL);
+}
+
+void
+man_endparse(struct roff_man *man)
+{
+ man_unscope(man, man->meta.first);
+}
+
+static int
+man_args(struct roff_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 = roff_getarg(man->roff, v, line, pos);
+ return 1;
+}
diff --git a/usr.bin/mandoc/man_term.c b/usr.bin/mandoc/man_term.c
new file mode 100644
index 0000000..3bf25ad
--- /dev/null
+++ b/usr.bin/mandoc/man_term.c
@@ -0,0 +1,1149 @@
+/* $OpenBSD: man_term.c,v 1.188 2020/03/13 00:31:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012 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 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.
+ *
+ * Plain text formatter for man(7), used by mandoc(1)
+ * for ASCII, UTF-8, PostScript, and PDF output.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "man.h"
+#include "out.h"
+#include "term.h"
+#include "term_tag.h"
+#include "main.h"
+
+#define MAXMARGINS 64 /* maximum number of indented scopes */
+
+struct mtermp {
+ int lmargin[MAXMARGINS]; /* margins (incl. vis. 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, \
+ struct roff_node *n, \
+ const struct roff_meta *meta
+
+struct man_term_act {
+ int (*pre)(DECL_ARGS);
+ void (*post)(DECL_ARGS);
+ int flags;
+#define MAN_NOTEXT (1 << 0) /* Never has text children. */
+};
+
+static void print_man_nodelist(DECL_ARGS);
+static void print_man_node(DECL_ARGS);
+static void print_man_head(struct termp *,
+ const struct roff_meta *);
+static void print_man_foot(struct termp *,
+ const struct roff_meta *);
+static void print_bvspace(struct termp *,
+ struct roff_node *, int);
+
+static int pre_B(DECL_ARGS);
+static int pre_DT(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_SY(DECL_ARGS);
+static int pre_TP(DECL_ARGS);
+static int pre_UR(DECL_ARGS);
+static int pre_abort(DECL_ARGS);
+static int pre_alternate(DECL_ARGS);
+static int pre_ign(DECL_ARGS);
+static int pre_in(DECL_ARGS);
+static int pre_literal(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_SY(DECL_ARGS);
+static void post_TP(DECL_ARGS);
+static void post_UR(DECL_ARGS);
+
+static const struct man_term_act man_term_acts[MAN_MAX - MAN_TH] = {
+ { NULL, NULL, 0 }, /* TH */
+ { pre_SH, post_SH, 0 }, /* SH */
+ { pre_SS, post_SH, 0 }, /* SS */
+ { pre_TP, post_TP, 0 }, /* TP */
+ { pre_TP, post_TP, 0 }, /* TQ */
+ { pre_abort, NULL, 0 }, /* LP */
+ { pre_PP, NULL, 0 }, /* PP */
+ { pre_abort, 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 */
+ { NULL, NULL, 0 }, /* RE */
+ { pre_RS, post_RS, 0 }, /* RS */
+ { pre_DT, NULL, 0 }, /* DT */
+ { pre_ign, NULL, MAN_NOTEXT }, /* UC */
+ { pre_PD, NULL, MAN_NOTEXT }, /* PD */
+ { pre_ign, NULL, 0 }, /* AT */
+ { pre_in, NULL, MAN_NOTEXT }, /* in */
+ { pre_SY, post_SY, 0 }, /* SY */
+ { NULL, NULL, 0 }, /* YS */
+ { 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_UR, post_UR, 0 }, /* MT */
+ { NULL, NULL, 0 }, /* ME */
+};
+static const struct man_term_act *man_term_act(enum roff_tok);
+
+
+static const struct man_term_act *
+man_term_act(enum roff_tok tok)
+{
+ assert(tok >= MAN_TH && tok <= MAN_MAX);
+ return man_term_acts + (tok - MAN_TH);
+}
+
+void
+terminal_man(void *arg, const struct roff_meta *man)
+{
+ struct mtermp mt;
+ struct termp *p;
+ struct roff_node *n, *nc, *nn;
+ size_t save_defindent;
+
+ p = (struct termp *)arg;
+ save_defindent = p->defindent;
+ if (p->synopsisonly == 0 && p->defindent == 0)
+ p->defindent = 7;
+ p->tcol->rmargin = p->maxrmargin = p->defrmargin;
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, ".5i");
+
+ memset(&mt, 0, sizeof(mt));
+ mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
+ mt.offset = term_len(p, p->defindent);
+ mt.pardist = 1;
+
+ n = man->first->child;
+ if (p->synopsisonly) {
+ for (nn = NULL; n != NULL; n = n->next) {
+ if (n->tok != MAN_SH)
+ continue;
+ nc = n->child->child;
+ if (nc->type != ROFFT_TEXT)
+ continue;
+ if (strcmp(nc->string, "SYNOPSIS") == 0)
+ break;
+ if (nn == NULL && strcmp(nc->string, "NAME") == 0)
+ nn = n;
+ }
+ if (n == NULL)
+ n = nn;
+ p->flags |= TERMP_NOSPACE;
+ if (n != NULL && (n = n->child->next->child) != NULL)
+ print_man_nodelist(p, &mt, n, man);
+ term_newln(p);
+ } else {
+ term_begin(p, print_man_head, print_man_foot, man);
+ p->flags |= TERMP_NOSPACE;
+ if (n != NULL)
+ print_man_nodelist(p, &mt, n, man);
+ term_end(p);
+ }
+ p->defindent = save_defindent;
+}
+
+/*
+ * 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, struct roff_node *n, int pardist)
+{
+ struct roff_node *nch;
+ int i;
+
+ term_newln(p);
+
+ if (n->body != NULL &&
+ (nch = roff_node_child(n->body)) != NULL &&
+ nch->type == ROFFT_TBL)
+ return;
+
+ if (n->parent->tok != MAN_RS && roff_node_prev(n) == NULL)
+ return;
+
+ for (i = 0; i < pardist; i++)
+ term_vspace(p);
+}
+
+
+static int
+pre_abort(DECL_ARGS)
+{
+ abort();
+}
+
+static int
+pre_ign(DECL_ARGS)
+{
+ return 0;
+}
+
+static int
+pre_I(DECL_ARGS)
+{
+ term_fontrepl(p, TERMFONT_UNDER);
+ return 1;
+}
+
+static int
+pre_literal(DECL_ARGS)
+{
+ term_newln(p);
+
+ /*
+ * 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 (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) {
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
+ p->flags |= TERMP_NOSPACE;
+ }
+ return 0;
+}
+
+static int
+pre_PD(DECL_ARGS)
+{
+ struct roffsu su;
+
+ n = n->child;
+ if (n == NULL) {
+ mt->pardist = 1;
+ return 0;
+ }
+ assert(n->type == ROFFT_TEXT);
+ if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
+ mt->pardist = term_vspan(p, &su);
+ return 0;
+}
+
+static int
+pre_alternate(DECL_ARGS)
+{
+ enum termfont font[2];
+ struct roff_node *nn;
+ int 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();
+ }
+ for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i = 1 - i) {
+ term_fontrepl(p, font[i]);
+ assert(nn->type == ROFFT_TEXT);
+ term_word(p, nn->string);
+ if (nn->flags & NODE_EOS)
+ p->flags |= TERMP_SENTENCE;
+ if (nn->next != NULL)
+ 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_KEEP | TERMP_NOSPACE;
+
+ if ((n = n->child) != NULL) {
+ term_fontrepl(p, TERMFONT_BOLD);
+ term_word(p, n->string);
+ }
+ if (n != NULL && n->next != NULL) {
+ term_fontrepl(p, TERMFONT_UNDER);
+ term_word(p, n->next->string);
+ }
+ term_fontrepl(p, TERMFONT_NONE);
+ p->flags &= ~TERMP_KEEP;
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "]");
+ return 0;
+}
+
+static int
+pre_in(DECL_ARGS)
+{
+ struct roffsu su;
+ const char *cp;
+ size_t v;
+ int less;
+
+ term_newln(p);
+
+ if (n->child == NULL) {
+ p->tcol->offset = mt->offset;
+ return 0;
+ }
+
+ cp = n->child->string;
+ less = 0;
+
+ if (*cp == '-')
+ less = -1;
+ else if (*cp == '+')
+ less = 1;
+ else
+ cp--;
+
+ if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
+ return 0;
+
+ v = term_hen(p, &su);
+
+ if (less < 0)
+ p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
+ else if (less > 0)
+ p->tcol->offset += v;
+ else
+ p->tcol->offset = v;
+ if (p->tcol->offset > SHRT_MAX)
+ p->tcol->offset = term_len(p, p->defindent);
+
+ return 0;
+}
+
+static int
+pre_DT(DECL_ARGS)
+{
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, ".5i");
+ return 0;
+}
+
+static int
+pre_HP(DECL_ARGS)
+{
+ struct roffsu su;
+ const struct roff_node *nn;
+ int len;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ print_bvspace(p, n, mt->pardist);
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+
+ if (n->child == NULL)
+ return 0;
+
+ if ((n->child->flags & NODE_NOFILL) == 0) {
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND;
+ p->trailspace = 2;
+ }
+
+ /* Calculate offset. */
+
+ if ((nn = n->parent->head->child) != NULL &&
+ a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
+ len = term_hen(p, &su);
+ if (len < 0 && (size_t)(-len) > mt->offset)
+ len = -mt->offset;
+ else if (len > SHRT_MAX)
+ len = term_len(p, p->defindent);
+ mt->lmargin[mt->lmargincur] = len;
+ } else
+ len = mt->lmargin[mt->lmargincur];
+
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = mt->offset + len;
+ return 1;
+}
+
+static void
+post_HP(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ case ROFFT_HEAD:
+ break;
+ case ROFFT_BODY:
+ term_newln(p);
+
+ /*
+ * Compatibility with a groff bug.
+ * The .HP macro uses the undocumented .tag request
+ * which causes a line break and cancels no-space
+ * mode even if there isn't any output.
+ */
+
+ if (n->child == NULL)
+ term_vspace(p);
+
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
+ p->trailspace = 0;
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = p->maxrmargin;
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+pre_PP(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+ print_bvspace(p, n, mt->pardist);
+ break;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ p->tcol->offset = mt->offset;
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static int
+pre_IP(DECL_ARGS)
+{
+ struct roffsu su;
+ const struct roff_node *nn;
+ int len;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ print_bvspace(p, n, mt->pardist);
+ return 1;
+ case ROFFT_HEAD:
+ p->flags |= TERMP_NOBREAK;
+ p->trailspace = 1;
+ break;
+ case ROFFT_BODY:
+ p->flags |= TERMP_NOSPACE;
+ break;
+ default:
+ abort();
+ }
+
+ /* Calculate the offset from the optional second argument. */
+ if ((nn = n->parent->head->child) != NULL &&
+ (nn = nn->next) != NULL &&
+ a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
+ len = term_hen(p, &su);
+ if (len < 0 && (size_t)(-len) > mt->offset)
+ len = -mt->offset;
+ else if (len > SHRT_MAX)
+ len = term_len(p, p->defindent);
+ mt->lmargin[mt->lmargincur] = len;
+ } else
+ len = mt->lmargin[mt->lmargincur];
+
+ switch (n->type) {
+ case ROFFT_HEAD:
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = mt->offset + len;
+ if (n->child != NULL)
+ print_man_node(p, mt, n->child, meta);
+ return 0;
+ case ROFFT_BODY:
+ p->tcol->offset = mt->offset + len;
+ p->tcol->rmargin = p->maxrmargin;
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static void
+post_IP(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ break;
+ case ROFFT_HEAD:
+ term_flushln(p);
+ p->flags &= ~TERMP_NOBREAK;
+ p->trailspace = 0;
+ p->tcol->rmargin = p->maxrmargin;
+ break;
+ case ROFFT_BODY:
+ term_newln(p);
+ p->tcol->offset = mt->offset;
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+pre_TP(DECL_ARGS)
+{
+ struct roffsu su;
+ struct roff_node *nn;
+ int len;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ if (n->tok == MAN_TP)
+ print_bvspace(p, n, mt->pardist);
+ return 1;
+ case ROFFT_HEAD:
+ p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
+ p->trailspace = 1;
+ break;
+ case ROFFT_BODY:
+ p->flags |= TERMP_NOSPACE;
+ break;
+ default:
+ abort();
+ }
+
+ /* Calculate offset. */
+
+ if ((nn = n->parent->head->child) != NULL &&
+ nn->string != NULL && ! (NODE_LINE & nn->flags) &&
+ a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
+ len = term_hen(p, &su);
+ if (len < 0 && (size_t)(-len) > mt->offset)
+ len = -mt->offset;
+ else if (len > SHRT_MAX)
+ len = term_len(p, p->defindent);
+ mt->lmargin[mt->lmargincur] = len;
+ } else
+ len = mt->lmargin[mt->lmargincur];
+
+ switch (n->type) {
+ case ROFFT_HEAD:
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = mt->offset + len;
+
+ /* Don't print same-line elements. */
+ nn = n->child;
+ while (nn != NULL && (nn->flags & NODE_LINE) == 0)
+ nn = nn->next;
+
+ while (nn != NULL) {
+ print_man_node(p, mt, nn, meta);
+ nn = nn->next;
+ }
+ return 0;
+ case ROFFT_BODY:
+ p->tcol->offset = mt->offset + len;
+ p->tcol->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static void
+post_TP(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ break;
+ case ROFFT_HEAD:
+ term_flushln(p);
+ break;
+ case ROFFT_BODY:
+ term_newln(p);
+ p->tcol->offset = mt->offset;
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+pre_SS(DECL_ARGS)
+{
+ int i;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+ mt->offset = term_len(p, p->defindent);
+
+ /*
+ * No vertical space before the first subsection
+ * and after an empty subsection.
+ */
+
+ if ((n = roff_node_prev(n)) == NULL ||
+ (n->tok == MAN_SS && roff_node_child(n->body) == NULL))
+ break;
+
+ for (i = 0; i < mt->pardist; i++)
+ term_vspace(p);
+ break;
+ case ROFFT_HEAD:
+ term_fontrepl(p, TERMFONT_BOLD);
+ p->tcol->offset = term_len(p, 3);
+ p->tcol->rmargin = mt->offset;
+ p->trailspace = mt->offset;
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND;
+ break;
+ case ROFFT_BODY:
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static int
+pre_SH(DECL_ARGS)
+{
+ int i;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+ mt->offset = term_len(p, p->defindent);
+
+ /*
+ * No vertical space before the first section
+ * and after an empty section.
+ */
+
+ if ((n = roff_node_prev(n)) == NULL ||
+ (n->tok == MAN_SH && roff_node_child(n->body) == NULL))
+ break;
+
+ for (i = 0; i < mt->pardist; i++)
+ term_vspace(p);
+ break;
+ case ROFFT_HEAD:
+ term_fontrepl(p, TERMFONT_BOLD);
+ p->tcol->offset = 0;
+ p->tcol->rmargin = mt->offset;
+ p->trailspace = mt->offset;
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND;
+ break;
+ case ROFFT_BODY:
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static void
+post_SH(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ break;
+ case ROFFT_HEAD:
+ case ROFFT_BODY:
+ term_newln(p);
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+pre_RS(DECL_ARGS)
+{
+ struct roffsu su;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ term_newln(p);
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+
+ n = n->parent->head;
+ n->aux = SHRT_MAX + 1;
+ if (n->child == NULL)
+ n->aux = mt->lmargin[mt->lmargincur];
+ else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
+ n->aux = term_hen(p, &su);
+ if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
+ n->aux = -mt->offset;
+ else if (n->aux > SHRT_MAX)
+ n->aux = term_len(p, p->defindent);
+
+ mt->offset += n->aux;
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = p->maxrmargin;
+
+ if (++mt->lmarginsz < MAXMARGINS)
+ mt->lmargincur = mt->lmarginsz;
+
+ mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+ return 1;
+}
+
+static void
+post_RS(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ case ROFFT_HEAD:
+ return;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+ term_newln(p);
+ mt->offset -= n->parent->head->aux;
+ p->tcol->offset = mt->offset;
+ if (--mt->lmarginsz < MAXMARGINS)
+ mt->lmargincur = mt->lmarginsz;
+}
+
+static int
+pre_SY(DECL_ARGS)
+{
+ const struct roff_node *nn;
+ int len;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ if ((nn = roff_node_prev(n)) == NULL || nn->tok != MAN_SY)
+ print_bvspace(p, n, mt->pardist);
+ return 1;
+ case ROFFT_HEAD:
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+
+ nn = n->parent->head->child;
+ len = nn == NULL ? 1 : term_strlen(p, nn->string) + 1;
+
+ switch (n->type) {
+ case ROFFT_HEAD:
+ p->tcol->offset = mt->offset;
+ p->tcol->rmargin = mt->offset + len;
+ if (n->next->child == NULL ||
+ (n->next->child->flags & NODE_NOFILL) == 0)
+ p->flags |= TERMP_NOBREAK;
+ term_fontrepl(p, TERMFONT_BOLD);
+ break;
+ case ROFFT_BODY:
+ mt->lmargin[mt->lmargincur] = len;
+ p->tcol->offset = mt->offset + len;
+ p->tcol->rmargin = p->maxrmargin;
+ p->flags |= TERMP_NOSPACE;
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static void
+post_SY(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ break;
+ case ROFFT_HEAD:
+ term_flushln(p);
+ p->flags &= ~TERMP_NOBREAK;
+ break;
+ case ROFFT_BODY:
+ term_newln(p);
+ p->tcol->offset = mt->offset;
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+pre_UR(DECL_ARGS)
+{
+ return n->type != ROFFT_HEAD;
+}
+
+static void
+post_UR(DECL_ARGS)
+{
+ if (n->type != ROFFT_BLOCK)
+ return;
+
+ term_word(p, "<");
+ p->flags |= TERMP_NOSPACE;
+
+ if (n->child->child != NULL)
+ print_man_node(p, mt, n->child->child, meta);
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ">");
+}
+
+static void
+print_man_node(DECL_ARGS)
+{
+ const struct man_term_act *act;
+ int c;
+
+ if (n->flags & NODE_ID)
+ term_tag_write(n, p->line);
+
+ switch (n->type) {
+ case ROFFT_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 (*n->string == '\0') {
+ if (p->flags & TERMP_NONEWLINE)
+ term_newln(p);
+ else
+ term_vspace(p);
+ return;
+ } else if (*n->string == ' ' && n->flags & NODE_LINE &&
+ (p->flags & TERMP_NONEWLINE) == 0)
+ term_newln(p);
+ else if (n->flags & NODE_DELIMC)
+ p->flags |= TERMP_NOSPACE;
+
+ term_word(p, n->string);
+ goto out;
+ case ROFFT_COMMENT:
+ return;
+ case ROFFT_EQN:
+ if ( ! (n->flags & NODE_LINE))
+ p->flags |= TERMP_NOSPACE;
+ term_eqn(p, n->eqn);
+ if (n->next != NULL && ! (n->next->flags & NODE_LINE))
+ p->flags |= TERMP_NOSPACE;
+ return;
+ case ROFFT_TBL:
+ if (p->tbl.cols == NULL)
+ term_vspace(p);
+ term_tbl(p, n->span);
+ return;
+ default:
+ break;
+ }
+
+ if (n->tok < ROFF_MAX) {
+ roff_term_pre(p, n);
+ return;
+ }
+
+ act = man_term_act(n->tok);
+ if ((act->flags & MAN_NOTEXT) == 0 && n->tok != MAN_SM)
+ term_fontrepl(p, TERMFONT_NONE);
+
+ c = 1;
+ if (act->pre != NULL)
+ c = (*act->pre)(p, mt, n, meta);
+
+ if (c && n->child != NULL)
+ print_man_nodelist(p, mt, n->child, meta);
+
+ if (act->post != NULL)
+ (*act->post)(p, mt, n, meta);
+ if ((act->flags & MAN_NOTEXT) == 0 && n->tok != MAN_SM)
+ 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 (n->flags & NODE_NOFILL &&
+ ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
+ (n->next == NULL || n->next->flags & NODE_LINE)) {
+ p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
+ if (n->string != NULL && *n->string != '\0')
+ term_flushln(p);
+ else
+ term_newln(p);
+ p->flags &= ~TERMP_BRNEVER;
+ if (p->tcol->rmargin < p->maxrmargin &&
+ n->parent->tok == MAN_HP) {
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin;
+ }
+ }
+ if (n->flags & NODE_EOS)
+ p->flags |= TERMP_SENTENCE;
+}
+
+static void
+print_man_nodelist(DECL_ARGS)
+{
+ while (n != NULL) {
+ print_man_node(p, mt, n, meta);
+ n = n->next;
+ }
+}
+
+static void
+print_man_foot(struct termp *p, const struct roff_meta *meta)
+{
+ char *title;
+ size_t datelen, titlen;
+
+ 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 operating system
+ * 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->os != NULL) {
+ title = mandoc_strdup(meta->os);
+ } else {
+ title = mandoc_strdup("");
+ }
+ datelen = term_strlen(p, meta->date);
+
+ /* Bottom left corner: operating system. */
+
+ p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+ p->trailspace = 1;
+ p->tcol->offset = 0;
+ p->tcol->rmargin = p->maxrmargin > datelen ?
+ (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
+
+ if (meta->os)
+ term_word(p, meta->os);
+ term_flushln(p);
+
+ /* At the bottom in the middle: manual date. */
+
+ p->tcol->offset = p->tcol->rmargin;
+ titlen = term_strlen(p, title);
+ p->tcol->rmargin = p->maxrmargin > titlen ?
+ p->maxrmargin - titlen : 0;
+ p->flags |= TERMP_NOSPACE;
+
+ 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->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin;
+
+ term_word(p, title);
+ term_flushln(p);
+
+ /*
+ * Reset the terminal state for more output after the footer:
+ * Some output modes, in particular PostScript and PDF, print
+ * the header and the footer into a buffer such that it can be
+ * reused for multiple output pages, then go on to format the
+ * main text.
+ */
+
+ p->tcol->offset = 0;
+ p->flags = 0;
+
+ free(title);
+}
+
+static void
+print_man_head(struct termp *p, const struct roff_meta *meta)
+{
+ const char *volume;
+ char *title;
+ size_t vollen, titlen;
+
+ 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->tcol->offset = 0;
+ p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
+ (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
+ vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
+
+ term_word(p, title);
+ term_flushln(p);
+
+ /* At the top in the middle: manual volume. */
+
+ p->flags |= TERMP_NOSPACE;
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->tcol->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->tcol->rmargin + titlen <= p->maxrmargin) {
+ p->flags |= TERMP_NOSPACE;
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin;
+ term_word(p, title);
+ term_flushln(p);
+ }
+
+ p->flags &= ~TERMP_NOSPACE;
+ p->tcol->offset = 0;
+ p->tcol->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/usr.bin/mandoc/man_validate.c b/usr.bin/mandoc/man_validate.c
new file mode 100644
index 0000000..49aa390
--- /dev/null
+++ b/usr.bin/mandoc/man_validate.c
@@ -0,0 +1,656 @@
+/* $OpenBSD: man_validate.c,v 1.124 2020/04/24 11:58:02 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2012-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * 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 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.
+ *
+ * Validation module for man(7) syntax trees used by mandoc(1).
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "man.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libman.h"
+#include "tag.h"
+
+#define CHKARGS struct roff_man *man, struct roff_node *n
+
+typedef void (*v_check)(CHKARGS);
+
+static void check_abort(CHKARGS) __attribute__((__noreturn__));
+static void check_par(CHKARGS);
+static void check_part(CHKARGS);
+static void check_root(CHKARGS);
+static void check_tag(struct roff_node *, struct roff_node *);
+static void check_text(CHKARGS);
+
+static void post_AT(CHKARGS);
+static void post_EE(CHKARGS);
+static void post_EX(CHKARGS);
+static void post_IP(CHKARGS);
+static void post_OP(CHKARGS);
+static void post_SH(CHKARGS);
+static void post_TH(CHKARGS);
+static void post_TP(CHKARGS);
+static void post_UC(CHKARGS);
+static void post_UR(CHKARGS);
+static void post_in(CHKARGS);
+
+static const v_check man_valids[MAN_MAX - MAN_TH] = {
+ post_TH, /* TH */
+ post_SH, /* SH */
+ post_SH, /* SS */
+ post_TP, /* TP */
+ post_TP, /* TQ */
+ check_abort,/* LP */
+ check_par, /* PP */
+ check_abort,/* 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 */
+ NULL, /* RE */
+ check_part, /* RS */
+ NULL, /* DT */
+ post_UC, /* UC */
+ NULL, /* PD */
+ post_AT, /* AT */
+ post_in, /* in */
+ NULL, /* SY */
+ NULL, /* YS */
+ post_OP, /* OP */
+ post_EX, /* EX */
+ post_EE, /* EE */
+ post_UR, /* UR */
+ NULL, /* UE */
+ post_UR, /* MT */
+ NULL, /* ME */
+};
+
+
+/* Validate the subtree rooted at man->last. */
+void
+man_validate(struct roff_man *man)
+{
+ struct roff_node *n;
+ const v_check *cp;
+
+ /*
+ * Translate obsolete macros such that later code
+ * does not need to look for them.
+ */
+
+ n = man->last;
+ switch (n->tok) {
+ case MAN_LP:
+ case MAN_P:
+ n->tok = MAN_PP;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Iterate over all children, recursing into each one
+ * in turn, depth-first.
+ */
+
+ man->last = man->last->child;
+ while (man->last != NULL) {
+ man_validate(man);
+ if (man->last == n)
+ man->last = man->last->child;
+ else
+ man->last = man->last->next;
+ }
+
+ /* Finally validate the macro itself. */
+
+ man->last = n;
+ man->next = ROFF_NEXT_SIBLING;
+ switch (n->type) {
+ case ROFFT_TEXT:
+ check_text(man, n);
+ break;
+ case ROFFT_ROOT:
+ check_root(man, n);
+ break;
+ case ROFFT_COMMENT:
+ case ROFFT_EQN:
+ case ROFFT_TBL:
+ break;
+ default:
+ if (n->tok < ROFF_MAX) {
+ roff_validate(man);
+ break;
+ }
+ assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
+ cp = man_valids + (n->tok - MAN_TH);
+ if (*cp)
+ (*cp)(man, n);
+ if (man->last == n)
+ n->flags |= NODE_VALID;
+ break;
+ }
+}
+
+static void
+check_root(CHKARGS)
+{
+ assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
+
+ if (n->last == NULL || n->last->type == ROFFT_COMMENT)
+ mandoc_msg(MANDOCERR_DOC_EMPTY, n->line, n->pos, NULL);
+ else
+ man->meta.hasbody = 1;
+
+ if (NULL == man->meta.title) {
+ mandoc_msg(MANDOCERR_TH_NOTITLE, 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 = mandoc_normdate(NULL, NULL);
+ }
+
+ if (man->meta.os_e &&
+ (man->meta.rcsids & (1 << man->meta.os_e)) == 0)
+ mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
+ man->meta.os_e == MANDOC_OS_OPENBSD ?
+ "(OpenBSD)" : "(NetBSD)");
+}
+
+static void
+check_abort(CHKARGS)
+{
+ abort();
+}
+
+/*
+ * Skip leading whitespace, dashes, backslashes, and font escapes,
+ * then create a tag if the first following byte is a letter.
+ * Priority is high unless whitespace is present.
+ */
+static void
+check_tag(struct roff_node *n, struct roff_node *nt)
+{
+ const char *cp, *arg;
+ int prio, sz;
+
+ if (nt == NULL || nt->type != ROFFT_TEXT)
+ return;
+
+ cp = nt->string;
+ prio = TAG_STRONG;
+ for (;;) {
+ switch (*cp) {
+ case ' ':
+ case '\t':
+ prio = TAG_WEAK;
+ /* FALLTHROUGH */
+ case '-':
+ cp++;
+ break;
+ case '\\':
+ cp++;
+ switch (mandoc_escape(&cp, &arg, &sz)) {
+ case ESCAPE_FONT:
+ case ESCAPE_FONTBOLD:
+ case ESCAPE_FONTITALIC:
+ case ESCAPE_FONTBI:
+ case ESCAPE_FONTROMAN:
+ case ESCAPE_FONTCW:
+ case ESCAPE_FONTPREV:
+ case ESCAPE_IGNORE:
+ break;
+ case ESCAPE_SPECIAL:
+ if (sz != 1)
+ return;
+ switch (*arg) {
+ case '-':
+ case 'e':
+ break;
+ default:
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+ break;
+ default:
+ if (isalpha((unsigned char)*cp))
+ tag_put(cp, prio, n);
+ return;
+ }
+ }
+}
+
+static void
+check_text(CHKARGS)
+{
+ char *cp, *p;
+
+ if (n->flags & NODE_NOFILL)
+ return;
+
+ cp = n->string;
+ for (p = cp; NULL != (p = strchr(p, '\t')); p++)
+ mandoc_msg(MANDOCERR_FI_TAB,
+ n->line, n->pos + (int)(p - cp), NULL);
+}
+
+static void
+post_EE(CHKARGS)
+{
+ if ((n->flags & NODE_NOFILL) == 0)
+ mandoc_msg(MANDOCERR_FI_SKIP, n->line, n->pos, "EE");
+}
+
+static void
+post_EX(CHKARGS)
+{
+ if (n->flags & NODE_NOFILL)
+ mandoc_msg(MANDOCERR_NF_SKIP, n->line, n->pos, "EX");
+}
+
+static void
+post_OP(CHKARGS)
+{
+
+ if (n->child == NULL)
+ mandoc_msg(MANDOCERR_OP_EMPTY, n->line, n->pos, "OP");
+ else if (n->child->next != NULL && n->child->next->next != NULL) {
+ n = n->child->next->next;
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ n->line, n->pos, "OP ... %s", n->string);
+ }
+}
+
+static void
+post_SH(CHKARGS)
+{
+ struct roff_node *nc;
+ char *cp, *tag;
+
+ nc = n->child;
+ switch (n->type) {
+ case ROFFT_HEAD:
+ tag = NULL;
+ deroff(&tag, n);
+ if (tag != NULL) {
+ for (cp = tag; *cp != '\0'; cp++)
+ if (*cp == ' ')
+ *cp = '_';
+ if (nc != NULL && nc->type == ROFFT_TEXT &&
+ strcmp(nc->string, tag) == 0)
+ tag_put(NULL, TAG_WEAK, n);
+ else
+ tag_put(tag, TAG_FALLBACK, n);
+ free(tag);
+ }
+ return;
+ case ROFFT_BODY:
+ if (nc != NULL)
+ break;
+ return;
+ default:
+ return;
+ }
+
+ if (nc->tok == MAN_PP && nc->body->child != NULL) {
+ while (nc->body->last != NULL) {
+ man->next = ROFF_NEXT_CHILD;
+ roff_node_relink(man, nc->body->last);
+ man->last = n;
+ }
+ }
+
+ if (nc->tok == MAN_PP || nc->tok == ROFF_sp || nc->tok == ROFF_br) {
+ mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos,
+ "%s after %s", roff_name[nc->tok], roff_name[n->tok]);
+ roff_node_delete(man, nc);
+ }
+
+ /*
+ * Trailing PP is empty, so it is deleted by check_par().
+ * Trailing sp is significant.
+ */
+
+ if ((nc = n->last) != NULL && nc->tok == ROFF_br) {
+ mandoc_msg(MANDOCERR_PAR_SKIP,
+ nc->line, nc->pos, "%s at the end of %s",
+ roff_name[nc->tok], roff_name[n->tok]);
+ roff_node_delete(man, nc);
+ }
+}
+
+static void
+post_UR(CHKARGS)
+{
+ if (n->type == ROFFT_HEAD && n->child == NULL)
+ mandoc_msg(MANDOCERR_UR_NOHEAD, n->line, n->pos,
+ "%s", roff_name[n->tok]);
+ check_part(man, n);
+}
+
+static void
+check_part(CHKARGS)
+{
+
+ if (n->type == ROFFT_BODY && n->child == NULL)
+ mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
+ "%s", roff_name[n->tok]);
+}
+
+static void
+check_par(CHKARGS)
+{
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ if (n->body->child == NULL)
+ roff_node_delete(man, n);
+ break;
+ case ROFFT_BODY:
+ if (n->child != NULL &&
+ (n->child->tok == ROFF_sp || n->child->tok == ROFF_br)) {
+ mandoc_msg(MANDOCERR_PAR_SKIP,
+ n->child->line, n->child->pos,
+ "%s after %s", roff_name[n->child->tok],
+ roff_name[n->tok]);
+ roff_node_delete(man, n->child);
+ }
+ if (n->child == NULL)
+ mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
+ "%s empty", roff_name[n->tok]);
+ break;
+ case ROFFT_HEAD:
+ if (n->child != NULL)
+ mandoc_msg(MANDOCERR_ARG_SKIP,
+ n->line, n->pos, "%s %s%s",
+ roff_name[n->tok], n->child->string,
+ n->child->next != NULL ? " ..." : "");
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+post_IP(CHKARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ if (n->head->child == NULL && n->body->child == NULL)
+ roff_node_delete(man, n);
+ break;
+ case ROFFT_HEAD:
+ check_tag(n, n->child);
+ break;
+ case ROFFT_BODY:
+ if (n->parent->head->child == NULL && n->child == NULL)
+ mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
+ "%s empty", roff_name[n->tok]);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * The first next-line element in the head is the tag.
+ * If that's a font macro, use its first child instead.
+ */
+static void
+post_TP(CHKARGS)
+{
+ struct roff_node *nt;
+
+ if (n->type != ROFFT_HEAD || (nt = n->child) == NULL)
+ return;
+
+ while ((nt->flags & NODE_LINE) == 0)
+ if ((nt = nt->next) == NULL)
+ return;
+
+ switch (nt->tok) {
+ case MAN_B:
+ case MAN_BI:
+ case MAN_BR:
+ case MAN_I:
+ case MAN_IB:
+ case MAN_IR:
+ nt = nt->child;
+ break;
+ default:
+ break;
+ }
+ check_tag(n, nt);
+}
+
+static void
+post_TH(CHKARGS)
+{
+ struct roff_node *nb;
+ const char *p;
+
+ free(man->meta.title);
+ free(man->meta.vol);
+ free(man->meta.os);
+ free(man->meta.msec);
+ free(man->meta.date);
+
+ man->meta.title = man->meta.vol = man->meta.date =
+ man->meta.msec = man->meta.os = NULL;
+
+ nb = n;
+
+ /* ->TITLE<- MSEC DATE OS VOL */
+
+ n = n->child;
+ if (n != NULL && n->string != NULL) {
+ for (p = n->string; *p != '\0'; p++) {
+ /* Only warn about this once... */
+ if (isalpha((unsigned char)*p) &&
+ ! isupper((unsigned char)*p)) {
+ mandoc_msg(MANDOCERR_TITLE_CASE, n->line,
+ n->pos + (int)(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, nb->line, nb->pos, "TH");
+ }
+
+ /* TITLE ->MSEC<- DATE OS VOL */
+
+ if (n != NULL)
+ n = n->next;
+ if (n != NULL && n->string != NULL) {
+ man->meta.msec = mandoc_strdup(n->string);
+ if (man->filesec != '\0' &&
+ man->filesec != *n->string &&
+ *n->string >= '1' && *n->string <= '9')
+ mandoc_msg(MANDOCERR_MSEC_FILE, n->line, n->pos,
+ "*.%c vs TH ... %c", man->filesec, *n->string);
+ } else {
+ man->meta.msec = mandoc_strdup("");
+ mandoc_msg(MANDOCERR_MSEC_MISSING,
+ nb->line, nb->pos, "TH %s", man->meta.title);
+ }
+
+ /* TITLE MSEC ->DATE<- OS VOL */
+
+ if (n != NULL)
+ n = n->next;
+ if (man->quick && n != NULL)
+ man->meta.date = mandoc_strdup("");
+ else
+ man->meta.date = mandoc_normdate(n, nb);
+
+ /* TITLE MSEC DATE ->OS<- VOL */
+
+ if (n && (n = n->next))
+ man->meta.os = mandoc_strdup(n->string);
+ else if (man->os_s != NULL)
+ man->meta.os = mandoc_strdup(man->os_s);
+ if (man->meta.os_e == MANDOC_OS_OTHER && man->meta.os != NULL) {
+ if (strstr(man->meta.os, "OpenBSD") != NULL)
+ man->meta.os_e = MANDOC_OS_OPENBSD;
+ else if (strstr(man->meta.os, "NetBSD") != NULL)
+ man->meta.os_e = MANDOC_OS_NETBSD;
+ }
+
+ /* TITLE MSEC DATE OS ->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);
+
+ if (n != NULL && (n = n->next) != NULL)
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ n->line, n->pos, "TH ... %s", n->string);
+
+ /*
+ * Remove the `TH' node after we've processed it for our
+ * meta-data.
+ */
+ roff_node_delete(man, man->last);
+}
+
+static void
+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 (n == NULL || n->type != ROFFT_TEXT)
+ 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.os);
+ man->meta.os = mandoc_strdup(p);
+}
+
+static void
+post_AT(CHKARGS)
+{
+ static const char * const unix_versions[] = {
+ "7th Edition",
+ "System III",
+ "System V",
+ "System V Release 2",
+ };
+
+ struct roff_node *nn;
+ const char *p, *s;
+
+ n = n->child;
+
+ if (n == NULL || n->type != ROFFT_TEXT)
+ 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 != NULL &&
+ nn->type == ROFFT_TEXT &&
+ nn->string[0] != '\0')
+ p = unix_versions[3];
+ else
+ p = unix_versions[2];
+ } else
+ p = unix_versions[0];
+ }
+
+ free(man->meta.os);
+ man->meta.os = mandoc_strdup(p);
+}
+
+static void
+post_in(CHKARGS)
+{
+ char *s;
+
+ if (n->parent->tok != MAN_TP ||
+ n->parent->type != ROFFT_HEAD ||
+ n->child == NULL ||
+ *n->child->string == '+' ||
+ *n->child->string == '-')
+ return;
+ mandoc_asprintf(&s, "+%s", n->child->string);
+ free(n->child->string);
+ n->child->string = s;
+}
diff --git a/usr.bin/mandoc/manconf.h b/usr.bin/mandoc/manconf.h
new file mode 100644
index 0000000..4cc623f
--- /dev/null
+++ b/usr.bin/mandoc/manconf.h
@@ -0,0 +1,56 @@
+/* $OpenBSD: manconf.h,v 1.8 2020/04/02 22:10:27 schwarze Exp $ */
+/*
+ * Copyright (c) 2011,2015,2017,2018,2020 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 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.
+ *
+ * Public interface to man(1) configuration management.
+ * For use by the main program and by the formatters.
+ */
+
+/* List of unique, absolute paths to manual trees. */
+
+struct manpaths {
+ char **paths;
+ size_t sz;
+};
+
+/* Data from -O options and man.conf(5) output directives. */
+
+struct manoutput {
+ char *includes;
+ char *man;
+ char *paper;
+ char *style;
+ char *tag;
+ size_t indent;
+ size_t width;
+ int fragment;
+ int mdoc;
+ int noval;
+ int synopsisonly;
+ int tag_found;
+ int toc;
+};
+
+struct manconf {
+ struct manoutput output;
+ struct manpaths manpath;
+};
+
+
+void manconf_parse(struct manconf *, const char *, char *, char *);
+int manconf_output(struct manoutput *, const char *, int);
+void manconf_free(struct manconf *);
+void manpath_base(struct manpaths *);
diff --git a/usr.bin/mandoc/mandoc.1 b/usr.bin/mandoc/mandoc.1
new file mode 100644
index 0000000..a5ea382
--- /dev/null
+++ b/usr.bin/mandoc/mandoc.1
@@ -0,0 +1,2336 @@
+.\" $OpenBSD: mandoc.1,v 1.167 2020/04/24 11:58:02 schwarze Exp $
+.\"
+.\" Copyright (c) 2012, 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
+.\" 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.
+.\"
+.Dd $Mdocdate: April 24 2020 $
+.Dt MANDOC 1
+.Os
+.Sh NAME
+.Nm mandoc
+.Nd format manual pages
+.Sh SYNOPSIS
+.Nm mandoc
+.Op Fl ac
+.Op Fl I Cm os Ns = Ns Ar name
+.Op Fl K Ar encoding
+.Op Fl mdoc | man
+.Op Fl O Ar options
+.Op Fl T Ar output
+.Op Fl W Ar level
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility formats manual pages for display.
+.Pp
+By default,
+.Nm
+reads
+.Xr mdoc 7
+or
+.Xr man 7
+text from stdin and produces
+.Fl T Cm locale
+output.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+If the standard output is a terminal device and
+.Fl c
+is not specified, use
+.Xr more 1
+to paginate the output, just like
+.Xr man 1
+would.
+.It Fl c
+Copy the formatted manual pages to the standard output without using
+.Xr more 1
+to paginate them.
+This is the default.
+It can be specified to override
+.Fl a .
+.It Fl I Cm os Ns = Ns Ar name
+Override the default operating system
+.Ar name
+for the
+.Xr mdoc 7
+.Ic \&Os
+and for the
+.Xr man 7
+.Ic \&TH
+macro.
+.It Fl K Ar encoding
+Specify the input encoding.
+The supported
+.Ar encoding
+arguments are
+.Cm us-ascii ,
+.Cm iso-8859-1 ,
+and
+.Cm utf-8 .
+If not specified, autodetection uses the first match in the following
+list:
+.Bl -enum
+.It
+If the first three bytes of the input file are the UTF-8 byte order
+mark (BOM, 0xefbbbf), input is interpreted as
+.Cm utf-8 .
+.It
+If the first or second line of the input file matches the
+.Sy emacs
+mode line format
+.Pp
+.D1 .\e" -*- Oo ...; Oc coding: Ar encoding ; No -*-
+.Pp
+then input is interpreted according to
+.Ar encoding .
+.It
+If the first non-ASCII byte in the file introduces a valid UTF-8
+sequence, input is interpreted as
+.Cm utf-8 .
+.It
+Otherwise, input is interpreted as
+.Cm iso-8859-1 .
+.El
+.It Fl mdoc | man
+With
+.Fl mdoc ,
+all input files are interpreted as
+.Xr mdoc 7 .
+With
+.Fl man ,
+all input files are interpreted as
+.Xr man 7 .
+By default, the input language is automatically detected for each file:
+if the first macro is
+.Ic \&Dd
+or
+.Ic \&Dt ,
+the
+.Xr mdoc 7
+parser is used; otherwise, the
+.Xr man 7
+parser is used.
+With other arguments,
+.Fl m
+is silently ignored.
+.It Fl O Ar options
+Comma-separated output options.
+See the descriptions of the individual output formats for supported
+.Ar options .
+.It Fl T Ar output
+Select the output format.
+Supported values for the
+.Ar output
+argument are
+.Cm ascii ,
+.Cm html ,
+the default of
+.Cm locale ,
+.Cm man ,
+.Cm markdown ,
+.Cm pdf ,
+.Cm ps ,
+.Cm tree ,
+and
+.Cm utf8 .
+.Pp
+The special
+.Fl T Cm lint
+mode only parses the input and produces no output.
+It implies
+.Fl W Cm all
+and redirects parser messages, which usually appear on standard
+error output, to standard output.
+.It Fl W 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 base ,
+.Cm style ,
+.Cm warning ,
+.Cm error ,
+or
+.Cm unsupp .
+The
+.Cm base
+level automatically derives the operating system from the contents of the
+.Ic \&Os
+macro, from the
+.Fl Ios
+command line option, or from the
+.Xr uname 3
+return value.
+The levels
+.Cm openbsd
+and
+.Cm netbsd
+are variants of
+.Cm base
+that bypass autodetection and request validation of base system
+conventions for a particular operating system.
+The level
+.Cm all
+is an alias for
+.Cm base .
+By default,
+.Nm
+is silent.
+See
+.Sx EXIT STATUS
+and
+.Sx DIAGNOSTICS
+for details.
+.Pp
+The special option
+.Fl W 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 Cm error , Ns Cm stop .
+.It Ar file
+Read from the given input file.
+If multiple files are specified, they are processed in the given order.
+If unspecified,
+.Nm
+reads from standard input.
+.El
+.Pp
+The options
+.Fl fhklw
+are also supported and are documented in
+.Xr man 1 .
+In
+.Fl f
+and
+.Fl k
+mode,
+.Nm
+also supports the options
+.Fl CMmOSs
+described in the
+.Xr apropos 1
+manual.
+The options
+.Fl fkl
+are mutually exclusive and override each other.
+.Ss ASCII Output
+Use
+.Fl T Cm ascii
+to force text output in 7-bit ASCII character encoding documented in the
+.Xr ascii 7
+manual page, ignoring the
+.Xr locale 1
+set in the environment.
+.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 .
+This markup is typically converted to appropriate terminal sequences by
+the pager or
+.Xr ul 1 .
+To remove the markup, pipe the output to
+.Xr col 1
+.Fl b
+instead.
+.Pp
+The special characters documented in
+.Xr mandoc_char 7
+are rendered best-effort in an ASCII equivalent.
+In particular, opening and closing
+.Sq single quotes
+are represented as characters number 0x60 and 0x27, respectively,
+which agrees with all ASCII standards from 1965 to the latest
+revision (2012) and which matches the traditional way in which
+.Xr roff 7
+formatters represent single quotes in ASCII output.
+This correct ASCII rendering may look strange with modern
+Unicode-compatible fonts because contrary to ASCII, Unicode uses
+the code point U+0060 for the grave accent only, never for an opening
+quote.
+.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.
+When output is to a pager on a terminal that is less than 66 columns
+wide, the default is reduced to three columns.
+.It Cm mdoc
+Format
+.Xr man 7
+input files in
+.Xr mdoc 7
+output style.
+Specifically, this suppresses the two additional blank lines near the
+top and the bottom of each page, and it implies
+.Fl O Cm indent Ns =5 .
+One useful application is for checking that
+.Fl T Cm man
+output formats in the same way as the
+.Xr mdoc 7
+source it was generated from.
+.It Cm tag Ns Op = Ns Ar term
+If the formatted manual page is opened in a pager,
+go to the definition of the
+.Ar term
+rather than showing the manual page from the beginning.
+If no
+.Ar term
+is specified, reuse the first command line argument that is not a
+.Ar section
+number.
+If that argument is in
+.Xr apropos 1
+.Ar key Ns = Ns Ar val
+format, only the
+.Ar val
+is used rather than the argument as a whole.
+This is useful for commands like
+.Ql man -akO tag Ic=ulimit
+to search for a keyword and jump right to its definition
+in the matching manual pages.
+.It Cm width Ns = Ns Ar width
+The output width is set to
+.Ar width
+instead of the default of 78.
+When output is to a pager on a terminal that is less than 79 columns
+wide, the default is reduced to one less than the terminal width.
+In any case, lines that are output in literal mode are never wrapped
+and may exceed the output width.
+.El
+.Ss HTML Output
+Output produced by
+.Fl T Cm html
+conforms to HTML5 using optional self-closing tags.
+Default styles use only CSS1.
+Equations rendered from
+.Xr eqn 7
+blocks use MathML.
+.Pp
+The file
+.Pa /usr/share/misc/mandoc.css
+documents style-sheet classes available for customising output.
+If a style-sheet is not specified with
+.Fl O Cm style ,
+.Fl T Cm html
+defaults to simple output (via an embedded style-sheet)
+readable in any graphical or text-based web
+browser.
+.Pp
+Non-ASCII characters are rendered
+as hexadecimal Unicode character references.
+.Pp
+The following
+.Fl O
+arguments are accepted:
+.Bl -tag -width Ds
+.It Cm fragment
+Omit the <!DOCTYPE> declaration and the <html>, <head>, and <body>
+elements and only emit the subtree below the <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
+.Ic \&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 Ns Op ; 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
+.Ic \&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.
+If two formats are given and a file
+.Ar %N.%S
+exists in the current directory, the first format is used;
+otherwise, the second format is used.
+.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.
+.It Cm toc
+If an input file contains at least two non-standard sections,
+print a table of contents near the beginning of the output.
+.El
+.Ss Locale Output
+By default,
+.Nm
+automatically selects UTF-8 or ASCII output according to the current
+.Xr locale 1 .
+If any of the environment variables
+.Ev LC_ALL ,
+.Ev LC_CTYPE ,
+or
+.Ev LANG
+are set and the first one that is set
+selects the UTF-8 character encoding, it produces
+.Sx UTF-8 Output ;
+otherwise, it falls back to
+.Sx ASCII Output .
+This output mode can also be selected explicitly with
+.Fl T Cm locale .
+.Ss Man Output
+Use
+.Fl T Cm man
+to translate
+.Xr mdoc 7
+input into
+.Xr man 7
+output format.
+This is useful for distributing manual sources to legacy systems
+lacking
+.Xr mdoc 7
+formatters.
+Embedded
+.Xr eqn 7
+and
+.Xr tbl 7
+code is not supported.
+.Pp
+If the input format of a file is
+.Xr man 7 ,
+the input is copied to the output, expanding any
+.Xr roff 7
+.Ic 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 Markdown Output
+Use
+.Fl T Cm markdown
+to translate
+.Xr mdoc 7
+input to the markdown format conforming to
+.Lk http://daringfireball.net/projects/markdown/syntax.text\
+ "John Gruber's 2004 specification" .
+The output also almost conforms to the
+.Lk http://commonmark.org/ CommonMark
+specification.
+.Pp
+The character set used for the markdown output is ASCII.
+Non-ASCII characters are encoded as HTML entities.
+Since that is not possible in literal font contexts, because these
+are rendered as code spans and code blocks in the markdown output,
+non-ASCII characters are transliterated to ASCII approximations in
+these contexts.
+.Pp
+Markdown is a very weak markup language, so all semantic markup is
+lost, and even part of the presentational markup may be lost.
+Do not use this as an intermediate step in converting to HTML;
+instead, use
+.Fl T Cm html
+directly.
+.Pp
+The
+.Xr man 7 ,
+.Xr tbl 7 ,
+and
+.Xr eqn 7
+input languages are not supported by
+.Fl T Cm markdown
+output mode.
+.Ss PDF Output
+PDF-1.1 output may be generated by
+.Fl T 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 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 Cm utf8
+to force text output in UTF-8 multi-byte character encoding,
+ignoring the
+.Xr locale 1
+settings in the environment.
+See
+.Sx ASCII Output
+regarding font styles and
+.Fl O
+arguments.
+.Pp
+On operating systems lacking locale or wide character support, and
+on those where the internal character representation is not UCS-4,
+.Nm
+always falls back to
+.Sx ASCII Output .
+.Ss Syntax tree output
+Use
+.Fl T Cm tree
+to show a human readable representation of the syntax tree.
+It is useful for debugging the source code of manual pages.
+The exact format is subject to change, so don't write parsers for it.
+.Pp
+The first paragraph shows meta data found in the
+.Xr mdoc 7
+prologue, on the
+.Xr man 7
+.Ic \&TH
+line, or the fallbacks used.
+.Pp
+In the tree dump, each output line shows one syntax tree node.
+Child nodes are indented with respect to their parent node.
+The columns are:
+.Pp
+.Bl -enum -compact
+.It
+For macro nodes, the macro name; for text and
+.Xr tbl 7
+nodes, the content.
+There is a special format for
+.Xr eqn 7
+nodes.
+.It
+Node type (text, elem, block, head, body, body-end, tail, tbl, eqn).
+.It
+Flags:
+.Bl -dash -compact
+.It
+An opening parenthesis if the node is an opening delimiter.
+.It
+An asterisk if the node starts a new input line.
+.It
+The input line number (starting at one).
+.It
+A colon.
+.It
+The input column number (starting at one).
+.It
+A closing parenthesis if the node is a closing delimiter.
+.It
+A full stop if the node ends a sentence.
+.It
+BROKEN if the node is a block broken by another block.
+.It
+NOSRC if the node is not in the input file,
+but automatically generated from macros.
+.It
+NOPRT if the node is not supposed to generate output
+for any output format.
+.El
+.El
+.Pp
+The following
+.Fl O
+argument is accepted:
+.Bl -tag -width Ds
+.It Cm noval
+Skip validation and show the unvalidated syntax tree.
+This can help to find out whether a given behaviour is caused by
+the parser or by the validator.
+Meta data is not available in this case.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width MANPAGER
+.It Ev LC_CTYPE
+The character encoding
+.Xr locale 1 .
+When
+.Sx Locale Output
+is selected, it decides whether to use ASCII or UTF-8 output format.
+It never affects the interpretation of input files.
+.It Ev MANPAGER
+Any non-empty value of the environment variable
+.Ev MANPAGER
+is used instead of the standard pagination program,
+.Xr more 1 ;
+see
+.Xr man 1
+for details.
+Only used if
+.Fl a
+or
+.Fl l
+is specified.
+.It Ev PAGER
+Specifies the pagination program to use when
+.Ev MANPAGER
+is not defined.
+If neither PAGER nor MANPAGER is defined,
+.Xr more 1
+.Fl s
+is used.
+Only used if
+.Fl a
+or
+.Fl l
+is specified.
+.El
+.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 base system convention violations, style suggestions, warnings,
+or errors occurred, or those that did were ignored because they
+were lower than the requested
+.Ar level .
+.It 1
+At least one base system convention violation or style suggestion
+occurred, but no warning or error, and
+.Fl W Cm base
+or
+.Fl W Cm style
+was specified.
+.It 2
+At least one warning occurred, but no error, and
+.Fl W Cm warning
+or a lower
+.Ar level
+was requested.
+.It 3
+At least one parsing error occurred,
+but no unsupported feature was encountered, and
+.Fl W Cm error
+or a lower
+.Ar level
+was requested.
+.It 4
+At least one unsupported feature was encountered, and
+.Fl W Cm unsupp
+or a lower
+.Ar level
+was requested.
+.It 5
+Invalid command line arguments were specified.
+No input files have been read.
+.It 6
+An operating system error occurred, for example exhaustion
+of memory, file descriptors, or process table entries.
+Such errors may cause
+.Nm
+to exit at once, possibly in the middle of parsing or formatting a file.
+.El
+.Pp
+Note that selecting
+.Fl T Cm lint
+output mode implies
+.Fl W Cm all .
+.Sh EXAMPLES
+To page manuals to the terminal:
+.Pp
+.Dl $ mandoc -l mandoc.1 man.1 apropos.1 makewhatis.8
+.Pp
+To produce HTML manuals with
+.Pa /usr/share/misc/mandoc.css
+as the style-sheet:
+.Pp
+.Dl $ mandoc \-T html -O style=/usr/share/misc/mandoc.css mdoc.7 > mdoc.7.html
+.Pp
+To check over a large set of manuals:
+.Pp
+.Dl $ mandoc \-T lint \(gafind /usr/src -name \e*\e.[1-9]\(ga
+.Pp
+To produce a series of PostScript manuals for A4 paper:
+.Pp
+.Dl $ mandoc \-T ps \-O paper=a4 mdoc.7 man.7 > 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 \-T man foo.mdoc > foo.man
+.Sh DIAGNOSTICS
+Messages displayed by
+.Nm
+follow this format:
+.Bd -ragged -offset indent
+.Nm :
+.Ar file : Ns Ar line : Ns Ar column : level : message : macro arguments
+.Pq Ar os
+.Ed
+.Pp
+The first three fields identify the
+.Ar file
+name,
+.Ar line
+number, and
+.Ar column
+number of the input file where the message was triggered.
+The line and column numbers start at 1.
+Both are omitted for messages referring to an input file as a whole.
+All
+.Ar level
+and
+.Ar message
+strings are explained below.
+The name of the
+.Ar macro
+triggering the message and its
+.Ar arguments
+are omitted where meaningless.
+The
+.Ar os
+operating system specifier is omitted for messages that are relevant
+for all operating systems.
+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
+An operating system error occurred.
+There isn't necessarily anything wrong with the input files.
+Output may all the same be missing or incomplete.
+.It Cm badarg
+Invalid command line arguments were specified.
+No input files have been read and no output is produced.
+.It Cm unsupp
+An input file uses unsupported low-level
+.Xr roff 7
+features.
+The output may be incomplete and/or misformatted,
+so using GNU troff instead of
+.Nm
+to process the file may be preferable.
+.It Cm error
+Indicates a risk of information loss or severe misformatting,
+in most cases caused by serious syntax errors.
+.It Cm warning
+Indicates a risk that the information shown or its formatting
+may mismatch the author's intent in minor ways.
+Additionally, syntax errors are classified at least as warnings,
+even if they do not usually cause misformatting.
+.It Cm style
+An input file uses dubious or discouraged style.
+This is not a complaint about the syntax, and probably neither
+formatting nor portability are in danger.
+While great care is taken to avoid false positives on the higher
+message levels, the
+.Cm style
+level tries to reduce the probability that issues go unnoticed,
+so it may occasionally issue bogus suggestions.
+Please use your good judgement to decide whether any particular
+.Cm style
+suggestion really justifies a change to the input file.
+.It Cm base
+A convention used in the base system of a specific operating system
+is not adhered to.
+These are not markup mistakes, and neither the quality of formatting
+nor portability are in danger.
+Messages of the
+.Cm base
+level are printed with the more intuitive
+.Cm style
+.Ar level
+tag.
+.El
+.Pp
+Messages of the
+.Cm base ,
+.Cm style ,
+.Cm warning ,
+.Cm error ,
+and
+.Cm unsupp
+levels are hidden unless their level, or a lower level, is requested using a
+.Fl W
+option or
+.Fl T Cm lint
+output mode.
+.Pp
+As indicated below, all
+.Cm base
+and some
+.Cm style
+checks are only performed if a specific operating system name occurs
+in the arguments of the
+.Fl W
+command line option, of the
+.Ic \&Os
+macro, of the
+.Fl Ios
+command line option, or, if neither are present, in the return value
+of the
+.Xr uname 3
+function.
+.Ss Conventions for base system manuals
+.Bl -ohang
+.It Sy "Mdocdate found"
+.Pq mdoc , Nx
+The
+.Ic \&Dd
+macro uses CVS
+.Ic Mdocdate
+keyword substitution, which is not supported by the
+.Nx
+base system.
+Consider using the conventional
+.Dq "Month dd, yyyy"
+format instead.
+.It Sy "Mdocdate missing"
+.Pq mdoc , Ox
+The
+.Ic \&Dd
+macro does not use CVS
+.Ic Mdocdate
+keyword substitution, but using it is conventionally expected in the
+.Ox
+base system.
+.It Sy "unknown architecture"
+.Pq mdoc , Ox , Nx
+The third argument of the
+.Ic \&Dt
+macro does not match any of the architectures this operating system
+is running on.
+.It Sy "operating system explicitly specified"
+.Pq mdoc , Ox , Nx
+The
+.Ic \&Os
+macro has an argument.
+In the base system, it is conventionally left blank.
+.It Sy "RCS id missing"
+.Pq Ox , Nx
+The manual page lacks the comment line with the RCS identifier
+generated by CVS
+.Ic OpenBSD
+or
+.Ic NetBSD
+keyword substitution as conventionally used in these operating systems.
+.It Sy "referenced manual not found"
+.Pq mdoc
+An
+.Ic \&Xr
+macro references a manual page that is not found in the base system.
+The path to look for base system manuals is configurable at compile
+time and defaults to
+.Pa /usr/share/man : /usr/X11R6/man .
+.El
+.Ss Style suggestions
+.Bl -ohang
+.It Sy "legacy man(7) date format"
+.Pq mdoc
+The
+.Ic \&Dd
+macro uses the legacy
+.Xr man 7
+date format
+.Dq yyyy-dd-mm .
+Consider using the conventional
+.Xr mdoc 7
+date format
+.Dq "Month dd, yyyy"
+instead.
+.It Sy "normalizing date format to" : No ...
+.Pq mdoc , man
+The
+.Ic \&Dd
+or
+.Ic \&TH
+macro provides an abbreviated month name or a day number with a
+leading zero.
+In the formatted output, the month name is written out in full
+and the leading zero is omitted.
+.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 "duplicate RCS id"
+A single manual page contains two copies of the RCS identifier for
+the same operating system.
+Consider deleting the later instance and moving the first one up
+to the top of the page.
+.It Sy "possible typo in section name"
+.Pq mdoc
+Fuzzy string matching revealed that the argument of an
+.Ic \&Sh
+macro is similar, but not identical to a standard section name.
+.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 "useless macro"
+.Pq mdoc
+A
+.Ic \&Bt ,
+.Ic \&Tn ,
+or
+.Ic \&Ud
+macro was found.
+Simply delete it: it serves no useful purpose.
+.It Sy "consider using OS macro"
+.Pq mdoc
+A string was found in plain text or in a
+.Ic \&Bx
+macro that could be represented using
+.Ic \&Ox ,
+.Ic \&Nx ,
+.Ic \&Fx ,
+or
+.Ic \&Dx .
+.It Sy "errnos out of order"
+.Pq mdoc, Nx
+The
+.Ic \&Er
+items in a
+.Ic \&Bl
+list are not in alphabetical order.
+.It Sy "duplicate errno"
+.Pq mdoc, Nx
+A
+.Ic \&Bl
+list contains two consecutive
+.Ic \&It
+entries describing the same
+.Ic \&Er
+number.
+.It Sy "trailing delimiter"
+.Pq mdoc
+The last argument of an
+.Ic \&Ex , \&Fo , \&Nd , \&Nm , \&Os , \&Sh , \&Ss , \&St ,
+or
+.Ic \&Sx
+macro ends with a trailing delimiter.
+This is usually bad style and often indicates typos.
+Most likely, the delimiter can be removed.
+.It Sy "no blank before trailing delimiter"
+.Pq mdoc
+The last argument of a macro that supports trailing delimiter
+arguments is longer than one byte and ends with a trailing delimiter.
+Consider inserting a blank such that the delimiter becomes a separate
+argument, thus moving it out of the scope of the macro.
+.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 "verbatim \(dq--\(dq, maybe consider using \e(em"
+.Pq mdoc
+Even though the ASCII output device renders an em-dash as
+.Qq \-\- ,
+that is not a good way to write it in an input file
+because it renders poorly on all other output devices.
+.It Sy "function name without markup"
+.Pq mdoc
+A word followed by an empty pair of parentheses occurs on a text line.
+Consider using an
+.Ic \&Fn
+or
+.Ic \&Xr
+macro.
+.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.
+.El
+.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 "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 "filename/section mismatch"
+.Pq mdoc , man
+The name of the input file being processed is known and its file
+name extension starts with a non-zero digit, but the
+.Ic \&Dt
+or
+.Ic \&TH
+macro contains a
+.Ar section
+argument that starts with a different non-zero digit.
+The
+.Ar section
+argument is used as provided anyway.
+Consider checking whether the file name or the argument need a correction.
+.It Sy "missing date, using \(dq\(dq"
+.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 "date in the future, using it anyway"
+.Pq mdoc , man
+The date given in a
+.Ic \&Dd
+or
+.Ic \&TH
+macro is more than a day ahead of the current system
+.Xr time 3 .
+.It Sy "missing Os macro, using \(dq\(dq"
+.Pq mdoc
+The default or current system is not shown in this case.
+.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 "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 "NAME section without Nm before Nd"
+.Pq mdoc
+The NAME section does not contain any
+.Ic \&Nm
+child macro before the first
+.Ic \&Nd
+macro.
+.It Sy "NAME section without description"
+.Pq mdoc
+The NAME section lacks the mandatory
+.Ic \&Nd
+child macro.
+.It Sy "description not at the end of NAME"
+.Pq mdoc
+The NAME section does contain an
+.Ic \&Nd
+child macro, but other content follows it.
+.It Sy "bad NAME section content"
+.Pq mdoc
+The NAME section contains plain text or macros other than
+.Ic \&Nm
+and
+.Ic \&Nd .
+.It Sy "missing comma before name"
+.Pq mdoc
+The NAME section contains an
+.Ic \&Nm
+macro that is neither the first one nor preceded by a comma.
+.It Sy "missing description line, using \(dq\(dq"
+.Pq mdoc
+The
+.Ic \&Nd
+macro lacks the required argument.
+The title line of the manual will end after the dash.
+.It Sy "description line outside NAME section"
+.Pq mdoc
+An
+.Ic \&Nd
+macro appears outside the NAME section.
+The arguments are printed anyway and the following text is used for
+.Xr apropos 1 ,
+but none of that behaviour is portable.
+.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.
+.It Sy "cross reference to self"
+.Pq mdoc
+An
+.Ic \&Xr
+macro refers to a name and section matching the section of the present
+manual page and a name mentioned in an
+.Ic \&Nm
+macro in the NAME or SYNOPSIS section, or in an
+.Ic \&Fn
+or
+.Ic \&Fo
+macro in the SYNOPSIS.
+Consider using
+.Ic \&Nm
+or
+.Ic \&Fn
+instead of
+.Ic \&Xr .
+.It Sy "unusual Xr order"
+.Pq mdoc
+In the SEE ALSO section, an
+.Ic \&Xr
+macro with a lower section number follows one with a higher number,
+or two
+.Ic \&Xr
+macros referring to the same section are out of alphabetical order.
+.It Sy "unusual Xr punctuation"
+.Pq mdoc
+In the SEE ALSO section, punctuation between two
+.Ic \&Xr
+macros differs from a single comma, or there is trailing punctuation
+after the last
+.Ic \&Xr
+macro.
+.It Sy "AUTHORS section without An macro"
+.Pq mdoc
+An AUTHORS sections contains no
+.Ic \&An
+macros, or only empty ones.
+Probably, there are author names lacking markup.
+.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 "macro neither callable nor escaped"
+.Pq mdoc
+The name of a macro that is not callable appears on a macro line.
+It is printed verbatim.
+If the intention is to call it, move it to its own input line;
+otherwise, escape it by prepending
+.Sq \e& .
+.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, or the next argument after an
+.Ic \&Ns
+macro is an isolated closing delimiter.
+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 "first macro on line"
+Inside a
+.Ic \&Bl Fl column
+list, a
+.Ic \&Ta
+macro occurs as the first macro on a line, which is not portable.
+.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 , eqn
+The macro name is missing from a macro definition request,
+or an
+.Xr eqn 7
+control statement or operation keyword lacks its required argument.
+.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 block"
+.Pq mdoc , man
+A
+.Ic \&Bd ,
+.Ic \&Bk ,
+.Ic \&Bl ,
+.Ic \&D1 ,
+.Ic \&Dl ,
+.Ic \&MT ,
+.Ic \&RS ,
+or
+.Ic \&UR
+block contains nothing in its body and will produce no output.
+.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 "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 "missing function name, using \(dq\(dq"
+.Pq mdoc
+The
+.Ic \&Fo
+macro is called without an argument.
+No function name is printed.
+.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 argument, using next line"
+.Pq mdoc
+An
+.Ic \&It
+macro in a
+.Ic \&Bd Fl column
+list has no arguments.
+While
+.Nm
+uses the text or macros of the following line, if any, for the cell,
+other formatters may misformat the list.
+.It Sy "missing font type, using \efR"
+.Pq mdoc
+A
+.Ic \&Bf
+macro has no argument.
+It switches to the default font.
+.It Sy "unknown font type, using \efR"
+.Pq mdoc
+The
+.Ic \&Bf
+argument is invalid.
+The default font is used instead.
+.It Sy "nothing follows prefix"
+.Pq mdoc
+A
+.Ic \&Pf
+macro has no argument, or only one argument and no macro follows
+on the same input line.
+This defeats its purpose; in particular, spacing is not suppressed
+before the text or macros following on the next input line.
+.It Sy "empty reference block"
+.Pq mdoc
+An
+.Ic \&Rs
+macro is immediately followed by an
+.Ic \&Re
+macro on the next input line.
+Such an empty block does not produce any output.
+.It Sy "missing section argument"
+.Pq mdoc
+An
+.Ic \&Xr
+macro lacks its second, section number argument.
+The first argument, i.e. the name, is printed, but without subsequent
+parentheses.
+.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.
+.It Sy "missing option string, using \(dq\(dq"
+.Pq man
+The
+.Ic \&OP
+macro is invoked without any argument.
+An empty pair of square brackets is shown.
+.It Sy "missing resource identifier, using \(dq\(dq"
+.Pq man
+The
+.Ic \&MT
+or
+.Ic \&UR
+macro is invoked without any argument.
+An empty pair of angle brackets is shown.
+.It Sy "missing eqn box, using \(dq\(dq"
+.Pq eqn
+A diacritic mark or a binary operator is found,
+but there is nothing to the left of it.
+An empty box is inserted.
+.El
+.Ss "Warnings related to bad macro arguments"
+.Bl -ohang
+.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 "wrong number of cells"
+In a line of a
+.Ic \&Bl Fl column
+list, the number of tabs or
+.Ic \&Ta
+macros is less than the number expected from the list header line
+or exceeds the expected number by more than one.
+Missing cells remain empty, and all cells exceeding the number of
+columns are joined into one single cell.
+.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 "comma in function argument"
+.Pq mdoc
+An argument of an
+.Ic \&Fa
+or
+.Ic \&Fn
+macro contains a comma; it should probably be split into two arguments.
+.It Sy "parenthesis in function name"
+.Pq mdoc
+The first argument of an
+.Ic \&Fc
+or
+.Ic \&Fn
+macro contains an opening or closing parenthesis; that's probably wrong,
+parentheses are added automatically.
+.It Sy "unknown library name"
+.Pq mdoc, not on Ox
+An
+.Ic \&Lb
+macro has an unknown name argument and will be rendered as
+.Qq library Dq Ar name .
+.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 "argument contains two font escapes"
+.Pq roff
+The second argument of a
+.Ic char
+request contains more than one font escape sequence.
+A wrong font may remain active after using the character.
+.It Sy "unknown font, skipping request"
+.Pq man , tbl
+A
+.Xr roff 7
+.Ic \&ft
+request or a
+.Xr tbl 7
+.Ic \&f
+layout modifier has an unknown
+.Ar font
+argument.
+.It Sy "odd number of characters in request"
+.Pq roff
+A
+.Ic \&tr
+request contains an odd number of characters.
+The last character is mapped to the blank character.
+.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 formatted like
+.Ic \&sp
+requests.
+To request a paragraph break, use
+.Ic \&Pp
+instead of a blank line.
+.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 "new sentence, new line"
+.Pq mdoc
+A new sentence starts in the middle of a text line.
+Start it on a new input line to help formatters produce correct spacing.
+.It Sy "invalid escape sequence"
+.Pq roff
+An escape sequence has an invalid opening argument delimiter, lacks the
+closing argument delimiter, the argument is of an invalid form, or it is
+a character escape sequence with an invalid name.
+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 escape, printing literally"
+.Pq roff
+In an escape sequence, the first character
+right after the leading backslash is invalid.
+That character is printed literally,
+which is equivalent to ignoring the backslash.
+.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 "Warnings related to tables"
+.Bl -ohang
+.It Sy "tbl line starts with span"
+.Pq tbl
+The first cell in a table layout line is a horizontal span
+.Pq Sq Cm s .
+Data provided for this cell is ignored, and nothing is printed in the cell.
+.It Sy "tbl column starts with span"
+.Pq tbl
+The first line of a table layout specification
+requests a vertical span
+.Pq Sq Cm ^ .
+Data provided for this cell is ignored, and nothing is printed in the cell.
+.It Sy "skipping vertical bar in tbl layout"
+.Pq tbl
+A table layout specification contains more than two consecutive vertical bars.
+A double bar is printed, all additional bars are discarded.
+.El
+.Ss "Errors related to tables"
+.Bl -ohang
+.It Sy "non-alphabetic character in tbl options"
+.Pq tbl
+The table options line contains a character other than a letter,
+blank, or comma where the beginning of an option name is expected.
+The character is ignored.
+.It Sy "skipping unknown tbl option"
+.Pq tbl
+The table options line contains a string of letters that does not
+match any known option name.
+The word is ignored.
+.It Sy "missing tbl option argument"
+.Pq tbl
+A table option that requires an argument is not followed by an
+opening parenthesis, or the opening parenthesis is immediately
+followed by a closing parenthesis.
+The option is ignored.
+.It Sy "wrong tbl option argument size"
+.Pq tbl
+A table option argument contains an invalid number of characters.
+Both the option and the argument are ignored.
+.It Sy "empty tbl layout"
+.Pq tbl
+A table layout specification is completely empty,
+specifying zero lines and zero columns.
+As a fallback, a single left-justified column is used.
+.It Sy "invalid character in tbl layout"
+.Pq tbl
+A table layout specification contains a character that can neither
+be interpreted as a layout key character nor as a layout modifier,
+or a modifier precedes the first key.
+The invalid character is discarded.
+.It Sy "unmatched parenthesis in tbl layout"
+.Pq tbl
+A table layout specification contains an opening parenthesis,
+but no matching closing parenthesis.
+The rest of the input line, starting from the parenthesis, has no effect.
+.It Sy "tbl without any data cells"
+.Pq tbl
+A table does not contain any data cells.
+It will probably produce no output.
+.It Sy "ignoring data in spanned tbl cell"
+.Pq tbl
+A table cell is marked as a horizontal span
+.Pq Sq Cm s
+or vertical span
+.Pq Sq Cm ^
+in the table layout, but it contains data.
+The data is ignored.
+.It Sy "ignoring extra tbl data cells"
+.Pq tbl
+A data line contains more cells than the corresponding layout line.
+The data in the extra cells is ignored.
+.It Sy "data block open at end of tbl"
+.Pq tbl
+A data block is opened with
+.Cm T{ ,
+but never closed with a matching
+.Cm T} .
+The remaining data lines of the table are all put into one cell,
+and any remaining cells stay empty.
+.El
+.Ss "Errors related to roff, mdoc, and man code"
+.Bl -ohang
+.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 "skipping late title macro"
+.Pq mdoc
+The
+.Ic \&Dt
+macro appears after the first non-prologue macro.
+Traditional formatters cannot handle this because
+they 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 "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 request outside macro"
+.Pq roff
+A
+.Ic shift
+or
+.Ic return
+request occurs outside any macro definition and has no effect.
+.It Sy "skipping insecure request"
+.Pq roff
+An input file attempted to run a shell command
+or to read or write an external file.
+Such attempts are denied for security reasons.
+.It Sy "skipping item outside list"
+.Pq mdoc , eqn
+An
+.Ic \&It
+macro occurs outside any
+.Ic \&Bl
+list, or an
+.Xr eqn 7
+.Ic above
+delimiter occurs outside any pile.
+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 \&ME , \&RE
+or
+.Ic \&UE
+macro, an
+.Xr eqn 7
+right delimiter or closing brace, 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 "fewer RS blocks open, skipping"
+.Pq man
+The
+.Ic \&RE
+macro is invoked with an argument, but less than the specified number of
+.Ic \&RS
+blocks is open.
+The
+.Ic \&RE
+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 "appending missing end of block"
+.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 \&MT , \&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 "using macro argument outside macro"
+.Pq roff
+The escape sequence \e$ occurs outside any macro definition
+and expands to the empty string.
+.It Sy "argument number is not numeric"
+.Pq roff
+The argument of the escape sequence \e$ is not a digit;
+the escape sequence expands to the empty string.
+.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 argument is ignored including the file name following it.
+.It Sy "skipping display without arguments"
+.Pq mdoc
+A
+.Ic \&Bd
+block macro does not have any arguments.
+The block is discarded, and the block content is displayed in
+whatever mode was active before the block.
+.It Sy "missing list type, using -item"
+.Pq mdoc
+A
+.Ic \&Bl
+macro fails to specify the list type.
+.It Sy "argument is not numeric, using 1"
+.Pq roff
+The argument of a
+.Ic \&ce
+request is not a number.
+.It Sy "argument is not a character"
+.Pq roff
+The first argument of a
+.Ic char
+request is neither a single ASCII character
+nor a single character escape sequence.
+The request is ignored including all its arguments.
+.It Sy "missing manual name, using \(dq\(dq"
+.Pq mdoc
+The first call to
+.Ic \&Nm ,
+or any call in the NAME section, 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 , eqn
+An
+.Ic \&it
+request or an
+.Xr eqn 7
+.Ic \&size
+or
+.Ic \&gsize
+statement has a non-numeric or negative argument or no argument at all.
+The invalid request or statement is ignored.
+.It Sy "excessive shift"
+.Pq roff
+The argument of a
+.Ic shift
+request is larger than the number of arguments of the macro that is
+currently being executed.
+All macro arguments are deleted and \en(.$ is set to zero.
+.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.
+.Nm
+only shows the path as it appears behind
+.Ic \&so .
+.It Sy ".so request failed"
+.Pq roff
+Servicing a
+.Ic \&so
+request requires reading an external file, but the file could not be
+opened.
+.Nm
+only shows the path as it appears behind
+.Ic \&so .
+.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 \&Lp ,
+.Ic \&Pp ,
+.Ic \&Re ,
+.Ic \&Rs ,
+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 \&EQ
+or
+.Ic \&EN
+macro, or a
+.Xr roff 7
+.Ic \&br ,
+.Ic \&fi ,
+or
+.Ic \&nf
+request or
+.Sq \&..
+block closing request is invoked with at least one argument.
+All arguments are ignored.
+.It Sy "skipping excess arguments"
+.Pq mdoc , man , roff
+A macro or request is invoked with too many arguments:
+.Bl -dash -offset 2n -width 2n -compact
+.It
+.Ic \&Fo ,
+.Ic \&MT ,
+.Ic \&PD ,
+.Ic \&RS ,
+.Ic \&UR ,
+.Ic \&ft ,
+or
+.Ic \&sp
+with more than one argument
+.It
+.Ic \&An
+with another argument after
+.Fl split
+or
+.Fl nosplit
+.It
+.Ic \&RE
+with more than one argument or with a non-integer argument
+.It
+.Ic \&OP
+or a request of the
+.Ic \&de
+family with more than two arguments
+.It
+.Ic \&Dt
+with more than three arguments
+.It
+.Ic \&TH
+with more than five arguments
+.It
+.Ic \&Bd ,
+.Ic \&Bk ,
+or
+.Ic \&Bl
+with invalid arguments
+.El
+The excess arguments are ignored.
+.El
+.Ss Unsupported features
+.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 "unsupported control character"
+.Pq roff
+An ASCII control character supported by other
+.Xr roff 7
+implementations but not by
+.Nm
+was found in an input file.
+It is replaced by a question mark.
+.It Sy "unsupported escape sequence"
+.Pq roff
+An input file contains an escape sequence supported by GNU troff
+or Heirloom troff but not by
+.Nm ,
+and it is likely that this will cause information loss
+or considerable misformatting.
+.It Sy "unsupported roff request"
+.Pq roff
+An input file contains a
+.Xr roff 7
+request supported by GNU troff or Heirloom troff but not by
+.Nm ,
+and it is likely that this will cause information loss
+or considerable misformatting.
+.It Sy "eqn delim option in tbl"
+.Pq eqn , tbl
+The options line of a table defines equation delimiters.
+Any equation source code contained in the table will be printed unformatted.
+.It Sy "unsupported table layout modifier"
+.Pq tbl
+A table layout specification contains an
+.Sq Cm m
+modifier.
+The modifier is discarded.
+.It Sy "ignoring macro in table"
+.Pq tbl , mdoc , man
+A table contains an invocation of an
+.Xr mdoc 7
+or
+.Xr man 7
+macro or of an undefined macro.
+The macro is ignored, and its arguments are handled
+as if they were a text line.
+.El
+.Ss Bad command line arguments
+.Bl -ohang
+.It Sy "bad command line argument"
+The argument following one of the
+.Fl IKMmOTW
+command line options is invalid, or a
+.Ar file
+given as a command line argument cannot be opened.
+.It Sy "duplicate command line argument"
+The
+.Fl I
+command line option was specified twice.
+.It Sy "option has a superfluous value"
+An argument to the
+.Fl O
+option has a value but does not accept one.
+.It Sy "missing option value"
+An argument to the
+.Fl O
+option has no argument but requires one.
+.It Sy "bad option value"
+An argument to the
+.Fl O
+.Cm indent
+or
+.Cm width
+option has an invalid value.
+.It Sy "duplicate option value"
+The same
+.Fl O
+option is specified more than once.
+.It Sy "no such tag"
+The
+.Fl O Cm tag
+option was specified but the tag was not found in any of the displayed
+manual pages.
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr man 1 ,
+.Xr eqn 7 ,
+.Xr man 7 ,
+.Xr mandoc_char 7 ,
+.Xr mdoc 7 ,
+.Xr roff 7 ,
+.Xr tbl 7
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Ox 4.8 .
+The option
+.Fl I
+appeared in
+.Ox 5.2 ,
+and
+.Fl aCcfhKklMSsw
+in
+.Ox 5.7 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and is maintained by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/usr.bin/mandoc/mandoc.c b/usr.bin/mandoc/mandoc.c
new file mode 100644
index 0000000..0b2d301
--- /dev/null
+++ b/usr.bin/mandoc/mandoc.c
@@ -0,0 +1,657 @@
+/* $OpenBSD: mandoc.c,v 1.85 2020/01/19 16:16:32 schwarze Exp $ */
+/*
+ * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011-2015, 2017-2020 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.
+ */
+#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_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+
+static int a2time(time_t *, const char *, const char *);
+static char *time2a(time_t);
+
+
+enum mandoc_esc
+mandoc_font(const char *cp, int sz)
+{
+ switch (sz) {
+ case 0:
+ return ESCAPE_FONTPREV;
+ case 1:
+ switch (cp[0]) {
+ case 'B':
+ case '3':
+ return ESCAPE_FONTBOLD;
+ case 'I':
+ case '2':
+ return ESCAPE_FONTITALIC;
+ case 'P':
+ return ESCAPE_FONTPREV;
+ case 'R':
+ case '1':
+ return ESCAPE_FONTROMAN;
+ case '4':
+ return ESCAPE_FONTBI;
+ default:
+ return ESCAPE_ERROR;
+ }
+ case 2:
+ switch (cp[0]) {
+ case 'B':
+ switch (cp[1]) {
+ case 'I':
+ return ESCAPE_FONTBI;
+ default:
+ return ESCAPE_ERROR;
+ }
+ case 'C':
+ switch (cp[1]) {
+ case 'B':
+ return ESCAPE_FONTBOLD;
+ case 'I':
+ return ESCAPE_FONTITALIC;
+ case 'R':
+ case 'W':
+ return ESCAPE_FONTCW;
+ default:
+ return ESCAPE_ERROR;
+ }
+ default:
+ return ESCAPE_ERROR;
+ }
+ default:
+ return ESCAPE_ERROR;
+ }
+}
+
+enum mandoc_esc
+mandoc_escape(const char **end, const char **start, int *sz)
+{
+ const char *local_start;
+ int local_sz, c, i;
+ 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;
+
+ /*
+ * Treat "\E" just like "\";
+ * it only makes a difference in copy mode.
+ */
+
+ if (**end == 'E')
+ ++*end;
+
+ /*
+ * 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 '[':
+ if (**start == ' ') {
+ ++*end;
+ return ESCAPE_ERROR;
+ }
+ gly = ESCAPE_SPECIAL;
+ term = ']';
+ break;
+ case 'C':
+ if ('\'' != **start)
+ return ESCAPE_ERROR;
+ *start = ++*end;
+ gly = ESCAPE_SPECIAL;
+ term = '\'';
+ break;
+
+ /*
+ * Escapes taking no arguments at all.
+ */
+ case '!':
+ case '?':
+ return ESCAPE_UNSUPP;
+ case '%':
+ case '&':
+ case ')':
+ case ',':
+ case '/':
+ case '^':
+ case 'a':
+ case 'd':
+ case 'r':
+ case 't':
+ case 'u':
+ case '{':
+ case '|':
+ case '}':
+ return ESCAPE_IGNORE;
+ case 'c':
+ return ESCAPE_NOSPACE;
+ case 'p':
+ return ESCAPE_BREAK;
+
+ /*
+ * 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':
+ case 'f':
+ case 'g':
+ case 'k':
+ case 'M':
+ case 'm':
+ case 'n':
+ case 'O':
+ case 'V':
+ case 'Y':
+ gly = (*start)[-1] == 'f' ? ESCAPE_FONT : ESCAPE_IGNORE;
+ switch (**start) {
+ case '(':
+ if ((*start)[-1] == 'O')
+ gly = ESCAPE_ERROR;
+ *start = ++*end;
+ *sz = 2;
+ break;
+ case '[':
+ if ((*start)[-1] == 'O')
+ gly = (*start)[1] == '5' ?
+ ESCAPE_UNSUPP : ESCAPE_ERROR;
+ *start = ++*end;
+ term = ']';
+ break;
+ default:
+ if ((*start)[-1] == 'O') {
+ switch (**start) {
+ case '0':
+ gly = ESCAPE_UNSUPP;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ break;
+ default:
+ gly = ESCAPE_ERROR;
+ break;
+ }
+ }
+ *sz = 1;
+ break;
+ }
+ break;
+ case '*':
+ if (strncmp(*start, "(.T", 3) != 0)
+ abort();
+ gly = ESCAPE_DEVICE;
+ *start = ++*end;
+ *sz = 2;
+ 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':
+ case 'b':
+ case 'D':
+ case 'R':
+ case 'X':
+ case 'Z':
+ gly = ESCAPE_IGNORE;
+ /* FALLTHROUGH */
+ case 'o':
+ if (**start == '\0')
+ return ESCAPE_ERROR;
+ if (gly == ESCAPE_ERROR)
+ gly = ESCAPE_OVERSTRIKE;
+ 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':
+ case 'H':
+ case 'L':
+ case 'l':
+ case 'S':
+ case 'v':
+ case 'x':
+ if (strchr(" %&()*+-./0123456789:<=>", **start)) {
+ if ('\0' != **start)
+ ++*end;
+ return ESCAPE_ERROR;
+ }
+ switch ((*start)[-1]) {
+ case 'h':
+ gly = ESCAPE_HORIZ;
+ break;
+ case 'l':
+ gly = ESCAPE_HLINE;
+ break;
+ default:
+ gly = ESCAPE_IGNORE;
+ break;
+ }
+ 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)
+ *start = ++*end;
+
+ switch (**end) {
+ case '(':
+ *start = ++*end;
+ *sz = 2;
+ break;
+ case '[':
+ *start = ++*end;
+ term = ']';
+ break;
+ case '\'':
+ *start = ++*end;
+ term = '\'';
+ break;
+ case '3':
+ case '2':
+ case '1':
+ *sz = (*end)[-1] == 's' &&
+ isdigit((unsigned char)(*end)[1]) ? 2 : 1;
+ break;
+ default:
+ *sz = 1;
+ break;
+ }
+
+ break;
+
+ /*
+ * Several special characters can be encoded as
+ * one-byte escape sequences without using \[].
+ */
+ case ' ':
+ case '\'':
+ case '-':
+ case '.':
+ case '0':
+ case ':':
+ case '_':
+ case '`':
+ case 'e':
+ case '~':
+ gly = ESCAPE_SPECIAL;
+ /* FALLTHROUGH */
+ default:
+ if (gly == ESCAPE_ERROR)
+ gly = ESCAPE_UNDEF;
+ *start = --*end;
+ *sz = 1;
+ break;
+ }
+
+ /*
+ * 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;
+
+ /*
+ * The file chars.c only provides one common list
+ * of character names, but \[-] == \- is the only
+ * one of the characters with one-byte names that
+ * allows enclosing the name in brackets.
+ */
+ if (gly == ESCAPE_SPECIAL && *sz == 1 && **start != '-')
+ return ESCAPE_ERROR;
+ } else {
+ assert(*sz > 0);
+ if ((size_t)*sz > strlen(*start))
+ return ESCAPE_ERROR;
+ *end += *sz;
+ }
+
+ /* Run post-processors. */
+
+ switch (gly) {
+ case ESCAPE_FONT:
+ gly = mandoc_font(*start, *sz);
+ break;
+ case ESCAPE_SPECIAL:
+ if (**start == 'c') {
+ if (*sz < 6 || *sz > 7 ||
+ strncmp(*start, "char", 4) != 0 ||
+ (int)strspn(*start + 4, "0123456789") + 4 < *sz)
+ break;
+ c = 0;
+ for (i = 4; i < *sz; i++)
+ c = 10 * c + ((*start)[i] - '0');
+ if (c < 0x21 || (c > 0x7e && c < 0xa0) || c > 0xff)
+ break;
+ *start += 4;
+ *sz -= 4;
+ gly = ESCAPE_NUMBERED;
+ break;
+ }
+
+ /*
+ * Unicode escapes are defined in groff as \[u0000]
+ * to \[u10FFFF], where the contained value must be
+ * a valid Unicode codepoint. Here, however, only
+ * check the length and range.
+ */
+ if (**start != 'u' || *sz < 5 || *sz > 7)
+ break;
+ if (*sz == 7 && ((*start)[1] != '1' || (*start)[2] != '0'))
+ break;
+ if (*sz == 6 && (*start)[1] == '0')
+ break;
+ if (*sz == 5 && (*start)[1] == 'D' &&
+ strchr("89ABCDEF", (*start)[2]) != NULL)
+ break;
+ if ((int)strspn(*start + 1, "0123456789ABCDEFabcdef")
+ + 1 == *sz)
+ gly = ESCAPE_UNICODE;
+ break;
+ default:
+ break;
+ }
+
+ return gly;
+}
+
+static int
+a2time(time_t *t, const char *fmt, const char *p)
+{
+ struct tm tm;
+ char *pp;
+
+ memset(&tm, 0, sizeof(struct tm));
+
+ pp = strptime(p, fmt, &tm);
+ 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;
+
+ buf = NULL;
+ tm = localtime(&t);
+ if (tm == NULL)
+ goto fail;
+
+ /*
+ * 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 ((ssz = strftime(p, 10 + 1, "%B ", tm)) == 0)
+ goto fail;
+ p += (int)ssz;
+
+ /*
+ * The output format is just "%d" here, not "%2d" or "%02d".
+ * That's also the reason why we can't just format the
+ * date as a whole with "%B %e, %Y" or "%B %d, %Y".
+ * Besides, the present approach is less prone to buffer
+ * overflows, in case anybody should ever introduce the bug
+ * of looking at LC_TIME.
+ */
+
+ isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday);
+ if (isz < 0 || isz > 4)
+ goto fail;
+ p += isz;
+
+ if (strftime(p, 4 + 1, "%Y", tm) == 0)
+ goto fail;
+ return buf;
+
+fail:
+ free(buf);
+ return mandoc_strdup("");
+}
+
+char *
+mandoc_normdate(struct roff_node *nch, struct roff_node *nbl)
+{
+ char *cp;
+ time_t t;
+
+ /* No date specified. */
+
+ if (nch == NULL) {
+ if (nbl == NULL)
+ mandoc_msg(MANDOCERR_DATE_MISSING, 0, 0, NULL);
+ else
+ mandoc_msg(MANDOCERR_DATE_MISSING, nbl->line,
+ nbl->pos, "%s", roff_name[nbl->tok]);
+ return mandoc_strdup("");
+ }
+ if (*nch->string == '\0') {
+ mandoc_msg(MANDOCERR_DATE_MISSING, nch->line,
+ nch->pos, "%s", roff_name[nbl->tok]);
+ return mandoc_strdup("");
+ }
+ if (strcmp(nch->string, "$" "Mdocdate$") == 0)
+ return time2a(time(NULL));
+
+ /* Valid mdoc(7) date format. */
+
+ if (a2time(&t, "$" "Mdocdate: %b %d %Y $", nch->string) ||
+ a2time(&t, "%b %d, %Y", nch->string)) {
+ cp = time2a(t);
+ if (t > time(NULL) + 86400)
+ mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line,
+ nch->pos, "%s %s", roff_name[nbl->tok], cp);
+ else if (*nch->string != '$' &&
+ strcmp(nch->string, cp) != 0)
+ mandoc_msg(MANDOCERR_DATE_NORM, nch->line,
+ nch->pos, "%s %s", roff_name[nbl->tok], cp);
+ return cp;
+ }
+
+ /* In man(7), do not warn about the legacy format. */
+
+ if (a2time(&t, "%Y-%m-%d", nch->string) == 0)
+ mandoc_msg(MANDOCERR_DATE_BAD, nch->line, nch->pos,
+ "%s %s", roff_name[nbl->tok], nch->string);
+ else if (t > time(NULL) + 86400)
+ mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line, nch->pos,
+ "%s %s", roff_name[nbl->tok], nch->string);
+ else if (nbl->tok == MDOC_Dd)
+ mandoc_msg(MANDOCERR_DATE_LEGACY, nch->line, nch->pos,
+ "Dd %s", nch->string);
+
+ /* Use any non-mdoc(7) date verbatim. */
+
+ return mandoc_strdup(nch->string);
+}
+
+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 '\"':
+ case '\'':
+ case ']':
+ case ')':
+ if (0 == found)
+ enclosed = 1;
+ break;
+ case '.':
+ case '!':
+ 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/usr.bin/mandoc/mandoc.css b/usr.bin/mandoc/mandoc.css
new file mode 100644
index 0000000..3c4ea18
--- /dev/null
+++ b/usr.bin/mandoc/mandoc.css
@@ -0,0 +1,360 @@
+/* $OpenBSD: mandoc.css,v 1.33 2019/06/02 16:50:46 schwarze Exp $ */
+/*
+ * Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
+ *
+ * Written by Ingo Schwarze <schwarze@openbsd.org>.
+ * I place this file into the public domain.
+ * Permission to use, copy, modify, and distribute it for any purpose
+ * with or without fee is hereby granted, without any conditions.
+ */
+
+/* Global defaults. */
+
+html { max-width: 65em;
+ --bg: #FFFFFF;
+ --fg: #000000; }
+body { background: var(--bg);
+ color: var(--fg);
+ font-family: Helvetica,Arial,sans-serif; }
+h1 { font-size: 110%; }
+table { margin-top: 0em;
+ margin-bottom: 0em;
+ border-collapse: collapse; }
+/* Some browsers set border-color in a browser style for tbody,
+ * but not for table, resulting in inconsistent border styling. */
+tbody { border-color: inherit; }
+tr { border-color: inherit; }
+td { vertical-align: top;
+ padding-left: 0.2em;
+ padding-right: 0.2em;
+ border-color: inherit; }
+ul, ol, dl { margin-top: 0em;
+ margin-bottom: 0em; }
+li, dt { margin-top: 1em; }
+
+.permalink { border-bottom: thin dotted;
+ color: inherit;
+ font: inherit;
+ text-decoration: inherit; }
+* { clear: both }
+
+/* Search form and search results. */
+
+fieldset { border: thin solid silver;
+ border-radius: 1em;
+ text-align: center; }
+input[name=expr] {
+ width: 25%; }
+
+table.results { margin-top: 1em;
+ margin-left: 2em;
+ font-size: smaller; }
+
+/* Header and footer lines. */
+
+table.head { width: 100%;
+ border-bottom: 1px dotted #808080;
+ margin-bottom: 1em;
+ font-size: smaller; }
+td.head-vol { text-align: center; }
+td.head-rtitle {
+ text-align: right; }
+
+table.foot { width: 100%;
+ border-top: 1px dotted #808080;
+ margin-top: 1em;
+ font-size: smaller; }
+td.foot-os { text-align: right; }
+
+/* Sections and paragraphs. */
+
+.manual-text {
+ margin-left: 3.8em; }
+.Nd { }
+section.Sh { }
+h1.Sh { margin-top: 1.2em;
+ margin-bottom: 0.6em;
+ margin-left: -3.2em; }
+section.Ss { }
+h2.Ss { margin-top: 1.2em;
+ margin-bottom: 0.6em;
+ margin-left: -1.2em;
+ font-size: 105%; }
+.Pp { margin: 0.6em 0em; }
+.Sx { }
+.Xr { }
+
+/* Displays and lists. */
+
+.Bd { }
+.Bd-indent { margin-left: 3.8em; }
+
+.Bl-bullet { list-style-type: disc;
+ padding-left: 1em; }
+.Bl-bullet > li { }
+.Bl-dash { list-style-type: none;
+ padding-left: 0em; }
+.Bl-dash > li:before {
+ content: "\2014 "; }
+.Bl-item { list-style-type: none;
+ padding-left: 0em; }
+.Bl-item > li { }
+.Bl-compact > li {
+ margin-top: 0em; }
+
+.Bl-enum { padding-left: 2em; }
+.Bl-enum > li { }
+.Bl-compact > li {
+ margin-top: 0em; }
+
+.Bl-diag { }
+.Bl-diag > dt {
+ font-style: normal;
+ font-weight: bold; }
+.Bl-diag > dd {
+ margin-left: 0em; }
+.Bl-hang { }
+.Bl-hang > dt { }
+.Bl-hang > dd {
+ margin-left: 5.5em; }
+.Bl-inset { }
+.Bl-inset > dt { }
+.Bl-inset > dd {
+ margin-left: 0em; }
+.Bl-ohang { }
+.Bl-ohang > dt { }
+.Bl-ohang > dd {
+ margin-left: 0em; }
+.Bl-tag { margin-top: 0.6em;
+ margin-left: 5.5em; }
+.Bl-tag > dt {
+ float: left;
+ margin-top: 0em;
+ margin-left: -5.5em;
+ padding-right: 0.5em;
+ vertical-align: top; }
+.Bl-tag > dd {
+ clear: right;
+ width: 100%;
+ margin-top: 0em;
+ margin-left: 0em;
+ margin-bottom: 0.6em;
+ vertical-align: top;
+ overflow: auto; }
+.Bl-compact { margin-top: 0em; }
+.Bl-compact > dd {
+ margin-bottom: 0em; }
+.Bl-compact > dt {
+ margin-top: 0em; }
+
+.Bl-column { }
+.Bl-column > tbody > tr { }
+.Bl-column > tbody > tr > td {
+ margin-top: 1em; }
+.Bl-compact > tbody > tr > td {
+ margin-top: 0em; }
+
+.Rs { font-style: normal;
+ font-weight: normal; }
+.RsA { }
+.RsB { font-style: italic;
+ font-weight: normal; }
+.RsC { }
+.RsD { }
+.RsI { font-style: italic;
+ font-weight: normal; }
+.RsJ { font-style: italic;
+ font-weight: normal; }
+.RsN { }
+.RsO { }
+.RsP { }
+.RsQ { }
+.RsR { }
+.RsT { text-decoration: underline; }
+.RsU { }
+.RsV { }
+
+.eqn { }
+.tbl td { vertical-align: middle; }
+
+.HP { margin-left: 3.8em;
+ text-indent: -3.8em; }
+
+/* Semantic markup for command line utilities. */
+
+table.Nm { }
+code.Nm { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+.Fl { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+.Cm { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+.Ar { font-style: italic;
+ font-weight: normal; }
+.Op { display: inline; }
+.Ic { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+.Ev { font-style: normal;
+ font-weight: normal;
+ font-family: monospace; }
+.Pa { font-style: italic;
+ font-weight: normal; }
+
+/* Semantic markup for function libraries. */
+
+.Lb { }
+code.In { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+a.In { }
+.Fd { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+.Ft { font-style: italic;
+ font-weight: normal; }
+.Fn { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+.Fa { font-style: italic;
+ font-weight: normal; }
+.Vt { font-style: italic;
+ font-weight: normal; }
+.Va { font-style: italic;
+ font-weight: normal; }
+.Dv { font-style: normal;
+ font-weight: normal;
+ font-family: monospace; }
+.Er { font-style: normal;
+ font-weight: normal;
+ font-family: monospace; }
+
+/* Various semantic markup. */
+
+.An { }
+.Lk { }
+.Mt { }
+.Cd { font-style: normal;
+ font-weight: bold;
+ font-family: inherit; }
+.Ad { font-style: italic;
+ font-weight: normal; }
+.Ms { font-style: normal;
+ font-weight: bold; }
+.St { }
+.Ux { }
+
+/* Physical markup. */
+
+.Bf { display: inline; }
+.No { font-style: normal;
+ font-weight: normal; }
+.Em { font-style: italic;
+ font-weight: normal; }
+.Sy { font-style: normal;
+ font-weight: bold; }
+.Li { font-style: normal;
+ font-weight: normal;
+ font-family: monospace; }
+
+/* Tooltip support. */
+
+h1.Sh, h2.Ss { position: relative; }
+.An, .Ar, .Cd, .Cm, .Dv, .Em, .Er, .Ev, .Fa, .Fd, .Fl, .Fn, .Ft,
+.Ic, code.In, .Lb, .Lk, .Ms, .Mt, .Nd, code.Nm, .Pa, .Rs,
+.St, .Sx, .Sy, .Va, .Vt, .Xr {
+ display: inline-block;
+ position: relative; }
+
+.An::before { content: "An"; }
+.Ar::before { content: "Ar"; }
+.Cd::before { content: "Cd"; }
+.Cm::before { content: "Cm"; }
+.Dv::before { content: "Dv"; }
+.Em::before { content: "Em"; }
+.Er::before { content: "Er"; }
+.Ev::before { content: "Ev"; }
+.Fa::before { content: "Fa"; }
+.Fd::before { content: "Fd"; }
+.Fl::before { content: "Fl"; }
+.Fn::before { content: "Fn"; }
+.Ft::before { content: "Ft"; }
+.Ic::before { content: "Ic"; }
+code.In::before { content: "In"; }
+.Lb::before { content: "Lb"; }
+.Lk::before { content: "Lk"; }
+.Ms::before { content: "Ms"; }
+.Mt::before { content: "Mt"; }
+.Nd::before { content: "Nd"; }
+code.Nm::before { content: "Nm"; }
+.Pa::before { content: "Pa"; }
+.Rs::before { content: "Rs"; }
+h1.Sh::before { content: "Sh"; }
+h2.Ss::before { content: "Ss"; }
+.St::before { content: "St"; }
+.Sx::before { content: "Sx"; }
+.Sy::before { content: "Sy"; }
+.Va::before { content: "Va"; }
+.Vt::before { content: "Vt"; }
+.Xr::before { content: "Xr"; }
+
+.An::before, .Ar::before, .Cd::before, .Cm::before,
+.Dv::before, .Em::before, .Er::before, .Ev::before,
+.Fa::before, .Fd::before, .Fl::before, .Fn::before, .Ft::before,
+.Ic::before, code.In::before, .Lb::before, .Lk::before,
+.Ms::before, .Mt::before, .Nd::before, code.Nm::before,
+.Pa::before, .Rs::before,
+h1.Sh::before, h2.Ss::before, .St::before, .Sx::before, .Sy::before,
+.Va::before, .Vt::before, .Xr::before {
+ opacity: 0;
+ transition: .15s ease opacity;
+ pointer-events: none;
+ position: absolute;
+ bottom: 100%;
+ box-shadow: 0 0 .35em var(--fg);
+ padding: .15em .25em;
+ white-space: nowrap;
+ font-family: Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: bold;
+ background: var(--bg);
+ color: var(--fg); }
+.An:hover::before, .Ar:hover::before, .Cd:hover::before, .Cm:hover::before,
+.Dv:hover::before, .Em:hover::before, .Er:hover::before, .Ev:hover::before,
+.Fa:hover::before, .Fd:hover::before, .Fl:hover::before, .Fn:hover::before,
+.Ft:hover::before, .Ic:hover::before, code.In:hover::before,
+.Lb:hover::before, .Lk:hover::before, .Ms:hover::before, .Mt:hover::before,
+.Nd:hover::before, code.Nm:hover::before, .Pa:hover::before,
+.Rs:hover::before, h1.Sh:hover::before, h2.Ss:hover::before, .St:hover::before,
+.Sx:hover::before, .Sy:hover::before, .Va:hover::before, .Vt:hover::before,
+.Xr:hover::before {
+ opacity: 1;
+ pointer-events: inherit; }
+
+/* Overrides to avoid excessive margins on small devices. */
+
+@media (max-width: 37.5em) {
+.manual-text {
+ margin-left: 0.5em; }
+h1.Sh, h2.Ss { margin-left: 0em; }
+.Bd-indent { margin-left: 2em; }
+.Bl-hang > dd {
+ margin-left: 2em; }
+.Bl-tag { margin-left: 2em; }
+.Bl-tag > dt {
+ margin-left: -2em; }
+.HP { margin-left: 2em;
+ text-indent: -2em; }
+}
+
+/* Overrides for a dark color scheme for accessibility. */
+
+@media (prefers-color-scheme: dark) {
+html { --bg: #1E1F21;
+ --fg: #EEEFF1; }
+:link { color: #BAD7FF; }
+:visited { color: #F6BAFF; }
+}
diff --git a/usr.bin/mandoc/mandoc.h b/usr.bin/mandoc/mandoc.h
new file mode 100644
index 0000000..3a48c89
--- /dev/null
+++ b/usr.bin/mandoc/mandoc.h
@@ -0,0 +1,322 @@
+/* $OpenBSD: mandoc.h,v 1.210 2020/04/24 11:58:02 schwarze Exp $ */
+/*
+ * Copyright (c) 2012-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010, 2011, 2014 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 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.
+ *
+ * Error handling, escape sequence, and character utilities.
+ * Can be used by all code in the mandoc package.
+ */
+
+#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_STYLE, /* style suggestions */
+ MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */
+ MANDOCLEVEL_ERROR, /* input has been thrown away */
+ MANDOCLEVEL_UNSUPP, /* input needs unimplemented features */
+ 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_BASE, /* ===== start of base system conventions ===== */
+
+ MANDOCERR_MDOCDATE, /* Mdocdate found: Dd ... */
+ MANDOCERR_MDOCDATE_MISSING, /* Mdocdate missing: Dd ... */
+ MANDOCERR_ARCH_BAD, /* unknown architecture: Dt ... arch */
+ MANDOCERR_OS_ARG, /* operating system explicitly specified: Os ... */
+ MANDOCERR_RCS_MISSING, /* RCS id missing */
+ MANDOCERR_XR_BAD, /* referenced manual not found: Xr name sec */
+
+ MANDOCERR_STYLE, /* ===== start of style suggestions ===== */
+
+ MANDOCERR_DATE_LEGACY, /* legacy man(7) date format: Dd ... */
+ MANDOCERR_DATE_NORM, /* normalizing date format to: ... */
+ MANDOCERR_TITLE_CASE, /* lower case character in document title */
+ MANDOCERR_RCS_REP, /* duplicate RCS id: ... */
+ MANDOCERR_SEC_TYPO, /* possible typo in section name: Sh ... */
+ MANDOCERR_ARG_QUOTE, /* unterminated quoted argument */
+ MANDOCERR_MACRO_USELESS, /* useless macro: macro */
+ MANDOCERR_BX, /* consider using OS macro: macro */
+ MANDOCERR_ER_ORDER, /* errnos out of order: Er ... */
+ MANDOCERR_ER_REP, /* duplicate errno: Er ... */
+ MANDOCERR_DELIM, /* trailing delimiter: macro ... */
+ MANDOCERR_DELIM_NB, /* no blank before trailing delimiter: macro ... */
+ MANDOCERR_FI_SKIP, /* fill mode already enabled, skipping: fi */
+ MANDOCERR_NF_SKIP, /* fill mode already disabled, skipping: nf */
+ MANDOCERR_DASHDASH, /* verbatim "--", maybe consider using \(em */
+ MANDOCERR_FUNC, /* function name without markup: name() */
+ MANDOCERR_SPACE_EOL, /* whitespace at end of input line */
+ MANDOCERR_COMMENT_BAD, /* bad comment style */
+
+ 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_MSEC_MISSING, /* missing manual section, using "": macro */
+ MANDOCERR_MSEC_BAD, /* unknown manual section: Dt ... section */
+ MANDOCERR_MSEC_FILE, /* filename/section mismatch: ... */
+ MANDOCERR_DATE_MISSING, /* missing date, using "": [macro] */
+ MANDOCERR_DATE_BAD, /* cannot parse date, using it verbatim: date */
+ MANDOCERR_DATE_FUTURE, /* date in the future, using it anyway: date */
+ MANDOCERR_OS_MISSING, /* missing Os macro, using "" */
+ MANDOCERR_PROLOG_LATE, /* late prologue macro: macro */
+ 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_NONM, /* NAME section without Nm before Nd */
+ MANDOCERR_NAMESEC_NOND, /* NAME section without description */
+ MANDOCERR_NAMESEC_ND, /* description not at the end of NAME */
+ MANDOCERR_NAMESEC_BAD, /* bad NAME section content: macro */
+ MANDOCERR_NAMESEC_PUNCT, /* missing comma before name: Nm name */
+ MANDOCERR_ND_EMPTY, /* missing description line, using "" */
+ MANDOCERR_ND_LATE, /* description line outside NAME section */
+ 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 */
+ MANDOCERR_XR_SELF, /* cross reference to self: Xr name sec */
+ MANDOCERR_XR_ORDER, /* unusual Xr order: ... after ... */
+ MANDOCERR_XR_PUNCT, /* unusual Xr punctuation: ... after ... */
+ MANDOCERR_AN_MISSING, /* AUTHORS section without An macro */
+
+ /* related to macros and nesting */
+ MANDOCERR_MACRO_OBS, /* obsolete macro: macro */
+ MANDOCERR_MACRO_CALL, /* macro neither callable nor escaped: 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_TA_LINE, /* first macro on line: Ta */
+ MANDOCERR_BLK_LINE, /* line scope broken: macro breaks macro */
+ MANDOCERR_BLK_BLANK, /* skipping blank line in line scope */
+
+ /* 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_BLK_EMPTY, /* empty block: macro */
+ MANDOCERR_ARG_EMPTY, /* empty argument, using 0n: macro arg */
+ 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 6n */
+ MANDOCERR_EX_NONAME, /* missing utility name, using "": Ex */
+ MANDOCERR_FO_NOHEAD, /* missing function name, using "": Fo */
+ MANDOCERR_IT_NOHEAD, /* empty head in list item: Bl -type It */
+ MANDOCERR_IT_NOBODY, /* empty list item: Bl -type It */
+ MANDOCERR_IT_NOARG, /* missing argument, using next line: Bl -c It */
+ MANDOCERR_BF_NOFONT, /* missing font type, using \fR: Bf */
+ MANDOCERR_BF_BADFONT, /* unknown font type, using \fR: Bf font */
+ MANDOCERR_PF_SKIP, /* nothing follows prefix: Pf arg */
+ MANDOCERR_RS_EMPTY, /* empty reference block: Rs */
+ MANDOCERR_XR_NOSEC, /* missing section argument: Xr arg */
+ MANDOCERR_ARG_STD, /* missing -std argument, adding it: macro */
+ MANDOCERR_OP_EMPTY, /* missing option string, using "": OP */
+ MANDOCERR_UR_NOHEAD, /* missing resource identifier, using "": UR */
+ MANDOCERR_EQN_NOBOX, /* missing eqn box, using "": op */
+
+ /* related to bad arguments */
+ 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_BL_COL, /* wrong number of cells */
+ MANDOCERR_AT_BAD, /* unknown AT&T UNIX version: At version */
+ MANDOCERR_FA_COMMA, /* comma in function argument: arg */
+ MANDOCERR_FN_PAREN, /* parenthesis in function name: arg */
+ MANDOCERR_LB_BAD, /* unknown library name: Lb ... */
+ MANDOCERR_RS_BAD, /* invalid content in Rs block: macro */
+ MANDOCERR_SM_BAD, /* invalid Boolean argument: macro arg */
+ MANDOCERR_CHAR_FONT, /* argument contains two font escapes */
+ MANDOCERR_FT_BAD, /* unknown font, skipping request: ft font */
+ MANDOCERR_TR_ODD, /* odd number of characters in request: tr char */
+
+ /* related to plain text */
+ MANDOCERR_FI_BLANK, /* blank line in fill mode, using .sp */
+ MANDOCERR_FI_TAB, /* tab in filled text */
+ MANDOCERR_EOS, /* new sentence, new line */
+ MANDOCERR_ESC_BAD, /* invalid escape sequence: esc */
+ MANDOCERR_ESC_UNDEF, /* undefined escape, printing literally: char */
+ MANDOCERR_STR_UNDEF, /* undefined string, using "": name */
+
+ /* related to tables */
+ MANDOCERR_TBLLAYOUT_SPAN, /* tbl line starts with span */
+ MANDOCERR_TBLLAYOUT_DOWN, /* tbl column starts with span */
+ MANDOCERR_TBLLAYOUT_VERT, /* skipping vertical bar in tbl layout */
+
+ MANDOCERR_ERROR, /* ===== start of errors ===== */
+
+ /* related to tables */
+ MANDOCERR_TBLOPT_ALPHA, /* non-alphabetic character in tbl options */
+ MANDOCERR_TBLOPT_BAD, /* skipping unknown tbl option: option */
+ MANDOCERR_TBLOPT_NOARG, /* missing tbl option argument: option */
+ MANDOCERR_TBLOPT_ARGSZ, /* wrong tbl option argument size: option */
+ MANDOCERR_TBLLAYOUT_NONE, /* empty tbl layout */
+ MANDOCERR_TBLLAYOUT_CHAR, /* invalid character in tbl layout: char */
+ MANDOCERR_TBLLAYOUT_PAR, /* unmatched parenthesis in tbl layout */
+ MANDOCERR_TBLDATA_NONE, /* tbl without any data cells */
+ MANDOCERR_TBLDATA_SPAN, /* ignoring data in spanned tbl cell: data */
+ MANDOCERR_TBLDATA_EXTRA, /* ignoring extra tbl data cells: data */
+ MANDOCERR_TBLDATA_BLK, /* data block open at end of tbl: macro */
+
+ /* related to document structure and macros */
+ MANDOCERR_PROLOG_REP, /* duplicate prologue macro: macro */
+ MANDOCERR_DT_LATE, /* skipping late title macro: Dt args */
+ MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
+ MANDOCERR_CHAR_BAD, /* skipping bad character: number */
+ MANDOCERR_MACRO, /* skipping unknown macro: macro */
+ MANDOCERR_REQ_NOMAC, /* skipping request outside macro: ... */
+ MANDOCERR_REQ_INSEC, /* skipping insecure request: request */
+ 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_RE_NOTOPEN, /* fewer RS blocks open, skipping: RE arg */
+ 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_ARG_UNDEF, /* using macro argument outside macro */
+ MANDOCERR_ARG_NONUM, /* argument number is not numeric */
+ MANDOCERR_BD_FILE, /* NOT IMPLEMENTED: Bd -file */
+ MANDOCERR_BD_NOARG, /* skipping display without arguments: Bd */
+ MANDOCERR_BL_NOTYPE, /* missing list type, using -item: Bl */
+ MANDOCERR_CE_NONUM, /* argument is not numeric, using 1: ce ... */
+ MANDOCERR_CHAR_ARG, /* argument is not a character: char ... */
+ 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_SHIFT, /* excessive shift: ..., but max is ... */
+ MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
+ MANDOCERR_SO_FAIL, /* .so request failed */
+ MANDOCERR_TG_SPC, /* skipping tag containing whitespace: tag */
+ MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */
+ MANDOCERR_ARG_EXCESS, /* skipping excess arguments: macro ... args */
+ MANDOCERR_DIVZERO, /* divide by zero */
+
+ MANDOCERR_UNSUPP, /* ===== start of unsupported features ===== */
+
+ MANDOCERR_TOOLARGE, /* input too large */
+ MANDOCERR_CHAR_UNSUPP, /* unsupported control character: number */
+ MANDOCERR_ESC_UNSUPP, /* unsupported escape sequence: escape */
+ MANDOCERR_REQ_UNSUPP, /* unsupported roff request: request */
+ MANDOCERR_WHILE_NEST, /* nested .while loops */
+ MANDOCERR_WHILE_OUTOF, /* end of scope with open .while loop */
+ MANDOCERR_WHILE_INTO, /* end of .while loop in inner scope */
+ MANDOCERR_WHILE_FAIL, /* cannot continue this .while loop */
+ MANDOCERR_TBLOPT_EQN, /* eqn delim option in tbl: arg */
+ MANDOCERR_TBLLAYOUT_MOD, /* unsupported tbl layout modifier: m */
+ MANDOCERR_TBLMACRO, /* ignoring macro in table: macro */
+
+ MANDOCERR_BADARG, /* ===== start of bad invocations ===== */
+
+ MANDOCERR_BADARG_BAD, /* bad argument */
+ MANDOCERR_BADARG_DUPE, /* duplicate argument */
+ MANDOCERR_BADVAL, /* does not take a value */
+ MANDOCERR_BADVAL_MISS, /* missing argument value */
+ MANDOCERR_BADVAL_BAD, /* bad argument value */
+ MANDOCERR_BADVAL_DUPE, /* duplicate argument value */
+ MANDOCERR_TAG, /* no such tag */
+
+ MANDOCERR_SYSERR, /* ===== start of system errors ===== */
+
+ MANDOCERR_DUP,
+ MANDOCERR_EXEC,
+ MANDOCERR_FDOPEN,
+ MANDOCERR_FFLUSH,
+ MANDOCERR_FORK,
+ MANDOCERR_FSTAT,
+ MANDOCERR_GETLINE,
+ MANDOCERR_GLOB,
+ MANDOCERR_GZCLOSE,
+ MANDOCERR_GZDOPEN,
+ MANDOCERR_MKSTEMP,
+ MANDOCERR_OPEN,
+ MANDOCERR_PLEDGE,
+ MANDOCERR_READ,
+ MANDOCERR_WAIT,
+ MANDOCERR_WRITE,
+
+ MANDOCERR_MAX
+};
+
+enum mandoc_esc {
+ ESCAPE_ERROR = 0, /* bail! unparsable escape */
+ ESCAPE_UNSUPP, /* unsupported escape; ignore it */
+ ESCAPE_IGNORE, /* escape to be ignored */
+ ESCAPE_UNDEF, /* undefined escape; print literal character */
+ 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_FONTCW, /* constant width font mode */
+ ESCAPE_FONTPREV, /* previous font mode */
+ ESCAPE_NUMBERED, /* a numbered glyph */
+ ESCAPE_UNICODE, /* a unicode codepoint */
+ ESCAPE_DEVICE, /* print the output device name */
+ ESCAPE_BREAK, /* break the output line */
+ ESCAPE_NOSPACE, /* suppress space if the last on a line */
+ ESCAPE_HORIZ, /* horizontal movement */
+ ESCAPE_HLINE, /* horizontal line drawing */
+ ESCAPE_SKIPCHAR, /* skip the next character */
+ ESCAPE_OVERSTRIKE /* overstrike all chars in the argument */
+};
+
+
+enum mandoc_esc mandoc_font(const char *, int);
+enum mandoc_esc mandoc_escape(const char **, const char **, int *);
+void mandoc_msg_setoutfile(FILE *);
+const char *mandoc_msg_getinfilename(void);
+void mandoc_msg_setinfilename(const char *);
+enum mandocerr mandoc_msg_getmin(void);
+void mandoc_msg_setmin(enum mandocerr);
+enum mandoclevel mandoc_msg_getrc(void);
+void mandoc_msg_setrc(enum mandoclevel);
+void mandoc_msg(enum mandocerr, int, int, const char *, ...)
+ __attribute__((__format__ (__printf__, 4, 5)));
+void mandoc_msg_summary(void);
+void mchars_alloc(void);
+void mchars_free(void);
+int mchars_num2char(const char *, size_t);
+const char *mchars_uc2str(int);
+int mchars_num2uc(const char *, size_t);
+int mchars_spec2cp(const char *, size_t);
+const char *mchars_spec2str(const char *, size_t, size_t *);
diff --git a/usr.bin/mandoc/mandoc_aux.c b/usr.bin/mandoc/mandoc_aux.c
new file mode 100644
index 0000000..7c23ecf
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_aux.c
@@ -0,0 +1,113 @@
+/* $OpenBSD: mandoc_aux.c,v 1.9 2018/02/07 20:04:33 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2017 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.
+ */
+#include <sys/types.h>
+
+#include <err.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 (ret == -1)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return ret;
+}
+
+void *
+mandoc_calloc(size_t num, size_t size)
+{
+ void *ptr;
+
+ ptr = calloc(num, size);
+ if (ptr == NULL)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return ptr;
+}
+
+void *
+mandoc_malloc(size_t size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (ptr == NULL)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return ptr;
+}
+
+void *
+mandoc_realloc(void *ptr, size_t size)
+{
+ ptr = realloc(ptr, size);
+ if (ptr == NULL)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return ptr;
+}
+
+void *
+mandoc_reallocarray(void *ptr, size_t num, size_t size)
+{
+ ptr = reallocarray(ptr, num, size);
+ if (ptr == NULL)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return ptr;
+}
+
+void *
+mandoc_recallocarray(void *ptr, size_t oldnum, size_t num, size_t size)
+{
+ ptr = recallocarray(ptr, oldnum, num, size);
+ if (ptr == NULL)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return ptr;
+}
+
+char *
+mandoc_strdup(const char *ptr)
+{
+ char *p;
+
+ p = strdup(ptr);
+ if (p == NULL)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return p;
+}
+
+char *
+mandoc_strndup(const char *ptr, size_t sz)
+{
+ char *p;
+
+ p = strndup(ptr, sz);
+ if (p == NULL)
+ err((int)MANDOCLEVEL_SYSERR, NULL);
+ return p;
+}
diff --git a/usr.bin/mandoc/mandoc_aux.h b/usr.bin/mandoc/mandoc_aux.h
new file mode 100644
index 0000000..f535d85
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_aux.h
@@ -0,0 +1,27 @@
+/* $OpenBSD: mandoc_aux.h,v 1.9 2017/06/12 18:55:42 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2017 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.
+ */
+
+int mandoc_asprintf(char **, const char *, ...)
+ __attribute__((__format__ (__printf__, 2, 3)));
+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);
+void *mandoc_recallocarray(void *, size_t, size_t, size_t);
+char *mandoc_strdup(const char *);
+char *mandoc_strndup(const char *, size_t);
diff --git a/usr.bin/mandoc/mandoc_msg.c b/usr.bin/mandoc/mandoc_msg.c
new file mode 100644
index 0000000..a9334ae
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_msg.c
@@ -0,0 +1,368 @@
+/* $OpenBSD: mandoc_msg.c,v 1.9 2020/04/24 11:58:02 schwarze Exp $ */
+/*
+ * Copyright (c) 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * 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 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.
+ *
+ * Implementation of warning and error messages for mandoc(1).
+ */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "mandoc.h"
+
+static const enum mandocerr lowest_type[MANDOCLEVEL_MAX] = {
+ MANDOCERR_OK,
+ MANDOCERR_OK,
+ MANDOCERR_WARNING,
+ MANDOCERR_ERROR,
+ MANDOCERR_UNSUPP,
+ MANDOCERR_BADARG,
+ MANDOCERR_SYSERR
+};
+
+static const char *const level_name[MANDOCLEVEL_MAX] = {
+ "SUCCESS",
+ "STYLE",
+ "WARNING",
+ "ERROR",
+ "UNSUPP",
+ "BADARG",
+ "SYSERR"
+};
+
+static const char *const type_message[MANDOCERR_MAX] = {
+ "ok",
+
+ "base system convention",
+
+ "Mdocdate found",
+ "Mdocdate missing",
+ "unknown architecture",
+ "operating system explicitly specified",
+ "RCS id missing",
+ "referenced manual not found",
+
+ "generic style suggestion",
+
+ "legacy man(7) date format",
+ "normalizing date format to",
+ "lower case character in document title",
+ "duplicate RCS id",
+ "possible typo in section name",
+ "unterminated quoted argument",
+ "useless macro",
+ "consider using OS macro",
+ "errnos out of order",
+ "duplicate errno",
+ "trailing delimiter",
+ "no blank before trailing delimiter",
+ "fill mode already enabled, skipping",
+ "fill mode already disabled, skipping",
+ "verbatim \"--\", maybe consider using \\(em",
+ "function name without markup",
+ "whitespace at end of input line",
+ "bad comment style",
+
+ "generic warning",
+
+ /* related to the prologue */
+ "missing manual title, using UNTITLED",
+ "missing manual title, using \"\"",
+ "missing manual section, using \"\"",
+ "unknown manual section",
+ "filename/section mismatch",
+ "missing date, using \"\"",
+ "cannot parse date, using it verbatim",
+ "date in the future, using it anyway",
+ "missing Os macro, using \"\"",
+ "late prologue 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\"",
+ "NAME section without Nm before Nd",
+ "NAME section without description",
+ "description not at the end of NAME",
+ "bad NAME section content",
+ "missing comma before name",
+ "missing description line, using \"\"",
+ "description line outside NAME section",
+ "sections out of conventional order",
+ "duplicate section title",
+ "unexpected section",
+ "cross reference to self",
+ "unusual Xr order",
+ "unusual Xr punctuation",
+ "AUTHORS section without An macro",
+
+ /* related to macros and nesting */
+ "obsolete macro",
+ "macro neither callable nor escaped",
+ "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",
+ "first macro on line",
+ "line scope broken",
+ "skipping blank line in line scope",
+
+ /* related to missing macro arguments */
+ "skipping empty request",
+ "conditional request controls empty scope",
+ "skipping empty macro",
+ "empty block",
+ "empty argument, using 0n",
+ "missing display type, using -ragged",
+ "list type is not the first argument",
+ "missing -width in -tag list, using 6n",
+ "missing utility name, using \"\"",
+ "missing function name, using \"\"",
+ "empty head in list item",
+ "empty list item",
+ "missing argument, using next line",
+ "missing font type, using \\fR",
+ "unknown font type, using \\fR",
+ "nothing follows prefix",
+ "empty reference block",
+ "missing section argument",
+ "missing -std argument, adding it",
+ "missing option string, using \"\"",
+ "missing resource identifier, using \"\"",
+ "missing eqn box, using \"\"",
+
+ /* related to bad macro arguments */
+ "duplicate argument",
+ "skipping duplicate argument",
+ "skipping duplicate display type",
+ "skipping duplicate list type",
+ "skipping -width argument",
+ "wrong number of cells",
+ "unknown AT&T UNIX version",
+ "comma in function argument",
+ "parenthesis in function name",
+ "unknown library name",
+ "invalid content in Rs block",
+ "invalid Boolean argument",
+ "argument contains two font escapes",
+ "unknown font, skipping request",
+ "odd number of characters in request",
+
+ /* related to plain text */
+ "blank line in fill mode, using .sp",
+ "tab in filled text",
+ "new sentence, new line",
+ "invalid escape sequence",
+ "undefined escape, printing literally",
+ "undefined string, using \"\"",
+
+ /* related to tables */
+ "tbl line starts with span",
+ "tbl column starts with span",
+ "skipping vertical bar in tbl layout",
+
+ "generic error",
+
+ /* related to tables */
+ "non-alphabetic character in tbl options",
+ "skipping unknown tbl option",
+ "missing tbl option argument",
+ "wrong tbl option argument size",
+ "empty tbl layout",
+ "invalid character in tbl layout",
+ "unmatched parenthesis in tbl layout",
+ "tbl without any data cells",
+ "ignoring data in spanned tbl cell",
+ "ignoring extra tbl data cells",
+ "data block open at end of tbl",
+
+ /* related to document structure and macros */
+ "duplicate prologue macro",
+ "skipping late title macro",
+ "input stack limit exceeded, infinite loop?",
+ "skipping bad character",
+ "skipping unknown macro",
+ "ignoring request outside macro",
+ "skipping insecure request",
+ "skipping item outside list",
+ "skipping column outside column list",
+ "skipping end of block that is not open",
+ "fewer RS blocks open, skipping",
+ "inserting missing end of block",
+ "appending missing end of block",
+
+ /* related to request and macro arguments */
+ "escaped character not allowed in a name",
+ "using macro argument outside macro",
+ "argument number is not numeric",
+ "NOT IMPLEMENTED: Bd -file",
+ "skipping display without arguments",
+ "missing list type, using -item",
+ "argument is not numeric, using 1",
+ "argument is not a character",
+ "missing manual name, using \"\"",
+ "uname(3) system call failed, using UNKNOWN",
+ "unknown standard specifier",
+ "skipping request without numeric argument",
+ "excessive shift",
+ "NOT IMPLEMENTED: .so with absolute path or \"..\"",
+ ".so request failed",
+ "skipping tag containing whitespace",
+ "skipping all arguments",
+ "skipping excess arguments",
+ "divide by zero",
+
+ "unsupported feature",
+ "input too large",
+ "unsupported control character",
+ "unsupported escape sequence",
+ "unsupported roff request",
+ "nested .while loops",
+ "end of scope with open .while loop",
+ "end of .while loop in inner scope",
+ "cannot continue this .while loop",
+ "eqn delim option in tbl",
+ "unsupported tbl layout modifier",
+ "ignoring macro in table",
+
+ /* bad command line arguments */
+ NULL,
+ "bad command line argument",
+ "duplicate command line argument",
+ "option has a superfluous value",
+ "missing option value",
+ "bad option value",
+ "duplicate option value",
+ "no such tag",
+
+ /* system errors */
+ NULL,
+ "dup",
+ "exec",
+ "fdopen",
+ "fflush",
+ "fork",
+ "fstat",
+ "getline",
+ "glob",
+ "gzclose",
+ "gzdopen",
+ "mkstemp",
+ "open",
+ "pledge",
+ "read",
+ "wait",
+ "write",
+};
+
+static FILE *fileptr = NULL;
+static const char *filename = NULL;
+static enum mandocerr min_type = MANDOCERR_BADARG;
+static enum mandoclevel rc = MANDOCLEVEL_OK;
+
+
+void
+mandoc_msg_setoutfile(FILE *fp)
+{
+ fileptr = fp;
+}
+
+const char *
+mandoc_msg_getinfilename(void)
+{
+ return filename;
+}
+
+void
+mandoc_msg_setinfilename(const char *fn)
+{
+ filename = fn;
+}
+
+enum mandocerr
+mandoc_msg_getmin(void)
+{
+ return min_type;
+}
+
+void
+mandoc_msg_setmin(enum mandocerr t)
+{
+ min_type = t;
+}
+
+enum mandoclevel
+mandoc_msg_getrc(void)
+{
+ return rc;
+}
+
+void
+mandoc_msg_setrc(enum mandoclevel level)
+{
+ if (rc < level)
+ rc = level;
+}
+
+void
+mandoc_msg(enum mandocerr t, int line, int col, const char *fmt, ...)
+{
+ va_list ap;
+ enum mandoclevel level;
+
+ if (t < min_type)
+ return;
+
+ level = MANDOCLEVEL_SYSERR;
+ while (t < lowest_type[level])
+ level--;
+ mandoc_msg_setrc(level);
+
+ if (fileptr == NULL)
+ return;
+
+ fprintf(fileptr, "%s:", getprogname());
+ if (filename != NULL)
+ fprintf(fileptr, " %s:", filename);
+
+ if (line > 0)
+ fprintf(fileptr, "%d:%d:", line, col + 1);
+
+ fprintf(fileptr, " %s", level_name[level]);
+ if (type_message[t] != NULL)
+ fprintf(fileptr, ": %s", type_message[t]);
+
+ if (fmt != NULL) {
+ fprintf(fileptr, ": ");
+ va_start(ap, fmt);
+ vfprintf(fileptr, fmt, ap);
+ va_end(ap);
+ }
+ fputc('\n', fileptr);
+}
+
+void
+mandoc_msg_summary(void)
+{
+ if (fileptr != NULL && rc != MANDOCLEVEL_OK)
+ fprintf(fileptr,
+ "%s: see above the output for %s messages\n",
+ getprogname(), level_name[rc]);
+}
diff --git a/usr.bin/mandoc/mandoc_ohash.c b/usr.bin/mandoc/mandoc_ohash.c
new file mode 100644
index 0000000..9557caf
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_ohash.c
@@ -0,0 +1,64 @@
+/* $OpenBSD: mandoc_ohash.c,v 1.2 2015/10/19 18:58:20 schwarze Exp $ */
+/*
+ * Copyright (c) 2014, 2015 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.
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+
+static void *hash_alloc(size_t, void *);
+static void *hash_calloc(size_t, size_t, void *);
+static void hash_free(void *, void *);
+
+
+void
+mandoc_ohash_init(struct ohash *h, unsigned int sz, ptrdiff_t ko)
+{
+ struct ohash_info info;
+
+ info.alloc = hash_alloc;
+ info.calloc = hash_calloc;
+ info.free = hash_free;
+ info.data = NULL;
+ info.key_offset = ko;
+
+ ohash_init(h, sz, &info);
+}
+
+static void *
+hash_alloc(size_t sz, void *arg)
+{
+
+ return mandoc_malloc(sz);
+}
+
+static void *
+hash_calloc(size_t n, size_t sz, void *arg)
+{
+
+ return mandoc_calloc(n, sz);
+}
+
+static void
+hash_free(void *p, void *arg)
+{
+
+ free(p);
+}
diff --git a/usr.bin/mandoc/mandoc_ohash.h b/usr.bin/mandoc/mandoc_ohash.h
new file mode 100644
index 0000000..0ea2d7a
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_ohash.h
@@ -0,0 +1,19 @@
+/* $OpenBSD: mandoc_ohash.h,v 1.2 2015/11/07 13:57:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2015 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.
+ */
+#include <ohash.h>
+
+void mandoc_ohash_init(struct ohash *, unsigned int, ptrdiff_t);
diff --git a/usr.bin/mandoc/mandoc_parse.h b/usr.bin/mandoc/mandoc_parse.h
new file mode 100644
index 0000000..1821c1e
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_parse.h
@@ -0,0 +1,44 @@
+/* $OpenBSD: mandoc_parse.h,v 1.4 2019/11/09 14:39:42 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014,2015,2016,2017,2018 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.
+ *
+ * Top level parser interface. For use in the main program
+ * and in the main parser, but not in formatters.
+ */
+
+/*
+ * Parse options.
+ */
+#define MPARSE_MDOC (1 << 0) /* assume -mdoc */
+#define MPARSE_MAN (1 << 1) /* assume -man */
+#define MPARSE_SO (1 << 2) /* honour .so requests */
+#define MPARSE_QUICK (1 << 3) /* abort the parse early */
+#define MPARSE_UTF8 (1 << 4) /* accept UTF-8 input */
+#define MPARSE_LATIN1 (1 << 5) /* accept ISO-LATIN-1 input */
+#define MPARSE_VALIDATE (1 << 6) /* call validation functions */
+#define MPARSE_COMMENT (1 << 7) /* save comments in the tree */
+
+
+struct roff_meta;
+struct mparse;
+
+struct mparse *mparse_alloc(int, enum mandoc_os, const char *);
+void mparse_copy(const struct mparse *);
+void mparse_free(struct mparse *);
+int mparse_open(struct mparse *, const char *);
+void mparse_readfd(struct mparse *, int, const char *);
+void mparse_reset(struct mparse *);
+struct roff_meta *mparse_result(struct mparse *);
diff --git a/usr.bin/mandoc/mandoc_xr.c b/usr.bin/mandoc/mandoc_xr.c
new file mode 100644
index 0000000..3f79da3
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_xr.c
@@ -0,0 +1,122 @@
+/* $OpenBSD: mandoc_xr.c,v 1.3 2017/07/02 21:17:12 schwarze Exp $ */
+/*
+ * Copyright (c) 2017 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.
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "mandoc_xr.h"
+
+static struct ohash *xr_hash = NULL;
+static struct mandoc_xr *xr_first = NULL;
+static struct mandoc_xr *xr_last = NULL;
+
+static void mandoc_xr_clear(void);
+
+
+static void
+mandoc_xr_clear(void)
+{
+ struct mandoc_xr *xr;
+ unsigned int slot;
+
+ if (xr_hash == NULL)
+ return;
+ for (xr = ohash_first(xr_hash, &slot); xr != NULL;
+ xr = ohash_next(xr_hash, &slot))
+ free(xr);
+ ohash_delete(xr_hash);
+}
+
+void
+mandoc_xr_reset(void)
+{
+ if (xr_hash == NULL)
+ xr_hash = mandoc_malloc(sizeof(*xr_hash));
+ else
+ mandoc_xr_clear();
+ mandoc_ohash_init(xr_hash, 5,
+ offsetof(struct mandoc_xr, hashkey));
+ xr_first = xr_last = NULL;
+}
+
+int
+mandoc_xr_add(const char *sec, const char *name, int line, int pos)
+{
+ struct mandoc_xr *xr, *oxr;
+ const char *pend;
+ size_t ssz, nsz, tsz;
+ unsigned int slot;
+ int ret;
+ uint32_t hv;
+
+ if (xr_hash == NULL)
+ return 0;
+
+ ssz = strlen(sec) + 1;
+ nsz = strlen(name) + 1;
+ tsz = ssz + nsz;
+ xr = mandoc_malloc(sizeof(*xr) + tsz);
+ xr->next = NULL;
+ xr->sec = xr->hashkey;
+ xr->name = xr->hashkey + ssz;
+ xr->line = line;
+ xr->pos = pos;
+ xr->count = 1;
+ memcpy(xr->sec, sec, ssz);
+ memcpy(xr->name, name, nsz);
+
+ pend = xr->hashkey + tsz;
+ hv = ohash_interval(xr->hashkey, &pend);
+ slot = ohash_lookup_memory(xr_hash, xr->hashkey, tsz, hv);
+ if ((oxr = ohash_find(xr_hash, slot)) == NULL) {
+ ohash_insert(xr_hash, slot, xr);
+ if (xr_first == NULL)
+ xr_first = xr;
+ else
+ xr_last->next = xr;
+ xr_last = xr;
+ return 0;
+ }
+
+ oxr->count++;
+ ret = (oxr->line == -1) ^ (xr->line == -1);
+ if (xr->line == -1)
+ oxr->line = -1;
+ free(xr);
+ return ret;
+}
+
+struct mandoc_xr *
+mandoc_xr_get(void)
+{
+ return xr_first;
+}
+
+void
+mandoc_xr_free(void)
+{
+ mandoc_xr_clear();
+ free(xr_hash);
+ xr_hash = NULL;
+}
diff --git a/usr.bin/mandoc/mandoc_xr.h b/usr.bin/mandoc/mandoc_xr.h
new file mode 100644
index 0000000..708f502
--- /dev/null
+++ b/usr.bin/mandoc/mandoc_xr.h
@@ -0,0 +1,31 @@
+/* $OpenBSD: mandoc_xr.h,v 1.3 2017/07/02 21:17:12 schwarze Exp $ */
+/*
+ * Copyright (c) 2017 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.
+ */
+
+struct mandoc_xr {
+ struct mandoc_xr *next;
+ char *sec;
+ char *name;
+ int line; /* Or -1 for this page's own names. */
+ int pos;
+ int count;
+ char hashkey[];
+};
+
+void mandoc_xr_reset(void);
+int mandoc_xr_add(const char *, const char *, int, int);
+struct mandoc_xr *mandoc_xr_get(void);
+void mandoc_xr_free(void);
diff --git a/usr.bin/mandoc/mandocdb.c b/usr.bin/mandoc/mandocdb.c
new file mode 100644
index 0000000..6e7efae
--- /dev/null
+++ b/usr.bin/mandoc/mandocdb.c
@@ -0,0 +1,2386 @@
+/* $OpenBSD: mandocdb.c,v 1.216 2020/04/03 11:34:19 schwarze Exp $ */
+/*
+ * Copyright (c) 2011-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2016 Ed Maste <emaste@freebsd.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.
+ *
+ * Implementation of the makewhatis(8) program.
+ */
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "man.h"
+#include "mandoc_parse.h"
+#include "manconf.h"
+#include "mansearch.h"
+#include "dba_array.h"
+#include "dba.h"
+
+extern const char *const mansearch_keynames[];
+
+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 */
+};
+
+struct str {
+ const struct mpage *mpage; /* if set, the owning parse */
+ uint64_t mask; /* bitmask in sequence */
+ char key[]; /* rendered text */
+};
+
+struct inodev {
+ ino_t st_ino;
+ dev_t st_dev;
+};
+
+struct mpage {
+ struct inodev inodev; /* used for hashing routine */
+ struct dba_array *dba;
+ 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 mpage *next; /* singly linked list */
+ struct mlink *mlinks; /* singly linked list */
+ int name_head_done;
+ enum form form; /* format from file content */
+};
+
+struct mlink {
+ char file[PATH_MAX]; /* filename rel. to manpath */
+ 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 form dform; /* format from directory */
+ enum form fform; /* format from file name suffix */
+};
+
+typedef int (*mdoc_fp)(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+
+struct mdoc_handler {
+ mdoc_fp fp; /* optional handler */
+ uint64_t mask; /* set unless handler returns 0 */
+ int taboo; /* node flags that must not be set */
+};
+
+
+int mandocdb(int, char *[]);
+
+static void dbadd(struct dba *, struct mpage *);
+static void dbadd_mlink(const struct mlink *);
+static void dbprune(struct dba *);
+static void dbwrite(struct dba *);
+static void filescan(const char *);
+static int fts_compare(const FTSENT **, const FTSENT **);
+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 dba *, struct mparse *);
+static void parse_cat(struct mpage *, int);
+static void parse_man(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static void parse_mdoc(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_head(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Fa(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Fd(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static void parse_mdoc_fname(struct mpage *, const struct roff_node *);
+static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Nd(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Nm(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Sh(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Va(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static int parse_mdoc_Xr(struct mpage *, const struct roff_meta *,
+ const struct roff_node *);
+static void putkey(const struct mpage *, char *, uint64_t);
+static void putkeys(const struct mpage *, char *, size_t, uint64_t);
+static void putmdockey(const struct mpage *,
+ const struct roff_node *, uint64_t, int);
+static int render_string(char **, size_t *);
+static void say(const char *, const char *, ...)
+ __attribute__((__format__ (__printf__, 2, 3)));
+static int set_basedir(const char *, int);
+static int treescan(void);
+static size_t utf8(unsigned int, char [7]);
+
+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 size_t basedir_len; /* strlen(basedir) */
+static struct mpage *mpage_head; /* list of distinct manual pages */
+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 uint64_t name_mask;
+
+static const struct mdoc_handler mdoc_handlers[MDOC_MAX - MDOC_Dd] = {
+ { NULL, 0, NODE_NOPRT }, /* Dd */
+ { NULL, 0, NODE_NOPRT }, /* Dt */
+ { NULL, 0, NODE_NOPRT }, /* Os */
+ { parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */
+ { parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */
+ { NULL, 0, 0 }, /* Pp */
+ { NULL, 0, 0 }, /* D1 */
+ { NULL, 0, 0 }, /* Dl */
+ { NULL, 0, 0 }, /* Bd */
+ { NULL, 0, 0 }, /* Ed */
+ { NULL, 0, 0 }, /* Bl */
+ { NULL, 0, 0 }, /* El */
+ { NULL, 0, 0 }, /* It */
+ { NULL, 0, 0 }, /* Ad */
+ { NULL, TYPE_An, 0 }, /* An */
+ { NULL, 0, 0 }, /* Ap */
+ { NULL, TYPE_Ar, 0 }, /* Ar */
+ { NULL, TYPE_Cd, 0 }, /* Cd */
+ { NULL, TYPE_Cm, 0 }, /* Cm */
+ { NULL, TYPE_Dv, 0 }, /* Dv */
+ { NULL, TYPE_Er, 0 }, /* Er */
+ { NULL, TYPE_Ev, 0 }, /* Ev */
+ { NULL, 0, 0 }, /* Ex */
+ { parse_mdoc_Fa, 0, 0 }, /* Fa */
+ { parse_mdoc_Fd, 0, 0 }, /* Fd */
+ { NULL, TYPE_Fl, 0 }, /* Fl */
+ { parse_mdoc_Fn, 0, 0 }, /* Fn */
+ { NULL, TYPE_Ft | TYPE_Vt, 0 }, /* Ft */
+ { NULL, TYPE_Ic, 0 }, /* Ic */
+ { NULL, TYPE_In, 0 }, /* In */
+ { NULL, TYPE_Li, 0 }, /* Li */
+ { parse_mdoc_Nd, 0, 0 }, /* Nd */
+ { parse_mdoc_Nm, 0, 0 }, /* Nm */
+ { NULL, 0, 0 }, /* Op */
+ { NULL, 0, 0 }, /* Ot */
+ { NULL, TYPE_Pa, NODE_NOSRC }, /* Pa */
+ { NULL, 0, 0 }, /* Rv */
+ { NULL, TYPE_St, 0 }, /* St */
+ { parse_mdoc_Va, TYPE_Va, 0 }, /* Va */
+ { parse_mdoc_Va, TYPE_Vt, 0 }, /* Vt */
+ { parse_mdoc_Xr, 0, 0 }, /* Xr */
+ { NULL, 0, 0 }, /* %A */
+ { NULL, 0, 0 }, /* %B */
+ { NULL, 0, 0 }, /* %D */
+ { NULL, 0, 0 }, /* %I */
+ { NULL, 0, 0 }, /* %J */
+ { NULL, 0, 0 }, /* %N */
+ { NULL, 0, 0 }, /* %O */
+ { NULL, 0, 0 }, /* %P */
+ { NULL, 0, 0 }, /* %R */
+ { NULL, 0, 0 }, /* %T */
+ { NULL, 0, 0 }, /* %V */
+ { NULL, 0, 0 }, /* Ac */
+ { NULL, 0, 0 }, /* Ao */
+ { NULL, 0, 0 }, /* Aq */
+ { NULL, TYPE_At, 0 }, /* At */
+ { NULL, 0, 0 }, /* Bc */
+ { NULL, 0, 0 }, /* Bf */
+ { NULL, 0, 0 }, /* Bo */
+ { NULL, 0, 0 }, /* Bq */
+ { NULL, TYPE_Bsx, NODE_NOSRC }, /* Bsx */
+ { NULL, TYPE_Bx, NODE_NOSRC }, /* Bx */
+ { NULL, 0, 0 }, /* Db */
+ { NULL, 0, 0 }, /* Dc */
+ { NULL, 0, 0 }, /* Do */
+ { NULL, 0, 0 }, /* Dq */
+ { NULL, 0, 0 }, /* Ec */
+ { NULL, 0, 0 }, /* Ef */
+ { NULL, TYPE_Em, 0 }, /* Em */
+ { NULL, 0, 0 }, /* Eo */
+ { NULL, TYPE_Fx, NODE_NOSRC }, /* Fx */
+ { NULL, TYPE_Ms, 0 }, /* Ms */
+ { NULL, 0, 0 }, /* No */
+ { NULL, 0, 0 }, /* Ns */
+ { NULL, TYPE_Nx, NODE_NOSRC }, /* Nx */
+ { NULL, TYPE_Ox, NODE_NOSRC }, /* Ox */
+ { NULL, 0, 0 }, /* Pc */
+ { NULL, 0, 0 }, /* Pf */
+ { NULL, 0, 0 }, /* Po */
+ { NULL, 0, 0 }, /* Pq */
+ { NULL, 0, 0 }, /* Qc */
+ { NULL, 0, 0 }, /* Ql */
+ { NULL, 0, 0 }, /* Qo */
+ { NULL, 0, 0 }, /* Qq */
+ { NULL, 0, 0 }, /* Re */
+ { NULL, 0, 0 }, /* Rs */
+ { NULL, 0, 0 }, /* Sc */
+ { NULL, 0, 0 }, /* So */
+ { NULL, 0, 0 }, /* Sq */
+ { NULL, 0, 0 }, /* Sm */
+ { NULL, 0, 0 }, /* Sx */
+ { NULL, TYPE_Sy, 0 }, /* Sy */
+ { NULL, TYPE_Tn, 0 }, /* Tn */
+ { NULL, 0, NODE_NOSRC }, /* Ux */
+ { NULL, 0, 0 }, /* Xc */
+ { NULL, 0, 0 }, /* Xo */
+ { parse_mdoc_Fo, 0, 0 }, /* Fo */
+ { NULL, 0, 0 }, /* Fc */
+ { NULL, 0, 0 }, /* Oo */
+ { NULL, 0, 0 }, /* Oc */
+ { NULL, 0, 0 }, /* Bk */
+ { NULL, 0, 0 }, /* Ek */
+ { NULL, 0, 0 }, /* Bt */
+ { NULL, 0, 0 }, /* Hf */
+ { NULL, 0, 0 }, /* Fr */
+ { NULL, 0, 0 }, /* Ud */
+ { NULL, TYPE_Lb, NODE_NOSRC }, /* Lb */
+ { NULL, 0, 0 }, /* Lp */
+ { NULL, TYPE_Lk, 0 }, /* Lk */
+ { NULL, TYPE_Mt, NODE_NOSRC }, /* Mt */
+ { NULL, 0, 0 }, /* Brq */
+ { NULL, 0, 0 }, /* Bro */
+ { NULL, 0, 0 }, /* Brc */
+ { NULL, 0, 0 }, /* %C */
+ { NULL, 0, 0 }, /* Es */
+ { NULL, 0, 0 }, /* En */
+ { NULL, TYPE_Dx, NODE_NOSRC }, /* Dx */
+ { NULL, 0, 0 }, /* %Q */
+ { NULL, 0, 0 }, /* %U */
+ { NULL, 0, 0 }, /* Ta */
+};
+
+
+int
+mandocdb(int argc, char *argv[])
+{
+ struct manconf conf;
+ struct mparse *mp;
+ struct dba *dba;
+ const char *path_arg, *progname;
+ size_t j, sz;
+ int ch, i;
+
+ if (pledge("stdio rpath wpath cpath", NULL) == -1) {
+ warn("pledge");
+ return (int)MANDOCLEVEL_SYSERR;
+ }
+
+ memset(&conf, 0, sizeof(conf));
+
+ /*
+ * 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) != OP_DEFAULT) { \
+ warnx("-%c: Conflicting option", (_ch)); \
+ goto usage; \
+ } while (/*CONSTCOND*/0)
+
+ mparse_options = MPARSE_VALIDATE;
+ path_arg = NULL;
+ op = OP_DEFAULT;
+
+ while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1)
+ 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") != 0) {
+ warnx("-T%s: Unsupported output format",
+ 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 (nodb) {
+ if (pledge("stdio rpath", NULL) == -1) {
+ warn("pledge");
+ return (int)MANDOCLEVEL_SYSERR;
+ }
+ }
+
+ if (op == OP_CONFFILE && argc > 0) {
+ warnx("-C: Too many arguments");
+ goto usage;
+ }
+
+ exitcode = (int)MANDOCLEVEL_OK;
+ mchars_alloc();
+ mp = mparse_alloc(mparse_options, MANDOC_OS_OTHER, NULL);
+ mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
+ mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));
+
+ if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) {
+
+ /*
+ * Most of these deal with a specific directory.
+ * Jump into that directory first.
+ */
+ if (op != OP_TEST && set_basedir(path_arg, 1) == 0)
+ goto out;
+
+ dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
+ if (dba != NULL) {
+ /*
+ * 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 (nodb == 0)
+ dbprune(dba);
+ } else {
+ /* Database missing or corrupt. */
+ if (op != OP_UPDATE || errno != ENOENT)
+ say(MANDOC_DB, "%s: Automatically recreating"
+ " from scratch", strerror(errno));
+ exitcode = (int)MANDOCLEVEL_OK;
+ op = OP_DEFAULT;
+ if (treescan() == 0)
+ goto out;
+ dba = dba_new(128);
+ }
+ if (op != OP_DELETE)
+ mpages_merge(dba, mp);
+ if (nodb == 0)
+ dbwrite(dba);
+ dba_free(dba);
+ } else {
+ /*
+ * If we have arguments, use them as our manpaths.
+ * If we don't, use man.conf(5).
+ */
+ if (argc > 0) {
+ conf.manpath.paths = mandoc_reallocarray(NULL,
+ argc, sizeof(char *));
+ conf.manpath.sz = (size_t)argc;
+ for (i = 0; i < argc; i++)
+ conf.manpath.paths[i] = mandoc_strdup(argv[i]);
+ } else
+ manconf_parse(&conf, path_arg, NULL, NULL);
+
+ if (conf.manpath.sz == 0) {
+ 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 < conf.manpath.sz; j++) {
+ sz = strlen(conf.manpath.paths[j]);
+ if (sz && conf.manpath.paths[j][sz - 1] == '/')
+ conf.manpath.paths[j][--sz] = '\0';
+ if (sz == 0)
+ continue;
+
+ if (j) {
+ mandoc_ohash_init(&mpages, 6,
+ offsetof(struct mpage, inodev));
+ mandoc_ohash_init(&mlinks, 6,
+ offsetof(struct mlink, file));
+ }
+
+ if (set_basedir(conf.manpath.paths[j], argc > 0) == 0)
+ continue;
+ if (treescan() == 0)
+ continue;
+ dba = dba_new(128);
+ mpages_merge(dba, mp);
+ if (nodb == 0)
+ dbwrite(dba);
+ dba_free(dba);
+
+ if (j + 1 < conf.manpath.sz) {
+ mpages_free();
+ ohash_delete(&mpages);
+ ohash_delete(&mlinks);
+ }
+ }
+ }
+out:
+ manconf_free(&conf);
+ mparse_free(mp);
+ mchars_free();
+ mpages_free();
+ ohash_delete(&mpages);
+ ohash_delete(&mlinks);
+ return exitcode;
+usage:
+ progname = getprogname();
+ 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;
+}
+
+/*
+ * To get a singly linked list in alpha order while inserting entries
+ * at the beginning, process directory entries in reverse alpha order.
+ */
+static int
+fts_compare(const FTSENT **a, const FTSENT **b)
+{
+ return -strcmp((*a)->fts_name, (*b)->fts_name);
+}
+
+/*
+ * 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: accommodate for multi-language directories.
+ */
+static int
+treescan(void)
+{
+ char buf[PATH_MAX];
+ FTS *f;
+ FTSENT *ff;
+ struct mlink *mlink;
+ int gzip;
+ enum form dform;
+ char *dsec, *arch, *fsec, *cp;
+ const char *path;
+ const char *argv[2];
+
+ argv[0] = ".";
+ argv[1] = NULL;
+
+ f = fts_open((char * const *)argv, FTS_PHYSICAL | FTS_NOCHDIR,
+ fts_compare);
+ if (f == NULL) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fts_open");
+ return 0;
+ }
+
+ dsec = arch = NULL;
+ dform = FORM_NONE;
+
+ while ((ff = fts_read(f)) != NULL) {
+ 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 (realpath(path, buf) == NULL) {
+ if (warnings)
+ say(path, "&realpath");
+ continue;
+ }
+ if (strncmp(buf, basedir, basedir_len) != 0) {
+ if (warnings) say("",
+ "%s: outside base directory", buf);
+ continue;
+ }
+ /* Use logical inode to avoid mpages dupe. */
+ if (stat(path, ff->fts_statp) == -1) {
+ 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 ( ! strcmp(path, MANDOC_DB))
+ continue;
+ if ( ! use_all && ff->fts_level < 2) {
+ if (warnings)
+ say(path, "Extraneous file");
+ continue;
+ }
+ gzip = 0;
+ fsec = NULL;
+ while (fsec == NULL) {
+ fsec = strrchr(ff->fts_name, '.');
+ if (fsec == NULL || strcmp(fsec+1, "gz"))
+ break;
+ gzip = 1;
+ *fsec = '\0';
+ fsec = NULL;
+ }
+ if (fsec == NULL) {
+ if ( ! use_all) {
+ if (warnings)
+ say(path,
+ "No filename suffix");
+ continue;
+ }
+ } else if ( ! strcmp(++fsec, "html")) {
+ if (warnings)
+ say(path, "Skip html");
+ continue;
+ } else if ( ! strcmp(fsec, "ps")) {
+ if (warnings)
+ say(path, "Skip ps");
+ continue;
+ } else if ( ! strcmp(fsec, "pdf")) {
+ if (warnings)
+ say(path, "Skip pdf");
+ continue;
+ } else if ( ! use_all &&
+ ((dform == FORM_SRC &&
+ strncmp(fsec, dsec, strlen(dsec))) ||
+ (dform == FORM_CAT && 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:
+ 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 (ff->fts_info == FTS_DP) {
+ dform = FORM_NONE;
+ dsec = NULL;
+ break;
+ }
+
+ if ( ! strncmp(cp, "man", 3)) {
+ dform = FORM_SRC;
+ dsec = cp + 3;
+ } else if ( ! strncmp(cp, "cat", 3)) {
+ dform = FORM_CAT;
+ dsec = cp + 3;
+ } else {
+ dform = FORM_NONE;
+ dsec = NULL;
+ }
+
+ if (dsec != NULL || 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 (ff->fts_info != FTS_DP && dsec != NULL)
+ arch = ff->fts_name;
+ else
+ arch = NULL;
+ break;
+ default:
+ if (ff->fts_info == FTS_DP || 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 *infile)
+{
+ struct stat st;
+ struct mlink *mlink;
+ char *linkfile, *p, *realdir, *start, *usefile;
+ size_t realdir_len;
+
+ assert(use_all);
+
+ if (strncmp(infile, "./", 2) == 0)
+ infile += 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 (lstat(infile, &st) == -1) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(infile, "&lstat");
+ return;
+ } else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(infile, "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 ((usefile = realpath(infile, NULL)) == NULL) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(infile, "&realpath");
+ return;
+ }
+
+ if (op == OP_TEST)
+ start = usefile;
+ else if (strncmp(usefile, basedir, basedir_len) == 0)
+ start = usefile + basedir_len;
+ else {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "%s: outside base directory", infile);
+ free(usefile);
+ 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.
+ */
+ do {
+ if (S_ISLNK(st.st_mode) == 0)
+ break;
+
+ /*
+ * Some implementations of realpath(3) may succeed
+ * even if the target of the link does not exist,
+ * so check again for extra safety.
+ */
+ if (stat(usefile, &st) == -1) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(infile, "&stat");
+ free(usefile);
+ return;
+ }
+ linkfile = mandoc_strdup(infile);
+ if (op == OP_TEST) {
+ free(usefile);
+ start = usefile = linkfile;
+ break;
+ }
+ if (strncmp(infile, basedir, basedir_len) == 0) {
+ free(usefile);
+ usefile = linkfile;
+ start = usefile + basedir_len;
+ break;
+ }
+
+ /*
+ * This symbolic link points into the basedir
+ * from the outside. Let's see whether any of
+ * the parent directories resolve to the basedir.
+ */
+ p = strchr(linkfile, '\0');
+ do {
+ while (*--p != '/')
+ continue;
+ *p = '\0';
+ if ((realdir = realpath(linkfile, NULL)) == NULL) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(infile, "&realpath");
+ free(linkfile);
+ free(usefile);
+ return;
+ }
+ realdir_len = strlen(realdir) + 1;
+ free(realdir);
+ *p = '/';
+ } while (realdir_len > basedir_len);
+
+ /*
+ * If one of the directories resolves to the basedir,
+ * use the rest of the original name.
+ * Otherwise, the best we can do
+ * is to use the filename pointed to.
+ */
+ if (realdir_len == basedir_len) {
+ free(usefile);
+ usefile = linkfile;
+ start = p + 1;
+ } else {
+ free(linkfile);
+ start = usefile + basedir_len;
+ }
+ } while (/* CONSTCOND */ 0);
+
+ mlink = mandoc_calloc(1, sizeof(struct mlink));
+ mlink->dform = FORM_NONE;
+ if (strlcpy(mlink->file, start, sizeof(mlink->file)) >=
+ sizeof(mlink->file)) {
+ say(start, "Filename too long");
+ free(mlink);
+ free(usefile);
+ return;
+ }
+
+ /*
+ * In test mode or when the original name is absolute
+ * but outside our tree, guess the base directory.
+ */
+
+ if (op == OP_TEST || (start == usefile && *start == '/')) {
+ if (strncmp(usefile, "man/", 4) == 0)
+ start = usefile + 4;
+ else if ((start = strstr(usefile, "/man/")) != NULL)
+ start += 5;
+ else
+ start = usefile;
+ }
+
+ /*
+ * 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 ((p = strchr(start, '/')) != NULL) {
+ *p++ = '\0';
+ if (strncmp(start, "man", 3) == 0) {
+ mlink->dform = FORM_SRC;
+ mlink->dsec = start + 3;
+ } else if (strncmp(start, "cat", 3) == 0) {
+ mlink->dform = FORM_CAT;
+ mlink->dsec = start + 3;
+ }
+
+ start = p;
+ if (mlink->dsec != NULL && (p = strchr(start, '/')) != NULL) {
+ *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 != '.')
+ continue;
+
+ if (*p == '.') {
+ *p++ = '\0';
+ mlink->fsec = p;
+ }
+
+ /*
+ * Now try to parse the name.
+ * Use the filename portion of the path.
+ */
+ mlink->name = start;
+ if ((p = strrchr(start, '/')) != NULL) {
+ mlink->name = p + 1;
+ *p = '\0';
+ }
+ mlink_add(mlink, &st);
+ free(usefile);
+}
+
+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);
+
+ memset(&inodev, 0, sizeof(inodev)); /* Clear padding. */
+ 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;
+ mpage->form = FORM_NONE;
+ mpage->next = mpage_head;
+ mpage_head = mpage;
+ 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;
+
+ while ((mpage = mpage_head) != NULL) {
+ while ((mlink = mpage->mlinks) != NULL) {
+ mpage->mlinks = mlink->next;
+ mlink_free(mlink);
+ }
+ mpage_head = mpage->next;
+ free(mpage->sec);
+ free(mpage->arch);
+ free(mpage->title);
+ free(mpage->desc);
+ free(mpage);
+ }
+}
+
+/*
+ * 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, 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 dba *dba, struct mparse *mp)
+{
+ struct mpage *mpage, *mpage_dest;
+ struct mlink *mlink, *mlink_dest;
+ struct roff_meta *meta;
+ char *cp;
+ int fd;
+
+ for (mpage = mpage_head; mpage != NULL; mpage = mpage->next) {
+ mlinks_undupe(mpage);
+ if ((mlink = mpage->mlinks) == NULL)
+ continue;
+
+ name_mask = NAME_MASK;
+ mandoc_ohash_init(&names, 4, offsetof(struct str, key));
+ mandoc_ohash_init(&strings, 6, offsetof(struct str, key));
+ mparse_reset(mp);
+ meta = NULL;
+
+ if ((fd = mparse_open(mp, mlink->file)) == -1) {
+ say(mlink->file, "&open");
+ goto nextpage;
+ }
+
+ /*
+ * Interpret the file as mdoc(7) or man(7) source
+ * code, unless it is known to be formatted.
+ */
+ if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) {
+ mparse_readfd(mp, fd, mlink->file);
+ close(fd);
+ fd = -1;
+ meta = mparse_result(mp);
+ }
+
+ if (meta != NULL && meta->sodest != NULL) {
+ mlink_dest = ohash_find(&mlinks,
+ ohash_qlookup(&mlinks, meta->sodest));
+ if (mlink_dest == NULL) {
+ mandoc_asprintf(&cp, "%s.gz", meta->sodest);
+ mlink_dest = ohash_find(&mlinks,
+ ohash_qlookup(&mlinks, cp));
+ free(cp);
+ }
+ if (mlink_dest != NULL) {
+
+ /* The .so target exists. */
+
+ mpage_dest = mlink_dest->mpage;
+ 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->dba != NULL)
+ dbadd_mlink(mlink);
+
+ if (mlink->next == NULL)
+ 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;
+ }
+ meta->macroset = MACROSET_NONE;
+ }
+ if (meta != NULL && meta->macroset == MACROSET_MDOC) {
+ mpage->form = FORM_SRC;
+ mpage->sec = meta->msec;
+ mpage->sec = mandoc_strdup(
+ mpage->sec == NULL ? "" : mpage->sec);
+ mpage->arch = meta->arch;
+ mpage->arch = mandoc_strdup(
+ mpage->arch == NULL ? "" : mpage->arch);
+ mpage->title = mandoc_strdup(meta->title);
+ } else if (meta != NULL && meta->macroset == MACROSET_MAN) {
+ if (*meta->msec != '\0' || *meta->title != '\0') {
+ mpage->form = FORM_SRC;
+ mpage->sec = mandoc_strdup(meta->msec);
+ mpage->arch = mandoc_strdup(mlink->arch);
+ mpage->title = mandoc_strdup(meta->title);
+ } else
+ meta = NULL;
+ }
+
+ assert(mpage->desc == NULL);
+ if (meta == NULL || meta->sodest != NULL) {
+ mpage->sec = mandoc_strdup(mlink->dsec);
+ mpage->arch = mandoc_strdup(mlink->arch);
+ mpage->title = mandoc_strdup(mlink->name);
+ if (meta == NULL) {
+ mpage->form = FORM_CAT;
+ parse_cat(mpage, fd);
+ } else
+ mpage->form = FORM_SRC;
+ } else if (meta->macroset == MACROSET_MDOC)
+ parse_mdoc(mpage, meta, meta->first);
+ else
+ parse_man(mpage, meta, meta->first);
+ if (mpage->desc == NULL) {
+ mpage->desc = mandoc_strdup(mlink->name);
+ if (warnings)
+ say(mlink->file, "No one-line description, "
+ "using filename \"%s\"", mlink->name);
+ }
+
+ for (mlink = mpage->mlinks;
+ mlink != NULL;
+ mlink = mlink->next) {
+ putkey(mpage, mlink->name, NAME_FILE);
+ if (warnings && !use_all)
+ mlink_check(mpage, mlink);
+ }
+
+ dbadd(dba, mpage);
+
+nextpage:
+ ohash_delete(&strings);
+ ohash_delete(&names);
+ }
+}
+
+static void
+parse_cat(struct mpage *mpage, int fd)
+{
+ FILE *stream;
+ struct mlink *mlink;
+ char *line, *p, *title, *sec;
+ size_t linesz, plen, titlesz;
+ ssize_t len;
+ int offs;
+
+ mlink = mpage->mlinks;
+ stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r");
+ if (stream == NULL) {
+ if (fd != -1)
+ close(fd);
+ if (warnings)
+ say(mlink->file, "&fopen");
+ return;
+ }
+
+ line = NULL;
+ linesz = 0;
+
+ /* Parse the section number from the header line. */
+
+ while (getline(&line, &linesz, stream) != -1) {
+ if (*line == '\n')
+ continue;
+ if ((sec = strchr(line, '(')) == NULL)
+ break;
+ if ((p = strchr(++sec, ')')) == NULL)
+ break;
+ free(mpage->sec);
+ mpage->sec = mandoc_strndup(sec, p - sec);
+ if (warnings && *mlink->dsec != '\0' &&
+ strcasecmp(mpage->sec, mlink->dsec))
+ say(mlink->file,
+ "Section \"%s\" manual in %s directory",
+ mpage->sec, mlink->dsec);
+ break;
+ }
+
+ /* Skip to first blank line. */
+
+ while (line == NULL || *line != '\n')
+ if (getline(&line, &linesz, stream) == -1)
+ break;
+
+ /*
+ * Assume the first line that is not indented
+ * is the first section header. Skip to it.
+ */
+
+ while (getline(&line, &linesz, stream) != -1)
+ if (*line != '\n' && *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 ((len = getline(&line, &linesz, stream)) != -1) {
+ if (*line != ' ')
+ break;
+ offs = 0;
+ while (isspace((unsigned char)line[offs]))
+ offs++;
+ if (line[offs] == '\0')
+ continue;
+ title = mandoc_realloc(title, titlesz + len - offs);
+ memcpy(title + titlesz, line + offs, len - offs);
+ titlesz += len - offs;
+ title[titlesz - 1] = ' ';
+ }
+ free(line);
+
+ /*
+ * 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(mlink->file, "Cannot find NAME section");
+ fclose(stream);
+ free(title);
+ return;
+ }
+
+ title[titlesz - 1] = '\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(mlink->file, "No dash in title line, "
+ "reusing \"%s\" as one-line description", title);
+ 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;
+ }
+
+ /*
+ * Cut off excessive one-line descriptions.
+ * Bad pages are not worth better heuristics.
+ */
+
+ mpage->desc = mandoc_strndup(p, 150);
+ 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)
+{
+ 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 roff_node *n, uint64_t m, int taboo)
+{
+
+ for ( ; NULL != n; n = n->next) {
+ if (n->flags & taboo)
+ continue;
+ if (NULL != n->child)
+ putmdockey(mpage, n->child, m, taboo);
+ if (n->type == ROFFT_TEXT)
+ putkey(mpage, n->string, m);
+ }
+}
+
+static void
+parse_man(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+ const struct roff_node *head, *body;
+ char *start, *title;
+ char byte;
+ size_t sz;
+
+ if (n == NULL)
+ 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 (n->type == ROFFT_BODY && n->tok == MAN_SH) {
+ body = n;
+ if ((head = body->parent->head) != NULL &&
+ (head = head->child) != NULL &&
+ head->next == NULL &&
+ head->type == ROFFT_TEXT &&
+ strcmp(head->string, "NAME") == 0 &&
+ body->child != NULL) {
+
+ /*
+ * 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;
+ 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 ( ! (mpage->name_head_done ||
+ strcasecmp(start, meta->title))) {
+ putkey(mpage, start, NAME_HEAD);
+ mpage->name_head_done = 1;
+ }
+
+ if (' ' == byte) {
+ start += sz + 1;
+ break;
+ }
+
+ assert(',' == byte);
+ start += sz + 1;
+ while (' ' == *start)
+ start++;
+ }
+
+ if (start == title) {
+ putkey(mpage, start, NAME_TITLE);
+ if ( ! (mpage->name_head_done ||
+ strcasecmp(start, meta->title))) {
+ putkey(mpage, start, NAME_HEAD);
+ mpage->name_head_done = 1;
+ }
+ 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++;
+
+ /*
+ * Cut off excessive one-line descriptions.
+ * Bad pages are not worth better heuristics.
+ */
+
+ mpage->desc = mandoc_strndup(start, 150);
+ free(title);
+ return;
+ }
+ }
+
+ for (n = n->child; n; n = n->next) {
+ if (NULL != mpage->desc)
+ break;
+ parse_man(mpage, meta, n);
+ }
+}
+
+static void
+parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+ const struct mdoc_handler *handler;
+
+ for (n = n->child; n != NULL; n = n->next) {
+ if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX)
+ continue;
+ assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
+ handler = mdoc_handlers + (n->tok - MDOC_Dd);
+ if (n->flags & handler->taboo)
+ continue;
+
+ switch (n->type) {
+ case ROFFT_ELEM:
+ case ROFFT_BLOCK:
+ case ROFFT_HEAD:
+ case ROFFT_BODY:
+ case ROFFT_TAIL:
+ if (handler->fp != NULL &&
+ (*handler->fp)(mpage, meta, n) == 0)
+ break;
+ if (handler->mask)
+ putmdockey(mpage, n->child,
+ handler->mask, handler->taboo);
+ break;
+ default:
+ continue;
+ }
+ if (NULL != n->child)
+ parse_mdoc(mpage, meta, n);
+ }
+}
+
+static int
+parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+ uint64_t mask;
+
+ mask = TYPE_Fa;
+ if (n->sec == SEC_SYNOPSIS)
+ mask |= TYPE_Vt;
+
+ putmdockey(mpage, n->child, mask, 0);
+ return 0;
+}
+
+static int
+parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+ char *start, *end;
+ size_t sz;
+
+ if (SEC_SYNOPSIS != n->sec ||
+ NULL == (n = n->child) ||
+ n->type != ROFFT_TEXT)
+ 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 ((n = n->next) == NULL || n->type != ROFFT_TEXT)
+ 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 void
+parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n)
+{
+ char *cp;
+ size_t sz;
+
+ if (n->type != ROFFT_TEXT)
+ return;
+
+ /* Skip function pointer punctuation. */
+
+ cp = n->string;
+ while (*cp == '(' || *cp == '*')
+ cp++;
+ sz = strcspn(cp, "()");
+
+ putkeys(mpage, cp, sz, TYPE_Fn);
+ if (n->sec == SEC_SYNOPSIS)
+ putkeys(mpage, cp, sz, NAME_SYN);
+}
+
+static int
+parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+ uint64_t mask;
+
+ if (n->child == NULL)
+ return 0;
+
+ parse_mdoc_fname(mpage, n->child);
+
+ n = n->child->next;
+ if (n != NULL && n->type == ROFFT_TEXT) {
+ mask = TYPE_Fa;
+ if (n->sec == SEC_SYNOPSIS)
+ mask |= TYPE_Vt;
+ putmdockey(mpage, n, mask, 0);
+ }
+
+ return 0;
+}
+
+static int
+parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+
+ if (n->type != ROFFT_HEAD)
+ return 1;
+
+ if (n->child != NULL)
+ parse_mdoc_fname(mpage, n->child);
+
+ return 0;
+}
+
+static int
+parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+ char *cp;
+
+ if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY)
+ return 0;
+
+ if (n->child != NULL &&
+ n->child->next == NULL &&
+ n->child->type == ROFFT_TEXT)
+ return 1;
+
+ cp = NULL;
+ deroff(&cp, n);
+ if (cp != NULL) {
+ putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va ||
+ n->type == ROFFT_BODY ? TYPE_Va : 0));
+ free(cp);
+ }
+
+ return 0;
+}
+
+static int
+parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_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 roff_meta *meta,
+ const struct roff_node *n)
+{
+
+ if (n->type == ROFFT_BODY)
+ deroff(&mpage->desc, n);
+ return 0;
+}
+
+static int
+parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+
+ if (SEC_NAME == n->sec)
+ putmdockey(mpage, n->child, NAME_TITLE, 0);
+ else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) {
+ if (n->child == NULL)
+ putkey(mpage, meta->name, NAME_SYN);
+ else
+ putmdockey(mpage, n->child, NAME_SYN, 0);
+ }
+ if ( ! (mpage->name_head_done ||
+ n->child == NULL || n->child->string == NULL ||
+ strcasecmp(n->child->string, meta->title))) {
+ putkey(mpage, n->child->string, NAME_HEAD);
+ mpage->name_head_done = 1;
+ }
+ return 0;
+}
+
+static int
+parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+
+ return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD;
+}
+
+static int
+parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta,
+ const struct roff_node *n)
+{
+
+ return n->type == ROFFT_HEAD;
+}
+
+/*
+ * 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, char *cp, size_t sz, uint64_t v)
+{
+ struct ohash *htab;
+ struct str *s;
+ const char *end;
+ unsigned int slot;
+ int i, mustfree;
+
+ if (0 == sz)
+ return;
+
+ mustfree = render_string(&cp, &sz);
+
+ if (TYPE_Nm & v) {
+ htab = &names;
+ v &= name_mask;
+ if (v & NAME_FIRST)
+ name_mask &= ~NAME_FIRST;
+ if (debug > 1)
+ say(mpage->mlinks->file,
+ "Adding name %*s, bits=0x%llx", (int)sz, cp,
+ (unsigned long long)v);
+ } else {
+ htab = &strings;
+ if (debug > 1)
+ for (i = 0; i < KEY_MAX; i++)
+ if ((uint64_t)1 << i & v)
+ say(mpage->mlinks->file,
+ "Adding key %s=%*s",
+ mansearch_keynames[i], (int)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;
+
+ if (mustfree)
+ free(cp);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * If the string contains escape sequences,
+ * replace it with an allocated rendering and return 1,
+ * such that the caller can free it after use.
+ * Otherwise, do nothing and return 0.
+ */
+static int
+render_string(char **public, size_t *psz)
+{
+ const char *src, *scp, *addcp, *seq;
+ char *dst;
+ size_t ssz, dsz, addsz;
+ char utfbuf[7], res[6];
+ int seqlen, unicode;
+
+ res[0] = '\\';
+ res[1] = '\t';
+ res[2] = ASCII_NBRSP;
+ res[3] = ASCII_HYPH;
+ res[4] = ASCII_BREAK;
+ res[5] = '\0';
+
+ src = scp = *public;
+ ssz = *psz;
+ dst = NULL;
+ dsz = 0;
+
+ while (scp < src + *psz) {
+
+ /* Leave normal characters unchanged. */
+
+ if (strchr(res, *scp) == NULL) {
+ if (dst != NULL)
+ dst[dsz++] = *scp;
+ scp++;
+ continue;
+ }
+
+ /*
+ * Found something that requires replacing,
+ * make sure we have a destination buffer.
+ */
+
+ if (dst == NULL) {
+ dst = mandoc_malloc(ssz + 1);
+ dsz = scp - src;
+ memcpy(dst, src, dsz);
+ }
+
+ /* Handle single-char special characters. */
+
+ switch (*scp) {
+ case '\\':
+ break;
+ case '\t':
+ case ASCII_NBRSP:
+ dst[dsz++] = ' ';
+ scp++;
+ continue;
+ case ASCII_HYPH:
+ dst[dsz++] = '-';
+ /* FALLTHROUGH */
+ case ASCII_BREAK:
+ scp++;
+ continue;
+ default:
+ abort();
+ }
+
+ /*
+ * Found an escape sequence.
+ * Read past the slash, then parse it.
+ * Ignore everything except characters.
+ */
+
+ scp++;
+ if (mandoc_escape(&scp, &seq, &seqlen) != ESCAPE_SPECIAL)
+ continue;
+
+ /*
+ * Render the special character
+ * as either UTF-8 or ASCII.
+ */
+
+ if (write_utf8) {
+ unicode = mchars_spec2cp(seq, seqlen);
+ if (unicode <= 0)
+ continue;
+ addsz = utf8(unicode, utfbuf);
+ if (addsz == 0)
+ continue;
+ addcp = utfbuf;
+ } else {
+ addcp = mchars_spec2str(seq, seqlen, &addsz);
+ if (addcp == NULL)
+ continue;
+ if (*addcp == ASCII_NBRSP) {
+ addcp = " ";
+ addsz = 1;
+ }
+ }
+
+ /* Copy the rendered glyph into the stream. */
+
+ ssz += addsz;
+ dst = mandoc_realloc(dst, ssz + 1);
+ memcpy(dst + dsz, addcp, addsz);
+ dsz += addsz;
+ }
+ if (dst != NULL) {
+ *public = dst;
+ *psz = dsz;
+ }
+
+ /* Trim trailing whitespace and NUL-terminate. */
+
+ while (*psz > 0 && (*public)[*psz - 1] == ' ')
+ --*psz;
+ if (dst != NULL) {
+ (*public)[*psz] = '\0';
+ return 1;
+ } else
+ return 0;
+}
+
+static void
+dbadd_mlink(const struct mlink *mlink)
+{
+ dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE);
+ dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec);
+ dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec);
+ dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch);
+ dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file);
+}
+
+/*
+ * Flush the current page's terms (and their bits) into the database.
+ * Also, handle escape sequences at the last possible moment.
+ */
+static void
+dbadd(struct dba *dba, struct mpage *mpage)
+{
+ struct mlink *mlink;
+ struct str *key;
+ char *cp;
+ uint64_t mask;
+ size_t i;
+ unsigned int slot;
+ int mustfree;
+
+ mlink = mpage->mlinks;
+
+ if (nodb) {
+ for (key = ohash_first(&names, &slot); NULL != key;
+ key = ohash_next(&names, &slot))
+ free(key);
+ for (key = ohash_first(&strings, &slot); NULL != key;
+ key = ohash_next(&strings, &slot))
+ 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");
+
+ cp = mpage->desc;
+ i = strlen(cp);
+ mustfree = render_string(&cp, &i);
+ mpage->dba = dba_page_new(dba->pages,
+ *mpage->arch == '\0' ? mlink->arch : mpage->arch,
+ cp, mlink->file, mpage->form);
+ if (mustfree)
+ free(cp);
+ dba_page_add(mpage->dba, DBP_SECT, mpage->sec);
+
+ while (mlink != NULL) {
+ dbadd_mlink(mlink);
+ mlink = mlink->next;
+ }
+
+ for (key = ohash_first(&names, &slot); NULL != key;
+ key = ohash_next(&names, &slot)) {
+ assert(key->mpage == mpage);
+ dba_page_alias(mpage->dba, key->key, key->mask);
+ free(key);
+ }
+ for (key = ohash_first(&strings, &slot); NULL != key;
+ key = ohash_next(&strings, &slot)) {
+ assert(key->mpage == mpage);
+ i = 0;
+ for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) {
+ if (key->mask & mask)
+ dba_macro_add(dba->macros, i,
+ key->key, mpage->dba);
+ i++;
+ }
+ free(key);
+ }
+}
+
+static void
+dbprune(struct dba *dba)
+{
+ struct dba_array *page, *files;
+ char *file;
+
+ dba_array_FOREACH(dba->pages, page) {
+ files = dba_array_get(page, DBP_FILE);
+ dba_array_FOREACH(files, file) {
+ if (*file < ' ')
+ file++;
+ if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
+ file)) != NULL) {
+ if (debug)
+ say(file, "Deleting from database");
+ dba_array_del(dba->pages);
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Write the database from memory to disk.
+ */
+static void
+dbwrite(struct dba *dba)
+{
+ struct stat sb1, sb2;
+ char tfn[33], *cp1, *cp2;
+ off_t i;
+ int fd1, fd2;
+
+ /*
+ * Do not write empty databases, and delete existing ones
+ * when makewhatis -u causes them to become empty.
+ */
+
+ dba_array_start(dba->pages);
+ if (dba_array_next(dba->pages) == NULL) {
+ if (unlink(MANDOC_DB) == -1 && errno != ENOENT)
+ say(MANDOC_DB, "&unlink");
+ return;
+ }
+
+ /*
+ * Build the database in a temporary file,
+ * then atomically move it into place.
+ */
+
+ if (dba_write(MANDOC_DB "~", dba) != -1) {
+ if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "&rename");
+ unlink(MANDOC_DB "~");
+ }
+ return;
+ }
+
+ /*
+ * We lack write permission and cannot replace the database
+ * file, but let's at least check whether the data changed.
+ */
+
+ (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
+ if (mkdtemp(tfn) == NULL) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&%s", tfn);
+ return;
+ }
+ cp1 = cp2 = MAP_FAILED;
+ fd1 = fd2 = -1;
+ (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
+ if (dba_write(tfn, dba) == -1) {
+ say(tfn, "&dba_write");
+ goto err;
+ }
+ if ((fd1 = open(MANDOC_DB, O_RDONLY, 0)) == -1) {
+ say(MANDOC_DB, "&open");
+ goto err;
+ }
+ if ((fd2 = open(tfn, O_RDONLY, 0)) == -1) {
+ say(tfn, "&open");
+ goto err;
+ }
+ if (fstat(fd1, &sb1) == -1) {
+ say(MANDOC_DB, "&fstat");
+ goto err;
+ }
+ if (fstat(fd2, &sb2) == -1) {
+ say(tfn, "&fstat");
+ goto err;
+ }
+ if (sb1.st_size != sb2.st_size)
+ goto err;
+ if ((cp1 = mmap(NULL, sb1.st_size, PROT_READ, MAP_PRIVATE,
+ fd1, 0)) == MAP_FAILED) {
+ say(MANDOC_DB, "&mmap");
+ goto err;
+ }
+ if ((cp2 = mmap(NULL, sb2.st_size, PROT_READ, MAP_PRIVATE,
+ fd2, 0)) == MAP_FAILED) {
+ say(tfn, "&mmap");
+ goto err;
+ }
+ for (i = 0; i < sb1.st_size; i++)
+ if (cp1[i] != cp2[i])
+ goto err;
+ goto out;
+
+err:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "Data changed, but cannot replace database");
+
+out:
+ if (cp1 != MAP_FAILED)
+ munmap(cp1, sb1.st_size);
+ if (cp2 != MAP_FAILED)
+ munmap(cp2, sb2.st_size);
+ if (fd1 != -1)
+ close(fd1);
+ if (fd2 != -1)
+ close(fd2);
+ unlink(tfn);
+ *strrchr(tfn, '/') = '\0';
+ rmdir(tfn);
+}
+
+static int
+set_basedir(const char *targetdir, int report_baddir)
+{
+ static char startdir[PATH_MAX];
+ static int getcwd_status; /* 1 = ok, 2 = failure */
+ static int chdir_status; /* 1 = changed directory */
+
+ /*
+ * 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 (getcwd_status == 0) {
+ if (getcwd(startdir, sizeof(startdir)) == NULL) {
+ 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';
+ basedir_len = 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 (getcwd_status == 2) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "getcwd: %s", startdir);
+ return 0;
+ }
+ if (chdir(startdir) == -1) {
+ 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 (realpath(targetdir, basedir) == NULL) {
+ if (report_baddir || errno != ENOENT) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&%s: realpath", targetdir);
+ }
+ *basedir = '\0';
+ return 0;
+ } else if (chdir(basedir) == -1) {
+ if (report_baddir || errno != ENOENT) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&chdir");
+ }
+ *basedir = '\0';
+ return 0;
+ }
+ chdir_status = 1;
+ basedir_len = strlen(basedir);
+ if (basedir[basedir_len - 1] != '/') {
+ if (basedir_len >= PATH_MAX - 1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "Filename too long");
+ *basedir = '\0';
+ basedir_len = 0;
+ return 0;
+ }
+ basedir[basedir_len++] = '/';
+ basedir[basedir_len] = '\0';
+ }
+ return 1;
+}
+
+static void
+say(const char *file, const char *format, ...)
+{
+ va_list ap;
+ int use_errno;
+
+ if (*basedir != '\0')
+ fprintf(stderr, "%s", basedir);
+ if (*basedir != '\0' && *file != '\0')
+ fputc('/', stderr);
+ if (*file != '\0')
+ fprintf(stderr, "%s", file);
+
+ use_errno = 1;
+ if (format != NULL) {
+ switch (*format) {
+ case '&':
+ format++;
+ break;
+ case '\0':
+ format = NULL;
+ break;
+ default:
+ use_errno = 0;
+ break;
+ }
+ }
+ if (format != NULL) {
+ if (*basedir != '\0' || *file != '\0')
+ fputs(": ", stderr);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ }
+ if (use_errno) {
+ if (*basedir != '\0' || *file != '\0' || format != NULL)
+ fputs(": ", stderr);
+ perror(NULL);
+ } else
+ fputc('\n', stderr);
+}
diff --git a/usr.bin/mandoc/manpath.c b/usr.bin/mandoc/manpath.c
new file mode 100644
index 0000000..dcd12a0
--- /dev/null
+++ b/usr.bin/mandoc/manpath.c
@@ -0,0 +1,342 @@
+/* $OpenBSD: manpath.c,v 1.28 2020/02/10 14:42:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2011,2014,2015,2017-2019 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 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.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "manconf.h"
+
+#define MAN_CONF_FILE "/etc/man.conf"
+#define MANPATH_BASE "/usr/share/man:/usr/X11R6/man"
+#define MANPATH_DEFAULT "/usr/share/man:/usr/X11R6/man:/usr/local/man"
+
+static void manconf_file(struct manconf *, const char *);
+static void manpath_add(struct manpaths *, const char *, char);
+static void manpath_parseline(struct manpaths *, char *, char);
+
+
+void
+manconf_parse(struct manconf *conf, const char *file,
+ char *defp, char *auxp)
+{
+ char *insert;
+
+ /* Always prepend -m. */
+ manpath_parseline(&conf->manpath, auxp, 'm');
+
+ /* If -M is given, it overrides everything else. */
+ if (NULL != defp) {
+ manpath_parseline(&conf->manpath, defp, 'M');
+ 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]) {
+ manconf_file(conf, file);
+ return;
+ }
+
+ /* Prepend man.conf(5) to MANPATH. */
+ if (':' == defp[0]) {
+ manconf_file(conf, file);
+ manpath_parseline(&conf->manpath, defp, '\0');
+ return;
+ }
+
+ /* Append man.conf(5) to MANPATH. */
+ if (':' == defp[strlen(defp) - 1]) {
+ manpath_parseline(&conf->manpath, defp, '\0');
+ manconf_file(conf, file);
+ return;
+ }
+
+ /* Insert man.conf(5) into MANPATH. */
+ insert = strstr(defp, "::");
+ if (NULL != insert) {
+ *insert++ = '\0';
+ manpath_parseline(&conf->manpath, defp, '\0');
+ manconf_file(conf, file);
+ manpath_parseline(&conf->manpath, insert + 1, '\0');
+ return;
+ }
+
+ /* MANPATH overrides man.conf(5) completely. */
+ manpath_parseline(&conf->manpath, defp, '\0');
+}
+
+void
+manpath_base(struct manpaths *dirs)
+{
+ char path_base[] = MANPATH_BASE;
+ manpath_parseline(dirs, path_base, '\0');
+}
+
+/*
+ * Parse a FULL pathname from a colon-separated list of arrays.
+ */
+static void
+manpath_parseline(struct manpaths *dirs, char *path, char option)
+{
+ char *dir;
+
+ if (NULL == path)
+ return;
+
+ for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
+ manpath_add(dirs, dir, option);
+}
+
+/*
+ * 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 option)
+{
+ char buf[PATH_MAX];
+ struct stat sb;
+ char *cp;
+ size_t i;
+
+ if ((cp = realpath(dir, buf)) == NULL)
+ goto fail;
+
+ for (i = 0; i < dirs->sz; i++)
+ if (strcmp(dirs->paths[i], dir) == 0)
+ return;
+
+ if (stat(cp, &sb) == -1)
+ goto fail;
+
+ dirs->paths = mandoc_reallocarray(dirs->paths,
+ dirs->sz + 1, sizeof(*dirs->paths));
+ dirs->paths[dirs->sz++] = mandoc_strdup(cp);
+ return;
+
+fail:
+ if (option != '\0')
+ mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
+ "-%c %s: %s", option, dir, strerror(errno));
+}
+
+void
+manconf_free(struct manconf *conf)
+{
+ size_t i;
+
+ for (i = 0; i < conf->manpath.sz; i++)
+ free(conf->manpath.paths[i]);
+
+ free(conf->manpath.paths);
+ free(conf->output.includes);
+ free(conf->output.man);
+ free(conf->output.paper);
+ free(conf->output.style);
+}
+
+static void
+manconf_file(struct manconf *conf, const char *file)
+{
+ const char *const toks[] = { "manpath", "output" };
+ char manpath_default[] = MANPATH_DEFAULT;
+
+ FILE *stream;
+ char *line, *cp, *ep;
+ size_t linesz, tok, toklen;
+ ssize_t linelen;
+
+ if ((stream = fopen(file, "r")) == NULL)
+ goto out;
+
+ line = NULL;
+ linesz = 0;
+
+ while ((linelen = getline(&line, &linesz, stream)) != -1) {
+ cp = line;
+ ep = cp + linelen - 1;
+ while (ep > cp && isspace((unsigned char)*ep))
+ *ep-- = '\0';
+ while (isspace((unsigned char)*cp))
+ cp++;
+ if (cp == ep || *cp == '#')
+ continue;
+
+ for (tok = 0; tok < sizeof(toks)/sizeof(toks[0]); tok++) {
+ toklen = strlen(toks[tok]);
+ if (cp + toklen < ep &&
+ isspace((unsigned char)cp[toklen]) &&
+ strncmp(cp, toks[tok], toklen) == 0) {
+ cp += toklen;
+ while (isspace((unsigned char)*cp))
+ cp++;
+ break;
+ }
+ }
+
+ switch (tok) {
+ case 0: /* manpath */
+ manpath_add(&conf->manpath, cp, '\0');
+ *manpath_default = '\0';
+ break;
+ case 1: /* output */
+ manconf_output(&conf->output, cp, 1);
+ break;
+ default:
+ break;
+ }
+ }
+ free(line);
+ fclose(stream);
+
+out:
+ if (*manpath_default != '\0')
+ manpath_parseline(&conf->manpath, manpath_default, '\0');
+}
+
+int
+manconf_output(struct manoutput *conf, const char *cp, int fromfile)
+{
+ const char *const toks[] = {
+ "includes", "man", "paper", "style", "indent", "width",
+ "tag", "fragment", "mdoc", "noval", "toc"
+ };
+ const size_t ntoks = sizeof(toks) / sizeof(toks[0]);
+
+ const char *errstr;
+ char *oldval;
+ size_t len, tok;
+
+ for (tok = 0; tok < ntoks; tok++) {
+ len = strlen(toks[tok]);
+ if (strncmp(cp, toks[tok], len) == 0 &&
+ strchr(" = ", cp[len]) != NULL) {
+ cp += len;
+ if (*cp == '=')
+ cp++;
+ while (isspace((unsigned char)*cp))
+ cp++;
+ break;
+ }
+ }
+
+ if (tok < 6 && *cp == '\0') {
+ mandoc_msg(MANDOCERR_BADVAL_MISS, 0, 0, "-O %s=?", toks[tok]);
+ return -1;
+ }
+ if (tok > 6 && tok < ntoks && *cp != '\0') {
+ mandoc_msg(MANDOCERR_BADVAL, 0, 0, "-O %s=%s", toks[tok], cp);
+ return -1;
+ }
+
+ switch (tok) {
+ case 0:
+ if (conf->includes != NULL) {
+ oldval = mandoc_strdup(conf->includes);
+ break;
+ }
+ conf->includes = mandoc_strdup(cp);
+ return 0;
+ case 1:
+ if (conf->man != NULL) {
+ oldval = mandoc_strdup(conf->man);
+ break;
+ }
+ conf->man = mandoc_strdup(cp);
+ return 0;
+ case 2:
+ if (conf->paper != NULL) {
+ oldval = mandoc_strdup(conf->paper);
+ break;
+ }
+ conf->paper = mandoc_strdup(cp);
+ return 0;
+ case 3:
+ if (conf->style != NULL) {
+ oldval = mandoc_strdup(conf->style);
+ break;
+ }
+ conf->style = mandoc_strdup(cp);
+ return 0;
+ case 4:
+ if (conf->indent) {
+ mandoc_asprintf(&oldval, "%zu", conf->indent);
+ break;
+ }
+ conf->indent = strtonum(cp, 0, 1000, &errstr);
+ if (errstr == NULL)
+ return 0;
+ mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0,
+ "-O indent=%s is %s", cp, errstr);
+ return -1;
+ case 5:
+ if (conf->width) {
+ mandoc_asprintf(&oldval, "%zu", conf->width);
+ break;
+ }
+ conf->width = strtonum(cp, 1, 1000, &errstr);
+ if (errstr == NULL)
+ return 0;
+ mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0,
+ "-O width=%s is %s", cp, errstr);
+ return -1;
+ case 6:
+ if (conf->tag != NULL) {
+ oldval = mandoc_strdup(conf->tag);
+ break;
+ }
+ conf->tag = mandoc_strdup(cp);
+ return 0;
+ case 7:
+ conf->fragment = 1;
+ return 0;
+ case 8:
+ conf->mdoc = 1;
+ return 0;
+ case 9:
+ conf->noval = 1;
+ return 0;
+ case 10:
+ conf->toc = 1;
+ return 0;
+ default:
+ mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-O %s", cp);
+ return -1;
+ }
+ if (fromfile) {
+ free(oldval);
+ return 0;
+ } else {
+ mandoc_msg(MANDOCERR_BADVAL_DUPE, 0, 0,
+ "-O %s=%s: already set to %s", toks[tok], cp, oldval);
+ free(oldval);
+ return -1;
+ }
+}
diff --git a/usr.bin/mandoc/mansearch.c b/usr.bin/mandoc/mansearch.c
new file mode 100644
index 0000000..7cb3468
--- /dev/null
+++ b/usr.bin/mandoc/mansearch.c
@@ -0,0 +1,842 @@
+/* $OpenBSD: mansearch.c,v 1.65 2019/07/01 22:43:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.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>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "manconf.h"
+#include "mansearch.h"
+#include "dbm.h"
+
+struct expr {
+ /* Used for terms: */
+ struct dbm_match match; /* Match type and expression. */
+ uint64_t bits; /* Type mask. */
+ /* Used for OR and AND groups: */
+ struct expr *next; /* Next child in the parent group. */
+ struct expr *child; /* First child in this group. */
+ enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
+};
+
+const char *const mansearch_keynames[KEY_MAX] = {
+ "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"
+};
+
+
+static struct ohash *manmerge(struct expr *, struct ohash *);
+static struct ohash *manmerge_term(struct expr *, struct ohash *);
+static struct ohash *manmerge_or(struct expr *, struct ohash *);
+static struct ohash *manmerge_and(struct expr *, struct ohash *);
+static char *buildnames(const struct dbm_page *);
+static char *buildoutput(size_t, struct dbm_page *);
+static size_t lstlen(const char *, size_t);
+static void lstcat(char *, size_t *, const char *, const char *);
+static int lstmatch(const char *, const char *);
+static struct expr *exprcomp(const struct mansearch *,
+ int, char *[], int *);
+static struct expr *expr_and(const struct mansearch *,
+ int, char *[], int *);
+static struct expr *exprterm(const struct mansearch *,
+ int, char *[], int *);
+static void exprfree(struct expr *);
+static int manpage_compare(const void *, const void *);
+
+
+int
+mansearch(const struct mansearch *search,
+ const struct manpaths *paths,
+ int argc, char *argv[],
+ struct manpage **res, size_t *sz)
+{
+ char buf[PATH_MAX];
+ struct dbm_res *rp;
+ struct expr *e;
+ struct dbm_page *page;
+ struct manpage *mpage;
+ struct ohash *htab;
+ size_t cur, i, maxres, outkey;
+ unsigned int slot;
+ int argi, chdir_status, getcwd_status, im;
+
+ argi = 0;
+ if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
+ *sz = 0;
+ return 0;
+ }
+
+ cur = maxres = 0;
+ if (res != NULL)
+ *res = NULL;
+
+ outkey = KEY_Nd;
+ if (search->outkey != NULL)
+ for (im = 0; im < KEY_MAX; im++)
+ if (0 == strcasecmp(search->outkey,
+ mansearch_keynames[im])) {
+ outkey = im;
+ break;
+ }
+
+ /*
+ * Remember the original working directory, if possible.
+ * This will be needed if the second or a later directory
+ * 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 (getcwd(buf, PATH_MAX) == NULL) {
+ getcwd_status = 0;
+ (void)strlcpy(buf, strerror(errno), sizeof(buf));
+ } else
+ getcwd_status = 1;
+
+ /*
+ * 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.
+ */
+
+ chdir_status = 0;
+ for (i = 0; i < paths->sz; i++) {
+ if (chdir_status && paths->paths[i][0] != '/') {
+ if ( ! getcwd_status) {
+ warnx("%s: getcwd: %s", paths->paths[i], buf);
+ continue;
+ } else if (chdir(buf) == -1) {
+ warn("%s", buf);
+ continue;
+ }
+ }
+ if (chdir(paths->paths[i]) == -1) {
+ warn("%s", paths->paths[i]);
+ continue;
+ }
+ chdir_status = 1;
+
+ if (dbm_open(MANDOC_DB) == -1) {
+ if (errno != ENOENT)
+ warn("%s/%s", paths->paths[i], MANDOC_DB);
+ continue;
+ }
+
+ if ((htab = manmerge(e, NULL)) == NULL) {
+ dbm_close();
+ continue;
+ }
+
+ for (rp = ohash_first(htab, &slot); rp != NULL;
+ rp = ohash_next(htab, &slot)) {
+ page = dbm_page_get(rp->page);
+
+ if (lstmatch(search->sec, page->sect) == 0 ||
+ lstmatch(search->arch, page->arch) == 0 ||
+ (search->argmode == ARG_NAME &&
+ rp->bits <= (int32_t)(NAME_SYN & NAME_MASK)))
+ continue;
+
+ if (res == NULL) {
+ cur = 1;
+ break;
+ }
+ if (cur + 1 > maxres) {
+ maxres += 1024;
+ *res = mandoc_reallocarray(*res,
+ maxres, sizeof(**res));
+ }
+ mpage = *res + cur;
+ mandoc_asprintf(&mpage->file, "%s/%s",
+ paths->paths[i], page->file + 1);
+ if (access(chdir_status ? page->file + 1 :
+ mpage->file, R_OK) == -1) {
+ warn("%s", mpage->file);
+ warnx("outdated mandoc.db contains "
+ "bogus %s entry, run makewhatis %s",
+ page->file + 1, paths->paths[i]);
+ free(mpage->file);
+ free(rp);
+ continue;
+ }
+ mpage->names = buildnames(page);
+ mpage->output = buildoutput(outkey, page);
+ mpage->bits = search->firstmatch ? rp->bits : 0;
+ mpage->ipath = i;
+ mpage->sec = *page->sect - '0';
+ if (mpage->sec < 0 || mpage->sec > 9)
+ mpage->sec = 10;
+ mpage->form = *page->file;
+ free(rp);
+ cur++;
+ }
+ ohash_delete(htab);
+ free(htab);
+ dbm_close();
+
+ /*
+ * In man(1) mode, prefer matches in earlier trees
+ * over matches in later trees.
+ */
+
+ if (cur && search->firstmatch)
+ break;
+ }
+ if (res != NULL)
+ qsort(*res, cur, sizeof(struct manpage), manpage_compare);
+ if (chdir_status && getcwd_status && chdir(buf) == -1)
+ warn("%s", buf);
+ exprfree(e);
+ *sz = cur;
+ return res != NULL || cur;
+}
+
+/*
+ * Merge the results for the expression tree rooted at e
+ * into the the result list htab.
+ */
+static struct ohash *
+manmerge(struct expr *e, struct ohash *htab)
+{
+ switch (e->type) {
+ case EXPR_TERM:
+ return manmerge_term(e, htab);
+ case EXPR_OR:
+ return manmerge_or(e->child, htab);
+ case EXPR_AND:
+ return manmerge_and(e->child, htab);
+ default:
+ abort();
+ }
+}
+
+static struct ohash *
+manmerge_term(struct expr *e, struct ohash *htab)
+{
+ struct dbm_res res, *rp;
+ uint64_t ib;
+ unsigned int slot;
+ int im;
+
+ if (htab == NULL) {
+ htab = mandoc_malloc(sizeof(*htab));
+ mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
+ }
+
+ for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
+ if ((e->bits & ib) == 0)
+ continue;
+
+ switch (ib) {
+ case TYPE_arch:
+ dbm_page_byarch(&e->match);
+ break;
+ case TYPE_sec:
+ dbm_page_bysect(&e->match);
+ break;
+ case TYPE_Nm:
+ dbm_page_byname(&e->match);
+ break;
+ case TYPE_Nd:
+ dbm_page_bydesc(&e->match);
+ break;
+ default:
+ dbm_page_bymacro(im - 2, &e->match);
+ break;
+ }
+
+ /*
+ * When hashing for deduplication, use the unique
+ * page ID itself instead of a hash function;
+ * that is quite efficient.
+ */
+
+ for (;;) {
+ res = dbm_page_next();
+ if (res.page == -1)
+ break;
+ slot = ohash_lookup_memory(htab,
+ (char *)&res, sizeof(res.page), res.page);
+ if ((rp = ohash_find(htab, slot)) != NULL) {
+ rp->bits |= res.bits;
+ continue;
+ }
+ rp = mandoc_malloc(sizeof(*rp));
+ *rp = res;
+ ohash_insert(htab, slot, rp);
+ }
+ }
+ return htab;
+}
+
+static struct ohash *
+manmerge_or(struct expr *e, struct ohash *htab)
+{
+ while (e != NULL) {
+ htab = manmerge(e, htab);
+ e = e->next;
+ }
+ return htab;
+}
+
+static struct ohash *
+manmerge_and(struct expr *e, struct ohash *htab)
+{
+ struct ohash *hand, *h1, *h2;
+ struct dbm_res *res;
+ unsigned int slot1, slot2;
+
+ /* Evaluate the first term of the AND clause. */
+
+ hand = manmerge(e, NULL);
+
+ while ((e = e->next) != NULL) {
+
+ /* Evaluate the next term and prepare for ANDing. */
+
+ h2 = manmerge(e, NULL);
+ if (ohash_entries(h2) < ohash_entries(hand)) {
+ h1 = h2;
+ h2 = hand;
+ } else
+ h1 = hand;
+ hand = mandoc_malloc(sizeof(*hand));
+ mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
+
+ /* Keep all pages that are in both result sets. */
+
+ for (res = ohash_first(h1, &slot1); res != NULL;
+ res = ohash_next(h1, &slot1)) {
+ if (ohash_find(h2, ohash_lookup_memory(h2,
+ (char *)res, sizeof(res->page),
+ res->page)) == NULL)
+ free(res);
+ else
+ ohash_insert(hand, ohash_lookup_memory(hand,
+ (char *)res, sizeof(res->page),
+ res->page), res);
+ }
+
+ /* Discard the merged results. */
+
+ for (res = ohash_first(h2, &slot2); res != NULL;
+ res = ohash_next(h2, &slot2))
+ free(res);
+ ohash_delete(h2);
+ free(h2);
+ ohash_delete(h1);
+ free(h1);
+ }
+
+ /* Merge the result of the AND into htab. */
+
+ if (htab == NULL)
+ return hand;
+
+ for (res = ohash_first(hand, &slot1); res != NULL;
+ res = ohash_next(hand, &slot1)) {
+ slot2 = ohash_lookup_memory(htab,
+ (char *)res, sizeof(res->page), res->page);
+ if (ohash_find(htab, slot2) == NULL)
+ ohash_insert(htab, slot2, res);
+ else
+ free(res);
+ }
+
+ /* Discard the merged result. */
+
+ ohash_delete(hand);
+ free(hand);
+ return htab;
+}
+
+void
+mansearch_free(struct manpage *res, size_t sz)
+{
+ size_t i;
+
+ for (i = 0; i < sz; i++) {
+ free(res[i].file);
+ free(res[i].names);
+ free(res[i].output);
+ }
+ free(res);
+}
+
+static int
+manpage_compare(const void *vp1, const void *vp2)
+{
+ const struct manpage *mp1, *mp2;
+ const char *cp1, *cp2;
+ size_t sz1, sz2;
+ int diff;
+
+ mp1 = vp1;
+ mp2 = vp2;
+ if ((diff = mp2->bits - mp1->bits) ||
+ (diff = mp1->sec - mp2->sec))
+ return diff;
+
+ /* Fall back to alphabetic ordering of names. */
+ sz1 = strcspn(mp1->names, "(");
+ sz2 = strcspn(mp2->names, "(");
+ if (sz1 < sz2)
+ sz1 = sz2;
+ if ((diff = strncasecmp(mp1->names, mp2->names, sz1)))
+ return diff;
+
+ /* For identical names and sections, prefer arch-dependent. */
+ cp1 = strchr(mp1->names + sz1, '/');
+ cp2 = strchr(mp2->names + sz2, '/');
+ return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) :
+ cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0;
+}
+
+static char *
+buildnames(const struct dbm_page *page)
+{
+ char *buf;
+ size_t i, sz;
+
+ sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) +
+ (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2;
+ buf = mandoc_malloc(sz);
+ i = 0;
+ lstcat(buf, &i, page->name, ", ");
+ buf[i++] = '(';
+ lstcat(buf, &i, page->sect, ", ");
+ if (page->arch != NULL) {
+ buf[i++] = '/';
+ lstcat(buf, &i, page->arch, ", ");
+ }
+ buf[i++] = ')';
+ buf[i++] = '\0';
+ assert(i == sz);
+ return buf;
+}
+
+/*
+ * Count the buffer space needed to print the NUL-terminated
+ * list of NUL-terminated strings, when printing sep separator
+ * characters between strings.
+ */
+static size_t
+lstlen(const char *cp, size_t sep)
+{
+ size_t sz;
+
+ for (sz = 0; *cp != '\0'; cp++) {
+
+ /* Skip names appearing only in the SYNOPSIS. */
+ if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
+ while (*cp != '\0')
+ cp++;
+ continue;
+ }
+
+ /* Skip name class markers. */
+ if (*cp < ' ')
+ cp++;
+
+ /* Print a separator before each but the first string. */
+ if (sz)
+ sz += sep;
+
+ /* Copy one string. */
+ while (*cp != '\0') {
+ sz++;
+ cp++;
+ }
+ }
+ return sz;
+}
+
+/*
+ * Print the NUL-terminated list of NUL-terminated strings
+ * into the buffer, seperating strings with sep.
+ */
+static void
+lstcat(char *buf, size_t *i, const char *cp, const char *sep)
+{
+ const char *s;
+ size_t i_start;
+
+ for (i_start = *i; *cp != '\0'; cp++) {
+
+ /* Skip names appearing only in the SYNOPSIS. */
+ if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
+ while (*cp != '\0')
+ cp++;
+ continue;
+ }
+
+ /* Skip name class markers. */
+ if (*cp < ' ')
+ cp++;
+
+ /* Print a separator before each but the first string. */
+ if (*i > i_start) {
+ s = sep;
+ while (*s != '\0')
+ buf[(*i)++] = *s++;
+ }
+
+ /* Copy one string. */
+ while (*cp != '\0')
+ buf[(*i)++] = *cp++;
+ }
+
+}
+
+/*
+ * Return 1 if the string *want occurs in any of the strings
+ * in the NUL-terminated string list *have, or 0 otherwise.
+ * If either argument is NULL or empty, assume no filtering
+ * is desired and return 1.
+ */
+static int
+lstmatch(const char *want, const char *have)
+{
+ if (want == NULL || have == NULL || *have == '\0')
+ return 1;
+ while (*have != '\0') {
+ if (strcasestr(have, want) != NULL)
+ return 1;
+ have = strchr(have, '\0') + 1;
+ }
+ return 0;
+}
+
+/*
+ * Build a list of values taken by the macro im in the manual page.
+ */
+static char *
+buildoutput(size_t im, struct dbm_page *page)
+{
+ const char *oldoutput, *sep, *input;
+ char *output, *newoutput, *value;
+ size_t sz, i;
+
+ switch (im) {
+ case KEY_Nd:
+ return mandoc_strdup(page->desc);
+ case KEY_Nm:
+ input = page->name;
+ break;
+ case KEY_sec:
+ input = page->sect;
+ break;
+ case KEY_arch:
+ input = page->arch;
+ if (input == NULL)
+ input = "all\0";
+ break;
+ default:
+ input = NULL;
+ break;
+ }
+
+ if (input != NULL) {
+ sz = lstlen(input, 3) + 1;
+ output = mandoc_malloc(sz);
+ i = 0;
+ lstcat(output, &i, input, " # ");
+ output[i++] = '\0';
+ assert(i == sz);
+ return output;
+ }
+
+ output = NULL;
+ dbm_macro_bypage(im - 2, page->addr);
+ while ((value = dbm_macro_next()) != NULL) {
+ if (output == NULL) {
+ oldoutput = "";
+ sep = "";
+ } else {
+ oldoutput = output;
+ sep = " # ";
+ }
+ mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
+ free(output);
+ output = newoutput;
+ }
+ return output;
+}
+
+/*
+ * 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[], int *argi)
+{
+ struct expr *parent, *child;
+ int needterm, nested;
+
+ if ((nested = *argi) == argc)
+ return NULL;
+ needterm = 1;
+ parent = child = NULL;
+ while (*argi < argc) {
+ if (strcmp(")", argv[*argi]) == 0) {
+ if (needterm)
+ warnx("missing term "
+ "before closing parenthesis");
+ needterm = 0;
+ if (nested)
+ break;
+ warnx("ignoring unmatched right parenthesis");
+ ++*argi;
+ continue;
+ }
+ if (strcmp("-o", argv[*argi]) == 0) {
+ if (needterm) {
+ if (*argi > 0)
+ warnx("ignoring -o after %s",
+ argv[*argi - 1]);
+ else
+ warnx("ignoring initial -o");
+ }
+ needterm = 1;
+ ++*argi;
+ continue;
+ }
+ needterm = 0;
+ if (child == NULL) {
+ child = expr_and(search, argc, argv, argi);
+ continue;
+ }
+ if (parent == NULL) {
+ parent = mandoc_calloc(1, sizeof(*parent));
+ parent->type = EXPR_OR;
+ parent->next = NULL;
+ parent->child = child;
+ }
+ child->next = expr_and(search, argc, argv, argi);
+ child = child->next;
+ }
+ if (needterm && *argi)
+ warnx("ignoring trailing %s", argv[*argi - 1]);
+ return parent == NULL ? child : parent;
+}
+
+static struct expr *
+expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
+{
+ struct expr *parent, *child;
+ int needterm;
+
+ needterm = 1;
+ parent = child = NULL;
+ while (*argi < argc) {
+ if (strcmp(")", argv[*argi]) == 0) {
+ if (needterm)
+ warnx("missing term "
+ "before closing parenthesis");
+ needterm = 0;
+ break;
+ }
+ if (strcmp("-o", argv[*argi]) == 0)
+ break;
+ if (strcmp("-a", argv[*argi]) == 0) {
+ if (needterm) {
+ if (*argi > 0)
+ warnx("ignoring -a after %s",
+ argv[*argi - 1]);
+ else
+ warnx("ignoring initial -a");
+ }
+ needterm = 1;
+ ++*argi;
+ continue;
+ }
+ if (needterm == 0)
+ break;
+ if (child == NULL) {
+ child = exprterm(search, argc, argv, argi);
+ if (child != NULL)
+ needterm = 0;
+ continue;
+ }
+ needterm = 0;
+ if (parent == NULL) {
+ parent = mandoc_calloc(1, sizeof(*parent));
+ parent->type = EXPR_AND;
+ parent->next = NULL;
+ parent->child = child;
+ }
+ child->next = exprterm(search, argc, argv, argi);
+ if (child->next != NULL) {
+ child = child->next;
+ needterm = 0;
+ }
+ }
+ if (needterm && *argi)
+ warnx("ignoring trailing %s", argv[*argi - 1]);
+ return parent == NULL ? child : parent;
+}
+
+static struct expr *
+exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
+{
+ char errbuf[BUFSIZ];
+ struct expr *e;
+ char *key, *val;
+ uint64_t iterbit;
+ int cs, i, irc;
+
+ if (strcmp("(", argv[*argi]) == 0) {
+ ++*argi;
+ e = exprcomp(search, argc, argv, argi);
+ if (*argi < argc) {
+ assert(strcmp(")", argv[*argi]) == 0);
+ ++*argi;
+ } else
+ warnx("unclosed parenthesis");
+ return e;
+ }
+
+ if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) {
+ cs = 0;
+ ++*argi;
+ } else
+ cs = 1;
+
+ e = mandoc_calloc(1, sizeof(*e));
+ e->type = EXPR_TERM;
+ e->bits = 0;
+ e->next = NULL;
+ e->child = NULL;
+
+ if (search->argmode == ARG_NAME) {
+ e->bits = TYPE_Nm;
+ e->match.type = DBM_EXACT;
+ e->match.str = argv[(*argi)++];
+ return e;
+ }
+
+ /*
+ * Separate macro keys from search string.
+ * If needed, request regular expression handling.
+ */
+
+ if (search->argmode == ARG_WORD) {
+ e->bits = TYPE_Nm;
+ e->match.type = DBM_REGEX;
+ mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
+ cs = 0;
+ } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
+ e->bits = TYPE_Nm | TYPE_Nd;
+ e->match.type = DBM_REGEX;
+ val = argv[*argi];
+ cs = 0;
+ } else {
+ if (val == argv[*argi])
+ e->bits = TYPE_Nm | TYPE_Nd;
+ if (*val == '=') {
+ e->match.type = DBM_SUB;
+ e->match.str = val + 1;
+ } else
+ e->match.type = DBM_REGEX;
+ *val++ = '\0';
+ if (strstr(argv[*argi], "arch") != NULL)
+ cs = 0;
+ }
+
+ /* Compile regular expressions. */
+
+ if (e->match.type == DBM_REGEX) {
+ e->match.re = mandoc_malloc(sizeof(*e->match.re));
+ irc = regcomp(e->match.re, val,
+ REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
+ if (irc) {
+ regerror(irc, e->match.re, errbuf, sizeof(errbuf));
+ warnx("regcomp /%s/: %s", val, errbuf);
+ }
+ if (search->argmode == ARG_WORD)
+ free(val);
+ if (irc) {
+ free(e->match.re);
+ free(e);
+ ++*argi;
+ return NULL;
+ }
+ }
+
+ if (e->bits) {
+ ++*argi;
+ return e;
+ }
+
+ /*
+ * Parse out all possible fields.
+ * If the field doesn't resolve, bail.
+ */
+
+ while (NULL != (key = strsep(&argv[*argi], ","))) {
+ if ('\0' == *key)
+ continue;
+ for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
+ if (0 == strcasecmp(key, mansearch_keynames[i])) {
+ e->bits |= iterbit;
+ break;
+ }
+ }
+ if (i == KEY_MAX) {
+ if (strcasecmp(key, "any"))
+ warnx("treating unknown key "
+ "\"%s\" as \"any\"", key);
+ e->bits |= ~0ULL;
+ }
+ }
+
+ ++*argi;
+ return e;
+}
+
+static void
+exprfree(struct expr *e)
+{
+ if (e->next != NULL)
+ exprfree(e->next);
+ if (e->child != NULL)
+ exprfree(e->child);
+ free(e);
+}
diff --git a/usr.bin/mandoc/mansearch.h b/usr.bin/mandoc/mansearch.h
new file mode 100644
index 0000000..c2efe7c
--- /dev/null
+++ b/usr.bin/mandoc/mansearch.h
@@ -0,0 +1,118 @@
+/* $OpenBSD: mansearch.h,v 1.24 2019/04/30 18:48:26 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013, 2014, 2016, 2017 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.
+ */
+
+#define MANDOC_DB "mandoc.db"
+#define MANDOCDB_MAGIC 0x3a7d0cdb
+#define MANDOCDB_VERSION 1
+
+#define MACRO_MAX 36
+#define KEY_arch 0
+#define KEY_sec 1
+#define KEY_Nm 38
+#define KEY_Nd 39
+#define KEY_MAX 40
+
+#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_FIRST 0x0000004000000004ULL
+#define NAME_TITLE 0x0000004000000006ULL
+#define NAME_HEAD 0x0000004000000008ULL
+#define NAME_FILE 0x0000004000000010ULL
+#define NAME_MASK 0x000000000000001fULL
+
+enum form {
+ FORM_SRC = 1, /* Format is mdoc(7) or man(7). */
+ FORM_CAT, /* Manual page is preformatted. */
+ FORM_NONE /* Format is unknown. */
+};
+
+enum argmode {
+ ARG_FILE = 0,
+ ARG_NAME,
+ ARG_WORD,
+ ARG_EXPR
+};
+
+struct manpage {
+ char *file; /* to be prefixed by manpath */
+ char *names; /* a list of names with sections */
+ char *output; /* user-defined additional output */
+ uint64_t bits; /* name type mask */
+ size_t ipath; /* number of the manpath */
+ int sec; /* section number, 10 means invalid */
+ enum form form;
+};
+
+struct mansearch {
+ const char *arch; /* architecture/NULL */
+ const char *sec; /* mansection/NULL */
+ const char *outkey; /* show content of this macro */
+ enum argmode argmode; /* interpretation of arguments */
+ int firstmatch; /* first matching database only */
+};
+
+
+struct manpaths;
+
+int mansearch(const struct mansearch *cfg, /* options */
+ const struct manpaths *paths, /* manpaths */
+ int argc, /* size of argv */
+ char *argv[], /* search terms */
+ struct manpage **res, /* results */
+ size_t *ressz); /* results returned */
+void mansearch_free(struct manpage *, size_t);
diff --git a/usr.bin/mandoc/mdoc.c b/usr.bin/mandoc/mdoc.c
new file mode 100644
index 0000000..7920985
--- /dev/null
+++ b/usr.bin/mandoc/mdoc.c
@@ -0,0 +1,431 @@
+/* $OpenBSD: mdoc.c,v 1.164 2020/04/06 09:55:49 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2012-2018, 2020 Ingo Schwarze <schwarze@openbsd.org>
+ * 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 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.
+ *
+ * Top level and utility functions of the mdoc(7) parser for mandoc(1).
+ */
+#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 "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libmdoc.h"
+
+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_argnames = __mdoc_argnames;
+
+static int mdoc_ptext(struct roff_man *, int, char *, int);
+static int mdoc_pmacro(struct roff_man *, int, char *, int);
+
+
+/*
+ * 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 roff_man *mdoc, int ln, char *buf, int offs)
+{
+
+ if (mdoc->last->type != ROFFT_EQN || ln > mdoc->last->line)
+ 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);
+}
+
+void
+mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok)
+{
+ struct roff_node *p;
+
+ p = roff_node_alloc(mdoc, line, pos, ROFFT_TAIL, tok);
+ roff_node_append(mdoc, p);
+ mdoc->next = ROFF_NEXT_CHILD;
+}
+
+struct roff_node *
+mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos,
+ enum roff_tok tok, struct roff_node *body)
+{
+ struct roff_node *p;
+
+ body->flags |= NODE_ENDED;
+ body->parent->flags |= NODE_ENDED;
+ p = roff_node_alloc(mdoc, line, pos, ROFFT_BODY, tok);
+ p->body = body;
+ p->norm = body->norm;
+ p->end = ENDBODY_SPACE;
+ roff_node_append(mdoc, p);
+ mdoc->next = ROFF_NEXT_SIBLING;
+ return p;
+}
+
+struct roff_node *
+mdoc_block_alloc(struct roff_man *mdoc, int line, int pos,
+ enum roff_tok tok, struct mdoc_arg *args)
+{
+ struct roff_node *p;
+
+ p = roff_node_alloc(mdoc, line, pos, ROFFT_BLOCK, tok);
+ p->args = args;
+ if (p->args)
+ (args->refcnt)++;
+
+ switch (tok) {
+ case MDOC_Bd:
+ case MDOC_Bf:
+ case MDOC_Bl:
+ case MDOC_En:
+ case MDOC_Rs:
+ p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
+ break;
+ default:
+ break;
+ }
+ roff_node_append(mdoc, p);
+ mdoc->next = ROFF_NEXT_CHILD;
+ return p;
+}
+
+void
+mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos,
+ enum roff_tok tok, struct mdoc_arg *args)
+{
+ struct roff_node *p;
+
+ p = roff_node_alloc(mdoc, line, pos, ROFFT_ELEM, tok);
+ 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;
+ }
+ roff_node_append(mdoc, p);
+ mdoc->next = ROFF_NEXT_CHILD;
+}
+
+/*
+ * Parse free-form text, that is, a line that does not begin with the
+ * control character.
+ */
+static int
+mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs)
+{
+ struct roff_node *n;
+ const char *cp, *sp;
+ char *c, *ws, *end;
+
+ n = mdoc->last;
+
+ /*
+ * If a column list contains plain text, assume an implicit item
+ * macro. This can happen one or more times at the beginning
+ * of such a list, intermixed with non-It mdoc macros and with
+ * nodes generated on the roff level, for example by tbl.
+ */
+
+ if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
+ n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) ||
+ (n->parent != NULL && n->parent->tok == MDOC_Bl &&
+ n->parent->norm->Bl.type == LIST_column)) {
+ mdoc->flags |= MDOC_FREECOL;
+ (*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It,
+ line, offs, &offs, buf);
+ return 1;
+ }
+
+ /*
+ * 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->flags & ROFF_NOFILL)
+ 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, line, (int)(ws - buf), NULL);
+
+ /*
+ * Blank lines are allowed in no-fill mode
+ * and cancel preceding \c,
+ * but add a single vertical space elsewhere.
+ */
+
+ if (buf[offs] == '\0' && (mdoc->flags & ROFF_NOFILL) == 0) {
+ switch (mdoc->last->type) {
+ case ROFFT_TEXT:
+ sp = mdoc->last->string;
+ cp = end = strchr(sp, '\0') - 2;
+ if (cp < sp || cp[0] != '\\' || cp[1] != 'c')
+ break;
+ while (cp > sp && cp[-1] == '\\')
+ cp--;
+ if ((end - cp) % 2)
+ break;
+ *end = '\0';
+ return 1;
+ default:
+ break;
+ }
+ mandoc_msg(MANDOCERR_FI_BLANK, line, (int)(c - buf), NULL);
+ roff_elem_alloc(mdoc, line, offs, ROFF_sp);
+ mdoc->last->flags |= NODE_VALID | NODE_ENDED;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ return 1;
+ }
+
+ roff_word_alloc(mdoc, line, offs, buf+offs);
+
+ if (mdoc->flags & ROFF_NOFILL)
+ 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 |= NODE_EOS;
+
+ for (c = buf + offs; c != NULL; c = strchr(c + 1, '.')) {
+ if (c - buf < offs + 2)
+ continue;
+ if (end - c < 3)
+ break;
+ if (c[1] != ' ' ||
+ isalnum((unsigned char)c[-2]) == 0 ||
+ isalnum((unsigned char)c[-1]) == 0 ||
+ (c[-2] == 'n' && c[-1] == 'c') ||
+ (c[-2] == 'v' && c[-1] == 's'))
+ continue;
+ c += 2;
+ if (*c == ' ')
+ c++;
+ if (*c == ' ')
+ c++;
+ if (isupper((unsigned char)(*c)))
+ mandoc_msg(MANDOCERR_EOS, line, (int)(c - buf), NULL);
+ }
+
+ return 1;
+}
+
+/*
+ * Parse a macro line, that is, a line beginning with the control
+ * character.
+ */
+static int
+mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs)
+{
+ struct roff_node *n;
+ const char *cp;
+ size_t sz;
+ enum roff_tok tok;
+ int sv;
+
+ /* Determine the line macro. */
+
+ sv = offs;
+ tok = TOKEN_NONE;
+ for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++)
+ offs++;
+ if (sz == 2 || sz == 3)
+ tok = roffhash_find(mdoc->mdocmac, buf + sv, sz);
+ if (tok == TOKEN_NONE) {
+ mandoc_msg(MANDOCERR_MACRO, ln, sv, "%s", buf + sv - 1);
+ return 1;
+ }
+
+ /* Skip a leading escape sequence or tab. */
+
+ switch (buf[offs]) {
+ case '\\':
+ cp = buf + offs + 1;
+ mandoc_escape(&cp, NULL, NULL);
+ offs = cp - buf;
+ break;
+ case '\t':
+ offs++;
+ break;
+ default:
+ break;
+ }
+
+ /* Jump to the next non-whitespace word. */
+
+ while (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, ln, offs - 1, NULL);
+
+ /*
+ * If an initial or transparent macro or a list invocation,
+ * divert directly into macro processing.
+ */
+
+ n = mdoc->last;
+ if (n == NULL || tok == MDOC_It || tok == MDOC_El ||
+ roff_tok_transparent(tok)) {
+ (*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf);
+ return 1;
+ }
+
+ /*
+ * If a column list contains a non-It macro, assume an implicit
+ * item macro. This can happen one or more times at the
+ * beginning of such a list, intermixed with text lines and
+ * with nodes generated on the roff level, for example by tbl.
+ */
+
+ if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
+ n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) ||
+ (n->parent != NULL && n->parent->tok == MDOC_Bl &&
+ n->parent->norm->Bl.type == LIST_column)) {
+ mdoc->flags |= MDOC_FREECOL;
+ (*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It, ln, sv, &sv, buf);
+ return 1;
+ }
+
+ /* Normal processing of a macro. */
+
+ (*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf);
+
+ /* 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 '(':
+ case '[':
+ return DELIM_OPEN;
+ case '|':
+ return DELIM_MIDDLE;
+ case '.':
+ case ',':
+ case ';':
+ case ':':
+ case '?':
+ case '!':
+ case ')':
+ 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;
+}
diff --git a/usr.bin/mandoc/mdoc.h b/usr.bin/mandoc/mdoc.h
new file mode 100644
index 0000000..aa4a5ec
--- /dev/null
+++ b/usr.bin/mandoc/mdoc.h
@@ -0,0 +1,158 @@
+/* $OpenBSD: mdoc.h,v 1.71 2018/12/30 00:48:47 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015 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.
+ */
+
+struct roff_node;
+struct roff_man;
+
+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
+};
+
+/*
+ * 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;
+};
+
+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 roff_node *Es;
+ struct mdoc_rs Rs;
+};
+
+/* Names of macro args. Index is enum mdocargt. */
+extern const char *const *mdoc_argnames;
+
+void mdoc_validate(struct roff_man *);
diff --git a/usr.bin/mandoc/mdoc_argv.c b/usr.bin/mandoc/mdoc_argv.c
new file mode 100644
index 0000000..bacac34
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_argv.c
@@ -0,0 +1,680 @@
+/* $OpenBSD: mdoc_argv.c,v 1.76 2019/07/11 16:56:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2012, 2014-2019 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libmdoc.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 roff_man *, int, int *,
+ char *, enum argsflag, char **);
+static int args_checkpunct(const char *, int);
+static void argv_multi(struct roff_man *, int,
+ struct mdoc_argv *, int *, char *);
+static void argv_single(struct roff_man *, 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 - MDOC_Dd] = {
+ { 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 }, /* Ap */
+ { 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 }, /* %U */
+ { ARGSFL_NONE, NULL }, /* Ta */
+};
+
+
+/*
+ * Parse flags and their arguments from the input line.
+ * These come in the form -flag [argument ...].
+ * Some flags take no argument, some one, some multiple.
+ */
+void
+mdoc_argv(struct roff_man *mdoc, int line, enum roff_tok tok,
+ struct mdoc_arg **reta, int *pos, char *buf)
+{
+ struct mdoc_argv tmpv;
+ struct mdoc_argv **retv;
+ const enum mdocargt *argtable;
+ char *argname;
+ int ipos, retc;
+ char savechar;
+
+ *reta = NULL;
+
+ /* Which flags does this macro support? */
+
+ assert(tok >= MDOC_Dd && tok < MDOC_MAX);
+ argtable = mdocargs[tok - MDOC_Dd].argvs;
+ if (argtable == NULL)
+ return;
+
+ /* Loop over the flags on the input line. */
+
+ ipos = *pos;
+ while (buf[ipos] == '-') {
+
+ /* Seek to the first unescaped space. */
+
+ for (argname = buf + ++ipos; buf[ipos] != '\0'; ipos++)
+ if (buf[ipos] == ' ' && buf[ipos - 1] != '\\')
+ break;
+
+ /*
+ * We want to nil-terminate the word to look it up.
+ * 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 ((savechar = buf[ipos]) != '\0')
+ buf[ipos++] = '\0';
+
+ /*
+ * Now look up the word as a flag. Use temporary
+ * storage that we'll copy into the node's flags.
+ */
+
+ while ((tmpv.arg = *argtable++) != MDOC_ARG_MAX)
+ if ( ! strcmp(argname, mdoc_argnames[tmpv.arg]))
+ break;
+
+ /* If it isn't a flag, restore the saved byte. */
+
+ if (tmpv.arg == MDOC_ARG_MAX) {
+ if (savechar != '\0')
+ buf[ipos - 1] = savechar;
+ break;
+ }
+
+ /* Read to the next word (the first argument). */
+
+ while (buf[ipos] == ' ')
+ ipos++;
+
+ /* Parse the arguments of the flag. */
+
+ tmpv.line = line;
+ tmpv.pos = *pos;
+ tmpv.sz = 0;
+ tmpv.value = NULL;
+
+ switch (argvflags[tmpv.arg]) {
+ case ARGV_SINGLE:
+ argv_single(mdoc, line, &tmpv, &ipos, buf);
+ break;
+ case ARGV_MULTI:
+ argv_multi(mdoc, line, &tmpv, &ipos, buf);
+ break;
+ case ARGV_NONE:
+ break;
+ }
+
+ /* Append to the return values. */
+
+ if (*reta == NULL)
+ *reta = mandoc_calloc(1, sizeof(**reta));
+
+ retc = ++(*reta)->argc;
+ retv = &(*reta)->argv;
+ *retv = mandoc_reallocarray(*retv, retc, sizeof(**retv));
+ memcpy(*retv + retc - 1, &tmpv, sizeof(**retv));
+
+ /* Prepare for parsing the next flag. */
+
+ *pos = ipos;
+ argtable = mdocargs[tok - MDOC_Dd].argvs;
+ }
+}
+
+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_args(struct roff_man *mdoc, int line, int *pos,
+ char *buf, enum roff_tok tok, char **v)
+{
+ struct roff_node *n;
+ enum argsflag fl;
+
+ fl = tok == TOKEN_NONE ? ARGSFL_NONE : mdocargs[tok - MDOC_Dd].flags;
+
+ /*
+ * 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.
+ */
+
+ if (tok == MDOC_It) {
+ for (n = mdoc->last; n != NULL; n = n->parent) {
+ if (n->tok != MDOC_Bl)
+ continue;
+ if (n->norm->Bl.type == LIST_column)
+ fl = ARGSFL_TABSEP;
+ break;
+ }
+ }
+
+ return args(mdoc, line, pos, buf, fl, v);
+}
+
+static enum margserr
+args(struct roff_man *mdoc, int line, int *pos,
+ char *buf, enum argsflag fl, char **v)
+{
+ char *p;
+ char *v_local;
+ int pairs;
+
+ if (buf[*pos] == '\0') {
+ if (mdoc->flags & MDOC_PHRASELIT &&
+ ! (mdoc->flags & MDOC_PHRASE)) {
+ mandoc_msg(MANDOCERR_ARG_QUOTE, line, *pos, NULL);
+ mdoc->flags &= ~MDOC_PHRASELIT;
+ }
+ mdoc->flags &= ~MDOC_PHRASEQL;
+ return ARGS_EOLN;
+ }
+
+ if (v == NULL)
+ v = &v_local;
+ *v = buf + *pos;
+
+ if (fl == ARGSFL_DELIM && args_checkpunct(buf, *pos))
+ return ARGS_PUNCT;
+
+ /*
+ * Tabs in `It' lines in `Bl -column' can't be escaped.
+ * Phrases are reparsed for `Ta' and other macros later.
+ */
+
+ if (fl == ARGSFL_TABSEP) {
+ if ((p = strchr(*v, '\t')) != NULL) {
+
+ /*
+ * Words right before and right after
+ * tab characters are not parsed,
+ * unless there is a blank in between.
+ */
+
+ if (p > buf && p[-1] != ' ')
+ mdoc->flags |= MDOC_PHRASEQL;
+ if (p[1] != ' ')
+ mdoc->flags |= MDOC_PHRASEQN;
+
+ /*
+ * One or more blanks after a tab cause
+ * one leading blank in the next column.
+ * So skip all but one of them.
+ */
+
+ *pos += (int)(p - *v) + 1;
+ while (buf[*pos] == ' ' && buf[*pos + 1] == ' ')
+ (*pos)++;
+
+ /*
+ * A tab at the end of an input line
+ * switches to the next column.
+ */
+
+ if (buf[*pos] == '\0' || buf[*pos + 1] == '\0')
+ mdoc->flags |= MDOC_PHRASEQN;
+ } else {
+ p = strchr(*v, '\0');
+ if (p[-1] == ' ')
+ mandoc_msg(MANDOCERR_SPACE_EOL,
+ line, *pos, NULL);
+ *pos += (int)(p - *v);
+ }
+
+ /* Skip any trailing blank characters. */
+ while (p > *v && p[-1] == ' ' &&
+ (p - 1 == *v || p[-2] != '\\'))
+ p--;
+ *p = '\0';
+
+ return ARGS_PHRASE;
+ }
+
+ /*
+ * Process a quoted literal. A quote begins with a double-quote
+ * and ends with a double-quote NOT preceded by a double-quote.
+ * NUL-terminate the literal in place.
+ * Collapse pairs of quotes inside quoted literals.
+ * Whitespace is NOT involved in literal termination.
+ */
+
+ if (mdoc->flags & MDOC_PHRASELIT ||
+ (mdoc->flags & MDOC_PHRASE && buf[*pos] == '\"')) {
+ if ((mdoc->flags & MDOC_PHRASELIT) == 0) {
+ *v = &buf[++(*pos)];
+ 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 (buf[*pos] == '\0') {
+ if ( ! (mdoc->flags & MDOC_PHRASE))
+ mandoc_msg(MANDOCERR_ARG_QUOTE,
+ line, *pos, NULL);
+ return ARGS_WORD;
+ }
+
+ mdoc->flags &= ~MDOC_PHRASELIT;
+ buf[(*pos)++] = '\0';
+
+ if ('\0' == buf[*pos])
+ return ARGS_WORD;
+
+ while (' ' == buf[*pos])
+ (*pos)++;
+
+ if ('\0' == buf[*pos])
+ mandoc_msg(MANDOCERR_SPACE_EOL, line, *pos, NULL);
+
+ return ARGS_WORD;
+ }
+
+ p = &buf[*pos];
+ *v = roff_getarg(mdoc->roff, &p, line, pos);
+ if (v == &v_local)
+ free(*v);
+
+ /*
+ * After parsing the last word in this phrase,
+ * tell lookup() whether or not to interpret it.
+ */
+
+ if (*p == '\0' && mdoc->flags & MDOC_PHRASEQL) {
+ mdoc->flags &= ~MDOC_PHRASEQL;
+ mdoc->flags |= MDOC_PHRASEQF;
+ }
+ return ARGS_ALLOC;
+}
+
+/*
+ * 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 void
+argv_multi(struct roff_man *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 (ac == ARGS_EOLN)
+ break;
+
+ if (v->sz % MULTI_STEP == 0)
+ v->value = mandoc_reallocarray(v->value,
+ v->sz + MULTI_STEP, sizeof(char *));
+
+ if (ac != ARGS_ALLOC)
+ p = mandoc_strdup(p);
+ v->value[(int)v->sz] = p;
+ }
+}
+
+static void
+argv_single(struct roff_man *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 (ac == ARGS_EOLN)
+ return;
+
+ if (ac != ARGS_ALLOC)
+ p = mandoc_strdup(p);
+
+ v->sz = 1;
+ v->value = mandoc_malloc(sizeof(char *));
+ v->value[0] = p;
+}
diff --git a/usr.bin/mandoc/mdoc_html.c b/usr.bin/mandoc/mdoc_html.c
new file mode 100644
index 0000000..fcff005
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_html.c
@@ -0,0 +1,1758 @@
+/* $OpenBSD: mdoc_html.c,v 1.215 2020/04/19 15:15:54 schwarze Exp $ */
+/*
+ * Copyright (c) 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2011, 2014 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 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.
+ *
+ * HTML formatter for mdoc(7) used by mandoc(1).
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "out.h"
+#include "html.h"
+#include "main.h"
+
+#define MDOC_ARGS const struct roff_meta *meta, \
+ struct roff_node *n, \
+ struct html *h
+
+#ifndef MIN
+#define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b))
+#endif
+
+struct mdoc_html_act {
+ int (*pre)(MDOC_ARGS);
+ void (*post)(MDOC_ARGS);
+};
+
+static void print_mdoc_head(const struct roff_meta *,
+ struct html *);
+static void print_mdoc_node(MDOC_ARGS);
+static void print_mdoc_nodelist(MDOC_ARGS);
+static void synopsis_pre(struct html *, struct roff_node *);
+
+static void mdoc_root_post(const struct roff_meta *,
+ struct html *);
+static int mdoc_root_pre(const struct roff_meta *,
+ struct html *);
+
+static void mdoc__x_post(MDOC_ARGS);
+static int mdoc__x_pre(MDOC_ARGS);
+static int mdoc_abort_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_cd_pre(MDOC_ARGS);
+static int mdoc_code_pre(MDOC_ARGS);
+static int mdoc_d1_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 void mdoc_eo_post(MDOC_ARGS);
+static int mdoc_eo_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_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_lk_pre(MDOC_ARGS);
+static int mdoc_mt_pre(MDOC_ARGS);
+static int mdoc_nd_pre(MDOC_ARGS);
+static int mdoc_nm_pre(MDOC_ARGS);
+static int mdoc_no_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_sh_pre(MDOC_ARGS);
+static int mdoc_skip_pre(MDOC_ARGS);
+static int mdoc_sm_pre(MDOC_ARGS);
+static int mdoc_ss_pre(MDOC_ARGS);
+static int mdoc_st_pre(MDOC_ARGS);
+static int mdoc_sx_pre(MDOC_ARGS);
+static int mdoc_sy_pre(MDOC_ARGS);
+static int mdoc_tg_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 mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = {
+ {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_ap_pre, NULL}, /* Ap */
+ {mdoc_ar_pre, NULL}, /* Ar */
+ {mdoc_cd_pre, NULL}, /* Cd */
+ {mdoc_code_pre, NULL}, /* Cm */
+ {mdoc_code_pre, NULL}, /* Dv */
+ {mdoc_code_pre, NULL}, /* Er */
+ {mdoc_code_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_code_pre, NULL}, /* Ic */
+ {mdoc_in_pre, NULL}, /* In */
+ {mdoc_code_pre, NULL}, /* Li */
+ {mdoc_nd_pre, NULL}, /* Nd */
+ {mdoc_nm_pre, NULL}, /* Nm */
+ {mdoc_quote_pre, mdoc_quote_post}, /* Op */
+ {mdoc_abort_pre, NULL}, /* Ot */
+ {mdoc_pa_pre, NULL}, /* Pa */
+ {mdoc_ex_pre, NULL}, /* Rv */
+ {mdoc_st_pre, 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 */
+ {mdoc_xx_pre, 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_xx_pre, NULL}, /* Bx */
+ {mdoc_skip_pre, 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_eo_pre, mdoc_eo_post}, /* Eo */
+ {mdoc_xx_pre, NULL}, /* Fx */
+ {mdoc_no_pre, NULL}, /* Ms */
+ {mdoc_no_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 */
+ {NULL, NULL}, /* Bt */
+ {NULL, NULL}, /* Hf */
+ {mdoc_em_pre, NULL}, /* Fr */
+ {NULL, NULL}, /* Ud */
+ {mdoc_lb_pre, NULL}, /* Lb */
+ {mdoc_abort_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__x_pre, mdoc__x_post}, /* %U */
+ {NULL, NULL}, /* Ta */
+ {mdoc_tg_pre, NULL}, /* Tg */
+};
+
+
+/*
+ * See the same function in mdoc_term.c for documentation.
+ */
+static void
+synopsis_pre(struct html *h, struct roff_node *n)
+{
+ struct roff_node *np;
+
+ if ((n->flags & NODE_SYNPRETTY) == 0 ||
+ (np = roff_node_prev(n)) == NULL)
+ return;
+
+ if (np->tok == n->tok &&
+ MDOC_Fo != n->tok &&
+ MDOC_Ft != n->tok &&
+ MDOC_Fn != n->tok) {
+ print_otag(h, TAG_BR, "");
+ return;
+ }
+
+ switch (np->tok) {
+ case MDOC_Fd:
+ case MDOC_Fn:
+ case MDOC_Fo:
+ case MDOC_In:
+ case MDOC_Vt:
+ break;
+ case MDOC_Ft:
+ if (n->tok != MDOC_Fn && n->tok != MDOC_Fo)
+ break;
+ /* FALLTHROUGH */
+ default:
+ print_otag(h, TAG_BR, "");
+ return;
+ }
+ html_close_paragraph(h);
+ print_otag(h, TAG_P, "c", "Pp");
+}
+
+void
+html_mdoc(void *arg, const struct roff_meta *mdoc)
+{
+ struct html *h;
+ struct roff_node *n;
+ struct tag *t;
+
+ h = (struct html *)arg;
+ n = mdoc->first->child;
+
+ if ((h->oflags & HTML_FRAGMENT) == 0) {
+ print_gen_decls(h);
+ print_otag(h, TAG_HTML, "");
+ if (n != NULL && n->type == ROFFT_COMMENT)
+ print_gen_comment(h, n);
+ t = print_otag(h, TAG_HEAD, "");
+ print_mdoc_head(mdoc, h);
+ print_tagq(h, t);
+ print_otag(h, TAG_BODY, "");
+ }
+
+ mdoc_root_pre(mdoc, h);
+ t = print_otag(h, TAG_DIV, "c", "manual-text");
+ print_mdoc_nodelist(mdoc, n, h);
+ print_tagq(h, t);
+ mdoc_root_post(mdoc, h);
+ print_tagq(h, NULL);
+}
+
+static void
+print_mdoc_head(const struct roff_meta *meta, struct html *h)
+{
+ char *cp;
+
+ print_gen_head(h);
+
+ if (meta->arch != NULL && meta->msec != NULL)
+ mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title,
+ meta->msec, meta->arch);
+ else if (meta->msec != NULL)
+ mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec);
+ else if (meta->arch != NULL)
+ mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch);
+ else
+ cp = mandoc_strdup(meta->title);
+
+ print_otag(h, TAG_TITLE, "");
+ print_text(h, cp);
+ free(cp);
+}
+
+static void
+print_mdoc_nodelist(MDOC_ARGS)
+{
+
+ while (n != NULL) {
+ print_mdoc_node(meta, n, h);
+ n = n->next;
+ }
+}
+
+static void
+print_mdoc_node(MDOC_ARGS)
+{
+ struct tag *t;
+ int child;
+
+ if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
+ return;
+
+ if (n->flags & NODE_NOFILL) {
+ html_fillmode(h, ROFF_nf);
+ if (n->flags & NODE_LINE)
+ print_endline(h);
+ } else
+ html_fillmode(h, ROFF_fi);
+
+ child = 1;
+ n->flags &= ~NODE_ENDED;
+ switch (n->type) {
+ case ROFFT_TEXT:
+ if (n->flags & NODE_LINE) {
+ switch (*n->string) {
+ case '\0':
+ h->col = 1;
+ print_endline(h);
+ return;
+ case ' ':
+ if ((h->flags & HTML_NONEWLINE) == 0 &&
+ (n->flags & NODE_NOFILL) == 0)
+ print_otag(h, TAG_BR, "");
+ break;
+ default:
+ break;
+ }
+ }
+ t = h->tag;
+ t->refcnt++;
+ if (n->flags & NODE_DELIMC)
+ h->flags |= HTML_NOSPACE;
+ if (n->flags & NODE_HREF)
+ print_tagged_text(h, n->string, n);
+ else
+ print_text(h, n->string);
+ if (n->flags & NODE_DELIMO)
+ h->flags |= HTML_NOSPACE;
+ break;
+ case ROFFT_EQN:
+ t = h->tag;
+ t->refcnt++;
+ print_eqn(h, n->eqn);
+ break;
+ case ROFFT_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 != NULL)
+ print_tblclose(h);
+ assert(h->tblt == NULL);
+ t = h->tag;
+ t->refcnt++;
+ if (n->tok < ROFF_MAX) {
+ roff_html_pre(h, n);
+ t->refcnt--;
+ print_stagq(h, t);
+ return;
+ }
+ assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
+ if (mdoc_html_acts[n->tok - MDOC_Dd].pre != NULL &&
+ (n->end == ENDBODY_NOT || n->child != NULL))
+ child = (*mdoc_html_acts[n->tok - MDOC_Dd].pre)(meta,
+ n, h);
+ break;
+ }
+
+ if (h->flags & HTML_KEEP && n->flags & NODE_LINE) {
+ h->flags &= ~HTML_KEEP;
+ h->flags |= HTML_PREKEEP;
+ }
+
+ if (child && n->child != NULL)
+ print_mdoc_nodelist(meta, n->child, h);
+
+ t->refcnt--;
+ print_stagq(h, t);
+
+ switch (n->type) {
+ case ROFFT_TEXT:
+ case ROFFT_EQN:
+ break;
+ default:
+ if (mdoc_html_acts[n->tok - MDOC_Dd].post == NULL ||
+ n->flags & NODE_ENDED)
+ break;
+ (*mdoc_html_acts[n->tok - MDOC_Dd].post)(meta, n, h);
+ if (n->end != ENDBODY_NOT)
+ n->body->flags |= NODE_ENDED;
+ break;
+ }
+}
+
+static void
+mdoc_root_post(const struct roff_meta *meta, struct html *h)
+{
+ struct tag *t, *tt;
+
+ t = print_otag(h, TAG_TABLE, "c", "foot");
+ tt = print_otag(h, TAG_TR, "");
+
+ print_otag(h, TAG_TD, "c", "foot-date");
+ print_text(h, meta->date);
+ print_stagq(h, tt);
+
+ print_otag(h, TAG_TD, "c", "foot-os");
+ print_text(h, meta->os);
+ print_tagq(h, t);
+}
+
+static int
+mdoc_root_pre(const struct roff_meta *meta, struct html *h)
+{
+ 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);
+
+ t = print_otag(h, TAG_TABLE, "c", "head");
+ tt = print_otag(h, TAG_TR, "");
+
+ print_otag(h, TAG_TD, "c", "head-ltitle");
+ print_text(h, title);
+ print_stagq(h, tt);
+
+ print_otag(h, TAG_TD, "c", "head-vol");
+ print_text(h, volume);
+ print_stagq(h, tt);
+
+ print_otag(h, TAG_TD, "c", "head-rtitle");
+ print_text(h, title);
+ print_tagq(h, t);
+
+ free(title);
+ free(volume);
+ return 1;
+}
+
+static int
+mdoc_code_pre(MDOC_ARGS)
+{
+ print_otag_id(h, TAG_CODE, roff_name[n->tok], n);
+ return 1;
+}
+
+static int
+mdoc_sh_pre(MDOC_ARGS)
+{
+ struct roff_node *sn, *subn;
+ struct tag *t, *tsec, *tsub;
+ char *id;
+ int sc;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ if ((h->oflags & HTML_TOC) == 0 ||
+ h->flags & HTML_TOCDONE ||
+ n->sec <= SEC_SYNOPSIS) {
+ print_otag(h, TAG_SECTION, "c", "Sh");
+ break;
+ }
+ h->flags |= HTML_TOCDONE;
+ sc = 0;
+ for (sn = n->next; sn != NULL; sn = sn->next)
+ if (sn->sec == SEC_CUSTOM)
+ if (++sc == 2)
+ break;
+ if (sc < 2)
+ break;
+ t = print_otag(h, TAG_H1, "c", "Sh");
+ print_text(h, "TABLE OF CONTENTS");
+ print_tagq(h, t);
+ t = print_otag(h, TAG_UL, "c", "Bl-compact");
+ for (sn = n; sn != NULL; sn = sn->next) {
+ tsec = print_otag(h, TAG_LI, "");
+ id = html_make_id(sn->head, 0);
+ tsub = print_otag(h, TAG_A, "hR", id);
+ free(id);
+ print_mdoc_nodelist(meta, sn->head->child, h);
+ print_tagq(h, tsub);
+ tsub = NULL;
+ for (subn = sn->body->child; subn != NULL;
+ subn = subn->next) {
+ if (subn->tok != MDOC_Ss)
+ continue;
+ id = html_make_id(subn->head, 0);
+ if (id == NULL)
+ continue;
+ if (tsub == NULL)
+ print_otag(h, TAG_UL,
+ "c", "Bl-compact");
+ tsub = print_otag(h, TAG_LI, "");
+ print_otag(h, TAG_A, "hR", id);
+ free(id);
+ print_mdoc_nodelist(meta,
+ subn->head->child, h);
+ print_tagq(h, tsub);
+ }
+ print_tagq(h, tsec);
+ }
+ print_tagq(h, t);
+ print_otag(h, TAG_SECTION, "c", "Sh");
+ break;
+ case ROFFT_HEAD:
+ print_otag_id(h, TAG_H1, "Sh", n);
+ break;
+ case ROFFT_BODY:
+ if (n->sec == SEC_AUTHORS)
+ h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static int
+mdoc_ss_pre(MDOC_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ print_otag(h, TAG_SECTION, "c", "Ss");
+ break;
+ case ROFFT_HEAD:
+ print_otag_id(h, TAG_H2, "Ss", n);
+ break;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static int
+mdoc_fl_pre(MDOC_ARGS)
+{
+ struct roff_node *nn;
+
+ print_otag_id(h, TAG_CODE, "Fl", n);
+ print_text(h, "\\-");
+ if (n->child != NULL ||
+ ((nn = roff_node_next(n)) != NULL &&
+ nn->type != ROFFT_TEXT &&
+ (nn->flags & NODE_LINE) == 0))
+ h->flags |= HTML_NOSPACE;
+
+ return 1;
+}
+
+static int
+mdoc_nd_pre(MDOC_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+ print_text(h, "\\(em");
+ print_otag(h, TAG_SPAN, "c", "Nd");
+ return 1;
+}
+
+static int
+mdoc_nm_pre(MDOC_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ break;
+ case ROFFT_HEAD:
+ print_otag(h, TAG_TD, "");
+ /* FALLTHROUGH */
+ case ROFFT_ELEM:
+ print_otag(h, TAG_CODE, "c", "Nm");
+ return 1;
+ case ROFFT_BODY:
+ print_otag(h, TAG_TD, "");
+ return 1;
+ default:
+ abort();
+ }
+ html_close_paragraph(h);
+ synopsis_pre(h, n);
+ print_otag(h, TAG_TABLE, "c", "Nm");
+ print_otag(h, TAG_TR, "");
+ return 1;
+}
+
+static int
+mdoc_xr_pre(MDOC_ARGS)
+{
+ if (NULL == n->child)
+ return 0;
+
+ if (h->base_man1)
+ print_otag(h, TAG_A, "chM", "Xr",
+ n->child->string, n->child->next == NULL ?
+ NULL : n->child->next->string);
+ else
+ print_otag(h, TAG_A, "c", "Xr");
+
+ 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_tg_pre(MDOC_ARGS)
+{
+ char *id;
+
+ if ((id = html_make_id(n, 1)) != NULL) {
+ print_tagq(h, print_otag(h, TAG_MARK, "i", id));
+ free(id);
+ }
+ return 0;
+}
+
+static int
+mdoc_ns_pre(MDOC_ARGS)
+{
+
+ if ( ! (NODE_LINE & n->flags))
+ h->flags |= HTML_NOSPACE;
+ return 1;
+}
+
+static int
+mdoc_ar_pre(MDOC_ARGS)
+{
+ print_otag(h, TAG_VAR, "c", "Ar");
+ return 1;
+}
+
+static int
+mdoc_xx_pre(MDOC_ARGS)
+{
+ print_otag(h, TAG_SPAN, "c", "Ux");
+ return 1;
+}
+
+static int
+mdoc_it_pre(MDOC_ARGS)
+{
+ const struct roff_node *bl;
+ enum mdoc_list type;
+
+ bl = n->parent;
+ while (bl->tok != MDOC_Bl)
+ bl = bl->parent;
+ type = bl->norm->Bl.type;
+
+ switch (type) {
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_hyphen:
+ case LIST_item:
+ case LIST_enum:
+ switch (n->type) {
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ print_otag_id(h, TAG_LI, NULL, n);
+ break;
+ default:
+ break;
+ }
+ break;
+ case LIST_diag:
+ case LIST_hang:
+ case LIST_inset:
+ case LIST_ohang:
+ switch (n->type) {
+ case ROFFT_HEAD:
+ print_otag_id(h, TAG_DT, NULL, n);
+ break;
+ case ROFFT_BODY:
+ print_otag(h, TAG_DD, "");
+ break;
+ default:
+ break;
+ }
+ break;
+ case LIST_tag:
+ switch (n->type) {
+ case ROFFT_HEAD:
+ print_otag_id(h, TAG_DT, NULL, n);
+ break;
+ case ROFFT_BODY:
+ if (n->child == NULL) {
+ print_otag(h, TAG_DD, "s", "width", "auto");
+ print_text(h, "\\ ");
+ } else
+ print_otag(h, TAG_DD, "");
+ break;
+ default:
+ break;
+ }
+ break;
+ case LIST_column:
+ switch (n->type) {
+ case ROFFT_HEAD:
+ break;
+ case ROFFT_BODY:
+ print_otag(h, TAG_TD, "");
+ break;
+ default:
+ print_otag_id(h, TAG_TR, NULL, n);
+ }
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int
+mdoc_bl_pre(MDOC_ARGS)
+{
+ char cattr[32];
+ struct mdoc_bl *bl;
+ enum htmltag elemtype;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ break;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ return 1;
+ default:
+ abort();
+ }
+
+ bl = &n->norm->Bl;
+ switch (bl->type) {
+ case LIST_bullet:
+ elemtype = TAG_UL;
+ (void)strlcpy(cattr, "Bl-bullet", sizeof(cattr));
+ break;
+ case LIST_dash:
+ case LIST_hyphen:
+ elemtype = TAG_UL;
+ (void)strlcpy(cattr, "Bl-dash", sizeof(cattr));
+ break;
+ case LIST_item:
+ elemtype = TAG_UL;
+ (void)strlcpy(cattr, "Bl-item", sizeof(cattr));
+ break;
+ case LIST_enum:
+ elemtype = TAG_OL;
+ (void)strlcpy(cattr, "Bl-enum", sizeof(cattr));
+ break;
+ case LIST_diag:
+ elemtype = TAG_DL;
+ (void)strlcpy(cattr, "Bl-diag", sizeof(cattr));
+ break;
+ case LIST_hang:
+ elemtype = TAG_DL;
+ (void)strlcpy(cattr, "Bl-hang", sizeof(cattr));
+ break;
+ case LIST_inset:
+ elemtype = TAG_DL;
+ (void)strlcpy(cattr, "Bl-inset", sizeof(cattr));
+ break;
+ case LIST_ohang:
+ elemtype = TAG_DL;
+ (void)strlcpy(cattr, "Bl-ohang", sizeof(cattr));
+ break;
+ case LIST_tag:
+ if (bl->offs)
+ print_otag(h, TAG_DIV, "c", "Bd-indent");
+ print_otag_id(h, TAG_DL,
+ bl->comp ? "Bl-tag Bl-compact" : "Bl-tag", n->body);
+ return 1;
+ case LIST_column:
+ elemtype = TAG_TABLE;
+ (void)strlcpy(cattr, "Bl-column", sizeof(cattr));
+ break;
+ default:
+ abort();
+ }
+ if (bl->offs != NULL)
+ (void)strlcat(cattr, " Bd-indent", sizeof(cattr));
+ if (bl->comp)
+ (void)strlcat(cattr, " Bl-compact", sizeof(cattr));
+ print_otag_id(h, elemtype, cattr, n->body);
+ return 1;
+}
+
+static int
+mdoc_ex_pre(MDOC_ARGS)
+{
+ if (roff_node_prev(n) != NULL)
+ print_otag(h, TAG_BR, "");
+ return 1;
+}
+
+static int
+mdoc_st_pre(MDOC_ARGS)
+{
+ print_otag(h, TAG_SPAN, "c", "St");
+ return 1;
+}
+
+static int
+mdoc_em_pre(MDOC_ARGS)
+{
+ print_otag_id(h, TAG_I, "Em", n);
+ return 1;
+}
+
+static int
+mdoc_d1_pre(MDOC_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+ print_otag_id(h, TAG_DIV, "Bd Bd-indent", n);
+ if (n->tok == MDOC_Dl)
+ print_otag(h, TAG_CODE, "c", "Li");
+ return 1;
+}
+
+static int
+mdoc_sx_pre(MDOC_ARGS)
+{
+ char *id;
+
+ id = html_make_id(n, 0);
+ print_otag(h, TAG_A, "chR", "Sx", id);
+ free(id);
+ return 1;
+}
+
+static int
+mdoc_bd_pre(MDOC_ARGS)
+{
+ char buf[16];
+ struct roff_node *nn;
+ int comp;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+
+ /* Handle preceding whitespace. */
+
+ comp = n->norm->Bd.comp;
+ for (nn = n; nn != NULL && comp == 0; nn = nn->parent) {
+ if (nn->type != ROFFT_BLOCK)
+ continue;
+ if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
+ comp = 1;
+ if (roff_node_prev(nn) != NULL)
+ break;
+ }
+ (void)strlcpy(buf, "Bd", sizeof(buf));
+ if (comp == 0)
+ (void)strlcat(buf, " Pp", sizeof(buf));
+
+ /* Handle the -offset argument. */
+
+ if (n->norm->Bd.offs != NULL &&
+ strcmp(n->norm->Bd.offs, "left") != 0)
+ (void)strlcat(buf, " Bd-indent", sizeof(buf));
+
+ print_otag_id(h, TAG_DIV, buf, n);
+ return 1;
+}
+
+static int
+mdoc_pa_pre(MDOC_ARGS)
+{
+ print_otag(h, TAG_SPAN, "c", "Pa");
+ return 1;
+}
+
+static int
+mdoc_ad_pre(MDOC_ARGS)
+{
+ print_otag(h, TAG_SPAN, "c", "Ad");
+ return 1;
+}
+
+static int
+mdoc_an_pre(MDOC_ARGS)
+{
+ if (n->norm->An.auth == AUTH_split) {
+ h->flags &= ~HTML_NOSPLIT;
+ h->flags |= HTML_SPLIT;
+ return 0;
+ }
+ if (n->norm->An.auth == AUTH_nosplit) {
+ h->flags &= ~HTML_SPLIT;
+ h->flags |= HTML_NOSPLIT;
+ return 0;
+ }
+
+ if (h->flags & HTML_SPLIT)
+ print_otag(h, TAG_BR, "");
+
+ if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
+ h->flags |= HTML_SPLIT;
+
+ print_otag(h, TAG_SPAN, "c", "An");
+ return 1;
+}
+
+static int
+mdoc_cd_pre(MDOC_ARGS)
+{
+ synopsis_pre(h, n);
+ print_otag(h, TAG_CODE, "c", "Cd");
+ return 1;
+}
+
+static int
+mdoc_fa_pre(MDOC_ARGS)
+{
+ const struct roff_node *nn;
+ struct tag *t;
+
+ if (n->parent->tok != MDOC_Fo) {
+ print_otag(h, TAG_VAR, "c", "Fa");
+ return 1;
+ }
+ for (nn = n->child; nn != NULL; nn = nn->next) {
+ t = print_otag(h, TAG_VAR, "c", "Fa");
+ print_text(h, nn->string);
+ print_tagq(h, t);
+ if (nn->next != NULL) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ",");
+ }
+ }
+ if (n->child != NULL &&
+ (nn = roff_node_next(n)) != NULL &&
+ nn->tok == MDOC_Fa) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, ",");
+ }
+ return 0;
+}
+
+static int
+mdoc_fd_pre(MDOC_ARGS)
+{
+ struct tag *t;
+ char *buf, *cp;
+
+ synopsis_pre(h, n);
+
+ if (NULL == (n = n->child))
+ return 0;
+
+ assert(n->type == ROFFT_TEXT);
+
+ if (strcmp(n->string, "#include")) {
+ print_otag(h, TAG_CODE, "c", "Fd");
+ return 1;
+ }
+
+ print_otag(h, TAG_CODE, "c", "In");
+ print_text(h, n->string);
+
+ if (NULL != (n = n->next)) {
+ assert(n->type == ROFFT_TEXT);
+
+ if (h->base_includes) {
+ cp = n->string;
+ if (*cp == '<' || *cp == '"')
+ cp++;
+ buf = mandoc_strdup(cp);
+ cp = strchr(buf, '\0') - 1;
+ if (cp >= buf && (*cp == '>' || *cp == '"'))
+ *cp = '\0';
+ t = print_otag(h, TAG_A, "chI", "In", buf);
+ free(buf);
+ } else
+ t = print_otag(h, TAG_A, "c", "In");
+
+ print_text(h, n->string);
+ print_tagq(h, t);
+
+ n = n->next;
+ }
+
+ for ( ; n; n = n->next) {
+ assert(n->type == ROFFT_TEXT);
+ print_text(h, n->string);
+ }
+
+ return 0;
+}
+
+static int
+mdoc_vt_pre(MDOC_ARGS)
+{
+ if (n->type == ROFFT_BLOCK) {
+ synopsis_pre(h, n);
+ return 1;
+ } else if (n->type == ROFFT_ELEM) {
+ synopsis_pre(h, n);
+ } else if (n->type == ROFFT_HEAD)
+ return 0;
+
+ print_otag(h, TAG_VAR, "c", "Vt");
+ return 1;
+}
+
+static int
+mdoc_ft_pre(MDOC_ARGS)
+{
+ synopsis_pre(h, n);
+ print_otag(h, TAG_VAR, "c", "Ft");
+ return 1;
+}
+
+static int
+mdoc_fn_pre(MDOC_ARGS)
+{
+ struct tag *t;
+ char nbuf[BUFSIZ];
+ const char *sp, *ep;
+ int sz, pretty;
+
+ pretty = NODE_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) {
+ t = print_otag(h, TAG_VAR, "c", "Ft");
+
+ 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);
+ }
+
+ t = print_otag_id(h, TAG_CODE, "Fn", n);
+
+ if (sp)
+ print_text(h, sp);
+
+ print_tagq(h, t);
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "(");
+ h->flags |= HTML_NOSPACE;
+
+ for (n = n->child->next; n; n = n->next) {
+ if (NODE_SYNPRETTY & n->flags)
+ t = print_otag(h, TAG_VAR, "cs", "Fa",
+ "white-space", "nowrap");
+ else
+ t = print_otag(h, TAG_VAR, "c", "Fa");
+ 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)
+{
+ char *id;
+
+ if (n->flags & NODE_NOFILL) {
+ print_endline(h);
+ if (n->flags & NODE_ID)
+ mdoc_tg_pre(meta, n, h);
+ else {
+ h->col = 1;
+ print_endline(h);
+ }
+ } else {
+ html_close_paragraph(h);
+ id = n->flags & NODE_ID ? html_make_id(n, 1) : NULL;
+ print_otag(h, TAG_P, "ci", "Pp", id);
+ free(id);
+ }
+ return 0;
+}
+
+static int
+mdoc_lk_pre(MDOC_ARGS)
+{
+ const struct roff_node *link, *descr, *punct;
+ struct tag *t;
+
+ if ((link = n->child) == NULL)
+ return 0;
+
+ /* Find beginning of trailing punctuation. */
+ punct = n->last;
+ while (punct != link && punct->flags & NODE_DELIMC)
+ punct = punct->prev;
+ punct = punct->next;
+
+ /* Link target and link text. */
+ descr = link->next;
+ if (descr == punct)
+ descr = link; /* no text */
+ t = print_otag(h, TAG_A, "ch", "Lk", link->string);
+ do {
+ if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
+ h->flags |= HTML_NOSPACE;
+ print_text(h, descr->string);
+ descr = descr->next;
+ } while (descr != punct);
+ print_tagq(h, t);
+
+ /* Trailing punctuation. */
+ while (punct != NULL) {
+ h->flags |= HTML_NOSPACE;
+ print_text(h, punct->string);
+ punct = punct->next;
+ }
+ return 0;
+}
+
+static int
+mdoc_mt_pre(MDOC_ARGS)
+{
+ struct tag *t;
+ char *cp;
+
+ for (n = n->child; n; n = n->next) {
+ assert(n->type == ROFFT_TEXT);
+ mandoc_asprintf(&cp, "mailto:%s", n->string);
+ t = print_otag(h, TAG_A, "ch", "Mt", cp);
+ print_text(h, n->string);
+ print_tagq(h, t);
+ free(cp);
+ }
+ return 0;
+}
+
+static int
+mdoc_fo_pre(MDOC_ARGS)
+{
+ struct tag *t;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ synopsis_pre(h, n);
+ return 1;
+ case ROFFT_HEAD:
+ if (n->child != NULL) {
+ t = print_otag_id(h, TAG_CODE, "Fn", n);
+ print_text(h, n->child->string);
+ print_tagq(h, t);
+ }
+ return 0;
+ case ROFFT_BODY:
+ h->flags |= HTML_NOSPACE;
+ print_text(h, "(");
+ h->flags |= HTML_NOSPACE;
+ return 1;
+ default:
+ abort();
+ }
+}
+
+static void
+mdoc_fo_post(MDOC_ARGS)
+{
+ if (n->type != ROFFT_BODY)
+ 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;
+
+ synopsis_pre(h, n);
+ print_otag(h, TAG_CODE, "c", "In");
+
+ /*
+ * 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 (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
+ print_text(h, "#include");
+
+ print_text(h, "<");
+ h->flags |= HTML_NOSPACE;
+
+ if (NULL != (n = n->child)) {
+ assert(n->type == ROFFT_TEXT);
+
+ if (h->base_includes)
+ t = print_otag(h, TAG_A, "chI", "In", n->string);
+ else
+ t = print_otag(h, TAG_A, "c", "In");
+ 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(n->type == ROFFT_TEXT);
+ print_text(h, n->string);
+ }
+ return 0;
+}
+
+static int
+mdoc_va_pre(MDOC_ARGS)
+{
+ print_otag(h, TAG_VAR, "c", "Va");
+ 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)
+{
+ const char *cattr;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ html_close_paragraph(h);
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ break;
+ default:
+ abort();
+ }
+
+ if (FONT_Em == n->norm->Bf.font)
+ cattr = "Bf Em";
+ else if (FONT_Sy == n->norm->Bf.font)
+ cattr = "Bf Sy";
+ else if (FONT_Li == n->norm->Bf.font)
+ cattr = "Bf Li";
+ else
+ cattr = "Bf No";
+
+ /* Cannot use TAG_SPAN because it may contain blocks. */
+ print_otag(h, TAG_DIV, "c", cattr);
+ return 1;
+}
+
+static int
+mdoc_igndelim_pre(MDOC_ARGS)
+{
+ h->flags |= HTML_IGNDELIM;
+ return 1;
+}
+
+static void
+mdoc_pf_post(MDOC_ARGS)
+{
+ if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
+ h->flags |= HTML_NOSPACE;
+}
+
+static int
+mdoc_rs_pre(MDOC_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ if (n->sec == SEC_SEE_ALSO)
+ html_close_paragraph(h);
+ break;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ if (n->sec == SEC_SEE_ALSO)
+ print_otag(h, TAG_P, "c", "Pp");
+ print_otag(h, TAG_CITE, "c", "Rs");
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static int
+mdoc_no_pre(MDOC_ARGS)
+{
+ print_otag_id(h, TAG_SPAN, roff_name[n->tok], n);
+ return 1;
+}
+
+static int
+mdoc_sy_pre(MDOC_ARGS)
+{
+ print_otag_id(h, TAG_B, "Sy", n);
+ return 1;
+}
+
+static int
+mdoc_lb_pre(MDOC_ARGS)
+{
+ if (n->sec == SEC_LIBRARY &&
+ n->flags & NODE_LINE &&
+ roff_node_prev(n) != NULL)
+ print_otag(h, TAG_BR, "");
+
+ print_otag(h, TAG_SPAN, "c", "Lb");
+ return 1;
+}
+
+static int
+mdoc__x_pre(MDOC_ARGS)
+{
+ struct roff_node *nn;
+ const char *cattr;
+ enum htmltag t;
+
+ t = TAG_SPAN;
+
+ switch (n->tok) {
+ case MDOC__A:
+ cattr = "RsA";
+ if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A &&
+ ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A))
+ print_text(h, "and");
+ break;
+ case MDOC__B:
+ t = TAG_I;
+ cattr = "RsB";
+ break;
+ case MDOC__C:
+ cattr = "RsC";
+ break;
+ case MDOC__D:
+ cattr = "RsD";
+ break;
+ case MDOC__I:
+ t = TAG_I;
+ cattr = "RsI";
+ break;
+ case MDOC__J:
+ t = TAG_I;
+ cattr = "RsJ";
+ break;
+ case MDOC__N:
+ cattr = "RsN";
+ break;
+ case MDOC__O:
+ cattr = "RsO";
+ break;
+ case MDOC__P:
+ cattr = "RsP";
+ break;
+ case MDOC__Q:
+ cattr = "RsQ";
+ break;
+ case MDOC__R:
+ cattr = "RsR";
+ break;
+ case MDOC__T:
+ cattr = "RsT";
+ break;
+ case MDOC__U:
+ print_otag(h, TAG_A, "ch", "RsU", n->child->string);
+ return 1;
+ case MDOC__V:
+ cattr = "RsV";
+ break;
+ default:
+ abort();
+ }
+
+ print_otag(h, t, "c", cattr);
+ return 1;
+}
+
+static void
+mdoc__x_post(MDOC_ARGS)
+{
+ struct roff_node *nn;
+
+ if (n->tok == MDOC__A &&
+ (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A &&
+ ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) &&
+ ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A))
+ return;
+
+ /* TODO: %U */
+
+ if (n->parent == NULL || n->parent->tok != MDOC_Rs)
+ return;
+
+ h->flags |= HTML_NOSPACE;
+ print_text(h, roff_node_next(n) ? "," : ".");
+}
+
+static int
+mdoc_bk_pre(MDOC_ARGS)
+{
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ break;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ if (n->parent->args != NULL || n->prev->child == NULL)
+ h->flags |= HTML_PREKEEP;
+ break;
+ default:
+ abort();
+ }
+
+ return 1;
+}
+
+static void
+mdoc_bk_post(MDOC_ARGS)
+{
+
+ if (n->type == ROFFT_BODY)
+ h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
+}
+
+static int
+mdoc_quote_pre(MDOC_ARGS)
+{
+ if (n->type != ROFFT_BODY)
+ return 1;
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ case MDOC_Aq:
+ print_text(h, n->child != NULL && n->child->next == NULL &&
+ n->child->tok == MDOC_Mt ? "<" : "\\(la");
+ break;
+ case MDOC_Bro:
+ case MDOC_Brq:
+ print_text(h, "\\(lC");
+ break;
+ case MDOC_Bo:
+ case MDOC_Bq:
+ print_text(h, "\\(lB");
+ break;
+ case MDOC_Oo:
+ case MDOC_Op:
+ print_text(h, "\\(lB");
+ /*
+ * Give up on semantic markup for now.
+ * We cannot use TAG_SPAN because .Oo may contain blocks.
+ * We cannot use TAG_DIV because we might be in a
+ * phrasing context (like .Dl or .Pp); we cannot
+ * close out a .Pp at this point either because
+ * that would break the line.
+ */
+ /* XXX print_otag(h, TAG_???, "c", "Op"); */
+ 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_Do:
+ case MDOC_Dq:
+ print_text(h, "\\(lq");
+ break;
+ case MDOC_Qo:
+ case MDOC_Qq:
+ print_text(h, "\"");
+ break;
+ case MDOC_Po:
+ case MDOC_Pq:
+ print_text(h, "(");
+ break;
+ case MDOC_Ql:
+ print_text(h, "\\(oq");
+ h->flags |= HTML_NOSPACE;
+ print_otag(h, TAG_CODE, "c", "Li");
+ break;
+ case MDOC_So:
+ case MDOC_Sq:
+ print_text(h, "\\(oq");
+ break;
+ default:
+ abort();
+ }
+
+ h->flags |= HTML_NOSPACE;
+ return 1;
+}
+
+static void
+mdoc_quote_post(MDOC_ARGS)
+{
+
+ if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
+ return;
+
+ h->flags |= HTML_NOSPACE;
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ case MDOC_Aq:
+ print_text(h, n->child != NULL && n->child->next == NULL &&
+ n->child->tok == MDOC_Mt ? ">" : "\\(ra");
+ break;
+ case MDOC_Bro:
+ case MDOC_Brq:
+ print_text(h, "\\(rC");
+ break;
+ case MDOC_Oo:
+ case MDOC_Op:
+ case MDOC_Bo:
+ case MDOC_Bq:
+ print_text(h, "\\(rB");
+ break;
+ case MDOC_En:
+ if (n->norm->Es == NULL ||
+ n->norm->Es->child == NULL ||
+ n->norm->Es->child->next == NULL)
+ h->flags &= ~HTML_NOSPACE;
+ else
+ print_text(h, n->norm->Es->child->next->string);
+ break;
+ case MDOC_Do:
+ case MDOC_Dq:
+ print_text(h, "\\(rq");
+ break;
+ case MDOC_Qo:
+ case MDOC_Qq:
+ print_text(h, "\"");
+ break;
+ case MDOC_Po:
+ case MDOC_Pq:
+ print_text(h, ")");
+ break;
+ case MDOC_Ql:
+ case MDOC_So:
+ case MDOC_Sq:
+ print_text(h, "\\(cq");
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+mdoc_eo_pre(MDOC_ARGS)
+{
+
+ if (n->type != ROFFT_BODY)
+ return 1;
+
+ if (n->end == ENDBODY_NOT &&
+ n->parent->head->child == NULL &&
+ n->child != NULL &&
+ n->child->end != ENDBODY_NOT)
+ print_text(h, "\\&");
+ else if (n->end != ENDBODY_NOT ? n->child != NULL :
+ n->parent->head->child != NULL && (n->child != NULL ||
+ (n->parent->tail != NULL && n->parent->tail->child != NULL)))
+ h->flags |= HTML_NOSPACE;
+ return 1;
+}
+
+static void
+mdoc_eo_post(MDOC_ARGS)
+{
+ int body, tail;
+
+ if (n->type != ROFFT_BODY)
+ return;
+
+ if (n->end != ENDBODY_NOT) {
+ h->flags &= ~HTML_NOSPACE;
+ return;
+ }
+
+ body = n->child != NULL || n->parent->head->child != NULL;
+ tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
+
+ if (body && tail)
+ h->flags |= HTML_NOSPACE;
+ else if ( ! tail)
+ h->flags &= ~HTML_NOSPACE;
+}
+
+static int
+mdoc_abort_pre(MDOC_ARGS)
+{
+ abort();
+}
diff --git a/usr.bin/mandoc/mdoc_macro.c b/usr.bin/mandoc/mdoc_macro.c
new file mode 100644
index 0000000..a8aa3c9
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_macro.c
@@ -0,0 +1,1598 @@
+/* $OpenBSD: mdoc_macro.c,v 1.191 2020/01/19 17:59:01 schwarze Exp $ */
+/*
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2012-2020 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libmdoc.h"
+
+static void blk_full(MACRO_PROT_ARGS);
+static void blk_exp_close(MACRO_PROT_ARGS);
+static void blk_part_exp(MACRO_PROT_ARGS);
+static void blk_part_imp(MACRO_PROT_ARGS);
+static void ctx_synopsis(MACRO_PROT_ARGS);
+static void in_line_eoln(MACRO_PROT_ARGS);
+static void in_line_argn(MACRO_PROT_ARGS);
+static void in_line(MACRO_PROT_ARGS);
+static void phrase_ta(MACRO_PROT_ARGS);
+
+static void append_delims(struct roff_man *, int, int *, char *);
+static void dword(struct roff_man *, int, int, const char *,
+ enum mdelim, int);
+static int find_pending(struct roff_man *, enum roff_tok,
+ int, int, struct roff_node *);
+static int lookup(struct roff_man *, int, int, int, const char *);
+static int macro_or_word(MACRO_PROT_ARGS, char *, int);
+static void break_intermediate(struct roff_node *,
+ struct roff_node *);
+static int parse_rest(struct roff_man *, enum roff_tok,
+ int, int *, char *);
+static enum roff_tok rew_alt(enum roff_tok);
+static void rew_elem(struct roff_man *, enum roff_tok);
+static void rew_last(struct roff_man *, const struct roff_node *);
+static void rew_pending(struct roff_man *,
+ const struct roff_node *);
+
+static const struct mdoc_macro mdoc_macros[MDOC_MAX - MDOC_Dd] = {
+ { in_line_eoln, MDOC_PROLOGUE | MDOC_JOIN }, /* 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_argn, MDOC_CALLABLE | MDOC_PARSED |
+ MDOC_IGNDELIM | MDOC_JOIN }, /* Ap */
+ { 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, MDOC_CALLABLE | MDOC_PARSED | 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_argn, 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 }, /* %U */
+ { phrase_ta, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ta */
+ { in_line_eoln, 0 }, /* Tg */
+};
+
+
+const struct mdoc_macro *
+mdoc_macro(enum roff_tok tok)
+{
+ assert(tok >= MDOC_Dd && tok < MDOC_MAX);
+ return mdoc_macros + (tok - MDOC_Dd);
+}
+
+/*
+ * 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.
+ */
+void
+mdoc_endparse(struct roff_man *mdoc)
+{
+ struct roff_node *n;
+
+ /* Scan for open explicit scopes. */
+
+ n = mdoc->last->flags & NODE_VALID ?
+ mdoc->last->parent : mdoc->last;
+
+ for ( ; n; n = n->parent)
+ if (n->type == ROFFT_BLOCK &&
+ mdoc_macro(n->tok)->flags & MDOC_EXPLICIT)
+ mandoc_msg(MANDOCERR_BLK_NOEND,
+ n->line, n->pos, "%s", roff_name[n->tok]);
+
+ /* Rewind to the first. */
+
+ rew_last(mdoc, mdoc->meta.first);
+}
+
+/*
+ * Look up the macro at *p called by "from",
+ * or as a line macro if from == TOKEN_NONE.
+ */
+static int
+lookup(struct roff_man *mdoc, int from, int line, int ppos, const char *p)
+{
+ enum roff_tok res;
+
+ if (mdoc->flags & MDOC_PHRASEQF) {
+ mdoc->flags &= ~MDOC_PHRASEQF;
+ return TOKEN_NONE;
+ }
+ if (from == TOKEN_NONE || mdoc_macro(from)->flags & MDOC_PARSED) {
+ res = roffhash_find(mdoc->mdocmac, p, 0);
+ if (res != TOKEN_NONE) {
+ if (mdoc_macro(res)->flags & MDOC_CALLABLE)
+ return res;
+ mandoc_msg(MANDOCERR_MACRO_CALL, line, ppos, "%s", p);
+ }
+ }
+ return TOKEN_NONE;
+}
+
+/*
+ * Rewind up to and including a specific node.
+ */
+static void
+rew_last(struct roff_man *mdoc, const struct roff_node *to)
+{
+
+ if (to->flags & NODE_VALID)
+ return;
+
+ while (mdoc->last != to) {
+ mdoc_state(mdoc, mdoc->last);
+ mdoc->last->flags |= NODE_VALID | NODE_ENDED;
+ mdoc->last = mdoc->last->parent;
+ }
+ mdoc_state(mdoc, mdoc->last);
+ mdoc->last->flags |= NODE_VALID | NODE_ENDED;
+ mdoc->next = ROFF_NEXT_SIBLING;
+}
+
+/*
+ * Rewind up to a specific block, including all blocks that broke it.
+ */
+static void
+rew_pending(struct roff_man *mdoc, const struct roff_node *n)
+{
+
+ for (;;) {
+ rew_last(mdoc, n);
+
+ if (mdoc->last == n) {
+ switch (n->type) {
+ case ROFFT_HEAD:
+ roff_body_alloc(mdoc, n->line, n->pos,
+ n->tok);
+ if (n->tok == MDOC_Ss)
+ mdoc->flags &= ~ROFF_NONOFILL;
+ break;
+ case ROFFT_BLOCK:
+ break;
+ default:
+ return;
+ }
+ if ( ! (n->flags & NODE_BROKEN))
+ return;
+ } else
+ n = mdoc->last;
+
+ for (;;) {
+ if ((n = n->parent) == NULL)
+ return;
+
+ if (n->type == ROFFT_BLOCK ||
+ n->type == ROFFT_HEAD) {
+ if (n->flags & NODE_ENDED)
+ break;
+ else
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * For a block closing macro, return the corresponding opening one.
+ * Otherwise, return the macro itself.
+ */
+static enum roff_tok
+rew_alt(enum roff_tok 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;
+ }
+}
+
+static void
+rew_elem(struct roff_man *mdoc, enum roff_tok tok)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ if (n->type != ROFFT_ELEM)
+ n = n->parent;
+ assert(n->type == ROFFT_ELEM);
+ assert(tok == n->tok);
+ rew_last(mdoc, n);
+}
+
+static void
+break_intermediate(struct roff_node *n, struct roff_node *breaker)
+{
+ if (n != breaker &&
+ n->type != ROFFT_BLOCK && n->type != ROFFT_HEAD &&
+ (n->type != ROFFT_BODY || n->end != ENDBODY_NOT))
+ n = n->parent;
+ while (n != breaker) {
+ if ( ! (n->flags & NODE_VALID))
+ n->flags |= NODE_BROKEN;
+ n = n->parent;
+ }
+}
+
+/*
+ * If there is an open sub-block of the target requiring
+ * explicit close-out, postpone closing out the target until
+ * the rew_pending() call closing out the sub-block.
+ */
+static int
+find_pending(struct roff_man *mdoc, enum roff_tok tok, int line, int ppos,
+ struct roff_node *target)
+{
+ struct roff_node *n;
+ int irc;
+
+ if (target->flags & NODE_VALID)
+ return 0;
+
+ irc = 0;
+ for (n = mdoc->last; n != NULL && n != target; n = n->parent) {
+ if (n->flags & NODE_ENDED)
+ continue;
+ if (n->type == ROFFT_BLOCK &&
+ mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) {
+ irc = 1;
+ break_intermediate(mdoc->last, target);
+ if (target->type == ROFFT_HEAD)
+ target->flags |= NODE_ENDED;
+ else if ( ! (target->flags & NODE_ENDED)) {
+ mandoc_msg(MANDOCERR_BLK_NEST,
+ line, ppos, "%s breaks %s",
+ roff_name[tok], roff_name[n->tok]);
+ mdoc_endbody_alloc(mdoc, line, ppos,
+ tok, target);
+ }
+ }
+ }
+ return irc;
+}
+
+/*
+ * Allocate a word and check whether it's punctuation or not.
+ * Punctuation consists of those tokens found in mdoc_isdelim().
+ */
+static void
+dword(struct roff_man *mdoc, int line, int col, const char *p,
+ enum mdelim d, int may_append)
+{
+
+ if (d == DELIM_MAX)
+ d = mdoc_isdelim(p);
+
+ if (may_append &&
+ ! (mdoc->flags & (MDOC_SYNOPSIS | MDOC_KEEP | MDOC_SMOFF)) &&
+ d == DELIM_NONE && mdoc->last->type == ROFFT_TEXT &&
+ mdoc_isdelim(mdoc->last->string) == DELIM_NONE) {
+ roff_word_append(mdoc, p);
+ return;
+ }
+
+ roff_word_alloc(mdoc, line, col, p);
+
+ /*
+ * If the word consists of a bare delimiter,
+ * flag the new node accordingly,
+ * unless doing so was vetoed by the invoking macro.
+ * Always clear the veto, it is only valid for one word.
+ */
+
+ if (d == DELIM_OPEN)
+ mdoc->last->flags |= NODE_DELIMO;
+ else if (d == DELIM_CLOSE &&
+ ! (mdoc->flags & MDOC_NODELIMC) &&
+ mdoc->last->parent->tok != MDOC_Fd)
+ mdoc->last->flags |= NODE_DELIMC;
+ mdoc->flags &= ~MDOC_NODELIMC;
+}
+
+static void
+append_delims(struct roff_man *mdoc, int line, int *pos, char *buf)
+{
+ char *p;
+ int la;
+ enum margserr ac;
+
+ if (buf[*pos] == '\0')
+ return;
+
+ for (;;) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, TOKEN_NONE, &p);
+ if (ac == ARGS_EOLN)
+ 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 |= NODE_EOS;
+ if (ac == ARGS_ALLOC)
+ free(p);
+ }
+}
+
+/*
+ * Parse one word.
+ * If it is a macro, call it and return 1.
+ * Otherwise, allocate it and return 0.
+ */
+static int
+macro_or_word(MACRO_PROT_ARGS, char *p, int parsed)
+{
+ int ntok;
+
+ ntok = buf[ppos] == '"' || parsed == 0 ||
+ mdoc->flags & MDOC_PHRASELIT ? TOKEN_NONE :
+ lookup(mdoc, tok, line, ppos, p);
+
+ if (ntok == TOKEN_NONE) {
+ dword(mdoc, line, ppos, p, DELIM_MAX, tok == TOKEN_NONE ||
+ mdoc_macro(tok)->flags & MDOC_JOIN);
+ return 0;
+ } else {
+ if (tok != TOKEN_NONE &&
+ mdoc_macro(tok)->fp == in_line_eoln)
+ rew_elem(mdoc, tok);
+ (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, ppos, pos, buf);
+ if (tok == TOKEN_NONE)
+ append_delims(mdoc, line, pos, buf);
+ return 1;
+ }
+}
+
+/*
+ * Close out block partial/full explicit.
+ */
+static void
+blk_exp_close(MACRO_PROT_ARGS)
+{
+ struct roff_node *body; /* Our own body. */
+ struct roff_node *endbody; /* Our own end marker. */
+ struct roff_node *itblk; /* An It block starting later. */
+ struct roff_node *later; /* A sub-block starting later. */
+ struct roff_node *n; /* Search back to our block. */
+ struct roff_node *target; /* For find_pending(). */
+
+ int j, lastarg, maxargs, nl, pending;
+ enum margserr ac;
+ enum roff_tok atok, ntok;
+ char *p;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ switch (tok) {
+ case MDOC_Ec:
+ maxargs = 1;
+ break;
+ case MDOC_Ek:
+ mdoc->flags &= ~MDOC_KEEP;
+ /* FALLTHROUGH */
+ default:
+ maxargs = 0;
+ break;
+ }
+
+ /* Search backwards for the beginning of our own body. */
+
+ atok = rew_alt(tok);
+ body = NULL;
+ for (n = mdoc->last; n; n = n->parent) {
+ if (n->flags & NODE_ENDED || n->tok != atok ||
+ n->type != ROFFT_BODY || n->end != ENDBODY_NOT)
+ continue;
+ body = n;
+ break;
+ }
+
+ /*
+ * Search backwards for beginnings of blocks,
+ * both of our own and of pending sub-blocks.
+ */
+
+ endbody = itblk = later = NULL;
+ for (n = mdoc->last; n; n = n->parent) {
+ if (n->flags & NODE_ENDED)
+ continue;
+
+ /*
+ * Mismatching end macros can never break anything
+ * and we only care about the breaking of BLOCKs.
+ */
+
+ if (body == NULL || n->type != ROFFT_BLOCK)
+ continue;
+
+ /*
+ * SYNOPSIS name blocks can not be broken themselves,
+ * but they do get broken together with a broken child.
+ */
+
+ if (n->tok == MDOC_Nm) {
+ if (later != NULL)
+ n->flags |= NODE_BROKEN | NODE_ENDED;
+ continue;
+ }
+
+ if (n->tok == MDOC_It) {
+ itblk = n;
+ continue;
+ }
+
+ if (atok == n->tok) {
+
+ /*
+ * Found the start of our own block.
+ * When there is no pending sub block,
+ * just proceed to closing out.
+ */
+
+ if (later == NULL ||
+ (tok == MDOC_El && itblk == NULL))
+ break;
+
+ /*
+ * When there is a pending sub block, postpone
+ * closing out the current block until the
+ * rew_pending() closing out the sub-block.
+ * Mark the place where the formatting - but not
+ * the scope - of the current block ends.
+ */
+
+ mandoc_msg(MANDOCERR_BLK_NEST,
+ line, ppos, "%s breaks %s",
+ roff_name[atok], roff_name[later->tok]);
+
+ endbody = mdoc_endbody_alloc(mdoc, line, ppos,
+ atok, body);
+
+ if (tok == MDOC_El)
+ itblk->flags |= NODE_ENDED | NODE_BROKEN;
+
+ /*
+ * If a block closing macro taking arguments
+ * breaks another block, put the arguments
+ * into the end marker.
+ */
+
+ if (maxargs)
+ mdoc->next = ROFF_NEXT_CHILD;
+ break;
+ }
+
+ /*
+ * Explicit blocks close out description lines, but
+ * even those can get broken together with a child.
+ */
+
+ if (n->tok == MDOC_Nd) {
+ if (later != NULL)
+ n->flags |= NODE_BROKEN | NODE_ENDED;
+ else
+ rew_last(mdoc, n);
+ continue;
+ }
+
+ /* Breaking an open sub block. */
+
+ break_intermediate(mdoc->last, body);
+ n->flags |= NODE_BROKEN;
+ if (later == NULL)
+ later = n;
+ }
+
+ if (body == NULL) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, line, ppos,
+ "%s", roff_name[tok]);
+ if (maxargs && endbody == NULL) {
+ /*
+ * Stray .Ec without previous .Eo:
+ * Break the output line, keep the arguments.
+ */
+ roff_elem_alloc(mdoc, line, ppos, ROFF_br);
+ rew_elem(mdoc, ROFF_br);
+ }
+ } else if (endbody == NULL) {
+ rew_last(mdoc, body);
+ if (maxargs)
+ mdoc_tail_alloc(mdoc, line, ppos, atok);
+ }
+
+ if ((mdoc_macro(tok)->flags & MDOC_PARSED) == 0) {
+ if (buf[*pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_SKIP, line, ppos,
+ "%s %s", roff_name[tok], buf + *pos);
+ if (endbody == NULL && n != NULL)
+ rew_pending(mdoc, n);
+
+ /*
+ * Restore the fill mode that was set before the display.
+ * This needs to be done here rather than during validation
+ * such that subsequent nodes get the right flags.
+ */
+
+ if (tok == MDOC_Ed && body != NULL) {
+ if (body->flags & NODE_NOFILL)
+ mdoc->flags |= ROFF_NOFILL;
+ else
+ mdoc->flags &= ~ROFF_NOFILL;
+ }
+ return;
+ }
+
+ if (endbody != NULL)
+ n = endbody;
+
+ ntok = TOKEN_NONE;
+ for (j = 0; ; j++) {
+ lastarg = *pos;
+
+ if (j == maxargs && n != NULL)
+ rew_last(mdoc, n);
+
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+ if (ac == ARGS_PUNCT || ac == ARGS_EOLN)
+ break;
+
+ ntok = lookup(mdoc, tok, line, lastarg, p);
+
+ if (ntok == TOKEN_NONE) {
+ dword(mdoc, line, lastarg, p, DELIM_MAX,
+ mdoc_macro(tok)->flags & MDOC_JOIN);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ continue;
+ }
+ if (ac == ARGS_ALLOC)
+ free(p);
+
+ if (n != NULL)
+ rew_last(mdoc, n);
+ mdoc->flags &= ~MDOC_NEWLINE;
+ (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, lastarg, pos, buf);
+ break;
+ }
+
+ if (n != NULL) {
+ pending = 0;
+ if (ntok != TOKEN_NONE && n->flags & NODE_BROKEN) {
+ target = n;
+ do
+ target = target->parent;
+ while ( ! (target->flags & NODE_ENDED));
+ pending = find_pending(mdoc, ntok, line, ppos, target);
+ }
+ if ( ! pending)
+ rew_pending(mdoc, n);
+ }
+ if (nl)
+ append_delims(mdoc, line, pos, buf);
+}
+
+static void
+in_line(MACRO_PROT_ARGS)
+{
+ int la, scope, cnt, firstarg, mayopen, nc, nl;
+ enum roff_tok 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:
+ case MDOC_Ar:
+ case MDOC_Fl:
+ case MDOC_Mt:
+ case MDOC_Nm:
+ case MDOC_Pa:
+ nc = 1;
+ break;
+ default:
+ nc = 0;
+ break;
+ }
+
+ mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+
+ d = DELIM_NONE;
+ firstarg = 1;
+ mayopen = 1;
+ for (cnt = scope = 0;; ) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ /*
+ * At the end of a macro line,
+ * opening delimiters do not suppress spacing.
+ */
+
+ if (ac == ARGS_EOLN) {
+ if (d == DELIM_OPEN)
+ mdoc->last->flags &= ~NODE_DELIMO;
+ break;
+ }
+
+ /*
+ * The rest of the macro line is only punctuation,
+ * to be handled by append_delims().
+ * If there were no other arguments,
+ * do not allow the first one to suppress spacing,
+ * even if it turns out to be a closing one.
+ */
+
+ if (ac == ARGS_PUNCT) {
+ if (cnt == 0 && (nc == 0 || tok == MDOC_An))
+ mdoc->flags |= MDOC_NODELIMC;
+ break;
+ }
+
+ ntok = (tok == MDOC_Fn && !cnt) ?
+ TOKEN_NONE : lookup(mdoc, tok, line, la, 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 (ntok != TOKEN_NONE) {
+ if (scope)
+ rew_elem(mdoc, tok);
+ if (nc && ! cnt) {
+ mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+ rew_last(mdoc, mdoc->last);
+ } else if ( ! nc && ! cnt) {
+ mdoc_argv_free(arg);
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ line, ppos, "%s", roff_name[tok]);
+ }
+ (*mdoc_macro(ntok)->fp)(mdoc, ntok,
+ line, la, pos, buf);
+ if (nl)
+ append_delims(mdoc, line, pos, buf);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ return;
+ }
+
+ /*
+ * Handle punctuation. Set up our scope, if a word;
+ * rewind the scope, if a delimiter; then append the word.
+ */
+
+ if ((d = mdoc_isdelim(p)) != DELIM_NONE) {
+ /*
+ * If we encounter closing punctuation, no word
+ * has been emitted, no scope is open, and we're
+ * allowed to have an empty element, then start
+ * a new scope.
+ */
+ if ((d == DELIM_CLOSE ||
+ (d == DELIM_MIDDLE && tok == MDOC_Fl)) &&
+ !cnt && !scope && nc && mayopen) {
+ mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+ scope = 1;
+ cnt++;
+ if (tok == MDOC_Nm)
+ mayopen = 0;
+ }
+ /*
+ * Close out our scope, if one is open, before
+ * any punctuation.
+ */
+ if (scope && tok != MDOC_Lk) {
+ rew_elem(mdoc, tok);
+ scope = 0;
+ if (tok == MDOC_Fn)
+ mayopen = 0;
+ }
+ } else if (mayopen && !scope) {
+ mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+ scope = 1;
+ cnt++;
+ }
+
+ dword(mdoc, line, la, p, d,
+ mdoc_macro(tok)->flags & MDOC_JOIN);
+
+ if (ac == ARGS_ALLOC)
+ free(p);
+
+ /*
+ * If the first argument is a closing delimiter,
+ * do not suppress spacing before it.
+ */
+
+ if (firstarg && d == DELIM_CLOSE && !nc)
+ mdoc->last->flags &= ~NODE_DELIMC;
+ firstarg = 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 && tok == MDOC_Fl) {
+ rew_elem(mdoc, tok);
+ scope = 0;
+ }
+ }
+
+ if (scope && tok != MDOC_Lk) {
+ rew_elem(mdoc, tok);
+ scope = 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 ( ! cnt) {
+ if (nc) {
+ mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+ rew_last(mdoc, mdoc->last);
+ } else {
+ mdoc_argv_free(arg);
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ line, ppos, "%s", roff_name[tok]);
+ }
+ }
+ if (nl)
+ append_delims(mdoc, line, pos, buf);
+ if (scope)
+ rew_elem(mdoc, tok);
+}
+
+static void
+blk_full(MACRO_PROT_ARGS)
+{
+ struct mdoc_arg *arg;
+ struct roff_node *blk; /* Our own or a broken block. */
+ struct roff_node *head; /* Our own head. */
+ struct roff_node *body; /* Our own body. */
+ struct roff_node *n;
+ char *p;
+ size_t iarg;
+ int done, la, nl, parsed;
+ enum margserr ac, lac;
+
+ nl = MDOC_NEWLINE & mdoc->flags;
+
+ if (buf[*pos] == '\0' && (tok == MDOC_Sh || tok == MDOC_Ss)) {
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ line, ppos, "%s", roff_name[tok]);
+ return;
+ }
+
+ if ((mdoc_macro(tok)->flags & MDOC_EXPLICIT) == 0) {
+
+ /* Here, tok is one of Sh Ss Nm Nd It. */
+
+ blk = NULL;
+ for (n = mdoc->last; n != NULL; n = n->parent) {
+ if (n->flags & NODE_ENDED) {
+ if ( ! (n->flags & NODE_VALID))
+ n->flags |= NODE_BROKEN;
+ continue;
+ }
+ if (n->type != ROFFT_BLOCK)
+ continue;
+
+ if (tok == MDOC_It && n->tok == MDOC_Bl) {
+ if (blk != NULL) {
+ mandoc_msg(MANDOCERR_BLK_BROKEN,
+ line, ppos, "It breaks %s",
+ roff_name[blk->tok]);
+ rew_pending(mdoc, blk);
+ }
+ break;
+ }
+
+ if (mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) {
+ switch (tok) {
+ case MDOC_Sh:
+ case MDOC_Ss:
+ mandoc_msg(MANDOCERR_BLK_BROKEN,
+ line, ppos,
+ "%s breaks %s", roff_name[tok],
+ roff_name[n->tok]);
+ rew_pending(mdoc, n);
+ n = mdoc->last;
+ continue;
+ case MDOC_It:
+ /* Delay in case it's astray. */
+ blk = n;
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+
+ /* Here, n is one of Sh Ss Nm Nd It. */
+
+ if (tok != MDOC_Sh && (n->tok == MDOC_Sh ||
+ (tok != MDOC_Ss && (n->tok == MDOC_Ss ||
+ (tok != MDOC_It && n->tok == MDOC_It)))))
+ break;
+
+ /* Item breaking an explicit block. */
+
+ if (blk != NULL) {
+ mandoc_msg(MANDOCERR_BLK_BROKEN, line, ppos,
+ "It breaks %s", roff_name[blk->tok]);
+ rew_pending(mdoc, blk);
+ blk = NULL;
+ }
+
+ /* Close out prior implicit scopes. */
+
+ rew_pending(mdoc, n);
+ }
+
+ /* Skip items outside lists. */
+
+ if (tok == MDOC_It && (n == NULL || n->tok != MDOC_Bl)) {
+ mandoc_msg(MANDOCERR_IT_STRAY,
+ line, ppos, "It %s", buf + *pos);
+ roff_elem_alloc(mdoc, line, ppos, ROFF_br);
+ rew_elem(mdoc, ROFF_br);
+ return;
+ }
+ }
+
+ /*
+ * 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.
+ */
+
+ switch (tok) {
+ case MDOC_Sh:
+ mdoc->flags &= ~ROFF_NOFILL;
+ break;
+ case MDOC_Ss:
+ mdoc->flags |= ROFF_NONOFILL;
+ break;
+ default:
+ break;
+ }
+ mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+ blk = mdoc_block_alloc(mdoc, line, ppos, tok, arg);
+ head = body = NULL;
+
+ /*
+ * Exception: Heads of `It' macros in `-diag' lists are not
+ * parsed, even though `It' macros in general are parsed.
+ */
+
+ parsed = tok != MDOC_It ||
+ mdoc->last->parent->tok != MDOC_Bl ||
+ mdoc->last->parent->norm->Bl.type != LIST_diag;
+
+ /*
+ * The `Nd' macro has all arguments in its body: it's a hybrid
+ * of block partial-explicit and full-implicit. Stupid.
+ */
+
+ if (tok == MDOC_Nd) {
+ head = roff_head_alloc(mdoc, line, ppos, tok);
+ rew_last(mdoc, head);
+ body = roff_body_alloc(mdoc, line, ppos, tok);
+ }
+
+ if (tok == MDOC_Bk)
+ mdoc->flags |= MDOC_KEEP;
+
+ ac = ARGS_EOLN;
+ for (;;) {
+
+ /*
+ * If we are right after a tab character,
+ * do not parse the first word for macros.
+ */
+
+ if (mdoc->flags & MDOC_PHRASEQN) {
+ mdoc->flags &= ~MDOC_PHRASEQN;
+ mdoc->flags |= MDOC_PHRASEQF;
+ }
+
+ la = *pos;
+ lac = ac;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+ if (ac == ARGS_EOLN) {
+ if (lac != ARGS_PHRASE ||
+ ! (mdoc->flags & MDOC_PHRASEQF))
+ break;
+
+ /*
+ * This line ends in a tab; start the next
+ * column now, with a leading blank.
+ */
+
+ if (body != NULL)
+ rew_last(mdoc, body);
+ body = roff_body_alloc(mdoc, line, ppos, tok);
+ roff_word_alloc(mdoc, line, ppos, "\\&");
+ break;
+ }
+
+ if (tok == MDOC_Bd || tok == MDOC_Bk) {
+ mandoc_msg(MANDOCERR_ARG_EXCESS, line, la,
+ "%s ... %s", roff_name[tok], buf + la);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ break;
+ }
+ if (tok == MDOC_Rs) {
+ mandoc_msg(MANDOCERR_ARG_SKIP,
+ line, la, "Rs %s", buf + la);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ break;
+ }
+ if (ac == ARGS_PUNCT)
+ break;
+
+ /*
+ * Emit leading punctuation (i.e., punctuation before
+ * the ROFFT_HEAD) for non-phrase types.
+ */
+
+ if (head == NULL &&
+ ac != ARGS_PHRASE &&
+ mdoc_isdelim(p) == DELIM_OPEN) {
+ dword(mdoc, line, la, p, DELIM_OPEN, 0);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ continue;
+ }
+
+ /* Open a head if one hasn't been opened. */
+
+ if (head == NULL)
+ head = roff_head_alloc(mdoc, line, ppos, tok);
+
+ if (ac == ARGS_PHRASE) {
+
+ /*
+ * If we haven't opened a body yet, rewind the
+ * head; if we have, rewind that instead.
+ */
+
+ rew_last(mdoc, body == NULL ? head : body);
+ body = roff_body_alloc(mdoc, line, ppos, tok);
+
+ /* Process to the tab or to the end of the line. */
+
+ mdoc->flags |= MDOC_PHRASE;
+ parse_rest(mdoc, TOKEN_NONE, line, &la, buf);
+ mdoc->flags &= ~MDOC_PHRASE;
+
+ /* There may have been `Ta' macros. */
+
+ while (body->next != NULL)
+ body = body->next;
+ continue;
+ }
+
+ done = macro_or_word(mdoc, tok, line, la, pos, buf, p, parsed);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ if (done)
+ break;
+ }
+
+ if (blk->flags & NODE_VALID)
+ return;
+ if (head == NULL)
+ head = roff_head_alloc(mdoc, line, ppos, tok);
+ if (nl && tok != MDOC_Bd && tok != MDOC_Bl && tok != MDOC_Rs)
+ append_delims(mdoc, line, pos, buf);
+ if (body != NULL)
+ goto out;
+ if (find_pending(mdoc, tok, line, ppos, head))
+ return;
+
+ /* Close out scopes to remain in a consistent state. */
+
+ rew_last(mdoc, head);
+ body = roff_body_alloc(mdoc, line, ppos, tok);
+ if (tok == MDOC_Ss)
+ mdoc->flags &= ~ROFF_NONOFILL;
+
+ /*
+ * Set up fill mode for display blocks.
+ * This needs to be done here up front rather than during
+ * validation such that child nodes get the right flags.
+ */
+
+ if (tok == MDOC_Bd && arg != NULL) {
+ for (iarg = 0; iarg < arg->argc; iarg++) {
+ switch (arg->argv[iarg].arg) {
+ case MDOC_Unfilled:
+ case MDOC_Literal:
+ mdoc->flags |= ROFF_NOFILL;
+ break;
+ case MDOC_Filled:
+ case MDOC_Ragged:
+ case MDOC_Centred:
+ mdoc->flags &= ~ROFF_NOFILL;
+ break;
+ default:
+ continue;
+ }
+ break;
+ }
+ }
+out:
+ if (mdoc->flags & MDOC_FREECOL) {
+ rew_last(mdoc, body);
+ rew_last(mdoc, blk);
+ mdoc->flags &= ~MDOC_FREECOL;
+ }
+}
+
+static void
+blk_part_imp(MACRO_PROT_ARGS)
+{
+ int done, la, nl;
+ enum margserr ac;
+ char *p;
+ struct roff_node *blk; /* saved block context */
+ struct roff_node *body; /* saved body context */
+ struct roff_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.
+ */
+
+ blk = mdoc_block_alloc(mdoc, line, ppos, tok, NULL);
+ rew_last(mdoc, roff_head_alloc(mdoc, line, ppos, tok));
+
+ /*
+ * 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 (ac == ARGS_EOLN || ac == ARGS_PUNCT)
+ break;
+
+ if (body == NULL && mdoc_isdelim(p) == DELIM_OPEN) {
+ dword(mdoc, line, la, p, DELIM_OPEN, 0);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ continue;
+ }
+
+ if (body == NULL)
+ body = roff_body_alloc(mdoc, line, ppos, tok);
+
+ done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ if (done)
+ break;
+ }
+ if (body == NULL)
+ body = roff_body_alloc(mdoc, line, ppos, tok);
+
+ if (find_pending(mdoc, tok, line, ppos, body))
+ return;
+
+ rew_last(mdoc, body);
+ if (nl)
+ append_delims(mdoc, line, pos, buf);
+ rew_pending(mdoc, blk);
+
+ /* Move trailing .Ns out of scope. */
+
+ for (n = body->child; n && n->next; n = n->next)
+ /* Do nothing. */ ;
+ if (n && n->tok == MDOC_Ns)
+ roff_node_relink(mdoc, n);
+}
+
+static void
+blk_part_exp(MACRO_PROT_ARGS)
+{
+ int done, la, nl;
+ enum margserr ac;
+ struct roff_node *head; /* keep track of head */
+ char *p;
+
+ 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.
+ */
+
+ roff_block_alloc(mdoc, line, ppos, tok);
+ head = NULL;
+ for (;;) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+ if (ac == ARGS_PUNCT || ac == ARGS_EOLN)
+ break;
+
+ /* Flush out leading punctuation. */
+
+ if (head == NULL && mdoc_isdelim(p) == DELIM_OPEN) {
+ dword(mdoc, line, la, p, DELIM_OPEN, 0);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ continue;
+ }
+
+ if (head == NULL) {
+ head = roff_head_alloc(mdoc, line, ppos, tok);
+ if (tok == MDOC_Eo) /* Not parsed. */
+ dword(mdoc, line, la, p, DELIM_MAX, 0);
+ rew_last(mdoc, head);
+ roff_body_alloc(mdoc, line, ppos, tok);
+ if (tok == MDOC_Eo) {
+ if (ac == ARGS_ALLOC)
+ free(p);
+ continue;
+ }
+ }
+
+ done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ if (done)
+ break;
+ }
+
+ /* Clean-up to leave in a consistent state. */
+
+ if (head == NULL) {
+ rew_last(mdoc, roff_head_alloc(mdoc, line, ppos, tok));
+ roff_body_alloc(mdoc, line, ppos, tok);
+ }
+ if (nl)
+ append_delims(mdoc, line, pos, buf);
+}
+
+static void
+in_line_argn(MACRO_PROT_ARGS)
+{
+ struct mdoc_arg *arg;
+ char *p;
+ enum margserr ac;
+ enum roff_tok ntok;
+ int state; /* arg#; -1: not yet open; -2: closed */
+ int la, maxargs, nl;
+
+ nl = mdoc->flags & MDOC_NEWLINE;
+
+ /*
+ * 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:
+ case MDOC_Ns:
+ case MDOC_Ux:
+ maxargs = 0;
+ break;
+ case MDOC_Bx:
+ case MDOC_Es:
+ case MDOC_Xr:
+ maxargs = 2;
+ break;
+ default:
+ maxargs = 1;
+ break;
+ }
+
+ mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+
+ state = -1;
+ p = NULL;
+ for (;;) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+
+ if ((ac == ARGS_WORD || ac == ARGS_ALLOC) && state == -1 &&
+ (mdoc_macro(tok)->flags & MDOC_IGNDELIM) == 0 &&
+ mdoc_isdelim(p) == DELIM_OPEN) {
+ dword(mdoc, line, la, p, DELIM_OPEN, 0);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ continue;
+ }
+
+ if (state == -1 && tok != MDOC_In &&
+ tok != MDOC_St && tok != MDOC_Xr) {
+ mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+ state = 0;
+ }
+
+ if (ac == ARGS_PUNCT || ac == ARGS_EOLN) {
+ if (abs(state) < 2 && tok == MDOC_Pf)
+ mandoc_msg(MANDOCERR_PF_SKIP,
+ line, ppos, "Pf %s",
+ p == NULL ? "at eol" : p);
+ break;
+ }
+
+ if (state == maxargs) {
+ rew_elem(mdoc, tok);
+ state = -2;
+ }
+
+ ntok = (tok == MDOC_Pf && state == 0) ?
+ TOKEN_NONE : lookup(mdoc, tok, line, la, p);
+
+ if (ntok != TOKEN_NONE) {
+ if (state >= 0) {
+ rew_elem(mdoc, tok);
+ state = -2;
+ }
+ (*mdoc_macro(ntok)->fp)(mdoc, ntok,
+ line, la, pos, buf);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ break;
+ }
+
+ if (mdoc_macro(tok)->flags & MDOC_IGNDELIM ||
+ mdoc_isdelim(p) == DELIM_NONE) {
+ if (state == -1) {
+ mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+ state = 1;
+ } else if (state >= 0)
+ state++;
+ } else if (state >= 0) {
+ rew_elem(mdoc, tok);
+ state = -2;
+ }
+
+ dword(mdoc, line, la, p, DELIM_MAX,
+ mdoc_macro(tok)->flags & MDOC_JOIN);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ p = mdoc->last->string;
+ }
+
+ if (state == -1) {
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ line, ppos, "%s", roff_name[tok]);
+ return;
+ }
+
+ if (state == 0 && tok == MDOC_Pf)
+ append_delims(mdoc, line, pos, buf);
+ if (state >= 0)
+ rew_elem(mdoc, tok);
+ if (nl)
+ append_delims(mdoc, line, pos, buf);
+}
+
+static void
+in_line_eoln(MACRO_PROT_ARGS)
+{
+ struct roff_node *n;
+ struct mdoc_arg *arg;
+
+ if ((tok == MDOC_Pp || tok == MDOC_Lp) &&
+ ! (mdoc->flags & MDOC_SYNOPSIS)) {
+ n = mdoc->last;
+ if (mdoc->next == ROFF_NEXT_SIBLING)
+ n = n->parent;
+ if (n->tok == MDOC_Nm)
+ rew_last(mdoc, n->parent);
+ }
+
+ if (buf[*pos] == '\0' &&
+ (tok == MDOC_Fd || *roff_name[tok] == '%')) {
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ line, ppos, "%s", roff_name[tok]);
+ return;
+ }
+
+ mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+ mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+ if (parse_rest(mdoc, tok, line, pos, buf))
+ return;
+ rew_elem(mdoc, tok);
+}
+
+/*
+ * The simplest argument parser available: Parse the remaining
+ * words until the end of the phrase or line and return 0
+ * or until the next macro, call that macro, and return 1.
+ */
+static int
+parse_rest(struct roff_man *mdoc, enum roff_tok tok,
+ int line, int *pos, char *buf)
+{
+ char *p;
+ int done, la;
+ enum margserr ac;
+
+ for (;;) {
+ la = *pos;
+ ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+ if (ac == ARGS_EOLN)
+ return 0;
+ done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1);
+ if (ac == ARGS_ALLOC)
+ free(p);
+ if (done)
+ return 1;
+ }
+}
+
+static void
+ctx_synopsis(MACRO_PROT_ARGS)
+{
+
+ if (~mdoc->flags & (MDOC_SYNOPSIS | MDOC_NEWLINE))
+ in_line(mdoc, tok, line, ppos, pos, buf);
+ else if (tok == MDOC_Nm)
+ blk_full(mdoc, tok, line, ppos, pos, buf);
+ else {
+ assert(tok == MDOC_Vt);
+ 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 void
+phrase_ta(MACRO_PROT_ARGS)
+{
+ struct roff_node *body, *n;
+
+ /* Make sure we are in a column list or ignore this macro. */
+
+ body = NULL;
+ for (n = mdoc->last; n != NULL; n = n->parent) {
+ if (n->flags & NODE_ENDED)
+ continue;
+ if (n->tok == MDOC_It && n->type == ROFFT_BODY)
+ body = n;
+ if (n->tok == MDOC_Bl && n->end == ENDBODY_NOT)
+ break;
+ }
+
+ if (n == NULL || n->norm->Bl.type != LIST_column) {
+ mandoc_msg(MANDOCERR_TA_STRAY, line, ppos, "Ta");
+ return;
+ }
+
+ /* Advance to the next column. */
+
+ rew_last(mdoc, body);
+ roff_body_alloc(mdoc, line, ppos, MDOC_It);
+ parse_rest(mdoc, TOKEN_NONE, line, pos, buf);
+}
diff --git a/usr.bin/mandoc/mdoc_man.c b/usr.bin/mandoc/mdoc_man.c
new file mode 100644
index 0000000..25d59a5
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_man.c
@@ -0,0 +1,1836 @@
+/* $OpenBSD: mdoc_man.c,v 1.134 2020/02/27 01:25:57 schwarze Exp $ */
+/*
+ * Copyright (c) 2011-2020 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "man.h"
+#include "out.h"
+#include "main.h"
+
+#define DECL_ARGS const struct roff_meta *meta, struct roff_node *n
+
+typedef int (*int_fp)(DECL_ARGS);
+typedef void (*void_fp)(DECL_ARGS);
+
+struct mdoc_man_act {
+ int_fp cond; /* DON'T run actions */
+ int_fp pre; /* pre-node action */
+ void_fp post; /* 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 int man_strlen(const char *);
+static void mid_it(void);
+static void post__t(DECL_ARGS);
+static void post_aq(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_vt(DECL_ARGS);
+static int pre__t(DECL_ARGS);
+static int pre_abort(DECL_ARGS);
+static int pre_an(DECL_ARGS);
+static int pre_ap(DECL_ARGS);
+static int pre_aq(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 void pre_br(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_skip(DECL_ARGS);
+static int pre_eo(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 void pre_ft(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_nm(DECL_ARGS);
+static int pre_no(DECL_ARGS);
+static void pre_noarg(DECL_ARGS);
+static int pre_ns(DECL_ARGS);
+static void pre_onearg(DECL_ARGS);
+static int pre_pp(DECL_ARGS);
+static int pre_rs(DECL_ARGS);
+static int pre_sm(DECL_ARGS);
+static void pre_sp(DECL_ARGS);
+static int pre_sect(DECL_ARGS);
+static int pre_sy(DECL_ARGS);
+static void pre_syn(struct roff_node *);
+static void pre_ta(DECL_ARGS);
+static int pre_vt(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 *, int);
+static void print_width(const struct mdoc_bl *,
+ const struct roff_node *);
+static void print_count(int *);
+static void print_node(DECL_ARGS);
+
+static const void_fp roff_man_acts[ROFF_MAX] = {
+ pre_br, /* br */
+ pre_onearg, /* ce */
+ pre_noarg, /* fi */
+ pre_ft, /* ft */
+ pre_onearg, /* ll */
+ pre_onearg, /* mc */
+ pre_noarg, /* nf */
+ pre_onearg, /* po */
+ pre_onearg, /* rj */
+ pre_sp, /* sp */
+ pre_ta, /* ta */
+ pre_onearg, /* ti */
+};
+
+static const struct mdoc_man_act mdoc_man_acts[MDOC_MAX - MDOC_Dd] = {
+ { 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_ap, NULL, NULL, NULL }, /* Ap */
+ { 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_abort, NULL, NULL, NULL }, /* Ot */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Pa */
+ { NULL, pre_ex, 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_aq, post_aq, NULL, NULL }, /* Ao */
+ { cond_body, pre_aq, post_aq, NULL, NULL }, /* 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_bk, post_bk, NULL, NULL }, /* Bsx */
+ { NULL, pre_bk, post_bk, NULL, NULL }, /* Bx */
+ { NULL, pre_skip, 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 */
+ { cond_body, pre_eo, post_eo, NULL, NULL }, /* Eo */
+ { NULL, pre_bk, post_bk, NULL, 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_bk, post_bk, NULL, NULL }, /* Nx */
+ { NULL, pre_bk, post_bk, NULL, 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, NULL, NULL, NULL, 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, NULL, NULL, NULL, NULL }, /* Bt */
+ { NULL, NULL, NULL, NULL, NULL }, /* Hf */
+ { NULL, pre_em, post_font, NULL, NULL }, /* Fr */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ud */
+ { NULL, NULL, post_lb, NULL, NULL }, /* Lb */
+ { NULL, pre_abort, 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_skip, NULL, NULL, NULL }, /* Es */
+ { cond_body, pre_en, post_en, NULL, NULL }, /* En */
+ { NULL, pre_bk, post_bk, NULL, NULL }, /* Dx */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %Q */
+ { NULL, NULL, post_percent, NULL, NULL }, /* %U */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ta */
+ { NULL, pre_skip, NULL, NULL, NULL }, /* Tg */
+};
+static const struct mdoc_man_act *mdoc_man_act(enum roff_tok);
+
+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 int 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 const struct mdoc_man_act *
+mdoc_man_act(enum roff_tok tok)
+{
+ assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
+ return mdoc_man_acts + (tok - MDOC_Dd);
+}
+
+static int
+man_strlen(const char *cp)
+{
+ size_t rsz;
+ int skip, sz;
+
+ sz = 0;
+ skip = 0;
+ for (;;) {
+ 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:
+ case ESCAPE_NUMBERED:
+ case ESCAPE_SPECIAL:
+ case ESCAPE_UNDEF:
+ case ESCAPE_OVERSTRIKE:
+ if (skip)
+ skip = 0;
+ else
+ sz++;
+ break;
+ case ESCAPE_SKIPCHAR:
+ skip = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return sz;
+}
+
+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_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, int keywords)
+{
+ char buf[24];
+ struct roffsu su;
+ const char *end;
+ int sz;
+
+ print_line(".RS", MMAN_Bk_susp);
+
+ /* Convert v into a number (of characters). */
+ if (NULL == v || '\0' == *v || (keywords && !strcmp(v, "left")))
+ sz = 0;
+ else if (keywords && !strcmp(v, "indent"))
+ sz = 6;
+ else if (keywords && !strcmp(v, "indent-two"))
+ sz = 12;
+ else {
+ end = a2roffsu(v, &su, SCALE_EN);
+ if (end == NULL || *end != '\0')
+ sz = man_strlen(v);
+ else 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;
+ }
+ }
+
+ /*
+ * 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), "%dn", sz);
+ print_word(buf);
+ outflags |= MMAN_nl;
+}
+
+/*
+ * Set up the indentation for a list item; used from pre_it().
+ */
+static void
+print_width(const struct mdoc_bl *bl, const struct roff_node *child)
+{
+ char buf[24];
+ struct roffsu su;
+ const char *end;
+ int numeric, remain, sz, chsz;
+
+ numeric = 1;
+ remain = 0;
+
+ /* Convert the width into a number (of characters). */
+ if (bl->width == NULL)
+ sz = (bl->type == LIST_hang) ? 6 : 0;
+ else {
+ end = a2roffsu(bl->width, &su, SCALE_MAX);
+ if (end == NULL || *end != '\0')
+ sz = man_strlen(bl->width);
+ else if (SCALE_EN == su.unit)
+ sz = su.scale;
+ else {
+ sz = 0;
+ numeric = 0;
+ }
+ }
+
+ /* XXX Rough estimation, might have multiple parts. */
+ if (bl->type == LIST_enum)
+ chsz = (bl->count > 8) + 1;
+ else if (child != NULL && child->type == ROFFT_TEXT)
+ chsz = man_strlen(child->string);
+ else
+ chsz = 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 (chsz > sz && bl->type != LIST_tag)
+ print_block(".HP", MMAN_spc);
+ else {
+ print_block(".TP", MMAN_spc);
+ remain = sz + 2;
+ }
+ if (numeric) {
+ (void)snprintf(buf, sizeof(buf), "%dn", sz + 2);
+ print_word(buf);
+ } else
+ print_word(bl->width);
+ TPremain = remain;
+}
+
+static void
+print_count(int *count)
+{
+ char buf[24];
+
+ (void)snprintf(buf, sizeof(buf), "%d.\\&", ++*count);
+ print_word(buf);
+}
+
+void
+man_mdoc(void *arg, const struct roff_meta *mdoc)
+{
+ struct roff_node *n;
+
+ printf(".\\\" Automatically generated from an mdoc input file."
+ " Do not edit.\n");
+ for (n = mdoc->first->child; n != NULL; n = n->next) {
+ if (n->type != ROFFT_COMMENT)
+ break;
+ printf(".\\\"%s\n", n->string);
+ }
+
+ printf(".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n",
+ mdoc->title, (mdoc->msec == NULL ? "" : mdoc->msec),
+ mdoc->date, mdoc->os, mdoc->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';
+ }
+ for (; n != NULL; n = n->next)
+ print_node(mdoc, n);
+ putchar('\n');
+}
+
+static void
+print_node(DECL_ARGS)
+{
+ const struct mdoc_man_act *act;
+ struct roff_node *sub;
+ int cond, do_sub;
+
+ if (n->flags & NODE_NOPRT)
+ return;
+
+ /*
+ * Break the line if we were parsed subsequent the current node.
+ * This makes the page structure be more consistent.
+ */
+ if (outflags & MMAN_spc &&
+ n->flags & NODE_LINE &&
+ !roff_node_transparent(n))
+ outflags |= MMAN_nl;
+
+ act = NULL;
+ cond = 0;
+ do_sub = 1;
+ n->flags &= ~NODE_ENDED;
+
+ if (n->type == ROFFT_TEXT) {
+ /*
+ * 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;
+ }
+ if (n->flags & NODE_DELIMC)
+ outflags &= ~(MMAN_spc | MMAN_spc_force);
+ else if (outflags & MMAN_Sm)
+ outflags |= MMAN_spc_force;
+ print_word(n->string);
+ if (n->flags & NODE_DELIMO)
+ outflags &= ~(MMAN_spc | MMAN_spc_force);
+ else if (outflags & MMAN_Sm)
+ outflags |= MMAN_spc;
+ } else if (n->tok < ROFF_MAX) {
+ (*roff_man_acts[n->tok])(meta, n);
+ return;
+ } else {
+ /*
+ * Conditionally run the pre-node action handler for a
+ * node.
+ */
+ act = mdoc_man_act(n->tok);
+ cond = act->cond == NULL || (*act->cond)(meta, n);
+ if (cond && act->pre != NULL &&
+ (n->end == ENDBODY_NOT || n->child != NULL))
+ 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 (NODE_ENDED & n->flags)
+ return;
+
+ if (cond && act->post)
+ (*act->post)(meta, n);
+
+ if (ENDBODY_NOT != n->end)
+ n->body->flags |= NODE_ENDED;
+}
+
+static int
+cond_head(DECL_ARGS)
+{
+
+ return n->type == ROFFT_HEAD;
+}
+
+static int
+cond_body(DECL_ARGS)
+{
+
+ return n->type == ROFFT_BODY;
+}
+
+static int
+pre_abort(DECL_ARGS)
+{
+ abort();
+}
+
+static int
+pre_enc(DECL_ARGS)
+{
+ const char *prefix;
+
+ prefix = mdoc_man_act(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 = mdoc_man_act(n->tok)->suffix;
+ if (NULL == suffix)
+ return;
+ outflags &= ~(MMAN_spc | MMAN_nl);
+ print_word(suffix);
+}
+
+static int
+pre_ex(DECL_ARGS)
+{
+ outflags |= MMAN_br | MMAN_nl;
+ return 1;
+}
+
+static void
+post_font(DECL_ARGS)
+{
+
+ font_pop();
+}
+
+static void
+post_percent(DECL_ARGS)
+{
+ struct roff_node *np, *nn, *nnn;
+
+ if (mdoc_man_act(n->tok)->pre == pre_em)
+ font_pop();
+
+ if ((nn = roff_node_next(n)) != NULL) {
+ np = roff_node_prev(n);
+ nnn = nn == NULL ? NULL : roff_node_next(nn);
+ if (nn->tok != n->tok ||
+ (np != NULL && np->tok == n->tok) ||
+ (nnn != NULL && nnn->tok == n->tok))
+ print_word(",");
+ if (nn->tok == n->tok &&
+ (nnn == NULL || nnn->tok != n->tok))
+ print_word("and");
+ } else {
+ print_word(".");
+ outflags |= MMAN_nl;
+ }
+}
+
+static int
+pre__t(DECL_ARGS)
+{
+
+ if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) {
+ print_word("\\(lq");
+ outflags &= ~MMAN_spc;
+ } else
+ font_push('I');
+ return 1;
+}
+
+static void
+post__t(DECL_ARGS)
+{
+
+ if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) {
+ outflags &= ~MMAN_spc;
+ print_word("\\(rq");
+ } else
+ font_pop();
+ post_percent(meta, n);
+}
+
+/*
+ * Print before a section header.
+ */
+static int
+pre_sect(DECL_ARGS)
+{
+
+ if (n->type == ROFFT_HEAD) {
+ outflags |= MMAN_sp;
+ print_block(mdoc_man_act(n->tok)->prefix, 0);
+ print_word("");
+ putchar('\"');
+ outflags &= ~MMAN_spc;
+ }
+ return 1;
+}
+
+/*
+ * Print subsequent a section header.
+ */
+static void
+post_sect(DECL_ARGS)
+{
+
+ if (n->type != ROFFT_HEAD)
+ 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(struct roff_node *n)
+{
+ struct roff_node *np;
+
+ if ((n->flags & NODE_SYNPRETTY) == 0 ||
+ (np = roff_node_prev(n)) == NULL)
+ return;
+
+ if (np->tok == n->tok &&
+ MDOC_Ft != n->tok &&
+ MDOC_Fo != n->tok &&
+ MDOC_Fn != n->tok) {
+ outflags |= MMAN_br;
+ return;
+ }
+
+ switch (np->tok) {
+ case MDOC_Fd:
+ case MDOC_Fn:
+ case MDOC_Fo:
+ case MDOC_In:
+ 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_aq(DECL_ARGS)
+{
+
+ print_word(n->child != NULL && n->child->next == NULL &&
+ n->child->tok == MDOC_Mt ? "<" : "\\(la");
+ outflags &= ~MMAN_spc;
+ return 1;
+}
+
+static void
+post_aq(DECL_ARGS)
+{
+
+ outflags &= ~(MMAN_spc | MMAN_nl);
+ print_word(n->child != NULL && n->child->next == NULL &&
+ n->child->tok == MDOC_Mt ? ">" : "\\(ra");
+}
+
+static int
+pre_bd(DECL_ARGS)
+{
+ outflags &= ~(MMAN_PP | MMAN_sp | MMAN_br);
+ if (n->norm->Bd.type == DISP_unfilled ||
+ n->norm->Bd.type == DISP_literal)
+ print_line(".nf", 0);
+ if (n->norm->Bd.comp == 0 && roff_node_prev(n->parent) != NULL)
+ outflags |= MMAN_sp;
+ print_offs(n->norm->Bd.offs, 1);
+ return 1;
+}
+
+static void
+post_bd(DECL_ARGS)
+{
+ enum roff_tok bef, now;
+
+ /* Close out this display. */
+ print_line(".RE", MMAN_nl);
+ bef = n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi;
+ if (n->last == NULL)
+ now = n->norm->Bd.type == DISP_unfilled ||
+ n->norm->Bd.type == DISP_literal ? ROFF_nf : ROFF_fi;
+ else if (n->last->tok == ROFF_nf)
+ now = ROFF_nf;
+ else if (n->last->tok == ROFF_fi)
+ now = ROFF_fi;
+ else
+ now = n->last->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi;
+ if (bef != now) {
+ outflags |= MMAN_nl;
+ print_word(".");
+ outflags &= ~MMAN_spc;
+ print_word(roff_name[bef]);
+ outflags |= MMAN_nl;
+ }
+
+ /* Maybe we are inside an enclosing list? */
+ if (roff_node_next(n->parent) != NULL)
+ mid_it();
+}
+
+static int
+pre_bf(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ return 1;
+ case ROFFT_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 (n->type == ROFFT_BODY)
+ font_pop();
+}
+
+static int
+pre_bk(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ return 1;
+ case ROFFT_BODY:
+ case ROFFT_ELEM:
+ outflags |= MMAN_Bk;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void
+post_bk(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_ELEM:
+ while ((n = n->parent) != NULL)
+ if (n->tok == MDOC_Bk)
+ return;
+ /* FALLTHROUGH */
+ case ROFFT_BODY:
+ outflags &= ~MMAN_Bk;
+ break;
+ default:
+ break;
+ }
+}
+
+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, 0);
+ 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;
+ }
+
+ if (n->child != NULL) {
+ 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:
+ if (n->child != NULL)
+ 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(Bl_stack[Bl_stack_len] == 0);
+ } else {
+ outflags |= MMAN_PP | MMAN_nl;
+ outflags &= ~(MMAN_sp | MMAN_br);
+ }
+
+ /* Maybe we are inside an enclosing list? */
+ if (roff_node_next(n->parent) != NULL)
+ mid_it();
+}
+
+static void
+pre_br(DECL_ARGS)
+{
+ outflags |= MMAN_br;
+}
+
+static int
+pre_dl(DECL_ARGS)
+{
+ print_offs("6n", 0);
+ return 1;
+}
+
+static void
+post_dl(DECL_ARGS)
+{
+ print_line(".RE", MMAN_nl);
+
+ /* Maybe we are inside an enclosing list? */
+ if (roff_node_next(n->parent) != NULL)
+ 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 int
+pre_eo(DECL_ARGS)
+{
+
+ if (n->end == ENDBODY_NOT &&
+ n->parent->head->child == NULL &&
+ n->child != NULL &&
+ n->child->end != ENDBODY_NOT)
+ print_word("\\&");
+ else if (n->end != ENDBODY_NOT ? n->child != NULL :
+ n->parent->head->child != NULL && (n->child != NULL ||
+ (n->parent->tail != NULL && n->parent->tail->child != NULL)))
+ outflags &= ~(MMAN_spc | MMAN_nl);
+ return 1;
+}
+
+static void
+post_eo(DECL_ARGS)
+{
+ int body, tail;
+
+ if (n->end != ENDBODY_NOT) {
+ outflags |= MMAN_spc;
+ return;
+ }
+
+ body = n->child != NULL || n->parent->head->child != NULL;
+ tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
+
+ if (body && tail)
+ outflags &= ~MMAN_spc;
+ else if ( ! (body || tail))
+ print_word("\\&");
+ else if ( ! tail)
+ outflags |= MMAN_spc;
+}
+
+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 || NODE_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)
+{
+ struct roff_node *nn;
+
+ if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
+ 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("\\-");
+ if (n->child != NULL)
+ outflags &= ~MMAN_spc;
+ return 1;
+}
+
+static void
+post_fl(DECL_ARGS)
+{
+ struct roff_node *nn;
+
+ font_pop();
+ if (n->child == NULL &&
+ ((nn = roff_node_next(n)) != NULL &&
+ nn->type != ROFFT_TEXT &&
+ (nn->flags & NODE_LINE) == 0))
+ outflags &= ~MMAN_spc;
+}
+
+static int
+pre_fn(DECL_ARGS)
+{
+
+ pre_syn(n);
+
+ n = n->child;
+ if (NULL == n)
+ return 0;
+
+ if (NODE_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 (NODE_SYNPRETTY & n->flags) {
+ print_word(";");
+ outflags |= MMAN_PP;
+ }
+}
+
+static int
+pre_fo(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ pre_syn(n);
+ break;
+ case ROFFT_HEAD:
+ if (n->child == NULL)
+ return 0;
+ if (NODE_SYNPRETTY & n->flags)
+ print_block(".HP 4n", MMAN_nl);
+ font_push('B');
+ break;
+ case ROFFT_BODY:
+ outflags &= ~(MMAN_spc | MMAN_nl);
+ print_word("(");
+ outflags &= ~MMAN_spc;
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static void
+post_fo(DECL_ARGS)
+{
+
+ switch (n->type) {
+ case ROFFT_HEAD:
+ if (n->child != NULL)
+ font_pop();
+ break;
+ case ROFFT_BODY:
+ post_fn(meta, n);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_Ft(DECL_ARGS)
+{
+
+ pre_syn(n);
+ font_push('I');
+ return 1;
+}
+
+static void
+pre_ft(DECL_ARGS)
+{
+ print_line(".ft", 0);
+ print_word(n->child->string);
+ outflags |= MMAN_nl;
+}
+
+static int
+pre_in(DECL_ARGS)
+{
+
+ if (NODE_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 (NODE_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 roff_node *bln;
+
+ switch (n->type) {
+ case ROFFT_HEAD:
+ outflags |= MMAN_PP | MMAN_nl;
+ bln = n->parent->parent;
+ if (bln->norm->Bl.comp == 0 ||
+ (n->parent->prev == NULL &&
+ roff_node_prev(bln->parent) == NULL))
+ outflags |= MMAN_sp;
+ outflags &= ~MMAN_br;
+ switch (bln->norm->Bl.type) {
+ case LIST_item:
+ return 0;
+ case LIST_inset:
+ case LIST_diag:
+ case LIST_ohang:
+ if (bln->norm->Bl.type == LIST_diag)
+ print_line(".B \"", 0);
+ else
+ print_line(".BR \\& \"", 0);
+ outflags &= ~MMAN_spc;
+ return 1;
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_hyphen:
+ print_width(&bln->norm->Bl, NULL);
+ TPremain = 0;
+ outflags |= MMAN_nl;
+ font_push('B');
+ if (LIST_bullet == bln->norm->Bl.type)
+ print_word("\\(bu");
+ else
+ print_word("-");
+ font_pop();
+ outflags |= MMAN_nl;
+ return 0;
+ case LIST_enum:
+ print_width(&bln->norm->Bl, NULL);
+ TPremain = 0;
+ outflags |= MMAN_nl;
+ print_count(&bln->norm->Bl.count);
+ outflags |= MMAN_nl;
+ return 0;
+ case LIST_hang:
+ print_width(&bln->norm->Bl, n->child);
+ TPremain = 0;
+ outflags |= MMAN_nl;
+ return 1;
+ case LIST_tag:
+ print_width(&bln->norm->Bl, n->child);
+ 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), "%dn",
+ 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 roff_node *bln;
+
+ bln = n->parent->parent;
+
+ switch (n->type) {
+ case ROFFT_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 ROFFT_BODY:
+ switch (bln->norm->Bl.type) {
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_hyphen:
+ case LIST_enum:
+ case LIST_hang:
+ 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 roff_node *link, *descr, *punct;
+
+ if ((link = n->child) == NULL)
+ return 0;
+
+ /* Find beginning of trailing punctuation. */
+ punct = n->last;
+ while (punct != link && punct->flags & NODE_DELIMC)
+ punct = punct->prev;
+ punct = punct->next;
+
+ /* Link text. */
+ if ((descr = link->next) != NULL && descr != punct) {
+ font_push('I');
+ while (descr != punct) {
+ print_word(descr->string);
+ descr = descr->next;
+ }
+ font_pop();
+ print_word(":");
+ }
+
+ /* Link target. */
+ font_push('B');
+ print_word(link->string);
+ font_pop();
+
+ /* Trailing punctuation. */
+ while (punct != NULL) {
+ print_word(punct->string);
+ punct = punct->next;
+ }
+ return 0;
+}
+
+static void
+pre_onearg(DECL_ARGS)
+{
+ outflags |= MMAN_nl;
+ print_word(".");
+ outflags &= ~MMAN_spc;
+ print_word(roff_name[n->tok]);
+ if (n->child != NULL)
+ print_word(n->child->string);
+ outflags |= MMAN_nl;
+ if (n->tok == ROFF_ce)
+ for (n = n->child->next; n != NULL; n = n->next)
+ print_node(meta, n);
+}
+
+static int
+pre_li(DECL_ARGS)
+{
+ font_push('R');
+ return 1;
+}
+
+static int
+pre_nm(DECL_ARGS)
+{
+ char *name;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ outflags |= MMAN_Bk;
+ pre_syn(n);
+ return 1;
+ case ROFFT_HEAD:
+ case ROFFT_ELEM:
+ break;
+ default:
+ return 1;
+ }
+ name = n->child == NULL ? NULL : n->child->string;
+ if (name == NULL)
+ return 0;
+ if (n->type == ROFFT_HEAD) {
+ if (roff_node_prev(n->parent) == NULL)
+ outflags |= MMAN_sp;
+ print_block(".HP", 0);
+ printf(" %dn", man_strlen(name) + 1);
+ outflags |= MMAN_nl;
+ }
+ font_push('B');
+ return 1;
+}
+
+static void
+post_nm(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ outflags &= ~MMAN_Bk;
+ break;
+ case ROFFT_HEAD:
+ case ROFFT_ELEM:
+ if (n->child != NULL && n->child->string != NULL)
+ font_pop();
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pre_no(DECL_ARGS)
+{
+ outflags |= MMAN_spc_force;
+ return 1;
+}
+
+static void
+pre_noarg(DECL_ARGS)
+{
+ outflags |= MMAN_nl;
+ print_word(".");
+ outflags &= ~MMAN_spc;
+ print_word(roff_name[n->tok]);
+ outflags |= MMAN_nl;
+}
+
+static int
+pre_ns(DECL_ARGS)
+{
+ outflags &= ~MMAN_spc;
+ return 0;
+}
+
+static void
+post_pf(DECL_ARGS)
+{
+
+ if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
+ 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_skip(DECL_ARGS)
+{
+
+ 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 void
+pre_sp(DECL_ARGS)
+{
+ if (outflags & MMAN_PP) {
+ outflags &= ~MMAN_PP;
+ print_line(".PP", 0);
+ } else {
+ print_line(".sp", 0);
+ if (n->child != NULL)
+ print_word(n->child->string);
+ }
+ outflags |= MMAN_nl;
+}
+
+static int
+pre_sy(DECL_ARGS)
+{
+
+ font_push('B');
+ return 1;
+}
+
+static void
+pre_ta(DECL_ARGS)
+{
+ print_line(".ta", 0);
+ for (n = n->child; n != NULL; n = n->next)
+ print_word(n->string);
+ outflags |= MMAN_nl;
+}
+
+static int
+pre_vt(DECL_ARGS)
+{
+
+ if (NODE_SYNPRETTY & n->flags) {
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ pre_syn(n);
+ return 1;
+ case ROFFT_BODY:
+ break;
+ default:
+ return 0;
+ }
+ }
+ font_push('I');
+ return 1;
+}
+
+static void
+post_vt(DECL_ARGS)
+{
+
+ if (n->flags & NODE_SYNPRETTY && n->type != ROFFT_BODY)
+ 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;
+}
diff --git a/usr.bin/mandoc/mdoc_markdown.c b/usr.bin/mandoc/mdoc_markdown.c
new file mode 100644
index 0000000..e0572cb
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_markdown.c
@@ -0,0 +1,1606 @@
+/* $OpenBSD: mdoc_markdown.c,v 1.35 2020/04/03 11:34:19 schwarze Exp $ */
+/*
+ * Copyright (c) 2017, 2018, 2020 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.
+ *
+ * Markdown formatter for mdoc(7) used by mandoc(1).
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "main.h"
+
+struct md_act {
+ int (*cond)(struct roff_node *);
+ int (*pre)(struct roff_node *);
+ void (*post)(struct roff_node *);
+ const char *prefix; /* pre-node string constant */
+ const char *suffix; /* post-node string constant */
+};
+
+static void md_nodelist(struct roff_node *);
+static void md_node(struct roff_node *);
+static const char *md_stack(char);
+static void md_preword(void);
+static void md_rawword(const char *);
+static void md_word(const char *);
+static void md_named(const char *);
+static void md_char(unsigned char);
+static void md_uri(const char *);
+
+static int md_cond_head(struct roff_node *);
+static int md_cond_body(struct roff_node *);
+
+static int md_pre_abort(struct roff_node *);
+static int md_pre_raw(struct roff_node *);
+static int md_pre_word(struct roff_node *);
+static int md_pre_skip(struct roff_node *);
+static void md_pre_syn(struct roff_node *);
+static int md_pre_An(struct roff_node *);
+static int md_pre_Ap(struct roff_node *);
+static int md_pre_Bd(struct roff_node *);
+static int md_pre_Bk(struct roff_node *);
+static int md_pre_Bl(struct roff_node *);
+static int md_pre_D1(struct roff_node *);
+static int md_pre_Dl(struct roff_node *);
+static int md_pre_En(struct roff_node *);
+static int md_pre_Eo(struct roff_node *);
+static int md_pre_Fa(struct roff_node *);
+static int md_pre_Fd(struct roff_node *);
+static int md_pre_Fn(struct roff_node *);
+static int md_pre_Fo(struct roff_node *);
+static int md_pre_In(struct roff_node *);
+static int md_pre_It(struct roff_node *);
+static int md_pre_Lk(struct roff_node *);
+static int md_pre_Mt(struct roff_node *);
+static int md_pre_Nd(struct roff_node *);
+static int md_pre_Nm(struct roff_node *);
+static int md_pre_No(struct roff_node *);
+static int md_pre_Ns(struct roff_node *);
+static int md_pre_Pp(struct roff_node *);
+static int md_pre_Rs(struct roff_node *);
+static int md_pre_Sh(struct roff_node *);
+static int md_pre_Sm(struct roff_node *);
+static int md_pre_Vt(struct roff_node *);
+static int md_pre_Xr(struct roff_node *);
+static int md_pre__T(struct roff_node *);
+static int md_pre_br(struct roff_node *);
+
+static void md_post_raw(struct roff_node *);
+static void md_post_word(struct roff_node *);
+static void md_post_pc(struct roff_node *);
+static void md_post_Bk(struct roff_node *);
+static void md_post_Bl(struct roff_node *);
+static void md_post_D1(struct roff_node *);
+static void md_post_En(struct roff_node *);
+static void md_post_Eo(struct roff_node *);
+static void md_post_Fa(struct roff_node *);
+static void md_post_Fd(struct roff_node *);
+static void md_post_Fl(struct roff_node *);
+static void md_post_Fn(struct roff_node *);
+static void md_post_Fo(struct roff_node *);
+static void md_post_In(struct roff_node *);
+static void md_post_It(struct roff_node *);
+static void md_post_Lb(struct roff_node *);
+static void md_post_Nm(struct roff_node *);
+static void md_post_Pf(struct roff_node *);
+static void md_post_Vt(struct roff_node *);
+static void md_post__T(struct roff_node *);
+
+static const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = {
+ { NULL, NULL, NULL, NULL, NULL }, /* Dd */
+ { NULL, NULL, NULL, NULL, NULL }, /* Dt */
+ { NULL, NULL, NULL, NULL, NULL }, /* Os */
+ { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */
+ { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */
+ { NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */
+ { md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */
+ { md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */
+ { md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ed */
+ { md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */
+ { NULL, NULL, NULL, NULL, NULL }, /* El */
+ { NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */
+ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */
+ { NULL, md_pre_An, NULL, NULL, NULL }, /* An */
+ { NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */
+ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */
+ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */
+ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */
+ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */
+ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */
+ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ex */
+ { NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */
+ { NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */
+ { NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */
+ { NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */
+ { NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */
+ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */
+ { NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */
+ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */
+ { md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */
+ { NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */
+ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */
+ { NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */
+ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */
+ { NULL, NULL, NULL, NULL, NULL }, /* Rv */
+ { NULL, NULL, NULL, NULL, NULL }, /* St */
+ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */
+ { NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */
+ { NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %A */
+ { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %D */
+ { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */
+ { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %N */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %O */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %P */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %R */
+ { NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %V */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ac */
+ { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */
+ { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */
+ { NULL, NULL, NULL, NULL, NULL }, /* At */
+ { NULL, NULL, NULL, NULL, NULL }, /* Bc */
+ { NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */
+ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */
+ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */
+ { NULL, NULL, NULL, NULL, NULL }, /* Bsx */
+ { NULL, NULL, NULL, NULL, NULL }, /* Bx */
+ { NULL, NULL, NULL, NULL, NULL }, /* Db */
+ { NULL, NULL, NULL, NULL, NULL }, /* Dc */
+ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */
+ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ec */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ef */
+ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */
+ { md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */
+ { NULL, NULL, NULL, NULL, NULL }, /* Fx */
+ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */
+ { NULL, md_pre_No, NULL, NULL, NULL }, /* No */
+ { NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */
+ { NULL, NULL, NULL, NULL, NULL }, /* Nx */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ox */
+ { NULL, NULL, NULL, NULL, NULL }, /* Pc */
+ { NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */
+ { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */
+ { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */
+ { NULL, NULL, NULL, NULL, NULL }, /* Qc */
+ { md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */
+ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */
+ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */
+ { NULL, NULL, NULL, NULL, NULL }, /* Re */
+ { md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */
+ { NULL, NULL, NULL, NULL, NULL }, /* Sc */
+ { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */
+ { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */
+ { NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */
+ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */
+ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */
+ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ux */
+ { NULL, NULL, NULL, NULL, NULL }, /* Xc */
+ { NULL, NULL, NULL, NULL, NULL }, /* Xo */
+ { NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */
+ { NULL, NULL, NULL, NULL, NULL }, /* Fc */
+ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */
+ { NULL, NULL, NULL, NULL, NULL }, /* Oc */
+ { NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ek */
+ { NULL, NULL, NULL, NULL, NULL }, /* Bt */
+ { NULL, NULL, NULL, NULL, NULL }, /* Hf */
+ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ud */
+ { NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */
+ { NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */
+ { NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */
+ { NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */
+ { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */
+ { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */
+ { NULL, NULL, NULL, NULL, NULL }, /* Brc */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %C */
+ { NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */
+ { md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */
+ { NULL, NULL, NULL, NULL, NULL }, /* Dx */
+ { NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */
+ { NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */
+ { NULL, NULL, NULL, NULL, NULL }, /* Ta */
+ { NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */
+};
+static const struct md_act *md_act(enum roff_tok);
+
+static int outflags;
+#define MD_spc (1 << 0) /* Blank character before next word. */
+#define MD_spc_force (1 << 1) /* Even before trailing punctuation. */
+#define MD_nonl (1 << 2) /* Prevent linebreak in markdown code. */
+#define MD_nl (1 << 3) /* Break markdown code line. */
+#define MD_br (1 << 4) /* Insert an output line break. */
+#define MD_sp (1 << 5) /* Insert a paragraph break. */
+#define MD_Sm (1 << 6) /* Horizontal spacing mode. */
+#define MD_Bk (1 << 7) /* Word keep mode. */
+#define MD_An_split (1 << 8) /* Author mode is "split". */
+#define MD_An_nosplit (1 << 9) /* Author mode is "nosplit". */
+
+static int escflags; /* Escape in generated markdown code: */
+#define ESC_BOL (1 << 0) /* "#*+-" near the beginning of a line. */
+#define ESC_NUM (1 << 1) /* "." after a leading number. */
+#define ESC_HYP (1 << 2) /* "(" immediately after "]". */
+#define ESC_SQU (1 << 4) /* "]" when "[" is open. */
+#define ESC_FON (1 << 5) /* "*" immediately after unrelated "*". */
+#define ESC_EOL (1 << 6) /* " " at the and of a line. */
+
+static int code_blocks, quote_blocks, list_blocks;
+static int outcount;
+
+
+static const struct md_act *
+md_act(enum roff_tok tok)
+{
+ assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
+ return md_acts + (tok - MDOC_Dd);
+}
+
+void
+markdown_mdoc(void *arg, const struct roff_meta *mdoc)
+{
+ outflags = MD_Sm;
+ md_word(mdoc->title);
+ if (mdoc->msec != NULL) {
+ outflags &= ~MD_spc;
+ md_word("(");
+ md_word(mdoc->msec);
+ md_word(")");
+ }
+ md_word("-");
+ md_word(mdoc->vol);
+ if (mdoc->arch != NULL) {
+ md_word("(");
+ md_word(mdoc->arch);
+ md_word(")");
+ }
+ outflags |= MD_sp;
+
+ md_nodelist(mdoc->first->child);
+
+ outflags |= MD_sp;
+ md_word(mdoc->os);
+ md_word("-");
+ md_word(mdoc->date);
+ putchar('\n');
+}
+
+static void
+md_nodelist(struct roff_node *n)
+{
+ while (n != NULL) {
+ md_node(n);
+ n = n->next;
+ }
+}
+
+static void
+md_node(struct roff_node *n)
+{
+ const struct md_act *act;
+ int cond, process_children;
+
+ if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
+ return;
+
+ if (outflags & MD_nonl)
+ outflags &= ~(MD_nl | MD_sp);
+ else if (outflags & MD_spc &&
+ n->flags & NODE_LINE &&
+ !roff_node_transparent(n))
+ outflags |= MD_nl;
+
+ act = NULL;
+ cond = 0;
+ process_children = 1;
+ n->flags &= ~NODE_ENDED;
+
+ if (n->type == ROFFT_TEXT) {
+ if (n->flags & NODE_DELIMC)
+ outflags &= ~(MD_spc | MD_spc_force);
+ else if (outflags & MD_Sm)
+ outflags |= MD_spc_force;
+ md_word(n->string);
+ if (n->flags & NODE_DELIMO)
+ outflags &= ~(MD_spc | MD_spc_force);
+ else if (outflags & MD_Sm)
+ outflags |= MD_spc;
+ } else if (n->tok < ROFF_MAX) {
+ switch (n->tok) {
+ case ROFF_br:
+ process_children = md_pre_br(n);
+ break;
+ case ROFF_sp:
+ process_children = md_pre_Pp(n);
+ break;
+ default:
+ process_children = 0;
+ break;
+ }
+ } else {
+ act = md_act(n->tok);
+ cond = act->cond == NULL || (*act->cond)(n);
+ if (cond && act->pre != NULL &&
+ (n->end == ENDBODY_NOT || n->child != NULL))
+ process_children = (*act->pre)(n);
+ }
+
+ if (process_children && n->child != NULL)
+ md_nodelist(n->child);
+
+ if (n->flags & NODE_ENDED)
+ return;
+
+ if (cond && act->post != NULL)
+ (*act->post)(n);
+
+ if (n->end != ENDBODY_NOT)
+ n->body->flags |= NODE_ENDED;
+}
+
+static const char *
+md_stack(char c)
+{
+ static char *stack;
+ static size_t sz;
+ static size_t cur;
+
+ switch (c) {
+ case '\0':
+ break;
+ case (char)-1:
+ assert(cur);
+ stack[--cur] = '\0';
+ break;
+ default:
+ if (cur + 1 >= sz) {
+ sz += 8;
+ stack = mandoc_realloc(stack, sz);
+ }
+ stack[cur] = c;
+ stack[++cur] = '\0';
+ break;
+ }
+ return stack == NULL ? "" : stack;
+}
+
+/*
+ * Handle vertical and horizontal spacing.
+ */
+static void
+md_preword(void)
+{
+ const char *cp;
+
+ /*
+ * If a list block is nested inside a code block or a blockquote,
+ * blank lines for paragraph breaks no longer work; instead,
+ * they terminate the list. Work around this markdown issue
+ * by using mere line breaks instead.
+ */
+
+ if (list_blocks && outflags & MD_sp) {
+ outflags &= ~MD_sp;
+ outflags |= MD_br;
+ }
+
+ /*
+ * End the old line if requested.
+ * Escape whitespace at the end of the markdown line
+ * such that it won't look like an output line break.
+ */
+
+ if (outflags & MD_sp)
+ putchar('\n');
+ else if (outflags & MD_br) {
+ putchar(' ');
+ putchar(' ');
+ } else if (outflags & MD_nl && escflags & ESC_EOL)
+ md_named("zwnj");
+
+ /* Start a new line if necessary. */
+
+ if (outflags & (MD_nl | MD_br | MD_sp)) {
+ putchar('\n');
+ for (cp = md_stack('\0'); *cp != '\0'; cp++) {
+ putchar(*cp);
+ if (*cp == '>')
+ putchar(' ');
+ }
+ outflags &= ~(MD_nl | MD_br | MD_sp);
+ escflags = ESC_BOL;
+ outcount = 0;
+
+ /* Handle horizontal spacing. */
+
+ } else if (outflags & MD_spc) {
+ if (outflags & MD_Bk)
+ fputs("&nbsp;", stdout);
+ else
+ putchar(' ');
+ escflags &= ~ESC_FON;
+ outcount++;
+ }
+
+ outflags &= ~(MD_spc_force | MD_nonl);
+ if (outflags & MD_Sm)
+ outflags |= MD_spc;
+ else
+ outflags &= ~MD_spc;
+}
+
+/*
+ * Print markdown syntax elements.
+ * Can also be used for constant strings when neither escaping
+ * nor delimiter handling is required.
+ */
+static void
+md_rawword(const char *s)
+{
+ md_preword();
+
+ if (*s == '\0')
+ return;
+
+ if (escflags & ESC_FON) {
+ escflags &= ~ESC_FON;
+ if (*s == '*' && !code_blocks)
+ fputs("&zwnj;", stdout);
+ }
+
+ while (*s != '\0') {
+ switch(*s) {
+ case '*':
+ if (s[1] == '\0')
+ escflags |= ESC_FON;
+ break;
+ case '[':
+ escflags |= ESC_SQU;
+ break;
+ case ']':
+ escflags |= ESC_HYP;
+ escflags &= ~ESC_SQU;
+ break;
+ default:
+ break;
+ }
+ md_char(*s++);
+ }
+ if (s[-1] == ' ')
+ escflags |= ESC_EOL;
+ else
+ escflags &= ~ESC_EOL;
+}
+
+/*
+ * Print text and mdoc(7) syntax elements.
+ */
+static void
+md_word(const char *s)
+{
+ const char *seq, *prevfont, *currfont, *nextfont;
+ char c;
+ int bs, sz, uc, breakline;
+
+ /* No spacing before closing delimiters. */
+ if (s[0] != '\0' && s[1] == '\0' &&
+ strchr("!),.:;?]", s[0]) != NULL &&
+ (outflags & MD_spc_force) == 0)
+ outflags &= ~MD_spc;
+
+ md_preword();
+
+ if (*s == '\0')
+ return;
+
+ /* No spacing after opening delimiters. */
+ if ((s[0] == '(' || s[0] == '[') && s[1] == '\0')
+ outflags &= ~MD_spc;
+
+ breakline = 0;
+ prevfont = currfont = "";
+ while ((c = *s++) != '\0') {
+ bs = 0;
+ switch(c) {
+ case ASCII_NBRSP:
+ if (code_blocks)
+ c = ' ';
+ else {
+ md_named("nbsp");
+ c = '\0';
+ }
+ break;
+ case ASCII_HYPH:
+ bs = escflags & ESC_BOL && !code_blocks;
+ c = '-';
+ break;
+ case ASCII_BREAK:
+ continue;
+ case '#':
+ case '+':
+ case '-':
+ bs = escflags & ESC_BOL && !code_blocks;
+ break;
+ case '(':
+ bs = escflags & ESC_HYP && !code_blocks;
+ break;
+ case ')':
+ bs = escflags & ESC_NUM && !code_blocks;
+ break;
+ case '*':
+ case '[':
+ case '_':
+ case '`':
+ bs = !code_blocks;
+ break;
+ case '.':
+ bs = escflags & ESC_NUM && !code_blocks;
+ break;
+ case '<':
+ if (code_blocks == 0) {
+ md_named("lt");
+ c = '\0';
+ }
+ break;
+ case '=':
+ if (escflags & ESC_BOL && !code_blocks) {
+ md_named("equals");
+ c = '\0';
+ }
+ break;
+ case '>':
+ if (code_blocks == 0) {
+ md_named("gt");
+ c = '\0';
+ }
+ break;
+ case '\\':
+ uc = 0;
+ nextfont = NULL;
+ switch (mandoc_escape(&s, &seq, &sz)) {
+ case ESCAPE_UNICODE:
+ uc = mchars_num2uc(seq + 1, sz - 1);
+ break;
+ case ESCAPE_NUMBERED:
+ uc = mchars_num2char(seq, sz);
+ break;
+ case ESCAPE_SPECIAL:
+ uc = mchars_spec2cp(seq, sz);
+ break;
+ case ESCAPE_UNDEF:
+ uc = *seq;
+ break;
+ case ESCAPE_DEVICE:
+ md_rawword("markdown");
+ continue;
+ case ESCAPE_FONTBOLD:
+ nextfont = "**";
+ break;
+ case ESCAPE_FONTITALIC:
+ nextfont = "*";
+ break;
+ case ESCAPE_FONTBI:
+ nextfont = "***";
+ break;
+ case ESCAPE_FONT:
+ case ESCAPE_FONTCW:
+ case ESCAPE_FONTROMAN:
+ nextfont = "";
+ break;
+ case ESCAPE_FONTPREV:
+ nextfont = prevfont;
+ break;
+ case ESCAPE_BREAK:
+ breakline = 1;
+ break;
+ case ESCAPE_NOSPACE:
+ case ESCAPE_SKIPCHAR:
+ case ESCAPE_OVERSTRIKE:
+ /* XXX not implemented */
+ /* FALLTHROUGH */
+ case ESCAPE_ERROR:
+ default:
+ break;
+ }
+ if (nextfont != NULL && !code_blocks) {
+ if (*currfont != '\0') {
+ outflags &= ~MD_spc;
+ md_rawword(currfont);
+ }
+ prevfont = currfont;
+ currfont = nextfont;
+ if (*currfont != '\0') {
+ outflags &= ~MD_spc;
+ md_rawword(currfont);
+ }
+ }
+ if (uc) {
+ if ((uc < 0x20 && uc != 0x09) ||
+ (uc > 0x7E && uc < 0xA0))
+ uc = 0xFFFD;
+ if (code_blocks) {
+ seq = mchars_uc2str(uc);
+ fputs(seq, stdout);
+ outcount += strlen(seq);
+ } else {
+ printf("&#%d;", uc);
+ outcount++;
+ }
+ escflags &= ~ESC_FON;
+ }
+ c = '\0';
+ break;
+ case ']':
+ bs = escflags & ESC_SQU && !code_blocks;
+ escflags |= ESC_HYP;
+ break;
+ default:
+ break;
+ }
+ if (bs)
+ putchar('\\');
+ md_char(c);
+ if (breakline &&
+ (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) {
+ printf(" \n");
+ breakline = 0;
+ while (*s == ' ' || *s == ASCII_NBRSP)
+ s++;
+ }
+ }
+ if (*currfont != '\0') {
+ outflags &= ~MD_spc;
+ md_rawword(currfont);
+ } else if (s[-2] == ' ')
+ escflags |= ESC_EOL;
+ else
+ escflags &= ~ESC_EOL;
+}
+
+/*
+ * Print a single HTML named character reference.
+ */
+static void
+md_named(const char *s)
+{
+ printf("&%s;", s);
+ escflags &= ~(ESC_FON | ESC_EOL);
+ outcount++;
+}
+
+/*
+ * Print a single raw character and maintain certain escape flags.
+ */
+static void
+md_char(unsigned char c)
+{
+ if (c != '\0') {
+ putchar(c);
+ if (c == '*')
+ escflags |= ESC_FON;
+ else
+ escflags &= ~ESC_FON;
+ outcount++;
+ }
+ if (c != ']')
+ escflags &= ~ESC_HYP;
+ if (c == ' ' || c == '\t' || c == '>')
+ return;
+ if (isdigit(c) == 0)
+ escflags &= ~ESC_NUM;
+ else if (escflags & ESC_BOL)
+ escflags |= ESC_NUM;
+ escflags &= ~ESC_BOL;
+}
+
+static int
+md_cond_head(struct roff_node *n)
+{
+ return n->type == ROFFT_HEAD;
+}
+
+static int
+md_cond_body(struct roff_node *n)
+{
+ return n->type == ROFFT_BODY;
+}
+
+static int
+md_pre_abort(struct roff_node *n)
+{
+ abort();
+}
+
+static int
+md_pre_raw(struct roff_node *n)
+{
+ const char *prefix;
+
+ if ((prefix = md_act(n->tok)->prefix) != NULL) {
+ md_rawword(prefix);
+ outflags &= ~MD_spc;
+ if (*prefix == '`')
+ code_blocks++;
+ }
+ return 1;
+}
+
+static void
+md_post_raw(struct roff_node *n)
+{
+ const char *suffix;
+
+ if ((suffix = md_act(n->tok)->suffix) != NULL) {
+ outflags &= ~(MD_spc | MD_nl);
+ md_rawword(suffix);
+ if (*suffix == '`')
+ code_blocks--;
+ }
+}
+
+static int
+md_pre_word(struct roff_node *n)
+{
+ const char *prefix;
+
+ if ((prefix = md_act(n->tok)->prefix) != NULL) {
+ md_word(prefix);
+ outflags &= ~MD_spc;
+ }
+ return 1;
+}
+
+static void
+md_post_word(struct roff_node *n)
+{
+ const char *suffix;
+
+ if ((suffix = md_act(n->tok)->suffix) != NULL) {
+ outflags &= ~(MD_spc | MD_nl);
+ md_word(suffix);
+ }
+}
+
+static void
+md_post_pc(struct roff_node *n)
+{
+ struct roff_node *nn;
+
+ md_post_raw(n);
+ if (n->parent->tok != MDOC_Rs)
+ return;
+
+ if ((nn = roff_node_next(n)) != NULL) {
+ md_word(",");
+ if (nn->tok == n->tok &&
+ (nn = roff_node_prev(n)) != NULL &&
+ nn->tok == n->tok)
+ md_word("and");
+ } else {
+ md_word(".");
+ outflags |= MD_nl;
+ }
+}
+
+static int
+md_pre_skip(struct roff_node *n)
+{
+ return 0;
+}
+
+static void
+md_pre_syn(struct roff_node *n)
+{
+ struct roff_node *np;
+
+ if ((n->flags & NODE_SYNPRETTY) == 0 ||
+ (np = roff_node_prev(n)) == NULL)
+ return;
+
+ if (np->tok == n->tok &&
+ n->tok != MDOC_Ft &&
+ n->tok != MDOC_Fo &&
+ n->tok != MDOC_Fn) {
+ outflags |= MD_br;
+ return;
+ }
+
+ switch (np->tok) {
+ case MDOC_Fd:
+ case MDOC_Fn:
+ case MDOC_Fo:
+ case MDOC_In:
+ case MDOC_Vt:
+ outflags |= MD_sp;
+ break;
+ case MDOC_Ft:
+ if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
+ outflags |= MD_sp;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ outflags |= MD_br;
+ break;
+ }
+}
+
+static int
+md_pre_An(struct roff_node *n)
+{
+ switch (n->norm->An.auth) {
+ case AUTH_split:
+ outflags &= ~MD_An_nosplit;
+ outflags |= MD_An_split;
+ return 0;
+ case AUTH_nosplit:
+ outflags &= ~MD_An_split;
+ outflags |= MD_An_nosplit;
+ return 0;
+ default:
+ if (outflags & MD_An_split)
+ outflags |= MD_br;
+ else if (n->sec == SEC_AUTHORS &&
+ ! (outflags & MD_An_nosplit))
+ outflags |= MD_An_split;
+ return 1;
+ }
+}
+
+static int
+md_pre_Ap(struct roff_node *n)
+{
+ outflags &= ~MD_spc;
+ md_word("'");
+ outflags &= ~MD_spc;
+ return 0;
+}
+
+static int
+md_pre_Bd(struct roff_node *n)
+{
+ switch (n->norm->Bd.type) {
+ case DISP_unfilled:
+ case DISP_literal:
+ return md_pre_Dl(n);
+ default:
+ return md_pre_D1(n);
+ }
+}
+
+static int
+md_pre_Bk(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ return 1;
+ case ROFFT_BODY:
+ outflags |= MD_Bk;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void
+md_post_Bk(struct roff_node *n)
+{
+ if (n->type == ROFFT_BODY)
+ outflags &= ~MD_Bk;
+}
+
+static int
+md_pre_Bl(struct roff_node *n)
+{
+ n->norm->Bl.count = 0;
+ if (n->norm->Bl.type == LIST_column)
+ md_pre_Dl(n);
+ outflags |= MD_sp;
+ return 1;
+}
+
+static void
+md_post_Bl(struct roff_node *n)
+{
+ n->norm->Bl.count = 0;
+ if (n->norm->Bl.type == LIST_column)
+ md_post_D1(n);
+ outflags |= MD_sp;
+}
+
+static int
+md_pre_D1(struct roff_node *n)
+{
+ /*
+ * Markdown blockquote syntax does not work inside code blocks.
+ * The best we can do is fall back to another nested code block.
+ */
+ if (code_blocks) {
+ md_stack('\t');
+ code_blocks++;
+ } else {
+ md_stack('>');
+ quote_blocks++;
+ }
+ outflags |= MD_sp;
+ return 1;
+}
+
+static void
+md_post_D1(struct roff_node *n)
+{
+ md_stack((char)-1);
+ if (code_blocks)
+ code_blocks--;
+ else
+ quote_blocks--;
+ outflags |= MD_sp;
+}
+
+static int
+md_pre_Dl(struct roff_node *n)
+{
+ /*
+ * Markdown code block syntax does not work inside blockquotes.
+ * The best we can do is fall back to another nested blockquote.
+ */
+ if (quote_blocks) {
+ md_stack('>');
+ quote_blocks++;
+ } else {
+ md_stack('\t');
+ code_blocks++;
+ }
+ outflags |= MD_sp;
+ return 1;
+}
+
+static int
+md_pre_En(struct roff_node *n)
+{
+ if (n->norm->Es == NULL ||
+ n->norm->Es->child == NULL)
+ return 1;
+
+ md_word(n->norm->Es->child->string);
+ outflags &= ~MD_spc;
+ return 1;
+}
+
+static void
+md_post_En(struct roff_node *n)
+{
+ if (n->norm->Es == NULL ||
+ n->norm->Es->child == NULL ||
+ n->norm->Es->child->next == NULL)
+ return;
+
+ outflags &= ~MD_spc;
+ md_word(n->norm->Es->child->next->string);
+}
+
+static int
+md_pre_Eo(struct roff_node *n)
+{
+ if (n->end == ENDBODY_NOT &&
+ n->parent->head->child == NULL &&
+ n->child != NULL &&
+ n->child->end != ENDBODY_NOT)
+ md_preword();
+ else if (n->end != ENDBODY_NOT ? n->child != NULL :
+ n->parent->head->child != NULL && (n->child != NULL ||
+ (n->parent->tail != NULL && n->parent->tail->child != NULL)))
+ outflags &= ~(MD_spc | MD_nl);
+ return 1;
+}
+
+static void
+md_post_Eo(struct roff_node *n)
+{
+ if (n->end != ENDBODY_NOT) {
+ outflags |= MD_spc;
+ return;
+ }
+
+ if (n->child == NULL && n->parent->head->child == NULL)
+ return;
+
+ if (n->parent->tail != NULL && n->parent->tail->child != NULL)
+ outflags &= ~MD_spc;
+ else
+ outflags |= MD_spc;
+}
+
+static int
+md_pre_Fa(struct roff_node *n)
+{
+ int am_Fa;
+
+ am_Fa = n->tok == MDOC_Fa;
+
+ if (am_Fa)
+ n = n->child;
+
+ while (n != NULL) {
+ md_rawword("*");
+ outflags &= ~MD_spc;
+ md_node(n);
+ outflags &= ~MD_spc;
+ md_rawword("*");
+ if ((n = n->next) != NULL)
+ md_word(",");
+ }
+ return 0;
+}
+
+static void
+md_post_Fa(struct roff_node *n)
+{
+ struct roff_node *nn;
+
+ if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
+ md_word(",");
+}
+
+static int
+md_pre_Fd(struct roff_node *n)
+{
+ md_pre_syn(n);
+ md_pre_raw(n);
+ return 1;
+}
+
+static void
+md_post_Fd(struct roff_node *n)
+{
+ md_post_raw(n);
+ outflags |= MD_br;
+}
+
+static void
+md_post_Fl(struct roff_node *n)
+{
+ struct roff_node *nn;
+
+ md_post_raw(n);
+ if (n->child == NULL && (nn = roff_node_next(n)) != NULL &&
+ nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)
+ outflags &= ~MD_spc;
+}
+
+static int
+md_pre_Fn(struct roff_node *n)
+{
+ md_pre_syn(n);
+
+ if ((n = n->child) == NULL)
+ return 0;
+
+ md_rawword("**");
+ outflags &= ~MD_spc;
+ md_node(n);
+ outflags &= ~MD_spc;
+ md_rawword("**");
+ outflags &= ~MD_spc;
+ md_word("(");
+
+ if ((n = n->next) != NULL)
+ md_pre_Fa(n);
+ return 0;
+}
+
+static void
+md_post_Fn(struct roff_node *n)
+{
+ md_word(")");
+ if (n->flags & NODE_SYNPRETTY) {
+ md_word(";");
+ outflags |= MD_sp;
+ }
+}
+
+static int
+md_pre_Fo(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ md_pre_syn(n);
+ break;
+ case ROFFT_HEAD:
+ if (n->child == NULL)
+ return 0;
+ md_pre_raw(n);
+ break;
+ case ROFFT_BODY:
+ outflags &= ~(MD_spc | MD_nl);
+ md_word("(");
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static void
+md_post_Fo(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_HEAD:
+ if (n->child != NULL)
+ md_post_raw(n);
+ break;
+ case ROFFT_BODY:
+ md_post_Fn(n);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+md_pre_In(struct roff_node *n)
+{
+ if (n->flags & NODE_SYNPRETTY) {
+ md_pre_syn(n);
+ md_rawword("**");
+ outflags &= ~MD_spc;
+ md_word("#include <");
+ } else {
+ md_word("<");
+ outflags &= ~MD_spc;
+ md_rawword("*");
+ }
+ outflags &= ~MD_spc;
+ return 1;
+}
+
+static void
+md_post_In(struct roff_node *n)
+{
+ if (n->flags & NODE_SYNPRETTY) {
+ outflags &= ~MD_spc;
+ md_rawword(">**");
+ outflags |= MD_nl;
+ } else {
+ outflags &= ~MD_spc;
+ md_rawword("*>");
+ }
+}
+
+static int
+md_pre_It(struct roff_node *n)
+{
+ struct roff_node *bln;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ return 1;
+
+ case ROFFT_HEAD:
+ bln = n->parent->parent;
+ if (bln->norm->Bl.comp == 0 &&
+ bln->norm->Bl.type != LIST_column)
+ outflags |= MD_sp;
+ outflags |= MD_nl;
+
+ switch (bln->norm->Bl.type) {
+ case LIST_item:
+ outflags |= MD_br;
+ return 0;
+ case LIST_inset:
+ case LIST_diag:
+ case LIST_ohang:
+ outflags |= MD_br;
+ return 1;
+ case LIST_tag:
+ case LIST_hang:
+ outflags |= MD_sp;
+ return 1;
+ case LIST_bullet:
+ md_rawword("*\t");
+ break;
+ case LIST_dash:
+ case LIST_hyphen:
+ md_rawword("-\t");
+ break;
+ case LIST_enum:
+ md_preword();
+ if (bln->norm->Bl.count < 99)
+ bln->norm->Bl.count++;
+ printf("%d.\t", bln->norm->Bl.count);
+ escflags &= ~ESC_FON;
+ break;
+ case LIST_column:
+ outflags |= MD_br;
+ return 0;
+ default:
+ return 0;
+ }
+ outflags &= ~MD_spc;
+ outflags |= MD_nonl;
+ outcount = 0;
+ md_stack('\t');
+ if (code_blocks || quote_blocks)
+ list_blocks++;
+ return 0;
+
+ case ROFFT_BODY:
+ bln = n->parent->parent;
+ switch (bln->norm->Bl.type) {
+ case LIST_ohang:
+ outflags |= MD_br;
+ break;
+ case LIST_tag:
+ case LIST_hang:
+ md_pre_D1(n);
+ break;
+ default:
+ break;
+ }
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static void
+md_post_It(struct roff_node *n)
+{
+ struct roff_node *bln;
+ int i, nc;
+
+ if (n->type != ROFFT_BODY)
+ return;
+
+ bln = n->parent->parent;
+ switch (bln->norm->Bl.type) {
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_hyphen:
+ case LIST_enum:
+ md_stack((char)-1);
+ if (code_blocks || quote_blocks)
+ list_blocks--;
+ break;
+ case LIST_tag:
+ case LIST_hang:
+ md_post_D1(n);
+ break;
+
+ case LIST_column:
+ if (n->next == NULL)
+ break;
+
+ /* Calculate the array index of the current column. */
+
+ i = 0;
+ while ((n = n->prev) != NULL && n->type != ROFFT_HEAD)
+ i++;
+
+ /*
+ * If a width was specified for this column,
+ * subtract what printed, and
+ * add the same spacing as in mdoc_term.c.
+ */
+
+ nc = bln->norm->Bl.ncols;
+ i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount +
+ (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1;
+ if (i < 1)
+ i = 1;
+ while (i-- > 0)
+ putchar(' ');
+
+ outflags &= ~MD_spc;
+ escflags &= ~ESC_FON;
+ outcount = 0;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+md_post_Lb(struct roff_node *n)
+{
+ if (n->sec == SEC_LIBRARY)
+ outflags |= MD_br;
+}
+
+static void
+md_uri(const char *s)
+{
+ while (*s != '\0') {
+ if (strchr("%()<>", *s) != NULL) {
+ printf("%%%2.2hhX", *s);
+ outcount += 3;
+ } else {
+ putchar(*s);
+ outcount++;
+ }
+ s++;
+ }
+}
+
+static int
+md_pre_Lk(struct roff_node *n)
+{
+ const struct roff_node *link, *descr, *punct;
+
+ if ((link = n->child) == NULL)
+ return 0;
+
+ /* Find beginning of trailing punctuation. */
+ punct = n->last;
+ while (punct != link && punct->flags & NODE_DELIMC)
+ punct = punct->prev;
+ punct = punct->next;
+
+ /* Link text. */
+ descr = link->next;
+ if (descr == punct)
+ descr = link; /* no text */
+ md_rawword("[");
+ outflags &= ~MD_spc;
+ do {
+ md_word(descr->string);
+ descr = descr->next;
+ } while (descr != punct);
+ outflags &= ~MD_spc;
+
+ /* Link target. */
+ md_rawword("](");
+ md_uri(link->string);
+ outflags &= ~MD_spc;
+ md_rawword(")");
+
+ /* Trailing punctuation. */
+ while (punct != NULL) {
+ md_word(punct->string);
+ punct = punct->next;
+ }
+ return 0;
+}
+
+static int
+md_pre_Mt(struct roff_node *n)
+{
+ const struct roff_node *nch;
+
+ md_rawword("[");
+ outflags &= ~MD_spc;
+ for (nch = n->child; nch != NULL; nch = nch->next)
+ md_word(nch->string);
+ outflags &= ~MD_spc;
+ md_rawword("](mailto:");
+ for (nch = n->child; nch != NULL; nch = nch->next) {
+ md_uri(nch->string);
+ if (nch->next != NULL) {
+ putchar(' ');
+ outcount++;
+ }
+ }
+ outflags &= ~MD_spc;
+ md_rawword(")");
+ return 0;
+}
+
+static int
+md_pre_Nd(struct roff_node *n)
+{
+ outflags &= ~MD_nl;
+ outflags |= MD_spc;
+ md_word("-");
+ return 1;
+}
+
+static int
+md_pre_Nm(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ outflags |= MD_Bk;
+ md_pre_syn(n);
+ break;
+ case ROFFT_HEAD:
+ case ROFFT_ELEM:
+ md_pre_raw(n);
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static void
+md_post_Nm(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ outflags &= ~MD_Bk;
+ break;
+ case ROFFT_HEAD:
+ case ROFFT_ELEM:
+ md_post_raw(n);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+md_pre_No(struct roff_node *n)
+{
+ outflags |= MD_spc_force;
+ return 1;
+}
+
+static int
+md_pre_Ns(struct roff_node *n)
+{
+ outflags &= ~MD_spc;
+ return 0;
+}
+
+static void
+md_post_Pf(struct roff_node *n)
+{
+ if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
+ outflags &= ~MD_spc;
+}
+
+static int
+md_pre_Pp(struct roff_node *n)
+{
+ outflags |= MD_sp;
+ return 0;
+}
+
+static int
+md_pre_Rs(struct roff_node *n)
+{
+ if (n->sec == SEC_SEE_ALSO)
+ outflags |= MD_sp;
+ return 1;
+}
+
+static int
+md_pre_Sh(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ if (n->sec == SEC_AUTHORS)
+ outflags &= ~(MD_An_split | MD_An_nosplit);
+ break;
+ case ROFFT_HEAD:
+ outflags |= MD_sp;
+ md_rawword(n->tok == MDOC_Sh ? "#" : "##");
+ break;
+ case ROFFT_BODY:
+ outflags |= MD_sp;
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static int
+md_pre_Sm(struct roff_node *n)
+{
+ if (n->child == NULL)
+ outflags ^= MD_Sm;
+ else if (strcmp("on", n->child->string) == 0)
+ outflags |= MD_Sm;
+ else
+ outflags &= ~MD_Sm;
+
+ if (outflags & MD_Sm)
+ outflags |= MD_spc;
+
+ return 0;
+}
+
+static int
+md_pre_Vt(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ md_pre_syn(n);
+ return 1;
+ case ROFFT_BODY:
+ case ROFFT_ELEM:
+ md_pre_raw(n);
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void
+md_post_Vt(struct roff_node *n)
+{
+ switch (n->type) {
+ case ROFFT_BODY:
+ case ROFFT_ELEM:
+ md_post_raw(n);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+md_pre_Xr(struct roff_node *n)
+{
+ n = n->child;
+ if (n == NULL)
+ return 0;
+ md_node(n);
+ n = n->next;
+ if (n == NULL)
+ return 0;
+ outflags &= ~MD_spc;
+ md_word("(");
+ md_node(n);
+ md_word(")");
+ return 0;
+}
+
+static int
+md_pre__T(struct roff_node *n)
+{
+ if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
+ md_word("\"");
+ else
+ md_rawword("*");
+ outflags &= ~MD_spc;
+ return 1;
+}
+
+static void
+md_post__T(struct roff_node *n)
+{
+ outflags &= ~MD_spc;
+ if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
+ md_word("\"");
+ else
+ md_rawword("*");
+ md_post_pc(n);
+}
+
+static int
+md_pre_br(struct roff_node *n)
+{
+ outflags |= MD_br;
+ return 0;
+}
diff --git a/usr.bin/mandoc/mdoc_state.c b/usr.bin/mandoc/mdoc_state.c
new file mode 100644
index 0000000..954f709
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_state.c
@@ -0,0 +1,254 @@
+/* $OpenBSD: mdoc_state.c,v 1.16 2020/01/19 17:59:01 schwarze Exp $ */
+/*
+ * Copyright (c) 2014, 2015, 2017, 2018 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libmdoc.h"
+
+#define STATE_ARGS struct roff_man *mdoc, struct roff_node *n
+
+typedef void (*state_handler)(STATE_ARGS);
+
+static void state_bl(STATE_ARGS);
+static void state_sh(STATE_ARGS);
+static void state_sm(STATE_ARGS);
+
+static const state_handler state_handlers[MDOC_MAX - MDOC_Dd] = {
+ NULL, /* Dd */
+ NULL, /* Dt */
+ NULL, /* Os */
+ state_sh, /* Sh */
+ NULL, /* Ss */
+ NULL, /* Pp */
+ NULL, /* D1 */
+ NULL, /* Dl */
+ NULL, /* Bd */
+ NULL, /* Ed */
+ state_bl, /* Bl */
+ NULL, /* El */
+ NULL, /* It */
+ NULL, /* Ad */
+ NULL, /* An */
+ NULL, /* Ap */
+ NULL, /* Ar */
+ NULL, /* Cd */
+ NULL, /* Cm */
+ NULL, /* Dv */
+ NULL, /* Er */
+ NULL, /* Ev */
+ NULL, /* Ex */
+ NULL, /* Fa */
+ NULL, /* Fd */
+ NULL, /* Fl */
+ NULL, /* Fn */
+ NULL, /* Ft */
+ NULL, /* Ic */
+ NULL, /* In */
+ NULL, /* Li */
+ NULL, /* Nd */
+ NULL, /* Nm */
+ NULL, /* Op */
+ NULL, /* Ot */
+ NULL, /* Pa */
+ NULL, /* Rv */
+ NULL, /* St */
+ NULL, /* Va */
+ NULL, /* Vt */
+ NULL, /* Xr */
+ NULL, /* %A */
+ NULL, /* %B */
+ NULL, /* %D */
+ NULL, /* %I */
+ NULL, /* %J */
+ NULL, /* %N */
+ NULL, /* %O */
+ NULL, /* %P */
+ NULL, /* %R */
+ NULL, /* %T */
+ NULL, /* %V */
+ NULL, /* Ac */
+ NULL, /* Ao */
+ NULL, /* Aq */
+ NULL, /* At */
+ NULL, /* Bc */
+ NULL, /* Bf */
+ NULL, /* Bo */
+ NULL, /* Bq */
+ NULL, /* Bsx */
+ NULL, /* Bx */
+ NULL, /* Db */
+ NULL, /* Dc */
+ NULL, /* Do */
+ NULL, /* Dq */
+ NULL, /* Ec */
+ NULL, /* Ef */
+ NULL, /* Em */
+ NULL, /* Eo */
+ NULL, /* Fx */
+ NULL, /* Ms */
+ NULL, /* No */
+ NULL, /* Ns */
+ NULL, /* Nx */
+ NULL, /* Ox */
+ NULL, /* Pc */
+ NULL, /* Pf */
+ NULL, /* Po */
+ NULL, /* Pq */
+ NULL, /* Qc */
+ NULL, /* Ql */
+ NULL, /* Qo */
+ NULL, /* Qq */
+ NULL, /* Re */
+ NULL, /* Rs */
+ NULL, /* Sc */
+ NULL, /* So */
+ NULL, /* Sq */
+ state_sm, /* Sm */
+ NULL, /* Sx */
+ NULL, /* Sy */
+ NULL, /* Tn */
+ NULL, /* Ux */
+ NULL, /* Xc */
+ NULL, /* Xo */
+ NULL, /* Fo */
+ NULL, /* Fc */
+ NULL, /* Oo */
+ NULL, /* Oc */
+ NULL, /* Bk */
+ NULL, /* Ek */
+ NULL, /* Bt */
+ NULL, /* Hf */
+ NULL, /* Fr */
+ NULL, /* Ud */
+ NULL, /* Lb */
+ NULL, /* Lp */
+ NULL, /* Lk */
+ NULL, /* Mt */
+ NULL, /* Brq */
+ NULL, /* Bro */
+ NULL, /* Brc */
+ NULL, /* %C */
+ NULL, /* Es */
+ NULL, /* En */
+ NULL, /* Dx */
+ NULL, /* %Q */
+ NULL, /* %U */
+ NULL, /* Ta */
+ NULL, /* Tg */
+};
+
+
+void
+mdoc_state(struct roff_man *mdoc, struct roff_node *n)
+{
+ state_handler handler;
+
+ if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX)
+ return;
+
+ assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
+ if ((mdoc_macro(n->tok)->flags & MDOC_PROLOGUE) == 0)
+ mdoc->flags |= MDOC_PBODY;
+
+ handler = state_handlers[n->tok - MDOC_Dd];
+ if (*handler)
+ (*handler)(mdoc, n);
+}
+
+static void
+state_bl(STATE_ARGS)
+{
+ struct mdoc_arg *args;
+ size_t i;
+
+ if (n->type != ROFFT_HEAD || n->parent->args == NULL)
+ return;
+
+ args = n->parent->args;
+ for (i = 0; i < args->argc; i++) {
+ switch(args->argv[i].arg) {
+ case MDOC_Diag:
+ n->norm->Bl.type = LIST_diag;
+ return;
+ case MDOC_Column:
+ n->norm->Bl.type = LIST_column;
+ return;
+ default:
+ break;
+ }
+ }
+}
+
+static void
+state_sh(STATE_ARGS)
+{
+ struct roff_node *nch;
+ char *secname;
+
+ if (n->type != ROFFT_HEAD)
+ return;
+
+ if ( ! (n->flags & NODE_VALID)) {
+ secname = NULL;
+ deroff(&secname, n);
+
+ /*
+ * Set the section attribute for the BLOCK, HEAD,
+ * and HEAD children; the latter can only be TEXT
+ * nodes, so no recursion is needed. For other
+ * nodes, including the .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.
+ */
+
+ n->sec = n->parent->sec = secname == NULL ?
+ SEC_CUSTOM : mdoc_a2sec(secname);
+ for (nch = n->child; nch != NULL; nch = nch->next)
+ nch->sec = n->sec;
+ free(secname);
+ }
+
+ if ((mdoc->lastsec = n->sec) == SEC_SYNOPSIS) {
+ roff_setreg(mdoc->roff, "nS", 1, '=');
+ mdoc->flags |= MDOC_SYNOPSIS;
+ } else {
+ roff_setreg(mdoc->roff, "nS", 0, '=');
+ mdoc->flags &= ~MDOC_SYNOPSIS;
+ }
+}
+
+static void
+state_sm(STATE_ARGS)
+{
+
+ if (n->child == NULL)
+ mdoc->flags ^= MDOC_SMOFF;
+ else if ( ! strcmp(n->child->string, "on"))
+ mdoc->flags &= ~MDOC_SMOFF;
+ else if ( ! strcmp(n->child->string, "off"))
+ mdoc->flags |= MDOC_SMOFF;
+}
diff --git a/usr.bin/mandoc/mdoc_term.c b/usr.bin/mandoc/mdoc_term.c
new file mode 100644
index 0000000..6362a21
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_term.c
@@ -0,0 +1,1962 @@
+/* $OpenBSD: mdoc_term.c,v 1.279 2020/04/06 09:55:49 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2012-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * 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 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.
+ *
+ * Plain text formatter for mdoc(7), used by mandoc(1)
+ * for ASCII, UTF-8, PostScript, and PDF output.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "out.h"
+#include "term.h"
+#include "term_tag.h"
+#include "main.h"
+
+struct termpair {
+ struct termpair *ppair;
+ int count;
+};
+
+#define DECL_ARGS struct termp *p, \
+ struct termpair *pair, \
+ const struct roff_meta *meta, \
+ struct roff_node *n
+
+struct mdoc_term_act {
+ int (*pre)(DECL_ARGS);
+ void (*post)(DECL_ARGS);
+};
+
+static int a2width(const struct termp *, const char *);
+
+static void print_bvspace(struct termp *,
+ struct roff_node *, struct roff_node *);
+static void print_mdoc_node(DECL_ARGS);
+static void print_mdoc_nodelist(DECL_ARGS);
+static void print_mdoc_head(struct termp *, const struct roff_meta *);
+static void print_mdoc_foot(struct termp *, const struct roff_meta *);
+static void synopsis_pre(struct termp *, struct roff_node *);
+
+static void termp____post(DECL_ARGS);
+static void termp__t_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_eo_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 void termp_xx_post(DECL_ARGS);
+
+static int termp__a_pre(DECL_ARGS);
+static int termp__t_pre(DECL_ARGS);
+static int termp_abort_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_d1_pre(DECL_ARGS);
+static int termp_eo_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_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_sh_pre(DECL_ARGS);
+static int termp_skip_pre(DECL_ARGS);
+static int termp_sm_pre(DECL_ARGS);
+static int termp_pp_pre(DECL_ARGS);
+static int termp_ss_pre(DECL_ARGS);
+static int termp_under_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 mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = {
+ { NULL, NULL }, /* Dd */
+ { NULL, NULL }, /* Dt */
+ { NULL, NULL }, /* Os */
+ { termp_sh_pre, termp_sh_post }, /* Sh */
+ { termp_ss_pre, termp_ss_post }, /* Ss */
+ { termp_pp_pre, NULL }, /* Pp */
+ { termp_d1_pre, termp_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, NULL }, /* An */
+ { termp_ap_pre, NULL }, /* Ap */
+ { termp_under_pre, NULL }, /* Ar */
+ { termp_fd_pre, NULL }, /* Cd */
+ { termp_bold_pre, NULL }, /* Cm */
+ { termp_li_pre, 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_abort_pre, NULL }, /* Ot */
+ { termp_under_pre, NULL }, /* Pa */
+ { termp_ex_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, termp_xx_post }, /* Bsx */
+ { NULL, NULL }, /* Bx */
+ { termp_skip_pre, 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_eo_pre, termp_eo_post }, /* Eo */
+ { termp_xx_pre, termp_xx_post }, /* Fx */
+ { termp_bold_pre, NULL }, /* Ms */
+ { termp_li_pre, NULL }, /* No */
+ { termp_ns_pre, NULL }, /* Ns */
+ { termp_xx_pre, termp_xx_post }, /* Nx */
+ { termp_xx_pre, termp_xx_post }, /* 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, termp_xx_post }, /* 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 */
+ { NULL, NULL }, /* Bt */
+ { NULL, NULL }, /* Hf */
+ { termp_under_pre, NULL }, /* Fr */
+ { NULL, NULL }, /* Ud */
+ { NULL, termp_lb_post }, /* Lb */
+ { termp_abort_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_skip_pre, NULL }, /* Es */
+ { termp_quote_pre, termp_quote_post }, /* En */
+ { termp_xx_pre, termp_xx_post }, /* Dx */
+ { NULL, termp____post }, /* %Q */
+ { NULL, termp____post }, /* %U */
+ { NULL, NULL }, /* Ta */
+ { termp_skip_pre, NULL }, /* Tg */
+};
+
+
+void
+terminal_mdoc(void *arg, const struct roff_meta *mdoc)
+{
+ struct roff_node *n, *nn;
+ struct termp *p;
+ size_t save_defindent;
+
+ p = (struct termp *)arg;
+ p->tcol->rmargin = p->maxrmargin = p->defrmargin;
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, ".5i");
+
+ n = mdoc->first->child;
+ if (p->synopsisonly) {
+ for (nn = NULL; n != NULL; n = n->next) {
+ if (n->tok != MDOC_Sh)
+ continue;
+ if (n->sec == SEC_SYNOPSIS)
+ break;
+ if (nn == NULL && n->sec == SEC_NAME)
+ nn = n;
+ }
+ if (n == NULL)
+ n = nn;
+ p->flags |= TERMP_NOSPACE;
+ if (n != NULL && (n = n->child->next->child) != NULL)
+ print_mdoc_nodelist(p, NULL, mdoc, n);
+ term_newln(p);
+ } else {
+ save_defindent = p->defindent;
+ if (p->defindent == 0)
+ p->defindent = 5;
+ term_begin(p, print_mdoc_head, print_mdoc_foot, mdoc);
+ while (n != NULL &&
+ (n->type == ROFFT_COMMENT ||
+ n->flags & NODE_NOPRT))
+ n = n->next;
+ if (n != NULL) {
+ if (n->tok != MDOC_Sh)
+ term_vspace(p);
+ print_mdoc_nodelist(p, NULL, mdoc, n);
+ }
+ term_end(p);
+ p->defindent = save_defindent;
+ }
+}
+
+static void
+print_mdoc_nodelist(DECL_ARGS)
+{
+ while (n != NULL) {
+ print_mdoc_node(p, pair, meta, n);
+ n = n->next;
+ }
+}
+
+static void
+print_mdoc_node(DECL_ARGS)
+{
+ const struct mdoc_term_act *act;
+ struct termpair npair;
+ size_t offset, rmargin;
+ int chld;
+
+ /*
+ * In no-fill mode, break the output line at the beginning
+ * of new input lines except after \c, and nowhere else.
+ */
+
+ if (n->flags & NODE_NOFILL) {
+ if (n->flags & NODE_LINE &&
+ (p->flags & TERMP_NONEWLINE) == 0)
+ term_newln(p);
+ p->flags |= TERMP_BRNEVER;
+ } else
+ p->flags &= ~TERMP_BRNEVER;
+
+ if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
+ return;
+
+ chld = 1;
+ offset = p->tcol->offset;
+ rmargin = p->tcol->rmargin;
+ n->flags &= ~NODE_ENDED;
+ n->prev_font = p->fonti;
+
+ memset(&npair, 0, sizeof(struct termpair));
+ npair.ppair = pair;
+
+ if (n->flags & NODE_ID && n->tok != MDOC_Pp &&
+ (n->tok != MDOC_It || n->type != ROFFT_BLOCK))
+ term_tag_write(n, p->line);
+
+ /*
+ * Keeps only work until the end of a line. If a keep was
+ * invoked in a prior line, revert it to PREKEEP.
+ */
+
+ if (p->flags & TERMP_KEEP && n->flags & NODE_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.
+ */
+
+ act = NULL;
+ switch (n->type) {
+ case ROFFT_TEXT:
+ if (n->flags & NODE_LINE) {
+ switch (*n->string) {
+ case '\0':
+ if (p->flags & TERMP_NONEWLINE)
+ term_newln(p);
+ else
+ term_vspace(p);
+ return;
+ case ' ':
+ if ((p->flags & TERMP_NONEWLINE) == 0)
+ term_newln(p);
+ break;
+ default:
+ break;
+ }
+ }
+ if (NODE_DELIMC & n->flags)
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, n->string);
+ if (NODE_DELIMO & n->flags)
+ p->flags |= TERMP_NOSPACE;
+ break;
+ case ROFFT_EQN:
+ if ( ! (n->flags & NODE_LINE))
+ p->flags |= TERMP_NOSPACE;
+ term_eqn(p, n->eqn);
+ if (n->next != NULL && ! (n->next->flags & NODE_LINE))
+ p->flags |= TERMP_NOSPACE;
+ break;
+ case ROFFT_TBL:
+ if (p->tbl.cols == NULL)
+ term_newln(p);
+ term_tbl(p, n->span);
+ break;
+ default:
+ if (n->tok < ROFF_MAX) {
+ roff_term_pre(p, n);
+ return;
+ }
+ assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
+ act = mdoc_term_acts + (n->tok - MDOC_Dd);
+ if (act->pre != NULL &&
+ (n->end == ENDBODY_NOT || n->child != NULL))
+ chld = (*act->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->body)->prev_font);
+
+ switch (n->type) {
+ case ROFFT_TEXT:
+ break;
+ case ROFFT_TBL:
+ break;
+ case ROFFT_EQN:
+ break;
+ default:
+ if (act->post == NULL || n->flags & NODE_ENDED)
+ break;
+ (void)(*act->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->body->flags |= NODE_ENDED;
+ break;
+ }
+
+ if (NODE_EOS & n->flags)
+ p->flags |= TERMP_SENTENCE;
+
+ if (n->type != ROFFT_TEXT)
+ p->tcol->offset = offset;
+ p->tcol->rmargin = rmargin;
+}
+
+static void
+print_mdoc_foot(struct termp *p, const struct roff_meta *meta)
+{
+ size_t sz;
+
+ 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->tcol->offset = 0;
+ sz = term_strlen(p, meta->date);
+ p->tcol->rmargin = p->maxrmargin > sz ?
+ (p->maxrmargin + term_len(p, 1) - sz) / 2 : 0;
+ p->trailspace = 1;
+ p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+
+ term_word(p, meta->os);
+ term_flushln(p);
+
+ p->tcol->offset = p->tcol->rmargin;
+ sz = term_strlen(p, meta->os);
+ p->tcol->rmargin = p->maxrmargin > sz ? p->maxrmargin - sz : 0;
+ p->flags |= TERMP_NOSPACE;
+
+ term_word(p, meta->date);
+ term_flushln(p);
+
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~TERMP_NOBREAK;
+ p->flags |= TERMP_NOSPACE;
+
+ term_word(p, meta->os);
+ term_flushln(p);
+
+ p->tcol->offset = 0;
+ p->tcol->rmargin = p->maxrmargin;
+ p->flags = 0;
+}
+
+static void
+print_mdoc_head(struct termp *p, const struct roff_meta *meta)
+{
+ char *volume, *title;
+ size_t vollen, titlen;
+
+ /*
+ * The header is strange. It has three components, which are
+ * really two with the first duplicated. It goes like this:
+ *
+ * IDENTIFIER TITLE IDENTIFIER
+ *
+ * The IDENTIFIER is NAME(SECTION), which is the command-name
+ * (if given, or "unknown" if not) followed by the manual page
+ * section. These are given in `Dt'. The TITLE is a free-form
+ * string depending on the manual volume. If not specified, it
+ * switches on the manual section.
+ */
+
+ assert(meta->vol);
+ 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->tcol->offset = 0;
+ p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
+ (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
+ vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
+
+ term_word(p, title);
+ term_flushln(p);
+
+ p->flags |= TERMP_NOSPACE;
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->tcol->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->tcol->rmargin + titlen <= p->maxrmargin) {
+ p->flags |= TERMP_NOSPACE;
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin;
+ term_word(p, title);
+ term_flushln(p);
+ }
+
+ p->flags &= ~TERMP_NOSPACE;
+ p->tcol->offset = 0;
+ p->tcol->rmargin = p->maxrmargin;
+ free(title);
+ free(volume);
+}
+
+static int
+a2width(const struct termp *p, const char *v)
+{
+ struct roffsu su;
+ const char *end;
+
+ end = a2roffsu(v, &su, SCALE_MAX);
+ if (end == NULL || *end != '\0') {
+ SCALE_HS_INIT(&su, term_strlen(p, v));
+ su.scale /= term_strlen(p, "0");
+ }
+ return term_hen(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, struct roff_node *bl, struct roff_node *n)
+{
+ struct roff_node *nn;
+
+ term_newln(p);
+
+ if ((bl->tok == MDOC_Bd && bl->norm->Bd.comp) ||
+ (bl->tok == MDOC_Bl && bl->norm->Bl.comp))
+ return;
+
+ /* Do not vspace directly after Ss/Sh. */
+
+ nn = n;
+ while (roff_node_prev(nn) == NULL) {
+ do {
+ nn = nn->parent;
+ if (nn->type == ROFFT_ROOT)
+ return;
+ } while (nn->type != ROFFT_BLOCK);
+ if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
+ return;
+ if (nn->tok == MDOC_It &&
+ nn->parent->parent->norm->Bl.type != LIST_item)
+ break;
+ }
+
+ /*
+ * No vertical space after:
+ * items in .Bl -column
+ * items without a body in .Bl -diag
+ */
+
+ if (bl->tok != MDOC_Bl ||
+ n->prev == NULL || n->prev->tok != MDOC_It ||
+ (bl->norm->Bl.type != LIST_column &&
+ (bl->norm->Bl.type != LIST_diag ||
+ n->prev->body->child != NULL)))
+ term_vspace(p);
+}
+
+
+static int
+termp_it_pre(DECL_ARGS)
+{
+ struct roffsu su;
+ char buf[24];
+ const struct roff_node *bl, *nn;
+ size_t ncols, dcol;
+ int i, offset, width;
+ enum mdoc_list type;
+
+ if (n->type == ROFFT_BLOCK) {
+ print_bvspace(p, n->parent->parent, n);
+ if (n->flags & NODE_ID)
+ term_tag_write(n, p->line);
+ return 1;
+ }
+
+ bl = n->parent->parent->parent;
+ type = bl->norm->Bl.type;
+
+ /*
+ * Defaults for specific list types.
+ */
+
+ switch (type) {
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_hyphen:
+ case LIST_enum:
+ width = term_len(p, 2);
+ break;
+ case LIST_hang:
+ case LIST_tag:
+ width = term_len(p, 8);
+ break;
+ case LIST_column:
+ width = term_len(p, 10);
+ break;
+ default:
+ width = 0;
+ break;
+ }
+ offset = 0;
+
+ /*
+ * 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.
+ */
+
+ if (bl->norm->Bl.offs != NULL) {
+ offset = a2width(p, bl->norm->Bl.offs);
+ if (offset < 0 && (size_t)(-offset) > p->tcol->offset)
+ offset = -p->tcol->offset;
+ else if (offset > SHRT_MAX)
+ offset = 0;
+ }
+
+ switch (type) {
+ case LIST_column:
+ if (n->type == ROFFT_HEAD)
+ 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 ROFFT_BODY,
+ * so we stop at the ROFFT_HEAD (nn->prev == NULL).
+ */
+
+ for (i = 0, nn = n->prev;
+ nn->prev && i < (int)ncols;
+ nn = nn->prev, i++) {
+ SCALE_HS_INIT(&su,
+ term_strlen(p, bl->norm->Bl.cols[i]));
+ su.scale /= term_strlen(p, "0");
+ offset += term_hen(p, &su) + dcol;
+ }
+
+ /*
+ * 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.
+ */
+ SCALE_HS_INIT(&su, term_strlen(p, bl->norm->Bl.cols[i]));
+ su.scale /= term_strlen(p, "0");
+ width = term_hen(p, &su) + 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.
+ */
+ width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
+ if (width < 0 && (size_t)(-width) > p->tcol->offset)
+ width = -p->tcol->offset;
+ else if (width > SHRT_MAX)
+ width = 0;
+ break;
+ }
+
+ /*
+ * Whitespace control. Inset bodies need an initial space,
+ * while diagonal bodies need two.
+ */
+
+ p->flags |= TERMP_NOSPACE;
+
+ switch (type) {
+ case LIST_diag:
+ if (n->type == ROFFT_BODY)
+ term_word(p, "\\ \\ ");
+ break;
+ case LIST_inset:
+ if (n->type == ROFFT_BODY && n->parent->head->child != NULL)
+ term_word(p, "\\ ");
+ break;
+ default:
+ break;
+ }
+
+ p->flags |= TERMP_NOSPACE;
+
+ switch (type) {
+ case LIST_diag:
+ if (n->type == ROFFT_HEAD)
+ 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:
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_hyphen:
+ if (n->type == ROFFT_HEAD) {
+ p->flags |= TERMP_NOBREAK | TERMP_HANG;
+ p->trailspace = 1;
+ } else if (width <= (int)term_len(p, 2))
+ p->flags |= TERMP_NOPAD;
+ break;
+ case LIST_hang:
+ if (n->type != ROFFT_HEAD)
+ break;
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
+ p->trailspace = 1;
+ break;
+ case LIST_tag:
+ if (n->type != ROFFT_HEAD)
+ break;
+
+ p->flags |= TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND;
+ p->trailspace = 2;
+
+ if (NULL == n->next || NULL == n->next->child)
+ p->flags |= TERMP_HANG;
+ break;
+ case LIST_column:
+ if (n->type == ROFFT_HEAD)
+ 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 (n->type != ROFFT_HEAD)
+ 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->tcol->offset += offset;
+
+ switch (type) {
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_enum:
+ case LIST_hyphen:
+ case LIST_hang:
+ case LIST_tag:
+ if (n->type == ROFFT_HEAD)
+ p->tcol->rmargin = p->tcol->offset + width;
+ else
+ p->tcol->offset += width;
+ break;
+ case LIST_column:
+ assert(width);
+ p->tcol->rmargin = p->tcol->offset + width;
+ /*
+ * XXX - this behaviour is not documented: the
+ * right-most column is filled to the right margin.
+ */
+ if (n->type == ROFFT_HEAD)
+ break;
+ if (n->next == NULL && p->tcol->rmargin < p->maxrmargin)
+ p->tcol->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 (n->type == ROFFT_HEAD)
+ switch (type) {
+ case LIST_bullet:
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, "\\[bu]");
+ term_fontpop(p);
+ break;
+ case LIST_dash:
+ case LIST_hyphen:
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, "-");
+ 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:
+ case LIST_item:
+ case LIST_dash:
+ case LIST_hyphen:
+ case LIST_enum:
+ if (n->type == ROFFT_HEAD)
+ return 0;
+ break;
+ case LIST_column:
+ if (n->type == ROFFT_HEAD)
+ return 0;
+ p->minbl = 0;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static void
+termp_it_post(DECL_ARGS)
+{
+ enum mdoc_list type;
+
+ if (n->type == ROFFT_BLOCK)
+ return;
+
+ type = n->parent->parent->parent->norm->Bl.type;
+
+ switch (type) {
+ case LIST_item:
+ case LIST_diag:
+ case LIST_inset:
+ if (n->type == ROFFT_BODY)
+ term_newln(p);
+ break;
+ case LIST_column:
+ if (n->type == ROFFT_BODY)
+ 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_BRTRSP | TERMP_BRIND | TERMP_HANG);
+ p->trailspace = 0;
+}
+
+static int
+termp_nm_pre(DECL_ARGS)
+{
+ const char *cp;
+
+ if (n->type == ROFFT_BLOCK) {
+ p->flags |= TERMP_PREKEEP;
+ return 1;
+ }
+
+ if (n->type == ROFFT_BODY) {
+ if (n->child == NULL)
+ return 0;
+ p->flags |= TERMP_NOSPACE;
+ cp = NULL;
+ if (n->prev->child != NULL)
+ cp = n->prev->child->string;
+ if (cp == NULL)
+ cp = meta->name;
+ if (cp == NULL)
+ p->tcol->offset += term_len(p, 6);
+ else
+ p->tcol->offset += term_len(p, 1) +
+ term_strlen(p, cp);
+ return 1;
+ }
+
+ if (n->child == NULL)
+ return 0;
+
+ if (n->type == ROFFT_HEAD)
+ synopsis_pre(p, n->parent);
+
+ if (n->type == ROFFT_HEAD &&
+ n->next != NULL && n->next->child != NULL) {
+ p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND;
+ p->trailspace = 1;
+ p->tcol->rmargin = p->tcol->offset + term_len(p, 1);
+ if (n->child == NULL)
+ p->tcol->rmargin += term_strlen(p, meta->name);
+ else if (n->child->type == ROFFT_TEXT) {
+ p->tcol->rmargin += term_strlen(p, n->child->string);
+ if (n->child->next != NULL)
+ p->flags |= TERMP_HANG;
+ } else {
+ p->tcol->rmargin += term_len(p, 5);
+ p->flags |= TERMP_HANG;
+ }
+ }
+ return termp_bold_pre(p, pair, meta, n);
+}
+
+static void
+termp_nm_post(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
+ break;
+ case ROFFT_HEAD:
+ if (n->next == NULL || n->next->child == NULL)
+ break;
+ term_flushln(p);
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
+ p->trailspace = 0;
+ break;
+ case ROFFT_BODY:
+ if (n->child != NULL)
+ term_flushln(p);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+termp_fl_pre(DECL_ARGS)
+{
+ struct roff_node *nn;
+
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, "\\-");
+
+ if (n->child != NULL ||
+ ((nn = roff_node_next(n)) != NULL &&
+ nn->type != ROFFT_TEXT &&
+ (nn->flags & NODE_LINE) == 0))
+ p->flags |= TERMP_NOSPACE;
+
+ return 1;
+}
+
+static int
+termp__a_pre(DECL_ARGS)
+{
+ struct roff_node *nn;
+
+ if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A &&
+ ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A))
+ term_word(p, "and");
+
+ return 1;
+}
+
+static int
+termp_an_pre(DECL_ARGS)
+{
+
+ if (n->norm->An.auth == AUTH_split) {
+ p->flags &= ~TERMP_NOSPLIT;
+ p->flags |= TERMP_SPLIT;
+ return 0;
+ }
+ if (n->norm->An.auth == AUTH_nosplit) {
+ p->flags &= ~TERMP_SPLIT;
+ p->flags |= TERMP_NOSPLIT;
+ return 0;
+ }
+
+ if (p->flags & TERMP_SPLIT)
+ term_newln(p);
+
+ if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT))
+ p->flags |= TERMP_SPLIT;
+
+ return 1;
+}
+
+static int
+termp_ns_pre(DECL_ARGS)
+{
+
+ if ( ! (NODE_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 (n->type == ROFFT_BLOCK && roff_node_prev(n) != NULL)
+ term_vspace(p);
+ return 1;
+}
+
+static int
+termp_ex_pre(DECL_ARGS)
+{
+ term_newln(p);
+ return 1;
+}
+
+static int
+termp_nd_pre(DECL_ARGS)
+{
+ if (n->type == ROFFT_BODY)
+ term_word(p, "\\(en");
+ return 1;
+}
+
+static int
+termp_bl_pre(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ term_newln(p);
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static void
+termp_bl_post(DECL_ARGS)
+{
+ if (n->type != ROFFT_BLOCK)
+ return;
+ term_newln(p);
+ if (n->tok != MDOC_Bl || n->norm->Bl.type != LIST_column)
+ return;
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, ".5i");
+}
+
+static int
+termp_xr_pre(DECL_ARGS)
+{
+ if (NULL == (n = n->child))
+ return 0;
+
+ assert(n->type == ROFFT_TEXT);
+ term_word(p, n->string);
+
+ if (NULL == (n = n->next))
+ return 0;
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "(");
+ p->flags |= TERMP_NOSPACE;
+
+ assert(n->type == ROFFT_TEXT);
+ 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, struct roff_node *n)
+{
+ struct roff_node *np;
+
+ if ((n->flags & NODE_SYNPRETTY) == 0 ||
+ (np = roff_node_prev(n)) == NULL)
+ 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 (np->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 (np->tok) {
+ case MDOC_Fd:
+ case MDOC_Fn:
+ case MDOC_Fo:
+ case MDOC_In:
+ case MDOC_Vt:
+ term_vspace(p);
+ break;
+ case MDOC_Ft:
+ if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
+ term_vspace(p);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ term_newln(p);
+ break;
+ }
+}
+
+static int
+termp_vt_pre(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_ELEM:
+ return termp_ft_pre(p, pair, meta, n);
+ case ROFFT_BLOCK:
+ synopsis_pre(p, n);
+ return 1;
+ case ROFFT_HEAD:
+ return 0;
+ default:
+ 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)
+{
+ struct roff_node *np;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ /*
+ * Vertical space before sections, except
+ * when the previous section was empty.
+ */
+ if ((np = roff_node_prev(n)) == NULL ||
+ np->tok != MDOC_Sh ||
+ (np->body != NULL && np->body->child != NULL))
+ term_vspace(p);
+ break;
+ case ROFFT_HEAD:
+ return termp_bold_pre(p, pair, meta, n);
+ case ROFFT_BODY:
+ p->tcol->offset = term_len(p, p->defindent);
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, ".5i");
+ if (n->sec == SEC_AUTHORS)
+ p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static void
+termp_sh_post(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_HEAD:
+ term_newln(p);
+ break;
+ case ROFFT_BODY:
+ term_newln(p);
+ p->tcol->offset = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+termp_lb_post(DECL_ARGS)
+{
+ if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE)
+ term_newln(p);
+}
+
+static int
+termp_d1_pre(DECL_ARGS)
+{
+ if (n->type != ROFFT_BLOCK)
+ return 1;
+ term_newln(p);
+ p->tcol->offset += term_len(p, p->defindent + 1);
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, ".5i");
+ return 1;
+}
+
+static int
+termp_ft_pre(DECL_ARGS)
+{
+ synopsis_pre(p, n);
+ return termp_under_pre(p, pair, meta, n);
+}
+
+static int
+termp_fn_pre(DECL_ARGS)
+{
+ size_t rmargin = 0;
+ int pretty;
+
+ synopsis_pre(p, n);
+ pretty = n->flags & NODE_SYNPRETTY;
+ if ((n = n->child) == NULL)
+ return 0;
+
+ if (pretty) {
+ rmargin = p->tcol->rmargin;
+ p->tcol->rmargin = p->tcol->offset + term_len(p, 4);
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
+ }
+
+ assert(n->type == ROFFT_TEXT);
+ 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->flags |= TERMP_NOPAD;
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = rmargin;
+ }
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, "(");
+ p->flags |= TERMP_NOSPACE;
+
+ for (n = n->next; n; n = n->next) {
+ assert(n->type == ROFFT_TEXT);
+ 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 roff_node *nn;
+
+ if (n->parent->tok != MDOC_Fo)
+ return termp_under_pre(p, pair, meta, n);
+
+ for (nn = n->child; nn != NULL; nn = nn->next) {
+ term_fontpush(p, TERMFONT_UNDER);
+ p->flags |= TERMP_NBRWORD;
+ term_word(p, nn->string);
+ term_fontpop(p);
+ if (nn->next != NULL) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ",");
+ }
+ }
+ if (n->child != NULL &&
+ (nn = roff_node_next(n)) != NULL &&
+ nn->tok == MDOC_Fa) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ",");
+ }
+ return 0;
+}
+
+static int
+termp_bd_pre(DECL_ARGS)
+{
+ int offset;
+
+ if (n->type == ROFFT_BLOCK) {
+ print_bvspace(p, n, n);
+ return 1;
+ } else if (n->type == ROFFT_HEAD)
+ return 0;
+
+ /* Handle the -offset argument. */
+
+ if (n->norm->Bd.offs == NULL ||
+ ! strcmp(n->norm->Bd.offs, "left"))
+ /* nothing */;
+ else if ( ! strcmp(n->norm->Bd.offs, "indent"))
+ p->tcol->offset += term_len(p, p->defindent + 1);
+ else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
+ p->tcol->offset += term_len(p, (p->defindent + 1) * 2);
+ else {
+ offset = a2width(p, n->norm->Bd.offs);
+ if (offset < 0 && (size_t)(-offset) > p->tcol->offset)
+ p->tcol->offset = 0;
+ else if (offset < SHRT_MAX)
+ p->tcol->offset += offset;
+ }
+
+ switch (n->norm->Bd.type) {
+ case DISP_literal:
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, "8n");
+ break;
+ case DISP_centered:
+ p->flags |= TERMP_CENTER;
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static void
+termp_bd_post(DECL_ARGS)
+{
+ if (n->type != ROFFT_BODY)
+ return;
+ if (n->norm->Bd.type == DISP_unfilled ||
+ n->norm->Bd.type == DISP_literal)
+ p->flags |= TERMP_BRNEVER;
+ p->flags |= TERMP_NOSPACE;
+ term_newln(p);
+ p->flags &= ~TERMP_BRNEVER;
+ if (n->norm->Bd.type == DISP_centered)
+ p->flags &= ~TERMP_CENTER;
+}
+
+static int
+termp_xx_pre(DECL_ARGS)
+{
+ if ((n->aux = p->flags & TERMP_PREKEEP) == 0)
+ p->flags |= TERMP_PREKEEP;
+ return 1;
+}
+
+static void
+termp_xx_post(DECL_ARGS)
+{
+ if (n->aux == 0)
+ p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
+}
+
+static void
+termp_pf_post(DECL_ARGS)
+{
+ if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
+ p->flags |= TERMP_NOSPACE;
+}
+
+static int
+termp_ss_pre(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ if (roff_node_prev(n) == NULL)
+ term_newln(p);
+ else
+ term_vspace(p);
+ break;
+ case ROFFT_HEAD:
+ p->tcol->offset = term_len(p, (p->defindent+1)/2);
+ return termp_bold_pre(p, pair, meta, n);
+ case ROFFT_BODY:
+ p->tcol->offset = term_len(p, p->defindent);
+ term_tab_set(p, NULL);
+ term_tab_set(p, "T");
+ term_tab_set(p, ".5i");
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+static void
+termp_ss_post(DECL_ARGS)
+{
+ if (n->type == ROFFT_HEAD || n->type == ROFFT_BODY)
+ term_newln(p);
+}
+
+static int
+termp_in_pre(DECL_ARGS)
+{
+ synopsis_pre(p, n);
+ if (n->flags & NODE_SYNPRETTY && n->flags & NODE_LINE) {
+ 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 (n->flags & NODE_SYNPRETTY)
+ term_fontpush(p, TERMFONT_BOLD);
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ">");
+ if (n->flags & NODE_SYNPRETTY)
+ term_fontpop(p);
+}
+
+static int
+termp_pp_pre(DECL_ARGS)
+{
+ term_vspace(p);
+ if (n->flags & NODE_ID)
+ term_tag_write(n, p->line);
+ return 0;
+}
+
+static int
+termp_skip_pre(DECL_ARGS)
+{
+ return 0;
+}
+
+static int
+termp_quote_pre(DECL_ARGS)
+{
+ if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
+ return 1;
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ case MDOC_Aq:
+ term_word(p, n->child != NULL && n->child->next == NULL &&
+ n->child->tok == MDOC_Mt ? "<" : "\\(la");
+ break;
+ case MDOC_Bro:
+ case MDOC_Brq:
+ term_word(p, "{");
+ break;
+ case MDOC_Oo:
+ case MDOC_Op:
+ case MDOC_Bo:
+ case MDOC_Bq:
+ term_word(p, "[");
+ break;
+ case MDOC__T:
+ /* FALLTHROUGH */
+ case MDOC_Do:
+ 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_Po:
+ case MDOC_Pq:
+ term_word(p, "(");
+ break;
+ case MDOC_Qo:
+ case MDOC_Qq:
+ term_word(p, "\"");
+ break;
+ case MDOC_Ql:
+ case MDOC_So:
+ case MDOC_Sq:
+ term_word(p, "\\(oq");
+ break;
+ default:
+ abort();
+ }
+
+ p->flags |= TERMP_NOSPACE;
+ return 1;
+}
+
+static void
+termp_quote_post(DECL_ARGS)
+{
+
+ if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
+ return;
+
+ p->flags |= TERMP_NOSPACE;
+
+ switch (n->tok) {
+ case MDOC_Ao:
+ case MDOC_Aq:
+ term_word(p, n->child != NULL && n->child->next == NULL &&
+ n->child->tok == MDOC_Mt ? ">" : "\\(ra");
+ break;
+ case MDOC_Bro:
+ case MDOC_Brq:
+ term_word(p, "}");
+ break;
+ case MDOC_Oo:
+ case MDOC_Op:
+ case MDOC_Bo:
+ case MDOC_Bq:
+ term_word(p, "]");
+ break;
+ case MDOC__T:
+ /* FALLTHROUGH */
+ case MDOC_Do:
+ case MDOC_Dq:
+ term_word(p, "\\(rq");
+ break;
+ case MDOC_En:
+ if (n->norm->Es == NULL ||
+ n->norm->Es->child == NULL ||
+ n->norm->Es->child->next == NULL)
+ p->flags &= ~TERMP_NOSPACE;
+ else
+ term_word(p, n->norm->Es->child->next->string);
+ break;
+ case MDOC_Po:
+ case MDOC_Pq:
+ term_word(p, ")");
+ break;
+ case MDOC_Qo:
+ case MDOC_Qq:
+ term_word(p, "\"");
+ break;
+ case MDOC_Ql:
+ case MDOC_So:
+ case MDOC_Sq:
+ term_word(p, "\\(cq");
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+termp_eo_pre(DECL_ARGS)
+{
+
+ if (n->type != ROFFT_BODY)
+ return 1;
+
+ if (n->end == ENDBODY_NOT &&
+ n->parent->head->child == NULL &&
+ n->child != NULL &&
+ n->child->end != ENDBODY_NOT)
+ term_word(p, "\\&");
+ else if (n->end != ENDBODY_NOT ? n->child != NULL :
+ n->parent->head->child != NULL && (n->child != NULL ||
+ (n->parent->tail != NULL && n->parent->tail->child != NULL)))
+ p->flags |= TERMP_NOSPACE;
+
+ return 1;
+}
+
+static void
+termp_eo_post(DECL_ARGS)
+{
+ int body, tail;
+
+ if (n->type != ROFFT_BODY)
+ return;
+
+ if (n->end != ENDBODY_NOT) {
+ p->flags &= ~TERMP_NOSPACE;
+ return;
+ }
+
+ body = n->child != NULL || n->parent->head->child != NULL;
+ tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
+
+ if (body && tail)
+ p->flags |= TERMP_NOSPACE;
+ else if ( ! (body || tail))
+ term_word(p, "\\&");
+ else if ( ! tail)
+ p->flags &= ~TERMP_NOSPACE;
+}
+
+static int
+termp_fo_pre(DECL_ARGS)
+{
+ size_t rmargin;
+
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ synopsis_pre(p, n);
+ return 1;
+ case ROFFT_BODY:
+ rmargin = p->tcol->rmargin;
+ if (n->flags & NODE_SYNPRETTY) {
+ p->tcol->rmargin = p->tcol->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 (n->flags & NODE_SYNPRETTY) {
+ term_flushln(p);
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
+ TERMP_HANG);
+ p->flags |= TERMP_NOPAD;
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = rmargin;
+ }
+ return 1;
+ default:
+ return termp_bold_pre(p, pair, meta, n);
+ }
+}
+
+static void
+termp_fo_post(DECL_ARGS)
+{
+ if (n->type != ROFFT_BODY)
+ return;
+
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ")");
+
+ if (n->flags & NODE_SYNPRETTY) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ";");
+ term_flushln(p);
+ }
+}
+
+static int
+termp_bf_pre(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ break;
+ default:
+ return 1;
+ }
+ switch (n->norm->Bf.font) {
+ case FONT_Em:
+ return termp_under_pre(p, pair, meta, n);
+ case FONT_Sy:
+ return termp_bold_pre(p, pair, meta, n);
+ default:
+ return termp_li_pre(p, pair, meta, n);
+ }
+}
+
+static int
+termp_sm_pre(DECL_ARGS)
+{
+ if (n->child == NULL)
+ p->flags ^= TERMP_NONOSPACE;
+ else if (strcmp(n->child->string, "on") == 0)
+ 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)
+{
+ struct roff_node *nn;
+
+ /*
+ * Handle lists of authors. In general, print each followed by
+ * a comma. Don't print the comma if there are only two
+ * authors.
+ */
+ if (n->tok == MDOC__A &&
+ (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A &&
+ ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) &&
+ ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A))
+ return;
+
+ /* TODO: %U. */
+
+ if (n->parent == NULL || n->parent->tok != MDOC_Rs)
+ return;
+
+ p->flags |= TERMP_NOSPACE;
+ if (roff_node_next(n) == NULL) {
+ 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 roff_node *link, *descr, *punct;
+
+ if ((link = n->child) == NULL)
+ return 0;
+
+ /* Find beginning of trailing punctuation. */
+ punct = n->last;
+ while (punct != link && punct->flags & NODE_DELIMC)
+ punct = punct->prev;
+ punct = punct->next;
+
+ /* Link text. */
+ if ((descr = link->next) != NULL && descr != punct) {
+ term_fontpush(p, TERMFONT_UNDER);
+ while (descr != punct) {
+ if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, descr->string);
+ descr = descr->next;
+ }
+ term_fontpop(p);
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, ":");
+ }
+
+ /* Link target. */
+ term_fontpush(p, TERMFONT_BOLD);
+ term_word(p, link->string);
+ term_fontpop(p);
+
+ /* Trailing punctuation. */
+ while (punct != NULL) {
+ p->flags |= TERMP_NOSPACE;
+ term_word(p, punct->string);
+ punct = punct->next;
+ }
+ return 0;
+}
+
+static int
+termp_bk_pre(DECL_ARGS)
+{
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ break;
+ case ROFFT_HEAD:
+ return 0;
+ case ROFFT_BODY:
+ if (n->parent->args != NULL || n->prev->child == NULL)
+ p->flags |= TERMP_PREKEEP;
+ break;
+ default:
+ abort();
+ }
+ return 1;
+}
+
+static void
+termp_bk_post(DECL_ARGS)
+{
+ if (n->type == ROFFT_BODY)
+ p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
+}
+
+/*
+ * If we are in an `Rs' and there is a journal present,
+ * then quote us instead of underlining us (for disambiguation).
+ */
+static void
+termp__t_post(DECL_ARGS)
+{
+ if (n->parent != NULL && n->parent->tok == MDOC_Rs &&
+ 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 (n->parent != NULL && n->parent->tok == MDOC_Rs &&
+ n->parent->norm->Rs.quote_T)
+ return termp_quote_pre(p, pair, meta, n);
+ else
+ return termp_under_pre(p, pair, meta, n);
+}
+
+static int
+termp_under_pre(DECL_ARGS)
+{
+ term_fontpush(p, TERMFONT_UNDER);
+ return 1;
+}
+
+static int
+termp_abort_pre(DECL_ARGS)
+{
+ abort();
+}
diff --git a/usr.bin/mandoc/mdoc_validate.c b/usr.bin/mandoc/mdoc_validate.c
new file mode 100644
index 0000000..34668bf
--- /dev/null
+++ b/usr.bin/mandoc/mdoc_validate.c
@@ -0,0 +1,3047 @@
+/* $OpenBSD: mdoc_validate.c,v 1.302 2020/04/26 21:29:45 schwarze Exp $ */
+/*
+ * Copyright (c) 2010-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * 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 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.
+ *
+ * Validation module for mdoc(7) syntax trees used by mandoc(1).
+ */
+#include <sys/types.h>
+#ifndef OSNAME
+#include <sys/utsname.h>
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "mandoc_xr.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "libmdoc.h"
+#include "tag.h"
+
+/* FIXME: .Bl -diag can't have non-text children in HEAD. */
+
+#define POST_ARGS struct roff_man *mdoc
+
+enum check_ineq {
+ CHECK_LT,
+ CHECK_GT,
+ CHECK_EQ
+};
+
+typedef void (*v_post)(POST_ARGS);
+
+static int build_list(struct roff_man *, int);
+static void check_argv(struct roff_man *,
+ struct roff_node *, struct mdoc_argv *);
+static void check_args(struct roff_man *, struct roff_node *);
+static void check_text(struct roff_man *, int, int, char *);
+static void check_text_em(struct roff_man *, int, int, char *);
+static void check_toptext(struct roff_man *, int, int, const char *);
+static int child_an(const struct roff_node *);
+static size_t macro2len(enum roff_tok);
+static void rewrite_macro2len(struct roff_man *, char **);
+static int similar(const char *, const char *);
+
+static void post_abort(POST_ARGS) __attribute__((__noreturn__));
+static void post_an(POST_ARGS);
+static void post_an_norm(POST_ARGS);
+static void post_at(POST_ARGS);
+static void post_bd(POST_ARGS);
+static void post_bf(POST_ARGS);
+static void post_bk(POST_ARGS);
+static void post_bl(POST_ARGS);
+static void post_bl_block(POST_ARGS);
+static void post_bl_head(POST_ARGS);
+static void post_bl_norm(POST_ARGS);
+static void post_bx(POST_ARGS);
+static void post_defaults(POST_ARGS);
+static void post_display(POST_ARGS);
+static void post_dd(POST_ARGS);
+static void post_delim(POST_ARGS);
+static void post_delim_nb(POST_ARGS);
+static void post_dt(POST_ARGS);
+static void post_em(POST_ARGS);
+static void post_en(POST_ARGS);
+static void post_er(POST_ARGS);
+static void post_es(POST_ARGS);
+static void post_eoln(POST_ARGS);
+static void post_ex(POST_ARGS);
+static void post_fa(POST_ARGS);
+static void post_fl(POST_ARGS);
+static void post_fn(POST_ARGS);
+static void post_fname(POST_ARGS);
+static void post_fo(POST_ARGS);
+static void post_hyph(POST_ARGS);
+static void post_it(POST_ARGS);
+static void post_lb(POST_ARGS);
+static void post_nd(POST_ARGS);
+static void post_nm(POST_ARGS);
+static void post_ns(POST_ARGS);
+static void post_obsolete(POST_ARGS);
+static void post_os(POST_ARGS);
+static void post_par(POST_ARGS);
+static void post_prevpar(POST_ARGS);
+static void post_root(POST_ARGS);
+static void post_rs(POST_ARGS);
+static void post_rv(POST_ARGS);
+static void post_section(POST_ARGS);
+static void post_sh(POST_ARGS);
+static void post_sh_head(POST_ARGS);
+static void post_sh_name(POST_ARGS);
+static void post_sh_see_also(POST_ARGS);
+static void post_sh_authors(POST_ARGS);
+static void post_sm(POST_ARGS);
+static void post_st(POST_ARGS);
+static void post_std(POST_ARGS);
+static void post_sx(POST_ARGS);
+static void post_tag(POST_ARGS);
+static void post_tg(POST_ARGS);
+static void post_useless(POST_ARGS);
+static void post_xr(POST_ARGS);
+static void post_xx(POST_ARGS);
+
+static const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
+ post_dd, /* Dd */
+ post_dt, /* Dt */
+ post_os, /* Os */
+ post_sh, /* Sh */
+ post_section, /* Ss */
+ post_par, /* Pp */
+ post_display, /* D1 */
+ post_display, /* Dl */
+ post_display, /* Bd */
+ NULL, /* Ed */
+ post_bl, /* Bl */
+ NULL, /* El */
+ post_it, /* It */
+ post_delim_nb, /* Ad */
+ post_an, /* An */
+ NULL, /* Ap */
+ post_defaults, /* Ar */
+ NULL, /* Cd */
+ post_tag, /* Cm */
+ post_tag, /* Dv */
+ post_er, /* Er */
+ post_tag, /* Ev */
+ post_ex, /* Ex */
+ post_fa, /* Fa */
+ NULL, /* Fd */
+ post_fl, /* Fl */
+ post_fn, /* Fn */
+ post_delim_nb, /* Ft */
+ post_tag, /* Ic */
+ post_delim_nb, /* In */
+ post_tag, /* Li */
+ post_nd, /* Nd */
+ post_nm, /* Nm */
+ post_delim_nb, /* Op */
+ post_abort, /* Ot */
+ post_defaults, /* Pa */
+ post_rv, /* Rv */
+ post_st, /* St */
+ post_delim_nb, /* Va */
+ post_delim_nb, /* Vt */
+ post_xr, /* Xr */
+ NULL, /* %A */
+ post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */
+ NULL, /* %D */
+ NULL, /* %I */
+ NULL, /* %J */
+ post_hyph, /* %N */
+ post_hyph, /* %O */
+ NULL, /* %P */
+ post_hyph, /* %R */
+ post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */
+ NULL, /* %V */
+ NULL, /* Ac */
+ NULL, /* Ao */
+ post_delim_nb, /* Aq */
+ post_at, /* At */
+ NULL, /* Bc */
+ post_bf, /* Bf */
+ NULL, /* Bo */
+ NULL, /* Bq */
+ post_xx, /* Bsx */
+ post_bx, /* Bx */
+ post_obsolete, /* Db */
+ NULL, /* Dc */
+ NULL, /* Do */
+ NULL, /* Dq */
+ NULL, /* Ec */
+ NULL, /* Ef */
+ post_em, /* Em */
+ NULL, /* Eo */
+ post_xx, /* Fx */
+ post_tag, /* Ms */
+ post_tag, /* No */
+ post_ns, /* Ns */
+ post_xx, /* Nx */
+ post_xx, /* Ox */
+ NULL, /* Pc */
+ NULL, /* Pf */
+ NULL, /* Po */
+ post_delim_nb, /* Pq */
+ NULL, /* Qc */
+ post_delim_nb, /* Ql */
+ NULL, /* Qo */
+ post_delim_nb, /* Qq */
+ NULL, /* Re */
+ post_rs, /* Rs */
+ NULL, /* Sc */
+ NULL, /* So */
+ post_delim_nb, /* Sq */
+ post_sm, /* Sm */
+ post_sx, /* Sx */
+ post_em, /* Sy */
+ post_useless, /* Tn */
+ post_xx, /* Ux */
+ NULL, /* Xc */
+ NULL, /* Xo */
+ post_fo, /* Fo */
+ NULL, /* Fc */
+ NULL, /* Oo */
+ NULL, /* Oc */
+ post_bk, /* Bk */
+ NULL, /* Ek */
+ post_eoln, /* Bt */
+ post_obsolete, /* Hf */
+ post_obsolete, /* Fr */
+ post_eoln, /* Ud */
+ post_lb, /* Lb */
+ post_abort, /* Lp */
+ post_delim_nb, /* Lk */
+ post_defaults, /* Mt */
+ post_delim_nb, /* Brq */
+ NULL, /* Bro */
+ NULL, /* Brc */
+ NULL, /* %C */
+ post_es, /* Es */
+ post_en, /* En */
+ post_xx, /* Dx */
+ NULL, /* %Q */
+ NULL, /* %U */
+ NULL, /* Ta */
+ post_tg, /* Tg */
+};
+
+#define RSORD_MAX 14 /* Number of `Rs' blocks. */
+
+static const enum roff_tok 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
+};
+
+static int fn_prio = TAG_STRONG;
+
+
+/* Validate the subtree rooted at mdoc->last. */
+void
+mdoc_validate(struct roff_man *mdoc)
+{
+ struct roff_node *n, *np;
+ const v_post *p;
+
+ /*
+ * Translate obsolete macros to modern macros first
+ * such that later code does not need to look
+ * for the obsolete versions.
+ */
+
+ n = mdoc->last;
+ switch (n->tok) {
+ case MDOC_Lp:
+ n->tok = MDOC_Pp;
+ break;
+ case MDOC_Ot:
+ post_obsolete(mdoc);
+ n->tok = MDOC_Ft;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Iterate over all children, recursing into each one
+ * in turn, depth-first.
+ */
+
+ mdoc->last = mdoc->last->child;
+ while (mdoc->last != NULL) {
+ mdoc_validate(mdoc);
+ if (mdoc->last == n)
+ mdoc->last = mdoc->last->child;
+ else
+ mdoc->last = mdoc->last->next;
+ }
+
+ /* Finally validate the macro itself. */
+
+ mdoc->last = n;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ switch (n->type) {
+ case ROFFT_TEXT:
+ np = n->parent;
+ if (n->sec != SEC_SYNOPSIS ||
+ (np->tok != MDOC_Cd && np->tok != MDOC_Fd))
+ check_text(mdoc, n->line, n->pos, n->string);
+ if ((n->flags & NODE_NOFILL) == 0 &&
+ (np->tok != MDOC_It || np->type != ROFFT_HEAD ||
+ np->parent->parent->norm->Bl.type != LIST_diag))
+ check_text_em(mdoc, n->line, n->pos, n->string);
+ if (np->tok == MDOC_It || (np->type == ROFFT_BODY &&
+ (np->tok == MDOC_Sh || np->tok == MDOC_Ss)))
+ check_toptext(mdoc, n->line, n->pos, n->string);
+ break;
+ case ROFFT_COMMENT:
+ case ROFFT_EQN:
+ case ROFFT_TBL:
+ break;
+ case ROFFT_ROOT:
+ post_root(mdoc);
+ break;
+ default:
+ check_args(mdoc, mdoc->last);
+
+ /*
+ * Closing delimiters are not special at the
+ * beginning of a block, opening delimiters
+ * are not special at the end.
+ */
+
+ if (n->child != NULL)
+ n->child->flags &= ~NODE_DELIMC;
+ if (n->last != NULL)
+ n->last->flags &= ~NODE_DELIMO;
+
+ /* Call the macro's postprocessor. */
+
+ if (n->tok < ROFF_MAX) {
+ roff_validate(mdoc);
+ break;
+ }
+
+ assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
+ p = mdoc_valids + (n->tok - MDOC_Dd);
+ if (*p)
+ (*p)(mdoc);
+ if (mdoc->last == n)
+ mdoc_state(mdoc, n);
+ break;
+ }
+}
+
+static void
+check_args(struct roff_man *mdoc, struct roff_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 roff_man *mdoc, struct roff_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 roff_man *mdoc, int ln, int pos, char *p)
+{
+ char *cp;
+
+ if (mdoc->last->flags & NODE_NOFILL)
+ return;
+
+ for (cp = p; NULL != (p = strchr(p, '\t')); p++)
+ mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL);
+}
+
+static void
+check_text_em(struct roff_man *mdoc, int ln, int pos, char *p)
+{
+ const struct roff_node *np, *nn;
+ char *cp;
+
+ np = mdoc->last->prev;
+ nn = mdoc->last->next;
+
+ /* Look for em-dashes wrongly encoded as "--". */
+
+ for (cp = p; *cp != '\0'; cp++) {
+ if (cp[0] != '-' || cp[1] != '-')
+ continue;
+ cp++;
+
+ /* Skip input sequences of more than two '-'. */
+
+ if (cp[1] == '-') {
+ while (cp[1] == '-')
+ cp++;
+ continue;
+ }
+
+ /* Skip "--" directly attached to something else. */
+
+ if ((cp - p > 1 && cp[-2] != ' ') ||
+ (cp[1] != '\0' && cp[1] != ' '))
+ continue;
+
+ /* Require a letter right before or right afterwards. */
+
+ if ((cp - p > 2 ?
+ isalpha((unsigned char)cp[-3]) :
+ np != NULL &&
+ np->type == ROFFT_TEXT &&
+ *np->string != '\0' &&
+ isalpha((unsigned char)np->string[
+ strlen(np->string) - 1])) ||
+ (cp[1] != '\0' && cp[2] != '\0' ?
+ isalpha((unsigned char)cp[2]) :
+ nn != NULL &&
+ nn->type == ROFFT_TEXT &&
+ isalpha((unsigned char)*nn->string))) {
+ mandoc_msg(MANDOCERR_DASHDASH,
+ ln, pos + (int)(cp - p) - 1, NULL);
+ break;
+ }
+ }
+}
+
+static void
+check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
+{
+ const char *cp, *cpr;
+
+ if (*p == '\0')
+ return;
+
+ if ((cp = strstr(p, "OpenBSD")) != NULL)
+ mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox");
+ if ((cp = strstr(p, "NetBSD")) != NULL)
+ mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx");
+ if ((cp = strstr(p, "FreeBSD")) != NULL)
+ mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx");
+ if ((cp = strstr(p, "DragonFly")) != NULL)
+ mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx");
+
+ cp = p;
+ while ((cp = strstr(cp + 1, "()")) != NULL) {
+ for (cpr = cp - 1; cpr >= p; cpr--)
+ if (*cpr != '_' && !isalnum((unsigned char)*cpr))
+ break;
+ if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
+ cpr++;
+ mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p),
+ "%.*s()", (int)(cp - cpr), cpr);
+ }
+ }
+}
+
+static void
+post_abort(POST_ARGS)
+{
+ abort();
+}
+
+static void
+post_delim(POST_ARGS)
+{
+ const struct roff_node *nch;
+ const char *lc;
+ enum mdelim delim;
+ enum roff_tok tok;
+
+ tok = mdoc->last->tok;
+ nch = mdoc->last->last;
+ if (nch == NULL || nch->type != ROFFT_TEXT)
+ return;
+ lc = strchr(nch->string, '\0') - 1;
+ if (lc < nch->string)
+ return;
+ delim = mdoc_isdelim(lc);
+ if (delim == DELIM_NONE || delim == DELIM_OPEN)
+ return;
+ if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
+ tok == MDOC_Ss || tok == MDOC_Fo))
+ return;
+
+ mandoc_msg(MANDOCERR_DELIM, nch->line,
+ nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
+ nch == mdoc->last->child ? "" : " ...", nch->string);
+}
+
+static void
+post_delim_nb(POST_ARGS)
+{
+ const struct roff_node *nch;
+ const char *lc, *cp;
+ int nw;
+ enum mdelim delim;
+ enum roff_tok tok;
+
+ /*
+ * Find candidates: at least two bytes,
+ * the last one a closing or middle delimiter.
+ */
+
+ tok = mdoc->last->tok;
+ nch = mdoc->last->last;
+ if (nch == NULL || nch->type != ROFFT_TEXT)
+ return;
+ lc = strchr(nch->string, '\0') - 1;
+ if (lc <= nch->string)
+ return;
+ delim = mdoc_isdelim(lc);
+ if (delim == DELIM_NONE || delim == DELIM_OPEN)
+ return;
+
+ /*
+ * Reduce false positives by allowing various cases.
+ */
+
+ /* Escaped delimiters. */
+ if (lc > nch->string + 1 && lc[-2] == '\\' &&
+ (lc[-1] == '&' || lc[-1] == 'e'))
+ return;
+
+ /* Specific byte sequences. */
+ switch (*lc) {
+ case ')':
+ for (cp = lc; cp >= nch->string; cp--)
+ if (*cp == '(')
+ return;
+ break;
+ case '.':
+ if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
+ return;
+ if (lc[-1] == '.')
+ return;
+ break;
+ case ';':
+ if (tok == MDOC_Vt)
+ return;
+ break;
+ case '?':
+ if (lc[-1] == '?')
+ return;
+ break;
+ case ']':
+ for (cp = lc; cp >= nch->string; cp--)
+ if (*cp == '[')
+ return;
+ break;
+ case '|':
+ if (lc == nch->string + 1 && lc[-1] == '|')
+ return;
+ default:
+ break;
+ }
+
+ /* Exactly two non-alphanumeric bytes. */
+ if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
+ return;
+
+ /* At least three alphabetic words with a sentence ending. */
+ if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
+ tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) {
+ nw = 0;
+ for (cp = lc - 1; cp >= nch->string; cp--) {
+ if (*cp == ' ') {
+ nw++;
+ if (cp > nch->string && cp[-1] == ',')
+ cp--;
+ } else if (isalpha((unsigned int)*cp)) {
+ if (nw > 1)
+ return;
+ } else
+ break;
+ }
+ }
+
+ mandoc_msg(MANDOCERR_DELIM_NB, nch->line,
+ nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
+ nch == mdoc->last->child ? "" : " ...", nch->string);
+}
+
+static void
+post_bl_norm(POST_ARGS)
+{
+ struct roff_node *n;
+ struct mdoc_argv *argv, *wa;
+ int i;
+ enum mdocargt mdoclt;
+ enum mdoc_list lt;
+
+ n = mdoc->last->parent;
+ n->norm->Bl.type = LIST__NONE;
+
+ /*
+ * 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 == NULL) ? NULL : 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,
+ 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,
+ argv->line, argv->pos, "Bl -width");
+ n->norm->Bl.width = "0n";
+ break;
+ }
+ if (NULL != n->norm->Bl.width)
+ mandoc_msg(MANDOCERR_ARG_REP,
+ argv->line, argv->pos,
+ "Bl -width %s", argv->value[0]);
+ rewrite_macro2len(mdoc, argv->value);
+ n->norm->Bl.width = argv->value[0];
+ break;
+ case MDOC_Offset:
+ if (0 == argv->sz) {
+ mandoc_msg(MANDOCERR_ARG_EMPTY,
+ argv->line, argv->pos, "Bl -offset");
+ break;
+ }
+ if (NULL != n->norm->Bl.offs)
+ mandoc_msg(MANDOCERR_ARG_REP,
+ argv->line, argv->pos,
+ "Bl -offset %s", argv->value[0]);
+ rewrite_macro2len(mdoc, argv->value);
+ 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_msg(MANDOCERR_BL_REP, 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_msg(MANDOCERR_BL_LATETYPE,
+ 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, n->line, n->pos, "Bl");
+ n->norm->Bl.type = LIST_item;
+ mdoclt = MDOC_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 (n->norm->Bl.width == NULL)
+ mandoc_msg(MANDOCERR_BL_NOWIDTH,
+ n->line, n->pos, "Bl -tag");
+ break;
+ case LIST_column:
+ case LIST_diag:
+ case LIST_ohang:
+ case LIST_inset:
+ case LIST_item:
+ if (n->norm->Bl.width != NULL)
+ mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos,
+ "Bl -%s", mdoc_argnames[mdoclt]);
+ n->norm->Bl.width = NULL;
+ break;
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_hyphen:
+ if (n->norm->Bl.width == NULL)
+ n->norm->Bl.width = "2n";
+ break;
+ case LIST_enum:
+ if (n->norm->Bl.width == NULL)
+ n->norm->Bl.width = "3n";
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+post_bd(POST_ARGS)
+{
+ struct roff_node *n;
+ struct mdoc_argv *argv;
+ int i;
+ enum mdoc_disp dt;
+
+ n = mdoc->last;
+ 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, n->line, n->pos, NULL);
+ break;
+ case MDOC_Offset:
+ if (0 == argv->sz) {
+ mandoc_msg(MANDOCERR_ARG_EMPTY,
+ argv->line, argv->pos, "Bd -offset");
+ break;
+ }
+ if (NULL != n->norm->Bd.offs)
+ mandoc_msg(MANDOCERR_ARG_REP,
+ argv->line, argv->pos,
+ "Bd -offset %s", argv->value[0]);
+ rewrite_macro2len(mdoc, argv->value);
+ n->norm->Bd.offs = argv->value[0];
+ break;
+ case MDOC_Compact:
+ if (n->norm->Bd.comp)
+ mandoc_msg(MANDOCERR_ARG_REP,
+ argv->line, argv->pos, "Bd -compact");
+ n->norm->Bd.comp = 1;
+ break;
+ default:
+ abort();
+ }
+ if (DISP__NONE == dt)
+ continue;
+
+ if (DISP__NONE == n->norm->Bd.type)
+ n->norm->Bd.type = dt;
+ else
+ mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos,
+ "Bd -%s", mdoc_argnames[argv->arg]);
+ }
+
+ if (DISP__NONE == n->norm->Bd.type) {
+ mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd");
+ n->norm->Bd.type = DISP_ragged;
+ }
+}
+
+/*
+ * Stand-alone line macros.
+ */
+
+static void
+post_an_norm(POST_ARGS)
+{
+ struct roff_node *n;
+ struct mdoc_argv *argv;
+ size_t i;
+
+ n = mdoc->last;
+ if (n->args == NULL)
+ return;
+
+ for (i = 1; i < n->args->argc; i++) {
+ argv = n->args->argv + i;
+ mandoc_msg(MANDOCERR_AN_REP, 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();
+}
+
+static void
+post_eoln(POST_ARGS)
+{
+ struct roff_node *n;
+
+ post_useless(mdoc);
+ n = mdoc->last;
+ if (n->child != NULL)
+ mandoc_msg(MANDOCERR_ARG_SKIP, n->line,
+ n->pos, "%s %s", roff_name[n->tok], n->child->string);
+
+ while (n->child != NULL)
+ roff_node_delete(mdoc, n->child);
+
+ roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
+ "is currently in beta test." : "currently under development.");
+ mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static int
+build_list(struct roff_man *mdoc, int tok)
+{
+ struct roff_node *n;
+ int ic;
+
+ n = mdoc->last->next;
+ for (ic = 1;; ic++) {
+ roff_elem_alloc(mdoc, n->line, n->pos, tok);
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_node_relink(mdoc, n);
+ n = mdoc->last = mdoc->last->parent;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ if (n->next == NULL)
+ return ic;
+ if (ic > 1 || n->next->next != NULL) {
+ roff_word_alloc(mdoc, n->line, n->pos, ",");
+ mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
+ }
+ n = mdoc->last->next;
+ if (n->next == NULL) {
+ roff_word_alloc(mdoc, n->line, n->pos, "and");
+ mdoc->last->flags |= NODE_NOSRC;
+ }
+ }
+}
+
+static void
+post_ex(POST_ARGS)
+{
+ struct roff_node *n;
+ int ic;
+
+ post_std(mdoc);
+
+ n = mdoc->last;
+ mdoc->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(mdoc, n->line, n->pos, "The");
+ mdoc->last->flags |= NODE_NOSRC;
+
+ if (mdoc->last->next != NULL)
+ ic = build_list(mdoc, MDOC_Nm);
+ else if (mdoc->meta.name != NULL) {
+ roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = mdoc->last->parent;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ ic = 1;
+ } else {
+ mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex");
+ ic = 0;
+ }
+
+ roff_word_alloc(mdoc, n->line, n->pos,
+ ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_word_alloc(mdoc, n->line, n->pos,
+ "on success, and\\~>0 if an error occurs.");
+ mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static void
+post_lb(POST_ARGS)
+{
+ struct roff_node *n;
+
+ post_delim_nb(mdoc);
+
+ n = mdoc->last;
+ assert(n->child->type == ROFFT_TEXT);
+ mdoc->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(mdoc, n->line, n->pos, "library");
+ mdoc->last->flags = NODE_NOSRC;
+ roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
+ mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
+ mdoc->last = mdoc->last->next;
+ roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
+ mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static void
+post_rv(POST_ARGS)
+{
+ struct roff_node *n;
+ int ic;
+
+ post_std(mdoc);
+
+ n = mdoc->last;
+ mdoc->next = ROFF_NEXT_CHILD;
+ if (n->child != NULL) {
+ roff_word_alloc(mdoc, n->line, n->pos, "The");
+ mdoc->last->flags |= NODE_NOSRC;
+ ic = build_list(mdoc, MDOC_Fn);
+ roff_word_alloc(mdoc, n->line, n->pos,
+ ic > 1 ? "functions return" : "function returns");
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_word_alloc(mdoc, n->line, n->pos,
+ "the value\\~0 if successful;");
+ } else
+ roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
+ "completion, the value\\~0 is returned;");
+ mdoc->last->flags |= NODE_NOSRC;
+
+ roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
+ "the value\\~\\-1 is returned and the global variable");
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_word_alloc(mdoc, n->line, n->pos, "errno");
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = mdoc->last->parent;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ roff_word_alloc(mdoc, n->line, n->pos,
+ "is set to indicate the error.");
+ mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static void
+post_std(POST_ARGS)
+{
+ struct roff_node *n;
+
+ post_delim(mdoc);
+
+ n = mdoc->last;
+ if (n->args && n->args->argc == 1)
+ if (n->args->argv[0].arg == MDOC_Std)
+ return;
+
+ mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos,
+ "%s", roff_name[n->tok]);
+}
+
+static void
+post_st(POST_ARGS)
+{
+ struct roff_node *n, *nch;
+ const char *p;
+
+ n = mdoc->last;
+ nch = n->child;
+ assert(nch->type == ROFFT_TEXT);
+
+ if ((p = mdoc_a2st(nch->string)) == NULL) {
+ mandoc_msg(MANDOCERR_ST_BAD,
+ nch->line, nch->pos, "St %s", nch->string);
+ roff_node_delete(mdoc, n);
+ return;
+ }
+
+ nch->flags |= NODE_NOPRT;
+ mdoc->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(mdoc, nch->line, nch->pos, p);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last= n;
+}
+
+static void
+post_tg(POST_ARGS)
+{
+ struct roff_node *n; /* The .Tg node. */
+ struct roff_node *nch; /* The first child of the .Tg node. */
+ struct roff_node *nn; /* The next node after the .Tg node. */
+ struct roff_node *np; /* The parent of the next node. */
+ struct roff_node *nt; /* The TEXT node containing the tag. */
+ size_t len; /* The number of bytes in the tag. */
+
+ /* Find the next node. */
+ n = mdoc->last;
+ for (nn = n; nn != NULL; nn = nn->parent) {
+ if (nn->next != NULL) {
+ nn = nn->next;
+ break;
+ }
+ }
+
+ /* Find the tag. */
+ nt = nch = n->child;
+ if (nch == NULL && nn != NULL && nn->child != NULL &&
+ nn->child->type == ROFFT_TEXT)
+ nt = nn->child;
+
+ /* Validate the tag. */
+ if (nt == NULL || *nt->string == '\0')
+ mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
+ if (nt == NULL) {
+ roff_node_delete(mdoc, n);
+ return;
+ }
+ len = strcspn(nt->string, " \t\\");
+ if (nt->string[len] != '\0')
+ mandoc_msg(MANDOCERR_TG_SPC, nt->line,
+ nt->pos + len, "Tg %s", nt->string);
+
+ /* Keep only the first argument. */
+ if (nch != NULL && nch->next != NULL) {
+ mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
+ nch->next->pos, "Tg ... %s", nch->next->string);
+ while (nch->next != NULL)
+ roff_node_delete(mdoc, nch->next);
+ }
+
+ /* Drop the macro if the first argument is invalid. */
+ if (len == 0 || nt->string[len] != '\0') {
+ roff_node_delete(mdoc, n);
+ return;
+ }
+
+ /* By default, tag the .Tg node itself. */
+ if (nn == NULL || nn->flags & NODE_ID)
+ nn = n;
+
+ /* Explicit tagging of specific macros. */
+ switch (nn->tok) {
+ case MDOC_Sh:
+ case MDOC_Ss:
+ case MDOC_Fo:
+ nn = nn->head->child == NULL ? n : nn->head;
+ break;
+ case MDOC_It:
+ np = nn->parent;
+ while (np->tok != MDOC_Bl)
+ np = np->parent;
+ switch (np->norm->Bl.type) {
+ case LIST_column:
+ break;
+ case LIST_diag:
+ case LIST_hang:
+ case LIST_inset:
+ case LIST_ohang:
+ case LIST_tag:
+ nn = nn->head;
+ break;
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_enum:
+ case LIST_hyphen:
+ case LIST_item:
+ nn = nn->body->child == NULL ? n : nn->body;
+ break;
+ default:
+ abort();
+ }
+ break;
+ case MDOC_Bd:
+ case MDOC_Bl:
+ case MDOC_D1:
+ case MDOC_Dl:
+ nn = nn->body->child == NULL ? n : nn->body;
+ break;
+ case MDOC_Pp:
+ break;
+ case MDOC_Cm:
+ case MDOC_Dv:
+ case MDOC_Em:
+ case MDOC_Er:
+ case MDOC_Ev:
+ case MDOC_Fl:
+ case MDOC_Fn:
+ case MDOC_Ic:
+ case MDOC_Li:
+ case MDOC_Ms:
+ case MDOC_No:
+ case MDOC_Sy:
+ if (nn->child == NULL)
+ nn = n;
+ break;
+ default:
+ nn = n;
+ break;
+ }
+ tag_put(nt->string, TAG_MANUAL, nn);
+ if (nn != n)
+ n->flags |= NODE_NOPRT;
+}
+
+static void
+post_obsolete(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
+ mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
+ "%s", roff_name[n->tok]);
+}
+
+static void
+post_useless(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
+ "%s", roff_name[n->tok]);
+}
+
+/*
+ * Block macros.
+ */
+
+static void
+post_bf(POST_ARGS)
+{
+ struct roff_node *np, *nch;
+
+ /*
+ * Unlike other data pointers, these are "housed" by the HEAD
+ * element, which contains the goods.
+ */
+
+ np = mdoc->last;
+ if (np->type != ROFFT_HEAD)
+ return;
+
+ assert(np->parent->type == ROFFT_BLOCK);
+ assert(np->parent->tok == MDOC_Bf);
+
+ /* Check the number of arguments. */
+
+ nch = np->child;
+ if (np->parent->args == NULL) {
+ if (nch == NULL) {
+ mandoc_msg(MANDOCERR_BF_NOFONT,
+ np->line, np->pos, "Bf");
+ return;
+ }
+ nch = nch->next;
+ }
+ if (nch != NULL)
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ nch->line, nch->pos, "Bf ... %s", nch->string);
+
+ /* Extract argument into data. */
+
+ if (np->parent->args != NULL) {
+ switch (np->parent->args->argv[0].arg) {
+ case MDOC_Emphasis:
+ np->norm->Bf.font = FONT_Em;
+ break;
+ case MDOC_Literal:
+ np->norm->Bf.font = FONT_Li;
+ break;
+ case MDOC_Symbolic:
+ np->norm->Bf.font = FONT_Sy;
+ break;
+ default:
+ abort();
+ }
+ return;
+ }
+
+ /* Extract parameter into data. */
+
+ if ( ! strcmp(np->child->string, "Em"))
+ np->norm->Bf.font = FONT_Em;
+ else if ( ! strcmp(np->child->string, "Li"))
+ np->norm->Bf.font = FONT_Li;
+ else if ( ! strcmp(np->child->string, "Sy"))
+ np->norm->Bf.font = FONT_Sy;
+ else
+ mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
+ np->child->pos, "Bf %s", np->child->string);
+}
+
+static void
+post_fname(POST_ARGS)
+{
+ struct roff_node *n, *nch;
+ const char *cp;
+ size_t pos;
+
+ n = mdoc->last;
+ nch = n->child;
+ cp = nch->string;
+ if (*cp == '(') {
+ if (cp[strlen(cp + 1)] == ')')
+ return;
+ pos = 0;
+ } else {
+ pos = strcspn(cp, "()");
+ if (cp[pos] == '\0') {
+ if (n->sec == SEC_DESCRIPTION ||
+ n->sec == SEC_CUSTOM)
+ tag_put(NULL, fn_prio++, n);
+ return;
+ }
+ }
+ mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
+}
+
+static void
+post_fn(POST_ARGS)
+{
+ post_fname(mdoc);
+ post_fa(mdoc);
+}
+
+static void
+post_fo(POST_ARGS)
+{
+ const struct roff_node *n;
+
+ n = mdoc->last;
+
+ if (n->type != ROFFT_HEAD)
+ return;
+
+ if (n->child == NULL) {
+ mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
+ return;
+ }
+ if (n->child != n->last) {
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ n->child->next->line, n->child->next->pos,
+ "Fo ... %s", n->child->next->string);
+ while (n->child != n->last)
+ roff_node_delete(mdoc, n->last);
+ } else
+ post_delim(mdoc);
+
+ post_fname(mdoc);
+}
+
+static void
+post_fa(POST_ARGS)
+{
+ const struct roff_node *n;
+ const char *cp;
+
+ for (n = mdoc->last->child; n != NULL; n = n->next) {
+ for (cp = n->string; *cp != '\0'; cp++) {
+ /* Ignore callbacks and alterations. */
+ if (*cp == '(' || *cp == '{')
+ break;
+ if (*cp != ',')
+ continue;
+ mandoc_msg(MANDOCERR_FA_COMMA, n->line,
+ n->pos + (int)(cp - n->string), "%s", n->string);
+ break;
+ }
+ }
+ post_delim_nb(mdoc);
+}
+
+static void
+post_nm(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+
+ if (n->sec == SEC_NAME && n->child != NULL &&
+ n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
+ mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
+
+ if (n->last != NULL && n->last->tok == MDOC_Pp)
+ roff_node_relink(mdoc, n->last);
+
+ if (mdoc->meta.name == NULL)
+ deroff(&mdoc->meta.name, n);
+
+ if (mdoc->meta.name == NULL ||
+ (mdoc->lastsec == SEC_NAME && n->child == NULL))
+ mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
+
+ switch (n->type) {
+ case ROFFT_ELEM:
+ post_delim_nb(mdoc);
+ break;
+ case ROFFT_HEAD:
+ post_delim(mdoc);
+ break;
+ default:
+ return;
+ }
+
+ if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
+ mdoc->meta.name == NULL)
+ return;
+
+ mdoc->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static void
+post_nd(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+
+ if (n->type != ROFFT_BODY)
+ return;
+
+ if (n->sec != SEC_NAME)
+ mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
+
+ if (n->child == NULL)
+ mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
+ else
+ post_delim(mdoc);
+
+ post_hyph(mdoc);
+}
+
+static void
+post_display(POST_ARGS)
+{
+ struct roff_node *n, *np;
+
+ n = mdoc->last;
+ switch (n->type) {
+ case ROFFT_BODY:
+ if (n->end != ENDBODY_NOT) {
+ if (n->tok == MDOC_Bd &&
+ n->body->parent->args == NULL)
+ roff_node_delete(mdoc, n);
+ } else if (n->child == NULL)
+ mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
+ "%s", roff_name[n->tok]);
+ else if (n->tok == MDOC_D1)
+ post_hyph(mdoc);
+ break;
+ case ROFFT_BLOCK:
+ if (n->tok == MDOC_Bd) {
+ if (n->args == NULL) {
+ mandoc_msg(MANDOCERR_BD_NOARG,
+ n->line, n->pos, "Bd");
+ mdoc->next = ROFF_NEXT_SIBLING;
+ while (n->body->child != NULL)
+ roff_node_relink(mdoc,
+ n->body->child);
+ roff_node_delete(mdoc, n);
+ break;
+ }
+ post_bd(mdoc);
+ post_prevpar(mdoc);
+ }
+ for (np = n->parent; np != NULL; np = np->parent) {
+ if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
+ mandoc_msg(MANDOCERR_BD_NEST, n->line,
+ n->pos, "%s in Bd", roff_name[n->tok]);
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+post_defaults(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ if (n->child != NULL) {
+ post_delim_nb(mdoc);
+ return;
+ }
+ mdoc->next = ROFF_NEXT_CHILD;
+ switch (n->tok) {
+ case MDOC_Ar:
+ roff_word_alloc(mdoc, n->line, n->pos, "file");
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_word_alloc(mdoc, n->line, n->pos, "...");
+ break;
+ case MDOC_Pa:
+ case MDOC_Mt:
+ roff_word_alloc(mdoc, n->line, n->pos, "~");
+ break;
+ default:
+ abort();
+ }
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static void
+post_at(POST_ARGS)
+{
+ struct roff_node *n, *nch;
+ const char *att;
+
+ n = mdoc->last;
+ nch = n->child;
+
+ /*
+ * 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.
+ */
+
+ att = NULL;
+ if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
+ mandoc_msg(MANDOCERR_AT_BAD,
+ nch->line, nch->pos, "At %s", nch->string);
+
+ mdoc->next = ROFF_NEXT_CHILD;
+ if (att != NULL) {
+ roff_word_alloc(mdoc, nch->line, nch->pos, att);
+ nch->flags |= NODE_NOPRT;
+ } else
+ roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static void
+post_an(POST_ARGS)
+{
+ struct roff_node *np, *nch;
+
+ post_an_norm(mdoc);
+
+ np = mdoc->last;
+ nch = np->child;
+ if (np->norm->An.auth == AUTH__NONE) {
+ if (nch == NULL)
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ np->line, np->pos, "An");
+ else
+ post_delim_nb(mdoc);
+ } else if (nch != NULL)
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ nch->line, nch->pos, "An ... %s", nch->string);
+}
+
+static void
+post_em(POST_ARGS)
+{
+ post_tag(mdoc);
+ tag_put(NULL, TAG_FALLBACK, mdoc->last);
+}
+
+static void
+post_en(POST_ARGS)
+{
+ post_obsolete(mdoc);
+ if (mdoc->last->type == ROFFT_BLOCK)
+ mdoc->last->norm->Es = mdoc->last_es;
+}
+
+static void
+post_er(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ if (n->sec == SEC_ERRORS &&
+ (n->parent->tok == MDOC_It ||
+ (n->parent->tok == MDOC_Bq &&
+ n->parent->parent->parent->tok == MDOC_It)))
+ tag_put(NULL, TAG_STRONG, n);
+ post_delim_nb(mdoc);
+}
+
+static void
+post_tag(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ if ((n->prev == NULL ||
+ (n->prev->type == ROFFT_TEXT &&
+ strcmp(n->prev->string, "|") == 0)) &&
+ (n->parent->tok == MDOC_It ||
+ (n->parent->tok == MDOC_Xo &&
+ n->parent->parent->prev == NULL &&
+ n->parent->parent->parent->tok == MDOC_It)))
+ tag_put(NULL, TAG_STRONG, n);
+ post_delim_nb(mdoc);
+}
+
+static void
+post_es(POST_ARGS)
+{
+ post_obsolete(mdoc);
+ mdoc->last_es = mdoc->last;
+}
+
+static void
+post_fl(POST_ARGS)
+{
+ struct roff_node *n;
+ char *cp;
+
+ /*
+ * Transform ".Fl Fl long" to ".Fl \-long",
+ * resulting for example in better HTML output.
+ */
+
+ n = mdoc->last;
+ if (n->prev != NULL && n->prev->tok == MDOC_Fl &&
+ n->prev->child == NULL && n->child != NULL &&
+ (n->flags & NODE_LINE) == 0) {
+ mandoc_asprintf(&cp, "\\-%s", n->child->string);
+ free(n->child->string);
+ n->child->string = cp;
+ roff_node_delete(mdoc, n->prev);
+ }
+ post_tag(mdoc);
+}
+
+static void
+post_xx(POST_ARGS)
+{
+ struct roff_node *n;
+ const char *os;
+ char *v;
+
+ post_delim_nb(mdoc);
+
+ n = mdoc->last;
+ switch (n->tok) {
+ case MDOC_Bsx:
+ os = "BSD/OS";
+ break;
+ case MDOC_Dx:
+ os = "DragonFly";
+ break;
+ case MDOC_Fx:
+ os = "FreeBSD";
+ break;
+ case MDOC_Nx:
+ os = "NetBSD";
+ if (n->child == NULL)
+ break;
+ v = n->child->string;
+ if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
+ v[2] < '0' || v[2] > '9' ||
+ v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
+ break;
+ n->child->flags |= NODE_NOPRT;
+ mdoc->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
+ v = mdoc->last->string;
+ v[3] = toupper((unsigned char)v[3]);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = n;
+ break;
+ case MDOC_Ox:
+ os = "OpenBSD";
+ break;
+ case MDOC_Ux:
+ os = "UNIX";
+ break;
+ default:
+ abort();
+ }
+ mdoc->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(mdoc, n->line, n->pos, os);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = n;
+}
+
+static void
+post_it(POST_ARGS)
+{
+ struct roff_node *nbl, *nit, *nch;
+ int i, cols;
+ enum mdoc_list lt;
+
+ post_prevpar(mdoc);
+
+ nit = mdoc->last;
+ if (nit->type != ROFFT_BLOCK)
+ return;
+
+ nbl = nit->parent->parent;
+ lt = nbl->norm->Bl.type;
+
+ switch (lt) {
+ case LIST_tag:
+ case LIST_hang:
+ case LIST_ohang:
+ case LIST_inset:
+ case LIST_diag:
+ if (nit->head->child == NULL)
+ mandoc_msg(MANDOCERR_IT_NOHEAD,
+ nit->line, nit->pos, "Bl -%s It",
+ mdoc_argnames[nbl->args->argv[0].arg]);
+ break;
+ case LIST_bullet:
+ case LIST_dash:
+ case LIST_enum:
+ case LIST_hyphen:
+ if (nit->body == NULL || nit->body->child == NULL)
+ mandoc_msg(MANDOCERR_IT_NOBODY,
+ nit->line, nit->pos, "Bl -%s It",
+ mdoc_argnames[nbl->args->argv[0].arg]);
+ /* FALLTHROUGH */
+ case LIST_item:
+ if ((nch = nit->head->child) != NULL)
+ mandoc_msg(MANDOCERR_ARG_SKIP,
+ nit->line, nit->pos, "It %s",
+ nch->type == ROFFT_TEXT ? nch->string :
+ roff_name[nch->tok]);
+ break;
+ case LIST_column:
+ cols = (int)nbl->norm->Bl.ncols;
+
+ assert(nit->head->child == NULL);
+
+ if (nit->head->next->child == NULL &&
+ nit->head->next->next == NULL) {
+ mandoc_msg(MANDOCERR_MACRO_EMPTY,
+ nit->line, nit->pos, "It");
+ roff_node_delete(mdoc, nit);
+ break;
+ }
+
+ i = 0;
+ for (nch = nit->child; nch != NULL; nch = nch->next) {
+ if (nch->type != ROFFT_BODY)
+ continue;
+ if (i++ && nch->flags & NODE_LINE)
+ mandoc_msg(MANDOCERR_TA_LINE,
+ nch->line, nch->pos, "Ta");
+ }
+ if (i < cols || i > cols + 1)
+ mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
+ "%d columns, %d cells", cols, i);
+ else if (nit->head->next->child != NULL &&
+ nit->head->next->child->flags & NODE_LINE)
+ mandoc_msg(MANDOCERR_IT_NOARG,
+ nit->line, nit->pos, "Bl -column It");
+ break;
+ default:
+ abort();
+ }
+}
+
+static void
+post_bl_block(POST_ARGS)
+{
+ struct roff_node *n, *ni, *nc;
+
+ post_prevpar(mdoc);
+
+ n = mdoc->last;
+ for (ni = n->body->child; ni != NULL; ni = ni->next) {
+ if (ni->body == NULL)
+ continue;
+ nc = ni->body->last;
+ while (nc != NULL) {
+ switch (nc->tok) {
+ case MDOC_Pp:
+ case ROFF_br:
+ break;
+ default:
+ nc = NULL;
+ continue;
+ }
+ if (ni->next == NULL) {
+ mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
+ nc->pos, "%s", roff_name[nc->tok]);
+ roff_node_relink(mdoc, nc);
+ } else if (n->norm->Bl.comp == 0 &&
+ n->norm->Bl.type != LIST_column) {
+ mandoc_msg(MANDOCERR_PAR_SKIP,
+ nc->line, nc->pos,
+ "%s before It", roff_name[nc->tok]);
+ roff_node_delete(mdoc, nc);
+ } else
+ break;
+ nc = ni->body->last;
+ }
+ }
+}
+
+/*
+ * If the argument of -offset or -width is a macro,
+ * replace it with the associated default width.
+ */
+static void
+rewrite_macro2len(struct roff_man *mdoc, char **arg)
+{
+ size_t width;
+ enum roff_tok tok;
+
+ if (*arg == NULL)
+ return;
+ else if ( ! strcmp(*arg, "Ds"))
+ width = 6;
+ else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
+ return;
+ else
+ width = macro2len(tok);
+
+ free(*arg);
+ mandoc_asprintf(arg, "%zun", width);
+}
+
+static void
+post_bl_head(POST_ARGS)
+{
+ struct roff_node *nbl, *nh, *nch, *nnext;
+ struct mdoc_argv *argv;
+ int i, j;
+
+ post_bl_norm(mdoc);
+
+ nh = mdoc->last;
+ if (nh->norm->Bl.type != LIST_column) {
+ if ((nch = nh->child) == NULL)
+ return;
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ nch->line, nch->pos, "Bl ... %s", nch->string);
+ while (nch != NULL) {
+ roff_node_delete(mdoc, nch);
+ nch = nh->child;
+ }
+ return;
+ }
+
+ /*
+ * 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 (nh->child == NULL)
+ return;
+
+ nbl = nh->parent;
+ for (j = 0; j < (int)nbl->args->argc; j++)
+ if (nbl->args->argv[j].arg == MDOC_Column)
+ break;
+
+ assert(j < (int)nbl->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 = nbl->args->argv + j;
+ i = argv->sz;
+ for (nch = nh->child; nch != NULL; nch = nch->next)
+ argv->sz++;
+ argv->value = mandoc_reallocarray(argv->value,
+ argv->sz, sizeof(char *));
+
+ nh->norm->Bl.ncols = argv->sz;
+ nh->norm->Bl.cols = (void *)argv->value;
+
+ for (nch = nh->child; nch != NULL; nch = nnext) {
+ argv->value[i++] = nch->string;
+ nch->string = NULL;
+ nnext = nch->next;
+ roff_node_delete(NULL, nch);
+ }
+ nh->child = NULL;
+}
+
+static void
+post_bl(POST_ARGS)
+{
+ struct roff_node *nbody; /* of the Bl */
+ struct roff_node *nchild, *nnext; /* of the Bl body */
+ const char *prev_Er;
+ int order;
+
+ nbody = mdoc->last;
+ switch (nbody->type) {
+ case ROFFT_BLOCK:
+ post_bl_block(mdoc);
+ return;
+ case ROFFT_HEAD:
+ post_bl_head(mdoc);
+ return;
+ case ROFFT_BODY:
+ break;
+ default:
+ return;
+ }
+ if (nbody->end != ENDBODY_NOT)
+ return;
+
+ /*
+ * Up to the first item, move nodes before the list,
+ * but leave transparent nodes where they are
+ * if they precede an item.
+ * The next non-transparent node is kept in nchild.
+ * It only needs to be updated after a non-transparent
+ * node was moved out, and at the very beginning
+ * when no node at all was moved yet.
+ */
+
+ nchild = mdoc->last;
+ for (;;) {
+ if (nchild == mdoc->last)
+ nchild = roff_node_child(nbody);
+ if (nchild == NULL) {
+ mdoc->last = nbody;
+ mandoc_msg(MANDOCERR_BLK_EMPTY,
+ nbody->line, nbody->pos, "Bl");
+ return;
+ }
+ if (nchild->tok == MDOC_It) {
+ mdoc->last = nbody;
+ break;
+ }
+ mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
+ nbody->child->pos, "%s", roff_name[nbody->child->tok]);
+ if (nbody->parent->prev == NULL) {
+ mdoc->last = nbody->parent->parent;
+ mdoc->next = ROFF_NEXT_CHILD;
+ } else {
+ mdoc->last = nbody->parent->prev;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ }
+ roff_node_relink(mdoc, nbody->child);
+ }
+
+ /*
+ * We have reached the first item,
+ * so moving nodes out is no longer possible.
+ * But in .Bl -column, the first rows may be implicit,
+ * that is, they may not start with .It macros.
+ * Such rows may be followed by nodes generated on the
+ * roff level, for example .TS.
+ * Wrap such roff nodes into an implicit row.
+ */
+
+ while (nchild != NULL) {
+ if (nchild->tok == MDOC_It) {
+ nchild = roff_node_next(nchild);
+ continue;
+ }
+ nnext = nchild->next;
+ mdoc->last = nchild->prev;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
+ roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
+ mdoc->next = ROFF_NEXT_SIBLING;
+ roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
+ while (nchild->tok != MDOC_It) {
+ roff_node_relink(mdoc, nchild);
+ if (nnext == NULL)
+ break;
+ nchild = nnext;
+ nnext = nchild->next;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ }
+ mdoc->last = nbody;
+ }
+
+ if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
+ return;
+
+ prev_Er = NULL;
+ for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
+ if (nchild->tok != MDOC_It)
+ continue;
+ if ((nnext = nchild->head->child) == NULL)
+ continue;
+ if (nnext->type == ROFFT_BLOCK)
+ nnext = nnext->body->child;
+ if (nnext == NULL || nnext->tok != MDOC_Er)
+ continue;
+ nnext = nnext->child;
+ if (prev_Er != NULL) {
+ order = strcmp(prev_Er, nnext->string);
+ if (order > 0)
+ mandoc_msg(MANDOCERR_ER_ORDER,
+ nnext->line, nnext->pos,
+ "Er %s %s (NetBSD)",
+ prev_Er, nnext->string);
+ else if (order == 0)
+ mandoc_msg(MANDOCERR_ER_REP,
+ nnext->line, nnext->pos,
+ "Er %s (NetBSD)", prev_Er);
+ }
+ prev_Er = nnext->string;
+ }
+}
+
+static void
+post_bk(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+
+ if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
+ mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
+ roff_node_delete(mdoc, n);
+ }
+}
+
+static void
+post_sm(POST_ARGS)
+{
+ struct roff_node *nch;
+
+ nch = mdoc->last->child;
+
+ if (nch == NULL) {
+ mdoc->flags ^= MDOC_SMOFF;
+ return;
+ }
+
+ assert(nch->type == ROFFT_TEXT);
+
+ if ( ! strcmp(nch->string, "on")) {
+ mdoc->flags &= ~MDOC_SMOFF;
+ return;
+ }
+ if ( ! strcmp(nch->string, "off")) {
+ mdoc->flags |= MDOC_SMOFF;
+ return;
+ }
+
+ mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
+ "%s %s", roff_name[mdoc->last->tok], nch->string);
+ roff_node_relink(mdoc, nch);
+ return;
+}
+
+static void
+post_root(POST_ARGS)
+{
+ struct roff_node *n;
+
+ /* Add missing prologue data. */
+
+ if (mdoc->meta.date == NULL)
+ mdoc->meta.date = mandoc_normdate(NULL, NULL);
+
+ if (mdoc->meta.title == NULL) {
+ mandoc_msg(MANDOCERR_DT_NOTITLE, 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, 0, 0, NULL);
+ mdoc->meta.os = mandoc_strdup("");
+ } else if (mdoc->meta.os_e &&
+ (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
+ mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
+ mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
+ "(OpenBSD)" : "(NetBSD)");
+
+ if (mdoc->meta.arch != NULL &&
+ arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
+ n = mdoc->meta.first->child;
+ while (n->tok != MDOC_Dt ||
+ n->child == NULL ||
+ n->child->next == NULL ||
+ n->child->next->next == NULL)
+ n = n->next;
+ n = n->child->next->next;
+ mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
+ "Dt ... %s %s", mdoc->meta.arch,
+ mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
+ "(OpenBSD)" : "(NetBSD)");
+ }
+
+ /* Check that we begin with a proper `Sh'. */
+
+ n = mdoc->meta.first->child;
+ while (n != NULL &&
+ (n->type == ROFFT_COMMENT ||
+ (n->tok >= MDOC_Dd &&
+ mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
+ n = n->next;
+
+ if (n == NULL)
+ mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
+ else if (n->tok != MDOC_Sh)
+ mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
+ "%s", roff_name[n->tok]);
+}
+
+static void
+post_rs(POST_ARGS)
+{
+ struct roff_node *np, *nch, *next, *prev;
+ int i, j;
+
+ np = mdoc->last;
+
+ if (np->type != ROFFT_BODY)
+ return;
+
+ if (np->child == NULL) {
+ mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
+ return;
+ }
+
+ /*
+ * 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 (nch = np->child->next; nch != NULL; nch = next) {
+ /* Determine order number of this child. */
+ for (i = 0; i < RSORD_MAX; i++)
+ if (rsord[i] == nch->tok)
+ break;
+
+ if (i == RSORD_MAX) {
+ mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
+ "%s", roff_name[nch->tok]);
+ i = -1;
+ } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
+ np->norm->Rs.quote_T++;
+
+ /*
+ * Remove this child from the chain. This somewhat
+ * repeats roff_node_unlink(), but since we're
+ * just re-ordering, there's no need for the
+ * full unlink process.
+ */
+
+ if ((next = nch->next) != NULL)
+ next->prev = nch->prev;
+
+ if ((prev = nch->prev) != NULL)
+ prev->next = nch->next;
+
+ nch->prev = nch->next = NULL;
+
+ /*
+ * Scan back until we reach a node that's
+ * to be ordered before this child.
+ */
+
+ 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 this child back into its correct place
+ * in front of the `prev' node.
+ */
+
+ nch->prev = prev;
+
+ if (prev == NULL) {
+ np->child->prev = nch;
+ nch->next = np->child;
+ np->child = nch;
+ } else {
+ if (prev->next)
+ prev->next->prev = nch;
+ nch->next = prev->next;
+ prev->next = nch;
+ }
+ }
+}
+
+/*
+ * For some arguments of some macros,
+ * convert all breakable hyphens into ASCII_HYPH.
+ */
+static void
+post_hyph(POST_ARGS)
+{
+ struct roff_node *n, *nch;
+ char *cp;
+
+ n = mdoc->last;
+ for (nch = n->child; nch != NULL; nch = nch->next) {
+ if (nch->type != ROFFT_TEXT)
+ continue;
+ cp = nch->string;
+ if (*cp == '\0')
+ continue;
+ while (*(++cp) != '\0')
+ if (*cp == '-' &&
+ isalpha((unsigned char)cp[-1]) &&
+ isalpha((unsigned char)cp[1])) {
+ if (n->tag == NULL && n->flags & NODE_ID)
+ n->tag = mandoc_strdup(nch->string);
+ *cp = ASCII_HYPH;
+ }
+ }
+}
+
+static void
+post_ns(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ if (n->flags & NODE_LINE ||
+ (n->next != NULL && n->next->flags & NODE_DELIMC))
+ mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
+}
+
+static void
+post_sx(POST_ARGS)
+{
+ post_delim(mdoc);
+ post_hyph(mdoc);
+}
+
+static void
+post_sh(POST_ARGS)
+{
+ post_section(mdoc);
+
+ switch (mdoc->last->type) {
+ case ROFFT_HEAD:
+ post_sh_head(mdoc);
+ break;
+ case ROFFT_BODY:
+ switch (mdoc->lastsec) {
+ case SEC_NAME:
+ post_sh_name(mdoc);
+ break;
+ case SEC_SEE_ALSO:
+ post_sh_see_also(mdoc);
+ break;
+ case SEC_AUTHORS:
+ post_sh_authors(mdoc);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+post_sh_name(POST_ARGS)
+{
+ struct roff_node *n;
+ int hasnm, hasnd;
+
+ hasnm = hasnd = 0;
+
+ for (n = mdoc->last->child; n != NULL; n = n->next) {
+ switch (n->tok) {
+ case MDOC_Nm:
+ if (hasnm && n->child != NULL)
+ mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
+ n->line, n->pos,
+ "Nm %s", n->child->string);
+ hasnm = 1;
+ continue;
+ case MDOC_Nd:
+ hasnd = 1;
+ if (n->next != NULL)
+ mandoc_msg(MANDOCERR_NAMESEC_ND,
+ n->line, n->pos, NULL);
+ break;
+ case TOKEN_NONE:
+ if (n->type == ROFFT_TEXT &&
+ n->string[0] == ',' && n->string[1] == '\0' &&
+ n->next != NULL && n->next->tok == MDOC_Nm) {
+ n = n->next;
+ continue;
+ }
+ /* FALLTHROUGH */
+ default:
+ mandoc_msg(MANDOCERR_NAMESEC_BAD,
+ n->line, n->pos, "%s", roff_name[n->tok]);
+ continue;
+ }
+ break;
+ }
+
+ if ( ! hasnm)
+ mandoc_msg(MANDOCERR_NAMESEC_NONM,
+ mdoc->last->line, mdoc->last->pos, NULL);
+ if ( ! hasnd)
+ mandoc_msg(MANDOCERR_NAMESEC_NOND,
+ mdoc->last->line, mdoc->last->pos, NULL);
+}
+
+static void
+post_sh_see_also(POST_ARGS)
+{
+ const struct roff_node *n;
+ const char *name, *sec;
+ const char *lastname, *lastsec, *lastpunct;
+ int cmp;
+
+ n = mdoc->last->child;
+ lastname = lastsec = lastpunct = NULL;
+ while (n != NULL) {
+ if (n->tok != MDOC_Xr ||
+ n->child == NULL ||
+ n->child->next == NULL)
+ break;
+
+ /* Process one .Xr node. */
+
+ name = n->child->string;
+ sec = n->child->next->string;
+ if (lastsec != NULL) {
+ if (lastpunct[0] != ',' || lastpunct[1] != '\0')
+ mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
+ n->pos, "%s before %s(%s)",
+ lastpunct, name, sec);
+ cmp = strcmp(lastsec, sec);
+ if (cmp > 0)
+ mandoc_msg(MANDOCERR_XR_ORDER, n->line,
+ n->pos, "%s(%s) after %s(%s)",
+ name, sec, lastname, lastsec);
+ else if (cmp == 0 &&
+ strcasecmp(lastname, name) > 0)
+ mandoc_msg(MANDOCERR_XR_ORDER, n->line,
+ n->pos, "%s after %s", name, lastname);
+ }
+ lastname = name;
+ lastsec = sec;
+
+ /* Process the following node. */
+
+ n = n->next;
+ if (n == NULL)
+ break;
+ if (n->tok == MDOC_Xr) {
+ lastpunct = "none";
+ continue;
+ }
+ if (n->type != ROFFT_TEXT)
+ break;
+ for (name = n->string; *name != '\0'; name++)
+ if (isalpha((const unsigned char)*name))
+ return;
+ lastpunct = n->string;
+ if (n->next == NULL || n->next->tok == MDOC_Rs)
+ mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
+ n->pos, "%s after %s(%s)",
+ lastpunct, lastname, lastsec);
+ n = n->next;
+ }
+}
+
+static int
+child_an(const struct roff_node *n)
+{
+
+ for (n = n->child; n != NULL; n = n->next)
+ if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
+ return 1;
+ return 0;
+}
+
+static void
+post_sh_authors(POST_ARGS)
+{
+
+ if ( ! child_an(mdoc->last))
+ mandoc_msg(MANDOCERR_AN_MISSING,
+ mdoc->last->line, mdoc->last->pos, NULL);
+}
+
+/*
+ * Return an upper bound for the string distance (allowing
+ * transpositions). Not a full Levenshtein implementation
+ * because Levenshtein is quadratic in the string length
+ * and this function is called for every standard name,
+ * so the check for each custom name would be cubic.
+ * The following crude heuristics is linear, resulting
+ * in quadratic behaviour for checking one custom name,
+ * which does not cause measurable slowdown.
+ */
+static int
+similar(const char *s1, const char *s2)
+{
+ const int maxdist = 3;
+ int dist = 0;
+
+ while (s1[0] != '\0' && s2[0] != '\0') {
+ if (s1[0] == s2[0]) {
+ s1++;
+ s2++;
+ continue;
+ }
+ if (++dist > maxdist)
+ return INT_MAX;
+ if (s1[1] == s2[1]) { /* replacement */
+ s1++;
+ s2++;
+ } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
+ s1 += 2; /* transposition */
+ s2 += 2;
+ } else if (s1[0] == s2[1]) /* insertion */
+ s2++;
+ else if (s1[1] == s2[0]) /* deletion */
+ s1++;
+ else
+ return INT_MAX;
+ }
+ dist += strlen(s1) + strlen(s2);
+ return dist > maxdist ? INT_MAX : dist;
+}
+
+static void
+post_sh_head(POST_ARGS)
+{
+ struct roff_node *nch;
+ const char *goodsec;
+ const char *const *testsec;
+ int dist, mindist;
+ enum roff_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.
+ */
+
+ sec = mdoc->last->sec;
+
+ /* The NAME should be first. */
+
+ if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
+ mandoc_msg(MANDOCERR_NAMESEC_FIRST,
+ mdoc->last->line, mdoc->last->pos, "Sh %s",
+ sec != SEC_CUSTOM ? secnames[sec] :
+ (nch = mdoc->last->child) == NULL ? "" :
+ nch->type == ROFFT_TEXT ? nch->string :
+ roff_name[nch->tok]);
+
+ /* The SYNOPSIS gets special attention in other areas. */
+
+ if (sec == SEC_SYNOPSIS) {
+ roff_setreg(mdoc->roff, "nS", 1, '=');
+ mdoc->flags |= MDOC_SYNOPSIS;
+ } else {
+ roff_setreg(mdoc->roff, "nS", 0, '=');
+ mdoc->flags &= ~MDOC_SYNOPSIS;
+ }
+ if (sec == SEC_DESCRIPTION)
+ fn_prio = TAG_STRONG;
+
+ /* Mark our last section. */
+
+ mdoc->lastsec = sec;
+
+ /* We don't care about custom sections after this. */
+
+ if (sec == SEC_CUSTOM) {
+ if ((nch = mdoc->last->child) == NULL ||
+ nch->type != ROFFT_TEXT || nch->next != NULL)
+ return;
+ goodsec = NULL;
+ mindist = INT_MAX;
+ for (testsec = secnames + 1; *testsec != NULL; testsec++) {
+ dist = similar(nch->string, *testsec);
+ if (dist < mindist) {
+ goodsec = *testsec;
+ mindist = dist;
+ }
+ }
+ if (goodsec != NULL)
+ mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
+ "Sh %s instead of %s", nch->string, goodsec);
+ return;
+ }
+
+ /*
+ * Check whether our non-custom section is being repeated or is
+ * out of order.
+ */
+
+ if (sec == mdoc->lastnamed)
+ mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
+ mdoc->last->pos, "Sh %s", secnames[sec]);
+
+ if (sec < mdoc->lastnamed)
+ mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
+ mdoc->last->pos, "Sh %s", secnames[sec]);
+
+ /* Mark the last named section. */
+
+ mdoc->lastnamed = sec;
+
+ /* Check particular section/manual conventions. */
+
+ if (mdoc->meta.msec == NULL)
+ return;
+
+ goodsec = NULL;
+ switch (sec) {
+ case SEC_ERRORS:
+ if (*mdoc->meta.msec == '4')
+ break;
+ goodsec = "2, 3, 4, 9";
+ /* FALLTHROUGH */
+ case SEC_RETURN_VALUES:
+ 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_msg(MANDOCERR_SEC_MSEC,
+ mdoc->last->line, mdoc->last->pos,
+ "Sh %s for %s only", secnames[sec], goodsec);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+post_xr(POST_ARGS)
+{
+ struct roff_node *n, *nch;
+
+ n = mdoc->last;
+ nch = n->child;
+ if (nch->next == NULL) {
+ mandoc_msg(MANDOCERR_XR_NOSEC,
+ n->line, n->pos, "Xr %s", nch->string);
+ } else {
+ assert(nch->next == n->last);
+ if(mandoc_xr_add(nch->next->string, nch->string,
+ nch->line, nch->pos))
+ mandoc_msg(MANDOCERR_XR_SELF,
+ nch->line, nch->pos, "Xr %s %s",
+ nch->string, nch->next->string);
+ }
+ post_delim_nb(mdoc);
+}
+
+static void
+post_section(POST_ARGS)
+{
+ struct roff_node *n, *nch;
+ char *cp, *tag;
+
+ n = mdoc->last;
+ switch (n->type) {
+ case ROFFT_BLOCK:
+ post_prevpar(mdoc);
+ return;
+ case ROFFT_HEAD:
+ tag = NULL;
+ deroff(&tag, n);
+ if (tag != NULL) {
+ for (cp = tag; *cp != '\0'; cp++)
+ if (*cp == ' ')
+ *cp = '_';
+ if ((nch = n->child) != NULL &&
+ nch->type == ROFFT_TEXT &&
+ strcmp(nch->string, tag) == 0)
+ tag_put(NULL, TAG_WEAK, n);
+ else
+ tag_put(tag, TAG_FALLBACK, n);
+ free(tag);
+ }
+ post_delim(mdoc);
+ post_hyph(mdoc);
+ return;
+ case ROFFT_BODY:
+ break;
+ default:
+ return;
+ }
+ if ((nch = n->child) != NULL &&
+ (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
+ nch->tok == ROFF_sp)) {
+ mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
+ "%s after %s", roff_name[nch->tok],
+ roff_name[n->tok]);
+ roff_node_delete(mdoc, nch);
+ }
+ if ((nch = n->last) != NULL &&
+ (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
+ mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
+ "%s at the end of %s", roff_name[nch->tok],
+ roff_name[n->tok]);
+ roff_node_delete(mdoc, nch);
+ }
+}
+
+static void
+post_prevpar(POST_ARGS)
+{
+ struct roff_node *n, *np;
+
+ n = mdoc->last;
+ if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
+ return;
+ if ((np = roff_node_prev(n)) == NULL)
+ return;
+
+ /*
+ * Don't allow `Pp' prior to a paragraph-type
+ * block: `Pp' or non-compact `Bd' or `Bl'.
+ */
+
+ if (np->tok != MDOC_Pp && np->tok != ROFF_br)
+ return;
+ if (n->tok == MDOC_Bl && n->norm->Bl.comp)
+ return;
+ if (n->tok == MDOC_Bd && n->norm->Bd.comp)
+ return;
+ if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
+ return;
+
+ mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
+ "%s before %s", roff_name[np->tok], roff_name[n->tok]);
+ roff_node_delete(mdoc, np);
+}
+
+static void
+post_par(POST_ARGS)
+{
+ struct roff_node *np;
+
+ fn_prio = TAG_STRONG;
+ post_prevpar(mdoc);
+
+ np = mdoc->last;
+ if (np->child != NULL)
+ mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
+ "%s %s", roff_name[np->tok], np->child->string);
+}
+
+static void
+post_dd(POST_ARGS)
+{
+ struct roff_node *n;
+
+ n = mdoc->last;
+ n->flags |= NODE_NOPRT;
+
+ if (mdoc->meta.date != NULL) {
+ mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
+ free(mdoc->meta.date);
+ } else if (mdoc->flags & MDOC_PBODY)
+ mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
+ else if (mdoc->meta.title != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_ORDER,
+ n->line, n->pos, "Dd after Dt");
+ else if (mdoc->meta.os != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_ORDER,
+ n->line, n->pos, "Dd after Os");
+
+ if (mdoc->quick && n != NULL)
+ mdoc->meta.date = mandoc_strdup("");
+ else
+ mdoc->meta.date = mandoc_normdate(n->child, n);
+}
+
+static void
+post_dt(POST_ARGS)
+{
+ struct roff_node *nn, *n;
+ const char *cp;
+ char *p;
+
+ n = mdoc->last;
+ n->flags |= NODE_NOPRT;
+
+ if (mdoc->flags & MDOC_PBODY) {
+ mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
+ return;
+ }
+
+ if (mdoc->meta.title != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
+ else if (mdoc->meta.os != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_ORDER,
+ n->line, n->pos, "Dt after Os");
+
+ 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;
+
+ /* Mandatory first argument: title. */
+
+ nn = n->child;
+ if (nn == NULL || *nn->string == '\0') {
+ mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
+ mdoc->meta.title = mandoc_strdup("UNTITLED");
+ } else {
+ mdoc->meta.title = mandoc_strdup(nn->string);
+
+ /* Check that all characters are uppercase. */
+
+ for (p = nn->string; *p != '\0'; p++)
+ if (islower((unsigned char)*p)) {
+ mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
+ nn->pos + (int)(p - nn->string),
+ "Dt %s", nn->string);
+ break;
+ }
+ }
+
+ /* Mandatory second argument: section. */
+
+ if (nn != NULL)
+ nn = nn->next;
+
+ if (nn == NULL) {
+ mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
+ "Dt %s", mdoc->meta.title);
+ mdoc->meta.vol = mandoc_strdup("LOCAL");
+ return; /* msec and arch remain NULL. */
+ }
+
+ mdoc->meta.msec = mandoc_strdup(nn->string);
+
+ /* Infer volume title from section number. */
+
+ cp = mandoc_a2msec(nn->string);
+ if (cp == NULL) {
+ mandoc_msg(MANDOCERR_MSEC_BAD,
+ nn->line, nn->pos, "Dt ... %s", nn->string);
+ mdoc->meta.vol = mandoc_strdup(nn->string);
+ } else {
+ mdoc->meta.vol = mandoc_strdup(cp);
+ if (mdoc->filesec != '\0' &&
+ mdoc->filesec != *nn->string &&
+ *nn->string >= '1' && *nn->string <= '9')
+ mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos,
+ "*.%c vs Dt ... %c", mdoc->filesec, *nn->string);
+ }
+
+ /* Optional third argument: architecture. */
+
+ if ((nn = nn->next) == NULL)
+ return;
+
+ for (p = nn->string; *p != '\0'; p++)
+ *p = tolower((unsigned char)*p);
+ mdoc->meta.arch = mandoc_strdup(nn->string);
+
+ /* Ignore fourth and later arguments. */
+
+ if ((nn = nn->next) != NULL)
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ nn->line, nn->pos, "Dt ... %s", nn->string);
+}
+
+static void
+post_bx(POST_ARGS)
+{
+ struct roff_node *n, *nch;
+ const char *macro;
+
+ post_delim_nb(mdoc);
+
+ n = mdoc->last;
+ nch = n->child;
+
+ if (nch != NULL) {
+ macro = !strcmp(nch->string, "Open") ? "Ox" :
+ !strcmp(nch->string, "Net") ? "Nx" :
+ !strcmp(nch->string, "Free") ? "Fx" :
+ !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
+ if (macro != NULL)
+ mandoc_msg(MANDOCERR_BX,
+ n->line, n->pos, "%s", macro);
+ mdoc->last = nch;
+ nch = nch->next;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ } else
+ mdoc->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(mdoc, n->line, n->pos, "BSD");
+ mdoc->last->flags |= NODE_NOSRC;
+
+ if (nch == NULL) {
+ mdoc->last = n;
+ return;
+ }
+
+ roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->next = ROFF_NEXT_SIBLING;
+ roff_word_alloc(mdoc, n->line, n->pos, "-");
+ mdoc->last->flags |= NODE_NOSRC;
+ roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
+ mdoc->last->flags |= NODE_NOSRC;
+ mdoc->last = 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.
+ */
+
+ *nch->string = (char)toupper((unsigned char)*nch->string);
+}
+
+static void
+post_os(POST_ARGS)
+{
+#ifndef OSNAME
+ struct utsname utsname;
+ static char *defbuf;
+#endif
+ struct roff_node *n;
+
+ n = mdoc->last;
+ n->flags |= NODE_NOPRT;
+
+ if (mdoc->meta.os != NULL)
+ mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
+ else if (mdoc->flags & MDOC_PBODY)
+ mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
+
+ post_delim(mdoc);
+
+ /*
+ * 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;
+ deroff(&mdoc->meta.os, n);
+ if (mdoc->meta.os)
+ goto out;
+
+ if (mdoc->os_s != NULL) {
+ mdoc->meta.os = mandoc_strdup(mdoc->os_s);
+ goto out;
+ }
+
+#ifdef OSNAME
+ mdoc->meta.os = mandoc_strdup(OSNAME);
+#else /*!OSNAME */
+ if (defbuf == NULL) {
+ if (uname(&utsname) == -1) {
+ mandoc_msg(MANDOCERR_OS_UNAME, 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:
+ if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
+ if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
+ mdoc->meta.os_e = MANDOC_OS_OPENBSD;
+ else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
+ mdoc->meta.os_e = MANDOC_OS_NETBSD;
+ }
+
+ /*
+ * This is the earliest point where we can check
+ * Mdocdate conventions because we don't know
+ * the operating system earlier.
+ */
+
+ if (n->child != NULL)
+ mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
+ "Os %s (%s)", n->child->string,
+ mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
+ "OpenBSD" : "NetBSD");
+
+ while (n->tok != MDOC_Dd)
+ if ((n = n->prev) == NULL)
+ return;
+ if ((n = n->child) == NULL)
+ return;
+ if (strncmp(n->string, "$" "Mdocdate", 9)) {
+ if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
+ mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
+ n->pos, "Dd %s (OpenBSD)", n->string);
+ } else {
+ if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
+ mandoc_msg(MANDOCERR_MDOCDATE, n->line,
+ n->pos, "Dd %s (NetBSD)", n->string);
+ }
+}
+
+enum roff_sec
+mdoc_a2sec(const char *p)
+{
+ int i;
+
+ for (i = 0; i < (int)SEC__MAX; i++)
+ if (secnames[i] && 0 == strcmp(p, secnames[i]))
+ return (enum roff_sec)i;
+
+ return SEC_CUSTOM;
+}
+
+static size_t
+macro2len(enum roff_tok 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/usr.bin/mandoc/msec.c b/usr.bin/mandoc/msec.c
new file mode 100644
index 0000000..813a140
--- /dev/null
+++ b/usr.bin/mandoc/msec.c
@@ -0,0 +1,35 @@
+/* $OpenBSD: msec.c,v 1.13 2018/12/14 01:17:46 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.
+ */
+#include <sys/types.h>
+
+#include <stdio.h>
+#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/usr.bin/mandoc/msec.in b/usr.bin/mandoc/msec.in
new file mode 100644
index 0000000..f8d786e
--- /dev/null
+++ b/usr.bin/mandoc/msec.in
@@ -0,0 +1,34 @@
+/* $OpenBSD: msec.in,v 1.6 2017/06/24 17:36:50 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.
+ */
+
+/*
+ * These are all possible manual-section macros and what they correspond
+ * to when rendered as the volume title.
+ *
+ * Be sure to escape strings.
+ */
+
+LINE("1", "General Commands Manual")
+LINE("2", "System Calls Manual")
+LINE("3", "Library Functions Manual")
+LINE("3p", "Perl Library Manual")
+LINE("4", "Device Drivers Manual")
+LINE("5", "File Formats Manual")
+LINE("6", "Games Manual")
+LINE("7", "Miscellaneous Information Manual")
+LINE("8", "System Manager\'s Manual")
+LINE("9", "Kernel Developer\'s Manual")
diff --git a/usr.bin/mandoc/out.c b/usr.bin/mandoc/out.c
new file mode 100644
index 0000000..7cc5702
--- /dev/null
+++ b/usr.bin/mandoc/out.c
@@ -0,0 +1,563 @@
+/* $OpenBSD: out.c,v 1.51 2019/12/31 22:49:17 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011,2014,2015,2017,2018 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc_aux.h"
+#include "tbl.h"
+#include "out.h"
+
+struct tbl_colgroup {
+ struct tbl_colgroup *next;
+ size_t wanted;
+ int startcol;
+ int endcol;
+};
+
+static size_t tblcalc_data(struct rofftbl *, struct roffcol *,
+ const struct tbl_opts *, const struct tbl_dat *,
+ size_t);
+static size_t tblcalc_literal(struct rofftbl *, struct roffcol *,
+ const struct tbl_dat *, size_t);
+static size_t tblcalc_number(struct rofftbl *, struct roffcol *,
+ const struct tbl_opts *, const struct tbl_dat *);
+
+
+/*
+ * Parse the *src string and store a scaling unit into *dst.
+ * If the string doesn't specify the unit, use the default.
+ * If no default is specified, fail.
+ * Return a pointer to the byte after the last byte used,
+ * or NULL on total failure.
+ */
+const char *
+a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
+{
+ char *endptr;
+
+ dst->unit = def == SCALE_MAX ? SCALE_BU : def;
+ dst->scale = strtod(src, &endptr);
+ if (endptr == src)
+ return NULL;
+
+ switch (*endptr++) {
+ case 'c':
+ dst->unit = SCALE_CM;
+ break;
+ case 'i':
+ dst->unit = SCALE_IN;
+ break;
+ case 'f':
+ dst->unit = SCALE_FS;
+ break;
+ case 'M':
+ dst->unit = SCALE_MM;
+ break;
+ case 'm':
+ dst->unit = SCALE_EM;
+ break;
+ case 'n':
+ dst->unit = SCALE_EN;
+ break;
+ case 'P':
+ dst->unit = SCALE_PC;
+ break;
+ case 'p':
+ dst->unit = SCALE_PT;
+ break;
+ case 'u':
+ dst->unit = SCALE_BU;
+ break;
+ case 'v':
+ dst->unit = SCALE_VS;
+ break;
+ default:
+ endptr--;
+ if (SCALE_MAX == def)
+ return NULL;
+ dst->unit = def;
+ break;
+ }
+ return endptr;
+}
+
+/*
+ * 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_first,
+ size_t offset, size_t rmargin)
+{
+ struct roffsu su;
+ const struct tbl_opts *opts;
+ const struct tbl_span *sp;
+ const struct tbl_dat *dp;
+ struct roffcol *col;
+ struct tbl_colgroup *first_group, **gp, *g;
+ size_t *colwidth;
+ size_t ewidth, min1, min2, wanted, width, xwidth;
+ int done, icol, maxcol, necol, nxcol, quirkcol;
+
+ /*
+ * 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(tbl->cols == NULL);
+ tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols,
+ sizeof(struct roffcol));
+ opts = sp_first->opts;
+
+ maxcol = -1;
+ first_group = NULL;
+ for (sp = sp_first; sp != NULL; sp = sp->next) {
+ if (sp->pos != TBL_SPAN_DATA)
+ continue;
+
+ /*
+ * Account for the data cells in the layout, matching it
+ * to data cells in the data section.
+ */
+
+ gp = &first_group;
+ for (dp = sp->first; dp != NULL; dp = dp->next) {
+ icol = dp->layout->col;
+ while (maxcol < icol + dp->hspans)
+ tbl->cols[++maxcol].spacing = SIZE_MAX;
+ col = tbl->cols + icol;
+ col->flags |= dp->layout->flags;
+ if (dp->layout->flags & TBL_CELL_WIGN)
+ continue;
+
+ /* Handle explicit width specifications. */
+
+ if (dp->layout->wstr != NULL &&
+ dp->layout->width == 0 &&
+ a2roffsu(dp->layout->wstr, &su, SCALE_EN)
+ != NULL)
+ dp->layout->width =
+ (*tbl->sulen)(&su, tbl->arg);
+ if (col->width < dp->layout->width)
+ col->width = dp->layout->width;
+ if (dp->layout->spacing != SIZE_MAX &&
+ (col->spacing == SIZE_MAX ||
+ col->spacing < dp->layout->spacing))
+ col->spacing = dp->layout->spacing;
+
+ /*
+ * Calculate an automatic width.
+ * Except for spanning cells, apply it.
+ */
+
+ width = tblcalc_data(tbl,
+ dp->hspans == 0 ? col : NULL,
+ opts, dp,
+ dp->block == 0 ? 0 :
+ dp->layout->width ? dp->layout->width :
+ rmargin ? (rmargin + sp->opts->cols / 2)
+ / (sp->opts->cols + 1) : 0);
+ if (dp->hspans == 0)
+ continue;
+
+ /*
+ * Build an ordered, singly linked list
+ * of all groups of columns joined by spans,
+ * recording the minimum width for each group.
+ */
+
+ while (*gp != NULL && ((*gp)->startcol < icol ||
+ (*gp)->endcol < icol + dp->hspans))
+ gp = &(*gp)->next;
+ if (*gp == NULL || (*gp)->startcol > icol ||
+ (*gp)->endcol > icol + dp->hspans) {
+ g = mandoc_malloc(sizeof(*g));
+ g->next = *gp;
+ g->wanted = width;
+ g->startcol = icol;
+ g->endcol = icol + dp->hspans;
+ *gp = g;
+ } else if ((*gp)->wanted < width)
+ (*gp)->wanted = width;
+ }
+ }
+
+ /*
+ * The minimum width of columns explicitly specified
+ * in the layout is 1n.
+ */
+
+ if (maxcol < sp_first->opts->cols - 1)
+ maxcol = sp_first->opts->cols - 1;
+ for (icol = 0; icol <= maxcol; icol++) {
+ col = tbl->cols + icol;
+ if (col->width < 1)
+ col->width = 1;
+
+ /*
+ * Column spacings are needed for span width
+ * calculations, so set the default values now.
+ */
+
+ if (col->spacing == SIZE_MAX || icol == maxcol)
+ col->spacing = 3;
+ }
+
+ /*
+ * Replace the minimum widths with the missing widths,
+ * and dismiss groups that are already wide enough.
+ */
+
+ gp = &first_group;
+ while ((g = *gp) != NULL) {
+ done = 0;
+ for (icol = g->startcol; icol <= g->endcol; icol++) {
+ width = tbl->cols[icol].width;
+ if (icol < g->endcol)
+ width += tbl->cols[icol].spacing;
+ if (g->wanted <= width) {
+ done = 1;
+ break;
+ } else
+ (*gp)->wanted -= width;
+ }
+ if (done) {
+ *gp = g->next;
+ free(g);
+ } else
+ gp = &(*gp)->next;
+ }
+
+ colwidth = mandoc_reallocarray(NULL, maxcol + 1, sizeof(*colwidth));
+ while (first_group != NULL) {
+
+ /*
+ * Rebuild the array of the widths of all columns
+ * participating in spans that require expansion.
+ */
+
+ for (icol = 0; icol <= maxcol; icol++)
+ colwidth[icol] = SIZE_MAX;
+ for (g = first_group; g != NULL; g = g->next)
+ for (icol = g->startcol; icol <= g->endcol; icol++)
+ colwidth[icol] = tbl->cols[icol].width;
+
+ /*
+ * Find the smallest and second smallest column width
+ * among the columns which may need expamsion.
+ */
+
+ min1 = min2 = SIZE_MAX;
+ for (icol = 0; icol <= maxcol; icol++) {
+ if (min1 > colwidth[icol]) {
+ min2 = min1;
+ min1 = colwidth[icol];
+ } else if (min1 < colwidth[icol] &&
+ min2 > colwidth[icol])
+ min2 = colwidth[icol];
+ }
+
+ /*
+ * Find the minimum wanted width
+ * for any one of the narrowest columns,
+ * and mark the columns wanting that width.
+ */
+
+ wanted = min2;
+ for (g = first_group; g != NULL; g = g->next) {
+ necol = 0;
+ for (icol = g->startcol; icol <= g->endcol; icol++)
+ if (tbl->cols[icol].width == min1)
+ necol++;
+ if (necol == 0)
+ continue;
+ width = min1 + (g->wanted - 1) / necol + 1;
+ if (width > min2)
+ width = min2;
+ if (wanted > width)
+ wanted = width;
+ for (icol = g->startcol; icol <= g->endcol; icol++)
+ if (colwidth[icol] == min1 ||
+ (colwidth[icol] < min2 &&
+ colwidth[icol] > width))
+ colwidth[icol] = width;
+ }
+
+ /* Record the effect of the widening on the group list. */
+
+ gp = &first_group;
+ while ((g = *gp) != NULL) {
+ done = 0;
+ for (icol = g->startcol; icol <= g->endcol; icol++) {
+ if (colwidth[icol] != wanted ||
+ tbl->cols[icol].width == wanted)
+ continue;
+ if (g->wanted <= wanted - min1) {
+ done = 1;
+ break;
+ }
+ g->wanted -= wanted - min1;
+ }
+ if (done) {
+ *gp = g->next;
+ free(g);
+ } else
+ gp = &(*gp)->next;
+ }
+
+ /* Record the effect of the widening on the columns. */
+
+ for (icol = 0; icol <= maxcol; icol++)
+ if (colwidth[icol] == wanted)
+ tbl->cols[icol].width = wanted;
+ }
+ free(colwidth);
+
+ /*
+ * Align numbers with text.
+ * Count columns to equalize and columns to maximize.
+ * Find maximum width of the columns to equalize.
+ * Find total width of the columns *not* to maximize.
+ */
+
+ necol = nxcol = 0;
+ ewidth = xwidth = 0;
+ for (icol = 0; icol <= maxcol; icol++) {
+ col = tbl->cols + icol;
+ if (col->width > col->nwidth)
+ col->decimal += (col->width - col->nwidth) / 2;
+ else
+ col->width = col->nwidth;
+ if (col->flags & TBL_CELL_EQUAL) {
+ necol++;
+ if (ewidth < col->width)
+ ewidth = col->width;
+ }
+ if (col->flags & TBL_CELL_WMAX)
+ nxcol++;
+ else
+ xwidth += col->width;
+ }
+
+ /*
+ * Equalize columns, if requested for any of them.
+ * Update total width of the columns not to maximize.
+ */
+
+ if (necol) {
+ for (icol = 0; icol <= maxcol; icol++) {
+ col = tbl->cols + icol;
+ if ( ! (col->flags & TBL_CELL_EQUAL))
+ continue;
+ if (col->width == ewidth)
+ continue;
+ if (nxcol && rmargin)
+ xwidth += ewidth - col->width;
+ col->width = ewidth;
+ }
+ }
+
+ /*
+ * If there are any columns to maximize, find the total
+ * available width, deducting 3n margins between columns.
+ * Distribute the available width evenly.
+ */
+
+ if (nxcol && rmargin) {
+ xwidth += 3*maxcol +
+ (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
+ 2 : !!opts->lvert + !!opts->rvert);
+ if (rmargin <= offset + xwidth)
+ return;
+ xwidth = rmargin - offset - xwidth;
+
+ /*
+ * Emulate a bug in GNU tbl width calculation that
+ * manifests itself for large numbers of x-columns.
+ * Emulating it for 5 x-columns gives identical
+ * behaviour for up to 6 x-columns.
+ */
+
+ if (nxcol == 5) {
+ quirkcol = xwidth % nxcol + 2;
+ if (quirkcol != 3 && quirkcol != 4)
+ quirkcol = -1;
+ } else
+ quirkcol = -1;
+
+ necol = 0;
+ ewidth = 0;
+ for (icol = 0; icol <= maxcol; icol++) {
+ col = tbl->cols + icol;
+ if ( ! (col->flags & TBL_CELL_WMAX))
+ continue;
+ col->width = (double)xwidth * ++necol / nxcol
+ - ewidth + 0.4995;
+ if (necol == quirkcol)
+ col->width--;
+ ewidth += col->width;
+ }
+ }
+}
+
+static size_t
+tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
+ const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw)
+{
+ size_t sz;
+
+ /* Branch down into data sub-types. */
+
+ switch (dp->layout->pos) {
+ case TBL_CELL_HORIZ:
+ case TBL_CELL_DHORIZ:
+ sz = (*tbl->len)(1, tbl->arg);
+ if (col != NULL && col->width < sz)
+ col->width = sz;
+ return sz;
+ case TBL_CELL_LONG:
+ case TBL_CELL_CENTRE:
+ case TBL_CELL_LEFT:
+ case TBL_CELL_RIGHT:
+ return tblcalc_literal(tbl, col, dp, mw);
+ case TBL_CELL_NUMBER:
+ return tblcalc_number(tbl, col, opts, dp);
+ case TBL_CELL_DOWN:
+ return 0;
+ default:
+ abort();
+ }
+}
+
+static size_t
+tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
+ const struct tbl_dat *dp, size_t mw)
+{
+ const char *str; /* Beginning of the first line. */
+ const char *beg; /* Beginning of the current line. */
+ char *end; /* End of the current line. */
+ size_t lsz; /* Length of the current line. */
+ size_t wsz; /* Length of the current word. */
+ size_t msz; /* Length of the longest line. */
+
+ if (dp->string == NULL || *dp->string == '\0')
+ return 0;
+ str = mw ? mandoc_strdup(dp->string) : dp->string;
+ msz = lsz = 0;
+ for (beg = str; beg != NULL && *beg != '\0'; beg = end) {
+ end = mw ? strchr(beg, ' ') : NULL;
+ if (end != NULL) {
+ *end++ = '\0';
+ while (*end == ' ')
+ end++;
+ }
+ wsz = (*tbl->slen)(beg, tbl->arg);
+ if (mw && lsz && lsz + 1 + wsz <= mw)
+ lsz += 1 + wsz;
+ else
+ lsz = wsz;
+ if (msz < lsz)
+ msz = lsz;
+ }
+ if (mw)
+ free((void *)str);
+ if (col != NULL && col->width < msz)
+ col->width = msz;
+ return msz;
+}
+
+static size_t
+tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
+ const struct tbl_opts *opts, const struct tbl_dat *dp)
+{
+ const char *cp, *lastdigit, *lastpoint;
+ size_t intsz, totsz;
+ char buf[2];
+
+ if (dp->string == NULL || *dp->string == '\0')
+ return 0;
+
+ totsz = (*tbl->slen)(dp->string, tbl->arg);
+ if (col == NULL)
+ return totsz;
+
+ /*
+ * Find the last digit and
+ * the last decimal point that is adjacent to a digit.
+ * The alignment indicator "\&" overrides everything.
+ */
+
+ lastdigit = lastpoint = NULL;
+ for (cp = dp->string; cp[0] != '\0'; cp++) {
+ if (cp[0] == '\\' && cp[1] == '&') {
+ lastdigit = lastpoint = cp;
+ break;
+ } else if (cp[0] == opts->decimal &&
+ (isdigit((unsigned char)cp[1]) ||
+ (cp > dp->string && isdigit((unsigned char)cp[-1]))))
+ lastpoint = cp;
+ else if (isdigit((unsigned char)cp[0]))
+ lastdigit = cp;
+ }
+
+ /* Not a number, treat as a literal string. */
+
+ if (lastdigit == NULL) {
+ if (col != NULL && col->width < totsz)
+ col->width = totsz;
+ return totsz;
+ }
+
+ /* Measure the width of the integer part. */
+
+ if (lastpoint == NULL)
+ lastpoint = lastdigit + 1;
+ intsz = 0;
+ buf[1] = '\0';
+ for (cp = dp->string; cp < lastpoint; cp++) {
+ buf[0] = cp[0];
+ intsz += (*tbl->slen)(buf, tbl->arg);
+ }
+
+ /*
+ * If this number has more integer digits than all numbers
+ * seen on earlier lines, shift them all to the right.
+ * If it has fewer, shift this number to the right.
+ */
+
+ if (intsz > col->decimal) {
+ col->nwidth += intsz - col->decimal;
+ col->decimal = intsz;
+ } else
+ totsz += col->decimal - intsz;
+
+ /* Update the maximum total width seen so far. */
+
+ if (totsz > col->nwidth)
+ col->nwidth = totsz;
+ return totsz;
+}
diff --git a/usr.bin/mandoc/out.h b/usr.bin/mandoc/out.h
new file mode 100644
index 0000000..fcd691c
--- /dev/null
+++ b/usr.bin/mandoc/out.h
@@ -0,0 +1,70 @@
+/* $OpenBSD: out.h,v 1.25 2020/04/03 11:34:19 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2017, 2018 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.
+ *
+ * Utilities for use by multiple mandoc(1) formatters.
+ */
+
+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 nwidth; /* max. width of number in cell */
+ size_t decimal; /* decimal position in cell */
+ size_t spacing; /* spacing after the column */
+ int flags; /* layout flags, see tbl_cell */
+};
+
+struct roffsu {
+ enum roffscale unit;
+ double scale;
+};
+
+typedef size_t (*tbl_sulen)(const struct roffsu *, void *);
+typedef size_t (*tbl_strlen)(const char *, void *);
+typedef size_t (*tbl_len)(size_t, void *);
+
+struct rofftbl {
+ tbl_sulen sulen; /* calculate scaling unit length */
+ tbl_strlen slen; /* calculate string length */
+ tbl_len len; /* produce width of empty space */
+ struct roffcol *cols; /* master column specifiers */
+ void *arg; /* passed to sulen, slen, and len */
+};
+
+#define SCALE_HS_INIT(p, v) \
+ do { (p)->unit = SCALE_EN; \
+ (p)->scale = (v); } \
+ while (/* CONSTCOND */ 0)
+
+
+struct tbl_span;
+
+const char *a2roffsu(const char *, struct roffsu *, enum roffscale);
+void tblcalc(struct rofftbl *,
+ const struct tbl_span *, size_t, size_t);
diff --git a/usr.bin/mandoc/preconv.c b/usr.bin/mandoc/preconv.c
new file mode 100644
index 0000000..2cbdcda
--- /dev/null
+++ b/usr.bin/mandoc/preconv.c
@@ -0,0 +1,177 @@
+/* $OpenBSD: preconv.c,v 1.9 2018/12/13 11:55:14 schwarze Exp $ */
+/*
+ * Copyright (c) 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "mandoc_parse.h"
+#include "libmandoc.h"
+
+int
+preconv_encode(const struct buf *ib, size_t *ii, struct buf *ob, size_t *oi,
+ int *filenc)
+{
+ const unsigned char *cu;
+ int nby;
+ unsigned int accum;
+
+ cu = (const unsigned char *)ib->buf + *ii;
+ assert(*cu & 0x80);
+
+ if ( ! (*filenc & MPARSE_UTF8))
+ goto latin;
+
+ nby = 1;
+ while (nby < 5 && *cu & (1 << (7 - nby)))
+ nby++;
+
+ switch (nby) {
+ case 2:
+ accum = *cu & 0x1f;
+ if (accum < 0x02) /* Obfuscated ASCII. */
+ goto latin;
+ break;
+ case 3:
+ accum = *cu & 0x0f;
+ break;
+ case 4:
+ accum = *cu & 0x07;
+ if (accum > 0x04) /* Beyond Unicode. */
+ goto latin;
+ break;
+ default: /* Bad sequence header. */
+ goto latin;
+ }
+
+ cu++;
+ switch (nby) {
+ case 3:
+ if ((accum == 0x00 && ! (*cu & 0x20)) || /* Use 2-byte. */
+ (accum == 0x0d && *cu & 0x20)) /* Surrogates. */
+ goto latin;
+ break;
+ case 4:
+ if ((accum == 0x00 && ! (*cu & 0x30)) || /* Use 3-byte. */
+ (accum == 0x04 && *cu & 0x30)) /* Beyond Unicode. */
+ goto latin;
+ break;
+ default:
+ break;
+ }
+
+ while (--nby) {
+ if ((*cu & 0xc0) != 0x80) /* Invalid continuation. */
+ goto latin;
+ accum <<= 6;
+ accum += *cu & 0x3f;
+ cu++;
+ }
+
+ assert(accum > 0x7f);
+ assert(accum < 0x110000);
+ assert(accum < 0xd800 || accum > 0xdfff);
+
+ *oi += snprintf(ob->buf + *oi, 11, "\\[u%.4X]", accum);
+ *ii = (const char *)cu - ib->buf;
+ *filenc &= ~MPARSE_LATIN1;
+ return 1;
+
+latin:
+ if ( ! (*filenc & MPARSE_LATIN1))
+ return 0;
+
+ *oi += snprintf(ob->buf + *oi, 11,
+ "\\[u%.4X]", (unsigned char)ib->buf[(*ii)++]);
+
+ *filenc &= ~MPARSE_UTF8;
+ return 1;
+}
+
+int
+preconv_cue(const struct buf *b, size_t offset)
+{
+ const char *ln, *eoln, *eoph;
+ size_t sz, phsz;
+
+ ln = b->buf + offset;
+ sz = b->sz - offset;
+
+ /* Look for the end-of-line. */
+
+ if (NULL == (eoln = memchr(ln, '\n', sz)))
+ eoln = ln + sz;
+
+ /* Check if we have the correct header/trailer. */
+
+ if ((sz = (size_t)(eoln - ln)) < 10 ||
+ memcmp(ln, ".\\\" -*-", 7) || memcmp(eoln - 3, "-*-", 3))
+ return MPARSE_UTF8 | MPARSE_LATIN1;
+
+ /* Move after the header and adjust for the trailer. */
+
+ ln += 7;
+ sz -= 10;
+
+ while (sz > 0) {
+ while (sz > 0 && ' ' == *ln) {
+ ln++;
+ sz--;
+ }
+ if (0 == sz)
+ break;
+
+ /* Find the end-of-phrase marker (or eoln). */
+
+ if (NULL == (eoph = memchr(ln, ';', sz)))
+ eoph = eoln - 3;
+ else
+ eoph++;
+
+ /* Only account for the "coding" phrase. */
+
+ if ((phsz = eoph - ln) < 7 ||
+ strncasecmp(ln, "coding:", 7)) {
+ sz -= phsz;
+ ln += phsz;
+ continue;
+ }
+
+ sz -= 7;
+ ln += 7;
+
+ while (sz > 0 && ' ' == *ln) {
+ ln++;
+ sz--;
+ }
+ if (0 == sz)
+ return 0;
+
+ /* Check us against known encodings. */
+
+ if (phsz > 4 && !strncasecmp(ln, "utf-8", 5))
+ return MPARSE_UTF8;
+ if (phsz > 10 && !strncasecmp(ln, "iso-latin-1", 11))
+ return MPARSE_LATIN1;
+ return 0;
+ }
+ return MPARSE_UTF8 | MPARSE_LATIN1;
+}
diff --git a/usr.bin/mandoc/predefs.in b/usr.bin/mandoc/predefs.in
new file mode 100644
index 0000000..f022028
--- /dev/null
+++ b/usr.bin/mandoc/predefs.in
@@ -0,0 +1,65 @@
+/* $OpenBSD: predefs.in,v 1.4 2014/11/28 19:25:03 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/usr.bin/mandoc/read.c b/usr.bin/mandoc/read.c
new file mode 100644
index 0000000..a6a25de
--- /dev/null
+++ b/usr.bin/mandoc/read.c
@@ -0,0 +1,727 @@
+/* $OpenBSD: read.c,v 1.190 2020/04/24 11:58:02 schwarze Exp $ */
+/*
+ * Copyright (c) 2010-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * 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 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.
+ *
+ * Top-level functions of the mandoc(3) parser:
+ * Parser and input encoding selection, decompression,
+ * handling of input bytes, characters, lines, and files,
+ * handling of roff(7) loops and file inclusion,
+ * and steering of the various parsers.
+ */
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "man.h"
+#include "mandoc_parse.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "tag.h"
+
+#define REPARSE_LIMIT 1000
+
+struct mparse {
+ struct roff *roff; /* roff parser (!NULL) */
+ struct roff_man *man; /* man parser */
+ struct buf *primary; /* buffer currently being parsed */
+ struct buf *secondary; /* copy of top level input */
+ struct buf *loop; /* open .while request line */
+ const char *os_s; /* default operating system */
+ int options; /* parser options */
+ int gzip; /* current input file is gzipped */
+ int filenc; /* encoding of the current file */
+ int reparse_count; /* finite interp. stack */
+ int line; /* line number in the file */
+};
+
+static void choose_parser(struct mparse *);
+static void free_buf_list(struct buf *);
+static void resize_buf(struct buf *, size_t);
+static int mparse_buf_r(struct mparse *, struct buf, size_t, int);
+static int read_whole_file(struct mparse *, int, struct buf *, int *);
+static void mparse_end(struct mparse *);
+
+
+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
+free_buf_list(struct buf *buf)
+{
+ struct buf *tmp;
+
+ while (buf != NULL) {
+ tmp = buf;
+ buf = tmp->next;
+ free(tmp->buf);
+ free(tmp);
+ }
+}
+
+static void
+choose_parser(struct mparse *curp)
+{
+ char *cp, *ep;
+ int format;
+
+ /*
+ * If neither command line arguments -mdoc or -man select
+ * a parser nor the roff parser found a .Dd or .TH macro
+ * yet, look ahead in the main input buffer.
+ */
+
+ if ((format = roff_getformat(curp->roff)) == 0) {
+ cp = curp->primary->buf;
+ ep = cp + curp->primary->sz;
+ while (cp < ep) {
+ if (*cp == '.' || *cp == '\'') {
+ cp++;
+ if (cp[0] == 'D' && cp[1] == 'd') {
+ format = MPARSE_MDOC;
+ break;
+ }
+ if (cp[0] == 'T' && cp[1] == 'H') {
+ format = MPARSE_MAN;
+ break;
+ }
+ }
+ cp = memchr(cp, '\n', ep - cp);
+ if (cp == NULL)
+ break;
+ cp++;
+ }
+ }
+
+ if (format == MPARSE_MDOC) {
+ curp->man->meta.macroset = MACROSET_MDOC;
+ if (curp->man->mdocmac == NULL)
+ curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX);
+ } else {
+ curp->man->meta.macroset = MACROSET_MAN;
+ if (curp->man->manmac == NULL)
+ curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX);
+ }
+ curp->man->meta.first->tok = TOKEN_NONE;
+}
+
+/*
+ * Main parse routine for a buffer.
+ * It assumes encoding and line numbering are already set up.
+ * It can recurse directly (for invocations of user-defined
+ * macros, inline equations, and input line traps)
+ * and indirectly (for .so file inclusion).
+ */
+static int
+mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
+{
+ struct buf ln;
+ struct buf *firstln, *lastln, *thisln, *loop;
+ char *cp;
+ size_t pos; /* byte number in the ln buffer */
+ int line_result, result;
+ int of;
+ int lnn; /* line number in the real file */
+ int fd;
+ int inloop; /* Saw .while on this level. */
+ unsigned char c;
+
+ ln.sz = 256;
+ ln.buf = mandoc_malloc(ln.sz);
+ ln.next = NULL;
+ firstln = lastln = loop = NULL;
+ lnn = curp->line;
+ pos = 0;
+ inloop = 0;
+ result = ROFF_CONT;
+
+ while (i < blk.sz && (blk.buf[i] != '\0' || pos != 0)) {
+ if (start) {
+ curp->line = lnn;
+ curp->reparse_count = 0;
+
+ if (lnn < 3 &&
+ curp->filenc & MPARSE_UTF8 &&
+ curp->filenc & MPARSE_LATIN1)
+ curp->filenc = preconv_cue(&blk, i);
+ }
+
+ while (i < blk.sz && (start || blk.buf[i] != '\0')) {
+
+ /*
+ * 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 < blk.sz &&
+ '\n' == blk.buf[i + 1])
+ ++i;
+ if ('\n' == blk.buf[i]) {
+ ++i;
+ ++lnn;
+ break;
+ }
+
+ /*
+ * Make sure we have space for the worst
+ * case of 12 bytes: "\\[u10ffff]\n\0"
+ */
+
+ if (pos + 12 > ln.sz)
+ resize_buf(&ln, 256);
+
+ /*
+ * Encode 8-bit input.
+ */
+
+ c = blk.buf[i];
+ if (c & 0x80) {
+ if ( ! (curp->filenc && preconv_encode(
+ &blk, &i, &ln, &pos, &curp->filenc))) {
+ mandoc_msg(MANDOCERR_CHAR_BAD,
+ curp->line, pos, "0x%x", c);
+ ln.buf[pos++] = '?';
+ i++;
+ }
+ continue;
+ }
+
+ /*
+ * Exclude control characters.
+ */
+
+ if (c == 0x7f || (c < 0x20 && c != 0x09)) {
+ mandoc_msg(c == 0x00 || c == 0x04 ||
+ c > 0x0a ? MANDOCERR_CHAR_BAD :
+ MANDOCERR_CHAR_UNSUPP,
+ curp->line, pos, "0x%x", c);
+ i++;
+ if (c != '\r')
+ ln.buf[pos++] = '?';
+ continue;
+ }
+
+ ln.buf[pos++] = blk.buf[i++];
+ }
+ ln.buf[pos] = '\0';
+
+ /*
+ * Maintain a lookaside buffer of all lines.
+ * parsed from this input source.
+ */
+
+ thisln = mandoc_malloc(sizeof(*thisln));
+ thisln->buf = mandoc_strdup(ln.buf);
+ thisln->sz = strlen(ln.buf) + 1;
+ thisln->next = NULL;
+ if (firstln == NULL) {
+ firstln = lastln = thisln;
+ if (curp->secondary == NULL)
+ curp->secondary = firstln;
+ } else {
+ lastln->next = thisln;
+ lastln = thisln;
+ }
+
+ /* XXX Ugly hack to mark the end of the input. */
+
+ if (i == blk.sz || blk.buf[i] == '\0') {
+ if (pos + 2 > ln.sz)
+ resize_buf(&ln, 256);
+ ln.buf[pos++] = '\n';
+ 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;
+rerun:
+ line_result = roff_parseln(curp->roff, curp->line, &ln, &of);
+
+ /* Process options. */
+
+ if (line_result & ROFF_APPEND)
+ assert(line_result == (ROFF_IGN | ROFF_APPEND));
+
+ if (line_result & ROFF_USERCALL)
+ assert((line_result & ROFF_MASK) == ROFF_REPARSE);
+
+ if (line_result & ROFF_USERRET) {
+ assert(line_result == (ROFF_IGN | ROFF_USERRET));
+ if (start == 0) {
+ /* Return from the current macro. */
+ result = ROFF_USERRET;
+ goto out;
+ }
+ }
+
+ switch (line_result & ROFF_LOOPMASK) {
+ case ROFF_IGN:
+ break;
+ case ROFF_WHILE:
+ if (curp->loop != NULL) {
+ if (loop == curp->loop)
+ break;
+ mandoc_msg(MANDOCERR_WHILE_NEST,
+ curp->line, pos, NULL);
+ }
+ curp->loop = thisln;
+ loop = NULL;
+ inloop = 1;
+ break;
+ case ROFF_LOOPCONT:
+ case ROFF_LOOPEXIT:
+ if (curp->loop == NULL) {
+ mandoc_msg(MANDOCERR_WHILE_FAIL,
+ curp->line, pos, NULL);
+ break;
+ }
+ if (inloop == 0) {
+ mandoc_msg(MANDOCERR_WHILE_INTO,
+ curp->line, pos, NULL);
+ curp->loop = loop = NULL;
+ break;
+ }
+ if (line_result & ROFF_LOOPCONT)
+ loop = curp->loop;
+ else {
+ curp->loop = loop = NULL;
+ inloop = 0;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ /* Process the main instruction from the roff parser. */
+
+ switch (line_result & ROFF_MASK) {
+ case ROFF_IGN:
+ break;
+ case ROFF_CONT:
+ if (curp->man->meta.macroset == MACROSET_NONE)
+ choose_parser(curp);
+ if ((curp->man->meta.macroset == MACROSET_MDOC ?
+ mdoc_parseln(curp->man, curp->line, ln.buf, of) :
+ man_parseln(curp->man, curp->line, ln.buf, of)
+ ) == 2)
+ goto out;
+ break;
+ case ROFF_RERUN:
+ goto rerun;
+ case ROFF_REPARSE:
+ if (++curp->reparse_count > REPARSE_LIMIT) {
+ /* Abort and return to the top level. */
+ result = ROFF_IGN;
+ mandoc_msg(MANDOCERR_ROFFLOOP,
+ curp->line, pos, NULL);
+ goto out;
+ }
+ result = mparse_buf_r(curp, ln, of, 0);
+ if (line_result & ROFF_USERCALL) {
+ roff_userret(curp->roff);
+ /* Continue normally. */
+ if (result & ROFF_USERRET)
+ result = ROFF_CONT;
+ }
+ if (start == 0 && result != ROFF_CONT)
+ goto out;
+ break;
+ case ROFF_SO:
+ if ( ! (curp->options & MPARSE_SO) &&
+ (i >= blk.sz || blk.buf[i] == '\0')) {
+ curp->man->meta.sodest =
+ mandoc_strdup(ln.buf + of);
+ goto out;
+ }
+ if ((fd = mparse_open(curp, ln.buf + of)) != -1) {
+ mparse_readfd(curp, fd, ln.buf + of);
+ close(fd);
+ } else {
+ mandoc_msg(MANDOCERR_SO_FAIL,
+ curp->line, of, ".so %s: %s",
+ ln.buf + of, strerror(errno));
+ ln.sz = mandoc_asprintf(&cp,
+ ".sp\nSee the file %s.\n.sp",
+ ln.buf + of);
+ free(ln.buf);
+ ln.buf = cp;
+ of = 0;
+ mparse_buf_r(curp, ln, of, 0);
+ }
+ break;
+ default:
+ abort();
+ }
+
+ /* Start the next input line. */
+
+ if (loop != NULL &&
+ (line_result & ROFF_LOOPMASK) == ROFF_IGN)
+ loop = loop->next;
+
+ if (loop != NULL) {
+ if ((line_result & ROFF_APPEND) == 0)
+ *ln.buf = '\0';
+ if (ln.sz < loop->sz)
+ resize_buf(&ln, loop->sz);
+ (void)strlcat(ln.buf, loop->buf, ln.sz);
+ of = 0;
+ goto rerun;
+ }
+
+ pos = (line_result & ROFF_APPEND) ? strlen(ln.buf) : 0;
+ }
+out:
+ if (inloop) {
+ if (result != ROFF_USERRET)
+ mandoc_msg(MANDOCERR_WHILE_OUTOF,
+ curp->line, pos, NULL);
+ curp->loop = NULL;
+ }
+ free(ln.buf);
+ if (firstln != curp->secondary)
+ free_buf_list(firstln);
+ return result;
+}
+
+static int
+read_whole_file(struct mparse *curp, int fd, struct buf *fb, int *with_mmap)
+{
+ struct stat st;
+ gzFile gz;
+ size_t off;
+ ssize_t ssz;
+ int gzerrnum, retval;
+
+ if (fstat(fd, &st) == -1) {
+ mandoc_msg(MANDOCERR_FSTAT, 0, 0, "%s", strerror(errno));
+ return -1;
+ }
+
+ /*
+ * 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 (curp->gzip == 0 && S_ISREG(st.st_mode)) {
+ if (st.st_size > 0x7fffffff) {
+ mandoc_msg(MANDOCERR_TOOLARGE, 0, 0, NULL);
+ return -1;
+ }
+ *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 0;
+ }
+
+ if (curp->gzip) {
+ /*
+ * Duplicating the file descriptor is required
+ * because we will have to call gzclose(3)
+ * to free memory used internally by zlib,
+ * but that will also close the file descriptor,
+ * which this function must not do.
+ */
+ if ((fd = dup(fd)) == -1) {
+ mandoc_msg(MANDOCERR_DUP, 0, 0,
+ "%s", strerror(errno));
+ return -1;
+ }
+ if ((gz = gzdopen(fd, "rb")) == NULL) {
+ mandoc_msg(MANDOCERR_GZDOPEN, 0, 0,
+ "%s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ } else
+ gz = NULL;
+
+ /*
+ * 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;
+ retval = -1;
+ fb->sz = 0;
+ fb->buf = NULL;
+ for (;;) {
+ if (off == fb->sz) {
+ if (fb->sz == (1U << 31)) {
+ mandoc_msg(MANDOCERR_TOOLARGE, 0, 0, NULL);
+ break;
+ }
+ resize_buf(fb, 65536);
+ }
+ ssz = curp->gzip ?
+ gzread(gz, fb->buf + (int)off, fb->sz - off) :
+ read(fd, fb->buf + (int)off, fb->sz - off);
+ if (ssz == 0) {
+ fb->sz = off;
+ retval = 0;
+ break;
+ }
+ if (ssz == -1) {
+ if (curp->gzip)
+ (void)gzerror(gz, &gzerrnum);
+ mandoc_msg(MANDOCERR_READ, 0, 0, "%s",
+ curp->gzip && gzerrnum != Z_ERRNO ?
+ zError(gzerrnum) : strerror(errno));
+ break;
+ }
+ off += (size_t)ssz;
+ }
+
+ if (curp->gzip && (gzerrnum = gzclose(gz)) != Z_OK)
+ mandoc_msg(MANDOCERR_GZCLOSE, 0, 0, "%s",
+ gzerrnum == Z_ERRNO ? strerror(errno) :
+ zError(gzerrnum));
+ if (retval == -1) {
+ free(fb->buf);
+ fb->buf = NULL;
+ }
+ return retval;
+}
+
+static void
+mparse_end(struct mparse *curp)
+{
+ if (curp->man->meta.macroset == MACROSET_NONE)
+ curp->man->meta.macroset = MACROSET_MAN;
+ if (curp->man->meta.macroset == MACROSET_MDOC)
+ mdoc_endparse(curp->man);
+ else
+ man_endparse(curp->man);
+ roff_endparse(curp->roff);
+}
+
+/*
+ * Read the whole file into memory and call the parsers.
+ * Called recursively when an .so request is encountered.
+ */
+void
+mparse_readfd(struct mparse *curp, int fd, const char *filename)
+{
+ static int recursion_depth;
+
+ struct buf blk;
+ struct buf *save_primary;
+ const char *save_filename, *cp;
+ size_t offset;
+ int save_filenc, save_lineno;
+ int with_mmap;
+
+ if (recursion_depth > 64) {
+ mandoc_msg(MANDOCERR_ROFFLOOP, curp->line, 0, NULL);
+ return;
+ } else if (recursion_depth == 0 &&
+ (cp = strrchr(filename, '.')) != NULL &&
+ cp[1] >= '1' && cp[1] <= '9')
+ curp->man->filesec = cp[1];
+ else
+ curp->man->filesec = '\0';
+
+ if (read_whole_file(curp, fd, &blk, &with_mmap) == -1)
+ return;
+
+ /*
+ * Save some properties of the parent file.
+ */
+
+ save_primary = curp->primary;
+ save_filenc = curp->filenc;
+ save_lineno = curp->line;
+ save_filename = mandoc_msg_getinfilename();
+
+ curp->primary = &blk;
+ curp->filenc = curp->options & (MPARSE_UTF8 | MPARSE_LATIN1);
+ curp->line = 1;
+ mandoc_msg_setinfilename(filename);
+
+ /* Skip an UTF-8 byte order mark. */
+ if (curp->filenc & MPARSE_UTF8 && blk.sz > 2 &&
+ (unsigned char)blk.buf[0] == 0xef &&
+ (unsigned char)blk.buf[1] == 0xbb &&
+ (unsigned char)blk.buf[2] == 0xbf) {
+ offset = 3;
+ curp->filenc &= ~MPARSE_LATIN1;
+ } else
+ offset = 0;
+
+ recursion_depth++;
+ mparse_buf_r(curp, blk, offset, 1);
+ if (--recursion_depth == 0)
+ mparse_end(curp);
+
+ /*
+ * Clean up and restore saved parent properties.
+ */
+
+ if (with_mmap)
+ munmap(blk.buf, blk.sz);
+ else
+ free(blk.buf);
+
+ curp->primary = save_primary;
+ curp->filenc = save_filenc;
+ curp->line = save_lineno;
+ if (save_filename != NULL)
+ mandoc_msg_setinfilename(save_filename);
+}
+
+int
+mparse_open(struct mparse *curp, const char *file)
+{
+ char *cp;
+ int fd, save_errno;
+
+ cp = strrchr(file, '.');
+ curp->gzip = (cp != NULL && ! strcmp(cp + 1, "gz"));
+
+ /* First try to use the filename as it is. */
+
+ if ((fd = open(file, O_RDONLY)) != -1)
+ return fd;
+
+ /*
+ * If that doesn't work and the filename doesn't
+ * already end in .gz, try appending .gz.
+ */
+
+ if ( ! curp->gzip) {
+ save_errno = errno;
+ mandoc_asprintf(&cp, "%s.gz", file);
+ fd = open(cp, O_RDONLY);
+ free(cp);
+ errno = save_errno;
+ if (fd != -1) {
+ curp->gzip = 1;
+ return fd;
+ }
+ }
+
+ /* Neither worked, give up. */
+
+ return -1;
+}
+
+struct mparse *
+mparse_alloc(int options, enum mandoc_os os_e, const char *os_s)
+{
+ struct mparse *curp;
+
+ curp = mandoc_calloc(1, sizeof(struct mparse));
+
+ curp->options = options;
+ curp->os_s = os_s;
+
+ curp->roff = roff_alloc(options);
+ curp->man = roff_man_alloc(curp->roff, curp->os_s,
+ curp->options & MPARSE_QUICK ? 1 : 0);
+ if (curp->options & MPARSE_MDOC) {
+ curp->man->meta.macroset = MACROSET_MDOC;
+ if (curp->man->mdocmac == NULL)
+ curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX);
+ } else if (curp->options & MPARSE_MAN) {
+ curp->man->meta.macroset = MACROSET_MAN;
+ if (curp->man->manmac == NULL)
+ curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX);
+ }
+ curp->man->meta.first->tok = TOKEN_NONE;
+ curp->man->meta.os_e = os_e;
+ tag_alloc();
+ return curp;
+}
+
+void
+mparse_reset(struct mparse *curp)
+{
+ tag_free();
+ roff_reset(curp->roff);
+ roff_man_reset(curp->man);
+ free_buf_list(curp->secondary);
+ curp->secondary = NULL;
+ curp->gzip = 0;
+ tag_alloc();
+}
+
+void
+mparse_free(struct mparse *curp)
+{
+ tag_free();
+ roffhash_free(curp->man->mdocmac);
+ roffhash_free(curp->man->manmac);
+ roff_man_free(curp->man);
+ roff_free(curp->roff);
+ free_buf_list(curp->secondary);
+ free(curp);
+}
+
+struct roff_meta *
+mparse_result(struct mparse *curp)
+{
+ roff_state_reset(curp->man);
+ if (curp->options & MPARSE_VALIDATE) {
+ if (curp->man->meta.macroset == MACROSET_MDOC)
+ mdoc_validate(curp->man);
+ else
+ man_validate(curp->man);
+ tag_postprocess(curp->man, curp->man->meta.first);
+ }
+ return &curp->man->meta;
+}
+
+void
+mparse_copy(const struct mparse *p)
+{
+ struct buf *buf;
+
+ for (buf = p->secondary; buf != NULL; buf = buf->next)
+ puts(buf->buf);
+}
diff --git a/usr.bin/mandoc/roff.c b/usr.bin/mandoc/roff.c
new file mode 100644
index 0000000..870305e
--- /dev/null
+++ b/usr.bin/mandoc/roff.c
@@ -0,0 +1,4372 @@
+/* $OpenBSD: roff.c,v 1.246 2020/04/08 11:54:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012, 2014 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 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.
+ *
+ * Implementation of the roff(7) parser for mandoc(1).
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mandoc_parse.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+#include "tbl_parse.h"
+#include "eqn_parse.h"
+
+/*
+ * ASCII_ESC is used to signal from roff_getarg() to roff_expand()
+ * that an escape sequence resulted from copy-in processing and
+ * needs to be checked or interpolated. As it is used nowhere
+ * else, it is defined here rather than in a header file.
+ */
+#define ASCII_ESC 27
+
+/* Maximum number of string expansions per line, to break infinite loops. */
+#define EXPAND_LIMIT 1000
+
+/* Types of definitions of macros and strings. */
+#define ROFFDEF_USER (1 << 1) /* User-defined. */
+#define ROFFDEF_PRE (1 << 2) /* Predefined. */
+#define ROFFDEF_REN (1 << 3) /* Renamed standard macro. */
+#define ROFFDEF_STD (1 << 4) /* mdoc(7) or man(7) macro. */
+#define ROFFDEF_ANY (ROFFDEF_USER | ROFFDEF_PRE | \
+ ROFFDEF_REN | ROFFDEF_STD)
+#define ROFFDEF_UNDEF (1 << 5) /* Completely undefined. */
+
+/* --- data types --------------------------------------------------------- */
+
+/*
+ * 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;
+ int step;
+ struct roffreg *next;
+};
+
+/*
+ * Association of request and macro names with token IDs.
+ */
+struct roffreq {
+ enum roff_tok tok;
+ char name[];
+};
+
+/*
+ * A macro processing context.
+ * More than one is needed when macro calls are nested.
+ */
+struct mctx {
+ char **argv;
+ int argc;
+ int argsz;
+};
+
+struct roff {
+ struct roff_man *man; /* mdoc or man parser */
+ struct roffnode *last; /* leaf of stack */
+ struct mctx *mstack; /* stack of macro contexts */
+ int *rstack; /* stack of inverted `ie' values */
+ struct ohash *reqtab; /* request lookup table */
+ struct roffreg *regtab; /* number registers */
+ struct roffkv *strtab; /* user-defined strings & macros */
+ struct roffkv *rentab; /* renamed 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; /* equation parser */
+ struct eqn_node *eqn; /* active equation parser */
+ int eqn_inline; /* current equation is inline */
+ int options; /* parse options */
+ int mstacksz; /* current size of mstack */
+ int mstackpos; /* position in mstack */
+ int rstacksz; /* current size limit of rstack */
+ int rstackpos; /* position in rstack */
+ int format; /* current file in mdoc or man format */
+ char control; /* control character */
+ char escape; /* escape character */
+};
+
+/*
+ * A macro definition, condition, or ignored block.
+ */
+struct roffnode {
+ enum roff_tok 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; /* custom end macro of the block */
+ int endspan; /* scope to: 1=eol 2=next line -1=\} */
+ int rule; /* content is: 1=evaluated 0=skipped */
+};
+
+#define ROFF_ARGS struct roff *r, /* parse ctx */ \
+ enum roff_tok tok, /* tok of macro */ \
+ struct buf *buf, /* 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 int (*roffproc)(ROFF_ARGS);
+
+struct roffmac {
+ 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 predef {
+ const char *name; /* predefined input name */
+ const char *str; /* replacement symbol */
+};
+
+#define PREDEF(__name, __str) \
+ { (__name), (__str) },
+
+/* --- function prototypes ------------------------------------------------ */
+
+static int roffnode_cleanscope(struct roff *);
+static int roffnode_pop(struct roff *);
+static void roffnode_push(struct roff *, enum roff_tok,
+ const char *, int, int);
+static void roff_addtbl(struct roff_man *, int, struct tbl_node *);
+static int roff_als(ROFF_ARGS);
+static int roff_block(ROFF_ARGS);
+static int roff_block_text(ROFF_ARGS);
+static int roff_block_sub(ROFF_ARGS);
+static int roff_break(ROFF_ARGS);
+static int roff_cblock(ROFF_ARGS);
+static int roff_cc(ROFF_ARGS);
+static int roff_ccond(struct roff *, int, int);
+static int roff_char(ROFF_ARGS);
+static int roff_cond(ROFF_ARGS);
+static int roff_cond_text(ROFF_ARGS);
+static int roff_cond_sub(ROFF_ARGS);
+static int roff_ds(ROFF_ARGS);
+static int roff_ec(ROFF_ARGS);
+static int roff_eo(ROFF_ARGS);
+static int roff_eqndelim(struct roff *, struct buf *, int);
+static int roff_evalcond(struct roff *, int, char *, int *);
+static int roff_evalnum(struct roff *, int,
+ const char *, int *, int *, int);
+static int roff_evalpar(struct roff *, int,
+ const char *, int *, int *, int);
+static int roff_evalstrcond(const char *, int *);
+static int roff_expand(struct roff *, struct buf *,
+ int, int, char);
+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 *, int);
+static int roff_getop(const char *, int *, char *);
+static int roff_getregn(struct roff *,
+ const char *, size_t, char);
+static int roff_getregro(const struct roff *,
+ const char *name);
+static const char *roff_getstrn(struct roff *,
+ const char *, size_t, int *);
+static int roff_hasregn(const struct roff *,
+ const char *, size_t);
+static int roff_insec(ROFF_ARGS);
+static int roff_it(ROFF_ARGS);
+static int roff_line_ignore(ROFF_ARGS);
+static void roff_man_alloc1(struct roff_man *);
+static void roff_man_free1(struct roff_man *);
+static int roff_manyarg(ROFF_ARGS);
+static int roff_noarg(ROFF_ARGS);
+static int roff_nop(ROFF_ARGS);
+static int roff_nr(ROFF_ARGS);
+static int roff_onearg(ROFF_ARGS);
+static enum roff_tok roff_parse(struct roff *, char *, int *,
+ int, int);
+static int roff_parsetext(struct roff *, struct buf *,
+ int, int *);
+static int roff_renamed(ROFF_ARGS);
+static int roff_return(ROFF_ARGS);
+static int roff_rm(ROFF_ARGS);
+static int roff_rn(ROFF_ARGS);
+static int roff_rr(ROFF_ARGS);
+static void roff_setregn(struct roff *, const char *,
+ size_t, int, char, int);
+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 int roff_shift(ROFF_ARGS);
+static int roff_so(ROFF_ARGS);
+static int roff_tr(ROFF_ARGS);
+static int roff_Dd(ROFF_ARGS);
+static int roff_TE(ROFF_ARGS);
+static int roff_TS(ROFF_ARGS);
+static int roff_EQ(ROFF_ARGS);
+static int roff_EN(ROFF_ARGS);
+static int roff_T_(ROFF_ARGS);
+static int roff_unsupp(ROFF_ARGS);
+static int roff_userdef(ROFF_ARGS);
+
+/* --- constant data ------------------------------------------------------ */
+
+#define ROFFNUM_SCALE (1 << 0) /* Honour scaling in roff_getnum(). */
+#define ROFFNUM_WHITE (1 << 1) /* Skip whitespace in roff_evalnum(). */
+
+const char *__roff_name[MAN_MAX + 1] = {
+ "br", "ce", "fi", "ft",
+ "ll", "mc", "nf",
+ "po", "rj", "sp",
+ "ta", "ti", NULL,
+ "ab", "ad", "af", "aln",
+ "als", "am", "am1", "ami",
+ "ami1", "as", "as1", "asciify",
+ "backtrace", "bd", "bleedat", "blm",
+ "box", "boxa", "bp", "BP",
+ "break", "breakchar", "brnl", "brp",
+ "brpnl", "c2", "cc",
+ "cf", "cflags", "ch", "char",
+ "chop", "class", "close", "CL",
+ "color", "composite", "continue", "cp",
+ "cropat", "cs", "cu", "da",
+ "dch", "Dd", "de", "de1",
+ "defcolor", "dei", "dei1", "device",
+ "devicem", "di", "do", "ds",
+ "ds1", "dwh", "dt", "ec",
+ "ecr", "ecs", "el", "em",
+ "EN", "eo", "EP", "EQ",
+ "errprint", "ev", "evc", "ex",
+ "fallback", "fam", "fc", "fchar",
+ "fcolor", "fdeferlig", "feature", "fkern",
+ "fl", "flig", "fp", "fps",
+ "fschar", "fspacewidth", "fspecial", "ftr",
+ "fzoom", "gcolor", "hc", "hcode",
+ "hidechar", "hla", "hlm", "hpf",
+ "hpfa", "hpfcode", "hw", "hy",
+ "hylang", "hylen", "hym", "hypp",
+ "hys", "ie", "if", "ig",
+ "index", "it", "itc", "IX",
+ "kern", "kernafter", "kernbefore", "kernpair",
+ "lc", "lc_ctype", "lds", "length",
+ "letadj", "lf", "lg", "lhang",
+ "linetabs", "lnr", "lnrf", "lpfx",
+ "ls", "lsm", "lt",
+ "mediasize", "minss", "mk", "mso",
+ "na", "ne", "nh", "nhychar",
+ "nm", "nn", "nop", "nr",
+ "nrf", "nroff", "ns", "nx",
+ "open", "opena", "os", "output",
+ "padj", "papersize", "pc", "pev",
+ "pi", "PI", "pl", "pm",
+ "pn", "pnr", "ps",
+ "psbb", "pshape", "pso", "ptr",
+ "pvs", "rchar", "rd", "recursionlimit",
+ "return", "rfschar", "rhang",
+ "rm", "rn", "rnn", "rr",
+ "rs", "rt", "schar", "sentchar",
+ "shc", "shift", "sizes", "so",
+ "spacewidth", "special", "spreadwarn", "ss",
+ "sty", "substring", "sv", "sy",
+ "T&", "tc", "TE",
+ "TH", "tkf", "tl",
+ "tm", "tm1", "tmc", "tr",
+ "track", "transchar", "trf", "trimat",
+ "trin", "trnt", "troff", "TS",
+ "uf", "ul", "unformat", "unwatch",
+ "unwatchn", "vpt", "vs", "warn",
+ "warnscale", "watch", "watchlength", "watchn",
+ "wh", "while", "write", "writec",
+ "writem", "xflag", ".", NULL,
+ NULL, "text",
+ "Dd", "Dt", "Os", "Sh",
+ "Ss", "Pp", "D1", "Dl",
+ "Bd", "Ed", "Bl", "El",
+ "It", "Ad", "An", "Ap",
+ "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", "%U", "Ta",
+ "Tg", NULL,
+ "TH", "SH", "SS", "TP",
+ "TQ",
+ "LP", "PP", "P", "IP",
+ "HP", "SM", "SB", "BI",
+ "IB", "BR", "RB", "R",
+ "B", "I", "IR", "RI",
+ "RE", "RS", "DT", "UC",
+ "PD", "AT", "in",
+ "SY", "YS", "OP",
+ "EX", "EE", "UR",
+ "UE", "MT", "ME", NULL
+};
+const char *const *roff_name = __roff_name;
+
+static struct roffmac roffs[TOKEN_NONE] = {
+ { roff_noarg, NULL, NULL, 0 }, /* br */
+ { roff_onearg, NULL, NULL, 0 }, /* ce */
+ { roff_noarg, NULL, NULL, 0 }, /* fi */
+ { roff_onearg, NULL, NULL, 0 }, /* ft */
+ { roff_onearg, NULL, NULL, 0 }, /* ll */
+ { roff_onearg, NULL, NULL, 0 }, /* mc */
+ { roff_noarg, NULL, NULL, 0 }, /* nf */
+ { roff_onearg, NULL, NULL, 0 }, /* po */
+ { roff_onearg, NULL, NULL, 0 }, /* rj */
+ { roff_onearg, NULL, NULL, 0 }, /* sp */
+ { roff_manyarg, NULL, NULL, 0 }, /* ta */
+ { roff_onearg, NULL, NULL, 0 }, /* ti */
+ { NULL, NULL, NULL, 0 }, /* ROFF_MAX */
+ { roff_unsupp, NULL, NULL, 0 }, /* ab */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ad */
+ { roff_line_ignore, NULL, NULL, 0 }, /* af */
+ { roff_unsupp, NULL, NULL, 0 }, /* aln */
+ { roff_als, NULL, NULL, 0 }, /* als */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* am */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* am1 */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* ami */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* ami1 */
+ { roff_ds, NULL, NULL, 0 }, /* as */
+ { roff_ds, NULL, NULL, 0 }, /* as1 */
+ { roff_unsupp, NULL, NULL, 0 }, /* asciify */
+ { roff_line_ignore, NULL, NULL, 0 }, /* backtrace */
+ { roff_line_ignore, NULL, NULL, 0 }, /* bd */
+ { roff_line_ignore, NULL, NULL, 0 }, /* bleedat */
+ { roff_unsupp, NULL, NULL, 0 }, /* blm */
+ { roff_unsupp, NULL, NULL, 0 }, /* box */
+ { roff_unsupp, NULL, NULL, 0 }, /* boxa */
+ { roff_line_ignore, NULL, NULL, 0 }, /* bp */
+ { roff_unsupp, NULL, NULL, 0 }, /* BP */
+ { roff_break, NULL, NULL, 0 }, /* break */
+ { roff_line_ignore, NULL, NULL, 0 }, /* breakchar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* brnl */
+ { roff_noarg, NULL, NULL, 0 }, /* brp */
+ { roff_line_ignore, NULL, NULL, 0 }, /* brpnl */
+ { roff_unsupp, NULL, NULL, 0 }, /* c2 */
+ { roff_cc, NULL, NULL, 0 }, /* cc */
+ { roff_insec, NULL, NULL, 0 }, /* cf */
+ { roff_line_ignore, NULL, NULL, 0 }, /* cflags */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ch */
+ { roff_char, NULL, NULL, 0 }, /* char */
+ { roff_unsupp, NULL, NULL, 0 }, /* chop */
+ { roff_line_ignore, NULL, NULL, 0 }, /* class */
+ { roff_insec, NULL, NULL, 0 }, /* close */
+ { roff_unsupp, NULL, NULL, 0 }, /* CL */
+ { roff_line_ignore, NULL, NULL, 0 }, /* color */
+ { roff_unsupp, NULL, NULL, 0 }, /* composite */
+ { roff_unsupp, NULL, NULL, 0 }, /* continue */
+ { roff_line_ignore, NULL, NULL, 0 }, /* cp */
+ { roff_line_ignore, NULL, NULL, 0 }, /* cropat */
+ { roff_line_ignore, NULL, NULL, 0 }, /* cs */
+ { roff_line_ignore, NULL, NULL, 0 }, /* cu */
+ { roff_unsupp, NULL, NULL, 0 }, /* da */
+ { roff_unsupp, NULL, NULL, 0 }, /* dch */
+ { roff_Dd, NULL, NULL, 0 }, /* Dd */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* de */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* de1 */
+ { roff_line_ignore, NULL, NULL, 0 }, /* defcolor */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* dei */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* dei1 */
+ { roff_unsupp, NULL, NULL, 0 }, /* device */
+ { roff_unsupp, NULL, NULL, 0 }, /* devicem */
+ { roff_unsupp, NULL, NULL, 0 }, /* di */
+ { roff_unsupp, NULL, NULL, 0 }, /* do */
+ { roff_ds, NULL, NULL, 0 }, /* ds */
+ { roff_ds, NULL, NULL, 0 }, /* ds1 */
+ { roff_unsupp, NULL, NULL, 0 }, /* dwh */
+ { roff_unsupp, NULL, NULL, 0 }, /* dt */
+ { roff_ec, NULL, NULL, 0 }, /* ec */
+ { roff_unsupp, NULL, NULL, 0 }, /* ecr */
+ { roff_unsupp, NULL, NULL, 0 }, /* ecs */
+ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /* el */
+ { roff_unsupp, NULL, NULL, 0 }, /* em */
+ { roff_EN, NULL, NULL, 0 }, /* EN */
+ { roff_eo, NULL, NULL, 0 }, /* eo */
+ { roff_unsupp, NULL, NULL, 0 }, /* EP */
+ { roff_EQ, NULL, NULL, 0 }, /* EQ */
+ { roff_line_ignore, NULL, NULL, 0 }, /* errprint */
+ { roff_unsupp, NULL, NULL, 0 }, /* ev */
+ { roff_unsupp, NULL, NULL, 0 }, /* evc */
+ { roff_unsupp, NULL, NULL, 0 }, /* ex */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fallback */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fam */
+ { roff_unsupp, NULL, NULL, 0 }, /* fc */
+ { roff_unsupp, NULL, NULL, 0 }, /* fchar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fcolor */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fdeferlig */
+ { roff_line_ignore, NULL, NULL, 0 }, /* feature */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fkern */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fl */
+ { roff_line_ignore, NULL, NULL, 0 }, /* flig */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fp */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fps */
+ { roff_unsupp, NULL, NULL, 0 }, /* fschar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fspacewidth */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fspecial */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ftr */
+ { roff_line_ignore, NULL, NULL, 0 }, /* fzoom */
+ { roff_line_ignore, NULL, NULL, 0 }, /* gcolor */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hc */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hcode */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hidechar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hla */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hlm */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hpf */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hpfa */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hpfcode */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hw */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hy */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hylang */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hylen */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hym */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hypp */
+ { roff_line_ignore, NULL, NULL, 0 }, /* hys */
+ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /* ie */
+ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /* if */
+ { roff_block, roff_block_text, roff_block_sub, 0 }, /* ig */
+ { roff_unsupp, NULL, NULL, 0 }, /* index */
+ { roff_it, NULL, NULL, 0 }, /* it */
+ { roff_unsupp, NULL, NULL, 0 }, /* itc */
+ { roff_line_ignore, NULL, NULL, 0 }, /* IX */
+ { roff_line_ignore, NULL, NULL, 0 }, /* kern */
+ { roff_line_ignore, NULL, NULL, 0 }, /* kernafter */
+ { roff_line_ignore, NULL, NULL, 0 }, /* kernbefore */
+ { roff_line_ignore, NULL, NULL, 0 }, /* kernpair */
+ { roff_unsupp, NULL, NULL, 0 }, /* lc */
+ { roff_unsupp, NULL, NULL, 0 }, /* lc_ctype */
+ { roff_unsupp, NULL, NULL, 0 }, /* lds */
+ { roff_unsupp, NULL, NULL, 0 }, /* length */
+ { roff_line_ignore, NULL, NULL, 0 }, /* letadj */
+ { roff_insec, NULL, NULL, 0 }, /* lf */
+ { roff_line_ignore, NULL, NULL, 0 }, /* lg */
+ { roff_line_ignore, NULL, NULL, 0 }, /* lhang */
+ { roff_unsupp, NULL, NULL, 0 }, /* linetabs */
+ { roff_unsupp, NULL, NULL, 0 }, /* lnr */
+ { roff_unsupp, NULL, NULL, 0 }, /* lnrf */
+ { roff_unsupp, NULL, NULL, 0 }, /* lpfx */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ls */
+ { roff_unsupp, NULL, NULL, 0 }, /* lsm */
+ { roff_line_ignore, NULL, NULL, 0 }, /* lt */
+ { roff_line_ignore, NULL, NULL, 0 }, /* mediasize */
+ { roff_line_ignore, NULL, NULL, 0 }, /* minss */
+ { roff_line_ignore, NULL, NULL, 0 }, /* mk */
+ { roff_insec, NULL, NULL, 0 }, /* mso */
+ { roff_line_ignore, NULL, NULL, 0 }, /* na */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ne */
+ { roff_line_ignore, NULL, NULL, 0 }, /* nh */
+ { roff_line_ignore, NULL, NULL, 0 }, /* nhychar */
+ { roff_unsupp, NULL, NULL, 0 }, /* nm */
+ { roff_unsupp, NULL, NULL, 0 }, /* nn */
+ { roff_nop, NULL, NULL, 0 }, /* nop */
+ { roff_nr, NULL, NULL, 0 }, /* nr */
+ { roff_unsupp, NULL, NULL, 0 }, /* nrf */
+ { roff_line_ignore, NULL, NULL, 0 }, /* nroff */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ns */
+ { roff_insec, NULL, NULL, 0 }, /* nx */
+ { roff_insec, NULL, NULL, 0 }, /* open */
+ { roff_insec, NULL, NULL, 0 }, /* opena */
+ { roff_line_ignore, NULL, NULL, 0 }, /* os */
+ { roff_unsupp, NULL, NULL, 0 }, /* output */
+ { roff_line_ignore, NULL, NULL, 0 }, /* padj */
+ { roff_line_ignore, NULL, NULL, 0 }, /* papersize */
+ { roff_line_ignore, NULL, NULL, 0 }, /* pc */
+ { roff_line_ignore, NULL, NULL, 0 }, /* pev */
+ { roff_insec, NULL, NULL, 0 }, /* pi */
+ { roff_unsupp, NULL, NULL, 0 }, /* PI */
+ { roff_line_ignore, NULL, NULL, 0 }, /* pl */
+ { roff_line_ignore, NULL, NULL, 0 }, /* pm */
+ { roff_line_ignore, NULL, NULL, 0 }, /* pn */
+ { roff_line_ignore, NULL, NULL, 0 }, /* pnr */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ps */
+ { roff_unsupp, NULL, NULL, 0 }, /* psbb */
+ { roff_unsupp, NULL, NULL, 0 }, /* pshape */
+ { roff_insec, NULL, NULL, 0 }, /* pso */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ptr */
+ { roff_line_ignore, NULL, NULL, 0 }, /* pvs */
+ { roff_unsupp, NULL, NULL, 0 }, /* rchar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* rd */
+ { roff_line_ignore, NULL, NULL, 0 }, /* recursionlimit */
+ { roff_return, NULL, NULL, 0 }, /* return */
+ { roff_unsupp, NULL, NULL, 0 }, /* rfschar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* rhang */
+ { roff_rm, NULL, NULL, 0 }, /* rm */
+ { roff_rn, NULL, NULL, 0 }, /* rn */
+ { roff_unsupp, NULL, NULL, 0 }, /* rnn */
+ { roff_rr, NULL, NULL, 0 }, /* rr */
+ { roff_line_ignore, NULL, NULL, 0 }, /* rs */
+ { roff_line_ignore, NULL, NULL, 0 }, /* rt */
+ { roff_unsupp, NULL, NULL, 0 }, /* schar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* sentchar */
+ { roff_line_ignore, NULL, NULL, 0 }, /* shc */
+ { roff_shift, NULL, NULL, 0 }, /* shift */
+ { roff_line_ignore, NULL, NULL, 0 }, /* sizes */
+ { roff_so, NULL, NULL, 0 }, /* so */
+ { roff_line_ignore, NULL, NULL, 0 }, /* spacewidth */
+ { roff_line_ignore, NULL, NULL, 0 }, /* special */
+ { roff_line_ignore, NULL, NULL, 0 }, /* spreadwarn */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ss */
+ { roff_line_ignore, NULL, NULL, 0 }, /* sty */
+ { roff_unsupp, NULL, NULL, 0 }, /* substring */
+ { roff_line_ignore, NULL, NULL, 0 }, /* sv */
+ { roff_insec, NULL, NULL, 0 }, /* sy */
+ { roff_T_, NULL, NULL, 0 }, /* T& */
+ { roff_unsupp, NULL, NULL, 0 }, /* tc */
+ { roff_TE, NULL, NULL, 0 }, /* TE */
+ { roff_Dd, NULL, NULL, 0 }, /* TH */
+ { roff_line_ignore, NULL, NULL, 0 }, /* tkf */
+ { roff_unsupp, NULL, NULL, 0 }, /* tl */
+ { roff_line_ignore, NULL, NULL, 0 }, /* tm */
+ { roff_line_ignore, NULL, NULL, 0 }, /* tm1 */
+ { roff_line_ignore, NULL, NULL, 0 }, /* tmc */
+ { roff_tr, NULL, NULL, 0 }, /* tr */
+ { roff_line_ignore, NULL, NULL, 0 }, /* track */
+ { roff_line_ignore, NULL, NULL, 0 }, /* transchar */
+ { roff_insec, NULL, NULL, 0 }, /* trf */
+ { roff_line_ignore, NULL, NULL, 0 }, /* trimat */
+ { roff_unsupp, NULL, NULL, 0 }, /* trin */
+ { roff_unsupp, NULL, NULL, 0 }, /* trnt */
+ { roff_line_ignore, NULL, NULL, 0 }, /* troff */
+ { roff_TS, NULL, NULL, 0 }, /* TS */
+ { roff_line_ignore, NULL, NULL, 0 }, /* uf */
+ { roff_line_ignore, NULL, NULL, 0 }, /* ul */
+ { roff_unsupp, NULL, NULL, 0 }, /* unformat */
+ { roff_line_ignore, NULL, NULL, 0 }, /* unwatch */
+ { roff_line_ignore, NULL, NULL, 0 }, /* unwatchn */
+ { roff_line_ignore, NULL, NULL, 0 }, /* vpt */
+ { roff_line_ignore, NULL, NULL, 0 }, /* vs */
+ { roff_line_ignore, NULL, NULL, 0 }, /* warn */
+ { roff_line_ignore, NULL, NULL, 0 }, /* warnscale */
+ { roff_line_ignore, NULL, NULL, 0 }, /* watch */
+ { roff_line_ignore, NULL, NULL, 0 }, /* watchlength */
+ { roff_line_ignore, NULL, NULL, 0 }, /* watchn */
+ { roff_unsupp, NULL, NULL, 0 }, /* wh */
+ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /*while*/
+ { roff_insec, NULL, NULL, 0 }, /* write */
+ { roff_insec, NULL, NULL, 0 }, /* writec */
+ { roff_insec, NULL, NULL, 0 }, /* writem */
+ { roff_line_ignore, NULL, NULL, 0 }, /* xflag */
+ { roff_cblock, NULL, NULL, 0 }, /* . */
+ { roff_renamed, NULL, NULL, 0 },
+ { roff_userdef, NULL, NULL, 0 }
+};
+
+/* Array of injected predefined strings. */
+#define PREDEFS_MAX 38
+static const struct predef predefs[PREDEFS_MAX] = {
+#include "predefs.in"
+};
+
+static int roffce_lines; /* number of input lines to center */
+static struct roff_node *roffce_node; /* active request */
+static int roffit_lines; /* number of lines to delay */
+static char *roffit_macro; /* nil-terminated macro line */
+
+
+/* --- request table ------------------------------------------------------ */
+
+struct ohash *
+roffhash_alloc(enum roff_tok mintok, enum roff_tok maxtok)
+{
+ struct ohash *htab;
+ struct roffreq *req;
+ enum roff_tok tok;
+ size_t sz;
+ unsigned int slot;
+
+ htab = mandoc_malloc(sizeof(*htab));
+ mandoc_ohash_init(htab, 8, offsetof(struct roffreq, name));
+
+ for (tok = mintok; tok < maxtok; tok++) {
+ if (roff_name[tok] == NULL)
+ continue;
+ sz = strlen(roff_name[tok]);
+ req = mandoc_malloc(sizeof(*req) + sz + 1);
+ req->tok = tok;
+ memcpy(req->name, roff_name[tok], sz + 1);
+ slot = ohash_qlookup(htab, req->name);
+ ohash_insert(htab, slot, req);
+ }
+ return htab;
+}
+
+void
+roffhash_free(struct ohash *htab)
+{
+ struct roffreq *req;
+ unsigned int slot;
+
+ if (htab == NULL)
+ return;
+ for (req = ohash_first(htab, &slot); req != NULL;
+ req = ohash_next(htab, &slot))
+ free(req);
+ ohash_delete(htab);
+ free(htab);
+}
+
+enum roff_tok
+roffhash_find(struct ohash *htab, const char *name, size_t sz)
+{
+ struct roffreq *req;
+ const char *end;
+
+ if (sz) {
+ end = name + sz;
+ req = ohash_find(htab, ohash_qlookupi(htab, name, &end));
+ } else
+ req = ohash_find(htab, ohash_qlookup(htab, name));
+ return req == NULL ? TOKEN_NONE : req->tok;
+}
+
+/* --- stack of request blocks -------------------------------------------- */
+
+/*
+ * Pop the current node off of the stack of roff instructions currently
+ * pending. Return 1 if it is a loop or 0 otherwise.
+ */
+static int
+roffnode_pop(struct roff *r)
+{
+ struct roffnode *p;
+ int inloop;
+
+ p = r->last;
+ inloop = p->tok == ROFF_while;
+ r->last = p->parent;
+ free(p->name);
+ free(p->end);
+ free(p);
+ return inloop;
+}
+
+/*
+ * Push a roff node onto the instruction stack. This must later be
+ * removed with roffnode_pop().
+ */
+static void
+roffnode_push(struct roff *r, enum roff_tok 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;
+}
+
+/* --- roff parser state data management ---------------------------------- */
+
+static void
+roff_free1(struct roff *r)
+{
+ int i;
+
+ tbl_free(r->first_tbl);
+ r->first_tbl = r->last_tbl = r->tbl = NULL;
+
+ eqn_free(r->last_eqn);
+ r->last_eqn = r->eqn = NULL;
+
+ while (r->mstackpos >= 0)
+ roff_userret(r);
+
+ 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->rentab);
+ roff_freestr(r->xmbtab);
+ r->strtab = r->rentab = 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->options |= MPARSE_COMMENT;
+ r->format = r->options & (MPARSE_MDOC | MPARSE_MAN);
+ r->control = '\0';
+ r->escape = '\\';
+ roffce_lines = 0;
+ roffce_node = NULL;
+ roffit_lines = 0;
+ roffit_macro = NULL;
+}
+
+void
+roff_free(struct roff *r)
+{
+ int i;
+
+ roff_free1(r);
+ for (i = 0; i < r->mstacksz; i++)
+ free(r->mstack[i].argv);
+ free(r->mstack);
+ roffhash_free(r->reqtab);
+ free(r);
+}
+
+struct roff *
+roff_alloc(int options)
+{
+ struct roff *r;
+
+ r = mandoc_calloc(1, sizeof(struct roff));
+ r->reqtab = roffhash_alloc(0, ROFF_RENAMED);
+ r->options = options | MPARSE_COMMENT;
+ r->format = options & (MPARSE_MDOC | MPARSE_MAN);
+ r->mstackpos = -1;
+ r->rstackpos = -1;
+ r->escape = '\\';
+ return r;
+}
+
+/* --- syntax tree state data management ---------------------------------- */
+
+static void
+roff_man_free1(struct roff_man *man)
+{
+ if (man->meta.first != NULL)
+ roff_node_delete(man, man->meta.first);
+ free(man->meta.msec);
+ free(man->meta.vol);
+ free(man->meta.os);
+ free(man->meta.arch);
+ free(man->meta.title);
+ free(man->meta.name);
+ free(man->meta.date);
+ free(man->meta.sodest);
+}
+
+void
+roff_state_reset(struct roff_man *man)
+{
+ man->last = man->meta.first;
+ man->last_es = NULL;
+ man->flags = 0;
+ man->lastsec = man->lastnamed = SEC_NONE;
+ man->next = ROFF_NEXT_CHILD;
+ roff_setreg(man->roff, "nS", 0, '=');
+}
+
+static void
+roff_man_alloc1(struct roff_man *man)
+{
+ memset(&man->meta, 0, sizeof(man->meta));
+ man->meta.first = mandoc_calloc(1, sizeof(*man->meta.first));
+ man->meta.first->type = ROFFT_ROOT;
+ man->meta.macroset = MACROSET_NONE;
+ roff_state_reset(man);
+}
+
+void
+roff_man_reset(struct roff_man *man)
+{
+ roff_man_free1(man);
+ roff_man_alloc1(man);
+}
+
+void
+roff_man_free(struct roff_man *man)
+{
+ roff_man_free1(man);
+ free(man);
+}
+
+struct roff_man *
+roff_man_alloc(struct roff *roff, const char *os_s, int quick)
+{
+ struct roff_man *man;
+
+ man = mandoc_calloc(1, sizeof(*man));
+ man->roff = roff;
+ man->os_s = os_s;
+ man->quick = quick;
+ roff_man_alloc1(man);
+ roff->man = man;
+ return man;
+}
+
+/* --- syntax tree handling ----------------------------------------------- */
+
+struct roff_node *
+roff_node_alloc(struct roff_man *man, int line, int pos,
+ enum roff_type type, int tok)
+{
+ struct roff_node *n;
+
+ n = mandoc_calloc(1, sizeof(*n));
+ n->line = line;
+ n->pos = pos;
+ n->tok = tok;
+ n->type = type;
+ n->sec = man->lastsec;
+
+ if (man->flags & MDOC_SYNOPSIS)
+ n->flags |= NODE_SYNPRETTY;
+ else
+ n->flags &= ~NODE_SYNPRETTY;
+ if ((man->flags & (ROFF_NOFILL | ROFF_NONOFILL)) == ROFF_NOFILL)
+ n->flags |= NODE_NOFILL;
+ else
+ n->flags &= ~NODE_NOFILL;
+ if (man->flags & MDOC_NEWLINE)
+ n->flags |= NODE_LINE;
+ man->flags &= ~MDOC_NEWLINE;
+
+ return n;
+}
+
+void
+roff_node_append(struct roff_man *man, struct roff_node *n)
+{
+
+ switch (man->next) {
+ case ROFF_NEXT_SIBLING:
+ if (man->last->next != NULL) {
+ n->next = man->last->next;
+ man->last->next->prev = n;
+ } else
+ man->last->parent->last = n;
+ man->last->next = n;
+ n->prev = man->last;
+ n->parent = man->last->parent;
+ break;
+ case ROFF_NEXT_CHILD:
+ if (man->last->child != NULL) {
+ n->next = man->last->child;
+ man->last->child->prev = n;
+ } else
+ man->last->last = n;
+ man->last->child = n;
+ n->parent = man->last;
+ break;
+ default:
+ abort();
+ }
+ man->last = n;
+
+ switch (n->type) {
+ case ROFFT_HEAD:
+ n->parent->head = n;
+ break;
+ case ROFFT_BODY:
+ if (n->end != ENDBODY_NOT)
+ return;
+ n->parent->body = n;
+ break;
+ case ROFFT_TAIL:
+ n->parent->tail = n;
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * Copy over the normalised-data pointer of our parent. Not
+ * everybody has one, but copying a null pointer is fine.
+ */
+
+ n->norm = n->parent->norm;
+ assert(n->parent->type == ROFFT_BLOCK);
+}
+
+void
+roff_word_alloc(struct roff_man *man, int line, int pos, const char *word)
+{
+ struct roff_node *n;
+
+ n = roff_node_alloc(man, line, pos, ROFFT_TEXT, TOKEN_NONE);
+ n->string = roff_strdup(man->roff, word);
+ roff_node_append(man, n);
+ n->flags |= NODE_VALID | NODE_ENDED;
+ man->next = ROFF_NEXT_SIBLING;
+}
+
+void
+roff_word_append(struct roff_man *man, const char *word)
+{
+ struct roff_node *n;
+ char *addstr, *newstr;
+
+ n = man->last;
+ addstr = roff_strdup(man->roff, word);
+ mandoc_asprintf(&newstr, "%s %s", n->string, addstr);
+ free(addstr);
+ free(n->string);
+ n->string = newstr;
+ man->next = ROFF_NEXT_SIBLING;
+}
+
+void
+roff_elem_alloc(struct roff_man *man, int line, int pos, int tok)
+{
+ struct roff_node *n;
+
+ n = roff_node_alloc(man, line, pos, ROFFT_ELEM, tok);
+ roff_node_append(man, n);
+ man->next = ROFF_NEXT_CHILD;
+}
+
+struct roff_node *
+roff_block_alloc(struct roff_man *man, int line, int pos, int tok)
+{
+ struct roff_node *n;
+
+ n = roff_node_alloc(man, line, pos, ROFFT_BLOCK, tok);
+ roff_node_append(man, n);
+ man->next = ROFF_NEXT_CHILD;
+ return n;
+}
+
+struct roff_node *
+roff_head_alloc(struct roff_man *man, int line, int pos, int tok)
+{
+ struct roff_node *n;
+
+ n = roff_node_alloc(man, line, pos, ROFFT_HEAD, tok);
+ roff_node_append(man, n);
+ man->next = ROFF_NEXT_CHILD;
+ return n;
+}
+
+struct roff_node *
+roff_body_alloc(struct roff_man *man, int line, int pos, int tok)
+{
+ struct roff_node *n;
+
+ n = roff_node_alloc(man, line, pos, ROFFT_BODY, tok);
+ roff_node_append(man, n);
+ man->next = ROFF_NEXT_CHILD;
+ return n;
+}
+
+static void
+roff_addtbl(struct roff_man *man, int line, struct tbl_node *tbl)
+{
+ struct roff_node *n;
+ struct tbl_span *span;
+
+ if (man->meta.macroset == MACROSET_MAN)
+ man_breakscope(man, ROFF_TS);
+ while ((span = tbl_span(tbl)) != NULL) {
+ n = roff_node_alloc(man, line, 0, ROFFT_TBL, TOKEN_NONE);
+ n->span = span;
+ roff_node_append(man, n);
+ n->flags |= NODE_VALID | NODE_ENDED;
+ man->next = ROFF_NEXT_SIBLING;
+ }
+}
+
+void
+roff_node_unlink(struct roff_man *man, struct roff_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 != NULL) {
+ if (n->parent->child == n)
+ n->parent->child = n->next;
+ if (n->parent->last == n)
+ n->parent->last = n->prev;
+ }
+
+ /* Adjust parse point. */
+
+ if (man == NULL)
+ return;
+ if (man->last == n) {
+ if (n->prev == NULL) {
+ man->last = n->parent;
+ man->next = ROFF_NEXT_CHILD;
+ } else {
+ man->last = n->prev;
+ man->next = ROFF_NEXT_SIBLING;
+ }
+ }
+ if (man->meta.first == n)
+ man->meta.first = NULL;
+}
+
+void
+roff_node_relink(struct roff_man *man, struct roff_node *n)
+{
+ roff_node_unlink(man, n);
+ n->prev = n->next = NULL;
+ roff_node_append(man, n);
+}
+
+void
+roff_node_free(struct roff_node *n)
+{
+
+ if (n->args != NULL)
+ mdoc_argv_free(n->args);
+ if (n->type == ROFFT_BLOCK || n->type == ROFFT_ELEM)
+ free(n->norm);
+ eqn_box_free(n->eqn);
+ free(n->string);
+ free(n->tag);
+ free(n);
+}
+
+void
+roff_node_delete(struct roff_man *man, struct roff_node *n)
+{
+
+ while (n->child != NULL)
+ roff_node_delete(man, n->child);
+ roff_node_unlink(man, n);
+ roff_node_free(n);
+}
+
+int
+roff_node_transparent(struct roff_node *n)
+{
+ if (n == NULL)
+ return 0;
+ if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
+ return 1;
+ return roff_tok_transparent(n->tok);
+}
+
+int
+roff_tok_transparent(enum roff_tok tok)
+{
+ switch (tok) {
+ case ROFF_ft:
+ case ROFF_ll:
+ case ROFF_mc:
+ case ROFF_po:
+ case ROFF_ta:
+ case MDOC_Db:
+ case MDOC_Es:
+ case MDOC_Sm:
+ case MDOC_Tg:
+ case MAN_DT:
+ case MAN_UC:
+ case MAN_PD:
+ case MAN_AT:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+struct roff_node *
+roff_node_child(struct roff_node *n)
+{
+ for (n = n->child; roff_node_transparent(n); n = n->next)
+ continue;
+ return n;
+}
+
+struct roff_node *
+roff_node_prev(struct roff_node *n)
+{
+ do {
+ n = n->prev;
+ } while (roff_node_transparent(n));
+ return n;
+}
+
+struct roff_node *
+roff_node_next(struct roff_node *n)
+{
+ do {
+ n = n->next;
+ } while (roff_node_transparent(n));
+ return n;
+}
+
+void
+deroff(char **dest, const struct roff_node *n)
+{
+ char *cp;
+ size_t sz;
+
+ if (n->string == NULL) {
+ for (n = n->child; n != NULL; n = n->next)
+ deroff(dest, n);
+ return;
+ }
+
+ /* Skip leading whitespace. */
+
+ for (cp = n->string; *cp != '\0'; cp++) {
+ if (cp[0] == '\\' && cp[1] != '\0' &&
+ strchr(" %&0^|~", cp[1]) != NULL)
+ cp++;
+ else if ( ! isspace((unsigned char)*cp))
+ break;
+ }
+
+ /* Skip trailing backslash. */
+
+ sz = strlen(cp);
+ if (sz > 0 && cp[sz - 1] == '\\')
+ sz--;
+
+ /* Skip trailing whitespace. */
+
+ for (; sz; sz--)
+ if ( ! isspace((unsigned char)cp[sz-1]))
+ break;
+
+ /* Skip empty strings. */
+
+ if (sz == 0)
+ return;
+
+ if (*dest == NULL) {
+ *dest = mandoc_strndup(cp, sz);
+ return;
+ }
+
+ mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
+ free(*dest);
+ *dest = cp;
+}
+
+/* --- main functions of the roff parser ---------------------------------- */
+
+/*
+ * In the current line, expand escape sequences that produce parsable
+ * input text. Also check the syntax of the remaining escape sequences,
+ * which typically produce output glyphs or change formatter state.
+ */
+static int
+roff_expand(struct roff *r, struct buf *buf, int ln, int pos, char newesc)
+{
+ struct mctx *ctx; /* current macro call context */
+ char ubuf[24]; /* buffer to print the number */
+ struct roff_node *n; /* used for header comments */
+ const char *start; /* start of the string to process */
+ char *stesc; /* start of an escape sequence ('\\') */
+ const char *esct; /* type of esccape sequence */
+ char *ep; /* end of comment string */
+ 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 buf->buf to */
+ size_t maxl; /* expected length of the escape name */
+ size_t naml; /* actual length of the escape name */
+ size_t asz; /* length of the replacement */
+ size_t rsz; /* length of the rest of the string */
+ int inaml; /* length returned from mandoc_escape() */
+ int expand_count; /* to avoid infinite loops */
+ int npos; /* position in numeric expression */
+ int arg_complete; /* argument not interrupted by eol */
+ int quote_args; /* true for \\$@, false for \\$* */
+ int done; /* no more input available */
+ int deftype; /* type of definition to paste */
+ int rcsid; /* kind of RCS id seen */
+ enum mandocerr err; /* for escape sequence problems */
+ char sign; /* increment number register */
+ char term; /* character terminating the escape */
+
+ /* Search forward for comments. */
+
+ done = 0;
+ start = buf->buf + pos;
+ for (stesc = buf->buf + pos; *stesc != '\0'; stesc++) {
+ if (stesc[0] != newesc || stesc[1] == '\0')
+ continue;
+ stesc++;
+ if (*stesc != '"' && *stesc != '#')
+ continue;
+
+ /* Comment found, look for RCS id. */
+
+ rcsid = 0;
+ if ((cp = strstr(stesc, "$" "OpenBSD")) != NULL) {
+ rcsid = 1 << MANDOC_OS_OPENBSD;
+ cp += 8;
+ } else if ((cp = strstr(stesc, "$" "NetBSD")) != NULL) {
+ rcsid = 1 << MANDOC_OS_NETBSD;
+ cp += 7;
+ }
+ if (cp != NULL &&
+ isalnum((unsigned char)*cp) == 0 &&
+ strchr(cp, '$') != NULL) {
+ if (r->man->meta.rcsids & rcsid)
+ mandoc_msg(MANDOCERR_RCS_REP, ln,
+ (int)(stesc - buf->buf) + 1,
+ "%s", stesc + 1);
+ r->man->meta.rcsids |= rcsid;
+ }
+
+ /* Handle trailing whitespace. */
+
+ ep = strchr(stesc--, '\0') - 1;
+ if (*ep == '\n') {
+ done = 1;
+ ep--;
+ }
+ if (*ep == ' ' || *ep == '\t')
+ mandoc_msg(MANDOCERR_SPACE_EOL,
+ ln, (int)(ep - buf->buf), NULL);
+
+ /*
+ * Save comments preceding the title macro
+ * in the syntax tree.
+ */
+
+ if (newesc != ASCII_ESC && r->options & MPARSE_COMMENT) {
+ while (*ep == ' ' || *ep == '\t')
+ ep--;
+ ep[1] = '\0';
+ n = roff_node_alloc(r->man,
+ ln, stesc + 1 - buf->buf,
+ ROFFT_COMMENT, TOKEN_NONE);
+ n->string = mandoc_strdup(stesc + 2);
+ roff_node_append(r->man, n);
+ n->flags |= NODE_VALID | NODE_ENDED;
+ r->man->next = ROFF_NEXT_SIBLING;
+ }
+
+ /* Line continuation with comment. */
+
+ if (stesc[1] == '#') {
+ *stesc = '\0';
+ return ROFF_IGN | ROFF_APPEND;
+ }
+
+ /* Discard normal comments. */
+
+ while (stesc > start && stesc[-1] == ' ' &&
+ (stesc == start + 1 || stesc[-2] != '\\'))
+ stesc--;
+ *stesc = '\0';
+ break;
+ }
+ if (stesc == start)
+ return ROFF_CONT;
+ stesc--;
+
+ /* Notice the end of the input. */
+
+ if (*stesc == '\n') {
+ *stesc-- = '\0';
+ done = 1;
+ }
+
+ expand_count = 0;
+ while (stesc >= start) {
+ if (*stesc != newesc) {
+
+ /*
+ * If we have a non-standard escape character,
+ * escape literal backslashes because all
+ * processing in subsequent functions uses
+ * the standard escaping rules.
+ */
+
+ if (newesc != ASCII_ESC && *stesc == '\\') {
+ *stesc = '\0';
+ buf->sz = mandoc_asprintf(&nbuf, "%s\\e%s",
+ buf->buf, stesc + 1) + 1;
+ start = nbuf + pos;
+ stesc = nbuf + (stesc - buf->buf);
+ free(buf->buf);
+ buf->buf = nbuf;
+ }
+
+ /* Search backwards for the next escape. */
+
+ stesc--;
+ continue;
+ }
+
+ /* If it is escaped, skip it. */
+
+ for (cp = stesc - 1; cp >= start; cp--)
+ if (*cp != r->escape)
+ break;
+
+ if ((stesc - cp) % 2 == 0) {
+ while (stesc > cp)
+ *stesc-- = '\\';
+ continue;
+ } else if (stesc[1] != '\0') {
+ *stesc = '\\';
+ } else {
+ *stesc-- = '\0';
+ if (done)
+ continue;
+ else
+ return ROFF_IGN | ROFF_APPEND;
+ }
+
+ /* Decide whether to expand or to check only. */
+
+ term = '\0';
+ cp = stesc + 1;
+ if (*cp == 'E')
+ cp++;
+ esct = cp;
+ switch (*esct) {
+ case '*':
+ case '$':
+ res = NULL;
+ break;
+ case 'B':
+ case 'w':
+ term = cp[1];
+ /* FALLTHROUGH */
+ case 'n':
+ sign = cp[1];
+ if (sign == '+' || sign == '-')
+ cp++;
+ res = ubuf;
+ break;
+ default:
+ err = MANDOCERR_OK;
+ switch(mandoc_escape(&cp, &stnam, &inaml)) {
+ case ESCAPE_SPECIAL:
+ if (mchars_spec2cp(stnam, inaml) >= 0)
+ break;
+ /* FALLTHROUGH */
+ case ESCAPE_ERROR:
+ err = MANDOCERR_ESC_BAD;
+ break;
+ case ESCAPE_UNDEF:
+ err = MANDOCERR_ESC_UNDEF;
+ break;
+ case ESCAPE_UNSUPP:
+ err = MANDOCERR_ESC_UNSUPP;
+ break;
+ default:
+ break;
+ }
+ if (err != MANDOCERR_OK)
+ mandoc_msg(err, ln, (int)(stesc - buf->buf),
+ "%.*s", (int)(cp - stesc), stesc);
+ stesc--;
+ continue;
+ }
+
+ if (EXPAND_LIMIT < ++expand_count) {
+ mandoc_msg(MANDOCERR_ROFFLOOP,
+ ln, (int)(stesc - buf->buf), 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 (term == '\0') {
+ 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. */
+
+ naml = 0;
+ arg_complete = 1;
+ while (maxl == 0 || naml < maxl) {
+ if (*cp == '\0') {
+ mandoc_msg(MANDOCERR_ESC_BAD, ln,
+ (int)(stesc - buf->buf), "%s", stesc);
+ arg_complete = 0;
+ break;
+ }
+ if (maxl == 0 && *cp == term) {
+ cp++;
+ break;
+ }
+ if (*cp++ != '\\' || *esct != 'w') {
+ naml++;
+ continue;
+ }
+ switch (mandoc_escape(&cp, NULL, NULL)) {
+ case ESCAPE_SPECIAL:
+ case ESCAPE_UNICODE:
+ case ESCAPE_NUMBERED:
+ case ESCAPE_UNDEF:
+ case ESCAPE_OVERSTRIKE:
+ naml++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Retrieve the replacement string; if it is
+ * undefined, resume searching for escapes.
+ */
+
+ switch (*esct) {
+ case '*':
+ if (arg_complete) {
+ deftype = ROFFDEF_USER | ROFFDEF_PRE;
+ res = roff_getstrn(r, stnam, naml, &deftype);
+
+ /*
+ * If not overriden, let \*(.T
+ * through to the formatters.
+ */
+
+ if (res == NULL && naml == 2 &&
+ stnam[0] == '.' && stnam[1] == 'T') {
+ roff_setstrn(&r->strtab,
+ ".T", 2, NULL, 0, 0);
+ stesc--;
+ continue;
+ }
+ }
+ break;
+ case '$':
+ if (r->mstackpos < 0) {
+ mandoc_msg(MANDOCERR_ARG_UNDEF, ln,
+ (int)(stesc - buf->buf), "%.3s", stesc);
+ break;
+ }
+ ctx = r->mstack + r->mstackpos;
+ npos = esct[1] - '1';
+ if (npos >= 0 && npos <= 8) {
+ res = npos < ctx->argc ?
+ ctx->argv[npos] : "";
+ break;
+ }
+ if (esct[1] == '*')
+ quote_args = 0;
+ else if (esct[1] == '@')
+ quote_args = 1;
+ else {
+ mandoc_msg(MANDOCERR_ARG_NONUM, ln,
+ (int)(stesc - buf->buf), "%.3s", stesc);
+ break;
+ }
+ asz = 0;
+ for (npos = 0; npos < ctx->argc; npos++) {
+ if (npos)
+ asz++; /* blank */
+ if (quote_args)
+ asz += 2; /* quotes */
+ asz += strlen(ctx->argv[npos]);
+ }
+ if (asz != 3) {
+ rsz = buf->sz - (stesc - buf->buf) - 3;
+ if (asz < 3)
+ memmove(stesc + asz, stesc + 3, rsz);
+ buf->sz += asz - 3;
+ nbuf = mandoc_realloc(buf->buf, buf->sz);
+ start = nbuf + pos;
+ stesc = nbuf + (stesc - buf->buf);
+ buf->buf = nbuf;
+ if (asz > 3)
+ memmove(stesc + asz, stesc + 3, rsz);
+ }
+ for (npos = 0; npos < ctx->argc; npos++) {
+ if (npos)
+ *stesc++ = ' ';
+ if (quote_args)
+ *stesc++ = '"';
+ cp = ctx->argv[npos];
+ while (*cp != '\0')
+ *stesc++ = *cp++;
+ if (quote_args)
+ *stesc++ = '"';
+ }
+ continue;
+ case 'B':
+ npos = 0;
+ ubuf[0] = arg_complete &&
+ roff_evalnum(r, ln, stnam, &npos,
+ NULL, ROFFNUM_SCALE) &&
+ 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, sign));
+ else
+ ubuf[0] = '\0';
+ break;
+ case 'w':
+ /* use even incomplete args */
+ (void)snprintf(ubuf, sizeof(ubuf), "%d",
+ 24 * (int)naml);
+ break;
+ }
+
+ if (res == NULL) {
+ if (*esct == '*')
+ mandoc_msg(MANDOCERR_STR_UNDEF,
+ ln, (int)(stesc - buf->buf),
+ "%.*s", (int)naml, stnam);
+ res = "";
+ } else if (buf->sz + strlen(res) > SHRT_MAX) {
+ mandoc_msg(MANDOCERR_ROFFLOOP,
+ ln, (int)(stesc - buf->buf), NULL);
+ return ROFF_IGN;
+ }
+
+ /* Replace the escape sequence by the string. */
+
+ *stesc = '\0';
+ buf->sz = mandoc_asprintf(&nbuf, "%s%s%s",
+ buf->buf, res, cp) + 1;
+
+ /* Prepare for the next replacement. */
+
+ start = nbuf + pos;
+ stesc = nbuf + (stesc - buf->buf) + strlen(res);
+ free(buf->buf);
+ buf->buf = nbuf;
+ }
+ return ROFF_CONT;
+}
+
+/*
+ * 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 *
+roff_getarg(struct roff *r, char **cpp, int ln, int *pos)
+{
+ struct buf buf;
+ char *cp, *start;
+ int newesc, pairs, quoted, white;
+
+ /* Quoting can only start with a new word. */
+ start = *cpp;
+ quoted = 0;
+ if ('"' == *start) {
+ quoted = 1;
+ start++;
+ }
+
+ newesc = pairs = 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 'a':
+ case 't':
+ cp[-pairs] = '\t';
+ pairs++;
+ cp++;
+ break;
+ case '\\':
+ newesc = 1;
+ cp[-pairs] = ASCII_ESC;
+ 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, 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, ln, *pos, NULL);
+
+ start = mandoc_strdup(start);
+ if (newesc == 0)
+ return start;
+
+ buf.buf = start;
+ buf.sz = strlen(start) + 1;
+ buf.next = NULL;
+ if (roff_expand(r, &buf, ln, 0, ASCII_ESC) & ROFF_IGN) {
+ free(buf.buf);
+ buf.buf = mandoc_strdup("");
+ }
+ return buf.buf;
+}
+
+
+/*
+ * Process text streams.
+ */
+static int
+roff_parsetext(struct roff *r, struct buf *buf, int pos, int *offs)
+{
+ size_t sz;
+ const char *start;
+ char *p;
+ int isz;
+ enum mandoc_esc esc;
+
+ /* Spring the input line trap. */
+
+ if (roffit_lines == 1) {
+ isz = mandoc_asprintf(&p, "%s\n.%s", buf->buf, roffit_macro);
+ free(buf->buf);
+ buf->buf = p;
+ buf->sz = isz + 1;
+ *offs = 0;
+ free(roffit_macro);
+ roffit_lines = 0;
+ return ROFF_REPARSE;
+ } else if (roffit_lines > 1)
+ --roffit_lines;
+
+ if (roffce_node != NULL && buf->buf[pos] != '\0') {
+ if (roffce_lines < 1) {
+ r->man->last = roffce_node;
+ r->man->next = ROFF_NEXT_SIBLING;
+ roffce_lines = 0;
+ roffce_node = NULL;
+ } else
+ roffce_lines--;
+ }
+
+ /* Convert all breakable hyphens into ASCII_HYPH. */
+
+ start = p = buf->buf + pos;
+
+ while (*p != '\0') {
+ sz = strcspn(p, "-\\");
+ p += sz;
+
+ if (*p == '\0')
+ break;
+
+ if (*p == '\\') {
+ /* Skip over escapes. */
+ p++;
+ esc = mandoc_escape((const char **)&p, NULL, NULL);
+ if (esc == ESCAPE_ERROR)
+ break;
+ while (*p == '-')
+ p++;
+ continue;
+ } else if (p == start) {
+ p++;
+ continue;
+ }
+
+ if (isalpha((unsigned char)p[-1]) &&
+ isalpha((unsigned char)p[1]))
+ *p = ASCII_HYPH;
+ p++;
+ }
+ return ROFF_CONT;
+}
+
+int
+roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
+{
+ enum roff_tok t;
+ int e;
+ int pos; /* parse point */
+ int spos; /* saved parse point for messages */
+ int ppos; /* original offset in buf->buf */
+ int ctl; /* macro line (boolean) */
+
+ ppos = pos = *offs;
+
+ /* Handle in-line equation delimiters. */
+
+ if (r->tbl == NULL &&
+ r->last_eqn != NULL && r->last_eqn->delim &&
+ (r->eqn == NULL || r->eqn_inline)) {
+ e = roff_eqndelim(r, buf, pos);
+ if (e == ROFF_REPARSE)
+ return e;
+ assert(e == ROFF_CONT);
+ }
+
+ /* Expand some escape sequences. */
+
+ e = roff_expand(r, buf, ln, pos, r->escape);
+ if ((e & ROFF_MASK) == ROFF_IGN)
+ return e;
+ assert(e == ROFF_CONT);
+
+ ctl = roff_getcontrol(r, buf->buf, &pos);
+
+ /*
+ * First, if a scope is open and we're not a macro, pass the
+ * text through the macro's filter.
+ * Equations process all content themselves.
+ * Tables process almost all content themselves, but we want
+ * to warn about macros before passing it there.
+ */
+
+ if (r->last != NULL && ! ctl) {
+ t = r->last->tok;
+ e = (*roffs[t].text)(r, t, buf, ln, pos, pos, offs);
+ if ((e & ROFF_MASK) == ROFF_IGN)
+ return e;
+ e &= ~ROFF_MASK;
+ } else
+ e = ROFF_IGN;
+ if (r->eqn != NULL && strncmp(buf->buf + ppos, ".EN", 3)) {
+ eqn_read(r->eqn, buf->buf + ppos);
+ return e;
+ }
+ if (r->tbl != NULL && (ctl == 0 || buf->buf[pos] == '\0')) {
+ tbl_read(r->tbl, ln, buf->buf, ppos);
+ roff_addtbl(r->man, ln, r->tbl);
+ return e;
+ }
+ if ( ! ctl) {
+ r->options &= ~MPARSE_COMMENT;
+ return roff_parsetext(r, buf, pos, offs) | e;
+ }
+
+ /* Skip empty request lines. */
+
+ if (buf->buf[pos] == '"') {
+ mandoc_msg(MANDOCERR_COMMENT_BAD, ln, pos, NULL);
+ return ROFF_IGN;
+ } else if (buf->buf[pos] == '\0')
+ return ROFF_IGN;
+
+ /*
+ * 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;
+ return (*roffs[t].sub)(r, t, buf, ln, ppos, pos, offs);
+ }
+
+ /* No scope is open. This is a new request or macro. */
+
+ r->options &= ~MPARSE_COMMENT;
+ spos = pos;
+ t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+ /* Tables ignore most macros. */
+
+ if (r->tbl != NULL && (t == TOKEN_NONE || t == ROFF_TS ||
+ t == ROFF_br || t == ROFF_ce || t == ROFF_rj || t == ROFF_sp)) {
+ mandoc_msg(MANDOCERR_TBLMACRO,
+ ln, pos, "%s", buf->buf + spos);
+ if (t != TOKEN_NONE)
+ return ROFF_IGN;
+ while (buf->buf[pos] != '\0' && buf->buf[pos] != ' ')
+ pos++;
+ while (buf->buf[pos] == ' ')
+ pos++;
+ tbl_read(r->tbl, ln, buf->buf, pos);
+ roff_addtbl(r->man, ln, r->tbl);
+ return ROFF_IGN;
+ }
+
+ /* For now, let high level macros abort .ce mode. */
+
+ if (ctl && roffce_node != NULL &&
+ (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ ||
+ t == ROFF_TH || t == ROFF_TS)) {
+ r->man->last = roffce_node;
+ r->man->next = ROFF_NEXT_SIBLING;
+ roffce_lines = 0;
+ roffce_node = NULL;
+ }
+
+ /*
+ * This is neither a roff request nor a user-defined macro.
+ * Let the standard macro set parsers handle it.
+ */
+
+ if (t == TOKEN_NONE)
+ return ROFF_CONT;
+
+ /* Execute a roff request or a user defined macro. */
+
+ return (*roffs[t].proc)(r, t, buf, ln, spos, pos, offs);
+}
+
+/*
+ * Internal interface function to tell the roff parser that execution
+ * of the current macro ended. This is required because macro
+ * definitions usually do not end with a .return request.
+ */
+void
+roff_userret(struct roff *r)
+{
+ struct mctx *ctx;
+ int i;
+
+ assert(r->mstackpos >= 0);
+ ctx = r->mstack + r->mstackpos;
+ for (i = 0; i < ctx->argc; i++)
+ free(ctx->argv[i]);
+ ctx->argc = 0;
+ r->mstackpos--;
+}
+
+void
+roff_endparse(struct roff *r)
+{
+ if (r->last != NULL)
+ mandoc_msg(MANDOCERR_BLK_NOEND, r->last->line,
+ r->last->col, "%s", roff_name[r->last->tok]);
+
+ if (r->eqn != NULL) {
+ mandoc_msg(MANDOCERR_BLK_NOEND,
+ r->eqn->node->line, r->eqn->node->pos, "EQ");
+ eqn_parse(r->eqn);
+ r->eqn = NULL;
+ }
+
+ if (r->tbl != NULL) {
+ tbl_end(r->tbl, 1);
+ r->tbl = NULL;
+ }
+}
+
+/*
+ * 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 roff_tok
+roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos)
+{
+ char *cp;
+ const char *mac;
+ size_t maclen;
+ int deftype;
+ enum roff_tok t;
+
+ cp = buf + *pos;
+
+ if ('\0' == *cp || '"' == *cp || '\t' == *cp || ' ' == *cp)
+ return TOKEN_NONE;
+
+ mac = cp;
+ maclen = roff_getname(r, &cp, ln, ppos);
+
+ deftype = ROFFDEF_USER | ROFFDEF_REN;
+ r->current_string = roff_getstrn(r, mac, maclen, &deftype);
+ switch (deftype) {
+ case ROFFDEF_USER:
+ t = ROFF_USERDEF;
+ break;
+ case ROFFDEF_REN:
+ t = ROFF_RENAMED;
+ break;
+ default:
+ t = roffhash_find(r->reqtab, mac, maclen);
+ break;
+ }
+ if (t != TOKEN_NONE)
+ *pos = cp - buf;
+ else if (deftype == ROFFDEF_UNDEF) {
+ /* Using an undefined macro defines it to be empty. */
+ roff_setstrn(&r->strtab, mac, maclen, "", 0, 0);
+ roff_setstrn(&r->rentab, mac, maclen, NULL, 0, 0);
+ }
+ return t;
+}
+
+/* --- handling of request blocks ----------------------------------------- */
+
+static int
+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 (r->last == NULL) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
+ return ROFF_IGN;
+ }
+
+ switch (r->last->tok) {
+ case ROFF_am:
+ /* ROFF_am1 is remapped to ROFF_am in roff_block(). */
+ case ROFF_ami:
+ case ROFF_de:
+ /* ROFF_de1 is remapped to ROFF_de in roff_block(). */
+ case ROFF_dei:
+ case ROFF_ig:
+ break;
+ default:
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
+ return ROFF_IGN;
+ }
+
+ if (buf->buf[pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
+ ".. %s", buf->buf + pos);
+
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+ return ROFF_IGN;
+
+}
+
+/*
+ * Pop all nodes ending at the end of the current input line.
+ * Return the number of loops ended.
+ */
+static int
+roffnode_cleanscope(struct roff *r)
+{
+ int inloop;
+
+ inloop = 0;
+ while (r->last != NULL) {
+ if (--r->last->endspan != 0)
+ break;
+ inloop += roffnode_pop(r);
+ }
+ return inloop;
+}
+
+/*
+ * Handle the closing \} of a conditional block.
+ * Apart from generating warnings, this only pops nodes.
+ * Return the number of loops ended.
+ */
+static int
+roff_ccond(struct roff *r, int ln, int ppos)
+{
+ if (NULL == r->last) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "\\}");
+ return 0;
+ }
+
+ switch (r->last->tok) {
+ case ROFF_el:
+ case ROFF_ie:
+ case ROFF_if:
+ case ROFF_while:
+ break;
+ default:
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "\\}");
+ return 0;
+ }
+
+ if (r->last->endspan > -1) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "\\}");
+ return 0;
+ }
+
+ return roffnode_pop(r) + roffnode_cleanscope(r);
+}
+
+static int
+roff_block(ROFF_ARGS)
+{
+ const char *name, *value;
+ char *call, *cp, *iname, *rname;
+ size_t csz, namesz, rsz;
+ int deftype;
+
+ /* Ignore groff compatibility mode for now. */
+
+ if (tok == ROFF_de1)
+ tok = ROFF_de;
+ else if (tok == ROFF_dei1)
+ tok = ROFF_dei;
+ else if (tok == ROFF_am1)
+ tok = ROFF_am;
+ else if (tok == ROFF_ami1)
+ tok = ROFF_ami;
+
+ /* Parse the macro name argument. */
+
+ cp = buf->buf + pos;
+ if (tok == ROFF_ig) {
+ 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 && (tok == ROFF_dei || tok == ROFF_ami)) {
+ deftype = ROFFDEF_USER;
+ name = roff_getstrn(r, iname, namesz, &deftype);
+ if (name == NULL) {
+ mandoc_msg(MANDOCERR_STR_UNDEF,
+ ln, (int)(iname - buf->buf),
+ "%.*s", (int)namesz, iname);
+ namesz = 0;
+ } else
+ namesz = strlen(name);
+ } else
+ name = iname;
+
+ if (namesz == 0 && tok != ROFF_ig) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ln, ppos, "%s", roff_name[tok]);
+ 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 (tok == ROFF_de || tok == ROFF_dei) {
+ roff_setstrn(&r->strtab, name, namesz, "", 0, 0);
+ roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
+ } else if (tok == ROFF_am || tok == ROFF_ami) {
+ deftype = ROFFDEF_ANY;
+ value = roff_getstrn(r, iname, namesz, &deftype);
+ switch (deftype) { /* Before appending, ... */
+ case ROFFDEF_PRE: /* copy predefined to user-defined. */
+ roff_setstrn(&r->strtab, name, namesz,
+ value, strlen(value), 0);
+ break;
+ case ROFFDEF_REN: /* call original standard macro. */
+ csz = mandoc_asprintf(&call, ".%.*s \\$* \\\"\n",
+ (int)strlen(value), value);
+ roff_setstrn(&r->strtab, name, namesz, call, csz, 0);
+ roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
+ free(call);
+ break;
+ case ROFFDEF_STD: /* rename and call standard macro. */
+ rsz = mandoc_asprintf(&rname, "__%s_renamed", name);
+ roff_setstrn(&r->rentab, rname, rsz, name, namesz, 0);
+ csz = mandoc_asprintf(&call, ".%.*s \\$* \\\"\n",
+ (int)rsz, rname);
+ roff_setstrn(&r->strtab, name, namesz, call, csz, 0);
+ free(call);
+ free(rname);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (*cp == '\0')
+ 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 && (tok == ROFF_dei || tok == ROFF_ami)) {
+ deftype = ROFFDEF_USER;
+ name = roff_getstrn(r, iname, namesz, &deftype);
+ if (name == NULL) {
+ mandoc_msg(MANDOCERR_STR_UNDEF,
+ ln, (int)(iname - buf->buf),
+ "%.*s", (int)namesz, iname);
+ namesz = 0;
+ } else
+ namesz = strlen(name);
+ } else
+ name = iname;
+
+ if (namesz)
+ r->last->end = mandoc_strndup(name, namesz);
+
+ if (*cp != '\0')
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ ln, pos, ".%s ... %s", roff_name[tok], cp);
+
+ return ROFF_IGN;
+}
+
+static int
+roff_block_sub(ROFF_ARGS)
+{
+ enum roff_tok 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 (buf->buf[i] != r->last->end[j])
+ break;
+
+ if (r->last->end[j] == '\0' &&
+ (buf->buf[i] == '\0' ||
+ buf->buf[i] == ' ' ||
+ buf->buf[i] == '\t')) {
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+
+ while (buf->buf[i] == ' ' || buf->buf[i] == '\t')
+ i++;
+
+ pos = i;
+ if (roff_parse(r, buf->buf, &pos, ln, ppos) !=
+ TOKEN_NONE)
+ 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, buf->buf, &pos, ln, ppos);
+
+ if (t != ROFF_cblock) {
+ if (tok != ROFF_ig)
+ roff_setstr(r, r->last->name, buf->buf + ppos, 2);
+ return ROFF_IGN;
+ }
+
+ return (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
+}
+
+static int
+roff_block_text(ROFF_ARGS)
+{
+
+ if (tok != ROFF_ig)
+ roff_setstr(r, r->last->name, buf->buf + pos, 2);
+
+ return ROFF_IGN;
+}
+
+static int
+roff_cond_sub(ROFF_ARGS)
+{
+ struct roffnode *bl;
+ char *ep;
+ int endloop, irc, rr;
+ enum roff_tok t;
+
+ irc = ROFF_IGN;
+ rr = r->last->rule;
+ endloop = tok != ROFF_while ? ROFF_IGN :
+ rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
+ if (roffnode_cleanscope(r))
+ irc |= endloop;
+
+ /*
+ * If `\}' occurs on a macro line without a preceding macro,
+ * drop the line completely.
+ */
+
+ ep = buf->buf + pos;
+ if (ep[0] == '\\' && ep[1] == '}')
+ rr = 0;
+
+ /*
+ * The closing delimiter `\}' rewinds the conditional scope
+ * but is otherwise ignored when interpreting the line.
+ */
+
+ while ((ep = strchr(ep, '\\')) != NULL) {
+ switch (ep[1]) {
+ case '}':
+ memmove(ep, ep + 2, strlen(ep + 2) + 1);
+ if (roff_ccond(r, ln, ep - buf->buf))
+ irc |= endloop;
+ break;
+ case '\0':
+ ++ep;
+ break;
+ default:
+ ep += 2;
+ break;
+ }
+ }
+
+ t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+ /* For now, let high level macros abort .ce mode. */
+
+ if (roffce_node != NULL &&
+ (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ ||
+ t == ROFF_TH || t == ROFF_TS)) {
+ r->man->last = roffce_node;
+ r->man->next = ROFF_NEXT_SIBLING;
+ roffce_lines = 0;
+ roffce_node = NULL;
+ }
+
+ /*
+ * Fully handle known macros when they are structurally
+ * required or when the conditional evaluated to true.
+ */
+
+ if (t == ROFF_break) {
+ if (irc & ROFF_LOOPMASK)
+ irc = ROFF_IGN | ROFF_LOOPEXIT;
+ else if (rr) {
+ for (bl = r->last; bl != NULL; bl = bl->parent) {
+ bl->rule = 0;
+ if (bl->tok == ROFF_while)
+ break;
+ }
+ }
+ } else if (t != TOKEN_NONE &&
+ (rr || roffs[t].flags & ROFFMAC_STRUCT))
+ irc |= (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
+ else
+ irc |= rr ? ROFF_CONT : ROFF_IGN;
+ return irc;
+}
+
+static int
+roff_cond_text(ROFF_ARGS)
+{
+ char *ep;
+ int endloop, irc, rr;
+
+ irc = ROFF_IGN;
+ rr = r->last->rule;
+ endloop = tok != ROFF_while ? ROFF_IGN :
+ rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
+ if (roffnode_cleanscope(r))
+ irc |= endloop;
+
+ /*
+ * If `\}' occurs on a text line with neither preceding
+ * nor following characters, drop the line completely.
+ */
+
+ ep = buf->buf + pos;
+ if (strcmp(ep, "\\}") == 0)
+ rr = 0;
+
+ /*
+ * The closing delimiter `\}' rewinds the conditional scope
+ * but is otherwise ignored when interpreting the line.
+ */
+
+ while ((ep = strchr(ep, '\\')) != NULL) {
+ switch (ep[1]) {
+ case '}':
+ memmove(ep, ep + 2, strlen(ep + 2) + 1);
+ if (roff_ccond(r, ln, ep - buf->buf))
+ irc |= endloop;
+ break;
+ case '\0':
+ ++ep;
+ break;
+ default:
+ ep += 2;
+ break;
+ }
+ }
+ if (rr)
+ irc |= ROFF_CONT;
+ return irc;
+}
+
+/* --- handling of numeric and conditional expressions -------------------- */
+
+/*
+ * 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 flags)
+{
+ int myres, scaled, n, p;
+
+ if (NULL == res)
+ res = &myres;
+
+ p = *pos;
+ n = v[p] == '-';
+ if (n || v[p] == '+')
+ p++;
+
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[p]))
+ 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;
+
+ /* Each number may be followed by one optional scaling unit. */
+
+ switch (v[p]) {
+ case 'f':
+ scaled = *res * 65536;
+ break;
+ case 'i':
+ scaled = *res * 240;
+ break;
+ case 'c':
+ scaled = *res * 240 / 2.54;
+ break;
+ case 'v':
+ case 'P':
+ scaled = *res * 40;
+ break;
+ case 'm':
+ case 'n':
+ scaled = *res * 24;
+ break;
+ case 'p':
+ scaled = *res * 10 / 3;
+ break;
+ case 'u':
+ scaled = *res;
+ break;
+ case 'M':
+ scaled = *res * 6 / 25;
+ break;
+ default:
+ scaled = *res;
+ p--;
+ break;
+ }
+ if (flags & ROFFNUM_SCALE)
+ *res = scaled;
+
+ *pos = p + 1;
+ 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 if (*s3 != '\0')
+ s3++;
+ *pos = s3 - v;
+ return match;
+}
+
+/*
+ * Evaluate an optionally negated single character, numerical,
+ * or string condition.
+ */
+static int
+roff_evalcond(struct roff *r, int ln, char *v, int *pos)
+{
+ const char *start, *end;
+ char *cp, *name;
+ size_t sz;
+ int deftype, len, number, savepos, istrue, wanttrue;
+
+ if ('!' == v[*pos]) {
+ wanttrue = 0;
+ (*pos)++;
+ } else
+ wanttrue = 1;
+
+ switch (v[*pos]) {
+ case '\0':
+ return 0;
+ case 'n':
+ case 'o':
+ (*pos)++;
+ return wanttrue;
+ case 'e':
+ case 't':
+ case 'v':
+ (*pos)++;
+ return !wanttrue;
+ case 'c':
+ do {
+ (*pos)++;
+ } while (v[*pos] == ' ');
+
+ /*
+ * Quirk for groff compatibility:
+ * The horizontal tab is neither available nor unavailable.
+ */
+
+ if (v[*pos] == '\t') {
+ (*pos)++;
+ return 0;
+ }
+
+ /* Printable ASCII characters are available. */
+
+ if (v[*pos] != '\\') {
+ (*pos)++;
+ return wanttrue;
+ }
+
+ end = v + ++*pos;
+ switch (mandoc_escape(&end, &start, &len)) {
+ case ESCAPE_SPECIAL:
+ istrue = mchars_spec2cp(start, len) != -1;
+ break;
+ case ESCAPE_UNICODE:
+ istrue = 1;
+ break;
+ case ESCAPE_NUMBERED:
+ istrue = mchars_num2char(start, len) != -1;
+ break;
+ default:
+ istrue = !wanttrue;
+ break;
+ }
+ *pos = end - v;
+ return istrue == wanttrue;
+ case 'd':
+ case 'r':
+ cp = v + *pos + 1;
+ while (*cp == ' ')
+ cp++;
+ name = cp;
+ sz = roff_getname(r, &cp, ln, cp - v);
+ if (sz == 0)
+ istrue = 0;
+ else if (v[*pos] == 'r')
+ istrue = roff_hasregn(r, name, sz);
+ else {
+ deftype = ROFFDEF_ANY;
+ roff_getstrn(r, name, sz, &deftype);
+ istrue = !!deftype;
+ }
+ *pos = (name + sz) - v;
+ return istrue == wanttrue;
+ default:
+ break;
+ }
+
+ savepos = *pos;
+ if (roff_evalnum(r, ln, v, pos, &number, ROFFNUM_SCALE))
+ return (number > 0) == wanttrue;
+ else if (*pos == savepos)
+ return roff_evalstrcond(v, pos) == wanttrue;
+ else
+ return 0;
+}
+
+static int
+roff_line_ignore(ROFF_ARGS)
+{
+
+ return ROFF_IGN;
+}
+
+static int
+roff_insec(ROFF_ARGS)
+{
+
+ mandoc_msg(MANDOCERR_REQ_INSEC, ln, ppos, "%s", roff_name[tok]);
+ return ROFF_IGN;
+}
+
+static int
+roff_unsupp(ROFF_ARGS)
+{
+
+ mandoc_msg(MANDOCERR_REQ_UNSUPP, ln, ppos, "%s", roff_name[tok]);
+ return ROFF_IGN;
+}
+
+static int
+roff_cond(ROFF_ARGS)
+{
+ int irc;
+
+ 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 = tok == ROFF_el ?
+ (r->rstackpos < 0 ? 0 : r->rstack[r->rstackpos--]) :
+ roff_evalcond(r, ln, buf->buf, &pos);
+
+ /*
+ * An if-else will put the NEGATION of the current evaluated
+ * conditional into the stack of rules.
+ */
+
+ if (tok == ROFF_ie) {
+ 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.
+ * Except that .while does not support next-line scope.
+ */
+
+ if (buf->buf[pos] == '\0' && tok != ROFF_while) {
+ r->last->endspan = 2;
+ goto out;
+ }
+
+ while (buf->buf[pos] == ' ')
+ pos++;
+
+ /* An opening brace requests multiline scope. */
+
+ if (buf->buf[pos] == '\\' && buf->buf[pos + 1] == '{') {
+ r->last->endspan = -1;
+ pos += 2;
+ while (buf->buf[pos] == ' ')
+ pos++;
+ goto out;
+ }
+
+ /*
+ * Anything else following the conditional causes
+ * single-line scope. Warn if the scope contains
+ * nothing but trailing whitespace.
+ */
+
+ if (buf->buf[pos] == '\0')
+ mandoc_msg(MANDOCERR_COND_EMPTY,
+ ln, ppos, "%s", roff_name[tok]);
+
+ r->last->endspan = 1;
+
+out:
+ *offs = pos;
+ irc = ROFF_RERUN;
+ if (tok == ROFF_while)
+ irc |= ROFF_WHILE;
+ return irc;
+}
+
+static int
+roff_ds(ROFF_ARGS)
+{
+ char *string;
+ const char *name;
+ size_t namesz;
+
+ /* Ignore groff compatibility mode for now. */
+
+ if (tok == ROFF_ds1)
+ tok = ROFF_ds;
+ else if (tok == ROFF_as1)
+ tok = ROFF_as;
+
+ /*
+ * 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 = buf->buf + pos;
+ if (*name == '\0')
+ return ROFF_IGN;
+
+ namesz = roff_getname(r, &string, ln, pos);
+ switch (name[namesz]) {
+ case '\\':
+ return ROFF_IGN;
+ case '\t':
+ string = buf->buf + pos + namesz;
+ break;
+ default:
+ break;
+ }
+
+ /* 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);
+ roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
+ 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 '+':
+ case '-':
+ case '*':
+ case '/':
+ case '%':
+ case '&':
+ 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(struct roff *r, int ln,
+ const char *v, int *pos, int *res, int flags)
+{
+
+ if ('(' != v[*pos])
+ return roff_getnum(v, pos, res, flags);
+
+ (*pos)++;
+ if ( ! roff_evalnum(r, ln, v, pos, res, flags | ROFFNUM_WHITE))
+ 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(struct roff *r, int ln, const char *v,
+ int *pos, int *res, int flags)
+{
+ int mypos, operand2;
+ char operator;
+
+ if (NULL == pos) {
+ mypos = 0;
+ pos = &mypos;
+ }
+
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_evalpar(r, ln, v, pos, res, flags))
+ return 0;
+
+ while (1) {
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_getop(v, pos, &operator))
+ break;
+
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_evalpar(r, ln, v, pos, &operand2, flags))
+ return 0;
+
+ if (flags & ROFFNUM_WHITE)
+ 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 '/':
+ if (operand2 == 0) {
+ mandoc_msg(MANDOCERR_DIVZERO,
+ ln, *pos, "%s", v);
+ *res = 0;
+ break;
+ }
+ *res /= operand2;
+ break;
+ case '%':
+ if (operand2 == 0) {
+ mandoc_msg(MANDOCERR_DIVZERO,
+ ln, *pos, "%s", v);
+ *res = 0;
+ break;
+ }
+ *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;
+}
+
+/* --- register management ------------------------------------------------ */
+
+void
+roff_setreg(struct roff *r, const char *name, int val, char sign)
+{
+ roff_setregn(r, name, strlen(name), val, sign, INT_MIN);
+}
+
+static void
+roff_setregn(struct roff *r, const char *name, size_t len,
+ int val, char sign, int step)
+{
+ struct roffreg *reg;
+
+ /* Search for an existing register with the same name. */
+ reg = r->regtab;
+
+ while (reg != NULL && (reg->key.sz != len ||
+ strncmp(reg->key.p, name, len) != 0))
+ reg = reg->next;
+
+ if (NULL == reg) {
+ /* Create a new register. */
+ reg = mandoc_malloc(sizeof(struct roffreg));
+ reg->key.p = mandoc_strndup(name, len);
+ reg->key.sz = len;
+ reg->val = 0;
+ reg->step = 0;
+ reg->next = r->regtab;
+ r->regtab = reg;
+ }
+
+ if ('+' == sign)
+ reg->val += val;
+ else if ('-' == sign)
+ reg->val -= val;
+ else
+ reg->val = val;
+ if (step != INT_MIN)
+ reg->step = step;
+}
+
+/*
+ * 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 struct roff *r, const char *name)
+{
+
+ switch (*name) {
+ case '$': /* Number of arguments of the last macro evaluated. */
+ return r->mstackpos < 0 ? 0 : r->mstack[r->mstackpos].argc;
+ 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(struct roff *r, const char *name)
+{
+ return roff_getregn(r, name, strlen(name), '\0');
+}
+
+static int
+roff_getregn(struct roff *r, const char *name, size_t len, char sign)
+{
+ struct roffreg *reg;
+ int val;
+
+ if ('.' == name[0] && 2 == len) {
+ val = roff_getregro(r, 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)) {
+ switch (sign) {
+ case '+':
+ reg->val += reg->step;
+ break;
+ case '-':
+ reg->val -= reg->step;
+ break;
+ default:
+ break;
+ }
+ return reg->val;
+ }
+ }
+
+ roff_setregn(r, name, len, 0, '\0', INT_MIN);
+ return 0;
+}
+
+static int
+roff_hasregn(const struct roff *r, const char *name, size_t len)
+{
+ struct roffreg *reg;
+ int val;
+
+ if ('.' == name[0] && 2 == len) {
+ val = roff_getregro(r, name + 1);
+ if (-1 != val)
+ return 1;
+ }
+
+ for (reg = r->regtab; reg; reg = reg->next)
+ if (len == reg->key.sz &&
+ 0 == strncmp(name, reg->key.p, len))
+ return 1;
+
+ 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 int
+roff_nr(ROFF_ARGS)
+{
+ char *key, *val, *step;
+ size_t keysz;
+ int iv, is, len;
+ char sign;
+
+ key = val = buf->buf + pos;
+ if (*key == '\0')
+ return ROFF_IGN;
+
+ keysz = roff_getname(r, &val, ln, pos);
+ if (key[keysz] == '\\' || key[keysz] == '\t')
+ return ROFF_IGN;
+
+ sign = *val;
+ if (sign == '+' || sign == '-')
+ val++;
+
+ len = 0;
+ if (roff_evalnum(r, ln, val, &len, &iv, ROFFNUM_SCALE) == 0)
+ return ROFF_IGN;
+
+ step = val + len;
+ while (isspace((unsigned char)*step))
+ step++;
+ if (roff_evalnum(r, ln, step, NULL, &is, 0) == 0)
+ is = INT_MIN;
+
+ roff_setregn(r, key, keysz, iv, sign, is);
+ return ROFF_IGN;
+}
+
+static int
+roff_rr(ROFF_ARGS)
+{
+ struct roffreg *reg, **prev;
+ char *name, *cp;
+ size_t namesz;
+
+ name = cp = buf->buf + pos;
+ if (*name == '\0')
+ return ROFF_IGN;
+ namesz = roff_getname(r, &cp, ln, pos);
+ name[namesz] = '\0';
+
+ prev = &r->regtab;
+ while (1) {
+ reg = *prev;
+ if (reg == NULL || !strcmp(name, reg->key.p))
+ break;
+ prev = &reg->next;
+ }
+ if (reg != NULL) {
+ *prev = reg->next;
+ free(reg->key.p);
+ free(reg);
+ }
+ return ROFF_IGN;
+}
+
+/* --- handler functions for roff requests -------------------------------- */
+
+static int
+roff_rm(ROFF_ARGS)
+{
+ const char *name;
+ char *cp;
+ size_t namesz;
+
+ cp = buf->buf + pos;
+ while (*cp != '\0') {
+ name = cp;
+ namesz = roff_getname(r, &cp, ln, (int)(cp - buf->buf));
+ roff_setstrn(&r->strtab, name, namesz, NULL, 0, 0);
+ roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
+ if (name[namesz] == '\\' || name[namesz] == '\t')
+ break;
+ }
+ return ROFF_IGN;
+}
+
+static int
+roff_it(ROFF_ARGS)
+{
+ int iv;
+
+ /* Parse the number of lines. */
+
+ if ( ! roff_evalnum(r, ln, buf->buf, &pos, &iv, 0)) {
+ mandoc_msg(MANDOCERR_IT_NONUM,
+ ln, ppos, "%s", buf->buf + 1);
+ return ROFF_IGN;
+ }
+
+ while (isspace((unsigned char)buf->buf[pos]))
+ pos++;
+
+ /*
+ * Arm the input line trap.
+ * Special-casing "an-trap" is an ugly workaround to cope
+ * with DocBook stupidly fiddling with man(7) internals.
+ */
+
+ roffit_lines = iv;
+ roffit_macro = mandoc_strdup(iv != 1 ||
+ strcmp(buf->buf + pos, "an-trap") ?
+ buf->buf + pos : "br");
+ return ROFF_IGN;
+}
+
+static int
+roff_Dd(ROFF_ARGS)
+{
+ int mask;
+ enum roff_tok t, te;
+
+ switch (tok) {
+ case ROFF_Dd:
+ tok = MDOC_Dd;
+ te = MDOC_MAX;
+ if (r->format == 0)
+ r->format = MPARSE_MDOC;
+ mask = MPARSE_MDOC | MPARSE_QUICK;
+ break;
+ case ROFF_TH:
+ tok = MAN_TH;
+ te = MAN_MAX;
+ if (r->format == 0)
+ r->format = MPARSE_MAN;
+ mask = MPARSE_QUICK;
+ break;
+ default:
+ abort();
+ }
+ if ((r->options & mask) == 0)
+ for (t = tok; t < te; t++)
+ roff_setstr(r, roff_name[t], NULL, 0);
+ return ROFF_CONT;
+}
+
+static int
+roff_TE(ROFF_ARGS)
+{
+ r->man->flags &= ~ROFF_NONOFILL;
+ if (r->tbl == NULL) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "TE");
+ return ROFF_IGN;
+ }
+ if (tbl_end(r->tbl, 0) == 0) {
+ r->tbl = NULL;
+ free(buf->buf);
+ buf->buf = mandoc_strdup(".sp");
+ buf->sz = 4;
+ *offs = 0;
+ return ROFF_REPARSE;
+ }
+ r->tbl = NULL;
+ return ROFF_IGN;
+}
+
+static int
+roff_T_(ROFF_ARGS)
+{
+
+ if (NULL == r->tbl)
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "T&");
+ else
+ tbl_restart(ln, ppos, r->tbl);
+
+ return ROFF_IGN;
+}
+
+/*
+ * Handle in-line equation delimiters.
+ */
+static int
+roff_eqndelim(struct roff *r, struct buf *buf, int pos)
+{
+ char *cp1, *cp2;
+ const char *bef_pr, *bef_nl, *mac, *aft_nl, *aft_pr;
+
+ /*
+ * Outside equations, look for an opening delimiter.
+ * If we are inside an equation, we already know it is
+ * in-line, or this function wouldn't have been called;
+ * so look for a closing delimiter.
+ */
+
+ cp1 = buf->buf + pos;
+ cp2 = strchr(cp1, r->eqn == NULL ?
+ r->last_eqn->odelim : r->last_eqn->cdelim);
+ if (cp2 == NULL)
+ return ROFF_CONT;
+
+ *cp2++ = '\0';
+ bef_pr = bef_nl = aft_nl = aft_pr = "";
+
+ /* Handle preceding text, protecting whitespace. */
+
+ if (*buf->buf != '\0') {
+ if (r->eqn == NULL)
+ bef_pr = "\\&";
+ bef_nl = "\n";
+ }
+
+ /*
+ * Prepare replacing the delimiter with an equation macro
+ * and drop leading white space from the equation.
+ */
+
+ if (r->eqn == NULL) {
+ while (*cp2 == ' ')
+ cp2++;
+ mac = ".EQ";
+ } else
+ mac = ".EN";
+
+ /* Handle following text, protecting whitespace. */
+
+ if (*cp2 != '\0') {
+ aft_nl = "\n";
+ if (r->eqn != NULL)
+ aft_pr = "\\&";
+ }
+
+ /* Do the actual replacement. */
+
+ buf->sz = mandoc_asprintf(&cp1, "%s%s%s%s%s%s%s", buf->buf,
+ bef_pr, bef_nl, mac, aft_nl, aft_pr, cp2) + 1;
+ free(buf->buf);
+ buf->buf = cp1;
+
+ /* Toggle the in-line state of the eqn subsystem. */
+
+ r->eqn_inline = r->eqn == NULL;
+ return ROFF_REPARSE;
+}
+
+static int
+roff_EQ(ROFF_ARGS)
+{
+ struct roff_node *n;
+
+ if (r->man->meta.macroset == MACROSET_MAN)
+ man_breakscope(r->man, ROFF_EQ);
+ n = roff_node_alloc(r->man, ln, ppos, ROFFT_EQN, TOKEN_NONE);
+ if (ln > r->man->last->line)
+ n->flags |= NODE_LINE;
+ n->eqn = eqn_box_new();
+ roff_node_append(r->man, n);
+ r->man->next = ROFF_NEXT_SIBLING;
+
+ assert(r->eqn == NULL);
+ if (r->last_eqn == NULL)
+ r->last_eqn = eqn_alloc();
+ else
+ eqn_reset(r->last_eqn);
+ r->eqn = r->last_eqn;
+ r->eqn->node = n;
+
+ if (buf->buf[pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
+ ".EQ %s", buf->buf + pos);
+
+ return ROFF_IGN;
+}
+
+static int
+roff_EN(ROFF_ARGS)
+{
+ if (r->eqn != NULL) {
+ eqn_parse(r->eqn);
+ r->eqn = NULL;
+ } else
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "EN");
+ if (buf->buf[pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
+ "EN %s", buf->buf + pos);
+ return ROFF_IGN;
+}
+
+static int
+roff_TS(ROFF_ARGS)
+{
+ if (r->tbl != NULL) {
+ mandoc_msg(MANDOCERR_BLK_BROKEN, ln, ppos, "TS breaks TS");
+ tbl_end(r->tbl, 0);
+ }
+ r->man->flags |= ROFF_NONOFILL;
+ r->tbl = tbl_alloc(ppos, ln, r->last_tbl);
+ if (r->last_tbl == NULL)
+ r->first_tbl = r->tbl;
+ r->last_tbl = r->tbl;
+ return ROFF_IGN;
+}
+
+static int
+roff_noarg(ROFF_ARGS)
+{
+ if (r->man->flags & (MAN_BLINE | MAN_ELINE))
+ man_breakscope(r->man, tok);
+ if (tok == ROFF_brp)
+ tok = ROFF_br;
+ roff_elem_alloc(r->man, ln, ppos, tok);
+ if (buf->buf[pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
+ "%s %s", roff_name[tok], buf->buf + pos);
+ if (tok == ROFF_nf)
+ r->man->flags |= ROFF_NOFILL;
+ else if (tok == ROFF_fi)
+ r->man->flags &= ~ROFF_NOFILL;
+ r->man->last->flags |= NODE_LINE | NODE_VALID | NODE_ENDED;
+ r->man->next = ROFF_NEXT_SIBLING;
+ return ROFF_IGN;
+}
+
+static int
+roff_onearg(ROFF_ARGS)
+{
+ struct roff_node *n;
+ char *cp;
+ int npos;
+
+ if (r->man->flags & (MAN_BLINE | MAN_ELINE) &&
+ (tok == ROFF_ce || tok == ROFF_rj || tok == ROFF_sp ||
+ tok == ROFF_ti))
+ man_breakscope(r->man, tok);
+
+ if (roffce_node != NULL && (tok == ROFF_ce || tok == ROFF_rj)) {
+ r->man->last = roffce_node;
+ r->man->next = ROFF_NEXT_SIBLING;
+ }
+
+ roff_elem_alloc(r->man, ln, ppos, tok);
+ n = r->man->last;
+
+ cp = buf->buf + pos;
+ if (*cp != '\0') {
+ while (*cp != '\0' && *cp != ' ')
+ cp++;
+ while (*cp == ' ')
+ *cp++ = '\0';
+ if (*cp != '\0')
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ ln, (int)(cp - buf->buf),
+ "%s ... %s", roff_name[tok], cp);
+ roff_word_alloc(r->man, ln, pos, buf->buf + pos);
+ }
+
+ if (tok == ROFF_ce || tok == ROFF_rj) {
+ if (r->man->last->type == ROFFT_ELEM) {
+ roff_word_alloc(r->man, ln, pos, "1");
+ r->man->last->flags |= NODE_NOSRC;
+ }
+ npos = 0;
+ if (roff_evalnum(r, ln, r->man->last->string, &npos,
+ &roffce_lines, 0) == 0) {
+ mandoc_msg(MANDOCERR_CE_NONUM,
+ ln, pos, "ce %s", buf->buf + pos);
+ roffce_lines = 1;
+ }
+ if (roffce_lines < 1) {
+ r->man->last = r->man->last->parent;
+ roffce_node = NULL;
+ roffce_lines = 0;
+ } else
+ roffce_node = r->man->last->parent;
+ } else {
+ n->flags |= NODE_VALID | NODE_ENDED;
+ r->man->last = n;
+ }
+ n->flags |= NODE_LINE;
+ r->man->next = ROFF_NEXT_SIBLING;
+ return ROFF_IGN;
+}
+
+static int
+roff_manyarg(ROFF_ARGS)
+{
+ struct roff_node *n;
+ char *sp, *ep;
+
+ roff_elem_alloc(r->man, ln, ppos, tok);
+ n = r->man->last;
+
+ for (sp = ep = buf->buf + pos; *sp != '\0'; sp = ep) {
+ while (*ep != '\0' && *ep != ' ')
+ ep++;
+ while (*ep == ' ')
+ *ep++ = '\0';
+ roff_word_alloc(r->man, ln, sp - buf->buf, sp);
+ }
+
+ n->flags |= NODE_LINE | NODE_VALID | NODE_ENDED;
+ r->man->last = n;
+ r->man->next = ROFF_NEXT_SIBLING;
+ return ROFF_IGN;
+}
+
+static int
+roff_als(ROFF_ARGS)
+{
+ char *oldn, *newn, *end, *value;
+ size_t oldsz, newsz, valsz;
+
+ newn = oldn = buf->buf + pos;
+ if (*newn == '\0')
+ return ROFF_IGN;
+
+ newsz = roff_getname(r, &oldn, ln, pos);
+ if (newn[newsz] == '\\' || newn[newsz] == '\t' || *oldn == '\0')
+ return ROFF_IGN;
+
+ end = oldn;
+ oldsz = roff_getname(r, &end, ln, oldn - buf->buf);
+ if (oldsz == 0)
+ return ROFF_IGN;
+
+ valsz = mandoc_asprintf(&value, ".%.*s \\$@\\\"\n",
+ (int)oldsz, oldn);
+ roff_setstrn(&r->strtab, newn, newsz, value, valsz, 0);
+ roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
+ free(value);
+ return ROFF_IGN;
+}
+
+/*
+ * The .break request only makes sense inside conditionals,
+ * and that case is already handled in roff_cond_sub().
+ */
+static int
+roff_break(ROFF_ARGS)
+{
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, pos, "break");
+ return ROFF_IGN;
+}
+
+static int
+roff_cc(ROFF_ARGS)
+{
+ const char *p;
+
+ p = buf->buf + pos;
+
+ if (*p == '\0' || (r->control = *p++) == '.')
+ r->control = '\0';
+
+ if (*p != '\0')
+ mandoc_msg(MANDOCERR_ARG_EXCESS,
+ ln, p - buf->buf, "cc ... %s", p);
+
+ return ROFF_IGN;
+}
+
+static int
+roff_char(ROFF_ARGS)
+{
+ const char *p, *kp, *vp;
+ size_t ksz, vsz;
+ int font;
+
+ /* Parse the character to be replaced. */
+
+ kp = buf->buf + pos;
+ p = kp + 1;
+ if (*kp == '\0' || (*kp == '\\' &&
+ mandoc_escape(&p, NULL, NULL) != ESCAPE_SPECIAL) ||
+ (*p != ' ' && *p != '\0')) {
+ mandoc_msg(MANDOCERR_CHAR_ARG, ln, pos, "char %s", kp);
+ return ROFF_IGN;
+ }
+ ksz = p - kp;
+ while (*p == ' ')
+ p++;
+
+ /*
+ * If the replacement string contains a font escape sequence,
+ * we have to restore the font at the end.
+ */
+
+ vp = p;
+ vsz = strlen(p);
+ font = 0;
+ while (*p != '\0') {
+ if (*p++ != '\\')
+ continue;
+ switch (mandoc_escape(&p, NULL, NULL)) {
+ case ESCAPE_FONT:
+ case ESCAPE_FONTROMAN:
+ case ESCAPE_FONTITALIC:
+ case ESCAPE_FONTBOLD:
+ case ESCAPE_FONTBI:
+ case ESCAPE_FONTCW:
+ case ESCAPE_FONTPREV:
+ font++;
+ break;
+ default:
+ break;
+ }
+ }
+ if (font > 1)
+ mandoc_msg(MANDOCERR_CHAR_FONT,
+ ln, (int)(vp - buf->buf), "%s", vp);
+
+ /*
+ * Approximate the effect of .char using the .tr tables.
+ * XXX In groff, .char and .tr interact differently.
+ */
+
+ if (ksz == 1) {
+ if (r->xtab == NULL)
+ r->xtab = mandoc_calloc(128, sizeof(*r->xtab));
+ assert((unsigned int)*kp < 128);
+ free(r->xtab[(int)*kp].p);
+ r->xtab[(int)*kp].sz = mandoc_asprintf(&r->xtab[(int)*kp].p,
+ "%s%s", vp, font ? "\fP" : "");
+ } else {
+ roff_setstrn(&r->xmbtab, kp, ksz, vp, vsz, 0);
+ if (font)
+ roff_setstrn(&r->xmbtab, kp, ksz, "\\fP", 3, 1);
+ }
+ return ROFF_IGN;
+}
+
+static int
+roff_ec(ROFF_ARGS)
+{
+ const char *p;
+
+ p = buf->buf + pos;
+ if (*p == '\0')
+ r->escape = '\\';
+ else {
+ r->escape = *p;
+ if (*++p != '\0')
+ mandoc_msg(MANDOCERR_ARG_EXCESS, ln,
+ (int)(p - buf->buf), "ec ... %s", p);
+ }
+ return ROFF_IGN;
+}
+
+static int
+roff_eo(ROFF_ARGS)
+{
+ r->escape = '\0';
+ if (buf->buf[pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_SKIP,
+ ln, pos, "eo %s", buf->buf + pos);
+ return ROFF_IGN;
+}
+
+static int
+roff_nop(ROFF_ARGS)
+{
+ while (buf->buf[pos] == ' ')
+ pos++;
+ *offs = pos;
+ return ROFF_RERUN;
+}
+
+static int
+roff_tr(ROFF_ARGS)
+{
+ const char *p, *first, *second;
+ size_t fsz, ssz;
+ enum mandoc_esc esc;
+
+ p = buf->buf + pos;
+
+ if (*p == '\0') {
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ln, ppos, "tr");
+ return ROFF_IGN;
+ }
+
+ while (*p != '\0') {
+ fsz = ssz = 1;
+
+ first = p++;
+ if (*first == '\\') {
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (esc == ESCAPE_ERROR) {
+ mandoc_msg(MANDOCERR_ESC_BAD, ln,
+ (int)(p - buf->buf), "%s", first);
+ return ROFF_IGN;
+ }
+ fsz = (size_t)(p - first);
+ }
+
+ second = p++;
+ if (*second == '\\') {
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (esc == ESCAPE_ERROR) {
+ mandoc_msg(MANDOCERR_ESC_BAD, ln,
+ (int)(p - buf->buf), "%s", second);
+ return ROFF_IGN;
+ }
+ ssz = (size_t)(p - second);
+ } else if (*second == '\0') {
+ mandoc_msg(MANDOCERR_TR_ODD, ln,
+ (int)(first - buf->buf), "tr %s", first);
+ second = " ";
+ p--;
+ }
+
+ if (fsz > 1) {
+ roff_setstrn(&r->xmbtab, first, fsz,
+ second, ssz, 0);
+ continue;
+ }
+
+ if (r->xtab == NULL)
+ 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;
+}
+
+/*
+ * Implementation of the .return request.
+ * There is no need to call roff_userret() from here.
+ * The read module will call that after rewinding the reader stack
+ * to the place from where the current macro was called.
+ */
+static int
+roff_return(ROFF_ARGS)
+{
+ if (r->mstackpos >= 0)
+ return ROFF_IGN | ROFF_USERRET;
+
+ mandoc_msg(MANDOCERR_REQ_NOMAC, ln, ppos, "return");
+ return ROFF_IGN;
+}
+
+static int
+roff_rn(ROFF_ARGS)
+{
+ const char *value;
+ char *oldn, *newn, *end;
+ size_t oldsz, newsz;
+ int deftype;
+
+ oldn = newn = buf->buf + pos;
+ if (*oldn == '\0')
+ return ROFF_IGN;
+
+ oldsz = roff_getname(r, &newn, ln, pos);
+ if (oldn[oldsz] == '\\' || oldn[oldsz] == '\t' || *newn == '\0')
+ return ROFF_IGN;
+
+ end = newn;
+ newsz = roff_getname(r, &end, ln, newn - buf->buf);
+ if (newsz == 0)
+ return ROFF_IGN;
+
+ deftype = ROFFDEF_ANY;
+ value = roff_getstrn(r, oldn, oldsz, &deftype);
+ switch (deftype) {
+ case ROFFDEF_USER:
+ roff_setstrn(&r->strtab, newn, newsz, value, strlen(value), 0);
+ roff_setstrn(&r->strtab, oldn, oldsz, NULL, 0, 0);
+ roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
+ break;
+ case ROFFDEF_PRE:
+ roff_setstrn(&r->strtab, newn, newsz, value, strlen(value), 0);
+ roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
+ break;
+ case ROFFDEF_REN:
+ roff_setstrn(&r->rentab, newn, newsz, value, strlen(value), 0);
+ roff_setstrn(&r->rentab, oldn, oldsz, NULL, 0, 0);
+ roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0);
+ break;
+ case ROFFDEF_STD:
+ roff_setstrn(&r->rentab, newn, newsz, oldn, oldsz, 0);
+ roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0);
+ break;
+ default:
+ roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0);
+ roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
+ break;
+ }
+ return ROFF_IGN;
+}
+
+static int
+roff_shift(ROFF_ARGS)
+{
+ struct mctx *ctx;
+ int levels, i;
+
+ levels = 1;
+ if (buf->buf[pos] != '\0' &&
+ roff_evalnum(r, ln, buf->buf, &pos, &levels, 0) == 0) {
+ mandoc_msg(MANDOCERR_CE_NONUM,
+ ln, pos, "shift %s", buf->buf + pos);
+ levels = 1;
+ }
+ if (r->mstackpos < 0) {
+ mandoc_msg(MANDOCERR_REQ_NOMAC, ln, ppos, "shift");
+ return ROFF_IGN;
+ }
+ ctx = r->mstack + r->mstackpos;
+ if (levels > ctx->argc) {
+ mandoc_msg(MANDOCERR_SHIFT,
+ ln, pos, "%d, but max is %d", levels, ctx->argc);
+ levels = ctx->argc;
+ }
+ if (levels == 0)
+ return ROFF_IGN;
+ for (i = 0; i < levels; i++)
+ free(ctx->argv[i]);
+ ctx->argc -= levels;
+ for (i = 0; i < ctx->argc; i++)
+ ctx->argv[i] = ctx->argv[i + levels];
+ return ROFF_IGN;
+}
+
+static int
+roff_so(ROFF_ARGS)
+{
+ char *name, *cp;
+
+ name = buf->buf + pos;
+ mandoc_msg(MANDOCERR_SO, 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_msg(MANDOCERR_SO_PATH, ln, ppos, ".so %s", name);
+ buf->sz = mandoc_asprintf(&cp,
+ ".sp\nSee the file %s.\n.sp", name) + 1;
+ free(buf->buf);
+ buf->buf = cp;
+ *offs = 0;
+ return ROFF_REPARSE;
+ }
+
+ *offs = pos;
+ return ROFF_SO;
+}
+
+/* --- user defined strings and macros ------------------------------------ */
+
+static int
+roff_userdef(ROFF_ARGS)
+{
+ struct mctx *ctx;
+ char *arg, *ap, *dst, *src;
+ size_t sz;
+
+ /* If the macro is empty, ignore it altogether. */
+
+ if (*r->current_string == '\0')
+ return ROFF_IGN;
+
+ /* Initialize a new macro stack context. */
+
+ if (++r->mstackpos == r->mstacksz) {
+ r->mstack = mandoc_recallocarray(r->mstack,
+ r->mstacksz, r->mstacksz + 8, sizeof(*r->mstack));
+ r->mstacksz += 8;
+ }
+ ctx = r->mstack + r->mstackpos;
+ ctx->argsz = 0;
+ ctx->argc = 0;
+ ctx->argv = NULL;
+
+ /*
+ * Collect pointers to macro argument strings,
+ * NUL-terminating them and escaping quotes.
+ */
+
+ src = buf->buf + pos;
+ while (*src != '\0') {
+ if (ctx->argc == ctx->argsz) {
+ ctx->argsz += 8;
+ ctx->argv = mandoc_reallocarray(ctx->argv,
+ ctx->argsz, sizeof(*ctx->argv));
+ }
+ arg = roff_getarg(r, &src, ln, &pos);
+ sz = 1; /* For the terminating NUL. */
+ for (ap = arg; *ap != '\0'; ap++)
+ sz += *ap == '"' ? 4 : 1;
+ ctx->argv[ctx->argc++] = dst = mandoc_malloc(sz);
+ for (ap = arg; *ap != '\0'; ap++) {
+ if (*ap == '"') {
+ memcpy(dst, "\\(dq", 4);
+ dst += 4;
+ } else
+ *dst++ = *ap;
+ }
+ *dst = '\0';
+ free(arg);
+ }
+
+ /* Replace the macro invocation by the macro definition. */
+
+ free(buf->buf);
+ buf->buf = mandoc_strdup(r->current_string);
+ buf->sz = strlen(buf->buf) + 1;
+ *offs = 0;
+
+ return buf->buf[buf->sz - 2] == '\n' ?
+ ROFF_REPARSE | ROFF_USERCALL : ROFF_IGN | ROFF_APPEND;
+}
+
+/*
+ * Calling a high-level macro that was renamed with .rn.
+ * r->current_string has already been set up by roff_parse().
+ */
+static int
+roff_renamed(ROFF_ARGS)
+{
+ char *nbuf;
+
+ buf->sz = mandoc_asprintf(&nbuf, ".%s%s%s", r->current_string,
+ buf->buf[pos] == '\0' ? "" : " ", buf->buf + pos) + 1;
+ free(buf->buf);
+ buf->buf = nbuf;
+ *offs = 0;
+ return ROFF_CONT;
+}
+
+/*
+ * Measure the length in bytes of the roff identifier at *cpp
+ * and advance the pointer to the next word.
+ */
+static size_t
+roff_getname(struct roff *r, char **cpp, int ln, int pos)
+{
+ char *name, *cp;
+ size_t namesz;
+
+ name = *cpp;
+ if (*name == '\0')
+ return 0;
+
+ /* Advance cp to the byte after the end of the name. */
+
+ for (cp = name; 1; cp++) {
+ namesz = cp - name;
+ if (*cp == '\0')
+ break;
+ if (*cp == ' ' || *cp == '\t') {
+ cp++;
+ break;
+ }
+ if (*cp != '\\')
+ continue;
+ if (cp[1] == '{' || cp[1] == '}')
+ break;
+ if (*++cp == '\\')
+ continue;
+ mandoc_msg(MANDOCERR_NAMESC, 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)
+{
+ size_t namesz;
+
+ namesz = strlen(name);
+ roff_setstrn(&r->strtab, name, namesz, string,
+ string ? strlen(string) : 0, append);
+ roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
+}
+
+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(struct roff *r, const char *name, size_t len,
+ int *deftype)
+{
+ const struct roffkv *n;
+ int found, i;
+ enum roff_tok tok;
+
+ found = 0;
+ for (n = r->strtab; n != NULL; n = n->next) {
+ if (strncmp(name, n->key.p, len) != 0 ||
+ n->key.p[len] != '\0' || n->val.p == NULL)
+ continue;
+ if (*deftype & ROFFDEF_USER) {
+ *deftype = ROFFDEF_USER;
+ return n->val.p;
+ } else {
+ found = 1;
+ break;
+ }
+ }
+ for (n = r->rentab; n != NULL; n = n->next) {
+ if (strncmp(name, n->key.p, len) != 0 ||
+ n->key.p[len] != '\0' || n->val.p == NULL)
+ continue;
+ if (*deftype & ROFFDEF_REN) {
+ *deftype = ROFFDEF_REN;
+ return n->val.p;
+ } else {
+ found = 1;
+ break;
+ }
+ }
+ for (i = 0; i < PREDEFS_MAX; i++) {
+ if (strncmp(name, predefs[i].name, len) != 0 ||
+ predefs[i].name[len] != '\0')
+ continue;
+ if (*deftype & ROFFDEF_PRE) {
+ *deftype = ROFFDEF_PRE;
+ return predefs[i].str;
+ } else {
+ found = 1;
+ break;
+ }
+ }
+ if (r->man->meta.macroset != MACROSET_MAN) {
+ for (tok = MDOC_Dd; tok < MDOC_MAX; tok++) {
+ if (strncmp(name, roff_name[tok], len) != 0 ||
+ roff_name[tok][len] != '\0')
+ continue;
+ if (*deftype & ROFFDEF_STD) {
+ *deftype = ROFFDEF_STD;
+ return NULL;
+ } else {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (r->man->meta.macroset != MACROSET_MDOC) {
+ for (tok = MAN_TH; tok < MAN_MAX; tok++) {
+ if (strncmp(name, roff_name[tok], len) != 0 ||
+ roff_name[tok][len] != '\0')
+ continue;
+ if (*deftype & ROFFDEF_STD) {
+ *deftype = ROFFDEF_STD;
+ return NULL;
+ } else {
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ if (found == 0 && *deftype != ROFFDEF_ANY) {
+ if (*deftype & ROFFDEF_REN) {
+ /*
+ * This might still be a request,
+ * so do not treat it as undefined yet.
+ */
+ *deftype = ROFFDEF_UNDEF;
+ return NULL;
+ }
+
+ /* Using an undefined string defines it to be empty. */
+
+ roff_setstrn(&r->strtab, name, len, "", 0, 0);
+ roff_setstrn(&r->rentab, name, len, NULL, 0, 0);
+ }
+
+ *deftype = 0;
+ 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);
+ }
+}
+
+/* --- accessors and utility functions ------------------------------------ */
+
+/*
+ * 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) {
+ assert((unsigned int)*p < 128);
+ if ('\\' != *p && r->xtab && r->xtab[(unsigned 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_expand() 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;
+}
+
+int
+roff_getformat(const struct roff *r)
+{
+
+ return r->format;
+}
+
+/*
+ * 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 (r->control != '\0' && cp[pos] == r->control)
+ pos++;
+ else if (r->control != '\0')
+ 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/usr.bin/mandoc/roff.h b/usr.bin/mandoc/roff.h
new file mode 100644
index 0000000..aefeafc
--- /dev/null
+++ b/usr.bin/mandoc/roff.h
@@ -0,0 +1,561 @@
+/* $OpenBSD: roff.h,v 1.56 2020/04/08 11:54:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * 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 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.
+ *
+ * Common data types for all syntax trees and related functions.
+ */
+
+struct ohash;
+struct mdoc_arg;
+union mdoc_data;
+struct tbl_span;
+struct eqn_box;
+
+enum roff_macroset {
+ MACROSET_NONE = 0,
+ MACROSET_MDOC,
+ MACROSET_MAN
+};
+
+enum roff_sec {
+ SEC_NONE = 0,
+ SEC_NAME,
+ SEC_LIBRARY,
+ SEC_SYNOPSIS,
+ SEC_DESCRIPTION,
+ SEC_CONTEXT,
+ SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */
+ SEC_RETURN_VALUES,
+ SEC_ENVIRONMENT,
+ SEC_FILES,
+ SEC_EXIT_STATUS,
+ SEC_EXAMPLES,
+ SEC_DIAGNOSTICS,
+ SEC_COMPATIBILITY,
+ SEC_ERRORS,
+ SEC_SEE_ALSO,
+ SEC_STANDARDS,
+ SEC_HISTORY,
+ SEC_AUTHORS,
+ SEC_CAVEATS,
+ SEC_BUGS,
+ SEC_SECURITY,
+ SEC_CUSTOM,
+ SEC__MAX
+};
+
+enum roff_type {
+ ROFFT_ROOT,
+ ROFFT_BLOCK,
+ ROFFT_HEAD,
+ ROFFT_BODY,
+ ROFFT_TAIL,
+ ROFFT_ELEM,
+ ROFFT_TEXT,
+ ROFFT_COMMENT,
+ ROFFT_TBL,
+ ROFFT_EQN
+};
+
+enum roff_tok {
+ ROFF_br = 0,
+ ROFF_ce,
+ ROFF_fi,
+ ROFF_ft,
+ ROFF_ll,
+ ROFF_mc,
+ ROFF_nf,
+ ROFF_po,
+ ROFF_rj,
+ ROFF_sp,
+ ROFF_ta,
+ ROFF_ti,
+ ROFF_MAX,
+ ROFF_ab,
+ ROFF_ad,
+ ROFF_af,
+ ROFF_aln,
+ ROFF_als,
+ ROFF_am,
+ ROFF_am1,
+ ROFF_ami,
+ ROFF_ami1,
+ ROFF_as,
+ ROFF_as1,
+ ROFF_asciify,
+ ROFF_backtrace,
+ ROFF_bd,
+ ROFF_bleedat,
+ ROFF_blm,
+ ROFF_box,
+ ROFF_boxa,
+ ROFF_bp,
+ ROFF_BP,
+ ROFF_break,
+ ROFF_breakchar,
+ ROFF_brnl,
+ ROFF_brp,
+ ROFF_brpnl,
+ ROFF_c2,
+ ROFF_cc,
+ ROFF_cf,
+ ROFF_cflags,
+ ROFF_ch,
+ ROFF_char,
+ ROFF_chop,
+ ROFF_class,
+ ROFF_close,
+ ROFF_CL,
+ ROFF_color,
+ ROFF_composite,
+ ROFF_continue,
+ ROFF_cp,
+ ROFF_cropat,
+ ROFF_cs,
+ ROFF_cu,
+ ROFF_da,
+ ROFF_dch,
+ ROFF_Dd,
+ ROFF_de,
+ ROFF_de1,
+ ROFF_defcolor,
+ ROFF_dei,
+ ROFF_dei1,
+ ROFF_device,
+ ROFF_devicem,
+ ROFF_di,
+ ROFF_do,
+ ROFF_ds,
+ ROFF_ds1,
+ ROFF_dwh,
+ ROFF_dt,
+ ROFF_ec,
+ ROFF_ecr,
+ ROFF_ecs,
+ ROFF_el,
+ ROFF_em,
+ ROFF_EN,
+ ROFF_eo,
+ ROFF_EP,
+ ROFF_EQ,
+ ROFF_errprint,
+ ROFF_ev,
+ ROFF_evc,
+ ROFF_ex,
+ ROFF_fallback,
+ ROFF_fam,
+ ROFF_fc,
+ ROFF_fchar,
+ ROFF_fcolor,
+ ROFF_fdeferlig,
+ ROFF_feature,
+ ROFF_fkern,
+ ROFF_fl,
+ ROFF_flig,
+ ROFF_fp,
+ ROFF_fps,
+ ROFF_fschar,
+ ROFF_fspacewidth,
+ ROFF_fspecial,
+ ROFF_ftr,
+ ROFF_fzoom,
+ ROFF_gcolor,
+ ROFF_hc,
+ ROFF_hcode,
+ ROFF_hidechar,
+ ROFF_hla,
+ ROFF_hlm,
+ ROFF_hpf,
+ ROFF_hpfa,
+ ROFF_hpfcode,
+ ROFF_hw,
+ ROFF_hy,
+ ROFF_hylang,
+ ROFF_hylen,
+ ROFF_hym,
+ ROFF_hypp,
+ ROFF_hys,
+ ROFF_ie,
+ ROFF_if,
+ ROFF_ig,
+ /* MAN_in; ignored in mdoc(7) */
+ ROFF_index,
+ ROFF_it,
+ ROFF_itc,
+ ROFF_IX,
+ ROFF_kern,
+ ROFF_kernafter,
+ ROFF_kernbefore,
+ ROFF_kernpair,
+ ROFF_lc,
+ ROFF_lc_ctype,
+ ROFF_lds,
+ ROFF_length,
+ ROFF_letadj,
+ ROFF_lf,
+ ROFF_lg,
+ ROFF_lhang,
+ ROFF_linetabs,
+ ROFF_lnr,
+ ROFF_lnrf,
+ ROFF_lpfx,
+ ROFF_ls,
+ ROFF_lsm,
+ ROFF_lt,
+ ROFF_mediasize,
+ ROFF_minss,
+ ROFF_mk,
+ ROFF_mso,
+ ROFF_na,
+ ROFF_ne,
+ ROFF_nh,
+ ROFF_nhychar,
+ ROFF_nm,
+ ROFF_nn,
+ ROFF_nop,
+ ROFF_nr,
+ ROFF_nrf,
+ ROFF_nroff,
+ ROFF_ns,
+ ROFF_nx,
+ ROFF_open,
+ ROFF_opena,
+ ROFF_os,
+ ROFF_output,
+ ROFF_padj,
+ ROFF_papersize,
+ ROFF_pc,
+ ROFF_pev,
+ ROFF_pi,
+ ROFF_PI,
+ ROFF_pl,
+ ROFF_pm,
+ ROFF_pn,
+ ROFF_pnr,
+ ROFF_ps,
+ ROFF_psbb,
+ ROFF_pshape,
+ ROFF_pso,
+ ROFF_ptr,
+ ROFF_pvs,
+ ROFF_rchar,
+ ROFF_rd,
+ ROFF_recursionlimit,
+ ROFF_return,
+ ROFF_rfschar,
+ ROFF_rhang,
+ ROFF_rm,
+ ROFF_rn,
+ ROFF_rnn,
+ ROFF_rr,
+ ROFF_rs,
+ ROFF_rt,
+ ROFF_schar,
+ ROFF_sentchar,
+ ROFF_shc,
+ ROFF_shift,
+ ROFF_sizes,
+ ROFF_so,
+ ROFF_spacewidth,
+ ROFF_special,
+ ROFF_spreadwarn,
+ ROFF_ss,
+ ROFF_sty,
+ ROFF_substring,
+ ROFF_sv,
+ ROFF_sy,
+ ROFF_T_,
+ ROFF_tc,
+ ROFF_TE,
+ ROFF_TH,
+ ROFF_tkf,
+ ROFF_tl,
+ ROFF_tm,
+ ROFF_tm1,
+ ROFF_tmc,
+ ROFF_tr,
+ ROFF_track,
+ ROFF_transchar,
+ ROFF_trf,
+ ROFF_trimat,
+ ROFF_trin,
+ ROFF_trnt,
+ ROFF_troff,
+ ROFF_TS,
+ ROFF_uf,
+ ROFF_ul,
+ ROFF_unformat,
+ ROFF_unwatch,
+ ROFF_unwatchn,
+ ROFF_vpt,
+ ROFF_vs,
+ ROFF_warn,
+ ROFF_warnscale,
+ ROFF_watch,
+ ROFF_watchlength,
+ ROFF_watchn,
+ ROFF_wh,
+ ROFF_while,
+ ROFF_write,
+ ROFF_writec,
+ ROFF_writem,
+ ROFF_xflag,
+ ROFF_cblock,
+ ROFF_RENAMED,
+ ROFF_USERDEF,
+ TOKEN_NONE,
+ 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_Ap,
+ 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__U,
+ MDOC_Ta,
+ MDOC_Tg,
+ MDOC_MAX,
+ MAN_TH,
+ MAN_SH,
+ MAN_SS,
+ MAN_TP,
+ MAN_TQ,
+ 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_RE,
+ MAN_RS,
+ MAN_DT,
+ MAN_UC,
+ MAN_PD,
+ MAN_AT,
+ MAN_in,
+ MAN_SY,
+ MAN_YS,
+ MAN_OP,
+ MAN_EX,
+ MAN_EE,
+ MAN_UR,
+ MAN_UE,
+ MAN_MT,
+ MAN_ME,
+ MAN_MAX
+};
+
+/*
+ * Indicates that a BODY's formatting has ended, but
+ * the scope is still open. Used for badly nested blocks.
+ */
+enum mdoc_endbody {
+ ENDBODY_NOT = 0,
+ ENDBODY_SPACE /* Is broken: append a space. */
+};
+
+enum mandoc_os {
+ MANDOC_OS_OTHER = 0,
+ MANDOC_OS_NETBSD,
+ MANDOC_OS_OPENBSD
+};
+
+struct roff_node {
+ struct roff_node *parent; /* Parent AST node. */
+ struct roff_node *child; /* First child AST node. */
+ struct roff_node *last; /* Last child AST node. */
+ struct roff_node *next; /* Sibling AST node. */
+ struct roff_node *prev; /* Prior sibling AST node. */
+ struct roff_node *head; /* BLOCK */
+ struct roff_node *body; /* BLOCK/ENDBODY */
+ struct roff_node *tail; /* BLOCK */
+ struct mdoc_arg *args; /* BLOCK/ELEM */
+ union mdoc_data *norm; /* Normalized arguments. */
+ char *string; /* TEXT */
+ char *tag; /* For less(1) :t and HTML id=. */
+ struct tbl_span *span; /* TBL */
+ struct eqn_box *eqn; /* EQN */
+ int line; /* Input file line number. */
+ int pos; /* Input file column number. */
+ int flags;
+#define NODE_VALID (1 << 0) /* Has been validated. */
+#define NODE_ENDED (1 << 1) /* Gone past body end mark. */
+#define NODE_BROKEN (1 << 2) /* Must validate parent when ending. */
+#define NODE_LINE (1 << 3) /* First macro/text on line. */
+#define NODE_DELIMO (1 << 4)
+#define NODE_DELIMC (1 << 5)
+#define NODE_EOS (1 << 6) /* At sentence boundary. */
+#define NODE_SYNPRETTY (1 << 7) /* SYNOPSIS-style formatting. */
+#define NODE_NOFILL (1 << 8) /* Fill mode switched off. */
+#define NODE_NOSRC (1 << 9) /* Generated node, not in input file. */
+#define NODE_NOPRT (1 << 10) /* Shall not print anything. */
+#define NODE_ID (1 << 11) /* Target for deep linking. */
+#define NODE_HREF (1 << 12) /* Link to another place in this page. */
+ int prev_font; /* Before entering this node. */
+ int aux; /* Decoded node data, type-dependent. */
+ enum roff_tok tok; /* Request or macro ID. */
+ enum roff_type type; /* AST node type. */
+ enum roff_sec sec; /* Current named section. */
+ enum mdoc_endbody end; /* BODY */
+};
+
+struct roff_meta {
+ struct roff_node *first; /* The first node parsed. */
+ char *msec; /* Manual section, usually a digit. */
+ char *vol; /* Manual volume title. */
+ char *os; /* Operating system. */
+ char *arch; /* Machine architecture. */
+ char *title; /* Manual title, usually CAPS. */
+ char *name; /* Leading manual name. */
+ char *date; /* Normalized date. */
+ char *sodest; /* .so target file name or NULL. */
+ int hasbody; /* Document is not empty. */
+ int rcsids; /* Bits indexed by enum mandoc_os. */
+ enum mandoc_os os_e; /* Operating system. */
+ enum roff_macroset macroset; /* Kind of high-level macros used. */
+};
+
+extern const char *const *roff_name;
+
+
+int arch_valid(const char *, enum mandoc_os);
+void deroff(char **, const struct roff_node *);
+struct roff_node *roff_node_child(struct roff_node *);
+struct roff_node *roff_node_next(struct roff_node *);
+struct roff_node *roff_node_prev(struct roff_node *);
+int roff_node_transparent(struct roff_node *);
+int roff_tok_transparent(enum roff_tok);
diff --git a/usr.bin/mandoc/roff_html.c b/usr.bin/mandoc/roff_html.c
new file mode 100644
index 0000000..ed9639a
--- /dev/null
+++ b/usr.bin/mandoc/roff_html.c
@@ -0,0 +1,117 @@
+/* $OpenBSD: roff_html.c,v 1.20 2019/04/30 15:52:42 schwarze Exp $ */
+/*
+ * Copyright (c) 2010 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2017, 2018, 2019 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "out.h"
+#include "html.h"
+
+#define ROFF_HTML_ARGS struct html *h, const struct roff_node *n
+
+typedef void (*roff_html_pre_fp)(ROFF_HTML_ARGS);
+
+static void roff_html_pre_br(ROFF_HTML_ARGS);
+static void roff_html_pre_ce(ROFF_HTML_ARGS);
+static void roff_html_pre_fi(ROFF_HTML_ARGS);
+static void roff_html_pre_ft(ROFF_HTML_ARGS);
+static void roff_html_pre_nf(ROFF_HTML_ARGS);
+static void roff_html_pre_sp(ROFF_HTML_ARGS);
+
+static const roff_html_pre_fp roff_html_pre_acts[ROFF_MAX] = {
+ roff_html_pre_br, /* br */
+ roff_html_pre_ce, /* ce */
+ roff_html_pre_fi, /* fi */
+ roff_html_pre_ft, /* ft */
+ NULL, /* ll */
+ NULL, /* mc */
+ roff_html_pre_nf, /* nf */
+ NULL, /* po */
+ roff_html_pre_ce, /* rj */
+ roff_html_pre_sp, /* sp */
+ NULL, /* ta */
+ NULL, /* ti */
+};
+
+
+void
+roff_html_pre(struct html *h, const struct roff_node *n)
+{
+ assert(n->tok < ROFF_MAX);
+ if (roff_html_pre_acts[n->tok] != NULL)
+ (*roff_html_pre_acts[n->tok])(h, n);
+}
+
+static void
+roff_html_pre_br(ROFF_HTML_ARGS)
+{
+ print_otag(h, TAG_BR, "");
+}
+
+static void
+roff_html_pre_ce(ROFF_HTML_ARGS)
+{
+ for (n = n->child->next; n != NULL; n = n->next) {
+ if (n->type == ROFFT_TEXT) {
+ if (n->flags & NODE_LINE)
+ roff_html_pre_br(h, n);
+ print_text(h, n->string);
+ } else
+ roff_html_pre(h, n);
+ }
+ roff_html_pre_br(h, n);
+}
+
+static void
+roff_html_pre_fi(ROFF_HTML_ARGS)
+{
+ if (html_fillmode(h, TOKEN_NONE) == ROFF_fi)
+ print_otag(h, TAG_BR, "");
+}
+
+static void
+roff_html_pre_ft(ROFF_HTML_ARGS)
+{
+ const char *cp;
+
+ cp = n->child->string;
+ html_setfont(h, mandoc_font(cp, (int)strlen(cp)));
+}
+
+static void
+roff_html_pre_nf(ROFF_HTML_ARGS)
+{
+ if (html_fillmode(h, TOKEN_NONE) == ROFF_nf)
+ print_otag(h, TAG_BR, "");
+}
+
+static void
+roff_html_pre_sp(ROFF_HTML_ARGS)
+{
+ if (html_fillmode(h, TOKEN_NONE) == ROFF_nf) {
+ h->col++;
+ print_endline(h);
+ } else {
+ html_close_paragraph(h);
+ print_otag(h, TAG_P, "c", "Pp");
+ }
+}
diff --git a/usr.bin/mandoc/roff_int.h b/usr.bin/mandoc/roff_int.h
new file mode 100644
index 0000000..779f50f
--- /dev/null
+++ b/usr.bin/mandoc/roff_int.h
@@ -0,0 +1,94 @@
+/* $OpenBSD: roff_int.h,v 1.17 2020/04/24 11:58:02 schwarze Exp $ */
+/*
+ * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * 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 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.
+ *
+ * Parser internals shared by multiple parsers.
+ */
+
+struct ohash;
+struct roff_node;
+struct roff_meta;
+struct roff;
+struct mdoc_arg;
+
+enum roff_next {
+ ROFF_NEXT_SIBLING = 0,
+ ROFF_NEXT_CHILD
+};
+
+struct roff_man {
+ struct roff_meta meta; /* Public parse results. */
+ struct roff *roff; /* Roff parser state data. */
+ struct ohash *mdocmac; /* Mdoc macro lookup table. */
+ struct ohash *manmac; /* Man macro lookup table. */
+ const char *os_s; /* Default operating system. */
+ struct roff_node *last; /* The last node parsed. */
+ struct roff_node *last_es; /* The most recent Es node. */
+ int quick; /* Abort parse early. */
+ int flags; /* Parse flags. */
+#define ROFF_NOFILL (1 << 1) /* Fill mode switched off. */
+#define MDOC_PBODY (1 << 2) /* In the document body. */
+#define MDOC_NEWLINE (1 << 3) /* First macro/text in a line. */
+#define MDOC_PHRASE (1 << 4) /* In a Bl -column phrase. */
+#define MDOC_PHRASELIT (1 << 5) /* Literal within a 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. */
+#define MDOC_NODELIMC (1 << 10) /* Disable closing delimiter handling. */
+#define MAN_ELINE (1 << 11) /* Next-line element scope. */
+#define MAN_BLINE (1 << 12) /* Next-line block scope. */
+#define MDOC_PHRASEQF (1 << 13) /* Quote first word encountered. */
+#define MDOC_PHRASEQL (1 << 14) /* Quote last word of this phrase. */
+#define MDOC_PHRASEQN (1 << 15) /* Quote first word of the next phrase. */
+#define ROFF_NONOFILL (1 << 16) /* Temporarily suspend no-fill mode. */
+#define MAN_NEWLINE MDOC_NEWLINE
+ enum roff_sec lastsec; /* Last section seen. */
+ enum roff_sec lastnamed; /* Last standard section seen. */
+ enum roff_next next; /* Where to put the next node. */
+ char filesec; /* Section digit in the file name. */
+};
+
+
+struct roff_node *roff_node_alloc(struct roff_man *, int, int,
+ enum roff_type, int);
+void roff_node_append(struct roff_man *, struct roff_node *);
+void roff_word_alloc(struct roff_man *, int, int, const char *);
+void roff_word_append(struct roff_man *, const char *);
+void roff_elem_alloc(struct roff_man *, int, int, int);
+struct roff_node *roff_block_alloc(struct roff_man *, int, int, int);
+struct roff_node *roff_head_alloc(struct roff_man *, int, int, int);
+struct roff_node *roff_body_alloc(struct roff_man *, int, int, int);
+void roff_node_unlink(struct roff_man *, struct roff_node *);
+void roff_node_relink(struct roff_man *, struct roff_node *);
+void roff_node_free(struct roff_node *);
+void roff_node_delete(struct roff_man *, struct roff_node *);
+
+struct ohash *roffhash_alloc(enum roff_tok, enum roff_tok);
+enum roff_tok roffhash_find(struct ohash *, const char *, size_t);
+void roffhash_free(struct ohash *);
+
+void roff_state_reset(struct roff_man *);
+void roff_validate(struct roff_man *);
+
+/*
+ * Functions called from roff.c need to be declared here,
+ * not in libmdoc.h or libman.h, even if they are specific
+ * to either the mdoc(7) or the man(7) parser.
+ */
+
+void man_breakscope(struct roff_man *, int);
+void mdoc_argv_free(struct mdoc_arg *);
diff --git a/usr.bin/mandoc/roff_term.c b/usr.bin/mandoc/roff_term.c
new file mode 100644
index 0000000..ef90623
--- /dev/null
+++ b/usr.bin/mandoc/roff_term.c
@@ -0,0 +1,244 @@
+/* $OpenBSD: roff_term.c,v 1.19 2019/01/04 03:24:30 schwarze Exp $ */
+/*
+ * Copyright (c) 2010,2014,2015,2017-2019 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "out.h"
+#include "term.h"
+
+#define ROFF_TERM_ARGS struct termp *p, const struct roff_node *n
+
+typedef void (*roff_term_pre_fp)(ROFF_TERM_ARGS);
+
+static void roff_term_pre_br(ROFF_TERM_ARGS);
+static void roff_term_pre_ce(ROFF_TERM_ARGS);
+static void roff_term_pre_ft(ROFF_TERM_ARGS);
+static void roff_term_pre_ll(ROFF_TERM_ARGS);
+static void roff_term_pre_mc(ROFF_TERM_ARGS);
+static void roff_term_pre_po(ROFF_TERM_ARGS);
+static void roff_term_pre_sp(ROFF_TERM_ARGS);
+static void roff_term_pre_ta(ROFF_TERM_ARGS);
+static void roff_term_pre_ti(ROFF_TERM_ARGS);
+
+static const roff_term_pre_fp roff_term_pre_acts[ROFF_MAX] = {
+ roff_term_pre_br, /* br */
+ roff_term_pre_ce, /* ce */
+ roff_term_pre_br, /* fi */
+ roff_term_pre_ft, /* ft */
+ roff_term_pre_ll, /* ll */
+ roff_term_pre_mc, /* mc */
+ roff_term_pre_br, /* nf */
+ roff_term_pre_po, /* po */
+ roff_term_pre_ce, /* rj */
+ roff_term_pre_sp, /* sp */
+ roff_term_pre_ta, /* ta */
+ roff_term_pre_ti, /* ti */
+};
+
+
+void
+roff_term_pre(struct termp *p, const struct roff_node *n)
+{
+ assert(n->tok < ROFF_MAX);
+ (*roff_term_pre_acts[n->tok])(p, n);
+}
+
+static void
+roff_term_pre_br(ROFF_TERM_ARGS)
+{
+ term_newln(p);
+ if (p->flags & TERMP_BRIND) {
+ p->tcol->offset = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin;
+ p->trailspace = 0;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
+ p->flags |= TERMP_NOSPACE;
+ }
+}
+
+static void
+roff_term_pre_ce(ROFF_TERM_ARGS)
+{
+ const struct roff_node *nc1, *nc2;
+
+ roff_term_pre_br(p, n);
+ p->flags |= n->tok == ROFF_ce ? TERMP_CENTER : TERMP_RIGHT;
+ nc1 = n->child->next;
+ while (nc1 != NULL) {
+ nc2 = nc1;
+ do {
+ nc2 = nc2->next;
+ } while (nc2 != NULL && (nc2->type != ROFFT_TEXT ||
+ (nc2->flags & NODE_LINE) == 0));
+ while (nc1 != nc2) {
+ if (nc1->type == ROFFT_TEXT)
+ term_word(p, nc1->string);
+ else
+ roff_term_pre(p, nc1);
+ nc1 = nc1->next;
+ }
+ p->flags |= TERMP_NOSPACE;
+ term_flushln(p);
+ }
+ p->flags &= ~(TERMP_CENTER | TERMP_RIGHT);
+}
+
+static void
+roff_term_pre_ft(ROFF_TERM_ARGS)
+{
+ const char *cp;
+
+ cp = n->child->string;
+ switch (mandoc_font(cp, (int)strlen(cp))) {
+ 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_FONTPREV:
+ term_fontlast(p);
+ break;
+ case ESCAPE_FONTROMAN:
+ case ESCAPE_FONTCW:
+ term_fontrepl(p, TERMFONT_NONE);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+roff_term_pre_ll(ROFF_TERM_ARGS)
+{
+ term_setwidth(p, n->child != NULL ? n->child->string : NULL);
+}
+
+static void
+roff_term_pre_mc(ROFF_TERM_ARGS)
+{
+ if (p->col) {
+ p->flags |= TERMP_NOBREAK;
+ term_flushln(p);
+ p->flags &= ~(TERMP_NOBREAK | TERMP_NOSPACE);
+ }
+ if (n->child != NULL) {
+ p->mc = n->child->string;
+ p->flags |= TERMP_NEWMC;
+ } else
+ p->flags |= TERMP_ENDMC;
+}
+
+static void
+roff_term_pre_po(ROFF_TERM_ARGS)
+{
+ struct roffsu su;
+ static int po, polast;
+ int ponew;
+
+ if (n->child != NULL &&
+ a2roffsu(n->child->string, &su, SCALE_EM) != NULL) {
+ ponew = term_hen(p, &su);
+ if (*n->child->string == '+' ||
+ *n->child->string == '-')
+ ponew += po;
+ } else
+ ponew = polast;
+ polast = po;
+ po = ponew;
+
+ ponew = po - polast + (int)p->tcol->offset;
+ p->tcol->offset = ponew > 0 ? ponew : 0;
+}
+
+static void
+roff_term_pre_sp(ROFF_TERM_ARGS)
+{
+ struct roffsu su;
+ int len;
+
+ if (n->child != NULL) {
+ if (a2roffsu(n->child->string, &su, SCALE_VS) == NULL)
+ su.scale = 1.0;
+ len = term_vspan(p, &su);
+ } else
+ len = 1;
+
+ if (len < 0)
+ p->skipvsp -= len;
+ else
+ while (len--)
+ term_vspace(p);
+
+ roff_term_pre_br(p, n);
+}
+
+static void
+roff_term_pre_ta(ROFF_TERM_ARGS)
+{
+ term_tab_set(p, NULL);
+ for (n = n->child; n != NULL; n = n->next)
+ term_tab_set(p, n->string);
+}
+
+static void
+roff_term_pre_ti(ROFF_TERM_ARGS)
+{
+ struct roffsu su;
+ const char *cp;
+ int len, sign;
+
+ roff_term_pre_br(p, n);
+
+ if (n->child == NULL)
+ return;
+ cp = n->child->string;
+ if (*cp == '+') {
+ sign = 1;
+ cp++;
+ } else if (*cp == '-') {
+ sign = -1;
+ cp++;
+ } else
+ sign = 0;
+
+ if (a2roffsu(cp, &su, SCALE_EM) == NULL)
+ return;
+ len = term_hen(p, &su);
+
+ if (sign == 0) {
+ p->ti = len - p->tcol->offset;
+ p->tcol->offset = len;
+ } else if (sign == 1) {
+ p->ti = len;
+ p->tcol->offset += len;
+ } else if ((size_t)len < p->tcol->offset) {
+ p->ti = -len;
+ p->tcol->offset -= len;
+ } else {
+ p->ti = -p->tcol->offset;
+ p->tcol->offset = 0;
+ }
+}
diff --git a/usr.bin/mandoc/roff_validate.c b/usr.bin/mandoc/roff_validate.c
new file mode 100644
index 0000000..6fa4f33
--- /dev/null
+++ b/usr.bin/mandoc/roff_validate.c
@@ -0,0 +1,149 @@
+/* $OpenBSD: roff_validate.c,v 1.19 2020/02/27 01:25:58 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2017, 2018, 2020 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "libmandoc.h"
+#include "roff_int.h"
+
+#define ROFF_VALID_ARGS struct roff_man *man, struct roff_node *n
+
+typedef void (*roff_valid_fp)(ROFF_VALID_ARGS);
+
+static void roff_valid_br(ROFF_VALID_ARGS);
+static void roff_valid_fi(ROFF_VALID_ARGS);
+static void roff_valid_ft(ROFF_VALID_ARGS);
+static void roff_valid_nf(ROFF_VALID_ARGS);
+static void roff_valid_sp(ROFF_VALID_ARGS);
+
+static const roff_valid_fp roff_valids[ROFF_MAX] = {
+ roff_valid_br, /* br */
+ NULL, /* ce */
+ roff_valid_fi, /* fi */
+ roff_valid_ft, /* ft */
+ NULL, /* ll */
+ NULL, /* mc */
+ roff_valid_nf, /* nf */
+ NULL, /* po */
+ NULL, /* rj */
+ roff_valid_sp, /* sp */
+ NULL, /* ta */
+ NULL, /* ti */
+};
+
+
+void
+roff_validate(struct roff_man *man)
+{
+ struct roff_node *n;
+
+ n = man->last;
+ assert(n->tok < ROFF_MAX);
+ if (roff_valids[n->tok] != NULL)
+ (*roff_valids[n->tok])(man, n);
+}
+
+static void
+roff_valid_br(ROFF_VALID_ARGS)
+{
+ struct roff_node *np;
+
+ if (n->next != NULL && n->next->type == ROFFT_TEXT &&
+ *n->next->string == ' ') {
+ mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
+ "br before text line with leading blank");
+ roff_node_delete(man, n);
+ return;
+ }
+
+ if ((np = roff_node_prev(n)) == NULL)
+ return;
+
+ switch (np->tok) {
+ case ROFF_br:
+ case ROFF_sp:
+ case MDOC_Pp:
+ mandoc_msg(MANDOCERR_PAR_SKIP,
+ n->line, n->pos, "br after %s", roff_name[np->tok]);
+ roff_node_delete(man, n);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+roff_valid_fi(ROFF_VALID_ARGS)
+{
+ if ((n->flags & NODE_NOFILL) == 0)
+ mandoc_msg(MANDOCERR_FI_SKIP, n->line, n->pos, "fi");
+}
+
+static void
+roff_valid_ft(ROFF_VALID_ARGS)
+{
+ const char *cp;
+
+ if (n->child == NULL) {
+ man->next = ROFF_NEXT_CHILD;
+ roff_word_alloc(man, n->line, n->pos, "P");
+ man->last = n;
+ return;
+ }
+
+ cp = n->child->string;
+ if (mandoc_font(cp, (int)strlen(cp)) != ESCAPE_ERROR)
+ return;
+ mandoc_msg(MANDOCERR_FT_BAD, n->line, n->pos, "ft %s", cp);
+ roff_node_delete(man, n);
+}
+
+static void
+roff_valid_nf(ROFF_VALID_ARGS)
+{
+ if (n->flags & NODE_NOFILL)
+ mandoc_msg(MANDOCERR_NF_SKIP, n->line, n->pos, "nf");
+}
+
+static void
+roff_valid_sp(ROFF_VALID_ARGS)
+{
+ struct roff_node *np;
+
+ if ((np = roff_node_prev(n)) == NULL)
+ return;
+
+ switch (np->tok) {
+ case ROFF_br:
+ mandoc_msg(MANDOCERR_PAR_SKIP,
+ np->line, np->pos, "br before sp");
+ roff_node_delete(man, np);
+ break;
+ case MDOC_Pp:
+ mandoc_msg(MANDOCERR_PAR_SKIP,
+ n->line, n->pos, "sp after Pp");
+ roff_node_delete(man, n);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/usr.bin/mandoc/st.c b/usr.bin/mandoc/st.c
new file mode 100644
index 0000000..27039fe
--- /dev/null
+++ b/usr.bin/mandoc/st.c
@@ -0,0 +1,80 @@
+/* $OpenBSD: st.c,v 1.13 2018/12/14 01:17:46 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.
+ */
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+ if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2st(const char *p)
+{
+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", "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.1g-2000", "IEEE Std 1003.1g-2000 (\\(lqPOSIX.1g\\(rq)")
+LINE("-p1003.1i-95", "IEEE Std 1003.1i-1995 (\\(lqPOSIX.1i\\(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("-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("-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("-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("-xcurses4.2", "X/Open Curses Issue\\~4, Version\\~2 (\\(lqXCURSES4.2\\(rq)")
+LINE("-susv1", "Version\\~1 of the Single UNIX Specification (\\(lqSUSv1\\(rq)")
+LINE("-susv2", "Version\\~2 of the Single UNIX Specification (\\(lqSUSv2\\(rq)")
+LINE("-susv3", "Version\\~3 of the Single UNIX Specification (\\(lqSUSv3\\(rq)")
+LINE("-susv4", "Version\\~4 of the Single UNIX Specification (\\(lqSUSv4\\(rq)")
+LINE("-svid4", "System\\~V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)")
+
+ return NULL;
+}
diff --git a/usr.bin/mandoc/tag.c b/usr.bin/mandoc/tag.c
new file mode 100644
index 0000000..c580d3b
--- /dev/null
+++ b/usr.bin/mandoc/tag.c
@@ -0,0 +1,326 @@
+/* $OpenBSD: tag.c,v 1.36 2020/04/19 16:26:11 schwarze Exp $ */
+/*
+ * Copyright (c) 2015,2016,2018,2019,2020 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.
+ *
+ * Functions to tag syntax tree nodes.
+ * For internal use by mandoc(1) validation modules only.
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mandoc_ohash.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "roff_int.h"
+#include "tag.h"
+
+struct tag_entry {
+ struct roff_node **nodes;
+ size_t maxnodes;
+ size_t nnodes;
+ int prio;
+ char s[];
+};
+
+static void tag_move_href(struct roff_man *,
+ struct roff_node *, const char *);
+static void tag_move_id(struct roff_node *);
+
+static struct ohash tag_data;
+
+
+/*
+ * Set up the ohash table to collect nodes
+ * where various marked-up terms are documented.
+ */
+void
+tag_alloc(void)
+{
+ mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
+}
+
+void
+tag_free(void)
+{
+ struct tag_entry *entry;
+ unsigned int slot;
+
+ if (tag_data.info.free == NULL)
+ return;
+ entry = ohash_first(&tag_data, &slot);
+ while (entry != NULL) {
+ free(entry->nodes);
+ free(entry);
+ entry = ohash_next(&tag_data, &slot);
+ }
+ ohash_delete(&tag_data);
+ tag_data.info.free = NULL;
+}
+
+/*
+ * Set a node where a term is defined,
+ * unless it is already defined at a lower priority.
+ */
+void
+tag_put(const char *s, int prio, struct roff_node *n)
+{
+ struct tag_entry *entry;
+ struct roff_node *nold;
+ const char *se;
+ size_t len;
+ unsigned int slot;
+
+ assert(prio <= TAG_FALLBACK);
+
+ if (s == NULL) {
+ if (n->child == NULL || n->child->type != ROFFT_TEXT)
+ return;
+ s = n->child->string;
+ switch (s[0]) {
+ case '-':
+ s++;
+ break;
+ case '\\':
+ switch (s[1]) {
+ case '&':
+ case '-':
+ case 'e':
+ s += 2;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Skip whitespace and escapes and whatever follows,
+ * and if there is any, downgrade the priority.
+ */
+
+ len = strcspn(s, " \t\\");
+ if (len == 0)
+ return;
+
+ se = s + len;
+ if (*se != '\0' && prio < TAG_WEAK)
+ prio = TAG_WEAK;
+
+ slot = ohash_qlookupi(&tag_data, s, &se);
+ entry = ohash_find(&tag_data, slot);
+
+ /* Build a new entry. */
+
+ if (entry == NULL) {
+ entry = mandoc_malloc(sizeof(*entry) + len + 1);
+ memcpy(entry->s, s, len);
+ entry->s[len] = '\0';
+ entry->nodes = NULL;
+ entry->maxnodes = entry->nnodes = 0;
+ ohash_insert(&tag_data, slot, entry);
+ }
+
+ /*
+ * Lower priority numbers take precedence.
+ * If a better entry is already present, ignore the new one.
+ */
+
+ else if (entry->prio < prio)
+ return;
+
+ /*
+ * If the existing entry is worse, clear it.
+ * In addition, a tag with priority TAG_FALLBACK
+ * is only used if the tag occurs exactly once.
+ */
+
+ else if (entry->prio > prio || prio == TAG_FALLBACK) {
+ while (entry->nnodes > 0) {
+ nold = entry->nodes[--entry->nnodes];
+ nold->flags &= ~NODE_ID;
+ free(nold->tag);
+ nold->tag = NULL;
+ }
+ if (prio == TAG_FALLBACK) {
+ entry->prio = TAG_DELETE;
+ return;
+ }
+ }
+
+ /* Remember the new node. */
+
+ if (entry->maxnodes == entry->nnodes) {
+ entry->maxnodes += 4;
+ entry->nodes = mandoc_reallocarray(entry->nodes,
+ entry->maxnodes, sizeof(*entry->nodes));
+ }
+ entry->nodes[entry->nnodes++] = n;
+ entry->prio = prio;
+ n->flags |= NODE_ID;
+ if (n->child == NULL || n->child->string != s || *se != '\0') {
+ assert(n->tag == NULL);
+ n->tag = mandoc_strndup(s, len);
+ }
+}
+
+int
+tag_exists(const char *tag)
+{
+ return ohash_find(&tag_data, ohash_qlookup(&tag_data, tag)) != NULL;
+}
+
+/*
+ * For in-line elements, move the link target
+ * to the enclosing paragraph when appropriate.
+ */
+static void
+tag_move_id(struct roff_node *n)
+{
+ struct roff_node *np;
+
+ np = n;
+ for (;;) {
+ if (np->prev != NULL)
+ np = np->prev;
+ else if ((np = np->parent) == NULL)
+ return;
+ switch (np->tok) {
+ case MDOC_It:
+ switch (np->parent->parent->norm->Bl.type) {
+ case LIST_column:
+ /* Target the ROFFT_BLOCK = <tr>. */
+ np = np->parent;
+ break;
+ case LIST_diag:
+ case LIST_hang:
+ case LIST_inset:
+ case LIST_ohang:
+ case LIST_tag:
+ /* Target the ROFFT_HEAD = <dt>. */
+ np = np->parent->head;
+ break;
+ default:
+ /* Target the ROFF_BODY = <li>. */
+ break;
+ }
+ /* FALLTHROUGH */
+ case MDOC_Pp: /* Target the ROFFT_ELEM = <p>. */
+ if (np->tag == NULL) {
+ np->tag = mandoc_strdup(n->tag == NULL ?
+ n->child->string : n->tag);
+ np->flags |= NODE_ID;
+ n->flags &= ~NODE_ID;
+ }
+ return;
+ case MDOC_Sh:
+ case MDOC_Ss:
+ case MDOC_Bd:
+ case MDOC_Bl:
+ case MDOC_D1:
+ case MDOC_Dl:
+ case MDOC_Rs:
+ /* Do not move past major blocks. */
+ return;
+ default:
+ /*
+ * Move past in-line content and partial
+ * blocks, for example .It Xo or .It Bq Er.
+ */
+ break;
+ }
+ }
+}
+
+/*
+ * When a paragraph is tagged and starts with text,
+ * move the permalink to the first few words.
+ */
+static void
+tag_move_href(struct roff_man *man, struct roff_node *n, const char *tag)
+{
+ char *cp;
+
+ if (n == NULL || n->type != ROFFT_TEXT ||
+ *n->string == '\0' || *n->string == ' ')
+ return;
+
+ cp = n->string;
+ while (cp != NULL && cp - n->string < 5)
+ cp = strchr(cp + 1, ' ');
+
+ /* If the first text node is longer, split it. */
+
+ if (cp != NULL && cp[1] != '\0') {
+ man->last = n;
+ man->next = ROFF_NEXT_SIBLING;
+ roff_word_alloc(man, n->line,
+ n->pos + (cp - n->string), cp + 1);
+ man->last->flags = n->flags & ~NODE_LINE;
+ *cp = '\0';
+ }
+
+ assert(n->tag == NULL);
+ n->tag = mandoc_strdup(tag);
+ n->flags |= NODE_HREF;
+}
+
+/*
+ * When all tags have been set, decide where to put
+ * the associated permalinks, and maybe move some tags
+ * to the beginning of the respective paragraphs.
+ */
+void
+tag_postprocess(struct roff_man *man, struct roff_node *n)
+{
+ if (n->flags & NODE_ID) {
+ switch (n->tok) {
+ case MDOC_Pp:
+ tag_move_href(man, n->next, n->tag);
+ break;
+ case MDOC_Bd:
+ case MDOC_D1:
+ case MDOC_Dl:
+ tag_move_href(man, n->child, n->tag);
+ break;
+ case MDOC_Bl:
+ /* XXX No permalink for now. */
+ break;
+ default:
+ if (n->type == ROFFT_ELEM || n->tok == MDOC_Fo)
+ tag_move_id(n);
+ if (n->tok != MDOC_Tg)
+ n->flags |= NODE_HREF;
+ else if ((n->flags & NODE_ID) == 0) {
+ n->flags |= NODE_NOPRT;
+ free(n->tag);
+ n->tag = NULL;
+ }
+ break;
+ }
+ }
+ for (n = n->child; n != NULL; n = n->next)
+ tag_postprocess(man, n);
+}
diff --git a/usr.bin/mandoc/tag.h b/usr.bin/mandoc/tag.h
new file mode 100644
index 0000000..7fa7504
--- /dev/null
+++ b/usr.bin/mandoc/tag.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: tag.h,v 1.14 2020/04/18 20:28:46 schwarze Exp $ */
+/*
+ * Copyright (c) 2015, 2018, 2019, 2020 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.
+ *
+ * Internal interfaces to tag syntax tree nodes.
+ * For use by mandoc(1) validation modules only.
+ */
+
+/*
+ * Tagging priorities.
+ * Lower numbers indicate higher importance.
+ */
+#define TAG_MANUAL 1 /* Set with a .Tg macro. */
+#define TAG_STRONG 2 /* Good automatic tagging. */
+#define TAG_WEAK (INT_MAX - 2) /* Dubious automatic tagging. */
+#define TAG_FALLBACK (INT_MAX - 1) /* Tag only used if unique. */
+#define TAG_DELETE (INT_MAX) /* Tag not used at all. */
+
+void tag_alloc(void);
+int tag_exists(const char *);
+void tag_put(const char *, int, struct roff_node *);
+void tag_postprocess(struct roff_man *, struct roff_node *);
+void tag_free(void);
diff --git a/usr.bin/mandoc/tbl.c b/usr.bin/mandoc/tbl.c
new file mode 100644
index 0000000..a3da958
--- /dev/null
+++ b/usr.bin/mandoc/tbl.c
@@ -0,0 +1,181 @@
+/* $OpenBSD: tbl.c,v 1.27 2018/12/14 06:33:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2015 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "tbl.h"
+#include "libmandoc.h"
+#include "tbl_parse.h"
+#include "tbl_int.h"
+
+
+void
+tbl_read(struct tbl_node *tbl, int ln, const char *p, int pos)
+{
+ const char *cp;
+ int active;
+
+ /*
+ * In the options section, proceed to the layout section
+ * after a semicolon, or right away if there is no semicolon.
+ * Ignore semicolons in arguments.
+ */
+
+ if (tbl->part == TBL_PART_OPTS) {
+ tbl->part = TBL_PART_LAYOUT;
+ active = 1;
+ for (cp = p + pos; *cp != '\0'; cp++) {
+ switch (*cp) {
+ case '(':
+ active = 0;
+ continue;
+ case ')':
+ active = 1;
+ continue;
+ case ';':
+ if (active)
+ break;
+ continue;
+ default:
+ continue;
+ }
+ break;
+ }
+ if (*cp == ';') {
+ tbl_option(tbl, ln, p, &pos);
+ if (p[pos] == '\0')
+ return;
+ }
+ }
+
+ /* Process the other section types. */
+
+ switch (tbl->part) {
+ case TBL_PART_LAYOUT:
+ tbl_layout(tbl, ln, p, pos);
+ break;
+ case TBL_PART_CDATA:
+ tbl_cdata(tbl, ln, p, pos);
+ break;
+ default:
+ tbl_data(tbl, ln, p, pos);
+ break;
+ }
+}
+
+struct tbl_node *
+tbl_alloc(int pos, int line, struct tbl_node *last_tbl)
+{
+ struct tbl_node *tbl;
+
+ tbl = mandoc_calloc(1, sizeof(*tbl));
+ if (last_tbl != NULL)
+ last_tbl->next = tbl;
+ tbl->line = line;
+ tbl->pos = pos;
+ tbl->part = TBL_PART_OPTS;
+ tbl->opts.tab = '\t';
+ tbl->opts.decimal = '.';
+ return tbl;
+}
+
+void
+tbl_free(struct tbl_node *tbl)
+{
+ struct tbl_node *old_tbl;
+ struct tbl_row *rp;
+ struct tbl_cell *cp;
+ struct tbl_span *sp;
+ struct tbl_dat *dp;
+
+ while (tbl != NULL) {
+ while ((rp = tbl->first_row) != NULL) {
+ tbl->first_row = rp->next;
+ while (rp->first != NULL) {
+ cp = rp->first;
+ rp->first = cp->next;
+ free(cp->wstr);
+ free(cp);
+ }
+ free(rp);
+ }
+ while ((sp = tbl->first_span) != NULL) {
+ tbl->first_span = sp->next;
+ while (sp->first != NULL) {
+ dp = sp->first;
+ sp->first = dp->next;
+ free(dp->string);
+ free(dp);
+ }
+ free(sp);
+ }
+ old_tbl = tbl;
+ tbl = tbl->next;
+ free(old_tbl);
+ }
+}
+
+void
+tbl_restart(int line, int pos, struct tbl_node *tbl)
+{
+ if (tbl->part == TBL_PART_CDATA)
+ mandoc_msg(MANDOCERR_TBLDATA_BLK, line, pos, "T&");
+
+ tbl->part = TBL_PART_LAYOUT;
+ tbl->line = line;
+ tbl->pos = pos;
+}
+
+struct tbl_span *
+tbl_span(struct tbl_node *tbl)
+{
+ struct tbl_span *span;
+
+ span = tbl->current_span ? tbl->current_span->next
+ : tbl->first_span;
+ if (span != NULL)
+ tbl->current_span = span;
+ return span;
+}
+
+int
+tbl_end(struct tbl_node *tbl, int still_open)
+{
+ struct tbl_span *sp;
+
+ if (still_open)
+ mandoc_msg(MANDOCERR_BLK_NOEND, tbl->line, tbl->pos, "TS");
+ else if (tbl->part == TBL_PART_CDATA)
+ mandoc_msg(MANDOCERR_TBLDATA_BLK, tbl->line, tbl->pos, "TE");
+
+ sp = tbl->first_span;
+ while (sp != NULL && sp->first == NULL)
+ sp = sp->next;
+ if (sp == NULL) {
+ mandoc_msg(MANDOCERR_TBLDATA_NONE, tbl->line, tbl->pos, NULL);
+ return 0;
+ }
+ return 1;
+}
diff --git a/usr.bin/mandoc/tbl.h b/usr.bin/mandoc/tbl.h
new file mode 100644
index 0000000..2e77ac1
--- /dev/null
+++ b/usr.bin/mandoc/tbl.h
@@ -0,0 +1,122 @@
+/* $OpenBSD: tbl.h,v 1.5 2018/12/12 21:54:30 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2017, 2018 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.
+ */
+
+struct tbl_opts {
+ int opts;
+#define TBL_OPT_ALLBOX (1 << 0) /* Option "allbox". */
+#define TBL_OPT_BOX (1 << 1) /* Option "box". */
+#define TBL_OPT_CENTRE (1 << 2) /* Option "center". */
+#define TBL_OPT_DBOX (1 << 3) /* Option "doublebox". */
+#define TBL_OPT_EXPAND (1 << 4) /* Option "expand". */
+#define TBL_OPT_NOKEEP (1 << 5) /* Option "nokeep". */
+#define TBL_OPT_NOSPACE (1 << 6) /* Option "nospaces". */
+#define TBL_OPT_NOWARN (1 << 7) /* Option "nowarn". */
+ int cols; /* Number of columns. */
+ int lvert; /* Width of left vertical line. */
+ int rvert; /* Width of right vertical line. */
+ char tab; /* Option "tab": cell separator. */
+ char decimal; /* Option "decimalpoint". */
+};
+
+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; /* Layout cell to the right. */
+ char *wstr; /* Min width represented as a string. */
+ size_t width; /* Minimum column width. */
+ size_t spacing; /* To the right of the column. */
+ int vert; /* Width of subsequent vertical line. */
+ int col; /* Column number, starting from 0. */
+ int flags;
+#define TBL_CELL_BOLD (1 << 0) /* b, B, fB */
+#define TBL_CELL_ITALIC (1 << 1) /* i, I, fI */
+#define TBL_CELL_TALIGN (1 << 2) /* t, T */
+#define TBL_CELL_UP (1 << 3) /* u, U */
+#define TBL_CELL_BALIGN (1 << 4) /* d, D */
+#define TBL_CELL_WIGN (1 << 5) /* z, Z */
+#define TBL_CELL_EQUAL (1 << 6) /* e, E */
+#define TBL_CELL_WMAX (1 << 7) /* x, X */
+ enum tbl_cellt pos;
+};
+
+/*
+ * A layout row.
+ */
+struct tbl_row {
+ struct tbl_row *next; /* Layout row below. */
+ struct tbl_cell *first; /* Leftmost layout cell. */
+ struct tbl_cell *last; /* Rightmost layout cell. */
+ int vert; /* Width of left vertical line. */
+};
+
+enum tbl_datt {
+ TBL_DATA_NONE, /* Uninitialized row. */
+ TBL_DATA_DATA, /* Contains data rather than a line. */
+ TBL_DATA_HORIZ, /* _: connecting horizontal line. */
+ TBL_DATA_DHORIZ, /* =: connecting double horizontal line. */
+ TBL_DATA_NHORIZ, /* \_: isolated horizontal line. */
+ TBL_DATA_NDHORIZ /* \=: isolated 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_dat *next; /* Data cell to the right. */
+ struct tbl_cell *layout; /* Associated layout cell. */
+ char *string; /* Data, or NULL if not TBL_DATA_DATA. */
+ int hspans; /* How many horizontal spans follow. */
+ int vspans; /* How many vertical spans follow. */
+ int block; /* T{ text block T} */
+ enum tbl_datt pos;
+};
+
+enum tbl_spant {
+ TBL_SPAN_DATA, /* Contains data rather than a line. */
+ TBL_SPAN_HORIZ, /* _: horizontal line. */
+ TBL_SPAN_DHORIZ /* =: double horizontal line. */
+};
+
+/*
+ * A row of data in a table.
+ */
+struct tbl_span {
+ struct tbl_opts *opts; /* Options for the table as a whole. */
+ struct tbl_span *prev; /* Data row above. */
+ struct tbl_span *next; /* Data row below. */
+ struct tbl_row *layout; /* Associated layout row. */
+ struct tbl_dat *first; /* Leftmost data cell. */
+ struct tbl_dat *last; /* Rightmost data cell. */
+ int line; /* Input file line number. */
+ enum tbl_spant pos;
+};
diff --git a/usr.bin/mandoc/tbl_data.c b/usr.bin/mandoc/tbl_data.c
new file mode 100644
index 0000000..b2556d9
--- /dev/null
+++ b/usr.bin/mandoc/tbl_data.c
@@ -0,0 +1,300 @@
+/* $OpenBSD: tbl_data.c,v 1.40 2020/01/11 20:48:13 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011,2015,2017,2018,2019 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "tbl.h"
+#include "libmandoc.h"
+#include "tbl_int.h"
+
+static void getdata(struct tbl_node *, struct tbl_span *,
+ int, const char *, int *);
+static struct tbl_span *newspan(struct tbl_node *, int,
+ struct tbl_row *);
+
+
+static void
+getdata(struct tbl_node *tbl, struct tbl_span *dp,
+ int ln, const char *p, int *pos)
+{
+ struct tbl_dat *dat, *pdat;
+ struct tbl_cell *cp;
+ struct tbl_span *pdp;
+ int sv;
+
+ /*
+ * Determine the length of the string in the cell
+ * and advance the parse point to the end of the cell.
+ */
+
+ sv = *pos;
+ while (p[*pos] != '\0' && p[*pos] != tbl->opts.tab)
+ (*pos)++;
+
+ /* Advance to the next layout cell, skipping spanners. */
+
+ cp = dp->last == NULL ? dp->layout->first : dp->last->layout->next;
+ while (cp != NULL && cp->pos == TBL_CELL_SPAN)
+ cp = cp->next;
+
+ /*
+ * If the current layout row is out of cells, allocate
+ * a new cell if another row of the table has at least
+ * this number of columns, or discard the input if we
+ * are beyond the last column of the table as a whole.
+ */
+
+ if (cp == NULL) {
+ if (dp->layout->last->col + 1 < dp->opts->cols) {
+ cp = mandoc_calloc(1, sizeof(*cp));
+ cp->pos = TBL_CELL_LEFT;
+ cp->spacing = SIZE_MAX;
+ dp->layout->last->next = cp;
+ cp->col = dp->layout->last->col + 1;
+ dp->layout->last = cp;
+ } else {
+ mandoc_msg(MANDOCERR_TBLDATA_EXTRA,
+ ln, sv, "%s", p + sv);
+ while (p[*pos] != '\0')
+ (*pos)++;
+ return;
+ }
+ }
+
+ dat = mandoc_malloc(sizeof(*dat));
+ dat->layout = cp;
+ dat->next = NULL;
+ dat->string = NULL;
+ dat->hspans = 0;
+ dat->vspans = 0;
+ dat->block = 0;
+ dat->pos = TBL_DATA_NONE;
+
+ /*
+ * Increment the number of vertical spans in a data cell above,
+ * if this cell vertically extends one or more cells above.
+ * The iteration must be done over data rows,
+ * not over layout rows, because one layout row
+ * can be reused for more than one data row.
+ */
+
+ if (cp->pos == TBL_CELL_DOWN ||
+ (*pos - sv == 2 && p[sv] == '\\' && p[sv + 1] == '^')) {
+ pdp = dp;
+ while ((pdp = pdp->prev) != NULL) {
+ pdat = pdp->first;
+ while (pdat != NULL &&
+ pdat->layout->col < dat->layout->col)
+ pdat = pdat->next;
+ if (pdat == NULL)
+ break;
+ if (pdat->layout->pos != TBL_CELL_DOWN &&
+ strcmp(pdat->string, "\\^") != 0) {
+ pdat->vspans++;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Count the number of horizontal spans to the right of this cell.
+ * This is purely a matter of the layout, independent of the data.
+ */
+
+ for (cp = cp->next; cp != NULL; cp = cp->next)
+ if (cp->pos == TBL_CELL_SPAN)
+ dat->hspans++;
+ else
+ break;
+
+ /* Append the new data cell to the data row. */
+
+ if (dp->last == NULL)
+ dp->first = dat;
+ else
+ dp->last->next = dat;
+ dp->last = dat;
+
+ /*
+ * 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 && p[sv] == 'T' && p[sv + 1] == '{') {
+ tbl->part = TBL_PART_CDATA;
+ return;
+ }
+
+ dat->string = mandoc_strndup(p + sv, *pos - sv);
+
+ if (p[*pos] != '\0')
+ (*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 ((dat->layout->pos == TBL_CELL_HORIZ ||
+ dat->layout->pos == TBL_CELL_DHORIZ ||
+ dat->layout->pos == TBL_CELL_DOWN) &&
+ dat->pos == TBL_DATA_DATA && *dat->string != '\0')
+ mandoc_msg(MANDOCERR_TBLDATA_SPAN,
+ ln, sv, "%s", dat->string);
+}
+
+void
+tbl_cdata(struct tbl_node *tbl, int ln, const char *p, int pos)
+{
+ struct tbl_dat *dat;
+ size_t sz;
+
+ 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++;
+ while (p[pos] != '\0')
+ getdata(tbl, tbl->last_span, ln, p, &pos);
+ return;
+ } else if (p[pos] == '\0') {
+ tbl->part = TBL_PART_DATA;
+ return;
+ }
+
+ /* Fallthrough: T} is part of a word. */
+ }
+
+ dat->pos = TBL_DATA_DATA;
+ dat->block = 1;
+
+ if (dat->string != NULL) {
+ sz = strlen(p + pos) + strlen(dat->string) + 2;
+ dat->string = mandoc_realloc(dat->string, sz);
+ (void)strlcat(dat->string, " ", sz);
+ (void)strlcat(dat->string, p + pos, sz);
+ } else
+ dat->string = mandoc_strdup(p + pos);
+
+ if (dat->layout->pos == TBL_CELL_DOWN)
+ mandoc_msg(MANDOCERR_TBLDATA_SPAN,
+ ln, pos, "%s", dat->string);
+}
+
+static struct tbl_span *
+newspan(struct tbl_node *tbl, int line, struct tbl_row *rp)
+{
+ struct tbl_span *dp;
+
+ dp = mandoc_calloc(1, sizeof(*dp));
+ dp->line = line;
+ dp->opts = &tbl->opts;
+ dp->layout = rp;
+ dp->prev = tbl->last_span;
+
+ if (dp->prev == NULL) {
+ tbl->first_span = dp;
+ tbl->current_span = NULL;
+ } else
+ dp->prev->next = dp;
+ tbl->last_span = dp;
+
+ return dp;
+}
+
+void
+tbl_data(struct tbl_node *tbl, int ln, const char *p, int pos)
+{
+ struct tbl_row *rp;
+ struct tbl_cell *cp;
+ struct tbl_span *sp;
+
+ rp = (sp = tbl->last_span) == NULL ? tbl->first_row :
+ sp->pos == TBL_SPAN_DATA && sp->layout->next != NULL ?
+ sp->layout->next : sp->layout;
+
+ assert(rp != NULL);
+
+ if (p[1] == '\0') {
+ switch (p[0]) {
+ case '.':
+ /*
+ * Empty request lines must be handled here
+ * and cannot be discarded in roff_parseln()
+ * because in the layout section, they
+ * are significant and end the layout.
+ */
+ return;
+ case '_':
+ sp = newspan(tbl, ln, rp);
+ sp->pos = TBL_SPAN_HORIZ;
+ return;
+ case '=':
+ sp = newspan(tbl, ln, rp);
+ sp->pos = TBL_SPAN_DHORIZ;
+ return;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * If the layout row contains nothing but horizontal lines,
+ * allocate an empty span for it and assign the current span
+ * to the next layout row accepting data.
+ */
+
+ while (rp->next != NULL) {
+ if (rp->last->col + 1 < tbl->opts.cols)
+ break;
+ for (cp = rp->first; cp != NULL; cp = cp->next)
+ if (cp->pos != TBL_CELL_HORIZ &&
+ cp->pos != TBL_CELL_DHORIZ)
+ break;
+ if (cp != NULL)
+ break;
+ sp = newspan(tbl, ln, rp);
+ sp->pos = TBL_SPAN_DATA;
+ rp = rp->next;
+ }
+
+ /* Process a real data row. */
+
+ sp = newspan(tbl, ln, rp);
+ sp->pos = TBL_SPAN_DATA;
+ while (p[pos] != '\0')
+ getdata(tbl, sp, ln, p, &pos);
+}
diff --git a/usr.bin/mandoc/tbl_html.c b/usr.bin/mandoc/tbl_html.c
new file mode 100644
index 0000000..cbc4bfa
--- /dev/null
+++ b/usr.bin/mandoc/tbl_html.c
@@ -0,0 +1,255 @@
+/* $OpenBSD: tbl_html.c,v 1.28 2019/03/17 18:20:07 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2017, 2018 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "tbl.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_sulen(const struct roffsu *, 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 size_t
+html_tbl_sulen(const struct roffsu *su, void *arg)
+{
+ if (su->scale < 0.0)
+ return 0;
+
+ switch (su->unit) {
+ case SCALE_FS: /* 2^16 basic units */
+ return su->scale * 65536.0 / 24.0;
+ case SCALE_IN: /* 10 characters per inch */
+ return su->scale * 10.0;
+ case SCALE_CM: /* 2.54 cm per inch */
+ return su->scale * 10.0 / 2.54;
+ case SCALE_PC: /* 6 pica per inch */
+ case SCALE_VS:
+ return su->scale * 10.0 / 6.0;
+ case SCALE_EN:
+ case SCALE_EM:
+ return su->scale;
+ case SCALE_PT: /* 12 points per pica */
+ return su->scale * 10.0 / 6.0 / 12.0;
+ case SCALE_BU: /* 24 basic units per character */
+ return su->scale / 24.0;
+ case SCALE_MM: /* 1/1000 inch */
+ return su->scale / 100.0;
+ default:
+ abort();
+ }
+}
+
+static void
+html_tblopen(struct html *h, const struct tbl_span *sp)
+{
+ html_close_paragraph(h);
+ if (h->tbl.cols == NULL) {
+ h->tbl.len = html_tbl_len;
+ h->tbl.slen = html_tbl_strlen;
+ h->tbl.sulen = html_tbl_sulen;
+ tblcalc(&h->tbl, sp, 0, 0);
+ }
+ assert(NULL == h->tblt);
+ h->tblt = print_otag(h, TAG_TABLE, "c?ss", "tbl",
+ "border",
+ sp->opts->opts & TBL_OPT_ALLBOX ? "1" : NULL,
+ "border-style",
+ sp->opts->opts & TBL_OPT_DBOX ? "double" :
+ sp->opts->opts & TBL_OPT_BOX ? "solid" : NULL,
+ "border-top-style",
+ sp->pos == TBL_SPAN_DHORIZ ? "double" :
+ sp->pos == TBL_SPAN_HORIZ ? "solid" : 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_dat *dp;
+ const struct tbl_cell *cp;
+ const struct tbl_span *psp;
+ struct tag *tt;
+ const char *hspans, *vspans, *halign, *valign;
+ const char *bborder, *lborder, *rborder;
+ char hbuf[4], vbuf[4];
+ int i;
+
+ if (h->tblt == NULL)
+ html_tblopen(h, sp);
+
+ /*
+ * Horizontal lines spanning the whole table
+ * are handled by previous or following table rows.
+ */
+
+ if (sp->pos != TBL_SPAN_DATA)
+ return;
+
+ /* Inhibit printing of spaces: we do padding ourselves. */
+
+ h->flags |= HTML_NONOSPACE;
+ h->flags |= HTML_NOSPACE;
+
+ /* Draw a vertical line left of this row? */
+
+ switch (sp->layout->vert) {
+ case 2:
+ lborder = "double";
+ break;
+ case 1:
+ lborder = "solid";
+ break;
+ default:
+ lborder = NULL;
+ break;
+ }
+
+ /* Draw a horizontal line below this row? */
+
+ bborder = NULL;
+ if ((psp = sp->next) != NULL) {
+ switch (psp->pos) {
+ case TBL_SPAN_DHORIZ:
+ bborder = "double";
+ break;
+ case TBL_SPAN_HORIZ:
+ bborder = "solid";
+ break;
+ default:
+ break;
+ }
+ }
+
+ tt = print_otag(h, TAG_TR, "ss",
+ "border-left-style", lborder,
+ "border-bottom-style", bborder);
+
+ for (dp = sp->first; dp != NULL; dp = dp->next) {
+ print_stagq(h, tt);
+
+ /*
+ * Do not generate <td> elements for continuations
+ * of spanned cells. Larger <td> elements covering
+ * this space were already generated earlier.
+ */
+
+ cp = dp->layout;
+ if (cp->pos == TBL_CELL_SPAN || cp->pos == TBL_CELL_DOWN ||
+ (dp->string != NULL && strcmp(dp->string, "\\^") == 0))
+ continue;
+
+ /* Determine the attribute values. */
+
+ if (dp->hspans > 0) {
+ (void)snprintf(hbuf, sizeof(hbuf),
+ "%d", dp->hspans + 1);
+ hspans = hbuf;
+ } else
+ hspans = NULL;
+ if (dp->vspans > 0) {
+ (void)snprintf(vbuf, sizeof(vbuf),
+ "%d", dp->vspans + 1);
+ vspans = vbuf;
+ } else
+ vspans = NULL;
+
+ switch (cp->pos) {
+ case TBL_CELL_CENTRE:
+ halign = "center";
+ break;
+ case TBL_CELL_RIGHT:
+ case TBL_CELL_NUMBER:
+ halign = "right";
+ break;
+ default:
+ halign = NULL;
+ break;
+ }
+ if (cp->flags & TBL_CELL_TALIGN)
+ valign = "top";
+ else if (cp->flags & TBL_CELL_BALIGN)
+ valign = "bottom";
+ else
+ valign = NULL;
+
+ for (i = dp->hspans; i > 0; i--)
+ cp = cp->next;
+ switch (cp->vert) {
+ case 2:
+ rborder = "double";
+ break;
+ case 1:
+ rborder = "solid";
+ break;
+ default:
+ rborder = NULL;
+ break;
+ }
+
+ /* Print the element and the attributes. */
+
+ print_otag(h, TAG_TD, "??sss",
+ "colspan", hspans, "rowspan", vspans,
+ "vertical-align", valign,
+ "text-align", halign,
+ "border-right-style", rborder);
+ if (dp->string != NULL)
+ print_text(h, dp->string);
+ }
+
+ print_tagq(h, tt);
+
+ h->flags &= ~HTML_NONOSPACE;
+
+ if (sp->next == NULL) {
+ assert(h->tbl.cols);
+ free(h->tbl.cols);
+ h->tbl.cols = NULL;
+ print_tblclose(h);
+ }
+}
diff --git a/usr.bin/mandoc/tbl_int.h b/usr.bin/mandoc/tbl_int.h
new file mode 100644
index 0000000..299ceaa
--- /dev/null
+++ b/usr.bin/mandoc/tbl_int.h
@@ -0,0 +1,47 @@
+/* $OpenBSD: tbl_int.h,v 1.2 2018/12/14 06:33:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011,2013,2015,2017,2018 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.
+ *
+ * Internal interfaces of the tbl(7) parser.
+ * For use inside the tbl(7) parser only.
+ */
+
+enum tbl_part {
+ TBL_PART_OPTS, /* In the first line, ends with semicolon. */
+ TBL_PART_LAYOUT, /* In the layout section, ends with full stop. */
+ TBL_PART_DATA, /* In the data section, ends with TE. */
+ TBL_PART_CDATA /* In a T{ block, ends with T} */
+};
+
+struct tbl_node {
+ struct tbl_opts opts; /* Options for the whole table. */
+ struct tbl_node *next; /* Next table. */
+ struct tbl_row *first_row; /* First layout row. */
+ struct tbl_row *last_row; /* Last layout row. */
+ struct tbl_span *first_span; /* First data row. */
+ struct tbl_span *current_span; /* Data row being parsed. */
+ struct tbl_span *last_span; /* Last data row. */
+ int line; /* Line number in input file. */
+ int pos; /* Column number in input file. */
+ enum tbl_part part; /* Table section being parsed. */
+};
+
+
+void tbl_option(struct tbl_node *, int, const char *, int *);
+void tbl_layout(struct tbl_node *, int, const char *, int);
+void tbl_data(struct tbl_node *, int, const char *, int);
+void tbl_cdata(struct tbl_node *, int, const char *, int);
+void tbl_reset(struct tbl_node *);
diff --git a/usr.bin/mandoc/tbl_layout.c b/usr.bin/mandoc/tbl_layout.c
new file mode 100644
index 0000000..aae36d9
--- /dev/null
+++ b/usr.bin/mandoc/tbl_layout.c
@@ -0,0 +1,371 @@
+/* $OpenBSD: tbl_layout.c,v 1.35 2018/12/14 05:17:45 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2012, 2014, 2015, 2017 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.
+ */
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "tbl.h"
+#include "libmandoc.h"
+#include "tbl_int.h"
+
+struct tbl_phrase {
+ char name;
+ enum tbl_cellt key;
+};
+
+static const struct tbl_phrase keys[] = {
+ { '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 }
+};
+
+#define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
+
+static void mods(struct tbl_node *, struct tbl_cell *,
+ int, const char *, int *);
+static void cell(struct tbl_node *, struct tbl_row *,
+ int, const char *, int *);
+static struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
+ enum tbl_cellt);
+
+
+static void
+mods(struct tbl_node *tbl, struct tbl_cell *cp,
+ int ln, const char *p, int *pos)
+{
+ char *endptr;
+ size_t sz;
+
+mod:
+ while (p[*pos] == ' ' || p[*pos] == '\t')
+ (*pos)++;
+
+ /* Row delimiters and cell specifiers end modifier lists. */
+
+ if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
+ return;
+
+ /* Throw away parenthesised expression. */
+
+ if ('(' == p[*pos]) {
+ (*pos)++;
+ while (p[*pos] && ')' != p[*pos])
+ (*pos)++;
+ if (')' == p[*pos]) {
+ (*pos)++;
+ goto mod;
+ }
+ mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL);
+ return;
+ }
+
+ /* Parse numerical spacing from modifier string. */
+
+ if (isdigit((unsigned char)p[*pos])) {
+ cp->spacing = strtoull(p + *pos, &endptr, 10);
+ *pos = endptr - p;
+ goto mod;
+ }
+
+ switch (tolower((unsigned char)p[(*pos)++])) {
+ case 'b':
+ cp->flags |= TBL_CELL_BOLD;
+ goto mod;
+ case 'd':
+ cp->flags |= TBL_CELL_BALIGN;
+ goto mod;
+ case 'e':
+ cp->flags |= TBL_CELL_EQUAL;
+ goto mod;
+ case 'f':
+ break;
+ case 'i':
+ cp->flags |= TBL_CELL_ITALIC;
+ goto mod;
+ case 'm':
+ mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m");
+ goto mod;
+ case 'p':
+ case 'v':
+ if (p[*pos] == '-' || p[*pos] == '+')
+ (*pos)++;
+ while (isdigit((unsigned char)p[*pos]))
+ (*pos)++;
+ goto mod;
+ case 't':
+ cp->flags |= TBL_CELL_TALIGN;
+ goto mod;
+ case 'u':
+ cp->flags |= TBL_CELL_UP;
+ goto mod;
+ case 'w':
+ sz = 0;
+ if (p[*pos] == '(') {
+ (*pos)++;
+ while (p[*pos + sz] != '\0' && p[*pos + sz] != ')')
+ sz++;
+ } else
+ while (isdigit((unsigned char)p[*pos + sz]))
+ sz++;
+ if (sz) {
+ free(cp->wstr);
+ cp->wstr = mandoc_strndup(p + *pos, sz);
+ *pos += sz;
+ if (p[*pos] == ')')
+ (*pos)++;
+ }
+ goto mod;
+ case 'x':
+ cp->flags |= TBL_CELL_WMAX;
+ goto mod;
+ case 'z':
+ cp->flags |= TBL_CELL_WIGN;
+ goto mod;
+ case '|':
+ if (cp->vert < 2)
+ cp->vert++;
+ else
+ mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
+ ln, *pos - 1, NULL);
+ goto mod;
+ default:
+ mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
+ ln, *pos - 1, "%c", p[*pos - 1]);
+ goto mod;
+ }
+
+ /* Ignore parenthised font names for now. */
+
+ if (p[*pos] == '(')
+ goto mod;
+
+ /* Support only one-character font-names for now. */
+
+ if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
+ mandoc_msg(MANDOCERR_FT_BAD,
+ ln, *pos, "TS %s", p + *pos - 1);
+ if (p[*pos] != '\0')
+ (*pos)++;
+ if (p[*pos] != '\0')
+ (*pos)++;
+ goto mod;
+ }
+
+ switch (p[(*pos)++]) {
+ case '3':
+ case 'B':
+ cp->flags |= TBL_CELL_BOLD;
+ goto mod;
+ case '2':
+ case 'I':
+ cp->flags |= TBL_CELL_ITALIC;
+ goto mod;
+ case '1':
+ case 'R':
+ goto mod;
+ default:
+ mandoc_msg(MANDOCERR_FT_BAD,
+ ln, *pos - 1, "TS f%c", p[*pos - 1]);
+ goto mod;
+ }
+}
+
+static void
+cell(struct tbl_node *tbl, struct tbl_row *rp,
+ int ln, const char *p, int *pos)
+{
+ int i;
+ enum tbl_cellt c;
+
+ /* Handle leading vertical lines */
+
+ while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
+ if (p[*pos] == '|') {
+ if (rp->vert < 2)
+ rp->vert++;
+ else
+ mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
+ ln, *pos, NULL);
+ }
+ (*pos)++;
+ }
+
+again:
+ while (p[*pos] == ' ' || p[*pos] == '\t')
+ (*pos)++;
+
+ if (p[*pos] == '.' || p[*pos] == '\0')
+ return;
+
+ /* 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 (i == KEYS_MAX) {
+ mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
+ ln, *pos, "%c", p[*pos]);
+ (*pos)++;
+ goto again;
+ }
+ c = keys[i].key;
+
+ /* Special cases of spanners. */
+
+ if (c == TBL_CELL_SPAN) {
+ if (rp->last == NULL)
+ mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL);
+ else if (rp->last->pos == TBL_CELL_HORIZ ||
+ rp->last->pos == TBL_CELL_DHORIZ)
+ c = rp->last->pos;
+ } else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
+ mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL);
+
+ (*pos)++;
+
+ /* Allocate cell then parse its modifiers. */
+
+ mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
+}
+
+void
+tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
+{
+ struct tbl_row *rp;
+
+ rp = NULL;
+ for (;;) {
+ /* Skip whitespace before and after each cell. */
+
+ while (p[pos] == ' ' || p[pos] == '\t')
+ pos++;
+
+ switch (p[pos]) {
+ case ',': /* Next row on this input line. */
+ pos++;
+ rp = NULL;
+ continue;
+ case '\0': /* Next row on next input line. */
+ return;
+ case '.': /* End of layout. */
+ pos++;
+ tbl->part = TBL_PART_DATA;
+
+ /*
+ * When the layout is completely empty,
+ * default to one left-justified column.
+ */
+
+ if (tbl->first_row == NULL) {
+ tbl->first_row = tbl->last_row =
+ mandoc_calloc(1, sizeof(*rp));
+ }
+ if (tbl->first_row->first == NULL) {
+ mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
+ ln, pos, NULL);
+ cell_alloc(tbl, tbl->first_row,
+ TBL_CELL_LEFT);
+ if (tbl->opts.lvert < tbl->first_row->vert)
+ tbl->opts.lvert = tbl->first_row->vert;
+ return;
+ }
+
+ /*
+ * Search for the widest line
+ * along the left and right margins.
+ */
+
+ for (rp = tbl->first_row; rp; rp = rp->next) {
+ if (tbl->opts.lvert < rp->vert)
+ tbl->opts.lvert = rp->vert;
+ if (rp->last != NULL &&
+ rp->last->col + 1 == tbl->opts.cols &&
+ tbl->opts.rvert < rp->last->vert)
+ tbl->opts.rvert = rp->last->vert;
+
+ /* If the last line is empty, drop it. */
+
+ if (rp->next != NULL &&
+ rp->next->first == NULL) {
+ free(rp->next);
+ rp->next = NULL;
+ tbl->last_row = rp;
+ }
+ }
+ return;
+ default: /* Cell. */
+ break;
+ }
+
+ /*
+ * If the last line had at least one cell,
+ * start a new one; otherwise, continue it.
+ */
+
+ if (rp == NULL) {
+ if (tbl->last_row == NULL ||
+ tbl->last_row->first != NULL) {
+ rp = mandoc_calloc(1, sizeof(*rp));
+ if (tbl->last_row)
+ tbl->last_row->next = rp;
+ else
+ tbl->first_row = rp;
+ tbl->last_row = rp;
+ } else
+ rp = tbl->last_row;
+ }
+ cell(tbl, rp, ln, p, &pos);
+ }
+}
+
+static struct tbl_cell *
+cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
+{
+ struct tbl_cell *p, *pp;
+
+ p = mandoc_calloc(1, sizeof(*p));
+ p->spacing = SIZE_MAX;
+ p->pos = pos;
+
+ if ((pp = rp->last) != NULL) {
+ pp->next = p;
+ p->col = pp->col + 1;
+ } else
+ rp->first = p;
+ rp->last = p;
+
+ if (tbl->opts.cols <= p->col)
+ tbl->opts.cols = p->col + 1;
+
+ return p;
+}
diff --git a/usr.bin/mandoc/tbl_opts.c b/usr.bin/mandoc/tbl_opts.c
new file mode 100644
index 0000000..8f1e77c
--- /dev/null
+++ b/usr.bin/mandoc/tbl_opts.c
@@ -0,0 +1,171 @@
+/* $OpenBSD: tbl_opts.c,v 1.16 2018/12/14 05:17:45 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2015 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.
+ */
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "tbl.h"
+#include "libmandoc.h"
+#include "tbl_int.h"
+
+#define KEY_DPOINT 0
+#define KEY_DELIM 1
+#define KEY_LINESIZE 2
+#define KEY_TAB 3
+
+struct tbl_phrase {
+ const char *name;
+ int key;
+};
+
+static const struct tbl_phrase keys[] = {
+ {"decimalpoint", 0},
+ {"delim", 0},
+ {"linesize", 0},
+ {"tab", 0},
+ {"allbox", TBL_OPT_ALLBOX | TBL_OPT_BOX},
+ {"box", TBL_OPT_BOX},
+ {"frame", TBL_OPT_BOX},
+ {"center", TBL_OPT_CENTRE},
+ {"centre", TBL_OPT_CENTRE},
+ {"doublebox", TBL_OPT_DBOX},
+ {"doubleframe", TBL_OPT_DBOX},
+ {"expand", TBL_OPT_EXPAND},
+ {"nokeep", TBL_OPT_NOKEEP},
+ {"nospaces", TBL_OPT_NOSPACE},
+ {"nowarn", TBL_OPT_NOWARN},
+};
+
+#define KEY_MAXKEYS ((int)(sizeof(keys)/sizeof(keys[0])))
+
+static void arg(struct tbl_node *, int, const char *, int *, int);
+
+
+static void
+arg(struct tbl_node *tbl, int ln, const char *p, int *pos, int key)
+{
+ int len, want;
+
+ while (p[*pos] == ' ' || p[*pos] == '\t')
+ (*pos)++;
+
+ /* Arguments are enclosed in parentheses. */
+
+ len = 0;
+ if (p[*pos] == '(') {
+ (*pos)++;
+ while (p[*pos + len] != ')')
+ len++;
+ }
+
+ switch (key) {
+ case KEY_DELIM:
+ mandoc_msg(MANDOCERR_TBLOPT_EQN,
+ ln, *pos, "%.*s", len, p + *pos);
+ want = 2;
+ break;
+ case KEY_TAB:
+ want = 1;
+ if (len == want)
+ tbl->opts.tab = p[*pos];
+ break;
+ case KEY_LINESIZE:
+ want = 0;
+ break;
+ case KEY_DPOINT:
+ want = 1;
+ if (len == want)
+ tbl->opts.decimal = p[*pos];
+ break;
+ default:
+ abort();
+ }
+
+ if (len == 0)
+ mandoc_msg(MANDOCERR_TBLOPT_NOARG, ln, *pos,
+ "%s", keys[key].name);
+ else if (want && len != want)
+ mandoc_msg(MANDOCERR_TBLOPT_ARGSZ, ln, *pos,
+ "%s want %d have %d", keys[key].name, want, len);
+
+ *pos += len;
+ if (p[*pos] == ')')
+ (*pos)++;
+}
+
+/*
+ * Parse one line of options up to the semicolon.
+ * Each option can be preceded by blanks and/or commas,
+ * and some options are followed by arguments.
+ */
+void
+tbl_option(struct tbl_node *tbl, int ln, const char *p, int *offs)
+{
+ int i, pos, len;
+
+ pos = *offs;
+ for (;;) {
+ while (p[pos] == ' ' || p[pos] == '\t' || p[pos] == ',')
+ pos++;
+
+ if (p[pos] == ';') {
+ *offs = pos + 1;
+ return;
+ }
+
+ /* Parse one option name. */
+
+ len = 0;
+ while (isalpha((unsigned char)p[pos + len]))
+ len++;
+
+ if (len == 0) {
+ mandoc_msg(MANDOCERR_TBLOPT_ALPHA,
+ ln, pos, "%c", p[pos]);
+ pos++;
+ continue;
+ }
+
+ /* Look up the option name. */
+
+ i = 0;
+ while (i < KEY_MAXKEYS &&
+ (strncasecmp(p + pos, keys[i].name, len) ||
+ keys[i].name[len] != '\0'))
+ i++;
+
+ if (i == KEY_MAXKEYS) {
+ mandoc_msg(MANDOCERR_TBLOPT_BAD,
+ ln, pos, "%.*s", len, p + pos);
+ pos += len;
+ continue;
+ }
+
+ /* Handle the option. */
+
+ pos += len;
+ if (keys[i].key)
+ tbl->opts.opts |= keys[i].key;
+ else
+ arg(tbl, ln, p, &pos, i);
+ }
+}
diff --git a/usr.bin/mandoc/tbl_parse.h b/usr.bin/mandoc/tbl_parse.h
new file mode 100644
index 0000000..b564490
--- /dev/null
+++ b/usr.bin/mandoc/tbl_parse.h
@@ -0,0 +1,30 @@
+/* $OpenBSD: tbl_parse.h,v 1.2 2018/12/14 06:33:03 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2017 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.
+ *
+ * External interface of the tbl(7) parser.
+ * For use in the roff(7) and tbl(7) parsers only.
+ */
+
+struct tbl_node;
+struct tbl_span;
+
+struct tbl_node *tbl_alloc(int, int, struct tbl_node *);
+int tbl_end(struct tbl_node *, int);
+void tbl_free(struct tbl_node *);
+void tbl_read(struct tbl_node *, int, const char *, int);
+void tbl_restart(int, int, struct tbl_node *);
+struct tbl_span *tbl_span(struct tbl_node *);
diff --git a/usr.bin/mandoc/tbl_term.c b/usr.bin/mandoc/tbl_term.c
new file mode 100644
index 0000000..238bf7a
--- /dev/null
+++ b/usr.bin/mandoc/tbl_term.c
@@ -0,0 +1,943 @@
+/* $OpenBSD: tbl_term.c,v 1.61 2020/01/11 16:24:33 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011-2020 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "tbl.h"
+#include "out.h"
+#include "term.h"
+
+#define IS_HORIZ(cp) ((cp)->pos == TBL_CELL_HORIZ || \
+ (cp)->pos == TBL_CELL_DHORIZ)
+
+
+static size_t term_tbl_len(size_t, void *);
+static size_t term_tbl_strlen(const char *, void *);
+static size_t term_tbl_sulen(const struct roffsu *, void *);
+static void tbl_data(struct termp *, const struct tbl_opts *,
+ const struct tbl_cell *,
+ const struct tbl_dat *,
+ const struct roffcol *);
+static void tbl_direct_border(struct termp *, int, size_t);
+static void tbl_fill_border(struct termp *, int, size_t);
+static void tbl_fill_char(struct termp *, char, size_t);
+static void tbl_fill_string(struct termp *, const char *, size_t);
+static void tbl_hrule(struct termp *, const struct tbl_span *,
+ const struct tbl_span *, 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_word(struct termp *, const struct tbl_dat *);
+
+
+/*
+ * The following border-character tables are indexed
+ * by ternary (3-based) numbers, as opposed to binary or decimal.
+ * Each ternary digit describes the line width in one direction:
+ * 0 means no line, 1 single or light line, 2 double or heavy line.
+ */
+
+/* Positional values of the four directions. */
+#define BRIGHT 1
+#define BDOWN 3
+#define BLEFT (3 * 3)
+#define BUP (3 * 3 * 3)
+#define BHORIZ (BLEFT + BRIGHT)
+
+/* Code points to use for each combination of widths. */
+static const int borders_utf8[81] = {
+ 0x0020, 0x2576, 0x257a, /* 000 right */
+ 0x2577, 0x250c, 0x250d, /* 001 down */
+ 0x257b, 0x250e, 0x250f, /* 002 */
+ 0x2574, 0x2500, 0x257c, /* 010 left */
+ 0x2510, 0x252c, 0x252e, /* 011 left down */
+ 0x2512, 0x2530, 0x2532, /* 012 */
+ 0x2578, 0x257e, 0x2501, /* 020 left */
+ 0x2511, 0x252d, 0x252f, /* 021 left down */
+ 0x2513, 0x2531, 0x2533, /* 022 */
+ 0x2575, 0x2514, 0x2515, /* 100 up */
+ 0x2502, 0x251c, 0x251d, /* 101 up down */
+ 0x257d, 0x251f, 0x2522, /* 102 */
+ 0x2518, 0x2534, 0x2536, /* 110 up left */
+ 0x2524, 0x253c, 0x253e, /* 111 all */
+ 0x2527, 0x2541, 0x2546, /* 112 */
+ 0x2519, 0x2535, 0x2537, /* 120 up left */
+ 0x2525, 0x253d, 0x253f, /* 121 all */
+ 0x252a, 0x2545, 0x2548, /* 122 */
+ 0x2579, 0x2516, 0x2517, /* 200 up */
+ 0x257f, 0x251e, 0x2521, /* 201 up down */
+ 0x2503, 0x2520, 0x2523, /* 202 */
+ 0x251a, 0x2538, 0x253a, /* 210 up left */
+ 0x2526, 0x2540, 0x2544, /* 211 all */
+ 0x2528, 0x2542, 0x254a, /* 212 */
+ 0x251b, 0x2539, 0x253b, /* 220 up left */
+ 0x2529, 0x2543, 0x2547, /* 221 all */
+ 0x252b, 0x2549, 0x254b, /* 222 */
+};
+
+/* ASCII approximations for these code points, compatible with groff. */
+static const int borders_ascii[81] = {
+ ' ', '-', '=', /* 000 right */
+ '|', '+', '+', /* 001 down */
+ '|', '+', '+', /* 002 */
+ '-', '-', '=', /* 010 left */
+ '+', '+', '+', /* 011 left down */
+ '+', '+', '+', /* 012 */
+ '=', '=', '=', /* 020 left */
+ '+', '+', '+', /* 021 left down */
+ '+', '+', '+', /* 022 */
+ '|', '+', '+', /* 100 up */
+ '|', '+', '+', /* 101 up down */
+ '|', '+', '+', /* 102 */
+ '+', '+', '+', /* 110 up left */
+ '+', '+', '+', /* 111 all */
+ '+', '+', '+', /* 112 */
+ '+', '+', '+', /* 120 up left */
+ '+', '+', '+', /* 121 all */
+ '+', '+', '+', /* 122 */
+ '|', '+', '+', /* 200 up */
+ '|', '+', '+', /* 201 up down */
+ '|', '+', '+', /* 202 */
+ '+', '+', '+', /* 210 up left */
+ '+', '+', '+', /* 211 all */
+ '+', '+', '+', /* 212 */
+ '+', '+', '+', /* 220 up left */
+ '+', '+', '+', /* 221 all */
+ '+', '+', '+', /* 222 */
+};
+
+/* Either of the above according to the selected output encoding. */
+static const int *borders_locale;
+
+
+static size_t
+term_tbl_sulen(const struct roffsu *su, void *arg)
+{
+ int i;
+
+ i = term_hen((const struct termp *)arg, su);
+ return i > 0 ? i : 0;
+}
+
+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_cell *cp, *cpn, *cpp, *cps;
+ const struct tbl_dat *dp;
+ static size_t offset;
+ size_t save_offset;
+ size_t coloff, tsz;
+ int hspans, ic, more;
+ int dvert, fc, horiz, lhori, rhori, uvert;
+
+ /* Inhibit printing of spaces: we do padding ourselves. */
+
+ tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE;
+ save_offset = tp->tcol->offset;
+
+ /*
+ * The first time we're invoked for a given table block,
+ * calculate the table widths and decimal positions.
+ */
+
+ if (tp->tbl.cols == NULL) {
+ borders_locale = tp->enc == TERMENC_UTF8 ?
+ borders_utf8 : borders_ascii;
+
+ tp->tbl.len = term_tbl_len;
+ tp->tbl.slen = term_tbl_strlen;
+ tp->tbl.sulen = term_tbl_sulen;
+ tp->tbl.arg = tp;
+
+ tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin);
+
+ /* Tables leak .ta settings to subsequent text. */
+
+ term_tab_set(tp, NULL);
+ coloff = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
+ sp->opts->lvert;
+ for (ic = 0; ic < sp->opts->cols; ic++) {
+ coloff += tp->tbl.cols[ic].width;
+ term_tab_iset(coloff);
+ coloff += tp->tbl.cols[ic].spacing;
+ }
+
+ /* Center the table as a whole. */
+
+ offset = tp->tcol->offset;
+ if (sp->opts->opts & TBL_OPT_CENTRE) {
+ tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
+ ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
+ for (ic = 0; ic + 1 < sp->opts->cols; ic++)
+ tsz += tp->tbl.cols[ic].width +
+ tp->tbl.cols[ic].spacing;
+ if (sp->opts->cols)
+ tsz += tp->tbl.cols[sp->opts->cols - 1].width;
+ if (offset + tsz > tp->tcol->rmargin)
+ tsz -= 1;
+ offset = offset + tp->tcol->rmargin > tsz ?
+ (offset + tp->tcol->rmargin - tsz) / 2 : 0;
+ tp->tcol->offset = offset;
+ }
+
+ /* Horizontal frame at the start of boxed tables. */
+
+ if (tp->enc == TERMENC_ASCII &&
+ sp->opts->opts & TBL_OPT_DBOX)
+ tbl_hrule(tp, NULL, sp, sp, TBL_OPT_DBOX);
+ if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
+ tbl_hrule(tp, NULL, sp, sp, TBL_OPT_BOX);
+ }
+
+ /* Set up the columns. */
+
+ tp->flags |= TERMP_MULTICOL;
+ tp->tcol->offset = offset;
+ horiz = 0;
+ switch (sp->pos) {
+ case TBL_SPAN_HORIZ:
+ case TBL_SPAN_DHORIZ:
+ horiz = 1;
+ term_setcol(tp, 1);
+ break;
+ case TBL_SPAN_DATA:
+ term_setcol(tp, sp->opts->cols + 2);
+ coloff = tp->tcol->offset;
+
+ /* Set up a column for a left vertical frame. */
+
+ if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ||
+ sp->opts->lvert)
+ coloff++;
+ tp->tcol->rmargin = coloff;
+
+ /* Set up the data columns. */
+
+ dp = sp->first;
+ hspans = 0;
+ for (ic = 0; ic < sp->opts->cols; ic++) {
+ if (hspans == 0) {
+ tp->tcol++;
+ tp->tcol->offset = coloff;
+ }
+ coloff += tp->tbl.cols[ic].width;
+ tp->tcol->rmargin = coloff;
+ if (ic + 1 < sp->opts->cols)
+ coloff += tp->tbl.cols[ic].spacing;
+ if (hspans) {
+ hspans--;
+ continue;
+ }
+ if (dp != NULL &&
+ (ic || sp->layout->first->pos != TBL_CELL_SPAN)) {
+ hspans = dp->hspans;
+ dp = dp->next;
+ }
+ }
+
+ /* Set up a column for a right vertical frame. */
+
+ tp->tcol++;
+ tp->tcol->offset = coloff + 1;
+ tp->tcol->rmargin = tp->maxrmargin;
+
+ /* Spans may have reduced the number of columns. */
+
+ tp->lasttcol = tp->tcol - tp->tcols;
+
+ /* Fill the buffers for all data columns. */
+
+ tp->tcol = tp->tcols;
+ cp = cpn = sp->layout->first;
+ dp = sp->first;
+ hspans = 0;
+ for (ic = 0; ic < sp->opts->cols; ic++) {
+ if (cpn != NULL) {
+ cp = cpn;
+ cpn = cpn->next;
+ }
+ if (hspans) {
+ hspans--;
+ continue;
+ }
+ tp->tcol++;
+ tp->col = 0;
+ tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic);
+ if (dp != NULL &&
+ (ic || sp->layout->first->pos != TBL_CELL_SPAN)) {
+ hspans = dp->hspans;
+ dp = dp->next;
+ }
+ }
+ break;
+ }
+
+ do {
+ /* Print the vertical frame at the start of each row. */
+
+ tp->tcol = tp->tcols;
+ uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 :
+ sp->opts->opts & TBL_OPT_BOX ? 1 : 0;
+ if (sp->pos == TBL_SPAN_DATA && uvert < sp->layout->vert)
+ uvert = dvert = sp->layout->vert;
+ if (sp->next != NULL && sp->next->pos == TBL_SPAN_DATA &&
+ dvert < sp->next->layout->vert)
+ dvert = sp->next->layout->vert;
+ if (sp->prev != NULL && uvert < sp->prev->layout->vert &&
+ (horiz || (IS_HORIZ(sp->layout->first) &&
+ !IS_HORIZ(sp->prev->layout->first))))
+ uvert = sp->prev->layout->vert;
+ rhori = sp->pos == TBL_SPAN_DHORIZ ||
+ (sp->first != NULL && sp->first->pos == TBL_DATA_DHORIZ) ||
+ sp->layout->first->pos == TBL_CELL_DHORIZ ? 2 :
+ sp->pos == TBL_SPAN_HORIZ ||
+ (sp->first != NULL && sp->first->pos == TBL_DATA_HORIZ) ||
+ sp->layout->first->pos == TBL_CELL_HORIZ ? 1 : 0;
+ fc = BUP * uvert + BDOWN * dvert + BRIGHT * rhori;
+ if (uvert > 0 || dvert > 0 || (horiz && sp->opts->lvert)) {
+ (*tp->advance)(tp, tp->tcols->offset);
+ tp->viscol = tp->tcol->offset;
+ tbl_direct_border(tp, fc, 1);
+ }
+
+ /* Print the data cells. */
+
+ more = 0;
+ if (horiz)
+ tbl_hrule(tp, sp->prev, sp, sp->next, 0);
+ else {
+ cp = sp->layout->first;
+ cpn = sp->next == NULL ? NULL :
+ sp->next->layout->first;
+ cpp = sp->prev == NULL ? NULL :
+ sp->prev->layout->first;
+ dp = sp->first;
+ hspans = 0;
+ for (ic = 0; ic < sp->opts->cols; ic++) {
+
+ /*
+ * Figure out whether to print a
+ * vertical line after this cell
+ * and advance to next layout cell.
+ */
+
+ uvert = dvert = fc = 0;
+ if (cp != NULL) {
+ cps = cp;
+ while (cps->next != NULL &&
+ cps->next->pos == TBL_CELL_SPAN)
+ cps = cps->next;
+ if (sp->pos == TBL_SPAN_DATA)
+ uvert = dvert = cps->vert;
+ switch (cp->pos) {
+ case TBL_CELL_HORIZ:
+ fc = BHORIZ;
+ break;
+ case TBL_CELL_DHORIZ:
+ fc = BHORIZ * 2;
+ break;
+ default:
+ break;
+ }
+ }
+ if (cpp != NULL) {
+ if (uvert < cpp->vert &&
+ cp != NULL &&
+ ((IS_HORIZ(cp) &&
+ !IS_HORIZ(cpp)) ||
+ (cp->next != NULL &&
+ cpp->next != NULL &&
+ IS_HORIZ(cp->next) &&
+ !IS_HORIZ(cpp->next))))
+ uvert = cpp->vert;
+ cpp = cpp->next;
+ }
+ if (sp->opts->opts & TBL_OPT_ALLBOX) {
+ if (uvert == 0)
+ uvert = 1;
+ if (dvert == 0)
+ dvert = 1;
+ }
+ if (cpn != NULL) {
+ if (dvert == 0 ||
+ (dvert < cpn->vert &&
+ tp->enc == TERMENC_UTF8))
+ dvert = cpn->vert;
+ cpn = cpn->next;
+ }
+
+ lhori = (cp != NULL &&
+ cp->pos == TBL_CELL_DHORIZ) ||
+ (dp != NULL &&
+ dp->pos == TBL_DATA_DHORIZ) ? 2 :
+ (cp != NULL &&
+ cp->pos == TBL_CELL_HORIZ) ||
+ (dp != NULL &&
+ dp->pos == TBL_DATA_HORIZ) ? 1 : 0;
+
+ /*
+ * Skip later cells in a span,
+ * figure out whether to start a span,
+ * and advance to next data cell.
+ */
+
+ if (hspans) {
+ hspans--;
+ cp = cp->next;
+ continue;
+ }
+ if (dp != NULL && (ic ||
+ sp->layout->first->pos != TBL_CELL_SPAN)) {
+ hspans = dp->hspans;
+ dp = dp->next;
+ }
+
+ /*
+ * Print one line of text in the cell
+ * and remember whether there is more.
+ */
+
+ tp->tcol++;
+ if (tp->tcol->col < tp->tcol->lastcol)
+ term_flushln(tp);
+ if (tp->tcol->col < tp->tcol->lastcol)
+ more = 1;
+
+ /*
+ * Vertical frames between data cells,
+ * but not after the last column.
+ */
+
+ if (fc == 0 &&
+ ((uvert == 0 && dvert == 0 &&
+ cp != NULL && (cp->next == NULL ||
+ !IS_HORIZ(cp->next))) ||
+ tp->tcol + 1 ==
+ tp->tcols + tp->lasttcol)) {
+ if (cp != NULL)
+ cp = cp->next;
+ continue;
+ }
+
+ if (tp->viscol < tp->tcol->rmargin) {
+ (*tp->advance)(tp, tp->tcol->rmargin
+ - tp->viscol);
+ tp->viscol = tp->tcol->rmargin;
+ }
+ while (tp->viscol < tp->tcol->rmargin +
+ tp->tbl.cols[ic].spacing / 2)
+ tbl_direct_border(tp,
+ BHORIZ * lhori, 1);
+
+ if (tp->tcol + 1 == tp->tcols + tp->lasttcol)
+ continue;
+
+ if (cp != NULL)
+ cp = cp->next;
+
+ rhori = (cp != NULL &&
+ cp->pos == TBL_CELL_DHORIZ) ||
+ (dp != NULL &&
+ dp->pos == TBL_DATA_DHORIZ) ? 2 :
+ (cp != NULL &&
+ cp->pos == TBL_CELL_HORIZ) ||
+ (dp != NULL &&
+ dp->pos == TBL_DATA_HORIZ) ? 1 : 0;
+
+ if (tp->tbl.cols[ic].spacing)
+ tbl_direct_border(tp,
+ BLEFT * lhori + BRIGHT * rhori +
+ BUP * uvert + BDOWN * dvert, 1);
+
+ if (tp->enc == TERMENC_UTF8)
+ uvert = dvert = 0;
+
+ if (tp->tbl.cols[ic].spacing > 2 &&
+ (uvert > 1 || dvert > 1 || rhori))
+ tbl_direct_border(tp,
+ BHORIZ * rhori +
+ BUP * (uvert > 1) +
+ BDOWN * (dvert > 1), 1);
+ }
+ }
+
+ /* Print the vertical frame at the end of each row. */
+
+ uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 :
+ sp->opts->opts & TBL_OPT_BOX ? 1 : 0;
+ if (sp->pos == TBL_SPAN_DATA &&
+ uvert < sp->layout->last->vert &&
+ sp->layout->last->col + 1 == sp->opts->cols)
+ uvert = dvert = sp->layout->last->vert;
+ if (sp->next != NULL &&
+ dvert < sp->next->layout->last->vert &&
+ sp->next->layout->last->col + 1 == sp->opts->cols)
+ dvert = sp->next->layout->last->vert;
+ if (sp->prev != NULL &&
+ uvert < sp->prev->layout->last->vert &&
+ sp->prev->layout->last->col + 1 == sp->opts->cols &&
+ (horiz || (IS_HORIZ(sp->layout->last) &&
+ !IS_HORIZ(sp->prev->layout->last))))
+ uvert = sp->prev->layout->last->vert;
+ lhori = sp->pos == TBL_SPAN_DHORIZ ||
+ (sp->last != NULL &&
+ sp->last->pos == TBL_DATA_DHORIZ &&
+ sp->last->layout->col + 1 == sp->opts->cols) ||
+ (sp->layout->last->pos == TBL_CELL_DHORIZ &&
+ sp->layout->last->col + 1 == sp->opts->cols) ? 2 :
+ sp->pos == TBL_SPAN_HORIZ ||
+ (sp->last != NULL &&
+ sp->last->pos == TBL_DATA_HORIZ &&
+ sp->last->layout->col + 1 == sp->opts->cols) ||
+ (sp->layout->last->pos == TBL_CELL_HORIZ &&
+ sp->layout->last->col + 1 == sp->opts->cols) ? 1 : 0;
+ fc = BUP * uvert + BDOWN * dvert + BLEFT * lhori;
+ if (uvert > 0 || dvert > 0 || (horiz && sp->opts->rvert)) {
+ if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 ||
+ sp->layout->last->col + 1 < sp->opts->cols)) {
+ tp->tcol++;
+ do {
+ tbl_direct_border(tp,
+ BHORIZ * lhori, 1);
+ } while (tp->viscol < tp->tcol->offset);
+ }
+ tbl_direct_border(tp, fc, 1);
+ }
+ (*tp->endline)(tp);
+ tp->viscol = 0;
+ } while (more);
+
+ /*
+ * Clean up after this row. If it is the last line
+ * of the table, print the box line and clean up
+ * column data; otherwise, print the allbox line.
+ */
+
+ term_setcol(tp, 1);
+ tp->flags &= ~TERMP_MULTICOL;
+ tp->tcol->rmargin = tp->maxrmargin;
+ if (sp->next == NULL) {
+ if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
+ tbl_hrule(tp, sp, sp, NULL, TBL_OPT_BOX);
+ tp->skipvsp = 1;
+ }
+ if (tp->enc == TERMENC_ASCII &&
+ sp->opts->opts & TBL_OPT_DBOX) {
+ tbl_hrule(tp, sp, sp, NULL, TBL_OPT_DBOX);
+ tp->skipvsp = 2;
+ }
+ assert(tp->tbl.cols);
+ free(tp->tbl.cols);
+ tp->tbl.cols = NULL;
+ } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX &&
+ (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA ||
+ sp->next->next != NULL))
+ tbl_hrule(tp, sp, sp, sp->next, TBL_OPT_ALLBOX);
+
+ tp->tcol->offset = save_offset;
+ tp->flags &= ~TERMP_NONOSPACE;
+}
+
+static void
+tbl_hrule(struct termp *tp, const struct tbl_span *spp,
+ const struct tbl_span *sp, const struct tbl_span *spn, int flags)
+{
+ const struct tbl_cell *cpp; /* Layout cell above this line. */
+ const struct tbl_cell *cp; /* Layout cell in this line. */
+ const struct tbl_cell *cpn; /* Layout cell below this line. */
+ const struct tbl_dat *dpn; /* Data cell below this line. */
+ const struct roffcol *col; /* Contains width and spacing. */
+ int opts; /* For the table as a whole. */
+ int bw; /* Box line width. */
+ int hw; /* Horizontal line width. */
+ int lw, rw; /* Left and right line widths. */
+ int uw, dw; /* Vertical line widths. */
+
+ cpp = spp == NULL ? NULL : spp->layout->first;
+ cp = sp == NULL ? NULL : sp->layout->first;
+ cpn = spn == NULL ? NULL : spn->layout->first;
+ dpn = NULL;
+ if (spn != NULL) {
+ if (spn->pos == TBL_SPAN_DATA)
+ dpn = spn->first;
+ else if (spn->next != NULL)
+ dpn = spn->next->first;
+ }
+ opts = sp->opts->opts;
+ bw = opts & TBL_OPT_DBOX ? (tp->enc == TERMENC_UTF8 ? 2 : 1) :
+ opts & (TBL_OPT_BOX | TBL_OPT_ALLBOX) ? 1 : 0;
+ hw = flags == TBL_OPT_DBOX || flags == TBL_OPT_BOX ? bw :
+ sp->pos == TBL_SPAN_DHORIZ ? 2 : 1;
+
+ /* Print the left end of the line. */
+
+ if (tp->viscol == 0) {
+ (*tp->advance)(tp, tp->tcols->offset);
+ tp->viscol = tp->tcols->offset;
+ }
+ if (flags != 0)
+ tbl_direct_border(tp,
+ (spp == NULL ? 0 : BUP * bw) +
+ (spn == NULL ? 0 : BDOWN * bw) +
+ (spp == NULL || cpn == NULL ||
+ cpn->pos != TBL_CELL_DOWN ? BRIGHT * hw : 0), 1);
+
+ col = tp->tbl.cols;
+ for (;;) {
+ if (cp == NULL)
+ col++;
+ else
+ col = tp->tbl.cols + cp->col;
+
+ /* Print the horizontal line inside this column. */
+
+ lw = cpp == NULL || cpn == NULL ||
+ (cpn->pos != TBL_CELL_DOWN &&
+ (dpn == NULL || dpn->string == NULL ||
+ strcmp(dpn->string, "\\^") != 0))
+ ? hw : 0;
+ tbl_direct_border(tp, BHORIZ * lw,
+ col->width + col->spacing / 2);
+
+ /*
+ * Figure out whether a vertical line is crossing
+ * at the end of this column,
+ * and advance to the next column.
+ */
+
+ uw = dw = 0;
+ if (cpp != NULL) {
+ if (flags != TBL_OPT_DBOX) {
+ uw = cpp->vert;
+ if (uw == 0 && opts & TBL_OPT_ALLBOX)
+ uw = 1;
+ }
+ cpp = cpp->next;
+ } else if (spp != NULL && opts & TBL_OPT_ALLBOX)
+ uw = 1;
+ if (cp != NULL)
+ cp = cp->next;
+ if (cpn != NULL) {
+ if (flags != TBL_OPT_DBOX) {
+ dw = cpn->vert;
+ if (dw == 0 && opts & TBL_OPT_ALLBOX)
+ dw = 1;
+ }
+ cpn = cpn->next;
+ while (dpn != NULL && dpn->layout != cpn)
+ dpn = dpn->next;
+ } else if (spn != NULL && opts & TBL_OPT_ALLBOX)
+ dw = 1;
+ if (col + 1 == tp->tbl.cols + sp->opts->cols)
+ break;
+
+ /* Vertical lines do not cross spanned cells. */
+
+ if (cpp != NULL && cpp->pos == TBL_CELL_SPAN)
+ uw = 0;
+ if (cpn != NULL && cpn->pos == TBL_CELL_SPAN)
+ dw = 0;
+
+ /* The horizontal line inside the next column. */
+
+ rw = cpp == NULL || cpn == NULL ||
+ (cpn->pos != TBL_CELL_DOWN &&
+ (dpn == NULL || dpn->string == NULL ||
+ strcmp(dpn->string, "\\^") != 0))
+ ? hw : 0;
+
+ /* The line crossing at the end of this column. */
+
+ if (col->spacing)
+ tbl_direct_border(tp, BLEFT * lw +
+ BRIGHT * rw + BUP * uw + BDOWN * dw, 1);
+
+ /*
+ * In ASCII output, a crossing may print two characters.
+ */
+
+ if (tp->enc != TERMENC_ASCII || (uw < 2 && dw < 2))
+ uw = dw = 0;
+ if (col->spacing > 2)
+ tbl_direct_border(tp,
+ BHORIZ * rw + BUP * uw + BDOWN * dw, 1);
+
+ /* Padding before the start of the next column. */
+
+ if (col->spacing > 4)
+ tbl_direct_border(tp,
+ BHORIZ * rw, (col->spacing - 3) / 2);
+ }
+
+ /* Print the right end of the line. */
+
+ if (flags != 0) {
+ tbl_direct_border(tp,
+ (spp == NULL ? 0 : BUP * bw) +
+ (spn == NULL ? 0 : BDOWN * bw) +
+ (spp == NULL || spn == NULL ||
+ spn->layout->last->pos != TBL_CELL_DOWN ?
+ BLEFT * hw : 0), 1);
+ (*tp->endline)(tp);
+ tp->viscol = 0;
+ }
+}
+
+static void
+tbl_data(struct termp *tp, const struct tbl_opts *opts,
+ const struct tbl_cell *cp, const struct tbl_dat *dp,
+ const struct roffcol *col)
+{
+ switch (cp->pos) {
+ case TBL_CELL_HORIZ:
+ tbl_fill_border(tp, BHORIZ, col->width);
+ return;
+ case TBL_CELL_DHORIZ:
+ tbl_fill_border(tp, BHORIZ * 2, col->width);
+ return;
+ default:
+ break;
+ }
+
+ if (dp == NULL)
+ return;
+
+ switch (dp->pos) {
+ case TBL_DATA_NONE:
+ return;
+ case TBL_DATA_HORIZ:
+ case TBL_DATA_NHORIZ:
+ tbl_fill_border(tp, BHORIZ, col->width);
+ return;
+ case TBL_DATA_NDHORIZ:
+ case TBL_DATA_DHORIZ:
+ tbl_fill_border(tp, BHORIZ * 2, col->width);
+ return;
+ default:
+ break;
+ }
+
+ switch (cp->pos) {
+ case TBL_CELL_LONG:
+ case TBL_CELL_CENTRE:
+ case TBL_CELL_LEFT:
+ 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:
+ case TBL_CELL_SPAN:
+ break;
+ default:
+ abort();
+ }
+}
+
+static void
+tbl_fill_string(struct termp *tp, const char *cp, size_t len)
+{
+ size_t i, sz;
+
+ sz = term_strlen(tp, cp);
+ for (i = 0; i < len; i += sz)
+ term_word(tp, cp);
+}
+
+static void
+tbl_fill_char(struct termp *tp, char c, size_t len)
+{
+ char cp[2];
+
+ cp[0] = c;
+ cp[1] = '\0';
+ tbl_fill_string(tp, cp, len);
+}
+
+static void
+tbl_fill_border(struct termp *tp, int c, size_t len)
+{
+ char buf[12];
+
+ if ((c = borders_locale[c]) > 127) {
+ (void)snprintf(buf, sizeof(buf), "\\[u%04x]", c);
+ tbl_fill_string(tp, buf, len);
+ } else
+ tbl_fill_char(tp, c, len);
+}
+
+static void
+tbl_direct_border(struct termp *tp, int c, size_t len)
+{
+ size_t i, sz;
+
+ c = borders_locale[c];
+ sz = (*tp->width)(tp, c);
+ for (i = 0; i < len; i += sz) {
+ (*tp->letter)(tp, c);
+ tp->viscol += sz;
+ }
+}
+
+static void
+tbl_literal(struct termp *tp, const struct tbl_dat *dp,
+ const struct roffcol *col)
+{
+ size_t len, padl, padr, width;
+ int ic, hspans;
+
+ assert(dp->string);
+ len = term_strlen(tp, dp->string);
+ width = col->width;
+ ic = dp->layout->col;
+ hspans = dp->hspans;
+ while (hspans--)
+ width += tp->tbl.cols[++ic].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_fill_char(tp, ASCII_NBRSP, padl);
+ tbl_word(tp, dp);
+ tbl_fill_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)
+{
+ const char *cp, *lastdigit, *lastpoint;
+ size_t intsz, padl, totsz;
+ char buf[2];
+
+ /*
+ * Almost the same code as in tblcalc_number():
+ * First find the position of the decimal point.
+ */
+
+ assert(dp->string);
+ lastdigit = lastpoint = NULL;
+ for (cp = dp->string; cp[0] != '\0'; cp++) {
+ if (cp[0] == '\\' && cp[1] == '&') {
+ lastdigit = lastpoint = cp;
+ break;
+ } else if (cp[0] == opts->decimal &&
+ (isdigit((unsigned char)cp[1]) ||
+ (cp > dp->string && isdigit((unsigned char)cp[-1]))))
+ lastpoint = cp;
+ else if (isdigit((unsigned char)cp[0]))
+ lastdigit = cp;
+ }
+
+ /* Then measure both widths. */
+
+ padl = 0;
+ totsz = term_strlen(tp, dp->string);
+ if (lastdigit != NULL) {
+ if (lastpoint == NULL)
+ lastpoint = lastdigit + 1;
+ intsz = 0;
+ buf[1] = '\0';
+ for (cp = dp->string; cp < lastpoint; cp++) {
+ buf[0] = cp[0];
+ intsz += term_strlen(tp, buf);
+ }
+
+ /*
+ * Pad left to match the decimal position,
+ * but avoid exceeding the total column width.
+ */
+
+ if (col->decimal > intsz && col->width > totsz) {
+ padl = col->decimal - intsz;
+ if (padl + totsz > col->width)
+ padl = col->width - totsz;
+ }
+
+ /* If it is not a number, simply center the string. */
+
+ } else if (col->width > totsz)
+ padl = (col->width - totsz) / 2;
+
+ tbl_fill_char(tp, ASCII_NBRSP, padl);
+ tbl_word(tp, dp);
+
+ /* Pad right to fill the column. */
+
+ if (col->width > padl + totsz)
+ tbl_fill_char(tp, ASCII_NBRSP, col->width - padl - totsz);
+}
+
+static void
+tbl_word(struct termp *tp, const struct tbl_dat *dp)
+{
+ int prev_font;
+
+ prev_font = tp->fonti;
+ if (dp->layout->flags & TBL_CELL_BOLD)
+ term_fontpush(tp, TERMFONT_BOLD);
+ else if (dp->layout->flags & TBL_CELL_ITALIC)
+ term_fontpush(tp, TERMFONT_UNDER);
+
+ term_word(tp, dp->string);
+
+ term_fontpopq(tp, prev_font);
+}
diff --git a/usr.bin/mandoc/term.c b/usr.bin/mandoc/term.c
new file mode 100644
index 0000000..68f78d7
--- /dev/null
+++ b/usr.bin/mandoc/term.c
@@ -0,0 +1,1112 @@
+/* $OpenBSD: term.c,v 1.141 2019/06/03 20:23:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010-2019 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.
+ */
+#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 "main.h"
+
+static size_t cond_width(const struct termp *, int, int *);
+static void adjbuf(struct termp_col *, size_t);
+static void bufferc(struct termp *, char);
+static void encode(struct termp *, const char *, size_t);
+static void encode1(struct termp *, int);
+static void endline(struct termp *);
+static void term_field(struct termp *, size_t, size_t,
+ size_t, size_t);
+static void term_fill(struct termp *, size_t *, size_t *,
+ size_t);
+
+
+void
+term_setcol(struct termp *p, size_t maxtcol)
+{
+ if (maxtcol > p->maxtcol) {
+ p->tcols = mandoc_recallocarray(p->tcols,
+ p->maxtcol, maxtcol, sizeof(*p->tcols));
+ p->maxtcol = maxtcol;
+ }
+ p->lasttcol = maxtcol - 1;
+ p->tcol = p->tcols;
+}
+
+void
+term_free(struct termp *p)
+{
+ for (p->tcol = p->tcols; p->tcol < p->tcols + p->maxtcol; p->tcol++)
+ free(p->tcol->buf);
+ free(p->tcols);
+ free(p->fontq);
+ free(p);
+}
+
+void
+term_begin(struct termp *p, term_margin head,
+ term_margin foot, const struct roff_meta *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. There are many flags modifying
+ * this behaviour, see the comments in the body of the function.
+ */
+void
+term_flushln(struct termp *p)
+{
+ size_t vbl; /* Number of blanks to prepend to the output. */
+ size_t vbr; /* Actual visual position of the end of field. */
+ size_t vfield; /* Desired visual field width. */
+ size_t vtarget; /* Desired visual position of the right margin. */
+ size_t ic; /* Character position in the input buffer. */
+ size_t nbr; /* Number of characters to print in this field. */
+
+ /*
+ * Normally, start writing at the left margin, but with the
+ * NOPAD flag, start writing at the current position instead.
+ */
+
+ vbl = (p->flags & TERMP_NOPAD) || p->tcol->offset < p->viscol ?
+ 0 : p->tcol->offset - p->viscol;
+ if (p->minbl && vbl < p->minbl)
+ vbl = p->minbl;
+
+ if ((p->flags & TERMP_MULTICOL) == 0)
+ p->tcol->col = 0;
+
+ /* Loop over output lines. */
+
+ for (;;) {
+ vfield = p->tcol->rmargin > p->viscol + vbl ?
+ p->tcol->rmargin - p->viscol - vbl : 0;
+
+ /*
+ * Normally, break the line at the the right margin
+ * of the field, but with the NOBREAK flag, only
+ * break it at the max right margin of the screen,
+ * and with the BRNEVER flag, never break it at all.
+ */
+
+ vtarget = p->flags & TERMP_BRNEVER ? SIZE_MAX :
+ (p->flags & TERMP_NOBREAK) == 0 ? vfield :
+ p->maxrmargin > p->viscol + vbl ?
+ p->maxrmargin - p->viscol - vbl : 0;
+
+ /*
+ * Figure out how much text will fit in the field.
+ * If there is whitespace only, print nothing.
+ */
+
+ term_fill(p, &nbr, &vbr, vtarget);
+ if (nbr == 0)
+ break;
+
+ /*
+ * With the CENTER or RIGHT flag, increase the indentation
+ * to center the text between the left and right margins
+ * or to adjust it to the right margin, respectively.
+ */
+
+ if (vbr < vtarget) {
+ if (p->flags & TERMP_CENTER)
+ vbl += (vtarget - vbr) / 2;
+ else if (p->flags & TERMP_RIGHT)
+ vbl += vtarget - vbr;
+ }
+
+ /* Finally, print the field content. */
+
+ term_field(p, vbl, nbr, vbr, vtarget);
+
+ /*
+ * If there is no text left in the field, exit the loop.
+ * If the BRTRSP flag is set, consider trailing
+ * whitespace significant when deciding whether
+ * the field fits or not.
+ */
+
+ for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
+ switch (p->tcol->buf[ic]) {
+ case '\t':
+ if (p->flags & TERMP_BRTRSP)
+ vbr = term_tab_next(vbr);
+ continue;
+ case ' ':
+ if (p->flags & TERMP_BRTRSP)
+ vbr += (*p->width)(p, ' ');
+ continue;
+ case '\n':
+ case ASCII_BREAK:
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ if (ic == p->tcol->lastcol)
+ break;
+
+ /*
+ * At the location of an automtic line break, input
+ * space characters are consumed by the line break.
+ */
+
+ while (p->tcol->col < p->tcol->lastcol &&
+ p->tcol->buf[p->tcol->col] == ' ')
+ p->tcol->col++;
+
+ /*
+ * In multi-column mode, leave the rest of the text
+ * in the buffer to be handled by a subsequent
+ * invocation, such that the other columns of the
+ * table can be handled first.
+ * In single-column mode, simply break the line.
+ */
+
+ if (p->flags & TERMP_MULTICOL)
+ return;
+
+ endline(p);
+ p->viscol = 0;
+
+ /*
+ * Normally, start the next line at the same indentation
+ * as this one, but with the BRIND flag, start it at the
+ * right margin instead. This is used together with
+ * NOBREAK for the tags in various kinds of tagged lists.
+ */
+
+ vbl = p->flags & TERMP_BRIND ?
+ p->tcol->rmargin : p->tcol->offset;
+ }
+
+ /* Reset output state in preparation for the next field. */
+
+ p->col = p->tcol->col = p->tcol->lastcol = 0;
+ p->minbl = p->trailspace;
+ p->flags &= ~(TERMP_BACKAFTER | TERMP_BACKBEFORE | TERMP_NOPAD);
+
+ if (p->flags & TERMP_MULTICOL)
+ return;
+
+ /*
+ * The HANG flag means that the next field
+ * always follows on the same line.
+ * The NOBREAK flag means that the next field
+ * follows on the same line unless the field was overrun.
+ * Normally, break the line at the end of each field.
+ */
+
+ if ((p->flags & TERMP_HANG) == 0 &&
+ ((p->flags & TERMP_NOBREAK) == 0 ||
+ vbr + term_len(p, p->trailspace) > vfield))
+ endline(p);
+}
+
+/*
+ * Store the number of input characters to print in this field in *nbr
+ * and their total visual width to print in *vbr.
+ * If there is only whitespace in the field, both remain zero.
+ * The desired visual width of the field is provided by vtarget.
+ * If the first word is longer, the field will be overrun.
+ */
+static void
+term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget)
+{
+ size_t ic; /* Character position in the input buffer. */
+ size_t vis; /* Visual position of the current character. */
+ size_t vn; /* Visual position of the next character. */
+ int breakline; /* Break at the end of this word. */
+ int graph; /* Last character was non-blank. */
+
+ *nbr = *vbr = vis = 0;
+ breakline = graph = 0;
+ for (ic = p->tcol->col; ic < p->tcol->lastcol; ic++) {
+ switch (p->tcol->buf[ic]) {
+ case '\b': /* Escape \o (overstrike) or backspace markup. */
+ assert(ic > 0);
+ vis -= (*p->width)(p, p->tcol->buf[ic - 1]);
+ continue;
+
+ case '\t': /* Normal ASCII whitespace. */
+ case ' ':
+ case ASCII_BREAK: /* Escape \: (breakpoint). */
+ switch (p->tcol->buf[ic]) {
+ case '\t':
+ vn = term_tab_next(vis);
+ break;
+ case ' ':
+ vn = vis + (*p->width)(p, ' ');
+ break;
+ case ASCII_BREAK:
+ vn = vis;
+ break;
+ default:
+ abort();
+ }
+ /* Can break at the end of a word. */
+ if (breakline || vn > vtarget)
+ break;
+ if (graph) {
+ *nbr = ic;
+ *vbr = vis;
+ graph = 0;
+ }
+ vis = vn;
+ continue;
+
+ case '\n': /* Escape \p (break at the end of the word). */
+ breakline = 1;
+ continue;
+
+ case ASCII_HYPH: /* Breakable hyphen. */
+ graph = 1;
+ /*
+ * We are about to decide whether to break the
+ * line or not, so we no longer need this hyphen
+ * to be marked as breakable. Put back a real
+ * hyphen such that we get the correct width.
+ */
+ p->tcol->buf[ic] = '-';
+ vis += (*p->width)(p, '-');
+ if (vis > vtarget) {
+ ic++;
+ break;
+ }
+ *nbr = ic + 1;
+ *vbr = vis;
+ continue;
+
+ case ASCII_NBRSP: /* Non-breakable space. */
+ p->tcol->buf[ic] = ' ';
+ /* FALLTHROUGH */
+ default: /* Printable character. */
+ graph = 1;
+ vis += (*p->width)(p, p->tcol->buf[ic]);
+ if (vis > vtarget && *nbr > 0)
+ return;
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * If the last word extends to the end of the field without any
+ * trailing whitespace, the loop could not check yet whether it
+ * can remain on this line. So do the check now.
+ */
+
+ if (graph && (vis <= vtarget || *nbr == 0)) {
+ *nbr = ic;
+ *vbr = vis;
+ }
+}
+
+/*
+ * Print the contents of one field
+ * with an indentation of vbl visual columns,
+ * an input string length of nbr characters,
+ * an output width of vbr visual columns,
+ * and a desired field width of vtarget visual columns.
+ */
+static void
+term_field(struct termp *p, size_t vbl, size_t nbr, size_t vbr, size_t vtarget)
+{
+ size_t ic; /* Character position in the input buffer. */
+ size_t vis; /* Visual position of the current character. */
+ size_t dv; /* Visual width of the current character. */
+ size_t vn; /* Visual position of the next character. */
+
+ vis = 0;
+ for (ic = p->tcol->col; ic < nbr; ic++) {
+
+ /*
+ * To avoid the printing of trailing whitespace,
+ * do not print whitespace right away, only count it.
+ */
+
+ switch (p->tcol->buf[ic]) {
+ case '\n':
+ case ASCII_BREAK:
+ continue;
+ case '\t':
+ vn = term_tab_next(vis);
+ vbl += vn - vis;
+ vis = vn;
+ continue;
+ case ' ':
+ case ASCII_NBRSP:
+ dv = (*p->width)(p, ' ');
+ vbl += dv;
+ vis += dv;
+ continue;
+ default:
+ break;
+ }
+
+ /*
+ * We found a non-blank character to print,
+ * so write preceding white space now.
+ */
+
+ if (vbl > 0) {
+ (*p->advance)(p, vbl);
+ p->viscol += vbl;
+ vbl = 0;
+ }
+
+ /* Print the character and adjust the visual position. */
+
+ (*p->letter)(p, p->tcol->buf[ic]);
+ if (p->tcol->buf[ic] == '\b') {
+ dv = (*p->width)(p, p->tcol->buf[ic - 1]);
+ p->viscol -= dv;
+ vis -= dv;
+ } else {
+ dv = (*p->width)(p, p->tcol->buf[ic]);
+ p->viscol += dv;
+ vis += dv;
+ }
+ }
+ p->tcol->col = nbr;
+}
+
+static void
+endline(struct termp *p)
+{
+ if ((p->flags & (TERMP_NEWMC | TERMP_ENDMC)) == TERMP_ENDMC) {
+ p->mc = NULL;
+ p->flags &= ~TERMP_ENDMC;
+ }
+ if (p->mc != NULL) {
+ if (p->viscol && p->maxrmargin >= p->viscol)
+ (*p->advance)(p, p->maxrmargin - p->viscol + 1);
+ p->flags |= TERMP_NOBUF | TERMP_NOSPACE;
+ term_word(p, p->mc);
+ p->flags &= ~(TERMP_NOBUF | TERMP_NEWMC);
+ }
+ p->viscol = 0;
+ p->minbl = 0;
+ (*p->endline)(p);
+}
+
+/*
+ * 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->tcol->lastcol || 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;
+ p->minbl = 0;
+ if (0 < p->skipvsp)
+ p->skipvsp--;
+ else
+ (*p->endline)(p);
+}
+
+/* Swap current and previous font; for \fP and .ft P */
+void
+term_fontlast(struct termp *p)
+{
+ enum termfont f;
+
+ f = p->fontl;
+ p->fontl = p->fontq[p->fonti];
+ p->fontq[p->fonti] = f;
+}
+
+/* Set font, save current, discard previous; for \f, .ft, .B etc. */
+void
+term_fontrepl(struct termp *p, enum termfont f)
+{
+
+ p->fontl = p->fontq[p->fonti];
+ p->fontq[p->fonti] = f;
+}
+
+/* Set font, save previous. */
+void
+term_fontpush(struct termp *p, enum termfont f)
+{
+
+ p->fontl = p->fontq[p->fonti];
+ if (++p->fonti == p->fontsz) {
+ p->fontsz += 8;
+ p->fontq = mandoc_reallocarray(p->fontq,
+ p->fontsz, sizeof(*p->fontq));
+ }
+ p->fontq[p->fonti] = f;
+}
+
+/* Flush to make the saved pointer current again. */
+void
+term_fontpopq(struct termp *p, int i)
+{
+
+ assert(i >= 0);
+ if (p->fonti > i)
+ p->fonti = i;
+}
+
+/* Pop one font off the stack. */
+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)
+{
+ struct roffsu su;
+ const char nbrsp[2] = { ASCII_NBRSP, 0 };
+ const char *seq, *cp;
+ int sz, uc;
+ size_t csz, lsz, ssz;
+ enum mandoc_esc esc;
+
+ if ((p->flags & TERMP_NOBUF) == 0) {
+ if ((p->flags & TERMP_NOSPACE) == 0) {
+ if ((p->flags & TERMP_KEEP) == 0) {
+ bufferc(p, ' ');
+ if (p->flags & TERMP_SENTENCE)
+ bufferc(p, ' ');
+ } else
+ bufferc(p, ASCII_NBRSP);
+ }
+ if (p->flags & TERMP_PREKEEP)
+ p->flags |= TERMP_KEEP;
+ if (p->flags & TERMP_NONOSPACE)
+ p->flags |= TERMP_NOSPACE;
+ else
+ p->flags &= ~TERMP_NOSPACE;
+ p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
+ p->skipvsp = 0;
+ }
+
+ while ('\0' != *word) {
+ if ('\\' != *word) {
+ 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);
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ uc = mchars_num2uc(seq + 1, sz - 1);
+ break;
+ case ESCAPE_NUMBERED:
+ uc = mchars_num2char(seq, sz);
+ if (uc < 0)
+ continue;
+ break;
+ case ESCAPE_SPECIAL:
+ if (p->enc == TERMENC_ASCII) {
+ cp = mchars_spec2str(seq, sz, &ssz);
+ if (cp != NULL)
+ encode(p, cp, ssz);
+ } else {
+ uc = mchars_spec2cp(seq, sz);
+ if (uc > 0)
+ encode1(p, uc);
+ }
+ continue;
+ case ESCAPE_UNDEF:
+ uc = *seq;
+ break;
+ case ESCAPE_FONTBOLD:
+ term_fontrepl(p, TERMFONT_BOLD);
+ continue;
+ case ESCAPE_FONTITALIC:
+ term_fontrepl(p, TERMFONT_UNDER);
+ continue;
+ case ESCAPE_FONTBI:
+ term_fontrepl(p, TERMFONT_BI);
+ continue;
+ case ESCAPE_FONT:
+ case ESCAPE_FONTCW:
+ case ESCAPE_FONTROMAN:
+ term_fontrepl(p, TERMFONT_NONE);
+ continue;
+ case ESCAPE_FONTPREV:
+ term_fontlast(p);
+ continue;
+ case ESCAPE_BREAK:
+ bufferc(p, '\n');
+ continue;
+ case ESCAPE_NOSPACE:
+ if (p->flags & TERMP_BACKAFTER)
+ p->flags &= ~TERMP_BACKAFTER;
+ else if (*word == '\0')
+ p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
+ continue;
+ case ESCAPE_DEVICE:
+ if (p->type == TERMTYPE_PDF)
+ encode(p, "pdf", 3);
+ else if (p->type == TERMTYPE_PS)
+ encode(p, "ps", 2);
+ else if (p->enc == TERMENC_ASCII)
+ encode(p, "ascii", 5);
+ else
+ encode(p, "utf8", 4);
+ continue;
+ case ESCAPE_HORIZ:
+ if (*seq == '|') {
+ seq++;
+ uc = -p->col;
+ } else
+ uc = 0;
+ if (a2roffsu(seq, &su, SCALE_EM) == NULL)
+ continue;
+ uc += term_hen(p, &su);
+ if (uc > 0)
+ while (uc-- > 0)
+ bufferc(p, ASCII_NBRSP);
+ else if (p->col > (size_t)(-uc))
+ p->col += uc;
+ else {
+ uc += p->col;
+ p->col = 0;
+ if (p->tcol->offset > (size_t)(-uc)) {
+ p->ti += uc;
+ p->tcol->offset += uc;
+ } else {
+ p->ti -= p->tcol->offset;
+ p->tcol->offset = 0;
+ }
+ }
+ continue;
+ case ESCAPE_HLINE:
+ if ((cp = a2roffsu(seq, &su, SCALE_EM)) == NULL)
+ continue;
+ uc = term_hen(p, &su);
+ if (uc <= 0) {
+ if (p->tcol->rmargin <= p->tcol->offset)
+ continue;
+ lsz = p->tcol->rmargin - p->tcol->offset;
+ } else
+ lsz = uc;
+ if (*cp == seq[-1])
+ uc = -1;
+ else if (*cp == '\\') {
+ seq = cp + 1;
+ esc = mandoc_escape(&seq, &cp, &sz);
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ uc = mchars_num2uc(cp + 1, sz - 1);
+ break;
+ case ESCAPE_NUMBERED:
+ uc = mchars_num2char(cp, sz);
+ break;
+ case ESCAPE_SPECIAL:
+ uc = mchars_spec2cp(cp, sz);
+ break;
+ case ESCAPE_UNDEF:
+ uc = *seq;
+ break;
+ default:
+ uc = -1;
+ break;
+ }
+ } else
+ uc = *cp;
+ if (uc < 0x20 || (uc > 0x7E && uc < 0xA0))
+ uc = '_';
+ if (p->enc == TERMENC_ASCII) {
+ cp = ascii_uc2str(uc);
+ csz = term_strlen(p, cp);
+ ssz = strlen(cp);
+ } else
+ csz = (*p->width)(p, uc);
+ while (lsz >= csz) {
+ if (p->enc == TERMENC_ASCII)
+ encode(p, cp, ssz);
+ else
+ encode1(p, uc);
+ lsz -= csz;
+ }
+ continue;
+ case ESCAPE_SKIPCHAR:
+ p->flags |= TERMP_BACKAFTER;
+ continue;
+ case ESCAPE_OVERSTRIKE:
+ cp = seq + sz;
+ while (seq < cp) {
+ if (*seq == '\\') {
+ mandoc_escape(&seq, NULL, NULL);
+ continue;
+ }
+ encode1(p, *seq++);
+ if (seq < cp) {
+ if (p->flags & TERMP_BACKBEFORE)
+ p->flags |= TERMP_BACKAFTER;
+ else
+ p->flags |= TERMP_BACKBEFORE;
+ }
+ }
+ /* Trim trailing backspace/blank pair. */
+ if (p->tcol->lastcol > 2 &&
+ (p->tcol->buf[p->tcol->lastcol - 1] == ' ' ||
+ p->tcol->buf[p->tcol->lastcol - 1] == '\t'))
+ p->tcol->lastcol -= 2;
+ if (p->col > p->tcol->lastcol)
+ p->col = p->tcol->lastcol;
+ continue;
+ default:
+ continue;
+ }
+
+ /*
+ * Common handling for Unicode and numbered
+ * character escape sequences.
+ */
+
+ if (p->enc == TERMENC_ASCII) {
+ cp = ascii_uc2str(uc);
+ encode(p, cp, strlen(cp));
+ } else {
+ if ((uc < 0x20 && uc != 0x09) ||
+ (uc > 0x7E && uc < 0xA0))
+ uc = 0xFFFD;
+ encode1(p, uc);
+ }
+ }
+ p->flags &= ~TERMP_NBRWORD;
+}
+
+static void
+adjbuf(struct termp_col *c, size_t sz)
+{
+ if (c->maxcols == 0)
+ c->maxcols = 1024;
+ while (c->maxcols <= sz)
+ c->maxcols <<= 2;
+ c->buf = mandoc_reallocarray(c->buf, c->maxcols, sizeof(*c->buf));
+}
+
+static void
+bufferc(struct termp *p, char c)
+{
+ if (p->flags & TERMP_NOBUF) {
+ (*p->letter)(p, c);
+ return;
+ }
+ if (p->col + 1 >= p->tcol->maxcols)
+ adjbuf(p->tcol, p->col + 1);
+ if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
+ p->tcol->buf[p->col] = c;
+ if (p->tcol->lastcol < ++p->col)
+ p->tcol->lastcol = p->col;
+}
+
+/*
+ * 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 (p->flags & TERMP_NOBUF) {
+ (*p->letter)(p, c);
+ return;
+ }
+
+ if (p->col + 7 >= p->tcol->maxcols)
+ adjbuf(p->tcol, p->col + 7);
+
+ f = (c == ASCII_HYPH || c > 127 || isgraph(c)) ?
+ p->fontq[p->fonti] : TERMFONT_NONE;
+
+ if (p->flags & TERMP_BACKBEFORE) {
+ if (p->tcol->buf[p->col - 1] == ' ' ||
+ p->tcol->buf[p->col - 1] == '\t')
+ p->col--;
+ else
+ p->tcol->buf[p->col++] = '\b';
+ p->flags &= ~TERMP_BACKBEFORE;
+ }
+ if (f == TERMFONT_UNDER || f == TERMFONT_BI) {
+ p->tcol->buf[p->col++] = '_';
+ p->tcol->buf[p->col++] = '\b';
+ }
+ if (f == TERMFONT_BOLD || f == TERMFONT_BI) {
+ if (c == ASCII_HYPH)
+ p->tcol->buf[p->col++] = '-';
+ else
+ p->tcol->buf[p->col++] = c;
+ p->tcol->buf[p->col++] = '\b';
+ }
+ if (p->tcol->lastcol <= p->col || (c != ' ' && c != ASCII_NBRSP))
+ p->tcol->buf[p->col] = c;
+ if (p->tcol->lastcol < ++p->col)
+ p->tcol->lastcol = p->col;
+ if (p->flags & TERMP_BACKAFTER) {
+ p->flags |= TERMP_BACKBEFORE;
+ p->flags &= ~TERMP_BACKAFTER;
+ }
+}
+
+static void
+encode(struct termp *p, const char *word, size_t sz)
+{
+ size_t i;
+
+ if (p->flags & TERMP_NOBUF) {
+ for (i = 0; i < sz; i++)
+ (*p->letter)(p, word[i]);
+ return;
+ }
+
+ if (p->col + 2 + (sz * 5) >= p->tcol->maxcols)
+ adjbuf(p->tcol, p->col + 2 + (sz * 5));
+
+ for (i = 0; i < sz; i++) {
+ if (ASCII_HYPH == word[i] ||
+ isgraph((unsigned char)word[i]))
+ encode1(p, word[i]);
+ else {
+ if (p->tcol->lastcol <= p->col ||
+ (word[i] != ' ' && word[i] != ASCII_NBRSP))
+ p->tcol->buf[p->col] = word[i];
+ p->col++;
+
+ /*
+ * Postpone the effect of \z while handling
+ * an overstrike sequence from ascii_uc2str().
+ */
+
+ if (word[i] == '\b' &&
+ (p->flags & TERMP_BACKBEFORE)) {
+ p->flags &= ~TERMP_BACKBEFORE;
+ p->flags |= TERMP_BACKAFTER;
+ }
+ }
+ }
+ if (p->tcol->lastcol < p->col)
+ p->tcol->lastcol = p->col;
+}
+
+void
+term_setwidth(struct termp *p, const char *wstr)
+{
+ struct roffsu su;
+ int iop, width;
+
+ 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) != NULL)
+ 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, uc;
+ 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++;
+ rhs = NULL;
+ esc = mandoc_escape(&cp, &seq, &ssz);
+ switch (esc) {
+ case ESCAPE_UNICODE:
+ uc = mchars_num2uc(seq + 1, ssz - 1);
+ break;
+ case ESCAPE_NUMBERED:
+ uc = mchars_num2char(seq, ssz);
+ if (uc < 0)
+ continue;
+ break;
+ case ESCAPE_SPECIAL:
+ if (p->enc == TERMENC_ASCII) {
+ rhs = mchars_spec2str(seq, ssz, &rsz);
+ if (rhs != NULL)
+ break;
+ } else {
+ uc = mchars_spec2cp(seq, ssz);
+ if (uc > 0)
+ sz += cond_width(p, uc, &skip);
+ }
+ continue;
+ case ESCAPE_UNDEF:
+ uc = *seq;
+ break;
+ case ESCAPE_DEVICE:
+ if (p->type == TERMTYPE_PDF) {
+ rhs = "pdf";
+ rsz = 3;
+ } else if (p->type == TERMTYPE_PS) {
+ rhs = "ps";
+ rsz = 2;
+ } else if (p->enc == TERMENC_ASCII) {
+ rhs = "ascii";
+ rsz = 5;
+ } else {
+ rhs = "utf8";
+ rsz = 4;
+ }
+ break;
+ case ESCAPE_SKIPCHAR:
+ skip = 1;
+ continue;
+ case ESCAPE_OVERSTRIKE:
+ rsz = 0;
+ rhs = seq + ssz;
+ while (seq < rhs) {
+ if (*seq == '\\') {
+ mandoc_escape(&seq, NULL, NULL);
+ continue;
+ }
+ i = (*p->width)(p, *seq++);
+ if (rsz < i)
+ rsz = i;
+ }
+ sz += rsz;
+ continue;
+ default:
+ continue;
+ }
+
+ /*
+ * Common handling for Unicode and numbered
+ * character escape sequences.
+ */
+
+ if (rhs == NULL) {
+ if (p->enc == TERMENC_ASCII) {
+ rhs = ascii_uc2str(uc);
+ rsz = strlen(rhs);
+ } else {
+ if ((uc < 0x20 && uc != 0x09) ||
+ (uc > 0x7E && uc < 0xA0))
+ uc = 0xFFFD;
+ sz += cond_width(p, uc, &skip);
+ continue;
+ }
+ }
+
+ if (skip) {
+ skip = 0;
+ break;
+ }
+
+ /*
+ * Common handling for all escape sequences
+ * printing more than one character.
+ */
+
+ 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++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return sz;
+}
+
+int
+term_vspan(const struct termp *p, const struct roffsu *su)
+{
+ double r;
+ int ri;
+
+ switch (su->unit) {
+ case SCALE_BU:
+ r = su->scale / 40.0;
+ break;
+ case SCALE_CM:
+ r = su->scale * 6.0 / 2.54;
+ break;
+ case SCALE_FS:
+ r = su->scale * 65536.0 / 40.0;
+ break;
+ case SCALE_IN:
+ r = su->scale * 6.0;
+ break;
+ case SCALE_MM:
+ r = su->scale * 0.006;
+ break;
+ case SCALE_PC:
+ r = su->scale;
+ break;
+ case SCALE_PT:
+ r = su->scale / 12.0;
+ break;
+ case SCALE_EN:
+ case SCALE_EM:
+ r = su->scale * 0.6;
+ break;
+ case SCALE_VS:
+ r = su->scale;
+ break;
+ default:
+ abort();
+ }
+ ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
+ return ri < 66 ? ri : 1;
+}
+
+/*
+ * Convert a scaling width to basic units, rounding towards 0.
+ */
+int
+term_hspan(const struct termp *p, const struct roffsu *su)
+{
+
+ return (*p->hspan)(p, su);
+}
+
+/*
+ * Convert a scaling width to basic units, rounding to closest.
+ */
+int
+term_hen(const struct termp *p, const struct roffsu *su)
+{
+ int bu;
+
+ if ((bu = (*p->hspan)(p, su)) >= 0)
+ return (bu + 11) / 24;
+ else
+ return -((-bu + 11) / 24);
+}
diff --git a/usr.bin/mandoc/term.h b/usr.bin/mandoc/term.h
new file mode 100644
index 0000000..525aa3f
--- /dev/null
+++ b/usr.bin/mandoc/term.h
@@ -0,0 +1,158 @@
+/* $OpenBSD: term.h,v 1.75 2019/01/04 03:20:44 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011-2015, 2017, 2019 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.
+ */
+
+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
+};
+
+struct eqn_box;
+struct roff_meta;
+struct roff_node;
+struct tbl_span;
+struct termp;
+
+typedef void (*term_margin)(struct termp *, const struct roff_meta *);
+
+struct termp_tbl {
+ int width; /* width in fixed chars */
+ int decimal; /* decimal point position */
+};
+
+struct termp_col {
+ int *buf; /* Output buffer. */
+ size_t maxcols; /* Allocated bytes in buf. */
+ size_t lastcol; /* Last byte in buf. */
+ size_t col; /* Byte in buf to be written. */
+ size_t rmargin; /* Current right margin. */
+ size_t offset; /* Current left margin. */
+};
+
+struct termp {
+ struct rofftbl tbl; /* Table configuration. */
+ struct termp_col *tcols; /* Array of table columns. */
+ struct termp_col *tcol; /* Current table column. */
+ size_t maxtcol; /* Allocated table columns. */
+ size_t lasttcol; /* Last column currently used. */
+ size_t line; /* Current output line number. */
+ 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 maxrmargin; /* Max right margin. */
+ size_t col; /* Byte position in buf. */
+ size_t viscol; /* Chars on current line. */
+ size_t trailspace; /* See term_flushln(). */
+ size_t minbl; /* Minimum blanks before next field. */
+ int synopsisonly; /* Print the synopsis only. */
+ int mdocstyle; /* Imitate mdoc(7) output. */
+ int ti; /* Temporary indent for one line. */
+ int skipvsp; /* Vertical space to skip. */
+ int flags;
+#define TERMP_SENTENCE (1 << 0) /* Space before a sentence. */
+#define TERMP_NOSPACE (1 << 1) /* No space before words. */
+#define TERMP_NONOSPACE (1 << 2) /* No space (no autounset). */
+#define TERMP_NBRWORD (1 << 3) /* Make next word nonbreaking. */
+#define TERMP_KEEP (1 << 4) /* Keep words together. */
+#define TERMP_PREKEEP (1 << 5) /* ...starting with the next one. */
+#define TERMP_BACKAFTER (1 << 6) /* Back up after next character. */
+#define TERMP_BACKBEFORE (1 << 7) /* Back up before next character. */
+#define TERMP_NOBREAK (1 << 8) /* See term_flushln(). */
+#define TERMP_BRTRSP (1 << 9) /* See term_flushln(). */
+#define TERMP_BRIND (1 << 10) /* See term_flushln(). */
+#define TERMP_HANG (1 << 11) /* See term_flushln(). */
+#define TERMP_NOPAD (1 << 12) /* See term_flushln(). */
+#define TERMP_NOSPLIT (1 << 13) /* Do not break line before .An. */
+#define TERMP_SPLIT (1 << 14) /* Break line before .An. */
+#define TERMP_NONEWLINE (1 << 15) /* No line break in nofill mode. */
+#define TERMP_BRNEVER (1 << 16) /* Don't even break at maxrmargin. */
+#define TERMP_NOBUF (1 << 17) /* Bypass output buffer. */
+#define TERMP_NEWMC (1 << 18) /* No .mc printed yet. */
+#define TERMP_ENDMC (1 << 19) /* Next break ends .mc mode. */
+#define TERMP_MULTICOL (1 << 20) /* Multiple column mode. */
+#define TERMP_CENTER (1 << 21) /* Center output lines. */
+#define TERMP_RIGHT (1 << 22) /* Adjust to the right margin. */
+ enum termtype type; /* Terminal, PS, or PDF. */
+ enum termenc enc; /* Type of encoding. */
+ enum termfont fontl; /* Last font set. */
+ enum termfont *fontq; /* Symmetric fonts. */
+ int fontsz; /* Allocated size of font stack */
+ 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, int);
+ size_t (*width)(const struct termp *, int);
+ int (*hspan)(const struct termp *,
+ const struct roffsu *);
+ const void *argf; /* arg for headf/footf */
+ const char *mc; /* Margin character. */
+ struct termp_ps *ps;
+};
+
+
+const char *ascii_uc2str(int);
+
+void roff_term_pre(struct termp *, const struct roff_node *);
+
+void term_eqn(struct termp *, const struct eqn_box *);
+void term_tbl(struct termp *, const struct tbl_span *);
+void term_free(struct termp *);
+void term_setcol(struct termp *, size_t);
+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 struct roff_meta *);
+void term_end(struct termp *);
+
+void term_setwidth(struct termp *, const char *);
+int term_hspan(const struct termp *, const struct roffsu *);
+int term_hen(const struct termp *, const struct roffsu *);
+int 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);
+
+void term_tab_set(const struct termp *, const char *);
+void term_tab_iset(size_t);
+size_t term_tab_next(size_t);
+
+void term_fontpush(struct termp *, enum termfont);
+void term_fontpop(struct termp *);
+void term_fontpopq(struct termp *, int);
+void term_fontrepl(struct termp *, enum termfont);
+void term_fontlast(struct termp *);
diff --git a/usr.bin/mandoc/term_ascii.c b/usr.bin/mandoc/term_ascii.c
new file mode 100644
index 0000000..9b28060
--- /dev/null
+++ b/usr.bin/mandoc/term_ascii.c
@@ -0,0 +1,392 @@
+/* $OpenBSD: term_ascii.c,v 1.50 2019/07/19 21:45:37 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2017, 2018 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "out.h"
+#include "term.h"
+#include "manconf.h"
+#include "main.h"
+
+#define UTF8_LOCALE "en_US.UTF-8"
+
+static struct termp *ascii_init(enum termenc, const struct manoutput *);
+static int 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, int);
+
+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);
+
+
+static struct termp *
+ascii_init(enum termenc enc, const struct manoutput *outopts)
+{
+ char *v;
+ struct termp *p;
+
+ p = mandoc_calloc(1, sizeof(*p));
+ p->tcol = p->tcols = mandoc_calloc(1, sizeof(*p->tcol));
+ p->maxtcol = 1;
+
+ p->line = 1;
+ p->defrmargin = p->lastrmargin = 78;
+ p->fontq = mandoc_reallocarray(NULL,
+ (p->fontsz = 8), sizeof(*p->fontq));
+ p->fontq[0] = p->fontl = TERMFONT_NONE;
+
+ 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;
+
+ if (enc != TERMENC_ASCII) {
+
+ /*
+ * Do not change any of this to LC_ALL. It might break
+ * the formatting by subtly changing the behaviour of
+ * various functions, for example strftime(3). As a
+ * worst case, it might even cause buffer overflows.
+ */
+
+ v = enc == TERMENC_LOCALE ?
+ setlocale(LC_CTYPE, "") :
+ setlocale(LC_CTYPE, UTF8_LOCALE);
+
+ /*
+ * We only support UTF-8,
+ * so revert to ASCII for anything else.
+ */
+
+ if (v != NULL &&
+ strcmp(nl_langinfo(CODESET), "UTF-8") != 0)
+ v = setlocale(LC_CTYPE, "C");
+
+ if (v != NULL && MB_CUR_MAX > 1) {
+ p->enc = TERMENC_UTF8;
+ p->advance = locale_advance;
+ p->endline = locale_endline;
+ p->letter = locale_letter;
+ p->width = locale_width;
+ }
+ }
+
+ if (outopts->mdoc) {
+ p->mdocstyle = 1;
+ p->defindent = 5;
+ }
+ if (outopts->indent)
+ p->defindent = outopts->indent;
+ if (outopts->width)
+ p->defrmargin = outopts->width;
+ if (outopts->synopsisonly)
+ p->synopsisonly = 1;
+
+ assert(p->defindent < UINT16_MAX);
+ assert(p->defrmargin < UINT16_MAX);
+ return p;
+}
+
+void *
+ascii_alloc(const struct manoutput *outopts)
+{
+
+ return ascii_init(TERMENC_ASCII, outopts);
+}
+
+void *
+utf8_alloc(const struct manoutput *outopts)
+{
+
+ return ascii_init(TERMENC_UTF8, outopts);
+}
+
+void *
+locale_alloc(const struct manoutput *outopts)
+{
+
+ return ascii_init(TERMENC_LOCALE, outopts);
+}
+
+static void
+ascii_setwidth(struct termp *p, int iop, int width)
+{
+
+ width /= 24;
+ p->tcol->rmargin = p->defrmargin;
+ if (iop > 0)
+ p->defrmargin += width;
+ else if (iop == 0)
+ p->defrmargin = width ? (size_t)width : p->lastrmargin;
+ else if (p->defrmargin > (size_t)width)
+ p->defrmargin -= width;
+ else
+ p->defrmargin = 0;
+ if (p->defrmargin > 1000)
+ p->defrmargin = 1000;
+ p->lastrmargin = p->tcol->rmargin;
+ p->tcol->rmargin = p->maxrmargin = p->defrmargin;
+}
+
+void
+terminal_sepline(void *arg)
+{
+ struct termp *p;
+ size_t i;
+
+ p = (struct termp *)arg;
+ (*p->endline)(p);
+ for (i = 0; i < p->defrmargin; i++)
+ (*p->letter)(p, '-');
+ (*p->endline)(p);
+ (*p->endline)(p);
+}
+
+static size_t
+ascii_width(const struct termp *p, int c)
+{
+ return c != ASCII_BREAK;
+}
+
+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)
+{
+
+ p->line++;
+ p->tcol->offset -= p->ti;
+ p->ti = 0;
+ putchar('\n');
+}
+
+static void
+ascii_advance(struct termp *p, size_t len)
+{
+ size_t i;
+
+ assert(len < UINT16_MAX);
+ for (i = 0; i < len; i++)
+ putchar(' ');
+}
+
+static int
+ascii_hspan(const struct termp *p, const struct roffsu *su)
+{
+ double r;
+
+ switch (su->unit) {
+ case SCALE_BU:
+ r = su->scale;
+ break;
+ case SCALE_CM:
+ r = su->scale * 240.0 / 2.54;
+ break;
+ case SCALE_FS:
+ r = su->scale * 65536.0;
+ break;
+ case SCALE_IN:
+ r = su->scale * 240.0;
+ break;
+ case SCALE_MM:
+ r = su->scale * 0.24;
+ break;
+ case SCALE_VS:
+ case SCALE_PC:
+ r = su->scale * 40.0;
+ break;
+ case SCALE_PT:
+ r = su->scale * 10.0 / 3.0;
+ break;
+ case SCALE_EN:
+ case SCALE_EM:
+ r = su->scale * 24.0;
+ break;
+ default:
+ abort();
+ }
+ return r > 0.0 ? r + 0.01 : r - 0.01;
+}
+
+const char *
+ascii_uc2str(int uc)
+{
+ static const char nbrsp[2] = { ASCII_NBRSP, '\0' };
+ static const char *tab[] = {
+ "<NUL>","<SOH>","<STX>","<ETX>","<EOT>","<ENQ>","<ACK>","<BEL>",
+ "<BS>", "\t", "<LF>", "<VT>", "<FF>", "<CR>", "<SO>", "<SI>",
+ "<DLE>","<DC1>","<DC2>","<DC3>","<DC4>","<NAK>","<SYN>","<ETB>",
+ "<CAN>","<EM>", "<SUB>","<ESC>","<FS>", "<GS>", "<RS>", "<US>",
+ " ", "!", "\"", "#", "$", "%", "&", "'",
+ "(", ")", "*", "+", ",", "-", ".", "/",
+ "0", "1", "2", "3", "4", "5", "6", "7",
+ "8", "9", ":", ";", "<", "=", ">", "?",
+ "@", "A", "B", "C", "D", "E", "F", "G",
+ "H", "I", "J", "K", "L", "M", "N", "O",
+ "P", "Q", "R", "S", "T", "U", "V", "W",
+ "X", "Y", "Z", "[", "\\", "]", "^", "_",
+ "`", "a", "b", "c", "d", "e", "f", "g",
+ "h", "i", "j", "k", "l", "m", "n", "o",
+ "p", "q", "r", "s", "t", "u", "v", "w",
+ "x", "y", "z", "{", "|", "}", "~", "<DEL>",
+ "<80>", "<81>", "<82>", "<83>", "<84>", "<85>", "<86>", "<87>",
+ "<88>", "<89>", "<8A>", "<8B>", "<8C>", "<8D>", "<8E>", "<8F>",
+ "<90>", "<91>", "<92>", "<93>", "<94>", "<95>", "<96>", "<97>",
+ "<98>", "<99>", "<9A>", "<9B>", "<9C>", "<9D>", "<9E>", "<9F>",
+ nbrsp, "!", "/\bc", "-\bL", "o\bx", "=\bY", "|", "<section>",
+ "\"", "(C)", "_\ba", "<<", "~", "", "(R)", "-",
+ "<degree>","+-","^2", "^3", "'","<micro>","<paragraph>",".",
+ ",", "^1", "_\bo", ">>", "1/4", "1/2", "3/4", "?",
+ "`\bA", "'\bA", "^\bA", "~\bA", "\"\bA","o\bA", "AE", ",\bC",
+ "`\bE", "'\bE", "^\bE", "\"\bE","`\bI", "'\bI", "^\bI", "\"\bI",
+ "Dh", "~\bN", "`\bO", "'\bO", "^\bO", "~\bO", "\"\bO","x",
+ "/\bO", "`\bU", "'\bU", "^\bU", "\"\bU","'\bY", "Th", "ss",
+ "`\ba", "'\ba", "^\ba", "~\ba", "\"\ba","o\ba", "ae", ",\bc",
+ "`\be", "'\be", "^\be", "\"\be","`\bi", "'\bi", "^\bi", "\"\bi",
+ "dh", "~\bn", "`\bo", "'\bo", "^\bo", "~\bo", "\"\bo","/",
+ "/\bo", "`\bu", "'\bu", "^\bu", "\"\bu","'\by", "th", "\"\by",
+ "A", "a", "A", "a", "A", "a", "'\bC", "'\bc",
+ "^\bC", "^\bc", "C", "c", "C", "c", "D", "d",
+ "/\bD", "/\bd", "E", "e", "E", "e", "E", "e",
+ "E", "e", "E", "e", "^\bG", "^\bg", "G", "g",
+ "G", "g", ",\bG", ",\bg", "^\bH", "^\bh", "/\bH", "/\bh",
+ "~\bI", "~\bi", "I", "i", "I", "i", "I", "i",
+ "I", "i", "IJ", "ij", "^\bJ", "^\bj", ",\bK", ",\bk",
+ "q", "'\bL", "'\bl", ",\bL", ",\bl", "L", "l", "L",
+ "l", "/\bL", "/\bl", "'\bN", "'\bn", ",\bN", ",\bn", "N",
+ "n", "'n", "Ng", "ng", "O", "o", "O", "o",
+ "O", "o", "OE", "oe", "'\bR", "'\br", ",\bR", ",\br",
+ "R", "r", "'\bS", "'\bs", "^\bS", "^\bs", ",\bS", ",\bs",
+ "S", "s", ",\bT", ",\bt", "T", "t", "/\bT", "/\bt",
+ "~\bU", "~\bu", "U", "u", "U", "u", "U", "u",
+ "U", "u", "U", "u", "^\bW", "^\bw", "^\bY", "^\by",
+ "\"\bY","'\bZ", "'\bz", "Z", "z", "Z", "z", "s",
+ "b", "B", "B", "b", "6", "6", "O", "C",
+ "c", "D", "D", "D", "d", "d", "3", "@",
+ "E", "F", ",\bf", "G", "G", "hv", "I", "/\bI",
+ "K", "k", "/\bl", "l", "W", "N", "n", "~\bO",
+ "O", "o", "OI", "oi", "P", "p", "YR", "2",
+ "2", "SH", "sh", "t", "T", "t", "T", "U",
+ "u", "Y", "V", "Y", "y", "/\bZ", "/\bz", "ZH",
+ "ZH", "zh", "zh", "/\b2", "5", "5", "ts", "w",
+ "|", "||", "|=", "!", "DZ", "Dz", "dz", "LJ",
+ "Lj", "lj", "NJ", "Nj", "nj", "A", "a", "I",
+ "i", "O", "o", "U", "u", "U", "u", "U",
+ "u", "U", "u", "U", "u", "@", "A", "a",
+ "A", "a", "AE", "ae", "/\bG", "/\bg", "G", "g",
+ "K", "k", "O", "o", "O", "o", "ZH", "zh",
+ "j", "DZ", "Dz", "dz", "'\bG", "'\bg", "HV", "W",
+ "`\bN", "`\bn", "A", "a", "'\bAE","'\bae","O", "o"};
+
+ assert(uc >= 0);
+ if ((size_t)uc < sizeof(tab)/sizeof(tab[0]))
+ return tab[uc];
+ return mchars_uc2str(uc);
+}
+
+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;
+
+ assert(len < UINT16_MAX);
+ for (i = 0; i < len; i++)
+ putwchar(L' ');
+}
+
+static void
+locale_endline(struct termp *p)
+{
+
+ p->line++;
+ p->tcol->offset -= p->ti;
+ p->ti = 0;
+ putwchar(L'\n');
+}
+
+static void
+locale_letter(struct termp *p, int c)
+{
+
+ putwchar(c);
+}
diff --git a/usr.bin/mandoc/term_ps.c b/usr.bin/mandoc/term_ps.c
new file mode 100644
index 0000000..9460c88
--- /dev/null
+++ b/usr.bin/mandoc/term_ps.c
@@ -0,0 +1,1355 @@
+/* $OpenBSD: term_ps.c,v 1.55 2017/11/10 14:16:28 espie Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2017 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 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.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "out.h"
+#include "term.h"
+#include "manconf.h"
+#include "main.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 */
+#define PS_BACKSP (1 << 3) /* last character was backspace */
+ size_t pscol; /* visible column (AFM units) */
+ size_t pscolnext; /* used for overstrike */
+ size_t psrow; /* visible row (AFM units) */
+ size_t lastrow; /* psrow of the previous word */
+ char *psmarg; /* margin buf */
+ size_t psmargsz; /* margin buf size */
+ size_t psmargcur; /* cur index in margin buf */
+ char last; /* last non-backspace seen */
+ enum termfont lastf; /* last set font */
+ enum termfont nextf; /* building next font here */
+ 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) */
+ const char *medianame; /* for DocumentMedia and PageSize */
+ 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 int 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_growbuf(struct termp *, size_t);
+static void ps_letter(struct termp *, int);
+static void ps_pclose(struct termp *);
+static void ps_plast(struct termp *);
+static void ps_pletter(struct termp *, int);
+static void ps_printf(struct termp *, const char *, ...)
+ __attribute__((__format__ (__printf__, 2, 3)));
+static void ps_putchar(struct termp *, char);
+static void ps_setfont(struct termp *, enum termfont);
+static void ps_setwidth(struct termp *, int, int);
+static struct termp *pspdf_alloc(const struct manoutput *, enum termtype);
+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 },
+ } },
+ { "Times-BoldItalic", {
+ { 250 },
+ { 389 },
+ { 555 },
+ { 500 },
+ { 500 },
+ { 833 },
+ { 778 },
+ { 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 },
+ { 832 },
+ { 667 },
+ { 667 },
+ { 667 },
+ { 722 },
+ { 667 },
+ { 667 },
+ { 722 },
+ { 778 },
+ { 389 },
+ { 500 },
+ { 667 },
+ { 611 },
+ { 889 },
+ { 722 },
+ { 722 },
+ { 611 },
+ { 722 },
+ { 667 },
+ { 556 },
+ { 611 },
+ { 722 },
+ { 667 },
+ { 889 },
+ { 667 },
+ { 611 },
+ { 611 },
+ { 333 },
+ { 278 },
+ { 333 },
+ { 570 },
+ { 500 },
+ { 333 },
+ { 500 },
+ { 500 },
+ { 444 },
+ { 500 },
+ { 444 },
+ { 333 },
+ { 500 },
+ { 556 },
+ { 278 },
+ { 278 },
+ { 500 },
+ { 278 },
+ { 778 },
+ { 556 },
+ { 500 },
+ { 500 },
+ { 500 },
+ { 389 },
+ { 389 },
+ { 278 },
+ { 556 },
+ { 444 },
+ { 667 },
+ { 500 },
+ { 444 },
+ { 389 },
+ { 348 },
+ { 220 },
+ { 348 },
+ { 570 },
+ } },
+};
+
+void *
+pdf_alloc(const struct manoutput *outopts)
+{
+ return pspdf_alloc(outopts, TERMTYPE_PDF);
+}
+
+void *
+ps_alloc(const struct manoutput *outopts)
+{
+ return pspdf_alloc(outopts, TERMTYPE_PS);
+}
+
+static struct termp *
+pspdf_alloc(const struct manoutput *outopts, enum termtype type)
+{
+ struct termp *p;
+ unsigned int pagex, pagey;
+ size_t marginx, marginy, lineheight;
+ const char *pp;
+
+ p = mandoc_calloc(1, sizeof(*p));
+ p->tcol = p->tcols = mandoc_calloc(1, sizeof(*p->tcol));
+ p->maxtcol = 1;
+ p->type = type;
+
+ p->enc = TERMENC_ASCII;
+ p->fontq = mandoc_reallocarray(NULL,
+ (p->fontsz = 8), sizeof(*p->fontq));
+ p->fontq[0] = p->fontl = TERMFONT_NONE;
+ p->ps = mandoc_calloc(1, sizeof(*p->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;
+
+ /* Default to US letter (millimetres). */
+
+ p->ps->medianame = "Letter";
+ 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.
+ */
+
+ pp = outopts->paper;
+ if (pp != NULL && strcasecmp(pp, "letter") != 0) {
+ if (strcasecmp(pp, "a3") == 0) {
+ p->ps->medianame = "A3";
+ pagex = 297;
+ pagey = 420;
+ } else if (strcasecmp(pp, "a4") == 0) {
+ p->ps->medianame = "A4";
+ pagex = 210;
+ pagey = 297;
+ } else if (strcasecmp(pp, "a5") == 0) {
+ p->ps->medianame = "A5";
+ pagex = 148;
+ pagey = 210;
+ } else if (strcasecmp(pp, "legal") == 0) {
+ p->ps->medianame = "Legal";
+ pagex = 216;
+ pagey = 356;
+ } else if (sscanf(pp, "%ux%u", &pagex, &pagey) == 2)
+ p->ps->medianame = "CustomSize";
+ else
+ warnx("%s: Unknown paper", 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 * 72.0 / 25.4));
+ pagey = PNT2AFM(p, ((double)pagey * 72.0 / 25.4));
+
+ /* 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, int width)
+{
+ size_t lastwidth;
+
+ lastwidth = p->ps->width;
+ if (iop > 0)
+ p->ps->width += width;
+ else if (iop == 0)
+ p->ps->width = width ? (size_t)width : p->ps->lastwidth;
+ else if (p->ps->width > (size_t)width)
+ p->ps->width -= width;
+ else
+ p->ps->width = 0;
+ p->ps->lastwidth = lastwidth;
+}
+
+void
+pspdf_free(void *arg)
+{
+ struct termp *p;
+
+ p = (struct termp *)arg;
+
+ free(p->ps->psmarg);
+ 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 shown. 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) {
+ 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>>\nendobj\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;
+
+ ps_plast(p);
+ ps_pclose(p);
+
+ /*
+ * 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, ">>\nendobj\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)
+{
+ size_t width, height;
+ 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;
+ p->ps->lastrow = 0; /* impossible row */
+
+ 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.
+ */
+
+ if (TERMTYPE_PS == p->type) {
+ width = AFM2PNT(p, p->ps->width);
+ height = AFM2PNT(p, p->ps->height);
+
+ ps_printf(p, "%%!PS-Adobe-3.0\n");
+ 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: man-%s %zu %zu 0 () ()\n",
+ p->ps->medianame, width, 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%%%%DocumentSuppliedResources: "
+ "procset MandocProcs 1.0 0\n");
+ ps_printf(p, "%%%%EndComments\n");
+ ps_printf(p, "%%%%BeginProlog\n");
+ ps_printf(p, "%%%%BeginResource: procset MandocProcs "
+ "10170 10170\n");
+ /* The font size is effectively hard-coded for now. */
+ ps_printf(p, "/fs %zu def\n", p->ps->scale);
+ for (i = 0; i < (int)TERMFONT__MAX; i++)
+ ps_printf(p, "/f%d { /%s fs selectfont } def\n",
+ i, fonts[i].name);
+ ps_printf(p, "/s { 3 1 roll moveto show } bind def\n");
+ ps_printf(p, "/c { exch currentpoint exch pop "
+ "moveto show } bind def\n");
+ ps_printf(p, "%%%%EndResource\n");
+ ps_printf(p, "%%%%EndProlog\n");
+ ps_printf(p, "%%%%BeginSetup\n");
+ ps_printf(p, "%%%%BeginFeature: *PageSize %s\n",
+ p->ps->medianame);
+ ps_printf(p, "<</PageSize [%zu %zu]>>setpagedevice\n",
+ width, height);
+ ps_printf(p, "%%%%EndFeature\n");
+ ps_printf(p, "%%%%EndSetup\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, ">>\nendobj\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, "f%d\n", (int)p->ps->lastf);
+ } 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", AFM2PNT(p, p->ps->pscol));
+ if (p->ps->psrow != p->ps->lastrow)
+ ps_printf(p, " %.3f",
+ AFM2PNT(p, p->ps->psrow));
+ ps_printf(p, "(");
+ }
+ 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 '(':
+ case ')':
+ 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 if (p->ps->psrow == p->ps->lastrow)
+ ps_printf(p, ")c\n");
+ else {
+ ps_printf(p, ")s\n");
+ p->ps->lastrow = p->ps->psrow;
+ }
+
+ p->ps->flags &= ~PS_INLINE;
+}
+
+/* If we have a `last' char that wasn't printed yet, print it now. */
+static void
+ps_plast(struct termp *p)
+{
+ size_t wx;
+
+ if (p->ps->last == '\0')
+ return;
+
+ /* Check the font mode; open a new scope if it doesn't match. */
+
+ if (p->ps->nextf != p->ps->lastf) {
+ ps_pclose(p);
+ ps_setfont(p, p->ps->nextf);
+ }
+ p->ps->nextf = TERMFONT_NONE;
+
+ /*
+ * For an overstrike, if a previous character
+ * was wider, advance to center the new one.
+ */
+
+ if (p->ps->pscolnext) {
+ wx = fonts[p->ps->lastf].gly[(int)p->ps->last-32].wx;
+ if (p->ps->pscol + wx < p->ps->pscolnext)
+ p->ps->pscol = (p->ps->pscol +
+ p->ps->pscolnext - wx) / 2;
+ }
+
+ ps_pletter(p, p->ps->last);
+ p->ps->last = '\0';
+
+ /*
+ * For an overstrike, if a previous character
+ * was wider, advance to the end of the old one.
+ */
+
+ if (p->ps->pscol < p->ps->pscolnext) {
+ ps_pclose(p);
+ p->ps->pscol = p->ps->pscolnext;
+ }
+}
+
+static void
+ps_letter(struct termp *p, int arg)
+{
+ size_t savecol;
+ char c;
+
+ c = arg >= 128 || arg <= 0 ? '?' : arg;
+
+ /*
+ * When receiving a backspace, merely flag it.
+ * We don't know yet whether it is
+ * a font instruction or an overstrike.
+ */
+
+ if (c == '\b') {
+ assert(p->ps->last != '\0');
+ assert( ! (p->ps->flags & PS_BACKSP));
+ p->ps->flags |= PS_BACKSP;
+ return;
+ }
+
+ /*
+ * Decode font instructions.
+ */
+
+ if (p->ps->flags & PS_BACKSP) {
+ if (p->ps->last == '_') {
+ switch (p->ps->nextf) {
+ case TERMFONT_BI:
+ break;
+ case TERMFONT_BOLD:
+ p->ps->nextf = TERMFONT_BI;
+ break;
+ default:
+ p->ps->nextf = TERMFONT_UNDER;
+ }
+ p->ps->last = c;
+ p->ps->flags &= ~PS_BACKSP;
+ return;
+ }
+ if (p->ps->last == c) {
+ switch (p->ps->nextf) {
+ case TERMFONT_BI:
+ break;
+ case TERMFONT_UNDER:
+ p->ps->nextf = TERMFONT_BI;
+ break;
+ default:
+ p->ps->nextf = TERMFONT_BOLD;
+ }
+ p->ps->flags &= ~PS_BACKSP;
+ return;
+ }
+
+ /*
+ * This is not a font instruction, but rather
+ * the next character. Prepare for overstrike.
+ */
+
+ savecol = p->ps->pscol;
+ } else
+ savecol = SIZE_MAX;
+
+ /*
+ * We found the next character, so the font instructions
+ * for the previous one are complete.
+ * Use them and print it.
+ */
+
+ ps_plast(p);
+
+ /*
+ * Do not print the current character yet because font
+ * instructions might follow; only remember the character.
+ * It will get printed later from ps_plast().
+ */
+
+ p->ps->last = c;
+
+ /*
+ * For an overstrike, back up to the previous position.
+ * If the previous character is wider than any it overstrikes,
+ * remember the current position, because it might also be
+ * wider than all that will overstrike it.
+ */
+
+ if (savecol != SIZE_MAX) {
+ if (p->ps->pscolnext < p->ps->pscol)
+ p->ps->pscolnext = p->ps->pscol;
+ ps_pclose(p);
+ p->ps->pscol = savecol;
+ p->ps->flags &= ~PS_BACKSP;
+ } else
+ p->ps->pscolnext = 0;
+}
+
+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_plast(p);
+ ps_pclose(p);
+ p->ps->pscol += len;
+}
+
+static void
+ps_endline(struct termp *p)
+{
+
+ /* Close out any scopes we have open: we're at eoln. */
+
+ ps_plast(p);
+ ps_pclose(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);
+
+ p->tcol->offset -= p->ti;
+ p->ti = 0;
+}
+
+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, "f%d\n", (int)f);
+ 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 int
+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_BU:
+ /*
+ * Traditionally, the default unit is fixed to the
+ * output media. So this would refer to the point. In
+ * mandoc(1), however, we stick to the default terminal
+ * scaling unit so that output is the same regardless
+ * the media.
+ */
+ r = PNT2AFM(p, su->scale * 72.0 / 240.0);
+ break;
+ case SCALE_CM:
+ r = PNT2AFM(p, su->scale * 72.0 / 2.54);
+ break;
+ case SCALE_EM:
+ r = su->scale *
+ fonts[(int)TERMFONT_NONE].gly[109 - 32].wx;
+ break;
+ case SCALE_EN:
+ r = su->scale *
+ fonts[(int)TERMFONT_NONE].gly[110 - 32].wx;
+ break;
+ case SCALE_IN:
+ r = PNT2AFM(p, su->scale * 72.0);
+ break;
+ case SCALE_MM:
+ r = su->scale *
+ fonts[(int)TERMFONT_NONE].gly[109 - 32].wx / 100.0;
+ break;
+ case SCALE_PC:
+ r = PNT2AFM(p, su->scale * 12.0);
+ break;
+ case SCALE_PT:
+ r = PNT2AFM(p, su->scale * 1.0);
+ break;
+ case SCALE_VS:
+ r = su->scale * p->ps->lineheight;
+ break;
+ default:
+ r = su->scale;
+ break;
+ }
+
+ return r * 24.0;
+}
+
+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/usr.bin/mandoc/term_tab.c b/usr.bin/mandoc/term_tab.c
new file mode 100644
index 0000000..0c80c72
--- /dev/null
+++ b/usr.bin/mandoc/term_tab.c
@@ -0,0 +1,128 @@
+/* $OpenBSD: term_tab.c,v 1.4 2017/06/17 14:55:02 schwarze Exp $ */
+/*
+ * Copyright (c) 2017 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.
+ */
+#include <sys/types.h>
+
+#include <stddef.h>
+
+#include "mandoc_aux.h"
+#include "out.h"
+#include "term.h"
+
+struct tablist {
+ size_t *t; /* Allocated array of tab positions. */
+ size_t s; /* Allocated number of positions. */
+ size_t n; /* Currently used number of positions. */
+};
+
+static struct {
+ struct tablist a; /* All tab positions for lookup. */
+ struct tablist p; /* Periodic tab positions to add. */
+ size_t d; /* Default tab width in units of n. */
+} tabs;
+
+
+void
+term_tab_set(const struct termp *p, const char *arg)
+{
+ static int recording_period;
+
+ struct roffsu su;
+ struct tablist *tl;
+ size_t pos;
+ int add;
+
+ /* Special arguments: clear all tabs or switch lists. */
+
+ if (arg == NULL) {
+ tabs.a.n = tabs.p.n = 0;
+ recording_period = 0;
+ if (tabs.d == 0) {
+ a2roffsu(".8i", &su, SCALE_IN);
+ tabs.d = term_hen(p, &su);
+ }
+ return;
+ }
+ if (arg[0] == 'T' && arg[1] == '\0') {
+ recording_period = 1;
+ return;
+ }
+
+ /* Parse the sign, the number, and the unit. */
+
+ if (*arg == '+') {
+ add = 1;
+ arg++;
+ } else
+ add = 0;
+ if (a2roffsu(arg, &su, SCALE_EM) == NULL)
+ return;
+
+ /* Select the list, and extend it if it is full. */
+
+ tl = recording_period ? &tabs.p : &tabs.a;
+ if (tl->n >= tl->s) {
+ tl->s += 8;
+ tl->t = mandoc_reallocarray(tl->t, tl->s, sizeof(*tl->t));
+ }
+
+ /* Append the new position. */
+
+ pos = term_hen(p, &su);
+ tl->t[tl->n] = pos;
+ if (add && tl->n)
+ tl->t[tl->n] += tl->t[tl->n - 1];
+ tl->n++;
+}
+
+/*
+ * Simplified version without a parser,
+ * never incremental, never periodic, for use by tbl(7).
+ */
+void
+term_tab_iset(size_t inc)
+{
+ if (tabs.a.n >= tabs.a.s) {
+ tabs.a.s += 8;
+ tabs.a.t = mandoc_reallocarray(tabs.a.t, tabs.a.s,
+ sizeof(*tabs.a.t));
+ }
+ tabs.a.t[tabs.a.n++] = inc;
+}
+
+size_t
+term_tab_next(size_t prev)
+{
+ size_t i, j;
+
+ for (i = 0;; i++) {
+ if (i == tabs.a.n) {
+ if (tabs.p.n == 0)
+ return prev;
+ tabs.a.n += tabs.p.n;
+ if (tabs.a.s < tabs.a.n) {
+ tabs.a.s = tabs.a.n;
+ tabs.a.t = mandoc_reallocarray(tabs.a.t,
+ tabs.a.s, sizeof(*tabs.a.t));
+ }
+ for (j = 0; j < tabs.p.n; j++)
+ tabs.a.t[i + j] = tabs.p.t[j] +
+ (i ? tabs.a.t[i - 1] : 0);
+ }
+ if (prev < tabs.a.t[i])
+ return tabs.a.t[i];
+ }
+}
diff --git a/usr.bin/mandoc/term_tag.c b/usr.bin/mandoc/term_tag.c
new file mode 100644
index 0000000..1c67dcc
--- /dev/null
+++ b/usr.bin/mandoc/term_tag.c
@@ -0,0 +1,199 @@
+/* $OpenBSD: term_tag.c,v 1.4 2020/04/18 20:28:46 schwarze Exp $ */
+/*
+ * Copyright (c) 2015,2016,2018,2019,2020 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.
+ *
+ * Functions to write a ctags(1) file.
+ * For use by the mandoc(1) ASCII and UTF-8 formatters only.
+ */
+#include <sys/types.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "roff_int.h"
+#include "tag.h"
+#include "term_tag.h"
+
+static void tag_signal(int) __attribute__((__noreturn__));
+
+static struct tag_files tag_files;
+
+
+/*
+ * Prepare for using a pager.
+ * Not all pagers are capable of using a tag file,
+ * but for simplicity, create it anyway.
+ */
+struct tag_files *
+term_tag_init(void)
+{
+ struct sigaction sa;
+ int ofd; /* In /tmp/, dup(2)ed to stdout. */
+ int tfd;
+
+ ofd = tfd = -1;
+ tag_files.tfs = NULL;
+ tag_files.tcpgid = -1;
+
+ /* Clean up when dying from a signal. */
+
+ memset(&sa, 0, sizeof(sa));
+ sigfillset(&sa.sa_mask);
+ sa.sa_handler = tag_signal;
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ /*
+ * POSIX requires that a process calling tcsetpgrp(3)
+ * from the background gets a SIGTTOU signal.
+ * In that case, do not stop.
+ */
+
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGTTOU, &sa, NULL);
+
+ /* Save the original standard output for use by the pager. */
+
+ if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) {
+ mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
+ goto fail;
+ }
+
+ /* Create both temporary output files. */
+
+ (void)strlcpy(tag_files.ofn, "/tmp/man.XXXXXXXXXX",
+ sizeof(tag_files.ofn));
+ (void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX",
+ sizeof(tag_files.tfn));
+ if ((ofd = mkstemp(tag_files.ofn)) == -1) {
+ mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
+ "%s: %s", tag_files.ofn, strerror(errno));
+ goto fail;
+ }
+ if ((tfd = mkstemp(tag_files.tfn)) == -1) {
+ mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
+ "%s: %s", tag_files.tfn, strerror(errno));
+ goto fail;
+ }
+ if ((tag_files.tfs = fdopen(tfd, "w")) == NULL) {
+ mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
+ goto fail;
+ }
+ tfd = -1;
+ if (dup2(ofd, STDOUT_FILENO) == -1) {
+ mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
+ goto fail;
+ }
+ close(ofd);
+ return &tag_files;
+
+fail:
+ term_tag_unlink();
+ if (ofd != -1)
+ close(ofd);
+ if (tfd != -1)
+ close(tfd);
+ if (tag_files.ofd != -1) {
+ close(tag_files.ofd);
+ tag_files.ofd = -1;
+ }
+ return NULL;
+}
+
+void
+term_tag_write(struct roff_node *n, size_t line)
+{
+ const char *cp;
+ int len;
+
+ if (tag_files.tfs == NULL)
+ return;
+ cp = n->tag == NULL ? n->child->string : n->tag;
+ if (cp[0] == '\\' && (cp[1] == '&' || cp[1] == 'e'))
+ cp += 2;
+ len = strcspn(cp, " \t\\");
+ fprintf(tag_files.tfs, "%.*s %s %zu\n",
+ len, cp, tag_files.ofn, line);
+}
+
+/*
+ * Close both output files and restore the original standard output
+ * to the terminal. In the unlikely case that the latter fails,
+ * trying to start a pager would be useless, so report the failure
+ * to the main program.
+ */
+int
+term_tag_close(void)
+{
+ int irc = 0;
+
+ if (tag_files.tfs != NULL) {
+ fclose(tag_files.tfs);
+ tag_files.tfs = NULL;
+ }
+ if (tag_files.ofd != -1) {
+ fflush(stdout);
+ if ((irc = dup2(tag_files.ofd, STDOUT_FILENO)) == -1)
+ mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
+ close(tag_files.ofd);
+ tag_files.ofd = -1;
+ }
+ return irc;
+}
+
+void
+term_tag_unlink(void)
+{
+ pid_t tc_pgid;
+
+ if (tag_files.tcpgid != -1) {
+ tc_pgid = tcgetpgrp(STDOUT_FILENO);
+ if (tc_pgid == tag_files.pager_pid ||
+ tc_pgid == getpgid(0) ||
+ getpgid(tc_pgid) == -1)
+ (void)tcsetpgrp(STDOUT_FILENO, tag_files.tcpgid);
+ }
+ if (*tag_files.ofn != '\0') {
+ unlink(tag_files.ofn);
+ *tag_files.ofn = '\0';
+ }
+ if (*tag_files.tfn != '\0') {
+ unlink(tag_files.tfn);
+ *tag_files.tfn = '\0';
+ }
+}
+
+static void
+tag_signal(int signum)
+{
+ struct sigaction sa;
+
+ term_tag_unlink();
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sigaction(signum, &sa, NULL);
+ kill(getpid(), signum);
+ /* NOTREACHED */
+ _exit(1);
+}
diff --git a/usr.bin/mandoc/term_tag.h b/usr.bin/mandoc/term_tag.h
new file mode 100644
index 0000000..62be2d3
--- /dev/null
+++ b/usr.bin/mandoc/term_tag.h
@@ -0,0 +1,34 @@
+/* $OpenBSD: term_tag.h,v 1.2 2020/04/02 22:10:27 schwarze Exp $ */
+/*
+ * Copyright (c) 2015, 2018, 2019, 2020 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.
+ *
+ * Internal interfaces to write a ctags(1) file.
+ * For use by the mandoc(1) ASCII and UTF-8 formatters only.
+ */
+
+struct tag_files {
+ char ofn[20]; /* Output file name. */
+ char tfn[20]; /* Tag file name. */
+ FILE *tfs; /* Tag file object. */
+ int ofd; /* Original output file descriptor. */
+ pid_t tcpgid; /* Process group controlling the terminal. */
+ pid_t pager_pid; /* Process ID of the pager. */
+};
+
+
+struct tag_files *term_tag_init(void);
+void term_tag_write(struct roff_node *, size_t);
+int term_tag_close(void);
+void term_tag_unlink(void);
diff --git a/usr.bin/mandoc/tree.c b/usr.bin/mandoc/tree.c
new file mode 100644
index 0000000..0125ebd
--- /dev/null
+++ b/usr.bin/mandoc/tree.c
@@ -0,0 +1,514 @@
+/* $OpenBSD: tree.c,v 1.56 2020/04/08 11:54:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008, 2009, 2011, 2014 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 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.
+ *
+ * Formatting module to let mandoc(1) show
+ * a human readable representation of the syntax tree.
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "man.h"
+#include "tbl.h"
+#include "eqn.h"
+#include "main.h"
+
+static void print_attr(const struct roff_node *);
+static void print_box(const struct eqn_box *, int);
+static void print_cellt(enum tbl_cellt);
+static void print_man(const struct roff_node *, int);
+static void print_meta(const struct roff_meta *);
+static void print_mdoc(const struct roff_node *, int);
+static void print_span(const struct tbl_span *, int);
+
+
+void
+tree_mdoc(void *arg, const struct roff_meta *mdoc)
+{
+ print_meta(mdoc);
+ putchar('\n');
+ print_mdoc(mdoc->first->child, 0);
+}
+
+void
+tree_man(void *arg, const struct roff_meta *man)
+{
+ print_meta(man);
+ if (man->hasbody == 0)
+ puts("body = empty");
+ putchar('\n');
+ print_man(man->first->child, 0);
+}
+
+static void
+print_meta(const struct roff_meta *meta)
+{
+ if (meta->title != NULL)
+ printf("title = \"%s\"\n", meta->title);
+ if (meta->name != NULL)
+ printf("name = \"%s\"\n", meta->name);
+ if (meta->msec != NULL)
+ printf("sec = \"%s\"\n", meta->msec);
+ if (meta->vol != NULL)
+ printf("vol = \"%s\"\n", meta->vol);
+ if (meta->arch != NULL)
+ printf("arch = \"%s\"\n", meta->arch);
+ if (meta->os != NULL)
+ printf("os = \"%s\"\n", meta->os);
+ if (meta->date != NULL)
+ printf("date = \"%s\"\n", meta->date);
+}
+
+static void
+print_mdoc(const struct roff_node *n, int indent)
+{
+ const char *p, *t;
+ int i, j;
+ size_t argc;
+ struct mdoc_argv *argv;
+
+ if (n == NULL)
+ return;
+
+ argv = NULL;
+ argc = 0;
+ t = p = NULL;
+
+ switch (n->type) {
+ case ROFFT_ROOT:
+ t = "root";
+ break;
+ case ROFFT_BLOCK:
+ t = "block";
+ break;
+ case ROFFT_HEAD:
+ t = "head";
+ break;
+ case ROFFT_BODY:
+ if (n->end)
+ t = "body-end";
+ else
+ t = "body";
+ break;
+ case ROFFT_TAIL:
+ t = "tail";
+ break;
+ case ROFFT_ELEM:
+ t = "elem";
+ break;
+ case ROFFT_TEXT:
+ t = "text";
+ break;
+ case ROFFT_COMMENT:
+ t = "comment";
+ break;
+ case ROFFT_TBL:
+ break;
+ case ROFFT_EQN:
+ t = "eqn";
+ break;
+ default:
+ abort();
+ }
+
+ switch (n->type) {
+ case ROFFT_TEXT:
+ case ROFFT_COMMENT:
+ p = n->string;
+ break;
+ case ROFFT_BODY:
+ p = roff_name[n->tok];
+ break;
+ case ROFFT_HEAD:
+ p = roff_name[n->tok];
+ break;
+ case ROFFT_TAIL:
+ p = roff_name[n->tok];
+ break;
+ case ROFFT_ELEM:
+ p = roff_name[n->tok];
+ if (n->args) {
+ argv = n->args->argv;
+ argc = n->args->argc;
+ }
+ break;
+ case ROFFT_BLOCK:
+ p = roff_name[n->tok];
+ if (n->args) {
+ argv = n->args->argv;
+ argc = n->args->argc;
+ }
+ break;
+ case ROFFT_TBL:
+ break;
+ case ROFFT_EQN:
+ p = "EQ";
+ break;
+ case ROFFT_ROOT:
+ p = "root";
+ break;
+ default:
+ abort();
+ }
+
+ if (n->span) {
+ assert(NULL == p && NULL == t);
+ print_span(n->span, indent);
+ } else {
+ for (i = 0; i < indent; i++)
+ putchar(' ');
+
+ 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(" ]");
+ }
+ print_attr(n);
+ }
+ if (n->eqn)
+ print_box(n->eqn->first, indent + 4);
+ if (n->child)
+ print_mdoc(n->child, indent +
+ (n->type == ROFFT_BLOCK ? 2 : 4));
+ if (n->next)
+ print_mdoc(n->next, indent);
+}
+
+static void
+print_man(const struct roff_node *n, int indent)
+{
+ const char *p, *t;
+ int i;
+
+ if (n == NULL)
+ return;
+
+ t = p = NULL;
+
+ switch (n->type) {
+ case ROFFT_ROOT:
+ t = "root";
+ break;
+ case ROFFT_ELEM:
+ t = "elem";
+ break;
+ case ROFFT_TEXT:
+ t = "text";
+ break;
+ case ROFFT_COMMENT:
+ t = "comment";
+ break;
+ case ROFFT_BLOCK:
+ t = "block";
+ break;
+ case ROFFT_HEAD:
+ t = "head";
+ break;
+ case ROFFT_BODY:
+ t = "body";
+ break;
+ case ROFFT_TBL:
+ break;
+ case ROFFT_EQN:
+ t = "eqn";
+ break;
+ default:
+ abort();
+ }
+
+ switch (n->type) {
+ case ROFFT_TEXT:
+ case ROFFT_COMMENT:
+ p = n->string;
+ break;
+ case ROFFT_ELEM:
+ case ROFFT_BLOCK:
+ case ROFFT_HEAD:
+ case ROFFT_BODY:
+ p = roff_name[n->tok];
+ break;
+ case ROFFT_ROOT:
+ p = "root";
+ break;
+ case ROFFT_TBL:
+ break;
+ case ROFFT_EQN:
+ p = "EQ";
+ break;
+ default:
+ abort();
+ }
+
+ if (n->span) {
+ assert(NULL == p && NULL == t);
+ print_span(n->span, indent);
+ } else {
+ for (i = 0; i < indent; i++)
+ putchar(' ');
+ printf("%s (%s)", p, t);
+ print_attr(n);
+ }
+ if (n->eqn)
+ print_box(n->eqn->first, indent + 4);
+ if (n->child)
+ print_man(n->child, indent +
+ (n->type == ROFFT_BLOCK ? 2 : 4));
+ if (n->next)
+ print_man(n->next, indent);
+}
+
+static void
+print_attr(const struct roff_node *n)
+{
+ putchar(' ');
+ if (n->flags & NODE_DELIMO)
+ putchar('(');
+ if (n->flags & NODE_LINE)
+ putchar('*');
+ printf("%d:%d", n->line, n->pos + 1);
+ if (n->flags & NODE_DELIMC)
+ putchar(')');
+ if (n->flags & NODE_EOS)
+ putchar('.');
+ if (n->flags & NODE_ID) {
+ printf(" ID");
+ if (n->flags & NODE_HREF)
+ printf("=HREF");
+ } else if (n->flags & NODE_HREF)
+ printf(" HREF");
+ else if (n->tag != NULL)
+ printf(" STRAYTAG");
+ if (n->tag != NULL)
+ printf("=%s", n->tag);
+ if (n->flags & NODE_BROKEN)
+ printf(" BROKEN");
+ if (n->flags & NODE_NOFILL)
+ printf(" NOFILL");
+ if (n->flags & NODE_NOSRC)
+ printf(" NOSRC");
+ if (n->flags & NODE_NOPRT)
+ printf(" NOPRT");
+ putchar('\n');
+}
+
+static void
+print_box(const struct eqn_box *ep, int indent)
+{
+ int i;
+ const char *t;
+
+ static const char *posnames[] = {
+ NULL, "sup", "subsup", "sub",
+ "to", "from", "fromto",
+ "over", "sqrt", NULL };
+
+ if (NULL == ep)
+ return;
+ for (i = 0; i < indent; i++)
+ putchar(' ');
+
+ t = NULL;
+ switch (ep->type) {
+ case EQN_LIST:
+ t = "eqn-list";
+ break;
+ case EQN_SUBEXPR:
+ t = "eqn-expr";
+ break;
+ case EQN_TEXT:
+ t = "eqn-text";
+ break;
+ case EQN_PILE:
+ t = "eqn-pile";
+ break;
+ case EQN_MATRIX:
+ t = "eqn-matrix";
+ break;
+ }
+
+ fputs(t, stdout);
+ if (ep->pos)
+ printf(" pos=%s", posnames[ep->pos]);
+ if (ep->left)
+ printf(" left=\"%s\"", ep->left);
+ if (ep->right)
+ printf(" right=\"%s\"", ep->right);
+ if (ep->top)
+ printf(" top=\"%s\"", ep->top);
+ if (ep->bottom)
+ printf(" bottom=\"%s\"", ep->bottom);
+ if (ep->text)
+ printf(" text=\"%s\"", ep->text);
+ if (ep->font)
+ printf(" font=%d", ep->font);
+ if (ep->size != EQN_DEFSIZE)
+ printf(" size=%d", ep->size);
+ if (ep->expectargs != UINT_MAX && ep->expectargs != ep->args)
+ printf(" badargs=%zu(%zu)", ep->args, ep->expectargs);
+ else if (ep->args)
+ printf(" args=%zu", ep->args);
+ putchar('\n');
+
+ print_box(ep->first, indent + 4);
+ print_box(ep->next, indent);
+}
+
+static void
+print_cellt(enum tbl_cellt pos)
+{
+ switch(pos) {
+ case TBL_CELL_LEFT:
+ putchar('L');
+ break;
+ case TBL_CELL_LONG:
+ putchar('a');
+ break;
+ case TBL_CELL_CENTRE:
+ putchar('c');
+ break;
+ case TBL_CELL_RIGHT:
+ putchar('r');
+ break;
+ case TBL_CELL_NUMBER:
+ putchar('n');
+ break;
+ case TBL_CELL_SPAN:
+ putchar('s');
+ break;
+ case TBL_CELL_DOWN:
+ putchar('^');
+ break;
+ case TBL_CELL_HORIZ:
+ putchar('-');
+ break;
+ case TBL_CELL_DHORIZ:
+ putchar('=');
+ break;
+ case TBL_CELL_MAX:
+ putchar('#');
+ break;
+ }
+}
+
+static void
+print_span(const struct tbl_span *sp, int indent)
+{
+ const struct tbl_dat *dp;
+ const struct tbl_cell *cp;
+ int i;
+
+ if (sp->prev == NULL) {
+ for (i = 0; i < indent; i++)
+ putchar(' ');
+ printf("%d", sp->opts->cols);
+ if (sp->opts->opts & TBL_OPT_CENTRE)
+ fputs(" center", stdout);
+ if (sp->opts->opts & TBL_OPT_EXPAND)
+ fputs(" expand", stdout);
+ if (sp->opts->opts & TBL_OPT_ALLBOX)
+ fputs(" allbox", stdout);
+ if (sp->opts->opts & TBL_OPT_BOX)
+ fputs(" box", stdout);
+ if (sp->opts->opts & TBL_OPT_DBOX)
+ fputs(" doublebox", stdout);
+ if (sp->opts->opts & TBL_OPT_NOKEEP)
+ fputs(" nokeep", stdout);
+ if (sp->opts->opts & TBL_OPT_NOSPACE)
+ fputs(" nospaces", stdout);
+ if (sp->opts->opts & TBL_OPT_NOWARN)
+ fputs(" nowarn", stdout);
+ printf(" (tbl options) %d:1\n", sp->line);
+ }
+
+ for (i = 0; i < indent; i++)
+ putchar(' ');
+
+ switch (sp->pos) {
+ case TBL_SPAN_HORIZ:
+ putchar('-');
+ putchar(' ');
+ break;
+ case TBL_SPAN_DHORIZ:
+ putchar('=');
+ putchar(' ');
+ break;
+ default:
+ for (cp = sp->layout->first; cp != NULL; cp = cp->next)
+ print_cellt(cp->pos);
+ putchar(' ');
+ for (dp = sp->first; dp; dp = dp->next) {
+ if ((cp = dp->layout) == NULL)
+ putchar('*');
+ else {
+ printf("%d", cp->col);
+ print_cellt(dp->layout->pos);
+ if (cp->flags & TBL_CELL_BOLD)
+ putchar('b');
+ if (cp->flags & TBL_CELL_ITALIC)
+ putchar('i');
+ if (cp->flags & TBL_CELL_TALIGN)
+ putchar('t');
+ if (cp->flags & TBL_CELL_UP)
+ putchar('u');
+ if (cp->flags & TBL_CELL_BALIGN)
+ putchar('d');
+ if (cp->flags & TBL_CELL_WIGN)
+ putchar('z');
+ if (cp->flags & TBL_CELL_EQUAL)
+ putchar('e');
+ if (cp->flags & TBL_CELL_WMAX)
+ putchar('x');
+ }
+ switch (dp->pos) {
+ case TBL_DATA_HORIZ:
+ case TBL_DATA_NHORIZ:
+ putchar('-');
+ break;
+ case TBL_DATA_DHORIZ:
+ case TBL_DATA_NDHORIZ:
+ putchar('=');
+ break;
+ default:
+ putchar(dp->block ? '{' : '[');
+ if (dp->string != NULL)
+ fputs(dp->string, stdout);
+ putchar(dp->block ? '}' : ']');
+ break;
+ }
+ if (dp->hspans)
+ printf(">%d", dp->hspans);
+ if (dp->vspans)
+ printf("v%d", dp->vspans);
+ putchar(' ');
+ }
+ break;
+ }
+ printf("(tbl) %d:1\n", sp->line);
+}