/**********************************************************************/
/*                                                                    */
/*	CRISP - Programmable editor                                   */
/*	===========================                                   */
/*                                                                    */
/*  File:          ninfo.c                                            */
/*  Author:        P. D. Fox                                          */
/*  Created:       1 Apr 1991                      		      */
/*                                                                    */
/*  Copyright (c) 1990, 1991 Paul Fox                                 */
/*                All Rights Reserved.                                */
/*                                                                    */
/*                                                                    */
/*--------------------------------------------------------------------*/
/*  Description:  Nroff to info convertor.                            */
/*                                                                    */
/*   This  file  is  a  mini nroff interpreter which understands the  */
/*   '-mm'   macros  I  use  for  doing  the  documentation.  It  is  */
/*   designed  to  produce output which can subsequently be fed into  */
/*   the  CRISP  help system. This is similar to makeinfo but allows  */
/*   me  to  do  what  I want without having to put TeX on my system  */
/*   (which I don't have).					      */
/*                                                                    */
/*   It  also  understands  a certain amount of 'tbl' so that it can  */
/*   produce  the  tables  I  use  in  the documentation. Because of  */
/*   this  it  is  highly  recursive  and  uses 'diversions' to keep  */
/*   track of the bits for each column.				      */
/**********************************************************************/

/* static char sccs_id[] = "%Z% %M% %R%.%L%";*/
# include	<stdio.h>
# include	"machine.h"
# if defined(BSD)
# 	include	<strings.h>
# else
# 	include	<string.h>
# endif
# include	<memory.h>
# include	<ctype.h>
# include	"ansic.h"
# include	"llist/llist.h"
# include	"foxlib/chkalloc.h"

# if !defined(TRUE)
#	define	TRUE	1
#	define	FALSE	0
# endif

enum lists {
	LIST_NONE = 0,
	LIST_AL,
	LIST_BL,
	LIST_DL,
	LIST_VL
	};
/**********************************************************************/
/*   Number of section levels we support for headers.		      */
/**********************************************************************/
# define	MAX_HEADERS	7

/**********************************************************************/
/*   Max number of arguments to a macro.			      */
/**********************************************************************/
# define	MAX_MACRO_ARGS	10

int	header_levels[MAX_HEADERS];

/**********************************************************************/
/*   Size of chunks to break the output into.			      */
/**********************************************************************/
int	split_size = 0;

/**********************************************************************/
/*   Structure  defining  the  current  type  of  environment we are  */
/*   processing.						      */
/**********************************************************************/
typedef struct env_t {
	int	right_margin;	/* Right margin.		  */
	int	line_length;	/* Current line length.		  */
	int	old_line_length;/* Previous value of line length. */
	int	indent;		/* Current left indent.		  */
	int	old_indent;	/* Previous value of indent.	  */
	int	width;		/* The width of this env.	  */
	int	fill_mode;	/* TRUE when we are filling.	  */
	int	center;		/* Number of lines needing centering.*/
	enum lists list_type;	/* Type of List.		  */
	int	list_level;	/* Current item no. in list.	  */
	int	list_indent;	/* Temporary indent for list.	  */
	int	env_level;	/* Keeps track of the level of nesting. */
	int	double_slash;	/* Inside .DS..DE and some other things */
				/* we need to double up the backslashes*/
	char	buf[BUFSIZ];	/* Buffer for building paragraph. */
	char	*bufp;
	} env_t;
	
/**********************************************************************/
/*   Structure to describe a table.				      */
/**********************************************************************/
# define	MAX_COLUMNS	256
typedef struct tbl_t {
	int	center;
	int	expand;
	int	box;
	int	allbox;
	int	columns;
	int	col_widths[MAX_COLUMNS];
	Head_p	col_text[MAX_COLUMNS];
	} tbl_t;

/**********************************************************************/
/*   Structure used to hold a macro reference.			      */
/**********************************************************************/
typedef struct macro_t {
	char	name[3];	/* Only two characters valid for a name. */
	int	size;		/* Number of bytes allocated to macro.	 */
	int	len;		/* Number of bytes in use.		 */
	char	*val;		/* Value of the macro.			 */
	} macro_t;
/**********************************************************************/
/*   Structure used to keep track of a diversion.		      */
/**********************************************************************/
typedef struct buf_t {
	char	*buf;
	int	size;
	int	used;
	int	line_number;	/* Keeps track of current line number. */
	} buf_t;
/**********************************************************************/
/*   Structure to keep track of current input file.		      */
/**********************************************************************/
typedef struct file_t {
	char	*str;		/* Pointer to macro being expanded. */
	char	*arg_string;	/* Memory allocated to arg list.    */
	char	*args[MAX_MACRO_ARGS];
				/* Pointer to macro arguments.	    */
	FILE	*fp;
	char	*filename;
	int	line_no;
	int	is_file;	/* FALSE for macros.		*/
	} file_t;

/**********************************************************************/
/*   Linked list containing current filename/line no. info.	      */
/**********************************************************************/
Head_p	hd_files;

/**********************************************************************/
/*   List  of  headers  together  with  the  line numbers they're on  */
/*   ready for the index.					      */
/**********************************************************************/
Head_p	hd_headers;
typedef struct hdr_t {
	int	line_no;	/* Line number where section starts.	    */
	int	level;		/* Number of places in dewey decimal system */
	char	*header;	/* Contents of the header line.		    */
	} hdr_t;

/**********************************************************************/
/*   Structure  used  to  keep  track  of lines which are marked for  */
/*   output (when using the split option.			      */
/**********************************************************************/
typedef struct sstop_t {
	long	start_line;	/* Line to start output from.		*/
	long	end_line;	/* Line to end output on.		*/
	char	*entry;		/* Name of this entry.			*/
	} sstop_t;
Head_p	hd_sstop;
sstop_t	*stopp;
/**********************************************************************/
/*   List to keep track of the environments we are building.	      */
/**********************************************************************/
Head_p	hd_env;
env_t	starting_env = {
	60,
	60, 0,
	0, 0,
	0
	};
env_t	*env_p;

FILE	*in_fp;
char	*output_file = NULL;
buf_t	bufp;
int	print_line_numbers = FALSE;

/**********************************************************************/
/*   Push-down stack for output buffers.			      */
/**********************************************************************/
Head_p	hd_bufs;

/**********************************************************************/
/*   Keep  a  linked  list  of macro definitions. Its slow but we're  */
/*   not worried about defining lots of macros (a la -mm or -ms).     */
/**********************************************************************/
Head_p	hd_macros;
macro_t	*current_macro;

/**********************************************************************/
/*   Name of file to receive the index.				      */
/**********************************************************************/
char	*index_file = NULL;

