# # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2014 Garrett D'Amore # Copyright 2015 Nexenta Systems, Inc. All rights reserved. # include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/mandoc/Makefile.common .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) install: all $(ROOTPROG) include $(SRC)/cmd/Makefile.targ # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2014 Garrett D'Amore # Copyright 2017 Nexenta Systems, Inc. # Copyright 2018, Joyent, Inc. # PROG= mandoc OBJS= arch.o \ att.o \ chars.o \ dba.o \ dba_array.o \ dba_read.o \ dba_write.o \ dbm.o \ dbm_map.o \ eqn.o \ eqn_html.o \ eqn_term.o \ html.o \ lib.o \ main.o \ man.o \ manpath.o \ man_html.o \ man_macro.o \ man_term.o \ man_validate.o \ mandoc.o \ mandoc_aux.o \ mandoc_msg.o \ mandoc_ohash.o \ mandoc_xr.o \ mandocdb.o \ mansearch.o \ mdoc.o \ mdoc_argv.o \ mdoc_html.o \ mdoc_macro.o \ mdoc_man.o \ mdoc_markdown.o \ mdoc_state.o \ mdoc_term.o \ mdoc_validate.o \ msec.o \ out.o \ read.o \ roff.o \ roff_html.o \ roff_term.o \ roff_validate.o \ preconv.o \ st.o \ tag.o \ tbl.o \ tbl_data.o \ tbl_html.o \ tbl_layout.o \ tbl_opts.o \ tbl_term.o \ term.o \ term_ascii.o \ term_ps.o \ term_tab.o \ term_tag.o \ tree.o OBJS += compat_ohash.o compat_stringlist.o # gcc 4.4.4 false positive roff.o : CERRWARN += $(CNOWARN_UNINIT) main.o : CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off CFLAGS += $(CC_VERBOSE) CSTD= $(CSTD_GNU99) CPPFLAGS += -DOSNAME="\"illumos\"" -D_FILE_OFFSET_BITS=64 LDLIBS += -lz $Id: LICENSE,v 1.22 2021/09/19 11:02:09 schwarze Exp $ With the exceptions noted below, all non-trivial files contained in the mandoc toolkit are protected by the Copyright of the following developers: Copyright (c) 2008-2012, 2014 Kristaps Dzonsons Copyright (c) 2010-2021 Ingo Schwarze Copyright (c) 1999, 2004, 2017 Marc Espie Copyright (c) 2009, 2010, 2011, 2012 Joerg Sonnenberger Copyright (c) 2013 Franco Fichtner Copyright (c) 2014 Baptiste Daroussin Copyright (c) 2016 Ed Maste Copyright (c) 2017 Michael Stapelberg Copyright (c) 2017 Anthony Bentley Copyright (c) 1998, 2004, 2010, 2015 Todd C. Miller Copyright (c) 2008, 2017 Otto Moerbeek Copyright (c) 2004 Ted Unangst Copyright (c) 1994 Christos Zoulas Copyright (c) 2003, 2007, 2008, 2014 Jason McIntyre See the individual files for information about who contributed to which file during which years. The mandoc distribution as a whole is distributed by its developers under the following license: Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The following files included from outside sources are protected by other people's Copyright and are distributed under various 2-clause and 3-clause BSD licenses; see these individual files for details. soelim.c, soelim.1: Copyright (c) 2014 Baptiste Daroussin compat_err.c, compat_fts.c, compat_fts.h, compat_getsubopt.c, compat_strcasestr.c, compat_strsep.c, man.1: Copyright (c) 1989,1990,1993,1994 The Regents of the University of California compat_stringlist.c, compat_stringlist.h: Copyright (c) 1994 Christos Zoulas The mandoc UNIX manpage compiler toolset /* $Id: arch.c,v 1.17 2021/05/13 13:33:11 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", "powerpc64", "riscv64", "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; } /* $Id: att.c,v 1.19 2021/09/04 20:26:43 schwarze Exp $ */ /* * Copyright (c) 2009 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 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 "roff.h" #include "libmdoc.h" #define LINE(x, y) \ if (0 == strcmp(p, x)) return(y) const char * mdoc_a2att(const char *p) { LINE("v1", "Version\\~1 AT&T UNIX"); LINE("v2", "Version\\~2 AT&T UNIX"); LINE("v3", "Version\\~3 AT&T UNIX"); LINE("v4", "Version\\~4 AT&T UNIX"); LINE("v5", "Version\\~5 AT&T UNIX"); LINE("v6", "Version\\~6 AT&T UNIX"); LINE("v7", "Version\\~7 AT&T UNIX"); LINE("32v", "Version\\~7 AT&T UNIX/32V"); LINE("III", "AT&T System\\~III UNIX"); LINE("V", "AT&T System\\~V UNIX"); LINE("V.1", "AT&T System\\~V Release\\~1 UNIX"); LINE("V.2", "AT&T System\\~V Release\\~2 UNIX"); LINE("V.3", "AT&T System\\~V Release\\~3 UNIX"); LINE("V.4", "AT&T System\\~V Release\\~4 UNIX"); return NULL; } /* $Id: chars.c,v 1.79 2020/02/13 16:18:29 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2020 * 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 #include "mandoc.h" #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "libmandoc.h" struct ln { const char roffcode[16]; const char *ascii; int unicode; }; /* Special break control characters. */ static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' }; static const char ascii_break[2] = { ASCII_BREAK, '\0' }; static struct ln lines[] = { /* Spacing. */ { " ", ascii_nbrsp, 0x00a0 }, { "~", ascii_nbrsp, 0x00a0 }, { "0", ascii_nbrsp, 0x00a0 }, { ":", ascii_break, 0 }, /* Lines. */ { "ba", "|", 0x007c }, { "br", "|", 0x2502 }, { "ul", "_", 0x005f }, { "_", "_", 0x005f }, { "ru", "_", 0x005f }, { "rn", "-", 0x203e }, { "bb", "|", 0x00a6 }, { "sl", "/", 0x002f }, { "rs", "\\", 0x005c }, /* Text markers. */ { "ci", "O", 0x25cb }, { "bu", "+\bo", 0x2022 }, { "dd", "<**>", 0x2021 }, { "dg", "<*>", 0x2020 }, { "lz", "<>", 0x25ca }, { "sq", "[]", 0x25a1 }, { "ps", "", 0x00b6 }, { "sc", "
", 0x00a7 }, { "lh", "<=", 0x261c }, { "rh", "=>", 0x261e }, { "at", "@", 0x0040 }, { "sh", "#", 0x0023 }, { "CR", "", 0x21b5 }, { "OK", "\\/", 0x2713 }, { "CL", "C", 0x2663 }, { "SP", "S", 0x2660 }, { "HE", "H", 0x2665 }, { "DI", "D", 0x2666 }, /* Legal symbols. */ { "co", "(C)", 0x00a9 }, { "rg", "(R)", 0x00ae }, { "tm", "tm", 0x2122 }, /* Punctuation. */ { "em", "--", 0x2014 }, { "en", "-", 0x2013 }, { "hy", "-", 0x2010 }, { "e", "\\", 0x005c }, { ".", ".", 0x002e }, { "r!", "!", 0x00a1 }, { "r?", "?", 0x00bf }, /* Quotes. */ { "Bq", ",,", 0x201e }, { "bq", ",", 0x201a }, { "lq", "\"", 0x201c }, { "rq", "\"", 0x201d }, { "Lq", "\"", 0x201c }, { "Rq", "\"", 0x201d }, { "oq", "`", 0x2018 }, { "cq", "\'", 0x2019 }, { "aq", "\'", 0x0027 }, { "dq", "\"", 0x0022 }, { "Fo", "<<", 0x00ab }, { "Fc", ">>", 0x00bb }, { "fo", "<", 0x2039 }, { "fc", ">", 0x203a }, /* Brackets. */ { "lB", "[", 0x005b }, { "rB", "]", 0x005d }, { "lC", "{", 0x007b }, { "rC", "}", 0x007d }, { "la", "<", 0x27e8 }, { "ra", ">", 0x27e9 }, { "bv", "|", 0x23aa }, { "braceex", "|", 0x23aa }, { "bracketlefttp", "|", 0x23a1 }, { "bracketleftbt", "|", 0x23a3 }, { "bracketleftex", "|", 0x23a2 }, { "bracketrighttp", "|", 0x23a4 }, { "bracketrightbt", "|", 0x23a6 }, { "bracketrightex", "|", 0x23a5 }, { "lt", ",-", 0x23a7 }, { "bracelefttp", ",-", 0x23a7 }, { "lk", "{", 0x23a8 }, { "braceleftmid", "{", 0x23a8 }, { "lb", "`-", 0x23a9 }, { "braceleftbt", "`-", 0x23a9 }, { "braceleftex", "|", 0x23aa }, { "rt", "-.", 0x23ab }, { "bracerighttp", "-.", 0x23ab }, { "rk", "}", 0x23ac }, { "bracerightmid", "}", 0x23ac }, { "rb", "-\'", 0x23ad }, { "bracerightbt", "-\'", 0x23ad }, { "bracerightex", "|", 0x23aa }, { "parenlefttp", "/", 0x239b }, { "parenleftbt", "\\", 0x239d }, { "parenleftex", "|", 0x239c }, { "parenrighttp", "\\", 0x239e }, { "parenrightbt", "/", 0x23a0 }, { "parenrightex", "|", 0x239f }, /* Arrows and lines. */ { "<-", "<-", 0x2190 }, { "->", "->", 0x2192 }, { "<>", "<->", 0x2194 }, { "da", "|\bv", 0x2193 }, { "ua", "|\b^", 0x2191 }, { "va", "^v", 0x2195 }, { "lA", "<=", 0x21d0 }, { "rA", "=>", 0x21d2 }, { "hA", "<=>", 0x21d4 }, { "uA", "=\b^", 0x21d1 }, { "dA", "=\bv", 0x21d3 }, { "vA", "^=v", 0x21d5 }, { "an", "-", 0x23af }, /* Logic. */ { "AN", "^", 0x2227 }, { "OR", "v", 0x2228 }, { "no", "~", 0x00ac }, { "tno", "~", 0x00ac }, { "te", "", 0x2203 }, { "fa", "", 0x2200 }, { "st", "", 0x220b }, { "tf", "", 0x2234 }, { "3d", "", 0x2234 }, { "or", "|", 0x007c }, /* Mathematicals. */ { "pl", "+", 0x002b }, { "mi", "-", 0x2212 }, { "-", "-", 0x002d }, { "-+", "-+", 0x2213 }, { "+-", "+-", 0x00b1 }, { "t+-", "+-", 0x00b1 }, { "pc", ".", 0x00b7 }, { "md", ".", 0x22c5 }, { "mu", "x", 0x00d7 }, { "tmu", "x", 0x00d7 }, { "c*", "O\bx", 0x2297 }, { "c+", "O\b+", 0x2295 }, { "di", "/", 0x00f7 }, { "tdi", "/", 0x00f7 }, { "f/", "/", 0x2044 }, { "**", "*", 0x2217 }, { "<=", "<=", 0x2264 }, { ">=", ">=", 0x2265 }, { "<<", "<<", 0x226a }, { ">>", ">>", 0x226b }, { "eq", "=", 0x003d }, { "!=", "!=", 0x2260 }, { "==", "==", 0x2261 }, { "ne", "!==", 0x2262 }, { "ap", "~", 0x223c }, { "|=", "-~", 0x2243 }, { "=~", "=~", 0x2245 }, { "~~", "~~", 0x2248 }, { "~=", "~=", 0x2248 }, { "pt", "", 0x221d }, { "es", "{}", 0x2205 }, { "mo", "", 0x2208 }, { "nm", "", 0x2209 }, { "sb", "", 0x2282 }, { "nb", "", 0x2284 }, { "sp", "", 0x2283 }, { "nc", "", 0x2285 }, { "ib", "", 0x2286 }, { "ip", "", 0x2287 }, { "ca", "", 0x2229 }, { "cu", "", 0x222a }, { "/_", "", 0x2220 }, { "pp", "", 0x22a5 }, { "is", "", 0x222b }, { "integral", "", 0x222b }, { "sum", "", 0x2211 }, { "product", "", 0x220f }, { "coproduct", "", 0x2210 }, { "gr", "", 0x2207 }, { "sr", "", 0x221a }, { "sqrt", "", 0x221a }, { "lc", "|~", 0x2308 }, { "rc", "~|", 0x2309 }, { "lf", "|_", 0x230a }, { "rf", "_|", 0x230b }, { "if", "", 0x221e }, { "Ah", "", 0x2135 }, { "Im", "", 0x2111 }, { "Re", "", 0x211c }, { "wp", "p", 0x2118 }, { "pd", "", 0x2202 }, { "-h", "/h", 0x210f }, { "hbar", "/h", 0x210f }, { "12", "1/2", 0x00bd }, { "14", "1/4", 0x00bc }, { "34", "3/4", 0x00be }, { "18", "1/8", 0x215B }, { "38", "3/8", 0x215C }, { "58", "5/8", 0x215D }, { "78", "7/8", 0x215E }, { "S1", "^1", 0x00B9 }, { "S2", "^2", 0x00B2 }, { "S3", "^3", 0x00B3 }, /* Ligatures. */ { "ff", "ff", 0xfb00 }, { "fi", "fi", 0xfb01 }, { "fl", "fl", 0xfb02 }, { "Fi", "ffi", 0xfb03 }, { "Fl", "ffl", 0xfb04 }, { "AE", "AE", 0x00c6 }, { "ae", "ae", 0x00e6 }, { "OE", "OE", 0x0152 }, { "oe", "oe", 0x0153 }, { "ss", "ss", 0x00df }, { "IJ", "IJ", 0x0132 }, { "ij", "ij", 0x0133 }, /* Accents. */ { "a\"", "\"", 0x02dd }, { "a-", "-", 0x00af }, { "a.", ".", 0x02d9 }, { "a^", "^", 0x005e }, { "aa", "\'", 0x00b4 }, { "\'", "\'", 0x00b4 }, { "ga", "`", 0x0060 }, { "`", "`", 0x0060 }, { "ab", "'\b`", 0x02d8 }, { "ac", ",", 0x00b8 }, { "ad", "\"", 0x00a8 }, { "ah", "v", 0x02c7 }, { "ao", "o", 0x02da }, { "a~", "~", 0x007e }, { "ho", ",", 0x02db }, { "ha", "^", 0x005e }, { "ti", "~", 0x007e }, { "u02DC", "~", 0x02dc }, /* Accented letters. */ { "'A", "'\bA", 0x00c1 }, { "'E", "'\bE", 0x00c9 }, { "'I", "'\bI", 0x00cd }, { "'O", "'\bO", 0x00d3 }, { "'U", "'\bU", 0x00da }, { "'Y", "'\bY", 0x00dd }, { "'a", "'\ba", 0x00e1 }, { "'e", "'\be", 0x00e9 }, { "'i", "'\bi", 0x00ed }, { "'o", "'\bo", 0x00f3 }, { "'u", "'\bu", 0x00fa }, { "'y", "'\by", 0x00fd }, { "`A", "`\bA", 0x00c0 }, { "`E", "`\bE", 0x00c8 }, { "`I", "`\bI", 0x00cc }, { "`O", "`\bO", 0x00d2 }, { "`U", "`\bU", 0x00d9 }, { "`a", "`\ba", 0x00e0 }, { "`e", "`\be", 0x00e8 }, { "`i", "`\bi", 0x00ec }, { "`o", "`\bo", 0x00f2 }, { "`u", "`\bu", 0x00f9 }, { "~A", "~\bA", 0x00c3 }, { "~N", "~\bN", 0x00d1 }, { "~O", "~\bO", 0x00d5 }, { "~a", "~\ba", 0x00e3 }, { "~n", "~\bn", 0x00f1 }, { "~o", "~\bo", 0x00f5 }, { ":A", "\"\bA", 0x00c4 }, { ":E", "\"\bE", 0x00cb }, { ":I", "\"\bI", 0x00cf }, { ":O", "\"\bO", 0x00d6 }, { ":U", "\"\bU", 0x00dc }, { ":a", "\"\ba", 0x00e4 }, { ":e", "\"\be", 0x00eb }, { ":i", "\"\bi", 0x00ef }, { ":o", "\"\bo", 0x00f6 }, { ":u", "\"\bu", 0x00fc }, { ":y", "\"\by", 0x00ff }, { "^A", "^\bA", 0x00c2 }, { "^E", "^\bE", 0x00ca }, { "^I", "^\bI", 0x00ce }, { "^O", "^\bO", 0x00d4 }, { "^U", "^\bU", 0x00db }, { "^a", "^\ba", 0x00e2 }, { "^e", "^\be", 0x00ea }, { "^i", "^\bi", 0x00ee }, { "^o", "^\bo", 0x00f4 }, { "^u", "^\bu", 0x00fb }, { ",C", ",\bC", 0x00c7 }, { ",c", ",\bc", 0x00e7 }, { "/L", "/\bL", 0x0141 }, { "/l", "/\bl", 0x0142 }, { "/O", "/\bO", 0x00d8 }, { "/o", "/\bo", 0x00f8 }, { "oA", "o\bA", 0x00c5 }, { "oa", "o\ba", 0x00e5 }, /* Special letters. */ { "-D", "Dh", 0x00d0 }, { "Sd", "dh", 0x00f0 }, { "TP", "Th", 0x00de }, { "Tp", "th", 0x00fe }, { ".i", "i", 0x0131 }, { ".j", "j", 0x0237 }, /* Currency. */ { "Do", "$", 0x0024 }, { "ct", "/\bc", 0x00a2 }, { "Eu", "EUR", 0x20ac }, { "eu", "EUR", 0x20ac }, { "Ye", "=\bY", 0x00a5 }, { "Po", "-\bL", 0x00a3 }, { "Cs", "o\bx", 0x00a4 }, { "Fn", ",\bf", 0x0192 }, /* Units. */ { "de", "", 0x00b0 }, { "%0", "", 0x2030 }, { "fm", "\'", 0x2032 }, { "sd", "''", 0x2033 }, { "mc", "", 0x00b5 }, { "Of", "_\ba", 0x00aa }, { "Om", "_\bo", 0x00ba }, /* Greek characters. */ { "*A", "A", 0x0391 }, { "*B", "B", 0x0392 }, { "*G", "", 0x0393 }, { "*D", "", 0x0394 }, { "*E", "E", 0x0395 }, { "*Z", "Z", 0x0396 }, { "*Y", "H", 0x0397 }, { "*H", "", 0x0398 }, { "*I", "I", 0x0399 }, { "*K", "K", 0x039a }, { "*L", "", 0x039b }, { "*M", "M", 0x039c }, { "*N", "N", 0x039d }, { "*C", "", 0x039e }, { "*O", "O", 0x039f }, { "*P", "", 0x03a0 }, { "*R", "P", 0x03a1 }, { "*S", "", 0x03a3 }, { "*T", "T", 0x03a4 }, { "*U", "Y", 0x03a5 }, { "*F", "", 0x03a6 }, { "*X", "X", 0x03a7 }, { "*Q", "", 0x03a8 }, { "*W", "", 0x03a9 }, { "*a", "", 0x03b1 }, { "*b", "", 0x03b2 }, { "*g", "", 0x03b3 }, { "*d", "", 0x03b4 }, { "*e", "", 0x03b5 }, { "*z", "", 0x03b6 }, { "*y", "", 0x03b7 }, { "*h", "", 0x03b8 }, { "*i", "", 0x03b9 }, { "*k", "", 0x03ba }, { "*l", "", 0x03bb }, { "*m", "", 0x03bc }, { "*n", "", 0x03bd }, { "*c", "", 0x03be }, { "*o", "o", 0x03bf }, { "*p", "", 0x03c0 }, { "*r", "", 0x03c1 }, { "*s", "", 0x03c3 }, { "*t", "", 0x03c4 }, { "*u", "", 0x03c5 }, { "*f", "", 0x03d5 }, { "*x", "", 0x03c7 }, { "*q", "", 0x03c8 }, { "*w", "", 0x03c9 }, { "+h", "", 0x03d1 }, { "+f", "", 0x03c6 }, { "+p", "", 0x03d6 }, { "+e", "", 0x03f5 }, { "ts", "", 0x03c2 }, }; static struct ohash mchars; void mchars_free(void) { ohash_delete(&mchars); } void mchars_alloc(void) { size_t i; unsigned int slot; mandoc_ohash_init(&mchars, 9, offsetof(struct ln, roffcode)); for (i = 0; i < sizeof(lines)/sizeof(lines[0]); i++) { slot = ohash_qlookup(&mchars, lines[i].roffcode); assert(ohash_find(&mchars, slot) == NULL); ohash_insert(&mchars, slot, lines + i); } } int mchars_spec2cp(const char *p, size_t sz) { const struct ln *ln; const char *end; end = p + sz; ln = ohash_find(&mchars, ohash_qlookupi(&mchars, p, &end)); return ln != NULL ? ln->unicode : -1; } int mchars_num2char(const char *p, size_t sz) { int i; i = mandoc_strntoi(p, sz, 10); return i >= 0 && i < 256 ? i : -1; } int mchars_num2uc(const char *p, size_t sz) { int i; i = mandoc_strntoi(p, sz, 16); assert(i >= 0 && i <= 0x10FFFF); return i; } const char * mchars_spec2str(const char *p, size_t sz, size_t *rsz) { const struct ln *ln; const char *end; end = p + sz; ln = ohash_find(&mchars, ohash_qlookupi(&mchars, p, &end)); if (ln == NULL) return NULL; *rsz = strlen(ln->ascii); return ln->ascii; } const char * mchars_uc2str(int uc) { size_t i; for (i = 0; i < sizeof(lines)/sizeof(lines[0]); i++) if (uc == lines[i].unicode) return lines[i].ascii; return ""; } /* $Id: compat_ohash.c,v 1.7 2020/06/15 01:37:15 schwarze Exp $ */ /* $OpenBSD: ohash.c,v 1.1 2014/06/02 18:52:03 deraadt Exp $ */ /* Copyright (c) 1999, 2004 Marc Espie * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 "compat_ohash.h" struct _ohash_record { uint32_t hv; const char *p; }; #define DELETED ((const char *)h) #define NONE (h->size) /* Don't bother changing the hash table if the change is small enough. */ #define MINSIZE (1UL << 4) #define MINDELETED 4 static void ohash_resize(struct ohash *); /* This handles the common case of variable length keys, where the * key is stored at the end of the record. */ void * ohash_create_entry(struct ohash_info *i, const char *start, const char **end) { char *p; if (!*end) *end = start + strlen(start); p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data); if (p) { memcpy(p+i->key_offset, start, *end-start); p[i->key_offset + (*end - start)] = '\0'; } return (void *)p; } /* hash_delete only frees the hash structure. Use hash_first/hash_next * to free entries as well. */ void ohash_delete(struct ohash *h) { (h->info.free)(h->t, h->info.data); #ifndef NDEBUG h->t = NULL; #endif } static void ohash_resize(struct ohash *h) { struct _ohash_record *n; size_t ns; unsigned int j; unsigned int i, incr; if (4 * h->deleted < h->total) { if (h->size >= (UINT_MAX >> 1U)) ns = UINT_MAX; else ns = h->size << 1U; } else if (3 * h->deleted > 2 * h->total) ns = h->size >> 1U; else ns = h->size; if (ns < MINSIZE) ns = MINSIZE; #ifdef STATS_HASH STAT_HASH_EXPAND++; STAT_HASH_SIZE += ns - h->size; #endif n = (h->info.calloc)(ns, sizeof(struct _ohash_record), h->info.data); if (!n) return; for (j = 0; j < h->size; j++) { if (h->t[j].p != NULL && h->t[j].p != DELETED) { i = h->t[j].hv % ns; incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1; while (n[i].p != NULL) { i += incr; if (i >= ns) i -= ns; } n[i].hv = h->t[j].hv; n[i].p = h->t[j].p; } } (h->info.free)(h->t, h->info.data); h->t = n; h->size = ns; h->total -= h->deleted; h->deleted = 0; } void * ohash_remove(struct ohash *h, unsigned int i) { void *result = (void *)h->t[i].p; if (result == NULL || result == DELETED) return NULL; #ifdef STATS_HASH STAT_HASH_ENTRIES--; #endif h->t[i].p = DELETED; h->deleted++; if (h->deleted >= MINDELETED && 4 * h->deleted > h->total) ohash_resize(h); return result; } void * ohash_find(struct ohash *h, unsigned int i) { if (h->t[i].p == DELETED) return NULL; else return (void *)h->t[i].p; } void * ohash_insert(struct ohash *h, unsigned int i, void *p) { #ifdef STATS_HASH STAT_HASH_ENTRIES++; #endif if (h->t[i].p == DELETED) { h->deleted--; h->t[i].p = p; } else { h->t[i].p = p; /* Arbitrary resize boundary. Tweak if not efficient enough. */ if (++h->total * 4 > h->size * 3) ohash_resize(h); } return p; } unsigned int ohash_entries(struct ohash *h) { return h->total - h->deleted; } void * ohash_first(struct ohash *h, unsigned int *pos) { *pos = 0; return ohash_next(h, pos); } void * ohash_next(struct ohash *h, unsigned int *pos) { for (; *pos < h->size; (*pos)++) if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL) return (void *)h->t[(*pos)++].p; return NULL; } void ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info) { h->size = 1UL << size; if (h->size < MINSIZE) h->size = MINSIZE; #ifdef STATS_HASH STAT_HASH_CREATION++; STAT_HASH_SIZE += h->size; #endif /* Copy info so that caller may free it. */ h->info.key_offset = info->key_offset; h->info.calloc = info->calloc; h->info.free = info->free; h->info.alloc = info->alloc; h->info.data = info->data; h->t = (h->info.calloc)(h->size, sizeof(struct _ohash_record), h->info.data); h->total = h->deleted = 0; } uint32_t ohash_interval(const char *s, const char **e) { uint32_t k; if (!*e) *e = s + strlen(s); if (s == *e) k = 0; else k = *s++; while (s != *e) k = ((k << 2) | (k >> 30)) ^ *s++; return k; } unsigned int ohash_lookup_interval(struct ohash *h, const char *start, const char *end, uint32_t hv) { unsigned int i, incr; unsigned int empty; #ifdef STATS_HASH STAT_HASH_LOOKUP++; #endif empty = NONE; i = hv % h->size; incr = ((hv % (h->size-2)) & ~1) + 1; while (h->t[i].p != NULL) { #ifdef STATS_HASH STAT_HASH_LENGTH++; #endif if (h->t[i].p == DELETED) { if (empty == NONE) empty = i; } else if (h->t[i].hv == hv && strncmp(h->t[i].p+h->info.key_offset, start, end - start) == 0 && (h->t[i].p+h->info.key_offset)[end-start] == '\0') { if (empty != NONE) { h->t[empty].hv = hv; h->t[empty].p = h->t[i].p; h->t[i].p = DELETED; return empty; } else { #ifdef STATS_HASH STAT_HASH_POSITIVE++; #endif return i; } } i += incr; if (i >= h->size) i -= h->size; } /* Found an empty position. */ if (empty != NONE) i = empty; h->t[i].hv = hv; return i; } unsigned int ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv) { unsigned int i, incr; unsigned int empty; #ifdef STATS_HASH STAT_HASH_LOOKUP++; #endif empty = NONE; i = hv % h->size; incr = ((hv % (h->size-2)) & ~1) + 1; while (h->t[i].p != NULL) { #ifdef STATS_HASH STAT_HASH_LENGTH++; #endif if (h->t[i].p == DELETED) { if (empty == NONE) empty = i; } else if (h->t[i].hv == hv && memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) { if (empty != NONE) { h->t[empty].hv = hv; h->t[empty].p = h->t[i].p; h->t[i].p = DELETED; return empty; } else { #ifdef STATS_HASH STAT_HASH_POSITIVE++; #endif } return i; } i += incr; if (i >= h->size) i -= h->size; } /* Found an empty position. */ if (empty != NONE) i = empty; h->t[i].hv = hv; return i; } unsigned int ohash_qlookup(struct ohash *h, const char *s) { const char *e = NULL; return ohash_qlookupi(h, s, &e); } unsigned int ohash_qlookupi(struct ohash *h, const char *s, const char **e) { uint32_t hv; hv = ohash_interval(s, e); return ohash_lookup_interval(h, s, *e, hv); } /* $OpenBSD: ohash.h,v 1.2 2014/06/02 18:52:03 deraadt Exp $ */ /* Copyright (c) 1999, 2004 Marc Espie * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef OHASH_H #define OHASH_H /* Open hashing support. * Open hashing was chosen because it is much lighter than other hash * techniques, and more efficient in most cases. */ /* user-visible data structure */ struct ohash_info { ptrdiff_t key_offset; void *data; /* user data */ void *(*calloc)(size_t, size_t, void *); void (*free)(void *, void *); void *(*alloc)(size_t, void *); }; struct _ohash_record; /* private structure. It's there just so you can do a sizeof */ struct ohash { struct _ohash_record *t; struct ohash_info info; unsigned int size; unsigned int total; unsigned int deleted; }; /* For this to be tweakable, we use small primitives, and leave part of the * logic to the client application. e.g., hashing is left to the client * application. We also provide a simple table entry lookup that yields * a hashing table index (opaque) to be used in find/insert/remove. * The keys are stored at a known position in the client data. */ void ohash_init(struct ohash *, unsigned, struct ohash_info *); void ohash_delete(struct ohash *); unsigned int ohash_lookup_interval(struct ohash *, const char *, const char *, uint32_t); unsigned int ohash_lookup_memory(struct ohash *, const char *, size_t, uint32_t); void *ohash_find(struct ohash *, unsigned int); void *ohash_remove(struct ohash *, unsigned int); void *ohash_insert(struct ohash *, unsigned int, void *); void *ohash_first(struct ohash *, unsigned int *); void *ohash_next(struct ohash *, unsigned int *); unsigned int ohash_entries(struct ohash *); void *ohash_create_entry(struct ohash_info *, const char *, const char **); uint32_t ohash_interval(const char *, const char **); unsigned int ohash_qlookupi(struct ohash *, const char *, const char **); unsigned int ohash_qlookup(struct ohash *, const char *); #endif /* $Id: compat_stringlist.c,v 1.8 2020/06/15 21:48:09 schwarze Exp $ */ /* $NetBSD: stringlist.c,v 1.14 2015/05/21 01:29:13 christos Exp $ */ /*- * Copyright (c) 1994, 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include "compat_stringlist.h" #define _SL_CHUNKSIZE 20 /* * sl_init(): Initialize a string list */ StringList * sl_init(void) { StringList *sl; sl = malloc(sizeof(StringList)); if (sl == NULL) return NULL; sl->sl_cur = 0; sl->sl_max = _SL_CHUNKSIZE; sl->sl_str = reallocarray(NULL, sl->sl_max, sizeof(char *)); if (sl->sl_str == NULL) { free(sl); sl = NULL; } return sl; } /* * sl_add(): Add an item to the string list */ int sl_add(StringList *sl, char *name) { if (sl->sl_cur == sl->sl_max - 1) { char **new; new = reallocarray(sl->sl_str, (sl->sl_max + _SL_CHUNKSIZE), sizeof(char *)); if (new == NULL) return -1; sl->sl_max += _SL_CHUNKSIZE; sl->sl_str = new; } sl->sl_str[sl->sl_cur++] = name; return 0; } /* * sl_free(): Free a stringlist */ void sl_free(StringList *sl, int all) { size_t i; if (sl == NULL) return; if (sl->sl_str) { if (all) for (i = 0; i < sl->sl_cur; i++) free(sl->sl_str[i]); free(sl->sl_str); } free(sl); } /* * sl_find(): Find a name in the string list */ char * sl_find(StringList *sl, const char *name) { size_t i; for (i = 0; i < sl->sl_cur; i++) if (strcmp(sl->sl_str[i], name) == 0) return sl->sl_str[i]; return NULL; } int sl_delete(StringList *sl, const char *name, int all) { size_t i, j; for (i = 0; i < sl->sl_cur; i++) if (strcmp(sl->sl_str[i], name) == 0) { if (all) free(sl->sl_str[i]); for (j = i + 1; j < sl->sl_cur; j++) sl->sl_str[j - 1] = sl->sl_str[j]; sl->sl_str[--sl->sl_cur] = NULL; return 0; } return -1; } /* $Id: compat_stringlist.h,v 1.5 2020/06/15 21:48:09 schwarze Exp $ */ /* $NetBSD: stringlist.h,v 1.7 2008/04/28 20:22:54 martin Exp $ */ /*- * Copyright (c) 1994 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include /* * Simple string list */ typedef struct _stringlist { char **sl_str; size_t sl_max; size_t sl_cur; } StringList; StringList *sl_init(void); int sl_add(StringList *, char *); void sl_free(StringList *, int); char *sl_find(StringList *, const char *); int sl_delete(StringList *, const char *, int); #ifndef MANDOC_CONFIG_H #define MANDOC_CONFIG_H #include /* * The tools build may be on a system without O_DIRECTORY. So we need to * explicitly include sys/fcntl.h and check for O_DIRECTORY and if not present, * use the default of it being zero. */ #include #ifndef O_DIRECTORY #define O_DIRECTORY 0 #endif #ifdef __cplusplus #error "Do not use C++. See the INSTALL file." #endif #define MAN_CONF_FILE "/etc/man.conf" #define MANPATH_BASE "/usr/share/man" #define MANPATH_DEFAULT "/usr/share/man" #define OSENUM MANDOC_OS_OTHER #define UTF8_LOCALE "en_US.UTF-8" #define EFTYPE EINVAL #define HAVE_DIRENT_NAMLEN 0 #define HAVE_ENDIAN 1 #define HAVE_ERR 1 #define HAVE_FTS 1 #define HAVE_FTS_COMPARE_CONST 0 #define HAVE_GETLINE 1 #define HAVE_GETSUBOPT 1 #define HAVE_ISBLANK 1 #define HAVE_LESS_T 1 #define HAVE_MKDTEMP 1 #define HAVE_MKSTEMPS 1 #define HAVE_NTOHL 1 #define HAVE_PLEDGE 0 #define HAVE_PROGNAME 1 #define HAVE_REALLOCARRAY 1 #define HAVE_RECALLOCARRAY 1 #define HAVE_REWB_BSD 1 #define HAVE_REWB_SYSV 1 #define HAVE_SANDBOX_INIT 0 #define HAVE_STRCASESTR 1 #define HAVE_STRINGLIST 0 #define HAVE_STRLCAT 1 #define HAVE_STRLCPY 1 #define HAVE_STRNDUP 1 #define HAVE_STRPTIME 1 #define HAVE_STRSEP 1 #define HAVE_STRTONUM 1 #define HAVE_SYS_ENDIAN 0 #define HAVE_VASPRINTF 1 #define HAVE_WCHAR 1 #define HAVE_OHASH 0 #define NEED_XPG4_2 1 #define BINM_APROPOS "apropos" #define BINM_MAKEWHATIS "man -w" #define BINM_CATMAN "catman" #define BINM_MAN "man" #define BINM_SOELIM "soelim" #define BINM_WHATIS "whatis" #define BINM_PAGER "less" #endif /* MANDOC_CONFIG_H */ /* $Id: dba.c,v 1.10 2017/02/17 14:43:54 schwarze Exp $ */ /* * Copyright (c) 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. * * Allocation-based version of the mandoc database, for read-write access. * The interface is defined in "dba.h". */ #include "config.h" #include #if HAVE_ENDIAN #include #elif HAVE_SYS_ENDIAN #include #elif HAVE_NTOHL #include #endif #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "mansearch.h" #include "dba_write.h" #include "dba_array.h" #include "dba.h" struct macro_entry { struct dba_array *pages; char value[]; }; static void *prepend(const char *, char); static void dba_pages_write(struct dba_array *); static int compare_names(const void *, const void *); static int compare_strings(const void *, const void *); static struct macro_entry *get_macro_entry(struct ohash *, const char *, int32_t); static void dba_macros_write(struct dba_array *); static void dba_macro_write(struct ohash *); static int compare_entries(const void *, const void *); /*** top-level functions **********************************************/ struct dba * dba_new(int32_t npages) { struct dba *dba; struct ohash *macro; int32_t im; dba = mandoc_malloc(sizeof(*dba)); dba->pages = dba_array_new(npages, DBA_GROW); dba->macros = dba_array_new(MACRO_MAX, 0); for (im = 0; im < MACRO_MAX; im++) { macro = mandoc_malloc(sizeof(*macro)); mandoc_ohash_init(macro, 4, offsetof(struct macro_entry, value)); dba_array_set(dba->macros, im, macro); } return dba; } void dba_free(struct dba *dba) { struct dba_array *page; struct ohash *macro; struct macro_entry *entry; unsigned int slot; dba_array_FOREACH(dba->macros, macro) { for (entry = ohash_first(macro, &slot); entry != NULL; entry = ohash_next(macro, &slot)) { dba_array_free(entry->pages); free(entry); } ohash_delete(macro); free(macro); } dba_array_free(dba->macros); dba_array_undel(dba->pages); dba_array_FOREACH(dba->pages, page) { dba_array_free(dba_array_get(page, DBP_NAME)); dba_array_free(dba_array_get(page, DBP_SECT)); dba_array_free(dba_array_get(page, DBP_ARCH)); free(dba_array_get(page, DBP_DESC)); dba_array_free(dba_array_get(page, DBP_FILE)); dba_array_free(page); } dba_array_free(dba->pages); free(dba); } /* * Write the complete mandoc database to disk; the format is: * - One integer each for magic and version. * - One pointer each to the macros table and to the final magic. * - The pages table. * - The macros table. * - And at the very end, the magic integer again. */ int dba_write(const char *fname, struct dba *dba) { int save_errno; int32_t pos_end, pos_macros, pos_macros_ptr; if (dba_open(fname) == -1) return -1; dba_int_write(MANDOCDB_MAGIC); dba_int_write(MANDOCDB_VERSION); pos_macros_ptr = dba_skip(1, 2); dba_pages_write(dba->pages); pos_macros = dba_tell(); dba_macros_write(dba->macros); pos_end = dba_tell(); dba_int_write(MANDOCDB_MAGIC); dba_seek(pos_macros_ptr); dba_int_write(pos_macros); dba_int_write(pos_end); if (dba_close() == -1) { save_errno = errno; unlink(fname); errno = save_errno; return -1; } return 0; } /*** functions for handling pages *************************************/ /* * Create a new page and append it to the pages table. */ struct dba_array * dba_page_new(struct dba_array *pages, const char *arch, const char *desc, const char *file, enum form form) { struct dba_array *page, *entry; page = dba_array_new(DBP_MAX, 0); entry = dba_array_new(1, DBA_STR | DBA_GROW); dba_array_add(page, entry); entry = dba_array_new(1, DBA_STR | DBA_GROW); dba_array_add(page, entry); if (arch != NULL && *arch != '\0') { entry = dba_array_new(1, DBA_STR | DBA_GROW); dba_array_add(entry, (void *)arch); } else entry = NULL; dba_array_add(page, entry); dba_array_add(page, mandoc_strdup(desc)); entry = dba_array_new(1, DBA_STR | DBA_GROW); dba_array_add(entry, prepend(file, form)); dba_array_add(page, entry); dba_array_add(pages, page); return page; } /* * Add a section, architecture, or file name to an existing page. * Passing the NULL pointer for the architecture makes the page MI. * In that case, any earlier or later architectures are ignored. */ void dba_page_add(struct dba_array *page, int32_t ie, const char *str) { struct dba_array *entries; char *entry; entries = dba_array_get(page, ie); if (ie == DBP_ARCH) { if (entries == NULL) return; if (str == NULL || *str == '\0') { dba_array_free(entries); dba_array_set(page, DBP_ARCH, NULL); return; } } if (*str == '\0') return; dba_array_FOREACH(entries, entry) { if (ie == DBP_FILE && *entry < ' ') entry++; if (strcmp(entry, str) == 0) return; } dba_array_add(entries, (void *)str); } /* * Add an additional name to an existing page. */ void dba_page_alias(struct dba_array *page, const char *name, uint64_t mask) { struct dba_array *entries; char *entry; char maskbyte; if (*name == '\0') return; maskbyte = mask & NAME_MASK; entries = dba_array_get(page, DBP_NAME); dba_array_FOREACH(entries, entry) { if (strcmp(entry + 1, name) == 0) { *entry |= maskbyte; return; } } dba_array_add(entries, prepend(name, maskbyte)); } /* * Return a pointer to a temporary copy of instr with inbyte prepended. */ static void * prepend(const char *instr, char inbyte) { static char *outstr = NULL; static size_t outlen = 0; size_t newlen; newlen = strlen(instr) + 1; if (newlen > outlen) { outstr = mandoc_realloc(outstr, newlen + 1); outlen = newlen; } *outstr = inbyte; memcpy(outstr + 1, instr, newlen); return outstr; } /* * Write the pages table to disk; the format is: * - One integer containing the number of pages. * - For each page, five pointers to the names, sections, * architectures, description, and file names of the page. * MI pages write 0 instead of the architecture pointer. * - One list each for names, sections, architectures, descriptions and * file names. The description for each page ends with a NUL byte. * For all the other lists, each string ends with a NUL byte, * and the last string for a page ends with two NUL bytes. * - To assure alignment of following integers, * the end is padded with NUL bytes up to a multiple of four bytes. */ static void dba_pages_write(struct dba_array *pages) { struct dba_array *page, *entry; int32_t pos_pages, pos_end; pos_pages = dba_array_writelen(pages, 5); dba_array_FOREACH(pages, page) { dba_array_setpos(page, DBP_NAME, dba_tell()); entry = dba_array_get(page, DBP_NAME); dba_array_sort(entry, compare_names); dba_array_writelst(entry); } dba_array_FOREACH(pages, page) { dba_array_setpos(page, DBP_SECT, dba_tell()); entry = dba_array_get(page, DBP_SECT); dba_array_sort(entry, compare_strings); dba_array_writelst(entry); } dba_array_FOREACH(pages, page) { if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) { dba_array_setpos(page, DBP_ARCH, dba_tell()); dba_array_sort(entry, compare_strings); dba_array_writelst(entry); } else dba_array_setpos(page, DBP_ARCH, 0); } dba_array_FOREACH(pages, page) { dba_array_setpos(page, DBP_DESC, dba_tell()); dba_str_write(dba_array_get(page, DBP_DESC)); } dba_array_FOREACH(pages, page) { dba_array_setpos(page, DBP_FILE, dba_tell()); dba_array_writelst(dba_array_get(page, DBP_FILE)); } pos_end = dba_align(); dba_seek(pos_pages); dba_array_FOREACH(pages, page) dba_array_writepos(page); dba_seek(pos_end); } static int compare_names(const void *vp1, const void *vp2) { const char *cp1, *cp2; int diff; cp1 = *(const char * const *)vp1; cp2 = *(const char * const *)vp2; return (diff = *cp2 - *cp1) ? diff : strcasecmp(cp1 + 1, cp2 + 1); } static int compare_strings(const void *vp1, const void *vp2) { const char *cp1, *cp2; cp1 = *(const char * const *)vp1; cp2 = *(const char * const *)vp2; return strcmp(cp1, cp2); } /*** functions for handling macros ************************************/ /* * In the hash table for a single macro, look up an entry by * the macro value or add an empty one if it doesn't exist yet. */ static struct macro_entry * get_macro_entry(struct ohash *macro, const char *value, int32_t np) { struct macro_entry *entry; size_t len; unsigned int slot; slot = ohash_qlookup(macro, value); if ((entry = ohash_find(macro, slot)) == NULL) { len = strlen(value) + 1; entry = mandoc_malloc(sizeof(*entry) + len); memcpy(&entry->value, value, len); entry->pages = dba_array_new(np, DBA_GROW); ohash_insert(macro, slot, entry); } return entry; } /* * In addition to get_macro_entry(), add multiple page references, * converting them from the on-disk format (byte offsets in the file) * to page pointers in memory. */ void dba_macro_new(struct dba *dba, int32_t im, const char *value, const int32_t *pp) { struct macro_entry *entry; const int32_t *ip; int32_t np; np = 0; for (ip = pp; *ip; ip++) np++; entry = get_macro_entry(dba_array_get(dba->macros, im), value, np); for (ip = pp; *ip; ip++) dba_array_add(entry->pages, dba_array_get(dba->pages, be32toh(*ip) / 5 / sizeof(*ip) - 1)); } /* * In addition to get_macro_entry(), add one page reference, * directly taking the in-memory page pointer as an argument. */ void dba_macro_add(struct dba_array *macros, int32_t im, const char *value, struct dba_array *page) { struct macro_entry *entry; if (*value == '\0') return; entry = get_macro_entry(dba_array_get(macros, im), value, 1); dba_array_add(entry->pages, page); } /* * Write the macros table to disk; the format is: * - The number of macro tables (actually, MACRO_MAX). * - That number of pointers to the individual macro tables. * - The individual macro tables. */ static void dba_macros_write(struct dba_array *macros) { struct ohash *macro; int32_t im, pos_macros, pos_end; pos_macros = dba_array_writelen(macros, 1); im = 0; dba_array_FOREACH(macros, macro) { dba_array_setpos(macros, im++, dba_tell()); dba_macro_write(macro); } pos_end = dba_tell(); dba_seek(pos_macros); dba_array_writepos(macros); dba_seek(pos_end); } /* * Write one individual macro table to disk; the format is: * - The number of entries in the table. * - For each entry, two pointers, the first one to the value * and the second one to the list of pages. * - A list of values, each ending in a NUL byte. * - To assure alignment of following integers, * padding with NUL bytes up to a multiple of four bytes. * - A list of pointers to pages, each list ending in a 0 integer. */ static void dba_macro_write(struct ohash *macro) { struct macro_entry **entries, *entry; struct dba_array *page; int32_t *kpos, *dpos; unsigned int ie, ne, slot; int use; int32_t addr, pos_macro, pos_end; /* Temporary storage for filtering and sorting. */ ne = ohash_entries(macro); entries = mandoc_reallocarray(NULL, ne, sizeof(*entries)); kpos = mandoc_reallocarray(NULL, ne, sizeof(*kpos)); dpos = mandoc_reallocarray(NULL, ne, sizeof(*dpos)); /* Build a list of non-empty entries and sort it. */ ne = 0; for (entry = ohash_first(macro, &slot); entry != NULL; entry = ohash_next(macro, &slot)) { use = 0; dba_array_FOREACH(entry->pages, page) if (dba_array_getpos(page)) use = 1; if (use) entries[ne++] = entry; } qsort(entries, ne, sizeof(*entries), compare_entries); /* Number of entries, and space for the pointer pairs. */ dba_int_write(ne); pos_macro = dba_skip(2, ne); /* String table. */ for (ie = 0; ie < ne; ie++) { kpos[ie] = dba_tell(); dba_str_write(entries[ie]->value); } dba_align(); /* Pages table. */ for (ie = 0; ie < ne; ie++) { dpos[ie] = dba_tell(); dba_array_FOREACH(entries[ie]->pages, page) if ((addr = dba_array_getpos(page))) dba_int_write(addr); dba_int_write(0); } pos_end = dba_tell(); /* Fill in the pointer pairs. */ dba_seek(pos_macro); for (ie = 0; ie < ne; ie++) { dba_int_write(kpos[ie]); dba_int_write(dpos[ie]); } dba_seek(pos_end); free(entries); free(kpos); free(dpos); } static int compare_entries(const void *vp1, const void *vp2) { const struct macro_entry *ep1, *ep2; ep1 = *(const struct macro_entry * const *)vp1; ep2 = *(const struct macro_entry * const *)vp2; return strcmp(ep1->value, ep2->value); } /* $Id: dba.h,v 1.2 2016/08/17 20:46:56 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. * * Public interface of the allocation-based version * of the mandoc database, for read-write access. * To be used by dba.c, dba_read.c, and makewhatis(8). */ #define DBP_NAME 0 #define DBP_SECT 1 #define DBP_ARCH 2 #define DBP_DESC 3 #define DBP_FILE 4 #define DBP_MAX 5 struct dba_array; struct dba { struct dba_array *pages; struct dba_array *macros; }; struct dba *dba_new(int32_t); void dba_free(struct dba *); struct dba *dba_read(const char *); int dba_write(const char *, struct dba *); struct dba_array *dba_page_new(struct dba_array *, const char *, const char *, const char *, enum form); void dba_page_add(struct dba_array *, int32_t, const char *); void dba_page_alias(struct dba_array *, const char *, uint64_t); void dba_macro_new(struct dba *, int32_t, const char *, const int32_t *); void dba_macro_add(struct dba_array *, int32_t, const char *, struct dba_array *); /* $Id: dba_array.c,v 1.2 2020/06/22 19:20:40 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. * * Allocation-based arrays for the mandoc database, for read-write access. * The interface is defined in "dba_array.h". */ #include "config.h" #include #include #include #include #include "mandoc_aux.h" #include "dba_write.h" #include "dba_array.h" struct dba_array { void **ep; /* Array of entries. */ int32_t *em; /* Array of map positions. */ int flags; int32_t ea; /* Entries allocated. */ int32_t eu; /* Entries used (including deleted). */ int32_t ed; /* Entries deleted. */ int32_t ec; /* Currently active entry. */ int32_t pos; /* Map position of this array. */ }; struct dba_array * dba_array_new(int32_t ea, int flags) { struct dba_array *array; assert(ea > 0); array = mandoc_malloc(sizeof(*array)); array->ep = mandoc_reallocarray(NULL, ea, sizeof(*array->ep)); array->em = mandoc_reallocarray(NULL, ea, sizeof(*array->em)); array->ea = ea; array->eu = 0; array->ed = 0; array->ec = 0; array->flags = flags; array->pos = 0; return array; } void dba_array_free(struct dba_array *array) { int32_t ie; if (array == NULL) return; if (array->flags & DBA_STR) for (ie = 0; ie < array->eu; ie++) free(array->ep[ie]); free(array->ep); free(array->em); free(array); } void dba_array_set(struct dba_array *array, int32_t ie, void *entry) { assert(ie >= 0); assert(ie < array->ea); assert(ie <= array->eu); if (ie == array->eu) array->eu++; if (array->flags & DBA_STR) entry = mandoc_strdup(entry); array->ep[ie] = entry; array->em[ie] = 0; } void dba_array_add(struct dba_array *array, void *entry) { if (array->eu == array->ea) { assert(array->flags & DBA_GROW); array->ep = mandoc_reallocarray(array->ep, 2, sizeof(*array->ep) * array->ea); array->em = mandoc_reallocarray(array->em, 2, sizeof(*array->em) * array->ea); array->ea *= 2; } dba_array_set(array, array->eu, entry); } void * dba_array_get(struct dba_array *array, int32_t ie) { if (ie < 0 || ie >= array->eu || array->em[ie] == -1) return NULL; return array->ep[ie]; } void dba_array_start(struct dba_array *array) { array->ec = array->eu; } void * dba_array_next(struct dba_array *array) { if (array->ec < array->eu) array->ec++; else array->ec = 0; while (array->ec < array->eu && array->em[array->ec] == -1) array->ec++; return array->ec < array->eu ? array->ep[array->ec] : NULL; } void dba_array_del(struct dba_array *array) { if (array->ec < array->eu && array->em[array->ec] != -1) { array->em[array->ec] = -1; array->ed++; } } void dba_array_undel(struct dba_array *array) { memset(array->em, 0, sizeof(*array->em) * array->eu); } void dba_array_setpos(struct dba_array *array, int32_t ie, int32_t pos) { array->em[ie] = pos; } int32_t dba_array_getpos(struct dba_array *array) { return array->pos; } void dba_array_sort(struct dba_array *array, dba_compare_func func) { assert(array->ed == 0); qsort(array->ep, array->eu, sizeof(*array->ep), func); } int32_t dba_array_writelen(struct dba_array *array, int32_t nmemb) { dba_int_write(array->eu - array->ed); return dba_skip(nmemb, array->eu - array->ed); } void dba_array_writepos(struct dba_array *array) { int32_t ie; array->pos = dba_tell(); for (ie = 0; ie < array->eu; ie++) if (array->em[ie] != -1) dba_int_write(array->em[ie]); } void dba_array_writelst(struct dba_array *array) { const char *str; dba_array_FOREACH(array, str) dba_str_write(str); dba_char_write('\0'); } /* $Id: dba_array.h,v 1.1 2016/07/19 21:31:55 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. * * Public interface for allocation-based arrays * for the mandoc database, for read-write access. * To be used by dba*.c and by makewhatis(8). */ struct dba_array; #define DBA_STR 0x01 /* Map contains strings, not pointers. */ #define DBA_GROW 0x02 /* Allow the array to grow. */ #define dba_array_FOREACH(a, e) \ dba_array_start(a); \ while (((e) = dba_array_next(a)) != NULL) typedef int dba_compare_func(const void *, const void *); struct dba_array *dba_array_new(int32_t, int); void dba_array_free(struct dba_array *); void dba_array_set(struct dba_array *, int32_t, void *); void dba_array_add(struct dba_array *, void *); void *dba_array_get(struct dba_array *, int32_t); void dba_array_start(struct dba_array *); void *dba_array_next(struct dba_array *); void dba_array_del(struct dba_array *); void dba_array_undel(struct dba_array *); void dba_array_setpos(struct dba_array *, int32_t, int32_t); int32_t dba_array_getpos(struct dba_array *); void dba_array_sort(struct dba_array *, dba_compare_func); int32_t dba_array_writelen(struct dba_array *, int32_t); void dba_array_writepos(struct dba_array *); void dba_array_writelst(struct dba_array *); /* $Id: dba_read.c,v 1.5 2020/06/22 19:20:40 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. * * Function to read the mandoc database from disk into RAM, * such that data can be added or removed. * The interface is defined in "dba.h". * This file is seperate from dba.c because this also uses "dbm.h". */ #include "config.h" #include #include #include #include #include #include "mandoc_aux.h" #include "mansearch.h" #include "dba_array.h" #include "dba.h" #include "dbm.h" struct dba * dba_read(const char *fname) { struct dba *dba; struct dba_array *page; struct dbm_page *pdata; struct dbm_macro *mdata; const char *cp; int32_t im, ip, iv, npages; if (dbm_open(fname) == -1) return NULL; npages = dbm_page_count(); dba = dba_new(npages < 128 ? 128 : npages); for (ip = 0; ip < npages; ip++) { pdata = dbm_page_get(ip); page = dba_page_new(dba->pages, pdata->arch, pdata->desc, pdata->file + 1, *pdata->file); for (cp = pdata->name; *cp != '\0'; cp = strchr(cp, '\0') + 1) dba_page_add(page, DBP_NAME, cp); for (cp = pdata->sect; *cp != '\0'; cp = strchr(cp, '\0') + 1) dba_page_add(page, DBP_SECT, cp); if ((cp = pdata->arch) != NULL) while (*(cp = strchr(cp, '\0') + 1) != '\0') dba_page_add(page, DBP_ARCH, cp); cp = pdata->file; while (*(cp = strchr(cp, '\0') + 1) != '\0') dba_page_add(page, DBP_FILE, cp); } for (im = 0; im < MACRO_MAX; im++) { for (iv = 0; iv < dbm_macro_count(im); iv++) { mdata = dbm_macro_get(im, iv); dba_macro_new(dba, im, mdata->value, mdata->pp); } } dbm_close(); return dba; } /* $Id: dba_write.c,v 1.3 2016/08/05 23:15:08 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. * * Low-level functions for serializing allocation-based data to disk. * The interface is defined in "dba_write.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 "dba_write.h" static FILE *ofp; int dba_open(const char *fname) { ofp = fopen(fname, "w"); return ofp == NULL ? -1 : 0; } int dba_close(void) { return fclose(ofp) == EOF ? -1 : 0; } int32_t dba_tell(void) { long pos; if ((pos = ftell(ofp)) == -1) err(1, "ftell"); if (pos >= INT32_MAX) { errno = EOVERFLOW; err(1, "ftell = %ld", pos); } return pos; } void dba_seek(int32_t pos) { if (fseek(ofp, pos, SEEK_SET) == -1) err(1, "fseek(%d)", pos); } int32_t dba_align(void) { int32_t pos; pos = dba_tell(); while (pos & 3) { dba_char_write('\0'); pos++; } return pos; } int32_t dba_skip(int32_t nmemb, int32_t sz) { const int32_t out[5] = {0, 0, 0, 0, 0}; int32_t i, pos; assert(sz >= 0); assert(nmemb > 0); assert(nmemb <= 5); pos = dba_tell(); for (i = 0; i < sz; i++) if (nmemb - fwrite(&out, sizeof(out[0]), nmemb, ofp)) err(1, "fwrite"); return pos; } void dba_char_write(int c) { if (putc(c, ofp) == EOF) err(1, "fputc"); } void dba_str_write(const char *str) { if (fputs(str, ofp) == EOF) err(1, "fputs"); dba_char_write('\0'); } void dba_int_write(int32_t i) { i = htobe32(i); if (fwrite(&i, sizeof(i), 1, ofp) != 1) err(1, "fwrite"); } /* $Id: dba_write.h,v 1.1 2016/07/19 21:31:55 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. * * Internal interface to low-level functions * for serializing allocation-based data to disk. * For use by dba_array.c and dba.c only. */ int dba_open(const char *); int dba_close(void); int32_t dba_tell(void); void dba_seek(int32_t); int32_t dba_align(void); int32_t dba_skip(int32_t, int32_t); void dba_char_write(int); void dba_str_write(const char *); void dba_int_write(int32_t); /* $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 int32_t ip; struct dbm_res res = {-1, 0}; assert(arg_iter == ITER_NAME || arg_iter == ITER_DESC || arg_iter == ITER_SECT); /* Initialize for a new iteration. */ if (arg_match != NULL) { iteration = arg_iter; match = arg_match; switch (iteration) { case ITER_NAME: cp = dbm_get(pages[0].name); break; case ITER_SECT: cp = dbm_get(pages[0].sect); break; case ITER_DESC: cp = dbm_get(pages[0].desc); break; default: abort(); } if (cp == NULL) { iteration = ITER_NONE; match = NULL; cp = NULL; ip = npages; } else ip = 0; return res; } /* Search for a name. */ while (ip < npages) { if (iteration == ITER_NAME) cp++; if (dbm_match(match, cp)) break; cp = strchr(cp, '\0') + 1; if (iteration == ITER_DESC) ip++; else if (*cp == '\0') { cp++; ip++; } } /* Reached the end without a match. */ if (ip == npages) { iteration = ITER_NONE; match = NULL; cp = NULL; return res; } /* Found a match; save the quality for later retrieval. */ res.page = ip; res.bits = iteration == ITER_NAME ? cp[-1] : 0; /* Skip the remaining names of this page. */ if (++ip < npages) { do { cp++; } while (cp[-1] != '\0' || (iteration != ITER_DESC && cp[-2] != '\0')); } return res; } static struct dbm_res page_byarch(const struct dbm_match *arg_match) { static const struct dbm_match *match; struct dbm_res res = {-1, 0}; static int32_t ip; const char *cp; /* Initialize for a new iteration. */ if (arg_match != NULL) { iteration = ITER_ARCH; match = arg_match; ip = 0; return res; } /* Search for an architecture. */ for ( ; ip < npages; ip++) if (pages[ip].arch) for (cp = dbm_get(pages[ip].arch); *cp != '\0'; cp = strchr(cp, '\0') + 1) if (dbm_match(match, cp)) { res.page = ip++; return res; } /* Reached the end without a match. */ iteration = ITER_NONE; match = NULL; return res; } static struct dbm_res page_bymacro(int32_t arg_im, const struct dbm_match *arg_match) { static const struct dbm_match *match; static const int32_t *pp; static const char *cp; static int32_t im, iv; struct dbm_res res = {-1, 0}; assert(im >= 0); assert(im < MACRO_MAX); /* Initialize for a new iteration. */ if (arg_match != NULL) { iteration = ITER_MACRO; match = arg_match; im = arg_im; cp = nvals[im] ? dbm_get(macros[im]->value) : NULL; pp = NULL; iv = -1; return res; } if (iteration != ITER_MACRO) return res; /* Find the next matching macro value. */ while (pp == NULL || *pp == 0) { if (++iv == nvals[im]) { iteration = ITER_NONE; return res; } if (iv) cp = strchr(cp, '\0') + 1; if (dbm_match(match, cp)) pp = dbm_get(macros[im][iv].pages); } /* Found a matching page. */ res.page = (struct page *)dbm_get(*pp++) - pages; return res; } /*** functions for handling macros ************************************/ int32_t dbm_macro_count(int32_t im) { assert(im >= 0); assert(im < MACRO_MAX); return nvals[im]; } struct dbm_macro * dbm_macro_get(int32_t im, int32_t iv) { static struct dbm_macro macro; assert(im >= 0); assert(im < MACRO_MAX); assert(iv >= 0); assert(iv < nvals[im]); macro.value = dbm_get(macros[im][iv].value); macro.pp = dbm_get(macros[im][iv].pages); return ¯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); } /* $Id: dbm.h,v 1.1 2016/07/19 21:31:55 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. * * Public interface for the map-based version * of the mandoc database, for read-only access. * To be used by dbm*.c, dba_read.c, and man(1) and apropos(1). */ enum dbm_mtype { DBM_EXACT = 0, DBM_SUB, DBM_REGEX }; struct dbm_match { regex_t *re; const char *str; enum dbm_mtype type; }; struct dbm_res { int32_t page; int32_t bits; }; struct dbm_page { const char *name; const char *sect; const char *arch; const char *desc; const char *file; int32_t addr; }; struct dbm_macro { const char *value; const int32_t *pp; }; int dbm_open(const char *); void dbm_close(void); int32_t dbm_page_count(void); struct dbm_page *dbm_page_get(int32_t); void dbm_page_byname(const struct dbm_match *); void dbm_page_bysect(const struct dbm_match *); void dbm_page_byarch(const struct dbm_match *); void dbm_page_bydesc(const struct dbm_match *); void dbm_page_bymacro(int32_t, const struct dbm_match *); struct dbm_res dbm_page_next(void); int32_t dbm_macro_count(int32_t); struct dbm_macro *dbm_macro_get(int32_t, int32_t); void dbm_macro_bypage(int32_t, int32_t); char *dbm_macro_next(void); /* $Id: dbm_map.c,v 1.8 2017/02/17 14:43:54 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. * * Low-level routines for the map-based version * of the mandoc database, for read-only access. * The interface is defined in "dbm_map.h". */ #include "config.h" #include #include #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 #include "mansearch.h" #include "dbm_map.h" #include "dbm.h" static struct stat st; static char *dbm_base; static int ifd; static int32_t max_offset; /* * Open a disk-based database for read-only access. * Validate the file format as far as it is not mandoc-specific. * Return 0 on success. Return -1 and set errno on failure. */ int dbm_map(const char *fname) { int save_errno; const int32_t *magic; if ((ifd = open(fname, O_RDONLY)) == -1) return -1; if (fstat(ifd, &st) == -1) goto fail; if (st.st_size < 5) { warnx("dbm_map(%s): File too short", fname); errno = EFTYPE; goto fail; } if (st.st_size > INT32_MAX) { errno = EFBIG; goto fail; } if ((dbm_base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, ifd, 0)) == MAP_FAILED) goto fail; magic = dbm_getint(0); if (be32toh(*magic) != MANDOCDB_MAGIC) { if (strncmp(dbm_base, "SQLite format 3", 15)) warnx("dbm_map(%s): " "Bad initial magic %x (expected %x)", fname, be32toh(*magic), MANDOCDB_MAGIC); else warnx("dbm_map(%s): " "Obsolete format based on SQLite 3", fname); errno = EFTYPE; goto fail; } magic = dbm_getint(1); if (be32toh(*magic) != MANDOCDB_VERSION) { warnx("dbm_map(%s): Bad version number %d (expected %d)", fname, be32toh(*magic), MANDOCDB_VERSION); errno = EFTYPE; goto fail; } max_offset = be32toh(*dbm_getint(3)) + sizeof(int32_t); if (st.st_size != max_offset) { warnx("dbm_map(%s): Inconsistent file size %lld (expected %d)", fname, (long long)st.st_size, max_offset); errno = EFTYPE; goto fail; } if ((magic = dbm_get(*dbm_getint(3))) == NULL) { errno = EFTYPE; goto fail; } if (be32toh(*magic) != MANDOCDB_MAGIC) { warnx("dbm_map(%s): Bad final magic %x (expected %x)", fname, be32toh(*magic), MANDOCDB_MAGIC); errno = EFTYPE; goto fail; } return 0; fail: save_errno = errno; close(ifd); errno = save_errno; return -1; } void dbm_unmap(void) { if (munmap(dbm_base, st.st_size) == -1) warn("dbm_unmap: munmap"); if (close(ifd) == -1) warn("dbm_unmap: close"); dbm_base = (char *)-1; } /* * Take a raw integer as it was read from the database. * Interpret it as an offset into the database file * and return a pointer to that place in the file. */ void * dbm_get(int32_t offset) { offset = be32toh(offset); if (offset < 0) { warnx("dbm_get: Database corrupt: offset %d", offset); return NULL; } if (offset >= max_offset) { warnx("dbm_get: Database corrupt: offset %d > %d", offset, max_offset); return NULL; } return dbm_base + offset; } /* * Assume the database starts with some integers. * Assume they are numbered starting from 0, increasing. * Get a pointer to one with the number "offset". */ int32_t * dbm_getint(int32_t offset) { return (int32_t *)dbm_base + offset; } /* * The reverse of dbm_get(). * Take pointer into the database file * and convert it to the raw integer * that would be used to refer to that place in the file. */ int32_t dbm_addr(const void *p) { return htobe32((const char *)p - dbm_base); } int dbm_match(const struct dbm_match *match, const char *str) { switch (match->type) { case DBM_EXACT: return strcmp(str, match->str) == 0; case DBM_SUB: return strcasestr(str, match->str) != NULL; case DBM_REGEX: return regexec(match->re, str, 0, NULL, 0) == 0; default: abort(); } } /* $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 * 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 *); /* $Id: eqn.c,v 1.84 2020/01/08 12:16:24 schwarze Exp $ */ /* * Copyright (c) 2011, 2014 Kristaps Dzonsons * Copyright (c) 2014,2015,2017,2018,2020 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 #include "mandoc_aux.h" #include "mandoc.h" #include "roff.h" #include "eqn.h" #include "libmandoc.h" #include "eqn_parse.h" #define EQN_NEST_MAX 128 /* maximum nesting of defines */ #define STRNEQ(p1, sz1, p2, sz2) \ ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1))) enum eqn_tok { EQN_TOK_DYAD = 0, EQN_TOK_VEC, EQN_TOK_UNDER, EQN_TOK_BAR, EQN_TOK_TILDE, EQN_TOK_HAT, EQN_TOK_DOT, EQN_TOK_DOTDOT, EQN_TOK_FWD, EQN_TOK_BACK, EQN_TOK_DOWN, EQN_TOK_UP, EQN_TOK_FAT, EQN_TOK_ROMAN, EQN_TOK_ITALIC, EQN_TOK_BOLD, EQN_TOK_SIZE, EQN_TOK_SUB, EQN_TOK_SUP, EQN_TOK_SQRT, EQN_TOK_OVER, EQN_TOK_FROM, EQN_TOK_TO, EQN_TOK_BRACE_OPEN, EQN_TOK_BRACE_CLOSE, EQN_TOK_GSIZE, EQN_TOK_GFONT, EQN_TOK_MARK, EQN_TOK_LINEUP, EQN_TOK_LEFT, EQN_TOK_RIGHT, EQN_TOK_PILE, EQN_TOK_LPILE, EQN_TOK_RPILE, EQN_TOK_CPILE, EQN_TOK_MATRIX, EQN_TOK_CCOL, EQN_TOK_LCOL, EQN_TOK_RCOL, EQN_TOK_DELIM, EQN_TOK_DEFINE, EQN_TOK_TDEFINE, EQN_TOK_NDEFINE, EQN_TOK_UNDEF, EQN_TOK_ABOVE, EQN_TOK__MAX, EQN_TOK_FUNC, EQN_TOK_QUOTED, EQN_TOK_SYM, EQN_TOK_EOF }; static const char *eqn_toks[EQN_TOK__MAX] = { "dyad", /* EQN_TOK_DYAD */ "vec", /* EQN_TOK_VEC */ "under", /* EQN_TOK_UNDER */ "bar", /* EQN_TOK_BAR */ "tilde", /* EQN_TOK_TILDE */ "hat", /* EQN_TOK_HAT */ "dot", /* EQN_TOK_DOT */ "dotdot", /* EQN_TOK_DOTDOT */ "fwd", /* EQN_TOK_FWD * */ "back", /* EQN_TOK_BACK */ "down", /* EQN_TOK_DOWN */ "up", /* EQN_TOK_UP */ "fat", /* EQN_TOK_FAT */ "roman", /* EQN_TOK_ROMAN */ "italic", /* EQN_TOK_ITALIC */ "bold", /* EQN_TOK_BOLD */ "size", /* EQN_TOK_SIZE */ "sub", /* EQN_TOK_SUB */ "sup", /* EQN_TOK_SUP */ "sqrt", /* EQN_TOK_SQRT */ "over", /* EQN_TOK_OVER */ "from", /* EQN_TOK_FROM */ "to", /* EQN_TOK_TO */ "{", /* EQN_TOK_BRACE_OPEN */ "}", /* EQN_TOK_BRACE_CLOSE */ "gsize", /* EQN_TOK_GSIZE */ "gfont", /* EQN_TOK_GFONT */ "mark", /* EQN_TOK_MARK */ "lineup", /* EQN_TOK_LINEUP */ "left", /* EQN_TOK_LEFT */ "right", /* EQN_TOK_RIGHT */ "pile", /* EQN_TOK_PILE */ "lpile", /* EQN_TOK_LPILE */ "rpile", /* EQN_TOK_RPILE */ "cpile", /* EQN_TOK_CPILE */ "matrix", /* EQN_TOK_MATRIX */ "ccol", /* EQN_TOK_CCOL */ "lcol", /* EQN_TOK_LCOL */ "rcol", /* EQN_TOK_RCOL */ "delim", /* EQN_TOK_DELIM */ "define", /* EQN_TOK_DEFINE */ "tdefine", /* EQN_TOK_TDEFINE */ "ndefine", /* EQN_TOK_NDEFINE */ "undef", /* EQN_TOK_UNDEF */ "above", /* EQN_TOK_ABOVE */ }; static const char *const eqn_func[] = { "acos", "acsc", "and", "arc", "asec", "asin", "atan", "cos", "cosh", "coth", "csc", "det", "exp", "for", "if", "lim", "ln", "log", "max", "min", "sec", "sin", "sinh", "tan", "tanh", "Im", "Re", }; enum eqn_symt { EQNSYM_alpha = 0, EQNSYM_beta, EQNSYM_chi, EQNSYM_delta, EQNSYM_epsilon, EQNSYM_eta, EQNSYM_gamma, EQNSYM_iota, EQNSYM_kappa, EQNSYM_lambda, EQNSYM_mu, EQNSYM_nu, EQNSYM_omega, EQNSYM_omicron, EQNSYM_phi, EQNSYM_pi, EQNSYM_ps, EQNSYM_rho, EQNSYM_sigma, EQNSYM_tau, EQNSYM_theta, EQNSYM_upsilon, EQNSYM_xi, EQNSYM_zeta, EQNSYM_DELTA, EQNSYM_GAMMA, EQNSYM_LAMBDA, EQNSYM_OMEGA, EQNSYM_PHI, EQNSYM_PI, EQNSYM_PSI, EQNSYM_SIGMA, EQNSYM_THETA, EQNSYM_UPSILON, EQNSYM_XI, EQNSYM_inter, EQNSYM_union, EQNSYM_prod, EQNSYM_int, EQNSYM_sum, EQNSYM_grad, EQNSYM_del, EQNSYM_times, EQNSYM_cdot, EQNSYM_nothing, EQNSYM_approx, EQNSYM_prime, EQNSYM_half, EQNSYM_partial, EQNSYM_inf, EQNSYM_muchgreat, EQNSYM_muchless, EQNSYM_larrow, EQNSYM_rarrow, EQNSYM_pm, EQNSYM_nequal, EQNSYM_equiv, EQNSYM_lessequal, EQNSYM_moreequal, EQNSYM_minus, EQNSYM__MAX }; struct eqnsym { const char *str; const char *sym; }; static const struct eqnsym eqnsyms[EQNSYM__MAX] = { { "alpha", "*a" }, /* EQNSYM_alpha */ { "beta", "*b" }, /* EQNSYM_beta */ { "chi", "*x" }, /* EQNSYM_chi */ { "delta", "*d" }, /* EQNSYM_delta */ { "epsilon", "*e" }, /* EQNSYM_epsilon */ { "eta", "*y" }, /* EQNSYM_eta */ { "gamma", "*g" }, /* EQNSYM_gamma */ { "iota", "*i" }, /* EQNSYM_iota */ { "kappa", "*k" }, /* EQNSYM_kappa */ { "lambda", "*l" }, /* EQNSYM_lambda */ { "mu", "*m" }, /* EQNSYM_mu */ { "nu", "*n" }, /* EQNSYM_nu */ { "omega", "*w" }, /* EQNSYM_omega */ { "omicron", "*o" }, /* EQNSYM_omicron */ { "phi", "*f" }, /* EQNSYM_phi */ { "pi", "*p" }, /* EQNSYM_pi */ { "psi", "*q" }, /* EQNSYM_psi */ { "rho", "*r" }, /* EQNSYM_rho */ { "sigma", "*s" }, /* EQNSYM_sigma */ { "tau", "*t" }, /* EQNSYM_tau */ { "theta", "*h" }, /* EQNSYM_theta */ { "upsilon", "*u" }, /* EQNSYM_upsilon */ { "xi", "*c" }, /* EQNSYM_xi */ { "zeta", "*z" }, /* EQNSYM_zeta */ { "DELTA", "*D" }, /* EQNSYM_DELTA */ { "GAMMA", "*G" }, /* EQNSYM_GAMMA */ { "LAMBDA", "*L" }, /* EQNSYM_LAMBDA */ { "OMEGA", "*W" }, /* EQNSYM_OMEGA */ { "PHI", "*F" }, /* EQNSYM_PHI */ { "PI", "*P" }, /* EQNSYM_PI */ { "PSI", "*Q" }, /* EQNSYM_PSI */ { "SIGMA", "*S" }, /* EQNSYM_SIGMA */ { "THETA", "*H" }, /* EQNSYM_THETA */ { "UPSILON", "*U" }, /* EQNSYM_UPSILON */ { "XI", "*C" }, /* EQNSYM_XI */ { "inter", "ca" }, /* EQNSYM_inter */ { "union", "cu" }, /* EQNSYM_union */ { "prod", "product" }, /* EQNSYM_prod */ { "int", "integral" }, /* EQNSYM_int */ { "sum", "sum" }, /* EQNSYM_sum */ { "grad", "gr" }, /* EQNSYM_grad */ { "del", "gr" }, /* EQNSYM_del */ { "times", "mu" }, /* EQNSYM_times */ { "cdot", "pc" }, /* EQNSYM_cdot */ { "nothing", "&" }, /* EQNSYM_nothing */ { "approx", "~~" }, /* EQNSYM_approx */ { "prime", "fm" }, /* EQNSYM_prime */ { "half", "12" }, /* EQNSYM_half */ { "partial", "pd" }, /* EQNSYM_partial */ { "inf", "if" }, /* EQNSYM_inf */ { ">>", ">>" }, /* EQNSYM_muchgreat */ { "<<", "<<" }, /* EQNSYM_muchless */ { "<-", "<-" }, /* EQNSYM_larrow */ { "->", "->" }, /* EQNSYM_rarrow */ { "+-", "+-" }, /* EQNSYM_pm */ { "!=", "!=" }, /* EQNSYM_nequal */ { "==", "==" }, /* EQNSYM_equiv */ { "<=", "<=" }, /* EQNSYM_lessequal */ { ">=", ">=" }, /* EQNSYM_moreequal */ { "-", "mi" }, /* EQNSYM_minus */ }; enum parse_mode { MODE_QUOTED, MODE_NOSUB, MODE_SUB, MODE_TOK }; struct eqn_def { char *key; size_t keysz; char *val; size_t valsz; }; static struct eqn_box *eqn_box_alloc(struct eqn_node *, struct eqn_box *); static struct eqn_box *eqn_box_makebinary(struct eqn_node *, struct eqn_box *); static void eqn_def(struct eqn_node *); static struct eqn_def *eqn_def_find(struct eqn_node *); static void eqn_delim(struct eqn_node *); static enum eqn_tok eqn_next(struct eqn_node *, enum parse_mode); static void eqn_undef(struct eqn_node *); struct eqn_node * eqn_alloc(void) { struct eqn_node *ep; ep = mandoc_calloc(1, sizeof(*ep)); ep->gsize = EQN_DEFSIZE; return ep; } void eqn_reset(struct eqn_node *ep) { free(ep->data); ep->data = ep->start = ep->end = NULL; ep->sz = ep->toksz = 0; } void eqn_read(struct eqn_node *ep, const char *p) { char *cp; if (ep->data == NULL) { ep->sz = strlen(p); ep->data = mandoc_strdup(p); } else { ep->sz = mandoc_asprintf(&cp, "%s %s", ep->data, p); free(ep->data); ep->data = cp; } ep->sz += 1; } /* * Find the key "key" of the give size within our eqn-defined values. */ static struct eqn_def * eqn_def_find(struct eqn_node *ep) { int i; for (i = 0; i < (int)ep->defsz; i++) if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key, ep->defs[i].keysz, ep->start, ep->toksz)) return &ep->defs[i]; return NULL; } /* * Parse a token from the input text. The modes are: * MODE_QUOTED: Use *ep->start as the delimiter; the token ends * before its next occurence. Do not interpret the token in any * way and return EQN_TOK_QUOTED. All other modes behave like * MODE_QUOTED when *ep->start is '"'. * MODE_NOSUB: If *ep->start is a curly brace, the token ends after it; * otherwise, it ends before the next whitespace or brace. * Do not interpret the token and return EQN_TOK__MAX. * MODE_SUB: Like MODE_NOSUB, but try to interpret the token as an * alias created with define. If it is an alias, replace it with * its string value and reparse. * MODE_TOK: Like MODE_SUB, but also check the token against the list * of tokens, and if there is a match, return that token. Otherwise, * if the token matches a symbol, return EQN_TOK_SYM; if it matches * a function name, EQN_TOK_FUNC, or else EQN_TOK__MAX. Except for * a token match, *ep->start is set to an allocated string that the * caller is expected to free. * All modes skip whitespace following the end of the token. */ static enum eqn_tok eqn_next(struct eqn_node *ep, enum parse_mode mode) { static int last_len, lim; struct eqn_def *def; size_t start; int diff, i, quoted; enum eqn_tok tok; /* * Reset the recursion counter after advancing * beyond the end of the previous substitution. */ if (ep->end - ep->data >= last_len) lim = 0; ep->start = ep->end; quoted = mode == MODE_QUOTED; for (;;) { switch (*ep->start) { case '\0': ep->toksz = 0; return EQN_TOK_EOF; case '"': quoted = 1; break; case ' ': case '\t': case '~': case '^': if (quoted) break; ep->start++; continue; default: break; } if (quoted) { ep->end = strchr(ep->start + 1, *ep->start); ep->start++; /* Skip opening quote. */ if (ep->end == NULL) { mandoc_msg(MANDOCERR_ARG_QUOTE, ep->node->line, ep->node->pos, NULL); ep->end = strchr(ep->start, '\0'); } } else { ep->end = ep->start + 1; if (*ep->start != '{' && *ep->start != '}') ep->end += strcspn(ep->end, " ^~\"{}\t"); } ep->toksz = ep->end - ep->start; if (quoted && *ep->end != '\0') ep->end++; /* Skip closing quote. */ while (*ep->end != '\0' && strchr(" \t^~", *ep->end) != NULL) ep->end++; if (quoted) /* Cannot return, may have to strndup. */ break; if (mode == MODE_NOSUB) return EQN_TOK__MAX; if ((def = eqn_def_find(ep)) == NULL) break; if (++lim > EQN_NEST_MAX) { mandoc_msg(MANDOCERR_ROFFLOOP, ep->node->line, ep->node->pos, NULL); return EQN_TOK_EOF; } /* Replace a defined name with its string value. */ if ((diff = def->valsz - ep->toksz) > 0) { start = ep->start - ep->data; ep->sz += diff; ep->data = mandoc_realloc(ep->data, ep->sz + 1); ep->start = ep->data + start; } if (diff) memmove(ep->start + def->valsz, ep->start + ep->toksz, strlen(ep->start + ep->toksz) + 1); memcpy(ep->start, def->val, def->valsz); last_len = ep->start - ep->data + def->valsz; } if (mode != MODE_TOK) return quoted ? EQN_TOK_QUOTED : EQN_TOK__MAX; if (quoted) { ep->start = mandoc_strndup(ep->start, ep->toksz); return EQN_TOK_QUOTED; } for (tok = 0; tok < EQN_TOK__MAX; tok++) if (STRNEQ(ep->start, ep->toksz, eqn_toks[tok], strlen(eqn_toks[tok]))) return tok; for (i = 0; i < EQNSYM__MAX; i++) { if (STRNEQ(ep->start, ep->toksz, eqnsyms[i].str, strlen(eqnsyms[i].str))) { mandoc_asprintf(&ep->start, "\\[%s]", eqnsyms[i].sym); return EQN_TOK_SYM; } } ep->start = mandoc_strndup(ep->start, ep->toksz); for (i = 0; i < (int)(sizeof(eqn_func)/sizeof(*eqn_func)); i++) if (STRNEQ(ep->start, ep->toksz, eqn_func[i], strlen(eqn_func[i]))) return EQN_TOK_FUNC; return EQN_TOK__MAX; } void eqn_box_free(struct eqn_box *bp) { if (bp == NULL) return; if (bp->first) eqn_box_free(bp->first); if (bp->next) eqn_box_free(bp->next); free(bp->text); free(bp->left); free(bp->right); free(bp->top); free(bp->bottom); free(bp); } struct eqn_box * eqn_box_new(void) { struct eqn_box *bp; bp = mandoc_calloc(1, sizeof(*bp)); bp->expectargs = UINT_MAX; return bp; } /* * Allocate a box as the last child of the parent node. */ static struct eqn_box * eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent) { struct eqn_box *bp; bp = eqn_box_new(); bp->parent = parent; bp->parent->args++; bp->font = bp->parent->font; bp->size = ep->gsize; if (NULL != parent->first) { parent->last->next = bp; bp->prev = parent->last; } else parent->first = bp; parent->last = bp; return bp; } /* * Reparent the current last node (of the current parent) under a new * EQN_SUBEXPR as the first element. * Then return the new parent. * The new EQN_SUBEXPR will have a two-child limit. */ static struct eqn_box * eqn_box_makebinary(struct eqn_node *ep, struct eqn_box *parent) { struct eqn_box *b, *newb; assert(NULL != parent->last); b = parent->last; if (parent->last == parent->first) parent->first = NULL; parent->args--; parent->last = b->prev; b->prev = NULL; newb = eqn_box_alloc(ep, parent); newb->type = EQN_SUBEXPR; newb->expectargs = 2; newb->args = 1; newb->first = newb->last = b; newb->first->next = NULL; b->parent = newb; return newb; } /* * Parse the "delim" control statement. */ static void eqn_delim(struct eqn_node *ep) { if (ep->end[0] == '\0' || ep->end[1] == '\0') { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "delim"); if (ep->end[0] != '\0') ep->end++; } else if (strncmp(ep->end, "off", 3) == 0) { ep->delim = 0; ep->end += 3; } else if (strncmp(ep->end, "on", 2) == 0) { if (ep->odelim && ep->cdelim) ep->delim = 1; ep->end += 2; } else { ep->odelim = *ep->end++; ep->cdelim = *ep->end++; ep->delim = 1; } } /* * Undefine a previously-defined string. */ static void eqn_undef(struct eqn_node *ep) { struct eqn_def *def; if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "undef"); return; } if ((def = eqn_def_find(ep)) == NULL) return; free(def->key); free(def->val); def->key = def->val = NULL; def->keysz = def->valsz = 0; } static void eqn_def(struct eqn_node *ep) { struct eqn_def *def; int i; if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "define"); return; } /* * Search for a key that already exists. * Create a new key if none is found. */ if ((def = eqn_def_find(ep)) == NULL) { /* Find holes in string array. */ for (i = 0; i < (int)ep->defsz; i++) if (0 == ep->defs[i].keysz) break; if (i == (int)ep->defsz) { ep->defsz++; ep->defs = mandoc_reallocarray(ep->defs, ep->defsz, sizeof(struct eqn_def)); ep->defs[i].key = ep->defs[i].val = NULL; } def = ep->defs + i; free(def->key); def->key = mandoc_strndup(ep->start, ep->toksz); def->keysz = ep->toksz; } if (eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "define %s", def->key); free(def->key); free(def->val); def->key = def->val = NULL; def->keysz = def->valsz = 0; return; } free(def->val); def->val = mandoc_strndup(ep->start, ep->toksz); def->valsz = ep->toksz; } void eqn_parse(struct eqn_node *ep) { struct eqn_box *cur, *nbox, *parent, *split; const char *cp, *cpn; char *p; enum eqn_tok tok; enum { CCL_LET, CCL_DIG, CCL_PUN } ccl, ccln; int size; parent = ep->node->eqn; assert(parent != NULL); /* * Empty equation. * Do not add it to the high-level syntax tree. */ if (ep->data == NULL) return; ep->start = ep->end = ep->data; next_tok: tok = eqn_next(ep, MODE_TOK); switch (tok) { case EQN_TOK_UNDEF: eqn_undef(ep); break; case EQN_TOK_NDEFINE: case EQN_TOK_DEFINE: eqn_def(ep); break; case EQN_TOK_TDEFINE: if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF || eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF) mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "tdefine"); break; case EQN_TOK_DELIM: eqn_delim(ep); break; case EQN_TOK_GFONT: if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; case EQN_TOK_MARK: case EQN_TOK_LINEUP: /* Ignore these. */ break; case EQN_TOK_DYAD: case EQN_TOK_VEC: case EQN_TOK_UNDER: case EQN_TOK_BAR: case EQN_TOK_TILDE: case EQN_TOK_HAT: case EQN_TOK_DOT: case EQN_TOK_DOTDOT: if (parent->last == NULL) { mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); cur = eqn_box_alloc(ep, parent); cur->type = EQN_TEXT; cur->text = mandoc_strdup(""); } parent = eqn_box_makebinary(ep, parent); parent->type = EQN_LIST; parent->expectargs = 1; parent->font = EQNFONT_ROMAN; switch (tok) { case EQN_TOK_DOTDOT: parent->top = mandoc_strdup("\\[ad]"); break; case EQN_TOK_VEC: parent->top = mandoc_strdup("\\[->]"); break; case EQN_TOK_DYAD: parent->top = mandoc_strdup("\\[<>]"); break; case EQN_TOK_TILDE: parent->top = mandoc_strdup("\\[a~]"); break; case EQN_TOK_UNDER: parent->bottom = mandoc_strdup("\\[ul]"); break; case EQN_TOK_BAR: parent->top = mandoc_strdup("\\[rn]"); break; case EQN_TOK_DOT: parent->top = mandoc_strdup("\\[a.]"); break; case EQN_TOK_HAT: parent->top = mandoc_strdup("\\[ha]"); break; default: abort(); } parent = parent->parent; break; case EQN_TOK_FWD: case EQN_TOK_BACK: case EQN_TOK_DOWN: case EQN_TOK_UP: if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; case EQN_TOK_FAT: case EQN_TOK_ROMAN: case EQN_TOK_ITALIC: case EQN_TOK_BOLD: while (parent->args == parent->expectargs) parent = parent->parent; /* * These values apply to the next word or sequence of * words; thus, we mark that we'll have a child with * exactly one of those. */ parent = eqn_box_alloc(ep, parent); parent->type = EQN_LIST; parent->expectargs = 1; switch (tok) { case EQN_TOK_FAT: parent->font = EQNFONT_FAT; break; case EQN_TOK_ROMAN: parent->font = EQNFONT_ROMAN; break; case EQN_TOK_ITALIC: parent->font = EQNFONT_ITALIC; break; case EQN_TOK_BOLD: parent->font = EQNFONT_BOLD; break; default: abort(); } break; case EQN_TOK_SIZE: case EQN_TOK_GSIZE: /* Accept two values: integral size and a single. */ if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; } size = mandoc_strntoi(ep->start, ep->toksz, 10); if (-1 == size) { mandoc_msg(MANDOCERR_IT_NONUM, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; } if (EQN_TOK_GSIZE == tok) { ep->gsize = size; break; } while (parent->args == parent->expectargs) parent = parent->parent; parent = eqn_box_alloc(ep, parent); parent->type = EQN_LIST; parent->expectargs = 1; parent->size = size; break; case EQN_TOK_FROM: case EQN_TOK_TO: case EQN_TOK_SUB: case EQN_TOK_SUP: /* * We have a left-right-associative expression. * Repivot under a positional node, open a child scope * and keep on reading. */ if (parent->last == NULL) { mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); cur = eqn_box_alloc(ep, parent); cur->type = EQN_TEXT; cur->text = mandoc_strdup(""); } while (parent->expectargs == 1 && parent->args == 1) parent = parent->parent; if (tok == EQN_TOK_FROM || tok == EQN_TOK_TO) { for (cur = parent; cur != NULL; cur = cur->parent) if (cur->pos == EQNPOS_SUB || cur->pos == EQNPOS_SUP || cur->pos == EQNPOS_SUBSUP || cur->pos == EQNPOS_SQRT || cur->pos == EQNPOS_OVER) break; if (cur != NULL) parent = cur->parent; } if (tok == EQN_TOK_SUP && parent->pos == EQNPOS_SUB) { parent->expectargs = 3; parent->pos = EQNPOS_SUBSUP; break; } if (tok == EQN_TOK_TO && parent->pos == EQNPOS_FROM) { parent->expectargs = 3; parent->pos = EQNPOS_FROMTO; break; } parent = eqn_box_makebinary(ep, parent); switch (tok) { case EQN_TOK_FROM: parent->pos = EQNPOS_FROM; break; case EQN_TOK_TO: parent->pos = EQNPOS_TO; break; case EQN_TOK_SUP: parent->pos = EQNPOS_SUP; break; case EQN_TOK_SUB: parent->pos = EQNPOS_SUB; break; default: abort(); } break; case EQN_TOK_SQRT: while (parent->args == parent->expectargs) parent = parent->parent; /* * Accept a left-right-associative set of arguments just * like sub and sup and friends but without rebalancing * under a pivot. */ parent = eqn_box_alloc(ep, parent); parent->type = EQN_SUBEXPR; parent->pos = EQNPOS_SQRT; parent->expectargs = 1; break; case EQN_TOK_OVER: /* * We have a right-left-associative fraction. * Close out anything that's currently open, then * rebalance and continue reading. */ if (parent->last == NULL) { mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); cur = eqn_box_alloc(ep, parent); cur->type = EQN_TEXT; cur->text = mandoc_strdup(""); } while (parent->args == parent->expectargs) parent = parent->parent; while (EQN_SUBEXPR == parent->type) parent = parent->parent; parent = eqn_box_makebinary(ep, parent); parent->pos = EQNPOS_OVER; break; case EQN_TOK_RIGHT: case EQN_TOK_BRACE_CLOSE: /* * Close out the existing brace. * FIXME: this is a shitty sentinel: we should really * have a native EQN_BRACE type or whatnot. */ for (cur = parent; cur != NULL; cur = cur->parent) if (cur->type == EQN_LIST && cur->expectargs > 1 && (tok == EQN_TOK_BRACE_CLOSE || cur->left != NULL)) break; if (cur == NULL) { mandoc_msg(MANDOCERR_BLK_NOTOPEN, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; } parent = cur; if (EQN_TOK_RIGHT == tok) { if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; } /* Handling depends on right/left. */ if (STRNEQ(ep->start, ep->toksz, "ceiling", 7)) parent->right = mandoc_strdup("\\[rc]"); else if (STRNEQ(ep->start, ep->toksz, "floor", 5)) parent->right = mandoc_strdup("\\[rf]"); else parent->right = mandoc_strndup(ep->start, ep->toksz); } parent = parent->parent; if (tok == EQN_TOK_BRACE_CLOSE && (parent->type == EQN_PILE || parent->type == EQN_MATRIX)) parent = parent->parent; /* Close out any "singleton" lists. */ while (parent->type == EQN_LIST && parent->expectargs == 1 && parent->args == 1) parent = parent->parent; break; case EQN_TOK_BRACE_OPEN: case EQN_TOK_LEFT: /* * If we already have something in the stack and we're * in an expression, then rewind til we're not any more * (just like with the text node). */ while (parent->args == parent->expectargs) parent = parent->parent; if (EQN_TOK_LEFT == tok && eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; } parent = eqn_box_alloc(ep, parent); parent->type = EQN_LIST; if (EQN_TOK_LEFT == tok) { if (STRNEQ(ep->start, ep->toksz, "ceiling", 7)) parent->left = mandoc_strdup("\\[lc]"); else if (STRNEQ(ep->start, ep->toksz, "floor", 5)) parent->left = mandoc_strdup("\\[lf]"); else parent->left = mandoc_strndup(ep->start, ep->toksz); } break; case EQN_TOK_PILE: case EQN_TOK_LPILE: case EQN_TOK_RPILE: case EQN_TOK_CPILE: case EQN_TOK_CCOL: case EQN_TOK_LCOL: case EQN_TOK_RCOL: while (parent->args == parent->expectargs) parent = parent->parent; parent = eqn_box_alloc(ep, parent); parent->type = EQN_PILE; parent->expectargs = 1; break; case EQN_TOK_ABOVE: for (cur = parent; cur != NULL; cur = cur->parent) if (cur->type == EQN_PILE) break; if (cur == NULL) { mandoc_msg(MANDOCERR_IT_STRAY, ep->node->line, ep->node->pos, "%s", eqn_toks[tok]); break; } parent = eqn_box_alloc(ep, cur); parent->type = EQN_LIST; break; case EQN_TOK_MATRIX: while (parent->args == parent->expectargs) parent = parent->parent; parent = eqn_box_alloc(ep, parent); parent->type = EQN_MATRIX; parent->expectargs = 1; break; case EQN_TOK_EOF: return; case EQN_TOK__MAX: case EQN_TOK_FUNC: case EQN_TOK_QUOTED: case EQN_TOK_SYM: p = ep->start; assert(p != NULL); /* * If we already have something in the stack and we're * in an expression, then rewind til we're not any more. */ while (parent->args == parent->expectargs) parent = parent->parent; cur = eqn_box_alloc(ep, parent); cur->type = EQN_TEXT; cur->text = p; switch (tok) { case EQN_TOK_FUNC: cur->font = EQNFONT_ROMAN; break; case EQN_TOK_QUOTED: if (cur->font == EQNFONT_NONE) cur->font = EQNFONT_ITALIC; break; case EQN_TOK_SYM: break; default: if (cur->font != EQNFONT_NONE || *p == '\0') break; cpn = p - 1; ccln = CCL_LET; split = NULL; for (;;) { /* Advance to next character. */ cp = cpn++; ccl = ccln; ccln = isalpha((unsigned char)*cpn) ? CCL_LET : isdigit((unsigned char)*cpn) || (*cpn == '.' && (ccl == CCL_DIG || isdigit((unsigned char)cpn[1]))) ? CCL_DIG : CCL_PUN; /* No boundary before first character. */ if (cp < p) continue; cur->font = ccl == CCL_LET ? EQNFONT_ITALIC : EQNFONT_ROMAN; if (*cp == '\\') mandoc_escape(&cpn, NULL, NULL); /* No boundary after last character. */ if (*cpn == '\0') break; if (ccln == ccl && *cp != ',' && *cpn != ',') continue; /* Boundary found, split the text. */ if (parent->args == parent->expectargs) { /* Remove the text from the tree. */ if (cur->prev == NULL) parent->first = cur->next; else cur->prev->next = NULL; parent->last = cur->prev; parent->args--; /* Set up a list instead. */ split = eqn_box_alloc(ep, parent); split->type = EQN_LIST; /* Insert the word into the list. */ split->first = split->last = cur; cur->parent = split; cur->prev = NULL; parent = split; } /* Append a new text box. */ nbox = eqn_box_alloc(ep, parent); nbox->type = EQN_TEXT; nbox->text = mandoc_strdup(cpn); /* Truncate the old box. */ p = mandoc_strndup(cur->text, cpn - cur->text); free(cur->text); cur->text = p; /* Setup to process the new box. */ cur = nbox; p = nbox->text; cpn = p - 1; ccln = CCL_LET; } if (split != NULL) parent = split->parent; break; } break; default: abort(); } goto next_tok; } void eqn_free(struct eqn_node *p) { int i; if (p == NULL) return; for (i = 0; i < (int)p->defsz; i++) { free(p->defs[i].key); free(p->defs[i].val); } free(p->data); free(p->defs); free(p); } /* $Id: eqn.h,v 1.1 2018/12/13 05:23:38 schwarze Exp $ */ /* * Copyright (c) 2011, 2014 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. * * Public data types for eqn(7) syntax trees. */ enum eqn_boxt { EQN_TEXT, /* Text, e.g. number, variable, operator, ... */ EQN_SUBEXPR, /* Nested eqn(7) subexpression. */ EQN_LIST, /* List, for example in braces. */ EQN_PILE, /* Vertical pile. */ EQN_MATRIX /* List of columns. */ }; enum eqn_fontt { EQNFONT_NONE = 0, EQNFONT_ROMAN, EQNFONT_BOLD, EQNFONT_FAT, EQNFONT_ITALIC, EQNFONT__MAX }; enum eqn_post { EQNPOS_NONE = 0, EQNPOS_SUP, EQNPOS_SUBSUP, EQNPOS_SUB, EQNPOS_TO, EQNPOS_FROM, EQNPOS_FROMTO, EQNPOS_OVER, EQNPOS_SQRT, EQNPOS__MAX }; /* * A "box" is a parsed mathematical expression as defined by the eqn.7 * grammar. */ struct eqn_box { struct eqn_box *parent; struct eqn_box *prev; struct eqn_box *next; struct eqn_box *first; /* First child node. */ struct eqn_box *last; /* Last child node. */ char *text; /* Text (or NULL). */ char *left; /* Left-hand fence. */ char *right; /* Right-hand fence. */ char *top; /* Symbol above. */ char *bottom; /* Symbol below. */ size_t expectargs; /* Maximal number of arguments. */ size_t args; /* Actual number of arguments. */ int size; /* Font size. */ #define EQN_DEFSIZE INT_MIN enum eqn_boxt type; /* Type of node. */ enum eqn_fontt font; /* Font in this box. */ enum eqn_post pos; /* Position of the next box. */ }; /* $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); } /* $Id: eqn_parse.h,v 1.3 2018/12/14 06:33:14 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons * Copyright (c) 2014, 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. * * External interface of the eqn(7) parser. * For use in the roff(7) and eqn(7) parsers only. */ struct roff_node; struct eqn_box; struct eqn_def; struct eqn_node { struct roff_node *node; /* Syntax tree of this equation. */ struct eqn_def *defs; /* Array of definitions. */ char *data; /* Source code of this equation. */ char *start; /* First byte of the current token. */ char *end; /* First byte of the next token. */ size_t defsz; /* Number of definitions. */ size_t sz; /* Length of the source code. */ size_t toksz; /* Length of the current token. */ int gsize; /* Default point size. */ int delim; /* In-line delimiters enabled. */ char odelim; /* In-line opening delimiter. */ char cdelim; /* In-line closing delimiter. */ }; struct eqn_node *eqn_alloc(void); struct eqn_box *eqn_box_new(void); void eqn_box_free(struct eqn_box *); void eqn_free(struct eqn_node *); void eqn_parse(struct eqn_node *); void eqn_read(struct eqn_node *, const char *); void eqn_reset(struct eqn_node *); /* $Id: eqn_term.c,v 1.19 2018/12/13 05:23:38 schwarze Exp $ */ /* * Copyright (c) 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. */ #include "config.h" #include #include #include #include #include #include #include "eqn.h" #include "out.h" #include "term.h" static const enum termfont fontmap[EQNFONT__MAX] = { TERMFONT_NONE, /* EQNFONT_NONE */ TERMFONT_NONE, /* EQNFONT_ROMAN */ TERMFONT_BOLD, /* EQNFONT_BOLD */ TERMFONT_BOLD, /* EQNFONT_FAT */ TERMFONT_UNDER /* EQNFONT_ITALIC */ }; static void eqn_box(struct termp *, const struct eqn_box *); void term_eqn(struct termp *p, const struct eqn_box *bp) { eqn_box(p, bp); p->flags &= ~TERMP_NOSPACE; } static void eqn_box(struct termp *p, const struct eqn_box *bp) { const struct eqn_box *child; const char *cp; int delim; /* Delimiters around this box? */ if ((bp->type == EQN_LIST && bp->expectargs > 1) || (bp->type == EQN_PILE && (bp->prev || bp->next)) || (bp->parent != NULL && (bp->parent->pos == EQNPOS_SQRT || /* Diacritic followed by ^ or _. */ ((bp->top != NULL || bp->bottom != NULL) && bp->parent->type == EQN_SUBEXPR && bp->parent->pos != EQNPOS_OVER && bp->next != NULL) || /* Nested over, sub, sup, from, to. */ (bp->type == EQN_SUBEXPR && bp->pos != EQNPOS_SQRT && ((bp->parent->type == EQN_LIST && bp->expectargs == 1) || (bp->parent->type == EQN_SUBEXPR && bp->pos != EQNPOS_SQRT)))))) { if ((bp->parent->type == EQN_SUBEXPR && bp->prev != NULL) || (bp->type == EQN_LIST && bp->first != NULL && bp->first->type != EQN_PILE && bp->first->type != EQN_MATRIX && bp->prev != NULL && (bp->prev->type == EQN_LIST || (bp->prev->type == EQN_TEXT && (*bp->prev->text == '\\' || isalpha((unsigned char)*bp->prev->text)))))) p->flags |= TERMP_NOSPACE; term_word(p, bp->left != NULL ? bp->left : "("); p->flags |= TERMP_NOSPACE; delim = 1; } else delim = 0; /* Handle Fonts and text. */ if (bp->font != EQNFONT_NONE) term_fontpush(p, fontmap[(int)bp->font]); if (bp->text != NULL) { if (strchr("!\"'),.:;?]}", *bp->text) != NULL) p->flags |= TERMP_NOSPACE; term_word(p, bp->text); if ((cp = strchr(bp->text, '\0')) > bp->text && (strchr("\"'([{", cp[-1]) != NULL || (bp->prev == NULL && (cp[-1] == '-' || (cp >= bp->text + 5 && strcmp(cp - 5, "\\[mi]") == 0))))) p->flags |= TERMP_NOSPACE; } /* Special box types. */ if (bp->pos == EQNPOS_SQRT) { term_word(p, "\\(sr"); if (bp->first != NULL) { p->flags |= TERMP_NOSPACE; eqn_box(p, bp->first); } } else if (bp->type == EQN_SUBEXPR) { child = bp->first; eqn_box(p, child); p->flags |= TERMP_NOSPACE; term_word(p, bp->pos == EQNPOS_OVER ? "/" : (bp->pos == EQNPOS_SUP || bp->pos == EQNPOS_TO) ? "^" : "_"); child = child->next; if (child != NULL) { p->flags |= TERMP_NOSPACE; eqn_box(p, child); if (bp->pos == EQNPOS_FROMTO || bp->pos == EQNPOS_SUBSUP) { p->flags |= TERMP_NOSPACE; term_word(p, "^"); p->flags |= TERMP_NOSPACE; child = child->next; if (child != NULL) eqn_box(p, child); } } } else { child = bp->first; if (bp->type == EQN_MATRIX && child != NULL && child->type == EQN_LIST && child->expectargs > 1) child = child->first; while (child != NULL) { eqn_box(p, bp->type == EQN_PILE && child->type == EQN_LIST && child->expectargs > 1 && child->args == 1 ? child->first : child); child = child->next; } } /* Handle Fonts and diacritics. */ if (bp->font != EQNFONT_NONE) term_fontpop(p); if (bp->top != NULL) { p->flags |= TERMP_NOSPACE; term_word(p, bp->top); } if (bp->bottom != NULL) { p->flags |= TERMP_NOSPACE; term_word(p, "_"); } /* Right delimiter after this box? */ if (delim) { p->flags |= TERMP_NOSPACE; term_word(p, bp->right != NULL ? bp->right : ")"); if (bp->parent->type == EQN_SUBEXPR && bp->next != NULL) p->flags |= TERMP_NOSPACE; } } /* $Id: html.c,v 1.275 2021/09/09 14:47:24 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons * Copyright (c) 2011-2015, 2017-2021 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. * * Common functions for mandoc(1) HTML formatters. * For use by individual formatters and by the main program. */ #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_INPHRASE (1 << 0) /* Can appear in phrasing context. */ #define HTML_TOPHRASE (1 << 1) /* Establishes phrasing context. */ #define HTML_NOSTACK (1 << 2) /* Does not have an end tag. */ #define HTML_NLBEFORE (1 << 3) /* Output line break before opening. */ #define HTML_NLBEGIN (1 << 4) /* Output line break after opening. */ #define HTML_NLEND (1 << 5) /* Output line break before closing. */ #define HTML_NLAFTER (1 << 6) /* Output line break after closing. */ #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER) #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND) #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE) #define HTML_INDENT (1 << 7) /* Indent content by two spaces. */ #define HTML_NOINDENT (1 << 8) /* Exception: never indent content. */ }; static const struct htmldata htmltags[TAG_MAX] = { {"html", HTML_NLALL}, {"head", HTML_NLALL | HTML_INDENT}, {"meta", HTML_NOSTACK | HTML_NLALL}, {"link", HTML_NOSTACK | HTML_NLALL}, {"style", HTML_NLALL | HTML_INDENT}, {"title", HTML_NLAROUND}, {"body", HTML_NLALL}, {"div", HTML_NLAROUND}, {"section", HTML_NLALL}, {"table", HTML_NLALL | HTML_INDENT}, {"tr", HTML_NLALL | HTML_INDENT}, {"td", HTML_NLAROUND}, {"li", HTML_NLAROUND | HTML_INDENT}, {"ul", HTML_NLALL | HTML_INDENT}, {"ol", HTML_NLALL | HTML_INDENT}, {"dl", HTML_NLALL | HTML_INDENT}, {"dt", HTML_NLAROUND}, {"dd", HTML_NLAROUND | HTML_INDENT}, {"h1", HTML_TOPHRASE | HTML_NLAROUND}, {"h2", HTML_TOPHRASE | HTML_NLAROUND}, {"p", HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT}, {"pre", HTML_TOPHRASE | HTML_NLAROUND | HTML_NOINDENT}, {"a", HTML_INPHRASE | HTML_TOPHRASE}, {"b", HTML_INPHRASE | HTML_TOPHRASE}, {"cite", HTML_INPHRASE | HTML_TOPHRASE}, {"code", HTML_INPHRASE | HTML_TOPHRASE}, {"i", HTML_INPHRASE | HTML_TOPHRASE}, {"small", HTML_INPHRASE | HTML_TOPHRASE}, {"span", HTML_INPHRASE | HTML_TOPHRASE}, {"var", HTML_INPHRASE | HTML_TOPHRASE}, {"br", HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL}, {"hr", HTML_INPHRASE | HTML_NOSTACK}, {"mark", HTML_INPHRASE }, {"math", HTML_INPHRASE | HTML_NLALL | HTML_INDENT}, {"mrow", 0}, {"mi", 0}, {"mn", 0}, {"mo", 0}, {"msup", 0}, {"msub", 0}, {"msubsup", 0}, {"mfrac", 0}, {"msqrt", 0}, {"mfenced", 0}, {"mtable", 0}, {"mtr", 0}, {"mtd", 0}, {"munderover", 0}, {"munder", 0}, {"mover", 0}, }; /* Avoid duplicate HTML id= attributes. */ struct id_entry { int ord; /* Ordinal number of the latest occurrence. */ char id[]; /* The id= attribute without any ordinal suffix. */ }; static struct ohash id_unique; static void html_reset_internal(struct html *); static void print_byte(struct html *, char); static void print_endword(struct html *); static void print_indent(struct html *); static void print_word(struct html *, const char *); static void print_ctag(struct html *, struct tag *); static int print_escape(struct html *, char); static int print_encode(struct html *, const char *, const char *, int); static void print_href(struct html *, const char *, const char *, int); static void print_metaf(struct html *); void * html_alloc(const struct manoutput *outopts) { struct html *h; h = mandoc_calloc(1, sizeof(struct html)); h->tag = NULL; h->metac = h->metal = ESCAPE_FONTROMAN; h->style = outopts->style; if ((h->base_man1 = outopts->man) == NULL) h->base_man2 = NULL; else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL) *h->base_man2++ = '\0'; h->base_includes = outopts->includes; if (outopts->fragment) h->oflags |= HTML_FRAGMENT; if (outopts->toc) h->oflags |= HTML_TOC; mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); return h; } static void html_reset_internal(struct html *h) { struct tag *tag; struct id_entry *entry; unsigned int slot; while ((tag = h->tag) != NULL) { h->tag = tag->next; free(tag); } entry = ohash_first(&id_unique, &slot); while (entry != NULL) { free(entry); entry = ohash_next(&id_unique, &slot); } ohash_delete(&id_unique); } void html_reset(void *p) { html_reset_internal(p); mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); } void html_free(void *p) { html_reset_internal(p); free(p); } void print_gen_head(struct html *h) { struct tag *t; print_otag(h, TAG_META, "?", "charset", "utf-8"); print_otag(h, TAG_META, "??", "name", "viewport", "content", "width=device-width, initial-scale=1.0"); if (h->style != NULL) { print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", h->style, "type", "text/css", "media", "all"); return; } /* * Print a minimal embedded style sheet. */ t = print_otag(h, TAG_STYLE, ""); print_text(h, "table.head, table.foot { width: 100%; }"); print_endline(h); print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }"); print_endline(h); print_text(h, "td.head-vol { text-align: center; }"); print_endline(h); print_text(h, ".Nd, .Bf, .Op { display: inline; }"); print_endline(h); print_text(h, ".Pa, .Ad { font-style: italic; }"); print_endline(h); print_text(h, ".Ms { font-weight: bold; }"); print_endline(h); print_text(h, ".Bl-diag "); print_byte(h, '>'); print_text(h, " dt { font-weight: bold; }"); print_endline(h); print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd " "{ font-weight: bold; font-family: inherit; }"); print_tagq(h, t); } int html_setfont(struct html *h, enum mandoc_esc font) { switch (font) { case ESCAPE_FONTPREV: font = h->metal; break; case ESCAPE_FONTITALIC: case ESCAPE_FONTBOLD: case ESCAPE_FONTBI: case ESCAPE_FONTROMAN: case ESCAPE_FONTCR: case ESCAPE_FONTCB: case ESCAPE_FONTCI: break; case ESCAPE_FONT: font = ESCAPE_FONTROMAN; break; default: return 0; } h->metal = h->metac; h->metac = font; return 1; } static void print_metaf(struct html *h) { if (h->metaf) { print_tagq(h, h->metaf); h->metaf = NULL; } switch (h->metac) { case ESCAPE_FONTITALIC: h->metaf = print_otag(h, TAG_I, ""); break; case ESCAPE_FONTBOLD: h->metaf = print_otag(h, TAG_B, ""); break; case ESCAPE_FONTBI: h->metaf = print_otag(h, TAG_B, ""); print_otag(h, TAG_I, ""); break; case ESCAPE_FONTCR: h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); break; case ESCAPE_FONTCB: h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); print_otag(h, TAG_B, ""); break; case ESCAPE_FONTCI: h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); print_otag(h, TAG_I, ""); break; default: break; } } void html_close_paragraph(struct html *h) { struct tag *this, *next; int flags; this = h->tag; for (;;) { next = this->next; flags = htmltags[this->tag].flags; if (flags & (HTML_INPHRASE | HTML_TOPHRASE)) print_ctag(h, this); if ((flags & HTML_INPHRASE) == 0) break; this = next; } } /* * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode. * TOKEN_NONE does not switch. The old mode is returned. */ enum roff_tok html_fillmode(struct html *h, enum roff_tok want) { struct tag *t; enum roff_tok had; for (t = h->tag; t != NULL; t = t->next) if (t->tag == TAG_PRE) break; had = t == NULL ? ROFF_fi : ROFF_nf; if (want != had) { switch (want) { case ROFF_fi: print_tagq(h, t); break; case ROFF_nf: html_close_paragraph(h); print_otag(h, TAG_PRE, ""); break; case TOKEN_NONE: break; default: abort(); } } return had; } /* * Allocate a string to be used for the "id=" attribute of an HTML * element and/or as a segment identifier for a URI in an element. * The function may fail and return NULL if the node lacks text data * to create the attribute from. * The caller is responsible for free(3)ing the returned string. * * If the "unique" argument is non-zero, the "id_unique" ohash table * is used for de-duplication. If the "unique" argument is 1, * it is the first time the function is called for this tag and * location, so if an ordinal suffix is needed, it is incremented. * If the "unique" argument is 2, it is the second time the function * is called for this tag and location, so the ordinal suffix * remains unchanged. */ char * html_make_id(const struct roff_node *n, int unique) { const struct roff_node *nch; struct id_entry *entry; char *buf, *cp; size_t len; unsigned int slot; if (n->tag != NULL) buf = mandoc_strdup(n->tag); else { switch (n->tok) { case MDOC_Sh: case MDOC_Ss: case MDOC_Sx: case MAN_SH: case MAN_SS: for (nch = n->child; nch != NULL; nch = nch->next) if (nch->type != ROFFT_TEXT) return NULL; buf = NULL; deroff(&buf, n); if (buf == NULL) return NULL; break; default: if (n->child == NULL || n->child->type != ROFFT_TEXT) return NULL; buf = mandoc_strdup(n->child->string); break; } } /* * In ID attributes, only use ASCII characters that are * permitted in URL-fragment strings according to the * explicit list at: * https://url.spec.whatwg.org/#url-fragment-string * In addition, reserve '~' for ordinal suffixes. */ for (cp = buf; *cp != '\0'; cp++) if (isalnum((unsigned char)*cp) == 0 && strchr("!$&'()*+,-./:;=?@_", *cp) == NULL) *cp = '_'; if (unique == 0) return buf; /* Avoid duplicate HTML id= attributes. */ slot = ohash_qlookup(&id_unique, buf); if ((entry = ohash_find(&id_unique, slot)) == NULL) { len = strlen(buf) + 1; entry = mandoc_malloc(sizeof(*entry) + len); entry->ord = 1; memcpy(entry->id, buf, len); ohash_insert(&id_unique, slot, entry); } else if (unique == 1) entry->ord++; if (entry->ord > 1) { cp = buf; mandoc_asprintf(&buf, "%s~%d", cp, entry->ord); free(cp); } return buf; } static int print_escape(struct html *h, char c) { switch (c) { case '<': print_word(h, "<"); 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_FONTROMAN: case ESCAPE_FONTCR: case ESCAPE_FONTCB: case ESCAPE_FONTCI: if (0 == norecurse) { h->flags |= HTML_NOSPACE; if (html_setfont(h, esc)) print_metaf(h); h->flags &= ~HTML_NOSPACE; } continue; case ESCAPE_SKIPCHAR: h->flags |= HTML_SKIPCHAR; continue; case ESCAPE_ERROR: continue; default: break; } if (h->flags & HTML_SKIPCHAR) { h->flags &= ~HTML_SKIPCHAR; continue; } switch (esc) { case ESCAPE_UNICODE: /* Skip past "u" header. */ c = mchars_num2uc(seq + 1, len - 1); break; case ESCAPE_NUMBERED: c = mchars_num2char(seq, len); if (c < 0) continue; break; case ESCAPE_SPECIAL: c = mchars_spec2cp(seq, len); if (c <= 0) continue; break; case ESCAPE_UNDEF: c = *seq; break; case ESCAPE_DEVICE: print_word(h, "html"); continue; case ESCAPE_BREAK: breakline = 1; continue; case ESCAPE_NOSPACE: if ('\0' == *p) nospace = 1; continue; case ESCAPE_OVERSTRIKE: if (len == 0) continue; c = seq[len - 1]; break; default: continue; } if ((c < 0x20 && c != 0x09) || (c > 0x7E && c < 0xA0)) c = 0xFFFD; if (c > 0x7E) { (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c); print_word(h, numbuf); } else if (print_escape(h, c) == 0) print_byte(h, c); } return nospace; } static void print_href(struct html *h, const char *name, const char *sec, int man) { struct stat sb; const char *p, *pp; char *filename; if (man) { pp = h->base_man1; if (h->base_man2 != NULL) { mandoc_asprintf(&filename, "%s.%s", name, sec); if (stat(filename, &sb) == -1) pp = h->base_man2; free(filename); } } else pp = h->base_includes; while ((p = strchr(pp, '%')) != NULL) { print_encode(h, pp, p, 1); if (man && p[1] == 'S') { if (sec == NULL) print_byte(h, '1'); else print_encode(h, sec, NULL, 1); } else if ((man && p[1] == 'N') || (man == 0 && p[1] == 'I')) print_encode(h, name, NULL, 1); else print_encode(h, p, p + 2, 1); pp = p + 2; } if (*pp != '\0') print_encode(h, pp, NULL, 1); } struct tag * print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) { va_list ap; struct tag *t; const char *attr; char *arg1, *arg2; int style_written, tflags; tflags = htmltags[tag].flags; /* Flow content is not allowed in phrasing context. */ if ((tflags & HTML_INPHRASE) == 0) { for (t = h->tag; t != NULL; t = t->next) { if (t->closed) continue; assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0); break; } /* * Always wrap phrasing elements in a paragraph * unless already contained in some flow container; * never put them directly into a section. */ } else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION) print_otag(h, TAG_P, "c", "Pp"); /* Push this tag onto the stack of open scopes. */ if ((tflags & HTML_NOSTACK) == 0) { t = mandoc_malloc(sizeof(struct tag)); t->tag = tag; t->next = h->tag; t->refcnt = 0; t->closed = 0; h->tag = t; } else t = NULL; if (tflags & HTML_NLBEFORE) print_endline(h); if (h->col == 0) print_indent(h); else if ((h->flags & HTML_NOSPACE) == 0) { if (h->flags & HTML_KEEP) print_word(h, " "); else { if (h->flags & HTML_PREKEEP) h->flags |= HTML_KEEP; print_endword(h); } } if ( ! (h->flags & HTML_NONOSPACE)) h->flags &= ~HTML_NOSPACE; else h->flags |= HTML_NOSPACE; /* Print out the tag name and attributes. */ print_byte(h, '<'); print_word(h, htmltags[tag].name); va_start(ap, fmt); while (*fmt != '\0' && *fmt != 's') { /* Parse attributes and arguments. */ arg1 = va_arg(ap, char *); arg2 = NULL; switch (*fmt++) { case 'c': attr = "class"; break; case 'h': attr = "href"; break; case 'i': attr = "id"; break; case '?': attr = arg1; arg1 = va_arg(ap, char *); break; default: abort(); } if (*fmt == 'M') arg2 = va_arg(ap, char *); if (arg1 == NULL) continue; /* Print the attributes. */ print_byte(h, ' '); print_word(h, attr); print_byte(h, '='); print_byte(h, '"'); switch (*fmt) { case 'I': print_href(h, arg1, NULL, 0); fmt++; break; case 'M': print_href(h, arg1, arg2, 1); fmt++; break; case 'R': print_byte(h, '#'); print_encode(h, arg1, NULL, 1); fmt++; break; default: print_encode(h, arg1, NULL, 1); break; } print_byte(h, '"'); } style_written = 0; while (*fmt++ == 's') { arg1 = va_arg(ap, char *); arg2 = va_arg(ap, char *); if (arg2 == NULL) continue; print_byte(h, ' '); if (style_written == 0) { print_word(h, "style=\""); style_written = 1; } print_word(h, arg1); print_byte(h, ':'); print_byte(h, ' '); print_word(h, arg2); print_byte(h, ';'); } if (style_written) print_byte(h, '"'); va_end(ap); /* Accommodate for "well-formed" singleton escaping. */ if (htmltags[tag].flags & HTML_NOSTACK) print_byte(h, '/'); print_byte(h, '>'); if (tflags & HTML_NLBEGIN) print_endline(h); else h->flags |= HTML_NOSPACE; if (tflags & HTML_INDENT) h->indent++; if (tflags & HTML_NOINDENT) h->noindent++; return t; } /* * Print an element with an optional "id=" attribute. * If the element has phrasing content and an "id=" attribute, * also add a permalink: outside if it can be in phrasing context, * inside otherwise. */ struct tag * print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr, struct roff_node *n) { struct roff_node *nch; struct tag *ret, *t; char *id, *href; ret = NULL; id = href = NULL; if (n->flags & NODE_ID) id = html_make_id(n, 1); if (n->flags & NODE_HREF) href = id == NULL ? html_make_id(n, 2) : id; if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE) ret = print_otag(h, TAG_A, "chR", "permalink", href); t = print_otag(h, elemtype, "ci", cattr, id); if (ret == NULL) { ret = t; if (href != NULL && (nch = n->child) != NULL) { /* man(7) is safe, it tags phrasing content only. */ if (n->tok > MDOC_MAX || htmltags[elemtype].flags & HTML_TOPHRASE) nch = NULL; else /* For mdoc(7), beware of nested blocks. */ while (nch != NULL && nch->type == ROFFT_TEXT) nch = nch->next; if (nch == NULL) print_otag(h, TAG_A, "chR", "permalink", href); } } free(id); if (id == NULL) free(href); return ret; } static void print_ctag(struct html *h, struct tag *tag) { int tflags; if (tag->closed == 0) { tag->closed = 1; if (tag == h->metaf) h->metaf = NULL; if (tag == h->tblt) h->tblt = NULL; tflags = htmltags[tag->tag].flags; if (tflags & HTML_INDENT) h->indent--; if (tflags & HTML_NOINDENT) h->noindent--; if (tflags & HTML_NLEND) print_endline(h); print_indent(h); print_byte(h, '<'); print_byte(h, '/'); print_word(h, htmltags[tag->tag].name); print_byte(h, '>'); if (tflags & HTML_NLAFTER) print_endline(h); } if (tag->refcnt == 0) { h->tag = tag->next; free(tag); } } void print_gen_decls(struct html *h) { print_word(h, ""); 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) { print_tagged_text(h, word, NULL); } void print_tagged_text(struct html *h, const char *word, struct roff_node *n) { struct tag *t; char *href; /* * Always wrap text in a paragraph unless already contained in * some flow container; never put it directly into a section. */ if (h->tag->tag == TAG_SECTION) print_otag(h, TAG_P, "c", "Pp"); /* Output whitespace before this text? */ if (h->col && (h->flags & HTML_NOSPACE) == 0) { if ( ! (HTML_KEEP & h->flags)) { if (HTML_PREKEEP & h->flags) h->flags |= HTML_KEEP; print_endword(h); } else print_word(h, " "); } /* * Optionally switch fonts, optionally write a permalink, then * print the text, optionally surrounded by HTML whitespace. */ assert(h->metaf == NULL); print_metaf(h); print_indent(h); if (n != NULL && (href = html_make_id(n, 2)) != NULL) { t = print_otag(h, TAG_A, "chR", "permalink", href); free(href); } else t = NULL; if ( ! print_encode(h, word, NULL, 0)) { if ( ! (h->flags & HTML_NONOSPACE)) h->flags &= ~HTML_NOSPACE; h->flags &= ~HTML_NONEWLINE; } else h->flags |= HTML_NOSPACE | HTML_NONEWLINE; if (h->metaf != NULL) { print_tagq(h, h->metaf); h->metaf = NULL; } else if (t != NULL) print_tagq(h, t); h->flags &= ~HTML_IGNDELIM; } void print_tagq(struct html *h, const struct tag *until) { struct tag *this, *next; for (this = h->tag; this != NULL; this = next) { next = this == until ? NULL : this->next; print_ctag(h, this); } } /* * Close out all open elements up to but excluding suntil. * Note that a paragraph just inside stays open together with it * because paragraphs include subsequent phrasing content. */ void print_stagq(struct html *h, const struct tag *suntil) { struct tag *this, *next; for (this = h->tag; this != NULL; this = next) { next = this->next; if (this == suntil || (next == suntil && (this->tag == TAG_P || this->tag == TAG_PRE))) break; print_ctag(h, this); } } /*********************************************************************** * Low level output functions. * They implement line breaking using a short static buffer. ***********************************************************************/ /* * Buffer one HTML output byte. * If the buffer is full, flush and deactivate it and start a new line. * If the buffer is inactive, print directly. */ static void print_byte(struct html *h, char c) { if ((h->flags & HTML_BUFFER) == 0) { putchar(c); h->col++; return; } if (h->col + h->bufcol < sizeof(h->buf)) { h->buf[h->bufcol++] = c; return; } putchar('\n'); h->col = 0; print_indent(h); putchar(' '); putchar(' '); fwrite(h->buf, h->bufcol, 1, stdout); putchar(c); h->col = (h->indent + 1) * 2 + h->bufcol + 1; h->bufcol = 0; h->flags &= ~HTML_BUFFER; } /* * If something was printed on the current output line, end it. * Not to be called right after print_indent(). */ void print_endline(struct html *h) { if (h->col == 0) return; if (h->bufcol) { putchar(' '); fwrite(h->buf, h->bufcol, 1, stdout); h->bufcol = 0; } putchar('\n'); h->col = 0; h->flags |= HTML_NOSPACE; h->flags &= ~HTML_BUFFER; } /* * Flush the HTML output buffer. * If it is inactive, activate it. */ static void print_endword(struct html *h) { if (h->noindent) { print_byte(h, ' '); return; } if ((h->flags & HTML_BUFFER) == 0) { h->col++; h->flags |= HTML_BUFFER; } else if (h->bufcol) { putchar(' '); fwrite(h->buf, h->bufcol, 1, stdout); h->col += h->bufcol + 1; } h->bufcol = 0; } /* * If at the beginning of a new output line, * perform indentation and mark the line as containing output. * Make sure to really produce some output right afterwards, * but do not use print_otag() for producing it. */ static void print_indent(struct html *h) { size_t i; if (h->col || h->noindent) return; h->col = h->indent * 2; for (i = 0; i < h->col; i++) putchar(' '); } /* * Print or buffer some characters * depending on the current HTML output buffer state. */ static void print_word(struct html *h, const char *cp) { while (*cp != '\0') print_byte(h, *cp++); } /* $Id: html.h,v 1.109 2021/09/09 14:47:24 schwarze Exp $ */ /* * Copyright (c) 2017, 2018, 2019, 2020 Ingo Schwarze * Copyright (c) 2008-2011, 2014 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 AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Internal interfaces for mandoc(1) HTML formatters. * For use by the individual HTML formatters only. */ enum htmltag { TAG_HTML, TAG_HEAD, TAG_META, TAG_LINK, TAG_STYLE, TAG_TITLE, TAG_BODY, TAG_DIV, TAG_SECTION, TAG_TABLE, TAG_TR, TAG_TD, TAG_LI, TAG_UL, TAG_OL, TAG_DL, TAG_DT, TAG_DD, TAG_H1, TAG_H2, TAG_P, TAG_PRE, TAG_A, TAG_B, TAG_CITE, TAG_CODE, TAG_I, TAG_SMALL, TAG_SPAN, TAG_VAR, TAG_BR, TAG_HR, TAG_MARK, TAG_MATH, TAG_MROW, TAG_MI, TAG_MN, TAG_MO, TAG_MSUP, TAG_MSUB, TAG_MSUBSUP, TAG_MFRAC, TAG_MSQRT, TAG_MFENCED, TAG_MTABLE, TAG_MTR, TAG_MTD, TAG_MUNDEROVER, TAG_MUNDER, TAG_MOVER, TAG_MAX }; struct tag { struct tag *next; int refcnt; int closed; enum htmltag tag; }; struct html { int flags; #define HTML_NOSPACE (1 << 0) /* suppress next space */ #define HTML_IGNDELIM (1 << 1) #define HTML_KEEP (1 << 2) #define HTML_PREKEEP (1 << 3) #define HTML_NONOSPACE (1 << 4) /* never add spaces */ #define HTML_SKIPCHAR (1 << 6) /* skip the next character */ #define HTML_NOSPLIT (1 << 7) /* do not break line before .An */ #define HTML_SPLIT (1 << 8) /* break line before .An */ #define HTML_NONEWLINE (1 << 9) /* No line break in nofill mode. */ #define HTML_BUFFER (1 << 10) /* Collect a word to see if it fits. */ #define HTML_TOCDONE (1 << 11) /* The TOC was already written. */ size_t indent; /* current output indentation level */ int noindent; /* indent disabled by
 */
	size_t		  col; /* current output byte position */
	size_t		  bufcol; /* current buf byte position */
	char		  buf[80]; /* output buffer */
	struct tag	 *tag; /* last open tag */
	struct rofftbl	  tbl; /* current table */
	struct tag	 *tblt; /* current open table scope */
	char		 *base_man1; /* bases for manpage href */
	char		 *base_man2;
	char		 *base_includes; /* base for include href */
	char		 *style; /* style-sheet URI */
	struct tag	 *metaf; /* current open font scope */
	enum mandoc_esc	  metal; /* last used font */
	enum mandoc_esc	  metac; /* current font mode */
	int		  oflags; /* output options */
#define	HTML_FRAGMENT	 (1 << 0) /* don't emit HTML/HEAD/BODY */
#define	HTML_TOC	 (1 << 1) /* emit a table of contents */
};


struct	roff_node;
struct	tbl_span;
struct	eqn_box;

void		  roff_html_pre(struct html *, const struct roff_node *);

void		  print_gen_comment(struct html *, struct roff_node *);
void		  print_gen_decls(struct html *);
void		  print_gen_head(struct html *);
struct tag	 *print_otag(struct html *, enum htmltag, const char *, ...);
struct tag	 *print_otag_id(struct html *, enum htmltag, const char *,
			struct roff_node *);
void		  print_tagq(struct html *, const struct tag *);
void		  print_stagq(struct html *, const struct tag *);
void		  print_tagged_text(struct html *, const char *,
			struct roff_node *);
void		  print_text(struct html *, const char *);
void		  print_tblclose(struct html *);
void		  print_tbl(struct html *, const struct tbl_span *);
void		  print_eqn(struct html *, const struct eqn_box *);
void		  print_endline(struct html *);

void		  html_close_paragraph(struct html *);
enum roff_tok	  html_fillmode(struct html *, enum roff_tok);
char		 *html_make_id(const struct roff_node *, int);
int		  html_setfont(struct html *, enum mandoc_esc);
/*	$Id: lib.c,v 1.15 2018/12/13 11:55:46 schwarze Exp $ */
/*
 * Copyright (c) 2009 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 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 "roff.h"
#include "libmdoc.h"

#define LINE(x, y) \
	if (0 == strcmp(p, x)) return(y);

const char *
mdoc_a2lib(const char *p)
{

#include "lib.in"

	return NULL;
}
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2014 Garrett D'Amore 
 */

/*
 * Note that we don't document "legacy" libraries that have moved into
 * libc.  While there will be section 3lib man pages for them, they
 * won't be referenced in other man pages.
 */
LINE("libadm",		"General Administrative Library (libadm, \\-ladm)")
LINE("libavl",		"AVL Tree Library (libavl, \\-lavl)")
LINE("libbsdmalloc",	"BSD Memory Allocation Library (libbsdmalloc, -lbsdmalloc)")
LINE("libbsm",		"Security and Auditing Library (libbsm, \\lbsm)")
LINE("libc",		"Standard C Library (libc, \\-lc)")
LINE("libc_db",		"Threads Debugging Library (libc_db, \\-lc_db)")
LINE("libcfgadm",	"Configuration Adminstration Library (libcfgadm, \\-lcfgadm)")
LINE("libcommputil",	"Communication Protocol Parser Utilities Library (libpcommutil, \\-lcommputil)")
LINE("libcontract",	"Contract Management Library (libcontract, \\-lcontract)")
LINE("libcpc",		"CPU Performance Counters Library (libcpc, \\-lcpc)")
LINE("libcurses",	"Curses Library (libcurses, \\-lcurses)")
LINE("libdat",		"Direct Access Transport Library (libdat, \\-ldat)")
LINE("libdevid",	"Device ID Library (libdevid, \\-ldevid)")
LINE("libdevinfo",	"Device Information Library (libdevinfo, \\-ldevinfo)")
LINE("libdlpi",		"Data Link Provider Interface (DLPI) Library (libdlpi, \\-ldlpi)")
LINE("libdns_sd",	"DNS Service Discovery Library (libdns_sd, \\-ldns_sd)")
LINE("libelf",		"ELF Access Library (libelf, \\-lelf)")
LINE("libexacct",	"Extended Accounting File Access Library (libexacct, \\-lexacct)")
LINE("libfcoe",		"FCoE Port Management Library (libfcoe, \\-lfcoe)")
LINE("libfstyp",	"File System Type Identification Library (libfstyp, \\-lfstyp \\-lnvpair)")
LINE("libgen",		"String Pattern Matching Library (libgen, \\-lgen)")
LINE("libgss",		"Generic Security Services Library (libgss, \\-lgss)")
LINE("libiscsit",	"iSCSI Management Library (libiscsit, \\-liscsit)")
LINE("libjedec",	"JEDEC Support Library Functions (libjedec, \\-ljedec)")
LINE("libkstat",	"Kernel Statistics Library (libkstat, \\-lkstat)")
LINE("libkvm",		"Kernel VM Library (libkvm, \\-lkvm)")
LINE("libldap",		"LDAP Library (libldap, \\-lldap)")
LINE("liblgrp",		"Locality Group Library (liblgrp, -llgrp)")
LINE("libm",		"Mathematical Library (libm, -lm)")
LINE("libmail",		"User Mailbox Library (libmail, -lmail)")
LINE("libmalloc",	"Memory Allocation Library (libmalloc, -lmalloc)")
LINE("libmd",		"Message Digest Library (libmd, -lmd)")
LINE("libmp",		"Multiple Precision Library (libmp, -lmp)")
LINE("libmpapi",	"Common Multipath Management Library (libmpapi, -lMPAPI)")
LINE("libnsl",		"Network Services Library (libnsl, \\-lnsl)")
LINE("libnvpair",	"Name-Value Pair Library (libnvpair, \\-lnvpair)")
LINE("libofmt",		"Formatted output library (libofmt, \\-lofmt)")
LINE("libpam",		"PAM (Pluggable Authentication Module) Library (libpam, \\-lpam)")
LINE("libpicl",		"PICL Library (libpicl, \\-lpicl)")
LINE("libpicltree",	"PICL Plug-In Library (libpicltree, \\-lpicltree)")
LINE("libpkcs11",	"PKCS#11 Cryptographic Framework Library (libpkcs11, \\-lpkcs11)")
LINE("libpool",		"Pool Configuration Manipulation Library (libpool, \\-lpool)")
LINE("libproc",		"Process Control Library (libproc, \\-lproc)")
LINE("libproject",	"Project Database Access Library (libproject, \\-lproject)")
LINE("libresolv",	"Resolver Library (libresolv, \\-lresolv \\-lsocket \\-lnsl)")
LINE("librpc",		"RPC Service Library (librpcsvc, \\-lrpc)")
LINE("librsm",		"Remote Shared Memory Interface Library (librsm, \\-lrsm)")
LINE("libsasl",		"Simple Authentication and Security Library (libsasl, \\-lsasl)")
LINE("libscf",		"Service Configuration Facility Library (libscf, \\-lscf)")
LINE("libsec",		"File Access Control Library (libsec, \\-lsec)")
LINE("libsecdb",	"Security Attributes Database Library (libsecdb, \\-lsecdb)")
LINE("libsip",		"Session Initiation Protocol Library (libsip, \\-lsip)")
LINE("libslp",		"Service Location Protocol Library (libslp, \\-lslp)")
LINE("libsocket",	"Sockets Library (libsocket, \\-lsocket)")
LINE("libstmf",		"SCSI Target Mode Framework Library (libstmf, \\-lstmf)")
LINE("libsysevet",	"System Event Inteface Library (libsysevent, \\-lsysevent)")
LINE("libtecla",	"Interactive Command Line Input Library (libtecla, \\-ltecla)")
LINE("libtsol",		"Trusted Extensions Library (libtsol, \\-ltsol)")
LINE("libumem",		"Object-Caching Memory Allocation Library (libumem, \\-lumem")
LINE("libutempter",	"UTEMPTER Library (libutempter, \\-lutempter)")
LINE("libuuid",		"UUID Library (libuuid, \\-luuid)")
LINE("libvolmgt",	"Volume Management Library (libvolmgt, \\-lvolmgt)")
LINE("libxcurses",	"X/Open Curses Library (libxcurses, \\-lxcurses)")
LINE("libxnet",		"X/Open Networking Library (libxnet, \\-lxnet)")
/*	$Id: libman.h,v 1.86 2018/12/31 10:04:39 schwarze Exp $ */
/*
 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
 * Copyright (c) 2014, 2015, 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.
 */

struct	roff_node;
struct	roff_man;

#define	MACRO_PROT_ARGS	  struct roff_man *man, \
			  enum roff_tok tok, \
			  int line, \
			  int ppos, \
			  int *pos, \
			  char *buf

struct	man_macro {
	void		(*fp)(MACRO_PROT_ARGS);
	int		  flags;
#define	MAN_BSCOPED	 (1 << 0)  /* Optional next-line block scope. */
#define	MAN_ESCOPED	 (1 << 1)  /* Optional next-line element scope. */
#define	MAN_NSCOPED	 (1 << 2)  /* Allowed in next-line element scope. */
#define	MAN_XSCOPE	 (1 << 3)  /* Exit next-line block scope. */
#define	MAN_JOIN	 (1 << 4)  /* Join arguments together. */
};

const struct man_macro *man_macro(enum roff_tok);

void		  man_descope(struct roff_man *, int, int, char *);
void		  man_unscope(struct roff_man *, const struct roff_node *);
/* $Id: libmandoc.h,v 1.80 2021/06/27 17:57:54 schwarze Exp $ */
/*
 * Copyright (c) 2013-2015,2017,2018,2020 Ingo Schwarze 
 * Copyright (c) 2009, 2010, 2011, 2012 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.
 *
 * Internal interfaces for parser utilities needed by multiple parsers
 * and the top-level functions to call the mdoc, man, and roff parsers.
 */

/*
 * Return codes passed from the roff parser to the main parser.
 */

/* Main instruction: what to do with the returned line. */
#define	ROFF_IGN	0x000	/* Don't do anything with it. */
#define	ROFF_CONT	0x001	/* Give it to the high-level parser. */
#define	ROFF_RERUN	0x002	/* Re-run the roff parser with an offset. */
#define	ROFF_REPARSE	0x004	/* Recursively run the main parser on it. */
#define	ROFF_SO		0x008	/* Include the named file. */
#define	ROFF_MASK	0x00f	/* Only one of these bits should be set. */

/* Options for further parsing, to be OR'ed with the above. */
#define	ROFF_APPEND	0x010	/* Append the next line to this one. */
#define	ROFF_USERCALL	0x020	/* Start execution of a new macro. */
#define	ROFF_USERRET	0x040	/* Abort execution of the current macro. */
#define	ROFF_WHILE	0x100	/* Start a new .while loop. */
#define	ROFF_LOOPCONT	0x200	/* Iterate the current .while loop. */
#define	ROFF_LOOPEXIT	0x400	/* Exit the current .while loop. */
#define	ROFF_LOOPMASK	0xf00


struct	buf {
	char		*buf;
	size_t		 sz;
	struct buf	*next;
};


struct	roff;
struct	roff_man;
struct	roff_node;

char		*mandoc_normdate(struct roff_node *, struct roff_node *);
int		 mandoc_eos(const char *, size_t);
int		 mandoc_strntoi(const char *, size_t, int);
const char	*mandoc_a2msec(const char*);

int		 mdoc_parseln(struct roff_man *, int, char *, int);
void		 mdoc_endparse(struct roff_man *);

int		 man_parseln(struct roff_man *, int, char *, int);
void		 man_endparse(struct roff_man *);

int		 preconv_cue(const struct buf *, size_t);
int		 preconv_encode(const struct buf *, size_t *,
			struct buf *, size_t *, int *);

void		 roff_free(struct roff *);
struct roff	*roff_alloc(int);
void		 roff_reset(struct roff *);
void		 roff_man_free(struct roff_man *);
struct roff_man	*roff_man_alloc(struct roff *, const char *, int);
void		 roff_man_reset(struct roff_man *);
int		 roff_parseln(struct roff *, int, struct buf *, int *, size_t);
void		 roff_userret(struct roff *);
void		 roff_endparse(struct roff *);
void		 roff_setreg(struct roff *, const char *, int, char);
int		 roff_getreg(struct roff *, const char *);
char		*roff_strdup(const struct roff *, const char *);
char		*roff_getarg(struct roff *, char **, int, int *);
int		 roff_getcontrol(const struct roff *,
			const char *, int *);
int		 roff_getformat(const struct roff *);
/*	$Id: libmdoc.h,v 1.117 2018/12/31 04:55:46 schwarze Exp $ */
/*
 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
 * Copyright (c) 2013,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 AUTHORS DISCLAIM ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

struct	roff_node;
struct	roff_man;
struct	mdoc_arg;

#define	MACRO_PROT_ARGS	struct roff_man *mdoc, \
			enum roff_tok tok, \
			int line, \
			int ppos, \
			int *pos, \
			char *buf

struct	mdoc_macro {
	void		(*fp)(MACRO_PROT_ARGS);
	int		  flags;
#define	MDOC_CALLABLE	 (1 << 0)
#define	MDOC_PARSED	 (1 << 1)
#define	MDOC_EXPLICIT	 (1 << 2)
#define	MDOC_PROLOGUE	 (1 << 3)
#define	MDOC_IGNDELIM	 (1 << 4)
#define	MDOC_JOIN	 (1 << 5)
};

enum	margserr {
	ARGS_ERROR,
	ARGS_EOLN, /* end-of-line */
	ARGS_WORD, /* normal word */
	ARGS_ALLOC, /* normal word from roff_getarg() */
	ARGS_PUNCT, /* series of punctuation */
	ARGS_PHRASE /* Bl -column phrase */
};

/*
 * A punctuation delimiter is opening, closing, or "middle mark"
 * punctuation.  These govern spacing.
 * Opening punctuation (e.g., the opening parenthesis) suppresses the
 * following space; closing punctuation (e.g., the closing parenthesis)
 * suppresses the leading space; middle punctuation (e.g., the vertical
 * bar) can do either.  The middle punctuation delimiter bends the rules
 * depending on usage.
 */
enum	mdelim {
	DELIM_NONE = 0,
	DELIM_OPEN,
	DELIM_MIDDLE,
	DELIM_CLOSE,
	DELIM_MAX
};

const struct mdoc_macro *mdoc_macro(enum roff_tok);

void		  mdoc_elem_alloc(struct roff_man *, int, int,
			enum roff_tok, struct mdoc_arg *);
struct roff_node *mdoc_block_alloc(struct roff_man *, int, int,
			enum roff_tok, struct mdoc_arg *);
void		  mdoc_tail_alloc(struct roff_man *, int, int,
			enum roff_tok);
struct roff_node *mdoc_endbody_alloc(struct roff_man *, int, int,
			enum roff_tok, struct roff_node *);
void		  mdoc_state(struct roff_man *, struct roff_node *);
const char	 *mdoc_a2arch(const char *);
const char	 *mdoc_a2att(const char *);
const char	 *mdoc_a2lib(const char *);
enum roff_sec	  mdoc_a2sec(const char *);
const char	 *mdoc_a2st(const char *);
void		  mdoc_argv(struct roff_man *, int, enum roff_tok,
			struct mdoc_arg **, int *, char *);
enum margserr	  mdoc_args(struct roff_man *, int,
			int *, char *, enum roff_tok, char **);
enum mdelim	  mdoc_isdelim(const char *);
/* $Id: main.c,v 1.358 2021/09/04 22:38:46 schwarze Exp $ */
/*
 * Copyright (c) 2010-2012, 2014-2021 Ingo Schwarze 
 * Copyright (c) 2008-2012 Kristaps Dzonsons 
 * 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.
 *
 * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1).
 */
#include "config.h"

#include 
#include 
#include 	/* MACHINE */
#include 
#include 

#include 
#include 
#if HAVE_ERR
#include 
#endif
#include 
#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 "term_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	outstate {
	struct tag_files *tag_files;	/* Tagging state variables. */
	void		 *outdata;	/* data for output */
	int		  use_pager;
	int		  wstop;	/* stop after a file with a warning */
	int		  had_output;	/* Some output was generated. */
	enum outt	  outtype;	/* which output to use */
};


int			  mandocdb(int, char *[]);

static	void		  check_xr(struct manpaths *);
static	void		  fs_append(char **, size_t, int,
				size_t, const char *, enum form,
				struct manpage **, size_t *);
static	int		  fs_lookup(const struct manpaths *, size_t,
				const char *, const char *, const char *,
				struct manpage **, size_t *);
static	int		  fs_search(const struct mansearch *,
				const struct manpaths *, const char *,
				struct manpage **, size_t *);
static	void		  glob_esc(char **, const char *, const char *);
static	void		  outdata_alloc(struct outstate *, struct manoutput *);
static	void		  parse(struct mparse *, int, const char *,
				struct outstate *, struct manconf *);
static	void		  passthrough(int, int);
static	void		  process_onefile(struct mparse *, struct manpage *,
				int, struct outstate *, struct manconf *);
static	void		  run_pager(struct outstate *, char *);
static	pid_t		  spawn_pager(struct outstate *, char *);
static	void		  usage(enum argmode) __attribute__((__noreturn__));
static	int		  woptions(char *, enum mandoc_os *, int *);

static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
static	char		  help_arg[] = "help";
static	char		 *help_argv[] = {help_arg, NULL};


int
main(int argc, char *argv[])
{
	struct manconf	 conf;		/* Manpaths and output options. */
	struct outstate	 outst;		/* Output state. */
	struct winsize	 ws;		/* Result of ioctl(TIOCGWINSZ). */
	struct mansearch search;	/* Search options. */
	struct manpage	*res;		/* Complete list of search results. */
	struct manpage	*resn;		/* Search results for one name. */
	struct mparse	*mp;		/* Opaque parser object. */
	const char	*conf_file;	/* -C: alternate config file. */
	const char	*os_s;		/* -I: Operating system for display. */
	const char	*progname, *sec, *ep;
	char		*defpaths;	/* -M: override manpaths. */
	char		*auxpaths;	/* -m: additional manpaths. */
	char		*oarg;		/* -O: output option string. */
	char		*tagarg;	/* -O tag: default value. */
	unsigned char	*uc;
	size_t		 ressz;		/* Number of elements in res[]. */
	size_t		 resnsz;	/* Number of elements in resn[]. */
	size_t		 i, ib, ssz;
	int		 options;	/* Parser options. */
	int		 show_usage;	/* Invalid argument: give up. */
	int		 prio, best_prio;
	int		 startdir;
	int		 c;
	enum mandoc_os	 os_e;		/* Check base system conventions. */
	enum outmode	 outmode;	/* According to command line. */

#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 wpath cpath 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 = NULL;
	defpaths = auxpaths = NULL;

	memset(&search, 0, sizeof(struct mansearch));
	search.outkey = "Nd";
	oarg = NULL;

	if (strcmp(progname, BINM_MAN) == 0)
		search.argmode = ARG_NAME;
	else if (strcmp(progname, BINM_APROPOS) == 0)
		search.argmode = ARG_EXPR;
	else if (strcmp(progname, BINM_WHATIS) == 0)
		search.argmode = ARG_WORD;
	else if (strncmp(progname, "help", 4) == 0)
		search.argmode = ARG_NAME;
	else
		search.argmode = ARG_FILE;

	/* Parser options. */

	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
	os_e = MANDOC_OS_OTHER;
	os_s = NULL;

	/* Formatter options. */

	memset(&outst, 0, sizeof(outst));
	outst.tag_files = NULL;
	outst.outtype = OUTT_LOCALE;
	outst.use_pager = 1;

	show_usage = 0;
	outmode = OUTMODE_DEF;

	while ((c = getopt(argc, argv,
	    "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
		if (c == 'i' && search.argmode == ARG_EXPR) {
			optind--;
			break;
		}
		switch (c) {
		case 'a':
			outmode = OUTMODE_ALL;
			break;
		case 'C':
			conf_file = optarg;
			break;
		case 'c':
			outst.use_pager = 0;
			break;
		case 'f':
			search.argmode = ARG_WORD;
			break;
		case 'h':
			conf.output.synopsisonly = 1;
			outst.use_pager = 0;
			outmode = OUTMODE_ALL;
			break;
		case 'I':
			if (strncmp(optarg, "os=", 3) != 0) {
				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
				    "-I %s", optarg);
				return mandoc_msg_getrc();
			}
			if (os_s != NULL) {
				mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0,
				    "-I %s", optarg);
				return mandoc_msg_getrc();
			}
			os_s = optarg + 3;
			break;
		case 'K':
			options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
			if (strcmp(optarg, "utf-8") == 0)
				options |=  MPARSE_UTF8;
			else if (strcmp(optarg, "iso-8859-1") == 0)
				options |=  MPARSE_LATIN1;
			else if (strcmp(optarg, "us-ascii") != 0) {
				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
				    "-K %s", optarg);
				return mandoc_msg_getrc();
			}
			break;
		case 'k':
			search.argmode = ARG_EXPR;
			break;
		case 'l':
			search.argmode = ARG_FILE;
			outmode = OUTMODE_ALL;
			break;
		case 'M':
			defpaths = optarg;
			break;
		case 'm':
			auxpaths = optarg;
			break;
		case 'O':
			oarg = optarg;
			break;
		case 'S':
			search.arch = optarg;
			break;
		case 's':
			search.sec = optarg;
			break;
		case 'T':
			if (strcmp(optarg, "ascii") == 0)
				outst.outtype = OUTT_ASCII;
			else if (strcmp(optarg, "lint") == 0) {
				outst.outtype = OUTT_LINT;
				mandoc_msg_setoutfile(stdout);
				mandoc_msg_setmin(MANDOCERR_BASE);
			} else if (strcmp(optarg, "tree") == 0)
				outst.outtype = OUTT_TREE;
			else if (strcmp(optarg, "man") == 0)
				outst.outtype = OUTT_MAN;
			else if (strcmp(optarg, "html") == 0)
				outst.outtype = OUTT_HTML;
			else if (strcmp(optarg, "markdown") == 0)
				outst.outtype = OUTT_MARKDOWN;
			else if (strcmp(optarg, "utf8") == 0)
				outst.outtype = OUTT_UTF8;
			else if (strcmp(optarg, "locale") == 0)
				outst.outtype = OUTT_LOCALE;
			else if (strcmp(optarg, "ps") == 0)
				outst.outtype = OUTT_PS;
			else if (strcmp(optarg, "pdf") == 0)
				outst.outtype = OUTT_PDF;
			else {
				mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
				    "-T %s", optarg);
				return mandoc_msg_getrc();
			}
			break;
		case 'W':
			if (woptions(optarg, &os_e, &outst.wstop) == -1)
				return mandoc_msg_getrc();
			break;
		case 'w':
			outmode = OUTMODE_FLN;
			break;
		default:
			show_usage = 1;
			break;
		}
	}

	if (show_usage)
		usage(search.argmode);

	/* Postprocess options. */

	switch (outmode) {
	case OUTMODE_DEF:
		switch (search.argmode) {
		case ARG_FILE:
			outmode = OUTMODE_ALL;
			outst.use_pager = 0;
			break;
		case ARG_NAME:
			outmode = OUTMODE_ONE;
			break;
		default:
			outmode = OUTMODE_LST;
			break;
		}
		break;
	case OUTMODE_FLN:
		if (search.argmode == ARG_FILE)
			outmode = OUTMODE_ALL;
		break;
	case OUTMODE_ALL:
		break;
	case OUTMODE_LST:
	case OUTMODE_ONE:
		abort();
	}

	if (oarg != NULL) {
		if (outmode == OUTMODE_LST)
			search.outkey = oarg;
		else {
			while (oarg != NULL) {
				if (manconf_output(&conf.output,
				    strsep(&oarg, ","), 0) == -1)
					return mandoc_msg_getrc();
			}
		}
	}

	if (outst.outtype != OUTT_TREE || conf.output.noval == 0)
		options |= MPARSE_VALIDATE;

	if (outmode == OUTMODE_FLN ||
	    outmode == OUTMODE_LST ||
	    (conf.output.outfilename == NULL &&
	     conf.output.tagfilename == NULL &&
	     isatty(STDOUT_FILENO) == 0))
		outst.use_pager = 0;

	if (outst.use_pager &&
	    (conf.output.width == 0 || conf.output.indent == 0) &&
	    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
	    ws.ws_col > 1) {
		if (conf.output.width == 0 && ws.ws_col < 79)
			conf.output.width = ws.ws_col - 1;
		if (conf.output.indent == 0 && ws.ws_col < 66)
			conf.output.indent = 3;
	}

#if HAVE_PLEDGE
	if (outst.use_pager == 0)
		c = pledge("stdio rpath", NULL);
	else if (conf.output.outfilename != NULL ||
	    conf.output.tagfilename != NULL)
		c = pledge("stdio rpath wpath cpath", NULL);
	else
		c = pledge("stdio rpath tmppath tty proc exec", NULL);
	if (c == -1) {
		mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno));
		return mandoc_msg_getrc();
	}
#endif

	/* Parse arguments. */

	if (argc > 0) {
		argc -= optind;
		argv += optind;
	}

	/*
	 * Quirks for help(1) and man(1),
	 * in particular for a section argument without -s.
	 */

	if (search.argmode == ARG_NAME) {
		if (*progname == 'h') {
			if (argc == 0) {
				argv = help_argv;
				argc = 1;
			}
		} else if (argc > 1 &&
		    ((uc = (unsigned char *)argv[0]) != NULL) &&
		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
		      isalpha(uc[1]))) ||
		     (uc[0] == 'n' && uc[1] == '\0'))) {
			search.sec = (char *)uc;
			argv++;
			argc--;
		}
		if (search.arch == NULL)
			search.arch = getenv("MACHINE");
#ifdef MACHINE
		if (search.arch == NULL)
			search.arch = MACHINE;
#endif
		if (outmode == OUTMODE_ONE)
			search.firstmatch = 1;
	}

	/*
	 * Use the first argument for -O tag in addition to
	 * using it as a search term for man(1) or apropos(1).
	 */

	if (conf.output.tag != NULL && *conf.output.tag == '\0') {
		tagarg = argc > 0 && search.argmode == ARG_EXPR ?
		    strchr(*argv, '=') : NULL;
		conf.output.tag = tagarg == NULL ? *argv : tagarg + 1;
	}

	/* Read the configuration file. */

	if (search.argmode != ARG_FILE ||
	    mandoc_msg_getmin() == MANDOCERR_STYLE)
		manconf_parse(&conf, conf_file, defpaths, auxpaths);

	/* man(1): Resolve each name individually. */

	if (search.argmode == ARG_NAME) {
		if (argc < 1) {
			if (outmode != OUTMODE_FLN)
				usage(ARG_NAME);
			if (conf.manpath.sz == 0) {
				warnx("The manpath is empty.");
				mandoc_msg_setrc(MANDOCLEVEL_BADARG);
			} else {
				for (i = 0; i + 1 < conf.manpath.sz; i++)
					printf("%s:", conf.manpath.paths[i]);
				printf("%s\n", conf.manpath.paths[i]);
			}
			manconf_free(&conf);
			return (int)mandoc_msg_getrc();
		}
		for (res = NULL, ressz = 0; argc > 0; argc--, argv++) {
			(void)mansearch(&search, &conf.manpath,
			    1, argv, &resn, &resnsz);
			if (resnsz == 0)
				(void)fs_search(&search, &conf.manpath,
				    *argv, &resn, &resnsz);
			if (resnsz == 0 && strchr(*argv, '/') == NULL) {
				if (search.arch != NULL &&
				    arch_valid(search.arch, OSENUM) == 0)
					warnx("Unknown architecture \"%s\".",
					    search.arch);
				else if (search.sec != NULL)
					warnx("No entry for %s in "
					    "section %s of the manual.",
					    *argv, search.sec);
				else
					warnx("No entry for %s in "
					    "the manual.", *argv);
				mandoc_msg_setrc(MANDOCLEVEL_BADARG);
				continue;
			}
			if (resnsz == 0) {
				if (access(*argv, R_OK) == -1) {
					mandoc_msg_setinfilename(*argv);
					mandoc_msg(MANDOCERR_BADARG_BAD,
					    0, 0, "%s", strerror(errno));
					mandoc_msg_setinfilename(NULL);
					continue;
				}
				resnsz = 1;
				resn = mandoc_calloc(resnsz, sizeof(*res));
				resn->file = mandoc_strdup(*argv);
				resn->ipath = SIZE_MAX;
				resn->form = FORM_SRC;
			}
			if (outmode != OUTMODE_ONE || resnsz == 1) {
				res = mandoc_reallocarray(res,
				    ressz + resnsz, sizeof(*res));
				memcpy(res + ressz, resn,
				    sizeof(*resn) * resnsz);
				ressz += resnsz;
				continue;
			}

			/* Search for the best section. */

			best_prio = 40;
			for (ib = i = 0; i < resnsz; i++) {
				sec = resn[i].file;
				sec += strcspn(sec, "123456789");
				if (sec[0] == '\0')
					continue; /* No section at all. */
				prio = sec_prios[sec[0] - '1'];
				if (search.sec != NULL) {
					ssz = strlen(search.sec);
					if (strncmp(sec, search.sec, ssz) == 0)
						sec += ssz;
				} else
					sec++; /* Prefer without suffix. */
				if (*sec != '/')
					prio += 10; /* Wrong dir name. */
				if (search.sec != NULL) {
					ep = strchr(sec, '\0');
					if (ep - sec > 3 &&
					    strncmp(ep - 3, ".gz", 3) == 0)
						ep -= 3;
					if ((size_t)(ep - sec) < ssz + 3 ||
					    strncmp(ep - ssz, search.sec,
					     ssz) != 0)      /* Wrong file */
						prio += 20;  /* extension. */
				}
				if (prio >= best_prio)
					continue;
				best_prio = prio;
				ib = i;
			}
			res = mandoc_reallocarray(res, ressz + 1,
			    sizeof(*res));
			memcpy(res + ressz++, resn + ib, sizeof(*resn));
		}

	/* apropos(1), whatis(1): Process the full search expression. */

	} else if (search.argmode != ARG_FILE) {
		if (mansearch(&search, &conf.manpath,
		    argc, argv, &res, &ressz) == 0)
			usage(search.argmode);

		if (ressz == 0) {
			warnx("nothing appropriate");
			mandoc_msg_setrc(MANDOCLEVEL_BADARG);
			goto out;
		}

	/* mandoc(1): Take command line arguments as file names. */

	} else {
		ressz = argc > 0 ? argc : 1;
		res = mandoc_calloc(ressz, sizeof(*res));
		for (i = 0; i < ressz; i++) {
			if (argc > 0)
				res[i].file = mandoc_strdup(argv[i]);
			res[i].ipath = SIZE_MAX;
			res[i].form = FORM_SRC;
		}
	}

	switch (outmode) {
	case OUTMODE_FLN:
		for (i = 0; i < ressz; i++)
			puts(res[i].file);
		goto out;
	case OUTMODE_LST:
		for (i = 0; i < ressz; i++)
			printf("%s - %s\n", res[i].names,
			    res[i].output == NULL ? "" :
			    res[i].output);
		goto out;
	default:
		break;
	}

	if (search.argmode == ARG_FILE && auxpaths != NULL) {
		if (strcmp(auxpaths, "doc") == 0)
			options |= MPARSE_MDOC;
		else if (strcmp(auxpaths, "an") == 0)
			options |= MPARSE_MAN;
	}

	mchars_alloc();
	mp = mparse_alloc(options, os_e, os_s);

	/*
	 * Remember the original working directory, if possible.
	 * This will be needed if some names on the command line
	 * are page names and some are relative file names.
	 * Do not error out if the current directory is not
	 * readable: Maybe it won't be needed after all.
	 */
	startdir = open(".", O_RDONLY | O_DIRECTORY);
	for (i = 0; i < ressz; i++) {
		process_onefile(mp, res + i, startdir, &outst, &conf);
		if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
			break;
	}
	if (startdir != -1) {
		(void)fchdir(startdir);
		close(startdir);
	}
	if (conf.output.tag != NULL && conf.output.tag_found == 0) {
		mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag);
		conf.output.tag = NULL;
	}
	if (outst.outdata != NULL) {
		switch (outst.outtype) {
		case OUTT_HTML:
			html_free(outst.outdata);
			break;
		case OUTT_UTF8:
		case OUTT_LOCALE:
		case OUTT_ASCII:
			ascii_free(outst.outdata);
			break;
		case OUTT_PDF:
		case OUTT_PS:
			pspdf_free(outst.outdata);
			break;
		default:
			break;
		}
	}
	mandoc_xr_free();
	mparse_free(mp);
	mchars_free();

out:
	mansearch_free(res, ressz);
	if (search.argmode != ARG_FILE)
		manconf_free(&conf);

	if (outst.tag_files != NULL) {
		if (term_tag_close() != -1 &&
		    conf.output.outfilename == NULL &&
		    conf.output.tagfilename == NULL)
			run_pager(&outst, conf.output.tag);
		term_tag_unlink();
	} else if (outst.had_output && outst.outtype != OUTT_LINT)
		mandoc_msg_summary();

	return (int)mandoc_msg_getrc();
}

static void
usage(enum argmode argmode)
{
	switch (argmode) {
	case ARG_FILE:
		fputs("usage: mandoc [-ac] [-I os=name] "
		    "[-K encoding] [-mdoc | -man] [-O options]\n"
		    "\t      [-T output] [-W level] [file ...]\n", stderr);
		break;
	case ARG_NAME:
		fputs("usage: man [-acfhklw] [-C file] [-M path] "
		    "[-m path] [-S subsection]\n"
		    "\t   [[-s] section] name ...\n", stderr);
		break;
	case ARG_WORD:
		fputs("usage: whatis [-afk] [-C file] "
		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
		    "\t      [-s section] name ...\n", stderr);
		break;
	case ARG_EXPR:
		fputs("usage: apropos [-afk] [-C file] "
		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
		    "\t       [-s section] expression ...\n", stderr);
		break;
	}
	exit((int)MANDOCLEVEL_BADARG);
}

static void
glob_esc(char **dst, const char *src, const char *suffix)
{
	while (*src != '\0') {
		if (strchr("*?[", *src) != NULL)
			*(*dst)++ = '\\';
		*(*dst)++ = *src++;
	}
	while (*suffix != '\0')
		*(*dst)++ = *suffix++;
}

static void
fs_append(char **file, size_t filesz, int copy, size_t ipath,
    const char *sec, enum form form, struct manpage **res, size_t *ressz)
{
	struct manpage	*page;

	*res = mandoc_reallocarray(*res, *ressz + filesz, sizeof(**res));
	page = *res + *ressz;
	*ressz += filesz;
	for (;;) {
		page->file = copy ? mandoc_strdup(*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;
		if (--filesz == 0)
			break;
		file++;
		page++;
	}
}

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;
	char		*file, *cp, secnum[2];
	int		 globres;
	enum form	 form;

	const char *const slman = "/man";
	const char *const slash = "/";
	const char *const sglob = ".[01-9]*";
	const char *const dot   = ".";
	const char *const aster = "*";

	memset(&globinfo, 0, sizeof(globinfo));
	form = FORM_SRC;

	mandoc_asprintf(&file, "%s/man%s/%s.%s",
	    paths->paths[ipath], sec, name, sec);
	if (stat(file, &sb) != -1)
		goto found;
	free(file);

	mandoc_asprintf(&file, "%s/cat%s/%s.0",
	    paths->paths[ipath], sec, name);
	if (stat(file, &sb) != -1) {
		form = FORM_CAT;
		goto found;
	}
	free(file);

	if (arch != NULL) {
		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
		    paths->paths[ipath], sec, arch, name, sec);
		if (stat(file, &sb) != -1)
			goto found;
		free(file);
	}

	cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
	    strlen(slman) + strlen(sec) * 2 + strlen(slash) +
	    strlen(name) * 2 + strlen(sglob) + 1);
	glob_esc(&cp, paths->paths[ipath], slman);
	glob_esc(&cp, sec, slash);
	glob_esc(&cp, name, sglob);
	*cp = '\0';
	globres = glob(file, 0, NULL, &globinfo);
	if (globres != 0 && globres != GLOB_NOMATCH)
		mandoc_msg(MANDOCERR_GLOB, 0, 0,
		    "%s: %s", file, strerror(errno));
	free(file);
	file = NULL;
	if (globres == 0)
		goto found;
	globfree(&globinfo);

	if (sec[1] != '\0' && *ressz == 0) {
		secnum[0] = sec[0];
		secnum[1] = '\0';
		cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 +
		    strlen(slman) + strlen(secnum) * 2 + strlen(slash) +
		    strlen(name) * 2 + strlen(dot) +
		    strlen(sec) * 2 + strlen(aster) + 1);
		glob_esc(&cp, paths->paths[ipath], slman);
		glob_esc(&cp, secnum, slash);
		glob_esc(&cp, name, dot);
		glob_esc(&cp, sec, aster);
		*cp = '\0';
		globres = glob(file, 0, NULL, &globinfo);
		if (globres != 0 && globres != GLOB_NOMATCH)
			mandoc_msg(MANDOCERR_GLOB, 0, 0,
			    "%s: %s", file, strerror(errno));
		free(file);
		file = NULL;
		if (globres == 0)
			goto found;
		globfree(&globinfo);
	}

	if (res != NULL || ipath + 1 != paths->sz)
		return -1;

	mandoc_asprintf(&file, "%s.%s", name, sec);
	globres = stat(file, &sb);
	free(file);
	return globres;

found:
	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
	if (res == NULL)
		free(file);
	else if (file == NULL)
		fs_append(globinfo.gl_pathv, globinfo.gl_pathc, 1,
		    ipath, sec, form, res, ressz);
	else
		fs_append(&file, 1, 0, ipath, sec, form, res, ressz);
	globfree(&globinfo);
	return 0;
}

static int
fs_search(const struct mansearch *cfg, const struct manpaths *paths,
	const char *name, struct manpage **res, size_t *ressz)
{
	const char *const sections[] =
	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
	const size_t nsec = sizeof(sections)/sizeof(sections[0]);

	size_t		 ipath, isec;

	assert(cfg->argmode == ARG_NAME);
	if (res != NULL)
		*res = NULL;
	*ressz = 0;
	for (ipath = 0; ipath < paths->sz; ipath++) {
		if (cfg->sec != NULL) {
			if (fs_lookup(paths, ipath, cfg->sec, cfg->arch,
			    name, res, ressz) != -1 && cfg->firstmatch)
				return 0;
		} else {
			for (isec = 0; isec < nsec; isec++)
				if (fs_lookup(paths, ipath, sections[isec],
				    cfg->arch, name, res, ressz) != -1 &&
				    cfg->firstmatch)
					return 0;
		}
	}
	return -1;
}

static void
process_onefile(struct mparse *mp, struct manpage *resp, int startdir,
    struct outstate *outst, struct manconf *conf)
{
	int	 fd;

	/*
	 * Changing directories is not needed in ARG_FILE mode.
	 * Do it on a best-effort basis.  Even in case of
	 * failure, some functionality may still work.
	 */
	if (resp->ipath != SIZE_MAX)
		(void)chdir(conf->manpath.paths[resp->ipath]);
	else if (startdir != -1)
		(void)fchdir(startdir);

	mandoc_msg_setinfilename(resp->file);
	if (resp->file != NULL) {
		if ((fd = mparse_open(mp, resp->file)) == -1) {
			mandoc_msg(resp->ipath == SIZE_MAX ?
			    MANDOCERR_BADARG_BAD : MANDOCERR_OPEN,
			    0, 0, "%s", strerror(errno));
			mandoc_msg_setinfilename(NULL);
			return;
		}
	} else
		fd = STDIN_FILENO;

	if (outst->use_pager) {
		outst->use_pager = 0;
		outst->tag_files = term_tag_init(conf->output.outfilename,
		    outst->outtype == OUTT_HTML ? ".html" : "",
		    conf->output.tagfilename);
#if HAVE_PLEDGE
		if ((conf->output.outfilename != NULL ||
		     conf->output.tagfilename != NULL) &&
		    pledge("stdio rpath cpath", NULL) == -1) {
			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
			    "%s", strerror(errno));
			exit(mandoc_msg_getrc());
		}
#endif
	}
	if (outst->had_output && outst->outtype <= OUTT_UTF8) {
		if (outst->outdata == NULL)
			outdata_alloc(outst, &conf->output);
		terminal_sepline(outst->outdata);
	}

	if (resp->form == FORM_SRC)
		parse(mp, fd, resp->file, outst, conf);
	else {
		passthrough(fd, conf->output.synopsisonly);
		outst->had_output = 1;
	}

	if (ferror(stdout)) {
		if (outst->tag_files != NULL) {
			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s",
			    outst->tag_files->ofn, strerror(errno));
			term_tag_unlink();
			outst->tag_files = NULL;
		} else
			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s",
			    strerror(errno));
	}
	mandoc_msg_setinfilename(NULL);
}

static void
parse(struct mparse *mp, int fd, const char *file,
    struct outstate *outst, struct manconf *conf)
{
	static struct manpaths	 basepaths;
	static int		 previous;
	struct roff_meta	*meta;

	assert(fd >= 0);
	if (file == NULL)
		file = "";

	if (previous)
		mparse_reset(mp);
	else
		previous = 1;

	mparse_readfd(mp, fd, file);
	if (fd != STDIN_FILENO)
		close(fd);

	/*
	 * With -Wstop and warnings or errors of at least the requested
	 * level, do not produce output.
	 */

	if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
		return;

	if (outst->outdata == NULL)
		outdata_alloc(outst, &conf->output);
	else if (outst->outtype == OUTT_HTML)
		html_reset(outst->outdata);

	mandoc_xr_reset();
	meta = mparse_result(mp);

	/* Execute the out device, if it exists. */

	outst->had_output = 1;
	if (meta->macroset == MACROSET_MDOC) {
		switch (outst->outtype) {
		case OUTT_HTML:
			html_mdoc(outst->outdata, meta);
			break;
		case OUTT_TREE:
			tree_mdoc(outst->outdata, meta);
			break;
		case OUTT_MAN:
			man_mdoc(outst->outdata, meta);
			break;
		case OUTT_PDF:
		case OUTT_ASCII:
		case OUTT_UTF8:
		case OUTT_LOCALE:
		case OUTT_PS:
			terminal_mdoc(outst->outdata, meta);
			break;
		case OUTT_MARKDOWN:
			markdown_mdoc(outst->outdata, meta);
			break;
		default:
			break;
		}
	}
	if (meta->macroset == MACROSET_MAN) {
		switch (outst->outtype) {
		case OUTT_HTML:
			html_man(outst->outdata, meta);
			break;
		case OUTT_TREE:
			tree_man(outst->outdata, meta);
			break;
		case OUTT_MAN:
			mparse_copy(mp);
			break;
		case OUTT_PDF:
		case OUTT_ASCII:
		case OUTT_UTF8:
		case OUTT_LOCALE:
		case OUTT_PS:
			terminal_man(outst->outdata, meta);
			break;
		case OUTT_MARKDOWN:
			mandoc_msg(MANDOCERR_MAN_TMARKDOWN, 0, 0, NULL);
			break;
		default:
			break;
		}
	}
	if (conf->output.tag != NULL && conf->output.tag_found == 0 &&
	    tag_exists(conf->output.tag))
		conf->output.tag_found = 1;

	if (mandoc_msg_getmin() < MANDOCERR_STYLE) {
		if (basepaths.sz == 0)
			manpath_base(&basepaths);
		check_xr(&basepaths);
	} else if (mandoc_msg_getmin() < MANDOCERR_WARNING)
		check_xr(&conf->manpath);
}

static void
check_xr(struct manpaths *paths)
{
	struct mansearch	 search;
	struct mandoc_xr	*xr;
	size_t			 sz;

	for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
		if (xr->line == -1)
			continue;
		search.arch = NULL;
		search.sec = xr->sec;
		search.outkey = NULL;
		search.argmode = ARG_NAME;
		search.firstmatch = 1;
		if (mansearch(&search, paths, 1, &xr->name, NULL, &sz))
			continue;
		if (fs_search(&search, paths, xr->name, NULL, &sz) != -1)
			continue;
		if (xr->count == 1)
			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
			    xr->pos + 1, "Xr %s %s", xr->name, xr->sec);
		else
			mandoc_msg(MANDOCERR_XR_BAD, xr->line,
			    xr->pos + 1, "Xr %s %s (%d times)",
			    xr->name, xr->sec, xr->count);
	}
}

static void
outdata_alloc(struct outstate *outst, struct manoutput *outconf)
{
	switch (outst->outtype) {
	case OUTT_HTML:
		outst->outdata = html_alloc(outconf);
		break;
	case OUTT_UTF8:
		outst->outdata = utf8_alloc(outconf);
		break;
	case OUTT_LOCALE:
		outst->outdata = locale_alloc(outconf);
		break;
	case OUTT_ASCII:
		outst->outdata = ascii_alloc(outconf);
		break;
	case OUTT_PDF:
		outst->outdata = pdf_alloc(outconf);
		break;
	case OUTT_PS:
		outst->outdata = ps_alloc(outconf);
		break;
	default:
		break;
	}
}

static void
passthrough(int fd, int synopsis_only)
{
	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
	const char	 synr[] = "SYNOPSIS";

	FILE		*stream;
	char		*line, *cp;
	size_t		 linesz;
	ssize_t		 len, written;
	int		 lno, print;

	stream = NULL;
	line = NULL;
	linesz = 0;

	if (fflush(stdout) == EOF) {
		mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno));
		goto done;
	}
	if ((stream = fdopen(fd, "r")) == NULL) {
		close(fd);
		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
		goto done;
	}

	lno = print = 0;
	while ((len = getline(&line, &linesz, stream)) != -1) {
		lno++;
		cp = line;
		if (synopsis_only) {
			if (print) {
				if ( ! isspace((unsigned char)*cp))
					goto done;
				while (isspace((unsigned char)*cp)) {
					cp++;
					len--;
				}
			} else {
				if (strcmp(cp, synb) == 0 ||
				    strcmp(cp, synr) == 0)
					print = 1;
				continue;
			}
		}
		for (; len > 0; len -= written) {
			if ((written = write(STDOUT_FILENO, cp, len)) == -1) {
				mandoc_msg(MANDOCERR_WRITE, 0, 0,
				    "%s", strerror(errno));
				goto done;
			}
		}
	}
	if (ferror(stream))
		mandoc_msg(MANDOCERR_GETLINE, lno, 0, "%s", strerror(errno));

done:
	free(line);
	if (stream != NULL)
		fclose(stream);
}

static int
woptions(char *arg, enum mandoc_os *os_e, int *wstop)
{
	char		*v, *o;
	const char	*toks[11];

	toks[0] = "stop";
	toks[1] = "all";
	toks[2] = "base";
	toks[3] = "style";
	toks[4] = "warning";
	toks[5] = "error";
	toks[6] = "unsupp";
	toks[7] = "fatal";
	toks[8] = "openbsd";
	toks[9] = "netbsd";
	toks[10] = NULL;

	while (*arg) {
		o = arg;
		switch (getsubopt(&arg, (char * const *)toks, &v)) {
		case 0:
			*wstop = 1;
			break;
		case 1:
		case 2:
			mandoc_msg_setmin(MANDOCERR_BASE);
			break;
		case 3:
			mandoc_msg_setmin(MANDOCERR_STYLE);
			break;
		case 4:
			mandoc_msg_setmin(MANDOCERR_WARNING);
			break;
		case 5:
			mandoc_msg_setmin(MANDOCERR_ERROR);
			break;
		case 6:
			mandoc_msg_setmin(MANDOCERR_UNSUPP);
			break;
		case 7:
			mandoc_msg_setmin(MANDOCERR_BADARG);
			break;
		case 8:
			mandoc_msg_setmin(MANDOCERR_BASE);
			*os_e = MANDOC_OS_OPENBSD;
			break;
		case 9:
			mandoc_msg_setmin(MANDOCERR_BASE);
			*os_e = MANDOC_OS_NETBSD;
			break;
		default:
			mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o);
			return -1;
		}
	}
	return 0;
}

/*
 * Wait until moved to the foreground,
 * then fork the pager and wait for the user to close it.
 */
static void
run_pager(struct outstate *outst, char *tag_target)
{
	int	 signum, status;
	pid_t	 man_pgid, tc_pgid;
	pid_t	 pager_pid, wait_pid;

	man_pgid = getpgid(0);
	outst->tag_files->tcpgid =
	    man_pgid == getpid() ? getpgid(getppid()) : man_pgid;
	pager_pid = 0;
	signum = SIGSTOP;

	for (;;) {
		/* Stop here until moved to the foreground. */

		tc_pgid = tcgetpgrp(STDOUT_FILENO);
		if (tc_pgid != man_pgid) {
			if (tc_pgid == pager_pid) {
				(void)tcsetpgrp(STDOUT_FILENO, man_pgid);
				if (signum == SIGTTIN)
					continue;
			} else
				outst->tag_files->tcpgid = tc_pgid;
			kill(0, signum);
			continue;
		}

		/* Once in the foreground, activate the pager. */

		if (pager_pid) {
			(void)tcsetpgrp(STDOUT_FILENO, pager_pid);
			kill(pager_pid, SIGCONT);
		} else
			pager_pid = spawn_pager(outst, tag_target);

		/* Wait for the pager to stop or exit. */

		while ((wait_pid = waitpid(pager_pid, &status,
		    WUNTRACED)) == -1 && errno == EINTR)
			continue;

		if (wait_pid == -1) {
			mandoc_msg(MANDOCERR_WAIT, 0, 0,
			    "%s", strerror(errno));
			break;
		}
		if (!WIFSTOPPED(status))
			break;

		signum = WSTOPSIG(status);
	}
}

static pid_t
spawn_pager(struct outstate *outst, char *tag_target)
{
	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
#define MAX_PAGER_ARGS 16
	char		*argv[MAX_PAGER_ARGS];
	const char	*pager;
	char		*cp;
#if HAVE_LESS_T
	size_t		 cmdlen;
#endif
	int		 argc, use_ofn;
	pid_t		 pager_pid;

	assert(outst->tag_files->ofd == -1);
	assert(outst->tag_files->tfs == NULL);

	pager = getenv("MANPAGER");
	if (pager == NULL || *pager == '\0')
		pager = getenv("PAGER");
	if (pager == NULL || *pager == '\0')
		pager = BINM_PAGER;
	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 (*outst->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++] = outst->tag_files->tfn;
			if (tag_target != NULL) {
				argv[argc++] = mandoc_strdup("-t");
				argv[argc++] = tag_target;
				use_ofn = 0;
			}
		}
	}
#endif
	if (use_ofn) {
		if (outst->outtype == OUTT_HTML && tag_target != NULL)
			mandoc_asprintf(&argv[argc], "file://%s#%s",
			    outst->tag_files->ofn, tag_target);
		else
			argv[argc] = outst->tag_files->ofn;
		argc++;
	}
	argv[argc] = NULL;

	switch (pager_pid = fork()) {
	case -1:
		mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno));
		exit(mandoc_msg_getrc());
	case 0:
		break;
	default:
		(void)setpgid(pager_pid, 0);
		(void)tcsetpgrp(STDOUT_FILENO, pager_pid);
#if HAVE_PLEDGE
		if (pledge("stdio rpath tmppath tty proc", NULL) == -1) {
			mandoc_msg(MANDOCERR_PLEDGE, 0, 0,
			    "%s", strerror(errno));
			exit(mandoc_msg_getrc());
		}
#endif
		outst->tag_files->pager_pid = pager_pid;
		return pager_pid;
	}

	/*
	 * The child process becomes the pager.
	 * Do not start it before controlling the terminal.
	 */

	while (tcgetpgrp(STDOUT_FILENO) != getpid())
		nanosleep(&timeout, NULL);

	execvp(argv[0], argv);
	mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno));
	_exit(mandoc_msg_getrc());
}
/*	$Id: main.h,v 1.30 2019/03/03 13:02:11 schwarze Exp $ */
/*
 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
 * Copyright (c) 2014, 2015, 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.
 */

struct	roff_meta;
struct	manoutput;

/*
 * Definitions for main.c-visible output device functions, e.g., -Thtml
 * and -Tascii.  Note that ascii_alloc() is named as such in
 * anticipation of latin1_alloc() and so on, all of which map into the
 * terminal output routines with different character settings.
 */

void		 *html_alloc(const struct manoutput *);
void		  html_mdoc(void *, const struct roff_meta *);
void		  html_man(void *, const struct roff_meta *);
void		  html_reset(void *);
void		  html_free(void *);

void		  tree_mdoc(void *, const struct roff_meta *);
void		  tree_man(void *, const struct roff_meta *);

void		  man_mdoc(void *, const struct roff_meta *);

void		 *locale_alloc(const struct manoutput *);
void		 *utf8_alloc(const struct manoutput *);
void		 *ascii_alloc(const struct manoutput *);
void		  ascii_free(void *);

void		 *pdf_alloc(const struct manoutput *);
void		 *ps_alloc(const struct manoutput *);
void		  pspdf_free(void *);

void		  terminal_mdoc(void *, const struct roff_meta *);
void		  terminal_man(void *, const struct roff_meta *);
void		  terminal_sepline(void *);

void		  markdown_mdoc(void *, const struct roff_meta *);
/*	$Id: man.c,v 1.187 2019/01/05 00:36:50 schwarze Exp $ */
/*
 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
 * Copyright (c) 2013-2015, 2017-2019 Ingo Schwarze 
 * Copyright (c) 2011 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 "mandoc_aux.h"
#include "mandoc.h"
#include "roff.h"
#include "man.h"
#include "libmandoc.h"
#include "roff_int.h"
#include "libman.h"

static	char		*man_hasc(char *);
static	int		 man_ptext(struct roff_man *, int, char *, int);
static	int		 man_pmacro(struct roff_man *, int, char *, int);


int
man_parseln(struct roff_man *man, int ln, char *buf, int offs)
{

	if (man->last->type != ROFFT_EQN || ln > man->last->line)
		man->flags |= MAN_NEWLINE;

	return roff_getcontrol(man->roff, buf, &offs) ?
	    man_pmacro(man, ln, buf, offs) :
	    man_ptext(man, ln, buf, offs);
}

/*
 * If the string ends with \c, return a pointer to the backslash.
 * Otherwise, return NULL.
 */
static char *
man_hasc(char *start)
{
	char	*cp, *ep;

	ep = strchr(start, '\0') - 2;
	if (ep < start || ep[0] != '\\' || ep[1] != 'c')
		return NULL;
	for (cp = ep; cp > start; cp--)
		if (cp[-1] != '\\')
			break;
	return (ep - cp) % 2 ? NULL : ep;
}

void
man_descope(struct roff_man *man, int line, int offs, char *start)
{
	/* Trailing \c keeps next-line scope open. */

	if (start != NULL && man_hasc(start) != NULL)
		return;

	/*
	 * Co-ordinate what happens with having a next-line scope open:
	 * first close out the element scopes (if applicable),
	 * then close out the block scope (also if applicable).
	 */

	if (man->flags & MAN_ELINE) {
		while (man->last->parent->type != ROFFT_ROOT &&
		    man_macro(man->last->parent->tok)->flags & MAN_ESCOPED)
			man_unscope(man, man->last->parent);
		man->flags &= ~MAN_ELINE;
	}
	if ( ! (man->flags & MAN_BLINE))
		return;
	man_unscope(man, man->last->parent);
	roff_body_alloc(man, line, offs, man->last->tok);
	man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
}

static int
man_ptext(struct roff_man *man, int line, char *buf, int offs)
{
	int		 i;
	char		*ep;

	/* In no-fill mode, whitespace is preserved on text lines. */

	if (man->flags & ROFF_NOFILL) {
		roff_word_alloc(man, line, offs, buf + offs);
		man_descope(man, line, offs, buf + offs);
		return 1;
	}

	for (i = offs; buf[i] == ' '; i++)
		/* Skip leading whitespace. */ ;

	/*
	 * Blank lines are ignored in next line scope
	 * and right after headings and cancel preceding \c,
	 * but add a single vertical space elsewhere.
	 */

	if (buf[i] == '\0') {
		if (man->flags & (MAN_ELINE | MAN_BLINE)) {
			mandoc_msg(MANDOCERR_BLK_BLANK, line, 0, NULL);
			return 1;
		}
		if (man->last->tok == MAN_SH || man->last->tok == MAN_SS)
			return 1;
		if (man->last->type == ROFFT_TEXT &&
		    ((ep = man_hasc(man->last->string)) != NULL)) {
			*ep = '\0';
			return 1;
		}
		roff_elem_alloc(man, line, offs, ROFF_sp);
		man->next = ROFF_NEXT_SIBLING;
		return 1;
	}

	/*
	 * Warn if the last un-escaped character is whitespace. Then
	 * strip away the remaining spaces (tabs stay!).
	 */

	i = (int)strlen(buf);
	assert(i);

	if (' ' == buf[i - 1] || '\t' == buf[i - 1]) {
		if (i > 1 && '\\' != buf[i - 2])
			mandoc_msg(MANDOCERR_SPACE_EOL, line, i - 1, NULL);

		for (--i; i && ' ' == buf[i]; i--)
			/* Spin back to non-space. */ ;

		/* Jump ahead of escaped whitespace. */
		i += '\\' == buf[i] ? 2 : 1;

		buf[i] = '\0';
	}
	roff_word_alloc(man, line, offs, buf + offs);

	/*
	 * End-of-sentence check.  If the last character is an unescaped
	 * EOS character, then flag the node as being the end of a
	 * sentence.  The front-end will know how to interpret this.
	 */

	assert(i);
	if (mandoc_eos(buf, (size_t)i))
		man->last->flags |= NODE_EOS;

	man_descope(man, line, offs, buf + offs);
	return 1;
}

static int
man_pmacro(struct roff_man *man, int ln, char *buf, int offs)
{
	struct roff_node *n;
	const char	*cp;
	size_t		 sz;
	enum roff_tok	 tok;
	int		 ppos;
	int		 bline;

	/* Determine the line macro. */

	ppos = offs;
	tok = TOKEN_NONE;
	for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++)
		offs++;
	if (sz > 0 && sz < 4)
		tok = roffhash_find(man->manmac, buf + ppos, sz);
	if (tok == TOKEN_NONE) {
		mandoc_msg(MANDOCERR_MACRO, ln, ppos, "%s", buf + ppos - 1);
		return 1;
	}

	/* Skip a leading escape sequence or tab. */

	switch (buf[offs]) {
	case '\\':
		cp = buf + offs + 1;
		mandoc_escape(&cp, NULL, NULL);
		offs = cp - buf;
		break;
	case '\t':
		offs++;
		break;
	default:
		break;
	}

	/* Jump to the next non-whitespace word. */

	while (buf[offs] == ' ')
		offs++;

	/*
	 * Trailing whitespace.  Note that tabs are allowed to be passed
	 * into the parser as "text", so we only warn about spaces here.
	 */

	if (buf[offs] == '\0' && buf[offs - 1] == ' ')
		mandoc_msg(MANDOCERR_SPACE_EOL, ln, offs - 1, NULL);

	/*
	 * Some macros break next-line scopes; otherwise, remember
	 * whether we are in next-line scope for a block head.
	 */

	man_breakscope(man, tok);
	bline = man->flags & MAN_BLINE;

	/*
	 * If the line in next-line scope ends with \c, keep the
	 * next-line scope open for the subsequent input line.
	 * That is not at all portable, only groff >= 1.22.4
	 * does it, but *if* this weird idiom occurs in a manual
	 * page, that's very likely what the author intended.
	 */

	if (bline && man_hasc(buf + offs))
		bline = 0;

	/* Call to handler... */

	(*man_macro(tok)->fp)(man, tok, ln, ppos, &offs, buf);

	/* In quick mode (for mandocdb), abort after the NAME section. */

	if (man->quick && tok == MAN_SH) {
		n = man->last;
		if (n->type == ROFFT_BODY &&
		    strcmp(n->prev->child->string, "NAME"))
			return 2;
	}

	/*
	 * If we are in a next-line scope for a block head,
	 * close it out now and switch to the body,
	 * unless the next-line scope is allowed to continue.
	 */

	if (bline == 0 ||
	    (man->flags & MAN_BLINE) == 0 ||
	    man->flags & MAN_ELINE ||
	    man_macro(tok)->flags & MAN_NSCOPED)
		return 1;

	man_unscope(man, man->last->parent);
	roff_body_alloc(man, ln, ppos, man->last->tok);
	man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
	return 1;
}

void
man_breakscope(struct roff_man *man, int tok)
{
	struct roff_node *n;

	/*
	 * An element next line scope is open,
	 * and the new macro is not allowed inside elements.
	 * Delete the element that is being broken.
	 */

	if (man->flags & MAN_ELINE && (tok < MAN_TH ||
	    (man_macro(tok)->flags & MAN_NSCOPED) == 0)) {
		n = man->last;
		if (n->type == ROFFT_TEXT)
			n = n->parent;
		if (n->tok < MAN_TH ||
		    (man_macro(n->tok)->flags & (MAN_NSCOPED | MAN_ESCOPED))
		     == MAN_NSCOPED)
			n = n->parent;

		mandoc_msg(MANDOCERR_BLK_LINE, n->line, n->pos,
		    "%s breaks %s", roff_name[tok], roff_name[n->tok]);

		roff_node_delete(man, n);
		man->flags &= ~MAN_ELINE;
	}

	/*
	 * Weird special case:
	 * Switching fill mode closes section headers.
	 */

	if (man->flags & MAN_BLINE &&
	    (tok == ROFF_nf || tok == ROFF_fi) &&
	    (man->last->tok == MAN_SH || man->last->tok == MAN_SS)) {
		n = man->last;
		man_unscope(man, n);
		roff_body_alloc(man, n->line, n->pos, n->tok);
		man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
	}

	/*
	 * A block header next line scope is open,
	 * and the new macro is not allowed inside block headers.
	 * Delete the block that is being broken.
	 */

	if (man->flags & MAN_BLINE && tok != ROFF_nf && tok != ROFF_fi &&
	    (tok < MAN_TH || man_macro(tok)->flags & MAN_XSCOPE)) {
		n = man->last;
		if (n->type == ROFFT_TEXT)
			n = n->parent;
		if (n->tok < MAN_TH ||
		    (man_macro(n->tok)->flags & MAN_XSCOPE) == 0)
			n = n->parent;

		assert(n->type == ROFFT_HEAD);
		n = n->parent;
		assert(n->type == ROFFT_BLOCK);
		assert(man_macro(n->tok)->flags & MAN_BSCOPED);

		mandoc_msg(MANDOCERR_BLK_LINE, n->line, n->pos,
		    "%s breaks %s", roff_name[tok], roff_name[n->tok]);

		roff_node_delete(man, n);
		man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
	}
}
/*	$Id: man.h,v 1.79 2018/08/23 19:33:27 schwarze Exp $ */
/*
 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
 * Copyright (c) 2014, 2015 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.
 */

struct	roff_man;

void			 man_validate(struct roff_man *);
/* $Id: man_html.c,v 1.179 2020/10/16 17:22:43 schwarze Exp $ */
/*
 * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze 
 * Copyright (c) 2008-2012, 2014 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.
 *
 * HTML formatter for man(7) used by mandoc(1).
 */
#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, \
			  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;

	if ((n->flags & NODE_NOFILL) == 0)
		html_fillmode(h, ROFF_fi);
	else if (html_fillmode(h, ROFF_nf) == ROFF_nf &&
	    n->tok != ROFF_fi && n->flags & NODE_LINE &&
	    (n->prev == NULL || n->prev->tok != MAN_YS))
		print_endline(h);

	child = 1;
	switch (n->type) {
	case ROFFT_TEXT:
		if (*n->string == '\0') {
			print_endline(h);
			return;
		}
		if (*n->string == ' ' && n->flags & NODE_LINE &&
		    (h->flags & HTML_NONEWLINE) == 0)
			print_otag(h, TAG_BR, "");
		else if (n->flags & NODE_DELIMC)
			h->flags |= HTML_NOSPACE;
		t = h->tag;
		t->refcnt++;
		print_text(h, n->string);
		break;
	case ROFFT_EQN:
		t = h->tag;
		t->refcnt++;
		print_eqn(h, n->eqn);
		break;
	case ROFFT_TBL:
		/*
		 * This will take care of initialising all of the table
		 * state data for the first table, then tearing it down
		 * for the last one.
		 */
		print_tbl(h, n->span);
		return;
	default:
		/*
		 * Close out scope of font prior to opening a macro
		 * scope.
		 */
		if (h->metac != ESCAPE_FONTROMAN) {
			h->metal = h->metac;
			h->metac = ESCAPE_FONTROMAN;
		}

		/*
		 * Close out the current table, if it's open, and unset
		 * the "meta" table state.  This will be reopened on the
		 * next table element.
		 */
		if (h->tblt != NULL)
			print_tblclose(h);
		t = h->tag;
		t->refcnt++;
		if (n->tok < ROFF_MAX) {
			roff_html_pre(h, n);
			t->refcnt--;
			print_stagq(h, t);
			return;
		}
		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
		if (man_html_acts[n->tok - MAN_TH].pre != NULL)
			child = (*man_html_acts[n->tok - MAN_TH].pre)(man,
			    n, h);
		break;
	}

	if (child && n->child != NULL)
		print_man_nodelist(man, n->child, h);

	/* This will automatically close out any font scope. */
	t->refcnt--;
	if (n->type == ROFFT_BLOCK &&
	    (n->tok == MAN_IP || n->tok == MAN_TP || n->tok == MAN_TQ)) {
		t = h->tag;
		while (t->tag != TAG_DL && t->tag != TAG_UL)
			t = t->next;
		/*
		 * Close the list if no further item of the same type
		 * follows; otherwise, close the item only.
		 */
		if (list_continues(n, roff_node_next(n)) == '\0') {
			print_tagq(h, t);
			t = NULL;
		}
	}
	if (t != NULL)
		print_stagq(h, t);
}

static void
man_root_pre(const struct roff_meta *man, struct html *h)
{
	struct tag	*t, *tt;
	char		*title;

	assert(man->title);
	assert(man->msec);
	mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);

	t = print_otag(h, TAG_TABLE, "c", "head");
	tt = print_otag(h, TAG_TR, "");

	print_otag(h, TAG_TD, "c", "head-ltitle");
	print_text(h, title);
	print_stagq(h, tt);

	print_otag(h, TAG_TD, "c", "head-vol");
	if (man->vol != NULL)
		print_text(h, man->vol);
	print_stagq(h, tt);

	print_otag(h, TAG_TD, "c", "head-rtitle");
	print_text(h, title);
	print_tagq(h, t);
	free(title);
}

static void
man_root_post(const struct roff_meta *man, struct html *h)
{
	struct tag	*t, *tt;

	t = print_otag(h, TAG_TABLE, "c", "foot");
	tt = print_otag(h, TAG_TR, "");

	print_otag(h, TAG_TD, "c", "foot-date");
	print_text(h, man->date);
	print_stagq(h, tt);

	print_otag(h, TAG_TD, "c", "foot-os");
	if (man->os != NULL)
		print_text(h, man->os);
	print_tagq(h, t);
}

static int
man_SH_pre(MAN_ARGS)
{
	const char	*class;
	enum htmltag	 tag;

	if (n->tok == MAN_SH) {
		tag = TAG_H1;
		class = "Sh";
	} else {
		tag = TAG_H2;
		class = "Ss";
	}
	switch (n->type) {
	case ROFFT_BLOCK:
		html_close_paragraph(h);
		print_otag(h, TAG_SECTION, "c", class);
		break;
	case ROFFT_HEAD:
		print_otag_id(h, tag, class, n);
		break;
	case ROFFT_BODY:
		break;
	default:
		abort();
	}
	return 1;
}

static int
man_alt_pre(MAN_ARGS)
{
	const struct roff_node	*nn;
	struct tag	*t;
	int		 i;
	enum htmltag	 fp;

	for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) {
		switch (n->tok) {
		case MAN_BI:
			fp = i % 2 ? TAG_I : TAG_B;
			break;
		case MAN_IB:
			fp = i % 2 ? TAG_B : TAG_I;
			break;
		case MAN_RI:
			fp = i % 2 ? TAG_I : TAG_MAX;
			break;
		case MAN_IR:
			fp = i % 2 ? TAG_MAX : TAG_I;
			break;
		case MAN_BR:
			fp = i % 2 ? TAG_MAX : TAG_B;
			break;
		case MAN_RB:
			fp = i % 2 ? TAG_B : TAG_MAX;
			break;
		default:
			abort();
		}

		if (i)
			h->flags |= HTML_NOSPACE;

		if (fp != TAG_MAX)
			t = print_otag(h, fp, "");

		print_text(h, nn->string);

		if (fp != TAG_MAX)
			print_tagq(h, t);
	}
	return 0;
}

static int
man_SM_pre(MAN_ARGS)
{
	print_otag(h, TAG_SMALL, "");
	if (n->tok == MAN_SB)
		print_otag(h, TAG_B, "");
	return 1;
}

static int
man_PP_pre(MAN_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		html_close_paragraph(h);
		break;
	case ROFFT_HEAD:
		return 0;
	case ROFFT_BODY:
		if (n->child != NULL &&
		    (n->child->flags & NODE_NOFILL) == 0)
			print_otag(h, TAG_P, "c",
			    n->tok == MAN_PP ? "Pp" : "Pp HP");
		break;
	default:
		abort();
	}
	return 1;
}

static char
list_continues(const struct roff_node *n1, const struct roff_node *n2)
{
	const char *s1, *s2;
	char c1, c2;

	if (n1 == NULL || n1->type != ROFFT_BLOCK ||
	    n2 == NULL || n2->type != ROFFT_BLOCK)
		return '\0';
	if ((n1->tok == MAN_TP || n1->tok == MAN_TQ) &&
	    (n2->tok == MAN_TP || n2->tok == MAN_TQ))
		return ' ';
	if (n1->tok != MAN_IP || n2->tok != MAN_IP)
		return '\0';
	n1 = n1->head->child;
	n2 = n2->head->child;
	s1 = n1 == NULL ? "" : n1->string;
	s2 = n2 == NULL ? "" : n2->string;
	c1 = strcmp(s1, "*") == 0 ? '*' :
	     strcmp(s1, "\\-") == 0 ? '-' :
	     strcmp(s1, "\\(bu") == 0 ? 'b' : ' ';
	c2 = strcmp(s2, "*") == 0 ? '*' :
	     strcmp(s2, "\\-") == 0 ? '-' :
	     strcmp(s2, "\\(bu") == 0 ? 'b' : ' ';
	return c1 != c2 ? '\0' : c1 == 'b' ? '*' : c1;
}

static int
man_IP_pre(MAN_ARGS)
{
	struct roff_node	*nn;
	const char		*list_class;
	enum htmltag		 list_elem, body_elem;
	char			 list_type;

	nn = n->type == ROFFT_BLOCK ? n : n->parent;
	list_type = list_continues(roff_node_prev(nn), nn);
	if (list_type == '\0') {
		/* Start a new list. */
		list_type = list_continues(nn, roff_node_next(nn));
		if (list_type == '\0')
			list_type = ' ';
		switch (list_type) {
		case ' ':
			list_class = "Bl-tag";
			list_elem = TAG_DL;
			break;
		case '*':
			list_class = "Bl-bullet";
			list_elem = TAG_UL;
			break;
		case '-':
			list_class = "Bl-dash";
			list_elem = TAG_UL;
			break;
		default:
			abort();
		}
	} else {
		/* Continue a list that was started earlier. */
		list_class = NULL;
		list_elem = TAG_MAX;
	}
	body_elem = list_type == ' ' ? TAG_DD : TAG_LI;

	switch (n->type) {
	case ROFFT_BLOCK:
		html_close_paragraph(h);
		if (list_elem != TAG_MAX)
			print_otag(h, list_elem, "c", list_class);
		return 1;
	case ROFFT_HEAD:
		if (body_elem == TAG_LI)
			return 0;
		print_otag_id(h, TAG_DT, NULL, n);
		break;
	case ROFFT_BODY:
		print_otag(h, body_elem, "");
		return 1;
	default:
		abort();
	}
	switch(n->tok) {
	case MAN_IP:  /* Only print the first header element. */
		if (n->child != NULL)
			print_man_node(man, n->child, h);
		break;
	case MAN_TP:  /* Only print next-line header elements. */
	case MAN_TQ:
		nn = n->child;
		while (nn != NULL && (NODE_LINE & nn->flags) == 0)
			nn = nn->next;
		while (nn != NULL) {
			print_man_node(man, nn, h);
			nn = nn->next;
		}
		break;
	default:
		abort();
	}
	return 0;
}

static int
man_OP_pre(MAN_ARGS)
{
	struct tag	*tt;

	print_text(h, "[");
	h->flags |= HTML_NOSPACE;
	tt = print_otag(h, TAG_SPAN, "c", "Op");

	if ((n = n->child) != NULL) {
		print_otag(h, TAG_B, "");
		print_text(h, n->string);
	}

	print_stagq(h, tt);

	if (n != NULL && n->next != NULL) {
		print_otag(h, TAG_I, "");
		print_text(h, n->next->string);
	}

	print_stagq(h, tt);
	h->flags |= HTML_NOSPACE;
	print_text(h, "]");
	return 0;
}

static int
man_B_pre(MAN_ARGS)
{
	print_otag(h, TAG_B, "");
	return 1;
}

static int
man_I_pre(MAN_ARGS)
{
	print_otag(h, TAG_I, "");
	return 1;
}

static int
man_in_pre(MAN_ARGS)
{
	print_otag(h, TAG_BR, "");
	return 0;
}

static int
man_ign_pre(MAN_ARGS)
{
	return 0;
}

static int
man_RS_pre(MAN_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		html_close_paragraph(h);
		break;
	case ROFFT_HEAD:
		return 0;
	case ROFFT_BODY:
		print_otag(h, TAG_DIV, "c", "Bd-indent");
		break;
	default:
		abort();
	}
	return 1;
}

static int
man_SY_pre(MAN_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		html_close_paragraph(h);
		print_otag(h, TAG_TABLE, "c", "Nm");
		print_otag(h, TAG_TR, "");
		break;
	case ROFFT_HEAD:
		print_otag(h, TAG_TD, "");
		print_otag(h, TAG_CODE, "c", "Nm");
		break;
	case ROFFT_BODY:
		print_otag(h, TAG_TD, "");
		break;
	default:
		abort();
	}
	return 1;
}

static int
man_UR_pre(MAN_ARGS)
{
	char *cp;

	n = n->child;
	assert(n->type == ROFFT_HEAD);
	if (n->child != NULL) {
		assert(n->child->type == ROFFT_TEXT);
		if (n->tok == MAN_MT) {
			mandoc_asprintf(&cp, "mailto:%s", n->child->string);
			print_otag(h, TAG_A, "ch", "Mt", cp);
			free(cp);
		} else
			print_otag(h, TAG_A, "ch", "Lk", n->child->string);
	}

	assert(n->next->type == ROFFT_BODY);
	if (n->next->child != NULL)
		n = n->next;

	print_man_nodelist(man, n->child, h);
	return 0;
}

static int
man_abort_pre(MAN_ARGS)
{
	abort();
}
/* $Id: man_macro.c,v 1.145 2020/09/09 17:01:10 schwarze Exp $ */
/*
 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
 * Copyright (c) 2012-2015, 2017-2020 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 "mandoc.h"
#include "roff.h"
#include "man.h"
#include "libmandoc.h"
#include "roff_int.h"
#include "libman.h"

static	void		 blk_close(MACRO_PROT_ARGS);
static	void		 blk_exp(MACRO_PROT_ARGS);
static	void		 blk_imp(MACRO_PROT_ARGS);
static	void		 in_line_eoln(MACRO_PROT_ARGS);
static	int		 man_args(struct roff_man *, int,
				int *, char *, char **);
static	void		 rew_scope(struct roff_man *, enum roff_tok);

static const struct man_macro man_macros[MAN_MAX - MAN_TH] = {
	{ in_line_eoln, MAN_XSCOPE }, /* TH */
	{ blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* SH */
	{ blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* SS */
	{ blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* TP */
	{ blk_imp, MAN_XSCOPE | MAN_BSCOPED }, /* TQ */
	{ blk_imp, MAN_XSCOPE }, /* LP */
	{ blk_imp, MAN_XSCOPE }, /* PP */
	{ blk_imp, MAN_XSCOPE }, /* P */
	{ blk_imp, MAN_XSCOPE }, /* IP */
	{ blk_imp, MAN_XSCOPE }, /* HP */
	{ in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* SM */
	{ in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* SB */
	{ in_line_eoln, 0 }, /* BI */
	{ in_line_eoln, 0 }, /* IB */
	{ in_line_eoln, 0 }, /* BR */
	{ in_line_eoln, 0 }, /* RB */
	{ in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* R */
	{ in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* B */
	{ in_line_eoln, MAN_NSCOPED | MAN_ESCOPED | MAN_JOIN }, /* I */
	{ in_line_eoln, 0 }, /* IR */
	{ in_line_eoln, 0 }, /* RI */
	{ blk_close, MAN_XSCOPE }, /* RE */
	{ blk_exp, MAN_XSCOPE }, /* RS */
	{ in_line_eoln, 0 }, /* DT */
	{ in_line_eoln, 0 }, /* UC */
	{ in_line_eoln, MAN_NSCOPED }, /* PD */
	{ in_line_eoln, 0 }, /* AT */
	{ in_line_eoln, MAN_NSCOPED }, /* in */
	{ blk_imp, MAN_XSCOPE }, /* SY */
	{ blk_close, MAN_XSCOPE }, /* YS */
	{ in_line_eoln, 0 }, /* OP */
	{ in_line_eoln, MAN_XSCOPE }, /* EX */
	{ in_line_eoln, MAN_XSCOPE }, /* EE */
	{ blk_exp, MAN_XSCOPE }, /* UR */
	{ blk_close, MAN_XSCOPE }, /* UE */
	{ blk_exp, MAN_XSCOPE }, /* MT */
	{ blk_close, MAN_XSCOPE }, /* ME */
};


const struct man_macro *
man_macro(enum roff_tok tok)
{
	assert(tok >= MAN_TH && tok <= MAN_MAX);
	return man_macros + (tok - MAN_TH);
}

void
man_unscope(struct roff_man *man, const struct roff_node *to)
{
	struct roff_node *n;

	to = to->parent;
	n = man->last;
	while (n != to) {

		/* Reached the end of the document? */

		if (to == NULL && ! (n->flags & NODE_VALID)) {
			if (man->flags & (MAN_BLINE | MAN_ELINE) &&
			    man_macro(n->tok)->flags &
			     (MAN_BSCOPED | MAN_NSCOPED)) {
				mandoc_msg(MANDOCERR_BLK_LINE,
				    n->line, n->pos,
				    "EOF breaks %s", roff_name[n->tok]);
				if (man->flags & MAN_ELINE) {
					if ((man_macro(n->parent->tok)->flags &
					    MAN_ESCOPED) == 0)
						man->flags &= ~MAN_ELINE;
				} else {
					assert(n->type == ROFFT_HEAD);
					n = n->parent;
					man->flags &= ~MAN_BLINE;
				}
				man->last = n;
				n = n->parent;
				roff_node_delete(man, man->last);
				continue;
			}
			if (n->type == ROFFT_BLOCK &&
			    man_macro(n->tok)->fp == blk_exp)
				mandoc_msg(MANDOCERR_BLK_NOEND,
				    n->line, n->pos, "%s",
				    roff_name[n->tok]);
		}

		/*
		 * We might delete the man->last node
		 * in the post-validation phase.
		 * Save a pointer to the parent such that
		 * we know where to continue the iteration.
		 */

		man->last = n;
		n = n->parent;
		man->last->flags |= NODE_VALID;
	}

	/*
	 * If we ended up at the parent of the node we were
	 * supposed to rewind to, that means the target node
	 * got deleted, so add the next node we parse as a child
	 * of the parent instead of as a sibling of the target.
	 */

	man->next = (man->last == to) ?
	    ROFF_NEXT_CHILD : ROFF_NEXT_SIBLING;
}

/*
 * Rewinding entails ascending the parse tree until a coherent point,
 * for example, the `SH' macro will close out any intervening `SS'
 * scopes.  When a scope is closed, it must be validated and actioned.
 */
static void
rew_scope(struct roff_man *man, enum roff_tok tok)
{
	struct roff_node *n;

	/* Preserve empty paragraphs before RS. */

	n = man->last;
	if (tok == MAN_RS && n->child == NULL &&
	    (n->tok == MAN_P || n->tok == MAN_PP || n->tok == MAN_LP))
		return;

	for (;;) {
		if (n->type == ROFFT_ROOT)
			return;
		if (n->flags & NODE_VALID) {
			n = n->parent;
			continue;
		}
		if (n->type != ROFFT_BLOCK) {
			if (n->parent->type == ROFFT_ROOT) {
				man_unscope(man, n);
				return;
			} else {
				n = n->parent;
				continue;
			}
		}
		if (tok != MAN_SH && (n->tok == MAN_SH ||
		    (tok != MAN_SS && (n->tok == MAN_SS ||
		     man_macro(n->tok)->fp == blk_exp))))
			return;
		man_unscope(man, n);
		n = man->last;
	}
}


/*
 * Close out a generic explicit macro.
 */
void
blk_close(MACRO_PROT_ARGS)
{
	enum roff_tok		 ctok, ntok;
	const struct roff_node	*nn;
	char			*p, *ep;
	int			 cline, cpos, la, nrew, target;

	nrew = 1;
	switch (tok) {
	case MAN_RE:
		ntok = MAN_RS;
		la = *pos;
		if ( ! man_args(man, line, pos, buf, &p))
			break;
		for (nn = man->last->parent; nn; nn = nn->parent)
			if (nn->tok == ntok && nn->type == ROFFT_BLOCK)
				nrew++;
		target = strtol(p, &ep, 10);
		if (*ep != '\0')
			mandoc_msg(MANDOCERR_ARG_EXCESS, line,
			    la + (buf[la] == '"') + (int)(ep - p),
			    "RE ... %s", ep);
		free(p);
		if (target == 0)
			target = 1;
		nrew -= target;
		if (nrew < 1) {
			mandoc_msg(MANDOCERR_RE_NOTOPEN,
			    line, ppos, "RE %d", target);
			return;
		}
		break;
	case MAN_YS:
		ntok = MAN_SY;
		break;
	case MAN_UE:
		ntok = MAN_UR;
		break;
	case MAN_ME:
		ntok = MAN_MT;
		break;
	default:
		abort();
	}

	for (nn = man->last->parent; nn; nn = nn->parent)
		if (nn->tok == ntok && nn->type == ROFFT_BLOCK && ! --nrew)
			break;

	if (nn == NULL) {
		mandoc_msg(MANDOCERR_BLK_NOTOPEN,
		    line, ppos, "%s", roff_name[tok]);
		rew_scope(man, MAN_PP);
		if (tok == MAN_RE) {
			roff_elem_alloc(man, line, ppos, ROFF_br);
			man->last->flags |= NODE_LINE |
			    NODE_VALID | NODE_ENDED;
			man->next = ROFF_NEXT_SIBLING;
		}
		return;
	}

	cline = man->last->line;
	cpos = man->last->pos;
	ctok = man->last->tok;
	man_unscope(man, nn);

	if (tok == MAN_RE && nn->head->aux > 0)
		roff_setreg(man->roff, "an-margin", nn->head->aux, '-');

	/* Trailing text. */

	if (buf[*pos] != '\0') {
		roff_word_alloc(man, line, ppos, buf + *pos);
		man->last->flags |= NODE_DELIMC;
		if (mandoc_eos(man->last->string, strlen(man->last->string)))
			man->last->flags |= NODE_EOS;
	}

	/* Move a trailing paragraph behind the block. */

	if (ctok == MAN_LP || ctok == MAN_PP || ctok == MAN_P) {
		*pos = strlen(buf);
		blk_imp(man, ctok, cline, cpos, pos, buf);
	}

	/* Synopsis blocks need an explicit end marker for spacing. */

	if (tok == MAN_YS && man->last == nn) {
		roff_elem_alloc(man, line, ppos, tok);
		man_unscope(man, man->last);
	}
}

void
blk_exp(MACRO_PROT_ARGS)
{
	struct roff_node *head;
	char		*p;
	int		 la;

	if (tok == MAN_RS) {
		rew_scope(man, tok);
		man->flags |= ROFF_NONOFILL;
	}
	roff_block_alloc(man, line, ppos, tok);
	head = roff_head_alloc(man, line, ppos, tok);

	la = *pos;
	if (man_args(man, line, pos, buf, &p)) {
		roff_word_alloc(man, line, la, p);
		if (tok == MAN_RS) {
			if (roff_getreg(man->roff, "an-margin") == 0)
				roff_setreg(man->roff, "an-margin",
				    7 * 24, '=');
			if ((head->aux = strtod(p, NULL) * 24.0) > 0)
				roff_setreg(man->roff, "an-margin",
				    head->aux, '+');
		}
		free(p);
	}

	if (buf[*pos] != '\0')
		mandoc_msg(MANDOCERR_ARG_EXCESS, line, *pos,
		    "%s ... %s", roff_name[tok], buf + *pos);

	man_unscope(man, head);
	roff_body_alloc(man, line, ppos, tok);
	man->flags &= ~ROFF_NONOFILL;
}

/*
 * Parse an implicit-block macro.  These contain a ROFFT_HEAD and a
 * ROFFT_BODY contained within a ROFFT_BLOCK.  Rules for closing out other
 * scopes, such as `SH' closing out an `SS', are defined in the rew
 * routines.
 */
void
blk_imp(MACRO_PROT_ARGS)
{
	int		 la;
	char		*p;
	struct roff_node *n;

	rew_scope(man, tok);
	man->flags |= ROFF_NONOFILL;
	if (tok == MAN_SH || tok == MAN_SS)
		man->flags &= ~ROFF_NOFILL;
	roff_block_alloc(man, line, ppos, tok);
	n = roff_head_alloc(man, line, ppos, tok);

	/* Add line arguments. */

	for (;;) {
		la = *pos;
		if ( ! man_args(man, line, pos, buf, &p))
			break;
		roff_word_alloc(man, line, la, p);
		free(p);
	}

	/*
	 * For macros having optional next-line scope,
	 * keep the head open if there were no arguments.
	 * For `TP' and `TQ', always keep the head open.
	 */

	if (man_macro(tok)->flags & MAN_BSCOPED &&
	    (tok == MAN_TP || tok == MAN_TQ || n == man->last)) {
		man->flags |= MAN_BLINE;
		return;
	}

	/* Close out the head and open the body. */

	man_unscope(man, n);
	roff_body_alloc(man, line, ppos, tok);
	man->flags &= ~ROFF_NONOFILL;
}

void
in_line_eoln(MACRO_PROT_ARGS)
{
	int		 la;
	char		*p;
	struct roff_node *n;

	roff_elem_alloc(man, line, ppos, tok);
	n = man->last;

	if (tok == MAN_EX)
		man->flags |= ROFF_NOFILL;
	else if (tok == MAN_EE)
		man->flags &= ~ROFF_NOFILL;

	for (;;) {
		if (buf[*pos] != '\0' && man->last != n && tok == MAN_PD) {
			mandoc_msg(MANDOCERR_ARG_EXCESS, line, *pos,
			    "%s ... %s", roff_name[tok], buf + *pos);
			break;
		}
		la = *pos;
		if ( ! man_args(man, line, pos, buf, &p))
			break;
		if (man_macro(tok)->flags & MAN_JOIN &&
		    man->last->type == ROFFT_TEXT)
			roff_word_append(man, p);
		else
			roff_word_alloc(man, line, la, p);
		free(p);
	}

	/*
	 * Append NODE_EOS in case the last snipped argument
	 * ends with a dot, e.g. `.IR syslog (3).'
	 */

	if (n != man->last &&
	    mandoc_eos(man->last->string, strlen(man->last->string)))
		man->last->flags |= NODE_EOS;

	/*
	 * If no arguments are specified and this is MAN_ESCOPED (i.e.,
	 * next-line scoped), then set our mode to indicate that we're
	 * waiting for terms to load into our context.
	 */

	if (n == man->last && man_macro(tok)->flags & MAN_ESCOPED) {
		man->flags |= MAN_ELINE;
		return;
	}

	assert(man->last->type != ROFFT_ROOT);
	man->next = ROFF_NEXT_SIBLING;

	/* Rewind our element scope. */

	for ( ; man->last; man->last = man->last->parent) {
		man->last->flags |= NODE_VALID;
		if (man->last == n)
			break;
	}

	/* Rewind next-line scoped ancestors, if any. */

	if (man_macro(tok)->flags & MAN_ESCOPED)
		man_descope(man, line, ppos, NULL);
}

void
man_endparse(struct roff_man *man)
{
	man_unscope(man, man->meta.first);
}

static int
man_args(struct roff_man *man, int line, int *pos, char *buf, char **v)
{
	char	 *start;

	assert(*pos);
	*v = start = buf + *pos;
	assert(' ' != *start);

	if ('\0' == *start)
		return 0;

	*v = roff_getarg(man->roff, v, line, pos);
	return 1;
}
/* $Id: man_term.c,v 1.236 2021/06/28 19:50:15 schwarze Exp $ */
/*
 * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze 
 * Copyright (c) 2008-2012 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.
 *
 * Plain text formatter for man(7), used by mandoc(1)
 * for ASCII, UTF-8, PostScript, and PDF output.
 */
#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 "term_tag.h"
#include "main.h"

#define	MAXMARGINS	  64 /* maximum number of indented scopes */

struct	mtermp {
	int		  lmargin[MAXMARGINS]; /* margins (incl. vis. page) */
	int		  lmargincur; /* index of current margin */
	int		  lmarginsz; /* actual number of nested margins */
	size_t		  offset; /* default offset to visible page */
	int		  pardist; /* vert. space before par., unit: [v] */
};

#define	DECL_ARGS	  struct termp *p, \
			  struct mtermp *mt, \
			  struct roff_node *n, \
			  const struct roff_meta *meta

struct	man_term_act {
	int		(*pre)(DECL_ARGS);
	void		(*post)(DECL_ARGS);
	int		  flags;
#define	MAN_NOTEXT	 (1 << 0) /* Never has text children. */
};

static	void		  print_man_nodelist(DECL_ARGS);
static	void		  print_man_node(DECL_ARGS);
static	void		  print_man_head(struct termp *,
				const struct roff_meta *);
static	void		  print_man_foot(struct termp *,
				const struct roff_meta *);
static	void		  print_bvspace(struct termp *,
				struct roff_node *, int);

static	int		  pre_B(DECL_ARGS);
static	int		  pre_DT(DECL_ARGS);
static	int		  pre_HP(DECL_ARGS);
static	int		  pre_I(DECL_ARGS);
static	int		  pre_IP(DECL_ARGS);
static	int		  pre_OP(DECL_ARGS);
static	int		  pre_PD(DECL_ARGS);
static	int		  pre_PP(DECL_ARGS);
static	int		  pre_RS(DECL_ARGS);
static	int		  pre_SH(DECL_ARGS);
static	int		  pre_SS(DECL_ARGS);
static	int		  pre_SY(DECL_ARGS);
static	int		  pre_TP(DECL_ARGS);
static	int		  pre_UR(DECL_ARGS);
static	int		  pre_abort(DECL_ARGS);
static	int		  pre_alternate(DECL_ARGS);
static	int		  pre_ign(DECL_ARGS);
static	int		  pre_in(DECL_ARGS);
static	int		  pre_literal(DECL_ARGS);

static	void		  post_IP(DECL_ARGS);
static	void		  post_HP(DECL_ARGS);
static	void		  post_RS(DECL_ARGS);
static	void		  post_SH(DECL_ARGS);
static	void		  post_SY(DECL_ARGS);
static	void		  post_TP(DECL_ARGS);
static	void		  post_UR(DECL_ARGS);

static const struct man_term_act man_term_acts[MAN_MAX - MAN_TH] = {
	{ NULL, NULL, 0 }, /* TH */
	{ pre_SH, post_SH, 0 }, /* SH */
	{ pre_SS, post_SH, 0 }, /* SS */
	{ pre_TP, post_TP, 0 }, /* TP */
	{ pre_TP, post_TP, 0 }, /* TQ */
	{ pre_abort, NULL, 0 }, /* LP */
	{ pre_PP, NULL, 0 }, /* PP */
	{ pre_abort, NULL, 0 }, /* P */
	{ pre_IP, post_IP, 0 }, /* IP */
	{ pre_HP, post_HP, 0 }, /* HP */
	{ NULL, NULL, 0 }, /* SM */
	{ pre_B, NULL, 0 }, /* SB */
	{ pre_alternate, NULL, 0 }, /* BI */
	{ pre_alternate, NULL, 0 }, /* IB */
	{ pre_alternate, NULL, 0 }, /* BR */
	{ pre_alternate, NULL, 0 }, /* RB */
	{ NULL, NULL, 0 }, /* R */
	{ pre_B, NULL, 0 }, /* B */
	{ pre_I, NULL, 0 }, /* I */
	{ pre_alternate, NULL, 0 }, /* IR */
	{ pre_alternate, NULL, 0 }, /* RI */
	{ NULL, NULL, 0 }, /* RE */
	{ pre_RS, post_RS, 0 }, /* RS */
	{ pre_DT, NULL, 0 }, /* DT */
	{ pre_ign, NULL, MAN_NOTEXT }, /* UC */
	{ pre_PD, NULL, MAN_NOTEXT }, /* PD */
	{ pre_ign, NULL, 0 }, /* AT */
	{ pre_in, NULL, MAN_NOTEXT }, /* in */
	{ pre_SY, post_SY, 0 }, /* SY */
	{ NULL, NULL, 0 }, /* YS */
	{ pre_OP, NULL, 0 }, /* OP */
	{ pre_literal, NULL, 0 }, /* EX */
	{ pre_literal, NULL, 0 }, /* EE */
	{ pre_UR, post_UR, 0 }, /* UR */
	{ NULL, NULL, 0 }, /* UE */
	{ pre_UR, post_UR, 0 }, /* MT */
	{ NULL, NULL, 0 }, /* ME */
};
static const struct man_term_act *man_term_act(enum roff_tok);


static const struct man_term_act *
man_term_act(enum roff_tok tok)
{
	assert(tok >= MAN_TH && tok <= MAN_MAX);
	return man_term_acts + (tok - MAN_TH);
}

void
terminal_man(void *arg, const struct roff_meta *man)
{
	struct mtermp		 mt;
	struct termp		*p;
	struct roff_node	*n, *nc, *nn;
	size_t			 save_defindent;

	p = (struct termp *)arg;
	save_defindent = p->defindent;
	if (p->synopsisonly == 0 && p->defindent == 0)
		p->defindent = 7;
	p->tcol->rmargin = p->maxrmargin = p->defrmargin;
	term_tab_set(p, NULL);
	term_tab_set(p, "T");
	term_tab_set(p, ".5i");

	memset(&mt, 0, sizeof(mt));
	mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
	mt.offset = term_len(p, p->defindent);
	mt.pardist = 1;

	n = man->first->child;
	if (p->synopsisonly) {
		for (nn = NULL; n != NULL; n = n->next) {
			if (n->tok != MAN_SH)
				continue;
			nc = n->child->child;
			if (nc->type != ROFFT_TEXT)
				continue;
			if (strcmp(nc->string, "SYNOPSIS") == 0)
				break;
			if (nn == NULL && strcmp(nc->string, "NAME") == 0)
				nn = n;
		}
		if (n == NULL)
			n = nn;
		p->flags |= TERMP_NOSPACE;
		if (n != NULL && (n = n->child->next->child) != NULL)
			print_man_nodelist(p, &mt, n, man);
		term_newln(p);
	} else {
		term_begin(p, print_man_head, print_man_foot, man);
		p->flags |= TERMP_NOSPACE;
		if (n != NULL)
			print_man_nodelist(p, &mt, n, man);
		term_end(p);
	}
	p->defindent = save_defindent;
}

/*
 * Printing leading vertical space before a block.
 * This is used for the paragraph macros.
 * The rules are pretty simple, since there's very little nesting going
 * on here.  Basically, if we're the first within another block (SS/SH),
 * then don't emit vertical space.  If we are (RS), then do.  If not the
 * first, print it.
 */
static void
print_bvspace(struct termp *p, struct roff_node *n, int pardist)
{
	struct roff_node	*nch;
	int			 i;

	term_newln(p);

	if (n->body != NULL &&
	    (nch = roff_node_child(n->body)) != NULL &&
	    nch->type == ROFFT_TBL)
		return;

	if (n->parent->tok != MAN_RS && roff_node_prev(n) == NULL)
		return;

	for (i = 0; i < pardist; i++)
		term_vspace(p);
}


static int
pre_abort(DECL_ARGS)
{
	abort();
}

static int
pre_ign(DECL_ARGS)
{
	return 0;
}

static int
pre_I(DECL_ARGS)
{
	term_fontrepl(p, TERMFONT_UNDER);
	return 1;
}

static int
pre_literal(DECL_ARGS)
{
	term_newln(p);

	/*
	 * Unlike .IP and .TP, .HP does not have a HEAD.
	 * So in case a second call to term_flushln() is needed,
	 * indentation has to be set up explicitly.
	 */
	if (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) {
		p->tcol->offset = p->tcol->rmargin;
		p->tcol->rmargin = p->maxrmargin;
		p->trailspace = 0;
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
		p->flags |= TERMP_NOSPACE;
	}
	return 0;
}

static int
pre_PD(DECL_ARGS)
{
	struct roffsu	 su;

	n = n->child;
	if (n == NULL) {
		mt->pardist = 1;
		return 0;
	}
	assert(n->type == ROFFT_TEXT);
	if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
		mt->pardist = term_vspan(p, &su);
	return 0;
}

static int
pre_alternate(DECL_ARGS)
{
	enum termfont		 font[2];
	struct roff_node	*nn;
	int			 i;

	switch (n->tok) {
	case MAN_RB:
		font[0] = TERMFONT_NONE;
		font[1] = TERMFONT_BOLD;
		break;
	case MAN_RI:
		font[0] = TERMFONT_NONE;
		font[1] = TERMFONT_UNDER;
		break;
	case MAN_BR:
		font[0] = TERMFONT_BOLD;
		font[1] = TERMFONT_NONE;
		break;
	case MAN_BI:
		font[0] = TERMFONT_BOLD;
		font[1] = TERMFONT_UNDER;
		break;
	case MAN_IR:
		font[0] = TERMFONT_UNDER;
		font[1] = TERMFONT_NONE;
		break;
	case MAN_IB:
		font[0] = TERMFONT_UNDER;
		font[1] = TERMFONT_BOLD;
		break;
	default:
		abort();
	}
	for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i = 1 - i) {
		term_fontrepl(p, font[i]);
		assert(nn->type == ROFFT_TEXT);
		term_word(p, nn->string);
		if (nn->flags & NODE_EOS)
			p->flags |= TERMP_SENTENCE;
		if (nn->next != NULL)
			p->flags |= TERMP_NOSPACE;
	}
	return 0;
}

static int
pre_B(DECL_ARGS)
{
	term_fontrepl(p, TERMFONT_BOLD);
	return 1;
}

static int
pre_OP(DECL_ARGS)
{
	term_word(p, "[");
	p->flags |= TERMP_KEEP | TERMP_NOSPACE;

	if ((n = n->child) != NULL) {
		term_fontrepl(p, TERMFONT_BOLD);
		term_word(p, n->string);
	}
	if (n != NULL && n->next != NULL) {
		term_fontrepl(p, TERMFONT_UNDER);
		term_word(p, n->next->string);
	}
	term_fontrepl(p, TERMFONT_NONE);
	p->flags &= ~TERMP_KEEP;
	p->flags |= TERMP_NOSPACE;
	term_word(p, "]");
	return 0;
}

static int
pre_in(DECL_ARGS)
{
	struct roffsu	 su;
	const char	*cp;
	size_t		 v;
	int		 less;

	term_newln(p);

	if (n->child == NULL) {
		p->tcol->offset = mt->offset;
		return 0;
	}

	cp = n->child->string;
	less = 0;

	if (*cp == '-')
		less = -1;
	else if (*cp == '+')
		less = 1;
	else
		cp--;

	if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
		return 0;

	v = term_hen(p, &su);

	if (less < 0)
		p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
	else if (less > 0)
		p->tcol->offset += v;
	else
		p->tcol->offset = v;
	if (p->tcol->offset > SHRT_MAX)
		p->tcol->offset = term_len(p, p->defindent);

	return 0;
}

static int
pre_DT(DECL_ARGS)
{
	term_tab_set(p, NULL);
	term_tab_set(p, "T");
	term_tab_set(p, ".5i");
	return 0;
}

static int
pre_HP(DECL_ARGS)
{
	struct roffsu		 su;
	const struct roff_node	*nn;
	int			 len;

	switch (n->type) {
	case ROFFT_BLOCK:
		print_bvspace(p, n, mt->pardist);
		return 1;
	case ROFFT_HEAD:
		return 0;
	case ROFFT_BODY:
		break;
	default:
		abort();
	}

	if (n->child == NULL)
		return 0;

	if ((n->child->flags & NODE_NOFILL) == 0) {
		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
		p->trailspace = 2;
	}

	/* Calculate offset. */

	if ((nn = n->parent->head->child) != NULL &&
	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
		len = term_hen(p, &su);
		if (len < 0 && (size_t)(-len) > mt->offset)
			len = -mt->offset;
		else if (len > SHRT_MAX)
			len = term_len(p, p->defindent);
		mt->lmargin[mt->lmargincur] = len;
	} else
		len = mt->lmargin[mt->lmargincur];

	p->tcol->offset = mt->offset;
	p->tcol->rmargin = mt->offset + len;
	return 1;
}

static void
post_HP(DECL_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
	case ROFFT_HEAD:
		break;
	case ROFFT_BODY:
		term_newln(p);

		/*
		 * Compatibility with a groff bug.
		 * The .HP macro uses the undocumented .tag request
		 * which causes a line break and cancels no-space
		 * mode even if there isn't any output.
		 */

		if (n->child == NULL)
			term_vspace(p);

		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
		p->trailspace = 0;
		p->tcol->offset = mt->offset;
		p->tcol->rmargin = p->maxrmargin;
		break;
	default:
		abort();
	}
}

static int
pre_PP(DECL_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
		print_bvspace(p, n, mt->pardist);
		break;
	case ROFFT_HEAD:
		return 0;
	case ROFFT_BODY:
		p->tcol->offset = mt->offset;
		break;
	default:
		abort();
	}
	return 1;
}

static int
pre_IP(DECL_ARGS)
{
	struct roffsu		 su;
	const struct roff_node	*nn;
	int			 len;

	switch (n->type) {
	case ROFFT_BLOCK:
		print_bvspace(p, n, mt->pardist);
		return 1;
	case ROFFT_HEAD:
		p->flags |= TERMP_NOBREAK;
		p->trailspace = 1;
		break;
	case ROFFT_BODY:
		p->flags |= TERMP_NOSPACE;
		break;
	default:
		abort();
	}

	/* Calculate the offset from the optional second argument. */
	if ((nn = n->parent->head->child) != NULL &&
	    (nn = nn->next) != NULL &&
	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
		len = term_hen(p, &su);
		if (len < 0 && (size_t)(-len) > mt->offset)
			len = -mt->offset;
		else if (len > SHRT_MAX)
			len = term_len(p, p->defindent);
		mt->lmargin[mt->lmargincur] = len;
	} else
		len = mt->lmargin[mt->lmargincur];

	switch (n->type) {
	case ROFFT_HEAD:
		p->tcol->offset = mt->offset;
		p->tcol->rmargin = mt->offset + len;
		if (n->child != NULL)
			print_man_node(p, mt, n->child, meta);
		return 0;
	case ROFFT_BODY:
		p->tcol->offset = mt->offset + len;
		p->tcol->rmargin = p->maxrmargin;
		break;
	default:
		abort();
	}
	return 1;
}

static void
post_IP(DECL_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		break;
	case ROFFT_HEAD:
		term_flushln(p);
		p->flags &= ~TERMP_NOBREAK;
		p->trailspace = 0;
		p->tcol->rmargin = p->maxrmargin;
		break;
	case ROFFT_BODY:
		term_newln(p);
		p->tcol->offset = mt->offset;
		break;
	default:
		abort();
	}
}

static int
pre_TP(DECL_ARGS)
{
	struct roffsu		 su;
	struct roff_node	*nn;
	int			 len;

	switch (n->type) {
	case ROFFT_BLOCK:
		if (n->tok == MAN_TP)
			print_bvspace(p, n, mt->pardist);
		return 1;
	case ROFFT_HEAD:
		p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
		p->trailspace = 1;
		break;
	case ROFFT_BODY:
		p->flags |= TERMP_NOSPACE;
		break;
	default:
		abort();
	}

	/* Calculate offset. */

	if ((nn = n->parent->head->child) != NULL &&
	    nn->string != NULL && ! (NODE_LINE & nn->flags) &&
	    a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
		len = term_hen(p, &su);
		if (len < 0 && (size_t)(-len) > mt->offset)
			len = -mt->offset;
		else if (len > SHRT_MAX)
			len = term_len(p, p->defindent);
		mt->lmargin[mt->lmargincur] = len;
	} else
		len = mt->lmargin[mt->lmargincur];

	switch (n->type) {
	case ROFFT_HEAD:
		p->tcol->offset = mt->offset;
		p->tcol->rmargin = mt->offset + len;

		/* Don't print same-line elements. */
		nn = n->child;
		while (nn != NULL && (nn->flags & NODE_LINE) == 0)
			nn = nn->next;

		while (nn != NULL) {
			print_man_node(p, mt, nn, meta);
			nn = nn->next;
		}
		return 0;
	case ROFFT_BODY:
		p->tcol->offset = mt->offset + len;
		p->tcol->rmargin = p->maxrmargin;
		p->trailspace = 0;
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
		break;
	default:
		abort();
	}
	return 1;
}

static void
post_TP(DECL_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		break;
	case ROFFT_HEAD:
		term_flushln(p);
		break;
	case ROFFT_BODY:
		term_newln(p);
		p->tcol->offset = mt->offset;
		break;
	default:
		abort();
	}
}

static int
pre_SS(DECL_ARGS)
{
	int	 i;

	switch (n->type) {
	case ROFFT_BLOCK:
		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
		mt->offset = term_len(p, p->defindent);

		/*
		 * No vertical space before the first subsection
		 * and after an empty subsection.
		 */

		if ((n = roff_node_prev(n)) == NULL ||
		    (n->tok == MAN_SS && roff_node_child(n->body) == NULL))
			break;

		for (i = 0; i < mt->pardist; i++)
			term_vspace(p);
		break;
	case ROFFT_HEAD:
		term_fontrepl(p, TERMFONT_BOLD);
		p->tcol->offset = term_len(p, 3);
		p->tcol->rmargin = mt->offset;
		p->trailspace = mt->offset;
		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
		break;
	case ROFFT_BODY:
		p->tcol->offset = mt->offset;
		p->tcol->rmargin = p->maxrmargin;
		p->trailspace = 0;
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
		break;
	default:
		break;
	}
	return 1;
}

static int
pre_SH(DECL_ARGS)
{
	int	 i;

	switch (n->type) {
	case ROFFT_BLOCK:
		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
		mt->offset = term_len(p, p->defindent);

		/*
		 * No vertical space before the first section
		 * and after an empty section.
		 */

		if ((n = roff_node_prev(n)) == NULL ||
		    (n->tok == MAN_SH && roff_node_child(n->body) == NULL))
			break;

		for (i = 0; i < mt->pardist; i++)
			term_vspace(p);
		break;
	case ROFFT_HEAD:
		term_fontrepl(p, TERMFONT_BOLD);
		p->tcol->offset = 0;
		p->tcol->rmargin = mt->offset;
		p->trailspace = mt->offset;
		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
		break;
	case ROFFT_BODY:
		p->tcol->offset = mt->offset;
		p->tcol->rmargin = p->maxrmargin;
		p->trailspace = 0;
		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
		break;
	default:
		abort();
	}
	return 1;
}

static void
post_SH(DECL_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		break;
	case ROFFT_HEAD:
	case ROFFT_BODY:
		term_newln(p);
		break;
	default:
		abort();
	}
}

static int
pre_RS(DECL_ARGS)
{
	struct roffsu	 su;

	switch (n->type) {
	case ROFFT_BLOCK:
		term_newln(p);
		return 1;
	case ROFFT_HEAD:
		return 0;
	case ROFFT_BODY:
		break;
	default:
		abort();
	}

	n = n->parent->head;
	n->aux = SHRT_MAX + 1;
	if (n->child == NULL)
		n->aux = mt->lmargin[mt->lmargincur];
	else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
		n->aux = term_hen(p, &su);
	if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
		n->aux = -mt->offset;
	else if (n->aux > SHRT_MAX)
		n->aux = term_len(p, p->defindent);

	mt->offset += n->aux;
	p->tcol->offset = mt->offset;
	p->tcol->rmargin = p->maxrmargin;

	if (++mt->lmarginsz < MAXMARGINS)
		mt->lmargincur = mt->lmarginsz;

	mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
	return 1;
}

static void
post_RS(DECL_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
	case ROFFT_HEAD:
		return;
	case ROFFT_BODY:
		break;
	default:
		abort();
	}
	term_newln(p);
	mt->offset -= n->parent->head->aux;
	p->tcol->offset = mt->offset;
	if (--mt->lmarginsz < MAXMARGINS)
		mt->lmargincur = mt->lmarginsz;
}

static int
pre_SY(DECL_ARGS)
{
	const struct roff_node	*nn;
	int			 len;

	switch (n->type) {
	case ROFFT_BLOCK:
		if ((nn = roff_node_prev(n)) == NULL || nn->tok != MAN_SY)
			print_bvspace(p, n, mt->pardist);
		return 1;
	case ROFFT_HEAD:
	case ROFFT_BODY:
		break;
	default:
		abort();
	}

	nn = n->parent->head->child;
	len = nn == NULL ? 1 : term_strlen(p, nn->string) + 1;

	switch (n->type) {
	case ROFFT_HEAD:
		p->tcol->offset = mt->offset;
		p->tcol->rmargin = mt->offset + len;
		if (n->next->child == NULL ||
		    (n->next->child->flags & NODE_NOFILL) == 0)
			p->flags |= TERMP_NOBREAK;
		term_fontrepl(p, TERMFONT_BOLD);
		break;
	case ROFFT_BODY:
		mt->lmargin[mt->lmargincur] = len;
		p->tcol->offset = mt->offset + len;
		p->tcol->rmargin = p->maxrmargin;
		p->flags |= TERMP_NOSPACE;
		break;
	default:
		abort();
	}
	return 1;
}

static void
post_SY(DECL_ARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		break;
	case ROFFT_HEAD:
		term_flushln(p);
		p->flags &= ~TERMP_NOBREAK;
		break;
	case ROFFT_BODY:
		term_newln(p);
		p->tcol->offset = mt->offset;
		break;
	default:
		abort();
	}
}

static int
pre_UR(DECL_ARGS)
{
	return n->type != ROFFT_HEAD;
}

static void
post_UR(DECL_ARGS)
{
	if (n->type != ROFFT_BLOCK)
		return;

	term_word(p, "<");
	p->flags |= TERMP_NOSPACE;

	if (n->child->child != NULL)
		print_man_node(p, mt, n->child->child, meta);

	p->flags |= TERMP_NOSPACE;
	term_word(p, ">");
}

static void
print_man_node(DECL_ARGS)
{
	const struct man_term_act *act;
	int c;

	if (n->flags & NODE_ID)
		term_tag_write(n, p->line);

	switch (n->type) {
	case ROFFT_TEXT:
		/*
		 * If we have a blank line, output a vertical space.
		 * If we have a space as the first character, break
		 * before printing the line's data.
		 */
		if (*n->string == '\0') {
			if (p->flags & TERMP_NONEWLINE)
				term_newln(p);
			else
				term_vspace(p);
			return;
		} else if (*n->string == ' ' && n->flags & NODE_LINE &&
		    (p->flags & TERMP_NONEWLINE) == 0)
			term_newln(p);
		else if (n->flags & NODE_DELIMC)
			p->flags |= TERMP_NOSPACE;

		term_word(p, n->string);
		goto out;
	case ROFFT_COMMENT:
		return;
	case ROFFT_EQN:
		if ( ! (n->flags & NODE_LINE))
			p->flags |= TERMP_NOSPACE;
		term_eqn(p, n->eqn);
		if (n->next != NULL && ! (n->next->flags & NODE_LINE))
			p->flags |= TERMP_NOSPACE;
		return;
	case ROFFT_TBL:
		if (p->tbl.cols == NULL)
			term_vspace(p);
		term_tbl(p, n->span);
		return;
	default:
		break;
	}

	if (n->tok < ROFF_MAX) {
		roff_term_pre(p, n);
		return;
	}

	act = man_term_act(n->tok);
	if ((act->flags & MAN_NOTEXT) == 0 && n->tok != MAN_SM)
		term_fontrepl(p, TERMFONT_NONE);

	c = 1;
	if (act->pre != NULL)
		c = (*act->pre)(p, mt, n, meta);

	if (c && n->child != NULL)
		print_man_nodelist(p, mt, n->child, meta);

	if (act->post != NULL)
		(*act->post)(p, mt, n, meta);
	if ((act->flags & MAN_NOTEXT) == 0 && n->tok != MAN_SM)
		term_fontrepl(p, TERMFONT_NONE);

out:
	/*
	 * If we're in a literal context, make sure that words
	 * together on the same line stay together.  This is a
	 * POST-printing call, so we check the NEXT word.  Since
	 * -man doesn't have nested macros, we don't need to be
	 * more specific than this.
	 */
	if (n->flags & NODE_NOFILL &&
	    ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
	    (n->next == NULL || n->next->flags & NODE_LINE)) {
		p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
		if (n->string != NULL && *n->string != '\0')
			term_flushln(p);
		else
			term_newln(p);
		p->flags &= ~TERMP_BRNEVER;
		if (p->tcol->rmargin < p->maxrmargin &&
		    n->parent->tok == MAN_HP) {
			p->tcol->offset = p->tcol->rmargin;
			p->tcol->rmargin = p->maxrmargin;
		}
	}
	if (n->flags & NODE_EOS)
		p->flags |= TERMP_SENTENCE;
}

static void
print_man_nodelist(DECL_ARGS)
{
	while (n != NULL) {
		print_man_node(p, mt, n, meta);
		n = n->next;
	}
}

static void
print_man_foot(struct termp *p, const struct roff_meta *meta)
{
	char			*title;
	size_t			 datelen, titlen;

	assert(meta->title);
	assert(meta->msec);
	assert(meta->date);

	term_fontrepl(p, TERMFONT_NONE);

	if (meta->hasbody)
		term_vspace(p);

	/*
	 * Temporary, undocumented option to imitate mdoc(7) output.
	 * In the bottom right corner, use the operating system
	 * instead of the title.
	 */

	if ( ! p->mdocstyle) {
		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);
	free(title);
}
/* $Id: man_validate.c,v 1.156 2021/08/10 12:55:03 schwarze Exp $ */
/*
 * Copyright (c) 2010, 2012-2020 Ingo Schwarze 
 * Copyright (c) 2008, 2009, 2010, 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.
 *
 * Validation module for man(7) syntax trees used by mandoc(1).
 */
#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"
#include "tag.h"

#define	CHKARGS	  struct roff_man *man, struct roff_node *n

typedef	void	(*v_check)(CHKARGS);

static	void	  check_abort(CHKARGS) __attribute__((__noreturn__));
static	void	  check_par(CHKARGS);
static	void	  check_part(CHKARGS);
static	void	  check_root(CHKARGS);
static	void	  check_tag(struct roff_node *, struct roff_node *);
static	void	  check_text(CHKARGS);

static	void	  post_AT(CHKARGS);
static	void	  post_EE(CHKARGS);
static	void	  post_EX(CHKARGS);
static	void	  post_IP(CHKARGS);
static	void	  post_OP(CHKARGS);
static	void	  post_SH(CHKARGS);
static	void	  post_TH(CHKARGS);
static	void	  post_TP(CHKARGS);
static	void	  post_UC(CHKARGS);
static	void	  post_UR(CHKARGS);
static	void	  post_in(CHKARGS);

static	const v_check man_valids[MAN_MAX - MAN_TH] = {
	post_TH,    /* TH */
	post_SH,    /* SH */
	post_SH,    /* SS */
	post_TP,    /* TP */
	post_TP,    /* TQ */
	check_abort,/* LP */
	check_par,  /* PP */
	check_abort,/* P */
	post_IP,    /* IP */
	NULL,       /* HP */
	NULL,       /* SM */
	NULL,       /* SB */
	NULL,       /* BI */
	NULL,       /* IB */
	NULL,       /* BR */
	NULL,       /* RB */
	NULL,       /* R */
	NULL,       /* B */
	NULL,       /* I */
	NULL,       /* IR */
	NULL,       /* RI */
	NULL,       /* RE */
	check_part, /* RS */
	NULL,       /* DT */
	post_UC,    /* UC */
	NULL,       /* PD */
	post_AT,    /* AT */
	post_in,    /* in */
	NULL,       /* SY */
	NULL,       /* YS */
	post_OP,    /* OP */
	post_EX,    /* EX */
	post_EE,    /* EE */
	post_UR,    /* UR */
	NULL,       /* UE */
	post_UR,    /* MT */
	NULL,       /* ME */
};


/* Validate the subtree rooted at man->last. */
void
man_validate(struct roff_man *man)
{
	struct roff_node *n;
	const v_check	 *cp;

	/*
	 * Translate obsolete macros such that later code
	 * does not need to look for them.
	 */

	n = man->last;
	switch (n->tok) {
	case MAN_LP:
	case MAN_P:
		n->tok = MAN_PP;
		break;
	default:
		break;
	}

	/*
	 * Iterate over all children, recursing into each one
	 * in turn, depth-first.
	 */

	man->last = man->last->child;
	while (man->last != NULL) {
		man_validate(man);
		if (man->last == n)
			man->last = man->last->child;
		else
			man->last = man->last->next;
	}

	/* Finally validate the macro itself. */

	man->last = n;
	man->next = ROFF_NEXT_SIBLING;
	switch (n->type) {
	case ROFFT_TEXT:
		check_text(man, n);
		break;
	case ROFFT_ROOT:
		check_root(man, n);
		break;
	case ROFFT_COMMENT:
	case ROFFT_EQN:
	case ROFFT_TBL:
		break;
	default:
		if (n->tok < ROFF_MAX) {
			roff_validate(man);
			break;
		}
		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
		cp = man_valids + (n->tok - MAN_TH);
		if (*cp)
			(*cp)(man, n);
		if (man->last == n)
			n->flags |= NODE_VALID;
		break;
	}
}

static void
check_root(CHKARGS)
{
	assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);

	if (n->last == NULL || n->last->type == ROFFT_COMMENT)
		mandoc_msg(MANDOCERR_DOC_EMPTY, n->line, n->pos, NULL);
	else
		man->meta.hasbody = 1;

	if (NULL == man->meta.title) {
		mandoc_msg(MANDOCERR_TH_NOTITLE, n->line, n->pos, NULL);

		/*
		 * If a title hasn't been set, do so now (by
		 * implication, date and section also aren't set).
		 */

		man->meta.title = mandoc_strdup("");
		man->meta.msec = mandoc_strdup("");
		man->meta.date = mandoc_normdate(NULL, NULL);
	}

	if (man->meta.os_e &&
	    (man->meta.rcsids & (1 << man->meta.os_e)) == 0)
		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
		    man->meta.os_e == MANDOC_OS_OPENBSD ?
		    "(OpenBSD)" : "(NetBSD)");
}

static void
check_abort(CHKARGS)
{
	abort();
}

/*
 * Skip leading whitespace, dashes, backslashes, and font escapes,
 * then create a tag if the first following byte is a letter.
 * Priority is high unless whitespace is present.
 */
static void
check_tag(struct roff_node *n, struct roff_node *nt)
{
	const char	*cp, *arg;
	int		 prio, sz;

	if (nt == NULL || nt->type != ROFFT_TEXT)
		return;

	cp = nt->string;
	prio = TAG_STRONG;
	for (;;) {
		switch (*cp) {
		case ' ':
		case '\t':
			prio = TAG_WEAK;
			/* FALLTHROUGH */
		case '-':
			cp++;
			break;
		case '\\':
			cp++;
			switch (mandoc_escape(&cp, &arg, &sz)) {
			case ESCAPE_FONT:
			case ESCAPE_FONTBOLD:
			case ESCAPE_FONTITALIC:
			case ESCAPE_FONTBI:
			case ESCAPE_FONTROMAN:
			case ESCAPE_FONTCR:
			case ESCAPE_FONTCB:
			case ESCAPE_FONTCI:
			case ESCAPE_FONTPREV:
			case ESCAPE_IGNORE:
				break;
			case ESCAPE_SPECIAL:
				if (sz != 1)
					return;
				switch (*arg) {
				case '-':
				case 'e':
					break;
				default:
					return;
				}
				break;
			default:
				return;
			}
			break;
		default:
			if (isalpha((unsigned char)*cp))
				tag_put(cp, prio, n);
			return;
		}
	}
}

static void
check_text(CHKARGS)
{
	char		*cp, *p;

	if (n->flags & NODE_NOFILL)
		return;

	cp = n->string;
	for (p = cp; NULL != (p = strchr(p, '\t')); p++)
		mandoc_msg(MANDOCERR_FI_TAB,
		    n->line, n->pos + (int)(p - cp), NULL);
}

static void
post_EE(CHKARGS)
{
	if ((n->flags & NODE_NOFILL) == 0)
		mandoc_msg(MANDOCERR_FI_SKIP, n->line, n->pos, "EE");
}

static void
post_EX(CHKARGS)
{
	if (n->flags & NODE_NOFILL)
		mandoc_msg(MANDOCERR_NF_SKIP, n->line, n->pos, "EX");
}

static void
post_OP(CHKARGS)
{

	if (n->child == NULL)
		mandoc_msg(MANDOCERR_OP_EMPTY, n->line, n->pos, "OP");
	else if (n->child->next != NULL && n->child->next->next != NULL) {
		n = n->child->next->next;
		mandoc_msg(MANDOCERR_ARG_EXCESS,
		    n->line, n->pos, "OP ... %s", n->string);
	}
}

static void
post_SH(CHKARGS)
{
	struct roff_node	*nc;
	char			*cp, *tag;

	nc = n->child;
	switch (n->type) {
	case ROFFT_HEAD:
		tag = NULL;
		deroff(&tag, n);
		if (tag != NULL) {
			for (cp = tag; *cp != '\0'; cp++)
				if (*cp == ' ')
					*cp = '_';
			if (nc != NULL && nc->type == ROFFT_TEXT &&
			    strcmp(nc->string, tag) == 0)
				tag_put(NULL, TAG_STRONG, n);
			else
				tag_put(tag, TAG_FALLBACK, n);
			free(tag);
		}
		return;
	case ROFFT_BODY:
		if (nc != NULL)
			break;
		return;
	default:
		return;
	}

	if (nc->tok == MAN_PP && nc->body->child != NULL) {
		while (nc->body->last != NULL) {
			man->next = ROFF_NEXT_CHILD;
			roff_node_relink(man, nc->body->last);
			man->last = n;
		}
	}

	if (nc->tok == MAN_PP || nc->tok == ROFF_sp || nc->tok == ROFF_br) {
		mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos,
		    "%s after %s", roff_name[nc->tok], roff_name[n->tok]);
		roff_node_delete(man, nc);
	}

	/*
	 * Trailing PP is empty, so it is deleted by check_par().
	 * Trailing sp is significant.
	 */

	if ((nc = n->last) != NULL && nc->tok == ROFF_br) {
		mandoc_msg(MANDOCERR_PAR_SKIP,
		    nc->line, nc->pos, "%s at the end of %s",
		    roff_name[nc->tok], roff_name[n->tok]);
		roff_node_delete(man, nc);
	}
}

static void
post_UR(CHKARGS)
{
	if (n->type == ROFFT_HEAD && n->child == NULL)
		mandoc_msg(MANDOCERR_UR_NOHEAD, n->line, n->pos,
		    "%s", roff_name[n->tok]);
	check_part(man, n);
}

static void
check_part(CHKARGS)
{

	if (n->type == ROFFT_BODY && n->child == NULL)
		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
		    "%s", roff_name[n->tok]);
}

static void
check_par(CHKARGS)
{

	switch (n->type) {
	case ROFFT_BLOCK:
		if (n->body->child == NULL)
			roff_node_delete(man, n);
		break;
	case ROFFT_BODY:
		if (n->child != NULL &&
		    (n->child->tok == ROFF_sp || n->child->tok == ROFF_br)) {
			mandoc_msg(MANDOCERR_PAR_SKIP,
			    n->child->line, n->child->pos,
			    "%s after %s", roff_name[n->child->tok],
			    roff_name[n->tok]);
			roff_node_delete(man, n->child);
		}
		if (n->child == NULL)
			mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
			    "%s empty", roff_name[n->tok]);
		break;
	case ROFFT_HEAD:
		if (n->child != NULL)
			mandoc_msg(MANDOCERR_ARG_SKIP,
			    n->line, n->pos, "%s %s%s",
			    roff_name[n->tok], n->child->string,
			    n->child->next != NULL ? " ..." : "");
		break;
	default:
		break;
	}
}

static void
post_IP(CHKARGS)
{
	switch (n->type) {
	case ROFFT_BLOCK:
		if (n->head->child == NULL && n->body->child == NULL)
			roff_node_delete(man, n);
		break;
	case ROFFT_HEAD:
		check_tag(n, n->child);
		break;
	case ROFFT_BODY:
		if (n->parent->head->child == NULL && n->child == NULL)
			mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
			    "%s empty", roff_name[n->tok]);
		break;
	default:
		break;
	}
}

/*
 * The first next-line element in the head is the tag.
 * If that's a font macro, use its first child instead.
 */
static void
post_TP(CHKARGS)
{
	struct roff_node *nt;

	if (n->type != ROFFT_HEAD || (nt = n->child) == NULL)
		return;

	while ((nt->flags & NODE_LINE) == 0)
		if ((nt = nt->next) == NULL)
			return;

	switch (nt->tok) {
	case MAN_B:
	case MAN_BI:
	case MAN_BR:
	case MAN_I:
	case MAN_IB:
	case MAN_IR:
		nt = nt->child;
		break;
	default:
		break;
	}
	check_tag(n, nt);
}

static void
post_TH(CHKARGS)
{
	struct roff_node *nb;
	const char	*p;

	free(man->meta.title);
	free(man->meta.vol);
	free(man->meta.os);
	free(man->meta.msec);
	free(man->meta.date);

	man->meta.title = man->meta.vol = man->meta.date =
	    man->meta.msec = man->meta.os = NULL;

	nb = n;

	/* ->TITLE<- MSEC DATE OS VOL */

	n = n->child;
	if (n != NULL && n->string != NULL) {
		for (p = n->string; *p != '\0'; p++) {
			/* Only warn about this once... */
			if (isalpha((unsigned char)*p) &&
			    ! isupper((unsigned char)*p)) {
				mandoc_msg(MANDOCERR_TITLE_CASE, n->line,
				    n->pos + (int)(p - n->string),
				    "TH %s", n->string);
				break;
			}
		}
		man->meta.title = mandoc_strdup(n->string);
	} else {
		man->meta.title = mandoc_strdup("");
		mandoc_msg(MANDOCERR_TH_NOTITLE, nb->line, nb->pos, "TH");
	}

	/* TITLE ->MSEC<- DATE OS VOL */

	if (n != NULL)
		n = n->next;
	if (n != NULL && n->string != NULL) {
		man->meta.msec = mandoc_strdup(n->string);
		if (man->filesec != '\0' &&
		    man->filesec != *n->string &&
		    *n->string >= '1' && *n->string <= '9')
			mandoc_msg(MANDOCERR_MSEC_FILE, n->line, n->pos,
			    "*.%c vs TH ... %c", man->filesec, *n->string);
	} else {
		man->meta.msec = mandoc_strdup("");
		mandoc_msg(MANDOCERR_MSEC_MISSING,
		    nb->line, nb->pos, "TH %s", man->meta.title);
	}

	/* TITLE MSEC ->DATE<- OS VOL */

	if (n != NULL)
		n = n->next;
	if (man->quick && n != NULL)
		man->meta.date = mandoc_strdup("");
	else
		man->meta.date = mandoc_normdate(n, nb);

	/* TITLE MSEC DATE ->OS<- VOL */

	if (n && (n = n->next))
		man->meta.os = mandoc_strdup(n->string);
	else if (man->os_s != NULL)
		man->meta.os = mandoc_strdup(man->os_s);
	if (man->meta.os_e == MANDOC_OS_OTHER && man->meta.os != NULL) {
		if (strstr(man->meta.os, "OpenBSD") != NULL)
			man->meta.os_e = MANDOC_OS_OPENBSD;
		else if (strstr(man->meta.os, "NetBSD") != NULL)
			man->meta.os_e = MANDOC_OS_NETBSD;
	}

	/* TITLE MSEC DATE OS ->VOL<- */
	/* If missing, use the default VOL name for MSEC. */

	if (n && (n = n->next))
		man->meta.vol = mandoc_strdup(n->string);
	else if ('\0' != man->meta.msec[0] &&
	    (NULL != (p = mandoc_a2msec(man->meta.msec))))
		man->meta.vol = mandoc_strdup(p);

	if (n != NULL && (n = n->next) != NULL)
		mandoc_msg(MANDOCERR_ARG_EXCESS,
		    n->line, n->pos, "TH ... %s", n->string);

	/*
	 * Remove the `TH' node after we've processed it for our
	 * meta-data.
	 */
	roff_node_delete(man, man->last);
}

static void
post_UC(CHKARGS)
{
	static const char * const bsd_versions[] = {
	    "3rd Berkeley Distribution",
	    "4th Berkeley Distribution",
	    "4.2 Berkeley Distribution",
	    "4.3 Berkeley Distribution",
	    "4.4 Berkeley Distribution",
	};

	const char	*p, *s;

	n = n->child;

	if (n == NULL || n->type != ROFFT_TEXT)
		p = bsd_versions[0];
	else {
		s = n->string;
		if (0 == strcmp(s, "3"))
			p = bsd_versions[0];
		else if (0 == strcmp(s, "4"))
			p = bsd_versions[1];
		else if (0 == strcmp(s, "5"))
			p = bsd_versions[2];
		else if (0 == strcmp(s, "6"))
			p = bsd_versions[3];
		else if (0 == strcmp(s, "7"))
			p = bsd_versions[4];
		else
			p = bsd_versions[0];
	}

	free(man->meta.os);
	man->meta.os = mandoc_strdup(p);
}

static void
post_AT(CHKARGS)
{
	static const char * const unix_versions[] = {
	    "7th Edition",
	    "System III",
	    "System V",
	    "System V Release 2",
	};

	struct roff_node *nn;
	const char	*p, *s;

	n = n->child;

	if (n == NULL || n->type != ROFFT_TEXT)
		p = unix_versions[0];
	else {
		s = n->string;
		if (0 == strcmp(s, "3"))
			p = unix_versions[0];
		else if (0 == strcmp(s, "4"))
			p = unix_versions[1];
		else if (0 == strcmp(s, "5")) {
			nn = n->next;
			if (nn != NULL &&
			    nn->type == ROFFT_TEXT &&
			    nn->string[0] != '\0')
				p = unix_versions[3];
			else
				p = unix_versions[2];
		} else
			p = unix_versions[0];
	}

	free(man->meta.os);
	man->meta.os = mandoc_strdup(p);
}

static void
post_in(CHKARGS)
{
	char	*s;

	if (n->parent->tok != MAN_TP ||
	    n->parent->type != ROFFT_HEAD ||
	    n->child == NULL ||
	    *n->child->string == '+' ||
	    *n->child->string == '-')
		return;
	mandoc_asprintf(&s, "+%s", n->child->string);
	free(n->child->string);
	n->child->string = s;
}
/* $OpenBSD: manconf.h,v 1.7 2018/11/22 11:30:15 schwarze Exp $ */
/*
 * Copyright (c) 2011,2015,2017,2018,2020 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.
 *
 * Public interface to man(1) configuration management.
 * For use by the main program and by the formatters.
 */

/* List of unique, absolute paths to manual trees. */

struct	manpaths {
	char	**paths;
	size_t	  sz;
};

/* Data from -O options and man.conf(5) output directives. */

struct	manoutput {
	char	 *includes;
	char	 *man;
	char	 *outfilename;
	char	 *paper;
	char	 *style;
	char	 *tag;
	char	 *tagfilename;
	size_t	  indent;
	size_t	  width;
	int	  fragment;
	int	  mdoc;
	int	  noval;
	int	  synopsisonly;
	int	  tag_found;
	int	  toc;
};

struct	manconf {
	struct manoutput	  output;
	struct manpaths		  manpath;
};


void	 manconf_parse(struct manconf *, const char *, char *, char *);
int	 manconf_output(struct manoutput *, const char *, int);
void	 manconf_free(struct manconf *);
void	 manpath_base(struct manpaths *);
/*	$Id: mandoc.c,v 1.119 2021/08/10 12:55:03 schwarze Exp $ */
/*
 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons 
 * Copyright (c) 2011-2015, 2017-2021 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_FONTCB;
			case 'I':
				return ESCAPE_FONTCI;
			case 'R':
			case 'W':
				return ESCAPE_FONTCR;
			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':
	case '*':
		switch ((*start)[-1]) {
		case 'f':
			gly = ESCAPE_FONT;
			break;
		case '*':
			gly = ESCAPE_DEVICE;
			break;
		default:
			gly = ESCAPE_IGNORE;
			break;
		}
		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;

	/*
	 * 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;
	case ESCAPE_DEVICE:
		assert(*sz == 2 && (*start)[0] == '.' && (*start)[1] == 'T');
		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)
		goto fail;

	/*
	 * Reserve space:
	 * up to 9 characters for the month (September) + blank
	 * up to 2 characters for the day + comma + blank
	 * 4 characters for the year and a terminating '\0'
	 */

	p = buf = mandoc_malloc(10 + 4 + 4 + 1);

	if ((ssz = strftime(p, 10 + 1, "%B ", tm)) == 0)
		goto fail;
	p += (int)ssz;

	/*
	 * The output format is just "%d" here, not "%2d" or "%02d".
	 * That's also the reason why we can't just format the
	 * date as a whole with "%B %e, %Y" or "%B %d, %Y".
	 * Besides, the present approach is less prone to buffer
	 * overflows, in case anybody should ever introduce the bug
	 * of looking at LC_TIME.
	 */

	isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday);
	if (isz < 0 || isz > 4)
		goto fail;
	p += isz;

	if (strftime(p, 4 + 1, "%Y", tm) == 0)
		goto fail;
	return buf;

fail:
	free(buf);
	return mandoc_strdup("");
}

char *
mandoc_normdate(struct roff_node *nch, struct roff_node *nbl)
{
	char		*cp;
	time_t		 t;

	/* No date specified. */

	if (nch == NULL) {
		if (nbl == NULL)
			mandoc_msg(MANDOCERR_DATE_MISSING, 0, 0, NULL);
		else
			mandoc_msg(MANDOCERR_DATE_MISSING, nbl->line,
			    nbl->pos, "%s", roff_name[nbl->tok]);
		return mandoc_strdup("");
	}
	if (*nch->string == '\0') {
		mandoc_msg(MANDOCERR_DATE_MISSING, nch->line,
		    nch->pos, "%s", roff_name[nbl->tok]);
		return mandoc_strdup("");
	}
	if (strcmp(nch->string, "$" "Mdocdate$") == 0)
		return time2a(time(NULL));

	/* Valid mdoc(7) date format. */

	if (a2time(&t, "$" "Mdocdate: %b %d %Y $", nch->string) ||
	    a2time(&t, "%b %d, %Y", nch->string)) {
		cp = time2a(t);
		if (t > time(NULL) + 86400)
			mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line,
			    nch->pos, "%s %s", roff_name[nbl->tok], cp);
		else if (*nch->string != '$' &&
		    strcmp(nch->string, cp) != 0)
			mandoc_msg(MANDOCERR_DATE_NORM, nch->line,
			    nch->pos, "%s %s", roff_name[nbl->tok], cp);
		return cp;
	}

	/* In man(7), do not warn about the legacy format. */

	if (a2time(&t, "%Y-%m-%d", nch->string) == 0)
		mandoc_msg(MANDOCERR_DATE_BAD, nch->line, nch->pos,
		    "%s %s", roff_name[nbl->tok], nch->string);
	else if (t > time(NULL) + 86400)
		mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line, nch->pos,
		    "%s %s", roff_name[nbl->tok], nch->string);
	else if (nbl->tok == MDOC_Dd)
		mandoc_msg(MANDOCERR_DATE_LEGACY, nch->line, nch->pos,
		    "Dd %s", nch->string);

	/* Use any non-mdoc(7) date verbatim. */

	return mandoc_strdup(nch->string);
}

int
mandoc_eos(const char *p, size_t sz)
{
	const char	*q;
	int		 enclosed, found;

	if (0 == sz)
		return 0;

	/*
	 * End-of-sentence recognition must include situations where
	 * some symbols, such as `)', allow prior EOS punctuation to
	 * propagate outward.
	 */

	enclosed = found = 0;
	for (q = p + (int)sz - 1; q >= p; q--) {
		switch (*q) {
		case '\"':
		case '\'':
		case ']':
		case ')':
			if (0 == found)
				enclosed = 1;
			break;
		case '.':
		case '!':
		case '?':
			found = 1;
			break;
		default:
			return found &&
			    (!enclosed || isalnum((unsigned char)*q));
		}
	}

	return found && !enclosed;
}

/*
 * Convert a string to a long that may not be <0.
 * If the string is invalid, or is less than 0, return -1.
 */
int
mandoc_strntoi(const char *p, size_t sz, int base)
{
	char		 buf[32];
	char		*ep;
	long		 v;

	if (sz > 31)
		return -1;

	memcpy(buf, p, sz);
	buf[(int)sz] = '\0';

	errno = 0;
	v = strtol(buf, &ep, base);

	if (buf[0] == '\0' || *ep != '\0')
		return -1;

	if (v > INT_MAX)
		v = INT_MAX;
	if (v < INT_MIN)
		v = INT_MIN;

	return (int)v;
}
/* $Id: mandoc.h,v 1.274 2021/08/14 13:53:08 schwarze Exp $ */
/*
 * Copyright (c) 2012-2021 Ingo Schwarze 
 * Copyright (c) 2010, 2011, 2014 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.
 *
 * Error handling, escape sequence, and character utilities.
 * Can be used by all code in the mandoc package.
 */

#define ASCII_NBRSP	 31  /* non-breaking space */
#define	ASCII_HYPH	 30  /* breakable hyphen */
#define	ASCII_BREAK	 29  /* breakable zero-width space */

/*
 * Status level.  This refers to both internal status (i.e., whilst
 * running, when warnings/errors are reported) and an indicator of a
 * threshold of when to halt (when said internal state exceeds the
 * threshold).
 */
enum	mandoclevel {
	MANDOCLEVEL_OK = 0,
	MANDOCLEVEL_STYLE, /* style suggestions */
	MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */
	MANDOCLEVEL_ERROR, /* input has been thrown away */
	MANDOCLEVEL_UNSUPP, /* input needs unimplemented features */
	MANDOCLEVEL_BADARG, /* bad argument in invocation */
	MANDOCLEVEL_SYSERR, /* system error */
	MANDOCLEVEL_MAX
};

/*
 * All possible things that can go wrong within a parse, be it libroff,
 * libmdoc, or libman.
 */
enum	mandocerr {
	MANDOCERR_OK,

	MANDOCERR_BASE, /* ===== start of base system conventions ===== */

	MANDOCERR_MDOCDATE, /* Mdocdate found: Dd ... */
	MANDOCERR_MDOCDATE_MISSING, /* Mdocdate missing: Dd ... */
	MANDOCERR_ARCH_BAD,  /* unknown architecture: Dt ... arch */
	MANDOCERR_OS_ARG,  /* operating system explicitly specified: Os ... */
	MANDOCERR_RCS_MISSING, /* RCS id missing */

	MANDOCERR_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_XR_BAD,  /* referenced manual not found: Xr name sec */
	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_TEXT_LONG, /* input text line longer than 80 bytes */
	MANDOCERR_DASHDASH, /* verbatim "--", maybe consider using \(em */
	MANDOCERR_FUNC, /* function name without markup: name() */
	MANDOCERR_SPACE_EOL, /* whitespace at end of input line */
	MANDOCERR_COMMENT_BAD, /* bad comment style */

	MANDOCERR_WARNING, /* ===== start of warnings ===== */

	/* related to the prologue */
	MANDOCERR_DT_NOTITLE, /* missing manual title, using UNTITLED: line */
	MANDOCERR_TH_NOTITLE, /* missing manual title, using "": [macro] */
	MANDOCERR_MSEC_MISSING, /* missing manual section, using "": macro */
	MANDOCERR_MSEC_BAD, /* unknown manual section: Dt ... section */
	MANDOCERR_MSEC_FILE, /* filename/section mismatch: ... */
	MANDOCERR_DATE_MISSING, /* missing date, using "": [macro] */
	MANDOCERR_DATE_BAD, /* cannot parse date, using it verbatim: date */
	MANDOCERR_DATE_FUTURE, /* date in the future, using it anyway: date */
	MANDOCERR_OS_MISSING, /* missing Os macro, using "" */
	MANDOCERR_PROLOG_LATE, /* late prologue macro: macro */
	MANDOCERR_PROLOG_ORDER, /* prologue macros out of order: macros */

	/* related to document structure */
	MANDOCERR_SO, /* .so is fragile, better use ln(1): so path */
	MANDOCERR_DOC_EMPTY, /* no document body */
	MANDOCERR_SEC_BEFORE, /* content before first section header: macro */
	MANDOCERR_NAMESEC_FIRST, /* first section is not NAME: Sh title */
	MANDOCERR_NAMESEC_NONM, /* NAME section without Nm before Nd */
	MANDOCERR_NAMESEC_NOND, /* NAME section without description */
	MANDOCERR_NAMESEC_ND, /* description not at the end of NAME */
	MANDOCERR_NAMESEC_BAD, /* bad NAME section content: macro */
	MANDOCERR_NAMESEC_PUNCT, /* missing comma before name: Nm name */
	MANDOCERR_ND_EMPTY, /* missing description line, using "" */
	MANDOCERR_ND_LATE, /* description line outside NAME section */
	MANDOCERR_SEC_ORDER, /* sections out of conventional order: Sh title */
	MANDOCERR_SEC_REP, /* duplicate section title: Sh title */
	MANDOCERR_SEC_MSEC, /* unexpected section: Sh title for ... only */
	MANDOCERR_XR_SELF,  /* cross reference to self: Xr name sec */
	MANDOCERR_XR_ORDER, /* unusual Xr order: ... after ... */
	MANDOCERR_XR_PUNCT, /* unusual Xr punctuation: ... after ... */
	MANDOCERR_AN_MISSING, /* AUTHORS section without An macro */

	/* related to macros and nesting */
	MANDOCERR_MACRO_OBS, /* obsolete macro: macro */
	MANDOCERR_MACRO_CALL, /* macro neither callable nor escaped: macro */
	MANDOCERR_PAR_SKIP, /* skipping paragraph macro: macro ... */
	MANDOCERR_PAR_MOVE, /* moving paragraph macro out of list: macro */
	MANDOCERR_NS_SKIP, /* skipping no-space macro */
	MANDOCERR_BLK_NEST, /* blocks badly nested: macro ... */
	MANDOCERR_BD_NEST, /* nested displays are not portable: macro ... */
	MANDOCERR_BL_MOVE, /* moving content out of list: macro */
	MANDOCERR_TA_LINE, /* first macro on line: Ta */
	MANDOCERR_BLK_LINE, /* line scope broken: macro breaks macro */
	MANDOCERR_BLK_BLANK, /* skipping blank line in line scope */

	/* related to missing arguments */
	MANDOCERR_REQ_EMPTY, /* skipping empty request: request */
	MANDOCERR_COND_EMPTY, /* conditional request controls empty scope */
	MANDOCERR_MACRO_EMPTY, /* skipping empty macro: macro */
	MANDOCERR_BLK_EMPTY, /* empty block: macro */
	MANDOCERR_ARG_EMPTY, /* empty argument, using 0n: macro arg */
	MANDOCERR_BD_NOTYPE, /* missing display type, using -ragged: Bd */
	MANDOCERR_BL_LATETYPE, /* list type is not the first argument: Bl arg */
	MANDOCERR_BL_NOWIDTH, /* missing -width in -tag list, using 6n */
	MANDOCERR_EX_NONAME, /* missing utility name, using "": Ex */
	MANDOCERR_FO_NOHEAD, /* missing function name, using "": Fo */
	MANDOCERR_IT_NOHEAD, /* empty head in list item: Bl -type It */
	MANDOCERR_IT_NOBODY, /* empty list item: Bl -type It */
	MANDOCERR_IT_NOARG, /* missing argument, using next line: Bl -c It */
	MANDOCERR_BF_NOFONT, /* missing font type, using \fR: Bf */
	MANDOCERR_BF_BADFONT, /* unknown font type, using \fR: Bf font */
	MANDOCERR_PF_SKIP, /* nothing follows prefix: Pf arg */
	MANDOCERR_RS_EMPTY, /* empty reference block: Rs */
	MANDOCERR_XR_NOSEC, /* missing section argument: Xr arg */
	MANDOCERR_ARG_STD, /* missing -std argument, adding it: macro */
	MANDOCERR_OP_EMPTY, /* missing option string, using "": OP */
	MANDOCERR_UR_NOHEAD, /* missing resource identifier, using "": UR */
	MANDOCERR_EQN_NOBOX, /* missing eqn box, using "": op */

	/* related to bad arguments */
	MANDOCERR_ARG_REP, /* duplicate argument: macro arg */
	MANDOCERR_AN_REP, /* skipping duplicate argument: An -arg */
	MANDOCERR_BD_REP, /* skipping duplicate display type: Bd -type */
	MANDOCERR_BL_REP, /* skipping duplicate list type: Bl -type */
	MANDOCERR_BL_SKIPW, /* skipping -width argument: Bl -type */
	MANDOCERR_BL_COL, /* wrong number of cells */
	MANDOCERR_AT_BAD, /* unknown AT&T UNIX version: At version */
	MANDOCERR_FA_COMMA, /* comma in function argument: arg */
	MANDOCERR_FN_PAREN, /* parenthesis in function name: arg */
	MANDOCERR_LB_BAD, /* unknown library name: Lb ... */
	MANDOCERR_RS_BAD, /* invalid content in Rs block: macro */
	MANDOCERR_SM_BAD, /* invalid Boolean argument: macro arg */
	MANDOCERR_CHAR_FONT, /* argument contains two font escapes */
	MANDOCERR_FT_BAD, /* unknown font, skipping request: ft font */
	MANDOCERR_TR_ODD, /* odd number of characters in request: tr char */

	/* related to plain text */
	MANDOCERR_FI_BLANK, /* blank line in fill mode, using .sp */
	MANDOCERR_FI_TAB, /* tab in filled text */
	MANDOCERR_EOS, /* new sentence, new line */
	MANDOCERR_ESC_BAD, /* invalid escape sequence: esc */
	MANDOCERR_ESC_UNDEF, /* undefined escape, printing literally: char */
	MANDOCERR_STR_UNDEF, /* undefined string, using "": name */

	/* related to tables */
	MANDOCERR_TBLLAYOUT_SPAN, /* tbl line starts with span */
	MANDOCERR_TBLLAYOUT_DOWN, /* tbl column starts with span */
	MANDOCERR_TBLLAYOUT_VERT, /* skipping vertical bar in tbl layout */

	MANDOCERR_ERROR, /* ===== start of errors ===== */

	/* related to tables */
	MANDOCERR_TBLOPT_ALPHA, /* non-alphabetic character in tbl options */
	MANDOCERR_TBLOPT_BAD, /* skipping unknown tbl option: option */
	MANDOCERR_TBLOPT_NOARG, /* missing tbl option argument: option */
	MANDOCERR_TBLOPT_ARGSZ, /* wrong tbl option argument size: option */
	MANDOCERR_TBLLAYOUT_NONE, /* empty tbl layout */
	MANDOCERR_TBLLAYOUT_CHAR, /* invalid character in tbl layout: char */
	MANDOCERR_TBLLAYOUT_PAR, /* unmatched parenthesis in tbl layout */
	MANDOCERR_TBLLAYOUT_SPC, /* ignoring excessive spacing in tbl layout */
	MANDOCERR_TBLDATA_NONE, /* tbl without any data cells */
	MANDOCERR_TBLDATA_SPAN, /* ignoring data in spanned tbl cell: data */
	MANDOCERR_TBLDATA_EXTRA, /* ignoring extra tbl data cells: data */
	MANDOCERR_TBLDATA_BLK, /* data block open at end of tbl: macro */

	/* related to document structure and macros */
	MANDOCERR_PROLOG_REP, /* duplicate prologue macro: macro */
	MANDOCERR_DT_LATE, /* skipping late title macro: Dt args */
	MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
	MANDOCERR_CHAR_BAD, /* skipping bad character: number */
	MANDOCERR_MACRO, /* skipping unknown macro: macro */
	MANDOCERR_REQ_NOMAC, /* skipping request outside macro: ... */
	MANDOCERR_REQ_INSEC, /* skipping insecure request: request */
	MANDOCERR_IT_STRAY, /* skipping item outside list: It ... */
	MANDOCERR_TA_STRAY, /* skipping column outside column list: Ta */
	MANDOCERR_BLK_NOTOPEN, /* skipping end of block that is not open */
	MANDOCERR_RE_NOTOPEN, /* fewer RS blocks open, skipping: RE arg */
	MANDOCERR_BLK_BROKEN, /* inserting missing end of block: macro ... */
	MANDOCERR_BLK_NOEND, /* appending missing end of block: macro */

	/* related to request and macro arguments */
	MANDOCERR_NAMESC, /* escaped character not allowed in a name: name */
	MANDOCERR_ARG_UNDEF, /* using macro argument outside macro */
	MANDOCERR_ARG_NONUM, /* argument number is not numeric */
	MANDOCERR_BD_FILE, /* NOT IMPLEMENTED: Bd -file */
	MANDOCERR_BD_NOARG, /* skipping display without arguments: Bd */
	MANDOCERR_BL_NOTYPE, /* missing list type, using -item: Bl */
	MANDOCERR_CE_NONUM, /* argument is not numeric, using 1: ce ... */
	MANDOCERR_CHAR_ARG, /* argument is not a character: char ... */
	MANDOCERR_NM_NONAME, /* missing manual name, using "": Nm */
	MANDOCERR_OS_UNAME, /* uname(3) system call failed, using UNKNOWN */
	MANDOCERR_ST_BAD, /* unknown standard specifier: St standard */
	MANDOCERR_IT_NONUM, /* skipping request without numeric argument */
	MANDOCERR_SHIFT, /* excessive shift: ..., but max is ... */
	MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
	MANDOCERR_SO_FAIL, /* .so request failed */
	MANDOCERR_TG_SPC, /* skipping tag containing whitespace: tag */
	MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */
	MANDOCERR_ARG_EXCESS, /* skipping excess arguments: macro ... args */
	MANDOCERR_DIVZERO, /* divide by zero */

	MANDOCERR_UNSUPP, /* ===== start of unsupported features ===== */

	MANDOCERR_TOOLARGE, /* input too large */
	MANDOCERR_CHAR_UNSUPP, /* unsupported control character: number */
	MANDOCERR_ESC_UNSUPP, /* unsupported escape sequence: escape */
	MANDOCERR_REQ_UNSUPP, /* unsupported roff request: request */
	MANDOCERR_WHILE_NEST, /* nested .while loops */
	MANDOCERR_WHILE_OUTOF, /* end of scope with open .while loop */
	MANDOCERR_WHILE_INTO, /* end of .while loop in inner scope */
	MANDOCERR_WHILE_FAIL, /* cannot continue this .while loop */
	MANDOCERR_TBLOPT_EQN, /* eqn delim option in tbl: arg */
	MANDOCERR_TBLLAYOUT_MOD, /* unsupported tbl layout modifier: m */
	MANDOCERR_TBLMACRO, /* ignoring macro in table: macro */
	MANDOCERR_TBL_TMAN, /* skipping tbl in -Tman mode */
	MANDOCERR_EQN_TMAN, /* skipping eqn in -Tman mode */

	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_MAN_TMARKDOWN, /* -Tmarkdown unsupported for man(7) input */

	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_FONTCR, /* constant width font mode */
	ESCAPE_FONTCB, /* constant width bold font mode */
	ESCAPE_FONTCI, /* constant width italic font mode */
	ESCAPE_FONTPREV, /* previous font mode */
	ESCAPE_NUMBERED, /* a numbered glyph */
	ESCAPE_UNICODE, /* a unicode codepoint */
	ESCAPE_DEVICE, /* print the output device name */
	ESCAPE_BREAK, /* break the output line */
	ESCAPE_NOSPACE, /* suppress space if the last on a line */
	ESCAPE_HORIZ, /* horizontal movement */
	ESCAPE_HLINE, /* horizontal line drawing */
	ESCAPE_SKIPCHAR, /* skip the next character */
	ESCAPE_OVERSTRIKE /* overstrike all chars in the argument */
};


enum mandoc_esc	  mandoc_font(const char *, int);
enum mandoc_esc	  mandoc_escape(const char **, const char **, int *);
void		  mandoc_msg_setoutfile(FILE *);
const char	 *mandoc_msg_getinfilename(void);
void		  mandoc_msg_setinfilename(const char *);
enum mandocerr	  mandoc_msg_getmin(void);
void		  mandoc_msg_setmin(enum mandocerr);
enum mandoclevel  mandoc_msg_getrc(void);
void		  mandoc_msg_setrc(enum mandoclevel);
void		  mandoc_msg(enum mandocerr, int, int, const char *, ...)
			__attribute__((__format__ (__printf__, 4, 5)));
void		  mandoc_msg_summary(void);
void		  mchars_alloc(void);
void		  mchars_free(void);
int		  mchars_num2char(const char *, size_t);
const char	 *mchars_uc2str(int);
int		  mchars_num2uc(const char *, size_t);
int		  mchars_spec2cp(const char *, size_t);
const char	 *mchars_spec2str(const char *, size_t, size_t *);
/*	$Id: mandoc_aux.c,v 1.11 2018/02/07 20:04:57 schwarze Exp $ */
/*
 * Copyright (c) 2009, 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 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 

#if HAVE_ERR
#include 
#endif
#include 
#include 
#include 
#include 

#include "mandoc.h"
#include "mandoc_aux.h"


int
mandoc_asprintf(char **dest, const char *fmt, ...)
{
	va_list	 ap;
	int	 ret;

	va_start(ap, fmt);
	ret = vasprintf(dest, fmt, ap);
	va_end(ap);

	if (ret == -1)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return ret;
}

void *
mandoc_calloc(size_t num, size_t size)
{
	void	*ptr;

	ptr = calloc(num, size);
	if (ptr == NULL)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return ptr;
}

void *
mandoc_malloc(size_t size)
{
	void	*ptr;

	ptr = malloc(size);
	if (ptr == NULL)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return ptr;
}

void *
mandoc_realloc(void *ptr, size_t size)
{
	ptr = realloc(ptr, size);
	if (ptr == NULL)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return ptr;
}

void *
mandoc_reallocarray(void *ptr, size_t num, size_t size)
{
	ptr = reallocarray(ptr, num, size);
	if (ptr == NULL)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return ptr;
}

void *
mandoc_recallocarray(void *ptr, size_t oldnum, size_t num, size_t size)
{
	ptr = recallocarray(ptr, oldnum, num, size);
	if (ptr == NULL)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return ptr;
}

char *
mandoc_strdup(const char *ptr)
{
	char	*p;

	p = strdup(ptr);
	if (p == NULL)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return p;
}

char *
mandoc_strndup(const char *ptr, size_t sz)
{
	char	*p;

	p = strndup(ptr, sz);
	if (p == NULL)
		err((int)MANDOCLEVEL_SYSERR, NULL);
	return p;
}
/*	$Id: mandoc_aux.h,v 1.7 2017/06/12 19:05:47 schwarze Exp $ */
/*
 * Copyright (c) 2009, 2011 Kristaps Dzonsons 
 * Copyright (c) 2014, 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.
 */

int		  mandoc_asprintf(char **, const char *, ...)
			__attribute__((__format__ (__printf__, 2, 3)));
void		 *mandoc_calloc(size_t, size_t);
void		 *mandoc_malloc(size_t);
void		 *mandoc_realloc(void *, size_t);
void		 *mandoc_reallocarray(void *, size_t, size_t);
void		 *mandoc_recallocarray(void *, size_t, size_t, size_t);
char		 *mandoc_strdup(const char *);
char		 *mandoc_strndup(const char *, size_t);
/* $OpenBSD: mandoc_msg.c,v 1.8 2020/01/19 17:59:01 schwarze Exp $ */
/*
 * Copyright (c) 2014-2021 Ingo Schwarze 
 * Copyright (c) 2010, 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.
 *
 * Implementation of warning and error messages for mandoc(1).
 */
#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_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",

	"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",
	"referenced manual not found",
	"trailing delimiter",
	"no blank before trailing delimiter",
	"fill mode already enabled, skipping",
	"fill mode already disabled, skipping",
	"input text line longer than 80 bytes",
	"verbatim \"--\", maybe consider using \\(em",
	"function name without markup",
	"whitespace at end of input line",
	"bad comment style",

	"generic warning",

	/* related to the prologue */
	"missing manual title, using UNTITLED",
	"missing manual title, using \"\"",
	"missing manual section, using \"\"",
	"unknown manual section",
	"filename/section mismatch",
	"missing date, using \"\"",
	"cannot parse date, using it verbatim",
	"date in the future, using it anyway",
	"missing Os macro, using \"\"",
	"late prologue macro",
	"prologue macros out of order",

	/* related to document structure */
	".so is fragile, better use ln(1)",
	"no document body",
	"content before first section header",
	"first section is not \"NAME\"",
	"NAME section without Nm before Nd",
	"NAME section without description",
	"description not at the end of NAME",
	"bad NAME section content",
	"missing comma before name",
	"missing description line, using \"\"",
	"description line outside NAME section",
	"sections out of conventional order",
	"duplicate section title",
	"unexpected section",
	"cross reference to self",
	"unusual Xr order",
	"unusual Xr punctuation",
	"AUTHORS section without An macro",

	/* related to macros and nesting */
	"obsolete macro",
	"macro neither callable nor escaped",
	"skipping paragraph macro",
	"moving paragraph macro out of list",
	"skipping no-space macro",
	"blocks badly nested",
	"nested displays are not portable",
	"moving content out of list",
	"first macro on line",
	"line scope broken",
	"skipping blank line in line scope",

	/* related to missing macro arguments */
	"skipping empty request",
	"conditional request controls empty scope",
	"skipping empty macro",
	"empty block",
	"empty argument, using 0n",
	"missing display type, using -ragged",
	"list type is not the first argument",
	"missing -width in -tag list, using 6n",
	"missing utility name, using \"\"",
	"missing function name, using \"\"",
	"empty head in list item",
	"empty list item",
	"missing argument, using next line",
	"missing font type, using \\fR",
	"unknown font type, using \\fR",
	"nothing follows prefix",
	"empty reference block",
	"missing section argument",
	"missing -std argument, adding it",
	"missing option string, using \"\"",
	"missing resource identifier, using \"\"",
	"missing eqn box, using \"\"",

	/* related to bad macro arguments */
	"duplicate argument",
	"skipping duplicate argument",
	"skipping duplicate display type",
	"skipping duplicate list type",
	"skipping -width argument",
	"wrong number of cells",
	"unknown AT&T UNIX version",
	"comma in function argument",
	"parenthesis in function name",
	"unknown library name",
	"invalid content in Rs block",
	"invalid Boolean argument",
	"argument contains two font escapes",
	"unknown font, skipping request",
	"odd number of characters in request",

	/* related to plain text */
	"blank line in fill mode, using .sp",
	"tab in filled text",
	"new sentence, new line",
	"invalid escape sequence",
	"undefined escape, printing literally",
	"undefined string, using \"\"",

	/* related to tables */
	"tbl line starts with span",
	"tbl column starts with span",
	"skipping vertical bar in tbl layout",

	"generic error",

	/* related to tables */
	"non-alphabetic character in tbl options",
	"skipping unknown tbl option",
	"missing tbl option argument",
	"wrong tbl option argument size",
	"empty tbl layout",
	"invalid character in tbl layout",
	"unmatched parenthesis in tbl layout",
	"ignoring excessive spacing in tbl layout",
	"tbl without any data cells",
	"ignoring data in spanned tbl cell",
	"ignoring extra tbl data cells",
	"data block open at end of tbl",

	/* related to document structure and macros */
	"duplicate prologue macro",
	"skipping late title macro",
	"input stack limit exceeded, infinite loop?",
	"skipping bad character",
	"skipping unknown macro",
	"ignoring request outside macro",
	"skipping insecure request",
	"skipping item outside list",
	"skipping column outside column list",
	"skipping end of block that is not open",
	"fewer RS blocks open, skipping",
	"inserting missing end of block",
	"appending missing end of block",

	/* related to request and macro arguments */
	"escaped character not allowed in a name",
	"using macro argument outside macro",
	"argument number is not numeric",
	"NOT IMPLEMENTED: Bd -file",
	"skipping display without arguments",
	"missing list type, using -item",
	"argument is not numeric, using 1",
	"argument is not a character",
	"missing manual name, using \"\"",
	"uname(2) system call failed, using UNKNOWN",
	"unknown standard specifier",
	"skipping request without numeric argument",
	"excessive shift",
	"NOT IMPLEMENTED: .so with absolute path or \"..\"",
	".so request failed",
	"skipping tag containing whitespace",
	"skipping all arguments",
	"skipping excess arguments",
	"divide by zero",

	"unsupported feature",
	"input too large",
	"unsupported control character",
	"unsupported escape sequence",
	"unsupported roff request",
	"nested .while loops",
	"end of scope with open .while loop",
	"end of .while loop in inner scope",
	"cannot continue this .while loop",
	"eqn delim option in tbl",
	"unsupported tbl layout modifier",
	"ignoring macro in table",
	"skipping tbl in -Tman mode",
	"skipping eqn in -Tman mode",

	/* 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",
	"-Tmarkdown unsupported for man(7) input",

	/* system errors */
	NULL,
	"dup",
	"exec",
	"fdopen",
	"fflush",
	"fork",
	"fstat",
	"getline",
	"glob",
	"gzclose",
	"gzdopen",
	"mkstemp",
	"open",
	"pledge",
	"read",
	"wait",
	"write",
};

static	FILE		*fileptr = NULL;
static	const char	*filename = NULL;
static	enum mandocerr	 min_type = MANDOCERR_BADARG;
static	enum mandoclevel rc = MANDOCLEVEL_OK;


void
mandoc_msg_setoutfile(FILE *fp)
{
	fileptr = fp;
}

const char *
mandoc_msg_getinfilename(void)
{
	return filename;
}

void
mandoc_msg_setinfilename(const char *fn)
{
	filename = fn;
}

enum mandocerr
mandoc_msg_getmin(void)
{
	return min_type;
}

void
mandoc_msg_setmin(enum mandocerr t)
{
	min_type = t;
}

enum mandoclevel
mandoc_msg_getrc(void)
{
	return rc;
}

void
mandoc_msg_setrc(enum mandoclevel level)
{
	if (rc < level)
		rc = level;
}

void
mandoc_msg(enum mandocerr t, int line, int col, const char *fmt, ...)
{
	va_list			 ap;
	enum mandoclevel	 level;

	if (t < min_type)
		return;

	level = MANDOCLEVEL_SYSERR;
	while (t < lowest_type[level])
		level--;
	mandoc_msg_setrc(level);

	if (fileptr == NULL)
		return;

	fprintf(fileptr, "%s:", getprogname());
	if (filename != NULL)
		fprintf(fileptr, " %s:", filename);

	if (line > 0)
		fprintf(fileptr, "%d:%d:", line, col + 1);

	fprintf(fileptr, " %s", level_name[level]);
	if (type_message[t] != NULL)
		fprintf(fileptr, ": %s", type_message[t]);

	if (fmt != NULL) {
		fprintf(fileptr, ": ");
		va_start(ap, fmt);
		vfprintf(fileptr, fmt, ap);
		va_end(ap);
	}
	fputc('\n', fileptr);
}

void
mandoc_msg_summary(void)
{
	if (fileptr != NULL && rc != MANDOCLEVEL_OK)
		fprintf(fileptr,
		    "%s: see above the output for %s messages\n",
		    getprogname(), level_name[rc]);
}
/* $Id: mandoc_ohash.c,v 1.3 2020/06/22 19:20:40 schwarze Exp $	*/
/*
 * Copyright (c) 2014, 2015 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 "mandoc_aux.h"
#include "mandoc_ohash.h"

static	void	 *hash_alloc(size_t, void *);
static	void	 *hash_calloc(size_t, size_t, void *);
static	void	  hash_free(void *, void *);


void
mandoc_ohash_init(struct ohash *h, unsigned int sz, ptrdiff_t ko)
{
	struct ohash_info info;

	info.alloc = hash_alloc;
	info.calloc = hash_calloc;
	info.free = hash_free;
	info.data = NULL;
	info.key_offset = ko;

	ohash_init(h, sz, &info);
}

static void *
hash_alloc(size_t sz, void *arg)
{

	return mandoc_malloc(sz);
}

static void *
hash_calloc(size_t n, size_t sz, void *arg)
{

	return mandoc_calloc(n, sz);
}

static void
hash_free(void *p, void *arg)
{

	free(p);
}
/*	$Id: mandoc_ohash.h,v 1.2 2015/11/07 14:01:16 schwarze Exp $	*/
/*
 * Copyright (c) 2015 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.
 */
#if HAVE_OHASH
#include 
#else
#include "compat_ohash.h"
#endif

void		  mandoc_ohash_init(struct ohash *, unsigned int, ptrdiff_t);
/*	$Id: mandoc_parse.h,v 1.5 2019/11/09 14:39:49 schwarze Exp $ */
/*
 * Copyright (c) 2010, 2011 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.
 *
 * Top level parser interface.  For use in the main program
 * and in the main parser, but not in formatters.
 */

/*
 * Parse options.
 */
#define	MPARSE_MDOC	(1 << 0)  /* assume -mdoc */
#define	MPARSE_MAN	(1 << 1)  /* assume -man */
#define	MPARSE_SO	(1 << 2)  /* honour .so requests */
#define	MPARSE_QUICK	(1 << 3)  /* abort the parse early */
#define	MPARSE_UTF8	(1 << 4)  /* accept UTF-8 input */
#define	MPARSE_LATIN1	(1 << 5)  /* accept ISO-LATIN-1 input */
#define	MPARSE_VALIDATE	(1 << 6)  /* call validation functions */
#define	MPARSE_COMMENT	(1 << 7)  /* save comments in the tree */


struct	roff_meta;
struct	mparse;

struct mparse	 *mparse_alloc(int, enum mandoc_os, const char *);
void		  mparse_copy(const struct mparse *);
void		  mparse_free(struct mparse *);
int		  mparse_open(struct mparse *, const char *);
void		  mparse_readfd(struct mparse *, int, const char *);
void		  mparse_reset(struct mparse *);
struct roff_meta *mparse_result(struct mparse *);
/* $Id: mandoc_xr.c,v 1.4 2020/06/22 19:20:40 schwarze Exp $ */
/*
 * 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_aux.h"
#include "mandoc_ohash.h"
#include "mandoc_xr.h"

static struct ohash	 *xr_hash = NULL;
static struct mandoc_xr	 *xr_first = NULL;
static struct mandoc_xr	 *xr_last = NULL;

static void		  mandoc_xr_clear(void);


static void
mandoc_xr_clear(void)
{
	struct mandoc_xr	*xr;
	unsigned int		 slot;

	if (xr_hash == NULL)
		return;
	for (xr = ohash_first(xr_hash, &slot); xr != NULL;
	     xr = ohash_next(xr_hash, &slot))
		free(xr);
	ohash_delete(xr_hash);
}

void
mandoc_xr_reset(void)
{
	if (xr_hash == NULL)
		xr_hash = mandoc_malloc(sizeof(*xr_hash));
	else
		mandoc_xr_clear();
	mandoc_ohash_init(xr_hash, 5,
	    offsetof(struct mandoc_xr, hashkey));
	xr_first = xr_last = NULL;
}

int
mandoc_xr_add(const char *sec, const char *name, int line, int pos)
{
	struct mandoc_xr	 *xr, *oxr;
	const char		 *pend;
	size_t			  ssz, nsz, tsz;
	unsigned int		  slot;
	int			  ret;
	uint32_t		  hv;

	if (xr_hash == NULL)
		return 0;

	ssz = strlen(sec) + 1;
	nsz = strlen(name) + 1;
	tsz = ssz + nsz;
	xr = mandoc_malloc(sizeof(*xr) + tsz);
	xr->next = NULL;
	xr->sec = xr->hashkey;
	xr->name = xr->hashkey + ssz;
	xr->line = line;
	xr->pos = pos;
	xr->count = 1;
	memcpy(xr->sec, sec, ssz);
	memcpy(xr->name, name, nsz);

	pend = xr->hashkey + tsz;
	hv = ohash_interval(xr->hashkey, &pend);
	slot = ohash_lookup_memory(xr_hash, xr->hashkey, tsz, hv);
	if ((oxr = ohash_find(xr_hash, slot)) == NULL) {
		ohash_insert(xr_hash, slot, xr);
		if (xr_first == NULL)
			xr_first = xr;
		else
			xr_last->next = xr;
		xr_last = xr;
		return 0;
	}

	oxr->count++;
	ret = (oxr->line == -1) ^ (xr->line == -1);
	if (xr->line == -1)
		oxr->line = -1;
	free(xr);
	return ret;
}

struct mandoc_xr *
mandoc_xr_get(void)
{
	return xr_first;
}

void
mandoc_xr_free(void)
{
	mandoc_xr_clear();
	free(xr_hash);
	xr_hash = NULL;
}
/*	$Id: mandoc_xr.h,v 1.3 2017/07/02 21:18:29 schwarze Exp $ */
/*
 * 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.
 */

struct	mandoc_xr {
	struct mandoc_xr *next;
	char		 *sec;
	char		 *name;
	int		  line;  /* Or -1 for this page's own names. */
	int		  pos;
	int		  count;
	char		  hashkey[];
};

void		  mandoc_xr_reset(void);
int		  mandoc_xr_add(const char *, const char *, int, int);
struct mandoc_xr *mandoc_xr_get(void);
void		  mandoc_xr_free(void);
/* $Id: mandocdb.c,v 1.269 2021/08/19 16:55:31 schwarze Exp $ */
/*
 * Copyright (c) 2011-2020 Ingo Schwarze 
 * Copyright (c) 2011, 2012 Kristaps Dzonsons 
 * 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.
 *
 * Implementation of the makewhatis(8) program.
 */
#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 *);
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);
#ifdef READ_ALLOWED_PATH
static	int	 read_allowed(const char *);
#endif
static	int	 render_string(char **, size_t *);
static	void	 say(const char *, const char *, ...)
			__attribute__((__format__ (__printf__, 2, 3)));
static	int	 set_basedir(const char *, int);
static	int	 treescan(void);
static	size_t	 utf8(unsigned int, char [7]);

static	int		 nodb; /* no database changes */
static	int		 mparse_options; /* abort the parse early */
static	int		 use_all; /* use all found files */
static	int		 debug; /* print what we're doing */
static	int		 warnings; /* warn about crap */
static	int		 write_utf8; /* write UTF-8 output; else ASCII */
static	int		 exitcode; /* to be returned by main */
static	enum op		 op; /* operational mode */
static	char		 basedir[PATH_MAX]; /* current base directory */
static	size_t		 basedir_len; /* strlen(basedir) */
static	struct mpage	*mpage_head; /* list of distinct manual pages */
static	struct ohash	 mpages; /* table of distinct manual pages */
static	struct ohash	 mlinks; /* table of directory entries */
static	struct ohash	 names; /* table of all names */
static	struct ohash	 strings; /* table of all strings */
static	uint64_t	 name_mask;

static	const struct mdoc_handler mdoc_handlers[MDOC_MAX - MDOC_Dd] = {
	{ NULL, 0, NODE_NOPRT },  /* Dd */
	{ NULL, 0, NODE_NOPRT },  /* Dt */
	{ NULL, 0, NODE_NOPRT },  /* Os */
	{ parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */
	{ parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */
	{ NULL, 0, 0 },  /* Pp */
	{ NULL, 0, 0 },  /* D1 */
	{ NULL, 0, 0 },  /* Dl */
	{ NULL, 0, 0 },  /* Bd */
	{ NULL, 0, 0 },  /* Ed */
	{ NULL, 0, 0 },  /* Bl */
	{ NULL, 0, 0 },  /* El */
	{ NULL, 0, 0 },  /* It */
	{ NULL, 0, 0 },  /* Ad */
	{ NULL, TYPE_An, 0 },  /* An */
	{ NULL, 0, 0 },  /* Ap */
	{ NULL, TYPE_Ar, 0 },  /* Ar */
	{ NULL, TYPE_Cd, 0 },  /* Cd */
	{ NULL, TYPE_Cm, 0 },  /* Cm */
	{ NULL, TYPE_Dv, 0 },  /* Dv */
	{ NULL, TYPE_Er, 0 },  /* Er */
	{ NULL, TYPE_Ev, 0 },  /* Ev */
	{ NULL, 0, 0 },  /* Ex */
	{ parse_mdoc_Fa, 0, 0 },  /* Fa */
	{ parse_mdoc_Fd, 0, 0 },  /* Fd */
	{ NULL, TYPE_Fl, 0 },  /* Fl */
	{ parse_mdoc_Fn, 0, 0 },  /* Fn */
	{ NULL, TYPE_Ft | TYPE_Vt, 0 },  /* Ft */
	{ NULL, TYPE_Ic, 0 },  /* Ic */
	{ NULL, TYPE_In, 0 },  /* In */
	{ NULL, TYPE_Li, 0 },  /* Li */
	{ parse_mdoc_Nd, 0, 0 },  /* Nd */
	{ parse_mdoc_Nm, 0, 0 },  /* Nm */
	{ NULL, 0, 0 },  /* Op */
	{ NULL, 0, 0 },  /* Ot */
	{ NULL, TYPE_Pa, NODE_NOSRC },  /* Pa */
	{ NULL, 0, 0 },  /* Rv */
	{ NULL, TYPE_St, 0 },  /* St */
	{ parse_mdoc_Va, TYPE_Va, 0 },  /* Va */
	{ parse_mdoc_Va, TYPE_Vt, 0 },  /* Vt */
	{ parse_mdoc_Xr, 0, 0 },  /* Xr */
	{ NULL, 0, 0 },  /* %A */
	{ NULL, 0, 0 },  /* %B */
	{ NULL, 0, 0 },  /* %D */
	{ NULL, 0, 0 },  /* %I */
	{ NULL, 0, 0 },  /* %J */
	{ NULL, 0, 0 },  /* %N */
	{ NULL, 0, 0 },  /* %O */
	{ NULL, 0, 0 },  /* %P */
	{ NULL, 0, 0 },  /* %R */
	{ NULL, 0, 0 },  /* %T */
	{ NULL, 0, 0 },  /* %V */
	{ NULL, 0, 0 },  /* Ac */
	{ NULL, 0, 0 },  /* Ao */
	{ NULL, 0, 0 },  /* Aq */
	{ NULL, TYPE_At, 0 },  /* At */
	{ NULL, 0, 0 },  /* Bc */
	{ NULL, 0, 0 },  /* Bf */
	{ NULL, 0, 0 },  /* Bo */
	{ NULL, 0, 0 },  /* Bq */
	{ NULL, TYPE_Bsx, NODE_NOSRC },  /* Bsx */
	{ NULL, TYPE_Bx, NODE_NOSRC },  /* Bx */
	{ NULL, 0, 0 },  /* Db */
	{ NULL, 0, 0 },  /* Dc */
	{ NULL, 0, 0 },  /* Do */
	{ NULL, 0, 0 },  /* Dq */
	{ NULL, 0, 0 },  /* Ec */
	{ NULL, 0, 0 },  /* Ef */
	{ NULL, TYPE_Em, 0 },  /* Em */
	{ NULL, 0, 0 },  /* Eo */
	{ NULL, TYPE_Fx, NODE_NOSRC },  /* Fx */
	{ NULL, TYPE_Ms, 0 },  /* Ms */
	{ NULL, 0, 0 },  /* No */
	{ NULL, 0, 0 },  /* Ns */
	{ NULL, TYPE_Nx, NODE_NOSRC },  /* Nx */
	{ NULL, TYPE_Ox, NODE_NOSRC },  /* Ox */
	{ NULL, 0, 0 },  /* Pc */
	{ NULL, 0, 0 },  /* Pf */
	{ NULL, 0, 0 },  /* Po */
	{ NULL, 0, 0 },  /* Pq */
	{ NULL, 0, 0 },  /* Qc */
	{ NULL, 0, 0 },  /* Ql */
	{ NULL, 0, 0 },  /* Qo */
	{ NULL, 0, 0 },  /* Qq */
	{ NULL, 0, 0 },  /* Re */
	{ NULL, 0, 0 },  /* Rs */
	{ NULL, 0, 0 },  /* Sc */
	{ NULL, 0, 0 },  /* So */
	{ NULL, 0, 0 },  /* Sq */
	{ NULL, 0, 0 },  /* Sm */
	{ NULL, 0, 0 },  /* Sx */
	{ NULL, TYPE_Sy, 0 },  /* Sy */
	{ NULL, TYPE_Tn, 0 },  /* Tn */
	{ NULL, 0, NODE_NOSRC },  /* Ux */
	{ NULL, 0, 0 },  /* Xc */
	{ NULL, 0, 0 },  /* Xo */
	{ parse_mdoc_Fo, 0, 0 },  /* Fo */
	{ NULL, 0, 0 },  /* Fc */
	{ NULL, 0, 0 },  /* Oo */
	{ NULL, 0, 0 },  /* Oc */
	{ NULL, 0, 0 },  /* Bk */
	{ NULL, 0, 0 },  /* Ek */
	{ NULL, 0, 0 },  /* Bt */
	{ NULL, 0, 0 },  /* Hf */
	{ NULL, 0, 0 },  /* Fr */
	{ NULL, 0, 0 },  /* Ud */
	{ NULL, TYPE_Lb, NODE_NOSRC },  /* Lb */
	{ NULL, 0, 0 },  /* Lp */
	{ NULL, TYPE_Lk, 0 },  /* Lk */
	{ NULL, TYPE_Mt, NODE_NOSRC },  /* Mt */
	{ NULL, 0, 0 },  /* Brq */
	{ NULL, 0, 0 },  /* Bro */
	{ NULL, 0, 0 },  /* Brc */
	{ NULL, 0, 0 },  /* %C */
	{ NULL, 0, 0 },  /* Es */
	{ NULL, 0, 0 },  /* En */
	{ NULL, TYPE_Dx, NODE_NOSRC },  /* Dx */
	{ NULL, 0, 0 },  /* %Q */
	{ NULL, 0, 0 },  /* %U */
	{ NULL, 0, 0 },  /* Ta */
};


int
mandocdb(int argc, char *argv[])
{
	struct manconf	  conf;
	struct mparse	 *mp;
	struct dba	 *dba;
	const char	 *path_arg, *progname;
	size_t		  j, sz;
	int		  ch, i;

#if 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) != OP_DEFAULT) { \
		warnx("-%c: Conflicting option", (_ch)); \
		goto usage; \
	} while (/*CONSTCOND*/0)

	mparse_options = MPARSE_VALIDATE;
	path_arg = NULL;
	op = OP_DEFAULT;

	while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1)
		switch (ch) {
		case 'a':
			use_all = 1;
			break;
		case 'C':
			CHECKOP(op, ch);
			path_arg = optarg;
			op = OP_CONFFILE;
			break;
		case 'D':
			debug++;
			break;
		case 'd':
			CHECKOP(op, ch);
			path_arg = optarg;
			op = OP_UPDATE;
			break;
		case 'n':
			nodb = 1;
			break;
		case 'p':
			warnings = 1;
			break;
		case 'Q':
			mparse_options |= MPARSE_QUICK;
			break;
		case 'T':
			if (strcmp(optarg, "utf8") != 0) {
				warnx("-T%s: Unsupported output format",
				    optarg);
				goto usage;
			}
			write_utf8 = 1;
			break;
		case 't':
			CHECKOP(op, ch);
			dup2(STDOUT_FILENO, STDERR_FILENO);
			op = OP_TEST;
			nodb = warnings = 1;
			break;
		case 'u':
			CHECKOP(op, ch);
			path_arg = optarg;
			op = OP_DELETE;
			break;
		case 'v':
			/* Compatibility with espie@'s makewhatis. */
			break;
		default:
			goto usage;
		}

	argc -= optind;
	argv += optind;

#if HAVE_PLEDGE
	if (nodb) {
		if (pledge("stdio rpath", NULL) == -1) {
			warn("pledge");
			return (int)MANDOCLEVEL_SYSERR;
		}
	}
#endif

	if (op == OP_CONFFILE && argc > 0) {
		warnx("-C: Too many arguments");
		goto usage;
	}

	exitcode = (int)MANDOCLEVEL_OK;
	mchars_alloc();
	mp = mparse_alloc(mparse_options, MANDOC_OS_OTHER, NULL);
	mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
	mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));

	if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) {

		/*
		 * Most of these deal with a specific directory.
		 * Jump into that directory first.
		 */
		if (op != OP_TEST && set_basedir(path_arg, 1) == 0)
			goto out;

		dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
		if (dba != NULL) {
			/*
			 * The existing database is usable.  Process
			 * all files specified on the command-line.
			 */
			use_all = 1;
			for (i = 0; i < argc; i++)
				filescan(argv[i]);
			if (nodb == 0)
				dbprune(dba);
		} else {
			/* Database missing or corrupt. */
			if (op != OP_UPDATE || errno != ENOENT)
				say(MANDOC_DB, "%s: Automatically recreating"
				    " from scratch", strerror(errno));
			exitcode = (int)MANDOCLEVEL_OK;
			op = OP_DEFAULT;
			if (treescan() == 0)
				goto out;
			dba = dba_new(128);
		}
		if (op != OP_DELETE)
			mpages_merge(dba, mp);
		if (nodb == 0)
			dbwrite(dba);
		dba_free(dba);
	} else {
		/*
		 * If we have arguments, use them as our manpaths.
		 * If we don't, use man.conf(5).
		 */
		if (argc > 0) {
			conf.manpath.paths = mandoc_reallocarray(NULL,
			    argc, sizeof(char *));
			conf.manpath.sz = (size_t)argc;
			for (i = 0; i < argc; i++)
				conf.manpath.paths[i] = mandoc_strdup(argv[i]);
		} else
			manconf_parse(&conf, path_arg, NULL, NULL);

		if (conf.manpath.sz == 0) {
			exitcode = (int)MANDOCLEVEL_BADARG;
			say("", "Empty manpath");
		}

		/*
		 * First scan the tree rooted at a base directory, then
		 * build a new database and finally move it into place.
		 * Ignore zero-length directories and strip trailing
		 * slashes.
		 */
		for (j = 0; j < conf.manpath.sz; j++) {
			sz = strlen(conf.manpath.paths[j]);
			if (sz && conf.manpath.paths[j][sz - 1] == '/')
				conf.manpath.paths[j][--sz] = '\0';
			if (sz == 0)
				continue;

			if (j) {
				mandoc_ohash_init(&mpages, 6,
				    offsetof(struct mpage, inodev));
				mandoc_ohash_init(&mlinks, 6,
				    offsetof(struct mlink, file));
			}

			if (set_basedir(conf.manpath.paths[j], argc > 0) == 0)
				continue;
			if (treescan() == 0)
				continue;
			dba = dba_new(128);
			mpages_merge(dba, mp);
			if (nodb == 0)
				dbwrite(dba);
			dba_free(dba);

			if (j + 1 < conf.manpath.sz) {
				mpages_free();
				ohash_delete(&mpages);
				ohash_delete(&mlinks);
			}
		}
	}
out:
	manconf_free(&conf);
	mparse_free(mp);
	mchars_free();
	mpages_free();
	ohash_delete(&mpages);
	ohash_delete(&mlinks);
	return exitcode;
usage:
	progname = getprogname();
	fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n"
			"       %s [-aDnpQ] [-Tutf8] dir ...\n"
			"       %s [-DnpQ] [-Tutf8] -d dir [file ...]\n"
			"       %s [-Dnp] -u dir [file ...]\n"
			"       %s [-Q] -t file ...\n",
		        progname, progname, progname, progname, progname);

	return (int)MANDOCLEVEL_BADARG;
}

/*
 * To get a singly linked list in alpha order while inserting entries
 * at the beginning, process directory entries in reverse alpha order.
 */
static int
#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 (strncmp(buf, basedir, basedir_len) != 0 #ifdef READ_ALLOWED_PATH && !read_allowed(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; } if ((ff->fts_statp->st_mode & S_IFMT) != S_IFREG) 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 *infile) { struct stat st; struct mlink *mlink; char *linkfile, *p, *realdir, *start, *usefile; size_t realdir_len; assert(use_all); if (strncmp(infile, "./", 2) == 0) infile += 2; /* * We have to do lstat(2) before realpath(3) loses * the information whether this is a symbolic link. * We need to know that because for symbolic links, * we want to use the orginal file name, while for * regular files, we want to use the real path. */ if (lstat(infile, &st) == -1) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&lstat"); return; } else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "Not a regular file"); return; } /* * We have to resolve the file name to the real path * in any case for the base directory check. */ if ((usefile = realpath(infile, NULL)) == NULL) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&realpath"); return; } if (op == OP_TEST) start = usefile; else if (strncmp(usefile, basedir, basedir_len) == 0) start = usefile + basedir_len; #ifdef READ_ALLOWED_PATH else if (read_allowed(usefile)) start = usefile; #endif else { exitcode = (int)MANDOCLEVEL_BADARG; say("", "%s: outside base directory", infile); free(usefile); return; } /* * Now we are sure the file is inside our tree. * If it is a symbolic link, ignore the real path * and use the original name. */ do { if (S_ISLNK(st.st_mode) == 0) break; /* * Some implementations of realpath(3) may succeed * even if the target of the link does not exist, * so check again for extra safety. */ if (stat(usefile, &st) == -1) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&stat"); free(usefile); return; } linkfile = mandoc_strdup(infile); if (op == OP_TEST) { free(usefile); start = usefile = linkfile; break; } if (strncmp(infile, basedir, basedir_len) == 0) { free(usefile); usefile = linkfile; start = usefile + basedir_len; break; } /* * This symbolic link points into the basedir * from the outside. Let's see whether any of * the parent directories resolve to the basedir. */ p = strchr(linkfile, '\0'); do { while (*--p != '/') continue; *p = '\0'; if ((realdir = realpath(linkfile, NULL)) == NULL) { exitcode = (int)MANDOCLEVEL_BADARG; say(infile, "&realpath"); free(linkfile); free(usefile); return; } realdir_len = strlen(realdir) + 1; free(realdir); *p = '/'; } while (realdir_len > basedir_len); /* * If one of the directories resolves to the basedir, * use the rest of the original name. * Otherwise, the best we can do * is to use the filename pointed to. */ if (realdir_len == basedir_len) { free(usefile); usefile = linkfile; start = p + 1; } else { free(linkfile); start = usefile + basedir_len; } } while (/* CONSTCOND */ 0); mlink = mandoc_calloc(1, sizeof(struct mlink)); mlink->dform = FORM_NONE; if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= sizeof(mlink->file)) { say(start, "Filename too long"); free(mlink); free(usefile); return; } /* * In test mode or when the original name is absolute * but outside our tree, guess the base directory. */ if (op == OP_TEST || (start == usefile && *start == '/')) { if (strncmp(usefile, "man/", 4) == 0) start = usefile + 4; else if ((start = strstr(usefile, "/man/")) != NULL) start += 5; else start = usefile; } /* * First try to guess our directory structure. * If we find a separator, try to look for man* or cat*. * If we find one of these and what's underneath is a directory, * assume it's an architecture. */ if ((p = strchr(start, '/')) != NULL) { *p++ = '\0'; if (strncmp(start, "man", 3) == 0) { mlink->dform = FORM_SRC; mlink->dsec = start + 3; } else if (strncmp(start, "cat", 3) == 0) { mlink->dform = FORM_CAT; mlink->dsec = start + 3; } start = p; if (mlink->dsec != NULL && (p = strchr(start, '/')) != NULL) { *p++ = '\0'; mlink->arch = start; start = p; } } /* * Now check the file suffix. * Suffix of `.0' indicates a catpage, `.1-9' is a manpage. */ p = strrchr(start, '\0'); while (p-- > start && *p != '/' && *p != '.') continue; if (*p == '.') { *p++ = '\0'; mlink->fsec = p; } /* * Now try to parse the name. * Use the filename portion of the path. */ mlink->name = start; if ((p = strrchr(start, '/')) != NULL) { mlink->name = p + 1; *p = '\0'; } mlink_add(mlink, &st); free(usefile); } static void mlink_add(struct mlink *mlink, const struct stat *st) { struct inodev inodev; struct mpage *mpage; unsigned int slot; assert(NULL != mlink->file); mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : ""); mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : ""); mlink->name = mandoc_strdup(mlink->name ? mlink->name : ""); mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : ""); if ('0' == *mlink->fsec) { free(mlink->fsec); mlink->fsec = mandoc_strdup(mlink->dsec); mlink->fform = FORM_CAT; } else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec) mlink->fform = FORM_SRC; else mlink->fform = FORM_NONE; slot = ohash_qlookup(&mlinks, mlink->file); assert(NULL == ohash_find(&mlinks, slot)); ohash_insert(&mlinks, slot, mlink); memset(&inodev, 0, sizeof(inodev)); /* Clear padding. */ inodev.st_ino = st->st_ino; inodev.st_dev = st->st_dev; slot = ohash_lookup_memory(&mpages, (char *)&inodev, sizeof(struct inodev), inodev.st_ino); mpage = ohash_find(&mpages, slot); if (NULL == mpage) { mpage = mandoc_calloc(1, sizeof(struct mpage)); mpage->inodev.st_ino = inodev.st_ino; mpage->inodev.st_dev = inodev.st_dev; mpage->form = FORM_NONE; mpage->next = mpage_head; mpage_head = mpage; ohash_insert(&mpages, slot, mpage); } else mlink->next = mpage->mlinks; mpage->mlinks = mlink; mlink->mpage = mpage; } static void mlink_free(struct mlink *mlink) { free(mlink->dsec); free(mlink->arch); free(mlink->name); free(mlink->fsec); free(mlink); } static void mpages_free(void) { struct mpage *mpage; struct mlink *mlink; while ((mpage = mpage_head) != NULL) { while ((mlink = mpage->mlinks) != NULL) { mpage->mlinks = mlink->next; mlink_free(mlink); } mpage_head = mpage->next; free(mpage->sec); free(mpage->arch); free(mpage->title); free(mpage->desc); free(mpage); } } /* * For each mlink to the mpage, check whether the path looks like * it is formatted, and if it does, check whether a source manual * exists by the same name, ignoring the suffix. * If both conditions hold, drop the mlink. */ static void mlinks_undupe(struct mpage *mpage) { char buf[PATH_MAX]; struct mlink **prev; struct mlink *mlink; char *bufp; mpage->form = FORM_CAT; prev = &mpage->mlinks; while (NULL != (mlink = *prev)) { if (FORM_CAT != mlink->dform) { mpage->form = FORM_NONE; goto nextlink; } (void)strlcpy(buf, mlink->file, sizeof(buf)); bufp = strstr(buf, "cat"); assert(NULL != bufp); memcpy(bufp, "man", 3); if (NULL != (bufp = strrchr(buf, '.'))) *++bufp = '\0'; (void)strlcat(buf, mlink->dsec, sizeof(buf)); if (NULL == ohash_find(&mlinks, ohash_qlookup(&mlinks, buf))) goto nextlink; if (warnings) say(mlink->file, "Man source exists: %s", buf); if (use_all) goto nextlink; *prev = mlink->next; mlink_free(mlink); continue; nextlink: prev = &(*prev)->next; } } static void mlink_check(struct mpage *mpage, struct mlink *mlink) { struct str *str; unsigned int slot; /* * Check whether the manual section given in a file * agrees with the directory where the file is located. * Some manuals have suffixes like (3p) on their * section number either inside the file or in the * directory name, some are linked into more than one * section, like encrypt(1) = makekey(8). */ if (FORM_SRC == mpage->form && strcasecmp(mpage->sec, mlink->dsec)) say(mlink->file, "Section \"%s\" manual in %s directory", mpage->sec, mlink->dsec); /* * Manual page directories exist for each kernel * architecture as returned by machine(1). * However, many manuals only depend on the * application architecture as returned by arch(1). * For example, some (2/ARM) manuals are shared * across the "armish" and "zaurus" kernel * architectures. * A few manuals are even shared across completely * different architectures, for example fdformat(1) * on amd64, i386, and sparc64. */ if (strcasecmp(mpage->arch, mlink->arch)) say(mlink->file, "Architecture \"%s\" manual in " "\"%s\" directory", mpage->arch, mlink->arch); /* * XXX * parse_cat() doesn't set NAME_TITLE yet. */ if (FORM_CAT == mpage->form) return; /* * Check whether this mlink * appears as a name in the NAME section. */ slot = ohash_qlookup(&names, mlink->name); str = ohash_find(&names, slot); assert(NULL != str); if ( ! (NAME_TITLE & str->mask)) say(mlink->file, "Name missing in NAME section"); } /* * Run through the files in the global vector "mpages" * and add them to the database specified in "basedir". * * This handles the parsing scheme itself, using the cues of directory * and filename to determine whether the file is parsable or not. */ static void mpages_merge(struct dba *dba, struct mparse *mp) { struct mpage *mpage, *mpage_dest; struct mlink *mlink, *mlink_dest; struct roff_meta *meta; char *cp; int fd; for (mpage = mpage_head; mpage != NULL; mpage = mpage->next) { mlinks_undupe(mpage); if ((mlink = mpage->mlinks) == NULL) continue; name_mask = NAME_MASK; mandoc_ohash_init(&names, 4, offsetof(struct str, key)); mandoc_ohash_init(&strings, 6, offsetof(struct str, key)); mparse_reset(mp); meta = NULL; if ((fd = mparse_open(mp, mlink->file)) == -1) { say(mlink->file, "&open"); goto nextpage; } /* * Interpret the file as mdoc(7) or man(7) source * code, unless it is known to be formatted. */ if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) { mparse_readfd(mp, fd, mlink->file); close(fd); fd = -1; meta = mparse_result(mp); } if (meta != NULL && meta->sodest != NULL) { mlink_dest = ohash_find(&mlinks, ohash_qlookup(&mlinks, meta->sodest)); if (mlink_dest == NULL) { mandoc_asprintf(&cp, "%s.gz", meta->sodest); mlink_dest = ohash_find(&mlinks, ohash_qlookup(&mlinks, cp)); free(cp); } if (mlink_dest != NULL) { /* The .so target exists. */ mpage_dest = mlink_dest->mpage; while (1) { mlink->mpage = mpage_dest; /* * If the target was already * processed, add the links * to the database now. * Otherwise, this will * happen when we come * to the target. */ if (mpage_dest->dba != NULL) dbadd_mlink(mlink); if (mlink->next == NULL) break; mlink = mlink->next; } /* Move all links to the target. */ mlink->next = mlink_dest->next; mlink_dest->next = mpage->mlinks; mpage->mlinks = NULL; goto nextpage; } meta->macroset = MACROSET_NONE; } if (meta != NULL && meta->macroset == MACROSET_MDOC) { mpage->form = FORM_SRC; mpage->sec = meta->msec; mpage->sec = mandoc_strdup( mpage->sec == NULL ? "" : mpage->sec); mpage->arch = meta->arch; mpage->arch = mandoc_strdup( mpage->arch == NULL ? "" : mpage->arch); mpage->title = mandoc_strdup(meta->title); } else if (meta != NULL && meta->macroset == MACROSET_MAN) { if (*meta->msec != '\0' || *meta->title != '\0') { mpage->form = FORM_SRC; mpage->sec = mandoc_strdup(meta->msec); mpage->arch = mandoc_strdup(mlink->arch); mpage->title = mandoc_strdup(meta->title); } else meta = NULL; } assert(mpage->desc == NULL); if (meta == NULL || meta->sodest != NULL) { mpage->sec = mandoc_strdup(mlink->dsec); mpage->arch = mandoc_strdup(mlink->arch); mpage->title = mandoc_strdup(mlink->name); if (meta == NULL) { mpage->form = FORM_CAT; parse_cat(mpage, fd); } else mpage->form = FORM_SRC; } else if (meta->macroset == MACROSET_MDOC) parse_mdoc(mpage, meta, meta->first); else parse_man(mpage, meta, meta->first); if (mpage->desc == NULL) { mpage->desc = mandoc_strdup(mlink->name); if (warnings) say(mlink->file, "No one-line description, " "using filename \"%s\"", mlink->name); } for (mlink = mpage->mlinks; mlink != NULL; mlink = mlink->next) { putkey(mpage, mlink->name, NAME_FILE); if (warnings && !use_all) mlink_check(mpage, mlink); } dbadd(dba, mpage); nextpage: ohash_delete(&strings); ohash_delete(&names); } } static void parse_cat(struct mpage *mpage, int fd) { FILE *stream; struct mlink *mlink; char *line, *p, *title, *sec; size_t linesz, plen, titlesz; ssize_t len; int offs; mlink = mpage->mlinks; stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r"); if (stream == NULL) { if (fd != -1) close(fd); if (warnings) say(mlink->file, "&fopen"); return; } line = NULL; linesz = 0; /* Parse the section number from the header line. */ while (getline(&line, &linesz, stream) != -1) { if (*line == '\n') continue; if ((sec = strchr(line, '(')) == NULL) break; if ((p = strchr(++sec, ')')) == NULL) break; free(mpage->sec); mpage->sec = mandoc_strndup(sec, p - sec); if (warnings && *mlink->dsec != '\0' && strcasecmp(mpage->sec, mlink->dsec)) say(mlink->file, "Section \"%s\" manual in %s directory", mpage->sec, mlink->dsec); break; } /* Skip to first blank line. */ while (line == NULL || *line != '\n') if (getline(&line, &linesz, stream) == -1) break; /* * Assume the first line that is not indented * is the first section header. Skip to it. */ while (getline(&line, &linesz, stream) != -1) if (*line != '\n' && *line != ' ') break; /* * Read up until the next section into a buffer. * Strip the leading and trailing newline from each read line, * appending a trailing space. * Ignore empty (whitespace-only) lines. */ titlesz = 0; title = NULL; while ((len = getline(&line, &linesz, stream)) != -1) { if (*line != ' ') break; offs = 0; while (isspace((unsigned char)line[offs])) offs++; if (line[offs] == '\0') continue; title = mandoc_realloc(title, titlesz + len - offs); memcpy(title + titlesz, line + offs, len - offs); titlesz += len - offs; title[titlesz - 1] = ' '; } free(line); /* * If no page content can be found, or the input line * is already the next section header, or there is no * trailing newline, reuse the page title as the page * description. */ if (NULL == title || '\0' == *title) { if (warnings) say(mlink->file, "Cannot find NAME section"); fclose(stream); free(title); return; } title[titlesz - 1] = '\0'; /* * Skip to the first dash. * Use the remaining line as the description (no more than 70 * bytes). */ if (NULL != (p = strstr(title, "- "))) { for (p += 2; ' ' == *p || '\b' == *p; p++) /* Skip to next word. */ ; } else { if (warnings) say(mlink->file, "No dash in title line, " "reusing \"%s\" as one-line description", title); p = title; } plen = strlen(p); /* Strip backspace-encoding from line. */ while (NULL != (line = memchr(p, '\b', plen))) { len = line - p; if (0 == len) { memmove(line, line + 1, plen--); continue; } memmove(line - 1, line + 1, plen - len); plen -= 2; } /* * Cut off excessive one-line descriptions. * Bad pages are not worth better heuristics. */ mpage->desc = mandoc_strndup(p, 150); fclose(stream); free(title); } /* * Put a type/word pair into the word database for this particular file. */ static void putkey(const struct mpage *mpage, char *value, uint64_t type) { putkeys(mpage, value, strlen(value), type); } /* * Grok all nodes at or below a certain mdoc node into putkey(). */ static void putmdockey(const struct mpage *mpage, const struct roff_node *n, uint64_t m, int taboo) { for ( ; NULL != n; n = n->next) { if (n->flags & taboo) continue; if (NULL != n->child) putmdockey(mpage, n->child, m, taboo); if (n->type == ROFFT_TEXT) putkey(mpage, n->string, m); } } static void parse_man(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { const struct roff_node *head, *body; char *start, *title; char byte; size_t sz; if (n == NULL) return; /* * We're only searching for one thing: the first text child in * the BODY of a NAME section. Since we don't keep track of * sections in -man, run some hoops to find out whether we're in * the correct section or not. */ if (n->type == ROFFT_BODY && n->tok == MAN_SH) { body = n; if ((head = body->parent->head) != NULL && (head = head->child) != NULL && head->next == NULL && head->type == ROFFT_TEXT && strcmp(head->string, "NAME") == 0 && body->child != NULL) { /* * Suck the entire NAME section into memory. * Yes, we might run away. * But too many manuals have big, spread-out * NAME sections over many lines. */ title = NULL; deroff(&title, body); if (NULL == title) return; /* * Go through a special heuristic dance here. * Conventionally, one or more manual names are * comma-specified prior to a whitespace, then a * dash, then a description. Try to puzzle out * the name parts here. */ start = title; for ( ;; ) { sz = strcspn(start, " ,"); if ('\0' == start[sz]) break; byte = start[sz]; start[sz] = '\0'; /* * Assume a stray trailing comma in the * name list if a name begins with a dash. */ if ('-' == start[0] || ('\\' == start[0] && '-' == start[1])) break; putkey(mpage, start, NAME_TITLE); if ( ! (mpage->name_head_done || strcasecmp(start, meta->title))) { putkey(mpage, start, NAME_HEAD); mpage->name_head_done = 1; } if (' ' == byte) { start += sz + 1; break; } assert(',' == byte); start += sz + 1; while (' ' == *start) start++; } if (start == title) { putkey(mpage, start, NAME_TITLE); if ( ! (mpage->name_head_done || strcasecmp(start, meta->title))) { putkey(mpage, start, NAME_HEAD); mpage->name_head_done = 1; } free(title); return; } while (isspace((unsigned char)*start)) start++; if (0 == strncmp(start, "-", 1)) start += 1; else if (0 == strncmp(start, "\\-\\-", 4)) start += 4; else if (0 == strncmp(start, "\\-", 2)) start += 2; else if (0 == strncmp(start, "\\(en", 4)) start += 4; else if (0 == strncmp(start, "\\(em", 4)) start += 4; while (' ' == *start) start++; /* * Cut off excessive one-line descriptions. * Bad pages are not worth better heuristics. */ mpage->desc = mandoc_strndup(start, 150); free(title); return; } } for (n = n->child; n; n = n->next) { if (NULL != mpage->desc) break; parse_man(mpage, meta, n); } } static void parse_mdoc(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { const struct mdoc_handler *handler; for (n = n->child; n != NULL; n = n->next) { if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX) continue; assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); handler = mdoc_handlers + (n->tok - MDOC_Dd); if (n->flags & handler->taboo) continue; switch (n->type) { case ROFFT_ELEM: case ROFFT_BLOCK: case ROFFT_HEAD: case ROFFT_BODY: case ROFFT_TAIL: if (handler->fp != NULL && (*handler->fp)(mpage, meta, n) == 0) break; if (handler->mask) putmdockey(mpage, n->child, handler->mask, handler->taboo); break; default: continue; } if (NULL != n->child) parse_mdoc(mpage, meta, n); } } static int parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { uint64_t mask; mask = TYPE_Fa; if (n->sec == SEC_SYNOPSIS) mask |= TYPE_Vt; putmdockey(mpage, n->child, mask, 0); return 0; } static int parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { char *start, *end; size_t sz; if (SEC_SYNOPSIS != n->sec || NULL == (n = n->child) || n->type != ROFFT_TEXT) return 0; /* * Only consider those `Fd' macro fields that begin with an * "inclusion" token (versus, e.g., #define). */ if (strcmp("#include", n->string)) return 0; if ((n = n->next) == NULL || n->type != ROFFT_TEXT) return 0; /* * Strip away the enclosing angle brackets and make sure we're * not zero-length. */ start = n->string; if ('<' == *start || '"' == *start) start++; if (0 == (sz = strlen(start))) return 0; end = &start[(int)sz - 1]; if ('>' == *end || '"' == *end) end--; if (end > start) putkeys(mpage, start, end - start + 1, TYPE_In); return 0; } static void parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n) { char *cp; size_t sz; if (n->type != ROFFT_TEXT) return; /* Skip function pointer punctuation. */ cp = n->string; while (*cp == '(' || *cp == '*') cp++; sz = strcspn(cp, "()"); putkeys(mpage, cp, sz, TYPE_Fn); if (n->sec == SEC_SYNOPSIS) putkeys(mpage, cp, sz, NAME_SYN); } static int parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { uint64_t mask; if (n->child == NULL) return 0; parse_mdoc_fname(mpage, n->child); n = n->child->next; if (n != NULL && n->type == ROFFT_TEXT) { mask = TYPE_Fa; if (n->sec == SEC_SYNOPSIS) mask |= TYPE_Vt; putmdockey(mpage, n, mask, 0); } return 0; } static int parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { if (n->type != ROFFT_HEAD) return 1; if (n->child != NULL) parse_mdoc_fname(mpage, n->child); return 0; } static int parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { char *cp; if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY) return 0; if (n->child != NULL && n->child->next == NULL && n->child->type == ROFFT_TEXT) return 1; cp = NULL; deroff(&cp, n); if (cp != NULL) { putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va || n->type == ROFFT_BODY ? TYPE_Va : 0)); free(cp); } return 0; } static int parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { char *cp; if (NULL == (n = n->child)) return 0; if (NULL == n->next) { putkey(mpage, n->string, TYPE_Xr); return 0; } mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string); putkey(mpage, cp, TYPE_Xr); free(cp); return 0; } static int parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { if (n->type == ROFFT_BODY) deroff(&mpage->desc, n); return 0; } static int parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { if (SEC_NAME == n->sec) putmdockey(mpage, n->child, NAME_TITLE, 0); else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) { if (n->child == NULL) putkey(mpage, meta->name, NAME_SYN); else putmdockey(mpage, n->child, NAME_SYN, 0); } if ( ! (mpage->name_head_done || n->child == NULL || n->child->string == NULL || strcasecmp(n->child->string, meta->title))) { putkey(mpage, n->child->string, NAME_HEAD); mpage->name_head_done = 1; } return 0; } static int parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD; } static int parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta, const struct roff_node *n) { return n->type == ROFFT_HEAD; } /* * Add a string to the hash table for the current manual. * Each string has a bitmask telling which macros it belongs to. * When we finish the manual, we'll dump the table. */ static void putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v) { struct ohash *htab; struct str *s; const char *end; unsigned int slot; int i, mustfree; if (0 == sz) return; mustfree = render_string(&cp, &sz); if (TYPE_Nm & v) { htab = &names; v &= name_mask; if (v & NAME_FIRST) name_mask &= ~NAME_FIRST; if (debug > 1) say(mpage->mlinks->file, "Adding name %*s, bits=0x%llx", (int)sz, cp, (unsigned long long)v); } else { htab = &strings; if (debug > 1) for (i = 0; i < KEY_MAX; i++) if ((uint64_t)1 << i & v) say(mpage->mlinks->file, "Adding key %s=%*s", mansearch_keynames[i], (int)sz, cp); } end = cp + sz; slot = ohash_qlookupi(htab, cp, &end); s = ohash_find(htab, slot); if (NULL != s && mpage == s->mpage) { s->mask |= v; return; } else if (NULL == s) { s = mandoc_calloc(1, sizeof(struct str) + sz + 1); memcpy(s->key, cp, sz); ohash_insert(htab, slot, s); } s->mpage = mpage; s->mask = v; if (mustfree) free(cp); } /* * Take a Unicode codepoint and produce its UTF-8 encoding. * This isn't the best way to do this, but it works. * The magic numbers are from the UTF-8 packaging. * They're not as scary as they seem: read the UTF-8 spec for details. */ static size_t utf8(unsigned int cp, char out[7]) { size_t rc; rc = 0; if (cp <= 0x0000007F) { rc = 1; out[0] = (char)cp; } else if (cp <= 0x000007FF) { rc = 2; out[0] = (cp >> 6 & 31) | 192; out[1] = (cp & 63) | 128; } else if (cp <= 0x0000FFFF) { rc = 3; out[0] = (cp >> 12 & 15) | 224; out[1] = (cp >> 6 & 63) | 128; out[2] = (cp & 63) | 128; } else if (cp <= 0x001FFFFF) { rc = 4; out[0] = (cp >> 18 & 7) | 240; out[1] = (cp >> 12 & 63) | 128; out[2] = (cp >> 6 & 63) | 128; out[3] = (cp & 63) | 128; } else if (cp <= 0x03FFFFFF) { rc = 5; out[0] = (cp >> 24 & 3) | 248; out[1] = (cp >> 18 & 63) | 128; out[2] = (cp >> 12 & 63) | 128; out[3] = (cp >> 6 & 63) | 128; out[4] = (cp & 63) | 128; } else if (cp <= 0x7FFFFFFF) { rc = 6; out[0] = (cp >> 30 & 1) | 252; out[1] = (cp >> 24 & 63) | 128; out[2] = (cp >> 18 & 63) | 128; out[3] = (cp >> 12 & 63) | 128; out[4] = (cp >> 6 & 63) | 128; out[5] = (cp & 63) | 128; } else return 0; out[rc] = '\0'; return rc; } /* * If the string contains escape sequences, * replace it with an allocated rendering and return 1, * such that the caller can free it after use. * Otherwise, do nothing and return 0. */ static int render_string(char **public, size_t *psz) { const char *src, *scp, *addcp, *seq; char *dst; size_t ssz, dsz, addsz; char utfbuf[7], res[6]; int seqlen, unicode; res[0] = '\\'; res[1] = '\t'; res[2] = ASCII_NBRSP; res[3] = ASCII_HYPH; res[4] = ASCII_BREAK; res[5] = '\0'; src = scp = *public; ssz = *psz; dst = NULL; dsz = 0; while (scp < src + *psz) { /* Leave normal characters unchanged. */ if (strchr(res, *scp) == NULL) { if (dst != NULL) dst[dsz++] = *scp; scp++; continue; } /* * Found something that requires replacing, * make sure we have a destination buffer. */ if (dst == NULL) { dst = mandoc_malloc(ssz + 1); dsz = scp - src; memcpy(dst, src, dsz); } /* Handle single-char special characters. */ switch (*scp) { case '\\': break; case '\t': case ASCII_NBRSP: dst[dsz++] = ' '; scp++; continue; case ASCII_HYPH: dst[dsz++] = '-'; /* FALLTHROUGH */ case ASCII_BREAK: scp++; continue; default: abort(); } /* * Found an escape sequence. * Read past the slash, then parse it. * Ignore everything except characters. */ scp++; if (mandoc_escape(&scp, &seq, &seqlen) != ESCAPE_SPECIAL) continue; /* * Render the special character * as either UTF-8 or ASCII. */ if (write_utf8) { unicode = mchars_spec2cp(seq, seqlen); if (unicode <= 0) continue; addsz = utf8(unicode, utfbuf); if (addsz == 0) continue; addcp = utfbuf; } else { addcp = mchars_spec2str(seq, seqlen, &addsz); if (addcp == NULL) continue; if (*addcp == ASCII_NBRSP) { addcp = " "; addsz = 1; } } /* Copy the rendered glyph into the stream. */ ssz += addsz; dst = mandoc_realloc(dst, ssz + 1); memcpy(dst + dsz, addcp, addsz); dsz += addsz; } if (dst != NULL) { *public = dst; *psz = dsz; } /* Trim trailing whitespace and NUL-terminate. */ while (*psz > 0 && (*public)[*psz - 1] == ' ') --*psz; if (dst != NULL) { (*public)[*psz] = '\0'; return 1; } else return 0; } static void dbadd_mlink(const struct mlink *mlink) { dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE); dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec); dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec); dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch); dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file); } /* * Flush the current page's terms (and their bits) into the database. * Also, handle escape sequences at the last possible moment. */ static void dbadd(struct dba *dba, struct mpage *mpage) { struct mlink *mlink; struct str *key; char *cp; uint64_t mask; size_t i; unsigned int slot; int mustfree; mlink = mpage->mlinks; if (nodb) { for (key = ohash_first(&names, &slot); NULL != key; key = ohash_next(&names, &slot)) free(key); for (key = ohash_first(&strings, &slot); NULL != key; key = ohash_next(&strings, &slot)) free(key); if (0 == debug) return; while (NULL != mlink) { fputs(mlink->name, stdout); if (NULL == mlink->next || strcmp(mlink->dsec, mlink->next->dsec) || strcmp(mlink->fsec, mlink->next->fsec) || strcmp(mlink->arch, mlink->next->arch)) { putchar('('); if ('\0' == *mlink->dsec) fputs(mlink->fsec, stdout); else fputs(mlink->dsec, stdout); if ('\0' != *mlink->arch) printf("/%s", mlink->arch); putchar(')'); } mlink = mlink->next; if (NULL != mlink) fputs(", ", stdout); } printf(" - %s\n", mpage->desc); return; } if (debug) say(mlink->file, "Adding to database"); cp = mpage->desc; i = strlen(cp); mustfree = render_string(&cp, &i); mpage->dba = dba_page_new(dba->pages, *mpage->arch == '\0' ? mlink->arch : mpage->arch, cp, mlink->file, mpage->form); if (mustfree) free(cp); dba_page_add(mpage->dba, DBP_SECT, mpage->sec); while (mlink != NULL) { dbadd_mlink(mlink); mlink = mlink->next; } for (key = ohash_first(&names, &slot); NULL != key; key = ohash_next(&names, &slot)) { assert(key->mpage == mpage); dba_page_alias(mpage->dba, key->key, key->mask); free(key); } for (key = ohash_first(&strings, &slot); NULL != key; key = ohash_next(&strings, &slot)) { assert(key->mpage == mpage); i = 0; for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) { if (key->mask & mask) dba_macro_add(dba->macros, i, key->key, mpage->dba); i++; } free(key); } } static void dbprune(struct dba *dba) { struct dba_array *page, *files; char *file; dba_array_FOREACH(dba->pages, page) { files = dba_array_get(page, DBP_FILE); dba_array_FOREACH(files, file) { if (*file < ' ') file++; if (ohash_find(&mlinks, ohash_qlookup(&mlinks, file)) != NULL) { if (debug) say(file, "Deleting from database"); dba_array_del(dba->pages); break; } } } } /* * Write the database from memory to disk. */ static void dbwrite(struct dba *dba) { struct stat sb1, sb2; char tfn[33], *cp1, *cp2; off_t i; int fd1, fd2; /* * Do not write empty databases, and delete existing ones * when makewhatis -u causes them to become empty. */ dba_array_start(dba->pages); if (dba_array_next(dba->pages) == NULL) { if (unlink(MANDOC_DB) == -1 && errno != ENOENT) say(MANDOC_DB, "&unlink"); return; } /* * Build the database in a temporary file, * then atomically move it into place. */ if (dba_write(MANDOC_DB "~", dba) != -1) { if (rename(MANDOC_DB "~", MANDOC_DB) == -1) { exitcode = (int)MANDOCLEVEL_SYSERR; say(MANDOC_DB, "&rename"); unlink(MANDOC_DB "~"); } return; } /* * We lack write permission and cannot replace the database * file, but let's at least check whether the data changed. */ (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn)); if (mkdtemp(tfn) == NULL) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&%s", tfn); return; } cp1 = cp2 = MAP_FAILED; fd1 = fd2 = -1; (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn)); if (dba_write(tfn, dba) == -1) { say(tfn, "&dba_write"); goto err; } if ((fd1 = open(MANDOC_DB, O_RDONLY, 0)) == -1) { say(MANDOC_DB, "&open"); goto err; } if ((fd2 = open(tfn, O_RDONLY, 0)) == -1) { say(tfn, "&open"); goto err; } if (fstat(fd1, &sb1) == -1) { say(MANDOC_DB, "&fstat"); goto err; } if (fstat(fd2, &sb2) == -1) { say(tfn, "&fstat"); goto err; } if (sb1.st_size != sb2.st_size) goto err; if ((cp1 = mmap(NULL, sb1.st_size, PROT_READ, MAP_PRIVATE, fd1, 0)) == MAP_FAILED) { say(MANDOC_DB, "&mmap"); goto err; } if ((cp2 = mmap(NULL, sb2.st_size, PROT_READ, MAP_PRIVATE, fd2, 0)) == MAP_FAILED) { say(tfn, "&mmap"); goto err; } for (i = 0; i < sb1.st_size; i++) if (cp1[i] != cp2[i]) goto err; goto out; err: exitcode = (int)MANDOCLEVEL_SYSERR; say(MANDOC_DB, "Data changed, but cannot replace database"); out: if (cp1 != MAP_FAILED) munmap(cp1, sb1.st_size); if (cp2 != MAP_FAILED) munmap(cp2, sb2.st_size); if (fd1 != -1) close(fd1); if (fd2 != -1) close(fd2); unlink(tfn); *strrchr(tfn, '/') = '\0'; rmdir(tfn); } static int set_basedir(const char *targetdir, int report_baddir) { static char startdir[PATH_MAX]; static int getcwd_status; /* 1 = ok, 2 = failure */ static int chdir_status; /* 1 = changed directory */ /* * Remember the original working directory, if possible. * This will be needed if the second or a later directory * on the command line is given as a relative path. * Do not error out if the current directory is not * searchable: Maybe it won't be needed after all. */ if (getcwd_status == 0) { if (getcwd(startdir, sizeof(startdir)) == NULL) { getcwd_status = 2; (void)strlcpy(startdir, strerror(errno), sizeof(startdir)); } else getcwd_status = 1; } /* * We are leaving the old base directory. * Do not use it any longer, not even for messages. */ *basedir = '\0'; basedir_len = 0; /* * If and only if the directory was changed earlier and * the next directory to process is given as a relative path, * first go back, or bail out if that is impossible. */ if (chdir_status && *targetdir != '/') { if (getcwd_status == 2) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "getcwd: %s", startdir); return 0; } if (chdir(startdir) == -1) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&chdir %s", startdir); return 0; } } /* * Always resolve basedir to the canonicalized absolute * pathname and append a trailing slash, such that * we can reliably check whether files are inside. */ if (realpath(targetdir, basedir) == NULL) { if (report_baddir || errno != ENOENT) { exitcode = (int)MANDOCLEVEL_BADARG; say("", "&%s: realpath", targetdir); } *basedir = '\0'; return 0; } else if (chdir(basedir) == -1) { if (report_baddir || errno != ENOENT) { exitcode = (int)MANDOCLEVEL_BADARG; say("", "&chdir"); } *basedir = '\0'; return 0; } chdir_status = 1; basedir_len = strlen(basedir); if (basedir[basedir_len - 1] != '/') { if (basedir_len >= PATH_MAX - 1) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "Filename too long"); *basedir = '\0'; basedir_len = 0; return 0; } basedir[basedir_len++] = '/'; basedir[basedir_len] = '\0'; } return 1; } #ifdef READ_ALLOWED_PATH static int read_allowed(const char *candidate) { const char *cp; size_t len; for (cp = READ_ALLOWED_PATH;; cp += len) { while (*cp == ':') cp++; if (*cp == '\0') return 0; len = strcspn(cp, ":"); if (strncmp(candidate, cp, len) == 0) return 1; } } #endif static void say(const char *file, const char *format, ...) { va_list ap; int use_errno; if (*basedir != '\0') fprintf(stderr, "%s", basedir); if (*basedir != '\0' && *file != '\0') fputc('/', stderr); if (*file != '\0') fprintf(stderr, "%s", file); use_errno = 1; if (format != NULL) { switch (*format) { case '&': format++; break; case '\0': format = NULL; break; default: use_errno = 0; break; } } if (format != NULL) { if (*basedir != '\0' || *file != '\0') fputs(": ", stderr); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } if (use_errno) { if (*basedir != '\0' || *file != '\0' || format != NULL) fputs(": ", stderr); perror(NULL); } else fputc('\n', stderr); } /* $Id: manpath.c,v 1.43 2020/08/27 14:59:47 schwarze Exp $ */ /* * 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 #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 *, char); static void manpath_parseline(struct manpaths *, char *, char); void manconf_parse(struct manconf *conf, const char *file, char *defp, char *auxp) { char *insert; /* Always prepend -m. */ manpath_parseline(&conf->manpath, auxp, 'm'); /* If -M is given, it overrides everything else. */ if (NULL != defp) { manpath_parseline(&conf->manpath, defp, 'M'); return; } /* MANPATH and man.conf(5) cooperate. */ defp = getenv("MANPATH"); if (NULL == file) file = MAN_CONF_FILE; /* No MANPATH; use man.conf(5) only. */ if (NULL == defp || '\0' == defp[0]) { manconf_file(conf, file); return; } /* Prepend man.conf(5) to MANPATH. */ if (':' == defp[0]) { manconf_file(conf, file); manpath_parseline(&conf->manpath, defp, '\0'); return; } /* Append man.conf(5) to MANPATH. */ if (':' == defp[strlen(defp) - 1]) { manpath_parseline(&conf->manpath, defp, '\0'); manconf_file(conf, file); return; } /* Insert man.conf(5) into MANPATH. */ insert = strstr(defp, "::"); if (NULL != insert) { *insert++ = '\0'; manpath_parseline(&conf->manpath, defp, '\0'); manconf_file(conf, file); manpath_parseline(&conf->manpath, insert + 1, '\0'); return; } /* MANPATH overrides man.conf(5) completely. */ manpath_parseline(&conf->manpath, defp, '\0'); } void manpath_base(struct manpaths *dirs) { char path_base[] = MANPATH_BASE; manpath_parseline(dirs, path_base, '\0'); } /* * Parse a FULL pathname from a colon-separated list of arrays. */ static void manpath_parseline(struct manpaths *dirs, char *path, char option) { char *dir; if (NULL == path) return; for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) manpath_add(dirs, dir, option); } /* * Add a directory to the array, ignoring bad directories. * Grow the array one-by-one for simplicity's sake. */ static void manpath_add(struct manpaths *dirs, const char *dir, char option) { char buf[PATH_MAX]; struct stat sb; char *cp; size_t i; if ((cp = realpath(dir, buf)) == NULL) goto fail; for (i = 0; i < dirs->sz; i++) if (strcmp(dirs->paths[i], dir) == 0) return; if (stat(cp, &sb) == -1) goto fail; dirs->paths = mandoc_reallocarray(dirs->paths, dirs->sz + 1, sizeof(*dirs->paths)); dirs->paths[dirs->sz++] = mandoc_strdup(cp); return; fail: if (option != '\0') mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-%c %s: %s", option, dir, strerror(errno)); } void manconf_free(struct manconf *conf) { size_t i; for (i = 0; i < conf->manpath.sz; i++) free(conf->manpath.paths[i]); free(conf->manpath.paths); free(conf->output.includes); free(conf->output.man); free(conf->output.paper); free(conf->output.style); } static void manconf_file(struct manconf *conf, const char *file) { const char *const toks[] = { "manpath", "output" }; char manpath_default[] = MANPATH_DEFAULT; FILE *stream; char *line, *cp, *ep; size_t linesz, tok, toklen; ssize_t linelen; if ((stream = fopen(file, "r")) == NULL) goto out; line = NULL; linesz = 0; while ((linelen = getline(&line, &linesz, stream)) != -1) { cp = line; ep = cp + linelen - 1; while (ep > cp && isspace((unsigned char)*ep)) *ep-- = '\0'; while (isspace((unsigned char)*cp)) cp++; if (cp == ep || *cp == '#') continue; for (tok = 0; tok < sizeof(toks)/sizeof(toks[0]); tok++) { toklen = strlen(toks[tok]); if (cp + toklen < ep && isspace((unsigned char)cp[toklen]) && strncmp(cp, toks[tok], toklen) == 0) { cp += toklen; while (isspace((unsigned char)*cp)) cp++; break; } } switch (tok) { case 0: /* manpath */ manpath_add(&conf->manpath, cp, '\0'); *manpath_default = '\0'; break; case 1: /* output */ manconf_output(&conf->output, cp, 1); break; default: break; } } free(line); fclose(stream); out: if (*manpath_default != '\0') manpath_parseline(&conf->manpath, manpath_default, '\0'); } int manconf_output(struct manoutput *conf, const char *cp, int fromfile) { const char *const toks[] = { /* Tokens requiring an argument. */ "includes", "man", "paper", "style", "indent", "width", "outfilename", "tagfilename", /* Token taking an optional argument. */ "tag", /* Tokens not taking arguments. */ "fragment", "mdoc", "noval", "toc" }; const size_t ntoks = sizeof(toks) / sizeof(toks[0]); const char *errstr; char *oldval; size_t len, tok; for (tok = 0; tok < ntoks; tok++) { len = strlen(toks[tok]); if (strncmp(cp, toks[tok], len) == 0 && strchr(" = ", cp[len]) != NULL) { cp += len; if (*cp == '=') cp++; while (isspace((unsigned char)*cp)) cp++; break; } } if (tok < 8 && *cp == '\0') { mandoc_msg(MANDOCERR_BADVAL_MISS, 0, 0, "-O %s=?", toks[tok]); return -1; } if (tok > 8 && tok < ntoks && *cp != '\0') { mandoc_msg(MANDOCERR_BADVAL, 0, 0, "-O %s=%s", toks[tok], cp); return -1; } switch (tok) { case 0: if (conf->includes != NULL) { oldval = mandoc_strdup(conf->includes); break; } conf->includes = mandoc_strdup(cp); return 0; case 1: if (conf->man != NULL) { oldval = mandoc_strdup(conf->man); break; } conf->man = mandoc_strdup(cp); return 0; case 2: if (conf->paper != NULL) { oldval = mandoc_strdup(conf->paper); break; } conf->paper = mandoc_strdup(cp); return 0; case 3: if (conf->style != NULL) { oldval = mandoc_strdup(conf->style); break; } conf->style = mandoc_strdup(cp); return 0; case 4: if (conf->indent) { mandoc_asprintf(&oldval, "%zu", conf->indent); break; } conf->indent = strtonum(cp, 0, 1000, &errstr); if (errstr == NULL) return 0; mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0, "-O indent=%s is %s", cp, errstr); return -1; case 5: if (conf->width) { mandoc_asprintf(&oldval, "%zu", conf->width); break; } conf->width = strtonum(cp, 1, 1000, &errstr); if (errstr == NULL) return 0; mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0, "-O width=%s is %s", cp, errstr); return -1; case 6: if (conf->outfilename != NULL) { oldval = mandoc_strdup(conf->outfilename); break; } conf->outfilename = mandoc_strdup(cp); return 0; case 7: if (conf->tagfilename != NULL) { oldval = mandoc_strdup(conf->tagfilename); break; } conf->tagfilename = mandoc_strdup(cp); return 0; /* * If the index of the following token changes, * do not forget to adjust the range check above the switch. */ case 8: if (conf->tag != NULL) { oldval = mandoc_strdup(conf->tag); break; } conf->tag = mandoc_strdup(cp); return 0; case 9: conf->fragment = 1; return 0; case 10: conf->mdoc = 1; return 0; case 11: conf->noval = 1; return 0; case 12: conf->toc = 1; return 0; default: mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-O %s", cp); return -1; } if (fromfile) { free(oldval); return 0; } else { mandoc_msg(MANDOCERR_BADVAL_DUPE, 0, 0, "-O %s=%s: already set to %s", toks[tok], cp, oldval); free(oldval); return -1; } } /* $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", page->file + 1, paths->paths[i]); free(mpage->file); free(rp); continue; } mpage->names = buildnames(page); mpage->output = buildoutput(outkey, page); mpage->bits = search->firstmatch ? rp->bits : 0; mpage->ipath = i; mpage->sec = *page->sect - '0'; if (mpage->sec < 0 || mpage->sec > 9) mpage->sec = 10; mpage->form = *page->file; free(rp); cur++; } ohash_delete(htab); free(htab); dbm_close(); /* * In man(1) mode, prefer matches in earlier trees * over matches in later trees. */ if (cur && search->firstmatch) break; } if (res != NULL) qsort(*res, cur, sizeof(struct manpage), manpage_compare); if (chdir_status && getcwd_status && chdir(buf) == -1) warn("%s", buf); exprfree(e); *sz = cur; return res != NULL || cur; } /* * Merge the results for the expression tree rooted at e * into the the result list htab. */ static struct ohash * manmerge(struct expr *e, struct ohash *htab) { switch (e->type) { case EXPR_TERM: return manmerge_term(e, htab); case EXPR_OR: return manmerge_or(e->child, htab); case EXPR_AND: return manmerge_and(e->child, htab); default: abort(); } } static struct ohash * manmerge_term(struct expr *e, struct ohash *htab) { struct dbm_res res, *rp; uint64_t ib; unsigned int slot; int im; if (htab == NULL) { htab = mandoc_malloc(sizeof(*htab)); mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page)); } for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) { if ((e->bits & ib) == 0) continue; switch (ib) { case TYPE_arch: dbm_page_byarch(&e->match); break; case TYPE_sec: dbm_page_bysect(&e->match); break; case TYPE_Nm: dbm_page_byname(&e->match); break; case TYPE_Nd: dbm_page_bydesc(&e->match); break; default: dbm_page_bymacro(im - 2, &e->match); break; } /* * When hashing for deduplication, use the unique * page ID itself instead of a hash function; * that is quite efficient. */ for (;;) { res = dbm_page_next(); if (res.page == -1) break; slot = ohash_lookup_memory(htab, (char *)&res, sizeof(res.page), res.page); if ((rp = ohash_find(htab, slot)) != NULL) { rp->bits |= res.bits; continue; } rp = mandoc_malloc(sizeof(*rp)); *rp = res; ohash_insert(htab, slot, rp); } } return htab; } static struct ohash * manmerge_or(struct expr *e, struct ohash *htab) { while (e != NULL) { htab = manmerge(e, htab); e = e->next; } return htab; } static struct ohash * manmerge_and(struct expr *e, struct ohash *htab) { struct ohash *hand, *h1, *h2; struct dbm_res *res; unsigned int slot1, slot2; /* Evaluate the first term of the AND clause. */ hand = manmerge(e, NULL); while ((e = e->next) != NULL) { /* Evaluate the next term and prepare for ANDing. */ h2 = manmerge(e, NULL); if (ohash_entries(h2) < ohash_entries(hand)) { h1 = h2; h2 = hand; } else h1 = hand; hand = mandoc_malloc(sizeof(*hand)); mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page)); /* Keep all pages that are in both result sets. */ for (res = ohash_first(h1, &slot1); res != NULL; res = ohash_next(h1, &slot1)) { if (ohash_find(h2, ohash_lookup_memory(h2, (char *)res, sizeof(res->page), res->page)) == NULL) free(res); else ohash_insert(hand, ohash_lookup_memory(hand, (char *)res, sizeof(res->page), res->page), res); } /* Discard the merged results. */ for (res = ohash_first(h2, &slot2); res != NULL; res = ohash_next(h2, &slot2)) free(res); ohash_delete(h2); free(h2); ohash_delete(h1); free(h1); } /* Merge the result of the AND into htab. */ if (htab == NULL) return hand; for (res = ohash_first(hand, &slot1); res != NULL; res = ohash_next(hand, &slot1)) { slot2 = ohash_lookup_memory(htab, (char *)res, sizeof(res->page), res->page); if (ohash_find(htab, slot2) == NULL) ohash_insert(htab, slot2, res); else free(res); } /* Discard the merged result. */ ohash_delete(hand); free(hand); return htab; } void mansearch_free(struct manpage *res, size_t sz) { size_t i; for (i = 0; i < sz; i++) { free(res[i].file); free(res[i].names); free(res[i].output); } free(res); } static int manpage_compare(const void *vp1, const void *vp2) { const struct manpage *mp1, *mp2; const char *cp1, *cp2; size_t sz1, sz2; int diff; mp1 = vp1; mp2 = vp2; if ((diff = mp2->bits - mp1->bits) || (diff = mp1->sec - mp2->sec)) return diff; /* Fall back to alphabetic ordering of names. */ sz1 = strcspn(mp1->names, "("); sz2 = strcspn(mp2->names, "("); if (sz1 < sz2) sz1 = sz2; if ((diff = strncasecmp(mp1->names, mp2->names, sz1))) return diff; /* For identical names and sections, prefer arch-dependent. */ cp1 = strchr(mp1->names + sz1, '/'); cp2 = strchr(mp2->names + sz2, '/'); return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) : cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0; } static char * buildnames(const struct dbm_page *page) { char *buf; size_t i, sz; sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) + (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2; buf = mandoc_malloc(sz); i = 0; lstcat(buf, &i, page->name, ", "); buf[i++] = '('; lstcat(buf, &i, page->sect, ", "); if (page->arch != NULL) { buf[i++] = '/'; lstcat(buf, &i, page->arch, ", "); } buf[i++] = ')'; buf[i++] = '\0'; assert(i == sz); return buf; } /* * Count the buffer space needed to print the NUL-terminated * list of NUL-terminated strings, when printing sep separator * characters between strings. */ static size_t lstlen(const char *cp, size_t sep) { size_t sz; for (sz = 0; *cp != '\0'; cp++) { /* Skip names appearing only in the SYNOPSIS. */ if (*cp <= (char)(NAME_SYN & NAME_MASK)) { while (*cp != '\0') cp++; continue; } /* Skip name class markers. */ if (*cp < ' ') cp++; /* Print a separator before each but the first string. */ if (sz) sz += sep; /* Copy one string. */ while (*cp != '\0') { sz++; cp++; } } return sz; } /* * Print the NUL-terminated list of NUL-terminated strings * into the buffer, seperating strings with sep. */ static void lstcat(char *buf, size_t *i, const char *cp, const char *sep) { const char *s; size_t i_start; for (i_start = *i; *cp != '\0'; cp++) { /* Skip names appearing only in the SYNOPSIS. */ if (*cp <= (char)(NAME_SYN & NAME_MASK)) { while (*cp != '\0') cp++; continue; } /* Skip name class markers. */ if (*cp < ' ') cp++; /* Print a separator before each but the first string. */ if (*i > i_start) { s = sep; while (*s != '\0') buf[(*i)++] = *s++; } /* Copy one string. */ while (*cp != '\0') buf[(*i)++] = *cp++; } } /* * Return 1 if the string *want occurs in any of the strings * in the NUL-terminated string list *have, or 0 otherwise. * If either argument is NULL or empty, assume no filtering * is desired and return 1. */ static int lstmatch(const char *want, const char *have) { if (want == NULL || have == NULL || *have == '\0') return 1; while (*have != '\0') { if (strcasestr(have, want) != NULL) return 1; have = strchr(have, '\0') + 1; } return 0; } /* * Build a list of values taken by the macro im in the manual page. */ static char * buildoutput(size_t im, struct dbm_page *page) { const char *oldoutput, *sep, *input; char *output, *newoutput, *value; size_t sz, i; switch (im) { case KEY_Nd: return mandoc_strdup(page->desc); case KEY_Nm: input = page->name; break; case KEY_sec: input = page->sect; break; case KEY_arch: input = page->arch; if (input == NULL) input = "all\0"; break; default: input = NULL; break; } if (input != NULL) { sz = lstlen(input, 3) + 1; output = mandoc_malloc(sz); i = 0; lstcat(output, &i, input, " # "); output[i++] = '\0'; assert(i == sz); return output; } output = NULL; dbm_macro_bypage(im - 2, page->addr); while ((value = dbm_macro_next()) != NULL) { if (output == NULL) { oldoutput = ""; sep = ""; } else { oldoutput = output; sep = " # "; } mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value); free(output); output = newoutput; } return output; } /* * Compile a set of string tokens into an expression. * Tokens in "argv" are assumed to be individual expression atoms (e.g., * "(", "foo=bar", etc.). */ static struct expr * exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi) { struct expr *parent, *child; int needterm, nested; if ((nested = *argi) == argc) return NULL; needterm = 1; parent = child = NULL; while (*argi < argc) { if (strcmp(")", argv[*argi]) == 0) { if (needterm) warnx("missing term " "before closing parenthesis"); needterm = 0; if (nested) break; warnx("ignoring unmatched right parenthesis"); ++*argi; continue; } if (strcmp("-o", argv[*argi]) == 0) { if (needterm) { if (*argi > 0) warnx("ignoring -o after %s", argv[*argi - 1]); else warnx("ignoring initial -o"); } needterm = 1; ++*argi; continue; } needterm = 0; if (child == NULL) { child = expr_and(search, argc, argv, argi); continue; } if (parent == NULL) { parent = mandoc_calloc(1, sizeof(*parent)); parent->type = EXPR_OR; parent->next = NULL; parent->child = child; } child->next = expr_and(search, argc, argv, argi); child = child->next; } if (needterm && *argi) warnx("ignoring trailing %s", argv[*argi - 1]); return parent == NULL ? child : parent; } static struct expr * expr_and(const struct mansearch *search, int argc, char *argv[], int *argi) { struct expr *parent, *child; int needterm; needterm = 1; parent = child = NULL; while (*argi < argc) { if (strcmp(")", argv[*argi]) == 0) { if (needterm) warnx("missing term " "before closing parenthesis"); needterm = 0; break; } if (strcmp("-o", argv[*argi]) == 0) break; if (strcmp("-a", argv[*argi]) == 0) { if (needterm) { if (*argi > 0) warnx("ignoring -a after %s", argv[*argi - 1]); else warnx("ignoring initial -a"); } needterm = 1; ++*argi; continue; } if (needterm == 0) break; if (child == NULL) { child = exprterm(search, argc, argv, argi); if (child != NULL) needterm = 0; continue; } needterm = 0; if (parent == NULL) { parent = mandoc_calloc(1, sizeof(*parent)); parent->type = EXPR_AND; parent->next = NULL; parent->child = child; } child->next = exprterm(search, argc, argv, argi); if (child->next != NULL) { child = child->next; needterm = 0; } } if (needterm && *argi) warnx("ignoring trailing %s", argv[*argi - 1]); return parent == NULL ? child : parent; } static struct expr * exprterm(const struct mansearch *search, int argc, char *argv[], int *argi) { char errbuf[BUFSIZ]; struct expr *e; char *key, *val; uint64_t iterbit; int cs, i, irc; if (strcmp("(", argv[*argi]) == 0) { ++*argi; e = exprcomp(search, argc, argv, argi); if (*argi < argc) { assert(strcmp(")", argv[*argi]) == 0); ++*argi; } else warnx("unclosed parenthesis"); return e; } if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) { cs = 0; ++*argi; } else cs = 1; e = mandoc_calloc(1, sizeof(*e)); e->type = EXPR_TERM; e->bits = 0; e->next = NULL; e->child = NULL; if (search->argmode == ARG_NAME) { e->bits = TYPE_Nm; e->match.type = DBM_EXACT; e->match.str = argv[(*argi)++]; return e; } /* * Separate macro keys from search string. * If needed, request regular expression handling. */ if (search->argmode == ARG_WORD) { e->bits = TYPE_Nm; e->match.type = DBM_REGEX; #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); } /* $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); /* $Id: mdoc.c,v 1.275 2020/04/06 10:16:17 schwarze Exp $ */ /* * Copyright (c) 2010, 2012-2018, 2020 Ingo Schwarze * Copyright (c) 2008, 2009, 2010, 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. * * Top level and utility functions of the mdoc(7) parser for mandoc(1). */ #include "config.h" #include #include #include #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" const char *const __mdoc_argnames[MDOC_ARG_MAX] = { "split", "nosplit", "ragged", "unfilled", "literal", "file", "offset", "bullet", "dash", "hyphen", "item", "enum", "tag", "diag", "hang", "ohang", "inset", "column", "width", "compact", "std", "filled", "words", "emphasis", "symbolic", "nested", "centered" }; const char * const *mdoc_argnames = __mdoc_argnames; static int mdoc_ptext(struct roff_man *, int, char *, int); static int mdoc_pmacro(struct roff_man *, int, char *, int); /* * Main parse routine. Parses a single line -- really just hands off to * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). */ int mdoc_parseln(struct roff_man *mdoc, int ln, char *buf, int offs) { if (mdoc->last->type != ROFFT_EQN || ln > mdoc->last->line) mdoc->flags |= MDOC_NEWLINE; /* * Let the roff nS register switch SYNOPSIS mode early, * such that the parser knows at all times * whether this mode is on or off. * Note that this mode is also switched by the Sh macro. */ if (roff_getreg(mdoc->roff, "nS")) mdoc->flags |= MDOC_SYNOPSIS; else mdoc->flags &= ~MDOC_SYNOPSIS; return roff_getcontrol(mdoc->roff, buf, &offs) ? mdoc_pmacro(mdoc, ln, buf, offs) : mdoc_ptext(mdoc, ln, buf, offs); } void mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok) { struct roff_node *p; p = roff_node_alloc(mdoc, line, pos, ROFFT_TAIL, tok); roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_CHILD; } struct roff_node * mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok, struct roff_node *body) { struct roff_node *p; body->flags |= NODE_ENDED; body->parent->flags |= NODE_ENDED; p = roff_node_alloc(mdoc, line, pos, ROFFT_BODY, tok); p->body = body; p->norm = body->norm; p->end = ENDBODY_SPACE; roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_SIBLING; return p; } struct roff_node * mdoc_block_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok, struct mdoc_arg *args) { struct roff_node *p; p = roff_node_alloc(mdoc, line, pos, ROFFT_BLOCK, tok); p->args = args; if (p->args) (args->refcnt)++; switch (tok) { case MDOC_Bd: case MDOC_Bf: case MDOC_Bl: case MDOC_En: case MDOC_Rs: p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); break; default: break; } roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_CHILD; return p; } void mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok, struct mdoc_arg *args) { struct roff_node *p; p = roff_node_alloc(mdoc, line, pos, ROFFT_ELEM, tok); p->args = args; if (p->args) (args->refcnt)++; switch (tok) { case MDOC_An: p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); break; default: break; } roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_CHILD; } /* * Parse free-form text, that is, a line that does not begin with the * control character. */ static int mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs) { struct roff_node *n; const char *cp, *sp; char *c, *ws, *end; n = mdoc->last; /* * If a column list contains plain text, assume an implicit item * macro. This can happen one or more times at the beginning * of such a list, intermixed with non-It mdoc macros and with * nodes generated on the roff level, for example by tbl. */ if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || (n->parent != NULL && n->parent->tok == MDOC_Bl && n->parent->norm->Bl.type == LIST_column)) { mdoc->flags |= MDOC_FREECOL; (*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It, line, offs, &offs, buf); return 1; } /* * Search for the beginning of unescaped trailing whitespace (ws) * and for the first character not to be output (end). */ /* FIXME: replace with strcspn(). */ ws = NULL; for (c = end = buf + offs; *c; c++) { switch (*c) { case ' ': if (NULL == ws) ws = c; continue; case '\t': /* * Always warn about trailing tabs, * even outside literal context, * where they should be put on the next line. */ if (NULL == ws) ws = c; /* * Strip trailing tabs in literal context only; * outside, they affect the next line. */ if (mdoc->flags & ROFF_NOFILL) continue; break; case '\\': /* Skip the escaped character, too, if any. */ if (c[1]) c++; /* FALLTHROUGH */ default: ws = NULL; break; } end = c + 1; } *end = '\0'; if (ws) mandoc_msg(MANDOCERR_SPACE_EOL, line, (int)(ws - buf), NULL); /* * Blank lines are allowed in no-fill mode * and cancel preceding \c, * but add a single vertical space elsewhere. */ if (buf[offs] == '\0' && (mdoc->flags & ROFF_NOFILL) == 0) { switch (mdoc->last->type) { case ROFFT_TEXT: sp = mdoc->last->string; cp = end = strchr(sp, '\0') - 2; if (cp < sp || cp[0] != '\\' || cp[1] != 'c') break; while (cp > sp && cp[-1] == '\\') cp--; if ((end - cp) % 2) break; *end = '\0'; return 1; default: break; } mandoc_msg(MANDOCERR_FI_BLANK, line, (int)(c - buf), NULL); roff_elem_alloc(mdoc, line, offs, ROFF_sp); mdoc->last->flags |= NODE_VALID | NODE_ENDED; mdoc->next = ROFF_NEXT_SIBLING; return 1; } roff_word_alloc(mdoc, line, offs, buf+offs); if (mdoc->flags & ROFF_NOFILL) return 1; /* * End-of-sentence check. If the last character is an unescaped * EOS character, then flag the node as being the end of a * sentence. The front-end will know how to interpret this. */ assert(buf < end); if (mandoc_eos(buf+offs, (size_t)(end-buf-offs))) mdoc->last->flags |= NODE_EOS; for (c = buf + offs; c != NULL; c = strchr(c + 1, '.')) { if (c - buf < offs + 2) continue; if (end - c < 3) break; if (c[1] != ' ' || isalnum((unsigned char)c[-2]) == 0 || isalnum((unsigned char)c[-1]) == 0 || (c[-2] == 'n' && c[-1] == 'c') || (c[-2] == 'v' && c[-1] == 's')) continue; c += 2; if (*c == ' ') c++; if (*c == ' ') c++; if (isupper((unsigned char)(*c))) mandoc_msg(MANDOCERR_EOS, line, (int)(c - buf), NULL); } return 1; } /* * Parse a macro line, that is, a line beginning with the control * character. */ static int mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs) { struct roff_node *n; const char *cp; size_t sz; enum roff_tok tok; int sv; /* Determine the line macro. */ sv = offs; tok = TOKEN_NONE; for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++) offs++; if (sz == 2 || sz == 3) tok = roffhash_find(mdoc->mdocmac, buf + sv, sz); if (tok == TOKEN_NONE) { mandoc_msg(MANDOCERR_MACRO, ln, sv, "%s", buf + sv - 1); return 1; } /* Skip a leading escape sequence or tab. */ switch (buf[offs]) { case '\\': cp = buf + offs + 1; mandoc_escape(&cp, NULL, NULL); offs = cp - buf; break; case '\t': offs++; break; default: break; } /* Jump to the next non-whitespace word. */ while (buf[offs] == ' ') offs++; /* * Trailing whitespace. Note that tabs are allowed to be passed * into the parser as "text", so we only warn about spaces here. */ if ('\0' == buf[offs] && ' ' == buf[offs - 1]) mandoc_msg(MANDOCERR_SPACE_EOL, ln, offs - 1, NULL); /* * If an initial or transparent macro or a list invocation, * divert directly into macro processing. */ n = mdoc->last; if (n == NULL || tok == MDOC_It || tok == MDOC_El || roff_tok_transparent(tok)) { (*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf); return 1; } /* * If a column list contains a non-It macro, assume an implicit * item macro. This can happen one or more times at the * beginning of such a list, intermixed with text lines and * with nodes generated on the roff level, for example by tbl. */ if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || (n->parent != NULL && n->parent->tok == MDOC_Bl && n->parent->norm->Bl.type == LIST_column)) { mdoc->flags |= MDOC_FREECOL; (*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It, ln, sv, &sv, buf); return 1; } /* Normal processing of a macro. */ (*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf); /* In quick mode (for mandocdb), abort after the NAME section. */ if (mdoc->quick && MDOC_Sh == tok && SEC_NAME != mdoc->last->sec) return 2; return 1; } enum mdelim mdoc_isdelim(const char *p) { if ('\0' == p[0]) return DELIM_NONE; if ('\0' == p[1]) switch (p[0]) { case '(': case '[': return DELIM_OPEN; case '|': return DELIM_MIDDLE; case '.': case ',': case ';': case ':': case '?': case '!': case ')': case ']': return DELIM_CLOSE; default: return DELIM_NONE; } if ('\\' != p[0]) return DELIM_NONE; if (0 == strcmp(p + 1, ".")) return DELIM_CLOSE; if (0 == strcmp(p + 1, "fR|\\fP")) return DELIM_MIDDLE; return DELIM_NONE; } /* $Id: mdoc.h,v 1.146 2018/12/30 00:49:55 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2014, 2015 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. */ struct roff_node; struct roff_man; enum mdocargt { MDOC_Split, /* -split */ MDOC_Nosplit, /* -nospli */ MDOC_Ragged, /* -ragged */ MDOC_Unfilled, /* -unfilled */ MDOC_Literal, /* -literal */ MDOC_File, /* -file */ MDOC_Offset, /* -offset */ MDOC_Bullet, /* -bullet */ MDOC_Dash, /* -dash */ MDOC_Hyphen, /* -hyphen */ MDOC_Item, /* -item */ MDOC_Enum, /* -enum */ MDOC_Tag, /* -tag */ MDOC_Diag, /* -diag */ MDOC_Hang, /* -hang */ MDOC_Ohang, /* -ohang */ MDOC_Inset, /* -inset */ MDOC_Column, /* -column */ MDOC_Width, /* -width */ MDOC_Compact, /* -compact */ MDOC_Std, /* -std */ MDOC_Filled, /* -filled */ MDOC_Words, /* -words */ MDOC_Emphasis, /* -emphasis */ MDOC_Symbolic, /* -symbolic */ MDOC_Nested, /* -nested */ MDOC_Centred, /* -centered */ MDOC_ARG_MAX }; /* * An argument to a macro (multiple values = `-column xxx yyy'). */ struct mdoc_argv { enum mdocargt arg; /* type of argument */ int line; int pos; size_t sz; /* elements in "value" */ char **value; /* argument strings */ }; /* * Reference-counted macro arguments. These are refcounted because * blocks have multiple instances of the same arguments spread across * the HEAD, BODY, TAIL, and BLOCK node types. */ struct mdoc_arg { size_t argc; struct mdoc_argv *argv; unsigned int refcnt; }; enum mdoc_list { LIST__NONE = 0, LIST_bullet, /* -bullet */ LIST_column, /* -column */ LIST_dash, /* -dash */ LIST_diag, /* -diag */ LIST_enum, /* -enum */ LIST_hang, /* -hang */ LIST_hyphen, /* -hyphen */ LIST_inset, /* -inset */ LIST_item, /* -item */ LIST_ohang, /* -ohang */ LIST_tag, /* -tag */ LIST_MAX }; enum mdoc_disp { DISP__NONE = 0, DISP_centered, /* -centered */ DISP_ragged, /* -ragged */ DISP_unfilled, /* -unfilled */ DISP_filled, /* -filled */ DISP_literal /* -literal */ }; enum mdoc_auth { AUTH__NONE = 0, AUTH_split, /* -split */ AUTH_nosplit /* -nosplit */ }; enum mdoc_font { FONT__NONE = 0, FONT_Em, /* Em, -emphasis */ FONT_Li, /* Li, -literal */ FONT_Sy /* Sy, -symbolic */ }; struct mdoc_bd { const char *offs; /* -offset */ enum mdoc_disp type; /* -ragged, etc. */ int comp; /* -compact */ }; struct mdoc_bl { const char *width; /* -width */ const char *offs; /* -offset */ enum mdoc_list type; /* -tag, -enum, etc. */ int comp; /* -compact */ size_t ncols; /* -column arg count */ const char **cols; /* -column val ptr */ int count; /* -enum counter */ }; struct mdoc_bf { enum mdoc_font font; /* font */ }; struct mdoc_an { enum mdoc_auth auth; /* -split, etc. */ }; struct mdoc_rs { int quote_T; /* whether to quote %T */ }; /* * Consists of normalised node arguments. These should be used instead * of iterating through the mdoc_arg pointers of a node: defaults are * provided, etc. */ union mdoc_data { struct mdoc_an An; struct mdoc_bd Bd; struct mdoc_bf Bf; struct mdoc_bl Bl; struct roff_node *Es; struct mdoc_rs Rs; }; /* Names of macro args. Index is enum mdocargt. */ extern const char *const *mdoc_argnames; void mdoc_validate(struct roff_man *); /* $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-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; } /* $Id: mdoc_html.c,v 1.342 2021/03/30 19:26:20 schwarze Exp $ */ /* * Copyright (c) 2014-2021 Ingo Schwarze * Copyright (c) 2008-2011, 2014 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. * * HTML formatter for mdoc(7) used by mandoc(1). */ #include "config.h" #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "out.h" #include "html.h" #include "main.h" #define MDOC_ARGS const struct roff_meta *meta, \ struct roff_node *n, \ struct html *h #ifndef MIN #define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b)) #endif struct mdoc_html_act { int (*pre)(MDOC_ARGS); void (*post)(MDOC_ARGS); }; static void print_mdoc_head(const struct roff_meta *, struct html *); static void print_mdoc_node(MDOC_ARGS); static void print_mdoc_nodelist(MDOC_ARGS); static void synopsis_pre(struct html *, struct roff_node *); static void mdoc_root_post(const struct roff_meta *, struct html *); static int mdoc_root_pre(const struct roff_meta *, struct html *); static void mdoc__x_post(MDOC_ARGS); static int mdoc__x_pre(MDOC_ARGS); static int mdoc_abort_pre(MDOC_ARGS); static int mdoc_ad_pre(MDOC_ARGS); static int mdoc_an_pre(MDOC_ARGS); static int mdoc_ap_pre(MDOC_ARGS); static int mdoc_ar_pre(MDOC_ARGS); static int mdoc_bd_pre(MDOC_ARGS); static int mdoc_bf_pre(MDOC_ARGS); static void mdoc_bk_post(MDOC_ARGS); static int mdoc_bk_pre(MDOC_ARGS); static int mdoc_bl_pre(MDOC_ARGS); static int mdoc_cd_pre(MDOC_ARGS); static int mdoc_code_pre(MDOC_ARGS); static int mdoc_d1_pre(MDOC_ARGS); static int mdoc_fa_pre(MDOC_ARGS); static int mdoc_fd_pre(MDOC_ARGS); static int mdoc_fl_pre(MDOC_ARGS); static int mdoc_fn_pre(MDOC_ARGS); static int mdoc_ft_pre(MDOC_ARGS); static int mdoc_em_pre(MDOC_ARGS); static void mdoc_eo_post(MDOC_ARGS); static int mdoc_eo_pre(MDOC_ARGS); static int mdoc_ex_pre(MDOC_ARGS); static void mdoc_fo_post(MDOC_ARGS); static int mdoc_fo_pre(MDOC_ARGS); static int mdoc_igndelim_pre(MDOC_ARGS); static int mdoc_in_pre(MDOC_ARGS); static int mdoc_it_pre(MDOC_ARGS); static int mdoc_lb_pre(MDOC_ARGS); static int mdoc_lk_pre(MDOC_ARGS); static int mdoc_mt_pre(MDOC_ARGS); static int mdoc_nd_pre(MDOC_ARGS); static int mdoc_nm_pre(MDOC_ARGS); static int mdoc_no_pre(MDOC_ARGS); static int mdoc_ns_pre(MDOC_ARGS); static int mdoc_pa_pre(MDOC_ARGS); static void mdoc_pf_post(MDOC_ARGS); static int mdoc_pp_pre(MDOC_ARGS); static void mdoc_quote_post(MDOC_ARGS); static int mdoc_quote_pre(MDOC_ARGS); static int mdoc_rs_pre(MDOC_ARGS); static int mdoc_sh_pre(MDOC_ARGS); static int mdoc_skip_pre(MDOC_ARGS); static int mdoc_sm_pre(MDOC_ARGS); static int mdoc_ss_pre(MDOC_ARGS); static int mdoc_st_pre(MDOC_ARGS); static int mdoc_sx_pre(MDOC_ARGS); static int mdoc_sy_pre(MDOC_ARGS); static int mdoc_tg_pre(MDOC_ARGS); static int mdoc_va_pre(MDOC_ARGS); static int mdoc_vt_pre(MDOC_ARGS); static int mdoc_xr_pre(MDOC_ARGS); static int mdoc_xx_pre(MDOC_ARGS); static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = { {NULL, NULL}, /* Dd */ {NULL, NULL}, /* Dt */ {NULL, NULL}, /* Os */ {mdoc_sh_pre, NULL }, /* Sh */ {mdoc_ss_pre, NULL }, /* Ss */ {mdoc_pp_pre, NULL}, /* Pp */ {mdoc_d1_pre, NULL}, /* D1 */ {mdoc_d1_pre, NULL}, /* Dl */ {mdoc_bd_pre, NULL}, /* Bd */ {NULL, NULL}, /* Ed */ {mdoc_bl_pre, NULL}, /* Bl */ {NULL, NULL}, /* El */ {mdoc_it_pre, NULL}, /* It */ {mdoc_ad_pre, NULL}, /* Ad */ {mdoc_an_pre, NULL}, /* An */ {mdoc_ap_pre, NULL}, /* Ap */ {mdoc_ar_pre, NULL}, /* Ar */ {mdoc_cd_pre, NULL}, /* Cd */ {mdoc_code_pre, NULL}, /* Cm */ {mdoc_code_pre, NULL}, /* Dv */ {mdoc_code_pre, NULL}, /* Er */ {mdoc_code_pre, NULL}, /* Ev */ {mdoc_ex_pre, NULL}, /* Ex */ {mdoc_fa_pre, NULL}, /* Fa */ {mdoc_fd_pre, NULL}, /* Fd */ {mdoc_fl_pre, NULL}, /* Fl */ {mdoc_fn_pre, NULL}, /* Fn */ {mdoc_ft_pre, NULL}, /* Ft */ {mdoc_code_pre, NULL}, /* Ic */ {mdoc_in_pre, NULL}, /* In */ {mdoc_code_pre, NULL}, /* Li */ {mdoc_nd_pre, NULL}, /* Nd */ {mdoc_nm_pre, NULL}, /* Nm */ {mdoc_quote_pre, mdoc_quote_post}, /* Op */ {mdoc_abort_pre, NULL}, /* Ot */ {mdoc_pa_pre, NULL}, /* Pa */ {mdoc_ex_pre, NULL}, /* Rv */ {mdoc_st_pre, NULL}, /* St */ {mdoc_va_pre, NULL}, /* Va */ {mdoc_vt_pre, NULL}, /* Vt */ {mdoc_xr_pre, NULL}, /* Xr */ {mdoc__x_pre, mdoc__x_post}, /* %A */ {mdoc__x_pre, mdoc__x_post}, /* %B */ {mdoc__x_pre, mdoc__x_post}, /* %D */ {mdoc__x_pre, mdoc__x_post}, /* %I */ {mdoc__x_pre, mdoc__x_post}, /* %J */ {mdoc__x_pre, mdoc__x_post}, /* %N */ {mdoc__x_pre, mdoc__x_post}, /* %O */ {mdoc__x_pre, mdoc__x_post}, /* %P */ {mdoc__x_pre, mdoc__x_post}, /* %R */ {mdoc__x_pre, mdoc__x_post}, /* %T */ {mdoc__x_pre, mdoc__x_post}, /* %V */ {NULL, NULL}, /* Ac */ {mdoc_quote_pre, mdoc_quote_post}, /* Ao */ {mdoc_quote_pre, mdoc_quote_post}, /* Aq */ {mdoc_xx_pre, NULL}, /* At */ {NULL, NULL}, /* Bc */ {mdoc_bf_pre, NULL}, /* Bf */ {mdoc_quote_pre, mdoc_quote_post}, /* Bo */ {mdoc_quote_pre, mdoc_quote_post}, /* Bq */ {mdoc_xx_pre, NULL}, /* Bsx */ {mdoc_xx_pre, NULL}, /* Bx */ {mdoc_skip_pre, NULL}, /* Db */ {NULL, NULL}, /* Dc */ {mdoc_quote_pre, mdoc_quote_post}, /* Do */ {mdoc_quote_pre, mdoc_quote_post}, /* Dq */ {NULL, NULL}, /* Ec */ /* FIXME: no space */ {NULL, NULL}, /* Ef */ {mdoc_em_pre, NULL}, /* Em */ {mdoc_eo_pre, mdoc_eo_post}, /* Eo */ {mdoc_xx_pre, NULL}, /* Fx */ {mdoc_no_pre, NULL}, /* Ms */ {mdoc_no_pre, NULL}, /* No */ {mdoc_ns_pre, NULL}, /* Ns */ {mdoc_xx_pre, NULL}, /* Nx */ {mdoc_xx_pre, NULL}, /* Ox */ {NULL, NULL}, /* Pc */ {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */ {mdoc_quote_pre, mdoc_quote_post}, /* Po */ {mdoc_quote_pre, mdoc_quote_post}, /* Pq */ {NULL, NULL}, /* Qc */ {mdoc_quote_pre, mdoc_quote_post}, /* Ql */ {mdoc_quote_pre, mdoc_quote_post}, /* Qo */ {mdoc_quote_pre, mdoc_quote_post}, /* Qq */ {NULL, NULL}, /* Re */ {mdoc_rs_pre, NULL}, /* Rs */ {NULL, NULL}, /* Sc */ {mdoc_quote_pre, mdoc_quote_post}, /* So */ {mdoc_quote_pre, mdoc_quote_post}, /* Sq */ {mdoc_sm_pre, NULL}, /* Sm */ {mdoc_sx_pre, NULL}, /* Sx */ {mdoc_sy_pre, NULL}, /* Sy */ {NULL, NULL}, /* Tn */ {mdoc_xx_pre, NULL}, /* Ux */ {NULL, NULL}, /* Xc */ {NULL, NULL}, /* Xo */ {mdoc_fo_pre, mdoc_fo_post}, /* Fo */ {NULL, NULL}, /* Fc */ {mdoc_quote_pre, mdoc_quote_post}, /* Oo */ {NULL, NULL}, /* Oc */ {mdoc_bk_pre, mdoc_bk_post}, /* Bk */ {NULL, NULL}, /* Ek */ {NULL, NULL}, /* Bt */ {NULL, NULL}, /* Hf */ {mdoc_em_pre, NULL}, /* Fr */ {NULL, NULL}, /* Ud */ {mdoc_lb_pre, NULL}, /* Lb */ {mdoc_abort_pre, NULL}, /* Lp */ {mdoc_lk_pre, NULL}, /* Lk */ {mdoc_mt_pre, NULL}, /* Mt */ {mdoc_quote_pre, mdoc_quote_post}, /* Brq */ {mdoc_quote_pre, mdoc_quote_post}, /* Bro */ {NULL, NULL}, /* Brc */ {mdoc__x_pre, mdoc__x_post}, /* %C */ {mdoc_skip_pre, NULL}, /* Es */ {mdoc_quote_pre, mdoc_quote_post}, /* En */ {mdoc_xx_pre, NULL}, /* Dx */ {mdoc__x_pre, mdoc__x_post}, /* %Q */ {mdoc__x_pre, mdoc__x_post}, /* %U */ {NULL, NULL}, /* Ta */ {mdoc_tg_pre, NULL}, /* Tg */ }; /* * See the same function in mdoc_term.c for documentation. */ static void synopsis_pre(struct html *h, struct roff_node *n) { struct roff_node *np; if ((n->flags & NODE_SYNPRETTY) == 0 || (np = roff_node_prev(n)) == NULL) return; if (np->tok == n->tok && MDOC_Fo != n->tok && MDOC_Ft != n->tok && MDOC_Fn != n->tok) { print_otag(h, TAG_BR, ""); return; } switch (np->tok) { case MDOC_Fd: case MDOC_Fn: case MDOC_Fo: case MDOC_In: case MDOC_Vt: break; case MDOC_Ft: if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) break; /* FALLTHROUGH */ default: print_otag(h, TAG_BR, ""); return; } html_close_paragraph(h); print_otag(h, TAG_P, "c", "Pp"); } void html_mdoc(void *arg, const struct roff_meta *mdoc) { struct html *h; struct roff_node *n; struct tag *t; h = (struct html *)arg; n = mdoc->first->child; if ((h->oflags & HTML_FRAGMENT) == 0) { print_gen_decls(h); print_otag(h, TAG_HTML, ""); if (n != NULL && n->type == ROFFT_COMMENT) print_gen_comment(h, n); t = print_otag(h, TAG_HEAD, ""); print_mdoc_head(mdoc, h); print_tagq(h, t); print_otag(h, TAG_BODY, ""); } mdoc_root_pre(mdoc, h); t = print_otag(h, TAG_DIV, "c", "manual-text"); print_mdoc_nodelist(mdoc, n, h); print_tagq(h, t); mdoc_root_post(mdoc, h); print_tagq(h, NULL); } static void print_mdoc_head(const struct roff_meta *meta, struct html *h) { char *cp; print_gen_head(h); if (meta->arch != NULL && meta->msec != NULL) mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title, meta->msec, meta->arch); else if (meta->msec != NULL) mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec); else if (meta->arch != NULL) mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch); else cp = mandoc_strdup(meta->title); print_otag(h, TAG_TITLE, ""); print_text(h, cp); free(cp); } static void print_mdoc_nodelist(MDOC_ARGS) { while (n != NULL) { print_mdoc_node(meta, n, h); n = n->next; } } static void print_mdoc_node(MDOC_ARGS) { struct tag *t; int child; if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) return; if ((n->flags & NODE_NOFILL) == 0) html_fillmode(h, ROFF_fi); else if (html_fillmode(h, ROFF_nf) == ROFF_nf && n->tok != ROFF_fi && n->flags & NODE_LINE) print_endline(h); child = 1; n->flags &= ~NODE_ENDED; switch (n->type) { case ROFFT_TEXT: if (n->flags & NODE_LINE) { switch (*n->string) { case '\0': h->col = 1; print_endline(h); return; case ' ': if ((h->flags & HTML_NONEWLINE) == 0 && (n->flags & NODE_NOFILL) == 0) print_otag(h, TAG_BR, ""); break; default: break; } } t = h->tag; t->refcnt++; if (n->flags & NODE_DELIMC) h->flags |= HTML_NOSPACE; if (n->flags & NODE_HREF) print_tagged_text(h, n->string, n); else print_text(h, n->string); if (n->flags & NODE_DELIMO) h->flags |= HTML_NOSPACE; break; case ROFFT_EQN: t = h->tag; t->refcnt++; print_eqn(h, n->eqn); break; case ROFFT_TBL: /* * This will take care of initialising all of the table * state data for the first table, then tearing it down * for the last one. */ print_tbl(h, n->span); return; default: /* * Close out the current table, if it's open, and unset * the "meta" table state. This will be reopened on the * next table element. */ if (h->tblt != NULL) print_tblclose(h); assert(h->tblt == NULL); t = h->tag; t->refcnt++; if (n->tok < ROFF_MAX) { roff_html_pre(h, n); t->refcnt--; print_stagq(h, t); return; } assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); if (mdoc_html_acts[n->tok - MDOC_Dd].pre != NULL && (n->end == ENDBODY_NOT || n->child != NULL)) child = (*mdoc_html_acts[n->tok - MDOC_Dd].pre)(meta, n, h); break; } if (h->flags & HTML_KEEP && n->flags & NODE_LINE) { h->flags &= ~HTML_KEEP; h->flags |= HTML_PREKEEP; } if (child && n->child != NULL) print_mdoc_nodelist(meta, n->child, h); t->refcnt--; print_stagq(h, t); switch (n->type) { case ROFFT_TEXT: case ROFFT_EQN: break; default: if (mdoc_html_acts[n->tok - MDOC_Dd].post == NULL || n->flags & NODE_ENDED) break; (*mdoc_html_acts[n->tok - MDOC_Dd].post)(meta, n, h); if (n->end != ENDBODY_NOT) n->body->flags |= NODE_ENDED; break; } } static void mdoc_root_post(const struct roff_meta *meta, struct html *h) { struct tag *t, *tt; t = print_otag(h, TAG_TABLE, "c", "foot"); tt = print_otag(h, TAG_TR, ""); print_otag(h, TAG_TD, "c", "foot-date"); print_text(h, meta->date); print_stagq(h, tt); print_otag(h, TAG_TD, "c", "foot-os"); print_text(h, meta->os); print_tagq(h, t); } static int mdoc_root_pre(const struct roff_meta *meta, struct html *h) { struct tag *t, *tt; char *volume, *title; if (NULL == meta->arch) volume = mandoc_strdup(meta->vol); else mandoc_asprintf(&volume, "%s (%s)", meta->vol, meta->arch); if (NULL == meta->msec) title = mandoc_strdup(meta->title); else mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); t = print_otag(h, TAG_TABLE, "c", "head"); tt = print_otag(h, TAG_TR, ""); print_otag(h, TAG_TD, "c", "head-ltitle"); print_text(h, title); print_stagq(h, tt); print_otag(h, TAG_TD, "c", "head-vol"); print_text(h, volume); print_stagq(h, tt); print_otag(h, TAG_TD, "c", "head-rtitle"); print_text(h, title); print_tagq(h, t); free(title); free(volume); return 1; } static int mdoc_code_pre(MDOC_ARGS) { print_otag_id(h, TAG_CODE, roff_name[n->tok], n); return 1; } static int mdoc_sh_pre(MDOC_ARGS) { struct roff_node *sn, *subn; struct tag *t, *tsec, *tsub; char *id; int sc; switch (n->type) { case ROFFT_BLOCK: html_close_paragraph(h); if ((h->oflags & HTML_TOC) == 0 || h->flags & HTML_TOCDONE || n->sec <= SEC_SYNOPSIS) { print_otag(h, TAG_SECTION, "c", "Sh"); break; } h->flags |= HTML_TOCDONE; sc = 0; for (sn = n->next; sn != NULL; sn = sn->next) if (sn->sec == SEC_CUSTOM) if (++sc == 2) break; if (sc < 2) break; t = print_otag(h, TAG_H1, "c", "Sh"); print_text(h, "TABLE OF CONTENTS"); print_tagq(h, t); t = print_otag(h, TAG_UL, "c", "Bl-compact"); for (sn = n; sn != NULL; sn = sn->next) { tsec = print_otag(h, TAG_LI, ""); id = html_make_id(sn->head, 0); tsub = print_otag(h, TAG_A, "hR", id); free(id); print_mdoc_nodelist(meta, sn->head->child, h); print_tagq(h, tsub); tsub = NULL; for (subn = sn->body->child; subn != NULL; subn = subn->next) { if (subn->tok != MDOC_Ss) continue; id = html_make_id(subn->head, 0); if (id == NULL) continue; if (tsub == NULL) print_otag(h, TAG_UL, "c", "Bl-compact"); tsub = print_otag(h, TAG_LI, ""); print_otag(h, TAG_A, "hR", id); free(id); print_mdoc_nodelist(meta, subn->head->child, h); print_tagq(h, tsub); } print_tagq(h, tsec); } print_tagq(h, t); print_otag(h, TAG_SECTION, "c", "Sh"); break; case ROFFT_HEAD: print_otag_id(h, TAG_H1, "Sh", n); break; case ROFFT_BODY: if (n->sec == SEC_AUTHORS) h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT); break; default: break; } return 1; } static int mdoc_ss_pre(MDOC_ARGS) { switch (n->type) { case ROFFT_BLOCK: html_close_paragraph(h); print_otag(h, TAG_SECTION, "c", "Ss"); break; case ROFFT_HEAD: print_otag_id(h, TAG_H2, "Ss", n); break; case ROFFT_BODY: break; default: abort(); } return 1; } static int mdoc_fl_pre(MDOC_ARGS) { struct roff_node *nn; print_otag_id(h, TAG_CODE, "Fl", n); print_text(h, "\\-"); if (n->child != NULL || ((nn = roff_node_next(n)) != NULL && nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)) h->flags |= HTML_NOSPACE; return 1; } static int mdoc_nd_pre(MDOC_ARGS) { switch (n->type) { case ROFFT_BLOCK: return 1; case ROFFT_HEAD: return 0; case ROFFT_BODY: break; default: abort(); } print_text(h, "\\(em"); print_otag(h, TAG_SPAN, "c", "Nd"); return 1; } static int mdoc_nm_pre(MDOC_ARGS) { switch (n->type) { case ROFFT_BLOCK: break; case ROFFT_HEAD: print_otag(h, TAG_TD, ""); /* FALLTHROUGH */ case ROFFT_ELEM: print_otag(h, TAG_CODE, "c", "Nm"); return 1; case ROFFT_BODY: print_otag(h, TAG_TD, ""); return 1; default: abort(); } html_close_paragraph(h); synopsis_pre(h, n); print_otag(h, TAG_TABLE, "c", "Nm"); print_otag(h, TAG_TR, ""); return 1; } static int mdoc_xr_pre(MDOC_ARGS) { if (NULL == n->child) return 0; if (h->base_man1) print_otag(h, TAG_A, "chM", "Xr", n->child->string, n->child->next == NULL ? NULL : n->child->next->string); else print_otag(h, TAG_A, "c", "Xr"); n = n->child; print_text(h, n->string); if (NULL == (n = n->next)) return 0; h->flags |= HTML_NOSPACE; print_text(h, "("); h->flags |= HTML_NOSPACE; print_text(h, n->string); h->flags |= HTML_NOSPACE; print_text(h, ")"); return 0; } static int mdoc_tg_pre(MDOC_ARGS) { char *id; if ((id = html_make_id(n, 1)) != NULL) { print_tagq(h, print_otag(h, TAG_MARK, "i", id)); free(id); } return 0; } static int mdoc_ns_pre(MDOC_ARGS) { if ( ! (NODE_LINE & n->flags)) h->flags |= HTML_NOSPACE; return 1; } static int mdoc_ar_pre(MDOC_ARGS) { print_otag(h, TAG_VAR, "c", "Ar"); return 1; } static int mdoc_xx_pre(MDOC_ARGS) { print_otag(h, TAG_SPAN, "c", "Ux"); return 1; } static int mdoc_it_pre(MDOC_ARGS) { const struct roff_node *bl; enum mdoc_list type; bl = n->parent; while (bl->tok != MDOC_Bl) bl = bl->parent; type = bl->norm->Bl.type; switch (type) { case LIST_bullet: case LIST_dash: case LIST_hyphen: case LIST_item: case LIST_enum: switch (n->type) { case ROFFT_HEAD: return 0; case ROFFT_BODY: print_otag_id(h, TAG_LI, NULL, n); break; default: break; } break; case LIST_diag: case LIST_hang: case LIST_inset: case LIST_ohang: switch (n->type) { case ROFFT_HEAD: print_otag_id(h, TAG_DT, NULL, n); break; case ROFFT_BODY: print_otag(h, TAG_DD, ""); break; default: break; } break; case LIST_tag: switch (n->type) { case ROFFT_HEAD: print_otag_id(h, TAG_DT, NULL, n); break; case ROFFT_BODY: if (n->child == NULL) { print_otag(h, TAG_DD, "s", "width", "auto"); print_text(h, "\\ "); } else print_otag(h, TAG_DD, ""); break; default: break; } break; case LIST_column: switch (n->type) { case ROFFT_HEAD: break; case ROFFT_BODY: print_otag(h, TAG_TD, ""); break; default: print_otag_id(h, TAG_TR, NULL, n); } default: break; } return 1; } static int mdoc_bl_pre(MDOC_ARGS) { char cattr[32]; struct mdoc_bl *bl; enum htmltag elemtype; switch (n->type) { case ROFFT_BLOCK: html_close_paragraph(h); break; case ROFFT_HEAD: return 0; case ROFFT_BODY: return 1; default: abort(); } bl = &n->norm->Bl; switch (bl->type) { case LIST_bullet: elemtype = TAG_UL; (void)strlcpy(cattr, "Bl-bullet", sizeof(cattr)); break; case LIST_dash: case LIST_hyphen: elemtype = TAG_UL; (void)strlcpy(cattr, "Bl-dash", sizeof(cattr)); break; case LIST_item: elemtype = TAG_UL; (void)strlcpy(cattr, "Bl-item", sizeof(cattr)); break; case LIST_enum: elemtype = TAG_OL; (void)strlcpy(cattr, "Bl-enum", sizeof(cattr)); break; case LIST_diag: elemtype = TAG_DL; (void)strlcpy(cattr, "Bl-diag", sizeof(cattr)); break; case LIST_hang: elemtype = TAG_DL; (void)strlcpy(cattr, "Bl-hang", sizeof(cattr)); break; case LIST_inset: elemtype = TAG_DL; (void)strlcpy(cattr, "Bl-inset", sizeof(cattr)); break; case LIST_ohang: elemtype = TAG_DL; (void)strlcpy(cattr, "Bl-ohang", sizeof(cattr)); break; case LIST_tag: if (bl->offs) print_otag(h, TAG_DIV, "c", "Bd-indent"); print_otag_id(h, TAG_DL, bl->comp ? "Bl-tag Bl-compact" : "Bl-tag", n->body); return 1; case LIST_column: elemtype = TAG_TABLE; (void)strlcpy(cattr, "Bl-column", sizeof(cattr)); break; default: abort(); } if (bl->offs != NULL) (void)strlcat(cattr, " Bd-indent", sizeof(cattr)); if (bl->comp) (void)strlcat(cattr, " Bl-compact", sizeof(cattr)); print_otag_id(h, elemtype, cattr, n->body); return 1; } static int mdoc_ex_pre(MDOC_ARGS) { if (roff_node_prev(n) != NULL) print_otag(h, TAG_BR, ""); return 1; } static int mdoc_st_pre(MDOC_ARGS) { print_otag(h, TAG_SPAN, "c", "St"); return 1; } static int mdoc_em_pre(MDOC_ARGS) { print_otag_id(h, TAG_I, "Em", n); return 1; } static int mdoc_d1_pre(MDOC_ARGS) { switch (n->type) { case ROFFT_BLOCK: html_close_paragraph(h); return 1; case ROFFT_HEAD: return 0; case ROFFT_BODY: break; default: abort(); } print_otag_id(h, TAG_DIV, "Bd Bd-indent", n); if (n->tok == MDOC_Dl) print_otag(h, TAG_CODE, "c", "Li"); return 1; } static int mdoc_sx_pre(MDOC_ARGS) { char *id; id = html_make_id(n, 0); print_otag(h, TAG_A, "chR", "Sx", id); free(id); return 1; } static int mdoc_bd_pre(MDOC_ARGS) { char buf[20]; struct roff_node *nn; int comp; switch (n->type) { case ROFFT_BLOCK: html_close_paragraph(h); return 1; case ROFFT_HEAD: return 0; case ROFFT_BODY: break; default: abort(); } /* Handle preceding whitespace. */ comp = n->norm->Bd.comp; for (nn = n; nn != NULL && comp == 0; nn = nn->parent) { if (nn->type != ROFFT_BLOCK) continue; if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss) comp = 1; if (roff_node_prev(nn) != NULL) break; } (void)strlcpy(buf, "Bd", sizeof(buf)); if (comp == 0) (void)strlcat(buf, " Pp", sizeof(buf)); /* Handle the -offset argument. */ if (n->norm->Bd.offs != NULL && strcmp(n->norm->Bd.offs, "left") != 0) (void)strlcat(buf, " Bd-indent", sizeof(buf)); if (n->norm->Bd.type == DISP_literal) (void)strlcat(buf, " Li", sizeof(buf)); print_otag_id(h, TAG_DIV, buf, n); return 1; } static int mdoc_pa_pre(MDOC_ARGS) { print_otag(h, TAG_SPAN, "c", "Pa"); return 1; } static int mdoc_ad_pre(MDOC_ARGS) { print_otag(h, TAG_SPAN, "c", "Ad"); return 1; } static int mdoc_an_pre(MDOC_ARGS) { if (n->norm->An.auth == AUTH_split) { h->flags &= ~HTML_NOSPLIT; h->flags |= HTML_SPLIT; return 0; } if (n->norm->An.auth == AUTH_nosplit) { h->flags &= ~HTML_SPLIT; h->flags |= HTML_NOSPLIT; return 0; } if (h->flags & HTML_SPLIT) print_otag(h, TAG_BR, ""); if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT)) h->flags |= HTML_SPLIT; print_otag(h, TAG_SPAN, "c", "An"); return 1; } static int mdoc_cd_pre(MDOC_ARGS) { synopsis_pre(h, n); print_otag(h, TAG_CODE, "c", "Cd"); return 1; } static int mdoc_fa_pre(MDOC_ARGS) { const struct roff_node *nn; struct tag *t; if (n->parent->tok != MDOC_Fo) { print_otag(h, TAG_VAR, "c", "Fa"); return 1; } for (nn = n->child; nn != NULL; nn = nn->next) { t = print_otag(h, TAG_VAR, "c", "Fa"); print_text(h, nn->string); print_tagq(h, t); if (nn->next != NULL) { h->flags |= HTML_NOSPACE; print_text(h, ","); } } if (n->child != NULL && (nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa) { h->flags |= HTML_NOSPACE; print_text(h, ","); } return 0; } static int mdoc_fd_pre(MDOC_ARGS) { struct tag *t; char *buf, *cp; synopsis_pre(h, n); if (NULL == (n = n->child)) return 0; assert(n->type == ROFFT_TEXT); if (strcmp(n->string, "#include")) { print_otag(h, TAG_CODE, "c", "Fd"); return 1; } print_otag(h, TAG_CODE, "c", "In"); print_text(h, n->string); if (NULL != (n = n->next)) { assert(n->type == ROFFT_TEXT); if (h->base_includes) { cp = n->string; if (*cp == '<' || *cp == '"') cp++; buf = mandoc_strdup(cp); cp = strchr(buf, '\0') - 1; if (cp >= buf && (*cp == '>' || *cp == '"')) *cp = '\0'; t = print_otag(h, TAG_A, "chI", "In", buf); free(buf); } else t = print_otag(h, TAG_A, "c", "In"); print_text(h, n->string); print_tagq(h, t); n = n->next; } for ( ; n; n = n->next) { assert(n->type == ROFFT_TEXT); print_text(h, n->string); } return 0; } static int mdoc_vt_pre(MDOC_ARGS) { if (n->type == ROFFT_BLOCK) { synopsis_pre(h, n); return 1; } else if (n->type == ROFFT_ELEM) { synopsis_pre(h, n); } else if (n->type == ROFFT_HEAD) return 0; print_otag(h, TAG_VAR, "c", "Vt"); return 1; } static int mdoc_ft_pre(MDOC_ARGS) { synopsis_pre(h, n); print_otag(h, TAG_VAR, "c", "Ft"); return 1; } static int mdoc_fn_pre(MDOC_ARGS) { struct tag *t; char nbuf[BUFSIZ]; const char *sp, *ep; int sz, pretty; pretty = NODE_SYNPRETTY & n->flags; synopsis_pre(h, n); /* Split apart into type and name. */ assert(n->child->string); sp = n->child->string; ep = strchr(sp, ' '); if (NULL != ep) { t = print_otag(h, TAG_VAR, "c", "Ft"); while (ep) { sz = MIN((int)(ep - sp), BUFSIZ - 1); (void)memcpy(nbuf, sp, (size_t)sz); nbuf[sz] = '\0'; print_text(h, nbuf); sp = ++ep; ep = strchr(sp, ' '); } print_tagq(h, t); } t = print_otag_id(h, TAG_CODE, "Fn", n); if (sp) print_text(h, sp); print_tagq(h, t); h->flags |= HTML_NOSPACE; print_text(h, "("); h->flags |= HTML_NOSPACE; for (n = n->child->next; n; n = n->next) { if (NODE_SYNPRETTY & n->flags) t = print_otag(h, TAG_VAR, "cs", "Fa", "white-space", "nowrap"); else t = print_otag(h, TAG_VAR, "c", "Fa"); print_text(h, n->string); print_tagq(h, t); if (n->next) { h->flags |= HTML_NOSPACE; print_text(h, ","); } } h->flags |= HTML_NOSPACE; print_text(h, ")"); if (pretty) { h->flags |= HTML_NOSPACE; print_text(h, ";"); } return 0; } static int mdoc_sm_pre(MDOC_ARGS) { if (NULL == n->child) h->flags ^= HTML_NONOSPACE; else if (0 == strcmp("on", n->child->string)) h->flags &= ~HTML_NONOSPACE; else h->flags |= HTML_NONOSPACE; if ( ! (HTML_NONOSPACE & h->flags)) h->flags &= ~HTML_NOSPACE; return 0; } static int mdoc_skip_pre(MDOC_ARGS) { return 0; } static int mdoc_pp_pre(MDOC_ARGS) { char *id; if (n->flags & NODE_NOFILL) { print_endline(h); if (n->flags & NODE_ID) mdoc_tg_pre(meta, n, h); else { h->col = 1; print_endline(h); } } else { html_close_paragraph(h); id = n->flags & NODE_ID ? html_make_id(n, 1) : NULL; print_otag(h, TAG_P, "ci", "Pp", id); free(id); } return 0; } static int mdoc_lk_pre(MDOC_ARGS) { const struct roff_node *link, *descr, *punct; struct tag *t; if ((link = n->child) == NULL) return 0; /* Find beginning of trailing punctuation. */ punct = n->last; while (punct != link && punct->flags & NODE_DELIMC) punct = punct->prev; punct = punct->next; /* Link target and link text. */ descr = link->next; if (descr == punct) descr = link; /* no text */ t = print_otag(h, TAG_A, "ch", "Lk", link->string); do { if (descr->flags & (NODE_DELIMC | NODE_DELIMO)) h->flags |= HTML_NOSPACE; print_text(h, descr->string); descr = descr->next; } while (descr != punct); print_tagq(h, t); /* Trailing punctuation. */ while (punct != NULL) { h->flags |= HTML_NOSPACE; print_text(h, punct->string); punct = punct->next; } return 0; } static int mdoc_mt_pre(MDOC_ARGS) { struct tag *t; char *cp; for (n = n->child; n; n = n->next) { assert(n->type == ROFFT_TEXT); mandoc_asprintf(&cp, "mailto:%s", n->string); t = print_otag(h, TAG_A, "ch", "Mt", cp); print_text(h, n->string); print_tagq(h, t); free(cp); } return 0; } static int mdoc_fo_pre(MDOC_ARGS) { struct tag *t; switch (n->type) { case ROFFT_BLOCK: synopsis_pre(h, n); return 1; case ROFFT_HEAD: if (n->child != NULL) { t = print_otag_id(h, TAG_CODE, "Fn", n); print_text(h, n->child->string); print_tagq(h, t); } return 0; case ROFFT_BODY: h->flags |= HTML_NOSPACE; print_text(h, "("); h->flags |= HTML_NOSPACE; return 1; default: abort(); } } static void mdoc_fo_post(MDOC_ARGS) { if (n->type != ROFFT_BODY) return; h->flags |= HTML_NOSPACE; print_text(h, ")"); h->flags |= HTML_NOSPACE; print_text(h, ";"); } static int mdoc_in_pre(MDOC_ARGS) { struct tag *t; synopsis_pre(h, n); print_otag(h, TAG_CODE, "c", "In"); /* * The first argument of the `In' gets special treatment as * being a linked value. Subsequent values are printed * afterward. groff does similarly. This also handles the case * of no children. */ if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags) print_text(h, "#include"); print_text(h, "<"); h->flags |= HTML_NOSPACE; if (NULL != (n = n->child)) { assert(n->type == ROFFT_TEXT); if (h->base_includes) t = print_otag(h, TAG_A, "chI", "In", n->string); else t = print_otag(h, TAG_A, "c", "In"); print_text(h, n->string); print_tagq(h, t); n = n->next; } h->flags |= HTML_NOSPACE; print_text(h, ">"); for ( ; n; n = n->next) { assert(n->type == ROFFT_TEXT); print_text(h, n->string); } return 0; } static int mdoc_va_pre(MDOC_ARGS) { print_otag(h, TAG_VAR, "c", "Va"); return 1; } static int mdoc_ap_pre(MDOC_ARGS) { h->flags |= HTML_NOSPACE; print_text(h, "\\(aq"); h->flags |= HTML_NOSPACE; return 1; } static int mdoc_bf_pre(MDOC_ARGS) { const char *cattr; switch (n->type) { case ROFFT_BLOCK: html_close_paragraph(h); return 1; case ROFFT_HEAD: return 0; case ROFFT_BODY: break; default: abort(); } if (FONT_Em == n->norm->Bf.font) cattr = "Bf Em"; else if (FONT_Sy == n->norm->Bf.font) cattr = "Bf Sy"; else if (FONT_Li == n->norm->Bf.font) cattr = "Bf Li"; else cattr = "Bf No"; /* Cannot use TAG_SPAN because it may contain blocks. */ print_otag(h, TAG_DIV, "c", cattr); return 1; } static int mdoc_igndelim_pre(MDOC_ARGS) { h->flags |= HTML_IGNDELIM; return 1; } static void mdoc_pf_post(MDOC_ARGS) { if ( ! (n->next == NULL || n->next->flags & NODE_LINE)) h->flags |= HTML_NOSPACE; } static int mdoc_rs_pre(MDOC_ARGS) { switch (n->type) { case ROFFT_BLOCK: if (n->sec == SEC_SEE_ALSO) html_close_paragraph(h); break; case ROFFT_HEAD: return 0; case ROFFT_BODY: if (n->sec == SEC_SEE_ALSO) print_otag(h, TAG_P, "c", "Pp"); print_otag(h, TAG_CITE, "c", "Rs"); break; default: abort(); } return 1; } static int mdoc_no_pre(MDOC_ARGS) { print_otag_id(h, TAG_SPAN, roff_name[n->tok], n); return 1; } static int mdoc_sy_pre(MDOC_ARGS) { print_otag_id(h, TAG_B, "Sy", n); return 1; } static int mdoc_lb_pre(MDOC_ARGS) { if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE && roff_node_prev(n) != NULL) print_otag(h, TAG_BR, ""); print_otag(h, TAG_SPAN, "c", "Lb"); return 1; } static int mdoc__x_pre(MDOC_ARGS) { struct roff_node *nn; const char *cattr; enum htmltag t; t = TAG_SPAN; switch (n->tok) { case MDOC__A: cattr = "RsA"; if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A && ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A)) print_text(h, "and"); break; case MDOC__B: t = TAG_I; cattr = "RsB"; break; case MDOC__C: cattr = "RsC"; break; case MDOC__D: cattr = "RsD"; break; case MDOC__I: t = TAG_I; cattr = "RsI"; break; case MDOC__J: t = TAG_I; cattr = "RsJ"; break; case MDOC__N: cattr = "RsN"; break; case MDOC__O: cattr = "RsO"; break; case MDOC__P: cattr = "RsP"; break; case MDOC__Q: cattr = "RsQ"; break; case MDOC__R: cattr = "RsR"; break; case MDOC__T: cattr = "RsT"; break; case MDOC__U: print_otag(h, TAG_A, "ch", "RsU", n->child->string); return 1; case MDOC__V: cattr = "RsV"; break; default: abort(); } print_otag(h, t, "c", cattr); return 1; } static void mdoc__x_post(MDOC_ARGS) { struct roff_node *nn; if (n->tok == MDOC__A && (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A && ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) && ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A)) return; /* TODO: %U */ if (n->parent == NULL || n->parent->tok != MDOC_Rs) return; h->flags |= HTML_NOSPACE; print_text(h, roff_node_next(n) ? "," : "."); } static int mdoc_bk_pre(MDOC_ARGS) { switch (n->type) { case ROFFT_BLOCK: break; case ROFFT_HEAD: return 0; case ROFFT_BODY: if (n->parent->args != NULL || n->prev->child == NULL) h->flags |= HTML_PREKEEP; break; default: abort(); } return 1; } static void mdoc_bk_post(MDOC_ARGS) { if (n->type == ROFFT_BODY) h->flags &= ~(HTML_KEEP | HTML_PREKEEP); } static int mdoc_quote_pre(MDOC_ARGS) { if (n->type != ROFFT_BODY) return 1; switch (n->tok) { case MDOC_Ao: case MDOC_Aq: print_text(h, n->child != NULL && n->child->next == NULL && n->child->tok == MDOC_Mt ? "<" : "\\(la"); break; case MDOC_Bro: case MDOC_Brq: print_text(h, "\\(lC"); break; case MDOC_Bo: case MDOC_Bq: print_text(h, "\\(lB"); break; case MDOC_Oo: case MDOC_Op: print_text(h, "\\(lB"); /* * Give up on semantic markup for now. * We cannot use TAG_SPAN because .Oo may contain blocks. * We cannot use TAG_DIV because we might be in a * phrasing context (like .Dl or .Pp); we cannot * close out a .Pp at this point either because * that would break the line. */ /* XXX print_otag(h, TAG_???, "c", "Op"); */ break; case MDOC_En: if (NULL == n->norm->Es || NULL == n->norm->Es->child) return 1; print_text(h, n->norm->Es->child->string); break; case MDOC_Do: case MDOC_Dq: print_text(h, "\\(lq"); break; case MDOC_Qo: case MDOC_Qq: print_text(h, "\""); break; case MDOC_Po: case MDOC_Pq: print_text(h, "("); break; case MDOC_Ql: print_text(h, "\\(oq"); h->flags |= HTML_NOSPACE; print_otag(h, TAG_CODE, "c", "Li"); break; case MDOC_So: case MDOC_Sq: print_text(h, "\\(oq"); break; default: abort(); } h->flags |= HTML_NOSPACE; return 1; } static void mdoc_quote_post(MDOC_ARGS) { if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) return; h->flags |= HTML_NOSPACE; switch (n->tok) { case MDOC_Ao: case MDOC_Aq: print_text(h, n->child != NULL && n->child->next == NULL && n->child->tok == MDOC_Mt ? ">" : "\\(ra"); break; case MDOC_Bro: case MDOC_Brq: print_text(h, "\\(rC"); break; case MDOC_Oo: case MDOC_Op: case MDOC_Bo: case MDOC_Bq: print_text(h, "\\(rB"); break; case MDOC_En: if (n->norm->Es == NULL || n->norm->Es->child == NULL || n->norm->Es->child->next == NULL) h->flags &= ~HTML_NOSPACE; else print_text(h, n->norm->Es->child->next->string); break; case MDOC_Do: case MDOC_Dq: print_text(h, "\\(rq"); break; case MDOC_Qo: case MDOC_Qq: print_text(h, "\""); break; case MDOC_Po: case MDOC_Pq: print_text(h, ")"); break; case MDOC_Ql: case MDOC_So: case MDOC_Sq: print_text(h, "\\(cq"); break; default: abort(); } } static int mdoc_eo_pre(MDOC_ARGS) { if (n->type != ROFFT_BODY) return 1; if (n->end == ENDBODY_NOT && n->parent->head->child == NULL && n->child != NULL && n->child->end != ENDBODY_NOT) print_text(h, "\\&"); else if (n->end != ENDBODY_NOT ? n->child != NULL : n->parent->head->child != NULL && (n->child != NULL || (n->parent->tail != NULL && n->parent->tail->child != NULL))) h->flags |= HTML_NOSPACE; return 1; } static void mdoc_eo_post(MDOC_ARGS) { int body, tail; if (n->type != ROFFT_BODY) return; if (n->end != ENDBODY_NOT) { h->flags &= ~HTML_NOSPACE; return; } body = n->child != NULL || n->parent->head->child != NULL; tail = n->parent->tail != NULL && n->parent->tail->child != NULL; if (body && tail) h->flags |= HTML_NOSPACE; else if ( ! tail) h->flags &= ~HTML_NOSPACE; } static int mdoc_abort_pre(MDOC_ARGS) { abort(); } /* $Id: mdoc_macro.c,v 1.234 2020/01/19 18:02:00 schwarze Exp $ */ /* * Copyright (c) 2008-2012 Kristaps Dzonsons * Copyright (c) 2010, 2012-2020 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 "roff.h" #include "mdoc.h" #include "libmandoc.h" #include "roff_int.h" #include "libmdoc.h" static void blk_full(MACRO_PROT_ARGS); static void blk_exp_close(MACRO_PROT_ARGS); static void blk_part_exp(MACRO_PROT_ARGS); static void blk_part_imp(MACRO_PROT_ARGS); static void ctx_synopsis(MACRO_PROT_ARGS); static void in_line_eoln(MACRO_PROT_ARGS); static void in_line_argn(MACRO_PROT_ARGS); static void in_line(MACRO_PROT_ARGS); static void phrase_ta(MACRO_PROT_ARGS); static void append_delims(struct roff_man *, int, int *, char *); static void dword(struct roff_man *, int, int, const char *, enum mdelim, int); static int find_pending(struct roff_man *, enum roff_tok, int, int, struct roff_node *); static int lookup(struct roff_man *, int, int, int, const char *); static int macro_or_word(MACRO_PROT_ARGS, char *, int); static void break_intermediate(struct roff_node *, struct roff_node *); static int parse_rest(struct roff_man *, enum roff_tok, int, int *, char *); static enum roff_tok rew_alt(enum roff_tok); static void rew_elem(struct roff_man *, enum roff_tok); static void rew_last(struct roff_man *, const struct roff_node *); static void rew_pending(struct roff_man *, const struct roff_node *); static const struct mdoc_macro mdoc_macros[MDOC_MAX - MDOC_Dd] = { { in_line_eoln, MDOC_PROLOGUE | MDOC_JOIN }, /* Dd */ { in_line_eoln, MDOC_PROLOGUE }, /* Dt */ { in_line_eoln, MDOC_PROLOGUE }, /* Os */ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* Sh */ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* Ss */ { in_line_eoln, 0 }, /* Pp */ { blk_part_imp, MDOC_PARSED | MDOC_JOIN }, /* D1 */ { blk_part_imp, MDOC_PARSED | MDOC_JOIN }, /* Dl */ { blk_full, MDOC_EXPLICIT }, /* Bd */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ed */ { blk_full, MDOC_EXPLICIT }, /* Bl */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* El */ { blk_full, MDOC_PARSED | MDOC_JOIN }, /* It */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* An */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM | MDOC_JOIN }, /* Ap */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Cd */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Er */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ev */ { in_line_eoln, 0 }, /* Ex */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fa */ { in_line_eoln, 0 }, /* Fd */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fl */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fn */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ft */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ic */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Li */ { blk_full, MDOC_JOIN }, /* Nd */ { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Op */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ot */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Pa */ { in_line_eoln, 0 }, /* Rv */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Va */ { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */ { in_line_eoln, MDOC_JOIN }, /* %A */ { in_line_eoln, MDOC_JOIN }, /* %B */ { in_line_eoln, MDOC_JOIN }, /* %D */ { in_line_eoln, MDOC_JOIN }, /* %I */ { in_line_eoln, MDOC_JOIN }, /* %J */ { in_line_eoln, 0 }, /* %N */ { in_line_eoln, MDOC_JOIN }, /* %O */ { in_line_eoln, 0 }, /* %P */ { in_line_eoln, MDOC_JOIN }, /* %R */ { in_line_eoln, MDOC_JOIN }, /* %T */ { in_line_eoln, 0 }, /* %V */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Ac */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Ao */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Aq */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* At */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Bc */ { blk_full, MDOC_EXPLICIT }, /* Bf */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Bo */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Bq */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bsx */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bx */ { in_line_eoln, 0 }, /* Db */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Dc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Do */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Dq */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ec */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ef */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Em */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Fx */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ms */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* No */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM | MDOC_JOIN }, /* Ns */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ox */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Pc */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Pf */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Po */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Pq */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Qc */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ql */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Qo */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Qq */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Re */ { blk_full, MDOC_EXPLICIT }, /* Rs */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Sc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* So */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sq */ { in_line_argn, 0 }, /* Sm */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sx */ { in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sy */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ux */ { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Xc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Xo */ { blk_full, MDOC_EXPLICIT | MDOC_CALLABLE }, /* Fo */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Fc */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Oo */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Oc */ { blk_full, MDOC_EXPLICIT }, /* Bk */ { blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ek */ { in_line_eoln, 0 }, /* Bt */ { in_line_eoln, 0 }, /* Hf */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fr */ { in_line_eoln, 0 }, /* Ud */ { in_line, 0 }, /* Lb */ { in_line_eoln, 0 }, /* Lp */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Lk */ { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Mt */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Brq */ { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Bro */ { blk_exp_close, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT | MDOC_JOIN }, /* Brc */ { in_line_eoln, MDOC_JOIN }, /* %C */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Es */ { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* En */ { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */ { in_line_eoln, MDOC_JOIN }, /* %Q */ { in_line_eoln, 0 }, /* %U */ { phrase_ta, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ta */ { in_line_eoln, 0 }, /* Tg */ }; const struct mdoc_macro * mdoc_macro(enum roff_tok tok) { assert(tok >= MDOC_Dd && tok < MDOC_MAX); return mdoc_macros + (tok - MDOC_Dd); } /* * This is called at the end of parsing. It must traverse up the tree, * closing out open [implicit] scopes. Obviously, open explicit scopes * are errors. */ void mdoc_endparse(struct roff_man *mdoc) { struct roff_node *n; /* Scan for open explicit scopes. */ n = mdoc->last->flags & NODE_VALID ? mdoc->last->parent : mdoc->last; for ( ; n; n = n->parent) if (n->type == ROFFT_BLOCK && mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) mandoc_msg(MANDOCERR_BLK_NOEND, n->line, n->pos, "%s", roff_name[n->tok]); /* Rewind to the first. */ rew_last(mdoc, mdoc->meta.first); } /* * Look up the macro at *p called by "from", * or as a line macro if from == TOKEN_NONE. */ static int lookup(struct roff_man *mdoc, int from, int line, int ppos, const char *p) { enum roff_tok res; if (mdoc->flags & MDOC_PHRASEQF) { mdoc->flags &= ~MDOC_PHRASEQF; return TOKEN_NONE; } if (from == TOKEN_NONE || mdoc_macro(from)->flags & MDOC_PARSED) { res = roffhash_find(mdoc->mdocmac, p, 0); if (res != TOKEN_NONE) { if (mdoc_macro(res)->flags & MDOC_CALLABLE) return res; mandoc_msg(MANDOCERR_MACRO_CALL, line, ppos, "%s", p); } } return TOKEN_NONE; } /* * Rewind up to and including a specific node. */ static void rew_last(struct roff_man *mdoc, const struct roff_node *to) { if (to->flags & NODE_VALID) return; while (mdoc->last != to) { mdoc_state(mdoc, mdoc->last); mdoc->last->flags |= NODE_VALID | NODE_ENDED; mdoc->last = mdoc->last->parent; } mdoc_state(mdoc, mdoc->last); mdoc->last->flags |= NODE_VALID | NODE_ENDED; mdoc->next = ROFF_NEXT_SIBLING; } /* * Rewind up to a specific block, including all blocks that broke it. */ static void rew_pending(struct roff_man *mdoc, const struct roff_node *n) { for (;;) { rew_last(mdoc, n); if (mdoc->last == n) { switch (n->type) { case ROFFT_HEAD: roff_body_alloc(mdoc, n->line, n->pos, n->tok); if (n->tok == MDOC_Ss) mdoc->flags &= ~ROFF_NONOFILL; break; case ROFFT_BLOCK: break; default: return; } if ( ! (n->flags & NODE_BROKEN)) return; } else n = mdoc->last; for (;;) { if ((n = n->parent) == NULL) return; if (n->type == ROFFT_BLOCK || n->type == ROFFT_HEAD) { if (n->flags & NODE_ENDED) break; else return; } } } } /* * For a block closing macro, return the corresponding opening one. * Otherwise, return the macro itself. */ static enum roff_tok rew_alt(enum roff_tok tok) { switch (tok) { case MDOC_Ac: return MDOC_Ao; case MDOC_Bc: return MDOC_Bo; case MDOC_Brc: return MDOC_Bro; case MDOC_Dc: return MDOC_Do; case MDOC_Ec: return MDOC_Eo; case MDOC_Ed: return MDOC_Bd; case MDOC_Ef: return MDOC_Bf; case MDOC_Ek: return MDOC_Bk; case MDOC_El: return MDOC_Bl; case MDOC_Fc: return MDOC_Fo; case MDOC_Oc: return MDOC_Oo; case MDOC_Pc: return MDOC_Po; case MDOC_Qc: return MDOC_Qo; case MDOC_Re: return MDOC_Rs; case MDOC_Sc: return MDOC_So; case MDOC_Xc: return MDOC_Xo; default: return tok; } } static void rew_elem(struct roff_man *mdoc, enum roff_tok tok) { struct roff_node *n; n = mdoc->last; if (n->type != ROFFT_ELEM) n = n->parent; assert(n->type == ROFFT_ELEM); assert(tok == n->tok); rew_last(mdoc, n); } static void break_intermediate(struct roff_node *n, struct roff_node *breaker) { if (n != breaker && n->type != ROFFT_BLOCK && n->type != ROFFT_HEAD && (n->type != ROFFT_BODY || n->end != ENDBODY_NOT)) n = n->parent; while (n != breaker) { if ( ! (n->flags & NODE_VALID)) n->flags |= NODE_BROKEN; n = n->parent; } } /* * If there is an open sub-block of the target requiring * explicit close-out, postpone closing out the target until * the rew_pending() call closing out the sub-block. */ static int find_pending(struct roff_man *mdoc, enum roff_tok tok, int line, int ppos, struct roff_node *target) { struct roff_node *n; int irc; if (target->flags & NODE_VALID) return 0; irc = 0; for (n = mdoc->last; n != NULL && n != target; n = n->parent) { if (n->flags & NODE_ENDED) continue; if (n->type == ROFFT_BLOCK && mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) { irc = 1; break_intermediate(mdoc->last, target); if (target->type == ROFFT_HEAD) target->flags |= NODE_ENDED; else if ( ! (target->flags & NODE_ENDED)) { mandoc_msg(MANDOCERR_BLK_NEST, line, ppos, "%s breaks %s", roff_name[tok], roff_name[n->tok]); mdoc_endbody_alloc(mdoc, line, ppos, tok, target); } } } return irc; } /* * Allocate a word and check whether it's punctuation or not. * Punctuation consists of those tokens found in mdoc_isdelim(). */ static void dword(struct roff_man *mdoc, int line, int col, const char *p, enum mdelim d, int may_append) { if (d == DELIM_MAX) d = mdoc_isdelim(p); if (may_append && ! (mdoc->flags & (MDOC_SYNOPSIS | MDOC_KEEP | MDOC_SMOFF)) && d == DELIM_NONE && mdoc->last->type == ROFFT_TEXT && mdoc_isdelim(mdoc->last->string) == DELIM_NONE) { roff_word_append(mdoc, p); return; } roff_word_alloc(mdoc, line, col, p); /* * If the word consists of a bare delimiter, * flag the new node accordingly, * unless doing so was vetoed by the invoking macro. * Always clear the veto, it is only valid for one word. */ if (d == DELIM_OPEN) mdoc->last->flags |= NODE_DELIMO; else if (d == DELIM_CLOSE && ! (mdoc->flags & MDOC_NODELIMC) && mdoc->last->parent->tok != MDOC_Fd) mdoc->last->flags |= NODE_DELIMC; mdoc->flags &= ~MDOC_NODELIMC; } static void append_delims(struct roff_man *mdoc, int line, int *pos, char *buf) { char *p; int la; enum margserr ac; if (buf[*pos] == '\0') return; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, TOKEN_NONE, &p); if (ac == ARGS_EOLN) break; dword(mdoc, line, la, p, DELIM_MAX, 1); /* * If we encounter end-of-sentence symbols, then trigger * the double-space. * * XXX: it's easy to allow this to propagate outward to * the last symbol, such that `. )' will cause the * correct double-spacing. However, (1) groff isn't * smart enough to do this and (2) it would require * knowing which symbols break this behaviour, for * example, `. ;' shouldn't propagate the double-space. */ if (mandoc_eos(p, strlen(p))) mdoc->last->flags |= NODE_EOS; if (ac == ARGS_ALLOC) free(p); } } /* * Parse one word. * If it is a macro, call it and return 1. * Otherwise, allocate it and return 0. */ static int macro_or_word(MACRO_PROT_ARGS, char *p, int parsed) { int ntok; ntok = buf[ppos] == '"' || parsed == 0 || mdoc->flags & MDOC_PHRASELIT ? TOKEN_NONE : lookup(mdoc, tok, line, ppos, p); if (ntok == TOKEN_NONE) { dword(mdoc, line, ppos, p, DELIM_MAX, tok == TOKEN_NONE || mdoc_macro(tok)->flags & MDOC_JOIN); return 0; } else { if (tok != TOKEN_NONE && mdoc_macro(tok)->fp == in_line_eoln) rew_elem(mdoc, tok); (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, ppos, pos, buf); if (tok == TOKEN_NONE) append_delims(mdoc, line, pos, buf); return 1; } } /* * Close out block partial/full explicit. */ static void blk_exp_close(MACRO_PROT_ARGS) { struct roff_node *body; /* Our own body. */ struct roff_node *endbody; /* Our own end marker. */ struct roff_node *itblk; /* An It block starting later. */ struct roff_node *later; /* A sub-block starting later. */ struct roff_node *n; /* Search back to our block. */ struct roff_node *target; /* For find_pending(). */ int j, lastarg, maxargs, nl, pending; enum margserr ac; enum roff_tok atok, ntok; char *p; nl = MDOC_NEWLINE & mdoc->flags; switch (tok) { case MDOC_Ec: maxargs = 1; break; case MDOC_Ek: mdoc->flags &= ~MDOC_KEEP; /* FALLTHROUGH */ default: maxargs = 0; break; } /* Search backwards for the beginning of our own body. */ atok = rew_alt(tok); body = NULL; for (n = mdoc->last; n; n = n->parent) { if (n->flags & NODE_ENDED || n->tok != atok || n->type != ROFFT_BODY || n->end != ENDBODY_NOT) continue; body = n; break; } /* * Search backwards for beginnings of blocks, * both of our own and of pending sub-blocks. */ endbody = itblk = later = NULL; for (n = mdoc->last; n; n = n->parent) { if (n->flags & NODE_ENDED) continue; /* * Mismatching end macros can never break anything * and we only care about the breaking of BLOCKs. */ if (body == NULL || n->type != ROFFT_BLOCK) continue; /* * SYNOPSIS name blocks can not be broken themselves, * but they do get broken together with a broken child. */ if (n->tok == MDOC_Nm) { if (later != NULL) n->flags |= NODE_BROKEN | NODE_ENDED; continue; } if (n->tok == MDOC_It) { itblk = n; continue; } if (atok == n->tok) { /* * Found the start of our own block. * When there is no pending sub block, * just proceed to closing out. */ if (later == NULL || (tok == MDOC_El && itblk == NULL)) break; /* * When there is a pending sub block, postpone * closing out the current block until the * rew_pending() closing out the sub-block. * Mark the place where the formatting - but not * the scope - of the current block ends. */ mandoc_msg(MANDOCERR_BLK_NEST, line, ppos, "%s breaks %s", roff_name[atok], roff_name[later->tok]); endbody = mdoc_endbody_alloc(mdoc, line, ppos, atok, body); if (tok == MDOC_El) itblk->flags |= NODE_ENDED | NODE_BROKEN; /* * If a block closing macro taking arguments * breaks another block, put the arguments * into the end marker. */ if (maxargs) mdoc->next = ROFF_NEXT_CHILD; break; } /* * Explicit blocks close out description lines, but * even those can get broken together with a child. */ if (n->tok == MDOC_Nd) { if (later != NULL) n->flags |= NODE_BROKEN | NODE_ENDED; else rew_last(mdoc, n); continue; } /* Breaking an open sub block. */ break_intermediate(mdoc->last, body); n->flags |= NODE_BROKEN; if (later == NULL) later = n; } if (body == NULL) { mandoc_msg(MANDOCERR_BLK_NOTOPEN, line, ppos, "%s", roff_name[tok]); if (maxargs && endbody == NULL) { /* * Stray .Ec without previous .Eo: * Break the output line, keep the arguments. */ roff_elem_alloc(mdoc, line, ppos, ROFF_br); rew_elem(mdoc, ROFF_br); } } else if (endbody == NULL) { rew_last(mdoc, body); if (maxargs) mdoc_tail_alloc(mdoc, line, ppos, atok); } if ((mdoc_macro(tok)->flags & MDOC_PARSED) == 0) { if (buf[*pos] != '\0') mandoc_msg(MANDOCERR_ARG_SKIP, line, ppos, "%s %s", roff_name[tok], buf + *pos); if (endbody == NULL && n != NULL) rew_pending(mdoc, n); /* * Restore the fill mode that was set before the display. * This needs to be done here rather than during validation * such that subsequent nodes get the right flags. */ if (tok == MDOC_Ed && body != NULL) { if (body->flags & NODE_NOFILL) mdoc->flags |= ROFF_NOFILL; else mdoc->flags &= ~ROFF_NOFILL; } return; } if (endbody != NULL) n = endbody; ntok = TOKEN_NONE; for (j = 0; ; j++) { lastarg = *pos; if (j == maxargs && n != NULL) rew_last(mdoc, n); ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_PUNCT || ac == ARGS_EOLN) break; ntok = lookup(mdoc, tok, line, lastarg, p); if (ntok == TOKEN_NONE) { dword(mdoc, line, lastarg, p, DELIM_MAX, mdoc_macro(tok)->flags & MDOC_JOIN); if (ac == ARGS_ALLOC) free(p); continue; } if (ac == ARGS_ALLOC) free(p); if (n != NULL) rew_last(mdoc, n); mdoc->flags &= ~MDOC_NEWLINE; (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, lastarg, pos, buf); break; } if (n != NULL) { pending = 0; if (ntok != TOKEN_NONE && n->flags & NODE_BROKEN) { target = n; do target = target->parent; while ( ! (target->flags & NODE_ENDED)); pending = find_pending(mdoc, ntok, line, ppos, target); } if ( ! pending) rew_pending(mdoc, n); } if (nl) append_delims(mdoc, line, pos, buf); } static void in_line(MACRO_PROT_ARGS) { int la, scope, cnt, firstarg, mayopen, nc, nl; enum roff_tok ntok; enum margserr ac; enum mdelim d; struct mdoc_arg *arg; char *p; nl = MDOC_NEWLINE & mdoc->flags; /* * Whether we allow ignored elements (those without content, * usually because of reserved words) to squeak by. */ switch (tok) { case MDOC_An: case MDOC_Ar: case MDOC_Fl: case MDOC_Mt: case MDOC_Nm: case MDOC_Pa: nc = 1; break; default: nc = 0; break; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); d = DELIM_NONE; firstarg = 1; mayopen = 1; for (cnt = scope = 0;; ) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); /* * At the end of a macro line, * opening delimiters do not suppress spacing. */ if (ac == ARGS_EOLN) { if (d == DELIM_OPEN) mdoc->last->flags &= ~NODE_DELIMO; break; } /* * The rest of the macro line is only punctuation, * to be handled by append_delims(). * If there were no other arguments, * do not allow the first one to suppress spacing, * even if it turns out to be a closing one. */ if (ac == ARGS_PUNCT) { if (cnt == 0 && (nc == 0 || tok == MDOC_An)) mdoc->flags |= MDOC_NODELIMC; break; } ntok = (tok == MDOC_Fn && !cnt) ? TOKEN_NONE : lookup(mdoc, tok, line, la, p); /* * In this case, we've located a submacro and must * execute it. Close out scope, if open. If no * elements have been generated, either create one (nc) * or raise a warning. */ if (ntok != TOKEN_NONE) { if (scope) rew_elem(mdoc, tok); if (nc && ! cnt) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); rew_last(mdoc, mdoc->last); } else if ( ! nc && ! cnt) { mdoc_argv_free(arg); mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); } (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, la, pos, buf); if (nl) append_delims(mdoc, line, pos, buf); if (ac == ARGS_ALLOC) free(p); return; } /* * Handle punctuation. Set up our scope, if a word; * rewind the scope, if a delimiter; then append the word. */ if ((d = mdoc_isdelim(p)) != DELIM_NONE) { /* * If we encounter closing punctuation, no word * has been emitted, no scope is open, and we're * allowed to have an empty element, then start * a new scope. */ if ((d == DELIM_CLOSE || (d == DELIM_MIDDLE && tok == MDOC_Fl)) && !cnt && !scope && nc && mayopen) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); scope = 1; cnt++; if (tok == MDOC_Nm) mayopen = 0; } /* * Close out our scope, if one is open, before * any punctuation. */ if (scope && tok != MDOC_Lk) { rew_elem(mdoc, tok); scope = 0; if (tok == MDOC_Fn) mayopen = 0; } } else if (mayopen && !scope) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); scope = 1; cnt++; } dword(mdoc, line, la, p, d, mdoc_macro(tok)->flags & MDOC_JOIN); if (ac == ARGS_ALLOC) free(p); /* * If the first argument is a closing delimiter, * do not suppress spacing before it. */ if (firstarg && d == DELIM_CLOSE && !nc) mdoc->last->flags &= ~NODE_DELIMC; firstarg = 0; /* * `Fl' macros have their scope re-opened with each new * word so that the `-' can be added to each one without * having to parse out spaces. */ if (scope && tok == MDOC_Fl) { rew_elem(mdoc, tok); scope = 0; } } if (scope && tok != MDOC_Lk) { rew_elem(mdoc, tok); scope = 0; } /* * If no elements have been collected and we're allowed to have * empties (nc), open a scope and close it out. Otherwise, * raise a warning. */ if ( ! cnt) { if (nc) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); rew_last(mdoc, mdoc->last); } else { mdoc_argv_free(arg); mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); } } if (nl) append_delims(mdoc, line, pos, buf); if (scope) rew_elem(mdoc, tok); } static void blk_full(MACRO_PROT_ARGS) { struct mdoc_arg *arg; struct roff_node *blk; /* Our own or a broken block. */ struct roff_node *head; /* Our own head. */ struct roff_node *body; /* Our own body. */ struct roff_node *n; char *p; size_t iarg; int done, la, nl, parsed; enum margserr ac, lac; nl = MDOC_NEWLINE & mdoc->flags; if (buf[*pos] == '\0' && (tok == MDOC_Sh || tok == MDOC_Ss)) { mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); return; } if ((mdoc_macro(tok)->flags & MDOC_EXPLICIT) == 0) { /* Here, tok is one of Sh Ss Nm Nd It. */ blk = NULL; for (n = mdoc->last; n != NULL; n = n->parent) { if (n->flags & NODE_ENDED) { if ( ! (n->flags & NODE_VALID)) n->flags |= NODE_BROKEN; continue; } if (n->type != ROFFT_BLOCK) continue; if (tok == MDOC_It && n->tok == MDOC_Bl) { if (blk != NULL) { mandoc_msg(MANDOCERR_BLK_BROKEN, line, ppos, "It breaks %s", roff_name[blk->tok]); rew_pending(mdoc, blk); } break; } if (mdoc_macro(n->tok)->flags & MDOC_EXPLICIT) { switch (tok) { case MDOC_Sh: case MDOC_Ss: mandoc_msg(MANDOCERR_BLK_BROKEN, line, ppos, "%s breaks %s", roff_name[tok], roff_name[n->tok]); rew_pending(mdoc, n); n = mdoc->last; continue; case MDOC_It: /* Delay in case it's astray. */ blk = n; continue; default: break; } break; } /* Here, n is one of Sh Ss Nm Nd It. */ if (tok != MDOC_Sh && (n->tok == MDOC_Sh || (tok != MDOC_Ss && (n->tok == MDOC_Ss || (tok != MDOC_It && n->tok == MDOC_It))))) break; /* Item breaking an explicit block. */ if (blk != NULL) { mandoc_msg(MANDOCERR_BLK_BROKEN, line, ppos, "It breaks %s", roff_name[blk->tok]); rew_pending(mdoc, blk); blk = NULL; } /* Close out prior implicit scopes. */ rew_pending(mdoc, n); } /* Skip items outside lists. */ if (tok == MDOC_It && (n == NULL || n->tok != MDOC_Bl)) { mandoc_msg(MANDOCERR_IT_STRAY, line, ppos, "It %s", buf + *pos); roff_elem_alloc(mdoc, line, ppos, ROFF_br); rew_elem(mdoc, ROFF_br); return; } } /* * This routine accommodates implicitly- and explicitly-scoped * macro openings. Implicit ones first close out prior scope * (seen above). Delay opening the head until necessary to * allow leading punctuation to print. Special consideration * for `It -column', which has phrase-part syntax instead of * regular child nodes. */ switch (tok) { case MDOC_Sh: mdoc->flags &= ~ROFF_NOFILL; break; case MDOC_Ss: mdoc->flags |= ROFF_NONOFILL; break; default: break; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); blk = mdoc_block_alloc(mdoc, line, ppos, tok, arg); head = body = NULL; /* * Exception: Heads of `It' macros in `-diag' lists are not * parsed, even though `It' macros in general are parsed. */ parsed = tok != MDOC_It || mdoc->last->parent->tok != MDOC_Bl || mdoc->last->parent->norm->Bl.type != LIST_diag; /* * The `Nd' macro has all arguments in its body: it's a hybrid * of block partial-explicit and full-implicit. Stupid. */ if (tok == MDOC_Nd) { head = roff_head_alloc(mdoc, line, ppos, tok); rew_last(mdoc, head); body = roff_body_alloc(mdoc, line, ppos, tok); } if (tok == MDOC_Bk) mdoc->flags |= MDOC_KEEP; ac = ARGS_EOLN; for (;;) { /* * If we are right after a tab character, * do not parse the first word for macros. */ if (mdoc->flags & MDOC_PHRASEQN) { mdoc->flags &= ~MDOC_PHRASEQN; mdoc->flags |= MDOC_PHRASEQF; } la = *pos; lac = ac; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN) { if (lac != ARGS_PHRASE || ! (mdoc->flags & MDOC_PHRASEQF)) break; /* * This line ends in a tab; start the next * column now, with a leading blank. */ if (body != NULL) rew_last(mdoc, body); body = roff_body_alloc(mdoc, line, ppos, tok); roff_word_alloc(mdoc, line, ppos, "\\&"); break; } if (tok == MDOC_Bd || tok == MDOC_Bk) { mandoc_msg(MANDOCERR_ARG_EXCESS, line, la, "%s ... %s", roff_name[tok], buf + la); if (ac == ARGS_ALLOC) free(p); break; } if (tok == MDOC_Rs) { mandoc_msg(MANDOCERR_ARG_SKIP, line, la, "Rs %s", buf + la); if (ac == ARGS_ALLOC) free(p); break; } if (ac == ARGS_PUNCT) break; /* * Emit leading punctuation (i.e., punctuation before * the ROFFT_HEAD) for non-phrase types. */ if (head == NULL && ac != ARGS_PHRASE && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } /* Open a head if one hasn't been opened. */ if (head == NULL) head = roff_head_alloc(mdoc, line, ppos, tok); if (ac == ARGS_PHRASE) { /* * If we haven't opened a body yet, rewind the * head; if we have, rewind that instead. */ rew_last(mdoc, body == NULL ? head : body); body = roff_body_alloc(mdoc, line, ppos, tok); /* Process to the tab or to the end of the line. */ mdoc->flags |= MDOC_PHRASE; parse_rest(mdoc, TOKEN_NONE, line, &la, buf); mdoc->flags &= ~MDOC_PHRASE; /* There may have been `Ta' macros. */ while (body->next != NULL) body = body->next; continue; } done = macro_or_word(mdoc, tok, line, la, pos, buf, p, parsed); if (ac == ARGS_ALLOC) free(p); if (done) break; } if (blk->flags & NODE_VALID) return; if (head == NULL) head = roff_head_alloc(mdoc, line, ppos, tok); if (nl && tok != MDOC_Bd && tok != MDOC_Bl && tok != MDOC_Rs) append_delims(mdoc, line, pos, buf); if (body != NULL) goto out; if (find_pending(mdoc, tok, line, ppos, head)) return; /* Close out scopes to remain in a consistent state. */ rew_last(mdoc, head); body = roff_body_alloc(mdoc, line, ppos, tok); if (tok == MDOC_Ss) mdoc->flags &= ~ROFF_NONOFILL; /* * Set up fill mode for display blocks. * This needs to be done here up front rather than during * validation such that child nodes get the right flags. */ if (tok == MDOC_Bd && arg != NULL) { for (iarg = 0; iarg < arg->argc; iarg++) { switch (arg->argv[iarg].arg) { case MDOC_Unfilled: case MDOC_Literal: mdoc->flags |= ROFF_NOFILL; break; case MDOC_Filled: case MDOC_Ragged: case MDOC_Centred: mdoc->flags &= ~ROFF_NOFILL; break; default: continue; } break; } } out: if (mdoc->flags & MDOC_FREECOL) { rew_last(mdoc, body); rew_last(mdoc, blk); mdoc->flags &= ~MDOC_FREECOL; } } static void blk_part_imp(MACRO_PROT_ARGS) { int done, la, nl; enum margserr ac; char *p; struct roff_node *blk; /* saved block context */ struct roff_node *body; /* saved body context */ struct roff_node *n; nl = MDOC_NEWLINE & mdoc->flags; /* * A macro that spans to the end of the line. This is generally * (but not necessarily) called as the first macro. The block * has a head as the immediate child, which is always empty, * followed by zero or more opening punctuation nodes, then the * body (which may be empty, depending on the macro), then zero * or more closing punctuation nodes. */ blk = mdoc_block_alloc(mdoc, line, ppos, tok, NULL); rew_last(mdoc, roff_head_alloc(mdoc, line, ppos, tok)); /* * Open the body scope "on-demand", that is, after we've * processed all our the leading delimiters (open parenthesis, * etc.). */ for (body = NULL; ; ) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN || ac == ARGS_PUNCT) break; if (body == NULL && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } if (body == NULL) body = roff_body_alloc(mdoc, line, ppos, tok); done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1); if (ac == ARGS_ALLOC) free(p); if (done) break; } if (body == NULL) body = roff_body_alloc(mdoc, line, ppos, tok); if (find_pending(mdoc, tok, line, ppos, body)) return; rew_last(mdoc, body); if (nl) append_delims(mdoc, line, pos, buf); rew_pending(mdoc, blk); /* Move trailing .Ns out of scope. */ for (n = body->child; n && n->next; n = n->next) /* Do nothing. */ ; if (n && n->tok == MDOC_Ns) roff_node_relink(mdoc, n); } static void blk_part_exp(MACRO_PROT_ARGS) { int done, la, nl; enum margserr ac; struct roff_node *head; /* keep track of head */ char *p; nl = MDOC_NEWLINE & mdoc->flags; /* * The opening of an explicit macro having zero or more leading * punctuation nodes; a head with optional single element (the * case of `Eo'); and a body that may be empty. */ roff_block_alloc(mdoc, line, ppos, tok); head = NULL; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_PUNCT || ac == ARGS_EOLN) break; /* Flush out leading punctuation. */ if (head == NULL && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } if (head == NULL) { head = roff_head_alloc(mdoc, line, ppos, tok); if (tok == MDOC_Eo) /* Not parsed. */ dword(mdoc, line, la, p, DELIM_MAX, 0); rew_last(mdoc, head); roff_body_alloc(mdoc, line, ppos, tok); if (tok == MDOC_Eo) { if (ac == ARGS_ALLOC) free(p); continue; } } done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1); if (ac == ARGS_ALLOC) free(p); if (done) break; } /* Clean-up to leave in a consistent state. */ if (head == NULL) { rew_last(mdoc, roff_head_alloc(mdoc, line, ppos, tok)); roff_body_alloc(mdoc, line, ppos, tok); } if (nl) append_delims(mdoc, line, pos, buf); } static void in_line_argn(MACRO_PROT_ARGS) { struct mdoc_arg *arg; char *p; enum margserr ac; enum roff_tok ntok; int state; /* arg#; -1: not yet open; -2: closed */ int la, maxargs, nl; nl = mdoc->flags & MDOC_NEWLINE; /* * A line macro that has a fixed number of arguments (maxargs). * Only open the scope once the first non-leading-punctuation is * found (unless MDOC_IGNDELIM is noted, like in `Pf'), then * keep it open until the maximum number of arguments are * exhausted. */ switch (tok) { case MDOC_Ap: case MDOC_Ns: case MDOC_Ux: maxargs = 0; break; case MDOC_Bx: case MDOC_Es: case MDOC_Xr: maxargs = 2; break; default: maxargs = 1; break; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); state = -1; p = NULL; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if ((ac == ARGS_WORD || ac == ARGS_ALLOC) && state == -1 && (mdoc_macro(tok)->flags & MDOC_IGNDELIM) == 0 && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); if (ac == ARGS_ALLOC) free(p); continue; } if (state == -1 && tok != MDOC_In && tok != MDOC_St && tok != MDOC_Xr) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); state = 0; } if (ac == ARGS_PUNCT || ac == ARGS_EOLN) { if (abs(state) < 2 && tok == MDOC_Pf) mandoc_msg(MANDOCERR_PF_SKIP, line, ppos, "Pf %s", p == NULL ? "at eol" : p); break; } if (state == maxargs) { rew_elem(mdoc, tok); state = -2; } ntok = (tok == MDOC_Pf && state == 0) ? TOKEN_NONE : lookup(mdoc, tok, line, la, p); if (ntok != TOKEN_NONE) { if (state >= 0) { rew_elem(mdoc, tok); state = -2; } (*mdoc_macro(ntok)->fp)(mdoc, ntok, line, la, pos, buf); if (ac == ARGS_ALLOC) free(p); break; } if (mdoc_macro(tok)->flags & MDOC_IGNDELIM || mdoc_isdelim(p) == DELIM_NONE) { if (state == -1) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); state = 1; } else if (state >= 0) state++; } else if (state >= 0) { rew_elem(mdoc, tok); state = -2; } dword(mdoc, line, la, p, DELIM_MAX, mdoc_macro(tok)->flags & MDOC_JOIN); if (ac == ARGS_ALLOC) free(p); p = mdoc->last->string; } if (state == -1) { mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); return; } if (state == 0 && tok == MDOC_Pf) append_delims(mdoc, line, pos, buf); if (state >= 0) rew_elem(mdoc, tok); if (nl) append_delims(mdoc, line, pos, buf); } static void in_line_eoln(MACRO_PROT_ARGS) { struct roff_node *n; struct mdoc_arg *arg; if ((tok == MDOC_Pp || tok == MDOC_Lp) && ! (mdoc->flags & MDOC_SYNOPSIS)) { n = mdoc->last; if (mdoc->next == ROFF_NEXT_SIBLING) n = n->parent; if (n->tok == MDOC_Nm) rew_last(mdoc, n->parent); } if (buf[*pos] == '\0' && (tok == MDOC_Fd || *roff_name[tok] == '%')) { mandoc_msg(MANDOCERR_MACRO_EMPTY, line, ppos, "%s", roff_name[tok]); return; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); mdoc_elem_alloc(mdoc, line, ppos, tok, arg); if (parse_rest(mdoc, tok, line, pos, buf)) return; rew_elem(mdoc, tok); } /* * The simplest argument parser available: Parse the remaining * words until the end of the phrase or line and return 0 * or until the next macro, call that macro, and return 1. */ static int parse_rest(struct roff_man *mdoc, enum roff_tok tok, int line, int *pos, char *buf) { char *p; int done, la; enum margserr ac; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN) return 0; done = macro_or_word(mdoc, tok, line, la, pos, buf, p, 1); if (ac == ARGS_ALLOC) free(p); if (done) return 1; } } static void ctx_synopsis(MACRO_PROT_ARGS) { if (~mdoc->flags & (MDOC_SYNOPSIS | MDOC_NEWLINE)) in_line(mdoc, tok, line, ppos, pos, buf); else if (tok == MDOC_Nm) blk_full(mdoc, tok, line, ppos, pos, buf); else { assert(tok == MDOC_Vt); blk_part_imp(mdoc, tok, line, ppos, pos, buf); } } /* * Phrases occur within `Bl -column' entries, separated by `Ta' or tabs. * They're unusual because they're basically free-form text until a * macro is encountered. */ static void phrase_ta(MACRO_PROT_ARGS) { struct roff_node *body, *n; /* Make sure we are in a column list or ignore this macro. */ body = NULL; for (n = mdoc->last; n != NULL; n = n->parent) { if (n->flags & NODE_ENDED) continue; if (n->tok == MDOC_It && n->type == ROFFT_BODY) body = n; if (n->tok == MDOC_Bl && n->end == ENDBODY_NOT) break; } if (n == NULL || n->norm->Bl.type != LIST_column) { mandoc_msg(MANDOCERR_TA_STRAY, line, ppos, "Ta"); return; } /* Advance to the next column. */ rew_last(mdoc, body); roff_body_alloc(mdoc, line, ppos, MDOC_It); parse_rest(mdoc, TOKEN_NONE, line, pos, buf); } /* $Id: mdoc_man.c,v 1.137 2021/07/04 15:38:26 schwarze Exp $ */ /* * Copyright (c) 2011-2021 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_aux.h" #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "man.h" #include "out.h" #include "main.h" #define DECL_ARGS const struct roff_meta *meta, struct roff_node *n typedef int (*int_fp)(DECL_ARGS); typedef void (*void_fp)(DECL_ARGS); struct mdoc_man_act { int_fp cond; /* DON'T run actions */ int_fp pre; /* pre-node action */ void_fp post; /* post-node action */ const char *prefix; /* pre-node string constant */ const char *suffix; /* post-node string constant */ }; static int cond_body(DECL_ARGS); static int cond_head(DECL_ARGS); static void font_push(char); static void font_pop(void); static int man_strlen(const char *); static void mid_it(void); static void post__t(DECL_ARGS); static void post_aq(DECL_ARGS); static void post_bd(DECL_ARGS); static void post_bf(DECL_ARGS); static void post_bk(DECL_ARGS); static void post_bl(DECL_ARGS); static void post_dl(DECL_ARGS); static void post_en(DECL_ARGS); static void post_enc(DECL_ARGS); static void post_eo(DECL_ARGS); static void post_fa(DECL_ARGS); static void post_fd(DECL_ARGS); static void post_fl(DECL_ARGS); static void post_fn(DECL_ARGS); static void post_fo(DECL_ARGS); static void post_font(DECL_ARGS); static void post_in(DECL_ARGS); static void post_it(DECL_ARGS); static void post_lb(DECL_ARGS); static void post_nm(DECL_ARGS); static void post_percent(DECL_ARGS); static void post_pf(DECL_ARGS); static void post_sect(DECL_ARGS); static void post_vt(DECL_ARGS); static int pre__t(DECL_ARGS); static int pre_abort(DECL_ARGS); static int pre_an(DECL_ARGS); static int pre_ap(DECL_ARGS); static int pre_aq(DECL_ARGS); static int pre_bd(DECL_ARGS); static int pre_bf(DECL_ARGS); static int pre_bk(DECL_ARGS); static int pre_bl(DECL_ARGS); static void pre_br(DECL_ARGS); static int pre_dl(DECL_ARGS); static int pre_en(DECL_ARGS); static int pre_enc(DECL_ARGS); static int pre_em(DECL_ARGS); static int pre_skip(DECL_ARGS); static int pre_eo(DECL_ARGS); static int pre_ex(DECL_ARGS); static int pre_fa(DECL_ARGS); static int pre_fd(DECL_ARGS); static int pre_fl(DECL_ARGS); static int pre_fn(DECL_ARGS); static int pre_fo(DECL_ARGS); static void pre_ft(DECL_ARGS); static int pre_Ft(DECL_ARGS); static int pre_in(DECL_ARGS); static int pre_it(DECL_ARGS); static int pre_lk(DECL_ARGS); static int pre_li(DECL_ARGS); static int pre_nm(DECL_ARGS); static int pre_no(DECL_ARGS); static void pre_noarg(DECL_ARGS); static int pre_ns(DECL_ARGS); static void pre_onearg(DECL_ARGS); static int pre_pp(DECL_ARGS); static int pre_rs(DECL_ARGS); static int pre_sm(DECL_ARGS); static void pre_sp(DECL_ARGS); static int pre_sect(DECL_ARGS); static int pre_sy(DECL_ARGS); static void pre_syn(struct roff_node *); static void pre_ta(DECL_ARGS); static int pre_vt(DECL_ARGS); static int pre_xr(DECL_ARGS); static void print_word(const char *); static void print_line(const char *, int); static void print_block(const char *, int); static void print_offs(const char *, int); static void print_width(const struct mdoc_bl *, const struct roff_node *); static void print_count(int *); static void print_node(DECL_ARGS); static const void_fp roff_man_acts[ROFF_MAX] = { pre_br, /* br */ pre_onearg, /* ce */ pre_noarg, /* fi */ pre_ft, /* ft */ pre_onearg, /* ll */ pre_onearg, /* mc */ pre_noarg, /* nf */ pre_onearg, /* po */ pre_onearg, /* rj */ pre_sp, /* sp */ pre_ta, /* ta */ pre_onearg, /* ti */ }; static const struct mdoc_man_act mdoc_man_acts[MDOC_MAX - MDOC_Dd] = { { NULL, NULL, NULL, NULL, NULL }, /* Dd */ { NULL, NULL, NULL, NULL, NULL }, /* Dt */ { NULL, NULL, NULL, NULL, NULL }, /* Os */ { NULL, pre_sect, post_sect, ".SH", NULL }, /* Sh */ { NULL, pre_sect, post_sect, ".SS", NULL }, /* Ss */ { NULL, pre_pp, NULL, NULL, NULL }, /* Pp */ { cond_body, pre_dl, post_dl, NULL, NULL }, /* D1 */ { cond_body, pre_dl, post_dl, NULL, NULL }, /* Dl */ { cond_body, pre_bd, post_bd, NULL, NULL }, /* Bd */ { NULL, NULL, NULL, NULL, NULL }, /* Ed */ { cond_body, pre_bl, post_bl, NULL, NULL }, /* Bl */ { NULL, NULL, NULL, NULL, NULL }, /* El */ { NULL, pre_it, post_it, NULL, NULL }, /* It */ { NULL, pre_em, post_font, NULL, NULL }, /* Ad */ { NULL, pre_an, NULL, NULL, NULL }, /* An */ { NULL, pre_ap, NULL, NULL, NULL }, /* Ap */ { NULL, pre_em, post_font, NULL, NULL }, /* Ar */ { NULL, pre_sy, post_font, NULL, NULL }, /* Cd */ { NULL, pre_sy, post_font, NULL, NULL }, /* Cm */ { NULL, pre_li, post_font, NULL, NULL }, /* Dv */ { NULL, pre_li, post_font, NULL, NULL }, /* Er */ { NULL, pre_li, post_font, NULL, NULL }, /* Ev */ { NULL, pre_ex, NULL, NULL, NULL }, /* Ex */ { NULL, pre_fa, post_fa, NULL, NULL }, /* Fa */ { NULL, pre_fd, post_fd, NULL, NULL }, /* Fd */ { NULL, pre_fl, post_fl, NULL, NULL }, /* Fl */ { NULL, pre_fn, post_fn, NULL, NULL }, /* Fn */ { NULL, pre_Ft, post_font, NULL, NULL }, /* Ft */ { NULL, pre_sy, post_font, NULL, NULL }, /* Ic */ { NULL, pre_in, post_in, NULL, NULL }, /* In */ { NULL, pre_li, post_font, NULL, NULL }, /* Li */ { cond_head, pre_enc, NULL, "\\- ", NULL }, /* Nd */ { NULL, pre_nm, post_nm, NULL, NULL }, /* Nm */ { cond_body, pre_enc, post_enc, "[", "]" }, /* Op */ { NULL, pre_abort, NULL, NULL, NULL }, /* Ot */ { NULL, pre_em, post_font, NULL, NULL }, /* Pa */ { NULL, pre_ex, NULL, NULL, NULL }, /* Rv */ { NULL, NULL, NULL, NULL, NULL }, /* St */ { NULL, pre_em, post_font, NULL, NULL }, /* Va */ { NULL, pre_vt, post_vt, NULL, NULL }, /* Vt */ { NULL, pre_xr, NULL, NULL, NULL }, /* Xr */ { NULL, NULL, post_percent, NULL, NULL }, /* %A */ { NULL, pre_em, post_percent, NULL, NULL }, /* %B */ { NULL, NULL, post_percent, NULL, NULL }, /* %D */ { NULL, pre_em, post_percent, NULL, NULL }, /* %I */ { NULL, pre_em, post_percent, NULL, NULL }, /* %J */ { NULL, NULL, post_percent, NULL, NULL }, /* %N */ { NULL, NULL, post_percent, NULL, NULL }, /* %O */ { NULL, NULL, post_percent, NULL, NULL }, /* %P */ { NULL, NULL, post_percent, NULL, NULL }, /* %R */ { NULL, pre__t, post__t, NULL, NULL }, /* %T */ { NULL, NULL, post_percent, NULL, NULL }, /* %V */ { NULL, NULL, NULL, NULL, NULL }, /* Ac */ { cond_body, pre_aq, post_aq, NULL, NULL }, /* Ao */ { cond_body, pre_aq, post_aq, NULL, NULL }, /* Aq */ { NULL, NULL, NULL, NULL, NULL }, /* At */ { NULL, NULL, NULL, NULL, NULL }, /* Bc */ { NULL, pre_bf, post_bf, NULL, NULL }, /* Bf */ { cond_body, pre_enc, post_enc, "[", "]" }, /* Bo */ { cond_body, pre_enc, post_enc, "[", "]" }, /* Bq */ { NULL, pre_bk, post_bk, NULL, NULL }, /* Bsx */ { NULL, pre_bk, post_bk, NULL, NULL }, /* Bx */ { NULL, pre_skip, NULL, NULL, NULL }, /* Db */ { NULL, NULL, NULL, NULL, NULL }, /* Dc */ { cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Do */ { cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Dq */ { NULL, NULL, NULL, NULL, NULL }, /* Ec */ { NULL, NULL, NULL, NULL, NULL }, /* Ef */ { NULL, pre_em, post_font, NULL, NULL }, /* Em */ { cond_body, pre_eo, post_eo, NULL, NULL }, /* Eo */ { NULL, pre_bk, post_bk, NULL, NULL }, /* Fx */ { NULL, pre_sy, post_font, NULL, NULL }, /* Ms */ { NULL, pre_no, NULL, NULL, NULL }, /* No */ { NULL, pre_ns, NULL, NULL, NULL }, /* Ns */ { NULL, pre_bk, post_bk, NULL, NULL }, /* Nx */ { NULL, pre_bk, post_bk, NULL, NULL }, /* Ox */ { NULL, NULL, NULL, NULL, NULL }, /* Pc */ { NULL, NULL, post_pf, NULL, NULL }, /* Pf */ { cond_body, pre_enc, post_enc, "(", ")" }, /* Po */ { cond_body, pre_enc, post_enc, "(", ")" }, /* Pq */ { NULL, NULL, NULL, NULL, NULL }, /* Qc */ { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* Ql */ { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qo */ { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qq */ { NULL, NULL, NULL, NULL, NULL }, /* Re */ { cond_body, pre_rs, NULL, NULL, NULL }, /* Rs */ { NULL, NULL, NULL, NULL, NULL }, /* Sc */ { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* So */ { cond_body, pre_enc, post_enc, "\\(oq", "\\(cq" }, /* Sq */ { NULL, pre_sm, NULL, NULL, NULL }, /* Sm */ { NULL, pre_em, post_font, NULL, NULL }, /* Sx */ { NULL, pre_sy, post_font, NULL, NULL }, /* Sy */ { NULL, pre_li, post_font, NULL, NULL }, /* Tn */ { NULL, NULL, NULL, NULL, NULL }, /* Ux */ { NULL, NULL, NULL, NULL, NULL }, /* Xc */ { NULL, NULL, NULL, NULL, NULL }, /* Xo */ { NULL, pre_fo, post_fo, NULL, NULL }, /* Fo */ { NULL, NULL, NULL, NULL, NULL }, /* Fc */ { cond_body, pre_enc, post_enc, "[", "]" }, /* Oo */ { NULL, NULL, NULL, NULL, NULL }, /* Oc */ { NULL, pre_bk, post_bk, NULL, NULL }, /* Bk */ { NULL, NULL, NULL, NULL, NULL }, /* Ek */ { NULL, NULL, NULL, NULL, NULL }, /* Bt */ { NULL, NULL, NULL, NULL, NULL }, /* Hf */ { NULL, pre_em, post_font, NULL, NULL }, /* Fr */ { NULL, NULL, NULL, NULL, NULL }, /* Ud */ { NULL, NULL, post_lb, NULL, NULL }, /* Lb */ { NULL, pre_abort, NULL, NULL, NULL }, /* Lp */ { NULL, pre_lk, NULL, NULL, NULL }, /* Lk */ { NULL, pre_em, post_font, NULL, NULL }, /* Mt */ { cond_body, pre_enc, post_enc, "{", "}" }, /* Brq */ { cond_body, pre_enc, post_enc, "{", "}" }, /* Bro */ { NULL, NULL, NULL, NULL, NULL }, /* Brc */ { NULL, NULL, post_percent, NULL, NULL }, /* %C */ { NULL, pre_skip, NULL, NULL, NULL }, /* Es */ { cond_body, pre_en, post_en, NULL, NULL }, /* En */ { NULL, pre_bk, post_bk, NULL, NULL }, /* Dx */ { NULL, NULL, post_percent, NULL, NULL }, /* %Q */ { NULL, NULL, post_percent, NULL, NULL }, /* %U */ { NULL, NULL, NULL, NULL, NULL }, /* Ta */ { NULL, pre_skip, NULL, NULL, NULL }, /* Tg */ }; static const struct mdoc_man_act *mdoc_man_act(enum roff_tok); static int outflags; #define MMAN_spc (1 << 0) /* blank character before next word */ #define MMAN_spc_force (1 << 1) /* even before trailing punctuation */ #define MMAN_nl (1 << 2) /* break man(7) code line */ #define MMAN_br (1 << 3) /* break output line */ #define MMAN_sp (1 << 4) /* insert a blank output line */ #define MMAN_PP (1 << 5) /* reset indentation etc. */ #define MMAN_Sm (1 << 6) /* horizontal spacing mode */ #define MMAN_Bk (1 << 7) /* word keep mode */ #define MMAN_Bk_susp (1 << 8) /* suspend this (after a macro) */ #define MMAN_An_split (1 << 9) /* author mode is "split" */ #define MMAN_An_nosplit (1 << 10) /* author mode is "nosplit" */ #define MMAN_PD (1 << 11) /* inter-paragraph spacing disabled */ #define MMAN_nbrword (1 << 12) /* do not break the next word */ #define BL_STACK_MAX 32 static int Bl_stack[BL_STACK_MAX]; /* offsets [chars] */ static int Bl_stack_post[BL_STACK_MAX]; /* add final .RE */ static int Bl_stack_len; /* number of nested Bl blocks */ static int TPremain; /* characters before tag is full */ static struct { char *head; char *tail; size_t size; } fontqueue; static const struct mdoc_man_act * mdoc_man_act(enum roff_tok tok) { assert(tok >= MDOC_Dd && tok <= MDOC_MAX); return mdoc_man_acts + (tok - MDOC_Dd); } static int man_strlen(const char *cp) { size_t rsz; int skip, sz; sz = 0; skip = 0; for (;;) { rsz = strcspn(cp, "\\"); if (rsz) { cp += rsz; if (skip) { skip = 0; rsz--; } sz += rsz; } if ('\0' == *cp) break; cp++; switch (mandoc_escape(&cp, NULL, NULL)) { case ESCAPE_ERROR: return sz; case ESCAPE_UNICODE: case ESCAPE_NUMBERED: case ESCAPE_SPECIAL: case ESCAPE_UNDEF: case ESCAPE_OVERSTRIKE: if (skip) skip = 0; else sz++; break; case ESCAPE_SKIPCHAR: skip = 1; break; default: break; } } return sz; } static void font_push(char newfont) { if (fontqueue.head + fontqueue.size <= ++fontqueue.tail) { fontqueue.size += 8; fontqueue.head = mandoc_realloc(fontqueue.head, fontqueue.size); } *fontqueue.tail = newfont; print_word(""); printf("\\f"); putchar(newfont); outflags &= ~MMAN_spc; } static void font_pop(void) { if (fontqueue.tail > fontqueue.head) fontqueue.tail--; outflags &= ~MMAN_spc; print_word(""); printf("\\f"); putchar(*fontqueue.tail); } static void print_word(const char *s) { if ((MMAN_PP | MMAN_sp | MMAN_br | MMAN_nl) & outflags) { /* * If we need a newline, print it now and start afresh. */ if (MMAN_PP & outflags) { if (MMAN_sp & outflags) { if (MMAN_PD & outflags) { printf("\n.PD"); outflags &= ~MMAN_PD; } } else if ( ! (MMAN_PD & outflags)) { printf("\n.PD 0"); outflags |= MMAN_PD; } printf("\n.PP\n"); } else if (MMAN_sp & outflags) printf("\n.sp\n"); else if (MMAN_br & outflags) printf("\n.br\n"); else if (MMAN_nl & outflags) putchar('\n'); outflags &= ~(MMAN_PP|MMAN_sp|MMAN_br|MMAN_nl|MMAN_spc); if (1 == TPremain) printf(".br\n"); TPremain = 0; } else if (MMAN_spc & outflags) { /* * If we need a space, only print it if * (1) it is forced by `No' or * (2) what follows is not terminating punctuation or * (3) what follows is longer than one character. */ if (MMAN_spc_force & outflags || '\0' == s[0] || NULL == strchr(".,:;)]?!", s[0]) || '\0' != s[1]) { if (MMAN_Bk & outflags && ! (MMAN_Bk_susp & outflags)) putchar('\\'); putchar(' '); if (TPremain) TPremain--; } } /* * Reassign needing space if we're not following opening * punctuation. */ if (MMAN_Sm & outflags && ('\0' == s[0] || (('(' != s[0] && '[' != s[0]) || '\0' != s[1]))) outflags |= MMAN_spc; else outflags &= ~MMAN_spc; outflags &= ~(MMAN_spc_force | MMAN_Bk_susp); for ( ; *s; s++) { switch (*s) { case ASCII_NBRSP: printf("\\ "); break; case ASCII_HYPH: putchar('-'); break; case ASCII_BREAK: printf("\\:"); break; case ' ': if (MMAN_nbrword & outflags) { printf("\\ "); break; } /* FALLTHROUGH */ default: putchar((unsigned char)*s); break; } if (TPremain) TPremain--; } outflags &= ~MMAN_nbrword; } static void print_line(const char *s, int newflags) { outflags |= MMAN_nl; print_word(s); outflags |= newflags; } static void print_block(const char *s, int newflags) { outflags &= ~MMAN_PP; if (MMAN_sp & outflags) { outflags &= ~(MMAN_sp | MMAN_br); if (MMAN_PD & outflags) { print_line(".PD", 0); outflags &= ~MMAN_PD; } } else if (! (MMAN_PD & outflags)) print_line(".PD 0", MMAN_PD); outflags |= MMAN_nl; print_word(s); outflags |= MMAN_Bk_susp | newflags; } static void print_offs(const char *v, int keywords) { char buf[24]; struct roffsu su; const char *end; int sz; print_line(".RS", MMAN_Bk_susp); /* Convert v into a number (of characters). */ if (NULL == v || '\0' == *v || (keywords && !strcmp(v, "left"))) sz = 0; else if (keywords && !strcmp(v, "indent")) sz = 6; else if (keywords && !strcmp(v, "indent-two")) sz = 12; else { end = a2roffsu(v, &su, SCALE_EN); if (end == NULL || *end != '\0') sz = man_strlen(v); else if (SCALE_EN == su.unit) sz = su.scale; else { /* * XXX * If we are inside an enclosing list, * there is no easy way to add the two * indentations because they are provided * in terms of different units. */ print_word(v); outflags |= MMAN_nl; return; } } /* * We are inside an enclosing list. * Add the two indentations. */ if (Bl_stack_len) sz += Bl_stack[Bl_stack_len - 1]; (void)snprintf(buf, sizeof(buf), "%dn", sz); print_word(buf); outflags |= MMAN_nl; } /* * Set up the indentation for a list item; used from pre_it(). */ static void print_width(const struct mdoc_bl *bl, const struct roff_node *child) { char buf[24]; struct roffsu su; const char *end; int numeric, remain, sz, chsz; numeric = 1; remain = 0; /* Convert the width into a number (of characters). */ if (bl->width == NULL) sz = (bl->type == LIST_hang) ? 6 : 0; else { end = a2roffsu(bl->width, &su, SCALE_MAX); if (end == NULL || *end != '\0') sz = man_strlen(bl->width); else if (SCALE_EN == su.unit) sz = su.scale; else { sz = 0; numeric = 0; } } /* XXX Rough estimation, might have multiple parts. */ if (bl->type == LIST_enum) chsz = (bl->count > 8) + 1; else if (child != NULL && child->type == ROFFT_TEXT) chsz = man_strlen(child->string); else chsz = 0; /* Maybe we are inside an enclosing list? */ mid_it(); /* * Save our own indentation, * such that child lists can use it. */ Bl_stack[Bl_stack_len++] = sz + 2; /* Set up the current list. */ if (chsz > sz && bl->type != LIST_tag) print_block(".HP", MMAN_spc); else { print_block(".TP", MMAN_spc); remain = sz + 2; } if (numeric) { (void)snprintf(buf, sizeof(buf), "%dn", sz + 2); print_word(buf); } else print_word(bl->width); TPremain = remain; } static void print_count(int *count) { char buf[24]; (void)snprintf(buf, sizeof(buf), "%d.\\&", ++*count); print_word(buf); } void man_mdoc(void *arg, const struct roff_meta *mdoc) { struct roff_node *n; printf(".\\\" Automatically generated from an mdoc input file." " Do not edit.\n"); for (n = mdoc->first->child; n != NULL; n = n->next) { if (n->type != ROFFT_COMMENT) break; printf(".\\\"%s\n", n->string); } printf(".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n", mdoc->title, (mdoc->msec == NULL ? "" : mdoc->msec), mdoc->date, mdoc->os, mdoc->vol); /* Disable hyphenation and if nroff, disable justification. */ printf(".nh\n.if n .ad l"); outflags = MMAN_nl | MMAN_Sm; if (0 == fontqueue.size) { fontqueue.size = 8; fontqueue.head = fontqueue.tail = mandoc_malloc(8); *fontqueue.tail = 'R'; } for (; n != NULL; n = n->next) print_node(mdoc, n); putchar('\n'); } static void print_node(DECL_ARGS) { const struct mdoc_man_act *act; struct roff_node *sub; int cond, do_sub; if (n->flags & NODE_NOPRT) return; /* * Break the line if we were parsed subsequent the current node. * This makes the page structure be more consistent. */ if (outflags & MMAN_spc && n->flags & NODE_LINE && !roff_node_transparent(n)) outflags |= MMAN_nl; act = NULL; cond = 0; do_sub = 1; n->flags &= ~NODE_ENDED; switch (n->type) { case ROFFT_EQN: case ROFFT_TBL: mandoc_msg(n->type == ROFFT_EQN ? MANDOCERR_EQN_TMAN : MANDOCERR_TBL_TMAN, n->line, n->pos, NULL); outflags |= MMAN_PP | MMAN_sp | MMAN_nl; print_word("The"); print_line(".B \\-T man", MMAN_nl); print_word("output mode does not support"); print_word(n->type == ROFFT_EQN ? "eqn(7)" : "tbl(7)"); print_word("input."); outflags |= MMAN_PP | MMAN_sp | MMAN_nl; return; case ROFFT_TEXT: /* * Make sure that we don't happen to start with a * control character at the start of a line. */ if (MMAN_nl & outflags && ('.' == *n->string || '\'' == *n->string)) { print_word(""); printf("\\&"); outflags &= ~MMAN_spc; } if (n->flags & NODE_DELIMC) outflags &= ~(MMAN_spc | MMAN_spc_force); else if (outflags & MMAN_Sm) outflags |= MMAN_spc_force; print_word(n->string); if (n->flags & NODE_DELIMO) outflags &= ~(MMAN_spc | MMAN_spc_force); else if (outflags & MMAN_Sm) outflags |= MMAN_spc; break; default: if (n->tok < ROFF_MAX) { (*roff_man_acts[n->tok])(meta, n); return; } act = mdoc_man_act(n->tok); cond = act->cond == NULL || (*act->cond)(meta, n); if (cond && act->pre != NULL && (n->end == ENDBODY_NOT || n->child != NULL)) do_sub = (*act->pre)(meta, n); break; } /* * Conditionally run all child nodes. * Note that this iterates over children instead of using * recursion. This prevents unnecessary depth in the stack. */ if (do_sub) for (sub = n->child; sub; sub = sub->next) print_node(meta, sub); /* * Lastly, conditionally run the post-node handler. */ if (NODE_ENDED & n->flags) return; if (cond && act->post) (*act->post)(meta, n); if (ENDBODY_NOT != n->end) n->body->flags |= NODE_ENDED; } static int cond_head(DECL_ARGS) { return n->type == ROFFT_HEAD; } static int cond_body(DECL_ARGS) { return n->type == ROFFT_BODY; } static int pre_abort(DECL_ARGS) { abort(); } static int pre_enc(DECL_ARGS) { const char *prefix; prefix = mdoc_man_act(n->tok)->prefix; if (NULL == prefix) return 1; print_word(prefix); outflags &= ~MMAN_spc; return 1; } static void post_enc(DECL_ARGS) { const char *suffix; suffix = mdoc_man_act(n->tok)->suffix; if (NULL == suffix) return; outflags &= ~(MMAN_spc | MMAN_nl); print_word(suffix); } static int pre_ex(DECL_ARGS) { outflags |= MMAN_br | MMAN_nl; return 1; } static void post_font(DECL_ARGS) { font_pop(); } static void post_percent(DECL_ARGS) { struct roff_node *np, *nn, *nnn; if (mdoc_man_act(n->tok)->pre == pre_em) font_pop(); if ((nn = roff_node_next(n)) != NULL) { np = roff_node_prev(n); nnn = nn == NULL ? NULL : roff_node_next(nn); if (nn->tok != n->tok || (np != NULL && np->tok == n->tok) || (nnn != NULL && nnn->tok == n->tok)) print_word(","); if (nn->tok == n->tok && (nnn == NULL || nnn->tok != n->tok)) print_word("and"); } else { print_word("."); outflags |= MMAN_nl; } } static int pre__t(DECL_ARGS) { if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) { print_word("\\(lq"); outflags &= ~MMAN_spc; } else font_push('I'); return 1; } static void post__t(DECL_ARGS) { if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) { outflags &= ~MMAN_spc; print_word("\\(rq"); } else font_pop(); post_percent(meta, n); } /* * Print before a section header. */ static int pre_sect(DECL_ARGS) { if (n->type == ROFFT_HEAD) { outflags |= MMAN_sp; print_block(mdoc_man_act(n->tok)->prefix, 0); print_word(""); putchar('\"'); outflags &= ~MMAN_spc; } return 1; } /* * Print subsequent a section header. */ static void post_sect(DECL_ARGS) { if (n->type != ROFFT_HEAD) return; outflags &= ~MMAN_spc; print_word(""); putchar('\"'); outflags |= MMAN_nl; if (MDOC_Sh == n->tok && SEC_AUTHORS == n->sec) outflags &= ~(MMAN_An_split | MMAN_An_nosplit); } /* See mdoc_term.c, synopsis_pre() for comments. */ static void pre_syn(struct roff_node *n) { struct roff_node *np; if ((n->flags & NODE_SYNPRETTY) == 0 || (np = roff_node_prev(n)) == NULL) return; if (np->tok == n->tok && MDOC_Ft != n->tok && MDOC_Fo != n->tok && MDOC_Fn != n->tok) { outflags |= MMAN_br; return; } switch (np->tok) { case MDOC_Fd: case MDOC_Fn: case MDOC_Fo: case MDOC_In: case MDOC_Vt: outflags |= MMAN_sp; break; case MDOC_Ft: if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { outflags |= MMAN_sp; break; } /* FALLTHROUGH */ default: outflags |= MMAN_br; break; } } static int pre_an(DECL_ARGS) { switch (n->norm->An.auth) { case AUTH_split: outflags &= ~MMAN_An_nosplit; outflags |= MMAN_An_split; return 0; case AUTH_nosplit: outflags &= ~MMAN_An_split; outflags |= MMAN_An_nosplit; return 0; default: if (MMAN_An_split & outflags) outflags |= MMAN_br; else if (SEC_AUTHORS == n->sec && ! (MMAN_An_nosplit & outflags)) outflags |= MMAN_An_split; return 1; } } static int pre_ap(DECL_ARGS) { outflags &= ~MMAN_spc; print_word("'"); outflags &= ~MMAN_spc; return 0; } static int pre_aq(DECL_ARGS) { print_word(n->child != NULL && n->child->next == NULL && n->child->tok == MDOC_Mt ? "<" : "\\(la"); outflags &= ~MMAN_spc; return 1; } static void post_aq(DECL_ARGS) { outflags &= ~(MMAN_spc | MMAN_nl); print_word(n->child != NULL && n->child->next == NULL && n->child->tok == MDOC_Mt ? ">" : "\\(ra"); } static int pre_bd(DECL_ARGS) { outflags &= ~(MMAN_PP | MMAN_sp | MMAN_br); if (n->norm->Bd.type == DISP_unfilled || n->norm->Bd.type == DISP_literal) print_line(".nf", 0); if (n->norm->Bd.comp == 0 && roff_node_prev(n->parent) != NULL) outflags |= MMAN_sp; print_offs(n->norm->Bd.offs, 1); return 1; } static void post_bd(DECL_ARGS) { enum roff_tok bef, now; /* Close out this display. */ print_line(".RE", MMAN_nl); bef = n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi; if (n->last == NULL) now = n->norm->Bd.type == DISP_unfilled || n->norm->Bd.type == DISP_literal ? ROFF_nf : ROFF_fi; else if (n->last->tok == ROFF_nf) now = ROFF_nf; else if (n->last->tok == ROFF_fi) now = ROFF_fi; else now = n->last->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi; if (bef != now) { outflags |= MMAN_nl; print_word("."); outflags &= ~MMAN_spc; print_word(roff_name[bef]); outflags |= MMAN_nl; } /* Maybe we are inside an enclosing list? */ if (roff_node_next(n->parent) != NULL) mid_it(); } static int pre_bf(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: return 1; case ROFFT_BODY: break; default: return 0; } switch (n->norm->Bf.font) { case FONT_Em: font_push('I'); break; case FONT_Sy: font_push('B'); break; default: font_push('R'); break; } return 1; } static void post_bf(DECL_ARGS) { if (n->type == ROFFT_BODY) font_pop(); } static int pre_bk(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: return 1; case ROFFT_BODY: case ROFFT_ELEM: outflags |= MMAN_Bk; return 1; default: return 0; } } static void post_bk(DECL_ARGS) { switch (n->type) { case ROFFT_ELEM: while ((n = n->parent) != NULL) if (n->tok == MDOC_Bk) return; /* FALLTHROUGH */ case ROFFT_BODY: outflags &= ~MMAN_Bk; break; default: break; } } static int pre_bl(DECL_ARGS) { size_t icol; /* * print_offs() will increase the -offset to account for * a possible enclosing .It, but any enclosed .It blocks * just nest and do not add up their indentation. */ if (n->norm->Bl.offs) { print_offs(n->norm->Bl.offs, 0); Bl_stack[Bl_stack_len++] = 0; } switch (n->norm->Bl.type) { case LIST_enum: n->norm->Bl.count = 0; return 1; case LIST_column: break; default: return 1; } if (n->child != NULL) { print_line(".TS", MMAN_nl); for (icol = 0; icol < n->norm->Bl.ncols; icol++) print_word("l"); print_word("."); } outflags |= MMAN_nl; return 1; } static void post_bl(DECL_ARGS) { switch (n->norm->Bl.type) { case LIST_column: if (n->child != NULL) print_line(".TE", 0); break; case LIST_enum: n->norm->Bl.count = 0; break; default: break; } if (n->norm->Bl.offs) { print_line(".RE", MMAN_nl); assert(Bl_stack_len); Bl_stack_len--; assert(Bl_stack[Bl_stack_len] == 0); } else { outflags |= MMAN_PP | MMAN_nl; outflags &= ~(MMAN_sp | MMAN_br); } /* Maybe we are inside an enclosing list? */ if (roff_node_next(n->parent) != NULL) mid_it(); } static void pre_br(DECL_ARGS) { outflags |= MMAN_br; } static int pre_dl(DECL_ARGS) { print_offs("6n", 0); return 1; } static void post_dl(DECL_ARGS) { print_line(".RE", MMAN_nl); /* Maybe we are inside an enclosing list? */ if (roff_node_next(n->parent) != NULL) mid_it(); } static int pre_em(DECL_ARGS) { font_push('I'); return 1; } static int pre_en(DECL_ARGS) { if (NULL == n->norm->Es || NULL == n->norm->Es->child) return 1; print_word(n->norm->Es->child->string); outflags &= ~MMAN_spc; return 1; } static void post_en(DECL_ARGS) { if (NULL == n->norm->Es || NULL == n->norm->Es->child || NULL == n->norm->Es->child->next) return; outflags &= ~MMAN_spc; print_word(n->norm->Es->child->next->string); return; } static int pre_eo(DECL_ARGS) { if (n->end == ENDBODY_NOT && n->parent->head->child == NULL && n->child != NULL && n->child->end != ENDBODY_NOT) print_word("\\&"); else if (n->end != ENDBODY_NOT ? n->child != NULL : n->parent->head->child != NULL && (n->child != NULL || (n->parent->tail != NULL && n->parent->tail->child != NULL))) outflags &= ~(MMAN_spc | MMAN_nl); return 1; } static void post_eo(DECL_ARGS) { int body, tail; if (n->end != ENDBODY_NOT) { outflags |= MMAN_spc; return; } body = n->child != NULL || n->parent->head->child != NULL; tail = n->parent->tail != NULL && n->parent->tail->child != NULL; if (body && tail) outflags &= ~MMAN_spc; else if ( ! (body || tail)) print_word("\\&"); else if ( ! tail) outflags |= MMAN_spc; } static int pre_fa(DECL_ARGS) { int am_Fa; am_Fa = MDOC_Fa == n->tok; if (am_Fa) n = n->child; while (NULL != n) { font_push('I'); if (am_Fa || NODE_SYNPRETTY & n->flags) outflags |= MMAN_nbrword; print_node(meta, n); font_pop(); if (NULL != (n = n->next)) print_word(","); } return 0; } static void post_fa(DECL_ARGS) { struct roff_node *nn; if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa) print_word(","); } static int pre_fd(DECL_ARGS) { pre_syn(n); font_push('B'); return 1; } static void post_fd(DECL_ARGS) { font_pop(); outflags |= MMAN_br; } static int pre_fl(DECL_ARGS) { font_push('B'); print_word("\\-"); if (n->child != NULL) outflags &= ~MMAN_spc; return 1; } static void post_fl(DECL_ARGS) { struct roff_node *nn; font_pop(); if (n->child == NULL && ((nn = roff_node_next(n)) != NULL && nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)) outflags &= ~MMAN_spc; } static int pre_fn(DECL_ARGS) { pre_syn(n); n = n->child; if (NULL == n) return 0; if (NODE_SYNPRETTY & n->flags) print_block(".HP 4n", MMAN_nl); font_push('B'); print_node(meta, n); font_pop(); outflags &= ~MMAN_spc; print_word("("); outflags &= ~MMAN_spc; n = n->next; if (NULL != n) pre_fa(meta, n); return 0; } static void post_fn(DECL_ARGS) { print_word(")"); if (NODE_SYNPRETTY & n->flags) { print_word(";"); outflags |= MMAN_PP; } } static int pre_fo(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: pre_syn(n); break; case ROFFT_HEAD: if (n->child == NULL) return 0; if (NODE_SYNPRETTY & n->flags) print_block(".HP 4n", MMAN_nl); font_push('B'); break; case ROFFT_BODY: outflags &= ~(MMAN_spc | MMAN_nl); print_word("("); outflags &= ~MMAN_spc; break; default: break; } return 1; } static void post_fo(DECL_ARGS) { switch (n->type) { case ROFFT_HEAD: if (n->child != NULL) font_pop(); break; case ROFFT_BODY: post_fn(meta, n); break; default: break; } } static int pre_Ft(DECL_ARGS) { pre_syn(n); font_push('I'); return 1; } static void pre_ft(DECL_ARGS) { print_line(".ft", 0); print_word(n->child->string); outflags |= MMAN_nl; } static int pre_in(DECL_ARGS) { if (NODE_SYNPRETTY & n->flags) { pre_syn(n); font_push('B'); print_word("#include <"); outflags &= ~MMAN_spc; } else { print_word("<"); outflags &= ~MMAN_spc; font_push('I'); } return 1; } static void post_in(DECL_ARGS) { if (NODE_SYNPRETTY & n->flags) { outflags &= ~MMAN_spc; print_word(">"); font_pop(); outflags |= MMAN_br; } else { font_pop(); outflags &= ~MMAN_spc; print_word(">"); } } static int pre_it(DECL_ARGS) { const struct roff_node *bln; switch (n->type) { case ROFFT_HEAD: outflags |= MMAN_PP | MMAN_nl; bln = n->parent->parent; if (bln->norm->Bl.comp == 0 || (n->parent->prev == NULL && roff_node_prev(bln->parent) == NULL)) outflags |= MMAN_sp; outflags &= ~MMAN_br; switch (bln->norm->Bl.type) { case LIST_item: return 0; case LIST_inset: case LIST_diag: case LIST_ohang: if (bln->norm->Bl.type == LIST_diag) print_line(".B \"", 0); else print_line(".BR \\& \"", 0); outflags &= ~MMAN_spc; return 1; case LIST_bullet: case LIST_dash: case LIST_hyphen: print_width(&bln->norm->Bl, NULL); TPremain = 0; outflags |= MMAN_nl; font_push('B'); if (LIST_bullet == bln->norm->Bl.type) print_word("\\(bu"); else print_word("-"); font_pop(); outflags |= MMAN_nl; return 0; case LIST_enum: print_width(&bln->norm->Bl, NULL); TPremain = 0; outflags |= MMAN_nl; print_count(&bln->norm->Bl.count); outflags |= MMAN_nl; return 0; case LIST_hang: print_width(&bln->norm->Bl, n->child); TPremain = 0; outflags |= MMAN_nl; return 1; case LIST_tag: print_width(&bln->norm->Bl, n->child); putchar('\n'); outflags &= ~MMAN_spc; return 1; default: return 1; } default: break; } return 1; } /* * This function is called after closing out an indented block. * If we are inside an enclosing list, restore its indentation. */ static void mid_it(void) { char buf[24]; /* Nothing to do outside a list. */ if (0 == Bl_stack_len || 0 == Bl_stack[Bl_stack_len - 1]) return; /* The indentation has already been set up. */ if (Bl_stack_post[Bl_stack_len - 1]) return; /* Restore the indentation of the enclosing list. */ print_line(".RS", MMAN_Bk_susp); (void)snprintf(buf, sizeof(buf), "%dn", Bl_stack[Bl_stack_len - 1]); print_word(buf); /* Remeber to close out this .RS block later. */ Bl_stack_post[Bl_stack_len - 1] = 1; } static void post_it(DECL_ARGS) { const struct roff_node *bln; bln = n->parent->parent; switch (n->type) { case ROFFT_HEAD: switch (bln->norm->Bl.type) { case LIST_diag: outflags &= ~MMAN_spc; print_word("\\ "); break; case LIST_ohang: outflags |= MMAN_br; break; default: break; } break; case ROFFT_BODY: switch (bln->norm->Bl.type) { case LIST_bullet: case LIST_dash: case LIST_hyphen: case LIST_enum: case LIST_hang: case LIST_tag: assert(Bl_stack_len); Bl_stack[--Bl_stack_len] = 0; /* * Our indentation had to be restored * after a child display or child list. * Close out that indentation block now. */ if (Bl_stack_post[Bl_stack_len]) { print_line(".RE", MMAN_nl); Bl_stack_post[Bl_stack_len] = 0; } break; case LIST_column: if (NULL != n->next) { putchar('\t'); outflags &= ~MMAN_spc; } break; default: break; } break; default: break; } } static void post_lb(DECL_ARGS) { if (SEC_LIBRARY == n->sec) outflags |= MMAN_br; } static int pre_lk(DECL_ARGS) { const struct roff_node *link, *descr, *punct; if ((link = n->child) == NULL) return 0; /* Find beginning of trailing punctuation. */ punct = n->last; while (punct != link && punct->flags & NODE_DELIMC) punct = punct->prev; punct = punct->next; /* Link text. */ if ((descr = link->next) != NULL && descr != punct) { font_push('I'); while (descr != punct) { print_word(descr->string); descr = descr->next; } font_pop(); print_word(":"); } /* Link target. */ font_push('B'); print_word(link->string); font_pop(); /* Trailing punctuation. */ while (punct != NULL) { print_word(punct->string); punct = punct->next; } return 0; } static void pre_onearg(DECL_ARGS) { outflags |= MMAN_nl; print_word("."); outflags &= ~MMAN_spc; print_word(roff_name[n->tok]); if (n->child != NULL) print_word(n->child->string); outflags |= MMAN_nl; if (n->tok == ROFF_ce) for (n = n->child->next; n != NULL; n = n->next) print_node(meta, n); } static int pre_li(DECL_ARGS) { font_push('R'); return 1; } static int pre_nm(DECL_ARGS) { char *name; switch (n->type) { case ROFFT_BLOCK: outflags |= MMAN_Bk; pre_syn(n); return 1; case ROFFT_HEAD: case ROFFT_ELEM: break; default: return 1; } name = n->child == NULL ? NULL : n->child->string; if (name == NULL) return 0; if (n->type == ROFFT_HEAD) { if (roff_node_prev(n->parent) == NULL) outflags |= MMAN_sp; print_block(".HP", 0); printf(" %dn", man_strlen(name) + 1); outflags |= MMAN_nl; } font_push('B'); return 1; } static void post_nm(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: outflags &= ~MMAN_Bk; break; case ROFFT_HEAD: case ROFFT_ELEM: if (n->child != NULL && n->child->string != NULL) font_pop(); break; default: break; } } static int pre_no(DECL_ARGS) { outflags |= MMAN_spc_force; return 1; } static void pre_noarg(DECL_ARGS) { outflags |= MMAN_nl; print_word("."); outflags &= ~MMAN_spc; print_word(roff_name[n->tok]); outflags |= MMAN_nl; } static int pre_ns(DECL_ARGS) { outflags &= ~MMAN_spc; return 0; } static void post_pf(DECL_ARGS) { if ( ! (n->next == NULL || n->next->flags & NODE_LINE)) outflags &= ~MMAN_spc; } static int pre_pp(DECL_ARGS) { if (MDOC_It != n->parent->tok) outflags |= MMAN_PP; outflags |= MMAN_sp | MMAN_nl; outflags &= ~MMAN_br; return 0; } static int pre_rs(DECL_ARGS) { if (SEC_SEE_ALSO == n->sec) { outflags |= MMAN_PP | MMAN_sp | MMAN_nl; outflags &= ~MMAN_br; } return 1; } static int pre_skip(DECL_ARGS) { return 0; } static int pre_sm(DECL_ARGS) { if (NULL == n->child) outflags ^= MMAN_Sm; else if (0 == strcmp("on", n->child->string)) outflags |= MMAN_Sm; else outflags &= ~MMAN_Sm; if (MMAN_Sm & outflags) outflags |= MMAN_spc; return 0; } static void pre_sp(DECL_ARGS) { if (outflags & MMAN_PP) { outflags &= ~MMAN_PP; print_line(".PP", 0); } else { print_line(".sp", 0); if (n->child != NULL) print_word(n->child->string); } outflags |= MMAN_nl; } static int pre_sy(DECL_ARGS) { font_push('B'); return 1; } static void pre_ta(DECL_ARGS) { print_line(".ta", 0); for (n = n->child; n != NULL; n = n->next) print_word(n->string); outflags |= MMAN_nl; } static int pre_vt(DECL_ARGS) { if (NODE_SYNPRETTY & n->flags) { switch (n->type) { case ROFFT_BLOCK: pre_syn(n); return 1; case ROFFT_BODY: break; default: return 0; } } font_push('I'); return 1; } static void post_vt(DECL_ARGS) { if (n->flags & NODE_SYNPRETTY && n->type != ROFFT_BODY) return; font_pop(); } static int pre_xr(DECL_ARGS) { n = n->child; if (NULL == n) return 0; print_node(meta, n); n = n->next; if (NULL == n) return 0; outflags &= ~MMAN_spc; print_word("("); print_node(meta, n); print_word(")"); return 0; } /* $Id: mdoc_markdown.c,v 1.37 2021/08/10 12:55:03 schwarze Exp $ */ /* * Copyright (c) 2017, 2018, 2020 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. * * Markdown formatter for mdoc(7) used by mandoc(1). */ #include "config.h" #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 *); int (*pre)(struct roff_node *); void (*post)(struct roff_node *); const char *prefix; /* pre-node string constant */ const char *suffix; /* post-node string constant */ }; static void md_nodelist(struct roff_node *); static void md_node(struct roff_node *); static const char *md_stack(char); static void md_preword(void); static void md_rawword(const char *); static void md_word(const char *); static void md_named(const char *); static void md_char(unsigned char); static void md_uri(const char *); static int md_cond_head(struct roff_node *); static int md_cond_body(struct roff_node *); static int md_pre_abort(struct roff_node *); static int md_pre_raw(struct roff_node *); static int md_pre_word(struct roff_node *); static int md_pre_skip(struct roff_node *); static void md_pre_syn(struct roff_node *); static int md_pre_An(struct roff_node *); static int md_pre_Ap(struct roff_node *); static int md_pre_Bd(struct roff_node *); static int md_pre_Bk(struct roff_node *); static int md_pre_Bl(struct roff_node *); static int md_pre_D1(struct roff_node *); static int md_pre_Dl(struct roff_node *); static int md_pre_En(struct roff_node *); static int md_pre_Eo(struct roff_node *); static int md_pre_Fa(struct roff_node *); static int md_pre_Fd(struct roff_node *); static int md_pre_Fn(struct roff_node *); static int md_pre_Fo(struct roff_node *); static int md_pre_In(struct roff_node *); static int md_pre_It(struct roff_node *); static int md_pre_Lk(struct roff_node *); static int md_pre_Mt(struct roff_node *); static int md_pre_Nd(struct roff_node *); static int md_pre_Nm(struct roff_node *); static int md_pre_No(struct roff_node *); static int md_pre_Ns(struct roff_node *); static int md_pre_Pp(struct roff_node *); static int md_pre_Rs(struct roff_node *); static int md_pre_Sh(struct roff_node *); static int md_pre_Sm(struct roff_node *); static int md_pre_Vt(struct roff_node *); static int md_pre_Xr(struct roff_node *); static int md_pre__T(struct roff_node *); static int md_pre_br(struct roff_node *); static void md_post_raw(struct roff_node *); static void md_post_word(struct roff_node *); static void md_post_pc(struct roff_node *); static void md_post_Bk(struct roff_node *); static void md_post_Bl(struct roff_node *); static void md_post_D1(struct roff_node *); static void md_post_En(struct roff_node *); static void md_post_Eo(struct roff_node *); static void md_post_Fa(struct roff_node *); static void md_post_Fd(struct roff_node *); static void md_post_Fl(struct roff_node *); static void md_post_Fn(struct roff_node *); static void md_post_Fo(struct roff_node *); static void md_post_In(struct roff_node *); static void md_post_It(struct roff_node *); static void md_post_Lb(struct roff_node *); static void md_post_Nm(struct roff_node *); static void md_post_Pf(struct roff_node *); static void md_post_Vt(struct roff_node *); static void md_post__T(struct roff_node *); static const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = { { NULL, NULL, NULL, NULL, NULL }, /* Dd */ { NULL, NULL, NULL, NULL, NULL }, /* Dt */ { NULL, NULL, NULL, NULL, NULL }, /* Os */ { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */ { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */ { NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */ { md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */ { md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */ { md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */ { NULL, NULL, NULL, NULL, NULL }, /* Ed */ { md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */ { NULL, NULL, NULL, NULL, NULL }, /* El */ { NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */ { NULL, md_pre_An, NULL, NULL, NULL }, /* An */ { NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */ { NULL, NULL, NULL, NULL, NULL }, /* Ex */ { NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */ { NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */ { NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */ { NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */ { NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */ { NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */ { md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */ { NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */ { NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */ { NULL, NULL, NULL, NULL, NULL }, /* Rv */ { NULL, NULL, NULL, NULL, NULL }, /* St */ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */ { NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */ { NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %A */ { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %D */ { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */ { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %N */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %O */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %P */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %R */ { NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %V */ { NULL, NULL, NULL, NULL, NULL }, /* Ac */ { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */ { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */ { NULL, NULL, NULL, NULL, NULL }, /* At */ { NULL, NULL, NULL, NULL, NULL }, /* Bc */ { NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */ { NULL, NULL, NULL, NULL, NULL }, /* Bsx */ { NULL, NULL, NULL, NULL, NULL }, /* Bx */ { NULL, NULL, NULL, NULL, NULL }, /* Db */ { NULL, NULL, NULL, NULL, NULL }, /* Dc */ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */ { NULL, NULL, NULL, NULL, NULL }, /* Ec */ { NULL, NULL, NULL, NULL, NULL }, /* Ef */ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */ { md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */ { NULL, NULL, NULL, NULL, NULL }, /* Fx */ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */ { NULL, md_pre_No, NULL, NULL, NULL }, /* No */ { NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */ { NULL, NULL, NULL, NULL, NULL }, /* Nx */ { NULL, NULL, NULL, NULL, NULL }, /* Ox */ { NULL, NULL, NULL, NULL, NULL }, /* Pc */ { NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */ { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */ { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */ { NULL, NULL, NULL, NULL, NULL }, /* Qc */ { md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */ { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */ { NULL, NULL, NULL, NULL, NULL }, /* Re */ { md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */ { NULL, NULL, NULL, NULL, NULL }, /* Sc */ { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */ { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */ { NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */ { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */ { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */ { NULL, NULL, NULL, NULL, NULL }, /* Ux */ { NULL, NULL, NULL, NULL, NULL }, /* Xc */ { NULL, NULL, NULL, NULL, NULL }, /* Xo */ { NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */ { NULL, NULL, NULL, NULL, NULL }, /* Fc */ { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */ { NULL, NULL, NULL, NULL, NULL }, /* Oc */ { NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */ { NULL, NULL, NULL, NULL, NULL }, /* Ek */ { NULL, NULL, NULL, NULL, NULL }, /* Bt */ { NULL, NULL, NULL, NULL, NULL }, /* Hf */ { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */ { NULL, NULL, NULL, NULL, NULL }, /* Ud */ { NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */ { NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */ { NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */ { NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */ { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */ { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */ { NULL, NULL, NULL, NULL, NULL }, /* Brc */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %C */ { NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */ { md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */ { NULL, NULL, NULL, NULL, NULL }, /* Dx */ { NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */ { NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */ { NULL, NULL, NULL, NULL, NULL }, /* Ta */ { NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */ }; static const struct md_act *md_act(enum roff_tok); static int outflags; #define MD_spc (1 << 0) /* Blank character before next word. */ #define MD_spc_force (1 << 1) /* Even before trailing punctuation. */ #define MD_nonl (1 << 2) /* Prevent linebreak in markdown code. */ #define MD_nl (1 << 3) /* Break markdown code line. */ #define MD_br (1 << 4) /* Insert an output line break. */ #define MD_sp (1 << 5) /* Insert a paragraph break. */ #define MD_Sm (1 << 6) /* Horizontal spacing mode. */ #define MD_Bk (1 << 7) /* Word keep mode. */ #define MD_An_split (1 << 8) /* Author mode is "split". */ #define MD_An_nosplit (1 << 9) /* Author mode is "nosplit". */ static int escflags; /* Escape in generated markdown code: */ #define ESC_BOL (1 << 0) /* "#*+-" near the beginning of a line. */ #define ESC_NUM (1 << 1) /* "." after a leading number. */ #define ESC_HYP (1 << 2) /* "(" immediately after "]". */ #define ESC_SQU (1 << 4) /* "]" when "[" is open. */ #define ESC_FON (1 << 5) /* "*" immediately after unrelated "*". */ #define ESC_EOL (1 << 6) /* " " at the and of a line. */ static int code_blocks, quote_blocks, list_blocks; static int outcount; static const struct md_act * md_act(enum roff_tok tok) { assert(tok >= MDOC_Dd && tok <= MDOC_MAX); return md_acts + (tok - MDOC_Dd); } void markdown_mdoc(void *arg, const struct roff_meta *mdoc) { outflags = MD_Sm; md_word(mdoc->title); if (mdoc->msec != NULL) { outflags &= ~MD_spc; md_word("("); md_word(mdoc->msec); md_word(")"); } md_word("-"); md_word(mdoc->vol); if (mdoc->arch != NULL) { md_word("("); md_word(mdoc->arch); md_word(")"); } outflags |= MD_sp; md_nodelist(mdoc->first->child); outflags |= MD_sp; md_word(mdoc->os); md_word("-"); md_word(mdoc->date); putchar('\n'); } static void md_nodelist(struct roff_node *n) { while (n != NULL) { md_node(n); n = n->next; } } static void md_node(struct roff_node *n) { const struct md_act *act; int cond, process_children; if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) return; if (outflags & MD_nonl) outflags &= ~(MD_nl | MD_sp); else if (outflags & MD_spc && n->flags & NODE_LINE && !roff_node_transparent(n)) outflags |= MD_nl; act = NULL; cond = 0; process_children = 1; n->flags &= ~NODE_ENDED; if (n->type == ROFFT_TEXT) { if (n->flags & NODE_DELIMC) outflags &= ~(MD_spc | MD_spc_force); else if (outflags & MD_Sm) outflags |= MD_spc_force; md_word(n->string); if (n->flags & NODE_DELIMO) outflags &= ~(MD_spc | MD_spc_force); else if (outflags & MD_Sm) outflags |= MD_spc; } else if (n->tok < ROFF_MAX) { switch (n->tok) { case ROFF_br: process_children = md_pre_br(n); break; case ROFF_sp: process_children = md_pre_Pp(n); break; default: process_children = 0; break; } } else { act = md_act(n->tok); cond = act->cond == NULL || (*act->cond)(n); if (cond && act->pre != NULL && (n->end == ENDBODY_NOT || n->child != NULL)) process_children = (*act->pre)(n); } if (process_children && n->child != NULL) md_nodelist(n->child); if (n->flags & NODE_ENDED) return; if (cond && act->post != NULL) (*act->post)(n); if (n->end != ENDBODY_NOT) n->body->flags |= NODE_ENDED; } static const char * md_stack(char c) { static char *stack; static size_t sz; static size_t cur; switch (c) { case '\0': break; case (char)-1: assert(cur); stack[--cur] = '\0'; break; default: if (cur + 1 >= sz) { sz += 8; stack = mandoc_realloc(stack, sz); } stack[cur] = c; stack[++cur] = '\0'; break; } return stack == NULL ? "" : stack; } /* * Handle vertical and horizontal spacing. */ static void md_preword(void) { const char *cp; /* * If a list block is nested inside a code block or a blockquote, * blank lines for paragraph breaks no longer work; instead, * they terminate the list. Work around this markdown issue * by using mere line breaks instead. */ if (list_blocks && outflags & MD_sp) { outflags &= ~MD_sp; outflags |= MD_br; } /* * End the old line if requested. * Escape whitespace at the end of the markdown line * such that it won't look like an output line break. */ if (outflags & MD_sp) putchar('\n'); else if (outflags & MD_br) { putchar(' '); putchar(' '); } else if (outflags & MD_nl && escflags & ESC_EOL) md_named("zwnj"); /* Start a new line if necessary. */ if (outflags & (MD_nl | MD_br | MD_sp)) { putchar('\n'); for (cp = md_stack('\0'); *cp != '\0'; cp++) { putchar(*cp); if (*cp == '>') putchar(' '); } outflags &= ~(MD_nl | MD_br | MD_sp); escflags = ESC_BOL; outcount = 0; /* Handle horizontal spacing. */ } else if (outflags & MD_spc) { if (outflags & MD_Bk) fputs(" ", 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: case ESCAPE_FONTCB: nextfont = "**"; break; case ESCAPE_FONTITALIC: case ESCAPE_FONTCI: nextfont = "*"; break; case ESCAPE_FONTBI: nextfont = "***"; break; case ESCAPE_FONT: case ESCAPE_FONTCR: case ESCAPE_FONTROMAN: nextfont = ""; break; case ESCAPE_FONTPREV: nextfont = prevfont; break; case ESCAPE_BREAK: breakline = 1; break; case ESCAPE_NOSPACE: case ESCAPE_SKIPCHAR: case ESCAPE_OVERSTRIKE: /* XXX not implemented */ /* FALLTHROUGH */ case ESCAPE_ERROR: default: break; } if (nextfont != NULL && !code_blocks) { if (*currfont != '\0') { outflags &= ~MD_spc; md_rawword(currfont); } prevfont = currfont; currfont = nextfont; if (*currfont != '\0') { outflags &= ~MD_spc; md_rawword(currfont); } } if (uc) { if ((uc < 0x20 && uc != 0x09) || (uc > 0x7E && uc < 0xA0)) uc = 0xFFFD; if (code_blocks) { seq = mchars_uc2str(uc); fputs(seq, stdout); outcount += strlen(seq); } else { printf("&#%d;", uc); outcount++; } escflags &= ~ESC_FON; } c = '\0'; break; case ']': bs = escflags & ESC_SQU && !code_blocks; escflags |= ESC_HYP; break; default: break; } if (bs) putchar('\\'); md_char(c); if (breakline && (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) { printf(" \n"); breakline = 0; while (*s == ' ' || *s == ASCII_NBRSP) s++; } } if (*currfont != '\0') { outflags &= ~MD_spc; md_rawword(currfont); } else if (s[-2] == ' ') escflags |= ESC_EOL; else escflags &= ~ESC_EOL; } /* * Print a single HTML named character reference. */ static void md_named(const char *s) { printf("&%s;", s); escflags &= ~(ESC_FON | ESC_EOL); outcount++; } /* * Print a single raw character and maintain certain escape flags. */ static void md_char(unsigned char c) { if (c != '\0') { putchar(c); if (c == '*') escflags |= ESC_FON; else escflags &= ~ESC_FON; outcount++; } if (c != ']') escflags &= ~ESC_HYP; if (c == ' ' || c == '\t' || c == '>') return; if (isdigit(c) == 0) escflags &= ~ESC_NUM; else if (escflags & ESC_BOL) escflags |= ESC_NUM; escflags &= ~ESC_BOL; } static int md_cond_head(struct roff_node *n) { return n->type == ROFFT_HEAD; } static int md_cond_body(struct roff_node *n) { return n->type == ROFFT_BODY; } static int md_pre_abort(struct roff_node *n) { abort(); } static int md_pre_raw(struct roff_node *n) { const char *prefix; if ((prefix = md_act(n->tok)->prefix) != NULL) { md_rawword(prefix); outflags &= ~MD_spc; if (*prefix == '`') code_blocks++; } return 1; } static void md_post_raw(struct roff_node *n) { const char *suffix; if ((suffix = md_act(n->tok)->suffix) != NULL) { outflags &= ~(MD_spc | MD_nl); md_rawword(suffix); if (*suffix == '`') code_blocks--; } } static int md_pre_word(struct roff_node *n) { const char *prefix; if ((prefix = md_act(n->tok)->prefix) != NULL) { md_word(prefix); outflags &= ~MD_spc; } return 1; } static void md_post_word(struct roff_node *n) { const char *suffix; if ((suffix = md_act(n->tok)->suffix) != NULL) { outflags &= ~(MD_spc | MD_nl); md_word(suffix); } } static void md_post_pc(struct roff_node *n) { struct roff_node *nn; md_post_raw(n); if (n->parent->tok != MDOC_Rs) return; if ((nn = roff_node_next(n)) != NULL) { md_word(","); if (nn->tok == n->tok && (nn = roff_node_prev(n)) != NULL && nn->tok == n->tok) md_word("and"); } else { md_word("."); outflags |= MD_nl; } } static int md_pre_skip(struct roff_node *n) { return 0; } static void md_pre_syn(struct roff_node *n) { struct roff_node *np; if ((n->flags & NODE_SYNPRETTY) == 0 || (np = roff_node_prev(n)) == NULL) return; if (np->tok == n->tok && n->tok != MDOC_Ft && n->tok != MDOC_Fo && n->tok != MDOC_Fn) { outflags |= MD_br; return; } switch (np->tok) { case MDOC_Fd: case MDOC_Fn: case MDOC_Fo: case MDOC_In: case MDOC_Vt: outflags |= MD_sp; break; case MDOC_Ft: if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) { outflags |= MD_sp; break; } /* FALLTHROUGH */ default: outflags |= MD_br; break; } } static int md_pre_An(struct roff_node *n) { switch (n->norm->An.auth) { case AUTH_split: outflags &= ~MD_An_nosplit; outflags |= MD_An_split; return 0; case AUTH_nosplit: outflags &= ~MD_An_split; outflags |= MD_An_nosplit; return 0; default: if (outflags & MD_An_split) outflags |= MD_br; else if (n->sec == SEC_AUTHORS && ! (outflags & MD_An_nosplit)) outflags |= MD_An_split; return 1; } } static int md_pre_Ap(struct roff_node *n) { outflags &= ~MD_spc; md_word("'"); outflags &= ~MD_spc; return 0; } static int md_pre_Bd(struct roff_node *n) { switch (n->norm->Bd.type) { case DISP_unfilled: case DISP_literal: return md_pre_Dl(n); default: return md_pre_D1(n); } } static int md_pre_Bk(struct roff_node *n) { switch (n->type) { case ROFFT_BLOCK: return 1; case ROFFT_BODY: outflags |= MD_Bk; return 1; default: return 0; } } static void md_post_Bk(struct roff_node *n) { if (n->type == ROFFT_BODY) outflags &= ~MD_Bk; } static int md_pre_Bl(struct roff_node *n) { n->norm->Bl.count = 0; if (n->norm->Bl.type == LIST_column) md_pre_Dl(n); outflags |= MD_sp; return 1; } static void md_post_Bl(struct roff_node *n) { n->norm->Bl.count = 0; if (n->norm->Bl.type == LIST_column) md_post_D1(n); outflags |= MD_sp; } static int md_pre_D1(struct roff_node *n) { /* * Markdown blockquote syntax does not work inside code blocks. * The best we can do is fall back to another nested code block. */ if (code_blocks) { md_stack('\t'); code_blocks++; } else { md_stack('>'); quote_blocks++; } outflags |= MD_sp; return 1; } static void md_post_D1(struct roff_node *n) { md_stack((char)-1); if (code_blocks) code_blocks--; else quote_blocks--; outflags |= MD_sp; } static int md_pre_Dl(struct roff_node *n) { /* * Markdown code block syntax does not work inside blockquotes. * The best we can do is fall back to another nested blockquote. */ if (quote_blocks) { md_stack('>'); quote_blocks++; } else { md_stack('\t'); code_blocks++; } outflags |= MD_sp; return 1; } static int md_pre_En(struct roff_node *n) { if (n->norm->Es == NULL || n->norm->Es->child == NULL) return 1; md_word(n->norm->Es->child->string); outflags &= ~MD_spc; return 1; } static void md_post_En(struct roff_node *n) { if (n->norm->Es == NULL || n->norm->Es->child == NULL || n->norm->Es->child->next == NULL) return; outflags &= ~MD_spc; md_word(n->norm->Es->child->next->string); } static int md_pre_Eo(struct roff_node *n) { if (n->end == ENDBODY_NOT && n->parent->head->child == NULL && n->child != NULL && n->child->end != ENDBODY_NOT) md_preword(); else if (n->end != ENDBODY_NOT ? n->child != NULL : n->parent->head->child != NULL && (n->child != NULL || (n->parent->tail != NULL && n->parent->tail->child != NULL))) outflags &= ~(MD_spc | MD_nl); return 1; } static void md_post_Eo(struct roff_node *n) { if (n->end != ENDBODY_NOT) { outflags |= MD_spc; return; } if (n->child == NULL && n->parent->head->child == NULL) return; if (n->parent->tail != NULL && n->parent->tail->child != NULL) outflags &= ~MD_spc; else outflags |= MD_spc; } static int md_pre_Fa(struct roff_node *n) { int am_Fa; am_Fa = n->tok == MDOC_Fa; if (am_Fa) n = n->child; while (n != NULL) { md_rawword("*"); outflags &= ~MD_spc; md_node(n); outflags &= ~MD_spc; md_rawword("*"); if ((n = n->next) != NULL) md_word(","); } return 0; } static void md_post_Fa(struct roff_node *n) { struct roff_node *nn; if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa) md_word(","); } static int md_pre_Fd(struct roff_node *n) { md_pre_syn(n); md_pre_raw(n); return 1; } static void md_post_Fd(struct roff_node *n) { md_post_raw(n); outflags |= MD_br; } static void md_post_Fl(struct roff_node *n) { struct roff_node *nn; md_post_raw(n); if (n->child == NULL && (nn = roff_node_next(n)) != NULL && nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0) outflags &= ~MD_spc; } static int md_pre_Fn(struct roff_node *n) { md_pre_syn(n); if ((n = n->child) == NULL) return 0; md_rawword("**"); outflags &= ~MD_spc; md_node(n); outflags &= ~MD_spc; md_rawword("**"); outflags &= ~MD_spc; md_word("("); if ((n = n->next) != NULL) md_pre_Fa(n); return 0; } static void md_post_Fn(struct roff_node *n) { md_word(")"); if (n->flags & NODE_SYNPRETTY) { md_word(";"); outflags |= MD_sp; } } static int md_pre_Fo(struct roff_node *n) { switch (n->type) { case ROFFT_BLOCK: md_pre_syn(n); break; case ROFFT_HEAD: if (n->child == NULL) return 0; md_pre_raw(n); break; case ROFFT_BODY: outflags &= ~(MD_spc | MD_nl); md_word("("); break; default: break; } return 1; } static void md_post_Fo(struct roff_node *n) { switch (n->type) { case ROFFT_HEAD: if (n->child != NULL) md_post_raw(n); break; case ROFFT_BODY: md_post_Fn(n); break; default: break; } } static int md_pre_In(struct roff_node *n) { if (n->flags & NODE_SYNPRETTY) { md_pre_syn(n); md_rawword("**"); outflags &= ~MD_spc; md_word("#include <"); } else { md_word("<"); outflags &= ~MD_spc; md_rawword("*"); } outflags &= ~MD_spc; return 1; } static void md_post_In(struct roff_node *n) { if (n->flags & NODE_SYNPRETTY) { outflags &= ~MD_spc; md_rawword(">**"); outflags |= MD_nl; } else { outflags &= ~MD_spc; md_rawword("*>"); } } static int md_pre_It(struct roff_node *n) { struct roff_node *bln; switch (n->type) { case ROFFT_BLOCK: return 1; case ROFFT_HEAD: bln = n->parent->parent; if (bln->norm->Bl.comp == 0 && bln->norm->Bl.type != LIST_column) outflags |= MD_sp; outflags |= MD_nl; switch (bln->norm->Bl.type) { case LIST_item: outflags |= MD_br; return 0; case LIST_inset: case LIST_diag: case LIST_ohang: outflags |= MD_br; return 1; case LIST_tag: case LIST_hang: outflags |= MD_sp; return 1; case LIST_bullet: md_rawword("*\t"); break; case LIST_dash: case LIST_hyphen: md_rawword("-\t"); break; case LIST_enum: md_preword(); if (bln->norm->Bl.count < 99) bln->norm->Bl.count++; printf("%d.\t", bln->norm->Bl.count); escflags &= ~ESC_FON; break; case LIST_column: outflags |= MD_br; return 0; default: return 0; } outflags &= ~MD_spc; outflags |= MD_nonl; outcount = 0; md_stack('\t'); if (code_blocks || quote_blocks) list_blocks++; return 0; case ROFFT_BODY: bln = n->parent->parent; switch (bln->norm->Bl.type) { case LIST_ohang: outflags |= MD_br; break; case LIST_tag: case LIST_hang: md_pre_D1(n); break; default: break; } return 1; default: return 0; } } static void md_post_It(struct roff_node *n) { struct roff_node *bln; int i, nc; if (n->type != ROFFT_BODY) return; bln = n->parent->parent; switch (bln->norm->Bl.type) { case LIST_bullet: case LIST_dash: case LIST_hyphen: case LIST_enum: md_stack((char)-1); if (code_blocks || quote_blocks) list_blocks--; break; case LIST_tag: case LIST_hang: md_post_D1(n); break; case LIST_column: if (n->next == NULL) break; /* Calculate the array index of the current column. */ i = 0; while ((n = n->prev) != NULL && n->type != ROFFT_HEAD) i++; /* * If a width was specified for this column, * subtract what printed, and * add the same spacing as in mdoc_term.c. */ nc = bln->norm->Bl.ncols; i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount + (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1; if (i < 1) i = 1; while (i-- > 0) putchar(' '); outflags &= ~MD_spc; escflags &= ~ESC_FON; outcount = 0; break; default: break; } } static void md_post_Lb(struct roff_node *n) { if (n->sec == SEC_LIBRARY) outflags |= MD_br; } static void md_uri(const char *s) { while (*s != '\0') { if (strchr("%()<>", *s) != NULL) { printf("%%%2.2hhX", *s); outcount += 3; } else { putchar(*s); outcount++; } s++; } } static int md_pre_Lk(struct roff_node *n) { const struct roff_node *link, *descr, *punct; if ((link = n->child) == NULL) return 0; /* Find beginning of trailing punctuation. */ punct = n->last; while (punct != link && punct->flags & NODE_DELIMC) punct = punct->prev; punct = punct->next; /* Link text. */ descr = link->next; if (descr == punct) descr = link; /* no text */ md_rawword("["); outflags &= ~MD_spc; do { md_word(descr->string); descr = descr->next; } while (descr != punct); outflags &= ~MD_spc; /* Link target. */ md_rawword("]("); md_uri(link->string); outflags &= ~MD_spc; md_rawword(")"); /* Trailing punctuation. */ while (punct != NULL) { md_word(punct->string); punct = punct->next; } return 0; } static int md_pre_Mt(struct roff_node *n) { const struct roff_node *nch; md_rawword("["); outflags &= ~MD_spc; for (nch = n->child; nch != NULL; nch = nch->next) md_word(nch->string); outflags &= ~MD_spc; md_rawword("](mailto:"); for (nch = n->child; nch != NULL; nch = nch->next) { md_uri(nch->string); if (nch->next != NULL) { putchar(' '); outcount++; } } outflags &= ~MD_spc; md_rawword(")"); return 0; } static int md_pre_Nd(struct roff_node *n) { outflags &= ~MD_nl; outflags |= MD_spc; md_word("-"); return 1; } static int md_pre_Nm(struct roff_node *n) { switch (n->type) { case ROFFT_BLOCK: outflags |= MD_Bk; md_pre_syn(n); break; case ROFFT_HEAD: case ROFFT_ELEM: md_pre_raw(n); break; default: break; } return 1; } static void md_post_Nm(struct roff_node *n) { switch (n->type) { case ROFFT_BLOCK: outflags &= ~MD_Bk; break; case ROFFT_HEAD: case ROFFT_ELEM: md_post_raw(n); break; default: break; } } static int md_pre_No(struct roff_node *n) { outflags |= MD_spc_force; return 1; } static int md_pre_Ns(struct roff_node *n) { outflags &= ~MD_spc; return 0; } static void md_post_Pf(struct roff_node *n) { if (n->next != NULL && (n->next->flags & NODE_LINE) == 0) outflags &= ~MD_spc; } static int md_pre_Pp(struct roff_node *n) { outflags |= MD_sp; return 0; } static int md_pre_Rs(struct roff_node *n) { if (n->sec == SEC_SEE_ALSO) outflags |= MD_sp; return 1; } static int md_pre_Sh(struct roff_node *n) { switch (n->type) { case ROFFT_BLOCK: if (n->sec == SEC_AUTHORS) outflags &= ~(MD_An_split | MD_An_nosplit); break; case ROFFT_HEAD: outflags |= MD_sp; md_rawword(n->tok == MDOC_Sh ? "#" : "##"); break; case ROFFT_BODY: outflags |= MD_sp; break; default: break; } return 1; } static int md_pre_Sm(struct roff_node *n) { if (n->child == NULL) outflags ^= MD_Sm; else if (strcmp("on", n->child->string) == 0) outflags |= MD_Sm; else outflags &= ~MD_Sm; if (outflags & MD_Sm) outflags |= MD_spc; return 0; } static int md_pre_Vt(struct roff_node *n) { switch (n->type) { case ROFFT_BLOCK: md_pre_syn(n); return 1; case ROFFT_BODY: case ROFFT_ELEM: md_pre_raw(n); return 1; default: return 0; } } static void md_post_Vt(struct roff_node *n) { switch (n->type) { case ROFFT_BODY: case ROFFT_ELEM: md_post_raw(n); break; default: break; } } static int md_pre_Xr(struct roff_node *n) { n = n->child; if (n == NULL) return 0; md_node(n); n = n->next; if (n == NULL) return 0; outflags &= ~MD_spc; md_word("("); md_node(n); md_word(")"); return 0; } static int md_pre__T(struct roff_node *n) { if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) md_word("\""); else md_rawword("*"); outflags &= ~MD_spc; return 1; } static void md_post__T(struct roff_node *n) { outflags &= ~MD_spc; if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) md_word("\""); else md_rawword("*"); md_post_pc(n); } static int md_pre_br(struct roff_node *n) { outflags |= MD_br; return 0; } /* $Id: mdoc_state.c,v 1.17 2020/06/22 19:20:40 schwarze Exp $ */ /* * 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. */ #include "config.h" #include #include #include #include #include #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "libmandoc.h" #include "roff_int.h" #include "libmdoc.h" #define STATE_ARGS struct roff_man *mdoc, struct roff_node *n typedef void (*state_handler)(STATE_ARGS); static void state_bl(STATE_ARGS); static void state_sh(STATE_ARGS); static void state_sm(STATE_ARGS); static const state_handler state_handlers[MDOC_MAX - MDOC_Dd] = { NULL, /* Dd */ NULL, /* Dt */ NULL, /* Os */ state_sh, /* Sh */ NULL, /* Ss */ NULL, /* Pp */ NULL, /* D1 */ NULL, /* Dl */ NULL, /* Bd */ NULL, /* Ed */ state_bl, /* Bl */ NULL, /* El */ NULL, /* It */ NULL, /* Ad */ NULL, /* An */ NULL, /* Ap */ NULL, /* Ar */ NULL, /* Cd */ NULL, /* Cm */ NULL, /* Dv */ NULL, /* Er */ NULL, /* Ev */ NULL, /* Ex */ NULL, /* Fa */ NULL, /* Fd */ NULL, /* Fl */ NULL, /* Fn */ NULL, /* Ft */ NULL, /* Ic */ NULL, /* In */ NULL, /* Li */ NULL, /* Nd */ NULL, /* Nm */ NULL, /* Op */ NULL, /* Ot */ NULL, /* Pa */ NULL, /* Rv */ NULL, /* St */ NULL, /* Va */ NULL, /* Vt */ NULL, /* Xr */ NULL, /* %A */ NULL, /* %B */ NULL, /* %D */ NULL, /* %I */ NULL, /* %J */ NULL, /* %N */ NULL, /* %O */ NULL, /* %P */ NULL, /* %R */ NULL, /* %T */ NULL, /* %V */ NULL, /* Ac */ NULL, /* Ao */ NULL, /* Aq */ NULL, /* At */ NULL, /* Bc */ NULL, /* Bf */ NULL, /* Bo */ NULL, /* Bq */ NULL, /* Bsx */ NULL, /* Bx */ NULL, /* Db */ NULL, /* Dc */ NULL, /* Do */ NULL, /* Dq */ NULL, /* Ec */ NULL, /* Ef */ NULL, /* Em */ NULL, /* Eo */ NULL, /* Fx */ NULL, /* Ms */ NULL, /* No */ NULL, /* Ns */ NULL, /* Nx */ NULL, /* Ox */ NULL, /* Pc */ NULL, /* Pf */ NULL, /* Po */ NULL, /* Pq */ NULL, /* Qc */ NULL, /* Ql */ NULL, /* Qo */ NULL, /* Qq */ NULL, /* Re */ NULL, /* Rs */ NULL, /* Sc */ NULL, /* So */ NULL, /* Sq */ state_sm, /* Sm */ NULL, /* Sx */ NULL, /* Sy */ NULL, /* Tn */ NULL, /* Ux */ NULL, /* Xc */ NULL, /* Xo */ NULL, /* Fo */ NULL, /* Fc */ NULL, /* Oo */ NULL, /* Oc */ NULL, /* Bk */ NULL, /* Ek */ NULL, /* Bt */ NULL, /* Hf */ NULL, /* Fr */ NULL, /* Ud */ NULL, /* Lb */ NULL, /* Lp */ NULL, /* Lk */ NULL, /* Mt */ NULL, /* Brq */ NULL, /* Bro */ NULL, /* Brc */ NULL, /* %C */ NULL, /* Es */ NULL, /* En */ NULL, /* Dx */ NULL, /* %Q */ NULL, /* %U */ NULL, /* Ta */ NULL, /* Tg */ }; void mdoc_state(struct roff_man *mdoc, struct roff_node *n) { state_handler handler; if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX) return; assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); if ((mdoc_macro(n->tok)->flags & MDOC_PROLOGUE) == 0) mdoc->flags |= MDOC_PBODY; handler = state_handlers[n->tok - MDOC_Dd]; if (*handler) (*handler)(mdoc, n); } static void state_bl(STATE_ARGS) { struct mdoc_arg *args; size_t i; if (n->type != ROFFT_HEAD || n->parent->args == NULL) return; args = n->parent->args; for (i = 0; i < args->argc; i++) { switch(args->argv[i].arg) { case MDOC_Diag: n->norm->Bl.type = LIST_diag; return; case MDOC_Column: n->norm->Bl.type = LIST_column; return; default: break; } } } static void state_sh(STATE_ARGS) { struct roff_node *nch; char *secname; if (n->type != ROFFT_HEAD) return; if ( ! (n->flags & NODE_VALID)) { secname = NULL; deroff(&secname, n); /* * Set the section attribute for the BLOCK, HEAD, * and HEAD children; the latter can only be TEXT * nodes, so no recursion is needed. For other * nodes, including the .Sh BODY, this is done * when allocating the node data structures, but * for .Sh BLOCK and HEAD, the section is still * unknown at that time. */ n->sec = n->parent->sec = secname == NULL ? SEC_CUSTOM : mdoc_a2sec(secname); for (nch = n->child; nch != NULL; nch = nch->next) nch->sec = n->sec; free(secname); } if ((mdoc->lastsec = n->sec) == SEC_SYNOPSIS) { roff_setreg(mdoc->roff, "nS", 1, '='); mdoc->flags |= MDOC_SYNOPSIS; } else { roff_setreg(mdoc->roff, "nS", 0, '='); mdoc->flags &= ~MDOC_SYNOPSIS; } } static void state_sm(STATE_ARGS) { if (n->child == NULL) mdoc->flags ^= MDOC_SMOFF; else if ( ! strcmp(n->child->string, "on")) mdoc->flags &= ~MDOC_SMOFF; else if ( ! strcmp(n->child->string, "off")) mdoc->flags |= MDOC_SMOFF; } /* $Id: mdoc_term.c,v 1.380 2020/04/06 10:16:17 schwarze Exp $ */ /* * Copyright (c) 2010, 2012-2020 Ingo Schwarze * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * 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. * * Plain text formatter for mdoc(7), used by mandoc(1) * for ASCII, UTF-8, PostScript, and PDF output. */ #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 "term_tag.h" #include "main.h" struct termpair { struct termpair *ppair; int count; }; #define DECL_ARGS struct termp *p, \ struct termpair *pair, \ const struct roff_meta *meta, \ struct roff_node *n struct mdoc_term_act { int (*pre)(DECL_ARGS); void (*post)(DECL_ARGS); }; static int a2width(const struct termp *, const char *); static void print_bvspace(struct termp *, struct roff_node *, struct roff_node *); static void print_mdoc_node(DECL_ARGS); static void print_mdoc_nodelist(DECL_ARGS); static void print_mdoc_head(struct termp *, const struct roff_meta *); static void print_mdoc_foot(struct termp *, const struct roff_meta *); static void synopsis_pre(struct termp *, struct roff_node *); static void termp____post(DECL_ARGS); static void termp__t_post(DECL_ARGS); static void termp_bd_post(DECL_ARGS); static void termp_bk_post(DECL_ARGS); static void termp_bl_post(DECL_ARGS); static void termp_eo_post(DECL_ARGS); static void termp_fd_post(DECL_ARGS); static void termp_fo_post(DECL_ARGS); static void termp_in_post(DECL_ARGS); static void termp_it_post(DECL_ARGS); static void termp_lb_post(DECL_ARGS); static void termp_nm_post(DECL_ARGS); static void termp_pf_post(DECL_ARGS); static void termp_quote_post(DECL_ARGS); static void termp_sh_post(DECL_ARGS); static void termp_ss_post(DECL_ARGS); static void termp_xx_post(DECL_ARGS); static int termp__a_pre(DECL_ARGS); static int termp__t_pre(DECL_ARGS); static int termp_abort_pre(DECL_ARGS); static int termp_an_pre(DECL_ARGS); static int termp_ap_pre(DECL_ARGS); static int termp_bd_pre(DECL_ARGS); static int termp_bf_pre(DECL_ARGS); static int termp_bk_pre(DECL_ARGS); static int termp_bl_pre(DECL_ARGS); static int termp_bold_pre(DECL_ARGS); static int termp_d1_pre(DECL_ARGS); static int termp_eo_pre(DECL_ARGS); static int termp_ex_pre(DECL_ARGS); static int termp_fa_pre(DECL_ARGS); static int termp_fd_pre(DECL_ARGS); static int termp_fl_pre(DECL_ARGS); static int termp_fn_pre(DECL_ARGS); static int termp_fo_pre(DECL_ARGS); static int termp_ft_pre(DECL_ARGS); static int termp_in_pre(DECL_ARGS); static int termp_it_pre(DECL_ARGS); static int termp_li_pre(DECL_ARGS); static int termp_lk_pre(DECL_ARGS); static int termp_nd_pre(DECL_ARGS); static int termp_nm_pre(DECL_ARGS); static int termp_ns_pre(DECL_ARGS); static int termp_quote_pre(DECL_ARGS); static int termp_rs_pre(DECL_ARGS); static int termp_sh_pre(DECL_ARGS); static int termp_skip_pre(DECL_ARGS); static int termp_sm_pre(DECL_ARGS); static int termp_pp_pre(DECL_ARGS); static int termp_ss_pre(DECL_ARGS); static int termp_under_pre(DECL_ARGS); static int termp_vt_pre(DECL_ARGS); static int termp_xr_pre(DECL_ARGS); static int termp_xx_pre(DECL_ARGS); static const struct mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = { { NULL, NULL }, /* Dd */ { NULL, NULL }, /* Dt */ { NULL, NULL }, /* Os */ { termp_sh_pre, termp_sh_post }, /* Sh */ { termp_ss_pre, termp_ss_post }, /* Ss */ { termp_pp_pre, NULL }, /* Pp */ { termp_d1_pre, termp_bl_post }, /* D1 */ { termp_d1_pre, termp_bl_post }, /* Dl */ { termp_bd_pre, termp_bd_post }, /* Bd */ { NULL, NULL }, /* Ed */ { termp_bl_pre, termp_bl_post }, /* Bl */ { NULL, NULL }, /* El */ { termp_it_pre, termp_it_post }, /* It */ { termp_under_pre, NULL }, /* Ad */ { termp_an_pre, NULL }, /* An */ { termp_ap_pre, NULL }, /* Ap */ { termp_under_pre, NULL }, /* Ar */ { termp_fd_pre, NULL }, /* Cd */ { termp_bold_pre, NULL }, /* Cm */ { termp_li_pre, NULL }, /* Dv */ { NULL, NULL }, /* Er */ { NULL, NULL }, /* Ev */ { termp_ex_pre, NULL }, /* Ex */ { termp_fa_pre, NULL }, /* Fa */ { termp_fd_pre, termp_fd_post }, /* Fd */ { termp_fl_pre, NULL }, /* Fl */ { termp_fn_pre, NULL }, /* Fn */ { termp_ft_pre, NULL }, /* Ft */ { termp_bold_pre, NULL }, /* Ic */ { termp_in_pre, termp_in_post }, /* In */ { termp_li_pre, NULL }, /* Li */ { termp_nd_pre, NULL }, /* Nd */ { termp_nm_pre, termp_nm_post }, /* Nm */ { termp_quote_pre, termp_quote_post }, /* Op */ { termp_abort_pre, NULL }, /* Ot */ { termp_under_pre, NULL }, /* Pa */ { termp_ex_pre, NULL }, /* Rv */ { NULL, NULL }, /* St */ { termp_under_pre, NULL }, /* Va */ { termp_vt_pre, NULL }, /* Vt */ { termp_xr_pre, NULL }, /* Xr */ { termp__a_pre, termp____post }, /* %A */ { termp_under_pre, termp____post }, /* %B */ { NULL, termp____post }, /* %D */ { termp_under_pre, termp____post }, /* %I */ { termp_under_pre, termp____post }, /* %J */ { NULL, termp____post }, /* %N */ { NULL, termp____post }, /* %O */ { NULL, termp____post }, /* %P */ { NULL, termp____post }, /* %R */ { termp__t_pre, termp__t_post }, /* %T */ { NULL, termp____post }, /* %V */ { NULL, NULL }, /* Ac */ { termp_quote_pre, termp_quote_post }, /* Ao */ { termp_quote_pre, termp_quote_post }, /* Aq */ { NULL, NULL }, /* At */ { NULL, NULL }, /* Bc */ { termp_bf_pre, NULL }, /* Bf */ { termp_quote_pre, termp_quote_post }, /* Bo */ { termp_quote_pre, termp_quote_post }, /* Bq */ { termp_xx_pre, termp_xx_post }, /* Bsx */ { NULL, NULL }, /* Bx */ { termp_skip_pre, NULL }, /* Db */ { NULL, NULL }, /* Dc */ { termp_quote_pre, termp_quote_post }, /* Do */ { termp_quote_pre, termp_quote_post }, /* Dq */ { NULL, NULL }, /* Ec */ /* FIXME: no space */ { NULL, NULL }, /* Ef */ { termp_under_pre, NULL }, /* Em */ { termp_eo_pre, termp_eo_post }, /* Eo */ { termp_xx_pre, termp_xx_post }, /* Fx */ { termp_bold_pre, NULL }, /* Ms */ { termp_li_pre, NULL }, /* No */ { termp_ns_pre, NULL }, /* Ns */ { termp_xx_pre, termp_xx_post }, /* Nx */ { termp_xx_pre, termp_xx_post }, /* Ox */ { NULL, NULL }, /* Pc */ { NULL, termp_pf_post }, /* Pf */ { termp_quote_pre, termp_quote_post }, /* Po */ { termp_quote_pre, termp_quote_post }, /* Pq */ { NULL, NULL }, /* Qc */ { termp_quote_pre, termp_quote_post }, /* Ql */ { termp_quote_pre, termp_quote_post }, /* Qo */ { termp_quote_pre, termp_quote_post }, /* Qq */ { NULL, NULL }, /* Re */ { termp_rs_pre, NULL }, /* Rs */ { NULL, NULL }, /* Sc */ { termp_quote_pre, termp_quote_post }, /* So */ { termp_quote_pre, termp_quote_post }, /* Sq */ { termp_sm_pre, NULL }, /* Sm */ { termp_under_pre, NULL }, /* Sx */ { termp_bold_pre, NULL }, /* Sy */ { NULL, NULL }, /* Tn */ { termp_xx_pre, termp_xx_post }, /* Ux */ { NULL, NULL }, /* Xc */ { NULL, NULL }, /* Xo */ { termp_fo_pre, termp_fo_post }, /* Fo */ { NULL, NULL }, /* Fc */ { termp_quote_pre, termp_quote_post }, /* Oo */ { NULL, NULL }, /* Oc */ { termp_bk_pre, termp_bk_post }, /* Bk */ { NULL, NULL }, /* Ek */ { NULL, NULL }, /* Bt */ { NULL, NULL }, /* Hf */ { termp_under_pre, NULL }, /* Fr */ { NULL, NULL }, /* Ud */ { NULL, termp_lb_post }, /* Lb */ { termp_abort_pre, NULL }, /* Lp */ { termp_lk_pre, NULL }, /* Lk */ { termp_under_pre, NULL }, /* Mt */ { termp_quote_pre, termp_quote_post }, /* Brq */ { termp_quote_pre, termp_quote_post }, /* Bro */ { NULL, NULL }, /* Brc */ { NULL, termp____post }, /* %C */ { termp_skip_pre, NULL }, /* Es */ { termp_quote_pre, termp_quote_post }, /* En */ { termp_xx_pre, termp_xx_post }, /* Dx */ { NULL, termp____post }, /* %Q */ { NULL, termp____post }, /* %U */ { NULL, NULL }, /* Ta */ { termp_skip_pre, NULL }, /* Tg */ }; void terminal_mdoc(void *arg, const struct roff_meta *mdoc) { struct roff_node *n, *nn; struct termp *p; size_t save_defindent; p = (struct termp *)arg; p->tcol->rmargin = p->maxrmargin = p->defrmargin; term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, ".5i"); n = mdoc->first->child; if (p->synopsisonly) { for (nn = NULL; n != NULL; n = n->next) { if (n->tok != MDOC_Sh) continue; if (n->sec == SEC_SYNOPSIS) break; if (nn == NULL && n->sec == SEC_NAME) nn = n; } if (n == NULL) n = nn; p->flags |= TERMP_NOSPACE; if (n != NULL && (n = n->child->next->child) != NULL) print_mdoc_nodelist(p, NULL, mdoc, n); term_newln(p); } else { save_defindent = p->defindent; if (p->defindent == 0) p->defindent = 5; term_begin(p, print_mdoc_head, print_mdoc_foot, mdoc); while (n != NULL && (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)) n = n->next; if (n != NULL) { if (n->tok != MDOC_Sh) term_vspace(p); print_mdoc_nodelist(p, NULL, mdoc, n); } term_end(p); p->defindent = save_defindent; } } static void print_mdoc_nodelist(DECL_ARGS) { while (n != NULL) { print_mdoc_node(p, pair, meta, n); n = n->next; } } static void print_mdoc_node(DECL_ARGS) { const struct mdoc_term_act *act; struct termpair npair; size_t offset, rmargin; int chld; /* * In no-fill mode, break the output line at the beginning * of new input lines except after \c, and nowhere else. */ if (n->flags & NODE_NOFILL) { if (n->flags & NODE_LINE && (p->flags & TERMP_NONEWLINE) == 0) term_newln(p); p->flags |= TERMP_BRNEVER; } else p->flags &= ~TERMP_BRNEVER; if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) return; chld = 1; offset = p->tcol->offset; rmargin = p->tcol->rmargin; n->flags &= ~NODE_ENDED; n->prev_font = p->fonti; memset(&npair, 0, sizeof(struct termpair)); npair.ppair = pair; if (n->flags & NODE_ID && n->tok != MDOC_Pp && (n->tok != MDOC_It || n->type != ROFFT_BLOCK)) term_tag_write(n, p->line); /* * Keeps only work until the end of a line. If a keep was * invoked in a prior line, revert it to PREKEEP. */ if (p->flags & TERMP_KEEP && n->flags & NODE_LINE) { p->flags &= ~TERMP_KEEP; p->flags |= TERMP_PREKEEP; } /* * After the keep flags have been set up, we may now * produce output. Note that some pre-handlers do so. */ act = NULL; switch (n->type) { case ROFFT_TEXT: if (n->flags & NODE_LINE) { switch (*n->string) { case '\0': if (p->flags & TERMP_NONEWLINE) term_newln(p); else term_vspace(p); return; case ' ': if ((p->flags & TERMP_NONEWLINE) == 0) term_newln(p); break; default: break; } } if (NODE_DELIMC & n->flags) p->flags |= TERMP_NOSPACE; term_word(p, n->string); if (NODE_DELIMO & n->flags) p->flags |= TERMP_NOSPACE; break; case ROFFT_EQN: if ( ! (n->flags & NODE_LINE)) p->flags |= TERMP_NOSPACE; term_eqn(p, n->eqn); if (n->next != NULL && ! (n->next->flags & NODE_LINE)) p->flags |= TERMP_NOSPACE; break; case ROFFT_TBL: if (p->tbl.cols == NULL) term_newln(p); term_tbl(p, n->span); break; default: if (n->tok < ROFF_MAX) { roff_term_pre(p, n); return; } assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); act = mdoc_term_acts + (n->tok - MDOC_Dd); if (act->pre != NULL && (n->end == ENDBODY_NOT || n->child != NULL)) chld = (*act->pre)(p, &npair, meta, n); break; } if (chld && n->child) print_mdoc_nodelist(p, &npair, meta, n->child); term_fontpopq(p, (ENDBODY_NOT == n->end ? n : n->body)->prev_font); switch (n->type) { case ROFFT_TEXT: break; case ROFFT_TBL: break; case ROFFT_EQN: break; default: if (act->post == NULL || n->flags & NODE_ENDED) break; (void)(*act->post)(p, &npair, meta, n); /* * Explicit end tokens not only call the post * handler, but also tell the respective block * that it must not call the post handler again. */ if (ENDBODY_NOT != n->end) n->body->flags |= NODE_ENDED; break; } if (NODE_EOS & n->flags) p->flags |= TERMP_SENTENCE; if (n->type != ROFFT_TEXT) p->tcol->offset = offset; p->tcol->rmargin = rmargin; } static void print_mdoc_foot(struct termp *p, const struct roff_meta *meta) { size_t sz; term_fontrepl(p, TERMFONT_NONE); /* * Output the footer in new-groff style, that is, three columns * with the middle being the manual date and flanking columns * being the operating system: * * SYSTEM DATE SYSTEM */ term_vspace(p); p->tcol->offset = 0; sz = term_strlen(p, meta->date); p->tcol->rmargin = p->maxrmargin > sz ? (p->maxrmargin + term_len(p, 1) - sz) / 2 : 0; p->trailspace = 1; p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; term_word(p, meta->os); term_flushln(p); p->tcol->offset = p->tcol->rmargin; sz = term_strlen(p, meta->os); p->tcol->rmargin = p->maxrmargin > sz ? p->maxrmargin - sz : 0; p->flags |= TERMP_NOSPACE; term_word(p, meta->date); term_flushln(p); p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin; p->trailspace = 0; p->flags &= ~TERMP_NOBREAK; p->flags |= TERMP_NOSPACE; term_word(p, meta->os); term_flushln(p); p->tcol->offset = 0; p->tcol->rmargin = p->maxrmargin; p->flags = 0; } static void print_mdoc_head(struct termp *p, const struct roff_meta *meta) { char *volume, *title; size_t vollen, titlen; /* * The header is strange. It has three components, which are * really two with the first duplicated. It goes like this: * * IDENTIFIER TITLE IDENTIFIER * * The IDENTIFIER is NAME(SECTION), which is the command-name * (if given, or "unknown" if not) followed by the manual page * section. These are given in `Dt'. The TITLE is a free-form * string depending on the manual volume. If not specified, it * switches on the manual section. */ assert(meta->vol); if (NULL == meta->arch) volume = mandoc_strdup(meta->vol); else mandoc_asprintf(&volume, "%s (%s)", meta->vol, meta->arch); vollen = term_strlen(p, volume); if (NULL == meta->msec) title = mandoc_strdup(meta->title); else mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec); titlen = term_strlen(p, title); p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; p->trailspace = 1; p->tcol->offset = 0; p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ? (p->maxrmargin - vollen + term_len(p, 1)) / 2 : vollen < p->maxrmargin ? p->maxrmargin - vollen : 0; term_word(p, title); term_flushln(p); p->flags |= TERMP_NOSPACE; p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->tcol->offset + vollen + titlen < p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin; term_word(p, volume); term_flushln(p); p->flags &= ~TERMP_NOBREAK; p->trailspace = 0; if (p->tcol->rmargin + titlen <= p->maxrmargin) { p->flags |= TERMP_NOSPACE; p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin; term_word(p, title); term_flushln(p); } p->flags &= ~TERMP_NOSPACE; p->tcol->offset = 0; p->tcol->rmargin = p->maxrmargin; free(title); free(volume); } static int a2width(const struct termp *p, const char *v) { struct roffsu su; const char *end; end = a2roffsu(v, &su, SCALE_MAX); if (end == NULL || *end != '\0') { SCALE_HS_INIT(&su, term_strlen(p, v)); su.scale /= term_strlen(p, "0"); } return term_hen(p, &su); } /* * Determine how much space to print out before block elements of `It' * (and thus `Bl') and `Bd'. And then go ahead and print that space, * too. */ static void print_bvspace(struct termp *p, struct roff_node *bl, struct roff_node *n) { struct roff_node *nn; term_newln(p); if ((bl->tok == MDOC_Bd && bl->norm->Bd.comp) || (bl->tok == MDOC_Bl && bl->norm->Bl.comp)) return; /* Do not vspace directly after Ss/Sh. */ nn = n; while (roff_node_prev(nn) == NULL) { do { nn = nn->parent; if (nn->type == ROFFT_ROOT) return; } while (nn->type != ROFFT_BLOCK); if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss) return; if (nn->tok == MDOC_It && nn->parent->parent->norm->Bl.type != LIST_item) break; } /* * No vertical space after: * items in .Bl -column * items without a body in .Bl -diag */ if (bl->tok != MDOC_Bl || n->prev == NULL || n->prev->tok != MDOC_It || (bl->norm->Bl.type != LIST_column && (bl->norm->Bl.type != LIST_diag || n->prev->body->child != NULL))) term_vspace(p); } static int termp_it_pre(DECL_ARGS) { struct roffsu su; char buf[24]; const struct roff_node *bl, *nn; size_t ncols, dcol; int i, offset, width; enum mdoc_list type; if (n->type == ROFFT_BLOCK) { print_bvspace(p, n->parent->parent, n); if (n->flags & NODE_ID) term_tag_write(n, p->line); return 1; } bl = n->parent->parent->parent; type = bl->norm->Bl.type; /* * Defaults for specific list types. */ switch (type) { case LIST_bullet: case LIST_dash: case LIST_hyphen: case LIST_enum: width = term_len(p, 2); break; case LIST_hang: case LIST_tag: width = term_len(p, 8); break; case LIST_column: width = term_len(p, 10); break; default: width = 0; break; } offset = 0; /* * First calculate width and offset. This is pretty easy unless * we're a -column list, in which case all prior columns must * be accounted for. */ if (bl->norm->Bl.offs != NULL) { offset = a2width(p, bl->norm->Bl.offs); if (offset < 0 && (size_t)(-offset) > p->tcol->offset) offset = -p->tcol->offset; else if (offset > SHRT_MAX) offset = 0; } switch (type) { case LIST_column: if (n->type == ROFFT_HEAD) break; /* * Imitate groff's column handling: * - For each earlier column, add its width. * - For less than 5 columns, add four more blanks per * column. * - For exactly 5 columns, add three more blank per * column. * - For more than 5 columns, add only one column. */ ncols = bl->norm->Bl.ncols; dcol = ncols < 5 ? term_len(p, 4) : ncols == 5 ? term_len(p, 3) : term_len(p, 1); /* * Calculate the offset by applying all prior ROFFT_BODY, * so we stop at the ROFFT_HEAD (nn->prev == NULL). */ for (i = 0, nn = n->prev; nn->prev && i < (int)ncols; nn = nn->prev, i++) { SCALE_HS_INIT(&su, term_strlen(p, bl->norm->Bl.cols[i])); su.scale /= term_strlen(p, "0"); offset += term_hen(p, &su) + dcol; } /* * When exceeding the declared number of columns, leave * the remaining widths at 0. This will later be * adjusted to the default width of 10, or, for the last * column, stretched to the right margin. */ if (i >= (int)ncols) break; /* * Use the declared column widths, extended as explained * in the preceding paragraph. */ SCALE_HS_INIT(&su, term_strlen(p, bl->norm->Bl.cols[i])); su.scale /= term_strlen(p, "0"); width = term_hen(p, &su) + dcol; break; default: if (NULL == bl->norm->Bl.width) break; /* * Note: buffer the width by 2, which is groff's magic * number for buffering single arguments. See the above * handling for column for how this changes. */ width = a2width(p, bl->norm->Bl.width) + term_len(p, 2); if (width < 0 && (size_t)(-width) > p->tcol->offset) width = -p->tcol->offset; else if (width > SHRT_MAX) width = 0; break; } /* * Whitespace control. Inset bodies need an initial space, * while diagonal bodies need two. */ p->flags |= TERMP_NOSPACE; switch (type) { case LIST_diag: if (n->type == ROFFT_BODY) term_word(p, "\\ \\ "); break; case LIST_inset: if (n->type == ROFFT_BODY && n->parent->head->child != NULL) term_word(p, "\\ "); break; default: break; } p->flags |= TERMP_NOSPACE; switch (type) { case LIST_diag: if (n->type == ROFFT_HEAD) term_fontpush(p, TERMFONT_BOLD); break; default: break; } /* * Pad and break control. This is the tricky part. These flags * are documented in term_flushln() in term.c. Note that we're * going to unset all of these flags in termp_it_post() when we * exit. */ switch (type) { case LIST_enum: case LIST_bullet: case LIST_dash: case LIST_hyphen: if (n->type == ROFFT_HEAD) { p->flags |= TERMP_NOBREAK | TERMP_HANG; p->trailspace = 1; } else if (width <= (int)term_len(p, 2)) p->flags |= TERMP_NOPAD; break; case LIST_hang: if (n->type != ROFFT_HEAD) break; p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG; p->trailspace = 1; break; case LIST_tag: if (n->type != ROFFT_HEAD) break; p->flags |= TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND; p->trailspace = 2; if (NULL == n->next || NULL == n->next->child) p->flags |= TERMP_HANG; break; case LIST_column: if (n->type == ROFFT_HEAD) break; if (NULL == n->next) { p->flags &= ~TERMP_NOBREAK; p->trailspace = 0; } else { p->flags |= TERMP_NOBREAK; p->trailspace = 1; } break; case LIST_diag: if (n->type != ROFFT_HEAD) break; p->flags |= TERMP_NOBREAK | TERMP_BRIND; p->trailspace = 1; break; default: break; } /* * Margin control. Set-head-width lists have their right * margins shortened. The body for these lists has the offset * necessarily lengthened. Everybody gets the offset. */ p->tcol->offset += offset; switch (type) { case LIST_bullet: case LIST_dash: case LIST_enum: case LIST_hyphen: case LIST_hang: case LIST_tag: if (n->type == ROFFT_HEAD) p->tcol->rmargin = p->tcol->offset + width; else p->tcol->offset += width; break; case LIST_column: assert(width); p->tcol->rmargin = p->tcol->offset + width; /* * XXX - this behaviour is not documented: the * right-most column is filled to the right margin. */ if (n->type == ROFFT_HEAD) break; if (n->next == NULL && p->tcol->rmargin < p->maxrmargin) p->tcol->rmargin = p->maxrmargin; break; default: break; } /* * The dash, hyphen, bullet and enum lists all have a special * HEAD character (temporarily bold, in some cases). */ if (n->type == ROFFT_HEAD) switch (type) { case LIST_bullet: term_fontpush(p, TERMFONT_BOLD); term_word(p, "\\[bu]"); term_fontpop(p); break; case LIST_dash: case LIST_hyphen: term_fontpush(p, TERMFONT_BOLD); term_word(p, "-"); term_fontpop(p); break; case LIST_enum: (pair->ppair->ppair->count)++; (void)snprintf(buf, sizeof(buf), "%d.", pair->ppair->ppair->count); term_word(p, buf); break; default: break; } /* * If we're not going to process our children, indicate so here. */ switch (type) { case LIST_bullet: case LIST_item: case LIST_dash: case LIST_hyphen: case LIST_enum: if (n->type == ROFFT_HEAD) return 0; break; case LIST_column: if (n->type == ROFFT_HEAD) return 0; p->minbl = 0; break; default: break; } return 1; } static void termp_it_post(DECL_ARGS) { enum mdoc_list type; if (n->type == ROFFT_BLOCK) return; type = n->parent->parent->parent->norm->Bl.type; switch (type) { case LIST_item: case LIST_diag: case LIST_inset: if (n->type == ROFFT_BODY) term_newln(p); break; case LIST_column: if (n->type == ROFFT_BODY) term_flushln(p); break; default: term_newln(p); break; } /* * Now that our output is flushed, we can reset our tags. Since * only `It' sets these flags, we're free to assume that nobody * has munged them in the meanwhile. */ p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP | TERMP_BRIND | TERMP_HANG); p->trailspace = 0; } static int termp_nm_pre(DECL_ARGS) { const char *cp; if (n->type == ROFFT_BLOCK) { p->flags |= TERMP_PREKEEP; return 1; } if (n->type == ROFFT_BODY) { if (n->child == NULL) return 0; p->flags |= TERMP_NOSPACE; cp = NULL; if (n->prev->child != NULL) cp = n->prev->child->string; if (cp == NULL) cp = meta->name; if (cp == NULL) p->tcol->offset += term_len(p, 6); else p->tcol->offset += term_len(p, 1) + term_strlen(p, cp); return 1; } if (n->child == NULL) return 0; if (n->type == ROFFT_HEAD) synopsis_pre(p, n->parent); if (n->type == ROFFT_HEAD && n->next != NULL && n->next->child != NULL) { p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND; p->trailspace = 1; p->tcol->rmargin = p->tcol->offset + term_len(p, 1); if (n->child == NULL) p->tcol->rmargin += term_strlen(p, meta->name); else if (n->child->type == ROFFT_TEXT) { p->tcol->rmargin += term_strlen(p, n->child->string); if (n->child->next != NULL) p->flags |= TERMP_HANG; } else { p->tcol->rmargin += term_len(p, 5); p->flags |= TERMP_HANG; } } return termp_bold_pre(p, pair, meta, n); } static void termp_nm_post(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); break; case ROFFT_HEAD: if (n->next == NULL || n->next->child == NULL) break; term_flushln(p); p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); p->trailspace = 0; break; case ROFFT_BODY: if (n->child != NULL) term_flushln(p); break; default: break; } } static int termp_fl_pre(DECL_ARGS) { struct roff_node *nn; term_fontpush(p, TERMFONT_BOLD); term_word(p, "\\-"); if (n->child != NULL || ((nn = roff_node_next(n)) != NULL && nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)) p->flags |= TERMP_NOSPACE; return 1; } static int termp__a_pre(DECL_ARGS) { struct roff_node *nn; if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A && ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A)) term_word(p, "and"); return 1; } static int termp_an_pre(DECL_ARGS) { if (n->norm->An.auth == AUTH_split) { p->flags &= ~TERMP_NOSPLIT; p->flags |= TERMP_SPLIT; return 0; } if (n->norm->An.auth == AUTH_nosplit) { p->flags &= ~TERMP_SPLIT; p->flags |= TERMP_NOSPLIT; return 0; } if (p->flags & TERMP_SPLIT) term_newln(p); if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT)) p->flags |= TERMP_SPLIT; return 1; } static int termp_ns_pre(DECL_ARGS) { if ( ! (NODE_LINE & n->flags)) p->flags |= TERMP_NOSPACE; return 1; } static int termp_rs_pre(DECL_ARGS) { if (SEC_SEE_ALSO != n->sec) return 1; if (n->type == ROFFT_BLOCK && roff_node_prev(n) != NULL) term_vspace(p); return 1; } static int termp_ex_pre(DECL_ARGS) { term_newln(p); return 1; } static int termp_nd_pre(DECL_ARGS) { if (n->type == ROFFT_BODY) term_word(p, "\\(en"); return 1; } static int termp_bl_pre(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: term_newln(p); return 1; case ROFFT_HEAD: return 0; default: return 1; } } static void termp_bl_post(DECL_ARGS) { if (n->type != ROFFT_BLOCK) return; term_newln(p); if (n->tok != MDOC_Bl || n->norm->Bl.type != LIST_column) return; term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, ".5i"); } static int termp_xr_pre(DECL_ARGS) { if (NULL == (n = n->child)) return 0; assert(n->type == ROFFT_TEXT); term_word(p, n->string); if (NULL == (n = n->next)) return 0; p->flags |= TERMP_NOSPACE; term_word(p, "("); p->flags |= TERMP_NOSPACE; assert(n->type == ROFFT_TEXT); term_word(p, n->string); p->flags |= TERMP_NOSPACE; term_word(p, ")"); return 0; } /* * This decides how to assert whitespace before any of the SYNOPSIS set * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain * macro combos). */ static void synopsis_pre(struct termp *p, struct roff_node *n) { struct roff_node *np; if ((n->flags & NODE_SYNPRETTY) == 0 || (np = roff_node_prev(n)) == NULL) return; /* * If we're the second in a pair of like elements, emit our * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which * case we soldier on. */ if (np->tok == n->tok && MDOC_Ft != n->tok && MDOC_Fo != n->tok && MDOC_Fn != n->tok) { term_newln(p); return; } /* * If we're one of the SYNOPSIS set and non-like pair-wise after * another (or Fn/Fo, which we've let slip through) then assert * vertical space, else only newline and move on. */ switch (np->tok) { case MDOC_Fd: case MDOC_Fn: case MDOC_Fo: case MDOC_In: case MDOC_Vt: term_vspace(p); break; case MDOC_Ft: if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) { term_vspace(p); break; } /* FALLTHROUGH */ default: term_newln(p); break; } } static int termp_vt_pre(DECL_ARGS) { switch (n->type) { case ROFFT_ELEM: return termp_ft_pre(p, pair, meta, n); case ROFFT_BLOCK: synopsis_pre(p, n); return 1; case ROFFT_HEAD: return 0; default: return termp_under_pre(p, pair, meta, n); } } static int termp_bold_pre(DECL_ARGS) { term_fontpush(p, TERMFONT_BOLD); return 1; } static int termp_fd_pre(DECL_ARGS) { synopsis_pre(p, n); return termp_bold_pre(p, pair, meta, n); } static void termp_fd_post(DECL_ARGS) { term_newln(p); } static int termp_sh_pre(DECL_ARGS) { struct roff_node *np; switch (n->type) { case ROFFT_BLOCK: /* * Vertical space before sections, except * when the previous section was empty. */ if ((np = roff_node_prev(n)) == NULL || np->tok != MDOC_Sh || (np->body != NULL && np->body->child != NULL)) term_vspace(p); break; case ROFFT_HEAD: return termp_bold_pre(p, pair, meta, n); case ROFFT_BODY: p->tcol->offset = term_len(p, p->defindent); term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, ".5i"); if (n->sec == SEC_AUTHORS) p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT); break; default: break; } return 1; } static void termp_sh_post(DECL_ARGS) { switch (n->type) { case ROFFT_HEAD: term_newln(p); break; case ROFFT_BODY: term_newln(p); p->tcol->offset = 0; break; default: break; } } static void termp_lb_post(DECL_ARGS) { if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE) term_newln(p); } static int termp_d1_pre(DECL_ARGS) { if (n->type != ROFFT_BLOCK) return 1; term_newln(p); p->tcol->offset += term_len(p, p->defindent + 1); term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, ".5i"); return 1; } static int termp_ft_pre(DECL_ARGS) { synopsis_pre(p, n); return termp_under_pre(p, pair, meta, n); } static int termp_fn_pre(DECL_ARGS) { size_t rmargin = 0; int pretty; synopsis_pre(p, n); pretty = n->flags & NODE_SYNPRETTY; if ((n = n->child) == NULL) return 0; if (pretty) { rmargin = p->tcol->rmargin; p->tcol->rmargin = p->tcol->offset + term_len(p, 4); p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG; } assert(n->type == ROFFT_TEXT); term_fontpush(p, TERMFONT_BOLD); term_word(p, n->string); term_fontpop(p); if (pretty) { term_flushln(p); p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); p->flags |= TERMP_NOPAD; p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = rmargin; } p->flags |= TERMP_NOSPACE; term_word(p, "("); p->flags |= TERMP_NOSPACE; for (n = n->next; n; n = n->next) { assert(n->type == ROFFT_TEXT); term_fontpush(p, TERMFONT_UNDER); if (pretty) p->flags |= TERMP_NBRWORD; term_word(p, n->string); term_fontpop(p); if (n->next) { p->flags |= TERMP_NOSPACE; term_word(p, ","); } } p->flags |= TERMP_NOSPACE; term_word(p, ")"); if (pretty) { p->flags |= TERMP_NOSPACE; term_word(p, ";"); term_flushln(p); } return 0; } static int termp_fa_pre(DECL_ARGS) { const struct roff_node *nn; if (n->parent->tok != MDOC_Fo) return termp_under_pre(p, pair, meta, n); for (nn = n->child; nn != NULL; nn = nn->next) { term_fontpush(p, TERMFONT_UNDER); p->flags |= TERMP_NBRWORD; term_word(p, nn->string); term_fontpop(p); if (nn->next != NULL) { p->flags |= TERMP_NOSPACE; term_word(p, ","); } } if (n->child != NULL && (nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa) { p->flags |= TERMP_NOSPACE; term_word(p, ","); } return 0; } static int termp_bd_pre(DECL_ARGS) { int offset; if (n->type == ROFFT_BLOCK) { print_bvspace(p, n, n); return 1; } else if (n->type == ROFFT_HEAD) return 0; /* Handle the -offset argument. */ if (n->norm->Bd.offs == NULL || ! strcmp(n->norm->Bd.offs, "left")) /* nothing */; else if ( ! strcmp(n->norm->Bd.offs, "indent")) p->tcol->offset += term_len(p, p->defindent + 1); else if ( ! strcmp(n->norm->Bd.offs, "indent-two")) p->tcol->offset += term_len(p, (p->defindent + 1) * 2); else { offset = a2width(p, n->norm->Bd.offs); if (offset < 0 && (size_t)(-offset) > p->tcol->offset) p->tcol->offset = 0; else if (offset < SHRT_MAX) p->tcol->offset += offset; } switch (n->norm->Bd.type) { case DISP_literal: term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, "8n"); break; case DISP_centered: p->flags |= TERMP_CENTER; break; default: break; } return 1; } static void termp_bd_post(DECL_ARGS) { if (n->type != ROFFT_BODY) return; if (n->norm->Bd.type == DISP_unfilled || n->norm->Bd.type == DISP_literal) p->flags |= TERMP_BRNEVER; p->flags |= TERMP_NOSPACE; term_newln(p); p->flags &= ~TERMP_BRNEVER; if (n->norm->Bd.type == DISP_centered) p->flags &= ~TERMP_CENTER; } static int termp_xx_pre(DECL_ARGS) { if ((n->aux = p->flags & TERMP_PREKEEP) == 0) p->flags |= TERMP_PREKEEP; return 1; } static void termp_xx_post(DECL_ARGS) { if (n->aux == 0) p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); } static void termp_pf_post(DECL_ARGS) { if (n->next != NULL && (n->next->flags & NODE_LINE) == 0) p->flags |= TERMP_NOSPACE; } static int termp_ss_pre(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: if (roff_node_prev(n) == NULL) term_newln(p); else term_vspace(p); break; case ROFFT_HEAD: p->tcol->offset = term_len(p, (p->defindent+1)/2); return termp_bold_pre(p, pair, meta, n); case ROFFT_BODY: p->tcol->offset = term_len(p, p->defindent); term_tab_set(p, NULL); term_tab_set(p, "T"); term_tab_set(p, ".5i"); break; default: break; } return 1; } static void termp_ss_post(DECL_ARGS) { if (n->type == ROFFT_HEAD || n->type == ROFFT_BODY) term_newln(p); } static int termp_in_pre(DECL_ARGS) { synopsis_pre(p, n); if (n->flags & NODE_SYNPRETTY && n->flags & NODE_LINE) { term_fontpush(p, TERMFONT_BOLD); term_word(p, "#include"); term_word(p, "<"); } else { term_word(p, "<"); term_fontpush(p, TERMFONT_UNDER); } p->flags |= TERMP_NOSPACE; return 1; } static void termp_in_post(DECL_ARGS) { if (n->flags & NODE_SYNPRETTY) term_fontpush(p, TERMFONT_BOLD); p->flags |= TERMP_NOSPACE; term_word(p, ">"); if (n->flags & NODE_SYNPRETTY) term_fontpop(p); } static int termp_pp_pre(DECL_ARGS) { term_vspace(p); if (n->flags & NODE_ID) term_tag_write(n, p->line); return 0; } static int termp_skip_pre(DECL_ARGS) { return 0; } static int termp_quote_pre(DECL_ARGS) { if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) return 1; switch (n->tok) { case MDOC_Ao: case MDOC_Aq: term_word(p, n->child != NULL && n->child->next == NULL && n->child->tok == MDOC_Mt ? "<" : "\\(la"); break; case MDOC_Bro: case MDOC_Brq: term_word(p, "{"); break; case MDOC_Oo: case MDOC_Op: case MDOC_Bo: case MDOC_Bq: term_word(p, "["); break; case MDOC__T: /* FALLTHROUGH */ case MDOC_Do: case MDOC_Dq: term_word(p, "\\(lq"); break; case MDOC_En: if (NULL == n->norm->Es || NULL == n->norm->Es->child) return 1; term_word(p, n->norm->Es->child->string); break; case MDOC_Po: case MDOC_Pq: term_word(p, "("); break; case MDOC_Qo: case MDOC_Qq: term_word(p, "\""); break; case MDOC_Ql: case MDOC_So: case MDOC_Sq: term_word(p, "\\(oq"); break; default: abort(); } p->flags |= TERMP_NOSPACE; return 1; } static void termp_quote_post(DECL_ARGS) { if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) return; p->flags |= TERMP_NOSPACE; switch (n->tok) { case MDOC_Ao: case MDOC_Aq: term_word(p, n->child != NULL && n->child->next == NULL && n->child->tok == MDOC_Mt ? ">" : "\\(ra"); break; case MDOC_Bro: case MDOC_Brq: term_word(p, "}"); break; case MDOC_Oo: case MDOC_Op: case MDOC_Bo: case MDOC_Bq: term_word(p, "]"); break; case MDOC__T: /* FALLTHROUGH */ case MDOC_Do: case MDOC_Dq: term_word(p, "\\(rq"); break; case MDOC_En: if (n->norm->Es == NULL || n->norm->Es->child == NULL || n->norm->Es->child->next == NULL) p->flags &= ~TERMP_NOSPACE; else term_word(p, n->norm->Es->child->next->string); break; case MDOC_Po: case MDOC_Pq: term_word(p, ")"); break; case MDOC_Qo: case MDOC_Qq: term_word(p, "\""); break; case MDOC_Ql: case MDOC_So: case MDOC_Sq: term_word(p, "\\(cq"); break; default: abort(); } } static int termp_eo_pre(DECL_ARGS) { if (n->type != ROFFT_BODY) return 1; if (n->end == ENDBODY_NOT && n->parent->head->child == NULL && n->child != NULL && n->child->end != ENDBODY_NOT) term_word(p, "\\&"); else if (n->end != ENDBODY_NOT ? n->child != NULL : n->parent->head->child != NULL && (n->child != NULL || (n->parent->tail != NULL && n->parent->tail->child != NULL))) p->flags |= TERMP_NOSPACE; return 1; } static void termp_eo_post(DECL_ARGS) { int body, tail; if (n->type != ROFFT_BODY) return; if (n->end != ENDBODY_NOT) { p->flags &= ~TERMP_NOSPACE; return; } body = n->child != NULL || n->parent->head->child != NULL; tail = n->parent->tail != NULL && n->parent->tail->child != NULL; if (body && tail) p->flags |= TERMP_NOSPACE; else if ( ! (body || tail)) term_word(p, "\\&"); else if ( ! tail) p->flags &= ~TERMP_NOSPACE; } static int termp_fo_pre(DECL_ARGS) { size_t rmargin; switch (n->type) { case ROFFT_BLOCK: synopsis_pre(p, n); return 1; case ROFFT_BODY: rmargin = p->tcol->rmargin; if (n->flags & NODE_SYNPRETTY) { p->tcol->rmargin = p->tcol->offset + term_len(p, 4); p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG; } p->flags |= TERMP_NOSPACE; term_word(p, "("); p->flags |= TERMP_NOSPACE; if (n->flags & NODE_SYNPRETTY) { term_flushln(p); p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG); p->flags |= TERMP_NOPAD; p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = rmargin; } return 1; default: return termp_bold_pre(p, pair, meta, n); } } static void termp_fo_post(DECL_ARGS) { if (n->type != ROFFT_BODY) return; p->flags |= TERMP_NOSPACE; term_word(p, ")"); if (n->flags & NODE_SYNPRETTY) { p->flags |= TERMP_NOSPACE; term_word(p, ";"); term_flushln(p); } } static int termp_bf_pre(DECL_ARGS) { switch (n->type) { case ROFFT_HEAD: return 0; case ROFFT_BODY: break; default: return 1; } switch (n->norm->Bf.font) { case FONT_Em: return termp_under_pre(p, pair, meta, n); case FONT_Sy: return termp_bold_pre(p, pair, meta, n); default: return termp_li_pre(p, pair, meta, n); } } static int termp_sm_pre(DECL_ARGS) { if (n->child == NULL) p->flags ^= TERMP_NONOSPACE; else if (strcmp(n->child->string, "on") == 0) p->flags &= ~TERMP_NONOSPACE; else p->flags |= TERMP_NONOSPACE; if (p->col && ! (TERMP_NONOSPACE & p->flags)) p->flags &= ~TERMP_NOSPACE; return 0; } static int termp_ap_pre(DECL_ARGS) { p->flags |= TERMP_NOSPACE; term_word(p, "'"); p->flags |= TERMP_NOSPACE; return 1; } static void termp____post(DECL_ARGS) { struct roff_node *nn; /* * Handle lists of authors. In general, print each followed by * a comma. Don't print the comma if there are only two * authors. */ if (n->tok == MDOC__A && (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A && ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) && ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A)) return; /* TODO: %U. */ if (n->parent == NULL || n->parent->tok != MDOC_Rs) return; p->flags |= TERMP_NOSPACE; if (roff_node_next(n) == NULL) { term_word(p, "."); p->flags |= TERMP_SENTENCE; } else term_word(p, ","); } static int termp_li_pre(DECL_ARGS) { term_fontpush(p, TERMFONT_NONE); return 1; } static int termp_lk_pre(DECL_ARGS) { const struct roff_node *link, *descr, *punct; if ((link = n->child) == NULL) return 0; /* Find beginning of trailing punctuation. */ punct = n->last; while (punct != link && punct->flags & NODE_DELIMC) punct = punct->prev; punct = punct->next; /* Link text. */ if ((descr = link->next) != NULL && descr != punct) { term_fontpush(p, TERMFONT_UNDER); while (descr != punct) { if (descr->flags & (NODE_DELIMC | NODE_DELIMO)) p->flags |= TERMP_NOSPACE; term_word(p, descr->string); descr = descr->next; } term_fontpop(p); p->flags |= TERMP_NOSPACE; term_word(p, ":"); } /* Link target. */ term_fontpush(p, TERMFONT_BOLD); term_word(p, link->string); term_fontpop(p); /* Trailing punctuation. */ while (punct != NULL) { p->flags |= TERMP_NOSPACE; term_word(p, punct->string); punct = punct->next; } return 0; } static int termp_bk_pre(DECL_ARGS) { switch (n->type) { case ROFFT_BLOCK: break; case ROFFT_HEAD: return 0; case ROFFT_BODY: if (n->parent->args != NULL || n->prev->child == NULL) p->flags |= TERMP_PREKEEP; break; default: abort(); } return 1; } static void termp_bk_post(DECL_ARGS) { if (n->type == ROFFT_BODY) p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); } /* * If we are in an `Rs' and there is a journal present, * then quote us instead of underlining us (for disambiguation). */ static void termp__t_post(DECL_ARGS) { if (n->parent != NULL && n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) termp_quote_post(p, pair, meta, n); termp____post(p, pair, meta, n); } static int termp__t_pre(DECL_ARGS) { if (n->parent != NULL && n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) return termp_quote_pre(p, pair, meta, n); else return termp_under_pre(p, pair, meta, n); } static int termp_under_pre(DECL_ARGS) { term_fontpush(p, TERMFONT_UNDER); return 1; } static int termp_abort_pre(DECL_ARGS) { abort(); } /* $Id: mdoc_validate.c,v 1.389 2021/07/18 11:41:23 schwarze Exp $ */ /* * Copyright (c) 2010-2020 Ingo Schwarze * Copyright (c) 2008-2012 Kristaps Dzonsons * 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. * * Validation module for mdoc(7) syntax trees used by mandoc(1). */ #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" #include "tag.h" /* FIXME: .Bl -diag can't have non-text children in HEAD. */ #define POST_ARGS struct roff_man *mdoc enum check_ineq { CHECK_LT, CHECK_GT, CHECK_EQ }; typedef void (*v_post)(POST_ARGS); static int build_list(struct roff_man *, int); static void check_argv(struct roff_man *, struct roff_node *, struct mdoc_argv *); static void check_args(struct roff_man *, struct roff_node *); static void check_text(struct roff_man *, int, int, char *); static void check_text_em(struct roff_man *, int, int, char *); static void check_toptext(struct roff_man *, int, int, const char *); static int child_an(const struct roff_node *); static size_t macro2len(enum roff_tok); static void rewrite_macro2len(struct roff_man *, char **); static int similar(const char *, const char *); static void post_abort(POST_ARGS) __attribute__((__noreturn__)); static void post_an(POST_ARGS); static void post_an_norm(POST_ARGS); static void post_at(POST_ARGS); static void post_bd(POST_ARGS); static void post_bf(POST_ARGS); static void post_bk(POST_ARGS); static void post_bl(POST_ARGS); static void post_bl_block(POST_ARGS); static void post_bl_head(POST_ARGS); static void post_bl_norm(POST_ARGS); static void post_bx(POST_ARGS); static void post_defaults(POST_ARGS); static void post_display(POST_ARGS); static void post_dd(POST_ARGS); static void post_delim(POST_ARGS); static void post_delim_nb(POST_ARGS); static void post_dt(POST_ARGS); static void post_em(POST_ARGS); static void post_en(POST_ARGS); static void post_er(POST_ARGS); static void post_es(POST_ARGS); static void post_eoln(POST_ARGS); static void post_ex(POST_ARGS); static void post_fa(POST_ARGS); static void post_fl(POST_ARGS); static void post_fn(POST_ARGS); static void post_fname(POST_ARGS); static void post_fo(POST_ARGS); static void post_hyph(POST_ARGS); static void post_it(POST_ARGS); static void post_lb(POST_ARGS); static void post_nd(POST_ARGS); static void post_nm(POST_ARGS); static void post_ns(POST_ARGS); static void post_obsolete(POST_ARGS); static void post_os(POST_ARGS); static void post_par(POST_ARGS); static void post_prevpar(POST_ARGS); static void post_root(POST_ARGS); static void post_rs(POST_ARGS); static void post_rv(POST_ARGS); static void post_section(POST_ARGS); static void post_sh(POST_ARGS); static void post_sh_head(POST_ARGS); static void post_sh_name(POST_ARGS); static void post_sh_see_also(POST_ARGS); static void post_sh_authors(POST_ARGS); static void post_sm(POST_ARGS); static void post_st(POST_ARGS); static void post_std(POST_ARGS); static void post_sx(POST_ARGS); static void post_tag(POST_ARGS); static void post_tg(POST_ARGS); static void post_useless(POST_ARGS); static void post_xr(POST_ARGS); static void post_xx(POST_ARGS); static const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = { post_dd, /* Dd */ post_dt, /* Dt */ post_os, /* Os */ post_sh, /* Sh */ post_section, /* Ss */ post_par, /* Pp */ post_display, /* D1 */ post_display, /* Dl */ post_display, /* Bd */ NULL, /* Ed */ post_bl, /* Bl */ NULL, /* El */ post_it, /* It */ post_delim_nb, /* Ad */ post_an, /* An */ NULL, /* Ap */ post_defaults, /* Ar */ NULL, /* Cd */ post_tag, /* Cm */ post_tag, /* Dv */ post_er, /* Er */ post_tag, /* Ev */ post_ex, /* Ex */ post_fa, /* Fa */ NULL, /* Fd */ post_fl, /* Fl */ post_fn, /* Fn */ post_delim_nb, /* Ft */ post_tag, /* Ic */ post_delim_nb, /* In */ post_tag, /* Li */ post_nd, /* Nd */ post_nm, /* Nm */ post_delim_nb, /* Op */ post_abort, /* Ot */ post_defaults, /* Pa */ post_rv, /* Rv */ post_st, /* St */ post_tag, /* Va */ post_delim_nb, /* Vt */ post_xr, /* Xr */ NULL, /* %A */ post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */ NULL, /* %D */ NULL, /* %I */ NULL, /* %J */ post_hyph, /* %N */ post_hyph, /* %O */ NULL, /* %P */ post_hyph, /* %R */ post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */ NULL, /* %V */ NULL, /* Ac */ NULL, /* Ao */ post_delim_nb, /* Aq */ post_at, /* At */ NULL, /* Bc */ post_bf, /* Bf */ NULL, /* Bo */ NULL, /* Bq */ post_xx, /* Bsx */ post_bx, /* Bx */ post_obsolete, /* Db */ NULL, /* Dc */ NULL, /* Do */ NULL, /* Dq */ NULL, /* Ec */ NULL, /* Ef */ post_em, /* Em */ NULL, /* Eo */ post_xx, /* Fx */ post_tag, /* Ms */ post_tag, /* No */ post_ns, /* Ns */ post_xx, /* Nx */ post_xx, /* Ox */ NULL, /* Pc */ NULL, /* Pf */ NULL, /* Po */ post_delim_nb, /* Pq */ NULL, /* Qc */ post_delim_nb, /* Ql */ NULL, /* Qo */ post_delim_nb, /* Qq */ NULL, /* Re */ post_rs, /* Rs */ NULL, /* Sc */ NULL, /* So */ post_delim_nb, /* Sq */ post_sm, /* Sm */ post_sx, /* Sx */ post_em, /* Sy */ post_useless, /* Tn */ post_xx, /* Ux */ NULL, /* Xc */ NULL, /* Xo */ post_fo, /* Fo */ NULL, /* Fc */ NULL, /* Oo */ NULL, /* Oc */ post_bk, /* Bk */ NULL, /* Ek */ post_eoln, /* Bt */ post_obsolete, /* Hf */ post_obsolete, /* Fr */ post_eoln, /* Ud */ post_lb, /* Lb */ post_abort, /* Lp */ post_delim_nb, /* Lk */ post_defaults, /* Mt */ post_delim_nb, /* Brq */ NULL, /* Bro */ NULL, /* Brc */ NULL, /* %C */ post_es, /* Es */ post_en, /* En */ post_xx, /* Dx */ NULL, /* %Q */ NULL, /* %U */ NULL, /* Ta */ post_tg, /* Tg */ }; #define RSORD_MAX 14 /* Number of `Rs' blocks. */ static const enum roff_tok rsord[RSORD_MAX] = { MDOC__A, MDOC__T, MDOC__B, MDOC__I, MDOC__J, MDOC__R, MDOC__N, MDOC__V, MDOC__U, MDOC__P, MDOC__Q, MDOC__C, MDOC__D, MDOC__O }; static const char * const secnames[SEC__MAX] = { NULL, "NAME", "LIBRARY", "SYNOPSIS", "DESCRIPTION", "CONTEXT", "IMPLEMENTATION NOTES", "RETURN VALUES", "ENVIRONMENT", "FILES", "EXIT STATUS", "EXAMPLES", "DIAGNOSTICS", "COMPATIBILITY", "ERRORS", "SEE ALSO", "STANDARDS", "HISTORY", "AUTHORS", "CAVEATS", "BUGS", "SECURITY CONSIDERATIONS", NULL }; static int fn_prio = TAG_STRONG; /* Validate the subtree rooted at mdoc->last. */ void mdoc_validate(struct roff_man *mdoc) { struct roff_node *n, *np; const v_post *p; /* * Translate obsolete macros to modern macros first * such that later code does not need to look * for the obsolete versions. */ n = mdoc->last; switch (n->tok) { case MDOC_Lp: n->tok = MDOC_Pp; break; case MDOC_Ot: post_obsolete(mdoc); n->tok = MDOC_Ft; break; default: break; } /* * Iterate over all children, recursing into each one * in turn, depth-first. */ mdoc->last = mdoc->last->child; while (mdoc->last != NULL) { mdoc_validate(mdoc); if (mdoc->last == n) mdoc->last = mdoc->last->child; else mdoc->last = mdoc->last->next; } /* Finally validate the macro itself. */ mdoc->last = n; mdoc->next = ROFF_NEXT_SIBLING; switch (n->type) { case ROFFT_TEXT: np = n->parent; if (n->sec != SEC_SYNOPSIS || (np->tok != MDOC_Cd && np->tok != MDOC_Fd)) check_text(mdoc, n->line, n->pos, n->string); if ((n->flags & NODE_NOFILL) == 0 && (np->tok != MDOC_It || np->type != ROFFT_HEAD || np->parent->parent->norm->Bl.type != LIST_diag)) check_text_em(mdoc, n->line, n->pos, n->string); if (np->tok == MDOC_It || (np->type == ROFFT_BODY && (np->tok == MDOC_Sh || np->tok == MDOC_Ss))) check_toptext(mdoc, n->line, n->pos, n->string); break; case ROFFT_COMMENT: case ROFFT_EQN: case ROFFT_TBL: break; case ROFFT_ROOT: post_root(mdoc); break; default: check_args(mdoc, mdoc->last); /* * Closing delimiters are not special at the * beginning of a block, opening delimiters * are not special at the end. */ if (n->child != NULL) n->child->flags &= ~NODE_DELIMC; if (n->last != NULL) n->last->flags &= ~NODE_DELIMO; /* Call the macro's postprocessor. */ if (n->tok < ROFF_MAX) { roff_validate(mdoc); break; } assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); p = mdoc_valids + (n->tok - MDOC_Dd); if (*p) (*p)(mdoc); if (mdoc->last == n) mdoc_state(mdoc, n); break; } } static void check_args(struct roff_man *mdoc, struct roff_node *n) { int i; if (NULL == n->args) return; assert(n->args->argc); for (i = 0; i < (int)n->args->argc; i++) check_argv(mdoc, n, &n->args->argv[i]); } static void check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v) { int i; for (i = 0; i < (int)v->sz; i++) check_text(mdoc, v->line, v->pos, v->value[i]); } static void check_text(struct roff_man *mdoc, int ln, int pos, char *p) { char *cp; if (mdoc->last->flags & NODE_NOFILL) return; for (cp = p; NULL != (p = strchr(p, '\t')); p++) mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL); } static void check_text_em(struct roff_man *mdoc, int ln, int pos, char *p) { const struct roff_node *np, *nn; char *cp; np = mdoc->last->prev; nn = mdoc->last->next; /* Look for em-dashes wrongly encoded as "--". */ for (cp = p; *cp != '\0'; cp++) { if (cp[0] != '-' || cp[1] != '-') continue; cp++; /* Skip input sequences of more than two '-'. */ if (cp[1] == '-') { while (cp[1] == '-') cp++; continue; } /* Skip "--" directly attached to something else. */ if ((cp - p > 1 && cp[-2] != ' ') || (cp[1] != '\0' && cp[1] != ' ')) continue; /* Require a letter right before or right afterwards. */ if ((cp - p > 2 ? isalpha((unsigned char)cp[-3]) : np != NULL && np->type == ROFFT_TEXT && *np->string != '\0' && isalpha((unsigned char)np->string[ strlen(np->string) - 1])) || (cp[1] != '\0' && cp[2] != '\0' ? isalpha((unsigned char)cp[2]) : nn != NULL && nn->type == ROFFT_TEXT && isalpha((unsigned char)*nn->string))) { mandoc_msg(MANDOCERR_DASHDASH, ln, pos + (int)(cp - p) - 1, NULL); break; } } } static void check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p) { const char *cp, *cpr; if (*p == '\0') return; if ((cp = strstr(p, "OpenBSD")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox"); if ((cp = strstr(p, "NetBSD")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx"); if ((cp = strstr(p, "FreeBSD")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx"); if ((cp = strstr(p, "DragonFly")) != NULL) mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx"); cp = p; while ((cp = strstr(cp + 1, "()")) != NULL) { for (cpr = cp - 1; cpr >= p; cpr--) if (*cpr != '_' && !isalnum((unsigned char)*cpr)) break; if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) { cpr++; mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p), "%.*s()", (int)(cp - cpr), cpr); } } } static void post_abort(POST_ARGS) { abort(); } static void post_delim(POST_ARGS) { const struct roff_node *nch; const char *lc; enum mdelim delim; enum roff_tok tok; tok = mdoc->last->tok; nch = mdoc->last->last; if (nch == NULL || nch->type != ROFFT_TEXT) return; lc = strchr(nch->string, '\0') - 1; if (lc < nch->string) return; delim = mdoc_isdelim(lc); if (delim == DELIM_NONE || delim == DELIM_OPEN) return; if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh || tok == MDOC_Ss || tok == MDOC_Fo)) return; mandoc_msg(MANDOCERR_DELIM, nch->line, nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok], nch == mdoc->last->child ? "" : " ...", nch->string); } static void post_delim_nb(POST_ARGS) { const struct roff_node *nch; const char *lc, *cp; int nw; enum mdelim delim; enum roff_tok tok; /* * Find candidates: at least two bytes, * the last one a closing or middle delimiter. */ tok = mdoc->last->tok; nch = mdoc->last->last; if (nch == NULL || nch->type != ROFFT_TEXT) return; lc = strchr(nch->string, '\0') - 1; if (lc <= nch->string) return; delim = mdoc_isdelim(lc); if (delim == DELIM_NONE || delim == DELIM_OPEN) return; /* * Reduce false positives by allowing various cases. */ /* Escaped delimiters. */ if (lc > nch->string + 1 && lc[-2] == '\\' && (lc[-1] == '&' || lc[-1] == 'e')) return; /* Specific byte sequences. */ switch (*lc) { case ')': for (cp = lc; cp >= nch->string; cp--) if (*cp == '(') return; break; case '.': if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.') return; if (lc[-1] == '.') return; break; case ';': if (tok == MDOC_Vt) return; break; case '?': if (lc[-1] == '?') return; break; case ']': for (cp = lc; cp >= nch->string; cp--) if (*cp == '[') return; break; case '|': if (lc == nch->string + 1 && lc[-1] == '|') return; default: break; } /* Exactly two non-alphanumeric bytes. */ if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1])) return; /* At least three alphabetic words with a sentence ending. */ if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em || tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) { nw = 0; for (cp = lc - 1; cp >= nch->string; cp--) { if (*cp == ' ') { nw++; if (cp > nch->string && cp[-1] == ',') cp--; } else if (isalpha((unsigned int)*cp)) { if (nw > 1) return; } else break; } } mandoc_msg(MANDOCERR_DELIM_NB, nch->line, nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok], nch == mdoc->last->child ? "" : " ...", nch->string); } static void post_bl_norm(POST_ARGS) { struct roff_node *n; struct mdoc_argv *argv, *wa; int i; enum mdocargt mdoclt; enum mdoc_list lt; n = mdoc->last->parent; n->norm->Bl.type = LIST__NONE; /* * First figure out which kind of list to use: bind ourselves to * the first mentioned list type and warn about any remaining * ones. If we find no list type, we default to LIST_item. */ wa = (n->args == NULL) ? NULL : n->args->argv; mdoclt = MDOC_ARG_MAX; for (i = 0; n->args && i < (int)n->args->argc; i++) { argv = n->args->argv + i; lt = LIST__NONE; switch (argv->arg) { /* Set list types. */ case MDOC_Bullet: lt = LIST_bullet; break; case MDOC_Dash: lt = LIST_dash; break; case MDOC_Enum: lt = LIST_enum; break; case MDOC_Hyphen: lt = LIST_hyphen; break; case MDOC_Item: lt = LIST_item; break; case MDOC_Tag: lt = LIST_tag; break; case MDOC_Diag: lt = LIST_diag; break; case MDOC_Hang: lt = LIST_hang; break; case MDOC_Ohang: lt = LIST_ohang; break; case MDOC_Inset: lt = LIST_inset; break; case MDOC_Column: lt = LIST_column; break; /* Set list arguments. */ case MDOC_Compact: if (n->norm->Bl.comp) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bl -compact"); n->norm->Bl.comp = 1; break; case MDOC_Width: wa = argv; if (0 == argv->sz) { mandoc_msg(MANDOCERR_ARG_EMPTY, argv->line, argv->pos, "Bl -width"); n->norm->Bl.width = "0n"; break; } if (NULL != n->norm->Bl.width) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bl -width %s", argv->value[0]); rewrite_macro2len(mdoc, argv->value); n->norm->Bl.width = argv->value[0]; break; case MDOC_Offset: if (0 == argv->sz) { mandoc_msg(MANDOCERR_ARG_EMPTY, argv->line, argv->pos, "Bl -offset"); break; } if (NULL != n->norm->Bl.offs) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bl -offset %s", argv->value[0]); rewrite_macro2len(mdoc, argv->value); n->norm->Bl.offs = argv->value[0]; break; default: continue; } if (LIST__NONE == lt) continue; mdoclt = argv->arg; /* Check: multiple list types. */ if (LIST__NONE != n->norm->Bl.type) { mandoc_msg(MANDOCERR_BL_REP, n->line, n->pos, "Bl -%s", mdoc_argnames[argv->arg]); continue; } /* The list type should come first. */ if (n->norm->Bl.width || n->norm->Bl.offs || n->norm->Bl.comp) mandoc_msg(MANDOCERR_BL_LATETYPE, n->line, n->pos, "Bl -%s", mdoc_argnames[n->args->argv[0].arg]); n->norm->Bl.type = lt; if (LIST_column == lt) { n->norm->Bl.ncols = argv->sz; n->norm->Bl.cols = (void *)argv->value; } } /* Allow lists to default to LIST_item. */ if (LIST__NONE == n->norm->Bl.type) { mandoc_msg(MANDOCERR_BL_NOTYPE, n->line, n->pos, "Bl"); n->norm->Bl.type = LIST_item; mdoclt = MDOC_Item; } /* * Validate the width field. Some list types don't need width * types and should be warned about them. Others should have it * and must also be warned. Yet others have a default and need * no warning. */ switch (n->norm->Bl.type) { case LIST_tag: if (n->norm->Bl.width == NULL) mandoc_msg(MANDOCERR_BL_NOWIDTH, n->line, n->pos, "Bl -tag"); break; case LIST_column: case LIST_diag: case LIST_ohang: case LIST_inset: case LIST_item: if (n->norm->Bl.width != NULL) mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos, "Bl -%s", mdoc_argnames[mdoclt]); n->norm->Bl.width = NULL; break; case LIST_bullet: case LIST_dash: case LIST_hyphen: if (n->norm->Bl.width == NULL) n->norm->Bl.width = "2n"; break; case LIST_enum: if (n->norm->Bl.width == NULL) n->norm->Bl.width = "3n"; break; default: break; } } static void post_bd(POST_ARGS) { struct roff_node *n; struct mdoc_argv *argv; int i; enum mdoc_disp dt; n = mdoc->last; for (i = 0; n->args && i < (int)n->args->argc; i++) { argv = n->args->argv + i; dt = DISP__NONE; switch (argv->arg) { case MDOC_Centred: dt = DISP_centered; break; case MDOC_Ragged: dt = DISP_ragged; break; case MDOC_Unfilled: dt = DISP_unfilled; break; case MDOC_Filled: dt = DISP_filled; break; case MDOC_Literal: dt = DISP_literal; break; case MDOC_File: mandoc_msg(MANDOCERR_BD_FILE, n->line, n->pos, NULL); break; case MDOC_Offset: if (0 == argv->sz) { mandoc_msg(MANDOCERR_ARG_EMPTY, argv->line, argv->pos, "Bd -offset"); break; } if (NULL != n->norm->Bd.offs) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bd -offset %s", argv->value[0]); rewrite_macro2len(mdoc, argv->value); n->norm->Bd.offs = argv->value[0]; break; case MDOC_Compact: if (n->norm->Bd.comp) mandoc_msg(MANDOCERR_ARG_REP, argv->line, argv->pos, "Bd -compact"); n->norm->Bd.comp = 1; break; default: abort(); } if (DISP__NONE == dt) continue; if (DISP__NONE == n->norm->Bd.type) n->norm->Bd.type = dt; else mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos, "Bd -%s", mdoc_argnames[argv->arg]); } if (DISP__NONE == n->norm->Bd.type) { mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd"); n->norm->Bd.type = DISP_ragged; } } /* * Stand-alone line macros. */ static void post_an_norm(POST_ARGS) { struct roff_node *n; struct mdoc_argv *argv; size_t i; n = mdoc->last; if (n->args == NULL) return; for (i = 1; i < n->args->argc; i++) { argv = n->args->argv + i; mandoc_msg(MANDOCERR_AN_REP, argv->line, argv->pos, "An -%s", mdoc_argnames[argv->arg]); } argv = n->args->argv; if (argv->arg == MDOC_Split) n->norm->An.auth = AUTH_split; else if (argv->arg == MDOC_Nosplit) n->norm->An.auth = AUTH_nosplit; else abort(); } static void post_eoln(POST_ARGS) { struct roff_node *n; post_useless(mdoc); n = mdoc->last; if (n->child != NULL) mandoc_msg(MANDOCERR_ARG_SKIP, n->line, n->pos, "%s %s", roff_name[n->tok], n->child->string); while (n->child != NULL) roff_node_delete(mdoc, n->child); roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ? "is currently in beta test." : "currently under development."); mdoc->last->flags |= NODE_EOS | NODE_NOSRC; mdoc->last = n; } static int build_list(struct roff_man *mdoc, int tok) { struct roff_node *n; int ic; n = mdoc->last->next; for (ic = 1;; ic++) { roff_elem_alloc(mdoc, n->line, n->pos, tok); mdoc->last->flags |= NODE_NOSRC; roff_node_relink(mdoc, n); n = mdoc->last = mdoc->last->parent; mdoc->next = ROFF_NEXT_SIBLING; if (n->next == NULL) return ic; if (ic > 1 || n->next->next != NULL) { roff_word_alloc(mdoc, n->line, n->pos, ","); mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC; } n = mdoc->last->next; if (n->next == NULL) { roff_word_alloc(mdoc, n->line, n->pos, "and"); mdoc->last->flags |= NODE_NOSRC; } } } static void post_ex(POST_ARGS) { struct roff_node *n; int ic; post_std(mdoc); n = mdoc->last; mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, "The"); mdoc->last->flags |= NODE_NOSRC; if (mdoc->last->next != NULL) ic = build_list(mdoc, MDOC_Nm); else if (mdoc->meta.name != NULL) { roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); mdoc->last->flags |= NODE_NOSRC; mdoc->last = mdoc->last->parent; mdoc->next = ROFF_NEXT_SIBLING; ic = 1; } else { mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex"); ic = 0; } roff_word_alloc(mdoc, n->line, n->pos, ic > 1 ? "utilities exit\\~0" : "utility exits\\~0"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "on success, and\\~>0 if an error occurs."); mdoc->last->flags |= NODE_EOS | NODE_NOSRC; mdoc->last = n; } static void post_lb(POST_ARGS) { struct roff_node *n; 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_tg(POST_ARGS) { struct roff_node *n; /* The .Tg node. */ struct roff_node *nch; /* The first child of the .Tg node. */ struct roff_node *nn; /* The next node after the .Tg node. */ struct roff_node *np; /* The parent of the next node. */ struct roff_node *nt; /* The TEXT node containing the tag. */ size_t len; /* The number of bytes in the tag. */ /* Find the next node. */ n = mdoc->last; for (nn = n; nn != NULL; nn = nn->parent) { if (nn->next != NULL) { nn = nn->next; break; } } /* Find the tag. */ nt = nch = n->child; if (nch == NULL && nn != NULL && nn->child != NULL && nn->child->type == ROFFT_TEXT) nt = nn->child; /* Validate the tag. */ if (nt == NULL || *nt->string == '\0') mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg"); if (nt == NULL) { roff_node_delete(mdoc, n); return; } len = strcspn(nt->string, " \t\\"); if (nt->string[len] != '\0') mandoc_msg(MANDOCERR_TG_SPC, nt->line, nt->pos + len, "Tg %s", nt->string); /* Keep only the first argument. */ if (nch != NULL && nch->next != NULL) { mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line, nch->next->pos, "Tg ... %s", nch->next->string); while (nch->next != NULL) roff_node_delete(mdoc, nch->next); } /* Drop the macro if the first argument is invalid. */ if (len == 0 || nt->string[len] != '\0') { roff_node_delete(mdoc, n); return; } /* By default, tag the .Tg node itself. */ if (nn == NULL || nn->flags & NODE_ID) nn = n; /* Explicit tagging of specific macros. */ switch (nn->tok) { case MDOC_Sh: case MDOC_Ss: case MDOC_Fo: nn = nn->head->child == NULL ? n : nn->head; break; case MDOC_It: np = nn->parent; while (np->tok != MDOC_Bl) np = np->parent; switch (np->norm->Bl.type) { case LIST_column: break; case LIST_diag: case LIST_hang: case LIST_inset: case LIST_ohang: case LIST_tag: nn = nn->head; break; case LIST_bullet: case LIST_dash: case LIST_enum: case LIST_hyphen: case LIST_item: nn = nn->body->child == NULL ? n : nn->body; break; default: abort(); } break; case MDOC_Bd: case MDOC_Bl: case MDOC_D1: case MDOC_Dl: nn = nn->body->child == NULL ? n : nn->body; break; case MDOC_Pp: break; case MDOC_Cm: case MDOC_Dv: case MDOC_Em: case MDOC_Er: case MDOC_Ev: case MDOC_Fl: case MDOC_Fn: case MDOC_Ic: case MDOC_Li: case MDOC_Ms: case MDOC_No: case MDOC_Sy: if (nn->child == NULL) nn = n; break; default: nn = n; break; } tag_put(nt->string, TAG_MANUAL, nn); if (nn != n) n->flags |= NODE_NOPRT; } static void post_obsolete(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK) mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos, "%s", roff_name[n->tok]); } static void post_useless(POST_ARGS) { struct roff_node *n; n = mdoc->last; mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos, "%s", roff_name[n->tok]); } /* * Block macros. */ static void post_bf(POST_ARGS) { struct roff_node *np, *nch; /* * Unlike other data pointers, these are "housed" by the HEAD * element, which contains the goods. */ np = mdoc->last; if (np->type != ROFFT_HEAD) return; assert(np->parent->type == ROFFT_BLOCK); assert(np->parent->tok == MDOC_Bf); /* Check the number of arguments. */ nch = np->child; if (np->parent->args == NULL) { if (nch == NULL) { mandoc_msg(MANDOCERR_BF_NOFONT, np->line, np->pos, "Bf"); return; } nch = nch->next; } if (nch != NULL) mandoc_msg(MANDOCERR_ARG_EXCESS, nch->line, nch->pos, "Bf ... %s", nch->string); /* Extract argument into data. */ if (np->parent->args != NULL) { switch (np->parent->args->argv[0].arg) { case MDOC_Emphasis: np->norm->Bf.font = FONT_Em; break; case MDOC_Literal: np->norm->Bf.font = FONT_Li; break; case MDOC_Symbolic: np->norm->Bf.font = FONT_Sy; break; default: abort(); } return; } /* Extract parameter into data. */ if ( ! strcmp(np->child->string, "Em")) np->norm->Bf.font = FONT_Em; else if ( ! strcmp(np->child->string, "Li")) np->norm->Bf.font = FONT_Li; else if ( ! strcmp(np->child->string, "Sy")) np->norm->Bf.font = FONT_Sy; else mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line, np->child->pos, "Bf %s", np->child->string); } static void post_fname(POST_ARGS) { struct roff_node *n, *nch; const char *cp; size_t pos; n = mdoc->last; nch = n->child; cp = nch->string; if (*cp == '(') { if (cp[strlen(cp + 1)] == ')') return; pos = 0; } else { pos = strcspn(cp, "()"); if (cp[pos] == '\0') { if (n->sec == SEC_DESCRIPTION || n->sec == SEC_CUSTOM) tag_put(NULL, fn_prio++, n); return; } } mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp); } static void post_fn(POST_ARGS) { post_fname(mdoc); post_fa(mdoc); } static void post_fo(POST_ARGS) { const struct roff_node *n; n = mdoc->last; if (n->type != ROFFT_HEAD) return; if (n->child == NULL) { mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo"); return; } if (n->child != n->last) { mandoc_msg(MANDOCERR_ARG_EXCESS, n->child->next->line, n->child->next->pos, "Fo ... %s", n->child->next->string); while (n->child != n->last) roff_node_delete(mdoc, n->last); } else post_delim(mdoc); post_fname(mdoc); } static void post_fa(POST_ARGS) { const struct roff_node *n; const char *cp; for (n = mdoc->last->child; n != NULL; n = n->next) { for (cp = n->string; *cp != '\0'; cp++) { /* Ignore callbacks and alterations. */ if (*cp == '(' || *cp == '{') break; if (*cp != ',') continue; mandoc_msg(MANDOCERR_FA_COMMA, n->line, n->pos + (int)(cp - n->string), "%s", n->string); break; } } post_delim_nb(mdoc); } static void post_nm(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->sec == SEC_NAME && n->child != NULL && n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL) mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1); if (n->last != NULL && n->last->tok == MDOC_Pp) roff_node_relink(mdoc, n->last); if (mdoc->meta.name == NULL) deroff(&mdoc->meta.name, n); if (mdoc->meta.name == NULL || (mdoc->lastsec == SEC_NAME && n->child == NULL)) mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm"); switch (n->type) { case ROFFT_ELEM: post_delim_nb(mdoc); break; case ROFFT_HEAD: post_delim(mdoc); break; default: return; } if ((n->child != NULL && n->child->type == ROFFT_TEXT) || mdoc->meta.name == NULL) return; mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_nd(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->type != ROFFT_BODY) return; if (n->sec != SEC_NAME) mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd"); if (n->child == NULL) mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd"); else post_delim(mdoc); post_hyph(mdoc); } static void post_display(POST_ARGS) { struct roff_node *n, *np; n = mdoc->last; switch (n->type) { case ROFFT_BODY: if (n->end != ENDBODY_NOT) { if (n->tok == MDOC_Bd && n->body->parent->args == NULL) roff_node_delete(mdoc, n); } else if (n->child == NULL) mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "%s", roff_name[n->tok]); else if (n->tok == MDOC_D1) post_hyph(mdoc); break; case ROFFT_BLOCK: if (n->tok == MDOC_Bd) { if (n->args == NULL) { mandoc_msg(MANDOCERR_BD_NOARG, n->line, n->pos, "Bd"); mdoc->next = ROFF_NEXT_SIBLING; while (n->body->child != NULL) roff_node_relink(mdoc, n->body->child); roff_node_delete(mdoc, n); break; } post_bd(mdoc); post_prevpar(mdoc); } for (np = n->parent; np != NULL; np = np->parent) { if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) { mandoc_msg(MANDOCERR_BD_NEST, n->line, n->pos, "%s in Bd", roff_name[n->tok]); break; } } break; default: break; } } static void post_defaults(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->child != NULL) { post_delim_nb(mdoc); return; } mdoc->next = ROFF_NEXT_CHILD; switch (n->tok) { case MDOC_Ar: roff_word_alloc(mdoc, n->line, n->pos, "file"); mdoc->last->flags |= NODE_NOSRC; roff_word_alloc(mdoc, n->line, n->pos, "..."); break; case MDOC_Pa: case MDOC_Mt: roff_word_alloc(mdoc, n->line, n->pos, "~"); break; default: abort(); } mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_at(POST_ARGS) { struct roff_node *n, *nch; const char *att; n = mdoc->last; nch = n->child; /* * If we have a child, look it up in the standard keys. If a * key exist, use that instead of the child; if it doesn't, * prefix "AT&T UNIX " to the existing data. */ att = NULL; if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL)) mandoc_msg(MANDOCERR_AT_BAD, nch->line, nch->pos, "At %s", nch->string); mdoc->next = ROFF_NEXT_CHILD; if (att != NULL) { roff_word_alloc(mdoc, nch->line, nch->pos, att); nch->flags |= NODE_NOPRT; } else roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX"); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_an(POST_ARGS) { struct roff_node *np, *nch; post_an_norm(mdoc); np = mdoc->last; nch = np->child; if (np->norm->An.auth == AUTH__NONE) { if (nch == NULL) mandoc_msg(MANDOCERR_MACRO_EMPTY, np->line, np->pos, "An"); else post_delim_nb(mdoc); } else if (nch != NULL) mandoc_msg(MANDOCERR_ARG_EXCESS, nch->line, nch->pos, "An ... %s", nch->string); } static void post_em(POST_ARGS) { post_tag(mdoc); tag_put(NULL, TAG_FALLBACK, mdoc->last); } static void post_en(POST_ARGS) { post_obsolete(mdoc); if (mdoc->last->type == ROFFT_BLOCK) mdoc->last->norm->Es = mdoc->last_es; } static void post_er(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->sec == SEC_ERRORS && (n->parent->tok == MDOC_It || (n->parent->tok == MDOC_Bq && n->parent->parent->parent->tok == MDOC_It))) tag_put(NULL, TAG_STRONG, n); post_delim_nb(mdoc); } static void post_tag(POST_ARGS) { struct roff_node *n; n = mdoc->last; if ((n->prev == NULL || (n->prev->type == ROFFT_TEXT && strcmp(n->prev->string, "|") == 0)) && (n->parent->tok == MDOC_It || (n->parent->tok == MDOC_Xo && n->parent->parent->prev == NULL && n->parent->parent->parent->tok == MDOC_It))) tag_put(NULL, TAG_STRONG, n); post_delim_nb(mdoc); } static void post_es(POST_ARGS) { post_obsolete(mdoc); mdoc->last_es = mdoc->last; } static void post_fl(POST_ARGS) { struct roff_node *n; char *cp; /* * Transform ".Fl Fl long" to ".Fl \-long", * resulting for example in better HTML output. */ n = mdoc->last; if (n->prev != NULL && n->prev->tok == MDOC_Fl && n->prev->child == NULL && n->child != NULL && (n->flags & NODE_LINE) == 0) { mandoc_asprintf(&cp, "\\-%s", n->child->string); free(n->child->string); n->child->string = cp; roff_node_delete(mdoc, n->prev); } post_tag(mdoc); } static void post_xx(POST_ARGS) { struct roff_node *n; const char *os; char *v; post_delim_nb(mdoc); n = mdoc->last; switch (n->tok) { case MDOC_Bsx: os = "BSD/OS"; break; case MDOC_Dx: os = "DragonFly"; break; case MDOC_Fx: os = "FreeBSD"; break; case MDOC_Nx: os = "NetBSD"; if (n->child == NULL) break; v = n->child->string; if ((v[0] != '0' && v[0] != '1') || v[1] != '.' || v[2] < '0' || v[2] > '9' || v[3] < 'a' || v[3] > 'z' || v[4] != '\0') break; n->child->flags |= NODE_NOPRT; mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->child->line, n->child->pos, v); v = mdoc->last->string; v[3] = toupper((unsigned char)v[3]); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; break; case MDOC_Ox: os = "OpenBSD"; break; case MDOC_Ux: os = "UNIX"; break; default: abort(); } mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, os); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; } static void post_it(POST_ARGS) { struct roff_node *nbl, *nit, *nch; int i, cols; enum mdoc_list lt; post_prevpar(mdoc); nit = mdoc->last; if (nit->type != ROFFT_BLOCK) return; nbl = nit->parent->parent; lt = nbl->norm->Bl.type; switch (lt) { case LIST_tag: case LIST_hang: case LIST_ohang: case LIST_inset: case LIST_diag: if (nit->head->child == NULL) mandoc_msg(MANDOCERR_IT_NOHEAD, nit->line, nit->pos, "Bl -%s It", mdoc_argnames[nbl->args->argv[0].arg]); break; case LIST_bullet: case LIST_dash: case LIST_enum: case LIST_hyphen: if (nit->body == NULL || nit->body->child == NULL) mandoc_msg(MANDOCERR_IT_NOBODY, nit->line, nit->pos, "Bl -%s It", mdoc_argnames[nbl->args->argv[0].arg]); /* FALLTHROUGH */ case LIST_item: if ((nch = nit->head->child) != NULL) mandoc_msg(MANDOCERR_ARG_SKIP, nit->line, nit->pos, "It %s", nch->type == ROFFT_TEXT ? nch->string : roff_name[nch->tok]); break; case LIST_column: cols = (int)nbl->norm->Bl.ncols; assert(nit->head->child == NULL); if (nit->head->next->child == NULL && nit->head->next->next == NULL) { mandoc_msg(MANDOCERR_MACRO_EMPTY, nit->line, nit->pos, "It"); roff_node_delete(mdoc, nit); break; } i = 0; for (nch = nit->child; nch != NULL; nch = nch->next) { if (nch->type != ROFFT_BODY) continue; if (i++ && nch->flags & NODE_LINE) mandoc_msg(MANDOCERR_TA_LINE, nch->line, nch->pos, "Ta"); } if (i < cols || i > cols + 1) mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos, "%d columns, %d cells", cols, i); else if (nit->head->next->child != NULL && nit->head->next->child->flags & NODE_LINE) mandoc_msg(MANDOCERR_IT_NOARG, nit->line, nit->pos, "Bl -column It"); break; default: abort(); } } static void post_bl_block(POST_ARGS) { struct roff_node *n, *ni, *nc; post_prevpar(mdoc); n = mdoc->last; for (ni = n->body->child; ni != NULL; ni = ni->next) { if (ni->body == NULL) continue; nc = ni->body->last; while (nc != NULL) { switch (nc->tok) { case MDOC_Pp: case ROFF_br: break; default: nc = NULL; continue; } if (ni->next == NULL) { mandoc_msg(MANDOCERR_PAR_MOVE, nc->line, nc->pos, "%s", roff_name[nc->tok]); roff_node_relink(mdoc, nc); } else if (n->norm->Bl.comp == 0 && n->norm->Bl.type != LIST_column) { mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos, "%s before It", roff_name[nc->tok]); roff_node_delete(mdoc, nc); } else break; nc = ni->body->last; } } } /* * If the argument of -offset or -width is a macro, * replace it with the associated default width. */ static void rewrite_macro2len(struct roff_man *mdoc, char **arg) { size_t width; enum roff_tok tok; if (*arg == NULL) return; else if ( ! strcmp(*arg, "Ds")) width = 6; else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE) return; else width = macro2len(tok); free(*arg); mandoc_asprintf(arg, "%zun", width); } static void post_bl_head(POST_ARGS) { struct roff_node *nbl, *nh, *nch, *nnext; struct mdoc_argv *argv; int i, j; post_bl_norm(mdoc); nh = mdoc->last; if (nh->norm->Bl.type != LIST_column) { if ((nch = nh->child) == NULL) return; mandoc_msg(MANDOCERR_ARG_EXCESS, nch->line, nch->pos, "Bl ... %s", nch->string); while (nch != NULL) { roff_node_delete(mdoc, nch); nch = nh->child; } return; } /* * Append old-style lists, where the column width specifiers * trail as macro parameters, to the new-style ("normal-form") * lists where they're argument values following -column. */ if (nh->child == NULL) return; nbl = nh->parent; for (j = 0; j < (int)nbl->args->argc; j++) if (nbl->args->argv[j].arg == MDOC_Column) break; assert(j < (int)nbl->args->argc); /* * Accommodate for new-style groff column syntax. Shuffle the * child nodes, all of which must be TEXT, as arguments for the * column field. Then, delete the head children. */ argv = nbl->args->argv + j; i = argv->sz; for (nch = nh->child; nch != NULL; nch = nch->next) argv->sz++; argv->value = mandoc_reallocarray(argv->value, argv->sz, sizeof(char *)); nh->norm->Bl.ncols = argv->sz; nh->norm->Bl.cols = (void *)argv->value; for (nch = nh->child; nch != NULL; nch = nnext) { argv->value[i++] = nch->string; nch->string = NULL; nnext = nch->next; roff_node_delete(NULL, nch); } nh->child = NULL; } static void post_bl(POST_ARGS) { struct roff_node *nbody; /* of the Bl */ struct roff_node *nchild, *nnext; /* of the Bl body */ const char *prev_Er; int order; nbody = mdoc->last; switch (nbody->type) { case ROFFT_BLOCK: post_bl_block(mdoc); return; case ROFFT_HEAD: post_bl_head(mdoc); return; case ROFFT_BODY: break; default: return; } if (nbody->end != ENDBODY_NOT) return; /* * Up to the first item, move nodes before the list, * but leave transparent nodes where they are * if they precede an item. * The next non-transparent node is kept in nchild. * It only needs to be updated after a non-transparent * node was moved out, and at the very beginning * when no node at all was moved yet. */ nchild = mdoc->last; for (;;) { if (nchild == mdoc->last) nchild = roff_node_child(nbody); if (nchild == NULL) { mdoc->last = nbody; mandoc_msg(MANDOCERR_BLK_EMPTY, nbody->line, nbody->pos, "Bl"); return; } if (nchild->tok == MDOC_It) { mdoc->last = nbody; break; } mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line, nbody->child->pos, "%s", roff_name[nbody->child->tok]); if (nbody->parent->prev == NULL) { mdoc->last = nbody->parent->parent; mdoc->next = ROFF_NEXT_CHILD; } else { mdoc->last = nbody->parent->prev; mdoc->next = ROFF_NEXT_SIBLING; } roff_node_relink(mdoc, nbody->child); } /* * We have reached the first item, * so moving nodes out is no longer possible. * But in .Bl -column, the first rows may be implicit, * that is, they may not start with .It macros. * Such rows may be followed by nodes generated on the * roff level, for example .TS. * Wrap such roff nodes into an implicit row. */ while (nchild != NULL) { if (nchild->tok == MDOC_It) { nchild = roff_node_next(nchild); continue; } nnext = nchild->next; mdoc->last = nchild->prev; mdoc->next = ROFF_NEXT_SIBLING; roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It); roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It); mdoc->next = ROFF_NEXT_SIBLING; roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It); while (nchild->tok != MDOC_It) { roff_node_relink(mdoc, nchild); if (nnext == NULL) break; nchild = nnext; nnext = nchild->next; mdoc->next = ROFF_NEXT_SIBLING; } mdoc->last = nbody; } if (mdoc->meta.os_e != MANDOC_OS_NETBSD) return; prev_Er = NULL; for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) { if (nchild->tok != MDOC_It) continue; if ((nnext = nchild->head->child) == NULL) continue; if (nnext->type == ROFFT_BLOCK) nnext = nnext->body->child; if (nnext == NULL || nnext->tok != MDOC_Er) continue; nnext = nnext->child; if (prev_Er != NULL) { order = strcmp(prev_Er, nnext->string); if (order > 0) mandoc_msg(MANDOCERR_ER_ORDER, nnext->line, nnext->pos, "Er %s %s (NetBSD)", prev_Er, nnext->string); else if (order == 0) mandoc_msg(MANDOCERR_ER_REP, nnext->line, nnext->pos, "Er %s (NetBSD)", prev_Er); } prev_Er = nnext->string; } } static void post_bk(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->type == ROFFT_BLOCK && n->body->child == NULL) { mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk"); roff_node_delete(mdoc, n); } } static void post_sm(POST_ARGS) { struct roff_node *nch; nch = mdoc->last->child; if (nch == NULL) { mdoc->flags ^= MDOC_SMOFF; return; } assert(nch->type == ROFFT_TEXT); if ( ! strcmp(nch->string, "on")) { mdoc->flags &= ~MDOC_SMOFF; return; } if ( ! strcmp(nch->string, "off")) { mdoc->flags |= MDOC_SMOFF; return; } mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos, "%s %s", roff_name[mdoc->last->tok], nch->string); roff_node_relink(mdoc, nch); return; } static void post_root(POST_ARGS) { struct roff_node *n; /* Add missing prologue data. */ if (mdoc->meta.date == NULL) mdoc->meta.date = mandoc_normdate(NULL, NULL); if (mdoc->meta.title == NULL) { mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF"); mdoc->meta.title = mandoc_strdup("UNTITLED"); } if (mdoc->meta.vol == NULL) mdoc->meta.vol = mandoc_strdup("LOCAL"); if (mdoc->meta.os == NULL) { mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL); mdoc->meta.os = mandoc_strdup(""); } else if (mdoc->meta.os_e && (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0) mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0, mdoc->meta.os_e == MANDOC_OS_OPENBSD ? "(OpenBSD)" : "(NetBSD)"); if (mdoc->meta.arch != NULL && arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) { n = mdoc->meta.first->child; while (n->tok != MDOC_Dt || n->child == NULL || n->child->next == NULL || n->child->next->next == NULL) n = n->next; n = n->child->next->next; mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos, "Dt ... %s %s", mdoc->meta.arch, mdoc->meta.os_e == MANDOC_OS_OPENBSD ? "(OpenBSD)" : "(NetBSD)"); } /* Check that we begin with a proper `Sh'. */ n = mdoc->meta.first->child; while (n != NULL && (n->type == ROFFT_COMMENT || (n->tok >= MDOC_Dd && mdoc_macro(n->tok)->flags & MDOC_PROLOGUE))) n = n->next; if (n == NULL) mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL); else if (n->tok != MDOC_Sh) mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos, "%s", roff_name[n->tok]); } static void post_rs(POST_ARGS) { struct roff_node *np, *nch, *next, *prev; int i, j; np = mdoc->last; if (np->type != ROFFT_BODY) return; if (np->child == NULL) { mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs"); return; } /* * The full `Rs' block needs special handling to order the * sub-elements according to `rsord'. Pick through each element * and correctly order it. This is an insertion sort. */ next = NULL; for (nch = np->child->next; nch != NULL; nch = next) { /* Determine order number of this child. */ for (i = 0; i < RSORD_MAX; i++) if (rsord[i] == nch->tok) break; if (i == RSORD_MAX) { mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos, "%s", roff_name[nch->tok]); i = -1; } else if (nch->tok == MDOC__J || nch->tok == MDOC__B) np->norm->Rs.quote_T++; /* * Remove this child from the chain. This somewhat * repeats roff_node_unlink(), but since we're * just re-ordering, there's no need for the * full unlink process. */ if ((next = nch->next) != NULL) next->prev = nch->prev; if ((prev = nch->prev) != NULL) prev->next = nch->next; nch->prev = nch->next = NULL; /* * Scan back until we reach a node that's * to be ordered before this child. */ for ( ; prev ; prev = prev->prev) { /* Determine order of `prev'. */ for (j = 0; j < RSORD_MAX; j++) if (rsord[j] == prev->tok) break; if (j == RSORD_MAX) j = -1; if (j <= i) break; } /* * Set this child back into its correct place * in front of the `prev' node. */ nch->prev = prev; if (prev == NULL) { np->child->prev = nch; nch->next = np->child; np->child = nch; } else { if (prev->next) prev->next->prev = nch; nch->next = prev->next; prev->next = nch; } } } /* * For some arguments of some macros, * convert all breakable hyphens into ASCII_HYPH. */ static void post_hyph(POST_ARGS) { struct roff_node *n, *nch; char *cp; n = mdoc->last; for (nch = n->child; nch != NULL; nch = nch->next) { if (nch->type != ROFFT_TEXT) continue; cp = nch->string; if (*cp == '\0') continue; while (*(++cp) != '\0') if (*cp == '-' && isalpha((unsigned char)cp[-1]) && isalpha((unsigned char)cp[1])) { if (n->tag == NULL && n->flags & NODE_ID) n->tag = mandoc_strdup(nch->string); *cp = ASCII_HYPH; } } } static void post_ns(POST_ARGS) { struct roff_node *n; n = mdoc->last; if (n->flags & NODE_LINE || (n->next != NULL && n->next->flags & NODE_DELIMC)) mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL); } static void post_sx(POST_ARGS) { post_delim(mdoc); post_hyph(mdoc); } static void post_sh(POST_ARGS) { post_section(mdoc); switch (mdoc->last->type) { case ROFFT_HEAD: post_sh_head(mdoc); break; case ROFFT_BODY: switch (mdoc->lastsec) { case SEC_NAME: post_sh_name(mdoc); break; case SEC_SEE_ALSO: post_sh_see_also(mdoc); break; case SEC_AUTHORS: post_sh_authors(mdoc); break; default: break; } break; default: break; } } static void post_sh_name(POST_ARGS) { struct roff_node *n; int hasnm, hasnd; hasnm = hasnd = 0; for (n = mdoc->last->child; n != NULL; n = n->next) { switch (n->tok) { case MDOC_Nm: if (hasnm && n->child != NULL) mandoc_msg(MANDOCERR_NAMESEC_PUNCT, n->line, n->pos, "Nm %s", n->child->string); hasnm = 1; continue; case MDOC_Nd: hasnd = 1; if (n->next != NULL) mandoc_msg(MANDOCERR_NAMESEC_ND, n->line, n->pos, NULL); break; case TOKEN_NONE: if (n->type == ROFFT_TEXT && n->string[0] == ',' && n->string[1] == '\0' && n->next != NULL && n->next->tok == MDOC_Nm) { n = n->next; continue; } /* FALLTHROUGH */ default: mandoc_msg(MANDOCERR_NAMESEC_BAD, n->line, n->pos, "%s", roff_name[n->tok]); continue; } break; } if ( ! hasnm) mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->last->line, mdoc->last->pos, NULL); if ( ! hasnd) mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->last->line, mdoc->last->pos, NULL); } static void post_sh_see_also(POST_ARGS) { const struct roff_node *n; const char *name, *sec; const char *lastname, *lastsec, *lastpunct; int cmp; n = mdoc->last->child; lastname = lastsec = lastpunct = NULL; while (n != NULL) { if (n->tok != MDOC_Xr || n->child == NULL || n->child->next == NULL) break; /* Process one .Xr node. */ name = n->child->string; sec = n->child->next->string; if (lastsec != NULL) { if (lastpunct[0] != ',' || lastpunct[1] != '\0') mandoc_msg(MANDOCERR_XR_PUNCT, n->line, n->pos, "%s before %s(%s)", lastpunct, name, sec); cmp = strcmp(lastsec, sec); if (cmp > 0) mandoc_msg(MANDOCERR_XR_ORDER, n->line, n->pos, "%s(%s) after %s(%s)", name, sec, lastname, lastsec); else if (cmp == 0 && strcasecmp(lastname, name) > 0) mandoc_msg(MANDOCERR_XR_ORDER, n->line, n->pos, "%s after %s", name, lastname); } lastname = name; lastsec = sec; /* Process the following node. */ n = n->next; if (n == NULL) break; if (n->tok == MDOC_Xr) { lastpunct = "none"; continue; } if (n->type != ROFFT_TEXT) break; for (name = n->string; *name != '\0'; name++) if (isalpha((const unsigned char)*name)) return; lastpunct = n->string; if (n->next == NULL || n->next->tok == MDOC_Rs) mandoc_msg(MANDOCERR_XR_PUNCT, n->line, n->pos, "%s after %s(%s)", lastpunct, lastname, lastsec); n = n->next; } } static int child_an(const struct roff_node *n) { for (n = n->child; n != NULL; n = n->next) if ((n->tok == MDOC_An && n->child != NULL) || child_an(n)) return 1; return 0; } static void post_sh_authors(POST_ARGS) { if ( ! child_an(mdoc->last)) mandoc_msg(MANDOCERR_AN_MISSING, mdoc->last->line, mdoc->last->pos, NULL); } /* * Return an upper bound for the string distance (allowing * transpositions). Not a full Levenshtein implementation * because Levenshtein is quadratic in the string length * and this function is called for every standard name, * so the check for each custom name would be cubic. * The following crude heuristics is linear, resulting * in quadratic behaviour for checking one custom name, * which does not cause measurable slowdown. */ static int similar(const char *s1, const char *s2) { const int maxdist = 3; int dist = 0; while (s1[0] != '\0' && s2[0] != '\0') { if (s1[0] == s2[0]) { s1++; s2++; continue; } if (++dist > maxdist) return INT_MAX; if (s1[1] == s2[1]) { /* replacement */ s1++; s2++; } else if (s1[0] == s2[1] && s1[1] == s2[0]) { s1 += 2; /* transposition */ s2 += 2; } else if (s1[0] == s2[1]) /* insertion */ s2++; else if (s1[1] == s2[0]) /* deletion */ s1++; else return INT_MAX; } dist += strlen(s1) + strlen(s2); return dist > maxdist ? INT_MAX : dist; } static void post_sh_head(POST_ARGS) { struct roff_node *nch; const char *goodsec; const char *const *testsec; int dist, mindist; enum roff_sec sec; /* * Process a new section. Sections are either "named" or * "custom". Custom sections are user-defined, while named ones * follow a conventional order and may only appear in certain * manual sections. */ sec = mdoc->last->sec; /* The NAME should be first. */ if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE) mandoc_msg(MANDOCERR_NAMESEC_FIRST, mdoc->last->line, mdoc->last->pos, "Sh %s", sec != SEC_CUSTOM ? secnames[sec] : (nch = mdoc->last->child) == NULL ? "" : nch->type == ROFFT_TEXT ? nch->string : roff_name[nch->tok]); /* The SYNOPSIS gets special attention in other areas. */ if (sec == SEC_SYNOPSIS) { roff_setreg(mdoc->roff, "nS", 1, '='); mdoc->flags |= MDOC_SYNOPSIS; } else { roff_setreg(mdoc->roff, "nS", 0, '='); mdoc->flags &= ~MDOC_SYNOPSIS; } if (sec == SEC_DESCRIPTION) fn_prio = TAG_STRONG; /* Mark our last section. */ mdoc->lastsec = sec; /* We don't care about custom sections after this. */ if (sec == SEC_CUSTOM) { if ((nch = mdoc->last->child) == NULL || nch->type != ROFFT_TEXT || nch->next != NULL) return; goodsec = NULL; mindist = INT_MAX; for (testsec = secnames + 1; *testsec != NULL; testsec++) { dist = similar(nch->string, *testsec); if (dist < mindist) { goodsec = *testsec; mindist = dist; } } if (goodsec != NULL) mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos, "Sh %s instead of %s", nch->string, goodsec); return; } /* * Check whether our non-custom section is being repeated or is * out of order. */ if (sec == mdoc->lastnamed) mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line, mdoc->last->pos, "Sh %s", secnames[sec]); if (sec < mdoc->lastnamed) mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line, mdoc->last->pos, "Sh %s", secnames[sec]); /* Mark the last named section. */ mdoc->lastnamed = sec; /* Check particular section/manual conventions. */ if (mdoc->meta.msec == NULL) return; goodsec = NULL; switch (sec) { case SEC_ERRORS: case SEC_RETURN_VALUES: if (*mdoc->meta.msec == '4') break; if (*mdoc->meta.msec == '7') break; goodsec = "2, 3, 4, 7, 9"; /* FALLTHROUGH */ case SEC_LIBRARY: if (*mdoc->meta.msec == '2') break; if (*mdoc->meta.msec == '3') break; if (NULL == goodsec) goodsec = "2, 3, 9"; /* FALLTHROUGH */ case SEC_CONTEXT: if (*mdoc->meta.msec == '9') break; if (NULL == goodsec) goodsec = "9"; mandoc_msg(MANDOCERR_SEC_MSEC, mdoc->last->line, mdoc->last->pos, "Sh %s for %s only", secnames[sec], goodsec); break; default: break; } } static void post_xr(POST_ARGS) { struct roff_node *n, *nch; n = mdoc->last; nch = n->child; if (nch->next == NULL) { mandoc_msg(MANDOCERR_XR_NOSEC, n->line, n->pos, "Xr %s", nch->string); } else { assert(nch->next == n->last); if(mandoc_xr_add(nch->next->string, nch->string, nch->line, nch->pos)) mandoc_msg(MANDOCERR_XR_SELF, nch->line, nch->pos, "Xr %s %s", nch->string, nch->next->string); } post_delim_nb(mdoc); } static void post_section(POST_ARGS) { struct roff_node *n, *nch; char *cp, *tag; n = mdoc->last; switch (n->type) { case ROFFT_BLOCK: post_prevpar(mdoc); return; case ROFFT_HEAD: tag = NULL; deroff(&tag, n); if (tag != NULL) { for (cp = tag; *cp != '\0'; cp++) if (*cp == ' ') *cp = '_'; if ((nch = n->child) != NULL && nch->type == ROFFT_TEXT && strcmp(nch->string, tag) == 0) tag_put(NULL, TAG_STRONG, n); else tag_put(tag, TAG_FALLBACK, n); free(tag); } post_delim(mdoc); post_hyph(mdoc); return; case ROFFT_BODY: break; default: return; } if ((nch = n->child) != NULL && (nch->tok == MDOC_Pp || nch->tok == ROFF_br || nch->tok == ROFF_sp)) { mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos, "%s after %s", roff_name[nch->tok], roff_name[n->tok]); roff_node_delete(mdoc, nch); } if ((nch = n->last) != NULL && (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) { mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos, "%s at the end of %s", roff_name[nch->tok], roff_name[n->tok]); roff_node_delete(mdoc, nch); } } static void post_prevpar(POST_ARGS) { struct roff_node *n, *np; n = mdoc->last; if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK) return; if ((np = roff_node_prev(n)) == NULL) return; /* * Don't allow `Pp' prior to a paragraph-type * block: `Pp' or non-compact `Bd' or `Bl'. */ if (np->tok != MDOC_Pp && np->tok != ROFF_br) return; if (n->tok == MDOC_Bl && n->norm->Bl.comp) return; if (n->tok == MDOC_Bd && n->norm->Bd.comp) return; if (n->tok == MDOC_It && n->parent->norm->Bl.comp) return; mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, "%s before %s", roff_name[np->tok], roff_name[n->tok]); roff_node_delete(mdoc, np); } static void post_par(POST_ARGS) { struct roff_node *np; fn_prio = TAG_STRONG; post_prevpar(mdoc); np = mdoc->last; if (np->child != NULL) mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos, "%s %s", roff_name[np->tok], np->child->string); } static void post_dd(POST_ARGS) { struct roff_node *n; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->meta.date != NULL) { mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd"); free(mdoc->meta.date); } else if (mdoc->flags & MDOC_PBODY) mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd"); else if (mdoc->meta.title != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, n->line, n->pos, "Dd after Dt"); else if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, n->line, n->pos, "Dd after Os"); if (mdoc->quick && n != NULL) mdoc->meta.date = mandoc_strdup(""); else mdoc->meta.date = mandoc_normdate(n->child, n); } static void post_dt(POST_ARGS) { struct roff_node *nn, *n; const char *cp; char *p; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->flags & MDOC_PBODY) { mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt"); return; } if (mdoc->meta.title != NULL) mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt"); else if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_ORDER, n->line, n->pos, "Dt after Os"); free(mdoc->meta.title); free(mdoc->meta.msec); free(mdoc->meta.vol); free(mdoc->meta.arch); mdoc->meta.title = NULL; mdoc->meta.msec = NULL; mdoc->meta.vol = NULL; mdoc->meta.arch = NULL; /* Mandatory first argument: title. */ nn = n->child; if (nn == NULL || *nn->string == '\0') { mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt"); mdoc->meta.title = mandoc_strdup("UNTITLED"); } else { mdoc->meta.title = mandoc_strdup(nn->string); /* Check that all characters are uppercase. */ for (p = nn->string; *p != '\0'; p++) if (islower((unsigned char)*p)) { mandoc_msg(MANDOCERR_TITLE_CASE, nn->line, nn->pos + (int)(p - nn->string), "Dt %s", nn->string); break; } } /* Mandatory second argument: section. */ if (nn != NULL) nn = nn->next; if (nn == NULL) { mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos, "Dt %s", mdoc->meta.title); mdoc->meta.vol = mandoc_strdup("LOCAL"); return; /* msec and arch remain NULL. */ } mdoc->meta.msec = mandoc_strdup(nn->string); /* Infer volume title from section number. */ cp = mandoc_a2msec(nn->string); if (cp == NULL) { mandoc_msg(MANDOCERR_MSEC_BAD, nn->line, nn->pos, "Dt ... %s", nn->string); mdoc->meta.vol = mandoc_strdup(nn->string); } else { mdoc->meta.vol = mandoc_strdup(cp); if (mdoc->filesec != '\0' && mdoc->filesec != *nn->string && *nn->string >= '1' && *nn->string <= '9') mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos, "*.%c vs Dt ... %c", mdoc->filesec, *nn->string); } /* Optional third argument: architecture. */ if ((nn = nn->next) == NULL) return; for (p = nn->string; *p != '\0'; p++) *p = tolower((unsigned char)*p); mdoc->meta.arch = mandoc_strdup(nn->string); /* Ignore fourth and later arguments. */ if ((nn = nn->next) != NULL) mandoc_msg(MANDOCERR_ARG_EXCESS, nn->line, nn->pos, "Dt ... %s", nn->string); } static void post_bx(POST_ARGS) { struct roff_node *n, *nch; const char *macro; post_delim_nb(mdoc); n = mdoc->last; nch = n->child; if (nch != NULL) { macro = !strcmp(nch->string, "Open") ? "Ox" : !strcmp(nch->string, "Net") ? "Nx" : !strcmp(nch->string, "Free") ? "Fx" : !strcmp(nch->string, "DragonFly") ? "Dx" : NULL; if (macro != NULL) mandoc_msg(MANDOCERR_BX, n->line, n->pos, "%s", macro); mdoc->last = nch; nch = nch->next; mdoc->next = ROFF_NEXT_SIBLING; roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); mdoc->last->flags |= NODE_NOSRC; mdoc->next = ROFF_NEXT_SIBLING; } else mdoc->next = ROFF_NEXT_CHILD; roff_word_alloc(mdoc, n->line, n->pos, "BSD"); mdoc->last->flags |= NODE_NOSRC; if (nch == NULL) { mdoc->last = n; return; } roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); mdoc->last->flags |= NODE_NOSRC; mdoc->next = ROFF_NEXT_SIBLING; roff_word_alloc(mdoc, n->line, n->pos, "-"); mdoc->last->flags |= NODE_NOSRC; roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns); mdoc->last->flags |= NODE_NOSRC; mdoc->last = n; /* * Make `Bx's second argument always start with an uppercase * letter. Groff checks if it's an "accepted" term, but we just * uppercase blindly. */ *nch->string = (char)toupper((unsigned char)*nch->string); } static void post_os(POST_ARGS) { #ifndef OSNAME struct utsname utsname; static char *defbuf; #endif struct roff_node *n; n = mdoc->last; n->flags |= NODE_NOPRT; if (mdoc->meta.os != NULL) mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os"); else if (mdoc->flags & MDOC_PBODY) mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os"); post_delim(mdoc); /* * Set the operating system by way of the `Os' macro. * The order of precedence is: * 1. the argument of the `Os' macro, unless empty * 2. the -Ios=foo command line argument, if provided * 3. -DOSNAME="\"foo\"", if provided during compilation * 4. "sysname release" from uname(2) */ 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; } /* $Id: msec.c,v 1.16 2018/12/14 01:18:26 schwarze Exp $ */ /* * Copyright (c) 2009 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 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 "mandoc.h" #include "libmandoc.h" #define LINE(x, y) \ if (0 == strcmp(p, x)) return(y); const char * mandoc_a2msec(const char *p) { #include "msec.in" return NULL; } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ LINE("1", "User Commands") LINE("1B", "BSD Compatibility Package Commands") LINE("1C", "Communication Commands") LINE("1HAS", "User Commands") LINE("1ONBLD", "illumos Build Tools") LINE("1S", "illumos Specific Commands") LINE("2", "System Calls") LINE("3", "Introduction to Library Functions") LINE("3AVL", "AVL Tree Library Functions") LINE("3BSDMALLOC", "BSD Memory Allocation Library") LINE("3BSM", "Security and Auditing Library Functions") LINE("3C", "Standard C Library Functions") LINE("3C++", "C++ Library Functions") LINE("3C_DB", "Threads Debugging Library Functions") LINE("3CFGADM", "Configuration Administration Library Functions") LINE("3COMMPUTIL", "Communication Protocol Parser Utilities Library Functions") LINE("3CONTRACT", "Contract Management Library Functions") LINE("3CPC", "CPU Performance Counters Library Functions") LINE("3CURSES", "Curses Library Functions") LINE("3DAT", "Direct Access Transport Library Functions") LINE("3DEVID", "Device ID Library Functions") LINE("3DEVINFO", "Device Information Library Functions") LINE("3DLPI", "Data Link Provider Interface Library Functions") LINE("3DNS_SD", "DNS Service Discovery Library Functions") LINE("3DOOR", "Door Library Functions") LINE("3ELF", "ELF Library Functions") LINE("3EXACCT", "Extended Accounting File Access Library Functions") LINE("3EXT", "Extended Library Functions") LINE("3FCOE", "FCoE Port Management Library Functions") LINE("3FSTYP", "File System Type Identification Library Functions") LINE("3GEN", "String Pattern-Matching Library Functions") LINE("3GSS", "Generic Security Services API Library Functions") LINE("3HEAD", "Headers") LINE("3ISCSIT", "iSCSI Management Library Functions") LINE("3JEDEC", "JEDEC Support Library Functions") LINE("3KRB", "Kerberos Library Functions") LINE("3KRB5", "MIT Kerberos 5 Library Functions") LINE("3KSTAT", "Kernel Statistics Library Functions") LINE("3KVM", "Kernel VM Library Functions") LINE("3LDAP", "LDAP Library Functions") LINE("3LGRP", "Locality Group Library Functions") LINE("3LIB", "Interface Libraries") LINE("3M", "Mathematical Library Functions") LINE("3MAIL", "User Mailbox Library Functions") LINE("3MALLOC", "Memory Allocation Library Functions") LINE("3MP", "Multiple Precision Library Functions") LINE("3MPAPI", "Common Multipath Management Library Functions") LINE("3NSL", "Networking Services Library Functions") LINE("3NVPAIR", "Name-value Pair Library Functions") LINE("3OFMT", "Formatted Output Functions") LINE("3PAM", "PAM Library Functions") LINE("3PAPI", "PAPI Library Functions") LINE("3PERL", "Perl Library Functions") LINE("3PICL", "PICL Library Functions") LINE("3PICLTREE", "PICL Plug-In Library Functions") LINE("3POOL", "Pool Configuration Manipulation Library Functions") LINE("3PROC", "Process Control Library Functions") LINE("3PROJECT", "Project Database Access Library Functions") LINE("3RAC", "Remote Asynchronous Calls Library Functions") LINE("3RESOLV", "Resolver Library Functions") LINE("3RPC", "RPC Library Functions") LINE("3RSM", "Remote Shared Memory Library Functions") LINE("3RT", "Realtime Library Functions") LINE("3SASL", "Simple Authentication Security Layer Library Functions") LINE("3SCF", "Service Configuration Facility Library Functions") LINE("3SEC", "File Access Control Library Functions") LINE("3SECDB", "Security Attributes Database Library Functions") LINE("3SIP", "Session Initiation Protocol Library Functions") LINE("3SLP", "Service Location Protocol Library Functions") LINE("3SOCKET", "Sockets Library Functions") LINE("3STMF", "SCSI Target Mode Framework Library Functions") LINE("3SYSEVENT", "System Event Library Functions") LINE("3TECLA", "Interactive Command-line Input Library Functions") LINE("3TNF", "TNF Library Functions") LINE("3TSOL", "Trusted Extensions Library Functions") LINE("3UTEMPTER", "UTEMPTER Library Functions") LINE("3UUID", "Universally Unique Identifier Library Functions") LINE("3VOLMGT", "Volume Management Library Functions") LINE("3XCURSES", "X/Open Curses Library Functions") LINE("3XNET", "X/Open Networking Services Library Functions") LINE("3F", "Fortran Library Routines") LINE("3X", "Miscellaneous Library Functions") LINE("4", "Device and Network Interfaces") LINE("4D", "Devices") LINE("4FS", "File Systems") LINE("4I", "Ioctl Requests") LINE("4IPP", "IP Quality of Service Modules") LINE("4M", "STREAMS Modules") LINE("4P", "Protocols") LINE("5", "File Formats and Configurations") LINE("6", "Games and Demos") LINE("7", "Standards, Environments, and Macros") LINE("8", "Maintenance Commands and Procedures") LINE("9", "Kernel Concepts") LINE("9E", "Driver Entry Points") LINE("9F", "Kernel Functions for Drivers") LINE("9P", "Kernel Properties for Drivers") LINE("9S", "Data Structures for Drivers") /* $Id: out.c,v 1.82 2021/09/07 17:07:58 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2019, 2021 * 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 #include "mandoc_aux.h" #include "mandoc.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 ewidth, min1, min2, wanted, width, xwidth; int done, icol, maxcol, necol, nxcol, quirkcol; /* * Allocate the master column specifiers. These will hold the * widths and decimal positions for all cells in the column. It * must be freed and nullified by the caller. */ assert(tbl->cols == NULL); tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols, sizeof(struct roffcol)); opts = sp_first->opts; maxcol = -1; first_group = NULL; for (sp = sp_first; sp != NULL; sp = sp->next) { if (sp->pos != TBL_SPAN_DATA) continue; /* * Account for the data cells in the layout, matching it * to data cells in the data section. */ gp = &first_group; for (dp = sp->first; dp != NULL; dp = dp->next) { icol = dp->layout->col; while (maxcol < icol + dp->hspans) tbl->cols[++maxcol].spacing = SIZE_MAX; col = tbl->cols + icol; col->flags |= dp->layout->flags; if (dp->layout->flags & TBL_CELL_WIGN) continue; /* Handle explicit width specifications. */ if (dp->layout->wstr != NULL && dp->layout->width == 0 && a2roffsu(dp->layout->wstr, &su, SCALE_EN) != NULL) dp->layout->width = (*tbl->sulen)(&su, tbl->arg); if (col->width < dp->layout->width) col->width = dp->layout->width; if (dp->layout->spacing != SIZE_MAX && (col->spacing == SIZE_MAX || col->spacing < dp->layout->spacing)) col->spacing = dp->layout->spacing; /* * Calculate an automatic width. * Except for spanning cells, apply it. */ width = tblcalc_data(tbl, dp->hspans == 0 ? col : NULL, opts, dp, dp->block == 0 ? 0 : dp->layout->width ? dp->layout->width : rmargin ? (rmargin + sp->opts->cols / 2) / (sp->opts->cols + 1) : 0); if (dp->hspans == 0) continue; /* * Build an ordered, singly linked list * of all groups of columns joined by spans, * recording the minimum width for each group. */ while (*gp != NULL && ((*gp)->startcol < icol || (*gp)->endcol < icol + dp->hspans)) gp = &(*gp)->next; if (*gp == NULL || (*gp)->startcol > icol || (*gp)->endcol > icol + dp->hspans) { g = mandoc_malloc(sizeof(*g)); g->next = *gp; g->wanted = width; g->startcol = icol; g->endcol = icol + dp->hspans; *gp = g; } else if ((*gp)->wanted < width) (*gp)->wanted = width; } } /* * The minimum width of columns explicitly specified * in the layout is 1n. */ if (maxcol < sp_first->opts->cols - 1) maxcol = sp_first->opts->cols - 1; for (icol = 0; icol <= maxcol; icol++) { col = tbl->cols + icol; if (col->width < 1) col->width = 1; /* * Column spacings are needed for span width * calculations, so set the default values now. */ if (col->spacing == SIZE_MAX || icol == maxcol) col->spacing = 3; } /* * Replace the minimum widths with the missing widths, * and dismiss groups that are already wide enough. */ gp = &first_group; while ((g = *gp) != NULL) { done = 0; for (icol = g->startcol; icol <= g->endcol; icol++) { width = tbl->cols[icol].width; if (icol < g->endcol) width += tbl->cols[icol].spacing; if (g->wanted <= width) { done = 1; break; } else (*gp)->wanted -= width; } if (done) { *gp = g->next; free(g); } else gp = &(*gp)->next; } while (first_group != NULL) { /* * 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++) { width = tbl->cols[icol].width; if (min1 > width) { min2 = min1; min1 = width; } else if (min1 < width && min2 > width) min2 = width; } /* * 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; } /* Record the effect of the widening. */ gp = &first_group; while ((g = *gp) != NULL) { done = 0; for (icol = g->startcol; icol <= g->endcol; icol++) { if (tbl->cols[icol].width != min1) continue; if (g->wanted <= wanted - min1) { tbl->cols[icol].width += g->wanted; done = 1; break; } tbl->cols[icol].width = wanted; g->wanted -= wanted - min1; } if (done) { *gp = g->next; free(g); } else gp = &(*gp)->next; } } /* * 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; 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; if (col->nwidth > col->width) col->width = col->nwidth; return totsz; } /* $Id: out.h,v 1.34 2020/04/03 11:35:01 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2014, 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. * * Utilities for use by multiple mandoc(1) formatters. */ enum roffscale { SCALE_CM, /* centimeters (c) */ SCALE_IN, /* inches (i) */ SCALE_PC, /* pica (P) */ SCALE_PT, /* points (p) */ SCALE_EM, /* ems (m) */ SCALE_MM, /* mini-ems (M) */ SCALE_EN, /* ens (n) */ SCALE_BU, /* default horizontal (u) */ SCALE_VS, /* default vertical (v) */ SCALE_FS, /* syn. for u (f) */ SCALE_MAX }; struct roffcol { size_t width; /* width of cell */ size_t nwidth; /* max. width of number in cell */ size_t decimal; /* decimal position in cell */ size_t spacing; /* spacing after the column */ int flags; /* layout flags, see tbl_cell */ }; struct roffsu { enum roffscale unit; double scale; }; typedef size_t (*tbl_sulen)(const struct roffsu *, void *); typedef size_t (*tbl_strlen)(const char *, void *); typedef size_t (*tbl_len)(size_t, void *); struct rofftbl { tbl_sulen sulen; /* calculate scaling unit length */ tbl_strlen slen; /* calculate string length */ tbl_len len; /* produce width of empty space */ struct roffcol *cols; /* master column specifiers */ void *arg; /* passed to sulen, slen, and len */ }; #define SCALE_HS_INIT(p, v) \ do { (p)->unit = SCALE_EN; \ (p)->scale = (v); } \ while (/* CONSTCOND */ 0) struct tbl_span; const char *a2roffsu(const char *, struct roffsu *, enum roffscale); void tblcalc(struct rofftbl *, const struct tbl_span *, size_t, size_t); /* $Id: preconv.c,v 1.17 2018/12/13 11:55:47 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. */ #include "config.h" #include #include #include #include #include "mandoc.h" #include "roff.h" #include "mandoc_parse.h" #include "libmandoc.h" int preconv_encode(const struct buf *ib, size_t *ii, struct buf *ob, size_t *oi, int *filenc) { const unsigned char *cu; int nby; unsigned int accum; cu = (const unsigned char *)ib->buf + *ii; assert(*cu & 0x80); if ( ! (*filenc & MPARSE_UTF8)) goto latin; nby = 1; while (nby < 5 && *cu & (1 << (7 - nby))) nby++; switch (nby) { case 2: accum = *cu & 0x1f; if (accum < 0x02) /* Obfuscated ASCII. */ goto latin; break; case 3: accum = *cu & 0x0f; break; case 4: accum = *cu & 0x07; if (accum > 0x04) /* Beyond Unicode. */ goto latin; break; default: /* Bad sequence header. */ goto latin; } cu++; switch (nby) { case 3: if ((accum == 0x00 && ! (*cu & 0x20)) || /* Use 2-byte. */ (accum == 0x0d && *cu & 0x20)) /* Surrogates. */ goto latin; break; case 4: if ((accum == 0x00 && ! (*cu & 0x30)) || /* Use 3-byte. */ (accum == 0x04 && *cu & 0x30)) /* Beyond Unicode. */ goto latin; break; default: break; } while (--nby) { if ((*cu & 0xc0) != 0x80) /* Invalid continuation. */ goto latin; accum <<= 6; accum += *cu & 0x3f; cu++; } assert(accum > 0x7f); assert(accum < 0x110000); assert(accum < 0xd800 || accum > 0xdfff); *oi += snprintf(ob->buf + *oi, 11, "\\[u%.4X]", accum); *ii = (const char *)cu - ib->buf; *filenc &= ~MPARSE_LATIN1; return 1; latin: if ( ! (*filenc & MPARSE_LATIN1)) return 0; *oi += snprintf(ob->buf + *oi, 11, "\\[u%.4X]", (unsigned char)ib->buf[(*ii)++]); *filenc &= ~MPARSE_UTF8; return 1; } int preconv_cue(const struct buf *b, size_t offset) { const char *ln, *eoln, *eoph; size_t sz, phsz; ln = b->buf + offset; sz = b->sz - offset; /* Look for the end-of-line. */ if (NULL == (eoln = memchr(ln, '\n', sz))) eoln = ln + sz; /* Check if we have the correct header/trailer. */ if ((sz = (size_t)(eoln - ln)) < 10 || memcmp(ln, ".\\\" -*-", 7) || memcmp(eoln - 3, "-*-", 3)) return MPARSE_UTF8 | MPARSE_LATIN1; /* Move after the header and adjust for the trailer. */ ln += 7; sz -= 10; while (sz > 0) { while (sz > 0 && ' ' == *ln) { ln++; sz--; } if (0 == sz) break; /* Find the end-of-phrase marker (or eoln). */ if (NULL == (eoph = memchr(ln, ';', sz))) eoph = eoln - 3; else eoph++; /* Only account for the "coding" phrase. */ if ((phsz = eoph - ln) < 7 || strncasecmp(ln, "coding:", 7)) { sz -= phsz; ln += phsz; continue; } sz -= 7; ln += 7; while (sz > 0 && ' ' == *ln) { ln++; sz--; } if (0 == sz) return 0; /* Check us against known encodings. */ if (phsz > 4 && !strncasecmp(ln, "utf-8", 5)) return MPARSE_UTF8; if (phsz > 10 && !strncasecmp(ln, "iso-latin-1", 11)) return MPARSE_LATIN1; return 0; } return MPARSE_UTF8 | MPARSE_LATIN1; } /* $Id: predefs.in,v 1.4 2012/07/18 10:39:19 schwarze Exp $ */ /* * 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 AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * The predefined-string translation tables. Each corresponds to a * predefined strings from (e.g.) tmac/mdoc/doc-nroff. The left-hand * side corresponds to the input sequence (\*x, \*(xx and so on). The * right-hand side is what's produced by libroff. * * XXX - C-escape strings! * XXX - update PREDEF_MAX in roff.c if adding more! */ PREDEF("Am", "&") PREDEF("Ba", "\\fR|\\fP") PREDEF("Ge", "\\(>=") PREDEF("Gt", ">") PREDEF("If", "infinity") PREDEF("Le", "\\(<=") PREDEF("Lq", "\\(lq") PREDEF("Lt", "<") PREDEF("Na", "NaN") PREDEF("Ne", "\\(!=") PREDEF("Pi", "pi") PREDEF("Pm", "\\(+-") PREDEF("Rq", "\\(rq") PREDEF("left-bracket", "[") PREDEF("left-parenthesis", "(") PREDEF("lp", "(") PREDEF("left-singlequote", "\\(oq") PREDEF("q", "\\(dq") PREDEF("quote-left", "\\(oq") PREDEF("quote-right", "\\(cq") PREDEF("R", "\\(rg") PREDEF("right-bracket", "]") PREDEF("right-parenthesis", ")") PREDEF("rp", ")") PREDEF("right-singlequote", "\\(cq") PREDEF("Tm", "(Tm)") PREDEF("Px", "POSIX") PREDEF("Ai", "ANSI") PREDEF("\'", "\\\'") PREDEF("aa", "\\(aa") PREDEF("ga", "\\(ga") PREDEF("`", "\\`") PREDEF("lq", "\\(lq") PREDEF("rq", "\\(rq") PREDEF("ua", "\\(ua") PREDEF("va", "\\(va") PREDEF("<=", "\\(<=") PREDEF(">=", "\\(>=") /* $Id: read.c,v 1.220 2021/06/27 17:57:54 schwarze Exp $ */ /* * Copyright (c) 2010-2020 Ingo Schwarze * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * 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. * * Top-level functions of the mandoc(3) parser: * Parser and input encoding selection, decompression, * handling of input bytes, characters, lines, and files, * handling of roff(7) loops and file inclusion, * and steering of the various parsers. */ #include "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" #include "tag.h" #define REPARSE_LIMIT 1000 struct mparse { struct roff *roff; /* roff parser (!NULL) */ struct roff_man *man; /* man parser */ struct buf *primary; /* buffer currently being parsed */ struct buf *secondary; /* copy of top level input */ struct buf *loop; /* open .while request line */ const char *os_s; /* default operating system */ int options; /* parser options */ int gzip; /* current input file is gzipped */ int filenc; /* encoding of the current file */ int reparse_count; /* finite interp. stack */ int line; /* line number in the file */ }; static void choose_parser(struct mparse *); static void free_buf_list(struct buf *); static void resize_buf(struct buf *, size_t); static int mparse_buf_r(struct mparse *, struct buf, size_t, int); static int read_whole_file(struct mparse *, int, struct buf *, int *); static void mparse_end(struct mparse *); static void resize_buf(struct buf *buf, size_t initial) { buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial; buf->buf = mandoc_realloc(buf->buf, buf->sz); } static void free_buf_list(struct buf *buf) { struct buf *tmp; while (buf != NULL) { tmp = buf; buf = tmp->next; free(tmp->buf); free(tmp); } } static void choose_parser(struct mparse *curp) { char *cp, *ep; int format; /* * If neither command line arguments -mdoc or -man select * a parser nor the roff parser found a .Dd or .TH macro * yet, look ahead in the main input buffer. */ if ((format = roff_getformat(curp->roff)) == 0) { cp = curp->primary->buf; ep = cp + curp->primary->sz; while (cp < ep) { if (*cp == '.' || *cp == '\'') { cp++; if (cp[0] == 'D' && cp[1] == 'd') { format = MPARSE_MDOC; break; } if (cp[0] == 'T' && cp[1] == 'H') { format = MPARSE_MAN; break; } } cp = memchr(cp, '\n', ep - cp); if (cp == NULL) break; cp++; } } if (format == MPARSE_MDOC) { curp->man->meta.macroset = MACROSET_MDOC; if (curp->man->mdocmac == NULL) curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX); } else { curp->man->meta.macroset = MACROSET_MAN; if (curp->man->manmac == NULL) curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX); } curp->man->meta.first->tok = TOKEN_NONE; } /* * Main parse routine for a buffer. * It assumes encoding and line numbering are already set up. * It can recurse directly (for invocations of user-defined * macros, inline equations, and input line traps) * and indirectly (for .so file inclusion). */ static int mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start) { struct buf ln; struct buf *firstln, *lastln, *thisln, *loop; char *cp; size_t pos; /* byte number in the ln buffer */ size_t spos; /* at the start of the current line parse */ int line_result, result; int of; int lnn; /* line number in the real file */ int fd; int inloop; /* Saw .while on this level. */ unsigned char c; ln.sz = 256; ln.buf = mandoc_malloc(ln.sz); ln.next = NULL; firstln = lastln = loop = NULL; lnn = curp->line; pos = 0; inloop = 0; result = ROFF_CONT; while (i < blk.sz && (blk.buf[i] != '\0' || pos != 0)) { if (start) { curp->line = lnn; curp->reparse_count = 0; if (lnn < 3 && curp->filenc & MPARSE_UTF8 && curp->filenc & MPARSE_LATIN1) curp->filenc = preconv_cue(&blk, i); } spos = pos; 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, start && spos == 0 ? pos : 0); /* Process options. */ if (line_result & ROFF_APPEND) assert(line_result == (ROFF_IGN | ROFF_APPEND)); if (line_result & ROFF_USERCALL) assert((line_result & ROFF_MASK) == ROFF_REPARSE); if (line_result & ROFF_USERRET) { assert(line_result == (ROFF_IGN | ROFF_USERRET)); if (start == 0) { /* Return from the current macro. */ result = ROFF_USERRET; goto out; } } switch (line_result & ROFF_LOOPMASK) { case ROFF_IGN: break; case ROFF_WHILE: if (curp->loop != NULL) { if (loop == curp->loop) break; mandoc_msg(MANDOCERR_WHILE_NEST, curp->line, pos, NULL); } curp->loop = thisln; loop = NULL; inloop = 1; break; case ROFF_LOOPCONT: case ROFF_LOOPEXIT: if (curp->loop == NULL) { mandoc_msg(MANDOCERR_WHILE_FAIL, curp->line, pos, NULL); break; } if (inloop == 0) { mandoc_msg(MANDOCERR_WHILE_INTO, curp->line, pos, NULL); curp->loop = loop = NULL; break; } if (line_result & ROFF_LOOPCONT) loop = curp->loop; else { curp->loop = loop = NULL; inloop = 0; } break; default: abort(); } /* Process the main instruction from the roff parser. */ switch (line_result & ROFF_MASK) { case ROFF_IGN: break; case ROFF_CONT: if (curp->man->meta.macroset == MACROSET_NONE) choose_parser(curp); if ((curp->man->meta.macroset == MACROSET_MDOC ? mdoc_parseln(curp->man, curp->line, ln.buf, of) : man_parseln(curp->man, curp->line, ln.buf, of) ) == 2) goto out; break; case ROFF_RERUN: goto rerun; case ROFF_REPARSE: if (++curp->reparse_count > REPARSE_LIMIT) { /* Abort and return to the top level. */ result = ROFF_IGN; mandoc_msg(MANDOCERR_ROFFLOOP, curp->line, pos, NULL); goto out; } result = mparse_buf_r(curp, ln, of, 0); if (line_result & ROFF_USERCALL) { roff_userret(curp->roff); /* Continue normally. */ if (result & ROFF_USERRET) result = ROFF_CONT; } if (start == 0 && result != ROFF_CONT) goto out; break; case ROFF_SO: if ( ! (curp->options & MPARSE_SO) && (i >= blk.sz || blk.buf[i] == '\0')) { curp->man->meta.sodest = mandoc_strdup(ln.buf + of); goto out; } if ((fd = mparse_open(curp, ln.buf + of)) != -1) { mparse_readfd(curp, fd, ln.buf + of); close(fd); } else { mandoc_msg(MANDOCERR_SO_FAIL, curp->line, of, ".so %s: %s", ln.buf + of, strerror(errno)); ln.sz = mandoc_asprintf(&cp, ".sp\nSee the file %s.\n.sp", ln.buf + of); free(ln.buf); ln.buf = cp; of = 0; mparse_buf_r(curp, ln, of, 0); } break; default: abort(); } /* Start the next input line. */ if (loop != NULL && (line_result & ROFF_LOOPMASK) == ROFF_IGN) loop = loop->next; if (loop != NULL) { if ((line_result & ROFF_APPEND) == 0) *ln.buf = '\0'; if (ln.sz < loop->sz) resize_buf(&ln, loop->sz); (void)strlcat(ln.buf, loop->buf, ln.sz); of = 0; goto rerun; } pos = (line_result & ROFF_APPEND) ? strlen(ln.buf) : 0; } out: if (inloop) { if (result != ROFF_USERRET) mandoc_msg(MANDOCERR_WHILE_OUTOF, curp->line, pos, NULL); curp->loop = NULL; } free(ln.buf); if (firstln != curp->secondary) free_buf_list(firstln); return result; } static int read_whole_file(struct mparse *curp, int fd, struct buf *fb, int *with_mmap) { struct stat st; gzFile gz; size_t off; ssize_t ssz; int gzerrnum, retval; if (fstat(fd, &st) == -1) { mandoc_msg(MANDOCERR_FSTAT, 0, 0, "%s", strerror(errno)); return -1; } /* * If we're a regular file, try just reading in the whole entry * via mmap(). This is faster than reading it into blocks, and * since each file is only a few bytes to begin with, I'm not * concerned that this is going to tank any machines. */ if (curp->gzip == 0 && S_ISREG(st.st_mode)) { if (st.st_size > 0x7fffffff) { mandoc_msg(MANDOCERR_TOOLARGE, 0, 0, NULL); return -1; } *with_mmap = 1; fb->sz = (size_t)st.st_size; fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0); if (fb->buf != MAP_FAILED) return 0; } if (curp->gzip) { /* * Duplicating the file descriptor is required * because we will have to call gzclose(3) * to free memory used internally by zlib, * but that will also close the file descriptor, * which this function must not do. */ if ((fd = dup(fd)) == -1) { mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); return -1; } if ((gz = gzdopen(fd, "rb")) == NULL) { mandoc_msg(MANDOCERR_GZDOPEN, 0, 0, "%s", strerror(errno)); close(fd); return -1; } } else gz = NULL; /* * If this isn't a regular file (like, say, stdin), then we must * go the old way and just read things in bit by bit. */ *with_mmap = 0; off = 0; retval = -1; fb->sz = 0; fb->buf = NULL; for (;;) { if (off == fb->sz) { if (fb->sz == (1U << 31)) { mandoc_msg(MANDOCERR_TOOLARGE, 0, 0, NULL); break; } resize_buf(fb, 65536); } ssz = curp->gzip ? gzread(gz, fb->buf + (int)off, fb->sz - off) : read(fd, fb->buf + (int)off, fb->sz - off); if (ssz == 0) { fb->sz = off; retval = 0; break; } if (ssz == -1) { if (curp->gzip) (void)gzerror(gz, &gzerrnum); mandoc_msg(MANDOCERR_READ, 0, 0, "%s", curp->gzip && gzerrnum != Z_ERRNO ? zError(gzerrnum) : strerror(errno)); break; } off += (size_t)ssz; } if (curp->gzip && (gzerrnum = gzclose(gz)) != Z_OK) mandoc_msg(MANDOCERR_GZCLOSE, 0, 0, "%s", gzerrnum == Z_ERRNO ? strerror(errno) : zError(gzerrnum)); if (retval == -1) { free(fb->buf); fb->buf = NULL; } return retval; } static void mparse_end(struct mparse *curp) { if (curp->man->meta.macroset == MACROSET_NONE) curp->man->meta.macroset = MACROSET_MAN; if (curp->man->meta.macroset == MACROSET_MDOC) mdoc_endparse(curp->man); else man_endparse(curp->man); roff_endparse(curp->roff); } /* * Read the whole file into memory and call the parsers. * Called recursively when an .so request is encountered. */ void mparse_readfd(struct mparse *curp, int fd, const char *filename) { static int recursion_depth; struct buf blk; struct buf *save_primary; const char *save_filename, *cp; size_t offset; int save_filenc, save_lineno; int with_mmap; if (recursion_depth > 64) { mandoc_msg(MANDOCERR_ROFFLOOP, curp->line, 0, NULL); return; } else if (recursion_depth == 0 && (cp = strrchr(filename, '.')) != NULL && cp[1] >= '1' && cp[1] <= '9') curp->man->filesec = cp[1]; else curp->man->filesec = '\0'; if (read_whole_file(curp, fd, &blk, &with_mmap) == -1) return; /* * Save some properties of the parent file. */ save_primary = curp->primary; save_filenc = curp->filenc; save_lineno = curp->line; save_filename = mandoc_msg_getinfilename(); curp->primary = &blk; curp->filenc = curp->options & (MPARSE_UTF8 | MPARSE_LATIN1); curp->line = 1; mandoc_msg_setinfilename(filename); /* Skip an UTF-8 byte order mark. */ if (curp->filenc & MPARSE_UTF8 && blk.sz > 2 && (unsigned char)blk.buf[0] == 0xef && (unsigned char)blk.buf[1] == 0xbb && (unsigned char)blk.buf[2] == 0xbf) { offset = 3; curp->filenc &= ~MPARSE_LATIN1; } else offset = 0; recursion_depth++; mparse_buf_r(curp, blk, offset, 1); if (--recursion_depth == 0) mparse_end(curp); /* * Clean up and restore saved parent properties. */ if (with_mmap) munmap(blk.buf, blk.sz); else free(blk.buf); curp->primary = save_primary; curp->filenc = save_filenc; curp->line = save_lineno; if (save_filename != NULL) mandoc_msg_setinfilename(save_filename); } int mparse_open(struct mparse *curp, const char *file) { char *cp; int fd, save_errno; cp = strrchr(file, '.'); curp->gzip = (cp != NULL && ! strcmp(cp + 1, "gz")); /* First try to use the filename as it is. */ if ((fd = open(file, O_RDONLY)) != -1) return fd; /* * If that doesn't work and the filename doesn't * already end in .gz, try appending .gz. */ if ( ! curp->gzip) { save_errno = errno; mandoc_asprintf(&cp, "%s.gz", file); fd = open(cp, O_RDONLY); free(cp); errno = save_errno; if (fd != -1) { curp->gzip = 1; return fd; } } /* Neither worked, give up. */ return -1; } struct mparse * mparse_alloc(int options, enum mandoc_os os_e, const char *os_s) { struct mparse *curp; curp = mandoc_calloc(1, sizeof(struct mparse)); curp->options = options; curp->os_s = os_s; curp->roff = roff_alloc(options); curp->man = roff_man_alloc(curp->roff, curp->os_s, curp->options & MPARSE_QUICK ? 1 : 0); if (curp->options & MPARSE_MDOC) { curp->man->meta.macroset = MACROSET_MDOC; if (curp->man->mdocmac == NULL) curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX); } else if (curp->options & MPARSE_MAN) { curp->man->meta.macroset = MACROSET_MAN; if (curp->man->manmac == NULL) curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX); } curp->man->meta.first->tok = TOKEN_NONE; curp->man->meta.os_e = os_e; tag_alloc(); return curp; } void mparse_reset(struct mparse *curp) { tag_free(); roff_reset(curp->roff); roff_man_reset(curp->man); free_buf_list(curp->secondary); curp->secondary = NULL; curp->gzip = 0; tag_alloc(); } void mparse_free(struct mparse *curp) { tag_free(); roffhash_free(curp->man->mdocmac); roffhash_free(curp->man->manmac); roff_man_free(curp->man); roff_free(curp->roff); free_buf_list(curp->secondary); free(curp); } struct roff_meta * mparse_result(struct mparse *curp) { roff_state_reset(curp->man); if (curp->options & MPARSE_VALIDATE) { if (curp->man->meta.macroset == MACROSET_MDOC) mdoc_validate(curp->man); else man_validate(curp->man); tag_postprocess(curp->man, curp->man->meta.first); } return &curp->man->meta; } void mparse_copy(const struct mparse *p) { struct buf *buf; for (buf = p->secondary; buf != NULL; buf = buf->next) puts(buf->buf); } /* $Id: roff.c,v 1.378 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze * Copyright (c) 2008-2012, 2014 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. * * Implementation of the roff(7) parser for mandoc(1). */ #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; /* 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_checkend(ROFF_ARGS); static int roff_cond_text(ROFF_ARGS); static int roff_cond_sub(ROFF_ARGS); static int roff_ds(ROFF_ARGS); static int roff_ec(ROFF_ARGS); static int roff_eo(ROFF_ARGS); static int roff_eqndelim(struct roff *, struct buf *, int); static int roff_evalcond(struct roff *, int, char *, int *); static int roff_evalnum(struct roff *, int, const char *, int *, int *, int); static int roff_evalpar(struct roff *, int, const char *, int *, int *, int); static int roff_evalstrcond(const char *, int *); static int roff_expand(struct roff *, struct buf *, int, int, char); static void roff_free1(struct roff *); static void roff_freereg(struct roffreg *); static void roff_freestr(struct roffkv *); static size_t roff_getname(struct roff *, char **, int, int); static int roff_getnum(const char *, int *, int *, int); static int roff_getop(const char *, int *, char *); static int roff_getregn(struct roff *, const char *, size_t, char); static int roff_getregro(const struct roff *, const char *name); static const char *roff_getstrn(struct roff *, const char *, size_t, int *); static int roff_hasregn(const struct roff *, const char *, size_t); static int roff_insec(ROFF_ARGS); static int roff_it(ROFF_ARGS); static int roff_line_ignore(ROFF_ARGS); static void roff_man_alloc1(struct roff_man *); static void roff_man_free1(struct roff_man *); static int roff_manyarg(ROFF_ARGS); static int roff_noarg(ROFF_ARGS); static int roff_nop(ROFF_ARGS); static int roff_nr(ROFF_ARGS); static int roff_onearg(ROFF_ARGS); static enum roff_tok roff_parse(struct roff *, char *, int *, int, int); static int roff_parsetext(struct roff *, struct buf *, int, int *); static int roff_renamed(ROFF_ARGS); static int roff_return(ROFF_ARGS); static int roff_rm(ROFF_ARGS); static int roff_rn(ROFF_ARGS); static int roff_rr(ROFF_ARGS); static void roff_setregn(struct roff *, const char *, size_t, int, char, int); static void roff_setstr(struct roff *, const char *, const char *, int); static void roff_setstrn(struct roffkv **, const char *, size_t, const char *, size_t, int); static int roff_shift(ROFF_ARGS); static int roff_so(ROFF_ARGS); static int roff_tr(ROFF_ARGS); static int roff_Dd(ROFF_ARGS); static int roff_TE(ROFF_ARGS); static int roff_TS(ROFF_ARGS); static int roff_EQ(ROFF_ARGS); static int roff_EN(ROFF_ARGS); static int roff_T_(ROFF_ARGS); static int roff_unsupp(ROFF_ARGS); static int roff_userdef(ROFF_ARGS); /* --- constant data ------------------------------------------------------ */ #define ROFFNUM_SCALE (1 << 0) /* Honour scaling in roff_getnum(). */ #define ROFFNUM_WHITE (1 << 1) /* Skip whitespace in roff_evalnum(). */ const char *__roff_name[MAN_MAX + 1] = { "br", "ce", "fi", "ft", "ll", "mc", "nf", "po", "rj", "sp", "ta", "ti", NULL, "ab", "ad", "af", "aln", "als", "am", "am1", "ami", "ami1", "as", "as1", "asciify", "backtrace", "bd", "bleedat", "blm", "box", "boxa", "bp", "BP", "break", "breakchar", "brnl", "brp", "brpnl", "c2", "cc", "cf", "cflags", "ch", "char", "chop", "class", "close", "CL", "color", "composite", "continue", "cp", "cropat", "cs", "cu", "da", "dch", "Dd", "de", "de1", "defcolor", "dei", "dei1", "device", "devicem", "di", "do", "ds", "ds1", "dwh", "dt", "ec", "ecr", "ecs", "el", "em", "EN", "eo", "EP", "EQ", "errprint", "ev", "evc", "ex", "fallback", "fam", "fc", "fchar", "fcolor", "fdeferlig", "feature", "fkern", "fl", "flig", "fp", "fps", "fschar", "fspacewidth", "fspecial", "ftr", "fzoom", "gcolor", "hc", "hcode", "hidechar", "hla", "hlm", "hpf", "hpfa", "hpfcode", "hw", "hy", "hylang", "hylen", "hym", "hypp", "hys", "ie", "if", "ig", "index", "it", "itc", "IX", "kern", "kernafter", "kernbefore", "kernpair", "lc", "lc_ctype", "lds", "length", "letadj", "lf", "lg", "lhang", "linetabs", "lnr", "lnrf", "lpfx", "ls", "lsm", "lt", "mediasize", "minss", "mk", "mso", "na", "ne", "nh", "nhychar", "nm", "nn", "nop", "nr", "nrf", "nroff", "ns", "nx", "open", "opena", "os", "output", "padj", "papersize", "pc", "pev", "pi", "PI", "pl", "pm", "pn", "pnr", "ps", "psbb", "pshape", "pso", "ptr", "pvs", "rchar", "rd", "recursionlimit", "return", "rfschar", "rhang", "rm", "rn", "rnn", "rr", "rs", "rt", "schar", "sentchar", "shc", "shift", "sizes", "so", "spacewidth", "special", "spreadwarn", "ss", "sty", "substring", "sv", "sy", "T&", "tc", "TE", "TH", "tkf", "tl", "tm", "tm1", "tmc", "tr", "track", "transchar", "trf", "trimat", "trin", "trnt", "troff", "TS", "uf", "ul", "unformat", "unwatch", "unwatchn", "vpt", "vs", "warn", "warnscale", "watch", "watchlength", "watchn", "wh", "while", "write", "writec", "writem", "xflag", ".", NULL, NULL, "text", "Dd", "Dt", "Os", "Sh", "Ss", "Pp", "D1", "Dl", "Bd", "Ed", "Bl", "El", "It", "Ad", "An", "Ap", "Ar", "Cd", "Cm", "Dv", "Er", "Ev", "Ex", "Fa", "Fd", "Fl", "Fn", "Ft", "Ic", "In", "Li", "Nd", "Nm", "Op", "Ot", "Pa", "Rv", "St", "Va", "Vt", "Xr", "%A", "%B", "%D", "%I", "%J", "%N", "%O", "%P", "%R", "%T", "%V", "Ac", "Ao", "Aq", "At", "Bc", "Bf", "Bo", "Bq", "Bsx", "Bx", "Db", "Dc", "Do", "Dq", "Ec", "Ef", "Em", "Eo", "Fx", "Ms", "No", "Ns", "Nx", "Ox", "Pc", "Pf", "Po", "Pq", "Qc", "Ql", "Qo", "Qq", "Re", "Rs", "Sc", "So", "Sq", "Sm", "Sx", "Sy", "Tn", "Ux", "Xc", "Xo", "Fo", "Fc", "Oo", "Oc", "Bk", "Ek", "Bt", "Hf", "Fr", "Ud", "Lb", "Lp", "Lk", "Mt", "Brq", "Bro", "Brc", "%C", "Es", "En", "Dx", "%Q", "%U", "Ta", "Tg", NULL, "TH", "SH", "SS", "TP", "TQ", "LP", "PP", "P", "IP", "HP", "SM", "SB", "BI", "IB", "BR", "RB", "R", "B", "I", "IR", "RI", "RE", "RS", "DT", "UC", "PD", "AT", "in", "SY", "YS", "OP", "EX", "EE", "UR", "UE", "MT", "ME", NULL }; const char *const *roff_name = __roff_name; static struct roffmac roffs[TOKEN_NONE] = { { roff_noarg, NULL, NULL, 0 }, /* br */ { roff_onearg, NULL, NULL, 0 }, /* ce */ { roff_noarg, NULL, NULL, 0 }, /* fi */ { roff_onearg, NULL, NULL, 0 }, /* ft */ { roff_onearg, NULL, NULL, 0 }, /* ll */ { roff_onearg, NULL, NULL, 0 }, /* mc */ { roff_noarg, NULL, NULL, 0 }, /* nf */ { roff_onearg, NULL, NULL, 0 }, /* po */ { roff_onearg, NULL, NULL, 0 }, /* rj */ { roff_onearg, NULL, NULL, 0 }, /* sp */ { roff_manyarg, NULL, NULL, 0 }, /* ta */ { roff_onearg, NULL, NULL, 0 }, /* ti */ { NULL, NULL, NULL, 0 }, /* ROFF_MAX */ { roff_unsupp, NULL, NULL, 0 }, /* ab */ { roff_line_ignore, NULL, NULL, 0 }, /* ad */ { roff_line_ignore, NULL, NULL, 0 }, /* af */ { roff_unsupp, NULL, NULL, 0 }, /* aln */ { roff_als, NULL, NULL, 0 }, /* als */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* am */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* am1 */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* ami */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* ami1 */ { roff_ds, NULL, NULL, 0 }, /* as */ { roff_ds, NULL, NULL, 0 }, /* as1 */ { roff_unsupp, NULL, NULL, 0 }, /* asciify */ { roff_line_ignore, NULL, NULL, 0 }, /* backtrace */ { roff_line_ignore, NULL, NULL, 0 }, /* bd */ { roff_line_ignore, NULL, NULL, 0 }, /* bleedat */ { roff_unsupp, NULL, NULL, 0 }, /* blm */ { roff_unsupp, NULL, NULL, 0 }, /* box */ { roff_unsupp, NULL, NULL, 0 }, /* boxa */ { roff_line_ignore, NULL, NULL, 0 }, /* bp */ { roff_unsupp, NULL, NULL, 0 }, /* BP */ { roff_break, NULL, NULL, 0 }, /* break */ { roff_line_ignore, NULL, NULL, 0 }, /* breakchar */ { roff_line_ignore, NULL, NULL, 0 }, /* brnl */ { roff_noarg, NULL, NULL, 0 }, /* brp */ { roff_line_ignore, NULL, NULL, 0 }, /* brpnl */ { roff_unsupp, NULL, NULL, 0 }, /* c2 */ { roff_cc, NULL, NULL, 0 }, /* cc */ { roff_insec, NULL, NULL, 0 }, /* cf */ { roff_line_ignore, NULL, NULL, 0 }, /* cflags */ { roff_line_ignore, NULL, NULL, 0 }, /* ch */ { roff_char, NULL, NULL, 0 }, /* char */ { roff_unsupp, NULL, NULL, 0 }, /* chop */ { roff_line_ignore, NULL, NULL, 0 }, /* class */ { roff_insec, NULL, NULL, 0 }, /* close */ { roff_unsupp, NULL, NULL, 0 }, /* CL */ { roff_line_ignore, NULL, NULL, 0 }, /* color */ { roff_unsupp, NULL, NULL, 0 }, /* composite */ { roff_unsupp, NULL, NULL, 0 }, /* continue */ { roff_line_ignore, NULL, NULL, 0 }, /* cp */ { roff_line_ignore, NULL, NULL, 0 }, /* cropat */ { roff_line_ignore, NULL, NULL, 0 }, /* cs */ { roff_line_ignore, NULL, NULL, 0 }, /* cu */ { roff_unsupp, NULL, NULL, 0 }, /* da */ { roff_unsupp, NULL, NULL, 0 }, /* dch */ { roff_Dd, NULL, NULL, 0 }, /* Dd */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* de */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* de1 */ { roff_line_ignore, NULL, NULL, 0 }, /* defcolor */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* dei */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* dei1 */ { roff_unsupp, NULL, NULL, 0 }, /* device */ { roff_unsupp, NULL, NULL, 0 }, /* devicem */ { roff_unsupp, NULL, NULL, 0 }, /* di */ { roff_unsupp, NULL, NULL, 0 }, /* do */ { roff_ds, NULL, NULL, 0 }, /* ds */ { roff_ds, NULL, NULL, 0 }, /* ds1 */ { roff_unsupp, NULL, NULL, 0 }, /* dwh */ { roff_unsupp, NULL, NULL, 0 }, /* dt */ { roff_ec, NULL, NULL, 0 }, /* ec */ { roff_unsupp, NULL, NULL, 0 }, /* ecr */ { roff_unsupp, NULL, NULL, 0 }, /* ecs */ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /* el */ { roff_unsupp, NULL, NULL, 0 }, /* em */ { roff_EN, NULL, NULL, 0 }, /* EN */ { roff_eo, NULL, NULL, 0 }, /* eo */ { roff_unsupp, NULL, NULL, 0 }, /* EP */ { roff_EQ, NULL, NULL, 0 }, /* EQ */ { roff_line_ignore, NULL, NULL, 0 }, /* errprint */ { roff_unsupp, NULL, NULL, 0 }, /* ev */ { roff_unsupp, NULL, NULL, 0 }, /* evc */ { roff_unsupp, NULL, NULL, 0 }, /* ex */ { roff_line_ignore, NULL, NULL, 0 }, /* fallback */ { roff_line_ignore, NULL, NULL, 0 }, /* fam */ { roff_unsupp, NULL, NULL, 0 }, /* fc */ { roff_unsupp, NULL, NULL, 0 }, /* fchar */ { roff_line_ignore, NULL, NULL, 0 }, /* fcolor */ { roff_line_ignore, NULL, NULL, 0 }, /* fdeferlig */ { roff_line_ignore, NULL, NULL, 0 }, /* feature */ { roff_line_ignore, NULL, NULL, 0 }, /* fkern */ { roff_line_ignore, NULL, NULL, 0 }, /* fl */ { roff_line_ignore, NULL, NULL, 0 }, /* flig */ { roff_line_ignore, NULL, NULL, 0 }, /* fp */ { roff_line_ignore, NULL, NULL, 0 }, /* fps */ { roff_unsupp, NULL, NULL, 0 }, /* fschar */ { roff_line_ignore, NULL, NULL, 0 }, /* fspacewidth */ { roff_line_ignore, NULL, NULL, 0 }, /* fspecial */ { roff_line_ignore, NULL, NULL, 0 }, /* ftr */ { roff_line_ignore, NULL, NULL, 0 }, /* fzoom */ { roff_line_ignore, NULL, NULL, 0 }, /* gcolor */ { roff_line_ignore, NULL, NULL, 0 }, /* hc */ { roff_line_ignore, NULL, NULL, 0 }, /* hcode */ { roff_line_ignore, NULL, NULL, 0 }, /* hidechar */ { roff_line_ignore, NULL, NULL, 0 }, /* hla */ { roff_line_ignore, NULL, NULL, 0 }, /* hlm */ { roff_line_ignore, NULL, NULL, 0 }, /* hpf */ { roff_line_ignore, NULL, NULL, 0 }, /* hpfa */ { roff_line_ignore, NULL, NULL, 0 }, /* hpfcode */ { roff_line_ignore, NULL, NULL, 0 }, /* hw */ { roff_line_ignore, NULL, NULL, 0 }, /* hy */ { roff_line_ignore, NULL, NULL, 0 }, /* hylang */ { roff_line_ignore, NULL, NULL, 0 }, /* hylen */ { roff_line_ignore, NULL, NULL, 0 }, /* hym */ { roff_line_ignore, NULL, NULL, 0 }, /* hypp */ { roff_line_ignore, NULL, NULL, 0 }, /* hys */ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /* ie */ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /* if */ { roff_block, roff_block_text, roff_block_sub, 0 }, /* ig */ { roff_unsupp, NULL, NULL, 0 }, /* index */ { roff_it, NULL, NULL, 0 }, /* it */ { roff_unsupp, NULL, NULL, 0 }, /* itc */ { roff_line_ignore, NULL, NULL, 0 }, /* IX */ { roff_line_ignore, NULL, NULL, 0 }, /* kern */ { roff_line_ignore, NULL, NULL, 0 }, /* kernafter */ { roff_line_ignore, NULL, NULL, 0 }, /* kernbefore */ { roff_line_ignore, NULL, NULL, 0 }, /* kernpair */ { roff_unsupp, NULL, NULL, 0 }, /* lc */ { roff_unsupp, NULL, NULL, 0 }, /* lc_ctype */ { roff_unsupp, NULL, NULL, 0 }, /* lds */ { roff_unsupp, NULL, NULL, 0 }, /* length */ { roff_line_ignore, NULL, NULL, 0 }, /* letadj */ { roff_insec, NULL, NULL, 0 }, /* lf */ { roff_line_ignore, NULL, NULL, 0 }, /* lg */ { roff_line_ignore, NULL, NULL, 0 }, /* lhang */ { roff_unsupp, NULL, NULL, 0 }, /* linetabs */ { roff_unsupp, NULL, NULL, 0 }, /* lnr */ { roff_unsupp, NULL, NULL, 0 }, /* lnrf */ { roff_unsupp, NULL, NULL, 0 }, /* lpfx */ { roff_line_ignore, NULL, NULL, 0 }, /* ls */ { roff_unsupp, NULL, NULL, 0 }, /* lsm */ { roff_line_ignore, NULL, NULL, 0 }, /* lt */ { roff_line_ignore, NULL, NULL, 0 }, /* mediasize */ { roff_line_ignore, NULL, NULL, 0 }, /* minss */ { roff_line_ignore, NULL, NULL, 0 }, /* mk */ { roff_insec, NULL, NULL, 0 }, /* mso */ { roff_line_ignore, NULL, NULL, 0 }, /* na */ { roff_line_ignore, NULL, NULL, 0 }, /* ne */ { roff_line_ignore, NULL, NULL, 0 }, /* nh */ { roff_line_ignore, NULL, NULL, 0 }, /* nhychar */ { roff_unsupp, NULL, NULL, 0 }, /* nm */ { roff_unsupp, NULL, NULL, 0 }, /* nn */ { roff_nop, NULL, NULL, 0 }, /* nop */ { roff_nr, NULL, NULL, 0 }, /* nr */ { roff_unsupp, NULL, NULL, 0 }, /* nrf */ { roff_line_ignore, NULL, NULL, 0 }, /* nroff */ { roff_line_ignore, NULL, NULL, 0 }, /* ns */ { roff_insec, NULL, NULL, 0 }, /* nx */ { roff_insec, NULL, NULL, 0 }, /* open */ { roff_insec, NULL, NULL, 0 }, /* opena */ { roff_line_ignore, NULL, NULL, 0 }, /* os */ { roff_unsupp, NULL, NULL, 0 }, /* output */ { roff_line_ignore, NULL, NULL, 0 }, /* padj */ { roff_line_ignore, NULL, NULL, 0 }, /* papersize */ { roff_line_ignore, NULL, NULL, 0 }, /* pc */ { roff_line_ignore, NULL, NULL, 0 }, /* pev */ { roff_insec, NULL, NULL, 0 }, /* pi */ { roff_unsupp, NULL, NULL, 0 }, /* PI */ { roff_line_ignore, NULL, NULL, 0 }, /* pl */ { roff_line_ignore, NULL, NULL, 0 }, /* pm */ { roff_line_ignore, NULL, NULL, 0 }, /* pn */ { roff_line_ignore, NULL, NULL, 0 }, /* pnr */ { roff_line_ignore, NULL, NULL, 0 }, /* ps */ { roff_unsupp, NULL, NULL, 0 }, /* psbb */ { roff_unsupp, NULL, NULL, 0 }, /* pshape */ { roff_insec, NULL, NULL, 0 }, /* pso */ { roff_line_ignore, NULL, NULL, 0 }, /* ptr */ { roff_line_ignore, NULL, NULL, 0 }, /* pvs */ { roff_unsupp, NULL, NULL, 0 }, /* rchar */ { roff_line_ignore, NULL, NULL, 0 }, /* rd */ { roff_line_ignore, NULL, NULL, 0 }, /* recursionlimit */ { roff_return, NULL, NULL, 0 }, /* return */ { roff_unsupp, NULL, NULL, 0 }, /* rfschar */ { roff_line_ignore, NULL, NULL, 0 }, /* rhang */ { roff_rm, NULL, NULL, 0 }, /* rm */ { roff_rn, NULL, NULL, 0 }, /* rn */ { roff_unsupp, NULL, NULL, 0 }, /* rnn */ { roff_rr, NULL, NULL, 0 }, /* rr */ { roff_line_ignore, NULL, NULL, 0 }, /* rs */ { roff_line_ignore, NULL, NULL, 0 }, /* rt */ { roff_unsupp, NULL, NULL, 0 }, /* schar */ { roff_line_ignore, NULL, NULL, 0 }, /* sentchar */ { roff_line_ignore, NULL, NULL, 0 }, /* shc */ { roff_shift, NULL, NULL, 0 }, /* shift */ { roff_line_ignore, NULL, NULL, 0 }, /* sizes */ { roff_so, NULL, NULL, 0 }, /* so */ { roff_line_ignore, NULL, NULL, 0 }, /* spacewidth */ { roff_line_ignore, NULL, NULL, 0 }, /* special */ { roff_line_ignore, NULL, NULL, 0 }, /* spreadwarn */ { roff_line_ignore, NULL, NULL, 0 }, /* ss */ { roff_line_ignore, NULL, NULL, 0 }, /* sty */ { roff_unsupp, NULL, NULL, 0 }, /* substring */ { roff_line_ignore, NULL, NULL, 0 }, /* sv */ { roff_insec, NULL, NULL, 0 }, /* sy */ { roff_T_, NULL, NULL, 0 }, /* T& */ { roff_unsupp, NULL, NULL, 0 }, /* tc */ { roff_TE, NULL, NULL, 0 }, /* TE */ { roff_Dd, NULL, NULL, 0 }, /* TH */ { roff_line_ignore, NULL, NULL, 0 }, /* tkf */ { roff_unsupp, NULL, NULL, 0 }, /* tl */ { roff_line_ignore, NULL, NULL, 0 }, /* tm */ { roff_line_ignore, NULL, NULL, 0 }, /* tm1 */ { roff_line_ignore, NULL, NULL, 0 }, /* tmc */ { roff_tr, NULL, NULL, 0 }, /* tr */ { roff_line_ignore, NULL, NULL, 0 }, /* track */ { roff_line_ignore, NULL, NULL, 0 }, /* transchar */ { roff_insec, NULL, NULL, 0 }, /* trf */ { roff_line_ignore, NULL, NULL, 0 }, /* trimat */ { roff_unsupp, NULL, NULL, 0 }, /* trin */ { roff_unsupp, NULL, NULL, 0 }, /* trnt */ { roff_line_ignore, NULL, NULL, 0 }, /* troff */ { roff_TS, NULL, NULL, 0 }, /* TS */ { roff_line_ignore, NULL, NULL, 0 }, /* uf */ { roff_line_ignore, NULL, NULL, 0 }, /* ul */ { roff_unsupp, NULL, NULL, 0 }, /* unformat */ { roff_line_ignore, NULL, NULL, 0 }, /* unwatch */ { roff_line_ignore, NULL, NULL, 0 }, /* unwatchn */ { roff_line_ignore, NULL, NULL, 0 }, /* vpt */ { roff_line_ignore, NULL, NULL, 0 }, /* vs */ { roff_line_ignore, NULL, NULL, 0 }, /* warn */ { roff_line_ignore, NULL, NULL, 0 }, /* warnscale */ { roff_line_ignore, NULL, NULL, 0 }, /* watch */ { roff_line_ignore, NULL, NULL, 0 }, /* watchlength */ { roff_line_ignore, NULL, NULL, 0 }, /* watchn */ { roff_unsupp, NULL, NULL, 0 }, /* wh */ { roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /*while*/ { roff_insec, NULL, NULL, 0 }, /* write */ { roff_insec, NULL, NULL, 0 }, /* writec */ { roff_insec, NULL, NULL, 0 }, /* writem */ { roff_line_ignore, NULL, NULL, 0 }, /* xflag */ { roff_cblock, NULL, NULL, 0 }, /* . */ { roff_renamed, NULL, NULL, 0 }, { roff_userdef, NULL, NULL, 0 } }; /* Array of injected predefined strings. */ #define PREDEFS_MAX 38 static const struct predef predefs[PREDEFS_MAX] = { #include "predefs.in" }; static int roffce_lines; /* number of input lines to center */ static struct roff_node *roffce_node; /* active request */ static int roffit_lines; /* number of lines to delay */ static char *roffit_macro; /* nil-terminated macro line */ /* --- request table ------------------------------------------------------ */ struct ohash * roffhash_alloc(enum roff_tok mintok, enum roff_tok maxtok) { struct ohash *htab; struct roffreq *req; enum roff_tok tok; size_t sz; unsigned int slot; htab = mandoc_malloc(sizeof(*htab)); mandoc_ohash_init(htab, 8, offsetof(struct roffreq, name)); for (tok = mintok; tok < maxtok; tok++) { if (roff_name[tok] == NULL) continue; sz = strlen(roff_name[tok]); req = mandoc_malloc(sizeof(*req) + sz + 1); req->tok = tok; memcpy(req->name, roff_name[tok], sz + 1); slot = ohash_qlookup(htab, req->name); ohash_insert(htab, slot, req); } return htab; } void roffhash_free(struct ohash *htab) { struct roffreq *req; unsigned int slot; if (htab == NULL) return; for (req = ohash_first(htab, &slot); req != NULL; req = ohash_next(htab, &slot)) free(req); ohash_delete(htab); free(htab); } enum roff_tok roffhash_find(struct ohash *htab, const char *name, size_t sz) { struct roffreq *req; const char *end; if (sz) { end = name + sz; req = ohash_find(htab, ohash_qlookupi(htab, name, &end)); } else req = ohash_find(htab, ohash_qlookup(htab, name)); return req == NULL ? TOKEN_NONE : req->tok; } /* --- stack of request blocks -------------------------------------------- */ /* * Pop the current node off of the stack of roff instructions currently * pending. Return 1 if it is a loop or 0 otherwise. */ static int roffnode_pop(struct roff *r) { struct roffnode *p; int inloop; p = r->last; inloop = p->tok == ROFF_while; r->last = p->parent; free(p->name); free(p->end); free(p); return inloop; } /* * Push a roff node onto the instruction stack. This must later be * removed with roffnode_pop(). */ static void roffnode_push(struct roff *r, enum roff_tok tok, const char *name, int line, int col) { struct roffnode *p; p = mandoc_calloc(1, sizeof(struct roffnode)); p->tok = tok; if (name) p->name = mandoc_strdup(name); p->parent = r->last; p->line = line; p->col = col; p->rule = p->parent ? p->parent->rule : 0; r->last = p; } /* --- roff parser state data management ---------------------------------- */ static void roff_free1(struct roff *r) { int i; tbl_free(r->first_tbl); r->first_tbl = r->last_tbl = r->tbl = NULL; eqn_free(r->last_eqn); r->last_eqn = r->eqn = NULL; while (r->mstackpos >= 0) roff_userret(r); while (r->last) roffnode_pop(r); free (r->rstack); r->rstack = NULL; r->rstacksz = 0; r->rstackpos = -1; roff_freereg(r->regtab); r->regtab = NULL; roff_freestr(r->strtab); roff_freestr(r->rentab); roff_freestr(r->xmbtab); r->strtab = r->rentab = r->xmbtab = NULL; if (r->xtab) for (i = 0; i < 128; i++) free(r->xtab[i].p); free(r->xtab); r->xtab = NULL; } void roff_reset(struct roff *r) { roff_free1(r); r->options |= MPARSE_COMMENT; r->format = r->options & (MPARSE_MDOC | MPARSE_MAN); r->control = '\0'; r->escape = '\\'; roffce_lines = 0; roffce_node = NULL; roffit_lines = 0; roffit_macro = NULL; } void roff_free(struct roff *r) { int i; roff_free1(r); for (i = 0; i < r->mstacksz; i++) free(r->mstack[i].argv); free(r->mstack); roffhash_free(r->reqtab); free(r); } struct roff * roff_alloc(int options) { struct roff *r; r = mandoc_calloc(1, sizeof(struct roff)); r->reqtab = roffhash_alloc(0, ROFF_RENAMED); r->options = options | MPARSE_COMMENT; r->format = options & (MPARSE_MDOC | MPARSE_MAN); r->mstackpos = -1; r->rstackpos = -1; r->escape = '\\'; return r; } /* --- syntax tree state data management ---------------------------------- */ static void roff_man_free1(struct roff_man *man) { if (man->meta.first != NULL) roff_node_delete(man, man->meta.first); free(man->meta.msec); free(man->meta.vol); free(man->meta.os); free(man->meta.arch); free(man->meta.title); free(man->meta.name); free(man->meta.date); free(man->meta.sodest); } void roff_state_reset(struct roff_man *man) { man->last = man->meta.first; man->last_es = NULL; man->flags = 0; man->lastsec = man->lastnamed = SEC_NONE; man->next = ROFF_NEXT_CHILD; roff_setreg(man->roff, "nS", 0, '='); } static void roff_man_alloc1(struct roff_man *man) { memset(&man->meta, 0, sizeof(man->meta)); man->meta.first = mandoc_calloc(1, sizeof(*man->meta.first)); man->meta.first->type = ROFFT_ROOT; man->meta.macroset = MACROSET_NONE; roff_state_reset(man); } void roff_man_reset(struct roff_man *man) { roff_man_free1(man); roff_man_alloc1(man); } void roff_man_free(struct roff_man *man) { roff_man_free1(man); free(man); } struct roff_man * roff_man_alloc(struct roff *roff, const char *os_s, int quick) { struct roff_man *man; man = mandoc_calloc(1, sizeof(*man)); man->roff = roff; man->os_s = os_s; man->quick = quick; roff_man_alloc1(man); roff->man = man; return man; } /* --- syntax tree handling ----------------------------------------------- */ struct roff_node * roff_node_alloc(struct roff_man *man, int line, int pos, enum roff_type type, int tok) { struct roff_node *n; n = mandoc_calloc(1, sizeof(*n)); n->line = line; n->pos = pos; n->tok = tok; n->type = type; n->sec = man->lastsec; if (man->flags & MDOC_SYNOPSIS) n->flags |= NODE_SYNPRETTY; else n->flags &= ~NODE_SYNPRETTY; if ((man->flags & (ROFF_NOFILL | ROFF_NONOFILL)) == ROFF_NOFILL) n->flags |= NODE_NOFILL; else n->flags &= ~NODE_NOFILL; if (man->flags & MDOC_NEWLINE) n->flags |= NODE_LINE; man->flags &= ~MDOC_NEWLINE; return n; } void roff_node_append(struct roff_man *man, struct roff_node *n) { switch (man->next) { case ROFF_NEXT_SIBLING: if (man->last->next != NULL) { n->next = man->last->next; man->last->next->prev = n; } else man->last->parent->last = n; man->last->next = n; n->prev = man->last; n->parent = man->last->parent; break; case ROFF_NEXT_CHILD: if (man->last->child != NULL) { n->next = man->last->child; man->last->child->prev = n; } else man->last->last = n; man->last->child = n; n->parent = man->last; break; default: abort(); } man->last = n; switch (n->type) { case ROFFT_HEAD: n->parent->head = n; break; case ROFFT_BODY: if (n->end != ENDBODY_NOT) return; n->parent->body = n; break; case ROFFT_TAIL: n->parent->tail = n; break; default: return; } /* * Copy over the normalised-data pointer of our parent. Not * everybody has one, but copying a null pointer is fine. */ n->norm = n->parent->norm; assert(n->parent->type == ROFFT_BLOCK); } void roff_word_alloc(struct roff_man *man, int line, int pos, const char *word) { struct roff_node *n; n = roff_node_alloc(man, line, pos, ROFFT_TEXT, TOKEN_NONE); n->string = roff_strdup(man->roff, word); roff_node_append(man, n); n->flags |= NODE_VALID | NODE_ENDED; man->next = ROFF_NEXT_SIBLING; } void roff_word_append(struct roff_man *man, const char *word) { struct roff_node *n; char *addstr, *newstr; n = man->last; addstr = roff_strdup(man->roff, word); mandoc_asprintf(&newstr, "%s %s", n->string, addstr); free(addstr); free(n->string); n->string = newstr; man->next = ROFF_NEXT_SIBLING; } void roff_elem_alloc(struct roff_man *man, int line, int pos, int tok) { struct roff_node *n; n = roff_node_alloc(man, line, pos, ROFFT_ELEM, tok); roff_node_append(man, n); man->next = ROFF_NEXT_CHILD; } struct roff_node * roff_block_alloc(struct roff_man *man, int line, int pos, int tok) { struct roff_node *n; n = roff_node_alloc(man, line, pos, ROFFT_BLOCK, tok); roff_node_append(man, n); man->next = ROFF_NEXT_CHILD; return n; } struct roff_node * roff_head_alloc(struct roff_man *man, int line, int pos, int tok) { struct roff_node *n; n = roff_node_alloc(man, line, pos, ROFFT_HEAD, tok); roff_node_append(man, n); man->next = ROFF_NEXT_CHILD; return n; } struct roff_node * roff_body_alloc(struct roff_man *man, int line, int pos, int tok) { struct roff_node *n; n = roff_node_alloc(man, line, pos, ROFFT_BODY, tok); roff_node_append(man, n); man->next = ROFF_NEXT_CHILD; return n; } static void roff_addtbl(struct roff_man *man, int line, struct tbl_node *tbl) { struct roff_node *n; struct tbl_span *span; if (man->meta.macroset == MACROSET_MAN) man_breakscope(man, ROFF_TS); while ((span = tbl_span(tbl)) != NULL) { n = roff_node_alloc(man, line, 0, ROFFT_TBL, TOKEN_NONE); n->span = span; roff_node_append(man, n); n->flags |= NODE_VALID | NODE_ENDED; man->next = ROFF_NEXT_SIBLING; } } void roff_node_unlink(struct roff_man *man, struct roff_node *n) { /* Adjust siblings. */ if (n->prev) n->prev->next = n->next; if (n->next) n->next->prev = n->prev; /* Adjust parent. */ if (n->parent != NULL) { if (n->parent->child == n) n->parent->child = n->next; if (n->parent->last == n) n->parent->last = n->prev; } /* Adjust parse point. */ if (man == NULL) return; if (man->last == n) { if (n->prev == NULL) { man->last = n->parent; man->next = ROFF_NEXT_CHILD; } else { man->last = n->prev; man->next = ROFF_NEXT_SIBLING; } } if (man->meta.first == n) man->meta.first = NULL; } void roff_node_relink(struct roff_man *man, struct roff_node *n) { roff_node_unlink(man, n); n->prev = n->next = NULL; roff_node_append(man, n); } void roff_node_free(struct roff_node *n) { if (n->args != NULL) mdoc_argv_free(n->args); if (n->type == ROFFT_BLOCK || n->type == ROFFT_ELEM) free(n->norm); eqn_box_free(n->eqn); free(n->string); free(n->tag); free(n); } void roff_node_delete(struct roff_man *man, struct roff_node *n) { while (n->child != NULL) roff_node_delete(man, n->child); roff_node_unlink(man, n); roff_node_free(n); } int roff_node_transparent(struct roff_node *n) { if (n == NULL) return 0; if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) return 1; return roff_tok_transparent(n->tok); } int roff_tok_transparent(enum roff_tok tok) { switch (tok) { case ROFF_ft: case ROFF_ll: case ROFF_mc: case ROFF_po: case ROFF_ta: case MDOC_Db: case MDOC_Es: case MDOC_Sm: case MDOC_Tg: case MAN_DT: case MAN_UC: case MAN_PD: case MAN_AT: return 1; default: return 0; } } struct roff_node * roff_node_child(struct roff_node *n) { for (n = n->child; roff_node_transparent(n); n = n->next) continue; return n; } struct roff_node * roff_node_prev(struct roff_node *n) { do { n = n->prev; } while (roff_node_transparent(n)); return n; } struct roff_node * roff_node_next(struct roff_node *n) { do { n = n->next; } while (roff_node_transparent(n)); return n; } void deroff(char **dest, const struct roff_node *n) { char *cp; size_t sz; if (n->string == NULL) { for (n = n->child; n != NULL; n = n->next) deroff(dest, n); return; } /* Skip leading whitespace. */ for (cp = n->string; *cp != '\0'; cp++) { if (cp[0] == '\\' && cp[1] != '\0' && strchr(" %&0^|~", cp[1]) != NULL) cp++; else if ( ! isspace((unsigned char)*cp)) break; } /* Skip trailing backslash. */ sz = strlen(cp); if (sz > 0 && cp[sz - 1] == '\\') sz--; /* Skip trailing whitespace. */ for (; sz; sz--) if ( ! isspace((unsigned char)cp[sz-1])) break; /* Skip empty strings. */ if (sz == 0) return; if (*dest == NULL) { *dest = mandoc_strndup(cp, sz); return; } mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp); free(*dest); *dest = cp; } /* --- main functions of the roff parser ---------------------------------- */ /* * In the current line, expand escape sequences that produce parsable * input text. Also check the syntax of the remaining escape sequences, * which typically produce output glyphs or change formatter state. */ static int roff_expand(struct roff *r, struct buf *buf, int ln, int pos, char newesc) { struct mctx *ctx; /* current macro call context */ char ubuf[24]; /* buffer to print the number */ struct roff_node *n; /* used for header comments */ const char *start; /* start of the string to process */ char *stesc; /* start of an escape sequence ('\\') */ const char *esct; /* type of esccape sequence */ char *ep; /* end of comment string */ const char *stnam; /* start of the name, after "[(*" */ const char *cp; /* end of the name, e.g. before ']' */ const char *res; /* the string to be substituted */ char *nbuf; /* new buffer to copy buf->buf to */ size_t maxl; /* expected length of the escape name */ size_t naml; /* actual length of the escape name */ size_t asz; /* length of the replacement */ size_t rsz; /* length of the rest of the string */ int inaml; /* length returned from mandoc_escape() */ int expand_count; /* to avoid infinite loops */ int npos; /* position in numeric expression */ int arg_complete; /* argument not interrupted by eol */ int quote_args; /* true for \\$@, false for \\$* */ int done; /* no more input available */ int deftype; /* type of definition to paste */ int rcsid; /* kind of RCS id seen */ enum mandocerr err; /* for escape sequence problems */ char sign; /* increment number register */ char term; /* character terminating the escape */ /* Search forward for comments. */ done = 0; start = buf->buf + pos; for (stesc = buf->buf + pos; *stesc != '\0'; stesc++) { if (stesc[0] != newesc || stesc[1] == '\0') continue; stesc++; if (*stesc != '"' && *stesc != '#') continue; /* Comment found, look for RCS id. */ rcsid = 0; if ((cp = strstr(stesc, "$" "OpenBSD")) != NULL) { rcsid = 1 << MANDOC_OS_OPENBSD; cp += 8; } else if ((cp = strstr(stesc, "$" "NetBSD")) != NULL) { rcsid = 1 << MANDOC_OS_NETBSD; cp += 7; } if (cp != NULL && isalnum((unsigned char)*cp) == 0 && strchr(cp, '$') != NULL) { if (r->man->meta.rcsids & rcsid) mandoc_msg(MANDOCERR_RCS_REP, ln, (int)(stesc - buf->buf) + 1, "%s", stesc + 1); r->man->meta.rcsids |= rcsid; } /* Handle trailing whitespace. */ ep = strchr(stesc--, '\0') - 1; if (*ep == '\n') { done = 1; ep--; } if (*ep == ' ' || *ep == '\t') mandoc_msg(MANDOCERR_SPACE_EOL, ln, (int)(ep - buf->buf), NULL); /* * Save comments preceding the title macro * in the syntax tree. */ if (newesc != ASCII_ESC && r->options & MPARSE_COMMENT) { while (*ep == ' ' || *ep == '\t') ep--; ep[1] = '\0'; n = roff_node_alloc(r->man, ln, stesc + 1 - buf->buf, ROFFT_COMMENT, TOKEN_NONE); n->string = mandoc_strdup(stesc + 2); roff_node_append(r->man, n); n->flags |= NODE_VALID | NODE_ENDED; r->man->next = ROFF_NEXT_SIBLING; } /* Line continuation with comment. */ if (stesc[1] == '#') { *stesc = '\0'; return ROFF_IGN | ROFF_APPEND; } /* Discard normal comments. */ while (stesc > start && stesc[-1] == ' ' && (stesc == start + 1 || stesc[-2] != '\\')) stesc--; *stesc = '\0'; break; } if (stesc == start) return ROFF_CONT; stesc--; /* Notice the end of the input. */ if (*stesc == '\n') { *stesc-- = '\0'; done = 1; } expand_count = 0; while (stesc >= start) { if (*stesc != newesc) { /* * If we have a non-standard escape character, * escape literal backslashes because all * processing in subsequent functions uses * the standard escaping rules. */ if (newesc != ASCII_ESC && *stesc == '\\') { *stesc = '\0'; buf->sz = mandoc_asprintf(&nbuf, "%s\\e%s", buf->buf, stesc + 1) + 1; start = nbuf + pos; stesc = nbuf + (stesc - buf->buf); free(buf->buf); buf->buf = nbuf; } /* Search backwards for the next escape. */ stesc--; continue; } /* If it is escaped, skip it. */ for (cp = stesc - 1; cp >= start; cp--) if (*cp != r->escape) break; if ((stesc - cp) % 2 == 0) { while (stesc > cp) *stesc-- = '\\'; continue; } else if (stesc[1] != '\0') { *stesc = '\\'; } else { *stesc-- = '\0'; if (done) continue; else return ROFF_IGN | ROFF_APPEND; } /* Decide whether to expand or to check only. */ term = '\0'; cp = stesc + 1; if (*cp == 'E') cp++; esct = cp; switch (*esct) { case '*': case '$': res = NULL; break; case 'B': case 'w': term = cp[1]; /* FALLTHROUGH */ case 'n': sign = cp[1]; if (sign == '+' || sign == '-') cp++; res = ubuf; break; default: err = MANDOCERR_OK; switch(mandoc_escape(&cp, &stnam, &inaml)) { case ESCAPE_SPECIAL: if (mchars_spec2cp(stnam, inaml) >= 0) break; /* FALLTHROUGH */ case ESCAPE_ERROR: err = MANDOCERR_ESC_BAD; break; case ESCAPE_UNDEF: err = MANDOCERR_ESC_UNDEF; break; case ESCAPE_UNSUPP: err = MANDOCERR_ESC_UNSUPP; break; default: break; } if (err != MANDOCERR_OK) mandoc_msg(err, ln, (int)(stesc - buf->buf), "%.*s", (int)(cp - stesc), stesc); stesc--; continue; } if (EXPAND_LIMIT < ++expand_count) { mandoc_msg(MANDOCERR_ROFFLOOP, ln, (int)(stesc - buf->buf), NULL); return ROFF_IGN; } /* * The third character decides the length * of the name of the string or register. * Save a pointer to the name. */ if (term == '\0') { switch (*++cp) { case '\0': maxl = 0; break; case '(': cp++; maxl = 2; break; case '[': cp++; term = ']'; maxl = 0; break; default: maxl = 1; break; } } else { cp += 2; maxl = 0; } stnam = cp; /* Advance to the end of the name. */ naml = 0; arg_complete = 1; while (maxl == 0 || naml < maxl) { if (*cp == '\0') { mandoc_msg(MANDOCERR_ESC_BAD, ln, (int)(stesc - buf->buf), "%s", stesc); arg_complete = 0; break; } if (maxl == 0 && *cp == term) { cp++; break; } if (*cp++ != '\\' || *esct != 'w') { naml++; continue; } switch (mandoc_escape(&cp, NULL, NULL)) { case ESCAPE_SPECIAL: case ESCAPE_UNICODE: case ESCAPE_NUMBERED: case ESCAPE_UNDEF: case ESCAPE_OVERSTRIKE: naml++; break; default: break; } } /* * Retrieve the replacement string; if it is * undefined, resume searching for escapes. */ switch (*esct) { case '*': if (arg_complete) { deftype = ROFFDEF_USER | ROFFDEF_PRE; res = roff_getstrn(r, stnam, naml, &deftype); /* * If not overriden, let \*(.T * through to the formatters. */ if (res == NULL && naml == 2 && stnam[0] == '.' && stnam[1] == 'T') { roff_setstrn(&r->strtab, ".T", 2, NULL, 0, 0); stesc--; continue; } } break; case '$': if (r->mstackpos < 0) { mandoc_msg(MANDOCERR_ARG_UNDEF, ln, (int)(stesc - buf->buf), "%.3s", stesc); break; } ctx = r->mstack + r->mstackpos; npos = esct[1] - '1'; if (npos >= 0 && npos <= 8) { res = npos < ctx->argc ? ctx->argv[npos] : ""; break; } if (esct[1] == '*') quote_args = 0; else if (esct[1] == '@') quote_args = 1; else { mandoc_msg(MANDOCERR_ARG_NONUM, ln, (int)(stesc - buf->buf), "%.3s", stesc); break; } asz = 0; for (npos = 0; npos < ctx->argc; npos++) { if (npos) asz++; /* blank */ if (quote_args) asz += 2; /* quotes */ asz += strlen(ctx->argv[npos]); } if (asz != 3) { rsz = buf->sz - (stesc - buf->buf) - 3; if (asz < 3) memmove(stesc + asz, stesc + 3, rsz); buf->sz += asz - 3; nbuf = mandoc_realloc(buf->buf, buf->sz); start = nbuf + pos; stesc = nbuf + (stesc - buf->buf); buf->buf = nbuf; if (asz > 3) memmove(stesc + asz, stesc + 3, rsz); } for (npos = 0; npos < ctx->argc; npos++) { if (npos) *stesc++ = ' '; if (quote_args) *stesc++ = '"'; cp = ctx->argv[npos]; while (*cp != '\0') *stesc++ = *cp++; if (quote_args) *stesc++ = '"'; } continue; case 'B': npos = 0; ubuf[0] = arg_complete && roff_evalnum(r, ln, stnam, &npos, NULL, ROFFNUM_SCALE) && stnam + npos + 1 == cp ? '1' : '0'; ubuf[1] = '\0'; break; case 'n': if (arg_complete) (void)snprintf(ubuf, sizeof(ubuf), "%d", roff_getregn(r, stnam, naml, sign)); else ubuf[0] = '\0'; break; case 'w': /* use even incomplete args */ (void)snprintf(ubuf, sizeof(ubuf), "%d", 24 * (int)naml); break; } if (res == NULL) { if (*esct == '*') mandoc_msg(MANDOCERR_STR_UNDEF, ln, (int)(stesc - buf->buf), "%.*s", (int)naml, stnam); res = ""; } else if (buf->sz + strlen(res) > SHRT_MAX) { mandoc_msg(MANDOCERR_ROFFLOOP, ln, (int)(stesc - buf->buf), NULL); return ROFF_IGN; } /* Replace the escape sequence by the string. */ *stesc = '\0'; buf->sz = mandoc_asprintf(&nbuf, "%s%s%s", buf->buf, res, cp) + 1; /* Prepare for the next replacement. */ start = nbuf + pos; stesc = nbuf + (stesc - buf->buf) + strlen(res); free(buf->buf); buf->buf = nbuf; } return ROFF_CONT; } /* * Parse a quoted or unquoted roff-style request or macro argument. * Return a pointer to the parsed argument, which is either the original * pointer or advanced by one byte in case the argument is quoted. * NUL-terminate the argument in place. * Collapse pairs of quotes inside quoted arguments. * Advance the argument pointer to the next argument, * or to the NUL byte terminating the argument line. */ char * roff_getarg(struct roff *r, char **cpp, int ln, int *pos) { struct buf buf; char *cp, *start; int newesc, pairs, quoted, white; /* Quoting can only start with a new word. */ start = *cpp; quoted = 0; if ('"' == *start) { quoted = 1; start++; } newesc = pairs = white = 0; for (cp = start; '\0' != *cp; cp++) { /* * Move the following text left * after quoted quotes and after "\\" and "\t". */ if (pairs) cp[-pairs] = cp[0]; if ('\\' == cp[0]) { /* * In copy mode, translate double to single * backslashes and backslash-t to literal tabs. */ switch (cp[1]) { case 'a': case 't': cp[-pairs] = '\t'; pairs++; cp++; break; case '\\': newesc = 1; cp[-pairs] = ASCII_ESC; pairs++; cp++; break; case ' ': /* Skip escaped blanks. */ if (0 == quoted) cp++; break; default: break; } } else if (0 == quoted) { if (' ' == cp[0]) { /* Unescaped blanks end unquoted args. */ white = 1; break; } } else if ('"' == cp[0]) { if ('"' == cp[1]) { /* Quoted quotes collapse. */ pairs++; cp++; } else { /* Unquoted quotes end quoted args. */ quoted = 2; break; } } } /* Quoted argument without a closing quote. */ if (1 == quoted) mandoc_msg(MANDOCERR_ARG_QUOTE, ln, *pos, NULL); /* NUL-terminate this argument and move to the next one. */ if (pairs) cp[-pairs] = '\0'; if ('\0' != *cp) { *cp++ = '\0'; while (' ' == *cp) cp++; } *pos += (int)(cp - start) + (quoted ? 1 : 0); *cpp = cp; if ('\0' == *cp && (white || ' ' == cp[-1])) mandoc_msg(MANDOCERR_SPACE_EOL, ln, *pos, NULL); start = mandoc_strdup(start); if (newesc == 0) return start; buf.buf = start; buf.sz = strlen(start) + 1; buf.next = NULL; if (roff_expand(r, &buf, ln, 0, ASCII_ESC) & ROFF_IGN) { free(buf.buf); buf.buf = mandoc_strdup(""); } return buf.buf; } /* * Process text streams. */ static int roff_parsetext(struct roff *r, struct buf *buf, int pos, int *offs) { size_t sz; const char *start; char *p; int isz; enum mandoc_esc esc; /* Spring the input line trap. */ if (roffit_lines == 1) { isz = mandoc_asprintf(&p, "%s\n.%s", buf->buf, roffit_macro); free(buf->buf); buf->buf = p; buf->sz = isz + 1; *offs = 0; free(roffit_macro); roffit_lines = 0; return ROFF_REPARSE; } else if (roffit_lines > 1) --roffit_lines; if (roffce_node != NULL && buf->buf[pos] != '\0') { if (roffce_lines < 1) { r->man->last = roffce_node; r->man->next = ROFF_NEXT_SIBLING; roffce_lines = 0; roffce_node = NULL; } else roffce_lines--; } /* Convert all breakable hyphens into ASCII_HYPH. */ start = p = buf->buf + pos; while (*p != '\0') { sz = strcspn(p, "-\\"); p += sz; if (*p == '\0') break; if (*p == '\\') { /* Skip over escapes. */ p++; esc = mandoc_escape((const char **)&p, NULL, NULL); if (esc == ESCAPE_ERROR) break; while (*p == '-') p++; continue; } else if (p == start) { p++; continue; } if (isalpha((unsigned char)p[-1]) && isalpha((unsigned char)p[1])) *p = ASCII_HYPH; p++; } return ROFF_CONT; } int roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs, size_t len) { 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; if (len > 80 && r->tbl == NULL && r->eqn == NULL && (r->man->flags & ROFF_NOFILL) == 0 && strchr(" .\\", buf->buf[pos]) == NULL && buf->buf[pos] != r->control && strcspn(buf->buf, " ") < 80) mandoc_msg(MANDOCERR_TEXT_LONG, ln, (int)len - 1, "%.20s...", buf->buf + pos); /* Handle in-line equation delimiters. */ if (r->tbl == NULL && r->last_eqn != NULL && r->last_eqn->delim && (r->eqn == NULL || r->eqn_inline)) { e = roff_eqndelim(r, buf, pos); if (e == ROFF_REPARSE) return e; assert(e == ROFF_CONT); } /* Expand some escape sequences. */ e = roff_expand(r, buf, ln, pos, r->escape); if ((e & ROFF_MASK) == ROFF_IGN) return e; assert(e == ROFF_CONT); ctl = roff_getcontrol(r, buf->buf, &pos); /* * First, if a scope is open and we're not a macro, pass the * text through the macro's filter. * Equations process all content themselves. * Tables process almost all content themselves, but we want * to warn about macros before passing it there. */ if (r->last != NULL && ! ctl) { t = r->last->tok; e = (*roffs[t].text)(r, t, buf, ln, pos, pos, offs); if ((e & ROFF_MASK) == ROFF_IGN) return e; e &= ~ROFF_MASK; } else e = ROFF_IGN; if (r->eqn != NULL && strncmp(buf->buf + ppos, ".EN", 3)) { eqn_read(r->eqn, buf->buf + ppos); return e; } if (r->tbl != NULL && (ctl == 0 || buf->buf[pos] == '\0')) { tbl_read(r->tbl, ln, buf->buf, ppos); roff_addtbl(r->man, ln, r->tbl); return e; } if ( ! ctl) { r->options &= ~MPARSE_COMMENT; return roff_parsetext(r, buf, pos, offs) | e; } /* Skip empty request lines. */ if (buf->buf[pos] == '"') { mandoc_msg(MANDOCERR_COMMENT_BAD, ln, pos, NULL); return ROFF_IGN; } else if (buf->buf[pos] == '\0') return ROFF_IGN; /* * If a scope is open, go to the child handler for that macro, * as it may want to preprocess before doing anything with it. * Don't do so if an equation is open. */ if (r->last) { t = r->last->tok; return (*roffs[t].sub)(r, t, buf, ln, ppos, pos, offs); } /* No scope is open. This is a new request or macro. */ r->options &= ~MPARSE_COMMENT; spos = pos; t = roff_parse(r, buf->buf, &pos, ln, ppos); /* Tables ignore most macros. */ if (r->tbl != NULL && (t == TOKEN_NONE || t == ROFF_TS || t == ROFF_br || t == ROFF_ce || t == ROFF_rj || t == ROFF_sp)) { mandoc_msg(MANDOCERR_TBLMACRO, ln, pos, "%s", buf->buf + spos); if (t != TOKEN_NONE) return ROFF_IGN; while (buf->buf[pos] != '\0' && buf->buf[pos] != ' ') pos++; while (buf->buf[pos] == ' ') pos++; tbl_read(r->tbl, ln, buf->buf, pos); roff_addtbl(r->man, ln, r->tbl); return ROFF_IGN; } /* For now, let high level macros abort .ce mode. */ if (ctl && roffce_node != NULL && (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ || t == ROFF_TH || t == ROFF_TS)) { r->man->last = roffce_node; r->man->next = ROFF_NEXT_SIBLING; roffce_lines = 0; roffce_node = NULL; } /* * This is neither a roff request nor a user-defined macro. * Let the standard macro set parsers handle it. */ if (t == TOKEN_NONE) return ROFF_CONT; /* Execute a roff request or a user defined macro. */ return (*roffs[t].proc)(r, t, buf, ln, spos, pos, offs); } /* * Internal interface function to tell the roff parser that execution * of the current macro ended. This is required because macro * definitions usually do not end with a .return request. */ void roff_userret(struct roff *r) { struct mctx *ctx; int i; assert(r->mstackpos >= 0); ctx = r->mstack + r->mstackpos; for (i = 0; i < ctx->argc; i++) free(ctx->argv[i]); ctx->argc = 0; r->mstackpos--; } void roff_endparse(struct roff *r) { if (r->last != NULL) mandoc_msg(MANDOCERR_BLK_NOEND, r->last->line, r->last->col, "%s", roff_name[r->last->tok]); if (r->eqn != NULL) { mandoc_msg(MANDOCERR_BLK_NOEND, r->eqn->node->line, r->eqn->node->pos, "EQ"); eqn_parse(r->eqn); r->eqn = NULL; } if (r->tbl != NULL) { tbl_end(r->tbl, 1); r->tbl = NULL; } } /* * Parse a roff node's type from the input buffer. This must be in the * form of ".foo xxx" in the usual way. */ static enum roff_tok roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos) { char *cp; const char *mac; size_t maclen; int deftype; enum roff_tok t; cp = buf + *pos; if ('\0' == *cp || '"' == *cp || '\t' == *cp || ' ' == *cp) return TOKEN_NONE; mac = cp; maclen = roff_getname(r, &cp, ln, ppos); deftype = ROFFDEF_USER | ROFFDEF_REN; r->current_string = roff_getstrn(r, mac, maclen, &deftype); switch (deftype) { case ROFFDEF_USER: t = ROFF_USERDEF; break; case ROFFDEF_REN: t = ROFF_RENAMED; break; default: t = roffhash_find(r->reqtab, mac, maclen); break; } if (t != TOKEN_NONE) *pos = cp - buf; else if (deftype == ROFFDEF_UNDEF) { /* Using an undefined macro defines it to be empty. */ roff_setstrn(&r->strtab, mac, maclen, "", 0, 0); roff_setstrn(&r->rentab, mac, maclen, NULL, 0, 0); } return t; } /* --- handling of request blocks ----------------------------------------- */ /* * Close a macro definition block or an "ignore" block. */ static int roff_cblock(ROFF_ARGS) { int rr; if (r->last == NULL) { mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, ".."); return ROFF_IGN; } switch (r->last->tok) { case ROFF_am: case ROFF_ami: case ROFF_de: case ROFF_dei: case ROFF_ig: break; case ROFF_am1: case ROFF_de1: /* Remapped in roff_block(). */ abort(); default: mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, ".."); return ROFF_IGN; } roffnode_pop(r); roffnode_cleanscope(r); /* * If a conditional block with braces is still open, * check for "\}" block end markers. */ if (r->last != NULL && r->last->endspan < 0) { rr = 1; /* If arguments follow "\}", warn about them. */ roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr); } if (buf->buf[pos] != '\0') mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos, ".. %s", buf->buf + pos); 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 && r->last->endspan > 0) { 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; } /* * Check for a closing "\}" and handle it. * In this function, the final "int *offs" argument is used for * different purposes than elsewhere: * Input: *offs == 0: caller wants to discard arguments following \} * *offs == 1: caller wants to preserve text following \} * Output: *offs = 0: tell caller to discard input line * *offs = 1: tell caller to use input line */ static int roff_cond_checkend(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 macro line without a preceding macro or * a text line contains nothing else, drop the line completely. */ ep = buf->buf + pos; if (ep[0] == '\\' && ep[1] == '}' && (ep[2] == '\0' || *offs == 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 '}': if (ep[2] == '\0') ep[0] = '\0'; else if (rr) ep[1] = '&'; else 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; } } *offs = rr; return irc; } /* * Parse and process a request or macro line in conditional scope. */ static int roff_cond_sub(ROFF_ARGS) { struct roffnode *bl; int irc, rr; enum roff_tok t; rr = 0; /* If arguments follow "\}", skip them. */ irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr); t = roff_parse(r, buf->buf, &pos, ln, ppos); /* For now, let high level macros abort .ce mode. */ if (roffce_node != NULL && (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ || t == ROFF_TH || t == ROFF_TS)) { r->man->last = roffce_node; r->man->next = ROFF_NEXT_SIBLING; roffce_lines = 0; roffce_node = NULL; } /* * Fully handle known macros when they are structurally * required or when the conditional evaluated to true. */ if (t == ROFF_break) { if (irc & ROFF_LOOPMASK) irc = ROFF_IGN | ROFF_LOOPEXIT; else if (rr) { for (bl = r->last; bl != NULL; bl = bl->parent) { bl->rule = 0; if (bl->tok == ROFF_while) break; } } } else if (t != TOKEN_NONE && (rr || roffs[t].flags & ROFFMAC_STRUCT)) irc |= (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs); else irc |= rr ? ROFF_CONT : ROFF_IGN; return irc; } /* * Parse and process a text line in conditional scope. */ static int roff_cond_text(ROFF_ARGS) { int irc, rr; rr = 1; /* If arguments follow "\}", preserve them. */ irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr); 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_FONTCR: case ESCAPE_FONTCB: case ESCAPE_FONTCI: case ESCAPE_FONTPREV: font++; break; default: break; } } if (font > 1) mandoc_msg(MANDOCERR_CHAR_FONT, ln, (int)(vp - buf->buf), "%s", vp); /* * Approximate the effect of .char using the .tr tables. * XXX In groff, .char and .tr interact differently. */ if (ksz == 1) { if (r->xtab == NULL) r->xtab = mandoc_calloc(128, sizeof(*r->xtab)); assert((unsigned int)*kp < 128); free(r->xtab[(int)*kp].p); r->xtab[(int)*kp].sz = mandoc_asprintf(&r->xtab[(int)*kp].p, "%s%s", vp, font ? "\fP" : ""); } else { roff_setstrn(&r->xmbtab, kp, ksz, vp, vsz, 0); if (font) roff_setstrn(&r->xmbtab, kp, ksz, "\\fP", 3, 1); } return ROFF_IGN; } static int roff_ec(ROFF_ARGS) { const char *p; p = buf->buf + pos; if (*p == '\0') r->escape = '\\'; else { r->escape = *p; if (*++p != '\0') mandoc_msg(MANDOCERR_ARG_EXCESS, ln, (int)(p - buf->buf), "ec ... %s", p); } return ROFF_IGN; } static int roff_eo(ROFF_ARGS) { r->escape = '\0'; if (buf->buf[pos] != '\0') mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos, "eo %s", buf->buf + pos); return ROFF_IGN; } static int roff_nop(ROFF_ARGS) { while (buf->buf[pos] == ' ') pos++; *offs = pos; return ROFF_RERUN; } static int roff_tr(ROFF_ARGS) { const char *p, *first, *second; size_t fsz, ssz; enum mandoc_esc esc; p = buf->buf + pos; if (*p == '\0') { mandoc_msg(MANDOCERR_REQ_EMPTY, ln, ppos, "tr"); return ROFF_IGN; } while (*p != '\0') { fsz = ssz = 1; first = p++; if (*first == '\\') { esc = mandoc_escape(&p, NULL, NULL); if (esc == ESCAPE_ERROR) { mandoc_msg(MANDOCERR_ESC_BAD, ln, (int)(p - buf->buf), "%s", first); return ROFF_IGN; } fsz = (size_t)(p - first); } second = p++; if (*second == '\\') { esc = mandoc_escape(&p, NULL, NULL); if (esc == ESCAPE_ERROR) { mandoc_msg(MANDOCERR_ESC_BAD, ln, (int)(p - buf->buf), "%s", second); return ROFF_IGN; } ssz = (size_t)(p - second); } else if (*second == '\0') { mandoc_msg(MANDOCERR_TR_ODD, ln, (int)(first - buf->buf), "tr %s", first); second = " "; p--; } if (fsz > 1) { roff_setstrn(&r->xmbtab, first, fsz, second, ssz, 0); continue; } if (r->xtab == NULL) r->xtab = mandoc_calloc(128, sizeof(struct roffstr)); free(r->xtab[(int)*first].p); r->xtab[(int)*first].p = mandoc_strndup(second, ssz); r->xtab[(int)*first].sz = ssz; } return ROFF_IGN; } /* * Implementation of the .return request. * There is no need to call roff_userret() from here. * The read module will call that after rewinding the reader stack * to the place from where the current macro was called. */ static int roff_return(ROFF_ARGS) { if (r->mstackpos >= 0) return ROFF_IGN | ROFF_USERRET; mandoc_msg(MANDOCERR_REQ_NOMAC, ln, ppos, "return"); return ROFF_IGN; } static int roff_rn(ROFF_ARGS) { const char *value; char *oldn, *newn, *end; size_t oldsz, newsz; int deftype; oldn = newn = buf->buf + pos; if (*oldn == '\0') return ROFF_IGN; oldsz = roff_getname(r, &newn, ln, pos); if (oldn[oldsz] == '\\' || oldn[oldsz] == '\t' || *newn == '\0') return ROFF_IGN; end = newn; newsz = roff_getname(r, &end, ln, newn - buf->buf); if (newsz == 0) return ROFF_IGN; deftype = ROFFDEF_ANY; value = roff_getstrn(r, oldn, oldsz, &deftype); switch (deftype) { case ROFFDEF_USER: roff_setstrn(&r->strtab, newn, newsz, value, strlen(value), 0); roff_setstrn(&r->strtab, oldn, oldsz, NULL, 0, 0); roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0); break; case ROFFDEF_PRE: roff_setstrn(&r->strtab, newn, newsz, value, strlen(value), 0); roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0); break; case ROFFDEF_REN: roff_setstrn(&r->rentab, newn, newsz, value, strlen(value), 0); roff_setstrn(&r->rentab, oldn, oldsz, NULL, 0, 0); roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0); break; case ROFFDEF_STD: roff_setstrn(&r->rentab, newn, newsz, oldn, oldsz, 0); roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0); break; default: roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0); roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0); break; } return ROFF_IGN; } static int roff_shift(ROFF_ARGS) { struct mctx *ctx; int levels, i; levels = 1; if (buf->buf[pos] != '\0' && roff_evalnum(r, ln, buf->buf, &pos, &levels, 0) == 0) { mandoc_msg(MANDOCERR_CE_NONUM, ln, pos, "shift %s", buf->buf + pos); levels = 1; } if (r->mstackpos < 0) { mandoc_msg(MANDOCERR_REQ_NOMAC, ln, ppos, "shift"); return ROFF_IGN; } ctx = r->mstack + r->mstackpos; if (levels > ctx->argc) { mandoc_msg(MANDOCERR_SHIFT, ln, pos, "%d, but max is %d", levels, ctx->argc); levels = ctx->argc; } if (levels == 0) return ROFF_IGN; for (i = 0; i < levels; i++) free(ctx->argv[i]); ctx->argc -= levels; for (i = 0; i < ctx->argc; i++) ctx->argv[i] = ctx->argv[i + levels]; return ROFF_IGN; } static int roff_so(ROFF_ARGS) { char *name, *cp; name = buf->buf + pos; mandoc_msg(MANDOCERR_SO, ln, ppos, "so %s", name); /* * Handle `so'. Be EXTREMELY careful, as we shouldn't be * opening anything that's not in our cwd or anything beneath * it. Thus, explicitly disallow traversing up the file-system * or using absolute paths. */ if (*name == '/' || strstr(name, "../") || strstr(name, "/..")) { mandoc_msg(MANDOCERR_SO_PATH, ln, ppos, ".so %s", name); buf->sz = mandoc_asprintf(&cp, ".sp\nSee the file %s.\n.sp", name) + 1; free(buf->buf); buf->buf = cp; *offs = 0; return ROFF_REPARSE; } *offs = pos; return ROFF_SO; } /* --- user defined strings and macros ------------------------------------ */ static int roff_userdef(ROFF_ARGS) { struct mctx *ctx; char *arg, *ap, *dst, *src; size_t sz; /* If the macro is empty, ignore it altogether. */ if (*r->current_string == '\0') return ROFF_IGN; /* Initialize a new macro stack context. */ if (++r->mstackpos == r->mstacksz) { r->mstack = mandoc_recallocarray(r->mstack, r->mstacksz, r->mstacksz + 8, sizeof(*r->mstack)); r->mstacksz += 8; } ctx = r->mstack + r->mstackpos; ctx->argsz = 0; ctx->argc = 0; ctx->argv = NULL; /* * Collect pointers to macro argument strings, * NUL-terminating them and escaping quotes. */ src = buf->buf + pos; while (*src != '\0') { if (ctx->argc == ctx->argsz) { ctx->argsz += 8; ctx->argv = mandoc_reallocarray(ctx->argv, ctx->argsz, sizeof(*ctx->argv)); } arg = roff_getarg(r, &src, ln, &pos); sz = 1; /* For the terminating NUL. */ for (ap = arg; *ap != '\0'; ap++) sz += *ap == '"' ? 4 : 1; ctx->argv[ctx->argc++] = dst = mandoc_malloc(sz); for (ap = arg; *ap != '\0'; ap++) { if (*ap == '"') { memcpy(dst, "\\(dq", 4); dst += 4; } else *dst++ = *ap; } *dst = '\0'; free(arg); } /* Replace the macro invocation by the macro definition. */ free(buf->buf); buf->buf = mandoc_strdup(r->current_string); buf->sz = strlen(buf->buf) + 1; *offs = 0; return buf->buf[buf->sz - 2] == '\n' ? ROFF_REPARSE | ROFF_USERCALL : ROFF_IGN | ROFF_APPEND; } /* * Calling a high-level macro that was renamed with .rn. * r->current_string has already been set up by roff_parse(). */ static int roff_renamed(ROFF_ARGS) { char *nbuf; buf->sz = mandoc_asprintf(&nbuf, ".%s%s%s", r->current_string, buf->buf[pos] == '\0' ? "" : " ", buf->buf + pos) + 1; free(buf->buf); buf->buf = nbuf; *offs = 0; return ROFF_CONT; } /* * Measure the length in bytes of the roff identifier at *cpp * and advance the pointer to the next word. */ static size_t roff_getname(struct roff *r, char **cpp, int ln, int pos) { char *name, *cp; size_t namesz; name = *cpp; if (*name == '\0') return 0; /* Advance cp to the byte after the end of the name. */ for (cp = name; 1; cp++) { namesz = cp - name; if (*cp == '\0') break; if (*cp == ' ' || *cp == '\t') { cp++; break; } if (*cp != '\\') continue; if (cp[1] == '{' || cp[1] == '}') break; if (*++cp == '\\') continue; mandoc_msg(MANDOCERR_NAMESC, ln, pos, "%.*s", (int)(cp - name + 1), name); mandoc_escape((const char **)&cp, NULL, NULL); break; } /* Read past spaces. */ while (*cp == ' ') cp++; *cpp = cp; return namesz; } /* * Store *string into the user-defined string called *name. * To clear an existing entry, call with (*r, *name, NULL, 0). * append == 0: replace mode * append == 1: single-line append mode * append == 2: multiline append mode, append '\n' after each call */ static void roff_setstr(struct roff *r, const char *name, const char *string, int append) { size_t namesz; namesz = strlen(name); roff_setstrn(&r->strtab, name, namesz, string, string ? strlen(string) : 0, append); roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0); } static void roff_setstrn(struct roffkv **r, const char *name, size_t namesz, const char *string, size_t stringsz, int append) { struct roffkv *n; char *c; int i; size_t oldch, newch; /* Search for an existing string with the same name. */ n = *r; while (n && (namesz != n->key.sz || strncmp(n->key.p, name, namesz))) n = n->next; if (NULL == n) { /* Create a new string table entry. */ n = mandoc_malloc(sizeof(struct roffkv)); n->key.p = mandoc_strndup(name, namesz); n->key.sz = namesz; n->val.p = NULL; n->val.sz = 0; n->next = *r; *r = n; } else if (0 == append) { free(n->val.p); n->val.p = NULL; n->val.sz = 0; } if (NULL == string) return; /* * One additional byte for the '\n' in multiline mode, * and one for the terminating '\0'. */ newch = stringsz + (1 < append ? 2u : 1u); if (NULL == n->val.p) { n->val.p = mandoc_malloc(newch); *n->val.p = '\0'; oldch = 0; } else { oldch = n->val.sz; n->val.p = mandoc_realloc(n->val.p, oldch + newch); } /* Skip existing content in the destination buffer. */ c = n->val.p + (int)oldch; /* Append new content to the destination buffer. */ i = 0; while (i < (int)stringsz) { /* * Rudimentary roff copy mode: * Handle escaped backslashes. */ if ('\\' == string[i] && '\\' == string[i + 1]) i++; *c++ = string[i++]; } /* Append terminating bytes. */ if (1 < append) *c++ = '\n'; *c = '\0'; n->val.sz = (int)(c - n->val.p); } static const char * roff_getstrn(struct roff *r, const char *name, size_t len, int *deftype) { const struct roffkv *n; int found, i; enum roff_tok tok; found = 0; for (n = r->strtab; n != NULL; n = n->next) { if (strncmp(name, n->key.p, len) != 0 || n->key.p[len] != '\0' || n->val.p == NULL) continue; if (*deftype & ROFFDEF_USER) { *deftype = ROFFDEF_USER; return n->val.p; } else { found = 1; break; } } for (n = r->rentab; n != NULL; n = n->next) { if (strncmp(name, n->key.p, len) != 0 || n->key.p[len] != '\0' || n->val.p == NULL) continue; if (*deftype & ROFFDEF_REN) { *deftype = ROFFDEF_REN; return n->val.p; } else { found = 1; break; } } for (i = 0; i < PREDEFS_MAX; i++) { if (strncmp(name, predefs[i].name, len) != 0 || predefs[i].name[len] != '\0') continue; if (*deftype & ROFFDEF_PRE) { *deftype = ROFFDEF_PRE; return predefs[i].str; } else { found = 1; break; } } if (r->man->meta.macroset != MACROSET_MAN) { for (tok = MDOC_Dd; tok < MDOC_MAX; tok++) { if (strncmp(name, roff_name[tok], len) != 0 || roff_name[tok][len] != '\0') continue; if (*deftype & ROFFDEF_STD) { *deftype = ROFFDEF_STD; return NULL; } else { found = 1; break; } } } if (r->man->meta.macroset != MACROSET_MDOC) { for (tok = MAN_TH; tok < MAN_MAX; tok++) { if (strncmp(name, roff_name[tok], len) != 0 || roff_name[tok][len] != '\0') continue; if (*deftype & ROFFDEF_STD) { *deftype = ROFFDEF_STD; return NULL; } else { found = 1; break; } } } if (found == 0 && *deftype != ROFFDEF_ANY) { if (*deftype & ROFFDEF_REN) { /* * This might still be a request, * so do not treat it as undefined yet. */ *deftype = ROFFDEF_UNDEF; return NULL; } /* Using an undefined string defines it to be empty. */ roff_setstrn(&r->strtab, name, len, "", 0, 0); roff_setstrn(&r->rentab, name, len, NULL, 0, 0); } *deftype = 0; return NULL; } static void roff_freestr(struct roffkv *r) { struct roffkv *n, *nn; for (n = r; n; n = nn) { free(n->key.p); free(n->val.p); nn = n->next; free(n); } } /* --- accessors and utility functions ------------------------------------ */ /* * Duplicate an input string, making the appropriate character * conversations (as stipulated by `tr') along the way. * Returns a heap-allocated string with all the replacements made. */ char * roff_strdup(const struct roff *r, const char *p) { const struct roffkv *cp; char *res; const char *pp; size_t ssz, sz; enum mandoc_esc esc; if (NULL == r->xmbtab && NULL == r->xtab) return mandoc_strdup(p); else if ('\0' == *p) return mandoc_strdup(""); /* * Step through each character looking for term matches * (remember that a `tr' can be invoked with an escape, which is * a glyph but the escape is multi-character). * We only do this if the character hash has been initialised * and the string is >0 length. */ res = NULL; ssz = 0; while ('\0' != *p) { assert((unsigned int)*p < 128); if ('\\' != *p && r->xtab && r->xtab[(unsigned int)*p].p) { sz = r->xtab[(int)*p].sz; res = mandoc_realloc(res, ssz + sz + 1); memcpy(res + ssz, r->xtab[(int)*p].p, sz); ssz += sz; p++; continue; } else if ('\\' != *p) { res = mandoc_realloc(res, ssz + 2); res[ssz++] = *p++; continue; } /* Search for term matches. */ for (cp = r->xmbtab; cp; cp = cp->next) if (0 == strncmp(p, cp->key.p, cp->key.sz)) break; if (NULL != cp) { /* * A match has been found. * Append the match to the array and move * forward by its keysize. */ res = mandoc_realloc(res, ssz + cp->val.sz + 1); memcpy(res + ssz, cp->val.p, cp->val.sz); ssz += cp->val.sz; p += (int)cp->key.sz; continue; } /* * Handle escapes carefully: we need to copy * over just the escape itself, or else we might * do replacements within the escape itself. * Make sure to pass along the bogus string. */ pp = p++; esc = mandoc_escape(&p, NULL, NULL); if (ESCAPE_ERROR == esc) { sz = strlen(pp); res = mandoc_realloc(res, ssz + sz + 1); memcpy(res + ssz, pp, sz); break; } /* * We bail out on bad escapes. * No need to warn: we already did so when * roff_expand() was called. */ sz = (int)(p - pp); res = mandoc_realloc(res, ssz + sz + 1); memcpy(res + ssz, pp, sz); ssz += sz; } res[(int)ssz] = '\0'; return res; } int roff_getformat(const struct roff *r) { return r->format; } /* * Find out whether a line is a macro line or not. * If it is, adjust the current position and return one; if it isn't, * return zero and don't change the current position. * If the control character has been set with `.cc', then let that grain * precedence. * This is slighly contrary to groff, where using the non-breaking * control character when `cc' has been invoked will cause the * non-breaking macro contents to be printed verbatim. */ int roff_getcontrol(const struct roff *r, const char *cp, int *ppos) { int pos; pos = *ppos; if (r->control != '\0' && cp[pos] == r->control) pos++; else if (r->control != '\0') return 0; else if ('\\' == cp[pos] && '.' == cp[pos + 1]) pos += 2; else if ('.' == cp[pos] || '\'' == cp[pos]) pos++; else return 0; while (' ' == cp[pos] || '\t' == cp[pos]) pos++; *ppos = pos; return 1; } /* $Id: roff.h,v 1.74 2020/04/08 11:56:03 schwarze Exp $ */ /* * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze * Copyright (c) 2008, 2009, 2010, 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. * * Common data types for all syntax trees and related functions. */ struct ohash; struct mdoc_arg; union mdoc_data; struct tbl_span; struct eqn_box; enum roff_macroset { MACROSET_NONE = 0, MACROSET_MDOC, MACROSET_MAN }; enum roff_sec { SEC_NONE = 0, SEC_NAME, SEC_LIBRARY, SEC_SYNOPSIS, SEC_DESCRIPTION, SEC_CONTEXT, SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */ SEC_RETURN_VALUES, SEC_ENVIRONMENT, SEC_FILES, SEC_EXIT_STATUS, SEC_EXAMPLES, SEC_DIAGNOSTICS, SEC_COMPATIBILITY, SEC_ERRORS, SEC_SEE_ALSO, SEC_STANDARDS, SEC_HISTORY, SEC_AUTHORS, SEC_CAVEATS, SEC_BUGS, SEC_SECURITY, SEC_CUSTOM, SEC__MAX }; enum roff_type { ROFFT_ROOT, ROFFT_BLOCK, ROFFT_HEAD, ROFFT_BODY, ROFFT_TAIL, ROFFT_ELEM, ROFFT_TEXT, ROFFT_COMMENT, ROFFT_TBL, ROFFT_EQN }; enum roff_tok { ROFF_br = 0, ROFF_ce, ROFF_fi, ROFF_ft, ROFF_ll, ROFF_mc, ROFF_nf, ROFF_po, ROFF_rj, ROFF_sp, ROFF_ta, ROFF_ti, ROFF_MAX, ROFF_ab, ROFF_ad, ROFF_af, ROFF_aln, ROFF_als, ROFF_am, ROFF_am1, ROFF_ami, ROFF_ami1, ROFF_as, ROFF_as1, ROFF_asciify, ROFF_backtrace, ROFF_bd, ROFF_bleedat, ROFF_blm, ROFF_box, ROFF_boxa, ROFF_bp, ROFF_BP, ROFF_break, ROFF_breakchar, ROFF_brnl, ROFF_brp, ROFF_brpnl, ROFF_c2, ROFF_cc, ROFF_cf, ROFF_cflags, ROFF_ch, ROFF_char, ROFF_chop, ROFF_class, ROFF_close, ROFF_CL, ROFF_color, ROFF_composite, ROFF_continue, ROFF_cp, ROFF_cropat, ROFF_cs, ROFF_cu, ROFF_da, ROFF_dch, ROFF_Dd, ROFF_de, ROFF_de1, ROFF_defcolor, ROFF_dei, ROFF_dei1, ROFF_device, ROFF_devicem, ROFF_di, ROFF_do, ROFF_ds, ROFF_ds1, ROFF_dwh, ROFF_dt, ROFF_ec, ROFF_ecr, ROFF_ecs, ROFF_el, ROFF_em, ROFF_EN, ROFF_eo, ROFF_EP, ROFF_EQ, ROFF_errprint, ROFF_ev, ROFF_evc, ROFF_ex, ROFF_fallback, ROFF_fam, ROFF_fc, ROFF_fchar, ROFF_fcolor, ROFF_fdeferlig, ROFF_feature, ROFF_fkern, ROFF_fl, ROFF_flig, ROFF_fp, ROFF_fps, ROFF_fschar, ROFF_fspacewidth, ROFF_fspecial, ROFF_ftr, ROFF_fzoom, ROFF_gcolor, ROFF_hc, ROFF_hcode, ROFF_hidechar, ROFF_hla, ROFF_hlm, ROFF_hpf, ROFF_hpfa, ROFF_hpfcode, ROFF_hw, ROFF_hy, ROFF_hylang, ROFF_hylen, ROFF_hym, ROFF_hypp, ROFF_hys, ROFF_ie, ROFF_if, ROFF_ig, /* MAN_in; ignored in mdoc(7) */ ROFF_index, ROFF_it, ROFF_itc, ROFF_IX, ROFF_kern, ROFF_kernafter, ROFF_kernbefore, ROFF_kernpair, ROFF_lc, ROFF_lc_ctype, ROFF_lds, ROFF_length, ROFF_letadj, ROFF_lf, ROFF_lg, ROFF_lhang, ROFF_linetabs, ROFF_lnr, ROFF_lnrf, ROFF_lpfx, ROFF_ls, ROFF_lsm, ROFF_lt, ROFF_mediasize, ROFF_minss, ROFF_mk, ROFF_mso, ROFF_na, ROFF_ne, ROFF_nh, ROFF_nhychar, ROFF_nm, ROFF_nn, ROFF_nop, ROFF_nr, ROFF_nrf, ROFF_nroff, ROFF_ns, ROFF_nx, ROFF_open, ROFF_opena, ROFF_os, ROFF_output, ROFF_padj, ROFF_papersize, ROFF_pc, ROFF_pev, ROFF_pi, ROFF_PI, ROFF_pl, ROFF_pm, ROFF_pn, ROFF_pnr, ROFF_ps, ROFF_psbb, ROFF_pshape, ROFF_pso, ROFF_ptr, ROFF_pvs, ROFF_rchar, ROFF_rd, ROFF_recursionlimit, ROFF_return, ROFF_rfschar, ROFF_rhang, ROFF_rm, ROFF_rn, ROFF_rnn, ROFF_rr, ROFF_rs, ROFF_rt, ROFF_schar, ROFF_sentchar, ROFF_shc, ROFF_shift, ROFF_sizes, ROFF_so, ROFF_spacewidth, ROFF_special, ROFF_spreadwarn, ROFF_ss, ROFF_sty, ROFF_substring, ROFF_sv, ROFF_sy, ROFF_T_, ROFF_tc, ROFF_TE, ROFF_TH, ROFF_tkf, ROFF_tl, ROFF_tm, ROFF_tm1, ROFF_tmc, ROFF_tr, ROFF_track, ROFF_transchar, ROFF_trf, ROFF_trimat, ROFF_trin, ROFF_trnt, ROFF_troff, ROFF_TS, ROFF_uf, ROFF_ul, ROFF_unformat, ROFF_unwatch, ROFF_unwatchn, ROFF_vpt, ROFF_vs, ROFF_warn, ROFF_warnscale, ROFF_watch, ROFF_watchlength, ROFF_watchn, ROFF_wh, ROFF_while, ROFF_write, ROFF_writec, ROFF_writem, ROFF_xflag, ROFF_cblock, ROFF_RENAMED, ROFF_USERDEF, TOKEN_NONE, MDOC_Dd, MDOC_Dt, MDOC_Os, MDOC_Sh, MDOC_Ss, MDOC_Pp, MDOC_D1, MDOC_Dl, MDOC_Bd, MDOC_Ed, MDOC_Bl, MDOC_El, MDOC_It, MDOC_Ad, MDOC_An, MDOC_Ap, MDOC_Ar, MDOC_Cd, MDOC_Cm, MDOC_Dv, MDOC_Er, MDOC_Ev, MDOC_Ex, MDOC_Fa, MDOC_Fd, MDOC_Fl, MDOC_Fn, MDOC_Ft, MDOC_Ic, MDOC_In, MDOC_Li, MDOC_Nd, MDOC_Nm, MDOC_Op, MDOC_Ot, MDOC_Pa, MDOC_Rv, MDOC_St, MDOC_Va, MDOC_Vt, MDOC_Xr, MDOC__A, MDOC__B, MDOC__D, MDOC__I, MDOC__J, MDOC__N, MDOC__O, MDOC__P, MDOC__R, MDOC__T, MDOC__V, MDOC_Ac, MDOC_Ao, MDOC_Aq, MDOC_At, MDOC_Bc, MDOC_Bf, MDOC_Bo, MDOC_Bq, MDOC_Bsx, MDOC_Bx, MDOC_Db, MDOC_Dc, MDOC_Do, MDOC_Dq, MDOC_Ec, MDOC_Ef, MDOC_Em, MDOC_Eo, MDOC_Fx, MDOC_Ms, MDOC_No, MDOC_Ns, MDOC_Nx, MDOC_Ox, MDOC_Pc, MDOC_Pf, MDOC_Po, MDOC_Pq, MDOC_Qc, MDOC_Ql, MDOC_Qo, MDOC_Qq, MDOC_Re, MDOC_Rs, MDOC_Sc, MDOC_So, MDOC_Sq, MDOC_Sm, MDOC_Sx, MDOC_Sy, MDOC_Tn, MDOC_Ux, MDOC_Xc, MDOC_Xo, MDOC_Fo, MDOC_Fc, MDOC_Oo, MDOC_Oc, MDOC_Bk, MDOC_Ek, MDOC_Bt, MDOC_Hf, MDOC_Fr, MDOC_Ud, MDOC_Lb, MDOC_Lp, MDOC_Lk, MDOC_Mt, MDOC_Brq, MDOC_Bro, MDOC_Brc, MDOC__C, MDOC_Es, MDOC_En, MDOC_Dx, MDOC__Q, MDOC__U, MDOC_Ta, MDOC_Tg, MDOC_MAX, MAN_TH, MAN_SH, MAN_SS, MAN_TP, MAN_TQ, MAN_LP, MAN_PP, MAN_P, MAN_IP, MAN_HP, MAN_SM, MAN_SB, MAN_BI, MAN_IB, MAN_BR, MAN_RB, MAN_R, MAN_B, MAN_I, MAN_IR, MAN_RI, MAN_RE, MAN_RS, MAN_DT, MAN_UC, MAN_PD, MAN_AT, MAN_in, MAN_SY, MAN_YS, MAN_OP, MAN_EX, MAN_EE, MAN_UR, MAN_UE, MAN_MT, MAN_ME, MAN_MAX }; /* * Indicates that a BODY's formatting has ended, but * the scope is still open. Used for badly nested blocks. */ enum mdoc_endbody { ENDBODY_NOT = 0, ENDBODY_SPACE /* Is broken: append a space. */ }; enum mandoc_os { MANDOC_OS_OTHER = 0, MANDOC_OS_NETBSD, MANDOC_OS_OPENBSD }; struct roff_node { struct roff_node *parent; /* Parent AST node. */ struct roff_node *child; /* First child AST node. */ struct roff_node *last; /* Last child AST node. */ struct roff_node *next; /* Sibling AST node. */ struct roff_node *prev; /* Prior sibling AST node. */ struct roff_node *head; /* BLOCK */ struct roff_node *body; /* BLOCK/ENDBODY */ struct roff_node *tail; /* BLOCK */ struct mdoc_arg *args; /* BLOCK/ELEM */ union mdoc_data *norm; /* Normalized arguments. */ char *string; /* TEXT */ char *tag; /* For less(1) :t and HTML id=. */ struct tbl_span *span; /* TBL */ struct eqn_box *eqn; /* EQN */ int line; /* Input file line number. */ int pos; /* Input file column number. */ int flags; #define NODE_VALID (1 << 0) /* Has been validated. */ #define NODE_ENDED (1 << 1) /* Gone past body end mark. */ #define NODE_BROKEN (1 << 2) /* Must validate parent when ending. */ #define NODE_LINE (1 << 3) /* First macro/text on line. */ #define NODE_DELIMO (1 << 4) #define NODE_DELIMC (1 << 5) #define NODE_EOS (1 << 6) /* At sentence boundary. */ #define NODE_SYNPRETTY (1 << 7) /* SYNOPSIS-style formatting. */ #define NODE_NOFILL (1 << 8) /* Fill mode switched off. */ #define NODE_NOSRC (1 << 9) /* Generated node, not in input file. */ #define NODE_NOPRT (1 << 10) /* Shall not print anything. */ #define NODE_ID (1 << 11) /* Target for deep linking. */ #define NODE_HREF (1 << 12) /* Link to another place in this page. */ int prev_font; /* Before entering this node. */ int aux; /* Decoded node data, type-dependent. */ enum roff_tok tok; /* Request or macro ID. */ enum roff_type type; /* AST node type. */ enum roff_sec sec; /* Current named section. */ enum mdoc_endbody end; /* BODY */ }; struct roff_meta { struct roff_node *first; /* The first node parsed. */ char *msec; /* Manual section, usually a digit. */ char *vol; /* Manual volume title. */ char *os; /* Operating system. */ char *arch; /* Machine architecture. */ char *title; /* Manual title, usually CAPS. */ char *name; /* Leading manual name. */ char *date; /* Normalized date. */ char *sodest; /* .so target file name or NULL. */ int hasbody; /* Document is not empty. */ int rcsids; /* Bits indexed by enum mandoc_os. */ enum mandoc_os os_e; /* Operating system. */ enum roff_macroset macroset; /* Kind of high-level macros used. */ }; extern const char *const *roff_name; int arch_valid(const char *, enum mandoc_os); void deroff(char **, const struct roff_node *); struct roff_node *roff_node_child(struct roff_node *); struct roff_node *roff_node_next(struct roff_node *); struct roff_node *roff_node_prev(struct roff_node *); int roff_node_transparent(struct roff_node *); int roff_tok_transparent(enum roff_tok); /* $Id: roff_html.c,v 1.21 2020/06/22 19:20:40 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 "config.h" #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; 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"); } } /* $OpenBSD: roff_int.h,v 1.16 2019/01/05 00:36:46 schwarze Exp $ */ /* * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze * Copyright (c) 2009, 2010, 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. * * Parser internals shared by multiple parsers. */ struct ohash; struct roff_node; struct roff_meta; struct roff; struct mdoc_arg; enum roff_next { ROFF_NEXT_SIBLING = 0, ROFF_NEXT_CHILD }; struct roff_man { struct roff_meta meta; /* Public parse results. */ struct roff *roff; /* Roff parser state data. */ struct ohash *mdocmac; /* Mdoc macro lookup table. */ struct ohash *manmac; /* Man macro lookup table. */ const char *os_s; /* Default operating system. */ struct roff_node *last; /* The last node parsed. */ struct roff_node *last_es; /* The most recent Es node. */ int quick; /* Abort parse early. */ int flags; /* Parse flags. */ #define ROFF_NOFILL (1 << 1) /* Fill mode switched off. */ #define MDOC_PBODY (1 << 2) /* In the document body. */ #define MDOC_NEWLINE (1 << 3) /* First macro/text in a line. */ #define MDOC_PHRASE (1 << 4) /* In a Bl -column phrase. */ #define MDOC_PHRASELIT (1 << 5) /* Literal within a phrase. */ #define MDOC_FREECOL (1 << 6) /* `It' invocation should close. */ #define MDOC_SYNOPSIS (1 << 7) /* SYNOPSIS-style formatting. */ #define MDOC_KEEP (1 << 8) /* In a word keep. */ #define MDOC_SMOFF (1 << 9) /* Spacing is off. */ #define MDOC_NODELIMC (1 << 10) /* Disable closing delimiter handling. */ #define MAN_ELINE (1 << 11) /* Next-line element scope. */ #define MAN_BLINE (1 << 12) /* Next-line block scope. */ #define MDOC_PHRASEQF (1 << 13) /* Quote first word encountered. */ #define MDOC_PHRASEQL (1 << 14) /* Quote last word of this phrase. */ #define MDOC_PHRASEQN (1 << 15) /* Quote first word of the next phrase. */ #define ROFF_NONOFILL (1 << 16) /* Temporarily suspend no-fill mode. */ #define MAN_NEWLINE MDOC_NEWLINE enum roff_sec lastsec; /* Last section seen. */ enum roff_sec lastnamed; /* Last standard section seen. */ enum roff_next next; /* Where to put the next node. */ char filesec; /* Section digit in the file name. */ }; struct roff_node *roff_node_alloc(struct roff_man *, int, int, enum roff_type, int); void roff_node_append(struct roff_man *, struct roff_node *); void roff_word_alloc(struct roff_man *, int, int, const char *); void roff_word_append(struct roff_man *, const char *); void roff_elem_alloc(struct roff_man *, int, int, int); struct roff_node *roff_block_alloc(struct roff_man *, int, int, int); struct roff_node *roff_head_alloc(struct roff_man *, int, int, int); struct roff_node *roff_body_alloc(struct roff_man *, int, int, int); void roff_node_unlink(struct roff_man *, struct roff_node *); void roff_node_relink(struct roff_man *, struct roff_node *); void roff_node_free(struct roff_node *); void roff_node_delete(struct roff_man *, struct roff_node *); struct ohash *roffhash_alloc(enum roff_tok, enum roff_tok); enum roff_tok roffhash_find(struct ohash *, const char *, size_t); void roffhash_free(struct ohash *); void roff_state_reset(struct roff_man *); void roff_validate(struct roff_man *); /* * Functions called from roff.c need to be declared here, * not in libmdoc.h or libman.h, even if they are specific * to either the mdoc(7) or the man(7) parser. */ void man_breakscope(struct roff_man *, int); void mdoc_argv_free(struct mdoc_arg *); /* $OpenBSD: roff_term.c,v 1.20 2020/09/03 17:37:06 schwarze Exp $ */ /* * Copyright (c) 2010,2014,2015,2017-2020 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 "mandoc.h" #include "roff.h" #include "out.h" #include "term.h" #define ROFF_TERM_ARGS struct termp *p, const struct roff_node *n typedef void (*roff_term_pre_fp)(ROFF_TERM_ARGS); static void roff_term_pre_br(ROFF_TERM_ARGS); static void roff_term_pre_ce(ROFF_TERM_ARGS); static void roff_term_pre_ft(ROFF_TERM_ARGS); static void roff_term_pre_ll(ROFF_TERM_ARGS); static void roff_term_pre_mc(ROFF_TERM_ARGS); static void roff_term_pre_po(ROFF_TERM_ARGS); static void roff_term_pre_sp(ROFF_TERM_ARGS); static void roff_term_pre_ta(ROFF_TERM_ARGS); static void roff_term_pre_ti(ROFF_TERM_ARGS); static const roff_term_pre_fp roff_term_pre_acts[ROFF_MAX] = { roff_term_pre_br, /* br */ roff_term_pre_ce, /* ce */ roff_term_pre_br, /* fi */ roff_term_pre_ft, /* ft */ roff_term_pre_ll, /* ll */ roff_term_pre_mc, /* mc */ roff_term_pre_br, /* nf */ roff_term_pre_po, /* po */ roff_term_pre_ce, /* rj */ roff_term_pre_sp, /* sp */ roff_term_pre_ta, /* ta */ roff_term_pre_ti, /* ti */ }; void roff_term_pre(struct termp *p, const struct roff_node *n) { assert(n->tok < ROFF_MAX); (*roff_term_pre_acts[n->tok])(p, n); } static void roff_term_pre_br(ROFF_TERM_ARGS) { term_newln(p); if (p->flags & TERMP_BRIND) { p->tcol->offset = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin; p->trailspace = 0; p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND); p->flags |= TERMP_NOSPACE; } } static void roff_term_pre_ce(ROFF_TERM_ARGS) { const struct roff_node *nc1, *nc2; roff_term_pre_br(p, n); p->flags |= n->tok == ROFF_ce ? TERMP_CENTER : TERMP_RIGHT; nc1 = n->child->next; while (nc1 != NULL) { nc2 = nc1; do { nc2 = nc2->next; } while (nc2 != NULL && (nc2->type != ROFFT_TEXT || (nc2->flags & NODE_LINE) == 0)); while (nc1 != nc2) { if (nc1->type == ROFFT_TEXT) term_word(p, nc1->string); else roff_term_pre(p, nc1); nc1 = nc1->next; } p->flags |= TERMP_NOSPACE; term_flushln(p); } p->flags &= ~(TERMP_CENTER | TERMP_RIGHT); } static void roff_term_pre_ft(ROFF_TERM_ARGS) { const char *cp; cp = n->child->string; switch (mandoc_font(cp, (int)strlen(cp))) { case ESCAPE_FONTBOLD: case ESCAPE_FONTCB: term_fontrepl(p, TERMFONT_BOLD); break; case ESCAPE_FONTITALIC: case ESCAPE_FONTCI: term_fontrepl(p, TERMFONT_UNDER); break; case ESCAPE_FONTBI: term_fontrepl(p, TERMFONT_BI); break; case ESCAPE_FONTPREV: term_fontlast(p); break; case ESCAPE_FONTROMAN: case ESCAPE_FONTCR: term_fontrepl(p, TERMFONT_NONE); break; default: break; } } static void roff_term_pre_ll(ROFF_TERM_ARGS) { term_setwidth(p, n->child != NULL ? n->child->string : NULL); } static void roff_term_pre_mc(ROFF_TERM_ARGS) { if (p->col) { p->flags |= TERMP_NOBREAK; term_flushln(p); p->flags &= ~(TERMP_NOBREAK | TERMP_NOSPACE); } if (n->child != NULL) { p->mc = n->child->string; p->flags |= TERMP_NEWMC; } else p->flags |= TERMP_ENDMC; } static void roff_term_pre_po(ROFF_TERM_ARGS) { struct roffsu su; static int po, pouse, polast; int ponew; /* Revert the currently active page offset. */ p->tcol->offset -= pouse; /* Determine the requested page offset. */ if (n->child != NULL && a2roffsu(n->child->string, &su, SCALE_EM) != NULL) { ponew = term_hen(p, &su); if (*n->child->string == '+' || *n->child->string == '-') ponew += po; } else ponew = polast; /* Remeber both the previous and the newly requested offset. */ polast = po; po = ponew; /* Truncate to the range [-offset, 60], remember, and apply it. */ pouse = po >= 60 ? 60 : po < -(int)p->tcol->offset ? -(int)p->tcol->offset : po; p->tcol->offset += pouse; } static void roff_term_pre_sp(ROFF_TERM_ARGS) { struct roffsu su; int len; if (n->child != NULL) { if (a2roffsu(n->child->string, &su, SCALE_VS) == NULL) su.scale = 1.0; len = term_vspan(p, &su); } else len = 1; if (len < 0) p->skipvsp -= len; else while (len--) term_vspace(p); roff_term_pre_br(p, n); } static void roff_term_pre_ta(ROFF_TERM_ARGS) { term_tab_set(p, NULL); for (n = n->child; n != NULL; n = n->next) term_tab_set(p, n->string); } static void roff_term_pre_ti(ROFF_TERM_ARGS) { struct roffsu su; const char *cp; const size_t maxoff = 72; int len, sign; roff_term_pre_br(p, n); if (n->child == NULL) return; cp = n->child->string; if (*cp == '+') { sign = 1; cp++; } else if (*cp == '-') { sign = -1; cp++; } else sign = 0; if (a2roffsu(cp, &su, SCALE_EM) == NULL) return; len = term_hen(p, &su); switch (sign) { case 1: if (p->tcol->offset + len <= maxoff) p->ti = len; else if (p->tcol->offset < maxoff) p->ti = maxoff - p->tcol->offset; else p->ti = 0; break; case -1: if ((size_t)len < p->tcol->offset) p->ti = -len; else p->ti = -p->tcol->offset; break; default: if ((size_t)len > maxoff) len = maxoff; p->ti = len - p->tcol->offset; break; } p->tcol->offset += p->ti; } /* $Id: roff_validate.c,v 1.20 2020/06/22 19:20:40 schwarze Exp $ */ /* * Copyright (c) 2010, 2017, 2018, 2020 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 "mandoc.h" #include "roff.h" #include "libmandoc.h" #include "roff_int.h" #define ROFF_VALID_ARGS struct roff_man *man, struct roff_node *n typedef void (*roff_valid_fp)(ROFF_VALID_ARGS); static void roff_valid_br(ROFF_VALID_ARGS); static void roff_valid_fi(ROFF_VALID_ARGS); static void roff_valid_ft(ROFF_VALID_ARGS); static void roff_valid_nf(ROFF_VALID_ARGS); static void roff_valid_sp(ROFF_VALID_ARGS); static const roff_valid_fp roff_valids[ROFF_MAX] = { roff_valid_br, /* br */ NULL, /* ce */ roff_valid_fi, /* fi */ roff_valid_ft, /* ft */ NULL, /* ll */ NULL, /* mc */ roff_valid_nf, /* nf */ NULL, /* po */ NULL, /* rj */ roff_valid_sp, /* sp */ NULL, /* ta */ NULL, /* ti */ }; void roff_validate(struct roff_man *man) { struct roff_node *n; n = man->last; assert(n->tok < ROFF_MAX); if (roff_valids[n->tok] != NULL) (*roff_valids[n->tok])(man, n); } static void roff_valid_br(ROFF_VALID_ARGS) { struct roff_node *np; if (n->next != NULL && n->next->type == ROFFT_TEXT && *n->next->string == ' ') { mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos, "br before text line with leading blank"); roff_node_delete(man, n); return; } if ((np = roff_node_prev(n)) == NULL) return; switch (np->tok) { case ROFF_br: case ROFF_sp: case MDOC_Pp: mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos, "br after %s", roff_name[np->tok]); roff_node_delete(man, n); break; default: break; } } static void roff_valid_fi(ROFF_VALID_ARGS) { if ((n->flags & NODE_NOFILL) == 0) mandoc_msg(MANDOCERR_FI_SKIP, n->line, n->pos, "fi"); } static void roff_valid_ft(ROFF_VALID_ARGS) { const char *cp; if (n->child == NULL) { man->next = ROFF_NEXT_CHILD; roff_word_alloc(man, n->line, n->pos, "P"); man->last = n; return; } cp = n->child->string; if (mandoc_font(cp, (int)strlen(cp)) != ESCAPE_ERROR) return; mandoc_msg(MANDOCERR_FT_BAD, n->line, n->pos, "ft %s", cp); roff_node_delete(man, n); } static void roff_valid_nf(ROFF_VALID_ARGS) { if (n->flags & NODE_NOFILL) mandoc_msg(MANDOCERR_NF_SKIP, n->line, n->pos, "nf"); } static void roff_valid_sp(ROFF_VALID_ARGS) { struct roff_node *np; if ((np = roff_node_prev(n)) == NULL) return; switch (np->tok) { case ROFF_br: mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos, "br before sp"); roff_node_delete(man, np); break; case MDOC_Pp: mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos, "sp after Pp"); roff_node_delete(man, n); break; default: break; } } /* $Id: st.c,v 1.16 2018/12/14 01:18:26 schwarze Exp $ */ /* * Copyright (c) 2009, 2010 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 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 "mandoc.h" #include "roff.h" #include "libmdoc.h" #define LINE(x, y) \ if (0 == strcmp(p, x)) return(y); const char * mdoc_a2st(const char *p) { LINE("-p1003.1-88", "IEEE Std 1003.1-1988 (\\(lqPOSIX.1\\(rq)") LINE("-p1003.1-90", "IEEE Std 1003.1-1990 (\\(lqPOSIX.1\\(rq)") LINE("-p1003.1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)") LINE("-p1003.1-2001", "IEEE Std 1003.1-2001 (\\(lqPOSIX.1\\(rq)") LINE("-p1003.1-2004", "IEEE Std 1003.1-2004 (\\(lqPOSIX.1\\(rq)") LINE("-p1003.1-2008", "IEEE Std 1003.1-2008 (\\(lqPOSIX.1\\(rq)") LINE("-p1003.1", "IEEE Std 1003.1 (\\(lqPOSIX.1\\(rq)") LINE("-p1003.1b", "IEEE Std 1003.1b (\\(lqPOSIX.1b\\(rq)") LINE("-p1003.1b-93", "IEEE Std 1003.1b-1993 (\\(lqPOSIX.1b\\(rq)") LINE("-p1003.1c-95", "IEEE Std 1003.1c-1995 (\\(lqPOSIX.1c\\(rq)") LINE("-p1003.1g-2000", "IEEE Std 1003.1g-2000 (\\(lqPOSIX.1g\\(rq)") LINE("-p1003.1i-95", "IEEE Std 1003.1i-1995 (\\(lqPOSIX.1i\\(rq)") LINE("-p1003.2", "IEEE Std 1003.2 (\\(lqPOSIX.2\\(rq)") LINE("-p1003.2-92", "IEEE Std 1003.2-1992 (\\(lqPOSIX.2\\(rq)") LINE("-p1003.2a-92", "IEEE Std 1003.2a-1992 (\\(lqPOSIX.2\\(rq)") LINE("-isoC", "ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)") LINE("-isoC-90", "ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)") LINE("-isoC-amd1", "ISO/IEC 9899/AMD1:1995 (\\(lqISO\\~C90, Amendment 1\\(rq)") LINE("-isoC-tcor1", "ISO/IEC 9899/TCOR1:1994 (\\(lqISO\\~C90, Technical Corrigendum 1\\(rq)") LINE("-isoC-tcor2", "ISO/IEC 9899/TCOR2:1995 (\\(lqISO\\~C90, Technical Corrigendum 2\\(rq)") LINE("-isoC-99", "ISO/IEC 9899:1999 (\\(lqISO\\~C99\\(rq)") LINE("-isoC-2011", "ISO/IEC 9899:2011 (\\(lqISO\\~C11\\(rq)") LINE("-iso9945-1-90", "ISO/IEC 9945-1:1990 (\\(lqPOSIX.1\\(rq)") LINE("-iso9945-1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)") LINE("-iso9945-2-93", "ISO/IEC 9945-2:1993 (\\(lqPOSIX.2\\(rq)") LINE("-ansiC", "ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)") LINE("-ansiC-89", "ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)") LINE("-ieee754", "IEEE Std 754-1985") LINE("-iso8802-3", "ISO 8802-3: 1989") LINE("-iso8601", "ISO 8601") LINE("-ieee1275-94", "IEEE Std 1275-1994 (\\(lqOpen Firmware\\(rq)") LINE("-xpg3", "X/Open Portability Guide Issue\\~3 (\\(lqXPG3\\(rq)") LINE("-xpg4", "X/Open Portability Guide Issue\\~4 (\\(lqXPG4\\(rq)") LINE("-xpg4.2", "X/Open Portability Guide Issue\\~4, Version\\~2 (\\(lqXPG4.2\\(rq)") LINE("-xbd5", "X/Open Base Definitions Issue\\~5 (\\(lqXBD5\\(rq)") LINE("-xcu5", "X/Open Commands and Utilities Issue\\~5 (\\(lqXCU5\\(rq)") LINE("-xsh4.2", "X/Open System Interfaces and Headers Issue\\~4, Version\\~2 (\\(lqXSH4.2\\(rq)") LINE("-xsh5", "X/Open System Interfaces and Headers Issue\\~5 (\\(lqXSH5\\(rq)") LINE("-xns5", "X/Open Networking Services Issue\\~5 (\\(lqXNS5\\(rq)") LINE("-xns5.2", "X/Open Networking Services Issue\\~5.2 (\\(lqXNS5.2\\(rq)") LINE("-xcurses4.2", "X/Open Curses Issue\\~4, Version\\~2 (\\(lqXCURSES4.2\\(rq)") LINE("-susv1", "Version\\~1 of the Single UNIX Specification (\\(lqSUSv1\\(rq)") LINE("-susv2", "Version\\~2 of the Single UNIX Specification (\\(lqSUSv2\\(rq)") LINE("-susv3", "Version\\~3 of the Single UNIX Specification (\\(lqSUSv3\\(rq)") LINE("-susv4", "Version\\~4 of the Single UNIX Specification (\\(lqSUSv4\\(rq)") LINE("-svid4", "System\\~V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)") return NULL; } /* $Id: tag.c,v 1.36 2020/04/19 16:36:16 schwarze Exp $ */ /* * Copyright (c) 2015,2016,2018,2019,2020 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. * * Functions to tag syntax tree nodes. * For internal use by mandoc(1) validation modules only. */ #include "config.h" #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "roff.h" #include "mdoc.h" #include "roff_int.h" #include "tag.h" struct tag_entry { struct roff_node **nodes; size_t maxnodes; size_t nnodes; int prio; char s[]; }; static void tag_move_href(struct roff_man *, struct roff_node *, const char *); static void tag_move_id(struct roff_node *); static struct ohash tag_data; /* * Set up the ohash table to collect nodes * where various marked-up terms are documented. */ void tag_alloc(void) { mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s)); } void tag_free(void) { struct tag_entry *entry; unsigned int slot; if (tag_data.info.free == NULL) return; entry = ohash_first(&tag_data, &slot); while (entry != NULL) { free(entry->nodes); free(entry); entry = ohash_next(&tag_data, &slot); } ohash_delete(&tag_data); tag_data.info.free = NULL; } /* * Set a node where a term is defined, * unless it is already defined at a lower priority. */ void tag_put(const char *s, int prio, struct roff_node *n) { struct tag_entry *entry; struct roff_node *nold; const char *se; size_t len; unsigned int slot; assert(prio <= TAG_FALLBACK); if (s == NULL) { if (n->child == NULL || n->child->type != ROFFT_TEXT) return; s = n->child->string; switch (s[0]) { case '-': s++; break; case '\\': switch (s[1]) { case '&': case '-': case 'e': s += 2; break; default: break; } break; default: break; } } /* * Skip whitespace and escapes and whatever follows, * and if there is any, downgrade the priority. */ len = strcspn(s, " \t\\"); if (len == 0) return; se = s + len; if (*se != '\0' && prio < TAG_WEAK) prio = TAG_WEAK; slot = ohash_qlookupi(&tag_data, s, &se); entry = ohash_find(&tag_data, slot); /* Build a new entry. */ if (entry == NULL) { entry = mandoc_malloc(sizeof(*entry) + len + 1); memcpy(entry->s, s, len); entry->s[len] = '\0'; entry->nodes = NULL; entry->maxnodes = entry->nnodes = 0; ohash_insert(&tag_data, slot, entry); } /* * Lower priority numbers take precedence. * If a better entry is already present, ignore the new one. */ else if (entry->prio < prio) return; /* * If the existing entry is worse, clear it. * In addition, a tag with priority TAG_FALLBACK * is only used if the tag occurs exactly once. */ else if (entry->prio > prio || prio == TAG_FALLBACK) { while (entry->nnodes > 0) { nold = entry->nodes[--entry->nnodes]; nold->flags &= ~NODE_ID; free(nold->tag); nold->tag = NULL; } if (prio == TAG_FALLBACK) { entry->prio = TAG_DELETE; return; } } /* Remember the new node. */ if (entry->maxnodes == entry->nnodes) { entry->maxnodes += 4; entry->nodes = mandoc_reallocarray(entry->nodes, entry->maxnodes, sizeof(*entry->nodes)); } entry->nodes[entry->nnodes++] = n; entry->prio = prio; n->flags |= NODE_ID; if (n->child == NULL || n->child->string != s || *se != '\0') { assert(n->tag == NULL); n->tag = mandoc_strndup(s, len); } } int tag_exists(const char *tag) { return ohash_find(&tag_data, ohash_qlookup(&tag_data, tag)) != NULL; } /* * For in-line elements, move the link target * to the enclosing paragraph when appropriate. */ static void tag_move_id(struct roff_node *n) { struct roff_node *np; np = n; for (;;) { if (np->prev != NULL) np = np->prev; else if ((np = np->parent) == NULL) return; switch (np->tok) { case MDOC_It: switch (np->parent->parent->norm->Bl.type) { case LIST_column: /* Target the ROFFT_BLOCK = . */ np = np->parent; break; case LIST_diag: case LIST_hang: case LIST_inset: case LIST_ohang: case LIST_tag: /* Target the ROFFT_HEAD =
. */ np = np->parent->head; break; default: /* Target the ROFF_BODY =
  • . */ break; } /* FALLTHROUGH */ case MDOC_Pp: /* Target the ROFFT_ELEM =

    . */ if (np->tag == NULL) { np->tag = mandoc_strdup(n->tag == NULL ? n->child->string : n->tag); np->flags |= NODE_ID; n->flags &= ~NODE_ID; } return; case MDOC_Sh: case MDOC_Ss: case MDOC_Bd: case MDOC_Bl: case MDOC_D1: case MDOC_Dl: case MDOC_Rs: /* Do not move past major blocks. */ return; default: /* * Move past in-line content and partial * blocks, for example .It Xo or .It Bq Er. */ break; } } } /* * When a paragraph is tagged and starts with text, * move the permalink to the first few words. */ static void tag_move_href(struct roff_man *man, struct roff_node *n, const char *tag) { char *cp; if (n == NULL || n->type != ROFFT_TEXT || *n->string == '\0' || *n->string == ' ') return; cp = n->string; while (cp != NULL && cp - n->string < 5) cp = strchr(cp + 1, ' '); /* If the first text node is longer, split it. */ if (cp != NULL && cp[1] != '\0') { man->last = n; man->next = ROFF_NEXT_SIBLING; roff_word_alloc(man, n->line, n->pos + (cp - n->string), cp + 1); man->last->flags = n->flags & ~NODE_LINE; *cp = '\0'; } assert(n->tag == NULL); n->tag = mandoc_strdup(tag); n->flags |= NODE_HREF; } /* * When all tags have been set, decide where to put * the associated permalinks, and maybe move some tags * to the beginning of the respective paragraphs. */ void tag_postprocess(struct roff_man *man, struct roff_node *n) { if (n->flags & NODE_ID) { switch (n->tok) { case MDOC_Pp: tag_move_href(man, n->next, n->tag); break; case MDOC_Bd: case MDOC_D1: case MDOC_Dl: tag_move_href(man, n->child, n->tag); break; case MDOC_Bl: /* XXX No permalink for now. */ break; default: if (n->type == ROFFT_ELEM || n->tok == MDOC_Fo) tag_move_id(n); if (n->tok != MDOC_Tg) n->flags |= NODE_HREF; else if ((n->flags & NODE_ID) == 0) { n->flags |= NODE_NOPRT; free(n->tag); n->tag = NULL; } break; } } for (n = n->child; n != NULL; n = n->next) tag_postprocess(man, n); } /* $Id: tag.h,v 1.14 2020/04/18 20:40:10 schwarze Exp $ */ /* * Copyright (c) 2015, 2018, 2019, 2020 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. * * Internal interfaces to tag syntax tree nodes. * For use by mandoc(1) validation modules only. */ /* * Tagging priorities. * Lower numbers indicate higher importance. */ #define TAG_MANUAL 1 /* Set with a .Tg macro. */ #define TAG_STRONG 2 /* Good automatic tagging. */ #define TAG_WEAK (INT_MAX - 2) /* Dubious automatic tagging. */ #define TAG_FALLBACK (INT_MAX - 1) /* Tag only used if unique. */ #define TAG_DELETE (INT_MAX) /* Tag not used at all. */ void tag_alloc(void); int tag_exists(const char *); void tag_put(const char *, int, struct roff_node *); void tag_postprocess(struct roff_man *, struct roff_node *); void tag_free(void); /* $Id: tbl.c,v 1.46 2018/12/14 06:33:14 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2011, 2015 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_aux.h" #include "mandoc.h" #include "tbl.h" #include "libmandoc.h" #include "tbl_parse.h" #include "tbl_int.h" void tbl_read(struct tbl_node *tbl, int ln, const char *p, int pos) { const char *cp; int active; /* * In the options section, proceed to the layout section * after a semicolon, or right away if there is no semicolon. * Ignore semicolons in arguments. */ if (tbl->part == TBL_PART_OPTS) { tbl->part = TBL_PART_LAYOUT; active = 1; for (cp = p + pos; *cp != '\0'; cp++) { switch (*cp) { case '(': active = 0; continue; case ')': active = 1; continue; case ';': if (active) break; continue; default: continue; } break; } if (*cp == ';') { tbl_option(tbl, ln, p, &pos); if (p[pos] == '\0') return; } } /* Process the other section types. */ switch (tbl->part) { case TBL_PART_LAYOUT: tbl_layout(tbl, ln, p, pos); break; case TBL_PART_CDATA: tbl_cdata(tbl, ln, p, pos); break; default: tbl_data(tbl, ln, p, pos); break; } } struct tbl_node * tbl_alloc(int pos, int line, struct tbl_node *last_tbl) { struct tbl_node *tbl; tbl = mandoc_calloc(1, sizeof(*tbl)); if (last_tbl != NULL) last_tbl->next = tbl; tbl->line = line; tbl->pos = pos; tbl->part = TBL_PART_OPTS; tbl->opts.tab = '\t'; tbl->opts.decimal = '.'; return tbl; } void tbl_free(struct tbl_node *tbl) { struct tbl_node *old_tbl; struct tbl_row *rp; struct tbl_cell *cp; struct tbl_span *sp; struct tbl_dat *dp; while (tbl != NULL) { while ((rp = tbl->first_row) != NULL) { tbl->first_row = rp->next; while (rp->first != NULL) { cp = rp->first; rp->first = cp->next; free(cp->wstr); free(cp); } free(rp); } while ((sp = tbl->first_span) != NULL) { tbl->first_span = sp->next; while (sp->first != NULL) { dp = sp->first; sp->first = dp->next; free(dp->string); free(dp); } free(sp); } old_tbl = tbl; tbl = tbl->next; free(old_tbl); } } void tbl_restart(int line, int pos, struct tbl_node *tbl) { if (tbl->part == TBL_PART_CDATA) mandoc_msg(MANDOCERR_TBLDATA_BLK, line, pos, "T&"); tbl->part = TBL_PART_LAYOUT; tbl->line = line; tbl->pos = pos; } struct tbl_span * tbl_span(struct tbl_node *tbl) { struct tbl_span *span; span = tbl->current_span ? tbl->current_span->next : tbl->first_span; if (span != NULL) tbl->current_span = span; return span; } int tbl_end(struct tbl_node *tbl, int still_open) { struct tbl_span *sp; if (still_open) mandoc_msg(MANDOCERR_BLK_NOEND, tbl->line, tbl->pos, "TS"); else if (tbl->part == TBL_PART_CDATA) mandoc_msg(MANDOCERR_TBLDATA_BLK, tbl->line, tbl->pos, "TE"); sp = tbl->first_span; while (sp != NULL && sp->first == NULL) sp = sp->next; if (sp == NULL) { mandoc_msg(MANDOCERR_TBLDATA_NONE, tbl->line, tbl->pos, NULL); return 0; } return 1; } /* $Id: tbl.h,v 1.2 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2010, 2011 Kristaps Dzonsons * Copyright (c) 2014,2015,2017,2018,2021 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. */ struct tbl_opts { int opts; #define TBL_OPT_ALLBOX (1 << 0) /* Option "allbox". */ #define TBL_OPT_BOX (1 << 1) /* Option "box". */ #define TBL_OPT_CENTRE (1 << 2) /* Option "center". */ #define TBL_OPT_DBOX (1 << 3) /* Option "doublebox". */ #define TBL_OPT_EXPAND (1 << 4) /* Option "expand". */ #define TBL_OPT_NOKEEP (1 << 5) /* Option "nokeep". */ #define TBL_OPT_NOSPACE (1 << 6) /* Option "nospaces". */ #define TBL_OPT_NOWARN (1 << 7) /* Option "nowarn". */ int cols; /* Number of columns. */ int lvert; /* Width of left vertical line. */ int rvert; /* Width of right vertical line. */ char tab; /* Option "tab": cell separator. */ char decimal; /* Option "decimalpoint". */ }; enum tbl_cellt { TBL_CELL_CENTRE, /* c, C */ TBL_CELL_RIGHT, /* r, R */ TBL_CELL_LEFT, /* l, L */ TBL_CELL_NUMBER, /* n, N */ TBL_CELL_SPAN, /* s, S */ TBL_CELL_LONG, /* a, A */ TBL_CELL_DOWN, /* ^ */ TBL_CELL_HORIZ, /* _, - */ TBL_CELL_DHORIZ, /* = */ TBL_CELL_MAX }; /* * A cell in a layout row. */ struct tbl_cell { struct tbl_cell *next; /* Layout cell to the right. */ char *wstr; /* Min width represented as a string. */ size_t width; /* Minimum column width. */ size_t spacing; /* To the right of the column. */ int vert; /* Width of subsequent vertical line. */ int col; /* Column number, starting from 0. */ int flags; #define TBL_CELL_TALIGN (1 << 2) /* t, T */ #define TBL_CELL_UP (1 << 3) /* u, U */ #define TBL_CELL_BALIGN (1 << 4) /* d, D */ #define TBL_CELL_WIGN (1 << 5) /* z, Z */ #define TBL_CELL_EQUAL (1 << 6) /* e, E */ #define TBL_CELL_WMAX (1 << 7) /* x, X */ enum mandoc_esc font; enum tbl_cellt pos; }; /* * A layout row. */ struct tbl_row { struct tbl_row *next; /* Layout row below. */ struct tbl_cell *first; /* Leftmost layout cell. */ struct tbl_cell *last; /* Rightmost layout cell. */ int vert; /* Width of left vertical line. */ }; enum tbl_datt { TBL_DATA_NONE, /* Uninitialized row. */ TBL_DATA_DATA, /* Contains data rather than a line. */ TBL_DATA_HORIZ, /* _: connecting horizontal line. */ TBL_DATA_DHORIZ, /* =: connecting double horizontal line. */ TBL_DATA_NHORIZ, /* \_: isolated horizontal line. */ TBL_DATA_NDHORIZ /* \=: isolated double horizontal line. */ }; /* * A cell within a row of data. The "string" field contains the * actual string value that's in the cell. The rest is layout. */ struct tbl_dat { struct tbl_dat *next; /* Data cell to the right. */ struct tbl_cell *layout; /* Associated layout cell. */ char *string; /* Data, or NULL if not TBL_DATA_DATA. */ int hspans; /* How many horizontal spans follow. */ int vspans; /* How many vertical spans follow. */ int block; /* T{ text block T} */ enum tbl_datt pos; }; enum tbl_spant { TBL_SPAN_DATA, /* Contains data rather than a line. */ TBL_SPAN_HORIZ, /* _: horizontal line. */ TBL_SPAN_DHORIZ /* =: double horizontal line. */ }; /* * A row of data in a table. */ struct tbl_span { struct tbl_opts *opts; /* Options for the table as a whole. */ struct tbl_span *prev; /* Data row above. */ struct tbl_span *next; /* Data row below. */ struct tbl_row *layout; /* Associated layout row. */ struct tbl_dat *first; /* Leftmost data cell. */ struct tbl_dat *last; /* Rightmost data cell. */ int line; /* Input file line number. */ enum tbl_spant pos; }; /* $Id: tbl_data.c,v 1.59 2021/09/10 13:24:38 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2011,2015,2017-2019,2021 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 #include "mandoc_aux.h" #include "mandoc.h" #include "tbl.h" #include "libmandoc.h" #include "tbl_int.h" static void getdata(struct tbl_node *, struct tbl_span *, int, const char *, int *); static struct tbl_span *newspan(struct tbl_node *, int, struct tbl_row *); static void getdata(struct tbl_node *tbl, struct tbl_span *dp, int ln, const char *p, int *pos) { struct tbl_dat *dat, *pdat; struct tbl_cell *cp; struct tbl_span *pdp; const char *ccp; int startpos, endpos; /* * Determine the length of the string in the cell * and advance the parse point to the end of the cell. */ startpos = *pos; ccp = p + startpos; while (*ccp != '\0' && *ccp != tbl->opts.tab) if (*ccp++ == '\\') mandoc_escape(&ccp, NULL, NULL); *pos = ccp - p; /* Advance to the next layout cell, skipping spanners. */ cp = dp->last == NULL ? dp->layout->first : dp->last->layout->next; while (cp != NULL && cp->pos == TBL_CELL_SPAN) cp = cp->next; /* * If the current layout row is out of cells, allocate * a new cell if another row of the table has at least * this number of columns, or discard the input if we * are beyond the last column of the table as a whole. */ if (cp == NULL) { if (dp->layout->last->col + 1 < dp->opts->cols) { cp = mandoc_calloc(1, sizeof(*cp)); cp->pos = TBL_CELL_LEFT; cp->font = ESCAPE_FONTROMAN; cp->spacing = SIZE_MAX; dp->layout->last->next = cp; cp->col = dp->layout->last->col + 1; dp->layout->last = cp; } else { mandoc_msg(MANDOCERR_TBLDATA_EXTRA, ln, startpos, "%s", p + startpos); while (p[*pos] != '\0') (*pos)++; return; } } dat = mandoc_malloc(sizeof(*dat)); dat->layout = cp; dat->next = NULL; dat->string = NULL; dat->hspans = 0; dat->vspans = 0; dat->block = 0; dat->pos = TBL_DATA_NONE; /* * Increment the number of vertical spans in a data cell above, * if this cell vertically extends one or more cells above. * The iteration must be done over data rows, * not over layout rows, because one layout row * can be reused for more than one data row. */ if (cp->pos == TBL_CELL_DOWN || (*pos - startpos == 2 && p[startpos] == '\\' && p[startpos + 1] == '^')) { pdp = dp; while ((pdp = pdp->prev) != NULL) { pdat = pdp->first; while (pdat != NULL && pdat->layout->col < dat->layout->col) pdat = pdat->next; if (pdat == NULL) break; if (pdat->layout->pos != TBL_CELL_DOWN && strcmp(pdat->string, "\\^") != 0) { pdat->vspans++; break; } } } /* * Count the number of horizontal spans to the right of this cell. * This is purely a matter of the layout, independent of the data. */ for (cp = cp->next; cp != NULL; cp = cp->next) if (cp->pos == TBL_CELL_SPAN) dat->hspans++; else break; /* Append the new data cell to the data row. */ if (dp->last == NULL) dp->first = dat; else dp->last->next = dat; dp->last = dat; /* Strip leading and trailing spaces, if requested. */ endpos = *pos; if (dp->opts->opts & TBL_OPT_NOSPACE) { while (p[startpos] == ' ') startpos++; while (endpos > startpos && p[endpos - 1] == ' ') endpos--; } /* * Check for a continued-data scope opening. This consists of a * trailing `T{' at the end of the line. Subsequent lines, * until a standalone `T}', are included in our cell. */ if (endpos - startpos == 2 && p[startpos] == 'T' && p[startpos + 1] == '{') { tbl->part = TBL_PART_CDATA; return; } dat->string = mandoc_strndup(p + startpos, endpos - startpos); if (p[*pos] != '\0') (*pos)++; if ( ! strcmp(dat->string, "_")) dat->pos = TBL_DATA_HORIZ; else if ( ! strcmp(dat->string, "=")) dat->pos = TBL_DATA_DHORIZ; else if ( ! strcmp(dat->string, "\\_")) dat->pos = TBL_DATA_NHORIZ; else if ( ! strcmp(dat->string, "\\=")) dat->pos = TBL_DATA_NDHORIZ; else dat->pos = TBL_DATA_DATA; if ((dat->layout->pos == TBL_CELL_HORIZ || dat->layout->pos == TBL_CELL_DHORIZ || dat->layout->pos == TBL_CELL_DOWN) && dat->pos == TBL_DATA_DATA && *dat->string != '\0') mandoc_msg(MANDOCERR_TBLDATA_SPAN, ln, startpos, "%s", dat->string); } void tbl_cdata(struct tbl_node *tbl, int ln, const char *p, int pos) { struct tbl_dat *dat; size_t sz; dat = tbl->last_span->last; if (p[pos] == 'T' && p[pos + 1] == '}') { pos += 2; if (tbl->opts.opts & TBL_OPT_NOSPACE) while (p[pos] == ' ') pos++; if (p[pos] == tbl->opts.tab) { tbl->part = TBL_PART_DATA; pos++; while (p[pos] != '\0') getdata(tbl, tbl->last_span, ln, p, &pos); return; } else if (p[pos] == '\0') { tbl->part = TBL_PART_DATA; return; } /* Fallthrough: T} is part of a word. */ } dat->pos = TBL_DATA_DATA; dat->block = 1; if (dat->string != NULL) { sz = strlen(p + pos) + strlen(dat->string) + 2; dat->string = mandoc_realloc(dat->string, sz); (void)strlcat(dat->string, " ", sz); (void)strlcat(dat->string, p + pos, sz); } else dat->string = mandoc_strdup(p + pos); if (dat->layout->pos == TBL_CELL_DOWN) mandoc_msg(MANDOCERR_TBLDATA_SPAN, ln, pos, "%s", dat->string); } static struct tbl_span * newspan(struct tbl_node *tbl, int line, struct tbl_row *rp) { struct tbl_span *dp; dp = mandoc_calloc(1, sizeof(*dp)); dp->line = line; dp->opts = &tbl->opts; dp->layout = rp; dp->prev = tbl->last_span; if (dp->prev == NULL) { tbl->first_span = dp; tbl->current_span = NULL; } else dp->prev->next = dp; tbl->last_span = dp; return dp; } void tbl_data(struct tbl_node *tbl, int ln, const char *p, int pos) { struct tbl_row *rp; struct tbl_cell *cp; struct tbl_span *sp; for (sp = tbl->last_span; sp != NULL; sp = sp->prev) if (sp->pos == TBL_SPAN_DATA) break; rp = sp == NULL ? tbl->first_row : sp->layout->next == NULL ? sp->layout : sp->layout->next; assert(rp != NULL); if (p[1] == '\0') { switch (p[0]) { case '.': /* * Empty request lines must be handled here * and cannot be discarded in roff_parseln() * because in the layout section, they * are significant and end the layout. */ return; case '_': sp = newspan(tbl, ln, rp); sp->pos = TBL_SPAN_HORIZ; return; case '=': sp = newspan(tbl, ln, rp); sp->pos = TBL_SPAN_DHORIZ; return; default: break; } } /* * If the layout row contains nothing but horizontal lines, * allocate an empty span for it and assign the current span * to the next layout row accepting data. */ while (rp->next != NULL) { if (rp->last->col + 1 < tbl->opts.cols) break; for (cp = rp->first; cp != NULL; cp = cp->next) if (cp->pos != TBL_CELL_HORIZ && cp->pos != TBL_CELL_DHORIZ) break; if (cp != NULL) break; sp = newspan(tbl, ln, rp); sp->pos = TBL_SPAN_DATA; rp = rp->next; } /* Process a real data row. */ sp = newspan(tbl, ln, rp); sp->pos = TBL_SPAN_DATA; while (p[pos] != '\0') getdata(tbl, sp, ln, p, &pos); } /* $Id: tbl_html.c,v 1.38 2021/09/09 16:52:52 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons * Copyright (c) 2014,2015,2017,2018,2021 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; const struct roffcol *col; struct tag *tt; const char *hspans, *vspans, *halign, *valign; const char *bborder, *lborder, *rborder; const char *ccp; char hbuf[4], vbuf[4]; size_t sz; enum mandoc_esc save_font; 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->layout->pos == TBL_CELL_HORIZ || dp->layout->pos == TBL_CELL_DHORIZ || dp->pos == TBL_DATA_HORIZ || dp->pos == TBL_DATA_DHORIZ) print_otag(h, TAG_HR, ""); else if (dp->string != NULL) { save_font = h->metac; html_setfont(h, dp->layout->font); if (dp->layout->pos == TBL_CELL_LONG) print_text(h, "\\[u2003]"); /* em space */ print_text(h, dp->string); if (dp->layout->pos == TBL_CELL_NUMBER) { col = h->tbl.cols + dp->layout->col; if (col->decimal < col->nwidth) { if ((ccp = strrchr(dp->string, sp->opts->decimal)) == NULL) { /* Punctuation space. */ print_text(h, "\\[u2008]"); ccp = strchr(dp->string, '\0'); } else ccp++; sz = col->nwidth - col->decimal; while (--sz > 0) { if (*ccp == '\0') /* Figure space. */ print_text(h, "\\[u2007]"); else ccp++; } } } html_setfont(h, save_font); } } 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); } } /* $Id: tbl_int.h,v 1.2 2018/12/14 06:33:14 schwarze Exp $ */ /* * Copyright (c) 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 AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Internal interfaces of the tbl(7) parser. * For use inside the tbl(7) parser only. */ enum tbl_part { TBL_PART_OPTS, /* In the first line, ends with semicolon. */ TBL_PART_LAYOUT, /* In the layout section, ends with full stop. */ TBL_PART_DATA, /* In the data section, ends with TE. */ TBL_PART_CDATA /* In a T{ block, ends with T} */ }; struct tbl_node { struct tbl_opts opts; /* Options for the whole table. */ struct tbl_node *next; /* Next table. */ struct tbl_row *first_row; /* First layout row. */ struct tbl_row *last_row; /* Last layout row. */ struct tbl_span *first_span; /* First data row. */ struct tbl_span *current_span; /* Data row being parsed. */ struct tbl_span *last_span; /* Last data row. */ int line; /* Line number in input file. */ int pos; /* Column number in input file. */ enum tbl_part part; /* Table section being parsed. */ }; void tbl_option(struct tbl_node *, int, const char *, int *); void tbl_layout(struct tbl_node *, int, const char *, int); void tbl_data(struct tbl_node *, int, const char *, int); void tbl_cdata(struct tbl_node *, int, const char *, int); void tbl_reset(struct tbl_node *); /* $Id: tbl_layout.c,v 1.50 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2012, 2014, 2015, 2017, 2020, 2021 * 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 "mandoc.h" #include "tbl.h" #include "libmandoc.h" #include "tbl_int.h" struct tbl_phrase { char name; enum tbl_cellt key; }; static const struct tbl_phrase keys[] = { { 'c', TBL_CELL_CENTRE }, { 'r', TBL_CELL_RIGHT }, { 'l', TBL_CELL_LEFT }, { 'n', TBL_CELL_NUMBER }, { 's', TBL_CELL_SPAN }, { 'a', TBL_CELL_LONG }, { '^', TBL_CELL_DOWN }, { '-', TBL_CELL_HORIZ }, { '_', TBL_CELL_HORIZ }, { '=', TBL_CELL_DHORIZ } }; #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0]))) static void mods(struct tbl_node *, struct tbl_cell *, int, const char *, int *); static void cell(struct tbl_node *, struct tbl_row *, int, const char *, int *); static struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *, enum tbl_cellt); static void mods(struct tbl_node *tbl, struct tbl_cell *cp, int ln, const char *p, int *pos) { char *endptr; unsigned long spacing; size_t sz; int isz; enum mandoc_esc fontesc; mod: while (p[*pos] == ' ' || p[*pos] == '\t') (*pos)++; /* Row delimiters and cell specifiers end modifier lists. */ if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL) return; /* Throw away parenthesised expression. */ if ('(' == p[*pos]) { (*pos)++; while (p[*pos] && ')' != p[*pos]) (*pos)++; if (')' == p[*pos]) { (*pos)++; goto mod; } mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL); return; } /* Parse numerical spacing from modifier string. */ if (isdigit((unsigned char)p[*pos])) { if ((spacing = strtoul(p + *pos, &endptr, 10)) > 9) mandoc_msg(MANDOCERR_TBLLAYOUT_SPC, ln, *pos, "%lu", spacing); else cp->spacing = spacing; *pos = endptr - p; goto mod; } switch (tolower((unsigned char)p[(*pos)++])) { case 'b': cp->font = ESCAPE_FONTBOLD; goto mod; case 'd': cp->flags |= TBL_CELL_BALIGN; goto mod; case 'e': cp->flags |= TBL_CELL_EQUAL; goto mod; case 'f': break; case 'i': cp->font = ESCAPE_FONTITALIC; goto mod; case 'm': mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m"); goto mod; case 'p': case 'v': if (p[*pos] == '-' || p[*pos] == '+') (*pos)++; while (isdigit((unsigned char)p[*pos])) (*pos)++; goto mod; case 't': cp->flags |= TBL_CELL_TALIGN; goto mod; case 'u': cp->flags |= TBL_CELL_UP; goto mod; case 'w': sz = 0; if (p[*pos] == '(') { (*pos)++; while (p[*pos + sz] != '\0' && p[*pos + sz] != ')') sz++; } else while (isdigit((unsigned char)p[*pos + sz])) sz++; if (sz) { free(cp->wstr); cp->wstr = mandoc_strndup(p + *pos, sz); *pos += sz; if (p[*pos] == ')') (*pos)++; } goto mod; case 'x': cp->flags |= TBL_CELL_WMAX; goto mod; case 'z': cp->flags |= TBL_CELL_WIGN; goto mod; case '|': if (cp->vert < 2) cp->vert++; else mandoc_msg(MANDOCERR_TBLLAYOUT_VERT, ln, *pos - 1, NULL); goto mod; default: mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR, ln, *pos - 1, "%c", p[*pos - 1]); goto mod; } while (p[*pos] == ' ' || p[*pos] == '\t') (*pos)++; /* Ignore parenthised font names for now. */ if (p[*pos] == '(') goto mod; isz = 0; if (p[*pos] != '\0') isz++; if (strchr(" \t.", p[*pos + isz]) == NULL) isz++; fontesc = mandoc_font(p + *pos, isz); switch (fontesc) { case ESCAPE_FONTPREV: case ESCAPE_ERROR: mandoc_msg(MANDOCERR_FT_BAD, ln, *pos, "TS %s", p + *pos - 1); break; default: cp->font = fontesc; break; } *pos += isz; goto mod; } static void cell(struct tbl_node *tbl, struct tbl_row *rp, int ln, const char *p, int *pos) { int i; enum tbl_cellt c; /* Handle leading vertical lines */ while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') { if (p[*pos] == '|') { if (rp->vert < 2) rp->vert++; else mandoc_msg(MANDOCERR_TBLLAYOUT_VERT, ln, *pos, NULL); } (*pos)++; } again: while (p[*pos] == ' ' || p[*pos] == '\t') (*pos)++; if (p[*pos] == '.' || p[*pos] == '\0') return; /* Parse the column position (`c', `l', `r', ...). */ for (i = 0; i < KEYS_MAX; i++) if (tolower((unsigned char)p[*pos]) == keys[i].name) break; if (i == KEYS_MAX) { mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR, ln, *pos, "%c", p[*pos]); (*pos)++; goto again; } c = keys[i].key; /* Special cases of spanners. */ if (c == TBL_CELL_SPAN) { if (rp->last == NULL) mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL); else if (rp->last->pos == TBL_CELL_HORIZ || rp->last->pos == TBL_CELL_DHORIZ) c = rp->last->pos; } else if (c == TBL_CELL_DOWN && rp == tbl->first_row) mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL); (*pos)++; /* Allocate cell then parse its modifiers. */ mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos); } void tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos) { struct tbl_row *rp; rp = NULL; for (;;) { /* Skip whitespace before and after each cell. */ while (p[pos] == ' ' || p[pos] == '\t') pos++; switch (p[pos]) { case ',': /* Next row on this input line. */ pos++; rp = NULL; continue; case '\0': /* Next row on next input line. */ return; case '.': /* End of layout. */ pos++; tbl->part = TBL_PART_DATA; /* * When the layout is completely empty, * default to one left-justified column. */ if (tbl->first_row == NULL) { tbl->first_row = tbl->last_row = mandoc_calloc(1, sizeof(*rp)); } if (tbl->first_row->first == NULL) { mandoc_msg(MANDOCERR_TBLLAYOUT_NONE, ln, pos, NULL); cell_alloc(tbl, tbl->first_row, TBL_CELL_LEFT); if (tbl->opts.lvert < tbl->first_row->vert) tbl->opts.lvert = tbl->first_row->vert; return; } /* * Search for the widest line * along the left and right margins. */ for (rp = tbl->first_row; rp; rp = rp->next) { if (tbl->opts.lvert < rp->vert) tbl->opts.lvert = rp->vert; if (rp->last != NULL && rp->last->col + 1 == tbl->opts.cols && tbl->opts.rvert < rp->last->vert) tbl->opts.rvert = rp->last->vert; /* If the last line is empty, drop it. */ if (rp->next != NULL && rp->next->first == NULL) { free(rp->next); rp->next = NULL; tbl->last_row = rp; } } return; default: /* Cell. */ break; } /* * If the last line had at least one cell, * start a new one; otherwise, continue it. */ if (rp == NULL) { if (tbl->last_row == NULL || tbl->last_row->first != NULL) { rp = mandoc_calloc(1, sizeof(*rp)); if (tbl->last_row) tbl->last_row->next = rp; else tbl->first_row = rp; tbl->last_row = rp; } else rp = tbl->last_row; } cell(tbl, rp, ln, p, &pos); } } static struct tbl_cell * cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos) { struct tbl_cell *p, *pp; p = mandoc_calloc(1, sizeof(*p)); p->spacing = SIZE_MAX; p->font = ESCAPE_FONTROMAN; p->pos = pos; if ((pp = rp->last) != NULL) { pp->next = p; p->col = pp->col + 1; } else rp->first = p; rp->last = p; if (tbl->opts.cols <= p->col) tbl->opts.cols = p->col + 1; return p; } /* $Id: tbl_opts.c,v 1.24 2018/12/14 05:18:03 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2015 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 "tbl.h" #include "libmandoc.h" #include "tbl_int.h" #define KEY_DPOINT 0 #define KEY_DELIM 1 #define KEY_LINESIZE 2 #define KEY_TAB 3 struct tbl_phrase { const char *name; int key; }; static const struct tbl_phrase keys[] = { {"decimalpoint", 0}, {"delim", 0}, {"linesize", 0}, {"tab", 0}, {"allbox", TBL_OPT_ALLBOX | TBL_OPT_BOX}, {"box", TBL_OPT_BOX}, {"frame", TBL_OPT_BOX}, {"center", TBL_OPT_CENTRE}, {"centre", TBL_OPT_CENTRE}, {"doublebox", TBL_OPT_DBOX}, {"doubleframe", TBL_OPT_DBOX}, {"expand", TBL_OPT_EXPAND}, {"nokeep", TBL_OPT_NOKEEP}, {"nospaces", TBL_OPT_NOSPACE}, {"nowarn", TBL_OPT_NOWARN}, }; #define KEY_MAXKEYS ((int)(sizeof(keys)/sizeof(keys[0]))) static void arg(struct tbl_node *, int, const char *, int *, int); static void arg(struct tbl_node *tbl, int ln, const char *p, int *pos, int key) { int len, want; while (p[*pos] == ' ' || p[*pos] == '\t') (*pos)++; /* Arguments are enclosed in parentheses. */ len = 0; if (p[*pos] == '(') { (*pos)++; while (p[*pos + len] != ')') len++; } switch (key) { case KEY_DELIM: mandoc_msg(MANDOCERR_TBLOPT_EQN, ln, *pos, "%.*s", len, p + *pos); want = 2; break; case KEY_TAB: want = 1; if (len == want) tbl->opts.tab = p[*pos]; break; case KEY_LINESIZE: want = 0; break; case KEY_DPOINT: want = 1; if (len == want) tbl->opts.decimal = p[*pos]; break; default: abort(); } if (len == 0) mandoc_msg(MANDOCERR_TBLOPT_NOARG, ln, *pos, "%s", keys[key].name); else if (want && len != want) mandoc_msg(MANDOCERR_TBLOPT_ARGSZ, ln, *pos, "%s want %d have %d", keys[key].name, want, len); *pos += len; if (p[*pos] == ')') (*pos)++; } /* * Parse one line of options up to the semicolon. * Each option can be preceded by blanks and/or commas, * and some options are followed by arguments. */ void tbl_option(struct tbl_node *tbl, int ln, const char *p, int *offs) { int i, pos, len; pos = *offs; for (;;) { while (p[pos] == ' ' || p[pos] == '\t' || p[pos] == ',') pos++; if (p[pos] == ';') { *offs = pos + 1; return; } /* Parse one option name. */ len = 0; while (isalpha((unsigned char)p[pos + len])) len++; if (len == 0) { mandoc_msg(MANDOCERR_TBLOPT_ALPHA, ln, pos, "%c", p[pos]); pos++; continue; } /* Look up the option name. */ i = 0; while (i < KEY_MAXKEYS && (strncasecmp(p + pos, keys[i].name, len) || keys[i].name[len] != '\0')) i++; if (i == KEY_MAXKEYS) { mandoc_msg(MANDOCERR_TBLOPT_BAD, ln, pos, "%.*s", len, p + pos); pos += len; continue; } /* Handle the option. */ pos += len; if (keys[i].key) tbl->opts.opts |= keys[i].key; else arg(tbl, ln, p, &pos, i); } } /* $Id: tbl_parse.h,v 1.2 2018/12/14 06:33:14 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons * Copyright (c) 2011, 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 AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * External interface of the tbl(7) parser. * For use in the roff(7) and tbl(7) parsers only. */ struct tbl_node; struct tbl_span; struct tbl_node *tbl_alloc(int, int, struct tbl_node *); int tbl_end(struct tbl_node *, int); void tbl_free(struct tbl_node *); void tbl_read(struct tbl_node *, int, const char *, int); void tbl_restart(int, int, struct tbl_node *); struct tbl_span *tbl_span(struct tbl_node *); /* $Id: tbl_term.c,v 1.75 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2009, 2011 Kristaps Dzonsons * Copyright (c) 2011-2021 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 *, const struct tbl_span *, int); static void tbl_literal(struct termp *, const struct tbl_dat *, const struct roffcol *); static void tbl_number(struct termp *, const struct tbl_opts *, const struct tbl_dat *, const struct roffcol *); static void tbl_word(struct termp *, const struct tbl_dat *); /* * The following border-character tables are indexed * by ternary (3-based) numbers, as opposed to binary or decimal. * Each ternary digit describes the line width in one direction: * 0 means no line, 1 single or light line, 2 double or heavy line. */ /* Positional values of the four directions. */ #define BRIGHT 1 #define BDOWN 3 #define BLEFT (3 * 3) #define BUP (3 * 3 * 3) #define BHORIZ (BLEFT + BRIGHT) /* Code points to use for each combination of widths. */ static const int borders_utf8[81] = { 0x0020, 0x2576, 0x257a, /* 000 right */ 0x2577, 0x250c, 0x250d, /* 001 down */ 0x257b, 0x250e, 0x250f, /* 002 */ 0x2574, 0x2500, 0x257c, /* 010 left */ 0x2510, 0x252c, 0x252e, /* 011 left down */ 0x2512, 0x2530, 0x2532, /* 012 */ 0x2578, 0x257e, 0x2501, /* 020 left */ 0x2511, 0x252d, 0x252f, /* 021 left down */ 0x2513, 0x2531, 0x2533, /* 022 */ 0x2575, 0x2514, 0x2515, /* 100 up */ 0x2502, 0x251c, 0x251d, /* 101 up down */ 0x257d, 0x251f, 0x2522, /* 102 */ 0x2518, 0x2534, 0x2536, /* 110 up left */ 0x2524, 0x253c, 0x253e, /* 111 all */ 0x2527, 0x2541, 0x2546, /* 112 */ 0x2519, 0x2535, 0x2537, /* 120 up left */ 0x2525, 0x253d, 0x253f, /* 121 all */ 0x252a, 0x2545, 0x2548, /* 122 */ 0x2579, 0x2516, 0x2517, /* 200 up */ 0x257f, 0x251e, 0x2521, /* 201 up down */ 0x2503, 0x2520, 0x2523, /* 202 */ 0x251a, 0x2538, 0x253a, /* 210 up left */ 0x2526, 0x2540, 0x2544, /* 211 all */ 0x2528, 0x2542, 0x254a, /* 212 */ 0x251b, 0x2539, 0x253b, /* 220 up left */ 0x2529, 0x2543, 0x2547, /* 221 all */ 0x252b, 0x2549, 0x254b, /* 222 */ }; /* ASCII approximations for these code points, compatible with groff. */ static const int borders_ascii[81] = { ' ', '-', '=', /* 000 right */ '|', '+', '+', /* 001 down */ '|', '+', '+', /* 002 */ '-', '-', '=', /* 010 left */ '+', '+', '+', /* 011 left down */ '+', '+', '+', /* 012 */ '=', '=', '=', /* 020 left */ '+', '+', '+', /* 021 left down */ '+', '+', '+', /* 022 */ '|', '+', '+', /* 100 up */ '|', '+', '+', /* 101 up down */ '|', '+', '+', /* 102 */ '+', '+', '+', /* 110 up left */ '+', '+', '+', /* 111 all */ '+', '+', '+', /* 112 */ '+', '+', '+', /* 120 up left */ '+', '+', '+', /* 121 all */ '+', '+', '+', /* 122 */ '|', '+', '+', /* 200 up */ '|', '+', '+', /* 201 up down */ '|', '+', '+', /* 202 */ '+', '+', '+', /* 210 up left */ '+', '+', '+', /* 211 all */ '+', '+', '+', /* 212 */ '+', '+', '+', /* 220 up left */ '+', '+', '+', /* 221 all */ '+', '+', '+', /* 222 */ }; /* Either of the above according to the selected output encoding. */ static const int *borders_locale; static size_t term_tbl_sulen(const struct roffsu *su, void *arg) { int i; i = term_hen((const struct termp *)arg, su); return i > 0 ? i : 0; } static size_t term_tbl_strlen(const char *p, void *arg) { return term_strlen((const struct termp *)arg, p); } static size_t term_tbl_len(size_t sz, void *arg) { return term_len((const struct termp *)arg, sz); } void term_tbl(struct termp *tp, const struct tbl_span *sp) { const struct tbl_cell *cp, *cpn, *cpp, *cps; const struct tbl_dat *dp; static size_t offset; size_t save_offset; size_t coloff, tsz; int hspans, ic, more; int dvert, fc, horiz, lhori, rhori, uvert; /* Inhibit printing of spaces: we do padding ourselves. */ tp->flags |= TERMP_NOSPACE | TERMP_NONOSPACE; save_offset = tp->tcol->offset; /* * The first time we're invoked for a given table block, * calculate the table widths and decimal positions. */ if (tp->tbl.cols == NULL) { borders_locale = tp->enc == TERMENC_UTF8 ? borders_utf8 : borders_ascii; tp->tbl.len = term_tbl_len; tp->tbl.slen = term_tbl_strlen; tp->tbl.sulen = term_tbl_sulen; tp->tbl.arg = tp; tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin); /* Center the table as a whole. */ offset = tp->tcol->offset; if (sp->opts->opts & TBL_OPT_CENTRE) { tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? 2 : !!sp->opts->lvert + !!sp->opts->rvert; for (ic = 0; ic + 1 < sp->opts->cols; ic++) tsz += tp->tbl.cols[ic].width + tp->tbl.cols[ic].spacing; if (sp->opts->cols) tsz += tp->tbl.cols[sp->opts->cols - 1].width; if (offset + tsz > tp->tcol->rmargin) tsz -= 1; offset = offset + tp->tcol->rmargin > tsz ? (offset + tp->tcol->rmargin - tsz) / 2 : 0; tp->tcol->offset = offset; } /* Horizontal frame at the start of boxed tables. */ if (tp->enc == TERMENC_ASCII && sp->opts->opts & TBL_OPT_DBOX) tbl_hrule(tp, NULL, sp, sp, TBL_OPT_DBOX); if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) tbl_hrule(tp, NULL, sp, sp, TBL_OPT_BOX); } /* Set up the columns. */ tp->flags |= TERMP_MULTICOL; tp->tcol->offset = offset; horiz = 0; switch (sp->pos) { case TBL_SPAN_HORIZ: case TBL_SPAN_DHORIZ: horiz = 1; term_setcol(tp, 1); break; case TBL_SPAN_DATA: term_setcol(tp, sp->opts->cols + 2); coloff = tp->tcol->offset; /* Set up a column for a left vertical frame. */ if (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) || sp->opts->lvert) coloff++; tp->tcol->rmargin = coloff; /* Set up the data columns. */ dp = sp->first; hspans = 0; for (ic = 0; ic < sp->opts->cols; ic++) { if (hspans == 0) { tp->tcol++; tp->tcol->offset = coloff; } coloff += tp->tbl.cols[ic].width; tp->tcol->rmargin = coloff; if (ic + 1 < sp->opts->cols) coloff += tp->tbl.cols[ic].spacing; if (hspans) { hspans--; continue; } if (dp != NULL && (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { hspans = dp->hspans; dp = dp->next; } } /* Set up a column for a right vertical frame. */ tp->tcol++; tp->tcol->offset = coloff + 1; tp->tcol->rmargin = tp->maxrmargin; /* Spans may have reduced the number of columns. */ tp->lasttcol = tp->tcol - tp->tcols; /* Fill the buffers for all data columns. */ tp->tcol = tp->tcols; cp = cpn = sp->layout->first; dp = sp->first; hspans = 0; for (ic = 0; ic < sp->opts->cols; ic++) { if (cpn != NULL) { cp = cpn; cpn = cpn->next; } if (hspans) { hspans--; continue; } tp->tcol++; tp->col = 0; tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic); if (dp != NULL && (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { hspans = dp->hspans; dp = dp->next; } } break; } do { /* Print the vertical frame at the start of each row. */ tp->tcol = tp->tcols; uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 : sp->opts->opts & TBL_OPT_BOX ? 1 : 0; if (sp->pos == TBL_SPAN_DATA && uvert < sp->layout->vert) uvert = dvert = sp->layout->vert; if (sp->next != NULL && sp->next->pos == TBL_SPAN_DATA && dvert < sp->next->layout->vert) dvert = sp->next->layout->vert; if (sp->prev != NULL && uvert < sp->prev->layout->vert && (horiz || (IS_HORIZ(sp->layout->first) && !IS_HORIZ(sp->prev->layout->first)))) uvert = sp->prev->layout->vert; rhori = sp->pos == TBL_SPAN_DHORIZ || (sp->first != NULL && sp->first->pos == TBL_DATA_DHORIZ) || sp->layout->first->pos == TBL_CELL_DHORIZ ? 2 : sp->pos == TBL_SPAN_HORIZ || (sp->first != NULL && sp->first->pos == TBL_DATA_HORIZ) || sp->layout->first->pos == TBL_CELL_HORIZ ? 1 : 0; fc = BUP * uvert + BDOWN * dvert + BRIGHT * rhori; if (uvert > 0 || dvert > 0 || (horiz && sp->opts->lvert)) { (*tp->advance)(tp, tp->tcols->offset); tp->viscol = tp->tcol->offset; tbl_direct_border(tp, fc, 1); } /* Print the data cells. */ more = 0; if (horiz) tbl_hrule(tp, sp->prev, sp, sp->next, 0); else { cp = sp->layout->first; cpn = sp->next == NULL ? NULL : sp->next->layout->first; cpp = sp->prev == NULL ? NULL : sp->prev->layout->first; dp = sp->first; hspans = 0; for (ic = 0; ic < sp->opts->cols; ic++) { /* * Figure out whether to print a * vertical line after this cell * and advance to next layout cell. */ uvert = dvert = fc = 0; if (cp != NULL) { cps = cp; while (cps->next != NULL && cps->next->pos == TBL_CELL_SPAN) cps = cps->next; if (sp->pos == TBL_SPAN_DATA) uvert = dvert = cps->vert; switch (cp->pos) { case TBL_CELL_HORIZ: fc = BHORIZ; break; case TBL_CELL_DHORIZ: fc = BHORIZ * 2; break; default: break; } } if (cpp != NULL) { if (uvert < cpp->vert && cp != NULL && ((IS_HORIZ(cp) && !IS_HORIZ(cpp)) || (cp->next != NULL && cpp->next != NULL && IS_HORIZ(cp->next) && !IS_HORIZ(cpp->next)))) uvert = cpp->vert; cpp = cpp->next; } if (sp->opts->opts & TBL_OPT_ALLBOX) { if (uvert == 0) uvert = 1; if (dvert == 0) dvert = 1; } if (cpn != NULL) { if (dvert == 0 || (dvert < cpn->vert && tp->enc == TERMENC_UTF8)) dvert = cpn->vert; cpn = cpn->next; } lhori = (cp != NULL && cp->pos == TBL_CELL_DHORIZ) || (dp != NULL && dp->pos == TBL_DATA_DHORIZ) ? 2 : (cp != NULL && cp->pos == TBL_CELL_HORIZ) || (dp != NULL && dp->pos == TBL_DATA_HORIZ) ? 1 : 0; /* * Skip later cells in a span, * figure out whether to start a span, * and advance to next data cell. */ if (hspans) { hspans--; cp = cp->next; continue; } if (dp != NULL && (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { hspans = dp->hspans; dp = dp->next; } /* * Print one line of text in the cell * and remember whether there is more. */ tp->tcol++; if (tp->tcol->col < tp->tcol->lastcol) term_flushln(tp); if (tp->tcol->col < tp->tcol->lastcol) more = 1; /* * Vertical frames between data cells, * but not after the last column. */ if (fc == 0 && ((uvert == 0 && dvert == 0 && cp != NULL && (cp->next == NULL || !IS_HORIZ(cp->next))) || tp->tcol + 1 == tp->tcols + tp->lasttcol)) { if (cp != NULL) cp = cp->next; continue; } if (tp->viscol < tp->tcol->rmargin) { (*tp->advance)(tp, tp->tcol->rmargin - tp->viscol); tp->viscol = tp->tcol->rmargin; } while (tp->viscol < tp->tcol->rmargin + tp->tbl.cols[ic].spacing / 2) tbl_direct_border(tp, BHORIZ * lhori, 1); if (tp->tcol + 1 == tp->tcols + tp->lasttcol) continue; if (cp != NULL) cp = cp->next; rhori = (cp != NULL && cp->pos == TBL_CELL_DHORIZ) || (dp != NULL && dp->pos == TBL_DATA_DHORIZ) ? 2 : (cp != NULL && cp->pos == TBL_CELL_HORIZ) || (dp != NULL && dp->pos == TBL_DATA_HORIZ) ? 1 : 0; if (tp->tbl.cols[ic].spacing) tbl_direct_border(tp, BLEFT * lhori + BRIGHT * rhori + BUP * uvert + BDOWN * dvert, 1); if (tp->enc == TERMENC_UTF8) uvert = dvert = 0; if (tp->tbl.cols[ic].spacing > 2 && (uvert > 1 || dvert > 1 || rhori)) tbl_direct_border(tp, BHORIZ * rhori + BUP * (uvert > 1) + BDOWN * (dvert > 1), 1); } } /* Print the vertical frame at the end of each row. */ uvert = dvert = sp->opts->opts & TBL_OPT_DBOX ? 2 : sp->opts->opts & TBL_OPT_BOX ? 1 : 0; if (sp->pos == TBL_SPAN_DATA && uvert < sp->layout->last->vert && sp->layout->last->col + 1 == sp->opts->cols) uvert = dvert = sp->layout->last->vert; if (sp->next != NULL && dvert < sp->next->layout->last->vert && sp->next->layout->last->col + 1 == sp->opts->cols) dvert = sp->next->layout->last->vert; if (sp->prev != NULL && uvert < sp->prev->layout->last->vert && sp->prev->layout->last->col + 1 == sp->opts->cols && (horiz || (IS_HORIZ(sp->layout->last) && !IS_HORIZ(sp->prev->layout->last)))) uvert = sp->prev->layout->last->vert; lhori = sp->pos == TBL_SPAN_DHORIZ || (sp->last != NULL && sp->last->pos == TBL_DATA_DHORIZ && sp->last->layout->col + 1 == sp->opts->cols) || (sp->layout->last->pos == TBL_CELL_DHORIZ && sp->layout->last->col + 1 == sp->opts->cols) ? 2 : sp->pos == TBL_SPAN_HORIZ || (sp->last != NULL && sp->last->pos == TBL_DATA_HORIZ && sp->last->layout->col + 1 == sp->opts->cols) || (sp->layout->last->pos == TBL_CELL_HORIZ && sp->layout->last->col + 1 == sp->opts->cols) ? 1 : 0; fc = BUP * uvert + BDOWN * dvert + BLEFT * lhori; if (uvert > 0 || dvert > 0 || (horiz && sp->opts->rvert)) { if (horiz == 0 && (IS_HORIZ(sp->layout->last) == 0 || sp->layout->last->col + 1 < sp->opts->cols)) { tp->tcol++; do { tbl_direct_border(tp, BHORIZ * lhori, 1); } while (tp->viscol < tp->tcol->offset); } tbl_direct_border(tp, fc, 1); } (*tp->endline)(tp); tp->viscol = 0; } while (more); /* * Clean up after this row. If it is the last line * of the table, print the box line and clean up * column data; otherwise, print the allbox line. */ term_setcol(tp, 1); tp->flags &= ~TERMP_MULTICOL; tp->tcol->rmargin = tp->maxrmargin; if (sp->next == NULL) { if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) { tbl_hrule(tp, sp, sp, NULL, TBL_OPT_BOX); tp->skipvsp = 1; } if (tp->enc == TERMENC_ASCII && sp->opts->opts & TBL_OPT_DBOX) { tbl_hrule(tp, sp, sp, NULL, TBL_OPT_DBOX); tp->skipvsp = 2; } assert(tp->tbl.cols); free(tp->tbl.cols); tp->tbl.cols = NULL; } else if (horiz == 0 && sp->opts->opts & TBL_OPT_ALLBOX && (sp->next == NULL || sp->next->pos == TBL_SPAN_DATA || sp->next->next != NULL)) tbl_hrule(tp, sp, sp, sp->next, TBL_OPT_ALLBOX); tp->tcol->offset = save_offset; tp->flags &= ~TERMP_NONOSPACE; } static void tbl_hrule(struct termp *tp, const struct tbl_span *spp, const struct tbl_span *sp, const struct tbl_span *spn, int flags) { const struct tbl_cell *cpp; /* Layout cell above this line. */ const struct tbl_cell *cp; /* Layout cell in this line. */ const struct tbl_cell *cpn; /* Layout cell below this line. */ const struct tbl_dat *dpn; /* Data cell below this line. */ const struct roffcol *col; /* Contains width and spacing. */ int opts; /* For the table as a whole. */ int bw; /* Box line width. */ int hw; /* Horizontal line width. */ int lw, rw; /* Left and right line widths. */ int uw, dw; /* Vertical line widths. */ cpp = spp == NULL ? NULL : spp->layout->first; cp = sp == NULL ? NULL : sp->layout->first; cpn = spn == NULL ? NULL : spn->layout->first; dpn = NULL; if (spn != NULL) { if (spn->pos == TBL_SPAN_DATA) dpn = spn->first; else if (spn->next != NULL) dpn = spn->next->first; } opts = sp->opts->opts; bw = opts & TBL_OPT_DBOX ? (tp->enc == TERMENC_UTF8 ? 2 : 1) : opts & (TBL_OPT_BOX | TBL_OPT_ALLBOX) ? 1 : 0; hw = flags == TBL_OPT_DBOX || flags == TBL_OPT_BOX ? bw : sp->pos == TBL_SPAN_DHORIZ ? 2 : 1; /* Print the left end of the line. */ if (tp->viscol == 0) { (*tp->advance)(tp, tp->tcols->offset); tp->viscol = tp->tcols->offset; } if (flags != 0) tbl_direct_border(tp, (spp == NULL ? 0 : BUP * bw) + (spn == NULL ? 0 : BDOWN * bw) + (spp == NULL || cpn == NULL || cpn->pos != TBL_CELL_DOWN ? BRIGHT * hw : 0), 1); col = tp->tbl.cols; for (;;) { if (cp == NULL) col++; else col = tp->tbl.cols + cp->col; /* Print the horizontal line inside this column. */ lw = cpp == NULL || cpn == NULL || (cpn->pos != TBL_CELL_DOWN && (dpn == NULL || dpn->string == NULL || strcmp(dpn->string, "\\^") != 0)) ? hw : 0; tbl_direct_border(tp, BHORIZ * lw, col->width + col->spacing / 2); /* * Figure out whether a vertical line is crossing * at the end of this column, * and advance to the next column. */ uw = dw = 0; if (cpp != NULL) { if (flags != TBL_OPT_DBOX) { uw = cpp->vert; if (uw == 0 && opts & TBL_OPT_ALLBOX) uw = 1; } cpp = cpp->next; } else if (spp != NULL && opts & TBL_OPT_ALLBOX) uw = 1; if (cp != NULL) cp = cp->next; if (cpn != NULL) { if (flags != TBL_OPT_DBOX) { dw = cpn->vert; if (dw == 0 && opts & TBL_OPT_ALLBOX) dw = 1; } cpn = cpn->next; while (dpn != NULL && dpn->layout != cpn) dpn = dpn->next; } else if (spn != NULL && opts & TBL_OPT_ALLBOX) dw = 1; if (col + 1 == tp->tbl.cols + sp->opts->cols) break; /* Vertical lines do not cross spanned cells. */ if (cpp != NULL && cpp->pos == TBL_CELL_SPAN) uw = 0; if (cpn != NULL && cpn->pos == TBL_CELL_SPAN) dw = 0; /* The horizontal line inside the next column. */ rw = cpp == NULL || cpn == NULL || (cpn->pos != TBL_CELL_DOWN && (dpn == NULL || dpn->string == NULL || strcmp(dpn->string, "\\^") != 0)) ? hw : 0; /* The line crossing at the end of this column. */ if (col->spacing) tbl_direct_border(tp, BLEFT * lw + BRIGHT * rw + BUP * uw + BDOWN * dw, 1); /* * In ASCII output, a crossing may print two characters. */ if (tp->enc != TERMENC_ASCII || (uw < 2 && dw < 2)) uw = dw = 0; if (col->spacing > 2) tbl_direct_border(tp, BHORIZ * rw + BUP * uw + BDOWN * dw, 1); /* Padding before the start of the next column. */ if (col->spacing > 4) tbl_direct_border(tp, BHORIZ * rw, (col->spacing - 3) / 2); } /* Print the right end of the line. */ if (flags != 0) { tbl_direct_border(tp, (spp == NULL ? 0 : BUP * bw) + (spn == NULL ? 0 : BDOWN * bw) + (spp == NULL || spn == NULL || spn->layout->last->pos != TBL_CELL_DOWN ? BLEFT * hw : 0), 1); (*tp->endline)(tp); tp->viscol = 0; } } static void tbl_data(struct termp *tp, const struct tbl_opts *opts, const struct tbl_cell *cp, const struct tbl_dat *dp, const struct roffcol *col) { switch (cp->pos) { case TBL_CELL_HORIZ: tbl_fill_border(tp, BHORIZ, col->width); return; case TBL_CELL_DHORIZ: tbl_fill_border(tp, BHORIZ * 2, col->width); return; default: break; } if (dp == NULL) return; switch (dp->pos) { case TBL_DATA_NONE: return; case TBL_DATA_HORIZ: case TBL_DATA_NHORIZ: tbl_fill_border(tp, BHORIZ, col->width); return; case TBL_DATA_NDHORIZ: case TBL_DATA_DHORIZ: tbl_fill_border(tp, BHORIZ * 2, col->width); return; default: break; } switch (cp->pos) { case TBL_CELL_LONG: case TBL_CELL_CENTRE: case TBL_CELL_LEFT: case TBL_CELL_RIGHT: tbl_literal(tp, dp, col); break; case TBL_CELL_NUMBER: tbl_number(tp, opts, dp, col); break; case TBL_CELL_DOWN: case TBL_CELL_SPAN: break; default: abort(); } } static void tbl_fill_string(struct termp *tp, const char *cp, size_t len) { size_t i, sz; sz = term_strlen(tp, cp); for (i = 0; i < len; i += sz) term_word(tp, cp); } static void tbl_fill_char(struct termp *tp, char c, size_t len) { char cp[2]; cp[0] = c; cp[1] = '\0'; tbl_fill_string(tp, cp, len); } static void tbl_fill_border(struct termp *tp, int c, size_t len) { char buf[12]; if ((c = borders_locale[c]) > 127) { (void)snprintf(buf, sizeof(buf), "\\[u%04x]", c); tbl_fill_string(tp, buf, len); } else tbl_fill_char(tp, c, len); } static void tbl_direct_border(struct termp *tp, int c, size_t len) { size_t i, sz; c = borders_locale[c]; sz = (*tp->width)(tp, c); for (i = 0; i < len; i += sz) { (*tp->letter)(tp, c); tp->viscol += sz; } } static void tbl_literal(struct termp *tp, const struct tbl_dat *dp, const struct roffcol *col) { size_t len, padl, padr, width; int ic, hspans; assert(dp->string); len = term_strlen(tp, dp->string); width = col->width; ic = dp->layout->col; hspans = dp->hspans; while (hspans--) width += tp->tbl.cols[++ic].width + 3; padr = width > len ? width - len : 0; padl = 0; switch (dp->layout->pos) { case TBL_CELL_LONG: padl = term_len(tp, 1); padr = padr > padl ? padr - padl : 0; break; case TBL_CELL_CENTRE: if (2 > padr) break; padl = padr / 2; padr -= padl; break; case TBL_CELL_RIGHT: padl = padr; padr = 0; break; default: break; } tbl_fill_char(tp, ASCII_NBRSP, padl); tbl_word(tp, dp); tbl_fill_char(tp, ASCII_NBRSP, padr); } static void tbl_number(struct termp *tp, const struct tbl_opts *opts, const struct tbl_dat *dp, const struct roffcol *col) { const char *cp, *lastdigit, *lastpoint; size_t intsz, padl, totsz; char buf[2]; /* * Almost the same code as in tblcalc_number(): * First find the position of the decimal point. */ assert(dp->string); lastdigit = lastpoint = NULL; for (cp = dp->string; cp[0] != '\0'; cp++) { if (cp[0] == '\\' && cp[1] == '&') { lastdigit = lastpoint = cp; break; } else if (cp[0] == opts->decimal && (isdigit((unsigned char)cp[1]) || (cp > dp->string && isdigit((unsigned char)cp[-1])))) lastpoint = cp; else if (isdigit((unsigned char)cp[0])) lastdigit = cp; } /* Then measure both widths. */ padl = 0; totsz = term_strlen(tp, dp->string); if (lastdigit != NULL) { if (lastpoint == NULL) lastpoint = lastdigit + 1; intsz = 0; buf[1] = '\0'; for (cp = dp->string; cp < lastpoint; cp++) { buf[0] = cp[0]; intsz += term_strlen(tp, buf); } /* * Pad left to match the decimal position, * but avoid exceeding the total column width. */ if (col->decimal > intsz && col->width > totsz) { padl = col->decimal - intsz; if (padl + totsz > col->width) padl = col->width - totsz; } /* If it is not a number, simply center the string. */ } else if (col->width > totsz) padl = (col->width - totsz) / 2; tbl_fill_char(tp, ASCII_NBRSP, padl); tbl_word(tp, dp); /* Pad right to fill the column. */ if (col->width > padl + totsz) tbl_fill_char(tp, ASCII_NBRSP, col->width - padl - totsz); } static void tbl_word(struct termp *tp, const struct tbl_dat *dp) { int prev_font; prev_font = tp->fonti; switch (dp->layout->font) { case ESCAPE_FONTBI: term_fontpush(tp, TERMFONT_BI); break; case ESCAPE_FONTBOLD: case ESCAPE_FONTCB: term_fontpush(tp, TERMFONT_BOLD); break; case ESCAPE_FONTITALIC: case ESCAPE_FONTCI: term_fontpush(tp, TERMFONT_UNDER); break; case ESCAPE_FONTROMAN: case ESCAPE_FONTCR: break; default: abort(); } term_word(tp, dp->string); term_fontpopq(tp, prev_font); } /* $Id: term.c,v 1.283 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2010-2020 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); 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_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, p->flags & TERMP_BRNEVER ? SIZE_MAX : 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); /* * 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, * and an input string length of nbr characters. */ static void term_field(struct termp *p, size_t vbl, size_t nbr) { 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: case ESCAPE_FONTCB: term_fontrepl(p, TERMFONT_BOLD); continue; case ESCAPE_FONTITALIC: case ESCAPE_FONTCI: term_fontrepl(p, TERMFONT_UNDER); continue; case ESCAPE_FONTBI: term_fontrepl(p, TERMFONT_BI); continue; case ESCAPE_FONT: case ESCAPE_FONTCR: 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); } /* $Id: term.h,v 1.131 2019/01/04 03:21:02 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 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. */ enum termenc { TERMENC_ASCII, TERMENC_LOCALE, TERMENC_UTF8 }; enum termtype { TERMTYPE_CHAR, TERMTYPE_PS, TERMTYPE_PDF }; enum termfont { TERMFONT_NONE = 0, TERMFONT_BOLD, TERMFONT_UNDER, TERMFONT_BI, TERMFONT__MAX }; struct eqn_box; struct roff_meta; struct roff_node; struct tbl_span; struct termp; typedef void (*term_margin)(struct termp *, const struct roff_meta *); struct termp_tbl { int width; /* width in fixed chars */ int decimal; /* decimal point position */ }; struct termp_col { int *buf; /* Output buffer. */ size_t maxcols; /* Allocated bytes in buf. */ size_t lastcol; /* Last byte in buf. */ size_t col; /* Byte in buf to be written. */ size_t rmargin; /* Current right margin. */ size_t offset; /* Current left margin. */ }; struct termp { struct rofftbl tbl; /* Table configuration. */ struct termp_col *tcols; /* Array of table columns. */ struct termp_col *tcol; /* Current table column. */ size_t maxtcol; /* Allocated table columns. */ size_t lasttcol; /* Last column currently used. */ size_t line; /* Current output line number. */ size_t defindent; /* Default indent for text. */ size_t defrmargin; /* Right margin of the device. */ size_t lastrmargin; /* Right margin before the last ll. */ size_t maxrmargin; /* Max right margin. */ size_t col; /* Byte position in buf. */ size_t viscol; /* Chars on current line. */ size_t trailspace; /* See term_flushln(). */ size_t minbl; /* Minimum blanks before next field. */ int synopsisonly; /* Print the synopsis only. */ int mdocstyle; /* Imitate mdoc(7) output. */ int ti; /* Temporary indent for one line. */ int skipvsp; /* Vertical space to skip. */ int flags; #define TERMP_SENTENCE (1 << 0) /* Space before a sentence. */ #define TERMP_NOSPACE (1 << 1) /* No space before words. */ #define TERMP_NONOSPACE (1 << 2) /* No space (no autounset). */ #define TERMP_NBRWORD (1 << 3) /* Make next word nonbreaking. */ #define TERMP_KEEP (1 << 4) /* Keep words together. */ #define TERMP_PREKEEP (1 << 5) /* ...starting with the next one. */ #define TERMP_BACKAFTER (1 << 6) /* Back up after next character. */ #define TERMP_BACKBEFORE (1 << 7) /* Back up before next character. */ #define TERMP_NOBREAK (1 << 8) /* See term_flushln(). */ #define TERMP_BRTRSP (1 << 9) /* See term_flushln(). */ #define TERMP_BRIND (1 << 10) /* See term_flushln(). */ #define TERMP_HANG (1 << 11) /* See term_flushln(). */ #define TERMP_NOPAD (1 << 12) /* See term_flushln(). */ #define TERMP_NOSPLIT (1 << 13) /* Do not break line before .An. */ #define TERMP_SPLIT (1 << 14) /* Break line before .An. */ #define TERMP_NONEWLINE (1 << 15) /* No line break in nofill mode. */ #define TERMP_BRNEVER (1 << 16) /* Don't even break at maxrmargin. */ #define TERMP_NOBUF (1 << 17) /* Bypass output buffer. */ #define TERMP_NEWMC (1 << 18) /* No .mc printed yet. */ #define TERMP_ENDMC (1 << 19) /* Next break ends .mc mode. */ #define TERMP_MULTICOL (1 << 20) /* Multiple column mode. */ #define TERMP_CENTER (1 << 21) /* Center output lines. */ #define TERMP_RIGHT (1 << 22) /* Adjust to the right margin. */ enum termtype type; /* Terminal, PS, or PDF. */ enum termenc enc; /* Type of encoding. */ enum termfont fontl; /* Last font set. */ enum termfont *fontq; /* Symmetric fonts. */ int fontsz; /* Allocated size of font stack */ int fonti; /* Index of font stack. */ term_margin headf; /* invoked to print head */ term_margin footf; /* invoked to print foot */ void (*letter)(struct termp *, int); void (*begin)(struct termp *); void (*end)(struct termp *); void (*endline)(struct termp *); void (*advance)(struct termp *, size_t); void (*setwidth)(struct termp *, int, int); size_t (*width)(const struct termp *, int); int (*hspan)(const struct termp *, const struct roffsu *); const void *argf; /* arg for headf/footf */ const char *mc; /* Margin character. */ struct termp_ps *ps; }; const char *ascii_uc2str(int); void roff_term_pre(struct termp *, const struct roff_node *); void term_eqn(struct termp *, const struct eqn_box *); void term_tbl(struct termp *, const struct tbl_span *); void term_free(struct termp *); void term_setcol(struct termp *, size_t); void term_newln(struct termp *); void term_vspace(struct termp *); void term_word(struct termp *, const char *); void term_flushln(struct termp *); void term_begin(struct termp *, term_margin, term_margin, const struct roff_meta *); void term_end(struct termp *); void term_setwidth(struct termp *, const char *); int term_hspan(const struct termp *, const struct roffsu *); int term_hen(const struct termp *, const struct roffsu *); int term_vspan(const struct termp *, const struct roffsu *); size_t term_strlen(const struct termp *, const char *); size_t term_len(const struct termp *, size_t); void term_tab_set(const struct termp *, const char *); void term_tab_iset(size_t); size_t term_tab_next(size_t); void term_fontpush(struct termp *, enum termfont); void term_fontpop(struct termp *); void term_fontpopq(struct termp *, int); void term_fontrepl(struct termp *, enum termfont); void term_fontlast(struct termp *); /* $Id: term_ascii.c,v 1.66 2020/09/09 13:45:05 schwarze Exp $ */ /* * Copyright (c) 2010, 2011 Kristaps Dzonsons * Copyright (c) 2014,2015,2017,2018,2020 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 #if HAVE_WCHAR #include #include #endif #include #include #include #include #include #if HAVE_WCHAR #include #endif #include "mandoc.h" #include "mandoc_aux.h" #include "out.h" #include "term.h" #include "manconf.h" #include "main.h" static struct termp *ascii_init(enum termenc, const struct manoutput *); static int ascii_hspan(const struct termp *, const struct roffsu *); static size_t ascii_width(const struct termp *, int); static void ascii_advance(struct termp *, size_t); static void ascii_begin(struct termp *); static void ascii_end(struct termp *); static void ascii_endline(struct termp *); static void ascii_letter(struct termp *, int); static void ascii_setwidth(struct termp *, int, int); #if HAVE_WCHAR static void locale_advance(struct termp *, size_t); static void locale_endline(struct termp *); static void locale_letter(struct termp *, int); static size_t locale_width(const struct termp *, int); #endif static struct termp * ascii_init(enum termenc enc, const struct manoutput *outopts) { #if HAVE_WCHAR char *v; #endif struct termp *p; p = mandoc_calloc(1, sizeof(*p)); p->tcol = p->tcols = mandoc_calloc(1, sizeof(*p->tcol)); p->maxtcol = 1; p->line = 1; p->defrmargin = p->lastrmargin = 78; p->fontq = mandoc_reallocarray(NULL, (p->fontsz = 8), sizeof(*p->fontq)); p->fontq[0] = p->fontl = TERMFONT_NONE; p->begin = ascii_begin; p->end = ascii_end; p->hspan = ascii_hspan; p->type = TERMTYPE_CHAR; p->enc = TERMENC_ASCII; p->advance = ascii_advance; p->endline = ascii_endline; p->letter = ascii_letter; p->setwidth = ascii_setwidth; p->width = ascii_width; #if HAVE_WCHAR if (enc != TERMENC_ASCII) { /* * Do not change any of this to LC_ALL. It might break * the formatting by subtly changing the behaviour of * various functions, for example strftime(3). As a * worst case, it might even cause buffer overflows. */ v = enc == TERMENC_LOCALE ? setlocale(LC_CTYPE, "") : setlocale(LC_CTYPE, UTF8_LOCALE); /* * We only support UTF-8, * so revert to ASCII for anything else. */ if (v != NULL && strcmp(nl_langinfo(CODESET), "UTF-8") != 0) v = setlocale(LC_CTYPE, "C"); if (v != NULL && MB_CUR_MAX > 1) { p->enc = TERMENC_UTF8; p->advance = locale_advance; p->endline = locale_endline; p->letter = locale_letter; p->width = locale_width; } } #endif if (outopts->mdoc) { p->mdocstyle = 1; p->defindent = 5; } if (outopts->indent) p->defindent = outopts->indent; if (outopts->width) p->defrmargin = outopts->width; if (outopts->synopsisonly) p->synopsisonly = 1; assert(p->defindent < UINT16_MAX); assert(p->defrmargin < UINT16_MAX); return p; } void * ascii_alloc(const struct manoutput *outopts) { return ascii_init(TERMENC_ASCII, outopts); } void * utf8_alloc(const struct manoutput *outopts) { return ascii_init(TERMENC_UTF8, outopts); } void * locale_alloc(const struct manoutput *outopts) { return ascii_init(TERMENC_LOCALE, outopts); } static void ascii_setwidth(struct termp *p, int iop, int width) { width /= 24; p->tcol->rmargin = p->defrmargin; if (iop > 0) p->defrmargin += width; else if (iop == 0) p->defrmargin = width ? (size_t)width : p->lastrmargin; else if (p->defrmargin > (size_t)width) p->defrmargin -= width; else p->defrmargin = 0; if (p->defrmargin > 1000) p->defrmargin = 1000; p->lastrmargin = p->tcol->rmargin; p->tcol->rmargin = p->maxrmargin = p->defrmargin; } void terminal_sepline(void *arg) { struct termp *p; size_t i; p = (struct termp *)arg; (*p->endline)(p); for (i = 0; i < p->defrmargin; i++) (*p->letter)(p, '-'); (*p->endline)(p); (*p->endline)(p); } static size_t ascii_width(const struct termp *p, int c) { return c != ASCII_BREAK; } void ascii_free(void *arg) { term_free((struct termp *)arg); } static void ascii_letter(struct termp *p, int c) { putchar(c); } static void ascii_begin(struct termp *p) { (*p->headf)(p, p->argf); } static void ascii_end(struct termp *p) { (*p->footf)(p, p->argf); } static void ascii_endline(struct termp *p) { p->line++; if ((int)p->tcol->offset > p->ti) p->tcol->offset -= p->ti; else p->tcol->offset = 0; p->ti = 0; putchar('\n'); } static void ascii_advance(struct termp *p, size_t len) { size_t i; /* * XXX We used to have "assert(len < UINT16_MAX)" here. * that is not quite right because the input document * can trigger that by merely providing large input. * For now, simply truncate. */ if (len > 256) len = 256; for (i = 0; i < len; i++) putchar(' '); } static int ascii_hspan(const struct termp *p, const struct roffsu *su) { double r; switch (su->unit) { case SCALE_BU: r = su->scale; break; case SCALE_CM: r = su->scale * 240.0 / 2.54; break; case SCALE_FS: r = su->scale * 65536.0; break; case SCALE_IN: r = su->scale * 240.0; break; case SCALE_MM: r = su->scale * 0.24; break; case SCALE_VS: case SCALE_PC: r = su->scale * 40.0; break; case SCALE_PT: r = su->scale * 10.0 / 3.0; break; case SCALE_EN: case SCALE_EM: r = su->scale * 24.0; break; default: abort(); } return r > 0.0 ? r + 0.01 : r - 0.01; } const char * ascii_uc2str(int uc) { static const char nbrsp[2] = { ASCII_NBRSP, '\0' }; static const char *tab[] = { "","","","","","","","", "", "\t", "", "", "", "", "", "", "","","","","","","","", "","", "","","", "", "", "", " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "", "<80>", "<81>", "<82>", "<83>", "<84>", "<85>", "<86>", "<87>", "<88>", "<89>", "<8A>", "<8B>", "<8C>", "<8D>", "<8E>", "<8F>", "<90>", "<91>", "<92>", "<93>", "<94>", "<95>", "<96>", "<97>", "<98>", "<99>", "<9A>", "<9B>", "<9C>", "<9D>", "<9E>", "<9F>", nbrsp, "!", "/\bc", "-\bL", "o\bx", "=\bY", "|", "

    ", "\"", "(C)", "_\ba", "<<", "~", "", "(R)", "-", "","+-","^2", "^3", "'","","",".", ",", "^1", "_\bo", ">>", "1/4", "1/2", "3/4", "?", "`\bA", "'\bA", "^\bA", "~\bA", "\"\bA","o\bA", "AE", ",\bC", "`\bE", "'\bE", "^\bE", "\"\bE","`\bI", "'\bI", "^\bI", "\"\bI", "Dh", "~\bN", "`\bO", "'\bO", "^\bO", "~\bO", "\"\bO","x", "/\bO", "`\bU", "'\bU", "^\bU", "\"\bU","'\bY", "Th", "ss", "`\ba", "'\ba", "^\ba", "~\ba", "\"\ba","o\ba", "ae", ",\bc", "`\be", "'\be", "^\be", "\"\be","`\bi", "'\bi", "^\bi", "\"\bi", "dh", "~\bn", "`\bo", "'\bo", "^\bo", "~\bo", "\"\bo","/", "/\bo", "`\bu", "'\bu", "^\bu", "\"\bu","'\by", "th", "\"\by", "A", "a", "A", "a", "A", "a", "'\bC", "'\bc", "^\bC", "^\bc", "C", "c", "C", "c", "D", "d", "/\bD", "/\bd", "E", "e", "E", "e", "E", "e", "E", "e", "E", "e", "^\bG", "^\bg", "G", "g", "G", "g", ",\bG", ",\bg", "^\bH", "^\bh", "/\bH", "/\bh", "~\bI", "~\bi", "I", "i", "I", "i", "I", "i", "I", "i", "IJ", "ij", "^\bJ", "^\bj", ",\bK", ",\bk", "q", "'\bL", "'\bl", ",\bL", ",\bl", "L", "l", "L", "l", "/\bL", "/\bl", "'\bN", "'\bn", ",\bN", ",\bn", "N", "n", "'n", "Ng", "ng", "O", "o", "O", "o", "O", "o", "OE", "oe", "'\bR", "'\br", ",\bR", ",\br", "R", "r", "'\bS", "'\bs", "^\bS", "^\bs", ",\bS", ",\bs", "S", "s", ",\bT", ",\bt", "T", "t", "/\bT", "/\bt", "~\bU", "~\bu", "U", "u", "U", "u", "U", "u", "U", "u", "U", "u", "^\bW", "^\bw", "^\bY", "^\by", "\"\bY","'\bZ", "'\bz", "Z", "z", "Z", "z", "s", "b", "B", "B", "b", "6", "6", "O", "C", "c", "D", "D", "D", "d", "d", "3", "@", "E", "F", ",\bf", "G", "G", "hv", "I", "/\bI", "K", "k", "/\bl", "l", "W", "N", "n", "~\bO", "O", "o", "OI", "oi", "P", "p", "YR", "2", "2", "SH", "sh", "t", "T", "t", "T", "U", "u", "Y", "V", "Y", "y", "/\bZ", "/\bz", "ZH", "ZH", "zh", "zh", "/\b2", "5", "5", "ts", "w", "|", "||", "|=", "!", "DZ", "Dz", "dz", "LJ", "Lj", "lj", "NJ", "Nj", "nj", "A", "a", "I", "i", "O", "o", "U", "u", "U", "u", "U", "u", "U", "u", "U", "u", "@", "A", "a", "A", "a", "AE", "ae", "/\bG", "/\bg", "G", "g", "K", "k", "O", "o", "O", "o", "ZH", "zh", "j", "DZ", "Dz", "dz", "'\bG", "'\bg", "HV", "W", "`\bN", "`\bn", "A", "a", "'\bAE","'\bae","O", "o"}; assert(uc >= 0); if ((size_t)uc < sizeof(tab)/sizeof(tab[0])) return tab[uc]; return mchars_uc2str(uc); } #if HAVE_WCHAR static size_t locale_width(const struct termp *p, int c) { int rc; if (c == ASCII_NBRSP) c = ' '; rc = wcwidth(c); if (rc < 0) rc = 0; return rc; } static void locale_advance(struct termp *p, size_t len) { size_t i; /* * XXX We used to have "assert(len < UINT16_MAX)" here. * that is not quite right because the input document * can trigger that by merely providing large input. * For now, simply truncate. */ if (len > 256) len = 256; for (i = 0; i < len; i++) putwchar(L' '); } static void locale_endline(struct termp *p) { p->line++; if ((int)p->tcol->offset > p->ti) p->tcol->offset -= p->ti; else p->tcol->offset = 0; p->ti = 0; putwchar(L'\n'); } static void locale_letter(struct termp *p, int c) { putwchar(c); } #endif /* $Id: term_ps.c,v 1.92 2020/09/06 14:45:22 schwarze Exp $ */ /* * Copyright (c) 2010, 2011 Kristaps Dzonsons * Copyright (c) 2014,2015,2016,2017,2020 Ingo Schwarze * Copyright (c) 2017 Marc Espie * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is 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 #if HAVE_ERR #include #endif #include #include #include #include #include #include #include "mandoc_aux.h" #include "out.h" #include "term.h" #include "manconf.h" #include "main.h" /* These work the buffer used by the header and footer. */ #define PS_BUFSLOP 128 /* Convert PostScript point "x" to an AFM unit. */ #define PNT2AFM(p, x) \ (size_t)((double)(x) * (1000.0 / (double)(p)->ps->scale)) /* Convert an AFM unit "x" to a PostScript points */ #define AFM2PNT(p, x) \ ((double)(x) / (1000.0 / (double)(p)->ps->scale)) struct glyph { unsigned short wx; /* WX in AFM */ }; struct font { const char *name; /* FontName in AFM */ #define MAXCHAR 95 /* total characters we can handle */ struct glyph gly[MAXCHAR]; /* glyph metrics */ }; struct termp_ps { int flags; #define PS_INLINE (1 << 0) /* we're in a word */ #define PS_MARGINS (1 << 1) /* we're in the margins */ #define PS_NEWPAGE (1 << 2) /* new page, no words yet */ #define PS_BACKSP (1 << 3) /* last character was backspace */ size_t pscol; /* visible column (AFM units) */ size_t pscolnext; /* used for overstrike */ size_t psrow; /* visible row (AFM units) */ size_t lastrow; /* psrow of the previous word */ char *psmarg; /* margin buf */ size_t psmargsz; /* margin buf size */ size_t psmargcur; /* cur index in margin buf */ char last; /* last non-backspace seen */ enum termfont lastf; /* last set font */ enum termfont nextf; /* building next font here */ size_t scale; /* font scaling factor */ size_t pages; /* number of pages shown */ size_t lineheight; /* line height (AFM units) */ size_t top; /* body top (AFM units) */ size_t bottom; /* body bottom (AFM units) */ const char *medianame; /* for DocumentMedia and PageSize */ size_t height; /* page height (AFM units */ size_t width; /* page width (AFM units) */ size_t lastwidth; /* page width before last ll */ size_t left; /* body left (AFM units) */ size_t header; /* header pos (AFM units) */ size_t footer; /* footer pos (AFM units) */ size_t pdfbytes; /* current output byte */ size_t pdflastpg; /* byte of last page mark */ size_t pdfbody; /* start of body object */ size_t *pdfobjs; /* table of object offsets */ size_t pdfobjsz; /* size of pdfobjs */ }; static int ps_hspan(const struct termp *, const struct roffsu *); static size_t ps_width(const struct termp *, int); static void ps_advance(struct termp *, size_t); static void ps_begin(struct termp *); static void ps_closepage(struct termp *); static void ps_end(struct termp *); static void ps_endline(struct termp *); static void ps_growbuf(struct termp *, size_t); static void ps_letter(struct termp *, int); static void ps_pclose(struct termp *); static void ps_plast(struct termp *); static void ps_pletter(struct termp *, int); static void ps_printf(struct termp *, const char *, ...) __attribute__((__format__ (__printf__, 2, 3))); static void ps_putchar(struct termp *, char); static void ps_setfont(struct termp *, enum termfont); static void ps_setwidth(struct termp *, int, int); static struct termp *pspdf_alloc(const struct manoutput *, enum termtype); static void pdf_obj(struct termp *, size_t); /* * We define, for the time being, three fonts: bold, oblique/italic, and * normal (roman). The following table hard-codes the font metrics for * ASCII, i.e., 32--127. */ static const struct font fonts[TERMFONT__MAX] = { { "Times-Roman", { { 250 }, { 333 }, { 408 }, { 500 }, { 500 }, { 833 }, { 778 }, { 333 }, { 333 }, { 333 }, { 500 }, { 564 }, { 250 }, { 333 }, { 250 }, { 278 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 278 }, { 278 }, { 564 }, { 564 }, { 564 }, { 444 }, { 921 }, { 722 }, { 667 }, { 667 }, { 722 }, { 611 }, { 556 }, { 722 }, { 722 }, { 333 }, { 389 }, { 722 }, { 611 }, { 889 }, { 722 }, { 722 }, { 556 }, { 722 }, { 667 }, { 556 }, { 611 }, { 722 }, { 722 }, { 944 }, { 722 }, { 722 }, { 611 }, { 333 }, { 278 }, { 333 }, { 469 }, { 500 }, { 333 }, { 444 }, { 500 }, { 444 }, { 500}, { 444}, { 333}, { 500}, { 500}, { 278}, { 278}, { 500}, { 278}, { 778}, { 500}, { 500}, { 500}, { 500}, { 333}, { 389}, { 278}, { 500}, { 500}, { 722}, { 500}, { 500}, { 444}, { 480}, { 200}, { 480}, { 541}, } }, { "Times-Bold", { { 250 }, { 333 }, { 555 }, { 500 }, { 500 }, { 1000 }, { 833 }, { 333 }, { 333 }, { 333 }, { 500 }, { 570 }, { 250 }, { 333 }, { 250 }, { 278 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 333 }, { 333 }, { 570 }, { 570 }, { 570 }, { 500 }, { 930 }, { 722 }, { 667 }, { 722 }, { 722 }, { 667 }, { 611 }, { 778 }, { 778 }, { 389 }, { 500 }, { 778 }, { 667 }, { 944 }, { 722 }, { 778 }, { 611 }, { 778 }, { 722 }, { 556 }, { 667 }, { 722 }, { 722 }, { 1000 }, { 722 }, { 722 }, { 667 }, { 333 }, { 278 }, { 333 }, { 581 }, { 500 }, { 333 }, { 500 }, { 556 }, { 444 }, { 556 }, { 444 }, { 333 }, { 500 }, { 556 }, { 278 }, { 333 }, { 556 }, { 278 }, { 833 }, { 556 }, { 500 }, { 556 }, { 556 }, { 444 }, { 389 }, { 333 }, { 556 }, { 500 }, { 722 }, { 500 }, { 500 }, { 444 }, { 394 }, { 220 }, { 394 }, { 520 }, } }, { "Times-Italic", { { 250 }, { 333 }, { 420 }, { 500 }, { 500 }, { 833 }, { 778 }, { 333 }, { 333 }, { 333 }, { 500 }, { 675 }, { 250 }, { 333 }, { 250 }, { 278 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 333 }, { 333 }, { 675 }, { 675 }, { 675 }, { 500 }, { 920 }, { 611 }, { 611 }, { 667 }, { 722 }, { 611 }, { 611 }, { 722 }, { 722 }, { 333 }, { 444 }, { 667 }, { 556 }, { 833 }, { 667 }, { 722 }, { 611 }, { 722 }, { 611 }, { 500 }, { 556 }, { 722 }, { 611 }, { 833 }, { 611 }, { 556 }, { 556 }, { 389 }, { 278 }, { 389 }, { 422 }, { 500 }, { 333 }, { 500 }, { 500 }, { 444 }, { 500 }, { 444 }, { 278 }, { 500 }, { 500 }, { 278 }, { 278 }, { 444 }, { 278 }, { 722 }, { 500 }, { 500 }, { 500 }, { 500 }, { 389 }, { 389 }, { 278 }, { 500 }, { 444 }, { 667 }, { 444 }, { 444 }, { 389 }, { 400 }, { 275 }, { 400 }, { 541 }, } }, { "Times-BoldItalic", { { 250 }, { 389 }, { 555 }, { 500 }, { 500 }, { 833 }, { 778 }, { 333 }, { 333 }, { 333 }, { 500 }, { 570 }, { 250 }, { 333 }, { 250 }, { 278 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 500 }, { 333 }, { 333 }, { 570 }, { 570 }, { 570 }, { 500 }, { 832 }, { 667 }, { 667 }, { 667 }, { 722 }, { 667 }, { 667 }, { 722 }, { 778 }, { 389 }, { 500 }, { 667 }, { 611 }, { 889 }, { 722 }, { 722 }, { 611 }, { 722 }, { 667 }, { 556 }, { 611 }, { 722 }, { 667 }, { 889 }, { 667 }, { 611 }, { 611 }, { 333 }, { 278 }, { 333 }, { 570 }, { 500 }, { 333 }, { 500 }, { 500 }, { 444 }, { 500 }, { 444 }, { 333 }, { 500 }, { 556 }, { 278 }, { 278 }, { 500 }, { 278 }, { 778 }, { 556 }, { 500 }, { 500 }, { 500 }, { 389 }, { 389 }, { 278 }, { 556 }, { 444 }, { 667 }, { 500 }, { 444 }, { 389 }, { 348 }, { 220 }, { 348 }, { 570 }, } }, }; void * pdf_alloc(const struct manoutput *outopts) { return pspdf_alloc(outopts, TERMTYPE_PDF); } void * ps_alloc(const struct manoutput *outopts) { return pspdf_alloc(outopts, TERMTYPE_PS); } static struct termp * pspdf_alloc(const struct manoutput *outopts, enum termtype type) { struct termp *p; unsigned int pagex, pagey; size_t marginx, marginy, lineheight; const char *pp; p = mandoc_calloc(1, sizeof(*p)); p->tcol = p->tcols = mandoc_calloc(1, sizeof(*p->tcol)); p->maxtcol = 1; p->type = type; p->enc = TERMENC_ASCII; p->fontq = mandoc_reallocarray(NULL, (p->fontsz = 8), sizeof(*p->fontq)); p->fontq[0] = p->fontl = TERMFONT_NONE; p->ps = mandoc_calloc(1, sizeof(*p->ps)); p->advance = ps_advance; p->begin = ps_begin; p->end = ps_end; p->endline = ps_endline; p->hspan = ps_hspan; p->letter = ps_letter; p->setwidth = ps_setwidth; p->width = ps_width; /* Default to US letter (millimetres). */ p->ps->medianame = "Letter"; pagex = 216; pagey = 279; /* * The ISO-269 paper sizes can be calculated automatically, but * it would require bringing in -lm for pow() and I'd rather not * do that. So just do it the easy way for now. Since this * only happens once, I'm not terribly concerned. */ pp = outopts->paper; if (pp != NULL && strcasecmp(pp, "letter") != 0) { if (strcasecmp(pp, "a3") == 0) { p->ps->medianame = "A3"; pagex = 297; pagey = 420; } else if (strcasecmp(pp, "a4") == 0) { p->ps->medianame = "A4"; pagex = 210; pagey = 297; } else if (strcasecmp(pp, "a5") == 0) { p->ps->medianame = "A5"; pagex = 148; pagey = 210; } else if (strcasecmp(pp, "legal") == 0) { p->ps->medianame = "Legal"; pagex = 216; pagey = 356; } else if (sscanf(pp, "%ux%u", &pagex, &pagey) == 2) p->ps->medianame = "CustomSize"; else warnx("%s: Unknown paper", pp); } /* * This MUST be defined before any PNT2AFM or AFM2PNT * calculations occur. */ p->ps->scale = 11; /* Remember millimetres -> AFM units. */ pagex = PNT2AFM(p, ((double)pagex * 72.0 / 25.4)); pagey = PNT2AFM(p, ((double)pagey * 72.0 / 25.4)); /* Margins are 1/9 the page x and y. */ marginx = (size_t)((double)pagex / 9.0); marginy = (size_t)((double)pagey / 9.0); /* Line-height is 1.4em. */ lineheight = PNT2AFM(p, ((double)p->ps->scale * 1.4)); p->ps->width = p->ps->lastwidth = (size_t)pagex; p->ps->height = (size_t)pagey; p->ps->header = pagey - (marginy / 2) - (lineheight / 2); p->ps->top = pagey - marginy; p->ps->footer = (marginy / 2) - (lineheight / 2); p->ps->bottom = marginy; p->ps->left = marginx; p->ps->lineheight = lineheight; p->defrmargin = pagex - (marginx * 2); return p; } static void ps_setwidth(struct termp *p, int iop, int width) { size_t lastwidth; lastwidth = p->ps->width; if (iop > 0) p->ps->width += width; else if (iop == 0) p->ps->width = width ? (size_t)width : p->ps->lastwidth; else if (p->ps->width > (size_t)width) p->ps->width -= width; else p->ps->width = 0; p->ps->lastwidth = lastwidth; } void pspdf_free(void *arg) { struct termp *p; p = (struct termp *)arg; free(p->ps->psmarg); free(p->ps->pdfobjs); free(p->ps); term_free(p); } static void ps_printf(struct termp *p, const char *fmt, ...) { va_list ap; int pos, len; va_start(ap, fmt); /* * If we're running in regular mode, then pipe directly into * vprintf(). If we're processing margins, then push the data * into our growable margin buffer. */ if ( ! (PS_MARGINS & p->ps->flags)) { len = vprintf(fmt, ap); va_end(ap); p->ps->pdfbytes += len < 0 ? 0 : (size_t)len; return; } /* * XXX: I assume that the in-margin print won't exceed * PS_BUFSLOP (128 bytes), which is reasonable but still an * assumption that will cause pukeage if it's not the case. */ ps_growbuf(p, PS_BUFSLOP); pos = (int)p->ps->psmargcur; vsnprintf(&p->ps->psmarg[pos], PS_BUFSLOP, fmt, ap); va_end(ap); p->ps->psmargcur = strlen(p->ps->psmarg); } static void ps_putchar(struct termp *p, char c) { int pos; /* See ps_printf(). */ if ( ! (PS_MARGINS & p->ps->flags)) { putchar(c); p->ps->pdfbytes++; return; } ps_growbuf(p, 2); pos = (int)p->ps->psmargcur++; p->ps->psmarg[pos++] = c; p->ps->psmarg[pos] = '\0'; } static void pdf_obj(struct termp *p, size_t obj) { assert(obj > 0); if ((obj - 1) >= p->ps->pdfobjsz) { p->ps->pdfobjsz = obj + 128; p->ps->pdfobjs = mandoc_reallocarray(p->ps->pdfobjs, p->ps->pdfobjsz, sizeof(size_t)); } p->ps->pdfobjs[(int)obj - 1] = p->ps->pdfbytes; ps_printf(p, "%zu 0 obj\n", obj); } static void ps_closepage(struct termp *p) { int i; size_t len, base; /* * Close out a page that we've already flushed to output. In * PostScript, we simply note that the page must be shown. In * PDF, we must now create the Length, Resource, and Page node * for the page contents. */ assert(p->ps->psmarg && p->ps->psmarg[0]); ps_printf(p, "%s", p->ps->psmarg); if (TERMTYPE_PS != p->type) { len = p->ps->pdfbytes - p->ps->pdflastpg; base = p->ps->pages * 4 + p->ps->pdfbody; ps_printf(p, "endstream\nendobj\n"); /* Length of content. */ pdf_obj(p, base + 1); ps_printf(p, "%zu\nendobj\n", len); /* Resource for content. */ pdf_obj(p, base + 2); ps_printf(p, "<<\n/ProcSet [/PDF /Text]\n"); ps_printf(p, "/Font <<\n"); for (i = 0; i < (int)TERMFONT__MAX; i++) ps_printf(p, "/F%d %d 0 R\n", i, 3 + i); ps_printf(p, ">>\n>>\nendobj\n"); /* Page node. */ pdf_obj(p, base + 3); ps_printf(p, "<<\n"); ps_printf(p, "/Type /Page\n"); ps_printf(p, "/Parent 2 0 R\n"); ps_printf(p, "/Resources %zu 0 R\n", base + 2); ps_printf(p, "/Contents %zu 0 R\n", base); ps_printf(p, ">>\nendobj\n"); } else ps_printf(p, "showpage\n"); p->ps->pages++; p->ps->psrow = p->ps->top; assert( ! (PS_NEWPAGE & p->ps->flags)); p->ps->flags |= PS_NEWPAGE; } static void ps_end(struct termp *p) { size_t i, xref, base; ps_plast(p); ps_pclose(p); /* * At the end of the file, do one last showpage. This is the * same behaviour as groff(1) and works for multiple pages as * well as just one. */ if ( ! (PS_NEWPAGE & p->ps->flags)) { assert(0 == p->ps->flags); assert('\0' == p->ps->last); ps_closepage(p); } if (TERMTYPE_PS == p->type) { ps_printf(p, "%%%%Trailer\n"); ps_printf(p, "%%%%Pages: %zu\n", p->ps->pages); ps_printf(p, "%%%%EOF\n"); return; } pdf_obj(p, 2); ps_printf(p, "<<\n/Type /Pages\n"); ps_printf(p, "/MediaBox [0 0 %zu %zu]\n", (size_t)AFM2PNT(p, p->ps->width), (size_t)AFM2PNT(p, p->ps->height)); ps_printf(p, "/Count %zu\n", p->ps->pages); ps_printf(p, "/Kids ["); for (i = 0; i < p->ps->pages; i++) ps_printf(p, " %zu 0 R", i * 4 + p->ps->pdfbody + 3); base = (p->ps->pages - 1) * 4 + p->ps->pdfbody + 4; ps_printf(p, "]\n>>\nendobj\n"); pdf_obj(p, base); ps_printf(p, "<<\n"); ps_printf(p, "/Type /Catalog\n"); ps_printf(p, "/Pages 2 0 R\n"); ps_printf(p, ">>\nendobj\n"); xref = p->ps->pdfbytes; ps_printf(p, "xref\n"); ps_printf(p, "0 %zu\n", base + 1); ps_printf(p, "0000000000 65535 f \n"); for (i = 0; i < base; i++) ps_printf(p, "%.10zu 00000 n \n", p->ps->pdfobjs[(int)i]); ps_printf(p, "trailer\n"); ps_printf(p, "<<\n"); ps_printf(p, "/Size %zu\n", base + 1); ps_printf(p, "/Root %zu 0 R\n", base); ps_printf(p, "/Info 1 0 R\n"); ps_printf(p, ">>\n"); ps_printf(p, "startxref\n"); ps_printf(p, "%zu\n", xref); ps_printf(p, "%%%%EOF\n"); } static void ps_begin(struct termp *p) { size_t width, height; int i; /* * Print margins into margin buffer. Nothing gets output to the * screen yet, so we don't need to initialise the primary state. */ if (p->ps->psmarg) { assert(p->ps->psmargsz); p->ps->psmarg[0] = '\0'; } /*p->ps->pdfbytes = 0;*/ p->ps->psmargcur = 0; p->ps->flags = PS_MARGINS; p->ps->pscol = p->ps->left; p->ps->psrow = p->ps->header; p->ps->lastrow = 0; /* impossible row */ ps_setfont(p, TERMFONT_NONE); (*p->headf)(p, p->argf); (*p->endline)(p); p->ps->pscol = p->ps->left; p->ps->psrow = p->ps->footer; (*p->footf)(p, p->argf); (*p->endline)(p); p->ps->flags &= ~PS_MARGINS; assert(0 == p->ps->flags); assert(p->ps->psmarg); assert('\0' != p->ps->psmarg[0]); /* * Print header and initialise page state. Following this, * stuff gets printed to the screen, so make sure we're sane. */ if (TERMTYPE_PS == p->type) { width = AFM2PNT(p, p->ps->width); height = AFM2PNT(p, p->ps->height); ps_printf(p, "%%!PS-Adobe-3.0\n"); ps_printf(p, "%%%%DocumentData: Clean7Bit\n"); ps_printf(p, "%%%%Orientation: Portrait\n"); ps_printf(p, "%%%%Pages: (atend)\n"); ps_printf(p, "%%%%PageOrder: Ascend\n"); ps_printf(p, "%%%%DocumentMedia: man-%s %zu %zu 0 () ()\n", p->ps->medianame, width, height); ps_printf(p, "%%%%DocumentNeededResources: font"); for (i = 0; i < (int)TERMFONT__MAX; i++) ps_printf(p, " %s", fonts[i].name); ps_printf(p, "\n%%%%DocumentSuppliedResources: " "procset MandocProcs 1.0 0\n"); ps_printf(p, "%%%%EndComments\n"); ps_printf(p, "%%%%BeginProlog\n"); ps_printf(p, "%%%%BeginResource: procset MandocProcs " "10170 10170\n"); /* The font size is effectively hard-coded for now. */ ps_printf(p, "/fs %zu def\n", p->ps->scale); for (i = 0; i < (int)TERMFONT__MAX; i++) ps_printf(p, "/f%d { /%s fs selectfont } def\n", i, fonts[i].name); ps_printf(p, "/s { 3 1 roll moveto show } bind def\n"); ps_printf(p, "/c { exch currentpoint exch pop " "moveto show } bind def\n"); ps_printf(p, "%%%%EndResource\n"); ps_printf(p, "%%%%EndProlog\n"); ps_printf(p, "%%%%BeginSetup\n"); ps_printf(p, "%%%%BeginFeature: *PageSize %s\n", p->ps->medianame); ps_printf(p, "<>setpagedevice\n", width, height); ps_printf(p, "%%%%EndFeature\n"); ps_printf(p, "%%%%EndSetup\n"); } else { ps_printf(p, "%%PDF-1.1\n"); pdf_obj(p, 1); ps_printf(p, "<<\n"); ps_printf(p, ">>\n"); ps_printf(p, "endobj\n"); for (i = 0; i < (int)TERMFONT__MAX; i++) { pdf_obj(p, (size_t)i + 3); ps_printf(p, "<<\n"); ps_printf(p, "/Type /Font\n"); ps_printf(p, "/Subtype /Type1\n"); ps_printf(p, "/Name /F%d\n", i); ps_printf(p, "/BaseFont /%s\n", fonts[i].name); ps_printf(p, ">>\nendobj\n"); } } p->ps->pdfbody = (size_t)TERMFONT__MAX + 3; p->ps->pscol = p->ps->left; p->ps->psrow = p->ps->top; p->ps->flags |= PS_NEWPAGE; ps_setfont(p, TERMFONT_NONE); } static void ps_pletter(struct termp *p, int c) { int f; /* * If we haven't opened a page context, then output that we're * in a new page and make sure the font is correctly set. */ if (PS_NEWPAGE & p->ps->flags) { if (TERMTYPE_PS == p->type) { ps_printf(p, "%%%%Page: %zu %zu\n", p->ps->pages + 1, p->ps->pages + 1); ps_printf(p, "f%d\n", (int)p->ps->lastf); } else { pdf_obj(p, p->ps->pdfbody + p->ps->pages * 4); ps_printf(p, "<<\n"); ps_printf(p, "/Length %zu 0 R\n", p->ps->pdfbody + 1 + p->ps->pages * 4); ps_printf(p, ">>\nstream\n"); } p->ps->pdflastpg = p->ps->pdfbytes; p->ps->flags &= ~PS_NEWPAGE; } /* * If we're not in a PostScript "word" context, then open one * now at the current cursor. */ if ( ! (PS_INLINE & p->ps->flags)) { if (TERMTYPE_PS != p->type) { ps_printf(p, "BT\n/F%d %zu Tf\n", (int)p->ps->lastf, p->ps->scale); ps_printf(p, "%.3f %.3f Td\n(", AFM2PNT(p, p->ps->pscol), AFM2PNT(p, p->ps->psrow)); } else { ps_printf(p, "%.3f", AFM2PNT(p, p->ps->pscol)); if (p->ps->psrow != p->ps->lastrow) ps_printf(p, " %.3f", AFM2PNT(p, p->ps->psrow)); ps_printf(p, "("); } p->ps->flags |= PS_INLINE; } assert( ! (PS_NEWPAGE & p->ps->flags)); /* * We need to escape these characters as per the PostScript * specification. We would also escape non-graphable characters * (like tabs), but none of them would get to this point and * it's superfluous to abort() on them. */ switch (c) { case '(': case ')': case '\\': ps_putchar(p, '\\'); break; default: break; } /* Write the character and adjust where we are on the page. */ f = (int)p->ps->lastf; if (c <= 32 || c - 32 >= MAXCHAR) c = 32; ps_putchar(p, (char)c); c -= 32; p->ps->pscol += (size_t)fonts[f].gly[c].wx; } static void ps_pclose(struct termp *p) { /* * Spit out that we're exiting a word context (this is a * "partial close" because we don't check the last-char buffer * or anything). */ if ( ! (PS_INLINE & p->ps->flags)) return; if (TERMTYPE_PS != p->type) ps_printf(p, ") Tj\nET\n"); else if (p->ps->psrow == p->ps->lastrow) ps_printf(p, ")c\n"); else { ps_printf(p, ")s\n"); p->ps->lastrow = p->ps->psrow; } p->ps->flags &= ~PS_INLINE; } /* If we have a `last' char that wasn't printed yet, print it now. */ static void ps_plast(struct termp *p) { size_t wx; if (p->ps->last == '\0') return; /* Check the font mode; open a new scope if it doesn't match. */ if (p->ps->nextf != p->ps->lastf) { ps_pclose(p); ps_setfont(p, p->ps->nextf); } p->ps->nextf = TERMFONT_NONE; /* * For an overstrike, if a previous character * was wider, advance to center the new one. */ if (p->ps->pscolnext) { wx = fonts[p->ps->lastf].gly[(int)p->ps->last-32].wx; if (p->ps->pscol + wx < p->ps->pscolnext) p->ps->pscol = (p->ps->pscol + p->ps->pscolnext - wx) / 2; } ps_pletter(p, p->ps->last); p->ps->last = '\0'; /* * For an overstrike, if a previous character * was wider, advance to the end of the old one. */ if (p->ps->pscol < p->ps->pscolnext) { ps_pclose(p); p->ps->pscol = p->ps->pscolnext; } } static void ps_letter(struct termp *p, int arg) { size_t savecol; char c; c = arg >= 128 || arg <= 0 ? '?' : arg; /* * When receiving a backspace, merely flag it. * We don't know yet whether it is * a font instruction or an overstrike. */ if (c == '\b') { assert(p->ps->last != '\0'); assert( ! (p->ps->flags & PS_BACKSP)); p->ps->flags |= PS_BACKSP; return; } /* * Decode font instructions. */ if (p->ps->flags & PS_BACKSP) { if (p->ps->last == '_') { switch (p->ps->nextf) { case TERMFONT_BI: break; case TERMFONT_BOLD: p->ps->nextf = TERMFONT_BI; break; default: p->ps->nextf = TERMFONT_UNDER; } p->ps->last = c; p->ps->flags &= ~PS_BACKSP; return; } if (p->ps->last == c) { switch (p->ps->nextf) { case TERMFONT_BI: break; case TERMFONT_UNDER: p->ps->nextf = TERMFONT_BI; break; default: p->ps->nextf = TERMFONT_BOLD; } p->ps->flags &= ~PS_BACKSP; return; } /* * This is not a font instruction, but rather * the next character. Prepare for overstrike. */ savecol = p->ps->pscol; } else savecol = SIZE_MAX; /* * We found the next character, so the font instructions * for the previous one are complete. * Use them and print it. */ ps_plast(p); /* * Do not print the current character yet because font * instructions might follow; only remember the character. * It will get printed later from ps_plast(). */ p->ps->last = c; /* * For an overstrike, back up to the previous position. * If the previous character is wider than any it overstrikes, * remember the current position, because it might also be * wider than all that will overstrike it. */ if (savecol != SIZE_MAX) { if (p->ps->pscolnext < p->ps->pscol) p->ps->pscolnext = p->ps->pscol; ps_pclose(p); p->ps->pscol = savecol; p->ps->flags &= ~PS_BACKSP; } else p->ps->pscolnext = 0; } static void ps_advance(struct termp *p, size_t len) { /* * Advance some spaces. This can probably be made smarter, * i.e., to have multiple space-separated words in the same * scope, but this is easier: just close out the current scope * and readjust our column settings. */ ps_plast(p); ps_pclose(p); p->ps->pscol += len; } static void ps_endline(struct termp *p) { /* Close out any scopes we have open: we're at eoln. */ ps_plast(p); ps_pclose(p); /* * If we're in the margin, don't try to recalculate our current * row. XXX: if the column tries to be fancy with multiple * lines, we'll do nasty stuff. */ if (PS_MARGINS & p->ps->flags) return; /* Left-justify. */ p->ps->pscol = p->ps->left; /* If we haven't printed anything, return. */ if (PS_NEWPAGE & p->ps->flags) return; /* * Put us down a line. If we're at the page bottom, spit out a * showpage and restart our row. */ if (p->ps->psrow >= p->ps->lineheight + p->ps->bottom) { p->ps->psrow -= p->ps->lineheight; return; } ps_closepage(p); if ((int)p->tcol->offset > p->ti) p->tcol->offset -= p->ti; else p->tcol->offset = 0; p->ti = 0; } static void ps_setfont(struct termp *p, enum termfont f) { assert(f < TERMFONT__MAX); p->ps->lastf = f; /* * If we're still at the top of the page, let the font-setting * be delayed until we actually have stuff to print. */ if (PS_NEWPAGE & p->ps->flags) return; if (TERMTYPE_PS == p->type) ps_printf(p, "f%d\n", (int)f); else ps_printf(p, "/F%d %zu Tf\n", (int)f, p->ps->scale); } static size_t ps_width(const struct termp *p, int c) { if (c <= 32 || c - 32 >= MAXCHAR) c = 0; else c -= 32; return (size_t)fonts[(int)TERMFONT_NONE].gly[c].wx; } static int ps_hspan(const struct termp *p, const struct roffsu *su) { double r; /* * All of these measurements are derived by converting from the * native measurement to AFM units. */ switch (su->unit) { case SCALE_BU: /* * Traditionally, the default unit is fixed to the * output media. So this would refer to the point. In * mandoc(1), however, we stick to the default terminal * scaling unit so that output is the same regardless * the media. */ r = PNT2AFM(p, su->scale * 72.0 / 240.0); break; case SCALE_CM: r = PNT2AFM(p, su->scale * 72.0 / 2.54); break; case SCALE_EM: r = su->scale * fonts[(int)TERMFONT_NONE].gly[109 - 32].wx; break; case SCALE_EN: r = su->scale * fonts[(int)TERMFONT_NONE].gly[110 - 32].wx; break; case SCALE_IN: r = PNT2AFM(p, su->scale * 72.0); break; case SCALE_MM: r = su->scale * fonts[(int)TERMFONT_NONE].gly[109 - 32].wx / 100.0; break; case SCALE_PC: r = PNT2AFM(p, su->scale * 12.0); break; case SCALE_PT: r = PNT2AFM(p, su->scale * 1.0); break; case SCALE_VS: r = su->scale * p->ps->lineheight; break; default: r = su->scale; break; } return r * 24.0; } static void ps_growbuf(struct termp *p, size_t sz) { if (p->ps->psmargcur + sz <= p->ps->psmargsz) return; if (sz < PS_BUFSLOP) sz = PS_BUFSLOP; p->ps->psmargsz += sz; p->ps->psmarg = mandoc_realloc(p->ps->psmarg, p->ps->psmargsz); } /* $Id: term_tab.c,v 1.6 2020/06/22 19:20:40 schwarze Exp $ */ /* * 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 "mandoc_aux.h" #include "out.h" #include "term.h" struct tablist { size_t *t; /* Allocated array of tab positions. */ size_t s; /* Allocated number of positions. */ size_t n; /* Currently used number of positions. */ }; static struct { struct tablist a; /* All tab positions for lookup. */ struct tablist p; /* Periodic tab positions to add. */ size_t d; /* Default tab width in units of n. */ } tabs; void term_tab_set(const struct termp *p, const char *arg) { static int recording_period; struct roffsu su; struct tablist *tl; size_t pos; int add; /* Special arguments: clear all tabs or switch lists. */ if (arg == NULL) { tabs.a.n = tabs.p.n = 0; recording_period = 0; if (tabs.d == 0) { a2roffsu(".8i", &su, SCALE_IN); tabs.d = term_hen(p, &su); } return; } if (arg[0] == 'T' && arg[1] == '\0') { recording_period = 1; return; } /* Parse the sign, the number, and the unit. */ if (*arg == '+') { add = 1; arg++; } else add = 0; if (a2roffsu(arg, &su, SCALE_EM) == NULL) return; /* Select the list, and extend it if it is full. */ tl = recording_period ? &tabs.p : &tabs.a; if (tl->n >= tl->s) { tl->s += 8; tl->t = mandoc_reallocarray(tl->t, tl->s, sizeof(*tl->t)); } /* Append the new position. */ pos = term_hen(p, &su); tl->t[tl->n] = pos; if (add && tl->n) tl->t[tl->n] += tl->t[tl->n - 1]; tl->n++; } /* * Simplified version without a parser, * never incremental, never periodic, for use by tbl(7). */ void term_tab_iset(size_t inc) { if (tabs.a.n >= tabs.a.s) { tabs.a.s += 8; tabs.a.t = mandoc_reallocarray(tabs.a.t, tabs.a.s, sizeof(*tabs.a.t)); } tabs.a.t[tabs.a.n++] = inc; } size_t term_tab_next(size_t prev) { size_t i, j; for (i = 0;; i++) { if (i == tabs.a.n) { if (tabs.p.n == 0) return prev; tabs.a.n += tabs.p.n; if (tabs.a.s < tabs.a.n) { tabs.a.s = tabs.a.n; tabs.a.t = mandoc_reallocarray(tabs.a.t, tabs.a.s, sizeof(*tabs.a.t)); } for (j = 0; j < tabs.p.n; j++) tabs.a.t[i + j] = tabs.p.t[j] + (i ? tabs.a.t[i - 1] : 0); } if (prev < tabs.a.t[i]) return tabs.a.t[i]; } } /* $Id: term_tag.c,v 1.6 2021/03/30 17:16:55 schwarze Exp $ */ /* * Copyright (c) 2015,2016,2018,2019,2020 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. * * Functions to write a ctags(1) file. * For use by the mandoc(1) ASCII and UTF-8 formatters only. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "mandoc.h" #include "roff.h" #include "roff_int.h" #include "tag.h" #include "term_tag.h" static void tag_signal(int) __attribute__((__noreturn__)); static struct tag_files tag_files; /* * Prepare for using a pager. * Not all pagers are capable of using a tag file, * but for simplicity, create it anyway. */ struct tag_files * term_tag_init(const char *outfilename, const char *suffix, const char *tagfilename) { struct sigaction sa; int ofd; /* In /tmp/, dup(2)ed to stdout. */ int tfd; ofd = tfd = -1; tag_files.tfs = NULL; tag_files.tcpgid = -1; /* Clean up when dying from a signal. */ memset(&sa, 0, sizeof(sa)); sigfillset(&sa.sa_mask); sa.sa_handler = tag_signal; sigaction(SIGHUP, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); /* * POSIX requires that a process calling tcsetpgrp(3) * from the background gets a SIGTTOU signal. * In that case, do not stop. */ sa.sa_handler = SIG_IGN; sigaction(SIGTTOU, &sa, NULL); /* Save the original standard output for use by the pager. */ if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) { mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); goto fail; } /* Create both temporary output files. */ if (outfilename == NULL) { (void)snprintf(tag_files.ofn, sizeof(tag_files.ofn), "/tmp/man.XXXXXXXXXX%s", suffix); if ((ofd = mkstemps(tag_files.ofn, strlen(suffix))) == -1) { mandoc_msg(MANDOCERR_MKSTEMP, 0, 0, "%s: %s", tag_files.ofn, strerror(errno)); goto fail; } } else { (void)strlcpy(tag_files.ofn, outfilename, sizeof(tag_files.ofn)); unlink(outfilename); ofd = open(outfilename, O_WRONLY | O_CREAT | O_EXCL, 0644); if (ofd == -1) { mandoc_msg(MANDOCERR_OPEN, 0, 0, "%s: %s", outfilename, strerror(errno)); goto fail; } } if (tagfilename == NULL) { (void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX", sizeof(tag_files.tfn)); if ((tfd = mkstemp(tag_files.tfn)) == -1) { mandoc_msg(MANDOCERR_MKSTEMP, 0, 0, "%s: %s", tag_files.tfn, strerror(errno)); goto fail; } } else { (void)strlcpy(tag_files.tfn, tagfilename, sizeof(tag_files.tfn)); unlink(tagfilename); tfd = open(tagfilename, O_WRONLY | O_CREAT | O_EXCL, 0644); if (tfd == -1) { mandoc_msg(MANDOCERR_OPEN, 0, 0, "%s: %s", tagfilename, strerror(errno)); goto fail; } } if ((tag_files.tfs = fdopen(tfd, "w")) == NULL) { mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno)); goto fail; } tfd = -1; if (dup2(ofd, STDOUT_FILENO) == -1) { mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); goto fail; } close(ofd); return &tag_files; fail: term_tag_unlink(); if (ofd != -1) close(ofd); if (tfd != -1) close(tfd); if (tag_files.ofd != -1) { close(tag_files.ofd); tag_files.ofd = -1; } return NULL; } void term_tag_write(struct roff_node *n, size_t line) { const char *cp; int len; if (tag_files.tfs == NULL) return; cp = n->tag == NULL ? n->child->string : n->tag; if (cp[0] == '\\' && (cp[1] == '&' || cp[1] == 'e')) cp += 2; len = strcspn(cp, " \t\\"); fprintf(tag_files.tfs, "%.*s %s %zu\n", len, cp, tag_files.ofn, line); } /* * Close both output files and restore the original standard output * to the terminal. In the unlikely case that the latter fails, * trying to start a pager would be useless, so report the failure * to the main program. */ int term_tag_close(void) { int irc = 0; if (tag_files.tfs != NULL) { fclose(tag_files.tfs); tag_files.tfs = NULL; } if (tag_files.ofd != -1) { fflush(stdout); if ((irc = dup2(tag_files.ofd, STDOUT_FILENO)) == -1) mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); close(tag_files.ofd); tag_files.ofd = -1; } return irc; } void term_tag_unlink(void) { pid_t tc_pgid; if (tag_files.tcpgid != -1) { tc_pgid = tcgetpgrp(STDOUT_FILENO); if (tc_pgid == tag_files.pager_pid || tc_pgid == getpgid(0) || getpgid(tc_pgid) == -1) (void)tcsetpgrp(STDOUT_FILENO, tag_files.tcpgid); } if (strncmp(tag_files.ofn, "/tmp/man.", 9) == 0) { unlink(tag_files.ofn); *tag_files.ofn = '\0'; } if (strncmp(tag_files.tfn, "/tmp/man.", 9) == 0) { unlink(tag_files.tfn); *tag_files.tfn = '\0'; } } static void tag_signal(int signum) { struct sigaction sa; term_tag_unlink(); memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_DFL; sigaction(signum, &sa, NULL); kill(getpid(), signum); /* NOTREACHED */ _exit(1); } /* $Id: term_tag.h,v 1.4 2021/03/30 17:16:55 schwarze Exp $ */ /* * Copyright (c) 2015, 2018, 2019, 2020 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. * * Internal interfaces to write a ctags(1) file. * For use by the mandoc(1) ASCII and UTF-8 formatters only. */ struct tag_files { char ofn[80]; /* Output file name. */ char tfn[80]; /* Tag file name. */ FILE *tfs; /* Tag file object. */ int ofd; /* Original output file descriptor. */ pid_t tcpgid; /* Process group controlling the terminal. */ pid_t pager_pid; /* Process ID of the pager. */ }; struct tag_files *term_tag_init(const char *, const char *, const char *); void term_tag_write(struct roff_node *, size_t); int term_tag_close(void); void term_tag_unlink(void); /* $Id: tree.c,v 1.91 2021/09/07 10:59:18 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2011, 2014 Kristaps Dzonsons * Copyright (c) 2013-2015, 2017-2021 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. * * Formatting module to let mandoc(1) show * a human readable representation of the syntax tree. */ #include "config.h" #include #include #include #include #include #include #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "man.h" #include "tbl.h" #include "eqn.h" #include "main.h" static void print_attr(const struct roff_node *); static void print_box(const struct eqn_box *, int); static void print_cellt(enum tbl_cellt); static void print_man(const struct roff_node *, int); static void print_meta(const struct roff_meta *); static void print_mdoc(const struct roff_node *, int); static void print_span(const struct tbl_span *, int); void tree_mdoc(void *arg, const struct roff_meta *mdoc) { print_meta(mdoc); putchar('\n'); print_mdoc(mdoc->first->child, 0); } void tree_man(void *arg, const struct roff_meta *man) { print_meta(man); if (man->hasbody == 0) puts("body = empty"); putchar('\n'); print_man(man->first->child, 0); } static void print_meta(const struct roff_meta *meta) { if (meta->title != NULL) printf("title = \"%s\"\n", meta->title); if (meta->name != NULL) printf("name = \"%s\"\n", meta->name); if (meta->msec != NULL) printf("sec = \"%s\"\n", meta->msec); if (meta->vol != NULL) printf("vol = \"%s\"\n", meta->vol); if (meta->arch != NULL) printf("arch = \"%s\"\n", meta->arch); if (meta->os != NULL) printf("os = \"%s\"\n", meta->os); if (meta->date != NULL) printf("date = \"%s\"\n", meta->date); } static void print_mdoc(const struct roff_node *n, int indent) { const char *p, *t; int i, j; size_t argc; struct mdoc_argv *argv; if (n == NULL) return; argv = NULL; argc = 0; t = p = NULL; switch (n->type) { case ROFFT_ROOT: t = "root"; break; case ROFFT_BLOCK: t = "block"; break; case ROFFT_HEAD: t = "head"; break; case ROFFT_BODY: if (n->end) t = "body-end"; else t = "body"; break; case ROFFT_TAIL: t = "tail"; break; case ROFFT_ELEM: t = "elem"; break; case ROFFT_TEXT: t = "text"; break; case ROFFT_COMMENT: t = "comment"; break; case ROFFT_TBL: break; case ROFFT_EQN: t = "eqn"; break; default: abort(); } switch (n->type) { case ROFFT_TEXT: case ROFFT_COMMENT: p = n->string; break; case ROFFT_BODY: p = roff_name[n->tok]; break; case ROFFT_HEAD: p = roff_name[n->tok]; break; case ROFFT_TAIL: p = roff_name[n->tok]; break; case ROFFT_ELEM: p = roff_name[n->tok]; if (n->args) { argv = n->args->argv; argc = n->args->argc; } break; case ROFFT_BLOCK: p = roff_name[n->tok]; if (n->args) { argv = n->args->argv; argc = n->args->argc; } break; case ROFFT_TBL: break; case ROFFT_EQN: p = "EQ"; break; case ROFFT_ROOT: p = "root"; break; default: abort(); } if (n->span) { assert(NULL == p && NULL == t); print_span(n->span, indent); } else { for (i = 0; i < indent; i++) putchar(' '); printf("%s (%s)", p, t); for (i = 0; i < (int)argc; i++) { printf(" -%s", mdoc_argnames[argv[i].arg]); if (argv[i].sz > 0) printf(" ["); for (j = 0; j < (int)argv[i].sz; j++) printf(" [%s]", argv[i].value[j]); if (argv[i].sz > 0) printf(" ]"); } print_attr(n); } if (n->eqn) print_box(n->eqn->first, indent + 4); if (n->child) print_mdoc(n->child, indent + (n->type == ROFFT_BLOCK ? 2 : 4)); if (n->next) print_mdoc(n->next, indent); } static void print_man(const struct roff_node *n, int indent) { const char *p, *t; int i; if (n == NULL) return; t = p = NULL; switch (n->type) { case ROFFT_ROOT: t = "root"; break; case ROFFT_ELEM: t = "elem"; break; case ROFFT_TEXT: t = "text"; break; case ROFFT_COMMENT: t = "comment"; break; case ROFFT_BLOCK: t = "block"; break; case ROFFT_HEAD: t = "head"; break; case ROFFT_BODY: t = "body"; break; case ROFFT_TBL: break; case ROFFT_EQN: t = "eqn"; break; default: abort(); } switch (n->type) { case ROFFT_TEXT: case ROFFT_COMMENT: p = n->string; break; case ROFFT_ELEM: case ROFFT_BLOCK: case ROFFT_HEAD: case ROFFT_BODY: p = roff_name[n->tok]; break; case ROFFT_ROOT: p = "root"; break; case ROFFT_TBL: break; case ROFFT_EQN: p = "EQ"; break; default: abort(); } if (n->span) { assert(NULL == p && NULL == t); print_span(n->span, indent); } else { for (i = 0; i < indent; i++) putchar(' '); printf("%s (%s)", p, t); print_attr(n); } if (n->eqn) print_box(n->eqn->first, indent + 4); if (n->child) print_man(n->child, indent + (n->type == ROFFT_BLOCK ? 2 : 4)); if (n->next) print_man(n->next, indent); } static void print_attr(const struct roff_node *n) { putchar(' '); if (n->flags & NODE_DELIMO) putchar('('); if (n->flags & NODE_LINE) putchar('*'); printf("%d:%d", n->line, n->pos + 1); if (n->flags & NODE_DELIMC) putchar(')'); if (n->flags & NODE_EOS) putchar('.'); if (n->flags & NODE_ID) { printf(" ID"); if (n->flags & NODE_HREF) printf("=HREF"); } else if (n->flags & NODE_HREF) printf(" HREF"); else if (n->tag != NULL) printf(" STRAYTAG"); if (n->tag != NULL) printf("=%s", n->tag); if (n->flags & NODE_BROKEN) printf(" BROKEN"); if (n->flags & NODE_NOFILL) printf(" NOFILL"); if (n->flags & NODE_NOSRC) printf(" NOSRC"); if (n->flags & NODE_NOPRT) printf(" NOPRT"); putchar('\n'); } static void print_box(const struct eqn_box *ep, int indent) { int i; const char *t; static const char *posnames[] = { NULL, "sup", "subsup", "sub", "to", "from", "fromto", "over", "sqrt", NULL }; if (NULL == ep) return; for (i = 0; i < indent; i++) putchar(' '); t = NULL; switch (ep->type) { case EQN_LIST: t = "eqn-list"; break; case EQN_SUBEXPR: t = "eqn-expr"; break; case EQN_TEXT: t = "eqn-text"; break; case EQN_PILE: t = "eqn-pile"; break; case EQN_MATRIX: t = "eqn-matrix"; break; } fputs(t, stdout); if (ep->pos) printf(" pos=%s", posnames[ep->pos]); if (ep->left) printf(" left=\"%s\"", ep->left); if (ep->right) printf(" right=\"%s\"", ep->right); if (ep->top) printf(" top=\"%s\"", ep->top); if (ep->bottom) printf(" bottom=\"%s\"", ep->bottom); if (ep->text) printf(" text=\"%s\"", ep->text); if (ep->font) printf(" font=%d", ep->font); if (ep->size != EQN_DEFSIZE) printf(" size=%d", ep->size); if (ep->expectargs != UINT_MAX && ep->expectargs != ep->args) printf(" badargs=%zu(%zu)", ep->args, ep->expectargs); else if (ep->args) printf(" args=%zu", ep->args); putchar('\n'); print_box(ep->first, indent + 4); print_box(ep->next, indent); } static void print_cellt(enum tbl_cellt pos) { switch(pos) { case TBL_CELL_LEFT: putchar('L'); break; case TBL_CELL_LONG: putchar('a'); break; case TBL_CELL_CENTRE: putchar('c'); break; case TBL_CELL_RIGHT: putchar('r'); break; case TBL_CELL_NUMBER: putchar('n'); break; case TBL_CELL_SPAN: putchar('s'); break; case TBL_CELL_DOWN: putchar('^'); break; case TBL_CELL_HORIZ: putchar('-'); break; case TBL_CELL_DHORIZ: putchar('='); break; case TBL_CELL_MAX: putchar('#'); break; } } static void print_span(const struct tbl_span *sp, int indent) { const struct tbl_dat *dp; const struct tbl_cell *cp; int i; if (sp->prev == NULL) { for (i = 0; i < indent; i++) putchar(' '); printf("%d", sp->opts->cols); if (sp->opts->opts & TBL_OPT_CENTRE) fputs(" center", stdout); if (sp->opts->opts & TBL_OPT_EXPAND) fputs(" expand", stdout); if (sp->opts->opts & TBL_OPT_ALLBOX) fputs(" allbox", stdout); if (sp->opts->opts & TBL_OPT_BOX) fputs(" box", stdout); if (sp->opts->opts & TBL_OPT_DBOX) fputs(" doublebox", stdout); if (sp->opts->opts & TBL_OPT_NOKEEP) fputs(" nokeep", stdout); if (sp->opts->opts & TBL_OPT_NOSPACE) fputs(" nospaces", stdout); if (sp->opts->opts & TBL_OPT_NOWARN) fputs(" nowarn", stdout); printf(" (tbl options) %d:1\n", sp->line); } for (i = 0; i < indent; i++) putchar(' '); switch (sp->pos) { case TBL_SPAN_HORIZ: putchar('-'); putchar(' '); break; case TBL_SPAN_DHORIZ: putchar('='); putchar(' '); break; default: for (cp = sp->layout->first; cp != NULL; cp = cp->next) print_cellt(cp->pos); putchar(' '); for (dp = sp->first; dp; dp = dp->next) { if ((cp = dp->layout) == NULL) putchar('*'); else { printf("%d", cp->col); print_cellt(dp->layout->pos); switch (cp->font) { case ESCAPE_FONTROMAN: break; case ESCAPE_FONTBOLD: putchar('b'); break; case ESCAPE_FONTITALIC: putchar('i'); break; case ESCAPE_FONTBI: fputs("bi", stdout); break; case ESCAPE_FONTCR: putchar('c'); break; case ESCAPE_FONTCB: fputs("cb", stdout); break; case ESCAPE_FONTCI: fputs("ci", stdout); break; default: abort(); } if (cp->flags & TBL_CELL_TALIGN) putchar('t'); if (cp->flags & TBL_CELL_UP) putchar('u'); if (cp->flags & TBL_CELL_BALIGN) putchar('d'); if (cp->flags & TBL_CELL_WIGN) putchar('z'); if (cp->flags & TBL_CELL_EQUAL) putchar('e'); if (cp->flags & TBL_CELL_WMAX) putchar('x'); } switch (dp->pos) { case TBL_DATA_HORIZ: case TBL_DATA_NHORIZ: putchar('-'); break; case TBL_DATA_DHORIZ: case TBL_DATA_NDHORIZ: putchar('='); break; default: putchar(dp->block ? '{' : '['); if (dp->string != NULL) fputs(dp->string, stdout); putchar(dp->block ? '}' : ']'); break; } if (dp->hspans) printf(">%d", dp->hspans); if (dp->vspans) printf("v%d", dp->vspans); putchar(' '); } break; } printf("(tbl) %d:1\n", sp->line); }