From: Sascha Wildner Date: Sat, 9 Apr 2011 21:38:59 +0000 (+0200) Subject: Import mdocml-1.11.1 X-Git-Tag: v2.11.0~70^2 X-Git-Url: https://gitweb.dragonflybsd.org/~nant/dragonfly.git/commitdiff_plain/60e1e7526e4aa4a40d14e4710d9ff2496d9f3f95 Import mdocml-1.11.1 --- diff --git a/contrib/mdocml/ChangeLog.xsl b/contrib/mdocml/ChangeLog.xsl deleted file mode 100644 index dccc79dba1..0000000000 --- a/contrib/mdocml/ChangeLog.xsl +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - mdocml - CVS-ChangeLog - - - - -
- Files modified by - -
-
- - Note: - - -
    - -
  • - - - — Rev: - - , Status: - - - , Tag: - - - -
  • -
    -
-
-
- - -
-
diff --git a/contrib/mdocml/Makefile b/contrib/mdocml/Makefile index 23615926fc..3cc7daecb4 100644 --- a/contrib/mdocml/Makefile +++ b/contrib/mdocml/Makefile @@ -1,345 +1,379 @@ -.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 +.PHONY: clean install installwww +.SUFFIXES: .sgml .html .md5 .h .h.html +.SUFFIXES: .1 .3 .7 +.SUFFIXES: .1.txt .3.txt .7.txt +.SUFFIXES: .1.pdf .3.pdf .7.pdf +.SUFFIXES: .1.ps .3.ps .7.ps +.SUFFIXES: .1.html .3.html .7.html +.SUFFIXES: .1.xhtml .3.xhtml .7.xhtml # 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) +# CFLAGS += -DOSNAME="\"OpenBSD 4.5\"" + +VERSION = 1.11.1 +VDATE = 04 April 2011 +CFLAGS += -g -DHAVE_CONFIG_H -DVERSION="\"$(VERSION)\"" +CFLAGS += -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin +INCLUDEDIR = $(PREFIX)/include/mandoc +LIBDIR = $(PREFIX)/lib/mandoc +MANDIR = $(PREFIX)/man +EXAMPLEDIR = $(PREFIX)/share/examples/mandoc +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 0444 +INSTALL_LIB = $(INSTALL) -m 0644 +INSTALL_MAN = $(INSTALL_DATA) + +all: mandoc + +SRCS = Makefile \ + arch.c \ + arch.in \ + att.c \ + att.in \ + chars.c \ + chars.in \ + compat.c \ + config.h.post \ + config.h.pre \ + eqn.7 \ + eqn.c \ + example.style.css \ + external.png \ + html.c \ + html.h \ + index.css \ + index.sgml \ + lib.c \ + lib.in \ + libman.h \ + libmandoc.h \ + libmdoc.h \ + libroff.h \ + main.c \ + main.h \ + man.h \ + man.7 \ + man.c \ + man_hash.c \ + man_html.c \ + man_macro.c \ + man_term.c \ + man_validate.c \ + mandoc.1 \ + mandoc.3 \ + mandoc.c \ + mandoc.h \ + mandoc-db.1 \ + mandoc-db.c \ + mandoc_char.7 \ + mdoc.h \ + mdoc.7 \ + mdoc.c \ + mdoc_argv.c \ + mdoc_hash.c \ + mdoc_html.c \ + mdoc_macro.c \ + mdoc_term.c \ + mdoc_validate.c \ + msec.c \ + msec.in \ + out.c \ + out.h \ + read.c \ + roff.7 \ + roff.c \ + st.c \ + st.in \ + style.css \ + tbl.7 \ + tbl.c \ + tbl_data.c \ + tbl_html.c \ + tbl_layout.c \ + tbl_opts.c \ + tbl_term.c \ + term.c \ + term.h \ + term_ascii.c \ + term_ps.c \ + test-strlcat.c \ + test-strlcpy.c \ + tree.c \ + vol.c \ + vol.in + +LIBMAN_OBJS = man.o \ + man_hash.o \ + man_macro.o \ + man_validate.o +LIBMAN_LNS = man.ln \ + man_hash.ln \ + man_macro.ln \ + man_validate.ln + +LIBMDOC_OBJS = arch.o \ + att.o \ + lib.o \ + mdoc.o \ + mdoc_argv.o \ + mdoc_hash.o \ + mdoc_macro.o \ + mdoc_validate.o \ + msec.o \ + st.o \ + vol.o +LIBMDOC_LNS = arch.ln \ + att.ln \ + lib.ln \ + mdoc.ln \ + mdoc_argv.ln \ + mdoc_hash.ln \ + mdoc_macro.ln \ + mdoc_validate.ln \ + msec.ln \ + st.ln \ + vol.ln + +LIBROFF_OBJS = eqn.o \ + roff.o \ + tbl.o \ + tbl_data.o \ + tbl_layout.o \ + tbl_opts.o +LIBROFF_LNS = eqn.ln \ + roff.ln \ + tbl.ln \ + tbl_data.ln \ + tbl_layout.ln \ + tbl_opts.ln + +LIBMANDOC_OBJS = $(LIBMAN_OBJS) \ + $(LIBMDOC_OBJS) \ + $(LIBROFF_OBJS) \ + mandoc.o \ + read.o +LIBMANDOC_LNS = $(LIBMAN_LNS) \ + $(LIBMDOC_LNS) \ + $(LIBROFF_LNS) \ + mandoc.ln \ + read.ln + +arch.o arch.ln: arch.in +att.o att.ln: att.in +lib.o lib.ln: lib.in +msec.o msec.ln: msec.in +st.o st.ln: st.in +vol.o vol.ln: vol.in + +$(LIBMAN_OBJS) $(LIBMAN_LNS): libman.h +$(LIBMDOC_OBJS) $(LIBMDOC_LNS): libmdoc.h +$(LIBROFF_OBJS) $(LIBROFF_LNS): libroff.h +$(LIBMANDOC_OBJS) $(LIBMANDOC_LNS): mandoc.h mdoc.h man.h libmandoc.h config.h + +MANDOC_HTML_OBJS = html.o \ + man_html.o \ + mdoc_html.o \ + tbl_html.o +MANDOC_HTML_LNS = html.ln \ + man_html.ln \ + mdoc_html.ln \ + tbl_html.ln + +MANDOC_TERM_OBJS = man_term.o \ + mdoc_term.o \ + term.o \ + term_ascii.o \ + term_ps.o \ + tbl_term.o +MANDOC_TERM_LNS = man_term.ln \ + mdoc_term.ln \ + term.ln \ + term_ascii.ln \ + term_ps.ln \ + tbl_term.ln + +MANDOC_OBJS = $(MANDOC_HTML_OBJS) \ + $(MANDOC_TERM_OBJS) \ + chars.o \ + main.o \ + out.o \ + tree.o +MANDOC_LNS = $(MANDOC_HTML_LNS) \ + $(MANDOC_TERM_LNS) \ + chars.ln \ + main.ln \ + out.ln \ + tree.ln + +chars.o chars.ln: chars.in + +$(MANDOC_HTML_OBJS) $(MANDOC_HTML_LNS): html.h +$(MANDOC_TERM_OBJS) $(MANDOC_TERM_LNS): term.h +$(MANDOC_OBJS) $(MANDOC_LNS): main.h mandoc.h mdoc.h man.h config.h out.h + +compat.o compat.ln: config.h + +MANDOCDB_OBJS = mandoc-db.o +MANDOCDB_LNS = mandoc-db.ln + +$(MANDOCDB_OBJS) $(MANDOCDB_LNS): mandoc.h mdoc.h man.h config.h + +INDEX_MANS = mandoc.1.html \ + mandoc.1.xhtml \ + mandoc.1.ps \ + mandoc.1.pdf \ + mandoc.1.txt \ + mandoc.3.html \ + mandoc.3.xhtml \ + mandoc.3.ps \ + mandoc.3.pdf \ + mandoc.3.txt \ + eqn.7.html \ + eqn.7.xhtml \ + eqn.7.ps \ + eqn.7.pdf \ + eqn.7.txt \ + man.7.html \ + man.7.xhtml \ + man.7.ps \ + man.7.pdf \ + man.7.txt \ + mandoc_char.7.html \ + mandoc_char.7.xhtml \ + mandoc_char.7.ps \ + mandoc_char.7.pdf \ + mandoc_char.7.txt \ + mdoc.7.html \ + mdoc.7.xhtml \ + mdoc.7.ps \ + mdoc.7.pdf \ + mdoc.7.txt \ + roff.7.html \ + roff.7.xhtml \ + roff.7.ps \ + roff.7.pdf \ + roff.7.txt \ + tbl.7.html \ + tbl.7.xhtml \ + tbl.7.ps \ + tbl.7.pdf \ + tbl.7.txt + +$(INDEX_MANS): mandoc + +INDEX_OBJS = $(INDEX_MANS) \ + man.h.html \ + mandoc.h.html \ + mdoc.h.html \ + mdocml.tar.gz \ + mdocml.md5 + +www: index.html + +lint: llib-llibmandoc.ln llib-lmandoc.ln 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: + rm -f libmandoc.a $(LIBMANDOC_OBJS) + rm -f llib-llibmandoc.ln $(LIBMANDOC_LNS) + rm -f mandoc-db $(MANDOCDB_OBJS) + rm -f llib-lmandoc-db.ln $(MANDOCDB_LNS) + rm -f mandoc $(MANDOC_OBJS) + rm -f llib-lmandoc.ln $(MANDOC_LNS) + rm -f config.h config.log compat.o compat.ln + rm -f mdocml.tar.gz + rm -f index.html $(INDEX_OBJS) + +install: all mkdir -p $(DESTDIR)$(BINDIR) mkdir -p $(DESTDIR)$(EXAMPLEDIR) mkdir -p $(DESTDIR)$(MANDIR)/man1 + mkdir -p $(DESTDIR)$(MANDIR)/man3 mkdir -p $(DESTDIR)$(MANDIR)/man7 $(INSTALL_PROGRAM) mandoc $(DESTDIR)$(BINDIR) + $(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR) $(INSTALL_MAN) mandoc.1 $(DESTDIR)$(MANDIR)/man1 - $(INSTALL_MAN) man.7 mdoc.7 roff.7 tbl.7 mandoc_char.7 $(DESTDIR)$(MANDIR)/man7 + $(INSTALL_MAN) mandoc.3 $(DESTDIR)$(MANDIR)/man3 + $(INSTALL_MAN) man.7 mdoc.7 roff.7 eqn.7 tbl.7 mandoc_char.7 $(DESTDIR)$(MANDIR)/man7 $(INSTALL_DATA) example.style.css $(DESTDIR)$(EXAMPLEDIR) -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 +installwww: www + mkdir -p $(PREFIX)/snapshots + $(INSTALL_DATA) index.html external.png index.css $(PREFIX) + $(INSTALL_DATA) $(INDEX_MANS) style.css $(PREFIX) + $(INSTALL_DATA) mandoc.h.html man.h.html mdoc.h.html $(PREFIX) + $(INSTALL_DATA) mdocml.tar.gz $(PREFIX)/snapshots + $(INSTALL_DATA) mdocml.md5 $(PREFIX)/snapshots + $(INSTALL_DATA) mdocml.tar.gz $(PREFIX)/snapshots/mdocml-$(VERSION).tar.gz + $(INSTALL_DATA) mdocml.md5 $(PREFIX)/snapshots/mdocml-$(VERSION).md5 -man_argv.ln man_argv.o: man_argv.c libman.h +libmandoc.a: compat.o $(LIBMANDOC_OBJS) + $(AR) rs $@ compat.o $(LIBMANDOC_OBJS) -man_validate.ln man_validate.o: man_validate.c libman.h +llib-llibmandoc.ln: compat.ln $(LIBMANDOC_LNS) + $(LINT) $(LINTFLAGS) -Clibmandoc compat.ln $(LIBMANDOC_LNS) -mdoc_validate.ln mdoc_validate.o: mdoc_validate.c libmdoc.h +mandoc: $(MANDOC_OBJS) libmandoc.a + $(CC) -o $@ $(MANDOC_OBJS) libmandoc.a -libmdoc.h: mdoc.h +# You'll need -ldb for Linux. +mandoc-db: $(MANDOCDB_OBJS) libmandoc.a + $(CC) -o $@ $(MANDOCDB_OBJS) libmandoc.a -ChangeLog.xml: - cvs2cl --xml --xml-encoding iso-8859-15 -t --noxmlns -f $@ +llib-lmandoc.ln: $(MANDOC_LNS) + $(LINT) $(LINTFLAGS) -Cmandoc $(MANDOC_LNS) -ChangeLog.txt: - cvs2cl -t -f $@ +llib-lmandoc-db.ln: $(MANDOCDB_LNS) + $(LINT) $(LINTFLAGS) -Cmandoc-db $(MANDOCDB_LNS) -ChangeLog.html: ChangeLog.xml ChangeLog.xsl - xsltproc -o $@ ChangeLog.xsl ChangeLog.xml +mdocml.md5: mdocml.tar.gz + md5 mdocml.tar.gz >$@ -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)/ ) +mdocml.tar.gz: $(SRCS) + mkdir -p .dist/mdocml-$(VERSION)/ + $(INSTALL) -m 0444 $(SRCS) .dist/mdocml-$(VERSION) + ( cd .dist/ && tar zcf ../$@ ./ ) 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) +index.html: $(INDEX_OBJS) -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 +config.h: config.h.pre config.h.post + rm -f config.log + ( cat config.h.pre; \ + echo; \ + if $(CC) $(CFLAGS) -Werror -o test-strlcat test-strlcat.c >> config.log 2>&1; then \ + echo '#define HAVE_STRLCAT'; \ + rm test-strlcat; \ + fi; \ + if $(CC) $(CFLAGS) -Werror -o test-strlcpy test-strlcpy.c >> config.log 2>&1; then \ + echo '#define HAVE_STRLCPY'; \ + rm test-strlcpy; \ + fi; \ + echo; \ + cat config.h.post \ + ) > $@ -.sgml.html: - validate --warn $< - sed -e "s!@VERSION@!$(VERSION)!" -e "s!@VDATE@!$(VDATE)!" $< > $@ +.h.h.html: + highlight -I $< >$@ .1.1.txt .3.3.txt .7.7.txt: - ./mandoc -Tascii -Wall,stop $< | col -b > $@ + ./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.html .3.3.html .7.7.html: + ./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 $< > $@ + ./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 $< > $@ + ./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 $< > $@ + ./mandoc -Tpdf -Wall,stop $< >$@ -.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 \ - ) > $@ +.sgml.html: + validate --warn $< + sed -e "s!@VERSION@!$(VERSION)!" -e "s!@VDATE@!$(VDATE)!" $< >$@ diff --git a/contrib/mdocml/arch.c b/contrib/mdocml/arch.c index c3d76340ab..e764bfe993 100644 --- a/contrib/mdocml/arch.c +++ b/contrib/mdocml/arch.c @@ -1,4 +1,4 @@ -/* $Id: arch.c,v 1.8 2010/06/19 20:46:27 kristaps Exp $ */ +/* $Id: arch.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ /* * Copyright (c) 2009 Kristaps Dzonsons * @@ -22,6 +22,7 @@ #include #include +#include "mdoc.h" #include "mandoc.h" #include "libmdoc.h" diff --git a/contrib/mdocml/att.c b/contrib/mdocml/att.c index cfd6b4436e..24d757ddf7 100644 --- a/contrib/mdocml/att.c +++ b/contrib/mdocml/att.c @@ -1,4 +1,4 @@ -/* $Id: att.c,v 1.8 2010/06/19 20:46:27 kristaps Exp $ */ +/* $Id: att.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ /* * Copyright (c) 2009 Kristaps Dzonsons * @@ -22,6 +22,7 @@ #include #include +#include "mdoc.h" #include "mandoc.h" #include "libmdoc.h" diff --git a/contrib/mdocml/chars.c b/contrib/mdocml/chars.c index 5edf947ae1..03e44910d8 100644 --- a/contrib/mdocml/chars.c +++ b/contrib/mdocml/chars.c @@ -1,6 +1,7 @@ -/* $Id: chars.c,v 1.31 2011/01/02 10:10:57 kristaps Exp $ */ +/* $Id: chars.c,v 1.34 2011/03/22 10:13:01 kristaps Exp $ */ /* * Copyright (c) 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 @@ -24,7 +25,7 @@ #include #include "mandoc.h" -#include "chars.h" +#include "out.h" #define PRINT_HI 126 #define PRINT_LO 32 @@ -91,17 +92,8 @@ chars_init(enum chars type) * (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); - } + tab = mandoc_malloc(sizeof(struct ctab)); + htab = mandoc_calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln **)); for (i = 0; i < LINES_MAX; i++) { hash = (int)lines[i].code[0] - PRINT_LO; @@ -152,6 +144,27 @@ chars_res2cp(void *arg, const char *p, size_t sz) } +/* + * Numbered character to literal character, + * represented as a null-terminated string for additional safety. + */ +const char * +chars_num2char(const char *p, size_t sz) +{ + int i; + static char c[2]; + + if (sz > 3) + return(NULL); + i = atoi(p); + if (i < 0 || i > 255) + return(NULL); + c[0] = (char)i; + c[1] = '\0'; + return(c); +} + + /* * Special character to string array. */ diff --git a/contrib/mdocml/chars.h b/contrib/mdocml/chars.h deleted file mode 100644 index 894008b673..0000000000 --- a/contrib/mdocml/chars.h +++ /dev/null @@ -1,36 +0,0 @@ -/* $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 index 63d9fb4171..f628960c2d 100644 --- a/contrib/mdocml/chars.in +++ b/contrib/mdocml/chars.in @@ -1,4 +1,4 @@ -/* $Id: chars.in,v 1.35 2010/09/15 13:10:30 kristaps Exp $ */ +/* $Id: chars.in,v 1.36 2011/03/16 22:49:55 schwarze Exp $ */ /* * Copyright (c) 2009, 2010 Kristaps Dzonsons * @@ -187,7 +187,7 @@ CHAR(":U", "U", 220) CHAR(":a", "a", 228) CHAR(":e", "e", 235) CHAR(":i", "i", 239) -CHAR(":o", "o", 245) +CHAR(":o", "o", 246) CHAR(":u", "u", 252) CHAR(":y", "y", 255) CHAR("\'A", "A", 193) diff --git a/contrib/mdocml/eqn.7 b/contrib/mdocml/eqn.7 new file mode 100644 index 0000000000..b2b66f424e --- /dev/null +++ b/contrib/mdocml/eqn.7 @@ -0,0 +1,92 @@ +.\" $Id: eqn.7,v 1.2 2011/03/17 15:12:42 kristaps Exp $ +.\" +.\" Copyright (c) 2011 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 17 2011 $ +.Dt EQN 7 +.Os +.Sh NAME +.Nm eqn +.Nd eqn language reference for mandoc +.Sh DESCRIPTION +The +.Nm eqn +language is a equation-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 +Equations within +.Xr mdoc 7 +or +.Xr man 7 +are enclosed by the +.Sq EQ +and +.Sq EN +macro tags, whose precise syntax is documented in +.Xr roff 7 . +Equations consist of multi-line equation data. +.Pp +For the time being, +.Xr mandoc 1 +reproduces the contents of +.Nm +equations verbatim in its output. +.Pp +The +.Nm +implementation in +.Xr mandoc 1 +is +.Ud +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 +.Rs +.%A Brian W. Kernighan +.%A Lorinda L. Cherry +.%T System for Typesetting Mathematics +.%J Communications of the ACM +.%V 18 +.%P 151\(en157 +.%D March, 1975 +.Re +.\" .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/eqn.c b/contrib/mdocml/eqn.c new file mode 100644 index 0000000000..220f3f88bf --- /dev/null +++ b/contrib/mdocml/eqn.c @@ -0,0 +1,81 @@ +/* $Id: eqn.c,v 1.4 2011/03/22 09:48:13 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +/* ARGSUSED */ +enum rofferr +eqn_read(struct eqn_node **epp, int ln, const char *p, int offs) +{ + size_t sz; + struct eqn_node *ep; + + if (0 == strcmp(p, ".EN")) { + *epp = NULL; + return(ROFF_EQN); + } + + ep = *epp; + + sz = strlen(&p[offs]); + ep->eqn.data = mandoc_realloc(ep->eqn.data, ep->eqn.sz + sz + 1); + if (0 == ep->eqn.sz) + *ep->eqn.data = '\0'; + + ep->eqn.sz += sz; + strlcat(ep->eqn.data, &p[offs], ep->eqn.sz + 1); + return(ROFF_IGN); +} + +struct eqn_node * +eqn_alloc(int pos, int line) +{ + struct eqn_node *p; + + p = mandoc_calloc(1, sizeof(struct eqn_node)); + p->eqn.line = line; + p->eqn.pos = pos; + + return(p); +} + +/* ARGSUSED */ +void +eqn_end(struct eqn_node *e) +{ + + /* Nothing to do. */ +} + +void +eqn_free(struct eqn_node *p) +{ + + free(p->eqn.data); + free(p); +} diff --git a/contrib/mdocml/example.style.css b/contrib/mdocml/example.style.css index 7f640ee834..c7cc484f35 100644 --- a/contrib/mdocml/example.style.css +++ b/contrib/mdocml/example.style.css @@ -1,4 +1,4 @@ -/* $Id: example.style.css,v 1.41 2011/01/05 13:00:11 kristaps Exp $ */ +/* $Id: example.style.css,v 1.42 2011/02/09 09:52:47 kristaps Exp $ */ /* * This is an example style-sheet provided for mandoc(1) and the -Thtml @@ -141,6 +141,10 @@ li.list-item { } ol.list-enum { padding-left: 2em; } li.list-enum { } +/* Equation modes. See eqn(7). */ + +span.eqn { } + /* Table modes. See tbl(7). */ table.tbl { } diff --git a/contrib/mdocml/html.c b/contrib/mdocml/html.c index 70403ff77e..ab57c3743f 100644 --- a/contrib/mdocml/html.c +++ b/contrib/mdocml/html.c @@ -1,6 +1,7 @@ -/* $Id: html.c,v 1.124 2010/12/27 21:41:05 schwarze Exp $ */ +/* $Id: html.c,v 1.131 2011/03/22 14:05:45 kristaps Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2008, 2009, 2010, 2011 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 @@ -31,7 +32,6 @@ #include "mandoc.h" #include "out.h" -#include "chars.h" #include "html.h" #include "main.h" @@ -90,8 +90,10 @@ static const char *const htmlattrs[ATTR_MAX] = { "id", /* ATTR_ID */ "summary", /* ATTR_SUMMARY */ "align", /* ATTR_ALIGN */ + "colspan", /* ATTR_COLSPAN */ }; +static void print_num(struct html *, const char *, size_t); static void print_spec(struct html *, enum roffdeco, const char *, size_t); static void print_res(struct html *, const char *, size_t); @@ -117,11 +119,7 @@ ml_alloc(char *outopts, enum htmltype type) toks[2] = "includes"; toks[3] = NULL; - h = calloc(1, sizeof(struct html)); - if (NULL == h) { - perror(NULL); - exit((int)MANDOCLEVEL_SYSERR); - } + h = mandoc_calloc(1, sizeof(struct html)); h->type = type; h->tags.head = NULL; @@ -211,6 +209,16 @@ print_gen_head(struct html *h) } } +/* ARGSUSED */ +static void +print_num(struct html *h, const char *p, size_t len) +{ + const char *rhs; + + rhs = chars_num2char(p, len); + if (rhs) + putchar((int)*rhs); +} static void print_spec(struct html *h, enum roffdeco d, const char *p, size_t len) @@ -332,6 +340,9 @@ print_encode(struct html *h, const char *p, int norecurse) len = a2roffdeco(&deco, &seq, &sz); switch (deco) { + case (DECO_NUMBERED): + print_num(h, seq, sz); + break; case (DECO_RESERVED): print_res(h, seq, sz); break; @@ -384,11 +395,7 @@ print_otag(struct html *h, enum htmltag tag, /* 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 = mandoc_malloc(sizeof(struct tag)); t->tag = tag; t->next = h->tags.head; h->tags.head = t; @@ -501,35 +508,10 @@ print_doctype(struct html *h) 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)) { @@ -557,21 +539,6 @@ print_text(struct html *h, const char *word) } 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; - } } @@ -581,8 +548,14 @@ print_tagq(struct html *h, const struct tag *until) struct tag *tag; while ((tag = h->tags.head) != NULL) { + /* + * Remember to close out and nullify the current + * meta-font and table, if applicable. + */ if (tag == h->metaf) h->metaf = NULL; + if (tag == h->tblt) + h->tblt = NULL; print_ctag(h, tag->tag); h->tags.head = tag->next; free(tag); @@ -600,8 +573,14 @@ print_stagq(struct html *h, const struct tag *suntil) while ((tag = h->tags.head) != NULL) { if (suntil && tag == suntil) return; + /* + * Remember to close out and nullify the current + * meta-font and table, if applicable. + */ if (tag == h->metaf) h->metaf = NULL; + if (tag == h->tblt) + h->tblt = NULL; print_ctag(h, tag->tag); h->tags.head = tag->next; free(tag); diff --git a/contrib/mdocml/html.h b/contrib/mdocml/html.h index 8d9db89095..561d06e2de 100644 --- a/contrib/mdocml/html.h +++ b/contrib/mdocml/html.h @@ -1,4 +1,4 @@ -/* $Id: html.h,v 1.38 2011/01/06 11:55:39 kristaps Exp $ */ +/* $Id: html.h,v 1.40 2011/01/29 14:49:44 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons * @@ -67,6 +67,7 @@ enum htmlattr { ATTR_ID, ATTR_SUMMARY, ATTR_ALIGN, + ATTR_COLSPAN, ATTR_MAX }; @@ -110,13 +111,15 @@ enum htmltype { struct html { int flags; -#define HTML_NOSPACE (1 << 0) +#define HTML_NOSPACE (1 << 0) /* suppress next space */ #define HTML_IGNDELIM (1 << 1) #define HTML_KEEP (1 << 2) #define HTML_PREKEEP (1 << 3) -#define HTML_NONOSPACE (1 << 4) +#define HTML_NONOSPACE (1 << 4) /* never add spaces */ +#define HTML_LITERAL (1 << 5) /* literal (e.g.,
) context */
 	struct tagq	  tags; /* stack of open tags */
 	struct rofftbl	  tbl; /* current table */
+	struct tag	 *tblt; /* current open table scope */
 	void		 *symtab; /* character-escapes */
 	char		 *base_man; /* base for manpage href */
 	char		 *base_includes; /* base for include href */
