Index: head/contrib/mandoc/libroff.h =================================================================== --- head/contrib/mandoc/libroff.h (revision 350350) +++ head/contrib/mandoc/libroff.h (nonexistent) @@ -1,80 +0,0 @@ -/* $Id: libroff.h,v 1.42 2017/07/08 17:52:49 schwarze Exp $ */ -/* - * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2014, 2015, 2017 Ingo Schwarze - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 tbl_part { - TBL_PART_OPTS, /* in options (first line) */ - TBL_PART_LAYOUT, /* describing layout */ - TBL_PART_DATA, /* creating data rows */ - TBL_PART_CDATA /* continue previous row */ -}; - -struct tbl_node { - struct mparse *parse; /* parse point */ - int pos; /* invocation column */ - int line; /* invocation line */ - enum tbl_part part; - struct tbl_opts opts; - struct tbl_row *first_row; - struct tbl_row *last_row; - struct tbl_span *first_span; - struct tbl_span *current_span; - struct tbl_span *last_span; - struct tbl_node *next; -}; - -struct eqn_node { - struct mparse *parse; /* main parser, for error reporting */ - 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_def { - char *key; - size_t keysz; - char *val; - size_t valsz; -}; - - -struct tbl_node *tbl_alloc(int, int, struct mparse *); -void tbl_restart(int, int, struct tbl_node *); -void tbl_free(struct tbl_node *); -void tbl_reset(struct tbl_node *); -void tbl_read(struct tbl_node *, int, const char *, int); -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); -const struct tbl_span *tbl_span(struct tbl_node *); -int tbl_end(struct tbl_node *); -struct eqn_node *eqn_alloc(struct mparse *); -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 *); Property changes on: head/contrib/mandoc/libroff.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Deleted: svn:mime-type ## -1 +0,0 ## -text/plain \ No newline at end of property Index: head/contrib/mandoc/Makefile.depend =================================================================== --- head/contrib/mandoc/Makefile.depend (revision 350350) +++ head/contrib/mandoc/Makefile.depend (revision 350351) @@ -1,81 +1,81 @@ arch.o: arch.c config.h roff.h att.o: att.c config.h roff.h libmdoc.h catman.o: catman.c config.h compat_fts.h cgi.o: cgi.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h main.h manconf.h mansearch.h cgi.h chars.o: chars.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h libmandoc.h compat_err.o: compat_err.c config.h compat_fts.o: compat_fts.c config.h compat_fts.h compat_getline.o: compat_getline.c config.h compat_getsubopt.o: compat_getsubopt.c config.h compat_isblank.o: compat_isblank.c config.h compat_mkdtemp.o: compat_mkdtemp.c config.h compat_ohash.o: compat_ohash.c config.h compat_ohash.h compat_progname.o: compat_progname.c config.h compat_reallocarray.o: compat_reallocarray.c config.h compat_recallocarray.o: compat_recallocarray.c config.h compat_strcasestr.o: compat_strcasestr.c config.h compat_stringlist.o: compat_stringlist.c config.h compat_stringlist.h compat_strlcat.o: compat_strlcat.c config.h compat_strlcpy.o: compat_strlcpy.c config.h compat_strndup.o: compat_strndup.c config.h compat_strsep.o: compat_strsep.c config.h compat_strtonum.o: compat_strtonum.c config.h compat_vasprintf.o: compat_vasprintf.c config.h dba.o: dba.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mansearch.h dba_write.h dba_array.h dba.h dba_array.o: dba_array.c mandoc_aux.h dba_write.h dba_array.h dba_read.o: dba_read.c mandoc_aux.h mansearch.h dba_array.h dba.h dbm.h dba_write.o: dba_write.c config.h dba_write.h dbm.o: dbm.c config.h mansearch.h dbm_map.h dbm.h dbm_map.o: dbm_map.c config.h mansearch.h dbm_map.h dbm.h demandoc.o: demandoc.c config.h mandoc.h roff.h man.h mdoc.h mandoc_parse.h eqn.o: eqn.c config.h mandoc_aux.h mandoc.h roff.h eqn.h libmandoc.h eqn_parse.h -eqn_html.o: eqn_html.c config.h mandoc.h eqn.h out.h html.h +eqn_html.o: eqn_html.c config.h mandoc.h roff.h eqn.h out.h html.h eqn_term.o: eqn_term.c config.h eqn.h out.h term.h html.o: html.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h out.h html.h manconf.h main.h lib.o: lib.c config.h roff.h libmdoc.h lib.in main.o: main.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h man.h mandoc_parse.h tag.h main.h manconf.h mansearch.h man.o: man.c config.h mandoc_aux.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h man_html.o: man_html.c config.h mandoc_aux.h mandoc.h roff.h man.h out.h html.h main.h man_macro.o: man_macro.c config.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h -man_term.o: man_term.c config.h mandoc_aux.h roff.h man.h out.h term.h main.h +man_term.o: man_term.c config.h mandoc_aux.h mandoc.h roff.h man.h out.h term.h tag.h main.h man_validate.o: man_validate.c config.h mandoc_aux.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h mandoc.o: mandoc.c config.h mandoc_aux.h mandoc.h roff.h libmandoc.h roff_int.h mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h -mandoc_msg.o: mandoc_msg.c mandoc.h +mandoc_msg.o: mandoc_msg.c config.h mandoc.h mandoc_ohash.o: mandoc_ohash.c mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc_xr.o: mandoc_xr.c mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc_xr.h mandocd.o: mandocd.c config.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h main.h manconf.h mandocdb.o: mandocdb.c config.h compat_fts.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h manconf.h mansearch.h dba_array.h dba.h -manpath.o: manpath.c config.h mandoc_aux.h manconf.h +manpath.o: manpath.c config.h mandoc_aux.h mandoc.h manconf.h mansearch.o: mansearch.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h manconf.h mansearch.h dbm.h mdoc.o: mdoc.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h mdoc_argv.o: mdoc_argv.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h mdoc_html.o: mdoc_html.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h out.h html.h main.h mdoc_macro.o: mdoc_macro.c config.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h mdoc_man.o: mdoc_man.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h out.h main.h mdoc_markdown.o: mdoc_markdown.c mandoc_aux.h mandoc.h roff.h mdoc.h main.h mdoc_state.o: mdoc_state.c mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h mdoc_term.o: mdoc_term.c config.h mandoc_aux.h roff.h mdoc.h out.h term.h tag.h main.h mdoc_validate.o: mdoc_validate.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h msec.o: msec.c config.h mandoc.h libmandoc.h msec.in out.o: out.c config.h mandoc_aux.h tbl.h out.h preconv.o: preconv.c config.h mandoc.h roff.h mandoc_parse.h libmandoc.h read.o: read.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h libmandoc.h roff_int.h roff.o: roff.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mandoc_parse.h libmandoc.h roff_int.h tbl_parse.h eqn_parse.h predefs.in roff_html.o: roff_html.c mandoc.h roff.h out.h html.h roff_term.o: roff_term.c mandoc.h roff.h out.h term.h roff_validate.o: roff_validate.c mandoc.h roff.h libmandoc.h roff_int.h soelim.o: soelim.c config.h compat_stringlist.h st.o: st.c config.h mandoc.h roff.h libmdoc.h -tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h tag.h +tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h tag.h tbl.o: tbl.c config.h mandoc_aux.h mandoc.h tbl.h libmandoc.h tbl_parse.h tbl_int.h tbl_data.o: tbl_data.c config.h mandoc_aux.h mandoc.h tbl.h libmandoc.h tbl_int.h -tbl_html.o: tbl_html.c config.h mandoc.h tbl.h out.h html.h +tbl_html.o: tbl_html.c config.h mandoc.h roff.h tbl.h out.h html.h tbl_layout.o: tbl_layout.c config.h mandoc_aux.h mandoc.h tbl.h libmandoc.h tbl_int.h tbl_opts.o: tbl_opts.c config.h mandoc.h tbl.h libmandoc.h tbl_int.h tbl_term.o: tbl_term.c config.h mandoc.h tbl.h out.h term.h term.o: term.c config.h mandoc.h mandoc_aux.h out.h term.h main.h term_ascii.o: term_ascii.c config.h mandoc.h mandoc_aux.h out.h term.h manconf.h main.h term_ps.o: term_ps.c config.h mandoc_aux.h out.h term.h manconf.h main.h term_tab.o: term_tab.c mandoc_aux.h out.h term.h tree.o: tree.c config.h mandoc.h roff.h mdoc.h man.h tbl.h eqn.h main.h Index: head/contrib/mandoc/TODO =================================================================== --- head/contrib/mandoc/TODO (revision 350350) +++ head/contrib/mandoc/TODO (revision 350351) @@ -1,559 +1,583 @@ ************************************************************************ * Official mandoc TODO. -* $Id: TODO,v 1.289 2019/03/04 13:01:57 schwarze Exp $ +* $Id: TODO,v 1.295 2019/06/11 16:04:36 schwarze Exp $ ************************************************************************ Many issues are annotated for difficulty as follows: - loc = locality of the issue * single file issue, affects file only, or very few ** single module issue, affects several files of one module *** cross-module issue, significantly impacts multiple modules and may require substantial changes to internal interfaces - exist = difficulty of the existing code in this area * affected code is straightforward and easy to read and change ** affected code is somewhat complex, but once you understand the design, not particularly difficult to understand *** affected code uses a special, exceptionally tricky design - algo = difficulty of the new algorithm to be written * the required logic and code is straightforward ** the required logic is somewhat complex and needs a careful design *** the required logic is exceptionally tricky, maybe an approach to solve that is not even known yet - size = the amount of code to be written or changed * a small number of lines (at most 100, usually much less) ** a considerable amount of code (several dozen to a few hundred) *** a large amount of code (many hundreds, maybe thousands) - imp = importance of the issue * mostly for completeness ** would be nice to have *** issue causes considerable inconvenience Obviously, as the issues have not been solved yet, these annotations are mere guesses, and some may be wrong. ************************************************************************ * missing features ************************************************************************ --- missing roff features ---------------------------------------------- - .ad (adjust margins) .ad l -- adjust left margin only (flush left) .ad r -- adjust right margin only (flush right) .ad c -- center text on line .ad b -- adjust both margins (alias: .ad n) .na -- temporarily disable adjustment without changing the mode .ad -- re-enable adjustment without changing the mode Adjustment mode is ignored while in no-fill mode (.nf). loc *** exist *** algo ** size ** imp ** (parser reorg would help) - .fc (field control) found by naddy@ in xloadimage(1) loc ** exist *** algo * size * imp * - .ns (no-space mode) occurs in xine-config(1) when implementing this, also let .TH set it reported by brad@ Sat, 15 Jan 2011 15:45:23 -0500 loc *** exist *** algo *** size ** imp * - \w'' improve width measurements would not be very useful without an expression parser, see below needed for Tcl_NewStringObj(3) via wiz@ Wed, 5 Mar 2014 22:27:43 +0100 loc ** exist *** algo *** size * imp *** +- .als only works for macros in mandoc, not for user-defined strings. + Also, the "val" field in struct roffkv would have to be replaced + with a pointer to a reference-counted wrapper, and an alias + would have to point to the same wrapper as the original. + .als to undefined does nothing; the alias is not created. + .rm'ing the original leaves the alias to point to the old value. + .de .als .de changes both, but + .de .als .rm .de only changes the new value, not the alias. + Found in groffer(1) version 1.19 + Jan Stary 20 Apr 2019 20:16:54 +0200 + loc * exist ** algo ** size ** imp * + +- roff string condition comparisons fail when vars contain quotes: + .ds s ' + .if '\*s'' \&... + hard to fix because of the basic architecture (string replacement + happens before roff(7) syntax parsing) + Found in groffer(1) version 1.19 + Jan Stary 20 Apr 2019 20:16:54 +0200 + loc * exist *** algo *** size ** imp * + --- missing mdoc features ---------------------------------------------- - .Bl -column .Xo support is missing ultimate goal: restore .Xr and .Dv to lib/libc/compat-43/sigvec.3 lib/libc/gen/signal.3 lib/libc/sys/sigaction.2 loc * exist *** algo *** size * imp ** - edge case: decide how to deal with blk_full bad nesting, e.g. .Sh .Nm .Bk .Nm .Ek .Sh found by jmc@ in ssh-keygen(1) from jmc@ Wed, 14 Jul 2010 18:10:32 +0100 loc * exist *** algo *** size ** imp ** - .Bd -filled should not be the same as .Bd -ragged, but align both the left and right margin. In groff, it is implemented in terms of .ad b, which we don't have either. Found in cksum(1). loc *** exist *** algo ** size ** imp ** (parser reorg would help) - implement blank `Bl -column', such as .Bl -column .It foo Ta bar .El loc * exist *** algo *** size * imp * - explicitly disallow nested `Bl -column', which would clobber internal flags defined for struct mdoc_macro loc * exist * algo * size * imp ** - In .Bl -column .It, the end of the line probably has to be regarded as an implicit .Ta, if there could be one, see the following mildly ugly code from login.conf(5): .Bl -column minpasswordlen program xetcxmotd .It path Ta path Ta value of Dv _PATH_DEFPATH .br Default search path. reported by Michal Mazurek via jmc@ Thu, 7 Apr 2011 16:00:53 +0059 loc * exist *** algo ** size * imp ** - inside `.Bl -column' phrases, punctuation is handled like normal text, e.g. `.Bl -column .It Fl x . Ta ...' should give "-x -." - inside `.Bl -column' phrases, TERMP_IGNDELIM handling by `Pf' is not safe, e.g. `.Bl -column .It Pf a b .' gives "ab." but should give "ab ." - prohibit `Nm' from having non-text HEAD children (e.g., NetBSD mDNSShared/dns-sd.1) (mdoc_html.c and mdoc_term.c `Nm' handlers can be slightly simplified) - support translated section names e.g. x11/scrotwm scrotwm_es.1:21:2: error: NAME section must be first that one uses NOMBRE because it is spanish... deraadt tends to think that section-dependent macro behaviour is a bad idea in the first place, so this may be irrelevant loc ** exist ** algo ** size * imp ** - When there is free text in the SYNOPSIS and that free text contains the .Nm macro, groff somehow understands to treat the .Nm as an in-line macro, while mandoc treats it as a block macro and breaks the line. No idea how the logic for distinguishing in-line and block instances should be, needs investigation. uqs@ Thu, 2 Jun 2011 11:03:51 +0200 uqs@ Thu, 2 Jun 2011 11:33:35 +0200 loc * exist ** algo *** size * imp ** --- missing man features ----------------------------------------------- - groff_www(7) .MTO and .URL These macros were used by the GNU grep(1) man page. The groff_www(7) manual page itself uses them, too. We should probably *not* add them to mandoc. Just mentioning this here to keep track of the abuse. Laura Morales 20 Apr 2018 07:33:02 +0200 loc ** exist * algo * size ** imp * --- missing tbl features ----------------------------------------------- - vertical centering in cells vertically spanned with ^ pali dot rohar at gmail dot com 16 Jul 2018 13:03:35 +0200 loc * exist *** algo *** size ** imp * - support mdoc(7) and man(7) macros inside tbl(7) code; probably requires the parser reorg and letting tbl(7) use roff_node such that macro sets can mix; informed by bapt@ that FreeBSD needs this: 3 Jan 2015 23:32:23 +0100 loc *** exist ** algo *** size ** imp *** - look at the POSIX manuals in the books/man-pages-posix port, they use some unsupported tbl(7) features, mostly macros in tbl(7). loc * exist ** algo ** size ** imp *** - look what Joerg Schilling manual pages use Thu, 19 Mar 2015 18:31:48 +0100 --- missing eqn features ----------------------------------------------- - In a matrix, break the output line after each matrix line. Found in the discussion at CDBUG 2015. Suggested by Avi Weinstock. This may not be the ideal solution after all: eqn(7) matrices are lists of columns, so Avi's proposal would show each *column* on its own *line*, which is likely to cause confusion. A better solution, but much harder to implement, would be to actually show the coordinates of column vectors on different terminal output lines, using the clumnated output facilities developed for .Bl -tag, .Bl -column, and also used for tbl(7). loc * exist * algo ** size ** imp ** - The "size" keyword is parsed, but ignored by the formatter. loc * exist * algo * size * imp * - The spacing characters `~', `^', and tab are currently ignored, see User's Guide (Second Edition) page 2 section 4. loc * exist * algo ** size * imp ** - Mark and lineup are parsed and ignored, see User's Guide (Second Edition) page 5 section 15. loc ** exist ** algo ** size ** imp ** - GNU eqn converts some operators to special characters, for example, input HYPHEN-MINUS becomes output \(mi, unless it is part of a quoted word. mandoc(1) only does this when the operator is surrounded by blanks, not when it is part of an unquoted word. Also, check whether there are more such cases (e.g., +?). reported by bentley@ 20 Jun 2017 02:04:29 -0600 loc * exist ** algo ** size * imp * - Primes, opprime, and ' bentley@ Thu, 13 Jul 2017 23:14:20 -0600 --- missing misc features ---------------------------------------------- - man -ks 1,8 route; kn@ Jul 13, 2018 orally - italic correction (\/) in PostScript mode Werner LEMBERG on groff at gnu dot org Sun, 10 Nov 2013 12:47:46 loc ** exist ** algo * size * imp * - change the default PAGER to more -Es and use the pager even for apropos title line output; req by bapt@ loc * exist * algo * size * imp *** - clean up escape sequence handling, creating three classes: (1) fully implemented, or parsed and ignored without loss of content (2) unimplemented, potentially causing loss of content or serious mangling of formatting (e.g. \n) -> ERROR see textproc/mgdiff(1) for nice examples (3) undefined, just output the character -> perhaps WARNING loc *** exist ** algo ** size ** imp *** (parser reorg helps) - kettenis wants base roff, ms, and me Fri, 1 Jan 2010 22:13:15 +0100 (CET) loc ** exist ** algo ** size *** imp * --- compatibility checks ----------------------------------------------- - is .Bk implemented correctly in modern groff? sobrado@ Tue, 19 Apr 2011 22:12:55 +0200 - compare output to Heirloom roff, Solaris roff, and http://repo.or.cz/w/neatroff.git http://litcave.rudi.ir/ - look at AT&T DWB http://www2.research.att.com/sw/download Carsten Kunze has patches Mon, 4 Aug 2014 17:01:28 +0200 ported version: https://github.com/n-t-roff/DWB3.3 Carsten Kunze Wed, 22 Apr 2015 11:21:43 +0200 - look at pages generated from reStructeredText, e.g. devel/mercurial hg(1) These are a weird mixture of man(7) and custom autogenerated low-level roff stuff. Figure out to what extent we can cope. For details, see http://docutils.sourceforge.net/rst.html noted by stsp@ Sat, 24 Apr 2010 09:17:55 +0200 reminded by nicm@ Mon, 3 May 2010 09:52:41 +0100 - look at pages generated from ronn(1) github.com/rtomayko/ronn (based on markdown) - look at pages generated from Texinfo source by yat2m, e.g. security/gnupg First impression is not that bad. - look at pages generated by pandoc; see https://github.com/jgm/pandoc/blob/master/src/Text/Pandoc/Writers/Man.hs porting planned by kili@ Thu, 19 Jun 2014 19:46:28 +0200 - check compatibility with Plan9: http://swtch.com/usr/local/plan9/tmac/tmac.an http://swtch.com/plan9port/man/man7/man.html "Anthony J. Bentley" 28 Dec 2010 21:58:40 -0700 - check compatibility with COHERENT troff: http://www.nesssoftware.com/home/mwc/source.php - check compatibility with the man(7) formatter https://raw.githubusercontent.com/rofl0r/hardcore-utils/master/man.c - check compatibility with http://ikiwiki.info/plugins/contrib/mandoc/ https://github.com/schmonz/ikiwiki/compare/mandoc Amitai Schlair Mon, 19 May 2014 14:05:53 -0400 + +- check compatibility with + https://git.sr.ht/~sircmpwn/scdoc - check features of the Slackware man.conf(5) format Carsten Kunze Wed, 11 Mar 2015 17:57:24 +0100 ************************************************************************ * formatting issues: ugly output ************************************************************************ - revisit empty in-line macros look at the difference between "Em x Em ." and "Sq x Em ." Carsten Kunze Fri, 12 Dec 2014 00:15:41 +0100 loc *** exist *** algo *** size * imp ** - a column list with blank `Ta' cells triggers a spurious start-with-whitespace printing of a newline - In .Bl -column, .It a"bc" shows the quotes in groff, but not in mandoc loc * exist *** algo ** size * imp ** - In .Bl -column, .It Em AuthenticationKey Length ought to render "Key Length" with emphasis, too, see OpenBSD iked.conf(5). reported again Nicolas Joly via wiz@ Wed, 12 Oct 2011 00:20:00 +0200 loc * exist *** algo *** size ** imp *** - empty phrases in .Bl column produce too few blanks try e.g. .Bl -column It Ta Ta reported by millert Fri, 02 Apr 2010 16:13:46 -0400 loc * exist *** algo *** size * imp ** - .%T can have trailing punctuation. Currently, it puts the trailing punctuation into a trailing MDOC_TEXT element inside its own scope. That element should rather be outside its scope, such that the punctuation does not get underlines. This is not trivial to implement because .%T then needs some features of in_line_eoln() - slurp all arguments into one single text element - and one feature of in_line() - put trailing punctuation out of scope. Found in mount_nfs(8) and exports(5), search for "Appendix". loc ** exist ** algo *** size * imp ** - Trailing punctuation after .%T triggers EOS spacing, at least outside .Rs (eek!). Simply setting ARGSFL_DELIM for .%T is not the right solution, it sends mandoc into an endless loop. reported by Nicolas Joly Sat, 17 Nov 2012 11:49:54 +0100 loc * exist ** algo ** size * imp ** - global variables in the SYNOPSIS of section 3 pages .Vt vs .Vt/.Va vs .Ft/.Va vs .Ft/.Fa ... from kristaps@ Tue, 08 Jun 2010 11:13:32 +0200 - implicit whitespace around inline equations example code: where '$times$' denotes matrix multiplication must not have an HTML line break, nor a blank, before partial solution: html.c {"math", HTML_NLINSIDE | HTML_INDENT}, bentley@ Thu, 13 Jul 2017 19:00:59 -0600 - in enclosures, mandoc sometimes fancies a bogus end of sentence reminded by jmc@ Thu, 23 Sep 2010 18:13:39 +0059 loc * exist ** algo *** size * imp *** - a line starting with "\fB something" counts as starting with whitespace and triggers a line break; found in audio/normalize-mp3(1) This will become easier once escape sequences are represented by syntax tree nodes. loc ** exist * algo ** size * imp ** - formatting /usr/local/man/man1/latex2man.1 with groff and mandoc reveals lots of bugs both in groff and mandoc... reported by bentley@ Wed, 22 May 2013 23:49:30 -0600 --- PostScript and PDF issues ------------------------------------------ - PDF output doesn't use a monospaced font for .Bd -literal Example: "mandoc -Tpdf afterboot.8 > output.pdf && pdfviewer output.pdf". Search the text "Routing tables". Also check what PostScript mode does when fixing this. reported by juanfra@ Wed, 04 Jun 2014 21:44:58 +0200 instructions from juanfra@ Wed, 11 Jun 2014 02:21:01 +0200 add a new <> block to the PDF files with /BaseFont /Courier and change the /Name from /F0 to the new font (/F5 (?)). re-reported by tb@ Mon, 16 Mar 2015 16:47:21 +0100 loc * exist ** algo ** size * imp ** --- HTML issues -------------------------------------------------------- - .Bf at the beginning of a paragraph inserts a bogus 1ex horizontal space, see for example random(3). Introduced in http://mdocml.bsd.lv/cgi-bin/cvsweb/mdoc_html.c.diff?r1=1.91&r2=1.92 reported by deraadt@ Mon, 28 Sep 2015 20:14:13 -0600 (MDT) loc ** exist ** algo ** size * imp * - jsg on icb, Nov 3, 2014: try to guess Xr in man(7) for hyperlinking and render them with https://github.com/Debian/debiman/issues/15 loc * exist * algo ** size ** imp ** - The tables used to render the three-part page headers actually force the width of the to the max-width given for . Not yet sure how to fix that... Observed by an Anonymous Coward on undeadly.org: http://undeadly.org/cgi?action=article&sid=20140925064244&pid=1 loc * exist * algo ** size * imp *** - generate tags in HTML idea from florian@ Tue, 7 Apr 2015 00:26:28 +0000 may be possible to implement with .Lk img://something.png alt_text - check https://github.com/trentm/mdocml ************************************************************************ * formatting issues: gratuitous differences ************************************************************************ - .Fn reopens a new scope after punctuation in mandoc, but closes its scope for good in groff. Do we want to change mandoc or groff? Steffen Nurpmeso Sat, 08 Nov 2014 13:34:59 +0100 loc * exist ** algo ** size * imp ** - In .Bl -enum -width 0n, groff continues one the same line after the number, mandoc breaks the line. mail to kristaps@ Mon, 20 Jul 2009 02:21:39 +0200 loc * exist ** algo ** size * imp ** - .Pp between two .It in .Bl -column should produce one, not two blank lines, see e.g. login.conf(5). reported by jmc@ Sun, 17 Apr 2011 14:04:58 +0059 reported again by sthen@ Wed, 18 Jan 2012 02:09:39 +0000 (UTC) loc * exist *** algo ** size * imp ** - If the *first* line after .It is .Pp, break the line right after the tag, do not pad with space characters before breaking. See the description of the a, c, and i commands in sed(1). loc * exist ** algo ** size * imp ** - If the first line after .It is .D1, do not assert a blank line in between, see for example tmux(1). reported by nicm@ 13 Jan 2011 00:18:57 +0000 loc * exist ** algo ** size * imp ** - Trailing punctuation after .It should trigger EOS spacing. reported by Nicolas Joly Sat, 17 Nov 2012 11:49:54 +0100 Probably, this should be fixed somewhere in termp_it_pre(), not sure. loc * exist ** algo ** size * imp ** - When the -width string contains macros, the macros must be rendered before measuring the width, for example .Bl -tag -width ".Dv message" in magic(5), located in src/usr.bin/file, is the same as -width 7n, not -width 11n. The same applies to .Bl -column column widths; reported again by Nicolas Joly Thu, 1 Mar 2012 13:41:26 +0100 via wiz@ 5 Mar reported again by Franco Fichtner Fri, 27 Sep 2013 21:02:28 +0200 reported again by Bruce Evans Fri, 17 Feb 2017 21:22:44 +0100 via bapt@ loc *** exist *** algo *** size ** imp *** An easy partial fix would be to just skip the first word if it starts with a dot, including any following white space, when measuring. loc * exist * algo * size * imp *** - The \& zero-width character counts as output. That is, when it is alone on a line between two .Pp, we want three blank lines, not two as in mandoc. loc ** exist ** algo ** size * imp ** - Sequences of multiple man(7) paragraphs (.PP, .IP) interspersed with .ps and .nf/.fi produce execessive blank lines, see libJudy and graphics/dcmtk. The parser reorg may help with this. - trailing whitespace must be ignored even when followed by a font escape, see for example makes \fBdig \fR operate in batch mode in dig(1). loc ** exist ** algo ** size * imp ** ************************************************************************ * warning issues ************************************************************************ - warn about duplicate .Sh/.Ss heads gre(4): Rename duplicate sections 20 Apr 2018 15:27:33 +0200 loc * exist * algo * size * imp ** - style message about macros inside .Bd -literal and .Dl, in particular font changing macros like .Cm, .Ar, .Fa (from the mdoclint TODO) - style message about mismatches between the section number in the file name (if it is known) and the section number in .Dt (from the mdoclint TODO) - style message about NULL without .Dv (from the mdoclint TODO) - style message about error constants without .Er (from the mdoclint TODO) - warn when .Sh or .Ss contain other macros Steffen Nurpmeso, savannah.gnu.org/bugs/index.php?45034 loc * exist * algo * size * imp ** - style message about violations of the convention .An name Aq Mt localpart@domain in AUTHORS (from the mdoclint TODO) - warn about attempts to call non-callable macros Steffen Nurpmeso Tue, 11 Nov 2014 22:55:16 +0100 Note that formatting is inconsistent in groff. .Fn Po prints "Po()", .Ar Sh prints "file ..." and no "Sh". Relatively hard because the relevant code is scattered all over mdoc_macro.c and all subtly different. loc ** exist ** algo ** size ** imp ** - warn about punctuation - e.g. ',' and ';' - at the beginning of a text line, if it is likely intended to follow the preceding output without intervening whitespace, in particular after a macro line (from the mdoclint TODO) - makewhatis -p complains about language subdirectories: /usr/local/man//ru: Unknown directory part ************************************************************************ * documentation issues ************************************************************************ - mark macros as: page structure domain, manual domain, general text domain is this useful? - mention /usr/share/misc/mdoc.template in mdoc(7)? - Is all the content from http://www.std.com/obi/BSD/doc/usd/28.tbl/tbl covered in tbl(7)? ************************************************************************ * performance issues ************************************************************************ - the PDF file is HUGE: this can be reduced by using relative offsets ************************************************************************ * structural issues ************************************************************************ - POSIX says in the documentation of sysconf(3) that PATH_MAX is allowed to be so large that it is a bad idea to use it for sizing static buffers. So use dynamic buffers throughout. See the file test-PATH_MAX.c for details. Found by Aaron M. Ucko in the GNU Hurd via Bdale Garbee, https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=829624 - Is it possible to further simplify ENDBODY_SPACE? - Find better ways to prevent endless loops in roff(7) macro and string expansion. - make buffers for parsing functions const christos@ via wiz@ Fri, 18 Dec 2015 17:10:01 +0100 - struct mparse refactoring Steffen Nurpmeso Thu, 04 Sep 2014 12:50:00 +0200 ************************************************************************ * CGI issues ************************************************************************ - Enable HTTP compression by detecting gzip encoding and filtering output through libz. - Privilege separation (see OpenSSH). - Enable caching support via HTTP 304 and If-Modified-Since. ************************************************************************ * to improve in the groff_mdoc(7) macros ************************************************************************ - .Cd # arch1, arch2 in section 4 pages: find better way to indicate multiple architectures, maybe: allow .Dt vgafb 4 "macppc sparc64" already shown as "Device Drivers Manual (macppc sparc64)" for apropos, make that "vgafb(4) - macppc # sparc64" instead of "- all" groff can be made to show multiple arches, too, but it is tedious to do the string parsing in roff code... jmc@ 23 Apr 2018 07:24:52 +0100 [man for vgafb(4)...] loc ** exist ** algo * size * imp *** - use uname(1) to set doc-default-operating-system at install time tobimensch Mon, 1 Dec 2014 00:25:07 +0100 - apostrophe (39), circumflex (94), grave (96), tilde (126) in manuals: \(aq, \(ha, \`, \(ti Re: [Groff] ASCII Minus Sign in man Pages. bentley@ 26 Apr 2017 10:02:06 -0600 Do we need to fix existing manuals? Do we need to fix the definition of the mdoc(7) language? Index: head/contrib/mandoc/arch.c =================================================================== --- head/contrib/mandoc/arch.c (revision 350350) +++ head/contrib/mandoc/arch.c (revision 350351) @@ -1,54 +1,54 @@ -/* $Id: arch.c,v 1.14 2019/03/04 13:01:57 schwarze Exp $ */ +/* $Id: arch.c,v 1.15 2019/05/21 07:52:00 schwarze Exp $ */ /* * Copyright (c) 2017, 2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 "config.h" #include #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", "socppc", "sparc64", NULL + "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; } Index: head/contrib/mandoc/cgi.c =================================================================== --- head/contrib/mandoc/cgi.c (revision 350350) +++ head/contrib/mandoc/cgi.c (revision 350351) @@ -1,1254 +1,1253 @@ -/* $Id: cgi.c,v 1.166 2019/03/06 12:32:41 schwarze Exp $ */ +/* $Id: cgi.c,v 1.167 2019/07/10 12:49:20 schwarze Exp $ */ /* * Copyright (c) 2011, 2012 Kristaps Dzonsons * Copyright (c) 2014, 2015, 2016, 2017, 2018 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #if HAVE_ERR #include #endif #include #include #include #include #include #include #include #include #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 *p); static void parse_manpath_conf(struct req *); static void parse_path_info(struct req *req, const char *path); 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 *, 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("""); break; case '&': printf("&"); break; case '>': printf(">"); break; case '<': printf("<"); 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" "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("\n" "\n" "\n" " \n" " \n" " \n" " ", 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\n" "\n" "\n", CUSTOMIZE_TITLE); resp_copy(MAN_DIR "/header.html"); } static void resp_end_html(void) { resp_copy(MAN_DIR "/footer.html"); puts("\n" ""); } static void resp_searchform(const struct req *req, enum focus focus) { int i; printf("
\n" "
\n" " Manual Page Search Parameters\n", scriptname); /* Write query input box. */ printf(" q.query != NULL) html_print(req->q.query); printf( "\" size=\"40\""); if (focus == FOCUS_QUERY) printf(" autofocus"); puts(">"); /* Write submission buttons. */ printf( " \n" " \n" "
\n"); /* Write section selector. */ puts(" "); /* Write architecture selector. */ printf( " "); /* Write manpath selector. */ if (req->psz > 1) { puts(" "); } puts("
\n" "
"); } 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("

\n" "This web interface is documented in the\n" "man.cgi(8)\n" "manual, and the\n" "apropos(1)\n" "manual explains the query syntax.\n" "

\n", scriptname, *scriptname == '\0' ? "" : "/", scriptname, *scriptname == '\0' ? "" : "/"); resp_end_html(); } static void pg_noresult(const struct req *req, const char *msg) { resp_begin_html(200, NULL, NULL); resp_searchform(req, FOCUS_QUERY); puts("

"); puts(msg); puts("

"); resp_end_html(); } static void pg_error_badrequest(const char *msg) { resp_begin_html(400, "Bad Request", NULL); puts("

Bad Request

\n" "

\n"); puts(msg); printf("Try again from the\n" "main page.\n" "

", scriptname); resp_end_html(); } static void pg_error_internal(void) { resp_begin_html(500, "Internal Server Error", NULL); puts("

Internal Server Error

"); 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(""); for (i = 0; i < sz; i++) { printf(" \n" " \n" " \n" " "); } puts("
" "q.manpath, req->p[0])) printf("%s/", req->q.manpath); printf("%s\">", r[i].file); html_print(r[i].names); printf(""); html_print(r[i].output); puts("
"); } if (req->q.equal || sz == 1) { puts("
"); 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("

You specified an invalid manual file.

"); return; } puts("
\n" "
");
 
 	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("");
 				if (bold)
 					printf("");
 				italic = bold = 0;
 				html_putchar(p[i]);
 				continue;
 			} else if (i + 2 >= len)
 				continue;
 
 			/* Italic mode. */
 
 			if ('_' == p[i]) {
 				if (bold)
 					printf("");
 				if ( ! italic)
 					printf("");
 				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("");
 				if (bold)
 					printf("");
 				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("");
 				if (bold)
 					printf("");
 				italic = bold = 0;
 				putchar('+');
 				i += 2;
 				continue;
 			}
 
 			/* Bold mode. */
 
 			if (italic)
 				printf("");
 			if ( ! bold)
 				printf("");
 			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("");
 		if (bold)
 			printf("");
 
 		if (i == len - 1 && p[i] != '\n')
 			html_putchar(p[i]);
 
 		putchar('\n');
 	}
 	free(p);
 
 	puts("
\n" "
"); 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("

You specified an invalid manual file.

"); 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"); - conf.toc = 1; 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, "You entered an invalid query."); else if (ressz == 0) pg_noresult(req, "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; #if HAVE_PLEDGE /* * 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; } #endif /* 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); } } Index: head/contrib/mandoc/configure =================================================================== --- head/contrib/mandoc/configure (revision 350350) +++ head/contrib/mandoc/configure (revision 350351) @@ -1,636 +1,636 @@ #!/bin/sh # -# $Id: configure,v 1.70 2019/03/06 16:04:31 schwarze Exp $ +# $Id: configure,v 1.71 2019/07/01 22:56:24 schwarze Exp $ # # Copyright (c) 2014-2019 Ingo Schwarze # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. set -e [ -w config.log ] && mv config.log config.log.old [ -w config.h ] && mv config.h config.h.old # Output file descriptor usage: # 1 (stdout): config.h, Makefile.local # 2 (stderr): original stderr, usually to the console # 3: config.log exec 3> config.log echo "file config.log: writing..." # --- default settings ------------------------------------------------- # Initialize all variables here, # such that nothing can leak in from the environment. SOURCEDIR=`dirname "$0"` MANPATH_BASE="/usr/share/man:/usr/X11R6/man" MANPATH_DEFAULT="/usr/share/man:/usr/X11R6/man:/usr/local/man" OSENUM= OSNAME= UTF8_LOCALE= CC=`printf "all:\\n\\t@echo \\\$(CC)\\n" | env -i make -sf -` CFLAGS= LDADD= LDFLAGS= LD_NANOSLEEP= LD_OHASH= LD_RECVMSG= STATIC= BUILD_CGI=0 BUILD_CATMAN=0 INSTALL_LIBMANDOC=0 HAVE_CMSG= HAVE_CMSG_XPG42=0 HAVE_DIRENT_NAMLEN= HAVE_EFTYPE= HAVE_ENDIAN= HAVE_ERR= HAVE_FTS= HAVE_FTS_COMPARE_CONST= HAVE_GETLINE= HAVE_GETSUBOPT= HAVE_ISBLANK= HAVE_LESS_T= HAVE_MKDTEMP= HAVE_NANOSLEEP= HAVE_NTOHL= HAVE_O_DIRECTORY= HAVE_OHASH= HAVE_PATH_MAX= HAVE_PLEDGE= HAVE_PROGNAME= HAVE_REALLOCARRAY= HAVE_RECALLOCARRAY= HAVE_RECVMSG= HAVE_REWB_BSD= HAVE_REWB_SYSV= HAVE_SANDBOX_INIT= HAVE_STRCASESTR= HAVE_STRINGLIST= HAVE_STRLCAT= HAVE_STRLCPY= HAVE_STRNDUP= HAVE_STRPTIME= HAVE_STRSEP= HAVE_STRTONUM= HAVE_SYS_ENDIAN= HAVE_VASPRINTF= HAVE_WCHAR= NEED_GNU_SOURCE=0 NEED_OPENBSD_SOURCE=0 PREFIX="/usr/local" BINDIR= SBINDIR= BIN_FROM_SBIN= INCLUDEDIR= LIBDIR= MANDIR= HOMEBREWDIR= WWWPREFIX="/var/www" HTDOCDIR= CGIBINDIR= BINM_APROPOS="apropos" BINM_CATMAN="catman" BINM_MAKEWHATIS="makewhatis" BINM_MAN="man" BINM_SOELIM="soelim" BINM_WHATIS="whatis" MANM_MAN="man" MANM_MANCONF="man.conf" MANM_MDOC="mdoc" MANM_ROFF="roff" MANM_EQN="eqn" MANM_TBL="tbl" INSTALL="install" INSTALL_PROGRAM= INSTALL_LIB= INSTALL_MAN= INSTALL_DATA= LN="ln -f" # --- manual settings from configure.local ----------------------------- if [ -r ./configure.local ]; then echo "file configure.local: reading..." 1>&2 echo "file configure.local: reading..." 1>&3 cat ./configure.local 1>&3 . ./configure.local else echo "file configure.local: no (fully automatic configuration)" 1>&2 echo "file configure.local: no (fully automatic configuration)" 1>&3 fi echo 1>&3 # --- tests functions -------------------------------------------------- # Check whether this HAVE_ setting is manually overridden. # If yes, use the override, if no, do not decide anything yet. # Arguments: test file name, test var name, manual value ismanual() { [ -z "${3}" ] && return 1 echo "tested ${1}: HAVE_${2}=${3} (manual)" 1>&2 echo "tested ${1}: HAVE_${2}=${3} (manual)" 1>&3 echo 1>&3 return 0 } # Run a single autoconfiguration test. # In case of success, enable the feature. # In case of failure, do not decide anything yet. # Arguments: test file name, test var name, additional CFLAGS singletest() { n=${1}${3}${4} cat 1>&3 << __HEREDOC__ testing ${n} ... ${COMP} -o test-${1} test-${1}.c ${3} ${4} __HEREDOC__ if ${COMP} -o "test-${1}" "${SOURCEDIR}/test-${1}.c" ${3} ${4} 1>&3 2>&3 then echo "partial result of ${n}: ${CC} succeeded" 1>&3 else echo "result of ${n}: ${CC} failed with exit status $?" 1>&3 echo "result of compiling ${n}: no" 1>&3 echo 1>&3 return 1 fi if ./test-${1} 1>&3 2>&3; then echo "tested ${n}: yes" 1>&2 echo "result of running ${n}: yes" 1>&3 echo 1>&3 eval HAVE_${2}=1 [ "X$3" = "X-D_GNU_SOURCE" ] && NEED_GNU_SOURCE=1 [ "X$3" = "X-D_OPENBSD_SOURCE" ] && NEED_OPENBSD_SOURCE=1 rm "test-${1}" return 0 else echo "result of ${n}: execution failed with exit status $?" 1>&3 echo "result of running ${n}: no" 1>&3 echo 1>&3 rm "test-${1}" return 1 fi } # Run a complete autoconfiguration test, including the check for # a manual override and disabling the feature on failure. # Arguments: test file name, test var name, additional CFLAGS runtest() { eval _manual=\${HAVE_${2}} ismanual "${1}" "${2}" "${_manual}" && return 0 singletest "${1}" "${2}" "${3}" "${4}" && return 0 echo "tested ${1}${3}${4}: no" 1>&2 eval HAVE_${2}=0 return 1 } # Select a UTF-8 locale. get_locale() { [ -n "${HAVE_WCHAR}" ] && [ "${HAVE_WCHAR}" -eq 0 ] && return 0 ismanual UTF8_LOCALE UTF8_LOCALE "$UTF8_LOCALE" && return 0 echo "testing UTF8_LOCALE ..." 1>&3 UTF8_LOCALE=`locale -a | grep -i '^en_US\.UTF-*8$' | head -n 1` if [ -z "${UTF8_LOCALE}" ]; then UTF8_LOCALE=`locale -a | grep -i '\.UTF-*8' | head -n 1` [ -n "${UTF8_LOCALE}" ] || return 1 fi echo "selected UTF8_LOCALE=${UTF8_LOCALE}" 1>&2 echo "selected UTF8_LOCALE=${UTF8_LOCALE}" 1>&3 echo 1>&3 return 0; } # --- operating system ------------------------------------------------- if [ -n "${OSENUM}" ]; then echo "OSENUM specified manually: ${OSENUM}" 1>&2 echo "OSENUM specified manually: ${OSENUM}" 1>&3 else OSDETECT=`uname` if [ "X${OSDETECT}" = "XNetBSD" ]; then OSENUM=MANDOC_OS_NETBSD elif [ "X${OSDETECT}" = "XOpenBSD" ]; then OSENUM=MANDOC_OS_OPENBSD else OSENUM=MANDOC_OS_OTHER fi echo "tested operating system: ${OSDETECT} -> OSENUM=${OSENUM}" 1>&2 echo "tested operating system: ${OSDETECT} -> OSENUM=${OSENUM}" 1>&3 unset OSDETECT fi echo 1>&3 # --- compiler options ------------------------------------------------- DEFCFLAGS="-g -W -Wall -Wmissing-prototypes -Wstrict-prototypes -Wwrite-strings -Wno-unused-parameter" if [ -n "${CFLAGS}" ]; then COMP="${CC} ${CFLAGS} -Wno-unused -Werror" else COMP="${CC} ${DEFCFLAGS} -Wno-unused -Werror" fi echo -n "tested ${CC} -W: " 1>&2 echo -n "testing ${CC} -W: " 1>&3 runtest noop WFLAG || true if [ -n "${CFLAGS}" ]; then echo "CFLAGS specified manually:" 1>&3 elif [ ${HAVE_WFLAG} -eq 0 ]; then CFLAGS="-g" else CFLAGS="${DEFCFLAGS}" fi echo "selected CFLAGS=\"${CFLAGS}\"" 1>&2 echo "selected CFLAGS=\"${CFLAGS}\"" 1>&3 echo 1>&3 COMP="${CC} ${CFLAGS}" [ ${HAVE_WFLAG} -eq 0 ] || COMP="${COMP} -Wno-unused -Werror" if [ -n "${STATIC}" ]; then echo "selected STATIC=\"${STATIC}\" (manual)" 1>&2 echo "selected STATIC=\"${STATIC}\" (manual)" 1>&3 echo 1>&3 else runtest noop STATIC -static || true [ ${HAVE_STATIC} -eq 0 ] || STATIC="-static" echo "selected STATIC=\"${STATIC}\"" 1>&2 echo "selected STATIC=\"${STATIC}\"" 1>&3 echo 1>&3 fi # --- tests for config.h ---------------------------------------------- # --- library functions --- runtest dirent-namlen DIRENT_NAMLEN || true runtest be32toh ENDIAN || true runtest be32toh SYS_ENDIAN -DSYS_ENDIAN || true runtest EFTYPE EFTYPE || true runtest err ERR || true runtest getline GETLINE || true singletest getsubopt GETSUBOPT || \ runtest getsubopt GETSUBOPT -D_GNU_SOURCE || true runtest isblank ISBLANK || true runtest mkdtemp MKDTEMP || true runtest ntohl NTOHL || true runtest O_DIRECTORY O_DIRECTORY || true runtest PATH_MAX PATH_MAX || true runtest pledge PLEDGE || true runtest sandbox_init SANDBOX_INIT || true runtest progname PROGNAME || true singletest reallocarray REALLOCARRAY || \ runtest reallocarray REALLOCARRAY -D_OPENBSD_SOURCE || true singletest recallocarray RECALLOCARRAY || \ runtest recallocarray RECALLOCARRAY -D_OPENBSD_SOURCE || true runtest rewb-bsd REWB_BSD || true runtest rewb-sysv REWB_SYSV || true singletest strcasestr STRCASESTR || \ runtest strcasestr STRCASESTR -D_GNU_SOURCE || true runtest stringlist STRINGLIST || true runtest strlcat STRLCAT || true runtest strlcpy STRLCPY || true runtest strndup STRNDUP || true singletest strptime STRPTIME || \ runtest strptime STRPTIME -D_GNU_SOURCE || true runtest strsep STRSEP || true singletest strtonum STRTONUM || \ runtest strtonum STRTONUM -D_OPENBSD_SOURCE || true singletest vasprintf VASPRINTF || \ runtest vasprintf VASPRINTF -D_GNU_SOURCE || true if [ ${HAVE_ENDIAN} -eq 0 -a \ ${HAVE_SYS_ENDIAN} -eq 0 -a \ ${HAVE_NTOHL} -eq 0 ]; then echo "FATAL: no endian conversion functions found" 1>&2 echo "FATAL: no endian conversion functions found" 1>&3 exit 1 fi if ismanual fts FTS ${HAVE_FTS}; then HAVE_FTS_COMPARE_CONST=0 elif runtest fts FTS_COMPARE_CONST -DFTS_COMPARE_CONST; then HAVE_FTS=1 else runtest fts FTS || true fi if ismanual "less -T" LESS_T ${HAVE_LESS_T}; then : elif less -ET /dev/null test-noop.c 1>/dev/null 2>&3; then HAVE_LESS_T=1 echo "tested less -T: yes" 1>&2 echo "tested less -T: yes" 1>&3 echo 1>&3 else HAVE_LESS_T=0 echo "tested less -T: no" 1>&2 echo "tested less -T: no" 1>&3 echo 1>&3 fi # --- wide character and locale support --- if get_locale; then singletest wchar WCHAR -DUTF8_LOCALE=\"${UTF8_LOCALE}\" || \ runtest wchar WCHAR -D_GNU_SOURCE \ -DUTF8_LOCALE=\"${UTF8_LOCALE}\" || true else HAVE_WCHAR=0 echo "tested wchar: no (no UTF8_LOCALE)" 1>&2 echo "tested wchar: no (no UTF8_LOCALE)" 1>&3 echo 1>&3 fi # --- nanosleep --- if [ -n "${LD_NANOSLEEP}" ]; then runtest nanosleep NANOSLEEP "${LD_NANOSLEEP}" || true elif singletest nanosleep NANOSLEEP; then : elif runtest nanosleep NANOSLEEP "-lrt"; then LD_NANOSLEEP="-lrt" fi if [ "${HAVE_NANOSLEEP}" -eq 0 ]; then echo "FATAL: nanosleep: no" 1>&2 echo "FATAL: nanosleep: no" 1>&3 exit 1 fi if [ ${BUILD_CATMAN} -gt 0 ]; then # --- recvmsg --- if [ -n "${LD_RECVMSG}" ]; then runtest recvmsg RECVMSG "${LD_RECVMSG}" || true elif singletest recvmsg RECVMSG; then : elif runtest recvmsg RECVMSG "-lsocket"; then LD_RECVMSG="-lsocket" fi if [ "${HAVE_RECVMSG}" -eq 0 ]; then echo "FATAL: recvmsg: no" 1>&2 echo "FATAL: recvmsg: no" 1>&3 echo "Without recvmsg(2), you cannot BUILD_CATMAN." 1>&2 exit 1 fi # --- cmsg --- if singletest cmsg CMSG; then : elif runtest cmsg CMSG "-D_XPG4_2"; then HAVE_CMSG_XPG42=1 fi if [ "${HAVE_CMSG}" -eq 0 ]; then echo "FATAL: cmsg: no" 1>&2 echo "FATAL: cmsg: no" 1>&3 echo "Without CMSG_FIRSTHDR(3), you cannot BUILD_CATMAN." 1>&2 exit 1 fi fi # --- ohash --- if ismanual ohash OHASH "${HAVE_OHASH}"; then : elif [ -n "${LD_OHASH}" ]; then runtest ohash OHASH "${LD_OHASH}" || true elif singletest ohash OHASH; then : elif runtest ohash OHASH "-lutil"; then LD_OHASH="-lutil" fi if [ "${HAVE_OHASH}" -eq 0 ]; then LD_OHASH= fi # --- LDADD --- LDADD="${LDADD} ${LD_NANOSLEEP} ${LD_RECVMSG} ${LD_OHASH} -lz" echo "selected LDADD=\"${LDADD}\"" 1>&2 echo "selected LDADD=\"${LDADD}\"" 1>&3 echo 1>&3 # --- write config.h --- exec > config.h cat << __HEREDOC__ #ifdef __cplusplus #error "Do not use C++. See the INSTALL file." #endif #if !defined(__GNUC__) || (__GNUC__ < 4) #define __attribute__(x) #endif __HEREDOC__ [ ${NEED_GNU_SOURCE} -eq 0 ] || echo "#define _GNU_SOURCE" [ ${NEED_OPENBSD_SOURCE} -eq 0 ] || echo "#define _OPENBSD_SOURCE" [ ${HAVE_GETLINE} -eq 0 -o \ ${HAVE_REALLOCARRAY} -eq 0 -o ${HAVE_RECALLOCARRAY} -eq 0 -o \ ${HAVE_STRLCAT} -eq 0 -o ${HAVE_STRLCPY} -eq 0 -o \ ${HAVE_STRNDUP} -eq 0 ] \ && echo "#include " [ ${HAVE_VASPRINTF} -eq 0 ] && echo "#include " [ ${HAVE_GETLINE} -eq 0 ] && echo "#include " echo echo "#define MAN_CONF_FILE \"/etc/${MANM_MANCONF}\"" echo "#define MANPATH_BASE \"${MANPATH_BASE}\"" echo "#define MANPATH_DEFAULT \"${MANPATH_DEFAULT}\"" echo "#define OSENUM ${OSENUM}" [ -n "${OSNAME}" ] && echo "#define OSNAME \"${OSNAME}\"" [ -n "${UTF8_LOCALE}" ] && echo "#define UTF8_LOCALE \"${UTF8_LOCALE}\"" [ -n "${HOMEBREWDIR}" ] && echo "#define HOMEBREWDIR \"${HOMEBREWDIR}\"" [ ${HAVE_EFTYPE} -eq 0 ] && echo "#define EFTYPE EINVAL" [ ${HAVE_O_DIRECTORY} -eq 0 ] && echo "#define O_DIRECTORY 0" [ ${HAVE_PATH_MAX} -eq 0 ] && echo "#define PATH_MAX 4096" if [ ${HAVE_ENDIAN} -eq 0 -a ${HAVE_SYS_ENDIAN} -eq 0 ]; then echo "#define be32toh ntohl" echo "#define htobe32 htonl" fi cat << __HEREDOC__ #define HAVE_CMSG_XPG42 ${HAVE_CMSG_XPG42} #define HAVE_DIRENT_NAMLEN ${HAVE_DIRENT_NAMLEN} #define HAVE_ENDIAN ${HAVE_ENDIAN} #define HAVE_ERR ${HAVE_ERR} #define HAVE_FTS ${HAVE_FTS} #define HAVE_FTS_COMPARE_CONST ${HAVE_FTS_COMPARE_CONST} #define HAVE_GETLINE ${HAVE_GETLINE} #define HAVE_GETSUBOPT ${HAVE_GETSUBOPT} #define HAVE_ISBLANK ${HAVE_ISBLANK} #define HAVE_LESS_T ${HAVE_LESS_T} #define HAVE_MKDTEMP ${HAVE_MKDTEMP} #define HAVE_NTOHL ${HAVE_NTOHL} #define HAVE_PLEDGE ${HAVE_PLEDGE} #define HAVE_PROGNAME ${HAVE_PROGNAME} #define HAVE_REALLOCARRAY ${HAVE_REALLOCARRAY} #define HAVE_RECALLOCARRAY ${HAVE_RECALLOCARRAY} #define HAVE_REWB_BSD ${HAVE_REWB_BSD} #define HAVE_REWB_SYSV ${HAVE_REWB_SYSV} #define HAVE_SANDBOX_INIT ${HAVE_SANDBOX_INIT} #define HAVE_STRCASESTR ${HAVE_STRCASESTR} #define HAVE_STRINGLIST ${HAVE_STRINGLIST} #define HAVE_STRLCAT ${HAVE_STRLCAT} #define HAVE_STRLCPY ${HAVE_STRLCPY} #define HAVE_STRNDUP ${HAVE_STRNDUP} #define HAVE_STRPTIME ${HAVE_STRPTIME} #define HAVE_STRSEP ${HAVE_STRSEP} #define HAVE_STRTONUM ${HAVE_STRTONUM} #define HAVE_SYS_ENDIAN ${HAVE_SYS_ENDIAN} #define HAVE_VASPRINTF ${HAVE_VASPRINTF} #define HAVE_WCHAR ${HAVE_WCHAR} #define HAVE_OHASH ${HAVE_OHASH} #define BINM_APROPOS "${BINM_APROPOS}" #define BINM_CATMAN "${BINM_CATMAN}" #define BINM_MAKEWHATIS "${BINM_MAKEWHATIS}" #define BINM_MAN "${BINM_MAN}" #define BINM_SOELIM "${BINM_SOELIM}" #define BINM_WHATIS "${BINM_WHATIS}" __HEREDOC__ if [ ${HAVE_ERR} -eq 0 ]; then echo "extern void err(int, const char *, ...);" echo "extern void errx(int, const char *, ...);" echo "extern void warn(const char *, ...);" echo "extern void warnx(const char *, ...);" fi [ ${HAVE_GETLINE} -eq 0 ] && \ echo "extern ssize_t getline(char **, size_t *, FILE *);" [ ${HAVE_GETSUBOPT} -eq 0 ] && \ echo "extern int getsubopt(char **, char * const *, char **);" [ ${HAVE_ISBLANK} -eq 0 ] && \ echo "extern int isblank(int);" [ ${HAVE_MKDTEMP} -eq 0 ] && \ echo "extern char *mkdtemp(char *);" if [ ${HAVE_PROGNAME} -eq 0 ]; then - echo "extern const char *getprogname(void);" + echo "extern const char *getprogname(void);" echo "extern void setprogname(const char *);" fi [ ${HAVE_REALLOCARRAY} -eq 0 ] && \ echo "extern void *reallocarray(void *, size_t, size_t);" [ ${HAVE_RECALLOCARRAY} -eq 0 ] && \ echo "extern void *recallocarray(void *, size_t, size_t, size_t);" [ ${HAVE_STRCASESTR} -eq 0 ] && \ echo "extern char *strcasestr(const char *, const char *);" [ ${HAVE_STRLCAT} -eq 0 ] && \ echo "extern size_t strlcat(char *, const char *, size_t);" [ ${HAVE_STRLCPY} -eq 0 ] && \ echo "extern size_t strlcpy(char *, const char *, size_t);" [ ${HAVE_STRNDUP} -eq 0 ] && \ echo "extern char *strndup(const char *, size_t);" [ ${HAVE_STRSEP} -eq 0 ] && \ echo "extern char *strsep(char **, const char *);" [ ${HAVE_STRTONUM} -eq 0 ] && \ echo "extern long long strtonum(const char *, long long, long long, const char **);" [ ${HAVE_VASPRINTF} -eq 0 ] && \ echo "extern int vasprintf(char **, const char *, va_list);" echo "file config.h: written" 1>&2 echo "file config.h: written" 1>&3 # --- tests for Makefile.local ----------------------------------------- exec > Makefile.local [ -z "${BINDIR}" ] && BINDIR="${PREFIX}/bin" [ -z "${SBINDIR}" ] && SBINDIR="${PREFIX}/sbin" [ -z "${BIN_FROM_SBIN}" ] && BIN_FROM_SBIN="../bin" [ -z "${INCLUDEDIR}" ] && INCLUDEDIR="${PREFIX}/include/mandoc" [ -z "${LIBDIR}" ] && LIBDIR="${PREFIX}/lib/mandoc" [ -z "${MANDIR}" ] && MANDIR="${PREFIX}/man" [ -z "${HTDOCDIR}" ] && HTDOCDIR="${WWWPREFIX}/htdocs" [ -z "${CGIBINDIR}" ] && CGIBINDIR="${WWWPREFIX}/cgi-bin" [ -z "${INSTALL_PROGRAM}" ] && INSTALL_PROGRAM="${INSTALL} -m 0555" [ -z "${INSTALL_LIB}" ] && INSTALL_LIB="${INSTALL} -m 0444" [ -z "${INSTALL_MAN}" ] && INSTALL_MAN="${INSTALL} -m 0444" [ -z "${INSTALL_DATA}" ] && INSTALL_DATA="${INSTALL} -m 0444" BUILD_TARGETS= [ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="man.cgi" [ ${BUILD_CATMAN} -gt 0 ] && \ BUILD_TARGETS="${BUILD_TARGETS} mandocd catman" INSTALL_TARGETS= [ ${INSTALL_LIBMANDOC} -gt 0 ] && INSTALL_TARGETS="lib-install" [ ${BUILD_CGI} -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} cgi-install" [ ${BUILD_CATMAN} -gt 0 ] && \ INSTALL_TARGETS="${INSTALL_TARGETS} catman-install" cat << __HEREDOC__ BUILD_TARGETS = ${BUILD_TARGETS} INSTALL_TARGETS = ${INSTALL_TARGETS} CC = ${CC} CFLAGS = ${CFLAGS} LDADD = ${LDADD} LDFLAGS = ${LDFLAGS} STATIC = ${STATIC} PREFIX = ${PREFIX} BINDIR = ${BINDIR} SBINDIR = ${SBINDIR} BIN_FROM_SBIN = ${BIN_FROM_SBIN} INCLUDEDIR = ${INCLUDEDIR} LIBDIR = ${LIBDIR} MANDIR = ${MANDIR} WWWPREFIX = ${WWWPREFIX} HTDOCDIR = ${HTDOCDIR} CGIBINDIR = ${CGIBINDIR} BINM_APROPOS = ${BINM_APROPOS} BINM_CATMAN = ${BINM_CATMAN} BINM_MAKEWHATIS = ${BINM_MAKEWHATIS} BINM_MAN = ${BINM_MAN} BINM_SOELIM = ${BINM_SOELIM} BINM_WHATIS = ${BINM_WHATIS} MANM_MAN = ${MANM_MAN} MANM_MANCONF = ${MANM_MANCONF} MANM_MDOC = ${MANM_MDOC} MANM_ROFF = ${MANM_ROFF} MANM_EQN = ${MANM_EQN} MANM_TBL = ${MANM_TBL} INSTALL = ${INSTALL} INSTALL_PROGRAM = ${INSTALL_PROGRAM} INSTALL_LIB = ${INSTALL_LIB} INSTALL_MAN = ${INSTALL_MAN} INSTALL_DATA = ${INSTALL_DATA} LN = ${LN} __HEREDOC__ echo "file Makefile.local: written" 1>&2 echo "file Makefile.local: written" 1>&3 exit 0 Index: head/contrib/mandoc/dbm.c =================================================================== --- head/contrib/mandoc/dbm.c (revision 350350) +++ head/contrib/mandoc/dbm.c (revision 350351) @@ -1,480 +1,480 @@ -/* $Id: dbm.c,v 1.6 2018/11/19 19:22:07 schwarze Exp $ */ +/* $Id: dbm.c,v 1.7 2019/07/01 22:56:24 schwarze Exp $ */ /* * Copyright (c) 2016 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 "config.h" #include #if HAVE_ENDIAN #include #elif HAVE_SYS_ENDIAN #include #elif HAVE_NTOHL #include #endif #if HAVE_ERR #include #endif #include #include #include #include #include #include #include "mansearch.h" #include "dbm_map.h" #include "dbm.h" 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 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; + 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 ¯o; } /* * 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); } Index: head/contrib/mandoc/dbm_map.h =================================================================== --- head/contrib/mandoc/dbm_map.h (revision 350350) +++ head/contrib/mandoc/dbm_map.h (revision 350351) @@ -1,29 +1,29 @@ -/* $Id: dbm_map.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */ +/* $Id: dbm_map.h,v 1.2 2019/07/01 22:56:24 schwarze Exp $ */ /* * Copyright (c) 2016 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 + * 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 *); Index: head/contrib/mandoc/eqn.7 =================================================================== --- head/contrib/mandoc/eqn.7 (revision 350350) +++ head/contrib/mandoc/eqn.7 (revision 350351) @@ -1,500 +1,500 @@ -.\" $Id: eqn.7,v 1.37 2017/09/04 10:35:27 schwarze Exp $ +.\" $Id: eqn.7,v 1.38 2019/04/23 17:57:49 schwarze Exp $ .\" .\" Copyright (c) 2011 Kristaps Dzonsons .\" Copyright (c) 2014 Ingo Schwarze .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: September 4 2017 $ +.Dd $Mdocdate: April 23 2019 $ .Dt EQN 7 .Os .Sh NAME .Nm eqn .Nd eqn language reference for mandoc .Sh DESCRIPTION The .Nm eqn language is an equation-formatting language. It is used within .Xr mdoc 7 and .Xr man 7 .Ux manual pages. It describes the .Em structure of an equation, not its mathematical meaning. This manual describes the .Nm language accepted by the .Xr mandoc 1 utility, which corresponds to the Second Edition .Nm specification (see .Sx SEE ALSO for references). .Pp Equations within .Xr mdoc 7 or .Xr man 7 documents are enclosed by the standalone .Sq \&.EQ and .Sq \&.EN tags. Equations are multi-line blocks consisting of formulas and control statements. .Sh EQUATION STRUCTURE Each equation is bracketed by .Sq \&.EQ and .Sq \&.EN strings. .Em Note : these are not the same as .Xr roff 7 macros, and may only be invoked as .Sq \&.EQ . .Pp The equation grammar is as follows, where quoted strings are case-sensitive literals in the input: .Bd -literal -offset indent eqn : box | eqn box box : text | \(dq{\(dq eqn \(dq}\(dq | \(dqdefine\(dq text text | \(dqndefine\(dq text text | \(dqtdefine\(dq text text | \(dqgfont\(dq text | \(dqgsize\(dq text | \(dqset\(dq text text | \(dqundef\(dq text | \(dqsqrt\(dq box | box pos box | box mark | \(dqmatrix\(dq \(dq{\(dq [col \(dq{\(dq list \(dq}\(dq]* \(dq}\(dq | pile \(dq{\(dq list \(dq}\(dq | font box | \(dqsize\(dq text box | \(dqleft\(dq text eqn [\(dqright\(dq text] col : \(dqlcol\(dq | \(dqrcol\(dq | \(dqccol\(dq | \(dqcol\(dq text : [^space\e\(dq]+ | \e\(dq.*\e\(dq pile : \(dqlpile\(dq | \(dqcpile\(dq | \(dqrpile\(dq | \(dqpile\(dq pos : \(dqover\(dq | \(dqsup\(dq | \(dqsub\(dq | \(dqto\(dq | \(dqfrom\(dq mark : \(dqdot\(dq | \(dqdotdot\(dq | \(dqhat\(dq | \(dqtilde\(dq | \(dqvec\(dq | \(dqdyad\(dq | \(dqbar\(dq | \(dqunder\(dq font : \(dqroman\(dq | \(dqitalic\(dq | \(dqbold\(dq | \(dqfat\(dq list : eqn | list \(dqabove\(dq eqn space : [\e^~ \et] .Ed .Pp White-space consists of the space, tab, circumflex, and tilde characters. It is required to delimit tokens consisting of alphabetic characters and it is ignored at other places. Braces and quotes also delimit tokens. If within a quoted string, these space characters are retained. Quoted strings are also not scanned for keywords, glyph names, and expansion of definitions. To print a literal quote character, it can be prepended with a backslash or expressed with the \e(dq escape sequence. .Pp Subequations can be enclosed in braces to pass them as arguments to operation keywords, overriding standard operation precedence. Braces can be nested. To set a brace verbatim, it needs to be enclosed in quotes. .Pp The following text terms are translated into a rendered glyph, if available: alpha, beta, chi, delta, epsilon, eta, gamma, iota, kappa, lambda, mu, nu, omega, omicron, phi, pi, psi, rho, sigma, tau, theta, upsilon, xi, zeta, DELTA, GAMMA, LAMBDA, OMEGA, PHI, PI, PSI, SIGMA, THETA, UPSILON, XI, inter (intersection), union (union), prod (product), int (integral), sum (summation), grad (gradient), del (vector differential), times (multiply), cdot (center-dot), nothing (zero-width space), approx (approximately equals), prime (prime), half (one-half), partial (partial differential), inf (infinity), >> (much greater), << (much less), <\- (left arrow), \-> (right arrow), +\- (plus-minus), != (not equal), == (equivalence), <= (less-than-equal), and >= (more-than-equal). The character escape sequences documented in .Xr mandoc_char 7 can be used, too. .Pp The following control statements are available: .Bl -tag -width Ds .It Cm define Replace all occurrences of a key with a value. Its syntax is as follows: .Pp .D1 Cm define Ar key cvalc .Pp The first character of the value string, .Ar c , is used as the delimiter for the value .Ar val . This allows for arbitrary enclosure of terms (not just quotes), such as .Pp .D1 Cm define Ar foo \(aqbar baz\(aq .D1 Cm define Ar foo cbar bazc .Pp It is an error to have an empty .Ar key or .Ar val . Note that a quoted .Ar key causes errors in some .Nm implementations and should not be considered portable. It is not expanded for replacements. Definitions may refer to other definitions; these are evaluated recursively when text replacement occurs and not when the definition is created. .Pp Definitions can create arbitrary strings, for example, the following is a legal construction. .Bd -literal -offset indent define foo \(aqdefine\(aq foo bar \(aqbaz\(aq .Ed .Pp Self-referencing definitions will raise an error. The .Cm ndefine statement is a synonym for .Cm define , while .Cm tdefine is discarded. .It Cm gfont Set the default font of subsequent output. Its syntax is as follows: .Pp .D1 Cm gfont Ar font .Pp In mandoc, this value is discarded. .It Cm gsize Set the default size of subsequent output. Its syntax is as follows: .Pp .D1 Cm gsize Oo +|\- Oc Ns Ar size .Pp The .Ar size value should be an integer. If prepended by a sign, the font size is changed relative to the current size. .It Cm set Set an equation mode. In mandoc, both arguments are thrown away. Its syntax is as follows: .Pp .D1 Cm set Ar key val .Pp The .Ar key and .Ar val are not expanded for replacements. This statement is a GNU extension. .It Cm undef Unset a previously-defined key. Its syntax is as follows: .Pp .D1 Cm define Ar key .Pp Once invoked, the definition for .Ar key is discarded. The .Ar key is not expanded for replacements. This statement is a GNU extension. .El .Pp Operation keywords have the following semantics: .Bl -tag -width Ds .It Cm above See .Cm pile . .It Cm bar Draw a line over the preceding box. .It Cm bold Set the following box using bold font. .It Cm ccol Like .Cm cpile , but for use in .Cm matrix . .It Cm cpile Like .Cm pile , but with slightly increased vertical spacing. .It Cm dot Set a single dot over the preceding box. .It Cm dotdot Set two dots (dieresis) over the preceding box. .It Cm dyad Set a dyad symbol (left-right arrow) over the preceding box. .It Cm fat A synonym for .Cm bold . .It Cm font Set the second argument using the font specified by the first argument; currently not recognized by the .Xr mandoc 1 .Nm parser. .It Cm from Set the following box below the preceding box, using a slightly smaller font. Used for sums, integrals, limits, and the like. .It Cm hat Set a hat (circumflex) over the preceding box. .It Cm italic Set the following box using italic font. .It Cm lcol Like .Cm lpile , but for use in .Cm matrix . .It Cm left Set the first argument as a big left delimiter before the second argument. As an optional third argument, .Cm right can follow. In that case, the fourth argument is set as a big right delimiter after the second argument. .It Cm lpile Like .Cm cpile , but subequations are left-justified. .It Cm matrix Followed by a list of columns enclosed in braces. All columns need to have the same number of subequations. The columns are set as a matrix. The difference compared to multiple subsequent .Cm pile operators is that in a .Cm matrix , corresponding subequations in all columns line up horizontally, while each .Cm pile does vertical spacing independently. .It Cm over Set a fraction. The preceding box is the numerator, the following box is the denominator. .It Cm pile Followed by a list of subequations enclosed in braces, the subequations being separated by .Cm above keywords. Sets the subequations one above the other, each of them centered. Typically used to represent vectors in coordinate representation. .It Cm rcol Like .Cm rpile , but for use in .Cm matrix . .It Cm right See .Cm left ; .Cm right cannot be used without .Cm left . To set a big right delimiter without a big left delimiter, the following construction can be used: .Pp .D1 Cm left No \(dq\(dq Ar box Cm right Ar delimiter .It Cm roman Set the following box using the default font. .It Cm rpile Like .Cm cpile , but subequations are right-justified. .It Cm size Set the second argument with the font size specified by the first argument; currently ignored by .Xr mandoc 1 . By prepending a plus or minus sign to the first argument, the font size can be selected relative to the current size. .It Cm sqrt Set the square root of the following box. .It Cm sub Set the following box as a subscript to the preceding box. .It Cm sup Set the following box as a superscript to the preceding box. As a special case, if a .Cm sup clause immediately follows a .Cm sub clause as in .Pp .D1 Ar mainbox Cm sub Ar subbox Cm sup Ar supbox .Pp both are set with respect to the same .Ar mainbox , that is, .Ar supbox is set above .Ar subbox . .It Cm tilde Set a tilde over the preceding box. .It Cm to Set the following box above the preceding box, using a slightly smaller font. Used for sums and integrals and the like. As a special case, if a .Cm to clause immediately follows a .Cm from clause as in .Pp .D1 Ar mainbox Cm from Ar frombox Cm to Ar tobox .Pp both are set below and above the same .Ar mainbox . .It Cm under Underline the preceding box. .It Cm vec Set a vector symbol (right arrow) over the preceding box. .El .Pp The binary operations .Cm from , .Cm to , .Cm sub , and .Cm sup group to the right, that is, .Pp .D1 Ar mainbox Cm sup Ar supbox Cm sub Ar subbox .Pp is the same as .Pp .D1 Ar mainbox Cm sup Brq Ar supbox Cm sub Ar subbox .Pp and different from .Pp .D1 Bro Ar mainbox Cm sup Ar supbox Brc Cm sub Ar subbox . .Pp By contrast, .Cm over groups to the left. .Pp In the following list, earlier operations bind more tightly than later operations: .Pp .Bl -enum -compact .It .Cm dyad , .Cm vec , .Cm under , .Cm bar , .Cm tilde , .Cm hat , .Cm dot , .Cm dotdot .It .Cm fat , .Cm roman , .Cm italic , .Cm bold , .Cm size .It .Cm sub , .Cm sup .It .Cm sqrt .It .Cm over .It .Cm from , .Cm to .El .Sh COMPATIBILITY This section documents the compatibility of mandoc .Nm and the troff .Nm implementation (including GNU troff). .Pp .Bl -dash -compact .It The text string .Sq \e\(dq is interpreted as a literal quote in troff. In mandoc, this is interpreted as a comment. .It In troff, The circumflex and tilde white-space symbols map to fixed-width spaces. In mandoc, these characters are synonyms for the space character. .It The troff implementation of .Nm allows for equation alignment with the .Cm mark and .Cm lineup tokens. mandoc discards these tokens. The .Cm back Ar n , .Cm fwd Ar n , .Cm up Ar n , and .Cm down Ar n commands are also ignored. .El .Sh SEE ALSO .Xr mandoc 1 , .Xr man 7 , .Xr mandoc_char 7 , .Xr mdoc 7 , .Xr roff 7 .Rs .%A Brian W. Kernighan .%A Lorinda L. Cherry .%T System for Typesetting Mathematics .%J Communications of the ACM .%V 18 -.%P 151\(en157 +.%P pp. 151\(en157 .%D March, 1975 .Re .Rs .%A Brian W. Kernighan .%A Lorinda L. Cherry .%T Typesetting Mathematics, User's Guide .%D 1976 .Re .Rs .%A Brian W. Kernighan .%A Lorinda L. Cherry .%T Typesetting Mathematics, User's Guide (Second Edition) .%D 1978 .Re .Sh HISTORY The eqn utility, a preprocessor for troff, was originally written by Brian W. Kernighan and Lorinda L. Cherry in 1975. The GNU reimplementation of eqn, part of the GNU troff package, was released in 1989 by James Clark. The eqn component of .Xr mandoc 1 was added in 2011. .Sh AUTHORS This .Nm reference was written by .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv . Index: head/contrib/mandoc/eqn_html.c =================================================================== --- head/contrib/mandoc/eqn_html.c (revision 350350) +++ head/contrib/mandoc/eqn_html.c (revision 350351) @@ -1,245 +1,246 @@ -/* $Id: eqn_html.c,v 1.18 2018/12/13 05:23:38 schwarze Exp $ */ +/* $Id: eqn_html.c,v 1.19 2019/03/17 18:21:45 schwarze Exp $ */ /* * Copyright (c) 2011, 2014 Kristaps Dzonsons * Copyright (c) 2017 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 "config.h" #include #include #include #include #include #include #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); } Index: head/contrib/mandoc/html.c =================================================================== --- head/contrib/mandoc/html.c (revision 350350) +++ head/contrib/mandoc/html.c (revision 350351) @@ -1,987 +1,965 @@ -/* $Id: html.c,v 1.254 2019/03/03 13:02:11 schwarze Exp $ */ +/* $Id: html.c,v 1.255 2019/04/30 15:53:00 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons * Copyright (c) 2011-2015, 2017-2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #include #include #include #include #include #include #include #include #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_NOSTACK (1 << 0) #define HTML_AUTOCLOSE (1 << 1) #define HTML_NLBEFORE (1 << 2) #define HTML_NLBEGIN (1 << 3) #define HTML_NLEND (1 << 4) #define HTML_NLAFTER (1 << 5) #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 << 6) #define HTML_NOINDENT (1 << 7) }; static const struct htmldata htmltags[TAG_MAX] = { {"html", HTML_NLALL}, {"head", HTML_NLALL | HTML_INDENT}, {"body", HTML_NLALL}, {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, {"title", HTML_NLAROUND}, {"div", HTML_NLAROUND}, {"div", 0}, {"section", HTML_NLALL}, {"h1", HTML_NLAROUND}, {"h2", HTML_NLAROUND}, {"span", 0}, {"link", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, {"a", 0}, {"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}, {"p", HTML_NLAROUND | HTML_INDENT}, {"pre", HTML_NLALL | HTML_NOINDENT}, {"var", 0}, {"cite", 0}, {"b", 0}, {"i", 0}, {"code", 0}, {"small", 0}, {"style", HTML_NLALL | HTML_INDENT}, {"math", 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. */ 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, 0); return h; } static void html_reset_internal(struct html *h) { struct tag *tag; char *cp; unsigned int slot; while ((tag = h->tag) != NULL) { h->tag = tag->next; free(tag); } cp = ohash_first(&id_unique, &slot); while (cp != NULL) { free(cp); cp = ohash_next(&id_unique, &slot); } ohash_delete(&id_unique); } void html_reset(void *p) { html_reset_internal(p); mandoc_ohash_init(&id_unique, 4, 0); } 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, "div.Pp { margin: 1ex 0ex; }"); print_endline(h); print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }"); print_endline(h); print_text(h, "span.Pa, span.Ad { font-style: italic; }"); print_endline(h); print_text(h, "span.Ms { font-weight: bold; }"); print_endline(h); print_text(h, "dl.Bl-diag "); print_byte(h, '>'); print_text(h, " dt { font-weight: bold; }"); print_endline(h); print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, " "code.In, code.Fd, code.Fn,"); print_endline(h); print_text(h, "code.Cd { font-weight: bold; " "font-family: inherit; }"); print_tagq(h, t); } -void -print_metaf(struct html *h, enum mandoc_esc deco) +int +html_setfont(struct html *h, enum mandoc_esc font) { - enum htmlfont font; - - switch (deco) { + switch (font) { case ESCAPE_FONTPREV: font = h->metal; break; case ESCAPE_FONTITALIC: - font = HTMLFONT_ITALIC; - break; case ESCAPE_FONTBOLD: - font = HTMLFONT_BOLD; - break; case ESCAPE_FONTBI: - font = HTMLFONT_BI; - break; case ESCAPE_FONTCW: - font = HTMLFONT_CW; + case ESCAPE_FONTROMAN: break; case ESCAPE_FONT: - case ESCAPE_FONTROMAN: - font = HTMLFONT_NONE; + font = ESCAPE_FONTROMAN; break; default: - return; + 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; } - - h->metal = h->metac; - h->metac = font; - - switch (font) { - case HTMLFONT_ITALIC: + switch (h->metac) { + case ESCAPE_FONTITALIC: h->metaf = print_otag(h, TAG_I, ""); break; - case HTMLFONT_BOLD: + case ESCAPE_FONTBOLD: h->metaf = print_otag(h, TAG_B, ""); break; - case HTMLFONT_BI: + case ESCAPE_FONTBI: h->metaf = print_otag(h, TAG_B, ""); print_otag(h, TAG_I, ""); break; - case HTMLFONT_CW: + case ESCAPE_FONTCW: h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); break; default: break; } } void html_close_paragraph(struct html *h) { struct tag *t; for (t = h->tag; t != NULL && t->closed == 0; t = t->next) { switch(t->tag) { case TAG_P: case TAG_PRE: print_tagq(h, t); break; case TAG_A: print_tagq(h, t); continue; default: continue; } break; } } /* * 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; } char * html_make_id(const struct roff_node *n, int unique) { const struct roff_node *nch; char *buf, *bufs, *cp; unsigned int slot; int suffix; 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; /* * 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 */ 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. */ bufs = NULL; suffix = 1; slot = ohash_qlookup(&id_unique, buf); cp = ohash_find(&id_unique, slot); if (cp != NULL) { while (cp != NULL) { free(bufs); if (++suffix > 127) { free(buf); return NULL; } mandoc_asprintf(&bufs, "%s_%d", buf, suffix); slot = ohash_qlookup(&id_unique, bufs); cp = ohash_find(&id_unique, slot); } free(buf); buf = bufs; } ohash_insert(&id_unique, slot, buf); return buf; } static int print_escape(struct html *h, char c) { switch (c) { case '<': print_word(h, "<"); break; case '>': print_word(h, ">"); break; case '&': print_word(h, "&"); break; case '"': print_word(h, """); break; case ASCII_NBRSP: print_word(h, " "); 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; - print_metaf(h, esc); + 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; /* 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, " "); 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 (HTML_AUTOCLOSE & htmltags[tag].flags) 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; } 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, ""); print_endline(h); } void print_gen_comment(struct html *h, struct roff_node *n) { int wantblank; print_word(h, "") == 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) { 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, " "); } - assert(NULL == h->metaf); - switch (h->metac) { - case HTMLFONT_ITALIC: - h->metaf = print_otag(h, TAG_I, ""); - break; - case HTMLFONT_BOLD: - h->metaf = print_otag(h, TAG_B, ""); - break; - case HTMLFONT_BI: - h->metaf = print_otag(h, TAG_B, ""); - print_otag(h, TAG_I, ""); - break; - case HTMLFONT_CW: - h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); - break; - default: - print_indent(h); - break; - } - - assert(word); + assert(h->metaf == NULL); + print_metaf(h); + print_indent(h); 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) { + if (h->metaf != NULL) { print_tagq(h, h->metaf); h->metaf = NULL; } 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) return; if (h->noindent == 0) { h->col = h->indent * 2; for (i = 0; i < h->col; i++) putchar(' '); } h->flags &= ~HTML_NOSPACE; } /* * 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++); } Index: head/contrib/mandoc/html.h =================================================================== --- head/contrib/mandoc/html.h (revision 350350) +++ head/contrib/mandoc/html.h (revision 350351) @@ -1,143 +1,134 @@ -/* $Id: html.h,v 1.102 2019/03/01 10:57:18 schwarze Exp $ */ +/* $Id: html.h,v 1.103 2019/04/30 15:53:00 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons * Copyright (c) 2017, 2018, 2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 htmltag { TAG_HTML, TAG_HEAD, TAG_BODY, TAG_META, TAG_TITLE, TAG_DIV, TAG_IDIV, TAG_SECTION, TAG_H1, TAG_H2, TAG_SPAN, TAG_LINK, TAG_BR, TAG_A, TAG_TABLE, TAG_TR, TAG_TD, TAG_LI, TAG_UL, TAG_OL, TAG_DL, TAG_DT, TAG_DD, TAG_P, TAG_PRE, TAG_VAR, TAG_CITE, TAG_B, TAG_I, TAG_CODE, TAG_SMALL, TAG_STYLE, 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 }; -enum htmlfont { - HTMLFONT_NONE = 0, - HTMLFONT_BOLD, - HTMLFONT_ITALIC, - HTMLFONT_BI, - HTMLFONT_CW, - HTMLFONT_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
 */
 	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 htmlfont	  metal; /* last used font */
-	enum htmlfont	  metac; /* current font mode */
+	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 *);
-void		  print_metaf(struct html *, enum mandoc_esc);
 struct tag	 *print_otag(struct html *, enum htmltag, const char *, ...);
 void		  print_tagq(struct html *, const struct tag *);
 void		  print_stagq(struct html *, const struct tag *);
 void		  print_text(struct html *, const char *);
 void		  print_tblclose(struct html *);
 void		  print_tbl(struct html *, const struct tbl_span *);
 void		  print_eqn(struct html *, const struct eqn_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);
Index: head/contrib/mandoc/lib.in
===================================================================
--- head/contrib/mandoc/lib.in	(revision 350350)
+++ head/contrib/mandoc/lib.in	(revision 350351)
@@ -1,138 +1,138 @@
-/*	$Id: lib.in,v 1.21 2019/03/04 17:35:21 schwarze Exp $ */
+/*	$Id: lib.in,v 1.22 2019/07/01 22:56:24 schwarze Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  * Copyright (c) 2009, 2012 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * copyright notice and this permission notice appear in all copies.
  *
  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
 /*
  * These are all possible .Lb strings.  When a new library is added, add
  * its short-string to the left-hand side and formatted string to the
  * right-hand side.
  *
  * Be sure to escape strings.
  */
 
 LINE("lib80211",	"802.11 Wireless Network Management Library (lib80211, \\-l80211)")
 LINE("libalias",	"Packet Aliasing Library (libalias, \\-lalias)")
 LINE("libarchive",	"Streaming Archive Library (libarchive, \\-larchive)")
 LINE("libarm",		"ARM Architecture Library (libarm, \\-larm)")
 LINE("libarm32",	"ARM32 Architecture Library (libarm32, \\-larm32)")
 LINE("libbe",		"Boot Environment Library (libbe, \\-lbe)")
 LINE("libbluetooth",	"Bluetooth Library (libbluetooth, \\-lbluetooth)")
 LINE("libbsdxml",	"eXpat XML parser library (libbsdxml, \\-lbsdxml)")
 LINE("libbsm",		"Basic Security Module Library (libbsm, \\-lbsm)")
 LINE("libc",		"Standard C\\~Library (libc, \\-lc)")
 LINE("libc_r",		"Reentrant C\\~Library (libc_r, \\-lc_r)")
 LINE("libcalendar",	"Calendar Arithmetic Library (libcalendar, \\-lcalendar)")
 LINE("libcam",		"Common Access Method User Library (libcam, \\-lcam)")
 LINE("libcasper",	"Casper Library (libcasper, \\-lcasper)")
 LINE("libcdk",		"Curses Development Kit Library (libcdk, \\-lcdk)")
 LINE("libcipher",	"FreeSec Crypt Library (libcipher, \\-lcipher)")
 LINE("libcompat",	"Compatibility Library (libcompat, \\-lcompat)")
 LINE("libcrypt",	"Crypt Library (libcrypt, \\-lcrypt)")
 LINE("libcurses",	"Curses Library (libcurses, \\-lcurses)")
-LINE("libcuse", 	"Userland Character Device Library (libcuse, \\-lcuse)")
+LINE("libcuse",		"Userland Character Device Library (libcuse, \\-lcuse)")
 LINE("libdevattr",	"Device attribute and event library (libdevattr, \\-ldevattr)")
 LINE("libdevctl",	"Device Control Library (libdevctl, \\-ldevctl)")
 LINE("libdevinfo",	"Device and Resource Information Utility Library (libdevinfo, \\-ldevinfo)")
 LINE("libdevstat",	"Device Statistics Library (libdevstat, \\-ldevstat)")
 LINE("libdisk",		"Interface to Slice and Partition Labels Library (libdisk, \\-ldisk)")
 LINE("libdl",		"Dynamic Linker Services Filter (libdl, \\-ldl)")
 LINE("libdm",		"Device Mapper Library (libdm, \\-ldm)")
 LINE("libdwarf",	"DWARF Access Library (libdwarf, \\-ldwarf)")
 LINE("libedit",		"Command Line Editor Library (libedit, \\-ledit)")
 LINE("libefi",		"EFI Runtime Services Library (libefi, \\-lefi)")
 LINE("libelf",		"ELF Access Library (libelf, \\-lelf)")
 LINE("libevent",	"Event Notification Library (libevent, \\-levent)")
 LINE("libexecinfo",	"Backtrace Information Library (libexecinfo, \\-lexecinfo)")
 LINE("libfetch",	"File Transfer Library (libfetch, \\-lfetch)")
 LINE("libfsid",		"Filesystem Identification Library (libfsid, \\-lfsid)")
 LINE("libftpio",	"FTP Connection Management Library (libftpio, \\-lftpio)")
 LINE("libform",		"Curses Form Library (libform, \\-lform)")
 LINE("libgeom",		"Userland API Library for Kernel GEOM subsystem (libgeom, \\-lgeom)")
 LINE("libgmock",	"GoogleMock library (libgmock, \\-lgmock)")
 LINE("libgpio",		"General-Purpose Input Output (GPIO) library (libgpio, \\-lgpio)")
 LINE("libgtest",	"GoogleTest library (libgtest, \\-lgtest)")
 LINE("libhammer",	"HAMMER Filesystem Userland Library (libhammer, \\-lhammer)")
 LINE("libi386",		"i386 Architecture Library (libi386, \\-li386)")
 LINE("libintl",		"Internationalized Message Handling Library (libintl, \\-lintl)")
 LINE("libipsec",	"IPsec Policy Control Library (libipsec, \\-lipsec)")
 LINE("libiscsi",	"iSCSI protocol library (libiscsi, \\-liscsi)")
 LINE("libisns",		"Internet Storage Name Service Library (libisns, \\-lisns)")
 LINE("libjail",		"Jail Library (libjail, \\-ljail)")
 LINE("libkcore",	"Kernel Memory Core Access Library (libkcore, \\-lkcore)")
 LINE("libkiconv",	"Kernel-side iconv Library (libkiconv, \\-lkiconv)")
 LINE("libkse",		"N:M Threading Library (libkse, \\-lkse)")
 LINE("libkvm",		"Kernel Data Access Library (libkvm, \\-lkvm)")
 LINE("libm",		"Math Library (libm, \\-lm)")
 LINE("libm68k",		"m68k Architecture Library (libm68k, \\-lm68k)")
 LINE("libmagic",	"Magic Number Recognition Library (libmagic, \\-lmagic)")
 LINE("libmandoc",	"Mandoc Macro Compiler Library (libmandoc, \\-lmandoc)")
 LINE("libmd",		"Message Digest (MD4, MD5, etc.) Support Library (libmd, \\-lmd)")
 LINE("libmemstat",	"Kernel Memory Allocator Statistics Library (libmemstat, \\-lmemstat)")
 LINE("libmenu",		"Curses Menu Library (libmenu, \\-lmenu)")
 LINE("libmj",		"Minimalist JSON library (libmj, \\-lmj)")
 LINE("libnetgraph",	"Netgraph User Library (libnetgraph, \\-lnetgraph)")
 LINE("libnetpgp",	"Netpgp Signing, Verification, Encryption and Decryption (libnetpgp, \\-lnetpgp)")
 LINE("libnetpgpverify",	"Netpgp Verification (libnetpgpverify, \\-lnetpgpverify)")
 LINE("libnpf",		"NPF Packet Filter Library (libnpf, \\-lnpf)")
 LINE("libnv",		"Name/value pairs library (libnv, \\-lnv)")
 LINE("libossaudio",	"OSS Audio Emulation Library (libossaudio, \\-lossaudio)")
 LINE("libpam",		"Pluggable Authentication Module Library (libpam, \\-lpam)")
 LINE("libpanel",	"Z-order for curses windows (libpanel, \\-lpanel)")
 LINE("libpcap",		"Packet capture Library (libpcap, \\-lpcap)")
 LINE("libpci",		"PCI Bus Access Library (libpci, \\-lpci)")
 LINE("libpmc",		"Performance Counters Library (libpmc, \\-lpmc)")
 LINE("libppath",	"Property-List Paths Library (libppath, \\-lppath)")
 LINE("libposix",	"POSIX Compatibility Library (libposix, \\-lposix)")
 LINE("libposix1e",	"POSIX.1e Security API Library (libposix1e, \\-lposix1e)")
 LINE("libppath",	"Property-List Paths Library (libppath, \\-lppath)")
 LINE("libproc",		"Processor Monitoring and Analysis Library (libproc, \\-lproc)")
 LINE("libprocstat",	"Process and Files Information Retrieval (libprocstat, \\-lprocstat)")
 LINE("libprop",		"Property Container Object Library (libprop, \\-lprop)")
 LINE("libpthread",	"POSIX Threads Library (libpthread, \\-lpthread)")
 LINE("libpthread_dbg",	"POSIX Debug Threads Library (libpthread_dbg, \\-lpthread_dbg)")
 LINE("libpuffs",	"puffs Convenience Library (libpuffs, \\-lpuffs)")
 LINE("libquota",	"Disk Quota Access and Control Library (libquota, \\-lquota)")
 LINE("libradius",	"RADIUS Client Library (libradius, \\-lradius)")
 LINE("librefuse",	"File System in Userspace Convenience Library (librefuse, \\-lrefuse)")
 LINE("libresolv",	"DNS Resolver Library (libresolv, \\-lresolv)")
 LINE("librpcsec_gss",	"RPC GSS-API Authentication Library (librpcsec_gss, \\-lrpcsec_gss)")
 LINE("librpcsvc",	"RPC Service Library (librpcsvc, \\-lrpcsvc)")
 LINE("librt",		"POSIX Real\\-time Library (librt, \\-lrt)")
 LINE("librtld_db",	"Debugging interface to the runtime linker Library (librtld_db, \\-lrtld_db)")
 LINE("librumpclient",	"Clientside Stubs for rump Kernel Remote Protocols (librumpclient, \\-lrumpclient)")
 LINE("libsaslc",	"Simple Authentication and Security Layer client library (libsaslc, \\-lsaslc)")
 LINE("libsbuf",		"Safe String Composition Library (libsbuf, \\-lsbuf)")
 LINE("libsdp",		"Bluetooth Service Discovery Protocol User Library (libsdp, \\-lsdp)")
 LINE("libssp",		"Buffer Overflow Protection Library (libssp, \\-lssp)")
 LINE("libstdthreads",	"C11 Threads Library (libstdthreads, \\-lstdthreads)")
 LINE("libstdthreads",	"C11 Threads Library (libstdthreads, \\-lstdthreads)")
 LINE("libSystem",	"System Library (libSystem, \\-lSystem)")
 LINE("libsysdecode",	"System Argument Decoding Library (libsysdecode, \\-lsysdecode)")
 LINE("libtacplus",	"TACACS+ Client Library (libtacplus, \\-ltacplus)")
 LINE("libtcplay",	"TrueCrypt-compatible API library (libtcplay, \\-ltcplay)")
 LINE("libtermcap",	"Termcap Access Library (libtermcap, \\-ltermcap)")
 LINE("libterminfo",	"Terminal Information Library (libterminfo, \\-lterminfo)")
 LINE("libthr",		"1:1 Threading Library (libthr, \\-lthr)")
 LINE("libufs",		"UFS File System Access Library (libufs, \\-lufs)")
 LINE("libugidfw",	"File System Firewall Interface Library (libugidfw, \\-lugidfw)")
 LINE("libulog",		"User Login Record Library (libulog, \\-lulog)")
 LINE("libusbhid",	"USB Human Interface Devices Library (libusbhid, \\-lusbhid)")
 LINE("libutil",		"System Utilities Library (libutil, \\-lutil)")
 LINE("libvgl",		"Video Graphics Library (libvgl, \\-lvgl)")
 LINE("libx86_64",	"x86_64 Architecture Library (libx86_64, \\-lx86_64)")
 LINE("libxo",		"Text, XML, JSON, and HTML Output Emission Library (libxo, \\-lxo)")
 LINE("libz",		"Compression Library (libz, \\-lz)")
Index: head/contrib/mandoc/main.c
===================================================================
--- head/contrib/mandoc/main.c	(revision 350350)
+++ head/contrib/mandoc/main.c	(revision 350351)
@@ -1,1277 +1,1282 @@
-/*	$Id: main.c,v 1.322 2019/03/06 10:18:58 schwarze Exp $ */
+/*	$Id: main.c,v 1.332 2019/07/19 20:27:25 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
  * Copyright (c) 2010-2012, 2014-2019 Ingo Schwarze 
  * Copyright (c) 2010 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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 "config.h"
 
 #include 
 #include 
 #include 	/* MACHINE */
+#include 
 #include 
 
 #include 
 #include 
 #if HAVE_ERR
 #include 
 #endif
 #include 
 #include 
 #include 
 #if HAVE_SANDBOX_INIT
 #include 
 #endif
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #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 "main.h"
 #include "manconf.h"
 #include "mansearch.h"
 
 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	curparse {
 	struct mparse	 *mp;
 	struct manoutput *outopts;	/* output options */
 	void		 *outdata;	/* data for output */
 	char		 *os_s;		/* operating system for display */
 	int		  wstop;	/* stop after a file with a warning */
 	enum mandoc_os	  os_e;		/* check base system conventions */
 	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 *, int, char**,
 				struct manpage **, size_t *);
-static	int		  koptions(int *, char *);
-static	void		  moptions(int *, char *);
 static	void		  outdata_alloc(struct curparse *);
 static	void		  parse(struct curparse *, int, const char *);
-static	void		  passthrough(const char *, int, int);
+static	void		  passthrough(int, int);
 static	pid_t		  spawn_pager(struct tag_files *);
-static	int		  toptions(struct curparse *, char *);
 static	void		  usage(enum argmode) __attribute__((__noreturn__));
 static	int		  woptions(struct curparse *, char *);
 
 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;
 	struct mansearch search;
 	struct curparse	 curp;
 	struct winsize	 ws;
 	struct tag_files *tag_files;
 	struct manpage	*res, *resp;
 	const char	*progname, *sec, *thisarg;
 	char		*conf_file, *defpaths, *auxpaths;
 	char		*oarg, *tagarg;
 	unsigned char	*uc;
-	size_t		 i, sz;
+	size_t		 i, sz, ssz;
 	int		 prio, best_prio;
 	enum outmode	 outmode;
 	int		 fd, startdir;
 	int		 show_usage;
 	int		 options;
 	int		 use_pager;
 	int		 status, signum;
 	int		 c;
 	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
 
 #if HAVE_PROGNAME
 	progname = getprogname();
 #else
 	if (argc < 1)
 		progname = mandoc_strdup("mandoc");
 	else if ((progname = strrchr(argv[0], '/')) == NULL)
 		progname = argv[0];
 	else
 		++progname;
 	setprogname(progname);
 #endif
 
 	mandoc_msg_setoutfile(stderr);
 	if (strncmp(progname, "mandocdb", 8) == 0 ||
 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
 		return mandocdb(argc, argv);
 
 #if HAVE_PLEDGE
-	if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
-		err((int)MANDOCLEVEL_SYSERR, "pledge");
+	if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) {
+		mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
+		return mandoc_msg_getrc();
+	}
 #endif
-
 #if HAVE_SANDBOX_INIT
 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
 		errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
 #endif
 
 	/* Search options. */
 
 	memset(&conf, 0, sizeof(conf));
 	conf_file = defpaths = NULL;
 	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 and formatter options. */
 
 	memset(&curp, 0, sizeof(struct curparse));
 	curp.outtype = OUTT_LOCALE;
 	curp.outopts = &conf.output;
 	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
 
 	use_pager = 1;
 	tag_files = NULL;
 	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':
 			use_pager = 0;
 			break;
 		case 'f':
 			search.argmode = ARG_WORD;
 			break;
 		case 'h':
 			conf.output.synopsisonly = 1;
 			use_pager = 0;
 			outmode = OUTMODE_ALL;
 			break;
 		case 'I':
-			if (strncmp(optarg, "os=", 3)) {
-				warnx("-I %s: Bad argument", optarg);
-				return (int)MANDOCLEVEL_BADARG;
+			if (strncmp(optarg, "os=", 3) != 0) {
+				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
+				    "-I %s", optarg);
+				return mandoc_msg_getrc();
 			}
 			if (curp.os_s != NULL) {
-				warnx("-I %s: Duplicate argument", optarg);
-				return (int)MANDOCLEVEL_BADARG;
+				mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0,
+				    "-I %s", optarg);
+				return mandoc_msg_getrc();
 			}
 			curp.os_s = mandoc_strdup(optarg + 3);
 			break;
 		case 'K':
-			if ( ! koptions(&options, optarg))
-				return (int)MANDOCLEVEL_BADARG;
+			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':
 #ifdef __FreeBSD__
 			defpaths = strdup(optarg);
 			if (defpaths == NULL)
 				err(1, "strdup");
 #else
 			defpaths = optarg;
 #endif
 			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 ( ! toptions(&curp, optarg))
-				return (int)MANDOCLEVEL_BADARG;
+			if (strcmp(optarg, "ascii") == 0)
+				curp.outtype = OUTT_ASCII;
+			else if (strcmp(optarg, "lint") == 0) {
+				curp.outtype = OUTT_LINT;
+				mandoc_msg_setoutfile(stdout);
+				mandoc_msg_setmin(MANDOCERR_BASE);
+			} else if (strcmp(optarg, "tree") == 0)
+				curp.outtype = OUTT_TREE;
+			else if (strcmp(optarg, "man") == 0)
+				curp.outtype = OUTT_MAN;
+			else if (strcmp(optarg, "html") == 0)
+				curp.outtype = OUTT_HTML;
+			else if (strcmp(optarg, "markdown") == 0)
+				curp.outtype = OUTT_MARKDOWN;
+			else if (strcmp(optarg, "utf8") == 0)
+				curp.outtype = OUTT_UTF8;
+			else if (strcmp(optarg, "locale") == 0)
+				curp.outtype = OUTT_LOCALE;
+			else if (strcmp(optarg, "ps") == 0)
+				curp.outtype = OUTT_PS;
+			else if (strcmp(optarg, "pdf") == 0)
+				curp.outtype = OUTT_PDF;
+			else {
+				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
+				    "-T %s", optarg);
+				return mandoc_msg_getrc();
+			}
 			break;
 		case 'W':
-			if ( ! woptions(&curp, optarg))
-				return (int)MANDOCLEVEL_BADARG;
+			if (woptions(&curp, optarg) == -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. */
 
 	if (outmode == OUTMODE_DEF) {
 		switch (search.argmode) {
 		case ARG_FILE:
 			outmode = OUTMODE_ALL;
 			use_pager = 0;
 			break;
 		case ARG_NAME:
 			outmode = OUTMODE_ONE;
 			break;
 		default:
 			outmode = OUTMODE_LST;
 			break;
 		}
 	}
 
 	if (oarg != NULL) {
 		if (outmode == OUTMODE_LST)
 			search.outkey = oarg;
 		else {
 			while (oarg != NULL) {
-				thisarg = oarg;
 				if (manconf_output(&conf.output,
-				    strsep(&oarg, ","), 0) == 0)
-					continue;
-				warnx("-O %s: Bad argument", thisarg);
-				return (int)MANDOCLEVEL_BADARG;
+				    strsep(&oarg, ","), 0) == -1)
+					return mandoc_msg_getrc();
 			}
 		}
 	}
 
 	if (curp.outtype != OUTT_TREE || !curp.outopts->noval)
 		options |= MPARSE_VALIDATE;
 
 	if (outmode == OUTMODE_FLN ||
 	    outmode == OUTMODE_LST ||
 	    !isatty(STDOUT_FILENO))
 		use_pager = 0;
 
 	if (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 HAVE_PLEDGE
-	if (!use_pager)
-		if (pledge("stdio rpath", NULL) == -1)
-			err((int)MANDOCLEVEL_SYSERR, "pledge");
+	if (use_pager == 0) {
+		if (pledge("stdio rpath", NULL) == -1) {
+			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
+			    "%s", strerror(errno));
+			return mandoc_msg_getrc();
+		}
+	}
 #endif
 
 	/* Parse arguments. */
 
 	if (argc > 0) {
 		argc -= optind;
 		argv += optind;
 	}
 	resp = NULL;
 
 	/*
 	 * Quirks for help(1)
 	 * and for a man(1) 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[2] == '\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
 	}
 
 	/*
 	 * 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;
 	}
 
 	/* man(1), whatis(1), apropos(1) */
 
 	if (search.argmode != ARG_FILE) {
 		if (search.argmode == ARG_NAME &&
 		    outmode == OUTMODE_ONE)
 			search.firstmatch = 1;
 
 #ifdef __FreeBSD__
 		/*
 		 * Use manpath(1) to populate defpaths if -M is not specified.
 		 * Don't treat any failures as fatal.
 		 */
 		if (defpaths == NULL) {
 			FILE *fp;
 			size_t linecap = 0;
 			ssize_t linelen;
 
 			if ((fp = popen("/usr/bin/manpath -q", "r")) != NULL) {
 				if ((linelen = getline(&defpaths,
 				    &linecap, fp)) > 0) {
 					/* Strip trailing newline */
 					defpaths[linelen - 1] = '\0';
 				}
 				pclose(fp);
 			}
 		}
 #endif
 
 		/* Access the mandoc database. */
 
 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
 #ifdef __FreeBSD__
 		free(defpaths);
 #endif
 
 		if ( ! mansearch(&search, &conf.manpath,
 		    argc, argv, &res, &sz))
 			usage(search.argmode);
 
 		if (sz == 0 && search.argmode == ARG_NAME)
-			fs_search(&search, &conf.manpath,
+			(void)fs_search(&search, &conf.manpath,
 			    argc, argv, &res, &sz);
 
 		if (search.argmode == ARG_NAME) {
 			for (c = 0; c < argc; c++) {
 				if (strchr(argv[c], '/') == NULL)
 					continue;
 				if (access(argv[c], R_OK) == -1) {
-					warn("%s", argv[c]);
+					mandoc_msg_setinfilename(argv[c]);
+					mandoc_msg(MANDOCERR_BADARG_BAD,
+					    0, 0, "%s", strerror(errno));
+					mandoc_msg_setinfilename(NULL);
 					continue;
 				}
 				res = mandoc_reallocarray(res,
 				    sz + 1, sizeof(*res));
 				res[sz].file = mandoc_strdup(argv[c]);
 				res[sz].names = NULL;
 				res[sz].output = NULL;
+				res[sz].bits = 0;
 				res[sz].ipath = SIZE_MAX;
 				res[sz].sec = 10;
 				res[sz].form = FORM_SRC;
 				sz++;
 			}
 		}
 
 		if (sz == 0) {
 			if (search.argmode != ARG_NAME)
 				warnx("nothing appropriate");
 			mandoc_msg_setrc(MANDOCLEVEL_BADARG);
 			goto out;
 		}
 
 		/*
 		 * For standard man(1) and -a output mode,
 		 * prepare for copying filename pointers
 		 * into the program parameter array.
 		 */
 
 		if (outmode == OUTMODE_ONE) {
 			argc = 1;
-			best_prio = 20;
+			best_prio = 40;
 		} else if (outmode == OUTMODE_ALL)
 			argc = (int)sz;
 
 		/* Iterate all matching manuals. */
 
 		resp = res;
 		for (i = 0; i < sz; i++) {
 			if (outmode == OUTMODE_FLN)
 				puts(res[i].file);
 			else if (outmode == OUTMODE_LST)
 				printf("%s - %s\n", res[i].names,
 				    res[i].output == NULL ? "" :
 				    res[i].output);
 			else if (outmode == OUTMODE_ONE) {
 				/* Search for the best section. */
 				sec = res[i].file;
 				sec += strcspn(sec, "123456789");
 				if (sec[0] == '\0')
-					continue;
+					continue; /* No section at all. */
 				prio = sec_prios[sec[0] - '1'];
-				if (sec[1] != '/')
-					prio += 10;
+				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;
 				resp = res + i;
 			}
 		}
 
 		/*
 		 * For man(1), -a and -i output mode, fall through
 		 * to the main mandoc(1) code iterating files
 		 * and running the parsers on each of them.
 		 */
 
 		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
 			goto out;
 	}
 
 	/* mandoc(1) */
 
 #if HAVE_PLEDGE
 	if (use_pager) {
-		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
-			err((int)MANDOCLEVEL_SYSERR, "pledge");
+		if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) {
+			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
+			    "%s", strerror(errno));
+			return mandoc_msg_getrc();
+		}
 	} else {
-		if (pledge("stdio rpath", NULL) == -1)
-			err((int)MANDOCLEVEL_SYSERR, "pledge");
+		if (pledge("stdio rpath", NULL) == -1) {
+			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
+			    "%s", strerror(errno));
+			return mandoc_msg_getrc();
+		}
 	}
 #endif
 
-	if (search.argmode == ARG_FILE)
-		moptions(&options, auxpaths);
+	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();
 	curp.mp = mparse_alloc(options, curp.os_e, curp.os_s);
 
 	if (argc < 1) {
 		if (use_pager) {
 			tag_files = tag_init();
-			tag_files->tagname = conf.output.tag;
+			if (tag_files != NULL)
+				tag_files->tagname = conf.output.tag;
 		}
 		thisarg = "";
 		mandoc_msg_setinfilename(thisarg);
 		parse(&curp, STDIN_FILENO, thisarg);
 		mandoc_msg_setinfilename(NULL);
 	}
 
 	/*
 	 * 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);
 
 	while (argc > 0) {
 
 		/*
 		 * 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 != NULL) {
 			if (resp->ipath != SIZE_MAX)
 				(void)chdir(conf.manpath.paths[resp->ipath]);
 			else if (startdir != -1)
 				(void)fchdir(startdir);
 			thisarg = resp->file;
 		} else
 			thisarg = *argv;
 
+		mandoc_msg_setinfilename(thisarg);
 		fd = mparse_open(curp.mp, thisarg);
 		if (fd != -1) {
 			if (use_pager) {
 				use_pager = 0;
 				tag_files = tag_init();
-				tag_files->tagname = conf.output.tag;
+				if (tag_files != NULL)
+					tag_files->tagname = conf.output.tag;
 			}
 
-			mandoc_msg_setinfilename(thisarg);
 			if (resp == NULL || resp->form == FORM_SRC)
 				parse(&curp, fd, thisarg);
 			else
-				passthrough(resp->file, fd,
-				    conf.output.synopsisonly);
-			mandoc_msg_setinfilename(NULL);
+				passthrough(fd, conf.output.synopsisonly);
 
 			if (ferror(stdout)) {
 				if (tag_files != NULL) {
-					warn("%s", tag_files->ofn);
+					mandoc_msg(MANDOCERR_WRITE, 0, 0,
+					    "%s: %s", tag_files->ofn,
+					    strerror(errno));
 					tag_unlink();
 					tag_files = NULL;
 				} else
-					warn("stdout");
-				mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
+					mandoc_msg(MANDOCERR_WRITE, 0, 0,
+					    "%s", strerror(errno));
 				break;
 			}
 
 			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
 				if (curp.outdata == NULL)
 					outdata_alloc(&curp);
 				terminal_sepline(curp.outdata);
 			}
 		} else
-			mandoc_msg(MANDOCERR_FILE, 0, 0,
-			    "%s: %s", thisarg, strerror(errno));
+			mandoc_msg(resp == NULL ? MANDOCERR_BADARG_BAD :
+			    MANDOCERR_OPEN, 0, 0, "%s", strerror(errno));
 
+		mandoc_msg_setinfilename(NULL);
+
 		if (curp.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
 			break;
 
 		if (resp != NULL)
 			resp++;
 		else
 			argv++;
 		if (--argc)
 			mparse_reset(curp.mp);
 	}
 	if (startdir != -1) {
 		(void)fchdir(startdir);
 		close(startdir);
 	}
 
 	if (curp.outdata != NULL) {
 		switch (curp.outtype) {
 		case OUTT_HTML:
 			html_free(curp.outdata);
 			break;
 		case OUTT_UTF8:
 		case OUTT_LOCALE:
 		case OUTT_ASCII:
 			ascii_free(curp.outdata);
 			break;
 		case OUTT_PDF:
 		case OUTT_PS:
 			pspdf_free(curp.outdata);
 			break;
 		default:
 			break;
 		}
 	}
 	mandoc_xr_free();
 	mparse_free(curp.mp);
 	mchars_free();
 
 out:
 	if (search.argmode != ARG_FILE) {
 		manconf_free(&conf);
 		mansearch_free(res, sz);
 	}
 
 	free(curp.os_s);
 
 	/*
 	 * When using a pager, finish writing both temporary files,
 	 * fork it, wait for the user to close it, and clean up.
 	 */
 
 	if (tag_files != NULL) {
 		fclose(stdout);
 		tag_write();
 		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(tag_files->ofd);
 			if (tc_pgid != man_pgid) {
 				if (tc_pgid == pager_pid) {
 					(void)tcsetpgrp(tag_files->ofd,
 					    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(tag_files->ofd, pager_pid);
 				kill(pager_pid, SIGCONT);
 			} else
 				pager_pid = spawn_pager(tag_files);
 
 			/* Wait for the pager to stop or exit. */
 
 			while ((pid = waitpid(pager_pid, &status,
 			    WUNTRACED)) == -1 && errno == EINTR)
 				continue;
 
 			if (pid == -1) {
-				warn("wait");
-				mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
+				mandoc_msg(MANDOCERR_WAIT, 0, 0,
+				    "%s", strerror(errno));
 				break;
 			}
 			if (!WIFSTOPPED(status))
 				break;
 
 			signum = WSTOPSIG(status);
 		}
 		tag_unlink();
-	}
+	} else if (curp.outtype != OUTT_LINT &&
+	    (search.argmode == ARG_FILE || sz > 0))
+		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 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;
 	int		 globres;
 	enum form	 form;
 
 	form = FORM_SRC;
 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
 	    paths->paths[ipath], sec, name, sec);
-	if (access(file, R_OK) != -1)
+	if (stat(file, &sb) != -1)
 		goto found;
 	free(file);
 
 	mandoc_asprintf(&file, "%s/cat%s/%s.0",
 	    paths->paths[ipath], sec, name);
-	if (access(file, R_OK) != -1) {
+	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 (access(file, R_OK) != -1)
+		if (stat(file, &sb) != -1)
 			goto found;
 		free(file);
 	}
 
 	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
 	    paths->paths[ipath], sec, name);
 	globres = glob(file, 0, NULL, &globinfo);
 	if (globres != 0 && globres != GLOB_NOMATCH)
-		warn("%s: glob", file);
+		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)
-		goto found;
+	if (globres == 0) {
+		if (stat(file, &sb) != -1)
+			goto found;
+		free(file);
+	}
 	if (res != NULL || ipath + 1 != paths->sz)
-		return 0;
+		return -1;
 
 	mandoc_asprintf(&file, "%s.%s", name, sec);
-	globres = access(file, R_OK);
+	globres = stat(file, &sb);
 	free(file);
-	return globres != -1;
+	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 1;
+		return 0;
 	}
-	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
+	*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 1;
+	return 0;
 }
 
 static int
 fs_search(const struct mansearch *cfg, const struct manpaths *paths,
 	int argc, char **argv, 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, lastsz;
 
 	assert(cfg->argmode == ARG_NAME);
 
 	if (res != NULL)
 		*res = NULL;
 	*ressz = lastsz = 0;
 	while (argc) {
 		for (ipath = 0; ipath < paths->sz; ipath++) {
 			if (cfg->sec != NULL) {
 				if (fs_lookup(paths, ipath, cfg->sec,
-				    cfg->arch, *argv, res, ressz) &&
+				    cfg->arch, *argv, res, ressz) != -1 &&
 				    cfg->firstmatch)
-					return 1;
+					return 0;
 			} else for (isec = 0; isec < nsec; isec++)
 				if (fs_lookup(paths, ipath, sections[isec],
-				    cfg->arch, *argv, res, ressz) &&
+				    cfg->arch, *argv, res, ressz) != -1 &&
 				    cfg->firstmatch)
-					return 1;
+					return 0;
 		}
 		if (res != NULL && *ressz == lastsz &&
 		    strchr(*argv, '/') == NULL) {
 			if (cfg->arch != NULL &&
 			    arch_valid(cfg->arch, OSENUM) == 0)
 				warnx("Unknown architecture \"%s\".",
 				    cfg->arch);
 			else if (cfg->sec == NULL)
 				warnx("No entry for %s in the manual.",
 				    *argv);
 			else
 				warnx("No entry for %s in section %s "
 				    "of the manual.", *argv, cfg->sec);
 		}
 		lastsz = *ressz;
 		argv++;
 		argc--;
 	}
-	return 0;
+	return -1;
 }
 
 static void
 parse(struct curparse *curp, int fd, const char *file)
 {
 	struct roff_meta *meta;
 
 	/* Begin by parsing the file itself. */
 
 	assert(file);
 	assert(fd >= 0);
 
 	mparse_readfd(curp->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 (curp->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
 		return;
 
 	if (curp->outdata == NULL)
 		outdata_alloc(curp);
 	else if (curp->outtype == OUTT_HTML)
 		html_reset(curp);
 
 	mandoc_xr_reset();
 	meta = mparse_result(curp->mp);
 
 	/* Execute the out device, if it exists. */
 
 	if (meta->macroset == MACROSET_MDOC) {
 		switch (curp->outtype) {
 		case OUTT_HTML:
 			html_mdoc(curp->outdata, meta);
 			break;
 		case OUTT_TREE:
 			tree_mdoc(curp->outdata, meta);
 			break;
 		case OUTT_MAN:
 			man_mdoc(curp->outdata, meta);
 			break;
 		case OUTT_PDF:
 		case OUTT_ASCII:
 		case OUTT_UTF8:
 		case OUTT_LOCALE:
 		case OUTT_PS:
 			terminal_mdoc(curp->outdata, meta);
 			break;
 		case OUTT_MARKDOWN:
 			markdown_mdoc(curp->outdata, meta);
 			break;
 		default:
 			break;
 		}
 	}
 	if (meta->macroset == MACROSET_MAN) {
 		switch (curp->outtype) {
 		case OUTT_HTML:
 			html_man(curp->outdata, meta);
 			break;
 		case OUTT_TREE:
 			tree_man(curp->outdata, meta);
 			break;
 		case OUTT_MAN:
 			mparse_copy(curp->mp);
 			break;
 		case OUTT_PDF:
 		case OUTT_ASCII:
 		case OUTT_UTF8:
 		case OUTT_LOCALE:
 		case OUTT_PS:
 			terminal_man(curp->outdata, meta);
 			break;
 		default:
 			break;
 		}
 	}
 	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, 1, &xr->name, NULL, &sz))
+		if (fs_search(&search, &paths, 1, &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 curparse *curp)
 {
 	switch (curp->outtype) {
 	case OUTT_HTML:
 		curp->outdata = html_alloc(curp->outopts);
 		break;
 	case OUTT_UTF8:
 		curp->outdata = utf8_alloc(curp->outopts);
 		break;
 	case OUTT_LOCALE:
 		curp->outdata = locale_alloc(curp->outopts);
 		break;
 	case OUTT_ASCII:
 		curp->outdata = ascii_alloc(curp->outopts);
 		break;
 	case OUTT_PDF:
 		curp->outdata = pdf_alloc(curp->outopts);
 		break;
 	case OUTT_PS:
 		curp->outdata = ps_alloc(curp->outopts);
 		break;
 	default:
 		break;
 	}
 }
 
 static void
-passthrough(const char *file, int fd, int synopsis_only)
+passthrough(int fd, int synopsis_only)
 {
 	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
 	const char	 synr[] = "SYNOPSIS";
 
 	FILE		*stream;
-	const char	*syscall;
 	char		*line, *cp;
 	size_t		 linesz;
 	ssize_t		 len, written;
-	int		 print;
+	int		 lno, print;
 
+	stream = NULL;
 	line = NULL;
 	linesz = 0;
 
 	if (fflush(stdout) == EOF) {
-		syscall = "fflush";
-		goto fail;
+		mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno));
+		goto done;
 	}
-
 	if ((stream = fdopen(fd, "r")) == NULL) {
 		close(fd);
-		syscall = "fdopen";
-		goto fail;
+		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
+		goto done;
 	}
 
-	print = 0;
+	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)
-				continue;
-			fclose(stream);
-			syscall = "write";
-			goto fail;
+			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));
 
-	if (ferror(stream)) {
-		fclose(stream);
-		syscall = "getline";
-		goto fail;
-	}
-
 done:
 	free(line);
-	fclose(stream);
-	return;
-
-fail:
-	free(line);
-	warn("%s: SYSERR: %s", file, syscall);
-	mandoc_msg_setrc(MANDOCLEVEL_SYSERR);
+	if (stream != NULL)
+		fclose(stream);
 }
 
 static int
-koptions(int *options, char *arg)
-{
-
-	if ( ! strcmp(arg, "utf-8")) {
-		*options |=  MPARSE_UTF8;
-		*options &= ~MPARSE_LATIN1;
-	} else if ( ! strcmp(arg, "iso-8859-1")) {
-		*options |=  MPARSE_LATIN1;
-		*options &= ~MPARSE_UTF8;
-	} else if ( ! strcmp(arg, "us-ascii")) {
-		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
-	} else {
-		warnx("-K %s: Bad argument", arg);
-		return 0;
-	}
-	return 1;
-}
-
-static void
-moptions(int *options, char *arg)
-{
-
-	if (arg == NULL)
-		return;
-	if (strcmp(arg, "doc") == 0)
-		*options |= MPARSE_MDOC;
-	else if (strcmp(arg, "an") == 0)
-		*options |= MPARSE_MAN;
-}
-
-static int
-toptions(struct curparse *curp, char *arg)
-{
-
-	if (0 == strcmp(arg, "ascii"))
-		curp->outtype = OUTT_ASCII;
-	else if (0 == strcmp(arg, "lint")) {
-		curp->outtype = OUTT_LINT;
-		mandoc_msg_setoutfile(stdout);
-		mandoc_msg_setmin(MANDOCERR_BASE);
-	} else if (0 == strcmp(arg, "tree"))
-		curp->outtype = OUTT_TREE;
-	else if (0 == strcmp(arg, "man"))
-		curp->outtype = OUTT_MAN;
-	else if (0 == strcmp(arg, "html"))
-		curp->outtype = OUTT_HTML;
-	else if (0 == strcmp(arg, "markdown"))
-		curp->outtype = OUTT_MARKDOWN;
-	else if (0 == strcmp(arg, "utf8"))
-		curp->outtype = OUTT_UTF8;
-	else if (0 == strcmp(arg, "locale"))
-		curp->outtype = OUTT_LOCALE;
-	else if (0 == strcmp(arg, "ps"))
-		curp->outtype = OUTT_PS;
-	else if (0 == strcmp(arg, "pdf"))
-		curp->outtype = OUTT_PDF;
-	else {
-		warnx("-T %s: Bad argument", arg);
-		return 0;
-	}
-
-	return 1;
-}
-
-static int
 woptions(struct curparse *curp, char *arg)
 {
 	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:
 			curp->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_MAX);
+			mandoc_msg_setmin(MANDOCERR_BADARG);
 			break;
 		case 8:
 			mandoc_msg_setmin(MANDOCERR_BASE);
 			curp->os_e = MANDOC_OS_OPENBSD;
 			break;
 		case 9:
 			mandoc_msg_setmin(MANDOCERR_BASE);
 			curp->os_e = MANDOC_OS_NETBSD;
 			break;
 		default:
-			warnx("-W %s: Bad argument", o);
-			return 0;
+			mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o);
+			return -1;
 		}
 	}
-	return 1;
+	return 0;
 }
 
 static pid_t
 spawn_pager(struct tag_files *tag_files)
 {
 	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
 #define MAX_PAGER_ARGS 16
 	char		*argv[MAX_PAGER_ARGS];
 	const char	*pager;
 	char		*cp;
 #if HAVE_LESS_T
 	size_t		 cmdlen;
 #endif
 	int		 argc, use_ofn;
 	pid_t		 pager_pid;
 
 	pager = getenv("MANPAGER");
 	if (pager == NULL || *pager == '\0')
 		pager = getenv("PAGER");
 	if (pager == NULL || *pager == '\0')
 		pager = "less -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 less(1), use the tag file. */
 
 	use_ofn = 1;
 #if HAVE_LESS_T
-	if ((cmdlen = strlen(argv[0])) >= 4) {
+	if (*tag_files->tfn != '\0' && (cmdlen = strlen(argv[0])) >= 4) {
 		cp = argv[0] + cmdlen - 4;
 		if (strcmp(cp, "less") == 0) {
 			argv[argc++] = mandoc_strdup("-T");
 			argv[argc++] = tag_files->tfn;
 			if (tag_files->tagname != NULL) {
 				argv[argc++] = mandoc_strdup("-t");
 				argv[argc++] = tag_files->tagname;
 				use_ofn = 0;
 			}
 		}
 	}
 #endif
 	if (use_ofn)
 		argv[argc++] = tag_files->ofn;
 	argv[argc] = NULL;
 
 	switch (pager_pid = fork()) {
 	case -1:
-		err((int)MANDOCLEVEL_SYSERR, "fork");
+		mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno));
+		exit(mandoc_msg_getrc());
 	case 0:
 		break;
 	default:
 		(void)setpgid(pager_pid, 0);
 		(void)tcsetpgrp(tag_files->ofd, pager_pid);
 #if HAVE_PLEDGE
-		if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
-			err((int)MANDOCLEVEL_SYSERR, "pledge");
+		if (pledge("stdio rpath tmppath tty proc", NULL) == -1) {
+			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
+			    "%s", strerror(errno));
+			exit(mandoc_msg_getrc());
+		}
 #endif
 		tag_files->pager_pid = pager_pid;
 		return pager_pid;
 	}
 
 	/* The child process becomes the pager. */
 
-	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
-		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
+	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) {
+		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
+		_exit(mandoc_msg_getrc());
+	}
 	close(tag_files->ofd);
 	assert(tag_files->tfd == -1);
 
 	/* Do not start the pager before controlling the terminal. */
 
 	while (tcgetpgrp(STDOUT_FILENO) != getpid())
 		nanosleep(&timeout, NULL);
 
 	execvp(argv[0], argv);
-	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
+	mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno));
+	_exit(mandoc_msg_getrc());
 }
Index: head/contrib/mandoc/man.7
===================================================================
--- head/contrib/mandoc/man.7	(revision 350350)
+++ head/contrib/mandoc/man.7	(revision 350351)
@@ -1,612 +1,616 @@
-.\"	$Id: man.7,v 1.143 2019/03/02 22:04:40 schwarze Exp $
+.\"	$Id: man.7,v 1.144 2019/07/09 03:46:59 schwarze Exp $
 .\"
 .\" Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons 
 .\" Copyright (c) 2011-2015,2017,2018,2019 Ingo Schwarze 
 .\" Copyright (c) 2017 Anthony Bentley 
 .\" Copyright (c) 2010 Joerg Sonnenberger 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
 .\" copyright notice and this permission notice appear in all copies.
 .\"
 .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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: March 2 2019 $
+.Dd $Mdocdate: July 9 2019 $
 .Dt MAN 7
 .Os
 .Sh NAME
 .Nm man
 .Nd legacy formatting language for manual pages
 .Sh DESCRIPTION
 The
 .Nm man
 language was the standard formatting language for
 .At
 manual pages from 1979 to 1989.
 Do not use it to write new manual pages: it is a purely presentational
 language and lacks support for semantic markup.
 Use the
 .Xr mdoc 7
 language, instead.
 .Pp
 In a
 .Nm
 document, lines beginning with the control character
 .Sq \&.
 are called
 .Dq macro lines .
 The first word is the macro name.
 It usually consists of two capital letters.
 For a list of portable macros, see
 .Sx MACRO OVERVIEW .
 The words following the macro name are arguments to the macro.
 .Pp
 Lines not beginning with the control character are called
 .Dq text lines .
 They provide free-form text to be printed; the formatting of the text
 depends on the respective processing context:
 .Bd -literal -offset indent
 \&.SH Macro lines change control state.
 Text lines are interpreted within the current state.
 .Ed
 .Pp
 Many aspects of the basic syntax of the
 .Nm
 language are based on the
 .Xr roff 7
 language; see the
 .Em LANGUAGE SYNTAX
 and
 .Em MACRO SYNTAX
 sections in the
 .Xr roff 7
 manual for details, in particular regarding
 comments, escape sequences, whitespace, and quoting.
 .Pp
 Each
 .Nm
 document starts with the
 .Ic TH
 macro specifying the document's name and section, followed by the
 .Sx NAME
 section formatted as follows:
 .Bd -literal -offset indent
 \&.TH PROGNAME 1 1979-01-10
 \&.SH NAME
 \efBprogname\efR \e(en one line about what it does
 .Ed
 .Sh MACRO OVERVIEW
 This overview is sorted such that macros of similar purpose are listed
 together.
 Deprecated and non-portable macros are not included in the overview,
 but can be found in the alphabetical reference below.
 .Ss Page header and footer meta-data
 .Bl -column "RS, RE" description
 .It Ic TH Ta set the title: Ar name section date Op Ar source Op Ar volume
 .It Ic AT Ta display AT&T UNIX version in the page footer (<= 1 argument)
 .It Ic UC Ta display BSD version in the page footer (<= 1 argument)
 .El
 .Ss Sections and paragraphs
 .Bl -column "RS, RE" description
 .It Ic SH Ta section header (one line)
 .It Ic SS Ta subsection header (one line)
 .It Ic PP Ta start an undecorated paragraph (no arguments)
 .It Ic RS , RE Ta reset the left margin: Op Ar width
 .It Ic IP Ta indented paragraph: Op Ar head Op Ar width
 .It Ic TP Ta tagged paragraph: Op Ar width
 .It Ic PD Ta set vertical paragraph distance: Op Ar height
 .It Ic in Ta additional indent: Op Ar width
 .El
 .Ss Physical markup
 .Bl -column "RS, RE" description
 .It Ic B Ta boldface font
 .It Ic I Ta italic font
 .It Ic SB Ta small boldface font
 .It Ic SM Ta small roman font
 .It Ic BI Ta alternate between boldface and italic fonts
 .It Ic BR Ta alternate between boldface and roman fonts
 .It Ic IB Ta alternate between italic and boldface fonts
 .It Ic IR Ta alternate between italic and roman fonts
 .It Ic RB Ta alternate between roman and boldface fonts
 .It Ic RI Ta alternate between roman and italic fonts
 .El
 .Sh MACRO REFERENCE
 This section is a canonical reference to all macros, arranged
 alphabetically.
 For the scoping of individual macros, see
 .Sx MACRO SYNTAX .
 .Bl -tag -width 3n
 .It Ic AT
 Sets the volume for the footer for compatibility with man pages from
 .At
 releases.
 The optional arguments specify which release it is from.
 .It Ic B
 Text is rendered in bold face.
 .It Ic BI
 Text is rendered alternately in bold face and italic.
 Thus,
 .Sq .BI this word and that
 causes
 .Sq this
 and
 .Sq and
 to render in bold face, while
 .Sq word
 and
 .Sq that
 render in italics.
 Whitespace between arguments is omitted in output.
 .Pp
 Example:
 .Pp
 .Dl \&.BI bold italic bold italic
 .It Ic BR
 Text is rendered alternately in bold face and roman (the default font).
 Whitespace between arguments is omitted in output.
 See also
 .Ic BI .
 .It Ic DT
 Restore the default tabulator positions.
 They are at intervals of 0.5 inches.
 This has no effect unless the tabulator positions were changed with the
 .Xr roff 7
 .Ic ta
 request.
 .It Ic EE
-This is a non-standard GNU extension.
+This is a non-standard Version 9
+.At
+extension later adopted by GNU.
 In
 .Xr mandoc 1 ,
 it does the same as the
 .Xr roff 7
 .Ic fi
 request (switch to fill mode).
 .It Ic EX
-This is a non-standard GNU extension.
+This is a non-standard Version 9
+.At
+extension later adopted by GNU.
 In
 .Xr mandoc 1 ,
 it does the same as the
 .Xr roff 7
 .Ic nf
 request (switch to no-fill mode).
 .It Ic HP
 Begin a paragraph whose initial output line is left-justified, but
 subsequent output lines are indented, with the following syntax:
 .Pp
 .D1 Pf . Ic HP Op Ar width
 .Pp
 The
 .Ar width
 argument is a
 .Xr roff 7
 scaling width.
 If specified, it's saved for later paragraph left margins;
 if unspecified, the saved or default width is used.
 .Pp
 This macro is portable, but deprecated
 because it has no good representation in HTML output,
 usually ending up indistinguishable from
 .Ic PP .
 .It Ic I
 Text is rendered in italics.
 .It Ic IB
 Text is rendered alternately in italics and bold face.
 Whitespace between arguments is omitted in output.
 See also
 .Ic BI .
 .It Ic IP
 Begin an indented paragraph with the following syntax:
 .Pp
 .D1 Pf . Ic IP Op Ar head Op Ar width
 .Pp
 The
 .Ar width
 argument is a
 .Xr roff 7
 scaling width defining the left margin.
 It's saved for later paragraph left-margins; if unspecified, the saved or
 default width is used.
 .Pp
 The
 .Ar head
 argument is used as a leading term, flushed to the left margin.
 This is useful for bulleted paragraphs and so on.
 .It Ic IR
 Text is rendered alternately in italics and roman (the default font).
 Whitespace between arguments is omitted in output.
 See also
 .Ic BI .
 .It Ic LP
 A synonym for
 .Ic PP .
 .It Ic ME
 End a mailto block started with
 .Ic MT .
 This is a non-standard GNU extension.
 .It Ic MT
 Begin a mailto block.
 This is a non-standard GNU extension.
 It has the following syntax:
 .Bd -unfilled -offset indent
 .Pf . Ic MT Ar address
 link description to be shown
 .Pf . Ic ME
 .Ed
 .It Ic OP
 Optional command-line argument.
 This is a non-standard GNU extension.
 It has the following syntax:
 .Pp
 .D1 Pf . Ic OP Ar key Op Ar value
 .Pp
 The
 .Ar key
 is usually a command-line flag and
 .Ar value
 its argument.
 .It Ic P
 A synonym for
 .Ic PP .
 .It Ic PD
 Specify the vertical space to be inserted before each new paragraph.
 .br
 The syntax is as follows:
 .Pp
 .D1 Pf . Ic PD Op Ar height
 .Pp
 The
 .Ar height
 argument is a
 .Xr roff 7
 scaling width.
 It defaults to
 .Cm 1v .
 If the unit is omitted,
 .Cm v
 is assumed.
 .Pp
 This macro affects the spacing before any subsequent instances of
 .Ic HP ,
 .Ic IP ,
 .Ic LP ,
 .Ic P ,
 .Ic PP ,
 .Ic SH ,
 .Ic SS ,
 .Ic SY ,
 and
 .Ic TP .
 .It Ic PP
 Begin an undecorated paragraph.
 The scope of a paragraph is closed by a subsequent paragraph,
 sub-section, section, or end of file.
 The saved paragraph left-margin width is reset to the default.
 .It Ic RB
 Text is rendered alternately in roman (the default font) and bold face.
 Whitespace between arguments is omitted in output.
 See also
 .Ic BI .
 .It Ic RE
 Explicitly close out the scope of a prior
 .Ic RS .
 The default left margin is restored to the state before that
 .Ic RS
 invocation.
 .Pp
 The syntax is as follows:
 .Pp
 .D1 Pf . Ic RE Op Ar level
 .Pp
 Without an argument, the most recent
 .Ic RS
 block is closed out.
 If
 .Ar level
 is 1, all open
 .Ic RS
 blocks are closed out.
 Otherwise,
 .Ar level No \(mi 1
 nested
 .Ic RS
 blocks remain open.
 .It Ic RI
 Text is rendered alternately in roman (the default font) and italics.
 Whitespace between arguments is omitted in output.
 See also
 .Ic BI .
 .It Ic RS
 Temporarily reset the default left margin.
 This has the following syntax:
 .Pp
 .D1 Pf . Ic RS Op Ar width
 .Pp
 The
 .Ar width
 argument is a
 .Xr roff 7
 scaling width.
 If not specified, the saved or default width is used.
 .Pp
 See also
 .Ic RE .
 .It Ic SB
 Text is rendered in small size (one point smaller than the default font)
 bold face.
 .It Ic SH
 Begin a section.
 The scope of a section is only closed by another section or the end of
 file.
 The paragraph left-margin width is reset to the default.
 .It Ic SM
 Text is rendered in small size (one point smaller than the default
 font).
 .It Ic SS
 Begin a sub-section.
 The scope of a sub-section is closed by a subsequent sub-section,
 section, or end of file.
 The paragraph left-margin width is reset to the default.
 .It Ic SY
 Begin a synopsis block with the following syntax:
 .Bd -unfilled -offset indent
 .Pf . Ic SY Ar command
 .Ar arguments
 .Pf . Ic YS
 .Ed
 .Pp
 This is a non-standard GNU extension
 and very rarely used even in GNU manual pages.
 Formatting is similar to
 .Ic IP .
 .It Ic TH
 Set the name of the manual page for use in the page header
 and footer with the following syntax:
 .Pp
 .D1 Pf . Ic TH Ar name section date Op Ar source Op Ar volume
 .Pp
 Conventionally, the document
 .Ar name
 is given in all caps.
 The
 .Ar section
 is usually a single digit, in a few cases followed by a letter.
 The recommended
 .Ar date
 format is
 .Sy YYYY-MM-DD
 as specified in the ISO-8601 standard;
 if the argument does not conform, it is printed verbatim.
 If the
 .Ar date
 is empty or not specified, the current date is used.
 The optional
 .Ar source
 string specifies the organisation providing the utility.
 When unspecified,
 .Xr mandoc 1
 uses its
 .Fl Ios
 argument.
 The
 .Ar volume
 string replaces the default volume title of the
 .Ar section .
 .Pp
 Examples:
 .Pp
 .Dl \&.TH CVS 5 "1992-02-12" GNU
 .It Ic TP
 Begin a paragraph where the head, if exceeding the indentation width, is
 followed by a newline; if not, the body follows on the same line after
 advancing to the indentation width.
 Subsequent output lines are indented.
 The syntax is as follows:
 .Bd -unfilled -offset indent
 .Pf . Ic TP Op Ar width
 .Ar head No \e" one line
 .Ar body
 .Ed
 .Pp
 The
 .Ar width
 argument is a
 .Xr roff 7
 scaling width.
 If specified, it's saved for later paragraph left-margins; if
 unspecified, the saved or default width is used.
 .It Ic TQ
 Like
 .Ic TP ,
 except that no vertical spacing is inserted before the paragraph.
 This is a non-standard GNU extension
 and very rarely used even in GNU manual pages.
 .It Ic UC
 Sets the volume for the footer for compatibility with man pages from
 .Bx
 releases.
 The optional first argument specifies which release it is from.
 .It Ic UE
 End a uniform resource identifier block started with
 .Ic UR .
 This is a non-standard GNU extension.
 .It Ic UR
 Begin a uniform resource identifier block.
 This is a non-standard GNU extension.
 It has the following syntax:
 .Bd -unfilled -offset indent
 .Pf . Ic UR Ar uri
 link description to be shown
 .Pf . Ic UE
 .Ed
 .It Ic YS
 End a synopsis block started with
 .Ic SY .
 This is a non-standard GNU extension.
 .It Ic in
 Indent relative to the current indentation:
 .Pp
 .D1 Pf . Ic in Op Ar width
 .Pp
 If
 .Ar width
 is signed, the new offset is relative.
 Otherwise, it is absolute.
 This value is reset upon the next paragraph, section, or sub-section.
 .El
 .Sh MACRO SYNTAX
 The
 .Nm
 macros are classified by scope: line scope or block scope.
 Line macros are only scoped to the current line (and, in some
 situations, the subsequent line).
 Block macros are scoped to the current line and subsequent lines until
 closed by another block macro.
 .Ss Line Macros
 Line macros are generally scoped to the current line, with the body
 consisting of zero or more arguments.
 If a macro is scoped to the next line and the line arguments are empty,
 the next line, which must be text, is used instead.
 Thus:
 .Bd -literal -offset indent
 \&.I
 foo
 .Ed
 .Pp
 is equivalent to
 .Sq .I foo .
 If next-line macros are invoked consecutively, only the last is used.
 If a next-line macro is followed by a non-next-line macro, an error is
 raised.
 .Pp
 The syntax is as follows:
 .Bd -literal -offset indent
 \&.YO \(lBbody...\(rB
 \(lBbody...\(rB
 .Ed
 .Bl -column "MacroX" "ArgumentsX" "ScopeXXXXX" "CompatX" -offset indent
 .It Em Macro Ta Em Arguments Ta Em Scope     Ta Em Notes
 .It Ic AT  Ta    <=1       Ta    current   Ta    \&
 .It Ic B   Ta    n         Ta    next-line Ta    \&
 .It Ic BI  Ta    n         Ta    current   Ta    \&
 .It Ic BR  Ta    n         Ta    current   Ta    \&
 .It Ic DT  Ta    0         Ta    current   Ta    \&
-.It Ic EE  Ta    0         Ta    current   Ta    GNU
-.It Ic EX  Ta    0         Ta    current   Ta    GNU
+.It Ic EE  Ta    0         Ta    current   Ta    Version 9 At
+.It Ic EX  Ta    0         Ta    current   Ta    Version 9 At
 .It Ic I   Ta    n         Ta    next-line Ta    \&
 .It Ic IB  Ta    n         Ta    current   Ta    \&
 .It Ic IR  Ta    n         Ta    current   Ta    \&
 .It Ic OP  Ta    >=1       Ta    current   Ta    GNU
 .It Ic PD  Ta    1         Ta    current   Ta    \&
 .It Ic RB  Ta    n         Ta    current   Ta    \&
 .It Ic RI  Ta    n         Ta    current   Ta    \&
 .It Ic SB  Ta    n         Ta    next-line Ta    \&
 .It Ic SM  Ta    n         Ta    next-line Ta    \&
 .It Ic TH  Ta    >1, <6    Ta    current   Ta    \&
 .It Ic UC  Ta    <=1       Ta    current   Ta    \&
 .It Ic in  Ta    1         Ta    current   Ta    Xr roff 7
 .El
 .Ss Block Macros
 Block macros comprise a head and body.
 As with in-line macros, the head is scoped to the current line and, in
 one circumstance, the next line (the next-line stipulations as in
 .Sx Line Macros
 apply here as well).
 .Pp
 The syntax is as follows:
 .Bd -literal -offset indent
 \&.YO \(lBhead...\(rB
 \(lBhead...\(rB
 \(lBbody...\(rB
 .Ed
 .Pp
 The closure of body scope may be to the section, where a macro is closed
 by
 .Ic SH ;
 sub-section, closed by a section or
 .Ic SS ;
 or paragraph, closed by a section, sub-section,
 .Ic HP ,
 .Ic IP ,
 .Ic LP ,
 .Ic P ,
 .Ic PP ,
 .Ic RE ,
 .Ic SY ,
 or
 .Ic TP .
 No closure refers to an explicit block closing macro.
 .Pp
 As a rule, block macros may not be nested; thus, calling a block macro
 while another block macro scope is open, and the open scope is not
 implicitly closed, is syntactically incorrect.
 .Bl -column "MacroX" "ArgumentsX" "Head ScopeX" "sub-sectionX" "compatX" -offset indent
 .It Em Macro Ta Em Arguments Ta Em Head Scope Ta Em Body Scope  Ta Em Notes
 .It Ic HP  Ta    <2        Ta    current    Ta    paragraph   Ta    \&
 .It Ic IP  Ta    <3        Ta    current    Ta    paragraph   Ta    \&
 .It Ic LP  Ta    0         Ta    current    Ta    paragraph   Ta    \&
 .It Ic ME  Ta    0         Ta    none       Ta    none        Ta    GNU
 .It Ic MT  Ta    1         Ta    current    Ta    to \&ME     Ta    GNU
 .It Ic P   Ta    0         Ta    current    Ta    paragraph   Ta    \&
 .It Ic PP  Ta    0         Ta    current    Ta    paragraph   Ta    \&
 .It Ic RE  Ta    <=1       Ta    current    Ta    none        Ta    \&
 .It Ic RS  Ta    1         Ta    current    Ta    to \&RE     Ta    \&
 .It Ic SH  Ta    >0        Ta    next-line  Ta    section     Ta    \&
 .It Ic SS  Ta    >0        Ta    next-line  Ta    sub-section Ta    \&
 .It Ic SY  Ta    1         Ta    current    Ta    to \&YS     Ta    GNU
 .It Ic TP  Ta    n         Ta    next-line  Ta    paragraph   Ta    \&
 .It Ic TQ  Ta    n         Ta    next-line  Ta    paragraph   Ta    GNU
 .It Ic UE  Ta    0         Ta    current    Ta    none        Ta    GNU
 .It Ic UR  Ta    1         Ta    current    Ta    part        Ta    GNU
 .It Ic YS  Ta    0         Ta    none       Ta    none        Ta    GNU
 .El
 .Pp
 If a block macro is next-line scoped, it may only be followed by in-line
 macros for decorating text.
 .Ss Font handling
 In
 .Nm
 documents, both
 .Sx Physical markup
 macros and
 .Xr roff 7
 .Ql \ef
 font escape sequences can be used to choose fonts.
 In text lines, the effect of manual font selection by escape sequences
 only lasts until the next macro invocation; in macro lines, it only lasts
 until the end of the macro scope.
 Note that macros like
 .Ic BR
 open and close a font scope for each argument.
 .Sh SEE ALSO
 .Xr man 1 ,
 .Xr mandoc 1 ,
 .Xr eqn 7 ,
 .Xr mandoc_char 7 ,
 .Xr mdoc 7 ,
 .Xr roff 7 ,
 .Xr tbl 7
 .Sh HISTORY
 The
 .Nm
 language first appeared as a macro package for the roff typesetting
 system in
 .At v7 .
 It was later rewritten by James Clark as a macro package for groff.
 Eric S. Raymond wrote the extended
 .Nm
 macros for groff in 2007.
 The stand-alone implementation that is part of the
 .Xr mandoc 1
 utility written by Kristaps Dzonsons appeared in
 .Ox 4.6 .
 .Sh AUTHORS
 This
 .Nm
 reference was written by
 .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
Index: head/contrib/mandoc/man_html.c
===================================================================
--- head/contrib/mandoc/man_html.c	(revision 350350)
+++ head/contrib/mandoc/man_html.c	(revision 350351)
@@ -1,643 +1,643 @@
-/*	$Id: man_html.c,v 1.173 2019/03/02 16:30:53 schwarze Exp $ */
+/*	$Id: man_html.c,v 1.174 2019/04/30 15:53:00 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons 
  * Copyright (c) 2013-2015, 2017-2019 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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 "config.h"
 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #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, \
 			  const 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_endline(h);
 		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 (HTMLFONT_NONE != h->metac) {
+		if (h->metac != ESCAPE_FONTROMAN) {
 			h->metal = h->metac;
-			h->metac = HTMLFONT_NONE;
+			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, n->next) == '\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 = 
, 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;
 	char		*id;
 	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:
 		id = html_make_id(n, 1);
 		print_otag(h, tag, "ci", class, id);
 		if (id != NULL)
 			print_otag(h, TAG_A, "chR", "permalink", id);
 		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)
 {
 	const 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;
 	if ((list_type = list_continues(nn->prev, nn)) == '\0') {
 		/* Start a new list. */
 		if ((list_type = list_continues(nn, nn->next)) == '\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(h, TAG_DT, "");
 		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();
 }
Index: head/contrib/mandoc/man_term.c
===================================================================
--- head/contrib/mandoc/man_term.c	(revision 350350)
+++ head/contrib/mandoc/man_term.c	(revision 350351)
@@ -1,1145 +1,1225 @@
-/*	$Id: man_term.c,v 1.228 2019/01/05 21:18:26 schwarze Exp $ */
+/*	$Id: man_term.c,v 1.232 2019/07/23 17:53:35 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
  * Copyright (c) 2010-2015, 2017-2019 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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 "config.h"
 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #include "mandoc_aux.h"
+#include "mandoc.h"
 #include "roff.h"
 #include "man.h"
 #include "out.h"
 #include "term.h"
+#include "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 *,
 				const 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	void		  tag_man(struct termp *, struct roff_node *);
+
 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;
+	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) {
-		while (n != NULL) {
-			if (n->tok == MAN_SH &&
-			    n->child->child->type == ROFFT_TEXT &&
-			    !strcmp(n->child->child->string, "SYNOPSIS")) {
-				if (n->child->next->child != NULL)
-					print_man_nodelist(p, &mt,
-					    n->child->next->child, man);
-				term_newln(p);
+		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;
-			}
-			n = n->next;
+			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, const struct roff_node *n, int pardist)
 {
 	int	 i;
 
 	term_newln(p);
 
 	if (n->body != NULL && n->body->child != NULL)
 		if (n->body->child->type == ROFFT_TBL)
 			return;
 
 	if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
 		if (n->prev == 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;
+			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)
+		if (n->child != NULL) {
 			print_man_node(p, mt, n->child, meta);
+			tag_man(p, n->child);
+		}
 		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;
 
+		if (nn == NULL)
+			return 0;
+
+		if (nn->type == ROFFT_TEXT)
+			tag_man(p, nn);
+		else if (nn->child != NULL &&
+		    nn->child->type == ROFFT_TEXT &&
+		    (nn->tok == MAN_B || nn->tok == MAN_BI ||
+		     nn->tok == MAN_BR || nn->tok == MAN_I ||
+		     nn->tok == MAN_IB || nn->tok == MAN_IR))
+			tag_man(p, nn->child);
+
 		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.
 		 */
 
 		do {
 			n = n->prev;
 		} while (n != NULL && n->tok >= MAN_TH &&
 		    man_term_act(n->tok)->flags & MAN_NOTEXT);
 		if (n == NULL || n->type == ROFFT_COMMENT ||
 		    (n->tok == MAN_SS && n->body->child == 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.
 		 */
 
 		do {
 			n = n->prev;
 		} while (n != NULL && n->tok >= MAN_TH &&
 		    man_term_act(n->tok)->flags & MAN_NOTEXT);
 		if (n == NULL || n->type == ROFFT_COMMENT ||
 		    (n->tok == MAN_SH && n->body->child == 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 (n->prev == NULL || n->prev->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;
 
 	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);
+}
+
+/*
+ * 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
+tag_man(struct termp *p, struct roff_node *n)
+{
+	const char	*cp, *arg;
+	int		 prio, sz;
+
+	assert(n->type == ROFFT_TEXT);
+	cp = n->string;
+	prio = 1;
+	for (;;) {
+		switch (*cp) {
+		case ' ':
+		case '\t':
+			prio = INT_MAX;
+			/* FALLTHROUGH */
+		case '-':
+			cp++;
+			break;
+		case '\\':
+			cp++;
+			switch (mandoc_escape(&cp, &arg, &sz)) {
+			case ESCAPE_FONT:
+			case ESCAPE_FONTROMAN:
+			case ESCAPE_FONTITALIC:
+			case ESCAPE_FONTBOLD:
+			case ESCAPE_FONTPREV:
+			case ESCAPE_FONTBI:
+				break;
+			case ESCAPE_SPECIAL:
+				if (sz != 1)
+					return;
+				switch (*arg) {
+				case '&':
+				case '-':
+				case 'e':
+					break;
+				default:
+					return;
+				}
+				break;
+			default:
+				return;
+			}
+			break;
+		default:
+			if (isalpha((unsigned char)*cp))
+				tag_put(cp, prio, p->line);
+			return;
+		}
+	}
 }
Index: head/contrib/mandoc/man_validate.c
===================================================================
--- head/contrib/mandoc/man_validate.c	(revision 350350)
+++ head/contrib/mandoc/man_validate.c	(revision 350351)
@@ -1,539 +1,537 @@
-/*	$Id: man_validate.c,v 1.146 2018/12/31 10:04:39 schwarze Exp $ */
+/*	$Id: man_validate.c,v 1.149 2019/06/27 15:07:30 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2010, 2012-2018 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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 "config.h"
 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #include "mandoc_aux.h"
 #include "mandoc.h"
 #include "roff.h"
 #include "man.h"
 #include "libmandoc.h"
 #include "roff_int.h"
 #include "libman.h"
 
 #define	CHKARGS	  struct roff_man *man, struct roff_node *n
 
 typedef	void	(*v_check)(CHKARGS);
 
-static	void	  check_abort(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_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_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 */
 	NULL,       /* TP */
 	NULL,       /* 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 = man->quick ? mandoc_strdup("") :
-		    mandoc_normdate(man, NULL, n->line, n->pos);
+		man->meta.date = mandoc_normdate(man, NULL, n->line, n->pos);
 	}
 
 	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();
 }
 
 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;
 
 	if (n->type != ROFFT_BODY || (nc = n->child) == NULL)
 		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_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;
 	}
 }
 
 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 && n->string) {
-		for (p = n->string; '\0' != *p; p++) {
+	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)
+	if (n != NULL)
 		n = n->next;
-	if (n && n->string)
+	if (n != NULL && n->string != NULL)
 		man->meta.msec = mandoc_strdup(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)
+	if (n != NULL)
 		n = n->next;
-	if (n && n->string && '\0' != n->string[0]) {
-		man->meta.date = man->quick ?
-		    mandoc_strdup(n->string) :
-		    mandoc_normdate(man, n->string, n->line, n->pos);
-	} else {
+	if (n != NULL && n->string != NULL && n->string[0] != '\0')
+		man->meta.date = mandoc_normdate(man,
+		    n->string, n->line, n->pos);
+	else {
 		man->meta.date = mandoc_strdup("");
 		mandoc_msg(MANDOCERR_DATE_MISSING,
-		    n ? n->line : nb->line,
-		    n ? n->pos : nb->pos, "TH");
+		    n == NULL ? nb->line : n->line,
+		    n == NULL ? nb->pos : n->pos, "TH");
 	}
 
 	/* 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;
 }
Index: head/contrib/mandoc/mandoc.1
===================================================================
--- head/contrib/mandoc/mandoc.1	(revision 350350)
+++ head/contrib/mandoc/mandoc.1	(revision 350351)
@@ -1,2270 +1,2317 @@
-.\"	$Id: mandoc.1,v 1.237 2019/02/23 18:53:54 schwarze Exp $
+.\"	$Id: mandoc.1,v 1.240 2019/07/10 19:39:01 schwarze Exp $
 .\"
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2012, 2014-2018 Ingo Schwarze 
+.\" Copyright (c) 2012, 2014-2019 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
 .\" copyright notice and this permission notice appear in all copies.
 .\"
 .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 23 2019 $
+.Dd $Mdocdate: July 10 2019 $
 .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 man(1).
+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  declaration and the , , and 
 elements and only emit the subtree below the  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.
 .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 cause
+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 except those about non-existent or unreadable input files
-are hidden unless their level, or a lower level, is requested using a
+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 "missing date, using today's date"
 .Pq mdoc, man
 The document was parsed as
 .Xr mdoc 7
 and it has no
 .Ic \&Dd
 macro, or the
 .Ic \&Dd
 macro has no arguments or only empty arguments;
 or the document was parsed as
 .Xr man 7
 and it has no
 .Ic \&TH
 macro, or the
 .Ic \&TH
 macro has less than three arguments or its third argument is empty.
 .It Sy "cannot parse date, using it verbatim"
 .Pq mdoc , man
 The date given in a
 .Ic \&Dd
 or
 .Ic \&TH
 macro does not follow the conventional format.
 .It Sy "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 replaced with
+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 .
Index: head/contrib/mandoc/mandoc.c
===================================================================
--- head/contrib/mandoc/mandoc.c	(revision 350350)
+++ head/contrib/mandoc/mandoc.c	(revision 350351)
@@ -1,643 +1,648 @@
-/*	$Id: mandoc.c,v 1.114 2018/12/30 00:49:55 schwarze Exp $ */
+/*	$Id: mandoc.c,v 1.116 2019/06/27 15:07:30 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons 
  * Copyright (c) 2011-2015, 2017, 2018 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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 "config.h"
 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #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 = NULL;
 #if HAVE_STRPTIME
 	pp = strptime(p, fmt, &tm);
 #endif
 	if (NULL != pp && '\0' == *pp) {
 		*t = mktime(&tm);
 		return 1;
 	}
 
 	return 0;
 }
 
 static char *
 time2a(time_t t)
 {
 	struct tm	*tm;
 	char		*buf, *p;
 	size_t		 ssz;
 	int		 isz;
 
+	buf = NULL;
 	tm = localtime(&t);
 	if (tm == NULL)
-		return 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.
 	 */
 
-	if ((isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)) == -1)
+	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 NULL;
+	return mandoc_strdup("");
 }
 
 char *
 mandoc_normdate(struct roff_man *man, char *in, int ln, int pos)
 {
 	char		*cp;
 	time_t		 t;
 
+	if (man->quick)
+		return mandoc_strdup(in == NULL ? "" : in);
+
 	/* No date specified: use today's date. */
 
-	if (in == NULL || *in == '\0' || strcmp(in, "$" "Mdocdate$") == 0) {
+	if (in == NULL || *in == '\0')
 		mandoc_msg(MANDOCERR_DATE_MISSING, ln, pos, NULL);
+	if (in == NULL || *in == '\0' || strcmp(in, "$" "Mdocdate$") == 0)
 		return time2a(time(NULL));
-	}
 
 	/* Valid mdoc(7) date format. */
 
 	if (a2time(&t, "$" "Mdocdate: %b %d %Y $", in) ||
 	    a2time(&t, "%b %d, %Y", in)) {
 		cp = time2a(t);
 		if (t > time(NULL) + 86400)
 			mandoc_msg(MANDOCERR_DATE_FUTURE, ln, pos, "%s", cp);
 		else if (*in != '$' && strcmp(in, cp) != 0)
 			mandoc_msg(MANDOCERR_DATE_NORM, ln, pos, "%s", cp);
 		return cp;
 	}
 
 	/* In man(7), do not warn about the legacy format. */
 
 	if (a2time(&t, "%Y-%m-%d", in) == 0)
 		mandoc_msg(MANDOCERR_DATE_BAD, ln, pos, "%s", in);
 	else if (t > time(NULL) + 86400)
 		mandoc_msg(MANDOCERR_DATE_FUTURE, ln, pos, "%s", in);
 	else if (man->meta.macroset == MACROSET_MDOC)
 		mandoc_msg(MANDOCERR_DATE_LEGACY, ln, pos, "Dd %s", in);
 
 	/* Use any non-mdoc(7) date verbatim. */
 
 	return mandoc_strdup(in);
 }
 
 int
 mandoc_eos(const char *p, size_t sz)
 {
 	const char	*q;
 	int		 enclosed, found;
 
 	if (0 == sz)
 		return 0;
 
 	/*
 	 * End-of-sentence recognition must include situations where
 	 * some symbols, such as `)', allow prior EOS punctuation to
 	 * propagate outward.
 	 */
 
 	enclosed = found = 0;
 	for (q = p + (int)sz - 1; q >= p; q--) {
 		switch (*q) {
 		case '\"':
 		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;
 }
Index: head/contrib/mandoc/mandoc.css
===================================================================
--- head/contrib/mandoc/mandoc.css	(revision 350350)
+++ head/contrib/mandoc/mandoc.css	(revision 350351)
@@ -1,347 +1,360 @@
-/* $Id: mandoc.css,v 1.45 2019/03/01 10:57:18 schwarze Exp $ */
+/* $Id: mandoc.css,v 1.46 2019/06/02 16:57:13 schwarze Exp $ */
 /*
  * Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
  *
  * Written by Ingo Schwarze .
  * 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; }
-body {		font-family: Helvetica,Arial,sans-serif; }
+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;
-		font-size: 110%; }
+		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 #000;
+		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;
-		color: black;
-		background: #fff; }
+		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; }
 }
Index: head/contrib/mandoc/mandoc.h
===================================================================
--- head/contrib/mandoc/mandoc.h	(revision 350350)
+++ head/contrib/mandoc/mandoc.h	(revision 350351)
@@ -1,290 +1,319 @@
-/*	$Id: mandoc.h,v 1.262 2018/12/16 00:17:02 schwarze Exp $ */
+/*	$Id: mandoc.h,v 1.264 2019/07/14 18:16:13 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011, 2014 Kristaps Dzonsons 
- * Copyright (c) 2012-2018 Ingo Schwarze 
+ * Copyright (c) 2012-2019 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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.
  */
 
 #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_DATE_MISSING, /* missing date, using today's date */
 	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_FILE, /* cannot open file */
 	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_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 sz);
 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 *);
Index: head/contrib/mandoc/mandoc_char.7
===================================================================
--- head/contrib/mandoc/mandoc_char.7	(revision 350350)
+++ head/contrib/mandoc/mandoc_char.7	(revision 350351)
@@ -1,834 +1,834 @@
-.\"	$Id: mandoc_char.7,v 1.75 2018/12/15 19:30:26 schwarze Exp $
+.\"	$Id: mandoc_char.7,v 1.76 2019/03/31 19:17:26 schwarze Exp $
 .\"
 .\" Copyright (c) 2003 Jason McIntyre 
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
 .\" Copyright (c) 2011,2013,2015,2017,2018 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
 .\" copyright notice and this permission notice appear in all copies.
 .\"
 .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: December 15 2018 $
+.Dd $Mdocdate: March 31 2019 $
 .Dt MANDOC_CHAR 7
 .Os
 .Sh NAME
 .Nm mandoc_char
 .Nd mandoc special characters
 .Sh DESCRIPTION
 This page documents the
 .Xr roff 7
 escape sequences accepted by
 .Xr mandoc 1
 to represent special characters in
 .Xr mdoc 7
 and
 .Xr man 7
 documents.
 .Pp
 The rendering depends on the
 .Xr mandoc 1
 output mode; it can be inspected by calling
 .Xr man 1
 on the
 .Nm
 manual page with different
 .Fl T
 arguments.
 In ASCII output, the rendering of some characters may be hard
 to interpret for the reader.
 Many are rendered as descriptive strings like
 .Qq  ,
 .Qq  ,
 or
 .Qq  ,
 which may look ugly, and many are replaced by similar ASCII characters.
 In particular, accented characters are usually shown without the accent.
 For that reason, try to avoid using any of the special characters
 documented here except those discussed in the
 .Sx DESCRIPTION ,
 unless they are essential for explaining the subject matter at hand,
 for example when documenting complicated mathematical functions.
 .Pp
 In particular, in English manual pages, do not use special-character
 escape sequences to represent national language characters in author
 names; instead, provide ASCII transcriptions of the names.
 .Ss Dashes and Hyphens
 In typography there are different types of dashes of various width:
 the hyphen (\(hy),
 the en-dash (\(en),
 the em-dash (\(em),
 and the mathematical minus sign (\(mi).
 .Pp
 Hyphens are used for adjectives;
 to separate the two parts of a compound word;
 or to separate a word across two successive lines of text.
 The hyphen does not need to be escaped:
 .Bd -unfilled -offset indent
 blue-eyed
 lorry-driver
 .Ed
 .Pp
 The en-dash is used to separate the two elements of a range,
 or can be used the same way as an em-dash.
 It should be written as
 .Sq \e(en :
 .Bd -unfilled -offset indent
 pp. 95\e(en97.
 Go away \e(en or else!
 .Ed
 .Pp
 The em-dash can be used to show an interruption
 or can be used the same way as colons, semi-colons, or parentheses.
 It should be written as
 .Sq \e(em :
 .Bd -unfilled -offset indent
 Three things \e(em apples, oranges, and bananas.
 This is not that \e(em rather, this is that.
 .Ed
 .Pp
 In
 .Xr roff 7
 documents, the minus sign is normally written as
 .Sq \e- .
 In manual pages, some style guides recommend to also use
 .Sq \e-
 if an ASCII 0x2d
 .Dq hyphen-minus
 output glyph that can be copied and pasted is desired in output modes
 supporting it, for example in
 .Fl T Cm utf8
 and
 .Fl T Cm html .
-But currently, no practically relevant manual page formatter actually
-requires that subtlety, so in manual pages just write plain
+But currently, no practically relevant manual page formatter requires
+that subtlety, so in manual pages, it is sufficient to write plain
 .Sq -
 to represent hyphen, minus, and hyphen-minus.
 .Pp
 If a word on a text input line contains a hyphen, a formatter may decide
 to insert an output line break after the hyphen if that helps filling
 the current output line, but the whole word would overflow the line.
 If it is important that the word is not broken across lines in this
 way, a zero-width space
 .Pq Sq \e&
 can be inserted before or after the hyphen.
 While
 .Xr mandoc 1
 never breaks the output line after hyphens adjacent to a zero-width
 space, after any of the other dash- or hyphen-like characters
 represented by escape sequences, or after hyphens inside words in
 macro arguments, other software may not respect these rules and may
 break the line even in such cases.
 .Pp
 Some
 .Xr roff 7
 implementations contains dictionaries allowing to break the line
 at syllable boundaries even inside words that contain no hyphens.
 Such automatic hyphenation is not supported by
 .Xr mandoc 1 ,
 which only breaks the line at whitespace, and inside words only
 after existing hyphens.
 .Ss Spaces
 To separate words in normal text, for indenting and alignment
 in literal context, and when none of the following special cases apply,
 just use the normal space character
 .Pq Sq \  .
 .Pp
 When filling text, output lines may be broken between words, i.e. at space
 characters.
 To prevent a line break between two particular words,
 use the unpaddable non-breaking space escape sequence
 .Pq Sq \e\ \&
 instead of the normal space character.
 For example, the input string
 .Dq number\e\ 1
 will be kept together as
 .Dq number\ 1
 on the same output line.
 .Pp
 On request and macro lines, the normal space character serves as an
 argument delimiter.
 To include whitespace into arguments, quoting is usually the best choice;
 see the MACRO SYNTAX section in
 .Xr roff 7 .
 In some cases, using the non-breaking space escape sequence
 .Pq Sq \e\ \&
 may be preferable.
 .Pp
 To escape macro names and to protect whitespace at the end
 of input lines, the zero-width space
 .Pq Sq \e&
 is often useful.
 For example, in
 .Xr mdoc 7 ,
 a normal space character can be displayed in single quotes in either
 of the following ways:
 .Pp
 .Dl .Sq \(dq \(dq
 .Dl .Sq \e \e&
 .Ss Quotes
 On request and macro lines, the double-quote character
 .Pq Sq \(dq
 is handled specially to allow quoting.
 One way to prevent this special handling is by using the
 .Sq \e(dq
 escape sequence.
 .Pp
 Note that on text lines, literal double-quote characters can be used
 verbatim.
 All other quote-like characters can be used verbatim as well,
 even on request and macro lines.
 .Ss Accents
 In output modes supporting such special output characters, for example
 .Fl T Cm pdf ,
 and sometimes less consistently in
 .Fl T Cm utf8 ,
 some
 .Xr roff 7
 formatters convert the following ASCII input characters to the
 following Unicode special output characters:
 .Bl -column x(ga U+2018 -offset indent
 .It \(ga Ta U+2018 Ta left single quotation mark
 .It \(aq Ta U+2019 Ta right single quotation mark
 .It \(ti Ta U+02DC Ta small tilde
 .It \(ha Ta U+02C6 Ta modifier letter circumflex
 .El
 .Pp
 In prose, this automatic substitution is often desirable;
 but when these characters have to be displayed as plain ASCII
 characters, for example in source code samples, they require
 escaping to render as follows:
 .Bl -column x(ga U+2018 -offset indent
 .It \e(ga Ta U+0060 Ta grave accent
 .It \e(aq Ta U+0027 Ta apostrophe
 .It \e(ti Ta U+007E Ta tilde
 .It \e(ha Ta U+005E Ta circumflex accent
 .El
 .Ss Periods
 The period
 .Pq Sq \&.
 is handled specially at the beginning of an input line,
 where it introduces a
 .Xr roff 7
 request or a macro, and when appearing alone as a macro argument in
 .Xr mdoc 7 .
 In such situations, prepend a zero-width space
 .Pq Sq \e&.
 to make it behave like normal text.
 .Pp
 Do not use the
 .Sq \e.
 escape sequence.
 It does not prevent special handling of the period.
 .Ss Backslashes
 To include a literal backslash
 .Pq Sq \e
 into the output, use the
 .Pq Sq \ee
 escape sequence.
 .Pp
 Note that doubling it
 .Pq Sq \e\e
 is not the right way to output a backslash.
 Because
 .Xr mandoc 1
 does not implement full
 .Xr roff 7
 functionality, it may work with
 .Xr mandoc 1 ,
 but it may have weird effects on complete
 .Xr roff 7
 implementations.
 .Sh SPECIAL CHARACTERS
 Special characters are encoded as
 .Sq \eX
 .Pq for a one-character escape ,
 .Sq \e(XX
 .Pq two-character ,
 and
 .Sq \e[N]
 .Pq N-character .
 For details, see the
 .Em Special Characters
 subsection of the
 .Xr roff 7
 manual.
 .Pp
 Spacing:
 .Bl -column "Input" "Description" -offset indent -compact
 .It Em Input Ta Em Description
 .It Sq \e\ \& Ta unpaddable non-breaking space
 .It \e\(ti   Ta paddable non-breaking space
 .It \e0      Ta digit-width space allowing line break
 .It \e|      Ta one-sixth \e(em narrow space, zero width in nroff mode
 .It \e^      Ta one-twelfth \e(em half-narrow space, zero width in nroff
 .It \e&      Ta zero-width non-breaking space
 .It \e)      Ta zero-width space transparent to end-of-sentence detection
 .It \e%      Ta zero-width space allowing hyphenation
 .It \e:      Ta zero-width space allowing line break
 .El
 .Pp
 Lines:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(ba    Ta \(ba        Ta bar
 .It \e(br    Ta \(br        Ta box rule
 .It \e(ul    Ta \(ul        Ta underscore
 .It \e(ru    Ta \(ru        Ta underscore (width 0.5m)
 .It \e(rn    Ta \(rn        Ta overline
 .It \e(bb    Ta \(bb        Ta broken bar
 .It \e(sl    Ta \(sl        Ta forward slash
 .It \e(rs    Ta \(rs        Ta backward slash
 .El
 .Pp
 Text markers:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(ci    Ta \(ci        Ta circle
 .It \e(bu    Ta \(bu        Ta bullet
 .It \e(dd    Ta \(dd        Ta double dagger
 .It \e(dg    Ta \(dg        Ta dagger
 .It \e(lz    Ta \(lz        Ta lozenge
 .It \e(sq    Ta \(sq        Ta white square
 .It \e(ps    Ta \(ps        Ta paragraph
 .It \e(sc    Ta \(sc        Ta section
 .It \e(lh    Ta \(lh        Ta left hand
 .It \e(rh    Ta \(rh        Ta right hand
 .It \e(at    Ta \(at        Ta at
 .It \e(sh    Ta \(sh        Ta hash (pound)
 .It \e(CR    Ta \(CR        Ta carriage return
 .It \e(OK    Ta \(OK        Ta check mark
 .It \e(CL    Ta \(CL        Ta club suit
 .It \e(SP    Ta \(SP        Ta spade suit
 .It \e(HE    Ta \(HE        Ta heart suit
 .It \e(DI    Ta \(DI        Ta diamond suit
 .El
 .Pp
 Legal symbols:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(co    Ta \(co        Ta copyright
 .It \e(rg    Ta \(rg        Ta registered
 .It \e(tm    Ta \(tm        Ta trademarked
 .El
 .Pp
 Punctuation:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(em    Ta \(em        Ta em-dash
 .It \e(en    Ta \(en        Ta en-dash
 .It \e(hy    Ta \(hy        Ta hyphen
 .It \ee      Ta \e          Ta back-slash
 .It \e.      Ta \.          Ta period
 .It \e(r!    Ta \(r!        Ta upside-down exclamation
 .It \e(r?    Ta \(r?        Ta upside-down question
 .El
 .Pp
 Quotes:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(Bq    Ta \(Bq        Ta right low double-quote
 .It \e(bq    Ta \(bq        Ta right low single-quote
 .It \e(lq    Ta \(lq        Ta left double-quote
 .It \e(rq    Ta \(rq        Ta right double-quote
 .It \e(oq    Ta \(oq        Ta left single-quote
 .It \e(cq    Ta \(cq        Ta right single-quote
 .It \e(aq    Ta \(aq        Ta apostrophe quote (ASCII character)
 .It \e(dq    Ta \(dq        Ta double quote (ASCII character)
 .It \e(Fo    Ta \(Fo        Ta left guillemet
 .It \e(Fc    Ta \(Fc        Ta right guillemet
 .It \e(fo    Ta \(fo        Ta left single guillemet
 .It \e(fc    Ta \(fc        Ta right single guillemet
 .El
 .Pp
 Brackets:
 .Bl -column "xxbracketrightbtx" Rendered Description -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(lB    Ta \(lB        Ta left bracket
 .It \e(rB    Ta \(rB        Ta right bracket
 .It \e(lC    Ta \(lC        Ta left brace
 .It \e(rC    Ta \(rC        Ta right brace
 .It \e(la    Ta \(la        Ta left angle
 .It \e(ra    Ta \(ra        Ta right angle
 .It \e(bv    Ta \(bv        Ta brace extension (special font)
 .It \e[braceex] Ta \[braceex] Ta brace extension
 .It \e[bracketlefttp] Ta \[bracketlefttp] Ta top-left hooked bracket
 .It \e[bracketleftbt] Ta \[bracketleftbt] Ta bottom-left hooked bracket
 .It \e[bracketleftex] Ta \[bracketleftex] Ta left hooked bracket extension
 .It \e[bracketrighttp] Ta \[bracketrighttp] Ta top-right hooked bracket
 .It \e[bracketrightbt] Ta \[bracketrightbt] Ta bottom-right hooked bracket
 .It \e[bracketrightex] Ta \[bracketrightex] Ta right hooked bracket extension
 .It \e(lt    Ta \(lt        Ta top-left hooked brace
 .It \e[bracelefttp] Ta \[bracelefttp] Ta top-left hooked brace
 .It \e(lk    Ta \(lk        Ta mid-left hooked brace
 .It \e[braceleftmid] Ta \[braceleftmid] Ta mid-left hooked brace
 .It \e(lb    Ta \(lb        Ta bottom-left hooked brace
 .It \e[braceleftbt] Ta \[braceleftbt] Ta bottom-left hooked brace
 .It \e[braceleftex] Ta \[braceleftex] Ta left hooked brace extension
 .It \e(rt    Ta \(rt        Ta top-left hooked brace
 .It \e[bracerighttp] Ta \[bracerighttp] Ta top-right hooked brace
 .It \e(rk    Ta \(rk        Ta mid-right hooked brace
 .It \e[bracerightmid] Ta \[bracerightmid] Ta mid-right hooked brace
 .It \e(rb    Ta \(rb        Ta bottom-right hooked brace
 .It \e[bracerightbt] Ta \[bracerightbt] Ta bottom-right hooked brace
 .It \e[bracerightex] Ta \[bracerightex] Ta right hooked brace extension
 .It \e[parenlefttp] Ta \[parenlefttp] Ta top-left hooked parenthesis
 .It \e[parenleftbt] Ta \[parenleftbt] Ta bottom-left hooked parenthesis
 .It \e[parenleftex] Ta \[parenleftex] Ta left hooked parenthesis extension
 .It \e[parenrighttp] Ta \[parenrighttp] Ta top-right hooked parenthesis
 .It \e[parenrightbt] Ta \[parenrightbt] Ta bottom-right hooked parenthesis
 .It \e[parenrightex] Ta \[parenrightex] Ta right hooked parenthesis extension
 .El
 .Pp
 Arrows:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(<-    Ta \(<-        Ta left arrow
 .It \e(->    Ta \(->        Ta right arrow
 .It \e(<>    Ta \(<>        Ta left-right arrow
 .It \e(da    Ta \(da        Ta down arrow
 .It \e(ua    Ta \(ua        Ta up arrow
 .It \e(va    Ta \(va        Ta up-down arrow
 .It \e(lA    Ta \(lA        Ta left double-arrow
 .It \e(rA    Ta \(rA        Ta right double-arrow
 .It \e(hA    Ta \(hA        Ta left-right double-arrow
 .It \e(uA    Ta \(uA        Ta up double-arrow
 .It \e(dA    Ta \(dA        Ta down double-arrow
 .It \e(vA    Ta \(vA        Ta up-down double-arrow
 .It \e(an    Ta \(an        Ta horizontal arrow extension
 .El
 .Pp
 Logical:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(AN    Ta \(AN        Ta logical and
 .It \e(OR    Ta \(OR        Ta logical or
 .It \e[tno]  Ta \[tno]      Ta logical not (text font)
 .It \e(no    Ta \(no        Ta logical not (special font)
 .It \e(te    Ta \(te        Ta existential quantifier
 .It \e(fa    Ta \(fa        Ta universal quantifier
 .It \e(st    Ta \(st        Ta such that
 .It \e(tf    Ta \(tf        Ta therefore
 .It \e(3d    Ta \(3d        Ta therefore
 .It \e(or    Ta \(or        Ta bitwise or
 .El
 .Pp
 Mathematical:
 .Bl -column "xxcoproductxx" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e-      Ta \-          Ta minus (text font)
 .It \e(mi    Ta \(mi        Ta minus (special font)
 .It +        Ta +           Ta plus (text font)
 .It \e(pl    Ta \(pl        Ta plus (special font)
 .It \e(-+    Ta \(-+        Ta minus-plus
 .It \e[t+-]  Ta \[t+-]      Ta plus-minus (text font)
 .It \e(+-    Ta \(+-        Ta plus-minus (special font)
 .It \e(pc    Ta \(pc        Ta center-dot
 .It \e[tmu]  Ta \[tmu]      Ta multiply (text font)
 .It \e(mu    Ta \(mu        Ta multiply (special font)
 .It \e(c*    Ta \(c*        Ta circle-multiply
 .It \e(c+    Ta \(c+        Ta circle-plus
 .It \e[tdi]  Ta \[tdi]      Ta divide (text font)
 .It \e(di    Ta \(di        Ta divide (special font)
 .It \e(f/    Ta \(f/        Ta fraction
 .It \e(**    Ta \(**        Ta asterisk
 .It \e(<=    Ta \(<=        Ta less-than-equal
 .It \e(>=    Ta \(>=        Ta greater-than-equal
 .It \e(<<    Ta \(<<        Ta much less
 .It \e(>>    Ta \(>>        Ta much greater
 .It \e(eq    Ta \(eq        Ta equal
 .It \e(!=    Ta \(!=        Ta not equal
 .It \e(==    Ta \(==        Ta equivalent
 .It \e(ne    Ta \(ne        Ta not equivalent
 .It \e(ap    Ta \(ap        Ta tilde operator
 .It \e(|=    Ta \(|=        Ta asymptotically equal
 .It \e(=\(ti Ta \(=~        Ta approximately equal
 .It \e(\(ti\(ti Ta \(~~        Ta almost equal
 .It \e(\(ti= Ta \(~=        Ta almost equal
 .It \e(pt    Ta \(pt        Ta proportionate
 .It \e(es    Ta \(es        Ta empty set
 .It \e(mo    Ta \(mo        Ta element
 .It \e(nm    Ta \(nm        Ta not element
 .It \e(sb    Ta \(sb        Ta proper subset
 .It \e(nb    Ta \(nb        Ta not subset
 .It \e(sp    Ta \(sp        Ta proper superset
 .It \e(nc    Ta \(nc        Ta not superset
 .It \e(ib    Ta \(ib        Ta reflexive subset
 .It \e(ip    Ta \(ip        Ta reflexive superset
 .It \e(ca    Ta \(ca        Ta intersection
 .It \e(cu    Ta \(cu        Ta union
 .It \e(/_    Ta \(/_        Ta angle
 .It \e(pp    Ta \(pp        Ta perpendicular
 .It \e(is    Ta \(is        Ta integral
 .It \e[integral] Ta \[integral] Ta integral
 .It \e[sum]    Ta \[sum]   Ta summation
 .It \e[product] Ta \[product] Ta product
 .It \e[coproduct] Ta \[coproduct] Ta coproduct
 .It \e(gr    Ta \(gr        Ta gradient
 .It \e(sr    Ta \(sr        Ta square root
 .It \e[sqrt] Ta \[sqrt]     Ta square root
 .It \e(lc    Ta \(lc        Ta left-ceiling
 .It \e(rc    Ta \(rc        Ta right-ceiling
 .It \e(lf    Ta \(lf        Ta left-floor
 .It \e(rf    Ta \(rf        Ta right-floor
 .It \e(if    Ta \(if        Ta infinity
 .It \e(Ah    Ta \(Ah        Ta aleph
 .It \e(Im    Ta \(Im        Ta imaginary
 .It \e(Re    Ta \(Re        Ta real
 .It \e(wp    Ta \(wp        Ta Weierstrass p
 .It \e(pd    Ta \(pd        Ta partial differential
 .It \e(-h    Ta \(-h        Ta Planck constant over 2\(*p
 .It \e[hbar] Ta \[hbar]     Ta Planck constant over 2\(*p
 .It \e(12    Ta \(12        Ta one-half
 .It \e(14    Ta \(14        Ta one-fourth
 .It \e(34    Ta \(34        Ta three-fourths
 .It \e(18    Ta \(18        Ta one-eighth
 .It \e(38    Ta \(38        Ta three-eighths
 .It \e(58    Ta \(58        Ta five-eighths
 .It \e(78    Ta \(78        Ta seven-eighths
 .It \e(S1    Ta \(S1        Ta superscript 1
 .It \e(S2    Ta \(S2        Ta superscript 2
 .It \e(S3    Ta \(S3        Ta superscript 3
 .El
 .Pp
 Ligatures:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(ff    Ta \(ff        Ta ff ligature
 .It \e(fi    Ta \(fi        Ta fi ligature
 .It \e(fl    Ta \(fl        Ta fl ligature
 .It \e(Fi    Ta \(Fi        Ta ffi ligature
 .It \e(Fl    Ta \(Fl        Ta ffl ligature
 .It \e(AE    Ta \(AE        Ta AE
 .It \e(ae    Ta \(ae        Ta ae
 .It \e(OE    Ta \(OE        Ta OE
 .It \e(oe    Ta \(oe        Ta oe
 .It \e(ss    Ta \(ss        Ta German eszett
 .It \e(IJ    Ta \(IJ        Ta IJ ligature
 .It \e(ij    Ta \(ij        Ta ij ligature
 .El
 .Pp
 Accents:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(a"    Ta \(a"        Ta Hungarian umlaut
 .It \e(a-    Ta \(a-        Ta macron
 .It \e(a.    Ta \(a.        Ta dotted
 .It \e(a^    Ta \(a^        Ta circumflex
 .It \e(aa    Ta \(aa        Ta acute
 .It \e\(aq   Ta \'          Ta acute
 .It \e(ga    Ta \(ga        Ta grave
 .It \e\(ga   Ta \`          Ta grave
 .It \e(ab    Ta \(ab        Ta breve
 .It \e(ac    Ta \(ac        Ta cedilla
 .It \e(ad    Ta \(ad        Ta dieresis
 .It \e(ah    Ta \(ah        Ta caron
 .It \e(ao    Ta \(ao        Ta ring
 .It \e(a\(ti Ta \(a~        Ta tilde
 .It \e(ho    Ta \(ho        Ta ogonek
 .It \e(ha    Ta \(ha        Ta hat (ASCII character)
 .It \e(ti    Ta \(ti        Ta tilde (ASCII character)
 .El
 .Pp
 Accented letters:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(\(aqA Ta \('A        Ta acute A
 .It \e(\(aqE Ta \('E        Ta acute E
 .It \e(\(aqI Ta \('I        Ta acute I
 .It \e(\(aqO Ta \('O        Ta acute O
 .It \e(\(aqU Ta \('U        Ta acute U
 .It \e(\(aqY Ta \('Y        Ta acute Y
 .It \e(\(aqa Ta \('a        Ta acute a
 .It \e(\(aqe Ta \('e        Ta acute e
 .It \e(\(aqi Ta \('i        Ta acute i
 .It \e(\(aqo Ta \('o        Ta acute o
 .It \e(\(aqu Ta \('u        Ta acute u
 .It \e(\(aqy Ta \('y        Ta acute y
 .It \e(\(gaA Ta \(`A        Ta grave A
 .It \e(\(gaE Ta \(`E        Ta grave E
 .It \e(\(gaI Ta \(`I        Ta grave I
 .It \e(\(gaO Ta \(`O        Ta grave O
 .It \e(\(gaU Ta \(`U        Ta grave U
 .It \e(\(gaa Ta \(`a        Ta grave a
 .It \e(\(gae Ta \(`e        Ta grave e
 .It \e(\(gai Ta \(`i        Ta grave i
 .It \e(\(gao Ta \(`i        Ta grave o
 .It \e(\(gau Ta \(`u        Ta grave u
 .It \e(\(tiA Ta \(~A        Ta tilde A
 .It \e(\(tiN Ta \(~N        Ta tilde N
 .It \e(\(tiO Ta \(~O        Ta tilde O
 .It \e(\(tia Ta \(~a        Ta tilde a
 .It \e(\(tin Ta \(~n        Ta tilde n
 .It \e(\(tio Ta \(~o        Ta tilde o
 .It \e(:A    Ta \(:A        Ta dieresis A
 .It \e(:E    Ta \(:E        Ta dieresis E
 .It \e(:I    Ta \(:I        Ta dieresis I
 .It \e(:O    Ta \(:O        Ta dieresis O
 .It \e(:U    Ta \(:U        Ta dieresis U
 .It \e(:a    Ta \(:a        Ta dieresis a
 .It \e(:e    Ta \(:e        Ta dieresis e
 .It \e(:i    Ta \(:i        Ta dieresis i
 .It \e(:o    Ta \(:o        Ta dieresis o
 .It \e(:u    Ta \(:u        Ta dieresis u
 .It \e(:y    Ta \(:y        Ta dieresis y
 .It \e(^A    Ta \(^A        Ta circumflex A
 .It \e(^E    Ta \(^E        Ta circumflex E
 .It \e(^I    Ta \(^I        Ta circumflex I
 .It \e(^O    Ta \(^O        Ta circumflex O
 .It \e(^U    Ta \(^U        Ta circumflex U
 .It \e(^a    Ta \(^a        Ta circumflex a
 .It \e(^e    Ta \(^e        Ta circumflex e
 .It \e(^i    Ta \(^i        Ta circumflex i
 .It \e(^o    Ta \(^o        Ta circumflex o
 .It \e(^u    Ta \(^u        Ta circumflex u
 .It \e(,C    Ta \(,C        Ta cedilla C
 .It \e(,c    Ta \(,c        Ta cedilla c
 .It \e(/L    Ta \(/L        Ta stroke L
 .It \e(/l    Ta \(/l        Ta stroke l
 .It \e(/O    Ta \(/O        Ta stroke O
 .It \e(/o    Ta \(/o        Ta stroke o
 .It \e(oA    Ta \(oA        Ta ring A
 .It \e(oa    Ta \(oa        Ta ring a
 .El
 .Pp
 Special letters:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(-D    Ta \(-D        Ta Eth
 .It \e(Sd    Ta \(Sd        Ta eth
 .It \e(TP    Ta \(TP        Ta Thorn
 .It \e(Tp    Ta \(Tp        Ta thorn
 .It \e(.i    Ta \(.i        Ta dotless i
 .It \e(.j    Ta \(.j        Ta dotless j
 .El
 .Pp
 Currency:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(Do    Ta \(Do        Ta dollar
 .It \e(ct    Ta \(ct        Ta cent
 .It \e(Eu    Ta \(Eu        Ta Euro symbol
 .It \e(eu    Ta \(eu        Ta Euro symbol
 .It \e(Ye    Ta \(Ye        Ta yen
 .It \e(Po    Ta \(Po        Ta pound
 .It \e(Cs    Ta \(Cs        Ta Scandinavian
 .It \e(Fn    Ta \(Fn        Ta florin
 .El
 .Pp
 Units:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(de    Ta \(de        Ta degree
 .It \e(%0    Ta \(%0        Ta per-thousand
 .It \e(fm    Ta \(fm        Ta minute
 .It \e(sd    Ta \(sd        Ta second
 .It \e(mc    Ta \(mc        Ta micro
 .It \e(Of    Ta \(Of        Ta Spanish female ordinal
 .It \e(Om    Ta \(Om        Ta Spanish masculine ordinal
 .El
 .Pp
 Greek letters:
 .Bl -column "Input" "Rendered" "Description" -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(*A    Ta \(*A        Ta Alpha
 .It \e(*B    Ta \(*B        Ta Beta
 .It \e(*G    Ta \(*G        Ta Gamma
 .It \e(*D    Ta \(*D        Ta Delta
 .It \e(*E    Ta \(*E        Ta Epsilon
 .It \e(*Z    Ta \(*Z        Ta Zeta
 .It \e(*Y    Ta \(*Y        Ta Eta
 .It \e(*H    Ta \(*H        Ta Theta
 .It \e(*I    Ta \(*I        Ta Iota
 .It \e(*K    Ta \(*K        Ta Kappa
 .It \e(*L    Ta \(*L        Ta Lambda
 .It \e(*M    Ta \(*M        Ta Mu
 .It \e(*N    Ta \(*N        Ta Nu
 .It \e(*C    Ta \(*C        Ta Xi
 .It \e(*O    Ta \(*O        Ta Omicron
 .It \e(*P    Ta \(*P        Ta Pi
 .It \e(*R    Ta \(*R        Ta Rho
 .It \e(*S    Ta \(*S        Ta Sigma
 .It \e(*T    Ta \(*T        Ta Tau
 .It \e(*U    Ta \(*U        Ta Upsilon
 .It \e(*F    Ta \(*F        Ta Phi
 .It \e(*X    Ta \(*X        Ta Chi
 .It \e(*Q    Ta \(*Q        Ta Psi
 .It \e(*W    Ta \(*W        Ta Omega
 .It \e(*a    Ta \(*a        Ta alpha
 .It \e(*b    Ta \(*b        Ta beta
 .It \e(*g    Ta \(*g        Ta gamma
 .It \e(*d    Ta \(*d        Ta delta
 .It \e(*e    Ta \(*e        Ta epsilon
 .It \e(*z    Ta \(*z        Ta zeta
 .It \e(*y    Ta \(*y        Ta eta
 .It \e(*h    Ta \(*h        Ta theta
 .It \e(*i    Ta \(*i        Ta iota
 .It \e(*k    Ta \(*k        Ta kappa
 .It \e(*l    Ta \(*l        Ta lambda
 .It \e(*m    Ta \(*m        Ta mu
 .It \e(*n    Ta \(*n        Ta nu
 .It \e(*c    Ta \(*c        Ta xi
 .It \e(*o    Ta \(*o        Ta omicron
 .It \e(*p    Ta \(*p        Ta pi
 .It \e(*r    Ta \(*r        Ta rho
 .It \e(*s    Ta \(*s        Ta sigma
 .It \e(*t    Ta \(*t        Ta tau
 .It \e(*u    Ta \(*u        Ta upsilon
 .It \e(*f    Ta \(*f        Ta phi
 .It \e(*x    Ta \(*x        Ta chi
 .It \e(*q    Ta \(*q        Ta psi
 .It \e(*w    Ta \(*w        Ta omega
 .It \e(+h    Ta \(+h        Ta theta variant
 .It \e(+f    Ta \(+f        Ta phi variant
 .It \e(+p    Ta \(+p        Ta pi variant
 .It \e(+e    Ta \(+e        Ta epsilon variant
 .It \e(ts    Ta \(ts        Ta sigma terminal
 .El
 .Sh PREDEFINED STRINGS
 Predefined strings are inherited from the macro packages of historical
 troff implementations.
 They are
 .Em not recommended
 for use, as they differ across implementations.
 Manuals using these predefined strings are almost certainly not
 portable.
 .Pp
 Their syntax is similar to special characters, using
 .Sq \e*X
 .Pq for a one-character escape ,
 .Sq \e*(XX
 .Pq two-character ,
 and
 .Sq \e*[N]
 .Pq N-character .
 For details, see the
 .Em Predefined Strings
 subsection of the
 .Xr roff 7
 manual.
 .Bl -column "Input" "Rendered" "Description" -offset indent
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e*(Ba   Ta \*(Ba       Ta vertical bar
 .It \e*(Ne   Ta \*(Ne       Ta not equal
 .It \e*(Ge   Ta \*(Ge       Ta greater-than-equal
 .It \e*(Le   Ta \*(Le       Ta less-than-equal
 .It \e*(Gt   Ta \*(Gt       Ta greater-than
 .It \e*(Lt   Ta \*(Lt       Ta less-than
 .It \e*(Pm   Ta \*(Pm       Ta plus-minus
 .It \e*(If   Ta \*(If       Ta infinity
 .It \e*(Pi   Ta \*(Pi       Ta pi
 .It \e*(Na   Ta \*(Na       Ta NaN
 .It \e*(Am   Ta \*(Am       Ta ampersand
 .It \e*R     Ta \*R         Ta restricted mark
 .It \e*(Tm   Ta \*(Tm       Ta trade mark
 .It \e*q     Ta \*q         Ta double-quote
 .It \e*(Rq   Ta \*(Rq       Ta right-double-quote
 .It \e*(Lq   Ta \*(Lq       Ta left-double-quote
 .It \e*(lp   Ta \*(lp       Ta right-parenthesis
 .It \e*(rp   Ta \*(rp       Ta left-parenthesis
 .It \e*(lq   Ta \*(lq       Ta left double-quote
 .It \e*(rq   Ta \*(rq       Ta right double-quote
 .It \e*(ua   Ta \*(ua       Ta up arrow
 .It \e*(va   Ta \*(va       Ta up-down arrow
 .It \e*(<=   Ta \*(<=       Ta less-than-equal
 .It \e*(>=   Ta \*(>=       Ta greater-than-equal
 .It \e*(aa   Ta \*(aa       Ta acute
 .It \e*(ga   Ta \*(ga       Ta grave
 .It \e*(Px   Ta \*(Px       Ta POSIX standard name
 .It \e*(Ai   Ta \*(Ai       Ta ANSI standard name
 .El
 .Sh UNICODE CHARACTERS
 The escape sequences
 .Pp
 .Dl \e[uXXXX] and \eC\(aquXXXX\(aq
 .Pp
 are interpreted as Unicode codepoints.
 The codepoint must be in the range above U+0080 and less than U+10FFFF.
 For compatibility, the hexadecimal digits
 .Sq A
 to
 .Sq F
 must be given as uppercase characters,
 and points must be zero-padded to four characters; if
 greater than four characters, no zero padding is allowed.
 Unicode surrogates are not allowed.
 .Sh NUMBERED CHARACTERS
 For backward compatibility with existing manuals,
 .Xr mandoc 1
 also supports the
 .Pp
 .Dl \eN\(aq Ns Ar number Ns \(aq and \e[ Ns Cm char Ns Ar number ]
 .Pp
 escape sequences, inserting the character
 .Ar number
 from the current character set into the output.
 Of course, this is inherently non-portable and is already marked
 as deprecated in the Heirloom roff manual;
 on top of that, the second form is a GNU extension.
 For example, do not use \eN\(aq34\(aq or \e[char34], use \e(dq,
 or even the plain
 .Sq \(dq
 character where possible.
 .Sh COMPATIBILITY
 This section documents compatibility between mandoc and other
 troff implementations, at this time limited to GNU troff
 .Pq Qq groff .
 .Pp
 .Bl -dash -compact
 .It
 The \eN\(aq\(aq escape sequence is limited to printable characters; in
 groff, it accepts arbitrary character numbers.
 .It
 In
 .Fl T Ns Cm ascii ,
 the
 \e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product],
 \e[coproduct], \e(gr, \e(-h, and \e(a. special characters render
 differently between mandoc and groff.
 .It
 In
 .Fl T Ns Cm html ,
 the \e(\(ti=, \e(nb, and \e(nc special characters render differently
 between mandoc and groff.
 .It
 The
 .Fl T Ns Cm ps
 and
 .Fl T Ns Cm pdf
 modes format like
 .Fl T Ns Cm ascii
 instead of rendering glyphs as in groff.
 .It
 The \e[radicalex], \e[sqrtex], and \e(ru special characters have been omitted
 from mandoc either because they are poorly documented or they have no
 known representation.
 .El
 .Sh SEE ALSO
 .Xr mandoc 1 ,
 .Xr man 7 ,
 .Xr mdoc 7 ,
 .Xr roff 7
 .Sh AUTHORS
 The
 .Nm
 manual page was written by
 .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
 .Sh CAVEATS
 The predefined string
 .Sq \e*(Ba
 mimics the behaviour of the
 .Sq \&|
 character in
 .Xr mdoc 7 ;
 thus, if you wish to render a vertical bar with no side effects, use
 the
 .Sq \e(ba
 escape.
Index: head/contrib/mandoc/mandoc_headers.3
===================================================================
--- head/contrib/mandoc/mandoc_headers.3	(revision 350350)
+++ head/contrib/mandoc/mandoc_headers.3	(revision 350351)
@@ -1,682 +1,701 @@
-.Dd $Mdocdate: December 30 2018 $
+.\"	$Id: mandoc_headers.3,v 1.31 2019/03/17 18:21:45 schwarze Exp $
+.\"
+.\" Copyright (c) 2014-2019 Ingo Schwarze 
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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: March 17 2019 $
 .Dt MANDOC_HEADERS 3
 .Os
 .Sh NAME
 .Nm mandoc_headers
 .Nd ordering of mandoc include files
 .Sh DESCRIPTION
 To support a cleaner coding style, the mandoc header files do not
 contain any include directives and do not guard against multiple
 inclusion.
 The application developer has to make sure that the headers are
 included in a proper order, and that no header is included more
 than once.
 .Pp
 The headers and functions form three major groups:
 .Sx Parser interface ,
 .Sx Parser internals ,
 and
 .Sx Formatter interface .
 .Pp
 Various rules are given below prohibiting the inclusion of certain
 combinations of headers into the same file.
 The intention is to keep the following functional components
 separate from each other:
 .Pp
 .Bl -dash -offset indent -compact
 .It
 .Xr roff 7
 parser
 .It
 .Xr mdoc 7
 parser
 .It
 .Xr man 7
 parser
 .It
 .Xr tbl 7
 parser
 .It
 .Xr eqn 7
 parser
 .It
 terminal formatters
 .It
 HTML formatters
 .It
 search tools
 .It
 main programs
 .El
 .Pp
 Note that mere usage of an opaque struct type does
 .Em not
 require inclusion of the header where that type is defined.
 .Ss Parser interface
 Each of the following headers can be included without including
 any other mandoc header.
 These headers should be included before any other mandoc headers.
 .Bl -tag -width Ds
 .It Qq Pa mandoc_aux.h
 Memory allocation utility functions; can be used everywhere.
 .Pp
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides the functions documented in
 .Xr mandoc_malloc 3 .
 .It Qq Pa mandoc_ohash.h
 Hashing utility functions; can be used everywhere.
 .Pp
 Requires
 .In stddef.h
 for
 .Vt ptrdiff_t
 and
 .In stdint.h
 for
 .Vt uint32_t .
 .Pp
 Includes
 .In ohash.h
 and provides
 .Fn mandoc_ohash_init .
 .It Qq Pa mandoc.h
 Error handling, escape sequence, and character utilities;
 can be used everywhere.
 .Pp
 Requires
 .In sys/types.h
 for
 .Vt size_t
 and
 .In stdio.h
 for
 .Vt FILE .
 .Pp
 Provides
 .Vt enum mandoc_esc ,
 .Vt enum mandocerr ,
 .Vt enum mandoclevel ,
 the function
 .Xr mandoc_escape 3 ,
 the functions described in
 .Xr mchars_alloc 3 ,
 and the
 .Fn mandoc_msg*
 functions.
 .It Qq Pa roff.h
 Common data types for all syntax trees and related functions;
 can be used everywhere.
 .Pp
 Provides
 .Vt enum mandoc_os ,
 .Vt enum mdoc_endbody ,
 .Vt enum roff_macroset ,
 .Vt enum roff_sec ,
 .Vt enum roff_tok ,
 .Vt enum roff_type ,
 .Vt struct roff_man ,
 .Vt struct roff_meta ,
 .Vt struct roff_node ,
 the constant array
 .Va roff_name
 and the function
 .Fn deroff .
 .Pp
 Uses pointers to the types
 .Vt struct ohash
 from
-.Pa mandoc_ohash.h ,
+.Qq Pa mandoc_ohash.h ,
 .Vt struct mdoc_arg
 and
 .Vt union mdoc_data
 from
-.Pa mdoc.h ,
+.Qq Pa mdoc.h ,
 .Vt struct tbl_span
 from
-.Pa tbl.h ,
+.Qq Pa tbl.h ,
 and
 .Vt struct eqn_box
 from
-.Pa eqn.h
+.Qq Pa eqn.h
 as opaque struct members.
 .It Qq Pa tbl.h
 Data structures for the
 .Xr tbl 7
 parse tree; can be used everywhere.
 .Pp
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides
 .Vt enum tbl_cellt ,
 .Vt enum tbl_datt ,
 .Vt enum tbl_spant ,
 .Vt struct tbl_opts ,
 .Vt struct tbl_cell ,
 .Vt struct tbl_row ,
 .Vt struct tbl_dat ,
 and
 .Vt struct tbl_span .
 .It Qq Pa eqn.h
 Data structures for the
 .Xr eqn 7
 parse tree; can be used everywhere.
 .Pp
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides
 .Vt enum eqn_boxt ,
 .Vt enum eqn_fontt ,
 .Vt enum eqn_post ,
 and
 .Vt struct eqn_box .
 .It Qq Pa mandoc_parse.h
 Top level parser interface, for use in the main program
 and in the main parser, but not in formatters.
 .Pp
 Requires
-.Pa mandoc.h
+.Qq Pa mandoc.h
 for
 .Vt enum mandocerr
 and
 .Vt enum mandoclevel
 and
-.Pa roff.h
+.Qq Pa roff.h
 for
 .Vt enum mandoc_os .
 .Pp
 Uses the opaque type
 .Vt struct mparse
 from
 .Pa read.c
 for function prototypes.
 Uses
 .Vt struct roff_meta
 from
-.Pa roff.h
+.Qq Pa roff.h
 as an opaque type for function prototypes.
 .It Qq Pa mandoc_xr.h
 Cross reference validation; intended for use in the main program
 and in parsers, but not in formatters.
 .Pp
 Provides
 .Vt struct mandoc_xr
 and the functions
 .Fn mandoc_xr_reset ,
 .Fn mandoc_xr_add ,
 .Fn mandoc_xr_get ,
 and
 .Fn mandoc_xr_free .
 .El
 .Pp
 The following two require
 .Qq Pa roff.h
 but no other mandoc headers.
 Afterwards, any other mandoc headers can be included as needed.
 .Bl -tag -width Ds
 .It Qq Pa mdoc.h
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides
 .Vt enum mdocargt ,
 .Vt enum mdoc_auth ,
 .Vt enum mdoc_disp ,
 .Vt enum mdoc_font ,
 .Vt enum mdoc_list ,
 .Vt struct mdoc_argv ,
 .Vt struct mdoc_arg ,
 .Vt struct mdoc_an ,
 .Vt struct mdoc_bd ,
 .Vt struct mdoc_bf ,
 .Vt struct mdoc_bl ,
 .Vt struct mdoc_rs ,
 .Vt union mdoc_data ,
 and the functions
 .Fn mdoc_*
 described in
 .Xr mandoc 3 .
 .Pp
 Uses the types
 .Vt struct roff_node
 from
-.Pa roff.h
+.Qq Pa roff.h
 and
 .Vt struct roff_man
 from
-.Pa roff_int.h
+.Qq Pa roff_int.h
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
 internals of different parsers.
 .It Qq Pa man.h
 Provides the functions
 .Fn man_*
 described in
 .Xr mandoc 3 .
 .Pp
 Uses the type
 .Vt struct roff_man
 from
-.Pa roff.h
+.Qq Pa roff.h
 as an opaque type for function prototypes.
 .Pp
 When this header is included, the same file should not include
 internals of different parsers.
 .El
 .Ss Parser internals
 Most of the following headers require inclusion of a parser interface header
 before they can be included.
 All parser interface headers should precede all parser internal headers.
 When any parser internal headers are included, the same file should
 not include any formatter headers.
 .Bl -tag -width Ds
 .It Qq Pa libmandoc.h
 Requires
 .In sys/types.h
 for
 .Vt size_t
 and
 .Qq Pa mandoc.h
 for
 .Vt enum mandocerr .
 .Pp
 Provides
 .Vt struct buf ,
 utility functions needed by multiple parsers,
 and the top-level functions to call the parsers.
 .Pp
 Uses the opaque type
 .Vt struct roff
 from
 .Pa roff.c
 for function prototypes.
 Uses the type
 .Vt struct roff_man
 from
-.Pa roff.h
+.Qq Pa roff.h
 as an opaque type for function prototypes.
 .It Qq Pa roff_int.h
 Parser internals shared by multiple parsers.
 Can be used in all parsers, but not in main programs or formatters.
 .Pp
 Requires
 .Qq Pa roff.h
 for
 .Vt enum roff_type
 and
 .Vt enum roff_tok .
 .Pp
 Provides
 .Vt enum roff_next ,
 .Vt struct roff_man ,
 functions named
 .Fn roff_*
 to handle roff nodes,
 .Fn roffhash_alloc ,
 .Fn roffhash_find ,
 .Fn roffhash_free ,
 and
 .Fn roff_validate ,
 and the two special functions
 .Fn man_breakscope
 and
 .Fn mdoc_argv_free
 because the latter two are needed by
-.Qq Pa roff.c .
+.Pa roff.c .
 .Pp
 Uses the types
 .Vt struct ohash
 from
-.Pa mandoc_ohash.h ,
+.Qq Pa mandoc_ohash.h ,
 .Vt struct roff_node
 and
 .Vt struct roff_meta
 from
-.Pa roff.h ,
+.Qq Pa roff.h ,
 .Vt struct roff
 from
 .Pa roff.c ,
 and
 .Vt struct mdoc_arg
 from
-.Pa mdoc.h
+.Qq Pa mdoc.h
 as opaque types for function prototypes.
 .It Qq Pa libmdoc.h
 Requires
 .Qq Pa roff.h
 for
 .Vt enum roff_tok
 and
 .Vt enum roff_sec .
 .Pp
 Provides
 .Vt enum margserr ,
 .Vt enum mdelim ,
 .Vt struct mdoc_macro ,
 and many functions internal to the
 .Xr mdoc 7
 parser.
 .Pp
 Uses the types
 .Vt struct roff_node
 from
-.Pa roff.h ,
+.Qq Pa roff.h ,
 .Vt struct roff_man
 from
-.Pa roff_int.h ,
+.Qq Pa roff_int.h ,
 and
 .Vt struct mdoc_arg
 from
-.Pa mdoc.h
+.Qq Pa mdoc.h
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
 interfaces of different parsers.
 .It Qq Pa libman.h
 Requires
 .Qq Pa roff.h
 for
 .Vt enum roff_tok .
 .Pp
 Provides
 .Vt struct man_macro
 and some functions internal to the
 .Xr man 7
 parser.
 .Pp
 Uses the types
 .Vt struct roff_node
 from
-.Pa roff.h
+.Qq Pa roff.h
 and
 .Vt struct roff_man
 from
-.Pa roff_int.h
+.Qq Pa roff_int.h
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
 interfaces of different parsers.
 .It Qq Pa eqn_parse.h
 External interface of the
 .Xr eqn 7
 parser, for use in the
 .Xr roff 7
 and
 .Xr eqn 7
 parsers only.
 .Pp
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides
 .Vt struct eqn_node
 and the functions
 .Fn eqn_alloc ,
 .Fn eqn_box_new ,
 .Fn eqn_box_free ,
 .Fn eqn_free ,
 .Fn eqn_parse ,
 .Fn eqn_read ,
 and
 .Fn eqn_reset .
 .Pp
 Uses the type
 .Vt struct eqn_box
 from
-.Pa mandoc.h
+.Qq Pa mandoc.h
 as an opaque type for function prototypes.
 Uses the types
 .Vt struct roff_node
 from
-.Pa roff.h
+.Qq Pa roff.h
 and
 .Vt struct eqn_def
 from
 .Pa eqn.c
 as opaque struct members.
 .Pp
 When this header is included, the same file should not include
 internals of different parsers.
 .It Qq Pa tbl_parse.h
 External interface of the
 .Xr tbl 7
 parser, for use in the
 .Xr roff 7
 and
 .Xr tbl 7
 parsers only.
 .Pp
 Provides the functions documented in
 .Xr tbl 3 .
 .Pp
 Uses the types
 .Vt struct tbl_span
 from
-.Pa tbl.h
+.Qq Pa tbl.h
 and
 .Vt struct tbl_node
 from
-.Pa tbl_int.h
+.Qq Pa tbl_int.h
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
 internals of different parsers.
 .It Qq Pa tbl_int.h
 Internal interfaces of the
 .Xr tbl 7
 parser, for use inside the
 .Xr tbl 7
 parser only.
 .Pp
 Requires
 .Qq Pa tbl.h
 for
 .Vt struct tbl_opts .
 .Pp
 Provides
 .Vt enum tbl_part ,
 .Vt struct tbl_node ,
 and the functions
 .Fn tbl_option ,
 .Fn tbl_layout ,
 .Fn tbl_data ,
 .Fn tbl_cdata ,
 and
 .Fn tbl_reset .
 .Pp
 When this header is included, the same file should not include
 interfaces of different parsers.
 .El
 .Ss Formatter interface
 These headers should be included after any parser interface headers.
 No parser internal headers should be included by the same file.
 .Bl -tag -width Ds
 .It Qq Pa out.h
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides
 .Vt enum roffscale ,
 .Vt struct roffcol ,
 .Vt struct roffsu ,
 .Vt struct rofftbl ,
 .Fn a2roffsu ,
 and
 .Fn tblcalc .
 .Pp
 Uses
 .Vt struct tbl_span
 from
-.Pa mandoc.h
+.Qq Pa mandoc.h
 as an opaque type for function prototypes.
 .Pp
 When this header is included, the same file should not include
-.Pa mansearch.h .
+.Qq Pa mansearch.h .
 .It Qq Pa term.h
 Requires
 .In sys/types.h
 for
 .Vt size_t
 and
 .Qq Pa out.h
 for
 .Vt struct roffsu
 and
 .Vt struct rofftbl .
 .Pp
 Provides
 .Vt enum termenc ,
 .Vt enum termfont ,
 .Vt enum termtype ,
 .Vt struct termp_tbl ,
 .Vt struct termp ,
 .Fn roff_term_pre ,
 and many terminal formatting functions.
 .Pp
 Uses the opaque type
 .Vt struct termp_ps
 from
 .Pa term_ps.c .
 Uses
 .Vt struct tbl_span
 and
 .Vt struct eqn_box
 from
-.Pa mandoc.h
+.Qq Pa mandoc.h
 and
 .Vt struct roff_meta
 and
 .Vt struct roff_node
 from
-.Pa roff.h
+.Qq Pa roff.h
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
-.Pa html.h
+.Qq Pa html.h
 or
-.Pa mansearch.h .
+.Qq Pa mansearch.h .
 .It Qq Pa html.h
 Requires
 .In sys/types.h
 for
 .Vt size_t ,
-.Pa mandoc.h
+.Qq Pa mandoc.h
 for
 .Vt enum mandoc_esc ,
+.Qq Pa roff.h
+for
+.Vt enum roff_tok ,
 and
 .Qq Pa out.h
 for
 .Vt struct roffsu
 and
 .Vt struct rofftbl .
 .Pp
 Provides
 .Vt enum htmltag ,
 .Vt enum htmlattr ,
 .Vt enum htmlfont ,
 .Vt struct tag ,
 .Vt struct tagq ,
 .Vt struct htmlpair ,
 .Vt struct html ,
 .Fn roff_html_pre ,
 and many HTML formatting functions.
 .Pp
 Uses
 .Vt struct tbl_span
 and
 .Vt struct eqn_box
 from
-.Pa mandoc.h
+.Qq Pa mandoc.h
 and
 .Vt struct roff_node
 from
-.Pa roff.h
+.Qq Pa roff.h
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
-.Pa term.h
+.Qq Pa term.h
 or
-.Pa mansearch.h .
+.Qq Pa mansearch.h .
 .It Qq Pa tag.h
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides an interface to generate
 .Xr ctags 1
 files for the
 .Ic :t
 functionality mentioned in
 .Xr man 1 .
 .It Qq Pa main.h
 Provides the top level steering functions for all formatters.
 .Pp
 Uses the type
 .Vt struct roff_meta
 from
-.Pa roff.h
+.Qq Pa roff.h
 as an opaque type for function prototypes.
 .It Qq Pa manconf.h
 Requires
 .In sys/types.h
 for
 .Vt size_t .
 .Pp
 Provides
 .Vt struct manconf ,
 .Vt struct manpaths ,
 .Vt struct manoutput ,
 and the functions
 .Fn manconf_parse ,
 .Fn manconf_output ,
 .Fn manconf_free ,
 and
 .Fn manpath_base .
 .It Qq Pa mansearch.h
 Requires
 .In sys/types.h
 for
 .Vt size_t
 and
 .In stdint.h
 for
 .Vt uint64_t .
 .Pp
 Provides
 .Vt enum argmode ,
 .Vt struct manpage ,
 .Vt struct mansearch ,
 and the functions
 .Fn mansearch
 and
 .Fn mansearch_free .
 .Pp
 Uses
 .Vt struct manpaths
 from
-.Pa manconf.h
+.Qq Pa manconf.h
 as an opaque type for function prototypes.
 .Pp
 When this header is included, the same file should not include
-.Pa out.h ,
-.Pa term.h ,
+.Qq Pa out.h ,
+.Qq Pa term.h ,
 or
-.Pa html.h .
+.Qq Pa html.h .
 .El
Index: head/contrib/mandoc/mandoc_msg.c
===================================================================
--- head/contrib/mandoc/mandoc_msg.c	(revision 350350)
+++ head/contrib/mandoc/mandoc_msg.c	(revision 350351)
@@ -1,329 +1,366 @@
-/*	$Id: mandoc_msg.c,v 1.6 2019/03/06 15:55:38 schwarze Exp $ */
+/*	$Id: mandoc_msg.c,v 1.8 2019/07/14 18:16:13 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2014,2015,2016,2017,2018 Ingo Schwarze 
+ * Copyright (c) 2014-2019 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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 "config.h"
 
 #include 
 #include 
 #include 
 
 #include "mandoc.h"
 
 static	const enum mandocerr lowest_type[MANDOCLEVEL_MAX] = {
 	MANDOCERR_OK,
 	MANDOCERR_OK,
 	MANDOCERR_WARNING,
 	MANDOCERR_ERROR,
 	MANDOCERR_UNSUPP,
-	MANDOCERR_MAX,
-	MANDOCERR_MAX
+	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",
 	"missing date, using today's date",
 	"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 */
-	NULL,
 	"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 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_MAX;
+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 && t != MANDOCERR_FILE)
+	if (t < min_type)
 		return;
 
-	level = MANDOCLEVEL_UNSUPP;
+	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]);
 }
Index: head/contrib/mandoc/mandocdb.c
===================================================================
--- head/contrib/mandoc/mandocdb.c	(revision 350350)
+++ head/contrib/mandoc/mandocdb.c	(revision 350351)
@@ -1,2362 +1,2367 @@
-/*	$Id: mandocdb.c,v 1.262 2018/12/30 00:49:55 schwarze Exp $ */
+/*	$Id: mandocdb.c,v 1.263 2019/05/03 18:17:12 schwarze Exp $ */
 /*
  * Copyright (c) 2011, 2012 Kristaps Dzonsons 
- * Copyright (c) 2011-2018 Ingo Schwarze 
+ * Copyright (c) 2011-2019 Ingo Schwarze 
  * Copyright (c) 2016 Ed Maste 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is 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 "config.h"
 
 #include 
 #include 
 #include 
 
 #include 
 #include 
 #if HAVE_ERR
 #include 
 #endif
 #include 
 #include 
 #if HAVE_FTS
 #include 
 #else
 #include "compat_fts.h"
 #endif
 #include 
 #if HAVE_SANDBOX_INIT
 #include 
 #endif
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #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 *mlink);
 static	void	 dbprune(struct dba *);
 static	void	 dbwrite(struct dba *);
 static	void	 filescan(const char *);
 #if HAVE_FTS_COMPARE_CONST
 static	int	 fts_compare(const FTSENT *const *, const FTSENT *const *);
 #else
 static	int	 fts_compare(const FTSENT **, const FTSENT **);
 #endif
 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	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 HAVE_PLEDGE
 	if (pledge("stdio rpath wpath cpath", NULL) == -1) {
 		warn("pledge");
 		return (int)MANDOCLEVEL_SYSERR;
 	}
 #endif
 
 #if HAVE_SANDBOX_INIT
 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) {
 		warnx("sandbox_init");
 		return (int)MANDOCLEVEL_SYSERR;
 	}
 #endif
 
 	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_DEFAULT != (_op)) { \
 		warnx("-%c: Conflicting option", (_ch)); \
 		goto usage; \
 	} while (/*CONSTCOND*/0)
 
 	mparse_options = MPARSE_VALIDATE;
 	path_arg = NULL;
 	op = OP_DEFAULT;
 
 	while (-1 != (ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")))
 		switch (ch) {
 		case 'a':
 			use_all = 1;
 			break;
 		case 'C':
 			CHECKOP(op, ch);
 			path_arg = optarg;
 			op = OP_CONFFILE;
 			break;
 		case 'D':
 			debug++;
 			break;
 		case 'd':
 			CHECKOP(op, ch);
 			path_arg = optarg;
 			op = OP_UPDATE;
 			break;
 		case 'n':
 			nodb = 1;
 			break;
 		case 'p':
 			warnings = 1;
 			break;
 		case 'Q':
 			mparse_options |= MPARSE_QUICK;
 			break;
 		case 'T':
 			if (strcmp(optarg, "utf8")) {
 				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 HAVE_PLEDGE
 	if (nodb) {
 		if (pledge("stdio rpath", NULL) == -1) {
 			warn("pledge");
 			return (int)MANDOCLEVEL_SYSERR;
 		}
 	}
 #endif
 
 	if (OP_CONFFILE == op && 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_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
 
 		/*
 		 * Most of these deal with a specific directory.
 		 * Jump into that directory first.
 		 */
 		if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
 			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 (0 == treescan())
 				goto out;
 			dba = dba_new(128);
 		}
 		if (OP_DELETE != op)
 			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 (0 == sz)
 				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))
 				continue;
 			if (0 == treescan())
 				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
 #if HAVE_FTS_COMPARE_CONST
 fts_compare(const FTSENT *const *a, const FTSENT *const *b)
 #else
 fts_compare(const FTSENT **a, const FTSENT **b)
 #endif
 {
 	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*[/]/.
* or * [./]cat
[/]/.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 (strstr(buf, basedir) != buf #ifdef HOMEBREWDIR && strstr(buf, HOMEBREWDIR) != buf #endif ) { 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*[/]/.
* or * [./]cat
[/]/.0 * * See treescan() for the fts(3) version of this. */ static void filescan(const char *file) { char buf[PATH_MAX]; struct stat st; struct mlink *mlink; char *p, *start; assert(use_all); if (0 == strncmp(file, "./", 2)) file += 2; /* * We have to do lstat(2) before realpath(3) loses * the information whether this is a symbolic link. * We need to know that because for symbolic links, * we want to use the orginal file name, while for * regular files, we want to use the real path. */ if (-1 == lstat(file, &st)) { exitcode = (int)MANDOCLEVEL_BADARG; say(file, "&lstat"); return; } else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) { exitcode = (int)MANDOCLEVEL_BADARG; say(file, "Not a regular file"); return; } /* * We have to resolve the file name to the real path * in any case for the base directory check. */ if (NULL == realpath(file, buf)) { exitcode = (int)MANDOCLEVEL_BADARG; say(file, "&realpath"); return; } if (OP_TEST == op) start = buf; else if (strstr(buf, basedir) == buf) start = buf + strlen(basedir); #ifdef HOMEBREWDIR else if (strstr(buf, HOMEBREWDIR) == buf) start = buf; #endif else { exitcode = (int)MANDOCLEVEL_BADARG; say("", "%s: outside base directory", buf); return; } /* * Now we are sure the file is inside our tree. * If it is a symbolic link, ignore the real path * and use the original name. * This implies passing stuff like "cat1/../man1/foo.1" * on the command line won't work. So don't do that. * Note the stat(2) can still fail if the link target * doesn't exist. */ if (S_IFLNK & st.st_mode) { if (-1 == stat(buf, &st)) { exitcode = (int)MANDOCLEVEL_BADARG; say(file, "&stat"); return; } if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) { say(file, "Filename too long"); return; } start = buf; if (OP_TEST != op && strstr(buf, basedir) == buf) start += strlen(basedir); } mlink = mandoc_calloc(1, sizeof(struct mlink)); mlink->dform = FORM_NONE; if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= sizeof(mlink->file)) { say(start, "Filename too long"); free(mlink); return; } /* * In test mode or when the original name is absolute * but outside our tree, guess the base directory. */ if (op == OP_TEST || (start == buf && *start == '/')) { if (strncmp(buf, "man/", 4) == 0) start = buf + 4; else if ((start = strstr(buf, "/man/")) != NULL) start += 5; else start = buf; } /* * First try to guess our directory structure. * If we find a separator, try to look for man* or cat*. * If we find one of these and what's underneath is a directory, * assume it's an architecture. */ if (NULL != (p = strchr(start, '/'))) { *p++ = '\0'; if (0 == strncmp(start, "man", 3)) { mlink->dform = FORM_SRC; mlink->dsec = start + 3; } else if (0 == strncmp(start, "cat", 3)) { mlink->dform = FORM_CAT; mlink->dsec = start + 3; } start = p; if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) { *p++ = '\0'; mlink->arch = start; start = p; } } /* * Now check the file suffix. * Suffix of `.0' indicates a catpage, `.1-9' is a manpage. */ p = strrchr(start, '\0'); while (p-- > start && '/' != *p && '.' != *p) /* Loop. */ ; if ('.' == *p) { *p++ = '\0'; mlink->fsec = p; } /* * Now try to parse the name. * Use the filename portion of the path. */ mlink->name = start; if (NULL != (p = strrchr(start, '/'))) { mlink->name = p + 1; *p = '\0'; } mlink_add(mlink, &st); } static void mlink_add(struct mlink *mlink, const struct stat *st) { struct inodev inodev; struct mpage *mpage; unsigned int slot; assert(NULL != mlink->file); mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : ""); mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : ""); mlink->name = mandoc_strdup(mlink->name ? mlink->name : ""); mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : ""); if ('0' == *mlink->fsec) { free(mlink->fsec); mlink->fsec = mandoc_strdup(mlink->dsec); mlink->fform = FORM_CAT; } else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec) mlink->fform = FORM_SRC; else mlink->fform = FORM_NONE; slot = ohash_qlookup(&mlinks, mlink->file); assert(NULL == ohash_find(&mlinks, slot)); ohash_insert(&mlinks, slot, mlink); 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; } - goto nextpage; - } else if (meta != NULL && meta->macroset == MACROSET_MDOC) { + 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) { - mpage->form = FORM_CAT; + if (meta == NULL || meta->sodest != NULL) { mpage->sec = mandoc_strdup(mlink->dsec); mpage->arch = mandoc_strdup(mlink->arch); mpage->title = mandoc_strdup(mlink->name); - parse_cat(mpage, fd); + 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 */ char *cp; /* * Remember the original working directory, if possible. * This will be needed if the second or a later directory * on the command line is given as a relative path. * Do not error out if the current directory is not * searchable: Maybe it won't be needed after all. */ if (0 == getcwd_status) { if (NULL == getcwd(startdir, sizeof(startdir))) { getcwd_status = 2; (void)strlcpy(startdir, strerror(errno), sizeof(startdir)); } else getcwd_status = 1; } /* * We are leaving the old base directory. * Do not use it any longer, not even for messages. */ *basedir = '\0'; /* * If and only if the directory was changed earlier and * the next directory to process is given as a relative path, * first go back, or bail out if that is impossible. */ if (chdir_status && '/' != *targetdir) { if (2 == getcwd_status) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "getcwd: %s", startdir); return 0; } if (-1 == chdir(startdir)) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&chdir %s", startdir); return 0; } } /* * Always resolve basedir to the canonicalized absolute * pathname and append a trailing slash, such that * we can reliably check whether files are inside. */ if (NULL == realpath(targetdir, basedir)) { if (report_baddir || errno != ENOENT) { exitcode = (int)MANDOCLEVEL_BADARG; say("", "&%s: realpath", targetdir); } return 0; } else if (-1 == chdir(basedir)) { if (report_baddir || errno != ENOENT) { exitcode = (int)MANDOCLEVEL_BADARG; say("", "&chdir"); } return 0; } chdir_status = 1; cp = strchr(basedir, '\0'); if ('/' != cp[-1]) { if (cp - basedir >= PATH_MAX - 1) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "Filename too long"); return 0; } *cp++ = '/'; *cp = '\0'; } return 1; } static void say(const char *file, const char *format, ...) { va_list ap; int use_errno; if ('\0' != *basedir) fprintf(stderr, "%s", basedir); if ('\0' != *basedir && '\0' != *file) fputc('/', stderr); if ('\0' != *file) fprintf(stderr, "%s", file); use_errno = 1; if (NULL != format) { switch (*format) { case '&': format++; break; case '\0': format = NULL; break; default: use_errno = 0; break; } } if (NULL != format) { if ('\0' != *basedir || '\0' != *file) fputs(": ", stderr); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } if (use_errno) { if ('\0' != *basedir || '\0' != *file || NULL != format) fputs(": ", stderr); perror(NULL); } else fputc('\n', stderr); } Index: head/contrib/mandoc/manpath.c =================================================================== --- head/contrib/mandoc/manpath.c (revision 350350) +++ head/contrib/mandoc/manpath.c (revision 350351) @@ -1,343 +1,347 @@ -/* $Id: manpath.c,v 1.37 2018/11/22 11:30:23 schwarze Exp $ */ +/* $Id: manpath.c,v 1.40 2019/07/10 19:39:01 schwarze Exp $ */ /* - * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze + * Copyright (c) 2011,2014,2015,2017-2019 Ingo Schwarze * Copyright (c) 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include -#if HAVE_ERR -#include -#endif +#include #include #include #include #include #include "mandoc_aux.h" +#include "mandoc.h" #include "manconf.h" static void manconf_file(struct manconf *, const char *); -static void manpath_add(struct manpaths *, const char *, int); -static void manpath_parseline(struct manpaths *, char *, int); +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, 1); + manpath_parseline(&conf->manpath, auxp, 'm'); /* If -M is given, it overrides everything else. */ if (NULL != defp) { - manpath_parseline(&conf->manpath, defp, 1); + 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); + manpath_parseline(&conf->manpath, defp, '\0'); return; } /* Append man.conf(5) to MANPATH. */ if (':' == defp[strlen(defp) - 1]) { - manpath_parseline(&conf->manpath, defp, 0); + 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); + manpath_parseline(&conf->manpath, defp, '\0'); manconf_file(conf, file); - manpath_parseline(&conf->manpath, insert + 1, 0); + manpath_parseline(&conf->manpath, insert + 1, '\0'); return; } /* MANPATH overrides man.conf(5) completely. */ - manpath_parseline(&conf->manpath, defp, 0); + manpath_parseline(&conf->manpath, defp, '\0'); } void manpath_base(struct manpaths *dirs) { char path_base[] = MANPATH_BASE; - manpath_parseline(dirs, path_base, 0); + 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, int complain) +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, complain); + 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, int complain) +manpath_add(struct manpaths *dirs, const char *dir, char option) { char buf[PATH_MAX]; struct stat sb; char *cp; size_t i; - if (NULL == (cp = realpath(dir, buf))) { - if (complain) - warn("manpath: %s", dir); - return; - } + if ((cp = realpath(dir, buf)) == NULL) + goto fail; for (i = 0; i < dirs->sz; i++) - if (0 == strcmp(dirs->paths[i], dir)) + if (strcmp(dirs->paths[i], dir) == 0) return; - if (stat(cp, &sb) == -1) { - if (complain) - warn("manpath: %s", dir); - return; - } + if (stat(cp, &sb) == -1) + goto fail; dirs->paths = mandoc_reallocarray(dirs->paths, - dirs->sz + 1, sizeof(char *)); - + 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", "_whatdb" }; 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 2: /* _whatdb */ while (ep > cp && ep[-1] != '/') ep--; if (ep == cp) continue; *ep = '\0'; /* FALLTHROUGH */ case 0: /* manpath */ - manpath_add(&conf->manpath, cp, 0); + 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); + 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 < sizeof(toks)/sizeof(toks[0]); tok++) { + for (tok = 0; tok < ntoks; tok++) { len = strlen(toks[tok]); - if ( ! strncmp(cp, toks[tok], len) && + 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') { - warnx("-O %s=?: Missing argument value", toks[tok]); + mandoc_msg(MANDOCERR_BADVAL_MISS, 0, 0, "-O %s=?", toks[tok]); return -1; } - if (tok > 6 && *cp != '\0') { - warnx("-O %s: Does not take a value: %s", toks[tok], cp); + 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; - warnx("-O indent=%s is %s", cp, errstr); + 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; - warnx("-O width=%s is %s", cp, errstr); + 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: - if (fromfile) - warnx("-O %s: Bad argument", cp); + mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-O %s", cp); return -1; } - if (fromfile == 0) - warnx("-O %s=%s: Option already set to %s", - toks[tok], cp, oldval); - free(oldval); - 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; + } } Index: head/contrib/mandoc/mansearch.c =================================================================== --- head/contrib/mandoc/mansearch.c (revision 350350) +++ head/contrib/mandoc/mansearch.c (revision 350351) @@ -1,847 +1,851 @@ -/* $Id: mansearch.c,v 1.80 2018/12/13 11:55:46 schwarze Exp $ */ +/* $Id: mansearch.c,v 1.82 2019/07/01 22:56:24 schwarze Exp $ */ /* * Copyright (c) 2012 Kristaps Dzonsons * Copyright (c) 2013-2018 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #if HAVE_ERR #include #endif #include #include #include #include #include #include #include #include #include #include #include #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", + "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) + 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 = mp1->sec - mp2->sec)) + 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; #if HAVE_REWB_BSD mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]); #elif HAVE_REWB_SYSV mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]); #else mandoc_asprintf(&val, "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]); #endif 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); } Index: head/contrib/mandoc/mansearch.h =================================================================== --- head/contrib/mandoc/mansearch.h (revision 350350) +++ head/contrib/mandoc/mansearch.h (revision 350351) @@ -1,117 +1,118 @@ -/* $Id: mansearch.h,v 1.29 2018/11/22 12:01:46 schwarze Exp $ */ +/* $Id: mansearch.h,v 1.30 2019/04/30 18:51:57 schwarze Exp $ */ /* * Copyright (c) 2012 Kristaps Dzonsons * Copyright (c) 2013, 2014, 2016, 2017 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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); Index: head/contrib/mandoc/mdoc.7 =================================================================== --- head/contrib/mandoc/mdoc.7 (revision 350350) +++ head/contrib/mandoc/mdoc.7 (revision 350351) @@ -1,3115 +1,3131 @@ -.\" $Id: mdoc.7,v 1.276 2019/02/07 15:45:53 schwarze Exp $ +.\" $Id: mdoc.7,v 1.279 2019/07/15 19:20:30 schwarze Exp $ .\" .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons .\" Copyright (c) 2010, 2011, 2013-2018 Ingo Schwarze .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 7 2019 $ +.Dd $Mdocdate: July 15 2019 $ .Dt MDOC 7 .Os .Sh NAME .Nm mdoc .Nd semantic markup language for formatting manual pages .Sh DESCRIPTION The .Nm mdoc language supports authoring of manual pages for the .Xr man 1 utility by allowing semantic annotations of words, phrases, page sections and complete manual pages. Such annotations are used by formatting tools to achieve a uniform presentation across all manuals written in .Nm , and to support hyperlinking if supported by the output medium. .Pp This reference document describes the structure of manual pages and the syntax and usage of the .Nm language. The reference implementation of a parsing and formatting tool is .Xr mandoc 1 ; the .Sx COMPATIBILITY section describes compatibility with other implementations. .Pp In an .Nm document, lines beginning with the control character .Sq \&. are called .Dq macro lines . The first word is the macro name. It consists of two or three letters. Most macro names begin with a capital letter. For a list of available macros, see .Sx MACRO OVERVIEW . The words following the macro name are arguments to the macro, optionally including the names of other, callable macros; see .Sx MACRO SYNTAX for details. .Pp Lines not beginning with the control character are called .Dq text lines . They provide free-form text to be printed; the formatting of the text depends on the respective processing context: .Bd -literal -offset indent \&.Sh Macro lines change control state. Text lines are interpreted within the current state. .Ed .Pp Many aspects of the basic syntax of the .Nm language are based on the .Xr roff 7 language; see the .Em LANGUAGE SYNTAX and .Em MACRO SYNTAX sections in the .Xr roff 7 manual for details, in particular regarding comments, escape sequences, whitespace, and quoting. However, using .Xr roff 7 requests in .Nm documents is discouraged; .Xr mandoc 1 supports some of them merely for backward compatibility. .Sh MANUAL STRUCTURE A well-formed .Nm document consists of a document prologue followed by one or more sections. .Pp The prologue, which consists of the .Ic \&Dd , .Ic \&Dt , and .Ic \&Os macros in that order, is required for every document. .Pp The first section (sections are denoted by .Ic \&Sh ) must be the NAME section, consisting of at least one .Ic \&Nm followed by .Ic \&Nd . .Pp Following that, convention dictates specifying at least the .Em SYNOPSIS and .Em DESCRIPTION sections, although this varies between manual sections. .Pp The following is a well-formed skeleton .Nm file for a utility .Qq progname : .Bd -literal -offset indent \&.Dd $\&Mdocdate$ \&.Dt PROGNAME section \&.Os \&.Sh NAME \&.Nm progname \&.Nd one line about what it does \&.\e\(dq .Sh LIBRARY \&.\e\(dq For sections 2, 3, and 9 only. \&.\e\(dq Not used in OpenBSD. \&.Sh SYNOPSIS \&.Nm progname \&.Op Fl options \&.Ar \&.Sh DESCRIPTION The \&.Nm utility processes files ... \&.\e\(dq .Sh CONTEXT \&.\e\(dq For section 9 functions only. \&.\e\(dq .Sh IMPLEMENTATION NOTES \&.\e\(dq Not used in OpenBSD. \&.\e\(dq .Sh RETURN VALUES \&.\e\(dq For sections 2, 3, and 9 function return values only. \&.\e\(dq .Sh ENVIRONMENT \&.\e\(dq For sections 1, 6, 7, and 8 only. \&.\e\(dq .Sh FILES \&.\e\(dq .Sh EXIT STATUS \&.\e\(dq For sections 1, 6, and 8 only. \&.\e\(dq .Sh EXAMPLES \&.\e\(dq .Sh DIAGNOSTICS \&.\e\(dq For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only. \&.\e\(dq .Sh ERRORS \&.\e\(dq For sections 2, 3, 4, and 9 errno settings only. \&.\e\(dq .Sh SEE ALSO \&.\e\(dq .Xr foobar 1 \&.\e\(dq .Sh STANDARDS \&.\e\(dq .Sh HISTORY \&.\e\(dq .Sh AUTHORS \&.\e\(dq .Sh CAVEATS \&.\e\(dq .Sh BUGS \&.\e\(dq .Sh SECURITY CONSIDERATIONS \&.\e\(dq Not used in OpenBSD. .Ed .Pp The sections in an .Nm document are conventionally ordered as they appear above. Sections should be composed as follows: .Bl -ohang -offset Ds .It Em NAME The name(s) and a one line description of the documented material. The syntax for this as follows: .Bd -literal -offset indent \&.Nm name0 , \&.Nm name1 , \&.Nm name2 \&.Nd a one line description .Ed .Pp Multiple .Sq \&Nm names should be separated by commas. .Pp The .Ic \&Nm macro(s) must precede the .Ic \&Nd macro. .Pp See .Ic \&Nm and .Ic \&Nd . .It Em LIBRARY The name of the library containing the documented material, which is assumed to be a function in a section 2, 3, or 9 manual. The syntax for this is as follows: .Bd -literal -offset indent \&.Lb libarm .Ed .Pp See .Ic \&Lb . .It Em SYNOPSIS Documents the utility invocation syntax, function call syntax, or device configuration. .Pp For the first, utilities (sections 1, 6, and 8), this is generally structured as follows: .Bd -literal -offset indent \&.Nm bar \&.Op Fl v \&.Op Fl o Ar file \&.Op Ar \&.Nm foo \&.Op Fl v \&.Op Fl o Ar file \&.Op Ar .Ed .Pp Commands should be ordered alphabetically. .Pp For the second, function calls (sections 2, 3, 9): .Bd -literal -offset indent \&.In header.h \&.Vt extern const char *global; \&.Ft "char *" \&.Fn foo "const char *src" \&.Ft "char *" \&.Fn bar "const char *src" .Ed .Pp Ordering of .Ic \&In , .Ic \&Vt , .Ic \&Fn , and .Ic \&Fo macros should follow C header-file conventions. .Pp And for the third, configurations (section 4): .Bd -literal -offset indent \&.Cd \(dqit* at isa? port 0x2e\(dq \&.Cd \(dqit* at isa? port 0x4e\(dq .Ed .Pp Manuals not in these sections generally don't need a .Em SYNOPSIS . .Pp Some macros are displayed differently in the .Em SYNOPSIS section, particularly .Ic \&Nm , .Ic \&Cd , .Ic \&Fd , .Ic \&Fn , .Ic \&Fo , .Ic \&In , .Ic \&Vt , and .Ic \&Ft . All of these macros are output on their own line. If two such dissimilar macros are pairwise invoked (except for .Ic \&Ft before .Ic \&Fo or .Ic \&Fn ) , they are separated by a vertical space, unless in the case of .Ic \&Fo , .Ic \&Fn , and .Ic \&Ft , which are always separated by vertical space. .Pp When text and macros following an .Ic \&Nm macro starting an input line span multiple output lines, all output lines but the first will be indented to align with the text immediately following the .Ic \&Nm macro, up to the next .Ic \&Nm , .Ic \&Sh , or .Ic \&Ss macro or the end of an enclosing block, whichever comes first. .It Em DESCRIPTION This begins with an expansion of the brief, one line description in .Em NAME : .Bd -literal -offset indent The \&.Nm utility does this, that, and the other. .Ed .Pp It usually follows with a breakdown of the options (if documenting a command), such as: .Bd -literal -offset indent The arguments are as follows: \&.Bl \-tag \-width Ds \&.It Fl v Print verbose information. \&.El .Ed .Pp List the options in alphabetical order, uppercase before lowercase for each letter and with no regard to whether an option takes an argument. Put digits in ascending order before all letter options. .Pp Manuals not documenting a command won't include the above fragment. .Pp Since the .Em DESCRIPTION section usually contains most of the text of a manual, longer manuals often use the .Ic \&Ss macro to form subsections. In very long manuals, the .Em DESCRIPTION may be split into multiple sections, each started by an .Ic \&Sh macro followed by a non-standard section name, and each having several subsections, like in the present .Nm manual. .It Em CONTEXT This section lists the contexts in which functions can be called in section 9. The contexts are autoconf, process, or interrupt. .It Em IMPLEMENTATION NOTES Implementation-specific notes should be kept here. This is useful when implementing standard functions that may have side effects or notable algorithmic implications. .It Em RETURN VALUES This section documents the return values of functions in sections 2, 3, and 9. .Pp See .Ic \&Rv . .It Em ENVIRONMENT Lists the environment variables used by the utility, and explains the syntax and semantics of their values. The .Xr environ 7 manual provides examples of typical content and formatting. .Pp See .Ic \&Ev . .It Em FILES Documents files used. It's helpful to document both the file name and a short description of how the file is used (created, modified, etc.). .Pp See .Ic \&Pa . .It Em EXIT STATUS This section documents the command exit status for section 1, 6, and 8 utilities. Historically, this information was described in .Em DIAGNOSTICS , a practise that is now discouraged. .Pp See .Ic \&Ex . .It Em EXAMPLES Example usages. This often contains snippets of well-formed, well-tested invocations. Make sure that examples work properly! .It Em DIAGNOSTICS Documents error messages. In section 4 and 9 manuals, these are usually messages printed by the kernel to the console and to the kernel log. In section 1, 6, 7, and 8, these are usually messages printed by userland programs to the standard error output. .Pp Historically, this section was used in place of .Em EXIT STATUS for manuals in sections 1, 6, and 8; however, this practise is discouraged. .Pp See .Ic \&Bl .Fl diag . .It Em ERRORS Documents .Xr errno 2 settings in sections 2, 3, 4, and 9. .Pp See .Ic \&Er . .It Em SEE ALSO References other manuals with related topics. This section should exist for most manuals. Cross-references should conventionally be ordered first by section, then alphabetically (ignoring case). .Pp References to other documentation concerning the topic of the manual page, for example authoritative books or journal articles, may also be provided in this section. .Pp See .Ic \&Rs and .Ic \&Xr . .It Em STANDARDS References any standards implemented or used. If not adhering to any standards, the .Em HISTORY section should be used instead. .Pp See .Ic \&St . .It Em HISTORY A brief history of the subject, including where it was first implemented, and when it was ported to or reimplemented for the operating system at hand. .It Em AUTHORS Credits to the person or persons who wrote the code and/or documentation. Authors should generally be noted by both name and email address. .Pp See .Ic \&An . .It Em CAVEATS Common misuses and misunderstandings should be explained in this section. .It Em BUGS Known bugs, limitations, and work-arounds should be described in this section. .It Em SECURITY CONSIDERATIONS Documents any security precautions that operators should consider. .El .Sh MACRO OVERVIEW This overview is sorted such that macros of similar purpose are listed together, to help find the best macro for any given purpose. Deprecated macros are not included in the overview, but can be found below in the alphabetical .Sx MACRO REFERENCE . .Ss Document preamble and NAME section macros .Bl -column "Brq, Bro, Brc" description .It Ic \&Dd Ta document date: Cm $\&Mdocdate$ | Ar month day , year .It Ic \&Dt Ta document title: Ar TITLE section Op Ar arch .It Ic \&Os Ta operating system version: Op Ar system Op Ar version .It Ic \&Nm Ta document name (one argument) .It Ic \&Nd Ta document description (one line) .El .Ss Sections and cross references .Bl -column "Brq, Bro, Brc" description .It Ic \&Sh Ta section header (one line) .It Ic \&Ss Ta subsection header (one line) .It Ic \&Sx Ta internal cross reference to a section or subsection .It Ic \&Xr Ta cross reference to another manual page: Ar name section .It Ic \&Pp Ta start a text paragraph (no arguments) .El .Ss Displays and lists .Bl -column "Brq, Bro, Brc" description .It Ic \&Bd , \&Ed Ta display block: .Fl Ar type .Op Fl offset Ar width .Op Fl compact .It Ic \&D1 Ta indented display (one line) .It Ic \&Dl Ta indented literal display (one line) .It Ic \&Ql Ta in-line literal display: Ql text .It Ic \&Bl , \&El Ta list block: .Fl Ar type .Op Fl width Ar val .Op Fl offset Ar val .Op Fl compact .It Ic \&It Ta list item (syntax depends on Fl Ar type ) .It Ic \&Ta Ta table cell separator in Ic \&Bl Fl column No lists .It Ic \&Rs , \&%* , \&Re Ta bibliographic block (references) .El .Ss Spacing control .Bl -column "Brq, Bro, Brc" description .It Ic \&Pf Ta prefix, no following horizontal space (one argument) .It Ic \&Ns Ta roman font, no preceding horizontal space (no arguments) .It Ic \&Ap Ta apostrophe without surrounding whitespace (no arguments) .It Ic \&Sm Ta switch horizontal spacing mode: Op Cm on | off .It Ic \&Bk , \&Ek Ta keep block: Fl words .El .Ss Semantic markup for command line utilities .Bl -column "Brq, Bro, Brc" description .It Ic \&Nm Ta start a SYNOPSIS block with the name of a utility .It Ic \&Fl Ta command line options (flags) (>=0 arguments) .It Ic \&Cm Ta command modifier (>0 arguments) .It Ic \&Ar Ta command arguments (>=0 arguments) .It Ic \&Op , \&Oo , \&Oc Ta optional syntax elements (enclosure) .It Ic \&Ic Ta internal or interactive command (>0 arguments) .It Ic \&Ev Ta environmental variable (>0 arguments) .It Ic \&Pa Ta file system path (>=0 arguments) .El .Ss Semantic markup for function libraries .Bl -column "Brq, Bro, Brc" description .It Ic \&Lb Ta function library (one argument) .It Ic \&In Ta include file (one argument) .It Ic \&Fd Ta other preprocessor directive (>0 arguments) .It Ic \&Ft Ta function type (>0 arguments) .It Ic \&Fo , \&Fc Ta function block: Ar funcname .It Ic \&Fn Ta function name: Ar funcname Op Ar argument ... .It Ic \&Fa Ta function argument (>0 arguments) .It Ic \&Vt Ta variable type (>0 arguments) .It Ic \&Va Ta variable name (>0 arguments) .It Ic \&Dv Ta defined variable or preprocessor constant (>0 arguments) .It Ic \&Er Ta error constant (>0 arguments) .It Ic \&Ev Ta environmental variable (>0 arguments) .El .Ss Various semantic markup .Bl -column "Brq, Bro, Brc" description .It Ic \&An Ta author name (>0 arguments) .It Ic \&Lk Ta hyperlink: Ar uri Op Ar display_name .It Ic \&Mt Ta Do mailto Dc hyperlink: Ar localpart Ns @ Ns Ar domain .It Ic \&Cd Ta kernel configuration declaration (>0 arguments) .It Ic \&Ad Ta memory address (>0 arguments) .It Ic \&Ms Ta mathematical symbol (>0 arguments) .El .Ss Physical markup .Bl -column "Brq, Bro, Brc" description .It Ic \&Em Ta italic font or underline (emphasis) (>0 arguments) .It Ic \&Sy Ta boldface font (symbolic) (>0 arguments) .It Ic \&No Ta return to roman font (normal) (>0 arguments) .It Ic \&Bf , \&Ef Ta font block: Fl Ar type | Cm \&Em | \&Li | \&Sy .El .Ss Physical enclosures .Bl -column "Brq, Bro, Brc" description .It Ic \&Dq , \&Do , \&Dc Ta enclose in typographic double quotes: Dq text .It Ic \&Qq , \&Qo , \&Qc Ta enclose in typewriter double quotes: Qq text .It Ic \&Sq , \&So , \&Sc Ta enclose in single quotes: Sq text .It Ic \&Pq , \&Po , \&Pc Ta enclose in parentheses: Pq text .It Ic \&Bq , \&Bo , \&Bc Ta enclose in square brackets: Bq text .It Ic \&Brq , \&Bro , \&Brc Ta enclose in curly braces: Brq text .It Ic \&Aq , \&Ao , \&Ac Ta enclose in angle brackets: Aq text .It Ic \&Eo , \&Ec Ta generic enclosure .El .Ss Text production .Bl -column "Brq, Bro, Brc" description .It Ic \&Ex Fl std Ta standard command exit values: Op Ar utility ... .It Ic \&Rv Fl std Ta standard function return values: Op Ar function ... .It Ic \&St Ta reference to a standards document (one argument) .It Ic \&At Ta At .It Ic \&Bx Ta Bx .It Ic \&Bsx Ta Bsx .It Ic \&Nx Ta Nx .It Ic \&Fx Ta Fx .It Ic \&Ox Ta Ox .It Ic \&Dx Ta Dx .El .Sh MACRO REFERENCE This section is a canonical reference of all macros, arranged alphabetically. For the scoping of individual macros, see .Sx MACRO SYNTAX . .Bl -tag -width 3n .It Ic \&%A Ar first_name ... last_name Author name of an .Ic \&Rs block. Multiple authors should each be accorded their own .Ic \%%A line. Author names should be ordered with full or abbreviated forename(s) first, then full surname. .It Ic \&%B Ar title Book title of an .Ic \&Rs block. This macro may also be used in a non-bibliographic context when referring to book titles. .It Ic \&%C Ar location Publication city or location of an .Ic \&Rs block. .It Ic \&%D Oo Ar month day , Oc Ar year Publication date of an .Ic \&Rs block. Provide the full English name of the .Ar month and all four digits of the .Ar year . .It Ic \&%I Ar name Publisher or issuer name of an .Ic \&Rs block. .It Ic \&%J Ar name Journal name of an .Ic \&Rs block. .It Ic \&%N Ar number Issue number (usually for journals) of an .Ic \&Rs block. .It Ic \&%O Ar line Optional information of an .Ic \&Rs block. .It Ic \&%P Ar number Book or journal page number of an .Ic \&Rs block. +Conventionally, the argument starts with +.Ql p.\& +for a single page or +.Ql pp.\& +for a range of pages, for example: +.Pp +.Dl .%P pp. 42\e(en47 .It Ic \&%Q Ar name Institutional author (school, government, etc.) of an .Ic \&Rs block. Multiple institutional authors should each be accorded their own .Ic \&%Q line. .It Ic \&%R Ar name Technical report name of an .Ic \&Rs block. .It Ic \&%T Ar title Article title of an .Ic \&Rs block. This macro may also be used in a non-bibliographical context when referring to article titles. .It Ic \&%U Ar protocol Ns :// Ns Ar path URI of reference document. .It Ic \&%V Ar number Volume number of an .Ic \&Rs block. .It Ic \&Ac Close an .Ic \&Ao block. Does not have any tail arguments. .It Ic \&Ad Ar address Memory address. Do not use this for postal addresses. .Pp Examples: .Dl \&.Ad [0,$] .Dl \&.Ad 0x00000000 .It Ic \&An Fl split | nosplit | Ar first_name ... last_name Author name. Can be used both for the authors of the program, function, or driver documented in the manual, or for the authors of the manual itself. Requires either the name of an author or one of the following arguments: .Pp .Bl -tag -width "-nosplitX" -offset indent -compact .It Fl split Start a new output line before each subsequent invocation of .Ic \&An . .It Fl nosplit The opposite of .Fl split . .El .Pp The default is .Fl nosplit . The effect of selecting either of the .Fl split modes ends at the beginning of the .Em AUTHORS section. In the .Em AUTHORS section, the default is .Fl nosplit for the first author listing and .Fl split for all other author listings. .Pp Examples: .Dl \&.An -nosplit .Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv .It Ic \&Ao Ar block Begin a block enclosed by angle brackets. Does not have any head arguments. This macro is almost never useful. See .Ic \&Aq for more details. .It Ic \&Ap Inserts an apostrophe without any surrounding whitespace. This is generally used as a grammatical device when referring to the verb form of a function. .Pp Examples: .Dl \&.Fn execve \&Ap d .It Ic \&Aq Ar line Enclose the rest of the input line in angle brackets. The only important use case is for email addresses. See .Ic \&Mt for an example. .Pp Occasionally, it is used for names of characters and keys, for example: .Bd -literal -offset indent Press the \&.Aq escape key to ... .Ed .Pp For URIs, use .Ic \&Lk instead, and .Ic \&In for .Dq #include directives. Never wrap .Ic \&Ar in .Ic \&Aq . .Pp Since .Ic \&Aq usually renders with non-ASCII characters in non-ASCII output modes, do not use it where the ASCII characters .Sq < and .Sq > are required as syntax elements. Instead, use these characters directly in such cases, combining them with the macros .Ic \&Pf , .Ic \&Ns , or .Ic \&Eo as needed. .Pp See also .Ic \&Ao . .It Ic \&Ar Op Ar placeholder ... Command arguments. If an argument is not provided, the string .Dq file ...\& is used as a default. .Pp Examples: .Dl ".Fl o Ar file" .Dl ".Ar" .Dl ".Ar arg1 , arg2 ." .Pp The arguments to the .Ic \&Ar macro are names and placeholders for command arguments; for fixed strings to be passed verbatim as arguments, use .Ic \&Fl or .Ic \&Cm . .It Ic \&At Op Ar version Formats an .At version. Accepts one optional argument: .Pp .Bl -tag -width "v[1-7] | 32vX" -offset indent -compact .It Cm v[1-7] | 32v A version of .At . .It Cm III .At III . .It Cm V | V.[1-4] A version of .At V . .El .Pp Note that these arguments do not begin with a hyphen. .Pp Examples: .Dl \&.At .Dl \&.At III .Dl \&.At V.1 .Pp See also .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Bc Close a .Ic \&Bo block. Does not have any tail arguments. .It Ic \&Bd Fl Ns Ar type Oo Fl offset Ar width Oc Op Fl compact Begin a display block. Display blocks are used to select a different indentation and justification than the one used by the surrounding text. They may contain both macro lines and text lines. By default, a display block is preceded by a vertical space. .Pp The .Ar type must be one of the following: .Bl -tag -width 13n -offset indent .It Fl centered Produce one output line from each input line, and center-justify each line. Using this display type is not recommended; many .Nm implementations render it poorly. .It Fl filled Change the positions of line breaks to fill each line, and left- and right-justify the resulting block. .It Fl literal Produce one output line from each input line, and do not justify the block at all. Preserve white space as it appears in the input. Always use a constant-width font. Use this for displaying source code. .It Fl ragged Change the positions of line breaks to fill each line, and left-justify the resulting block. .It Fl unfilled The same as .Fl literal , but using the same font as for normal text, which is a variable width font if supported by the output device. .El .Pp The .Ar type must be provided first. Additional arguments may follow: .Bl -tag -width 13n -offset indent .It Fl offset Ar width Indent the display by the .Ar width , which may be one of the following: .Bl -item .It One of the pre-defined strings .Cm indent , the width of a standard indentation (six constant width characters); .Cm indent-two , twice .Cm indent ; .Cm left , which has no effect; .Cm right , which justifies to the right margin; or .Cm center , which aligns around an imagined center axis. .It A macro invocation, which selects a predefined width associated with that macro. The most popular is the imaginary macro .Ar \&Ds , which resolves to .Sy 6n . .It A scaling width as described in .Xr roff 7 . .It An arbitrary string, which indents by the length of this string. .El .Pp When the argument is missing, .Fl offset is ignored. .It Fl compact Do not assert vertical space before the display. .El .Pp Examples: .Bd -literal -offset indent \&.Bd \-literal \-offset indent \-compact Hello world. \&.Ed .Ed .Pp See also .Ic \&D1 and .Ic \&Dl . .It Ic \&Bf Fl emphasis | literal | symbolic | Cm \&Em | \&Li | \&Sy Change the font mode for a scoped block of text. The .Fl emphasis and .Cm \&Em argument are equivalent, as are .Fl symbolic and .Cm \&Sy , and .Fl literal and .Cm \&Li . Without an argument, this macro does nothing. The font mode continues until broken by a new font mode in a nested scope or .Ic \&Ef is encountered. .Pp See also .Ic \&Li , .Ic \&Ef , .Ic \&Em , and .Ic \&Sy . .It Ic \&Bk Fl words For each macro, keep its output together on the same output line, until the end of the macro or the end of the input line is reached, whichever comes first. Line breaks in text lines are unaffected. .Pp The .Fl words argument is required; additional arguments are ignored. .Pp The following example will not break within each .Ic \&Op macro line: .Bd -literal -offset indent \&.Bk \-words \&.Op Fl f Ar flags \&.Op Fl o Ar output \&.Ek .Ed .Pp Be careful in using over-long lines within a keep block! Doing so will clobber the right margin. .It Xo .Ic \&Bl .Fl Ns Ar type .Op Fl width Ar val .Op Fl offset Ar val .Op Fl compact .Op Ar col ... .Xc Begin a list. Lists consist of items specified using the .Ic \&It macro, containing a head or a body or both. .Pp The list .Ar type is mandatory and must be specified first. The .Fl width and .Fl offset arguments accept macro names as described for .Ic \&Bd .Fl offset , scaling widths as described in .Xr roff 7 , or use the length of the given string. The .Fl offset is a global indentation for the whole list, affecting both item heads and bodies. For those list types supporting it, the .Fl width argument requests an additional indentation of item bodies, to be added to the .Fl offset . Unless the .Fl compact argument is specified, list entries are separated by vertical space. .Pp A list must specify one of the following list types: .Bl -tag -width 12n -offset indent .It Fl bullet No item heads can be specified, but a bullet will be printed at the head of each item. Item bodies start on the same output line as the bullet and are indented according to the .Fl width argument. .It Fl column A columnated list. The .Fl width argument has no effect; instead, the string length of each argument specifies the width of one column. If the first line of the body of a .Fl column list is not an .Ic \&It macro line, .Ic \&It contexts spanning one input line each are implied until an .Ic \&It macro line is encountered, at which point items start being interpreted as described in the .Ic \&It documentation. .It Fl dash Like .Fl bullet , except that dashes are used in place of bullets. .It Fl diag Like .Fl inset , except that item heads are not parsed for macro invocations. Most often used in the .Em DIAGNOSTICS section with error constants in the item heads. .It Fl enum A numbered list. No item heads can be specified. Formatted like .Fl bullet , except that cardinal numbers are used in place of bullets, starting at 1. .It Fl hang Like .Fl tag , except that the first lines of item bodies are not indented, but follow the item heads like in .Fl inset lists. .It Fl hyphen Synonym for .Fl dash . .It Fl inset Item bodies follow items heads on the same line, using normal inter-word spacing. Bodies are not indented, and the .Fl width argument is ignored. .It Fl item No item heads can be specified, and none are printed. Bodies are not indented, and the .Fl width argument is ignored. .It Fl ohang Item bodies start on the line following item heads and are not indented. The .Fl width argument is ignored. .It Fl tag Item bodies are indented according to the .Fl width argument. When an item head fits inside the indentation, the item body follows this head on the same output line. Otherwise, the body starts on the output line following the head. .El .Pp Lists may be nested within lists and displays. Nesting of .Fl column and .Fl enum lists may not be portable. .Pp See also .Ic \&El and .Ic \&It . .It Ic \&Bo Ar block Begin a block enclosed by square brackets. Does not have any head arguments. .Pp Examples: .Bd -literal -offset indent -compact \&.Bo 1 , \&.Dv BUFSIZ \&Bc .Ed .Pp See also .Ic \&Bq . .It Ic \&Bq Ar line Encloses its arguments in square brackets. .Pp Examples: .Dl \&.Bq 1 , \&Dv BUFSIZ .Pp .Em Remarks : this macro is sometimes abused to emulate optional arguments for commands; the correct macros to use for this purpose are .Ic \&Op , .Ic \&Oo , and .Ic \&Oc . .Pp See also .Ic \&Bo . .It Ic \&Brc Close a .Ic \&Bro block. Does not have any tail arguments. .It Ic \&Bro Ar block Begin a block enclosed by curly braces. Does not have any head arguments. .Pp Examples: .Bd -literal -offset indent -compact \&.Bro 1 , ... , \&.Va n \&Brc .Ed .Pp See also .Ic \&Brq . .It Ic \&Brq Ar line Encloses its arguments in curly braces. .Pp Examples: .Dl \&.Brq 1 , ... , \&Va n .Pp See also .Ic \&Bro . .It Ic \&Bsx Op Ar version Format the .Bsx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Bsx 1.0 .Dl \&.Bsx .Pp See also .Ic \&At , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Bt Supported only for compatibility, do not use this in new manuals. Prints .Dq is currently in beta test. .It Ic \&Bx Op Ar version Op Ar variant Format the .Bx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Bx 4.3 Tahoe .Dl \&.Bx 4.4 .Dl \&.Bx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Dx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Cd Ar line Kernel configuration declaration. This denotes strings accepted by .Xr config 8 . It is most often used in section 4 manual pages. .Pp Examples: .Dl \&.Cd device le0 at scode? .Pp .Em Remarks : this macro is commonly abused by using quoted literals to retain whitespace and align consecutive .Ic \&Cd declarations. This practise is discouraged. .It Ic \&Cm Ar keyword ... Command modifiers. -Typically used for fixed strings passed as arguments, unless +Typically used for fixed strings passed as arguments to interactive +commands, to commands in interpreted scripts, or to configuration +file directives, unless .Ic \&Fl is more appropriate. -Also useful when specifying configuration options or keys. .Pp Examples: .Dl ".Nm mt Fl f Ar device Cm rewind" .Dl ".Nm ps Fl o Cm pid , Ns Cm command" .Dl ".Nm dd Cm if= Ns Ar file1 Cm of= Ns Ar file2" -.Dl ".Cm IdentityFile Pa ~/.ssh/id_rsa" -.Dl ".Cm LogLevel Dv DEBUG" +.Dl ".Ic set Fl o Cm vi" +.Dl ".Ic lookup Cm file bind" +.Dl ".Ic permit Ar identity Op Cm as Ar target" .It Ic \&D1 Ar line One-line indented display. This is formatted by the default rules and is useful for simple indented statements. It is followed by a newline. .Pp Examples: .Dl \&.D1 \&Fl abcdefgh .Pp See also .Ic \&Bd and .Ic \&Dl . .It Ic \&Db This macro is obsolete. No replacement is needed. It is ignored by .Xr mandoc 1 and groff including its arguments. It was formerly used to toggle a debugging mode. .It Ic \&Dc Close a .Ic \&Do block. Does not have any tail arguments. .It Ic \&Dd Cm $\&Mdocdate$ | Ar month day , year Document date for display in the page footer. This is the mandatory first macro of any .Nm manual. .Pp The .Ar month is the full English month name, the .Ar day is an integer number, and the .Ar year is the full four-digit year. .Pp Other arguments are not portable; the .Xr mandoc 1 utility handles them as follows: .Bl -dash -offset 3n -compact .It To have the date automatically filled in by the .Ox version of .Xr cvs 1 , the special string .Dq $\&Mdocdate$ can be given as an argument. .It The traditional, purely numeric .Xr man 7 format .Ar year Ns \(en Ns Ar month Ns \(en Ns Ar day is accepted, too. .It If a date string cannot be parsed, it is used verbatim. .It If no date string is given, the current date is used. .El .Pp Examples: .Dl \&.Dd $\&Mdocdate$ .Dl \&.Dd $\&Mdocdate: July 2 2018$ .Dl \&.Dd July 2, 2018 .Pp See also .Ic \&Dt and .Ic \&Os . .It Ic \&Dl Ar line One-line indented display. This is formatted as literal text and is useful for commands and invocations. It is followed by a newline. .Pp Examples: .Dl \&.Dl % mandoc mdoc.7 \e(ba less .Pp See also .Ic \&Ql , .Ic \&Bd Fl literal , and .Ic \&D1 . .It Ic \&Do Ar block Begin a block enclosed by double quotes. Does not have any head arguments. .Pp Examples: .Bd -literal -offset indent -compact \&.Do April is the cruellest month \&.Dc \e(em T.S. Eliot .Ed .Pp See also .Ic \&Dq . .It Ic \&Dq Ar line Encloses its arguments in .Dq typographic double-quotes. .Pp Examples: .Bd -literal -offset indent -compact \&.Dq April is the cruellest month \e(em T.S. Eliot .Ed .Pp See also .Ic \&Qq , .Ic \&Sq , and .Ic \&Do . .It Ic \&Dt Ar TITLE section Op Ar arch Document title for display in the page header. This is the mandatory second macro of any .Nm file. .Pp Its arguments are as follows: .Bl -tag -width section -offset 2n .It Ar TITLE The document's title (name), defaulting to .Dq UNTITLED if unspecified. To achieve a uniform appearance of page header lines, it should by convention be all caps. .It Ar section The manual section. This may be one of .Cm 1 .Pq General Commands , .Cm 2 .Pq System Calls , .Cm 3 .Pq Library Functions , .Cm 3p .Pq Perl Library , .Cm 4 .Pq Device Drivers , .Cm 5 .Pq File Formats , .Cm 6 .Pq Games , .Cm 7 .Pq Miscellaneous Information , .Cm 8 .Pq System Manager's Manual , or .Cm 9 .Pq Kernel Developer's Manual . It should correspond to the manual's filename suffix and defaults to the empty string if unspecified. .It Ar arch This specifies the machine architecture a manual page applies to, where relevant, for example .Cm alpha , .Cm amd64 , .Cm i386 , or .Cm sparc64 . The list of valid architectures varies by operating system. .El .Pp Examples: .Dl \&.Dt FOO 1 .Dl \&.Dt FOO 9 i386 .Pp See also .Ic \&Dd and .Ic \&Os . .It Ic \&Dv Ar identifier ... Defined variables such as preprocessor constants, constant symbols, enumeration values, and so on. .Pp Examples: .Dl \&.Dv NULL .Dl \&.Dv BUFSIZ .Dl \&.Dv STDOUT_FILENO .Pp See also .Ic \&Er and .Ic \&Ev for special-purpose constants, .Ic \&Va for variable symbols, and .Ic \&Fd for listing preprocessor variable definitions in the .Em SYNOPSIS . .It Ic \&Dx Op Ar version Format the .Dx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Dx 2.4.1 .Dl \&.Dx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Fx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Ec Op Ar closing_delimiter Close a scope started by .Ic \&Eo . .Pp The .Ar closing_delimiter argument is used as the enclosure tail, for example, specifying \e(rq will emulate .Ic \&Dc . .It Ic \&Ed End a display context started by .Ic \&Bd . .It Ic \&Ef End a font mode context started by .Ic \&Bf . .It Ic \&Ek End a keep context started by .Ic \&Bk . .It Ic \&El End a list context started by .Ic \&Bl . See also .Ic \&It . .It Ic \&Em Ar word ... Request an italic font. If the output device does not provide that, underline. .Pp This is most often used for stress emphasis (not to be confused with importance, see .Ic \&Sy ) . In the rare cases where none of the semantic markup macros fit, it can also be used for technical terms and placeholders, except that for syntax elements, .Ic \&Sy and .Ic \&Ar are preferred, respectively. .Pp Examples: .Bd -literal -compact -offset indent Selected lines are those \&.Em not matching any of the specified patterns. Some of the functions use a \&.Em hold space to save the pattern space for subsequent retrieval. .Ed .Pp See also .Ic \&No , .Ic \&Ql , and .Ic \&Sy . .It Ic \&En Ar word ... This macro is obsolete. Use .Ic \&Eo or any of the other enclosure macros. .Pp It encloses its argument in the delimiters specified by the last .Ic \&Es macro. .It Ic \&Eo Op Ar opening_delimiter An arbitrary enclosure. The .Ar opening_delimiter argument is used as the enclosure head, for example, specifying \e(lq will emulate .Ic \&Do . .It Ic \&Er Ar identifier ... Error constants for definitions of the .Va errno libc global variable. This is most often used in section 2 and 3 manual pages. .Pp Examples: .Dl \&.Er EPERM .Dl \&.Er ENOENT .Pp See also .Ic \&Dv for general constants. .It Ic \&Es Ar opening_delimiter closing_delimiter This macro is obsolete. Use .Ic \&Eo or any of the other enclosure macros. .Pp It takes two arguments, defining the delimiters to be used by subsequent .Ic \&En macros. .It Ic \&Ev Ar identifier ... Environmental variables such as those specified in .Xr environ 7 . .Pp Examples: .Dl \&.Ev DISPLAY .Dl \&.Ev PATH .Pp See also .Ic \&Dv for general constants. .It Ic \&Ex Fl std Op Ar utility ... Insert a standard sentence regarding command exit values of 0 on success and >0 on failure. This is most often used in section 1, 6, and 8 manual pages. .Pp If .Ar utility is not specified, the document's name set by .Ic \&Nm is used. Multiple .Ar utility arguments are treated as separate utilities. .Pp See also .Ic \&Rv . .It Ic \&Fa Ar argument ... Function argument or parameter. Each argument may be a name and a type (recommended for the .Em SYNOPSIS section), a name alone (for function invocations), or a type alone (for function prototypes). If both a type and a name are given or if the type consists of multiple words, all words belonging to the same function argument have to be given in a single argument to the .Ic \&Fa macro. .Pp This macro is also used to specify the field name of a structure. .Pp Most often, the .Ic \&Fa macro is used in the .Em SYNOPSIS within .Ic \&Fo blocks when documenting multi-line function prototypes. If invoked with multiple arguments, the arguments are separated by a comma. Furthermore, if the following macro is another .Ic \&Fa , the last argument will also have a trailing comma. .Pp Examples: .Dl \&.Fa \(dqconst char *p\(dq .Dl \&.Fa \(dqint a\(dq \(dqint b\(dq \(dqint c\(dq .Dl \&.Fa \(dqchar *\(dq size_t .Pp See also .Ic \&Fo . .It Ic \&Fc End a function context started by .Ic \&Fo . .It Ic \&Fd Pf # Ar directive Op Ar argument ... Preprocessor directive, in particular for listing it in the .Em SYNOPSIS . Historically, it was also used to document include files. The latter usage has been deprecated in favour of .Ic \&In . .Pp Examples: .Dl \&.Fd #define sa_handler __sigaction_u.__sa_handler .Dl \&.Fd #define SIO_MAXNFDS .Dl \&.Fd #ifdef FS_DEBUG .Dl \&.Ft void .Dl \&.Fn dbg_open \(dqconst char *\(dq .Dl \&.Fd #endif .Pp See also .Sx MANUAL STRUCTURE , .Ic \&In , and .Ic \&Dv . .It Ic \&Fl Op Ar word ... Command-line flag or option. Used when listing arguments to command-line utilities. Prints a fixed-width hyphen .Sq \- directly followed by each argument. If no arguments are provided, a hyphen is printed followed by a space. If the argument is a macro, a hyphen is prefixed to the subsequent macro output. .Pp Examples: .Dl ".Fl R Op Fl H | L | P" .Dl ".Op Fl 1AaCcdFfgHhikLlmnopqRrSsTtux" .Dl ".Fl type Cm d Fl name Pa CVS" .Dl ".Fl Ar signal_number" .Dl ".Fl o Fl" .Pp See also .Ic \&Cm . .It Ic \&Fn Ar funcname Op Ar argument ... A function name. .Pp Function arguments are surrounded in parenthesis and are delimited by commas. If no arguments are specified, blank parenthesis are output. In the .Em SYNOPSIS section, this macro starts a new output line, and a blank line is automatically inserted between function definitions. .Pp Examples: .Dl \&.Fn \(dqint funcname\(dq \(dqint arg0\(dq \(dqint arg1\(dq .Dl \&.Fn funcname \(dqint arg0\(dq .Dl \&.Fn funcname arg0 .Bd -literal -offset indent \&.Ft functype \&.Fn funcname .Ed .Pp When referring to a function documented in another manual page, use .Ic \&Xr instead. See also .Sx MANUAL STRUCTURE , .Ic \&Fo , and .Ic \&Ft . .It Ic \&Fo Ar funcname Begin a function block. This is a multi-line version of .Ic \&Fn . .Pp Invocations usually occur in the following context: .Bd -ragged -offset indent .Pf \. Ic \&Ft Ar functype .br .Pf \. Ic \&Fo Ar funcname .br .Pf \. Ic \&Fa Qq Ar argtype Ar argname .br \&.\.\. .br .Pf \. Ic \&Fc .Ed .Pp A .Ic \&Fo scope is closed by .Ic \&Fc . .Pp See also .Sx MANUAL STRUCTURE , .Ic \&Fa , .Ic \&Fc , and .Ic \&Ft . .It Ic \&Fr Ar number This macro is obsolete. No replacement markup is needed. .Pp It was used to show numerical function return values in an italic font. .It Ic \&Ft Ar functype A function type. .Pp In the .Em SYNOPSIS section, a new output line is started after this macro. .Pp Examples: .Dl \&.Ft int .Bd -literal -offset indent -compact \&.Ft functype \&.Fn funcname .Ed .Pp See also .Sx MANUAL STRUCTURE , .Ic \&Fn , and .Ic \&Fo . .It Ic \&Fx Op Ar version Format the .Fx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Fx 7.1 .Dl \&.Fx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Nx , and .Ic \&Ox . .It Ic \&Hf Ar filename This macro is not implemented in .Xr mandoc 1 . It was used to include the contents of a (header) file literally. .It Ic \&Ic Ar keyword ... -Designate an internal or interactive command. -This is similar to -.Ic \&Cm -but used for instructions rather than values. +Internal or interactive command, or configuration instruction +in a configuration file. +See also +.Ic \&Cm . .Pp Examples: .Dl \&.Ic :wq .Dl \&.Ic hash .Dl \&.Ic alias .Pp Note that using .Ic \&Ql , .Ic \&Dl , or .Ic \&Bd Fl literal is preferred for displaying code samples; the .Ic \&Ic macro is used when referring to an individual command name. .It Ic \&In Ar filename The name of an include file. This macro is most often used in section 2, 3, and 9 manual pages. .Pp When invoked as the first macro on an input line in the .Em SYNOPSIS section, the argument is displayed in angle brackets and preceded by .Qq #include , and a blank line is inserted in front if there is a preceding function declaration. In other sections, it only encloses its argument in angle brackets and causes no line break. .Pp Examples: .Dl \&.In sys/types.h .Pp See also .Sx MANUAL STRUCTURE . .It Ic \&It Op Ar head A list item. The syntax of this macro depends on the list type. .Pp Lists of type .Fl hang , .Fl ohang , .Fl inset , and .Fl diag have the following syntax: .Pp .D1 Pf \. Ic \&It Ar args .Pp Lists of type .Fl bullet , .Fl dash , .Fl enum , .Fl hyphen and .Fl item have the following syntax: .Pp .D1 Pf \. Ic \&It .Pp with subsequent lines interpreted within the scope of the .Ic \&It until either a closing .Ic \&El or another .Ic \&It . .Pp The .Fl tag list has the following syntax: .Pp .D1 Pf \. Ic \&It Op Cm args .Pp Subsequent lines are interpreted as with .Fl bullet and family. The line arguments correspond to the list's left-hand side; body arguments correspond to the list's contents. .Pp The .Fl column list is the most complicated. Its syntax is as follows: .Pp .D1 Pf \. Ic \&It Ar cell Op Ic \&Ta Ar cell ... .D1 Pf \. Ic \&It Ar cell Op Ar cell ... .Pp The arguments consist of one or more lines of text and macros representing a complete table line. Cells within the line are delimited by the special .Ic \&Ta block macro or by literal tab characters. .Pp Using literal tabs is strongly discouraged because they are very hard to use correctly and .Nm code using them is very hard to read. In particular, a blank character is syntactically significant before and after the literal tab character. If a word precedes or follows the tab without an intervening blank, that word is never interpreted as a macro call, but always output literally. .Pp The tab cell delimiter may only be used within the .Ic \&It line itself; on following lines, only the .Ic \&Ta macro can be used to delimit cells, and portability requires that .Ic \&Ta is called by other macros: some parsers do not recognize it when it appears as the first macro on a line. .Pp Note that quoted strings may span tab-delimited cells on an .Ic \&It line. For example, .Pp .Dl .It \(dqcol1 ,\& col2 ,\(dq \&; .Pp will preserve the whitespace before both commas, but not the whitespace before the semicolon. .Pp See also .Ic \&Bl . .It Ic \&Lb Cm lib Ns Ar name Specify a library. .Pp The .Ar name parameter may be a system library, such as .Cm z or .Cm pam , in which case a small library description is printed next to the linker invocation; or a custom library, in which case the library name is printed in quotes. This is most commonly used in the .Em SYNOPSIS section as described in .Sx MANUAL STRUCTURE . .Pp Examples: .Dl \&.Lb libz .Dl \&.Lb libmandoc .It Ic \&Li Ar word ... Request a typewriter (literal) font. Deprecated because on terminal output devices, this is usually indistinguishable from normal text. For literal displays, use .Ic \&Ql Pq in-line , .Ic \&Dl Pq single line , or .Ic \&Bd Fl literal Pq multi-line instead. .It Ic \&Lk Ar uri Op Ar display_name Format a hyperlink. .Pp Examples: .Dl \&.Lk http://bsd.lv \(dqThe BSD.lv Project\(dq .Dl \&.Lk http://bsd.lv .Pp See also .Ic \&Mt . .It Ic \&Lp Deprecated synonym for .Ic \&Pp . .It Ic \&Ms Ar name Display a mathematical symbol. .Pp Examples: .Dl \&.Ms sigma .Dl \&.Ms aleph .It Ic \&Mt Ar localpart Ns @ Ns Ar domain Format a .Dq mailto: hyperlink. .Pp Examples: .Dl \&.Mt discuss@manpages.bsd.lv .Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv .It Ic \&Nd Ar line A one line description of the manual's content. This is the mandatory last macro of the .Em NAME section and not appropriate for other sections. .Pp Examples: .Dl Pf . Ic \&Nd mdoc language reference .Dl Pf . Ic \&Nd format and display UNIX manuals .Pp The .Ic \&Nd macro technically accepts child macros and terminates with a subsequent .Ic \&Sh invocation. Do not assume this behaviour: some .Xr whatis 1 database generators are not smart enough to parse more than the line arguments and will display macros verbatim. .Pp See also .Ic \&Nm . .It Ic \&Nm Op Ar name The name of the manual page, or \(em in particular in section 1, 6, and 8 pages \(em of an additional command or feature documented in the manual page. When first invoked, the .Ic \&Nm macro expects a single argument, the name of the manual page. Usually, the first invocation happens in the .Em NAME section of the page. The specified name will be remembered and used whenever the macro is called again without arguments later in the page. The .Ic \&Nm macro uses .Sx Block full-implicit semantics when invoked as the first macro on an input line in the .Em SYNOPSIS section; otherwise, it uses ordinary .Sx In-line semantics. .Pp Examples: .Bd -literal -offset indent \&.Sh SYNOPSIS \&.Nm cat \&.Op Fl benstuv \&.Op Ar .Ed .Pp In the .Em SYNOPSIS of section 2, 3 and 9 manual pages, use the .Ic \&Fn macro rather than .Ic \&Nm to mark up the name of the manual page. .It Ic \&No Ar word ... Normal text. Closes the scope of any preceding in-line macro. When used after physical formatting macros like .Ic \&Em or .Ic \&Sy , switches back to the standard font face and weight. Can also be used to embed plain text strings in macro lines using semantic annotation macros. .Pp Examples: .Dl ".Em italic , Sy bold , No and roman" .Bd -literal -offset indent \&.Sm off \&.Cm :C No / Ar pattern No / Ar replacement No / \&.Sm on .Ed .Pp See also .Ic \&Em , .Ic \&Ql , and .Ic \&Sy . .It Ic \&Ns Suppress a space between the output of the preceding macro and the following text or macro. Following invocation, input is interpreted as normal text just like after an .Ic \&No macro. .Pp This has no effect when invoked at the start of a macro line. .Pp Examples: .Dl ".Ar name Ns = Ns Ar value" .Dl ".Cm :M Ns Ar pattern" .Dl ".Fl o Ns Ar output" .Pp See also .Ic \&No and .Ic \&Sm . .It Ic \&Nx Op Ar version Format the .Nx version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Nx 5.01 .Dl \&.Nx .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , and .Ic \&Ox . .It Ic \&Oc Close multi-line .Ic \&Oo context. .It Ic \&Oo Ar block Multi-line version of .Ic \&Op . .Pp Examples: .Bd -literal -offset indent -compact \&.Oo \&.Op Fl flag Ns Ar value \&.Oc .Ed .It Ic \&Op Ar line Optional part of a command line. Prints the argument(s) in brackets. This is most often used in the .Em SYNOPSIS section of section 1 and 8 manual pages. .Pp Examples: .Dl \&.Op \&Fl a \&Ar b .Dl \&.Op \&Ar a | b .Pp See also .Ic \&Oo . .It Ic \&Os Op Ar system Op Ar version Operating system version for display in the page footer. This is the mandatory third macro of any .Nm file. .Pp The optional .Ar system parameter specifies the relevant operating system or environment. It is suggested to leave it unspecified, in which case .Xr mandoc 1 uses its .Fl Ios argument or, if that isn't specified either, .Fa sysname and .Fa release as returned by .Xr uname 3 . .Pp Examples: .Dl \&.Os .Dl \&.Os KTH/CSC/TCS .Dl \&.Os BSD 4.3 .Pp See also .Ic \&Dd and .Ic \&Dt . .It Ic \&Ot Ar functype This macro is obsolete. Use .Ic \&Ft instead; with .Xr mandoc 1 , both have the same effect. .Pp Historical .Nm packages described it as .Dq "old function type (FORTRAN)" . .It Ic \&Ox Op Ar version Format the .Ox version provided as an argument, or a default value if no argument is provided. .Pp Examples: .Dl \&.Ox 4.5 .Dl \&.Ox .Pp See also .Ic \&At , .Ic \&Bsx , .Ic \&Bx , .Ic \&Dx , .Ic \&Fx , and .Ic \&Nx . .It Ic \&Pa Ar name ... An absolute or relative file system path, or a file or directory name. If an argument is not provided, the character .Sq \(ti is used as a default. .Pp Examples: .Dl \&.Pa /usr/bin/mandoc .Dl \&.Pa /usr/share/man/man7/mdoc.7 .Pp See also .Ic \&Lk . .It Ic \&Pc Close parenthesised context opened by .Ic \&Po . .It Ic \&Pf Ar prefix macro Op Ar argument ... Removes the space between its argument and the following macro. It is equivalent to: .Pp .D1 Ic \&No Pf \e& Ar prefix Ic \&Ns Ar macro Op Ar argument ... .Pp The .Ar prefix argument is not parsed for macro names or delimiters, but used verbatim as if it were escaped. .Pp Examples: .Dl ".Pf $ Ar variable_name" .Dl ".Pf . Ar macro_name" .Dl ".Pf 0x Ar hex_digits" .Pp See also .Ic \&Ns and .Ic \&Sm . .It Ic \&Po Ar block Multi-line version of .Ic \&Pq . .It Ic \&Pp Break a paragraph. This will assert vertical space between prior and subsequent macros and/or text. .Pp Paragraph breaks are not needed before or after .Ic \&Sh or .Ic \&Ss macros or before displays .Pq Ic \&Bd Ar line or lists .Pq Ic \&Bl unless the .Fl compact flag is given. .It Ic \&Pq Ar line Parenthesised enclosure. .Pp See also .Ic \&Po . .It Ic \&Qc Close quoted context opened by .Ic \&Qo . .It Ic \&Ql Ar line In-line literal display. This can be used for complete command invocations and for multi-word code examples when an indented display is not desired. .Pp See also .Ic \&Dl and .Ic \&Bd .Fl literal . .It Ic \&Qo Ar block Multi-line version of .Ic \&Qq . .It Ic \&Qq Ar line Encloses its arguments in .Qq typewriter double-quotes. Consider using .Ic \&Dq . .Pp See also .Ic \&Dq , .Ic \&Sq , and .Ic \&Qo . .It Ic \&Re Close an .Ic \&Rs block. Does not have any tail arguments. .It Ic \&Rs Begin a bibliographic .Pq Dq reference block. Does not have any head arguments. The block macro may only contain .Ic \&%A , .Ic \&%B , .Ic \&%C , .Ic \&%D , .Ic \&%I , .Ic \&%J , .Ic \&%N , .Ic \&%O , .Ic \&%P , .Ic \&%Q , .Ic \&%R , .Ic \&%T , .Ic \&%U , and .Ic \&%V child macros (at least one must be specified). .Pp Examples: .Bd -literal -offset indent -compact \&.Rs \&.%A J. E. Hopcroft \&.%A J. D. Ullman \&.%B Introduction to Automata Theory, Languages, and Computation \&.%I Addison-Wesley \&.%C Reading, Massachusetts \&.%D 1979 \&.Re .Ed .Pp If an .Ic \&Rs block is used within a SEE ALSO section, a vertical space is asserted before the rendered output, else the block continues on the current line. .It Ic \&Rv Fl std Op Ar function ... Insert a standard sentence regarding a function call's return value of 0 on success and \-1 on error, with the .Va errno libc global variable set on error. .Pp If .Ar function is not specified, the document's name set by .Ic \&Nm is used. Multiple .Ar function arguments are treated as separate functions. .Pp See also .Ic \&Ex . .It Ic \&Sc Close single-quoted context opened by .Ic \&So . .It Ic \&Sh Ar TITLE LINE Begin a new section. For a list of conventional manual sections, see .Sx MANUAL STRUCTURE . These sections should be used unless it's absolutely necessary that custom sections be used. .Pp Section names should be unique so that they may be keyed by .Ic \&Sx . Although this macro is parsed, it should not consist of child node or it may not be linked with .Ic \&Sx . .Pp See also .Ic \&Pp , .Ic \&Ss , and .Ic \&Sx . .It Ic \&Sm Op Cm on | off Switches the spacing mode for output generated from macros. .Pp By default, spacing is .Cm on . When switched .Cm off , no white space is inserted between macro arguments and between the output generated from adjacent macros, but text lines still get normal spacing between words and sentences. .Pp When called without an argument, the .Ic \&Sm macro toggles the spacing mode. Using this is not recommended because it makes the code harder to read. .It Ic \&So Ar block Multi-line version of .Ic \&Sq . .It Ic \&Sq Ar line Encloses its arguments in .Sq typewriter single-quotes. .Pp See also .Ic \&Dq , .Ic \&Qq , and .Ic \&So . .It Ic \&Ss Ar Title line Begin a new subsection. Unlike with .Ic \&Sh , there is no convention for the naming of subsections. Except .Em DESCRIPTION , the conventional sections described in .Sx MANUAL STRUCTURE rarely have subsections. .Pp Sub-section names should be unique so that they may be keyed by .Ic \&Sx . Although this macro is parsed, it should not consist of child node or it may not be linked with .Ic \&Sx . .Pp See also .Ic \&Pp , .Ic \&Sh , and .Ic \&Sx . .It Ic \&St Fl Ns Ar abbreviation Replace an abbreviation for a standard with the full form. The following standards are recognised. Where multiple lines are given without a blank line in between, they all refer to the same standard, and using the first form is recommended. .Bl -tag -width 1n .It C language standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-ansiC .St -ansiC .It \-ansiC-89 .St -ansiC-89 .It \-isoC .St -isoC .It \-isoC-90 .St -isoC-90 .br The original C standard. .Pp .It \-isoC-amd1 .St -isoC-amd1 .Pp .It \-isoC-tcor1 .St -isoC-tcor1 .Pp .It \-isoC-tcor2 .St -isoC-tcor2 .Pp .It \-isoC-99 .St -isoC-99 .br The second major version of the C language standard. .Pp .It \-isoC-2011 .St -isoC-2011 .br The third major version of the C language standard. .El .It POSIX.1 before the Single UNIX Specification .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-p1003.1-88 .St -p1003.1-88 .It \-p1003.1 .St -p1003.1 .br The original POSIX standard, based on ANSI C. .Pp .It \-p1003.1-90 .St -p1003.1-90 .It \-iso9945-1-90 .St -iso9945-1-90 .br The first update of POSIX.1. .Pp .It \-p1003.1b-93 .St -p1003.1b-93 .It \-p1003.1b .St -p1003.1b .br Real-time extensions. .Pp .It \-p1003.1c-95 .St -p1003.1c-95 .br POSIX thread interfaces. .Pp .It \-p1003.1i-95 .St -p1003.1i-95 .br Technical Corrigendum. .Pp .It \-p1003.1-96 .St -p1003.1-96 .It \-iso9945-1-96 .St -iso9945-1-96 .br Includes POSIX.1-1990, 1b, 1c, and 1i. .El .It X/Open Portability Guide version 4 and related standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-xpg3 .St -xpg3 .br An XPG4 precursor, published in 1989. .Pp .It \-p1003.2 .St -p1003.2 .It \-p1003.2-92 .St -p1003.2-92 .It \-iso9945-2-93 .St -iso9945-2-93 .br An XCU4 precursor. .Pp .It \-p1003.2a-92 .St -p1003.2a-92 .br Updates to POSIX.2. .Pp .It \-xpg4 .St -xpg4 .br Based on POSIX.1 and POSIX.2, published in 1992. .El .It Single UNIX Specification version 1 and related standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-susv1 .St -susv1 .It \-xpg4.2 .St -xpg4.2 .br This standard was published in 1994. It was used as the basis for UNIX 95 certification. The following three refer to parts of it. .Pp .It \-xsh4.2 .St -xsh4.2 .Pp .It \-xcurses4.2 .St -xcurses4.2 .Pp .It \-p1003.1g-2000 .St -p1003.1g-2000 .br Networking APIs, including sockets. .Pp .It \-svid4 .St -svid4 , .br Published in 1995. .El .It Single UNIX Specification version 2 and related standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-susv2 .St -susv2 This Standard was published in 1997 and is also called X/Open Portability Guide version 5. It was used as the basis for UNIX 98 certification. The following refer to parts of it. .Pp .It \-xbd5 .St -xbd5 .Pp .It \-xsh5 .St -xsh5 .Pp .It \-xcu5 .St -xcu5 .Pp .It \-xns5 .St -xns5 .It \-xns5.2 .St -xns5.2 .El .It Single UNIX Specification version 3 .Pp .Bl -tag -width "-p1003.1-2001" -compact .It \-p1003.1-2001 .St -p1003.1-2001 .It \-susv3 .St -susv3 .br This standard is based on C99, SUSv2, POSIX.1-1996, 1d, and 1j. It is also called X/Open Portability Guide version 6. It is used as the basis for UNIX 03 certification. .Pp .It \-p1003.1-2004 .St -p1003.1-2004 .br The second and last Technical Corrigendum. .El .It Single UNIX Specification version 4 .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-p1003.1-2008 .St -p1003.1-2008 .It \-susv4 .St -susv4 .br This standard is also called X/Open Portability Guide version 7. .El .It Other standards .Pp .Bl -tag -width "-p1003.1g-2000" -compact .It \-ieee754 .St -ieee754 .br Floating-point arithmetic. .Pp .It \-iso8601 .St -iso8601 .br Representation of dates and times, published in 1988. .Pp .It \-iso8802-3 .St -iso8802-3 .br Ethernet local area networks. .Pp .It \-ieee1275-94 .St -ieee1275-94 .El .El .It Ic \&Sx Ar Title line Reference a section or subsection in the same manual page. The referenced section or subsection name must be identical to the enclosed argument, including whitespace. .Pp Examples: .Dl \&.Sx MANUAL STRUCTURE .Pp See also .Ic \&Sh and .Ic \&Ss . .It Ic \&Sy Ar word ... Request a boldface font. .Pp This is most often used to indicate importance or seriousness (not to be confused with stress emphasis, see .Ic \&Em ) . When none of the semantic macros fit, it is also adequate for syntax elements that have to be given or that appear verbatim. .Pp Examples: .Bd -literal -compact -offset indent \&.Sy Warning : If \&.Sy s appears in the owner permissions, set-user-ID mode is set. This utility replaces the former \&.Sy dumpdir program. .Ed .Pp See also .Ic \&Em , .Ic \&No , and .Ic \&Ql . .It Ic \&Ta Table cell separator in .Ic \&Bl Fl column lists; can only be used below .Ic \&It . .It Ic \&Tn Ar word ... Supported only for compatibility, do not use this in new manuals. Even though the macro name .Pq Dq tradename suggests a semantic function, historic usage is inconsistent, mostly using it as a presentation-level macro to request a small caps font. .It Ic \&Ud Supported only for compatibility, do not use this in new manuals. Prints out .Dq currently under development. .It Ic \&Ux Supported only for compatibility, do not use this in new manuals. Prints out .Dq Ux . .It Ic \&Va Oo Ar type Oc Ar identifier ... A variable name. .Pp Examples: .Dl \&.Va foo .Dl \&.Va const char *bar ; .Pp For function arguments and parameters, use .Ic \&Fa instead. For declarations of global variables in the .Em SYNOPSIS section, use .Ic \&Vt . .It Ic \&Vt Ar type Op Ar identifier A variable type. .Pp This is also used for indicating global variables in the .Em SYNOPSIS section, in which case a variable name is also specified. Note that it accepts .Sx Block partial-implicit syntax when invoked as the first macro on an input line in the .Em SYNOPSIS section, else it accepts ordinary .Sx In-line syntax. In the former case, this macro starts a new output line, and a blank line is inserted in front if there is a preceding function definition or include directive. .Pp Examples: .Dl \&.Vt unsigned char .Dl \&.Vt extern const char * const sys_signame[] \&; .Pp For parameters in function prototypes, use .Ic \&Fa instead, for function return types .Ic \&Ft , and for variable names outside the .Em SYNOPSIS section .Ic \&Va , even when including a type with the name. See also .Sx MANUAL STRUCTURE . .It Ic \&Xc Close a scope opened by .Ic \&Xo . .It Ic \&Xo Ar block Extend the header of an .Ic \&It macro or the body of a partial-implicit block macro beyond the end of the input line. This macro originally existed to work around the 9-argument limit of historic .Xr roff 7 . .It Ic \&Xr Ar name section Link to another manual .Pq Qq cross-reference . .Pp Cross reference the .Ar name and .Ar section number of another man page. .Pp Examples: .Dl \&.Xr mandoc 1 .Dl \&.Xr mandoc 1 \&; .Dl \&.Xr mandoc 1 \&Ns s behaviour .El .Sh MACRO SYNTAX The syntax of a macro depends on its classification. In this section, .Sq \-arg refers to macro arguments, which may be followed by zero or more .Sq parm parameters; .Sq \&Yo opens the scope of a macro; and if specified, .Sq \&Yc closes it out. .Pp The .Em Callable column indicates that the macro may also be called by passing its name as an argument to another macro. For example, .Sq \&.Op \&Fl O \&Ar file produces .Sq Op Fl O Ar file . To prevent a macro call and render the macro name literally, escape it by prepending a zero-width space, .Sq \e& . For example, .Sq \&Op \e&Fl O produces .Sq Op \&Fl O . If a macro is not callable but its name appears as an argument to another macro, it is interpreted as opaque text. For example, .Sq \&.Fl \&Sh produces .Sq Fl \&Sh . .Pp The .Em Parsed column indicates whether the macro may call other macros by receiving their names as arguments. If a macro is not parsed but the name of another macro appears as an argument, it is interpreted as opaque text. .Pp The .Em Scope column, if applicable, describes closure rules. .Ss Block full-explicit Multi-line scope closed by an explicit closing macro. All macros contains bodies; only .Ic \s&Bf and .Pq optionally .Ic \&Bl contain a head. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \(lBbody...\(rB \&.Yc .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&Bd Ta \&No Ta \&No Ta closed by Ic \&Ed .It Ic \&Bf Ta \&No Ta \&No Ta closed by Ic \&Ef .It Ic \&Bk Ta \&No Ta \&No Ta closed by Ic \&Ek .It Ic \&Bl Ta \&No Ta \&No Ta closed by Ic \&El .It Ic \&Ed Ta \&No Ta \&No Ta opened by Ic \&Bd .It Ic \&Ef Ta \&No Ta \&No Ta opened by Ic \&Bf .It Ic \&Ek Ta \&No Ta \&No Ta opened by Ic \&Bk .It Ic \&El Ta \&No Ta \&No Ta opened by Ic \&Bl .El .Ss Block full-implicit Multi-line scope closed by end-of-file or implicitly by another macro. All macros have bodies; some .Po .Ic \&It Fl bullet , .Fl hyphen , .Fl dash , .Fl enum , .Fl item .Pc don't have heads; only one .Po .Ic \&It in .Ic \&Bl Fl column .Pc has multiple heads. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB \(lBbody...\(rB .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXXXXXXXXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&It Ta \&No Ta Yes Ta closed by Ic \&It , Ic \&El .It Ic \&Nd Ta \&No Ta \&No Ta closed by Ic \&Sh .It Ic \&Nm Ta \&No Ta Yes Ta closed by Ic \&Nm , Ic \&Sh , Ic \&Ss .It Ic \&Sh Ta \&No Ta Yes Ta closed by Ic \&Sh .It Ic \&Ss Ta \&No Ta Yes Ta closed by Ic \&Sh , Ic \&Ss .El .Pp Note that the .Ic \&Nm macro is a .Sx Block full-implicit macro only when invoked as the first macro in a .Em SYNOPSIS section line, else it is .Sx In-line . .Ss Block partial-explicit Like block full-explicit, but also with single-line scope. Each has at least a body and, in limited circumstances, a head .Po .Ic \&Fo , .Ic \&Eo .Pc and/or tail .Pq Ic \&Ec . .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \(lBbody...\(rB \&.Yc \(lBtail...\(rB \&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \ \(lBbody...\(rB \&Yc \(lBtail...\(rB .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&Ac Ta Yes Ta Yes Ta opened by Ic \&Ao .It Ic \&Ao Ta Yes Ta Yes Ta closed by Ic \&Ac .It Ic \&Bc Ta Yes Ta Yes Ta closed by Ic \&Bo .It Ic \&Bo Ta Yes Ta Yes Ta opened by Ic \&Bc .It Ic \&Brc Ta Yes Ta Yes Ta opened by Ic \&Bro .It Ic \&Bro Ta Yes Ta Yes Ta closed by Ic \&Brc .It Ic \&Dc Ta Yes Ta Yes Ta opened by Ic \&Do .It Ic \&Do Ta Yes Ta Yes Ta closed by Ic \&Dc .It Ic \&Ec Ta Yes Ta Yes Ta opened by Ic \&Eo .It Ic \&Eo Ta Yes Ta Yes Ta closed by Ic \&Ec .It Ic \&Fc Ta Yes Ta Yes Ta opened by Ic \&Fo .It Ic \&Fo Ta \&No Ta \&No Ta closed by Ic \&Fc .It Ic \&Oc Ta Yes Ta Yes Ta closed by Ic \&Oo .It Ic \&Oo Ta Yes Ta Yes Ta opened by Ic \&Oc .It Ic \&Pc Ta Yes Ta Yes Ta closed by Ic \&Po .It Ic \&Po Ta Yes Ta Yes Ta opened by Ic \&Pc .It Ic \&Qc Ta Yes Ta Yes Ta opened by Ic \&Oo .It Ic \&Qo Ta Yes Ta Yes Ta closed by Ic \&Oc .It Ic \&Re Ta \&No Ta \&No Ta opened by Ic \&Rs .It Ic \&Rs Ta \&No Ta \&No Ta closed by Ic \&Re .It Ic \&Sc Ta Yes Ta Yes Ta opened by Ic \&So .It Ic \&So Ta Yes Ta Yes Ta closed by Ic \&Sc .It Ic \&Xc Ta Yes Ta Yes Ta opened by Ic \&Xo .It Ic \&Xo Ta Yes Ta Yes Ta closed by Ic \&Xc .El .Ss Block partial-implicit Like block full-implicit, but with single-line scope closed by the end of the line. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB .Ed .Bl -column "MacroX" "CallableX" "ParsedX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed .It Ic \&Aq Ta Yes Ta Yes .It Ic \&Bq Ta Yes Ta Yes .It Ic \&Brq Ta Yes Ta Yes .It Ic \&D1 Ta \&No Ta \&Yes .It Ic \&Dl Ta \&No Ta Yes .It Ic \&Dq Ta Yes Ta Yes .It Ic \&En Ta Yes Ta Yes .It Ic \&Op Ta Yes Ta Yes .It Ic \&Pq Ta Yes Ta Yes .It Ic \&Ql Ta Yes Ta Yes .It Ic \&Qq Ta Yes Ta Yes .It Ic \&Sq Ta Yes Ta Yes .It Ic \&Vt Ta Yes Ta Yes .El .Pp Note that the .Ic \&Vt macro is a .Sx Block partial-implicit only when invoked as the first macro in a .Em SYNOPSIS section line, else it is .Sx In-line . .Ss Special block macro The .Ic \&Ta macro can only be used below .Ic \&It in .Ic \&Bl Fl column lists. It delimits blocks representing table cells; these blocks have bodies, but no heads. .Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope .It Ic \&Ta Ta Yes Ta Yes Ta closed by Ic \&Ta , Ic \&It .El .Ss In-line Closed by the end of the line, fixed argument lengths, and/or subsequent macros. In-line macros have only text children. If a number (or inequality) of arguments is .Pq n , then the macro accepts an arbitrary number of arguments. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB \(lBres...\(rB \&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB Yc... \&.Yo \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN .Ed .Bl -column "MacroX" "CallableX" "ParsedX" "Arguments" -offset indent .It Em Macro Ta Em Callable Ta Em Parsed Ta Em Arguments .It Ic \&%A Ta \&No Ta \&No Ta >0 .It Ic \&%B Ta \&No Ta \&No Ta >0 .It Ic \&%C Ta \&No Ta \&No Ta >0 .It Ic \&%D Ta \&No Ta \&No Ta >0 .It Ic \&%I Ta \&No Ta \&No Ta >0 .It Ic \&%J Ta \&No Ta \&No Ta >0 .It Ic \&%N Ta \&No Ta \&No Ta >0 .It Ic \&%O Ta \&No Ta \&No Ta >0 .It Ic \&%P Ta \&No Ta \&No Ta >0 .It Ic \&%Q Ta \&No Ta \&No Ta >0 .It Ic \&%R Ta \&No Ta \&No Ta >0 .It Ic \&%T Ta \&No Ta \&No Ta >0 .It Ic \&%U Ta \&No Ta \&No Ta >0 .It Ic \&%V Ta \&No Ta \&No Ta >0 .It Ic \&Ad Ta Yes Ta Yes Ta >0 .It Ic \&An Ta Yes Ta Yes Ta >0 .It Ic \&Ap Ta Yes Ta Yes Ta 0 .It Ic \&Ar Ta Yes Ta Yes Ta n .It Ic \&At Ta Yes Ta Yes Ta 1 .It Ic \&Bsx Ta Yes Ta Yes Ta n .It Ic \&Bt Ta \&No Ta \&No Ta 0 .It Ic \&Bx Ta Yes Ta Yes Ta n .It Ic \&Cd Ta Yes Ta Yes Ta >0 .It Ic \&Cm Ta Yes Ta Yes Ta >0 .It Ic \&Db Ta \&No Ta \&No Ta 1 .It Ic \&Dd Ta \&No Ta \&No Ta n .It Ic \&Dt Ta \&No Ta \&No Ta n .It Ic \&Dv Ta Yes Ta Yes Ta >0 .It Ic \&Dx Ta Yes Ta Yes Ta n .It Ic \&Em Ta Yes Ta Yes Ta >0 .It Ic \&Er Ta Yes Ta Yes Ta >0 .It Ic \&Es Ta Yes Ta Yes Ta 2 .It Ic \&Ev Ta Yes Ta Yes Ta >0 .It Ic \&Ex Ta \&No Ta \&No Ta n .It Ic \&Fa Ta Yes Ta Yes Ta >0 .It Ic \&Fd Ta \&No Ta \&No Ta >0 .It Ic \&Fl Ta Yes Ta Yes Ta n .It Ic \&Fn Ta Yes Ta Yes Ta >0 .It Ic \&Fr Ta Yes Ta Yes Ta >0 .It Ic \&Ft Ta Yes Ta Yes Ta >0 .It Ic \&Fx Ta Yes Ta Yes Ta n .It Ic \&Hf Ta \&No Ta \&No Ta n .It Ic \&Ic Ta Yes Ta Yes Ta >0 .It Ic \&In Ta \&No Ta \&No Ta 1 .It Ic \&Lb Ta \&No Ta \&No Ta 1 .It Ic \&Li Ta Yes Ta Yes Ta >0 .It Ic \&Lk Ta Yes Ta Yes Ta >0 .It Ic \&Lp Ta \&No Ta \&No Ta 0 .It Ic \&Ms Ta Yes Ta Yes Ta >0 .It Ic \&Mt Ta Yes Ta Yes Ta >0 .It Ic \&Nm Ta Yes Ta Yes Ta n .It Ic \&No Ta Yes Ta Yes Ta >0 .It Ic \&Ns Ta Yes Ta Yes Ta 0 .It Ic \&Nx Ta Yes Ta Yes Ta n .It Ic \&Os Ta \&No Ta \&No Ta n .It Ic \&Ot Ta Yes Ta Yes Ta >0 .It Ic \&Ox Ta Yes Ta Yes Ta n .It Ic \&Pa Ta Yes Ta Yes Ta n .It Ic \&Pf Ta Yes Ta Yes Ta 1 .It Ic \&Pp Ta \&No Ta \&No Ta 0 .It Ic \&Rv Ta \&No Ta \&No Ta n .It Ic \&Sm Ta \&No Ta \&No Ta <2 .It Ic \&St Ta \&No Ta Yes Ta 1 .It Ic \&Sx Ta Yes Ta Yes Ta >0 .It Ic \&Sy Ta Yes Ta Yes Ta >0 .It Ic \&Tn Ta Yes Ta Yes Ta >0 .It Ic \&Ud Ta \&No Ta \&No Ta 0 .It Ic \&Ux Ta Yes Ta Yes Ta n .It Ic \&Va Ta Yes Ta Yes Ta n .It Ic \&Vt Ta Yes Ta Yes Ta >0 .It Ic \&Xr Ta Yes Ta Yes Ta 2 .El .Ss Delimiters When a macro argument consists of one single input character considered as a delimiter, the argument gets special handling. This does not apply when delimiters appear in arguments containing more than one character. Consequently, to prevent special handling and just handle it like any other argument, a delimiter can be escaped by prepending a zero-width space .Pq Sq \e& . In text lines, delimiters never need escaping, but may be used as normal punctuation. .Pp For many macros, when the leading arguments are opening delimiters, these delimiters are put before the macro scope, and when the trailing arguments are closing delimiters, these delimiters are put after the macro scope. Spacing is suppressed after opening delimiters and before closing delimiters. For example, .Pp .D1 Pf \. \&Aq "( [ word ] ) ." .Pp renders as: .Pp .D1 Aq ( [ word ] ) . .Pp Opening delimiters are: .Pp .Bl -tag -width Ds -offset indent -compact .It \&( left parenthesis .It \&[ left bracket .El .Pp Closing delimiters are: .Pp .Bl -tag -width Ds -offset indent -compact .It \&. period .It \&, comma .It \&: colon .It \&; semicolon .It \&) right parenthesis .It \&] right bracket .It \&? question mark .It \&! exclamation mark .El .Pp Note that even a period preceded by a backslash .Pq Sq \e.\& gets this special handling; use -.Sq \e&. +.Sq \e&.\& to prevent that. .Pp Many in-line macros interrupt their scope when they encounter delimiters, and resume their scope when more arguments follow that are not delimiters. For example, .Pp .D1 Pf \. \&Fl "a ( b | c \e*(Ba d ) e" .Pp renders as: .Pp .D1 Fl a ( b | c \*(Ba d ) e .Pp This applies to both opening and closing delimiters, and also to the middle delimiter, which does not suppress spacing: .Pp .Bl -tag -width Ds -offset indent -compact .It \&| vertical bar .El .Pp As a special case, the predefined string \e*(Ba is handled and rendered in the same way as a plain .Sq \&| character. Using this predefined string is not recommended in new manuals. +.Pp +Appending a zero-width space +.Pq Sq \e& +to the end of an input line is also useful to prevent the interpretation +of a trailing period, exclamation or question mark as the end of a +sentence, for example when an abbreviation happens to occur +at the end of a text or macro input line. .Ss Font handling In .Nm documents, usage of semantic markup is recommended in order to have proper fonts automatically selected; only when no fitting semantic markup is available, consider falling back to .Sx Physical markup macros. Whenever any .Nm macro switches the .Xr roff 7 font mode, it will automatically restore the previous font when exiting its scope. Manually switching the font using the .Xr roff 7 .Ql \ef font escape sequences is never required. .Sh COMPATIBILITY This section provides an incomplete list of compatibility issues between mandoc and GNU troff .Pq Qq groff . .Pp The following problematic behaviour is found in groff: .Pp .Bl -dash -compact .It .Ic \&Dd with non-standard arguments behaves very strangely. When there are three arguments, they are printed verbatim. Any other number of arguments is replaced by the current date, but without any arguments the string .Dq Epoch is printed. .It .Ic \&Lk only accepts a single link-name argument; the remainder is misformatted. .It .Ic \&Pa does not format its arguments when used in the FILES section under certain list types. .It .Ic \&Ta can only be called by other macros, but not at the beginning of a line. .It .Ic \&%C is not implemented (up to and including groff-1.22.2). .It .Sq \ef .Pq font face and .Sq \eF .Pq font family face .Sx Text Decoration escapes behave irregularly when specified within line-macro scopes. .It Negative scaling units return to prior lines. Instead, mandoc truncates them to zero. .El .Pp The following features are unimplemented in mandoc: .Pp .Bl -dash -compact .It .Ic \&Bd Fl file Ar file is unsupported for security reasons. .It .Ic \&Bd .Fl filled does not adjust the right margin, but is an alias for .Ic \&Bd .Fl ragged . .It .Ic \&Bd .Fl literal does not use a literal font, but is an alias for .Ic \&Bd .Fl unfilled . .It .Ic \&Bd .Fl offset Cm center and .Fl offset Cm right don't work. Groff does not implement centered and flush-right rendering either, but produces large indentations. .El .Sh SEE ALSO .Xr man 1 , .Xr mandoc 1 , .Xr eqn 7 , .Xr man 7 , .Xr mandoc_char 7 , .Xr roff 7 , .Xr tbl 7 .Pp The web page .Lk http://mandoc.bsd.lv/mdoc/ "extended documentation for the mdoc language" provides a few tutorial-style pages for beginners, an extensive style guide for advanced authors, and an alphabetic index helping to choose the best macros for various kinds of content. .Sh HISTORY The .Nm language first appeared as a troff macro package in .Bx 4.4 . It was later significantly updated by Werner Lemberg and Ruslan Ermilov in groff-1.17. The standalone implementation that is part of the .Xr mandoc 1 utility written by Kristaps Dzonsons appeared in .Ox 4.6 . .Sh AUTHORS The .Nm reference was written by .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv . Index: head/contrib/mandoc/mdoc_argv.c =================================================================== --- head/contrib/mandoc/mdoc_argv.c (revision 350350) +++ head/contrib/mandoc/mdoc_argv.c (revision 350351) @@ -1,681 +1,682 @@ -/* $Id: mdoc_argv.c,v 1.119 2018/12/21 17:15:19 schwarze Exp $ */ +/* $Id: mdoc_argv.c,v 1.120 2019/07/11 17:06:17 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2012, 2014-2018 Ingo Schwarze + * Copyright (c) 2012, 2014-2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #include #include #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; } Index: head/contrib/mandoc/mdoc_markdown.c =================================================================== --- head/contrib/mandoc/mdoc_markdown.c (revision 350350) +++ head/contrib/mandoc/mdoc_markdown.c (revision 350351) @@ -1,1591 +1,1591 @@ -/* $Id: mdoc_markdown.c,v 1.30 2018/12/30 00:49:55 schwarze Exp $ */ +/* $Id: mdoc_markdown.c,v 1.31 2019/07/01 22:56:24 schwarze Exp $ */ /* * Copyright (c) 2017, 2018 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "main.h" struct md_act { int (*cond)(struct roff_node *n); int (*pre)(struct roff_node *n); void (*post)(struct roff_node *n); 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 c); 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 */ }; 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) 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(" ", 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("‌", 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) { md_post_raw(n); if (n->parent->tok != MDOC_Rs) return; if (n->next != NULL) { md_word(","); if (n->prev != NULL && n->prev->tok == n->tok && n->next->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) { if (n->prev == NULL || ! (n->flags & NODE_SYNPRETTY)) return; if (n->prev->tok == n->tok && n->tok != MDOC_Ft && n->tok != MDOC_Fo && n->tok != MDOC_Fn) { outflags |= MD_br; return; } switch (n->prev->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) { if (n->next != NULL && n->next->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) { md_post_raw(n); if (n->child == NULL && n->next != NULL && n->next->type != ROFFT_TEXT && !(n->next->flags & NODE_LINE)) 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; } Index: head/contrib/mandoc/mdoc_term.c =================================================================== --- head/contrib/mandoc/mdoc_term.c (revision 350350) +++ head/contrib/mandoc/mdoc_term.c (revision 350351) @@ -1,2083 +1,2088 @@ -/* $Id: mdoc_term.c,v 1.372 2019/01/04 03:39:01 schwarze Exp $ */ +/* $Id: mdoc_term.c,v 1.374 2019/06/27 12:20:18 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2010, 2012-2019 Ingo Schwarze * Copyright (c) 2013 Franco Fichtner * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "roff.h" #include "mdoc.h" #include "out.h" #include "term.h" #include "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 *, const struct roff_node *, const 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 *, const 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_cd_pre(DECL_ARGS); static int termp_d1_pre(DECL_ARGS); static int termp_eo_pre(DECL_ARGS); static int termp_em_pre(DECL_ARGS); static int termp_er_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_sy_pre(DECL_ARGS); static int termp_tag_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_cd_pre, NULL }, /* Cd */ { termp_bold_pre, NULL }, /* Cm */ { termp_li_pre, NULL }, /* Dv */ { termp_er_pre, NULL }, /* Er */ { termp_tag_pre, 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_em_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_sy_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 */ }; static int fn_prio; void terminal_mdoc(void *arg, const struct roff_meta *mdoc) { - struct roff_node *n; + 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) { - while (n != NULL) { - if (n->tok == MDOC_Sh && n->sec == SEC_SYNOPSIS) { - if (n->child->next->child != NULL) - print_mdoc_nodelist(p, NULL, - mdoc, n->child->next->child); - term_newln(p); + for (nn = NULL; n != NULL; n = n->next) { + if (n->tok != MDOC_Sh) + continue; + if (n->sec == SEC_SYNOPSIS) break; - } - n = n->next; + 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; /* * 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, const struct roff_node *bl, const struct roff_node *n) { const struct roff_node *nn; assert(n); term_newln(p); if (MDOC_Bd == bl->tok && bl->norm->Bd.comp) return; if (MDOC_Bl == bl->tok && bl->norm->Bl.comp) return; /* Do not vspace directly after Ss/Sh. */ nn = n; while (nn->prev != NULL && (nn->prev->type == ROFFT_COMMENT || nn->prev->flags & NODE_NOPRT)) nn = nn->prev; while (nn->prev == 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; } /* A `-column' does not assert vspace within the list. */ if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type) if (n->prev && MDOC_It == n->prev->tok) return; /* A `-diag' without body does not vspace. */ if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type) if (n->prev && MDOC_It == n->prev->tok) { assert(n->prev->body); if (NULL == n->prev->body->child) return; } term_vspace(p); } static int termp_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); 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; } } term_fontpush(p, TERMFONT_BOLD); return 1; } static void termp_nm_post(DECL_ARGS) { if (n->type == ROFFT_BLOCK) { p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); } else if (n->type == ROFFT_HEAD && NULL != n->next && NULL != n->next->child) { term_flushln(p); p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); p->trailspace = 0; } else if (n->type == ROFFT_BODY && n->child != NULL) term_flushln(p); } static int termp_fl_pre(DECL_ARGS) { termp_tag_pre(p, pair, meta, n); term_fontpush(p, TERMFONT_BOLD); term_word(p, "\\-"); if (!(n->child == NULL && (n->next == NULL || n->next->type == ROFFT_TEXT || n->next->flags & NODE_LINE))) p->flags |= TERMP_NOSPACE; return 1; } static int termp__a_pre(DECL_ARGS) { if (n->prev && MDOC__A == n->prev->tok) if (NULL == n->next || MDOC__A != n->next->tok) term_word(p, "and"); return 1; } static int termp_an_pre(DECL_ARGS) { if (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 && n->prev != 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) { return n->type != ROFFT_HEAD; } 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, const struct roff_node *n) { /* * Obviously, if we're not in a SYNOPSIS or no prior macros * exist, do nothing. */ if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags)) return; /* * If we're the second in a pair of like elements, emit our * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which * case we soldier on. */ if (n->prev->tok == n->tok && MDOC_Ft != n->tok && MDOC_Fo != n->tok && MDOC_Fn != n->tok) { term_newln(p); return; } /* * If we're one of the SYNOPSIS set and non-like pair-wise after * another (or Fn/Fo, which we've let slip through) then assert * vertical space, else only newline and move on. */ switch (n->prev->tok) { case MDOC_Fd: case MDOC_Fn: case MDOC_Fo: case MDOC_In: case MDOC_Vt: term_vspace(p); break; case MDOC_Ft: if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { term_vspace(p); break; } /* FALLTHROUGH */ default: term_newln(p); break; } } static int termp_vt_pre(DECL_ARGS) { if (n->type == ROFFT_ELEM) { synopsis_pre(p, n); return termp_under_pre(p, pair, meta, n); } else if (n->type == ROFFT_BLOCK) { synopsis_pre(p, n); return 1; } else if (n->type == ROFFT_HEAD) return 0; return termp_under_pre(p, pair, meta, n); } static int termp_bold_pre(DECL_ARGS) { termp_tag_pre(p, pair, meta, n); 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) { switch (n->type) { case ROFFT_BLOCK: /* * Vertical space before sections, except * when the previous section was empty. */ if (n->prev == NULL || n->prev->tok != MDOC_Sh || (n->prev->body != NULL && n->prev->body->child != NULL)) term_vspace(p); break; case ROFFT_HEAD: term_fontpush(p, TERMFONT_BOLD); break; 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"); switch (n->sec) { case SEC_DESCRIPTION: fn_prio = 0; break; case SEC_AUTHORS: p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT); break; default: break; } 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 (SEC_LIBRARY == n->sec && NODE_LINE & n->flags) 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) { /* NB: NODE_LINE does not effect this! */ synopsis_pre(p, n); term_fontpush(p, TERMFONT_UNDER); return 1; } static int termp_fn_pre(DECL_ARGS) { size_t rmargin = 0; int pretty; pretty = NODE_SYNPRETTY & n->flags; synopsis_pre(p, n); if (NULL == (n = n->child)) 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 (n->sec == SEC_DESCRIPTION || n->sec == SEC_CUSTOM) tag_put(n->string, ++fn_prio, p->line); 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) { term_fontpush(p, TERMFONT_UNDER); return 1; } for (nn = n->child; nn; nn = nn->next) { term_fontpush(p, TERMFONT_UNDER); p->flags |= TERMP_NBRWORD; term_word(p, nn->string); term_fontpop(p); if (nn->next || (n->next && n->next->tok == MDOC_Fa)) { p->flags |= TERMP_NOSPACE; term_word(p, ","); } } return 0; } static int termp_bd_pre(DECL_ARGS) { 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)) p->flags |= TERMP_NOSPACE; } static int termp_ss_pre(DECL_ARGS) { struct roff_node *nn; switch (n->type) { case ROFFT_BLOCK: term_newln(p); for (nn = n->prev; nn != NULL; nn = nn->prev) if (nn->type != ROFFT_COMMENT && (nn->flags & NODE_NOPRT) == 0) break; if (nn != NULL) term_vspace(p); break; case ROFFT_HEAD: term_fontpush(p, TERMFONT_BOLD); p->tcol->offset = term_len(p, (p->defindent+1)/2); break; 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_cd_pre(DECL_ARGS) { synopsis_pre(p, n); term_fontpush(p, TERMFONT_BOLD); return 1; } static int termp_in_pre(DECL_ARGS) { synopsis_pre(p, n); if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags) { term_fontpush(p, TERMFONT_BOLD); term_word(p, "#include"); term_word(p, "<"); } else { term_word(p, "<"); term_fontpush(p, TERMFONT_UNDER); } p->flags |= TERMP_NOSPACE; return 1; } static void termp_in_post(DECL_ARGS) { if (NODE_SYNPRETTY & n->flags) term_fontpush(p, TERMFONT_BOLD); p->flags |= TERMP_NOSPACE; term_word(p, ">"); if (NODE_SYNPRETTY & n->flags) term_fontpop(p); } static int termp_pp_pre(DECL_ARGS) { fn_prio = 0; term_vspace(p); 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 = 0; int pretty; pretty = NODE_SYNPRETTY & n->flags; if (n->type == ROFFT_BLOCK) { synopsis_pre(p, n); return 1; } else if (n->type == ROFFT_BODY) { if (pretty) { rmargin = p->tcol->rmargin; 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 (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; } return 1; } if (NULL == n->child) return 0; /* XXX: we drop non-initial arguments as per groff. */ assert(n->child->string); term_fontpush(p, TERMFONT_BOLD); term_word(p, n->child->string); return 0; } static void termp_fo_post(DECL_ARGS) { if (n->type != ROFFT_BODY) return; p->flags |= TERMP_NOSPACE; term_word(p, ")"); if (NODE_SYNPRETTY & n->flags) { p->flags |= TERMP_NOSPACE; term_word(p, ";"); term_flushln(p); } } static int termp_bf_pre(DECL_ARGS) { if (n->type == ROFFT_HEAD) return 0; else if (n->type != ROFFT_BODY) return 1; if (FONT_Em == n->norm->Bf.font) term_fontpush(p, TERMFONT_UNDER); else if (FONT_Sy == n->norm->Bf.font) term_fontpush(p, TERMFONT_BOLD); else term_fontpush(p, TERMFONT_NONE); return 1; } static int termp_sm_pre(DECL_ARGS) { if (NULL == n->child) p->flags ^= TERMP_NONOSPACE; else if (0 == strcmp("on", n->child->string)) p->flags &= ~TERMP_NONOSPACE; else p->flags |= TERMP_NONOSPACE; if (p->col && ! (TERMP_NONOSPACE & p->flags)) p->flags &= ~TERMP_NOSPACE; return 0; } static int termp_ap_pre(DECL_ARGS) { p->flags |= TERMP_NOSPACE; term_word(p, "'"); p->flags |= TERMP_NOSPACE; return 1; } static void termp____post(DECL_ARGS) { /* * Handle lists of authors. In general, print each followed by * a comma. Don't print the comma if there are only two * authors. */ if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok) if (NULL == n->next->next || MDOC__A != n->next->next->tok) if (NULL == n->prev || MDOC__A != n->prev->tok) return; /* TODO: %U. */ if (NULL == n->parent || MDOC_Rs != n->parent->tok) return; p->flags |= TERMP_NOSPACE; if (NULL == n->next) { term_word(p, "."); p->flags |= TERMP_SENTENCE; } else term_word(p, ","); } static int termp_li_pre(DECL_ARGS) { termp_tag_pre(p, pair, meta, n); 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); } static void termp__t_post(DECL_ARGS) { /* * If we're in an `Rs' and there's a journal present, then quote * us instead of underlining us (for disambiguation). */ if (n->parent && MDOC_Rs == n->parent->tok && n->parent->norm->Rs.quote_T) termp_quote_post(p, pair, meta, n); termp____post(p, pair, meta, n); } static int termp__t_pre(DECL_ARGS) { /* * If we're in an `Rs' and there's a journal present, then quote * us instead of underlining us (for disambiguation). */ if (n->parent && MDOC_Rs == n->parent->tok && n->parent->norm->Rs.quote_T) return termp_quote_pre(p, pair, meta, n); term_fontpush(p, TERMFONT_UNDER); return 1; } static int termp_under_pre(DECL_ARGS) { term_fontpush(p, TERMFONT_UNDER); return 1; } static int termp_em_pre(DECL_ARGS) { if (n->child != NULL && n->child->type == ROFFT_TEXT) tag_put(n->child->string, 0, p->line); term_fontpush(p, TERMFONT_UNDER); return 1; } static int termp_sy_pre(DECL_ARGS) { if (n->child != NULL && n->child->type == ROFFT_TEXT) tag_put(n->child->string, 0, p->line); term_fontpush(p, TERMFONT_BOLD); return 1; } static int termp_er_pre(DECL_ARGS) { if (n->sec == SEC_ERRORS && (n->parent->tok == MDOC_It || (n->parent->tok == MDOC_Bq && n->parent->parent->parent->tok == MDOC_It))) tag_put(n->child->string, 1, p->line); return 1; } static int termp_tag_pre(DECL_ARGS) { if (n->child != NULL && n->child->type == ROFFT_TEXT && (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(n->child->string, 1, p->line); return 1; } static int termp_abort_pre(DECL_ARGS) { abort(); } Index: head/contrib/mandoc/mdoc_validate.c =================================================================== --- head/contrib/mandoc/mdoc_validate.c (revision 350350) +++ head/contrib/mandoc/mdoc_validate.c (revision 350351) @@ -1,2875 +1,2863 @@ -/* $Id: mdoc_validate.c,v 1.371 2019/03/04 13:01:57 schwarze Exp $ */ +/* $Id: mdoc_validate.c,v 1.374 2019/06/27 15:07:30 schwarze Exp $ */ /* * Copyright (c) 2008-2012 Kristaps Dzonsons * Copyright (c) 2010-2019 Ingo Schwarze * Copyright (c) 2010 Joerg Sonnenberger * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #ifndef OSNAME #include #endif #include #include #include #include #include #include #include #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" /* 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); +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_en(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_fn(POST_ARGS); static void post_fname(POST_ARGS); static void post_fo(POST_ARGS); static void post_hyph(POST_ARGS); static void post_ignpar(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_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_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_ignpar, /* 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_delim_nb, /* Cm */ post_delim_nb, /* Dv */ post_delim_nb, /* Er */ post_delim_nb, /* Ev */ post_ex, /* Ex */ post_fa, /* Fa */ NULL, /* Fd */ post_delim_nb, /* Fl */ post_fn, /* Fn */ post_delim_nb, /* Ft */ post_delim_nb, /* Ic */ post_delim_nb, /* In */ post_defaults, /* 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_delim_nb, /* Em */ NULL, /* Eo */ post_xx, /* Fx */ post_delim_nb, /* Ms */ NULL, /* 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_delim_nb, /* 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 */ }; #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 }; /* 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; const char *p; post_delim_nb(mdoc); n = mdoc->last; assert(n->child->type == ROFFT_TEXT); mdoc->next = ROFF_NEXT_CHILD; if ((p = mdoc_a2lib(n->child->string)) != NULL) { n->child->flags |= NODE_NOPRT; roff_word_alloc(mdoc, n->line, n->pos, p); mdoc->last->flags = NODE_NOSRC; mdoc->last = n; return; } mandoc_msg(MANDOCERR_LB_BAD, n->child->line, n->child->pos, "Lb %s", n->child->string); 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_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) { const struct roff_node *n; const char *cp; size_t pos; n = mdoc->last->child; pos = strcspn(n->string, "()"); cp = n->string + pos; if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*'))) mandoc_msg(MANDOCERR_FN_PAREN, n->line, n->pos + pos, "%s", n->string); } 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 *nn; if (mdoc->last->child != NULL) { post_delim_nb(mdoc); return; } /* * The `Ar' defaults to "file ..." if no value is provided as an * argument; the `Mt' and `Pa' macros use "~"; the `Li' just * gets an empty string. */ nn = mdoc->last; switch (nn->tok) { case MDOC_Ar: mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, nn->line, nn->pos, "file"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, nn->line, nn->pos, "..."); mdoc->last->flags |= NODE_NOSRC; break; case MDOC_Pa: case MDOC_Mt: mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, nn->line, nn->pos, "~"); mdoc->last->flags |= NODE_NOSRC; break; default: abort(); } mdoc->last = nn; } 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_en(POST_ARGS) { post_obsolete(mdoc); if (mdoc->last->type == ROFFT_BLOCK) mdoc->last->norm->Es = mdoc->last_es; } static void post_es(POST_ARGS) { post_obsolete(mdoc); mdoc->last_es = mdoc->last; } 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->string == NULL ? roff_name[nch->tok] : nch->string); 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 *nparent, *nprev; /* of the Bl block */ struct roff_node *nblock, *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; nchild = nbody->child; if (nchild == NULL) { mandoc_msg(MANDOCERR_BLK_EMPTY, nbody->line, nbody->pos, "Bl"); return; } while (nchild != NULL) { nnext = nchild->next; if (nchild->tok == MDOC_It || (nchild->tok == MDOC_Sm && nnext != NULL && nnext->tok == MDOC_It)) { nchild = nnext; continue; } /* * 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, which cannot be moved * out of the list. In that case, wrap such roff nodes * into an implicit row. */ if (nchild->prev != NULL) { mdoc->last = nchild; 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 ((nchild = nnext) == NULL) break; nnext = nchild->next; mdoc->next = ROFF_NEXT_SIBLING; } mdoc->last = nbody; continue; } mandoc_msg(MANDOCERR_BL_MOVE, nchild->line, nchild->pos, "%s", roff_name[nchild->tok]); /* * Move the node out of the Bl block. * First, collect all required node pointers. */ nblock = nbody->parent; nprev = nblock->prev; nparent = nblock->parent; /* * Unlink this child. */ nbody->child = nnext; if (nnext == NULL) nbody->last = NULL; else nnext->prev = NULL; /* * Relink this child. */ nchild->parent = nparent; nchild->prev = nprev; nchild->next = nblock; nblock->prev = nchild; if (nprev == NULL) nparent->child = nchild; else nprev->next = nchild; nchild = nnext; } 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 = mdoc->quick ? mandoc_strdup("") : - mandoc_normdate(mdoc, NULL, 0, 0); + mdoc->meta.date = mandoc_normdate(mdoc, NULL, 0, 0); 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 *nch; char *cp; for (nch = mdoc->last->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])) *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_ignpar(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; } /* 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_ignpar(POST_ARGS) { struct roff_node *np; switch (mdoc->last->type) { case ROFFT_BLOCK: post_prevpar(mdoc); return; case ROFFT_HEAD: post_delim(mdoc); post_hyph(mdoc); return; case ROFFT_BODY: break; default: return; } if ((np = mdoc->last->child) != NULL) if (np->tok == MDOC_Pp || np->tok == ROFF_br || np->tok == ROFF_sp) { mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, "%s after %s", roff_name[np->tok], roff_name[mdoc->last->tok]); roff_node_delete(mdoc, np); } if ((np = mdoc->last->last) != NULL) if (np->tok == MDOC_Pp || np->tok == ROFF_br) { mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, "%s at the end of %s", roff_name[np->tok], roff_name[mdoc->last->tok]); roff_node_delete(mdoc, np); } } static void post_prevpar(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (NULL == n->prev) return; if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) return; /* * Don't allow `Pp' prior to a paragraph-type * block: `Pp' or non-compact `Bd' or `Bl'. */ if (n->prev->tok != MDOC_Pp && n->prev->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, n->prev->line, n->prev->pos, "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]); roff_node_delete(mdoc, n->prev); } static void post_par(POST_ARGS) { struct roff_node *np; 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; char *datestr; 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 (n->child == NULL || n->child->string[0] == '\0') { - mdoc->meta.date = mdoc->quick ? mandoc_strdup("") : - mandoc_normdate(mdoc, NULL, n->line, n->pos); - return; - } - datestr = NULL; deroff(&datestr, n); - if (mdoc->quick) - mdoc->meta.date = datestr; - else { - mdoc->meta.date = mandoc_normdate(mdoc, - datestr, n->line, n->pos); - free(datestr); - } + mdoc->meta.date = mandoc_normdate(mdoc, datestr, n->line, n->pos); + free(datestr); } 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); /* 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; } Index: head/contrib/mandoc/out.c =================================================================== --- head/contrib/mandoc/out.c (revision 350350) +++ head/contrib/mandoc/out.c (revision 350351) @@ -1,553 +1,553 @@ -/* $Id: out.c,v 1.77 2018/12/13 11:55:47 schwarze Exp $ */ +/* $Id: out.c,v 1.78 2019/03/29 21:27:06 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 "config.h" #include #include #include #include #include #include #include #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 (icol > maxcol) + 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; } } /* * Column spacings are needed for span width calculations, * so set the default values now. */ for (icol = 0; icol <= maxcol; icol++) if (tbl->cols[icol].spacing == SIZE_MAX || icol == maxcol) tbl->cols[icol].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; } Index: head/contrib/mandoc/read.c =================================================================== --- head/contrib/mandoc/read.c (revision 350350) +++ head/contrib/mandoc/read.c (revision 350351) @@ -1,710 +1,711 @@ -/* $Id: read.c,v 1.211 2019/01/11 17:04:44 schwarze Exp $ */ +/* $Id: read.c,v 1.214 2019/07/10 19:39:01 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2010-2019 Ingo Schwarze * Copyright (c) 2010, 2012 Joerg Sonnenberger * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #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" #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 = loop = 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_FILE, 0, 0, - "fstat: %s", strerror(errno)); - return 0; + 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 0; + 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 1; + 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_FILE, 0, 0, - "dup: %s", strerror(errno)); - return 0; + mandoc_msg(MANDOCERR_DUP, 0, 0, + "%s", strerror(errno)); + return -1; } if ((gz = gzdopen(fd, "rb")) == NULL) { - mandoc_msg(MANDOCERR_FILE, 0, 0, - "gzdopen: %s", strerror(errno)); + mandoc_msg(MANDOCERR_GZDOPEN, 0, 0, + "%s", strerror(errno)); close(fd); - return 0; + 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 = 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 = 1; + retval = 0; break; } if (ssz == -1) { if (curp->gzip) (void)gzerror(gz, &gzerrnum); - mandoc_msg(MANDOCERR_FILE, 0, 0, "read: %s", + 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_FILE, 0, 0, "gzclose: %s", + mandoc_msg(MANDOCERR_GZCLOSE, 0, 0, "%s", gzerrnum == Z_ERRNO ? strerror(errno) : zError(gzerrnum)); - if (retval == 0) { + 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; size_t offset; int save_filenc, save_lineno; int with_mmap; if (recursion_depth > 64) { mandoc_msg(MANDOCERR_ROFFLOOP, curp->line, 0, NULL); return; } - if (read_whole_file(curp, fd, &blk, &with_mmap) == 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; return curp; } void mparse_reset(struct mparse *curp) { roff_reset(curp->roff); roff_man_reset(curp->man); free_buf_list(curp->secondary); curp->secondary = NULL; curp->gzip = 0; } void mparse_free(struct mparse *curp) { 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); } 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); } Index: head/contrib/mandoc/roff.7 =================================================================== --- head/contrib/mandoc/roff.7 (revision 350350) +++ head/contrib/mandoc/roff.7 (revision 350351) @@ -1,2337 +1,2342 @@ -.\" $Id: roff.7,v 1.111 2019/01/01 03:45:29 schwarze Exp $ +.\" $Id: roff.7,v 1.114 2019/07/15 19:20:30 schwarze Exp $ .\" .\" Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons .\" Copyright (c) 2010-2019 Ingo Schwarze .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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: January 1 2019 $ +.Dd $Mdocdate: July 15 2019 $ .Dt ROFF 7 .Os .Sh NAME .Nm roff .Nd roff language reference for mandoc .Sh DESCRIPTION The .Nm roff language is a general purpose text formatting language. Since traditional implementations of the .Xr mdoc 7 and .Xr man 7 manual formatting languages are based on it, many real-world manuals use small numbers of .Nm requests and escape sequences intermixed with their .Xr mdoc 7 or .Xr man 7 code. To properly format such manuals, the .Xr mandoc 1 utility supports a subset of .Nm requests and escapes. Even though this manual page lists all .Nm requests and escape sequences, it only contains partial information about requests not supported by .Xr mandoc 1 and about language features that do not matter for manual pages. For complete .Nm manuals, consult the .Sx SEE ALSO section. .Pp Input lines beginning with the control character .Sq \&. are parsed for requests and macros. Such lines are called .Dq request lines or .Dq macro lines , respectively. Requests change the processing state and manipulate the formatting; some macros also define the document structure and produce formatted output. The single quote .Pq Qq \(aq is accepted as an alternative control character, treated by .Xr mandoc 1 just like .Ql \&. .Pp Lines not beginning with control characters are called .Dq text lines . They provide free-form text to be printed; the formatting of the text depends on the respective processing context. .Sh LANGUAGE SYNTAX .Nm documents may contain only graphable 7-bit ASCII characters, the space character, and, in certain circumstances, the tab character. The backslash character .Sq \e indicates the start of an escape sequence, used for example for .Sx Comments and .Sx Special Characters . For a complete listing of escape sequences, consult the .Sx ESCAPE SEQUENCE REFERENCE below. .Ss Comments Text following an escaped double-quote .Sq \e\(dq , whether in a request, macro, or text line, is ignored to the end of the line. A request line beginning with a control character and comment escape .Sq \&.\e\(dq is also ignored. Furthermore, request lines with only a control character and optional trailing whitespace are stripped from input. .Pp Examples: .Bd -literal -offset indent -compact \&.\e\(dq This is a comment line. \&.\e\(dq The next line is ignored: \&. \&.Sh EXAMPLES \e\(dq This is a comment, too. \&example text \e\(dq And so is this. .Ed .Ss Special Characters Special characters are used to encode special glyphs and are rendered differently across output media. They may occur in request, macro, and text lines. Sequences begin with the escape character .Sq \e followed by either an open-parenthesis .Sq \&( for two-character sequences; an open-bracket .Sq \&[ for n-character sequences (terminated at a close-bracket .Sq \&] ) ; or a single one character sequence. .Pp Examples: .Bl -tag -width Ds -offset indent -compact .It Li \e(em Two-letter em dash escape. .It Li \ee One-letter backslash escape. .El .Pp See .Xr mandoc_char 7 for a complete list. .Ss Font Selection In .Xr mdoc 7 and .Xr man 7 documents, fonts are usually selected with macros. The .Ic \ef escape sequence and the .Ic \&ft request can be used to manually change the font, but this is not recommended in .Xr mdoc 7 documents. Such manual font changes are overridden by many subsequent macros. .Pp The following fonts are supported: .Pp .Bl -tag -width CW -offset indent -compact .It Cm B Bold font. .It Cm BI A font that is both bold and italic. .It Cm CB Bold constant width font. Same as .Cm B in terminal output. .It Cm CI Italic constant width font. Same as .Cm I in terminal output. .It Cm CR Regular constant width font. Same as .Cm R in terminal output. .It Cm CW An alias for .Cm CR . .It Cm I Italic font. .It Cm P Return to the previous font. If a macro caused a font change since the last .Ic \ef eascape sequence or .Ic \&ft request, this returns to the font before the last font change in the macro rather than to the font before the last manual font change. .It Cm R Roman font. This is the default font. .It Cm 1 An alias for .Cm R . .It Cm 2 An alias for .Cm I . .It Cm 3 An alias for .Cm B . .It Cm 4 An alias for .Cm BI . .El .Pp Examples: .Bl -tag -width Ds -offset indent -compact .It Li \efBbold\efR Write in \fBbold\fP, then switch to regular font mode. .It Li \efIitalic\efP Write in \fIitalic\fP, then return to previous font mode. .It Li \ef(BIbold italic\efP Write in \f(BIbold italic\fP, then return to previous font mode. .El .Ss Whitespace Whitespace consists of the space character. In text lines, whitespace is preserved within a line. In request and macro lines, whitespace delimits arguments and is discarded. .Pp Unescaped trailing spaces are stripped from text line input unless in a literal context. In general, trailing whitespace on any input line is discouraged for reasons of portability. In the rare case that a space character is needed at the end of an input line, it may be forced by .Sq \e\ \e& . .Pp Literal space characters can be produced in the output using escape sequences. In macro lines, they can also be included in arguments using quotation; see .Sx MACRO SYNTAX for details. .Pp Blank text lines, which may include whitespace, are only permitted within literal contexts. If the first character of a text line is a space, that line is printed with a leading newline. .Ss Scaling Widths Many requests and macros support scaled widths for their arguments. The syntax for a scaled width is .Sq Li [+-]?[0-9]*.[0-9]*[:unit:] , where a decimal must be preceded or followed by at least one digit. .Pp The following scaling units are accepted: .Pp .Bl -tag -width Ds -offset indent -compact .It c centimetre .It i inch .It P pica (1/6 inch) .It p point (1/72 inch) .It f scale .Sq u by 65536 .It v default vertical span .It m width of rendered .Sq m .Pq em character .It n width of rendered .Sq n .Pq en character .It u default horizontal span for the terminal .It M mini-em (1/100 em) .El .Pp Using anything other than .Sq m , .Sq n , or .Sq v is necessarily non-portable across output media. See .Sx COMPATIBILITY . .Pp If a scaling unit is not provided, the numerical value is interpreted under the default rules of .Sq v for vertical spaces and .Sq u for horizontal ones. .Pp Examples: .Bl -tag -width ".Bl -tag -width 2i" -offset indent -compact .It Li \&.Bl -tag -width 2i two-inch tagged list indentation in .Xr mdoc 7 .It Li \&.HP 2i two-inch tagged list indentation in .Xr man 7 .It Li \&.sp 2v two vertical spaces .El .Ss Sentence Spacing Each sentence should terminate at the end of an input line. By doing this, a formatter will be able to apply the proper amount of spacing after the end of sentence (unescaped) period, exclamation mark, or question mark followed by zero or more non-sentence closing delimiters .Po .Sq \&) , .Sq \&] , .Sq \&' , .Sq \&" .Pc . .Pp The proper spacing is also intelligently preserved if a sentence ends at the boundary of a macro line. .Pp +If an input line happens to end with a period, exclamation or question +mark that isn't the end of a sentence, append a zero-width space +.Pq Sq \e& . +.Pp Examples: .Bd -literal -offset indent -compact Do not end sentences mid-line like this. Instead, end a sentence like this. A macro would end like this: \&.Xr mandoc 1 \&. +An abbreviation at the end of an input line needs escaping, e.g.\e& +like this. .Ed .Sh REQUEST SYNTAX A request or macro line consists of: .Pp .Bl -enum -compact .It the control character .Sq \&. or .Sq \(aq at the beginning of the line, .It optionally an arbitrary amount of whitespace, .It the name of the request or the macro, which is one word of arbitrary length, terminated by whitespace, .It and zero or more arguments delimited by whitespace. .El .Pp Thus, the following request lines are all equivalent: .Bd -literal -offset indent \&.ig end \&.ig end \&. ig end .Ed .Sh MACRO SYNTAX Macros are provided by the .Xr mdoc 7 and .Xr man 7 languages and can be defined by the .Ic \&de request. When called, they follow the same syntax as requests, except that macro arguments may optionally be quoted by enclosing them in double quote characters .Pq Sq \(dq . Quoted text, even if it contains whitespace or would cause a macro invocation when unquoted, is always considered literal text. Inside quoted text, pairs of double quote characters .Pq Sq Qq resolve to single double quote characters. .Pp To be recognised as the beginning of a quoted argument, the opening quote character must be preceded by a space character. A quoted argument extends to the next double quote character that is not part of a pair, or to the end of the input line, whichever comes earlier. Leaving out the terminating double quote character at the end of the line is discouraged. For clarity, if more arguments follow on the same input line, it is recommended to follow the terminating double quote character by a space character; in case the next character after the terminating double quote character is anything else, it is regarded as the beginning of the next, unquoted argument. .Pp Both in quoted and unquoted arguments, pairs of backslashes .Pq Sq \e\e resolve to single backslashes. In unquoted arguments, space characters can alternatively be included by preceding them with a backslash .Pq Sq \e\~ , but quoting is usually better for clarity. .Pp Examples: .Bl -tag -width Ds -offset indent -compact .It Li .Fn strlen \(dqconst char *s\(dq Group arguments .Qq const char *s into one function argument. If unspecified, .Qq const , .Qq char , and .Qq *s would be considered separate arguments. .It Li .Op \(dqFl a\(dq Consider .Qq \&Fl a as literal text instead of a flag macro. .El .Sh REQUEST REFERENCE The .Xr mandoc 1 .Nm parser recognises the following requests. For requests marked as "ignored" or "unsupported", any arguments are ignored, and the number of arguments is not checked. .Bl -tag -width Ds .It Ic \&ab Op Ar message Abort processing. Currently unsupported. .It Ic \&ad Op Cm b | c | l | n | r Set line adjustment mode for subsequent text. Currently ignored. .It Ic \&af Ar registername format Assign an output format to a number register. Currently ignored. .It Ic \&aln Ar newname oldname Create an alias for a number register. Currently unsupported. .It Ic \&als Ar newname oldname Create an alias for a request, string, macro, or diversion. .It Ic \&am Ar macroname Op Ar endmacro Append to a macro definition. The syntax of this request is the same as that of .Ic \&de . .It Ic \&am1 Ar macroname Op Ar endmacro Append to a macro definition, switching roff compatibility mode off during macro execution (groff extension). The syntax of this request is the same as that of .Ic \&de1 . Since .Xr mandoc 1 does not implement .Nm compatibility mode at all, it handles this request as an alias for .Ic \&am . .It Ic \&ami Ar macrostring Op Ar endstring Append to a macro definition, specifying the macro name indirectly (groff extension). The syntax of this request is the same as that of .Ic \&dei . .It Ic \&ami1 Ar macrostring Op Ar endstring Append to a macro definition, specifying the macro name indirectly and switching roff compatibility mode off during macro execution (groff extension). The syntax of this request is the same as that of .Ic \&dei1 . Since .Xr mandoc 1 does not implement .Nm compatibility mode at all, it handles this request as an alias for .Ic \&ami . .It Ic \&as Ar stringname Op Ar string Append to a user-defined string. The syntax of this request is the same as that of .Ic \&ds . If a user-defined string with the specified name does not yet exist, it is set to the empty string before appending. .It Ic \&as1 Ar stringname Op Ar string Append to a user-defined string, switching roff compatibility mode off during macro execution (groff extension). The syntax of this request is the same as that of .Ic \&ds1 . Since .Xr mandoc 1 does not implement .Nm compatibility mode at all, it handles this request as an alias for .Ic \&as . .It Ic \&asciify Ar divname Fully unformat a diversion. Currently unsupported. .It Ic \&backtrace Print a backtrace of the input stack. This is a groff extension and currently ignored. .It Ic \&bd Ar font Oo Ar curfont Oc Op Ar offset Artificially embolden by repeated printing with small shifts. Currently ignored. .It Ic \&bleedat Ar left top width height Set the BleedBox page parameter for PDF generation. This is a Heirloom extension and currently ignored. .It Ic \&blm Ar macroname Set a blank line trap. Currently unsupported. .It Ic \&box Ar divname Begin a diversion without including a partially filled line. Currently unsupported. .It Ic \&boxa Ar divname Add to a diversion without including a partially filled line. Currently unsupported. .It Ic \&bp Oo Cm + Ns | Ns Cm - Oc Ns Ar pagenumber Begin a new page. Currently ignored. .It Ic \&BP Ar source height width position offset flags label Define a frame and place a picture in it. This is a Heirloom extension and currently unsupported. .It Ic \&br Break the output line. .It Ic \&break -Break out of a +Break out of the innermost .Ic \&while loop. -Currently unsupported. .It Ic \&breakchar Ar char ... Optional line break characters. This is a Heirloom extension and currently ignored. .It Ic \&brnl Ar N Break output line after the next .Ar N input lines. This is a Heirloom extension and currently ignored. .It Ic \&brp Break and spread output line. Currently, this is implemented as an alias for .Ic \&br . .It Ic \&brpnl Ar N Break and spread output line after the next .Ar N input lines. This is a Heirloom extension and currently ignored. .It Ic \&c2 Op Ar char Change the no-break control character. Currently unsupported. .It Ic \&cc Op Ar char Change the control character. If .Ar char is not specified, the control character is reset to .Sq \&. . Trailing characters are ignored. .It Ic \&ce Op Ar N Center the next .Ar N input lines without filling. .Ar N defaults to 1. An argument of 0 or less ends centering. Currently, high level macros abort centering. .It Ic \&cf Ar filename Output the contents of a file. Ignored because insecure. .It Ic \&cflags Ar flags char ... Set character flags. This is a groff extension and currently ignored. .It Ic \&ch Ar macroname Op Ar dist Change a trap location. Currently ignored. .It Ic \&char Ar glyph Op Ar string Define or redefine the ASCII character or character escape sequence .Ar glyph to be rendered as .Ar string , which can be empty. Only partially supported in .Xr mandoc 1 ; may interact incorrectly with .Ic \&tr . .It Ic \&chop Ar stringname Remove the last character from a macro, string, or diversion. Currently unsupported. .It Ic \&class Ar classname char ... Define a character class. This is a groff extension and currently ignored. .It Ic \&close Ar streamname Close an open file. Ignored because insecure. .It Ic \&CL Ar color text Print text in color. This is a Heirloom extension and currently unsupported. .It Ic \&color Op Cm 1 | 0 Activate or deactivate colors. This is a groff extension and currently ignored. .It Ic \&composite Ar from to Define a name component for composite glyph names. This is a groff extension and currently unsupported. .It Ic \&continue Immediately start the next iteration of a .Ic \&while loop. Currently unsupported. .It Ic \&cp Op Cm 1 | 0 Switch .Nm compatibility mode on or off. Currently ignored. .It Ic \&cropat Ar left top width height Set the CropBox page parameter for PDF generation. This is a Heirloom extension and currently ignored. .It Ic \&cs Ar font Op Ar width Op Ar emsize Constant character spacing mode. Currently ignored. .It Ic \&cu Op Ar N Underline next .Ar N input lines including whitespace. Currently ignored. .It Ic \&da Ar divname Append to a diversion. Currently unsupported. .It Ic \&dch Ar macroname Op Ar dist Change a trap location in the current diversion. This is a Heirloom extension and currently unsupported. .It Ic \&de Ar macroname Op Ar endmacro Define a .Nm macro. Its syntax can be either .Bd -literal -offset indent .Pf . Ic \&de Ar macroname .Ar definition \&.. .Ed .Pp or .Bd -literal -offset indent .Pf . Ic \&de Ar macroname Ar endmacro .Ar definition .Pf . Ar endmacro .Ed .Pp Both forms define or redefine the macro .Ar macroname to represent the .Ar definition , which may consist of one or more input lines, including the newline characters terminating each line, optionally containing calls to .Nm requests, .Nm macros or high-level macros like .Xr man 7 or .Xr mdoc 7 macros, whichever applies to the document in question. .Pp Specifying a custom .Ar endmacro works in the same way as for .Ic \&ig ; namely, the call to .Sq Pf . Ar endmacro first ends the .Ar definition , and after that, it is also evaluated as a .Nm request or .Nm macro, but not as a high-level macro. .Pp The macro can be invoked later using the syntax .Pp .D1 Pf . Ar macroname Op Ar argument Op Ar argument ... .Pp Regarding argument parsing, see .Sx MACRO SYNTAX above. .Pp The line invoking the macro will be replaced in the input stream by the .Ar definition , replacing all occurrences of .No \e\e$ Ns Ar N , where .Ar N is a digit, by the .Ar N Ns th Ar argument . For example, .Bd -literal -offset indent \&.de ZN \efI\e^\e\e$1\e^\efP\e\e$2 \&.. \&.ZN XtFree . .Ed .Pp produces .Pp .D1 \efI\e^XtFree\e^\efP. .Pp in the input stream, and thus in the output: \fI\^XtFree\^\fP. Each occurrence of \e\e$* is replaced with all the arguments, joined together with single space characters. The variant \e\e$@ is similar, except that each argument is individually quoted. .Pp Since macros and user-defined strings share a common string table, defining a macro .Ar macroname clobbers the user-defined string .Ar macroname , and the .Ar definition can also be printed using the .Sq \e* string interpolation syntax described below .Ic ds , but this is rarely useful because every macro definition contains at least one explicit newline character. .Pp In order to prevent endless recursion, both groff and .Xr mandoc 1 limit the stack depth for expanding macros and strings to a large, but finite number, and .Xr mandoc 1 also limits the length of the expanded input line. Do not rely on the exact values of these limits. .It Ic \&de1 Ar macroname Op Ar endmacro Define a .Nm macro that will be executed with .Nm compatibility mode switched off during macro execution. This is a groff extension. Since .Xr mandoc 1 does not implement .Nm compatibility mode at all, it handles this request as an alias for .Ic \&de . .It Ic \&defcolor Ar newname scheme component ... Define a color name. This is a groff extension and currently ignored. .It Ic \&dei Ar macrostring Op Ar endstring Define a .Nm macro, specifying the macro name indirectly (groff extension). The syntax of this request is the same as that of .Ic \&de . The effect is the same as: .Pp .D1 Pf . Cm \&de No \e* Ns Bo Ar macrostring Bc Op \e* Ns Bq Ar endstring .It Ic \&dei1 Ar macrostring Op Ar endstring Define a .Nm macro that will be executed with .Nm compatibility mode switched off during macro execution, specifying the macro name indirectly (groff extension). Since .Xr mandoc 1 does not implement .Nm compatibility mode at all, it handles this request as an alias for .Ic \&dei . .It Ic \&device Ar string ... .It Ic \&devicem Ar stringname These two requests only make sense with the groff-specific intermediate output format and are unsupported. .It Ic \&di Ar divname Begin a diversion. Currently unsupported. .It Ic \&do Ar command Op Ar argument ... Execute .Nm request or macro line with compatibility mode disabled. Currently unsupported. .It Ic \&ds Ar stringname Op Oo \(dq Oc Ns Ar string Define a user-defined string. The .Ar stringname and .Ar string arguments are space-separated. If the .Ar string begins with a double-quote character, that character will not be part of the string. All remaining characters on the input line form the .Ar string , including whitespace and double-quote characters, even trailing ones. .Pp The .Ar string can be interpolated into subsequent text by using .No \e* Ns Bq Ar stringname for a .Ar stringname of arbitrary length, or \e*(NN or \e*N if the length of .Ar stringname is two or one characters, respectively. Interpolation can be prevented by escaping the leading backslash; that is, an asterisk preceded by an even number of backslashes does not trigger string interpolation. .Pp Since user-defined strings and macros share a common string table, defining a string .Ar stringname clobbers the macro .Ar stringname , and the .Ar stringname used for defining a string can also be invoked as a macro, in which case the following input line will be appended to the .Ar string , forming a new input line passed to the .Nm parser. For example, .Bd -literal -offset indent \&.ds badidea .S \&.badidea H SYNOPSIS .Ed .Pp invokes the .Ic SH macro when used in a .Xr man 7 document. Such abuse is of course strongly discouraged. .It Ic \&ds1 Ar stringname Op Oo \(dq Oc Ns Ar string Define a user-defined string that will be expanded with .Nm compatibility mode switched off during string expansion. This is a groff extension. Since .Xr mandoc 1 does not implement .Nm compatibility mode at all, it handles this request as an alias for .Ic \&ds . .It Ic \&dwh Ar dist macroname Set a location trap in the current diversion. This is a Heirloom extension and currently unsupported. .It Ic \&dt Op Ar dist macroname Set a trap within a diversion. Currently unsupported. .It Ic \&ec Op Ar char Enable the escape mechanism and change the escape character. The .Ar char argument defaults to the backslash .Pq Sq \e . .It Ic \&ecr Restore the escape character. Currently unsupported. .It Ic \&ecs Save the escape character. Currently unsupported. .It Ic \&el Ar body The .Dq else half of an if/else conditional. Pops a result off the stack of conditional evaluations pushed by .Ic \&ie and uses it as its conditional. If no stack entries are present (e.g., due to no prior .Ic \&ie calls) then false is assumed. The syntax of this request is similar to .Ic \&if except that the conditional is missing. .It Ic \&em Ar macroname Set a trap at the end of input. Currently unsupported. .It Ic \&EN End an equation block. See .Ic \&EQ . .It Ic \&eo Disable the escape mechanism completely. .It Ic \&EP End a picture started by .Ic \&BP . This is a Heirloom extension and currently unsupported. .It Ic \&EQ Begin an equation block. See .Xr eqn 7 for a description of the equation language. .It Ic \&errprint Ar message Print a string like an error message. This is a Heirloom extension and currently ignored. .It Ic \&ev Op Ar envname Switch to another environment. Currently unsupported. .It Ic \&evc Op Ar envname Copy an environment into the current environment. Currently unsupported. .It Ic \&ex Abort processing and exit. Currently unsupported. .It Ic \&fallback Ar curfont font ... Select the fallback sequence for a font. This is a Heirloom extension and currently ignored. .It Ic \&fam Op Ar familyname Change the font family. This is a groff extension and currently ignored. .It Ic \&fc Op Ar delimchar Op Ar padchar Define a delimiting and a padding character for fields. Currently unsupported. .It Ic \&fchar Ar glyphname Op Ar string Define a fallback glyph. Currently unsupported. .It Ic \&fcolor Ar colorname Set the fill color for \eD objects. This is a groff extension and currently ignored. .It Ic \&fdeferlig Ar font string ... Defer ligature building. This is a Heirloom extension and currently ignored. .It Ic \&feature Cm + Ns | Ns Cm - Ns Ar name Enable or disable an OpenType feature. This is a Heirloom extension and currently ignored. .It Ic \&fi Break the output line and switch to fill mode, which is active by default but can be ended with the .Ic \&nf request. In fill mode, input from subsequent input lines is added to the same output line until the next word no longer fits, at which point the output line is broken. This request is implied by the .Xr mdoc 7 .Ic \&Sh macro and by the .Xr man 7 .Ic \&SH , .Ic \&SS , and .Ic \&EE macros. .It Ic \&fkern Ar font minkern Control the use of kerning tables for a font. This is a Heirloom extension and currently ignored. .It Ic \&fl Flush output. Currently ignored. .It Ic \&flig Ar font string char ... Define ligatures. This is a Heirloom extension and currently ignored. .It Ic \&fp Ar position font Op Ar filename Assign font position. Currently ignored. .It Ic \&fps Ar mapname ... Mount a font with a special character map. This is a Heirloom extension and currently ignored. .It Ic \&fschar Ar font glyphname Op Ar string Define a font-specific fallback glyph. This is a groff extension and currently unsupported. .It Ic \&fspacewidth Ar font Op Ar afmunits Set a font-specific width for the space character. This is a Heirloom extension and currently ignored. .It Ic \&fspecial Ar curfont Op Ar font ... Conditionally define a special font. This is a groff extension and currently ignored. .It Ic \&ft Op Ar font Change the font; see .Sx Font Selection . The .Ar font argument defaults to .Cm P . .It Ic \&ftr Ar newname Op Ar oldname Translate font name. This is a groff extension and currently ignored. .It Ic \&fzoom Ar font Op Ar permille Zoom font size. Currently ignored. .It Ic \&gcolor Op Ar colorname Set glyph color. This is a groff extension and currently ignored. .It Ic \&hc Op Ar char Set the hyphenation character. Currently ignored. .It Ic \&hcode Ar char code ... Set hyphenation codes of characters. Currently ignored. .It Ic \&hidechar Ar font char ... Hide characters in a font. This is a Heirloom extension and currently ignored. .It Ic \&hla Ar language Set hyphenation language. This is a groff extension and currently ignored. .It Ic \&hlm Op Ar number Set maximum number of consecutive hyphenated lines. Currently ignored. .It Ic \&hpf Ar filename Load hyphenation pattern file. This is a groff extension and currently ignored. .It Ic \&hpfa Ar filename Load hyphenation pattern file, appending to the current patterns. This is a groff extension and currently ignored. .It Ic \&hpfcode Ar code code ... Define mapping values for character codes in hyphenation patterns. This is a groff extension and currently ignored. .It Ic \&hw Ar word ... Specify hyphenation points in words. Currently ignored. .It Ic \&hy Op Ar mode Set automatic hyphenation mode. Currently ignored. .It Ic \&hylang Ar language Set hyphenation language. This is a Heirloom extension and currently ignored. .It Ic \&hylen Ar nchar Minimum word length for hyphenation. This is a Heirloom extension and currently ignored. .It Ic \&hym Op Ar length Set hyphenation margin. This is a groff extension and currently ignored. .It Ic \&hypp Ar penalty ... Define hyphenation penalties. This is a Heirloom extension and currently ignored. .It Ic \&hys Op Ar length Set hyphenation space. This is a groff extension and currently ignored. .It Ic \&ie Ar condition body The .Dq if half of an if/else conditional. The result of the conditional is pushed into a stack used by subsequent invocations of .Ic \&el , which may be separated by any intervening input (or not exist at all). Its syntax is equivalent to .Ic \&if . .It Ic \&if Ar condition body Begin a conditional. This request can also be written as follows: .Bd -unfilled -offset indent .Pf . Ic \&if Ar condition No \e{ Ns Ar body .Ar body ... Ns \e} .Ed .Bd -unfilled -offset indent .Pf . Ic \&if Ar condition No \e{\e .Ar body ... .Pf . No \e} .Ed .Pp The .Ar condition is a boolean expression. Currently, .Xr mandoc 1 supports the following subset of roff conditionals: .Bl -bullet .It If .Sq \&! is prefixed to .Ar condition , it is logically inverted. .It If the first character of .Ar condition is .Sq n .Pq nroff mode or .Sq o .Pq odd page , it evaluates to true, and the .Ar body starts with the next character. .It If the first character of .Ar condition is .Sq e .Pq even page , .Sq t .Pq troff mode , or .Sq v .Pq vroff mode , it evaluates to false, and the .Ar body starts with the next character. .It If the first character of .Ar condition is .Sq c .Pq character available , it evaluates to true if the following character is an ASCII character or a valid character escape sequence, or to false otherwise. The .Ar body starts with the character following that next character. .It If the first character of .Ar condition is .Sq d , it evaluates to true if the rest of .Ar condition is the name of an existing user defined macro or string; otherwise, it evaluates to false. .It If the first character of .Ar condition is .Sq r , it evaluates to true if the rest of .Ar condition is the name of an existing number register; otherwise, it evaluates to false. .It If the .Ar condition starts with a parenthesis or with an optionally signed integer number, it is evaluated according to the rules of .Sx Numerical expressions explained below. It evaluates to true if the result is positive, or to false if the result is zero or negative. .It Otherwise, the first character of .Ar condition is regarded as a delimiter and it evaluates to true if the string extending from its first to its second occurrence is equal to the string extending from its second to its third occurrence. .It If .Ar condition cannot be parsed, it evaluates to false. .El .Pp If a conditional is false, its children are not processed, but are syntactically interpreted to preserve the integrity of the input document. Thus, .Pp .D1 \&.if t .ig .Pp will discard the .Sq \&.ig , which may lead to interesting results, but .Pp .D1 \&.if t .if t \e{\e .Pp will continue to syntactically interpret to the block close of the final conditional. Sub-conditionals, in this case, obviously inherit the truth value of the parent. .Pp If the .Ar body section is begun by an escaped brace .Sq \e{ , scope continues until the end of the input line containing the matching closing-brace escape sequence .Sq \e} . If the .Ar body is not enclosed in braces, scope continues until the end of the line. If the .Ar condition is followed by a .Ar body on the same line, whether after a brace or not, then requests and macros .Em must begin with a control character. It is generally more intuitive, in this case, to write .Bd -unfilled -offset indent .Pf . Ic \&if Ar condition No \e{\e .Pf . Ar request .Pf . No \e} .Ed .Pp than having the request or macro follow as .Pp .D1 Pf . Ic \&if Ar condition Pf \e{. Ar request .Pp The scope of a conditional is always parsed, but only executed if the conditional evaluates to true. .Pp Note that the .Sq \e} is converted into a zero-width escape sequence if not passed as a standalone macro .Sq \&.\e} . For example, .Pp .D1 \&.Fl a \e} b .Pp will result in .Sq \e} being considered an argument of the .Sq \&Fl macro. .It Ic \&ig Op Ar endmacro Ignore input. Its syntax can be either .Bd -literal -offset indent .Pf . Cm \&ig .Ar ignored text \&.. .Ed .Pp or .Bd -literal -offset indent .Pf . Cm \&ig Ar endmacro .Ar ignored text .Pf . Ar endmacro .Ed .Pp In the first case, input is ignored until a .Sq \&.. request is encountered on its own line. In the second case, input is ignored until the specified .Sq Pf . Ar endmacro is encountered. Do not use the escape character .Sq \e anywhere in the definition of .Ar endmacro ; it would cause very strange behaviour. .Pp When the .Ar endmacro is a roff request or a roff macro, like in .Pp .D1 \&.ig if .Pp the subsequent invocation of .Ic \&if will first terminate the .Ar ignored text , then be invoked as usual. Otherwise, it only terminates the .Ar ignored text , and arguments following it or the .Sq \&.. request are discarded. .It Ic \&in Op Oo Cm + Ns | Ns Cm - Oc Ns Ar width Change indentation. See .Xr man 7 . Ignored in .Xr mdoc 7 . .It Ic \&index Ar register stringname substring Find a substring in a string. This is a Heirloom extension and currently unsupported. .It Ic \&it Ar expression macro Set an input line trap. The named .Ar macro will be invoked after processing the number of input text lines specified by the numerical .Ar expression . While evaluating the .Ar expression , the unit suffixes described below .Sx Scaling Widths are ignored. .It Ic \&itc Ar expression macro Set an input line trap, not counting lines ending with \ec. Currently unsupported. .It Ic \&IX Ar class keystring To support the generation of a table of contents, .Xr pod2man 1 emits this user-defined macro, usually without defining it. To avoid reporting large numbers of spurious errors, .Xr mandoc 1 ignores it. .It Ic \&kern Op Cm 1 | 0 Switch kerning on or off. Currently ignored. .It Ic \&kernafter Ar font char ... afmunits ... Increase kerning after some characters. This is a Heirloom extension and currently ignored. .It Ic \&kernbefore Ar font char ... afmunits ... Increase kerning before some characters. This is a Heirloom extension and currently ignored. .It Ic \&kernpair Ar font char ... font char ... afmunits Add a kerning pair to the kerning table. This is a Heirloom extension and currently ignored. .It Ic \&lc Op Ar glyph Define a leader repetition character. Currently unsupported. .It Ic \&lc_ctype Ar localename Set the .Dv LC_CTYPE locale. This is a Heirloom extension and currently unsupported. .It Ic \&lds Ar macroname string Define a local string. This is a Heirloom extension and currently unsupported. .It Ic \&length Ar register string Count the number of input characters in a string. Currently unsupported. .It Ic \&letadj Ar lspmin lshmin letss lspmax lshmax Dynamic letter spacing and reshaping. This is a Heirloom extension and currently ignored. .It Ic \&lf Ar lineno Op Ar filename Change the line number for error messages. Ignored because insecure. .It Ic \&lg Op Cm 1 | 0 Switch the ligature mechanism on or off. Currently ignored. .It Ic \&lhang Ar font char ... afmunits Hang characters at left margin. This is a Heirloom extension and currently ignored. .It Ic \&linetabs Op Cm 1 | 0 Enable or disable line-tabs mode. This is a groff extension and currently unsupported. .It Ic \&ll Op Oo Cm + Ns | Ns Cm - Oc Ns Ar width Change the output line length. If the .Ar width argument is omitted, the line length is reset to its previous value. The default setting for terminal output is 78n. If a sign is given, the line length is added to or subtracted from; otherwise, it is set to the provided value. Using this request in new manuals is discouraged for several reasons, among others because it overrides the .Xr mandoc 1 .Fl O Cm width command line option. .It Ic \&lnr Ar register Oo Cm + Ns | Ns Cm - Oc Ns Ar value Op Ar increment Set local number register. This is a Heirloom extension and currently unsupported. .It Ic \&lnrf Ar register Oo Cm + Ns | Ns Cm - Oc Ns Ar value Op Ar increment Set local floating-point register. This is a Heirloom extension and currently unsupported. .It Ic \&lpfx Ar string Set a line prefix. This is a Heirloom extension and currently unsupported. .It Ic \&ls Op Ar factor Set line spacing. It takes one integer argument specifying the vertical distance of subsequent output text lines measured in v units. Currently ignored. .It Ic \&lsm Ar macroname Set a leading spaces trap. This is a groff extension and currently unsupported. .It Ic \< Op Oo Cm + Ns | Ns Cm - Oc Ns Ar width Set title line length. Currently ignored. .It Ic \&mc Ar glyph Op Ar dist Print margin character in the right margin. The .Ar dist is currently ignored; instead, 1n is used. .It Ic \&mediasize Ar media Set the device media size. This is a Heirloom extension and currently ignored. .It Ic \&minss Ar width Set minimum word space. This is a Heirloom extension and currently ignored. .It Ic \&mk Op Ar register Mark vertical position. Currently ignored. .It Ic \&mso Ar filename Load a macro file using the search path. Ignored because insecure. .It Ic \&na Disable adjusting without changing the adjustment mode. Currently ignored. .It Ic \&ne Op Ar height Declare the need for the specified minimum vertical space before the next trap or the bottom of the page. Currently ignored. .It Ic \&nf Break the output line and switch to no-fill mode. Subsequent input lines are kept together on the same output line even when exceeding the right margin, and line breaks in subsequent input cause output line breaks. This request is implied by the .Xr mdoc 7 .Ic \&Bd Fl unfilled and .Ic \&Bd Fl literal macros and by the .Xr man 7 .Ic \&EX macro. The .Ic \&fi request switches back to the default fill mode. .It Ic \&nh Turn off automatic hyphenation mode. Currently ignored. .It Ic \&nhychar Ar char ... Define hyphenation-inhibiting characters. This is a Heirloom extension and currently ignored. .It Ic \&nm Op Ar start Op Ar inc Op Ar space Op Ar indent Print line numbers. Currently unsupported. .It Ic \&nn Op Ar number Temporarily turn off line numbering. Currently unsupported. .It Ic \&nop Ar body Execute the rest of the input line as a request, macro, or text line, skipping the .Ic \&nop request and any space characters immediately following it. This is mostly used to indent text lines inside macro definitions. .It Ic \&nr Ar register Oo Cm + Ns | Ns Cm - Oc Ns Ar expression Op Ar stepsize Define or change a register. A register is an arbitrary string value that defines some sort of state, which influences parsing and/or formatting. For the syntax of .Ar expression , see .Sx Numerical expressions below. If it is prefixed by a sign, the register will be incremented or decremented instead of assigned to. .Pp The .Ar stepsize is used by the .Ic \en+ auto-increment feature. It remains unchanged when omitted while changing an existing register, and it defaults to 0 when defining a new register. .Pp The following .Ar register is handled specially: .Bl -tag -width Ds .It Cm nS If set to a positive integer value, certain .Xr mdoc 7 macros will behave in the same way as in the .Em SYNOPSIS section. If set to 0, these macros will behave in the same way as outside the .Em SYNOPSIS section, even when called within the .Em SYNOPSIS section itself. Note that starting a new .Xr mdoc 7 section with the .Ic \&Sh macro will reset this register. .El .It Xo .Ic \&nrf Ar register Oo Cm + Ns | Ns Cm - Oc Ns Ar expression .Op Ar increment .Xc Define or change a floating-point register. This is a Heirloom extension and currently unsupported. .It Ic \&nroff Force nroff mode. This is a groff extension and currently ignored. .It Ic \&ns Turn on no-space mode. Currently ignored. .It Ic \&nx Op Ar filename Abort processing of the current input file and process another one. Ignored because insecure. .It Ic \&open Ar stream file Open a file for writing. Ignored because insecure. .It Ic \&opena Ar stream file Open a file for appending. Ignored because insecure. .It Ic \&os Output saved vertical space. Currently ignored. .It Ic \&output Ar string Output directly to intermediate output. Not supported. .It Ic \&padj Op Cm 1 | 0 Globally control paragraph-at-once adjustment. This is a Heirloom extension and currently ignored. .It Ic \&papersize Ar media Set the paper size. This is a Heirloom extension and currently ignored. .It Ic \&pc Op Ar char Change the page number character. Currently ignored. .It Ic \&pev Print environments. This is a groff extension and currently ignored. .It Ic \&pi Ar command Pipe output to a shell command. Ignored because insecure. .It Ic \&PI Low-level request used by .Ic \&BP . This is a Heirloom extension and currently unsupported. .It Ic \&pl Op Oo Cm + Ns | Ns Cm - Oc Ns Ar height Change page length. Currently ignored. .It Ic \&pm Print names and sizes of macros, strings, and diversions to standard error output. Currently ignored. .It Ic \&pn Oo Cm + Ns | Ns Cm - Oc Ns Ar number Change the page number of the next page. Currently ignored. .It Ic \&pnr Print all number registers on standard error output. Currently ignored. .It Ic \&po Op Oo Cm + Ns | Ns Cm - Oc Ns Ar offset Set a horizontal page offset. If no argument is specified, the page offset is reverted to its previous value. If a sign is specified, the new page offset is calculated relative to the current one; otherwise, it is absolute. The argument follows the syntax of .Sx Scaling Widths and the default scaling unit is .Cm m . .It Ic \&ps Op Oo Cm + Ns | Ns Cm - Oc Ns size Change point size. Currently ignored. .It Ic \&psbb Ar filename Retrieve the bounding box of a PostScript file. Currently unsupported. .It Ic \&pshape Ar indent length ... Set a special shape for the current paragraph. This is a Heirloom extension and currently unsupported. .It Ic \&pso Ar command Include output of a shell command. Ignored because insecure. .It Ic \&ptr Print the names and positions of all traps on standard error output. This is a groff extension and currently ignored. .It Ic \&pvs Op Oo Cm + Ns | Ns Cm - Oc Ns Ar height Change post-vertical spacing. This is a groff extension and currently ignored. .It Ic \&rchar Ar glyph ... Remove glyph definitions. Currently unsupported. .It Ic \&rd Op Ar prompt Op Ar argument ... Read from standard input. Currently ignored. .It Ic \&recursionlimit Ar maxrec maxtail Set the maximum stack depth for recursive macros. This is a Heirloom extension and currently ignored. .It Ic \&return Op Ar twice Exit the presently executed macro and return to the caller. The argument is currently ignored. .It Ic \&rfschar Ar font glyph ... Remove font-specific fallback glyph definitions. Currently unsupported. .It Ic \&rhang Ar font char ... afmunits Hang characters at right margin. This is a Heirloom extension and currently ignored. .It Ic \&rj Op Ar N Justify the next .Ar N input lines to the right margin without filling. .Ar N defaults to 1. An argument of 0 or less ends right adjustment. .It Ic \&rm Ar macroname Remove a request, macro or string. .It Ic \&rn Ar oldname newname Rename a request, macro, diversion, or string. In .Xr mandoc 1 , user-defined macros, .Xr mdoc 7 and .Xr man 7 macros, and user-defined strings can be renamed, but renaming of predefined strings and of .Nm requests is not supported, and diversions are not implemented at all. .It Ic \&rnn Ar oldname newname Rename a number register. Currently unsupported. .It Ic \&rr Ar register Remove a register. .It Ic \&rs End no-space mode. Currently ignored. .It Ic \&rt Op Ar dist Return to marked vertical position. Currently ignored. .It Ic \&schar Ar glyph Op Ar string Define global fallback glyph. This is a groff extension and currently unsupported. .It Ic \&sentchar Ar char ... Define sentence-ending characters. This is a Heirloom extension and currently ignored. .It Ic \&shc Op Ar glyph Change the soft hyphen character. Currently ignored. .It Ic \&shift Op Ar number Shift macro arguments .Ar number times, by default once: \e\e$i becomes what \e\e$i+number was. Also decrement \en(.$ by .Ar number . .It Ic \&sizes Ar size ... Define permissible point sizes. This is a groff extension and currently ignored. .It Ic \&so Ar filename Include a source file. The file is read and its contents processed as input in place of the .Ic \&so request line. To avoid inadvertent inclusion of unrelated files, .Xr mandoc 1 only accepts relative paths not containing the strings .Qq ../ and .Qq /.. . .Pp This request requires .Xr man 1 to change to the right directory before calling .Xr mandoc 1 , per convention to the root of the manual tree. Typical usage looks like: .Pp .Dl \&.so man3/Xcursor.3 .Pp As the whole concept is rather fragile, the use of .Ic \&so is discouraged. Use .Xr ln 1 instead. .It Ic \&sp Op Ar height Break the output line and emit vertical space. The argument follows the syntax of .Sx Scaling Widths and defaults to one blank line .Pq Li 1v . .It Ic \&spacewidth Op Cm 1 | 0 Set the space width from the font metrics file. This is a Heirloom extension and currently ignored. .It Ic \&special Op Ar font ... Define a special font. This is a groff extension and currently ignored. .It Ic \&spreadwarn Op Ar width Warn about wide spacing between words. Currently ignored. .It Ic \&ss Ar wordspace Op Ar sentencespace Set space character size. Currently ignored. .It Ic \&sty Ar position style Associate style with a font position. This is a groff extension and currently ignored. .It Ic \&substring Ar stringname startpos Op Ar endpos Replace a user-defined string with a substring. Currently unsupported. .It Ic \&sv Op Ar height Save vertical space. Currently ignored. .It Ic \&sy Ar command Execute shell command. Ignored because insecure. .It Ic \&T& Re-start a table layout, retaining the options of the prior table invocation. See .Ic \&TS . .It Ic \&ta Op Ar width ... Op Cm T Ar width ... Set tab stops. Each .Ar width argument follows the syntax of .Sx Scaling Widths . If prefixed by a plus sign, it is relative to the previous tab stop. The arguments after the .Cm T marker are used repeatedly as often as needed; for each reuse, they are taken relative to the last previously established tab stop. When .Ic \&ta is called without arguments, all tab stops are cleared. .It Ic \&tc Op Ar glyph Change tab repetition character. Currently unsupported. .It Ic \&TE End a table context. See .Ic \&TS . .It Ic \&ti Oo Cm + Ns | Ns Cm - Oc Ns Ar width Break the output line and indent the next output line by .Ar width . If a sign is specified, the temporary indentation is calculated relative to the current indentation; otherwise, it is absolute. The argument follows the syntax of .Sx Scaling Widths and the default scaling unit is .Cm m . .It Ic \&tkf Ar font minps width1 maxps width2 Enable track kerning for a font. Currently ignored. .It Ic \&tl No \& Ap Ar left Ap Ar center Ap Ar right Ap Print a title line. Currently unsupported. .It Ic \&tm Ar string Print to standard error output. Currently ignored. .It Ic \&tm1 Ar string Print to standard error output, allowing leading blanks. This is a groff extension and currently ignored. .It Ic \&tmc Ar string Print to standard error output without a trailing newline. This is a groff extension and currently ignored. .It Ic \&tr Ar glyph glyph ... Output character translation. The first glyph in each pair is replaced by the second one. Character escapes can be used; for example, .Pp .Dl tr \e(xx\e(yy .Pp replaces all invocations of \e(xx with \e(yy. .It Ic \&track Ar font minps width1 maxps width2 Static letter space tracking. This is a Heirloom extension and currently ignored. .It Ic \&transchar Ar char ... Define transparent characters for sentence-ending. This is a Heirloom extension and currently ignored. .It Ic \&trf Ar filename Output the contents of a file, disallowing invalid characters. This is a groff extension and ignored because insecure. .It Ic \&trimat Ar left top width height Set the TrimBox page parameter for PDF generation. This is a Heirloom extension and currently ignored. .It Ic \&trin Ar glyph glyph ... Output character translation, ignored by .Ic \&asciify . Currently unsupported. .It Ic \&trnt Ar glyph glyph ... Output character translation, ignored by \e!. Currently unsupported. .It Ic \&troff Force troff mode. This is a groff extension and currently ignored. .It Ic \&TS Begin a table, which formats input in aligned rows and columns. See .Xr tbl 7 for a description of the tbl language. .It Ic \&uf Ar font Globally set the underline font. Currently ignored. .It Ic \&ul Op Ar N Underline next .Ar N input lines. Currently ignored. .It Ic \&unformat Ar divname Unformat spaces and tabs in a diversion. Currently unsupported. .It Ic \&unwatch Ar macroname Disable notification for string or macro. This is a Heirloom extension and currently ignored. .It Ic \&unwatchn Ar register Disable notification for register. This is a Heirloom extension and currently ignored. .It Ic \&vpt Op Cm 1 | 0 Enable or disable vertical position traps. This is a groff extension and currently ignored. .It Ic \&vs Op Oo Cm + Ns | Ns Cm - Oc Ns Ar height Change vertical spacing. Currently ignored. .It Ic \&warn Ar flags Set warning level. Currently ignored. .It Ic \&warnscale Ar si Set the scaling indicator used in warnings. This is a groff extension and currently ignored. .It Ic \&watch Ar macroname Notify on change of string or macro. This is a Heirloom extension and currently ignored. .It Ic \&watchlength Ar maxlength On change, report the contents of macros and strings up to the specified length. This is a Heirloom extension and currently ignored. .It Ic \&watchn Ar register Notify on change of register. This is a Heirloom extension and currently ignored. .It Ic \&wh Ar dist Op Ar macroname Set a page location trap. Currently unsupported. .It Ic \&while Ar condition body Repeated execution while a .Ar condition is true, with syntax similar to .Ic \&if . Currently implemented with two restrictions: cannot nest, and each loop must start and end in the same scope. .It Ic \&write Oo \(dq Oc Ns Ar string Write to an open file. Ignored because insecure. .It Ic \&writec Oo \(dq Oc Ns Ar string Write to an open file without appending a newline. Ignored because insecure. .It Ic \&writem Ar macroname Write macro or string to an open file. Ignored because insecure. .It Ic \&xflag Ar level Set the extension level. This is a Heirloom extension and currently ignored. .El .Ss Numerical expressions The .Ic \&nr , .Ic \&if , and .Ic \&ie requests accept integer numerical expressions as arguments. These are always evaluated using the C .Vt int type; integer overflow works the same way as in the C language. Numbers consist of an arbitrary number of digits .Sq 0 to .Sq 9 prefixed by an optional sign .Sq + or .Sq - . Each number may be followed by one optional scaling unit described below .Sx Scaling Widths . The following equations hold: .Bd -literal -offset indent 1i = 6v = 6P = 10m = 10n = 72p = 1000M = 240u = 240 254c = 100i = 24000u = 24000 1f = 65536u = 65536 .Ed .Pp The following binary operators are implemented. Unless otherwise stated, they behave as in the C language: .Pp .Bl -tag -width 2n -compact .It Ic + addition .It Ic - subtraction .It Ic * multiplication .It Ic / division .It Ic % remainder of division .It Ic < less than .It Ic > greater than .It Ic == equal to .It Ic = equal to, same effect as .Ic == (this differs from C) .It Ic <= less than or equal to .It Ic >= greater than or equal to .It Ic <> not equal to (corresponds to C .Ic != ; this one is of limited portability, it is supported by Heirloom roff, but not by groff) .It Ic & logical and (corresponds to C .Ic && ) .It Ic \&: logical or (corresponds to C .Ic || ) .It Ic ? maximum (not available in C) .El .Pp There is no concept of precedence; evaluation proceeds from left to right, except when subexpressions are enclosed in parentheses. Inside parentheses, whitespace is ignored. .Sh ESCAPE SEQUENCE REFERENCE The .Xr mandoc 1 .Nm parser recognises the following escape sequences. In .Xr mdoc 7 and .Xr man 7 documents, using escape sequences is discouraged except for those described in the .Sx LANGUAGE SYNTAX section above. .Pp A backslash followed by any character not listed here simply prints that character itself. .Bl -tag -width Ds .It Ic \e A backslash at the end of an input line can be used to continue the logical input line on the next physical input line, joining the text on both lines together as if it were on a single input line. .It Ic \e The escape sequence backslash-space .Pq Sq \e\ \& is an unpaddable space-sized non-breaking space character; see .Sx Whitespace and .Xr mandoc_char 7 . .It Ic \e! Embed text up to and including the end of the input line into the current diversion or into intermediate output without interpreting requests, macros, and escapes. Currently unsupported. .It Ic \e\(dq The rest of the input line is treated as .Sx Comments . .It Ic \e# Line continuation with comment. Discard the rest of the physical input line and continue the logical input line on the next physical input line, joining the text on both lines together as if it were on a single input line. This is a groff extension. .It Ic \e$ Ns Ar arg Macro argument expansion, see .Ic \&de . .It Ic \e% Hyphenation allowed at this point of the word; ignored by .Xr mandoc 1 . .It Ic \e& Non-printing zero-width character, often used for various kinds of escaping; see .Sx Whitespace , .Xr mandoc_char 7 , and the .Dq MACRO SYNTAX and .Dq Delimiters sections in .Xr mdoc 7 . .It Ic \e\(aq Acute accent special character; use .Ic \e(aa instead. .It Ic \e( Ns Ar cc .Sx Special Characters with two-letter names, see .Xr mandoc_char 7 . .It Ic \e) Zero-width space transparent to end-of-sentence detection; ignored by .Xr mandoc 1 . .It Ic \e*[ Ns Ar name Ns Ic \&] Interpolate the string with the .Ar name . For short names, there are variants .Ic \e* Ns Ar c and .Ic \e*( Ns Ar cc . .Pp One string is predefined on the .Nm language level: .Ic \e*(.T expands to the name of the output device, for example ascii, utf8, ps, pdf, html, or markdown. .Pp Macro sets traditionally predefine additional strings which are not portable and differ across implementations. Those supported by .Xr mandoc 1 are listed in .Xr mandoc_char 7 . .Pp Strings can be defined, changed, and deleted with the .Ic \&ds , .Ic \&as , and .Ic \&rm requests. .It Ic \e, Left italic correction (groff extension); ignored by .Xr mandoc 1 . .It Ic \e- Special character .Dq mathematical minus sign ; see .Xr mandoc_char 7 for details. .It Ic \e/ Right italic correction (groff extension); ignored by .Xr mandoc 1 . .It Ic \e: Breaking the line is allowed at this point of the word without inserting a hyphen. .It Ic \e? Embed the text up to the next .Ic \e? into the current diversion without interpreting requests, macros, and escapes. This is a groff extension and currently unsupported. .It Ic \e[ Ns Ar name Ns Ic \&] .Sx Special Characters with names of arbitrary length, see .Xr mandoc_char 7 . .It Ic \e^ One-twelfth em half-narrow space character, effectively zero-width in .Xr mandoc 1 . .It Ic \e_ Underline special character; use .Ic \e(ul instead. .It Ic \e` Grave accent special character; use .Ic \e(ga instead. .It Ic \e{ Begin conditional input; see .Ic \&if . .It Ic \e\(ba One-sixth em narrow space character, effectively zero-width in .Xr mandoc 1 . .It Ic \e} End conditional input; see .Ic \&if . .It Ic \e~ Paddable non-breaking space character. .It Ic \e0 Digit width space character. .It Ic \eA\(aq Ns Ar string Ns Ic \(aq Anchor definition; ignored by .Xr mandoc 1 . .It Ic \ea Leader character; ignored by .Xr mandoc 1 . .It Ic \eB\(aq Ns Ar string Ns Ic \(aq Interpolate .Sq 1 if .Ar string conforms to the syntax of .Sx Numerical expressions explained above or .Sq 0 otherwise. .It Ic \eb\(aq Ns Ar string Ns Ic \(aq Bracket building function; ignored by .Xr mandoc 1 . .It Ic \eC\(aq Ns Ar name Ns Ic \(aq .Sx Special Characters with names of arbitrary length. .It Ic \ec When encountered at the end of an input text line, the next input text line is considered to continue that line, even if there are request or macro lines in between. No whitespace is inserted. .It Ic \eD\(aq Ns Ar string Ns Ic \(aq Draw graphics function; ignored by .Xr mandoc 1 . .It Ic \ed Move down by half a line; ignored by .Xr mandoc 1 . .It Ic \eE Escape character intended to not be interpreted in copy mode. In .Xr mandoc 1 , it currently does the same as .Ic \e itself. .It Ic \ee Backslash special character. .It Ic \eF[ Ns Ar name Ns Ic \&] Switch font family (groff extension); ignored by .Xr mandoc 1 . For short names, there are variants .Ic \eF Ns Ar c and .Ic \eF( Ns Ar cc . .It Ic \ef[ Ns Ar name Ns Ic \&] Switch to the font .Ar name , see .Sx Font Selection . For short names, there are variants .Ic \ef Ns Ar c and .Ic \ef( Ns Ar cc . An empty name .Ic \ef[] defaults to .Ic \efP . .It Ic \eg[ Ns Ar name Ns Ic \&] Interpolate the format of a number register; ignored by .Xr mandoc 1 . For short names, there are variants .Ic \eg Ns Ar c and .Ic \eg( Ns Ar cc . .It Ic \eH\(aq Ns Oo +|- Oc Ns Ar number Ns Ic \(aq Set the height of the current font; ignored by .Xr mandoc 1 . .It Ic \eh\(aq Ns Oo Cm \&| Oc Ns Ar width Ns Ic \(aq Horizontal motion. If the vertical bar is given, the motion is relative to the current indentation. Otherwise, it is relative to the current position. The default scaling unit is .Cm m . .It Ic \ek[ Ns Ar name Ns Ic \&] Mark horizontal input place in register; ignored by .Xr mandoc 1 . For short names, there are variants .Ic \ek Ns Ar c and .Ic \ek( Ns Ar cc . .It Ic \eL\(aq Ns Ar number Ns Oo Ar c Oc Ns Ic \(aq Vertical line drawing function; ignored by .Xr mandoc 1 . .It Ic \el\(aq Ns Ar width Ns Oo Ar c Oc Ns Ic \(aq Draw a horizontal line of .Ar width using the glyph .Ar c . .It Ic \eM[ Ns Ar name Ns Ic \&] Set fill (background) color (groff extension); ignored by .Xr mandoc 1 . For short names, there are variants .Ic \eM Ns Ar c and .Ic \eM( Ns Ar cc . .It Ic \em[ Ns Ar name Ns Ic \&] Set glyph drawing color (groff extension); ignored by .Xr mandoc 1 . For short names, there are variants .Ic \em Ns Ar c and .Ic \em( Ns Ar cc . .It Ic \eN\(aq Ns Ar number Ns Ic \(aq Character .Ar number on the current font. .It Ic \en Ns Oo +|- Oc Ns Ic \&[ Ns Ar name Ns Ic \&] Interpolate the number register .Ar name . For short names, there are variants .Ic \en Ns Ar c and .Ic \en( Ns Ar cc . If the optional sign is specified, the register is first incremented or decremented by the .Ar stepsize that was specified in the relevant .Ic \&nr request, and the changed value is interpolated. .It Ic \eO Ns Ar digit , Ic \eO[5 Ns arguments Ns Ic \&] Suppress output. This is a groff extension and currently unsupported. With an argument of .Ic 1 , 2 , 3 , or .Ic 4 , it is ignored. .It Ic \eo\(aq Ns Ar string Ns Ic \(aq Overstrike, writing all the characters contained in the .Ar string to the same output position. In terminal and HTML output modes, only the last one of the characters is visible. .It Ic \ep Break the output line at the end of the current word. .It Ic \eR\(aq Ns Ar name Oo +|- Oc Ns Ar number Ns Ic \(aq Set number register; ignored by .Xr mandoc 1 . .It Ic \er Move up by one line; ignored by .Xr mandoc 1 . .It Ic \eS\(aq Ns Ar number Ns Ic \(aq Slant output; ignored by .Xr mandoc 1 . .It Ic \es\(aq Ns Oo +|- Oc Ns Ar number Ns Ic \(aq Change point size; ignored by .Xr mandoc 1 . Alternative forms .Ic \es Ns Oo +|- Oc Ns Ar n , .Ic \es Ns Oo +|- Oc Ns Ic \(aq Ns Ar number Ns Ic \(aq , .Ic \es[ Ns Oo +|- Oc Ns Ar number Ns Ic \&] , and .Ic \es Ns Oo +|- Oc Ns Ic \&[ Ns Ar number Ns Ic \&] are also parsed and ignored. .It Ic \et Horizontal tab; ignored by .Xr mandoc 1 . .It Ic \eu Move up by half a line; ignored by .Xr mandoc 1 . .It Ic \eV[ Ns Ar name Ns Ic \&] Interpolate an environment variable; ignored by .Xr mandoc 1 . For short names, there are variants .Ic \eV Ns Ar c and .Ic \eV( Ns Ar cc . .It Ic \ev\(aq Ns Ar number Ns Ic \(aq Vertical motion; ignored by .Xr mandoc 1 . .It Ic \ew\(aq Ns Ar string Ns Ic \(aq Interpolate the width of the .Ar string . The .Xr mandoc 1 implementation assumes that after expansion of user-defined strings, the .Ar string only contains normal characters, no escape sequences, and that each character has a width of 24 basic units. .It Ic \eX\(aq Ns Ar string Ns Ic \(aq Output .Ar string as device control function; ignored in nroff mode and by .Xr mandoc 1 . .It Ic \ex\(aq Ns Ar number Ns Ic \(aq Extra line space function; ignored by .Xr mandoc 1 . .It Ic \eY[ Ns Ar name Ns Ic \&] Output a string as a device control function; ignored in nroff mode and by .Xr mandoc 1 . For short names, there are variants .Ic \eY Ns Ar c and .Ic \eY( Ns Ar cc . .It Ic \eZ\(aq Ns Ar string Ns Ic \(aq Print .Ar string with zero width and height; ignored by .Xr mandoc 1 . .It Ic \ez Output the next character without advancing the cursor position. .El .Sh COMPATIBILITY The .Xr mandoc 1 implementation of the .Nm language is incomplete. Major unimplemented features include: .Pp .Bl -dash -compact .It For security reasons, .Xr mandoc 1 never reads or writes external files except via .Ic \&so requests with safe relative paths. .It There is no automatic hyphenation, no adjustment to the right margin, and very limited support for centering; the output is always set flush-left. .It Support for setting tabulator and leader characters is missing, and support for manually changing indentation is limited. .It The .Sq u scaling unit is the default terminal unit. In traditional troff systems, this unit changes depending on the output media. .It Width measurements are implemented in a crude way and often yield wrong results. Support for explicit movement requests and escapes is limited. .It There is no concept of output pages, no support for floats, graphics drawing, and picture inclusion; terminal output is always continuous. .It Requests regarding color, font families, font sizes, and glyph manipulation are ignored. Font support is very limited. Kerning is not implemented, and no ligatures are produced. .It The .Qq \(aq macro control character does not suppress output line breaks. .It Diversions and environments are not implemented, and support for traps is very incomplete. .It Use of macros is not supported inside .Xr tbl 7 code. .El .Pp The special semantics of the .Cm nS -number register is an idiosyncracy of +number register is an idiosyncrasy of .Ox manuals and not supported by other .Xr mdoc 7 implementations. .Sh SEE ALSO .Xr mandoc 1 , .Xr eqn 7 , .Xr man 7 , .Xr mandoc_char 7 , .Xr mdoc 7 , .Xr tbl 7 .Rs .%A Joseph F. Ossanna .%A Brian W. Kernighan .%I AT&T Bell Laboratories .%T Troff User's Manual .%R Computing Science Technical Report .%N 54 .%C Murray Hill, New Jersey .%D 1976 and 1992 .%U http://www.kohala.com/start/troff/cstr54.ps .Re .Rs .%A Joseph F. Ossanna .%A Brian W. Kernighan .%A Gunnar Ritter .%T Heirloom Documentation Tools Nroff/Troff User's Manual .%D September 17, 2007 .%U http://heirloom.sourceforge.net/doctools/troff.pdf .Re .Sh HISTORY The RUNOFF typesetting system, whose input forms the basis for .Nm , was written in MAD and FAP for the CTSS operating system by Jerome E. Saltzer in 1964. Doug McIlroy rewrote it in BCPL in 1969, renaming it .Nm . Dennis M. Ritchie rewrote McIlroy's .Nm in PDP-11 assembly for .At v1 , Joseph F. Ossanna improved roff and renamed it nroff for .At v2 , then ported nroff to C as troff, which Brian W. Kernighan released with .At v7 . In 1989, James Clarke re-implemented troff in C++, naming it groff. .Sh AUTHORS .An -nosplit This .Nm reference was written by .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv and .An Ingo Schwarze Aq Mt schwarze@openbsd.org . Index: head/contrib/mandoc/roff.c =================================================================== --- head/contrib/mandoc/roff.c (revision 350350) +++ head/contrib/mandoc/roff.c (revision 350351) @@ -1,4253 +1,4295 @@ -/* $Id: roff.c,v 1.363 2019/02/06 21:11:43 schwarze Exp $ */ +/* $Id: roff.c,v 1.366 2019/07/01 22:56:24 schwarze Exp $ */ /* * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons * Copyright (c) 2010-2015, 2017-2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #include #include #include #include #include #include #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; /* end-rules: custom token */ - int endspan; /* end-rules: next-line or infty */ - int rule; /* current evaluation rule */ + 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 *r, 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", 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_unsupp, NULL, NULL, 0 }, /* break */ + { 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. + * 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->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; + 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; 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); } 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); } void deroff(char **dest, const struct roff_node *n) { char *cp; size_t sz; if (n->type != ROFFT_TEXT) { 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->format == 0) { 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; + 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) 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. */ 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; } } /* * Fully handle known macros when they are structurally * required or when the conditional evaluated to true. */ t = roff_parse(r, buf->buf, &pos, ln, ppos); - irc |= t != TOKEN_NONE && (rr || roffs[t].flags & ROFFMAC_STRUCT) ? - (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs) : - rr ? ROFF_CONT : ROFF_IGN; + 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 = ®->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->sz > 1 && buf->buf[buf->sz - 2] == '\n' ? + 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; } Index: head/contrib/mandoc/roff_html.c =================================================================== --- head/contrib/mandoc/roff_html.c (revision 350350) +++ head/contrib/mandoc/roff_html.c (revision 350351) @@ -1,117 +1,117 @@ -/* $Id: roff_html.c,v 1.19 2019/01/07 07:26:29 schwarze Exp $ */ +/* $Id: roff_html.c,v 1.20 2019/04/30 15:53:01 schwarze Exp $ */ /* * Copyright (c) 2010 Kristaps Dzonsons * Copyright (c) 2014, 2017, 2018, 2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 #include #include #include #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; - print_metaf(h, mandoc_font(cp, (int)strlen(cp))); + 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"); } } Index: head/contrib/mandoc/tag.c =================================================================== --- head/contrib/mandoc/tag.c (revision 350350) +++ head/contrib/mandoc/tag.c (revision 350351) @@ -1,277 +1,296 @@ -/* $Id: tag.c,v 1.21 2018/11/22 11:30:23 schwarze Exp $ */ +/* $Id: tag.c,v 1.24 2019/07/22 03:21:50 schwarze Exp $ */ /* - * Copyright (c) 2015, 2016, 2018 Ingo Schwarze + * Copyright (c) 2015, 2016, 2018, 2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 "config.h" #include -#if HAVE_ERR -#include -#endif +#include #include #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc_ohash.h" +#include "mandoc.h" #include "tag.h" struct tag_entry { size_t *lines; size_t maxlines; size_t nlines; int prio; char s[]; }; static void tag_signal(int) __attribute__((__noreturn__)); static struct ohash tag_data; 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 * tag_init(void) { struct sigaction sa; int ofd; ofd = -1; tag_files.tfd = -1; 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) + 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) + if ((ofd = mkstemp(tag_files.ofn)) == -1) { + mandoc_msg(MANDOCERR_MKSTEMP, 0, 0, + "%s: %s", tag_files.ofn, strerror(errno)); goto fail; - if ((tag_files.tfd = mkstemp(tag_files.tfn)) == -1) + } + if ((tag_files.tfd = mkstemp(tag_files.tfn)) == -1) { + mandoc_msg(MANDOCERR_MKSTEMP, 0, 0, + "%s: %s", tag_files.tfn, strerror(errno)); goto fail; - if (dup2(ofd, STDOUT_FILENO) == -1) + } + if (dup2(ofd, STDOUT_FILENO) == -1) { + mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); goto fail; + } close(ofd); /* * Set up the ohash table to collect output line numbers * where various marked-up terms are documented. */ mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s)); return &tag_files; fail: tag_unlink(); if (ofd != -1) close(ofd); if (tag_files.ofd != -1) close(tag_files.ofd); if (tag_files.tfd != -1) close(tag_files.tfd); *tag_files.ofn = '\0'; *tag_files.tfn = '\0'; tag_files.ofd = -1; tag_files.tfd = -1; return NULL; } /* * Set the line number where a term is defined, * unless it is already defined at a lower priority. */ void tag_put(const char *s, int prio, size_t line) { struct tag_entry *entry; const char *se; size_t len; unsigned int slot; if (tag_files.tfd <= 0) return; if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e')) s += 2; /* - * Skip whitespace and whatever follows it, + * Skip whitespace and escapes and whatever follows, * and if there is any, downgrade the priority. */ - len = strcspn(s, " \t"); + len = strcspn(s, " \t\\"); if (len == 0) return; se = s + len; if (*se != '\0') prio = INT_MAX; slot = ohash_qlookupi(&tag_data, s, &se); entry = ohash_find(&tag_data, slot); if (entry == NULL) { /* Build a new entry. */ entry = mandoc_malloc(sizeof(*entry) + len + 1); memcpy(entry->s, s, len); entry->s[len] = '\0'; entry->lines = NULL; entry->maxlines = entry->nlines = 0; ohash_insert(&tag_data, slot, entry); } else { /* * Lower priority numbers take precedence, * but 0 is special. * A tag with priority 0 is only used * if the tag occurs exactly once. */ if (prio == 0) { if (entry->prio == 0) entry->prio = -1; return; } /* A better entry is already present, ignore the new one. */ if (entry->prio > 0 && entry->prio < prio) return; /* The existing entry is worse, clear it. */ if (entry->prio < 1 || entry->prio > prio) entry->nlines = 0; } /* Remember the new line. */ if (entry->maxlines == entry->nlines) { entry->maxlines += 4; entry->lines = mandoc_reallocarray(entry->lines, entry->maxlines, sizeof(*entry->lines)); } entry->lines[entry->nlines++] = line; entry->prio = prio; } /* * Write out the tags file using the previously collected * information and clear the ohash table while going along. */ void tag_write(void) { FILE *stream; struct tag_entry *entry; size_t i; unsigned int slot; + int empty; if (tag_files.tfd <= 0) return; if (tag_files.tagname != NULL && ohash_find(&tag_data, ohash_qlookup(&tag_data, tag_files.tagname)) == NULL) { - warnx("%s: no such tag", tag_files.tagname); + mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", tag_files.tagname); tag_files.tagname = NULL; } - stream = fdopen(tag_files.tfd, "w"); + if ((stream = fdopen(tag_files.tfd, "w")) == NULL) + mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno)); + empty = 1; entry = ohash_first(&tag_data, &slot); while (entry != NULL) { - if (stream != NULL && entry->prio >= 0) - for (i = 0; i < entry->nlines; i++) + if (stream != NULL && entry->prio >= 0) { + for (i = 0; i < entry->nlines; i++) { fprintf(stream, "%s %s %zu\n", entry->s, tag_files.ofn, entry->lines[i]); + empty = 0; + } + } free(entry->lines); free(entry); entry = ohash_next(&tag_data, &slot); } ohash_delete(&tag_data); if (stream != NULL) fclose(stream); else close(tag_files.tfd); tag_files.tfd = -1; + if (empty) { + unlink(tag_files.tfn); + *tag_files.tfn = '\0'; + } } void tag_unlink(void) { pid_t tc_pgid; if (tag_files.tcpgid != -1) { tc_pgid = tcgetpgrp(tag_files.ofd); if (tc_pgid == tag_files.pager_pid || tc_pgid == getpgid(0) || getpgid(tc_pgid) == -1) (void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid); } if (*tag_files.ofn != '\0') unlink(tag_files.ofn); if (*tag_files.tfn != '\0') unlink(tag_files.tfn); } static void tag_signal(int signum) { struct sigaction sa; 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); } Index: head/contrib/mandoc/tbl_html.c =================================================================== --- head/contrib/mandoc/tbl_html.c (revision 350350) +++ head/contrib/mandoc/tbl_html.c (revision 350351) @@ -1,256 +1,257 @@ -/* $Id: tbl_html.c,v 1.32 2019/01/06 04:55:09 schwarze Exp $ */ +/* $Id: tbl_html.c,v 1.33 2019/03/17 18:21:45 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons * Copyright (c) 2014, 2015, 2017, 2018 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 "config.h" #include #include #include #include #include #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 elements for continuations * of spanned cells. Larger 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); } } Index: head/contrib/mandoc/tbl_term.c =================================================================== --- head/contrib/mandoc/tbl_term.c (revision 350350) +++ head/contrib/mandoc/tbl_term.c (revision 350351) @@ -1,933 +1,946 @@ -/* $Id: tbl_term.c,v 1.68 2019/02/09 21:02:47 schwarze Exp $ */ +/* $Id: tbl_term.c,v 1.72 2019/07/01 22:56:24 schwarze Exp $ */ /* * Copyright (c) 2009, 2011 Kristaps Dzonsons * Copyright (c) 2011-2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 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 "config.h" #include #include #include #include #include #include #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 *, int); + 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 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, 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, 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) continue; hspans = dp->hspans; if (ic || sp->layout->first->pos != TBL_CELL_SPAN) 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) continue; hspans = dp->hspans; if (cp->pos != TBL_CELL_SPAN) 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, 0); + 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) { hspans = dp->hspans; if (ic || sp->layout->first->pos != TBL_CELL_SPAN) 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, NULL, 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, NULL, 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->next, TBL_OPT_ALLBOX); + 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 *spn, int flags) + 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 = spn == NULL ? spp->opts->opts : spn->opts->opts; + 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 : - spn->pos == TBL_SPAN_DHORIZ ? 2 : 1; + 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 (;;) { - col = tp->tbl.cols + (cpn == NULL ? cpp->col : cpn->col); + 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 || strcmp(dpn->string, "\\^") != 0)) + (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; - } - if (cpp == NULL && cpn == NULL) + } 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 || strcmp(dpn->string, "\\^") != 0)) + (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); } Index: head/contrib/mandoc/term.c =================================================================== --- head/contrib/mandoc/term.c (revision 350350) +++ head/contrib/mandoc/term.c (revision 350351) @@ -1,1112 +1,1114 @@ -/* $Id: term.c,v 1.280 2019/01/15 12:16:18 schwarze Exp $ */ +/* $Id: term.c,v 1.281 2019/06/03 20:23:41 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2010-2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "config.h" #include #include #include #include #include #include #include #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); } Index: head/contrib/mandoc =================================================================== --- head/contrib/mandoc (revision 350350) +++ head/contrib/mandoc (revision 350351) Property changes on: head/contrib/mandoc ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /vendor/mandoc/dist:r346149-350350