From 8038763877d6afcf120af7abea9e6738ce9ed3c9 Mon Sep 17 00:00:00 2001 From: Sascha Wildner Date: Mon, 7 Mar 2011 05:57:19 +0100 Subject: [PATCH] Import mdocml-1.10.9 --- contrib/mdocml/ChangeLog.xsl | 43 + contrib/mdocml/Makefile | 345 ++++ contrib/mdocml/arch.c | 38 + contrib/mdocml/arch.in | 56 + contrib/mdocml/att.c | 38 + contrib/mdocml/att.in | 37 + contrib/mdocml/chars.c | 243 +++ contrib/mdocml/chars.h | 36 + contrib/mdocml/chars.in | 425 +++++ contrib/mdocml/compat.c | 95 + contrib/mdocml/config.h.post | 25 + contrib/mdocml/config.h.pre | 6 + contrib/mdocml/example.style.css | 146 ++ contrib/mdocml/external.png | Bin 0 -> 165 bytes contrib/mdocml/html.c | 798 ++++++++ contrib/mdocml/html.h | 157 ++ contrib/mdocml/index.css | 48 + contrib/mdocml/index.sgml | 398 ++++ contrib/mdocml/lib.c | 38 + contrib/mdocml/lib.in | 93 + contrib/mdocml/libman.h | 92 + contrib/mdocml/libmandoc.h | 38 + contrib/mdocml/libmdoc.h | 147 ++ contrib/mdocml/libroff.h | 62 + contrib/mdocml/main.c | 1040 +++++++++++ contrib/mdocml/main.h | 56 + contrib/mdocml/man.3 | 272 +++ contrib/mdocml/man.7 | 939 ++++++++++ contrib/mdocml/man.c | 666 +++++++ contrib/mdocml/man.h | 130 ++ contrib/mdocml/man_argv.c | 44 + contrib/mdocml/man_hash.c | 106 ++ contrib/mdocml/man_html.c | 684 +++++++ contrib/mdocml/man_macro.c | 477 +++++ contrib/mdocml/man_term.c | 1031 +++++++++++ contrib/mdocml/man_validate.c | 577 ++++++ contrib/mdocml/mandoc.1 | 569 ++++++ contrib/mdocml/mandoc.c | 487 +++++ contrib/mdocml/mandoc.h | 315 ++++ contrib/mdocml/mandoc_char.7 | 558 ++++++ contrib/mdocml/mdoc.3 | 348 ++++ contrib/mdocml/mdoc.7 | 2925 ++++++++++++++++++++++++++++++ contrib/mdocml/mdoc.c | 916 ++++++++++ contrib/mdocml/mdoc.h | 436 +++++ contrib/mdocml/mdoc_argv.c | 792 ++++++++ contrib/mdocml/mdoc_hash.c | 93 + contrib/mdocml/mdoc_html.c | 2139 ++++++++++++++++++++++ contrib/mdocml/mdoc_macro.c | 1753 ++++++++++++++++++ contrib/mdocml/mdoc_strings.c | 219 +++ contrib/mdocml/mdoc_term.c | 2200 ++++++++++++++++++++++ contrib/mdocml/mdoc_validate.c | 2239 +++++++++++++++++++++++ contrib/mdocml/msec.c | 37 + contrib/mdocml/msec.in | 40 + contrib/mdocml/out.c | 562 ++++++ contrib/mdocml/out.h | 90 + contrib/mdocml/roff.3 | 177 ++ contrib/mdocml/roff.7 | 619 +++++++ contrib/mdocml/roff.c | 1356 ++++++++++++++ contrib/mdocml/roff.h | 45 + contrib/mdocml/st.c | 38 + contrib/mdocml/st.in | 74 + contrib/mdocml/style.css | 146 ++ contrib/mdocml/tbl.7 | 322 ++++ contrib/mdocml/tbl.c | 159 ++ contrib/mdocml/tbl_data.c | 212 +++ contrib/mdocml/tbl_html.c | 126 ++ contrib/mdocml/tbl_layout.c | 408 +++++ contrib/mdocml/tbl_opts.c | 260 +++ contrib/mdocml/tbl_term.c | 425 +++++ contrib/mdocml/term.c | 740 ++++++++ contrib/mdocml/term.h | 156 ++ contrib/mdocml/term_ascii.c | 187 ++ contrib/mdocml/term_ps.c | 1161 ++++++++++++ contrib/mdocml/test-strlcat.c | 8 + contrib/mdocml/test-strlcpy.c | 8 + contrib/mdocml/tree.c | 289 +++ contrib/mdocml/vol.c | 38 + contrib/mdocml/vol.in | 35 + 78 files changed, 33133 insertions(+) create mode 100644 contrib/mdocml/ChangeLog.xsl create mode 100644 contrib/mdocml/Makefile create mode 100644 contrib/mdocml/arch.c create mode 100644 contrib/mdocml/arch.in create mode 100644 contrib/mdocml/att.c create mode 100644 contrib/mdocml/att.in create mode 100644 contrib/mdocml/chars.c create mode 100644 contrib/mdocml/chars.h create mode 100644 contrib/mdocml/chars.in create mode 100644 contrib/mdocml/compat.c create mode 100644 contrib/mdocml/config.h.post create mode 100644 contrib/mdocml/config.h.pre create mode 100644 contrib/mdocml/example.style.css create mode 100644 contrib/mdocml/external.png create mode 100644 contrib/mdocml/html.c create mode 100644 contrib/mdocml/html.h create mode 100644 contrib/mdocml/index.css create mode 100644 contrib/mdocml/index.sgml create mode 100644 contrib/mdocml/lib.c create mode 100644 contrib/mdocml/lib.in create mode 100644 contrib/mdocml/libman.h create mode 100644 contrib/mdocml/libmandoc.h create mode 100644 contrib/mdocml/libmdoc.h create mode 100644 contrib/mdocml/libroff.h create mode 100644 contrib/mdocml/main.c create mode 100644 contrib/mdocml/main.h create mode 100644 contrib/mdocml/man.3 create mode 100644 contrib/mdocml/man.7 create mode 100644 contrib/mdocml/man.c create mode 100644 contrib/mdocml/man.h create mode 100644 contrib/mdocml/man_argv.c create mode 100644 contrib/mdocml/man_hash.c create mode 100644 contrib/mdocml/man_html.c create mode 100644 contrib/mdocml/man_macro.c create mode 100644 contrib/mdocml/man_term.c create mode 100644 contrib/mdocml/man_validate.c create mode 100644 contrib/mdocml/mandoc.1 create mode 100644 contrib/mdocml/mandoc.c create mode 100644 contrib/mdocml/mandoc.h create mode 100644 contrib/mdocml/mandoc_char.7 create mode 100644 contrib/mdocml/mdoc.3 create mode 100644 contrib/mdocml/mdoc.7 create mode 100644 contrib/mdocml/mdoc.c create mode 100644 contrib/mdocml/mdoc.h create mode 100644 contrib/mdocml/mdoc_argv.c create mode 100644 contrib/mdocml/mdoc_hash.c create mode 100644 contrib/mdocml/mdoc_html.c create mode 100644 contrib/mdocml/mdoc_macro.c create mode 100644 contrib/mdocml/mdoc_strings.c create mode 100644 contrib/mdocml/mdoc_term.c create mode 100644 contrib/mdocml/mdoc_validate.c create mode 100644 contrib/mdocml/msec.c create mode 100644 contrib/mdocml/msec.in create mode 100644 contrib/mdocml/out.c create mode 100644 contrib/mdocml/out.h create mode 100644 contrib/mdocml/roff.3 create mode 100644 contrib/mdocml/roff.7 create mode 100644 contrib/mdocml/roff.c create mode 100644 contrib/mdocml/roff.h create mode 100644 contrib/mdocml/st.c create mode 100644 contrib/mdocml/st.in create mode 100644 contrib/mdocml/style.css create mode 100644 contrib/mdocml/tbl.7 create mode 100644 contrib/mdocml/tbl.c create mode 100644 contrib/mdocml/tbl_data.c create mode 100644 contrib/mdocml/tbl_html.c create mode 100644 contrib/mdocml/tbl_layout.c create mode 100644 contrib/mdocml/tbl_opts.c create mode 100644 contrib/mdocml/tbl_term.c create mode 100644 contrib/mdocml/term.c create mode 100644 contrib/mdocml/term.h create mode 100644 contrib/mdocml/term_ascii.c create mode 100644 contrib/mdocml/term_ps.c create mode 100644 contrib/mdocml/test-strlcat.c create mode 100644 contrib/mdocml/test-strlcpy.c create mode 100644 contrib/mdocml/tree.c create mode 100644 contrib/mdocml/vol.c create mode 100644 contrib/mdocml/vol.in diff --git a/contrib/mdocml/ChangeLog.xsl b/contrib/mdocml/ChangeLog.xsl new file mode 100644 index 0000000000..dccc79dba1 --- /dev/null +++ b/contrib/mdocml/ChangeLog.xsl @@ -0,0 +1,43 @@ + + + + + + + mdocml - CVS-ChangeLog + + + + +
+ Files modified by + +
+
+ + Note: + + +
    + +
  • + + + — Rev: + + , Status: + + + , Tag: + + + +
  • +
    +
+
+
+ + +
+
diff --git a/contrib/mdocml/Makefile b/contrib/mdocml/Makefile new file mode 100644 index 0000000000..23615926fc --- /dev/null +++ b/contrib/mdocml/Makefile @@ -0,0 +1,345 @@ +.SUFFIXES: .html .xml .sgml .1 .3 .7 .md5 .tar.gz +.SUFFIXES: .1.txt .3.txt .7.txt +.SUFFIXES: .1.xhtml .3.xhtml .7.xhtml +.SUFFIXES: .1.sgml .3.sgml .7.sgml +.SUFFIXES: .h .h.html +.SUFFIXES: .1.ps .3.ps .7.ps +.SUFFIXES: .1.pdf .3.pdf .7.pdf + +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin +INCLUDEDIR = $(PREFIX)/include +LIBDIR = $(PREFIX)/lib +MANDIR = $(PREFIX)/man +EXAMPLEDIR = $(PREFIX)/share/examples/mandoc +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 0444 +INSTALL_LIB = $(INSTALL) -m 0644 +INSTALL_MAN = $(INSTALL_DATA) + +VERSION = 1.10.9 +VDATE = 07 January 2010 + +VFLAGS = -DVERSION="\"$(VERSION)\"" +WFLAGS = -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings +CFLAGS += -g $(WFLAGS) $(VFLAGS) -DHAVE_CONFIG_H + +# Specify this if you want to hard-code the operating system to appear +# in the lower-left hand corner of -mdoc manuals. +# CFLAGS += -DOSNAME="\"OpenBSD 4.5\"" + +LINTFLAGS += $(VFLAGS) + +ROFFLNS = roff.ln tbl.ln tbl_opts.ln tbl_layout.ln tbl_data.ln + +ROFFSRCS = roff.c tbl.c tbl_opts.c tbl_layout.c tbl_data.c + +ROFFOBJS = roff.o tbl.o tbl_opts.o tbl_layout.o tbl_data.o + +MANDOCLNS = mandoc.ln + +MANDOCSRCS = mandoc.c + +MANDOCOBJS = mandoc.o + +MDOCLNS = mdoc_macro.ln mdoc.ln mdoc_hash.ln mdoc_strings.ln \ + mdoc_argv.ln mdoc_validate.ln \ + lib.ln att.ln arch.ln vol.ln msec.ln st.ln + +MDOCOBJS = mdoc_macro.o mdoc.o mdoc_hash.o mdoc_strings.o \ + mdoc_argv.o mdoc_validate.o lib.o att.o \ + arch.o vol.o msec.o st.o + +MDOCSRCS = mdoc_macro.c mdoc.c mdoc_hash.c mdoc_strings.c \ + mdoc_argv.c mdoc_validate.c lib.c att.c \ + arch.c vol.c msec.c st.c + +MANLNS = man_macro.ln man.ln man_hash.ln man_validate.ln \ + man_argv.ln + +MANOBJS = man_macro.o man.o man_hash.o man_validate.o \ + man_argv.o +MANSRCS = man_macro.c man.c man_hash.c man_validate.c \ + man_argv.c + +MAINLNS = main.ln mdoc_term.ln chars.ln term.ln tree.ln \ + compat.ln man_term.ln html.ln mdoc_html.ln \ + man_html.ln out.ln term_ps.ln term_ascii.ln \ + tbl_term.ln tbl_html.ln + +MAINOBJS = main.o mdoc_term.o chars.o term.o tree.o compat.o \ + man_term.o html.o mdoc_html.o man_html.o out.o \ + term_ps.o term_ascii.o tbl_term.o tbl_html.o + +MAINSRCS = main.c mdoc_term.c chars.c term.c tree.c compat.c \ + man_term.c html.c mdoc_html.c man_html.c out.c \ + term_ps.c term_ascii.c tbl_term.c tbl_html.c + +LLNS = llib-llibmdoc.ln llib-llibman.ln llib-lmandoc.ln \ + llib-llibmandoc.ln llib-llibroff.ln + +LNS = $(MAINLNS) $(MDOCLNS) $(MANLNS) \ + $(MANDOCLNS) $(ROFFLNS) + +LIBS = libmdoc.a libman.a libmandoc.a libroff.a + +OBJS = $(MDOCOBJS) $(MAINOBJS) $(MANOBJS) \ + $(MANDOCOBJS) $(ROFFOBJS) + +SRCS = $(MDOCSRCS) $(MAINSRCS) $(MANSRCS) \ + $(MANDOCSRCS) $(ROFFSRCS) + +DATAS = arch.in att.in lib.in msec.in st.in \ + vol.in chars.in + +HEADS = mdoc.h libmdoc.h man.h libman.h term.h \ + libmandoc.h html.h chars.h out.h main.h roff.h \ + mandoc.h libroff.h + +GSGMLS = mandoc.1.sgml mdoc.3.sgml mdoc.7.sgml \ + mandoc_char.7.sgml man.7.sgml man.3.sgml roff.7.sgml \ + roff.3.sgml tbl.7.sgml + +SGMLS = index.sgml + +XHTMLS = mandoc.1.xhtml mdoc.3.xhtml \ + man.3.xhtml mdoc.7.xhtml man.7.xhtml mandoc_char.7.xhtml \ + roff.7.xhtml roff.3.xhtml tbl.7.xhtml + +HTMLS = ChangeLog.html index.html man.h.html mdoc.h.html \ + mandoc.h.html roff.h.html mandoc.1.html mdoc.3.html \ + man.3.html mdoc.7.html man.7.html mandoc_char.7.html \ + roff.7.html roff.3.html tbl.7.html + +PSS = mandoc.1.ps mdoc.3.ps man.3.ps mdoc.7.ps man.7.ps \ + mandoc_char.7.ps roff.7.ps roff.3.ps tbl.7.ps + +PDFS = mandoc.1.pdf mdoc.3.pdf man.3.pdf mdoc.7.pdf man.7.pdf \ + mandoc_char.7.pdf roff.7.pdf roff.3.pdf tbl.7.pdf + +XSLS = ChangeLog.xsl + +TEXTS = mandoc.1.txt mdoc.3.txt man.3.txt mdoc.7.txt man.7.txt \ + mandoc_char.7.txt ChangeLog.txt \ + roff.7.txt roff.3.txt tbl.7.txt + +EXAMPLES = example.style.css + +XMLS = ChangeLog.xml + +STATICS = index.css style.css external.png + +MD5S = mdocml-$(VERSION).md5 + +TARGZS = mdocml-$(VERSION).tar.gz + +MANS = mandoc.1 mdoc.3 mdoc.7 mandoc_char.7 man.7 \ + man.3 roff.7 roff.3 tbl.7 + +BINS = mandoc + +TESTS = test-strlcat.c test-strlcpy.c + +CONFIGS = config.h.pre config.h.post + +DOCLEAN = $(BINS) $(LNS) $(LLNS) $(LIBS) $(OBJS) $(HTMLS) \ + $(TARGZS) tags $(MD5S) $(XMLS) $(TEXTS) $(GSGMLS) \ + config.h config.log $(PSS) $(PDFS) $(XHTMLS) + +DOINSTALL = $(SRCS) $(HEADS) Makefile $(MANS) $(SGMLS) $(STATICS) \ + $(DATAS) $(XSLS) $(EXAMPLES) $(TESTS) $(CONFIGS) + +all: $(BINS) + +lint: $(LLNS) + +clean: + rm -f $(DOCLEAN) + +dist: mdocml-$(VERSION).tar.gz + +www: all $(GSGMLS) $(HTMLS) $(XHTMLS) $(TEXTS) $(MD5S) $(TARGZS) $(PSS) $(PDFS) + +ps: $(PSS) + +pdf: $(PDFS) + +installwww: www + $(INSTALL_DATA) $(HTMLS) $(XHTMLS) $(PSS) $(PDFS) $(TEXTS) $(STATICS) $(DESTDIR)$(PREFIX)/ + $(INSTALL_DATA) mdocml-$(VERSION).tar.gz $(DESTDIR)$(PREFIX)/snapshots/ + $(INSTALL_DATA) mdocml-$(VERSION).md5 $(DESTDIR)$(PREFIX)/snapshots/ + $(INSTALL_DATA) mdocml-$(VERSION).tar.gz $(DESTDIR)$(PREFIX)/snapshots/mdocml.tar.gz + $(INSTALL_DATA) mdocml-$(VERSION).md5 $(DESTDIR)$(PREFIX)/snapshots/mdocml.md5 + +install: + mkdir -p $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(EXAMPLEDIR) + mkdir -p $(DESTDIR)$(MANDIR)/man1 + mkdir -p $(DESTDIR)$(MANDIR)/man7 + $(INSTALL_PROGRAM) mandoc $(DESTDIR)$(BINDIR) + $(INSTALL_MAN) mandoc.1 $(DESTDIR)$(MANDIR)/man1 + $(INSTALL_MAN) man.7 mdoc.7 roff.7 tbl.7 mandoc_char.7 $(DESTDIR)$(MANDIR)/man7 + $(INSTALL_DATA) example.style.css $(DESTDIR)$(EXAMPLEDIR) + +uninstall: + rm -f $(DESTDIR)$(BINDIR)/mandoc + rm -f $(DESTDIR)$(MANDIR)/man1/mandoc.1 + rm -f $(DESTDIR)$(MANDIR)/man7/mdoc.7 + rm -f $(DESTDIR)$(MANDIR)/man7/roff.7 + rm -f $(DESTDIR)$(MANDIR)/man7/tbl.7 + rm -f $(DESTDIR)$(MANDIR)/man7/man.7 + rm -f $(DESTDIR)$(MANDIR)/man7/mandoc_char.7 + rm -f $(DESTDIR)$(EXAMPLEDIR)/example.style.css + +$(OBJS): config.h + +$(LNS): config.h + +man_macro.ln man_macro.o: man_macro.c libman.h + +lib.ln lib.o: lib.c lib.in libmdoc.h + +att.ln att.o: att.c att.in libmdoc.h + +arch.ln arch.o: arch.c arch.in libmdoc.h + +vol.ln vol.o: vol.c vol.in libmdoc.h + +chars.ln chars.o: chars.c chars.in chars.h + +msec.ln msec.o: msec.c msec.in libmdoc.h + +st.ln st.o: st.c st.in libmdoc.h + +mdoc_macro.ln mdoc_macro.o: mdoc_macro.c libmdoc.h + +mdoc_term.ln mdoc_term.o: mdoc_term.c term.h mdoc.h + +mdoc_strings.ln mdoc_strings.o: mdoc_strings.c libmdoc.h + +man_hash.ln man_hash.o: man_hash.c libman.h + +mdoc_hash.ln mdoc_hash.o: mdoc_hash.c libmdoc.h + +mdoc.ln mdoc.o: mdoc.c libmdoc.h + +man.ln man.o: man.c libman.h + +main.ln main.o: main.c mdoc.h man.h roff.h + +compat.ln compat.o: compat.c + +term.ln term.o: term.c term.h man.h mdoc.h chars.h + +term_ps.ln term_ps.o: term_ps.c term.h main.h + +term_ascii.ln term_ascii.o: term_ascii.c term.h main.h + +html.ln html.o: html.c html.h chars.h + +mdoc_html.ln mdoc_html.o: mdoc_html.c html.h mdoc.h + +man_html.ln man_html.o: man_html.c html.h man.h out.h + +out.ln out.o: out.c out.h + +mandoc.ln mandoc.o: mandoc.c libmandoc.h + +tree.ln tree.o: tree.c man.h mdoc.h + +mdoc_argv.ln mdoc_argv.o: mdoc_argv.c libmdoc.h + +man_argv.ln man_argv.o: man_argv.c libman.h + +man_validate.ln man_validate.o: man_validate.c libman.h + +mdoc_validate.ln mdoc_validate.o: mdoc_validate.c libmdoc.h + +libmdoc.h: mdoc.h + +ChangeLog.xml: + cvs2cl --xml --xml-encoding iso-8859-15 -t --noxmlns -f $@ + +ChangeLog.txt: + cvs2cl -t -f $@ + +ChangeLog.html: ChangeLog.xml ChangeLog.xsl + xsltproc -o $@ ChangeLog.xsl ChangeLog.xml + +mdocml-$(VERSION).tar.gz: $(DOINSTALL) + mkdir -p .dist/mdocml/mdocml-$(VERSION)/ + cp -f $(DOINSTALL) .dist/mdocml/mdocml-$(VERSION)/ + ( cd .dist/mdocml/ && tar zcf ../../$@ mdocml-$(VERSION)/ ) + rm -rf .dist/ + +llib-llibmdoc.ln: $(MDOCLNS) + $(LINT) -Clibmdoc $(MDOCLNS) + +llib-llibman.ln: $(MANLNS) + $(LINT) -Clibman $(MANLNS) + +llib-llibmandoc.ln: $(MANDOCLNS) + $(LINT) -Clibmandoc $(MANDOCLNS) + +llib-llibroff.ln: $(ROFFLNS) + $(LINT) -Clibroff $(ROFFLNS) + +llib-lmandoc.ln: $(MAINLNS) llib-llibmdoc.ln llib-llibman.ln llib-llibmandoc.ln llib-llibroff.ln + $(LINT) -Cmandoc $(MAINLNS) llib-llibmdoc.ln llib-llibman.ln llib-llibmandoc.ln llib-llibroff.ln + +libmdoc.a: $(MDOCOBJS) + $(AR) rs $@ $(MDOCOBJS) + +libman.a: $(MANOBJS) + $(AR) rs $@ $(MANOBJS) + +libmandoc.a: $(MANDOCOBJS) + $(AR) rs $@ $(MANDOCOBJS) + +libroff.a: $(ROFFOBJS) + $(AR) rs $@ $(ROFFOBJS) + +mandoc: $(MAINOBJS) libroff.a libmdoc.a libman.a libmandoc.a + $(CC) $(CFLAGS) -o $@ $(MAINOBJS) libroff.a libmdoc.a libman.a libmandoc.a + +.sgml.html: + validate --warn $< + sed -e "s!@VERSION@!$(VERSION)!" -e "s!@VDATE@!$(VDATE)!" $< > $@ + +.1.1.txt .3.3.txt .7.7.txt: + ./mandoc -Tascii -Wall,stop $< | col -b > $@ + +.1.1.sgml .3.3.sgml .7.7.sgml: + ./mandoc -Thtml -Wall,stop -Ostyle=style.css,man=%N.%S.html,includes=%I.html $< > $@ + +.1.1.ps .3.3.ps .7.7.ps: + ./mandoc -Tps -Wall,stop $< > $@ + +.1.1.xhtml .3.3.xhtml .7.7.xhtml: + ./mandoc -Txhtml -Wall,stop -Ostyle=style.css,man=%N.%S.xhtml,includes=%I.html $< > $@ + +.1.1.pdf .3.3.pdf .7.7.pdf: + ./mandoc -Tpdf -Wall,stop $< > $@ + +.tar.gz.md5: + md5 $< > $@ + +.h.h.html: + highlight -I $< >$@ + +config.h: config.h.pre config.h.post + rm -f config.log + ( cat config.h.pre; \ + echo; \ + if $(CC) $(CFLAGS) -Werror -c test-strlcat.c >> config.log 2>&1; then \ + echo '#define HAVE_STRLCAT'; \ + rm test-strlcat.o; \ + fi; \ + if $(CC) $(CFLAGS) -Werror -c test-strlcpy.c >> config.log 2>&1; then \ + echo '#define HAVE_STRLCPY'; \ + rm test-strlcpy.o; \ + fi; \ + echo; \ + cat config.h.post \ + ) > $@ diff --git a/contrib/mdocml/arch.c b/contrib/mdocml/arch.c new file mode 100644 index 0000000000..c3d76340ab --- /dev/null +++ b/contrib/mdocml/arch.c @@ -0,0 +1,38 @@ +/* $Id: arch.c,v 1.8 2010/06/19 20:46:27 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2arch(const char *p) +{ + +#include "arch.in" + + return(NULL); +} diff --git a/contrib/mdocml/arch.in b/contrib/mdocml/arch.in new file mode 100644 index 0000000000..41543a9afc --- /dev/null +++ b/contrib/mdocml/arch.in @@ -0,0 +1,56 @@ +/* $Id: arch.in,v 1.10 2010/09/27 06:56:44 kristaps 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. + */ + +/* + * This file defines the architecture token of the .Dt prologue macro. + * All architectures that your system supports (or the manuals of your + * system) should be included here. The right-hand-side is the + * formatted output. + * + * Be sure to escape strings. + * + * REMEMBER TO ADD NEW ARCHITECTURES TO MDOC.7! + */ + +LINE("alpha", "Alpha") +LINE("amd64", "AMD64") +LINE("amiga", "Amiga") +LINE("arc", "ARC") +LINE("arm", "ARM") +LINE("armish", "ARMISH") +LINE("aviion", "AViiON") +LINE("hp300", "HP300") +LINE("hppa", "HPPA") +LINE("hppa64", "HPPA64") +LINE("i386", "i386") +LINE("landisk", "LANDISK") +LINE("loongson", "Loongson") +LINE("luna88k", "Luna88k") +LINE("mac68k", "Mac68k") +LINE("macppc", "MacPPC") +LINE("mips64", "MIPS64") +LINE("mvme68k", "MVME68k") +LINE("mvme88k", "MVME88k") +LINE("mvmeppc", "MVMEPPC") +LINE("pmax", "PMAX") +LINE("sgi", "SGI") +LINE("socppc", "SOCPPC") +LINE("sparc", "SPARC") +LINE("sparc64", "SPARC64") +LINE("sun3", "Sun3") +LINE("vax", "VAX") +LINE("zaurus", "Zaurus") diff --git a/contrib/mdocml/att.c b/contrib/mdocml/att.c new file mode 100644 index 0000000000..cfd6b4436e --- /dev/null +++ b/contrib/mdocml/att.c @@ -0,0 +1,38 @@ +/* $Id: att.c,v 1.8 2010/06/19 20:46:27 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2att(const char *p) +{ + +#include "att.in" + + return(NULL); +} diff --git a/contrib/mdocml/att.in b/contrib/mdocml/att.in new file mode 100644 index 0000000000..48fcd30b99 --- /dev/null +++ b/contrib/mdocml/att.in @@ -0,0 +1,37 @@ +/* $Id: att.in,v 1.6 2010/06/19 20:46:27 kristaps 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. + */ + +/* + * This file defines the AT&T versions of the .At macro. This probably + * isn't going to change. The right-hand side is the formatted string. + * + * Be sure to escape strings. + */ + +LINE("v1", "Version 1 AT&T UNIX") +LINE("v2", "Version 2 AT&T UNIX") +LINE("v3", "Version 3 AT&T UNIX") +LINE("v4", "Version 4 AT&T UNIX") +LINE("v5", "Version 5 AT&T UNIX") +LINE("v6", "Version 6 AT&T UNIX") +LINE("v7", "Version 7 AT&T UNIX") +LINE("32v", "Version 32V AT&T UNIX") +LINE("V", "AT&T System V UNIX") +LINE("V.1", "AT&T System V.1 UNIX") +LINE("V.2", "AT&T System V.2 UNIX") +LINE("V.3", "AT&T System V.3 UNIX") +LINE("V.4", "AT&T System V.4 UNIX") diff --git a/contrib/mdocml/chars.c b/contrib/mdocml/chars.c new file mode 100644 index 0000000000..5edf947ae1 --- /dev/null +++ b/contrib/mdocml/chars.c @@ -0,0 +1,243 @@ +/* $Id: chars.c,v 1.31 2011/01/02 10:10:57 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "mandoc.h" +#include "chars.h" + +#define PRINT_HI 126 +#define PRINT_LO 32 + +struct ln { + struct ln *next; + const char *code; + const char *ascii; + int unicode; + int type; +#define CHARS_CHAR (1 << 0) +#define CHARS_STRING (1 << 1) +#define CHARS_BOTH (CHARS_CHAR | CHARS_STRING) +}; + +#define LINES_MAX 351 + +#define CHAR(in, ch, code) \ + { NULL, (in), (ch), (code), CHARS_CHAR }, +#define STRING(in, ch, code) \ + { NULL, (in), (ch), (code), CHARS_STRING }, +#define BOTH(in, ch, code) \ + { NULL, (in), (ch), (code), CHARS_BOTH }, + +#define CHAR_TBL_START static struct ln lines[LINES_MAX] = { +#define CHAR_TBL_END }; + +#include "chars.in" + +struct ctab { + enum chars type; + struct ln **htab; +}; + +static inline int match(const struct ln *, + const char *, size_t, int); +static const struct ln *find(struct ctab *, const char *, size_t, int); + + +void +chars_free(void *arg) +{ + struct ctab *tab; + + tab = (struct ctab *)arg; + + free(tab->htab); + free(tab); +} + + +void * +chars_init(enum chars type) +{ + struct ctab *tab; + struct ln **htab; + struct ln *pp; + int i, hash; + + /* + * Constructs a very basic chaining hashtable. The hash routine + * is simply the integral value of the first character. + * Subsequent entries are chained in the order they're processed + * (they're in-line re-ordered during lookup). + */ + + tab = malloc(sizeof(struct ctab)); + if (NULL == tab) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + htab = calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln **)); + if (NULL == htab) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + for (i = 0; i < LINES_MAX; i++) { + hash = (int)lines[i].code[0] - PRINT_LO; + + if (NULL == (pp = htab[hash])) { + htab[hash] = &lines[i]; + continue; + } + + for ( ; pp->next; pp = pp->next) + /* Scan ahead. */ ; + pp->next = &lines[i]; + } + + tab->htab = htab; + tab->type = type; + return(tab); +} + + +/* + * Special character to Unicode codepoint. + */ +int +chars_spec2cp(void *arg, const char *p, size_t sz) +{ + const struct ln *ln; + + ln = find((struct ctab *)arg, p, sz, CHARS_CHAR); + if (NULL == ln) + return(-1); + return(ln->unicode); +} + + +/* + * Reserved word to Unicode codepoint. + */ +int +chars_res2cp(void *arg, const char *p, size_t sz) +{ + const struct ln *ln; + + ln = find((struct ctab *)arg, p, sz, CHARS_STRING); + if (NULL == ln) + return(-1); + return(ln->unicode); +} + + +/* + * Special character to string array. + */ +const char * +chars_spec2str(void *arg, const char *p, size_t sz, size_t *rsz) +{ + const struct ln *ln; + + ln = find((struct ctab *)arg, p, sz, CHARS_CHAR); + if (NULL == ln) + return(NULL); + + *rsz = strlen(ln->ascii); + return(ln->ascii); +} + + +/* + * Reserved word to string array. + */ +const char * +chars_res2str(void *arg, const char *p, size_t sz, size_t *rsz) +{ + const struct ln *ln; + + ln = find((struct ctab *)arg, p, sz, CHARS_STRING); + if (NULL == ln) + return(NULL); + + *rsz = strlen(ln->ascii); + return(ln->ascii); +} + + +static const struct ln * +find(struct ctab *tab, const char *p, size_t sz, int type) +{ + struct ln *pp, *prev; + struct ln **htab; + int hash; + + assert(p); + if (0 == sz) + return(NULL); + + if (p[0] < PRINT_LO || p[0] > PRINT_HI) + return(NULL); + + /* + * Lookup the symbol in the symbol hash. See ascii2htab for the + * hashtable specs. This dynamically re-orders the hash chain + * to optimise for repeat hits. + */ + + hash = (int)p[0] - PRINT_LO; + htab = tab->htab; + + if (NULL == (pp = htab[hash])) + return(NULL); + + for (prev = NULL; pp; pp = pp->next) { + if ( ! match(pp, p, sz, type)) { + prev = pp; + continue; + } + + if (prev) { + prev->next = pp->next; + pp->next = htab[hash]; + htab[hash] = pp; + } + + return(pp); + } + + return(NULL); +} + + +static inline int +match(const struct ln *ln, const char *p, size_t sz, int type) +{ + + if ( ! (ln->type & type)) + return(0); + if (strncmp(ln->code, p, sz)) + return(0); + return('\0' == ln->code[(int)sz]); +} diff --git a/contrib/mdocml/chars.h b/contrib/mdocml/chars.h new file mode 100644 index 0000000000..894008b673 --- /dev/null +++ b/contrib/mdocml/chars.h @@ -0,0 +1,36 @@ +/* $Id: chars.h,v 1.6 2010/07/31 23:52:58 schwarze Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifndef CHARS_H +#define CHARS_H + +__BEGIN_DECLS + +enum chars { + CHARS_ASCII, + CHARS_HTML +}; + +void *chars_init(enum chars); +const char *chars_spec2str(void *, const char *, size_t, size_t *); +int chars_spec2cp(void *, const char *, size_t); +const char *chars_res2str(void *, const char *, size_t, size_t *); +int chars_res2cp(void *, const char *, size_t); +void chars_free(void *); + +__END_DECLS + +#endif /*!CHARS_H*/ diff --git a/contrib/mdocml/chars.in b/contrib/mdocml/chars.in new file mode 100644 index 0000000000..63d9fb4171 --- /dev/null +++ b/contrib/mdocml/chars.in @@ -0,0 +1,425 @@ +/* $Id: chars.in,v 1.35 2010/09/15 13:10:30 kristaps 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. + */ + +/* + * The ASCII translation tables. STRING corresponds to predefined + * strings (cf. mdoc_samples.7 and tmac/mdoc/doc-nroff). CHAR + * corresponds to special characters (cf. groff_char.7). BOTH contains + * sequences that are equivalent in both STRING and CHAR. + * + * Either way, the left-hand side corresponds to the input sequence (\x, + * \(xx, \*(xx and so on) whose length is listed second element. The + * right-hand side is what's produced by the front-end, with the fourth + * element being its length. + * + * XXX - C-escape strings! + * XXX - update LINES_MAX if adding more! + */ + +/* Non-breaking, non-collapsing space uses unit separator. */ +static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' }; + +CHAR_TBL_START + +/* Spacing. */ +CHAR("c", "", 0) +CHAR("0", " ", 8194) +CHAR(" ", ascii_nbrsp, 160) +CHAR("~", ascii_nbrsp, 160) +CHAR("%", "", 0) +CHAR("&", "", 0) +CHAR("^", "", 0) +CHAR("|", "", 0) +CHAR("}", "", 0) + +/* Accents. */ +CHAR("a\"", "\"", 779) +CHAR("a-", "-", 175) +CHAR("a.", ".", 729) +CHAR("a^", "^", 770) +BOTH("\'", "\'", 769) +BOTH("aa", "\'", 769) +BOTH("ga", "`", 768) +BOTH("`", "`", 768) +CHAR("ab", "`", 774) +CHAR("ac", ",", 807) +CHAR("ad", "\"", 776) +CHAR("ah", "v", 711) +CHAR("ao", "o", 730) +CHAR("a~", "~", 771) +CHAR("ho", ",", 808) +CHAR("ha", "^", 94) +CHAR("ti", "~", 126) + +/* Quotes. */ +CHAR("Bq", ",,", 8222) +CHAR("bq", ",", 8218) +BOTH("lq", "``", 8220) +BOTH("rq", "\'\'", 8221) +CHAR("oq", "`", 8216) +CHAR("cq", "\'", 8217) +CHAR("aq", "\'", 39) +CHAR("dq", "\"", 34) +CHAR("Fo", "<<", 171) +CHAR("Fc", ">>", 187) +CHAR("fo", "<", 8249) +CHAR("fc", ">", 8250) + +/* Brackets. */ +CHAR("lB", "[", 91) +CHAR("rB", "]", 93) +CHAR("lC", "{", 123) +CHAR("rC", "}", 125) +CHAR("la", "<", 60) +CHAR("ra", ">", 62) +CHAR("bv", "|", 9130) +CHAR("braceex", "|", 9130) +CHAR("bracketlefttp", "|", 9121) +CHAR("bracketleftbp", "|", 9123) +CHAR("bracketleftex", "|", 9122) +CHAR("bracketrighttp", "|", 9124) +CHAR("bracketrightbp", "|", 9126) +CHAR("bracketrightex", "|", 9125) +CHAR("lt", ",-", 9127) +CHAR("bracelefttp", ",-", 9127) +CHAR("lk", "{", 9128) +CHAR("braceleftmid", "{", 9128) +CHAR("lb", ",-", 9129) +CHAR("braceleftbp", "`-", 9129) +CHAR("braceleftex", "|", 9130) +CHAR("rt", "-.", 9131) +CHAR("bracerighttp", "-.", 9131) +CHAR("rk", "}", 9132) +CHAR("bracerightmid", "}", 9132) +CHAR("rb", "-\'", 9133) +CHAR("bracerightbp", "-\'", 9133) +CHAR("bracerightex", "|", 9130) +CHAR("parenlefttp", "/", 9115) +CHAR("parenleftbp", "\\", 9117) +CHAR("parenleftex", "|", 9116) +CHAR("parenrighttp", "\\", 9118) +CHAR("parenrightbp", "/", 9120) +CHAR("parenrightex", "|", 9119) + +/* Greek characters. */ +CHAR("*A", "A", 913) +CHAR("*B", "B", 914) +CHAR("*G", "|", 915) +CHAR("*D", "/\\", 916) +CHAR("*E", "E", 917) +CHAR("*Z", "Z", 918) +CHAR("*Y", "H", 919) +CHAR("*H", "O", 920) +CHAR("*I", "I", 921) +CHAR("*K", "K", 922) +CHAR("*L", "/\\", 923) +CHAR("*M", "M", 924) +CHAR("*N", "N", 925) +CHAR("*C", "H", 926) +CHAR("*O", "O", 927) +CHAR("*P", "TT", 928) +CHAR("*R", "P", 929) +CHAR("*S", ">", 931) +CHAR("*T", "T", 932) +CHAR("*U", "Y", 933) +CHAR("*F", "O_", 934) +CHAR("*X", "X", 935) +CHAR("*Q", "Y", 936) +CHAR("*W", "O", 937) +CHAR("*a", "a", 945) +CHAR("*b", "B", 946) +CHAR("*g", "y", 947) +CHAR("*d", "d", 948) +CHAR("*e", "e", 949) +CHAR("*z", "C", 950) +CHAR("*y", "n", 951) +CHAR("*h", "0", 952) +CHAR("*i", "i", 953) +CHAR("*k", "k", 954) +CHAR("*l", "\\", 955) +CHAR("*m", "u", 956) +CHAR("*n", "v", 957) +CHAR("*c", "E", 958) +CHAR("*o", "o", 959) +CHAR("*p", "n", 960) +CHAR("*r", "p", 961) +CHAR("*s", "o", 963) +CHAR("*t", "t", 964) +CHAR("*u", "u", 965) +CHAR("*f", "o", 981) +CHAR("*x", "x", 967) +CHAR("*q", "u", 968) +CHAR("*w", "w", 969) +CHAR("+h", "0", 977) +CHAR("+f", "o", 966) +CHAR("+p", "w", 982) +CHAR("+e", "e", 1013) +CHAR("ts", "s", 962) + +/* Accented letters. */ +CHAR(",C", "C", 199) +CHAR(",c", "c", 231) +CHAR("/L", "L", 321) +CHAR("/O", "O", 216) +CHAR("/l", "l", 322) +CHAR("/o", "o", 248) +CHAR("oA", "A", 197) +CHAR("oa", "a", 229) +CHAR(":A", "A", 196) +CHAR(":E", "E", 203) +CHAR(":I", "I", 207) +CHAR(":O", "O", 214) +CHAR(":U", "U", 220) +CHAR(":a", "a", 228) +CHAR(":e", "e", 235) +CHAR(":i", "i", 239) +CHAR(":o", "o", 245) +CHAR(":u", "u", 252) +CHAR(":y", "y", 255) +CHAR("\'A", "A", 193) +CHAR("\'E", "E", 201) +CHAR("\'I", "I", 205) +CHAR("\'O", "O", 211) +CHAR("\'U", "U", 218) +CHAR("\'a", "a", 225) +CHAR("\'e", "e", 233) +CHAR("\'i", "i", 237) +CHAR("\'o", "o", 243) +CHAR("\'u", "u", 250) +CHAR("^A", "A", 194) +CHAR("^E", "E", 202) +CHAR("^I", "I", 206) +CHAR("^O", "O", 212) +CHAR("^U", "U", 219) +CHAR("^a", "a", 226) +CHAR("^e", "e", 234) +CHAR("^i", "i", 238) +CHAR("^o", "o", 244) +CHAR("^u", "u", 251) +CHAR("`A", "A", 192) +CHAR("`E", "E", 200) +CHAR("`I", "I", 204) +CHAR("`O", "O", 210) +CHAR("`U", "U", 217) +CHAR("`a", "a", 224) +CHAR("`e", "e", 232) +CHAR("`i", "i", 236) +CHAR("`o", "o", 242) +CHAR("`u", "u", 249) +CHAR("~A", "A", 195) +CHAR("~N", "N", 209) +CHAR("~O", "O", 213) +CHAR("~a", "a", 227) +CHAR("~n", "n", 241) +CHAR("~o", "o", 245) + +/* Arrows and lines. */ +CHAR("<-", "<-", 8592) +CHAR("->", "->", 8594) +CHAR("<>", "<>", 8596) +CHAR("da", "v", 8595) +BOTH("ua", "^", 8593) +BOTH("va", "^v", 8597) +CHAR("lA", "<=", 8656) +CHAR("rA", "=>", 8658) +CHAR("hA", "<=>", 8660) +CHAR("dA", "v", 8659) +CHAR("uA", "^", 8657) +CHAR("vA", "^=v", 8661) + +/* Logic. */ +CHAR("AN", "^", 8743) +CHAR("OR", "v", 8744) +CHAR("no", "~", 172) +CHAR("tno", "~", 172) +CHAR("te", "3", 8707) +CHAR("fa", "V", 8704) +CHAR("st", "-)", 8715) +CHAR("tf", ".:.", 8756) +CHAR("3d", ".:.", 8756) +CHAR("or", "|", 124) + +/* Mathematicals. */ +CHAR("pl", "+", 43) +CHAR("mi", "-", 8722) +CHAR("-", "-", 45) +CHAR("-+", "-+", 8723) +CHAR("+-", "+-", 177) +CHAR("t+-", "+-", 177) +CHAR("pc", ".", 183) +CHAR("md", ".", 8901) +CHAR("mu", "x", 215) +CHAR("tmu", "x", 215) +CHAR("c*", "x", 8855) +CHAR("c+", "+", 8853) +CHAR("di", "-:-", 247) +CHAR("tdi", "-:-", 247) +CHAR("f/", "/", 8260) +CHAR("**", "*", 8727) +BOTH("<=", "<=", 8804) +BOTH(">=", ">=", 8805) +CHAR("<<", "<<", 8810) +CHAR(">>", ">>", 8811) +CHAR("eq", "=", 61) +CHAR("!=", "!=", 8800) +CHAR("==", "==", 8801) +CHAR("ne", "!==", 8802) +CHAR("=~", "=~", 8773) +CHAR("-~", "-~", 8771) +CHAR("ap", "~", 8764) +CHAR("~~", "~~", 8776) +CHAR("~=", "~=", 8780) +CHAR("pt", "oc", 8733) +CHAR("es", "{}", 8709) +CHAR("mo", "E", 8712) +CHAR("nm", "!E", 8713) +CHAR("sb", "(=", 8834) +CHAR("nb", "(!=", 8836) +CHAR("sp", "=)", 8835) +CHAR("nc", "!=)", 8837) +CHAR("ib", "(=", 8838) +CHAR("ip", "=)", 8839) +CHAR("ca", "(^)", 8745) +CHAR("cu", "U", 8746) +CHAR("/_", "/_", 8736) +CHAR("pp", "_|_", 8869) +CHAR("is", "I", 8747) +CHAR("integral", "I", 8747) +CHAR("sum", "E", 8721) +CHAR("product", "TT", 8719) +CHAR("coproduct", "U", 8720) +CHAR("gr", "V", 8711) +CHAR("sr", "\\/", 8730) +CHAR("sqrt", "\\/", 8730) +CHAR("lc", "|~", 8968) +CHAR("rc", "~|", 8969) +CHAR("lf", "|_", 8970) +CHAR("rf", "_|", 8971) +CHAR("if", "oo", 8734) +CHAR("Ah", "N", 8501) +CHAR("Im", "I", 8465) +CHAR("Re", "R", 8476) +CHAR("pd", "a", 8706) +CHAR("-h", "/h", 8463) + +/* Ligatures. */ +CHAR("ff", "ff", 64256) +CHAR("fi", "fi", 64257) +CHAR("fl", "fl", 64258) +CHAR("Fi", "ffi", 64259) +CHAR("Fl", "ffl", 64260) +CHAR("AE", "AE", 198) +CHAR("ae", "ae", 230) +CHAR("OE", "OE", 338) +CHAR("oe", "oe", 339) +CHAR("ss", "ss", 223) +CHAR("IJ", "IJ", 306) +CHAR("ij", "ij", 307) + +/* Special letters. */ +CHAR("-D", "D", 208) +CHAR("Sd", "o", 240) +CHAR("TP", "b", 222) +CHAR("Tp", "b", 254) +CHAR(".i", "i", 305) +CHAR(".j", "j", 567) + +/* Currency. */ +CHAR("Do", "$", 36) +CHAR("ct", "c", 162) +CHAR("Eu", "EUR", 8364) +CHAR("eu", "EUR", 8364) +CHAR("Ye", "Y", 165) +CHAR("Po", "L", 163) +CHAR("Cs", "x", 164) +CHAR("Fn", "f", 402) + +/* Old style. */ +STRING("Am", "&", 38) +STRING("Ba", "|", 124) +STRING("Ge", ">=", 8805) +STRING("Gt", ">", 62) +STRING("If", "infinity", 0) +STRING("Le", "<=", 8804) +STRING("Lq", "``", 8220) +STRING("Lt", "<", 60) +STRING("Na", "NaN", 0) +STRING("Ne", "!=", 8800) +STRING("Pi", "pi", 960) +STRING("Pm", "+-", 177) +STRING("Rq", "\'\'", 8221) +STRING("left-bracket", "[", 91) +STRING("left-parenthesis", "(", 40) +STRING("left-singlequote", "`", 8216) +STRING("lp", "(", 40) +STRING("q", "\"", 34) +STRING("quote-left", "`", 8216) +STRING("quote-right", "\'", 8217) +STRING("R", "(R)", 174) +STRING("right-bracket", "]", 93) +STRING("right-parenthesis", ")", 41) +STRING("right-singlequote", "\'", 8217) +STRING("rp", ")", 41) +STRING("Tm", "(Tm)", 8482) + +/* Lines. */ +CHAR("ba", "|", 124) +CHAR("br", "|", 9474) +CHAR("ul", "_", 95) +CHAR("rl", "-", 8254) +CHAR("bb", "|", 166) +CHAR("sl", "/", 47) +CHAR("rs", "\\", 92) + +/* Text markers. */ +CHAR("ci", "o", 9675) +CHAR("bu", "o", 8226) +CHAR("dd", "=", 8225) +CHAR("dg", "-", 8224) +CHAR("lz", "<>", 9674) +CHAR("sq", "[]", 9633) +CHAR("ps", "9|", 182) +CHAR("sc", "S", 167) +CHAR("lh", "<=", 9756) +CHAR("rh", "=>", 9758) +CHAR("at", "@", 64) +CHAR("sh", "#", 35) +CHAR("CR", "_|", 8629) +CHAR("OK", "\\/", 10003) + +/* Legal symbols. */ +CHAR("co", "(C)", 169) +CHAR("rg", "(R)", 174) +CHAR("tm", "tm", 8482) + +/* Punctuation. */ +CHAR(".", ".", 46) +CHAR("r!", "i", 161) +CHAR("r?", "c", 191) +CHAR("em", "--", 8212) +CHAR("en", "-", 8211) +CHAR("hy", "-", 8208) +CHAR("e", "\\", 92) + +/* Units. */ +CHAR("de", "o", 176) +CHAR("%0", "%o", 8240) +CHAR("fm", "\'", 8242) +CHAR("sd", "\"", 8243) +CHAR("mc", "mu", 181) + +CHAR_TBL_END diff --git a/contrib/mdocml/compat.c b/contrib/mdocml/compat.c new file mode 100644 index 0000000000..f00cc5c6b4 --- /dev/null +++ b/contrib/mdocml/compat.c @@ -0,0 +1,95 @@ +/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +int dummy; /* To prevent an empty object file */ + +#ifndef HAVE_STRLCAT +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} +#endif + +#ifndef HAVE_STRLCPY +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} +#endif diff --git a/contrib/mdocml/config.h.post b/contrib/mdocml/config.h.post new file mode 100644 index 0000000000..81c01b9316 --- /dev/null +++ b/contrib/mdocml/config.h.post @@ -0,0 +1,25 @@ +#include + +#if !defined(__BEGIN_DECLS) +# ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# else +# define __BEGIN_DECLS +# endif +#endif +#if !defined(__END_DECLS) +# ifdef __cplusplus +# define __END_DECLS } +# else +# define __END_DECLS +# endif +#endif + +#ifndef HAVE_STRLCAT +extern size_t strlcat(char *, const char *, size_t); +#endif +#ifndef HAVE_STRLCPY +extern size_t strlcpy(char *, const char *, size_t); +#endif + +#endif /* MANDOC_CONFIG_H */ diff --git a/contrib/mdocml/config.h.pre b/contrib/mdocml/config.h.pre new file mode 100644 index 0000000000..a309ed9569 --- /dev/null +++ b/contrib/mdocml/config.h.pre @@ -0,0 +1,6 @@ +#ifndef MANDOC_CONFIG_H +#define MANDOC_CONFIG_H + +#if defined(__linux__) || defined(__MINT__) +# define _GNU_SOURCE /* strptime(), getsubopt() */ +#endif diff --git a/contrib/mdocml/example.style.css b/contrib/mdocml/example.style.css new file mode 100644 index 0000000000..7f640ee834 --- /dev/null +++ b/contrib/mdocml/example.style.css @@ -0,0 +1,146 @@ +/* $Id: example.style.css,v 1.41 2011/01/05 13:00:11 kristaps Exp $ */ + +/* + * This is an example style-sheet provided for mandoc(1) and the -Thtml + * or -Txhtml output mode. + * + * It mimics the appearance of the traditional cvsweb output. + * + * See mdoc(7) and man(7) for macro explanations. + */ + +html { min-width: 580px; width: 580px; } +body { font-family: monospace; } + +/* Preamble structure. */ + +table.foot { width: 100%; } /* Document footer. */ +td.foot-date { width: 50%; } /* Document footer: date. */ +td.foot-os { width: 50%; text-align: right; } /* Document footer: OS/source. */ +table.head { width: 100%; } /* Document header. */ +td.head-ltitle { width: 10%; } /* Document header: left-title. */ +td.head-vol { width: 80%; text-align: center; } /* Document header: volume. */ +td.head-rtitle { width: 10%; text-align: right; } /* Document header: right-title. */ + +/* Sections. */ + +h1 { margin-bottom: 0px; font-size: medium; margin-left: -4ex; } /* Section header (Sh, SH). */ +h2 { margin-bottom: 0px; font-size: medium; margin-left: -2ex; } /* Sub-section header (Ss, SS). */ +div.section { margin-bottom: 2ex; margin-left: 4ex; } /* Sections (Sh, SH). */ +div.subsection { } /* Sub-sections (Ss, SS). */ +table.synopsis { } /* SYNOPSIS section table. */ + +/* Vertical spacing. */ + +p { } /* Paragraph: Pp, Lp. */ +blockquote { margin-top: 0px; margin-bottom: 0px; } +table { margin-top: 0px; margin-bottom: 0px; } +td { vertical-align: top; } + +/* General font modes. */ + +i { } /* Italic: BI, IB, I, (implicit). */ +.emph { font-style: italic; font-weight: normal; } /* Emphasis: Em, Bl -emphasis. */ +b { } /* Bold: SB, BI, IB, BR, RB, B, (implicit). */ +.symb { font-style: normal; font-weight: bold; } /* Symbolic: Sy, Ms, Bf -symbolic. */ +small { } /* Small: SB, SM. */ + +/* Block modes. */ + +.display { } /* Top of all Bd, D1, Dl. */ +.list { } /* Top of all Bl. */ + +/* Context-specific modes. */ + +i.addr { font-weight: normal; } /* Address (Ad). */ +i.arg { font-weight: normal; } /* Command argument (Ar). */ +span.author { } /* Author name (An). */ +b.cmd { font-style: normal; } /* Command (Cm). */ +b.config { font-style: normal; } /* Config statement (Cd). */ +span.define { } /* Defines (Dv). */ +span.desc { } /* Nd. After em-dash. */ +b.diag { font-style: normal; } /* Diagnostic (Bl -diag). */ +span.env { } /* Environment variables (Ev). */ +span.errno { } /* Error string (Er). */ +i.farg { font-weight: normal; } /* Function argument (Fa, Fn). */ +i.file { font-weight: normal; } /* File (Pa). */ +b.flag { font-style: normal; } /* Flag (Fl, Cm). */ +b.fname { font-style: normal; } /* Function name (Fa, Fn, Rv). */ +i.ftype { font-weight: normal; } /* Function types (Ft, Fn). */ +b.includes { font-style: normal; } /* Header includes (In). */ +span.lib { } /* Library (Lb). */ +i.link-sec { font-weight: normal; } /* Section links (Sx). */ +code.lit { font-style: normal; font-weight: normal; } /* Literal: Dl, Li, Bf -literal, Bl -literal, Bl -unfilled. */ +b.macro { font-style: normal; } /* Macro-ish thing (Fd). */ +b.name { font-style: normal; } /* Name of utility (Nm). */ +span.opt { } /* Options (Op, Oo/Oc). */ +span.ref { } /* Citations (Rs). */ +span.ref-auth { } /* Reference author (%A). */ +i.ref-book { font-weight: normal; } /* Reference book (%B). */ +span.ref-city { } /* Reference city (%C). */ +span.ref-date { } /* Reference date (%D). */ +i.ref-issue { font-weight: normal; } /* Reference issuer/publisher (%I). */ +i.ref-jrnl { font-weight: normal; } /* Reference journal (%J). */ +span.ref-num { } /* Reference number (%N). */ +span.ref-opt { } /* Reference optionals (%O). */ +span.ref-page { } /* Reference page (%P). */ +span.ref-corp { } /* Reference corporate/foreign author (%Q). */ +span.ref-rep { } /* Reference report (%R). */ +span.ref-title { text-decoration: underline; } /* Reference title (%T). */ +span.ref-vol { } /* Reference volume (%V). */ +span.type { font-style: italic; font-weight: normal; } /* Variable types (Vt). */ +span.unix { } /* Unices (Ux, Ox, Nx, Fx, Bx, Bsx, Dx). */ +b.utility { font-style: normal; } /* Name of utility (Ex). */ +b.var { font-style: normal; } /* Variables (Rv). */ + +a.link-ext { } /* Off-site link (Lk). */ +a.link-includes { } /* Include-file link (In). */ +a.link-mail { } /* Mailto links (Mt). */ +a.link-man { } /* Manual links (Xr). */ +a.link-ref { } /* Reference section links (%Q). */ +a.link-sec { } /* Section links (Sx). */ + +/* Formatting for lists. See mdoc(7). */ + +dl.list-diag { } +dt.list-diag { } +dd.list-diag { } + +dl.list-hang { } +dt.list-hang { } +dd.list-hang { } + +dl.list-inset { } +dt.list-inset { } +dd.list-inset { } + +dl.list-ohang { } +dt.list-ohang { } +dd.list-ohang { margin-left: 0em; } + +dl.list-tag { } +dt.list-tag { } +dd.list-tag { } + +table.list-col { } +tr.list-col { } +td.list-col { } + +ul.list-bul { list-style-type: disc; padding-left: 1em; } +li.list-bul { } + +ul.list-dash { list-style-type: none; padding-left: 0em; } +li.list-dash:before { content: "\2014 "; } + +ul.list-hyph { list-style-type: none; padding-left: 0em; } +li.list-hyph:before { content: "\2013 "; } + +ul.list-item { list-style-type: none; padding-left: 0em; } +li.list-item { } + +ol.list-enum { padding-left: 2em; } +li.list-enum { } + +/* Table modes. See tbl(7). */ + +table.tbl { } diff --git a/contrib/mdocml/external.png b/contrib/mdocml/external.png new file mode 100644 index 0000000000000000000000000000000000000000..419c06fb960b0b665791c90044a78621616a4cb8 GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&i3a$DxTeiKV?6WB%rpNP(#|lX z{f7XTR~%D;3fN16{DL7O3{u|AZa^UmPZ!4!iE!1^jzSC$EX7 z>#2$@InOtpuqyg8<+R}9g-+|v&13&5`dCp`{|95_8^+nQkC>(b&0z3!^>bP0l+XkK DO07OF literal 0 HcmV?d00001 diff --git a/contrib/mdocml/html.c b/contrib/mdocml/html.c new file mode 100644 index 0000000000..70403ff77e --- /dev/null +++ b/contrib/mdocml/html.c @@ -0,0 +1,798 @@ +/* $Id: html.c,v 1.124 2010/12/27 21:41:05 schwarze Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "chars.h" +#include "html.h" +#include "main.h" + +struct htmldata { + const char *name; + int flags; +#define HTML_CLRLINE (1 << 0) +#define HTML_NOSTACK (1 << 1) +#define HTML_AUTOCLOSE (1 << 2) /* Tag has auto-closure. */ +}; + +static const struct htmldata htmltags[TAG_MAX] = { + {"html", HTML_CLRLINE}, /* TAG_HTML */ + {"head", HTML_CLRLINE}, /* TAG_HEAD */ + {"body", HTML_CLRLINE}, /* TAG_BODY */ + {"meta", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */ + {"title", HTML_CLRLINE}, /* TAG_TITLE */ + {"div", HTML_CLRLINE}, /* TAG_DIV */ + {"h1", 0}, /* TAG_H1 */ + {"h2", 0}, /* TAG_H2 */ + {"span", 0}, /* TAG_SPAN */ + {"link", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */ + {"br", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */ + {"a", 0}, /* TAG_A */ + {"table", HTML_CLRLINE}, /* TAG_TABLE */ + {"tbody", HTML_CLRLINE}, /* TAG_TBODY */ + {"col", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */ + {"tr", HTML_CLRLINE}, /* TAG_TR */ + {"td", HTML_CLRLINE}, /* TAG_TD */ + {"li", HTML_CLRLINE}, /* TAG_LI */ + {"ul", HTML_CLRLINE}, /* TAG_UL */ + {"ol", HTML_CLRLINE}, /* TAG_OL */ + {"dl", HTML_CLRLINE}, /* TAG_DL */ + {"dt", HTML_CLRLINE}, /* TAG_DT */ + {"dd", HTML_CLRLINE}, /* TAG_DD */ + {"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */ + {"p", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */ + {"pre", HTML_CLRLINE }, /* TAG_PRE */ + {"b", 0 }, /* TAG_B */ + {"i", 0 }, /* TAG_I */ + {"code", 0 }, /* TAG_CODE */ + {"small", 0 }, /* TAG_SMALL */ +}; + +static const char *const htmlattrs[ATTR_MAX] = { + "http-equiv", /* ATTR_HTTPEQUIV */ + "content", /* ATTR_CONTENT */ + "name", /* ATTR_NAME */ + "rel", /* ATTR_REL */ + "href", /* ATTR_HREF */ + "type", /* ATTR_TYPE */ + "media", /* ATTR_MEDIA */ + "class", /* ATTR_CLASS */ + "style", /* ATTR_STYLE */ + "width", /* ATTR_WIDTH */ + "id", /* ATTR_ID */ + "summary", /* ATTR_SUMMARY */ + "align", /* ATTR_ALIGN */ +}; + +static void print_spec(struct html *, enum roffdeco, + const char *, size_t); +static void print_res(struct html *, const char *, size_t); +static void print_ctag(struct html *, enum htmltag); +static void print_doctype(struct html *); +static void print_xmltype(struct html *); +static int print_encode(struct html *, const char *, int); +static void print_metaf(struct html *, enum roffdeco); +static void print_attr(struct html *, + const char *, const char *); +static void *ml_alloc(char *, enum htmltype); + + +static void * +ml_alloc(char *outopts, enum htmltype type) +{ + struct html *h; + const char *toks[4]; + char *v; + + toks[0] = "style"; + toks[1] = "man"; + toks[2] = "includes"; + toks[3] = NULL; + + h = calloc(1, sizeof(struct html)); + if (NULL == h) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + h->type = type; + h->tags.head = NULL; + h->symtab = chars_init(CHARS_HTML); + + while (outopts && *outopts) + switch (getsubopt(&outopts, UNCONST(toks), &v)) { + case (0): + h->style = v; + break; + case (1): + h->base_man = v; + break; + case (2): + h->base_includes = v; + break; + default: + break; + } + + return(h); +} + +void * +html_alloc(char *outopts) +{ + + return(ml_alloc(outopts, HTML_HTML_4_01_STRICT)); +} + + +void * +xhtml_alloc(char *outopts) +{ + + return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT)); +} + + +void +html_free(void *p) +{ + struct tag *tag; + struct html *h; + + h = (struct html *)p; + + while ((tag = h->tags.head) != NULL) { + h->tags.head = tag->next; + free(tag); + } + + if (h->symtab) + chars_free(h->symtab); + + free(h); +} + + +void +print_gen_head(struct html *h) +{ + struct htmlpair tag[4]; + + tag[0].key = ATTR_HTTPEQUIV; + tag[0].val = "Content-Type"; + tag[1].key = ATTR_CONTENT; + tag[1].val = "text/html; charset=utf-8"; + print_otag(h, TAG_META, 2, tag); + + tag[0].key = ATTR_NAME; + tag[0].val = "resource-type"; + tag[1].key = ATTR_CONTENT; + tag[1].val = "document"; + print_otag(h, TAG_META, 2, tag); + + if (h->style) { + tag[0].key = ATTR_REL; + tag[0].val = "stylesheet"; + tag[1].key = ATTR_HREF; + tag[1].val = h->style; + tag[2].key = ATTR_TYPE; + tag[2].val = "text/css"; + tag[3].key = ATTR_MEDIA; + tag[3].val = "all"; + print_otag(h, TAG_LINK, 4, tag); + } +} + + +static void +print_spec(struct html *h, enum roffdeco d, const char *p, size_t len) +{ + int cp; + const char *rhs; + size_t sz; + + if ((cp = chars_spec2cp(h->symtab, p, len)) > 0) { + printf("&#%d;", cp); + return; + } else if (-1 == cp && DECO_SSPECIAL == d) { + fwrite(p, 1, len, stdout); + return; + } else if (-1 == cp) + return; + + if (NULL != (rhs = chars_spec2str(h->symtab, p, len, &sz))) + fwrite(rhs, 1, sz, stdout); +} + + +static void +print_res(struct html *h, const char *p, size_t len) +{ + int cp; + const char *rhs; + size_t sz; + + if ((cp = chars_res2cp(h->symtab, p, len)) > 0) { + printf("&#%d;", cp); + return; + } else if (-1 == cp) + return; + + if (NULL != (rhs = chars_res2str(h->symtab, p, len, &sz))) + fwrite(rhs, 1, sz, stdout); +} + + +static void +print_metaf(struct html *h, enum roffdeco deco) +{ + enum htmlfont font; + + switch (deco) { + case (DECO_PREVIOUS): + font = h->metal; + break; + case (DECO_ITALIC): + font = HTMLFONT_ITALIC; + break; + case (DECO_BOLD): + font = HTMLFONT_BOLD; + break; + case (DECO_ROMAN): + font = HTMLFONT_NONE; + break; + default: + abort(); + /* NOTREACHED */ + } + + if (h->metaf) { + print_tagq(h, h->metaf); + h->metaf = NULL; + } + + h->metal = h->metac; + h->metac = font; + + if (HTMLFONT_NONE != font) + h->metaf = HTMLFONT_BOLD == font ? + print_otag(h, TAG_B, 0, NULL) : + print_otag(h, TAG_I, 0, NULL); +} + + +static int +print_encode(struct html *h, const char *p, int norecurse) +{ + size_t sz; + int len, nospace; + const char *seq; + enum roffdeco deco; + static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' }; + + nospace = 0; + + for (; *p; p++) { + sz = strcspn(p, rejs); + + fwrite(p, 1, sz, stdout); + p += /* LINTED */ + sz; + + if ('<' == *p) { + printf("<"); + continue; + } else if ('>' == *p) { + printf(">"); + continue; + } else if ('&' == *p) { + printf("&"); + continue; + } else if (ASCII_HYPH == *p) { + /* + * Note: "soft hyphens" aren't graphically + * displayed when not breaking the text; we want + * them to be displayed. + */ + /*printf("­");*/ + putchar('-'); + continue; + } else if ('\0' == *p) + break; + + seq = ++p; + len = a2roffdeco(&deco, &seq, &sz); + + switch (deco) { + case (DECO_RESERVED): + print_res(h, seq, sz); + break; + case (DECO_SSPECIAL): + /* FALLTHROUGH */ + case (DECO_SPECIAL): + print_spec(h, deco, seq, sz); + break; + case (DECO_PREVIOUS): + /* FALLTHROUGH */ + case (DECO_BOLD): + /* FALLTHROUGH */ + case (DECO_ITALIC): + /* FALLTHROUGH */ + case (DECO_ROMAN): + if (norecurse) + break; + print_metaf(h, deco); + break; + default: + break; + } + + p += len - 1; + + if (DECO_NOSPACE == deco && '\0' == *(p + 1)) + nospace = 1; + } + + return(nospace); +} + + +static void +print_attr(struct html *h, const char *key, const char *val) +{ + printf(" %s=\"", key); + (void)print_encode(h, val, 1); + putchar('\"'); +} + + +struct tag * +print_otag(struct html *h, enum htmltag tag, + int sz, const struct htmlpair *p) +{ + int i; + struct tag *t; + + /* Push this tags onto the stack of open scopes. */ + + if ( ! (HTML_NOSTACK & htmltags[tag].flags)) { + t = malloc(sizeof(struct tag)); + if (NULL == t) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + t->tag = tag; + t->next = h->tags.head; + h->tags.head = t; + } else + t = NULL; + + if ( ! (HTML_NOSPACE & h->flags)) + if ( ! (HTML_CLRLINE & htmltags[tag].flags)) { + /* Manage keeps! */ + if ( ! (HTML_KEEP & h->flags)) { + if (HTML_PREKEEP & h->flags) + h->flags |= HTML_KEEP; + putchar(' '); + } else + printf(" "); + } + + if ( ! (h->flags & HTML_NONOSPACE)) + h->flags &= ~HTML_NOSPACE; + else + h->flags |= HTML_NOSPACE; + + /* Print out the tag name and attributes. */ + + printf("<%s", htmltags[tag].name); + for (i = 0; i < sz; i++) + print_attr(h, htmlattrs[p[i].key], p[i].val); + + /* Add non-overridable attributes. */ + + if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) { + print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml"); + print_attr(h, "xml:lang", "en"); + print_attr(h, "lang", "en"); + } + + /* Accomodate for XML "well-formed" singleton escaping. */ + + if (HTML_AUTOCLOSE & htmltags[tag].flags) + switch (h->type) { + case (HTML_XHTML_1_0_STRICT): + putchar('/'); + break; + default: + break; + } + + putchar('>'); + + h->flags |= HTML_NOSPACE; + + if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags) + putchar('\n'); + + return(t); +} + + +static void +print_ctag(struct html *h, enum htmltag tag) +{ + + printf("", htmltags[tag].name); + if (HTML_CLRLINE & htmltags[tag].flags) { + h->flags |= HTML_NOSPACE; + putchar('\n'); + } +} + + +void +print_gen_decls(struct html *h) +{ + + print_xmltype(h); + print_doctype(h); +} + + +static void +print_xmltype(struct html *h) +{ + + if (HTML_XHTML_1_0_STRICT == h->type) + puts(""); +} + + +static void +print_doctype(struct html *h) +{ + const char *doctype; + const char *dtd; + const char *name; + + switch (h->type) { + case (HTML_HTML_4_01_STRICT): + name = "HTML"; + doctype = "-//W3C//DTD HTML 4.01//EN"; + dtd = "http://www.w3.org/TR/html4/strict.dtd"; + break; + default: + name = "html"; + doctype = "-//W3C//DTD XHTML 1.0 Strict//EN"; + dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"; + break; + } + + printf("\n", + name, doctype, dtd); +} + + +void +print_text(struct html *h, const char *word) +{ + + if (word[0] && '\0' == word[1]) + switch (word[0]) { + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + if ( ! (HTML_IGNDELIM & h->flags)) + h->flags |= HTML_NOSPACE; + break; + default: + break; + } + + if ( ! (HTML_NOSPACE & h->flags)) { + /* Manage keeps! */ + if ( ! (HTML_KEEP & h->flags)) { + if (HTML_PREKEEP & h->flags) + h->flags |= HTML_KEEP; + putchar(' '); + } else + printf(" "); + } + + assert(NULL == h->metaf); + if (HTMLFONT_NONE != h->metac) + h->metaf = HTMLFONT_BOLD == h->metac ? + print_otag(h, TAG_B, 0, NULL) : + print_otag(h, TAG_I, 0, NULL); + + assert(word); + if ( ! print_encode(h, word, 0)) + if ( ! (h->flags & HTML_NONOSPACE)) + h->flags &= ~HTML_NOSPACE; + + if (h->metaf) { + print_tagq(h, h->metaf); + h->metaf = NULL; + } + + h->flags &= ~HTML_IGNDELIM; + + /* + * Note that we don't process the pipe: the parser sees it as + * punctuation, but we don't in terms of typography. + */ + if (word[0] && '\0' == word[1]) + switch (word[0]) { + case('('): + /* FALLTHROUGH */ + case('['): + h->flags |= HTML_NOSPACE; + break; + default: + break; + } +} + + +void +print_tagq(struct html *h, const struct tag *until) +{ + struct tag *tag; + + while ((tag = h->tags.head) != NULL) { + if (tag == h->metaf) + h->metaf = NULL; + print_ctag(h, tag->tag); + h->tags.head = tag->next; + free(tag); + if (until && tag == until) + return; + } +} + + +void +print_stagq(struct html *h, const struct tag *suntil) +{ + struct tag *tag; + + while ((tag = h->tags.head) != NULL) { + if (suntil && tag == suntil) + return; + if (tag == h->metaf) + h->metaf = NULL; + print_ctag(h, tag->tag); + h->tags.head = tag->next; + free(tag); + } +} + + +void +bufinit(struct html *h) +{ + + h->buf[0] = '\0'; + h->buflen = 0; +} + + +void +bufcat_style(struct html *h, const char *key, const char *val) +{ + + bufcat(h, key); + bufncat(h, ":", 1); + bufcat(h, val); + bufncat(h, ";", 1); +} + + +void +bufcat(struct html *h, const char *p) +{ + + bufncat(h, p, strlen(p)); +} + + +void +buffmt(struct html *h, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)vsnprintf(h->buf + (int)h->buflen, + BUFSIZ - h->buflen - 1, fmt, ap); + va_end(ap); + h->buflen = strlen(h->buf); +} + + +void +bufncat(struct html *h, const char *p, size_t sz) +{ + + if (h->buflen + sz > BUFSIZ - 1) + sz = BUFSIZ - 1 - h->buflen; + + (void)strncat(h->buf, p, sz); + h->buflen += sz; +} + + +void +buffmt_includes(struct html *h, const char *name) +{ + const char *p, *pp; + + pp = h->base_includes; + + while (NULL != (p = strchr(pp, '%'))) { + bufncat(h, pp, (size_t)(p - pp)); + switch (*(p + 1)) { + case('I'): + bufcat(h, name); + break; + default: + bufncat(h, p, 2); + break; + } + pp = p + 2; + } + if (pp) + bufcat(h, pp); +} + + +void +buffmt_man(struct html *h, + const char *name, const char *sec) +{ + const char *p, *pp; + + pp = h->base_man; + + /* LINTED */ + while (NULL != (p = strchr(pp, '%'))) { + bufncat(h, pp, (size_t)(p - pp)); + switch (*(p + 1)) { + case('S'): + bufcat(h, sec ? sec : "1"); + break; + case('N'): + buffmt(h, name); + break; + default: + bufncat(h, p, 2); + break; + } + pp = p + 2; + } + if (pp) + bufcat(h, pp); +} + + +void +bufcat_su(struct html *h, const char *p, const struct roffsu *su) +{ + double v; + const char *u; + + v = su->scale; + + switch (su->unit) { + case (SCALE_CM): + u = "cm"; + break; + case (SCALE_IN): + u = "in"; + break; + case (SCALE_PC): + u = "pc"; + break; + case (SCALE_PT): + u = "pt"; + break; + case (SCALE_EM): + u = "em"; + break; + case (SCALE_MM): + if (0 == (v /= 100)) + v = 1; + u = "em"; + break; + case (SCALE_EN): + u = "ex"; + break; + case (SCALE_BU): + u = "ex"; + break; + case (SCALE_VS): + u = "em"; + break; + default: + u = "ex"; + break; + } + + /* + * XXX: the CSS spec isn't clear as to which types accept + * integer or real numbers, so we just make them all decimals. + */ + buffmt(h, "%s: %.2f%s;", p, v, u); +} + + +void +html_idcat(char *dst, const char *src, int sz) +{ + int ssz; + + assert(sz > 2); + + /* Cf. . */ + + /* We can't start with a number (bah). */ + + if ('#' == *dst) { + dst++; + sz--; + } + if ('\0' == *dst) { + *dst++ = 'x'; + *dst = '\0'; + sz--; + } + + for ( ; *dst != '\0' && sz; dst++, sz--) + /* Jump to end. */ ; + + for ( ; *src != '\0' && sz > 1; src++) { + ssz = snprintf(dst, (size_t)sz, "%.2x", *src); + sz -= ssz; + dst += ssz; + } +} diff --git a/contrib/mdocml/html.h b/contrib/mdocml/html.h new file mode 100644 index 0000000000..8d9db89095 --- /dev/null +++ b/contrib/mdocml/html.h @@ -0,0 +1,157 @@ +/* $Id: html.h,v 1.38 2011/01/06 11:55:39 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifndef HTML_H +#define HTML_H + +__BEGIN_DECLS + +enum htmltag { + TAG_HTML, + TAG_HEAD, + TAG_BODY, + TAG_META, + TAG_TITLE, + TAG_DIV, + TAG_H1, + TAG_H2, + TAG_SPAN, + TAG_LINK, + TAG_BR, + TAG_A, + TAG_TABLE, + TAG_TBODY, + TAG_COL, + TAG_TR, + TAG_TD, + TAG_LI, + TAG_UL, + TAG_OL, + TAG_DL, + TAG_DT, + TAG_DD, + TAG_BLOCKQUOTE, + TAG_P, + TAG_PRE, + TAG_B, + TAG_I, + TAG_CODE, + TAG_SMALL, + TAG_MAX +}; + +enum htmlattr { + ATTR_HTTPEQUIV, + ATTR_CONTENT, + ATTR_NAME, + ATTR_REL, + ATTR_HREF, + ATTR_TYPE, + ATTR_MEDIA, + ATTR_CLASS, + ATTR_STYLE, + ATTR_WIDTH, + ATTR_ID, + ATTR_SUMMARY, + ATTR_ALIGN, + ATTR_MAX +}; + +enum htmlfont { + HTMLFONT_NONE = 0, + HTMLFONT_BOLD, + HTMLFONT_ITALIC, + HTMLFONT_MAX +}; + +struct tag { + struct tag *next; + enum htmltag tag; +}; + +struct tagq { + struct tag *head; +}; + +struct htmlpair { + enum htmlattr key; + const char *val; +}; + +#define PAIR_INIT(p, t, v) \ + do { \ + (p)->key = (t); \ + (p)->val = (v); \ + } while (/* CONSTCOND */ 0) + +#define PAIR_ID_INIT(p, v) PAIR_INIT(p, ATTR_ID, v) +#define PAIR_CLASS_INIT(p, v) PAIR_INIT(p, ATTR_CLASS, v) +#define PAIR_HREF_INIT(p, v) PAIR_INIT(p, ATTR_HREF, v) +#define PAIR_STYLE_INIT(p, h) PAIR_INIT(p, ATTR_STYLE, (h)->buf) +#define PAIR_SUMMARY_INIT(p, v) PAIR_INIT(p, ATTR_SUMMARY, v) + +enum htmltype { + HTML_HTML_4_01_STRICT, + HTML_XHTML_1_0_STRICT +}; + +struct html { + int flags; +#define HTML_NOSPACE (1 << 0) +#define HTML_IGNDELIM (1 << 1) +#define HTML_KEEP (1 << 2) +#define HTML_PREKEEP (1 << 3) +#define HTML_NONOSPACE (1 << 4) + struct tagq tags; /* stack of open tags */ + struct rofftbl tbl; /* current table */ + void *symtab; /* character-escapes */ + char *base_man; /* base for manpage href */ + char *base_includes; /* base for include href */ + char *style; /* style-sheet URI */ + char buf[BUFSIZ]; /* see bufcat and friends */ + size_t buflen; + struct tag *metaf; /* current open font scope */ + enum htmlfont metal; /* last used font */ + enum htmlfont metac; /* current font mode */ + enum htmltype type; +}; + +void print_gen_decls(struct html *); +void print_gen_head(struct html *); +struct tag *print_otag(struct html *, enum htmltag, + int, const struct htmlpair *); +void print_tagq(struct html *, const struct tag *); +void print_stagq(struct html *, const struct tag *); +void print_text(struct html *, const char *); +void print_tbl(struct html *, const struct tbl_span *); + +void bufcat_su(struct html *, const char *, + const struct roffsu *); +void buffmt_man(struct html *, + const char *, const char *); +void buffmt_includes(struct html *, const char *); +void buffmt(struct html *, const char *, ...); +void bufcat(struct html *, const char *); +void bufcat_style(struct html *, + const char *, const char *); +void bufncat(struct html *, const char *, size_t); +void bufinit(struct html *); + +void html_idcat(char *, const char *, int); + +__END_DECLS + +#endif /*!HTML_H*/ diff --git a/contrib/mdocml/index.css b/contrib/mdocml/index.css new file mode 100644 index 0000000000..d8d0b2d80f --- /dev/null +++ b/contrib/mdocml/index.css @@ -0,0 +1,48 @@ +body { color: #333333; + font-size: 0.93em; + font-family: Times, sans-serif; } + +table.frame { max-width: 800px; + padding-right: 2em; + padding-left: 1em; } + +table { padding-left: 40px; } + +p { padding-left: 40px; + text-align: justify; } + +h1 { font-weight: bold; + font-size: small; + font-family: Verdana, Tahoma, Arial, sans-serif; } + +h2 { font-weight: bold; + font-size: small; + padding-left: 20px; + margin-bottom: 0px; + font-family: Verdana, Tahoma, Arial, sans-serif; } + +span.nm { font-weight: bold; } + +span.file { font-style: italic; } + +span.attn { color: #000000; font-weight: bold; } + +span.flag { font-weight: bold; } + +div.head { border-bottom: 1px solid #dddddd; + padding-bottom: 5px; + text-align: right; } + +div.foot { border-top: 1px solid #dddddd; + padding-top: 5px; + font-size: smaller; + text-align: right; } + +a.external { background: transparent url(external.png) center right no-repeat; + padding-right: 12px; } + +span.date { color: #000000; } + +div.news { margin-bottom: 2em; } + +div.news ul { margin-left: 4em; } diff --git a/contrib/mdocml/index.sgml b/contrib/mdocml/index.sgml new file mode 100644 index 0000000000..7abc9975f2 --- /dev/null +++ b/contrib/mdocml/index.sgml @@ -0,0 +1,398 @@ + + + + + + + mdocml | mdoc macro compiler + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ mdocml – mdoc macro compiler +
+
+

+ DESCRIPTION +

+ +

+ mdocml is a suite of tools compiling -mdoc, the + roff macro package of choice for BSD manual pages, and -man, the + predominant historical package for UNIX manuals. The mission of mdocml is to + deprecate groff, the GNU troff + implementation, for displaying -mdoc pages whilst providing token support for -man. +

+ +

+ Why? groff amounts to over 5 MB of source code, most of which is C++ and all of which is GPL. It runs + slowly, produces uncertain output, and varies in operation from system to system. mdocml strives to fix + this (respectively small, C, ISC-licensed, fast and regular). +

+ +

+ mdocml consists of the libmdoc, libman, and libroff validating compilers; and mandoc, which interfaces with the compiler libraries to format output for UNIX + terminals, XHTML, HTML, PostScript, and PDF. It is a BSD.lv project. +

+ +

+ Disambiguation: mdocml is often referred to by its installed binary, + mandoc. +

+
+

+ SOURCES +

+ +

+ mdocml is architecture- and system-neutral, written in plain-old C. The most + current version is @VERSION@, dated @VDATE@. A full + ChangeLog (txt) is written with each release. +

+ +

+ Current +

+ + + + + + + + + + + + + + +
Source archive + /snapshots/mdocml.tar.gz + (md5) +
Online source + cvsweb +
+ +

+ Downstream +

+ + + + + + + + + + + + + + + + + + + + + + +
DragonFly BSD + usr.bin/mandoc +
FreeBSD + ports/textproc/mdocml +
NetBSD + src/external/bsd/mdocml +
OpenBSD + src/usr.bin/mandoc +
+ +

+ Historical +

+ + + + + + + + + + +
Source archive + /snapshots/ +
+
+

+ DOCUMENTATION +

+ +

+ These manuals are generated automatically and refer to the current snapshot. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
mandoc(1) + format and display UNIX manuals + + (text | + xhtml | + pdf | + postscript) + +
man(3) + man macro compiler library + + (text | + xhtml | + pdf | + postscript) + +
mdoc(3) + mdoc macro compiler library + + (text | + xhtml | + pdf | + postscript) + +
roff(3) + roff macro compiler library + + (text | + xhtml | + pdf | + postscript) + +
man(7) + man language reference + + (text | + xhtml | + pdf | + postscript) + +
mandoc_char(7) + mandoc special characters + + (text | + xhtml | + pdf | + postscript) + +
mdoc(7) + mdoc language reference + + (text | + xhtml | + pdf | + postscript) + +
roff(7) + roff-mandoc language reference + + (text | + xhtml | + pdf | + postscript) + +
tbl(7) + tbl-mandoc language reference + + (text | + xhtml | + pdf | + postscript) + +
+ +

+ See Writing UNIX Manual Pages for a general + introduction to manpages and mdoc. +

+
+

+ CONTACT +

+ +

+ Please use the mailing lists for bug-reports, patches, questions, etc. (these require + subscription). Beyond that, contact Kristaps at kris...@bsd.lv. +

+ + + + + + + + + + + + + + + + + + +
+ disc...@mdocml.bsd.lv + + bug-reports, general questions, and announcements + (archive) +
+ tec...@mdocml.bsd.lv + + patches and system discussions + (archive) +
+ sou...@mdocml.bsd.lv + + source commit messages + (archive) +
+
+

+ NEWS +

+
+

+ 07-01-2011: + version 1.10.9 +

+

+ Many back-end fixes have been implemented: argument handling (quoting), man improvements, error/warning classes, and many more. +

+

+ Initial tbl functionality (see the TS, TE, and + T& macros in the roff manual) has been + merged from tbl.bsd.lv. Output is + still minimal, especially for -Thtml and -Txhtml, but manages to at least display data. This means that mandoc now has built-in support for two troff preprocessors via + libroff: soelim and tbl. +

+
+
+

+ 24-12-2010: + version 1.10.8 +

+

+ Significant improvements merged from OpenBSD downstream, including +

+
    +
  • many new roff components,
  • +
  • in-line implementation of troff's soelim,
  • +
  • broken-block handling,
  • +
  • overhauled error classifications, and
  • +
  • cleaned up handling of error conditions.
  • +
+

+ Also overhauled the -Thtml and -Txhtml output modes. They now display readable output in arbitrary + browsers, including text-based ones like lynx. See HTML and XHTML manuals in the DOCUMENTATION section for examples. Attention: available style-sheet classes have been considerably + changed! See the example.style.css file for details. + Lastly, libmdoc and libman have been + cleaned up and reduced in size and complexity. +

+
+

+ See cvsweb for + historical notes. +

+
+
+ Copyright © 2008–2010 Kristaps Dzonsons, $Date: 2011/01/07 13:10:03 $ +
+
+ + diff --git a/contrib/mdocml/lib.c b/contrib/mdocml/lib.c new file mode 100644 index 0000000000..bbf2aec8bb --- /dev/null +++ b/contrib/mdocml/lib.c @@ -0,0 +1,38 @@ +/* $Id: lib.c,v 1.8 2010/06/19 20:46:27 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2lib(const char *p) +{ + +#include "lib.in" + + return(NULL); +} diff --git a/contrib/mdocml/lib.in b/contrib/mdocml/lib.in new file mode 100644 index 0000000000..18ee711c1b --- /dev/null +++ b/contrib/mdocml/lib.in @@ -0,0 +1,93 @@ +/* $Id: lib.in,v 1.9 2010/06/19 20:46:27 kristaps 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. + */ + +/* + * These are all possible .Lb strings. When a new library is added, add + * its short-string to the left-hand side and formatted string to the + * right-hand side. + * + * Be sure to escape strings. + */ + +LINE("libarchive", "Reading and Writing Streaming Archives Library (libarchive, \\-larchive)") +LINE("libarm", "ARM Architecture Library (libarm, \\-larm)") +LINE("libarm32", "ARM32 Architecture Library (libarm32, \\-larm32)") +LINE("libbluetooth", "Bluetooth Library (libbluetooth, \\-lbluetooth)") +LINE("libbsm", "Basic Security Module User Library (libbsm, \\-lbsm)") +LINE("libc", "Standard C Library (libc, \\-lc)") +LINE("libc_r", "Reentrant C\\~Library (libc_r, \\-lc_r)") +LINE("libcalendar", "Calendar Arithmetic Library (libcalendar, \\-lcalendar)") +LINE("libcam", "Common Access Method User Library (libcam, \\-lcam)") +LINE("libcdk", "Curses Development Kit Library (libcdk, \\-lcdk)") +LINE("libcipher", "FreeSec Crypt Library (libcipher, \\-lcipher)") +LINE("libcompat", "Compatibility Library (libcompat, \\-lcompat)") +LINE("libcrypt", "Crypt Library (libcrypt, \\-lcrypt)") +LINE("libcurses", "Curses Library (libcurses, \\-lcurses)") +LINE("libdevinfo", "Device and Resource Information Utility Library (libdevinfo, \\-ldevinfo)") +LINE("libdevstat", "Device Statistics Library (libdevstat, \\-ldevstat)") +LINE("libdisk", "Interface to Slice and Partition Labels Library (libdisk, \\-ldisk)") +LINE("libedit", "Command Line Editor Library (libedit, \\-ledit)") +LINE("libelf", "ELF Parsing Library (libelf, \\-lelf)") +LINE("libevent", "Event Notification Library (libevent, \\-levent)") +LINE("libfetch", "File Transfer Library for URLs (libfetch, \\-lfetch)") +LINE("libform", "Curses Form Library (libform, \\-lform)") +LINE("libgeom", "Userland API Library for kernel GEOM subsystem (libgeom, \\-lgeom)") +LINE("libgpib", "General-Purpose Instrument Bus (GPIB) library (libgpib, \\-lgpib)") +LINE("libi386", "i386 Architecture Library (libi386, \\-li386)") +LINE("libintl", "Internationalized Message Handling Library (libintl, \\-lintl)") +LINE("libipsec", "IPsec Policy Control Library (libipsec, \\-lipsec)") +LINE("libipx", "IPX Address Conversion Support Library (libipx, \\-lipx)") +LINE("libiscsi", "iSCSI protocol library (libiscsi, \\-liscsi)") +LINE("libjail", "Jail Library (libjail, \\-ljail)") +LINE("libkiconv", "Kernel side iconv library (libkiconv, \\-lkiconv)") +LINE("libkse", "N:M Threading Library (libkse, \\-lkse)") +LINE("libkvm", "Kernel Data Access Library (libkvm, \\-lkvm)") +LINE("libm", "Math Library (libm, \\-lm)") +LINE("libm68k", "m68k Architecture Library (libm68k, \\-lm68k)") +LINE("libmagic", "Magic Number Recognition Library (libmagic, \\-lmagic)") +LINE("libmd", "Message Digest (MD4, MD5, etc.) Support Library (libmd, \\-lmd)") +LINE("libmemstat", "Kernel Memory Allocator Statistics Library (libmemstat, \\-lmemstat)") +LINE("libmenu", "Curses Menu Library (libmenu, \\-lmenu)") +LINE("libnetgraph", "Netgraph User Library (libnetgraph, \\-lnetgraph)") +LINE("libnetpgp", "Netpgp signing, verification, encryption and decryption (libnetpgp, \\-lnetpgp)") +LINE("libossaudio", "OSS Audio Emulation Library (libossaudio, \\-lossaudio)") +LINE("libpam", "Pluggable Authentication Module Library (libpam, \\-lpam)") +LINE("libpcap", "Capture Library (libpcap, \\-lpcap)") +LINE("libpci", "PCI Bus Access Library (libpci, \\-lpci)") +LINE("libpmc", "Performance Counters Library (libpmc, \\-lpmc)") +LINE("libposix", "POSIX Compatibility Library (libposix, \\-lposix)") +LINE("libprop", "Property Container Object Library (libprop, \\-lprop)") +LINE("libpthread", "POSIX Threads Library (libpthread, \\-lpthread)") +LINE("libpuffs", "puffs Convenience Library (libpuffs, \\-lpuffs)") +LINE("librefuse", "File System in Userspace Convenience Library (librefuse, \\-lrefuse)") +LINE("libresolv", "DNS Resolver Library (libresolv, \\-lresolv)") +LINE("librpcsec_gss", "RPC GSS-API Authentication Library (librpcsec_gss, \\-lrpcsec_gss)") +LINE("librpcsvc", "RPC Service Library (librpcsvc, \\-lrpcsvc)") +LINE("librt", "POSIX Real\\-time Library (librt, -lrt)") +LINE("libsdp", "Bluetooth Service Discovery Protocol User Library (libsdp, \\-lsdp)") +LINE("libssp", "Buffer Overflow Protection Library (libssp, \\-lssp)") +LINE("libtermcap", "Termcap Access Library (libtermcap, \\-ltermcap)") +LINE("libterminfo", "Terminal Information Library (libterminfo, \\-lterminfo)") +LINE("libthr", "1:1 Threading Library (libthr, \\-lthr)") +LINE("libufs", "UFS File System Access Library (libufs, \\-lufs)") +LINE("libugidfw", "File System Firewall Interface Library (libugidfw, \\-lugidfw)") +LINE("libulog", "User Login Record Library (libulog, \\-lulog)") +LINE("libusbhid", "USB Human Interface Devices Library (libusbhid, \\-lusbhid)") +LINE("libutil", "System Utilities Library (libutil, \\-lutil)") +LINE("libvgl", "Video Graphics Library (libvgl, \\-lvgl)") +LINE("libx86_64", "x86_64 Architecture Library (libx86_64, \\-lx86_64)") +LINE("libz", "Compression Library (libz, \\-lz)") diff --git a/contrib/mdocml/libman.h b/contrib/mdocml/libman.h new file mode 100644 index 0000000000..e1a9aecc8e --- /dev/null +++ b/contrib/mdocml/libman.h @@ -0,0 +1,92 @@ +/* $Id: libman.h,v 1.44 2010/11/30 15:36:28 kristaps 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. + */ +#ifndef LIBMAN_H +#define LIBMAN_H + +#include "man.h" + +enum man_next { + MAN_NEXT_SIBLING = 0, + MAN_NEXT_CHILD +}; + +struct man { + void *data; /* private application data */ + mandocmsg msg; /* output message handler */ + int flags; /* parse flags */ +#define MAN_HALT (1 << 0) /* badness happened: die */ +#define MAN_ELINE (1 << 1) /* Next-line element scope. */ +#define MAN_BLINE (1 << 2) /* Next-line block scope. */ +#define MAN_ILINE (1 << 3) /* Ignored in next-line scope. */ +#define MAN_LITERAL (1 << 4) /* Literal input. */ +#define MAN_BPLINE (1 << 5) + enum man_next next; /* where to put the next node */ + struct man_node *last; /* the last parsed node */ + struct man_node *first; /* the first parsed node */ + struct man_meta meta; /* document meta-data */ + struct regset *regs; /* registers */ +}; + +#define MACRO_PROT_ARGS struct man *m, \ + enum mant tok, \ + int line, \ + int ppos, \ + int *pos, \ + char *buf + +struct man_macro { + int (*fp)(MACRO_PROT_ARGS); + int flags; +#define MAN_SCOPED (1 << 0) +#define MAN_EXPLICIT (1 << 1) /* See blk_imp(). */ +#define MAN_FSCOPED (1 << 2) /* See blk_imp(). */ +#define MAN_NSCOPED (1 << 3) /* See in_line_eoln(). */ +#define MAN_NOCLOSE (1 << 4) /* See blk_exp(). */ +}; + +extern const struct man_macro *const man_macros; + +__BEGIN_DECLS + +#define man_pmsg(m, l, p, t) \ + (*(m)->msg)((t), (m)->data, (l), (p), NULL) +#define man_nmsg(m, n, t) \ + (*(m)->msg)((t), (m)->data, (n)->line, (n)->pos, NULL) +int man_word_alloc(struct man *, int, int, const char *); +int man_block_alloc(struct man *, int, int, enum mant); +int man_head_alloc(struct man *, int, int, enum mant); +int man_body_alloc(struct man *, int, int, enum mant); +int man_elem_alloc(struct man *, int, int, enum mant); +void man_node_delete(struct man *, struct man_node *); +void man_hash_init(void); +enum mant man_hash_find(const char *); +int man_macroend(struct man *); +int man_args(struct man *, int, int *, char *, char **); +#define ARGS_ERROR (-1) +#define ARGS_EOLN (0) +#define ARGS_WORD (1) +#define ARGS_QWORD (1) +int man_vmsg(struct man *, enum mandocerr, + int, int, const char *, ...); +int man_valid_post(struct man *); +int man_valid_pre(struct man *, struct man_node *); +int man_unscope(struct man *, + const struct man_node *, enum mandocerr); + +__END_DECLS + +#endif /*!LIBMAN_H*/ diff --git a/contrib/mdocml/libmandoc.h b/contrib/mdocml/libmandoc.h new file mode 100644 index 0000000000..0e9a749282 --- /dev/null +++ b/contrib/mdocml/libmandoc.h @@ -0,0 +1,38 @@ +/* $Id: libmandoc.h,v 1.10 2011/01/03 22:42:37 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. + */ +#ifndef LIBMANDOC_H +#define LIBMANDOC_H + +__BEGIN_DECLS + +int mandoc_special(char *); +void *mandoc_calloc(size_t, size_t); +char *mandoc_strdup(const char *); +void *mandoc_malloc(size_t); +void *mandoc_realloc(void *, size_t); +char *mandoc_getarg(char **, mandocmsg, void *, int, int *); +time_t mandoc_a2time(int, const char *); +#define MTIME_CANONICAL (1 << 0) +#define MTIME_REDUCED (1 << 1) +#define MTIME_MDOCDATE (1 << 2) +#define MTIME_ISO_8601 (1 << 3) +int mandoc_eos(const char *, size_t, int); +int mandoc_hyph(const char *, const char *); + +__END_DECLS + +#endif /*!LIBMANDOC_H*/ diff --git a/contrib/mdocml/libmdoc.h b/contrib/mdocml/libmdoc.h new file mode 100644 index 0000000000..5a46d1cfb9 --- /dev/null +++ b/contrib/mdocml/libmdoc.h @@ -0,0 +1,147 @@ +/* $Id: libmdoc.h,v 1.63 2010/11/30 13:04:14 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifndef LIBMDOC_H +#define LIBMDOC_H + +#include "mdoc.h" + +enum mdoc_next { + MDOC_NEXT_SIBLING = 0, + MDOC_NEXT_CHILD +}; + +struct mdoc { + void *data; /* private application data */ + mandocmsg msg; /* message callback */ + int flags; +#define MDOC_HALT (1 << 0) /* error in parse: halt */ +#define MDOC_LITERAL (1 << 1) /* in a literal scope */ +#define MDOC_PBODY (1 << 2) /* in the document body */ +#define MDOC_NEWLINE (1 << 3) /* first macro/text in a line */ +#define MDOC_PHRASELIT (1 << 4) /* literal within a partila phrase */ +#define MDOC_PPHRASE (1 << 5) /* within a partial phrase */ +#define MDOC_FREECOL (1 << 6) /* `It' invocation should close */ +#define MDOC_SYNOPSIS (1 << 7) /* SYNOPSIS-style formatting */ + enum mdoc_next next; /* where to put the next node */ + struct mdoc_node *last; /* the last node parsed */ + struct mdoc_node *first; /* the first node parsed */ + struct mdoc_meta meta; /* document meta-data */ + enum mdoc_sec lastnamed; + enum mdoc_sec lastsec; + struct regset *regs; /* registers */ +}; + +#define MACRO_PROT_ARGS struct mdoc *m, \ + enum mdoct tok, \ + int line, \ + int ppos, \ + int *pos, \ + char *buf + +struct mdoc_macro { + int (*fp)(MACRO_PROT_ARGS); + int flags; +#define MDOC_CALLABLE (1 << 0) +#define MDOC_PARSED (1 << 1) +#define MDOC_EXPLICIT (1 << 2) +#define MDOC_PROLOGUE (1 << 3) +#define MDOC_IGNDELIM (1 << 4) + /* Reserved words in arguments treated as text. */ +}; + +enum margserr { + ARGS_ERROR, + ARGS_EOLN, + ARGS_WORD, + ARGS_PUNCT, + ARGS_QWORD, + ARGS_PHRASE, + ARGS_PPHRASE, + ARGS_PEND +}; + +enum margverr { + ARGV_ERROR, + ARGV_EOLN, + ARGV_ARG, + ARGV_WORD +}; + +enum mdelim { + DELIM_NONE = 0, + DELIM_OPEN, + DELIM_MIDDLE, + DELIM_CLOSE +}; + +extern const struct mdoc_macro *const mdoc_macros; + +__BEGIN_DECLS + +#define mdoc_pmsg(m, l, p, t) \ + (*(m)->msg)((t), (m)->data, (l), (p), NULL) +#define mdoc_nmsg(m, n, t) \ + (*(m)->msg)((t), (m)->data, (n)->line, (n)->pos, NULL) +int mdoc_vmsg(struct mdoc *, enum mandocerr, + int, int, const char *, ...); +int mdoc_macro(MACRO_PROT_ARGS); +int mdoc_word_alloc(struct mdoc *, + int, int, const char *); +int mdoc_elem_alloc(struct mdoc *, int, int, + enum mdoct, struct mdoc_arg *); +int mdoc_block_alloc(struct mdoc *, int, int, + enum mdoct, struct mdoc_arg *); +int mdoc_head_alloc(struct mdoc *, int, int, enum mdoct); +int mdoc_tail_alloc(struct mdoc *, int, int, enum mdoct); +int mdoc_body_alloc(struct mdoc *, int, int, enum mdoct); +int mdoc_endbody_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, struct mdoc_node *body, + enum mdoc_endbody end); +void mdoc_node_delete(struct mdoc *, struct mdoc_node *); +void mdoc_hash_init(void); +enum mdoct mdoc_hash_find(const char *); +enum mdelim mdoc_iscdelim(char); +enum mdelim mdoc_isdelim(const char *); +size_t mdoc_isescape(const char *); +enum mdoc_sec mdoc_str2sec(const char *); +time_t mdoc_atotime(const char *); +size_t mdoc_macro2len(enum mdoct); +const char *mdoc_a2att(const char *); +const char *mdoc_a2lib(const char *); +const char *mdoc_a2st(const char *); +const char *mdoc_a2arch(const char *); +const char *mdoc_a2vol(const char *); +const char *mdoc_a2msec(const char *); +int mdoc_valid_pre(struct mdoc *, struct mdoc_node *); +int mdoc_valid_post(struct mdoc *); +enum margverr mdoc_argv(struct mdoc *, int, enum mdoct, + struct mdoc_arg **, int *, char *); +void mdoc_argv_free(struct mdoc_arg *); +void mdoc_argn_free(struct mdoc_arg *, int); +enum margserr mdoc_args(struct mdoc *, int, + int *, char *, enum mdoct, char **); +enum margserr mdoc_zargs(struct mdoc *, int, + int *, char *, int, char **); +#define ARGS_DELIM (1 << 1) +#define ARGS_TABSEP (1 << 2) +#define ARGS_NOWARN (1 << 3) + +int mdoc_macroend(struct mdoc *); + +__END_DECLS + +#endif /*!LIBMDOC_H*/ diff --git a/contrib/mdocml/libroff.h b/contrib/mdocml/libroff.h new file mode 100644 index 0000000000..b4e043a7ac --- /dev/null +++ b/contrib/mdocml/libroff.h @@ -0,0 +1,62 @@ +/* $Id: libroff.h,v 1.16 2011/01/04 15:02:00 kristaps 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. + */ +#ifndef LIBROFF_H +#define LIBROFF_H + +__BEGIN_DECLS + +enum tbl_part { + TBL_PART_OPTS, /* in options (first line) */ + TBL_PART_LAYOUT, /* describing layout */ + TBL_PART_DATA, /* creating data rows */ + TBL_PART_CDATA /* continue previous row */ +}; + +struct tbl_node { + mandocmsg msg; /* status messages */ + void *data; /* privdata for messages */ + int pos; /* invocation column */ + int line; /* invocation line */ + enum tbl_part part; + struct tbl opts; + struct tbl_row *first_row; + struct tbl_row *last_row; + struct tbl_span *first_span; + struct tbl_span *last_span; + struct tbl_head *first_head; + struct tbl_head *last_head; + struct tbl_node *next; +}; + +#define TBL_MSG(tblp, type, line, col) \ + (*(tblp)->msg)((type), (tblp)->data, (line), (col), NULL) + +struct tbl_node *tbl_alloc(int, int, void *, mandocmsg); +void tbl_restart(int, int, struct tbl_node *); +void tbl_free(struct tbl_node *); +void tbl_reset(struct tbl_node *); +enum rofferr tbl_read(struct tbl_node *, int, const char *, int); +int tbl_option(struct tbl_node *, int, const char *); +int tbl_layout(struct tbl_node *, int, const char *); +int tbl_data(struct tbl_node *, int, const char *); +int tbl_cdata(struct tbl_node *, int, const char *); +const struct tbl_span *tbl_span(const struct tbl_node *); +void tbl_end(struct tbl_node *); + +__END_DECLS + +#endif /*LIBROFF_H*/ diff --git a/contrib/mdocml/main.c b/contrib/mdocml/main.c new file mode 100644 index 0000000000..2be68a9350 --- /dev/null +++ b/contrib/mdocml/main.c @@ -0,0 +1,1040 @@ +/* $Id: main.c,v 1.135 2011/01/04 15:02:00 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons + * Copyright (c) 2010 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "main.h" +#include "mdoc.h" +#include "man.h" +#include "roff.h" + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +#define REPARSE_LIMIT 1000 +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) + +/* FIXME: Intel's compiler? LLVM? pcc? */ + +#if !defined(__GNUC__) || (__GNUC__ < 2) +# if !defined(lint) +# define __attribute__(x) +# endif +#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */ + +typedef void (*out_mdoc)(void *, const struct mdoc *); +typedef void (*out_man)(void *, const struct man *); +typedef void (*out_free)(void *); + +struct buf { + char *buf; + size_t sz; +}; + +enum intt { + INTT_AUTO, + INTT_MDOC, + INTT_MAN +}; + +enum outt { + OUTT_ASCII = 0, + OUTT_TREE, + OUTT_HTML, + OUTT_XHTML, + OUTT_LINT, + OUTT_PS, + OUTT_PDF +}; + +struct curparse { + const char *file; /* Current parse. */ + int fd; /* Current parse. */ + int line; /* Line number in the file. */ + enum mandoclevel wlevel; /* Ignore messages below this. */ + int wstop; /* Stop after a file with a warning. */ + enum intt inttype; /* which parser to use */ + struct man *pman; /* persistent man parser */ + struct mdoc *pmdoc; /* persistent mdoc parser */ + struct man *man; /* man parser */ + struct mdoc *mdoc; /* mdoc parser */ + struct roff *roff; /* roff parser (!NULL) */ + struct regset regs; /* roff registers */ + int reparse_count; /* finite interpolation stack */ + enum outt outtype; /* which output to use */ + out_mdoc outmdoc; /* mdoc output ptr */ + out_man outman; /* man output ptr */ + out_free outfree; /* free output ptr */ + void *outdata; /* data for output */ + char outopts[BUFSIZ]; /* buf of output opts */ +}; + +static const char * const mandoclevels[MANDOCLEVEL_MAX] = { + "SUCCESS", + "RESERVED", + "WARNING", + "ERROR", + "FATAL", + "BADARG", + "SYSERR" +}; + +static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = { + MANDOCERR_OK, + MANDOCERR_WARNING, + MANDOCERR_WARNING, + MANDOCERR_ERROR, + MANDOCERR_FATAL, + MANDOCERR_MAX, + MANDOCERR_MAX +}; + +static const char * const mandocerrs[MANDOCERR_MAX] = { + "ok", + + "generic warning", + + /* related to the prologue */ + "no title in document", + "document title should be all caps", + "unknown manual section", + "cannot parse date argument", + "prologue macros out of order", + "duplicate prologue macro", + "macro not allowed in prologue", + "macro not allowed in body", + + /* related to document structure */ + ".so is fragile, better use ln(1)", + "NAME section must come first", + "bad NAME section contents", + "manual name not yet set", + "sections out of conventional order", + "duplicate section name", + "section not in conventional manual section", + + /* related to macros and nesting */ + "skipping obsolete macro", + "skipping paragraph macro", + "blocks badly nested", + "child violates parent syntax", + "nested displays are not portable", + "already in literal mode", + + /* related to missing macro arguments */ + "skipping empty macro", + "argument count wrong", + "missing display type", + "list type must come first", + "tag lists require a width argument", + "missing font type", + + /* related to bad macro arguments */ + "skipping argument", + "duplicate argument", + "duplicate display type", + "duplicate list type", + "unknown AT&T UNIX version", + "bad Boolean value", + "unknown font", + "unknown standard specifier", + "bad width argument", + + /* related to plain text */ + "blank line in non-literal context", + "tab in non-literal context", + "end of line whitespace", + "bad comment style", + "unknown escape sequence", + "unterminated quoted string", + + /* related to tables */ + "extra data cells", + + "generic error", + + /* related to tables */ + "bad table syntax", + "bad table option", + "bad table layout", + "no table layout cells specified", + "no table data cells specified", + "ignore data in cell", + "data block still open", + + "input stack limit exceeded, infinite loop?", + "skipping bad character", + "skipping text before the first section header", + "skipping unknown macro", + "NOT IMPLEMENTED: skipping request", + "line scope broken", + "argument count wrong", + "skipping end of block that is not open", + "missing end of block", + "scope open on exit", + "uname(3) system call failed", + "macro requires line argument(s)", + "macro requires body argument(s)", + "macro requires argument(s)", + "missing list type", + "line argument(s) will be lost", + "body argument(s) will be lost", + + "generic fatal error", + + "column syntax is inconsistent", + "NOT IMPLEMENTED: .Bd -file", + "line scope broken, syntax violated", + "argument count wrong, violates syntax", + "child violates parent syntax", + "argument count wrong, violates syntax", + "NOT IMPLEMENTED: .so with absolute path or \"..\"", + "no document body", + "no document prologue", + "static buffer exhausted", +}; + +static void parsebuf(struct curparse *, struct buf, int); +static void pdesc(struct curparse *); +static void fdesc(struct curparse *); +static void ffile(const char *, struct curparse *); +static int pfile(const char *, struct curparse *); +static int moptions(enum intt *, char *); +static int mmsg(enum mandocerr, void *, + int, int, const char *); +static void pset(const char *, int, struct curparse *); +static int toptions(struct curparse *, char *); +static void usage(void) __attribute__((noreturn)); +static void version(void) __attribute__((noreturn)); +static int woptions(struct curparse *, char *); + +static const char *progname; +static enum mandoclevel file_status = MANDOCLEVEL_OK; +static enum mandoclevel exit_status = MANDOCLEVEL_OK; + +int +main(int argc, char *argv[]) +{ + int c; + struct curparse curp; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + memset(&curp, 0, sizeof(struct curparse)); + + curp.inttype = INTT_AUTO; + curp.outtype = OUTT_ASCII; + curp.wlevel = MANDOCLEVEL_FATAL; + + /* LINTED */ + while (-1 != (c = getopt(argc, argv, "m:O:T:VW:"))) + switch (c) { + case ('m'): + if ( ! moptions(&curp.inttype, optarg)) + return((int)MANDOCLEVEL_BADARG); + break; + case ('O'): + (void)strlcat(curp.outopts, optarg, BUFSIZ); + (void)strlcat(curp.outopts, ",", BUFSIZ); + break; + case ('T'): + if ( ! toptions(&curp, optarg)) + return((int)MANDOCLEVEL_BADARG); + break; + case ('W'): + if ( ! woptions(&curp, optarg)) + return((int)MANDOCLEVEL_BADARG); + break; + case ('V'): + version(); + /* NOTREACHED */ + default: + usage(); + /* NOTREACHED */ + } + + argc -= optind; + argv += optind; + + if (NULL == *argv) { + curp.file = ""; + curp.fd = STDIN_FILENO; + + fdesc(&curp); + } + + while (*argv) { + ffile(*argv, &curp); + if (MANDOCLEVEL_OK != exit_status && curp.wstop) + break; + ++argv; + } + + if (curp.outfree) + (*curp.outfree)(curp.outdata); + if (curp.pmdoc) + mdoc_free(curp.pmdoc); + if (curp.pman) + man_free(curp.pman); + if (curp.roff) + roff_free(curp.roff); + + return((int)exit_status); +} + + +static void +version(void) +{ + + (void)printf("%s %s\n", progname, VERSION); + exit((int)MANDOCLEVEL_OK); +} + + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: %s " + "[-V] " + "[-foption] " + "[-mformat] " + "[-Ooption] " + "[-Toutput] " + "[-Werr] " + "[file...]\n", + progname); + + exit((int)MANDOCLEVEL_BADARG); +} + +static void +ffile(const char *file, struct curparse *curp) +{ + + /* + * Called once per input file. Get the file ready for reading, + * pass it through to the parser-driver, then close it out. + * XXX: don't do anything special as this is only called for + * files; stdin goes directly to fdesc(). + */ + + curp->file = file; + + if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) { + perror(curp->file); + exit_status = MANDOCLEVEL_SYSERR; + return; + } + + fdesc(curp); + + if (-1 == close(curp->fd)) + perror(curp->file); +} + +static int +pfile(const char *file, struct curparse *curp) +{ + const char *savefile; + int fd, savefd; + + if (-1 == (fd = open(file, O_RDONLY, 0))) { + perror(file); + file_status = MANDOCLEVEL_SYSERR; + return(0); + } + + savefile = curp->file; + savefd = curp->fd; + + curp->file = file; + curp->fd = fd; + + pdesc(curp); + + curp->file = savefile; + curp->fd = savefd; + + if (-1 == close(fd)) + perror(file); + + return(MANDOCLEVEL_FATAL > file_status ? 1 : 0); +} + + +static void +resize_buf(struct buf *buf, size_t initial) +{ + + buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial; + buf->buf = realloc(buf->buf, buf->sz); + if (NULL == buf->buf) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } +} + + +static int +read_whole_file(struct curparse *curp, struct buf *fb, int *with_mmap) +{ + struct stat st; + size_t off; + ssize_t ssz; + + if (-1 == fstat(curp->fd, &st)) { + perror(curp->file); + return(0); + } + + /* + * If we're a regular file, try just reading in the whole entry + * via mmap(). This is faster than reading it into blocks, and + * since each file is only a few bytes to begin with, I'm not + * concerned that this is going to tank any machines. + */ + + if (S_ISREG(st.st_mode)) { + if (st.st_size >= (1U << 31)) { + fprintf(stderr, "%s: input too large\n", + curp->file); + return(0); + } + *with_mmap = 1; + fb->sz = (size_t)st.st_size; + fb->buf = mmap(NULL, fb->sz, PROT_READ, + MAP_FILE|MAP_SHARED, curp->fd, 0); + if (fb->buf != MAP_FAILED) + return(1); + } + + /* + * If this isn't a regular file (like, say, stdin), then we must + * go the old way and just read things in bit by bit. + */ + + *with_mmap = 0; + off = 0; + fb->sz = 0; + fb->buf = NULL; + for (;;) { + if (off == fb->sz) { + if (fb->sz == (1U << 31)) { + fprintf(stderr, "%s: input too large\n", + curp->file); + break; + } + resize_buf(fb, 65536); + } + ssz = read(curp->fd, fb->buf + (int)off, fb->sz - off); + if (ssz == 0) { + fb->sz = off; + return(1); + } + if (ssz == -1) { + perror(curp->file); + break; + } + off += (size_t)ssz; + } + + free(fb->buf); + fb->buf = NULL; + return(0); +} + + +static void +fdesc(struct curparse *curp) +{ + + /* + * Called once per file with an opened file descriptor. All + * pre-file-parse operations (whether stdin or a file) should go + * here. + * + * This calls down into the nested parser, which drills down and + * fully parses a file and all its dependences (i.e., `so'). It + * then runs the cleanup validators and pushes to output. + */ + + /* Zero the parse type. */ + + curp->mdoc = NULL; + curp->man = NULL; + file_status = MANDOCLEVEL_OK; + + /* Make sure the mandotory roff parser is initialised. */ + + if (NULL == curp->roff) { + curp->roff = roff_alloc(&curp->regs, curp, mmsg); + assert(curp->roff); + } + + /* Fully parse the file. */ + + pdesc(curp); + + if (MANDOCLEVEL_FATAL <= file_status) + goto cleanup; + + /* NOTE a parser may not have been assigned, yet. */ + + if ( ! (curp->man || curp->mdoc)) { + fprintf(stderr, "%s: Not a manual\n", curp->file); + file_status = MANDOCLEVEL_FATAL; + goto cleanup; + } + + /* Clean up the parse routine ASTs. */ + + if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) { + assert(MANDOCLEVEL_FATAL <= file_status); + goto cleanup; + } + + if (curp->man && ! man_endparse(curp->man)) { + assert(MANDOCLEVEL_FATAL <= file_status); + goto cleanup; + } + + assert(curp->roff); + roff_endparse(curp->roff); + + /* + * With -Wstop and warnings or errors of at least + * the requested level, do not produce output. + */ + + if (MANDOCLEVEL_OK != file_status && curp->wstop) + goto cleanup; + + /* If unset, allocate output dev now (if applicable). */ + + if ( ! (curp->outman && curp->outmdoc)) { + switch (curp->outtype) { + case (OUTT_XHTML): + curp->outdata = xhtml_alloc(curp->outopts); + break; + case (OUTT_HTML): + curp->outdata = html_alloc(curp->outopts); + break; + case (OUTT_ASCII): + curp->outdata = ascii_alloc(curp->outopts); + curp->outfree = ascii_free; + break; + case (OUTT_PDF): + curp->outdata = pdf_alloc(curp->outopts); + curp->outfree = pspdf_free; + break; + case (OUTT_PS): + curp->outdata = ps_alloc(curp->outopts); + curp->outfree = pspdf_free; + break; + default: + break; + } + + switch (curp->outtype) { + case (OUTT_HTML): + /* FALLTHROUGH */ + case (OUTT_XHTML): + curp->outman = html_man; + curp->outmdoc = html_mdoc; + curp->outfree = html_free; + break; + case (OUTT_TREE): + curp->outman = tree_man; + curp->outmdoc = tree_mdoc; + break; + case (OUTT_PDF): + /* FALLTHROUGH */ + case (OUTT_ASCII): + /* FALLTHROUGH */ + case (OUTT_PS): + curp->outman = terminal_man; + curp->outmdoc = terminal_mdoc; + break; + default: + break; + } + } + + /* Execute the out device, if it exists. */ + + if (curp->man && curp->outman) + (*curp->outman)(curp->outdata, curp->man); + if (curp->mdoc && curp->outmdoc) + (*curp->outmdoc)(curp->outdata, curp->mdoc); + + cleanup: + + memset(&curp->regs, 0, sizeof(struct regset)); + + /* Reset the current-parse compilers. */ + + if (curp->mdoc) + mdoc_reset(curp->mdoc); + if (curp->man) + man_reset(curp->man); + + assert(curp->roff); + roff_reset(curp->roff); + + if (exit_status < file_status) + exit_status = file_status; + + return; +} + +static void +pdesc(struct curparse *curp) +{ + struct buf blk; + int with_mmap; + + /* + * Run for each opened file; may be called more than once for + * each full parse sequence if the opened file is nested (i.e., + * from `so'). Simply sucks in the whole file and moves into + * the parse phase for the file. + */ + + if ( ! read_whole_file(curp, &blk, &with_mmap)) { + file_status = MANDOCLEVEL_SYSERR; + return; + } + + /* Line number is per-file. */ + + curp->line = 1; + + parsebuf(curp, blk, 1); + + if (with_mmap) + munmap(blk.buf, blk.sz); + else + free(blk.buf); +} + +static void +parsebuf(struct curparse *curp, struct buf blk, int start) +{ + struct buf ln; + enum rofferr rr; + int i, of, rc; + int pos; /* byte number in the ln buffer */ + int lnn; /* line number in the real file */ + unsigned char c; + + /* + * Main parse routine for an opened file. This is called for + * each opened file and simply loops around the full input file, + * possibly nesting (i.e., with `so'). + */ + + memset(&ln, 0, sizeof(struct buf)); + + lnn = curp->line; + pos = 0; + + for (i = 0; i < (int)blk.sz; ) { + if (0 == pos && '\0' == blk.buf[i]) + break; + + if (start) { + curp->line = lnn; + curp->reparse_count = 0; + } + + while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) { + if ('\n' == blk.buf[i]) { + ++i; + ++lnn; + break; + } + + /* + * Warn about bogus characters. If you're using + * non-ASCII encoding, you're screwing your + * readers. Since I'd rather this not happen, + * I'll be helpful and drop these characters so + * we don't display gibberish. Note to manual + * writers: use special characters. + */ + + c = (unsigned char) blk.buf[i]; + + if ( ! (isascii(c) && + (isgraph(c) || isblank(c)))) { + mmsg(MANDOCERR_BADCHAR, curp, + curp->line, pos, "ignoring byte"); + i++; + continue; + } + + /* Trailing backslash = a plain char. */ + + if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) { + if (pos >= (int)ln.sz) + resize_buf(&ln, 256); + ln.buf[pos++] = blk.buf[i++]; + continue; + } + + /* Found escape & at least one other char. */ + + if ('\n' == blk.buf[i + 1]) { + i += 2; + /* Escaped newlines are skipped over */ + ++lnn; + continue; + } + + if ('"' == blk.buf[i + 1]) { + i += 2; + /* Comment, skip to end of line */ + for (; i < (int)blk.sz; ++i) { + if ('\n' == blk.buf[i]) { + ++i; + ++lnn; + break; + } + } + + /* Backout trailing whitespaces */ + for (; pos > 0; --pos) { + if (ln.buf[pos - 1] != ' ') + break; + if (pos > 2 && ln.buf[pos - 2] == '\\') + break; + } + break; + } + + /* Some other escape sequence, copy & cont. */ + + if (pos + 1 >= (int)ln.sz) + resize_buf(&ln, 256); + + ln.buf[pos++] = blk.buf[i++]; + ln.buf[pos++] = blk.buf[i++]; + } + + if (pos >= (int)ln.sz) + resize_buf(&ln, 256); + + ln.buf[pos] = '\0'; + + /* + * A significant amount of complexity is contained by + * the roff preprocessor. It's line-oriented but can be + * expressed on one line, so we need at times to + * readjust our starting point and re-run it. The roff + * preprocessor can also readjust the buffers with new + * data, so we pass them in wholesale. + */ + + of = 0; + +rerun: + rr = roff_parseln + (curp->roff, curp->line, + &ln.buf, &ln.sz, of, &of); + + switch (rr) { + case (ROFF_REPARSE): + if (REPARSE_LIMIT >= ++curp->reparse_count) + parsebuf(curp, ln, 0); + else + mmsg(MANDOCERR_ROFFLOOP, curp, + curp->line, pos, NULL); + pos = 0; + continue; + case (ROFF_APPEND): + pos = strlen(ln.buf); + continue; + case (ROFF_RERUN): + goto rerun; + case (ROFF_IGN): + pos = 0; + continue; + case (ROFF_ERR): + assert(MANDOCLEVEL_FATAL <= file_status); + break; + case (ROFF_SO): + if (pfile(ln.buf + of, curp)) { + pos = 0; + continue; + } else + break; + default: + break; + } + + /* + * If we encounter errors in the recursive parsebuf() + * call, make sure we don't continue parsing. + */ + + if (MANDOCLEVEL_FATAL <= file_status) + break; + + /* + * If input parsers have not been allocated, do so now. + * We keep these instanced betwen parsers, but set them + * locally per parse routine since we can use different + * parsers with each one. + */ + + if ( ! (curp->man || curp->mdoc)) + pset(ln.buf + of, pos - of, curp); + + /* + * Lastly, push down into the parsers themselves. One + * of these will have already been set in the pset() + * routine. + * If libroff returns ROFF_TBL, then add it to the + * currently open parse. Since we only get here if + * there does exist data (see tbl_data.c), we're + * guaranteed that something's been allocated. + */ + + if (ROFF_TBL == rr) { + assert(curp->man || curp->mdoc); + if (curp->man) + man_addspan(curp->man, roff_span(curp->roff)); + else + mdoc_addspan(curp->mdoc, roff_span(curp->roff)); + + } else if (curp->man || curp->mdoc) { + rc = curp->man ? + man_parseln(curp->man, + curp->line, ln.buf, of) : + mdoc_parseln(curp->mdoc, + curp->line, ln.buf, of); + + if ( ! rc) { + assert(MANDOCLEVEL_FATAL <= file_status); + break; + } + } + + /* Temporary buffers typically are not full. */ + + if (0 == start && '\0' == blk.buf[i]) + break; + + /* Start the next input line. */ + + pos = 0; + } + + free(ln.buf); +} + +static void +pset(const char *buf, int pos, struct curparse *curp) +{ + int i; + + /* + * Try to intuit which kind of manual parser should be used. If + * passed in by command-line (-man, -mdoc), then use that + * explicitly. If passed as -mandoc, then try to guess from the + * line: either skip dot-lines, use -mdoc when finding `.Dt', or + * default to -man, which is more lenient. + * + * Separate out pmdoc/pman from mdoc/man: the first persists + * through all parsers, while the latter is used per-parse. + */ + + if ('.' == buf[0] || '\'' == buf[0]) { + for (i = 1; buf[i]; i++) + if (' ' != buf[i] && '\t' != buf[i]) + break; + if ('\0' == buf[i]) + return; + } + + switch (curp->inttype) { + case (INTT_MDOC): + if (NULL == curp->pmdoc) + curp->pmdoc = mdoc_alloc + (&curp->regs, curp, mmsg); + assert(curp->pmdoc); + curp->mdoc = curp->pmdoc; + return; + case (INTT_MAN): + if (NULL == curp->pman) + curp->pman = man_alloc + (&curp->regs, curp, mmsg); + assert(curp->pman); + curp->man = curp->pman; + return; + default: + break; + } + + if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) { + if (NULL == curp->pmdoc) + curp->pmdoc = mdoc_alloc + (&curp->regs, curp, mmsg); + assert(curp->pmdoc); + curp->mdoc = curp->pmdoc; + return; + } + + if (NULL == curp->pman) + curp->pman = man_alloc(&curp->regs, curp, mmsg); + assert(curp->pman); + curp->man = curp->pman; +} + +static int +moptions(enum intt *tflags, char *arg) +{ + + if (0 == strcmp(arg, "doc")) + *tflags = INTT_MDOC; + else if (0 == strcmp(arg, "andoc")) + *tflags = INTT_AUTO; + else if (0 == strcmp(arg, "an")) + *tflags = INTT_MAN; + else { + fprintf(stderr, "%s: Bad argument\n", arg); + return(0); + } + + return(1); +} + +static int +toptions(struct curparse *curp, char *arg) +{ + + if (0 == strcmp(arg, "ascii")) + curp->outtype = OUTT_ASCII; + else if (0 == strcmp(arg, "lint")) { + curp->outtype = OUTT_LINT; + curp->wlevel = MANDOCLEVEL_WARNING; + } + else if (0 == strcmp(arg, "tree")) + curp->outtype = OUTT_TREE; + else if (0 == strcmp(arg, "html")) + curp->outtype = OUTT_HTML; + else if (0 == strcmp(arg, "xhtml")) + curp->outtype = OUTT_XHTML; + else if (0 == strcmp(arg, "ps")) + curp->outtype = OUTT_PS; + else if (0 == strcmp(arg, "pdf")) + curp->outtype = OUTT_PDF; + else { + fprintf(stderr, "%s: Bad argument\n", arg); + return(0); + } + + return(1); +} + +static int +woptions(struct curparse *curp, char *arg) +{ + char *v, *o; + const char *toks[6]; + + toks[0] = "stop"; + toks[1] = "all"; + toks[2] = "warning"; + toks[3] = "error"; + toks[4] = "fatal"; + toks[5] = NULL; + + while (*arg) { + o = arg; + switch (getsubopt(&arg, UNCONST(toks), &v)) { + case (0): + curp->wstop = 1; + break; + case (1): + /* FALLTHROUGH */ + case (2): + curp->wlevel = MANDOCLEVEL_WARNING; + break; + case (3): + curp->wlevel = MANDOCLEVEL_ERROR; + break; + case (4): + curp->wlevel = MANDOCLEVEL_FATAL; + break; + default: + fprintf(stderr, "-W%s: Bad argument\n", o); + return(0); + } + } + + return(1); +} + +static int +mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg) +{ + struct curparse *cp; + enum mandoclevel level; + + level = MANDOCLEVEL_FATAL; + while (t < mandoclimits[level]) + /* LINTED */ + level--; + + cp = (struct curparse *)arg; + if (level < cp->wlevel) + return(1); + + fprintf(stderr, "%s:%d:%d: %s: %s", + cp->file, ln, col + 1, mandoclevels[level], mandocerrs[t]); + if (msg) + fprintf(stderr, ": %s", msg); + fputc('\n', stderr); + + if (file_status < level) + file_status = level; + + return(level < MANDOCLEVEL_FATAL); +} diff --git a/contrib/mdocml/main.h b/contrib/mdocml/main.h new file mode 100644 index 0000000000..bb503eb79e --- /dev/null +++ b/contrib/mdocml/main.h @@ -0,0 +1,56 @@ +/* $Id: main.h,v 1.10 2010/07/31 23:52:58 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. + */ +#ifndef MAIN_H +#define MAIN_H + +__BEGIN_DECLS + +struct mdoc; +struct man; + +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) + + +/* + * Definitions for main.c-visible output device functions, e.g., -Thtml + * and -Tascii. Note that ascii_alloc() is named as such in + * anticipation of latin1_alloc() and so on, all of which map into the + * terminal output routines with different character settings. + */ + +void *html_alloc(char *); +void *xhtml_alloc(char *); +void html_mdoc(void *, const struct mdoc *); +void html_man(void *, const struct man *); +void html_free(void *); + +void tree_mdoc(void *, const struct mdoc *); +void tree_man(void *, const struct man *); + +void *ascii_alloc(char *); +void ascii_free(void *); + +void *pdf_alloc(char *); +void *ps_alloc(char *); +void pspdf_free(void *); + +void terminal_mdoc(void *, const struct mdoc *); +void terminal_man(void *, const struct man *); + +__END_DECLS + +#endif /*!MAIN_H*/ diff --git a/contrib/mdocml/man.3 b/contrib/mdocml/man.3 new file mode 100644 index 0000000000..2b2d0a910e --- /dev/null +++ b/contrib/mdocml/man.3 @@ -0,0 +1,272 @@ +.\" $Id: man.3,v 1.29 2011/01/03 11:31:26 kristaps 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. +.\" +.Dd $Mdocdate: January 3 2011 $ +.Dt MAN 3 +.Os +.Sh NAME +.Nm man , +.Nm man_alloc , +.Nm man_endparse , +.Nm man_free , +.Nm man_meta , +.Nm man_node , +.Nm man_parseln , +.Nm man_reset +.Nd man macro compiler library +.Sh SYNOPSIS +.In mandoc.h +.In man.h +.Vt extern const char * const * man_macronames; +.Ft int +.Fo man_addspan +.Fa "struct man *man" +.Fa "const struct tbl_span *span" +.Fc +.Ft "struct man *" +.Fo man_alloc +.Fa "struct regset *regs" +.Fa "void *data" +.Fa "mandocmsg msgs" +.Fc +.Ft int +.Fn man_endparse "struct man *man" +.Ft void +.Fn man_free "struct man *man" +.Ft "const struct man_meta *" +.Fn man_meta "const struct man *man" +.Ft "const struct man_node *" +.Fn man_node "const struct man *man" +.Ft int +.Fo man_parseln +.Fa "struct man *man" +.Fa "int line" +.Fa "char *buf" +.Fc +.Ft void +.Fn man_reset "struct man *man" +.Sh DESCRIPTION +The +.Nm +library parses lines of +.Xr man 7 +input into an abstract syntax tree (AST). +.Pp +In general, applications initiate a parsing sequence with +.Fn man_alloc , +parse each line in a document with +.Fn man_parseln , +close the parsing session with +.Fn man_endparse , +operate over the syntax tree returned by +.Fn man_node +and +.Fn man_meta , +then free all allocated memory with +.Fn man_free . +The +.Fn man_reset +function may be used in order to reset the parser for another input +sequence. +.Pp +Beyond the full set of macros defined in +.Xr man 7 , +the +.Nm +library also accepts the following macro: +.Pp +.Bl -tag -width Ds -compact +.It PD +Has no effect. +Handled as a current-scope line macro. +.El +.Ss Types +.Bl -ohang +.It Vt struct man +An opaque type. +Its values are only used privately within the library. +.It Vt struct man_node +A parsed node. +See +.Sx Abstract Syntax Tree +for details. +.El +.Ss Functions +If +.Fn man_addspan , +.Fn man_parseln , +or +.Fn man_endparse +return 0, calls to any function but +.Fn man_reset +or +.Fn man_free +will raise an assertion. +.Bl -ohang +.It Fn man_addspan +Add a table span to the parsing stream. +Returns 0 on failure, 1 on success. +.It Fn man_alloc +Allocates a parsing structure. +The +.Fa data +pointer is passed to +.Fa msgs . +Always returns a valid pointer. +The pointer must be freed with +.Fn man_free . +.It Fn man_reset +Reset the parser for another parse routine. +After its use, +.Fn man_parseln +behaves as if invoked for the first time. +.It Fn man_free +Free all resources of a parser. +The pointer is no longer valid after invocation. +.It Fn man_parseln +Parse a nil-terminated line of input. +This line should not contain the trailing newline. +Returns 0 on failure, 1 on success. +The input buffer +.Fa buf +is modified by this function. +.It Fn man_endparse +Signals that the parse is complete. +Returns 0 on failure, 1 on success. +.It Fn man_node +Returns the first node of the parse. +.It Fn man_meta +Returns the document's parsed meta-data. +.El +.Ss Variables +The following variables are also defined: +.Bl -ohang +.It Va man_macronames +An array of string-ified token names. +.El +.Ss Abstract Syntax Tree +The +.Nm +functions produce an abstract syntax tree (AST) describing input in a +regular form. +It may be reviewed at any time with +.Fn man_nodes ; +however, if called before +.Fn man_endparse , +or after +.Fn man_endparse +or +.Fn man_parseln +fail, it may be incomplete. +.Pp +This AST is governed by the ontological rules dictated in +.Xr man 7 +and derives its terminology accordingly. +.Pp +The AST is composed of +.Vt struct man_node +nodes with element, root and text types as declared by the +.Va type +field. +Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va next +and +.Va prev +fields) and some type-specific data. +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact +.It ROOT +\(<- mnode+ +.It mnode +\(<- ELEMENT | TEXT | BLOCK +.It BLOCK +\(<- HEAD BODY +.It HEAD +\(<- mnode* +.It BODY +\(<- mnode* +.It ELEMENT +\(<- ELEMENT | TEXT* +.It TEXT +\(<- [[:alpha:]]* +.El +.Pp +The only elements capable of nesting other elements are those with +next-lint scope as documented in +.Xr man 7 . +.Sh EXAMPLES +The following example reads lines from stdin and parses them, operating +on the finished parse tree with +.Fn parsed . +This example does not error-check nor free memory upon failure. +.Bd -literal -offset indent +struct regset regs; +struct man *man; +struct man_node *node; +char *buf; +size_t len; +int line; + +bzero(®s, sizeof(struct regset)); +line = 1; +man = man_alloc(®s, NULL, NULL); +buf = NULL; +alloc_len = 0; + +while ((len = getline(&buf, &alloc_len, stdin)) >= 0) { + if (len && buflen[len - 1] = '\en') + buf[len - 1] = '\e0'; + if ( ! man_parseln(man, line, buf)) + errx(1, "man_parseln"); + line++; +} + +free(buf); + +if ( ! man_endparse(man)) + errx(1, "man_endparse"); +if (NULL == (node = man_node(man))) + errx(1, "man_node"); + +parsed(man, node); +man_free(man); +.Ed +.Pp +To compile this, execute +.Pp +.Dl % cc main.c libman.a libmandoc.a +.Pp +where +.Pa main.c +is the example file. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 +.Sh AUTHORS +The +.Nm +library was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . diff --git a/contrib/mdocml/man.7 b/contrib/mdocml/man.7 new file mode 100644 index 0000000000..d8bd6ee7ae --- /dev/null +++ b/contrib/mdocml/man.7 @@ -0,0 +1,939 @@ +.\" $Id: man.7,v 1.94 2011/01/04 23:32:21 kristaps 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. +.\" +.Dd $Mdocdate: January 4 2011 $ +.Dt MAN 7 +.Os +.Sh NAME +.Nm man +.Nd man language reference +.Sh DESCRIPTION +The +.Nm man +language was historically used to format +.Ux +manuals. +This reference document describes its syntax, structure, and usage. +.Pp +.Bf -emphasis +Do not use +.Nm +to write your manuals. +.Ef +Use the +.Xr mdoc 7 +language, instead. +.Pp +A +.Nm +document follows simple rules: lines beginning with the control +character +.Sq \&. +are parsed for macros. +Other lines are interpreted within the scope of +prior macros: +.Bd -literal -offset indent +\&.SH Macro lines change control state. +Other lines are interpreted within the current state. +.Ed +.Sh INPUT ENCODING +.Nm +documents may contain only graphable 7-bit ASCII characters, the +space character, and the tab character. +All manuals must have +.Ux +line termination. +.Pp +Blank lines are acceptable; where found, the output will assert a +vertical space. +.Ss Comments +Text following a +.Sq \e\*q , +whether in a macro or free-form text line, is ignored to the end of +line. +A macro line with only a control character and comment escape, +.Sq \&.\e\*q , +is also ignored. +Macro lines with only a control character and optionally whitespace are +stripped from input. +.Ss Special Characters +Special characters may occur in both macro and free-form lines. +Sequences begin with the escape character +.Sq \e +followed by either an open-parenthesis +.Sq \&( +for two-character sequences; an open-bracket +.Sq \&[ +for n-character sequences (terminated at a close-bracket +.Sq \&] ) ; +or a single one-character sequence. +See +.Xr mandoc_char 7 +for a complete list. +Examples include +.Sq \e(em +.Pq em-dash +and +.Sq \ee +.Pq back-slash . +.Ss Text Decoration +Terms may be text-decorated using the +.Sq \ef +escape followed by an indicator: B (bold), I (italic), R (Roman), or P +(revert to previous mode): +.Pp +.D1 \efBbold\efR \efIitalic\efP +.Pp +A numerical representation 3, 2, or 1 (bold, italic, and Roman, +respectively) may be used instead. +A text decoration is only valid, if specified in free-form text, until +the next macro invocation; if specified within a macro, it's only valid +until the macro closes scope. +Note that macros like +.Sx \&BR +open and close a font scope with each argument. +.Pp +The +.Sq \ef +attribute is forgotten when entering or exiting a macro block. +.Ss Whitespace +Whitespace consists of the space character. +In free-form lines, whitespace is preserved within a line; unescaped +trailing spaces are stripped from input (unless in a literal context). +Blank free-form lines, which may include spaces, are permitted and +rendered as an empty line. +.Pp +In macro lines, whitespace delimits arguments and is discarded. +If arguments are quoted, whitespace within the quotes is retained. +.Ss Dates +The +.Sx \&TH +macro is the only +.Nm +macro that requires a date. +The form for this date is the ISO-8601 +standard +.Cm YYYY-MM-DD . +.Ss Scaling Widths +Many macros support scaled widths for their arguments, such as +stipulating a two-inch paragraph indentation with the following: +.Bd -literal -offset indent +\&.HP 2i +.Ed +.Pp +The syntax for scaled widths is +.Sq Li [+-]?[0-9]*.[0-9]*[:unit:]? , +where a decimal must be preceded or proceeded by at least one digit. +Negative numbers, while accepted, are truncated to zero. +The following scaling units are accepted: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It c +centimetre +.It i +inch +.It P +pica (~1/6 inch) +.It p +point (~1/72 inch) +.It f +synonym for +.Sq u +.It v +default vertical span +.It m +width of rendered +.Sq m +.Pq em +character +.It n +width of rendered +.Sq n +.Pq en +character +.It u +default horizontal span +.It M +mini-em (~1/100 em) +.El +.Pp +Using anything other than +.Sq m , +.Sq n , +.Sq u , +or +.Sq v +is necessarily non-portable across output media. +.Pp +If a scaling unit is not provided, the numerical value is interpreted +under the default rules of +.Sq v +for vertical spaces and +.Sq u +for horizontal ones. +.Em Note : +this differs from +.Xr mdoc 7 , +which, if a unit is not provided, will instead interpret the string as +literal text. +.Ss Sentence Spacing +When composing a manual, make sure that sentences end at the end of +a line. +By doing so, front-ends will be able to apply the proper amount of +spacing after the end of sentence (unescaped) period, exclamation mark, +or question mark followed by zero or more non-sentence closing +delimiters +.Po +.Sq \&) , +.Sq \&] , +.Sq \&' , +.Sq \&" +.Pc . +.Sh MANUAL STRUCTURE +Each +.Nm +document must contain the +.Sx \&TH +macro describing the document's section and title. +It may occur anywhere in the document, although conventionally it +appears as the first macro. +.Pp +Beyond +.Sx \&TH , +at least one macro or text node must appear in the document. +Documents are generally structured as follows: +.Bd -literal -offset indent +\&.TH FOO 1 2009-10-10 +\&.SH NAME +\efBfoo\efR \e(en a description goes here +\&.\e\*q .SH LIBRARY +\&.\e\*q For sections 2 & 3 only. +\&.\e\*q Not used in OpenBSD. +\&.SH SYNOPSIS +\efBfoo\efR [\efB\e-options\efR] arguments... +\&.SH DESCRIPTION +The \efBfoo\efR utility processes files... +\&.\e\*q .SH IMPLEMENTATION NOTES +\&.\e\*q Not used in OpenBSD. +\&.\e\*q .SH RETURN VALUES +\&.\e\*q For sections 2, 3, & 9 only. +\&.\e\*q .SH ENVIRONMENT +\&.\e\*q For sections 1, 6, 7, & 8 only. +\&.\e\*q .SH FILES +\&.\e\*q .SH EXIT STATUS +\&.\e\*q For sections 1, 6, & 8 only. +\&.\e\*q .SH EXAMPLES +\&.\e\*q .SH DIAGNOSTICS +\&.\e\*q For sections 1, 4, 6, 7, & 8 only. +\&.\e\*q .SH ERRORS +\&.\e\*q For sections 2, 3, & 9 only. +\&.\e\*q .SH SEE ALSO +\&.\e\*q .BR foo ( 1 ) +\&.\e\*q .SH STANDARDS +\&.\e\*q .SH HISTORY +\&.\e\*q .SH AUTHORS +\&.\e\*q .SH CAVEATS +\&.\e\*q .SH BUGS +\&.\e\*q .SH SECURITY CONSIDERATIONS +\&.\e\*q Not used in OpenBSD. +.Ed +.Pp +The sections in a +.Nm +document are conventionally ordered as they appear above. +Sections should be composed as follows: +.Bl -ohang -offset indent +.It Em NAME +The name(s) and a short description of the documented material. +The syntax for this is generally as follows: +.Pp +.D1 \efBname\efR \e(en description +.It Em LIBRARY +The name of the library containing the documented material, which is +assumed to be a function in a section 2 or 3 manual. +For functions in the C library, this may be as follows: +.Pp +.D1 Standard C Library (libc, -lc) +.It Em SYNOPSIS +Documents the utility invocation syntax, function call syntax, or device +configuration. +.Pp +For the first, utilities (sections 1, 6, and 8), this is +generally structured as follows: +.Pp +.D1 \efBname\efR [-\efBab\efR] [-\efBc\efR\efIarg\efR] \efBpath\efR... +.Pp +For the second, function calls (sections 2, 3, 9): +.Pp +.D1 \&.B char *name(char *\efIarg\efR); +.Pp +And for the third, configurations (section 4): +.Pp +.D1 \&.B name* at cardbus ? function ? +.Pp +Manuals not in these sections generally don't need a +.Em SYNOPSIS . +.It Em DESCRIPTION +This expands upon the brief, one-line description in +.Em NAME . +It usually contains a break-down of the options (if documenting a +command). +.It Em IMPLEMENTATION NOTES +Implementation-specific notes should be kept here. +This is useful when implementing standard functions that may have side +effects or notable algorithmic implications. +.It Em RETURN VALUES +This section documents the return values of functions in sections 2, 3, and 9. +.It Em ENVIRONMENT +Documents any usages of environment variables, e.g., +.Xr environ 7 . +.It Em FILES +Documents files used. +It's helpful to document both the file name and a short description of how +the file is used (created, modified, etc.). +.It Em EXIT STATUS +This section documents the command exit status for +section 1, 6, and 8 utilities. +Historically, this information was described in +.Em DIAGNOSTICS , +a practise that is now discouraged. +.It Em EXAMPLES +Example usages. +This often contains snippets of well-formed, +well-tested invocations. +Make sure that examples work properly! +.It Em DIAGNOSTICS +Documents error conditions. +This is most useful in section 4 manuals. +Historically, this section was used in place of +.Em EXIT STATUS +for manuals in sections 1, 6, and 8; however, this practise is +discouraged. +.It Em ERRORS +Documents error handling in sections 2, 3, and 9. +.It Em SEE ALSO +References other manuals with related topics. +This section should exist for most manuals. +.Pp +.D1 \&.BR bar \&( 1 \&), +.Pp +Cross-references should conventionally be ordered +first by section, then alphabetically. +.It Em STANDARDS +References any standards implemented or used, such as +.Pp +.D1 IEEE Std 1003.2 (\e(lqPOSIX.2\e(rq) +.Pp +If not adhering to any standards, the +.Em HISTORY +section should be used. +.It Em HISTORY +A brief history of the subject, including where support first appeared. +.It Em AUTHORS +Credits to the person or persons who wrote the code and/or documentation. +Authors should generally be noted by both name and email address. +.It Em CAVEATS +Common misuses and misunderstandings should be explained +in this section. +.It Em BUGS +Known bugs, limitations, and work-arounds should be described +in this section. +.It Em SECURITY CONSIDERATIONS +Documents any security precautions that operators should consider. +.El +.Sh MACRO SYNTAX +Macros are one to three characters in length and begin with a +control character, +.Sq \&. , +at the beginning of the line. +The +.Sq \(aq +macro control character is also accepted. +An arbitrary amount of whitespace (spaces or tabs) may sit between the +control character and the macro name. +Thus, the following are equivalent: +.Bd -literal -offset indent +\&.PP +\&.\ \ \ PP +.Ed +.Pp +The +.Nm +macros are classified by scope: line scope or block scope. +Line macros are only scoped to the current line (and, in some +situations, the subsequent line). +Block macros are scoped to the current line and subsequent lines until +closed by another block macro. +.Ss Line Macros +Line macros are generally scoped to the current line, with the body +consisting of zero or more arguments. +If a macro is scoped to the next line and the line arguments are empty, +the next line, which must be text, is used instead. +Thus: +.Bd -literal -offset indent +\&.I +foo +.Ed +.Pp +is equivalent to +.Sq \&.I foo . +If next-line macros are invoked consecutively, only the last is used. +If a next-line macro is followed by a non-next-line macro, an error is +raised, except for +.Sx \&br , +.Sx \&sp , +and +.Sx \&na . +.Pp +The syntax is as follows: +.Bd -literal -offset indent +\&.YO \(lBbody...\(rB +\(lBbody...\(rB +.Ed +.Pp +.Bl -column -compact -offset indent "MacroX" "ArgumentsX" "ScopeXXXXX" "CompatX" +.It Em Macro Ta Em Arguments Ta Em Scope Ta Em Notes +.It Sx \&AT Ta <=1 Ta current Ta \& +.It Sx \&B Ta n Ta next-line Ta \& +.It Sx \&BI Ta n Ta current Ta \& +.It Sx \&BR Ta n Ta current Ta \& +.It Sx \&DT Ta 0 Ta current Ta \& +.It Sx \&I Ta n Ta next-line Ta \& +.It Sx \&IB Ta n Ta current Ta \& +.It Sx \&IR Ta n Ta current Ta \& +.It Sx \&R Ta n Ta next-line Ta \& +.It Sx \&RB Ta n Ta current Ta \& +.It Sx \&RI Ta n Ta current Ta \& +.It Sx \&SB Ta n Ta next-line Ta \& +.It Sx \&SM Ta n Ta next-line Ta \& +.It Sx \&TH Ta >1, <6 Ta current Ta \& +.It Sx \&UC Ta <=1 Ta current Ta \& +.It Sx \&br Ta 0 Ta current Ta compat +.It Sx \&fi Ta 0 Ta current Ta compat +.It Sx \&ft Ta 1 Ta current Ta compat +.It Sx \&in Ta 1 Ta current Ta compat +.It Sx \&na Ta 0 Ta current Ta compat +.It Sx \&nf Ta 0 Ta current Ta compat +.It Sx \&sp Ta 1 Ta current Ta compat +.El +.Pp +Macros marked as +.Qq compat +are included for compatibility with the significant corpus of existing +manuals that mix dialects of roff. +These macros should not be used for portable +.Nm +manuals. +.Ss Block Macros +Block macros comprise a head and body. +As with in-line macros, the head is scoped to the current line and, in +one circumstance, the next line (the next-line stipulations as in +.Sx Line Macros +apply here as well). +.Pp +The syntax is as follows: +.Bd -literal -offset indent +\&.YO \(lBhead...\(rB +\(lBhead...\(rB +\(lBbody...\(rB +.Ed +.Pp +The closure of body scope may be to the section, where a macro is closed +by +.Sx \&SH ; +sub-section, closed by a section or +.Sx \&SS ; +part, closed by a section, sub-section, or +.Sx \&RE ; +or paragraph, closed by a section, sub-section, part, +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +.Sx \&PP , +or +.Sx \&TP . +No closure refers to an explicit block closing macro. +.Pp +As a rule, block macros may not be nested; thus, calling a block macro +while another block macro scope is open, and the open scope is not +implicitly closed, is syntactically incorrect. +.Pp +.Bl -column -compact -offset indent "MacroX" "ArgumentsX" "Head ScopeX" "sub-sectionX" "compatX" +.It Em Macro Ta Em Arguments Ta Em Head Scope Ta Em Body Scope Ta Em Notes +.It Sx \&HP Ta <2 Ta current Ta paragraph Ta \& +.It Sx \&IP Ta <3 Ta current Ta paragraph Ta \& +.It Sx \&LP Ta 0 Ta current Ta paragraph Ta \& +.It Sx \&P Ta 0 Ta current Ta paragraph Ta \& +.It Sx \&PP Ta 0 Ta current Ta paragraph Ta \& +.It Sx \&RE Ta 0 Ta current Ta none Ta compat +.It Sx \&RS Ta 1 Ta current Ta part Ta compat +.It Sx \&SH Ta >0 Ta next-line Ta section Ta \& +.It Sx \&SS Ta >0 Ta next-line Ta sub-section Ta \& +.It Sx \&TP Ta n Ta next-line Ta paragraph Ta \& +.El +.Pp +Macros marked +.Qq compat +are as mentioned in +.Sx Line Macros . +.Pp +If a block macro is next-line scoped, it may only be followed by in-line +macros for decorating text. +.Sh REFERENCE +This section is a canonical reference to all macros, arranged +alphabetically. +For the scoping of individual macros, see +.Sx MACRO SYNTAX . +.Ss \&AT +Sets the volume for the footer for compatibility with man pages from +.Tn AT&T UNIX +releases. +The optional arguments specify which release it is from. +.Ss \&B +Text is rendered in bold face. +.Pp +See also +.Sx \&I +and +.Sx \&R . +.Ss \&BI +Text is rendered alternately in bold face and italic. +Thus, +.Sq .BI this word and that +causes +.Sq this +and +.Sq and +to render in bold face, while +.Sq word +and +.Sq that +render in italics. +Whitespace between arguments is omitted in output. +.Pp +Examples: +.Pp +.Dl \&.BI bold italic bold italic +.Pp +The output of this example will be emboldened +.Dq bold +and italicised +.Dq italic , +with spaces stripped between arguments. +.Pp +See also +.Sx \&IB , +.Sx \&BR , +.Sx \&RB , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&BR +Text is rendered alternately in bold face and roman (the default font). +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&RB , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&DT +Has no effect. +Included for compatibility. +.Ss \&HP +Begin a paragraph whose initial output line is left-justified, but +subsequent output lines are indented, with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&HP +.Op Cm width +.Ed +.Pp +The +.Cm width +argument must conform to +.Sx Scaling Widths . +If specified, it's saved for later paragraph left-margins; if unspecified, the +saved or default width is used. +.Pp +See also +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&I +Text is rendered in italics. +.Pp +See also +.Sx \&B +and +.Sx \&R . +.Ss \&IB +Text is rendered alternately in italics and bold face. +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&BR , +.Sx \&RB , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&IP +Begin an indented paragraph with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&IP +.Op Cm head Op Cm width +.Ed +.Pp +The +.Cm width +argument defines the width of the left margin and is defined by +.Sx Scaling Widths . +It's saved for later paragraph left-margins; if unspecified, the saved or +default width is used. +.Pp +The +.Cm head +argument is used as a leading term, flushed to the left margin. +This is useful for bulleted paragraphs and so on. +.Pp +See also +.Sx \&HP , +.Sx \&LP , +.Sx \&P , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&IR +Text is rendered alternately in italics and roman (the default font). +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&BR , +.Sx \&RB , +and +.Sx \&RI . +.Ss \&LP +Begin an undecorated paragraph. +The scope of a paragraph is closed by a subsequent paragraph, +sub-section, section, or end of file. +The saved paragraph left-margin width is reset to the default. +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&P , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&P +Synonym for +.Sx \&LP . +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&PP +Synonym for +.Sx \&LP . +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +and +.Sx \&TP . +.Ss \&R +Text is rendered in roman (the default font). +.Pp +See also +.Sx \&I +and +.Sx \&B . +.Ss \&RB +Text is rendered alternately in roman (the default font) and bold face. +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&BR , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&RE +Explicitly close out the scope of a prior +.Sx \&RS . +.Ss \&RI +Text is rendered alternately in roman (the default font) and italics. +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&BR , +.Sx \&RB , +and +.Sx \&IR . +.Ss \&RS +Begin a part setting the left margin. +The left margin controls the offset, following an initial indentation, +to un-indented text such as that of +.Sx \&PP . +This has the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&Rs +.Op Cm width +.Ed +.Pp +The +.Cm width +argument must conform to +.Sx Scaling Widths . +If not specified, the saved or default width is used. +.Ss \&SB +Text is rendered in small size (one point smaller than the default font) +bold face. +.Ss \&SH +Begin a section. +The scope of a section is only closed by another section or the end of +file. +The paragraph left-margin width is reset to the default. +.Ss \&SM +Text is rendered in small size (one point smaller than the default +font). +.Ss \&SS +Begin a sub-section. +The scope of a sub-section is closed by a subsequent sub-section, +section, or end of file. +The paragraph left-margin width is reset to the default. +.Ss \&TH +Sets the title of the manual page with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&TH +.Cm title section +.Op Cm date Op Cm source Op Cm volume +.Ed +.Pp +At least the upper-case document +.Cm title +and the manual +.Cm section +arguments must be provided. +The +.Cm date +argument should be formatted as described in +.Sx Dates , +but will be printed verbatim if it is not. +If the date is not specified, the current date is used. +The +.Cm source +string specifies the organisation providing the utility. +The +.Cm volume +string replaces the default rendered volume, which is dictated by the +manual section. +.Pp +Examples: +.Pp +.Dl \&.TH CVS 5 "1992-02-12" GNU +.Ss \&TP +Begin a paragraph where the head, if exceeding the indentation width, is +followed by a newline; if not, the body follows on the same line after a +buffer to the indentation width. +Subsequent output lines are indented. +The syntax is as follows: +.Bd -filled -offset indent +.Pf \. Sx \&TP +.Op Cm width +.Ed +.Pp +The +.Cm width +argument must conform to +.Sx Scaling Widths . +If specified, it's saved for later paragraph left-margins; if +unspecified, the saved or default width is used. +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +and +.Sx \&PP . +.Ss \&UC +Sets the volume for the footer for compatibility with man pages from +BSD releases. +The optional first argument specifies which release it is from. +.Ss \&br +Breaks the current line. +Consecutive invocations have no further effect. +.Pp +See also +.Sx \&sp . +.Ss \&fi +End literal mode begun by +.Sx \&nf . +.Ss \&ft +Change the current font mode. +See +.Sx Text Decoration +for a listing of available font modes. +.Ss \&in +Indent relative to the current indentation: +.Pp +.D1 Pf \. Sx \&in Op Cm width +.Pp +If +.Cm width +is signed, the new offset is relative. +Otherwise, it is absolute. +This value is reset upon the next paragraph, section, or sub-section. +.Ss \&na +Don't align to the right margin. +.Ss \&nf +Begin literal mode: all subsequent free-form lines have their end of +line boundaries preserved. +May be ended by +.Sx \&fi . +.Ss \&sp +Insert vertical spaces into output with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&sp +.Op Cm height +.Ed +.Pp +Insert +.Cm height +spaces, which must conform to +.Sx Scaling Widths . +If 0, this is equivalent to the +.Sx \&br +macro. +Defaults to 1, if unspecified. +.Pp +See also +.Sx \&br . +.Sh COMPATIBILITY +This section documents areas of questionable portability between +implementations of the +.Nm +language. +.Pp +.Bl -dash -compact +.It +In quoted literals, GNU troff allowed pair-wise double-quotes to produce +a standalone double-quote in formatted output. +It is not known whether this behaviour is exhibited by other formatters. +.It +troff suppresses a newline before +.Sq \(aq +macro output; in mandoc, it is an alias for the standard +.Sq \&. +control character. +.It +The +.Sq \eh +.Pq horizontal position , +.Sq \ev +.Pq vertical position , +.Sq \em +.Pq text colour , +.Sq \eM +.Pq text filling colour , +.Sq \ez +.Pq zero-length character , +.Sq \ew +.Pq string length , +.Sq \ek +.Pq horizontal position marker , +.Sq \eo +.Pq text overstrike , +and +.Sq \es +.Pq text size +escape sequences are all discarded in mandoc. +.It +The +.Sq \ef +scaling unit is accepted by mandoc, but rendered as the default unit. +.It +The +.Sx \&sp +macro does not accept negative values in mandoc. +In GNU troff, this would result in strange behaviour. +.El +.Sh SEE ALSO +.Xr man 1 , +.Xr mandoc 1 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 , +.Xr tbl 7 +.Sh HISTORY +The +.Nm +language first appeared as a macro package for the roff typesetting +system in +.At v7 . +It was later rewritten by James Clark as a macro package for groff. +The stand-alone implementation that is part of the +.Xr mandoc 1 +utility written by Kristaps Dzonsons appeared in +.Ox 4.6 . +.Sh AUTHORS +This +.Nm +reference was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . +.Sh CAVEATS +Do not use this language. +Use +.Xr mdoc 7 , +instead. diff --git a/contrib/mdocml/man.c b/contrib/mdocml/man.c new file mode 100644 index 0000000000..6788c92399 --- /dev/null +++ b/contrib/mdocml/man.c @@ -0,0 +1,666 @@ +/* $Id: man.c,v 1.96 2011/01/03 11:31:26 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libman.h" +#include "libmandoc.h" + +const char *const __man_macronames[MAN_MAX] = { + "br", "TH", "SH", "SS", + "TP", "LP", "PP", "P", + "IP", "HP", "SM", "SB", + "BI", "IB", "BR", "RB", + "R", "B", "I", "IR", + "RI", "na", "sp", "nf", + "fi", "RE", "RS", "DT", + "UC", "PD", "AT", "in", + "ft" + }; + +const char * const *man_macronames = __man_macronames; + +static struct man_node *man_node_alloc(int, int, + enum man_type, enum mant); +static int man_node_append(struct man *, + struct man_node *); +static int man_span_alloc(struct man *, + const struct tbl_span *); +static void man_node_free(struct man_node *); +static void man_node_unlink(struct man *, + struct man_node *); +static int man_ptext(struct man *, int, char *, int); +static int man_pmacro(struct man *, int, char *, int); +static void man_free1(struct man *); +static void man_alloc1(struct man *); +static int man_descope(struct man *, int, int); + + +const struct man_node * +man_node(const struct man *m) +{ + + assert( ! (MAN_HALT & m->flags)); + return(m->first); +} + + +const struct man_meta * +man_meta(const struct man *m) +{ + + assert( ! (MAN_HALT & m->flags)); + return(&m->meta); +} + + +void +man_reset(struct man *man) +{ + + man_free1(man); + man_alloc1(man); +} + + +void +man_free(struct man *man) +{ + + man_free1(man); + free(man); +} + + +struct man * +man_alloc(struct regset *regs, void *data, mandocmsg msg) +{ + struct man *p; + + p = mandoc_calloc(1, sizeof(struct man)); + + man_hash_init(); + p->data = data; + p->msg = msg; + p->regs = regs; + + man_alloc1(p); + return(p); +} + + +int +man_endparse(struct man *m) +{ + + assert( ! (MAN_HALT & m->flags)); + if (man_macroend(m)) + return(1); + m->flags |= MAN_HALT; + return(0); +} + + +int +man_parseln(struct man *m, int ln, char *buf, int offs) +{ + + assert( ! (MAN_HALT & m->flags)); + return(('.' == buf[offs] || '\'' == buf[offs]) ? + man_pmacro(m, ln, buf, offs) : + man_ptext(m, ln, buf, offs)); +} + + +static void +man_free1(struct man *man) +{ + + if (man->first) + man_node_delete(man, man->first); + if (man->meta.title) + free(man->meta.title); + if (man->meta.source) + free(man->meta.source); + if (man->meta.rawdate) + free(man->meta.rawdate); + if (man->meta.vol) + free(man->meta.vol); + if (man->meta.msec) + free(man->meta.msec); +} + + +static void +man_alloc1(struct man *m) +{ + + memset(&m->meta, 0, sizeof(struct man_meta)); + m->flags = 0; + m->last = mandoc_calloc(1, sizeof(struct man_node)); + m->first = m->last; + m->last->type = MAN_ROOT; + m->last->tok = MAN_MAX; + m->next = MAN_NEXT_CHILD; +} + + +static int +man_node_append(struct man *man, struct man_node *p) +{ + + assert(man->last); + assert(man->first); + assert(MAN_ROOT != p->type); + + switch (man->next) { + case (MAN_NEXT_SIBLING): + man->last->next = p; + p->prev = man->last; + p->parent = man->last->parent; + break; + case (MAN_NEXT_CHILD): + man->last->child = p; + p->parent = man->last; + break; + default: + abort(); + /* NOTREACHED */ + } + + assert(p->parent); + p->parent->nchild++; + + if ( ! man_valid_pre(man, p)) + return(0); + + switch (p->type) { + case (MAN_HEAD): + assert(MAN_BLOCK == p->parent->type); + p->parent->head = p; + break; + case (MAN_BODY): + assert(MAN_BLOCK == p->parent->type); + p->parent->body = p; + break; + default: + break; + } + + man->last = p; + + switch (p->type) { + case (MAN_TBL): + /* FALLTHROUGH */ + case (MAN_TEXT): + if ( ! man_valid_post(man)) + return(0); + break; + default: + break; + } + + return(1); +} + + +static struct man_node * +man_node_alloc(int line, int pos, enum man_type type, enum mant tok) +{ + struct man_node *p; + + p = mandoc_calloc(1, sizeof(struct man_node)); + p->line = line; + p->pos = pos; + p->type = type; + p->tok = tok; + return(p); +} + + +int +man_elem_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_ELEM, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_head_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_HEAD, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_body_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_BODY, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_block_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(line, pos, MAN_BLOCK, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + +static int +man_span_alloc(struct man *m, const struct tbl_span *span) +{ + struct man_node *n; + + /* FIXME: grab from span */ + n = man_node_alloc(0, 0, MAN_TBL, MAN_MAX); + n->span = span; + + if ( ! man_node_append(m, n)) + return(0); + + m->next = MAN_NEXT_SIBLING; + return(1); +} + +int +man_word_alloc(struct man *m, int line, int pos, const char *word) +{ + struct man_node *n; + size_t sv, len; + + len = strlen(word); + + n = man_node_alloc(line, pos, MAN_TEXT, MAN_MAX); + n->string = mandoc_malloc(len + 1); + sv = strlcpy(n->string, word, len + 1); + + /* Prohibit truncation. */ + assert(sv < len + 1); + + if ( ! man_node_append(m, n)) + return(0); + + m->next = MAN_NEXT_SIBLING; + return(1); +} + + +/* + * Free all of the resources held by a node. This does NOT unlink a + * node from its context; for that, see man_node_unlink(). + */ +static void +man_node_free(struct man_node *p) +{ + + if (p->string) + free(p->string); + free(p); +} + + +void +man_node_delete(struct man *m, struct man_node *p) +{ + + while (p->child) + man_node_delete(m, p->child); + + man_node_unlink(m, p); + man_node_free(p); +} + + +int +man_addspan(struct man *m, const struct tbl_span *sp) +{ + + assert( ! (MAN_HALT & m->flags)); + if ( ! man_span_alloc(m, sp)) + return(0); + return(man_descope(m, 0, 0)); +} + +static int +man_descope(struct man *m, int line, int offs) +{ + /* + * Co-ordinate what happens with having a next-line scope open: + * first close out the element scope (if applicable), then close + * out the block scope (also if applicable). + */ + + if (MAN_ELINE & m->flags) { + m->flags &= ~MAN_ELINE; + if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX)) + return(0); + } + + if ( ! (MAN_BLINE & m->flags)) + return(1); + m->flags &= ~MAN_BLINE; + + if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX)) + return(0); + return(man_body_alloc(m, line, offs, m->last->tok)); +} + + +static int +man_ptext(struct man *m, int line, char *buf, int offs) +{ + int i; + + /* Ignore bogus comments. */ + + if ('\\' == buf[offs] && + '.' == buf[offs + 1] && + '"' == buf[offs + 2]) { + man_pmsg(m, line, offs, MANDOCERR_BADCOMMENT); + return(1); + } + + /* Literal free-form text whitespace is preserved. */ + + if (MAN_LITERAL & m->flags) { + if ( ! man_word_alloc(m, line, offs, buf + offs)) + return(0); + return(man_descope(m, line, offs)); + } + + /* Pump blank lines directly into the backend. */ + + for (i = offs; ' ' == buf[i]; i++) + /* Skip leading whitespace. */ ; + + if ('\0' == buf[i]) { + /* Allocate a blank entry. */ + if ( ! man_word_alloc(m, line, offs, "")) + return(0); + return(man_descope(m, line, offs)); + } + + /* + * 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]) + man_pmsg(m, line, i - 1, MANDOCERR_EOLNSPACE); + + for (--i; i && ' ' == buf[i]; i--) + /* Spin back to non-space. */ ; + + /* Jump ahead of escaped whitespace. */ + i += '\\' == buf[i] ? 2 : 1; + + buf[i] = '\0'; + } + + if ( ! man_word_alloc(m, line, offs, buf + offs)) + return(0); + + /* + * End-of-sentence check. If the last character is an unescaped + * EOS character, then flag the node as being the end of a + * sentence. The front-end will know how to interpret this. + */ + + assert(i); + if (mandoc_eos(buf, (size_t)i, 0)) + m->last->flags |= MAN_EOS; + + return(man_descope(m, line, offs)); +} + + +static int +man_pmacro(struct man *m, int ln, char *buf, int offs) +{ + int i, j, ppos; + enum mant tok; + char mac[5]; + struct man_node *n; + + /* Comments and empties are quickly ignored. */ + + offs++; + + if ('\0' == buf[offs]) + return(1); + + i = offs; + + /* + * Skip whitespace between the control character and initial + * text. "Whitespace" is both spaces and tabs. + */ + + if (' ' == buf[i] || '\t' == buf[i]) { + i++; + while (buf[i] && (' ' == buf[i] || '\t' == buf[i])) + i++; + if ('\0' == buf[i]) + goto out; + } + + ppos = i; + + /* + * Copy the first word into a nil-terminated buffer. + * Stop copying when a tab, space, or eoln is encountered. + */ + + j = 0; + while (j < 4 && '\0' != buf[i] && ' ' != buf[i] && '\t' != buf[i]) + mac[j++] = buf[i++]; + mac[j] = '\0'; + + tok = (j > 0 && j < 4) ? man_hash_find(mac) : MAN_MAX; + if (MAN_MAX == tok) { + man_vmsg(m, MANDOCERR_MACRO, ln, ppos, "%s", buf + ppos - 1); + return(1); + } + + /* The macro is sane. Jump to the next word. */ + + while (buf[i] && ' ' == buf[i]) + i++; + + /* + * 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[i] && ' ' == buf[i - 1]) + man_pmsg(m, ln, i - 1, MANDOCERR_EOLNSPACE); + + /* + * Remove prior ELINE macro, as it's being clobbered by a new + * macro. Note that NSCOPED macros do not close out ELINE + * macros---they don't print text---so we let those slip by. + */ + + if ( ! (MAN_NSCOPED & man_macros[tok].flags) && + m->flags & MAN_ELINE) { + n = m->last; + assert(MAN_TEXT != n->type); + + /* Remove repeated NSCOPED macros causing ELINE. */ + + if (MAN_NSCOPED & man_macros[n->tok].flags) + n = n->parent; + + man_vmsg(m, MANDOCERR_LINESCOPE, n->line, n->pos, + "%s", man_macronames[n->tok]); + + man_node_delete(m, n); + m->flags &= ~MAN_ELINE; + } + + /* + * Save the fact that we're in the next-line for a block. In + * this way, embedded roff instructions can "remember" state + * when they exit. + */ + + if (MAN_BLINE & m->flags) + m->flags |= MAN_BPLINE; + + /* Call to handler... */ + + assert(man_macros[tok].fp); + if ( ! (*man_macros[tok].fp)(m, tok, ln, ppos, &i, buf)) + goto err; + +out: + /* + * We weren't in a block-line scope when entering the + * above-parsed macro, so return. + */ + + if ( ! (MAN_BPLINE & m->flags)) { + m->flags &= ~MAN_ILINE; + return(1); + } + m->flags &= ~MAN_BPLINE; + + /* + * If we're in a block scope, then allow this macro to slip by + * without closing scope around it. + */ + + if (MAN_ILINE & m->flags) { + m->flags &= ~MAN_ILINE; + return(1); + } + + /* + * If we've opened a new next-line element scope, then return + * now, as the next line will close out the block scope. + */ + + if (MAN_ELINE & m->flags) + return(1); + + /* Close out the block scope opened in the prior line. */ + + assert(MAN_BLINE & m->flags); + m->flags &= ~MAN_BLINE; + + if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX)) + return(0); + return(man_body_alloc(m, ln, offs, m->last->tok)); + +err: /* Error out. */ + + m->flags |= MAN_HALT; + return(0); +} + + +int +man_vmsg(struct man *man, enum mandocerr t, + int ln, int pos, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + return((*man->msg)(t, man->data, ln, pos, buf)); +} + + +/* + * Unlink a node from its context. If "m" is provided, the last parse + * point will also be adjusted accordingly. + */ +static void +man_node_unlink(struct man *m, struct man_node *n) +{ + + /* Adjust siblings. */ + + if (n->prev) + n->prev->next = n->next; + if (n->next) + n->next->prev = n->prev; + + /* Adjust parent. */ + + if (n->parent) { + n->parent->nchild--; + if (n->parent->child == n) + n->parent->child = n->prev ? n->prev : n->next; + } + + /* Adjust parse point, if applicable. */ + + if (m && m->last == n) { + /*XXX: this can occur when bailing from validation. */ + /*assert(NULL == n->next);*/ + if (n->prev) { + m->last = n->prev; + m->next = MAN_NEXT_SIBLING; + } else { + m->last = n->parent; + m->next = MAN_NEXT_CHILD; + } + } + + if (m && m->first == n) + m->first = NULL; +} diff --git a/contrib/mdocml/man.h b/contrib/mdocml/man.h new file mode 100644 index 0000000000..581f55ffc4 --- /dev/null +++ b/contrib/mdocml/man.h @@ -0,0 +1,130 @@ +/* $Id: man.h,v 1.50 2011/01/01 12:59:17 kristaps 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. + */ +#ifndef MAN_H +#define MAN_H + +/* + * What follows is a list of ALL possible macros. + */ +enum mant { + MAN_br = 0, + MAN_TH, + MAN_SH, + MAN_SS, + MAN_TP, + MAN_LP, + MAN_PP, + MAN_P, + MAN_IP, + MAN_HP, + MAN_SM, + MAN_SB, + MAN_BI, + MAN_IB, + MAN_BR, + MAN_RB, + MAN_R, + MAN_B, + MAN_I, + MAN_IR, + MAN_RI, + MAN_na, + MAN_sp, + MAN_nf, + MAN_fi, + MAN_RE, + MAN_RS, + MAN_DT, + MAN_UC, + MAN_PD, + MAN_AT, + MAN_in, + MAN_ft, + MAN_MAX +}; + +/* + * Type of a syntax node. + */ +enum man_type { + MAN_TEXT, + MAN_ELEM, + MAN_ROOT, + MAN_BLOCK, + MAN_HEAD, + MAN_BODY, + MAN_TBL +}; + +/* + * Information from prologue. + */ +struct man_meta { + char *msec; /* `TH' section (1, 3p, etc.) */ + time_t date; /* `TH' normalised date */ + char *rawdate; /* raw `TH' date */ + char *vol; /* `TH' volume */ + char *title; /* `TH' title (e.g., FOO) */ + char *source; /* `TH' source (e.g., GNU) */ +}; + +/* + * Single node in tree-linked AST. + */ +struct man_node { + struct man_node *parent; /* parent AST node */ + struct man_node *child; /* first child AST node */ + struct man_node *next; /* sibling AST node */ + struct man_node *prev; /* prior sibling AST node */ + int nchild; /* number children */ + int line; + int pos; + enum mant tok; /* tok or MAN__MAX if none */ + int flags; +#define MAN_VALID (1 << 0) /* has been validated */ +#define MAN_EOS (1 << 2) /* at sentence boundary */ + enum man_type type; /* AST node type */ + char *string; /* TEXT node argument */ + struct man_node *head; /* BLOCK node HEAD ptr */ + struct man_node *body; /* BLOCK node BODY ptr */ + const struct tbl_span *span; /* TBL */ +}; + +/* + * Names of macros. Index is enum mant. Indexing into this returns + * the normalised name, e.g., man_macronames[MAN_SH] -> "SH". + */ +extern const char *const *man_macronames; + +__BEGIN_DECLS + +struct man; + +void man_free(struct man *); +struct man *man_alloc(struct regset *, void *, mandocmsg); +void man_reset(struct man *); +int man_parseln(struct man *, int, char *, int); +int man_endparse(struct man *); +int man_addspan(struct man *, + const struct tbl_span *); + +const struct man_node *man_node(const struct man *); +const struct man_meta *man_meta(const struct man *); + +__END_DECLS + +#endif /*!MAN_H*/ diff --git a/contrib/mdocml/man_argv.c b/contrib/mdocml/man_argv.c new file mode 100644 index 0000000000..37aac030c1 --- /dev/null +++ b/contrib/mdocml/man_argv.c @@ -0,0 +1,44 @@ +/* $Id: man_argv.c,v 1.5 2011/01/03 22:42:37 schwarze Exp $ */ +/* + * Copyright (c) 2011 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "mandoc.h" +#include "libman.h" +#include "libmandoc.h" + + +int +man_args(struct man *m, int line, int *pos, char *buf, char **v) +{ + char *start; + + assert(*pos); + *v = start = buf + *pos; + assert(' ' != *start); + + if ('\0' == *start) + return(ARGS_EOLN); + + *v = mandoc_getarg(v, m->msg, m->data, line, pos); + return('"' == *start ? ARGS_QWORD : ARGS_WORD); +} diff --git a/contrib/mdocml/man_hash.c b/contrib/mdocml/man_hash.c new file mode 100644 index 0000000000..6524f36e4d --- /dev/null +++ b/contrib/mdocml/man_hash.c @@ -0,0 +1,106 @@ +/* $Id: man_hash.c,v 1.23 2010/07/31 23:52:58 schwarze Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libman.h" + +#define HASH_DEPTH 6 + +#define HASH_ROW(x) do { \ + if (isupper((u_char)(x))) \ + (x) -= 65; \ + else \ + (x) -= 97; \ + (x) *= HASH_DEPTH; \ + } while (/* CONSTCOND */ 0) + +/* + * Lookup table is indexed first by lower-case first letter (plus one + * for the period, which is stored in the last row), then by lower or + * uppercase second letter. Buckets correspond to the index of the + * macro (the integer value of the enum stored as a char to save a bit + * of space). + */ +static u_char table[26 * HASH_DEPTH]; + +/* + * XXX - this hash has global scope, so if intended for use as a library + * with multiple callers, it will need re-invocation protection. + */ +void +man_hash_init(void) +{ + int i, j, x; + + memset(table, UCHAR_MAX, sizeof(table)); + + assert(/* LINTED */ + MAN_MAX < UCHAR_MAX); + + for (i = 0; i < (int)MAN_MAX; i++) { + x = man_macronames[i][0]; + + assert(isalpha((u_char)x)); + + HASH_ROW(x); + + for (j = 0; j < HASH_DEPTH; j++) + if (UCHAR_MAX == table[x + j]) { + table[x + j] = (u_char)i; + break; + } + + assert(j < HASH_DEPTH); + } +} + + +enum mant +man_hash_find(const char *tmp) +{ + int x, y, i; + enum mant tok; + + if ('\0' == (x = tmp[0])) + return(MAN_MAX); + if ( ! (isalpha((u_char)x))) + return(MAN_MAX); + + HASH_ROW(x); + + for (i = 0; i < HASH_DEPTH; i++) { + if (UCHAR_MAX == (y = table[x + i])) + return(MAN_MAX); + + tok = (enum mant)y; + if (0 == strcmp(tmp, man_macronames[tok])) + return(tok); + } + + return(MAN_MAX); +} diff --git a/contrib/mdocml/man_html.c b/contrib/mdocml/man_html.c new file mode 100644 index 0000000000..da6880a1c4 --- /dev/null +++ b/contrib/mdocml/man_html.c @@ -0,0 +1,684 @@ +/* $Id: man_html.c,v 1.62 2011/01/07 13:20:58 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "html.h" +#include "man.h" +#include "main.h" + +/* TODO: preserve ident widths. */ +/* FIXME: have PD set the default vspace width. */ + +#define INDENT 5 +#define HALFINDENT 3 + +#define MAN_ARGS const struct man_meta *m, \ + const struct man_node *n, \ + struct mhtml *mh, \ + struct html *h + +struct mhtml { + int fl; +#define MANH_LITERAL (1 << 0) /* literal context */ +}; + +struct htmlman { + int (*pre)(MAN_ARGS); + int (*post)(MAN_ARGS); +}; + +static void print_man(MAN_ARGS); +static void print_man_head(MAN_ARGS); +static void print_man_nodelist(MAN_ARGS); +static void print_man_node(MAN_ARGS); + +static int a2width(const struct man_node *, + struct roffsu *); + +static int man_alt_pre(MAN_ARGS); +static int man_br_pre(MAN_ARGS); +static int man_ign_pre(MAN_ARGS); +static int man_in_pre(MAN_ARGS); +static int man_literal_pre(MAN_ARGS); +static void man_root_post(MAN_ARGS); +static int man_root_pre(MAN_ARGS); +static int man_B_pre(MAN_ARGS); +static int man_HP_pre(MAN_ARGS); +static int man_I_pre(MAN_ARGS); +static int man_IP_pre(MAN_ARGS); +static int man_PP_pre(MAN_ARGS); +static int man_RS_pre(MAN_ARGS); +static int man_SH_pre(MAN_ARGS); +static int man_SM_pre(MAN_ARGS); +static int man_SS_pre(MAN_ARGS); + +static const struct htmlman mans[MAN_MAX] = { + { man_br_pre, NULL }, /* br */ + { NULL, NULL }, /* TH */ + { man_SH_pre, NULL }, /* SH */ + { man_SS_pre, NULL }, /* SS */ + { man_IP_pre, NULL }, /* TP */ + { man_PP_pre, NULL }, /* LP */ + { man_PP_pre, NULL }, /* PP */ + { man_PP_pre, NULL }, /* P */ + { man_IP_pre, NULL }, /* IP */ + { man_HP_pre, NULL }, /* HP */ + { man_SM_pre, NULL }, /* SM */ + { man_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 }, /* na */ + { man_br_pre, NULL }, /* sp */ + { man_literal_pre, NULL }, /* nf */ + { man_literal_pre, NULL }, /* fi */ + { NULL, NULL }, /* RE */ + { man_RS_pre, NULL }, /* RS */ + { man_ign_pre, NULL }, /* DT */ + { man_ign_pre, NULL }, /* UC */ + { man_ign_pre, NULL }, /* PD */ + { man_ign_pre, NULL }, /* AT */ + { man_in_pre, NULL }, /* in */ + { man_ign_pre, NULL }, /* ft */ +}; + + +void +html_man(void *arg, const struct man *m) +{ + struct html *h; + struct tag *t; + struct mhtml mh; + + h = (struct html *)arg; + + print_gen_decls(h); + + memset(&mh, 0, sizeof(struct mhtml)); + + t = print_otag(h, TAG_HTML, 0, NULL); + print_man(man_meta(m), man_node(m), &mh, h); + print_tagq(h, t); + + printf("\n"); +} + + +static void +print_man(MAN_ARGS) +{ + struct tag *t; + + t = print_otag(h, TAG_HEAD, 0, NULL); + print_man_head(m, n, mh, h); + print_tagq(h, t); + + t = print_otag(h, TAG_BODY, 0, NULL); + print_man_nodelist(m, n, mh, h); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static void +print_man_head(MAN_ARGS) +{ + + print_gen_head(h); + bufinit(h); + buffmt(h, "%s(%s)", m->title, m->msec); + + print_otag(h, TAG_TITLE, 0, NULL); + print_text(h, h->buf); +} + + +static void +print_man_nodelist(MAN_ARGS) +{ + + print_man_node(m, n, mh, h); + if (n->next) + print_man_nodelist(m, n->next, mh, h); +} + + +static void +print_man_node(MAN_ARGS) +{ + int child; + struct tag *t; + + child = 1; + t = h->tags.head; + + bufinit(h); + + /* + * FIXME: embedded elements within next-line scopes (e.g., `br' + * within an empty `B') will cause formatting to be forgotten + * due to scope closing out. + */ + + switch (n->type) { + case (MAN_ROOT): + child = man_root_pre(m, n, mh, h); + break; + case (MAN_TEXT): + print_text(h, n->string); + if (MANH_LITERAL & mh->fl) + print_otag(h, TAG_BR, 0, NULL); + return; + case (MAN_TBL): + print_tbl(h, n->span); + break; + default: + /* + * Close out scope of font prior to opening a macro + * scope. Assert that the metafont is on the top of the + * stack (it's never nested). + */ + if (HTMLFONT_NONE != h->metac) { + h->metal = h->metac; + h->metac = HTMLFONT_NONE; + } + if (mans[n->tok].pre) + child = (*mans[n->tok].pre)(m, n, mh, h); + break; + } + + if (child && n->child) + print_man_nodelist(m, n->child, mh, h); + + /* This will automatically close out any font scope. */ + print_stagq(h, t); + + bufinit(h); + + switch (n->type) { + case (MAN_ROOT): + man_root_post(m, n, mh, h); + break; + case (MAN_TBL): + break; + default: + if (mans[n->tok].post) + (*mans[n->tok].post)(m, n, mh, h); + break; + } +} + + +static int +a2width(const struct man_node *n, struct roffsu *su) +{ + + if (MAN_TEXT != n->type) + return(0); + if (a2roffsu(n->string, su, SCALE_BU)) + return(1); + + return(0); +} + + +/* ARGSUSED */ +static int +man_root_pre(MAN_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + char b[BUFSIZ], title[BUFSIZ]; + + b[0] = 0; + if (m->vol) + (void)strlcat(b, m->vol, BUFSIZ); + + snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec); + + PAIR_SUMMARY_INIT(&tag[0], "Document Header"); + PAIR_CLASS_INIT(&tag[1], "head"); + if (NULL == h->style) { + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "30%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + } else + t = print_otag(h, TAG_TABLE, 2, tag); + + print_otag(h, TAG_TBODY, 0, NULL); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "head-ltitle"); + print_otag(h, TAG_TD, 1, tag); + + print_text(h, title); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-vol"); + if (NULL == h->style) { + PAIR_INIT(&tag[1], ATTR_ALIGN, "center"); + print_otag(h, TAG_TD, 2, tag); + } else + print_otag(h, TAG_TD, 1, tag); + + print_text(h, b); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-rtitle"); + if (NULL == h->style) { + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + } else + print_otag(h, TAG_TD, 1, tag); + + print_text(h, title); + print_tagq(h, t); + return(1); +} + + +/* ARGSUSED */ +static void +man_root_post(MAN_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + char b[DATESIZ]; + + if (m->rawdate) + strlcpy(b, m->rawdate, DATESIZ); + else + time2a(m->date, b, DATESIZ); + + PAIR_SUMMARY_INIT(&tag[0], "Document Footer"); + PAIR_CLASS_INIT(&tag[1], "foot"); + if (NULL == h->style) { + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "50%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + } else + t = print_otag(h, TAG_TABLE, 2, tag); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "foot-date"); + print_otag(h, TAG_TD, 1, tag); + + print_text(h, b); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "foot-os"); + if (NULL == h->style) { + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + } else + print_otag(h, TAG_TD, 1, tag); + + if (m->source) + print_text(h, m->source); + print_tagq(h, t); +} + + + +/* ARGSUSED */ +static int +man_br_pre(MAN_ARGS) +{ + struct roffsu su; + struct htmlpair tag; + + SCALE_VS_INIT(&su, 1); + + if (MAN_sp == n->tok) { + if (n->child) + a2roffsu(n->child->string, &su, SCALE_VS); + } else + su.scale = 0; + + bufcat_su(h, "height", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + + /* So the div isn't empty: */ + print_text(h, "\\~"); + + return(0); +} + + +/* ARGSUSED */ +static int +man_SH_pre(MAN_ARGS) +{ + struct htmlpair tag; + + if (MAN_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag, "section"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MAN_BODY == n->type) + return(1); + + print_otag(h, TAG_H1, 0, NULL); + return(1); +} + + +/* ARGSUSED */ +static int +man_alt_pre(MAN_ARGS) +{ + const struct man_node *nn; + int i; + enum htmltag fp; + struct tag *t; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + t = NULL; + switch (n->tok) { + case (MAN_BI): + fp = i % 2 ? TAG_I : TAG_B; + break; + case (MAN_IB): + fp = i % 2 ? TAG_B : TAG_I; + break; + case (MAN_RI): + fp = i % 2 ? TAG_I : TAG_MAX; + break; + case (MAN_IR): + fp = i % 2 ? TAG_MAX : TAG_I; + break; + case (MAN_BR): + fp = i % 2 ? TAG_MAX : TAG_B; + break; + case (MAN_RB): + fp = i % 2 ? TAG_B : TAG_MAX; + break; + default: + abort(); + /* NOTREACHED */ + } + + if (i) + h->flags |= HTML_NOSPACE; + + if (TAG_MAX != fp) + t = print_otag(h, fp, 0, NULL); + + print_man_node(m, nn, mh, h); + + if (t) + print_tagq(h, t); + } + + return(0); +} + + +/* ARGSUSED */ +static int +man_SM_pre(MAN_ARGS) +{ + + print_otag(h, TAG_SMALL, 0, NULL); + if (MAN_SB == n->tok) + print_otag(h, TAG_B, 0, NULL); + return(1); +} + + +/* ARGSUSED */ +static int +man_SS_pre(MAN_ARGS) +{ + struct htmlpair tag; + + if (MAN_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag, "subsection"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MAN_BODY == n->type) + return(1); + + print_otag(h, TAG_H2, 0, NULL); + return(1); +} + + +/* ARGSUSED */ +static int +man_PP_pre(MAN_ARGS) +{ + + if (MAN_HEAD == n->type) + return(0); + else if (MAN_BODY == n->type && n->prev) + print_otag(h, TAG_P, 0, NULL); + + return(1); +} + + +/* ARGSUSED */ +static int +man_IP_pre(MAN_ARGS) +{ + struct roffsu su; + struct htmlpair tag; + const struct man_node *nn; + + /* + * This scattering of 1-BU margins and pads is to make sure that + * when text overruns its box, the subsequent text isn't flush + * up against it. However, the rest of the right-hand box must + * also be adjusted in consideration of this 1-BU space. + */ + + if (MAN_BODY == n->type) { + print_otag(h, TAG_TD, 0, NULL); + return(1); + } + + nn = MAN_BLOCK == n->type ? + n->head->child : n->parent->head->child; + + SCALE_HS_INIT(&su, INDENT); + + /* Width is the second token. */ + + if (MAN_IP == n->tok && NULL != nn) + if (NULL != (nn = nn->next)) + a2width(nn, &su); + + /* Width is the first token. */ + + if (MAN_TP == n->tok && NULL != nn) { + /* Skip past non-text children. */ + while (nn && MAN_TEXT != nn->type) + nn = nn->next; + if (nn) + a2width(nn, &su); + } + + if (MAN_BLOCK == n->type) { + print_otag(h, TAG_P, 0, NULL); + print_otag(h, TAG_TABLE, 0, NULL); + bufcat_su(h, "width", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_COL, 1, &tag); + print_otag(h, TAG_COL, 0, NULL); + print_otag(h, TAG_TBODY, 0, NULL); + print_otag(h, TAG_TR, 0, NULL); + return(1); + } + + print_otag(h, TAG_TD, 0, NULL); + + /* For IP, only print the first header element. */ + + if (MAN_IP == n->tok && n->child) + print_man_node(m, n->child, mh, h); + + /* For TP, only print next-line header elements. */ + + if (MAN_TP == n->tok) + for (nn = n->child; nn; nn = nn->next) + if (nn->line > n->line) + print_man_node(m, nn, mh, h); + + return(0); +} + + +/* ARGSUSED */ +static int +man_HP_pre(MAN_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + const struct man_node *np; + + np = MAN_BLOCK == n->type ? + n->head->child : + n->parent->head->child; + + if (NULL == np || ! a2width(np, &su)) + SCALE_HS_INIT(&su, INDENT); + + if (MAN_HEAD == n->type) { + print_otag(h, TAG_TD, 0, NULL); + return(0); + } else if (MAN_BLOCK == n->type) { + print_otag(h, TAG_P, 0, NULL); + print_otag(h, TAG_TABLE, 0, NULL); + bufcat_su(h, "width", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_COL, 1, &tag); + print_otag(h, TAG_COL, 0, NULL); + print_otag(h, TAG_TBODY, 0, NULL); + print_otag(h, TAG_TR, 0, NULL); + return(1); + } + + su.scale = -su.scale; + bufcat_su(h, "text-indent", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_TD, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +man_B_pre(MAN_ARGS) +{ + + print_otag(h, TAG_B, 0, NULL); + return(1); +} + + +/* ARGSUSED */ +static int +man_I_pre(MAN_ARGS) +{ + + print_otag(h, TAG_I, 0, NULL); + return(1); +} + + +/* ARGSUSED */ +static int +man_literal_pre(MAN_ARGS) +{ + + if (MAN_nf == n->tok) { + print_otag(h, TAG_BR, 0, NULL); + mh->fl |= MANH_LITERAL; + } else + mh->fl &= ~MANH_LITERAL; + + return(1); +} + + +/* ARGSUSED */ +static int +man_in_pre(MAN_ARGS) +{ + + print_otag(h, TAG_BR, 0, NULL); + return(0); +} + + +/* ARGSUSED */ +static int +man_ign_pre(MAN_ARGS) +{ + + return(0); +} + + +/* ARGSUSED */ +static int +man_RS_pre(MAN_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + + if (MAN_HEAD == n->type) + return(0); + else if (MAN_BODY == n->type) + return(1); + + SCALE_HS_INIT(&su, INDENT); + if (n->head->child) + a2width(n->head->child, &su); + + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); +} diff --git a/contrib/mdocml/man_macro.c b/contrib/mdocml/man_macro.c new file mode 100644 index 0000000000..bd0ca99243 --- /dev/null +++ b/contrib/mdocml/man_macro.c @@ -0,0 +1,477 @@ +/* $Id: man_macro.c,v 1.54 2010/12/08 10:58:22 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "mandoc.h" +#include "libman.h" + +enum rew { + REW_REWIND, + REW_NOHALT, + REW_HALT +}; + +static int blk_close(MACRO_PROT_ARGS); +static int blk_exp(MACRO_PROT_ARGS); +static int blk_imp(MACRO_PROT_ARGS); +static int in_line_eoln(MACRO_PROT_ARGS); + +static int rew_scope(enum man_type, + struct man *, enum mant); +static enum rew rew_dohalt(enum mant, enum man_type, + const struct man_node *); +static enum rew rew_block(enum mant, enum man_type, + const struct man_node *); +static int rew_warn(struct man *, + struct man_node *, enum mandocerr); + +const struct man_macro __man_macros[MAN_MAX] = { + { in_line_eoln, MAN_NSCOPED }, /* br */ + { in_line_eoln, 0 }, /* TH */ + { blk_imp, MAN_SCOPED }, /* SH */ + { blk_imp, MAN_SCOPED }, /* SS */ + { blk_imp, MAN_SCOPED | MAN_FSCOPED }, /* TP */ + { blk_imp, 0 }, /* LP */ + { blk_imp, 0 }, /* PP */ + { blk_imp, 0 }, /* P */ + { blk_imp, 0 }, /* IP */ + { blk_imp, 0 }, /* HP */ + { in_line_eoln, MAN_SCOPED }, /* SM */ + { in_line_eoln, MAN_SCOPED }, /* SB */ + { in_line_eoln, 0 }, /* BI */ + { in_line_eoln, 0 }, /* IB */ + { in_line_eoln, 0 }, /* BR */ + { in_line_eoln, 0 }, /* RB */ + { in_line_eoln, MAN_SCOPED }, /* R */ + { in_line_eoln, MAN_SCOPED }, /* B */ + { in_line_eoln, MAN_SCOPED }, /* I */ + { in_line_eoln, 0 }, /* IR */ + { in_line_eoln, 0 }, /* RI */ + { in_line_eoln, MAN_NSCOPED }, /* na */ + { in_line_eoln, MAN_NSCOPED }, /* sp */ + { in_line_eoln, 0 }, /* nf */ + { in_line_eoln, 0 }, /* fi */ + { blk_close, 0 }, /* RE */ + { blk_exp, MAN_EXPLICIT }, /* RS */ + { in_line_eoln, 0 }, /* DT */ + { in_line_eoln, 0 }, /* UC */ + { in_line_eoln, 0 }, /* PD */ + { in_line_eoln, 0 }, /* AT */ + { in_line_eoln, 0 }, /* in */ + { in_line_eoln, 0 }, /* ft */ +}; + +const struct man_macro * const man_macros = __man_macros; + + +/* + * Warn when "n" is an explicit non-roff macro. + */ +static int +rew_warn(struct man *m, struct man_node *n, enum mandocerr er) +{ + + if (er == MANDOCERR_MAX || MAN_BLOCK != n->type) + return(1); + if (MAN_VALID & n->flags) + return(1); + if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags)) + return(1); + return(man_nmsg(m, n, er)); +} + + +/* + * Rewind scope. If a code "er" != MANDOCERR_MAX has been provided, it + * will be used if an explicit block scope is being closed out. + */ +int +man_unscope(struct man *m, const struct man_node *n, + enum mandocerr er) +{ + + assert(n); + + /* LINTED */ + while (m->last != n) { + if ( ! rew_warn(m, m->last, er)) + return(0); + if ( ! man_valid_post(m)) + return(0); + m->last = m->last->parent; + assert(m->last); + } + + if ( ! rew_warn(m, m->last, er)) + return(0); + if ( ! man_valid_post(m)) + return(0); + + m->next = MAN_ROOT == m->last->type ? + MAN_NEXT_CHILD : MAN_NEXT_SIBLING; + + return(1); +} + + +static enum rew +rew_block(enum mant ntok, enum man_type type, const struct man_node *n) +{ + + if (MAN_BLOCK == type && ntok == n->parent->tok && + MAN_BODY == n->parent->type) + return(REW_REWIND); + return(ntok == n->tok ? REW_HALT : REW_NOHALT); +} + + +/* + * There are three scope levels: scoped to the root (all), scoped to the + * section (all less sections), and scoped to subsections (all less + * sections and subsections). + */ +static enum rew +rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n) +{ + enum rew c; + + /* We cannot progress beyond the root ever. */ + if (MAN_ROOT == n->type) + return(REW_HALT); + + assert(n->parent); + + /* Normal nodes shouldn't go to the level of the root. */ + if (MAN_ROOT == n->parent->type) + return(REW_REWIND); + + /* Already-validated nodes should be closed out. */ + if (MAN_VALID & n->flags) + return(REW_NOHALT); + + /* First: rewind to ourselves. */ + if (type == n->type && tok == n->tok) + return(REW_REWIND); + + /* + * Next follow the implicit scope-smashings as defined by man.7: + * section, sub-section, etc. + */ + + switch (tok) { + case (MAN_SH): + break; + case (MAN_SS): + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + case (MAN_RS): + /* Rewind to a subsection, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SS, type, n))) + return(c); + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + default: + /* Rewind to an offsetter, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_RS, type, n))) + return(c); + /* Rewind to a subsection, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SS, type, n))) + return(c); + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + } + + return(REW_NOHALT); +} + + +/* + * Rewinding entails ascending the parse tree until a coherent point, + * for example, the `SH' macro will close out any intervening `SS' + * scopes. When a scope is closed, it must be validated and actioned. + */ +static int +rew_scope(enum man_type type, struct man *m, enum mant tok) +{ + struct man_node *n; + enum rew c; + + /* LINTED */ + for (n = m->last; n; n = n->parent) { + /* + * Whether we should stop immediately (REW_HALT), stop + * and rewind until this point (REW_REWIND), or keep + * rewinding (REW_NOHALT). + */ + c = rew_dohalt(tok, type, n); + if (REW_HALT == c) + return(1); + if (REW_REWIND == c) + break; + } + + /* + * Rewind until the current point. Warn if we're a roff + * instruction that's mowing over explicit scopes. + */ + assert(n); + + return(man_unscope(m, n, MANDOCERR_MAX)); +} + + +/* + * Close out a generic explicit macro. + */ +/* ARGSUSED */ +int +blk_close(MACRO_PROT_ARGS) +{ + enum mant ntok; + const struct man_node *nn; + + switch (tok) { + case (MAN_RE): + ntok = MAN_RS; + break; + default: + abort(); + /* NOTREACHED */ + } + + for (nn = m->last->parent; nn; nn = nn->parent) + if (ntok == nn->tok) + break; + + if (NULL == nn) + if ( ! man_pmsg(m, line, ppos, MANDOCERR_NOSCOPE)) + return(0); + + if ( ! rew_scope(MAN_BODY, m, ntok)) + return(0); + if ( ! rew_scope(MAN_BLOCK, m, ntok)) + return(0); + + return(1); +} + + +/* ARGSUSED */ +int +blk_exp(MACRO_PROT_ARGS) +{ + int w, la; + char *p; + + /* + * Close out prior scopes. "Regular" explicit macros cannot be + * nested, but we allow roff macros to be placed just about + * anywhere. + */ + + if ( ! rew_scope(MAN_BODY, m, tok)) + return(0); + if ( ! rew_scope(MAN_BLOCK, m, tok)) + return(0); + + if ( ! man_block_alloc(m, line, ppos, tok)) + return(0); + if ( ! man_head_alloc(m, line, ppos, tok)) + return(0); + + for (;;) { + la = *pos; + w = man_args(m, line, pos, buf, &p); + + if (-1 == w) + return(0); + if (0 == w) + break; + + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + assert(m); + assert(tok != MAN_MAX); + + if ( ! rew_scope(MAN_HEAD, m, tok)) + return(0); + return(man_body_alloc(m, line, ppos, tok)); +} + + + +/* + * Parse an implicit-block macro. These contain a MAN_HEAD and a + * MAN_BODY contained within a MAN_BLOCK. Rules for closing out other + * scopes, such as `SH' closing out an `SS', are defined in the rew + * routines. + */ +/* ARGSUSED */ +int +blk_imp(MACRO_PROT_ARGS) +{ + int w, la; + char *p; + struct man_node *n; + + /* Close out prior scopes. */ + + if ( ! rew_scope(MAN_BODY, m, tok)) + return(0); + if ( ! rew_scope(MAN_BLOCK, m, tok)) + return(0); + + /* Allocate new block & head scope. */ + + if ( ! man_block_alloc(m, line, ppos, tok)) + return(0); + if ( ! man_head_alloc(m, line, ppos, tok)) + return(0); + + n = m->last; + + /* Add line arguments. */ + + for (;;) { + la = *pos; + w = man_args(m, line, pos, buf, &p); + + if (-1 == w) + return(0); + if (0 == w) + break; + + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + /* Close out head and open body (unless MAN_SCOPE). */ + + if (MAN_SCOPED & man_macros[tok].flags) { + /* If we're forcing scope (`TP'), keep it open. */ + if (MAN_FSCOPED & man_macros[tok].flags) { + m->flags |= MAN_BLINE; + return(1); + } else if (n == m->last) { + m->flags |= MAN_BLINE; + return(1); + } + } + + if ( ! rew_scope(MAN_HEAD, m, tok)) + return(0); + return(man_body_alloc(m, line, ppos, tok)); +} + + +/* ARGSUSED */ +int +in_line_eoln(MACRO_PROT_ARGS) +{ + int w, la; + char *p; + struct man_node *n; + + if ( ! man_elem_alloc(m, line, ppos, tok)) + return(0); + + n = m->last; + + for (;;) { + la = *pos; + w = man_args(m, line, pos, buf, &p); + + if (-1 == w) + return(0); + if (0 == w) + break; + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + /* + * If no arguments are specified and this is MAN_SCOPED (i.e., + * next-line scoped), then set our mode to indicate that we're + * waiting for terms to load into our context. + */ + + if (n == m->last && MAN_SCOPED & man_macros[tok].flags) { + assert( ! (MAN_NSCOPED & man_macros[tok].flags)); + m->flags |= MAN_ELINE; + return(1); + } + + /* Set ignorable context, if applicable. */ + + if (MAN_NSCOPED & man_macros[tok].flags) { + assert( ! (MAN_SCOPED & man_macros[tok].flags)); + m->flags |= MAN_ILINE; + } + + /* + * Rewind our element scope. Note that when TH is pruned, we'll + * be back at the root, so make sure that we don't clobber as + * its sibling. + */ + + for ( ; m->last; m->last = m->last->parent) { + if (m->last == n) + break; + if (m->last->type == MAN_ROOT) + break; + if ( ! man_valid_post(m)) + return(0); + } + + assert(m->last); + + /* + * Same here regarding whether we're back at the root. + */ + + if (m->last->type != MAN_ROOT && ! man_valid_post(m)) + return(0); + + m->next = MAN_ROOT == m->last->type ? + MAN_NEXT_CHILD : MAN_NEXT_SIBLING; + + return(1); +} + + +int +man_macroend(struct man *m) +{ + + return(man_unscope(m, m->first, MANDOCERR_SCOPEEXIT)); +} + diff --git a/contrib/mdocml/man_term.c b/contrib/mdocml/man_term.c new file mode 100644 index 0000000000..c0ef70dbea --- /dev/null +++ b/contrib/mdocml/man_term.c @@ -0,0 +1,1031 @@ +/* $Id: man_term.c,v 1.94 2011/01/04 01:23:18 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2010, 2011 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "man.h" +#include "term.h" +#include "chars.h" +#include "main.h" + +#define INDENT 7 +#define HALFINDENT 3 + +/* FIXME: have PD set the default vspace width. */ + +struct mtermp { + int fl; +#define MANT_LITERAL (1 << 0) + /* + * Default amount to indent the left margin after leading text + * has been printed (e.g., `HP' left-indent, `TP' and `IP' body + * indent). This needs to be saved because `HP' and so on, if + * not having a specified value, must default. + * + * Note that this is the indentation AFTER the left offset, so + * the total offset is usually offset + lmargin. + */ + size_t lmargin; + /* + * The default offset, i.e., the amount between any text and the + * page boundary. + */ + size_t offset; +}; + +#define DECL_ARGS struct termp *p, \ + struct mtermp *mt, \ + const struct man_node *n, \ + const struct man_meta *m + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); + int flags; +#define MAN_NOTEXT (1 << 0) /* Never has text children. */ +}; + +static int a2width(const struct termp *, const char *); +static size_t a2height(const struct termp *, const char *); + +static void print_man_nodelist(DECL_ARGS); +static void print_man_node(DECL_ARGS); +static void print_man_head(struct termp *, const void *); +static void print_man_foot(struct termp *, const void *); +static void print_bvspace(struct termp *, + const struct man_node *); + +static int pre_alternate(DECL_ARGS); +static int pre_B(DECL_ARGS); +static int pre_HP(DECL_ARGS); +static int pre_I(DECL_ARGS); +static int pre_IP(DECL_ARGS); +static int pre_PP(DECL_ARGS); +static int pre_RS(DECL_ARGS); +static int pre_SH(DECL_ARGS); +static int pre_SS(DECL_ARGS); +static int pre_TP(DECL_ARGS); +static int pre_ign(DECL_ARGS); +static int pre_in(DECL_ARGS); +static int pre_literal(DECL_ARGS); +static int pre_sp(DECL_ARGS); +static int pre_ft(DECL_ARGS); + +static void post_IP(DECL_ARGS); +static void post_HP(DECL_ARGS); +static void post_RS(DECL_ARGS); +static void post_SH(DECL_ARGS); +static void post_SS(DECL_ARGS); +static void post_TP(DECL_ARGS); + +static const struct termact termacts[MAN_MAX] = { + { pre_sp, NULL, MAN_NOTEXT }, /* br */ + { NULL, NULL, 0 }, /* TH */ + { pre_SH, post_SH, 0 }, /* SH */ + { pre_SS, post_SS, 0 }, /* SS */ + { pre_TP, post_TP, 0 }, /* TP */ + { pre_PP, NULL, 0 }, /* LP */ + { pre_PP, NULL, 0 }, /* PP */ + { pre_PP, NULL, 0 }, /* P */ + { pre_IP, post_IP, 0 }, /* IP */ + { pre_HP, post_HP, 0 }, /* HP */ + { NULL, NULL, 0 }, /* SM */ + { pre_B, NULL, 0 }, /* SB */ + { pre_alternate, NULL, 0 }, /* BI */ + { pre_alternate, NULL, 0 }, /* IB */ + { pre_alternate, NULL, 0 }, /* BR */ + { pre_alternate, NULL, 0 }, /* RB */ + { NULL, NULL, 0 }, /* R */ + { pre_B, NULL, 0 }, /* B */ + { pre_I, NULL, 0 }, /* I */ + { pre_alternate, NULL, 0 }, /* IR */ + { pre_alternate, NULL, 0 }, /* RI */ + { NULL, NULL, MAN_NOTEXT }, /* na */ + { pre_sp, NULL, MAN_NOTEXT }, /* sp */ + { pre_literal, NULL, 0 }, /* nf */ + { pre_literal, NULL, 0 }, /* fi */ + { NULL, NULL, 0 }, /* RE */ + { pre_RS, post_RS, 0 }, /* RS */ + { pre_ign, NULL, 0 }, /* DT */ + { pre_ign, NULL, 0 }, /* UC */ + { pre_ign, NULL, 0 }, /* PD */ + { pre_ign, NULL, 0 }, /* AT */ + { pre_in, NULL, MAN_NOTEXT }, /* in */ + { pre_ft, NULL, MAN_NOTEXT }, /* ft */ +}; + + + +void +terminal_man(void *arg, const struct man *man) +{ + struct termp *p; + const struct man_node *n; + const struct man_meta *m; + struct mtermp mt; + + p = (struct termp *)arg; + + p->overstep = 0; + p->maxrmargin = p->defrmargin; + p->tabwidth = term_len(p, 5); + + if (NULL == p->symtab) + switch (p->enc) { + case (TERMENC_ASCII): + p->symtab = chars_init(CHARS_ASCII); + break; + default: + abort(); + /* NOTREACHED */ + } + + n = man_node(man); + m = man_meta(man); + + term_begin(p, print_man_head, print_man_foot, m); + p->flags |= TERMP_NOSPACE; + + mt.fl = 0; + mt.lmargin = term_len(p, INDENT); + mt.offset = term_len(p, INDENT); + + if (n->child) + print_man_nodelist(p, &mt, n->child, m); + + term_end(p); +} + + +static size_t +a2height(const struct termp *p, const char *cp) +{ + struct roffsu su; + + if ( ! a2roffsu(cp, &su, SCALE_VS)) + SCALE_VS_INIT(&su, term_strlen(p, cp)); + + return(term_vspan(p, &su)); +} + + +static int +a2width(const struct termp *p, const char *cp) +{ + struct roffsu su; + + if ( ! a2roffsu(cp, &su, SCALE_BU)) + return(-1); + + return((int)term_hspan(p, &su)); +} + + +static void +print_bvspace(struct termp *p, const struct man_node *n) +{ + term_newln(p); + + if (NULL == n->prev) + return; + + if (MAN_SS == n->prev->tok) + return; + if (MAN_SH == n->prev->tok) + return; + + term_vspace(p); +} + + +/* ARGSUSED */ +static int +pre_ign(DECL_ARGS) +{ + + return(0); +} + + +/* ARGSUSED */ +static int +pre_I(DECL_ARGS) +{ + + term_fontrepl(p, TERMFONT_UNDER); + return(1); +} + + +/* ARGSUSED */ +static int +pre_literal(DECL_ARGS) +{ + + term_newln(p); + + if (MAN_nf == n->tok) + mt->fl |= MANT_LITERAL; + else + mt->fl &= ~MANT_LITERAL; + + return(1); +} + +/* ARGSUSED */ +static int +pre_alternate(DECL_ARGS) +{ + enum termfont font[2]; + const struct man_node *nn; + int savelit, i; + + switch (n->tok) { + case (MAN_RB): + font[0] = TERMFONT_NONE; + font[1] = TERMFONT_BOLD; + break; + case (MAN_RI): + font[0] = TERMFONT_NONE; + font[1] = TERMFONT_UNDER; + break; + case (MAN_BR): + font[0] = TERMFONT_BOLD; + font[1] = TERMFONT_NONE; + break; + case (MAN_BI): + font[0] = TERMFONT_BOLD; + font[1] = TERMFONT_UNDER; + break; + case (MAN_IR): + font[0] = TERMFONT_UNDER; + font[1] = TERMFONT_NONE; + break; + case (MAN_IB): + font[0] = TERMFONT_UNDER; + font[1] = TERMFONT_BOLD; + break; + default: + abort(); + } + + savelit = MANT_LITERAL & mt->fl; + mt->fl &= ~MANT_LITERAL; + + for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) { + term_fontrepl(p, font[i]); + if (savelit && NULL == nn->next) + mt->fl |= MANT_LITERAL; + print_man_node(p, mt, nn, m); + if (nn->next) + p->flags |= TERMP_NOSPACE; + } + + return(0); +} + +/* ARGSUSED */ +static int +pre_B(DECL_ARGS) +{ + + term_fontrepl(p, TERMFONT_BOLD); + return(1); +} + +/* ARGSUSED */ +static int +pre_ft(DECL_ARGS) +{ + const char *cp; + + if (NULL == n->child) { + term_fontlast(p); + return(0); + } + + cp = n->child->string; + switch (*cp) { + case ('4'): + /* FALLTHROUGH */ + case ('3'): + /* FALLTHROUGH */ + case ('B'): + term_fontrepl(p, TERMFONT_BOLD); + break; + case ('2'): + /* FALLTHROUGH */ + case ('I'): + term_fontrepl(p, TERMFONT_UNDER); + break; + case ('P'): + term_fontlast(p); + break; + case ('1'): + /* FALLTHROUGH */ + case ('C'): + /* FALLTHROUGH */ + case ('R'): + term_fontrepl(p, TERMFONT_NONE); + break; + default: + break; + } + return(0); +} + +/* ARGSUSED */ +static int +pre_in(DECL_ARGS) +{ + int len, less; + size_t v; + const char *cp; + + term_newln(p); + + if (NULL == n->child) { + p->offset = mt->offset; + return(0); + } + + cp = n->child->string; + less = 0; + + if ('-' == *cp) + less = -1; + else if ('+' == *cp) + less = 1; + else + cp--; + + if ((len = a2width(p, ++cp)) < 0) + return(0); + + v = (size_t)len; + + if (less < 0) + p->offset -= p->offset > v ? v : p->offset; + else if (less > 0) + p->offset += v; + else + p->offset = v; + + return(0); +} + + +/* ARGSUSED */ +static int +pre_sp(DECL_ARGS) +{ + size_t i, len; + + switch (n->tok) { + case (MAN_br): + len = 0; + break; + default: + len = n->child ? a2height(p, n->child->string) : 1; + break; + } + + if (0 == len) + term_newln(p); + for (i = 0; i < len; i++) + term_vspace(p); + + return(0); +} + + +/* ARGSUSED */ +static int +pre_HP(DECL_ARGS) +{ + size_t len; + int ival; + const struct man_node *nn; + + switch (n->type) { + case (MAN_BLOCK): + print_bvspace(p, n); + return(1); + case (MAN_BODY): + p->flags |= TERMP_NOBREAK; + p->flags |= TERMP_TWOSPACE; + break; + default: + return(0); + } + + len = mt->lmargin; + ival = -1; + + /* Calculate offset. */ + + if (NULL != (nn = n->parent->head->child)) + if ((ival = a2width(p, nn->string)) >= 0) + len = (size_t)ival; + + if (0 == len) + len = term_len(p, 1); + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + + if (ival >= 0) + mt->lmargin = (size_t)ival; + + return(1); +} + + +/* ARGSUSED */ +static void +post_HP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + term_flushln(p); + break; + case (MAN_BODY): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->offset = mt->offset; + p->rmargin = p->maxrmargin; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_PP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->lmargin = term_len(p, INDENT); + print_bvspace(p, n); + break; + default: + p->offset = mt->offset; + break; + } + + return(MAN_HEAD != n->type); +} + + +/* ARGSUSED */ +static int +pre_IP(DECL_ARGS) +{ + const struct man_node *nn; + size_t len; + int savelit, ival; + + switch (n->type) { + case (MAN_BODY): + p->flags |= TERMP_NOLPAD; + p->flags |= TERMP_NOSPACE; + break; + case (MAN_HEAD): + p->flags |= TERMP_NOBREAK; + break; + case (MAN_BLOCK): + print_bvspace(p, n); + /* FALLTHROUGH */ + default: + return(1); + } + + len = mt->lmargin; + ival = -1; + + /* Calculate the offset from the optional second argument. */ + if (NULL != (nn = n->parent->head->child)) + if (NULL != (nn = nn->next)) + if ((ival = a2width(p, nn->string)) >= 0) + len = (size_t)ival; + + switch (n->type) { + case (MAN_HEAD): + /* Handle zero-width lengths. */ + if (0 == len) + len = term_len(p, 1); + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + if (ival < 0) + break; + + /* Set the saved left-margin. */ + mt->lmargin = (size_t)ival; + + savelit = MANT_LITERAL & mt->fl; + mt->fl &= ~MANT_LITERAL; + + if (n->child) + print_man_node(p, mt, n->child, m); + + if (savelit) + mt->fl |= MANT_LITERAL; + + return(0); + case (MAN_BODY): + p->offset = mt->offset + len; + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_IP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->rmargin = p->maxrmargin; + break; + case (MAN_BODY): + term_newln(p); + p->flags &= ~TERMP_NOLPAD; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_TP(DECL_ARGS) +{ + const struct man_node *nn; + size_t len; + int savelit, ival; + + switch (n->type) { + case (MAN_HEAD): + p->flags |= TERMP_NOBREAK; + break; + case (MAN_BODY): + p->flags |= TERMP_NOLPAD; + p->flags |= TERMP_NOSPACE; + break; + case (MAN_BLOCK): + print_bvspace(p, n); + /* FALLTHROUGH */ + default: + return(1); + } + + len = (size_t)mt->lmargin; + ival = -1; + + /* Calculate offset. */ + + if (NULL != (nn = n->parent->head->child)) { + while (nn && MAN_TEXT != nn->type) + nn = nn->next; + if (nn && nn->next) + if ((ival = a2width(p, nn->string)) >= 0) + len = (size_t)ival; + } + + switch (n->type) { + case (MAN_HEAD): + /* Handle zero-length properly. */ + if (0 == len) + len = term_len(p, 1); + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + + savelit = MANT_LITERAL & mt->fl; + mt->fl &= ~MANT_LITERAL; + + /* Don't print same-line elements. */ + for (nn = n->child; nn; nn = nn->next) + if (nn->line > n->line) + print_man_node(p, mt, nn, m); + + if (savelit) + mt->fl |= MANT_LITERAL; + + if (ival >= 0) + mt->lmargin = (size_t)ival; + + return(0); + case (MAN_BODY): + p->offset = mt->offset + len; + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_TP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->rmargin = p->maxrmargin; + break; + case (MAN_BODY): + term_newln(p); + p->flags &= ~TERMP_NOLPAD; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_SS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->lmargin = term_len(p, INDENT); + mt->offset = term_len(p, INDENT); + /* If following a prior empty `SS', no vspace. */ + if (n->prev && MAN_SS == n->prev->tok) + if (NULL == n->prev->body->child) + break; + if (NULL == n->prev) + break; + term_vspace(p); + break; + case (MAN_HEAD): + term_fontrepl(p, TERMFONT_BOLD); + p->offset = term_len(p, HALFINDENT); + break; + case (MAN_BODY): + p->offset = mt->offset; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_SS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_newln(p); + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_SH(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->lmargin = term_len(p, INDENT); + mt->offset = term_len(p, INDENT); + /* If following a prior empty `SH', no vspace. */ + if (n->prev && MAN_SH == n->prev->tok) + if (NULL == n->prev->body->child) + break; + /* If the first macro, no vspae. */ + if (NULL == n->prev) + break; + term_vspace(p); + break; + case (MAN_HEAD): + term_fontrepl(p, TERMFONT_BOLD); + p->offset = 0; + break; + case (MAN_BODY): + p->offset = mt->offset; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_SH(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_newln(p); + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_RS(DECL_ARGS) +{ + const struct man_node *nn; + int ival; + + switch (n->type) { + case (MAN_BLOCK): + term_newln(p); + return(1); + case (MAN_HEAD): + return(0); + default: + break; + } + + if (NULL == (nn = n->parent->head->child)) { + mt->offset = mt->lmargin + term_len(p, INDENT); + p->offset = mt->offset; + return(1); + } + + if ((ival = a2width(p, nn->string)) < 0) + return(1); + + mt->offset = term_len(p, INDENT) + (size_t)ival; + p->offset = mt->offset; + + return(1); +} + + +/* ARGSUSED */ +static void +post_RS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->offset = mt->lmargin = term_len(p, INDENT); + break; + case (MAN_HEAD): + break; + default: + term_newln(p); + p->offset = term_len(p, INDENT); + break; + } +} + + +static void +print_man_node(DECL_ARGS) +{ + size_t rm, rmax; + int c; + + c = 1; + + switch (n->type) { + case(MAN_TEXT): + if (0 == *n->string) { + term_vspace(p); + break; + } + + term_word(p, n->string); + + /* FIXME: this means that macro lines are munged! */ + + if (MANT_LITERAL & mt->fl) { + rm = p->rmargin; + rmax = p->maxrmargin; + p->rmargin = p->maxrmargin = TERM_MAXMARGIN; + p->flags |= TERMP_NOSPACE; + term_flushln(p); + p->flags &= ~TERMP_NOLPAD; + p->rmargin = rm; + p->maxrmargin = rmax; + } + break; + case (MAN_TBL): + if (TBL_SPAN_FIRST & n->span->flags) + term_newln(p); + term_tbl(p, n->span); + break; + default: + if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) + term_fontrepl(p, TERMFONT_NONE); + if (termacts[n->tok].pre) + c = (*termacts[n->tok].pre)(p, mt, n, m); + break; + } + + if (c && n->child) + print_man_nodelist(p, mt, n->child, m); + + switch (n->type) { + case (MAN_TEXT): + /* FALLTHROUGH */ + case (MAN_TBL): + break; + default: + if (termacts[n->tok].post) + (*termacts[n->tok].post)(p, mt, n, m); + if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) + term_fontrepl(p, TERMFONT_NONE); + break; + } + + if (MAN_EOS & n->flags) + p->flags |= TERMP_SENTENCE; +} + + +static void +print_man_nodelist(DECL_ARGS) +{ + + print_man_node(p, mt, n, m); + if ( ! n->next) + return; + print_man_nodelist(p, mt, n->next, m); +} + + +static void +print_man_foot(struct termp *p, const void *arg) +{ + char buf[DATESIZ]; + const struct man_meta *meta; + + meta = (const struct man_meta *)arg; + + term_fontrepl(p, TERMFONT_NONE); + + if (meta->rawdate) + strlcpy(buf, meta->rawdate, DATESIZ); + else + time2a(meta->date, buf, DATESIZ); + + term_vspace(p); + term_vspace(p); + term_vspace(p); + + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + p->rmargin = p->maxrmargin - term_strlen(p, buf); + p->offset = 0; + + /* term_strlen() can return zero. */ + if (p->rmargin == p->maxrmargin) + p->rmargin--; + + if (meta->source) + term_word(p, meta->source); + if (meta->source) + term_word(p, ""); + term_flushln(p); + + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + + term_word(p, buf); + term_flushln(p); +} + + +static void +print_man_head(struct termp *p, const void *arg) +{ + char buf[BUFSIZ], title[BUFSIZ]; + size_t buflen, titlen; + const struct man_meta *m; + + m = (const struct man_meta *)arg; + + /* + * Note that old groff would spit out some spaces before the + * header. We discontinue this strange behaviour, but at one + * point we did so here. + */ + + p->rmargin = p->maxrmargin; + + p->offset = 0; + buf[0] = title[0] = '\0'; + + if (m->vol) + strlcpy(buf, m->vol, BUFSIZ); + buflen = term_strlen(p, buf); + + snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec); + titlen = term_strlen(p, title); + + p->offset = 0; + p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ? + (p->maxrmargin - + term_strlen(p, buf) + term_len(p, 1)) / 2 : + p->maxrmargin - buflen; + p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; + + term_word(p, title); + term_flushln(p); + + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->offset + buflen + titlen < p->maxrmargin ? + p->maxrmargin - titlen : p->maxrmargin; + + term_word(p, buf); + term_flushln(p); + + p->flags &= ~TERMP_NOBREAK; + if (p->rmargin + titlen <= p->maxrmargin) { + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + term_word(p, title); + term_flushln(p); + } + + p->rmargin = p->maxrmargin; + p->offset = 0; + p->flags &= ~TERMP_NOSPACE; + + /* + * Groff likes to have some leading spaces before content. Well + * that's fine by me. + */ + + term_vspace(p); + term_vspace(p); + term_vspace(p); +} diff --git a/contrib/mdocml/man_validate.c b/contrib/mdocml/man_validate.c new file mode 100644 index 0000000000..bcfcbacfa4 --- /dev/null +++ b/contrib/mdocml/man_validate.c @@ -0,0 +1,577 @@ +/* $Id: man_validate.c,v 1.57 2011/01/01 12:59:17 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libman.h" +#include "libmandoc.h" + +#define CHKARGS struct man *m, struct man_node *n + +typedef int (*v_check)(CHKARGS); + +struct man_valid { + v_check *pres; + v_check *posts; +}; + +static int check_bline(CHKARGS); +static int check_eq0(CHKARGS); +static int check_ft(CHKARGS); +static int check_le1(CHKARGS); +static int check_ge2(CHKARGS); +static int check_le5(CHKARGS); +static int check_par(CHKARGS); +static int check_part(CHKARGS); +static int check_root(CHKARGS); +static int check_sec(CHKARGS); +static int check_text(CHKARGS); +static int check_title(CHKARGS); + +static int post_AT(CHKARGS); +static int post_fi(CHKARGS); +static int post_nf(CHKARGS); +static int post_TH(CHKARGS); +static int post_UC(CHKARGS); + +static v_check posts_at[] = { post_AT, NULL }; +static v_check posts_eq0[] = { check_eq0, NULL }; +static v_check posts_fi[] = { check_eq0, post_fi, NULL }; +static v_check posts_le1[] = { check_le1, NULL }; +static v_check posts_ft[] = { check_ft, NULL }; +static v_check posts_nf[] = { check_eq0, post_nf, NULL }; +static v_check posts_par[] = { check_par, NULL }; +static v_check posts_part[] = { check_part, NULL }; +static v_check posts_sec[] = { check_sec, NULL }; +static v_check posts_th[] = { check_ge2, check_le5, check_title, post_TH, NULL }; +static v_check posts_uc[] = { post_UC, NULL }; +static v_check pres_bline[] = { check_bline, NULL }; + + +static const struct man_valid man_valids[MAN_MAX] = { + { NULL, posts_eq0 }, /* br */ + { pres_bline, posts_th }, /* TH */ + { pres_bline, posts_sec }, /* SH */ + { pres_bline, posts_sec }, /* SS */ + { pres_bline, posts_par }, /* TP */ + { pres_bline, posts_par }, /* LP */ + { pres_bline, posts_par }, /* PP */ + { pres_bline, posts_par }, /* P */ + { pres_bline, posts_par }, /* IP */ + { pres_bline, posts_par }, /* HP */ + { NULL, NULL }, /* SM */ + { NULL, NULL }, /* SB */ + { NULL, NULL }, /* BI */ + { NULL, NULL }, /* IB */ + { NULL, NULL }, /* BR */ + { NULL, NULL }, /* RB */ + { NULL, NULL }, /* R */ + { NULL, NULL }, /* B */ + { NULL, NULL }, /* I */ + { NULL, NULL }, /* IR */ + { NULL, NULL }, /* RI */ + { NULL, posts_eq0 }, /* na */ /* FIXME: should warn only. */ + { NULL, posts_le1 }, /* sp */ /* FIXME: should warn only. */ + { pres_bline, posts_nf }, /* nf */ + { pres_bline, posts_fi }, /* fi */ + { NULL, NULL }, /* RE */ + { NULL, posts_part }, /* RS */ + { NULL, NULL }, /* DT */ + { NULL, posts_uc }, /* UC */ + { NULL, NULL }, /* PD */ + { NULL, posts_at }, /* AT */ + { NULL, NULL }, /* in */ + { NULL, posts_ft }, /* ft */ +}; + + +int +man_valid_pre(struct man *m, struct man_node *n) +{ + v_check *cp; + + switch (n->type) { + case (MAN_TEXT): + /* FALLTHROUGH */ + case (MAN_ROOT): + /* FALLTHROUGH */ + case (MAN_TBL): + return(1); + default: + break; + } + + if (NULL == (cp = man_valids[n->tok].pres)) + return(1); + for ( ; *cp; cp++) + if ( ! (*cp)(m, n)) + return(0); + return(1); +} + + +int +man_valid_post(struct man *m) +{ + v_check *cp; + + if (MAN_VALID & m->last->flags) + return(1); + m->last->flags |= MAN_VALID; + + switch (m->last->type) { + case (MAN_TEXT): + return(check_text(m, m->last)); + case (MAN_ROOT): + return(check_root(m, m->last)); + case (MAN_TBL): + return(1); + default: + break; + } + + if (NULL == (cp = man_valids[m->last->tok].posts)) + return(1); + for ( ; *cp; cp++) + if ( ! (*cp)(m, m->last)) + return(0); + + return(1); +} + + +static int +check_root(CHKARGS) +{ + + if (MAN_BLINE & m->flags) + man_nmsg(m, n, MANDOCERR_SCOPEEXIT); + else if (MAN_ELINE & m->flags) + man_nmsg(m, n, MANDOCERR_SCOPEEXIT); + + m->flags &= ~MAN_BLINE; + m->flags &= ~MAN_ELINE; + + if (NULL == m->first->child) { + man_nmsg(m, n, MANDOCERR_NODOCBODY); + return(0); + } else if (NULL == m->meta.title) { + man_nmsg(m, n, MANDOCERR_NOTITLE); + + /* + * If a title hasn't been set, do so now (by + * implication, date and section also aren't set). + */ + + m->meta.title = mandoc_strdup("unknown"); + m->meta.date = time(NULL); + m->meta.msec = mandoc_strdup("1"); + } + + return(1); +} + + +static int +check_title(CHKARGS) +{ + const char *p; + + assert(n->child); + /* FIXME: is this sufficient? */ + if ('\0' == *n->child->string) { + man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT); + return(0); + } + + for (p = n->child->string; '\0' != *p; p++) + /* Only warn about this once... */ + if (isalpha((u_char)*p) && ! isupper((u_char)*p)) { + man_nmsg(m, n, MANDOCERR_UPPERCASE); + break; + } + + return(1); +} + + +static int +check_text(CHKARGS) +{ + char *p; + int pos, c; + size_t sz; + + for (p = n->string, pos = n->pos + 1; *p; p++, pos++) { + sz = strcspn(p, "\t\\"); + p += (int)sz; + + if ('\0' == *p) + break; + + pos += (int)sz; + + if ('\t' == *p) { + if (MAN_LITERAL & m->flags) + continue; + if (man_pmsg(m, n->line, pos, MANDOCERR_BADTAB)) + continue; + return(0); + } + + /* Check the special character. */ + + c = mandoc_special(p); + if (c) { + p += c - 1; + pos += c - 1; + } else + man_pmsg(m, n->line, pos, MANDOCERR_BADESCAPE); + } + + return(1); +} + + +#define INEQ_DEFINE(x, ineq, name) \ +static int \ +check_##name(CHKARGS) \ +{ \ + if (n->nchild ineq (x)) \ + return(1); \ + man_vmsg(m, MANDOCERR_SYNTARGCOUNT, n->line, n->pos, \ + "line arguments %s %d (have %d)", \ + #ineq, (x), n->nchild); \ + return(0); \ +} + +INEQ_DEFINE(0, ==, eq0) +INEQ_DEFINE(1, <=, le1) +INEQ_DEFINE(2, >=, ge2) +INEQ_DEFINE(5, <=, le5) + +static int +check_ft(CHKARGS) +{ + char *cp; + int ok; + + if (0 == n->nchild) + return(1); + + ok = 0; + cp = n->child->string; + switch (*cp) { + case ('1'): + /* FALLTHROUGH */ + case ('2'): + /* FALLTHROUGH */ + case ('3'): + /* FALLTHROUGH */ + case ('4'): + /* FALLTHROUGH */ + case ('I'): + /* FALLTHROUGH */ + case ('P'): + /* FALLTHROUGH */ + case ('R'): + if ('\0' == cp[1]) + ok = 1; + break; + case ('B'): + if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2])) + ok = 1; + break; + case ('C'): + if ('W' == cp[1] && '\0' == cp[2]) + ok = 1; + break; + default: + break; + } + + if (0 == ok) { + man_vmsg(m, MANDOCERR_BADFONT, + n->line, n->pos, "%s", cp); + *cp = '\0'; + } + + if (1 < n->nchild) + man_vmsg(m, MANDOCERR_ARGCOUNT, n->line, n->pos, + "want one child (have %d)", n->nchild); + + return(1); +} + +static int +check_sec(CHKARGS) +{ + + if (MAN_HEAD == n->type && 0 == n->nchild) { + man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT); + return(0); + } else if (MAN_BODY == n->type && 0 == n->nchild) + man_nmsg(m, n, MANDOCERR_NOBODY); + + return(1); +} + + +static int +check_part(CHKARGS) +{ + + if (MAN_BODY == n->type && 0 == n->nchild) + man_nmsg(m, n, MANDOCERR_NOBODY); + + return(1); +} + + +static int +check_par(CHKARGS) +{ + + if (MAN_BODY == n->type) + switch (n->tok) { + case (MAN_IP): + /* FALLTHROUGH */ + case (MAN_HP): + /* FALLTHROUGH */ + case (MAN_TP): + /* Body-less lists are ok. */ + break; + default: + if (0 == n->nchild) + man_nmsg(m, n, MANDOCERR_NOBODY); + break; + } + if (MAN_HEAD == n->type) + switch (n->tok) { + case (MAN_PP): + /* FALLTHROUGH */ + case (MAN_P): + /* FALLTHROUGH */ + case (MAN_LP): + if (n->nchild) + man_nmsg(m, n, MANDOCERR_ARGSLOST); + break; + default: + break; + } + + return(1); +} + + +static int +check_bline(CHKARGS) +{ + + assert( ! (MAN_ELINE & m->flags)); + if (MAN_BLINE & m->flags) { + man_nmsg(m, n, MANDOCERR_SYNTLINESCOPE); + return(0); + } + + return(1); +} + +static int +post_TH(CHKARGS) +{ + + if (m->meta.title) + free(m->meta.title); + if (m->meta.vol) + free(m->meta.vol); + if (m->meta.source) + free(m->meta.source); + if (m->meta.msec) + free(m->meta.msec); + if (m->meta.rawdate) + free(m->meta.rawdate); + + m->meta.title = m->meta.vol = m->meta.rawdate = + m->meta.msec = m->meta.source = NULL; + m->meta.date = 0; + + /* ->TITLE<- MSEC DATE SOURCE VOL */ + + n = n->child; + assert(n); + m->meta.title = mandoc_strdup(n->string); + + /* TITLE ->MSEC<- DATE SOURCE VOL */ + + n = n->next; + assert(n); + m->meta.msec = mandoc_strdup(n->string); + + /* TITLE MSEC ->DATE<- SOURCE VOL */ + + /* + * Try to parse the date. If this works, stash the epoch (this + * is optimal because we can reformat it in the canonical form). + * If it doesn't parse, isn't specified at all, or is an empty + * string, then use the current date. + */ + + n = n->next; + if (n && n->string && *n->string) { + m->meta.date = mandoc_a2time + (MTIME_ISO_8601, n->string); + if (0 == m->meta.date) { + man_nmsg(m, n, MANDOCERR_BADDATE); + m->meta.rawdate = mandoc_strdup(n->string); + } + } else + m->meta.date = time(NULL); + + /* TITLE MSEC DATE ->SOURCE<- VOL */ + + if (n && (n = n->next)) + m->meta.source = mandoc_strdup(n->string); + + /* TITLE MSEC DATE SOURCE ->VOL<- */ + + if (n && (n = n->next)) + m->meta.vol = mandoc_strdup(n->string); + + /* + * Remove the `TH' node after we've processed it for our + * meta-data. + */ + man_node_delete(m, m->last); + return(1); +} + +static int +post_nf(CHKARGS) +{ + + if (MAN_LITERAL & m->flags) + man_nmsg(m, n, MANDOCERR_SCOPEREP); + + m->flags |= MAN_LITERAL; + return(1); +} + +static int +post_fi(CHKARGS) +{ + + if ( ! (MAN_LITERAL & m->flags)) + man_nmsg(m, n, MANDOCERR_NOSCOPE); + + m->flags &= ~MAN_LITERAL; + return(1); +} + +static int +post_UC(CHKARGS) +{ + static const char * const bsd_versions[] = { + "3rd Berkeley Distribution", + "4th Berkeley Distribution", + "4.2 Berkeley Distribution", + "4.3 Berkeley Distribution", + "4.4 Berkeley Distribution", + }; + + const char *p, *s; + + n = n->child; + n = m->last->child; + + if (NULL == n || MAN_TEXT != n->type) + p = bsd_versions[0]; + else { + s = n->string; + if (0 == strcmp(s, "3")) + p = bsd_versions[0]; + else if (0 == strcmp(s, "4")) + p = bsd_versions[1]; + else if (0 == strcmp(s, "5")) + p = bsd_versions[2]; + else if (0 == strcmp(s, "6")) + p = bsd_versions[3]; + else if (0 == strcmp(s, "7")) + p = bsd_versions[4]; + else + p = bsd_versions[0]; + } + + if (m->meta.source) + free(m->meta.source); + + m->meta.source = mandoc_strdup(p); + return(1); +} + +static int +post_AT(CHKARGS) +{ + static const char * const unix_versions[] = { + "7th Edition", + "System III", + "System V", + "System V Release 2", + }; + + const char *p, *s; + struct man_node *nn; + + n = n->child; + + if (NULL == n || MAN_TEXT != n->type) + p = unix_versions[0]; + else { + s = n->string; + if (0 == strcmp(s, "3")) + p = unix_versions[0]; + else if (0 == strcmp(s, "4")) + p = unix_versions[1]; + else if (0 == strcmp(s, "5")) { + nn = n->next; + if (nn && MAN_TEXT == nn->type && nn->string[0]) + p = unix_versions[3]; + else + p = unix_versions[2]; + } else + p = unix_versions[0]; + } + + if (m->meta.source) + free(m->meta.source); + + m->meta.source = mandoc_strdup(p); + return(1); +} diff --git a/contrib/mdocml/mandoc.1 b/contrib/mdocml/mandoc.1 new file mode 100644 index 0000000000..843f3d738c --- /dev/null +++ b/contrib/mdocml/mandoc.1 @@ -0,0 +1,569 @@ +.\" $Id: mandoc.1,v 1.84 2011/01/04 23:32:21 kristaps 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. +.\" +.Dd $Mdocdate: January 4 2011 $ +.Dt MANDOC 1 +.Os +.Sh NAME +.Nm mandoc +.Nd format and display UNIX manuals +.Sh SYNOPSIS +.Nm mandoc +.Op Fl V +.Op Fl m Ns Ar format +.Op Fl O Ns Ar option +.Op Fl T Ns Ar output +.Op Fl W Ns Ar level +.Op Ar file... +.Sh DESCRIPTION +The +.Nm +utility formats +.Ux +manual pages for display. +The arguments are as follows: +.Bl -tag -width Ds +.It Fl m Ns Ar format +Input format. +See +.Sx Input Formats +for available formats. +Defaults to +.Fl m Ns Cm andoc . +.It Fl O Ns Ar option +Comma-separated output options. +.It Fl T Ns Ar output +Output format. +See +.Sx Output Formats +for available formats. +Defaults to +.Fl T Ns Cm ascii . +.It Fl V +Print version and exit. +.It Fl W Ns Ar level +Specify the minimum message +.Ar level +to be reported on the standard error output and to affect the exit status. +The +.Ar level +can be +.Cm warning , +.Cm error , +or +.Cm fatal . +The default is +.Fl W Ns Cm fatal ; +.Fl W Ns Cm all +is an alias for +.Fl W Ns Cm warning . +See +.Sx EXIT STATUS +and +.Sx DIAGNOSTICS +for details. +.Pp +The special option +.Fl W Ns Cm stop +tells +.Nm +to exit after parsing a file that causes warnings or errors of at least +the requested level. +No formatted output will be produced from that file. +If both a +.Ar level +and +.Cm stop +are requested, they can be joined with a comma, for example +.Fl W Ns Cm error , Ns Cm stop . +.It Ar file +Read input from zero or more files. +If unspecified, reads from stdin. +If multiple files are specified, +.Nm +will halt with the first failed parse. +.El +.Pp +By default, +.Nm +reads +.Xr mdoc 7 +or +.Xr man 7 +text from stdin, implying +.Fl m Ns Cm andoc , +and produces +.Fl T Ns Cm ascii +output. +.Ss Input Formats +The +.Nm +utility accepts +.Xr mdoc 7 +and +.Xr man 7 +input with +.Fl m Ns Cm doc +and +.Fl m Ns Cm an , +respectively. +The +.Xr mdoc 7 +format is +.Em strongly +recommended; +.Xr man 7 +should only be used for legacy manuals. +.Pp +A third option, +.Fl m Ns Cm andoc , +which is also the default, determines encoding on-the-fly: if the first +non-comment macro is +.Sq \&Dd +or +.Sq \&Dt , +the +.Xr mdoc 7 +parser is used; otherwise, the +.Xr man 7 +parser is used. +.Pp +If multiple +files are specified with +.Fl m Ns Cm andoc , +each has its file-type determined this way. +If multiple files are +specified and +.Fl m Ns Cm doc +or +.Fl m Ns Cm an +is specified, then this format is used exclusively. +.Ss Output Formats +The +.Nm +utility accepts the following +.Fl T +arguments, which correspond to output modes: +.Bl -tag -width Ds +.It Fl T Ns Cm ascii +Produce 7-bit ASCII output. +This is the default. +See +.Sx ASCII Output . +.It Fl T Ns Cm html +Produce strict CSS1/HTML-4.01 output. +See +.Sx HTML Output . +.It Fl T Ns Cm lint +Parse only: produce no output. +Implies +.Fl W Ns Cm warning . +.It Fl T Ns Cm pdf +Produce PDF output. +See +.Sx PDF Output . +.It Fl T Ns Cm ps +Produce PostScript output. +See +.Sx PostScript Output . +.It Fl T Ns Cm tree +Produce an indented parse tree. +.It Fl T Ns Cm xhtml +Produce strict CSS1/XHTML-1.0 output. +See +.Sx XHTML Output . +.El +.Pp +If multiple input files are specified, these will be processed by the +corresponding filter in-order. +.Ss ASCII Output +Output produced by +.Fl T Ns Cm ascii , +which is the default, is rendered in standard 7-bit ASCII documented in +.Xr ascii 7 . +.Pp +Font styles are applied by using back-spaced encoding such that an +underlined character +.Sq c +is rendered as +.Sq _ Ns \e[bs] Ns c , +where +.Sq \e[bs] +is the back-space character number 8. +Emboldened characters are rendered as +.Sq c Ns \e[bs] Ns c . +.Pp +The special characters documented in +.Xr mandoc_char 7 +are rendered best-effort in an ASCII equivalent. +.Pp +Output width is limited to 78 visible columns unless literal input lines +exceed this limit. +.Pp +The following +.Fl O +arguments are accepted: +.Bl -tag -width Ds +.It Cm width Ns = Ns Ar width +The output width is set to +.Ar width , +which will normalise to \(>=60. +.El +.Ss HTML Output +Output produced by +.Fl T Ns Cm html +conforms to HTML-4.01 strict. +.Pp +The +.Pa example.style.css +file documents style-sheet classes available for customising output. +If a style-sheet is not specified with +.Fl O Ns Ar style , +.Fl T Ns Cm html +defaults to simple output readable in any graphical or text-based web +browser. +.Pp +Special characters are rendered in decimal-encoded UTF-8. +.Pp +The following +.Fl O +arguments are accepted: +.Bl -tag -width Ds +.It Cm includes Ns = Ns Ar fmt +The string +.Ar fmt , +for example, +.Ar ../src/%I.html , +is used as a template for linked header files (usually via the +.Sq \&In +macro). +Instances of +.Sq \&%I +are replaced with the include filename. +The default is not to present a +hyperlink. +.It Cm man Ns = Ns Ar fmt +The string +.Ar fmt , +for example, +.Ar ../html%S/%N.%S.html , +is used as a template for linked manuals (usually via the +.Sq \&Xr +macro). +Instances of +.Sq \&%N +and +.Sq %S +are replaced with the linked manual's name and section, respectively. +If no section is included, section 1 is assumed. +The default is not to +present a hyperlink. +.It Cm style Ns = Ns Ar style.css +The file +.Ar style.css +is used for an external style-sheet. +This must be a valid absolute or +relative URI. +.El +.Ss PostScript Output +PostScript +.Qq Adobe-3.0 +Level-2 pages may be generated by +.Fl T Ns Cm ps . +Output pages default to letter sized and are rendered in the Times font +family, 11-point. +Margins are calculated as 1/9 the page length and width. +Line-height is 1.4m. +.Pp +Special characters are rendered as in +.Sx ASCII Output . +.Pp +The following +.Fl O +arguments are accepted: +.Bl -tag -width Ds +.It Cm paper Ns = Ns Ar name +The paper size +.Ar name +may be one of +.Ar a3 , +.Ar a4 , +.Ar a5 , +.Ar legal , +or +.Ar letter . +You may also manually specify dimensions as +.Ar NNxNN , +width by height in millimetres. +If an unknown value is encountered, +.Ar letter +is used. +.El +.Ss PDF Output +PDF-1.1 output may be generated by +.Fl T Ns Cm pdf . +See +.Sx PostScript Output +for +.Fl O +arguments and defaults. +.Ss XHTML Output +Output produced by +.Fl T Ns Cm xhtml +conforms to XHTML-1.0 strict. +.Pp +See +.Sx HTML Output +for details; beyond generating XHTML tags instead of HTML tags, these +output modes are identical. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values, controlled by the message +.Ar level +associated with the +.Fl W +option: +.Pp +.Bl -tag -width Ds -compact +.It 0 +No warnings or errors occurred, or those that did were ignored because +they were lower than the requested +.Ar level . +.It 2 +At least one warning occurred, but no error, and +.Fl W Ns Cm warning +was specified. +.It 3 +At least one parsing error occurred, but no fatal error, and +.Fl W Ns Cm error +or +.Fl W Ns Cm warning +was specified. +.It 4 +A fatal parsing error occurred. +.It 5 +Invalid command line arguments were specified. +No input files have been read. +.It 6 +An operating system error occurred, for example memory exhaustion or an +error accessing input files. +Such errors cause +.Nm +to exit at once, possibly in the middle of parsing or formatting a file. +.El +.Pp +Note that selecting +.Fl T Ns Cm lint +output mode implies +.Fl W Ns Cm warning . +.Sh EXAMPLES +To page manuals to the terminal: +.Pp +.Dl $ mandoc \-Wall,stop mandoc.1 2\*(Gt&1 | less +.Dl $ mandoc mandoc.1 mdoc.3 mdoc.7 | less +.Pp +To produce HTML manuals with +.Ar style.css +as the style-sheet: +.Pp +.Dl $ mandoc \-Thtml -Ostyle=style.css mdoc.7 \*(Gt mdoc.7.html +.Pp +To check over a large set of manuals: +.Pp +.Dl $ mandoc \-Tlint `find /usr/src -name \e*\e.[1-9]` +.Pp +To produce a series of PostScript manuals for A4 paper: +.Pp +.Dl $ mandoc \-Tps \-Opaper=a4 mdoc.7 man.7 \*(Gt manuals.ps +.Sh DIAGNOSTICS +Standard error messages reporting parsing errors are prefixed by +.Pp +.Sm off +.D1 Ar file : line : column : \ level : +.Sm on +.Pp +where the fields have the following meanings: +.Bl -tag -width "column" +.It Ar file +The name of the input file causing the message. +.It Ar line +The line number in that input file. +Line numbering starts at 1. +.It Ar column +The column number in that input file. +Column numbering starts at 1. +If the issue is caused by a word, the column number usually +points to the first character of the word. +.It Ar level +The message level, printed in capital letters. +.El +.Pp +Message levels have the following meanings: +.Bl -tag -width "warning" +.It Cm fatal +The parser is unable to parse a given input file at all. +No formatted output is produced from that input file. +.It Cm error +An input file contains syntax that cannot be safely interpreted, +either because it is invalid or because +.Nm +does not implement it yet. +By discarding part of the input or inserting missing tokens, +the parser is able to continue, and the error does not prevent +generation of formatted output, but typically, preparing that +output involves information loss, broken document structure +or unintended formatting. +.It Cm warning +An input file uses obsolete, discouraged or non-portable syntax. +All the same, the meaning of the input is unambiguous and a correct +rendering can be produced. +Documents causing warnings may render poorly when using other +formatting tools instead of +.Nm . +.El +.Pp +Messages of the +.Cm warning +and +.Cm error +levels are hidden unless their level, or a lower level, is requested using a +.Fl W +option or +.Fl T Ns Cm lint +output mode. +.Pp +The +.Nm +utility may also print messages related to invalid command line arguments +or operating system errors, for example when memory is exhausted or +input files cannot be read. +Such messages do not carry the prefix described above. +.Sh COMPATIBILITY +This section summarises +.Nm +compatibility with GNU troff. +Each input and output format is separately noted. +.Ss ASCII Compatibility +.Bl -bullet -compact +.It +The +.Sq \&Bd \-literal +and +.Sq \&Bd \-unfilled +macros of +.Xr mdoc 7 +in +.Fl T Ns Cm ascii +are synonyms, as are \-filled and \-ragged. +.It +In GNU troff, the +.Sq \&Pa +.Xr mdoc 7 +macro does not underline when scoped under an +.Sq \&It +in the FILES section. +This behaves correctly in +.Nm . +.It +A list or display following the +.Sq \&Ss +.Xr mdoc 7 +macro in +.Fl T Ns Cm ascii +does not assert a prior vertical break, just as it doesn't with +.Sq \&Sh . +.It +The +.Sq \&na +.Xr man 7 +macro in +.Fl T Ns Cm ascii +has no effect. +.It +Words aren't hyphenated. +.It +Sentences are unilaterally monospaced. +.El +.Ss HTML/XHTML Compatibility +.Bl -bullet -compact +.It +The +.Sq \efP +escape will revert the font to the previous +.Sq \ef +escape, not to the last rendered decoration, which is now dictated by +CSS instead of hard-coded. +It also will not span past the current scope, +for the same reason. +Note that in +.Sx ASCII Output +mode, this will work fine. +.It +The +.Xr mdoc 7 +.Sq \&Bl \-hang +and +.Sq \&Bl \-tag +list types render similarly (no break following overreached left-hand +side) due to the expressive constraints of HTML. +.It +The +.Xr man 7 +.Sq IP +and +.Sq TP +lists render similarly. +.El +.Sh SEE ALSO +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 , +.Xr tbl 7 +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . +.Sh CAVEATS +In +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml , +the maximum size of an element attribute is determined by +.Dv BUFSIZ , +which is usually 1024 bytes. +Be aware of this when setting long link +formats such as +.Fl O Ns Cm style Ns = Ns Ar really/long/link . +.Pp +Nesting elements within next-line element scopes of +.Fl m Ns Cm an , +such as +.Sq br +within an empty +.Sq B , +will confuse +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml +and cause them to forget the formatting of the prior next-line scope. +.Pp +The +.Sq \(aq +control character is an alias for the standard macro control character +and does not emit a line-break as stipulated in GNU troff. diff --git a/contrib/mdocml/mandoc.c b/contrib/mdocml/mandoc.c new file mode 100644 index 0000000000..4faa5a78fa --- /dev/null +++ b/contrib/mdocml/mandoc.c @@ -0,0 +1,487 @@ +/* $Id: mandoc.c,v 1.36 2011/01/03 22:42:37 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2011 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmandoc.h" + +static int a2time(time_t *, const char *, const char *); + + +int +mandoc_special(char *p) +{ + int len, i; + char term; + char *sv; + + len = 0; + term = '\0'; + sv = p; + + assert('\\' == *p); + p++; + + switch (*p++) { +#if 0 + case ('Z'): + /* FALLTHROUGH */ + case ('X'): + /* FALLTHROUGH */ + case ('x'): + /* FALLTHROUGH */ + case ('S'): + /* FALLTHROUGH */ + case ('R'): + /* FALLTHROUGH */ + case ('N'): + /* FALLTHROUGH */ + case ('l'): + /* FALLTHROUGH */ + case ('L'): + /* FALLTHROUGH */ + case ('H'): + /* FALLTHROUGH */ + case ('h'): + /* FALLTHROUGH */ + case ('D'): + /* FALLTHROUGH */ + case ('C'): + /* FALLTHROUGH */ + case ('b'): + /* FALLTHROUGH */ + case ('B'): + /* FALLTHROUGH */ + case ('a'): + /* FALLTHROUGH */ + case ('A'): + if (*p++ != '\'') + return(0); + term = '\''; + break; +#endif + case ('h'): + /* FALLTHROUGH */ + case ('v'): + /* FALLTHROUGH */ + case ('s'): + if (ASCII_HYPH == *p) + *p = '-'; + + i = 0; + if ('+' == *p || '-' == *p) { + p++; + i = 1; + } + + switch (*p++) { + case ('('): + len = 2; + break; + case ('['): + term = ']'; + break; + case ('\''): + term = '\''; + break; + case ('0'): + i = 1; + /* FALLTHROUGH */ + default: + len = 1; + p--; + break; + } + + if (ASCII_HYPH == *p) + *p = '-'; + if ('+' == *p || '-' == *p) { + if (i) + return(0); + p++; + } + + /* Handle embedded numerical subexp or escape. */ + + if ('(' == *p) { + while (*p && ')' != *p) + if ('\\' == *p++) { + i = mandoc_special(--p); + if (0 == i) + return(0); + p += i; + } + + if (')' == *p++) + break; + + return(0); + } else if ('\\' == *p) { + if (0 == (i = mandoc_special(p))) + return(0); + p += i; + } + + break; +#if 0 + case ('Y'): + /* FALLTHROUGH */ + case ('V'): + /* FALLTHROUGH */ + case ('$'): + /* FALLTHROUGH */ + case ('n'): + /* FALLTHROUGH */ +#endif + case ('k'): + /* FALLTHROUGH */ + case ('M'): + /* FALLTHROUGH */ + case ('m'): + /* FALLTHROUGH */ + case ('f'): + /* FALLTHROUGH */ + case ('F'): + /* FALLTHROUGH */ + case ('*'): + switch (*p++) { + case ('('): + len = 2; + break; + case ('['): + term = ']'; + break; + default: + len = 1; + p--; + break; + } + break; + case ('('): + len = 2; + break; + case ('['): + term = ']'; + break; + case ('z'): + len = 1; + if ('\\' == *p) { + if (0 == (i = mandoc_special(p))) + return(0); + p += i; + return(*p ? (int)(p - sv) : 0); + } + break; + case ('o'): + /* FALLTHROUGH */ + case ('w'): + if ('\'' == *p++) { + term = '\''; + break; + } + /* FALLTHROUGH */ + default: + len = 1; + p--; + break; + } + + if (term) { + for ( ; *p && term != *p; p++) + if (ASCII_HYPH == *p) + *p = '-'; + return(*p ? (int)(p - sv) : 0); + } + + for (i = 0; *p && i < len; i++, p++) + if (ASCII_HYPH == *p) + *p = '-'; + return(i == len ? (int)(p - sv) : 0); +} + + +void * +mandoc_calloc(size_t num, size_t size) +{ + void *ptr; + + ptr = calloc(num, size); + if (NULL == ptr) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(ptr); +} + + +void * +mandoc_malloc(size_t size) +{ + void *ptr; + + ptr = malloc(size); + if (NULL == ptr) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(ptr); +} + + +void * +mandoc_realloc(void *ptr, size_t size) +{ + + ptr = realloc(ptr, size); + if (NULL == ptr) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(ptr); +} + + +char * +mandoc_strdup(const char *ptr) +{ + char *p; + + p = strdup(ptr); + if (NULL == p) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(p); +} + +/* + * 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. + * Null-terminate the argument in place. + * Collapse pairs of quotes inside quoted arguments. + * Advance the argument pointer to the next argument, + * or to the null byte terminating the argument line. + */ +char * +mandoc_getarg(char **cpp, mandocmsg msg, void *data, int ln, int *pos) +{ + char *start, *cp; + int quoted, pairs, white; + + /* Quoting can only start with a new word. */ + start = *cpp; + if ('"' == *start) { + quoted = 1; + start++; + } else + quoted = 0; + + pairs = 0; + white = 0; + for (cp = start; '\0' != *cp; cp++) { + /* Move left after quoted quotes and escaped backslashes. */ + if (pairs) + cp[-pairs] = cp[0]; + if ('\\' == cp[0]) { + if ('\\' == cp[1]) { + /* Poor man's copy mode. */ + pairs++; + cp++; + } else if (0 == quoted && ' ' == cp[1]) + /* Skip escaped blanks. */ + cp++; + } 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 && msg) + (*msg)(MANDOCERR_BADQUOTE, data, ln, *pos, NULL); + + /* Null-terminate this argument and move to the next one. */ + if (pairs) + cp[-pairs] = '\0'; + if ('\0' != *cp) { + *cp++ = '\0'; + while (' ' == *cp) + cp++; + } + *pos += (cp - start) + (quoted ? 1 : 0); + *cpp = cp; + + if ('\0' == *cp && msg && (white || ' ' == cp[-1])) + (*msg)(MANDOCERR_EOLNSPACE, data, ln, *pos, NULL); + + return(start); +} + + +static int +a2time(time_t *t, const char *fmt, const char *p) +{ + struct tm tm; + char *pp; + + memset(&tm, 0, sizeof(struct tm)); + + pp = strptime(p, fmt, &tm); + if (NULL != pp && '\0' == *pp) { + *t = mktime(&tm); + return(1); + } + + return(0); +} + + +/* + * Convert from a manual date string (see mdoc(7) and man(7)) into a + * date according to the stipulated date type. + */ +time_t +mandoc_a2time(int flags, const char *p) +{ + time_t t; + + if (MTIME_MDOCDATE & flags) { + if (0 == strcmp(p, "$" "Mdocdate$")) + return(time(NULL)); + if (a2time(&t, "$" "Mdocdate: %b %d %Y $", p)) + return(t); + } + + if (MTIME_CANONICAL & flags || MTIME_REDUCED & flags) + if (a2time(&t, "%b %d, %Y", p)) + return(t); + + if (MTIME_ISO_8601 & flags) + if (a2time(&t, "%Y-%m-%d", p)) + return(t); + + if (MTIME_REDUCED & flags) { + if (a2time(&t, "%d, %Y", p)) + return(t); + if (a2time(&t, "%Y", p)) + return(t); + } + + return(0); +} + + +int +mandoc_eos(const char *p, size_t sz, int enclosed) +{ + const char *q; + int found; + + if (0 == sz) + return(0); + + /* + * End-of-sentence recognition must include situations where + * some symbols, such as `)', allow prior EOS punctuation to + * propogate outward. + */ + + found = 0; + for (q = p + (int)sz - 1; q >= p; q--) { + switch (*q) { + case ('\"'): + /* FALLTHROUGH */ + case ('\''): + /* FALLTHROUGH */ + case (']'): + /* FALLTHROUGH */ + case (')'): + if (0 == found) + enclosed = 1; + break; + case ('.'): + /* FALLTHROUGH */ + case ('!'): + /* FALLTHROUGH */ + case ('?'): + found = 1; + break; + default: + return(found && (!enclosed || isalnum((unsigned char)*q))); + } + } + + return(found && !enclosed); +} + + +int +mandoc_hyph(const char *start, const char *c) +{ + + /* + * Choose whether to break at a hyphenated character. We only + * do this if it's free-standing within a word. + */ + + /* Skip first/last character of buffer. */ + if (c == start || '\0' == *(c + 1)) + return(0); + /* Skip first/last character of word. */ + if ('\t' == *(c + 1) || '\t' == *(c - 1)) + return(0); + if (' ' == *(c + 1) || ' ' == *(c - 1)) + return(0); + /* Skip double invocations. */ + if ('-' == *(c + 1) || '-' == *(c - 1)) + return(0); + /* Skip escapes. */ + if ('\\' == *(c - 1)) + return(0); + + return(1); +} diff --git a/contrib/mdocml/mandoc.h b/contrib/mdocml/mandoc.h new file mode 100644 index 0000000000..9dd448e6dd --- /dev/null +++ b/contrib/mdocml/mandoc.h @@ -0,0 +1,315 @@ +/* $Id: mandoc.h,v 1.49 2011/01/06 13:45:47 kristaps Exp $ */ +/* + * 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 AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MANDOC_H +#define MANDOC_H + +#define ASCII_NBRSP 31 /* non-breaking space */ +#define ASCII_HYPH 30 /* breakable hyphen */ + +/* + * Status level. This refers to both internal status (i.e., whilst + * running, when warnings/errors are reported) and an indicator of a + * threshold of when to halt (when said internal state exceeds the + * threshold). + */ +enum mandoclevel { + MANDOCLEVEL_OK = 0, + MANDOCLEVEL_RESERVED, + MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */ + MANDOCLEVEL_ERROR, /* input has been thrown away */ + MANDOCLEVEL_FATAL, /* input is borked */ + MANDOCLEVEL_BADARG, /* bad argument in invocation */ + MANDOCLEVEL_SYSERR, /* system error */ + MANDOCLEVEL_MAX +}; + +/* + * All possible things that can go wrong within a parse, be it libroff, + * libmdoc, or libman. + */ +enum mandocerr { + MANDOCERR_OK, + + MANDOCERR_WARNING, /* ===== start of warnings ===== */ + + /* related to the prologue */ + MANDOCERR_NOTITLE, /* no title in document */ + MANDOCERR_UPPERCASE, /* document title should be all caps */ + MANDOCERR_BADMSEC, /* unknown manual section */ + MANDOCERR_BADDATE, /* cannot parse date argument */ + MANDOCERR_PROLOGOOO, /* prologue macros out of order */ + MANDOCERR_PROLOGREP, /* duplicate prologue macro */ + MANDOCERR_BADPROLOG, /* macro not allowed in prologue */ + MANDOCERR_BADBODY, /* macro not allowed in body */ + + /* related to document structure */ + MANDOCERR_SO, /* .so is fragile, better use ln(1) */ + MANDOCERR_NAMESECFIRST, /* NAME section must come first */ + MANDOCERR_BADNAMESEC, /* bad NAME section contents */ + MANDOCERR_NONAME, /* manual name not yet set */ + MANDOCERR_SECOOO, /* sections out of conventional order */ + MANDOCERR_SECREP, /* duplicate section name */ + MANDOCERR_SECMSEC, /* section not in conventional manual section */ + + /* related to macros and nesting */ + MANDOCERR_MACROOBS, /* skipping obsolete macro */ + MANDOCERR_IGNPAR, /* skipping paragraph macro */ + MANDOCERR_SCOPENEST, /* blocks badly nested */ + MANDOCERR_CHILD, /* child violates parent syntax */ + MANDOCERR_NESTEDDISP, /* nested displays are not portable */ + MANDOCERR_SCOPEREP, /* already in literal mode */ + + /* related to missing macro arguments */ + MANDOCERR_MACROEMPTY, /* skipping empty macro */ + MANDOCERR_ARGCWARN, /* argument count wrong */ + MANDOCERR_DISPTYPE, /* missing display type */ + MANDOCERR_LISTFIRST, /* list type must come first */ + MANDOCERR_NOWIDTHARG, /* tag lists require a width argument */ + MANDOCERR_FONTTYPE, /* missing font type */ + + /* related to bad macro arguments */ + MANDOCERR_IGNARGV, /* skipping argument */ + MANDOCERR_ARGVREP, /* duplicate argument */ + MANDOCERR_DISPREP, /* duplicate display type */ + MANDOCERR_LISTREP, /* duplicate list type */ + MANDOCERR_BADATT, /* unknown AT&T UNIX version */ + MANDOCERR_BADBOOL, /* bad Boolean value */ + MANDOCERR_BADFONT, /* unknown font */ + MANDOCERR_BADSTANDARD, /* unknown standard specifier */ + MANDOCERR_BADWIDTH, /* bad width argument */ + + /* related to plain text */ + MANDOCERR_NOBLANKLN, /* blank line in non-literal context */ + MANDOCERR_BADTAB, /* tab in non-literal context */ + MANDOCERR_EOLNSPACE, /* end of line whitespace */ + MANDOCERR_BADCOMMENT, /* bad comment style */ + MANDOCERR_BADESCAPE, /* unknown escape sequence */ + MANDOCERR_BADQUOTE, /* unterminated quoted string */ + + /* related to tables */ + MANDOCERR_TBLEXTRADAT, /* extra data cells */ + + MANDOCERR_ERROR, /* ===== start of errors ===== */ + + /* related to tables */ + MANDOCERR_TBL, /* bad table syntax */ + MANDOCERR_TBLOPT, /* bad table option */ + MANDOCERR_TBLLAYOUT, /* bad table layout */ + MANDOCERR_TBLNOLAYOUT, /* no table layout cells specified */ + MANDOCERR_TBLNODATA, /* no table data cells specified */ + MANDOCERR_TBLIGNDATA, /* ignore data in cell */ + MANDOCERR_TBLBLOCK, /* data block still open */ + + MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */ + MANDOCERR_BADCHAR, /* skipping bad character */ + MANDOCERR_NOTEXT, /* skipping text before the first section header */ + MANDOCERR_MACRO, /* skipping unknown macro */ + MANDOCERR_REQUEST, /* NOT IMPLEMENTED: skipping request */ + MANDOCERR_LINESCOPE, /* line scope broken */ + MANDOCERR_ARGCOUNT, /* argument count wrong */ + MANDOCERR_NOSCOPE, /* skipping end of block that is not open */ + MANDOCERR_SCOPEBROKEN, /* missing end of block */ + MANDOCERR_SCOPEEXIT, /* scope open on exit */ + MANDOCERR_UNAME, /* uname(3) system call failed */ + /* FIXME: merge following with MANDOCERR_ARGCOUNT */ + MANDOCERR_NOARGS, /* macro requires line argument(s) */ + MANDOCERR_NOBODY, /* macro requires body argument(s) */ + MANDOCERR_NOARGV, /* macro requires argument(s) */ + MANDOCERR_LISTTYPE, /* missing list type */ + MANDOCERR_ARGSLOST, /* line argument(s) will be lost */ + MANDOCERR_BODYLOST, /* body argument(s) will be lost */ + + MANDOCERR_FATAL, /* ===== start of fatal errors ===== */ + + MANDOCERR_COLUMNS, /* column syntax is inconsistent */ + MANDOCERR_BADDISP, /* NOT IMPLEMENTED: .Bd -file */ + MANDOCERR_SYNTLINESCOPE, /* line scope broken, syntax violated */ + MANDOCERR_SYNTARGVCOUNT, /* argument count wrong, violates syntax */ + MANDOCERR_SYNTCHILD, /* child violates parent syntax */ + MANDOCERR_SYNTARGCOUNT, /* argument count wrong, violates syntax */ + MANDOCERR_SOPATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */ + MANDOCERR_NODOCBODY, /* no document body */ + MANDOCERR_NODOCPROLOG, /* no document prologue */ + MANDOCERR_MEM, /* static buffer exhausted */ + MANDOCERR_MAX +}; + +struct tbl { + char tab; /* cell-separator */ + char decimal; /* decimal point */ + int linesize; + int opts; +#define TBL_OPT_CENTRE (1 << 0) +#define TBL_OPT_EXPAND (1 << 1) +#define TBL_OPT_BOX (1 << 2) +#define TBL_OPT_DBOX (1 << 3) +#define TBL_OPT_ALLBOX (1 << 4) +#define TBL_OPT_NOKEEP (1 << 5) +#define TBL_OPT_NOSPACE (1 << 6) + int cols; /* number of columns */ +}; + +enum tbl_headt { + TBL_HEAD_DATA, /* plug in data from tbl_dat */ + TBL_HEAD_VERT, /* vertical spacer */ + TBL_HEAD_DVERT /* double-vertical spacer */ +}; + +/* + * The head of a table specifies all of its columns. When formatting a + * tbl_span, iterate over these and plug in data from the tbl_span when + * appropriate, using tbl_cell as a guide to placement. + */ +struct tbl_head { + enum tbl_headt pos; + int ident; /* 0 <= unique id < cols */ + struct tbl_head *next; + struct tbl_head *prev; +}; + +enum tbl_cellt { + TBL_CELL_CENTRE, /* c, C */ + TBL_CELL_RIGHT, /* r, R */ + TBL_CELL_LEFT, /* l, L */ + TBL_CELL_NUMBER, /* n, N */ + TBL_CELL_SPAN, /* s, S */ + TBL_CELL_LONG, /* a, A */ + TBL_CELL_DOWN, /* ^ */ + TBL_CELL_HORIZ, /* _, - */ + TBL_CELL_DHORIZ, /* = */ + TBL_CELL_VERT, /* | */ + TBL_CELL_DVERT, /* || */ + TBL_CELL_MAX +}; + +/* + * A cell in a layout row. + */ +struct tbl_cell { + struct tbl_cell *next; + enum tbl_cellt pos; + int spacing; + int flags; +#define TBL_CELL_TALIGN (1 << 0) /* t, T */ +#define TBL_CELL_BALIGN (1 << 1) /* d, D */ +#define TBL_CELL_BOLD (1 << 2) /* fB, B, b */ +#define TBL_CELL_ITALIC (1 << 3) /* fI, I, i */ +#define TBL_CELL_EQUAL (1 << 4) /* e, E */ +#define TBL_CELL_UP (1 << 5) /* u, U */ +#define TBL_CELL_WIGN (1 << 6) /* z, Z */ + struct tbl_head *head; +}; + +/* + * A layout row. + */ +struct tbl_row { + struct tbl_row *next; + struct tbl_cell *first; + struct tbl_cell *last; +}; + +enum tbl_datt { + TBL_DATA_NONE, + TBL_DATA_DATA, + TBL_DATA_HORIZ, + TBL_DATA_DHORIZ, + TBL_DATA_NHORIZ, + TBL_DATA_NDHORIZ +}; + +/* + * A cell within a row of data. The "string" field contains the actual + * string value that's in the cell. The rest is layout. + */ +struct tbl_dat { + struct tbl_cell *layout; /* layout cell: CAN BE NULL */ + struct tbl_dat *next; + char *string; + enum tbl_datt pos; +}; + +enum tbl_spant { + TBL_SPAN_DATA, /* span consists of data */ + TBL_SPAN_HORIZ, /* span is horizontal line */ + TBL_SPAN_DHORIZ /* span is double horizontal line */ +}; + +/* + * A row of data in a table. + */ +struct tbl_span { + struct tbl *tbl; + struct tbl_head *head; + struct tbl_row *layout; /* layout row: CAN BE NULL */ + struct tbl_dat *first; + struct tbl_dat *last; + int flags; +#define TBL_SPAN_FIRST (1 << 0) +#define TBL_SPAN_LAST (1 << 1) + enum tbl_spant pos; + struct tbl_span *next; +}; + +/* + * Available registers (set in libroff, accessed elsewhere). + */ +enum regs { + REG_nS = 0, + REG__MAX +}; + +/* + * A register (struct reg) can consist of many types: this consists of + * normalised types from the original string form. + */ +union regval { + unsigned u; /* unsigned integer */ +}; + +/* + * A single register entity. If "set" is zero, the value of the + * register should be the default one, which is per-register. It's + * assumed that callers know which type in "v" corresponds to which + * register value. + */ +struct reg { + int set; /* whether set or not */ + union regval v; /* parsed data */ +}; + +/* + * The primary interface to setting register values is in libroff, + * although libmdoc and libman from time to time will manipulate + * registers (such as `.Sh SYNOPSIS' enabling REG_nS). + */ +struct regset { + struct reg regs[REG__MAX]; +}; + +__BEGIN_DECLS + +/* + * Callback function for warnings, errors, and fatal errors as they + * occur in the compilers libroff, libmdoc, and libman. + */ +typedef int (*mandocmsg)(enum mandocerr, void *, + int, int, const char *); + +__END_DECLS + +#endif /*!MANDOC_H*/ diff --git a/contrib/mdocml/mandoc_char.7 b/contrib/mdocml/mandoc_char.7 new file mode 100644 index 0000000000..ff693d7778 --- /dev/null +++ b/contrib/mdocml/mandoc_char.7 @@ -0,0 +1,558 @@ +.\" $Id: mandoc_char.7,v 1.40 2010/10/29 00:05:53 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. +.\" +.Dd $Mdocdate: October 29 2010 $ +.Dt MANDOC_CHAR 7 +.Os +.Sh NAME +.Nm mandoc_char +.Nd mandoc special characters +.Sh DESCRIPTION +This page documents the special characters and predefined strings accepted by +.Xr mandoc 1 +to format +.Xr mdoc 7 +and +.Xr man 7 +documents. +.Pp +Both +.Xr mdoc 7 +and +.Xr man 7 +encode special characters with +.Sq \eX +.Pq for a one-character escape , +.Sq \e(XX +.Pq two-character , +and +.Sq \e[N] +.Pq N-character . +One may generalise +.Sq \e(XX +as +.Sq \e[XX] +and +.Sq \eX +as +.Sq \e[X] . +Predefined strings are functionally similar to special characters, using +.Sq \e*X +.Pq for a one-character escape , +.Sq \e*(XX +.Pq two-character , +and +.Sq \e*[N] +.Pq N-character . +One may generalise +.Sq \e*(XX +as +.Sq \e*[XX] +and +.Sq \e*X +as +.Sq \e*[X] . +.Pp +Note that each output mode will have a different rendering of the +characters. +It's guaranteed that each input symbol will correspond to a +(more or less) meaningful output rendering, regardless the mode. +.Sh SPECIAL CHARACTERS +These are the preferred input symbols for producing special characters. +.Pp +Spacing: +.Bl -column -compact -offset indent "Input" "Description" +.It Em Input Ta Em Description +.It \e~ Ta non-breaking, non-collapsing space +.It \e Ta breaking, non-collapsing n-width space +.It \e^ Ta zero-width space +.It \e% Ta zero-width space +.It \e& Ta zero-width space +.It \e| Ta zero-width space +.It \e0 Ta breaking, non-collapsing digit-width space +.It \ec Ta removes any trailing space (if applicable) +.El +.Pp +Lines: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ba Ta \(ba Ta bar +.It \e(br Ta \(br Ta box rule +.It \e(ul Ta \(ul Ta underscore +.It \e(rl Ta \(rl Ta overline +.It \e(bb Ta \(bb Ta broken bar +.It \e(sl Ta \(sl Ta forward slash +.It \e(rs Ta \(rs Ta backward slash +.El +.Pp +Text markers: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ci Ta \(ci Ta circle +.It \e(bu Ta \(bu Ta bullet +.It \e(dd Ta \(dd Ta double dagger +.It \e(dg Ta \(dg Ta dagger +.It \e(lz Ta \(lz Ta lozenge +.It \e(sq Ta \(sq Ta white square +.It \e(ps Ta \(ps Ta paragraph +.It \e(sc Ta \(sc Ta section +.It \e(lh Ta \(lh Ta left hand +.It \e(rh Ta \(rh Ta right hand +.It \e(at Ta \(at Ta at +.It \e(sh Ta \(sh Ta hash (pound) +.It \e(CR Ta \(CR Ta carriage return +.It \e(OK Ta \(OK Ta check mark +.El +.Pp +Legal symbols: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(co Ta \(co Ta copyright +.It \e(rg Ta \(rg Ta registered +.It \e(tm Ta \(tm Ta trademarked +.El +.Pp +Punctuation: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(em Ta \(em Ta em-dash +.It \e(en Ta \(en Ta en-dash +.It \e(hy Ta \(hy Ta hyphen +.It \ee Ta \e Ta back-slash +.It \e. Ta \. Ta period +.It \e(r! Ta \(r! Ta upside-down exclamation +.It \e(r? Ta \(r? Ta upside-down question +.El +.Pp +Quotes: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(Bq Ta \(Bq Ta right low double-quote +.It \e(bq Ta \(bq Ta right low single-quote +.It \e(lq Ta \(lq Ta left double-quote +.It \e(rq Ta \(rq Ta right double-quote +.It \e(oq Ta \(oq Ta left single-quote +.It \e(cq Ta \(cq Ta right single-quote +.It \e(aq Ta \(aq Ta apostrophe quote (text) +.It \e(dq Ta \(dq Ta double quote (text) +.It \e(Fo Ta \(Fo Ta left guillemet +.It \e(Fc Ta \(Fc Ta right guillemet +.It \e(fo Ta \(fo Ta left single guillemet +.It \e(fc Ta \(fc Ta right single guillemet +.El +.Pp +Brackets: +.Bl -column -compact -offset indent "xxbracketrightbpx" Rendered Description +.It Em Input Ta Em Rendered Ta Em Description +.It \e(lB Ta \(lB Ta left bracket +.It \e(rB Ta \(rB Ta right bracket +.It \e(lC Ta \(lC Ta left brace +.It \e(rC Ta \(rC Ta right brace +.It \e(la Ta \(la Ta left angle +.It \e(ra Ta \(ra Ta right angle +.It \e(bv Ta \(bv Ta brace extension +.It \e[braceex] Ta \[braceex] Ta brace extension +.It \e[bracketlefttp] Ta \[bracketlefttp] Ta top-left hooked bracket +.It \e[bracketleftbp] Ta \[bracketleftbp] Ta bottom-left hooked bracket +.It \e[bracketleftex] Ta \[bracketleftex] Ta left hooked bracket extension +.It \e[bracketrighttp] Ta \[bracketrighttp] Ta top-right hooked bracket +.It \e[bracketrightbp] Ta \[bracketrightbp] Ta bottom-right hooked bracket +.It \e[bracketrightex] Ta \[bracketrightex] Ta right hooked bracket extension +.It \e(lt Ta \(lt Ta top-left hooked brace +.It \e[bracelefttp] Ta \[bracelefttp] Ta top-left hooked brace +.It \e(lk Ta \(lk Ta mid-left hooked brace +.It \e[braceleftmid] Ta \[braceleftmid] Ta mid-left hooked brace +.It \e(lb Ta \(lb Ta bottom-left hooked brace +.It \e[braceleftbp] Ta \[braceleftbp] Ta bottom-left hooked brace +.It \e[braceleftex] Ta \[braceleftex] Ta left hooked brace extension +.It \e(rt Ta \(rt Ta top-left hooked brace +.It \e[bracerighttp] Ta \[bracerighttp] Ta top-right hooked brace +.It \e(rk Ta \(rk Ta mid-right hooked brace +.It \e[bracerightmid] Ta \[bracerightmid] Ta mid-right hooked brace +.It \e(rb Ta \(rb Ta bottom-right hooked brace +.It \e[bracerightbp] Ta \[bracerightbp] Ta bottom-right hooked brace +.It \e[bracerightex] Ta \[bracerightex] Ta right hooked brace extension +.It \e[parenlefttp] Ta \[parenlefttp] Ta top-left hooked parenthesis +.It \e[parenleftbp] Ta \[parenleftbp] Ta bottom-left hooked parenthesis +.It \e[parenleftex] Ta \[parenleftex] Ta left hooked parenthesis extension +.It \e[parenrighttp] Ta \[parenrighttp] Ta top-right hooked parenthesis +.It \e[parenrightbp] Ta \[parenrightbp] Ta bottom-right hooked parenthesis +.It \e[parenrightex] Ta \[parenrightex] Ta right hooked parenthesis extension +.El +.Pp +Arrows: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(<- Ta \(<- Ta left arrow +.It \e(-> Ta \(-> Ta right arrow +.It \e(<> Ta \(<> Ta left-right arrow +.It \e(da Ta \(da Ta down arrow +.It \e(ua Ta \(ua Ta up arrow +.It \e(va Ta \(va Ta up-down arrow +.It \e(lA Ta \(lA Ta left double-arrow +.It \e(rA Ta \(rA Ta right double-arrow +.It \e(hA Ta \(hA Ta left-right double-arrow +.It \e(uA Ta \(uA Ta up double-arrow +.It \e(dA Ta \(dA Ta down double-arrow +.It \e(vA Ta \(vA Ta up-down double-arrow +.El +.Pp +Logical: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(AN Ta \(AN Ta logical and +.It \e(OR Ta \(OR Ta logical or +.It \e(no Ta \(no Ta logical not +.It \e[tno] Ta \[tno] Ta logical not (text) +.It \e(te Ta \(te Ta existential quantifier +.It \e(fa Ta \(fa Ta universal quantifier +.It \e(st Ta \(st Ta such that +.It \e(tf Ta \(tf Ta therefore +.It \e(3d Ta \(3d Ta therefore +.It \e(or Ta \(or Ta bitwise or +.El +.Pp +Mathematical: +.Bl -column -compact -offset indent "xxcoproductxx" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(pl Ta \(pl Ta plus +.It \e(mi Ta \(mi Ta minus +.It \e- Ta \- Ta minus (text) +.It \e(-+ Ta \(-+ Ta minus-plus +.It \e(+- Ta \(+- Ta plus-minus +.It \e[t+-] Ta \[t+-] Ta plus-minus (text) +.It \e(pc Ta \(pc Ta centre-dot +.It \e(mu Ta \(mu Ta multiply +.It \e[tmu] Ta \[tmu] Ta multiply (text) +.It \e(c* Ta \(c* Ta circle-multiply +.It \e(c+ Ta \(c+ Ta circle-plus +.It \e(di Ta \(di Ta divide +.It \e[tdi] Ta \[tdi] Ta divide (text) +.It \e(f/ Ta \(f/ Ta fraction +.It \e(** Ta \(** Ta asterisk +.It \e(<= Ta \(<= Ta less-than-equal +.It \e(>= Ta \(>= Ta greater-than-equal +.It \e(<< Ta \(<< Ta much less +.It \e(>> Ta \(>> Ta much greater +.It \e(eq Ta \(eq Ta equal +.It \e(!= Ta \(!= Ta not equal +.It \e(== Ta \(== Ta equivalent +.It \e(ne Ta \(ne Ta not equivalent +.It \e(=~ Ta \(=~ Ta congruent +.It \e(-~ Ta \(-~ Ta asymptotically congruent +.It \e(ap Ta \(ap Ta asymptotically similar +.It \e(~~ Ta \(~~ Ta approximately similar +.It \e(~= Ta \(~= Ta approximately equal +.It \e(pt Ta \(pt Ta proportionate +.It \e(es Ta \(es Ta empty set +.It \e(mo Ta \(mo Ta element +.It \e(nm Ta \(nm Ta not element +.It \e(sb Ta \(sb Ta proper subset +.It \e(nb Ta \(nb Ta not subset +.It \e(sp Ta \(sp Ta proper superset +.It \e(nc Ta \(nc Ta not superset +.It \e(ib Ta \(ib Ta reflexive subset +.It \e(ip Ta \(ip Ta reflexive superset +.It \e(ca Ta \(ca Ta intersection +.It \e(cu Ta \(cu Ta union +.It \e(/_ Ta \(/_ Ta angle +.It \e(pp Ta \(pp Ta perpendicular +.It \e(is Ta \(is Ta integral +.It \e[integral] Ta \[integral] Ta integral +.It \e[sum] Ta \[sum] Ta summation +.It \e[product] Ta \[product] Ta product +.It \e[coproduct] Ta \[coproduct] Ta coproduct +.It \e(gr Ta \(gr Ta gradient +.It \e(sr Ta \(sr Ta square root +.It \e[sqrt] Ta \[sqrt] Ta square root +.It \e(lc Ta \(lc Ta left-ceiling +.It \e(rc Ta \(rc Ta right-ceiling +.It \e(lf Ta \(lf Ta left-floor +.It \e(rf Ta \(rf Ta right-floor +.It \e(if Ta \(if Ta infinity +.It \e(Ah Ta \(Ah Ta aleph +.It \e(Im Ta \(Im Ta imaginary +.It \e(Re Ta \(Re Ta real +.It \e(pd Ta \(pd Ta partial differential +.It \e(-h Ta \(-h Ta Planck constant over 2\(*p +.El +.Pp +Ligatures: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ff Ta \(ff Ta ff ligature +.It \e(fi Ta \(fi Ta fi ligature +.It \e(fl Ta \(fl Ta fl ligature +.It \e(Fi Ta \(Fi Ta ffi ligature +.It \e(Fl Ta \(Fl Ta ffl ligature +.It \e(AE Ta \(AE Ta AE +.It \e(ae Ta \(ae Ta ae +.It \e(OE Ta \(OE Ta OE +.It \e(oe Ta \(oe Ta oe +.It \e(ss Ta \(ss Ta German eszett +.It \e(IJ Ta \(IJ Ta IJ ligature +.It \e(ij Ta \(ij Ta ij ligature +.El +.Pp +Accents: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(a" Ta \(a" Ta Hungarian umlaut +.It \e(a- Ta \(a- Ta macron +.It \e(a. Ta \(a. Ta dotted +.It \e(a^ Ta \(a^ Ta circumflex +.It \e(aa Ta \(aa Ta acute +.It \e' Ta \' Ta acute +.It \e(ga Ta \(ga Ta grave +.It \e` Ta \` Ta grave +.It \e(ab Ta \(ab Ta breve +.It \e(ac Ta \(ac Ta cedilla +.It \e(ad Ta \(ad Ta dieresis +.It \e(ah Ta \(ah Ta caron +.It \e(ao Ta \(ao Ta ring +.It \e(a~ Ta \(a~ Ta tilde +.It \e(ho Ta \(ho Ta ogonek +.It \e(ha Ta \(ha Ta hat (text) +.It \e(ti Ta \(ti Ta tilde (text) +.El +.Pp +Accented letters: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e('A Ta \('A Ta acute A +.It \e('E Ta \('E Ta acute E +.It \e('I Ta \('I Ta acute I +.It \e('O Ta \('O Ta acute O +.It \e('U Ta \('U Ta acute U +.It \e('a Ta \('a Ta acute a +.It \e('e Ta \('e Ta acute e +.It \e('i Ta \('i Ta acute i +.It \e('o Ta \('o Ta acute o +.It \e('u Ta \('u Ta acute u +.It \e(`A Ta \(`A Ta grave A +.It \e(`E Ta \(`E Ta grave E +.It \e(`I Ta \(`I Ta grave I +.It \e(`O Ta \(`O Ta grave O +.It \e(`U Ta \(`U Ta grave U +.It \e(`a Ta \(`a Ta grave a +.It \e(`e Ta \(`e Ta grave e +.It \e(`i Ta \(`i Ta grave i +.It \e(`o Ta \(`i Ta grave o +.It \e(`u Ta \(`u Ta grave u +.It \e(~A Ta \(~A Ta tilde A +.It \e(~N Ta \(~N Ta tilde N +.It \e(~O Ta \(~O Ta tilde O +.It \e(~a Ta \(~a Ta tilde a +.It \e(~n Ta \(~n Ta tilde n +.It \e(~o Ta \(~o Ta tilde o +.It \e(:A Ta \(:A Ta dieresis A +.It \e(:E Ta \(:E Ta dieresis E +.It \e(:I Ta \(:I Ta dieresis I +.It \e(:O Ta \(:O Ta dieresis O +.It \e(:U Ta \(:U Ta dieresis U +.It \e(:a Ta \(:a Ta dieresis a +.It \e(:e Ta \(:e Ta dieresis e +.It \e(:i Ta \(:i Ta dieresis i +.It \e(:o Ta \(:o Ta dieresis o +.It \e(:u Ta \(:u Ta dieresis u +.It \e(:y Ta \(:y Ta dieresis y +.It \e(^A Ta \(^A Ta circumflex A +.It \e(^E Ta \(^E Ta circumflex E +.It \e(^I Ta \(^I Ta circumflex I +.It \e(^O Ta \(^O Ta circumflex O +.It \e(^U Ta \(^U Ta circumflex U +.It \e(^a Ta \(^a Ta circumflex a +.It \e(^e Ta \(^e Ta circumflex e +.It \e(^i Ta \(^i Ta circumflex i +.It \e(^o Ta \(^o Ta circumflex o +.It \e(^u Ta \(^u Ta circumflex u +.It \e(,C Ta \(,C Ta cedilla C +.It \e(,c Ta \(,c Ta cedilla c +.It \e(/L Ta \(/L Ta stroke L +.It \e(/l Ta \(/l Ta stroke l +.It \e(/O Ta \(/O Ta stroke O +.It \e(/o Ta \(/o Ta stroke o +.It \e(oA Ta \(oA Ta ring A +.It \e(oa Ta \(oa Ta ring a +.El +.Pp +Special letters: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(-D Ta \(-D Ta Eth +.It \e(Sd Ta \(Sd Ta eth +.It \e(TP Ta \(TP Ta Thorn +.It \e(Tp Ta \(Tp Ta thorn +.It \e(.i Ta \(.i Ta dotless i +.It \e(.j Ta \(.j Ta dotless j +.El +.Pp +Currency: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(Do Ta \(Do Ta dollar +.It \e(ct Ta \(ct Ta cent +.It \e(Eu Ta \(Eu Ta Euro symbol +.It \e(eu Ta \(eu Ta Euro symbol +.It \e(Ye Ta \(Ye Ta yen +.It \e(Po Ta \(Po Ta pound +.It \e(Cs Ta \(Cs Ta Scandinavian +.It \e(Fn Ta \(Fn Ta florin +.El +.Pp +Units: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(de Ta \(de Ta degree +.It \e(%0 Ta \(%0 Ta per-thousand +.It \e(fm Ta \(fm Ta minute +.It \e(sd Ta \(sd Ta second +.It \e(mc Ta \(mc Ta micro +.El +.Pp +Greek letters: +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e(*A Ta \(*A Ta Alpha +.It \e(*B Ta \(*B Ta Beta +.It \e(*G Ta \(*G Ta Gamma +.It \e(*D Ta \(*D Ta Delta +.It \e(*E Ta \(*E Ta Epsilon +.It \e(*Z Ta \(*Z Ta Zeta +.It \e(*Y Ta \(*Y Ta Eta +.It \e(*H Ta \(*H Ta Theta +.It \e(*I Ta \(*I Ta Iota +.It \e(*K Ta \(*K Ta Kappa +.It \e(*L Ta \(*L Ta Lambda +.It \e(*M Ta \(*M Ta Mu +.It \e(*N Ta \(*N Ta Nu +.It \e(*C Ta \(*C Ta Xi +.It \e(*O Ta \(*O Ta Omicron +.It \e(*P Ta \(*P Ta Pi +.It \e(*R Ta \(*R Ta Rho +.It \e(*S Ta \(*S Ta Sigma +.It \e(*T Ta \(*T Ta Tau +.It \e(*U Ta \(*U Ta Upsilon +.It \e(*F Ta \(*F Ta Phi +.It \e(*X Ta \(*X Ta Chi +.It \e(*Q Ta \(*Q Ta Psi +.It \e(*W Ta \(*W Ta Omega +.It \e(*a Ta \(*a Ta alpha +.It \e(*b Ta \(*b Ta beta +.It \e(*g Ta \(*g Ta gamma +.It \e(*d Ta \(*d Ta delta +.It \e(*e Ta \(*e Ta epsilon +.It \e(*z Ta \(*z Ta zeta +.It \e(*y Ta \(*y Ta eta +.It \e(*h Ta \(*h Ta theta +.It \e(*i Ta \(*i Ta iota +.It \e(*k Ta \(*k Ta kappa +.It \e(*l Ta \(*l Ta lambda +.It \e(*m Ta \(*m Ta mu +.It \e(*n Ta \(*n Ta nu +.It \e(*c Ta \(*c Ta xi +.It \e(*o Ta \(*o Ta omicron +.It \e(*p Ta \(*p Ta pi +.It \e(*r Ta \(*r Ta rho +.It \e(*s Ta \(*s Ta sigma +.It \e(*t Ta \(*t Ta tau +.It \e(*u Ta \(*u Ta upsilon +.It \e(*f Ta \(*f Ta phi +.It \e(*x Ta \(*x Ta chi +.It \e(*q Ta \(*q Ta psi +.It \e(*w Ta \(*w Ta omega +.It \e(+h Ta \(+h Ta theta variant +.It \e(+f Ta \(+f Ta phi variant +.It \e(+p Ta \(+p Ta pi variant +.It \e(+e Ta \(+e Ta epsilon variant +.It \e(ts Ta \(ts Ta sigma terminal +.El +.Sh PREDEFINED STRINGS +These are not recommended for use, as they differ across +implementations: +.Pp +.Bl -column -compact -offset indent "Input" "Rendered" "Description" +.It Em Input Ta Em Rendered Ta Em Description +.It \e*(Ba Ta \*(Ba Ta vertical bar +.It \e*(Ne Ta \*(Ne Ta not equal +.It \e*(Ge Ta \*(Ge Ta greater-than-equal +.It \e*(Le Ta \*(Le Ta less-than-equal +.It \e*(Gt Ta \*(Gt Ta greater-than +.It \e*(Lt Ta \*(Lt Ta less-than +.It \e*(Pm Ta \*(Pm Ta plus-minus +.It \e*(If Ta \*(If Ta infinity +.It \e*(Pi Ta \*(Pi Ta pi +.It \e*(Na Ta \*(Na Ta NaN +.It \e*(Am Ta \*(Am Ta ampersand +.It \e*R Ta \*R Ta restricted mark +.It \e*(Tm Ta \*(Tm Ta trade mark +.It \e*q Ta \*q Ta double-quote +.It \e*(Rq Ta \*(Rq Ta right-double-quote +.It \e*(Lq Ta \*(Lq Ta left-double-quote +.It \e*(lp Ta \*(lp Ta right-parenthesis +.It \e*(rp Ta \*(rp Ta left-parenthesis +.It \e*(lq Ta \*(lq Ta left double-quote +.It \e*(rq Ta \*(rq Ta right double-quote +.It \e*(ua Ta \*(ua Ta up arrow +.It \e*(va Ta \*(va Ta up-down arrow +.It \e*(<= Ta \*(<= Ta less-than-equal +.It \e*(>= Ta \*(>= Ta greater-than-equal +.It \e*(aa Ta \*(aa Ta acute +.It \e*(ga Ta \*(ga Ta grave +.El +.Sh COMPATIBILITY +This section documents compatibility of +.Nm +with older or existing versions of groff. +.Pp +The following render differently in +.Fl T Ns Ar ascii +output mode: +.Bd -ragged -offset indent +\e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product], +\e[coproduct], \e(gr, \e(-h, \e(a. +.Ed +.Pp +The following render differently in +.Fl T Ns Ar html +output mode: +.Bd -ragged -offset indent +\e(~=, \e(nb, \e(nc +.Ed +.Pp +Finally, the following have been omitted by being poorly documented or +having no known representation: +.Bd -ragged -offset indent +\e[radicalex], \e[sqrtex], \e(ru +.Ed +.Sh SEE ALSO +.Xr mandoc 1 +.Sh AUTHORS +The +.Nm +manual page was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . +.Sh CAVEATS +The +.Sq \e*(Ba +escape mimics the behaviour of the +.Sq \&| +character in +.Xr mdoc 7 ; +thus, if you wish to render a vertical bar with no side effects, use +the +.Sq \e(ba +escape. diff --git a/contrib/mdocml/mdoc.3 b/contrib/mdocml/mdoc.3 new file mode 100644 index 0000000000..1a2fc9bac0 --- /dev/null +++ b/contrib/mdocml/mdoc.3 @@ -0,0 +1,348 @@ +.\" $Id: mdoc.3,v 1.55 2011/01/07 15:07:21 kristaps Exp $ +.\" +.\" Copyright (c) 2009, 2010 Kristaps Dzonsons +.\" Copyright (c) 2010 Ingo Schwarze +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 7 2011 $ +.Dt MDOC 3 +.Os +.Sh NAME +.Nm mdoc , +.Nm mdoc_alloc , +.Nm mdoc_endparse , +.Nm mdoc_free , +.Nm mdoc_meta , +.Nm mdoc_node , +.Nm mdoc_parseln , +.Nm mdoc_reset +.Nd mdoc macro compiler library +.Sh SYNOPSIS +.In mandoc.h +.In mdoc.h +.Vt extern const char * const * mdoc_macronames; +.Vt extern const char * const * mdoc_argnames; +.Ft int +.Fo mdoc_addspan +.Fa "struct mdoc *mdoc" +.Fa "const struct tbl_span *span" +.Fc +.Ft "struct mdoc *" +.Fo mdoc_alloc +.Fa "struct regset *regs" +.Fa "void *data" +.Fa "mandocmsg msgs" +.Fc +.Ft int +.Fn mdoc_endparse "struct mdoc *mdoc" +.Ft void +.Fn mdoc_free "struct mdoc *mdoc" +.Ft "const struct mdoc_meta *" +.Fn mdoc_meta "const struct mdoc *mdoc" +.Ft "const struct mdoc_node *" +.Fn mdoc_node "const struct mdoc *mdoc" +.Ft int +.Fo mdoc_parseln +.Fa "struct mdoc *mdoc" +.Fa "int line" +.Fa "char *buf" +.Fc +.Ft int +.Fn mdoc_reset "struct mdoc *mdoc" +.Sh DESCRIPTION +The +.Nm mdoc +library parses lines of +.Xr mdoc 7 +input +into an abstract syntax tree (AST). +.Pp +In general, applications initiate a parsing sequence with +.Fn mdoc_alloc , +parse each line in a document with +.Fn mdoc_parseln , +close the parsing session with +.Fn mdoc_endparse , +operate over the syntax tree returned by +.Fn mdoc_node +and +.Fn mdoc_meta , +then free all allocated memory with +.Fn mdoc_free . +The +.Fn mdoc_reset +function may be used in order to reset the parser for another input +sequence. +.Ss Types +.Bl -ohang +.It Vt struct mdoc +An opaque type. +Its values are only used privately within the library. +.It Vt struct mdoc_node +A parsed node. +See +.Sx Abstract Syntax Tree +for details. +.El +.Ss Functions +If +.Fn mdoc_addspan , +.Fn mdoc_parseln , +or +.Fn mdoc_endparse +return 0, calls to any function but +.Fn mdoc_reset +or +.Fn mdoc_free +will raise an assertion. +.Bl -ohang +.It Fn mdoc_addspan +Add a table span to the parsing stream. +Returns 0 on failure, 1 on success. +.It Fn mdoc_alloc +Allocates a parsing structure. +The +.Fa data +pointer is passed to +.Fa msgs . +Always returns a valid pointer. +The pointer must be freed with +.Fn mdoc_free . +.It Fn mdoc_reset +Reset the parser for another parse routine. +After its use, +.Fn mdoc_parseln +behaves as if invoked for the first time. +If it returns 0, memory could not be allocated. +.It Fn mdoc_free +Free all resources of a parser. +The pointer is no longer valid after invocation. +.It Fn mdoc_parseln +Parse a nil-terminated line of input. +This line should not contain the trailing newline. +Returns 0 on failure, 1 on success. +The input buffer +.Fa buf +is modified by this function. +.It Fn mdoc_endparse +Signals that the parse is complete. +Returns 0 on failure, 1 on success. +.It Fn mdoc_node +Returns the first node of the parse. +.It Fn mdoc_meta +Returns the document's parsed meta-data. +.El +.Ss Variables +.Bl -ohang +.It Va mdoc_macronames +An array of string-ified token names. +.It Va mdoc_argnames +An array of string-ified token argument names. +.El +.Ss Abstract Syntax Tree +The +.Nm +functions produce an abstract syntax tree (AST) describing input in a +regular form. +It may be reviewed at any time with +.Fn mdoc_nodes ; +however, if called before +.Fn mdoc_endparse , +or after +.Fn mdoc_endparse +or +.Fn mdoc_parseln +fail, it may be incomplete. +.Pp +This AST is governed by the ontological +rules dictated in +.Xr mdoc 7 +and derives its terminology accordingly. +.Qq In-line +elements described in +.Xr mdoc 7 +are described simply as +.Qq elements . +.Pp +The AST is composed of +.Vt struct mdoc_node +nodes with block, head, body, element, root and text types as declared +by the +.Va type +field. +Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va nchild , +.Va next +and +.Va prev +fields) and some type-specific data, in particular, for nodes generated +from macros, the generating macro in the +.Va tok +field. +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact +.It ROOT +\(<- mnode+ +.It mnode +\(<- BLOCK | ELEMENT | TEXT +.It BLOCK +\(<- HEAD [TEXT] (BODY [TEXT])+ [TAIL [TEXT]] +.It ELEMENT +\(<- TEXT* +.It HEAD +\(<- mnode* +.It BODY +\(<- mnode* [ENDBODY mnode*] +.It TAIL +\(<- mnode* +.It TEXT +\(<- [[:printable:],0x1e]* +.El +.Pp +Of note are the TEXT nodes following the HEAD, BODY and TAIL nodes of +the BLOCK production: these refer to punctuation marks. +Furthermore, although a TEXT node will generally have a non-zero-length +string, in the specific case of +.Sq \&.Bd \-literal , +an empty line will produce a zero-length string. +Multiple body parts are only found in invocations of +.Sq \&Bl \-column , +where a new body introduces a new phrase. +.Ss Badly-nested Blocks +The ENDBODY node is available to end the formatting associated +with a given block before the physical end of that block. +It has a non-null +.Va end +field, is of the BODY +.Va type , +has the same +.Va tok +as the BLOCK it is ending, and has a +.Va pending +field pointing to that BLOCK's BODY node. +It is an indirect child of that BODY node +and has no children of its own. +.Pp +An ENDBODY node is generated when a block ends while one of its child +blocks is still open, like in the following example: +.Bd -literal -offset indent +\&.Ao ao +\&.Bo bo ac +\&.Ac bc +\&.Bc end +.Ed +.Pp +This example results in the following block structure: +.Bd -literal -offset indent +BLOCK Ao + HEAD Ao + BODY Ao + TEXT ao + BLOCK Bo, pending -> Ao + HEAD Bo + BODY Bo + TEXT bo + TEXT ac + ENDBODY Ao, pending -> Ao + TEXT bc +TEXT end +.Ed +.Pp +Here, the formatting of the +.Sq \&Ao +block extends from TEXT ao to TEXT ac, +while the formatting of the +.Sq \&Bo +block extends from TEXT bo to TEXT bc. +It renders as follows in +.Fl T Ns Cm ascii +mode: +.Pp +.Dl bc] end +.Pp +Support for badly-nested blocks is only provided for backward +compatibility with some older +.Xr mdoc 7 +implementations. +Using badly-nested blocks is +.Em strongly discouraged : +the +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml +front-ends are unable to render them in any meaningful way. +Furthermore, behaviour when encountering badly-nested blocks is not +consistent across troff implementations, especially when using multiple +levels of badly-nested blocks. +.Sh EXAMPLES +The following example reads lines from stdin and parses them, operating +on the finished parse tree with +.Fn parsed . +This example does not error-check nor free memory upon failure. +.Bd -literal -offset indent +struct regset regs; +struct mdoc *mdoc; +const struct mdoc_node *node; +char *buf; +size_t len; +int line; + +bzero(®s, sizeof(struct regset)); +line = 1; +mdoc = mdoc_alloc(®s, NULL, NULL); +buf = NULL; +alloc_len = 0; + +while ((len = getline(&buf, &alloc_len, stdin)) >= 0) { + if (len && buflen[len - 1] = '\en') + buf[len - 1] = '\e0'; + if ( ! mdoc_parseln(mdoc, line, buf)) + errx(1, "mdoc_parseln"); + line++; +} + +if ( ! mdoc_endparse(mdoc)) + errx(1, "mdoc_endparse"); +if (NULL == (node = mdoc_node(mdoc))) + errx(1, "mdoc_node"); + +parsed(mdoc, node); +mdoc_free(mdoc); +.Ed +.Pp +To compile this, execute +.Pp +.Dl % cc main.c libmdoc.a libmandoc.a +.Pp +where +.Pa main.c +is the example file. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr mdoc 7 +.Sh AUTHORS +The +.Nm +library was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . diff --git a/contrib/mdocml/mdoc.7 b/contrib/mdocml/mdoc.7 new file mode 100644 index 0000000000..6ecd5a7b29 --- /dev/null +++ b/contrib/mdocml/mdoc.7 @@ -0,0 +1,2925 @@ +.\" $Id: mdoc.7,v 1.174 2011/01/04 23:32:21 kristaps Exp $ +.\" +.\" Copyright (c) 2009, 2010 Kristaps Dzonsons +.\" Copyright (c) 2010 Ingo Schwarze +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 4 2011 $ +.Dt MDOC 7 +.Os +.Sh NAME +.Nm mdoc +.Nd mdoc language reference +.Sh DESCRIPTION +The +.Nm mdoc +language is used to format +.Bx +.Ux +manuals. +This reference document describes its syntax, structure, and +usage. +The reference implementation is +.Xr mandoc 1 ; +the +.Sx COMPATIBILITY +section describes compatibility with other troff \-mdoc implementations. +.Pp +An +.Nm +document follows simple rules: lines beginning with the control +character +.Sq \. +are parsed for macros. +Other lines are interpreted within the scope of +prior macros: +.Bd -literal -offset indent +\&.Sh Macro lines change control state. +Other lines are interpreted within the current state. +.Ed +.Sh LANGUAGE SYNTAX +.Nm +documents may contain only graphable 7-bit ASCII characters, the space +character, and, in certain circumstances, the tab character. +All manuals must have +.Ux +line terminators. +.Ss Comments +Text following a +.Sq \e\*q , +whether in a macro or free-form text line, is ignored to the end of +line. +A macro line with only a control character and comment escape, +.Sq \&.\e\*q , +is also ignored. +Macro lines with only a control character and optional whitespace are +stripped from input. +.Ss Reserved Characters +Within a macro line, the following characters are reserved: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It \&. +.Pq period +.It \&, +.Pq comma +.It \&: +.Pq colon +.It \&; +.Pq semicolon +.It \&( +.Pq left-parenthesis +.It \&) +.Pq right-parenthesis +.It \&[ +.Pq left-bracket +.It \&] +.Pq right-bracket +.It \&? +.Pq question +.It \&! +.Pq exclamation +.It \&| +.Pq vertical bar +.El +.Pp +Use of reserved characters is described in +.Sx MACRO SYNTAX . +For general use in macro lines, these characters can either be escaped +with a non-breaking space +.Pq Sq \e& +or, if applicable, an appropriate escape sequence can be used. +.Ss Special Characters +Special characters may occur in both macro and free-form lines. +Sequences begin with the escape character +.Sq \e +followed by either an open-parenthesis +.Sq \&( +for two-character sequences; an open-bracket +.Sq \&[ +for n-character sequences (terminated at a close-bracket +.Sq \&] ) ; +or a single one character sequence. +See +.Xr mandoc_char 7 +for a complete list. +Examples include +.Sq \e(em +.Pq em-dash +and +.Sq \ee +.Pq back-slash . +.Ss Text Decoration +Terms may be text-decorated using the +.Sq \ef +escape followed by an indicator: B (bold), I (italic), R (Roman), or P +(revert to previous mode): +.Pp +.Dl \efBbold\efR \efIitalic\efP +.Pp +A numerical representation 3, 2, or 1 (bold, italic, and Roman, +respectively) may be used instead. +A text decoration is valid within +the current font scope only: if a macro opens a font scope alongside +its own scope, such as +.Sx \&Bf +.Cm \&Sy , +in-scope invocations of +.Sq \ef +are only valid within the font scope of the macro. +If +.Sq \ef +is specified outside of any font scope, such as in unenclosed, free-form +text, it will affect the remainder of the document. +.Pp +Note this form is +.Em not +recommended for +.Nm , +which encourages semantic annotation. +.Ss Predefined Strings +Historically, +troff +also defined a set of package-specific +.Dq predefined strings , +which, like +.Sx Special Characters , +mark special output characters and strings by way of input codes. +Predefined strings are escaped with the slash-asterisk, +.Sq \e* : +single-character +.Sq \e*X , +two-character +.Sq \e*(XX , +and N-character +.Sq \e*[N] . +See +.Xr mandoc_char 7 +for a complete list. +Examples include +.Sq \e*(Am +.Pq ampersand +and +.Sq \e*(Ba +.Pq vertical bar . +.Ss Whitespace +Whitespace consists of the space character. +In free-form lines, whitespace is preserved within a line; unescaped +trailing spaces are stripped from input (unless in a literal context). +Blank free-form lines, which may include whitespace, are only permitted +within literal contexts. +.Pp +In macro lines, whitespace delimits arguments and is discarded. +If arguments are quoted, whitespace within the quotes is retained. +.Ss Quotation +Macro arguments may be quoted with double-quotes to group +space-delimited terms or to retain blocks of whitespace. +A quoted argument begins with a double-quote preceded by whitespace. +The next double-quote not pairwise adjacent to another double-quote +terminates the literal, regardless of surrounding whitespace. +.Pp +Note that any quoted text, even if it would cause a macro invocation +when unquoted, is considered literal text. +Thus, the following produces +.Sq Op "Fl a" : +.Bd -literal -offset indent +\&.Op "Fl a" +.Ed +.Pp +In free-form mode, quotes are regarded as opaque text. +.Ss Dates +There are several macros in +.Nm +that require a date argument. +The canonical form for dates is the American format: +.Pp +.D1 Cm Month Day , Year +.Pp +The +.Cm Day +value is an optionally zero-padded numeral. +The +.Cm Month +value is the full month name. +The +.Cm Year +value is the full four-digit year. +.Pp +Reduced form dates are broken-down canonical form dates: +.Pp +.D1 Cm Month , Year +.D1 Cm Year +.Pp +Some examples of valid dates follow: +.Pp +.D1 "May, 2009" Pq reduced form +.D1 "2009" Pq reduced form +.D1 "May 20, 2009" Pq canonical form +.Ss Scaling Widths +Many macros support scaled widths for their arguments, such as +stipulating a two-inch list indentation with the following: +.Bd -literal -offset indent +\&.Bl -tag -width 2i +.Ed +.Pp +The syntax for scaled widths is +.Sq Li [+-]?[0-9]*.[0-9]*[:unit:] , +where a decimal must be preceded or proceeded by at least one digit. +Negative numbers, while accepted, are truncated to zero. +The following scaling units are accepted: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It c +centimetre +.It i +inch +.It P +pica (~1/6 inch) +.It p +point (~1/72 inch) +.It f +synonym for +.Sq u +.It v +default vertical span +.It m +width of rendered +.Sq m +.Pq em +character +.It n +width of rendered +.Sq n +.Pq en +character +.It u +default horizontal span +.It M +mini-em (~1/100 em) +.El +.Pp +Using anything other than +.Sq m , +.Sq n , +.Sq u , +or +.Sq v +is necessarily non-portable across output media. +See +.Sx COMPATIBILITY . +.Ss Sentence Spacing +When composing a manual, make sure that sentences end at the end of +a line. +By doing so, front-ends will be able to apply the proper amount of +spacing after the end of sentence (unescaped) period, exclamation mark, +or question mark followed by zero or more non-sentence closing +delimiters ( +.Ns Sq \&) , +.Sq \&] , +.Sq \&' , +.Sq \&" ) . +.Pp +The proper spacing is also intelligently preserved if a sentence ends at +the boundary of a macro line. +For example: +.Pp +.Dl \&Xr mandoc 1 \. +.Dl \&Fl T \&Ns \&Cm ascii \. +.Sh MANUAL STRUCTURE +A well-formed +.Nm +document consists of a document prologue followed by one or more +sections. +.Pp +The prologue, which consists of the +.Sx \&Dd , +.Sx \&Dt , +and +.Sx \&Os +macros in that order, is required for every document. +.Pp +The first section (sections are denoted by +.Sx \&Sh ) +must be the NAME section, consisting of at least one +.Sx \&Nm +followed by +.Sx \&Nd . +.Pp +Following that, convention dictates specifying at least the +.Em SYNOPSIS +and +.Em DESCRIPTION +sections, although this varies between manual sections. +.Pp +The following is a well-formed skeleton +.Nm +file: +.Bd -literal -offset indent +\&.Dd $\&Mdocdate$ +\&.Dt mdoc 7 +\&.Os +\&.Sh NAME +\&.Nm foo +\&.Nd a description goes here +\&.\e\*q .Sh LIBRARY +\&.\e\*q For sections 2, 3, & 9 only. +\&.\e\*q Not used in OpenBSD. +\&.Sh SYNOPSIS +\&.Nm foo +\&.Op Fl options +\&.Ar +\&.Sh DESCRIPTION +The +\&.Nm +utility processes files ... +\&.\e\*q .Sh IMPLEMENTATION NOTES +\&.\e\*q Not used in OpenBSD. +\&.\e\*q .Sh RETURN VALUES +\&.\e\*q For sections 2, 3, & 9 only. +\&.\e\*q .Sh ENVIRONMENT +\&.\e\*q For sections 1, 6, 7, & 8 only. +\&.\e\*q .Sh FILES +\&.\e\*q .Sh EXIT STATUS +\&.\e\*q For sections 1, 6, & 8 only. +\&.\e\*q .Sh EXAMPLES +\&.\e\*q .Sh DIAGNOSTICS +\&.\e\*q For sections 1, 4, 6, 7, & 8 only. +\&.\e\*q .Sh ERRORS +\&.\e\*q For sections 2, 3, & 9 only. +\&.\e\*q .Sh SEE ALSO +\&.\e\*q .Xr foobar 1 +\&.\e\*q .Sh STANDARDS +\&.\e\*q .Sh HISTORY +\&.\e\*q .Sh AUTHORS +\&.\e\*q .Sh CAVEATS +\&.\e\*q .Sh BUGS +\&.\e\*q .Sh SECURITY CONSIDERATIONS +\&.\e\*q Not used in OpenBSD. +.Ed +.Pp +The sections in an +.Nm +document are conventionally ordered as they appear above. +Sections should be composed as follows: +.Bl -ohang -offset Ds +.It Em NAME +The name(s) and a one line description of the documented material. +The syntax for this as follows: +.Bd -literal -offset indent +\&.Nm name0 , +\&.Nm name1 , +\&.Nm name2 +\&.Nd a one line description +.Ed +.Pp +The +.Sx \&Nm +macro(s) must precede the +.Sx \&Nd +macro. +.Pp +See +.Sx \&Nm +and +.Sx \&Nd . +.It Em LIBRARY +The name of the library containing the documented material, which is +assumed to be a function in a section 2, 3, or 9 manual. +The syntax for this is as follows: +.Bd -literal -offset indent +\&.Lb libarm +.Ed +.Pp +See +.Sx \&Lb . +.It Em SYNOPSIS +Documents the utility invocation syntax, function call syntax, or device +configuration. +.Pp +For the first, utilities (sections 1, 6, and 8), this is +generally structured as follows: +.Bd -literal -offset indent +\&.Nm foo +\&.Op Fl v +\&.Op Fl o Ar file +\&.Op Ar +\&.Nm bar +\&.Op Fl v +\&.Op Fl o Ar file +\&.Op Ar +.Ed +.Pp +For the second, function calls (sections 2, 3, 9): +.Bd -literal -offset indent +\&.In header.h +\&.Vt extern const char *global; +\&.Ft "char *" +\&.Fn foo "const char *src" +\&.Ft "char *" +\&.Fn bar "const char *src" +.Ed +.Pp +And for the third, configurations (section 4): +.Bd -literal -offset indent +\&.Cd \*qit* at isa? port 0x2e\*q +\&.Cd \*qit* at isa? port 0x4e\*q +.Ed +.Pp +Manuals not in these sections generally don't need a +.Em SYNOPSIS . +.Pp +Some macros are displayed differently in the +.Em SYNOPSIS +section, particularly +.Sx \&Nm , +.Sx \&Cd , +.Sx \&Fd , +.Sx \&Fn , +.Sx \&Fo , +.Sx \&In , +.Sx \&Vt , +and +.Sx \&Ft . +All of these macros are output on their own line. +If two such dissimilar macros are pairwise invoked (except for +.Sx \&Ft +before +.Sx \&Fo +or +.Sx \&Fn ) , +they are separated by a vertical space, unless in the case of +.Sx \&Fo , +.Sx \&Fn , +and +.Sx \&Ft , +which are always separated by vertical space. +.Pp +When text and macros following an +.Sx \&Nm +macro starting an input line span multiple output lines, +all output lines but the first will be indented to align +with the text immediately following the +.Sx \&Nm +macro, up to the next +.Sx \&Nm , +.Sx \&Sh , +or +.Sx \&Ss +macro or the end of an enclosing block, whichever comes first. +.It Em DESCRIPTION +This expands upon the brief, one line description in +.Em NAME . +It usually contains a breakdown of the options (if documenting a +command), such as: +.Bd -literal -offset indent +The arguments are as follows: +\&.Bl \-tag \-width Ds +\&.It Fl v +Print verbose information. +\&.El +.Ed +.Pp +Manuals not documenting a command won't include the above fragment. +.It Em IMPLEMENTATION NOTES +Implementation-specific notes should be kept here. +This is useful when implementing standard functions that may have side +effects or notable algorithmic implications. +.It Em RETURN VALUES +This section documents the +return values of functions in sections 2, 3, and 9. +.Pp +See +.Sx \&Rv . +.It Em ENVIRONMENT +Lists the environment variables used by the utility, +and explains the syntax and semantics of their values. +The +.Xr environ 7 +manual provides examples of typical content and formatting. +.Pp +See +.Sx \&Ev . +.It Em FILES +Documents files used. +It's helpful to document both the file name and a short description of how +the file is used (created, modified, etc.). +.Pp +See +.Sx \&Pa . +.It Em EXIT STATUS +This section documents the +command exit status for section 1, 6, and 8 utilities. +Historically, this information was described in +.Em DIAGNOSTICS , +a practise that is now discouraged. +.Pp +See +.Sx \&Ex . +.It Em EXAMPLES +Example usages. +This often contains snippets of well-formed, well-tested invocations. +Make sure that examples work properly! +.It Em DIAGNOSTICS +Documents error conditions. +This is most useful in section 4 manuals. +Historically, this section was used in place of +.Em EXIT STATUS +for manuals in sections 1, 6, and 8; however, this practise is +discouraged. +.Pp +See +.Sx \&Bl +.Fl diag . +.It Em ERRORS +Documents error handling in sections 2, 3, and 9. +.Pp +See +.Sx \&Er . +.It Em SEE ALSO +References other manuals with related topics. +This section should exist for most manuals. +Cross-references should conventionally be ordered first by section, then +alphabetically. +.Pp +See +.Sx \&Xr . +.It Em STANDARDS +References any standards implemented or used. +If not adhering to any standards, the +.Em HISTORY +section should be used instead. +.Pp +See +.Sx \&St . +.It Em HISTORY +A brief history of the subject, including where support first appeared. +.It Em AUTHORS +Credits to the person or persons who wrote the code and/or documentation. +Authors should generally be noted by both name and email address. +.Pp +See +.Sx \&An . +.It Em CAVEATS +Common misuses and misunderstandings should be explained +in this section. +.It Em BUGS +Known bugs, limitations, and work-arounds should be described +in this section. +.It Em SECURITY CONSIDERATIONS +Documents any security precautions that operators should consider. +.El +.Sh MACRO SYNTAX +Macros are one to three three characters in length and begin with a +control character, +.Sq \&. , +at the beginning of the line. +An arbitrary amount of whitespace may sit between the control character +and the macro name. +Thus, the following are equivalent: +.Bd -literal -offset indent +\&.Pp +\&.\ \ \ \&Pp +.Ed +.Pp +The syntax of a macro depends on its classification. +In this section, +.Sq \-arg +refers to macro arguments, which may be followed by zero or more +.Sq parm +parameters; +.Sq \&Yo +opens the scope of a macro; and if specified, +.Sq \&Yc +closes it out. +.Pp +The +.Em Callable +column indicates that the macro may also be called by passing its name +as an argument to another macro. +If a macro is not callable but its name appears as an argument +to another macro, it is interpreted as opaque text. +For example, +.Sq \&.Fl \&Sh +produces +.Sq Fl \&Sh . +.Pp +The +.Em Parsed +column indicates whether the macro may call other macros by receiving +their names as arguments. +If a macro is not parsed but the name of another macro appears +as an argument, it is interpreted as opaque text. +.Pp +The +.Em Scope +column, if applicable, describes closure rules. +.Ss Block full-explicit +Multi-line scope closed by an explicit closing macro. +All macros contains bodies; only +.Sx \&Bf +contains a head. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc +.Ed +.Pp +.Bl -column -compact -offset indent "MacroX" "CallableX" "ParsedX" "closed by XXX" +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope +.It Sx \&Bd Ta \&No Ta \&No Ta closed by Sx \&Ed +.It Sx \&Bf Ta \&No Ta \&No Ta closed by Sx \&Ef +.It Sx \&Bk Ta \&No Ta \&No Ta closed by Sx \&Ek +.It Sx \&Bl Ta \&No Ta \&No Ta closed by Sx \&El +.It Sx \&Ed Ta \&No Ta \&No Ta opened by Sx \&Bd +.It Sx \&Ef Ta \&No Ta \&No Ta opened by Sx \&Bf +.It Sx \&Ek Ta \&No Ta \&No Ta opened by Sx \&Bk +.It Sx \&El Ta \&No Ta \&No Ta opened by Sx \&Bl +.El +.Ss Block full-implicit +Multi-line scope closed by end-of-file or implicitly by another macro. +All macros have bodies; some +.Po +.Sx \&It Fl bullet , +.Fl hyphen , +.Fl dash , +.Fl enum , +.Fl item +.Pc +don't have heads; only one +.Po +.Sx \&It +in +.Sx \&Bl Fl column +.Pc +has multiple heads. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB +\(lBbody...\(rB +.Ed +.Pp +.Bl -column -compact -offset indent "MacroX" "CallableX" "ParsedX" "closed by XXXXXXXXXXX" +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope +.It Sx \&It Ta \&No Ta Yes Ta closed by Sx \&It , Sx \&El +.It Sx \&Nd Ta \&No Ta \&No Ta closed by Sx \&Sh +.It Sx \&Nm Ta \&No Ta Yes Ta closed by Sx \&Nm , Sx \&Sh , Sx \&Ss +.It Sx \&Sh Ta \&No Ta \&No Ta closed by Sx \&Sh +.It Sx \&Ss Ta \&No Ta \&No Ta closed by Sx \&Sh , Sx \&Ss +.El +.Pp +Note that the +.Sx \&Nm +macro is a +.Sx Block full-implicit +macro only when invoked as the first macro +in a +.Em SYNOPSIS +section line, else it is +.Sx In-line . +.Ss Block partial-explicit +Like block full-explicit, but also with single-line scope. +Each has at least a body and, in limited circumstances, a head +.Po +.Sx \&Fo , +.Sx \&Eo +.Pc +and/or tail +.Pq Sx \&Ec . +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc \(lBtail...\(rB + +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \ +\(lBbody...\(rB \&Yc \(lBtail...\(rB +.Ed +.Pp +.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -compact -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope +.It Sx \&Ac Ta Yes Ta Yes Ta opened by Sx \&Ao +.It Sx \&Ao Ta Yes Ta Yes Ta closed by Sx \&Ac +.It Sx \&Bc Ta Yes Ta Yes Ta closed by Sx \&Bo +.It Sx \&Bo Ta Yes Ta Yes Ta opened by Sx \&Bc +.It Sx \&Brc Ta Yes Ta Yes Ta opened by Sx \&Bro +.It Sx \&Bro Ta Yes Ta Yes Ta closed by Sx \&Brc +.It Sx \&Dc Ta Yes Ta Yes Ta opened by Sx \&Do +.It Sx \&Do Ta Yes Ta Yes Ta closed by Sx \&Dc +.It Sx \&Ec Ta Yes Ta Yes Ta opened by Sx \&Eo +.It Sx \&Eo Ta Yes Ta Yes Ta closed by Sx \&Ec +.It Sx \&Fc Ta Yes Ta Yes Ta opened by Sx \&Fo +.It Sx \&Fo Ta \&No Ta \&No Ta closed by Sx \&Fc +.It Sx \&Oc Ta Yes Ta Yes Ta closed by Sx \&Oo +.It Sx \&Oo Ta Yes Ta Yes Ta opened by Sx \&Oc +.It Sx \&Pc Ta Yes Ta Yes Ta closed by Sx \&Po +.It Sx \&Po Ta Yes Ta Yes Ta opened by Sx \&Pc +.It Sx \&Qc Ta Yes Ta Yes Ta opened by Sx \&Oo +.It Sx \&Qo Ta Yes Ta Yes Ta closed by Sx \&Oc +.It Sx \&Re Ta \&No Ta \&No Ta opened by Sx \&Rs +.It Sx \&Rs Ta \&No Ta \&No Ta closed by Sx \&Re +.It Sx \&Sc Ta Yes Ta Yes Ta opened by Sx \&So +.It Sx \&So Ta Yes Ta Yes Ta closed by Sx \&Sc +.It Sx \&Xc Ta Yes Ta Yes Ta opened by Sx \&Xo +.It Sx \&Xo Ta Yes Ta Yes Ta closed by Sx \&Xc +.El +.Ss Block partial-implicit +Like block full-implicit, but with single-line scope closed by +.Sx Reserved Characters +or end of line. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB +.Ed +.Pp +.Bl -column "MacroX" "CallableX" "ParsedX" -compact -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed +.It Sx \&Aq Ta Yes Ta Yes +.It Sx \&Bq Ta Yes Ta Yes +.It Sx \&Brq Ta Yes Ta Yes +.It Sx \&D1 Ta \&No Ta \&Yes +.It Sx \&Dl Ta \&No Ta Yes +.It Sx \&Dq Ta Yes Ta Yes +.It Sx \&Op Ta Yes Ta Yes +.It Sx \&Pq Ta Yes Ta Yes +.It Sx \&Ql Ta Yes Ta Yes +.It Sx \&Qq Ta Yes Ta Yes +.It Sx \&Sq Ta Yes Ta Yes +.It Sx \&Vt Ta Yes Ta Yes +.El +.Pp +Note that the +.Sx \&Vt +macro is a +.Sx Block partial-implicit +only when invoked as the first macro +in a +.Em SYNOPSIS +section line, else it is +.Sx In-line . +.Ss In-line +Closed by +.Sx Reserved Characters , +end of line, fixed argument lengths, and/or subsequent macros. +In-line macros have only text children. +If a number (or inequality) of arguments is +.Pq n , +then the macro accepts an arbitrary number of arguments. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB \(lBres...\(rB + +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB Yc... + +\&.Yo \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN +.Ed +.Pp +.Bl -column "MacroX" "CallableX" "ParsedX" "Arguments" -compact -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Arguments +.It Sx \&%A Ta \&No Ta \&No Ta >0 +.It Sx \&%B Ta \&No Ta \&No Ta >0 +.It Sx \&%C Ta \&No Ta \&No Ta >0 +.It Sx \&%D Ta \&No Ta \&No Ta >0 +.It Sx \&%I Ta \&No Ta \&No Ta >0 +.It Sx \&%J Ta \&No Ta \&No Ta >0 +.It Sx \&%N Ta \&No Ta \&No Ta >0 +.It Sx \&%O Ta \&No Ta \&No Ta >0 +.It Sx \&%P Ta \&No Ta \&No Ta >0 +.It Sx \&%Q Ta \&No Ta \&No Ta >0 +.It Sx \&%R Ta \&No Ta \&No Ta >0 +.It Sx \&%T Ta \&No Ta \&No Ta >0 +.It Sx \&%U Ta \&No Ta \&No Ta >0 +.It Sx \&%V Ta \&No Ta \&No Ta >0 +.It Sx \&Ad Ta Yes Ta Yes Ta n +.It Sx \&An Ta Yes Ta Yes Ta n +.It Sx \&Ap Ta Yes Ta Yes Ta 0 +.It Sx \&Ar Ta Yes Ta Yes Ta n +.It Sx \&At Ta Yes Ta Yes Ta 1 +.It Sx \&Bsx Ta Yes Ta Yes Ta n +.It Sx \&Bt Ta \&No Ta \&No Ta 0 +.It Sx \&Bx Ta Yes Ta Yes Ta n +.It Sx \&Cd Ta Yes Ta Yes Ta >0 +.It Sx \&Cm Ta Yes Ta Yes Ta n +.It Sx \&Db Ta \&No Ta \&No Ta 1 +.It Sx \&Dd Ta \&No Ta \&No Ta n +.It Sx \&Dt Ta \&No Ta \&No Ta n +.It Sx \&Dv Ta Yes Ta Yes Ta n +.It Sx \&Dx Ta Yes Ta Yes Ta n +.It Sx \&Em Ta Yes Ta Yes Ta >0 +.It Sx \&En Ta \&No Ta \&No Ta 0 +.It Sx \&Er Ta Yes Ta Yes Ta >0 +.It Sx \&Es Ta \&No Ta \&No Ta 0 +.It Sx \&Ev Ta Yes Ta Yes Ta n +.It Sx \&Ex Ta \&No Ta \&No Ta n +.It Sx \&Fa Ta Yes Ta Yes Ta n +.It Sx \&Fd Ta \&No Ta \&No Ta >0 +.It Sx \&Fl Ta Yes Ta Yes Ta n +.It Sx \&Fn Ta Yes Ta Yes Ta >0 +.It Sx \&Fr Ta \&No Ta \&No Ta n +.It Sx \&Ft Ta Yes Ta Yes Ta n +.It Sx \&Fx Ta Yes Ta Yes Ta n +.It Sx \&Hf Ta \&No Ta \&No Ta n +.It Sx \&Ic Ta Yes Ta Yes Ta >0 +.It Sx \&In Ta \&No Ta \&No Ta n +.It Sx \&Lb Ta \&No Ta \&No Ta 1 +.It Sx \&Li Ta Yes Ta Yes Ta n +.It Sx \&Lk Ta Yes Ta Yes Ta n +.It Sx \&Lp Ta \&No Ta \&No Ta 0 +.It Sx \&Ms Ta Yes Ta Yes Ta >0 +.It Sx \&Mt Ta Yes Ta Yes Ta >0 +.It Sx \&Nm Ta Yes Ta Yes Ta n +.It Sx \&No Ta Yes Ta Yes Ta 0 +.It Sx \&Ns Ta Yes Ta Yes Ta 0 +.It Sx \&Nx Ta Yes Ta Yes Ta n +.It Sx \&Os Ta \&No Ta \&No Ta n +.It Sx \&Ot Ta \&No Ta \&No Ta n +.It Sx \&Ox Ta Yes Ta Yes Ta n +.It Sx \&Pa Ta Yes Ta Yes Ta n +.It Sx \&Pf Ta Yes Ta Yes Ta 1 +.It Sx \&Pp Ta \&No Ta \&No Ta 0 +.It Sx \&Rv Ta \&No Ta \&No Ta n +.It Sx \&Sm Ta \&No Ta \&No Ta 1 +.It Sx \&St Ta \&No Ta Yes Ta 1 +.It Sx \&Sx Ta Yes Ta Yes Ta >0 +.It Sx \&Sy Ta Yes Ta Yes Ta >0 +.It Sx \&Tn Ta Yes Ta Yes Ta >0 +.It Sx \&Ud Ta \&No Ta \&No Ta 0 +.It Sx \&Ux Ta Yes Ta Yes Ta n +.It Sx \&Va Ta Yes Ta Yes Ta n +.It Sx \&Vt Ta Yes Ta Yes Ta >0 +.It Sx \&Xr Ta Yes Ta Yes Ta >0 +.It Sx \&br Ta \&No Ta \&No Ta 0 +.It Sx \&sp Ta \&No Ta \&No Ta 1 +.El +.Sh REFERENCE +This section is a canonical reference of all macros, arranged +alphabetically. +For the scoping of individual macros, see +.Sx MACRO SYNTAX . +.Ss \&%A +Author name of an +.Sx \&Rs +block. +Multiple authors should each be accorded their own +.Sx \%%A +line. +Author names should be ordered with full or abbreviated forename(s) +first, then full surname. +.Ss \&%B +Book title of an +.Sx \&Rs +block. +This macro may also be used in a non-bibliographic context when +referring to book titles. +.Ss \&%C +Publication city or location of an +.Sx \&Rs +block. +.Ss \&%D +Publication date of an +.Sx \&Rs +block. +This should follow the reduced or canonical form syntax described in +.Sx Dates . +.Ss \&%I +Publisher or issuer name of an +.Sx \&Rs +block. +.Ss \&%J +Journal name of an +.Sx \&Rs +block. +.Ss \&%N +Issue number (usually for journals) of an +.Sx \&Rs +block. +.Ss \&%O +Optional information of an +.Sx \&Rs +block. +.Ss \&%P +Book or journal page number of an +.Sx \&Rs +block. +.Ss \&%Q +Institutional author (school, government, etc.) of an +.Sx \&Rs +block. +Multiple institutional authors should each be accorded their own +.Sx \&%Q +line. +.Ss \&%R +Technical report name of an +.Sx \&Rs +block. +.Ss \&%T +Article title of an +.Sx \&Rs +block. +This macro may also be used in a non-bibliographical context when +referring to article titles. +.Ss \&%U +URI of reference document. +.Ss \&%V +Volume number of an +.Sx \&Rs +block. +.Ss \&Ac +Close an +.Sx \&Ao +block. +Does not have any tail arguments. +.Ss \&Ad +Memory address. +Do not use this for postal addresses. +.Pp +Examples: +.Dl \&.Ad [0,$] +.Dl \&.Ad 0x00000000 +.Ss \&An +Author name. +Requires either the name of an author or one of the following arguments: +.Pp +.Bl -tag -width "-nosplitX" -offset indent -compact +.It Fl split +Start a new output line before each subsequent invocation of +.Sx \&An . +.It Fl nosplit +The opposite of +.Fl split . +.El +.Pp +The default is +.Fl nosplit . +The effect of selecting either of the +.Fl split +modes ends at the beginning of the +.Em AUTHORS +section. +In the +.Em AUTHORS +section, the default is +.Fl nosplit +for the first author listing and +.Fl split +for all other author listings. +.Pp +Examples: +.Dl \&.An -nosplit +.Dl \&.An Kristaps Dzonsons \&Aq kristaps@bsd.lv +.Ss \&Ao +Begin a block enclosed by angle brackets. +Does not have any head arguments. +.Pp +Examples: +.Dl \&.Fl -key= \&Ns \&Ao \&Ar val \&Ac +.Pp +See also +.Sx \&Aq . +.Ss \&Ap +Inserts an apostrophe without any surrounding whitespace. +This is generally used as a grammatical device when referring to the verb +form of a function. +.Pp +Examples: +.Dl \&.Fn execve \&Ap d +.Ss \&Aq +Encloses its arguments in angle brackets. +.Pp +Examples: +.Dl \&.Fl -key= \&Ns \&Aq \&Ar val +.Pp +.Em Remarks : +this macro is often abused for rendering URIs, which should instead use +.Sx \&Lk +or +.Sx \&Mt , +or to note pre-processor +.Dq Li #include +statements, which should use +.Sx \&In . +.Pp +See also +.Sx \&Ao . +.Ss \&Ar +Command arguments. +If an argument is not provided, the string +.Dq file ...\& +is used as a default. +.Pp +Examples: +.Dl \&.Fl o \&Ns \&Ar file1 +.Dl \&.Ar +.Dl \&.Ar arg1 , arg2 . +.Ss \&At +Formats an AT&T version. +Accepts one optional argument: +.Pp +.Bl -tag -width "v[1-7] | 32vX" -offset indent -compact +.It Cm v[1-7] | 32v +A version of +.At . +.It Cm V[.[1-4]]? +A version of +.At V . +.El +.Pp +Note that these arguments do not begin with a hyphen. +.Pp +Examples: +.Dl \&.At +.Dl \&.At V.1 +.Pp +See also +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Bc +Close a +.Sx \&Bo +block. +Does not have any tail arguments. +.Ss \&Bd +Begin a display block. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Bd +.Fl Ns Ar type +.Op Fl offset Ar width +.Op Fl compact +.Ed +.Pp +Display blocks are used to select a different indentation and +justification than the one used by the surrounding text. +They may contain both macro lines and free-form text lines. +By default, a display block is preceded by a vertical space. +.Pp +The +.Ar type +must be one of the following: +.Bl -tag -width 13n -offset indent +.It Fl centered +Centre-justify each line. +Using this display type is not recommended; many +.Nm +implementations render it poorly. +.It Fl filled +Left- and right-justify the block. +.It Fl literal +Do not justify the block at all. +Preserve white space as it appears in the input. +.It Fl ragged +Only left-justify the block. +.It Fl unfilled +An alias for +.Fl literal . +.El +.Pp +The +.Ar type +must be provided first. +Additional arguments may follow: +.Bl -tag -width 13n -offset indent +.It Fl offset Ar width +Indent the display by the +.Ar width , +which may be one of the following: +.Bl -item +.It +One of the pre-defined strings +.Cm indent , +the width of standard indentation; +.Cm indent-two , +twice +.Cm indent ; +.Cm left , +which has no effect; +.Cm right , +which justifies to the right margin; or +.Cm center , +which aligns around an imagined centre axis. +.It +A macro invocation, which selects a predefined width +associated with that macro. +The most popular is the imaginary macro +.Ar \&Ds , +which resolves to +.Sy 6n . +.It +A width using the syntax described in +.Sx Scaling Widths . +.It +An arbitrary string, which indents by the length of this string. +.El +.Pp +When the argument is missing, +.Fl offset +is ignored. +.It Fl compact +Do not assert vertical space before the display. +.El +.Pp +Examples: +.Bd -literal -offset indent +\&.Bd \-literal \-offset indent \-compact + Hello world. +\&.Ed +.Ed +.Pp +See also +.Sx \&D1 +and +.Sx \&Dl . +.Ss \&Bf +Change the font mode for a scoped block of text. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Bf +.Oo +.Fl emphasis | literal | symbolic | +.Cm \&Em | \&Li | \&Sy +.Oc +.Ed +.Pp +The +.Fl emphasis +and +.Cm \&Em +argument are equivalent, as are +.Fl symbolic +and +.Cm \&Sy , +and +.Fl literal +and +.Cm \&Li . +Without an argument, this macro does nothing. +The font mode continues until broken by a new font mode in a nested +scope or +.Sx \&Ef +is encountered. +.Pp +See also +.Sx \&Li , +.Sx \&Ef , +.Sx \&Em , +and +.Sx \&Sy . +.Ss \&Bk +Keep the output generated from each macro input line together +on one single output line. +Line breaks in free-form text lines are unaffected. +The syntax is as follows: +.Pp +.D1 Pf \. Sx \&Bk Fl words +.Pp +The +.Fl words +argument is required; additional arguments are ignored. +.Pp +The following example will not break within each +.Sx \&Op +macro line: +.Bd -literal -offset indent +\&.Bk \-words +\&.Op Fl f Ar flags +\&.Op Fl o Ar output +\&.Ek +.Ed +.Pp +Be careful in using over-long lines within a keep block! +Doing so will clobber the right margin. +.Ss \&Bl +Begin a list. +Lists consist of items specified using the +.Sx \&It +macro, containing a head or a body or both. +The list syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Bl +.Fl Ns Ar type +.Op Fl width Ar val +.Op Fl offset Ar val +.Op Fl compact +.Op HEAD ... +.Ed +.Pp +The list +.Ar type +is mandatory and must be specified first. +The +.Fl width +and +.Fl offset +arguments accept +.Sx Scaling Widths +or use the length of the given string. +The +.Fl offset +is a global indentation for the whole list, affecting both item heads +and bodies. +For those list types supporting it, the +.Fl width +argument requests an additional indentation of item bodies, +to be added to the +.Fl offset . +Unless the +.Fl compact +argument is specified, list entries are separated by vertical space. +.Pp +A list must specify one of the following list types: +.Bl -tag -width 12n -offset indent +.It Fl bullet +No item heads can be specified, but a bullet will be printed at the head +of each item. +Item bodies start on the same output line as the bullet +and are indented according to the +.Fl width +argument. +.It Fl column +A columnated list. +The +.Fl width +argument has no effect; instead, each argument specifies the width +of one column, using either the +.Sx Scaling Widths +syntax or the string length of the argument. +If the first line of the body of a +.Fl column +list is not an +.Sx \&It +macro line, +.Sx \&It +contexts spanning one input line each are implied until an +.Sx \&It +macro line is encountered, at which point items start being interpreted as +described in the +.Sx \&It +documentation. +.It Fl dash +Like +.Fl bullet , +except that dashes are used in place of bullets. +.It Fl diag +Like +.Fl inset , +except that item heads are not parsed for macro invocations. +.\" but with additional formatting to the head. +.It Fl enum +A numbered list. +Formatted like +.Fl bullet , +except that cardinal numbers are used in place of bullets, +starting at 1. +.It Fl hang +Like +.Fl tag , +except that the first lines of item bodies are not indented, but follow +the item heads like in +.Fl inset +lists. +.It Fl hyphen +Synonym for +.Fl dash . +.It Fl inset +Item bodies follow items heads on the same line, using normal inter-word +spacing. +Bodies are not indented, and the +.Fl width +argument is ignored. +.It Fl item +No item heads can be specified, and none are printed. +Bodies are not indented, and the +.Fl width +argument is ignored. +.It Fl ohang +Item bodies start on the line following item heads and are not indented. +The +.Fl width +argument is ignored. +.It Fl tag +Item bodies are indented according to the +.Fl width +argument. +When an item head fits inside the indentation, the item body follows +this head on the same output line. +Otherwise, the body starts on the output line following the head. +.El +.Pp +See also +.Sx \&El +and +.Sx \&It . +.Ss \&Bo +Begin a block enclosed by square brackets. +Does not have any head arguments. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Bo 1 , +\&.Dv BUFSIZ \&Bc +.Ed +.Pp +See also +.Sx \&Bq . +.Ss \&Bq +Encloses its arguments in square brackets. +.Pp +Examples: +.Dl \&.Bq 1 , \&Dv BUFSIZ +.Pp +.Em Remarks : +this macro is sometimes abused to emulate optional arguments for +commands; the correct macros to use for this purpose are +.Sx \&Op , +.Sx \&Oo , +and +.Sx \&Oc . +.Pp +See also +.Sx \&Bo . +.Ss \&Brc +Close a +.Sx \&Bro +block. +Does not have any tail arguments. +.Ss \&Bro +Begin a block enclosed by curly braces. +Does not have any head arguments. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Bro 1 , ... , +\&.Va n \&Brc +.Ed +.Pp +See also +.Sx \&Brq . +.Ss \&Brq +Encloses its arguments in curly braces. +.Pp +Examples: +.Dl \&.Brq 1 , ... , \&Va n +.Pp +See also +.Sx \&Bro . +.Ss \&Bsx +Format the BSD/OS version provided as an argument, or a default value if +no argument is provided. +.Pp +Examples: +.Dl \&.Bsx 1.0 +.Dl \&.Bsx +.Pp +See also +.Sx \&At , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Bt +Prints +.Dq is currently in beta test . +.Ss \&Bx +Format the BSD version provided as an argument, or a default value if no +argument is provided. +.Pp +Examples: +.Dl \&.Bx 4.4 +.Dl \&.Bx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Cd +Kernel configuration declaration. +This denotes strings accepted by +.Xr config 8 . +.Pp +Examples: +.Dl \&.Cd device le0 at scode? +.Pp +.Em Remarks : +this macro is commonly abused by using quoted literals to retain +whitespace and align consecutive +.Sx \&Cd +declarations. +This practise is discouraged. +.Ss \&Cm +Command modifiers. +Useful when specifying configuration options or keys. +.Pp +Examples: +.Dl \&.Cm ControlPath +.Dl \&.Cm ControlMaster +.Pp +See also +.Sx \&Fl . +.Ss \&D1 +One-line indented display. +This is formatted by the default rules and is useful for simple indented +statements. +It is followed by a newline. +.Pp +Examples: +.Dl \&.D1 \&Fl abcdefgh +.Pp +See also +.Sx \&Bd +and +.Sx \&Dl . +.Ss \&Db +Switch debugging mode. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Db Cm on | off +.Pp +This macro is ignored by +.Xr mandoc 1 . +.Ss \&Dc +Close a +.Sx \&Do +block. +Does not have any tail arguments. +.Ss \&Dd +Document date. +This is the mandatory first macro of any +.Nm +manual. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Dd Op Ar date +.Pp +The +.Ar date +may be either +.Ar $\&Mdocdate$ , +which signifies the current manual revision date dictated by +.Xr cvs 1 , +or instead a valid canonical date as specified by +.Sx Dates . +If a date does not conform or is empty, the current date is used. +.Pp +Examples: +.Dl \&.Dd $\&Mdocdate$ +.Dl \&.Dd $\&Mdocdate: July 21 2007$ +.Dl \&.Dd July 21, 2007 +.Pp +See also +.Sx \&Dt +and +.Sx \&Os . +.Ss \&Dl +One-line intended display. +This is formatted as literal text and is useful for commands and +invocations. +It is followed by a newline. +.Pp +Examples: +.Dl \&.Dl % mandoc mdoc.7 \e(ba less +.Pp +See also +.Sx \&Bd +and +.Sx \&D1 . +.Ss \&Do +Begin a block enclosed by double quotes. +Does not have any head arguments. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Do +April is the cruellest month +\&.Dc +\e(em T.S. Eliot +.Ed +.Pp +See also +.Sx \&Dq . +.Ss \&Dq +Encloses its arguments in +.Dq typographic +double-quotes. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Dq April is the cruellest month +\e(em T.S. Eliot +.Ed +.Pp +See also +.Sx \&Qq , +.Sx \&Sq , +and +.Sx \&Do . +.Ss \&Dt +Document title. +This is the mandatory second macro of any +.Nm +file. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Dt +.Oo +.Ar title +.Oo +.Ar section +.Op Ar volume | arch +.Oc +.Oc +.Ed +.Pp +Its arguments are as follows: +.Bl -tag -width Ds -offset Ds +.It Ar title +The document's title (name), defaulting to +.Dq UNKNOWN +if unspecified. +It should be capitalised. +.It Ar section +The manual section. +This may be one of +.Ar 1 +.Pq utilities , +.Ar 2 +.Pq system calls , +.Ar 3 +.Pq libraries , +.Ar 3p +.Pq Perl libraries , +.Ar 4 +.Pq devices , +.Ar 5 +.Pq file formats , +.Ar 6 +.Pq games , +.Ar 7 +.Pq miscellaneous , +.Ar 8 +.Pq system utilities , +.Ar 9 +.Pq kernel functions , +.Ar X11 +.Pq X Window System , +.Ar X11R6 +.Pq X Window System , +.Ar unass +.Pq unassociated , +.Ar local +.Pq local system , +.Ar draft +.Pq draft manual , +or +.Ar paper +.Pq paper . +It should correspond to the manual's filename suffix and defaults to +.Dq 1 +if unspecified. +.It Ar volume +This overrides the volume inferred from +.Ar section . +This field is optional, and if specified, must be one of +.Ar USD +.Pq users' supplementary documents , +.Ar PS1 +.Pq programmers' supplementary documents , +.Ar AMD +.Pq administrators' supplementary documents , +.Ar SMM +.Pq system managers' manuals , +.Ar URM +.Pq users' reference manuals , +.Ar PRM +.Pq programmers' reference manuals , +.Ar KM +.Pq kernel manuals , +.Ar IND +.Pq master index , +.Ar MMI +.Pq master index , +.Ar LOCAL +.Pq local manuals , +.Ar LOC +.Pq local manuals , +or +.Ar CON +.Pq contributed manuals . +.It Ar arch +This specifies a specific relevant architecture. +If +.Ar volume +is not provided, it may be used in its place, else it may be used +subsequent that. +It, too, is optional. +It must be one of +.Ar alpha , +.Ar amd64 , +.Ar amiga , +.Ar arc , +.Ar arm , +.Ar armish , +.Ar aviion , +.Ar hp300 , +.Ar hppa , +.Ar hppa64 , +.Ar i386 , +.Ar landisk , +.Ar loongson , +.Ar luna88k , +.Ar mac68k , +.Ar macppc , +.Ar mips64 , +.Ar mvme68k , +.Ar mvme88k , +.Ar mvmeppc , +.Ar pmax , +.Ar sgi , +.Ar socppc , +.Ar sparc , +.Ar sparc64 , +.Ar sun3 , +.Ar vax , +or +.Ar zaurus . +.El +.Pp +Examples: +.Dl \&.Dt FOO 1 +.Dl \&.Dt FOO 4 KM +.Dl \&.Dt FOO 9 i386 +.Pp +See also +.Sx \&Dd +and +.Sx \&Os . +.Ss \&Dv +Defined variables such as preprocessor constants. +.Pp +Examples: +.Dl \&.Dv BUFSIZ +.Dl \&.Dv STDOUT_FILENO +.Pp +See also +.Sx \&Er . +.Ss \&Dx +Format the DragonFly BSD version provided as an argument, or a default +value if no argument is provided. +.Pp +Examples: +.Dl \&.Dx 2.4.1 +.Dl \&.Dx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Ec +Close a scope started by +.Sx \&Eo . +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ec Op Ar TERM +.Pp +The +.Ar TERM +argument is used as the enclosure tail, for example, specifying \e(rq +will emulate +.Sx \&Dc . +.Ss \&Ed +End a display context started by +.Sx \&Bd . +.Ss \&Ef +End a font mode context started by +.Sx \&Bf . +.Ss \&Ek +End a keep context started by +.Sx \&Bk . +.Ss \&El +End a list context started by +.Sx \&Bl . +.Pp +See also +.Sx \&Bl +and +.Sx \&It . +.Ss \&Em +Denotes text that should be emphasised. +Note that this is a presentation term and should not be used for +stylistically decorating technical terms. +.Pp +Examples: +.Dl \&.Em Warnings! +.Dl \&.Em Remarks : +.Pp +See also +.Sx \&Bf , +.Sx \&Sy , +and +.Sx \&Li . +.Ss \&En +This macro is obsolete and not implemented in +.Xr mandoc 1 . +.Ss \&Eo +An arbitrary enclosure. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Eo Op Ar TERM +.Pp +The +.Ar TERM +argument is used as the enclosure head, for example, specifying \e(lq +will emulate +.Sx \&Do . +.Ss \&Er +Display error constants. +.Pp +Examples: +.Dl \&.Er EPERM +.Dl \&.Er ENOENT +.Pp +See also +.Sx \&Dv . +.Ss \&Es +This macro is obsolete and not implemented. +.Ss \&Ev +Environmental variables such as those specified in +.Xr environ 7 . +.Pp +Examples: +.Dl \&.Ev DISPLAY +.Dl \&.Ev PATH +.Ss \&Ex +Insert a standard sentence regarding exit values. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ex Fl std Op Ar utility +.Pp +When +.Ar utility +is not specified, the document's name set by +.Sx \&Nm +is used. +.Pp +See also +.Sx \&Rv . +.Ss \&Fa +Function argument. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Fa +.Op Cm argtype +.Cm argname +.Ed +.Pp +This may be invoked for names with or without the corresponding type. +It is also used to specify the field name of a structure. +Most often, the +.Sx \&Fa +macro is used in the +.Em SYNOPSIS +within +.Sx \&Fo +section when documenting multi-line function prototypes. +If invoked with multiple arguments, the arguments are separated by a +comma. +Furthermore, if the following macro is another +.Sx \&Fa , +the last argument will also have a trailing comma. +.Pp +Examples: +.Dl \&.Fa \(dqconst char *p\(dq +.Dl \&.Fa \(dqint a\(dq \(dqint b\(dq \(dqint c\(dq +.Dl \&.Fa foo +.Pp +See also +.Sx \&Fo . +.Ss \&Fc +End a function context started by +.Sx \&Fo . +.Ss \&Fd +Historically used to document include files. +This usage has been deprecated in favour of +.Sx \&In . +Do not use this macro. +.Pp +See also +.Sx MANUAL STRUCTURE +and +.Sx \&In . +.Ss \&Fl +Command-line flag. +Used when listing arguments to command-line utilities. +Prints a fixed-width hyphen +.Sq \- +directly followed by each argument. +If no arguments are provided, a hyphen is printed followed by a space. +If the argument is a macro, a hyphen is prefixed to the subsequent macro +output. +.Pp +Examples: +.Dl \&.Fl a b c +.Dl \&.Fl \&Pf a b +.Dl \&.Fl +.Dl \&.Op \&Fl o \&Ns \&Ar file +.Pp +See also +.Sx \&Cm . +.Ss \&Fn +A function name. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Ns Sx \&Fn +.Op Cm functype +.Cm funcname +.Op Oo Cm argtype Oc Cm argname +.Ed +.Pp +Function arguments are surrounded in parenthesis and +are delimited by commas. +If no arguments are specified, blank parenthesis are output. +.Pp +Examples: +.Dl \&.Fn "int funcname" "int arg0" "int arg1" +.Dl \&.Fn funcname "int arg0" +.Dl \&.Fn funcname arg0 +.Bd -literal -offset indent -compact +\&.Ft functype +\&.Fn funcname +.Ed +.Pp +When referring to a function documented in another manual page, use +.Sx \&Xr +instead. +See also +.Sx MANUAL STRUCTURE +and +.Sx \&Ft . +.Ss \&Fo +Begin a function block. +This is a multi-line version of +.Sx \&Fn . +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Fo Cm funcname +.Pp +Invocations usually occur in the following context: +.Bd -ragged -offset indent +.Pf \. Sx \&Ft Cm functype +.br +.Pf \. Sx \&Fo Cm funcname +.br +.Pf \. Sx \&Fa Oo Cm argtype Oc Cm argname +.br +\.\.\. +.br +.Pf \. Sx \&Fc +.Ed +.Pp +A +.Sx \&Fo +scope is closed by +.Pp +See also +.Sx MANUAL STRUCTURE , +.Sx \&Fa , +.Sx \&Fc , +and +.Sx \&Ft . +.Ss \&Ft +A function type. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ft Cm functype +.Pp +Examples: +.Dl \&.Ft int +.Bd -literal -offset indent -compact +\&.Ft functype +\&.Fn funcname +.Ed +.Pp +See also +.Sx MANUAL STRUCTURE , +.Sx \&Fn , +and +.Sx \&Fo . +.Ss \&Fx +Format the +.Fx +version provided as an argument, or a default value +if no argument is provided. +.Pp +Examples: +.Dl \&.Fx 7.1 +.Dl \&.Fx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Hf +This macro is obsolete and not implemented. +.Ss \&Ic +Designate an internal or interactive command. +This is similar to +.Sx \&Cm +but used for instructions rather than values. +.Pp +Examples: +.Dl \&.Ic hash +.Dl \&.Ic alias +.Pp +Note that using +.Sx \&Bd Fl literal +or +.Sx \&D1 +is preferred for displaying code; the +.Sx \&Ic +macro is used when referring to specific instructions. +.Ss \&In +An +.Dq include +file. +In the +.Em SYNOPSIS +section (only if invoked as the line macro), the first argument is +preceded by +.Dq #include , +the arguments is enclosed in angle brackets. +.Pp +Examples: +.Dl \&.In sys/types +.Pp +See also +.Sx MANUAL STRUCTURE . +.Ss \&It +A list item. +The syntax of this macro depends on the list type. +.Pp +Lists +of type +.Fl hang , +.Fl ohang , +.Fl inset , +and +.Fl diag +have the following syntax: +.Pp +.D1 Pf \. Sx \&It Cm args +.Pp +Lists of type +.Fl bullet , +.Fl dash , +.Fl enum , +.Fl hyphen +and +.Fl item +have the following syntax: +.Pp +.D1 Pf \. Sx \&It +.Pp +with subsequent lines interpreted within the scope of the +.Sx \&It +until either a closing +.Sx \&El +or another +.Sx \&It . +.Pp +The +.Fl tag +list has the following syntax: +.Pp +.D1 Pf \. Sx \&It Op Cm args +.Pp +Subsequent lines are interpreted as with +.Fl bullet +and family. +The line arguments correspond to the list's left-hand side; body +arguments correspond to the list's contents. +.Pp +The +.Fl column +list is the most complicated. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&It Op Cm args +.Pp +The +.Cm args +are phrases, a mix of macros and text corresponding to a line column, +delimited by tabs or the special +.Sq \&Ta +pseudo-macro. +Lines subsequent the +.Sx \&It +are interpreted within the scope of the last phrase. +Calling the pseudo-macro +.Sq \&Ta +will open a new phrase scope (this must occur on a macro line to be +interpreted as a macro). +Note that the tab phrase delimiter may only be used within the +.Sx \&It +line itself. +Subsequent this, only the +.Sq \&Ta +pseudo-macro may be used to delimit phrases. +Furthermore, note that quoted sections propagate over tab-delimited +phrases on an +.Sx \&It , +for example, +.Pp +.Dl .It \(dqcol1 ; col2 ;\(dq \&; +.Pp +will preserve the semicolon whitespace except for the last. +.Pp +See also +.Sx \&Bl . +.Ss \&Lb +Specify a library. +The syntax is as follows: +.Pp +.D1 Pf \. Sx \&Lb Cm library +.Pp +The +.Cm library +parameter may be a system library, such as +.Cm libz +or +.Cm libpam , +in which case a small library description is printed next to the linker +invocation; or a custom library, in which case the library name is +printed in quotes. +This is most commonly used in the +.Em SYNOPSIS +section as described in +.Sx MANUAL STRUCTURE . +.Pp +Examples: +.Dl \&.Lb libz +.Dl \&.Lb mdoc +.Ss \&Li +Denotes text that should be in a literal font mode. +Note that this is a presentation term and should not be used for +stylistically decorating technical terms. +.Pp +See also +.Sx \&Bf , +.Sx \&Sy , +and +.Sx \&Em . +.Ss \&Lk +Format a hyperlink. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Lk Cm uri Op Cm name +.Pp +Examples: +.Dl \&.Lk http://bsd.lv \*qThe BSD.lv Project\*q +.Dl \&.Lk http://bsd.lv +.Pp +See also +.Sx \&Mt . +.Ss \&Lp +Synonym for +.Sx \&Pp . +.Ss \&Ms +Display a mathematical symbol. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ms Cm symbol +.Pp +Examples: +.Dl \&.Ms sigma +.Dl \&.Ms aleph +.Ss \&Mt +Format a +.Dq mailto: +hyperlink. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Mt Cm address +.Pp +Examples: +.Dl \&.Mt discuss@manpages.bsd.lv +.Ss \&Nd +A one line description of the manual's content. +This may only be invoked in the +.Em SYNOPSIS +section subsequent the +.Sx \&Nm +macro. +.Pp +Examples: +.Dl \&.Sx \&Nd mdoc language reference +.Dl \&.Sx \&Nd format and display UNIX manuals +.Pp +The +.Sx \&Nd +macro technically accepts child macros and terminates with a subsequent +.Sx \&Sh +invocation. +Do not assume this behaviour: some +.Xr whatis 1 +database generators are not smart enough to parse more than the line +arguments and will display macros verbatim. +.Pp +See also +.Sx \&Nm . +.Ss \&Nm +The name of the manual page, or \(em in particular in section 1, 6, +and 8 pages \(em of an additional command or feature documented in +the manual page. +When first invoked, the +.Sx \&Nm +macro expects a single argument, the name of the manual page. +Usually, the first invocation happens in the +.Em NAME +section of the page. +The specified name will be remembered and used whenever the macro is +called again without arguments later in the page. +The +.Sx \&Nm +macro uses +.Sx Block full-implicit +semantics when invoked as the first macro on an input line in the +.Em SYNOPSIS +section; otherwise, it uses ordinary +.Sx In-line +semantics. +.Pp +Examples: +.Bd -literal -offset indent +\&.Sh SYNOPSIS +\&.Nm cat +\&.Op Fl benstuv +\&.Op Ar +.Ed +.Pp +In the +.Em SYNOPSIS +of section 2, 3 and 9 manual pages, use the +.Sx \&Fn +macro rather than +.Sx \&Nm +to mark up the name of the manual page. +.Ss \&No +A +.Dq noop +macro used to terminate prior macro contexts. +.Pp +Examples: +.Dl \&.Sx \&Fl ab \&No cd \&Fl ef +.Ss \&Ns +Suppress a space. +Following invocation, text is interpreted as free-form text until a +macro is encountered. +.Pp +Examples: +.Dl \&.Fl o \&Ns \&Ar output +.Pp +See also +.Sx \&No +and +.Sx \&Sm . +.Ss \&Nx +Format the +.Nx +version provided as an argument, or a default value if +no argument is provided. +.Pp +Examples: +.Dl \&.Nx 5.01 +.Dl \&.Nx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Oc +Close multi-line +.Sx \&Oo +context. +.Ss \&Oo +Multi-line version of +.Sx \&Op . +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Oo +\&.Op Fl flag Ns Ar value +\&.Oc +.Ed +.Ss \&Op +Command-line option. +Used when listing options to command-line utilities. +Prints the argument(s) in brackets. +.Pp +Examples: +.Dl \&.Op \&Fl a \&Ar b +.Dl \&.Op \&Ar a | b +.Pp +See also +.Sx \&Oo . +.Ss \&Os +Document operating system version. +This is the mandatory third macro of +any +.Nm +file. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Os Op Cm system Op Cm version +.Pp +The optional +.Cm system +parameter specifies the relevant operating system or environment. +Left unspecified, it defaults to the local operating system version. +This is the suggested form. +.Pp +Examples: +.Dl \&.Os +.Dl \&.Os KTH/CSC/TCS +.Dl \&.Os BSD 4.3 +.Pp +See also +.Sx \&Dd +and +.Sx \&Dt . +.Ss \&Ot +Unknown usage. +.Pp +.Em Remarks : +this macro has been deprecated. +.Ss \&Ox +Format the +.Ox +version provided as an argument, or a default value +if no argument is provided. +.Pp +Examples: +.Dl \&.Ox 4.5 +.Dl \&.Ox +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +and +.Sx \&Ux . +.Ss \&Pa +A file-system path. +If an argument is not provided, the string +.Dq \(ti +is used as a default. +.Pp +Examples: +.Dl \&.Pa /usr/bin/mandoc +.Dl \&.Pa /usr/share/man/man7/mdoc.7 +.Pp +See also +.Sx \&Lk . +.Ss \&Pc +Close parenthesised context opened by +.Sx \&Po . +.Ss \&Pf +Removes the space +.Pq Dq prefix +between its arguments. +Its syntax is as follows: +.Pp +.D1 Pf \. \&Pf Cm prefix suffix +.Pp +The +.Cm suffix +argument may be a macro. +.Pp +Examples: +.Dl \&.Pf \e. \&Sx \&Pf \&Cm prefix suffix +.Ss \&Po +Multi-line version of +.Sx \&Pq . +.Ss \&Pp +Break a paragraph. +This will assert vertical space between prior and subsequent macros +and/or text. +.Ss \&Pq +Parenthesised enclosure. +.Pp +See also +.Sx \&Po . +.Ss \&Qc +Close quoted context opened by +.Sx \&Qo . +.Ss \&Ql +Format a single-quoted literal. +See also +.Sx \&Qq +and +.Sx \&Sq . +.Ss \&Qo +Multi-line version of +.Sx \&Qq . +.Ss \&Qq +Encloses its arguments in +.Dq typewriter +double-quotes. +Consider using +.Sx \&Dq . +.Pp +See also +.Sx \&Dq , +.Sx \&Sq , +and +.Sx \&Qo . +.Ss \&Re +Close an +.Sx \&Rs +block. +Does not have any tail arguments. +.Ss \&Rs +Begin a bibliographic +.Pq Dq reference +block. +Does not have any head arguments. +The block macro may only contain +.Sx \&%A , +.Sx \&%B , +.Sx \&%C , +.Sx \&%D , +.Sx \&%I , +.Sx \&%J , +.Sx \&%N , +.Sx \&%O , +.Sx \&%P , +.Sx \&%Q , +.Sx \&%R , +.Sx \&%T , +.Sx \&%U , +and +.Sx \&%V +child macros (at least one must be specified). +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Rs +\&.%A J. E. Hopcroft +\&.%A J. D. Ullman +\&.%B Introduction to Automata Theory, Languages, and Computation +\&.%I Addison-Wesley +\&.%C Reading, Massachusettes +\&.%D 1979 +\&.Re +.Ed +.Pp +If an +.Sx \&Rs +block is used within a SEE ALSO section, a vertical space is asserted +before the rendered output, else the block continues on the current +line. +.Ss \&Rv +Inserts text regarding a function call's return value. +This macro must consist of the +.Fl std +argument followed by an optional +.Ar function . +If +.Ar function +is not provided, the document's name as stipulated by the first +.Sx \&Nm +is provided. +.Pp +See also +.Sx \&Ex . +.Ss \&Sc +Close single-quoted context opened by +.Sx \&So . +.Ss \&Sh +Begin a new section. +For a list of conventional manual sections, see +.Sx MANUAL STRUCTURE . +These sections should be used unless it's absolutely necessary that +custom sections be used. +.Pp +Section names should be unique so that they may be keyed by +.Sx \&Sx . +.Pp +See also +.Sx \&Pp , +.Sx \&Ss , +and +.Sx \&Sx . +.Ss \&Sm +Switches the spacing mode for output generated from macros. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Sm Cm on | off +.Pp +By default, spacing is +.Cm on . +When switched +.Cm off , +no white space is inserted between macro arguments and between the +output generated from adjacent macros, but free-form text lines +still get normal spacing between words and sentences. +.Ss \&So +Multi-line version of +.Sx \&Sq . +.Ss \&Sq +Encloses its arguments in +.Dq typewriter +single-quotes. +.Pp +See also +.Sx \&Dq , +.Sx \&Qq , +and +.Sx \&So . +.Ss \&Ss +Begin a new sub-section. +Unlike with +.Sx \&Sh , +there's no convention for sub-sections. +Conventional sections, as described in +.Sx MANUAL STRUCTURE , +rarely have sub-sections. +.Pp +Sub-section names should be unique so that they may be keyed by +.Sx \&Sx . +.Pp +See also +.Sx \&Pp , +.Sx \&Sh , +and +.Sx \&Sx . +.Ss \&St +Replace an abbreviation for a standard with the full form. +The following standards are recognised: +.Pp +.Bl -tag -width "-p1003.1g-2000X" -compact +.It \-p1003.1-88 +.St -p1003.1-88 +.It \-p1003.1-90 +.St -p1003.1-90 +.It \-p1003.1-96 +.St -p1003.1-96 +.It \-p1003.1-2001 +.St -p1003.1-2001 +.It \-p1003.1-2004 +.St -p1003.1-2004 +.It \-p1003.1-2008 +.St -p1003.1-2008 +.It \-p1003.1 +.St -p1003.1 +.It \-p1003.1b +.St -p1003.1b +.It \-p1003.1b-93 +.St -p1003.1b-93 +.It \-p1003.1c-95 +.St -p1003.1c-95 +.It \-p1003.1g-2000 +.St -p1003.1g-2000 +.It \-p1003.1i-95 +.St -p1003.1i-95 +.It \-p1003.2-92 +.St -p1003.2-92 +.It \-p1003.2a-92 +.St -p1003.2a-92 +.It \-p1387.2-95 +.St -p1387.2-95 +.It \-p1003.2 +.St -p1003.2 +.It \-p1387.2 +.St -p1387.2 +.It \-isoC +.St -isoC +.It \-isoC-90 +.St -isoC-90 +.It \-isoC-amd1 +.St -isoC-amd1 +.It \-isoC-tcor1 +.St -isoC-tcor1 +.It \-isoC-tcor2 +.St -isoC-tcor2 +.It \-isoC-99 +.St -isoC-99 +.It \-iso9945-1-90 +.St -iso9945-1-90 +.It \-iso9945-1-96 +.St -iso9945-1-96 +.It \-iso9945-2-93 +.St -iso9945-2-93 +.It \-ansiC +.St -ansiC +.It \-ansiC-89 +.St -ansiC-89 +.It \-ansiC-99 +.St -ansiC-99 +.It \-ieee754 +.St -ieee754 +.It \-iso8802-3 +.St -iso8802-3 +.It \-ieee1275-94 +.St -ieee1275-94 +.It \-xpg3 +.St -xpg3 +.It \-xpg4 +.St -xpg4 +.It \-xpg4.2 +.St -xpg4.2 +.St -xpg4.3 +.It \-xbd5 +.St -xbd5 +.It \-xcu5 +.St -xcu5 +.It \-xsh5 +.St -xsh5 +.It \-xns5 +.St -xns5 +.It \-xns5.2 +.St -xns5.2 +.It \-xns5.2d2.0 +.St -xns5.2d2.0 +.It \-xcurses4.2 +.St -xcurses4.2 +.It \-susv2 +.St -susv2 +.It \-susv3 +.St -susv3 +.It \-svid4 +.St -svid4 +.El +.Ss \&Sx +Reference a section or sub-section. +The referenced section or sub-section name must be identical to the +enclosed argument, including whitespace. +.Pp +Examples: +.Dl \&.Sx MANUAL STRUCTURE +.Pp +See also +.Sx \&Sh +and +.Sx \&Ss . +.Ss \&Sy +Format enclosed arguments in symbolic +.Pq Dq boldface . +Note that this is a presentation term and should not be used for +stylistically decorating technical terms. +.Pp +See also +.Sx \&Bf , +.Sx \&Li , +and +.Sx \&Em . +.Ss \&Tn +Format a tradename. +.Pp +Examples: +.Dl \&.Tn IBM +.Ss \&Ud +Prints out +.Dq currently under development . +.Ss \&Ux +Format the UNIX name. +Accepts no argument. +.Pp +Examples: +.Dl \&.Ux +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +and +.Sx \&Ox . +.Ss \&Va +A variable name. +.Pp +Examples: +.Dl \&.Va foo +.Dl \&.Va const char *bar ; +.Ss \&Vt +A variable type. +This is also used for indicating global variables in the +.Em SYNOPSIS +section, in which case a variable name is also specified. +Note that it accepts +.Sx Block partial-implicit +syntax when invoked as the first macro in the +.Em SYNOPSIS +section, else it accepts ordinary +.Sx In-line +syntax. +.Pp +Note that this should not be confused with +.Sx \&Ft , +which is used for function return types. +.Pp +Examples: +.Dl \&.Vt unsigned char +.Dl \&.Vt extern const char * const sys_signame[] \&; +.Pp +See also +.Sx MANUAL STRUCTURE +and +.Sx \&Va . +.Ss \&Xc +Close a scope opened by +.Sx \&Xo . +.Ss \&Xo +Extend the header of an +.Sx \&It +macro or the body of a partial-implicit block macro +beyond the end of the input line. +This macro originally existed to work around the 9-argument limit +of historic +.Xr roff 7 . +.Ss \&Xr +Link to another manual +.Pq Qq cross-reference . +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Xr Cm name section +.Pp +The +.Cm name +and +.Cm section +are the name and section of the linked manual. +If +.Cm section +is followed by non-punctuation, an +.Sx \&Ns +is inserted into the token stream. +This behaviour is for compatibility with +GNU troff. +.Pp +Examples: +.Dl \&.Xr mandoc 1 +.Dl \&.Xr mandoc 1 \&; +.Dl \&.Xr mandoc 1 \&Ns s behaviour +.Ss \&br +Emits a line-break. +This macro should not be used; it is implemented for compatibility with +historical manuals. +.Pp +Consider using +.Sx \&Pp +in the event of natural paragraph breaks. +.Ss \&sp +Emits vertical space. +This macro should not be used; it is implemented for compatibility with +historical manuals. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&sp Op Cm height +.Pp +The +.Cm height +argument must be formatted as described in +.Sx Scaling Widths . +If unspecified, +.Sx \&sp +asserts a single vertical space. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other other +troff implementations, at this time limited to GNU troff +.Pq Qq groff . +The term +.Qq historic groff +refers to groff versions before 1.17, +which featured a significant update of the +.Pa doc.tmac +file. +.Pp +Heirloom troff, the other significant troff implementation accepting +\-mdoc, is similar to historic groff. +.Pp +The following problematic behaviour is found in groff: +.ds hist (Historic groff only.) +.Pp +.Bl -dash -compact +.It +Display macros +.Po +.Sx \&Bd , +.Sx \&Dl , +and +.Sx \&D1 +.Pc +may not be nested. +\*[hist] +.It +.Sx \&At +with unknown arguments produces no output at all. +\*[hist] +Newer groff and mandoc print +.Qq AT&T UNIX +and the arguments. +.It +.Sx \&Bd Fl column +does not recognize trailing punctuation characters when they immediately +precede tabulator characters, but treats them as normal text and +outputs a space before them. +.It +.Sx \&Bd Fl ragged compact +does not start a new line. +\*[hist] +.It +.Sx \&Dd +without an argument prints +.Dq Epoch . +In mandoc, it resolves to the current date. +.It +.Sx \&Fl +does not print a dash for an empty argument. +\*[hist] +.It +.Sx \&Fn +does not start a new line unless invoked as the line macro in the +.Em SYNOPSIS +section. +\*[hist] +.It +.Sx \&Fo +with +.Pf non- Sx \&Fa +children causes inconsistent spacing between arguments. +In mandoc, a single space is always inserted between arguments. +.It +.Sx \&Ft +in the +.Em SYNOPSIS +causes inconsistent vertical spacing, depending on whether a prior +.Sx \&Fn +has been invoked. +See +.Sx \&Ft +and +.Sx \&Fn +for the normalised behaviour in mandoc. +.It +.Sx \&In +ignores additional arguments and is not treated specially in the +.Em SYNOPSIS . +\*[hist] +.It +.Sx \&It +sometimes requires a +.Fl nested +flag. +\*[hist] +In new groff and mandoc, any list may be nested by default and +.Fl enum +lists will restart the sequence only for the sub-list. +.It +.Sx \&Li +followed by a reserved character is incorrectly used in some manuals +instead of properly quoting that character, which sometimes works with +historic groff. +.It +.Sx \&Lk +only accepts a single link-name argument; the remainder is misformatted. +.It +.Sx \&Pa +does not format its arguments when used in the FILES section under +certain list types. +.It +.Sx \&Ta +can only be called by other macros, but not at the beginning of a line. +.It +.Sx \&%C +is not implemented. +.It +Historic groff only allows up to eight or nine arguments per macro input +line, depending on the exact situation. +Providing more arguments causes garbled output. +The number of arguments on one input line is not limited with mandoc. +.It +Historic groff has many un-callable macros. +Most of these (excluding some block-level macros) are callable +in new groff and mandoc. +.It +.Sq \(ba +(vertical bar) is not fully supported as a delimiter. +\*[hist] +.It +.Sq \ef +.Pq font face +and +.Sq \ef +.Pq font family face +.Sx Text Decoration +escapes behave irregularly when specified within line-macro scopes. +.It +Negative scaling units return to prior lines. +Instead, mandoc truncates them to zero. +.El +.Pp +The following features are unimplemented in mandoc: +.Pp +.Bl -dash -compact +.It +.Sx \&Bd +.Fl file Ar file . +.It +.Sx \&Bd +.Fl offset Ar center +and +.Fl offset Ar right . +Groff does not implement centered and flush-right rendering either, +but produces large indentations. +.It +The +.Sq \eh +.Pq horizontal position , +.Sq \ev +.Pq vertical position , +.Sq \em +.Pq text colour , +.Sq \eM +.Pq text filling colour , +.Sq \ez +.Pq zero-length character , +.Sq \ew +.Pq string length , +.Sq \ek +.Pq horizontal position marker , +.Sq \eo +.Pq text overstrike , +and +.Sq \es +.Pq text size +escape sequences are all discarded in mandoc. +.It +The +.Sq \ef +scaling unit is accepted by mandoc, but rendered as the default unit. +.It +In quoted literals, groff allows pairwise double-quotes to produce a +standalone double-quote in formatted output. +This is not supported by mandoc. +.El +.Sh SEE ALSO +.Xr man 1 , +.Xr mandoc 1 , +.Xr man 7 , +.Xr mandoc_char 7 +.Xr roff 7 , +.Xr tbl 7 +.Sh HISTORY +The +.Nm +language first appeared as a troff macro package in +.Bx 4.4 . +It was later significantly updated by Werner Lemberg and Ruslan Ermilov +in groff-1.17. +The standalone implementation that is part of the +.Xr mandoc 1 +utility written by Kristaps Dzonsons appeared in +.Ox 4.6 . +.Sh AUTHORS +The +.Nm +reference was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . diff --git a/contrib/mdocml/mdoc.c b/contrib/mdocml/mdoc.c new file mode 100644 index 0000000000..af39521720 --- /dev/null +++ b/contrib/mdocml/mdoc.c @@ -0,0 +1,916 @@ +/* $Id: mdoc.c,v 1.177 2011/01/03 11:27:33 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2010 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +const char *const __mdoc_macronames[MDOC_MAX] = { + "Ap", "Dd", "Dt", "Os", + "Sh", "Ss", "Pp", "D1", + "Dl", "Bd", "Ed", "Bl", + "El", "It", "Ad", "An", + "Ar", "Cd", "Cm", "Dv", + "Er", "Ev", "Ex", "Fa", + "Fd", "Fl", "Fn", "Ft", + "Ic", "In", "Li", "Nd", + "Nm", "Op", "Ot", "Pa", + "Rv", "St", "Va", "Vt", + /* LINTED */ + "Xr", "%A", "%B", "%D", + /* LINTED */ + "%I", "%J", "%N", "%O", + /* LINTED */ + "%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", + /* LINTED */ + "Brc", "%C", "Es", "En", + /* LINTED */ + "Dx", "%Q", "br", "sp", + /* LINTED */ + "%U", "Ta" + }; + +const char *const __mdoc_argnames[MDOC_ARG_MAX] = { + "split", "nosplit", "ragged", + "unfilled", "literal", "file", + "offset", "bullet", "dash", + "hyphen", "item", "enum", + "tag", "diag", "hang", + "ohang", "inset", "column", + "width", "compact", "std", + "filled", "words", "emphasis", + "symbolic", "nested", "centered" + }; + +const char * const *mdoc_macronames = __mdoc_macronames; +const char * const *mdoc_argnames = __mdoc_argnames; + +static void mdoc_node_free(struct mdoc_node *); +static void mdoc_node_unlink(struct mdoc *, + struct mdoc_node *); +static void mdoc_free1(struct mdoc *); +static void mdoc_alloc1(struct mdoc *); +static struct mdoc_node *node_alloc(struct mdoc *, int, int, + enum mdoct, enum mdoc_type); +static int node_append(struct mdoc *, + struct mdoc_node *); +static int mdoc_ptext(struct mdoc *, int, char *, int); +static int mdoc_pmacro(struct mdoc *, int, char *, int); +static int mdoc_span_alloc(struct mdoc *, + const struct tbl_span *); + + +const struct mdoc_node * +mdoc_node(const struct mdoc *m) +{ + + assert( ! (MDOC_HALT & m->flags)); + return(m->first); +} + + +const struct mdoc_meta * +mdoc_meta(const struct mdoc *m) +{ + + assert( ! (MDOC_HALT & m->flags)); + return(&m->meta); +} + + +/* + * Frees volatile resources (parse tree, meta-data, fields). + */ +static void +mdoc_free1(struct mdoc *mdoc) +{ + + if (mdoc->first) + mdoc_node_delete(mdoc, mdoc->first); + if (mdoc->meta.title) + free(mdoc->meta.title); + if (mdoc->meta.os) + free(mdoc->meta.os); + if (mdoc->meta.name) + free(mdoc->meta.name); + if (mdoc->meta.arch) + free(mdoc->meta.arch); + if (mdoc->meta.vol) + free(mdoc->meta.vol); + if (mdoc->meta.msec) + free(mdoc->meta.msec); +} + + +/* + * Allocate all volatile resources (parse tree, meta-data, fields). + */ +static void +mdoc_alloc1(struct mdoc *mdoc) +{ + + memset(&mdoc->meta, 0, sizeof(struct mdoc_meta)); + mdoc->flags = 0; + mdoc->lastnamed = mdoc->lastsec = SEC_NONE; + mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node)); + mdoc->first = mdoc->last; + mdoc->last->type = MDOC_ROOT; + mdoc->next = MDOC_NEXT_CHILD; +} + + +/* + * Free up volatile resources (see mdoc_free1()) then re-initialises the + * data with mdoc_alloc1(). After invocation, parse data has been reset + * and the parser is ready for re-invocation on a new tree; however, + * cross-parse non-volatile data is kept intact. + */ +void +mdoc_reset(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + mdoc_alloc1(mdoc); +} + + +/* + * Completely free up all volatile and non-volatile parse resources. + * After invocation, the pointer is no longer usable. + */ +void +mdoc_free(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + free(mdoc); +} + + +/* + * Allocate volatile and non-volatile parse resources. + */ +struct mdoc * +mdoc_alloc(struct regset *regs, void *data, mandocmsg msg) +{ + struct mdoc *p; + + p = mandoc_calloc(1, sizeof(struct mdoc)); + + p->msg = msg; + p->data = data; + p->regs = regs; + + mdoc_hash_init(); + mdoc_alloc1(p); + return(p); +} + + +/* + * Climb back up the parse tree, validating open scopes. Mostly calls + * through to macro_end() in macro.c. + */ +int +mdoc_endparse(struct mdoc *m) +{ + + assert( ! (MDOC_HALT & m->flags)); + if (mdoc_macroend(m)) + return(1); + m->flags |= MDOC_HALT; + return(0); +} + +int +mdoc_addspan(struct mdoc *m, const struct tbl_span *sp) +{ + + assert( ! (MDOC_HALT & m->flags)); + + /* No text before an initial macro. */ + + if (SEC_NONE == m->lastnamed) { + /* FIXME: grab from span. */ + mdoc_pmsg(m, 0, 0, MANDOCERR_NOTEXT); + return(1); + } + + return(mdoc_span_alloc(m, sp)); +} + + +/* + * Main parse routine. Parses a single line -- really just hands off to + * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). + */ +int +mdoc_parseln(struct mdoc *m, int ln, char *buf, int offs) +{ + + assert( ! (MDOC_HALT & m->flags)); + + m->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 (m->regs->regs[(int)REG_nS].set) { + if (m->regs->regs[(int)REG_nS].v.u) + m->flags |= MDOC_SYNOPSIS; + else + m->flags &= ~MDOC_SYNOPSIS; + } + + return(('.' == buf[offs] || '\'' == buf[offs]) ? + mdoc_pmacro(m, ln, buf, offs) : + mdoc_ptext(m, ln, buf, offs)); +} + + +int +mdoc_vmsg(struct mdoc *mdoc, enum mandocerr t, + int ln, int pos, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + + return((*mdoc->msg)(t, mdoc->data, ln, pos, buf)); +} + + +int +mdoc_macro(MACRO_PROT_ARGS) +{ + assert(tok < MDOC_MAX); + + /* If we're in the body, deny prologue calls. */ + + if (MDOC_PROLOGUE & mdoc_macros[tok].flags && + MDOC_PBODY & m->flags) { + mdoc_pmsg(m, line, ppos, MANDOCERR_BADBODY); + return(1); + } + + /* If we're in the prologue, deny "body" macros. */ + + if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && + ! (MDOC_PBODY & m->flags)) { + mdoc_pmsg(m, line, ppos, MANDOCERR_BADPROLOG); + if (NULL == m->meta.msec) + m->meta.msec = mandoc_strdup("1"); + if (NULL == m->meta.title) + m->meta.title = mandoc_strdup("UNKNOWN"); + if (NULL == m->meta.vol) + m->meta.vol = mandoc_strdup("LOCAL"); + if (NULL == m->meta.os) + m->meta.os = mandoc_strdup("LOCAL"); + if (0 == m->meta.date) + m->meta.date = time(NULL); + m->flags |= MDOC_PBODY; + } + + return((*mdoc_macros[tok].fp)(m, tok, line, ppos, pos, buf)); +} + + +static int +node_append(struct mdoc *mdoc, struct mdoc_node *p) +{ + + assert(mdoc->last); + assert(mdoc->first); + assert(MDOC_ROOT != p->type); + + switch (mdoc->next) { + case (MDOC_NEXT_SIBLING): + mdoc->last->next = p; + p->prev = mdoc->last; + p->parent = mdoc->last->parent; + break; + case (MDOC_NEXT_CHILD): + mdoc->last->child = p; + p->parent = mdoc->last; + break; + default: + abort(); + /* NOTREACHED */ + } + + p->parent->nchild++; + + /* + * Copy over the normalised-data pointer of our parent. Not + * everybody has one, but copying a null pointer is fine. + */ + + switch (p->type) { + case (MDOC_BODY): + /* FALLTHROUGH */ + case (MDOC_TAIL): + /* FALLTHROUGH */ + case (MDOC_HEAD): + p->norm = p->parent->norm; + break; + default: + break; + } + + if ( ! mdoc_valid_pre(mdoc, p)) + return(0); + + switch (p->type) { + case (MDOC_HEAD): + assert(MDOC_BLOCK == p->parent->type); + p->parent->head = p; + break; + case (MDOC_TAIL): + assert(MDOC_BLOCK == p->parent->type); + p->parent->tail = p; + break; + case (MDOC_BODY): + if (p->end) + break; + assert(MDOC_BLOCK == p->parent->type); + p->parent->body = p; + break; + default: + break; + } + + mdoc->last = p; + + switch (p->type) { + case (MDOC_TBL): + /* FALLTHROUGH */ + case (MDOC_TEXT): + if ( ! mdoc_valid_post(mdoc)) + return(0); + break; + default: + break; + } + + return(1); +} + + +static struct mdoc_node * +node_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, enum mdoc_type type) +{ + struct mdoc_node *p; + + p = mandoc_calloc(1, sizeof(struct mdoc_node)); + p->sec = m->lastsec; + p->line = line; + p->pos = pos; + p->tok = tok; + p->type = type; + + /* Flag analysis. */ + + if (MDOC_SYNOPSIS & m->flags) + p->flags |= MDOC_SYNPRETTY; + else + p->flags &= ~MDOC_SYNPRETTY; + if (MDOC_NEWLINE & m->flags) + p->flags |= MDOC_LINE; + m->flags &= ~MDOC_NEWLINE; + + return(p); +} + + +int +mdoc_tail_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_TAIL); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_head_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) +{ + struct mdoc_node *p; + + assert(m->first); + assert(m->last); + + p = node_alloc(m, line, pos, tok, MDOC_HEAD); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_body_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BODY); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_endbody_alloc(struct mdoc *m, int line, int pos, enum mdoct tok, + struct mdoc_node *body, enum mdoc_endbody end) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BODY); + p->pending = body; + p->end = end; + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +int +mdoc_block_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BLOCK); + p->args = args; + if (p->args) + (args->refcnt)++; + + switch (tok) { + case (MDOC_Bd): + /* FALLTHROUGH */ + case (MDOC_Bf): + /* FALLTHROUGH */ + case (MDOC_Bl): + /* FALLTHROUGH */ + case (MDOC_Rs): + p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); + break; + default: + break; + } + + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_elem_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_ELEM); + p->args = args; + if (p->args) + (args->refcnt)++; + + switch (tok) { + case (MDOC_An): + p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); + break; + default: + break; + } + + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + +static int +mdoc_span_alloc(struct mdoc *m, const struct tbl_span *sp) +{ + struct mdoc_node *n; + + /* FIXME: grab from tbl_span. */ + n = node_alloc(m, 0, 0, MDOC_MAX, MDOC_TBL); + n->span = sp; + + if ( ! node_append(m, n)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +int +mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p) +{ + struct mdoc_node *n; + size_t sv, len; + + len = strlen(p); + + n = node_alloc(m, line, pos, MDOC_MAX, MDOC_TEXT); + n->string = mandoc_malloc(len + 1); + sv = strlcpy(n->string, p, len + 1); + + /* Prohibit truncation. */ + assert(sv < len + 1); + + if ( ! node_append(m, n)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +static void +mdoc_node_free(struct mdoc_node *p) +{ + + if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type) + free(p->norm); + if (p->string) + free(p->string); + if (p->args) + mdoc_argv_free(p->args); + free(p); +} + + +static void +mdoc_node_unlink(struct mdoc *m, struct mdoc_node *n) +{ + + /* Adjust siblings. */ + + if (n->prev) + n->prev->next = n->next; + if (n->next) + n->next->prev = n->prev; + + /* Adjust parent. */ + + if (n->parent) { + n->parent->nchild--; + if (n->parent->child == n) + n->parent->child = n->prev ? n->prev : n->next; + if (n->parent->last == n) + n->parent->last = n->prev ? n->prev : NULL; + } + + /* Adjust parse point, if applicable. */ + + if (m && m->last == n) { + if (n->prev) { + m->last = n->prev; + m->next = MDOC_NEXT_SIBLING; + } else { + m->last = n->parent; + m->next = MDOC_NEXT_CHILD; + } + } + + if (m && m->first == n) + m->first = NULL; +} + + +void +mdoc_node_delete(struct mdoc *m, struct mdoc_node *p) +{ + + while (p->child) { + assert(p->nchild); + mdoc_node_delete(m, p->child); + } + assert(0 == p->nchild); + + mdoc_node_unlink(m, p); + mdoc_node_free(p); +} + + +/* + * Parse free-form text, that is, a line that does not begin with the + * control character. + */ +static int +mdoc_ptext(struct mdoc *m, int line, char *buf, int offs) +{ + char *c, *ws, *end; + struct mdoc_node *n; + + /* Ignore bogus comments. */ + + if ('\\' == buf[offs] && + '.' == buf[offs + 1] && + '"' == buf[offs + 2]) { + mdoc_pmsg(m, line, offs, MANDOCERR_BADCOMMENT); + return(1); + } + + /* No text before an initial macro. */ + + if (SEC_NONE == m->lastnamed) { + mdoc_pmsg(m, line, offs, MANDOCERR_NOTEXT); + return(1); + } + + assert(m->last); + n = m->last; + + /* + * Divert directly to list processing if we're encountering a + * columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry + * (a MDOC_BODY means it's already open, in which case we should + * process within its context in the normal way). + */ + + if (MDOC_Bl == n->tok && MDOC_BODY == n->type && + LIST_column == n->norm->Bl.type) { + /* `Bl' is open without any children. */ + m->flags |= MDOC_FREECOL; + return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf)); + } + + if (MDOC_It == n->tok && MDOC_BLOCK == n->type && + NULL != n->parent && + MDOC_Bl == n->parent->tok && + LIST_column == n->parent->norm->Bl.type) { + /* `Bl' has block-level `It' children. */ + m->flags |= MDOC_FREECOL; + return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf)); + } + + /* + * Search for the beginning of unescaped trailing whitespace (ws) + * and for the first character not to be output (end). + */ + + /* FIXME: replace with strcspn(). */ + ws = NULL; + for (c = end = buf + offs; *c; c++) { + switch (*c) { + case '-': + if (mandoc_hyph(buf + offs, c)) + *c = ASCII_HYPH; + ws = NULL; + break; + case ' ': + if (NULL == ws) + ws = c; + continue; + case '\t': + /* + * Always warn about trailing tabs, + * even outside literal context, + * where they should be put on the next line. + */ + if (NULL == ws) + ws = c; + /* + * Strip trailing tabs in literal context only; + * outside, they affect the next line. + */ + if (MDOC_LITERAL & m->flags) + continue; + break; + case '\\': + /* Skip the escaped character, too, if any. */ + if (c[1]) + c++; + /* FALLTHROUGH */ + default: + ws = NULL; + break; + } + end = c + 1; + } + *end = '\0'; + + if (ws) + mdoc_pmsg(m, line, (int)(ws-buf), MANDOCERR_EOLNSPACE); + + if ('\0' == buf[offs] && ! (MDOC_LITERAL & m->flags)) { + mdoc_pmsg(m, line, (int)(c-buf), MANDOCERR_NOBLANKLN); + + /* + * Insert a `sp' in the case of a blank line. Technically, + * blank lines aren't allowed, but enough manuals assume this + * behaviour that we want to work around it. + */ + if ( ! mdoc_elem_alloc(m, line, offs, MDOC_sp, NULL)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); + } + + if ( ! mdoc_word_alloc(m, line, offs, buf+offs)) + return(0); + + if (MDOC_LITERAL & m->flags) + return(1); + + /* + * End-of-sentence check. If the last character is an unescaped + * EOS character, then flag the node as being the end of a + * sentence. The front-end will know how to interpret this. + */ + + assert(buf < end); + + if (mandoc_eos(buf+offs, (size_t)(end-buf-offs), 0)) + m->last->flags |= MDOC_EOS; + + return(1); +} + + +/* + * Parse a macro line, that is, a line beginning with the control + * character. + */ +static int +mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs) +{ + enum mdoct tok; + int i, j, sv; + char mac[5]; + struct mdoc_node *n; + + /* Empty lines are ignored. */ + + offs++; + + if ('\0' == buf[offs]) + return(1); + + i = offs; + + /* Accept tabs/whitespace after the initial control char. */ + + if (' ' == buf[i] || '\t' == buf[i]) { + i++; + while (buf[i] && (' ' == buf[i] || '\t' == buf[i])) + i++; + if ('\0' == buf[i]) + return(1); + } + + sv = i; + + /* + * Copy the first word into a nil-terminated buffer. + * Stop copying when a tab, space, or eoln is encountered. + */ + + j = 0; + while (j < 4 && '\0' != buf[i] && ' ' != buf[i] && '\t' != buf[i]) + mac[j++] = buf[i++]; + mac[j] = '\0'; + + tok = (j > 1 || j < 4) ? mdoc_hash_find(mac) : MDOC_MAX; + if (MDOC_MAX == tok) { + mdoc_vmsg(m, MANDOCERR_MACRO, ln, sv, "%s", buf + sv - 1); + return(1); + } + + /* Disregard the first trailing tab, if applicable. */ + + if ('\t' == buf[i]) + i++; + + /* Jump to the next non-whitespace word. */ + + while (buf[i] && ' ' == buf[i]) + i++; + + /* + * 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[i] && ' ' == buf[i - 1]) + mdoc_pmsg(m, ln, i - 1, MANDOCERR_EOLNSPACE); + + /* + * If an initial macro or a list invocation, divert directly + * into macro processing. + */ + + if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) { + if ( ! mdoc_macro(m, tok, ln, sv, &i, buf)) + goto err; + return(1); + } + + n = m->last; + assert(m->last); + + /* + * If the first macro of a `Bl -column', open an `It' block + * context around the parsed macro. + */ + + if (MDOC_Bl == n->tok && MDOC_BODY == n->type && + LIST_column == n->norm->Bl.type) { + m->flags |= MDOC_FREECOL; + if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) + goto err; + return(1); + } + + /* + * If we're following a block-level `It' within a `Bl -column' + * context (perhaps opened in the above block or in ptext()), + * then open an `It' block context around the parsed macro. + */ + + if (MDOC_It == n->tok && MDOC_BLOCK == n->type && + NULL != n->parent && + MDOC_Bl == n->parent->tok && + LIST_column == n->parent->norm->Bl.type) { + m->flags |= MDOC_FREECOL; + if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) + goto err; + return(1); + } + + /* Normal processing of a macro. */ + + if ( ! mdoc_macro(m, tok, ln, sv, &i, buf)) + goto err; + + return(1); + +err: /* Error out. */ + + m->flags |= MDOC_HALT; + return(0); +} + + diff --git a/contrib/mdocml/mdoc.h b/contrib/mdocml/mdoc.h new file mode 100644 index 0000000000..9c22c33640 --- /dev/null +++ b/contrib/mdocml/mdoc.h @@ -0,0 +1,436 @@ +/* $Id: mdoc.h,v 1.114 2011/01/01 12:18:37 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifndef MDOC_H +#define MDOC_H + +/* + * What follows is a list of ALL possible macros. + */ +enum mdoct { + MDOC_Ap = 0, + MDOC_Dd, + MDOC_Dt, + MDOC_Os, + MDOC_Sh, + MDOC_Ss, + MDOC_Pp, + MDOC_D1, + MDOC_Dl, + MDOC_Bd, + MDOC_Ed, + MDOC_Bl, + MDOC_El, + MDOC_It, + MDOC_Ad, + MDOC_An, + MDOC_Ar, + MDOC_Cd, + MDOC_Cm, + MDOC_Dv, + MDOC_Er, + MDOC_Ev, + MDOC_Ex, + MDOC_Fa, + MDOC_Fd, + MDOC_Fl, + MDOC_Fn, + MDOC_Ft, + MDOC_Ic, + MDOC_In, + MDOC_Li, + MDOC_Nd, + MDOC_Nm, + MDOC_Op, + MDOC_Ot, + MDOC_Pa, + MDOC_Rv, + MDOC_St, + MDOC_Va, + MDOC_Vt, + MDOC_Xr, + MDOC__A, + MDOC__B, + MDOC__D, + MDOC__I, + MDOC__J, + MDOC__N, + MDOC__O, + MDOC__P, + MDOC__R, + MDOC__T, + MDOC__V, + MDOC_Ac, + MDOC_Ao, + MDOC_Aq, + MDOC_At, + MDOC_Bc, + MDOC_Bf, + MDOC_Bo, + MDOC_Bq, + MDOC_Bsx, + MDOC_Bx, + MDOC_Db, + MDOC_Dc, + MDOC_Do, + MDOC_Dq, + MDOC_Ec, + MDOC_Ef, + MDOC_Em, + MDOC_Eo, + MDOC_Fx, + MDOC_Ms, + MDOC_No, + MDOC_Ns, + MDOC_Nx, + MDOC_Ox, + MDOC_Pc, + MDOC_Pf, + MDOC_Po, + MDOC_Pq, + MDOC_Qc, + MDOC_Ql, + MDOC_Qo, + MDOC_Qq, + MDOC_Re, + MDOC_Rs, + MDOC_Sc, + MDOC_So, + MDOC_Sq, + MDOC_Sm, + MDOC_Sx, + MDOC_Sy, + MDOC_Tn, + MDOC_Ux, + MDOC_Xc, + MDOC_Xo, + MDOC_Fo, + MDOC_Fc, + MDOC_Oo, + MDOC_Oc, + MDOC_Bk, + MDOC_Ek, + MDOC_Bt, + MDOC_Hf, + MDOC_Fr, + MDOC_Ud, + MDOC_Lb, + MDOC_Lp, + MDOC_Lk, + MDOC_Mt, + MDOC_Brq, + MDOC_Bro, + MDOC_Brc, + MDOC__C, + MDOC_Es, + MDOC_En, + MDOC_Dx, + MDOC__Q, + MDOC_br, + MDOC_sp, + MDOC__U, + MDOC_Ta, + MDOC_MAX +}; + +/* + * What follows is a list of ALL possible macro arguments. + */ +enum mdocargt { + MDOC_Split, + MDOC_Nosplit, + MDOC_Ragged, + MDOC_Unfilled, + MDOC_Literal, + MDOC_File, + MDOC_Offset, + 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_Compact, + MDOC_Std, + MDOC_Filled, + MDOC_Words, + MDOC_Emphasis, + MDOC_Symbolic, + MDOC_Nested, + MDOC_Centred, + MDOC_ARG_MAX +}; + +/* + * Type of a syntax node. + */ +enum mdoc_type { + MDOC_TEXT, + MDOC_ELEM, + MDOC_HEAD, + MDOC_TAIL, + MDOC_BODY, + MDOC_BLOCK, + MDOC_TBL, + MDOC_ROOT +}; + +/* + * Section (named/unnamed) of `Sh'. Note that these appear in the + * conventional order imposed by mdoc.7. + */ +enum mdoc_sec { + SEC_NONE = 0, /* No section, yet. */ + SEC_NAME, + SEC_LIBRARY, + SEC_SYNOPSIS, + SEC_DESCRIPTION, + SEC_IMPLEMENTATION, + 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, /* User-defined. */ + SEC__MAX +}; + +/* + * Information from prologue. + */ +struct mdoc_meta { + char *msec; /* `Dt' section (1, 3p, etc.) */ + char *vol; /* `Dt' volume (implied) */ + char *arch; /* `Dt' arch (i386, etc.) */ + time_t date; /* `Dd' normalised date */ + char *title; /* `Dt' title (FOO, etc.) */ + char *os; /* `Os' system (OpenBSD, etc.) */ + char *name; /* leading `Nm' name */ +}; + +/* + * An argument to a macro (multiple values = `-column xxx yyy'). + */ +struct mdoc_argv { + enum mdocargt arg; /* type of argument */ + int line; + int pos; + size_t sz; /* elements in "value" */ + char **value; /* argument strings */ +}; + +/* + * Reference-counted macro arguments. These are refcounted because + * blocks have multiple instances of the same arguments spread across + * the HEAD, BODY, TAIL, and BLOCK node types. + */ +struct mdoc_arg { + size_t argc; + struct mdoc_argv *argv; + unsigned int refcnt; +}; + +/* + * Indicates that a BODY's formatting has ended, but the scope is still + * open. Used for syntax-broken blocks. + */ +enum mdoc_endbody { + ENDBODY_NOT = 0, + ENDBODY_SPACE, /* is broken: append a space */ + ENDBODY_NOSPACE /* is broken: don't append a space */ +}; + +/* + * Normalised `Bl' list type. + */ +enum mdoc_list { + LIST__NONE = 0, + LIST_bullet, + LIST_column, + LIST_dash, + LIST_diag, + LIST_enum, + LIST_hang, + LIST_hyphen, + LIST_inset, + LIST_item, + LIST_ohang, + LIST_tag, + LIST_MAX +}; + +/* + * Normalised `Bd' display type. + */ +enum mdoc_disp { + DISP__NONE = 0, + DISP_centred, + DISP_ragged, + DISP_unfilled, + DISP_filled, + DISP_literal +}; + +/* + * Normalised `An' splitting argument. + */ +enum mdoc_auth { + AUTH__NONE = 0, + AUTH_split, + AUTH_nosplit +}; + +/* + * Normalised `Bf' font type. + */ +enum mdoc_font { + FONT__NONE = 0, + FONT_Em, + FONT_Li, + FONT_Sy +}; + +/* + * Normalised arguments for `Bd'. + */ +struct mdoc_bd { + const char *offs; /* -offset */ + enum mdoc_disp type; /* -ragged, etc. */ + int comp; /* -compact */ +}; + +/* + * Normalised arguments for `Bl'. + */ +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 */ +}; + +/* + * Normalised arguments for `Bf'. + */ +struct mdoc_bf { + enum mdoc_font font; /* font */ +}; + +/* + * Normalised arguments for `An'. + */ +struct mdoc_an { + enum mdoc_auth auth; /* -split, etc. */ +}; + +struct mdoc_rs { + struct mdoc_node *child_J; /* pointer to %J */ +}; + +/* + * Consists of normalised node arguments. These should be used instead + * of iterating through the mdoc_arg pointers of a node: defaults are + * provided, etc. + */ +union mdoc_data { + struct mdoc_an An; + struct mdoc_bd Bd; + struct mdoc_bf Bf; + struct mdoc_bl Bl; + struct mdoc_rs Rs; +}; + +/* + * Single node in tree-linked AST. + */ +struct mdoc_node { + struct mdoc_node *parent; /* parent AST node */ + struct mdoc_node *child; /* first child AST node */ + struct mdoc_node *last; /* last child AST node */ + struct mdoc_node *next; /* sibling AST node */ + struct mdoc_node *prev; /* prior sibling AST node */ + int nchild; /* number children */ + int line; /* parse line */ + int pos; /* parse column */ + enum mdoct tok; /* tok or MDOC__MAX if none */ + int flags; +#define MDOC_VALID (1 << 0) /* has been validated */ +#define MDOC_EOS (1 << 2) /* at sentence boundary */ +#define MDOC_LINE (1 << 3) /* first macro/text on line */ +#define MDOC_SYNPRETTY (1 << 4) /* SYNOPSIS-style formatting */ +#define MDOC_ENDED (1 << 5) /* rendering has been ended */ + enum mdoc_type type; /* AST node type */ + enum mdoc_sec sec; /* current named section */ + union mdoc_data *norm; /* normalised args */ + /* FIXME: these can be union'd to shave a few bytes. */ + struct mdoc_arg *args; /* BLOCK/ELEM */ + struct mdoc_node *pending; /* BLOCK */ + struct mdoc_node *head; /* BLOCK */ + struct mdoc_node *body; /* BLOCK */ + struct mdoc_node *tail; /* BLOCK */ + char *string; /* TEXT */ + const struct tbl_span *span; /* TBL */ + enum mdoc_endbody end; /* BODY */ +}; + +/* + * Names of macros. Index is enum mdoct. Indexing into this returns + * the normalised name, e.g., mdoc_macronames[MDOC_Sh] -> "Sh". + */ +extern const char *const *mdoc_macronames; + +/* + * Names of macro args. Index is enum mdocargt. Indexing into this + * returns the normalised name, e.g., mdoc_argnames[MDOC_File] -> + * "file". + */ +extern const char *const *mdoc_argnames; + +__BEGIN_DECLS + +struct mdoc; + +void mdoc_free(struct mdoc *); +struct mdoc *mdoc_alloc(struct regset *, void *, mandocmsg); +void mdoc_reset(struct mdoc *); +int mdoc_parseln(struct mdoc *, int, char *, int); +const struct mdoc_node *mdoc_node(const struct mdoc *); +const struct mdoc_meta *mdoc_meta(const struct mdoc *); +int mdoc_endparse(struct mdoc *); +int mdoc_addspan(struct mdoc *, + const struct tbl_span *); + +__END_DECLS + +#endif /*!MDOC_H*/ diff --git a/contrib/mdocml/mdoc_argv.c b/contrib/mdocml/mdoc_argv.c new file mode 100644 index 0000000000..f68e55f5e0 --- /dev/null +++ b/contrib/mdocml/mdoc_argv.c @@ -0,0 +1,792 @@ +/* $Id: mdoc_argv.c,v 1.62 2010/12/24 14:00:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +/* + * Routines to parse arguments of macros. Arguments follow the syntax + * of `-arg [val [valN...]]'. Arguments come in all types: quoted + * arguments, multiple arguments per value, no-value arguments, etc. + * + * There's no limit to the number or arguments that may be allocated. + */ + +#define ARGV_NONE (1 << 0) +#define ARGV_SINGLE (1 << 1) +#define ARGV_MULTI (1 << 2) +#define ARGV_OPT_SINGLE (1 << 3) + +#define MULTI_STEP 5 + +static enum mdocargt argv_a2arg(enum mdoct, const char *); +static enum margserr args(struct mdoc *, int, int *, + char *, int, char **); +static int argv(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_single(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_opt_single(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_multi(struct mdoc *, int, + struct mdoc_argv *, int *, char *); + +/* Per-argument flags. */ + +static int mdoc_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_OPT_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 int mdoc_argflags[MDOC_MAX] = { + 0, /* Ap */ + 0, /* Dd */ + 0, /* Dt */ + 0, /* Os */ + 0, /* Sh */ + 0, /* Ss */ + 0, /* Pp */ + ARGS_DELIM, /* D1 */ + ARGS_DELIM, /* Dl */ + 0, /* Bd */ + 0, /* Ed */ + 0, /* Bl */ + 0, /* El */ + 0, /* It */ + ARGS_DELIM, /* Ad */ + ARGS_DELIM, /* An */ + ARGS_DELIM, /* Ar */ + 0, /* Cd */ + ARGS_DELIM, /* Cm */ + ARGS_DELIM, /* Dv */ + ARGS_DELIM, /* Er */ + ARGS_DELIM, /* Ev */ + 0, /* Ex */ + ARGS_DELIM, /* Fa */ + 0, /* Fd */ + ARGS_DELIM, /* Fl */ + ARGS_DELIM, /* Fn */ + ARGS_DELIM, /* Ft */ + ARGS_DELIM, /* Ic */ + 0, /* In */ + ARGS_DELIM, /* Li */ + 0, /* Nd */ + ARGS_DELIM, /* Nm */ + ARGS_DELIM, /* Op */ + 0, /* Ot */ + ARGS_DELIM, /* Pa */ + 0, /* Rv */ + ARGS_DELIM, /* St */ + ARGS_DELIM, /* Va */ + ARGS_DELIM, /* Vt */ + ARGS_DELIM, /* Xr */ + 0, /* %A */ + 0, /* %B */ + 0, /* %D */ + 0, /* %I */ + 0, /* %J */ + 0, /* %N */ + 0, /* %O */ + 0, /* %P */ + 0, /* %R */ + 0, /* %T */ + 0, /* %V */ + ARGS_DELIM, /* Ac */ + 0, /* Ao */ + ARGS_DELIM, /* Aq */ + ARGS_DELIM, /* At */ + ARGS_DELIM, /* Bc */ + 0, /* Bf */ + 0, /* Bo */ + ARGS_DELIM, /* Bq */ + ARGS_DELIM, /* Bsx */ + ARGS_DELIM, /* Bx */ + 0, /* Db */ + ARGS_DELIM, /* Dc */ + 0, /* Do */ + ARGS_DELIM, /* Dq */ + ARGS_DELIM, /* Ec */ + 0, /* Ef */ + ARGS_DELIM, /* Em */ + 0, /* Eo */ + ARGS_DELIM, /* Fx */ + ARGS_DELIM, /* Ms */ + ARGS_DELIM, /* No */ + ARGS_DELIM, /* Ns */ + ARGS_DELIM, /* Nx */ + ARGS_DELIM, /* Ox */ + ARGS_DELIM, /* Pc */ + ARGS_DELIM, /* Pf */ + 0, /* Po */ + ARGS_DELIM, /* Pq */ + ARGS_DELIM, /* Qc */ + ARGS_DELIM, /* Ql */ + 0, /* Qo */ + ARGS_DELIM, /* Qq */ + 0, /* Re */ + 0, /* Rs */ + ARGS_DELIM, /* Sc */ + 0, /* So */ + ARGS_DELIM, /* Sq */ + 0, /* Sm */ + ARGS_DELIM, /* Sx */ + ARGS_DELIM, /* Sy */ + ARGS_DELIM, /* Tn */ + ARGS_DELIM, /* Ux */ + ARGS_DELIM, /* Xc */ + 0, /* Xo */ + 0, /* Fo */ + 0, /* Fc */ + 0, /* Oo */ + ARGS_DELIM, /* Oc */ + 0, /* Bk */ + 0, /* Ek */ + 0, /* Bt */ + 0, /* Hf */ + 0, /* Fr */ + 0, /* Ud */ + 0, /* Lb */ + 0, /* Lp */ + ARGS_DELIM, /* Lk */ + ARGS_DELIM, /* Mt */ + ARGS_DELIM, /* Brq */ + 0, /* Bro */ + ARGS_DELIM, /* Brc */ + 0, /* %C */ + 0, /* Es */ + 0, /* En */ + 0, /* Dx */ + 0, /* %Q */ + 0, /* br */ + 0, /* sp */ + 0, /* %U */ + 0, /* Ta */ +}; + + +/* + * Parse an argument from line text. This comes in the form of -key + * [value0...], which may either have a single mandatory value, at least + * one mandatory value, an optional single value, or no value. + */ +enum margverr +mdoc_argv(struct mdoc *m, int line, enum mdoct tok, + struct mdoc_arg **v, int *pos, char *buf) +{ + char *p, sv; + struct mdoc_argv tmp; + struct mdoc_arg *arg; + + if ('\0' == buf[*pos]) + return(ARGV_EOLN); + + assert(' ' != buf[*pos]); + + /* Parse through to the first unescaped space. */ + + p = &buf[++(*pos)]; + + assert(*pos > 0); + + /* LINTED */ + while (buf[*pos]) { + if (' ' == buf[*pos]) + if ('\\' != buf[*pos - 1]) + break; + (*pos)++; + } + + /* XXX - save zeroed byte, if not an argument. */ + + sv = '\0'; + if (buf[*pos]) { + sv = buf[*pos]; + buf[(*pos)++] = '\0'; + } + + (void)memset(&tmp, 0, sizeof(struct mdoc_argv)); + tmp.line = line; + tmp.pos = *pos; + + /* See if our token accepts the argument. */ + + if (MDOC_ARG_MAX == (tmp.arg = argv_a2arg(tok, p))) { + /* XXX - restore saved zeroed byte. */ + if (sv) + buf[*pos - 1] = sv; + return(ARGV_WORD); + } + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + if ( ! argv(m, line, &tmp, pos, buf)) + return(ARGV_ERROR); + + if (NULL == (arg = *v)) + arg = *v = mandoc_calloc(1, sizeof(struct mdoc_arg)); + + arg->argc++; + arg->argv = mandoc_realloc + (arg->argv, arg->argc * sizeof(struct mdoc_argv)); + + (void)memcpy(&arg->argv[(int)arg->argc - 1], + &tmp, sizeof(struct mdoc_argv)); + + return(ARGV_ARG); +} + + +void +mdoc_argv_free(struct mdoc_arg *p) +{ + int i; + + if (NULL == p) + return; + + if (p->refcnt) { + --(p->refcnt); + if (p->refcnt) + return; + } + assert(p->argc); + + for (i = (int)p->argc - 1; i >= 0; i--) + mdoc_argn_free(p, i); + + free(p->argv); + free(p); +} + + +void +mdoc_argn_free(struct mdoc_arg *p, int iarg) +{ + struct mdoc_argv *arg; + int j; + + arg = &p->argv[iarg]; + + if (arg->sz && arg->value) { + for (j = (int)arg->sz - 1; j >= 0; j--) + free(arg->value[j]); + free(arg->value); + } + + for (--p->argc; iarg < (int)p->argc; iarg++) + p->argv[iarg] = p->argv[iarg+1]; +} + + +enum margserr +mdoc_zargs(struct mdoc *m, int line, int *pos, + char *buf, int flags, char **v) +{ + + return(args(m, line, pos, buf, flags, v)); +} + + +enum margserr +mdoc_args(struct mdoc *m, int line, int *pos, + char *buf, enum mdoct tok, char **v) +{ + int fl; + struct mdoc_node *n; + + fl = mdoc_argflags[tok]; + + if (MDOC_It != tok) + return(args(m, line, pos, buf, fl, v)); + + /* + * We know that we're in an `It', so it's reasonable to expect + * us to be sitting in a `Bl'. Someday this may not be the case + * (if we allow random `It's sitting out there), so provide a + * safe fall-back into the default behaviour. + */ + + for (n = m->last; n; n = n->parent) + if (MDOC_Bl == n->tok) + break; + + if (n && LIST_column == n->norm->Bl.type) { + fl |= ARGS_TABSEP; + fl &= ~ARGS_DELIM; + } + + return(args(m, line, pos, buf, fl, v)); +} + + +static enum margserr +args(struct mdoc *m, int line, int *pos, + char *buf, int fl, char **v) +{ + int i; + char *p, *pp; + enum margserr rc; + enum mdelim d; + + /* + * Parse out the terms (like `val' in `.Xx -arg val' or simply + * `.Xx val'), which can have all sorts of properties: + * + * ARGS_DELIM: use special handling if encountering trailing + * delimiters in the form of [[::delim::][ ]+]+. + * + * ARGS_NOWARN: don't post warnings. This is only used when + * re-parsing delimiters, as the warnings have already been + * posted. + * + * ARGS_TABSEP: use special handling for tab/`Ta' separated + * phrases like in `Bl -column'. + */ + + assert(' ' != buf[*pos]); + + if ('\0' == buf[*pos]) { + if (MDOC_PPHRASE & m->flags) + return(ARGS_EOLN); + /* + * If we're not in a partial phrase and the flag for + * being a phrase literal is still set, the punctuation + * is unterminated. + */ + if (MDOC_PHRASELIT & m->flags) + if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE)) + return(ARGS_ERROR); + + m->flags &= ~MDOC_PHRASELIT; + return(ARGS_EOLN); + } + + /* + * If the first character is a closing delimiter and we're to + * look for delimited strings, then pass down the buffer seeing + * if it follows the pattern of [[::delim::][ ]+]+. Note that + * we ONLY care about closing delimiters. + */ + + if ((fl & ARGS_DELIM) && DELIM_CLOSE == mdoc_iscdelim(buf[*pos])) { + for (i = *pos; buf[i]; ) { + d = mdoc_iscdelim(buf[i]); + if (DELIM_NONE == d || DELIM_OPEN == d) + break; + i++; + if ('\0' == buf[i] || ' ' != buf[i]) + break; + i++; + while (buf[i] && ' ' == buf[i]) + i++; + } + + if ('\0' == buf[i]) { + *v = &buf[*pos]; + if (i && ' ' != buf[i - 1]) + return(ARGS_PUNCT); + if (ARGS_NOWARN & fl) + return(ARGS_PUNCT); + if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE)) + return(ARGS_ERROR); + return(ARGS_PUNCT); + } + } + + *v = &buf[*pos]; + + /* + * First handle TABSEP items, restricted to `Bl -column'. This + * ignores conventional token parsing and instead uses tabs or + * `Ta' macros to separate phrases. Phrases are parsed again + * for arguments at a later phase. + */ + + if (ARGS_TABSEP & fl) { + /* Scan ahead to tab (can't be escaped). */ + p = strchr(*v, '\t'); + pp = NULL; + + /* Scan ahead to unescaped `Ta'. */ + if ( ! (MDOC_PHRASELIT & m->flags)) + for (pp = *v; ; pp++) { + if (NULL == (pp = strstr(pp, "Ta"))) + break; + if (pp > *v && ' ' != *(pp - 1)) + continue; + if (' ' == *(pp + 2) || '\0' == *(pp + 2)) + break; + } + + /* By default, assume a phrase. */ + rc = ARGS_PHRASE; + + /* + * Adjust new-buffer position to be beyond delimiter + * mark (e.g., Ta -> end + 2). + */ + if (p && pp) { + *pos += pp < p ? 2 : 1; + rc = pp < p ? ARGS_PHRASE : ARGS_PPHRASE; + p = pp < p ? pp : p; + } else if (p && ! pp) { + rc = ARGS_PPHRASE; + *pos += 1; + } else if (pp && ! p) { + p = pp; + *pos += 2; + } else { + rc = ARGS_PEND; + p = strchr(*v, 0); + } + + /* Whitespace check for eoln case... */ + if ('\0' == *p && ' ' == *(p - 1) && ! (ARGS_NOWARN & fl)) + if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE)) + return(ARGS_ERROR); + + *pos += (int)(p - *v); + + /* Strip delimiter's preceding whitespace. */ + pp = p - 1; + while (pp > *v && ' ' == *pp) { + if (pp > *v && '\\' == *(pp - 1)) + break; + pp--; + } + *(pp + 1) = 0; + + /* Strip delimiter's proceeding whitespace. */ + for (pp = &buf[*pos]; ' ' == *pp; pp++, (*pos)++) + /* Skip ahead. */ ; + + return(rc); + } + + /* + * Process a quoted literal. A quote begins with a double-quote + * and ends with a double-quote NOT preceded by a double-quote. + * Whitespace is NOT involved in literal termination. + */ + + if (MDOC_PHRASELIT & m->flags || '\"' == buf[*pos]) { + if ( ! (MDOC_PHRASELIT & m->flags)) + *v = &buf[++(*pos)]; + + if (MDOC_PPHRASE & m->flags) + m->flags |= MDOC_PHRASELIT; + + for ( ; buf[*pos]; (*pos)++) { + if ('\"' != buf[*pos]) + continue; + if ('\"' != buf[*pos + 1]) + break; + (*pos)++; + } + + if ('\0' == buf[*pos]) { + if (ARGS_NOWARN & fl || MDOC_PPHRASE & m->flags) + return(ARGS_QWORD); + if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE)) + return(ARGS_ERROR); + return(ARGS_QWORD); + } + + m->flags &= ~MDOC_PHRASELIT; + buf[(*pos)++] = '\0'; + + if ('\0' == buf[*pos]) + return(ARGS_QWORD); + + while (' ' == buf[*pos]) + (*pos)++; + + if (0 == buf[*pos] && ! (ARGS_NOWARN & fl)) + if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE)) + return(ARGS_ERROR); + + return(ARGS_QWORD); + } + + /* + * A non-quoted term progresses until either the end of line or + * a non-escaped whitespace. + */ + + for ( ; buf[*pos]; (*pos)++) + if (*pos && ' ' == buf[*pos] && '\\' != buf[*pos - 1]) + break; + + if ('\0' == buf[*pos]) + return(ARGS_WORD); + + buf[(*pos)++] = '\0'; + + while (' ' == buf[*pos]) + (*pos)++; + + if ('\0' == buf[*pos] && ! (ARGS_NOWARN & fl)) + if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE)) + return(ARGS_ERROR); + + return(ARGS_WORD); +} + + +static enum mdocargt +argv_a2arg(enum mdoct tok, const char *p) +{ + + /* + * Parse an argument identifier from its text. XXX - this + * should really be table-driven to clarify the code. + * + * If you add an argument to the list, make sure that you + * register it here with its one or more macros! + */ + + switch (tok) { + case (MDOC_An): + if (0 == strcmp(p, "split")) + return(MDOC_Split); + else if (0 == strcmp(p, "nosplit")) + return(MDOC_Nosplit); + break; + + case (MDOC_Bd): + if (0 == strcmp(p, "ragged")) + return(MDOC_Ragged); + else if (0 == strcmp(p, "unfilled")) + return(MDOC_Unfilled); + else if (0 == strcmp(p, "filled")) + return(MDOC_Filled); + else if (0 == strcmp(p, "literal")) + return(MDOC_Literal); + else if (0 == strcmp(p, "file")) + return(MDOC_File); + else if (0 == strcmp(p, "offset")) + return(MDOC_Offset); + else if (0 == strcmp(p, "compact")) + return(MDOC_Compact); + else if (0 == strcmp(p, "centered")) + return(MDOC_Centred); + break; + + case (MDOC_Bf): + if (0 == strcmp(p, "emphasis")) + return(MDOC_Emphasis); + else if (0 == strcmp(p, "literal")) + return(MDOC_Literal); + else if (0 == strcmp(p, "symbolic")) + return(MDOC_Symbolic); + break; + + case (MDOC_Bk): + if (0 == strcmp(p, "words")) + return(MDOC_Words); + break; + + case (MDOC_Bl): + if (0 == strcmp(p, "bullet")) + return(MDOC_Bullet); + else if (0 == strcmp(p, "dash")) + return(MDOC_Dash); + else if (0 == strcmp(p, "hyphen")) + return(MDOC_Hyphen); + else if (0 == strcmp(p, "item")) + return(MDOC_Item); + else if (0 == strcmp(p, "enum")) + return(MDOC_Enum); + else if (0 == strcmp(p, "tag")) + return(MDOC_Tag); + else if (0 == strcmp(p, "diag")) + return(MDOC_Diag); + else if (0 == strcmp(p, "hang")) + return(MDOC_Hang); + else if (0 == strcmp(p, "ohang")) + return(MDOC_Ohang); + else if (0 == strcmp(p, "inset")) + return(MDOC_Inset); + else if (0 == strcmp(p, "column")) + return(MDOC_Column); + else if (0 == strcmp(p, "width")) + return(MDOC_Width); + else if (0 == strcmp(p, "offset")) + return(MDOC_Offset); + else if (0 == strcmp(p, "compact")) + return(MDOC_Compact); + else if (0 == strcmp(p, "nested")) + return(MDOC_Nested); + break; + + case (MDOC_Rv): + /* FALLTHROUGH */ + case (MDOC_Ex): + if (0 == strcmp(p, "std")) + return(MDOC_Std); + break; + default: + break; + } + + return(MDOC_ARG_MAX); +} + + +static int +argv_multi(struct mdoc *m, 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(m, line, pos, buf, 0, &p); + if (ARGS_ERROR == ac) + return(0); + else if (ARGS_EOLN == ac) + break; + + if (0 == v->sz % MULTI_STEP) + v->value = mandoc_realloc(v->value, + (v->sz + MULTI_STEP) * sizeof(char *)); + + v->value[(int)v->sz] = mandoc_strdup(p); + } + + return(1); +} + + +static int +argv_opt_single(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + enum margserr ac; + char *p; + + if ('-' == buf[*pos]) + return(1); + + ac = args(m, line, pos, buf, 0, &p); + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + return(1); + + v->sz = 1; + v->value = mandoc_malloc(sizeof(char *)); + v->value[0] = mandoc_strdup(p); + + return(1); +} + + +/* + * Parse a single, mandatory value from the stream. + */ +static int +argv_single(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int ppos; + enum margserr ac; + char *p; + + ppos = *pos; + + ac = args(m, line, pos, buf, 0, &p); + if (ARGS_EOLN == ac) { + mdoc_pmsg(m, line, ppos, MANDOCERR_SYNTARGVCOUNT); + return(0); + } else if (ARGS_ERROR == ac) + return(0); + + v->sz = 1; + v->value = mandoc_malloc(sizeof(char *)); + v->value[0] = mandoc_strdup(p); + + return(1); +} + + +/* + * Determine rules for parsing arguments. Arguments can either accept + * no parameters, an optional single parameter, one parameter, or + * multiple parameters. + */ +static int +argv(struct mdoc *mdoc, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + + v->sz = 0; + v->value = NULL; + + switch (mdoc_argvflags[v->arg]) { + case (ARGV_SINGLE): + return(argv_single(mdoc, line, v, pos, buf)); + case (ARGV_MULTI): + return(argv_multi(mdoc, line, v, pos, buf)); + case (ARGV_OPT_SINGLE): + return(argv_opt_single(mdoc, line, v, pos, buf)); + default: + /* ARGV_NONE */ + break; + } + + return(1); +} diff --git a/contrib/mdocml/mdoc_hash.c b/contrib/mdocml/mdoc_hash.c new file mode 100644 index 0000000000..3bf29dfd85 --- /dev/null +++ b/contrib/mdocml/mdoc_hash.c @@ -0,0 +1,93 @@ +/* $Id: mdoc_hash.c,v 1.16 2010/06/19 20:46:28 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +static u_char table[27 * 12]; + +/* + * XXX - this hash has global scope, so if intended for use as a library + * with multiple callers, it will need re-invocation protection. + */ +void +mdoc_hash_init(void) +{ + int i, j, major; + const char *p; + + memset(table, UCHAR_MAX, sizeof(table)); + + for (i = 0; i < (int)MDOC_MAX; i++) { + p = mdoc_macronames[i]; + + if (isalpha((u_char)p[1])) + major = 12 * (tolower((u_char)p[1]) - 97); + else + major = 12 * 26; + + for (j = 0; j < 12; j++) + if (UCHAR_MAX == table[major + j]) { + table[major + j] = (u_char)i; + break; + } + + assert(j < 12); + } +} + +enum mdoct +mdoc_hash_find(const char *p) +{ + int major, i, j; + + if (0 == p[0]) + return(MDOC_MAX); + if ( ! isalpha((u_char)p[0]) && '%' != p[0]) + return(MDOC_MAX); + + if (isalpha((u_char)p[1])) + major = 12 * (tolower((u_char)p[1]) - 97); + else if ('1' == p[1]) + major = 12 * 26; + else + return(MDOC_MAX); + + if (p[2] && p[3]) + return(MDOC_MAX); + + for (j = 0; j < 12; j++) { + if (UCHAR_MAX == (i = table[major + j])) + break; + if (0 == strcmp(p, mdoc_macronames[i])) + return((enum mdoct)i); + } + + return(MDOC_MAX); +} diff --git a/contrib/mdocml/mdoc_html.c b/contrib/mdocml/mdoc_html.c new file mode 100644 index 0000000000..44b41d2f17 --- /dev/null +++ b/contrib/mdocml/mdoc_html.c @@ -0,0 +1,2139 @@ +/* $Id: mdoc_html.c,v 1.142 2011/01/07 13:20:58 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "html.h" +#include "mdoc.h" +#include "main.h" + +#define INDENT 5 +#define HALFINDENT 3 + +#define MDOC_ARGS const struct mdoc_meta *m, \ + const struct mdoc_node *n, \ + struct html *h + +#ifndef MIN +#define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b)) +#endif + +struct htmlmdoc { + int (*pre)(MDOC_ARGS); + void (*post)(MDOC_ARGS); +}; + +static void print_mdoc(MDOC_ARGS); +static void print_mdoc_head(MDOC_ARGS); +static void print_mdoc_node(MDOC_ARGS); +static void print_mdoc_nodelist(MDOC_ARGS); +static void synopsis_pre(struct html *, + const struct mdoc_node *); + +static void a2width(const char *, struct roffsu *); +static void a2offs(const char *, struct roffsu *); + +static void mdoc_root_post(MDOC_ARGS); +static int mdoc_root_pre(MDOC_ARGS); + +static void mdoc__x_post(MDOC_ARGS); +static int mdoc__x_pre(MDOC_ARGS); +static int mdoc_ad_pre(MDOC_ARGS); +static int mdoc_an_pre(MDOC_ARGS); +static int mdoc_ap_pre(MDOC_ARGS); +static int mdoc_ar_pre(MDOC_ARGS); +static int mdoc_bd_pre(MDOC_ARGS); +static int mdoc_bf_pre(MDOC_ARGS); +static void mdoc_bk_post(MDOC_ARGS); +static int mdoc_bk_pre(MDOC_ARGS); +static int mdoc_bl_pre(MDOC_ARGS); +static int mdoc_bt_pre(MDOC_ARGS); +static int mdoc_bx_pre(MDOC_ARGS); +static int mdoc_cd_pre(MDOC_ARGS); +static int mdoc_d1_pre(MDOC_ARGS); +static int mdoc_dv_pre(MDOC_ARGS); +static int mdoc_fa_pre(MDOC_ARGS); +static int mdoc_fd_pre(MDOC_ARGS); +static int mdoc_fl_pre(MDOC_ARGS); +static int mdoc_fn_pre(MDOC_ARGS); +static int mdoc_ft_pre(MDOC_ARGS); +static int mdoc_em_pre(MDOC_ARGS); +static int mdoc_er_pre(MDOC_ARGS); +static int mdoc_ev_pre(MDOC_ARGS); +static int mdoc_ex_pre(MDOC_ARGS); +static void mdoc_fo_post(MDOC_ARGS); +static int mdoc_fo_pre(MDOC_ARGS); +static int mdoc_ic_pre(MDOC_ARGS); +static int mdoc_igndelim_pre(MDOC_ARGS); +static int mdoc_in_pre(MDOC_ARGS); +static int mdoc_it_pre(MDOC_ARGS); +static int mdoc_lb_pre(MDOC_ARGS); +static int mdoc_li_pre(MDOC_ARGS); +static int mdoc_lk_pre(MDOC_ARGS); +static int mdoc_mt_pre(MDOC_ARGS); +static int mdoc_ms_pre(MDOC_ARGS); +static int mdoc_nd_pre(MDOC_ARGS); +static int mdoc_nm_pre(MDOC_ARGS); +static int mdoc_ns_pre(MDOC_ARGS); +static int mdoc_pa_pre(MDOC_ARGS); +static void mdoc_pf_post(MDOC_ARGS); +static int mdoc_pp_pre(MDOC_ARGS); +static void mdoc_quote_post(MDOC_ARGS); +static int mdoc_quote_pre(MDOC_ARGS); +static int mdoc_rs_pre(MDOC_ARGS); +static int mdoc_rv_pre(MDOC_ARGS); +static int mdoc_sh_pre(MDOC_ARGS); +static int mdoc_sm_pre(MDOC_ARGS); +static int mdoc_sp_pre(MDOC_ARGS); +static int mdoc_ss_pre(MDOC_ARGS); +static int mdoc_sx_pre(MDOC_ARGS); +static int mdoc_sy_pre(MDOC_ARGS); +static int mdoc_ud_pre(MDOC_ARGS); +static int mdoc_va_pre(MDOC_ARGS); +static int mdoc_vt_pre(MDOC_ARGS); +static int mdoc_xr_pre(MDOC_ARGS); +static int mdoc_xx_pre(MDOC_ARGS); + +static const struct htmlmdoc mdocs[MDOC_MAX] = { + {mdoc_ap_pre, NULL}, /* Ap */ + {NULL, NULL}, /* Dd */ + {NULL, NULL}, /* Dt */ + {NULL, NULL}, /* Os */ + {mdoc_sh_pre, NULL }, /* Sh */ + {mdoc_ss_pre, NULL }, /* Ss */ + {mdoc_pp_pre, NULL}, /* Pp */ + {mdoc_d1_pre, NULL}, /* D1 */ + {mdoc_d1_pre, NULL}, /* Dl */ + {mdoc_bd_pre, NULL}, /* Bd */ + {NULL, NULL}, /* Ed */ + {mdoc_bl_pre, NULL}, /* Bl */ + {NULL, NULL}, /* El */ + {mdoc_it_pre, NULL}, /* It */ + {mdoc_ad_pre, NULL}, /* Ad */ + {mdoc_an_pre, NULL}, /* An */ + {mdoc_ar_pre, NULL}, /* Ar */ + {mdoc_cd_pre, NULL}, /* Cd */ + {mdoc_fl_pre, NULL}, /* Cm */ + {mdoc_dv_pre, NULL}, /* Dv */ + {mdoc_er_pre, NULL}, /* Er */ + {mdoc_ev_pre, NULL}, /* Ev */ + {mdoc_ex_pre, NULL}, /* Ex */ + {mdoc_fa_pre, NULL}, /* Fa */ + {mdoc_fd_pre, NULL}, /* Fd */ + {mdoc_fl_pre, NULL}, /* Fl */ + {mdoc_fn_pre, NULL}, /* Fn */ + {mdoc_ft_pre, NULL}, /* Ft */ + {mdoc_ic_pre, NULL}, /* Ic */ + {mdoc_in_pre, NULL}, /* In */ + {mdoc_li_pre, NULL}, /* Li */ + {mdoc_nd_pre, NULL}, /* Nd */ + {mdoc_nm_pre, NULL}, /* Nm */ + {mdoc_quote_pre, mdoc_quote_post}, /* Op */ + {NULL, NULL}, /* Ot */ + {mdoc_pa_pre, NULL}, /* Pa */ + {mdoc_rv_pre, NULL}, /* Rv */ + {NULL, NULL}, /* St */ + {mdoc_va_pre, NULL}, /* Va */ + {mdoc_vt_pre, NULL}, /* Vt */ + {mdoc_xr_pre, NULL}, /* Xr */ + {mdoc__x_pre, mdoc__x_post}, /* %A */ + {mdoc__x_pre, mdoc__x_post}, /* %B */ + {mdoc__x_pre, mdoc__x_post}, /* %D */ + {mdoc__x_pre, mdoc__x_post}, /* %I */ + {mdoc__x_pre, mdoc__x_post}, /* %J */ + {mdoc__x_pre, mdoc__x_post}, /* %N */ + {mdoc__x_pre, mdoc__x_post}, /* %O */ + {mdoc__x_pre, mdoc__x_post}, /* %P */ + {mdoc__x_pre, mdoc__x_post}, /* %R */ + {mdoc__x_pre, mdoc__x_post}, /* %T */ + {mdoc__x_pre, mdoc__x_post}, /* %V */ + {NULL, NULL}, /* Ac */ + {mdoc_quote_pre, mdoc_quote_post}, /* Ao */ + {mdoc_quote_pre, mdoc_quote_post}, /* Aq */ + {NULL, NULL}, /* At */ + {NULL, NULL}, /* Bc */ + {mdoc_bf_pre, NULL}, /* Bf */ + {mdoc_quote_pre, mdoc_quote_post}, /* Bo */ + {mdoc_quote_pre, mdoc_quote_post}, /* Bq */ + {mdoc_xx_pre, NULL}, /* Bsx */ + {mdoc_bx_pre, NULL}, /* Bx */ + {NULL, NULL}, /* Db */ + {NULL, NULL}, /* Dc */ + {mdoc_quote_pre, mdoc_quote_post}, /* Do */ + {mdoc_quote_pre, mdoc_quote_post}, /* Dq */ + {NULL, NULL}, /* Ec */ /* FIXME: no space */ + {NULL, NULL}, /* Ef */ + {mdoc_em_pre, NULL}, /* Em */ + {NULL, NULL}, /* Eo */ + {mdoc_xx_pre, NULL}, /* Fx */ + {mdoc_ms_pre, NULL}, /* Ms */ + {mdoc_igndelim_pre, NULL}, /* No */ + {mdoc_ns_pre, NULL}, /* Ns */ + {mdoc_xx_pre, NULL}, /* Nx */ + {mdoc_xx_pre, NULL}, /* Ox */ + {NULL, NULL}, /* Pc */ + {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */ + {mdoc_quote_pre, mdoc_quote_post}, /* Po */ + {mdoc_quote_pre, mdoc_quote_post}, /* Pq */ + {NULL, NULL}, /* Qc */ + {mdoc_quote_pre, mdoc_quote_post}, /* Ql */ + {mdoc_quote_pre, mdoc_quote_post}, /* Qo */ + {mdoc_quote_pre, mdoc_quote_post}, /* Qq */ + {NULL, NULL}, /* Re */ + {mdoc_rs_pre, NULL}, /* Rs */ + {NULL, NULL}, /* Sc */ + {mdoc_quote_pre, mdoc_quote_post}, /* So */ + {mdoc_quote_pre, mdoc_quote_post}, /* Sq */ + {mdoc_sm_pre, NULL}, /* Sm */ + {mdoc_sx_pre, NULL}, /* Sx */ + {mdoc_sy_pre, NULL}, /* Sy */ + {NULL, NULL}, /* Tn */ + {mdoc_xx_pre, NULL}, /* Ux */ + {NULL, NULL}, /* Xc */ + {NULL, NULL}, /* Xo */ + {mdoc_fo_pre, mdoc_fo_post}, /* Fo */ + {NULL, NULL}, /* Fc */ + {mdoc_quote_pre, mdoc_quote_post}, /* Oo */ + {NULL, NULL}, /* Oc */ + {mdoc_bk_pre, mdoc_bk_post}, /* Bk */ + {NULL, NULL}, /* Ek */ + {mdoc_bt_pre, NULL}, /* Bt */ + {NULL, NULL}, /* Hf */ + {NULL, NULL}, /* Fr */ + {mdoc_ud_pre, NULL}, /* Ud */ + {mdoc_lb_pre, NULL}, /* Lb */ + {mdoc_pp_pre, NULL}, /* Lp */ + {mdoc_lk_pre, NULL}, /* Lk */ + {mdoc_mt_pre, NULL}, /* Mt */ + {mdoc_quote_pre, mdoc_quote_post}, /* Brq */ + {mdoc_quote_pre, mdoc_quote_post}, /* Bro */ + {NULL, NULL}, /* Brc */ + {mdoc__x_pre, mdoc__x_post}, /* %C */ + {NULL, NULL}, /* Es */ /* TODO */ + {NULL, NULL}, /* En */ /* TODO */ + {mdoc_xx_pre, NULL}, /* Dx */ + {mdoc__x_pre, mdoc__x_post}, /* %Q */ + {mdoc_sp_pre, NULL}, /* br */ + {mdoc_sp_pre, NULL}, /* sp */ + {mdoc__x_pre, mdoc__x_post}, /* %U */ + {NULL, NULL}, /* Ta */ +}; + +static const char * const lists[LIST_MAX] = { + NULL, + "list-bul", + "list-col", + "list-dash", + "list-diag", + "list-enum", + "list-hang", + "list-hyph", + "list-inset", + "list-item", + "list-ohang", + "list-tag" +}; + +void +html_mdoc(void *arg, const struct mdoc *m) +{ + struct html *h; + struct tag *t; + + h = (struct html *)arg; + + print_gen_decls(h); + t = print_otag(h, TAG_HTML, 0, NULL); + print_mdoc(mdoc_meta(m), mdoc_node(m), h); + print_tagq(h, t); + + printf("\n"); +} + + +/* + * Calculate the scaling unit passed in a `-width' argument. This uses + * either a native scaling unit (e.g., 1i, 2m) or the string length of + * the value. + */ +static void +a2width(const char *p, struct roffsu *su) +{ + + if ( ! a2roffsu(p, su, SCALE_MAX)) { + su->unit = SCALE_BU; + su->scale = (int)strlen(p); + } +} + + +/* + * See the same function in mdoc_term.c for documentation. + */ +static void +synopsis_pre(struct html *h, const struct mdoc_node *n) +{ + + if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags)) + return; + + if (n->prev->tok == n->tok && + MDOC_Fo != n->tok && + MDOC_Ft != n->tok && + MDOC_Fn != n->tok) { + print_otag(h, TAG_BR, 0, NULL); + return; + } + + switch (n->prev->tok) { + case (MDOC_Fd): + /* FALLTHROUGH */ + case (MDOC_Fn): + /* FALLTHROUGH */ + case (MDOC_Fo): + /* FALLTHROUGH */ + case (MDOC_In): + /* FALLTHROUGH */ + case (MDOC_Vt): + print_otag(h, TAG_P, 0, NULL); + break; + case (MDOC_Ft): + if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { + print_otag(h, TAG_P, 0, NULL); + break; + } + /* FALLTHROUGH */ + default: + print_otag(h, TAG_BR, 0, NULL); + break; + } +} + + +/* + * Calculate the scaling unit passed in an `-offset' argument. This + * uses either a native scaling unit (e.g., 1i, 2m), one of a set of + * predefined strings (indent, etc.), or the string length of the value. + */ +static void +a2offs(const char *p, struct roffsu *su) +{ + + /* FIXME: "right"? */ + + if (0 == strcmp(p, "left")) + SCALE_HS_INIT(su, 0); + else if (0 == strcmp(p, "indent")) + SCALE_HS_INIT(su, INDENT); + else if (0 == strcmp(p, "indent-two")) + SCALE_HS_INIT(su, INDENT * 2); + else if ( ! a2roffsu(p, su, SCALE_MAX)) { + su->unit = SCALE_BU; + su->scale = (int)strlen(p); + } +} + + +static void +print_mdoc(MDOC_ARGS) +{ + struct tag *t; + + t = print_otag(h, TAG_HEAD, 0, NULL); + print_mdoc_head(m, n, h); + print_tagq(h, t); + + t = print_otag(h, TAG_BODY, 0, NULL); + print_mdoc_nodelist(m, n, h); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static void +print_mdoc_head(MDOC_ARGS) +{ + + print_gen_head(h); + bufinit(h); + buffmt(h, "%s(%s)", m->title, m->msec); + + if (m->arch) { + bufcat(h, " ("); + bufcat(h, m->arch); + bufcat(h, ")"); + } + + print_otag(h, TAG_TITLE, 0, NULL); + print_text(h, h->buf); +} + + +static void +print_mdoc_nodelist(MDOC_ARGS) +{ + + print_mdoc_node(m, n, h); + if (n->next) + print_mdoc_nodelist(m, n->next, h); +} + + +static void +print_mdoc_node(MDOC_ARGS) +{ + int child; + struct tag *t; + + child = 1; + t = h->tags.head; + + bufinit(h); + switch (n->type) { + case (MDOC_ROOT): + child = mdoc_root_pre(m, n, h); + break; + case (MDOC_TEXT): + print_text(h, n->string); + return; + case (MDOC_TBL): + print_tbl(h, n->span); + break; + default: + if (mdocs[n->tok].pre && ENDBODY_NOT == n->end) + child = (*mdocs[n->tok].pre)(m, n, h); + break; + } + + if (HTML_KEEP & h->flags) { + if (n->prev && n->prev->line != n->line) { + h->flags &= ~HTML_KEEP; + h->flags |= HTML_PREKEEP; + } else if (NULL == n->prev) { + if (n->parent && n->parent->line != n->line) { + h->flags &= ~HTML_KEEP; + h->flags |= HTML_PREKEEP; + } + } + } + + if (child && n->child) + print_mdoc_nodelist(m, n->child, h); + + print_stagq(h, t); + + bufinit(h); + switch (n->type) { + case (MDOC_ROOT): + mdoc_root_post(m, n, h); + break; + case (MDOC_TBL): + break; + default: + if (mdocs[n->tok].post && ENDBODY_NOT == n->end) + (*mdocs[n->tok].post)(m, n, h); + break; + } +} + +/* ARGSUSED */ +static void +mdoc_root_post(MDOC_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + char b[DATESIZ]; + + time2a(m->date, b, DATESIZ); + + PAIR_SUMMARY_INIT(&tag[0], "Document Footer"); + PAIR_CLASS_INIT(&tag[1], "foot"); + if (NULL == h->style) { + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "50%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + } else + t = print_otag(h, TAG_TABLE, 2, tag); + + t = print_otag(h, TAG_TBODY, 0, NULL); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "foot-date"); + print_otag(h, TAG_TD, 1, tag); + + print_text(h, b); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "foot-os"); + if (NULL == h->style) { + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + } else + print_otag(h, TAG_TD, 1, tag); + + print_text(h, m->os); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static int +mdoc_root_pre(MDOC_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + char b[BUFSIZ], title[BUFSIZ]; + + strlcpy(b, m->vol, BUFSIZ); + + if (m->arch) { + strlcat(b, " (", BUFSIZ); + strlcat(b, m->arch, BUFSIZ); + strlcat(b, ")", BUFSIZ); + } + + snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec); + + PAIR_SUMMARY_INIT(&tag[0], "Document Header"); + PAIR_CLASS_INIT(&tag[1], "head"); + if (NULL == h->style) { + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "30%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + } else + t = print_otag(h, TAG_TABLE, 2, tag); + + print_otag(h, TAG_TBODY, 0, NULL); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "head-ltitle"); + print_otag(h, TAG_TD, 1, tag); + + print_text(h, title); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-vol"); + if (NULL == h->style) { + PAIR_INIT(&tag[1], ATTR_ALIGN, "center"); + print_otag(h, TAG_TD, 2, tag); + } else + print_otag(h, TAG_TD, 1, tag); + + print_text(h, b); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-rtitle"); + if (NULL == h->style) { + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + } else + print_otag(h, TAG_TD, 1, tag); + + print_text(h, title); + print_tagq(h, t); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sh_pre(MDOC_ARGS) +{ + struct htmlpair tag; + char buf[BUFSIZ]; + + if (MDOC_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag, "section"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MDOC_BODY == n->type) + return(1); + + buf[0] = '\0'; + for (n = n->child; n; n = n->next) { + html_idcat(buf, n->string, BUFSIZ); + if (n->next) + html_idcat(buf, " ", BUFSIZ); + } + + PAIR_ID_INIT(&tag, buf); + print_otag(h, TAG_H1, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ss_pre(MDOC_ARGS) +{ + struct htmlpair tag; + char buf[BUFSIZ]; + + if (MDOC_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag, "subsection"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MDOC_BODY == n->type) + return(1); + + buf[0] = '\0'; + for (n = n->child; n; n = n->next) { + html_idcat(buf, n->string, BUFSIZ); + if (n->next) + html_idcat(buf, " ", BUFSIZ); + } + + PAIR_ID_INIT(&tag, buf); + print_otag(h, TAG_H2, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fl_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "flag"); + print_otag(h, TAG_B, 1, &tag); + + /* `Cm' has no leading hyphen. */ + + if (MDOC_Cm == n->tok) + return(1); + + print_text(h, "\\-"); + + if (n->child) + h->flags |= HTML_NOSPACE; + else if (n->next && n->next->line == n->line) + h->flags |= HTML_NOSPACE; + + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_nd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BODY != n->type) + return(1); + + /* XXX: this tag in theory can contain block elements. */ + + print_text(h, "\\(em"); + PAIR_CLASS_INIT(&tag, "desc"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +static int +mdoc_nm_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + size_t len; + + switch (n->type) { + case (MDOC_ELEM): + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "name"); + print_otag(h, TAG_B, 1, &tag); + if (NULL == n->child && m->name) + print_text(h, m->name); + return(1); + case (MDOC_HEAD): + print_otag(h, TAG_TD, 0, NULL); + if (NULL == n->child && m->name) + print_text(h, m->name); + return(1); + case (MDOC_BODY): + print_otag(h, TAG_TD, 0, NULL); + return(1); + default: + break; + } + + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "synopsis"); + print_otag(h, TAG_TABLE, 1, &tag); + + for (len = 0, n = n->child; n; n = n->next) + if (MDOC_TEXT == n->type) + len += strlen(n->string); + + if (0 == len && m->name) + len = strlen(m->name); + + SCALE_HS_INIT(&su, (double)len); + bufcat_su(h, "width", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_COL, 1, &tag); + print_otag(h, TAG_COL, 0, NULL); + print_otag(h, TAG_TBODY, 0, NULL); + print_otag(h, TAG_TR, 0, NULL); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_xr_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + const struct mdoc_node *nn; + + if (NULL == n->child) + return(0); + + PAIR_CLASS_INIT(&tag[0], "link-man"); + + if (h->base_man) { + buffmt_man(h, n->child->string, + n->child->next ? + n->child->next->string : NULL); + PAIR_HREF_INIT(&tag[1], h->buf); + print_otag(h, TAG_A, 2, tag); + } else + print_otag(h, TAG_A, 1, tag); + + nn = n->child; + print_text(h, nn->string); + + if (NULL == (nn = nn->next)) + return(0); + + h->flags |= HTML_NOSPACE; + print_text(h, "("); + h->flags |= HTML_NOSPACE; + print_text(h, nn->string); + h->flags |= HTML_NOSPACE; + print_text(h, ")"); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ns_pre(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ar_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "arg"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_xx_pre(MDOC_ARGS) +{ + const char *pp; + struct htmlpair tag; + + switch (n->tok) { + case (MDOC_Bsx): + pp = "BSD/OS"; + break; + case (MDOC_Dx): + pp = "DragonFly"; + break; + case (MDOC_Fx): + pp = "FreeBSD"; + break; + case (MDOC_Nx): + pp = "NetBSD"; + break; + case (MDOC_Ox): + pp = "OpenBSD"; + break; + case (MDOC_Ux): + pp = "UNIX"; + break; + default: + return(1); + } + + PAIR_CLASS_INIT(&tag, "unix"); + print_otag(h, TAG_SPAN, 1, &tag); + print_text(h, pp); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bx_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "unix"); + print_otag(h, TAG_SPAN, 1, &tag); + + for (nn = n->child; nn; nn = nn->next) + print_mdoc_node(m, nn, h); + + if (n->child) + h->flags |= HTML_NOSPACE; + + print_text(h, "BSD"); + return(0); +} + +/* ARGSUSED */ +static int +mdoc_it_pre(MDOC_ARGS) +{ + struct roffsu su; + enum mdoc_list type; + struct htmlpair tag[2]; + const struct mdoc_node *bl; + + bl = n->parent; + while (bl && MDOC_Bl != bl->tok) + bl = bl->parent; + + assert(bl); + + type = bl->norm->Bl.type; + + assert(lists[type]); + PAIR_CLASS_INIT(&tag[0], lists[type]); + + if (MDOC_HEAD == n->type) { + switch (type) { + case(LIST_bullet): + /* FALLTHROUGH */ + case(LIST_dash): + /* FALLTHROUGH */ + case(LIST_item): + /* FALLTHROUGH */ + case(LIST_hyphen): + /* FALLTHROUGH */ + case(LIST_enum): + return(0); + case(LIST_diag): + /* FALLTHROUGH */ + case(LIST_hang): + /* FALLTHROUGH */ + case(LIST_inset): + /* FALLTHROUGH */ + case(LIST_ohang): + /* FALLTHROUGH */ + case(LIST_tag): + SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DT, 2, tag); + if (LIST_diag != type) + break; + PAIR_CLASS_INIT(&tag[0], "diag"); + print_otag(h, TAG_B, 1, tag); + break; + case(LIST_column): + break; + default: + break; + } + } else if (MDOC_BODY == n->type) { + switch (type) { + case(LIST_bullet): + /* FALLTHROUGH */ + case(LIST_hyphen): + /* FALLTHROUGH */ + case(LIST_dash): + /* FALLTHROUGH */ + case(LIST_enum): + /* FALLTHROUGH */ + case(LIST_item): + SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_LI, 2, tag); + break; + case(LIST_diag): + /* FALLTHROUGH */ + case(LIST_hang): + /* FALLTHROUGH */ + case(LIST_inset): + /* FALLTHROUGH */ + case(LIST_ohang): + /* FALLTHROUGH */ + case(LIST_tag): + if (NULL == bl->norm->Bl.width) { + print_otag(h, TAG_DD, 1, tag); + break; + } + a2width(bl->norm->Bl.width, &su); + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DD, 2, tag); + break; + case(LIST_column): + SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_TD, 2, tag); + break; + default: + break; + } + } else { + switch (type) { + case (LIST_column): + print_otag(h, TAG_TR, 1, tag); + break; + default: + break; + } + } + + return(1); +} + +/* ARGSUSED */ +static int +mdoc_bl_pre(MDOC_ARGS) +{ + int i; + struct htmlpair tag[3]; + struct roffsu su; + char buf[BUFSIZ]; + + if (MDOC_BODY == n->type) { + if (LIST_column == n->norm->Bl.type) + print_otag(h, TAG_TBODY, 0, NULL); + return(1); + } + + if (MDOC_HEAD == n->type) { + if (LIST_column != n->norm->Bl.type) + return(0); + + /* + * For each column, print out the tag with our + * suggested width. The last column gets min-width, as + * in terminal mode it auto-sizes to the width of the + * screen and we want to preserve that behaviour. + */ + + for (i = 0; i < (int)n->norm->Bl.ncols; i++) { + a2width(n->norm->Bl.cols[i], &su); + bufinit(h); + if (i < (int)n->norm->Bl.ncols - 1) + bufcat_su(h, "width", &su); + else + bufcat_su(h, "min-width", &su); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_COL, 1, tag); + } + + return(0); + } + + SCALE_VS_INIT(&su, 0); + bufcat_su(h, "margin-top", &su); + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[0], h); + + assert(lists[n->norm->Bl.type]); + strlcpy(buf, "list ", BUFSIZ); + strlcat(buf, lists[n->norm->Bl.type], BUFSIZ); + PAIR_INIT(&tag[1], ATTR_CLASS, buf); + + /* Set the block's left-hand margin. */ + + if (n->norm->Bl.offs) { + a2offs(n->norm->Bl.offs, &su); + bufcat_su(h, "margin-left", &su); + } + + switch (n->norm->Bl.type) { + case(LIST_bullet): + /* FALLTHROUGH */ + case(LIST_dash): + /* FALLTHROUGH */ + case(LIST_hyphen): + /* FALLTHROUGH */ + case(LIST_item): + print_otag(h, TAG_UL, 2, tag); + break; + case(LIST_enum): + print_otag(h, TAG_OL, 2, tag); + break; + case(LIST_diag): + /* FALLTHROUGH */ + case(LIST_hang): + /* FALLTHROUGH */ + case(LIST_inset): + /* FALLTHROUGH */ + case(LIST_ohang): + /* FALLTHROUGH */ + case(LIST_tag): + print_otag(h, TAG_DL, 2, tag); + break; + case(LIST_column): + print_otag(h, TAG_TABLE, 2, tag); + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + +/* ARGSUSED */ +static int +mdoc_ex_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct tag *t; + struct htmlpair tag; + + if (n->prev) + print_otag(h, TAG_BR, 0, NULL); + + PAIR_CLASS_INIT(&tag, "utility"); + + print_text(h, "The"); + for (nn = n->child; nn; nn = nn->next) { + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, nn->string); + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + + if (nn->next && NULL == nn->next->next) + print_text(h, ", and"); + else if (nn->next) + print_text(h, ","); + else + h->flags &= ~HTML_NOSPACE; + } + + if (n->child && n->child->next) + print_text(h, "utilities exit"); + else + print_text(h, "utility exits"); + + print_text(h, "0 on success, and >0 if an error occurs."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_em_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "emph"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_d1_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct roffsu su; + + if (MDOC_BLOCK != n->type) + return(1); + + SCALE_VS_INIT(&su, 0); + bufcat_su(h, "margin-top", &su); + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_BLOCKQUOTE, 1, tag); + + /* BLOCKQUOTE needs a block body. */ + + PAIR_CLASS_INIT(&tag[0], "display"); + print_otag(h, TAG_DIV, 1, tag); + + if (MDOC_Dl == n->tok) { + PAIR_CLASS_INIT(&tag[0], "lit"); + print_otag(h, TAG_CODE, 1, tag); + } + + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sx_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + const struct mdoc_node *nn; + char buf[BUFSIZ]; + + strlcpy(buf, "#", BUFSIZ); + for (nn = n->child; nn; nn = nn->next) { + html_idcat(buf, nn->string, BUFSIZ); + if (nn->next) + html_idcat(buf, " ", BUFSIZ); + } + + PAIR_CLASS_INIT(&tag[0], "link-sec"); + PAIR_HREF_INIT(&tag[1], buf); + + print_otag(h, TAG_I, 1, tag); + print_otag(h, TAG_A, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bd_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + int comp; + const struct mdoc_node *nn; + struct roffsu su; + + if (MDOC_HEAD == n->type) + return(0); + + if (MDOC_BLOCK == n->type) { + comp = n->norm->Bd.comp; + for (nn = n; nn && ! comp; nn = nn->parent) { + if (MDOC_BLOCK != nn->type) + continue; + if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok) + comp = 1; + if (nn->prev) + break; + } + if ( ! comp) + print_otag(h, TAG_P, 0, NULL); + return(1); + } + + SCALE_HS_INIT(&su, 0); + if (n->norm->Bd.offs) + a2offs(n->norm->Bd.offs, &su); + + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag[0], h); + + if (DISP_unfilled != n->norm->Bd.type && + DISP_literal != n->norm->Bd.type) { + PAIR_CLASS_INIT(&tag[1], "display"); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } + + PAIR_CLASS_INIT(&tag[1], "lit display"); + print_otag(h, TAG_PRE, 2, tag); + + for (nn = n->child; nn; nn = nn->next) { + print_mdoc_node(m, nn, h); + /* + * If the printed node flushes its own line, then we + * needn't do it here as well. This is hacky, but the + * notion of selective eoln whitespace is pretty dumb + * anyway, so don't sweat it. + */ + switch (nn->tok) { + case (MDOC_Sm): + /* FALLTHROUGH */ + case (MDOC_br): + /* FALLTHROUGH */ + case (MDOC_sp): + /* FALLTHROUGH */ + case (MDOC_Bl): + /* FALLTHROUGH */ + case (MDOC_D1): + /* FALLTHROUGH */ + case (MDOC_Dl): + /* FALLTHROUGH */ + case (MDOC_Lp): + /* FALLTHROUGH */ + case (MDOC_Pp): + continue; + default: + break; + } + if (nn->next && nn->next->line == nn->line) + continue; + else if (nn->next) + print_text(h, "\n"); + + h->flags |= HTML_NOSPACE; + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_pa_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "file"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ad_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "addr"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_an_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + /* TODO: -split and -nosplit (see termp_an_pre()). */ + + PAIR_CLASS_INIT(&tag, "author"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_cd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "config"); + print_otag(h, TAG_B, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_dv_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "define"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ev_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "env"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_er_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "errno"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fa_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag; + struct tag *t; + + PAIR_CLASS_INIT(&tag, "farg"); + if (n->parent->tok != MDOC_Fo) { + print_otag(h, TAG_I, 1, &tag); + return(1); + } + + for (nn = n->child; nn; nn = nn->next) { + t = print_otag(h, TAG_I, 1, &tag); + print_text(h, nn->string); + print_tagq(h, t); + if (nn->next) + print_text(h, ","); + } + + if (n->child && n->next && n->next->tok == MDOC_Fa) + print_text(h, ","); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_fd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + synopsis_pre(h, n); + + PAIR_CLASS_INIT(&tag, "macro"); + print_otag(h, TAG_B, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_vt_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BLOCK == n->type) { + synopsis_pre(h, n); + return(1); + } else if (MDOC_ELEM == n->type) { + synopsis_pre(h, n); + } else if (MDOC_HEAD == n->type) + return(0); + + PAIR_CLASS_INIT(&tag, "type"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ft_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "ftype"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fn_pre(MDOC_ARGS) +{ + struct tag *t; + struct htmlpair tag[2]; + const struct mdoc_node *nn; + char nbuf[BUFSIZ]; + const char *sp, *ep; + int sz, i; + + synopsis_pre(h, n); + + /* Split apart into type and name. */ + assert(n->child->string); + sp = n->child->string; + + ep = strchr(sp, ' '); + if (NULL != ep) { + PAIR_CLASS_INIT(&tag[0], "ftype"); + t = print_otag(h, TAG_I, 1, tag); + + while (ep) { + sz = MIN((int)(ep - sp), BUFSIZ - 1); + (void)memcpy(nbuf, sp, (size_t)sz); + nbuf[sz] = '\0'; + print_text(h, nbuf); + sp = ++ep; + ep = strchr(sp, ' '); + } + print_tagq(h, t); + } + + PAIR_CLASS_INIT(&tag[0], "fname"); + + /* + * FIXME: only refer to IDs that we know exist. + */ + +#if 0 + if (MDOC_SYNPRETTY & n->flags) { + nbuf[0] = '\0'; + html_idcat(nbuf, sp, BUFSIZ); + PAIR_ID_INIT(&tag[1], nbuf); + } else { + strlcpy(nbuf, "#", BUFSIZ); + html_idcat(nbuf, sp, BUFSIZ); + PAIR_HREF_INIT(&tag[1], nbuf); + } +#endif + + t = print_otag(h, TAG_B, 1, tag); + + if (sp) { + strlcpy(nbuf, sp, BUFSIZ); + print_text(h, nbuf); + } + + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + print_text(h, "("); + + bufinit(h); + PAIR_CLASS_INIT(&tag[0], "farg"); + bufcat_style(h, "white-space", "nowrap"); + PAIR_STYLE_INIT(&tag[1], h); + + for (nn = n->child->next; nn; nn = nn->next) { + i = 1; + if (MDOC_SYNPRETTY & n->flags) + i = 2; + t = print_otag(h, TAG_I, i, tag); + print_text(h, nn->string); + print_tagq(h, t); + if (nn->next) + print_text(h, ","); + } + + print_text(h, ")"); + if (MDOC_SYNPRETTY & n->flags) + print_text(h, ";"); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_sm_pre(MDOC_ARGS) +{ + + assert(n->child && MDOC_TEXT == n->child->type); + if (0 == strcmp("on", n->child->string)) { + /* + * FIXME: no p->col to check. Thus, if we have + * .Bd -literal + * .Sm off + * 1 2 + * .Sm on + * 3 + * .Ed + * the "3" is preceded by a space. + */ + h->flags &= ~HTML_NOSPACE; + h->flags &= ~HTML_NONOSPACE; + } else + h->flags |= HTML_NONOSPACE; + + return(0); +} + +/* ARGSUSED */ +static int +mdoc_pp_pre(MDOC_ARGS) +{ + + print_otag(h, TAG_P, 0, NULL); + return(0); + +} + +/* ARGSUSED */ +static int +mdoc_sp_pre(MDOC_ARGS) +{ + struct roffsu su; + struct htmlpair tag; + + SCALE_VS_INIT(&su, 1); + + if (MDOC_sp == n->tok) { + if (n->child) + a2roffsu(n->child->string, &su, SCALE_VS); + } else + su.scale = 0; + + bufcat_su(h, "height", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + + /* So the div isn't empty: */ + print_text(h, "\\~"); + + return(0); + +} + +/* ARGSUSED */ +static int +mdoc_lk_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag[2]; + + nn = n->child; + + PAIR_CLASS_INIT(&tag[0], "link-ext"); + PAIR_HREF_INIT(&tag[1], nn->string); + print_otag(h, TAG_A, 2, tag); + + if (NULL == nn || NULL == nn->next) + return(1); + + for (nn = nn->next; nn; nn = nn->next) + print_text(h, nn->string); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_mt_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct tag *t; + const struct mdoc_node *nn; + + PAIR_CLASS_INIT(&tag[0], "link-mail"); + + for (nn = n->child; nn; nn = nn->next) { + bufinit(h); + bufcat(h, "mailto:"); + bufcat(h, nn->string); + PAIR_HREF_INIT(&tag[1], h->buf); + t = print_otag(h, TAG_A, 2, tag); + print_text(h, nn->string); + print_tagq(h, t); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_fo_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct tag *t; + + if (MDOC_BODY == n->type) { + h->flags |= HTML_NOSPACE; + print_text(h, "("); + h->flags |= HTML_NOSPACE; + return(1); + } else if (MDOC_BLOCK == n->type) { + synopsis_pre(h, n); + return(1); + } + + /* XXX: we drop non-initial arguments as per groff. */ + + assert(n->child); + assert(n->child->string); + + PAIR_CLASS_INIT(&tag, "fname"); + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, n->child->string); + print_tagq(h, t); + return(0); +} + + +/* ARGSUSED */ +static void +mdoc_fo_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + print_text(h, ")"); + print_text(h, ";"); +} + + +/* ARGSUSED */ +static int +mdoc_in_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct tag *t; + struct htmlpair tag[2]; + int i; + + synopsis_pre(h, n); + + PAIR_CLASS_INIT(&tag[0], "includes"); + print_otag(h, TAG_B, 1, tag); + + if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) + print_text(h, "#include"); + + print_text(h, "<"); + h->flags |= HTML_NOSPACE; + + for (nn = n->child; nn; nn = nn->next) { + PAIR_CLASS_INIT(&tag[0], "link-includes"); + i = 1; + bufinit(h); + if (h->base_includes) { + buffmt_includes(h, nn->string); + PAIR_HREF_INIT(&tag[i], h->buf); + i++; + } + t = print_otag(h, TAG_A, i, tag); + print_mdoc_node(m, nn, h); + print_tagq(h, t); + } + + h->flags |= HTML_NOSPACE; + print_text(h, ">"); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ic_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "cmd"); + print_otag(h, TAG_B, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_rv_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag; + struct tag *t; + + if (n->prev) + print_otag(h, TAG_BR, 0, NULL); + + print_text(h, "The"); + + for (nn = n->child; nn; nn = nn->next) { + PAIR_CLASS_INIT(&tag, "fname"); + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, nn->string); + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + if (nn->next && NULL == nn->next->next) + print_text(h, "(), and"); + else if (nn->next) + print_text(h, "(),"); + else + print_text(h, "()"); + } + + if (n->child && n->child->next) + print_text(h, "functions return"); + else + print_text(h, "function returns"); + + print_text(h, "the value 0 if successful; otherwise the value " + "-1 is returned and the global variable"); + + PAIR_CLASS_INIT(&tag, "var"); + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, "errno"); + print_tagq(h, t); + print_text(h, "is set to indicate the error."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_va_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "var"); + print_otag(h, TAG_B, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ap_pre(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; + print_text(h, "\\(aq"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bf_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct roffsu su; + + if (MDOC_HEAD == n->type) + return(0); + else if (MDOC_BODY != n->type) + return(1); + + if (FONT_Em == n->norm->Bf.font) + PAIR_CLASS_INIT(&tag[0], "emph"); + else if (FONT_Sy == n->norm->Bf.font) + PAIR_CLASS_INIT(&tag[0], "symb"); + else if (FONT_Li == n->norm->Bf.font) + PAIR_CLASS_INIT(&tag[0], "lit"); + else + PAIR_CLASS_INIT(&tag[0], "none"); + + /* + * We want this to be inline-formatted, but needs to be div to + * accept block children. + */ + bufcat_style(h, "display", "inline"); + SCALE_HS_INIT(&su, 1); + /* Needs a left-margin for spacing. */ + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ms_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "symb"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_igndelim_pre(MDOC_ARGS) +{ + + h->flags |= HTML_IGNDELIM; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_pf_post(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; +} + + +/* ARGSUSED */ +static int +mdoc_rs_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BLOCK != n->type) + return(1); + + if (n->prev && SEC_SEE_ALSO == n->sec) + print_otag(h, TAG_P, 0, NULL); + + PAIR_CLASS_INIT(&tag, "ref"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + + +/* ARGSUSED */ +static int +mdoc_li_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "lit"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sy_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "symb"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bt_pre(MDOC_ARGS) +{ + + print_text(h, "is currently in beta test."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ud_pre(MDOC_ARGS) +{ + + print_text(h, "currently under development."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_lb_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev) + print_otag(h, TAG_BR, 0, NULL); + + PAIR_CLASS_INIT(&tag, "lib"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc__x_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + enum htmltag t; + + t = TAG_SPAN; + + switch (n->tok) { + case(MDOC__A): + PAIR_CLASS_INIT(&tag[0], "ref-auth"); + if (n->prev && MDOC__A == n->prev->tok) + if (NULL == n->next || MDOC__A != n->next->tok) + print_text(h, "and"); + break; + case(MDOC__B): + PAIR_CLASS_INIT(&tag[0], "ref-book"); + t = TAG_I; + break; + case(MDOC__C): + PAIR_CLASS_INIT(&tag[0], "ref-city"); + break; + case(MDOC__D): + PAIR_CLASS_INIT(&tag[0], "ref-date"); + break; + case(MDOC__I): + PAIR_CLASS_INIT(&tag[0], "ref-issue"); + t = TAG_I; + break; + case(MDOC__J): + PAIR_CLASS_INIT(&tag[0], "ref-jrnl"); + t = TAG_I; + break; + case(MDOC__N): + PAIR_CLASS_INIT(&tag[0], "ref-num"); + break; + case(MDOC__O): + PAIR_CLASS_INIT(&tag[0], "ref-opt"); + break; + case(MDOC__P): + PAIR_CLASS_INIT(&tag[0], "ref-page"); + break; + case(MDOC__Q): + PAIR_CLASS_INIT(&tag[0], "ref-corp"); + break; + case(MDOC__R): + PAIR_CLASS_INIT(&tag[0], "ref-rep"); + break; + case(MDOC__T): + PAIR_CLASS_INIT(&tag[0], "ref-title"); + break; + case(MDOC__U): + PAIR_CLASS_INIT(&tag[0], "link-ref"); + break; + case(MDOC__V): + PAIR_CLASS_INIT(&tag[0], "ref-vol"); + break; + default: + abort(); + /* NOTREACHED */ + } + + if (MDOC__U != n->tok) { + print_otag(h, t, 1, tag); + return(1); + } + + PAIR_HREF_INIT(&tag[1], n->child->string); + print_otag(h, TAG_A, 2, tag); + + return(1); +} + + +/* ARGSUSED */ +static void +mdoc__x_post(MDOC_ARGS) +{ + + if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok) + if (NULL == n->next->next || MDOC__A != n->next->next->tok) + if (NULL == n->prev || MDOC__A != n->prev->tok) + return; + + /* TODO: %U */ + + if (NULL == n->parent || MDOC_Rs != n->parent->tok) + return; + + print_text(h, n->next ? "," : "."); +} + + +/* ARGSUSED */ +static int +mdoc_bk_pre(MDOC_ARGS) +{ + + switch (n->type) { + case (MDOC_BLOCK): + break; + case (MDOC_HEAD): + return(0); + case (MDOC_BODY): + if (n->parent->args || 0 == n->prev->nchild) + h->flags |= HTML_PREKEEP; + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_bk_post(MDOC_ARGS) +{ + + if (MDOC_BODY == n->type) + h->flags &= ~(HTML_KEEP | HTML_PREKEEP); +} + + +/* ARGSUSED */ +static int +mdoc_quote_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BODY != n->type) + return(1); + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + print_text(h, "\\(la"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + print_text(h, "\\(lC"); + break; + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + print_text(h, "\\(lB"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + print_text(h, "\\(lB"); + h->flags |= HTML_NOSPACE; + PAIR_CLASS_INIT(&tag, "opt"); + print_otag(h, TAG_SPAN, 1, &tag); + break; + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + /* FALLTHROUGH */ + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + print_text(h, "\\(lq"); + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + print_text(h, "("); + break; + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + print_text(h, "\\(oq"); + break; + default: + abort(); + /* NOTREACHED */ + } + + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_quote_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + + h->flags |= HTML_NOSPACE; + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + print_text(h, "\\(ra"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + print_text(h, "\\(rC"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + /* FALLTHROUGH */ + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + print_text(h, "\\(rB"); + break; + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + /* FALLTHROUGH */ + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + print_text(h, "\\(rq"); + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + print_text(h, ")"); + break; + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + print_text(h, "\\(aq"); + break; + default: + abort(); + /* NOTREACHED */ + } +} + + diff --git a/contrib/mdocml/mdoc_macro.c b/contrib/mdocml/mdoc_macro.c new file mode 100644 index 0000000000..1c97f88c95 --- /dev/null +++ b/contrib/mdocml/mdoc_macro.c @@ -0,0 +1,1753 @@ +/* $Id: mdoc_macro.c,v 1.99 2010/12/15 23:39:40 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2010 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +enum rew { /* see rew_dohalt() */ + REWIND_NONE, + REWIND_THIS, + REWIND_MORE, + REWIND_FORCE, + REWIND_LATER, + REWIND_ERROR +}; + +static int blk_full(MACRO_PROT_ARGS); +static int blk_exp_close(MACRO_PROT_ARGS); +static int blk_part_exp(MACRO_PROT_ARGS); +static int blk_part_imp(MACRO_PROT_ARGS); +static int ctx_synopsis(MACRO_PROT_ARGS); +static int in_line_eoln(MACRO_PROT_ARGS); +static int in_line_argn(MACRO_PROT_ARGS); +static int in_line(MACRO_PROT_ARGS); +static int obsolete(MACRO_PROT_ARGS); +static int phrase_ta(MACRO_PROT_ARGS); + +static int append_delims(struct mdoc *, + int, int *, char *); +static enum mdoct lookup(enum mdoct, const char *); +static enum mdoct lookup_raw(const char *); +static int make_pending(struct mdoc_node *, enum mdoct, + struct mdoc *, int, int); +static int phrase(struct mdoc *, int, int, char *); +static enum mdoct rew_alt(enum mdoct); +static enum rew rew_dohalt(enum mdoct, enum mdoc_type, + const struct mdoc_node *); +static int rew_elem(struct mdoc *, enum mdoct); +static int rew_last(struct mdoc *, + const struct mdoc_node *); +static int rew_sub(enum mdoc_type, struct mdoc *, + enum mdoct, int, int); + +const struct mdoc_macro __mdoc_macros[MDOC_MAX] = { + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ap */ + { in_line_eoln, MDOC_PROLOGUE }, /* Dd */ + { in_line_eoln, MDOC_PROLOGUE }, /* Dt */ + { in_line_eoln, MDOC_PROLOGUE }, /* Os */ + { blk_full, 0 }, /* Sh */ + { blk_full, 0 }, /* Ss */ + { in_line_eoln, 0 }, /* Pp */ + { blk_part_imp, MDOC_PARSED }, /* D1 */ + { blk_part_imp, MDOC_PARSED }, /* Dl */ + { blk_full, MDOC_EXPLICIT }, /* Bd */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ed */ + { blk_full, MDOC_EXPLICIT }, /* Bl */ + { blk_exp_close, MDOC_EXPLICIT }, /* El */ + { blk_full, MDOC_PARSED }, /* It */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* An */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* 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 }, /* Ic */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Li */ + { blk_full, 0 }, /* Nd */ + { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Op */ + { obsolete, 0 }, /* 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, 0 }, /* %A */ + { in_line_eoln, 0 }, /* %B */ + { in_line_eoln, 0 }, /* %D */ + { in_line_eoln, 0 }, /* %I */ + { in_line_eoln, 0 }, /* %J */ + { in_line_eoln, 0 }, /* %N */ + { in_line_eoln, 0 }, /* %O */ + { in_line_eoln, 0 }, /* %P */ + { in_line_eoln, 0 }, /* %R */ + { in_line_eoln, 0 }, /* %T */ + { in_line_eoln, 0 }, /* %V */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ac */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ao */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Aq */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* At */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Bc */ + { blk_full, MDOC_EXPLICIT }, /* Bf */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bo */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* 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_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Dc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Do */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Dq */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ec */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ef */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Em */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Fx */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ms */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* No */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Ns */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ox */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Pc */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Pf */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Po */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Pq */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Qc */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Ql */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Qo */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Qq */ + { blk_exp_close, MDOC_EXPLICIT }, /* Re */ + { blk_full, MDOC_EXPLICIT }, /* Rs */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Sc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* So */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Sq */ + { in_line_eoln, 0 }, /* Sm */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sx */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sy */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* 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_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Fc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Oo */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Oc */ + { blk_full, MDOC_EXPLICIT }, /* Bk */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ek */ + { in_line_eoln, 0 }, /* Bt */ + { in_line_eoln, 0 }, /* Hf */ + { obsolete, 0 }, /* 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 }, /* Brq */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bro */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Brc */ + { in_line_eoln, 0 }, /* %C */ + { obsolete, 0 }, /* Es */ + { obsolete, 0 }, /* En */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */ + { in_line_eoln, 0 }, /* %Q */ + { in_line_eoln, 0 }, /* br */ + { in_line_eoln, 0 }, /* sp */ + { in_line_eoln, 0 }, /* %U */ + { phrase_ta, MDOC_CALLABLE | MDOC_PARSED }, /* Ta */ +}; + +const struct mdoc_macro * const mdoc_macros = __mdoc_macros; + + +/* + * This is called at the end of parsing. It must traverse up the tree, + * closing out open [implicit] scopes. Obviously, open explicit scopes + * are errors. + */ +int +mdoc_macroend(struct mdoc *m) +{ + struct mdoc_node *n; + + /* Scan for open explicit scopes. */ + + n = MDOC_VALID & m->last->flags ? m->last->parent : m->last; + + for ( ; n; n = n->parent) + if (MDOC_BLOCK == n->type && + MDOC_EXPLICIT & mdoc_macros[n->tok].flags) + mdoc_nmsg(m, n, MANDOCERR_SCOPEEXIT); + + /* Rewind to the first. */ + + return(rew_last(m, m->first)); +} + + +/* + * Look up a macro from within a subsequent context. + */ +static enum mdoct +lookup(enum mdoct from, const char *p) +{ + /* FIXME: make -diag lists be un-PARSED. */ + + if ( ! (MDOC_PARSED & mdoc_macros[from].flags)) + return(MDOC_MAX); + return(lookup_raw(p)); +} + + +/* + * Lookup a macro following the initial line macro. + */ +static enum mdoct +lookup_raw(const char *p) +{ + enum mdoct res; + + if (MDOC_MAX == (res = mdoc_hash_find(p))) + return(MDOC_MAX); + if (MDOC_CALLABLE & mdoc_macros[res].flags) + return(res); + return(MDOC_MAX); +} + + +static int +rew_last(struct mdoc *mdoc, const struct mdoc_node *to) +{ + struct mdoc_node *n; + + assert(to); + mdoc->next = MDOC_NEXT_SIBLING; + + /* LINTED */ + while (mdoc->last != to) { + if ( ! mdoc_valid_post(mdoc)) + return(0); + n = mdoc->last; + mdoc->last = mdoc->last->parent; + assert(mdoc->last); + mdoc->last->last = n; + } + + return(mdoc_valid_post(mdoc)); +} + + +/* + * For a block closing macro, return the corresponding opening one. + * Otherwise, return the macro itself. + */ +static enum mdoct +rew_alt(enum mdoct tok) +{ + switch (tok) { + case (MDOC_Ac): + return(MDOC_Ao); + case (MDOC_Bc): + return(MDOC_Bo); + case (MDOC_Brc): + return(MDOC_Bro); + case (MDOC_Dc): + return(MDOC_Do); + case (MDOC_Ec): + return(MDOC_Eo); + case (MDOC_Ed): + return(MDOC_Bd); + case (MDOC_Ef): + return(MDOC_Bf); + case (MDOC_Ek): + return(MDOC_Bk); + case (MDOC_El): + return(MDOC_Bl); + case (MDOC_Fc): + return(MDOC_Fo); + case (MDOC_Oc): + return(MDOC_Oo); + case (MDOC_Pc): + return(MDOC_Po); + case (MDOC_Qc): + return(MDOC_Qo); + case (MDOC_Re): + return(MDOC_Rs); + case (MDOC_Sc): + return(MDOC_So); + case (MDOC_Xc): + return(MDOC_Xo); + default: + return(tok); + } + /* NOTREACHED */ +} + + +/* + * Rewinding to tok, how do we have to handle *p? + * REWIND_NONE: *p would delimit tok, but no tok scope is open + * inside *p, so there is no need to rewind anything at all. + * REWIND_THIS: *p matches tok, so rewind *p and nothing else. + * REWIND_MORE: *p is implicit, rewind it and keep searching for tok. + * REWIND_FORCE: *p is explicit, but tok is full, force rewinding *p. + * REWIND_LATER: *p is explicit and still open, postpone rewinding. + * REWIND_ERROR: No tok block is open at all. + */ +static enum rew +rew_dohalt(enum mdoct tok, enum mdoc_type type, + const struct mdoc_node *p) +{ + + /* + * No matching token, no delimiting block, no broken block. + * This can happen when full implicit macros are called for + * the first time but try to rewind their previous + * instance anyway. + */ + if (MDOC_ROOT == p->type) + return(MDOC_BLOCK == type && + MDOC_EXPLICIT & mdoc_macros[tok].flags ? + REWIND_ERROR : REWIND_NONE); + + /* + * When starting to rewind, skip plain text + * and nodes that have already been rewound. + */ + if (MDOC_TEXT == p->type || MDOC_VALID & p->flags) + return(REWIND_MORE); + + /* + * The easiest case: Found a matching token. + * This applies to both blocks and elements. + */ + tok = rew_alt(tok); + if (tok == p->tok) + return(p->end ? REWIND_NONE : + type == p->type ? REWIND_THIS : REWIND_MORE); + + /* + * While elements do require rewinding for themselves, + * they never affect rewinding of other nodes. + */ + if (MDOC_ELEM == p->type) + return(REWIND_MORE); + + /* + * Blocks delimited by our target token get REWIND_MORE. + * Blocks delimiting our target token get REWIND_NONE. + */ + switch (tok) { + case (MDOC_Bl): + if (MDOC_It == p->tok) + return(REWIND_MORE); + break; + case (MDOC_It): + if (MDOC_BODY == p->type && MDOC_Bl == p->tok) + return(REWIND_NONE); + break; + /* + * XXX Badly nested block handling still fails badly + * when one block is breaking two blocks of the same type. + * This is an incomplete and extremely ugly workaround, + * required to let the OpenBSD tree build. + */ + case (MDOC_Oo): + if (MDOC_Op == p->tok) + return(REWIND_MORE); + break; + case (MDOC_Nm): + return(REWIND_NONE); + case (MDOC_Nd): + /* FALLTHROUGH */ + case (MDOC_Ss): + if (MDOC_BODY == p->type && MDOC_Sh == p->tok) + return(REWIND_NONE); + /* FALLTHROUGH */ + case (MDOC_Sh): + if (MDOC_Nd == p->tok || MDOC_Ss == p->tok || + MDOC_Sh == p->tok) + return(REWIND_MORE); + break; + default: + break; + } + + /* + * Default block rewinding rules. + * In particular, always skip block end markers, + * and let all blocks rewind Nm children. + */ + if (ENDBODY_NOT != p->end || MDOC_Nm == p->tok || + (MDOC_BLOCK == p->type && + ! (MDOC_EXPLICIT & mdoc_macros[tok].flags))) + return(REWIND_MORE); + + /* + * By default, closing out full blocks + * forces closing of broken explicit blocks, + * while closing out partial blocks + * allows delayed rewinding by default. + */ + return (&blk_full == mdoc_macros[tok].fp ? + REWIND_FORCE : REWIND_LATER); +} + + +static int +rew_elem(struct mdoc *mdoc, enum mdoct tok) +{ + struct mdoc_node *n; + + n = mdoc->last; + if (MDOC_ELEM != n->type) + n = n->parent; + assert(MDOC_ELEM == n->type); + assert(tok == n->tok); + + return(rew_last(mdoc, n)); +} + + +/* + * We are trying to close a block identified by tok, + * but the child block *broken is still open. + * Thus, postpone closing the tok block + * until the rew_sub call closing *broken. + */ +static int +make_pending(struct mdoc_node *broken, enum mdoct tok, + struct mdoc *m, int line, int ppos) +{ + struct mdoc_node *breaker; + + /* + * Iterate backwards, searching for the block matching tok, + * that is, the block breaking the *broken block. + */ + for (breaker = broken->parent; breaker; breaker = breaker->parent) { + + /* + * If the *broken block had already been broken before + * and we encounter its breaker, make the tok block + * pending on the inner breaker. + * Graphically, "[A breaker=[B broken=[C->B B] tok=A] C]" + * becomes "[A broken=[B [C->B B] tok=A] C]" + * and finally "[A [B->A [C->B B] A] C]". + */ + if (breaker == broken->pending) { + broken = breaker; + continue; + } + + if (REWIND_THIS != rew_dohalt(tok, MDOC_BLOCK, breaker)) + continue; + if (MDOC_BODY == broken->type) + broken = broken->parent; + + /* + * Found the breaker. + * If another, outer breaker is already pending on + * the *broken block, we must not clobber the link + * to the outer breaker, but make it pending on the + * new, now inner breaker. + * Graphically, "[A breaker=[B broken=[C->A A] tok=B] C]" + * becomes "[A breaker=[B->A broken=[C A] tok=B] C]" + * and finally "[A [B->A [C->B A] B] C]". + */ + if (broken->pending) { + struct mdoc_node *taker; + + /* + * If the breaker had also been broken before, + * it cannot take on the outer breaker itself, + * but must hand it on to its own breakers. + * Graphically, this is the following situation: + * "[A [B breaker=[C->B B] broken=[D->A A] tok=C] D]" + * "[A taker=[B->A breaker=[C->B B] [D->C A] C] D]" + */ + taker = breaker; + while (taker->pending) + taker = taker->pending; + taker->pending = broken->pending; + } + broken->pending = breaker; + mdoc_vmsg(m, MANDOCERR_SCOPENEST, line, ppos, + "%s breaks %s", mdoc_macronames[tok], + mdoc_macronames[broken->tok]); + return(1); + } + + /* + * Found no matching block for tok. + * Are you trying to close a block that is not open? + */ + return(0); +} + + +static int +rew_sub(enum mdoc_type t, struct mdoc *m, + enum mdoct tok, int line, int ppos) +{ + struct mdoc_node *n; + + n = m->last; + while (n) { + switch (rew_dohalt(tok, t, n)) { + case (REWIND_NONE): + return(1); + case (REWIND_THIS): + break; + case (REWIND_FORCE): + mdoc_vmsg(m, MANDOCERR_SCOPEBROKEN, line, ppos, + "%s breaks %s", mdoc_macronames[tok], + mdoc_macronames[n->tok]); + /* FALLTHROUGH */ + case (REWIND_MORE): + n = n->parent; + continue; + case (REWIND_LATER): + if (make_pending(n, tok, m, line, ppos) || + MDOC_BLOCK != t) + return(1); + /* FALLTHROUGH */ + case (REWIND_ERROR): + mdoc_pmsg(m, line, ppos, MANDOCERR_NOSCOPE); + return(1); + } + break; + } + + assert(n); + if ( ! rew_last(m, n)) + return(0); + + /* + * The current block extends an enclosing block. + * Now that the current block ends, close the enclosing block, too. + */ + while (NULL != (n = n->pending)) { + if ( ! rew_last(m, n)) + return(0); + if (MDOC_HEAD == n->type && + ! mdoc_body_alloc(m, n->line, n->pos, n->tok)) + return(0); + } + + return(1); +} + + +static int +append_delims(struct mdoc *m, int line, int *pos, char *buf) +{ + int la; + enum margserr ac; + char *p; + + if ('\0' == buf[*pos]) + return(1); + + for (;;) { + la = *pos; + ac = mdoc_zargs(m, line, pos, buf, ARGS_NOWARN, &p); + + if (ARGS_ERROR == ac) + return(0); + else if (ARGS_EOLN == ac) + break; + + assert(DELIM_NONE != mdoc_isdelim(p)); + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + + /* + * If we encounter end-of-sentence symbols, then trigger + * the double-space. + * + * XXX: it's easy to allow this to propogate 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 propogate the double-space. + */ + if (mandoc_eos(p, strlen(p), 0)) + m->last->flags |= MDOC_EOS; + } + + return(1); +} + + +/* + * Close out block partial/full explicit. + */ +static int +blk_exp_close(MACRO_PROT_ARGS) +{ + struct mdoc_node *body; /* Our own body. */ + struct mdoc_node *later; /* A sub-block starting later. */ + struct mdoc_node *n; /* For searching backwards. */ + + int j, lastarg, maxargs, flushed, nl; + enum margserr ac; + enum mdoct atok, ntok; + char *p; + + nl = MDOC_NEWLINE & m->flags; + + switch (tok) { + case (MDOC_Ec): + maxargs = 1; + break; + default: + maxargs = 0; + break; + } + + /* + * Search backwards for beginnings of blocks, + * both of our own and of pending sub-blocks. + */ + atok = rew_alt(tok); + body = later = NULL; + for (n = m->last; n; n = n->parent) { + if (MDOC_VALID & n->flags) + continue; + + /* Remember the start of our own body. */ + if (MDOC_BODY == n->type && atok == n->tok) { + if (ENDBODY_NOT == n->end) + body = n; + continue; + } + + if (MDOC_BLOCK != n->type || MDOC_Nm == n->tok) + continue; + if (atok == n->tok) { + assert(body); + + /* + * Found the start of our own block. + * When there is no pending sub block, + * just proceed to closing out. + */ + if (NULL == later) + break; + + /* + * When there is a pending sub block, + * postpone closing out the current block + * until the rew_sub() closing out the sub-block. + */ + make_pending(later, tok, m, line, ppos); + + /* + * Mark the place where the formatting - but not + * the scope - of the current block ends. + */ + if ( ! mdoc_endbody_alloc(m, line, ppos, + atok, body, ENDBODY_SPACE)) + return(0); + break; + } + + /* + * When finding an open sub block, remember the last + * open explicit block, or, in case there are only + * implicit ones, the first open implicit block. + */ + if (later && + MDOC_EXPLICIT & mdoc_macros[later->tok].flags) + continue; + if (MDOC_CALLABLE & mdoc_macros[n->tok].flags) + later = n; + } + + if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) { + /* FIXME: do this in validate */ + if (buf[*pos]) + if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_ARGSLOST)) + return(0); + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + return(rew_sub(MDOC_BLOCK, m, tok, line, ppos)); + } + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + + if (NULL == later && maxargs > 0) + if ( ! mdoc_tail_alloc(m, line, ppos, rew_alt(tok))) + return(0); + + for (flushed = j = 0; ; j++) { + lastarg = *pos; + + if (j == maxargs && ! flushed) { + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + flushed = 1; + } + + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_PUNCT == ac) + break; + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! mdoc_word_alloc(m, line, lastarg, p)) + return(0); + continue; + } + + if ( ! flushed) { + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + flushed = 1; + } + if ( ! mdoc_macro(m, ntok, line, lastarg, pos, buf)) + return(0); + break; + } + + if ( ! flushed && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +in_line(MACRO_PROT_ARGS) +{ + int la, scope, cnt, nc, nl; + enum margverr av; + enum mdoct ntok; + enum margserr ac; + enum mdelim d; + struct mdoc_arg *arg; + char *p; + + nl = MDOC_NEWLINE & m->flags; + + /* + * Whether we allow ignored elements (those without content, + * usually because of reserved words) to squeak by. + */ + + switch (tok) { + case (MDOC_An): + /* FALLTHROUGH */ + case (MDOC_Ar): + /* FALLTHROUGH */ + case (MDOC_Fl): + /* FALLTHROUGH */ + case (MDOC_Mt): + /* FALLTHROUGH */ + case (MDOC_Nm): + /* FALLTHROUGH */ + case (MDOC_Pa): + nc = 1; + break; + default: + nc = 0; + break; + } + + for (arg = NULL;; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + for (cnt = scope = 0;; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + if (ARGS_PUNCT == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + /* + * In this case, we've located a submacro and must + * execute it. Close out scope, if open. If no + * elements have been generated, either create one (nc) + * or raise a warning. + */ + + if (MDOC_MAX != ntok) { + if (scope && ! rew_elem(m, tok)) + return(0); + if (nc && 0 == cnt) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if ( ! rew_last(m, m->last)) + return(0); + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY)) + return(0); + } + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); + } + + /* + * Non-quote-enclosed punctuation. Set up our scope, if + * a word; rewind the scope, if a delimiter; then append + * the word. + */ + + d = ARGS_QWORD == ac ? DELIM_NONE : mdoc_isdelim(p); + + if (DELIM_NONE != d) { + /* + * If we encounter closing punctuation, no word + * has been omitted, no scope is open, and we're + * allowed to have an empty element, then start + * a new scope. `Ar', `Fl', and `Li', only do + * this once per invocation. There may be more + * of these (all of them?). + */ + if (0 == cnt && (nc || MDOC_Li == tok) && + DELIM_CLOSE == d && ! scope) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if (MDOC_Ar == tok || MDOC_Li == tok || + MDOC_Fl == tok) + cnt++; + scope = 1; + } + /* + * Close out our scope, if one is open, before + * any punctuation. + */ + if (scope && ! rew_elem(m, tok)) + return(0); + scope = 0; + } else if ( ! scope) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + scope = 1; + } + + if (DELIM_NONE == d) + cnt++; + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + + /* + * `Fl' macros have their scope re-opened with each new + * word so that the `-' can be added to each one without + * having to parse out spaces. + */ + if (scope && MDOC_Fl == tok) { + if ( ! rew_elem(m, tok)) + return(0); + scope = 0; + } + } + + if (scope && ! rew_elem(m, tok)) + return(0); + + /* + * If no elements have been collected and we're allowed to have + * empties (nc), open a scope and close it out. Otherwise, + * raise a warning. + */ + + if (nc && 0 == cnt) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if ( ! rew_last(m, m->last)) + return(0); + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY)) + return(0); + } + + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +blk_full(MACRO_PROT_ARGS) +{ + int la, nl; + struct mdoc_arg *arg; + struct mdoc_node *head; /* save of head macro */ + struct mdoc_node *body; /* save of body macro */ + struct mdoc_node *n; + enum mdoc_type mtt; + enum mdoct ntok; + enum margserr ac, lac; + enum margverr av; + char *p; + + nl = MDOC_NEWLINE & m->flags; + + /* Close out prior implicit scope. */ + + if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) { + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + } + + /* + * This routine accomodates implicitly- and explicitly-scoped + * macro openings. Implicit ones first close out prior scope + * (seen above). Delay opening the head until necessary to + * allow leading punctuation to print. Special consideration + * for `It -column', which has phrase-part syntax instead of + * regular child nodes. + */ + + for (arg = NULL;; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + if ( ! mdoc_block_alloc(m, line, ppos, tok, arg)) + return(0); + + head = body = NULL; + + /* + * The `Nd' macro has all arguments in its body: it's a hybrid + * of block partial-explicit and full-implicit. Stupid. + */ + + if (MDOC_Nd == tok) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + } + + ac = ARGS_ERROR; + + for ( ; ; ) { + la = *pos; + /* Initialise last-phrase-type with ARGS_PEND. */ + lac = ARGS_ERROR == ac ? ARGS_PEND : ac; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_PUNCT == ac) + break; + + if (ARGS_ERROR == ac) + return(0); + + if (ARGS_EOLN == ac) { + if (ARGS_PPHRASE != lac && ARGS_PHRASE != lac) + break; + /* + * This is necessary: if the last token on a + * line is a `Ta' or tab, then we'll get + * ARGS_EOLN, so we must be smart enough to + * reopen our scope if the last parse was a + * phrase or partial phrase. + */ + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + break; + } + + /* + * Emit leading punctuation (i.e., punctuation before + * the MDOC_HEAD) for non-phrase types. + */ + + if (NULL == head && + ARGS_PEND != ac && + ARGS_PHRASE != ac && + ARGS_PPHRASE != ac && + ARGS_QWORD != ac && + DELIM_OPEN == mdoc_isdelim(p)) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + /* Open a head if one hasn't been opened. */ + + if (NULL == head) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + } + + if (ARGS_PHRASE == ac || + ARGS_PEND == ac || + ARGS_PPHRASE == ac) { + /* + * If we haven't opened a body yet, rewind the + * head; if we have, rewind that instead. + */ + + mtt = body ? MDOC_BODY : MDOC_HEAD; + if ( ! rew_sub(mtt, m, tok, line, ppos)) + return(0); + + /* Then allocate our body context. */ + + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + + /* + * Process phrases: set whether we're in a + * partial-phrase (this effects line handling) + * then call down into the phrase parser. + */ + + if (ARGS_PPHRASE == ac) + m->flags |= MDOC_PPHRASE; + if (ARGS_PEND == ac && ARGS_PPHRASE == lac) + m->flags |= MDOC_PPHRASE; + + if ( ! phrase(m, line, la, buf)) + return(0); + + m->flags &= ~MDOC_PPHRASE; + continue; + } + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + break; + } + + if (NULL == head) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + } + + if (nl && ! append_delims(m, line, pos, buf)) + return(0); + + /* If we've already opened our body, exit now. */ + + if (NULL != body) + goto out; + + /* + * If there is an open (i.e., unvalidated) sub-block requiring + * explicit close-out, postpone switching the current block from + * head to body until the rew_sub() call closing out that + * sub-block. + */ + for (n = m->last; n && n != head; n = n->parent) { + if (MDOC_BLOCK == n->type && + MDOC_EXPLICIT & mdoc_macros[n->tok].flags && + ! (MDOC_VALID & n->flags)) { + n->pending = head; + return(1); + } + } + + /* Close out scopes to remain in a consistent state. */ + + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + +out: + if ( ! (MDOC_FREECOL & m->flags)) + return(1); + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + + m->flags &= ~MDOC_FREECOL; + return(1); +} + + +static int +blk_part_imp(MACRO_PROT_ARGS) +{ + int la, nl; + enum mdoct ntok; + enum margserr ac; + char *p; + struct mdoc_node *blk; /* saved block context */ + struct mdoc_node *body; /* saved body context */ + struct mdoc_node *n; + + nl = MDOC_NEWLINE & m->flags; + + /* + * A macro that spans to the end of the line. This is generally + * (but not necessarily) called as the first macro. The block + * has a head as the immediate child, which is always empty, + * followed by zero or more opening punctuation nodes, then the + * body (which may be empty, depending on the macro), then zero + * or more closing punctuation nodes. + */ + + if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL)) + return(0); + + blk = m->last; + + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + + /* + * Open the body scope "on-demand", that is, after we've + * processed all our the leading delimiters (open parenthesis, + * etc.). + */ + + for (body = NULL; ; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + if (ARGS_PUNCT == ac) + break; + + if (NULL == body && ARGS_QWORD != ac && + DELIM_OPEN == mdoc_isdelim(p)) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if (NULL == body) { + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + } + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + break; + } + + /* Clean-ups to leave in a consistent state. */ + + if (NULL == body) { + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + } + + for (n = body->child; n && n->next; n = n->next) + /* Do nothing. */ ; + + /* + * End of sentence spacing: if the last node is a text node and + * has a trailing period, then mark it as being end-of-sentence. + */ + + if (n && MDOC_TEXT == n->type && n->string) + if (mandoc_eos(n->string, strlen(n->string), 1)) + n->flags |= MDOC_EOS; + + /* Up-propogate the end-of-space flag. */ + + if (n && (MDOC_EOS & n->flags)) { + body->flags |= MDOC_EOS; + body->parent->flags |= MDOC_EOS; + } + + /* + * If there is an open sub-block requiring explicit close-out, + * postpone closing out the current block + * until the rew_sub() call closing out the sub-block. + */ + for (n = m->last; n && n != body && n != blk->parent; n = n->parent) { + if (MDOC_BLOCK == n->type && + MDOC_EXPLICIT & mdoc_macros[n->tok].flags && + ! (MDOC_VALID & n->flags)) { + make_pending(n, tok, m, line, ppos); + if ( ! mdoc_endbody_alloc(m, line, ppos, + tok, body, ENDBODY_NOSPACE)) + return(0); + return(1); + } + } + + /* + * If we can't rewind to our body, then our scope has already + * been closed by another macro (like `Oc' closing `Op'). This + * is ugly behaviour nodding its head to OpenBSD's overwhelming + * crufty use of `Op' breakage. + */ + if (n != body && ! mdoc_vmsg(m, MANDOCERR_SCOPENEST, + line, ppos, "%s broken", mdoc_macronames[tok])) + return(0); + + if (n && ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + + /* Standard appending of delimiters. */ + + if (nl && ! append_delims(m, line, pos, buf)) + return(0); + + /* Rewind scope, if applicable. */ + + if (n && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + + return(1); +} + + +static int +blk_part_exp(MACRO_PROT_ARGS) +{ + int la, nl; + enum margserr ac; + struct mdoc_node *head; /* keep track of head */ + struct mdoc_node *body; /* keep track of body */ + char *p; + enum mdoct ntok; + + nl = MDOC_NEWLINE & m->flags; + + /* + * The opening of an explicit macro having zero or more leading + * punctuation nodes; a head with optional single element (the + * case of `Eo'); and a body that may be empty. + */ + + if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL)) + return(0); + + for (head = body = NULL; ; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_PUNCT == ac) + break; + if (ARGS_EOLN == ac) + break; + + /* Flush out leading punctuation. */ + + if (NULL == head && ARGS_QWORD != ac && + DELIM_OPEN == mdoc_isdelim(p)) { + assert(NULL == body); + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if (NULL == head) { + assert(NULL == body); + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + } + + /* + * `Eo' gobbles any data into the head, but most other + * macros just immediately close out and begin the body. + */ + + if (NULL == body) { + assert(head); + /* No check whether it's a macro! */ + if (MDOC_Eo == tok) + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + + if (MDOC_Eo == tok) + continue; + } + + assert(NULL != head && NULL != body); + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + break; + } + + /* Clean-up to leave in a consistent state. */ + + if (NULL == head) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + } + + if (NULL == body) { + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + } + + /* Standard appending of delimiters. */ + + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +/* ARGSUSED */ +static int +in_line_argn(MACRO_PROT_ARGS) +{ + int la, flushed, j, maxargs, nl; + enum margserr ac; + enum margverr av; + struct mdoc_arg *arg; + char *p; + enum mdoct ntok; + + nl = MDOC_NEWLINE & m->flags; + + /* + * A line macro that has a fixed number of arguments (maxargs). + * Only open the scope once the first non-leading-punctuation is + * found (unless MDOC_IGNDELIM is noted, like in `Pf'), then + * keep it open until the maximum number of arguments are + * exhausted. + */ + + switch (tok) { + case (MDOC_Ap): + /* FALLTHROUGH */ + case (MDOC_No): + /* FALLTHROUGH */ + case (MDOC_Ns): + /* FALLTHROUGH */ + case (MDOC_Ux): + maxargs = 0; + break; + case (MDOC_Xr): + maxargs = 2; + break; + default: + maxargs = 1; + break; + } + + for (arg = NULL; ; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + for (flushed = j = 0; ; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_PUNCT == ac) + break; + if (ARGS_EOLN == ac) + break; + + if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && + ARGS_QWORD != ac && + 0 == j && DELIM_OPEN == mdoc_isdelim(p)) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } else if (0 == j) + if ( ! mdoc_elem_alloc(m, line, la, tok, arg)) + return(0); + + if (j == maxargs && ! flushed) { + if ( ! rew_elem(m, tok)) + return(0); + flushed = 1; + } + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX != ntok) { + if ( ! flushed && ! rew_elem(m, tok)) + return(0); + flushed = 1; + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + j++; + break; + } + + if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && + ARGS_QWORD != ac && + ! flushed && + DELIM_NONE != mdoc_isdelim(p)) { + if ( ! rew_elem(m, tok)) + return(0); + flushed = 1; + } + + /* + * XXX: this is a hack to work around groff's ugliness + * as regards `Xr' and extraneous arguments. It should + * ideally be deprecated behaviour, but because this is + * code is no here, it's unlikely to be removed. + */ + +#ifdef __OpenBSD__ + if (MDOC_Xr == tok && j == maxargs) { + if ( ! mdoc_elem_alloc(m, line, la, MDOC_Ns, NULL)) + return(0); + if ( ! rew_elem(m, MDOC_Ns)) + return(0); + } +#endif + + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + j++; + } + + if (0 == j && ! mdoc_elem_alloc(m, line, la, tok, arg)) + return(0); + + /* Close out in a consistent state. */ + + if ( ! flushed && ! rew_elem(m, tok)) + return(0); + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +in_line_eoln(MACRO_PROT_ARGS) +{ + int la; + enum margserr ac; + enum margverr av; + struct mdoc_arg *arg; + char *p; + enum mdoct ntok; + + assert( ! (MDOC_PARSED & mdoc_macros[tok].flags)); + + if (tok == MDOC_Pp) + rew_sub(MDOC_BLOCK, m, MDOC_Nm, line, ppos); + + /* Parse macro arguments. */ + + for (arg = NULL; ; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + /* Open element scope. */ + + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + + /* Parse argument terms. */ + + for (;;) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if ( ! rew_elem(m, tok)) + return(0); + return(mdoc_macro(m, ntok, line, la, pos, buf)); + } + + /* Close out (no delimiters). */ + + return(rew_elem(m, tok)); +} + + +/* ARGSUSED */ +static int +ctx_synopsis(MACRO_PROT_ARGS) +{ + int nl; + + nl = MDOC_NEWLINE & m->flags; + + /* If we're not in the SYNOPSIS, go straight to in-line. */ + if ( ! (MDOC_SYNOPSIS & m->flags)) + return(in_line(m, tok, line, ppos, pos, buf)); + + /* If we're a nested call, same place. */ + if ( ! nl) + return(in_line(m, tok, line, ppos, pos, buf)); + + /* + * XXX: this will open a block scope; however, if later we end + * up formatting the block scope, then child nodes will inherit + * the formatting. Be careful. + */ + if (MDOC_Nm == tok) + return(blk_full(m, tok, line, ppos, pos, buf)); + assert(MDOC_Vt == tok); + return(blk_part_imp(m, tok, line, ppos, pos, buf)); +} + + +/* ARGSUSED */ +static int +obsolete(MACRO_PROT_ARGS) +{ + + return(mdoc_pmsg(m, line, ppos, MANDOCERR_MACROOBS)); +} + + +/* + * Phrases occur within `Bl -column' entries, separated by `Ta' or tabs. + * They're unusual because they're basically free-form text until a + * macro is encountered. + */ +static int +phrase(struct mdoc *m, int line, int ppos, char *buf) +{ + int la, pos; + enum margserr ac; + enum mdoct ntok; + char *p; + + for (pos = ppos; ; ) { + la = pos; + + ac = mdoc_zargs(m, line, &pos, buf, 0, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p); + + if (MDOC_MAX == ntok) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, &pos, buf)) + return(0); + return(append_delims(m, line, &pos, buf)); + } + + return(1); +} + + +/* ARGSUSED */ +static int +phrase_ta(MACRO_PROT_ARGS) +{ + int la; + enum mdoct ntok; + enum margserr ac; + char *p; + + /* + * FIXME: this is overly restrictive: if the `Ta' is unexpected, + * it should simply error out with ARGSLOST. + */ + + if ( ! rew_sub(MDOC_BODY, m, MDOC_It, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, MDOC_It)) + return(0); + + for (;;) { + la = *pos; + ac = mdoc_zargs(m, line, pos, buf, 0, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p); + + if (MDOC_MAX == ntok) { + if ( ! mdoc_word_alloc(m, line, la, p)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + return(append_delims(m, line, pos, buf)); + } + + return(1); +} diff --git a/contrib/mdocml/mdoc_strings.c b/contrib/mdocml/mdoc_strings.c new file mode 100644 index 0000000000..e7ced1fe1b --- /dev/null +++ b/contrib/mdocml/mdoc_strings.c @@ -0,0 +1,219 @@ +/* $Id: mdoc_strings.c,v 1.24 2010/07/31 23:52:58 schwarze Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +static const char * const secnames[SEC__MAX] = { + NULL, + "NAME", + "LIBRARY", + "SYNOPSIS", + "DESCRIPTION", + "IMPLEMENTATION NOTES", + "RETURN VALUES", + "ENVIRONMENT", + "FILES", + "EXIT STATUS", + "EXAMPLES", + "DIAGNOSTICS", + "COMPATIBILITY", + "ERRORS", + "SEE ALSO", + "STANDARDS", + "HISTORY", + "AUTHORS", + "CAVEATS", + "BUGS", + "SECURITY CONSIDERATIONS", + NULL +}; + +/* + * FIXME: this is repeated in print_text() (html.c) and term_word() + * (term.c). + */ +enum mdelim +mdoc_iscdelim(char p) +{ + + switch (p) { + case('('): + /* FALLTHROUGH */ + case('['): + return(DELIM_OPEN); + case('|'): + return(DELIM_MIDDLE); + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + return(DELIM_CLOSE); + default: + break; + } + + return(DELIM_NONE); +} + + +enum mdelim +mdoc_isdelim(const char *p) +{ + + if ('\0' == p[0]) + return(DELIM_NONE); + if ('\0' == p[1]) + return(mdoc_iscdelim(p[0])); + + /* + * XXX; account for groff bubu where the \*(Ba reserved string + * is treated in exactly the same way as the vertical bar. This + * is the only function that checks for this. + */ + return(strcmp(p, "\\*(Ba") ? DELIM_NONE : DELIM_MIDDLE); +} + + +enum mdoc_sec +mdoc_str2sec(const char *p) +{ + int i; + + for (i = 0; i < (int)SEC__MAX; i++) + if (secnames[i] && 0 == strcmp(p, secnames[i])) + return((enum mdoc_sec)i); + + return(SEC_CUSTOM); +} + + +/* FIXME: move this into an editable .in file. */ +size_t +mdoc_macro2len(enum mdoct macro) +{ + + switch (macro) { + case(MDOC_Ad): + return(12); + case(MDOC_Ao): + return(12); + case(MDOC_An): + return(12); + case(MDOC_Aq): + return(12); + case(MDOC_Ar): + return(12); + case(MDOC_Bo): + return(12); + case(MDOC_Bq): + return(12); + case(MDOC_Cd): + return(12); + case(MDOC_Cm): + return(10); + case(MDOC_Do): + return(10); + case(MDOC_Dq): + return(12); + case(MDOC_Dv): + return(12); + case(MDOC_Eo): + return(12); + case(MDOC_Em): + return(10); + case(MDOC_Er): + return(17); + case(MDOC_Ev): + return(15); + case(MDOC_Fa): + return(12); + case(MDOC_Fl): + return(10); + case(MDOC_Fo): + return(16); + case(MDOC_Fn): + return(16); + case(MDOC_Ic): + return(10); + case(MDOC_Li): + return(16); + case(MDOC_Ms): + return(6); + case(MDOC_Nm): + return(10); + case(MDOC_No): + return(12); + case(MDOC_Oo): + return(10); + case(MDOC_Op): + return(14); + case(MDOC_Pa): + return(32); + case(MDOC_Pf): + return(12); + case(MDOC_Po): + return(12); + case(MDOC_Pq): + return(12); + case(MDOC_Ql): + return(16); + case(MDOC_Qo): + return(12); + case(MDOC_So): + return(12); + case(MDOC_Sq): + return(12); + case(MDOC_Sy): + return(6); + case(MDOC_Sx): + return(16); + case(MDOC_Tn): + return(10); + case(MDOC_Va): + return(12); + case(MDOC_Vt): + return(12); + case(MDOC_Xr): + return(10); + default: + break; + }; + return(0); +} diff --git a/contrib/mdocml/mdoc_term.c b/contrib/mdocml/mdoc_term.c new file mode 100644 index 0000000000..0f699abd83 --- /dev/null +++ b/contrib/mdocml/mdoc_term.c @@ -0,0 +1,2200 @@ +/* $Id: mdoc_term.c,v 1.208 2011/01/06 14:05:12 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2010 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "term.h" +#include "mdoc.h" +#include "chars.h" +#include "main.h" + +#define INDENT 5 +#define HALFINDENT 3 + +struct termpair { + struct termpair *ppair; + int count; +}; + +#define DECL_ARGS struct termp *p, \ + struct termpair *pair, \ + const struct mdoc_meta *m, \ + const struct mdoc_node *n + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); +}; + +static size_t a2width(const struct termp *, const char *); +static size_t a2height(const struct termp *, const char *); +static size_t a2offs(const struct termp *, const char *); + +static void print_bvspace(struct termp *, + const struct mdoc_node *, + const struct mdoc_node *); +static void print_mdoc_node(DECL_ARGS); +static void print_mdoc_nodelist(DECL_ARGS); +static void print_mdoc_head(struct termp *, const void *); +static void print_mdoc_foot(struct termp *, const void *); +static void synopsis_pre(struct termp *, + const struct mdoc_node *); + +static void termp____post(DECL_ARGS); +static void termp__t_post(DECL_ARGS); +static void termp_an_post(DECL_ARGS); +static void termp_bd_post(DECL_ARGS); +static void termp_bk_post(DECL_ARGS); +static void termp_bl_post(DECL_ARGS); +static void termp_bx_post(DECL_ARGS); +static void termp_d1_post(DECL_ARGS); +static void termp_fo_post(DECL_ARGS); +static void termp_in_post(DECL_ARGS); +static void termp_it_post(DECL_ARGS); +static void termp_lb_post(DECL_ARGS); +static void termp_nm_post(DECL_ARGS); +static void termp_pf_post(DECL_ARGS); +static void termp_quote_post(DECL_ARGS); +static void termp_sh_post(DECL_ARGS); +static void termp_ss_post(DECL_ARGS); + +static int termp__a_pre(DECL_ARGS); +static int termp__t_pre(DECL_ARGS); +static int termp_an_pre(DECL_ARGS); +static int termp_ap_pre(DECL_ARGS); +static int termp_bd_pre(DECL_ARGS); +static int termp_bf_pre(DECL_ARGS); +static int termp_bk_pre(DECL_ARGS); +static int termp_bl_pre(DECL_ARGS); +static int termp_bold_pre(DECL_ARGS); +static int termp_bt_pre(DECL_ARGS); +static int termp_cd_pre(DECL_ARGS); +static int termp_d1_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_igndelim_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_rv_pre(DECL_ARGS); +static int termp_sh_pre(DECL_ARGS); +static int termp_sm_pre(DECL_ARGS); +static int termp_sp_pre(DECL_ARGS); +static int termp_ss_pre(DECL_ARGS); +static int termp_under_pre(DECL_ARGS); +static int termp_ud_pre(DECL_ARGS); +static int termp_vt_pre(DECL_ARGS); +static int termp_xr_pre(DECL_ARGS); +static int termp_xx_pre(DECL_ARGS); + +static const struct termact termacts[MDOC_MAX] = { + { termp_ap_pre, NULL }, /* Ap */ + { NULL, NULL }, /* Dd */ + { NULL, NULL }, /* Dt */ + { NULL, NULL }, /* Os */ + { termp_sh_pre, termp_sh_post }, /* Sh */ + { termp_ss_pre, termp_ss_post }, /* Ss */ + { termp_sp_pre, NULL }, /* Pp */ + { termp_d1_pre, termp_d1_post }, /* D1 */ + { termp_d1_pre, termp_d1_post }, /* Dl */ + { termp_bd_pre, termp_bd_post }, /* Bd */ + { NULL, NULL }, /* Ed */ + { termp_bl_pre, termp_bl_post }, /* Bl */ + { NULL, NULL }, /* El */ + { termp_it_pre, termp_it_post }, /* It */ + { termp_under_pre, NULL }, /* Ad */ + { termp_an_pre, termp_an_post }, /* An */ + { termp_under_pre, NULL }, /* Ar */ + { termp_cd_pre, NULL }, /* Cd */ + { termp_bold_pre, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { NULL, NULL }, /* Er */ + { NULL, NULL }, /* Ev */ + { termp_ex_pre, NULL }, /* Ex */ + { termp_fa_pre, NULL }, /* Fa */ + { termp_fd_pre, NULL }, /* 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 */ + { NULL, NULL }, /* Ot */ + { termp_under_pre, NULL }, /* Pa */ + { termp_rv_pre, NULL }, /* Rv */ + { NULL, NULL }, /* St */ + { termp_under_pre, NULL }, /* Va */ + { termp_vt_pre, NULL }, /* Vt */ + { termp_xr_pre, NULL }, /* Xr */ + { termp__a_pre, termp____post }, /* %A */ + { termp_under_pre, termp____post }, /* %B */ + { NULL, termp____post }, /* %D */ + { termp_under_pre, termp____post }, /* %I */ + { termp_under_pre, termp____post }, /* %J */ + { NULL, termp____post }, /* %N */ + { NULL, termp____post }, /* %O */ + { NULL, termp____post }, /* %P */ + { NULL, termp____post }, /* %R */ + { termp__t_pre, termp__t_post }, /* %T */ + { NULL, termp____post }, /* %V */ + { NULL, NULL }, /* Ac */ + { termp_quote_pre, termp_quote_post }, /* Ao */ + { termp_quote_pre, termp_quote_post }, /* Aq */ + { NULL, NULL }, /* At */ + { NULL, NULL }, /* Bc */ + { termp_bf_pre, NULL }, /* Bf */ + { termp_quote_pre, termp_quote_post }, /* Bo */ + { termp_quote_pre, termp_quote_post }, /* Bq */ + { termp_xx_pre, NULL }, /* Bsx */ + { NULL, termp_bx_post }, /* Bx */ + { NULL, NULL }, /* Db */ + { NULL, NULL }, /* Dc */ + { termp_quote_pre, termp_quote_post }, /* Do */ + { termp_quote_pre, termp_quote_post }, /* Dq */ + { NULL, NULL }, /* Ec */ /* FIXME: no space */ + { NULL, NULL }, /* Ef */ + { termp_under_pre, NULL }, /* Em */ + { NULL, NULL }, /* Eo */ + { termp_xx_pre, NULL }, /* Fx */ + { termp_bold_pre, NULL }, /* Ms */ + { termp_igndelim_pre, NULL }, /* No */ + { termp_ns_pre, NULL }, /* Ns */ + { termp_xx_pre, NULL }, /* Nx */ + { termp_xx_pre, NULL }, /* Ox */ + { NULL, NULL }, /* Pc */ + { termp_igndelim_pre, termp_pf_post }, /* Pf */ + { termp_quote_pre, termp_quote_post }, /* Po */ + { termp_quote_pre, termp_quote_post }, /* Pq */ + { NULL, NULL }, /* Qc */ + { termp_quote_pre, termp_quote_post }, /* Ql */ + { termp_quote_pre, termp_quote_post }, /* Qo */ + { termp_quote_pre, termp_quote_post }, /* Qq */ + { NULL, NULL }, /* Re */ + { termp_rs_pre, NULL }, /* Rs */ + { NULL, NULL }, /* Sc */ + { termp_quote_pre, termp_quote_post }, /* So */ + { termp_quote_pre, termp_quote_post }, /* Sq */ + { termp_sm_pre, NULL }, /* Sm */ + { termp_under_pre, NULL }, /* Sx */ + { termp_bold_pre, NULL }, /* Sy */ + { NULL, NULL }, /* Tn */ + { termp_xx_pre, NULL }, /* Ux */ + { NULL, NULL }, /* Xc */ + { NULL, NULL }, /* Xo */ + { termp_fo_pre, termp_fo_post }, /* Fo */ + { NULL, NULL }, /* Fc */ + { termp_quote_pre, termp_quote_post }, /* Oo */ + { NULL, NULL }, /* Oc */ + { termp_bk_pre, termp_bk_post }, /* Bk */ + { NULL, NULL }, /* Ek */ + { termp_bt_pre, NULL }, /* Bt */ + { NULL, NULL }, /* Hf */ + { NULL, NULL }, /* Fr */ + { termp_ud_pre, NULL }, /* Ud */ + { NULL, termp_lb_post }, /* Lb */ + { termp_sp_pre, NULL }, /* Lp */ + { termp_lk_pre, NULL }, /* Lk */ + { termp_under_pre, NULL }, /* Mt */ + { termp_quote_pre, termp_quote_post }, /* Brq */ + { termp_quote_pre, termp_quote_post }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, termp____post }, /* %C */ + { NULL, NULL }, /* Es */ /* TODO */ + { NULL, NULL }, /* En */ /* TODO */ + { termp_xx_pre, NULL }, /* Dx */ + { NULL, termp____post }, /* %Q */ + { termp_sp_pre, NULL }, /* br */ + { termp_sp_pre, NULL }, /* sp */ + { termp_under_pre, termp____post }, /* %U */ + { NULL, NULL }, /* Ta */ +}; + + +void +terminal_mdoc(void *arg, const struct mdoc *mdoc) +{ + const struct mdoc_node *n; + const struct mdoc_meta *m; + struct termp *p; + + p = (struct termp *)arg; + + p->overstep = 0; + p->maxrmargin = p->defrmargin; + p->tabwidth = term_len(p, 5); + + if (NULL == p->symtab) + switch (p->enc) { + case (TERMENC_ASCII): + p->symtab = chars_init(CHARS_ASCII); + break; + default: + abort(); + /* NOTREACHED */ + } + + n = mdoc_node(mdoc); + m = mdoc_meta(mdoc); + + term_begin(p, print_mdoc_head, print_mdoc_foot, m); + + if (n->child) + print_mdoc_nodelist(p, NULL, m, n->child); + + term_end(p); +} + + +static void +print_mdoc_nodelist(DECL_ARGS) +{ + + print_mdoc_node(p, pair, m, n); + if (n->next) + print_mdoc_nodelist(p, pair, m, n->next); +} + + +/* ARGSUSED */ +static void +print_mdoc_node(DECL_ARGS) +{ + int chld; + const void *font; + struct termpair npair; + size_t offset, rmargin; + + chld = 1; + offset = p->offset; + rmargin = p->rmargin; + font = term_fontq(p); + + memset(&npair, 0, sizeof(struct termpair)); + npair.ppair = pair; + + switch (n->type) { + case (MDOC_TEXT): + term_word(p, n->string); + break; + case (MDOC_TBL): + term_tbl(p, n->span); + break; + default: + if (termacts[n->tok].pre && ENDBODY_NOT == n->end) + chld = (*termacts[n->tok].pre) + (p, &npair, m, n); + break; + } + + /* + * Keeps only work until the end of a line. If a keep was + * invoked in a prior line, revert it to PREKEEP. + * + * Also let SYNPRETTY sections behave as if they were wrapped + * in a `Bk' block. + */ + + if (TERMP_KEEP & p->flags || MDOC_SYNPRETTY & n->flags) { + if (n->prev && n->prev->line != n->line) { + p->flags &= ~TERMP_KEEP; + p->flags |= TERMP_PREKEEP; + } else if (NULL == n->prev) { + if (n->parent && n->parent->line != n->line) { + p->flags &= ~TERMP_KEEP; + p->flags |= TERMP_PREKEEP; + } + } + } + + /* + * Since SYNPRETTY sections aren't "turned off" with `Ek', + * we have to intuit whether we should disable formatting. + */ + + if ( ! (MDOC_SYNPRETTY & n->flags) && + ((n->prev && MDOC_SYNPRETTY & n->prev->flags) || + (n->parent && MDOC_SYNPRETTY & n->parent->flags))) + p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); + + if (chld && n->child) + print_mdoc_nodelist(p, &npair, m, n->child); + + term_fontpopq(p, font); + + switch (n->type) { + case (MDOC_TEXT): + break; + case (MDOC_TBL): + break; + default: + if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags) + break; + (void)(*termacts[n->tok].post)(p, &npair, m, n); + + /* + * Explicit end tokens not only call the post + * handler, but also tell the respective block + * that it must not call the post handler again. + */ + if (ENDBODY_NOT != n->end) + n->pending->flags |= MDOC_ENDED; + + /* + * End of line terminating an implicit block + * while an explicit block is still open. + * Continue the explicit block without spacing. + */ + if (ENDBODY_NOSPACE == n->end) + p->flags |= TERMP_NOSPACE; + break; + } + + if (MDOC_EOS & n->flags) + p->flags |= TERMP_SENTENCE; + + p->offset = offset; + p->rmargin = rmargin; +} + + +static void +print_mdoc_foot(struct termp *p, const void *arg) +{ + char buf[DATESIZ], os[BUFSIZ]; + const struct mdoc_meta *m; + + m = (const struct mdoc_meta *)arg; + + term_fontrepl(p, TERMFONT_NONE); + + /* + * Output the footer in new-groff style, that is, three columns + * with the middle being the manual date and flanking columns + * being the operating system: + * + * SYSTEM DATE SYSTEM + */ + + time2a(m->date, buf, DATESIZ); + strlcpy(os, m->os, BUFSIZ); + + term_vspace(p); + + p->offset = 0; + p->rmargin = (p->maxrmargin - + term_strlen(p, buf) + term_len(p, 1)) / 2; + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + + term_word(p, os); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin - term_strlen(p, os); + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, buf); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, os); + term_flushln(p); + + p->offset = 0; + p->rmargin = p->maxrmargin; + p->flags = 0; +} + + +static void +print_mdoc_head(struct termp *p, const void *arg) +{ + char buf[BUFSIZ], title[BUFSIZ]; + const struct mdoc_meta *m; + + m = (const struct mdoc_meta *)arg; + + p->rmargin = p->maxrmargin; + p->offset = 0; + + /* + * 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(m->vol); + strlcpy(buf, m->vol, BUFSIZ); + + if (m->arch) { + strlcat(buf, " (", BUFSIZ); + strlcat(buf, m->arch, BUFSIZ); + strlcat(buf, ")", BUFSIZ); + } + + snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec); + + p->offset = 0; + p->rmargin = (p->maxrmargin - + term_strlen(p, buf) + term_len(p, 1)) / 2; + p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; + + term_word(p, title); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin - term_strlen(p, title); + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, buf); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + + term_word(p, title); + term_flushln(p); + + p->offset = 0; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOSPACE; +} + + +static size_t +a2height(const struct termp *p, const char *v) +{ + struct roffsu su; + + assert(v); + if ( ! a2roffsu(v, &su, SCALE_VS)) + SCALE_VS_INIT(&su, term_len(p, 1)); + + return(term_vspan(p, &su)); +} + + +static size_t +a2width(const struct termp *p, const char *v) +{ + struct roffsu su; + + assert(v); + if ( ! a2roffsu(v, &su, SCALE_MAX)) + SCALE_HS_INIT(&su, term_strlen(p, v)); + + return(term_hspan(p, &su)); +} + + +static size_t +a2offs(const struct termp *p, const char *v) +{ + struct roffsu su; + + if ('\0' == *v) + return(0); + else if (0 == strcmp(v, "left")) + return(0); + else if (0 == strcmp(v, "indent")) + return(term_len(p, INDENT + 1)); + else if (0 == strcmp(v, "indent-two")) + return(term_len(p, (INDENT + 1) * 2)); + else if ( ! a2roffsu(v, &su, SCALE_MAX)) + SCALE_HS_INIT(&su, term_strlen(p, v)); + + return(term_hspan(p, &su)); +} + + +/* + * Determine how much space to print out before block elements of `It' + * (and thus `Bl') and `Bd'. And then go ahead and print that space, + * too. + */ +static void +print_bvspace(struct termp *p, + const struct mdoc_node *bl, + const struct mdoc_node *n) +{ + const struct mdoc_node *nn; + + term_newln(p); + + if (MDOC_Bd == bl->tok && bl->norm->Bd.comp) + return; + if (MDOC_Bl == bl->tok && bl->norm->Bl.comp) + return; + + /* Do not vspace directly after Ss/Sh. */ + + for (nn = n; nn; nn = nn->parent) { + if (MDOC_BLOCK != nn->type) + continue; + if (MDOC_Ss == nn->tok) + return; + if (MDOC_Sh == nn->tok) + return; + if (NULL == nn->prev) + continue; + break; + } + + /* A `-column' does not assert vspace within the list. */ + + if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type) + if (n->prev && MDOC_It == n->prev->tok) + return; + + /* A `-diag' without body does not vspace. */ + + if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type) + if (n->prev && MDOC_It == n->prev->tok) { + assert(n->prev->body); + if (NULL == n->prev->body->child) + return; + } + + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_it_pre(DECL_ARGS) +{ + const struct mdoc_node *bl, *nn; + char buf[7]; + int i; + size_t width, offset, ncols, dcol; + enum mdoc_list type; + + if (MDOC_BLOCK == n->type) { + print_bvspace(p, n->parent->parent, n); + return(1); + } + + bl = n->parent->parent->parent; + type = bl->norm->Bl.type; + + /* + * First calculate width and offset. This is pretty easy unless + * we're a -column list, in which case all prior columns must + * be accounted for. + */ + + width = offset = 0; + + if (bl->norm->Bl.offs) + offset = a2offs(p, bl->norm->Bl.offs); + + switch (type) { + case (LIST_column): + if (MDOC_HEAD == n->type) + break; + + /* + * Imitate groff's column handling: + * - For each earlier column, add its width. + * - For less than 5 columns, add four more blanks per + * column. + * - For exactly 5 columns, add three more blank per + * column. + * - For more than 5 columns, add only one column. + */ + ncols = bl->norm->Bl.ncols; + + /* LINTED */ + dcol = ncols < 5 ? term_len(p, 4) : + ncols == 5 ? term_len(p, 3) : term_len(p, 1); + + /* + * Calculate the offset by applying all prior MDOC_BODY, + * so we stop at the MDOC_HEAD (NULL == nn->prev). + */ + + for (i = 0, nn = n->prev; + nn->prev && i < (int)ncols; + nn = nn->prev, i++) + offset += dcol + a2width + (p, bl->norm->Bl.cols[i]); + + /* + * When exceeding the declared number of columns, leave + * the remaining widths at 0. This will later be + * adjusted to the default width of 10, or, for the last + * column, stretched to the right margin. + */ + if (i >= (int)ncols) + break; + + /* + * Use the declared column widths, extended as explained + * in the preceding paragraph. + */ + width = a2width(p, bl->norm->Bl.cols[i]) + dcol; + break; + default: + if (NULL == bl->norm->Bl.width) + break; + + /* + * Note: buffer the width by 2, which is groff's magic + * number for buffering single arguments. See the above + * handling for column for how this changes. + */ + assert(bl->norm->Bl.width); + width = a2width(p, bl->norm->Bl.width) + term_len(p, 2); + break; + } + + /* + * List-type can override the width in the case of fixed-head + * values (bullet, dash/hyphen, enum). Tags need a non-zero + * offset. + */ + + switch (type) { + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_hyphen): + if (width < term_len(p, 4)) + width = term_len(p, 4); + break; + case (LIST_enum): + if (width < term_len(p, 5)) + width = term_len(p, 5); + break; + case (LIST_hang): + if (0 == width) + width = term_len(p, 8); + break; + case (LIST_column): + /* FALLTHROUGH */ + case (LIST_tag): + if (0 == width) + width = term_len(p, 10); + break; + default: + break; + } + + /* + * Whitespace control. Inset bodies need an initial space, + * while diagonal bodies need two. + */ + + p->flags |= TERMP_NOSPACE; + + switch (type) { + case (LIST_diag): + if (MDOC_BODY == n->type) + term_word(p, "\\ \\ "); + break; + case (LIST_inset): + if (MDOC_BODY == n->type) + term_word(p, "\\ "); + break; + default: + break; + } + + p->flags |= TERMP_NOSPACE; + + switch (type) { + case (LIST_diag): + if (MDOC_HEAD == n->type) + term_fontpush(p, TERMFONT_BOLD); + break; + default: + break; + } + + /* + * Pad and break control. This is the tricky part. These flags + * are documented in term_flushln() in term.c. Note that we're + * going to unset all of these flags in termp_it_post() when we + * exit. + */ + + switch (type) { + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_enum): + /* FALLTHROUGH */ + case (LIST_hyphen): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + else + p->flags |= TERMP_NOLPAD; + break; + case (LIST_hang): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + else + p->flags |= TERMP_NOLPAD; + + if (MDOC_HEAD != n->type) + break; + + /* + * This is ugly. If `-hang' is specified and the body + * is a `Bl' or `Bd', then we want basically to nullify + * the "overstep" effect in term_flushln() and treat + * this as a `-ohang' list instead. + */ + if (n->next->child && + (MDOC_Bl == n->next->child->tok || + MDOC_Bd == n->next->child->tok)) { + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_NOLPAD; + } else + p->flags |= TERMP_HANG; + break; + case (LIST_tag): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE; + else + p->flags |= TERMP_NOLPAD; + + if (MDOC_HEAD != n->type) + break; + if (NULL == n->next || NULL == n->next->child) + p->flags |= TERMP_DANGLE; + break; + case (LIST_column): + if (MDOC_HEAD == n->type) + break; + + if (NULL == n->next) + p->flags &= ~TERMP_NOBREAK; + else + p->flags |= TERMP_NOBREAK; + + assert(n->prev); + if (MDOC_BODY == n->prev->type) + p->flags |= TERMP_NOLPAD; + + break; + case (LIST_diag): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + break; + default: + break; + } + + /* + * Margin control. Set-head-width lists have their right + * margins shortened. The body for these lists has the offset + * necessarily lengthened. Everybody gets the offset. + */ + + p->offset += offset; + + switch (type) { + case (LIST_hang): + /* + * Same stipulation as above, regarding `-hang'. We + * don't want to recalculate rmargin and offsets when + * using `Bd' or `Bl' within `-hang' overstep lists. + */ + if (MDOC_HEAD == n->type && n->next->child && + (MDOC_Bl == n->next->child->tok || + MDOC_Bd == n->next->child->tok)) + break; + /* FALLTHROUGH */ + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_enum): + /* FALLTHROUGH */ + case (LIST_hyphen): + /* FALLTHROUGH */ + case (LIST_tag): + assert(width); + if (MDOC_HEAD == n->type) + p->rmargin = p->offset + width; + else + p->offset += width; + break; + case (LIST_column): + assert(width); + p->rmargin = p->offset + width; + /* + * XXX - this behaviour is not documented: the + * right-most column is filled to the right margin. + */ + if (MDOC_HEAD == n->type) + break; + if (NULL == n->next && p->rmargin < p->maxrmargin) + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + /* + * The dash, hyphen, bullet and enum lists all have a special + * HEAD character (temporarily bold, in some cases). + */ + + if (MDOC_HEAD == n->type) + switch (type) { + case (LIST_bullet): + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "\\[bu]"); + term_fontpop(p); + break; + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_hyphen): + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "\\(hy"); + term_fontpop(p); + break; + case (LIST_enum): + (pair->ppair->ppair->count)++; + snprintf(buf, sizeof(buf), "%d.", + pair->ppair->ppair->count); + term_word(p, buf); + break; + default: + break; + } + + /* + * If we're not going to process our children, indicate so here. + */ + + switch (type) { + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_item): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_hyphen): + /* FALLTHROUGH */ + case (LIST_enum): + if (MDOC_HEAD == n->type) + return(0); + break; + case (LIST_column): + if (MDOC_HEAD == n->type) + return(0); + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_it_post(DECL_ARGS) +{ + enum mdoc_list type; + + if (MDOC_BLOCK == n->type) + return; + + type = n->parent->parent->parent->norm->Bl.type; + + switch (type) { + case (LIST_item): + /* FALLTHROUGH */ + case (LIST_diag): + /* FALLTHROUGH */ + case (LIST_inset): + if (MDOC_BODY == n->type) + term_newln(p); + break; + case (LIST_column): + if (MDOC_BODY == n->type) + term_flushln(p); + break; + default: + term_newln(p); + break; + } + + /* + * Now that our output is flushed, we can reset our tags. Since + * only `It' sets these flags, we're free to assume that nobody + * has munged them in the meanwhile. + */ + + p->flags &= ~TERMP_DANGLE; + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->flags &= ~TERMP_NOLPAD; + p->flags &= ~TERMP_HANG; +} + + +/* ARGSUSED */ +static int +termp_nm_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK == n->type) + return(1); + + if (MDOC_BODY == n->type) { + if (NULL == n->child) + return(0); + p->flags |= TERMP_NOLPAD | TERMP_NOSPACE; + p->offset += term_len(p, 1) + + (NULL == n->prev->child ? term_strlen(p, m->name) : + MDOC_TEXT == n->prev->child->type ? + term_strlen(p, n->prev->child->string) : + term_len(p, 5)); + return(1); + } + + if (NULL == n->child && NULL == m->name) + return(0); + + if (MDOC_HEAD == n->type) + synopsis_pre(p, n->parent); + + if (MDOC_HEAD == n->type && n->next->child) { + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + p->rmargin = p->offset + term_len(p, 1); + if (NULL == n->child) { + p->rmargin += term_strlen(p, m->name); + } else if (MDOC_TEXT == n->child->type) { + p->rmargin += term_strlen(p, n->child->string); + if (n->child->next) + p->flags |= TERMP_HANG; + } else { + p->rmargin += term_len(p, 5); + p->flags |= TERMP_HANG; + } + } + + term_fontpush(p, TERMFONT_BOLD); + if (NULL == n->child) + term_word(p, m->name); + return(1); +} + + +/* ARGSUSED */ +static void +termp_nm_post(DECL_ARGS) +{ + + if (MDOC_HEAD == n->type && n->next->child) { + term_flushln(p); + p->flags &= ~(TERMP_NOBREAK | TERMP_HANG); + } else if (MDOC_BODY == n->type && n->child) { + term_flushln(p); + p->flags &= ~TERMP_NOLPAD; + } +} + + +/* ARGSUSED */ +static int +termp_fl_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "\\-"); + + if (n->child) + p->flags |= TERMP_NOSPACE; + else if (n->next && n->next->line == n->line) + p->flags |= TERMP_NOSPACE; + + return(1); +} + + +/* ARGSUSED */ +static int +termp__a_pre(DECL_ARGS) +{ + + if (n->prev && MDOC__A == n->prev->tok) + if (NULL == n->next || MDOC__A != n->next->tok) + term_word(p, "and"); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_an_pre(DECL_ARGS) +{ + + if (NULL == n->child) + return(1); + + /* + * If not in the AUTHORS section, `An -split' will cause + * newlines to occur before the author name. If in the AUTHORS + * section, by default, the first `An' invocation is nosplit, + * then all subsequent ones, regardless of whether interspersed + * with other macros/text, are split. -split, in this case, + * will override the condition of the implied first -nosplit. + */ + + if (n->sec == SEC_AUTHORS) { + if ( ! (TERMP_ANPREC & p->flags)) { + if (TERMP_SPLIT & p->flags) + term_newln(p); + return(1); + } + if (TERMP_NOSPLIT & p->flags) + return(1); + term_newln(p); + return(1); + } + + if (TERMP_SPLIT & p->flags) + term_newln(p); + + return(1); +} + + +/* ARGSUSED */ +static void +termp_an_post(DECL_ARGS) +{ + + if (n->child) { + if (SEC_AUTHORS == n->sec) + p->flags |= TERMP_ANPREC; + return; + } + + if (AUTH_split == n->norm->An.auth) { + p->flags &= ~TERMP_NOSPLIT; + p->flags |= TERMP_SPLIT; + } else if (AUTH_nosplit == n->norm->An.auth) { + p->flags &= ~TERMP_SPLIT; + p->flags |= TERMP_NOSPLIT; + } + +} + + +/* ARGSUSED */ +static int +termp_ns_pre(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +termp_rs_pre(DECL_ARGS) +{ + + if (SEC_SEE_ALSO != n->sec) + return(1); + if (MDOC_BLOCK == n->type && n->prev) + term_vspace(p); + return(1); +} + + +/* ARGSUSED */ +static int +termp_rv_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + term_newln(p); + term_word(p, "The"); + + for (nn = n->child; nn; nn = nn->next) { + term_fontpush(p, TERMFONT_BOLD); + term_word(p, nn->string); + term_fontpop(p); + p->flags |= TERMP_NOSPACE; + if (nn->next && NULL == nn->next->next) + term_word(p, "(), and"); + else if (nn->next) + term_word(p, "(),"); + else + term_word(p, "()"); + } + + if (n->child && n->child->next) + term_word(p, "functions return"); + else + term_word(p, "function returns"); + + term_word(p, "the value 0 if successful; otherwise the value " + "-1 is returned and the global variable"); + + term_fontpush(p, TERMFONT_UNDER); + term_word(p, "errno"); + term_fontpop(p); + + term_word(p, "is set to indicate the error."); + p->flags |= TERMP_SENTENCE; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_ex_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + term_word(p, "The"); + + for (nn = n->child; nn; nn = nn->next) { + term_fontpush(p, TERMFONT_BOLD); + term_word(p, nn->string); + term_fontpop(p); + p->flags |= TERMP_NOSPACE; + if (nn->next && NULL == nn->next->next) + term_word(p, ", and"); + else if (nn->next) + term_word(p, ","); + else + p->flags &= ~TERMP_NOSPACE; + } + + if (n->child && n->child->next) + term_word(p, "utilities exit"); + else + term_word(p, "utility exits"); + + term_word(p, "0 on success, and >0 if an error occurs."); + p->flags |= TERMP_SENTENCE; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_nd_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + +#if defined(__OpenBSD__) || defined(__linux__) + term_word(p, "\\(en"); +#else + term_word(p, "\\(em"); +#endif + return(1); +} + + +/* ARGSUSED */ +static int +termp_bl_pre(DECL_ARGS) +{ + + return(MDOC_HEAD != n->type); +} + + +/* ARGSUSED */ +static void +termp_bl_post(DECL_ARGS) +{ + + if (MDOC_BLOCK == n->type) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_xr_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + if (NULL == n->child) + return(0); + + assert(MDOC_TEXT == n->child->type); + nn = n->child; + + term_word(p, nn->string); + if (NULL == (nn = nn->next)) + return(0); + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + term_word(p, nn->string); + term_word(p, ")"); + + return(0); +} + + +/* + * This decides how to assert whitespace before any of the SYNOPSIS set + * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain + * macro combos). + */ +static void +synopsis_pre(struct termp *p, const struct mdoc_node *n) +{ + /* + * Obviously, if we're not in a SYNOPSIS or no prior macros + * exist, do nothing. + */ + if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags)) + return; + + /* + * If we're the second in a pair of like elements, emit our + * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which + * case we soldier on. + */ + if (n->prev->tok == n->tok && + MDOC_Ft != n->tok && + MDOC_Fo != n->tok && + MDOC_Fn != n->tok) { + term_newln(p); + return; + } + + /* + * If we're one of the SYNOPSIS set and non-like pair-wise after + * another (or Fn/Fo, which we've let slip through) then assert + * vertical space, else only newline and move on. + */ + switch (n->prev->tok) { + case (MDOC_Fd): + /* FALLTHROUGH */ + case (MDOC_Fn): + /* FALLTHROUGH */ + case (MDOC_Fo): + /* FALLTHROUGH */ + case (MDOC_In): + /* FALLTHROUGH */ + case (MDOC_Vt): + term_vspace(p); + break; + case (MDOC_Ft): + if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { + term_vspace(p); + break; + } + /* FALLTHROUGH */ + default: + term_newln(p); + break; + } +} + + +static int +termp_vt_pre(DECL_ARGS) +{ + + if (MDOC_ELEM == n->type) { + synopsis_pre(p, n); + return(termp_under_pre(p, pair, m, n)); + } else if (MDOC_BLOCK == n->type) { + synopsis_pre(p, n); + return(1); + } else if (MDOC_HEAD == n->type) + return(0); + + return(termp_under_pre(p, pair, m, n)); +} + + +/* ARGSUSED */ +static int +termp_bold_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_BOLD); + return(1); +} + + +/* ARGSUSED */ +static int +termp_fd_pre(DECL_ARGS) +{ + + synopsis_pre(p, n); + return(termp_bold_pre(p, pair, m, n)); +} + + +/* ARGSUSED */ +static int +termp_sh_pre(DECL_ARGS) +{ + + /* No vspace between consecutive `Sh' calls. */ + + switch (n->type) { + case (MDOC_BLOCK): + if (n->prev && MDOC_Sh == n->prev->tok) + if (NULL == n->prev->body->child) + break; + term_vspace(p); + break; + case (MDOC_HEAD): + term_fontpush(p, TERMFONT_BOLD); + break; + case (MDOC_BODY): + p->offset = term_len(p, INDENT); + break; + default: + break; + } + return(1); +} + + +/* ARGSUSED */ +static void +termp_sh_post(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_HEAD): + term_newln(p); + break; + case (MDOC_BODY): + term_newln(p); + p->offset = 0; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +termp_bt_pre(DECL_ARGS) +{ + + term_word(p, "is currently in beta test."); + p->flags |= TERMP_SENTENCE; + return(0); +} + + +/* ARGSUSED */ +static void +termp_lb_post(DECL_ARGS) +{ + + if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_ud_pre(DECL_ARGS) +{ + + term_word(p, "currently under development."); + p->flags |= TERMP_SENTENCE; + return(0); +} + + +/* ARGSUSED */ +static int +termp_d1_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + term_newln(p); + p->offset += term_len(p, (INDENT + 1)); + return(1); +} + + +/* ARGSUSED */ +static void +termp_d1_post(DECL_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return; + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_ft_pre(DECL_ARGS) +{ + + /* NB: MDOC_LINE does not effect this! */ + synopsis_pre(p, n); + term_fontpush(p, TERMFONT_UNDER); + return(1); +} + + +/* ARGSUSED */ +static int +termp_fn_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + synopsis_pre(p, n); + + term_fontpush(p, TERMFONT_BOLD); + term_word(p, n->child->string); + term_fontpop(p); + + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + + for (nn = n->child->next; nn; nn = nn->next) { + term_fontpush(p, TERMFONT_UNDER); + term_word(p, nn->string); + term_fontpop(p); + + if (nn->next) + term_word(p, ","); + } + + term_word(p, ")"); + + if (MDOC_SYNPRETTY & n->flags) + term_word(p, ";"); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_fa_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + if (n->parent->tok != MDOC_Fo) { + term_fontpush(p, TERMFONT_UNDER); + return(1); + } + + for (nn = n->child; nn; nn = nn->next) { + term_fontpush(p, TERMFONT_UNDER); + term_word(p, nn->string); + term_fontpop(p); + + if (nn->next) + term_word(p, ","); + } + + if (n->child && n->next && n->next->tok == MDOC_Fa) + term_word(p, ","); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_bd_pre(DECL_ARGS) +{ + size_t tabwidth, rm, rmax; + const struct mdoc_node *nn; + + if (MDOC_BLOCK == n->type) { + print_bvspace(p, n, n); + return(1); + } else if (MDOC_HEAD == n->type) + return(0); + + if (n->norm->Bd.offs) + p->offset += a2offs(p, n->norm->Bd.offs); + + /* + * If -ragged or -filled are specified, the block does nothing + * but change the indentation. If -unfilled or -literal are + * specified, text is printed exactly as entered in the display: + * for macro lines, a newline is appended to the line. Blank + * lines are allowed. + */ + + if (DISP_literal != n->norm->Bd.type && + DISP_unfilled != n->norm->Bd.type) + return(1); + + tabwidth = p->tabwidth; + if (DISP_literal == n->norm->Bd.type) + p->tabwidth = term_len(p, 8); + + rm = p->rmargin; + rmax = p->maxrmargin; + p->rmargin = p->maxrmargin = TERM_MAXMARGIN; + + for (nn = n->child; nn; nn = nn->next) { + print_mdoc_node(p, pair, m, nn); + /* + * If the printed node flushes its own line, then we + * needn't do it here as well. This is hacky, but the + * notion of selective eoln whitespace is pretty dumb + * anyway, so don't sweat it. + */ + switch (nn->tok) { + case (MDOC_Sm): + /* FALLTHROUGH */ + case (MDOC_br): + /* FALLTHROUGH */ + case (MDOC_sp): + /* FALLTHROUGH */ + case (MDOC_Bl): + /* FALLTHROUGH */ + case (MDOC_D1): + /* FALLTHROUGH */ + case (MDOC_Dl): + /* FALLTHROUGH */ + case (MDOC_Lp): + /* FALLTHROUGH */ + case (MDOC_Pp): + continue; + default: + break; + } + if (nn->next && nn->next->line == nn->line) + continue; + term_flushln(p); + p->flags |= TERMP_NOSPACE; + } + + p->tabwidth = tabwidth; + p->rmargin = rm; + p->maxrmargin = rmax; + return(0); +} + + +/* ARGSUSED */ +static void +termp_bd_post(DECL_ARGS) +{ + size_t rm, rmax; + + if (MDOC_BODY != n->type) + return; + + rm = p->rmargin; + rmax = p->maxrmargin; + + if (DISP_literal == n->norm->Bd.type || + DISP_unfilled == n->norm->Bd.type) + p->rmargin = p->maxrmargin = TERM_MAXMARGIN; + + p->flags |= TERMP_NOSPACE; + term_newln(p); + + p->rmargin = rm; + p->maxrmargin = rmax; +} + + +/* ARGSUSED */ +static void +termp_bx_post(DECL_ARGS) +{ + + if (n->child) + p->flags |= TERMP_NOSPACE; + term_word(p, "BSD"); +} + + +/* ARGSUSED */ +static int +termp_xx_pre(DECL_ARGS) +{ + const char *pp; + + pp = NULL; + switch (n->tok) { + case (MDOC_Bsx): + pp = "BSD/OS"; + break; + case (MDOC_Dx): + pp = "DragonFly"; + break; + case (MDOC_Fx): + pp = "FreeBSD"; + break; + case (MDOC_Nx): + pp = "NetBSD"; + break; + case (MDOC_Ox): + pp = "OpenBSD"; + break; + case (MDOC_Ux): + pp = "UNIX"; + break; + default: + break; + } + + assert(pp); + term_word(p, pp); + return(1); +} + + +/* ARGSUSED */ +static int +termp_igndelim_pre(DECL_ARGS) +{ + + p->flags |= TERMP_IGNDELIM; + return(1); +} + + +/* ARGSUSED */ +static void +termp_pf_post(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; +} + + +/* ARGSUSED */ +static int +termp_ss_pre(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_BLOCK): + term_newln(p); + if (n->prev) + term_vspace(p); + break; + case (MDOC_HEAD): + term_fontpush(p, TERMFONT_BOLD); + p->offset = term_len(p, HALFINDENT); + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_ss_post(DECL_ARGS) +{ + + if (MDOC_HEAD == n->type) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_cd_pre(DECL_ARGS) +{ + + synopsis_pre(p, n); + term_fontpush(p, TERMFONT_BOLD); + return(1); +} + + +/* ARGSUSED */ +static int +termp_in_pre(DECL_ARGS) +{ + + synopsis_pre(p, n); + + if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) { + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "#include"); + term_word(p, "<"); + } else { + term_word(p, "<"); + term_fontpush(p, TERMFONT_UNDER); + } + + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_in_post(DECL_ARGS) +{ + + if (MDOC_SYNPRETTY & n->flags) + term_fontpush(p, TERMFONT_BOLD); + + p->flags |= TERMP_NOSPACE; + term_word(p, ">"); + + if (MDOC_SYNPRETTY & n->flags) + term_fontpop(p); +} + + +/* ARGSUSED */ +static int +termp_sp_pre(DECL_ARGS) +{ + size_t i, len; + + switch (n->tok) { + case (MDOC_sp): + len = n->child ? a2height(p, n->child->string) : 1; + break; + case (MDOC_br): + len = 0; + break; + default: + len = 1; + break; + } + + if (0 == len) + term_newln(p); + for (i = 0; i < len; i++) + term_vspace(p); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_quote_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type && MDOC_ELEM != n->type) + return(1); + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + term_word(p, "<"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + term_word(p, "{"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + /* FALLTHROUGH */ + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + term_word(p, "["); + break; + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + term_word(p, "``"); + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + term_word(p, "("); + break; + case (MDOC__T): + /* FALLTHROUGH */ + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + term_word(p, "\""); + break; + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + term_word(p, "`"); + break; + default: + abort(); + /* NOTREACHED */ + } + + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_quote_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type && MDOC_ELEM != n->type) + return; + + p->flags |= TERMP_NOSPACE; + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + term_word(p, ">"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + term_word(p, "}"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + /* FALLTHROUGH */ + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + term_word(p, "]"); + break; + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + term_word(p, "''"); + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + term_word(p, ")"); + break; + case (MDOC__T): + /* FALLTHROUGH */ + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + term_word(p, "\""); + break; + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + term_word(p, "'"); + break; + default: + abort(); + /* NOTREACHED */ + } +} + + +/* ARGSUSED */ +static int +termp_fo_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK == n->type) { + synopsis_pre(p, n); + return(1); + } else if (MDOC_BODY == n->type) { + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + return(1); + } + + if (NULL == n->child) + return(0); + + /* XXX: we drop non-initial arguments as per groff. */ + + assert(n->child->string); + term_fontpush(p, TERMFONT_BOLD); + term_word(p, n->child->string); + return(0); +} + + +/* ARGSUSED */ +static void +termp_fo_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + + term_word(p, ")"); + + if (MDOC_SYNPRETTY & n->flags) + term_word(p, ";"); +} + + +/* ARGSUSED */ +static int +termp_bf_pre(DECL_ARGS) +{ + + if (MDOC_HEAD == n->type) + return(0); + else if (MDOC_BLOCK != n->type) + return(1); + + if (FONT_Em == n->norm->Bf.font) + term_fontpush(p, TERMFONT_UNDER); + else if (FONT_Sy == n->norm->Bf.font) + term_fontpush(p, TERMFONT_BOLD); + else + term_fontpush(p, TERMFONT_NONE); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_sm_pre(DECL_ARGS) +{ + + assert(n->child && MDOC_TEXT == n->child->type); + if (0 == strcmp("on", n->child->string)) { + if (p->col) + p->flags &= ~TERMP_NOSPACE; + p->flags &= ~TERMP_NONOSPACE; + } else + p->flags |= TERMP_NONOSPACE; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_ap_pre(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + term_word(p, "'"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp____post(DECL_ARGS) +{ + + /* + * Handle lists of authors. In general, print each followed by + * a comma. Don't print the comma if there are only two + * authors. + */ + if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok) + if (NULL == n->next->next || MDOC__A != n->next->next->tok) + if (NULL == n->prev || MDOC__A != n->prev->tok) + return; + + /* TODO: %U. */ + + if (NULL == n->parent || MDOC_Rs != n->parent->tok) + return; + + if (NULL == n->next) { + term_word(p, "."); + p->flags |= TERMP_SENTENCE; + } else + term_word(p, ","); +} + + +/* ARGSUSED */ +static int +termp_li_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_NONE); + return(1); +} + + +/* ARGSUSED */ +static int +termp_lk_pre(DECL_ARGS) +{ + const struct mdoc_node *nn, *sv; + + term_fontpush(p, TERMFONT_UNDER); + + nn = sv = n->child; + + if (NULL == nn || NULL == nn->next) + return(1); + + for (nn = nn->next; nn; nn = nn->next) + term_word(p, nn->string); + + term_fontpop(p); + + term_word(p, ":"); + + term_fontpush(p, TERMFONT_BOLD); + term_word(p, sv->string); + term_fontpop(p); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_bk_pre(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_BLOCK): + break; + case (MDOC_HEAD): + return(0); + case (MDOC_BODY): + if (n->parent->args || 0 == n->prev->nchild) + p->flags |= TERMP_PREKEEP; + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_bk_post(DECL_ARGS) +{ + + if (MDOC_BODY == n->type) + p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); +} + +/* ARGSUSED */ +static void +termp__t_post(DECL_ARGS) +{ + + /* + * If we're in an `Rs' and there's a journal present, then quote + * us instead of underlining us (for disambiguation). + */ + if (n->parent && MDOC_Rs == n->parent->tok && + n->parent->norm->Rs.child_J) + termp_quote_post(p, pair, m, n); + + termp____post(p, pair, m, n); +} + +/* ARGSUSED */ +static int +termp__t_pre(DECL_ARGS) +{ + + /* + * If we're in an `Rs' and there's a journal present, then quote + * us instead of underlining us (for disambiguation). + */ + if (n->parent && MDOC_Rs == n->parent->tok && + n->parent->norm->Rs.child_J) + return(termp_quote_pre(p, pair, m, n)); + + term_fontpush(p, TERMFONT_UNDER); + return(1); +} + +/* ARGSUSED */ +static int +termp_under_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_UNDER); + return(1); +} diff --git a/contrib/mdocml/mdoc_validate.c b/contrib/mdocml/mdoc_validate.c new file mode 100644 index 0000000000..0146219967 --- /dev/null +++ b/contrib/mdocml/mdoc_validate.c @@ -0,0 +1,2239 @@ +/* $Id: mdoc_validate.c,v 1.151 2011/01/03 23:53:51 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons + * Copyright (c) 2010, 2011 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef OSNAME +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +/* FIXME: .Bl -diag can't have non-text children in HEAD. */ + +#define PRE_ARGS struct mdoc *mdoc, struct mdoc_node *n +#define POST_ARGS struct mdoc *mdoc + +#define NUMSIZ 32 +#define DATESIZE 32 + +enum check_ineq { + CHECK_LT, + CHECK_GT, + CHECK_EQ +}; + +enum check_lvl { + CHECK_WARN, + CHECK_ERROR, +}; + +typedef int (*v_pre)(PRE_ARGS); +typedef int (*v_post)(POST_ARGS); + +struct valids { + v_pre *pre; + v_post *post; +}; + +static int check_count(struct mdoc *, enum mdoc_type, + enum check_lvl, enum check_ineq, int); +static int check_parent(PRE_ARGS, enum mdoct, enum mdoc_type); +static void check_text(struct mdoc *, int, int, char *); +static void check_argv(struct mdoc *, + struct mdoc_node *, struct mdoc_argv *); +static void check_args(struct mdoc *, struct mdoc_node *); + +static int concat(struct mdoc *, char *, + const struct mdoc_node *, size_t); + +static int ebool(POST_ARGS); +static int berr_ge1(POST_ARGS); +static int bwarn_ge1(POST_ARGS); +static int eerr_ge1(POST_ARGS); +static int ewarn_eq0(POST_ARGS); +static int ewarn_eq1(POST_ARGS); +static int ewarn_ge1(POST_ARGS); +static int ewarn_le1(POST_ARGS); +static int hwarn_eq0(POST_ARGS); +static int hwarn_eq1(POST_ARGS); +static int hwarn_ge1(POST_ARGS); +static int hwarn_le1(POST_ARGS); + +static int post_an(POST_ARGS); +static int post_at(POST_ARGS); +static int post_bf(POST_ARGS); +static int post_bl(POST_ARGS); +static int post_bl_block(POST_ARGS); +static int post_bl_block_width(POST_ARGS); +static int post_bl_block_tag(POST_ARGS); +static int post_bl_head(POST_ARGS); +static int post_dd(POST_ARGS); +static int post_dt(POST_ARGS); +static int post_defaults(POST_ARGS); +static int post_literal(POST_ARGS); +static int post_eoln(POST_ARGS); +static int post_it(POST_ARGS); +static int post_lb(POST_ARGS); +static int post_nm(POST_ARGS); +static int post_os(POST_ARGS); +static int post_ignpar(POST_ARGS); +static int post_prol(POST_ARGS); +static int post_root(POST_ARGS); +static int post_rs(POST_ARGS); +static int post_sh(POST_ARGS); +static int post_sh_body(POST_ARGS); +static int post_sh_head(POST_ARGS); +static int post_st(POST_ARGS); +static int post_std(POST_ARGS); +static int post_vt(POST_ARGS); +static int pre_an(PRE_ARGS); +static int pre_bd(PRE_ARGS); +static int pre_bl(PRE_ARGS); +static int pre_dd(PRE_ARGS); +static int pre_display(PRE_ARGS); +static int pre_dt(PRE_ARGS); +static int pre_it(PRE_ARGS); +static int pre_literal(PRE_ARGS); +static int pre_os(PRE_ARGS); +static int pre_par(PRE_ARGS); +static int pre_sh(PRE_ARGS); +static int pre_ss(PRE_ARGS); +static int pre_std(PRE_ARGS); + +static v_post posts_an[] = { post_an, NULL }; +static v_post posts_at[] = { post_at, post_defaults, NULL }; +static v_post posts_bd[] = { post_literal, hwarn_eq0, bwarn_ge1, NULL }; +static v_post posts_bf[] = { hwarn_le1, post_bf, NULL }; +static v_post posts_bk[] = { hwarn_eq0, bwarn_ge1, NULL }; +static v_post posts_bl[] = { bwarn_ge1, post_bl, NULL }; +static v_post posts_bool[] = { ebool, NULL }; +static v_post posts_eoln[] = { post_eoln, NULL }; +static v_post posts_defaults[] = { post_defaults, NULL }; +static v_post posts_dd[] = { ewarn_ge1, post_dd, post_prol, NULL }; +static v_post posts_dl[] = { post_literal, bwarn_ge1, NULL }; +static v_post posts_dt[] = { post_dt, post_prol, NULL }; +static v_post posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL }; +static v_post posts_it[] = { post_it, NULL }; +static v_post posts_lb[] = { post_lb, NULL }; +static v_post posts_nd[] = { berr_ge1, NULL }; +static v_post posts_nm[] = { post_nm, NULL }; +static v_post posts_notext[] = { ewarn_eq0, NULL }; +static v_post posts_os[] = { post_os, post_prol, NULL }; +static v_post posts_rs[] = { post_rs, NULL }; +static v_post posts_sh[] = { post_ignpar, hwarn_ge1, bwarn_ge1, post_sh, NULL }; +static v_post posts_sp[] = { ewarn_le1, NULL }; +static v_post posts_ss[] = { post_ignpar, hwarn_ge1, bwarn_ge1, NULL }; +static v_post posts_st[] = { post_st, NULL }; +static v_post posts_std[] = { post_std, NULL }; +static v_post posts_text[] = { eerr_ge1, NULL }; +static v_post posts_text1[] = { ewarn_eq1, NULL }; +static v_post posts_vt[] = { post_vt, NULL }; +static v_post posts_wline[] = { bwarn_ge1, NULL }; +static v_post posts_wtext[] = { ewarn_ge1, NULL }; +static v_pre pres_an[] = { pre_an, NULL }; +static v_pre pres_bd[] = { pre_display, pre_bd, pre_literal, pre_par, NULL }; +static v_pre pres_bl[] = { pre_bl, pre_par, NULL }; +static v_pre pres_d1[] = { pre_display, NULL }; +static v_pre pres_dl[] = { pre_literal, pre_display, NULL }; +static v_pre pres_dd[] = { pre_dd, NULL }; +static v_pre pres_dt[] = { pre_dt, NULL }; +static v_pre pres_er[] = { NULL, NULL }; +static v_pre pres_fd[] = { NULL, NULL }; +static v_pre pres_it[] = { pre_it, pre_par, NULL }; +static v_pre pres_os[] = { pre_os, NULL }; +static v_pre pres_pp[] = { pre_par, NULL }; +static v_pre pres_sh[] = { pre_sh, NULL }; +static v_pre pres_ss[] = { pre_ss, NULL }; +static v_pre pres_std[] = { pre_std, NULL }; + +const struct valids mdoc_valids[MDOC_MAX] = { + { NULL, NULL }, /* Ap */ + { pres_dd, posts_dd }, /* Dd */ + { pres_dt, posts_dt }, /* Dt */ + { pres_os, posts_os }, /* Os */ + { pres_sh, posts_sh }, /* Sh */ + { pres_ss, posts_ss }, /* Ss */ + { pres_pp, posts_notext }, /* Pp */ + { pres_d1, posts_wline }, /* D1 */ + { pres_dl, posts_dl }, /* Dl */ + { pres_bd, posts_bd }, /* Bd */ + { NULL, NULL }, /* Ed */ + { pres_bl, posts_bl }, /* Bl */ + { NULL, NULL }, /* El */ + { pres_it, posts_it }, /* It */ + { NULL, posts_text }, /* Ad */ + { pres_an, posts_an }, /* An */ + { NULL, posts_defaults }, /* Ar */ + { NULL, posts_text }, /* Cd */ + { NULL, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { pres_er, posts_text }, /* Er */ + { NULL, NULL }, /* Ev */ + { pres_std, posts_std }, /* Ex */ + { NULL, NULL }, /* Fa */ + { pres_fd, posts_wtext }, /* Fd */ + { NULL, NULL }, /* Fl */ + { NULL, posts_text }, /* Fn */ + { NULL, posts_wtext }, /* Ft */ + { NULL, posts_text }, /* Ic */ + { NULL, posts_text1 }, /* In */ + { NULL, posts_defaults }, /* Li */ + { NULL, posts_nd }, /* Nd */ + { NULL, posts_nm }, /* Nm */ + { NULL, NULL }, /* Op */ + { NULL, NULL }, /* Ot */ + { NULL, posts_defaults }, /* Pa */ + { pres_std, posts_std }, /* Rv */ + { NULL, posts_st }, /* St */ + { NULL, NULL }, /* Va */ + { NULL, posts_vt }, /* Vt */ + { NULL, posts_wtext }, /* Xr */ + { NULL, posts_text }, /* %A */ + { NULL, posts_text }, /* %B */ /* FIXME: can be used outside Rs/Re. */ + { NULL, posts_text }, /* %D */ /* FIXME: check date with mandoc_a2time(). */ + { NULL, posts_text }, /* %I */ + { NULL, posts_text }, /* %J */ + { NULL, posts_text }, /* %N */ + { NULL, posts_text }, /* %O */ + { NULL, posts_text }, /* %P */ + { NULL, posts_text }, /* %R */ + { NULL, posts_text }, /* %T */ /* FIXME: can be used outside Rs/Re. */ + { NULL, posts_text }, /* %V */ + { NULL, NULL }, /* Ac */ + { NULL, NULL }, /* Ao */ + { NULL, NULL }, /* Aq */ + { NULL, posts_at }, /* At */ + { NULL, NULL }, /* Bc */ + { NULL, posts_bf }, /* Bf */ + { NULL, NULL }, /* Bo */ + { NULL, NULL }, /* Bq */ + { NULL, NULL }, /* Bsx */ + { NULL, NULL }, /* Bx */ + { NULL, posts_bool }, /* Db */ + { NULL, NULL }, /* Dc */ + { NULL, NULL }, /* Do */ + { NULL, NULL }, /* Dq */ + { NULL, NULL }, /* Ec */ + { NULL, NULL }, /* Ef */ + { NULL, NULL }, /* Em */ + { NULL, NULL }, /* Eo */ + { NULL, NULL }, /* Fx */ + { NULL, posts_text }, /* Ms */ + { NULL, posts_notext }, /* No */ + { NULL, posts_notext }, /* Ns */ + { NULL, NULL }, /* Nx */ + { NULL, NULL }, /* Ox */ + { NULL, NULL }, /* Pc */ + { NULL, posts_text1 }, /* Pf */ + { NULL, NULL }, /* Po */ + { NULL, NULL }, /* Pq */ + { NULL, NULL }, /* Qc */ + { NULL, NULL }, /* Ql */ + { NULL, NULL }, /* Qo */ + { NULL, NULL }, /* Qq */ + { NULL, NULL }, /* Re */ + { NULL, posts_rs }, /* Rs */ + { NULL, NULL }, /* Sc */ + { NULL, NULL }, /* So */ + { NULL, NULL }, /* Sq */ + { NULL, posts_bool }, /* Sm */ + { NULL, posts_text }, /* Sx */ + { NULL, posts_text }, /* Sy */ + { NULL, posts_text }, /* Tn */ + { NULL, NULL }, /* Ux */ + { NULL, NULL }, /* Xc */ + { NULL, NULL }, /* Xo */ + { NULL, posts_fo }, /* Fo */ + { NULL, NULL }, /* Fc */ + { NULL, NULL }, /* Oo */ + { NULL, NULL }, /* Oc */ + { NULL, posts_bk }, /* Bk */ + { NULL, NULL }, /* Ek */ + { NULL, posts_eoln }, /* Bt */ + { NULL, NULL }, /* Hf */ + { NULL, NULL }, /* Fr */ + { NULL, posts_eoln }, /* Ud */ + { NULL, posts_lb }, /* Lb */ + { NULL, posts_notext }, /* Lp */ + { NULL, posts_text }, /* Lk */ + { NULL, posts_defaults }, /* Mt */ + { NULL, NULL }, /* Brq */ + { NULL, NULL }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, posts_text }, /* %C */ + { NULL, NULL }, /* Es */ + { NULL, NULL }, /* En */ + { NULL, NULL }, /* Dx */ + { NULL, posts_text }, /* %Q */ + { NULL, posts_notext }, /* br */ + { pres_pp, posts_sp }, /* sp */ + { NULL, posts_text1 }, /* %U */ + { NULL, NULL }, /* Ta */ +}; + +#define RSORD_MAX 14 /* Number of `Rs' blocks. */ + +static const enum mdoct rsord[RSORD_MAX] = { + MDOC__A, + MDOC__T, + MDOC__B, + MDOC__I, + MDOC__J, + MDOC__R, + MDOC__N, + MDOC__V, + MDOC__P, + MDOC__Q, + MDOC__D, + MDOC__O, + MDOC__C, + MDOC__U +}; + + +int +mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n) +{ + v_pre *p; + int line, pos; + char *tp; + + switch (n->type) { + case (MDOC_TEXT): + tp = n->string; + line = n->line; + pos = n->pos; + check_text(mdoc, line, pos, tp); + /* FALLTHROUGH */ + case (MDOC_TBL): + /* FALLTHROUGH */ + case (MDOC_ROOT): + return(1); + default: + break; + } + + check_args(mdoc, n); + + if (NULL == mdoc_valids[n->tok].pre) + return(1); + for (p = mdoc_valids[n->tok].pre; *p; p++) + if ( ! (*p)(mdoc, n)) + return(0); + return(1); +} + + +int +mdoc_valid_post(struct mdoc *mdoc) +{ + v_post *p; + + if (MDOC_VALID & mdoc->last->flags) + return(1); + mdoc->last->flags |= MDOC_VALID; + + switch (mdoc->last->type) { + case (MDOC_TEXT): + /* FALLTHROUGH */ + case (MDOC_TBL): + return(1); + case (MDOC_ROOT): + return(post_root(mdoc)); + default: + break; + } + + if (NULL == mdoc_valids[mdoc->last->tok].post) + return(1); + for (p = mdoc_valids[mdoc->last->tok].post; *p; p++) + if ( ! (*p)(mdoc)) + return(0); + + return(1); +} + +static int +check_count(struct mdoc *m, enum mdoc_type type, + enum check_lvl lvl, enum check_ineq ineq, int val) +{ + const char *p; + enum mandocerr t; + + if (m->last->type != type) + return(1); + + switch (ineq) { + case (CHECK_LT): + p = "less than "; + if (m->last->nchild < val) + return(1); + break; + case (CHECK_GT): + p = "more than "; + if (m->last->nchild > val) + return(1); + break; + case (CHECK_EQ): + p = ""; + if (val == m->last->nchild) + return(1); + break; + default: + abort(); + /* NOTREACHED */ + } + + t = lvl == CHECK_WARN ? MANDOCERR_ARGCWARN : MANDOCERR_ARGCOUNT; + + return(mdoc_vmsg(m, t, m->last->line, m->last->pos, + "want %s%d children (have %d)", + p, val, m->last->nchild)); +} + +static int +berr_ge1(POST_ARGS) +{ + + return(check_count(mdoc, MDOC_BODY, CHECK_ERROR, CHECK_GT, 0)); +} + +static int +bwarn_ge1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0)); +} + +static int +eerr_ge1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_ERROR, CHECK_GT, 0)); +} + +static int +ewarn_eq0(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0)); +} + +static int +ewarn_eq1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1)); +} + +static int +ewarn_ge1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0)); +} + +static int +ewarn_le1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2)); +} + +static int +hwarn_eq0(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0)); +} + +static int +hwarn_eq1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 1)); +} + +static int +hwarn_ge1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_GT, 0)); +} + +static int +hwarn_le1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_LT, 2)); +} + +static void +check_args(struct mdoc *m, struct mdoc_node *n) +{ + int i; + + if (NULL == n->args) + return; + + assert(n->args->argc); + for (i = 0; i < (int)n->args->argc; i++) + check_argv(m, n, &n->args->argv[i]); +} + +static void +check_argv(struct mdoc *m, struct mdoc_node *n, struct mdoc_argv *v) +{ + int i; + + for (i = 0; i < (int)v->sz; i++) + check_text(m, v->line, v->pos, v->value[i]); + + /* FIXME: move to post_std(). */ + + if (MDOC_Std == v->arg) + if ( ! (v->sz || m->meta.name)) + mdoc_nmsg(m, n, MANDOCERR_NONAME); +} + +static void +check_text(struct mdoc *m, int ln, int pos, char *p) +{ + int c; + size_t sz; + + for ( ; *p; p++, pos++) { + sz = strcspn(p, "\t\\"); + p += (int)sz; + + if ('\0' == *p) + break; + + pos += (int)sz; + + if ('\t' == *p) { + if ( ! (MDOC_LITERAL & m->flags)) + mdoc_pmsg(m, ln, pos, MANDOCERR_BADTAB); + continue; + } + + if (0 == (c = mandoc_special(p))) { + mdoc_pmsg(m, ln, pos, MANDOCERR_BADESCAPE); + continue; + } + + p += c - 1; + pos += c - 1; + } +} + +static int +check_parent(PRE_ARGS, enum mdoct tok, enum mdoc_type t) +{ + + assert(n->parent); + if ((MDOC_ROOT == t || tok == n->parent->tok) && + (t == n->parent->type)) + return(1); + + mdoc_vmsg(mdoc, MANDOCERR_SYNTCHILD, + n->line, n->pos, "want parent %s", + MDOC_ROOT == t ? "" : + mdoc_macronames[tok]); + return(0); +} + + +static int +pre_display(PRE_ARGS) +{ + struct mdoc_node *node; + + if (MDOC_BLOCK != n->type) + return(1); + + for (node = mdoc->last->parent; node; node = node->parent) + if (MDOC_BLOCK == node->type) + if (MDOC_Bd == node->tok) + break; + + if (node) + mdoc_nmsg(mdoc, n, MANDOCERR_NESTEDDISP); + + return(1); +} + + +static int +pre_bl(PRE_ARGS) +{ + int i, comp, dup; + const char *offs, *width; + enum mdoc_list lt; + struct mdoc_node *np; + + if (MDOC_BLOCK != n->type) { + if (ENDBODY_NOT != n->end) { + assert(n->pending); + np = n->pending->parent; + } else + np = n->parent; + + assert(np); + assert(MDOC_BLOCK == np->type); + assert(MDOC_Bl == np->tok); + return(1); + } + + /* + * First figure out which kind of list to use: bind ourselves to + * the first mentioned list type and warn about any remaining + * ones. If we find no list type, we default to LIST_item. + */ + + /* LINTED */ + for (i = 0; n->args && i < (int)n->args->argc; i++) { + lt = LIST__NONE; + dup = comp = 0; + width = offs = NULL; + switch (n->args->argv[i].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): + dup = n->norm->Bl.comp; + comp = 1; + break; + case (MDOC_Width): + dup = (NULL != n->norm->Bl.width); + width = n->args->argv[i].value[0]; + break; + case (MDOC_Offset): + /* NB: this can be empty! */ + if (n->args->argv[i].sz) { + offs = n->args->argv[i].value[0]; + dup = (NULL != n->norm->Bl.offs); + break; + } + mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV); + break; + default: + continue; + } + + /* Check: duplicate auxiliary arguments. */ + + if (dup) + mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP); + + if (comp && ! dup) + n->norm->Bl.comp = comp; + if (offs && ! dup) + n->norm->Bl.offs = offs; + if (width && ! dup) + n->norm->Bl.width = width; + + /* Check: multiple list types. */ + + if (LIST__NONE != lt && n->norm->Bl.type != LIST__NONE) + mdoc_nmsg(mdoc, n, MANDOCERR_LISTREP); + + /* Assign list type. */ + + if (LIST__NONE != lt && n->norm->Bl.type == LIST__NONE) { + n->norm->Bl.type = lt; + /* Set column information, too. */ + if (LIST_column == lt) { + n->norm->Bl.ncols = + n->args->argv[i].sz; + n->norm->Bl.cols = (const char **) + n->args->argv[i].value; + } + } + + /* The list type should come first. */ + + if (n->norm->Bl.type == LIST__NONE) + if (n->norm->Bl.width || + n->norm->Bl.offs || + n->norm->Bl.comp) + mdoc_nmsg(mdoc, n, MANDOCERR_LISTFIRST); + + continue; + } + + /* Allow lists to default to LIST_item. */ + + if (LIST__NONE == n->norm->Bl.type) { + mdoc_nmsg(mdoc, n, MANDOCERR_LISTTYPE); + n->norm->Bl.type = LIST_item; + } + + /* + * Validate the width field. Some list types don't need width + * types and should be warned about them. Others should have it + * and must also be warned. + */ + + switch (n->norm->Bl.type) { + case (LIST_tag): + if (n->norm->Bl.width) + break; + mdoc_nmsg(mdoc, n, MANDOCERR_NOWIDTHARG); + break; + case (LIST_column): + /* FALLTHROUGH */ + case (LIST_diag): + /* FALLTHROUGH */ + case (LIST_ohang): + /* FALLTHROUGH */ + case (LIST_inset): + /* FALLTHROUGH */ + case (LIST_item): + if (n->norm->Bl.width) + mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV); + break; + default: + break; + } + + return(1); +} + + +static int +pre_bd(PRE_ARGS) +{ + int i, dup, comp; + enum mdoc_disp dt; + const char *offs; + struct mdoc_node *np; + + if (MDOC_BLOCK != n->type) { + if (ENDBODY_NOT != n->end) { + assert(n->pending); + np = n->pending->parent; + } else + np = n->parent; + + assert(np); + assert(MDOC_BLOCK == np->type); + assert(MDOC_Bd == np->tok); + return(1); + } + + /* LINTED */ + for (i = 0; n->args && i < (int)n->args->argc; i++) { + dt = DISP__NONE; + dup = comp = 0; + offs = NULL; + + switch (n->args->argv[i].arg) { + case (MDOC_Centred): + dt = DISP_centred; + 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): + mdoc_nmsg(mdoc, n, MANDOCERR_BADDISP); + return(0); + case (MDOC_Offset): + /* NB: this can be empty! */ + if (n->args->argv[i].sz) { + offs = n->args->argv[i].value[0]; + dup = (NULL != n->norm->Bd.offs); + break; + } + mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV); + break; + case (MDOC_Compact): + comp = 1; + dup = n->norm->Bd.comp; + break; + default: + abort(); + /* NOTREACHED */ + } + + /* Check whether we have duplicates. */ + + if (dup) + mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP); + + /* Make our auxiliary assignments. */ + + if (offs && ! dup) + n->norm->Bd.offs = offs; + if (comp && ! dup) + n->norm->Bd.comp = comp; + + /* Check whether a type has already been assigned. */ + + if (DISP__NONE != dt && n->norm->Bd.type != DISP__NONE) + mdoc_nmsg(mdoc, n, MANDOCERR_DISPREP); + + /* Make our type assignment. */ + + if (DISP__NONE != dt && n->norm->Bd.type == DISP__NONE) + n->norm->Bd.type = dt; + } + + if (DISP__NONE == n->norm->Bd.type) { + mdoc_nmsg(mdoc, n, MANDOCERR_DISPTYPE); + n->norm->Bd.type = DISP_ragged; + } + + return(1); +} + + +static int +pre_ss(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY)); +} + + +static int +pre_sh(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + + mdoc->regs->regs[(int)REG_nS].set = 0; + return(check_parent(mdoc, n, MDOC_MAX, MDOC_ROOT)); +} + + +static int +pre_it(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + + return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY)); +} + + +static int +pre_an(PRE_ARGS) +{ + int i; + + if (NULL == n->args) + return(1); + + for (i = 1; i < (int)n->args->argc; i++) + mdoc_pmsg(mdoc, n->args->argv[i].line, + n->args->argv[i].pos, MANDOCERR_IGNARGV); + + if (MDOC_Split == n->args->argv[0].arg) + n->norm->An.auth = AUTH_split; + else if (MDOC_Nosplit == n->args->argv[0].arg) + n->norm->An.auth = AUTH_nosplit; + else + abort(); + + return(1); +} + +static int +pre_std(PRE_ARGS) +{ + + if (n->args && 1 == n->args->argc) + if (MDOC_Std == n->args->argv[0].arg) + return(1); + + mdoc_nmsg(mdoc, n, MANDOCERR_NOARGV); + return(1); +} + +static int +pre_dt(PRE_ARGS) +{ + + if (0 == mdoc->meta.date || mdoc->meta.os) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO); + + if (mdoc->meta.title) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP); + + return(1); +} + +static int +pre_os(PRE_ARGS) +{ + + if (NULL == mdoc->meta.title || 0 == mdoc->meta.date) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO); + + if (mdoc->meta.os) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP); + + return(1); +} + +static int +pre_dd(PRE_ARGS) +{ + + if (mdoc->meta.title || mdoc->meta.os) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO); + + if (mdoc->meta.date) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP); + + return(1); +} + + +static int +post_bf(POST_ARGS) +{ + struct mdoc_node *np; + enum mdocargt arg; + + /* + * Unlike other data pointers, these are "housed" by the HEAD + * element, which contains the goods. + */ + + if (MDOC_HEAD != mdoc->last->type) { + if (ENDBODY_NOT != mdoc->last->end) { + assert(mdoc->last->pending); + np = mdoc->last->pending->parent->head; + } else if (MDOC_BLOCK != mdoc->last->type) { + np = mdoc->last->parent->head; + } else + np = mdoc->last->head; + + assert(np); + assert(MDOC_HEAD == np->type); + assert(MDOC_Bf == np->tok); + return(1); + } + + np = mdoc->last; + assert(MDOC_BLOCK == np->parent->type); + assert(MDOC_Bf == np->parent->tok); + + /* + * Cannot have both argument and parameter. + * If neither is specified, let it through with a warning. + */ + + if (np->parent->args && np->child) { + mdoc_nmsg(mdoc, np, MANDOCERR_SYNTARGVCOUNT); + return(0); + } else if (NULL == np->parent->args && NULL == np->child) { + mdoc_nmsg(mdoc, np, MANDOCERR_FONTTYPE); + return(1); + } + + /* Extract argument into data. */ + + if (np->parent->args) { + arg = np->parent->args->argv[0].arg; + if (MDOC_Emphasis == arg) + np->norm->Bf.font = FONT_Em; + else if (MDOC_Literal == arg) + np->norm->Bf.font = FONT_Li; + else if (MDOC_Symbolic == arg) + np->norm->Bf.font = FONT_Sy; + else + abort(); + return(1); + } + + /* Extract parameter into data. */ + + if (0 == strcmp(np->child->string, "Em")) + np->norm->Bf.font = FONT_Em; + else if (0 == strcmp(np->child->string, "Li")) + np->norm->Bf.font = FONT_Li; + else if (0 == strcmp(np->child->string, "Sy")) + np->norm->Bf.font = FONT_Sy; + else + mdoc_nmsg(mdoc, np, MANDOCERR_FONTTYPE); + + return(1); +} + +static int +post_lb(POST_ARGS) +{ + const char *p; + char *buf; + size_t sz; + + check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1); + + assert(mdoc->last->child); + assert(MDOC_TEXT == mdoc->last->child->type); + + p = mdoc_a2lib(mdoc->last->child->string); + + /* If lookup ok, replace with table value. */ + + if (p) { + free(mdoc->last->child->string); + mdoc->last->child->string = mandoc_strdup(p); + return(1); + } + + /* If not, use "library ``xxxx''. */ + + sz = strlen(mdoc->last->child->string) + + 2 + strlen("\\(lqlibrary\\(rq"); + buf = mandoc_malloc(sz); + snprintf(buf, sz, "library \\(lq%s\\(rq", + mdoc->last->child->string); + free(mdoc->last->child->string); + mdoc->last->child->string = buf; + return(1); +} + +static int +post_eoln(POST_ARGS) +{ + + if (mdoc->last->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST); + return(1); +} + + +static int +post_vt(POST_ARGS) +{ + const struct mdoc_node *n; + + /* + * The Vt macro comes in both ELEM and BLOCK form, both of which + * have different syntaxes (yet more context-sensitive + * behaviour). ELEM types must have a child; BLOCK types, + * specifically the BODY, should only have TEXT children. + */ + + if (MDOC_ELEM == mdoc->last->type) + return(eerr_ge1(mdoc)); + if (MDOC_BODY != mdoc->last->type) + return(1); + + for (n = mdoc->last->child; n; n = n->next) + if (MDOC_TEXT != n->type) + mdoc_nmsg(mdoc, n, MANDOCERR_CHILD); + + return(1); +} + + +static int +post_nm(POST_ARGS) +{ + char buf[BUFSIZ]; + + /* If no child specified, make sure we have the meta name. */ + + if (NULL == mdoc->last->child && NULL == mdoc->meta.name) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME); + return(1); + } else if (mdoc->meta.name) + return(1); + + /* If no meta name, set it from the child. */ + + if ( ! concat(mdoc, buf, mdoc->last->child, BUFSIZ)) + return(0); + + mdoc->meta.name = mandoc_strdup(buf); + + return(1); +} + +static int +post_literal(POST_ARGS) +{ + + /* + * The `Dl' (note "el" not "one") and `Bd' macros unset the + * MDOC_LITERAL flag as they leave. Note that `Bd' only sets + * this in literal mode, but it doesn't hurt to just switch it + * off in general since displays can't be nested. + */ + + if (MDOC_BODY == mdoc->last->type) + mdoc->flags &= ~MDOC_LITERAL; + + return(1); +} + +static int +post_defaults(POST_ARGS) +{ + struct mdoc_node *nn; + + /* + * The `Ar' defaults to "file ..." if no value is provided as an + * argument; the `Mt' and `Pa' macros use "~"; the `Li' just + * gets an empty string. + */ + + if (mdoc->last->child) + return(1); + + nn = mdoc->last; + mdoc->next = MDOC_NEXT_CHILD; + + switch (nn->tok) { + case (MDOC_Ar): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "file")) + return(0); + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "...")) + return(0); + break; + case (MDOC_At): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "AT&T")) + return(0); + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "UNIX")) + return(0); + break; + case (MDOC_Li): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "")) + return(0); + break; + case (MDOC_Pa): + /* FALLTHROUGH */ + case (MDOC_Mt): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "~")) + return(0); + break; + default: + abort(); + /* NOTREACHED */ + } + + mdoc->last = nn; + return(1); +} + +static int +post_at(POST_ARGS) +{ + const char *p, *q; + char *buf; + size_t sz; + + /* + * 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. + */ + + if (NULL == mdoc->last->child) + return(1); + + assert(MDOC_TEXT == mdoc->last->child->type); + p = mdoc_a2att(mdoc->last->child->string); + + if (p) { + free(mdoc->last->child->string); + mdoc->last->child->string = mandoc_strdup(p); + } else { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADATT); + p = "AT&T UNIX "; + q = mdoc->last->child->string; + sz = strlen(p) + strlen(q) + 1; + buf = mandoc_malloc(sz); + strlcpy(buf, p, sz); + strlcat(buf, q, sz); + free(mdoc->last->child->string); + mdoc->last->child->string = buf; + } + + return(1); +} + +static int +post_an(POST_ARGS) +{ + struct mdoc_node *np; + + np = mdoc->last; + if (AUTH__NONE != np->norm->An.auth && np->child) { + check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0); + return(1); + } + + /* + * FIXME: make this ewarn and make sure that the front-ends + * don't print the arguments. + */ + if (AUTH__NONE != np->norm->An.auth || np->child) + return(1); + + mdoc_nmsg(mdoc, np, MANDOCERR_NOARGS); + return(1); +} + + +static int +post_it(POST_ARGS) +{ + int i, cols, rc; + enum mdoc_list lt; + struct mdoc_node *n, *c; + enum mandocerr er; + + if (MDOC_BLOCK != mdoc->last->type) + return(1); + + n = mdoc->last->parent->parent; + lt = n->norm->Bl.type; + + if (LIST__NONE == lt) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_LISTTYPE); + return(1); + } + + switch (lt) { + case (LIST_tag): + if (mdoc->last->head->child) + break; + /* FIXME: give this a dummy value. */ + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS); + break; + case (LIST_hang): + /* FALLTHROUGH */ + case (LIST_ohang): + /* FALLTHROUGH */ + case (LIST_inset): + /* FALLTHROUGH */ + case (LIST_diag): + if (NULL == mdoc->last->head->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS); + break; + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_enum): + /* FALLTHROUGH */ + case (LIST_hyphen): + if (NULL == mdoc->last->body->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY); + /* FALLTHROUGH */ + case (LIST_item): + if (mdoc->last->head->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST); + break; + case (LIST_column): + cols = (int)n->norm->Bl.ncols; + + assert(NULL == mdoc->last->head->child); + + if (NULL == mdoc->last->body->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY); + + for (i = 0, c = mdoc->last->child; c; c = c->next) + if (MDOC_BODY == c->type) + i++; + + if (i < cols) + er = MANDOCERR_ARGCOUNT; + else if (i == cols || i == cols + 1) + break; + else + er = MANDOCERR_SYNTARGCOUNT; + + rc = mdoc_vmsg(mdoc, er, + mdoc->last->line, mdoc->last->pos, + "columns == %d (have %d)", cols, i); + return(rc); + default: + break; + } + + return(1); +} + +static int +post_bl_block(POST_ARGS) +{ + struct mdoc_node *n; + + /* + * These are fairly complicated, so we've broken them into two + * functions. post_bl_block_tag() is called when a -tag is + * specified, but no -width (it must be guessed). The second + * when a -width is specified (macro indicators must be + * rewritten into real lengths). + */ + + n = mdoc->last; + + if (LIST_tag == n->norm->Bl.type && + NULL == n->norm->Bl.width) { + if ( ! post_bl_block_tag(mdoc)) + return(0); + } else if (NULL != n->norm->Bl.width) { + if ( ! post_bl_block_width(mdoc)) + return(0); + } else + return(1); + + assert(n->norm->Bl.width); + return(1); +} + +static int +post_bl_block_width(POST_ARGS) +{ + size_t width; + int i; + enum mdoct tok; + struct mdoc_node *n; + char buf[NUMSIZ]; + + n = mdoc->last; + + /* + * Calculate the real width of a list from the -width string, + * which may contain a macro (with a known default width), a + * literal string, or a scaling width. + * + * If the value to -width is a macro, then we re-write it to be + * the macro's width as set in share/tmac/mdoc/doc-common. + */ + + if (0 == strcmp(n->norm->Bl.width, "Ds")) + width = 6; + else if (MDOC_MAX == (tok = mdoc_hash_find(n->norm->Bl.width))) + return(1); + else if (0 == (width = mdoc_macro2len(tok))) { + mdoc_nmsg(mdoc, n, MANDOCERR_BADWIDTH); + return(1); + } + + /* The value already exists: free and reallocate it. */ + + assert(n->args); + + for (i = 0; i < (int)n->args->argc; i++) + if (MDOC_Width == n->args->argv[i].arg) + break; + + assert(i < (int)n->args->argc); + + snprintf(buf, NUMSIZ, "%zun", width); + free(n->args->argv[i].value[0]); + n->args->argv[i].value[0] = mandoc_strdup(buf); + + /* Set our width! */ + n->norm->Bl.width = n->args->argv[i].value[0]; + return(1); +} + +static int +post_bl_block_tag(POST_ARGS) +{ + struct mdoc_node *n, *nn; + size_t sz, ssz; + int i; + char buf[NUMSIZ]; + + /* + * Calculate the -width for a `Bl -tag' list if it hasn't been + * provided. Uses the first head macro. NOTE AGAIN: this is + * ONLY if the -width argument has NOT been provided. See + * post_bl_block_width() for converting the -width string. + */ + + sz = 10; + n = mdoc->last; + + for (nn = n->body->child; nn; nn = nn->next) { + if (MDOC_It != nn->tok) + continue; + + assert(MDOC_BLOCK == nn->type); + nn = nn->head->child; + + if (nn == NULL) + break; + + if (MDOC_TEXT == nn->type) { + sz = strlen(nn->string) + 1; + break; + } + + if (0 != (ssz = mdoc_macro2len(nn->tok))) + sz = ssz; + + break; + } + + /* Defaults to ten ens. */ + + snprintf(buf, NUMSIZ, "%zun", sz); + + /* + * We have to dynamically add this to the macro's argument list. + * We're guaranteed that a MDOC_Width doesn't already exist. + */ + + assert(n->args); + i = (int)(n->args->argc)++; + + n->args->argv = mandoc_realloc(n->args->argv, + n->args->argc * sizeof(struct mdoc_argv)); + + n->args->argv[i].arg = MDOC_Width; + n->args->argv[i].line = n->line; + n->args->argv[i].pos = n->pos; + n->args->argv[i].sz = 1; + n->args->argv[i].value = mandoc_malloc(sizeof(char *)); + n->args->argv[i].value[0] = mandoc_strdup(buf); + + /* Set our width! */ + n->norm->Bl.width = n->args->argv[i].value[0]; + return(1); +} + + +static int +post_bl_head(POST_ARGS) +{ + struct mdoc_node *np, *nn, *nnp; + int i, j; + + if (LIST_column != mdoc->last->norm->Bl.type) + /* FIXME: this should be ERROR class... */ + return(hwarn_eq0(mdoc)); + + /* + * Convert 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. + */ + + /* First, disallow both types and allow normal-form. */ + + /* + * TODO: technically, we can accept both and just merge the two + * lists, but I'll leave that for another day. + */ + + if (mdoc->last->norm->Bl.ncols && mdoc->last->nchild) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_COLUMNS); + return(0); + } else if (NULL == mdoc->last->child) + return(1); + + np = mdoc->last->parent; + assert(np->args); + + for (j = 0; j < (int)np->args->argc; j++) + if (MDOC_Column == np->args->argv[j].arg) + break; + + assert(j < (int)np->args->argc); + assert(0 == np->args->argv[j].sz); + + /* + * Accomodate 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. + */ + + np->args->argv[j].sz = (size_t)mdoc->last->nchild; + np->args->argv[j].value = mandoc_malloc + ((size_t)mdoc->last->nchild * sizeof(char *)); + + mdoc->last->norm->Bl.ncols = np->args->argv[j].sz; + mdoc->last->norm->Bl.cols = (const char **)np->args->argv[j].value; + + for (i = 0, nn = mdoc->last->child; nn; i++) { + np->args->argv[j].value[i] = nn->string; + nn->string = NULL; + nnp = nn; + nn = nn->next; + mdoc_node_delete(NULL, nnp); + } + + mdoc->last->nchild = 0; + mdoc->last->child = NULL; + + return(1); +} + +static int +post_bl(POST_ARGS) +{ + struct mdoc_node *n; + + if (MDOC_HEAD == mdoc->last->type) + return(post_bl_head(mdoc)); + if (MDOC_BLOCK == mdoc->last->type) + return(post_bl_block(mdoc)); + if (MDOC_BODY != mdoc->last->type) + return(1); + + for (n = mdoc->last->child; n; n = n->next) { + switch (n->tok) { + case (MDOC_Lp): + /* FALLTHROUGH */ + case (MDOC_Pp): + mdoc_nmsg(mdoc, n, MANDOCERR_CHILD); + /* FALLTHROUGH */ + case (MDOC_It): + /* FALLTHROUGH */ + case (MDOC_Sm): + continue; + default: + break; + } + + mdoc_nmsg(mdoc, n, MANDOCERR_SYNTCHILD); + return(0); + } + + return(1); +} + +static int +ebool(struct mdoc *mdoc) +{ + + if (NULL == mdoc->last->child) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY); + mdoc_node_delete(mdoc, mdoc->last); + return(1); + } + check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1); + + assert(MDOC_TEXT == mdoc->last->child->type); + + if (0 == strcmp(mdoc->last->child->string, "on")) + return(1); + if (0 == strcmp(mdoc->last->child->string, "off")) + return(1); + + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADBOOL); + return(1); +} + +static int +post_root(POST_ARGS) +{ + int erc; + struct mdoc_node *n; + + erc = 0; + + /* Check that we have a finished prologue. */ + + if ( ! (MDOC_PBODY & mdoc->flags)) { + erc++; + mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCPROLOG); + } + + n = mdoc->first; + assert(n); + + /* Check that we begin with a proper `Sh'. */ + + if (NULL == n->child) { + erc++; + mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY); + } else if (MDOC_BLOCK != n->child->type || + MDOC_Sh != n->child->tok) { + erc++; + /* Can this be lifted? See rxdebug.1 for example. */ + mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY); + } + + return(erc ? 0 : 1); +} + +static int +post_st(POST_ARGS) +{ + struct mdoc_node *ch; + const char *p; + + if (NULL == (ch = mdoc->last->child)) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY); + mdoc_node_delete(mdoc, mdoc->last); + return(1); + } + + assert(MDOC_TEXT == ch->type); + + if (NULL == (p = mdoc_a2st(ch->string))) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADSTANDARD); + mdoc_node_delete(mdoc, mdoc->last); + } else { + free(ch->string); + ch->string = mandoc_strdup(p); + } + + return(1); +} + +static int +post_rs(POST_ARGS) +{ + struct mdoc_node *nn, *next, *prev; + int i, j; + + switch (mdoc->last->type) { + case (MDOC_HEAD): + check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0); + return(1); + case (MDOC_BODY): + if (mdoc->last->child) + break; + check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0); + return(1); + default: + return(1); + } + + /* + * Make sure only certain types of nodes are allowed within the + * the `Rs' body. Delete offending nodes and raise a warning. + * Do this before re-ordering for the sake of clarity. + */ + + next = NULL; + for (nn = mdoc->last->child; nn; nn = next) { + for (i = 0; i < RSORD_MAX; i++) + if (nn->tok == rsord[i]) + break; + + if (i < RSORD_MAX) { + if (MDOC__J == rsord[i]) + mdoc->last->norm->Rs.child_J = nn; + next = nn->next; + continue; + } + + next = nn->next; + mdoc_nmsg(mdoc, nn, MANDOCERR_CHILD); + mdoc_node_delete(mdoc, nn); + } + + /* + * 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 a insertion sort. + */ + + next = NULL; + for (nn = mdoc->last->child->next; nn; nn = next) { + /* Determine order of `nn'. */ + for (i = 0; i < RSORD_MAX; i++) + if (rsord[i] == nn->tok) + break; + + /* + * Remove `nn' from the chain. This somewhat + * repeats mdoc_node_unlink(), but since we're + * just re-ordering, there's no need for the + * full unlink process. + */ + + if (NULL != (next = nn->next)) + next->prev = nn->prev; + + if (NULL != (prev = nn->prev)) + prev->next = nn->next; + + nn->prev = nn->next = NULL; + + /* + * Scan back until we reach a node that's + * ordered before `nn'. + */ + + for ( ; prev ; prev = prev->prev) { + /* Determine order of `prev'. */ + for (j = 0; j < RSORD_MAX; j++) + if (rsord[j] == prev->tok) + break; + + if (j <= i) + break; + } + + /* + * Set `nn' back into its correct place in front + * of the `prev' node. + */ + + nn->prev = prev; + + if (prev) { + if (prev->next) + prev->next->prev = nn; + nn->next = prev->next; + prev->next = nn; + } else { + mdoc->last->child->prev = nn; + nn->next = mdoc->last->child; + mdoc->last->child = nn; + } + } + + return(1); +} + +static int +post_sh(POST_ARGS) +{ + + if (MDOC_HEAD == mdoc->last->type) + return(post_sh_head(mdoc)); + if (MDOC_BODY == mdoc->last->type) + return(post_sh_body(mdoc)); + + return(1); +} + +static int +post_sh_body(POST_ARGS) +{ + struct mdoc_node *n; + + if (SEC_NAME != mdoc->lastsec) + return(1); + + /* + * Warn if the NAME section doesn't contain the `Nm' and `Nd' + * macros (can have multiple `Nm' and one `Nd'). Note that the + * children of the BODY declaration can also be "text". + */ + + if (NULL == (n = mdoc->last->child)) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC); + return(1); + } + + for ( ; n && n->next; n = n->next) { + if (MDOC_ELEM == n->type && MDOC_Nm == n->tok) + continue; + if (MDOC_TEXT == n->type) + continue; + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC); + } + + assert(n); + if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok) + return(1); + + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC); + return(1); +} + +static int +post_sh_head(POST_ARGS) +{ + char buf[BUFSIZ]; + enum mdoc_sec sec; + + /* + * Process a new section. Sections are either "named" or + * "custom". Custom sections are user-defined, while named ones + * follow a conventional order and may only appear in certain + * manual sections. + */ + + if ( ! concat(mdoc, buf, mdoc->last->child, BUFSIZ)) + return(0); + + sec = mdoc_str2sec(buf); + + /* The NAME should be first. */ + + if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST); + + /* The SYNOPSIS gets special attention in other areas. */ + + if (SEC_SYNOPSIS == sec) + mdoc->flags |= MDOC_SYNOPSIS; + else + mdoc->flags &= ~MDOC_SYNOPSIS; + + /* Mark our last section. */ + + mdoc->lastsec = sec; + + /* We don't care about custom sections after this. */ + + if (SEC_CUSTOM == sec) + return(1); + + /* + * Check whether our non-custom section is being repeated or is + * out of order. + */ + + if (sec == mdoc->lastnamed) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP); + + if (sec < mdoc->lastnamed) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO); + + /* Mark the last named section. */ + + mdoc->lastnamed = sec; + + /* Check particular section/manual conventions. */ + + assert(mdoc->meta.msec); + + switch (sec) { + case (SEC_RETURN_VALUES): + /* FALLTHROUGH */ + case (SEC_ERRORS): + /* FALLTHROUGH */ + case (SEC_LIBRARY): + if (*mdoc->meta.msec == '2') + break; + if (*mdoc->meta.msec == '3') + break; + if (*mdoc->meta.msec == '9') + break; + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECMSEC); + break; + default: + break; + } + + return(1); +} + +static int +post_ignpar(POST_ARGS) +{ + struct mdoc_node *np; + + if (MDOC_BODY != mdoc->last->type) + return(1); + + if (NULL != (np = mdoc->last->child)) + if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) { + mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR); + mdoc_node_delete(mdoc, np); + } + + if (NULL != (np = mdoc->last->last)) + if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) { + mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR); + mdoc_node_delete(mdoc, np); + } + + return(1); +} + +static int +pre_par(PRE_ARGS) +{ + + if (NULL == mdoc->last) + return(1); + if (MDOC_ELEM != n->type && MDOC_BLOCK != n->type) + return(1); + + /* + * Don't allow prior `Lp' or `Pp' prior to a paragraph-type + * block: `Lp', `Pp', or non-compact `Bd' or `Bl'. + */ + + if (MDOC_Pp != mdoc->last->tok && MDOC_Lp != mdoc->last->tok) + return(1); + if (MDOC_Bl == n->tok && n->norm->Bl.comp) + return(1); + if (MDOC_Bd == n->tok && n->norm->Bd.comp) + return(1); + if (MDOC_It == n->tok && n->parent->norm->Bl.comp) + return(1); + + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR); + mdoc_node_delete(mdoc, mdoc->last); + return(1); +} + +static int +pre_literal(PRE_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + + /* + * The `Dl' (note "el" not "one") and `Bd -literal' and `Bd + * -unfilled' macros set MDOC_LITERAL on entrance to the body. + */ + + switch (n->tok) { + case (MDOC_Dl): + mdoc->flags |= MDOC_LITERAL; + break; + case (MDOC_Bd): + if (DISP_literal == n->norm->Bd.type) + mdoc->flags |= MDOC_LITERAL; + if (DISP_unfilled == n->norm->Bd.type) + mdoc->flags |= MDOC_LITERAL; + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + +static int +post_dd(POST_ARGS) +{ + char buf[DATESIZE]; + struct mdoc_node *n; + + n = mdoc->last; + + if (NULL == n->child) { + mdoc->meta.date = time(NULL); + return(1); + } + + if ( ! concat(mdoc, buf, n->child, DATESIZE)) + return(0); + + mdoc->meta.date = mandoc_a2time + (MTIME_MDOCDATE | MTIME_CANONICAL, buf); + + if (0 == mdoc->meta.date) { + mdoc_nmsg(mdoc, n, MANDOCERR_BADDATE); + mdoc->meta.date = time(NULL); + } + + return(1); +} + +static int +post_dt(POST_ARGS) +{ + struct mdoc_node *nn, *n; + const char *cp; + char *p; + + n = mdoc->last; + + if (mdoc->meta.title) + free(mdoc->meta.title); + if (mdoc->meta.vol) + free(mdoc->meta.vol); + if (mdoc->meta.arch) + free(mdoc->meta.arch); + + mdoc->meta.title = mdoc->meta.vol = mdoc->meta.arch = NULL; + + /* First make all characters uppercase. */ + + if (NULL != (nn = n->child)) + for (p = nn->string; *p; p++) { + if (toupper((u_char)*p) == *p) + continue; + + /* + * FIXME: don't be lazy: have this make all + * characters be uppercase and just warn once. + */ + mdoc_nmsg(mdoc, nn, MANDOCERR_UPPERCASE); + break; + } + + /* Handles: `.Dt' + * --> title = unknown, volume = local, msec = 0, arch = NULL + */ + + if (NULL == (nn = n->child)) { + /* XXX: make these macro values. */ + /* FIXME: warn about missing values. */ + mdoc->meta.title = mandoc_strdup("UNKNOWN"); + mdoc->meta.vol = mandoc_strdup("LOCAL"); + mdoc->meta.msec = mandoc_strdup("1"); + return(1); + } + + /* Handles: `.Dt TITLE' + * --> title = TITLE, volume = local, msec = 0, arch = NULL + */ + + mdoc->meta.title = mandoc_strdup + ('\0' == nn->string[0] ? "UNKNOWN" : nn->string); + + if (NULL == (nn = nn->next)) { + /* FIXME: warn about missing msec. */ + /* XXX: make this a macro value. */ + mdoc->meta.vol = mandoc_strdup("LOCAL"); + mdoc->meta.msec = mandoc_strdup("1"); + return(1); + } + + /* Handles: `.Dt TITLE SEC' + * --> title = TITLE, volume = SEC is msec ? + * format(msec) : SEC, + * msec = SEC is msec ? atoi(msec) : 0, + * arch = NULL + */ + + cp = mdoc_a2msec(nn->string); + if (cp) { + mdoc->meta.vol = mandoc_strdup(cp); + mdoc->meta.msec = mandoc_strdup(nn->string); + } else { + mdoc_nmsg(mdoc, n, MANDOCERR_BADMSEC); + mdoc->meta.vol = mandoc_strdup(nn->string); + mdoc->meta.msec = mandoc_strdup(nn->string); + } + + if (NULL == (nn = nn->next)) + return(1); + + /* Handles: `.Dt TITLE SEC VOL' + * --> title = TITLE, volume = VOL is vol ? + * format(VOL) : + * VOL is arch ? format(arch) : + * VOL + */ + + cp = mdoc_a2vol(nn->string); + if (cp) { + free(mdoc->meta.vol); + mdoc->meta.vol = mandoc_strdup(cp); + } else { + /* FIXME: warn about bad arch. */ + cp = mdoc_a2arch(nn->string); + if (NULL == cp) { + free(mdoc->meta.vol); + mdoc->meta.vol = mandoc_strdup(nn->string); + } else + mdoc->meta.arch = mandoc_strdup(cp); + } + + /* Ignore any subsequent parameters... */ + /* FIXME: warn about subsequent parameters. */ + + return(1); +} + +static int +post_prol(POST_ARGS) +{ + /* + * Remove prologue macros from the document after they're + * processed. The final document uses mdoc_meta for these + * values and discards the originals. + */ + + mdoc_node_delete(mdoc, mdoc->last); + if (mdoc->meta.title && mdoc->meta.date && mdoc->meta.os) + mdoc->flags |= MDOC_PBODY; + + return(1); +} + +static int +post_os(POST_ARGS) +{ + struct mdoc_node *n; + char buf[BUFSIZ]; +#ifndef OSNAME + struct utsname utsname; +#endif + + n = mdoc->last; + + /* + * Set the operating system by way of the `Os' macro. Note that + * if an argument isn't provided and -DOSNAME="\"foo\"" is + * provided during compilation, this value will be used instead + * of filling in "sysname release" from uname(). + */ + + if (mdoc->meta.os) + free(mdoc->meta.os); + + if ( ! concat(mdoc, buf, n->child, BUFSIZ)) + return(0); + + /* XXX: yes, these can all be dynamically-adjusted buffers, but + * it's really not worth the extra hackery. + */ + + if ('\0' == buf[0]) { +#ifdef OSNAME + if (strlcat(buf, OSNAME, BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } +#else /*!OSNAME */ + if (uname(&utsname)) { + mdoc_nmsg(mdoc, n, MANDOCERR_UNAME); + mdoc->meta.os = mandoc_strdup("UNKNOWN"); + return(post_prol(mdoc)); + } + + if (strlcat(buf, utsname.sysname, BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } + if (strlcat(buf, " ", BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } + if (strlcat(buf, utsname.release, BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } +#endif /*!OSNAME*/ + } + + mdoc->meta.os = mandoc_strdup(buf); + return(1); +} + +static int +post_std(POST_ARGS) +{ + struct mdoc_node *nn, *n; + + n = mdoc->last; + + /* + * Macros accepting `-std' as an argument have the name of the + * current document (`Nm') filled in as the argument if it's not + * provided. + */ + + if (n->child) + return(1); + + if (NULL == mdoc->meta.name) + return(1); + + nn = n; + mdoc->next = MDOC_NEXT_CHILD; + + if ( ! mdoc_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name)) + return(0); + + mdoc->last = nn; + return(1); +} + +static int +concat(struct mdoc *m, char *p, const struct mdoc_node *n, size_t sz) +{ + + p[0] = '\0'; + + /* + * Concatenate sibling nodes together. All siblings must be of + * type MDOC_TEXT or an assertion is raised. Concatenation is + * separated by a single whitespace. Returns 0 on fatal (string + * overrun) error. + */ + + for ( ; n; n = n->next) { + assert(MDOC_TEXT == n->type); + + if (strlcat(p, n->string, sz) >= sz) { + mdoc_nmsg(m, n, MANDOCERR_MEM); + return(0); + } + + if (NULL == n->next) + continue; + + if (strlcat(p, " ", sz) >= sz) { + mdoc_nmsg(m, n, MANDOCERR_MEM); + return(0); + } + } + + return(1); +} + diff --git a/contrib/mdocml/msec.c b/contrib/mdocml/msec.c new file mode 100644 index 0000000000..ba5e8d7839 --- /dev/null +++ b/contrib/mdocml/msec.c @@ -0,0 +1,37 @@ +/* $Id: msec.c,v 1.8 2010/05/17 22:11:42 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2msec(const char *p) +{ + +#include "msec.in" + + return(NULL); +} diff --git a/contrib/mdocml/msec.in b/contrib/mdocml/msec.in new file mode 100644 index 0000000000..f3aebb46a1 --- /dev/null +++ b/contrib/mdocml/msec.in @@ -0,0 +1,40 @@ +/* $Id: msec.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * These are all possible manual-section macros and what they correspond + * to when rendered as the volume title. + * + * Be sure to escape strings. + */ + +LINE("1", "General Commands Manual") +LINE("2", "System Calls Manual") +LINE("3", "Library Functions Manual") +LINE("3p", "Perl Library Functions Manual") +LINE("4", "Kernel Interfaces Manual") +LINE("5", "File Formats Manual") +LINE("6", "Games Manual") +LINE("7", "Miscellaneous Information Manual") +LINE("8", "System Manager\'s Manual") +LINE("9", "Kernel Developer\'s Manual") +LINE("X11", "X11 Developer\'s Manual") +LINE("X11R6", "X11 Developer\'s Manual") +LINE("unass", "Unassociated") +LINE("local", "Local") +LINE("draft", "Draft") +LINE("paper", "Paper") diff --git a/contrib/mdocml/out.c b/contrib/mdocml/out.c new file mode 100644 index 0000000000..d0629e4325 --- /dev/null +++ b/contrib/mdocml/out.c @@ -0,0 +1,562 @@ +/* $Id: out.c,v 1.30 2011/01/05 15:37:23 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" + +static void tblcalc_data(struct rofftbl *, struct roffcol *, + const struct tbl *, const struct tbl_dat *); +static void tblcalc_literal(struct rofftbl *, struct roffcol *, + const struct tbl_dat *); +static void tblcalc_number(struct rofftbl *, struct roffcol *, + const struct tbl *, const struct tbl_dat *); + +/* + * Convert a `scaling unit' to a consistent form, or fail. Scaling + * units are documented in groff.7, mdoc.7, man.7. + */ +int +a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) +{ + char buf[BUFSIZ], hasd; + int i; + enum roffscale unit; + + if ('\0' == *src) + return(0); + + i = hasd = 0; + + switch (*src) { + case ('+'): + src++; + break; + case ('-'): + buf[i++] = *src++; + break; + default: + break; + } + + if ('\0' == *src) + return(0); + + while (i < BUFSIZ) { + if ( ! isdigit((u_char)*src)) { + if ('.' != *src) + break; + else if (hasd) + break; + else + hasd = 1; + } + buf[i++] = *src++; + } + + if (BUFSIZ == i || (*src && *(src + 1))) + return(0); + + buf[i] = '\0'; + + switch (*src) { + case ('c'): + unit = SCALE_CM; + break; + case ('i'): + unit = SCALE_IN; + break; + case ('P'): + unit = SCALE_PC; + break; + case ('p'): + unit = SCALE_PT; + break; + case ('f'): + unit = SCALE_FS; + break; + case ('v'): + unit = SCALE_VS; + break; + case ('m'): + unit = SCALE_EM; + break; + case ('\0'): + if (SCALE_MAX == def) + return(0); + unit = SCALE_BU; + break; + case ('u'): + unit = SCALE_BU; + break; + case ('M'): + unit = SCALE_MM; + break; + case ('n'): + unit = SCALE_EN; + break; + default: + return(0); + } + + /* FIXME: do this in the caller. */ + if ((dst->scale = atof(buf)) < 0) + dst->scale = 0; + dst->unit = unit; + return(1); +} + + +/* + * Correctly writes the time in nroff form, which differs from standard + * form in that a space isn't printed in lieu of the extra %e field for + * single-digit dates. + */ +void +time2a(time_t t, char *dst, size_t sz) +{ + struct tm tm; + char buf[5]; + char *p; + size_t nsz; + + assert(sz > 1); + localtime_r(&t, &tm); + + p = dst; + nsz = 0; + + dst[0] = '\0'; + + if (0 == (nsz = strftime(p, sz, "%B ", &tm))) + return; + + p += (int)nsz; + sz -= nsz; + + if (0 == strftime(buf, sizeof(buf), "%e, ", &tm)) + return; + + nsz = strlcat(p, buf + (' ' == buf[0] ? 1 : 0), sz); + + if (nsz >= sz) + return; + + p += (int)nsz; + sz -= nsz; + + (void)strftime(p, sz, "%Y", &tm); +} + + +int +a2roffdeco(enum roffdeco *d, const char **word, size_t *sz) +{ + int i, j, lim; + char term, c; + const char *wp; + enum roffdeco dd; + + *d = DECO_NONE; + lim = i = 0; + term = '\0'; + wp = *word; + + switch ((c = wp[i++])) { + case ('('): + *d = DECO_SPECIAL; + lim = 2; + break; + case ('F'): + /* FALLTHROUGH */ + case ('f'): + *d = 'F' == c ? DECO_FFONT : DECO_FONT; + + switch (wp[i++]) { + case ('('): + lim = 2; + break; + case ('['): + term = ']'; + break; + case ('3'): + /* FALLTHROUGH */ + case ('B'): + *d = DECO_BOLD; + return(i); + case ('2'): + /* FALLTHROUGH */ + case ('I'): + *d = DECO_ITALIC; + return(i); + case ('P'): + *d = DECO_PREVIOUS; + return(i); + case ('1'): + /* FALLTHROUGH */ + case ('R'): + *d = DECO_ROMAN; + return(i); + default: + i--; + lim = 1; + break; + } + break; + case ('k'): + /* FALLTHROUGH */ + case ('M'): + /* FALLTHROUGH */ + case ('m'): + /* FALLTHROUGH */ + case ('*'): + if ('*' == c) + *d = DECO_RESERVED; + + switch (wp[i++]) { + case ('('): + lim = 2; + break; + case ('['): + term = ']'; + break; + default: + i--; + lim = 1; + break; + } + break; + case ('h'): + /* FALLTHROUGH */ + case ('v'): + /* FALLTHROUGH */ + case ('s'): + j = 0; + if ('+' == wp[i] || '-' == wp[i]) { + i++; + j = 1; + } + + switch (wp[i++]) { + case ('('): + lim = 2; + break; + case ('['): + term = ']'; + break; + case ('\''): + term = '\''; + break; + case ('0'): + j = 1; + /* FALLTHROUGH */ + default: + i--; + lim = 1; + break; + } + + if ('+' == wp[i] || '-' == wp[i]) { + if (j) + return(i); + i++; + } + + /* Handle embedded numerical subexp or escape. */ + + if ('(' == wp[i]) { + while (wp[i] && ')' != wp[i]) + if ('\\' == wp[i++]) { + /* Handle embedded escape. */ + *word = &wp[i]; + i += a2roffdeco(&dd, word, sz); + } + + if (')' == wp[i++]) + break; + + *d = DECO_NONE; + return(i - 1); + } else if ('\\' == wp[i]) { + *word = &wp[++i]; + i += a2roffdeco(&dd, word, sz); + } + + break; + case ('['): + *d = DECO_SPECIAL; + term = ']'; + break; + case ('c'): + *d = DECO_NOSPACE; + return(i); + case ('z'): + *d = DECO_NONE; + if ('\\' == wp[i]) { + *word = &wp[++i]; + return(i + a2roffdeco(&dd, word, sz)); + } else + lim = 1; + break; + case ('o'): + /* FALLTHROUGH */ + case ('w'): + if ('\'' == wp[i++]) { + term = '\''; + break; + } + /* FALLTHROUGH */ + default: + *d = DECO_SSPECIAL; + i--; + lim = 1; + break; + } + + assert(term || lim); + *word = &wp[i]; + + if (term) { + j = i; + while (wp[i] && wp[i] != term) + i++; + if ('\0' == wp[i]) { + *d = DECO_NONE; + return(i); + } + + assert(i >= j); + *sz = (size_t)(i - j); + + return(i + 1); + } + + assert(lim > 0); + *sz = (size_t)lim; + + for (j = 0; wp[i] && j < lim; j++) + i++; + if (j < lim) + *d = DECO_NONE; + + return(i); +} + +/* + * Calculate the abstract widths and decimal positions of columns in a + * table. This routine allocates the columns structures then runs over + * all rows and cells in the table. The function pointers in "tbl" are + * used for the actual width calculations. + */ +void +tblcalc(struct rofftbl *tbl, const struct tbl_span *sp) +{ + const struct tbl_dat *dp; + const struct tbl_head *hp; + struct roffcol *col; + + /* + * Allocate the master column specifiers. These will hold the + * widths and decimal positions for all cells in the column. It + * must be freed and nullified by the caller. + */ + + assert(NULL == tbl->cols); + tbl->cols = calloc(sp->tbl->cols, sizeof(struct roffcol)); + + hp = sp->head; + + for ( ; sp; sp = sp->next) { + if (TBL_SPAN_DATA != sp->pos) + continue; + /* + * Account for the data cells in the layout, matching it + * to data cells in the data section. + */ + for (dp = sp->first; dp; dp = dp->next) { + if (NULL == dp->layout) + continue; + col = &tbl->cols[dp->layout->head->ident]; + tblcalc_data(tbl, col, sp->tbl, dp); + } + } + + /* + * Calculate width of the spanners. These get one space for a + * vertical line, two for a double-vertical line. + */ + + for ( ; hp; hp = hp->next) { + col = &tbl->cols[hp->ident]; + switch (hp->pos) { + case (TBL_HEAD_VERT): + col->width = (*tbl->len)(1, tbl->arg); + break; + case (TBL_HEAD_DVERT): + col->width = (*tbl->len)(2, tbl->arg); + break; + default: + break; + } + } +} + +static void +tblcalc_data(struct rofftbl *tbl, struct roffcol *col, + const struct tbl *tp, const struct tbl_dat *dp) +{ + size_t sz; + + /* Branch down into data sub-types. */ + + switch (dp->layout->pos) { + case (TBL_CELL_HORIZ): + /* FALLTHROUGH */ + case (TBL_CELL_DHORIZ): + sz = (*tbl->len)(1, tbl->arg); + if (col->width < sz) + col->width = sz; + break; + case (TBL_CELL_LONG): + /* FALLTHROUGH */ + case (TBL_CELL_CENTRE): + /* FALLTHROUGH */ + case (TBL_CELL_LEFT): + /* FALLTHROUGH */ + case (TBL_CELL_RIGHT): + tblcalc_literal(tbl, col, dp); + break; + case (TBL_CELL_NUMBER): + tblcalc_number(tbl, col, tp, dp); + break; + default: + abort(); + /* NOTREACHED */ + } +} + +static void +tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, + const struct tbl_dat *dp) +{ + size_t sz, bufsz, spsz; + + /* + * Calculate our width and use the spacing, with a minimum + * spacing dictated by position (centre, e.g,. gets a space on + * either side, while right/left get a single adjacent space). + */ + + sz = bufsz = spsz = 0; + if (dp->string) + sz = (*tbl->slen)(dp->string, tbl->arg); + + assert(dp->layout); + switch (dp->layout->pos) { + case (TBL_CELL_LONG): + /* FALLTHROUGH */ + case (TBL_CELL_CENTRE): + bufsz = (*tbl->len)(2, tbl->arg); + break; + default: + bufsz = (*tbl->len)(1, tbl->arg); + break; + } + + if (dp->layout->spacing) { + spsz = (*tbl->len)(dp->layout->spacing, tbl->arg); + bufsz = bufsz > spsz ? bufsz : spsz; + } + + sz += bufsz; + if (col->width < sz) + col->width = sz; +} + +static void +tblcalc_number(struct rofftbl *tbl, struct roffcol *col, + const struct tbl *tp, const struct tbl_dat *dp) +{ + int i; + size_t sz, psz, ssz, d; + char *cp; + const char *str; + char buf[2]; + + /* TODO: use spacing modifier. */ + + /* + * First calculate number width and decimal place (last + 1 for + * no-decimal numbers). If the stored decimal is subsequent + * ours, make our size longer by that difference + * (right-"shifting"); similarly, if ours is subsequent the + * stored, then extend the stored size by the difference. + * Finally, re-assign the stored values. + */ + + str = ""; + if (dp->string) + str = dp->string; + + sz = (*tbl->slen)(str, tbl->arg); + + buf[0] = tp->decimal; + buf[1] = '\0'; + + psz = (*tbl->slen)(buf, tbl->arg); + + if (NULL != (cp = strchr(str, tp->decimal))) { + buf[1] = '\0'; + for (ssz = 0, i = 0; cp != &str[i]; i++) { + buf[0] = str[i]; + ssz += (*tbl->slen)(buf, tbl->arg); + } + d = ssz + psz; + } else + d = sz + psz; + + /* Padding. */ + + sz += (*tbl->len)(2, tbl->arg); + d += (*tbl->len)(1, tbl->arg); + + /* Adjust the settings for this column. */ + + if (col->decimal > d) { + sz += col->decimal - d; + d = col->decimal; + } else + col->width += d - col->decimal; + + if (sz > col->width) + col->width = sz; + if (d > col->decimal) + col->decimal = d; +} + + diff --git a/contrib/mdocml/out.h b/contrib/mdocml/out.h new file mode 100644 index 0000000000..f544b8e32e --- /dev/null +++ b/contrib/mdocml/out.h @@ -0,0 +1,90 @@ +/* $Id: out.h,v 1.15 2011/01/05 15:37:23 kristaps 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. + */ +#ifndef OUT_H +#define OUT_H + +#define DATESIZ 24 + +__BEGIN_DECLS + +struct roffcol { + size_t width; /* width of cell */ + size_t decimal; /* decimal position in cell */ +}; + +typedef size_t (*tbl_strlen)(const char *, void *); +typedef size_t (*tbl_len)(size_t, void *); + +struct rofftbl { + tbl_strlen slen; /* calculate string length */ + tbl_len len; /* produce width of empty space */ + struct roffcol *cols; /* master column specifiers */ + void *arg; /* passed to slen and len */ +}; + +enum roffscale { + SCALE_CM, + SCALE_IN, + SCALE_PC, + SCALE_PT, + SCALE_EM, + SCALE_MM, + SCALE_EN, + SCALE_BU, + SCALE_VS, + SCALE_FS, + SCALE_MAX +}; + +enum roffdeco { + DECO_NONE, + DECO_SPECIAL, /* special character */ + DECO_SSPECIAL, /* single-char special */ + DECO_RESERVED, /* reserved word */ + DECO_BOLD, /* bold font */ + DECO_ITALIC, /* italic font */ + DECO_ROMAN, /* "normal" undecorated font */ + DECO_PREVIOUS, /* revert to previous font */ + DECO_NOSPACE, /* suppress spacing */ + DECO_FONT, /* font */ + DECO_FFONT, /* font family */ + DECO_MAX +}; + +struct roffsu { + enum roffscale unit; + double scale; +}; + +#define SCALE_VS_INIT(p, v) \ + do { (p)->unit = SCALE_VS; \ + (p)->scale = (v); } \ + while (/* CONSTCOND */ 0) + +#define SCALE_HS_INIT(p, v) \ + do { (p)->unit = SCALE_BU; \ + (p)->scale = (v); } \ + while (/* CONSTCOND */ 0) + +int a2roffsu(const char *, struct roffsu *, enum roffscale); +int a2roffdeco(enum roffdeco *, const char **, size_t *); +void time2a(time_t, char *, size_t); +void tblcalc(struct rofftbl *tbl, const struct tbl_span *); + +__END_DECLS + +#endif /*!OUT_H*/ diff --git a/contrib/mdocml/roff.3 b/contrib/mdocml/roff.3 new file mode 100644 index 0000000000..7d8bff5c1b --- /dev/null +++ b/contrib/mdocml/roff.3 @@ -0,0 +1,177 @@ +.\" $Id: roff.3,v 1.10 2011/01/01 16:18:39 kristaps Exp $ +.\" +.\" Copyright (c) 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. +.\" +.Dd $Mdocdate: January 1 2011 $ +.Dt ROFF 3 +.Os +.Sh NAME +.Nm roff , +.Nm roff_alloc , +.Nm roff_endparse , +.Nm roff_free , +.Nm roff_parseln , +.Nm roff_reset , +.Nm roff_span +.Nd roff macro compiler library +.Sh SYNOPSIS +.In mandoc.h +.In roff.h +.Ft "struct roff *" +.Fo roff_alloc +.Fa "struct regset *regs" +.Fa "void *data" +.Fa "mandocmsg msgs" +.Fc +.Ft void +.Fn roff_endparse "struct roff *roff" +.Ft void +.Fn roff_free "struct roff *roff" +.Ft "enum rofferr" +.Fo roff_parseln +.Fa "struct roff *roff" +.Fa "int line" +.Fa "char **bufp" +.Fa "size_t *bufsz" +.Fa "int pos" +.Fa "int *offs" +.Fc +.Ft void +.Fn roff_reset "struct roff *roff" +.Ft "const struct tbl_span *" +.Fn roff_span "const struct roff *roff" +.Sh DESCRIPTION +The +.Nm +library processes lines of +.Xr roff 7 +input. +.Pp +In general, applications initiate a parsing sequence with +.Fn roff_alloc , +parse each line in a document with +.Fn roff_parseln , +close the parsing session with +.Fn roff_endparse , +and finally free all allocated memory with +.Fn roff_free . +The +.Fn roff_reset +function may be used in order to reset the parser for another input +sequence. +.Pp +The +.Fn roff_parseln +function should be invoked before passing a line into the +.Xr mdoc 3 +or +.Xr man 3 +libraries. +.Pp +See the +.Sx EXAMPLES +section for a full example. +.Sh REFERENCE +This section further defines the +.Sx Types +and +.Sx Functions +available to programmers. +.Ss Types +Functions (see +.Sx Functions ) +may use the following types: +.Bl -ohang +.It Vt "enum rofferr" +Instructions for further processing to the caller of +.Fn roff_parseln . +.It Vt struct roff +An opaque type defined in +.Pa roff.c . +Its values are only used privately within the library. +.It Vt mandocmsg +A function callback type defined in +.Pa mandoc.h . +.El +.Ss Functions +Function descriptions follow: +.Bl -ohang +.It Fn roff_alloc +Allocates a parsing structure. +The +.Fa data +pointer is passed to +.Fa msgs . +Returns NULL on failure. +If non-NULL, the pointer must be freed with +.Fn roff_free . +.It Fn roff_reset +Reset the parser for another parse routine. +After its use, +.Fn roff_parseln +behaves as if invoked for the first time. +.It Fn roff_free +Free all resources of a parser. +The pointer is no longer valid after invocation. +.It Fn roff_parseln +Parse a nil-terminated line of input. +The character array +.Fa bufp +may be modified or reallocated within this function. +In the latter case, +.Fa bufsz +will be modified accordingly. +The +.Fa offs +pointer will be modified if the line start during subsequent processing +of the line is not at the zeroth index. +This line should not contain the trailing newline. +Returns 0 on failure, 1 on success. +.It Fn roff_endparse +Signals that the parse is complete. +.It Fn roff_span +If +.Fn roff_parseln +returned +.Va ROFF_TBL , +return the last parsed table row. +Returns NULL otherwise. +.El +.Sh EXAMPLES +See +.Pa main.c +in the source distribution for an example of usage. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 3 , +.Xr mdoc 3 , +.Xr roff 7 +.Sh AUTHORS +The +.Nm +library was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . +.Sh BUGS +The implementation of user-defined strings needs improvement: +.Bl -dash +.It +String values are taken literally and are not interpreted. +.It +Parsing of quoted strings is incomplete. +.It +The stings are stored internally using a singly linked list, +which is fine for small numbers of strings, +but ineffient when handling many strings. +.El diff --git a/contrib/mdocml/roff.7 b/contrib/mdocml/roff.7 new file mode 100644 index 0000000000..052edf7874 --- /dev/null +++ b/contrib/mdocml/roff.7 @@ -0,0 +1,619 @@ +.\" $Id: roff.7,v 1.23 2011/01/04 23:32:21 kristaps Exp $ +.\" +.\" Copyright (c) 2010 Kristaps Dzonsons +.\" Copyright (c) 2010 Ingo Schwarze +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 4 2011 $ +.Dt ROFF 7 +.Os +.Sh NAME +.Nm roff +.Nd roff language reference for mandoc +.Sh DESCRIPTION +The +.Nm roff +language is a general purpose text formatting language. +In particular, it serves as the basis for the +.Xr mdoc 7 +and +.Xr man 7 +manual formatting macro languages. +This manual describes the subset of the +.Nm +language accepted by the +.Xr mandoc 1 +utility. +.Pp +Input lines beginning with the control characters +.Sq \&. +or +.Sq \(aq +are parsed for requests and macros. +These define the document structure, change the processing state +and manipulate the formatting. +Some requests and macros also produce formatted output, +while others do not. +.Pp +All other input lines provide free-form text to be printed; +the formatting of free-form text depends on the respective +processing context. +.Sh LANGUAGE SYNTAX +.Nm +documents may contain only graphable 7-bit ASCII characters, the space +character, and, in certain circumstances, the tab character. +To produce other characters in the output, use the escape sequences +documented in the +.Xr mandoc_char 7 +manual. +.Pp +All manuals must have +.Ux +line terminators. +.Sh REQUEST SYNTAX +A request or macro line consists of: +.Pp +.Bl -enum -compact +.It +the control character +.Sq \&. +or +.Sq \(aq +at the beginning of the line, +.It +optionally an arbitrary amount of whitespace, +.It +the name of the request or the macro, which is one word of arbitrary +length, terminated by whitespace, +.It +and zero or more arguments delimited by whitespace. +.El +.Pp +Thus, the following request lines are all equivalent: +.Bd -literal -offset indent +\&.ig end +\&.ig end +\&. ig end +.Ed +.Sh REQUEST REFERENCE +The +.Xr mandoc 1 +.Nm +parser recognizes the following requests. +Note that the +.Nm +language defines many more requests not implemented in +.Xr mandoc 1 . +.Ss \&ad +Set line adjustment mode. +This line-scoped request is intended to have one argument to select +normal, left, right, or center adjustment for subsequent text. +Currently, it is ignored including its arguments, +and the number of arguments is not checked. +.Ss \&am +Append to a macro definition. +The syntax of this request is the same as that of +.Sx \&de . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&ami +Append to a macro definition, specifying the macro name indirectly. +The syntax of this request is the same as that of +.Sx \&dei . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&am1 +Append to a macro definition, switching roff compatibility mode off +during macro execution. +The syntax of this request is the same as that of +.Sx \&de1 . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&de +Define a +.Nm +macro. +Its syntax can be either +.Bd -literal -offset indent +.Pf . Cm \&de Ar name +.Ar macro definition +\&.. +.Ed +.Pp +or +.Bd -literal -offset indent +.Pf . Cm \&de Ar name Ar end +.Ar macro definition +.Pf . Ar end +.Ed +.Pp +Both forms define or redefine the macro +.Ar name +to represent the +.Ar macro definition , +which may consist of one or more input lines, including the newline +characters terminating each line, optionally containing calls to +.Nm +requests, +.Nm +macros or high-level macros like +.Xr man 7 +or +.Xr mdoc 7 +macros, whichever applies to the document in question. +.Pp +Specifying a custom +.Ar end +macro works in the same way as for +.Sx \&ig ; +namely, the call to +.Sq Pf . Ar end +first ends the +.Ar macro definition , +and after that, it is also evaluated as a +.Nm +request or +.Nm +macro, but not as a high-level macro. +.Pp +The macro can be invoked later using the syntax +.Pp +.D1 Pf . Ar name Op Ar argument Op Ar argument ... +.Pp +Arguments are separated by blank characters and can be quoted +using double-quotes +.Pq Sq \(dq +to allow inclusion of blank characters into arguments. +To include the double-quote character into a quoted argument, +escape it from ending the argument by doubling it. +.Pp +The line invoking the macro will be replaced +in the input stream by the +.Ar macro definition , +replacing all occurrences of +.No \e\e$ Ns Ar N , +where +.Ar N +is a digit, by the +.Ar N Ns th Ar argument . +For example, +.Bd -literal -offset indent +\&.de ZN +\efI\e^\e\e$1\e^\efP\e\e$2 +\&.. +\&.ZN XtFree . +.Ed +.Pp +produces +.Pp +.D1 \efI\e^XtFree\e^\efP. +.Pp +in the input stream, and thus in the output: \fI\^XtFree\^\fP. +.Pp +Since macros and user-defined strings share a common string table, +defining a macro +.Ar name +clobbers the user-defined string +.Ar name , +and the +.Ar macro definition +can also be printed using the +.Sq \e* +string interpolation syntax described below +.Sx ds , +but this is rarely useful because every macro definition contains at least +one explicit newline character. +.Pp +In order to prevent endless recursion, both groff and +.Xr mandoc 1 +limit the stack depth for expanding macros and strings +to a large, but finite number. +Do not rely on the exact value of this limit. +.Ss \&dei +Define a +.Nm +macro, specifying the macro name indirectly. +The syntax of this request is the same as that of +.Sx \&de . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&de1 +Define a +.Nm +macro that will be executed with +.Nm +compatibility mode switched off during macro execution. +This is a GNU extension not available in traditional +.Nm +implementations and not even in older versions of groff. +Since +.Xr mandoc 1 +does not implement +.Nm +compatibility mode at all, it handles this request as an alias for +.Sx \&de . +.Ss \&ds +Define a user-defined string. +Its syntax is as follows: +.Pp +.D1 Pf . Cm \&ds Ar name Oo \(dq Oc Ns Ar string +.Pp +The +.Ar name +and +.Ar string +arguments are space-separated. +If the +.Ar string +begins with a double-quote character, that character will not be part +of the string. +All remaining characters on the input line form the +.Ar string , +including whitespace and double-quote characters, even trailing ones. +.Pp +The +.Ar string +can be interpolated into subsequent text by using +.No \e* Ns Bq Ar name +for a +.Ar name +of arbitrary length, or \e*(NN or \e*N if the length of +.Ar name +is two or one characters, respectively. +Interpolation can be prevented by escaping the leading backslash; +that is, an asterisk preceded by an even number of backslashes +does not trigger string interpolation. +.Pp +Since user-defined strings and macros share a common string table, +defining a string +.Ar name +clobbers the macro +.Ar name , +and the +.Ar name +used for defining a string can also be invoked as a macro, +in which case the following input line will be appended to the +.Ar string , +forming a new input line passed to the +.Nm +parser. +For example, +.Bd -literal -offset indent +\&.ds badidea .S +\&.badidea +H SYNOPSIS +.Ed +.Pp +invokes the +.Cm SH +macro when used in a +.Xr man 7 +document. +Such abuse is of course strongly discouraged. +.Ss \&el +The +.Qq else +half of an if/else conditional. +Pops a result off the stack of conditional evaluations pushed by +.Sx \&ie +and uses it as its conditional. +If no stack entries are present (e.g., due to no prior +.Sx \&ie +calls) +then false is assumed. +The syntax of this request is similar to +.Sx \&if +except that the conditional is missing. +.Ss \&hy +Set automatic hyphenation mode. +This line-scoped request is currently ignored. +.Ss \&ie +The +.Qq if +half of an if/else conditional. +The result of the conditional is pushed into a stack used by subsequent +invocations of +.Sx \&el , +which may be separated by any intervening input (or not exist at all). +Its syntax is equivalent to +.Sx \&if . +.Ss \&if +Begins a conditional. +Right now, the conditional evaluates to true +if and only if it starts with the letter +.Sy n , +indicating processing in nroff style as opposed to troff style. +If a conditional is false, its children are not processed, but are +syntactically interpreted to preserve the integrity of the input +document. +Thus, +.Pp +.D1 \&.if t .ig +.Pp +will discard the +.Sq \&.ig , +which may lead to interesting results, but +.Pp +.D1 \&.if t .if t \e{\e +.Pp +will continue to syntactically interpret to the block close of the final +conditional. +Sub-conditionals, in this case, obviously inherit the truth value of +the parent. +This request has the following syntax: +.Bd -literal -offset indent +\&.if COND \e{\e +BODY... +\&.\e} +.Ed +.Bd -literal -offset indent +\&.if COND \e{ BODY +BODY... \e} +.Ed +.Bd -literal -offset indent +\&.if COND \e{ BODY +BODY... +\&.\e} +.Ed +.Bd -literal -offset indent +\&.if COND \e +BODY +.Ed +.Pp +COND is a conditional statement. +roff allows for complicated conditionals; mandoc is much simpler. +At this time, mandoc supports only +.Sq n , +evaluating to true; +and +.Sq t , +.Sq e , +and +.Sq o , +evaluating to false. +All other invocations are read up to the next end of line or space and +evaluate as false. +.Pp +If the BODY section is begun by an escaped brace +.Sq \e{ , +scope continues until a closing-brace escape sequence +.Sq \.\e} . +If the BODY is not enclosed in braces, scope continues until +the end of the line. +If the COND is followed by a BODY on the same line, whether after a +brace or not, then requests and macros +.Em must +begin with a control character. +It is generally more intuitive, in this case, to write +.Bd -literal -offset indent +\&.if COND \e{\e +\&.foo +bar +\&.\e} +.Ed +.Pp +than having the request or macro follow as +.Pp +.D1 \&.if COND \e{ .foo +.Pp +The scope of a conditional is always parsed, but only executed if the +conditional evaluates to true. +.Pp +Note that text following an +.Sq \&.\e} +escape sequence is discarded. +Furthermore, if an explicit closing sequence +.Sq \e} +is specified in a free-form line, the entire line is accepted within the +scope of the prior request, not only the text preceding the close, with the +.Sq \e} +collapsing into a zero-width space. +.Ss \&ig +Ignore input. +Its syntax can be either +.Bd -literal -offset indent +.Pf . Cm \&ig +.Ar ignored text +\&.. +.Ed +.Pp +or +.Bd -literal -offset indent +.Pf . Cm \&ig Ar end +.Ar ignored text +.Pf . Ar end +.Ed +.Pp +In the first case, input is ignored until a +.Sq \&.. +request is encountered on its own line. +In the second case, input is ignored until the specified +.Sq Pf . Ar end +macro is encountered. +Do not use the escape character +.Sq \e +anywhere in the definition of +.Ar end ; +it would cause very strange behaviour. +.Pp +When the +.Ar end +macro is a roff request or a roff macro, like in +.Pp +.D1 \&.ig if +.Pp +the subsequent invocation of +.Sx \&if +will first terminate the +.Ar ignored text , +then be invoked as usual. +Otherwise, it only terminates the +.Ar ignored text , +and arguments following it or the +.Sq \&.. +request are discarded. +.Ss \&ne +Declare the need for the specified minimum vertical space +before the next trap or the bottom of the page. +This line-scoped request is currently ignored. +.Ss \&nh +Turn off automatic hyphenation mode. +This line-scoped request is currently ignored. +.Ss \&rm +Remove a request, macro or string. +This request is intended to have one argument, +the name of the request, macro or string to be undefined. +Currently, it is ignored including its arguments, +and the number of arguments is not checked. +.Ss \&nr +Define a register. +A register is an arbitrary string value that defines some sort of state, +which influences parsing and/or formatting. +Its syntax is as follows: +.Pp +.D1 Pf \. Cm \&nr Ar name Ar value +.Pp +The +.Ar value +may, at the moment, only be an integer. +So far, only the following register +.Ar name +is recognised: +.Bl -tag -width Ds +.It Cm nS +If set to a positive integer value, certain +.Xr mdoc 7 +macros will behave in the same way as in the +.Em SYNOPSIS +section. +If set to 0, these macros will behave in the same way as outside the +.Em SYNOPSIS +section, even when called within the +.Em SYNOPSIS +section itself. +Note that starting a new +.Xr mdoc 7 +section with the +.Cm \&Sh +macro will reset this register. +.El +.Ss \&so +Include a source file. +Its syntax is as follows: +.Pp +.D1 Pf \. Cm \&so Ar file +.Pp +The +.Ar file +will be read and its contents processed as input in place of the +.Sq \&.so +request line. +To avoid inadvertant inclusion of unrelated files, +.Xr mandoc 1 +only accepts relative paths not containing the strings +.Qq ../ +and +.Qq /.. . +.Ss \&tr +Output character translation. +This request is intended to have one argument, +consisting of an even number of characters. +Currently, it is ignored including its arguments, +and the number of arguments is not checked. +.Ss \&T& +Re-start a table layout, retaining the options of the prior table +invocation. +See +.Sx \&TS . +.Ss \&TE +End a table context. +See +.Sx \&TS . +.Ss \&TS +Begin a table, which formats input in aligned rows and columns. +See +.Xr tbl 7 +for a description of the tbl language. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other other +.Nm +implementations, at this time limited to GNU troff +.Pq Qq groff . +The term +.Qq historic groff +refers to groff version 1.15. +.Pp +.Bl -dash -compact +.It +The +.Cm nS +register is only compatible with OpenBSD's groff-1.15. +.It +Historic groff did not accept white-space before a custom +.Ar end +macro for the +.Sx \&ig +request. +.It +The +.Sx \&if +and family would print funny white-spaces with historic groff when +using the next-line syntax. +.El +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr tbl 7 +.Rs +.%A Joseph F. Ossanna +.%A Brian W. Kernighan +.%I AT&T Bell Laboratories +.%T Troff User's Manual +.%R Computing Science Technical Report +.%N 54 +.%C Murray Hill, New Jersey +.%D 1976 and 1992 +.%U http://www.kohala.com/start/troff/cstr54.ps +.Re +.Rs +.%A Joseph F. Ossanna +.%A Brian W. Kernighan +.%A Gunnar Ritter +.%T Heirloom Documentation Tools Nroff/Troff User's Manual +.%D September 17, 2007 +.%U http://heirloom.sourceforge.net/doctools/troff.pdf +.Re +.Sh HISTORY +The RUNOFF typesetting system was written in PL/1 for the CTSS +operating system by Jerome ("Jerry") E. Saltzer in 1961. +It was first used as the main documentation tool by Multics since 1963. +Robert ("Bob") H. Morris ported it to the GE-635 and called it +.Nm , +Doug McIlroy rewrote it in BCPL in 1969, +Joseph F. Ossanna rewrote it in PDP-11 assembly in 1973, +and Brian W. Kernighan rewrote it in C in 1975. +.Sh AUTHORS +.An -nosplit +This partial +.Nm +reference was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv +and +.An Ingo Schwarze Aq schwarze@openbsd.org . diff --git a/contrib/mdocml/roff.c b/contrib/mdocml/roff.c new file mode 100644 index 0000000000..5053bef514 --- /dev/null +++ b/contrib/mdocml/roff.c @@ -0,0 +1,1356 @@ +/* $Id: roff.c,v 1.120 2011/01/03 23:24:16 schwarze Exp $ */ +/* + * Copyright (c) 2010, 2011 Kristaps Dzonsons + * Copyright (c) 2010, 2011 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "roff.h" +#include "libroff.h" +#include "libmandoc.h" + +#define RSTACK_MAX 128 + +#define ROFF_CTL(c) \ + ('.' == (c) || '\'' == (c)) + +enum rofft { + ROFF_ad, + ROFF_am, + ROFF_ami, + ROFF_am1, + ROFF_de, + ROFF_dei, + ROFF_de1, + ROFF_ds, + ROFF_el, + ROFF_hy, + ROFF_ie, + ROFF_if, + ROFF_ig, + ROFF_ne, + ROFF_nh, + ROFF_nr, + ROFF_rm, + ROFF_so, + ROFF_tr, + ROFF_TS, + ROFF_TE, + ROFF_T_, + ROFF_cblock, + ROFF_ccond, /* FIXME: remove this. */ + ROFF_USERDEF, + ROFF_MAX +}; + +enum roffrule { + ROFFRULE_ALLOW, + ROFFRULE_DENY +}; + +struct roffstr { + char *name; /* key of symbol */ + char *string; /* current value */ + struct roffstr *next; /* next in list */ +}; + +struct roff { + struct roffnode *last; /* leaf of stack */ + mandocmsg msg; /* err/warn/fatal messages */ + void *data; /* privdata for messages */ + enum roffrule rstack[RSTACK_MAX]; /* stack of !`ie' rules */ + int rstackpos; /* position in rstack */ + struct regset *regs; /* read/writable registers */ + struct roffstr *first_string; /* user-defined strings & macros */ + 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 roffnode { + enum rofft tok; /* type of node */ + struct roffnode *parent; /* up one in stack */ + int line; /* parse line */ + int col; /* parse col */ + char *name; /* node name, e.g. macro name */ + char *end; /* end-rules: custom token */ + int endspan; /* end-rules: next-line or infty */ + enum roffrule rule; /* current evaluation rule */ +}; + +#define ROFF_ARGS struct roff *r, /* parse ctx */ \ + enum rofft tok, /* tok of macro */ \ + char **bufp, /* input buffer */ \ + size_t *szp, /* size of input buffer */ \ + int ln, /* parse line */ \ + int ppos, /* original pos in buffer */ \ + int pos, /* current pos in buffer */ \ + int *offs /* reset offset of buffer data */ + +typedef enum rofferr (*roffproc)(ROFF_ARGS); + +struct roffmac { + const char *name; /* macro name */ + roffproc proc; /* process new macro */ + roffproc text; /* process as child text of macro */ + roffproc sub; /* process as child of macro */ + int flags; +#define ROFFMAC_STRUCT (1 << 0) /* always interpret */ + struct roffmac *next; +}; + +static enum rofferr roff_block(ROFF_ARGS); +static enum rofferr roff_block_text(ROFF_ARGS); +static enum rofferr roff_block_sub(ROFF_ARGS); +static enum rofferr roff_cblock(ROFF_ARGS); +static enum rofferr roff_ccond(ROFF_ARGS); +static enum rofferr roff_cond(ROFF_ARGS); +static enum rofferr roff_cond_text(ROFF_ARGS); +static enum rofferr roff_cond_sub(ROFF_ARGS); +static enum rofferr roff_ds(ROFF_ARGS); +static enum roffrule roff_evalcond(const char *, int *); +static void roff_freestr(struct roff *); +static const char *roff_getstrn(const struct roff *, + const char *, size_t); +static enum rofferr roff_line_ignore(ROFF_ARGS); +static enum rofferr roff_line_error(ROFF_ARGS); +static enum rofferr roff_nr(ROFF_ARGS); +static int roff_res(struct roff *, + char **, size_t *, int); +static void roff_setstr(struct roff *, + const char *, const char *, int); +static enum rofferr roff_so(ROFF_ARGS); +static enum rofferr roff_TE(ROFF_ARGS); +static enum rofferr roff_TS(ROFF_ARGS); +static enum rofferr roff_T_(ROFF_ARGS); +static enum rofferr roff_userdef(ROFF_ARGS); + +/* See roff_hash_find() */ + +#define ASCII_HI 126 +#define ASCII_LO 33 +#define HASHWIDTH (ASCII_HI - ASCII_LO + 1) + +static struct roffmac *hash[HASHWIDTH]; + +static struct roffmac roffs[ROFF_MAX] = { + { "ad", roff_line_ignore, NULL, NULL, 0, NULL }, + { "am", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "de", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "ds", roff_ds, NULL, NULL, 0, NULL }, + { "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL }, + { "hy", roff_line_ignore, NULL, NULL, 0, NULL }, + { "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL }, + { "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL }, + { "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "ne", roff_line_ignore, NULL, NULL, 0, NULL }, + { "nh", roff_line_ignore, NULL, NULL, 0, NULL }, + { "nr", roff_nr, NULL, NULL, 0, NULL }, + { "rm", roff_line_error, NULL, NULL, 0, NULL }, + { "so", roff_so, NULL, NULL, 0, NULL }, + { "tr", roff_line_ignore, NULL, NULL, 0, NULL }, + { "TS", roff_TS, NULL, NULL, 0, NULL }, + { "TE", roff_TE, NULL, NULL, 0, NULL }, + { "T&", roff_T_, NULL, NULL, 0, NULL }, + { ".", roff_cblock, NULL, NULL, 0, NULL }, + { "\\}", roff_ccond, NULL, NULL, 0, NULL }, + { NULL, roff_userdef, NULL, NULL, 0, NULL }, +}; + +static void roff_free1(struct roff *); +static enum rofft roff_hash_find(const char *, size_t); +static void roff_hash_init(void); +static void roffnode_cleanscope(struct roff *); +static void roffnode_push(struct roff *, enum rofft, + const char *, int, int); +static void roffnode_pop(struct roff *); +static enum rofft roff_parse(struct roff *, const char *, int *); +static int roff_parse_nat(const char *, unsigned int *); + +/* See roff_hash_find() */ +#define ROFF_HASH(p) (p[0] - ASCII_LO) + +static void +roff_hash_init(void) +{ + struct roffmac *n; + int buc, i; + + for (i = 0; i < (int)ROFF_USERDEF; i++) { + assert(roffs[i].name[0] >= ASCII_LO); + assert(roffs[i].name[0] <= ASCII_HI); + + buc = ROFF_HASH(roffs[i].name); + + if (NULL != (n = hash[buc])) { + for ( ; n->next; n = n->next) + /* Do nothing. */ ; + n->next = &roffs[i]; + } else + hash[buc] = &roffs[i]; + } +} + + +/* + * Look up a roff token by its name. Returns ROFF_MAX if no macro by + * the nil-terminated string name could be found. + */ +static enum rofft +roff_hash_find(const char *p, size_t s) +{ + int buc; + struct roffmac *n; + + /* + * libroff has an extremely simple hashtable, for the time + * being, which simply keys on the first character, which must + * be printable, then walks a chain. It works well enough until + * optimised. + */ + + if (p[0] < ASCII_LO || p[0] > ASCII_HI) + return(ROFF_MAX); + + buc = ROFF_HASH(p); + + if (NULL == (n = hash[buc])) + return(ROFF_MAX); + for ( ; n; n = n->next) + if (0 == strncmp(n->name, p, s) && '\0' == n->name[(int)s]) + return((enum rofft)(n - roffs)); + + return(ROFF_MAX); +} + + +/* + * Pop the current node off of the stack of roff instructions currently + * pending. + */ +static void +roffnode_pop(struct roff *r) +{ + struct roffnode *p; + + assert(r->last); + p = r->last; + + if (ROFF_el == p->tok) + if (r->rstackpos > -1) + r->rstackpos--; + + r->last = r->last->parent; + free(p->name); + free(p->end); + free(p); +} + + +/* + * Push a roff node onto the instruction stack. This must later be + * removed with roffnode_pop(). + */ +static void +roffnode_push(struct roff *r, enum rofft tok, const char *name, + int line, int col) +{ + struct roffnode *p; + + p = mandoc_calloc(1, sizeof(struct roffnode)); + p->tok = tok; + if (name) + p->name = mandoc_strdup(name); + p->parent = r->last; + p->line = line; + p->col = col; + p->rule = p->parent ? p->parent->rule : ROFFRULE_DENY; + + r->last = p; +} + + +static void +roff_free1(struct roff *r) +{ + struct tbl_node *t; + + while (r->first_tbl) { + t = r->first_tbl; + r->first_tbl = t->next; + tbl_free(t); + } + + r->first_tbl = r->last_tbl = r->tbl = NULL; + + while (r->last) + roffnode_pop(r); + + roff_freestr(r); +} + + +void +roff_reset(struct roff *r) +{ + + roff_free1(r); +} + + +void +roff_free(struct roff *r) +{ + + roff_free1(r); + free(r); +} + + +struct roff * +roff_alloc(struct regset *regs, void *data, const mandocmsg msg) +{ + struct roff *r; + + r = mandoc_calloc(1, sizeof(struct roff)); + r->regs = regs; + r->msg = msg; + r->data = data; + r->rstackpos = -1; + + roff_hash_init(); + return(r); +} + + +/* + * Pre-filter each and every line for reserved words (one beginning with + * `\*', e.g., `\*(ab'). These must be handled before the actual line + * is processed. + */ +static int +roff_res(struct roff *r, char **bufp, size_t *szp, int pos) +{ + const char *stesc; /* start of an escape sequence ('\\') */ + const char *stnam; /* start of the name, after "[(*" */ + const char *cp; /* end of the name, e.g. before ']' */ + const char *res; /* the string to be substituted */ + int i, maxl; + size_t nsz; + char *n; + + /* Search for a leading backslash and save a pointer to it. */ + + cp = *bufp + pos; + while (NULL != (cp = strchr(cp, '\\'))) { + stesc = cp++; + + /* + * The second character must be an asterisk. + * If it isn't, skip it anyway: It is escaped, + * so it can't start another escape sequence. + */ + + if ('\0' == *cp) + return(1); + if ('*' != *cp++) + continue; + + /* + * The third character decides the length + * of the name of the string. + * Save a pointer to the name. + */ + + switch (*cp) { + case ('\0'): + return(1); + case ('('): + cp++; + maxl = 2; + break; + case ('['): + cp++; + maxl = 0; + break; + default: + maxl = 1; + break; + } + stnam = cp; + + /* Advance to the end of the name. */ + + for (i = 0; 0 == maxl || i < maxl; i++, cp++) { + if ('\0' == *cp) + return(1); /* Error. */ + if (0 == maxl && ']' == *cp) + break; + } + + /* + * Retrieve the replacement string; if it is + * undefined, resume searching for escapes. + */ + + res = roff_getstrn(r, stnam, (size_t)i); + + if (NULL == res) { + cp -= maxl ? 1 : 0; + continue; + } + + /* Replace the escape sequence by the string. */ + + nsz = *szp + strlen(res) + 1; + n = mandoc_malloc(nsz); + + strlcpy(n, *bufp, (size_t)(stesc - *bufp + 1)); + strlcat(n, res, nsz); + strlcat(n, cp + (maxl ? 0 : 1), nsz); + + free(*bufp); + + *bufp = n; + *szp = nsz; + return(0); + } + + return(1); +} + + +enum rofferr +roff_parseln(struct roff *r, int ln, char **bufp, + size_t *szp, int pos, int *offs) +{ + enum rofft t; + enum rofferr e; + int ppos; + + /* + * Run the reserved-word filter only if we have some reserved + * words to fill in. + */ + + if (r->first_string && ! roff_res(r, bufp, szp, pos)) + return(ROFF_REPARSE); + + /* + * First, if a scope is open and we're not a macro, pass the + * text through the macro's filter. If a scope isn't open and + * we're not a macro, just let it through. + */ + + if (r->last && ! ROFF_CTL((*bufp)[pos])) { + t = r->last->tok; + assert(roffs[t].text); + e = (*roffs[t].text) + (r, t, bufp, szp, ln, pos, pos, offs); + assert(ROFF_IGN == e || ROFF_CONT == e); + if (ROFF_CONT == e && r->tbl) + return(tbl_read(r->tbl, ln, *bufp, *offs)); + return(e); + } else if ( ! ROFF_CTL((*bufp)[pos])) { + if (r->tbl) + return(tbl_read(r->tbl, ln, *bufp, *offs)); + return(ROFF_CONT); + } + + /* + * If a scope is open, go to the child handler for that macro, + * as it may want to preprocess before doing anything with it. + */ + + if (r->last) { + t = r->last->tok; + assert(roffs[t].sub); + return((*roffs[t].sub) + (r, t, bufp, szp, + ln, pos, pos, offs)); + } + + /* + * Lastly, as we've no scope open, try to look up and execute + * the new macro. If no macro is found, simply return and let + * the compilers handle it. + */ + + ppos = pos; + if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos))) + return(ROFF_CONT); + + assert(roffs[t].proc); + return((*roffs[t].proc) + (r, t, bufp, szp, + ln, ppos, pos, offs)); +} + + +void +roff_endparse(struct roff *r) +{ + + if (r->last) + (*r->msg)(MANDOCERR_SCOPEEXIT, r->data, + r->last->line, r->last->col, NULL); + + if (r->tbl) { + (*r->msg)(MANDOCERR_SCOPEEXIT, r->data, + r->tbl->line, r->tbl->pos, NULL); + tbl_end(r->tbl); + 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 rofft +roff_parse(struct roff *r, const char *buf, int *pos) +{ + const char *mac; + size_t maclen; + enum rofft t; + + assert(ROFF_CTL(buf[*pos])); + (*pos)++; + + while (' ' == buf[*pos] || '\t' == buf[*pos]) + (*pos)++; + + if ('\0' == buf[*pos]) + return(ROFF_MAX); + + mac = buf + *pos; + maclen = strcspn(mac, " \\\t\0"); + + t = (r->current_string = roff_getstrn(r, mac, maclen)) + ? ROFF_USERDEF : roff_hash_find(mac, maclen); + + *pos += maclen; + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + return(t); +} + + +static int +roff_parse_nat(const char *buf, unsigned int *res) +{ + char *ep; + long lval; + + errno = 0; + lval = strtol(buf, &ep, 10); + if (buf[0] == '\0' || *ep != '\0') + return(0); + if ((errno == ERANGE && + (lval == LONG_MAX || lval == LONG_MIN)) || + (lval > INT_MAX || lval < 0)) + return(0); + + *res = (unsigned int)lval; + return(1); +} + + +/* ARGSUSED */ +static enum rofferr +roff_cblock(ROFF_ARGS) +{ + + /* + * A block-close `..' should only be invoked as a child of an + * ignore macro, otherwise raise a warning and just ignore it. + */ + + if (NULL == r->last) { + (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL); + return(ROFF_IGN); + } + + switch (r->last->tok) { + case (ROFF_am): + /* FALLTHROUGH */ + case (ROFF_ami): + /* FALLTHROUGH */ + case (ROFF_am1): + /* FALLTHROUGH */ + case (ROFF_de): + /* ROFF_de1 is remapped to ROFF_de in roff_block(). */ + /* FALLTHROUGH */ + case (ROFF_dei): + /* FALLTHROUGH */ + case (ROFF_ig): + break; + default: + (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL); + return(ROFF_IGN); + } + + if ((*bufp)[pos]) + (*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL); + + roffnode_pop(r); + roffnode_cleanscope(r); + return(ROFF_IGN); + +} + + +static void +roffnode_cleanscope(struct roff *r) +{ + + while (r->last) { + if (--r->last->endspan < 0) + break; + roffnode_pop(r); + } +} + + +/* ARGSUSED */ +static enum rofferr +roff_ccond(ROFF_ARGS) +{ + + if (NULL == r->last) { + (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL); + return(ROFF_IGN); + } + + switch (r->last->tok) { + case (ROFF_el): + /* FALLTHROUGH */ + case (ROFF_ie): + /* FALLTHROUGH */ + case (ROFF_if): + break; + default: + (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL); + return(ROFF_IGN); + } + + if (r->last->endspan > -1) { + (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL); + return(ROFF_IGN); + } + + if ((*bufp)[pos]) + (*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL); + + roffnode_pop(r); + roffnode_cleanscope(r); + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_block(ROFF_ARGS) +{ + int sv; + size_t sz; + char *name; + + name = NULL; + + if (ROFF_ig != tok) { + if ('\0' == (*bufp)[pos]) { + (*r->msg)(MANDOCERR_NOARGS, r->data, ln, ppos, NULL); + return(ROFF_IGN); + } + + /* + * Re-write `de1', since we don't really care about + * groff's strange compatibility mode, into `de'. + */ + + if (ROFF_de1 == tok) + tok = ROFF_de; + if (ROFF_de == tok) + name = *bufp + pos; + else + (*r->msg)(MANDOCERR_REQUEST, r->data, ln, ppos, + roffs[tok].name); + + while ((*bufp)[pos] && ' ' != (*bufp)[pos]) + pos++; + + while (' ' == (*bufp)[pos]) + (*bufp)[pos++] = '\0'; + } + + 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 + * added from roff_block_text() in multiline mode. + */ + + if (ROFF_de == tok) + roff_setstr(r, name, "", 0); + + if ('\0' == (*bufp)[pos]) + return(ROFF_IGN); + + /* If present, process the custom end-of-line marker. */ + + sv = pos; + while ((*bufp)[pos] && + ' ' != (*bufp)[pos] && + '\t' != (*bufp)[pos]) + pos++; + + /* + * Note: groff does NOT like escape characters in the input. + * Instead of detecting this, we're just going to let it fly and + * to hell with it. + */ + + assert(pos > sv); + sz = (size_t)(pos - sv); + + if (1 == sz && '.' == (*bufp)[sv]) + return(ROFF_IGN); + + r->last->end = mandoc_malloc(sz + 1); + + memcpy(r->last->end, *bufp + sv, sz); + r->last->end[(int)sz] = '\0'; + + if ((*bufp)[pos]) + (*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL); + + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_block_sub(ROFF_ARGS) +{ + enum rofft t; + int i, j; + + /* + * First check whether a custom macro exists at this level. If + * it does, then check against it. This is some of groff's + * stranger behaviours. If we encountered a custom end-scope + * tag and that tag also happens to be a "real" macro, then we + * need to try interpreting it again as a real macro. If it's + * not, then return ignore. Else continue. + */ + + if (r->last->end) { + i = pos + 1; + while (' ' == (*bufp)[i] || '\t' == (*bufp)[i]) + i++; + + for (j = 0; r->last->end[j]; j++, i++) + if ((*bufp)[i] != r->last->end[j]) + break; + + if ('\0' == r->last->end[j] && + ('\0' == (*bufp)[i] || + ' ' == (*bufp)[i] || + '\t' == (*bufp)[i])) { + roffnode_pop(r); + roffnode_cleanscope(r); + + if (ROFF_MAX != roff_parse(r, *bufp, &pos)) + return(ROFF_RERUN); + return(ROFF_IGN); + } + } + + /* + * If we have no custom end-query or lookup failed, then try + * pulling it out of the hashtable. + */ + + ppos = pos; + t = roff_parse(r, *bufp, &pos); + + /* + * Macros other than block-end are only significant + * in `de' blocks; elsewhere, simply throw them away. + */ + if (ROFF_cblock != t) { + if (ROFF_de == tok) + roff_setstr(r, r->last->name, *bufp + ppos, 1); + return(ROFF_IGN); + } + + assert(roffs[t].proc); + return((*roffs[t].proc)(r, t, bufp, szp, + ln, ppos, pos, offs)); +} + + +/* ARGSUSED */ +static enum rofferr +roff_block_text(ROFF_ARGS) +{ + + if (ROFF_de == tok) + roff_setstr(r, r->last->name, *bufp + pos, 1); + + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_cond_sub(ROFF_ARGS) +{ + enum rofft t; + enum roffrule rr; + + ppos = pos; + rr = r->last->rule; + + /* + * Clean out scope. If we've closed ourselves, then don't + * continue. + */ + + roffnode_cleanscope(r); + + if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos))) { + if ('\\' == (*bufp)[pos] && '}' == (*bufp)[pos + 1]) + return(roff_ccond + (r, ROFF_ccond, bufp, szp, + ln, pos, pos + 2, offs)); + return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT); + } + + /* + * A denied conditional must evaluate its children if and only + * if they're either structurally required (such as loops and + * conditionals) or a closing macro. + */ + if (ROFFRULE_DENY == rr) + if ( ! (ROFFMAC_STRUCT & roffs[t].flags)) + if (ROFF_ccond != t) + return(ROFF_IGN); + + assert(roffs[t].proc); + return((*roffs[t].proc)(r, t, bufp, szp, + ln, ppos, pos, offs)); +} + + +/* ARGSUSED */ +static enum rofferr +roff_cond_text(ROFF_ARGS) +{ + char *ep, *st; + enum roffrule rr; + + rr = r->last->rule; + + /* + * We display the value of the text if out current evaluation + * scope permits us to do so. + */ + + /* FIXME: use roff_ccond? */ + + st = &(*bufp)[pos]; + if (NULL == (ep = strstr(st, "\\}"))) { + roffnode_cleanscope(r); + return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT); + } + + if (ep == st || (ep > st && '\\' != *(ep - 1))) + roffnode_pop(r); + + roffnode_cleanscope(r); + return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT); +} + + +static enum roffrule +roff_evalcond(const char *v, int *pos) +{ + + switch (v[*pos]) { + case ('n'): + (*pos)++; + return(ROFFRULE_ALLOW); + case ('e'): + /* FALLTHROUGH */ + case ('o'): + /* FALLTHROUGH */ + case ('t'): + (*pos)++; + return(ROFFRULE_DENY); + default: + break; + } + + while (v[*pos] && ' ' != v[*pos]) + (*pos)++; + return(ROFFRULE_DENY); +} + +/* ARGSUSED */ +static enum rofferr +roff_line_ignore(ROFF_ARGS) +{ + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_line_error(ROFF_ARGS) +{ + + (*r->msg)(MANDOCERR_REQUEST, r->data, ln, ppos, roffs[tok].name); + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_cond(ROFF_ARGS) +{ + int sv; + enum roffrule rule; + + /* Stack overflow! */ + + if (ROFF_ie == tok && r->rstackpos == RSTACK_MAX - 1) { + (*r->msg)(MANDOCERR_MEM, r->data, ln, ppos, NULL); + return(ROFF_ERR); + } + + /* First, evaluate the conditional. */ + + if (ROFF_el == tok) { + /* + * An `.el' will get the value of the current rstack + * entry set in prior `ie' calls or defaults to DENY. + */ + if (r->rstackpos < 0) + rule = ROFFRULE_DENY; + else + rule = r->rstack[r->rstackpos]; + } else + rule = roff_evalcond(*bufp, &pos); + + sv = pos; + + while (' ' == (*bufp)[pos]) + pos++; + + /* + * Roff is weird. If we have just white-space after the + * conditional, it's considered the BODY and we exit without + * really doing anything. Warn about this. It's probably + * wrong. + */ + + if ('\0' == (*bufp)[pos] && sv != pos) { + (*r->msg)(MANDOCERR_NOARGS, r->data, ln, ppos, NULL); + return(ROFF_IGN); + } + + roffnode_push(r, tok, NULL, ln, ppos); + + r->last->rule = rule; + + if (ROFF_ie == tok) { + /* + * An if-else will put the NEGATION of the current + * evaluated conditional into the stack. + */ + r->rstackpos++; + if (ROFFRULE_DENY == r->last->rule) + r->rstack[r->rstackpos] = ROFFRULE_ALLOW; + else + r->rstack[r->rstackpos] = ROFFRULE_DENY; + } + + /* If the parent has false as its rule, then so do we. */ + + if (r->last->parent && ROFFRULE_DENY == r->last->parent->rule) + r->last->rule = ROFFRULE_DENY; + + /* + * Determine scope. If we're invoked with "\{" trailing the + * conditional, then we're in a multiline scope. Else our scope + * expires on the next line. + */ + + r->last->endspan = 1; + + if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) { + r->last->endspan = -1; + pos += 2; + } + + /* + * If there are no arguments on the line, the next-line scope is + * assumed. + */ + + if ('\0' == (*bufp)[pos]) + return(ROFF_IGN); + + /* Otherwise re-run the roff parser after recalculating. */ + + *offs = pos; + return(ROFF_RERUN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_ds(ROFF_ARGS) +{ + char *name, *string; + + /* + * A symbol is named by the first word following the macro + * invocation up to a space. Its value is anything after the + * name's trailing whitespace and optional double-quote. Thus, + * + * [.ds foo "bar " ] + * + * will have `bar " ' as its value. + */ + + name = *bufp + pos; + if ('\0' == *name) + return(ROFF_IGN); + + string = name; + /* Read until end of name. */ + while (*string && ' ' != *string) + string++; + + /* Nil-terminate name. */ + if (*string) + *(string++) = '\0'; + + /* Read past spaces. */ + while (*string && ' ' == *string) + string++; + + /* Read passed initial double-quote. */ + if (*string && '"' == *string) + string++; + + /* The rest is the value. */ + roff_setstr(r, name, string, 0); + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_nr(ROFF_ARGS) +{ + const char *key, *val; + struct reg *rg; + + key = &(*bufp)[pos]; + rg = r->regs->regs; + + /* Parse register request. */ + while ((*bufp)[pos] && ' ' != (*bufp)[pos]) + pos++; + + /* + * Set our nil terminator. Because this line is going to be + * ignored anyway, we can munge it as we please. + */ + if ((*bufp)[pos]) + (*bufp)[pos++] = '\0'; + + /* Skip whitespace to register token. */ + while ((*bufp)[pos] && ' ' == (*bufp)[pos]) + pos++; + + val = &(*bufp)[pos]; + + /* Process register token. */ + + if (0 == strcmp(key, "nS")) { + rg[(int)REG_nS].set = 1; + if ( ! roff_parse_nat(val, &rg[(int)REG_nS].v.u)) + rg[(int)REG_nS].v.u = 0; + } + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_TE(ROFF_ARGS) +{ + + if (NULL == r->tbl) + (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL); + else + tbl_end(r->tbl); + + r->tbl = NULL; + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_T_(ROFF_ARGS) +{ + + if (NULL == r->tbl) + (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL); + else + tbl_restart(ppos, ln, r->tbl); + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_TS(ROFF_ARGS) +{ + struct tbl_node *t; + + if (r->tbl) { + (*r->msg)(MANDOCERR_SCOPEBROKEN, r->data, ln, ppos, NULL); + tbl_end(r->tbl); + } + + t = tbl_alloc(ppos, ln, r->data, r->msg); + + if (r->last_tbl) + r->last_tbl->next = t; + else + r->first_tbl = r->last_tbl = t; + + r->tbl = r->last_tbl = t; + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_so(ROFF_ARGS) +{ + char *name; + + (*r->msg)(MANDOCERR_SO, r->data, ln, ppos, NULL); + + /* + * 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. + */ + + name = *bufp + pos; + if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) { + (*r->msg)(MANDOCERR_SOPATH, r->data, ln, pos, NULL); + return(ROFF_ERR); + } + + *offs = pos; + return(ROFF_SO); +} + +/* ARGSUSED */ +static enum rofferr +roff_userdef(ROFF_ARGS) +{ + const char *arg[9]; + char *cp, *n1, *n2; + int i; + + /* + * Collect pointers to macro argument strings + * and null-terminate them. + */ + cp = *bufp + pos; + for (i = 0; i < 9; i++) + arg[i] = '\0' == *cp ? "" : + mandoc_getarg(&cp, r->msg, r->data, ln, &pos); + + /* + * Expand macro arguments. + */ + *szp = 0; + n1 = cp = mandoc_strdup(r->current_string); + while (NULL != (cp = strstr(cp, "\\$"))) { + i = cp[2] - '1'; + if (0 > i || 8 < i) { + /* Not an argument invocation. */ + cp += 2; + continue; + } + + *szp = strlen(n1) - 3 + strlen(arg[i]) + 1; + n2 = mandoc_malloc(*szp); + + strlcpy(n2, n1, (size_t)(cp - n1 + 1)); + strlcat(n2, arg[i], *szp); + strlcat(n2, cp + 3, *szp); + + cp = n2 + (cp - n1); + free(n1); + n1 = n2; + } + + /* + * Replace the macro invocation + * by the expanded macro. + */ + free(*bufp); + *bufp = n1; + if (0 == *szp) + *szp = strlen(*bufp) + 1; + + return(*szp > 1 && '\n' == (*bufp)[(int)*szp - 2] ? + ROFF_REPARSE : ROFF_APPEND); +} + +/* + * Store *string into the user-defined string called *name. + * In multiline mode, append to an existing entry and append '\n'; + * else replace the existing entry, if there is one. + * To clear an existing entry, call with (*r, *name, NULL, 0). + */ +static void +roff_setstr(struct roff *r, const char *name, const char *string, + int multiline) +{ + struct roffstr *n; + char *c; + size_t oldch, newch; + + /* Search for an existing string with the same name. */ + n = r->first_string; + while (n && strcmp(name, n->name)) + n = n->next; + + if (NULL == n) { + /* Create a new string table entry. */ + n = mandoc_malloc(sizeof(struct roffstr)); + n->name = mandoc_strdup(name); + n->string = NULL; + n->next = r->first_string; + r->first_string = n; + } else if (0 == multiline) { + /* In multiline mode, append; else replace. */ + free(n->string); + n->string = NULL; + } + + if (NULL == string) + return; + + /* + * One additional byte for the '\n' in multiline mode, + * and one for the terminating '\0'. + */ + newch = strlen(string) + (multiline ? 2 : 1); + if (NULL == n->string) { + n->string = mandoc_malloc(newch); + *n->string = '\0'; + oldch = 0; + } else { + oldch = strlen(n->string); + n->string = mandoc_realloc(n->string, oldch + newch); + } + + /* Skip existing content in the destination buffer. */ + c = n->string + oldch; + + /* Append new content to the destination buffer. */ + while (*string) { + /* + * Rudimentary roff copy mode: + * Handle escaped backslashes. + */ + if ('\\' == *string && '\\' == *(string + 1)) + string++; + *c++ = *string++; + } + + /* Append terminating bytes. */ + if (multiline) + *c++ = '\n'; + *c = '\0'; +} + + +static const char * +roff_getstrn(const struct roff *r, const char *name, size_t len) +{ + const struct roffstr *n; + + n = r->first_string; + while (n && (strncmp(name, n->name, len) || '\0' != n->name[(int)len])) + n = n->next; + + return(n ? n->string : NULL); +} + + +static void +roff_freestr(struct roff *r) +{ + struct roffstr *n, *nn; + + for (n = r->first_string; n; n = nn) { + free(n->name); + free(n->string); + nn = n->next; + free(n); + } + + r->first_string = NULL; +} + +const struct tbl_span * +roff_span(const struct roff *r) +{ + + return(r->tbl ? tbl_span(r->tbl) : NULL); +} diff --git a/contrib/mdocml/roff.h b/contrib/mdocml/roff.h new file mode 100644 index 0000000000..141a469acb --- /dev/null +++ b/contrib/mdocml/roff.h @@ -0,0 +1,45 @@ +/* $Id: roff.h,v 1.22 2011/01/01 16:18:39 kristaps Exp $ */ +/* + * Copyright (c) 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. + */ +#ifndef ROFF_H +#define ROFF_H + +enum rofferr { + ROFF_CONT, /* continue processing line */ + ROFF_RERUN, /* re-run roff interpreter with offset */ + ROFF_APPEND, /* re-run main parser, appending next line */ + ROFF_REPARSE, /* re-run main parser on the result */ + ROFF_SO, /* include another file */ + ROFF_IGN, /* ignore current line */ + ROFF_TBL, /* a table row was successfully parsed */ + ROFF_ERR /* badness: puke and stop */ +}; + +__BEGIN_DECLS + +struct roff; + +void roff_free(struct roff *); +struct roff *roff_alloc(struct regset *, void *, mandocmsg); +void roff_reset(struct roff *); +enum rofferr roff_parseln(struct roff *, int, + char **, size_t *, int, int *); +void roff_endparse(struct roff *); +const struct tbl_span *roff_span(const struct roff *); + +__END_DECLS + +#endif /*!ROFF_H*/ diff --git a/contrib/mdocml/st.c b/contrib/mdocml/st.c new file mode 100644 index 0000000000..5c6798e585 --- /dev/null +++ b/contrib/mdocml/st.c @@ -0,0 +1,38 @@ +/* $Id: st.c,v 1.8 2010/06/19 20:46:28 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2st(const char *p) +{ + +#include "st.in" + + return(NULL); +} diff --git a/contrib/mdocml/st.in b/contrib/mdocml/st.in new file mode 100644 index 0000000000..2d7d005e61 --- /dev/null +++ b/contrib/mdocml/st.in @@ -0,0 +1,74 @@ +/* $Id: st.in,v 1.15 2010/07/31 23:52:58 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. + */ + +/* + * This file defines the .St macro arguments. If you add a new + * standard, make sure that the left-and side corresponds to the .St + * argument (like .St -p1003.1) and the right-hand side corresponds to + * the formatted output string. + * + * Be sure to escape strings. + * + * REMEMBER TO ADD NEW STANDARDS TO MDOC.7! + */ + +LINE("-p1003.1-88", "IEEE Std 1003.1-1988 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-90", "IEEE Std 1003.1-1990 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-2001", "IEEE Std 1003.1-2001 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-2004", "IEEE Std 1003.1-2004 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-2008", "IEEE Std 1003.1-2008 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1", "IEEE Std 1003.1 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1b", "IEEE Std 1003.1b (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1b-93", "IEEE Std 1003.1b-1993 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1c-95", "IEEE Std 1003.1c-1995 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1g-2000", "IEEE Std 1003.1g-2000 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1i-95", "IEEE Std 1003.1i-1995 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.2-92", "IEEE Std 1003.2-1992 (\\(lqPOSIX.2\\(rq)") +LINE("-p1003.2a-92", "IEEE Std 1003.2a-1992 (\\(lqPOSIX.2\\(rq)") +LINE("-p1387.2-95", "IEEE Std 1387.2-1995 (\\(lqPOSIX.7.2\\(rq)") +LINE("-p1003.2", "IEEE Std 1003.2 (\\(lqPOSIX.2\\(rq)") +LINE("-p1387.2", "IEEE Std 1387.2 (\\(lqPOSIX.7.2\\(rq)") +LINE("-isoC", "ISO/IEC 9899:1990 (\\(lqISO C90\\(rq)") +LINE("-isoC-90", "ISO/IEC 9899:1990 (\\(lqISO C90\\(rq)") +LINE("-isoC-amd1", "ISO/IEC 9899/AMD1:1995 (\\(lqISO C90\\(rq)") +LINE("-isoC-tcor1", "ISO/IEC 9899/TCOR1:1994 (\\(lqISO C90\\(rq)") +LINE("-isoC-tcor2", "ISO/IEC 9899/TCOR2:1995 (\\(lqISO C90\\(rq)") +LINE("-isoC-99", "ISO/IEC 9899:1999 (\\(lqISO C99\\(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 C\\(rq)") +LINE("-ansiC-89", "ANSI X3.159-1989 (\\(lqANSI C\\(rq)") +LINE("-ansiC-99", "ANSI/ISO/IEC 9899-1999 (\\(lqANSI C99\\(rq)") +LINE("-ieee754", "IEEE Std 754-1985") +LINE("-iso8802-3", "ISO 8802-3: 1989") +LINE("-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.2 (\\(lqXPG4.2\\(rq)") +LINE("-xpg4.3", "X/Open Portability Guide Issue 4.3 (\\(lqXPG4.3\\(rq)") +LINE("-xbd5", "X/Open System Interface Definitions Issue 5 (\\(lqXBD5\\(rq)") +LINE("-xcu5", "X/Open Commands and Utilities Issue 5 (\\(lqXCU5\\(rq)") +LINE("-xsh5", "X/Open System Interfaces and Headers Issue 5 (\\(lqXSH5\\(rq)") +LINE("-xns5", "X/Open Networking Services Issue 5 (\\(lqXNS5\\(rq)") +LINE("-xns5.2", "X/Open Networking Services Issue 5.2 (\\(lqXNS5.2\\(rq)") +LINE("-xns5.2d2.0", "X/Open Networking Services Issue 5.2 Draft 2.0 (\\(lqXNS5.2D2.0\\(rq)") +LINE("-xcurses4.2", "X/Open Curses Issue 4 Version 2 (\\(lqXCURSES4.2\\(rq)") +LINE("-susv2", "Version 2 of the Single UNIX Specification") +LINE("-susv3", "Version 3 of the Single UNIX Specification") +LINE("-svid4", "System V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)") diff --git a/contrib/mdocml/style.css b/contrib/mdocml/style.css new file mode 100644 index 0000000000..4fb1b5d802 --- /dev/null +++ b/contrib/mdocml/style.css @@ -0,0 +1,146 @@ +/* $Id: style.css,v 1.20 2010/12/24 22:51:13 kristaps Exp $ */ + +html { max-width: 800px; } +body { color: #333333; + font-size: 0.93em; + font-family: Times, serif; } + +/* Preamble structure. */ + +table.foot { width: 100%; + font-size: 0.8em; + margin-top: 1em; + border-top: 1px dotted #dddddd; + color: #999999; } /* Document footer. */ +td.foot-date { width: 50%; } /* Document footer: date. */ +td.foot-os { width: 50%; text-align: right; } /* Document footer: OS/source. */ +table.head { width: 100%; + font-size: 0.8em; + margin-bottom: 1em; + border-bottom: 1px dotted #dddddd; + color: #999999; } /* Document header. */ +td.head-ltitle { width: 10%; } /* Document header: left-title. */ +td.head-vol { width: 80%; text-align: center; } /* Document header: volume. */ +td.head-rtitle { width: 10%; text-align: right; } /* Document header: right-title. */ + +/* Sections. */ + +h1 { margin-bottom: 0px; color: #000000; font-size: 0.93em; margin-left: -4ex; } /* Section header (Sh, SH). */ +h2 { margin-bottom: 0px; color: #000000; font-size: 0.93em; margin-left: -2ex; } /* Sub-section header (Ss, SS). */ +div.section { margin-bottom: 2ex; margin-left: 4ex; } /* Sections (Sh, SH). */ +div.subsection { } /* Sub-sections (Ss, SS). */ +table.synopsis { } /* SYNOPSIS section table. */ +table.synopsis td { vertical-align: top; } /* SYNOPSIS section table. */ + +/* Vertical spacing. */ + +p { } /* Paragraph: Pp, Lp. */ +blockquote { margin-top: 0px; margin-bottom: 0px; } +table { margin-top: 0px; margin-bottom: 0px; } + +/* General font modes. */ + +.lit { font-family: monospace; font-style: normal; font-weight: normal; } /* Literal: Dl, Li, Bf -literal, Bl -literal, Bl -unfilled. */ +.italic { font-style: italic; font-weight: normal; } /* Italic: BI, IB, I, (implicit). */ +.emph { font-style: italic; font-weight: normal; } /* Emphasis: Em, Bl -emphasis. */ +.bold { font-style: normal; font-weight: bold; } /* Bold: SB, BI, IB, BR, RB, B, (implicit). */ +.symb { font-style: normal; font-weight: bold; } /* Symbolic: Sy, Ms, Bf -symbolic. */ +.roman { font-style: normal; font-weight: normal; } /* Roman: (implicit). */ +.small { font-style: normal; font-weight: normal; font-size: smaller; } /* Small: SB, SM. */ + +/* Block modes. */ + +.display { background-color: #EEEEEE; + margin: 3px; + padding: 3px; + border: 1px solid #339999; } /* Top of all Bd, D1, Dl. */ +.list { } /* Top of all Bl. */ + +/* Context-specific modes. */ + +i.addr { font-weight: normal; } /* Address (Ad). */ +i.arg { font-weight: normal; } /* Command argument (Ar). */ +span.author { } /* Author name (An). */ +b.cmd { font-style: normal; } /* Command (Cm). */ +b.config { font-style: normal; } /* Config statement (Cd). */ +span.define { } /* Defines (Dv). */ +span.desc { } /* Nd. After em-dash. */ +b.diag { font-style: normal; } /* Diagnostic (Bl -diag). */ +span.env { } /* Environment variables (Ev). */ +span.errno { } /* Error string (Er). */ +i.farg { font-weight: normal; } /* Function argument (Fa, Fn). */ +i.file { font-weight: normal; } /* File (Pa). */ +b.flag { font-style: normal; } /* Flag (Fl, Cm). */ +b.fname { font-style: normal; } /* Function name (Fa, Fn, Rv). */ +i.ftype { font-weight: normal; } /* Function types (Ft, Fn). */ +b.includes { font-style: normal; } /* Header includes (In). */ +span.lib { } /* Library (Lb). */ +b.macro { font-style: normal; } /* Macro-ish thing (Fd). */ +b.name { color: blue; font-style: normal; } /* Name of utility (Nm). */ +span.opt { } /* Options (Op, Oo/Oc). */ +span.ref { } /* Citations (Rs). */ +span.ref-auth { } /* Reference author (%A). */ +i.ref-book { font-weight: normal; } /* Reference book (%B). */ +span.ref-city { } /* Reference city (%C). */ +span.ref-date { } /* Reference date (%D). */ +i.ref-issue { font-weight: normal; } /* Reference issuer/publisher (%I). */ +i.ref-jrnl { font-weight: normal; } /* Reference journal (%J). */ +span.ref-num { } /* Reference number (%N). */ +span.ref-opt { } /* Reference optionals (%O). */ +span.ref-page { } /* Reference page (%P). */ +span.ref-corp { } /* Reference corporate/foreign author (%Q). */ +span.ref-rep { } /* Reference report (%R). */ +span.ref-title { text-decoration: underline; } /* Reference title (%T). */ +span.ref-vol { } /* Reference volume (%V). */ +span.type { font-style: italic; font-weight: normal; } /* Variable types (Vt). */ +span.unix { } /* Unices (Ux, Ox, Nx, Fx, Bx, Bsx, Dx). */ +b.utility { font-style: normal; } /* Name of utility (Ex). */ +b.var { font-style: normal; } /* Variables (Rv). */ + +a.link-ext { background: transparent url(external.png) center right no-repeat; padding-right: 12px; }/* Off-site link (Lk). */ +a.link-includes { } /* Include-file link (In). */ +a.link-mail { background: transparent url(external.png) center right no-repeat; padding-right: 12px; }/* Mailto links (Mt). */ +a.link-man { } /* Manual links (Xr). */ +a.link-ref { } /* Reference section links (%Q). */ +a.link-sec { text-decoration: none; border-bottom: 1px dotted #339999; } /* Section links (Sx). */ + +/* Formatting for lists. */ + +dl.list-diag { } +dt.list-diag { } +dd.list-diag { } + +dl.list-hang { } +dt.list-hang { } +dd.list-hang { } + +dl.list-inset { } +dt.list-inset { } +dd.list-inset { } + +dl.list-ohang { } +dt.list-ohang { } +dd.list-ohang { margin-left: 0em; } + +dl.list-tag { } +dt.list-tag { } +dd.list-tag { } + +table.list-col { } +tr.list-col { } +td.list-col { } + +ul.list-bul { list-style-type: disc; padding-left: 1em; } +li.list-bul { } + +ul.list-dash { list-style-type: none; padding-left: 0em; } +li.list-dash:before { content: "\2014 "; } + +ul.list-hyph { list-style-type: none; padding-left: 0em; } +li.list-hyph:before { content: "\2013 "; } + +ul.list-item { list-style-type: none; padding-left: 0em; } +li.list-item { } + +ol.list-enum { padding-left: 2em; } +li.list-enum { } diff --git a/contrib/mdocml/tbl.7 b/contrib/mdocml/tbl.7 new file mode 100644 index 0000000000..3c7f0c6100 --- /dev/null +++ b/contrib/mdocml/tbl.7 @@ -0,0 +1,322 @@ +.\" $Id: tbl.7,v 1.4 2011/01/07 14:59:52 kristaps Exp $ +.\" +.\" Copyright (c) 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. +.\" +.Dd $Mdocdate: January 7 2011 $ +.Dt TBL 7 +.Os +.Sh NAME +.Nm tbl +.Nd tbl language reference for mandoc +.Sh DESCRIPTION +The +.Nm tbl +language is a table-formatting language. +It is used within +.Xr mdoc 7 +and +.Xr man 7 +.Ux +manual pages. +This manual describes the subset of the +.Nm +language accepted by the +.Xr mandoc 1 +utility. +.Pp +Tables within +.Xr mdoc 7 +or +.Xr man 7 +are enclosed by the +.Sq TS +and +.Sq TE +macro tags, whose precise syntax is documented in +.Xr roff 7 . +Tables consist of a series of options on a single line, followed by the +table layout, followed by data. +.Pp +For example, the following creates a boxed table with digits centered in +the cells. +.Bd -literal -offset indent +\&.TS +tab(:) box; +c5 c5 c5. +1:2:3 +4:5:6 +\&.TE +.Ed +.Pp +When formatted, the following output is produced: +.Bd -filled -offset indent -compact +.TS +tab(:) box; +c5 c5 c5. +1:2:3 +4:5:6 +.TE +.Ed +.Sh TABLE STRUCTURE +Tables are enclosed by the +.Sq TS +and +.Sq TE +.Xr roff 7 +macros. +A table consists of an optional single line of table +.Sx Options +terminated by a semicolon, followed by one or more lines of +.Sx Layout +specifications terminated by a period, then +.Sx Data . +All input must be 7-bit ASCII. +Example: +.Bd -literal -offset indent +\&.TS +box tab(:); +c | c +| c | c. +1:2 +3:4 +\&.TE +.Ed +.Pp +Table data is +.Em pre-processed , +that is, data rows are parsed then inserted into the underlying stream +of input data. +This allows data rows to be interspersed by arbitrary +.Xr roff 7 , +.Xr mdoc 7 , +and +.Xr man 7 +macros such as +.Bd -literal -offset indent +\&.TS +tab(:); +c c c. +1:2:3 +\&.Ao +3:2:1 +\&.Ac +\&.TE +.Ed +.Pp +in the case of +.Xr mdoc 7 +or +.Bd -literal -offset indent +\&.TS +tab(:); +c c c. +\&.ds ab 2 +1:\e*(ab:3 +\&.I +3:2:1 +\&.TE +.Ed +.Pp +in the case of +.Xr man 7 . +.Ss Options +The first line of a table consists of space-separated option keys and +modifiers terminated by a semicolon. +If the first line does not have a terminating semicolon, it is assumed +that no options are specified and instead a +.Sx Layout +is processed. +Some options accept arguments enclosed by parenthesis. +The following case-insensitive options are available: +.Bl -tag -width Ds +.It Cm center +This option is not supported by +.Xr mandoc 1 . +This may also be invoked with +.Cm centre . +.It Cm delim +Accepts a two-character argument. +This option is not supported by +.Xr mandoc 1 . +.It Cm expand +This option is not supported by +.Xr mandoc 1 . +.It Cm box +Draw a single-line box around the table. +This may also be invoked with +.Cm frame . +.It Cm doublebox +Draw a double-line box around the table. +This may also be invoked with +.Cm doubleframe . +.It Cm allbox +This option is not supported by +.Xr mandoc 1 . +.It Cm tab +Accepts a single-character argument. +This character is used as a delimiter between data cells, which otherwise +defaults to the tab character. +.It Cm linesize +Accepts a natural number (all digits). +This option is not supported by +.Xr mandoc 1 . +.It Cm nokeep +This option is not supported by +.Xr mandoc 1 . +.It Cm decimalpoint +Accepts a single-character argument. +This character will be used as the decimal point with the +.Cm n +layout key. +This option is not supported by +.Xr mandoc 1 . +.It Cm nospaces +This option is not supported by +.Xr mandoc 1 . +.El +.Ss Layout +The table layout follows +.Sx Options +or a +.Sq \&T& +macro invocation. +Layout specifies how data rows are displayed on output. +Each layout line corresponds to a line of data; the last layout line +applies to all remaining data lines. +Layout lines may also be separated by a comma. +Each layout cell consists of one of the following case-insensitive keys: +.Bl -tag -width Ds +.It Cm c +Centre a literal string within its column. +.It Cm r +Right-justify a literal string within its column. +.It Cm l +Left-justify a literal string within its column. +.It Cm n +Justify a number around its decimal point. +If the decimal point is not found on the number, it's assumed to trail +the number. +.It Cm s +This option is not supported by +.Xr mandoc 1 . +.It Cm a +This option is not supported by +.Xr mandoc 1 . +.It Cm ^ +This option is not supported by +.Xr mandoc 1 . +.It Cm \- +Replace the data cell (its contents will be lost) with a single +horizontal line. +This may also be invoked with +.Cm _ . +.It Cm = +Replace the data cell (its contents will be lost) with a double +horizontal line. +.It Cm \(ba +Emit a vertical bar instead of data. +.It Cm \(ba\(ba +Emit a double-vertical bar instead of data. +.El +.Pp +For example, the following layout specifies a centre-justified column of +minimum width 10, followed by vertical bar, followed by a left-justified +column of minimum width 10, another vertical bar, then a column +justified about the decimal point in numbers: +.Pp +.Dl c10 | l10 | n +.Pp +Keys may be followed by a set of modifiers. +A modifier is either a modifier key or a natural number for specifying +spacing. +The following case-insensitive modifier keys are available: +.Cm z , +.Cm u , +.Cm e , +.Cm t , +.Cm d , +.Cm f , +.Cm b , +.Cm i , +.Cm b , +and +.Cm i . +All of these are ignored by +.Xr mandoc 1 . +.Ss Data +The data section follows the last layout row. +By default, cells in a data section are delimited by a tab. +This behaviour may be changed with the +.Cm tab +option. +If +.Cm _ +or +.Cm = +is specified, a single or double line, respectively, is drawn across the +data field. +If +.Cm \e- +or +.Cm \e= +is specified, a line is drawn within the data field (i.e. terminating +within the cell and not draw to the border). +If the last cell of a line is +.Cm T{ , +all subsequent lines are included as part of the cell until +.Cm T} +is specified as its own data cell. +It may then be followed by a tab +.Pq or as designated by Cm tab +or an end-of-line to terminate the row. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other +.Nm +implementations, at this time limited to GNU tbl. +.Pp +.Bl -dash -compact +.It +In GNU tbl, comments and macros are disallowed prior to the data block +of a table. +The +.Xr mandoc 1 +implementation allows them. +.El +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 +.Rs +.%A M. E. Lesk +.%T Tbl\(emA Program to Format Tables +.%D June 11, 1976 +.Re +.Sh HISTORY +The tbl utility, a preprocessor for troff, was originally written by M. +E. Lesk at Bell Labs in 1975. +The GNU reimplementation of tbl, part of the groff package, was released +in 1990 by James Clark. +A standalone tbl implementation was written by Kristaps Dzonsons in +2010. +This formed the basis of the implementation that is part of the +.Xr mandoc 1 +utility. +.Sh AUTHORS +This partial +.Nm +reference was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . diff --git a/contrib/mdocml/tbl.c b/contrib/mdocml/tbl.c new file mode 100644 index 0000000000..ba591ceb6e --- /dev/null +++ b/contrib/mdocml/tbl.c @@ -0,0 +1,159 @@ +/* $Id: tbl.c,v 1.21 2011/01/04 15:02:00 kristaps 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 +#include +#include +#include +#include + +#include "mandoc.h" +#include "roff.h" +#include "libmandoc.h" +#include "libroff.h" + +enum rofferr +tbl_read(struct tbl_node *tbl, int ln, const char *p, int offs) +{ + int len; + const char *cp; + + cp = &p[offs]; + len = (int)strlen(cp); + + /* + * If we're in the options section and we don't have a + * terminating semicolon, assume we've moved directly into the + * layout section. No need to report a warning: this is, + * apparently, standard behaviour. + */ + + if (TBL_PART_OPTS == tbl->part && len) + if (';' != cp[len - 1]) + tbl->part = TBL_PART_LAYOUT; + + /* Now process each logical section of the table. */ + + switch (tbl->part) { + case (TBL_PART_OPTS): + return(tbl_option(tbl, ln, p) ? ROFF_IGN : ROFF_ERR); + case (TBL_PART_LAYOUT): + return(tbl_layout(tbl, ln, p) ? ROFF_IGN : ROFF_ERR); + case (TBL_PART_CDATA): + return(tbl_cdata(tbl, ln, p) ? ROFF_TBL : ROFF_IGN); + default: + break; + } + + /* + * This only returns zero if the line is empty, so we ignore it + * and continue on. + */ + return(tbl_data(tbl, ln, p) ? ROFF_TBL : ROFF_IGN); +} + +struct tbl_node * +tbl_alloc(int pos, int line, void *data, const mandocmsg msg) +{ + struct tbl_node *p; + + p = mandoc_calloc(1, sizeof(struct tbl_node)); + p->line = line; + p->pos = pos; + p->data = data; + p->msg = msg; + p->part = TBL_PART_OPTS; + p->opts.tab = '\t'; + p->opts.linesize = 12; + p->opts.decimal = '.'; + return(p); +} + +void +tbl_free(struct tbl_node *p) +{ + struct tbl_row *rp; + struct tbl_cell *cp; + struct tbl_span *sp; + struct tbl_dat *dp; + struct tbl_head *hp; + + while (NULL != (rp = p->first_row)) { + p->first_row = rp->next; + while (rp->first) { + cp = rp->first; + rp->first = cp->next; + free(cp); + } + free(rp); + } + + while (NULL != (sp = p->first_span)) { + p->first_span = sp->next; + while (sp->first) { + dp = sp->first; + sp->first = dp->next; + if (dp->string) + free(dp->string); + free(dp); + } + free(sp); + } + + while (NULL != (hp = p->first_head)) { + p->first_head = hp->next; + free(hp); + } + + free(p); +} + +void +tbl_restart(int line, int pos, struct tbl_node *tbl) +{ + if (TBL_PART_CDATA == tbl->part) + TBL_MSG(tbl, MANDOCERR_TBLBLOCK, tbl->line, tbl->pos); + + tbl->part = TBL_PART_LAYOUT; + tbl->line = line; + tbl->pos = pos; + + if (NULL == tbl->first_span || NULL == tbl->first_span->first) + TBL_MSG(tbl, MANDOCERR_TBLNODATA, tbl->line, tbl->pos); +} + +const struct tbl_span * +tbl_span(const struct tbl_node *tbl) +{ + + assert(tbl); + return(tbl->last_span); +} + +void +tbl_end(struct tbl_node *tbl) +{ + + if (NULL == tbl->first_span || NULL == tbl->first_span->first) + TBL_MSG(tbl, MANDOCERR_TBLNODATA, tbl->line, tbl->pos); + + if (tbl->last_span) + tbl->last_span->flags |= TBL_SPAN_LAST; + + if (TBL_PART_CDATA == tbl->part) + TBL_MSG(tbl, MANDOCERR_TBLBLOCK, tbl->line, tbl->pos); +} + diff --git a/contrib/mdocml/tbl_data.c b/contrib/mdocml/tbl_data.c new file mode 100644 index 0000000000..f53e109952 --- /dev/null +++ b/contrib/mdocml/tbl_data.c @@ -0,0 +1,212 @@ +/* $Id: tbl_data.c,v 1.14 2011/01/07 14:59:52 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +static int data(struct tbl_node *, struct tbl_span *, + int, const char *, int *); + +static int +data(struct tbl_node *tbl, struct tbl_span *dp, + int ln, const char *p, int *pos) +{ + struct tbl_dat *dat; + struct tbl_cell *cp; + int sv; + + cp = NULL; + if (dp->last && dp->last->layout) + cp = dp->last->layout->next; + else if (NULL == dp->last) + cp = dp->layout->first; + + /* + * Skip over spanners and vertical lines to data formats, since + * we want to match data with data layout cells in the header. + */ + + while (cp && (TBL_CELL_VERT == cp->pos || + TBL_CELL_DVERT == cp->pos || + TBL_CELL_SPAN == cp->pos)) + cp = cp->next; + + dat = mandoc_calloc(1, sizeof(struct tbl_dat)); + dat->layout = cp; + dat->pos = TBL_DATA_NONE; + + if (NULL == dat->layout) + TBL_MSG(tbl, MANDOCERR_TBLEXTRADAT, ln, *pos); + + if (dp->last) { + dp->last->next = dat; + dp->last = dat; + } else + dp->last = dp->first = dat; + + sv = *pos; + while (p[*pos] && p[*pos] != tbl->opts.tab) + (*pos)++; + + /* + * Check for a continued-data scope opening. This consists of a + * trailing `T{' at the end of the line. Subsequent lines, + * until a standalone `T}', are included in our cell. + */ + + if (*pos - sv == 2 && 'T' == p[sv] && '{' == p[sv + 1]) { + tbl->part = TBL_PART_CDATA; + return(0); + } + + dat->string = mandoc_malloc(*pos - sv + 1); + memcpy(dat->string, &p[sv], *pos - sv); + dat->string[*pos - sv] = '\0'; + + if (p[*pos]) + (*pos)++; + + if ( ! strcmp(dat->string, "_")) + dat->pos = TBL_DATA_HORIZ; + else if ( ! strcmp(dat->string, "=")) + dat->pos = TBL_DATA_DHORIZ; + else if ( ! strcmp(dat->string, "\\_")) + dat->pos = TBL_DATA_NHORIZ; + else if ( ! strcmp(dat->string, "\\=")) + dat->pos = TBL_DATA_NDHORIZ; + else + dat->pos = TBL_DATA_DATA; + + if (NULL == dat->layout) + return(1); + + if (TBL_CELL_HORIZ == dat->layout->pos || + TBL_CELL_DHORIZ == dat->layout->pos) + if (TBL_DATA_DATA == dat->pos && '\0' != *dat->string) + TBL_MSG(tbl, MANDOCERR_TBLIGNDATA, ln, sv); + + return(1); +} + +/* ARGSUSED */ +int +tbl_cdata(struct tbl_node *tbl, int ln, const char *p) +{ + struct tbl_dat *dat; + size_t sz; + int pos; + + pos = 0; + + dat = tbl->last_span->last; + dat->pos = TBL_DATA_DATA; + + if (p[pos] == 'T' && p[pos + 1] == '}') { + pos += 2; + if (p[pos] == tbl->opts.tab) { + tbl->part = TBL_PART_DATA; + pos++; + return(data(tbl, tbl->last_span, ln, p, &pos)); + } else if ('\0' == p[pos]) { + tbl->part = TBL_PART_DATA; + return(1); + } + + /* Fallthrough: T} is part of a word. */ + } + + if (dat->string) { + sz = strlen(p) + strlen(dat->string) + 2; + dat->string = mandoc_realloc(dat->string, sz); + strlcat(dat->string, " ", sz); + strlcat(dat->string, p, sz); + } else + dat->string = mandoc_strdup(p); + + return(0); +} + +int +tbl_data(struct tbl_node *tbl, int ln, const char *p) +{ + struct tbl_span *dp; + struct tbl_row *rp; + int pos; + + pos = 0; + + if ('\0' == p[pos]) { + TBL_MSG(tbl, MANDOCERR_TBL, ln, pos); + return(0); + } + + /* + * Choose a layout row: take the one following the last parsed + * span's. If that doesn't exist, use the last parsed span's. + * If there's no last parsed span, use the first row. This can + * be NULL! + */ + + if (tbl->last_span) { + assert(tbl->last_span->layout); + rp = tbl->last_span->layout->next; + if (NULL == rp) + rp = tbl->last_span->layout; + } else + rp = tbl->first_row; + + dp = mandoc_calloc(1, sizeof(struct tbl_span)); + dp->tbl = &tbl->opts; + dp->layout = rp; + dp->head = tbl->first_head; + + if (tbl->last_span) { + tbl->last_span->next = dp; + tbl->last_span = dp; + } else { + tbl->last_span = tbl->first_span = dp; + dp->flags |= TBL_SPAN_FIRST; + } + + if ( ! strcmp(p, "_")) { + dp->pos = TBL_SPAN_HORIZ; + return(1); + } else if ( ! strcmp(p, "=")) { + dp->pos = TBL_SPAN_DHORIZ; + return(1); + } + + dp->pos = TBL_SPAN_DATA; + + /* This returns 0 when TBL_PART_CDATA is entered. */ + + while ('\0' != p[pos]) + if ( ! data(tbl, dp, ln, p, &pos)) + return(0); + + return(1); +} diff --git a/contrib/mdocml/tbl_html.c b/contrib/mdocml/tbl_html.c new file mode 100644 index 0000000000..7594dfc961 --- /dev/null +++ b/contrib/mdocml/tbl_html.c @@ -0,0 +1,126 @@ +/* $Id: tbl_html.c,v 1.5 2011/01/06 12:31:39 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "html.h" + +static size_t html_tbl_len(size_t, void *); +static size_t html_tbl_strlen(const char *, void *); + +/* ARGSUSED */ +static size_t +html_tbl_len(size_t sz, void *arg) +{ + + return(sz); +} + +/* ARGSUSED */ +static size_t +html_tbl_strlen(const char *p, void *arg) +{ + + return(strlen(p)); +} + +void +print_tbl(struct html *h, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + const struct tbl_dat *dp; + struct tag *tt; + struct htmlpair tag; + struct roffsu su; + struct roffcol *col; + + /* Inhibit printing of spaces: we do padding ourselves. */ + + h->flags |= HTML_NONOSPACE; + h->flags |= HTML_NOSPACE; + + /* First pass: calculate widths. */ + + if (TBL_SPAN_FIRST & sp->flags) { + h->tbl.len = html_tbl_len; + h->tbl.slen = html_tbl_strlen; + tblcalc(&h->tbl, sp); + } + + switch (sp->pos) { + case (TBL_SPAN_HORIZ): + /* FALLTHROUGH */ + case (TBL_SPAN_DHORIZ): + break; + default: + PAIR_CLASS_INIT(&tag, "tbl"); + print_otag(h, TAG_TABLE, 1, &tag); + print_otag(h, TAG_TR, 0, NULL); + + /* Iterate over template headers. */ + + dp = sp->first; + for (hp = sp->head; hp; hp = hp->next) { + switch (hp->pos) { + case (TBL_HEAD_VERT): + /* FALLTHROUGH */ + case (TBL_HEAD_DVERT): + continue; + case (TBL_HEAD_DATA): + break; + } + + /* + * For the time being, use the simplest possible + * table styling: setting the widths of data + * columns. + */ + + col = &h->tbl.cols[hp->ident]; + SCALE_HS_INIT(&su, col->width); + bufcat_su(h, "width", &su); + PAIR_STYLE_INIT(&tag, h); + tt = print_otag(h, TAG_TD, 1, &tag); + + if (dp && dp->string) + print_text(h, dp->string); + if (dp) + dp = dp->next; + + print_tagq(h, tt); + } + break; + } + + h->flags &= ~HTML_NONOSPACE; + + /* Close out column specifiers on the last span. */ + + if (TBL_SPAN_LAST & sp->flags) { + assert(h->tbl.cols); + free(h->tbl.cols); + h->tbl.cols = NULL; + } +} diff --git a/contrib/mdocml/tbl_layout.c b/contrib/mdocml/tbl_layout.c new file mode 100644 index 0000000000..fc03086bec --- /dev/null +++ b/contrib/mdocml/tbl_layout.c @@ -0,0 +1,408 @@ +/* $Id: tbl_layout.c,v 1.12 2011/01/07 14:59:52 kristaps 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 +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +struct tbl_phrase { + char name; + enum tbl_cellt key; +}; + +/* + * FIXME: we can make this parse a lot nicer by, when an error is + * encountered in a layout key, bailing to the next key (i.e. to the + * next whitespace then continuing). + */ + +#define KEYS_MAX 11 + +static const struct tbl_phrase keys[KEYS_MAX] = { + { 'c', TBL_CELL_CENTRE }, + { 'r', TBL_CELL_RIGHT }, + { 'l', TBL_CELL_LEFT }, + { 'n', TBL_CELL_NUMBER }, + { 's', TBL_CELL_SPAN }, + { 'a', TBL_CELL_LONG }, + { '^', TBL_CELL_DOWN }, + { '-', TBL_CELL_HORIZ }, + { '_', TBL_CELL_HORIZ }, + { '=', TBL_CELL_DHORIZ }, + { '|', TBL_CELL_VERT } +}; + +static int mods(struct tbl_node *, struct tbl_cell *, + int, const char *, int *); +static int cell(struct tbl_node *, struct tbl_row *, + int, const char *, int *); +static void row(struct tbl_node *, int, const char *, int *); +static struct tbl_cell *cell_alloc(struct tbl_node *, + struct tbl_row *, enum tbl_cellt); +static void head_adjust(const struct tbl_cell *, + struct tbl_head *); + +static int +mods(struct tbl_node *tbl, struct tbl_cell *cp, + int ln, const char *p, int *pos) +{ + char buf[5]; + int i; + +mod: + /* + * XXX: since, at least for now, modifiers are non-conflicting + * (are separable by value, regardless of position), we let + * modifiers come in any order. The existing tbl doesn't let + * this happen. + */ + switch (p[*pos]) { + case ('\0'): + /* FALLTHROUGH */ + case (' '): + /* FALLTHROUGH */ + case ('\t'): + /* FALLTHROUGH */ + case (','): + /* FALLTHROUGH */ + case ('.'): + return(1); + default: + break; + } + + /* Throw away parenthesised expression. */ + + if ('(' == p[*pos]) { + (*pos)++; + while (p[*pos] && ')' != p[*pos]) + (*pos)++; + if (')' == p[*pos]) { + (*pos)++; + goto mod; + } + TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos); + return(0); + } + + /* Parse numerical spacing from modifier string. */ + + if (isdigit((unsigned char)p[*pos])) { + for (i = 0; i < 4; i++) { + if ( ! isdigit((unsigned char)p[*pos + i])) + break; + buf[i] = p[*pos + i]; + } + buf[i] = '\0'; + + /* No greater than 4 digits. */ + + if (4 == i) { + TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos); + return(0); + } + + *pos += i; + cp->spacing = atoi(buf); + + goto mod; + /* NOTREACHED */ + } + + /* TODO: GNU has many more extensions. */ + + switch (tolower(p[(*pos)++])) { + case ('z'): + cp->flags |= TBL_CELL_WIGN; + goto mod; + case ('u'): + cp->flags |= TBL_CELL_UP; + goto mod; + case ('e'): + cp->flags |= TBL_CELL_EQUAL; + goto mod; + case ('t'): + cp->flags |= TBL_CELL_TALIGN; + goto mod; + case ('d'): + cp->flags |= TBL_CELL_BALIGN; + goto mod; + case ('w'): /* XXX for now, ignore minimal column width */ + goto mod; + case ('f'): + break; + case ('b'): + /* FALLTHROUGH */ + case ('i'): + (*pos)--; + break; + default: + TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos - 1); + return(0); + } + + switch (tolower(p[(*pos)++])) { + case ('b'): + cp->flags |= TBL_CELL_BOLD; + goto mod; + case ('i'): + cp->flags |= TBL_CELL_ITALIC; + goto mod; + default: + break; + } + + TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos - 1); + return(0); +} + +static int +cell(struct tbl_node *tbl, struct tbl_row *rp, + int ln, const char *p, int *pos) +{ + int i; + enum tbl_cellt c; + + /* Parse the column position (`r', `R', `|', ...). */ + + for (i = 0; i < KEYS_MAX; i++) + if (tolower(p[*pos]) == keys[i].name) + break; + + if (KEYS_MAX == i) { + TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos); + return(0); + } + + c = keys[i].key; + + /* + * If a span cell is found first, raise a warning and abort the + * parse. FIXME: recover from this somehow? + */ + + if (NULL == rp->first && TBL_CELL_SPAN == c) { + TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos); + return(0); + } + + (*pos)++; + + /* Extra check for the double-vertical. */ + + if (TBL_CELL_VERT == c && '|' == p[*pos]) { + (*pos)++; + c = TBL_CELL_DVERT; + } + + /* Disallow adjacent spacers. */ + + if (rp->last && (TBL_CELL_VERT == c || TBL_CELL_DVERT == c) && + (TBL_CELL_VERT == rp->last->pos || + TBL_CELL_DVERT == rp->last->pos)) { + TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos - 1); + return(0); + } + + /* Allocate cell then parse its modifiers. */ + + return(mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos)); +} + + +static void +row(struct tbl_node *tbl, int ln, const char *p, int *pos) +{ + struct tbl_row *rp; + +row: /* + * EBNF describing this section: + * + * row ::= row_list [:space:]* [.]?[\n] + * row_list ::= [:space:]* row_elem row_tail + * row_tail ::= [:space:]*[,] row_list | + * epsilon + * row_elem ::= [\t\ ]*[:alpha:]+ + */ + + rp = mandoc_calloc(1, sizeof(struct tbl_row)); + if (tbl->last_row) { + tbl->last_row->next = rp; + tbl->last_row = rp; + } else + tbl->last_row = tbl->first_row = rp; + +cell: + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* Safely exit layout context. */ + + if ('.' == p[*pos]) { + tbl->part = TBL_PART_DATA; + if (NULL == tbl->first_row) + TBL_MSG(tbl, MANDOCERR_TBLNOLAYOUT, ln, *pos); + (*pos)++; + return; + } + + /* End (and possibly restart) a row. */ + + if (',' == p[*pos]) { + (*pos)++; + goto row; + } else if ('\0' == p[*pos]) + return; + + if ( ! cell(tbl, rp, ln, p, pos)) + return; + + goto cell; + /* NOTREACHED */ +} + +int +tbl_layout(struct tbl_node *tbl, int ln, const char *p) +{ + int pos; + + pos = 0; + row(tbl, ln, p, &pos); + + /* Always succeed. */ + return(1); +} + +static struct tbl_cell * +cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos) +{ + struct tbl_cell *p, *pp; + struct tbl_head *h, *hp; + + p = mandoc_calloc(1, sizeof(struct tbl_cell)); + + if (NULL != (pp = rp->last)) { + rp->last->next = p; + rp->last = p; + } else + rp->last = rp->first = p; + + p->pos = pos; + + /* + * This is a little bit complicated. Here we determine the + * header the corresponds to a cell. We add headers dynamically + * when need be or re-use them, otherwise. As an example, given + * the following: + * + * 1 c || l + * 2 | c | l + * 3 l l + * 3 || c | l |. + * + * We first add the new headers (as there are none) in (1); then + * in (2) we insert the first spanner (as it doesn't match up + * with the header); then we re-use the prior data headers, + * skipping over the spanners; then we re-use everything and add + * a last spanner. Note that VERT headers are made into DVERT + * ones. + */ + + h = pp ? pp->head->next : tbl->first_head; + + if (h) { + /* Re-use data header. */ + if (TBL_HEAD_DATA == h->pos && + (TBL_CELL_VERT != p->pos && + TBL_CELL_DVERT != p->pos)) { + p->head = h; + return(p); + } + + /* Re-use spanner header. */ + if (TBL_HEAD_DATA != h->pos && + (TBL_CELL_VERT == p->pos || + TBL_CELL_DVERT == p->pos)) { + head_adjust(p, h); + p->head = h; + return(p); + } + + /* Right-shift headers with a new spanner. */ + if (TBL_HEAD_DATA == h->pos && + (TBL_CELL_VERT == p->pos || + TBL_CELL_DVERT == p->pos)) { + hp = mandoc_calloc(1, sizeof(struct tbl_head)); + hp->ident = tbl->opts.cols++; + hp->prev = h->prev; + if (h->prev) + h->prev->next = hp; + if (h == tbl->first_head) + tbl->first_head = hp; + h->prev = hp; + hp->next = h; + head_adjust(p, hp); + p->head = hp; + return(p); + } + + if (NULL != (h = h->next)) { + head_adjust(p, h); + p->head = h; + return(p); + } + + /* Fall through to default case... */ + } + + hp = mandoc_calloc(1, sizeof(struct tbl_head)); + hp->ident = tbl->opts.cols++; + + if (tbl->last_head) { + hp->prev = tbl->last_head; + tbl->last_head->next = hp; + tbl->last_head = hp; + } else + tbl->last_head = tbl->first_head = hp; + + head_adjust(p, hp); + p->head = hp; + return(p); +} + +static void +head_adjust(const struct tbl_cell *cell, struct tbl_head *head) +{ + if (TBL_CELL_VERT != cell->pos && + TBL_CELL_DVERT != cell->pos) { + head->pos = TBL_HEAD_DATA; + return; + } + + if (TBL_CELL_VERT == cell->pos) + if (TBL_HEAD_DVERT != head->pos) + head->pos = TBL_HEAD_VERT; + + if (TBL_CELL_DVERT == cell->pos) + head->pos = TBL_HEAD_DVERT; +} + diff --git a/contrib/mdocml/tbl_opts.c b/contrib/mdocml/tbl_opts.c new file mode 100644 index 0000000000..b93886764a --- /dev/null +++ b/contrib/mdocml/tbl_opts.c @@ -0,0 +1,260 @@ +/* $Id: tbl_opts.c,v 1.7 2011/01/07 13:20:58 kristaps 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 +#include +#include +#include + +#include "mandoc.h" +#include "libroff.h" + +enum tbl_ident { + KEY_CENTRE = 0, + KEY_DELIM, + KEY_EXPAND, + KEY_BOX, + KEY_DBOX, + KEY_ALLBOX, + KEY_TAB, + KEY_LINESIZE, + KEY_NOKEEP, + KEY_DPOINT, + KEY_NOSPACE, + KEY_FRAME, + KEY_DFRAME, + KEY_MAX +}; + +struct tbl_phrase { + const char *name; + int key; + enum tbl_ident ident; +}; + +/* Handle Commonwealth/American spellings. */ +#define KEY_MAXKEYS 14 + +/* Maximum length of key name string. */ +#define KEY_MAXNAME 13 + +/* Maximum length of key number size. */ +#define KEY_MAXNUMSZ 10 + +static const struct tbl_phrase keys[KEY_MAXKEYS] = { + { "center", TBL_OPT_CENTRE, KEY_CENTRE}, + { "centre", TBL_OPT_CENTRE, KEY_CENTRE}, + { "delim", 0, KEY_DELIM}, + { "expand", TBL_OPT_EXPAND, KEY_EXPAND}, + { "box", TBL_OPT_BOX, KEY_BOX}, + { "doublebox", TBL_OPT_DBOX, KEY_DBOX}, + { "allbox", TBL_OPT_ALLBOX, KEY_ALLBOX}, + { "frame", TBL_OPT_BOX, KEY_FRAME}, + { "doubleframe", TBL_OPT_DBOX, KEY_DFRAME}, + { "tab", 0, KEY_TAB}, + { "linesize", 0, KEY_LINESIZE}, + { "nokeep", TBL_OPT_NOKEEP, KEY_NOKEEP}, + { "decimalpoint", 0, KEY_DPOINT}, + { "nospaces", TBL_OPT_NOSPACE, KEY_NOSPACE}, +}; + +static int arg(struct tbl_node *, int, + const char *, int *, enum tbl_ident); +static void opt(struct tbl_node *, int, + const char *, int *); + +static int +arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key) +{ + int i; + char buf[KEY_MAXNUMSZ]; + + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* Arguments always begin with a parenthesis. */ + + if ('(' != p[*pos]) { + TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos); + return(0); + } + + (*pos)++; + + /* + * The arguments can be ANY value, so we can't just stop at the + * next close parenthesis (the argument can be a closed + * parenthesis itself). + */ + + switch (key) { + case (KEY_DELIM): + if ('\0' == p[(*pos)++]) { + TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); + return(0); + } + + if ('\0' == p[(*pos)++]) { + TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); + return(0); + } + break; + case (KEY_TAB): + if ('\0' != (tbl->opts.tab = p[(*pos)++])) + break; + + TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); + return(0); + case (KEY_LINESIZE): + for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) { + buf[i] = p[*pos]; + if ( ! isdigit((unsigned char)buf[i])) + break; + } + + if (i < KEY_MAXNUMSZ) { + buf[i] = '\0'; + tbl->opts.linesize = atoi(buf); + break; + } + + (*tbl->msg)(MANDOCERR_TBL, tbl->data, ln, *pos, NULL); + return(0); + case (KEY_DPOINT): + if ('\0' != (tbl->opts.decimal = p[(*pos)++])) + break; + + TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); + return(0); + default: + abort(); + /* NOTREACHED */ + } + + /* End with a close parenthesis. */ + + if (')' == p[(*pos)++]) + return(1); + + TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); + return(0); +} + +static void +opt(struct tbl_node *tbl, int ln, const char *p, int *pos) +{ + int i, sv; + char buf[KEY_MAXNAME]; + + /* + * Parse individual options from the stream as surrounded by + * this goto. Each pass through the routine parses out a single + * option and registers it. Option arguments are processed in + * the arg() function. + */ + +again: /* + * EBNF describing this section: + * + * options ::= option_list [:space:]* [;][\n] + * option_list ::= option option_tail + * option_tail ::= [:space:]+ option_list | + * ::= epsilon + * option ::= [:alpha:]+ args + * args ::= [:space:]* [(] [:alpha:]+ [)] + */ + + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* Safe exit point. */ + + if (';' == p[*pos]) + return; + + /* Copy up to first non-alpha character. */ + + for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) { + buf[i] = tolower(p[*pos]); + if ( ! isalpha((unsigned char)buf[i])) + break; + } + + /* Exit if buffer is empty (or overrun). */ + + if (KEY_MAXNAME == i || 0 == i) { + TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos); + return; + } + + buf[i] = '\0'; + + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* + * Look through all of the available keys to find one that + * matches the input. FIXME: hashtable this. + */ + + for (i = 0; i < KEY_MAXKEYS; i++) { + if (strcmp(buf, keys[i].name)) + continue; + + /* + * Note: this is more difficult to recover from, as we + * can be anywhere in the option sequence and it's + * harder to jump to the next. Meanwhile, just bail out + * of the sequence altogether. + */ + + if (keys[i].key) + tbl->opts.opts |= keys[i].key; + else if ( ! arg(tbl, ln, p, pos, keys[i].ident)) + return; + + break; + } + + /* + * Allow us to recover from bad options by continuing to another + * parse sequence. + */ + + if (KEY_MAXKEYS == i) + TBL_MSG(tbl, MANDOCERR_TBLOPT, ln, sv); + + goto again; + /* NOTREACHED */ +} + +int +tbl_option(struct tbl_node *tbl, int ln, const char *p) +{ + int pos; + + /* + * Table options are always on just one line, so automatically + * switch into the next input mode here. + */ + tbl->part = TBL_PART_LAYOUT; + + pos = 0; + opt(tbl, ln, p, &pos); + + /* Always succeed. */ + return(1); +} diff --git a/contrib/mdocml/tbl_term.c b/contrib/mdocml/tbl_term.c new file mode 100644 index 0000000000..a30e723504 --- /dev/null +++ b/contrib/mdocml/tbl_term.c @@ -0,0 +1,425 @@ +/* $Id: tbl_term.c,v 1.13 2011/01/07 14:59:52 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "term.h" + +/* FIXME: `n' modifier doesn't always do the right thing. */ +/* FIXME: `n' modifier doesn't use the cell-spacing buffer. */ + +static size_t term_tbl_len(size_t, void *); +static size_t term_tbl_strlen(const char *, void *); +static void tbl_char(struct termp *, char, size_t); +static void tbl_data(struct termp *, const struct tbl *, + const struct tbl_dat *, + const struct roffcol *); +static void tbl_hframe(struct termp *, const struct tbl_span *); +static void tbl_literal(struct termp *, const struct tbl_dat *, + const struct roffcol *); +static void tbl_number(struct termp *, const struct tbl *, + const struct tbl_dat *, + const struct roffcol *); +static void tbl_hrule(struct termp *, const struct tbl_span *); +static void tbl_vframe(struct termp *, const struct tbl *); +static void tbl_vrule(struct termp *, const struct tbl_head *); + + +static size_t +term_tbl_strlen(const char *p, void *arg) +{ + + return(term_strlen((const struct termp *)arg, p)); +} + +static size_t +term_tbl_len(size_t sz, void *arg) +{ + + return(term_len((const struct termp *)arg, sz)); +} + +void +term_tbl(struct termp *tp, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + const struct tbl_dat *dp; + struct roffcol *col; + size_t rmargin, maxrmargin; + + rmargin = tp->rmargin; + maxrmargin = tp->maxrmargin; + + tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN; + + /* Inhibit printing of spaces: we do padding ourselves. */ + + tp->flags |= TERMP_NONOSPACE; + tp->flags |= TERMP_NOSPACE; + + /* + * The first time we're invoked for a given table block, + * calculate the table widths and decimal positions. + */ + + if (TBL_SPAN_FIRST & sp->flags) { + term_flushln(tp); + + tp->tbl.len = term_tbl_len; + tp->tbl.slen = term_tbl_strlen; + tp->tbl.arg = tp; + + tblcalc(&tp->tbl, sp); + } + + /* Horizontal frame at the start of boxed tables. */ + + if (TBL_SPAN_FIRST & sp->flags) + tbl_hframe(tp, sp); + + /* Vertical frame at the start of each row. */ + + tbl_vframe(tp, sp->tbl); + + /* + * Now print the actual data itself depending on the span type. + * Spanner spans get a horizontal rule; data spanners have their + * data printed by matching data to header. + */ + + switch (sp->pos) { + case (TBL_SPAN_HORIZ): + /* FALLTHROUGH */ + case (TBL_SPAN_DHORIZ): + tbl_hrule(tp, sp); + break; + case (TBL_SPAN_DATA): + /* Iterate over template headers. */ + dp = sp->first; + for (hp = sp->head; hp; hp = hp->next) { + switch (hp->pos) { + case (TBL_HEAD_VERT): + /* FALLTHROUGH */ + case (TBL_HEAD_DVERT): + tbl_vrule(tp, hp); + continue; + case (TBL_HEAD_DATA): + break; + } + + col = &tp->tbl.cols[hp->ident]; + tbl_data(tp, sp->tbl, dp, col); + + /* Go to the next data cell. */ + if (dp) + dp = dp->next; + } + break; + } + + tbl_vframe(tp, sp->tbl); + term_flushln(tp); + + /* + * If we're the last row, clean up after ourselves: clear the + * existing table configuration and set it to NULL. + */ + + if (TBL_SPAN_LAST & sp->flags) { + tbl_hframe(tp, sp); + assert(tp->tbl.cols); + free(tp->tbl.cols); + tp->tbl.cols = NULL; + } + + tp->flags &= ~TERMP_NONOSPACE; + tp->rmargin = rmargin; + tp->maxrmargin = maxrmargin; + +} + +static void +tbl_hrule(struct termp *tp, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + char c; + size_t width; + + /* + * An hrule extends across the entire table and is demarked by a + * standalone `_' or whatnot in lieu of a table row. Spanning + * headers are marked by a `+', as are table boundaries. + */ + + c = '-'; + if (TBL_SPAN_DHORIZ == sp->pos) + c = '='; + + /* FIXME: don't use `+' between data and a spanner! */ + + for (hp = sp->head; hp; hp = hp->next) { + width = tp->tbl.cols[hp->ident].width; + switch (hp->pos) { + case (TBL_HEAD_DATA): + tbl_char(tp, c, width); + break; + case (TBL_HEAD_DVERT): + tbl_char(tp, '+', width); + /* FALLTHROUGH */ + case (TBL_HEAD_VERT): + tbl_char(tp, '+', width); + break; + default: + abort(); + /* NOTREACHED */ + } + } +} + +static void +tbl_hframe(struct termp *tp, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + size_t width; + + if ( ! (TBL_OPT_BOX & sp->tbl->opts || + TBL_OPT_DBOX & sp->tbl->opts)) + return; + + /* + * Print out the horizontal part of a frame or double frame. A + * double frame has an unbroken `-' outer line the width of the + * table, bordered by `+'. The frame (or inner frame, in the + * case of the double frame) is a `-' bordered by `+' and broken + * by `+' whenever a span is encountered. + */ + + if (TBL_OPT_DBOX & sp->tbl->opts) { + term_word(tp, "+"); + for (hp = sp->head; hp; hp = hp->next) { + width = tp->tbl.cols[hp->ident].width; + tbl_char(tp, '-', width); + } + term_word(tp, "+"); + term_flushln(tp); + } + + term_word(tp, "+"); + for (hp = sp->head; hp; hp = hp->next) { + width = tp->tbl.cols[hp->ident].width; + switch (hp->pos) { + case (TBL_HEAD_DATA): + tbl_char(tp, '-', width); + break; + default: + tbl_char(tp, '+', width); + break; + } + } + term_word(tp, "+"); + term_flushln(tp); +} + +static void +tbl_data(struct termp *tp, const struct tbl *tbl, + const struct tbl_dat *dp, + const struct roffcol *col) +{ + enum tbl_cellt pos; + + if (NULL == dp) { + tbl_char(tp, ASCII_NBRSP, col->width); + return; + } + + switch (dp->pos) { + case (TBL_DATA_NONE): + tbl_char(tp, ASCII_NBRSP, col->width); + return; + case (TBL_DATA_HORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_NHORIZ): + tbl_char(tp, '-', col->width); + return; + case (TBL_DATA_NDHORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_DHORIZ): + tbl_char(tp, '=', col->width); + return; + default: + break; + } + + pos = dp && dp->layout ? dp->layout->pos : TBL_CELL_LEFT; + + switch (pos) { + case (TBL_CELL_HORIZ): + tbl_char(tp, '-', col->width); + break; + case (TBL_CELL_DHORIZ): + tbl_char(tp, '=', col->width); + break; + case (TBL_CELL_LONG): + /* FALLTHROUGH */ + case (TBL_CELL_CENTRE): + /* FALLTHROUGH */ + case (TBL_CELL_LEFT): + /* FALLTHROUGH */ + case (TBL_CELL_RIGHT): + tbl_literal(tp, dp, col); + break; + case (TBL_CELL_NUMBER): + tbl_number(tp, tbl, dp, col); + break; + default: + abort(); + /* NOTREACHED */ + } +} + +static void +tbl_vrule(struct termp *tp, const struct tbl_head *hp) +{ + + switch (hp->pos) { + case (TBL_HEAD_VERT): + term_word(tp, "|"); + break; + case (TBL_HEAD_DVERT): + term_word(tp, "||"); + break; + default: + break; + } +} + +static void +tbl_vframe(struct termp *tp, const struct tbl *tbl) +{ + + if (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts) + term_word(tp, "|"); +} + +static void +tbl_char(struct termp *tp, char c, size_t len) +{ + size_t i, sz; + char cp[2]; + + cp[0] = c; + cp[1] = '\0'; + + sz = term_strlen(tp, cp); + + for (i = 0; i < len; i += sz) + term_word(tp, cp); +} + +static void +tbl_literal(struct termp *tp, const struct tbl_dat *dp, + const struct roffcol *col) +{ + size_t padl, padr, ssz; + enum tbl_cellt pos; + const char *str; + + padl = padr = 0; + + pos = dp && dp->layout ? dp->layout->pos : TBL_CELL_LEFT; + str = dp && dp->string ? dp->string : ""; + + ssz = term_len(tp, 1); + + switch (pos) { + case (TBL_CELL_LONG): + padl = ssz; + padr = col->width - term_strlen(tp, str) - ssz; + break; + case (TBL_CELL_CENTRE): + padl = col->width - term_strlen(tp, str); + if (padl % 2) + padr++; + padl /= 2; + padr += padl; + break; + case (TBL_CELL_RIGHT): + padl = col->width - term_strlen(tp, str); + break; + default: + padr = col->width - term_strlen(tp, str); + break; + } + + tbl_char(tp, ASCII_NBRSP, padl); + term_word(tp, str); + tbl_char(tp, ASCII_NBRSP, padr); +} + +static void +tbl_number(struct termp *tp, const struct tbl *tbl, + const struct tbl_dat *dp, + const struct roffcol *col) +{ + char *cp; + char buf[2]; + const char *str; + size_t sz, psz, ssz, d, padl; + int i; + + /* + * See calc_data_number(). Left-pad by taking the offset of our + * and the maximum decimal; right-pad by the remaining amount. + */ + + str = dp && dp->string ? dp->string : ""; + + sz = term_strlen(tp, str); + + buf[0] = tbl->decimal; + buf[1] = '\0'; + + psz = term_strlen(tp, buf); + + if (NULL != (cp = strchr(str, tbl->decimal))) { + buf[1] = '\0'; + for (ssz = 0, i = 0; cp != &str[i]; i++) { + buf[0] = str[i]; + ssz += term_strlen(tp, buf); + } + d = ssz + psz; + } else + d = sz + psz; + + sz += term_len(tp, 2); + d += term_len(tp, 1); + + padl = col->decimal - d; + + tbl_char(tp, ASCII_NBRSP, padl); + term_word(tp, str); + tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); +} + diff --git a/contrib/mdocml/term.c b/contrib/mdocml/term.c new file mode 100644 index 0000000000..ba54c31d2a --- /dev/null +++ b/contrib/mdocml/term.c @@ -0,0 +1,740 @@ +/* $Id: term.c,v 1.176 2011/01/04 13:14:26 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2010 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "chars.h" +#include "out.h" +#include "term.h" +#include "main.h" + +static void spec(struct termp *, enum roffdeco, + const char *, size_t); +static void res(struct termp *, const char *, size_t); +static void bufferc(struct termp *, char); +static void adjbuf(struct termp *p, size_t); +static void encode(struct termp *, const char *, size_t); + + +void +term_free(struct termp *p) +{ + + if (p->buf) + free(p->buf); + if (p->symtab) + chars_free(p->symtab); + + free(p); +} + + +void +term_begin(struct termp *p, term_margin head, + term_margin foot, const void *arg) +{ + + p->headf = head; + p->footf = foot; + p->argf = arg; + (*p->begin)(p); +} + + +void +term_end(struct termp *p) +{ + + (*p->end)(p); +} + + +struct termp * +term_alloc(enum termenc enc) +{ + struct termp *p; + + p = calloc(1, sizeof(struct termp)); + if (NULL == p) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + p->enc = enc; + return(p); +} + + +/* + * Flush a line of text. A "line" is loosely defined as being something + * that should be followed by a newline, regardless of whether it's + * broken apart by newlines getting there. A line can also be a + * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does + * not have a trailing newline. + * + * The following flags may be specified: + * + * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the + * offset value. This is useful when doing columnar lists where the + * prior column has right-padded. + * + * - TERMP_NOBREAK: this is the most important and is used when making + * columns. In short: don't print a newline and instead pad to the + * right margin. Used in conjunction with TERMP_NOLPAD. + * + * - TERMP_TWOSPACE: when padding, make sure there are at least two + * space characters of padding. Otherwise, rather break the line. + * + * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and + * the line is overrun, and don't pad-right if it's underrun. + * + * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when + * overruning, instead save the position and continue at that point + * when the next invocation. + * + * In-line line breaking: + * + * If TERMP_NOBREAK is specified and the line overruns the right + * margin, it will break and pad-right to the right margin after + * writing. If maxrmargin is violated, it will break and continue + * writing from the right-margin, which will lead to the above scenario + * upon exit. Otherwise, the line will break at the right margin. + */ +void +term_flushln(struct termp *p) +{ + int i; /* current input position in p->buf */ + size_t vis; /* current visual position on output */ + size_t vbl; /* number of blanks to prepend to output */ + size_t vend; /* end of word visual position on output */ + size_t bp; /* visual right border position */ + size_t dv; /* temporary for visual pos calculations */ + int j; /* temporary loop index for p->buf */ + int jhy; /* last hyph before overflow w/r/t j */ + size_t maxvis; /* output position of visible boundary */ + size_t mmax; /* used in calculating bp */ + + /* + * First, establish the maximum columns of "visible" content. + * This is usually the difference between the right-margin and + * an indentation, but can be, for tagged lists or columns, a + * small set of values. + */ + assert (p->rmargin >= p->offset); + dv = p->rmargin - p->offset; + maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; + dv = p->maxrmargin - p->offset; + mmax = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; + + bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; + + /* + * Indent the first line of a paragraph. + */ + vbl = p->flags & TERMP_NOLPAD ? (size_t)0 : p->offset; + + vis = vend = 0; + i = 0; + + while (i < (int)p->col) { + /* + * Handle literal tab characters: collapse all + * subsequent tabs into a single huge set of spaces. + */ + while (i < (int)p->col && '\t' == p->buf[i]) { + vend = (vis / p->tabwidth + 1) * p->tabwidth; + vbl += vend - vis; + vis = vend; + i++; + } + + /* + * Count up visible word characters. Control sequences + * (starting with the CSI) aren't counted. A space + * generates a non-printing word, which is valid (the + * space is printed according to regular spacing rules). + */ + + for (j = i, jhy = 0; j < (int)p->col; j++) { + if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j]) + break; + + /* Back over the the last printed character. */ + if (8 == p->buf[j]) { + assert(j); + vend -= (*p->width)(p, p->buf[j - 1]); + continue; + } + + /* Regular word. */ + /* Break at the hyphen point if we overrun. */ + if (vend > vis && vend < bp && + ASCII_HYPH == p->buf[j]) + jhy = j; + + vend += (*p->width)(p, p->buf[j]); + } + + /* + * Find out whether we would exceed the right margin. + * If so, break to the next line. + */ + if (vend > bp && 0 == jhy && vis > 0) { + vend -= vis; + (*p->endline)(p); + if (TERMP_NOBREAK & p->flags) { + p->viscol = p->rmargin; + (*p->advance)(p, p->rmargin); + vend += p->rmargin - p->offset; + } else { + p->viscol = 0; + vbl = p->offset; + } + + /* Remove the p->overstep width. */ + + bp += (size_t)p->overstep; + p->overstep = 0; + } + + /* Write out the [remaining] word. */ + for ( ; i < (int)p->col; i++) { + if (vend > bp && jhy > 0 && i > jhy) + break; + if ('\t' == p->buf[i]) + break; + if (' ' == p->buf[i]) { + j = i; + while (' ' == p->buf[i]) + i++; + dv = (size_t)(i - j) * (*p->width)(p, ' '); + vbl += dv; + vend += dv; + break; + } + if (ASCII_NBRSP == p->buf[i]) { + vbl += (*p->width)(p, ' '); + continue; + } + + /* + * Now we definitely know there will be + * printable characters to output, + * so write preceding white space now. + */ + if (vbl) { + (*p->advance)(p, vbl); + p->viscol += vbl; + vbl = 0; + } + + if (ASCII_HYPH == p->buf[i]) { + (*p->letter)(p, '-'); + p->viscol += (*p->width)(p, '-'); + } else { + (*p->letter)(p, p->buf[i]); + p->viscol += (*p->width)(p, p->buf[i]); + } + } + vis = vend; + } + + /* + * If there was trailing white space, it was not printed; + * so reset the cursor position accordingly. + */ + vis -= vbl; + + p->col = 0; + p->overstep = 0; + + if ( ! (TERMP_NOBREAK & p->flags)) { + p->viscol = 0; + (*p->endline)(p); + return; + } + + if (TERMP_HANG & p->flags) { + /* We need one blank after the tag. */ + p->overstep = (int)(vis - maxvis + (*p->width)(p, ' ')); + + /* + * Behave exactly the same way as groff: + * If we have overstepped the margin, temporarily move + * it to the right and flag the rest of the line to be + * shorter. + * If we landed right at the margin, be happy. + * If we are one step before the margin, temporarily + * move it one step LEFT and flag the rest of the line + * to be longer. + */ + if (p->overstep >= -1) { + assert((int)maxvis + p->overstep >= 0); + maxvis += (size_t)p->overstep; + } else + p->overstep = 0; + + } else if (TERMP_DANGLE & p->flags) + return; + + /* Right-pad. */ + if (maxvis > vis + + ((TERMP_TWOSPACE & p->flags) ? (*p->width)(p, ' ') : 0)) { + p->viscol += maxvis - vis; + (*p->advance)(p, maxvis - vis); + vis += (maxvis - vis); + } else { /* ...or newline break. */ + (*p->endline)(p); + p->viscol = p->rmargin; + (*p->advance)(p, p->rmargin); + } +} + + +/* + * 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 (0 == p->col && 0 == p->viscol) { + p->flags &= ~TERMP_NOLPAD; + return; + } + term_flushln(p); + p->flags &= ~TERMP_NOLPAD; +} + + +/* + * 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->endline)(p); +} + + +static void +spec(struct termp *p, enum roffdeco d, const char *word, size_t len) +{ + const char *rhs; + size_t sz; + + rhs = chars_spec2str(p->symtab, word, len, &sz); + if (rhs) + encode(p, rhs, sz); + else if (DECO_SSPECIAL == d) + encode(p, word, len); +} + + +static void +res(struct termp *p, const char *word, size_t len) +{ + const char *rhs; + size_t sz; + + rhs = chars_res2str(p->symtab, word, len, &sz); + if (rhs) + encode(p, rhs, sz); +} + + +void +term_fontlast(struct termp *p) +{ + enum termfont f; + + f = p->fontl; + p->fontl = p->fontq[p->fonti]; + p->fontq[p->fonti] = f; +} + + +void +term_fontrepl(struct termp *p, enum termfont f) +{ + + p->fontl = p->fontq[p->fonti]; + p->fontq[p->fonti] = f; +} + + +void +term_fontpush(struct termp *p, enum termfont f) +{ + + assert(p->fonti + 1 < 10); + p->fontl = p->fontq[p->fonti]; + p->fontq[++p->fonti] = f; +} + + +const void * +term_fontq(struct termp *p) +{ + + return(&p->fontq[p->fonti]); +} + + +enum termfont +term_fonttop(struct termp *p) +{ + + return(p->fontq[p->fonti]); +} + + +void +term_fontpopq(struct termp *p, const void *key) +{ + + while (p->fonti >= 0 && key != &p->fontq[p->fonti]) + p->fonti--; + assert(p->fonti >= 0); +} + + +void +term_fontpop(struct termp *p) +{ + + assert(p->fonti); + p->fonti--; +} + + +/* + * Handle pwords, partial words, which may be either a single word or a + * phrase that cannot be broken down (such as a literal string). This + * handles word styling. + */ +void +term_word(struct termp *p, const char *word) +{ + const char *sv, *seq; + size_t ssz; + enum roffdeco deco; + + sv = word; + + if (word[0] && '\0' == word[1]) + switch (word[0]) { + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + if ( ! (TERMP_IGNDELIM & p->flags)) + p->flags |= TERMP_NOSPACE; + break; + default: + break; + } + + if ( ! (TERMP_NOSPACE & p->flags)) { + if ( ! (TERMP_KEEP & p->flags)) { + if (TERMP_PREKEEP & p->flags) + p->flags |= TERMP_KEEP; + bufferc(p, ' '); + if (TERMP_SENTENCE & p->flags) + bufferc(p, ' '); + } else + bufferc(p, ASCII_NBRSP); + } + + if ( ! (p->flags & TERMP_NONOSPACE)) + p->flags &= ~TERMP_NOSPACE; + else + p->flags |= TERMP_NOSPACE; + + p->flags &= ~(TERMP_SENTENCE | TERMP_IGNDELIM); + + while (*word) { + if ((ssz = strcspn(word, "\\")) > 0) + encode(p, word, ssz); + + word += ssz; + if ('\\' != *word) + continue; + + seq = ++word; + word += a2roffdeco(&deco, &seq, &ssz); + + switch (deco) { + case (DECO_RESERVED): + res(p, seq, ssz); + break; + case (DECO_SPECIAL): + /* FALLTHROUGH */ + case (DECO_SSPECIAL): + spec(p, deco, seq, ssz); + break; + case (DECO_BOLD): + term_fontrepl(p, TERMFONT_BOLD); + break; + case (DECO_ITALIC): + term_fontrepl(p, TERMFONT_UNDER); + break; + case (DECO_ROMAN): + term_fontrepl(p, TERMFONT_NONE); + break; + case (DECO_PREVIOUS): + term_fontlast(p); + break; + default: + break; + } + + if (DECO_NOSPACE == deco && '\0' == *word) + p->flags |= TERMP_NOSPACE; + } + + /* + * Note that we don't process the pipe: the parser sees it as + * punctuation, but we don't in terms of typography. + */ + if (sv[0] && '\0' == sv[1]) + switch (sv[0]) { + case('('): + /* FALLTHROUGH */ + case('['): + p->flags |= TERMP_NOSPACE; + break; + default: + break; + } +} + + +static void +adjbuf(struct termp *p, size_t sz) +{ + + if (0 == p->maxcols) + p->maxcols = 1024; + while (sz >= p->maxcols) + p->maxcols <<= 2; + + p->buf = realloc(p->buf, p->maxcols); + if (NULL == p->buf) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } +} + + +static void +bufferc(struct termp *p, char c) +{ + + if (p->col + 1 >= p->maxcols) + adjbuf(p, p->col + 1); + + p->buf[(int)p->col++] = c; +} + + +static void +encode(struct termp *p, const char *word, size_t sz) +{ + enum termfont f; + int i; + + /* + * Encode and buffer a string of characters. If the current + * font mode is unset, buffer directly, else encode then buffer + * character by character. + */ + + if (TERMFONT_NONE == (f = term_fonttop(p))) { + if (p->col + sz >= p->maxcols) + adjbuf(p, p->col + sz); + memcpy(&p->buf[(int)p->col], word, sz); + p->col += sz; + return; + } + + /* Pre-buffer, assuming worst-case. */ + + if (p->col + 1 + (sz * 3) >= p->maxcols) + adjbuf(p, p->col + 1 + (sz * 3)); + + for (i = 0; i < (int)sz; i++) { + if ( ! isgraph((u_char)word[i])) { + p->buf[(int)p->col++] = word[i]; + continue; + } + + if (TERMFONT_UNDER == f) + p->buf[(int)p->col++] = '_'; + else + p->buf[(int)p->col++] = word[i]; + + p->buf[(int)p->col++] = 8; + p->buf[(int)p->col++] = word[i]; + } +} + + +size_t +term_len(const struct termp *p, size_t sz) +{ + + return((*p->width)(p, ' ') * sz); +} + + +size_t +term_strlen(const struct termp *p, const char *cp) +{ + size_t sz, ssz, rsz, i; + enum roffdeco d; + const char *seq, *rhs; + + for (sz = 0; '\0' != *cp; ) + /* + * Account for escaped sequences within string length + * calculations. This follows the logic in term_word() + * as we must calculate the width of produced strings. + */ + if ('\\' == *cp) { + seq = ++cp; + cp += a2roffdeco(&d, &seq, &ssz); + + switch (d) { + case (DECO_RESERVED): + rhs = chars_res2str + (p->symtab, seq, ssz, &rsz); + break; + case (DECO_SPECIAL): + /* FALLTHROUGH */ + case (DECO_SSPECIAL): + rhs = chars_spec2str + (p->symtab, seq, ssz, &rsz); + + /* Allow for one-char escapes. */ + if (DECO_SSPECIAL != d || rhs) + break; + + rhs = seq; + rsz = ssz; + break; + default: + rhs = NULL; + break; + } + + if (rhs) + for (i = 0; i < rsz; i++) + sz += (*p->width)(p, *rhs++); + } else if (ASCII_NBRSP == *cp) { + sz += (*p->width)(p, ' '); + cp++; + } else if (ASCII_HYPH == *cp) { + sz += (*p->width)(p, '-'); + cp++; + } else + sz += (*p->width)(p, *cp++); + + return(sz); +} + + +/* ARGSUSED */ +size_t +term_vspan(const struct termp *p, const struct roffsu *su) +{ + double r; + + switch (su->unit) { + case (SCALE_CM): + r = su->scale * 2; + break; + case (SCALE_IN): + r = su->scale * 6; + break; + case (SCALE_PC): + r = su->scale; + break; + case (SCALE_PT): + r = su->scale / 8; + break; + case (SCALE_MM): + r = su->scale / 1000; + break; + case (SCALE_VS): + r = su->scale; + break; + default: + r = su->scale - 1; + break; + } + + if (r < 0.0) + r = 0.0; + return(/* LINTED */(size_t) + r); +} + + +size_t +term_hspan(const struct termp *p, const struct roffsu *su) +{ + double v; + + v = ((*p->hspan)(p, su)); + if (v < 0.0) + v = 0.0; + return((size_t) /* LINTED */ + v); +} diff --git a/contrib/mdocml/term.h b/contrib/mdocml/term.h new file mode 100644 index 0000000000..79b738cae9 --- /dev/null +++ b/contrib/mdocml/term.h @@ -0,0 +1,156 @@ +/* $Id: term.h,v 1.79 2011/01/05 15:37:23 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifndef TERM_H +#define TERM_H + +__BEGIN_DECLS + +struct termp; + +enum termenc { + TERMENC_ASCII +}; + +enum termtype { + TERMTYPE_CHAR, + TERMTYPE_PS, + TERMTYPE_PDF +}; + +enum termfont { + TERMFONT_NONE = 0, + TERMFONT_BOLD, + TERMFONT_UNDER, + TERMFONT__MAX +}; + +#define TERM_MAXMARGIN 100000 /* FIXME */ + +typedef void (*term_margin)(struct termp *, const void *); + +struct termp_ps { + int flags; +#define PS_INLINE (1 << 0) /* we're in a word */ +#define PS_MARGINS (1 << 1) /* we're in the margins */ +#define PS_NEWPAGE (1 << 2) /* new page, no words yet */ + size_t pscol; /* visible column (AFM units) */ + size_t psrow; /* visible row (AFM units) */ + char *psmarg; /* margin buf */ + size_t psmargsz; /* margin buf size */ + size_t psmargcur; /* cur index in margin buf */ + char last; /* character buffer */ + enum termfont lastf; /* last set font */ + size_t scale; /* font scaling factor */ + size_t pages; /* number of pages shown */ + size_t lineheight; /* line height (AFM units) */ + size_t top; /* body top (AFM units) */ + size_t bottom; /* body bottom (AFM units) */ + size_t height; /* page height (AFM units */ + size_t width; /* page width (AFM units) */ + size_t 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 */ +}; + +struct termp_tbl { + int width; /* width in fixed chars */ + int decimal; /* decimal point position */ +}; + +struct termp { + enum termtype type; + struct rofftbl tbl; /* table configuration */ + size_t defrmargin; /* Right margin of the device. */ + size_t rmargin; /* Current right margin. */ + size_t maxrmargin; /* Max right margin. */ + size_t maxcols; /* Max size of buf. */ + size_t offset; /* Margin offest. */ + size_t tabwidth; /* Distance of tab positions. */ + size_t col; /* Bytes in buf. */ + size_t viscol; /* Chars on current line. */ + int overstep; /* See termp_flushln(). */ + int flags; +#define TERMP_SENTENCE (1 << 1) /* Space before a sentence. */ +#define TERMP_NOSPACE (1 << 2) /* No space before words. */ +#define TERMP_NOLPAD (1 << 3) /* See term_flushln(). */ +#define TERMP_NOBREAK (1 << 4) /* See term_flushln(). */ +#define TERMP_IGNDELIM (1 << 6) /* Delims like regulars. */ +#define TERMP_NONOSPACE (1 << 7) /* No space (no autounset). */ +#define TERMP_DANGLE (1 << 8) /* See term_flushln(). */ +#define TERMP_HANG (1 << 9) /* See term_flushln(). */ +#define TERMP_TWOSPACE (1 << 10) /* See term_flushln(). */ +#define TERMP_NOSPLIT (1 << 11) /* See termp_an_pre/post(). */ +#define TERMP_SPLIT (1 << 12) /* See termp_an_pre/post(). */ +#define TERMP_ANPREC (1 << 13) /* See termp_an_pre(). */ +#define TERMP_KEEP (1 << 14) /* Keep words together. */ +#define TERMP_PREKEEP (1 << 15) /* ...starting with the next one. */ + char *buf; /* Output buffer. */ + enum termenc enc; /* Type of encoding. */ + void *symtab; /* Encoded-symbol table. */ + enum termfont fontl; /* Last font set. */ + enum termfont fontq[10]; /* Symmetric fonts. */ + int fonti; /* Index of font stack. */ + term_margin headf; /* invoked to print head */ + term_margin footf; /* invoked to print foot */ + void (*letter)(struct termp *, char); + void (*begin)(struct termp *); + void (*end)(struct termp *); + void (*endline)(struct termp *); + void (*advance)(struct termp *, size_t); + size_t (*width)(const struct termp *, char); + double (*hspan)(const struct termp *, + const struct roffsu *); + const void *argf; /* arg for headf/footf */ + union { + struct termp_ps ps; + } engine; +}; + +struct termp *term_alloc(enum termenc); +void term_tbl(struct termp *, const struct tbl_span *); +void term_free(struct termp *); +void term_newln(struct termp *); +void term_vspace(struct termp *); +void term_word(struct termp *, const char *); +void term_flushln(struct termp *); +void term_begin(struct termp *, term_margin, + term_margin, const void *); +void term_end(struct termp *); + +size_t term_hspan(const struct termp *, + const struct roffsu *); +size_t term_vspan(const struct termp *, + const struct roffsu *); +size_t term_strlen(const struct termp *, const char *); +size_t term_len(const struct termp *, size_t); + +enum termfont term_fonttop(struct termp *); +const void *term_fontq(struct termp *); +void term_fontpush(struct termp *, enum termfont); +void term_fontpop(struct termp *); +void term_fontpopq(struct termp *, const void *); +void term_fontrepl(struct termp *, enum termfont); +void term_fontlast(struct termp *); + +__END_DECLS + +#endif /*!TERM_H*/ diff --git a/contrib/mdocml/term_ascii.c b/contrib/mdocml/term_ascii.c new file mode 100644 index 0000000000..b926a5b378 --- /dev/null +++ b/contrib/mdocml/term_ascii.c @@ -0,0 +1,187 @@ +/* $Id: term_ascii.c,v 1.11 2011/01/02 12:21:07 kristaps Exp $ */ +/* + * Copyright (c) 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "term.h" +#include "main.h" + +static double ascii_hspan(const struct termp *, + const struct roffsu *); +static size_t ascii_width(const struct termp *, char); +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 *, char); + + +void * +ascii_alloc(char *outopts) +{ + struct termp *p; + const char *toks[2]; + char *v; + + if (NULL == (p = term_alloc(TERMENC_ASCII))) + return(NULL); + + p->tabwidth = 5; + p->defrmargin = 78; + + p->advance = ascii_advance; + p->begin = ascii_begin; + p->end = ascii_end; + p->endline = ascii_endline; + p->hspan = ascii_hspan; + p->letter = ascii_letter; + p->type = TERMTYPE_CHAR; + p->width = ascii_width; + + toks[0] = "width"; + toks[1] = NULL; + + while (outopts && *outopts) + switch (getsubopt(&outopts, UNCONST(toks), &v)) { + case (0): + p->defrmargin = (size_t)atoi(v); + break; + default: + break; + } + + /* Enforce a lower boundary. */ + if (p->defrmargin < 58) + p->defrmargin = 58; + + return(p); +} + + +/* ARGSUSED */ +static size_t +ascii_width(const struct termp *p, char c) +{ + + return(1); +} + + +void +ascii_free(void *arg) +{ + + term_free((struct termp *)arg); +} + + +/* ARGSUSED */ +static void +ascii_letter(struct termp *p, char c) +{ + + /* LINTED */ + 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); +} + + +/* ARGSUSED */ +static void +ascii_endline(struct termp *p) +{ + + putchar('\n'); +} + + +/* ARGSUSED */ +static void +ascii_advance(struct termp *p, size_t len) +{ + size_t i; + + /* Just print whitespace on the terminal. */ + for (i = 0; i < len; i++) + putchar(' '); +} + + +/* ARGSUSED */ +static double +ascii_hspan(const struct termp *p, const struct roffsu *su) +{ + double r; + + /* + * Approximate based on character width. These are generated + * entirely by eyeballing the screen, but appear to be correct. + */ + + switch (su->unit) { + case (SCALE_CM): + r = 4 * su->scale; + break; + case (SCALE_IN): + r = 10 * su->scale; + break; + case (SCALE_PC): + r = (10 * su->scale) / 6; + break; + case (SCALE_PT): + r = (10 * su->scale) / 72; + break; + case (SCALE_MM): + r = su->scale / 1000; + break; + case (SCALE_VS): + r = su->scale * 2 - 1; + break; + default: + r = su->scale; + break; + } + + return(r); +} + diff --git a/contrib/mdocml/term_ps.c b/contrib/mdocml/term_ps.c new file mode 100644 index 0000000000..fa2f68fe26 --- /dev/null +++ b/contrib/mdocml/term_ps.c @@ -0,0 +1,1161 @@ +/* $Id: term_ps.c,v 1.45 2010/09/27 23:03:44 schwarze Exp $ */ +/* + * Copyright (c) 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "out.h" +#include "main.h" +#include "term.h" + +/* Convert PostScript point "x" to an AFM unit. */ +#define PNT2AFM(p, x) /* LINTED */ \ + (size_t)((double)(x) * (1000.0 / (double)(p)->engine.ps.scale)) + +/* Convert an AFM unit "x" to a PostScript points */ +#define AFM2PNT(p, x) /* LINTED */ \ + ((double)(x) / (1000.0 / (double)(p)->engine.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 */ +}; + +/* + * 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 }, + } }, +}; + +/* These work the buffer used by the header and footer. */ +#define PS_BUFSLOP 128 + +static void +ps_growbuf(struct termp *p, size_t sz) +{ + if (p->engine.ps.psmargcur + sz <= p->engine.ps.psmargsz) + return; + + if (sz < PS_BUFSLOP) + sz = PS_BUFSLOP; + + p->engine.ps.psmargsz += sz; + + p->engine.ps.psmarg = realloc + (p->engine.ps.psmarg, + p->engine.ps.psmargsz); + + if (NULL == p->engine.ps.psmarg) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } +} + +static double ps_hspan(const struct termp *, + const struct roffsu *); +static size_t ps_width(const struct termp *, char); +static void ps_advance(struct termp *, size_t); +static void ps_begin(struct termp *); +static void ps_closepage(struct termp *); +static void ps_end(struct termp *); +static void ps_endline(struct termp *); +static void ps_fclose(struct termp *); +static void ps_letter(struct termp *, char); +static void ps_pclose(struct termp *); +static void ps_pletter(struct termp *, int); +static void ps_printf(struct termp *, const char *, ...); +static void ps_putchar(struct termp *, char); +static void ps_setfont(struct termp *, enum termfont); +static struct termp *pspdf_alloc(char *); +static void pdf_obj(struct termp *, size_t); + + +void * +pdf_alloc(char *outopts) +{ + struct termp *p; + + if (NULL != (p = pspdf_alloc(outopts))) + p->type = TERMTYPE_PDF; + + return(p); +} + + +void * +ps_alloc(char *outopts) +{ + struct termp *p; + + if (NULL != (p = pspdf_alloc(outopts))) + p->type = TERMTYPE_PS; + + return(p); +} + + +static struct termp * +pspdf_alloc(char *outopts) +{ + struct termp *p; + size_t pagex, pagey, marginx, marginy, lineheight; + const char *toks[2]; + const char *pp; + char *v; + + if (NULL == (p = term_alloc(TERMENC_ASCII))) + return(NULL); + + 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->width = ps_width; + + toks[0] = "paper"; + toks[1] = NULL; + + pp = NULL; + + while (outopts && *outopts) + switch (getsubopt(&outopts, UNCONST(toks), &v)) { + case (0): + pp = v; + break; + default: + break; + } + + /* Default to US letter (millimetres). */ + + pagex = 216; + pagey = 279; + + /* + * The ISO-269 paper sizes can be calculated automatically, but + * it would require bringing in -lm for pow() and I'd rather not + * do that. So just do it the easy way for now. Since this + * only happens once, I'm not terribly concerned. + */ + + if (pp && strcasecmp(pp, "letter")) { + if (0 == strcasecmp(pp, "a3")) { + pagex = 297; + pagey = 420; + } else if (0 == strcasecmp(pp, "a4")) { + pagex = 210; + pagey = 297; + } else if (0 == strcasecmp(pp, "a5")) { + pagex = 148; + pagey = 210; + } else if (0 == strcasecmp(pp, "legal")) { + pagex = 216; + pagey = 356; + } else if (2 != sscanf(pp, "%zux%zu", &pagex, &pagey)) + fprintf(stderr, "%s: Unknown paper\n", pp); + } else if (NULL == pp) + pp = "letter"; + + /* + * This MUST be defined before any PNT2AFM or AFM2PNT + * calculations occur. + */ + + p->engine.ps.scale = 11; + + /* Remember millimetres -> AFM units. */ + + pagex = PNT2AFM(p, ((double)pagex * 2.834)); + pagey = PNT2AFM(p, ((double)pagey * 2.834)); + + /* Margins are 1/9 the page x and y. */ + + marginx = /* LINTED */ + (size_t)((double)pagex / 9.0); + marginy = /* LINTED */ + (size_t)((double)pagey / 9.0); + + /* Line-height is 1.4em. */ + + lineheight = PNT2AFM(p, ((double)p->engine.ps.scale * 1.4)); + + p->engine.ps.width = pagex; + p->engine.ps.height = pagey; + p->engine.ps.header = pagey - (marginy / 2) - (lineheight / 2); + p->engine.ps.top = pagey - marginy; + p->engine.ps.footer = (marginy / 2) - (lineheight / 2); + p->engine.ps.bottom = marginy; + p->engine.ps.left = marginx; + p->engine.ps.lineheight = lineheight; + + p->defrmargin = pagex - (marginx * 2); + return(p); +} + + +void +pspdf_free(void *arg) +{ + struct termp *p; + + p = (struct termp *)arg; + + if (p->engine.ps.psmarg) + free(p->engine.ps.psmarg); + if (p->engine.ps.pdfobjs) + free(p->engine.ps.pdfobjs); + + 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->engine.ps.flags)) { + len = vprintf(fmt, ap); + va_end(ap); + p->engine.ps.pdfbytes += /* LINTED */ + 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->engine.ps.psmargcur; + len = vsnprintf(&p->engine.ps.psmarg[pos], PS_BUFSLOP, fmt, ap); + + va_end(ap); + + p->engine.ps.psmargcur = strlen(p->engine.ps.psmarg); +} + + +static void +ps_putchar(struct termp *p, char c) +{ + int pos; + + /* See ps_printf(). */ + + if ( ! (PS_MARGINS & p->engine.ps.flags)) { + /* LINTED */ + putchar(c); + p->engine.ps.pdfbytes++; + return; + } + + ps_growbuf(p, 2); + + pos = (int)p->engine.ps.psmargcur++; + p->engine.ps.psmarg[pos++] = c; + p->engine.ps.psmarg[pos] = '\0'; +} + + +static void +pdf_obj(struct termp *p, size_t obj) +{ + + assert(obj > 0); + + if ((obj - 1) >= p->engine.ps.pdfobjsz) { + p->engine.ps.pdfobjsz = obj + 128; + p->engine.ps.pdfobjs = realloc + (p->engine.ps.pdfobjs, + p->engine.ps.pdfobjsz * sizeof(size_t)); + if (NULL == p->engine.ps.pdfobjs) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + } + + p->engine.ps.pdfobjs[(int)obj - 1] = p->engine.ps.pdfbytes; + ps_printf(p, "%zu 0 obj\n", obj); +} + + +static void +ps_closepage(struct termp *p) +{ + int i; + size_t len, base; + + /* + * Close out a page that we've already flushed to output. In + * PostScript, we simply note that the page must be showed. In + * PDF, we must now create the Length, Resource, and Page node + * for the page contents. + */ + + assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]); + ps_printf(p, "%s", p->engine.ps.psmarg); + + if (TERMTYPE_PS != p->type) { + ps_printf(p, "ET\n"); + + len = p->engine.ps.pdfbytes - p->engine.ps.pdflastpg; + base = p->engine.ps.pages * 4 + p->engine.ps.pdfbody; + + ps_printf(p, "endstream\nendobj\n"); + + /* Length of content. */ + pdf_obj(p, base + 1); + ps_printf(p, "%zu\nendobj\n", len); + + /* Resource for content. */ + pdf_obj(p, base + 2); + ps_printf(p, "<<\n/ProcSet [/PDF /Text]\n"); + ps_printf(p, "/Font <<\n"); + for (i = 0; i < (int)TERMFONT__MAX; i++) + ps_printf(p, "/F%d %d 0 R\n", i, 3 + i); + ps_printf(p, ">>\n>>\n"); + + /* Page node. */ + pdf_obj(p, base + 3); + ps_printf(p, "<<\n"); + ps_printf(p, "/Type /Page\n"); + ps_printf(p, "/Parent 2 0 R\n"); + ps_printf(p, "/Resources %zu 0 R\n", base + 2); + ps_printf(p, "/Contents %zu 0 R\n", base); + ps_printf(p, ">>\nendobj\n"); + } else + ps_printf(p, "showpage\n"); + + p->engine.ps.pages++; + p->engine.ps.psrow = p->engine.ps.top; + assert( ! (PS_NEWPAGE & p->engine.ps.flags)); + p->engine.ps.flags |= PS_NEWPAGE; +} + + +/* ARGSUSED */ +static void +ps_end(struct termp *p) +{ + size_t i, xref, base; + + /* + * At the end of the file, do one last showpage. This is the + * same behaviour as groff(1) and works for multiple pages as + * well as just one. + */ + + if ( ! (PS_NEWPAGE & p->engine.ps.flags)) { + assert(0 == p->engine.ps.flags); + assert('\0' == p->engine.ps.last); + ps_closepage(p); + } + + if (TERMTYPE_PS == p->type) { + ps_printf(p, "%%%%Trailer\n"); + ps_printf(p, "%%%%Pages: %zu\n", p->engine.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->engine.ps.width), + (size_t)AFM2PNT(p, p->engine.ps.height)); + + ps_printf(p, "/Count %zu\n", p->engine.ps.pages); + ps_printf(p, "/Kids ["); + + for (i = 0; i < p->engine.ps.pages; i++) + ps_printf(p, " %zu 0 R", i * 4 + + p->engine.ps.pdfbody + 3); + + base = (p->engine.ps.pages - 1) * 4 + + p->engine.ps.pdfbody + 4; + + ps_printf(p, "]\n>>\nendobj\n"); + pdf_obj(p, base); + ps_printf(p, "<<\n"); + ps_printf(p, "/Type /Catalog\n"); + ps_printf(p, "/Pages 2 0 R\n"); + ps_printf(p, ">>\n"); + xref = p->engine.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->engine.ps.pdfobjs[(int)i]); + + ps_printf(p, "trailer\n"); + ps_printf(p, "<<\n"); + ps_printf(p, "/Size %zu\n", base + 1); + ps_printf(p, "/Root %zu 0 R\n", base); + ps_printf(p, "/Info 1 0 R\n"); + ps_printf(p, ">>\n"); + ps_printf(p, "startxref\n"); + ps_printf(p, "%zu\n", xref); + ps_printf(p, "%%%%EOF\n"); +} + + +static void +ps_begin(struct termp *p) +{ + time_t t; + int i; + + /* + * Print margins into margin buffer. Nothing gets output to the + * screen yet, so we don't need to initialise the primary state. + */ + + if (p->engine.ps.psmarg) { + assert(p->engine.ps.psmargsz); + p->engine.ps.psmarg[0] = '\0'; + } + + /*p->engine.ps.pdfbytes = 0;*/ + p->engine.ps.psmargcur = 0; + p->engine.ps.flags = PS_MARGINS; + p->engine.ps.pscol = p->engine.ps.left; + p->engine.ps.psrow = p->engine.ps.header; + + ps_setfont(p, TERMFONT_NONE); + + (*p->headf)(p, p->argf); + (*p->endline)(p); + + p->engine.ps.pscol = p->engine.ps.left; + p->engine.ps.psrow = p->engine.ps.footer; + + (*p->footf)(p, p->argf); + (*p->endline)(p); + + p->engine.ps.flags &= ~PS_MARGINS; + + assert(0 == p->engine.ps.flags); + assert(p->engine.ps.psmarg); + assert('\0' != p->engine.ps.psmarg[0]); + + /* + * Print header and initialise page state. Following this, + * stuff gets printed to the screen, so make sure we're sane. + */ + + t = time(NULL); + + if (TERMTYPE_PS == p->type) { + ps_printf(p, "%%!PS-Adobe-3.0\n"); + ps_printf(p, "%%%%Creator: mandoc-%s\n", VERSION); + ps_printf(p, "%%%%CreationDate: %s", ctime(&t)); + ps_printf(p, "%%%%DocumentData: Clean7Bit\n"); + ps_printf(p, "%%%%Orientation: Portrait\n"); + ps_printf(p, "%%%%Pages: (atend)\n"); + ps_printf(p, "%%%%PageOrder: Ascend\n"); + ps_printf(p, "%%%%DocumentMedia: " + "Default %zu %zu 0 () ()\n", + (size_t)AFM2PNT(p, p->engine.ps.width), + (size_t)AFM2PNT(p, p->engine.ps.height)); + ps_printf(p, "%%%%DocumentNeededResources: font"); + + for (i = 0; i < (int)TERMFONT__MAX; i++) + ps_printf(p, " %s", fonts[i].name); + + ps_printf(p, "\n%%%%EndComments\n"); + } else { + ps_printf(p, "%%PDF-1.1\n"); + pdf_obj(p, 1); + ps_printf(p, "<<\n"); + ps_printf(p, "/Creator mandoc-%s\n", VERSION); + 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%zu\n", i); + ps_printf(p, "/BaseFont /%s\n", fonts[i].name); + ps_printf(p, ">>\n"); + } + } + + p->engine.ps.pdfbody = (size_t)TERMFONT__MAX + 3; + p->engine.ps.pscol = p->engine.ps.left; + p->engine.ps.psrow = p->engine.ps.top; + p->engine.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->engine.ps.flags) { + if (TERMTYPE_PS == p->type) { + ps_printf(p, "%%%%Page: %zu %zu\n", + p->engine.ps.pages + 1, + p->engine.ps.pages + 1); + ps_printf(p, "/%s %zu selectfont\n", + fonts[(int)p->engine.ps.lastf].name, + p->engine.ps.scale); + } else { + pdf_obj(p, p->engine.ps.pdfbody + + p->engine.ps.pages * 4); + ps_printf(p, "<<\n"); + ps_printf(p, "/Length %zu 0 R\n", + p->engine.ps.pdfbody + 1 + + p->engine.ps.pages * 4); + ps_printf(p, ">>\nstream\n"); + } + p->engine.ps.pdflastpg = p->engine.ps.pdfbytes; + p->engine.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->engine.ps.flags)) { + if (TERMTYPE_PS != p->type) { + ps_printf(p, "BT\n/F%d %zu Tf\n", + (int)p->engine.ps.lastf, + p->engine.ps.scale); + ps_printf(p, "%.3f %.3f Td\n(", + AFM2PNT(p, p->engine.ps.pscol), + AFM2PNT(p, p->engine.ps.psrow)); + } else + ps_printf(p, "%.3f %.3f moveto\n(", + AFM2PNT(p, p->engine.ps.pscol), + AFM2PNT(p, p->engine.ps.psrow)); + p->engine.ps.flags |= PS_INLINE; + } + + assert( ! (PS_NEWPAGE & p->engine.ps.flags)); + + /* + * We need to escape these characters as per the PostScript + * specification. We would also escape non-graphable characters + * (like tabs), but none of them would get to this point and + * it's superfluous to abort() on them. + */ + + switch (c) { + case ('('): + /* FALLTHROUGH */ + case (')'): + /* FALLTHROUGH */ + case ('\\'): + ps_putchar(p, '\\'); + break; + default: + break; + } + + /* Write the character and adjust where we are on the page. */ + + f = (int)p->engine.ps.lastf; + + if (c <= 32 || (c - 32 >= MAXCHAR)) { + ps_putchar(p, ' '); + p->engine.ps.pscol += (size_t)fonts[f].gly[0].wx; + return; + } + + ps_putchar(p, (char)c); + c -= 32; + p->engine.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->engine.ps.flags)) + return; + + if (TERMTYPE_PS != p->type) { + ps_printf(p, ") Tj\nET\n"); + } else + ps_printf(p, ") show\n"); + + p->engine.ps.flags &= ~PS_INLINE; +} + + +static void +ps_fclose(struct termp *p) +{ + + /* + * Strong closure: if we have a last-char, spit it out after + * checking that we're in the right font mode. This will of + * course open a new scope, if applicable. + * + * Following this, close out any scope that's open. + */ + + if ('\0' != p->engine.ps.last) { + if (p->engine.ps.lastf != TERMFONT_NONE) { + ps_pclose(p); + ps_setfont(p, TERMFONT_NONE); + } + ps_pletter(p, p->engine.ps.last); + p->engine.ps.last = '\0'; + } + + if ( ! (PS_INLINE & p->engine.ps.flags)) + return; + + ps_pclose(p); +} + + +static void +ps_letter(struct termp *p, char c) +{ + char cc; + + /* + * State machine dictates whether to buffer the last character + * or not. Basically, encoded words are detected by checking if + * we're an "8" and switching on the buffer. Then we put "8" in + * our buffer, and on the next charater, flush both character + * and buffer. Thus, "regular" words are detected by having a + * regular character and a regular buffer character. + */ + + if ('\0' == p->engine.ps.last) { + assert(8 != c); + p->engine.ps.last = c; + return; + } else if (8 == p->engine.ps.last) { + assert(8 != c); + p->engine.ps.last = '\0'; + } else if (8 == c) { + assert(8 != p->engine.ps.last); + if ('_' == p->engine.ps.last) { + if (p->engine.ps.lastf != TERMFONT_UNDER) { + ps_pclose(p); + ps_setfont(p, TERMFONT_UNDER); + } + } else if (p->engine.ps.lastf != TERMFONT_BOLD) { + ps_pclose(p); + ps_setfont(p, TERMFONT_BOLD); + } + p->engine.ps.last = c; + return; + } else { + if (p->engine.ps.lastf != TERMFONT_NONE) { + ps_pclose(p); + ps_setfont(p, TERMFONT_NONE); + } + cc = p->engine.ps.last; + p->engine.ps.last = c; + c = cc; + } + + ps_pletter(p, c); +} + + +static void +ps_advance(struct termp *p, size_t len) +{ + + /* + * Advance some spaces. This can probably be made smarter, + * i.e., to have multiple space-separated words in the same + * scope, but this is easier: just close out the current scope + * and readjust our column settings. + */ + + ps_fclose(p); + p->engine.ps.pscol += len; +} + + +static void +ps_endline(struct termp *p) +{ + + /* Close out any scopes we have open: we're at eoln. */ + + ps_fclose(p); + + /* + * If we're in the margin, don't try to recalculate our current + * row. XXX: if the column tries to be fancy with multiple + * lines, we'll do nasty stuff. + */ + + if (PS_MARGINS & p->engine.ps.flags) + return; + + /* Left-justify. */ + + p->engine.ps.pscol = p->engine.ps.left; + + /* If we haven't printed anything, return. */ + + if (PS_NEWPAGE & p->engine.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->engine.ps.psrow >= p->engine.ps.lineheight + + p->engine.ps.bottom) { + p->engine.ps.psrow -= p->engine.ps.lineheight; + return; + } + + ps_closepage(p); +} + + +static void +ps_setfont(struct termp *p, enum termfont f) +{ + + assert(f < TERMFONT__MAX); + p->engine.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->engine.ps.flags) + return; + + if (TERMTYPE_PS == p->type) + ps_printf(p, "/%s %zu selectfont\n", + fonts[(int)f].name, + p->engine.ps.scale); + else + ps_printf(p, "/F%d %zu Tf\n", + (int)f, + p->engine.ps.scale); +} + + +/* ARGSUSED */ +static size_t +ps_width(const struct termp *p, char c) +{ + + if (c <= 32 || c - 32 >= MAXCHAR) + return((size_t)fonts[(int)TERMFONT_NONE].gly[0].wx); + + c -= 32; + return((size_t)fonts[(int)TERMFONT_NONE].gly[(int)c].wx); +} + + +static double +ps_hspan(const struct termp *p, const struct roffsu *su) +{ + double r; + + /* + * All of these measurements are derived by converting from the + * native measurement to AFM units. + */ + + switch (su->unit) { + case (SCALE_CM): + r = PNT2AFM(p, su->scale * 28.34); + break; + case (SCALE_IN): + r = PNT2AFM(p, su->scale * 72); + break; + case (SCALE_PC): + r = PNT2AFM(p, su->scale * 12); + break; + case (SCALE_PT): + r = PNT2AFM(p, su->scale * 100); + break; + case (SCALE_EM): + r = su->scale * + fonts[(int)TERMFONT_NONE].gly[109 - 32].wx; + break; + case (SCALE_MM): + r = PNT2AFM(p, su->scale * 2.834); + break; + case (SCALE_EN): + r = su->scale * + fonts[(int)TERMFONT_NONE].gly[110 - 32].wx; + break; + case (SCALE_VS): + r = su->scale * p->engine.ps.lineheight; + break; + default: + r = su->scale; + break; + } + + return(r); +} + diff --git a/contrib/mdocml/test-strlcat.c b/contrib/mdocml/test-strlcat.c new file mode 100644 index 0000000000..5d450dd04d --- /dev/null +++ b/contrib/mdocml/test-strlcat.c @@ -0,0 +1,8 @@ +#include + +int +main(int argc, char **argv) +{ + strlcat(argv[0], argv[1], 10); + return 0; +} diff --git a/contrib/mdocml/test-strlcpy.c b/contrib/mdocml/test-strlcpy.c new file mode 100644 index 0000000000..c7d182aaf2 --- /dev/null +++ b/contrib/mdocml/test-strlcpy.c @@ -0,0 +1,8 @@ +#include + +int +main(int argc, char **argv) +{ + strlcpy(argv[0], argv[1], 10); + return 0; +} diff --git a/contrib/mdocml/tree.c b/contrib/mdocml/tree.c new file mode 100644 index 0000000000..70bd73d9ef --- /dev/null +++ b/contrib/mdocml/tree.c @@ -0,0 +1,289 @@ +/* $Id: tree.c,v 1.31 2011/01/03 13:59:21 kristaps Exp $ */ +/* + * Copyright (c) 2008, 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "mandoc.h" +#include "mdoc.h" +#include "man.h" +#include "main.h" + +static void print_mdoc(const struct mdoc_node *, int); +static void print_man(const struct man_node *, int); +static void print_span(const struct tbl_span *, int); + + +/* ARGSUSED */ +void +tree_mdoc(void *arg, const struct mdoc *mdoc) +{ + + print_mdoc(mdoc_node(mdoc), 0); +} + + +/* ARGSUSED */ +void +tree_man(void *arg, const struct man *man) +{ + + print_man(man_node(man), 0); +} + + +static void +print_mdoc(const struct mdoc_node *n, int indent) +{ + const char *p, *t; + int i, j; + size_t argc, sz; + char **params; + struct mdoc_argv *argv; + + argv = NULL; + argc = sz = 0; + params = NULL; + + switch (n->type) { + case (MDOC_ROOT): + t = "root"; + break; + case (MDOC_BLOCK): + t = "block"; + break; + case (MDOC_HEAD): + t = "block-head"; + break; + case (MDOC_BODY): + if (n->end) + t = "body-end"; + else + t = "block-body"; + break; + case (MDOC_TAIL): + t = "block-tail"; + break; + case (MDOC_ELEM): + t = "elem"; + break; + case (MDOC_TEXT): + t = "text"; + break; + case (MDOC_TBL): + t = "tbl"; + break; + default: + abort(); + /* NOTREACHED */ + } + + p = NULL; + + switch (n->type) { + case (MDOC_TEXT): + p = n->string; + break; + case (MDOC_BODY): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_HEAD): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_TAIL): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_ELEM): + p = mdoc_macronames[n->tok]; + if (n->args) { + argv = n->args->argv; + argc = n->args->argc; + } + break; + case (MDOC_BLOCK): + p = mdoc_macronames[n->tok]; + if (n->args) { + argv = n->args->argv; + argc = n->args->argc; + } + break; + case (MDOC_TBL): + break; + case (MDOC_ROOT): + p = "root"; + break; + default: + abort(); + /* NOTREACHED */ + } + + if (n->span) { + assert(NULL == p); + print_span(n->span, indent); + } else { + for (i = 0; i < indent; i++) + putchar('\t'); + + printf("%s (%s)", p, t); + + for (i = 0; i < (int)argc; i++) { + printf(" -%s", mdoc_argnames[argv[i].arg]); + if (argv[i].sz > 0) + printf(" ["); + for (j = 0; j < (int)argv[i].sz; j++) + printf(" [%s]", argv[i].value[j]); + if (argv[i].sz > 0) + printf(" ]"); + } + + for (i = 0; i < (int)sz; i++) + printf(" [%s]", params[i]); + + printf(" %d:%d", n->line, n->pos); + } + + putchar('\n'); + + if (n->child) + print_mdoc(n->child, indent + 1); + if (n->next) + print_mdoc(n->next, indent); +} + + +static void +print_man(const struct man_node *n, int indent) +{ + const char *p, *t; + int i; + + switch (n->type) { + case (MAN_ROOT): + t = "root"; + break; + case (MAN_ELEM): + t = "elem"; + break; + case (MAN_TEXT): + t = "text"; + break; + case (MAN_BLOCK): + t = "block"; + break; + case (MAN_HEAD): + t = "block-head"; + break; + case (MAN_BODY): + t = "block-body"; + break; + case (MAN_TBL): + t = "tbl"; + break; + default: + abort(); + /* NOTREACHED */ + } + + p = NULL; + + switch (n->type) { + case (MAN_TEXT): + p = n->string; + break; + case (MAN_ELEM): + /* FALLTHROUGH */ + case (MAN_BLOCK): + /* FALLTHROUGH */ + case (MAN_HEAD): + /* FALLTHROUGH */ + case (MAN_BODY): + p = man_macronames[n->tok]; + break; + case (MAN_ROOT): + p = "root"; + break; + case (MAN_TBL): + break; + default: + abort(); + /* NOTREACHED */ + } + + if (n->span) { + assert(NULL == p); + print_span(n->span, indent); + } else { + for (i = 0; i < indent; i++) + putchar('\t'); + printf("%s (%s) %d:%d", p, t, n->line, n->pos); + } + + putchar('\n'); + + if (n->child) + print_man(n->child, indent + 1); + if (n->next) + print_man(n->next, indent); +} + +static void +print_span(const struct tbl_span *sp, int indent) +{ + const struct tbl_dat *dp; + int i; + + for (i = 0; i < indent; i++) + putchar('\t'); + + printf("tbl: "); + + switch (sp->pos) { + case (TBL_SPAN_HORIZ): + putchar('-'); + return; + case (TBL_SPAN_DHORIZ): + putchar('='); + return; + default: + break; + } + + for (dp = sp->first; dp; dp = dp->next) { + switch (dp->pos) { + case (TBL_DATA_HORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_NHORIZ): + putchar('-'); + continue; + case (TBL_DATA_DHORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_NDHORIZ): + putchar('='); + continue; + default: + break; + } + printf("[%s%s]", dp->string, dp->layout ? "" : "*"); + if (dp->next) + putchar(' '); + } +} diff --git a/contrib/mdocml/vol.c b/contrib/mdocml/vol.c new file mode 100644 index 0000000000..144d363ff6 --- /dev/null +++ b/contrib/mdocml/vol.c @@ -0,0 +1,38 @@ +/* $Id: vol.c,v 1.8 2010/06/19 20:46:28 kristaps 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. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2vol(const char *p) +{ + +#include "vol.in" + + return(NULL); +} diff --git a/contrib/mdocml/vol.in b/contrib/mdocml/vol.in new file mode 100644 index 0000000000..7650b57a14 --- /dev/null +++ b/contrib/mdocml/vol.in @@ -0,0 +1,35 @@ +/* $Id: vol.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines volume titles for .Dt. + * + * Be sure to escape strings. + */ + +LINE("USD", "User\'s Supplementary Documents") +LINE("PS1", "Programmer\'s Supplementary Documents") +LINE("AMD", "Ancestral Manual Documents") +LINE("SMM", "System Manager\'s Manual") +LINE("URM", "User\'s Reference Manual") +LINE("PRM", "Programmer\'s Manual") +LINE("KM", "Kernel Manual") +LINE("IND", "Manual Master Index") +LINE("MMI", "Manual Master Index") +LINE("LOCAL", "Local Manual") +LINE("LOC", "Local Manual") +LINE("CON", "Contributed Software Manual") -- 2.41.0