@@ -136,6 +139,7 @@ struct tag	 *print_otag(struct html *, enum htmltag,
 void		  print_tagq(struct html *, const struct tag *);
 void		  print_stagq(struct html *, const struct tag *);
 void		  print_text(struct html *, const char *);
+void		  print_tblclose(struct html *);
 void		  print_tbl(struct html *, const struct tbl_span *);
 
 void		  bufcat_su(struct html *, const char *, 
diff --git a/contrib/mdocml/index.sgml b/contrib/mdocml/index.sgml
index 7abc9975f2..d4fd1edc62 100644
--- a/contrib/mdocml/index.sgml
+++ b/contrib/mdocml/index.sgml
@@ -39,10 +39,9 @@
 					

- 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 mdocml consists of the libmandoc validating + compiler and mandoc, which interfaces with the compiler library to format + output for UNIX terminals, XHTML, HTML, PostScript, and PDF. It is a BSD.lv project.

@@ -59,9 +58,8 @@

- 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. + mdocml is in plain-old ANSI C and should build and run on any UNIX system. + The most current version is @VERSION@, dated @VDATE@.

@@ -172,38 +170,14 @@ - man(3) + mandoc(3) - man macro compiler library + mandoc 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) + (text | + xhtml | + pdf | + postscript) @@ -219,6 +193,18 @@ + + eqn(7) + + eqn-mandoc language reference + + (text | + xhtml | + pdf | + postscript) + + + mandoc_char(7) @@ -283,8 +269,12 @@

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

@@ -332,6 +322,27 @@

NEWS

+
+

+ 04-04-2011: + version 1.11.1 +

+

+ The earlier libroff, libmdoc, and libman soup have been merged into + a single library, libmandoc, which manages all aspects of + parsing real manuals (from line-handling to tbl parsing). +

+

+ Beyond this structural change, initial eqn functionality is in + place. For the time being, this is limited to the recognition of equation blocks; + future version of mdocml will expand upon this framework. +

+

+ As usual, many general fixes and improvements have also occured. In particular, a great + deal of redundancy and superfluous code has been removed with the merging of the backend + libraries. +

+

07-01-2011: @@ -348,7 +359,7 @@ 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. + libroff: soelim and tbl.

@@ -375,7 +386,7 @@ HREF="#documentation">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 + Lastly, libmdoc and libman have been cleaned up and reduced in size and complexity.

@@ -388,7 +399,7 @@
- Copyright © 2008–2010 Kristaps Dzonsons, $Date: 2011/01/07 13:10:03 $ + Copyright © 2008–2011 Kristaps Dzonsons, $Date: 2011/04/04 21:07:20 $
diff --git a/contrib/mdocml/lib.c b/contrib/mdocml/lib.c index bbf2aec8bb..7a18a5dd4f 100644 --- a/contrib/mdocml/lib.c +++ b/contrib/mdocml/lib.c @@ -1,4 +1,4 @@ -/* $Id: lib.c,v 1.8 2010/06/19 20:46:27 kristaps Exp $ */ +/* $Id: lib.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ /* * Copyright (c) 2009 Kristaps Dzonsons * @@ -22,6 +22,7 @@ #include #include +#include "mdoc.h" #include "mandoc.h" #include "libmdoc.h" diff --git a/contrib/mdocml/libman.h b/contrib/mdocml/libman.h index e1a9aecc8e..30c159f7da 100644 --- a/contrib/mdocml/libman.h +++ b/contrib/mdocml/libman.h @@ -1,4 +1,4 @@ -/* $Id: libman.h,v 1.44 2010/11/30 15:36:28 kristaps Exp $ */ +/* $Id: libman.h,v 1.51 2011/03/23 15:33:57 kristaps Exp $ */ /* * Copyright (c) 2009, 2010 Kristaps Dzonsons * @@ -17,16 +17,13 @@ #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 */ + struct mparse *parse; /* parse pointer */ int flags; /* parse flags */ #define MAN_HALT (1 << 0) /* badness happened: die */ #define MAN_ELINE (1 << 1) /* Next-line element scope. */ @@ -34,6 +31,7 @@ struct man { #define MAN_ILINE (1 << 3) /* Ignored in next-line scope. */ #define MAN_LITERAL (1 << 4) /* Literal input. */ #define MAN_BPLINE (1 << 5) +#define MAN_NEWLINE (1 << 6) /* first macro/text in a line */ enum man_next next; /* where to put the next node */ struct man_node *last; /* the last parsed node */ struct man_node *first; /* the first parsed node */ @@ -63,25 +61,19 @@ 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) + mandoc_msg((t), (m)->parse, (l), (p), NULL) #define man_nmsg(m, n, t) \ - (*(m)->msg)((t), (m)->data, (n)->line, (n)->pos, NULL) + mandoc_msg((t), (m)->parse, (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_tail_alloc(struct man *, int, int, enum mant); int man_body_alloc(struct man *, int, int, enum mant); int man_elem_alloc(struct man *, int, int, enum mant); void man_node_delete(struct man *, struct man_node *); void man_hash_init(void); -enum mant man_hash_find(const char *); +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 *, diff --git a/contrib/mdocml/libmandoc.h b/contrib/mdocml/libmandoc.h index 0e9a749282..3157f290f2 100644 --- a/contrib/mdocml/libmandoc.h +++ b/contrib/mdocml/libmandoc.h @@ -1,6 +1,6 @@ -/* $Id: libmandoc.h,v 1.10 2011/01/03 22:42:37 schwarze Exp $ */ +/* $Id: libmandoc.h,v 1.17 2011/03/28 23:52:13 kristaps Exp $ */ /* - * Copyright (c) 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,21 +17,95 @@ #ifndef LIBMANDOC_H #define LIBMANDOC_H +enum rofferr { + ROFF_CONT, /* continue processing line */ + ROFF_RERUN, /* re-run roff interpreter with offset */ + ROFF_APPEND, /* re-run main parser, appending next line */ + ROFF_REPARSE, /* re-run main parser on the result */ + ROFF_SO, /* include another file */ + ROFF_IGN, /* ignore current line */ + ROFF_TBL, /* a table row was successfully parsed */ + ROFF_EQN, /* an equation was successfully parsed */ + ROFF_ERR /* badness: puke and stop */ +}; + +enum regs { + REG_nS = 0, /* nS register */ + REG__MAX +}; + +/* + * A register (struct reg) can consist of many types: this consists of + * normalised types from the original string form. For the time being, + * there's only an unsigned integer type. + */ +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 +struct roff; +struct mdoc; +struct man; + +void mandoc_msg(enum mandocerr, struct mparse *, + int, int, const char *); +void mandoc_vmsg(enum mandocerr, struct mparse *, + int, int, const char *, ...); 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) +char *mandoc_getarg(struct mparse *, char **, int, int *); +char *mandoc_normdate(struct mparse *, char *, int, int); int mandoc_eos(const char *, size_t, int); int mandoc_hyph(const char *, const char *); +int mandoc_getcontrol(const char *, int *); + +void mdoc_free(struct mdoc *); +struct mdoc *mdoc_alloc(struct regset *, struct mparse *); +void mdoc_reset(struct mdoc *); +int mdoc_parseln(struct mdoc *, int, char *, int); +int mdoc_endparse(struct mdoc *); +int mdoc_addspan(struct mdoc *, const struct tbl_span *); +int mdoc_addeqn(struct mdoc *, const struct eqn *); + +void man_free(struct man *); +struct man *man_alloc(struct regset *, struct mparse *); +void man_reset(struct man *); +int man_parseln(struct man *, int, char *, int); +int man_endparse(struct man *); +int man_addspan(struct man *, const struct tbl_span *); +int man_addeqn(struct man *, const struct eqn *); + +void roff_free(struct roff *); +struct roff *roff_alloc(struct regset *, struct mparse *); +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 *); +const struct eqn *roff_eqn(const struct roff *); __END_DECLS diff --git a/contrib/mdocml/libmdoc.h b/contrib/mdocml/libmdoc.h index 5a46d1cfb9..2a55eb37be 100644 --- a/contrib/mdocml/libmdoc.h +++ b/contrib/mdocml/libmdoc.h @@ -1,4 +1,4 @@ -/* $Id: libmdoc.h,v 1.63 2010/11/30 13:04:14 kristaps Exp $ */ +/* $Id: libmdoc.h,v 1.72 2011/03/22 14:33:05 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons * @@ -17,17 +17,14 @@ #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; + struct mparse *parse; /* parse pointer */ + int flags; /* parse 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 */ @@ -81,11 +78,21 @@ enum margverr { ARGV_WORD }; +/* + * A punctuation delimiter is opening, closing, or "middle mark" + * punctuation. These govern spacing. + * Opening punctuation (e.g., the opening parenthesis) suppresses the + * following space; closing punctuation (e.g., the closing parenthesis) + * suppresses the leading space; middle punctuation (e.g., the vertical + * bar) can do either. The middle punctuation delimiter bends the rules + * depending on usage. + */ enum mdelim { DELIM_NONE = 0, DELIM_OPEN, DELIM_MIDDLE, - DELIM_CLOSE + DELIM_CLOSE, + DELIM_MAX }; extern const struct mdoc_macro *const mdoc_macros; @@ -93,11 +100,9 @@ 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) + mandoc_msg((t), (m)->parse, (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 *, ...); + mandoc_msg((t), (m)->parse, (n)->line, (n)->pos, NULL) int mdoc_macro(MACRO_PROT_ARGS); int mdoc_word_alloc(struct mdoc *, int, int, const char *); @@ -114,12 +119,6 @@ int mdoc_endbody_alloc(struct mdoc *m, int line, int pos, 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 *); @@ -131,7 +130,6 @@ 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, @@ -142,6 +140,9 @@ enum margserr mdoc_zargs(struct mdoc *, int, int mdoc_macroend(struct mdoc *); +#define DELIMSZ 6 /* hint: max possible size of a delimiter */ +enum mdelim mdoc_isdelim(const char *); + __END_DECLS #endif /*!LIBMDOC_H*/ diff --git a/contrib/mdocml/libroff.h b/contrib/mdocml/libroff.h index b4e043a7ac..d013688778 100644 --- a/contrib/mdocml/libroff.h +++ b/contrib/mdocml/libroff.h @@ -1,6 +1,6 @@ -/* $Id: libroff.h,v 1.16 2011/01/04 15:02:00 kristaps Exp $ */ +/* $Id: libroff.h,v 1.20 2011/03/20 16:02:05 kristaps Exp $ */ /* - * Copyright (c) 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -27,8 +27,7 @@ enum tbl_part { }; struct tbl_node { - mandocmsg msg; /* status messages */ - void *data; /* privdata for messages */ + struct mparse *parse; /* parse point */ int pos; /* invocation column */ int line; /* invocation line */ enum tbl_part part; @@ -36,16 +35,19 @@ struct tbl_node { struct tbl_row *first_row; struct tbl_row *last_row; struct tbl_span *first_span; + struct tbl_span *current_span; struct tbl_span *last_span; struct tbl_head *first_head; struct tbl_head *last_head; struct tbl_node *next; }; -#define TBL_MSG(tblp, type, line, col) \ - (*(tblp)->msg)((type), (tblp)->data, (line), (col), NULL) +struct eqn_node { + struct eqn eqn; + struct eqn_node *next; +}; -struct tbl_node *tbl_alloc(int, int, void *, mandocmsg); +struct tbl_node *tbl_alloc(int, int, struct mparse *); void tbl_restart(int, int, struct tbl_node *); void tbl_free(struct tbl_node *); void tbl_reset(struct tbl_node *); @@ -54,8 +56,12 @@ 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 *); +const struct tbl_span *tbl_span(struct tbl_node *); void tbl_end(struct tbl_node *); +struct eqn_node *eqn_alloc(int, int); +void eqn_end(struct eqn_node *); +void eqn_free(struct eqn_node *); +enum rofferr eqn_read(struct eqn_node **, int, const char *, int); __END_DECLS diff --git a/contrib/mdocml/main.c b/contrib/mdocml/main.c index 2be68a9350..bbbb88466a 100644 --- a/contrib/mdocml/main.c +++ b/contrib/mdocml/main.c @@ -1,7 +1,7 @@ -/* $Id: main.c,v 1.135 2011/01/04 15:02:00 kristaps Exp $ */ +/* $Id: main.c,v 1.161 2011/03/31 10:53:43 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2010 Ingo Schwarze + * 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 @@ -19,12 +19,7 @@ #include "config.h" #endif -#include -#include - #include -#include -#include #include #include #include @@ -35,16 +30,6 @@ #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) @@ -56,41 +41,20 @@ 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 + OUTT_ASCII = 0, /* -Tascii */ + OUTT_TREE, /* -Ttree */ + OUTT_HTML, /* -Thtml */ + OUTT_XHTML, /* -Txhtml */ + OUTT_LINT, /* -Tlint */ + OUTT_PS, /* -Tps */ + OUTT_PDF /* -Tpdf */ }; 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 */ + struct mparse *mp; + enum mandoclevel wlevel; /* ignore messages below this */ + int wstop; /* stop after a file with a warning */ enum outt outtype; /* which output to use */ out_mdoc outmdoc; /* mdoc output ptr */ out_man outman; /* man output ptr */ @@ -99,154 +63,25 @@ struct curparse { 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 moptions(enum mparset *, char *); +static void mmsg(enum mandocerr, enum mandoclevel, + const char *, int, int, const char *); +static void parse(struct curparse *, int, + const char *, enum mandoclevel *); static int toptions(struct curparse *, char *); static void usage(void) __attribute__((noreturn)); static void version(void) __attribute__((noreturn)); static int woptions(struct curparse *, char *); static const char *progname; -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; + enum mparset type; + enum mandoclevel rc; progname = strrchr(argv[0], '/'); if (progname == NULL) @@ -256,7 +91,7 @@ main(int argc, char *argv[]) memset(&curp, 0, sizeof(struct curparse)); - curp.inttype = INTT_AUTO; + type = MPARSE_AUTO; curp.outtype = OUTT_ASCII; curp.wlevel = MANDOCLEVEL_FATAL; @@ -264,7 +99,7 @@ main(int argc, char *argv[]) while (-1 != (c = getopt(argc, argv, "m:O:T:VW:"))) switch (c) { case ('m'): - if ( ! moptions(&curp.inttype, optarg)) + if ( ! moptions(&type, optarg)) return((int)MANDOCLEVEL_BADARG); break; case ('O'): @@ -287,56 +122,50 @@ main(int argc, char *argv[]) /* NOTREACHED */ } + curp.mp = mparse_alloc(type, curp.wlevel, mmsg, &curp); + argc -= optind; argv += optind; - if (NULL == *argv) { - curp.file = ""; - curp.fd = STDIN_FILENO; + rc = MANDOCLEVEL_OK; - fdesc(&curp); - } + if (NULL == *argv) + parse(&curp, STDIN_FILENO, "", &rc); while (*argv) { - ffile(*argv, &curp); - if (MANDOCLEVEL_OK != exit_status && curp.wstop) + parse(&curp, -1, *argv, &rc); + if (MANDOCLEVEL_OK != rc && 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); + if (curp.mp) + mparse_free(curp.mp); - return((int)exit_status); + return((int)rc); } - static void version(void) { - (void)printf("%s %s\n", progname, VERSION); + printf("%s %s\n", progname, VERSION); exit((int)MANDOCLEVEL_OK); } - static void usage(void) { - (void)fprintf(stderr, "usage: %s " + fprintf(stderr, "usage: %s " "[-V] " "[-foption] " "[-mformat] " "[-Ooption] " "[-Toutput] " - "[-Werr] " + "[-Wlevel] " "[file...]\n", progname); @@ -344,205 +173,31 @@ usage(void) } 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) +parse(struct curparse *curp, int fd, + const char *file, enum mandoclevel *level) { + enum mandoclevel rc; + struct mdoc *mdoc; + struct man *man; - /* - * 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. */ + /* Begin by parsing the file itself. */ - if (NULL == curp->roff) { - curp->roff = roff_alloc(&curp->regs, curp, mmsg); - assert(curp->roff); - } - - /* Fully parse the file. */ - - pdesc(curp); + assert(file); + assert(fd >= -1); - if (MANDOCLEVEL_FATAL <= file_status) - goto cleanup; + rc = mparse_readfd(curp->mp, fd, file); - /* NOTE a parser may not have been assigned, yet. */ + /* Stop immediately if the parse has failed. */ - if ( ! (curp->man || curp->mdoc)) { - fprintf(stderr, "%s: Not a manual\n", curp->file); - file_status = MANDOCLEVEL_FATAL; + if (MANDOCLEVEL_FATAL <= rc) 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. + * With -Wstop and warnings or errors of at least the requested + * level, do not produce output. */ - if (MANDOCLEVEL_OK != file_status && curp->wstop) + if (MANDOCLEVEL_OK != rc && curp->wstop) goto cleanup; /* If unset, allocate output dev now (if applicable). */ @@ -596,347 +251,33 @@ fdesc(struct curparse *curp) } } + mparse_result(curp->mp, &mdoc, &man); + /* 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); + if (man && curp->outman) + (*curp->outman)(curp->outdata, man); + if (mdoc && curp->outmdoc) + (*curp->outmdoc)(curp->outdata, 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); + mparse_reset(curp->mp); - 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; + if (*level < rc) + *level = rc; } static int -moptions(enum intt *tflags, char *arg) +moptions(enum mparset *tflags, char *arg) { if (0 == strcmp(arg, "doc")) - *tflags = INTT_MDOC; + *tflags = MPARSE_MDOC; else if (0 == strcmp(arg, "andoc")) - *tflags = INTT_AUTO; + *tflags = MPARSE_AUTO; else if (0 == strcmp(arg, "an")) - *tflags = INTT_MAN; + *tflags = MPARSE_MAN; else { fprintf(stderr, "%s: Bad argument\n", arg); return(0); @@ -954,8 +295,7 @@ toptions(struct curparse *curp, char *arg) else if (0 == strcmp(arg, "lint")) { curp->outtype = OUTT_LINT; curp->wlevel = MANDOCLEVEL_WARNING; - } - else if (0 == strcmp(arg, "tree")) + } else if (0 == strcmp(arg, "tree")) curp->outtype = OUTT_TREE; else if (0 == strcmp(arg, "html")) curp->outtype = OUTT_HTML; @@ -1012,29 +352,18 @@ woptions(struct curparse *curp, char *arg) return(1); } -static int -mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg) +static void +mmsg(enum mandocerr t, enum mandoclevel lvl, + const char *file, int line, int col, const char *msg) { - struct curparse *cp; - enum mandoclevel level; - level = MANDOCLEVEL_FATAL; - while (t < mandoclimits[level]) - /* LINTED */ - level--; + fprintf(stderr, "%s:%d:%d: %s: %s", + file, line, col + 1, + mparse_strlevel(lvl), + mparse_strerror(t)); - 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); + fputc('\n', stderr); } diff --git a/contrib/mdocml/man.3 b/contrib/mdocml/man.3 deleted file mode 100644 index 2b2d0a910e..0000000000 --- a/contrib/mdocml/man.3 +++ /dev/null @@ -1,272 +0,0 @@ -.\" $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 index d8bd6ee7ae..876f32a312 100644 --- a/contrib/mdocml/man.7 +++ b/contrib/mdocml/man.7 @@ -1,4 +1,4 @@ -.\" $Id: man.7,v 1.94 2011/01/04 23:32:21 kristaps Exp $ +.\" $Id: man.7,v 1.99 2011/03/07 01:35:51 schwarze Exp $ .\" .\" Copyright (c) 2009, 2010 Kristaps Dzonsons .\" @@ -14,7 +14,7 @@ .\" 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 $ +.Dd $Mdocdate: March 7 2011 $ .Dt MAN 7 .Os .Sh NAME @@ -53,12 +53,12 @@ Other lines are interpreted within the current state. .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. +.Pp +If the first character of a line is a space, that line is printed +with a leading newline. .Ss Comments Text following a .Sq \e\*q , @@ -118,15 +118,6 @@ 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: @@ -370,6 +361,13 @@ Thus, the following are equivalent: \&.\ \ \ PP .Ed .Pp +To include space characters in macro arguments, arguments may be quoted; +see the +.Sq MACRO SYNTAX +section in the +.Xr roff 7 +manual for details. +.Pp The .Nm macros are classified by scope: line scope or block scope. @@ -756,26 +754,27 @@ The paragraph left-margin width is reset to the default. 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 +.Ar title section date +.Op Ar source Op Ar 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 +Conventionally, the document +.Ar title +is given in all caps. +The recommended +.Ar date +format is +.Sy YYYY-MM-DD +as specified in the ISO-8601 standard; +if the argument does not conform, it is printed verbatim. +If the +.Ar date +is empty or not specified, the current date is used. +The optional +.Ar source string specifies the organisation providing the utility. The -.Cm volume +.Ar volume string replaces the default rendered volume, which is dictated by the manual section. .Pp @@ -912,6 +911,7 @@ In GNU troff, this would result in strange behaviour. .Sh SEE ALSO .Xr man 1 , .Xr mandoc 1 , +.Xr eqn 7 , .Xr mandoc_char 7 , .Xr mdoc 7 , .Xr roff 7 , diff --git a/contrib/mdocml/man.c b/contrib/mdocml/man.c index 6788c92399..50a0cf0e95 100644 --- a/contrib/mdocml/man.c +++ b/contrib/mdocml/man.c @@ -1,6 +1,6 @@ -/* $Id: man.c,v 1.96 2011/01/03 11:31:26 kristaps Exp $ */ +/* $Id: man.c,v 1.107 2011/03/29 08:30:49 kristaps Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,6 +26,7 @@ #include #include +#include "man.h" #include "mandoc.h" #include "libman.h" #include "libmandoc.h" @@ -44,12 +45,10 @@ const char *const __man_macronames[MAN_MAX] = { const char * const *man_macronames = __man_macronames; -static struct man_node *man_node_alloc(int, int, +static struct man_node *man_node_alloc(struct man *, int, int, enum man_type, enum mant); static int man_node_append(struct man *, struct man_node *); -static 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 *); @@ -97,15 +96,14 @@ man_free(struct man *man) struct man * -man_alloc(struct regset *regs, void *data, mandocmsg msg) +man_alloc(struct regset *regs, struct mparse *parse) { struct man *p; p = mandoc_calloc(1, sizeof(struct man)); man_hash_init(); - p->data = data; - p->msg = msg; + p->parse = parse; p->regs = regs; man_alloc1(p); @@ -129,8 +127,11 @@ int man_parseln(struct man *m, int ln, char *buf, int offs) { + m->flags |= MAN_NEWLINE; + assert( ! (MAN_HALT & m->flags)); - return(('.' == buf[offs] || '\'' == buf[offs]) ? + + return (mandoc_getcontrol(buf, &offs) ? man_pmacro(m, ln, buf, offs) : man_ptext(m, ln, buf, offs)); } @@ -146,8 +147,8 @@ man_free1(struct man *man) free(man->meta.title); if (man->meta.source) free(man->meta.source); - if (man->meta.rawdate) - free(man->meta.rawdate); + if (man->meta.date) + free(man->meta.date); if (man->meta.vol) free(man->meta.vol); if (man->meta.msec) @@ -203,6 +204,10 @@ man_node_append(struct man *man, struct man_node *p) assert(MAN_BLOCK == p->parent->type); p->parent->head = p; break; + case (MAN_TAIL): + assert(MAN_BLOCK == p->parent->type); + p->parent->tail = p; + break; case (MAN_BODY): assert(MAN_BLOCK == p->parent->type); p->parent->body = p; @@ -229,7 +234,8 @@ man_node_append(struct man *man, struct man_node *p) static struct man_node * -man_node_alloc(int line, int pos, enum man_type type, enum mant tok) +man_node_alloc(struct man *m, int line, int pos, + enum man_type type, enum mant tok) { struct man_node *p; @@ -238,6 +244,10 @@ man_node_alloc(int line, int pos, enum man_type type, enum mant tok) p->pos = pos; p->type = type; p->tok = tok; + + if (MAN_NEWLINE & m->flags) + p->flags |= MAN_LINE; + m->flags &= ~MAN_NEWLINE; return(p); } @@ -247,7 +257,7 @@ 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); + p = man_node_alloc(m, line, pos, MAN_ELEM, tok); if ( ! man_node_append(m, p)) return(0); m->next = MAN_NEXT_CHILD; @@ -256,11 +266,11 @@ man_elem_alloc(struct man *m, int line, int pos, enum mant tok) int -man_head_alloc(struct man *m, int line, int pos, enum mant tok) +man_tail_alloc(struct man *m, int line, int pos, enum mant tok) { struct man_node *p; - p = man_node_alloc(line, pos, MAN_HEAD, tok); + p = man_node_alloc(m, line, pos, MAN_TAIL, tok); if ( ! man_node_append(m, p)) return(0); m->next = MAN_NEXT_CHILD; @@ -269,11 +279,11 @@ man_head_alloc(struct man *m, int line, int pos, enum mant tok) int -man_body_alloc(struct man *m, int line, int pos, enum mant tok) +man_head_alloc(struct man *m, int line, int pos, enum mant tok) { struct man_node *p; - p = man_node_alloc(line, pos, MAN_BODY, tok); + p = man_node_alloc(m, line, pos, MAN_HEAD, tok); if ( ! man_node_append(m, p)) return(0); m->next = MAN_NEXT_CHILD; @@ -282,30 +292,27 @@ man_body_alloc(struct man *m, int line, int pos, enum mant tok) int -man_block_alloc(struct man *m, int line, int pos, enum mant tok) +man_body_alloc(struct man *m, int line, int pos, enum mant tok) { struct man_node *p; - p = man_node_alloc(line, pos, MAN_BLOCK, tok); + p = man_node_alloc(m, line, pos, MAN_BODY, 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; +int +man_block_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; - if ( ! man_node_append(m, n)) + p = man_node_alloc(m, line, pos, MAN_BLOCK, tok); + if ( ! man_node_append(m, p)) return(0); - - m->next = MAN_NEXT_SIBLING; + m->next = MAN_NEXT_CHILD; return(1); } @@ -317,7 +324,7 @@ man_word_alloc(struct man *m, int line, int pos, const char *word) len = strlen(word); - n = man_node_alloc(line, pos, MAN_TEXT, MAN_MAX); + n = man_node_alloc(m, line, pos, MAN_TEXT, MAN_MAX); n->string = mandoc_malloc(len + 1); sv = strlcpy(n->string, word, len + 1); @@ -357,15 +364,38 @@ man_node_delete(struct man *m, struct man_node *p) man_node_free(p); } +int +man_addeqn(struct man *m, const struct eqn *ep) +{ + struct man_node *n; + + assert( ! (MAN_HALT & m->flags)); + + n = man_node_alloc(m, ep->line, ep->pos, MAN_EQN, MAN_MAX); + n->eqn = ep; + + if ( ! man_node_append(m, n)) + return(0); + + m->next = MAN_NEXT_SIBLING; + return(man_descope(m, ep->line, ep->pos)); +} int man_addspan(struct man *m, const struct tbl_span *sp) { + struct man_node *n; assert( ! (MAN_HALT & m->flags)); - if ( ! man_span_alloc(m, sp)) + + n = man_node_alloc(m, sp->line, 0, MAN_TBL, MAN_MAX); + n->span = sp; + + if ( ! man_node_append(m, n)) return(0); - return(man_descope(m, 0, 0)); + + m->next = MAN_NEXT_SIBLING; + return(man_descope(m, sp->line, 0)); } static int @@ -392,21 +422,11 @@ man_descope(struct man *m, int line, int offs) 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) { @@ -464,67 +484,54 @@ man_ptext(struct man *m, int line, char *buf, int offs) return(man_descope(m, line, offs)); } - static int man_pmacro(struct man *m, int ln, char *buf, int offs) { - int i, j, ppos; + int i, ppos; enum mant tok; char mac[5]; struct man_node *n; - /* Comments and empties are quickly ignored. */ - - offs++; - - if ('\0' == buf[offs]) + if ('"' == buf[offs]) { + man_pmsg(m, ln, offs, MANDOCERR_BADCOMMENT); + return(1); + } else 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; + ppos = offs; /* * 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'; + i = 0; + while (i < 4 && '\0' != buf[offs] && + ' ' != buf[offs] && '\t' != buf[offs]) + mac[i++] = buf[offs++]; + + mac[i] = '\0'; + + tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX; - 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); + mandoc_vmsg(MANDOCERR_MACRO, m->parse, ln, + ppos, "%s", buf + ppos - 1); return(1); } /* The macro is sane. Jump to the next word. */ - while (buf[i] && ' ' == buf[i]) - i++; + while (buf[offs] && ' ' == buf[offs]) + offs++; /* * Trailing whitespace. Note that tabs are allowed to be passed * into the parser as "text", so we only warn about spaces here. */ - if ('\0' == buf[i] && ' ' == buf[i - 1]) - man_pmsg(m, ln, i - 1, MANDOCERR_EOLNSPACE); + if ('\0' == buf[offs] && ' ' == buf[offs - 1]) + man_pmsg(m, ln, offs - 1, MANDOCERR_EOLNSPACE); /* * Remove prior ELINE macro, as it's being clobbered by a new @@ -542,8 +549,8 @@ man_pmacro(struct man *m, int ln, char *buf, int offs) 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]); + mandoc_vmsg(MANDOCERR_LINESCOPE, m->parse, n->line, + n->pos, "%s", man_macronames[n->tok]); man_node_delete(m, n); m->flags &= ~MAN_ELINE; @@ -561,10 +568,9 @@ man_pmacro(struct man *m, int ln, char *buf, int offs) /* Call to handler... */ assert(man_macros[tok].fp); - if ( ! (*man_macros[tok].fp)(m, tok, ln, ppos, &i, buf)) + if ( ! (*man_macros[tok].fp)(m, tok, ln, ppos, &offs, buf)) goto err; -out: /* * We weren't in a block-line scope when entering the * above-parsed macro, so return. @@ -601,7 +607,7 @@ out: if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX)) return(0); - return(man_body_alloc(m, ln, offs, m->last->tok)); + return(man_body_alloc(m, ln, ppos, m->last->tok)); err: /* Error out. */ @@ -609,21 +615,6 @@ err: /* Error out. */ 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. diff --git a/contrib/mdocml/man.h b/contrib/mdocml/man.h index 581f55ffc4..1b0bb6878d 100644 --- a/contrib/mdocml/man.h +++ b/contrib/mdocml/man.h @@ -1,6 +1,6 @@ -/* $Id: man.h,v 1.50 2011/01/01 12:59:17 kristaps Exp $ */ +/* $Id: man.h,v 1.58 2011/03/23 12:33:01 kristaps Exp $ */ /* - * Copyright (c) 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,9 +17,6 @@ #ifndef MAN_H #define MAN_H -/* - * What follows is a list of ALL possible macros. - */ enum mant { MAN_br = 0, MAN_TH, @@ -57,9 +54,6 @@ enum mant { MAN_MAX }; -/* - * Type of a syntax node. - */ enum man_type { MAN_TEXT, MAN_ELEM, @@ -67,24 +61,19 @@ enum man_type { MAN_BLOCK, MAN_HEAD, MAN_BODY, - MAN_TBL + MAN_TAIL, + MAN_TBL, + MAN_EQN }; -/* - * 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 *date; /* `TH' normalised 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 */ @@ -97,31 +86,23 @@ struct man_node { int flags; #define MAN_VALID (1 << 0) /* has been validated */ #define MAN_EOS (1 << 2) /* at sentence boundary */ +#define MAN_LINE (1 << 3) /* first macro/text on line */ enum man_type type; /* AST node type */ char *string; /* TEXT node argument */ struct man_node *head; /* BLOCK node HEAD ptr */ + struct man_node *tail; /* BLOCK node TAIL ptr */ struct man_node *body; /* BLOCK node BODY ptr */ const struct tbl_span *span; /* TBL */ + const struct eqn *eqn; /* EQN */ }; -/* - * Names of macros. Index is enum mant. Indexing into this returns - * the normalised name, e.g., man_macronames[MAN_SH] -> "SH". - */ +/* Names of macros. Index is enum mant. */ 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 *); diff --git a/contrib/mdocml/man_argv.c b/contrib/mdocml/man_argv.c deleted file mode 100644 index 37aac030c1..0000000000 --- a/contrib/mdocml/man_argv.c +++ /dev/null @@ -1,44 +0,0 @@ -/* $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 index 6524f36e4d..c52640e802 100644 --- a/contrib/mdocml/man_hash.c +++ b/contrib/mdocml/man_hash.c @@ -1,4 +1,4 @@ -/* $Id: man_hash.c,v 1.23 2010/07/31 23:52:58 schwarze Exp $ */ +/* $Id: man_hash.c,v 1.24 2011/03/22 14:33:05 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons * @@ -26,6 +26,7 @@ #include #include +#include "man.h" #include "mandoc.h" #include "libman.h" diff --git a/contrib/mdocml/man_html.c b/contrib/mdocml/man_html.c index da6880a1c4..610e58fdbd 100644 --- a/contrib/mdocml/man_html.c +++ b/contrib/mdocml/man_html.c @@ -1,6 +1,6 @@ -/* $Id: man_html.c,v 1.62 2011/01/07 13:20:58 kristaps Exp $ */ +/* $Id: man_html.c,v 1.70 2011/03/07 01:35:51 schwarze Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -67,7 +67,7 @@ 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 void 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); @@ -100,7 +100,7 @@ static const struct htmlman mans[MAN_MAX] = { { man_I_pre, NULL }, /* I */ { man_alt_pre, NULL }, /* IR */ { man_alt_pre, NULL }, /* RI */ - { NULL, NULL }, /* na */ + { man_ign_pre, NULL }, /* na */ { man_br_pre, NULL }, /* sp */ { man_literal_pre, NULL }, /* nf */ { man_literal_pre, NULL }, /* fi */ @@ -180,40 +180,74 @@ print_man_node(MAN_ARGS) { int child; struct tag *t; + struct htmlpair tag; 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); + man_root_pre(m, n, mh, h); break; case (MAN_TEXT): + /* + * If we have a blank line, output a vertical space. + * If we have a space as the first character, break + * before printing the line's data. + */ + if ('\0' == *n->string) { + print_otag(h, TAG_P, 0, NULL); + return; + } else if (' ' == *n->string && MAN_LINE & n->flags) + print_otag(h, TAG_BR, 0, NULL); + print_text(h, n->string); - if (MANH_LITERAL & mh->fl) + + /* + * If we're in a literal context, make sure that words + * togehter on the same line stay together. This is a + * POST-printing call, so we check the NEXT word. Since + * -man doesn't have nested macros, we don't need to be + * more specific than this. + */ + if (MANH_LITERAL & mh->fl && + (NULL == n->next || + n->next->line > n->line)) print_otag(h, TAG_BR, 0, NULL); return; + case (MAN_EQN): + PAIR_CLASS_INIT(&tag, "eqn"); + print_otag(h, TAG_SPAN, 1, &tag); + print_text(h, n->eqn->data); + break; case (MAN_TBL): + /* + * This will take care of initialising all of the table + * state data for the first table, then tearing it down + * for the last one. + */ print_tbl(h, n->span); - break; + return; 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). + * scope. */ if (HTMLFONT_NONE != h->metac) { h->metal = h->metac; h->metac = HTMLFONT_NONE; } + + /* + * Close out the current table, if it's open, and unset + * the "meta" table state. This will be reopened on the + * next table element. + */ + if (h->tblt) { + print_tblclose(h); + t = h->tags.head; + } if (mans[n->tok].pre) child = (*mans[n->tok].pre)(m, n, mh, h); break; @@ -231,7 +265,7 @@ print_man_node(MAN_ARGS) case (MAN_ROOT): man_root_post(m, n, mh, h); break; - case (MAN_TBL): + case (MAN_EQN): break; default: if (mans[n->tok].post) @@ -255,7 +289,7 @@ a2width(const struct man_node *n, struct roffsu *su) /* ARGSUSED */ -static int +static void man_root_pre(MAN_ARGS) { struct htmlpair tag[3]; @@ -309,7 +343,6 @@ man_root_pre(MAN_ARGS) print_text(h, title); print_tagq(h, t); - return(1); } @@ -319,12 +352,6 @@ 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"); @@ -342,7 +369,7 @@ man_root_post(MAN_ARGS) PAIR_CLASS_INIT(&tag[0], "foot-date"); print_otag(h, TAG_TD, 1, tag); - print_text(h, b); + print_text(h, m->date); print_stagq(h, tt); PAIR_CLASS_INIT(&tag[0], "foot-os"); @@ -638,7 +665,7 @@ man_literal_pre(MAN_ARGS) } else mh->fl &= ~MANH_LITERAL; - return(1); + return(0); } diff --git a/contrib/mdocml/man_macro.c b/contrib/mdocml/man_macro.c index bd0ca99243..b3212e6806 100644 --- a/contrib/mdocml/man_macro.c +++ b/contrib/mdocml/man_macro.c @@ -1,4 +1,4 @@ -/* $Id: man_macro.c,v 1.54 2010/12/08 10:58:22 kristaps Exp $ */ +/* $Id: man_macro.c,v 1.60 2011/03/23 15:33:57 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons * @@ -23,7 +23,9 @@ #include #include +#include "man.h" #include "mandoc.h" +#include "libmandoc.h" #include "libman.h" enum rew { @@ -36,6 +38,8 @@ static int blk_close(MACRO_PROT_ARGS); static int blk_exp(MACRO_PROT_ARGS); static int blk_imp(MACRO_PROT_ARGS); static int in_line_eoln(MACRO_PROT_ARGS); +static int man_args(struct man *, int, + int *, char *, char **); static int rew_scope(enum man_type, struct man *, enum mant); @@ -43,7 +47,7 @@ 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 *, +static void rew_warn(struct man *, struct man_node *, enum mandocerr); const struct man_macro __man_macros[MAN_MAX] = { @@ -88,17 +92,19 @@ const struct man_macro * const man_macros = __man_macros; /* * Warn when "n" is an explicit non-roff macro. */ -static int +static void rew_warn(struct man *m, struct man_node *n, enum mandocerr er) { if (er == MANDOCERR_MAX || MAN_BLOCK != n->type) - return(1); + return; if (MAN_VALID & n->flags) - return(1); + return; if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags)) - return(1); - return(man_nmsg(m, n, er)); + return; + + assert(er < MANDOCERR_FATAL); + man_nmsg(m, n, er); } @@ -107,24 +113,30 @@ rew_warn(struct man *m, struct man_node *n, enum mandocerr er) * will be used if an explicit block scope is being closed out. */ int -man_unscope(struct man *m, const struct man_node *n, +man_unscope(struct man *m, const struct man_node *to, enum mandocerr er) { + struct man_node *n; - assert(n); + assert(to); /* LINTED */ - while (m->last != n) { - if ( ! rew_warn(m, m->last, er)) - return(0); + while (m->last != to) { + /* + * Save the parent here, because we may delete the + * m->last node in the post-validation phase and reset + * it to m->last->parent, causing a step in the closing + * out to be lost. + */ + n = m->last->parent; + rew_warn(m, m->last, er); if ( ! man_valid_post(m)) return(0); - m->last = m->last->parent; + m->last = n; assert(m->last); } - if ( ! rew_warn(m, m->last, er)) - return(0); + rew_warn(m, m->last, er); if ( ! man_valid_post(m)) return(0); @@ -271,8 +283,7 @@ blk_close(MACRO_PROT_ARGS) break; if (NULL == nn) - if ( ! man_pmsg(m, line, ppos, MANDOCERR_NOSCOPE)) - return(0); + man_pmsg(m, line, ppos, MANDOCERR_NOSCOPE); if ( ! rew_scope(MAN_BODY, m, ntok)) return(0); @@ -287,7 +298,7 @@ blk_close(MACRO_PROT_ARGS) int blk_exp(MACRO_PROT_ARGS) { - int w, la; + int la; char *p; /* @@ -308,13 +319,8 @@ blk_exp(MACRO_PROT_ARGS) for (;;) { la = *pos; - w = man_args(m, line, pos, buf, &p); - - if (-1 == w) - return(0); - if (0 == w) + if ( ! man_args(m, line, pos, buf, &p)) break; - if ( ! man_word_alloc(m, line, la, p)) return(0); } @@ -339,7 +345,7 @@ blk_exp(MACRO_PROT_ARGS) int blk_imp(MACRO_PROT_ARGS) { - int w, la; + int la; char *p; struct man_node *n; @@ -363,13 +369,8 @@ blk_imp(MACRO_PROT_ARGS) for (;;) { la = *pos; - w = man_args(m, line, pos, buf, &p); - - if (-1 == w) - return(0); - if (0 == w) + if ( ! man_args(m, line, pos, buf, &p)) break; - if ( ! man_word_alloc(m, line, la, p)) return(0); } @@ -397,7 +398,7 @@ blk_imp(MACRO_PROT_ARGS) int in_line_eoln(MACRO_PROT_ARGS) { - int w, la; + int la; char *p; struct man_node *n; @@ -408,11 +409,7 @@ in_line_eoln(MACRO_PROT_ARGS) for (;;) { la = *pos; - w = man_args(m, line, pos, buf, &p); - - if (-1 == w) - return(0); - if (0 == w) + if ( ! man_args(m, line, pos, buf, &p)) break; if ( ! man_word_alloc(m, line, la, p)) return(0); @@ -475,3 +472,18 @@ man_macroend(struct man *m) return(man_unscope(m, m->first, MANDOCERR_SCOPEEXIT)); } +static 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(0); + + *v = mandoc_getarg(m->parse, v, line, pos); + return(1); +} diff --git a/contrib/mdocml/man_term.c b/contrib/mdocml/man_term.c index c0ef70dbea..cb0b08d7e6 100644 --- a/contrib/mdocml/man_term.c +++ b/contrib/mdocml/man_term.c @@ -1,6 +1,6 @@ -/* $Id: man_term.c,v 1.94 2011/01/04 01:23:18 schwarze Exp $ */ +/* $Id: man_term.c,v 1.105 2011/03/22 10:13:01 kristaps Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * 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 @@ -31,7 +31,6 @@ #include "out.h" #include "man.h" #include "term.h" -#include "chars.h" #include "main.h" #define INDENT 7 @@ -126,7 +125,7 @@ static const struct termact termacts[MAN_MAX] = { { pre_I, NULL, 0 }, /* I */ { pre_alternate, NULL, 0 }, /* IR */ { pre_alternate, NULL, 0 }, /* RI */ - { NULL, NULL, MAN_NOTEXT }, /* na */ + { pre_ign, NULL, MAN_NOTEXT }, /* na */ { pre_sp, NULL, MAN_NOTEXT }, /* sp */ { pre_literal, NULL, 0 }, /* nf */ { pre_literal, NULL, 0 }, /* fi */ @@ -212,6 +211,9 @@ print_bvspace(struct termp *p, const struct man_node *n) { term_newln(p); + if (n->body && n->body->child && MAN_TBL == n->body->child->type) + return; + if (NULL == n->prev) return; @@ -255,7 +257,7 @@ pre_literal(DECL_ARGS) else mt->fl &= ~MANT_LITERAL; - return(1); + return(0); } /* ARGSUSED */ @@ -397,6 +399,11 @@ pre_in(DECL_ARGS) else p->offset = v; + /* Don't let this creep beyond the right margin. */ + + if (p->offset > p->rmargin) + p->offset = p->rmargin; + return(0); } @@ -850,20 +857,31 @@ print_man_node(DECL_ARGS) size_t rm, rmax; int c; - c = 1; - switch (n->type) { case(MAN_TEXT): - if (0 == *n->string) { + /* + * If we have a blank line, output a vertical space. + * If we have a space as the first character, break + * before printing the line's data. + */ + if ('\0' == *n->string) { term_vspace(p); - break; - } + return; + } else if (' ' == *n->string && MAN_LINE & n->flags) + term_newln(p); term_word(p, n->string); - /* FIXME: this means that macro lines are munged! */ - - if (MANT_LITERAL & mt->fl) { + /* + * If we're in a literal context, make sure that words + * togehter on the same line stay together. This is a + * POST-printing call, so we check the NEXT word. Since + * -man doesn't have nested macros, we don't need to be + * more specific than this. + */ + if (MANT_LITERAL & mt->fl && + (NULL == n->next || + n->next->line > n->line)) { rm = p->rmargin; rmax = p->maxrmargin; p->rmargin = p->maxrmargin = TERM_MAXMARGIN; @@ -873,35 +891,40 @@ print_man_node(DECL_ARGS) p->rmargin = rm; p->maxrmargin = rmax; } - break; + + if (MAN_EOS & n->flags) + p->flags |= TERMP_SENTENCE; + return; + case (MAN_EQN): + term_word(p, n->eqn->data); + return; case (MAN_TBL): + /* + * Tables are preceded by a newline. Then process a + * table line, which will cause line termination, + */ if (TBL_SPAN_FIRST & n->span->flags) term_newln(p); term_tbl(p, n->span); - break; + return; 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 ( ! (MAN_NOTEXT & termacts[n->tok].flags)) + term_fontrepl(p, TERMFONT_NONE); + + c = 1; + if (termacts[n->tok].pre) + c = (*termacts[n->tok].pre)(p, mt, n, m); + 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 (termacts[n->tok].post) + (*termacts[n->tok].post)(p, mt, n, m); + if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) + term_fontrepl(p, TERMFONT_NONE); if (MAN_EOS & n->flags) p->flags |= TERMP_SENTENCE; @@ -922,24 +945,18 @@ print_man_nodelist(DECL_ARGS) 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->rmargin = p->maxrmargin - term_strlen(p, meta->date); p->offset = 0; /* term_strlen() can return zero. */ @@ -957,7 +974,7 @@ print_man_foot(struct termp *p, const void *arg) p->rmargin = p->maxrmargin; p->flags &= ~TERMP_NOBREAK; - term_word(p, buf); + term_word(p, meta->date); term_flushln(p); } diff --git a/contrib/mdocml/man_validate.c b/contrib/mdocml/man_validate.c index bcfcbacfa4..03bb120f56 100644 --- a/contrib/mdocml/man_validate.c +++ b/contrib/mdocml/man_validate.c @@ -1,6 +1,7 @@ -/* $Id: man_validate.c,v 1.57 2011/01/01 12:59:17 kristaps Exp $ */ +/* $Id: man_validate.c,v 1.67 2011/03/22 15:30:30 kristaps Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * 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 @@ -29,6 +30,7 @@ #include #include +#include "man.h" #include "mandoc.h" #include "libman.h" #include "libmandoc.h" @@ -53,7 +55,6 @@ 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); @@ -70,7 +71,7 @@ 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_th[] = { check_ge2, check_le5, post_TH, NULL }; static v_check posts_uc[] = { post_UC, NULL }; static v_check pres_bline[] = { check_bline, NULL }; @@ -80,12 +81,12 @@ static const struct man_valid man_valids[MAN_MAX] = { { pres_bline, posts_th }, /* TH */ { pres_bline, posts_sec }, /* SH */ { pres_bline, posts_sec }, /* SS */ - { pres_bline, posts_par }, /* TP */ + { pres_bline, NULL }, /* 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 */ + { pres_bline, NULL }, /* IP */ + { pres_bline, NULL }, /* HP */ { NULL, NULL }, /* SM */ { NULL, NULL }, /* SB */ { NULL, NULL }, /* BI */ @@ -122,6 +123,8 @@ man_valid_pre(struct man *m, struct man_node *n) /* FALLTHROUGH */ case (MAN_ROOT): /* FALLTHROUGH */ + case (MAN_EQN): + /* FALLTHROUGH */ case (MAN_TBL): return(1); default: @@ -151,6 +154,8 @@ man_valid_post(struct man *m) return(check_text(m, m->last)); case (MAN_ROOT): return(check_root(m, m->last)); + case (MAN_EQN): + /* FALLTHROUGH */ case (MAN_TBL): return(1); default: @@ -191,37 +196,15 @@ check_root(CHKARGS) */ m->meta.title = mandoc_strdup("unknown"); - m->meta.date = time(NULL); m->meta.msec = mandoc_strdup("1"); + m->meta.date = mandoc_normdate + (m->parse, NULL, n->line, n->pos); } 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) { @@ -241,9 +224,8 @@ check_text(CHKARGS) if ('\t' == *p) { if (MAN_LITERAL & m->flags) continue; - if (man_pmsg(m, n->line, pos, MANDOCERR_BADTAB)) - continue; - return(0); + man_pmsg(m, n->line, pos, MANDOCERR_BADTAB); + continue; } /* Check the special character. */ @@ -266,10 +248,10 @@ check_##name(CHKARGS) \ { \ if (n->nchild ineq (x)) \ return(1); \ - man_vmsg(m, MANDOCERR_SYNTARGCOUNT, n->line, n->pos, \ + mandoc_vmsg(MANDOCERR_ARGCOUNT, m->parse, n->line, n->pos, \ "line arguments %s %d (have %d)", \ #ineq, (x), n->nchild); \ - return(0); \ + return(1); \ } INEQ_DEFINE(0, ==, eq0) @@ -318,14 +300,17 @@ check_ft(CHKARGS) } if (0 == ok) { - man_vmsg(m, MANDOCERR_BADFONT, - n->line, n->pos, "%s", cp); + mandoc_vmsg + (MANDOCERR_BADFONT, m->parse, + 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); + mandoc_vmsg + (MANDOCERR_ARGCOUNT, m->parse, n->line, + n->pos, "want one child (have %d)", + n->nchild); return(1); } @@ -338,7 +323,8 @@ check_sec(CHKARGS) man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT); return(0); } else if (MAN_BODY == n->type && 0 == n->nchild) - man_nmsg(m, n, MANDOCERR_NOBODY); + mandoc_msg(MANDOCERR_ARGCWARN, m->parse, n->line, + n->pos, "want children (have none)"); return(1); } @@ -349,7 +335,8 @@ check_part(CHKARGS) { if (MAN_BODY == n->type && 0 == n->nchild) - man_nmsg(m, n, MANDOCERR_NOBODY); + mandoc_msg(MANDOCERR_ARGCWARN, m->parse, n->line, + n->pos, "want children (have none)"); return(1); } @@ -359,33 +346,22 @@ 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; - } + switch (n->type) { + case (MAN_BLOCK): + if (0 == n->body->nchild) + man_node_delete(m, n); + break; + case (MAN_BODY): + if (0 == n->nchild) + man_nmsg(m, n, MANDOCERR_IGNPAR); + break; + case (MAN_HEAD): + if (n->nchild) + man_nmsg(m, n, MANDOCERR_ARGSLOST); + break; + default: + break; + } return(1); } @@ -407,6 +383,8 @@ check_bline(CHKARGS) static int post_TH(CHKARGS) { + const char *p; + int line, pos; if (m->meta.title) free(m->meta.title); @@ -416,44 +394,46 @@ post_TH(CHKARGS) free(m->meta.source); if (m->meta.msec) free(m->meta.msec); - if (m->meta.rawdate) - free(m->meta.rawdate); + if (m->meta.date) + free(m->meta.date); - m->meta.title = m->meta.vol = m->meta.rawdate = + line = n->line; + pos = n->pos; + m->meta.title = m->meta.vol = m->meta.date = 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); + if (n && n->string) { + for (p = n->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; + } + } + m->meta.title = mandoc_strdup(n->string); + } else + m->meta.title = mandoc_strdup(""); /* TITLE ->MSEC<- DATE SOURCE VOL */ - n = n->next; - assert(n); - m->meta.msec = mandoc_strdup(n->string); + if (n) + n = n->next; + if (n && n->string) + m->meta.msec = mandoc_strdup(n->string); + else + m->meta.msec = mandoc_strdup(""); /* 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); + if (n) + n = n->next; + if (n) + pos = n->pos; + m->meta.date = mandoc_normdate + (m->parse, n ? n->string : NULL, line, pos); /* TITLE MSEC DATE ->SOURCE<- VOL */ @@ -489,7 +469,7 @@ post_fi(CHKARGS) { if ( ! (MAN_LITERAL & m->flags)) - man_nmsg(m, n, MANDOCERR_NOSCOPE); + man_nmsg(m, n, MANDOCERR_WNOSCOPE); m->flags &= ~MAN_LITERAL; return(1); diff --git a/contrib/mdocml/mandoc-db.1 b/contrib/mdocml/mandoc-db.1 new file mode 100644 index 0000000000..24d147c170 --- /dev/null +++ b/contrib/mdocml/mandoc-db.1 @@ -0,0 +1,132 @@ +.\" $Id: mandoc-db.1,v 1.1 2011/04/04 10:53:15 kristaps Exp $ +.\" +.\" Copyright (c) 2011 Kristaps Dzonsons +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: April 4 2011 $ +.Dt MANDOC-DB 1 +.Os +.Sh NAME +.Nm mandoc-db +.Nd index UNIX manuals +.Sh SYNOPSIS +.Nm +.Op Fl d Ar dir +.Ar +.Sh DESCRIPTION +The +.Nm +utility extracts keywords from +.Ux +manuals and indexes them for fast retrieval. +The arguments are as follows: +.Bl -tag -width Ds +.It Fl d Ar dir +The directory into which to write the keyword and index databases. +.It Ar +Read input from zero or more files in +.Xr mdoc 7 +or +.Xr man 7 +.Ux +manual format. +.El +.Pp +By default, +.Nm +constructs the +.Sx Index Database +and +.Sx Keyword Database +in the current working directory. +.Pp +If fatal parse errors are encountered, the offending file is printed to +stderr, omitted from the index, and the parse continues with the next +input file. +.Ss Index Database +The index database, +.Pa mandoc.index , +is a +.Xr recno 3 +database of nil-terminated filenames (record length is non-zero string +length plus one) passed to +.Nm . +Entries are sequentially-numbered, but the filenames are unordered. +.Ss Keyword Database +The keyword database, +.Pa mandoc.db , +is a +.Xr btree 3 +database of nil-terminated keywords (record length is non-zero string +length plus one) mapping to a 8-byte binary field consisting of the +keyword type and source +.Sx Index Database +record number. +The type, an unsigned 32-bit integer in host order, is one of the +following: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Li 0x01 +The name of a manual page as given in the NAME section. +.It Li 0x02 +A function prototype name as given in the SYNOPSIS section. +.It Li 0x03 +A utility name as given in the SYNOPSIS section. +.It Li 0x04 +An include file as given in the SYNOPSIS section. +.It Li 0x05 +A variable name as given in the SYNOPSIS section. +.El +.Pp +If a value is encountered outside of this range, the database is +corrupt. +.Pp +The latter four bytes are a host-ordered record number within the +.Sx Index Database . +.Pp +The +.Nm +utility is +.Ud +.Sh FILES +.Bl -tag -width Ds +.It Pa mandoc.db +A +.Xr btree 3 +keyword database mapping keywords to a type and file reference in +.Pa mandoc.index . +.It Pa mandoc.db~ +Working copy of +.Pa mandoc.db . +.It Pa mandoc.index +A +.Xr recno 3 +database of indexed file-names. +.It Pa mandoc.index~ +Working copy of +.Pa mandoc.index . +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr mandoc 1 +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . +.Sh CAVEATS +Only +.Xr mdoc 7 +manuals are processed. diff --git a/contrib/mdocml/mandoc-db.c b/contrib/mdocml/mandoc-db.c new file mode 100644 index 0000000000..0c66efaf2b --- /dev/null +++ b/contrib/mdocml/mandoc-db.c @@ -0,0 +1,669 @@ +/* $Id: mandoc-db.c,v 1.5 2011/04/04 16:49:03 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#ifdef __linux__ +# include +#else +# include +#endif +#include +#include +#include +#include +#include +#include + +#include "man.h" +#include "mdoc.h" +#include "mandoc.h" + +#define MANDOC_DB "mandoc.db" +#define MANDOC_IDX "mandoc.index" +#define MANDOC_BUFSZ BUFSIZ +#define MANDOC_FLAGS O_CREAT|O_TRUNC|O_RDWR + +enum type { + MANDOC_NONE = 0, + MANDOC_NAME, + MANDOC_FUNCTION, + MANDOC_UTILITY, + MANDOC_INCLUDES, + MANDOC_VARIABLE +}; + +#define MDOC_ARGS DB *db, \ + const char *dbn, \ + DBT *key, size_t *ksz, \ + DBT *val, \ + const struct mdoc_node *n + +static void dbt_append(DBT *, size_t *, const char *); +static void dbt_appendb(DBT *, size_t *, + const void *, size_t); +static void dbt_init(DBT *, size_t *); +static void usage(void); +static void pmdoc(DB *, const char *, + DBT *, size_t *, DBT *, + const char *, struct mdoc *); +static void pmdoc_node(MDOC_ARGS); +static void pmdoc_Fd(MDOC_ARGS); +static void pmdoc_In(MDOC_ARGS); +static void pmdoc_Fn(MDOC_ARGS); +static void pmdoc_Fo(MDOC_ARGS); +static void pmdoc_Nm(MDOC_ARGS); +static void pmdoc_Vt(MDOC_ARGS); + +typedef void (*pmdoc_nf)(MDOC_ARGS); + +static const char *progname; + +static const pmdoc_nf mdocs[MDOC_MAX] = { + NULL, /* Ap */ + NULL, /* Dd */ + NULL, /* Dt */ + NULL, /* Os */ + NULL, /* Sh */ + NULL, /* Ss */ + NULL, /* Pp */ + NULL, /* D1 */ + NULL, /* Dl */ + NULL, /* Bd */ + NULL, /* Ed */ + NULL, /* Bl */ + NULL, /* El */ + NULL, /* It */ + NULL, /* Ad */ + NULL, /* An */ + NULL, /* Ar */ + NULL, /* Cd */ + NULL, /* Cm */ + NULL, /* Dv */ + NULL, /* Er */ + NULL, /* Ev */ + NULL, /* Ex */ + NULL, /* Fa */ + pmdoc_Fd, /* Fd */ + NULL, /* Fl */ + pmdoc_Fn, /* Fn */ + NULL, /* Ft */ + NULL, /* Ic */ + pmdoc_In, /* In */ + NULL, /* Li */ + NULL, /* Nd */ + pmdoc_Nm, /* Nm */ + NULL, /* Op */ + NULL, /* Ot */ + NULL, /* Pa */ + NULL, /* Rv */ + NULL, /* St */ + pmdoc_Vt, /* Va */ + pmdoc_Vt, /* Vt */ + NULL, /* Xr */ + NULL, /* %A */ + NULL, /* %B */ + NULL, /* %D */ + NULL, /* %I */ + NULL, /* %J */ + NULL, /* %N */ + NULL, /* %O */ + NULL, /* %P */ + NULL, /* %R */ + NULL, /* %T */ + NULL, /* %V */ + NULL, /* Ac */ + NULL, /* Ao */ + NULL, /* Aq */ + NULL, /* At */ + NULL, /* Bc */ + NULL, /* Bf */ + NULL, /* Bo */ + NULL, /* Bq */ + NULL, /* Bsx */ + NULL, /* Bx */ + NULL, /* Db */ + NULL, /* Dc */ + NULL, /* Do */ + NULL, /* Dq */ + NULL, /* Ec */ + NULL, /* Ef */ + NULL, /* Em */ + NULL, /* Eo */ + NULL, /* Fx */ + NULL, /* Ms */ + NULL, /* No */ + NULL, /* Ns */ + NULL, /* Nx */ + NULL, /* Ox */ + NULL, /* Pc */ + NULL, /* Pf */ + NULL, /* Po */ + NULL, /* Pq */ + NULL, /* Qc */ + NULL, /* Ql */ + NULL, /* Qo */ + NULL, /* Qq */ + NULL, /* Re */ + NULL, /* Rs */ + NULL, /* Sc */ + NULL, /* So */ + NULL, /* Sq */ + NULL, /* Sm */ + NULL, /* Sx */ + NULL, /* Sy */ + NULL, /* Tn */ + NULL, /* Ux */ + NULL, /* Xc */ + NULL, /* Xo */ + pmdoc_Fo, /* Fo */ + NULL, /* Fc */ + NULL, /* Oo */ + NULL, /* Oc */ + NULL, /* Bk */ + NULL, /* Ek */ + NULL, /* Bt */ + NULL, /* Hf */ + NULL, /* Fr */ + NULL, /* Ud */ + NULL, /* Lb */ + NULL, /* Lp */ + NULL, /* Lk */ + NULL, /* Mt */ + NULL, /* Brq */ + NULL, /* Bro */ + NULL, /* Brc */ + NULL, /* %C */ + NULL, /* Es */ + NULL, /* En */ + NULL, /* Dx */ + NULL, /* %Q */ + NULL, /* br */ + NULL, /* sp */ + NULL, /* %U */ + NULL, /* Ta */ +}; + +int +main(int argc, char *argv[]) +{ + struct mparse *mp; /* parse sequence */ + struct mdoc *mdoc; /* resulting mdoc */ + char *fn; + const char *dir; /* result dir (default: cwd) */ + char ibuf[MAXPATHLEN], /* index fname */ + ibbuf[MAXPATHLEN], /* index backup fname */ + fbuf[MAXPATHLEN], /* btree fname */ + fbbuf[MAXPATHLEN]; /* btree backup fname */ + int c; + DB *index, /* index database */ + *db; /* keyword database */ + DBT rkey, rval, /* recno entries */ + key, val; /* persistent keyword entries */ + size_t ksz; /* entry buffer size */ + char vbuf[8]; + BTREEINFO info; /* btree configuration */ + recno_t rec; + extern int optind; + extern char *optarg; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + dir = ""; + + while (-1 != (c = getopt(argc, argv, "d:"))) + switch (c) { + case ('d'): + dir = optarg; + break; + default: + usage(); + return((int)MANDOCLEVEL_BADARG); + } + + argc -= optind; + argv += optind; + + /* + * Set up temporary file-names into which we're going to write + * all of our data (both for the index and database). These + * will be securely renamed to the real file-names after we've + * written all of our data. + */ + + ibuf[0] = ibuf[MAXPATHLEN - 2] = + ibbuf[0] = ibbuf[MAXPATHLEN - 2] = + fbuf[0] = fbuf[MAXPATHLEN - 2] = + fbbuf[0] = fbbuf[MAXPATHLEN - 2] = '\0'; + + strlcat(fbuf, dir, MAXPATHLEN); + strlcat(fbuf, MANDOC_DB, MAXPATHLEN); + + strlcat(fbbuf, fbuf, MAXPATHLEN); + strlcat(fbbuf, "~", MAXPATHLEN); + + strlcat(ibuf, dir, MAXPATHLEN); + strlcat(ibuf, MANDOC_IDX, MAXPATHLEN); + + strlcat(ibbuf, ibuf, MAXPATHLEN); + strlcat(ibbuf, "~", MAXPATHLEN); + + if ('\0' != fbuf[MAXPATHLEN - 2] || + '\0' != fbbuf[MAXPATHLEN - 2] || + '\0' != ibuf[MAXPATHLEN - 2] || + '\0' != ibbuf[MAXPATHLEN - 2]) { + fprintf(stderr, "%s: Path too long\n", progname); + exit((int)MANDOCLEVEL_SYSERR); + } + + /* + * For the keyword database, open a BTREE database that allows + * duplicates. For the index database, use a standard RECNO + * database type. + */ + + memset(&info, 0, sizeof(BTREEINFO)); + info.flags = R_DUP; + db = dbopen(fbbuf, MANDOC_FLAGS, 0644, DB_BTREE, &info); + + if (NULL == db) { + perror(fbbuf); + exit((int)MANDOCLEVEL_SYSERR); + } + + index = dbopen(ibbuf, MANDOC_FLAGS, 0644, DB_RECNO, NULL); + + if (NULL == db) { + perror(ibbuf); + (*db->close)(db); + exit((int)MANDOCLEVEL_SYSERR); + } + + /* + * Try parsing the manuals given on the command line. If we + * totally fail, then just keep on going. Take resulting trees + * and push them down into the database code. + * Use the auto-parser and don't report any errors. + */ + + mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL); + + memset(&key, 0, sizeof(DBT)); + memset(&val, 0, sizeof(DBT)); + memset(&rkey, 0, sizeof(DBT)); + memset(&rval, 0, sizeof(DBT)); + + val.size = sizeof(vbuf); + val.data = vbuf; + rkey.size = sizeof(recno_t); + + rec = 1; + ksz = 0; + + while (NULL != (fn = *argv++)) { + mparse_reset(mp); + + if (mparse_readfd(mp, -1, fn) >= MANDOCLEVEL_FATAL) { + fprintf(stderr, "%s: Parse failure\n", fn); + continue; + } + + mparse_result(mp, &mdoc, NULL); + if (NULL == mdoc) + continue; + + rkey.data = &rec; + rval.data = fn; + rval.size = strlen(fn) + 1; + + if (-1 == (*index->put)(index, &rkey, &rval, 0)) { + perror(ibbuf); + break; + } + + memset(val.data, 0, sizeof(uint32_t)); + memcpy(val.data + 4, &rec, sizeof(uint32_t)); + + pmdoc(db, fbbuf, &key, &ksz, &val, fn, mdoc); + rec++; + } + + (*db->close)(db); + (*index->close)(index); + + mparse_free(mp); + + free(key.data); + + /* Atomically replace the file with our temporary one. */ + + if (-1 == rename(fbbuf, fbuf)) + perror(fbuf); + if (-1 == rename(ibbuf, ibuf)) + perror(fbuf); + + return((int)MANDOCLEVEL_OK); +} + +/* + * Initialise the stored database key whose data buffer is shared + * between uses (as the key must sometimes be constructed from an array + * of + */ +static void +dbt_init(DBT *key, size_t *ksz) +{ + + if (0 == *ksz) { + assert(0 == key->size); + assert(NULL == key->data); + key->data = mandoc_malloc(MANDOC_BUFSZ); + *ksz = MANDOC_BUFSZ; + } + + key->size = 0; +} + +/* + * Append a binary value to a database entry. This can be invoked + * multiple times; the buffer is automatically resized. + */ +static void +dbt_appendb(DBT *key, size_t *ksz, const void *cp, size_t sz) +{ + + assert(key->data); + + /* Overshoot by MANDOC_BUFSZ. */ + + while (key->size + sz >= *ksz) { + *ksz = key->size + sz + MANDOC_BUFSZ; + key->data = mandoc_realloc(key->data, *ksz); + } + + memcpy(key->data + (int)key->size, cp, sz); + key->size += sz; +} + +/* + * Append a nil-terminated string to the database entry. This can be + * invoked multiple times. The database entry will be nil-terminated as + * well; if invoked multiple times, a space is put between strings. + */ +static void +dbt_append(DBT *key, size_t *ksz, const char *cp) +{ + size_t sz; + + if (0 == (sz = strlen(cp))) + return; + + assert(key->data); + + if (key->size) + ((char *)key->data)[(int)key->size - 1] = ' '; + + dbt_appendb(key, ksz, cp, sz + 1); +} + +/* ARGSUSED */ +static void +pmdoc_Fd(MDOC_ARGS) +{ + uint32_t fl; + const char *start, *end; + size_t sz; + char nil; + + if (SEC_SYNOPSIS != n->sec) + return; + if (NULL == (n = n->child) || MDOC_TEXT != n->type) + return; + + /* + * Only consider those `Fd' macro fields that begin with an + * "inclusion" token (versus, e.g., #define). + */ + if (strcmp("#include", n->string)) + return; + + if (NULL == (n = n->next) || MDOC_TEXT != n->type) + return; + + /* + * Strip away the enclosing angle brackets and make sure we're + * not zero-length. + */ + + start = n->string; + if ('<' == *start || '"' == *start) + start++; + + if (0 == (sz = strlen(start))) + return; + + end = &start[(int)sz - 1]; + if ('>' == *end || '"' == *end) + end--; + + nil = '\0'; + dbt_appendb(key, ksz, start, end - start + 1); + dbt_appendb(key, ksz, &nil, 1); + + fl = MANDOC_INCLUDES; + memcpy(val->data, &fl, 4); +} + +/* ARGSUSED */ +static void +pmdoc_In(MDOC_ARGS) +{ + uint32_t fl; + + if (SEC_SYNOPSIS != n->sec) + return; + if (NULL == n->child || MDOC_TEXT != n->child->type) + return; + + dbt_append(key, ksz, n->child->string); + fl = MANDOC_INCLUDES; + memcpy(val->data, &fl, 4); +} + +/* ARGSUSED */ +static void +pmdoc_Fn(MDOC_ARGS) +{ + uint32_t fl; + const char *cp; + + if (SEC_SYNOPSIS != n->sec) + return; + if (NULL == n->child || MDOC_TEXT != n->child->type) + return; + + /* .Fn "struct type *arg" "foo" */ + + cp = strrchr(n->child->string, ' '); + if (NULL == cp) + cp = n->child->string; + + /* Strip away pointer symbol. */ + + while ('*' == *cp) + cp++; + + dbt_append(key, ksz, cp); + fl = MANDOC_FUNCTION; + memcpy(val->data, &fl, 4); +} + +/* ARGSUSED */ +static void +pmdoc_Vt(MDOC_ARGS) +{ + uint32_t fl; + const char *start, *end; + size_t sz; + char nil; + + if (SEC_SYNOPSIS != n->sec) + return; + if (MDOC_Vt == n->tok && MDOC_BODY != n->type) + return; + if (NULL == n->child || MDOC_TEXT != n->child->type) + return; + + /* + * Strip away leading pointer symbol '*' and trailing ';'. + */ + + start = n->last->string; + + while ('*' == *start) + start++; + + if (0 == (sz = strlen(start))) + return; + + end = &start[sz - 1]; + while (end > start && ';' == *end) + end--; + + if (end == start) + return; + + nil = '\0'; + dbt_appendb(key, ksz, start, end - start + 1); + dbt_appendb(key, ksz, &nil, 1); + fl = MANDOC_VARIABLE; + memcpy(val->data, &fl, 4); +} + +/* ARGSUSED */ +static void +pmdoc_Fo(MDOC_ARGS) +{ + uint32_t fl; + + if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type) + return; + if (NULL == n->child || MDOC_TEXT != n->child->type) + return; + + dbt_append(key, ksz, n->child->string); + fl = MANDOC_FUNCTION; + memcpy(val->data, &fl, 4); +} + +/* ARGSUSED */ +static void +pmdoc_Nm(MDOC_ARGS) +{ + uint32_t fl; + + if (SEC_NAME == n->sec) { + for (n = n->child; n; n = n->next) { + if (MDOC_TEXT != n->type) + continue; + dbt_append(key, ksz, n->string); + } + fl = MANDOC_NAME; + memcpy(val->data, &fl, 4); + return; + } else if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type) + return; + + for (n = n->child; n; n = n->next) { + if (MDOC_TEXT != n->type) + continue; + dbt_append(key, ksz, n->string); + } + + fl = MANDOC_UTILITY; + memcpy(val->data, &fl, 4); +} + +/* + * Call out to per-macro handlers after clearing the persistent database + * key. If the macro sets the database key, flush it to the database. + */ +static void +pmdoc_node(MDOC_ARGS) +{ + + if (NULL == n) + return; + + switch (n->type) { + case (MDOC_HEAD): + /* FALLTHROUGH */ + case (MDOC_BODY): + /* FALLTHROUGH */ + case (MDOC_TAIL): + /* FALLTHROUGH */ + case (MDOC_BLOCK): + /* FALLTHROUGH */ + case (MDOC_ELEM): + if (NULL == mdocs[n->tok]) + break; + + dbt_init(key, ksz); + (*mdocs[n->tok])(db, dbn, key, ksz, val, n); + + if (0 == key->size) + break; + if (0 == (*db->put)(db, key, val, 0)) + break; + + perror(dbn); + exit((int)MANDOCLEVEL_SYSERR); + /* NOTREACHED */ + default: + break; + } + + pmdoc_node(db, dbn, key, ksz, val, n->child); + pmdoc_node(db, dbn, key, ksz, val, n->next); +} + +static void +pmdoc(DB *db, const char *dbn, + DBT *key, size_t *ksz, DBT *val, + const char *path, struct mdoc *m) +{ + + pmdoc_node(db, dbn, key, ksz, val, mdoc_node(m)); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s " + "[-d path] " + "[file...]\n", + progname); +} diff --git a/contrib/mdocml/mandoc.1 b/contrib/mdocml/mandoc.1 index 843f3d738c..91cb8fe890 100644 --- a/contrib/mdocml/mandoc.1 +++ b/contrib/mdocml/mandoc.1 @@ -1,4 +1,4 @@ -.\" $Id: mandoc.1,v 1.84 2011/01/04 23:32:21 kristaps Exp $ +.\" $Id: mandoc.1,v 1.85 2011/02/09 10:03:02 kristaps Exp $ .\" .\" Copyright (c) 2009, 2010 Kristaps Dzonsons .\" @@ -14,7 +14,7 @@ .\" 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 $ +.Dd $Mdocdate: February 9 2011 $ .Dt MANDOC 1 .Os .Sh NAME @@ -529,6 +529,7 @@ and lists render similarly. .El .Sh SEE ALSO +.Xr eqn 7 , .Xr man 7 , .Xr mandoc_char 7 , .Xr mdoc 7 , diff --git a/contrib/mdocml/mandoc.3 b/contrib/mdocml/mandoc.3 new file mode 100644 index 0000000000..2fd887b8f0 --- /dev/null +++ b/contrib/mdocml/mandoc.3 @@ -0,0 +1,333 @@ +.\" $Id: mandoc.3,v 1.2 2011/03/28 21:49:42 kristaps Exp $ +.\" +.\" Copyright (c) 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. +.\" +.Dd $Mdocdate: March 28 2011 $ +.Dt MANDOC 3 +.Os +.Sh NAME +.Nm mandoc , +.Nm man_meta , +.Nm man_node , +.Nm mdoc_meta , +.Nm mdoc_node , +.Nm mparse_alloc , +.Nm mparse_free , +.Nm mparse_readfd , +.Nm mparse_reset , +.Nm mparse_result , +.Nm mparse_strerror , +.Nm mparse_strlevel +.Nd mandoc macro compiler library +.Sh SYNOPSIS +.In man.h +.In mdoc.h +.In mandoc.h +.Ft "const struct man_meta *" +.Fo man_meta +.Fa "const struct man *man" +.Fc +.Ft "const struct man_node *" +.Fo man_node +.Fa "const struct man *man" +.Fc +.Ft "const struct mdoc_meta *" +.Fo mdoc_meta +.Fa "const struct mdoc *mdoc" +.Fc +.Ft "const struct mdoc_node *" +.Fo mdoc_node +.Fa "const struct mdoc *mdoc" +.Fc +.Ft void +.Fo mparse_alloc +.Fa "enum mparset type" +.Fa "enum mandoclevel wlevel" +.Fa "mandocmsg msg" +.Fa "void *msgarg" +.Fc +.Ft void +.Fo mparse_free +.Fa "struct mparse *parse" +.Fc +.Ft "enum mandoclevel" +.Fo mparse_readfd +.Fa "struct mparse *parse" +.Fa "int fd" +.Fa "const char *fname" +.Fc +.Ft void +.Fo mparse_reset +.Fa "struct mparse *parse" +.Fc +.Ft void +.Fo mparse_result +.Fa "struct mparse *parse" +.Fa "struct mdoc **mdoc" +.Fa "struct man **man" +.Fc +.Ft "const char *" +.Fo mparse_strerror +.Fa "enum mandocerr" +.Fc +.Ft "const char *" +.Fo mparse_strlevel +.Fa "enum mandoclevel" +.Fc +.Vt extern const char * const * man_macronames; +.Vt extern const char * const * mdoc_argnames; +.Vt extern const char * const * mdoc_macronames; +.Sh DESCRIPTION +The +.Nm mandoc +library parses a +.Ux +manual into an abstract syntax tree (AST). +.Ux +manuals are composed of +.Xr mdoc 7 +or +.Xr man 7 , +and may be mixed with +.Xr roff 7 , +.Xr tbl 7 , +and +.Xr eqn 7 +invocations. +.Pp +The following describes a general parse sequence: +.Bl -enum +.It +initiate a parsing sequence with +.Fn mparse_alloc ; +.It +parse files or file descriptors with +.Fn mparse_readfd ; +.It +retrieve a parsed syntax tree, if the parse was successful, with +.Fn mparse_result ; +.It +iterate over parse nodes with +.Fn mdoc_node +or +.Fn man_node ; +.It +free all allocated memory with +.Fn mparse_free , +or invoke +.Fn mparse_reset +and parse new files. +.El +.Sh IMPLEMENTATION NOTES +This section consists of structural documentation for +.Xr mdoc 7 +and +.Xr man 7 +syntax trees. +.Ss Man Abstract Syntax Tree +This AST is governed by the ontological rules dictated in +.Xr man 7 +and derives its terminology accordingly. +.Pp +The AST is composed of +.Vt struct man_node +nodes with element, root and text types as declared by the +.Va type +field. +Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va next +and +.Va prev +fields) and some type-specific data. +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact +.It ROOT +\(<- mnode+ +.It mnode +\(<- ELEMENT | TEXT | BLOCK +.It BLOCK +\(<- HEAD BODY +.It HEAD +\(<- mnode* +.It BODY +\(<- mnode* +.It ELEMENT +\(<- ELEMENT | TEXT* +.It TEXT +\(<- [[:alpha:]]* +.El +.Pp +The only elements capable of nesting other elements are those with +next-lint scope as documented in +.Xr man 7 . +.Ss Mdoc Abstract Syntax Tree +This AST is governed by the ontological +rules dictated in +.Xr mdoc 7 +and derives its terminology accordingly. +.Qq In-line +elements described in +.Xr mdoc 7 +are described simply as +.Qq elements . +.Pp +The AST is composed of +.Vt struct mdoc_node +nodes with block, head, body, element, root and text types as declared +by the +.Va type +field. +Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va nchild , +.Va next +and +.Va prev +fields) and some type-specific data, in particular, for nodes generated +from macros, the generating macro in the +.Va tok +field. +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact +.It ROOT +\(<- mnode+ +.It mnode +\(<- BLOCK | ELEMENT | TEXT +.It BLOCK +\(<- HEAD [TEXT] (BODY [TEXT])+ [TAIL [TEXT]] +.It ELEMENT +\(<- TEXT* +.It HEAD +\(<- mnode* +.It BODY +\(<- mnode* [ENDBODY mnode*] +.It TAIL +\(<- mnode* +.It TEXT +\(<- [[: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. +.Pp +The +.Xr mdoc 7 +syntax tree accomodates for broken block structures as well. +The ENDBODY node is available to end the formatting associated +with a given block before the physical end of that block. +It has a non-null +.Va end +field, is of the BODY +.Va type , +has the same +.Va tok +as the BLOCK it is ending, and has a +.Va pending +field pointing to that BLOCK's BODY node. +It is an indirect child of that BODY node +and has no children of its own. +.Pp +An ENDBODY node is generated when a block ends while one of its child +blocks is still open, like in the following example: +.Bd -literal -offset indent +\&.Ao ao +\&.Bo bo ac +\&.Ac bc +\&.Bc end +.Ed +.Pp +This example results in the following block structure: +.Bd -literal -offset indent +BLOCK Ao + HEAD Ao + BODY Ao + TEXT ao + BLOCK Bo, pending -> Ao + HEAD Bo + BODY Bo + TEXT bo + TEXT ac + ENDBODY Ao, pending -> Ao + TEXT bc +TEXT end +.Ed +.Pp +Here, the formatting of the +.Sq \&Ao +block extends from TEXT ao to TEXT ac, +while the formatting of the +.Sq \&Bo +block extends from TEXT bo to TEXT bc. +It renders as follows in +.Fl T Ns Cm ascii +mode: +.Pp +.Dl bc] end +.Pp +Support for badly-nested blocks is only provided for backward +compatibility with some older +.Xr mdoc 7 +implementations. +Using badly-nested blocks is +.Em strongly discouraged ; +for example, the +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml +front-ends to +.Xr mandoc 1 +are unable to render them in any meaningful way. +Furthermore, behaviour when encountering badly-nested blocks is not +consistent across troff implementations, especially when using multiple +levels of badly-nested blocks. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr eqn 7 , +.Xr man 7 , +.Xr mdoc 7 , +.Xr roff 7 , +.Xr tbl 7 +.Sh AUTHORS +The +.Nm +library was written by +.An Kristaps Dzonsons Aq kristaps@bsd.lv . diff --git a/contrib/mdocml/mandoc.c b/contrib/mdocml/mandoc.c index 4faa5a78fa..da4a16067c 100644 --- a/contrib/mdocml/mandoc.c +++ b/contrib/mdocml/mandoc.c @@ -1,4 +1,4 @@ -/* $Id: mandoc.c,v 1.36 2011/01/03 22:42:37 schwarze Exp $ */ +/* $Id: mandoc.c,v 1.44 2011/03/28 23:52:13 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons * Copyright (c) 2011 Ingo Schwarze @@ -31,8 +31,10 @@ #include "mandoc.h" #include "libmandoc.h" -static int a2time(time_t *, const char *, const char *); +#define DATESIZE 32 +static int a2time(time_t *, const char *, const char *); +static char *time2a(time_t); int mandoc_special(char *p) @@ -294,7 +296,7 @@ mandoc_strdup(const char *ptr) * or to the null byte terminating the argument line. */ char * -mandoc_getarg(char **cpp, mandocmsg msg, void *data, int ln, int *pos) +mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos) { char *start, *cp; int quoted, pairs, white; @@ -341,8 +343,8 @@ mandoc_getarg(char **cpp, mandocmsg msg, void *data, int ln, int *pos) } /* Quoted argument without a closing quote. */ - if (1 == quoted && msg) - (*msg)(MANDOCERR_BADQUOTE, data, ln, *pos, NULL); + if (1 == quoted) + mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL); /* Null-terminate this argument and move to the next one. */ if (pairs) @@ -352,16 +354,15 @@ mandoc_getarg(char **cpp, mandocmsg msg, void *data, int ln, int *pos) while (' ' == *cp) cp++; } - *pos += (cp - start) + (quoted ? 1 : 0); + *pos += (int)(cp - start) + (quoted ? 1 : 0); *cpp = cp; - if ('\0' == *cp && msg && (white || ' ' == cp[-1])) - (*msg)(MANDOCERR_EOLNSPACE, data, ln, *pos, NULL); + if ('\0' == *cp && (white || ' ' == cp[-1])) + mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL); return(start); } - static int a2time(time_t *t, const char *fmt, const char *p) { @@ -379,41 +380,61 @@ a2time(time_t *t, const char *fmt, const char *p) 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) +static char * +time2a(time_t t) { - time_t t; + struct tm tm; + char *buf, *p; + size_t ssz; + int isz; - if (MTIME_MDOCDATE & flags) { - if (0 == strcmp(p, "$" "Mdocdate$")) - return(time(NULL)); - if (a2time(&t, "$" "Mdocdate: %b %d %Y $", p)) - return(t); - } + localtime_r(&t, &tm); - if (MTIME_CANONICAL & flags || MTIME_REDUCED & flags) - if (a2time(&t, "%b %d, %Y", p)) - return(t); + /* + * Reserve space: + * up to 9 characters for the month (September) + blank + * up to 2 characters for the day + comma + blank + * 4 characters for the year and a terminating '\0' + */ + p = buf = mandoc_malloc(10 + 4 + 4 + 1); - if (MTIME_ISO_8601 & flags) - if (a2time(&t, "%Y-%m-%d", p)) - return(t); + if (0 == (ssz = strftime(p, 10 + 1, "%B ", &tm))) + goto fail; + p += (int)ssz; - if (MTIME_REDUCED & flags) { - if (a2time(&t, "%d, %Y", p)) - return(t); - if (a2time(&t, "%Y", p)) - return(t); - } + if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm.tm_mday))) + goto fail; + p += isz; - return(0); + if (0 == strftime(p, 4 + 1, "%Y", &tm)) + goto fail; + return(buf); + +fail: + free(buf); + return(NULL); } +char * +mandoc_normdate(struct mparse *parse, char *in, int ln, int pos) +{ + char *out; + time_t t; + + if (NULL == in || '\0' == *in || + 0 == strcmp(in, "$" "Mdocdate$")) { + mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL); + time(&t); + } + else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) && + !a2time(&t, "%b %d, %Y", in) && + !a2time(&t, "%Y-%m-%d", in)) { + mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL); + t = 0; + } + out = t ? time2a(t) : NULL; + return(out ? out : mandoc_strdup(in)); +} int mandoc_eos(const char *p, size_t sz, int enclosed) @@ -458,7 +479,6 @@ mandoc_eos(const char *p, size_t sz, int enclosed) return(found && !enclosed); } - int mandoc_hyph(const char *start, const char *c) { @@ -485,3 +505,29 @@ mandoc_hyph(const char *start, const char *c) return(1); } + +/* + * Find out whether a line is a macro line or not. If it is, adjust the + * current position and return one; if it isn't, return zero and don't + * change the current position. + */ +int +mandoc_getcontrol(const char *cp, int *ppos) +{ + int pos; + + pos = *ppos; + + if ('\\' == cp[pos] && '.' == cp[pos + 1]) + pos += 2; + else if ('.' == cp[pos] || '\'' == cp[pos]) + pos++; + else + return(0); + + while (' ' == cp[pos] || '\t' == cp[pos]) + pos++; + + *ppos = pos; + return(1); +} diff --git a/contrib/mdocml/mandoc.h b/contrib/mdocml/mandoc.h index 9dd448e6dd..185c10bf47 100644 --- a/contrib/mdocml/mandoc.h +++ b/contrib/mdocml/mandoc.h @@ -1,4 +1,4 @@ -/* $Id: mandoc.h,v 1.49 2011/01/06 13:45:47 kristaps Exp $ */ +/* $Id: mandoc.h,v 1.69 2011/03/28 21:49:42 kristaps Exp $ */ /* * Copyright (c) 2010, 2011 Kristaps Dzonsons * @@ -50,7 +50,8 @@ enum mandocerr { 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_NODATE, /* date missing, using today's date */ + MANDOCERR_BADDATE, /* cannot parse date, using it verbatim */ MANDOCERR_PROLOGOOO, /* prologue macros out of order */ MANDOCERR_PROLOGREP, /* duplicate prologue macro */ MANDOCERR_BADPROLOG, /* macro not allowed in prologue */ @@ -68,10 +69,12 @@ enum mandocerr { /* related to macros and nesting */ MANDOCERR_MACROOBS, /* skipping obsolete macro */ MANDOCERR_IGNPAR, /* skipping paragraph macro */ + MANDOCERR_IGNNS, /* skipping no-space 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 */ + MANDOCERR_LINESCOPE, /* line scope broken */ /* related to missing macro arguments */ MANDOCERR_MACROEMPTY, /* skipping empty macro */ @@ -80,6 +83,7 @@ enum mandocerr { MANDOCERR_LISTFIRST, /* list type must come first */ MANDOCERR_NOWIDTHARG, /* tag lists require a width argument */ MANDOCERR_FONTTYPE, /* missing font type */ + MANDOCERR_WNOSCOPE, /* skipping end of block that is not open */ /* related to bad macro arguments */ MANDOCERR_IGNARGV, /* skipping argument */ @@ -100,9 +104,6 @@ enum mandocerr { 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 */ @@ -113,13 +114,14 @@ enum mandocerr { MANDOCERR_TBLNODATA, /* no table data cells specified */ MANDOCERR_TBLIGNDATA, /* ignore data in cell */ MANDOCERR_TBLBLOCK, /* data block still open */ + MANDOCERR_TBLEXTRADAT, /* ignoring extra data cells */ MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */ MANDOCERR_BADCHAR, /* skipping bad character */ + MANDOCERR_NAMESC, /* escaped character not allowed in a name */ 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 */ @@ -135,6 +137,7 @@ enum mandocerr { MANDOCERR_FATAL, /* ===== start of fatal errors ===== */ + MANDOCERR_NOTMANUAL, /* manual isn't really a manual */ MANDOCERR_COLUMNS, /* column syntax is inconsistent */ MANDOCERR_BADDISP, /* NOT IMPLEMENTED: .Bd -file */ MANDOCERR_SYNTLINESCOPE, /* line scope broken, syntax violated */ @@ -202,7 +205,7 @@ enum tbl_cellt { struct tbl_cell { struct tbl_cell *next; enum tbl_cellt pos; - int spacing; + size_t spacing; int flags; #define TBL_CELL_TALIGN (1 << 0) /* t, T */ #define TBL_CELL_BALIGN (1 << 1) /* d, D */ @@ -224,12 +227,12 @@ struct tbl_row { }; enum tbl_datt { - TBL_DATA_NONE, - TBL_DATA_DATA, - TBL_DATA_HORIZ, - TBL_DATA_DHORIZ, - TBL_DATA_NHORIZ, - TBL_DATA_NDHORIZ + TBL_DATA_NONE, /* has no data */ + TBL_DATA_DATA, /* consists of data/string */ + TBL_DATA_HORIZ, /* horizontal line */ + TBL_DATA_DHORIZ, /* double-horizontal line */ + TBL_DATA_NHORIZ, /* squeezed horizontal line */ + TBL_DATA_NDHORIZ /* squeezed double-horizontal line */ }; /* @@ -237,9 +240,10 @@ enum tbl_datt { * 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_cell *layout; /* layout cell */ + int spans; /* how many spans follow */ struct tbl_dat *next; - char *string; + char *string; /* data (NULL if not TBL_DATA_DATA) */ enum tbl_datt pos; }; @@ -255,9 +259,10 @@ enum tbl_spant { struct tbl_span { struct tbl *tbl; struct tbl_head *head; - struct tbl_row *layout; /* layout row: CAN BE NULL */ + struct tbl_row *layout; /* layout row */ struct tbl_dat *first; struct tbl_dat *last; + int line; /* parse line */ int flags; #define TBL_SPAN_FIRST (1 << 0) #define TBL_SPAN_LAST (1 << 1) @@ -265,50 +270,45 @@ struct tbl_span { struct tbl_span *next; }; -/* - * Available registers (set in libroff, accessed elsewhere). - */ -enum regs { - REG_nS = 0, - REG__MAX +struct eqn { + size_t sz; + char *data; + int line; /* invocation line */ + int pos; /* invocation position */ }; /* - * A register (struct reg) can consist of many types: this consists of - * normalised types from the original string form. + * The type of parse sequence. This value is usually passed via the + * mandoc(1) command line of -man and -mdoc. It's almost exclusively + * -mandoc but the others have been retained for compatibility. */ -union regval { - unsigned u; /* unsigned integer */ +enum mparset { + MPARSE_AUTO, /* magically determine the document type */ + MPARSE_MDOC, /* assume -mdoc */ + MPARSE_MAN /* assume -man */ }; -/* - * 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 */ -}; +typedef void (*mandocmsg)(enum mandocerr, enum mandoclevel, + const char *, int, int, const char *); -/* - * 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]; -}; +struct mparse; +struct mdoc; +struct man; __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 *); +void mparse_free(struct mparse *); +void mparse_reset(struct mparse *); +struct mparse *mparse_alloc(enum mparset, + enum mandoclevel, mandocmsg, void *); +enum mandoclevel mparse_readfd(struct mparse *, int, const char *); +void mparse_result(struct mparse *, struct mdoc **, struct man **); +const char *mparse_strerror(enum mandocerr); +const char *mparse_strlevel(enum mandoclevel); + +void *mandoc_calloc(size_t, size_t); +void *mandoc_malloc(size_t); +void *mandoc_realloc(void *, size_t); __END_DECLS diff --git a/contrib/mdocml/mandoc_char.7 b/contrib/mdocml/mandoc_char.7 index ff693d7778..ec478e09d5 100644 --- a/contrib/mdocml/mandoc_char.7 +++ b/contrib/mdocml/mandoc_char.7 @@ -1,4 +1,4 @@ -.\" $Id: mandoc_char.7,v 1.40 2010/10/29 00:05:53 schwarze Exp $ +.\" $Id: mandoc_char.7,v 1.42 2011/02/09 22:53:20 schwarze Exp $ .\" .\" Copyright (c) 2009 Kristaps Dzonsons .\" @@ -14,7 +14,7 @@ .\" 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 $ +.Dd $Mdocdate: February 9 2011 $ .Dt MANDOC_CHAR 7 .Os .Sh NAME @@ -513,31 +513,54 @@ implementations: .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. +.Sh NUMBERED CHARACTERS +For backward compatibility with existing manuals, +.Xr mandoc 1 +also supports the .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 +.Dl \eN\(aq Ns Ar number Ns \(aq .Pp -The following render differently in -.Fl T Ns Ar html -output mode: -.Bd -ragged -offset indent -\e(~=, \e(nb, \e(nc -.Ed +escape sequence, inserting the character +.Ar number +from the current character set into the output. +Of course, this is inherently non-portable and is already marked +as deprecated in the Heirloom roff manual. +For example, do not use \eN'34', use \e(dq, or even the plain +.Sq \(dq +character where possible. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other other +troff implementations, at this time limited to GNU troff +.Pq Qq groff . .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 +.Bl -dash -compact +.It +In +.Fl T Ns Cm ascii , +the +\e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product], +\e[coproduct], \e(gr, \e(\-h, and \e(a. special characters render +differently between mandoc and groff. +.It +In +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml , +the \e(~=, \e(nb, and \e(nc special characters render differently +between mandoc and groff. +.It +The +.Fl T Ns Cm ps +and +.Fl T Ns Cm pdf +modes format like +.Fl T Ns Cm ascii +instead of rendering glyphs as in groff. +.It +The \e[radicalex], \e[sqrtex], and \e(ru special characters have been omitted +from mandoc either because they are poorly documented or they have no +known representation. +.El .Sh SEE ALSO .Xr mandoc 1 .Sh AUTHORS diff --git a/contrib/mdocml/mdoc.3 b/contrib/mdocml/mdoc.3 deleted file mode 100644 index 1a2fc9bac0..0000000000 --- a/contrib/mdocml/mdoc.3 +++ /dev/null @@ -1,348 +0,0 @@ -.\" $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 index 6ecd5a7b29..3a68ca5498 100644 --- a/contrib/mdocml/mdoc.7 +++ b/contrib/mdocml/mdoc.7 @@ -1,6 +1,6 @@ -.\" $Id: mdoc.7,v 1.174 2011/01/04 23:32:21 kristaps Exp $ +.\" $Id: mdoc.7,v 1.184 2011/04/01 19:50:49 kristaps Exp $ .\" -.\" Copyright (c) 2009, 2010 Kristaps Dzonsons +.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons .\" Copyright (c) 2010 Ingo Schwarze .\" .\" Permission to use, copy, modify, and distribute this software for any @@ -15,7 +15,7 @@ .\" 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 $ +.Dd $Mdocdate: April 1 2011 $ .Dt MDOC 7 .Os .Sh NAME @@ -52,9 +52,9 @@ Other lines are interpreted within the current state. .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. +.Pp +If the first character of a line is a space, that line is printed +with a leading newline. .Ss Comments Text following a .Sq \e\*q , @@ -65,12 +65,14 @@ A macro line with only a control character and comment escape, 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: +.Ss Reserved Terms +Within a macro line, the following terms are reserved: .Pp .Bl -tag -width Ds -offset indent -compact .It \&. .Pq period +.It \e. +.Pq escaped period .It \&, .Pq comma .It \&: @@ -91,14 +93,15 @@ Within a macro line, the following characters are reserved: .Pq exclamation .It \&| .Pq vertical bar +.It \e*(Ba +.Pq reserved-word vertical bar .El .Pp -Use of reserved characters is described in +Use of reserved terms 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. +For general use in macro lines, these can be escaped with a non-breaking +space +.Pq Sq \e& . .Ss Special Characters Special characters may occur in both macro and free-form lines. Sequences begin with the escape character @@ -197,34 +200,6 @@ Thus, the following produces .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: @@ -283,11 +258,13 @@ 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 \&) , +delimiters +.Po +.Sq \&) , .Sq \&] , .Sq \&' , -.Sq \&" ) . +.Sq \&" +.Pc . .Pp The proper spacing is also intelligently preserved if a sentence ends at the boundary of a macro line. @@ -732,7 +709,7 @@ and/or tail .El .Ss Block partial-implicit Like block full-implicit, but with single-line scope closed by -.Sx Reserved Characters +.Sx Reserved Terms or end of line. .Bd -literal -offset indent \&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB @@ -765,7 +742,7 @@ section line, else it is .Sx In-line . .Ss In-line Closed by -.Sx Reserved Characters , +.Sx Reserved Terms , 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 @@ -795,8 +772,8 @@ then the macro accepts an arbitrary number of arguments. .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 \&Ad Ta Yes Ta Yes Ta >0 +.It Sx \&An Ta Yes Ta Yes Ta >0 .It Sx \&Ap Ta Yes Ta Yes Ta 0 .It Sx \&Ar Ta Yes Ta Yes Ta n .It Sx \&At Ta Yes Ta Yes Ta 1 @@ -804,31 +781,31 @@ then the macro accepts an arbitrary number of arguments. .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 \&Cm Ta Yes Ta Yes Ta >0 .It Sx \&Db Ta \&No Ta \&No Ta 1 .It Sx \&Dd Ta \&No Ta \&No Ta n .It Sx \&Dt Ta \&No Ta \&No Ta n -.It Sx \&Dv Ta Yes Ta Yes Ta n +.It Sx \&Dv Ta Yes Ta Yes Ta >0 .It Sx \&Dx Ta Yes Ta Yes Ta n .It Sx \&Em Ta Yes Ta Yes Ta >0 .It Sx \&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 \&Ev Ta Yes Ta Yes Ta >0 .It Sx \&Ex Ta \&No Ta \&No Ta n -.It Sx \&Fa Ta Yes Ta Yes Ta n +.It Sx \&Fa Ta Yes Ta Yes Ta >0 .It Sx \&Fd Ta \&No Ta \&No Ta >0 .It Sx \&Fl Ta Yes Ta Yes Ta n .It Sx \&Fn Ta Yes Ta Yes Ta >0 .It Sx \&Fr Ta \&No Ta \&No Ta n -.It Sx \&Ft Ta Yes Ta Yes Ta n +.It Sx \&Ft Ta Yes Ta Yes Ta >0 .It Sx \&Fx Ta Yes Ta Yes Ta n .It Sx \&Hf Ta \&No Ta \&No Ta n .It Sx \&Ic Ta Yes Ta Yes Ta >0 -.It Sx \&In Ta \&No Ta \&No Ta n +.It Sx \&In Ta \&No Ta \&No Ta 1 .It Sx \&Lb Ta \&No Ta \&No Ta 1 -.It Sx \&Li Ta Yes Ta Yes Ta n -.It Sx \&Lk Ta Yes Ta Yes Ta n +.It Sx \&Li Ta Yes Ta Yes Ta >0 +.It Sx \&Lk Ta Yes Ta Yes Ta >0 .It Sx \&Lp Ta \&No Ta \&No Ta 0 .It Sx \&Ms Ta Yes Ta Yes Ta >0 .It Sx \&Mt Ta Yes Ta Yes Ta >0 @@ -884,8 +861,10 @@ block. Publication date of an .Sx \&Rs block. -This should follow the reduced or canonical form syntax described in -.Sx Dates . +Recommended formats of arguments are +.Ar month day , year +or just +.Ar year . .Ss \&%I Publisher or issuer name of an .Sx \&Rs @@ -1467,17 +1446,36 @@ This is the mandatory first macro of any manual. Its syntax is as follows: .Pp -.D1 Pf \. Sx \&Dd Op Ar date +.D1 Pf \. Sx \&Dd Ar month day , year .Pp The -.Ar date -may be either -.Ar $\&Mdocdate$ , -which signifies the current manual revision date dictated by +.Ar month +is the full English month name, the +.Ar day +is an optionally zero-padded numeral, and the +.Ar year +is the full four-digit year. +.Pp +Other arguments are not portable; the +.Xr mandoc 1 +utility handles them as follows: +.Bl -dash -offset 3n -compact +.It +To have the date automatically filled in by the +.Ox +version of .Xr cvs 1 , -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. +the special string +.Dq $\&Mdocdate$ +can be given as an argument. +.It +A few alternative date formats are accepted as well +and converted to the standard form. +.It +If a date string cannot be parsed, it is used verbatim. +.It +If no date string is given, the current date is used. +.El .Pp Examples: .Dl \&.Dd $\&Mdocdate$ @@ -1863,8 +1861,8 @@ 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 \*qint funcname\*q \*qint arg0\*q \*qint arg1\*q +.Dl \&.Fn funcname \*qint arg0\*q .Dl \&.Fn funcname arg0 .Bd -literal -offset indent -compact \&.Ft functype @@ -1894,7 +1892,7 @@ Invocations usually occur in the following context: .br .Pf \. Sx \&Fa Oo Cm argtype Oc Cm argname .br -\.\.\. +\&.\.\. .br .Pf \. Sx \&Fc .Ed @@ -2203,6 +2201,8 @@ Suppress a space. Following invocation, text is interpreted as free-form text until a macro is encountered. .Pp +This has no effect when invoked at the start of a macro line. +.Pp Examples: .Dl \&.Fl o \&Ns \&Ar output .Pp @@ -2767,9 +2767,12 @@ does not start a new line. \*[hist] .It .Sx \&Dd -without an argument prints -.Dq Epoch . -In mandoc, it resolves to the current date. +with non-standard arguments behaves very strangely. +When there are three arguments, they are printed verbatim. +Any other number of arguments is replaced by the current date, +but without any arguments the string +.Dq Epoch +is printed. .It .Sx \&Fl does not print a dash for an empty argument. @@ -2903,6 +2906,7 @@ This is not supported by mandoc. .Sh SEE ALSO .Xr man 1 , .Xr mandoc 1 , +.Xr eqn 7 , .Xr man 7 , .Xr mandoc_char 7 .Xr roff 7 , diff --git a/contrib/mdocml/mdoc.c b/contrib/mdocml/mdoc.c index af39521720..ea3fda2691 100644 --- a/contrib/mdocml/mdoc.c +++ b/contrib/mdocml/mdoc.c @@ -1,6 +1,6 @@ -/* $Id: mdoc.c,v 1.177 2011/01/03 11:27:33 kristaps Exp $ */ +/* $Id: mdoc.c,v 1.188 2011/03/28 23:52:13 kristaps Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2010 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any @@ -28,6 +28,7 @@ #include #include +#include "mdoc.h" #include "mandoc.h" #include "libmdoc.h" #include "libmandoc.h" @@ -98,9 +99,6 @@ 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) @@ -141,6 +139,8 @@ mdoc_free1(struct mdoc *mdoc) free(mdoc->meta.vol); if (mdoc->meta.msec) free(mdoc->meta.msec); + if (mdoc->meta.date) + free(mdoc->meta.date); } @@ -193,14 +193,13 @@ mdoc_free(struct mdoc *mdoc) * Allocate volatile and non-volatile parse resources. */ struct mdoc * -mdoc_alloc(struct regset *regs, void *data, mandocmsg msg) +mdoc_alloc(struct regset *regs, struct mparse *parse) { struct mdoc *p; p = mandoc_calloc(1, sizeof(struct mdoc)); - p->msg = msg; - p->data = data; + p->parse = parse; p->regs = regs; mdoc_hash_init(); @@ -224,21 +223,52 @@ mdoc_endparse(struct mdoc *m) return(0); } +int +mdoc_addeqn(struct mdoc *m, const struct eqn *ep) +{ + struct mdoc_node *n; + + assert( ! (MDOC_HALT & m->flags)); + + /* No text before an initial macro. */ + + if (SEC_NONE == m->lastnamed) { + mdoc_pmsg(m, ep->line, ep->pos, MANDOCERR_NOTEXT); + return(1); + } + + n = node_alloc(m, ep->line, ep->pos, MDOC_MAX, MDOC_EQN); + n->eqn = ep; + + if ( ! node_append(m, n)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); +} + int mdoc_addspan(struct mdoc *m, const struct tbl_span *sp) { + struct mdoc_node *n; 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); + mdoc_pmsg(m, sp->line, 0, MANDOCERR_NOTEXT); return(1); } - return(mdoc_span_alloc(m, sp)); + n = node_alloc(m, sp->line, 0, MDOC_MAX, MDOC_TBL); + n->span = sp; + + if ( ! node_append(m, n)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); } @@ -267,27 +297,11 @@ mdoc_parseln(struct mdoc *m, int ln, char *buf, int offs) m->flags &= ~MDOC_SYNOPSIS; } - return(('.' == buf[offs] || '\'' == buf[offs]) ? + return(mandoc_getcontrol(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) { @@ -314,8 +328,9 @@ mdoc_macro(MACRO_PROT_ARGS) 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); + if (NULL == m->meta.date) + m->meta.date = mandoc_normdate + (m->parse, NULL, line, ppos); m->flags |= MDOC_PBODY; } @@ -546,23 +561,6 @@ mdoc_elem_alloc(struct mdoc *m, int line, int pos, 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) { @@ -663,15 +661,6 @@ 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) { @@ -798,64 +787,57 @@ static int mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs) { enum mdoct tok; - int i, j, sv; + int i, sv; char mac[5]; struct mdoc_node *n; - /* Empty lines are ignored. */ + /* Empty post-control lines are ignored. */ - offs++; - - if ('\0' == buf[offs]) + if ('"' == buf[offs]) { + mdoc_pmsg(m, ln, offs, MANDOCERR_BADCOMMENT); + return(1); + } else 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; + sv = offs; /* * 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'; + i = 0; + while (i < 4 && '\0' != buf[offs] && + ' ' != buf[offs] && '\t' != buf[offs]) + mac[i++] = buf[offs++]; + + mac[i] = '\0'; + + tok = (i > 1 || i < 4) ? mdoc_hash_find(mac) : MDOC_MAX; - 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); + mandoc_vmsg(MANDOCERR_MACRO, m->parse, + ln, sv, "%s", buf + sv - 1); return(1); } /* Disregard the first trailing tab, if applicable. */ - if ('\t' == buf[i]) - i++; + if ('\t' == buf[offs]) + offs++; /* Jump to the next non-whitespace word. */ - while (buf[i] && ' ' == buf[i]) - i++; + while (buf[offs] && ' ' == buf[offs]) + offs++; /* * Trailing whitespace. Note that tabs are allowed to be passed * into the parser as "text", so we only warn about spaces here. */ - if ('\0' == buf[i] && ' ' == buf[i - 1]) - mdoc_pmsg(m, ln, i - 1, MANDOCERR_EOLNSPACE); + if ('\0' == buf[offs] && ' ' == buf[offs - 1]) + mdoc_pmsg(m, ln, offs - 1, MANDOCERR_EOLNSPACE); /* * If an initial macro or a list invocation, divert directly @@ -863,7 +845,7 @@ mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs) */ if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) { - if ( ! mdoc_macro(m, tok, ln, sv, &i, buf)) + if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) goto err; return(1); } @@ -902,7 +884,7 @@ mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs) /* Normal processing of a macro. */ - if ( ! mdoc_macro(m, tok, ln, sv, &i, buf)) + if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) goto err; return(1); @@ -913,4 +895,48 @@ err: /* Error out. */ return(0); } +enum mdelim +mdoc_isdelim(const char *p) +{ + + if ('\0' == p[0]) + return(DELIM_NONE); + if ('\0' == p[1]) + switch (p[0]) { + case('('): + /* FALLTHROUGH */ + case('['): + return(DELIM_OPEN); + case('|'): + return(DELIM_MIDDLE); + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + return(DELIM_CLOSE); + default: + return(DELIM_NONE); + } + + if ('\\' != p[0]) + return(DELIM_NONE); + + if (0 == strcmp(p + 1, ".")) + return(DELIM_CLOSE); + if (0 == strcmp(p + 1, "*(Ba")) + return(DELIM_MIDDLE); + + return(DELIM_NONE); +} diff --git a/contrib/mdocml/mdoc.h b/contrib/mdocml/mdoc.h index 9c22c33640..9cee098e7f 100644 --- a/contrib/mdocml/mdoc.h +++ b/contrib/mdocml/mdoc.h @@ -1,6 +1,6 @@ -/* $Id: mdoc.h,v 1.114 2011/01/01 12:18:37 kristaps Exp $ */ +/* $Id: mdoc.h,v 1.122 2011/03/22 14:05:45 kristaps Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,9 +17,6 @@ #ifndef MDOC_H #define MDOC_H -/* - * What follows is a list of ALL possible macros. - */ enum mdoct { MDOC_Ap = 0, MDOC_Dd, @@ -146,43 +143,37 @@ enum mdoct { 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_Split, /* -split */ + MDOC_Nosplit, /* -nospli */ + MDOC_Ragged, /* -ragged */ + MDOC_Unfilled, /* -unfilled */ + MDOC_Literal, /* -literal */ + MDOC_File, /* -file */ + MDOC_Offset, /* -offset */ + MDOC_Bullet, /* -bullet */ + MDOC_Dash, /* -dash */ + MDOC_Hyphen, /* -hyphen */ + MDOC_Item, /* -item */ + MDOC_Enum, /* -enum */ + MDOC_Tag, /* -tag */ + MDOC_Diag, /* -diag */ + MDOC_Hang, /* -hang */ + MDOC_Ohang, /* -ohang */ + MDOC_Inset, /* -inset */ + MDOC_Column, /* -column */ + MDOC_Width, /* -width */ + MDOC_Compact, /* -compact */ + MDOC_Std, /* -std */ + MDOC_Filled, /* -filled */ + MDOC_Words, /* -words */ + MDOC_Emphasis, /* -emphasis */ + MDOC_Symbolic, /* -symbolic */ + MDOC_Nested, /* -nested */ + MDOC_Centred, /* -centered */ MDOC_ARG_MAX }; -/* - * Type of a syntax node. - */ enum mdoc_type { MDOC_TEXT, MDOC_ELEM, @@ -191,47 +182,47 @@ enum mdoc_type { MDOC_BODY, MDOC_BLOCK, MDOC_TBL, + MDOC_EQN, MDOC_ROOT }; /* * Section (named/unnamed) of `Sh'. Note that these appear in the - * conventional order imposed by mdoc.7. + * conventional order imposed by mdoc.7. In the case of SEC_NONE, no + * section has been invoked (this shouldn't happen). SEC_CUSTOM refers + * to other sections. */ enum mdoc_sec { - SEC_NONE = 0, /* 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_NONE = 0, + SEC_NAME, /* NAME */ + SEC_LIBRARY, /* LIBRARY */ + SEC_SYNOPSIS, /* SYNOPSIS */ + SEC_DESCRIPTION, /* DESCRIPTION */ + SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */ + SEC_RETURN_VALUES, /* RETURN VALUES */ + SEC_ENVIRONMENT, /* ENVIRONMENT */ + SEC_FILES, /* FILES */ + SEC_EXIT_STATUS, /* EXIT STATUS */ + SEC_EXAMPLES, /* EXAMPLES */ + SEC_DIAGNOSTICS, /* DIAGNOSTICS */ + SEC_COMPATIBILITY, /* COMPATIBILITY */ + SEC_ERRORS, /* ERRORS */ + SEC_SEE_ALSO, /* SEE ALSO */ + SEC_STANDARDS, /* STANDARDS */ + SEC_HISTORY, /* HISTORY */ + SEC_AUTHORS, /* AUTHORS */ + SEC_CAVEATS, /* CAVEATS */ + SEC_BUGS, /* BUGS */ + SEC_SECURITY, /* SECURITY */ + SEC_CUSTOM, SEC__MAX }; -/* - * 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 *date; /* `Dd' normalised date */ char *title; /* `Dt' title (FOO, etc.) */ char *os; /* `Os' system (OpenBSD, etc.) */ char *name; /* leading `Nm' name */ @@ -269,68 +260,50 @@ enum mdoc_endbody { 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_bullet, /* -bullet */ + LIST_column, /* -column */ + LIST_dash, /* -dash */ + LIST_diag, /* -diag */ + LIST_enum, /* -enum */ + LIST_hang, /* -hang */ + LIST_hyphen, /* -hyphen */ + LIST_inset, /* -inset */ + LIST_item, /* -item */ + LIST_ohang, /* -ohang */ + LIST_tag, /* -tag */ LIST_MAX }; -/* - * Normalised `Bd' display type. - */ enum mdoc_disp { DISP__NONE = 0, - DISP_centred, - DISP_ragged, - DISP_unfilled, - DISP_filled, - DISP_literal + DISP_centred, /* -centered */ + DISP_ragged, /* -ragged */ + DISP_unfilled, /* -unfilled */ + DISP_filled, /* -filled */ + DISP_literal /* -literal */ }; -/* - * Normalised `An' splitting argument. - */ enum mdoc_auth { AUTH__NONE = 0, - AUTH_split, - AUTH_nosplit + AUTH_split, /* -split */ + AUTH_nosplit /* -nosplit */ }; -/* - * Normalised `Bf' font type. - */ enum mdoc_font { FONT__NONE = 0, - FONT_Em, - FONT_Li, - FONT_Sy + FONT_Em, /* Em, -emphasis */ + FONT_Li, /* Li, -literal */ + FONT_Sy /* Sy, -symbolic */ }; -/* - * 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 */ @@ -340,22 +313,16 @@ struct mdoc_bl { 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 */ + int quote_T; /* whether to quote %T */ }; /* @@ -390,6 +357,8 @@ struct mdoc_node { #define MDOC_LINE (1 << 3) /* first macro/text on line */ #define MDOC_SYNPRETTY (1 << 4) /* SYNOPSIS-style formatting */ #define MDOC_ENDED (1 << 5) /* rendering has been ended */ +#define MDOC_DELIMO (1 << 6) +#define MDOC_DELIMC (1 << 7) enum mdoc_type type; /* AST node type */ enum mdoc_sec sec; /* current named section */ union mdoc_data *norm; /* normalised args */ @@ -401,35 +370,22 @@ struct mdoc_node { struct mdoc_node *tail; /* BLOCK */ char *string; /* TEXT */ const struct tbl_span *span; /* TBL */ + const struct eqn *eqn; /* EQN */ enum mdoc_endbody end; /* BODY */ }; -/* - * Names of macros. Index is enum mdoct. Indexing into this returns - * the normalised name, e.g., mdoc_macronames[MDOC_Sh] -> "Sh". - */ +/* Names of macros. Index is enum mdoct. */ 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". - */ +/* Names of macro args. Index is enum mdocargt. */ 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 diff --git a/contrib/mdocml/mdoc_argv.c b/contrib/mdocml/mdoc_argv.c index f68e55f5e0..c3fd74b0f3 100644 --- a/contrib/mdocml/mdoc_argv.c +++ b/contrib/mdocml/mdoc_argv.c @@ -1,4 +1,4 @@ -/* $Id: mdoc_argv.c,v 1.62 2010/12/24 14:00:40 kristaps Exp $ */ +/* $Id: mdoc_argv.c,v 1.73 2011/03/23 15:46:02 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons * @@ -26,28 +26,18 @@ #include #include +#include "mdoc.h" #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 +#define MULTI_STEP 5 /* pre-allocate argument values */ static enum mdocargt argv_a2arg(enum mdoct, const char *); static enum margserr args(struct mdoc *, int, int *, char *, int, char **); +static int args_checkpunct(struct mdoc *, + const char *, int, int, int); static int argv(struct mdoc *, int, struct mdoc_argv *, int *, char *); static int argv_single(struct mdoc *, int, @@ -56,10 +46,16 @@ 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 *); +static void argn_free(struct mdoc_arg *, int); -/* Per-argument flags. */ +enum argvflag { + ARGV_NONE, /* no args to flag (e.g., -split) */ + ARGV_SINGLE, /* one arg to flag (e.g., -file xxx) */ + ARGV_MULTI, /* multiple args (e.g., -column xxx yyy) */ + ARGV_OPT_SINGLE /* optional arg (e.g., -offset [xxx]) */ +}; -static int mdoc_argvflags[MDOC_ARG_MAX] = { +static const enum argvflag argvflags[MDOC_ARG_MAX] = { ARGV_NONE, /* MDOC_Split */ ARGV_NONE, /* MDOC_Nosplit */ ARGV_NONE, /* MDOC_Ragged */ @@ -88,7 +84,7 @@ static int mdoc_argvflags[MDOC_ARG_MAX] = { ARGV_NONE /* MDOC_Symbolic */ }; -static int mdoc_argflags[MDOC_MAX] = { +static const int argflags[MDOC_MAX] = { 0, /* Ap */ 0, /* Dd */ 0, /* Dt */ @@ -213,6 +209,59 @@ static int mdoc_argflags[MDOC_MAX] = { 0, /* Ta */ }; +static const enum mdocargt args_Ex[] = { + MDOC_Std, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_An[] = { + MDOC_Split, + MDOC_Nosplit, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bd[] = { + MDOC_Ragged, + MDOC_Unfilled, + MDOC_Filled, + MDOC_Literal, + MDOC_File, + MDOC_Offset, + MDOC_Compact, + MDOC_Centred, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bf[] = { + MDOC_Emphasis, + MDOC_Literal, + MDOC_Symbolic, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bk[] = { + MDOC_Words, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bl[] = { + MDOC_Bullet, + MDOC_Dash, + MDOC_Hyphen, + MDOC_Item, + MDOC_Enum, + MDOC_Tag, + MDOC_Diag, + MDOC_Hang, + MDOC_Ohang, + MDOC_Inset, + MDOC_Column, + MDOC_Width, + MDOC_Offset, + MDOC_Compact, + MDOC_Nested, + MDOC_ARG_MAX +}; /* * Parse an argument from line text. This comes in the form of -key @@ -254,7 +303,7 @@ mdoc_argv(struct mdoc *m, int line, enum mdoct tok, buf[(*pos)++] = '\0'; } - (void)memset(&tmp, 0, sizeof(struct mdoc_argv)); + memset(&tmp, 0, sizeof(struct mdoc_argv)); tmp.line = line; tmp.pos = *pos; @@ -280,13 +329,12 @@ mdoc_argv(struct mdoc *m, int line, enum mdoct tok, arg->argv = mandoc_realloc (arg->argv, arg->argc * sizeof(struct mdoc_argv)); - (void)memcpy(&arg->argv[(int)arg->argc - 1], + memcpy(&arg->argv[(int)arg->argc - 1], &tmp, sizeof(struct mdoc_argv)); return(ARGV_ARG); } - void mdoc_argv_free(struct mdoc_arg *p) { @@ -303,15 +351,14 @@ mdoc_argv_free(struct mdoc_arg *p) assert(p->argc); for (i = (int)p->argc - 1; i >= 0; i--) - mdoc_argn_free(p, i); + argn_free(p, i); free(p->argv); free(p); } - -void -mdoc_argn_free(struct mdoc_arg *p, int iarg) +static void +argn_free(struct mdoc_arg *p, int iarg) { struct mdoc_argv *arg; int j; @@ -328,7 +375,6 @@ mdoc_argn_free(struct mdoc_arg *p, int 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) @@ -337,7 +383,6 @@ mdoc_zargs(struct mdoc *m, int line, int *pos, 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) @@ -345,7 +390,7 @@ mdoc_args(struct mdoc *m, int line, int *pos, int fl; struct mdoc_node *n; - fl = mdoc_argflags[tok]; + fl = argflags[tok]; if (MDOC_It != tok) return(args(m, line, pos, buf, fl, v)); @@ -369,15 +414,12 @@ mdoc_args(struct mdoc *m, int line, int *pos, 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 @@ -405,47 +447,17 @@ args(struct mdoc *m, int line, int *pos, * is unterminated. */ if (MDOC_PHRASELIT & m->flags) - if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE)) - return(ARGS_ERROR); + mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE); 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]; + if (ARGS_DELIM & fl && args_checkpunct(m, buf, *pos, line, fl)) + return(ARGS_PUNCT); + /* * First handle TABSEP items, restricted to `Bl -column'. This * ignores conventional token parsing and instead uses tabs or @@ -493,8 +505,7 @@ args(struct mdoc *m, int line, int *pos, /* Whitespace check for eoln case... */ if ('\0' == *p && ' ' == *(p - 1) && ! (ARGS_NOWARN & fl)) - if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE)) - return(ARGS_ERROR); + mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE); *pos += (int)(p - *v); @@ -538,8 +549,7 @@ args(struct mdoc *m, int line, int *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); + mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE); return(ARGS_QWORD); } @@ -553,8 +563,7 @@ args(struct mdoc *m, int line, int *pos, (*pos)++; if (0 == buf[*pos] && ! (ARGS_NOWARN & fl)) - if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE)) - return(ARGS_ERROR); + mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE); return(ARGS_QWORD); } @@ -577,113 +586,110 @@ args(struct mdoc *m, int line, int *pos, (*pos)++; if ('\0' == buf[*pos] && ! (ARGS_NOWARN & fl)) - if ( ! mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE)) - return(ARGS_ERROR); + mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE); return(ARGS_WORD); } +/* + * Check if the string consists only of space-separated closing + * delimiters. This is a bit of a dance: the first must be a close + * delimiter, but it may be followed by middle delimiters. Arbitrary + * whitespace may separate these tokens. + */ +static int +args_checkpunct(struct mdoc *m, const char *buf, int i, int ln, int fl) +{ + int j; + char dbuf[DELIMSZ]; + enum mdelim d; + + /* First token must be a close-delimiter. */ + + for (j = 0; buf[i] && ' ' != buf[i] && j < DELIMSZ; j++, i++) + dbuf[j] = buf[i]; + + if (DELIMSZ == j) + return(0); + + dbuf[j] = '\0'; + if (DELIM_CLOSE != mdoc_isdelim(dbuf)) + return(0); + + while (' ' == buf[i]) + i++; + + /* Remaining must NOT be open/none. */ + + while (buf[i]) { + j = 0; + while (buf[i] && ' ' != buf[i] && j < DELIMSZ) + dbuf[j++] = buf[i++]; + + if (DELIMSZ == j) + return(0); + + dbuf[j] = '\0'; + d = mdoc_isdelim(dbuf); + if (DELIM_NONE == d || DELIM_OPEN == d) + return(0); + + while (' ' == buf[i]) + i++; + } + + if ( ! (ARGS_NOWARN & fl) && i && ' ' == buf[i - 1]) + mdoc_pmsg(m, ln, i - 1, MANDOCERR_EOLNSPACE); + + return('\0' == buf[i]); +} +/* + * Match up an argument string (e.g., `-foo bar' having "foo") with the + * correrct identifier. It must apply to the given macro. If none was + * found (including bad matches), return MDOC_ARG_MAX. + */ static enum mdocargt argv_a2arg(enum mdoct tok, const char *p) { + const enum mdocargt *args; - /* - * 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! - */ + args = NULL; switch (tok) { case (MDOC_An): - if (0 == strcmp(p, "split")) - return(MDOC_Split); - else if (0 == strcmp(p, "nosplit")) - return(MDOC_Nosplit); + args = args_An; 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); + args = args_Bd; 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); + args = args_Bf; break; - case (MDOC_Bk): - if (0 == strcmp(p, "words")) - return(MDOC_Words); + args = args_Bk; 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); + args = args_Bl; break; - case (MDOC_Rv): /* FALLTHROUGH */ case (MDOC_Ex): - if (0 == strcmp(p, "std")) - return(MDOC_Std); + args = args_Ex; break; default: - break; + return(MDOC_ARG_MAX); } + assert(args); + + for ( ; MDOC_ARG_MAX != *args ; args++) + if (0 == strcmp(p, mdoc_argnames[*args])) + return(*args); + return(MDOC_ARG_MAX); } - static int argv_multi(struct mdoc *m, int line, struct mdoc_argv *v, int *pos, char *buf) @@ -710,7 +716,6 @@ argv_multi(struct mdoc *m, int line, return(1); } - static int argv_opt_single(struct mdoc *m, int line, struct mdoc_argv *v, int *pos, char *buf) @@ -734,7 +739,6 @@ argv_opt_single(struct mdoc *m, int line, return(1); } - /* * Parse a single, mandatory value from the stream. */ @@ -762,7 +766,6 @@ argv_single(struct mdoc *m, int line, return(1); } - /* * Determine rules for parsing arguments. Arguments can either accept * no parameters, an optional single parameter, one parameter, or @@ -776,16 +779,18 @@ argv(struct mdoc *mdoc, int line, v->sz = 0; v->value = NULL; - switch (mdoc_argvflags[v->arg]) { + switch (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 */ + case (ARGV_NONE): break; + default: + abort(); + /* NOTREACHED */ } return(1); diff --git a/contrib/mdocml/mdoc_hash.c b/contrib/mdocml/mdoc_hash.c index 3bf29dfd85..a1a03094f7 100644 --- a/contrib/mdocml/mdoc_hash.c +++ b/contrib/mdocml/mdoc_hash.c @@ -1,4 +1,4 @@ -/* $Id: mdoc_hash.c,v 1.16 2010/06/19 20:46:28 kristaps Exp $ */ +/* $Id: mdoc_hash.c,v 1.17 2011/03/22 14:33:05 kristaps Exp $ */ /* * Copyright (c) 2008, 2009 Kristaps Dzonsons * @@ -27,6 +27,7 @@ #include #include +#include "mdoc.h" #include "mandoc.h" #include "libmdoc.h" diff --git a/contrib/mdocml/mdoc_html.c b/contrib/mdocml/mdoc_html.c index 44b41d2f17..49782a39f9 100644 --- a/contrib/mdocml/mdoc_html.c +++ b/contrib/mdocml/mdoc_html.c @@ -1,6 +1,6 @@ -/* $Id: mdoc_html.c,v 1.142 2011/01/07 13:20:58 kristaps Exp $ */ +/* $Id: mdoc_html.c,v 1.162 2011/04/04 16:48:18 kristaps Exp $ */ /* - * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -410,6 +410,7 @@ print_mdoc_node(MDOC_ARGS) { int child; struct tag *t; + struct htmlpair tag; child = 1; t = h->tags.head; @@ -420,12 +421,47 @@ print_mdoc_node(MDOC_ARGS) child = mdoc_root_pre(m, n, h); break; case (MDOC_TEXT): + /* No tables in this mode... */ + assert(NULL == h->tblt); + + /* + * Make sure that if we're in a literal mode already + * (i.e., within a
) don't print the newline.
+		 */
+		if (' ' == *n->string && MDOC_LINE & n->flags)
+			if ( ! (HTML_LITERAL & h->flags))
+				print_otag(h, TAG_BR, 0, NULL);
+		if (MDOC_DELIMC & n->flags)
+			h->flags |= HTML_NOSPACE;
 		print_text(h, n->string);
