Sending this here as suggested by henn...@.
Comments welcome!

----- Forwarded message from Ingo Schwarze <[email protected]> -----

Hi,

here is my plan to get rid of groff in base:

(1) tonight: commit tbl(1) support for man(7) into mandoc
(2) tomorrow: commit tbl(1) support for mdoc(7) into mandoc
(3) tomorrow: send patches to switch "tbl | nroff"
    over to mandoc in the base build system
(4) tomorrow: send patches to unlink the last me(7) and ms(7)
    documents form the base build
(5) beginning of next week: espie@ could enable the groff port
(6) beginning of next week: disable groff in the base build

I suggest to get this done as soon as possible,
well before the ports hackathon.

As a reminder, at least at the beginning, the plan is to give
ports containing man(7) or mdoc(7) manual pages a build dependency -
but not a run dependency - on the groff port, and to allow the
respective port maintainer to switch the port over to using mandoc(1),
*IF* and when he regards that switch as worthwhile.
Thus, the intention is to avoid any kind of a make-work project
for porters.

The following patch is what i hope to get into the tree this night,
to achieve (1).  Final testing may yet result in minor tweaks.
After that, (2) will be easy, analogous, and a much smaller patch.

Step (1) lets the following build with mandoc:
  /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/libcurses/term.5tbl
  /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

Step (2) will hopefully fix:
  /usr/src/share/man/man4/wi.4tbl
  /usr/src/share/man/man4/man4.hppa/cpu.4tbl
  /usr/src/games/phantasia/phantasia.6tbl

With the patch below, the command to build these is just:
  mandoc /usr/src/lib/libcurses/curs_addch.3tbl

Of course, there are still a few minor issues.
For example, the .T{ macro is not yet supported,
but i think that should not keep us from moving forward.

Any reason to not do the steps (1) to (4) right now?

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    15 Oct 2010 10:39:15 -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       15 Oct 2010 10:39:15 -0000
@@ -26,6 +26,10 @@
 #include "libman.h"
 #include "libmandoc.h"
 
+#include "out.h"
+#include "term.h"
+#include "tbl.h"
+
 const  char *const __man_macronames[MAN_MAX] = {                
        "br",           "TH",           "SH",           "SS",
        "TP",           "LP",           "PP",           "P",
@@ -36,7 +40,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 +125,20 @@ 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,
+                   strlen(buf+offs)) ? 1 : 0);
+       }
+
        return(('.' == buf[offs] || '\'' == buf[offs]) ? 
                        man_pmacro(m, ln, buf, offs) : 
                        man_ptext(m, ln, buf, offs));
@@ -322,6 +336,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       15 Oct 2010 10:39:15 -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        15 Oct 2010 10:39:15 -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  15 Oct 2010 10:39:16 -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 15 Oct 2010 10:39:16 -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  15 Oct 2010 10:39:16 -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,24 @@ 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(p, n->data.TS, "<man>", n->line))
+               return(0);
+
+       term_newln(p);
+       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      15 Oct 2010 10:39:16 -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 }, /* TE */
 };
 
 
Index: tbl.c
===================================================================
RCS file: tbl.c
diff -N tbl.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ tbl.c       15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,546 @@
+/*     $Id: tbl.c,v 1.14 2009/09/12 16:05:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ *
+ * 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 termp *p, 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(p, 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       15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,34 @@
+/*     $Id: tbl.h,v 1.3 2009/09/11 15:01:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ *
+ * 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 termp *, 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  15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,132 @@
+/*     $Id: data.c,v 1.11 2009/09/12 16:05:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ *
+ * 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        15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,183 @@
+/*     $Id: extern.h,v 1.10 2009/09/13 12:37:28 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ *
+ * 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 termp *, 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        15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,282 @@
+/*     $Id: layout.c,v 1.7 2009/09/11 13:24:04 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ *
+ * 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        15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,197 @@
+/*     $Id: option.c,v 1.5 2009/09/09 12:51:34 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ *
+ * 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  15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,523 @@
+/*     $Id: term.c,v 1.13 2009/09/14 09:06:40 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ * Copyright (c) 2010 Ingo Schwarze <[email protected]>
+ *
+ * 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 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);
+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 termp *p, 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(p, data);
+       }
+
+       /* Calculate width as the simple spanner value. */
+
+       TAILQ_FOREACH(head, &tbl->head, entries) 
+               switch (head->pos) {
+               case (TBL_HEAD_VERT):
+                       head->width = term_len(p, 1);
+                       break;
+               case (TBL_HEAD_DVERT):
+                       head->width = term_len(p, 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 termp *p, struct tbl_data *data)
+{
+
+       /* N.B., these are horiz spanners (not vert) so always 1. */
+       data->cell->head->width = term_len(p, 1);
+}
+
+
+static void
+calc_data_number(struct termp *p, 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)term_strlen(p, data->string);
+       pnt = data->span->tbl->decimal;
+
+       dp = strchr(data->string, pnt);
+       d = dp ? sz - (int)term_strlen(p, dp) : sz;
+       d += term_len(p, 1);
+
+       sz += term_len(p, 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 termp *p, 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)term_strlen(p, 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 += term_len(p, bufsz);
+       if (data->cell->head->width < sz)
+               data->cell->head->width = sz;
+}
+
+
+static void
+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(p, data);
+               break;
+       case (TBL_CELL_LONG):
+               /* FALLTHROUGH */
+       case (TBL_CELL_CENTRE):
+               /* FALLTHROUGH */
+       case (TBL_CELL_LEFT):
+               /* FALLTHROUGH */
+       case (TBL_CELL_RIGHT):
+               calc_data_literal(p, data);
+               break;
+       case (TBL_CELL_NUMBER):
+               calc_data_number(p, data);
+               break;
+       case (TBL_CELL_SPAN):
+               data->cell->head->width = 0;
+               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;
+       case (TBL_CELL_SPAN):
+               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  15 Oct 2010 10:39:18 -0000
@@ -0,0 +1,88 @@
+/*     $Id: tree.c,v 1.2 2009/09/11 13:24:04 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <[email protected]>
+ *
+ * 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);
+}

----- End forwarded message -----

Reply via email to