[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.3, Tue Feb 17 17:02:03 2015 UTC (9 years, 1 month ago) by kristaps
Branch: MAIN
Changes since 1.2: +581 -134 lines

Clean up.

/*	$Id: main.c,v 1.3 2015/02/17 17:02:03 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 <libgen.h>
#include <limits.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_ACRONYM,
	TEXICMD_A4PAPER,
	TEXICMD_ANCHOR,
	TEXICMD_APPENDIX,
	TEXICMD_APPENDIXSEC,
	TEXICMD_ASTERISK,
	TEXICMD_AT,
	TEXICMD_AUTHOR,
	TEXICMD_BANG,
	TEXICMD_BYE,
	TEXICMD_CHAPTER,
	TEXICMD_CINDEX,
	TEXICMD_CITE,
	TEXICMD_CODE,
	TEXICMD_COLON,
	TEXICMD_COMMAND,
	TEXICMD_COMMENT,
	TEXICMD_COMMENT_LONG,
	TEXICMD_CONTENTS,
	TEXICMD_COPYING,
	TEXICMD_COPYRIGHT,
	TEXICMD_DEFTYPEFN,
	TEXICMD_DEFTYPEFNX,
	TEXICMD_DEFTYPEFUN,
	TEXICMD_DEFTYPEFUNX,
	TEXICMD_DEFTYPEVAR,
	TEXICMD_DEFTYPEVR,
	TEXICMD_DETAILMENU,
	TEXICMD_DFN,
	TEXICMD_DIRCATEGORY,
	TEXICMD_DIRENTRY,
	TEXICMD_DISPLAY,
	TEXICMD_DOTS,
	TEXICMD_EMAIL,
	TEXICMD_EMPH,
	TEXICMD_END,
	TEXICMD_ENUMERATE,
	TEXICMD_ENV,
	TEXICMD_EXAMPLE,
	TEXICMD_FILE,
	TEXICMD_GROUP,
	TEXICMD_HEADING,
	TEXICMD_HEADINGS,
	TEXICMD_HYPHEN,
	TEXICMD_I,
	TEXICMD_IFCLEAR,
	TEXICMD_IFHTML,
	TEXICMD_IFINFO,
	TEXICMD_IFNOTTEX,
	TEXICMD_IFTEX,
	TEXICMD_IFSET,
	TEXICMD_IMAGE,
	TEXICMD_INCLUDE,
	TEXICMD_ITEM,
	TEXICMD_ITEMIZE,
	TEXICMD_KBD,
	TEXICMD_LATEX,
	TEXICMD_MATH,
	TEXICMD_MENU,
	TEXICMD_NEWLINE,
	TEXICMD_NODE,
	TEXICMD_NOINDENT,
	TEXICMD_QUESTIONMARK,
	TEXICMD_QUOTATION,
	TEXICMD_PAGE,
	TEXICMD_PARINDENT,
	TEXICMD_PRINTINDEX,
	TEXICMD_REF,
	TEXICMD_SAMP,
	TEXICMD_SECTION,
	TEXICMD_SET,
	TEXICMD_SETCHAPNEWPAGE,
	TEXICMD_SETFILENAME,
	TEXICMD_SETTITLE,
	TEXICMD_SP,
	TEXICMD_SPACE,
	TEXICMD_SMALLEXAMPLE,
	TEXICMD_SQUIGGLE_LEFT,
	TEXICMD_SQUIGGLE_RIGHT,
	TEXICMD_SUBSECTION,
	TEXICMD_SUBTITLE,
	TEXICMD_TAB,
	TEXICMD_TABLE,
	TEXICMD_TEX,
	TEXICMD_TEXSYM,
	TEXICMD_TITLE,
	TEXICMD_TITLEFONT,
	TEXICMD_TITLEPAGE,
	TEXICMD_TOP,
	TEXICMD_UNNUMBERED,
	TEXICMD_UNNUMBEREDSEC,
	TEXICMD_UREF,
	TEXICMD_URL,
	TEXICMD_VAR,
	TEXICMD_W,
	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;

/*
 * Callback for functions implementing texi commands.
 */
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) */
};