+		if (MDOC_DELIMO & n->flags)
+			h->flags |= HTML_NOSPACE;
 		return;
+	case (MDOC_EQN):
+		PAIR_CLASS_INIT(&tag, "eqn");
+		print_otag(h, TAG_SPAN, 1, &tag);
+		print_text(h, n->eqn->data);
+		break;
 	case (MDOC_TBL):
+		/*
+		 * This will take care of initialising all of the table
+		 * state data for the first table, then tearing it down
+		 * for the last one.
+		 */
 		print_tbl(h, n->span);
-		break;
+		return;
 	default:
+		/*
+		 * Close out the current table, if it's open, and unset
+		 * the "meta" table state.  This will be reopened on the
+		 * next table element.
+		 */
+		if (h->tblt) {
+			print_tblclose(h);
+			t = h->tags.head;
+		}
+
+		assert(NULL == h->tblt);
 		if (mdocs[n->tok].pre && ENDBODY_NOT == n->end)
 			child = (*mdocs[n->tok].pre)(m, n, h);
 		break;
@@ -453,7 +489,7 @@ print_mdoc_node(MDOC_ARGS)
 	case (MDOC_ROOT):
 		mdoc_root_post(m, n, h);
 		break;
-	case (MDOC_TBL):
+	case (MDOC_EQN):
 		break;
 	default:
 		if (mdocs[n->tok].post && ENDBODY_NOT == n->end)
