tech@mandoc.bsd.lv
 help / color / mirror / Atom feed
* integrate tbl into mandoc
@ 2010-10-13  0:46 Ingo Schwarze
  2010-10-14 21:18 ` Ingo Schwarze
  0 siblings, 1 reply; 7+ messages in thread
From: Ingo Schwarze @ 2010-10-13  0:46 UTC (permalink / raw)
  To: tech

Hi,

i should like to remove groff out of the OpenBSD tree as soon as
possible.  I was slacking: we should not wait until the 4.9 release
comes close.
The last holdup is that we still need groff to format about one or
two dozens of manuals using tbl(1) syntax.  Thus, i am trying to get
mandoc to deal with tbl(1) as quickly as possible.

During EuroBSDCon last weekend, i discussed various approaches to get
this done with Joerg Sonnenberger and Marc Espie.

With Joerg, i came to the conclusion that it would be most rigorous
to do tbl parsing before roff parsing, because when using groff,
tbl output is piped into nroff, so that's the proper order.

On the other hand, i now looked at Kristaps' actual tbl code and it
turns out that a clean solution portable to various frontends will
rather be to put the tbl parser alongside the outher parsers, adding
its AST output to the other ASTs, and to put the tbl output frontend
alongside the other frontends.  Unfortunately, that is hard to do
when parsing tbl before roff: It would mean to have the tbl ASTs
before having the man AST ready to hook it into.

Thus, for now, here is a proposal to implement the .TS/.TE macros
in libman and call the tbl parser and frontend from the man code.
The same can of course be done for mdoc.
The files are completely unchanged with respect to Kristaps'
latest published version, tbl-0.1.5.
Here, i'm just showing the glue.

Note that this patch applies to OpenBSD, not to bsd.lv.
As i said, i hope to get it in quickly, even if we do not merge
the same to mdocml.bsd.lv, and even if we have to do more cleanup
later.

One thing to do before commit is probably to replace the printfs
in the output frontend by calls into term.c, such that indentation
is respected and escape sequences get resolved.  There may be more
i need to do.
The following already look semi-useful with this:
  /usr/src/lib/libcurses/curs_attr.3tbl
  /usr/src/lib/libcurses/curs_getch.3tbl
  /usr/src/lib/libcurses/curses.3tbl
  /usr/src/usr.bin/infocmp/infocmp.1tbl
The following still crash:
  /usr/src/lib/libcurses/curs_addch.3tbl
  /usr/src/lib/libcurses/curs_inch.3tbl
  /usr/src/lib/libcurses/curs_mouse.3tbl
  /usr/src/lib/libform/form.3tbl
  /usr/src/lib/libmenu/menu.3tbl
  /usr/src/usr.bin/tic/captoinfo.1tbl
  /usr/src/gnu/usr.sbin/mkhybrid/src/mkhybrid.8tbl
The following have a tbl extension, but no .TS:
  /usr/src/lib/libcurses/term.5tbl
The following are mdoc(7) with tbl:
  /usr/src/share/man/man4/wi.4tbl
  /usr/src/share/man/man4/man4.hppa/cpu.4tbl
  /usr/src/games/phantasia/phantasia.6tbl

Thoughts?
  Ingo


Index: Makefile
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/Makefile,v
retrieving revision 1.45
diff -u -p -r1.45 Makefile
--- Makefile	27 Sep 2010 21:25:28 -0000	1.45
+++ Makefile	12 Oct 2010 23:37:36 -0000
@@ -20,6 +20,8 @@ SRCS+=	main.c mdoc_term.c chars.c term.c
 SRCS+=	html.c mdoc_html.c man_html.c out.c
 SRCS+=	term_ps.c term_ascii.c
 
+SRCS+=	tbl_data.c tbl_layout.c tbl_option.c tbl.c tbl_term.c tbl_tree.c
+
 PROG=	mandoc
 
 .include <bsd.prog.mk>
Index: man.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man.c,v
retrieving revision 1.40
diff -u -p -r1.40 man.c
--- man.c	20 Aug 2010 00:53:35 -0000	1.40
+++ man.c	12 Oct 2010 23:37:36 -0000
@@ -25,6 +25,7 @@
 #include "mandoc.h"
 #include "libman.h"
 #include "libmandoc.h"
+#include "tbl.h"
 
 const	char *const __man_macronames[MAN_MAX] = {		 
 	"br",		"TH",		"SH",		"SS",
@@ -36,7 +37,7 @@ const	char *const __man_macronames[MAN_M
 	"nf",		"fi",		"r",		"RE",
 	"RS",		"DT",		"UC",		"PD",
 	"Sp",		"Vb",		"Ve",		"AT",
-	"in"
+	"in",		"TS",		"TE"
 	};
 
 const	char * const *man_macronames = __man_macronames;
@@ -121,10 +122,19 @@ man_endparse(struct man *m)
 int
 man_parseln(struct man *m, int ln, char *buf, int offs)
 {
+	struct man_node *n;
 
 	if (MAN_HALT & m->flags)
 		return(0);
 
+	n = m->last;
+
+	if (n && MAN_TS == n->tok && MAN_BODY == n->type &&
+	    strncmp(buf+offs, ".TE", 3)) {
+		n = n->parent;
+		return(tbl_read(n->data.TS, "<man>", ln, buf, offs) ? 1 : 0);
+	}
+
 	return(('.' == buf[offs] || '\'' == buf[offs]) ? 
 			man_pmacro(m, ln, buf, offs) : 
 			man_ptext(m, ln, buf, offs));
@@ -322,6 +332,8 @@ man_node_free(struct man_node *p)
 
 	if (p->string)
 		free(p->string);
+	if (p->data.TS)
+		tbl_free(p->data.TS);
 	free(p);
 }
 
Index: man.h
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man.h,v
retrieving revision 1.26
diff -u -p -r1.26 man.h
--- man.h	20 Aug 2010 00:53:35 -0000	1.26
+++ man.h	12 Oct 2010 23:37:36 -0000
@@ -57,6 +57,8 @@ enum	mant {
 	MAN_Ve,
 	MAN_AT,
 	MAN_in,
+	MAN_TS,
+	MAN_TE,
 	MAN_MAX
 };
 
@@ -95,6 +97,9 @@ struct	man_node {
 	char		*string;
 	struct man_node	*head;
 	struct man_node	*body;
+	union {
+		struct tbl *TS;
+	} data;
 };
 
 extern	const char *const *man_macronames;
Index: man_action.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_action.c,v
retrieving revision 1.24
diff -u -p -r1.24 man_action.c
--- man_action.c	25 Jul 2010 18:05:54 -0000	1.24
+++ man_action.c	12 Oct 2010 23:37:36 -0000
@@ -22,6 +22,7 @@
 #include "mandoc.h"
 #include "libman.h"
 #include "libmandoc.h"
+#include "tbl.h"
 
 struct	actions {
 	int	(*post)(struct man *);
@@ -32,6 +33,7 @@ static	int	  post_fi(struct man *);
 static	int	  post_nf(struct man *);
 static	int	  post_AT(struct man *);
 static	int	  post_UC(struct man *);
+static	int	  post_TS(struct man *);
 
 const	struct actions man_actions[MAN_MAX] = {
 	{ NULL }, /* br */
@@ -71,6 +73,8 @@ const	struct actions man_actions[MAN_MAX
 	{ post_fi }, /* Ve */
 	{ post_AT }, /* AT */
 	{ NULL }, /* in */
+	{ post_TS }, /* TS */
+	{ NULL }, /* TE */
 };
 
 
@@ -273,6 +277,29 @@ post_UC(struct man *m)
 		free(m->meta.source);
 
 	m->meta.source = mandoc_strdup(p);
+
+	return(1);
+}
+
+
+static int
+post_TS(struct man *m)
+{
+	struct man_node	*n;
+
+	n = m->last;
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		n->parent->data.TS = tbl_alloc();
+		break;
+	case (MAN_BODY):
+		if ( ! tbl_close(n->parent->data.TS, "<man>", n->line))
+			return(0);
+		break;
+	default:
+		break;
+	}
 
 	return(1);
 }
Index: man_html.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_html.c,v
retrieving revision 1.18
diff -u -p -r1.18 man_html.c
--- man_html.c	25 Jul 2010 18:05:54 -0000	1.18
+++ man_html.c	12 Oct 2010 23:37:37 -0000
@@ -113,6 +113,8 @@ static	const struct htmlman mans[MAN_MAX
 	{ man_literal_pre, NULL }, /* Ve */
 	{ man_ign_pre, NULL }, /* AT */
 	{ man_in_pre, NULL }, /* in */
+	{ NULL, NULL }, /* TS */
+	{ NULL, NULL }, /* TE */
 };
 
 
Index: man_macro.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_macro.c,v
retrieving revision 1.20
diff -u -p -r1.20 man_macro.c
--- man_macro.c	25 Jul 2010 18:05:54 -0000	1.20
+++ man_macro.c	12 Oct 2010 23:37:37 -0000
@@ -80,6 +80,8 @@ const	struct man_macro __man_macros[MAN_
 	{ in_line_eoln, 0 }, /* Ve */
 	{ in_line_eoln, 0 }, /* AT */
 	{ in_line_eoln, 0 }, /* in */
+	{ blk_exp, MAN_EXPLICIT }, /* TS */
+	{ blk_close, 0 }, /* TE */
 };
 
 const	struct man_macro * const man_macros = __man_macros;
@@ -264,6 +266,9 @@ blk_close(MACRO_PROT_ARGS)
 	switch (tok) {
 	case (MAN_RE):
 		ntok = MAN_RS;
+		break;
+	case (MAN_TE):
+		ntok = MAN_TS;
 		break;
 	default:
 		abort();
Index: man_term.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_term.c,v
retrieving revision 1.46
diff -u -p -r1.46 man_term.c
--- man_term.c	21 Sep 2010 22:33:41 -0000	1.46
+++ man_term.c	12 Oct 2010 23:37:37 -0000
@@ -28,6 +28,7 @@
 #include "term.h"
 #include "chars.h"
 #include "main.h"
+#include "tbl.h"
 
 #define	INDENT		  7
 #define	HALFINDENT	  3
@@ -92,6 +93,7 @@ static	int		  pre_ign(DECL_ARGS);
 static	int		  pre_in(DECL_ARGS);
 static	int		  pre_literal(DECL_ARGS);
 static	int		  pre_sp(DECL_ARGS);
+static	int		  pre_TS(DECL_ARGS);
 
 static	void		  post_IP(DECL_ARGS);
 static	void		  post_HP(DECL_ARGS);
@@ -138,6 +140,8 @@ static	const struct termact termacts[MAN
  	{ pre_literal, NULL, 0 }, /* Ve */
 	{ pre_ign, NULL, 0 }, /* AT */
 	{ pre_in, NULL, MAN_NOTEXT }, /* in */
+	{ pre_TS, NULL, 0 }, /* TS */
+	{ NULL, NULL, 0 }, /* TE */
 };
 
 
@@ -823,6 +827,18 @@ post_RS(DECL_ARGS)
 		p->offset = term_len(p, INDENT);
 		break;
 	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_TS(DECL_ARGS)
+{
+
+	if (MAN_BLOCK == n->type)
+		tbl_write(n->data.TS);
+
+	return(0);
 }
 
 
Index: man_validate.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_validate.c,v
retrieving revision 1.29
diff -u -p -r1.29 man_validate.c
--- man_validate.c	20 Aug 2010 00:53:35 -0000	1.29
+++ man_validate.c	12 Oct 2010 23:37:37 -0000
@@ -95,6 +95,8 @@ static	const struct man_valid man_valids
 	{ pres_bline, posts_eq0 }, /* Ve */
 	{ NULL, NULL }, /* AT */
 	{ NULL, NULL }, /* in */
+	{ NULL, NULL }, /* TS */
+	{ NULL, NULL }, /* RE */
 };
 
 
Index: tbl.c
===================================================================
RCS file: tbl.c
diff -N tbl.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl.c	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,544 @@
+/*	$Id: tbl.c,v 1.14 2009/09/12 16:05:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tbl.h"
+#include "tbl_extern.h"
+
+
+const	char *const	 errnames[ERR_MAX] = {
+	"bad syntax",	 /* ERR_SYNTAX */
+	"bad option"	 /* ERR_OPTION */
+};
+
+static	char		 buf[1024]; /* XXX */
+
+static	enum tbl_tok	 tbl_next_char(char);
+static	void		 tbl_init(struct tbl *);
+static	void		 tbl_clear(struct tbl *);
+static	struct tbl_head *tbl_head_alloc(struct tbl *);
+static	void		 tbl_span_free(struct tbl_span *);
+static	void		 tbl_data_free(struct tbl_data *);
+static	void		 tbl_row_free(struct tbl_row *);
+
+static	void		 headadj(const struct tbl_cell *, 
+				struct tbl_head *);
+
+static void
+tbl_init(struct tbl *tbl)
+{
+
+	bzero(tbl, sizeof(struct tbl));
+
+	tbl->part = TBL_PART_OPTS;
+	tbl->tab = '\t';
+	tbl->linesize = 12;
+	tbl->decimal = '.';
+
+	TAILQ_INIT(&tbl->span);
+	TAILQ_INIT(&tbl->row);
+	TAILQ_INIT(&tbl->head);
+}
+
+
+int
+tbl_read(struct tbl *tbl, const char *f, int ln, const char *p, int len)
+{
+	
+	if (len && TBL_PART_OPTS == tbl->part)
+		if (';' != p[len - 1])
+			tbl->part = TBL_PART_LAYOUT;
+
+	switch (tbl->part) {
+	case (TBL_PART_OPTS):
+		return(tbl_option(tbl, f, ln, p));
+	case (TBL_PART_CLAYOUT):
+		/* FALLTHROUGH */
+	case (TBL_PART_LAYOUT):
+		return(tbl_layout(tbl, f, ln, p));
+	case (TBL_PART_DATA):
+		return(tbl_data(tbl, f, ln, p));
+	case (TBL_PART_ERROR):
+		break;
+	}
+
+	return(0);
+}
+
+
+int
+tbl_close(struct tbl *tbl, const char *f, int ln)
+{
+
+	if (TBL_PART_DATA != tbl->part) 
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+	if ( ! tbl_data_close(tbl, f, ln))
+		return(0);
+#if 1
+	return(tbl_calc_term(tbl));
+#else
+	return(tbl_calc_tree(tbl));
+#endif
+}
+
+
+int
+tbl_write(const struct tbl *tbl)
+{
+
+#if 1
+	return(tbl_write_term(tbl));
+#else
+	return(tbl_write_tree(tbl));
+#endif
+}
+
+
+static enum tbl_tok
+tbl_next_char(char c)
+{
+
+	/*
+	 * These are delimiting tokens.  They separate out words in the
+	 * token stream.
+	 */
+
+	switch (c) {
+	case ('('):
+		return(TBL_TOK_OPENPAREN);
+	case (')'):
+		return(TBL_TOK_CLOSEPAREN);
+	case (' '):
+		return(TBL_TOK_SPACE);
+	case ('\t'):
+		return(TBL_TOK_TAB);
+	case (';'):
+		return(TBL_TOK_SEMICOLON);
+	case ('.'):
+		return(TBL_TOK_PERIOD);
+	case (','):
+		return(TBL_TOK_COMMA);
+	case (0):
+		return(TBL_TOK_NIL);
+	default:
+		break;
+	}
+
+	return(TBL_TOK_WORD);
+}
+
+
+const char *
+tbl_last(void)
+{
+
+	return(buf);
+}
+
+
+int
+tbl_last_uint(void)
+{
+	char		*ep;
+	long		 lval;
+
+	/* From OpenBSD's strtol(3).  Gross. */
+
+	errno = 0;
+	lval = strtol(buf, &ep, 10);
+	if (buf[0] == 0 || *ep != 0)
+		return(-1);
+	if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN))
+		return(-1);
+	if (lval < 0 || lval > INT_MAX)
+		return(-1);
+
+	return((int)lval);
+}
+
+
+enum tbl_tok
+tbl_next(const char *p, int *pos)
+{
+	int		 i;
+	enum tbl_tok	 c;
+
+	buf[0] = 0;
+
+	if (TBL_TOK_WORD != (c = tbl_next_char(p[*pos]))) {
+		if (TBL_TOK_NIL != c) {
+			buf[0] = p[*pos];
+			buf[1] = 0;
+			(*pos)++;
+		}
+		return(c);
+	}
+
+	/*
+	 * Copy words into a nil-terminated buffer.  For now, we use a
+	 * static buffer.  Eventually this should be made into a dynamic
+	 * one living in struct tbl.
+	 */
+
+	for (i = 0; i < 1023; i++, (*pos)++)
+		if (TBL_TOK_WORD == tbl_next_char(p[*pos]))
+			buf[i] = p[*pos];
+		else
+			break;
+
+	assert(i < 1023);
+	buf[i] = 0;
+
+	return(TBL_TOK_WORD);
+}
+
+
+int
+tbl_err(struct tbl *tbl)
+{
+
+	(void)fprintf(stderr, "%s\n", strerror(errno));
+	tbl->part = TBL_PART_ERROR;
+	return(0);
+}
+
+
+/* ARGSUSED */
+int
+tbl_warnx(struct tbl *tbl, enum tbl_err tok, 
+		const char *f, int line, int pos)
+{
+
+	(void)fprintf(stderr, "%s:%d:%d: %s\n", 
+			f, line, pos + 1, errnames[tok]);
+
+	/* TODO: -Werror */
+	return(1);
+}
+
+
+int
+tbl_errx(struct tbl *tbl, enum tbl_err tok, 
+		const char *f, int line, int pos)
+{
+
+	(void)fprintf(stderr, "%s:%d:%d: %s\n", 
+			f, line, pos + 1, errnames[tok]);
+
+	tbl->part = TBL_PART_ERROR;
+	return(0);
+}
+
+
+struct tbl *
+tbl_alloc(void)
+{
+	struct tbl	*p;
+
+	if (NULL == (p = malloc(sizeof(struct tbl))))
+		return(NULL);
+
+	tbl_init(p);
+	return(p);
+}
+
+
+void
+tbl_free(struct tbl *p)
+{
+
+	tbl_clear(p);
+	free(p);
+}
+
+
+void
+tbl_reset(struct tbl *tbl)
+{
+
+	tbl_clear(tbl);
+	tbl_init(tbl);
+}
+
+
+struct tbl_span *
+tbl_span_alloc(struct tbl *tbl)
+{
+	struct tbl_span	*p, *pp;
+	struct tbl_row	*row;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_span)))) {
+		(void)tbl_err(tbl);
+		return(NULL);
+	}
+
+	TAILQ_INIT(&p->data);
+	TAILQ_INSERT_TAIL(&tbl->span, p, entries);
+
+	/* LINTED */
+	pp = TAILQ_PREV(p, tbl_spanh, entries);
+
+	if (pp) {
+		row = TAILQ_NEXT(pp->row, entries);
+		if (NULL == row)
+			row = pp->row;
+	} else {
+		row = TAILQ_FIRST(&tbl->row);
+	}
+
+	assert(row);
+	p->row = row;
+	p->tbl = tbl;
+	return(p);
+}
+
+
+struct tbl_row *
+tbl_row_alloc(struct tbl *tbl)
+{
+	struct tbl_row	*p;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_row)))) {
+		(void)tbl_err(tbl);
+		return(NULL);
+	}
+
+	TAILQ_INIT(&p->cell);
+	TAILQ_INSERT_TAIL(&tbl->row, p, entries);
+	p->tbl = tbl;
+	return(p);
+}
+
+
+static void
+headadj(const struct tbl_cell *cell, struct tbl_head *head)
+{
+	if (TBL_CELL_VERT != cell->pos &&
+			TBL_CELL_DVERT != cell->pos) {
+		head->pos = TBL_HEAD_DATA;
+		return;
+	}
+	if (TBL_CELL_VERT == cell->pos)
+		if (TBL_HEAD_DVERT != head->pos)
+			head->pos = TBL_HEAD_VERT;
+	if (TBL_CELL_DVERT == cell->pos)
+		head->pos = TBL_HEAD_DVERT;
+}
+
+
+static struct tbl_head *
+tbl_head_alloc(struct tbl *tbl)
+{
+	struct tbl_head	*p;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_head)))) {
+		(void)tbl_err(tbl);
+		return(NULL);
+	}
+	p->tbl = tbl;
+	return(p);
+}
+
+
+struct tbl_cell *
+tbl_cell_alloc(struct tbl_row *rp, enum tbl_cellt pos)
+{
+	struct tbl_cell	*p, *pp;
+	struct tbl_head	*h, *hp;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_cell)))) {
+		(void)tbl_err(rp->tbl);
+		return(NULL);
+	}
+
+	TAILQ_INSERT_TAIL(&rp->cell, p, entries);
+	p->pos = pos;
+	p->row = rp;
+
+	/*
+	 * This is a little bit complicated.  Here we determine the
+	 * header the corresponds to a cell.  We add headers dynamically
+	 * when need be or re-use them, otherwise.  As an example, given
+	 * the following:
+	 *
+	 * 	1  c || l 
+	 * 	2  | c | l
+	 * 	3  l l
+	 * 	3  || c | l |.
+	 *
+	 * We first add the new headers (as there are none) in (1); then
+	 * in (2) we insert the first spanner (as it doesn't match up
+	 * with the header); then we re-use the prior data headers,
+	 * skipping over the spanners; then we re-use everything and add
+	 * a last spanner.  Note that VERT headers are made into DVERT
+	 * ones.
+	 */
+
+	/* LINTED */
+	pp = TAILQ_PREV(p, tbl_cellh, entries);
+
+	h = pp ? TAILQ_NEXT(pp->head, entries) : 
+		TAILQ_FIRST(&rp->tbl->head);
+
+	if (h) {
+		/* Re-use data header. */
+		if (TBL_HEAD_DATA == h->pos && 
+				(TBL_CELL_VERT != p->pos &&
+				 TBL_CELL_DVERT != p->pos)) {
+			p->head = h;
+			return(p);
+		}
+
+		/* Re-use spanner header. */
+		if (TBL_HEAD_DATA != h->pos && 
+				(TBL_CELL_VERT == p->pos ||
+				 TBL_CELL_DVERT == p->pos)) {
+			headadj(p, h);
+			p->head = h;
+			return(p);
+		}
+
+		/* Right-shift headers with a new spanner. */
+		if (TBL_HEAD_DATA == h->pos && 
+				(TBL_CELL_VERT == p->pos ||
+				 TBL_CELL_DVERT == p->pos)) {
+			if (NULL == (hp = tbl_head_alloc(rp->tbl)))
+				return(NULL);
+			TAILQ_INSERT_BEFORE(h, hp, entries);
+			headadj(p, hp);
+			p->head = hp;
+			return(p);
+		}
+
+		h = TAILQ_NEXT(h, entries);
+		if (h) {
+			headadj(p, h);
+			p->head = h;
+			return(p);
+		}
+
+		/* Fall through to default case... */
+	}
+
+	if (NULL == (hp = tbl_head_alloc(rp->tbl)))
+		return(NULL);
+	TAILQ_INSERT_TAIL(&rp->tbl->head, hp, entries);
+	headadj(p, hp);
+	p->head = hp;
+	return(p);
+}
+
+
+struct tbl_data *
+tbl_data_alloc(struct tbl_span *sp)
+{
+	struct tbl_data	*p;
+	struct tbl_cell	*cp;
+	struct tbl_data	*dp;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_data)))) {
+		(void)tbl_err(sp->row->tbl);
+		return(NULL);
+	}
+
+	cp = NULL;
+	/* LINTED */
+	if (NULL == (dp = TAILQ_LAST(&sp->data, tbl_datah)))
+		cp = TAILQ_FIRST(&sp->row->cell);
+	else if (dp->cell)
+		cp = TAILQ_NEXT(dp->cell, entries);
+
+	TAILQ_INSERT_TAIL(&sp->data, p, entries);
+
+	if (cp && (TBL_CELL_VERT == cp->pos || 
+				TBL_CELL_DVERT == cp->pos))
+		cp = TAILQ_NEXT(cp, entries);
+
+	p->span = sp;
+	p->cell = cp;
+	return(p);
+}
+
+
+static void
+tbl_clear(struct tbl *p)
+{
+	struct tbl_span	*span;
+	struct tbl_head	*head;
+	struct tbl_row	*row;
+
+	/* LINTED */
+	while ((span = TAILQ_FIRST(&p->span))) {
+		TAILQ_REMOVE(&p->span, span, entries);
+		tbl_span_free(span);
+	}
+	/* LINTED */
+	while ((row = TAILQ_FIRST(&p->row))) {
+		TAILQ_REMOVE(&p->row, row, entries);
+		tbl_row_free(row);
+	}
+	/* LINTED */
+	while ((head = TAILQ_FIRST(&p->head))) {
+		TAILQ_REMOVE(&p->head, head, entries);
+		free(head);
+	}
+}
+
+
+static void
+tbl_span_free(struct tbl_span *p)
+{
+	struct tbl_data	*data;
+
+	/* LINTED */
+	while ((data = TAILQ_FIRST(&p->data))) {
+		TAILQ_REMOVE(&p->data, data, entries);
+		tbl_data_free(data);
+	}
+	free(p);
+}
+
+
+static void
+tbl_data_free(struct tbl_data *p)
+{
+
+	if (p->string)
+		free(p->string);
+	free(p);
+}
+
+
+static void
+tbl_row_free(struct tbl_row *p)
+{
+	struct tbl_cell	*cell;
+
+	/* LINTED */
+	while ((cell = TAILQ_FIRST(&p->cell))) {
+		TAILQ_REMOVE(&p->cell, cell, entries);
+		free(cell);
+	}
+	free(p);
+}
Index: tbl.h
===================================================================
RCS file: tbl.h
diff -N tbl.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl.h	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,34 @@
+/*	$Id: tbl.h,v 1.3 2009/09/11 15:01:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is 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 TBL_H
+#define TBL_H
+
+__BEGIN_DECLS
+
+struct tbl;
+
+struct tbl	*tbl_alloc(void);
+void		 tbl_free(struct tbl *);
+void		 tbl_reset(struct tbl *);
+
+int	 	 tbl_read(struct tbl *, const char *, int, const char *, int);
+int		 tbl_close(struct tbl *, const char *, int);
+int		 tbl_write(const struct tbl *);
+
+__END_DECLS
+
+#endif /*TBL_H*/
Index: tbl_data.c
===================================================================
RCS file: tbl_data.c
diff -N tbl_data.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_data.c	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,130 @@
+/*	$Id: data.c,v 1.11 2009/09/12 16:05:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tbl_extern.h"
+
+/* FIXME: warn about losing data contents if cell is HORIZ. */
+
+static	int		data(struct tbl *, struct tbl_span *, 
+				const char *, int, int, 
+				const char *, int, int);
+
+
+int
+data(struct tbl *tbl, struct tbl_span *dp, 
+		const char *f, int ln, int pos, 
+		const char *p, int start, int end)
+{
+	struct tbl_data	*dat;
+
+	if (NULL == (dat = tbl_data_alloc(dp)))
+		return(0);
+
+	if (NULL == dat->cell)
+		if ( ! tbl_warnx(tbl, ERR_SYNTAX, f, ln, pos))
+			return(0);
+
+	assert(end >= start);
+	if (NULL == (dat->string = malloc((size_t)(end - start + 1))))
+		return(tbl_err(tbl));
+
+	(void)memcpy(dat->string, &p[start], (size_t)(end - start));
+	dat->string[end - start] = 0;
+
+	/* XXX: do the strcmps, then malloc(). */
+
+	if ( ! strcmp(dat->string, "_"))
+		dat->flags |= TBL_DATA_HORIZ;
+	else if ( ! strcmp(dat->string, "="))
+		dat->flags |= TBL_DATA_DHORIZ;
+	else if ( ! strcmp(dat->string, "\\_"))
+		dat->flags |= TBL_DATA_NHORIZ;
+	else if ( ! strcmp(dat->string, "\\="))
+		dat->flags |= TBL_DATA_NDHORIZ;
+	else
+		return(1);
+
+	free(dat->string);
+	dat->string = NULL;
+	return(1);
+}
+
+
+int
+tbl_data(struct tbl *tbl, const char *f, int ln, const char *p)
+{
+	struct tbl_span	*dp;
+	int		 i, j;
+
+	if (0 == p[0])
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+
+	if ('.' == p[0] && ! isdigit((u_char)p[1])) {
+		/*
+		 * XXX: departs from tbl convention in that we disallow
+		 * macros in the data body.
+		 */
+		if (strncasecmp(p, ".T&", 3)) 
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+		return(tbl_data_close(tbl, f, ln));
+	}
+
+	if (NULL == (dp = tbl_span_alloc(tbl)))
+		return(0);
+
+	if ( ! strcmp(p, "_")) {
+		dp->flags |= TBL_SPAN_HORIZ;
+		return(1);
+	} else if ( ! strcmp(p, "=")) {
+		dp->flags |= TBL_SPAN_DHORIZ;
+		return(1);
+	}
+
+	for (j = i = 0; p[i]; i++) {
+		if (p[i] != tbl->tab)
+			continue;
+		if ( ! data(tbl, dp, f, ln, i, p, j, i))
+			return(0);
+		j = i + 1;
+	}
+
+	return(data(tbl, dp, f, ln, i, p, j, i));
+}
+
+
+int
+tbl_data_close(struct tbl *tbl, const char *f, int ln)
+{
+	struct tbl_span	*span;
+
+	/* LINTED */
+	span = TAILQ_LAST(&tbl->span, tbl_spanh);
+	if (NULL == span)
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+	if (TAILQ_NEXT(span->row, entries))
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+
+	tbl->part = TBL_PART_LAYOUT;
+	return(1);
+}
Index: tbl_extern.h
===================================================================
RCS file: tbl_extern.h
diff -N tbl_extern.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_extern.h	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,183 @@
+/*	$Id: extern.h,v 1.10 2009/09/13 12:37:28 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is 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 TBL_EXTERN_H
+#define TBL_EXTERN_H
+
+enum	tbl_err {
+	ERR_SYNTAX,
+	ERR_OPTION,
+	ERR_MAX
+};
+
+enum	tbl_tok {
+	TBL_TOK_WORD,
+	TBL_TOK_OPENPAREN,
+	TBL_TOK_CLOSEPAREN,
+	TBL_TOK_COMMA,
+	TBL_TOK_SEMICOLON,
+	TBL_TOK_PERIOD,
+	TBL_TOK_SPACE,
+	TBL_TOK_TAB,
+	TBL_TOK_NIL
+};
+
+enum	tbl_part {
+	TBL_PART_OPTS,
+	TBL_PART_LAYOUT,
+	TBL_PART_CLAYOUT,
+	TBL_PART_DATA,
+	TBL_PART_ERROR
+};
+
+struct	tbl;
+struct	tbl_head;
+struct	tbl_row;
+struct	tbl_cell;
+struct	tbl_span;
+struct	tbl_data;
+
+TAILQ_HEAD(tbl_rowh, tbl_row);
+TAILQ_HEAD(tbl_cellh, tbl_cell);
+TAILQ_HEAD(tbl_headh, tbl_head);
+TAILQ_HEAD(tbl_spanh, tbl_span);
+TAILQ_HEAD(tbl_datah, tbl_data);
+
+struct	tbl {
+	enum tbl_part	 	 part;
+	int		 	 opts;
+#define	TBL_OPT_CENTRE		(1 << 0)
+#define	TBL_OPT_EXPAND		(1 << 1)
+#define	TBL_OPT_BOX		(1 << 2)
+#define	TBL_OPT_DBOX		(1 << 3)
+#define	TBL_OPT_ALLBOX		(1 << 4)
+#define	TBL_OPT_NOKEEP		(1 << 5)
+#define	TBL_OPT_NOSPACE		(1 << 6)
+	char		 	 tab;
+	char		 	 decimal;
+	int		 	 linesize;
+	char		 	 delims[2];
+	struct tbl_spanh	 span;
+	struct tbl_headh	 head;
+	struct tbl_rowh		 row;
+};
+
+enum	tbl_headt {
+	TBL_HEAD_DATA,
+	TBL_HEAD_VERT,
+	TBL_HEAD_DVERT,
+	TBL_HEAD_MAX
+};
+
+struct	tbl_head {
+	struct tbl		*tbl;
+	enum tbl_headt	 	 pos;
+	int			 width;
+	int			 decimal;
+	TAILQ_ENTRY(tbl_head)	 entries;
+};
+
+struct	tbl_row {
+	struct tbl		*tbl;
+	struct tbl_cellh	 cell;
+	TAILQ_ENTRY(tbl_row)	 entries;
+};
+
+enum	tbl_cellt {
+	TBL_CELL_CENTRE,	/* c, C */
+	TBL_CELL_RIGHT,		/* r, R */
+	TBL_CELL_LEFT,		/* l, L */
+	TBL_CELL_NUMBER,	/* n, N */
+	TBL_CELL_SPAN,		/* s, S */
+	TBL_CELL_LONG,		/* a, A */
+	TBL_CELL_DOWN,		/* ^ */
+	TBL_CELL_HORIZ,		/* _, - */
+	TBL_CELL_DHORIZ,	/* = */
+	TBL_CELL_VERT,		/* | */
+	TBL_CELL_DVERT,		/* || */
+	TBL_CELL_MAX
+};
+
+struct	tbl_cell {
+	struct tbl_row		*row;
+	struct tbl_head		*head;
+	enum tbl_cellt	 	 pos;
+	int		  	 spacing;
+	int		 	 flags;
+#define	TBL_CELL_TALIGN		(1 << 0)	/* t, T */
+#define	TBL_CELL_BALIGN		(1 << 1)	/* d, D */
+#define	TBL_CELL_BOLD		(1 << 2)	/* fB, B, b */
+#define	TBL_CELL_ITALIC		(1 << 3)	/* fI, I, i */
+#define	TBL_CELL_EQUAL		(1 << 4)	/* e, E */
+#define	TBL_CELL_UP		(1 << 5)	/* u, U */
+#define	TBL_CELL_WIGN		(1 << 6)	/* z, Z */
+	TAILQ_ENTRY(tbl_cell)	 entries;
+};
+
+struct	tbl_data {
+	struct tbl_span		*span;
+	struct tbl_cell		*cell;
+	int		 	 flags;
+#define	TBL_DATA_HORIZ		(1 << 0)
+#define	TBL_DATA_DHORIZ		(1 << 1)
+#define	TBL_DATA_NHORIZ		(1 << 2)
+#define	TBL_DATA_NDHORIZ 	(1 << 3)
+	char			*string;
+	TAILQ_ENTRY(tbl_data)	 entries;
+};
+
+struct	tbl_span {
+	struct tbl_row		*row;
+	struct tbl		*tbl;
+	int		 	 flags;
+#define	TBL_SPAN_HORIZ		(1 << 0)
+#define	TBL_SPAN_DHORIZ		(1 << 1)
+	struct tbl_datah	 data;
+	TAILQ_ENTRY(tbl_span)	 entries;
+};
+
+__BEGIN_DECLS
+
+int	 	 tbl_option(struct tbl *, 
+			const char *, int, const char *);
+int	 	 tbl_layout(struct tbl *, 
+			const char *, int, const char *);
+int	 	 tbl_data(struct tbl *, 
+			const char *, int, const char *);
+int		 tbl_data_close(struct tbl *,  const char *, int);
+
+enum tbl_tok	 tbl_next(const char *, int *);
+const char	*tbl_last(void);
+int		 tbl_last_uint(void);
+int		 tbl_errx(struct tbl *, enum tbl_err, 
+			const char *, int, int);
+int		 tbl_warnx(struct tbl *, enum tbl_err, 
+			const char *, int, int);
+int		 tbl_err(struct tbl *);
+
+struct tbl_row	*tbl_row_alloc(struct tbl *);
+struct tbl_cell	*tbl_cell_alloc(struct tbl_row *, enum tbl_cellt);
+struct tbl_span	*tbl_span_alloc(struct tbl *);
+struct tbl_data	*tbl_data_alloc(struct tbl_span *);
+
+int		 tbl_write_term(const struct tbl *);
+int		 tbl_calc_term(struct tbl *);
+int		 tbl_write_tree(const struct tbl *);
+int		 tbl_calc_tree(struct tbl *);
+
+__END_DECLS
+
+#endif /*TBL_EXTERN_H*/
Index: tbl_layout.c
===================================================================
RCS file: tbl_layout.c
diff -N tbl_layout.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_layout.c	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,280 @@
+/*	$Id: layout.c,v 1.7 2009/09/11 13:24:04 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tbl_extern.h"
+
+struct	tbl_phrase {
+	char		 name;
+	enum tbl_cellt	 key;
+};
+
+#define	KEYS_MAX	 17
+
+static	const struct tbl_phrase keys[KEYS_MAX] = {
+	{ 'c',		 TBL_CELL_CENTRE },
+	{ 'C',		 TBL_CELL_CENTRE },
+	{ 'r',		 TBL_CELL_RIGHT },
+	{ 'R',		 TBL_CELL_RIGHT },
+	{ 'l',		 TBL_CELL_LEFT },
+	{ 'L',		 TBL_CELL_LEFT },
+	{ 'n',		 TBL_CELL_NUMBER },
+	{ 'N',		 TBL_CELL_NUMBER },
+	{ 's',		 TBL_CELL_SPAN },
+	{ 'S',		 TBL_CELL_SPAN },
+	{ 'a',		 TBL_CELL_LONG },
+	{ 'A',		 TBL_CELL_LONG },
+	{ '^',		 TBL_CELL_DOWN },
+	{ '-',		 TBL_CELL_HORIZ },
+	{ '_',		 TBL_CELL_HORIZ },
+	{ '=',		 TBL_CELL_DHORIZ },
+	{ '|',		 TBL_CELL_VERT }
+};
+
+static	int		mods(struct tbl *, struct tbl_cell *, 
+				const char *, int, 
+				const char *, int, int);
+static	int		cell(struct tbl *, struct tbl_row *, 
+				const char *, int, int);
+static	int		row(struct tbl *, const char *,
+				int, const char *, int *);
+
+
+static int
+mods(struct tbl *tbl, struct tbl_cell *cp, const char *p, 
+		int pp, const char *f, int ln, int pos)
+{
+	char		 buf[5];
+	int		 i;
+
+	/* 
+	 * XXX: since, at least for now, modifiers are non-conflicting
+	 * (are separable by value, regardless of position), we let
+	 * modifiers come in any order.  The existing tbl doesn't let
+	 * this happen.
+	 */
+
+	if (0 == p[pp])
+		return(1);
+
+	/* Parse numerical spacing from modifier string. */
+
+	if (isdigit((u_char)p[pp])) {
+		for (i = 0; i < 4; i++) {
+			if ( ! isdigit((u_char)p[pp + i]))
+				break;
+			buf[i] = p[pp + i];
+		}
+		buf[i] = 0;
+
+		/* No greater than 4 digits. */
+
+		if (4 == i)
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos + pp));
+
+		/* 
+		 * We can't change the spacing in any subsequent layout
+		 * definitions.  FIXME: I don't think we can change the
+		 * spacing for a column at all, after it's already been
+		 * initialised.
+		 */
+
+		if (TBL_PART_CLAYOUT != tbl->part)
+			cp->spacing = atoi(buf);
+		else if ( ! tbl_warnx(tbl, ERR_SYNTAX, f, ln, pos + pp))
+			return(0);
+		
+		/* Continue parsing modifiers. */
+
+		return(mods(tbl, cp, p, pp + i, f, ln, pos));
+	} 
+
+	/* TODO: GNU has many more extensions. */
+
+	switch (p[pp]) {
+	case ('z'):
+		/* FALLTHROUGH */
+	case ('Z'):
+		cp->flags |= TBL_CELL_WIGN;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('u'):
+		/* FALLTHROUGH */
+	case ('U'):
+		cp->flags |= TBL_CELL_UP;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('e'):
+		/* FALLTHROUGH */
+	case ('E'):
+		cp->flags |= TBL_CELL_EQUAL;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('t'):
+		/* FALLTHROUGH */
+	case ('T'):
+		cp->flags |= TBL_CELL_TALIGN;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('d'):
+		/* FALLTHROUGH */
+	case ('D'):
+		cp->flags |= TBL_CELL_BALIGN;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('f'):
+		pp++;
+		/* FALLTHROUGH */
+	case ('B'):
+		/* FALLTHROUGH */
+	case ('I'):
+		/* FALLTHROUGH */
+	case ('b'):
+		/* FALLTHROUGH */
+	case ('i'):
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos + pp));
+	}
+
+	switch (p[pp]) {
+	case ('b'):
+		/* FALLTHROUGH */
+	case ('B'):
+		cp->flags |= TBL_CELL_BOLD;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('i'):
+		/* FALLTHROUGH */
+	case ('I'):
+		cp->flags |= TBL_CELL_ITALIC;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	default:
+		break;
+	}
+
+	return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos + pp));
+}
+
+
+static int
+cell(struct tbl *tbl, struct tbl_row *rp, 
+		const char *f, int ln, int pos)
+{
+	struct tbl_cell	*cp;
+	const char	*p;
+	int		 j, i;
+	enum tbl_cellt	 c;
+
+	/* Parse the column position (`r', `R', `|', ...). */
+
+	c = TBL_CELL_MAX;
+	for (p = tbl_last(), i = 0; i < KEYS_MAX; i++) {
+		if (keys[i].name != p[0])
+			continue;
+		c = keys[i].key;
+		break;
+	}
+
+	if (i == KEYS_MAX)
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos));
+
+	/* Extra check for the double-vertical. */
+
+	if (TBL_CELL_VERT == c && '|' == p[1]) {
+		j = 2;
+		c = TBL_CELL_DVERT;
+	} else
+		j = 1;
+	
+	/* Disallow subsequent spacers. */
+
+	/* LINTED */
+	cp = TAILQ_LAST(&rp->cell, tbl_cellh);
+
+	if (cp && (TBL_CELL_VERT == c || TBL_CELL_DVERT == c) && 
+			(TBL_CELL_VERT == cp->pos || 
+			 TBL_CELL_DVERT == cp->pos))
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos));
+
+	/* Allocate cell then parse its modifiers. */
+
+	if (NULL == (cp = tbl_cell_alloc(rp, c)))
+		return(0);
+	return(mods(tbl, cp, p, j, f, ln, pos));
+}
+
+
+static int
+row(struct tbl *tbl, const char *f, int ln,
+		const char *p, int *pos)
+{
+	struct tbl_row	*rp;
+	int		 sv;
+
+	rp = tbl_row_alloc(tbl);
+again:
+	sv = *pos;
+
+	/*
+	 * EBNF describing this section:
+	 *
+	 * row		::= row_list [:space:]* [.]?[\n]
+	 * row_list	::= [:space:]* row_elem row_tail
+	 * row_tail	::= [:space:]*[,] row_list |
+	 *                  epsilon
+	 * row_elem	::= [\t\ ]*[:alpha:]+
+	 */
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_TAB):
+		/* FALLTHROUGH */
+	case (TBL_TOK_SPACE):
+		goto again;
+	case (TBL_TOK_WORD):
+		if ( ! cell(tbl, rp, f, ln, sv))
+			return(0);
+		goto again;
+	case (TBL_TOK_COMMA):
+		if (NULL == TAILQ_FIRST(&rp->cell))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		return(row(tbl, f, ln, p, pos));
+	case (TBL_TOK_PERIOD):
+		if (NULL == TAILQ_FIRST(&rp->cell))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->part = TBL_PART_DATA;
+		break;
+	case (TBL_TOK_NIL):
+		if (NULL == TAILQ_FIRST(&rp->cell))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	return(1);
+}
+
+
+int
+tbl_layout(struct tbl *tbl, const char *f, int ln, const char *p)
+{
+	int		 pos;
+
+	pos = 0;
+	return(row(tbl, f, ln, p, &pos));
+}
Index: tbl_option.c
===================================================================
RCS file: tbl_option.c
diff -N tbl_option.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_option.c	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,195 @@
+/*	$Id: option.c,v 1.5 2009/09/09 12:51:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tbl_extern.h"
+
+struct	tbl_phrase {
+	char		*name;
+	int		 key;
+	int		 ident;
+#define	KEY_CENTRE	 0
+#define	KEY_DELIM	 1
+#define	KEY_EXPAND	 2
+#define	KEY_BOX		 3
+#define	KEY_DBOX	 4
+#define	KEY_ALLBOX	 5
+#define	KEY_TAB		 6
+#define	KEY_LINESIZE	 7
+#define	KEY_NOKEEP	 8
+#define	KEY_DPOINT	 9
+#define	KEY_NOSPACE	 10
+#define	KEY_FRAME	 11
+#define	KEY_DFRAME	 12
+};
+
+#define	KEY_MAXKEYS	 14
+
+static	const struct tbl_phrase keys[KEY_MAXKEYS] = {
+	{ "center",	 TBL_OPT_CENTRE,	KEY_CENTRE},
+	{ "centre",	 TBL_OPT_CENTRE,	KEY_CENTRE},
+	{ "delim",	 0,	       		KEY_DELIM},
+	{ "expand",	 TBL_OPT_EXPAND,	KEY_EXPAND},
+	{ "box",	 TBL_OPT_BOX,   	KEY_BOX},
+	{ "doublebox",	 TBL_OPT_DBOX,  	KEY_DBOX},
+	{ "allbox",	 TBL_OPT_ALLBOX,	KEY_ALLBOX},
+	{ "frame",	 TBL_OPT_BOX,		KEY_FRAME},
+	{ "doubleframe", TBL_OPT_DBOX,		KEY_DFRAME},
+	{ "tab",	 0,			KEY_TAB},
+	{ "linesize",	 0,			KEY_LINESIZE},
+	{ "nokeep",	 TBL_OPT_NOKEEP,	KEY_NOKEEP},
+	{ "decimalpoint", 0,			KEY_DPOINT},
+	{ "nospaces",	 TBL_OPT_NOSPACE,	KEY_NOSPACE},
+};
+
+static	int		 arg(struct tbl *, const char *, 
+				int, const char *, int *, int);
+static	int		 opt(struct tbl *, const char *, 
+				int, const char *, int *);
+
+static int
+arg(struct tbl *tbl, const char *f, int ln,
+		const char *p, int *pos, int key)
+{
+	const char	*buf;
+	int		 sv;
+
+again:
+	sv = *pos;
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_OPENPAREN):
+		break;
+	case (TBL_TOK_SPACE):
+		/* FALLTHROUGH */
+	case (TBL_TOK_TAB):
+		goto again;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	sv = *pos;
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_WORD):
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	buf = tbl_last();
+
+	switch (key) {
+	case (KEY_DELIM):
+		if (2 != strlen(buf))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->delims[0] = buf[0];
+		tbl->delims[1] = buf[1];
+		break;
+	case (KEY_TAB):
+		if (1 != strlen(buf))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->tab = buf[0];
+		break;
+	case (KEY_LINESIZE):
+		if (-1 == (tbl->linesize = tbl_last_uint()))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		break;
+	case (KEY_DPOINT):
+		if (1 != strlen(buf))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->decimal = buf[0];
+		break;
+	default:
+		abort();
+	}
+
+	sv = *pos;
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_CLOSEPAREN):
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	return(1);
+}
+
+
+static int
+opt(struct tbl *tbl, const char *f, int ln, const char *p, int *pos)
+{
+	int		 i, sv;
+
+again:
+	sv = *pos;
+
+	/*
+	 * EBNF describing this section:
+	 *
+	 * options	::= option_list [:space:]* [;][\n]
+	 * option_list	::= option option_tail
+	 * option_tail	::= [:space:]+ option_list |
+	 * 		::= epsilon
+	 * option	::= [:alpha:]+ args
+	 * args		::= [:space:]* [(] [:alpha:]+ [)]
+	 */
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_WORD):
+		break;
+	case (TBL_TOK_SPACE):
+		/* FALLTHROUGH */
+	case (TBL_TOK_TAB):
+		goto again;
+	case (TBL_TOK_SEMICOLON):
+		tbl->part = TBL_PART_LAYOUT;
+		return(1);
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	for (i = 0; i < KEY_MAXKEYS; i++) {
+		if (strcasecmp(tbl_last(), keys[i].name))
+			continue;
+		if (keys[i].key) 
+			tbl->opts |= keys[i].key;
+		else if ( ! arg(tbl, f, ln, p, pos, keys[i].ident))
+			return(0);
+
+		break;
+	}
+
+	if (KEY_MAXKEYS == i)
+		return(tbl_errx(tbl, ERR_OPTION, f, ln, sv));
+
+	return(opt(tbl, f, ln, p, pos));
+}
+
+
+int
+tbl_option(struct tbl *tbl, const char *f, int ln, const char *p)
+{
+	int		 pos;
+
+	pos = 0;
+	return(opt(tbl, f, ln, p, &pos));
+}
Index: tbl_term.c
===================================================================
RCS file: tbl_term.c
diff -N tbl_term.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_term.c	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,504 @@
+/*	$Id: term.c,v 1.13 2009/09/14 09:06:40 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tbl_extern.h"
+
+/* FIXME: `n' modifier doesn't always do the right thing. */
+/* FIXME: `n' modifier doesn't use the cell-spacing buffer. */
+
+static	void		 calc_data(struct tbl_data *);
+static	void		 calc_data_literal(struct tbl_data *);
+static	void		 calc_data_number(struct tbl_data *);
+static	void		 calc_data_spanner(struct tbl_data *);
+static	inline void	 write_char(char, int);
+static	void		 write_data(const struct tbl_data *, int);
+static	void		 write_data_literal(const struct tbl_data *, int);
+static	void		 write_data_number(const struct tbl_data *, int);
+static	void		 write_data_spanner(const struct tbl_data *, int);
+static	void		 write_hframe(const struct tbl *);
+static	void		 write_hrule(const struct tbl_span *);
+static	void		 write_spanner(const struct tbl_head *);
+static	void		 write_vframe(const struct tbl *);
+
+
+int
+tbl_write_term(const struct tbl *tbl)
+{
+	const struct tbl_span	*span;
+	const struct tbl_data	*data;
+	const struct tbl_head	*head;
+
+	/*
+	 * Note that the absolute widths and decimal places for headers
+	 * were set when tbl_calc_term was called.
+	 */
+
+	/* First, write out our head horizontal frame. */
+
+	write_hframe(tbl);
+
+	/*
+	 * Iterate through each span, and inside, through the global
+	 * headers.  If the global header's a spanner, print it
+	 * directly; if it's data, use the corresponding data in the
+	 * span as the object to print.
+	 */
+
+	TAILQ_FOREACH(span, &tbl->span, entries) {
+		write_vframe(tbl);
+
+		/* Accomodate for the horizontal rule. */
+		if (TBL_DATA_DHORIZ & span->flags || 
+				TBL_DATA_HORIZ & span->flags) {
+			write_hrule(span);
+			write_vframe(tbl);
+			printf("\n");
+			continue;
+		}
+
+		data = TAILQ_FIRST(&span->data);
+		TAILQ_FOREACH(head, &tbl->head, entries) {
+			switch (head->pos) {
+			case (TBL_HEAD_VERT):
+				/* FALLTHROUGH */
+			case (TBL_HEAD_DVERT):
+				write_spanner(head);
+				break;
+			case (TBL_HEAD_DATA):
+				write_data(data, head->width);
+				if (data)
+					data = TAILQ_NEXT(data, entries);
+				break;
+			default:
+				abort();
+				/* NOTREACHED */
+			}
+		}
+		write_vframe(tbl);
+		printf("\n");
+	}
+
+	/* Last, write out our tail horizontal frame. */
+
+	write_hframe(tbl);
+
+	return(1);
+}
+
+
+int
+tbl_calc_term(struct tbl *tbl)
+{
+	struct tbl_span	*span;
+	struct tbl_data	*data;
+	struct tbl_head	*head;
+
+	/* Calculate width as the max of column cells' widths. */
+
+	TAILQ_FOREACH(span, &tbl->span, entries) {
+		if (TBL_DATA_HORIZ & span->flags)
+			continue;
+		if (TBL_DATA_DHORIZ & span->flags)
+			continue;
+		if (TBL_DATA_NHORIZ & span->flags)
+			continue;
+		if (TBL_DATA_NDHORIZ & span->flags)
+			continue;
+		TAILQ_FOREACH(data, &span->data, entries)
+			calc_data(data);
+	}
+
+	/* Calculate width as the simple spanner value. */
+
+	TAILQ_FOREACH(head, &tbl->head, entries) 
+		switch (head->pos) {
+		case (TBL_HEAD_VERT):
+			head->width = 1;
+			break;
+		case (TBL_HEAD_DVERT):
+			head->width = 2;
+			break;
+		default:
+			break;
+		}
+
+	return(1);
+}
+
+
+static void
+write_hrule(const struct tbl_span *span)
+{
+	const struct tbl_head	*head;
+	char			 c;
+
+	/*
+	 * An hrule extends across the entire table and is demarked by a
+	 * standalone `_' or whatnot in lieu of a table row.  Spanning
+	 * headers are marked by a `+', as are table boundaries.
+	 */
+
+	c = '-';
+	if (TBL_SPAN_DHORIZ & span->flags)
+		c = '=';
+
+	/* FIXME: don't use `+' between data and a spanner! */
+
+	TAILQ_FOREACH(head, &span->tbl->head, entries) {
+		switch (head->pos) {
+		case (TBL_HEAD_DATA):
+			write_char(c, head->width);
+			break;
+		case (TBL_HEAD_DVERT):
+			write_char('+', head->width);
+			/* FALLTHROUGH */
+		case (TBL_HEAD_VERT):
+			write_char('+', head->width);
+			break;
+		default:
+			abort();
+			/* NOTREACHED */
+		}
+	}
+}
+
+
+static void
+write_hframe(const struct tbl *tbl)
+{
+	const struct tbl_head	*head;
+
+	if ( ! (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts))
+		return;
+
+	/* 
+	 * Print out the horizontal part of a frame or double frame.  A
+	 * double frame has an unbroken `-' outer line the width of the
+	 * table, bordered by `+'.  The frame (or inner frame, in the
+	 * case of the double frame) is a `-' bordered by `+' and broken
+	 * by `+' whenever a span is encountered.
+	 */
+
+	if (TBL_OPT_DBOX & tbl->opts) {
+		printf("+");
+		TAILQ_FOREACH(head, &tbl->head, entries)
+			write_char('-', head->width);
+		printf("+\n");
+	}
+
+	printf("+");
+	TAILQ_FOREACH(head, &tbl->head, entries) {
+		switch (head->pos) {
+		case (TBL_HEAD_DATA):
+			write_char('-', head->width);
+			break;
+		default:
+			write_char('+', head->width);
+			break;
+		}
+	}
+	printf("+\n");
+}
+
+
+static void
+write_vframe(const struct tbl *tbl)
+{
+	/* Always just a single vertical line. */
+
+	if ( ! (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts))
+		return;
+	printf("|");
+}
+
+
+static void
+calc_data_spanner(struct tbl_data *data)
+{
+
+	/* N.B., these are horiz spanners (not vert) so always 1. */
+	data->cell->head->width = 1;
+}
+
+
+static void
+calc_data_number(struct tbl_data *data)
+{
+	int 		 sz, d;
+	char		*dp, pnt;
+
+	/*
+	 * First calculate number width and decimal place (last + 1 for
+	 * no-decimal numbers).  If the stored decimal is subsequent
+	 * ours, make our size longer by that difference
+	 * (right-"shifting"); similarly, if ours is subsequent the
+	 * stored, then extend the stored size by the difference.
+	 * Finally, re-assign the stored values.
+	 */
+
+	/* TODO: use spacing modifier. */
+
+	assert(data->string);
+	sz = (int)strlen(data->string);
+	pnt = data->span->tbl->decimal;
+
+	if (NULL == (dp = strchr(data->string, pnt)))
+		d = sz + 1;
+	else
+		d = (int)(dp - data->string) + 1;
+
+	sz += 2;
+
+	if (data->cell->head->decimal > d) {
+		sz += data->cell->head->decimal - d;
+		d = data->cell->head->decimal;
+	} else
+		data->cell->head->width += 
+			d - data->cell->head->decimal;
+
+	if (sz > data->cell->head->width)
+		data->cell->head->width = sz;
+	if (d > data->cell->head->decimal)
+		data->cell->head->decimal = d;
+}
+
+
+static void
+calc_data_literal(struct tbl_data *data)
+{
+	int		 sz, bufsz;
+
+	/* 
+	 * Calculate our width and use the spacing, with a minimum
+	 * spacing dictated by position (centre, e.g,. gets a space on
+	 * either side, while right/left get a single adjacent space).
+	 */
+
+	assert(data->string);
+	sz = (int)strlen(data->string);
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		bufsz = 2;
+		break;
+	default:
+		bufsz = 1;
+		break;
+	}
+
+	if (data->cell->spacing)
+		bufsz = bufsz > data->cell->spacing ? 
+			bufsz : data->cell->spacing;
+
+	sz += bufsz;
+	if (data->cell->head->width < sz)
+		data->cell->head->width = sz;
+}
+
+
+static void
+calc_data(struct tbl_data *data)
+{
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_HORIZ):
+		/* FALLTHROUGH */
+	case (TBL_CELL_DHORIZ):
+		calc_data_spanner(data);
+		break;
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		/* FALLTHROUGH */
+	case (TBL_CELL_LEFT):
+		/* FALLTHROUGH */
+	case (TBL_CELL_RIGHT):
+		calc_data_literal(data);
+		break;
+	case (TBL_CELL_NUMBER):
+		calc_data_number(data);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+
+static void
+write_data_spanner(const struct tbl_data *data, int width)
+{
+
+	/*
+	 * Write spanners dictated by both our cell designation (in the
+	 * layout) or as data.
+	 */
+	if (TBL_DATA_HORIZ & data->flags)
+		write_char('-', width);
+	else if (TBL_DATA_DHORIZ & data->flags)
+		write_char('=', width);
+	else if (TBL_CELL_HORIZ == data->cell->pos)
+		write_char('-', width);
+	else if (TBL_CELL_DHORIZ == data->cell->pos)
+		write_char('=', width);
+}
+
+
+static void
+write_data_number(const struct tbl_data *data, int width)
+{
+	char		*dp, pnt;
+	int		 d, padl, sz;
+
+	/*
+	 * See calc_data_number().  Left-pad by taking the offset of our
+	 * and the maximum decimal; right-pad by the remaining amount.
+	 */
+
+	sz = (int)strlen(data->string);
+	pnt = data->span->tbl->decimal;
+
+	if (NULL == (dp = strchr(data->string, pnt))) {
+		d = sz + 1;
+	} else {
+		d = (int)(dp - data->string) + 1;
+	}
+
+	assert(d <= data->cell->head->decimal);
+	assert(sz - d <= data->cell->head->width -
+			data->cell->head->decimal);
+
+	padl = data->cell->head->decimal - d + 1;
+	assert(width - sz - padl);
+
+	write_char(' ', padl);
+	(void)printf("%s", data->string);
+	write_char(' ', width - sz - padl);
+}
+
+
+static void
+write_data_literal(const struct tbl_data *data, int width)
+{
+	int		 padl, padr;
+
+	padl = padr = 0;
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_LONG):
+		padl = 1;
+		padr = width - (int)strlen(data->string) - 1;
+		break;
+	case (TBL_CELL_CENTRE):
+		padl = width - (int)strlen(data->string);
+		if (padl % 2)
+			padr++;
+		padl /= 2;
+		padr += padl;
+		break;
+	case (TBL_CELL_RIGHT):
+		padl = width - (int)strlen(data->string);
+		break;
+	default:
+		padr = width - (int)strlen(data->string);
+		break;
+	}
+
+	write_char(' ', padl);
+	(void)printf("%s", data->string);
+	write_char(' ', padr);
+}
+
+
+static void
+write_data(const struct tbl_data *data, int width)
+{
+
+	if (NULL == data) {
+		write_char(' ', width);
+		return;
+	}
+
+	if (TBL_DATA_HORIZ & data->flags || 
+			TBL_DATA_DHORIZ & data->flags) {
+		write_data_spanner(data, width);
+		return;
+	}
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_HORIZ):
+		/* FALLTHROUGH */
+	case (TBL_CELL_DHORIZ):
+		write_data_spanner(data, width);
+		break;
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		/* FALLTHROUGH */
+	case (TBL_CELL_LEFT):
+		/* FALLTHROUGH */
+	case (TBL_CELL_RIGHT):
+		write_data_literal(data, width);
+		break;
+	case (TBL_CELL_NUMBER):
+		write_data_number(data, width);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+
+static void
+write_spanner(const struct tbl_head *head)
+{
+	char		*p;
+
+	p = NULL;
+	switch (head->pos) {
+	case (TBL_HEAD_VERT):
+		p = "|";
+		break;
+	case (TBL_HEAD_DVERT):
+		p = "||";
+		break;
+	default:
+		break;
+	}
+
+	assert(p);
+	printf("%s", p);
+}
+
+
+static inline void
+write_char(char c, int len)
+{
+	int		 i;
+
+	for (i = 0; i < len; i++)
+		printf("%c", c);
+}
Index: tbl_tree.c
===================================================================
RCS file: tbl_tree.c
diff -N tbl_tree.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_tree.c	12 Oct 2010 23:37:37 -0000
@@ -0,0 +1,86 @@
+/*	$Id: tree.c,v 1.2 2009/09/11 13:24:04 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tbl_extern.h"
+
+static	const char * const htypes[TBL_HEAD_MAX] = {
+	"data",
+	"vert",
+	"dvert",
+};
+
+static	const char * const ctypes[TBL_CELL_MAX] = {
+	"centre",
+	"right",
+	"left",
+	"number",
+	"span",
+	"long",
+	"down",
+	"horiz",
+	"dhoriz",
+	"vert",
+	"dvert",
+};
+
+
+/* ARGSUSED */
+int
+tbl_calc_tree(struct tbl *tbl)
+{
+
+	return(1);
+}
+
+
+int
+tbl_write_tree(const struct tbl *tbl)
+{
+	struct tbl_row	*row;
+	struct tbl_cell	*cell;
+	struct tbl_span	*span;
+	struct tbl_data	*data;
+	struct tbl_head	*head;
+
+	(void)printf("header\n");
+	TAILQ_FOREACH(head, &tbl->head, entries)
+		(void)printf("\t%s (=%p)\n", htypes[head->pos], head);
+
+	(void)printf("layout\n");
+	TAILQ_FOREACH(row, &tbl->row, entries) {
+		(void)printf("\trow (=%p)\n", row);
+		TAILQ_FOREACH(cell, &row->cell, entries)
+			(void)printf("\t\t%s (=%p) >%p\n", 
+					ctypes[cell->pos], 
+					cell, cell->head);
+	}
+
+	(void)printf("data\n");
+	TAILQ_FOREACH(span, &tbl->span, entries) {
+		(void)printf("\tspan >%p\n", span->row);
+		TAILQ_FOREACH(data, &span->data, entries)
+			(void)printf("\t\tdata >%p\n", data->cell);
+	}
+
+	return(1);
+}
--
 To unsubscribe send an email to tech+unsubscribe@mdocml.bsd.lv

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: integrate tbl into mandoc
  2010-10-13  0:46 integrate tbl into mandoc Ingo Schwarze