/**********************************************************************/
/*   Set to TRUE whilst defining macro.				      */
/**********************************************************************/
int	defining_macro = FALSE;
/**********************************************************************/
/*   Prototypes							      */
/**********************************************************************/
int	check_ninfo_comment PROTO((char *));
void	write_output_file PROTO((void));
void	define_macro PROTO((char *));
char	**macro_args PROTO((void));
void	add_to_macro PROTO((char *));
int	lookup_macro PROTO((char *));
file_t	*setup_str PROTO((char *, char *));
void	indent_index PROTO((FILE *, int, hdr_t *));
void	create_index PROTO((void));
void	print_table_bar PROTO((tbl_t *, int));
void	free_table PROTO((tbl_t *));
buf_t	*steal_buf PROTO((void));
void	tab_to_left_margin PROTO((void));
int	read_line PROTO((char *, int));
void	buf_outc PROTO((int));
void	buf_outs PROTO((char *));
int	do_table PROTO((char *));
int	do_list_element PROTO((char *));
void	push_env PROTO((void));
void	pop_env PROTO((void));
void	exit PROTO((int));
int	do_header PROTO((char *));
int	newline PROTO((void));
int	get_num PROTO((char *, int));
int	atoi PROTO((char *));
int	output_char PROTO((int));
int	do_break PROTO((int));
int	do_switches PROTO((int, char **));
void	usage PROTO((void));
int	setup_file PROTO((char *, FILE *));
void	error PROTO((char *));
int	process_file PROTO((void));
char	*process_column PROTO((char *, char *, int));
int	close_files PROTO((void));
int	process_dot PROTO((char *));
int	process_line PROTO((char *));
char	*strdup();
char	*strchr();

