[BACK]Return to main.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / texi2mdoc

File: [cvsweb.bsd.lv] / texi2mdoc / main.c (download)

Revision 1.1.1.1 (vendor branch), Mon Feb 16 22:24:43 2015 UTC (9 years, 1 month ago) by kristaps
Branch: version_0
CVS Tags: VERSION_0
Changes since 1.1: +0 -0 lines

Initial import of texi2mdoc.

/*	$Id: main.c,v 1.1.1.1 2015/02/16 22:24:43 kristaps Exp $ */
/*
 * Copyright (c) 2015 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * 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/mman.h>
#include <sys/stat.h>

#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * This defines each one of the Texinfo commands that we understand.
 * Obviously this only refers to native commands; overriden names are a
 * different story.
 */
enum	texicmd {
	TEXICMD_A4PAPER,
	TEXICMD_ANCHOR,
	TEXICMD_AT,
	TEXICMD_BYE,
	TEXICMD_CHAPTER,
	TEXICMD_CINDEX,
	TEXICMD_CODE,
	TEXICMD_COMMAND,
	TEXICMD_COMMENT,
	TEXICMD_CONTENTS,
	TEXICMD_COPYING,
	TEXICMD_COPYRIGHT,
	TEXICMD_DETAILMENU,
	TEXICMD_DIRCATEGORY,
	TEXICMD_DIRENTRY,
	TEXICMD_EMAIL,
	TEXICMD_EMPH,
	TEXICMD_END,
	TEXICMD_EXAMPLE,
	TEXICMD_FILE,
	TEXICMD_I,
	TEXICMD_IFHTML,
	TEXICMD_IFNOTTEX,
	TEXICMD_IFTEX,
	TEXICMD_IMAGE,
	TEXICMD_ITEM,
	TEXICMD_ITEMIZE,
	TEXICMD_KBD,
	TEXICMD_LATEX,
	TEXICMD_MENU,
	TEXICMD_NODE,
	TEXICMD_QUOTATION,
	TEXICMD_PARINDENT,
	TEXICMD_REF,
	TEXICMD_SAMP,
	TEXICMD_SECTION,
	TEXICMD_SETCHAPNEWPAGE,
	TEXICMD_SETFILENAME,
	TEXICMD_SETTITLE,
	TEXICMD_SUBSECTION,
	TEXICMD_TABLE,
	TEXICMD_TEX,
	TEXICMD_TEXSYM,
	TEXICMD_TITLEFONT,
	TEXICMD_TITLEPAGE,
	TEXICMD_TOP,
	TEXICMD_UNNUMBERED,
	TEXICMD_URL,
	TEXICMD_VAR,
	TEXICMD__MAX
};

/*
 * The file currently being parsed.
 * This keeps track of our location within that file.
 */
struct	texifile {
	const char	*name; /* name of the file */
	size_t	  	 line; /* current line (from zero) */
	size_t	  	 col; /* current column in line (from zero) */
	char		*map; /* mmap'd file */
	size_t		 mapsz; /* size of mmap */
};

struct	texi;

typedef	void (*texicmdfp)(struct texi *, 
	enum texicmd, const char *, size_t, size_t *);

/*
 * Describes Texinfo commands, whether native or overriden.
 */
struct	texitok {
	texicmdfp	 fp; /* callback (or NULL if none) */
	const char	*tok; /* name of the token */
	size_t		 len; /* strlen(tok) */
};

/*
 * The main parse structure.
 * This keeps any necessary information handy.
 */
struct	texi {
	struct texifile  files[64];
	size_t		 filepos;
	unsigned 	 flags;
#define	TEXI_IGN	 0x01 /* don't print anything */
#define	TEXI_HEADER	(TEXI_IGN | 0x02) /* haven't seen @top yet */
#define	TEXI_LITERAL	 0x04 /* output all whitespace */
	size_t		 outcol; /* column of output */
	int		 outmacro; /* whether output is in line macro */
	int		 seenws; /* whitespace has been ignored */
};

