[BACK]Return to tbl.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mandoc

File: [local] / src / usr.bin / mandoc / tbl.c (download)

Revision 1.1, Fri Oct 15 19:20:03 2010 UTC (13 years, 7 months ago) by schwarze
Branch: MAIN

Import tbl parser and renderer written by kristaps@.
Unchanged code from bsd.lv release 0.1.5, but without the main program.
Not yet linked to the build; next commit will integrate it into mandoc.

/*	$Id: tbl.c,v 1.1 2010/10/15 19:20:03 schwarze 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);
}