@@ -468,9 +504,6 @@ 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");
@@ -490,7 +523,7 @@ mdoc_root_post(MDOC_ARGS)
 	PAIR_CLASS_INIT(&tag[0], "foot-date");
 	print_otag(h, TAG_TD, 1, tag);
 
-	print_text(h, b);
+	print_text(h, m->date);
 	print_stagq(h, tt);
 
 	PAIR_CLASS_INIT(&tag[0], "foot-os");
@@ -718,8 +751,7 @@ mdoc_nm_pre(MDOC_ARGS)
 static int
 mdoc_xr_pre(MDOC_ARGS)
 {
-	struct htmlpair	 	 tag[2];
-	const struct mdoc_node	*nn;
+	struct htmlpair	 tag[2];
 
 	if (NULL == n->child)
 		return(0);
@@ -735,16 +767,16 @@ mdoc_xr_pre(MDOC_ARGS)
 	} else
 		print_otag(h, TAG_A, 1, tag);
 
-	nn = n->child;
-	print_text(h, nn->string);
+	n = n->child;
+	print_text(h, n->string);
 
-	if (NULL == (nn = nn->next))
+	if (NULL == (n = n->next))
 		return(0);
 
 	h->flags |= HTML_NOSPACE;
 	print_text(h, "(");
 	h->flags |= HTML_NOSPACE;