#define	ismpunct(_x) \
	('.' == (_x) || \
	 ',' == (_x) || \
	 ';' == (_x))

static	void doarg1(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dobracket(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dobye(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void docommand(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doemph(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doexample(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dofile(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doifnottex(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doignblock(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doignbracket(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doignline(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doitalic(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doitem(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doitemize(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doliteral(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doquotation(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dotable(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dotop(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dosection(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dosh(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dosubsection(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dosymbol(struct texi *, enum texicmd, const char *, size_t, size_t *);

static	const struct texitok texitoks[TEXICMD__MAX] = {
	{ doignline, "afourpaper", 10 }, /* TEXICMD_A4PAPER */
	{ doignbracket, "anchor", 6 }, /* TEXICMD_ANCHOR */
	{ dosymbol, "@", 1 }, /* TEXICMD_AT */
	{ dobye, "bye", 3 }, /* TEXICMD_BYE */
	{ dosh, "chapter", 7 }, /* TEXICMD_CHAPTER */
	{ doignline, "cindex", 6 }, /* TEXICMD_CINDEX */
	{ doliteral, "code", 4 }, /* TEXICMD_CODE */
	{ docommand, "command", 7 }, /* TEXICMD_COMMAND */
	{ doignline, "c", 1 }, /* TEXICMD_COMMENT */
	{ doignline, "contents", 8 }, /* TEXICMD_CONTENTS */
	{ doignblock, "copying", 7 }, /* TEXICMD_COPYING */
	{ dosymbol, "copyright", 9 }, /* TEXICMD_COPYRIGHT */
	{ doignblock, "detailmenu", 10 }, /* TEXICMD_DETAILMENU */
	{ doignline, "dircategory", 11 }, /* TEXICMD_DIRCATEGORY */
	{ doignblock, "direntry", 8 }, /* TEXICMD_DIRENTRY */
	{ doarg1, "email", 5 }, /* TEXICMD_EMAIL */
	{ doemph, "emph", 4 }, /* TEXICMD_EMPH */
	{ NULL, "end", 3 }, /* TEXICMD_END */
	{ doexample, "example", 7 }, /* TEXICMD_EXAMPLE */
	{ dofile, "file", 4 }, /* TEXICMD_FILE */
	{ doitalic, "i", 1 }, /* TEXICMD_I */
	{ doignblock, "ifhtml", 6 }, /* TEXICMD_IFHTML */
	{ doifnottex, "ifnottex", 8 }, /* TEXICMD_IFNOTTEX */
	{ doignblock, "iftex", 5 }, /* TEXICMD_IFTEX */
	{ doignbracket, "image", 5 }, /* TEXICMD_IMAGE */
	{ doitem, "item", 4 }, /* TEXICMD_ITEM */
	{ doitemize, "itemize", 7 }, /* TEXICMD_ITEMIZE */
	{ doliteral, "kbd", 3 }, /* TEXICMD_KBD */
	{ dosymbol, "LaTeX", 5 }, /* TEXICMD_LATEX */
	{ doignblock, "menu", 4 }, /* TEXICMD_MENU */
	{ doignline, "node", 4 }, /* TEXICMD_NODE */
	{ doquotation, "quotation", 9 }, /* TEXICMD_QUOTATION */
	{ doignline, "paragraphindent", 14 }, /* TEXICMD_PARINDENT */
	{ dobracket, "ref", 3 }, /* TEXICMD_REF */
	{ doliteral, "samp", 4 }, /* TEXICMD_SAMP */
	{ dosection, "section", 7 }, /* TEXICMD_SECTION */
	{ doignline, "setchapternewpage", 17 }, /* TEXICMD_SETCHAPNEWPAGE */
	{ doignline, "setfilename", 11 }, /* TEXICMD_SETFILENAME */
	{ doignline, "settitle", 8 }, /* TEXICMD_SETTITLE */
	{ dosubsection, "subsection", 10 }, /* TEXICMD_SUBSECTION */
	{ dotable, "table", 5 }, /* TEXICMD_TABLE */
	{ doignblock, "tex", 3 }, /* TEXICMD_TEX */
	{ dosymbol, "TeX", 3 }, /* TEXICMD_TEXSYM */
	{ dobracket, "titlefont", 9 }, /* TEXICMD_TITLEFONT */
	{ doignblock, "titlepage", 9 }, /* TEXICMD_TITLEPAGE */
	{ dotop, "top", 3 }, /* TEXICMD_TOP */
	{ dosh, "unnumbered", 10 }, /* TEXICMD_UNNUMBERED */
	{ doarg1, "url", 3 }, /* TEXICMD_URL */
	{ doliteral, "var", 3 }, /* TEXICMD_VAR */
};

static void
texifilepop(struct texi *p)
{
	struct texifile	*f;

	assert(p->filepos > 0);
	f = &p->files[--p->filepos];
	munmap(f->map, f->mapsz);
}

static void
texiexit(struct texi *p)
{

	while (p->filepos > 0)
		texifilepop(p);
}

static void
texifatal(struct texi *p, const char *errstring)
{

	perror(errstring);
	texiexit(p);
	exit(EXIT_FAILURE);
}

/*
 * Print a generic warning message (to stderr) tied to our current
 * location in the parse sequence.
 */
static void
texiwarn(const struct texi *p, const char *fmt, ...)
{
	va_list	 ap;

	fprintf(stderr, "%s:%zu:%zu: ",
		p->files[p->filepos - 1].name,
		p->files[p->filepos - 1].line + 1,
		p->files[p->filepos - 1].col + 1);
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fputc('\n', stderr);
}

/*
 * Put a single data character.
 * This MUST NOT be a mdoc(7) command: it should be free text that's
 * outputted to the screen.
 */
static void
texiputchar(struct texi *p, char c)
{

	if (TEXI_IGN & p->flags)
		return;

	putchar(c);
	if ('\n' == c) {
		p->outcol = 0;
		p->outmacro = 0;
		p->seenws = 0;
	} else
		p->outcol++;
}

/*
 * Put multiple characters (see texiputchar()).
 */
static void
texiputchars(struct texi *p, const char *s)
{

	while ('\0' != *s)
		texiputchar(p, *s++);
}

/*
 * Put an mdoc(7) command without the trailing newline.
 * This should ONLY be used for mdoc(7) commands!
 */
static void
texifputs(struct texi *p, const char *s)
{
	int	 rc;

	if (TEXI_IGN & p->flags)
		return;
	if (p->outcol)
		texiputchar(p, '\n');
	if (EOF != (rc = fputs(s, stdout)))
		p->outcol += rc;
}

/*
 * Put an mdoc(7) command with the trailing newline.
 * This should ONLY be used for mdoc(7) commands!
 */
static void
teximacro(struct texi *p, const char *s)
{

	if (TEXI_IGN & p->flags)
		return;
	if (p->outcol)
		texiputchar(p, '\n');
	puts(s);
	p->outcol = 0;
	p->seenws = 0;
}

/*
 * Advance by a single byte in the input stream.
 */
static void
advance(struct texi *p, const char *buf, size_t *pos)
{

	if ('\n' == buf[*pos]) {
		p->files[p->filepos - 1].line++;
		p->files[p->filepos - 1].col = 0;
	} else
		p->files[p->filepos - 1].col++;

	(*pos)++;
}

/*
 * Advance to the next non-whitespace word in the input stream.
 * If we're in literal mode, then print all of the whitespace as we're
 * doing so.
 */
static size_t
advancenext(struct texi *p, const char *buf, size_t sz, size_t *pos)
{
	
	if (TEXI_LITERAL & p->flags) {
		while (*pos < sz && isspace(buf[*pos])) {
			texiputchar(p, buf[*pos]);
			advance(p, buf, pos);
		}
		return(*pos);
	} 

	while (*pos < sz && isspace(buf[*pos])) {
		p->seenws = 1;
		/* 
		 * If it looks like we've printed a double-line, then
		 * output a paragraph.
		 * FIXME: this is stupid.
		 */
		if (*pos && '\n' == buf[*pos] && '\n' == buf[*pos - 1])
			teximacro(p, ".Pp");
		advance(p, buf, pos);
	}
	return(*pos);
}

/*
 * Advance to the EOLN in the input stream.
 */
static size_t
advanceeoln(struct texi *p, const char *buf, size_t sz, size_t *pos)
{

	while (*pos < sz && '\n' != buf[*pos])
		advance(p, buf, pos);
	return(*pos);
}

/*
 * Advance to position "end", which is an absolute position in the
 * current buffer greater than or equal to the current position.
 */
static void
advanceto(struct texi *p, const char *buf, size_t *pos, size_t end)
{

	assert(*pos <= end);
	while (*pos < end) 
		advance(p, buf, pos);
}

/*
 * Output a free-form word in the input stream, progressing to the next
 * command or white-space.
 * This also will advance the input stream.
 */
static void
texiword(struct texi *p, const char *buf, size_t sz, size_t *pos)
{

	/* 
	 * XXX: if we're in literal mode, then we shouldn't do any
	 * reflowing of text here.
	 */
	if (p->outcol > 72 && ! (TEXI_LITERAL & p->flags))
		texiputchar(p, '\n');

	if (p->seenws && p->outcol && ! (TEXI_LITERAL & p->flags))
		texiputchar(p, ' ');

	p->seenws = 0;

	while (*pos < sz && ! isspace(buf[*pos])) {
		switch (buf[*pos]) {
		case ('@'):
		case ('}'):
		case ('{'):
			return;
		}
		if (*pos < sz - 1 && 
			 '`' == buf[*pos] && 
			 '`' == buf[*pos + 1]) {
			texiputchars(p, "\\(lq");
			advance(p, buf, pos);
		} else if (*pos < sz - 1 && 
			 '\'' == buf[*pos] && 
			 '\'' == buf[*pos + 1]) {
			texiputchars(p, "\\(rq");
			advance(p, buf, pos);
		} else
			texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
}

static enum texicmd
texicmd(struct texi *p, const char *buf, 
	size_t pos, size_t sz, size_t *end)
{
	size_t	 i, len;

	assert('@' == buf[pos]);
	for (*end = ++pos; *end < sz && ! isspace(buf[*end]); (*end)++)
		if ('@' == buf[*end] || '{' == buf[*end])
			break;

	len = *end - pos;
	for (i = 0; i < TEXICMD__MAX; i++) {
		if (len != texitoks[i].len)
			continue;
		if (0 == strncmp(texitoks[i].tok, &buf[pos], len))
			return(i);
	}

	texiwarn(p, "bad command: %.*s", (int)len, &buf[pos]);
	return(TEXICMD__MAX);
}

static void
parseeof(struct texi *p, const char *buf, size_t sz)
{
	size_t	 	 pos = 0;
	enum texicmd	 cmd;
	size_t	 	 end;

	while ((pos = advancenext(p, buf, sz, &pos)) < sz) {
		switch (buf[pos]) {
		case ('}'):
			texiwarn(p, "unexpected \"}\"");
			advance(p, buf, &pos);
			continue;
		case ('{'):
			texiwarn(p, "unexpected \"{\"");
			advance(p, buf, &pos);
			continue;
		case ('@'):
			break;
		default:
			texiword(p, buf, sz, &pos);
			continue;
		}

		cmd = texicmd(p, buf, pos, sz, &end);
		advanceto(p, buf, &pos, end);
		if (TEXICMD__MAX == cmd) 
			continue;
		if (NULL != texitoks[cmd].fp)
			(*texitoks[cmd].fp)(p, cmd, buf, sz, &pos);
	} 
}

static void
parsebracket(struct texi *p, const char *buf, size_t sz, size_t *pos)
{
	size_t		 end;
	enum texicmd	 cmd;

	if (*pos == sz || '{' != buf[*pos])
		return;
	advance(p, buf, pos);

	while ((*pos = advancenext(p, buf, sz, pos)) < sz) {
		switch (buf[*pos]) {
		case ('}'):
			advance(p, buf, pos);
			return;
		case ('{'):
			texiwarn(p, "unexpected \"{\"");
			advance(p, buf, pos);
			continue;
		case ('@'):
			break;
		default:
			texiword(p, buf, sz, pos);
			continue;
		}

		cmd = texicmd(p, buf, *pos, sz, &end);
		advanceto(p, buf, pos, end);
		if (TEXICMD__MAX == cmd) 
			continue;
		if (NULL != texitoks[cmd].fp)
			(*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
	}
}

static void
parseto(struct texi *p, const char *buf, 
	size_t sz, size_t *pos, const char *endtoken)
{
	size_t		 end;
	enum texicmd	 cmd;
	size_t		 endtoksz;

	endtoksz = strlen(endtoken);
	assert(endtoksz > 0);
	
	while ((*pos = advancenext(p, buf, sz, pos)) < sz) {
		switch (buf[*pos]) {
		case ('}'):
			texiwarn(p, "unexpected \"}\"");
			advance(p, buf, pos);
			continue;
		case ('{'):
			texiwarn(p, "unexpected \"{\"");
			advance(p, buf, pos);
			continue;
		case ('@'):
			break;
		default:
			texiword(p, buf, sz, pos);
			continue;
		}

		cmd = texicmd(p, buf, *pos, sz, &end);
		advanceto(p, buf, pos, end);
		if (TEXICMD_END == cmd) {
			while (*pos < sz && ' ' == buf[*pos])
				advance(p, buf, pos);
			/* 
			 * FIXME: skip tabs and also check the full
			 * word, not just its initial substring!
			 */
			if (sz - *pos >= endtoksz && 0 == strncmp
				 (&buf[*pos], endtoken, endtoksz)) {
				advanceeoln(p, buf, sz, pos);
				break;
			}
			texiwarn(p, "unexpected \"end\"");
			advanceeoln(p, buf, sz, pos);
			continue;
		} else if (TEXICMD__MAX != cmd)
			if (NULL != texitoks[cmd].fp) 
				(*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
	}
}

static void
doignblock(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	unsigned int	 sv = p->flags;
	const char	*blockname;
	
	p->flags |= TEXI_IGN;
	switch (cmd) {
	case (TEXICMD_COPYING):
		blockname = "copying";
		break;
	case (TEXICMD_DETAILMENU):
		blockname = "detailmenu";
		break;
	case (TEXICMD_DIRENTRY):
		blockname = "direntry";
		break;
	case (TEXICMD_IFHTML):
		blockname = "ifhtml";
		break;
	case (TEXICMD_IFTEX):
		blockname = "iftex";
		break;
	case (TEXICMD_MENU):
		blockname = "menu";
		break;
	case (TEXICMD_TEX):
		blockname = "tex";
		break;
	case (TEXICMD_TITLEPAGE):
		blockname = "titlepage";
		break;
	default:
		abort();
	}
	parseto(p, buf, sz, pos, blockname);
	p->flags = sv;
}

static void
doifnottex(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	
	parseto(p, buf, sz, pos, "ifnottex");
}

static void
doinline(struct texi *p, const char *buf, 
	size_t sz, size_t *pos, const char *macro)
{

	if ( ! p->outmacro)
		texifputs(p, ".");
	texiputchars(p, macro);
	texiputchar(p, ' ');
	p->seenws = 0;
	p->outmacro++;
	parsebracket(p, buf, sz, pos);
	p->outmacro--;
	if (*pos < sz - 1 && 
		 ismpunct(buf[*pos]) && 
		 isspace(buf[*pos + 1])) {
		texiputchar(p, ' ');
		texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
	if ( ! p->outmacro)
		texiputchar(p, '\n');
}

static void
doitalic(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	texiputchars(p, "\\fI");
	parsebracket(p, buf, sz, pos);
	texiputchars(p, "\\fP");
}

static void
doliteral(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	if (TEXI_LITERAL & p->flags)
		parsebracket(p, buf, sz, pos);
	else
		doinline(p, buf, sz, pos, "Li");
}

static void
doemph(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	if (TEXI_LITERAL & p->flags)
		doitalic(p, cmd, buf, sz, pos);
	else
		doinline(p, buf, sz, pos, "Em");
}

static void
docommand(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	doinline(p, buf, sz, pos, "Xr");
}

static void
dobracket(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	parsebracket(p, buf, sz, pos);
}

static void
dofile(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	if (TEXI_LITERAL & p->flags)
		parsebracket(p, buf, sz, pos);
	else
		doinline(p, buf, sz, pos, "Pa");
}

static void
doexample(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	unsigned int	sv;

	teximacro(p, ".Bd -literal");
	advanceeoln(p, buf, sz, pos);
	if ('\n' == buf[*pos])
		advance(p, buf, pos);
	sv = p->flags;
	p->flags |= TEXI_LITERAL;
	parseto(p, buf, sz, pos, "example");
	p->flags = sv;
	teximacro(p, ".Ed");
}

static void
dobye(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	texiexit(p);
	exit(EXIT_SUCCESS);
}

static void
dosymbol(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	switch (cmd) {
	case (TEXICMD_AT):
		texiputchars(p, "@");
		break;
	case (TEXICMD_COPYRIGHT):
		texiputchars(p, "\\(co");
		break;
	case (TEXICMD_LATEX):
		texiputchars(p, "LaTeX");
		break;
	case (TEXICMD_TEXSYM):
		texiputchars(p, "TeX");
		break;
	default:
		abort();
	}

	doignbracket(p, cmd, buf, sz, pos);
}

static void
doquotation(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	
	teximacro(p, ".Qo");
	parseto(p, buf, sz, pos, "quotation");
	teximacro(p, ".Qc");
}

static void
doarg1(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	if (*pos == sz || '{' != buf[*pos])
		return;
	advance(p, buf, pos);
	if ( ! p->outmacro)
		texifputs(p, ".");
	switch (cmd) {
	case (TEXICMD_EMAIL):
		texiputchars(p, "Lk ");
		break;
	case (TEXICMD_URL):
		texiputchars(p, "Mt ");
		break;
	default:
		abort();
	}
	while (*pos < sz && '}' != buf[*pos] && ',' != buf[*pos]) {
		texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
	while (*pos < sz && '}' != buf[*pos])
		advance(p, buf, pos);
	if (*pos < sz)
		advance(p, buf, pos);
	if (*pos < sz - 1 && 
		 ismpunct(buf[*pos]) && 
		 isspace(buf[*pos + 1])) {
		texiputchar(p, ' ');
		texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
	if ( ! p->outmacro)
		texiputchar(p, '\n');
}

static void
dosubsection(struct texi *p, enum texicmd cmd, 
		const char *buf, size_t sz, size_t *pos)
{

	if (TEXI_IGN & p->flags) {
		advanceeoln(p, buf, sz, pos);
		return;
	}
	while (*pos < sz && ' ' == buf[*pos]) 
		advance(p, buf, pos);
	texifputs(p, ".Pp");
	while (*pos < sz && '\n' != buf[*pos]) {
		texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
	texifputs(p, ".Pp");
}

static void
dosection(struct texi *p, enum texicmd cmd, 
		const char *buf, size_t sz, size_t *pos)
{

	if (TEXI_IGN & p->flags) {
		advanceeoln(p, buf, sz, pos);
		return;
	}
	while (*pos < sz && ' ' == buf[*pos]) 
		advance(p, buf, pos);
	texifputs(p, ".Ss ");
	while (*pos < sz && '\n' != buf[*pos]) {
		texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
	texiputchar(p, '\n');
}

static void
dosh(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	if (TEXI_IGN & p->flags) {
		advanceeoln(p, buf, sz, pos);
		return;
	}
	while (*pos < sz && ' ' == buf[*pos]) 
		advance(p, buf, pos);
	texifputs(p, ".Sh ");
	while (*pos < sz && '\n' != buf[*pos]) {
		texiputchar(p, toupper(buf[*pos]));
		advance(p, buf, pos);
	}
	texiputchar(p, '\n');
}

static void
dotop(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	p->flags &= ~TEXI_HEADER;
	advanceeoln(p, buf, sz, pos);
	teximacro(p, ".Dd $Mdocdate: February 16 2015 $");
	teximacro(p, ".Dt SOMETHING 7");
	teximacro(p, ".Os");
	teximacro(p, ".Sh NAME");
	teximacro(p, ".Nm Something");
	teximacro(p, ".Nd Something");
}

static void
doitem(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	size_t	 end;

	/* See if we have arguments... */
	for (end = *pos; end < sz; end++) 
		if (' ' != buf[end] && '\t' != buf[end])
			break;

	/* If we have arguments, print them too. */
	if ('\n' != buf[end]) {
		texifputs(p, ".It");
		/* FIXME: process commands. */
		while (*pos < sz && '\n' != buf[*pos]) {
			texiputchar(p, buf[*pos]);
			advance(p, buf, pos);
		}
		texiputchar(p, '\n');
	} else
		teximacro(p, ".It");
}

static void
dotable(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	teximacro(p, ".Bl -tag -width Ds");
	parseto(p, buf, sz, pos, "table");
	teximacro(p, ".El");
}

static void
doitemize(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	teximacro(p, ".Bl -bullet");
	parseto(p, buf, sz, pos, "itemize");
	teximacro(p, ".El");
}

static void
doignbracket(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	unsigned int	 sv = p->flags;

	p->flags |= TEXI_IGN;
	parsebracket(p, buf, sz, pos);
	p->flags = sv;
}

static void
doignline(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	advanceeoln(p, buf, sz, pos);
	if (*pos < sz) 
		advance(p, buf, pos);
}

static int
parsefile(struct texi *p, const char *fname)
{
	struct texifile	 *f;
	int		  fd;
	struct stat	  st;

	assert(p->filepos < 64);
	f = &p->files[p->filepos];
	memset(f, 0, sizeof(struct texifile));

	f->name = fname;
	if (-1 == (fd = open(fname, O_RDONLY, 0))) {
		texifatal(p, fname);
	} else if (-1 == fstat(fd, &st)) {
		close(fd);
		texifatal(p, fname);
	} 

	f->mapsz = st.st_size;
	f->map = mmap(NULL, f->mapsz,
		PROT_READ, MAP_SHARED, fd, 0);
	close(fd);

	if (MAP_FAILED == f->map) {
		texifatal(p, fname);
		return(0);
	}

	p->filepos++;
	parseeof(p, f->map, f->mapsz);
	texifilepop(p);
	return(1);
}

int
main(int argc, char *argv[])
{
	struct texi	 texi;
	int		 c, rc;
	const char	*progname;

	progname = strrchr(argv[0], '/');
	if (progname == NULL)
		progname = argv[0];
	else
		++progname;

	while (-1 != (c = getopt(argc, argv, ""))) 
		switch (c) {
		default:
			goto usage;
		}

	argv += optind;
	if (0 == (argc -= optind))
		goto usage;

	memset(&texi, 0, sizeof(struct texi));
	texi.flags = TEXI_HEADER;
	rc = parsefile(&texi, argv[0]);
	return(rc ? EXIT_SUCCESS : EXIT_FAILURE);

usage:
	fprintf(stderr, "usage: %s file\n", progname);
	return(EXIT_FAILURE);
}