@ 2010-10-14 21:18 ` Ingo Schwarze
  2010-10-14 21:55   ` Ingo Schwarze
                     ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Ingo Schwarze @ 2010-10-14 21:18 UTC (permalink / raw)
  To: tech

[-- Attachment #1: Type: text/plain, Size: 55343 bytes --]

Hi,

Replying to myself:

> One thing to do before commit is probably to replace the printfs
> in the output frontend by calls into term.c, such that indentation
> is respected and escape sequences get resolved.

Here you are.

Inline, i'm sending the updated full diff against the OpenBSD tree.
To make it easier to see how the term.c integration was done,
i'm sending a smaller diff as an attachment.  That one is not
intended for applying, but just for reading.


The following still holds:

> There may be more i need to do.
> The following already look semi-useful with this:
>   /usr/src/lib/libcurses/curs_attr.3tbl
>   /usr/src/lib/libcurses/curs_getch.3tbl
>   /usr/src/lib/libcurses/curses.3tbl
>   /usr/src/usr.bin/infocmp/infocmp.1tbl
> The following still crash:
>   /usr/src/lib/libcurses/curs_addch.3tbl
>   /usr/src/lib/libcurses/curs_inch.3tbl
>   /usr/src/lib/libcurses/curs_mouse.3tbl
>   /usr/src/lib/libform/form.3tbl
>   /usr/src/lib/libmenu/menu.3tbl
>   /usr/src/usr.bin/tic/captoinfo.1tbl
>   /usr/src/gnu/usr.sbin/mkhybrid/src/mkhybrid.8tbl
> The following have a tbl extension, but no .TS:
>   /usr/src/lib/libcurses/term.5tbl
> The following are mdoc(7) with tbl:
>   /usr/src/share/man/man4/wi.4tbl
>   /usr/src/share/man/man4/man4.hppa/cpu.4tbl
>   /usr/src/games/phantasia/phantasia.6tbl

Any objection against putting this into OpenBSD for now,
such that i can hack in tree?

That won't prevent improving the glue, later on,
when we have more time.

Yours,
  Ingo


Index: Makefile
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/Makefile,v
retrieving revision 1.45
diff -u -p -r1.45 Makefile
--- Makefile	27 Sep 2010 21:25:28 -0000	1.45
+++ Makefile	14 Oct 2010 21:03:05 -0000
@@ -20,6 +20,8 @@ SRCS+=	main.c mdoc_term.c chars.c term.c
 SRCS+=	html.c mdoc_html.c man_html.c out.c
 SRCS+=	term_ps.c term_ascii.c
 
+SRCS+=	tbl_data.c tbl_layout.c tbl_option.c tbl.c tbl_term.c tbl_tree.c
+
 PROG=	mandoc
 
 .include <bsd.prog.mk>
Index: man.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man.c,v
retrieving revision 1.40
diff -u -p -r1.40 man.c
--- man.c	20 Aug 2010 00:53:35 -0000	1.40
+++ man.c	14 Oct 2010 21:03:05 -0000
@@ -25,6 +25,7 @@
 #include "mandoc.h"
 #include "libman.h"
 #include "libmandoc.h"
+#include "tbl.h"
 
 const	char *const __man_macronames[MAN_MAX] = {		 
 	"br",		"TH",		"SH",		"SS",
@@ -36,7 +37,7 @@ const	char *const __man_macronames[MAN_M
 	"nf",		"fi",		"r",		"RE",
 	"RS",		"DT",		"UC",		"PD",
 	"Sp",		"Vb",		"Ve",		"AT",
-	"in"
+	"in",		"TS",		"TE"
 	};
 
 const	char * const *man_macronames = __man_macronames;
@@ -121,10 +122,19 @@ man_endparse(struct man *m)
 int
 man_parseln(struct man *m, int ln, char *buf, int offs)
 {
+	struct man_node *n;
 
 	if (MAN_HALT & m->flags)
 		return(0);
 
+	n = m->last;
+
+	if (n && MAN_TS == n->tok && MAN_BODY == n->type &&
+	    strncmp(buf+offs, ".TE", 3)) {
+		n = n->parent;
+		return(tbl_read(n->data.TS, "<man>", ln, buf, offs) ? 1 : 0);
+	}
+
 	return(('.' == buf[offs] || '\'' == buf[offs]) ? 
 			man_pmacro(m, ln, buf, offs) : 
 			man_ptext(m, ln, buf, offs));
@@ -322,6 +332,8 @@ man_node_free(struct man_node *p)
 
 	if (p->string)
 		free(p->string);
+	if (p->data.TS)
+		tbl_free(p->data.TS);
 	free(p);
 }
 
Index: man.h
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man.h,v
retrieving revision 1.26
diff -u -p -r1.26 man.h
--- man.h	20 Aug 2010 00:53:35 -0000	1.26
+++ man.h	14 Oct 2010 21:03:05 -0000
@@ -57,6 +57,8 @@ enum	mant {
 	MAN_Ve,
 	MAN_AT,
 	MAN_in,
+	MAN_TS,
+	MAN_TE,
 	MAN_MAX
 };
 
@@ -95,6 +97,9 @@ struct	man_node {
 	char		*string;
 	struct man_node	*head;
 	struct man_node	*body;
+	union {
+		struct tbl *TS;
+	} data;
 };
 
 extern	const char *const *man_macronames;
Index: man_action.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_action.c,v
retrieving revision 1.24
diff -u -p -r1.24 man_action.c
--- man_action.c	25 Jul 2010 18:05:54 -0000	1.24
+++ man_action.c	14 Oct 2010 21:03:05 -0000
@@ -22,6 +22,9 @@
 #include "mandoc.h"
 #include "libman.h"
 #include "libmandoc.h"
+#include "out.h"
+#include "term.h"
+#include "tbl.h"
 
 struct	actions {
 	int	(*post)(struct man *);
@@ -32,6 +35,7 @@ static	int	  post_fi(struct man *);
 static	int	  post_nf(struct man *);
 static	int	  post_AT(struct man *);
 static	int	  post_UC(struct man *);
+static	int	  post_TS(struct man *);
 
 const	struct actions man_actions[MAN_MAX] = {
 	{ NULL }, /* br */
@@ -71,6 +75,8 @@ const	struct actions man_actions[MAN_MAX
 	{ post_fi }, /* Ve */
 	{ post_AT }, /* AT */
 	{ NULL }, /* in */
+	{ post_TS }, /* TS */
+	{ NULL }, /* TE */
 };
 
 
@@ -273,6 +279,17 @@ post_UC(struct man *m)
 		free(m->meta.source);
 
 	m->meta.source = mandoc_strdup(p);
+
+	return(1);
+}
+
+
+static int
+post_TS(struct man *m)
+{
+
+	if (MAN_HEAD == m->last->type)
+		m->last->parent->data.TS = tbl_alloc();
 
 	return(1);
 }
Index: man_html.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_html.c,v
retrieving revision 1.18
diff -u -p -r1.18 man_html.c
--- man_html.c	25 Jul 2010 18:05:54 -0000	1.18
+++ man_html.c	14 Oct 2010 21:03:05 -0000
@@ -113,6 +113,8 @@ static	const struct htmlman mans[MAN_MAX
 	{ man_literal_pre, NULL }, /* Ve */
 	{ man_ign_pre, NULL }, /* AT */
 	{ man_in_pre, NULL }, /* in */
+	{ NULL, NULL }, /* TS */
+	{ NULL, NULL }, /* TE */
 };
 
 
Index: man_macro.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_macro.c,v
retrieving revision 1.20
diff -u -p -r1.20 man_macro.c
--- man_macro.c	25 Jul 2010 18:05:54 -0000	1.20
+++ man_macro.c	14 Oct 2010 21:03:06 -0000
@@ -80,6 +80,8 @@ const	struct man_macro __man_macros[MAN_
 	{ in_line_eoln, 0 }, /* Ve */
 	{ in_line_eoln, 0 }, /* AT */
 	{ in_line_eoln, 0 }, /* in */
+	{ blk_exp, MAN_EXPLICIT }, /* TS */
+	{ blk_close, 0 }, /* TE */
 };
 
 const	struct man_macro * const man_macros = __man_macros;
@@ -264,6 +266,9 @@ blk_close(MACRO_PROT_ARGS)
 	switch (tok) {
 	case (MAN_RE):
 		ntok = MAN_RS;
+		break;
+	case (MAN_TE):
+		ntok = MAN_TS;
 		break;
 	default:
 		abort();
Index: man_term.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_term.c,v
retrieving revision 1.46
diff -u -p -r1.46 man_term.c
--- man_term.c	21 Sep 2010 22:33:41 -0000	1.46
+++ man_term.c	14 Oct 2010 21:03:06 -0000
@@ -28,6 +28,7 @@
 #include "term.h"
 #include "chars.h"
 #include "main.h"
+#include "tbl.h"
 
 #define	INDENT		  7
 #define	HALFINDENT	  3
@@ -92,6 +93,7 @@ static	int		  pre_ign(DECL_ARGS);
 static	int		  pre_in(DECL_ARGS);
 static	int		  pre_literal(DECL_ARGS);
 static	int		  pre_sp(DECL_ARGS);
+static	int		  pre_TS(DECL_ARGS);
 
 static	void		  post_IP(DECL_ARGS);
 static	void		  post_HP(DECL_ARGS);
@@ -138,6 +140,8 @@ static	const struct termact termacts[MAN
  	{ pre_literal, NULL, 0 }, /* Ve */
 	{ pre_ign, NULL, 0 }, /* AT */
 	{ pre_in, NULL, MAN_NOTEXT }, /* in */
+	{ pre_TS, NULL, 0 }, /* TS */
+	{ NULL, NULL, 0 }, /* TE */
 };
 
 
@@ -823,6 +827,23 @@ post_RS(DECL_ARGS)
 		p->offset = term_len(p, INDENT);
 		break;
 	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_TS(DECL_ARGS)
+{
+
+	if (MAN_BLOCK != n->type)
+		return(0);
+
+	if ( ! tbl_close(n->data.TS, "<man>", n->line))
+		return(0);
+
+	tbl_write(p, n->data.TS);
+
+	return(0);
 }
 
 
Index: man_validate.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/man_validate.c,v
retrieving revision 1.29
diff -u -p -r1.29 man_validate.c
--- man_validate.c	20 Aug 2010 00:53:35 -0000	1.29
+++ man_validate.c	14 Oct 2010 21:03:06 -0000
@@ -95,6 +95,8 @@ static	const struct man_valid man_valids
 	{ pres_bline, posts_eq0 }, /* Ve */
 	{ NULL, NULL }, /* AT */
 	{ NULL, NULL }, /* in */
+	{ NULL, NULL }, /* TS */
+	{ NULL, NULL }, /* RE */
 };
 
 
Index: tbl.c
===================================================================
RCS file: tbl.c
diff -N tbl.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl.c	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,546 @@
+/*	$Id: tbl.c,v 1.14 2009/09/12 16:05:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "term.h"
+#include "tbl.h"
+#include "tbl_extern.h"
+
+
+const	char *const	 errnames[ERR_MAX] = {
+	"bad syntax",	 /* ERR_SYNTAX */
+	"bad option"	 /* ERR_OPTION */
+};
+
+static	char		 buf[1024]; /* XXX */
+
+static	enum tbl_tok	 tbl_next_char(char);
+static	void		 tbl_init(struct tbl *);
+static	void		 tbl_clear(struct tbl *);
+static	struct tbl_head *tbl_head_alloc(struct tbl *);
+static	void		 tbl_span_free(struct tbl_span *);
+static	void		 tbl_data_free(struct tbl_data *);
+static	void		 tbl_row_free(struct tbl_row *);
+
+static	void		 headadj(const struct tbl_cell *, 
+				struct tbl_head *);
+
+static void
+tbl_init(struct tbl *tbl)
+{
+
+	bzero(tbl, sizeof(struct tbl));
+
+	tbl->part = TBL_PART_OPTS;
+	tbl->tab = '\t';
+	tbl->linesize = 12;
+	tbl->decimal = '.';
+
+	TAILQ_INIT(&tbl->span);
+	TAILQ_INIT(&tbl->row);
+	TAILQ_INIT(&tbl->head);
+}
+
+
+int
+tbl_read(struct tbl *tbl, const char *f, int ln, const char *p, int len)
+{
+	
+	if (len && TBL_PART_OPTS == tbl->part)
+		if (';' != p[len - 1])
+			tbl->part = TBL_PART_LAYOUT;
+
+	switch (tbl->part) {
+	case (TBL_PART_OPTS):
+		return(tbl_option(tbl, f, ln, p));
+	case (TBL_PART_CLAYOUT):
+		/* FALLTHROUGH */
+	case (TBL_PART_LAYOUT):
+		return(tbl_layout(tbl, f, ln, p));
+	case (TBL_PART_DATA):
+		return(tbl_data(tbl, f, ln, p));
+	case (TBL_PART_ERROR):
+		break;
+	}
+
+	return(0);
+}
+
+
+int
+tbl_close(struct tbl *tbl, const char *f, int ln)
+{
+
+	if (TBL_PART_DATA != tbl->part) 
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+	if ( ! tbl_data_close(tbl, f, ln))
+		return(0);
+#if 1
+	return(tbl_calc_term(tbl));
+#else
+	return(tbl_calc_tree(tbl));
+#endif
+}
+
+
+int
+tbl_write(struct termp *p, const struct tbl *tbl)
+{
+
+#if 1
+	return(tbl_write_term(p, tbl));
+#else
+	return(tbl_write_tree(tbl));
+#endif
+}
+
+
+static enum tbl_tok
+tbl_next_char(char c)
+{
+
+	/*
+	 * These are delimiting tokens.  They separate out words in the
+	 * token stream.
+	 */
+
+	switch (c) {
+	case ('('):
+		return(TBL_TOK_OPENPAREN);
+	case (')'):
+		return(TBL_TOK_CLOSEPAREN);
+	case (' '):
+		return(TBL_TOK_SPACE);
+	case ('\t'):
+		return(TBL_TOK_TAB);
+	case (';'):
+		return(TBL_TOK_SEMICOLON);
+	case ('.'):
+		return(TBL_TOK_PERIOD);
+	case (','):
+		return(TBL_TOK_COMMA);
+	case (0):
+		return(TBL_TOK_NIL);
+	default:
+		break;
+	}
+
+	return(TBL_TOK_WORD);
+}
+
+
+const char *
+tbl_last(void)
+{
+
+	return(buf);
+}
+
+
+int
+tbl_last_uint(void)
+{
+	char		*ep;
+	long		 lval;
+
+	/* From OpenBSD's strtol(3).  Gross. */
+
+	errno = 0;
+	lval = strtol(buf, &ep, 10);
+	if (buf[0] == 0 || *ep != 0)
+		return(-1);
+	if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN))
+		return(-1);
+	if (lval < 0 || lval > INT_MAX)
+		return(-1);
+
+	return((int)lval);
+}
+
+
+enum tbl_tok
+tbl_next(const char *p, int *pos)
+{
+	int		 i;
+	enum tbl_tok	 c;
+
+	buf[0] = 0;
+
+	if (TBL_TOK_WORD != (c = tbl_next_char(p[*pos]))) {
+		if (TBL_TOK_NIL != c) {
+			buf[0] = p[*pos];
+			buf[1] = 0;
+			(*pos)++;
+		}
+		return(c);
+	}
+
+	/*
+	 * Copy words into a nil-terminated buffer.  For now, we use a
+	 * static buffer.  Eventually this should be made into a dynamic
+	 * one living in struct tbl.
+	 */
+
+	for (i = 0; i < 1023; i++, (*pos)++)
+		if (TBL_TOK_WORD == tbl_next_char(p[*pos]))
+			buf[i] = p[*pos];
+		else
+			break;
+
+	assert(i < 1023);
+	buf[i] = 0;
+
+	return(TBL_TOK_WORD);
+}
+
+
+int
+tbl_err(struct tbl *tbl)
+{
+
+	(void)fprintf(stderr, "%s\n", strerror(errno));
+	tbl->part = TBL_PART_ERROR;
+	return(0);
+}
+
+
+/* ARGSUSED */
+int
+tbl_warnx(struct tbl *tbl, enum tbl_err tok, 
+		const char *f, int line, int pos)
+{
+
+	(void)fprintf(stderr, "%s:%d:%d: %s\n", 
+			f, line, pos + 1, errnames[tok]);
+
+	/* TODO: -Werror */
+	return(1);
+}
+
+
+int
+tbl_errx(struct tbl *tbl, enum tbl_err tok, 
+		const char *f, int line, int pos)
+{
+
+	(void)fprintf(stderr, "%s:%d:%d: %s\n", 
+			f, line, pos + 1, errnames[tok]);
+
+	tbl->part = TBL_PART_ERROR;
+	return(0);
+}
+
+
+struct tbl *
+tbl_alloc(void)
+{
+	struct tbl	*p;
+
+	if (NULL == (p = malloc(sizeof(struct tbl))))
+		return(NULL);
+
+	tbl_init(p);
+	return(p);
+}
+
+
+void
+tbl_free(struct tbl *p)
+{
+
+	tbl_clear(p);
+	free(p);
+}
+
+
+void
+tbl_reset(struct tbl *tbl)
+{
+
+	tbl_clear(tbl);
+	tbl_init(tbl);
+}
+
+
+struct tbl_span *
+tbl_span_alloc(struct tbl *tbl)
+{
+	struct tbl_span	*p, *pp;
+	struct tbl_row	*row;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_span)))) {
+		(void)tbl_err(tbl);
+		return(NULL);
+	}
+
+	TAILQ_INIT(&p->data);
+	TAILQ_INSERT_TAIL(&tbl->span, p, entries);
+
+	/* LINTED */
+	pp = TAILQ_PREV(p, tbl_spanh, entries);
+
+	if (pp) {
+		row = TAILQ_NEXT(pp->row, entries);
+		if (NULL == row)
+			row = pp->row;
+	} else {
+		row = TAILQ_FIRST(&tbl->row);
+	}
+
+	assert(row);
+	p->row = row;
+	p->tbl = tbl;
+	return(p);
+}
+
+
+struct tbl_row *
+tbl_row_alloc(struct tbl *tbl)
+{
+	struct tbl_row	*p;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_row)))) {
+		(void)tbl_err(tbl);
+		return(NULL);
+	}
+
+	TAILQ_INIT(&p->cell);
+	TAILQ_INSERT_TAIL(&tbl->row, p, entries);
+	p->tbl = tbl;
+	return(p);
+}
+
+
+static void
+headadj(const struct tbl_cell *cell, struct tbl_head *head)
+{
+	if (TBL_CELL_VERT != cell->pos &&
+			TBL_CELL_DVERT != cell->pos) {
+		head->pos = TBL_HEAD_DATA;
+		return;
+	}
+	if (TBL_CELL_VERT == cell->pos)
+		if (TBL_HEAD_DVERT != head->pos)
+			head->pos = TBL_HEAD_VERT;
+	if (TBL_CELL_DVERT == cell->pos)
+		head->pos = TBL_HEAD_DVERT;
+}
+
+
+static struct tbl_head *
+tbl_head_alloc(struct tbl *tbl)
+{
+	struct tbl_head	*p;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_head)))) {
+		(void)tbl_err(tbl);
+		return(NULL);
+	}
+	p->tbl = tbl;
+	return(p);
+}
+
+
+struct tbl_cell *
+tbl_cell_alloc(struct tbl_row *rp, enum tbl_cellt pos)
+{
+	struct tbl_cell	*p, *pp;
+	struct tbl_head	*h, *hp;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_cell)))) {
+		(void)tbl_err(rp->tbl);
+		return(NULL);
+	}
+
+	TAILQ_INSERT_TAIL(&rp->cell, p, entries);
+	p->pos = pos;
+	p->row = rp;
+
+	/*
+	 * This is a little bit complicated.  Here we determine the
+	 * header the corresponds to a cell.  We add headers dynamically
+	 * when need be or re-use them, otherwise.  As an example, given
+	 * the following:
+	 *
+	 * 	1  c || l 
+	 * 	2  | c | l
+	 * 	3  l l
+	 * 	3  || c | l |.
+	 *
+	 * We first add the new headers (as there are none) in (1); then
+	 * in (2) we insert the first spanner (as it doesn't match up
+	 * with the header); then we re-use the prior data headers,
+	 * skipping over the spanners; then we re-use everything and add
+	 * a last spanner.  Note that VERT headers are made into DVERT
+	 * ones.
+	 */
+
+	/* LINTED */
+	pp = TAILQ_PREV(p, tbl_cellh, entries);
+
+	h = pp ? TAILQ_NEXT(pp->head, entries) : 
+		TAILQ_FIRST(&rp->tbl->head);
+
+	if (h) {
+		/* Re-use data header. */
+		if (TBL_HEAD_DATA == h->pos && 
+				(TBL_CELL_VERT != p->pos &&
+				 TBL_CELL_DVERT != p->pos)) {
+			p->head = h;
+			return(p);
+		}
+
+		/* Re-use spanner header. */
+		if (TBL_HEAD_DATA != h->pos && 
+				(TBL_CELL_VERT == p->pos ||
+				 TBL_CELL_DVERT == p->pos)) {
+			headadj(p, h);
+			p->head = h;
+			return(p);
+		}
+
+		/* Right-shift headers with a new spanner. */
+		if (TBL_HEAD_DATA == h->pos && 
+				(TBL_CELL_VERT == p->pos ||
+				 TBL_CELL_DVERT == p->pos)) {
+			if (NULL == (hp = tbl_head_alloc(rp->tbl)))
+				return(NULL);
+			TAILQ_INSERT_BEFORE(h, hp, entries);
+			headadj(p, hp);
+			p->head = hp;
+			return(p);
+		}
+
+		h = TAILQ_NEXT(h, entries);
+		if (h) {
+			headadj(p, h);
+			p->head = h;
+			return(p);
+		}
+
+		/* Fall through to default case... */
+	}
+
+	if (NULL == (hp = tbl_head_alloc(rp->tbl)))
+		return(NULL);
+	TAILQ_INSERT_TAIL(&rp->tbl->head, hp, entries);
+	headadj(p, hp);
+	p->head = hp;
+	return(p);
+}
+
+
+struct tbl_data *
+tbl_data_alloc(struct tbl_span *sp)
+{
+	struct tbl_data	*p;
+	struct tbl_cell	*cp;
+	struct tbl_data	*dp;
+
+	if (NULL == (p = calloc(1, sizeof(struct tbl_data)))) {
+		(void)tbl_err(sp->row->tbl);
+		return(NULL);
+	}
+
+	cp = NULL;
+	/* LINTED */
+	if (NULL == (dp = TAILQ_LAST(&sp->data, tbl_datah)))
+		cp = TAILQ_FIRST(&sp->row->cell);
+	else if (dp->cell)
+		cp = TAILQ_NEXT(dp->cell, entries);
+
+	TAILQ_INSERT_TAIL(&sp->data, p, entries);
+
+	if (cp && (TBL_CELL_VERT == cp->pos || 
+				TBL_CELL_DVERT == cp->pos))
+		cp = TAILQ_NEXT(cp, entries);
+
+	p->span = sp;
+	p->cell = cp;
+	return(p);
+}
+
+
+static void
+tbl_clear(struct tbl *p)
+{
+	struct tbl_span	*span;
+	struct tbl_head	*head;
+	struct tbl_row	*row;
+
+	/* LINTED */
+	while ((span = TAILQ_FIRST(&p->span))) {
+		TAILQ_REMOVE(&p->span, span, entries);
+		tbl_span_free(span);
+	}
+	/* LINTED */
+	while ((row = TAILQ_FIRST(&p->row))) {
+		TAILQ_REMOVE(&p->row, row, entries);
+		tbl_row_free(row);
+	}
+	/* LINTED */
+	while ((head = TAILQ_FIRST(&p->head))) {
+		TAILQ_REMOVE(&p->head, head, entries);
+		free(head);
+	}
+}
+
+
+static void
+tbl_span_free(struct tbl_span *p)
+{
+	struct tbl_data	*data;
+
+	/* LINTED */
+	while ((data = TAILQ_FIRST(&p->data))) {
+		TAILQ_REMOVE(&p->data, data, entries);
+		tbl_data_free(data);
+	}
+	free(p);
+}
+
+
+static void
+tbl_data_free(struct tbl_data *p)
+{
+
+	if (p->string)
+		free(p->string);
+	free(p);
+}
+
+
+static void
+tbl_row_free(struct tbl_row *p)
+{
+	struct tbl_cell	*cell;
+
+	/* LINTED */
+	while ((cell = TAILQ_FIRST(&p->cell))) {
+		TAILQ_REMOVE(&p->cell, cell, entries);
+		free(cell);
+	}
+	free(p);
+}
Index: tbl.h
===================================================================
RCS file: tbl.h
diff -N tbl.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl.h	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,34 @@
+/*	$Id: tbl.h,v 1.3 2009/09/11 15:01:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is 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 TBL_H
+#define TBL_H
+
+__BEGIN_DECLS
+
+struct tbl;
+
+struct tbl	*tbl_alloc(void);
+void		 tbl_free(struct tbl *);
+void		 tbl_reset(struct tbl *);
+
+int	 	 tbl_read(struct tbl *, const char *, int, const char *, int);
+int		 tbl_close(struct tbl *, const char *, int);
+int		 tbl_write(struct termp *, const struct tbl *);
+
+__END_DECLS
+
+#endif /*TBL_H*/
Index: tbl_data.c
===================================================================
RCS file: tbl_data.c
diff -N tbl_data.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_data.c	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,132 @@
+/*	$Id: data.c,v 1.11 2009/09/12 16:05:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "term.h"
+#include "tbl_extern.h"
+
+/* FIXME: warn about losing data contents if cell is HORIZ. */
+
+static	int		data(struct tbl *, struct tbl_span *, 
+				const char *, int, int, 
+				const char *, int, int);
+
+
+int
+data(struct tbl *tbl, struct tbl_span *dp, 
+		const char *f, int ln, int pos, 
+		const char *p, int start, int end)
+{
+	struct tbl_data	*dat;
+
+	if (NULL == (dat = tbl_data_alloc(dp)))
+		return(0);
+
+	if (NULL == dat->cell)
+		if ( ! tbl_warnx(tbl, ERR_SYNTAX, f, ln, pos))
+			return(0);
+
+	assert(end >= start);
+	if (NULL == (dat->string = malloc((size_t)(end - start + 1))))
+		return(tbl_err(tbl));
+
+	(void)memcpy(dat->string, &p[start], (size_t)(end - start));
+	dat->string[end - start] = 0;
+
+	/* XXX: do the strcmps, then malloc(). */
+
+	if ( ! strcmp(dat->string, "_"))
+		dat->flags |= TBL_DATA_HORIZ;
+	else if ( ! strcmp(dat->string, "="))
+		dat->flags |= TBL_DATA_DHORIZ;
+	else if ( ! strcmp(dat->string, "\\_"))
+		dat->flags |= TBL_DATA_NHORIZ;
+	else if ( ! strcmp(dat->string, "\\="))
+		dat->flags |= TBL_DATA_NDHORIZ;
+	else
+		return(1);
+
+	free(dat->string);
+	dat->string = NULL;
+	return(1);
+}
+
+
+int
+tbl_data(struct tbl *tbl, const char *f, int ln, const char *p)
+{
+	struct tbl_span	*dp;
+	int		 i, j;
+
+	if (0 == p[0])
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+
+	if ('.' == p[0] && ! isdigit((u_char)p[1])) {
+		/*
+		 * XXX: departs from tbl convention in that we disallow
+		 * macros in the data body.
+		 */
+		if (strncasecmp(p, ".T&", 3)) 
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+		return(tbl_data_close(tbl, f, ln));
+	}
+
+	if (NULL == (dp = tbl_span_alloc(tbl)))
+		return(0);
+
+	if ( ! strcmp(p, "_")) {
+		dp->flags |= TBL_SPAN_HORIZ;
+		return(1);
+	} else if ( ! strcmp(p, "=")) {
+		dp->flags |= TBL_SPAN_DHORIZ;
+		return(1);
+	}
+
+	for (j = i = 0; p[i]; i++) {
+		if (p[i] != tbl->tab)
+			continue;
+		if ( ! data(tbl, dp, f, ln, i, p, j, i))
+			return(0);
+		j = i + 1;
+	}
+
+	return(data(tbl, dp, f, ln, i, p, j, i));
+}
+
+
+int
+tbl_data_close(struct tbl *tbl, const char *f, int ln)
+{
+	struct tbl_span	*span;
+
+	/* LINTED */
+	span = TAILQ_LAST(&tbl->span, tbl_spanh);
+	if (NULL == span)
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+	if (TAILQ_NEXT(span->row, entries))
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, 0));
+
+	tbl->part = TBL_PART_LAYOUT;
+	return(1);
+}
Index: tbl_extern.h
===================================================================
RCS file: tbl_extern.h
diff -N tbl_extern.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_extern.h	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,183 @@
+/*	$Id: extern.h,v 1.10 2009/09/13 12:37:28 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is 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 TBL_EXTERN_H
+#define TBL_EXTERN_H
+
+enum	tbl_err {
+	ERR_SYNTAX,
+	ERR_OPTION,
+	ERR_MAX
+};
+
+enum	tbl_tok {
+	TBL_TOK_WORD,
+	TBL_TOK_OPENPAREN,
+	TBL_TOK_CLOSEPAREN,
+	TBL_TOK_COMMA,
+	TBL_TOK_SEMICOLON,
+	TBL_TOK_PERIOD,
+	TBL_TOK_SPACE,
+	TBL_TOK_TAB,
+	TBL_TOK_NIL
+};
+
+enum	tbl_part {
+	TBL_PART_OPTS,
+	TBL_PART_LAYOUT,
+	TBL_PART_CLAYOUT,
+	TBL_PART_DATA,
+	TBL_PART_ERROR
+};
+
+struct	tbl;
+struct	tbl_head;
+struct	tbl_row;
+struct	tbl_cell;
+struct	tbl_span;
+struct	tbl_data;
+
+TAILQ_HEAD(tbl_rowh, tbl_row);
+TAILQ_HEAD(tbl_cellh, tbl_cell);
+TAILQ_HEAD(tbl_headh, tbl_head);
+TAILQ_HEAD(tbl_spanh, tbl_span);
+TAILQ_HEAD(tbl_datah, tbl_data);
+
+struct	tbl {
+	enum tbl_part	 	 part;
+	int		 	 opts;
+#define	TBL_OPT_CENTRE		(1 << 0)
+#define	TBL_OPT_EXPAND		(1 << 1)
+#define	TBL_OPT_BOX		(1 << 2)
+#define	TBL_OPT_DBOX		(1 << 3)
+#define	TBL_OPT_ALLBOX		(1 << 4)
+#define	TBL_OPT_NOKEEP		(1 << 5)
+#define	TBL_OPT_NOSPACE		(1 << 6)
+	char		 	 tab;
+	char		 	 decimal;
+	int		 	 linesize;
+	char		 	 delims[2];
+	struct tbl_spanh	 span;
+	struct tbl_headh	 head;
+	struct tbl_rowh		 row;
+};
+
+enum	tbl_headt {
+	TBL_HEAD_DATA,
+	TBL_HEAD_VERT,
+	TBL_HEAD_DVERT,
+	TBL_HEAD_MAX
+};
+
+struct	tbl_head {
+	struct tbl		*tbl;
+	enum tbl_headt	 	 pos;
+	int			 width;
+	int			 decimal;
+	TAILQ_ENTRY(tbl_head)	 entries;
+};
+
+struct	tbl_row {
+	struct tbl		*tbl;
+	struct tbl_cellh	 cell;
+	TAILQ_ENTRY(tbl_row)	 entries;
+};
+
+enum	tbl_cellt {
+	TBL_CELL_CENTRE,	/* c, C */
+	TBL_CELL_RIGHT,		/* r, R */
+	TBL_CELL_LEFT,		/* l, L */
+	TBL_CELL_NUMBER,	/* n, N */
+	TBL_CELL_SPAN,		/* s, S */
+	TBL_CELL_LONG,		/* a, A */
+	TBL_CELL_DOWN,		/* ^ */
+	TBL_CELL_HORIZ,		/* _, - */
+	TBL_CELL_DHORIZ,	/* = */
+	TBL_CELL_VERT,		/* | */
+	TBL_CELL_DVERT,		/* || */
+	TBL_CELL_MAX
+};
+
+struct	tbl_cell {
+	struct tbl_row		*row;
+	struct tbl_head		*head;
+	enum tbl_cellt	 	 pos;
+	int		  	 spacing;
+	int		 	 flags;
+#define	TBL_CELL_TALIGN		(1 << 0)	/* t, T */
+#define	TBL_CELL_BALIGN		(1 << 1)	/* d, D */
+#define	TBL_CELL_BOLD		(1 << 2)	/* fB, B, b */
+#define	TBL_CELL_ITALIC		(1 << 3)	/* fI, I, i */
+#define	TBL_CELL_EQUAL		(1 << 4)	/* e, E */
+#define	TBL_CELL_UP		(1 << 5)	/* u, U */
+#define	TBL_CELL_WIGN		(1 << 6)	/* z, Z */
+	TAILQ_ENTRY(tbl_cell)	 entries;
+};
+
+struct	tbl_data {
+	struct tbl_span		*span;
+	struct tbl_cell		*cell;
+	int		 	 flags;
+#define	TBL_DATA_HORIZ		(1 << 0)
+#define	TBL_DATA_DHORIZ		(1 << 1)
+#define	TBL_DATA_NHORIZ		(1 << 2)
+#define	TBL_DATA_NDHORIZ 	(1 << 3)
+	char			*string;
+	TAILQ_ENTRY(tbl_data)	 entries;
+};
+
+struct	tbl_span {
+	struct tbl_row		*row;
+	struct tbl		*tbl;
+	int		 	 flags;
+#define	TBL_SPAN_HORIZ		(1 << 0)
+#define	TBL_SPAN_DHORIZ		(1 << 1)
+	struct tbl_datah	 data;
+	TAILQ_ENTRY(tbl_span)	 entries;
+};
+
+__BEGIN_DECLS
+
+int	 	 tbl_option(struct tbl *, 
+			const char *, int, const char *);
+int	 	 tbl_layout(struct tbl *, 
+			const char *, int, const char *);
+int	 	 tbl_data(struct tbl *, 
+			const char *, int, const char *);
+int		 tbl_data_close(struct tbl *,  const char *, int);
+
+enum tbl_tok	 tbl_next(const char *, int *);
+const char	*tbl_last(void);
+int		 tbl_last_uint(void);
+int		 tbl_errx(struct tbl *, enum tbl_err, 
+			const char *, int, int);
+int		 tbl_warnx(struct tbl *, enum tbl_err, 
+			const char *, int, int);
+int		 tbl_err(struct tbl *);
+
+struct tbl_row	*tbl_row_alloc(struct tbl *);
+struct tbl_cell	*tbl_cell_alloc(struct tbl_row *, enum tbl_cellt);
+struct tbl_span	*tbl_span_alloc(struct tbl *);
+struct tbl_data	*tbl_data_alloc(struct tbl_span *);
+
+int		 tbl_write_term(struct termp *, const struct tbl *);
+int		 tbl_calc_term(struct tbl *);
+int		 tbl_write_tree(const struct tbl *);
+int		 tbl_calc_tree(struct tbl *);
+
+__END_DECLS
+
+#endif /*TBL_EXTERN_H*/
Index: tbl_layout.c
===================================================================
RCS file: tbl_layout.c
diff -N tbl_layout.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_layout.c	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,282 @@
+/*	$Id: layout.c,v 1.7 2009/09/11 13:24:04 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "term.h"
+#include "tbl_extern.h"
+
+struct	tbl_phrase {
+	char		 name;
+	enum tbl_cellt	 key;
+};
+
+#define	KEYS_MAX	 17
+
+static	const struct tbl_phrase keys[KEYS_MAX] = {
+	{ 'c',		 TBL_CELL_CENTRE },
+	{ 'C',		 TBL_CELL_CENTRE },
+	{ 'r',		 TBL_CELL_RIGHT },
+	{ 'R',		 TBL_CELL_RIGHT },
+	{ 'l',		 TBL_CELL_LEFT },
+	{ 'L',		 TBL_CELL_LEFT },
+	{ 'n',		 TBL_CELL_NUMBER },
+	{ 'N',		 TBL_CELL_NUMBER },
+	{ 's',		 TBL_CELL_SPAN },
+	{ 'S',		 TBL_CELL_SPAN },
+	{ 'a',		 TBL_CELL_LONG },
+	{ 'A',		 TBL_CELL_LONG },
+	{ '^',		 TBL_CELL_DOWN },
+	{ '-',		 TBL_CELL_HORIZ },
+	{ '_',		 TBL_CELL_HORIZ },
+	{ '=',		 TBL_CELL_DHORIZ },
+	{ '|',		 TBL_CELL_VERT }
+};
+
+static	int		mods(struct tbl *, struct tbl_cell *, 
+				const char *, int, 
+				const char *, int, int);
+static	int		cell(struct tbl *, struct tbl_row *, 
+				const char *, int, int);
+static	int		row(struct tbl *, const char *,
+				int, const char *, int *);
+
+
+static int
+mods(struct tbl *tbl, struct tbl_cell *cp, const char *p, 
+		int pp, const char *f, int ln, int pos)
+{
+	char		 buf[5];
+	int		 i;
+
+	/* 
+	 * XXX: since, at least for now, modifiers are non-conflicting
+	 * (are separable by value, regardless of position), we let
+	 * modifiers come in any order.  The existing tbl doesn't let
+	 * this happen.
+	 */
+
+	if (0 == p[pp])
+		return(1);
+
+	/* Parse numerical spacing from modifier string. */
+
+	if (isdigit((u_char)p[pp])) {
+		for (i = 0; i < 4; i++) {
+			if ( ! isdigit((u_char)p[pp + i]))
+				break;
+			buf[i] = p[pp + i];
+		}
+		buf[i] = 0;
+
+		/* No greater than 4 digits. */
+
+		if (4 == i)
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos + pp));
+
+		/* 
+		 * We can't change the spacing in any subsequent layout
+		 * definitions.  FIXME: I don't think we can change the
+		 * spacing for a column at all, after it's already been
+		 * initialised.
+		 */
+
+		if (TBL_PART_CLAYOUT != tbl->part)
+			cp->spacing = atoi(buf);
+		else if ( ! tbl_warnx(tbl, ERR_SYNTAX, f, ln, pos + pp))
+			return(0);
+		
+		/* Continue parsing modifiers. */
+
+		return(mods(tbl, cp, p, pp + i, f, ln, pos));
+	} 
+
+	/* TODO: GNU has many more extensions. */
+
+	switch (p[pp]) {
+	case ('z'):
+		/* FALLTHROUGH */
+	case ('Z'):
+		cp->flags |= TBL_CELL_WIGN;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('u'):
+		/* FALLTHROUGH */
+	case ('U'):
+		cp->flags |= TBL_CELL_UP;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('e'):
+		/* FALLTHROUGH */
+	case ('E'):
+		cp->flags |= TBL_CELL_EQUAL;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('t'):
+		/* FALLTHROUGH */
+	case ('T'):
+		cp->flags |= TBL_CELL_TALIGN;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('d'):
+		/* FALLTHROUGH */
+	case ('D'):
+		cp->flags |= TBL_CELL_BALIGN;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('f'):
+		pp++;
+		/* FALLTHROUGH */
+	case ('B'):
+		/* FALLTHROUGH */
+	case ('I'):
+		/* FALLTHROUGH */
+	case ('b'):
+		/* FALLTHROUGH */
+	case ('i'):
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos + pp));
+	}
+
+	switch (p[pp]) {
+	case ('b'):
+		/* FALLTHROUGH */
+	case ('B'):
+		cp->flags |= TBL_CELL_BOLD;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	case ('i'):
+		/* FALLTHROUGH */
+	case ('I'):
+		cp->flags |= TBL_CELL_ITALIC;
+		return(mods(tbl, cp, p, pp + 1, f, ln, pos));
+	default:
+		break;
+	}
+
+	return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos + pp));
+}
+
+
+static int
+cell(struct tbl *tbl, struct tbl_row *rp, 
+		const char *f, int ln, int pos)
+{
+	struct tbl_cell	*cp;
+	const char	*p;
+	int		 j, i;
+	enum tbl_cellt	 c;
+
+	/* Parse the column position (`r', `R', `|', ...). */
+
+	c = TBL_CELL_MAX;
+	for (p = tbl_last(), i = 0; i < KEYS_MAX; i++) {
+		if (keys[i].name != p[0])
+			continue;
+		c = keys[i].key;
+		break;
+	}
+
+	if (i == KEYS_MAX)
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos));
+
+	/* Extra check for the double-vertical. */
+
+	if (TBL_CELL_VERT == c && '|' == p[1]) {
+		j = 2;
+		c = TBL_CELL_DVERT;
+	} else
+		j = 1;
+	
+	/* Disallow subsequent spacers. */
+
+	/* LINTED */
+	cp = TAILQ_LAST(&rp->cell, tbl_cellh);
+
+	if (cp && (TBL_CELL_VERT == c || TBL_CELL_DVERT == c) && 
+			(TBL_CELL_VERT == cp->pos || 
+			 TBL_CELL_DVERT == cp->pos))
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, pos));
+
+	/* Allocate cell then parse its modifiers. */
+
+	if (NULL == (cp = tbl_cell_alloc(rp, c)))
+		return(0);
+	return(mods(tbl, cp, p, j, f, ln, pos));
+}
+
+
+static int
+row(struct tbl *tbl, const char *f, int ln,
+		const char *p, int *pos)
+{
+	struct tbl_row	*rp;
+	int		 sv;
+
+	rp = tbl_row_alloc(tbl);
+again:
+	sv = *pos;
+
+	/*
+	 * EBNF describing this section:
+	 *
+	 * row		::= row_list [:space:]* [.]?[\n]
+	 * row_list	::= [:space:]* row_elem row_tail
+	 * row_tail	::= [:space:]*[,] row_list |
+	 *                  epsilon
+	 * row_elem	::= [\t\ ]*[:alpha:]+
+	 */
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_TAB):
+		/* FALLTHROUGH */
+	case (TBL_TOK_SPACE):
+		goto again;
+	case (TBL_TOK_WORD):
+		if ( ! cell(tbl, rp, f, ln, sv))
+			return(0);
+		goto again;
+	case (TBL_TOK_COMMA):
+		if (NULL == TAILQ_FIRST(&rp->cell))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		return(row(tbl, f, ln, p, pos));
+	case (TBL_TOK_PERIOD):
+		if (NULL == TAILQ_FIRST(&rp->cell))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->part = TBL_PART_DATA;
+		break;
+	case (TBL_TOK_NIL):
+		if (NULL == TAILQ_FIRST(&rp->cell))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	return(1);
+}
+
+
+int
+tbl_layout(struct tbl *tbl, const char *f, int ln, const char *p)
+{
+	int		 pos;
+
+	pos = 0;
+	return(row(tbl, f, ln, p, &pos));
+}
Index: tbl_option.c
===================================================================
RCS file: tbl_option.c
diff -N tbl_option.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_option.c	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,197 @@
+/*	$Id: option.c,v 1.5 2009/09/09 12:51:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "term.h"
+#include "tbl_extern.h"
+
+struct	tbl_phrase {
+	char		*name;
+	int		 key;
+	int		 ident;
+#define	KEY_CENTRE	 0
+#define	KEY_DELIM	 1
+#define	KEY_EXPAND	 2
+#define	KEY_BOX		 3
+#define	KEY_DBOX	 4
+#define	KEY_ALLBOX	 5
+#define	KEY_TAB		 6
+#define	KEY_LINESIZE	 7
+#define	KEY_NOKEEP	 8
+#define	KEY_DPOINT	 9
+#define	KEY_NOSPACE	 10
+#define	KEY_FRAME	 11
+#define	KEY_DFRAME	 12
+};
+
+#define	KEY_MAXKEYS	 14
+
+static	const struct tbl_phrase keys[KEY_MAXKEYS] = {
+	{ "center",	 TBL_OPT_CENTRE,	KEY_CENTRE},
+	{ "centre",	 TBL_OPT_CENTRE,	KEY_CENTRE},
+	{ "delim",	 0,	       		KEY_DELIM},
+	{ "expand",	 TBL_OPT_EXPAND,	KEY_EXPAND},
+	{ "box",	 TBL_OPT_BOX,   	KEY_BOX},
+	{ "doublebox",	 TBL_OPT_DBOX,  	KEY_DBOX},
+	{ "allbox",	 TBL_OPT_ALLBOX,	KEY_ALLBOX},
+	{ "frame",	 TBL_OPT_BOX,		KEY_FRAME},
+	{ "doubleframe", TBL_OPT_DBOX,		KEY_DFRAME},
+	{ "tab",	 0,			KEY_TAB},
+	{ "linesize",	 0,			KEY_LINESIZE},
+	{ "nokeep",	 TBL_OPT_NOKEEP,	KEY_NOKEEP},
+	{ "decimalpoint", 0,			KEY_DPOINT},
+	{ "nospaces",	 TBL_OPT_NOSPACE,	KEY_NOSPACE},
+};
+
+static	int		 arg(struct tbl *, const char *, 
+				int, const char *, int *, int);
+static	int		 opt(struct tbl *, const char *, 
+				int, const char *, int *);
+
+static int
+arg(struct tbl *tbl, const char *f, int ln,
+		const char *p, int *pos, int key)
+{
+	const char	*buf;
+	int		 sv;
+
+again:
+	sv = *pos;
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_OPENPAREN):
+		break;
+	case (TBL_TOK_SPACE):
+		/* FALLTHROUGH */
+	case (TBL_TOK_TAB):
+		goto again;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	sv = *pos;
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_WORD):
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	buf = tbl_last();
+
+	switch (key) {
+	case (KEY_DELIM):
+		if (2 != strlen(buf))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->delims[0] = buf[0];
+		tbl->delims[1] = buf[1];
+		break;
+	case (KEY_TAB):
+		if (1 != strlen(buf))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->tab = buf[0];
+		break;
+	case (KEY_LINESIZE):
+		if (-1 == (tbl->linesize = tbl_last_uint()))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		break;
+	case (KEY_DPOINT):
+		if (1 != strlen(buf))
+			return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+		tbl->decimal = buf[0];
+		break;
+	default:
+		abort();
+	}
+
+	sv = *pos;
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_CLOSEPAREN):
+		break;
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	return(1);
+}
+
+
+static int
+opt(struct tbl *tbl, const char *f, int ln, const char *p, int *pos)
+{
+	int		 i, sv;
+
+again:
+	sv = *pos;
+
+	/*
+	 * EBNF describing this section:
+	 *
+	 * options	::= option_list [:space:]* [;][\n]
+	 * option_list	::= option option_tail
+	 * option_tail	::= [:space:]+ option_list |
+	 * 		::= epsilon
+	 * option	::= [:alpha:]+ args
+	 * args		::= [:space:]* [(] [:alpha:]+ [)]
+	 */
+
+	switch (tbl_next(p, pos)) {
+	case (TBL_TOK_WORD):
+		break;
+	case (TBL_TOK_SPACE):
+		/* FALLTHROUGH */
+	case (TBL_TOK_TAB):
+		goto again;
+	case (TBL_TOK_SEMICOLON):
+		tbl->part = TBL_PART_LAYOUT;
+		return(1);
+	default:
+		return(tbl_errx(tbl, ERR_SYNTAX, f, ln, sv));
+	}
+
+	for (i = 0; i < KEY_MAXKEYS; i++) {
+		if (strcasecmp(tbl_last(), keys[i].name))
+			continue;
+		if (keys[i].key) 
+			tbl->opts |= keys[i].key;
+		else if ( ! arg(tbl, f, ln, p, pos, keys[i].ident))
+			return(0);
+
+		break;
+	}
+
+	if (KEY_MAXKEYS == i)
+		return(tbl_errx(tbl, ERR_OPTION, f, ln, sv));
+
+	return(opt(tbl, f, ln, p, pos));
+}
+
+
+int
+tbl_option(struct tbl *tbl, const char *f, int ln, const char *p)
+{
+	int		 pos;
+
+	pos = 0;
+	return(opt(tbl, f, ln, p, &pos));
+}
Index: tbl_term.c
===================================================================
RCS file: tbl_term.c
diff -N tbl_term.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_term.c	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,518 @@
+/*	$Id: term.c,v 1.13 2009/09/14 09:06:40 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "term.h"
+#include "tbl_extern.h"
+
+/* FIXME: `n' modifier doesn't always do the right thing. */
+/* FIXME: `n' modifier doesn't use the cell-spacing buffer. */
+
+static	void		 calc_data(struct tbl_data *);
+static	void		 calc_data_literal(struct tbl_data *);
+static	void		 calc_data_number(struct tbl_data *);
+static	void		 calc_data_spanner(struct tbl_data *);
+static	inline void	 write_char(struct termp *, char, int);
+static	void		 write_data(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_data_literal(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_data_number(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_data_spanner(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_hframe(struct termp *, const struct tbl *);
+static	void		 write_hrule(struct termp *, const struct tbl_span *);
+static	void		 write_spanner(struct termp *, const struct tbl_head *);
+static	void		 write_vframe(struct termp *, const struct tbl *);
+
+
+int
+tbl_write_term(struct termp *p, const struct tbl *tbl)
+{
+	const struct tbl_span	*span;
+	const struct tbl_data	*data;
+	const struct tbl_head	*head;
+
+	/*
+	 * Note that the absolute widths and decimal places for headers
+	 * were set when tbl_calc_term was called.
+	 */
+
+	p->flags |= TERMP_NONOSPACE;
+
+	/* First, write out our head horizontal frame. */
+
+	write_hframe(p, tbl);
+
+	/*
+	 * Iterate through each span, and inside, through the global
+	 * headers.  If the global header's a spanner, print it
+	 * directly; if it's data, use the corresponding data in the
+	 * span as the object to print.
+	 */
+
+	TAILQ_FOREACH(span, &tbl->span, entries) {
+		write_vframe(p, tbl);
+
+		/* Accomodate for the horizontal rule. */
+		if (TBL_DATA_DHORIZ & span->flags || 
+				TBL_DATA_HORIZ & span->flags) {
+			write_hrule(p, span);
+			write_vframe(p, tbl);
+			term_flushln(p);
+			continue;
+		}
+
+		data = TAILQ_FIRST(&span->data);
+		TAILQ_FOREACH(head, &tbl->head, entries) {
+			switch (head->pos) {
+			case (TBL_HEAD_VERT):
+				/* FALLTHROUGH */
+			case (TBL_HEAD_DVERT):
+				write_spanner(p, head);
+				break;
+			case (TBL_HEAD_DATA):
+				write_data(p, data, head->width);
+				if (data)
+					data = TAILQ_NEXT(data, entries);
+				break;
+			default:
+				abort();
+				/* NOTREACHED */
+			}
+		}
+		write_vframe(p, tbl);
+		term_flushln(p);
+	}
+
+	/* Last, write out our tail horizontal frame. */
+
+	write_hframe(p, tbl);
+
+	p->flags &= ~TERMP_NONOSPACE;
+
+	return(1);
+}
+
+
+int
+tbl_calc_term(struct tbl *tbl)
+{
+	struct tbl_span	*span;
+	struct tbl_data	*data;
+	struct tbl_head	*head;
+
+	/* Calculate width as the max of column cells' widths. */
+
+	TAILQ_FOREACH(span, &tbl->span, entries) {
+		if (TBL_DATA_HORIZ & span->flags)
+			continue;
+		if (TBL_DATA_DHORIZ & span->flags)
+			continue;
+		if (TBL_DATA_NHORIZ & span->flags)
+			continue;
+		if (TBL_DATA_NDHORIZ & span->flags)
+			continue;
+		TAILQ_FOREACH(data, &span->data, entries)
+			calc_data(data);
+	}
+
+	/* Calculate width as the simple spanner value. */
+
+	TAILQ_FOREACH(head, &tbl->head, entries) 
+		switch (head->pos) {
+		case (TBL_HEAD_VERT):
+			head->width = 1;
+			break;
+		case (TBL_HEAD_DVERT):
+			head->width = 2;
+			break;
+		default:
+			break;
+		}
+
+	return(1);
+}
+
+
+static void
+write_hrule(struct termp *p, const struct tbl_span *span)
+{
+	const struct tbl_head	*head;
+	char			 c;
+
+	/*
+	 * An hrule extends across the entire table and is demarked by a
+	 * standalone `_' or whatnot in lieu of a table row.  Spanning
+	 * headers are marked by a `+', as are table boundaries.
+	 */
+
+	c = '-';
+	if (TBL_SPAN_DHORIZ & span->flags)
+		c = '=';
+
+	/* FIXME: don't use `+' between data and a spanner! */
+
+	TAILQ_FOREACH(head, &span->tbl->head, entries) {
+		switch (head->pos) {
+		case (TBL_HEAD_DATA):
+			write_char(p, c, head->width);
+			break;
+		case (TBL_HEAD_DVERT):
+			write_char(p, '+', head->width);
+			/* FALLTHROUGH */
+		case (TBL_HEAD_VERT):
+			write_char(p, '+', head->width);
+			break;
+		default:
+			abort();
+			/* NOTREACHED */
+		}
+	}
+}
+
+
+static void
+write_hframe(struct termp *p, const struct tbl *tbl)
+{
+	const struct tbl_head	*head;
+
+	if ( ! (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts))
+		return;
+
+	/* 
+	 * Print out the horizontal part of a frame or double frame.  A
+	 * double frame has an unbroken `-' outer line the width of the
+	 * table, bordered by `+'.  The frame (or inner frame, in the
+	 * case of the double frame) is a `-' bordered by `+' and broken
+	 * by `+' whenever a span is encountered.
+	 */
+
+	if (TBL_OPT_DBOX & tbl->opts) {
+		term_word(p, "+");
+		TAILQ_FOREACH(head, &tbl->head, entries)
+			write_char(p, '-', head->width);
+		term_word(p, "+");
+		term_flushln(p);
+	}
+
+	term_word(p, "+");
+	TAILQ_FOREACH(head, &tbl->head, entries) {
+		switch (head->pos) {
+		case (TBL_HEAD_DATA):
+			write_char(p, '-', head->width);
+			break;
+		default:
+			write_char(p, '+', head->width);
+			break;
+		}
+	}
+	term_word(p, "+");
+	term_flushln(p);
+}
+
+
+static void
+write_vframe(struct termp *p, const struct tbl *tbl)
+{
+	/* Always just a single vertical line. */
+
+	if ( ! (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts))
+		return;
+	term_word(p, "|");
+}
+
+
+static void
+calc_data_spanner(struct tbl_data *data)
+{
+
+	/* N.B., these are horiz spanners (not vert) so always 1. */
+	data->cell->head->width = 1;
+}
+
+
+static void
+calc_data_number(struct tbl_data *data)
+{
+	int 		 sz, d;
+	char		*dp, pnt;
+
+	/*
+	 * First calculate number width and decimal place (last + 1 for
+	 * no-decimal numbers).  If the stored decimal is subsequent
+	 * ours, make our size longer by that difference
+	 * (right-"shifting"); similarly, if ours is subsequent the
+	 * stored, then extend the stored size by the difference.
+	 * Finally, re-assign the stored values.
+	 */
+
+	/* TODO: use spacing modifier. */
+
+	assert(data->string);
+	sz = (int)strlen(data->string);
+	pnt = data->span->tbl->decimal;
+
+	if (NULL == (dp = strchr(data->string, pnt)))
+		d = sz + 1;
+	else
+		d = (int)(dp - data->string) + 1;
+
+	sz += 2;
+
+	if (data->cell->head->decimal > d) {
+		sz += data->cell->head->decimal - d;
+		d = data->cell->head->decimal;
+	} else
+		data->cell->head->width += 
+			d - data->cell->head->decimal;
+
+	if (sz > data->cell->head->width)
+		data->cell->head->width = sz;
+	if (d > data->cell->head->decimal)
+		data->cell->head->decimal = d;
+}
+
+
+static void
+calc_data_literal(struct tbl_data *data)
+{
+	int		 sz, bufsz;
+
+	/* 
+	 * Calculate our width and use the spacing, with a minimum
+	 * spacing dictated by position (centre, e.g,. gets a space on
+	 * either side, while right/left get a single adjacent space).
+	 */
+
+	assert(data->string);
+	sz = (int)strlen(data->string);
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		bufsz = 2;
+		break;
+	default:
+		bufsz = 1;
+		break;
+	}
+
+	if (data->cell->spacing)
+		bufsz = bufsz > data->cell->spacing ? 
+			bufsz : data->cell->spacing;
+
+	sz += bufsz;
+	if (data->cell->head->width < sz)
+		data->cell->head->width = sz;
+}
+
+
+static void
+calc_data(struct tbl_data *data)
+{
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_HORIZ):
+		/* FALLTHROUGH */
+	case (TBL_CELL_DHORIZ):
+		calc_data_spanner(data);
+		break;
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		/* FALLTHROUGH */
+	case (TBL_CELL_LEFT):
+		/* FALLTHROUGH */
+	case (TBL_CELL_RIGHT):
+		calc_data_literal(data);
+		break;
+	case (TBL_CELL_NUMBER):
+		calc_data_number(data);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+
+static void
+write_data_spanner(struct termp *p, const struct tbl_data *data, int width)
+{
+
+	/*
+	 * Write spanners dictated by both our cell designation (in the
+	 * layout) or as data.
+	 */
+	if (TBL_DATA_HORIZ & data->flags)
+		write_char(p, '-', width);
+	else if (TBL_DATA_DHORIZ & data->flags)
+		write_char(p, '=', width);
+	else if (TBL_CELL_HORIZ == data->cell->pos)
+		write_char(p, '-', width);
+	else if (TBL_CELL_DHORIZ == data->cell->pos)
+		write_char(p, '=', width);
+}
+
+
+static void
+write_data_number(struct termp *p, const struct tbl_data *data, int width)
+{
+	char		*dp, pnt;
+	int		 d, padl, sz;
+
+	/*
+	 * See calc_data_number().  Left-pad by taking the offset of our
+	 * and the maximum decimal; right-pad by the remaining amount.
+	 */
+
+	sz = (int)term_strlen(p, data->string);
+	pnt = data->span->tbl->decimal;
+
+	if (NULL == (dp = strchr(data->string, pnt))) {
+		d = sz + 1;
+	} else {
+		d = (int)(dp - data->string) + 1;
+	}
+
+	assert(d <= data->cell->head->decimal);
+	assert(sz - d <= data->cell->head->width -
+			data->cell->head->decimal);
+
+	padl = data->cell->head->decimal - d + 1;
+	assert(width - sz - padl);
+
+	write_char(p, ' ', padl);
+	term_word(p, data->string);
+	write_char(p, ' ', width - sz - padl);
+}
+
+
+static void
+write_data_literal(struct termp *p, const struct tbl_data *data, int width)
+{
+	int		 padl, padr;
+
+	padl = padr = 0;
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_LONG):
+		padl = 1;
+		padr = width - (int)term_strlen(p, data->string) - 1;
+		break;
+	case (TBL_CELL_CENTRE):
+		padl = width - (int)term_strlen(p, data->string);
+		if (padl % 2)
+			padr++;
+		padl /= 2;
+		padr += padl;
+		break;
+	case (TBL_CELL_RIGHT):
+		padl = width - (int)term_strlen(p, data->string);
+		break;
+	default:
+		padr = width - (int)term_strlen(p, data->string);
+		break;
+	}
+
+	write_char(p, ' ', padl);
+	term_word(p, data->string);
+	write_char(p, ' ', padr);
+}
+
+
+static void
+write_data(struct termp *p, const struct tbl_data *data, int width)
+{
+
+	if (NULL == data) {
+		write_char(p, ' ', width);
+		return;
+	}
+
+	if (TBL_DATA_HORIZ & data->flags || 
+			TBL_DATA_DHORIZ & data->flags) {
+		write_data_spanner(p, data, width);
+		return;
+	}
+
+	switch (data->cell->pos) {
+	case (TBL_CELL_HORIZ):
+		/* FALLTHROUGH */
+	case (TBL_CELL_DHORIZ):
+		write_data_spanner(p, data, width);
+		break;
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		/* FALLTHROUGH */
+	case (TBL_CELL_LEFT):
+		/* FALLTHROUGH */
+	case (TBL_CELL_RIGHT):
+		write_data_literal(p, data, width);
+		break;
+	case (TBL_CELL_NUMBER):
+		write_data_number(p, data, width);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+
+static void
+write_spanner(struct termp *p, const struct tbl_head *head)
+{
+	char		*w;
+
+	w = NULL;
+	switch (head->pos) {
+	case (TBL_HEAD_VERT):
+		w = "|";
+		break;
+	case (TBL_HEAD_DVERT):
+		w = "||";
+		break;
+	default:
+		break;
+	}
+
+	assert(p);
+	term_word(p, w);
+}
+
+
+static inline void
+write_char(struct termp *p, char c, int len)
+{
+	int		 i;
+	static char	 w[2];
+
+	w[0] = c;
+	for (i = 0; i < len; i++)
+		term_word(p, w);
+}
Index: tbl_tree.c
===================================================================
RCS file: tbl_tree.c
diff -N tbl_tree.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tbl_tree.c	14 Oct 2010 21:03:06 -0000
@@ -0,0 +1,88 @@
+/*	$Id: tree.c,v 1.2 2009/09/11 13:24:04 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "out.h"
+#include "term.h"
+#include "tbl_extern.h"
+
+static	const char * const htypes[TBL_HEAD_MAX] = {
+	"data",
+	"vert",
+	"dvert",
+};
+
+static	const char * const ctypes[TBL_CELL_MAX] = {
+	"centre",
+	"right",
+	"left",
+	"number",
+	"span",
+	"long",
+	"down",
+	"horiz",
+	"dhoriz",
+	"vert",
+	"dvert",
+};
+
+
+/* ARGSUSED */
+int
+tbl_calc_tree(struct tbl *tbl)
+{
+
+	return(1);
+}
+
+
+int
+tbl_write_tree(const struct tbl *tbl)
+{
+	struct tbl_row	*row;
+	struct tbl_cell	*cell;
+	struct tbl_span	*span;
+	struct tbl_data	*data;
+	struct tbl_head	*head;
+
+	(void)printf("header\n");
+	TAILQ_FOREACH(head, &tbl->head, entries)
+		(void)printf("\t%s (=%p)\n", htypes[head->pos], head);
+
+	(void)printf("layout\n");
+	TAILQ_FOREACH(row, &tbl->row, entries) {
+		(void)printf("\trow (=%p)\n", row);
+		TAILQ_FOREACH(cell, &row->cell, entries)
+			(void)printf("\t\t%s (=%p) >%p\n", 
+					ctypes[cell->pos], 
+					cell, cell->head);
+	}
+
+	(void)printf("data\n");
+	TAILQ_FOREACH(span, &tbl->span, entries) {
+		(void)printf("\tspan >%p\n", span->row);
+		TAILQ_FOREACH(data, &span->data, entries)
+			(void)printf("\t\tdata >%p\n", data->cell);
+	}
+
+	return(1);
+}

[-- Attachment #2: tmp.patch --]
[-- Type: text/plain, Size: 12134 bytes --]

diff -Naur mandoc-tbl-minglue/man_term.c mandoc-tbl-term/man_term.c
--- mandoc-tbl-minglue/man_term.c	Thu Oct 14 22:46:41 2010
+++ mandoc-tbl-term/man_term.c	Thu Oct 14 22:49:10 2010
@@ -841,7 +841,7 @@
 	if ( ! tbl_close(n->data.TS, "<man>", n->line))
 		return(0);
 
-	tbl_write(n->data.TS);
+	tbl_write(p, n->data.TS);
 
 	return(0);
 }
diff -Naur mandoc-tbl-minglue/tbl.c mandoc-tbl-term/tbl.c
--- mandoc-tbl-minglue/tbl.c	Wed Oct 13 00:29:08 2010
+++ mandoc-tbl-term/tbl.c	Thu Oct 14 22:50:44 2010
@@ -23,6 +23,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "out.h"
+#include "term.h"
 #include "tbl.h"
 #include "tbl_extern.h"
 
@@ -104,11 +106,11 @@
 
 
 int
-tbl_write(const struct tbl *tbl)
+tbl_write(struct termp *p, const struct tbl *tbl)
 {
 
 #if 1
-	return(tbl_write_term(tbl));
+	return(tbl_write_term(p, tbl));
 #else
 	return(tbl_write_tree(tbl));
 #endif
diff -Naur mandoc-tbl-minglue/tbl.h mandoc-tbl-term/tbl.h
--- mandoc-tbl-minglue/tbl.h	Mon Sep 14 12:31:24 2009
+++ mandoc-tbl-term/tbl.h	Thu Oct 14 22:50:54 2010
@@ -27,7 +27,7 @@
 
 int	 	 tbl_read(struct tbl *, const char *, int, const char *, int);
 int		 tbl_close(struct tbl *, const char *, int);
-int		 tbl_write(const struct tbl *);
+int		 tbl_write(struct termp *, const struct tbl *);
 
 __END_DECLS
 
diff -Naur mandoc-tbl-minglue/tbl_data.c mandoc-tbl-term/tbl_data.c
--- mandoc-tbl-minglue/tbl_data.c	Wed Oct 13 00:29:23 2010
+++ mandoc-tbl-term/tbl_data.c	Thu Oct 14 23:01:09 2010
@@ -22,6 +22,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "out.h"
+#include "term.h"
 #include "tbl_extern.h"
 
 /* FIXME: warn about losing data contents if cell is HORIZ. */
diff -Naur mandoc-tbl-minglue/tbl_extern.h mandoc-tbl-term/tbl_extern.h
--- mandoc-tbl-minglue/tbl_extern.h	Wed Oct 13 00:29:50 2010
+++ mandoc-tbl-term/tbl_extern.h	Thu Oct 14 23:00:42 2010
@@ -173,7 +173,7 @@
 struct tbl_span	*tbl_span_alloc(struct tbl *);
 struct tbl_data	*tbl_data_alloc(struct tbl_span *);
 
-int		 tbl_write_term(const struct tbl *);
+int		 tbl_write_term(struct termp *, const struct tbl *);
 int		 tbl_calc_term(struct tbl *);
 int		 tbl_write_tree(const struct tbl *);
 int		 tbl_calc_tree(struct tbl *);
diff -Naur mandoc-tbl-minglue/tbl_layout.c mandoc-tbl-term/tbl_layout.c
--- mandoc-tbl-minglue/tbl_layout.c	Wed Oct 13 00:30:00 2010
+++ mandoc-tbl-term/tbl_layout.c	Thu Oct 14 23:01:22 2010
@@ -22,6 +22,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "out.h"
+#include "term.h"
 #include "tbl_extern.h"
 
 struct	tbl_phrase {
diff -Naur mandoc-tbl-minglue/tbl_option.c mandoc-tbl-term/tbl_option.c
--- mandoc-tbl-minglue/tbl_option.c	Wed Oct 13 00:30:09 2010
+++ mandoc-tbl-term/tbl_option.c	Thu Oct 14 23:01:33 2010
@@ -19,6 +19,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "out.h"
+#include "term.h"
 #include "tbl_extern.h"
 
 struct	tbl_phrase {
diff -Naur mandoc-tbl-minglue/tbl_term.c mandoc-tbl-term/tbl_term.c
--- mandoc-tbl-minglue/tbl_term.c	Wed Oct 13 00:30:18 2010
+++ mandoc-tbl-term/tbl_term.c	Thu Oct 14 22:55:13 2010
@@ -21,6 +21,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "out.h"
+#include "term.h"
 #include "tbl_extern.h"
 
 /* FIXME: `n' modifier doesn't always do the right thing. */
@@ -30,19 +32,23 @@
 static	void		 calc_data_literal(struct tbl_data *);
 static	void		 calc_data_number(struct tbl_data *);
 static	void		 calc_data_spanner(struct tbl_data *);
-static	inline void	 write_char(char, int);
-static	void		 write_data(const struct tbl_data *, int);
-static	void		 write_data_literal(const struct tbl_data *, int);
-static	void		 write_data_number(const struct tbl_data *, int);
-static	void		 write_data_spanner(const struct tbl_data *, int);
-static	void		 write_hframe(const struct tbl *);
-static	void		 write_hrule(const struct tbl_span *);
-static	void		 write_spanner(const struct tbl_head *);
-static	void		 write_vframe(const struct tbl *);
+static	inline void	 write_char(struct termp *, char, int);
+static	void		 write_data(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_data_literal(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_data_number(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_data_spanner(struct termp *,
+				const struct tbl_data *, int);
+static	void		 write_hframe(struct termp *, const struct tbl *);
+static	void		 write_hrule(struct termp *, const struct tbl_span *);
+static	void		 write_spanner(struct termp *, const struct tbl_head *);
+static	void		 write_vframe(struct termp *, const struct tbl *);
 
 
 int
-tbl_write_term(const struct tbl *tbl)
+tbl_write_term(struct termp *p, const struct tbl *tbl)
 {
 	const struct tbl_span	*span;
 	const struct tbl_data	*data;
@@ -53,9 +59,11 @@
 	 * were set when tbl_calc_term was called.
 	 */
 
+	p->flags |= TERMP_NONOSPACE;
+
 	/* First, write out our head horizontal frame. */
 
-	write_hframe(tbl);
+	write_hframe(p, tbl);
 
 	/*
 	 * Iterate through each span, and inside, through the global
@@ -65,14 +73,14 @@
 	 */
 
 	TAILQ_FOREACH(span, &tbl->span, entries) {
-		write_vframe(tbl);
+		write_vframe(p, tbl);
 
 		/* Accomodate for the horizontal rule. */
 		if (TBL_DATA_DHORIZ & span->flags || 
 				TBL_DATA_HORIZ & span->flags) {
-			write_hrule(span);
-			write_vframe(tbl);
-			printf("\n");
+			write_hrule(p, span);
+			write_vframe(p, tbl);
+			term_flushln(p);
 			continue;
 		}
 
@@ -82,10 +90,10 @@
 			case (TBL_HEAD_VERT):
 				/* FALLTHROUGH */
 			case (TBL_HEAD_DVERT):
-				write_spanner(head);
+				write_spanner(p, head);
 				break;
 			case (TBL_HEAD_DATA):
-				write_data(data, head->width);
+				write_data(p, data, head->width);
 				if (data)
 					data = TAILQ_NEXT(data, entries);
 				break;
@@ -94,14 +102,16 @@
 				/* NOTREACHED */
 			}
 		}
-		write_vframe(tbl);
-		printf("\n");
+		write_vframe(p, tbl);
+		term_flushln(p);
 	}
 
 	/* Last, write out our tail horizontal frame. */
 
-	write_hframe(tbl);
+	write_hframe(p, tbl);
 
+	p->flags &= ~TERMP_NONOSPACE;
+
 	return(1);
 }
 
@@ -147,7 +157,7 @@
 
 
 static void
-write_hrule(const struct tbl_span *span)
+write_hrule(struct termp *p, const struct tbl_span *span)
 {
 	const struct tbl_head	*head;
 	char			 c;
@@ -167,13 +177,13 @@
 	TAILQ_FOREACH(head, &span->tbl->head, entries) {
 		switch (head->pos) {
 		case (TBL_HEAD_DATA):
-			write_char(c, head->width);
+			write_char(p, c, head->width);
 			break;
 		case (TBL_HEAD_DVERT):
-			write_char('+', head->width);
+			write_char(p, '+', head->width);
 			/* FALLTHROUGH */
 		case (TBL_HEAD_VERT):
-			write_char('+', head->width);
+			write_char(p, '+', head->width);
 			break;
 		default:
 			abort();
@@ -184,7 +194,7 @@
 
 
 static void
-write_hframe(const struct tbl *tbl)
+write_hframe(struct termp *p, const struct tbl *tbl)
 {
 	const struct tbl_head	*head;
 
@@ -200,35 +210,37 @@
 	 */
 
 	if (TBL_OPT_DBOX & tbl->opts) {
-		printf("+");
+		term_word(p, "+");
 		TAILQ_FOREACH(head, &tbl->head, entries)
-			write_char('-', head->width);
-		printf("+\n");
+			write_char(p, '-', head->width);
+		term_word(p, "+");
+		term_flushln(p);
 	}
 
-	printf("+");
+	term_word(p, "+");
 	TAILQ_FOREACH(head, &tbl->head, entries) {
 		switch (head->pos) {
 		case (TBL_HEAD_DATA):
-			write_char('-', head->width);
+			write_char(p, '-', head->width);
 			break;
 		default:
-			write_char('+', head->width);
+			write_char(p, '+', head->width);
 			break;
 		}
 	}
-	printf("+\n");
+	term_word(p, "+");
+	term_flushln(p);
 }
 
 
 static void
-write_vframe(const struct tbl *tbl)
+write_vframe(struct termp *p, const struct tbl *tbl)
 {
 	/* Always just a single vertical line. */
 
 	if ( ! (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts))
 		return;
-	printf("|");
+	term_word(p, "|");
 }
 
 
@@ -348,7 +360,7 @@
 
 
 static void
-write_data_spanner(const struct tbl_data *data, int width)
+write_data_spanner(struct termp *p, const struct tbl_data *data, int width)
 {
 
 	/*
@@ -356,18 +368,18 @@
 	 * layout) or as data.
 	 */
 	if (TBL_DATA_HORIZ & data->flags)
-		write_char('-', width);
+		write_char(p, '-', width);
 	else if (TBL_DATA_DHORIZ & data->flags)
-		write_char('=', width);
+		write_char(p, '=', width);
 	else if (TBL_CELL_HORIZ == data->cell->pos)
-		write_char('-', width);
+		write_char(p, '-', width);
 	else if (TBL_CELL_DHORIZ == data->cell->pos)
-		write_char('=', width);
+		write_char(p, '=', width);
 }
 
 
 static void
-write_data_number(const struct tbl_data *data, int width)
+write_data_number(struct termp *p, const struct tbl_data *data, int width)
 {
 	char		*dp, pnt;
 	int		 d, padl, sz;
@@ -377,7 +389,7 @@
 	 * and the maximum decimal; right-pad by the remaining amount.
 	 */
 
-	sz = (int)strlen(data->string);
+	sz = (int)term_strlen(p, data->string);
 	pnt = data->span->tbl->decimal;
 
 	if (NULL == (dp = strchr(data->string, pnt))) {
@@ -393,14 +405,14 @@
 	padl = data->cell->head->decimal - d + 1;
 	assert(width - sz - padl);
 
-	write_char(' ', padl);
-	(void)printf("%s", data->string);
-	write_char(' ', width - sz - padl);
+	write_char(p, ' ', padl);
+	term_word(p, data->string);
+	write_char(p, ' ', width - sz - padl);
 }
 
 
 static void
-write_data_literal(const struct tbl_data *data, int width)
+write_data_literal(struct termp *p, const struct tbl_data *data, int width)
 {
 	int		 padl, padr;
 
@@ -409,41 +421,41 @@
 	switch (data->cell->pos) {
 	case (TBL_CELL_LONG):
 		padl = 1;
-		padr = width - (int)strlen(data->string) - 1;
+		padr = width - (int)term_strlen(p, data->string) - 1;
 		break;
 	case (TBL_CELL_CENTRE):
-		padl = width - (int)strlen(data->string);
+		padl = width - (int)term_strlen(p, data->string);
 		if (padl % 2)
 			padr++;
 		padl /= 2;
 		padr += padl;
 		break;
 	case (TBL_CELL_RIGHT):
-		padl = width - (int)strlen(data->string);
+		padl = width - (int)term_strlen(p, data->string);
 		break;
 	default:
-		padr = width - (int)strlen(data->string);
+		padr = width - (int)term_strlen(p, data->string);
 		break;
 	}
 
-	write_char(' ', padl);
-	(void)printf("%s", data->string);
-	write_char(' ', padr);
+	write_char(p, ' ', padl);
+	term_word(p, data->string);
+	write_char(p, ' ', padr);
 }
 
 
 static void
-write_data(const struct tbl_data *data, int width)
+write_data(struct termp *p, const struct tbl_data *data, int width)
 {
 
 	if (NULL == data) {
-		write_char(' ', width);
+		write_char(p, ' ', width);
 		return;
 	}
 
 	if (TBL_DATA_HORIZ & data->flags || 
 			TBL_DATA_DHORIZ & data->flags) {
-		write_data_spanner(data, width);
+		write_data_spanner(p, data, width);
 		return;
 	}
 
@@ -451,7 +463,7 @@
 	case (TBL_CELL_HORIZ):
 		/* FALLTHROUGH */
 	case (TBL_CELL_DHORIZ):
-		write_data_spanner(data, width);
+		write_data_spanner(p, data, width);
 		break;
 	case (TBL_CELL_LONG):
 		/* FALLTHROUGH */
@@ -460,10 +472,10 @@
 	case (TBL_CELL_LEFT):
 		/* FALLTHROUGH */
 	case (TBL_CELL_RIGHT):
-		write_data_literal(data, width);
+		write_data_literal(p, data, width);
 		break;
 	case (TBL_CELL_NUMBER):
-		write_data_number(data, width);
+		write_data_number(p, data, width);
 		break;
 	default:
 		abort();
@@ -473,32 +485,34 @@
 
 
 static void
-write_spanner(const struct tbl_head *head)
+write_spanner(struct termp *p, const struct tbl_head *head)
 {
-	char		*p;
+	char		*w;
 
-	p = NULL;
+	w = NULL;
 	switch (head->pos) {
 	case (TBL_HEAD_VERT):
-		p = "|";
+		w = "|";
 		break;
 	case (TBL_HEAD_DVERT):
-		p = "||";
+		w = "||";
 		break;
 	default:
 		break;
 	}
 
 	assert(p);
-	printf("%s", p);
+	term_word(p, w);
 }
 
 
 static inline void
-write_char(char c, int len)
+write_char(struct termp *p, char c, int len)
 {
 	int		 i;
+	static char	 w[2];
 
+	w[0] = c;
 	for (i = 0; i < len; i++)
-		printf("%c", c);
+		term_word(p, w);
 }
diff -Naur mandoc-tbl-minglue/tbl_tree.c mandoc-tbl-term/tbl_tree.c
--- mandoc-tbl-minglue/tbl_tree.c	Wed Oct 13 00:30:28 2010
+++ mandoc-tbl-term/tbl_tree.c	Thu Oct 14 23:01:46 2010
@@ -21,6 +21,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "out.h"
+#include "term.h"
 #include "tbl_extern.h"
 
 static	const char * const htypes[TBL_HEAD_MAX] = {

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: integrate tbl into mandoc
  2010-10-14 21:18 ` Ingo Schwarze
@ 2010-10-14 21:55   ` Ingo Schwarze
  2010-10-14 22:19   ` Ingo Schwarze
  2010-10-18 16:20   ` Kristaps Dzonsons
  2 siblings, 0 replies; 7+ messages in thread
From: Ingo Schwarze @ 2010-10-14 21:55 UTC (permalink / raw)
  To: tech

Hi,

this one corrects alignment by using term_*len() functions.
I guess not all details are 100% correct yet,
but this will get us started with respect to alignment.

It is cumulative to the last patch i sent just before.

Yours,
  Ingo


diff -Naur mandoc-tbl-term/man_term.c mandoc-tbl/man_term.c
--- mandoc-tbl-term/man_term.c	Thu Oct 14 22:49:10 2010
+++ mandoc-tbl/man_term.c	Thu Oct 14 23:43:50 2010
@@ -838,7 +838,7 @@
 	if (MAN_BLOCK != n->type)
 		return(0);
 
-	if ( ! tbl_close(n->data.TS, "<man>", n->line))
+	if ( ! tbl_close(p, n->data.TS, "<man>", n->line))
 		return(0);
 
 	tbl_write(p, n->data.TS);
diff -Naur mandoc-tbl-term/tbl.c mandoc-tbl/tbl.c
--- mandoc-tbl-term/tbl.c	Thu Oct 14 22:50:44 2010
+++ mandoc-tbl/tbl.c	Wed Oct 13 22:55:39 2010
@@ -90,7 +90,7 @@
 
 
 int
-tbl_close(struct tbl *tbl, const char *f, int ln)
+tbl_close(struct termp *p, struct tbl *tbl, const char *f, int ln)
 {
 
 	if (TBL_PART_DATA != tbl->part) 
@@ -98,7 +98,7 @@
 	if ( ! tbl_data_close(tbl, f, ln))
 		return(0);
 #if 1
-	return(tbl_calc_term(tbl));
+	return(tbl_calc_term(p, tbl));
 #else
 	return(tbl_calc_tree(tbl));
 #endif
diff -Naur mandoc-tbl-term/tbl.h mandoc-tbl/tbl.h
--- mandoc-tbl-term/tbl.h	Thu Oct 14 22:50:54 2010
+++ mandoc-tbl/tbl.h	Wed Oct 13 22:53:55 2010
@@ -26,7 +26,7 @@
 void		 tbl_reset(struct tbl *);
 
 int	 	 tbl_read(struct tbl *, const char *, int, const char *, int);
-int		 tbl_close(struct tbl *, const char *, int);
+int		 tbl_close(struct termp *, struct tbl *, const char *, int);
 int		 tbl_write(struct termp *, const struct tbl *);
 
 __END_DECLS
diff -Naur mandoc-tbl-term/tbl_extern.h mandoc-tbl/tbl_extern.h
--- mandoc-tbl-term/tbl_extern.h	Thu Oct 14 23:00:42 2010
+++ mandoc-tbl/tbl_extern.h	Wed Oct 13 22:56:31 2010
@@ -174,7 +174,7 @@
 struct tbl_data	*tbl_data_alloc(struct tbl_span *);
 
 int		 tbl_write_term(struct termp *, const struct tbl *);
-int		 tbl_calc_term(struct tbl *);
+int		 tbl_calc_term(struct termp *, struct tbl *);
 int		 tbl_write_tree(const struct tbl *);
 int		 tbl_calc_tree(struct tbl *);
 
diff -Naur mandoc-tbl-term/tbl_term.c mandoc-tbl/tbl_term.c
--- mandoc-tbl-term/tbl_term.c	Thu Oct 14 23:28:00 2010
+++ mandoc-tbl/tbl_term.c	Thu Oct 14 23:40:15 2010
@@ -29,10 +29,10 @@
 /* FIXME: `n' modifier doesn't always do the right thing. */
 /* FIXME: `n' modifier doesn't use the cell-spacing buffer. */
 
-static	void		 calc_data(struct tbl_data *);
-static	void		 calc_data_literal(struct tbl_data *);
-static	void		 calc_data_number(struct tbl_data *);
-static	void		 calc_data_spanner(struct tbl_data *);
+static	void		 calc_data(struct termp *, struct tbl_data *);
+static	void		 calc_data_literal(struct termp *, struct tbl_data *);
+static	void		 calc_data_number(struct termp *, struct tbl_data *);
+static	void		 calc_data_spanner(struct termp *, struct tbl_data *);
 static	inline void	 write_char(struct termp *, char, int);
 static	void		 write_data(struct termp *,
 				const struct tbl_data *, int);
@@ -118,7 +118,7 @@
 
 
 int
-tbl_calc_term(struct tbl *tbl)
+tbl_calc_term(struct termp *p, struct tbl *tbl)
 {
 	struct tbl_span	*span;
 	struct tbl_data	*data;
@@ -136,7 +136,7 @@
 		if (TBL_DATA_NDHORIZ & span->flags)
 			continue;
 		TAILQ_FOREACH(data, &span->data, entries)
-			calc_data(data);
+			calc_data(p, data);
 	}
 
 	/* Calculate width as the simple spanner value. */
@@ -144,10 +144,10 @@
 	TAILQ_FOREACH(head, &tbl->head, entries) 
 		switch (head->pos) {
 		case (TBL_HEAD_VERT):
-			head->width = 1;
+			head->width = term_len(p, 1);
 			break;
 		case (TBL_HEAD_DVERT):
-			head->width = 2;
+			head->width = term_len(p, 2);
 			break;
 		default:
 			break;
@@ -246,16 +246,16 @@
 
 
 static void
-calc_data_spanner(struct tbl_data *data)
+calc_data_spanner(struct termp *p, struct tbl_data *data)
 {
 
 	/* N.B., these are horiz spanners (not vert) so always 1. */
-	data->cell->head->width = 1;
+	data->cell->head->width = term_len(p, 1);
 }
 
 
 static void
-calc_data_number(struct tbl_data *data)
+calc_data_number(struct termp *p, struct tbl_data *data)
 {
 	int 		 sz, d;
 	char		*dp, pnt;
@@ -272,15 +272,14 @@
 	/* TODO: use spacing modifier. */
 
 	assert(data->string);
-	sz = (int)strlen(data->string);
+	sz = (int)term_strlen(p, data->string);
 	pnt = data->span->tbl->decimal;
 
-	if (NULL == (dp = strchr(data->string, pnt)))
-		d = sz + 1;
-	else
-		d = (int)(dp - data->string) + 1;
+	dp = strchr(data->string, pnt);
+	d = dp ? sz - (int)term_strlen(p, dp) : sz;
+	d += term_len(p, 1);
 
-	sz += 2;
+	sz += term_len(p, 2);
 
 	if (data->cell->head->decimal > d) {
 		sz += data->cell->head->decimal - d;
@@ -297,7 +296,7 @@
 
 
 static void
-calc_data_literal(struct tbl_data *data)
+calc_data_literal(struct termp *p, struct tbl_data *data)
 {
 	int		 sz, bufsz;
 
@@ -308,7 +307,7 @@
 	 */
 
 	assert(data->string);
-	sz = (int)strlen(data->string);
+	sz = (int)term_strlen(p, data->string);
 
 	switch (data->cell->pos) {
 	case (TBL_CELL_LONG):
@@ -325,21 +324,21 @@
 		bufsz = bufsz > data->cell->spacing ? 
 			bufsz : data->cell->spacing;
 
-	sz += bufsz;
+	sz += term_len(p, bufsz);
 	if (data->cell->head->width < sz)
 		data->cell->head->width = sz;
 }
 
 
 static void
-calc_data(struct tbl_data *data)
+calc_data(struct termp *p, struct tbl_data *data)
 {
 
 	switch (data->cell->pos) {
 	case (TBL_CELL_HORIZ):
 		/* FALLTHROUGH */
 	case (TBL_CELL_DHORIZ):
-		calc_data_spanner(data);
+		calc_data_spanner(p, data);
 		break;
 	case (TBL_CELL_LONG):
 		/* FALLTHROUGH */
@@ -348,10 +347,10 @@
 	case (TBL_CELL_LEFT):
 		/* FALLTHROUGH */
 	case (TBL_CELL_RIGHT):
-		calc_data_literal(data);
+		calc_data_literal(p, data);
 		break;
 	case (TBL_CELL_NUMBER):
-		calc_data_number(data);
+		calc_data_number(p, data);
 		break;
 	default:
 		abort();
--
 To unsubscribe send an email to tech+unsubscribe@mdocml.bsd.lv

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: integrate tbl into mandoc
  2010-10-14 21:18 ` Ingo Schwarze
  2010-10-14 21:55   ` Ingo Schwarze
@ 2010-10-14 22:19   ` Ingo Schwarze
  2010-10-14 23:02     ` Ingo Schwarze
  2010-10-18 16:20   ` Kristaps Dzonsons
  2 siblings, 1 reply; 7+ messages in thread
From: Ingo Schwarze @ 2010-10-14 22:19 UTC (permalink / raw)
  To: tech

Hi,

>> The following still crash:

as one might expect, options are optional,
so the following patch fixes most of these.

The following now work:

  /usr/src/lib/libcurses/curs_addch.3tbl
  /usr/src/lib/libcurses/curs_attr.3tbl
  /usr/src/lib/libcurses/curs_getch.3tbl
  /usr/src/lib/libcurses/curs_mouse.3tbl
  /usr/src/lib/libcurses/curses.3tbl
  /usr/src/lib/libform/form.3tbl
  /usr/src/usr.bin/infocmp/infocmp.1tbl
  /usr/src/usr.bin/tic/captoinfo.1tbl

These still crash:

>>   /usr/src/lib/libcurses/curs_inch.3tbl
>>   /usr/src/lib/libmenu/menu.3tbl
>>   /usr/src/gnu/usr.sbin/mkhybrid/src/mkhybrid.8tbl

>> The following have a tbl extension, but no .TS:
>>   /usr/src/lib/libcurses/term.5tbl
>> The following are mdoc(7) with tbl:
>>   /usr/src/share/man/man4/wi.4tbl
>>   /usr/src/share/man/man4/man4.hppa/cpu.4tbl
>>   /usr/src/games/phantasia/phantasia.6tbl

Yours,
  Ingo

diff -Napur mandoc-tbl-calc/tbl_option.c mandoc-tbl/tbl_option.c
--- mandoc-tbl-calc/tbl_option.c	Thu Oct 14 23:24:14 2010
+++ mandoc-tbl/tbl_option.c	Fri Oct 15 00:11:52 2010
@@ -141,6 +141,11 @@ opt(struct tbl *tbl, const char *f, int ln, const char
 {
 	int		 i, sv;
 
+	if (NULL == strchr(p + *pos, ';')) {
+		tbl->part = TBL_PART_LAYOUT;
+		return(1);
+	}
+
 again:
 	sv = *pos;
 
--
 To unsubscribe send an email to tech+unsubscribe@mdocml.bsd.lv

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: integrate tbl into mandoc
  2010-10-14 22:19   ` Ingo Schwarze
@ 2010-10-14 23:02     ` Ingo Schwarze
  0 siblings, 0 replies; 7+ messages in thread
From: Ingo Schwarze @ 2010-10-14 23:02 UTC (permalink / raw)
  To: tech

Hi,

> as one might expect, options are optional,
> so the following patch fixes most of these.

Gah, forget about this one, it is completely wrong.
Kristaps correctly coded the "optional" feature,
i just called the function tbl_read() with bad arguments.

See below for a better patch, which also fixes the
line break at the start of a table.

The following now work:

  /usr/src/lib/libcurses/curs_addch.3tbl
  /usr/src/lib/libcurses/curs_attr.3tbl
  /usr/src/lib/libcurses/curs_getch.3tbl
  /usr/src/lib/libcurses/curs_inch.3tbl
  /usr/src/lib/libcurses/curs_mouse.3tbl
  /usr/src/lib/libcurses/curses.3tbl
  /usr/src/lib/libform/form.3tbl
  /usr/src/lib/libmenu/menu.3tbl
  /usr/src/usr.bin/infocmp/infocmp.1tbl
  /usr/src/usr.bin/tic/captoinfo.1tbl

Only one still crashes:

  /usr/src/gnu/usr.sbin/mkhybrid/src/mkhybrid.8tbl

> The following have a tbl extension, but no .TS:
>   /usr/src/lib/libcurses/term.5tbl
> The following are mdoc(7) with tbl:
>   /usr/src/share/man/man4/wi.4tbl
>   /usr/src/share/man/man4/man4.hppa/cpu.4tbl
>   /usr/src/games/phantasia/phantasia.6tbl

So, i will put this into OpenBSD tomorrow and do the mdoc part,
such that we can kick groff out of the tree this weekend.

Yours,
  Ingo


diff -Napur mandoc-tbl-calc/man.c mandoc-tbl-opt/man.c
--- mandoc-tbl-calc/man.c	Thu Oct 14 23:41:50 2010
+++ mandoc-tbl-opt/man.c	Fri Oct 15 00:47:16 2010
@@ -135,7 +135,8 @@ man_parseln(struct man *m, int ln, char *buf, int offs
 	if (n && MAN_TS == n->tok && MAN_BODY == n->type &&
 	    strncmp(buf+offs, ".TE", 3)) {
 		n = n->parent;
-		return(tbl_read(n->data.TS, "<man>", ln, buf, offs) ? 1 : 0);
+		return(tbl_read(n->data.TS, "<man>", ln, buf+offs,
+		    strlen(buf+offs)) ? 1 : 0);
 	}
 
 	return(('.' == buf[offs] || '\'' == buf[offs]) ? 
diff -Napur mandoc-tbl-calc/man_term.c mandoc-tbl-opt/man_term.c
--- mandoc-tbl-calc/man_term.c	Thu Oct 14 23:43:50 2010
+++ mandoc-tbl-opt/man_term.c	Fri Oct 15 00:51:10 2010
@@ -841,6 +841,7 @@ pre_TS(DECL_ARGS)
 	if ( ! tbl_close(p, n->data.TS, "<man>", n->line))
 		return(0);
 
+	term_newln(p);
 	tbl_write(p, n->data.TS);
 
 	return(0);
--
 To unsubscribe send an email to tech+unsubscribe@mdocml.bsd.lv

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: integrate tbl into mandoc
  2010-10-14 21:18 ` Ingo Schwarze
  2010-10-14 21:55   ` Ingo Schwarze
  2010-10-14 22:19   ` Ingo Schwarze
@ 2010-10-18 16:20   ` Kristaps Dzonsons
  2010-10-18 17:38     ` Joerg Sonnenberger
  2 siblings, 1 reply; 7+ messages in thread
From: Kristaps Dzonsons @ 2010-10-18 16:20 UTC (permalink / raw)
  To: tech

Hi Ingo---I'm on my way back from a little bit in the mountains.  Just 
getting your patches and messages now.

I'll respond over the next few days, but I warn you that I absolutely 
don't intend to merge tbl into mandoc.  I don't want the code volume 
increased so much for a corner case.  Tables are already supported by 
`Bl -column'; pulling this extra code into the parser for all manuals in 
the general case is wasteful.

I'll produce a patch by way of explanation, but I intend to instead have 
a separate tbl utility that links with libmandoc for escape-width 
calculation (which will, happily, need deduplication) that will produce 
fixed text that's pumped into libman or libmdoc.

I'll need a few days, but it'll be quite straightforward.

Take care,

Kristaps
--
 To unsubscribe send an email to tech+unsubscribe@mdocml.bsd.lv

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: integrate tbl into mandoc
  2010-10-18 16:20   ` Kristaps Dzonsons
@ 2010-10-18 17:38     ` Joerg Sonnenberger
  0 siblings, 0 replies; 7+ messages in thread
From: Joerg Sonnenberger @ 2010-10-18 17:38 UTC (permalink / raw)
  To: tech

On Mon, Oct 18, 2010 at 06:20:07PM +0200, Kristaps Dzonsons wrote:
> I'll respond over the next few days, but I warn you that I
> absolutely don't intend to merge tbl into mandoc.  I don't want the
> code volume increased so much for a corner case.  Tables are already
> supported by `Bl -column'; pulling this extra code into the parser
> for all manuals in the general case is wasteful.

I somewhat disagree on this. The code of .Bl -column is complicated and
lacking in some important areas. Same can be said about tbl. Properly
integrating both into a shared table engine can even result in major
improvements for .Bl -column. Consider the ability to have proper
multi-line fields.

Joerg
--
 To unsubscribe send an email to tech+unsubscribe@mdocml.bsd.lv

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2010-10-18 17:37 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-10-13  0:46 integrate tbl into mandoc Ingo Schwarze
2010-10-14 21:18 ` Ingo Schwarze
2010-10-14 21:55   ` Ingo Schwarze
2010-10-14 22:19   ` Ingo Schwarze
2010-10-14 23:02     ` Ingo Schwarze
2010-10-18 16:20   ` Kristaps Dzonsons
2010-10-18 17:38     ` Joerg Sonnenberger

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).