-	print_text(h, nn->string);
+	print_text(h, n->string);
 	h->flags |= HTML_NOSPACE;
 	print_text(h, ")");
 	return(0);
@@ -756,7 +788,8 @@ static int
 mdoc_ns_pre(MDOC_ARGS)
 {
 
-	h->flags |= HTML_NOSPACE;
+	if ( ! (MDOC_LINE & n->flags))
+		h->flags |= HTML_NOSPACE;
 	return(1);
 }
 
@@ -779,6 +812,7 @@ mdoc_xx_pre(MDOC_ARGS)
 {
 	const char	*pp;
 	struct htmlpair	 tag;
+	int		 flags;
 
 	switch (n->tok) {
 	case (MDOC_Bsx):
@@ -805,8 +839,15 @@ mdoc_xx_pre(MDOC_ARGS)
 
 	PAIR_CLASS_INIT(&tag, "unix");
 	print_otag(h, TAG_SPAN, 1, &tag);
+
 	print_text(h, pp);
-	return(1);
+	if (n->child) {
+		flags = h->flags;
+		h->flags |= HTML_KEEP;
+		print_text(h, n->child->string);
+		h->flags = flags;
+	}
+	return(0);
 }
 
 
@@ -814,19 +855,27 @@ mdoc_xx_pre(MDOC_ARGS)
 static int
 mdoc_bx_pre(MDOC_ARGS)
 {
-	const struct mdoc_node	*nn;
-	struct htmlpair		 tag;
+	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 (NULL != (n = n->child)) {
+		print_text(h, n->string);
+		h->flags |= HTML_NOSPACE;
+		print_text(h, "BSD");
+	} else {
+		print_text(h, "BSD");
+		return(0);
+	}
 
-	if (n->child)
+	if (NULL != (n = n->next)) {
+		h->flags |= HTML_NOSPACE;
+		print_text(h, "-");
 		h->flags |= HTML_NOSPACE;
+		print_text(h, n->string);
+	}
 
-	print_text(h, "BSD");
 	return(0);
 }
 
@@ -1037,9 +1086,9 @@ mdoc_bl_pre(MDOC_ARGS)
 static int
 mdoc_ex_pre(MDOC_ARGS)
 {
-	const struct mdoc_node	*nn;
-	struct tag		*t;
-	struct htmlpair		 tag;
+	struct tag	*t;
+	struct htmlpair	 tag;
+	int		 nchild;
 
 	if (n->prev)
 		print_otag(h, TAG_BR, 0, NULL);
@@ -1047,22 +1096,25 @@ mdoc_ex_pre(MDOC_ARGS)
 	PAIR_CLASS_INIT(&tag, "utility");
 
 	print_text(h, "The");
-	for (nn = n->child; nn; nn = nn->next) {
+
+	nchild = n->nchild;
+	for (n = n->child; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+
 		t = print_otag(h, TAG_B, 1, &tag);
-		print_text(h, nn->string);
+		print_text(h, n->string);
 		print_tagq(h, t);
 
-		h->flags |= HTML_NOSPACE;
-
-		if (nn->next && NULL == nn->next->next)
-			print_text(h, ", and");
-		else if (nn->next)
+		if (nchild > 2 && n->next) {
+			h->flags |= HTML_NOSPACE;
 			print_text(h, ",");
-		else
-			h->flags &= ~HTML_NOSPACE;
+		}
+
+		if (n->next && NULL == n->next->next)
+			print_text(h, "and");
 	}
 
-	if (n->child && n->child->next)
+	if (nchild > 1)
 		print_text(h, "utilities exit");
 	else
 		print_text(h, "utility exits");
@@ -1118,14 +1170,13 @@ mdoc_d1_pre(MDOC_ARGS)
 static int
 mdoc_sx_pre(MDOC_ARGS)
 {
-	struct htmlpair		 tag[2];
-	const struct mdoc_node	*nn;
-	char			 buf[BUFSIZ];
+	struct htmlpair	 tag[2];
+	char		 buf[BUFSIZ];
 
 	strlcpy(buf, "#", BUFSIZ);
-	for (nn = n->child; nn; nn = nn->next) {
-		html_idcat(buf, nn->string, BUFSIZ);
-		if (nn->next)
+	for (n = n->child; n; n = n->next) {
+		html_idcat(buf, n->string, BUFSIZ);
+		if (n->next)
 			html_idcat(buf, " ", BUFSIZ);
 	}
 
@@ -1143,7 +1194,7 @@ static int
 mdoc_bd_pre(MDOC_ARGS)
 {
 	struct htmlpair	 	 tag[2];
-	int		 	 comp;
+	int		 	 comp, sv;
 	const struct mdoc_node	*nn;
 	struct roffsu		 su;
 
@@ -1182,6 +1233,11 @@ mdoc_bd_pre(MDOC_ARGS)
 	PAIR_CLASS_INIT(&tag[1], "lit display");
 	print_otag(h, TAG_PRE, 2, tag);
 
+	/* This can be recursive: save & set our literal state. */
+
+	sv = h->flags & HTML_LITERAL;
+	h->flags |= HTML_LITERAL;
+
 	for (nn = n->child; nn; nn = nn->next) {
 		print_mdoc_node(m, nn, h);
 		/*
@@ -1218,6 +1274,9 @@ mdoc_bd_pre(MDOC_ARGS)
 		h->flags |= HTML_NOSPACE;
 	}
 
+	if (0 == sv)
+		h->flags &= ~HTML_LITERAL;
+
 	return(0);
 }
 
@@ -1327,12 +1386,16 @@ mdoc_fa_pre(MDOC_ARGS)
 		t = print_otag(h, TAG_I, 1, &tag);
 		print_text(h, nn->string);
 		print_tagq(h, t);
-		if (nn->next)
+		if (nn->next) {
+			h->flags |= HTML_NOSPACE;
 			print_text(h, ",");
+		}
 	}
 
-	if (n->child && n->next && n->next->tok == MDOC_Fa)
+	if (n->child && n->next && n->next->tok == MDOC_Fa) {
+		h->flags |= HTML_NOSPACE;
 		print_text(h, ",");
+	}
 
 	return(0);
 }
@@ -1342,13 +1405,61 @@ mdoc_fa_pre(MDOC_ARGS)
 static int
 mdoc_fd_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
+	struct htmlpair	 tag[2];
+	char		 buf[BUFSIZ];
+	size_t		 sz;
+	int		 i;
+	struct tag	*t;
 
 	synopsis_pre(h, n);
 
-	PAIR_CLASS_INIT(&tag, "macro");
-	print_otag(h, TAG_B, 1, &tag);
-	return(1);
+	if (NULL == (n = n->child))
+		return(0);
+
+	assert(MDOC_TEXT == n->type);
+
+	if (strcmp(n->string, "#include")) {
+		PAIR_CLASS_INIT(&tag[0], "macro");
+		print_otag(h, TAG_B, 1, tag);
+		return(1);
+	}
+
+	PAIR_CLASS_INIT(&tag[0], "includes");
+	print_otag(h, TAG_B, 1, tag);
+	print_text(h, n->string);
+
+	if (NULL != (n = n->next)) {
+		assert(MDOC_TEXT == n->type);
+		strlcpy(buf, '<' == *n->string || '"' == *n->string ? 
+				n->string + 1 : n->string, BUFSIZ);
+
+		sz = strlen(buf);
+		if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1]))
+			buf[sz - 1] = '\0';
+
+		PAIR_CLASS_INIT(&tag[0], "link-includes");
+		bufinit(h);
+		
+		i = 1;
+		if (h->base_includes) {
+			buffmt_includes(h, buf);
+			PAIR_HREF_INIT(&tag[i], h->buf);
+			i++;
+		} 
+
+		t = print_otag(h, TAG_A, i, tag);
+		print_text(h, n->string);
+		print_tagq(h, t);
+
+		n = n->next;
+	}
+
+	for ( ; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+		print_text(h, n->string);
+	}
+
+	return(0);
 }
 
 
@@ -1389,13 +1500,13 @@ mdoc_ft_pre(MDOC_ARGS)
 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;
+	struct tag	*t;
+	struct htmlpair	 tag[2];
+	char		 nbuf[BUFSIZ];
+	const char	*sp, *ep;
+	int		 sz, i, pretty;
 
+	pretty = MDOC_SYNPRETTY & n->flags;
 	synopsis_pre(h, n);
 
 	/* Split apart into type and name. */
@@ -1453,20 +1564,26 @@ mdoc_fn_pre(MDOC_ARGS)
 	bufcat_style(h, "white-space", "nowrap");
 	PAIR_STYLE_INIT(&tag[1], h);
 
-	for (nn = n->child->next; nn; nn = nn->next) {
+	for (n = n->child->next; n; n = n->next) {
 		i = 1;
 		if (MDOC_SYNPRETTY & n->flags)
 			i = 2;
 		t = print_otag(h, TAG_I, i, tag);
-		print_text(h, nn->string);
+		print_text(h, n->string);
 		print_tagq(h, t);
-		if (nn->next)
+		if (n->next) {
+			h->flags |= HTML_NOSPACE;
 			print_text(h, ",");
+		}
 	}
 
+	h->flags |= HTML_NOSPACE;
 	print_text(h, ")");
-	if (MDOC_SYNPRETTY & n->flags)
+
+	if (pretty) {
+		h->flags |= HTML_NOSPACE;
 		print_text(h, ";");
+	}
 
 	return(0);
 }
@@ -1537,20 +1654,22 @@ mdoc_sp_pre(MDOC_ARGS)
 static int
 mdoc_lk_pre(MDOC_ARGS)
 {
-	const struct mdoc_node	*nn;
-	struct htmlpair		 tag[2];
+	struct htmlpair	 tag[2];
 
-	nn = n->child;
+	if (NULL == (n = n->child))
+		return(0);
+
+	assert(MDOC_TEXT == n->type);
 
 	PAIR_CLASS_INIT(&tag[0], "link-ext");
-	PAIR_HREF_INIT(&tag[1], nn->string);
-	print_otag(h, TAG_A, 2, tag);
+	PAIR_HREF_INIT(&tag[1], n->string);
 
-	if (NULL == nn || NULL == nn->next) 
-		return(1);
+	print_otag(h, TAG_A, 2, tag);
 
-	for (nn = nn->next; nn; nn = nn->next) 
-		print_text(h, nn->string);
+	for (n = n->next; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+		print_text(h, n->string);
+	}
 
 	return(0);
 }
@@ -1560,19 +1679,21 @@ mdoc_lk_pre(MDOC_ARGS)
 static int
 mdoc_mt_pre(MDOC_ARGS)
 {
-	struct htmlpair	 	 tag[2];
-	struct tag		*t;
-	const struct mdoc_node	*nn;
+	struct htmlpair	 tag[2];
+	struct tag	*t;
 
 	PAIR_CLASS_INIT(&tag[0], "link-mail");
 
-	for (nn = n->child; nn; nn = nn->next) {
+	for (n = n->child; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+
 		bufinit(h);
 		bufcat(h, "mailto:");
-		bufcat(h, nn->string);
+		bufcat(h, n->string);
+
 		PAIR_HREF_INIT(&tag[1], h->buf);
 		t = print_otag(h, TAG_A, 2, tag);
-		print_text(h, nn->string);
+		print_text(h, n->string);
 		print_tagq(h, t);
 	}
 	
@@ -1617,7 +1738,9 @@ mdoc_fo_post(MDOC_ARGS)
 
 	if (MDOC_BODY != n->type)
 		return;
+	h->flags |= HTML_NOSPACE;
 	print_text(h, ")");
+	h->flags |= HTML_NOSPACE;
 	print_text(h, ";");
 }
 
@@ -1626,39 +1749,57 @@ mdoc_fo_post(MDOC_ARGS)
 static int
 mdoc_in_pre(MDOC_ARGS)
 {
-	const struct mdoc_node	*nn;
-	struct tag		*t;
-	struct htmlpair		 tag[2];
-	int			 i;
+	struct tag	*t;
+	struct htmlpair	 tag[2];
+	int		 i;
 
 	synopsis_pre(h, n);
 
 	PAIR_CLASS_INIT(&tag[0], "includes");
 	print_otag(h, TAG_B, 1, tag);
 
+	/*
+	 * The first argument of the `In' gets special treatment as
+	 * being a linked value.  Subsequent values are printed
+	 * afterward.  groff does similarly.  This also handles the case
+	 * of no children.
+	 */
+
 	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags)
 		print_text(h, "#include");
 
 	print_text(h, "<");
 	h->flags |= HTML_NOSPACE;
 
-	for (nn = n->child; nn; nn = nn->next) {
+	if (NULL != (n = n->child)) {
+		assert(MDOC_TEXT == n->type);
+
 		PAIR_CLASS_INIT(&tag[0], "link-includes");
-		i = 1;
 		bufinit(h);
+
+		i = 1;
+
 		if (h->base_includes) {
-			buffmt_includes(h, nn->string);
+			buffmt_includes(h, n->string);
 			PAIR_HREF_INIT(&tag[i], h->buf);
 			i++;
-		}
+		} 
+
 		t = print_otag(h, TAG_A, i, tag);
-		print_mdoc_node(m, nn, h);
+		print_text(h, n->string);
 		print_tagq(h, t);
+
+		n = n->next;
 	}
 
 	h->flags |= HTML_NOSPACE;
 	print_text(h, ">");
 
+	for ( ; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+		print_text(h, n->string);
+	}
+
 	return(0);
 }
 
@@ -1679,31 +1820,38 @@ mdoc_ic_pre(MDOC_ARGS)
 static int
 mdoc_rv_pre(MDOC_ARGS)
 {
-	const struct mdoc_node	*nn;
-	struct htmlpair		 tag;
-	struct tag		*t;
+	struct htmlpair	 tag;
+	struct tag	*t;
+	int		 nchild;
 
 	if (n->prev)
 		print_otag(h, TAG_BR, 0, NULL);
 
+	PAIR_CLASS_INIT(&tag, "fname");
+
 	print_text(h, "The");
 
-	for (nn = n->child; nn; nn = nn->next) {
-		PAIR_CLASS_INIT(&tag, "fname");
+	nchild = n->nchild;
+	for (n = n->child; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+
 		t = print_otag(h, TAG_B, 1, &tag);
-		print_text(h, nn->string);
+		print_text(h, n->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, "()");
+		print_text(h, "()");
+
+		if (nchild > 2 && n->next) {
+			h->flags |= HTML_NOSPACE;
+			print_text(h, ",");
+		}
+
+		if (n->next && NULL == n->next->next)
+			print_text(h, "and");
 	}
 
-	if (n->child && n->child->next)
+	if (nchild > 1)
 		print_text(h, "functions return");
 	else
 		print_text(h, "function returns");
@@ -1978,6 +2126,7 @@ mdoc__x_post(MDOC_ARGS)
 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
 		return;
 
+	h->flags |= HTML_NOSPACE;
 	print_text(h, n->next ? "," : ".");
 }
 
diff --git a/contrib/mdocml/mdoc_macro.c b/contrib/mdocml/mdoc_macro.c
index 1c97f88c95..b334b4e402 100644
--- a/contrib/mdocml/mdoc_macro.c
+++ b/contrib/mdocml/mdoc_macro.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_macro.c,v 1.99 2010/12/15 23:39:40 kristaps Exp $ */
+/*	$Id: mdoc_macro.c,v 1.106 2011/03/22 14:33:05 kristaps Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons 
  * Copyright (c) 2010 Ingo Schwarze 
@@ -26,6 +26,7 @@
 #include 
 #include 
 
+#include "mdoc.h"
 #include "mandoc.h"
 #include "libmdoc.h"
 #include "libmandoc.h"
@@ -50,6 +51,8 @@ static	int	  	in_line(MACRO_PROT_ARGS);
 static	int	  	obsolete(MACRO_PROT_ARGS);
 static	int	  	phrase_ta(MACRO_PROT_ARGS);
 
+static	int		dword(struct mdoc *, int, int, 
+				const char *, enum mdelim);
 static	int	  	append_delims(struct mdoc *, 
 				int, int *, char *);
 static	enum mdoct	lookup(enum mdoct, const char *);
@@ -252,17 +255,24 @@ lookup_raw(const char *p)
 static int
 rew_last(struct mdoc *mdoc, const struct mdoc_node *to)
 {
-	struct mdoc_node *n;
+	struct mdoc_node *n, *np;
 
 	assert(to);
 	mdoc->next = MDOC_NEXT_SIBLING;
 
 	/* LINTED */
 	while (mdoc->last != to) {
+		/*
+		 * Save the parent here, because we may delete the
+		 * m->last node in the post-validation phase and reset
+		 * it to m->last->parent, causing a step in the closing
+		 * out to be lost.
+		 */
+		np = mdoc->last->parent;
 		if ( ! mdoc_valid_post(mdoc))
 			return(0);
 		n = mdoc->last;
-		mdoc->last = mdoc->last->parent;
+		mdoc->last = np;
 		assert(mdoc->last);
 		mdoc->last->last = n;
 	}
@@ -506,9 +516,9 @@ make_pending(struct mdoc_node *broken, enum mdoct tok,
 			taker->pending = broken->pending;
 		}
 		broken->pending = breaker;
-		mdoc_vmsg(m, MANDOCERR_SCOPENEST, line, ppos,
-		    "%s breaks %s", mdoc_macronames[tok],
-		    mdoc_macronames[broken->tok]);
+		mandoc_vmsg(MANDOCERR_SCOPENEST, m->parse, line, ppos,
+				"%s breaks %s", mdoc_macronames[tok],
+				mdoc_macronames[broken->tok]);
 		return(1);
 	}
 
@@ -534,9 +544,10 @@ rew_sub(enum mdoc_type t, struct mdoc *m,
 		case (REWIND_THIS):
 			break;
 		case (REWIND_FORCE):
-			mdoc_vmsg(m, MANDOCERR_SCOPEBROKEN, line, ppos,
-			    "%s breaks %s", mdoc_macronames[tok],
-			    mdoc_macronames[n->tok]);
+			mandoc_vmsg(MANDOCERR_SCOPEBROKEN, m->parse, 
+					line, ppos, "%s breaks %s", 
+					mdoc_macronames[tok],
+					mdoc_macronames[n->tok]);
 			/* FALLTHROUGH */
 		case (REWIND_MORE):
 			n = n->parent;
@@ -572,6 +583,28 @@ rew_sub(enum mdoc_type t, struct mdoc *m,
 	return(1);
 }
 
+/*
+ * Allocate a word and check whether it's punctuation or not.
+ * Punctuation consists of those tokens found in mdoc_isdelim().
+ */
+static int
+dword(struct mdoc *m, int line, 
+		int col, const char *p, enum mdelim d)
+{
+	
+	if (DELIM_MAX == d)
+		d = mdoc_isdelim(p);
+
+	if ( ! mdoc_word_alloc(m, line, col, p))
+		return(0);
+
+	if (DELIM_OPEN == d)
+		m->last->flags |= MDOC_DELIMO;
+	else if (DELIM_CLOSE == d)
+		m->last->flags |= MDOC_DELIMC;
+
+	return(1);
+}
 
 static int
 append_delims(struct mdoc *m, int line, int *pos, char *buf)
@@ -592,9 +625,7 @@ append_delims(struct mdoc *m, int line, int *pos, char *buf)
 		else if (ARGS_EOLN == ac)
 			break;
 
-		assert(DELIM_NONE != mdoc_isdelim(p));
-		if ( ! mdoc_word_alloc(m, line, la, p))
-			return(0);
+		dword(m, line, la, p, DELIM_MAX);
 
 		/*
 		 * If we encounter end-of-sentence symbols, then trigger
@@ -703,8 +734,7 @@ blk_exp_close(MACRO_PROT_ARGS)
 	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);
+			mdoc_pmsg(m, line, ppos, MANDOCERR_ARGSLOST);
 
 		if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
 			return(0);
@@ -739,7 +769,7 @@ blk_exp_close(MACRO_PROT_ARGS)
 		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
 		if (MDOC_MAX == ntok) {
-			if ( ! mdoc_word_alloc(m, line, lastarg, p))
+			if ( ! dword(m, line, lastarg, p, DELIM_MAX))
 				return(0);
 			continue;
 		}
@@ -847,9 +877,9 @@ in_line(MACRO_PROT_ARGS)
 					return(0);
 			} else if ( ! nc && 0 == cnt) {
 				mdoc_argv_free(arg);
-				if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY))
-					return(0);
+				mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY);
 			}
+
 			if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
 				return(0);
 			if ( ! nl)
@@ -898,7 +928,8 @@ in_line(MACRO_PROT_ARGS)
 
 		if (DELIM_NONE == d)
 			cnt++;
-		if ( ! mdoc_word_alloc(m, line, la, p))
+
+		if ( ! dword(m, line, la, p, d))
 			return(0);
 
 		/*
@@ -929,8 +960,7 @@ in_line(MACRO_PROT_ARGS)
 			return(0);
 	} else if ( ! nc && 0 == cnt) {
 		mdoc_argv_free(arg);
-		if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY))
-			return(0);
+		mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY);
 	}
 
 	if ( ! nl)
@@ -1055,7 +1085,7 @@ blk_full(MACRO_PROT_ARGS)
 				ARGS_PPHRASE != ac &&
 				ARGS_QWORD != ac &&
 				DELIM_OPEN == mdoc_isdelim(p)) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_OPEN))
 				return(0);
 			continue;
 		}
@@ -1107,7 +1137,7 @@ blk_full(MACRO_PROT_ARGS)
 		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
 		if (MDOC_MAX == ntok) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_MAX))
 				return(0);
 			continue;
 		}
@@ -1217,8 +1247,8 @@ blk_part_imp(MACRO_PROT_ARGS)
 			break;
 
 		if (NULL == body && ARGS_QWORD != ac &&
-		    DELIM_OPEN == mdoc_isdelim(p)) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+				DELIM_OPEN == mdoc_isdelim(p)) {
+			if ( ! dword(m, line, la, p, DELIM_OPEN))
 				return(0);
 			continue;
 		} 
@@ -1232,7 +1262,7 @@ blk_part_imp(MACRO_PROT_ARGS)
 		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
 		if (MDOC_MAX == ntok) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_MAX))
 				return(0);
 			continue;
 		}
@@ -1292,9 +1322,9 @@ blk_part_imp(MACRO_PROT_ARGS)
 	 * 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 != body)
+		mandoc_vmsg(MANDOCERR_SCOPENEST, m->parse, line, ppos, 
+				"%s broken", mdoc_macronames[tok]);
 
 	if (n && ! rew_sub(MDOC_BODY, m, tok, line, ppos))
 		return(0);
@@ -1348,9 +1378,9 @@ blk_part_exp(MACRO_PROT_ARGS)
 		/* Flush out leading punctuation. */
 
 		if (NULL == head && ARGS_QWORD != ac &&
-		    DELIM_OPEN == mdoc_isdelim(p)) {
+				DELIM_OPEN == mdoc_isdelim(p)) {
 			assert(NULL == body);
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_OPEN))
 				return(0);
 			continue;
 		} 
@@ -1371,7 +1401,7 @@ blk_part_exp(MACRO_PROT_ARGS)
 			assert(head);
 			/* No check whether it's a macro! */
 			if (MDOC_Eo == tok)
-				if ( ! mdoc_word_alloc(m, line, la, p))
+				if ( ! dword(m, line, la, p, DELIM_MAX))
 					return(0);
 
 			if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
@@ -1389,7 +1419,7 @@ blk_part_exp(MACRO_PROT_ARGS)
 		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
 		if (MDOC_MAX == ntok) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_MAX))
 				return(0);
 			continue;
 		}
@@ -1454,6 +1484,8 @@ in_line_argn(MACRO_PROT_ARGS)
 	case (MDOC_Ux):
 		maxargs = 0;
 		break;
+	case (MDOC_Bx):
+		/* FALLTHROUGH */
 	case (MDOC_Xr):
 		maxargs = 2;
 		break;
@@ -1492,9 +1524,9 @@ in_line_argn(MACRO_PROT_ARGS)
 			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))
+				ARGS_QWORD != ac && 0 == j && 
+				DELIM_OPEN == mdoc_isdelim(p)) {
+			if ( ! dword(m, line, la, p, DELIM_OPEN))
 				return(0);
 			continue;
 		} else if (0 == j)
@@ -1544,7 +1576,7 @@ in_line_argn(MACRO_PROT_ARGS)
 		}
 #endif
 
-		if ( ! mdoc_word_alloc(m, line, la, p))
+		if ( ! dword(m, line, la, p, DELIM_MAX))
 			return(0);
 		j++;
 	}
@@ -1615,7 +1647,7 @@ in_line_eoln(MACRO_PROT_ARGS)
 		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
 		if (MDOC_MAX == ntok) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_MAX))
 				return(0);
 			continue;
 		}
@@ -1664,7 +1696,8 @@ static int
 obsolete(MACRO_PROT_ARGS)
 {
 
-	return(mdoc_pmsg(m, line, ppos, MANDOCERR_MACROOBS));
+	mdoc_pmsg(m, line, ppos, MANDOCERR_MACROOBS);
+	return(1);
 }
 
 
@@ -1694,7 +1727,7 @@ phrase(struct mdoc *m, int line, int ppos, char *buf)
 		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p);
 
 		if (MDOC_MAX == ntok) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_MAX))
 				return(0);
 			continue;
 		}
@@ -1739,7 +1772,7 @@ phrase_ta(MACRO_PROT_ARGS)
 		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p);
 
 		if (MDOC_MAX == ntok) {
-			if ( ! mdoc_word_alloc(m, line, la, p))
+			if ( ! dword(m, line, la, p, DELIM_MAX))
 				return(0);
 			continue;
 		}
diff --git a/contrib/mdocml/mdoc_strings.c b/contrib/mdocml/mdoc_strings.c
deleted file mode 100644
index e7ced1fe1b..0000000000
--- a/contrib/mdocml/mdoc_strings.c
+++ /dev/null
@@ -1,219 +0,0 @@
-/*	$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
index 0f699abd83..47c212489d 100644
--- a/contrib/mdocml/mdoc_term.c
+++ b/contrib/mdocml/mdoc_term.c
@@ -1,6 +1,6 @@
-/*	$Id: mdoc_term.c,v 1.208 2011/01/06 14:05:12 kristaps Exp $ */
+/*	$Id: mdoc_term.c,v 1.226 2011/04/04 16:27:03 kristaps Exp $ */
 /*
- * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons 
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2010 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -32,7 +32,6 @@
 #include "out.h"
 #include "term.h"
 #include "mdoc.h"
-#include "chars.h"
 #include "main.h"
 
 #define	INDENT		  5
@@ -73,7 +72,6 @@ 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);
@@ -95,6 +93,7 @@ static	int	  termp_bk_pre(DECL_ARGS);
 static	int	  termp_bl_pre(DECL_ARGS);
 static	int	  termp_bold_pre(DECL_ARGS);
 static	int	  termp_bt_pre(DECL_ARGS);
+static	int	  termp_bx_pre(DECL_ARGS);
 static	int	  termp_cd_pre(DECL_ARGS);
 static	int	  termp_d1_pre(DECL_ARGS);
 static	int	  termp_ex_pre(DECL_ARGS);
@@ -187,7 +186,7 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ termp_quote_pre, termp_quote_post }, /* Bo */
 	{ termp_quote_pre, termp_quote_post }, /* Bq */
 	{ termp_xx_pre, NULL }, /* Bsx */
-	{ NULL, termp_bx_post }, /* Bx */
+	{ termp_bx_pre, NULL }, /* Bx */
 	{ NULL, NULL }, /* Db */
 	{ NULL, NULL }, /* Dc */
 	{ termp_quote_pre, termp_quote_post }, /* Do */
@@ -312,20 +311,6 @@ print_mdoc_node(DECL_ARGS)
 
 	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
@@ -357,6 +342,34 @@ print_mdoc_node(DECL_ARGS)
 	     (n->parent && MDOC_SYNPRETTY & n->parent->flags)))
 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
 
+	/*
+	 * After the keep flags have been set up, we may now
+	 * produce output.  Note that some pre-handlers do so.
+	 */
+
+	switch (n->type) {
+	case (MDOC_TEXT):
+		if (' ' == *n->string && MDOC_LINE & n->flags)
+			term_newln(p);
+		if (MDOC_DELIMC & n->flags)
+			p->flags |= TERMP_NOSPACE;
+		term_word(p, n->string);
+		if (MDOC_DELIMO & n->flags)
+			p->flags |= TERMP_NOSPACE;
+		break;
+	case (MDOC_EQN):
+		term_word(p, n->eqn->data);
+		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;
+	}
+
 	if (chld && n->child)
 		print_mdoc_nodelist(p, &npair, m, n->child);
 
@@ -367,6 +380,8 @@ print_mdoc_node(DECL_ARGS)
 		break;
 	case (MDOC_TBL):
 		break;
+	case (MDOC_EQN):
+		break;
 	default:
 		if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
 			break;
@@ -401,7 +416,6 @@ print_mdoc_node(DECL_ARGS)
 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;
@@ -416,24 +430,21 @@ print_mdoc_foot(struct termp *p, const void *arg)
 	 * 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;
+			term_strlen(p, m->date) + term_len(p, 1)) / 2;
 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
 
-	term_word(p, os);
+	term_word(p, m->os);
 	term_flushln(p);
 
 	p->offset = p->rmargin;
-	p->rmargin = p->maxrmargin - term_strlen(p, os);
+	p->rmargin = p->maxrmargin - term_strlen(p, m->os);
 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
 
-	term_word(p, buf);
+	term_word(p, m->date);
 	term_flushln(p);
 
 	p->offset = p->rmargin;
@@ -441,7 +452,7 @@ print_mdoc_foot(struct termp *p, const void *arg)
 	p->flags &= ~TERMP_NOBREAK;
 	p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
 
-	term_word(p, os);
+	term_word(p, m->os);
 	term_flushln(p);
 
 	p->offset = 0;
@@ -1149,7 +1160,8 @@ static int
 termp_ns_pre(DECL_ARGS)
 {
 
-	p->flags |= TERMP_NOSPACE;
+	if ( ! (MDOC_LINE & n->flags))
+		p->flags |= TERMP_NOSPACE;
 	return(1);
 }
 