enum	texilist {
	TEXILIST_NONE = 0,
	TEXILIST_ITEM,
	TEXILIST_NOITEM,
};

/*
 * The main parse structure.
 * This keeps any necessary information handy.
 */
struct	texi {
	struct texifile  files[64];
	size_t		 filepos;
	size_t		 outcol; /* column of output */
	int		 outmacro; /* whether output is in line macro */
	int		 seenws; /* whitespace has been ignored */
	int		 ign; /* don't print anything */
	int		 literal;
	char		*dir; /* texi directory */
	enum texilist	 list;
};

/* FIXME: don't use this crap. */
#define	ismpunct(_x) \
	('.' == (_x) || \
	 ',' == (_x) || \
	 ';' == (_x))
#define	isws(_x) \
	(' ' == (_x) || '\t' == (_x))

static	void doarg1(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doblock(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 dochapter(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 dodeftypefun(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dodeftypevar(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void dodisplay(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 doenumerate(struct texi *, enum texicmd, const char *, size_t, size_t *);
static	void doenv(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 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 doinclude(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 domath(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 dosp(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] = {
	{ doarg1, "acronym", 7 }, /* TEXICMD_ACRONYM */
	{ doignline, "afourpaper", 10 }, /* TEXICMD_A4PAPER */
	{ doignbracket, "anchor", 6 }, /* TEXICMD_ANCHOR */
	{ dochapter, "appendix", 8 }, /* TEXICMD_APPENDIX */
	{ dochapter, "appendixsec", 11 }, /* TEXICMD_APPENDIXSEC */
	{ dosymbol, "*", 1 }, /* TEXICMD_ASTERISK */
	{ dosymbol, "@", 1 }, /* TEXICMD_AT */
	{ doignline, "author", 6 }, /* TEXICMD_AUTHOR */
	{ dosymbol, "!", 1 }, /* TEXICMD_BANG */
	{ dobye, "bye", 3 }, /* TEXICMD_BYE */
	{ dochapter, "chapter", 7 }, /* TEXICMD_CHAPTER */
	{ doignline, "cindex", 6 }, /* TEXICMD_CINDEX */
	{ doliteral, "code", 4 }, /* TEXICMD_CODE */
	{ doitalic, "cite", 4 }, /* TEXICMD_CITE */
	{ dosymbol, ":", 1 }, /* TEXICMD_COLON */
	{ docommand, "command", 7 }, /* TEXICMD_COMMAND */
	{ doignline, "c", 1 }, /* TEXICMD_COMMENT */
	{ doignline, "comment", 7 }, /* TEXICMD_COMMENT_LONG */
	{ doignline, "contents", 8 }, /* TEXICMD_CONTENTS */
	{ doignblock, "copying", 7 }, /* TEXICMD_COPYING */
	{ dosymbol, "copyright", 9 }, /* TEXICMD_COPYRIGHT */
	{ dodeftypefun, "deftypefn", 9 }, /* TEXICMD_DEFTYPEFN */
	{ dodeftypefun, "deftypefnx", 10 }, /* TEXICMD_DEFTYPEFNX */
	{ dodeftypefun, "deftypefun", 10 }, /* TEXICMD_DEFTYPEFUN */
	{ dodeftypefun, "deftypefunx", 11 }, /* TEXICMD_DEFTYPEFUNX */
	{ dodeftypevar, "deftypevar", 10 }, /* TEXICMD_DEFTYPEVAR */
	{ dodeftypevar, "deftypevr", 9 }, /* TEXICMD_DEFTYPEVR */
	{ doignblock, "detailmenu", 10 }, /* TEXICMD_DETAILMENU */
	{ doitalic, "dfn", 3 }, /* TEXICMD_DFN */
	{ doignline, "dircategory", 11 }, /* TEXICMD_DIRCATEGORY */
	{ doignblock, "direntry", 8 }, /* TEXICMD_DIRENTRY */
	{ dodisplay, "display", 7 }, /* TEXICMD_DISPLAY */
	{ dosymbol, "dots", 4 }, /* TEXICMD_DOTS */
	{ doarg1, "email", 5 }, /* TEXICMD_EMAIL */
	{ doemph, "emph", 4 }, /* TEXICMD_EMPH */
	{ NULL, "end", 3 }, /* TEXICMD_END */
	{ doenumerate, "enumerate", 9 }, /* TEXICMD_ENUMERATE */
	{ doenv, "env", 3 }, /* TEXICMD_ENV */
	{ doexample, "example", 7 }, /* TEXICMD_EXAMPLE */
	{ dofile, "file", 4 }, /* TEXICMD_FILE */
	{ doblock, "group", 5 }, /* TEXICMD_GROUP */
	{ dosection, "heading", 7 }, /* TEXICMD_HEADING */
	{ doignline, "headings", 8 }, /* TEXICMD_HEADINGS */
	{ dosymbol, "-", 1 }, /* TEXICMD_HYPHEN */
	{ doitalic, "i", 1 }, /* TEXICMD_I */
	{ doignblock, "ifclear", 7 }, /* TEXICMD_IFCLEAR */
	{ doignblock, "ifhtml", 6 }, /* TEXICMD_IFHTML */
	{ doignblock, "ifinfo", 6 }, /* TEXICMD_IFINFO */
	{ doblock, "ifnottex", 8 }, /* TEXICMD_IFNOTTEX */
	{ doignblock, "iftex", 5 }, /* TEXICMD_IFTEX */
	{ doignblock, "ifset", 5 }, /* TEXICMD_IFSET */
	{ doignbracket, "image", 5 }, /* TEXICMD_IMAGE */
	{ doinclude, "include", 7 }, /* TEXICMD_INCLUDE */
	{ doitem, "item", 4 }, /* TEXICMD_ITEM */
	{ doitemize, "itemize", 7 }, /* TEXICMD_ITEMIZE */
	{ doliteral, "kbd", 3 }, /* TEXICMD_KBD */
	{ dosymbol, "LaTeX", 5 }, /* TEXICMD_LATEX */
	{ domath, "math", 4 }, /* TEXICMD_MATH */
	{ doignblock, "menu", 4 }, /* TEXICMD_MENU */
	{ dosymbol, "\n", 1 }, /* TEXICMD_NEWLINE */
	{ doignline, "node", 4 }, /* TEXICMD_NODE */
	{ doignline, "noindent", 8 }, /* TEXICMD_NOINDENT */
	{ dosymbol, "?", 1 }, /* TEXICMD_QUESTIONMARK */
	{ doquotation, "quotation", 9 }, /* TEXICMD_QUOTATION */
	{ doignline, "page", 4 }, /* TEXICMD_PAGE */
	{ doignline, "paragraphindent", 14 }, /* TEXICMD_PARINDENT */
	{ doignline, "printindex", 10 }, /* TEXICMD_PRINTINDEX */
	{ dobracket, "ref", 3 }, /* TEXICMD_REF */
	{ doliteral, "samp", 4 }, /* TEXICMD_SAMP */
	{ dosection, "section", 7 }, /* TEXICMD_SECTION */
	{ doignline, "set", 3 }, /* TEXICMD_SET */
	{ doignline, "setchapternewpage", 17 }, /* TEXICMD_SETCHAPNEWPAGE */
	{ doignline, "setfilename", 11 }, /* TEXICMD_SETFILENAME */
	{ dosp, "sp", 2 }, /* TEXICMD_SP */
	{ dosymbol, " ", 1 }, /* TEXICMD_SPACE */
	{ doexample, "smallexample", 12 }, /* TEXICMD_SMALLEXAMPLE */
	{ doignline, "settitle", 8 }, /* TEXICMD_SETTITLE */
	{ dosymbol, "{", 1 }, /* TEXICMD_SQUIGGLE_LEFT */
	{ dosymbol, "}", 1 }, /* TEXICMD_SQUIGGLE_RIGHT */
	{ dosubsection, "subsection", 10 }, /* TEXICMD_SUBSECTION */
	{ doignline, "subtitle", 8 }, /* TEXICMD_SUBTITLE */
	{ dosymbol, "\t", 1 }, /* TEXICMD_TAB */
	{ dotable, "table", 5 }, /* TEXICMD_TABLE */
	{ doignblock, "tex", 3 }, /* TEXICMD_TEX */
	{ dosymbol, "TeX", 3 }, /* TEXICMD_TEXSYM */
	{ doignline, "title", 5 }, /* TEXICMD_TITLE */
	{ dobracket, "titlefont", 9 }, /* TEXICMD_TITLEFONT */
	{ doignblock, "titlepage", 9 }, /* TEXICMD_TITLEPAGE */
	{ dotop, "top", 3 }, /* TEXICMD_TOP */
	{ dochapter, "unnumbered", 10 }, /* TEXICMD_UNNUMBERED */
	{ dosection, "unnumberedsec", 13 }, /* TEXICMD_UNNUMBEREDSEC */
	{ doarg1, "uref", 4 }, /* TEXICMD_UREF */
	{ doarg1, "url", 3 }, /* TEXICMD_URL */
	{ doliteral, "var", 3 }, /* TEXICMD_VAR */
	{ dobracket, "w", 1 }, /* TEXICMD_W */
};

/*
 * Unmap the top-most file that we're using.
 */
static void
texifilepop(struct texi *p)
{
	struct texifile	*f;

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

/*
 * Unmap all files that we're currently using.
 * The utility should exit(...) after this is called.
 */
static void
texiexit(struct texi *p)
{

	while (p->filepos > 0)
		texifilepop(p);
	free(p->dir);
	if (p->outcol)
		putchar('\n');
}

/*
 * Fatal error: unmap all files and exit.
 * The "errstring" is passed to perror(3).
 */
static void
texiabort(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: warning: ",
		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);
}

static void
texierr(struct texi *p, const char *fmt, ...)
{
	va_list	 ap;

	fprintf(stderr, "%s:%zu:%zu: error: ",
		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);
	texiexit(p);
	exit(EXIT_FAILURE);
}

/*
 * 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 (p->ign)
		return;
	putchar(c);
	if ('\n' == c) {
		p->outcol = 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
teximacroclose(struct texi *p)
{

	p->outmacro--;
	if (p->ign)
		return;
	texiputchar(p, '\n');
}

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

	p->outmacro++;
	if (p->ign)
		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 (p->ign)
		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 (p->literal) {
		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, int consumenl)
{

	while (*pos < sz && '\n' != buf[*pos])
		advance(p, buf, pos);
	if (*pos < sz && consumenl)
		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 (0 == p->outmacro && p->outcol > 72 && 0 == p->literal)
		texiputchar(p, '\n');

	if (p->seenws && p->outcol && 0 == p->literal)
		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]);

	if (++pos >= sz)
		return(TEXICMD__MAX);

	/* Alphabetic commands are special. */
	if ( ! isalpha(buf[pos])) {
		*end = pos + 1;
		for (i = 0; i < TEXICMD__MAX; i++) {
			if (1 != texitoks[i].len)
				continue;
			if (0 == strncmp(texitoks[i].tok, &buf[pos], 1))
				return(i);
		}
		texiwarn(p, "bad command: @%c", buf[pos]);
		return(TEXICMD__MAX);
	}

	for (*end = pos; *end < sz && ! isspace(buf[*end]); (*end)++)
		if ((*end > pos && ('@' == buf[*end] || 
			  '{' == 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 ('}'):
			if (0 == p->ign)
				texiwarn(p, "unexpected \"}\"");
			advance(p, buf, &pos);
			continue;
		case ('{'):
			if (0 == p->ign)
				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;

	while (*pos < sz && isspace(buf[*pos]))
		advance(p, buf, pos);

	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 ('{'):
			if (0 == p->ign)
				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);
	}
}

/*
 * This should be invoked when we're on a macro line and want to process
 * to the end of the current input line, doing all of our macros along
 * the way.
 */
static void
parseeoln(struct texi *p, const char *buf, size_t sz, size_t *pos)
{
	size_t		 end;
	enum texicmd	 cmd;

	assert(0 == p->literal);

	while (*pos < sz && '\n' != buf[*pos]) {
		while (*pos < sz && isws(buf[*pos])) {
			p->seenws = 1;
			advance(p, buf, pos);
		}
		switch (buf[*pos]) {
		case ('}'):
			if (0 == p->ign)
				texiwarn(p, "unexpected \"}\"");
			advance(p, buf, pos);
			continue;
		case ('{'):
			if (0 == p->ign)
				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
parsesingle(struct texi *p, const char *buf, size_t sz, size_t *pos)
{
	size_t		 end;
	enum texicmd	 cmd;

	if ((*pos = advancenext(p, buf, sz, pos)) >= sz)
		return;

	switch (buf[*pos]) {
	case ('}'):
		if (0 == p->ign)
			texiwarn(p, "unexpected \"}\"");
		advance(p, buf, pos);
		return;
	case ('{'):
		if (0 == p->ign)
			texiwarn(p, "unexpected \"{\"");
		advance(p, buf, pos);
		return;
	case ('@'):
		break;
	default:
		texiword(p, buf, sz, pos);
		return;
	}

	cmd = texicmd(p, buf, *pos, sz, &end);
	advanceto(p, buf, pos, end);
	if (TEXICMD__MAX == cmd) 
		return;
	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 ('}'):
			if (0 == p->ign)
				texiwarn(p, "unexpected \"}\"");
			advance(p, buf, pos);
			continue;
		case ('{'):
			if (0 == p->ign)
				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 && isws(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, 0);
				break;
			}
			if (0 == p->ign)
				texiwarn(p, "unexpected \"end\"");
			advanceeoln(p, buf, sz, pos, 0);
			continue;
		} else if (TEXICMD__MAX != cmd)
			if (NULL != texitoks[cmd].fp) 
				(*texitoks[cmd].fp)(p, cmd, buf, sz, pos);
	}
}

static void
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))) {
		texiabort(p, fname);
	} else if (-1 == fstat(fd, &st)) {
		close(fd);
		texiabort(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)
		texiabort(p, fname);

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

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

	blk = TEXICMD_DEFTYPEVR == cmd ? 
		"deftypevr" : "deftypevar";

	if (p->ign) {
		parseto(p, buf, sz, pos, blk);
		return;
	}

	teximacro(p, ".Pp");
	if (TEXICMD_DEFTYPEVR == cmd) {
		parsebracket(p, buf, sz, pos);
		texiputchars(p, ":\n");
	}
	p->literal++;
	teximacroopen(p, ".Vt ");
	while (*pos < sz && '\n' != buf[*pos])
		parsesingle(p, buf, sz, pos);
	teximacroclose(p);
	p->literal--;
	teximacro(p, ".Pp");
	parseto(p, buf, sz, pos, blk);
}

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

	switch (cmd) {
	case (TEXICMD_DEFTYPEFN):
		blk = "deftypefn";
		break;
	case (TEXICMD_DEFTYPEFUN):
		blk = "deftypefun";
		break;
	case (TEXICMD_DEFTYPEFNX):
	case (TEXICMD_DEFTYPEFUNX):
		blk = NULL;
		break;
	default:
		abort();
	}

	if (p->ign) {
		if (NULL != blk)
			parseto(p, buf, sz, pos, blk);
		return;
	}

	switch (cmd) {
	case (TEXICMD_DEFTYPEFN):
	case (TEXICMD_DEFTYPEFUN):
		teximacro(p, ".Pp");
		break;
	default:
		break;
	}
	if (TEXICMD_DEFTYPEFN == cmd || 
			TEXICMD_DEFTYPEFNX == cmd) {
		parsebracket(p, buf, sz, pos);
		texiputchars(p, ":\n");
	}
	teximacroopen(p, ".Ft ");
	parsesingle(p, buf, sz, pos);
	teximacroclose(p);
	teximacroopen(p, ".Fn ");
	parsesingle(p, buf, sz, pos);
	teximacroclose(p);
	teximacroopen(p, ".Li ");
	while (*pos < sz && '\n' != buf[*pos])
		parsesingle(p, buf, sz, pos);
	teximacroclose(p);
	teximacro(p, ".Pp");
	if (NULL != blk)
		parseto(p, buf, sz, pos, blk);
}

static void
doignblock(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	const char	*blk;
	
	switch (cmd) {
	case (TEXICMD_COPYING):
		blk = "copying";
		break;
	case (TEXICMD_DETAILMENU):
		blk = "detailmenu";
		break;
	case (TEXICMD_DIRENTRY):
		blk = "direntry";
		break;
	case (TEXICMD_IFCLEAR):
		blk = "ifclear";
		break;
	case (TEXICMD_IFHTML):
		blk = "ifhtml";
		break;
	case (TEXICMD_IFINFO):
		blk = "ifinfo";
		break;
	case (TEXICMD_IFSET):
		blk = "ifset";
		break;
	case (TEXICMD_IFTEX):
		blk = "iftex";
		break;
	case (TEXICMD_MENU):
		blk = "menu";
		break;
	case (TEXICMD_TEX):
		blk = "tex";
		break;
	case (TEXICMD_TITLEPAGE):
		blk = "titlepage";
		break;
	default:
		abort();
	}
	p->ign++;
	parseto(p, buf, sz, pos, blk);
	p->ign--;
}

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

	switch (cmd) {
	case (TEXICMD_GROUP):
		blk = "group";
		break;
	case (TEXICMD_IFNOTTEX):
		blk = "ifnottex";
		break;
	default:
		abort();
	}
	
	parseto(p, buf, sz, pos, blk);
}

static void
doinline(struct texi *p, const char *buf, 
	size_t sz, size_t *pos, const char *macro)
{
	int	 open = 0;

	if (0 == p->outmacro) {
		open = 1;
		teximacroopen(p, ".");
	} else
		texiputchar(p, ' ');

	texiputchars(p, macro);
	texiputchar(p, ' ');
	p->seenws = 0;
	parsebracket(p, buf, sz, pos);
	if (*pos < sz - 1 && 
		 ismpunct(buf[*pos]) && 
		 isspace(buf[*pos + 1])) {
		texiputchar(p, ' ');
		texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
	if (open)
		teximacroclose(p);
}

static void
doinclude(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	char	 fname[PATH_MAX], path[PATH_MAX];
	size_t	 i;
	int	 rc;

	while (*pos < sz && ' ' == buf[*pos])
		advance(p, buf, pos);

	/* Read in the filename. */
	for (i = 0; *pos < sz && '\n' != buf[*pos]; i++) {
		if (i == sizeof(fname) - 1)
			break;
		fname[i] = buf[*pos];
		advance(p, buf, pos);
	}

	if (i == 0)
		texierr(p, "path too short");
	else if ('\n' != buf[*pos])
		texierr(p, "path too long");
	else if ('/' == fname[0])
		texierr(p, "no absolute paths");
	fname[i] = '\0';

	if (strstr(fname, "../") || strstr(fname, "/.."))
		texierr(p, "insecure path");

	/* Append filename to original name's directory. */
	rc = snprintf(path, sizeof(path), "%s/%s", p->dir, fname);
	if (rc < 0) 
		texierr(p, "couldn't format filename");
	else if ((size_t)rc >= sizeof(path))
		texierr(p, "path too long");

	/* Pump through to parser. */
	parsefile(p, path);
}

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
doenv(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{

	if (p->literal)
		parsebracket(p, buf, sz, pos);
	else
		doinline(p, buf, sz, pos, "Ev");
}

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

	if (p->literal)
		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 (p->literal)
		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 (p->literal)
		parsebracket(p, buf, sz, pos);
	else
		doinline(p, buf, sz, pos, "Pa");
}

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

	if (p->outmacro)
		texierr(p, "display in open line scope!?");
	else if (p->literal)
		texierr(p, "display in a literal scope!?");

	teximacro(p, ".Bd -display");
	advanceeoln(p, buf, sz, pos, 1);
	parseto(p, buf, sz, pos, "display");
	teximacro(p, ".Ed");
}

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

	if (p->outmacro)
		texierr(p, "example in open line scope!?");
	else if (p->literal)
		texierr(p, "example in a literal scope!?");

	blk = TEXICMD_EXAMPLE == cmd ?  "example" : "smallexample";

	teximacro(p, ".Bd -literal");
	advanceeoln(p, buf, sz, pos, 1);
	p->literal++;
	parseto(p, buf, sz, pos, blk);
	p->literal--;
	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)
{

	if (p->seenws && p->outcol && 0 == p->literal) {
		texiputchar(p, ' ');
		p->seenws = 0;
	}

	switch (cmd) {
	case (TEXICMD_ASTERISK):
	case (TEXICMD_NEWLINE):
	case (TEXICMD_SPACE):
	case (TEXICMD_TAB):
		texiputchar(p, ' ');
		break;
	case (TEXICMD_AT):
		texiputchar(p, '@');
		break;
	case (TEXICMD_BANG):
		texiputchar(p, '!');
		break;
	case (TEXICMD_COPYRIGHT):
		texiputchars(p, "\\(co");
		break;
	case (TEXICMD_DOTS):
		texiputchars(p, "...");
		break;
	case (TEXICMD_LATEX):
		texiputchars(p, "LaTeX");
		break;
	case (TEXICMD_QUESTIONMARK):
		texiputchar(p, '?');
		break;
	case (TEXICMD_SQUIGGLE_LEFT):
		texiputchars(p, "{");
		break;
	case (TEXICMD_SQUIGGLE_RIGHT):
		texiputchars(p, "}");
		break;
	case (TEXICMD_TEXSYM):
		texiputchars(p, "TeX");
		break;
	case (TEXICMD_COLON):
	case (TEXICMD_HYPHEN):
		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");
}

/* FIXME */
static void
domath(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	size_t	 nest;

	/*
	 * Math handling is different from everything else.
	 * We don't allow any subcomponents, and we ignore the rules in
	 * terms of @-commands.
	 * This departs from GNU's rules, but whatever.
	 */
	while (*pos < sz && isws(buf[*pos]))
		advance(p, buf, pos);
	if (*pos == sz || '{' != buf[*pos])
		return;
	advance(p, buf, pos);
	if (p->seenws && p->outcol && 0 == p->literal)
		texiputchar(p, ' ');
	p->seenws = 0;
	for (nest = 1; *pos < sz && nest > 0; ) {
		if ('{' == buf[*pos])
			nest++;
		else if ('}' == buf[*pos])
			if (0 == --nest)
				continue;
		texiputchar(p, buf[*pos]);
		advance(p, buf, pos);
	}
	if (*pos == sz)
		return;
	assert('}' == buf[*pos]);
	advance(p, buf, pos);
}

/* FIXME */
static void
doarg1(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	int	 open = 0;

	if (*pos == sz || '{' != buf[*pos])
		return;
	advance(p, buf, pos);
	switch (cmd) {
	case (TEXICMD_EMAIL):
		if ( ! p->outmacro) {
			open = 1;
			teximacroopen(p, ".");
		}
		texiputchars(p, "Lk ");
		break;
	case (TEXICMD_UREF):
	case (TEXICMD_URL):
		if ( ! p->outmacro) {
			open = 1;
			teximacroopen(p, ".");
		}
		texiputchars(p, "Mt ");
		break;
	default:
		break;
	}
	/* FIXME: this is ugly */
	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 (open)
		teximacroclose(p);
}

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

	if (p->outmacro)
		texierr(p, "subsubsection in open line scope!?");
	else if (p->literal)
		texierr(p, "subsubsection in a literal scope!?");

	teximacro(p, ".Pp");
	parseeoln(p, buf, sz, pos);
	teximacro(p, ".Pp");
}

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

	if (p->outmacro)
		texierr(p, "subsection in open line scope!?");
	else if (p->literal)
		texierr(p, "subsection in a literal scope!?");

	teximacroopen(p, ".Ss ");
	parseeoln(p, buf, sz, pos);
	teximacroclose(p);
}

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

	if (p->outmacro)
		texierr(p, "spacing in open line scope!?");
	else if (p->literal)
		texierr(p, "spacing in a literal scope!?");

	teximacro(p, ".Pp");
	advanceeoln(p, buf, sz, pos, 1);
}

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

	if (p->outmacro)
		texierr(p, "section in open line scope!?");
	else if (p->literal)
		texierr(p, "section in a literal scope!?");

	teximacroopen(p, ".Sh ");
	parseeoln(p, buf, sz, pos);
	teximacroclose(p);
}

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

	p->ign--;
	advanceeoln(p, buf, sz, pos, 1);
	teximacro(p, ".Dd $Mdocdate: February 17 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)
{

	if (p->outmacro)
		texierr(p, "item in open line scope!?");
	else if (p->literal)
		texierr(p, "item in a literal scope!?");

	switch (p->list) {
	case (TEXILIST_ITEM):
		teximacroopen(p, ".It");
		break;
	case (TEXILIST_NOITEM):
		teximacro(p, ".It");
		break;
	default:
		teximacro(p, ".Pp");
		break;
	}

	parseeoln(p, buf, sz, pos);

	if (TEXILIST_ITEM == p->list)
		teximacroclose(p);
	else
		texiputchar(p, '\n');
}

static void
dotable(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	enum texilist	sv = p->list;

	if (p->outmacro)
		texierr(p, "table in open line scope!?");
	else if (p->literal)
		texierr(p, "table in a literal scope!?");

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

static void
doenumerate(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	enum texilist	 sv = p->list;

	if (p->outmacro)
		texierr(p, "enumerate in open line scope!?");
	else if (p->literal)
		texierr(p, "enumerate in a literal scope!?");

	p->list = TEXILIST_NOITEM;
	teximacro(p, ".Bl -enum");
	parseto(p, buf, sz, pos, "enumerate");
	teximacro(p, ".El");
	p->list = sv;
}

static void
doitemize(struct texi *p, enum texicmd cmd, 
	const char *buf, size_t sz, size_t *pos)
{
	enum texilist	sv = p->list;

	if (p->outmacro)
		texierr(p, "itemize in open line scope!?");
	else if (p->literal)
		texierr(p, "itemize in a literal scope!?");

	p->list = TEXILIST_ITEM;
	teximacro(p, ".Bl -bullet");
	parseto(p, buf, sz, pos, "itemize");
	teximacro(p, ".El");
	p->list = sv;
}

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

	p->ign++;
	parsebracket(p, buf, sz, pos);
	p->ign--;
}

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

	advanceeoln(p, buf, sz, pos, 1);
}

int
main(int argc, char *argv[])
{
	struct texi	 texi;
	int		 c;
	char		*path, *dir;
	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;

	if (NULL == (path = strdup(argv[0]))) {
		perror(NULL);
		exit(EXIT_FAILURE);
	} else if (NULL == (dir = dirname(path))) {
		perror(argv[0]);
		free(path);
		exit(EXIT_FAILURE);
	}
	free(path);

	memset(&texi, 0, sizeof(struct texi));
	texi.ign = 1;
	texi.dir = strdup(dir);
	parsefile(&texi, argv[0]);
	texiexit(&texi);
	return(EXIT_FAILURE);
usage:
	fprintf(stderr, "usage: %s file\n", progname);
	return(EXIT_FAILURE);
}