int
main(argc, argv)
int	argc;
char	**argv;
{	int	arg_index = do_switches(argc, argv);

	if (output_file == NULL && split_size != 0) {
		fprintf(stderr, "Must use the -o switch when using the -s switch.\n\n");
		usage();
		}
	hd_files = ll_init();
	hd_env = ll_init();
	hd_bufs = ll_init();
	hd_headers = ll_init();
	hd_macros = ll_init();
	hd_sstop = ll_init();
	
	env_p = &starting_env;
	env_p->bufp = env_p->buf;
	bufp.line_number = 1;

	if (arg_index >= argc) {
		setup_file("stdin", stdin);
		process_file();
		}
	else {
		while (arg_index < argc) {
			setup_file(argv[arg_index++], NULL);
			process_file();
			}
		}
	do_break(TRUE);
	/***********************************************/
	/*   Make  sure  we  terminate  file  with  a  */
	/*   new-line character.		       */
	/***********************************************/
	if (bufp.buf[bufp.used-1] != '\n')
		buf_outc('\n');
	write_output_file();
	
	/***********************************************/
	/*   Now create the index file.		       */
	/***********************************************/
	create_index();
	return 0;
}
/**********************************************************************/
/*   Write  out  the  output  file.  If user wants output file split  */
/*   into  chunks  then  he  must  have  used  the  NINFO:start/stop  */
/*   directives so we know what to discard.			      */
/**********************************************************************/
void
write_output_file()
{	register List_p	lp;
	register sstop_t *sp;
	register char *cp;
	FILE	*fp = NULL;
	FILE	*fp_contents;
	int	part_no = 0;
	char	*end_buf;
	int	line = 1;
	int	oline = 1;
	int	obytes;
	char	buf[BUFSIZ];
	sstop_t	sbuf;
	
	/***********************************************/
	/*   If  he's  not  splitting  then write the  */
	/*   whole lot out.			       */
	/***********************************************/
	if (split_size == 0) {
		write(1, bufp.buf, bufp.used);
		return;
		}
		
	/***********************************************/
	/*   The  '.000'  file  contains the table of  */
	/*   contents.				       */
	/***********************************************/
	sprintf(buf, "%s.000", output_file);
	fp_contents = fopen(buf, "w");
	if (fp_contents == NULL) {
		perror(buf);
		exit(1);
		}
	cp = bufp.buf;
	end_buf = bufp.buf + bufp.used;
	lp = ll_first(hd_sstop);
	/***********************************************/
	/*   Force  us  to open the first file during  */
	/*   the loop.				       */
	/***********************************************/
	sbuf.end_line = -1;
	sp = &sbuf;
	obytes = split_size + 1;
	while (lp && cp < end_buf) {
		if (line > sp->end_line) {
			lp = sp == &sbuf ? ll_first(hd_sstop) : ll_next(lp);
			if (lp == NULL)
				break;
			sp = (sstop_t *) ll_elem(lp);
			/***********************************************/
			/*   If  this  output file is too large, then  */
			/*   go onto the next one.		       */
			/***********************************************/
			if (obytes >= split_size) {
				if (fp)
					fclose(fp);
				sprintf(buf, "%s.%03d", output_file, ++part_no);
				fp = fopen(buf, "w");
				if (fp == NULL) {
					perror(buf);
					exit(1);
					}
				obytes = 0;
				oline = 1;
				}
			fprintf(fp_contents, "%s: %s.%03d %d %d\n",
				sp->entry, output_file, 
				part_no,
				oline, oline + (sp->end_line - sp->start_line));
			}
		if (line >= sp->start_line && line <= sp->end_line) {
			while (*cp != '\n') {
				putc(*cp, fp);
				cp++;
				obytes++;
				}
			putc('\n', fp);
			obytes++;
			oline++;
			}
		else {
			while (*cp != '\n')
				cp++;
			}
		cp++;
		line++;
		}
	fclose(fp_contents);
	fclose(fp);
}
/**********************************************************************/
/*   Function  to  write  out the index file if we were given a file  */
/*   name to write to.						      */
/**********************************************************************/
void
create_index()
{	FILE	*fp;
	List_p	lp;
	hdr_t	*hp;
	int	level = 1;
	int	j;
	hdr_t	*last_hp = NULL;
	hdr_t	*next_hp;
	int     num_lines = 0;

	if (index_file == NULL)
		return;
	if ((fp = fopen(index_file, "w")) == NULL) {
		perror(index_file);
		return;
		}

	fprintf(fp, "/**************************************/\n");		
	fprintf(fp, "/* Following list generated by ninfo. */\n");		
	fprintf(fp, "/**************************************/\n");		
	fprintf(fp, "list crisp_index = {\n");
	/***********************************************/
	/*   We  need  to  write  out  all entries at  */
	/*   the same level together.		       */
	/***********************************************/
	for (lp = ll_first(hd_headers); lp; ) {
		hp = (hdr_t *) ll_elem(lp);
		indent_index(fp, level, hp);
		if (level < hp->level && last_hp) {
			fprintf(fp, "\"%s\", %d, %d,\n", 
				last_hp->header,
				last_hp->line_no,
				num_lines);
			for (j = 0; j < hp->level; j++)
				fprintf(fp, "    ");
			}
		level = hp->level;
		lp = ll_next(lp);
		if (lp) {
			next_hp = (hdr_t *) ll_elem(lp);
			num_lines = next_hp->line_no - hp->line_no;
			if (num_lines < 10)
				num_lines = 50;
			}
		else {
			next_hp = NULL;
			num_lines = 100;
			}
		if (next_hp && next_hp->level > hp->level)
			fprintf(fp, "\"%s\", NULL,\n", hp->header);
		else
			fprintf(fp, "\"%s\", %d, %d,\n", 
					hp->header,
					hp->line_no,
					num_lines);
		last_hp = hp;
		}
	while (level-- > 1) {
		for (j = 0; j <= level; j++)
			fprintf(fp, "    ");
		fprintf(fp, "},\n");
		}
	fprintf(fp, "};\n");
	fclose(fp);
}
void
indent_index(fp, level, hp)
FILE	*fp;
int	level;
hdr_t	*hp;
{	int	j;
	int	l;
	
	while (1) {
		l = hp->level;
		if (hp->level < level)
			l = level;
		for (j = 0; j < l; j++)
			fprintf(fp, "    ");
		if (hp->level > level) {
			fprintf(fp, "{");
			}
		else if (hp->level < level) {
			fprintf(fp, "},\n");
			for (j = 0; j < hp->level; j++)
				fprintf(fp, "    ");
			}
		if (level-- <= hp->level)
			return;
		fprintf(fp, "\n");
		}
}
/**********************************************************************/
/*   Function to open a file and start processing it.		      */
/**********************************************************************/
static file_t	null_file;
int
setup_file(filename, fp)
char	*filename;
FILE	*fp;
{	file_t	*fip;
	
	if (fp == NULL) {
		fp = fopen(filename, "r");
		if (fp == NULL) {
			fprintf(stderr, "Cannot read file %s\n", filename);
			exit(1);
			}
		}
	fip = (file_t *) chk_alloc(sizeof (file_t));
	*fip = null_file;
	fip->filename = strdup(filename);
	fip->is_file = TRUE;
	fip->fp = fp;
	
	ll_push(hd_files, (char *) fip);
	return TRUE;	
}
/**********************************************************************/
/*   Setup a macro as a soure of input (macro expansion).	      */
/**********************************************************************/
file_t	*
setup_str(name, str)
char	*name;
char	*str;
{	file_t	*fip;

	fip = (file_t *) chk_alloc(sizeof (file_t));
	*fip = null_file;
	fip->filename = name;
	fip->str = str;
	fip->is_file = FALSE;
	
	ll_push(hd_files, (char *) fip);
	return fip;
}
/**********************************************************************/
/*   Close currently open file and popup back to where we came from.  */
/**********************************************************************/
int
close_file()
{	file_t	*fip;
	List_p	lp;
	
	lp = ll_first(hd_files);
	fip = (file_t *) ll_elem(lp);
	if (fip->is_file) {
		fclose(fip->fp);
		chk_free(fip->filename);
		}
	if (fip->arg_string)
		chk_free((void *) fip->arg_string);
	ll_pop(hd_files);
	if (ll_first(hd_files))
		return TRUE;
	return FALSE;
}
void
error(str)
char	*str;
{	file_t	*fip;
	List_p	lp;
	
	lp = ll_first(hd_files);
	if (lp == NULL) {
		fprintf(stderr, "%s\n", str);
		}
	else {
		fip = (file_t *) ll_elem(lp);
		if (fip->is_file) {
			fprintf(stderr, "%s:%d: %s\n", 
				fip->filename, fip->line_no, str);
			}
		else
			fprintf(stderr, "Macro %s:%d: %s\n",
				fip->filename, fip->line_no, str);
		}
}
int
do_switches(argc, argv)
int	argc;
char	**argv;
{	int	i;
	char	*cp;
	
	for (i = 1; i < argc; i++) {
		cp = argv[i];
		if (*cp++ != '-')
			return i;
		while (*cp) {
			switch (*cp++) {
			  case 'i':
			  	index_file = argv[++i];
				break;
			  case 'l':
			  	print_line_numbers = TRUE;
			  	break;
			  case 's':
			  	split_size = atoi(argv[++i]);
			  	break;
			  case 'o':
			  	output_file = argv[++i];
				break;
			  default:
			  	usage();
			  }
			}
		}
	return i;
}
void
usage()
{	
	fprintf(stderr, "ninfo -- nroff to crisp info file format.\n");
	fprintf(stderr, "Usage: ninfo [-o ofile] [-i index-file] [-l] [-s nn] file\n");
	fprintf(stderr, "	-i file		Name of index file to create.\n");
	fprintf(stderr, "	-l		Print line numbers on output file.\n");
	fprintf(stderr, "	-o ofile	Specifies prefix of output file when splitting files.\n");
	fprintf(stderr, "	-s nn		Split output file into nn size chunks (nn is byte count).\n");
	exit(1);
}
int
process_file()
{
	char	buf[BUFSIZ];
	
	while (read_line(buf, sizeof buf) >= 0) {
		if (defining_macro) {
			add_to_macro(buf);
			continue;
			}
		if (buf[0] == '.') {
			process_dot(buf);
			continue;
			}
		process_line(buf);
		}
	return 0;
}
/**********************************************************************/
/*   Return pointer to argument list for current macro expansion.     */
/**********************************************************************/
char **
macro_args()
{	file_t	*fip;
	List_p	lp;
	lp = ll_first(hd_files);
	if (lp == NULL)
		return NULL;
	fip = (file_t *) ll_elem(lp);
	return !fip->is_file ? fip->args : (char **) NULL;
}
/**********************************************************************/
/*   Function  to  read  a line -- either from a file or the current  */
/*   macro expansion.						      */
/**********************************************************************/
int
read_line(buf, len)
char	*buf;
int	len;
{	file_t	*fip;
	List_p	lp;

	while ((lp = ll_first(hd_files)) != NULL) {
		fip = (file_t *) ll_elem(lp);
		if (fip->is_file) {
			if (feof(fip->fp))
				return -1;
			/***********************************************/
			/*   If  we  reach  end  of  this  file, then  */
			/*   continue   using   any  previous  source  */
			/*   files.				       */
			/***********************************************/
			if (fgets(buf, len, fip->fp) != NULL) {
				fip->line_no++;
				return 0;
				}
			if (close_file() == FALSE)
				return -1;
			continue;
			}
		/***********************************************/
		/*   Copy  a  line  over  to  the destination  */
		/*   buffer.				       */
		/***********************************************/
		fip->line_no++;
		while (*fip->str && *fip->str != '\n' && len > 1) {
			*buf++ = *fip->str++;
			len--;
			}
		if ((*buf = *fip->str) != NULL)
			buf[1] = NULL;
		if (*fip->str == NULL) {
			if (close_file() == FALSE)
				return -1;
			}
		else
			fip->str++;
		return 0;
		}
	return -1;

}
/**********************************************************************/
/*   Process line of text, stripping out font and point changes.      */
/**********************************************************************/
int
process_line(str)
char	*str;
{	char	**args;

	if (*str == '\n') {
	  	do_break(FALSE);
		newline();
		return 0;
		}
	while (*str) {
		if (*str != '\\') {
			if (*str == '\n') {
				output_char(' ');
				if (!env_p->fill_mode)
					do_break(TRUE);
				str++;
				}
			else
				output_char(*str++);
			continue;
			}
		str++;
		switch (*str++) {
		  case 'f':
		  	if (*str == '(') {
				str++;
				if (*str)
					str++;
				}
			if (*str)
				str++;
		  	break;
		  case 's':
		  	if (*str == '+' || *str == '-')
				str++;
			if (*str)
				str++;
		  	break;
		  case '\\':
		  	if ((args = macro_args()) != NULL && 
			    *str == '$' && isdigit(str[1]) && str[1] != '0') {
			    	int	arg = str[1] - '1';
				char	*cp;
				cp = args[arg];
				if (cp == NULL)
					cp = "";
				str += 2;
				while (*cp)
					output_char(*cp++);
				break;
				}
			/* Fallthru... */
		  default:
		  	if (env_p->double_slash == 0)
				output_char(str[-1]);
			else {
				if (*str == '\\' && str[1] == '\\') {
					output_char('\\');
					str += 2;
					}
				}
		  	break;
		  }
		}
	return 0;
}
/**********************************************************************/
/*   Function  to  save  the  current  environment.  Called  when we  */
/*   start doing lists to save the indentation, etc.		      */
/**********************************************************************/
void
push_env()
{	env_t	*ep;

	do_break(FALSE);
	ep = (env_t *) chk_alloc(sizeof (env_t));
	*ep = *env_p;
	ll_push(hd_env, (char *) ep);
	env_p->env_level++;
}
/**********************************************************************/
/*   Function  called  to pop the environment. Called when we exit a  */
/*   list.							      */
/**********************************************************************/
void
pop_env()
{	env_t	*ep;

	do_break(FALSE);
	if (ll_first(hd_env) == NULL) {
		error(".LE not inside a list.");
		return;
		}
	ep = (env_t *) ll_elem(ll_first(hd_env));
	*env_p = *ep;
	chk_free((char *) ep);
	ll_pop(hd_env);
}
/**********************************************************************/
/*   Function to save the current output buffer.		      */
/**********************************************************************/
void
push_buf()
{	buf_t	*bp = (buf_t *) chk_alloc(sizeof(buf_t));

	*bp = bufp;
	ll_push(hd_bufs, (char *) bp);

	memset((char *) &bufp, 0, sizeof bufp);
}
/**********************************************************************/
/*   Restore an output buffer.					      */
/**********************************************************************/
void
pop_buf()
{	buf_t	*bp;

	bp = (buf_t *) ll_elem(ll_first(hd_bufs));
	bufp = *bp;
	chk_free((char *) bp);
	ll_pop(hd_bufs);
}
/**********************************************************************/
/*   Steal  the  current  contents  of the output buffer and replace  */
/*   with  a  fresh new buffer. Called when we need to save away the  */
/*   contents of each column and line in a table.		      */
/**********************************************************************/
buf_t *
steal_buf()
{	buf_t	*bp = (buf_t *) chk_alloc(sizeof(buf_t));
	*bp = bufp;
	memset((char *) &bufp, 0, sizeof bufp);
	return bp;
}
/**********************************************************************/
/*   Function to write a character to the output buffer.	      */
/**********************************************************************/
void
buf_outc(ch)
int	ch;
{
	if (ch == '\n')
		bufp.line_number++;
	if (bufp.buf == NULL) {
		bufp.buf = (char *) chk_alloc(BUFSIZ);
		bufp.size = BUFSIZ;
		bufp.used = 0;
		}
	else if (bufp.used + 1 >= bufp.size) {
		bufp.size *= 2;
		bufp.buf = (char *) chk_realloc(bufp.buf, bufp.size);
		}
	bufp.buf[bufp.used++] = ch;
}
/**********************************************************************/
/*   Function to write a string to the output buffer.		      */
/**********************************************************************/
void
buf_outs(str)
char	*str;
{	int	len = strlen(str);
	char	*cp;

	if (len == 0)
		return;	
	if (bufp.buf == NULL) {
		bufp.buf = (char *) chk_alloc(BUFSIZ);
		bufp.size = BUFSIZ;
		bufp.used = 0;
		}
	else if (bufp.used + len + 1 >= bufp.size) {
		bufp.size += bufp.size + len + 16;
		bufp.buf = (char *) chk_realloc(bufp.buf, bufp.size);
		}
	memcpy(bufp.buf + bufp.used, str, len);
	bufp.used += len;
	
	for (cp = strchr(str, '\n'); cp; cp = strchr(cp+1, '\n'))
		bufp.line_number++;
}
/**********************************************************************/
/*   Output a newline and move over to the left margin.		      */
/**********************************************************************/
int
newline()
{

	buf_outc('\n');
	tab_to_left_margin();
	return 0;
}
void
tab_to_left_margin()
{	register int i;
	for (i = 0; i < env_p->indent; i++)
		buf_outc(' ');
}
/**********************************************************************/
/*   Output a character to the current paragraph.		      */
/**********************************************************************/
int
output_char(ch)
int	ch;
{	char	*cp, *cp1;
	char	*words[256];
	int	spaces[256];
	int	windex;
	int	len, i;
	static	where = 0;
	char	buf[BUFSIZ];
		
	*env_p->bufp++ = ch;
	if (env_p->bufp - env_p->buf <= env_p->right_margin)
		return 0;
	*env_p->bufp = NULL;
	cp = env_p->bufp-1;
	while (cp > env_p->buf) {
		if (*cp == ' ')
			break;
		cp--;
		}
	
	/***********************************************/
	/*   If  we  didn't  find  a space, then word  */
	/*   must  be  wider  than margin, and theres  */
	/*   little  we  can  do.  So we will have to  */
	/*   split the word.			       */
	/***********************************************/
	if (cp <= env_p->buf) {
		int	ch;
		cp = &env_p->buf[env_p->right_margin+1];
		ch = *cp;
		*cp = NULL;
		buf_outs(env_p->buf);
		newline();
		*cp = ch;
		strcpy(env_p->buf, cp);
		env_p->bufp = env_p->buf;
		return 0;
		}		
	/***********************************************/
	/*   Find  out  how many words we have on the  */
	/*   line first.			       */
	/***********************************************/
	for (windex = 0, len = 0, cp1 = strtok(env_p->buf, " "); cp1; ) {
		words[windex] = cp1;
		len += strlen(cp1) + 1;
		spaces[windex++] = 1;
		if (cp1 + strlen(cp1) >= cp)
			break;
		cp1 = strtok((char *) NULL, " ");
		}

	/***********************************************/
	/*   Make  sure  we  don't  print  any spaces  */
	/*   after the last word.		       */
	/***********************************************/
	spaces[windex-1] = 0;
	len--;
	
	/***********************************************/
	/*   Keep  trying  to  sprinkle space between  */
	/*   words.				       */
	/***********************************************/
	while (len < env_p->right_margin && windex > 2) {
		if (where++ & 1) {
			for (i = 0; i < windex - 1 && len < env_p->right_margin; i++) {
				len++;
				spaces[i]++;
				}
			}
		else {
			for (i = windex - 2; i > 0 && len < env_p->right_margin; i--) {
				len++;
				spaces[i]++;
				}
			}
		}
	/***********************************************/
	/*   If  we've  got  one or two words on line  */
	/*   then  add  any  extra  space after first  */
	/*   word.				       */
	/***********************************************/
	if (windex <= 2)
		spaces[0] += env_p->right_margin - len;
		
	/***********************************************/
	/*   Output each word plus its spaces.	       */
	/***********************************************/
	for (i = 0; i < windex; i++) {
		strcpy(buf, words[i]);
		cp1 = buf + strlen(buf);
		while (spaces[i]-- > 0)
			*cp1++ = ' ';
		*cp1 = NULL;
		buf_outs(buf);
		}
	newline();
	strcpy(env_p->buf, cp+1);
	env_p->bufp = env_p->buf + strlen(env_p->buf);
	return 0;
}
/**********************************************************************/
/*   Output any internally stored data.				      */
/**********************************************************************/
int
do_break(do_nl)
int	do_nl;
{	int	num_spaces;

	*env_p->bufp = NULL;
	if (env_p->center) {
		num_spaces = (env_p->right_margin - strlen(env_p->buf) - 1) / 2;
		/***********************************************/
		/*   Following  line  corrects a bug in GNU C  */
		/*   v1.39.0				       */
		/***********************************************/
		if (num_spaces > 10000)
			num_spaces = -1;
		while (num_spaces-- > 0)
			buf_outc(' ');
		env_p->center--;
		}
	buf_outs(env_p->buf);
	if (do_nl || env_p->buf[0])
		newline();
	env_p->bufp = env_p->buf;
	return 0;
}
/**********************************************************************/
/*   Function to handle a .H macro.				      */
/**********************************************************************/
int
do_header(str)
char	*str;
{	int	level = atoi(str);
	int	i;
	char	buf[BUFSIZ];
	hdr_t	*hp;
	
	while (isdigit(*str))
		str++;
	while (*str == ' ')
		str++;
	if (*str == '"')
		str++;

	if (str[strlen(str)-1] == '\n')
		str[strlen(str)-1] = NULL;
	if (str[strlen(str)-1] == '"')
		str[strlen(str)-1] = NULL;
	do_break(FALSE);
	newline();
	for (i = level; i < MAX_HEADERS; i++)
		header_levels[i] = 0;
	header_levels[level-1]++;
	
	/***********************************************/
	/*   Create   the   header   numbers.  Header  */
	/*   numbers   don't   have  a  trailing  dot  */
	/*   unless theres only one level of number.   */
	/***********************************************/
	buf[0] = NULL;
	for (i = 0; header_levels[i]; i++)
		sprintf(buf + strlen(buf), "%s%d", 
			i == 0 ? "" : ".", header_levels[i]);
	if (i == 1)
		strcat(buf, ". ");
	else
		strcat(buf, " ");
	strcat(buf, str);
	
	/***********************************************/
	/*   Save the header for the index.	       */
	/***********************************************/
	hp = (hdr_t *) chk_alloc(sizeof (hdr_t));
	hp->line_no = bufp.line_number;
	hp->level = level;
	hp->header = strdup(buf);
	ll_append(hd_headers, (char *) hp);
	buf_outs(buf);
	newline();
	return 0;
}
/**********************************************************************/
/*   Get a number allowing relative values to be specified.	      */
/**********************************************************************/
int
get_num(str, val)
char	*str;
int	val;
{	int	newval;
	int	sign;
	int	frac = 0;
	
	if (*str == '+')
		sign = *str++;
	else if (*str == '-')
		sign = *str++;
	else
		sign = 0;
	newval = atoi(str);
	while (isdigit(*str))
		str++;
	if (*str == '.')
		frac = atoi(++str);
	while (isdigit(*str))
		str++;
	if (*str == 'i') {
		newval = 10 * newval + frac;
		}

	if (sign == '+')
		return val + newval;
	if (sign == '-')
		return val - newval;
	return newval;
	
}
int
do_list_element(str)
char	*str;
{	char	buf[BUFSIZ];
	char	*cp;
	
	do_break(FALSE);
	env_p->indent -= env_p->list_indent;
	newline();
	env_p->indent += env_p->list_indent;
	
	/***********************************************/
	/*   Ignore leading white space.	       */
	/***********************************************/
	while (*str == ' ' || *str == '\t')
		str++;
	/***********************************************/
	/*   Delete quotes if they are there.	       */
	/***********************************************/
	if (*str == '"') {
		str++;
		cp = str + strlen(str) - 1;
		while (cp > str && *cp != '"')
			cp--;
		if (*cp == '"')
			*cp = NULL;
		}
	switch (env_p->list_type) {
	  case LIST_AL:
	  	sprintf(buf, " %d. ", ++env_p->list_level);
	  	break;
	  case LIST_BL:
	  	sprintf(buf, " o  ", ++env_p->list_level);
	  	break;
	  case LIST_DL:
	  	sprintf(buf, " -  ", ++env_p->list_level);
	  	break;
	  case LIST_VL:
	  	str[strlen(str)-1] = NULL;
	  	sprintf(buf, " %*s ", -(env_p->list_indent - 2), str);
	  	break;
	  default:
	  	error(".LI not inside a list.");
		return 0;
	  }
	buf_outs(buf);
	return 0;
}
/**********************************************************************/
/*   Function  to  parse  a  'tbl' style table and process the lines  */
/*   and columns.						      */
/**********************************************************************/
int
do_table(str)
char	*str;
{
	char	buf[BUFSIZ];
	tbl_t	tbl;
	char	*cp, *cp1;
	int	cols, col;
	int	col_width;
	int	cols_to_do;
	int	i, j, k;
	buf_t	*bp;
	int	line;
	int	seen_dot;
	int	block_no;
	int	do_line;
			
	do_break(TRUE);
	newline();
	push_env();
	push_buf();
	memset((char *) &tbl, 0, sizeof tbl);
	
	if (read_line(buf, sizeof buf) < 0) {
		error("Unterminated table");
		return 0;
		}
		
	for (cp = strtok(buf, " ;"); cp; cp = strtok((char *) NULL, " ;")) {
		if (strcmp(cp, "expand") == 0)
			tbl.expand = TRUE;
		else if (strcmp(cp, "box") == 0)
			tbl.box = TRUE;
		else if (strcmp(cp, "allbox") == 0) {
			tbl.allbox = TRUE;
			tbl.box = TRUE;
			}
		else if (strcmp(cp, "center") == 0)
			tbl.center = TRUE;
		}
	while (1) {
		if (read_line(buf, sizeof buf) < 0) {
			error("Unterminated table");
			return 0;
			}
		if (buf[strlen(buf)-1] == '\n')
			buf[strlen(buf)-1] = NULL;
		/***********************************************/
		/*   Evaluate the column descriptors.	       */
		/***********************************************/
		seen_dot = FALSE;
		for (cols = 0, cp = strtok(buf, " \t"); cp && !seen_dot; cp = strtok((char *) NULL, " \t")) {
			while (*cp) {
				switch (*cp++) {
			  	  case 'w':
				  	if (*cp == '(') {
						tbl.col_widths[cols] = get_num(cp+1, 0);
						while (*cp && *cp != ')')
							cp++;
						}
				  	break;
				  case '.':
				  	seen_dot = TRUE;
					break;
				  }
			  	}
			cols++;
			}
		if (cols > tbl.columns)
			tbl.columns = cols;
		if (seen_dot)
			break;
		}
		
	/***********************************************/
	/*   Now calculate column widths.	       */
	/***********************************************/
	cols_to_do = 0;
	for (i = 0, col_width = 0; i < tbl.columns; i++) {
		if (tbl.col_widths[i] == 0)
			cols_to_do++;
		else
			col_width += tbl.col_widths[i];
		}
	if (cols_to_do == 0)
		cols_to_do = 1;
	col_width = (env_p->right_margin - tbl.columns - col_width - 2) 
			/ cols_to_do;
	for (i = 0; i < tbl.columns; i++) {
		tbl.col_text[i] = ll_init();
		if (tbl.col_widths[i] == 0)
			tbl.col_widths[i] = col_width;
		}
	block_no = 0;
	do_line = TRUE;
	while (read_line(buf, sizeof buf) >= 0) {
		char	colbuf[BUFSIZ];
		char	*new_cp;
		block_no++;
		/***********************************************/
		/*   If   user   wants  a  single  or  double  */
		/*   height   line   across  the  table  then  */
		/*   create  some  pseudo  columns with these  */
		/*   filled in.				       */
		/***********************************************/
		if (buf[0] == '=' || buf[0] == '_') {
			for (col = 0; col < tbl.columns; col++) {
				memset(buf, buf[0], tbl.col_widths[col]);
				buf[tbl.col_widths[col]] = NULL;
				bp = (buf_t *) chk_alloc(sizeof(buf_t));
				bp->buf = strdup(buf);
				bp->used = strlen(buf);
				ll_append(tbl.col_text[col], (char *) bp);
				}
			do_line = FALSE;
			continue;
			}
		if (buf[0] == '.' && buf[1] == 'T' && buf[2] == 'E')
			break;
		if (buf[0] == '.') {
			process_dot(buf);
			continue;
			}
		for (col = 0, cp = buf; cp && tbl.col_text[col]; 
		     col++) {
			int	ch;
			/***********************************************/
			/*   Put  in  a  line on top of this block if  */
			/*   user asked for boxes.		       */
			/***********************************************/
			if (tbl.allbox && block_no != 1 && do_line) {
				char	tmpbuf[BUFSIZ];
				memset(tmpbuf, '_', tbl.col_widths[col]);
				tmpbuf[tbl.col_widths[col]] = NULL;
				bp = (buf_t *) chk_alloc(sizeof(buf_t));
				bp->buf = strdup(tmpbuf);
				bp->used = strlen(tmpbuf);
				ll_append(tbl.col_text[col], (char *) bp);
				}
			for (cp1 = cp; *cp1 != '\n' && *cp1 && *cp1 != '\t'; )
				cp1++;
			if (*cp1 == '\n')
				*cp1 = NULL;
			ch = *cp1;
			*cp1 = NULL;
			
			env_p->indent = 0;
			env_p->right_margin = tbl.col_widths[col] - 3;
			env_p->line_length = env_p->right_margin;
			new_cp = process_column(cp, colbuf, sizeof colbuf);
			do_break(FALSE);
			bp = steal_buf();
			if (bp->buf)
				bp->buf[bp->used] = NULL;
			ll_append(tbl.col_text[col], (char *) bp);
			*cp1 = ch;
			if (new_cp) {
				cp = new_cp;
				if (*cp == '\t')
					cp++;
				}
			else {
				if (ch)
					cp = cp1+1;
				else
					break;
				}
			}
		do_line = TRUE;
		}
		
	pop_env();
	pop_buf();
	/***********************************************/
	/*   Output  the  top  line  of  the  box, if  */
	/*   user specified the box or allbox option.  */
	/***********************************************/
	print_table_bar(&tbl, '=');
	/***********************************************/
	/*   Now try and print the table.	       */
	/***********************************************/
	for (j = 0; ; j++) {
		int	blanks = 0;
		int	do_line = TRUE;
		char	line_buf[BUFSIZ];
		char	*linep;
		for (line = 0; blanks < tbl.columns; line++) {
			int	done_left = FALSE;
			blanks = 0;
			linep = line_buf;
			for (i = 0; i < tbl.columns; i++) {
				char	*tmpbuf;
				List_p	lp = ll_first(tbl.col_text[i]);
				for (k = 0; k < j && lp; k++)
					lp = ll_next(lp);
				if (lp == NULL)
					goto end_of_table;
				bp = (buf_t *) ll_elem(lp);
				tmpbuf = (char *) chk_alloc(bp->used + 256);
				memcpy(tmpbuf, bp->buf, bp->used);
				tmpbuf[bp->used] = NULL;
				cp1 = tmpbuf;
				cp = NULL;
				for (k = 0; k < line; k++) {
					cp = strchr(cp1, '\n');
					if (cp == NULL || cp[1] == NULL) {
						tmpbuf[0] = NULL;
						blanks++;
						break;
						}
					cp1 = cp+1;
					cp = cp1;
					}
				if (cp)
					strcpy(tmpbuf, cp);
				cp = strchr(tmpbuf, '\n');
				if (cp)
					*cp = NULL;
				k = strlen(tmpbuf);
				while (k < tbl.col_widths[i])
					tmpbuf[k++] = ' ';
				tmpbuf[k++] = '|';
				tmpbuf[k] = NULL;
				/***********************************************/
				/*   Output  left  bar  if we haven't done it  */
				/*   yet.				       */
				/***********************************************/
				if (!done_left) {
					*linep++ = '|';
					done_left = TRUE;
					}
				if (*tmpbuf == '=' || *tmpbuf == '-')
					do_line = FALSE;
				strcpy(linep, tmpbuf);
				linep += strlen(linep);
				chk_free(tmpbuf);
				}
			*linep = NULL;
			if (blanks != tbl.columns) {
				buf_outs(line_buf);
				newline();
				}
			}
		}
end_of_table:
	print_table_bar(&tbl, '=');
	free_table(&tbl);
	return 0;
}
/**********************************************************************/
/*   Print  a  line  the  same  width as the table. Used for top and  */
/*   bottom titles.						      */
/**********************************************************************/
void
print_table_bar(tbl, ch)
tbl_t	*tbl;
int	ch;
{	int	i, j;

	if (tbl->box) {
		buf_outc('|');
		for (i = 0; i < tbl->columns; i++) {
			for (j = 0; j < tbl->col_widths[i]; j++)
				buf_outc(ch);
			buf_outc('|');
			}
		newline();
		}
}
/**********************************************************************/
/*   Function  to  free  memory  allocated whilst we were building a  */
/*   table.							      */
/**********************************************************************/
void
free_table(tbl)
register tbl_t	*tbl;
{	int	i;
	register List_p	lp;
	buf_t	*bp;

	/***********************************************/
	/*   Now  free  up  memory saved away for the  */
	/*   table.				       */
	/***********************************************/
	for (i = 0; i < tbl->columns; i++) {
		while ((lp = ll_first(tbl->col_text[i])) != NULL) {
			bp = (buf_t *) ll_elem(lp);
			if (bp->buf)
				chk_free(bp->buf);
			chk_free((char *) bp);
			ll_pop(tbl->col_text[i]);
			}
		ll_free(tbl->col_text[i]);
		}
}
/**********************************************************************/
/*   Function  to  handle  parsing of a column. We either have an in  */
/*   situ  column  or  we  have a block split over several lines (T{  */
/*   and  T}).  In  the latter case we have to tell the caller where  */
/*   we left off.						      */
/**********************************************************************/
char *
process_column(str, buf, size)
char	*str;
char	*buf;
int	size;
{	

	if (*str != 'T' || str[1] != '{') {
		process_line(str);
		return NULL;
		}

	while (read_line(buf, size) >= 0) {
		if (buf[0] == '.') {
			process_dot(buf);
			continue;
			}
		if (*buf == 'T' && buf[1] == '}')
			return buf + 2;
		process_line(buf);
		}
	return NULL;
}
/**********************************************************************/
/*   Function   to   define   a   macro.   Terminated  with  a  '..'  */
/*   definition.						      */
/**********************************************************************/
void
define_macro(str)
char	*str;
{	macro_t	*mp;

	mp = (macro_t *) chk_alloc(sizeof(*mp));
	mp->name[0] = str[0];
	mp->name[1] = NULL;
	mp->name[2] = NULL;
	if (str[1] && str[1] != '\n') 
		mp->name[1] = str[1];
	mp->size = 32;
	mp->len = 0;
	mp->val = (char *) chk_alloc(mp->size);
	ll_push(hd_macros, (char *) mp);
	defining_macro = TRUE;
	current_macro = mp;
}
/**********************************************************************/
/*   Function  called  whilst  defining  macro to add line to end of  */
/*   string.							      */
/**********************************************************************/
void
add_to_macro(str)
char	*str;
{	int	len = strlen(str);

	if (*str == '.' && str[1] == '.') {
		defining_macro = FALSE;
		current_macro->val[current_macro->len] = NULL;
		return;
		}
	if (len + current_macro->len >= current_macro->size) {
		current_macro->size += current_macro->len + 
				current_macro->size;
		current_macro->val = (char *) chk_realloc(current_macro->val, current_macro->size);
		}
	memcpy(current_macro->val + current_macro->len, str, len);
	current_macro->len += len;
	
}
/**********************************************************************/
/*   Lookup a macro and if it exists push it on the input stack.      */
/**********************************************************************/
int
lookup_macro(name)
char	*name;
{	register List_p	lp;
	macro_t	*mp = NULL;
	char	str[32];
	file_t	*fip;
	int	i;
	int	quoted;
		
	str[0] = *name++;
	if (*name != ' ' && *name != '\t' && *name != '\n' && *name) {
		str[1] = *name++;
		str[2] = NULL;
		}
	else
		str[1] = NULL;
	/***********************************************/
	/*   Skip   over   white  space  looking  for  */
	/*   start of argument list.		       */
	/***********************************************/
	while (*name == ' ' || *name == '\t')
		name++;

	for (lp = ll_first(hd_macros); lp; lp = ll_next(lp)) {
		mp = (macro_t *) ll_elem(lp);
		if (mp->name[0] == *str && strcmp(mp->name, str) == 0)
			break;
		}
	if (lp == NULL)
		return -1;
	fip = setup_str(str, mp->val);
	/***********************************************/
	/*   If  no  arguments  for  this  macro then  */
	/*   forget it.				       */
	/***********************************************/
	if (*name == NULL) {
		fip->arg_string = NULL;
		return 0;
		}
	if (name[strlen(name) - 1] == '\n')
		name[strlen(name) - 1] = NULL;
	fip->arg_string = strdup(name);
	name = fip->arg_string;
	for (i = 0; i < MAX_MACRO_ARGS; i++) {
		while (*name == ' ' && *name == '\t')
			name++;
		quoted = *name == '"';
		if (quoted)
			name++;
		fip->args[i] = name;
		while (*name) {
			if (quoted) {
				if (*name == '"')
					break;
				}
			else if (*name == ' ' || *name == '\t')
				break;
			if (*name == '\\')
				name++;
			name++;
			}
		if (*name == NULL)
			break;
		*name++ = NULL;
		}
	return 0;
}
/**********************************************************************/
/*   Check  a  comment  to see if it contains one of our magic words  */
/*   of advice.							      */
/**********************************************************************/
int
check_ninfo_comment(str)
char	*str;
{	char	**args;
	int	i;

	if (strncmp(str, "NINFO:", 6) != 0)
		return FALSE;
	str += 6;
	while (*str == ' ' || *str == '\t')
		str++;
	if (strncmp(str, "start", 5) == 0) {
		if (stopp != NULL) {
			error("NINFO:start without a previous end clause\n");
			return TRUE;
			}
		stopp = (sstop_t *) chk_alloc (sizeof(sstop_t));
		stopp->start_line = bufp.line_number;
		stopp->end_line = 10000000;
		str += 5;
		while (*str == ' ' || *str == '\t')
			str++;
		if (*str == '$') {
			args = macro_args();
			i = str[1] - '1';
			if (args != NULL && i >= 0 && i <= MAX_MACRO_ARGS)
				str = args[i];
			else
				str = "unknown";
			}
		else
			str = "unknown";
		stopp->entry = strdup(str);
		ll_append(hd_sstop, (char *) stopp);
		return TRUE;
		}
	if (strncmp(str, "stop", 4) == 0) {
		if (stopp == NULL)
			return TRUE;
		stopp->end_line = bufp.line_number;
		stopp = NULL;
		return TRUE;
		}
	error("Undefined NINFO clause");
	return TRUE;
}
/**********************************************************************/
/*   Function to handle processing of the dot commands.		      */
/**********************************************************************/
int
process_dot(str)
char	*str;
{	int	i;
# define	CMD(x,y)	((x << 8) | (y))
	int	cmd1 = str[1];
	int	cmd2 = str[2];
	char	buf[BUFSIZ];
	char	*start_str = str;
	
	if (cmd2 == '\n')
		cmd2 = ' ';
	/***********************************************/
	/*   Skip to first argument of command.	       */
	/***********************************************/
	while (*str != '\n' && *str != ' ')
		str++;
	while (*str == ' ')
		str++;
	switch (CMD(cmd1, cmd2)) {
	  case CMD('p', 's'):
	  	break;
	  case CMD('s', 'p'):
	  case CMD('S', 'P'):
	  	do_break(FALSE);
		if (isdigit(str[0])) {
			for (i = atoi(str); i-- > 0; )
				newline();
			}
		else
			newline();
	  	break;
	  case CMD('b', 'r'):
	  	do_break(FALSE);
		break;
	  case CMD('P', ' '):
	  	do_break(FALSE);
		newline();
	  	break;
	  case CMD('f', 'i'):
	  	env_p->fill_mode = TRUE;
		break;
	  case CMD('n', 'f'):
	  	env_p->fill_mode = FALSE;
		break;
	  case CMD('b', 'p'):
	  	break;
	  case CMD('l', 'l'):
	  	if (*str == '\n')
			env_p->line_length = env_p->old_line_length;
		else {
			env_p->old_line_length = env_p->line_length;
			env_p->line_length = get_num(str, env_p->line_length);
			}
		env_p->right_margin = env_p->line_length - env_p->indent;
	  	break;
	  case CMD('i', 'n'):
	  	i = env_p->indent;
	  	if (*str == '\n')
			env_p->indent = env_p->old_indent;
		else {
			env_p->old_indent = env_p->indent;
			env_p->indent = get_num(str, env_p->indent);
			}
		if (env_p->buf == env_p->bufp) 
			tab_to_left_margin();
		else
		  	do_break(FALSE);
		env_p->right_margin = env_p->line_length - env_p->indent;
	  	break;
	  case CMD('P', 'H'):
	  	break;
	  case CMD('P', 'F'):
	  	break;
	  case CMD('H', ' '):
	  	do_header(str);
	  	break;
	  case CMD('D', 'S'):
	  	push_env();
		newline();
		env_p->fill_mode = FALSE;
		env_p->double_slash = TRUE;
	  	break;
	  case CMD('D', 'E'):
	  	pop_env();
		newline();
	  	break;
	  case CMD('L', 'I'):
	  	do_list_element(str);
	  	break;
	  case CMD('A', 'L'):
	  	push_env();
		env_p->list_type = LIST_AL;
		env_p->list_level = 0;
		env_p->list_indent = 4;
		env_p->indent += 4;
		env_p->right_margin = env_p->line_length - env_p->indent;
	  	break;
	  case CMD('B', 'L'):
	  	push_env();
		env_p->list_type = LIST_BL;
		env_p->list_level = 0;
		env_p->list_indent = 4;
		env_p->indent += 4;
		env_p->right_margin = env_p->line_length - env_p->indent;
	  	break;
	  case CMD('D', 'L'):
	  	push_env();
		env_p->list_type = LIST_DL;
		env_p->list_level = 0;
		env_p->list_indent = 4;
		env_p->indent += 4;
		env_p->right_margin = env_p->line_length - env_p->indent;
	  	break;
	  case CMD('V', 'L'):
	  	push_env();
		env_p->list_type = LIST_VL;
		env_p->list_level = 0;
		env_p->list_indent = 4;
		env_p->indent += 4;
		if (isdigit(*str)) {
			env_p->list_indent = atoi(str);
			env_p->indent += atoi(str);
			}
		env_p->right_margin = env_p->line_length - env_p->indent;
	  	break;
	  case CMD('S', 'H'):
	  	do_break(FALSE);
	  	env_p->indent = 0;
		newline();
	  	env_p->indent = env_p->old_indent;
		buf_outs(str);
		env_p->indent = 4;
		env_p->right_margin = env_p->line_length - env_p->indent;
		tab_to_left_margin();
	  	break;
	  case CMD('T', 'S'):
	  	do_table(str);
		break;
	  case CMD('L', 'E'):
	  	pop_env();
		break;
	  case CMD('T', 'C'):
	  	break;
	  case CMD('T', 'H'):
	  	break;
	  case CMD('c', 'e'):
	  	if (isdigit(*str))
			env_p->center = atoi(str);
		else
			env_p->center = 1;
	  	break;
	  case CMD('s', 'o'):
	  	break;
	  case CMD('d', 'e'):
	  	define_macro(str);
		break;
	  case CMD('.', '.'):
	  	error(".. not defined at end of macro");
		break;
	  case CMD('\\', '"'):
	  	if (check_ninfo_comment(str))
			break;
		/* Fallthru.. */
	  case CMD('f', 't'):
	  case CMD('n', 'e'):
	  	break;
	  default:
	  	if (lookup_macro(start_str+1) < 0) {
		  	sprintf(buf, "'.%c%c' not implemented", cmd1, cmd2);
			error(buf);
			}
	  }
	return 0;
}