@@ -1171,25 +1183,30 @@ termp_rs_pre(DECL_ARGS)
 static int
 termp_rv_pre(DECL_ARGS)
 {
-	const struct mdoc_node	*nn;
+	int		 nchild;
 
 	term_newln(p);
 	term_word(p, "The");
 
-	for (nn = n->child; nn; nn = nn->next) {
+	nchild = n->nchild;
+	for (n = n->child; n; n = n->next) {
 		term_fontpush(p, TERMFONT_BOLD);
-		term_word(p, nn->string);
+		term_word(p, n->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, "()");
+		term_word(p, "()");
+
+		if (nchild > 2 && n->next) {
+			p->flags |= TERMP_NOSPACE;
+			term_word(p, ",");
+		}
+
+		if (n->next && NULL == n->next->next)
+			term_word(p, "and");
 	}
 
-	if (n->child && n->child->next)
+	if (nchild > 1)
 		term_word(p, "functions return");
 	else
 		term_word(p, "function returns");
@@ -1212,31 +1229,34 @@ termp_rv_pre(DECL_ARGS)
 static int
 termp_ex_pre(DECL_ARGS)
 {
-	const struct mdoc_node	*nn;
+	int		 nchild;
 
+	term_newln(p);
 	term_word(p, "The");
 
-	for (nn = n->child; nn; nn = nn->next) {
+	nchild = n->nchild;
+	for (n = n->child; n; n = n->next) {
 		term_fontpush(p, TERMFONT_BOLD);
-		term_word(p, nn->string);
+		term_word(p, n->string);
 		term_fontpop(p);
-		p->flags |= TERMP_NOSPACE;
-		if (nn->next && NULL == nn->next->next)
-			term_word(p, ", and");
-		else if (nn->next)
+
+		if (nchild > 2 && n->next) {
+			p->flags |= TERMP_NOSPACE;
 			term_word(p, ",");
-		else
-			p->flags &= ~TERMP_NOSPACE;
+		}
+
+		if (n->next && NULL == n->next->next)
+			term_word(p, "and");
 	}
 
-	if (n->child && n->child->next)
+	if (nchild > 1)
 		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;
 
+	p->flags |= TERMP_SENTENCE;
 	return(0);
 }
 
@@ -1276,31 +1296,33 @@ termp_bl_post(DECL_ARGS)
 		term_newln(p);
 }
 
-
 /* ARGSUSED */
 static int
 termp_xr_pre(DECL_ARGS)
 {
-	const struct mdoc_node *nn;
 
-	if (NULL == n->child)
+	if (NULL == (n = n->child))
 		return(0);
 
-	assert(MDOC_TEXT == n->child->type);
-	nn = n->child;
+	assert(MDOC_TEXT == n->type);
+	term_word(p, n->string);
 
-	term_word(p, nn->string);
-	if (NULL == (nn = nn->next)) 
+	if (NULL == (n = n->next)) 
 		return(0);
+
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, "(");
-	term_word(p, nn->string);
+	p->flags |= TERMP_NOSPACE;
+
+	assert(MDOC_TEXT == n->type);
+	term_word(p, n->string);
+
+	p->flags |= TERMP_NOSPACE;
 	term_word(p, ")");
 
 	return(0);
 }
 
-
 /*
  * This decides how to assert whitespace before any of the SYNOPSIS set
  * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
@@ -1514,30 +1536,43 @@ termp_ft_pre(DECL_ARGS)
 static int
 termp_fn_pre(DECL_ARGS)
 {
-	const struct mdoc_node	*nn;
+	int		 pretty;
+
+	pretty = MDOC_SYNPRETTY & n->flags;
 
 	synopsis_pre(p, n);
 
+	if (NULL == (n = n->child))
+		return(0);
+
+	assert(MDOC_TEXT == n->type);
 	term_fontpush(p, TERMFONT_BOLD);
-	term_word(p, n->child->string);
+	term_word(p, n->string);
 	term_fontpop(p);
 
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, "(");
+	p->flags |= TERMP_NOSPACE;
 
-	for (nn = n->child->next; nn; nn = nn->next) {
+	for (n = n->next; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
 		term_fontpush(p, TERMFONT_UNDER);
-		term_word(p, nn->string);
+		term_word(p, n->string);
 		term_fontpop(p);
 
-		if (nn->next)
+		if (n->next) {
+			p->flags |= TERMP_NOSPACE;
 			term_word(p, ",");
+		}
 	}
 
+	p->flags |= TERMP_NOSPACE;
 	term_word(p, ")");
 
-	if (MDOC_SYNPRETTY & n->flags)
+	if (pretty) {
+		p->flags |= TERMP_NOSPACE;
 		term_word(p, ";");
+	}
 
 	return(0);
 }
@@ -1559,12 +1594,16 @@ termp_fa_pre(DECL_ARGS)
 		term_word(p, nn->string);
 		term_fontpop(p);
 
-		if (nn->next)
+		if (nn->next) {
+			p->flags |= TERMP_NOSPACE;
 			term_word(p, ",");
+		}
 	}
 
-	if (n->child && n->next && n->next->tok == MDOC_Fa)
+	if (n->child && n->next && n->next->tok == MDOC_Fa) {
+		p->flags |= TERMP_NOSPACE;
 		term_word(p, ",");
+	}
 
 	return(0);
 }
@@ -1672,13 +1711,27 @@ termp_bd_post(DECL_ARGS)
 
 
 /* ARGSUSED */
-static void
-termp_bx_post(DECL_ARGS)
+static int
+termp_bx_pre(DECL_ARGS)
 {
 
-	if (n->child)
+	if (NULL != (n = n->child)) {
+		term_word(p, n->string);
 		p->flags |= TERMP_NOSPACE;
-	term_word(p, "BSD");
+		term_word(p, "BSD");
+	} else {
+		term_word(p, "BSD");
+		return(0);
+	}
+
+	if (NULL != (n = n->next)) {
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, "-");
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, n->string);
+	}
+
+	return(0);
 }
 
 
@@ -1687,6 +1740,7 @@ static int
 termp_xx_pre(DECL_ARGS)
 {
 	const char	*pp;
+	int		 flags;
 
 	pp = NULL;
 	switch (n->tok) {
@@ -1712,9 +1766,14 @@ termp_xx_pre(DECL_ARGS)
 		break;
 	}
 
-	assert(pp);
 	term_word(p, pp);
-	return(1);
+	if (n->child) {
+		flags = p->flags;
+		p->flags |= TERMP_KEEP;
+		term_word(p, n->child->string);
+		p->flags = flags;
+	}
+	return(0);
 }
 
 
@@ -1979,6 +2038,7 @@ termp_fo_pre(DECL_ARGS)
 	} else if (MDOC_BODY == n->type) {
 		p->flags |= TERMP_NOSPACE;
 		term_word(p, "(");
+		p->flags |= TERMP_NOSPACE;
 		return(1);
 	} 
 
@@ -2002,10 +2062,13 @@ termp_fo_post(DECL_ARGS)
 	if (MDOC_BODY != n->type) 
 		return;
 
+	p->flags |= TERMP_NOSPACE;
 	term_word(p, ")");
 
-	if (MDOC_SYNPRETTY & n->flags)
+	if (MDOC_SYNPRETTY & n->flags) {
+		p->flags |= TERMP_NOSPACE;
 		term_word(p, ";");
+	}
 }
 
 
@@ -2079,6 +2142,7 @@ termp____post(DECL_ARGS)
 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
 		return;
 
+	p->flags |= TERMP_NOSPACE;
 	if (NULL == n->next) {
 		term_word(p, ".");
 		p->flags |= TERMP_SENTENCE;
@@ -2115,6 +2179,7 @@ termp_lk_pre(DECL_ARGS)
 
 	term_fontpop(p);
 
+	p->flags |= TERMP_NOSPACE;
 	term_word(p, ":");
 
 	term_fontpush(p, TERMFONT_BOLD);
@@ -2166,8 +2231,8 @@ 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)
+	if (n->parent && MDOC_Rs == n->parent->tok &&
+			n->parent->norm->Rs.quote_T)
 		termp_quote_post(p, pair, m, n);
 
 	termp____post(p, pair, m, n);
@@ -2183,7 +2248,7 @@ termp__t_pre(DECL_ARGS)
 	 * us instead of underlining us (for disambiguation).
 	 */
 	if (n->parent && MDOC_Rs == n->parent->tok &&
-			n->parent->norm->Rs.child_J)
+			n->parent->norm->Rs.quote_T)
 		return(termp_quote_pre(p, pair, m, n));
 
 	term_fontpush(p, TERMFONT_UNDER);
diff --git a/contrib/mdocml/mdoc_validate.c b/contrib/mdocml/mdoc_validate.c
index 0146219967..707864441c 100644
--- a/contrib/mdocml/mdoc_validate.c
+++ b/contrib/mdocml/mdoc_validate.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_validate.c,v 1.151 2011/01/03 23:53:51 schwarze Exp $ */
+/*	$Id: mdoc_validate.c,v 1.166 2011/04/03 09:53:50 kristaps Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2010, 2011 Ingo Schwarze 
@@ -33,6 +33,7 @@
 #include 
 #include 
 
+#include "mdoc.h"
 #include "mandoc.h"
 #include "libmdoc.h"
 #include "libmandoc.h"
@@ -74,11 +75,12 @@ static	void	 check_args(struct mdoc *, struct mdoc_node *);
 
 static	int	 concat(struct mdoc *, char *, 
 			const struct mdoc_node *, size_t);
+static	enum mdoc_sec	a2sec(const char *);
+static	size_t		macro2len(enum mdoct);
 
 static	int	 ebool(POST_ARGS);
 static	int	 berr_ge1(POST_ARGS);
 static	int	 bwarn_ge1(POST_ARGS);
-static	int	 eerr_ge1(POST_ARGS);
 static	int	 ewarn_eq0(POST_ARGS);
 static	int	 ewarn_eq1(POST_ARGS);
 static	int	 ewarn_ge1(POST_ARGS);
@@ -96,6 +98,7 @@ static	int	 post_bl_block(POST_ARGS);
 static	int	 post_bl_block_width(POST_ARGS);
 static	int	 post_bl_block_tag(POST_ARGS);
 static	int	 post_bl_head(POST_ARGS);
+static	int	 post_bx(POST_ARGS);
 static	int	 post_dd(POST_ARGS);
 static	int	 post_dt(POST_ARGS);
 static	int	 post_defaults(POST_ARGS);
@@ -104,6 +107,7 @@ 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_ns(POST_ARGS);
 static	int	 post_os(POST_ARGS);
 static	int	 post_ignpar(POST_ARGS);
 static	int	 post_prol(POST_ARGS);
@@ -135,10 +139,11 @@ 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_bx[] = { post_bx, 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_dd[] = { 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 };
@@ -147,6 +152,7 @@ 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_ns[] = { post_ns, 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 };
@@ -154,11 +160,10 @@ 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_text[] = { ewarn_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 };
@@ -175,7 +180,7 @@ 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] = {
+static	const struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* Ap */
 	{ pres_dd, posts_dd },			/* Dd */
 	{ pres_dt, posts_dt },			/* Dt */
@@ -190,21 +195,21 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ pres_bl, posts_bl },			/* Bl */ 
 	{ NULL, NULL },				/* El */
 	{ pres_it, posts_it },			/* It */
-	{ NULL, posts_text },			/* Ad */ 
+	{ NULL, NULL },				/* Ad */ 
 	{ pres_an, posts_an },			/* An */ 
 	{ NULL, posts_defaults },		/* Ar */
-	{ NULL, posts_text },			/* Cd */ 
+	{ NULL, NULL },				/* Cd */ 
 	{ NULL, NULL },				/* Cm */
 	{ NULL, NULL },				/* Dv */ 
-	{ pres_er, posts_text },		/* Er */ 
+	{ pres_er, NULL },			/* Er */ 
 	{ NULL, NULL },				/* Ev */ 
 	{ pres_std, posts_std },		/* Ex */ 
 	{ NULL, NULL },				/* Fa */ 
-	{ pres_fd, posts_wtext },		/* Fd */
+	{ pres_fd, posts_text },		/* Fd */
 	{ NULL, NULL },				/* Fl */
-	{ NULL, posts_text },			/* Fn */ 
-	{ NULL, posts_wtext },			/* Ft */ 
-	{ NULL, posts_text },			/* Ic */ 
+	{ NULL, NULL },				/* Fn */ 
+	{ NULL, NULL },				/* Ft */ 
+	{ NULL, NULL },				/* Ic */ 
 	{ NULL, posts_text1 },			/* In */ 
 	{ NULL, posts_defaults },		/* Li */
 	{ NULL, posts_nd },			/* Nd */
@@ -216,10 +221,10 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, posts_st },			/* St */ 
 	{ NULL, NULL },				/* Va */
 	{ NULL, posts_vt },			/* Vt */ 
-	{ NULL, posts_wtext },			/* Xr */ 
+	{ NULL, posts_text },			/* 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 },			/* %D */
 	{ NULL, posts_text },			/* %I */
 	{ NULL, posts_text },			/* %J */
 	{ NULL, posts_text },			/* %N */
@@ -237,7 +242,7 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* Bo */
 	{ NULL, NULL },				/* Bq */
 	{ NULL, NULL },				/* Bsx */
-	{ NULL, NULL },				/* Bx */
+	{ NULL, posts_bx },			/* Bx */
 	{ NULL, posts_bool },			/* Db */
 	{ NULL, NULL },				/* Dc */
 	{ NULL, NULL },				/* Do */
@@ -247,9 +252,9 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* Em */ 
 	{ NULL, NULL },				/* Eo */
 	{ NULL, NULL },				/* Fx */
-	{ NULL, posts_text },			/* Ms */ 
+	{ NULL, NULL },				/* Ms */ 
 	{ NULL, posts_notext },			/* No */
-	{ NULL, posts_notext },			/* Ns */
+	{ NULL, posts_ns },			/* Ns */
 	{ NULL, NULL },				/* Nx */
 	{ NULL, NULL },				/* Ox */
 	{ NULL, NULL },				/* Pc */
@@ -266,9 +271,9 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* So */
 	{ NULL, NULL },				/* Sq */
 	{ NULL, posts_bool },			/* Sm */ 
-	{ NULL, posts_text },			/* Sx */
-	{ NULL, posts_text },			/* Sy */
-	{ NULL, posts_text },			/* Tn */
+	{ NULL, NULL },				/* Sx */
+	{ NULL, NULL },				/* Sy */
+	{ NULL, NULL },				/* Tn */
 	{ NULL, NULL },				/* Ux */
 	{ NULL, NULL },				/* Xc */
 	{ NULL, NULL },				/* Xo */
@@ -284,7 +289,7 @@ const	struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, posts_eoln },			/* Ud */
 	{ NULL, posts_lb },			/* Lb */
 	{ NULL, posts_notext },			/* Lp */ 
-	{ NULL, posts_text },			/* Lk */ 
+	{ NULL, NULL },				/* Lk */ 
 	{ NULL, posts_defaults },		/* Mt */ 
 	{ NULL, NULL },				/* Brq */ 
 	{ NULL, NULL },				/* Bro */ 
@@ -319,6 +324,30 @@ static	const enum mdoct rsord[RSORD_MAX] = {
 	MDOC__U
 };
 
+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
+};
 
 int
 mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
@@ -336,6 +365,8 @@ mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
 		/* FALLTHROUGH */
 	case (MDOC_TBL):
 		/* FALLTHROUGH */
+	case (MDOC_EQN):
+		/* FALLTHROUGH */
 	case (MDOC_ROOT):
 		return(1);
 	default:
@@ -365,6 +396,8 @@ mdoc_valid_post(struct mdoc *mdoc)
 	switch (mdoc->last->type) {
 	case (MDOC_TEXT):
 		/* FALLTHROUGH */
+	case (MDOC_EQN):
+		/* FALLTHROUGH */
 	case (MDOC_TBL):
 		return(1);
 	case (MDOC_ROOT):
@@ -414,10 +447,10 @@ check_count(struct mdoc *m, enum mdoc_type type,
 	}
 
 	t = lvl == CHECK_WARN ? MANDOCERR_ARGCWARN : MANDOCERR_ARGCOUNT;
-
-	return(mdoc_vmsg(m, t, m->last->line, m->last->pos,
+	mandoc_vmsg(t, m->parse, m->last->line, m->last->pos,
 			"want %s%d children (have %d)",
-			p, val, m->last->nchild));
+			p, val, m->last->nchild);
+	return(1);
 }
 
 static int
@@ -433,12 +466,6 @@ 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)
 {
@@ -555,10 +582,9 @@ check_parent(PRE_ARGS, enum mdoct tok, enum mdoc_type t)
 			(t == n->parent->type))
 		return(1);
 
-	mdoc_vmsg(mdoc, MANDOCERR_SYNTCHILD,
-				n->line, n->pos, "want parent %s",
-				MDOC_ROOT == t ? "" : 
-					mdoc_macronames[tok]);
+	mandoc_vmsg(MANDOCERR_SYNTCHILD, mdoc->parse, n->line, 
+			n->pos, "want parent %s", MDOC_ROOT == t ? 
+			"" : mdoc_macronames[tok]);
 	return(0);
 }
 
@@ -919,7 +945,7 @@ static int
 pre_dt(PRE_ARGS)
 {
 
-	if (0 == mdoc->meta.date || mdoc->meta.os)
+	if (NULL == mdoc->meta.date || mdoc->meta.os)
 		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
 
 	if (mdoc->meta.title)
@@ -932,7 +958,7 @@ static int
 pre_os(PRE_ARGS)
 {
 
-	if (NULL == mdoc->meta.title || 0 == mdoc->meta.date)
+	if (NULL == mdoc->meta.title || NULL == mdoc->meta.date)
 		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
 
 	if (mdoc->meta.os)
@@ -1079,12 +1105,11 @@ post_vt(POST_ARGS)
 	/*
 	 * 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,
+	 * behaviour).  ELEM types must have a child, which is already
+	 * guaranteed by the in_line parsing routine; BLOCK types,
 	 * specifically the BODY, should only have TEXT children.
 	 */
 
-	if (MDOC_ELEM == mdoc->last->type)
-		return(eerr_ge1(mdoc));
 	if (MDOC_BODY != mdoc->last->type)
 		return(1);
 	
@@ -1228,19 +1253,12 @@ post_an(POST_ARGS)
 	struct mdoc_node *np;
 
 	np = mdoc->last;
-	if (AUTH__NONE != np->norm->An.auth && np->child) {
+	if (AUTH__NONE == np->norm->An.auth) {
+		if (0 == np->child)
+			check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0);
+	} else if (np->child)
 		check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0);
-		return(1);
-	}
 
-	/* 
-	 * 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);
 }
 
@@ -1248,7 +1266,7 @@ post_an(POST_ARGS)
 static int
 post_it(POST_ARGS)
 {
-	int		  i, cols, rc;
+	int		  i, cols;
 	enum mdoc_list	  lt;
 	struct mdoc_node *n, *c;
 	enum mandocerr	  er;
@@ -1314,10 +1332,10 @@ post_it(POST_ARGS)
 		else
 			er = MANDOCERR_SYNTARGCOUNT;
 
-		rc = mdoc_vmsg(mdoc, er, 
-				mdoc->last->line, mdoc->last->pos, 
+		mandoc_vmsg(er, mdoc->parse, mdoc->last->line, 
+				mdoc->last->pos, 
 				"columns == %d (have %d)", cols, i);
-		return(rc);
+		return(MANDOCERR_ARGCOUNT == er);
 	default:
 		break;
 	}
@@ -1378,7 +1396,7 @@ post_bl_block_width(POST_ARGS)
 		width = 6;
 	else if (MDOC_MAX == (tok = mdoc_hash_find(n->norm->Bl.width)))
 		return(1);
-	else if (0 == (width = mdoc_macro2len(tok)))  {
+	else if (0 == (width = macro2len(tok)))  {
 		mdoc_nmsg(mdoc, n, MANDOCERR_BADWIDTH);
 		return(1);
 	}
@@ -1435,7 +1453,7 @@ post_bl_block_tag(POST_ARGS)
 			break;
 		}
 
-		if (0 != (ssz = mdoc_macro2len(nn->tok)))
+		if (0 != (ssz = macro2len(nn->tok)))
 			sz = ssz;
 
 		break;
@@ -1681,8 +1699,8 @@ post_rs(POST_ARGS)
 				break;
 
 		if (i < RSORD_MAX) {
-			if (MDOC__J == rsord[i])
-				mdoc->last->norm->Rs.child_J = nn;
+			if (MDOC__J == rsord[i] || MDOC__B == rsord[i])
+				mdoc->last->norm->Rs.quote_T++;
 			next = nn->next;
 			continue;
 		}
@@ -1757,6 +1775,15 @@ post_rs(POST_ARGS)
 	return(1);
 }
 
+static int
+post_ns(POST_ARGS)
+{
+
+	if (MDOC_LINE & mdoc->last->flags)
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNNS);
+	return(1);
+}
+
 static int
 post_sh(POST_ARGS)
 {
@@ -1820,7 +1847,7 @@ post_sh_head(POST_ARGS)
 	if ( ! concat(mdoc, buf, mdoc->last->child, BUFSIZ))
 		return(0);
 
-	sec = mdoc_str2sec(buf);
+	sec = a2sec(buf);
 
 	/* The NAME should be first. */
 
@@ -1970,23 +1997,21 @@ post_dd(POST_ARGS)
 	char		  buf[DATESIZE];
 	struct mdoc_node *n;
 
-	n = mdoc->last;
+	if (mdoc->meta.date)
+		free(mdoc->meta.date);
 
-	if (NULL == n->child) {
-		mdoc->meta.date = time(NULL);
+	n = mdoc->last;
+	if (NULL == n->child || '\0' == n->child->string[0]) {
+		mdoc->meta.date = mandoc_normdate
+			(mdoc->parse, NULL, n->line, n->pos);
 		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);
-	}
+	mdoc->meta.date = mandoc_normdate
+		(mdoc->parse, buf, n->line, n->pos);
 
 	return(1);
 }
@@ -2115,6 +2140,25 @@ post_prol(POST_ARGS)
 	return(1);
 }
 
+static int
+post_bx(POST_ARGS)
+{
+	struct mdoc_node	*n;
+
+	/* 
+	 * Make `Bx's second argument always start with an uppercase
+	 * letter.  Groff checks if it's an "accepted" term, but we just
+	 * uppercase blindly.
+	 */
+
+	n = mdoc->last->child;
+	if (n && NULL != (n = n->next))
+		*n->string = (char)toupper
+			((unsigned char)*n->string);
+
+	return(1);
+}
+
 static int
 post_os(POST_ARGS)
 {
@@ -2150,7 +2194,7 @@ post_os(POST_ARGS)
 			return(0);
 		}
 #else /*!OSNAME */
-		if (uname(&utsname)) {
+		if (-1 == uname(&utsname)) {
 			mdoc_nmsg(mdoc, n, MANDOCERR_UNAME);
                         mdoc->meta.os = mandoc_strdup("UNKNOWN");
                         return(post_prol(mdoc));
@@ -2237,3 +2281,107 @@ concat(struct mdoc *m, char *p, const struct mdoc_node *n, size_t sz)
 	return(1);
 }
 
+static enum mdoc_sec 
+a2sec(const char *p)
+{
+	int		 i;
+
+	for (i = 0; i < (int)SEC__MAX; i++) 
+		if (secnames[i] && 0 == strcmp(p, secnames[i]))
+			return((enum mdoc_sec)i);
+
+	return(SEC_CUSTOM);
+}
+
+static size_t
+macro2len(enum mdoct macro)
+{
+
+	switch (macro) {
+	case(MDOC_Ad):
+		return(12);
+	case(MDOC_Ao):
+		return(12);
+	case(MDOC_An):
+		return(12);
+	case(MDOC_Aq):
+		return(12);
+	case(MDOC_Ar):
+		return(12);
+	case(MDOC_Bo):
+		return(12);
+	case(MDOC_Bq):
+		return(12);
+	case(MDOC_Cd):
+		return(12);
+	case(MDOC_Cm):
+		return(10);
+	case(MDOC_Do):
+		return(10);
+	case(MDOC_Dq):
+		return(12);
+	case(MDOC_Dv):
+		return(12);
+	case(MDOC_Eo):
+		return(12);
+	case(MDOC_Em):
+		return(10);
+	case(MDOC_Er):
+		return(17);
+	case(MDOC_Ev):
+		return(15);
+	case(MDOC_Fa):
+		return(12);
+	case(MDOC_Fl):
+		return(10);
+	case(MDOC_Fo):
+		return(16);
+	case(MDOC_Fn):
+		return(16);
+	case(MDOC_Ic):
+		return(10);
+	case(MDOC_Li):
+		return(16);
+	case(MDOC_Ms):
+		return(6);
+	case(MDOC_Nm):
+		return(10);
+	case(MDOC_No):
+		return(12);
+	case(MDOC_Oo):
+		return(10);
+	case(MDOC_Op):
+		return(14);
+	case(MDOC_Pa):
+		return(32);
+	case(MDOC_Pf):
+		return(12);
+	case(MDOC_Po):
+		return(12);
+	case(MDOC_Pq):
+		return(12);
+	case(MDOC_Ql):
+		return(16);
+	case(MDOC_Qo):
+		return(12);
+	case(MDOC_So):
+		return(12);
+	case(MDOC_Sq):
+		return(12);
+	case(MDOC_Sy):
+		return(6);
+	case(MDOC_Sx):
+		return(16);
+	case(MDOC_Tn):
+		return(10);
+	case(MDOC_Va):
+		return(12);
+	case(MDOC_Vt):
+		return(12);
+	case(MDOC_Xr):
+		return(10);
+	default:
+		break;
+	};
+	return(0);
+}
diff --git a/contrib/mdocml/msec.c b/contrib/mdocml/msec.c
index ba5e8d7839..f51360e597 100644
--- a/contrib/mdocml/msec.c
+++ b/contrib/mdocml/msec.c
@@ -1,4 +1,4 @@
-/*	$Id: msec.c,v 1.8 2010/05/17 22:11:42 kristaps Exp $ */
+/*	$Id: msec.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  *
@@ -21,6 +21,7 @@
 #include 
 #include 
 
+#include "mdoc.h"
 #include "mandoc.h"
 #include "libmdoc.h"
 
diff --git a/contrib/mdocml/out.c b/contrib/mdocml/out.c
index d0629e4325..eb303d5194 100644
--- a/contrib/mdocml/out.c
+++ b/contrib/mdocml/out.c
@@ -1,6 +1,7 @@
-/*	$Id: out.c,v 1.30 2011/01/05 15:37:23 kristaps Exp $ */
+/*	$Id: out.c,v 1.39 2011/03/17 08:49:34 kristaps Exp $ */
 /*
- * Copyright (c) 2009, 2010 Kristaps Dzonsons 
+ * Copyright (c) 2009, 2010, 2011 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
@@ -251,6 +252,49 @@ a2roffdeco(enum roffdeco *d, const char **word, size_t *sz)
 			break;
 		}
 		break;
+
+	case ('N'):
+
+		/*
+		 * Sequence of characters:  backslash,  'N' (i = 0),
+		 * starting delimiter (i = 1), character number (i = 2).
+		 */
+
+		*word = wp + 2;
+		*sz = 0;
+
+		/*
+		 * Cannot use a digit as a starting delimiter;
+		 * but skip the digit anyway.
+		 */
+
+		if (isdigit((int)wp[1]))
+			return(2);
+
+		/*
+		 * Any non-digit terminates the character number.
+		 * That is, the terminating delimiter need not
+		 * match the starting delimiter.
+		 */
+
+		for (i = 2; isdigit((int)wp[i]); i++)
+			(*sz)++;
+
+		/*
+		 * This is only a numbered character
+		 * if the character number has at least one digit.
+		 */
+
+		if (*sz)
+			*d = DECO_NUMBERED;
+
+		/*
+		 * Skip the terminating delimiter, even if it does not
+		 * match, and even if there is no character number.
+		 */
+
+		return(++i);
+
 	case ('h'):
 		/* FALLTHROUGH */
 	case ('v'):
@@ -387,7 +431,8 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
 	 */
 
 	assert(NULL == tbl->cols);
-	tbl->cols = calloc(sp->tbl->cols, sizeof(struct roffcol));
+	tbl->cols = mandoc_calloc
+		((size_t)sp->tbl->cols, sizeof(struct roffcol));
 
 	hp = sp->head;
 
@@ -399,8 +444,7 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
 		 * to data cells in the data section.
 		 */
 		for (dp = sp->first; dp; dp = dp->next) {
-			if (NULL == dp->layout)
-				continue;
+			assert(dp->layout);
 			col = &tbl->cols[dp->layout->head->ident];
 			tblcalc_data(tbl, col, sp->tbl, dp);
 		}
@@ -454,6 +498,8 @@ tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
 	case (TBL_CELL_NUMBER):
 		tblcalc_number(tbl, col, tp, dp);
 		break;
+	case (TBL_CELL_DOWN):
+		break;
 	default:
 		abort();
 		/* NOTREACHED */
@@ -465,6 +511,7 @@ tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
 		const struct tbl_dat *dp)
 {
 	size_t		 sz, bufsz, spsz;
+	const char	*str;
 
 	/* 
 	 * Calculate our width and use the spacing, with a minimum
@@ -472,16 +519,18 @@ tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
 	 * either side, while right/left get a single adjacent space).
 	 */
 
-	sz = bufsz = spsz = 0;
-	if (dp->string)
-		sz = (*tbl->slen)(dp->string, tbl->arg);
+	bufsz = spsz = 0;
+	str = dp->string ? dp->string : "";
+	sz = (*tbl->slen)(str, tbl->arg);
+
+	/* FIXME: TBL_DATA_HORIZ et al.? */
 
 	assert(dp->layout);
 	switch (dp->layout->pos) {
 	case (TBL_CELL_LONG):
 		/* FALLTHROUGH */
 	case (TBL_CELL_CENTRE):
-		bufsz = (*tbl->len)(2, tbl->arg);
+		bufsz = (*tbl->len)(1, tbl->arg);
 		break;
 	default:
 		bufsz = (*tbl->len)(1, tbl->arg);
@@ -504,12 +553,10 @@ tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
 {
 	int 		 i;
 	size_t		 sz, psz, ssz, d;
-	char		*cp;
 	const char	*str;
+	char		*cp;
 	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
@@ -519,18 +566,17 @@ tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
 	 * Finally, re-assign the stored values.
 	 */
 
-	str = "";
-	if (dp->string)
-		str = dp->string;
-
+	str = dp->string ? dp->string : "";
 	sz = (*tbl->slen)(str, tbl->arg);
 
+	/* FIXME: TBL_DATA_HORIZ et al.? */
+
 	buf[0] = tp->decimal;
 	buf[1] = '\0';
 
 	psz = (*tbl->slen)(buf, tbl->arg);
 
-	if (NULL != (cp = strchr(str, tp->decimal))) {
+	if (NULL != (cp = strrchr(str, tp->decimal))) {
 		buf[1] = '\0';
 		for (ssz = 0, i = 0; cp != &str[i]; i++) {
 			buf[0] = str[i];
@@ -557,6 +603,11 @@ tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
 		col->width = sz;
 	if (d > col->decimal)
 		col->decimal = d;
+
+	/* Adjust for stipulated width. */
+
+	if (col->width < dp->layout->spacing)
+		col->width = dp->layout->spacing;
 }
 
 
diff --git a/contrib/mdocml/out.h b/contrib/mdocml/out.h
index f544b8e32e..9bf5357b30 100644
--- a/contrib/mdocml/out.h
+++ b/contrib/mdocml/out.h
@@ -1,6 +1,6 @@
-/*	$Id: out.h,v 1.15 2011/01/05 15:37:23 kristaps Exp $ */
+/*	$Id: out.h,v 1.18 2011/03/22 10:13:01 kristaps Exp $ */
 /*
- * Copyright (c) 2009 Kristaps Dzonsons 
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -17,41 +17,23 @@
 #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_CM, /* centimeters (c) */
+	SCALE_IN, /* inches (i) */
+	SCALE_PC, /* pica (P) */
+	SCALE_PT, /* points (p) */
+	SCALE_EM, /* ems (m) */
+	SCALE_MM, /* mini-ems (M) */
+	SCALE_EN, /* ens (n) */
+	SCALE_BU, /* default horizontal (u) */
+	SCALE_VS, /* default vertical (v) */
+	SCALE_FS, /* syn. for u (f) */
 	SCALE_MAX
 };
 
 enum	roffdeco {
 	DECO_NONE,
+	DECO_NUMBERED, /* numbered character */
 	DECO_SPECIAL, /* special character */
 	DECO_SSPECIAL, /* single-char special */
 	DECO_RESERVED, /* reserved word */
@@ -65,11 +47,33 @@ enum	roffdeco {
 	DECO_MAX
 };
 
+enum	chars {
+	CHARS_ASCII, /* 7-bit ascii representation */
+	CHARS_HTML /* unicode values */
+};
+
+struct	roffcol {
+	size_t		 width; /* width of cell */
+	size_t		 decimal; /* decimal position in cell */
+};
+
 struct	roffsu {
 	enum roffscale	  unit;
 	double		  scale;
 };
 
+typedef	size_t	(*tbl_strlen)(const char *, void *);
+typedef	size_t	(*tbl_len)(size_t, void *);
+
+struct	rofftbl {
+	tbl_strlen	 slen; /* calculate string length */
+	tbl_len		 len; /* produce width of empty space */
+	struct roffcol	*cols; /* master column specifiers */
+	void		*arg; /* passed to slen and len */
+};
+
+__BEGIN_DECLS
+
 #define	SCALE_VS_INIT(p, v) \
 	do { (p)->unit = SCALE_VS; \
 	     (p)->scale = (v); } \
@@ -80,10 +84,18 @@ struct	roffsu {
 	     (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 *);
+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 *);
+
+void		 *chars_init(enum chars);
+const char	 *chars_num2char(const char *, size_t);
+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
 
diff --git a/contrib/mdocml/read.c b/contrib/mdocml/read.c
new file mode 100644
index 0000000000..6c240c2bd2
--- /dev/null
+++ b/contrib/mdocml/read.c
@@ -0,0 +1,765 @@
+/*	$Id: read.c,v 1.10 2011/04/03 10:11:25 kristaps 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.
+ */
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "mdoc.h"
+#include "man.h"
+
+#ifndef MAP_FILE
+#define	MAP_FILE	0
+#endif
+
+#define	REPARSE_LIMIT	1000
+
+struct	buf {
+	char	 	 *buf; /* binary input buffer */
+	size_t		  sz; /* size of binary buffer */
+};
+
+struct	mparse {
+	enum mandoclevel  file_status; /* status of current parse */
+	enum mandoclevel  wlevel; /* ignore messages below this */
+	int		  line; /* line number in the file */
+	enum mparset	  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 interp. stack */
+	mandocmsg	  mmsg; /* warning/error message handler */
+	void		 *arg; /* argument to mmsg */
+	const char	 *file; 
+};
+
+static	void	  resize_buf(struct buf *, size_t);
+static	void	  mparse_buf_r(struct mparse *, struct buf, int);
+static	void	  mparse_readfd_r(struct mparse *, int, const char *, int);
+static	void	  pset(const char *, int, struct mparse *);
+static	void	  pdesc(struct mparse *, const char *, int);
+static	int	  read_whole_file(const char *, int, struct buf *, int *);
+static	void	  mparse_end(struct mparse *);
+
+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",
+	"date missing, using today's date",
+	"cannot parse date, using it verbatim",
+	"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",
+	"skipping no-space macro",
+	"blocks badly nested",
+	"child violates parent syntax",
+	"nested displays are not portable",
+	"already in literal mode",
+	"line scope broken",
+
+	/* 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",
+	"skipping end of block that is not open",
+
+	/* 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",
+	
+	"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",
+	"ignoring extra data cells",
+
+	"input stack limit exceeded, infinite loop?",
+	"skipping bad character",
+	"escaped character not allowed in a name",
+	"skipping text before the first section header",
+	"skipping unknown macro",
+	"NOT IMPLEMENTED, please use groff: skipping request",
+	"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",
+
+	"not a manual",
+	"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	const char * const	mandoclevels[MANDOCLEVEL_MAX] = {
+	"SUCCESS",
+	"RESERVED",
+	"WARNING",
+	"ERROR",
+	"FATAL",
+	"BADARG",
+	"SYSERR"
+};
+
+static void
+resize_buf(struct buf *buf, size_t initial)
+{
+
+	buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial;
+	buf->buf = mandoc_realloc(buf->buf, buf->sz);
+}
+
+static void
+pset(const char *buf, int pos, struct mparse *curp)
+{
+	int		 i;
+
+	/*
+	 * Try to intuit which kind of manual parser should be used.  If
+	 * passed in by command-line (-man, -mdoc), then use that
+	 * explicitly.  If passed as -mandoc, then try to guess from the
+	 * line: either skip dot-lines, use -mdoc when finding `.Dt', or
+	 * default to -man, which is more lenient.
+	 *
+	 * Separate out pmdoc/pman from mdoc/man: the first persists
+	 * through all parsers, while the latter is used per-parse.
+	 */
+
+	if ('.' == buf[0] || '\'' == buf[0]) {
+		for (i = 1; buf[i]; i++)
+			if (' ' != buf[i] && '\t' != buf[i])
+				break;
+		if ('\0' == buf[i])
+			return;
+	}
+
+	switch (curp->inttype) {
+	case (MPARSE_MDOC):
+		if (NULL == curp->pmdoc) 
+			curp->pmdoc = mdoc_alloc(&curp->regs, curp);
+		assert(curp->pmdoc);
+		curp->mdoc = curp->pmdoc;
+		return;
+	case (MPARSE_MAN):
+		if (NULL == curp->pman) 
+			curp->pman = man_alloc(&curp->regs, curp);
+		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);
+		assert(curp->pmdoc);
+		curp->mdoc = curp->pmdoc;
+		return;
+	} 
+
+	if (NULL == curp->pman) 
+		curp->pman = man_alloc(&curp->regs, curp);
+	assert(curp->pman);
+	curp->man = curp->pman;
+}
+
+/*
+ * Main parse routine for an opened file.  This is called for each
+ * opened file and simply loops around the full input file, possibly
+ * nesting (i.e., with `so').
+ */
+static void
+mparse_buf_r(struct mparse *curp, struct buf blk, int start)
+{
+	const struct tbl_span	*span;
+	struct buf	 ln;
+	enum rofferr	 rr;
+	int		 i, of, rc;
+	int		 pos; /* byte number in the ln buffer */
+	int		 lnn; /* line number in the real file */
+	unsigned char	 c;
+
+	memset(&ln, 0, sizeof(struct buf));
+
+	lnn = curp->line; 
+	pos = 0; 
+
+	for (i = 0; i < (int)blk.sz; ) {
+		if (0 == pos && '\0' == blk.buf[i])
+			break;
+
+		if (start) {
+			curp->line = lnn;
+			curp->reparse_count = 0;
+		}
+
+		while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) {
+
+			/*
+			 * When finding an unescaped newline character,
+			 * leave the character loop to process the line.
+			 * Skip a preceding carriage return, if any.
+			 */
+
+			if ('\r' == blk.buf[i] && i + 1 < (int)blk.sz &&
+			    '\n' == blk.buf[i + 1])
+				++i;
+			if ('\n' == blk.buf[i]) {
+				++i;
+				++lnn;
+				break;
+			}
+
+			/* 
+			 * 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)))) {
+				mandoc_msg(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 and at least one other character.
+			 * When it's a newline character, skip it.
+			 * When there is a carriage return in between,
+			 * skip that one as well.
+			 */
+
+			if ('\r' == blk.buf[i + 1] && i + 2 < (int)blk.sz &&
+			    '\n' == blk.buf[i + 2])
+				++i;
+			if ('\n' == blk.buf[i + 1]) {
+				i += 2;
+				++lnn;
+				continue;
+			}
+
+			if ('"' == blk.buf[i + 1]) {
+				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)
+				mparse_buf_r(curp, ln, 0);
+			else
+				mandoc_msg(MANDOCERR_ROFFLOOP, curp,
+					curp->line, pos, NULL);
+			pos = 0;
+			continue;
+		case (ROFF_APPEND):
+			pos = (int)strlen(ln.buf);
+			continue;
+		case (ROFF_RERUN):
+			goto rerun;
+		case (ROFF_IGN):
+			pos = 0;
+			continue;
+		case (ROFF_ERR):
+			assert(MANDOCLEVEL_FATAL <= curp->file_status);
+			break;
+		case (ROFF_SO):
+			mparse_readfd_r(curp, -1, ln.buf + of, 1);
+			if (MANDOCLEVEL_FATAL <= curp->file_status)
+				break;
+			pos = 0;
+			continue;
+		default:
+			break;
+		}
+
+		/*
+		 * If we encounter errors in the recursive parse, make
+		 * sure we don't continue parsing.
+		 */
+
+		if (MANDOCLEVEL_FATAL <= curp->file_status)
+			break;
+
+		/*
+		 * If input parsers have not been allocated, do so now.
+		 * We keep these instanced 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.
+		 * Do the same for ROFF_EQN.
+		 */
+
+		rc = -1;
+
+		if (ROFF_TBL == rr)
+			while (NULL != (span = roff_span(curp->roff))) {
+				rc = curp->man ?
+					man_addspan(curp->man, span) :
+					mdoc_addspan(curp->mdoc, span);
+				if (0 == rc)
+					break;
+			}
+		else if (ROFF_EQN == rr)
+			rc = curp->mdoc ? 
+				mdoc_addeqn(curp->mdoc, 
+					roff_eqn(curp->roff)) :
+				man_addeqn(curp->man,
+					roff_eqn(curp->roff));
+		else if (curp->man || curp->mdoc)
+			rc = curp->man ?
+				man_parseln(curp->man, 
+					curp->line, ln.buf, of) :
+				mdoc_parseln(curp->mdoc, 
+					curp->line, ln.buf, of);
+
+		if (0 == rc) {
+			assert(MANDOCLEVEL_FATAL <= curp->file_status);
+			break;
+		}
+
+		/* 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
+pdesc(struct mparse *curp, const char *file, int fd)
+{
+	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(file, fd, &blk, &with_mmap)) {
+		curp->file_status = MANDOCLEVEL_SYSERR;
+		return;
+	}
+
+	/* Line number is per-file. */
+
+	curp->line = 1;
+
+	mparse_buf_r(curp, blk, 1);
+
+	if (with_mmap)
+		munmap(blk.buf, blk.sz);
+	else
+		free(blk.buf);
+}
+
+static int
+read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
+{
+	struct stat	 st;
+	size_t		 off;
+	ssize_t		 ssz;
+
+	if (-1 == fstat(fd, &st)) {
+		perror(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", 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, 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", file);
+				break;
+			}
+			resize_buf(fb, 65536);
+		}
+		ssz = read(fd, fb->buf + (int)off, fb->sz - off);
+		if (ssz == 0) {
+			fb->sz = off;
+			return(1);
+		}
+		if (ssz == -1) {
+			perror(file);
+			break;
+		}
+		off += (size_t)ssz;
+	}
+
+	free(fb->buf);
+	fb->buf = NULL;
+	return(0);
+}
+
+static void
+mparse_end(struct mparse *curp)
+{
+
+	if (MANDOCLEVEL_FATAL <= curp->file_status)
+		return;
+
+	if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
+		assert(MANDOCLEVEL_FATAL <= curp->file_status);
+		return;
+	}
+
+	if (curp->man && ! man_endparse(curp->man)) {
+		assert(MANDOCLEVEL_FATAL <= curp->file_status);
+		return;
+	}
+
+	if ( ! (curp->man || curp->mdoc)) {
+		mandoc_msg(MANDOCERR_NOTMANUAL, curp, 1, 0, NULL);
+		curp->file_status = MANDOCLEVEL_FATAL;
+		return;
+	}
+
+	roff_endparse(curp->roff);
+}
+
+static void
+mparse_readfd_r(struct mparse *curp, int fd, const char *file, int re)
+{
+	const char	*svfile;
+
+	if (-1 == fd)
+		if (-1 == (fd = open(file, O_RDONLY, 0))) {
+			perror(file);
+			curp->file_status = MANDOCLEVEL_SYSERR;
+			return;
+		}
+
+	svfile = curp->file;
+	curp->file = file;
+
+	pdesc(curp, file, fd);
+
+	if (0 == re && MANDOCLEVEL_FATAL > curp->file_status)
+		mparse_end(curp);
+
+	if (STDIN_FILENO != fd && -1 == close(fd))
+		perror(file);
+
+	curp->file = svfile;
+}
+
+enum mandoclevel
+mparse_readfd(struct mparse *curp, int fd, const char *file)
+{
+
+	mparse_readfd_r(curp, fd, file, 0);
+	return(curp->file_status);
+}
+
+struct mparse *
+mparse_alloc(enum mparset inttype, enum mandoclevel wlevel, mandocmsg mmsg, void *arg)
+{
+	struct mparse	*curp;
+
+	assert(wlevel <= MANDOCLEVEL_FATAL);
+
+	curp = mandoc_calloc(1, sizeof(struct mparse));
+
+	curp->wlevel = wlevel;
+	curp->mmsg = mmsg;
+	curp->arg = arg;
+	curp->inttype = inttype;
+
+	curp->roff = roff_alloc(&curp->regs, curp);
+	return(curp);
+}
+
+void
+mparse_reset(struct mparse *curp)
+{
+
+	memset(&curp->regs, 0, sizeof(struct regset));
+
+	roff_reset(curp->roff);
+
+	if (curp->mdoc)
+		mdoc_reset(curp->mdoc);
+	if (curp->man)
+		man_reset(curp->man);
+
+	curp->file_status = MANDOCLEVEL_OK;
+	curp->mdoc = NULL;
+	curp->man = NULL;
+}
+
+void
+mparse_free(struct mparse *curp)
+{
+
+	if (curp->pmdoc)
+		mdoc_free(curp->pmdoc);
+	if (curp->pman)
+		man_free(curp->pman);
+	if (curp->roff)
+		roff_free(curp->roff);
+
+	free(curp);
+}
+
+void
+mparse_result(struct mparse *curp, struct mdoc **mdoc, struct man **man)
+{
+
+	if (mdoc)
+		*mdoc = curp->mdoc;
+	if (man)
+		*man = curp->man;
+}
+
+void
+mandoc_vmsg(enum mandocerr t, struct mparse *m,
+		int ln, int pos, const char *fmt, ...)
+{
+	char		 buf[256];
+	va_list		 ap;
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
+	va_end(ap);
+
+	mandoc_msg(t, m, ln, pos, buf);
+}
+
+void
+mandoc_msg(enum mandocerr er, struct mparse *m, 
+		int ln, int col, const char *msg)
+{
+	enum mandoclevel level;
+
+	level = MANDOCLEVEL_FATAL;
+	while (er < mandoclimits[level])
+		level--;
+
+	if (level < m->wlevel)
+		return;
+
+	if (m->mmsg)
+		(*m->mmsg)(er, level, m->file, ln, col, msg);
+
+	if (m->file_status < level)
+		m->file_status = level;
+}
+
+const char *
+mparse_strerror(enum mandocerr er)
+{
+
+	return(mandocerrs[er]);
+}
+
+const char *
+mparse_strlevel(enum mandoclevel lvl)
+{
+	return(mandoclevels[lvl]);
+}
diff --git a/contrib/mdocml/roff.3 b/contrib/mdocml/roff.3
deleted file mode 100644
index 7d8bff5c1b..0000000000
--- a/contrib/mdocml/roff.3
+++ /dev/null
@@ -1,177 +0,0 @@
-.\"	$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
index 052edf7874..8f40d96cbe 100644
--- a/contrib/mdocml/roff.7
+++ b/contrib/mdocml/roff.7
@@ -1,4 +1,4 @@
-.\"	$Id: roff.7,v 1.23 2011/01/04 23:32:21 kristaps Exp $
+.\"	$Id: roff.7,v 1.27 2011/02/09 10:03:02 kristaps Exp $
 .\"
 .\" Copyright (c) 2010 Kristaps Dzonsons 
 .\" Copyright (c) 2010 Ingo Schwarze 
