/*
 *	getrno -- Convert macro comments to .rno for documentation
 */

/*)BUILD	$(TKBOPTIONS) = {
			TASK	= ...GTR
		}
*/

#ifdef	DOCUMENTATION
title	getrno	Build Documentation Source
index		Convert Comments To Runoff Source Format

synopsis

	getrno [-options] -o output -h header input_files

description

	getrno reads a list of files, compiling comments to runoff source.
	Switches are:
	.lm +8

 -d	Debugging

 -b	Blank lines in C source become .s

 -c	C source files (default)

 -m	Macro source files

 -r	RSTS/E title hack flag;  see below

 -u	Usage -- output an abbreviated documentation
	containing only the information between "Usage" and "Description".
	(Note: ignored if -c option selected.)

 -w	Wizard:  output package internal calls, too.

 -o file	Write output to "file", rather than stdout.

 -h file	Process "file" as a header file and prepend to the output.

	.lm -8
	There is an error in the RSTS/E V7.0 runoff (RNO.TSK) such
	that title lines do not get correct page numbers.  If the -r
	flag is set, .t lines in the header program will be marked for
	the fixdoc.c program.  Note, for a title to be recognized, it
	must have the exact form:

		.t Anything  (or .t ########Anything)

	Getrno will change it to:

		.t [[Anything]]

	and fixdoc.c will look for same for post processing.
	.tp 10

C source file format

	C source must have the following format:

		#ifdef	DOCUMENTATION
		title	tool_name	header_text
		index	index text
		section_head
			text for the section
		#endif

	Note that the comment leadin must be

		#ifdef<tab>DOCUMENTATION

	.tp 8
 	In order to define a uniform environment, getrno will insert the
	following commands at the beginning of the output file:

		.no autosubtitle .style headers 3,0,0
		.pg.uc.ps 58,80.lm 8.rm 72
		.hd
		.hd mixed
		.head mixed

	(Various flavors of runoff will flag one or more of these commands as
	errors, none of them fatal.)

	Getrno inserts the following commands between the data from each pair
	of files scanned:

		.lm 8.rm 72.nhy

	Within the body of the documentation, lines are handled depending on
	what column their text starts in (i.e. where the first non-<space>,
	non-<tab> character falls); a <tab> is always assumed to put the next
	character at column 8.  (And you thought card images were dead!)

	An empty line becomes a .space command.

	Section heads begin in column 1.  They will be left-justified
	and printed in upper case with a trailing ":".  The only section heads
	with any special meaning to getrno are "title", "index", and
	"internal"; they are recognized regardless of case.  "Title" and
	"index" must come first, as shown in the example.  "Internal" flags
	the beginning of data that is output only if the -w switch was used;
	the next section head ends the "wizards only" data.  However, if
	"internal" appears before any other section head (not counting
	"index"), nothing at all will be output for this file unless -w
	appears.
 
	Normal text starts in column 8 with a character other than a <tab> or
	<space>; it will be justified and filled, and will start in column 16
	on the paper.  The <space>'s and <tab>'s that filled the first 7
	columns are stripped.

	A line that has a <space> or <tab> in column 8 will be output with the
	<space>'s and <tab>'s that filled the first 7 columns stripped, and
	with runoff set to nofill mode.

	A line that starts with a <space> but has a non-blank before column 8
	will have the leading <space>'s stripped and will be output with a
	leading ".i -8;".  Lines of this form are normally used between
	".lm +8"/".lm -8" pairs, as shown in the example below.

	A line whose first character AFTER stripping leading blanks as defined
	above is a "." has leading runoff commands.  It is your responsibility
	to maintain alignment and to quote runoff-specific characters on such
	lines; in all other cases, getrno will do it for you.  For example:
	.tp 9

		diagnostics
			.lm +8
			.s.i-8;bad file _& other stuff
			.s.i-8;something else
			.lm -8
			.s
		author
			etc...

	Avoid manipulating fill mode directly.  Getrno keeps track of whether
	it has set runoff to fill or nofill mode, and can get confused if you
	shift modes on it.  Hence, the results may then not be as you expect.

	Note that the above example can be more easily handled as:
	.tp 11

		diagnostics
			.lm +8

		 bad file & other stuff

		 something else
			.lm -8

		author
			etc...

Macro source file format

	The Macro input must have the following syntax:

		.title	name	Title line
	 ;
	 ;+				(Start of commentary)
	 ;
	 ; Index		Short description for kwik index
	 ; Index		(Another short description)
	 ;
	 ; Usage		(Start of usage information)
	 ;
	 ;	Usage information, output, starting in column 1
	 ;	exactly as it is written (rno .nf mode).  The
	 ;	leading semicolon is not output, but the tab (or
	 ;	spaces) is.
	 ;
	 ; Description
	 ;
	 ;	Description, output, starting in column 8 in
	 ;	runoff .fill mode, except that any line starting
	 ;	with ";<tab><TAB or SPACE>" will be output in
	 ;	.no fill mode.  The leading ";<tab>" will not be
	 ;	output.
	 ;
	 ;	A line consisting of just ";" will generate a
	 ;	runoff .space while a line consisting of
	 ;	";<SPACE>text" will generate ".indent -8 ;text".
	 ;
	 ;-	(End of commentary)

	If the wizard flag (-w) is not given, a Macro source line of
	the format:

	; Internal

	may be used to signal the start of package-internal information.
	If "Internal" preceeds "Usage", no information will be output for
	this file.  If it follows "Usage" or "Description", text following
	(up to the next section initiator) will not be output.

Header file format

	The header file is assumed to be in runoff format.  It
	should start as follows:

		.comment Set top-of-form at 4 lines
		.comment below perforation.
		.ps 58,80	! 58 lines per page, 80 columns.
		.lm 8		! left margin at 8
		.rm 72		! right margin at 72
		.hd Mixed	! Mixed case headings

	Because the left margin is set to 8, titles and subtitles should
	be written as:

		.t  ########Title
		.st ########Subtitle

	The wizard flag may be used to select parts of the header file:

		.comment wizard
			*** For wizards only ***
		.comment else
			*** For non-wizards only ***
		.comment mundane
			*** Exit wizard section ***

	A line indicating the date on which the documentation was built, and
	optionally some other information, can be inserted by:

		.comment date[ info]

	The optional info will be included on the date line, which is
	centered.  A single <space> must appear after "date" if info appears;
	it is not part of info.  The format of the date line is:

		Document compiled <today's date><space>[info]

	The date inserted is in the format returned by ctime() (so it's
	actually the date and time).

	The format of all the comments is EXACTLY ".comment<space>argument" -
	one space only.

Workfile format:

	Each file builds one or more records (lines) in the workfile.
	The first has the title line, (information following .title).
	To allow building an index, this should be in the format:

		name<tab>Title information

	Following this are lines containing .rno text.  The last
	line is followed by a record containing "!!" in the first
	two bytes.

	Records with "!" in the first byte may be used to communicate
	information between passes:  they are not written to the output.
	This allows writing Usage information as a separate file.

	Note that there is a bug in the RSTS RNO (as distributed with Version
	7.0).  Consequently, if your runoff source has chapter headings,
	you should not have explicit titles, or have page numbers.

bugs

	Getrno was written to aid in documenting the Decus C libraries.
	(I.e., getrno and the library documentation are interdependent.)
	An attempt has been made to make the C-style processing generally
	useful; the Macro-style processing is more closely tied to the
	library documentation and is probably of less general interest.
	Further, the program, particularly in Macro mode, is very sensitive
	to the exact format of the input; minor variations can produce
	unexpected results.  Some attempt has been made to avoid this in
	C-style processing; in Macro mode, you are on your own.

	Due to the size of the files involved, the usage option (-u) is
	no longer used in building library documentation, and is not
	supported in C-mode.  Perhaps it should be.

	There should be a way to pass a "#" through to runoff within a line
	that is otherwise processed normally.

	All files are processed in the same mode.  It might be useful to
	allow mixed-mode processing so that libraries consisting of both
	Macro and C modules could be handled.

diagnostics

	A warning message is printed if a file does not have any
	documentation.

	There are many other messages, hopefully self-explanatory.

author

	Martin Minow

#endif

/*	Revision History
 *	0.0 ??-???-??  MM	Invention
 *	1.0 ??-???-??  MM	Added support for C source.
 *	1.1 24-Jun-82 JSL	Added ".comment date"
 *	2.0 28-Jun-82 JSL	Many minor cleanups.  Support 8-bit ascii.
 *	2.1 30-Jun-82 JSL	Go by columns instead of leading <TAB>/space;
 *				added -m, made C mode the default.
 *	2.2 16-Jul-82 JSL	Indented lines have RUNOFF char's escaped.
 */

#include	<stdio.h>
#ifndef	nomacarg
#include	<ctype.h>
#endif

#define	EOS		0
#define	FALSE		0
#define	TRUE		1
#define REALSHARP	'\001'
#define	NENTRIES	200
#define	NAMESIZE	11
#define	WORKFILE	"getrno.tmp"
#define	LINESIZE	257

typedef struct {
	char	e_name[NAMESIZE];	/* Entry name		*/
	long	e_place;		/* File position	*/
} ITEM;

ITEM	entries[NENTRIES];
ITEM	*free_entry = entries;
#define	ELAST	&entries[NENTRIES]

extern	long	ftell();

int		rstshack = 0;		/* If -r flag set	*/
unsigned	linect = 0;		/* Needed for debugging	*/
int		debug = 0;		/* Set for debugging	*/
int		uflag = 0;		/* Set if -u flagged	*/
int		wizard = 0;		/* Set if -w flagged	*/
int		cflag = TRUE;		/* Set if -c flagged	*/

int		fill_flag = TRUE;	/* If runoff will fill	*/

char		line[LINESIZE];
char		temptext[LINESIZE];
char		macfile[81];
char		*section;		/* For bug()		*/
char		header[LINESIZE];	/* Header file name	*/
FILE		*infd;			/* Input file		*/
FILE		*workfd;		/* Temporary file	*/
long		place;			/* For work file	*/
char		name_text[NAMESIZE];	/* From title header	*/
char		title_text[LINESIZE];	/* From title header	*/
extern char	*skipbl();

/*
 * Define  C documentation layout:
 *	.left margin	8	(16 in documentation body)
 *	.right margin	72
 * Note: various flavors of runoff give various error messages,
 * none fatal.
 */

char	*layout = ".no autosubtitle .style headers 3,0,0\n\
.pg.uc.ps 58,80.lm 8.rm 72\n.hd\n.hd mixed\n.head mixed\n";

char	*interfile = ".lm 8.rm 72.nhy\n";


main(argc, argv)
int		argc;
char		*argv[];
{
	register int	i;
	register char	*ap;
	register char	c;

	for (i = 1; i < argc; i++) {
		ap = argv[i];
		if (*ap == '-') {
			switch (c = tolower(ap[1])) {
			case 'c':
					cflag = TRUE;
					break;

			case 'd':	debug++;
					break;

			case 'm':
					cflag = FALSE;
					break;

			case 'r':	rstshack++;
					break;

			case 'u':	uflag++;
					break;

			case 'w':	wizard++;
					break;

			default:
				switch (c) {

				case 'h':
					if (++i >= argc)
					    error("?No file after -h\n");
					strcpy(header, argv[i]);
					break;

				case 'o':
					if (++i >= argc)
					    error("?No file after -o\n");
					if (freopen(argv[i], "w", stdout)
							== NULL)
						cant("output", argv[i]);
					break;

				default:
					error("?Illegal switch '%c'\n",c);
				}
				argv[i-1] = NULL;
			}
			argv[i] = NULL;		/* Erase this argument	*/
		}
	}
	/*
	 * Now open the work file and process all files
	 */
	if ((workfd = fopen(WORKFILE, "w")) == NULL)
		cant("work", WORKFILE);

	for (i = 1; i < argc; i++) {
		if ((ap = argv[i]) == NULL)
			continue;		/* 'Twas a switch	*/
		if ((infd = fwild(ap, "r")) == NULL)
			cant("wild card input", ap);
		else {
			while (fnext(infd) != NULL) {
				filename(infd, macfile);
				if (debug)
					fprintf(stderr, "* %s\n", macfile);
				linect = 0;
				process();
			}
		}
	}
	if (debug)
		fprintf(stderr, "* EOF\n");
	/*
	 * All file information read, now write it out
	 */
	fclose(workfd);
	doheader();
	if (debug)
		fprintf(stderr, "* Header processed\n");
	if ((workfd = fopen(WORKFILE, "r")) == NULL)
		cant("work (reopening)", WORKFILE);
	puts(interfile);
	output();
	fgetname(workfd, line);
	fclose(workfd);
	delete(line);
}

filename(fd, outbuf)
FILE		*fd;
char		*outbuf;
/*
 * Get the file name, account number, and extension.
 * Remove the device name and the version number.
 */
{
	register char	*tp;
	register char	*op;
	register int	c;

	fgetname(fd, temptext);
	/*
	 * Skip over the device name
	 */
	for (tp = temptext; (c = *tp++) && c != ':';);
	if (c == EOS)
		tp = temptext;
	/*
	 * Don't bother outputting the version number
	 */
	for (op = tp; (c = *op) && c != ';'; op++);
	*op = EOS;
	/*
	 * Copy the file spec, forcing lowercase.
	 */
	for (op = outbuf; (c = *tp++) ;) {
		*op++ = tolower(c);
	}
	*op = EOS;
}

doheader()
/*
 * Process the header file
 */
{
	register int	inwizard;
	register int	skipit;
	register char	*lp;

	if (*header == EOS)
		return;
	if ((infd = fopen(header, "r")) == NULL) {
		fprintf(stderr, "can't open ");
		perror(header);
		return;
	}
	strcpy(macfile, header);
	inwizard = FALSE;
	skipit = FALSE;
	linect = 0;
	while (getline(infd)) {
		if (match(line, ".comment wizard")) {
			if (inwizard)
				bug("headfile: embedded .comment wizard",
						NULL);
			inwizard = TRUE;
			skipit = !wizard;
		}
		else if (match(line, ".comment else")) {
			if (!inwizard)
				bug("headfile: else, no .comment wizard",
						NULL);
			else
				skipit = !skipit;
		}
		else if (match(line, ".comment mundane")) {
			if (!inwizard)
				bug("headfile: mundane, no .comment wizard",
						NULL);
			inwizard = FALSE;
			skipit = FALSE;
		}
		if (!skipit) {
			if (rstshack
			 && line[0] == '.'
			 && tolower(line[1]) == 't'
			 && (isspace(line[2]) || line[2] == ';')) {
				lp = &line[2];
				while ((*lp != EOS) && isspace(*lp))
					lp++;
				if (*lp == ';')
					lp++;
				printf(".t [[%s]]\n", lp);
			}
			else if ((lp = match(line, ".comment date"))
			      && (isspace(*lp) || *lp == EOS)) {
				printf(".c ;Document compiled %s%s\n",
					ctime(0), lp);
			}
			else
				printf("%s\n", line);
		}
	}
	fclose(infd);
	macfile[0] = EOS;
}

process()
/*
 * Process input text, saving it in the workfile.
 */
{
	register int	flag;

	if (cflag) {
		if (docstuff())
			return;
	}
	else {
		section = "Title scan";
		title();
		section = "Usage scan";
		if ((flag = usage()) == 2)
			return;
		else if (flag && !uflag) {
			section = "Document scan";
			rest();
		}
	}
	fill_flag = TRUE;
	save("!!", FALSE);			/* Terminate entry	*/
}

docstuff()
/*
 * Process C documentation.  Return FALSE if all OK, TRUE if output for this
 * file should be discarded.
 */
{
	register int		titlestate;
	register int		skipit;

	skipit = FALSE;
	while (getline(infd)) {
		if (match(line, "#ifdef")
		 && streq(skipbl(line+6), "DOCUMENTATION"))
			break;
	}
	if (feof(infd)) {
		fprintf(stderr, "Warning: no documentation in %s\n", macfile);
		return (TRUE);
	}
	/*
	 * Do some documentation
	 */
	titlestate = 0;				/* No title seen yet	*/
	while (getline(infd)) {
		if (match(line, "title")) {
			savetitle(skipbl(&line[5]));
			titlestate = 1;		/* Title, no real	*/
						/* section heads yet	*/
		}
		else if (match(line, "index"))
			continue;
		else {
			switch (titlestate) {
			case 0:
				if (line[0] == EOS)
					continue;
				section = "document scan";
				bug("Need a title, using", macfile);
				strcpy(name_text, macfile);
				strcpy(title_text, "");
				/*
				 * Fall through
				 */
			case 1:
				if (line[0] == EOS)
					continue;
				if (!wizard && match(line, "internal"))
					return (TRUE);
				savename();
				save(".lm +8", FALSE);
				if (wizard) {
					concat(temptext,
						".s.i -8;File name:\t",
						macfile, NULL);
					save(temptext, FALSE);
					save(".s 2", FALSE);
				}
				concat(temptext,
					".s.i -8;NAME:\t", name_text,
					" -- ", title_text, NULL);
				save(temptext, FALSE);
				save(".s.f", FALSE);
				fill_flag = TRUE;
				titlestate = 2;
				/*
				 * Fall through
				 */
			case 2:			/* Write the .rno stuff	*/
				if (match(line, "#endif"))
					return(FALSE);
				if (!wizard && isgraph(line[0]))
					skipit = match(line, "internal");
				if (!skipit)
					docline();
			}
		}
	}
	return (FALSE);
}

docline()
/*
 * Process a line from a c document
 */
{
	register char		*lp;
	register int		skip;

	switch(line[0]) {

	case EOS:
		save("",FALSE);
		break;

	case ' ':
		for (skip = 1; skip < 8; skip++)
			if (line[skip] == '\t')
				goto tabcase;
			else	if (line[skip] != ' ')
					break;
		fill();

/* The following two save()'s are done in two calls so that they end	*/
/* up on two lines; otherwise, runoff characters in the line won't	*/
/* get "escaped". 							*/
		save(".i -8",FALSE);
		save(&line[skip],FALSE);
		break;

	case '\t':
		skip = 0;
tabcase:
		if (line[++skip] == ' ' || line[skip] == '\t')
			nofill();
		else
			fill();
		save(&line[skip],FALSE);
		break;

	default:
		/*
		 * sub-header
		 */
		for (lp = line; *lp != EOS; lp++)
			*lp = toupper(*lp);
		if (lp[-1] != ':') {
			*lp++ = ':';
			*lp = EOS;
		}
		fill();
		concat(temptext, ".i -8;", line, NULL);
		save(temptext, FALSE);
		break;
	}
}

title()
/*
 * First, skip to the title
 */
{
	register char		*lp;
	register char		*np;

	while (getline(infd)) {
		lp = skipbl(line);
		if (!match(lp, ".title"))
			continue;
		savetitle(skipbl(lp + 6));
		break;
	}
}

savetitle(lp)
register char		*lp;
/*
 * Save the title text, on entry lp -> just after "title "
 */
{
	register char		*np;

	place = ftell(workfd);
	np = name_text;
	while (*lp > ' ' && np < &name_text[sizeof name_text - 1])
		*np++ = *lp++;
	while (np < &name_text[sizeof name_text])
		*np++ = EOS;
	strcpy(title_text, skipbl(lp));
}

savename()
/*
 * Save info. in name/place which were setup by title()
 */
{
	register ITEM	*ep;
	register ITEM	*lastep;

	/*
	 * Save in sorted order
	 */
	if ((lastep = free_entry++) >= ELAST)
		error("?Too many files, %d maximum", NENTRIES);
	for (ep = entries; ep < lastep &&
			strcmp(ep->e_name, name_text) <= 0; ep++);
	for (; lastep > ep; lastep--) {
		lastep->e_place = (lastep-1)->e_place;
		copy(lastep->e_name, (lastep-1)->e_name, NAMESIZE);
	}
	ep->e_place = place;
	copy(ep->e_name, name_text, NAMESIZE);
	/*
	 * Save the title line
	 */
	save((title_text[0]) ? title_text : "!", TRUE);	/* Title line	*/
	fill_flag = TRUE;
}
#ifdef	vms
copy(out, in, count)
char		*out;
char		*in;
int		count;
/*
 * Copy a buffer -- not in vms library
 */
{
	while (--count >= 0) {
		*out++ = *in++;
	}
}
#endif

int
usage()
/*
 * Skip to ";+" (ignored what preceeds), then to "; Usage"
 * and put out usage section.  Return 1 if ok, 0 if trouble, 2 to skip.
 */
{
	register char	*lp;
	register int	usage_seen;
	register int	skipit;

	skipit = FALSE;
	for (;;) {				/* Skip to ;+		*/
		if (!getline(infd)) {
			fprintf(stderr, "Warning, no documentation for %s\n",
				macfile);
			return(0);		/* Ignore if none	*/
		}
		if (line[0] == ';' && line[1] == '+')
			break;
	}
	usage_seen = 0;
	while (getline(infd)) {
		if (line[0] != ';') {
			bug("Expecting ';', got", line);
			return(0);
		}
		else if (line[1] == 0) {
			if (usage_seen && !skipit)
				save(&line[1], 1);
			continue;
		}
		else if (line[1] == '-') {
			bug("No Description, etc.", NULL);
			return(0);
		}
		else if (line[1] == ' ') {
			if (match(&line[2], "internal")) {
				if (wizard) {		/* Do internal	*/
					if (usage_seen)
						unjust();
					continue;
				}
				else if (usage_seen) {
					skipit = TRUE;
					continue;
				}
				else	return(2);
			}
			else if (usage_seen == FALSE) {
				if (match(&line[2], "index"))
					continue;
				if (match(&line[2], "usage")
				 || match(&line[2], "synopsis")) {
					savename();
					usage_seen = TRUE;
					save(".lm +8.nf", FALSE);
					if (wizard) {
						concat(line,
						    ".s.i -8;File name:\t",
						    macfile, NULL);
						save(line, FALSE);
						save(".s 2", FALSE);
					}
					save(".i -8;Usage", FALSE);
					save("!b", FALSE);
					fill_flag = FALSE;
					continue;
				}
			}
			if (line[2] >= 'A') {
				if (usage_seen)
					save("!e", FALSE);
				return(1);
			}
			else if (usage_seen && line[2] == ' ') {
				if (!skipit) {
					unjust();
				}
				continue;
			}
		}
		else if (usage_seen) {
			if (line[1] == '\t') {
				if (!skipit)
					save(&line[2], TRUE);
				continue;
			}
		}
		bug("Ununderstandable line", line);
		return(0);
	}
	bug("No ;- at end of file", NULL);
	return(0);
}

rest()
/*
 * Output the remainder of the commentary.
 * The line buffer contains "; Description"
 */
{
	register int	skipit;

	skipit = 0;
	indent(&line[2]);
	while (getline(infd)) {
		if (line[0] != ';') {
			bug("Line doesn't start with a ';'", line);
			continue;
		}
		else if (line[1] == '-') {
			save(".lm -8.fill", FALSE);
			fill_flag = 1;
			return;
		}
		else if (line[1] == EOS) {
			if (!skipit)
				save(&line[1], 1);
		}
		else if (line[1] == ' ') {
			if (line[2] < 'A') {
				if (!skipit) {
					unjust();
				}
			}
			else {
				if (!wizard) {
					if (match(&line[2], "internal"))
						skipit++;
					else	skipit = 0;
				}
				if (!skipit)
					indent(&line[2]);
			}
			continue;
		}
		else if (line[1] == '\t') {
			if (skipit)
				continue;
			else if (line[2] <= ' ') {
				nofill();
				save(&line[2], TRUE);
			}
			else {
				fill();
				save(&line[2], TRUE);
			}
			continue;
		}
		else
			bug("Ununderstandable line", line);
	}
	bug("Unexpected end of file", NULL);
	save(".lm -8.fill", FALSE);
}

unjust()
/*
 * Unjustify the line
 */
{
	register char	*lp;

	line[0] = REALSHARP;
	for (lp = line + 1; *lp == ' ';)
		*lp++ = REALSHARP;
	indent(line);
}

save(text, dotflag)
char		*text;
int		dotflag;
/*
 * Write the text to the work file.  If dotflag is set, initial '.'
 * is quoted.
 */
{
	if (dotflag && *text == '.')
		putc('_', workfd);
	fprintf(workfd, "%s\n", (*text != 0) ? text : ".s");
}

indent(text)
char		*text;
/*
 * Save an indented text item
 */
{

	sprintf(temptext, ".i -8;%s", text);
	save(temptext, FALSE);
}

fill()
/*
 * Turn on fill mode
 */
{
	if (!fill_flag) {
		save(".fill", FALSE);
		fill_flag = TRUE;
	}
}

nofill()
/*
 * Turn off fill mode
 */
{
	if (fill_flag) {
		save(".nf", FALSE);
		fill_flag = FALSE;
	}
}

int
match(text, lookfor)
register char	*text;
register char	*lookfor;
/*
 * If the beginning of text matches lookfor (ignoring case), followed by a
 * no-alphanumeric, returns pointer to the first thing after lookfor;
 * otherwise, returns NULL.  lookfor must always be in lowercase.
 */
{
	while (*lookfor != EOS) {
		if (tolower(*text++) != *lookfor++)
			return(NULL);
	}
	return(isalnum(*text) ? NULL : text);
}

output()
/*
 * Write out all records
 */
{
	register int		namlen;
	register int		stars;
	register ITEM		*ep;

	if (cflag) {
		puts(layout);
	}
	for (ep = entries; ep < free_entry; ep++) {
		fseek(workfd, ep->e_place, 0);
		if (!getline(workfd)) {
			bug("Can't read record", NULL);
			fprintf(stderr, "rfa = %08o\n", ep->e_place);
			continue;
		}
		if (uflag) {
			printf(".tp 10.s 4.lm +8.nf\n.i -8 ;%s",
				ep->e_name);
			if (line[0] != '!')
				printf("\t%s", line);
		}
		else {
			printf(".st ########%s", ep->e_name);
			if (line[0] != '!') {
				namlen = 8 - (strlen(ep->e_name) & 7);
				while (--namlen >= 0)
					putchar('#');
				printf("%s", line);
			}
			printf("\n.pg\n.hl 1 ");
			if (line[0] != '!')
				printf("^&%s\\&\n", line);
			else
				printf("#\n");
			printf(".s 2\n.c ;");
			namlen = strlen(ep -> e_name) + 4;
			for (stars = namlen; --stars >= 0;)
				putchar('*');
			printf("\n.c ;* %s *\n.c ;", ep -> e_name);
			for (stars = namlen; --stars >= 0;)
				putchar('*');
		}
		printf("\n.s 2\n");
		if (uflag) {
			/*
			 * Usage
			 */
			while (getline(workfd) &&
					line[0] != '!' && line[1] != 'b');
			while (getline(workfd)) {
				if (line[0] == '!' && (line[1] == 'e'
						|| line[1] == '!'))
					break;
				writeout(line);
			}
		}
		else {
			/*
			 * Not usage
			 */
			while (getline(workfd)) {
				if (line[0] == '!') {
					if (line[1] == '!') {
						break;
					}
				}
				else if (cflag && line[0] == '.')
					puts(line);
				else
					writeout(line);
			}
		}
		puts(interfile);
	}
}

writeout(text)
char	*text;
/*
 * Write this line to the .rno output, watching out for weird .rno
 * characters.  Note: to put a wierd .rno character out, flag it with
 * an initial underline.
 */
{
	register char	c;
	register char	*tp;

	tp = text;
	while ((c = *tp++) != 0) {
		switch (c) {
		case REALSHARP:
				putchar('#');
				break;
		case	'_':
				if (tp - text == 1 && *tp == '.')
					goto ignore;
		case	'%':
		case	'&':
		case	'\\':
		case	'^':
		case	'#':
				putchar('_');
		default:
ignore:				putchar(c);
		}
	}
	putchar('\n');
}

getline(fd)
FILE	*fd;
/*
 * Read a line from fd into line[].  Return 0 at end of file.
 */
{
	register char	*lp;

	if (fgets(line, sizeof line, fd) == NULL) {
		if (debug)
			fprintf(stderr, "* end of \"%s\" after %u lines\n",
				macfile, linect);
		return(0);
	}
	linect++;
	/*
	 * Erase trailing <Newline>, spaces, and tabs.  Note that
	 * line[strlen(line)] is the newline.
	 */
	for (lp = &line[strlen(line)]; lp > line && lp[-1] <= ' '; lp--);
	*lp = 0;
	if (debug > 1)
		fprintf(stderr, "\"%s\"\n", line);
	return(1);
}

char
*skipbl(text)
register char	*text;
/*
 * Skip blanks, return -> first non blank (or -> trailing null)
 */
{
	register char	c;

	while ((c = *text) && c <= ' ')
		text++;
	return(text);
}

bug(mess, arg)
char		*mess;
char		*arg;
{
	fprintf(stderr, "?GETRNO-E-Confused at %s", section);
	if (macfile[0])
		fprintf(stderr, " at line %u in file \"%s\"\n",
			linect, macfile);
	fprintf(stderr, "%s", mess);
	if (arg != NULL)
		fprintf(stderr, ": \"%s\"", arg);
	fprintf(stderr, "\n");
}

cant(what, who)
char		*what;
char		*who;
/*
 * Can't open the file, die
 */
{
	error("?Can't open %s file \"%s\", fatal.\n", what, who);
}