@@ -15,7 +15,7 @@
 .\" 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 $
+.Dd $Mdocdate: February 9 2011 $
 .Dt ROFF 7
 .Os
 .Sh NAME
@@ -57,10 +57,6 @@ 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
@@ -86,6 +82,38 @@ Thus, the following request lines are all equivalent:
 \&.ig    end
 \&.   ig end
 .Ed
+.Sh MACRO SYNTAX
+Macros can be defined by the
+.Sx \&de
+request.
+When called, they follow the same syntax as requests, except that
+macro arguments may optionally be quoted by enclosing them
+in double quote characters
+.Pq Sq \(dq .
+To be recognized as the beginning of a quoted argument, the opening
+quote character must be preceded by a space character.
+.Pp
+A quoted argument may contain whitespace, and pairs of double quote
+characters
+.Pq Sq Qq
+resolve to single double quote characters.
+A quoted argument extends to the next double quote character that is not
+part of a pair, or to the end of the input line, whichever comes earlier.
+Leaving out the terminating double quote character at the end of the line
+is discouraged.
+For clarity, if more arguments follow on the same input line,
+it is recommended to follow the terminating double quote character
+by a space character; in case the next character after the terminating
+double quote character is anything else, it is regarded as the beginning
+of the next, unquoted argument.
+.Pp
+Both in quoted and unquoted arguments, pairs of backslashes
+.Pq Sq \e\e
+resolve to single backslashes.
+In unquoted arguments, space characters can alternatively be included
+by preceding them with a backslash
+.Pq Sq \e\~ ,
+but quoting is usually better for clarity.
 .Sh REQUEST REFERENCE
 The
 .Xr mandoc 1
@@ -174,12 +202,9 @@ 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.
+Regarding argument parsing, see
+.Sx MACRO SYNTAX
+above.
 .Pp
 The line invoking the macro will be replaced
 in the input stream by the
@@ -319,6 +344,15 @@ then false is assumed.
 The syntax of this request is similar to
 .Sx \&if
 except that the conditional is missing.
+.Ss \&EN
+End an equation block.
+See
+.Sx \&EQ .
+.Ss \&EQ
+Begin an equation block.
+See
+.Xr eqn 7
+for a description of the equation language.
 .Ss \&hy
 Set automatic hyphenation mode.
 This line-scoped request is currently ignored.
@@ -512,6 +546,16 @@ section with the
 .Cm \&Sh
 macro will reset this register.
 .El
+.Ss \&ns
+Turn on no-space mode.
+This line-scoped request is intended to take no arguments.
+Currently, it is ignored including its arguments,
+and the number of arguments is not checked.
+.Ss \&ps
+Change point size.
+This line-scoped request is intended to take one numerical argument.
+Currently, it is ignored including its arguments,
+and the number of arguments is not checked.
 .Ss \&so
 Include a source file.
 Its syntax is as follows:
@@ -529,6 +573,10 @@ only accepts relative paths not containing the strings
 .Qq ../
 and
 .Qq /.. .
+.Ss \&ta
+Set tab stops.
+This line-scoped request can take an arbitrary number of arguments.
+Currently, it is ignored including its arguments.
 .Ss \&tr
 Output character translation.
 This request is intended to have one argument,
@@ -560,6 +608,19 @@ refers to groff version 1.15.
 .Pp
 .Bl -dash -compact
 .It
+In mandoc, the
+.Sx \&EQ ,
+.Sx \&TE ,
+.Sx \&TS ,
+and
+.Sx \&T& ,
+macros are considered regular macros.
+In all other
+.Nm
+implementations, these are special macros that must be specified without
+spacing between the control character (which must be a period) and the
+macro name.
+.It
 The
 .Cm nS
 register is only compatible with OpenBSD's groff-1.15.
@@ -577,6 +638,7 @@ using the next-line syntax.
 .El
 .Sh SEE ALSO
 .Xr mandoc 1 ,
+.Xr eqn 7 ,
 .Xr man 7 ,
 .Xr mandoc_char 7 ,
 .Xr mdoc 7 ,
diff --git a/contrib/mdocml/roff.c b/contrib/mdocml/roff.c
index 5053bef514..3aa3972b6d 100644
--- a/contrib/mdocml/roff.c
+++ b/contrib/mdocml/roff.c
@@ -1,4 +1,4 @@
-/*	$Id: roff.c,v 1.120 2011/01/03 23:24:16 schwarze Exp $ */
+/*	$Id: roff.c,v 1.130 2011/03/29 09:00:48 kristaps Exp $ */
 /*
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2010, 2011 Ingo Schwarze 
@@ -28,15 +28,11 @@
 #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,
@@ -51,15 +47,21 @@ enum	rofft {
 	ROFF_ie,
 	ROFF_if,
 	ROFF_ig,
+	ROFF_it,
 	ROFF_ne,
 	ROFF_nh,
 	ROFF_nr,
+	ROFF_ns,
+	ROFF_ps,
 	ROFF_rm,
 	ROFF_so,
+	ROFF_ta,
 	ROFF_tr,
 	ROFF_TS,
 	ROFF_TE,
 	ROFF_T_,
+	ROFF_EQ,
+	ROFF_EN,
 	ROFF_cblock,
 	ROFF_ccond, /* FIXME: remove this. */
 	ROFF_USERDEF,
@@ -78,9 +80,8 @@ struct	roffstr {
 };
 
 struct	roff {
+	struct mparse	*parse; /* parse point */
 	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 */
@@ -89,6 +90,9 @@ struct	roff {
 	struct tbl_node	*first_tbl; /* first table parsed */
 	struct tbl_node	*last_tbl; /* last table parsed */
 	struct tbl_node	*tbl; /* current table being parsed */
+	struct eqn_node	*last_eqn; /* last equation parsed */
+	struct eqn_node	*first_eqn; /* first equation parsed */
+	struct eqn_node	*eqn; /* current equation being parsed */
 };
 
 struct	roffnode {
@@ -134,18 +138,21 @@ 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	char		*roff_getname(struct roff *, char **, int, int);
 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	enum rofferr	 roff_rm(ROFF_ARGS);
 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_EQ(ROFF_ARGS);
+static	enum rofferr	 roff_EN(ROFF_ARGS);
 static	enum rofferr	 roff_T_(ROFF_ARGS);
 static	enum rofferr	 roff_userdef(ROFF_ARGS);
 
@@ -171,15 +178,21 @@ static	struct roffmac	 roffs[ROFF_MAX] = {
 	{ "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
 	{ "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
 	{ "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "it", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "ne", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "nh", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "nr", roff_nr, NULL, NULL, 0, NULL },
-	{ "rm", roff_line_error, NULL, NULL, 0, NULL },
+	{ "ns", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ps", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "rm", roff_rm, NULL, NULL, 0, NULL },
 	{ "so", roff_so, NULL, NULL, 0, NULL },
+	{ "ta", roff_line_ignore, 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 },
+	{ "EQ", roff_EQ, NULL, NULL, 0, NULL },
+	{ "EN", roff_EN, NULL, NULL, 0, NULL },
 	{ ".", roff_cblock, NULL, NULL, 0, NULL },
 	{ "\\}", roff_ccond, NULL, NULL, 0, NULL },
 	{ NULL, roff_userdef, NULL, NULL, 0, NULL },
@@ -302,15 +315,22 @@ static void
 roff_free1(struct roff *r)
 {
 	struct tbl_node	*t;
+	struct eqn_node	*e;
 
-	while (r->first_tbl) {
-		t = r->first_tbl;
+	while (NULL != (t = r->first_tbl)) {
 		r->first_tbl = t->next;
 		tbl_free(t);
 	}
 
 	r->first_tbl = r->last_tbl = r->tbl = NULL;
 
+	while (NULL != (e = r->first_eqn)) {
+		r->first_eqn = e->next;
+		eqn_free(e);
+	}
+
+	r->first_eqn = r->last_eqn = r->eqn = NULL;
+
 	while (r->last)
 		roffnode_pop(r);
 
@@ -336,14 +356,13 @@ roff_free(struct roff *r)
 
 
 struct roff *
-roff_alloc(struct regset *regs, void *data, const mandocmsg msg)
+roff_alloc(struct regset *regs, struct mparse *parse)
 {
 	struct roff	*r;
 
 	r = mandoc_calloc(1, sizeof(struct roff));
 	r->regs = regs;
-	r->msg = msg;
-	r->data = data;
+	r->parse = parse;
 	r->rstackpos = -1;
 	
 	roff_hash_init();
@@ -454,7 +473,7 @@ roff_parseln(struct roff *r, int ln, char **bufp,
 {
 	enum rofft	 t;
 	enum rofferr	 e;
-	int		 ppos;
+	int		 ppos, ctl;
 
 	/*
 	 * Run the reserved-word filter only if we have some reserved
@@ -464,30 +483,43 @@ roff_parseln(struct roff *r, int ln, char **bufp,
 	if (r->first_string && ! roff_res(r, bufp, szp, pos))
 		return(ROFF_REPARSE);
 
+	ppos = pos;
+	ctl = mandoc_getcontrol(*bufp, &pos);
+
 	/*
 	 * First, if a scope is open and we're not a macro, pass the
 	 * text through the macro's filter.  If a scope isn't open and
 	 * we're not a macro, just let it through.
+	 * Finally, if there's an equation scope open, divert it into it
+	 * no matter our state.
 	 */
 
-	if (r->last && ! ROFF_CTL((*bufp)[pos])) {
+	if (r->last && ! ctl) {
 		t = r->last->tok;
 		assert(roffs[t].text);
 		e = (*roffs[t].text)
 			(r, t, bufp, szp, ln, pos, pos, offs);
 		assert(ROFF_IGN == e || ROFF_CONT == e);
-		if (ROFF_CONT == e && r->tbl)
-			return(tbl_read(r->tbl, ln, *bufp, *offs));
-		return(e);
-	} else if ( ! ROFF_CTL((*bufp)[pos])) {
+		if (ROFF_CONT != e)
+			return(e);
+		if (r->eqn)
+			return(eqn_read(&r->eqn, ln, *bufp, pos));
 		if (r->tbl)
-			return(tbl_read(r->tbl, ln, *bufp, *offs));
+			return(tbl_read(r->tbl, ln, *bufp, pos));
 		return(ROFF_CONT);
-	}
+	} else if ( ! ctl) {
+		if (r->eqn)
+			return(eqn_read(&r->eqn, ln, *bufp, pos));
+		if (r->tbl)
+			return(tbl_read(r->tbl, ln, *bufp, pos));
+		return(ROFF_CONT);
+	} else if (r->eqn)
+		return(eqn_read(&r->eqn, ln, *bufp, ppos));
 
 	/*
 	 * If a scope is open, go to the child handler for that macro,
 	 * as it may want to preprocess before doing anything with it.
+	 * Don't do so if an equation is open.
 	 */
 
 	if (r->last) {
@@ -495,7 +527,7 @@ roff_parseln(struct roff *r, int ln, char **bufp,
 		assert(roffs[t].sub);
 		return((*roffs[t].sub)
 				(r, t, bufp, szp, 
-				 ln, pos, pos, offs));
+				 ln, ppos, pos, offs));
 	}
 
 	/*
@@ -504,7 +536,6 @@ roff_parseln(struct roff *r, int ln, char **bufp,
 	 * the compilers handle it.
 	 */
 
-	ppos = pos;
 	if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos)))
 		return(ROFF_CONT);
 
@@ -520,18 +551,24 @@ roff_endparse(struct roff *r)
 {
 
 	if (r->last)
-		(*r->msg)(MANDOCERR_SCOPEEXIT, r->data, 
+		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
 				r->last->line, r->last->col, NULL);
 
+	if (r->eqn) {
+		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, 
+				r->eqn->eqn.line, r->eqn->eqn.pos, NULL);
+		eqn_end(r->eqn);
+		r->eqn = NULL;
+	}
+
 	if (r->tbl) {
-		(*r->msg)(MANDOCERR_SCOPEEXIT, r->data, 
+		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, 
 				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.
@@ -543,13 +580,7 @@ roff_parse(struct roff *r, const char *buf, int *pos)
 	size_t		 maclen;
 	enum rofft	 t;
 
-	assert(ROFF_CTL(buf[*pos]));
-	(*pos)++;
-
-	while (' ' == buf[*pos] || '\t' == buf[*pos])
-		(*pos)++;
-
-	if ('\0' == buf[*pos])
+	if ('\0' == buf[*pos] || '"' == buf[*pos])
 		return(ROFF_MAX);
 
 	mac = buf + *pos;
@@ -558,7 +589,8 @@ roff_parse(struct roff *r, const char *buf, int *pos)
 	t = (r->current_string = roff_getstrn(r, mac, maclen))
 	    ? ROFF_USERDEF : roff_hash_find(mac, maclen);
 
-	*pos += maclen;
+	*pos += (int)maclen;
+
 	while (buf[*pos] && ' ' == buf[*pos])
 		(*pos)++;
 
@@ -597,7 +629,7 @@ roff_cblock(ROFF_ARGS)
 	 */
 
 	if (NULL == r->last) {
-		(*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
 		return(ROFF_IGN);
 	}
 
@@ -616,12 +648,12 @@ roff_cblock(ROFF_ARGS)
 	case (ROFF_ig):
 		break;
 	default:
-		(*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
 		return(ROFF_IGN);
 	}
 
 	if ((*bufp)[pos])
-		(*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL);
+		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
 
 	roffnode_pop(r);
 	roffnode_cleanscope(r);
@@ -648,7 +680,7 @@ roff_ccond(ROFF_ARGS)
 {
 
 	if (NULL == r->last) {
-		(*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
 		return(ROFF_IGN);
 	}
 
@@ -660,17 +692,17 @@ roff_ccond(ROFF_ARGS)
 	case (ROFF_if):
 		break;
 	default:
-		(*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
 		return(ROFF_IGN);
 	}
 
 	if (r->last->endspan > -1) {
-		(*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
 		return(ROFF_IGN);
 	}
 
 	if ((*bufp)[pos])
-		(*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL);
+		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
 
 	roffnode_pop(r);
 	roffnode_cleanscope(r);
@@ -690,7 +722,7 @@ roff_block(ROFF_ARGS)
 
 	if (ROFF_ig != tok) {
 		if ('\0' == (*bufp)[pos]) {
-			(*r->msg)(MANDOCERR_NOARGS, r->data, ln, ppos, NULL);
+			mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
 			return(ROFF_IGN);
 		}
 
@@ -704,7 +736,7 @@ roff_block(ROFF_ARGS)
 		if (ROFF_de == tok)
 			name = *bufp + pos;
 		else
-			(*r->msg)(MANDOCERR_REQUEST, r->data, ln, ppos,
+			mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos,
 			    roffs[tok].name);
 
 		while ((*bufp)[pos] && ' ' != (*bufp)[pos])
@@ -754,7 +786,7 @@ roff_block(ROFF_ARGS)
 	r->last->end[(int)sz] = '\0';
 
 	if ((*bufp)[pos])
-		(*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL);
+		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
 
 	return(ROFF_IGN);
 }
@@ -777,11 +809,7 @@ roff_block_sub(ROFF_ARGS)
 	 */
 
 	if (r->last->end) {
-		i = pos + 1;
-		while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
-			i++;
-
-		for (j = 0; r->last->end[j]; j++, i++)
+		for (i = pos, j = 0; r->last->end[j]; j++, i++)
 			if ((*bufp)[i] != r->last->end[j])
 				break;
 
@@ -792,6 +820,10 @@ roff_block_sub(ROFF_ARGS)
 			roffnode_pop(r);
 			roffnode_cleanscope(r);
 
+			while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
+				i++;
+
+			pos = i;
 			if (ROFF_MAX != roff_parse(r, *bufp, &pos))
 				return(ROFF_RERUN);
 			return(ROFF_IGN);
@@ -803,8 +835,8 @@ roff_block_sub(ROFF_ARGS)
 	 * pulling it out of the hashtable.
 	 */
 
-	ppos = pos;
-	t = roff_parse(r, *bufp, &pos);
+	if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos)))
+		return(ROFF_IGN);
 
 	/*
 	 * Macros other than block-end are only significant
@@ -841,7 +873,6 @@ roff_cond_sub(ROFF_ARGS)
 	enum rofft	 t;
 	enum roffrule	 rr;
 
-	ppos = pos;
 	rr = r->last->rule;
 
 	/* 
@@ -934,15 +965,9 @@ static enum rofferr
 roff_line_ignore(ROFF_ARGS)
 {
 
-	return(ROFF_IGN);
-}
-
-/* ARGSUSED */
-static enum rofferr
-roff_line_error(ROFF_ARGS)
-{
+	if (ROFF_it == tok)
+		mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos, "it");
 
-	(*r->msg)(MANDOCERR_REQUEST, r->data, ln, ppos, roffs[tok].name);
 	return(ROFF_IGN);
 }
 
@@ -956,7 +981,7 @@ roff_cond(ROFF_ARGS)
 	/* Stack overflow! */
 
 	if (ROFF_ie == tok && r->rstackpos == RSTACK_MAX - 1) {
-		(*r->msg)(MANDOCERR_MEM, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_MEM, r->parse, ln, ppos, NULL);
 		return(ROFF_ERR);
 	}
 
@@ -987,7 +1012,7 @@ roff_cond(ROFF_ARGS)
 	 */
 
 	if ('\0' == (*bufp)[pos] && sv != pos) {
-		(*r->msg)(MANDOCERR_NOARGS, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
 		return(ROFF_IGN);
 	}
 
@@ -1056,25 +1081,13 @@ roff_ds(ROFF_ARGS)
 	 * will have `bar  "     ' as its value.
 	 */
 
-	name = *bufp + pos;
+	string = *bufp + pos;
+	name = roff_getname(r, &string, ln, 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)
+	/* Read past initial double-quote. */
+	if ('"' == *string)
 		string++;
 
 	/* The rest is the value. */
@@ -1087,31 +1100,14 @@ roff_ds(ROFF_ARGS)
 static enum rofferr
 roff_nr(ROFF_ARGS)
 {
-	const char	*key, *val;
+	const char	*key;
+	char		*val;
 	struct reg	*rg;
 
-	key = &(*bufp)[pos];
+	val = *bufp + pos;
+	key = roff_getname(r, &val, ln, 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))
@@ -1121,13 +1117,29 @@ roff_nr(ROFF_ARGS)
 	return(ROFF_IGN);
 }
 
+/* ARGSUSED */
+static enum rofferr
+roff_rm(ROFF_ARGS)
+{
+	const char	 *name;
+	char		 *cp;
+
+	cp = *bufp + pos;
+	while ('\0' != *cp) {
+		name = roff_getname(r, &cp, ln, (int)(cp - *bufp));
+		if ('\0' != *name)
+			roff_setstr(r, name, NULL, 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);
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
 	else
 		tbl_end(r->tbl);
 
@@ -1141,13 +1153,40 @@ roff_T_(ROFF_ARGS)
 {
 
 	if (NULL == r->tbl)
-		(*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
 	else
 		tbl_restart(ppos, ln, r->tbl);
 
 	return(ROFF_IGN);
 }
 
+/* ARGSUSED */
+static enum rofferr
+roff_EQ(ROFF_ARGS)
+{
+	struct eqn_node	*e;
+
+	assert(NULL == r->eqn);
+	e = eqn_alloc(ppos, ln);
+
+	if (r->last_eqn)
+		r->last_eqn->next = e;
+	else
+		r->first_eqn = r->last_eqn = e;
+
+	r->eqn = r->last_eqn = e;
+	return(ROFF_IGN);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_EN(ROFF_ARGS)
+{
+
+	mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+	return(ROFF_IGN);
+}
+
 /* ARGSUSED */
 static enum rofferr
 roff_TS(ROFF_ARGS)
@@ -1155,11 +1194,11 @@ roff_TS(ROFF_ARGS)
 	struct tbl_node	*t;
 
 	if (r->tbl) {
-		(*r->msg)(MANDOCERR_SCOPEBROKEN, r->data, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_SCOPEBROKEN, r->parse, ln, ppos, NULL);
 		tbl_end(r->tbl);
 	}
 
-	t = tbl_alloc(ppos, ln, r->data, r->msg);
+	t = tbl_alloc(ppos, ln, r->parse);
 
 	if (r->last_tbl)
 		r->last_tbl->next = t;
@@ -1176,7 +1215,7 @@ roff_so(ROFF_ARGS)
 {
 	char *name;
 
-	(*r->msg)(MANDOCERR_SO, r->data, ln, ppos, NULL);
+	mandoc_msg(MANDOCERR_SO, r->parse, ln, ppos, NULL);
 
 	/*
 	 * Handle `so'.  Be EXTREMELY careful, as we shouldn't be
@@ -1187,7 +1226,7 @@ roff_so(ROFF_ARGS)
 
 	name = *bufp + pos;
 	if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) {
-		(*r->msg)(MANDOCERR_SOPATH, r->data, ln, pos, NULL);
+		mandoc_msg(MANDOCERR_SOPATH, r->parse, ln, pos, NULL);
 		return(ROFF_ERR);
 	}
 
@@ -1210,7 +1249,7 @@ roff_userdef(ROFF_ARGS)
 	cp = *bufp + pos;
 	for (i = 0; i < 9; i++)
 		arg[i] = '\0' == *cp ? "" :
-		    mandoc_getarg(&cp, r->msg, r->data, ln, &pos);
+		    mandoc_getarg(r->parse, &cp, ln, &pos);
 
 	/*
 	 * Expand macro arguments.
@@ -1250,6 +1289,39 @@ roff_userdef(ROFF_ARGS)
 	   ROFF_REPARSE : ROFF_APPEND);
 }
 
+static char *
+roff_getname(struct roff *r, char **cpp, int ln, int pos)
+{
+	char	 *name, *cp;
+
+	name = *cpp;
+	if ('\0' == *name)
+		return(name);
+
+	/* Read until end of name. */
+	for (cp = name; '\0' != *cp && ' ' != *cp; cp++) {
+		if ('\\' != *cp)
+			continue;
+		cp++;
+		if ('\\' == *cp)
+			continue;
+		mandoc_msg(MANDOCERR_NAMESC, r->parse, ln, pos, NULL);
+		*cp = '\0';
+		name = cp;
+	}
+
+	/* Nil-terminate name. */
+	if ('\0' != *cp)
+		*(cp++) = '\0';
+
+	/* Read past spaces. */
+	while (' ' == *cp)
+		cp++;
+
+	*cpp = cp;
+	return(name);
+}
+
 /*
  * Store *string into the user-defined string called *name.
  * In multiline mode, append to an existing entry and append '\n';
@@ -1289,7 +1361,7 @@ roff_setstr(struct roff *r, const char *name, const char *string,
 	 * One additional byte for the '\n' in multiline mode,
 	 * and one for the terminating '\0'.
 	 */
-	newch = strlen(string) + (multiline ? 2 : 1);
+	newch = strlen(string) + (multiline ? 2u : 1u);
 	if (NULL == n->string) {
 		n->string = mandoc_malloc(newch);
 		*n->string = '\0';
@@ -1300,7 +1372,7 @@ roff_setstr(struct roff *r, const char *name, const char *string,
 	}
 
 	/* Skip existing content in the destination buffer. */
-	c = n->string + oldch;
+	c = n->string + (int)oldch;
 
 	/* Append new content to the destination buffer. */
 	while (*string) {
@@ -1319,7 +1391,6 @@ roff_setstr(struct roff *r, const char *name, const char *string,
 	*c = '\0';
 }
 
-
 static const char *
 roff_getstrn(const struct roff *r, const char *name, size_t len)
 {
@@ -1332,7 +1403,6 @@ roff_getstrn(const struct roff *r, const char *name, size_t len)
 	return(n ? n->string : NULL);
 }
 
-
 static void
 roff_freestr(struct roff *r)
 {
@@ -1354,3 +1424,10 @@ roff_span(const struct roff *r)
 	
 	return(r->tbl ? tbl_span(r->tbl) : NULL);
 }
+
+const struct eqn *
+roff_eqn(const struct roff *r)
+{
+	
+	return(r->last_eqn ? &r->last_eqn->eqn : NULL);
+}
diff --git a/contrib/mdocml/roff.h b/contrib/mdocml/roff.h
deleted file mode 100644
index 141a469acb..0000000000
--- a/contrib/mdocml/roff.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*	$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
index 5c6798e585..70c21a269e 100644
--- a/contrib/mdocml/st.c
+++ b/contrib/mdocml/st.c
@@ -1,4 +1,4 @@
-/*	$Id: st.c,v 1.8 2010/06/19 20:46:28 kristaps Exp $ */
+/*	$Id: st.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  *
@@ -22,6 +22,7 @@
 #include 
 #include 
 
+#include "mdoc.h"
 #include "mandoc.h"
 #include "libmdoc.h"
 
diff --git a/contrib/mdocml/style.css b/contrib/mdocml/style.css
index 4fb1b5d802..b51c72191c 100644
--- a/contrib/mdocml/style.css
+++ b/contrib/mdocml/style.css
@@ -1,4 +1,4 @@
-/* $Id: style.css,v 1.20 2010/12/24 22:51:13 kristaps Exp $ */
+/* $Id: style.css,v 1.21 2011/02/09 09:52:47 kristaps Exp $ */
 
 html		{ max-width: 800px; }
 body		{ color: #333333;
@@ -30,23 +30,18 @@ h2		{ margin-bottom: 0px; color: #000000; font-size: 0.93em; margin-left: -2ex;
 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; }
+td 		{ vertical-align: top; } /* SYNOPSIS section table. */
 
 /* 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. */
 
@@ -75,6 +70,8 @@ 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		{ color: blue; font-style: normal; } /* Name of utility (Nm). */
 span.opt	{ } /* Options (Op, Oo/Oc). */
@@ -144,3 +141,11 @@ li.list-item	{ }
 
 ol.list-enum	{ padding-left: 2em; }
 li.list-enum	{ }
+
+/* Equation modes.  See eqn(7). */
+
+span.eqn	{ }
+
+/* Table modes.  See tbl(7). */
+
+table.tbl	{ }
diff --git a/contrib/mdocml/tbl.7 b/contrib/mdocml/tbl.7
index 3c7f0c6100..52039bf0e8 100644
--- a/contrib/mdocml/tbl.7
+++ b/contrib/mdocml/tbl.7
@@ -1,6 +1,6 @@
-.\"	$Id: tbl.7,v 1.4 2011/01/07 14:59:52 kristaps Exp $
+.\"	$Id: tbl.7,v 1.13 2011/03/17 15:12:42 kristaps Exp $
 .\"
-.\" Copyright (c) 2010 Kristaps Dzonsons 
+.\" 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
@@ -14,7 +14,7 @@
 .\" 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 $
+.Dd $Mdocdate: March 17 2011 $
 .Dt TBL 7
 .Os
 .Sh NAME
@@ -69,6 +69,13 @@ c5 c5 c5.
 4:5:6
 .TE
 .Ed
+.Pp
+The
+.Nm
+implementation in
+.Xr mandoc 1
+is
+.Ud
 .Sh TABLE STRUCTURE
 Tables are enclosed by the
 .Sq TS
@@ -180,8 +187,6 @@ 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 .
@@ -205,18 +210,29 @@ 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.
+Justify a number around its last decimal point.
 If the decimal point is not found on the number, it's assumed to trail
 the number.
 .It Cm s
+Horizontally span columns from the last
+.No non- Ns Cm s
+data cell.
+It is an error if spanning columns follow a
+.Cm \-
+or
+.Cm \(ba
+cell, or come first.
 This option is not supported by
 .Xr mandoc 1 .
 .It Cm a
-This option is not supported by
-.Xr mandoc 1 .
+Left-justify a literal string and pad with one space.
 .It Cm ^
-This option is not supported by
-.Xr mandoc 1 .
+Vertically span rows from the last
+.No non- Ns Cm ^
+data cell.
+It is an error to invoke a vertical span on the first layout row.
+Unlike a horizontal spanner, you must specify an empty cell (if it not
+empty, the data is discarded) in the corresponding data cell.
 .It Cm \-
 Replace the data cell (its contents will be lost) with a single
 horizontal line.
@@ -231,16 +247,9 @@ Emit a vertical bar instead of data.
 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 minimum width of a column.
 The following case-insensitive modifier keys are available:
 .Cm z ,
 .Cm u ,
@@ -255,6 +264,13 @@ and
 .Cm i .
 All of these are ignored by
 .Xr mandoc 1 .
+.Pp
+For example, the following layout specifies a centre-justified column of
+minimum width 10, followed by vertical bar, followed by a left-justified
+column of minimum width 10, another vertical bar, then a column
+justified about the decimal point in numbers:
+.Pp
+.Dl c10 | l10 | n
 .Ss Data
 The data section follows the last layout row.
 By default, cells in a data section are delimited by a tab.
diff --git a/contrib/mdocml/tbl.c b/contrib/mdocml/tbl.c
index ba591ceb6e..9b331e86bf 100644
--- a/contrib/mdocml/tbl.c
+++ b/contrib/mdocml/tbl.c
@@ -1,6 +1,7 @@
-/*	$Id: tbl.c,v 1.21 2011/01/04 15:02:00 kristaps Exp $ */
+/*	$Id: tbl.c,v 1.24 2011/03/22 09:48:13 kristaps Exp $ */
 /*
- * Copyright (c) 2009, 2010 Kristaps Dzonsons 
+ * Copyright (c) 2009, 2010, 2011 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
@@ -21,7 +22,6 @@
 #include 
 
 #include "mandoc.h"
-#include "roff.h"
 #include "libmandoc.h"
 #include "libroff.h"
 
@@ -66,15 +66,14 @@ tbl_read(struct tbl_node *tbl, int ln, const char *p, int offs)
 }
 
 struct tbl_node *
-tbl_alloc(int pos, int line, void *data, const mandocmsg msg)
+tbl_alloc(int pos, int line, struct mparse *parse)
 {
 	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->parse = parse;
 	p->part = TBL_PART_OPTS;
 	p->opts.tab = '\t';
 	p->opts.linesize = 12;
@@ -125,22 +124,29 @@ 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);
+		mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse, 
+				tbl->line, tbl->pos, NULL);
 
 	tbl->part = TBL_PART_LAYOUT;
 	tbl->line = line;
 	tbl->pos = pos;
 
 	if (NULL == tbl->first_span || NULL == tbl->first_span->first)
-		TBL_MSG(tbl, MANDOCERR_TBLNODATA, tbl->line, tbl->pos);
+		mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse,
+				tbl->line, tbl->pos, NULL);
 }
 
 const struct tbl_span *
-tbl_span(const struct tbl_node *tbl)
+tbl_span(struct tbl_node *tbl)
 {
+	struct tbl_span	 *span;
 
 	assert(tbl);
-	return(tbl->last_span);
+	span = tbl->current_span ? tbl->current_span->next
+				 : tbl->first_span;
+	if (span)
+		tbl->current_span = span;
+	return(span);
 }
 
 void
@@ -148,12 +154,14 @@ 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);
+		mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse, 
+				tbl->line, tbl->pos, NULL);
 
 	if (tbl->last_span)
 		tbl->last_span->flags |= TBL_SPAN_LAST;
 
 	if (TBL_PART_CDATA == tbl->part)
-		TBL_MSG(tbl, MANDOCERR_TBLBLOCK, tbl->line, tbl->pos);
+		mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse, 
+				tbl->line, tbl->pos, NULL);
 }
 
diff --git a/contrib/mdocml/tbl_data.c b/contrib/mdocml/tbl_data.c
index f53e109952..129695d8bb 100644
--- a/contrib/mdocml/tbl_data.c
+++ b/contrib/mdocml/tbl_data.c
@@ -1,6 +1,7 @@
-/*	$Id: tbl_data.c,v 1.14 2011/01/07 14:59:52 kristaps Exp $ */
+/*	$Id: tbl_data.c,v 1.24 2011/03/20 16:02:05 kristaps Exp $ */
 /*
- * Copyright (c) 2009, 2010 Kristaps Dzonsons 
+ * Copyright (c) 2009, 2010, 2011 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
@@ -28,8 +29,10 @@
 #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 *, struct tbl_span *, 
+				int, const char *, int *);
+static	struct tbl_span	*newspan(struct tbl_node *, int, 
+				struct tbl_row *);
 
 static int
 data(struct tbl_node *tbl, struct tbl_span *dp, 
@@ -37,7 +40,7 @@ data(struct tbl_node *tbl, struct tbl_span *dp,
 {
 	struct tbl_dat	*dat;
 	struct tbl_cell	*cp;
-	int		 sv;
+	int		 sv, spans;
 
 	cp = NULL;
 	if (dp->last && dp->last->layout)
@@ -55,12 +58,33 @@ data(struct tbl_node *tbl, struct tbl_span *dp,
 				TBL_CELL_SPAN == cp->pos))
 		cp = cp->next;
 
+	/*
+	 * Stop processing when we reach the end of the available layout
+	 * cells.  This means that we have extra input.
+	 */
+
+	if (NULL == cp) {
+		mandoc_msg(MANDOCERR_TBLEXTRADAT, 
+				tbl->parse, ln, *pos, NULL);
+		/* Skip to the end... */
+		while (p[*pos])
+			(*pos)++;
+		return(1);
+	}
+
 	dat = mandoc_calloc(1, sizeof(struct tbl_dat));
 	dat->layout = cp;
 	dat->pos = TBL_DATA_NONE;
 
-	if (NULL == dat->layout)
-		TBL_MSG(tbl, MANDOCERR_TBLEXTRADAT, ln, *pos);
+	assert(TBL_CELL_SPAN != cp->pos);
+
+	for (spans = 0, cp = cp->next; cp; cp = cp->next)
+		if (TBL_CELL_SPAN == cp->pos)
+			spans++;
+		else
+			break;
+	
+	dat->spans = spans;
 
 	if (dp->last) {
 		dp->last->next = dat;
@@ -83,8 +107,10 @@ data(struct tbl_node *tbl, struct tbl_span *dp,
 		return(0);
 	}
 
-	dat->string = mandoc_malloc(*pos - sv + 1);
-	memcpy(dat->string, &p[sv], *pos - sv);
+	assert(*pos - sv >= 0);
+
+	dat->string = mandoc_malloc((size_t)(*pos - sv + 1));
+	memcpy(dat->string, &p[sv], (size_t)(*pos - sv));
 	dat->string[*pos - sv] = '\0';
 
 	if (p[*pos])
@@ -101,13 +127,12 @@ data(struct tbl_node *tbl, struct tbl_span *dp,
 	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)
+			TBL_CELL_DHORIZ == dat->layout->pos ||
+			TBL_CELL_DOWN == dat->layout->pos)
 		if (TBL_DATA_DATA == dat->pos && '\0' != *dat->string)
-			TBL_MSG(tbl, MANDOCERR_TBLIGNDATA, ln, sv);
+			mandoc_msg(MANDOCERR_TBLIGNDATA, 
+					tbl->parse, ln, sv, NULL);
 
 	return(1);
 }
@@ -123,7 +148,6 @@ tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
 	pos = 0;
 
 	dat = tbl->last_span->last;
-	dat->pos = TBL_DATA_DATA;
 
 	if (p[pos] == 'T' && p[pos + 1] == '}') {
 		pos += 2;
@@ -139,6 +163,8 @@ tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
 		/* Fallthrough: T} is part of a word. */
 	}
 
+	dat->pos = TBL_DATA_DATA;
+
 	if (dat->string) {
 		sz = strlen(p) + strlen(dat->string) + 2;
 		dat->string = mandoc_realloc(dat->string, sz);
@@ -147,9 +173,36 @@ tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
 	} else
 		dat->string = mandoc_strdup(p);
 
+	if (TBL_CELL_DOWN == dat->layout->pos) 
+		mandoc_msg(MANDOCERR_TBLIGNDATA, 
+				tbl->parse, ln, pos, NULL);
+
 	return(0);
 }
 
+static struct tbl_span *
+newspan(struct tbl_node *tbl, int line, struct tbl_row *rp)
+{
+	struct tbl_span	*dp;
+
+	dp = mandoc_calloc(1, sizeof(struct tbl_span));
+	dp->line = line;
+	dp->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;
+		tbl->current_span = NULL;
+		dp->flags |= TBL_SPAN_FIRST;
+	}
+
+	return(dp);
+}
+
 int
 tbl_data(struct tbl_node *tbl, int ln, const char *p)
 {
@@ -160,37 +213,48 @@ tbl_data(struct tbl_node *tbl, int ln, const char *p)
 	pos = 0;
 
 	if ('\0' == p[pos]) {
-		TBL_MSG(tbl, MANDOCERR_TBL, ln, pos);
+		mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, pos, NULL);
 		return(0);
 	}
 
 	/* 
 	 * Choose a layout row: take the one following the last parsed
 	 * span's.  If that doesn't exist, use the last parsed span's.
-	 * If there's no last parsed span, use the first row.  This can
-	 * be NULL!
+	 * If there's no last parsed span, use the first row.  Lastly,
+	 * if the last span was a horizontal line, use the same layout
+	 * (it doesn't "consume" the layout).
 	 */
 
 	if (tbl->last_span) {
 		assert(tbl->last_span->layout);
-		rp = tbl->last_span->layout->next;
+		if (tbl->last_span->pos == TBL_SPAN_DATA) {
+			for (rp = tbl->last_span->layout->next;
+					rp && rp->first; rp = rp->next) {
+				switch (rp->first->pos) {
+				case (TBL_CELL_HORIZ):
+					dp = newspan(tbl, ln, rp);
+					dp->pos = TBL_SPAN_HORIZ;
+					continue;
+				case (TBL_CELL_DHORIZ):
+					dp = newspan(tbl, ln, rp);
+					dp->pos = TBL_SPAN_DHORIZ;
+					continue;
+				default:
+					break;
+				}
+				break;
+			}
+		} else
+			rp = tbl->last_span->layout;
+
 		if (NULL == rp)
 			rp = tbl->last_span->layout;
 	} else
 		rp = tbl->first_row;
 
-	dp = mandoc_calloc(1, sizeof(struct tbl_span));
-	dp->tbl = &tbl->opts;
-	dp->layout = rp;
-	dp->head = tbl->first_head;
+	assert(rp);
 
-	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;
-	}
+	dp = newspan(tbl, ln, rp);
 
 	if ( ! strcmp(p, "_")) {
 		dp->pos = TBL_SPAN_HORIZ;
diff --git a/contrib/mdocml/tbl_html.c b/contrib/mdocml/tbl_html.c
index 7594dfc961..68d3f9c534 100644
--- a/contrib/mdocml/tbl_html.c
+++ b/contrib/mdocml/tbl_html.c
@@ -1,4 +1,4 @@
-/*	$Id: tbl_html.c,v 1.5 2011/01/06 12:31:39 kristaps Exp $ */
+/*	$Id: tbl_html.c,v 1.7 2011/01/13 14:30:13 kristaps Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  *
@@ -27,6 +27,7 @@
 #include "out.h"
 #include "html.h"
 
+static	void	 html_tblopen(struct html *, const struct tbl_span *);
 static	size_t	 html_tbl_len(size_t, void *);
 static	size_t	 html_tbl_strlen(const char *, void *);
 
@@ -46,81 +47,105 @@ html_tbl_strlen(const char *p, void *arg)
 	return(strlen(p));
 }
 
+static void
+html_tblopen(struct html *h, const struct tbl_span *sp)
+{
+	const struct tbl_head *hp;
+	struct htmlpair	 tag;
+	struct roffsu	 su;
+	struct roffcol	*col;
+
+	if (TBL_SPAN_FIRST & sp->flags) {
+		h->tbl.len = html_tbl_len;
+		h->tbl.slen = html_tbl_strlen;
+		tblcalc(&h->tbl, sp);
+	}
+
+	assert(NULL == h->tblt);
+	PAIR_CLASS_INIT(&tag, "tbl");
+	h->tblt = print_otag(h, TAG_TABLE, 1, &tag);
+
+	for (hp = sp->head; hp; hp = hp->next) {
+		bufinit(h);
+		col = &h->tbl.cols[hp->ident];
+		SCALE_HS_INIT(&su, col->width);
+		bufcat_su(h, "width", &su);
+		PAIR_STYLE_INIT(&tag, h);
+		print_otag(h, TAG_COL, 1, &tag);
+	}
+
+	print_otag(h, TAG_TBODY, 0, NULL);
+}
+
+void
+print_tblclose(struct html *h)
+{
+
+	assert(h->tblt);
+	print_tagq(h, h->tblt);
+	h->tblt = NULL;
+}
+
 void
 print_tbl(struct html *h, const struct tbl_span *sp)
 {
 	const struct tbl_head *hp;
 	const struct tbl_dat *dp;
-	struct tag	*tt;
 	struct htmlpair	 tag;
-	struct roffsu	 su;
-	struct roffcol	*col;
+	struct tag	*tt;
 
 	/* Inhibit printing of spaces: we do padding ourselves. */
 
+	if (NULL == h->tblt)
+		html_tblopen(h, sp);
+
+	assert(h->tblt);
+
 	h->flags |= HTML_NONOSPACE;
 	h->flags |= HTML_NOSPACE;
 
-	/* 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);
-	}
+	tt = print_otag(h, TAG_TR, 0, NULL);
 
 	switch (sp->pos) {
 	case (TBL_SPAN_HORIZ):
 		/* FALLTHROUGH */
 	case (TBL_SPAN_DHORIZ):
+		PAIR_INIT(&tag, ATTR_COLSPAN, "0");
+		print_otag(h, TAG_TD, 1, &tag);
 		break;
 	default:
-		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) {
+			print_stagq(h, tt);
+			print_otag(h, TAG_TD, 0, NULL);
+
 			switch (hp->pos) {
 			case (TBL_HEAD_VERT):
 				/* FALLTHROUGH */
 			case (TBL_HEAD_DVERT):
 				continue;
 			case (TBL_HEAD_DATA):
+				if (NULL == dp)
+					break;
+				if (TBL_CELL_DOWN != dp->layout->pos)
+					if (dp->string)
+						print_text(h, dp->string);
+				dp = dp->next;
 				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;
+	print_tagq(h, tt);
 
-	/* Close out column specifiers on the last span. */
+	h->flags &= ~HTML_NONOSPACE;
 
 	if (TBL_SPAN_LAST & sp->flags) {
 		assert(h->tbl.cols);
 		free(h->tbl.cols);
 		h->tbl.cols = NULL;
+		print_tblclose(h);
 	}
+
 }
diff --git a/contrib/mdocml/tbl_layout.c b/contrib/mdocml/tbl_layout.c
index fc03086bec..8245003b1d 100644
--- a/contrib/mdocml/tbl_layout.c
+++ b/contrib/mdocml/tbl_layout.c
@@ -1,4 +1,4 @@
-/*	$Id: tbl_layout.c,v 1.12 2011/01/07 14:59:52 kristaps Exp $ */
+/*	$Id: tbl_layout.c,v 1.17 2011/03/20 16:02:05 kristaps Exp $ */
 /*
  * Copyright (c) 2009, 2010 Kristaps Dzonsons 
  *
@@ -100,7 +100,8 @@ mod:
 			(*pos)++;
 			goto mod;
 		}
-		TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos);
+		mandoc_msg(MANDOCERR_TBLLAYOUT, 
+				tbl->parse, ln, *pos, NULL);
 		return(0);
 	}
 
@@ -117,12 +118,13 @@ mod:
 		/* No greater than 4 digits. */
 
 		if (4 == i) {
-			TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos);
+			mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+					ln, *pos, NULL);
 			return(0);
 		}
 
 		*pos += i;
-		cp->spacing = atoi(buf);
+		cp->spacing = (size_t)atoi(buf);
 
 		goto mod;
 		/* NOTREACHED */
@@ -130,7 +132,7 @@ mod:
 
 	/* TODO: GNU has many more extensions. */
 
-	switch (tolower(p[(*pos)++])) {
+	switch (tolower((unsigned char)p[(*pos)++])) {
 	case ('z'):
 		cp->flags |= TBL_CELL_WIGN;
 		goto mod;
@@ -156,11 +158,12 @@ mod:
 		(*pos)--;
 		break;
 	default:
-		TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos - 1);
+		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+				ln, *pos - 1, NULL);
 		return(0);
 	}
 
-	switch (tolower(p[(*pos)++])) {
+	switch (tolower((unsigned char)p[(*pos)++])) {
 	case ('b'):
 		cp->flags |= TBL_CELL_BOLD;
 		goto mod;
@@ -171,7 +174,8 @@ mod:
 		break;
 	}
 
-	TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos - 1);
+	mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+			ln, *pos - 1, NULL);
 	return(0);
 }
 
@@ -185,11 +189,12 @@ cell(struct tbl_node *tbl, struct tbl_row *rp,
 	/* Parse the column position (`r', `R', `|', ...). */
 
 	for (i = 0; i < KEYS_MAX; i++)
-		if (tolower(p[*pos]) == keys[i].name)
+		if (tolower((unsigned char)p[*pos]) == keys[i].name)
 			break;
 
 	if (KEYS_MAX == i) {
-		TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos);
+		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 
+				ln, *pos, NULL);
 		return(0);
 	}
 
@@ -197,11 +202,38 @@ cell(struct tbl_node *tbl, struct tbl_row *rp,
 
 	/*
 	 * If a span cell is found first, raise a warning and abort the
-	 * parse.  FIXME: recover from this somehow?
+	 * parse.  If a span cell is found and the last layout element
+	 * isn't a "normal" layout, bail.
+	 *
+	 * FIXME: recover from this somehow?
+	 */
+
+	if (TBL_CELL_SPAN == c) {
+		if (NULL == rp->first) {
+			mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+					ln, *pos, NULL);
+			return(0);
+		} else if (rp->last)
+			switch (rp->last->pos) {
+			case (TBL_CELL_VERT):
+			case (TBL_CELL_DVERT):
+			case (TBL_CELL_HORIZ):
+			case (TBL_CELL_DHORIZ):
+				mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+						ln, *pos, NULL);
+				return(0);
+			default:
+				break;
+			}
+	}
+
+	/*
+	 * If a vertical spanner is found, we may not be in the first
+	 * row.
 	 */
 
-	if (NULL == rp->first && TBL_CELL_SPAN == c) {
-		TBL_MSG(tbl, MANDOCERR_TBLLAYOUT, ln, *pos);
+	if (TBL_CELL_DOWN == c && rp == tbl->first_row) {
+		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL);
 		return(0);
 	}
 
@@ -219,7 +251,7 @@ cell(struct tbl_node *tbl, struct tbl_row *rp,
 	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);
+		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL);
 		return(0);
 	}
 
@@ -260,7 +292,8 @@ cell:
 	if ('.' == p[*pos]) {
 		tbl->part = TBL_PART_DATA;
 		if (NULL == tbl->first_row) 
-			TBL_MSG(tbl, MANDOCERR_TBLNOLAYOUT, ln, *pos);
+			mandoc_msg(MANDOCERR_TBLNOLAYOUT, tbl->parse, 
+					ln, *pos, NULL);
 		(*pos)++;
 		return;
 	}
diff --git a/contrib/mdocml/tbl_opts.c b/contrib/mdocml/tbl_opts.c
index b93886764a..dbdcaa81b7 100644
--- a/contrib/mdocml/tbl_opts.c
+++ b/contrib/mdocml/tbl_opts.c
@@ -1,4 +1,4 @@
-/*	$Id: tbl_opts.c,v 1.7 2011/01/07 13:20:58 kristaps Exp $ */
+/*	$Id: tbl_opts.c,v 1.10 2011/03/20 16:02:05 kristaps Exp $ */
 /*
  * Copyright (c) 2009, 2010 Kristaps Dzonsons 
  *
@@ -20,6 +20,7 @@
 #include 
 
 #include "mandoc.h"
+#include "libmandoc.h"
 #include "libroff.h"
 
 enum	tbl_ident {
@@ -88,7 +89,8 @@ arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
 	/* Arguments always begin with a parenthesis. */
 
 	if ('(' != p[*pos]) {
-		TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos);
+		mandoc_msg(MANDOCERR_TBL, tbl->parse, 
+				ln, *pos, NULL);
 		return(0);
 	}
 
@@ -103,12 +105,14 @@ arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
 	switch (key) {
 	case (KEY_DELIM):
 		if ('\0' == p[(*pos)++]) {
-			TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1);
+			mandoc_msg(MANDOCERR_TBL, tbl->parse,
+					ln, *pos - 1, NULL);
 			return(0);
 		} 
 
 		if ('\0' == p[(*pos)++]) {
-			TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1);
+			mandoc_msg(MANDOCERR_TBL, tbl->parse,
+					ln, *pos - 1, NULL);
 			return(0);
 		} 
 		break;
@@ -116,7 +120,8 @@ arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
 		if ('\0' != (tbl->opts.tab = p[(*pos)++]))
 			break;
 
-		TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1);
+		mandoc_msg(MANDOCERR_TBL, tbl->parse,
+				ln, *pos - 1, NULL);
 		return(0);
 	case (KEY_LINESIZE):
 		for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) {
@@ -131,13 +136,14 @@ arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
 			break;
 		}
 
-		(*tbl->msg)(MANDOCERR_TBL, tbl->data, ln, *pos, NULL);
+		mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
 		return(0);
 	case (KEY_DPOINT):
 		if ('\0' != (tbl->opts.decimal = p[(*pos)++]))
 			break;
 
-		TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1);
+		mandoc_msg(MANDOCERR_TBL, tbl->parse, 
+				ln, *pos - 1, NULL);
 		return(0);
 	default:
 		abort();
@@ -149,7 +155,7 @@ arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
 	if (')' == p[(*pos)++])
 		return(1);
 
-	TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1);
+	mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos - 1, NULL);
 	return(0);
 }
 
@@ -188,7 +194,7 @@ again:	/*
 	/* Copy up to first non-alpha character. */
 
 	for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) {
-		buf[i] = tolower(p[*pos]);
+		buf[i] = (char)tolower((unsigned char)p[*pos]);
 		if ( ! isalpha((unsigned char)buf[i]))
 			break;
 	}
@@ -196,7 +202,7 @@ again:	/*
 	/* Exit if buffer is empty (or overrun). */
 
 	if (KEY_MAXNAME == i || 0 == i) {
-		TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos);
+		mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
 		return;
 	}
 
@@ -235,7 +241,7 @@ again:	/*
 	 */
 
 	if (KEY_MAXKEYS == i)
-		TBL_MSG(tbl, MANDOCERR_TBLOPT, ln, sv);
+		mandoc_msg(MANDOCERR_TBLOPT, tbl->parse, ln, sv, NULL);
 
 	goto again;
 	/* NOTREACHED */
diff --git a/contrib/mdocml/tbl_term.c b/contrib/mdocml/tbl_term.c
index a30e723504..1e567c690e 100644
--- a/contrib/mdocml/tbl_term.c
+++ b/contrib/mdocml/tbl_term.c
@@ -1,6 +1,7 @@
-/*	$Id: tbl_term.c,v 1.13 2011/01/07 14:59:52 kristaps Exp $ */
+/*	$Id: tbl_term.c,v 1.19 2011/01/25 12:07:30 schwarze Exp $ */
 /*
- * Copyright (c) 2009 Kristaps Dzonsons 
+ * Copyright (c) 2009, 2011 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
@@ -27,9 +28,6 @@
 #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);
@@ -67,6 +65,7 @@ term_tbl(struct termp *tp, const struct tbl_span *sp)
 	const struct tbl_head	*hp;
 	const struct tbl_dat	*dp;
 	struct roffcol		*col;
+	int			 spans;
 	size_t		   	 rmargin, maxrmargin;
 
 	rmargin = tp->rmargin;
@@ -118,23 +117,39 @@ term_tbl(struct termp *tp, const struct tbl_span *sp)
 	case (TBL_SPAN_DATA):
 		/* Iterate over template headers. */
 		dp = sp->first;
+		spans = 0;
 		for (hp = sp->head; hp; hp = hp->next) {
+			/* 
+			 * If the current data header is invoked during
+			 * a spanner ("spans" > 0), don't emit anything
+			 * at all.
+			 */
 			switch (hp->pos) {
 			case (TBL_HEAD_VERT):
 				/* FALLTHROUGH */
 			case (TBL_HEAD_DVERT):
-				tbl_vrule(tp, hp);
+				if (spans <= 0)
+					tbl_vrule(tp, hp);
 				continue;
 			case (TBL_HEAD_DATA):
 				break;
 			}
 
+			if (--spans >= 0)
+				continue;
+
 			col = &tp->tbl.cols[hp->ident];
 			tbl_data(tp, sp->tbl, dp, col);
 
-			/* Go to the next data cell. */
-			if (dp)
+			/* 
+			 * Go to the next data cell and assign the
+			 * number of subsequent spans, if applicable.
+			 */
+
+			if (dp) {
+				spans = dp->spans;
 				dp = dp->next;
+			}
 		}
 		break;
 	}
@@ -183,6 +198,8 @@ tbl_hrule(struct termp *tp, const struct tbl_span *sp)
 		width = tp->tbl.cols[hp->ident].width;
 		switch (hp->pos) {
 		case (TBL_HEAD_DATA):
+			if (hp->next)
+				width += 2;
 			tbl_char(tp, c, width);
 			break;
 		case (TBL_HEAD_DVERT):
@@ -247,12 +264,12 @@ 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;
 	}
+	assert(dp->layout);
 
 	switch (dp->pos) {
 	case (TBL_DATA_NONE):
@@ -272,9 +289,7 @@ tbl_data(struct termp *tp, const struct tbl *tbl,
 		break;
 	}
 	
-	pos = dp && dp->layout ? dp->layout->pos : TBL_CELL_LEFT;
-
-	switch (pos) {
+	switch (dp->layout->pos) {
 	case (TBL_CELL_HORIZ):
 		tbl_char(tp, '-', col->width);
 		break;
@@ -293,6 +308,9 @@ tbl_data(struct termp *tp, const struct tbl *tbl,
 	case (TBL_CELL_NUMBER):
 		tbl_number(tp, tbl, dp, col);
 		break;
+	case (TBL_CELL_DOWN):
+		tbl_char(tp, ASCII_NBRSP, col->width);
+		break;
 	default:
 		abort();
 		/* NOTREACHED */
@@ -343,39 +361,36 @@ 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 : "";
+	assert(dp->string);
 
 	ssz = term_len(tp, 1);
 
-	switch (pos) {
+	switch (dp->layout->pos) {
 	case (TBL_CELL_LONG):
 		padl = ssz;
-		padr = col->width - term_strlen(tp, str) - ssz;
+		padr = col->width - term_strlen(tp, dp->string) - ssz;
 		break;
 	case (TBL_CELL_CENTRE):
-		padl = col->width - term_strlen(tp, str);
-		if (padl % 2)
-			padr++;
-		padl /= 2;
-		padr += padl;
+		padr = col->width - term_strlen(tp, dp->string);
+		if (3 > padr)
+			break;
+		padl = (padr - 1) / 2;
+		padr -= padl;
 		break;
 	case (TBL_CELL_RIGHT):
-		padl = col->width - term_strlen(tp, str);
+		padl = col->width - term_strlen(tp, dp->string);
 		break;
 	default:
-		padr = col->width - term_strlen(tp, str);
+		padr = col->width - term_strlen(tp, dp->string);
 		break;
 	}
 
 	tbl_char(tp, ASCII_NBRSP, padl);
-	term_word(tp, str);
-	tbl_char(tp, ASCII_NBRSP, padr);
+	term_word(tp, dp->string);
+	tbl_char(tp, ASCII_NBRSP, padr + 2);
 }
 
 static void
@@ -385,7 +400,6 @@ tbl_number(struct termp *tp, const struct tbl *tbl,
 {
 	char		*cp;
 	char		 buf[2];
-	const char	*str;
 	size_t		 sz, psz, ssz, d, padl;
 	int		 i;
 
@@ -394,19 +408,19 @@ tbl_number(struct termp *tp, const struct tbl *tbl,
 	 * and the maximum decimal; right-pad by the remaining amount.
 	 */
 
-	str = dp && dp->string ? dp->string : "";
+	assert(dp->string);
 
-	sz = term_strlen(tp, str);
+	sz = term_strlen(tp, dp->string);
 
 	buf[0] = tbl->decimal;
 	buf[1] = '\0';
 
 	psz = term_strlen(tp, buf);
 
-	if (NULL != (cp = strchr(str, tbl->decimal))) {
+	if (NULL != (cp = strrchr(dp->string, tbl->decimal))) {
 		buf[1] = '\0';
-		for (ssz = 0, i = 0; cp != &str[i]; i++) {
-			buf[0] = str[i];
+		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
+			buf[0] = dp->string[i];
 			ssz += term_strlen(tp, buf);
 		}
 		d = ssz + psz;
@@ -419,7 +433,7 @@ tbl_number(struct termp *tp, const struct tbl *tbl,
 	padl = col->decimal - d;
 
 	tbl_char(tp, ASCII_NBRSP, padl);
-	term_word(tp, str);
+	term_word(tp, dp->string);
 	tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
 }
 
diff --git a/contrib/mdocml/term.c b/contrib/mdocml/term.c
index ba54c31d2a..b0ddd1ed5f 100644
--- a/contrib/mdocml/term.c
+++ b/contrib/mdocml/term.c
@@ -1,7 +1,7 @@
-/*	$Id: term.c,v 1.176 2011/01/04 13:14:26 kristaps Exp $ */
+/*	$Id: term.c,v 1.183 2011/04/04 21:14:12 kristaps Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons 
- * Copyright (c) 2010 Ingo Schwarze 
+ * 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
@@ -29,7 +29,6 @@
 #include 
 
 #include "mandoc.h"
-#include "chars.h"
 #include "out.h"
 #include "term.h"
 #include "main.h"
@@ -80,12 +79,7 @@ term_alloc(enum termenc enc)
 {
 	struct termp	*p;
 
-	p = calloc(1, sizeof(struct termp));
-	if (NULL == p) {
-		perror(NULL);
-		exit((int)MANDOCLEVEL_SYSERR);
-	}
-
+	p = mandoc_calloc(1, sizeof(struct termp));
 	p->enc = enc;
 	return(p);
 }
@@ -352,6 +346,17 @@ term_vspace(struct termp *p)
 }
 
 
+static void
+numbered(struct termp *p, const char *word, size_t len)
+{
+	const char	*rhs;
+
+	rhs = chars_num2char(word, len);
+	if (rhs) 
+		encode(p, rhs, 1);
+}
+
+
 static void
 spec(struct termp *p, enum roffdeco d, const char *word, size_t len)
 {
@@ -451,36 +456,10 @@ term_fontpop(struct termp *p)
 void
 term_word(struct termp *p, const char *word)
 {
-	const char	*sv, *seq;
+	const char	*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)
@@ -503,7 +482,7 @@ term_word(struct termp *p, const char *word)
 		if ((ssz = strcspn(word, "\\")) > 0)
 			encode(p, word, ssz);
 
-		word += ssz;
+		word += (int)ssz;
 		if ('\\' != *word)
 			continue;
 
@@ -511,6 +490,9 @@ term_word(struct termp *p, const char *word)
 		word += a2roffdeco(&deco, &seq, &ssz);
 
 		switch (deco) {
+		case (DECO_NUMBERED):
+			numbered(p, seq, ssz);
+			break;
 		case (DECO_RESERVED):
 			res(p, seq, ssz);
 			break;
@@ -538,21 +520,6 @@ term_word(struct termp *p, const char *word)
 		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;
-		}
 }
 
 
@@ -565,11 +532,7 @@ adjbuf(struct termp *p, size_t sz)
 	while (sz >= p->maxcols)
 		p->maxcols <<= 2;
 
-	p->buf = realloc(p->buf, p->maxcols);
-	if (NULL == p->buf) {
-		perror(NULL);
-		exit((int)MANDOCLEVEL_SYSERR);
-	}
+	p->buf = mandoc_realloc(p->buf, p->maxcols);
 }
 
 
diff --git a/contrib/mdocml/term_ascii.c b/contrib/mdocml/term_ascii.c
index b926a5b378..374a2a02e0 100644
--- a/contrib/mdocml/term_ascii.c
+++ b/contrib/mdocml/term_ascii.c
@@ -1,4 +1,4 @@
-/*	$Id: term_ascii.c,v 1.11 2011/01/02 12:21:07 kristaps Exp $ */
+/*	$Id: term_ascii.c,v 1.12 2011/01/25 17:32:04 kristaps Exp $ */
 /*
  * Copyright (c) 2010 Kristaps Dzonsons 
  *
@@ -48,8 +48,7 @@ ascii_alloc(char *outopts)
 	const char	*toks[2];
 	char		*v;
 
-	if (NULL == (p = term_alloc(TERMENC_ASCII)))
-		return(NULL);
+	p = term_alloc(TERMENC_ASCII);
 
 	p->tabwidth = 5;
 	p->defrmargin = 78;
diff --git a/contrib/mdocml/term_ps.c b/contrib/mdocml/term_ps.c
index fa2f68fe26..233118b819 100644
--- a/contrib/mdocml/term_ps.c
+++ b/contrib/mdocml/term_ps.c
@@ -1,4 +1,4 @@
-/*	$Id: term_ps.c,v 1.45 2010/09/27 23:03:44 schwarze Exp $ */
+/*	$Id: term_ps.c,v 1.48 2011/03/17 08:49:34 kristaps Exp $ */
 /*
  * Copyright (c) 2010 Kristaps Dzonsons 
  *
@@ -366,14 +366,9 @@ ps_growbuf(struct termp *p, size_t sz)
 
 	p->engine.ps.psmargsz += sz;
 
-	p->engine.ps.psmarg = realloc
+	p->engine.ps.psmarg = mandoc_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 *,
@@ -428,8 +423,7 @@ pspdf_alloc(char *outopts)
 	const char	*pp;
 	char		*v;
 
-	if (NULL == (p = term_alloc(TERMENC_ASCII)))
-		return(NULL);
+	p = term_alloc(TERMENC_ASCII);
 
 	p->advance = ps_advance;
 	p->begin = ps_begin;
@@ -789,7 +783,6 @@ ps_begin(struct termp *p)
 
 	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");
@@ -809,7 +802,6 @@ ps_begin(struct termp *p)
 		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");
 
diff --git a/contrib/mdocml/tree.c b/contrib/mdocml/tree.c
index 70bd73d9ef..fecad80ed9 100644
--- a/contrib/mdocml/tree.c
+++ b/contrib/mdocml/tree.c
@@ -1,4 +1,4 @@
-/*	$Id: tree.c,v 1.31 2011/01/03 13:59:21 kristaps Exp $ */
+/*	$Id: tree.c,v 1.37 2011/03/23 12:33:01 kristaps Exp $ */
 /*
  * Copyright (c) 2008, 2009 Kristaps Dzonsons 
  *
@@ -92,6 +92,9 @@ print_mdoc(const struct mdoc_node *n, int indent)
 	case (MDOC_TBL):
 		t = "tbl";
 		break;
+	case (MDOC_EQN):
+		t = "eqn";
+		break;
 	default:
 		abort();
 		/* NOTREACHED */
@@ -128,6 +131,9 @@ print_mdoc(const struct mdoc_node *n, int indent)
 		break;
 	case (MDOC_TBL):
 		break;
+	case (MDOC_EQN):
+		p = n->eqn->data;
+		break;
 	case (MDOC_ROOT):
 		p = "root";
 		break;
@@ -195,9 +201,15 @@ print_man(const struct man_node *n, int indent)
 	case (MAN_BODY):
 		t = "block-body";
 		break;
+	case (MAN_TAIL):
+		t = "block-tail";
+		break;
 	case (MAN_TBL):
 		t = "tbl";
 		break;
+	case (MAN_EQN):
+		t = "eqn";
+		break;
 	default:
 		abort();
 		/* NOTREACHED */
@@ -215,6 +227,8 @@ print_man(const struct man_node *n, int indent)
 		/* FALLTHROUGH */
 	case (MAN_HEAD):
 		/* FALLTHROUGH */
+	case (MAN_TAIL):
+		/* FALLTHROUGH */
 	case (MAN_BODY):
 		p = man_macronames[n->tok];
 		break;
@@ -223,6 +237,9 @@ print_man(const struct man_node *n, int indent)
 		break;
 	case (MAN_TBL):
 		break;
+	case (MAN_EQN):
+		p = n->eqn->data;
+		break;
 	default:
 		abort();
 		/* NOTREACHED */
@@ -254,8 +271,6 @@ print_span(const struct tbl_span *sp, int indent)
 	for (i = 0; i < indent; i++)
 		putchar('\t');
 
-	printf("tbl: ");
-
 	switch (sp->pos) {
 	case (TBL_SPAN_HORIZ):
 		putchar('-');
@@ -282,8 +297,14 @@ print_span(const struct tbl_span *sp, int indent)
 		default:
 			break;
 		}
-		printf("[%s%s]", dp->string, dp->layout ?  "" : "*");
-		if (dp->next)
-			putchar(' ');
+		printf("[\"%s\"", dp->string ? dp->string : "");
+		if (dp->spans)
+			printf("(%d)", dp->spans);
+		if (NULL == dp->layout)
+			putchar('*');
+		putchar(']');
+		putchar(' ');
 	}
+
+	printf("(tbl) %d:1", sp->line);
 }
diff --git a/contrib/mdocml/vol.c b/contrib/mdocml/vol.c
index 144d363ff6..3ea7441a42 100644
--- a/contrib/mdocml/vol.c
+++ b/contrib/mdocml/vol.c
@@ -1,4 +1,4 @@
-/*	$Id: vol.c,v 1.8 2010/06/19 20:46:28 kristaps Exp $ */
+/*	$Id: vol.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  *
@@ -22,6 +22,7 @@
 #include 
 #include 
 
+#include "mdoc.h"
 #include "mandoc.h"
 #include "libmdoc.h"