/************************************************************************
 *									*
 *			Copyright (c) 1982, Fred Fish			*
 *			    All Rights Reserved				*
 *									*
 *	This software and/or documentation is released for public	*
 *	distribution for personal, non-commercial use only.		*
 *	Limited rights to use, modify, and redistribute are hereby	*
 *	granted for non-commercial purposes, provided that all		*
 *	copyright notices remain intact and all changes are clearly	*
 *	documented.  The author makes no warranty of any kind with	*
 *	respect to this product and explicitly disclaims any implied	*
 *	warranties of merchantability or fitness for any particular	*
 *	purpose.							*
 *									*
 ************************************************************************
 */


/*
 *  FILE
 *
 *	dex0.c   main routines for documentation extraction utility
 *
 *  DESCRIPTION
 *
 *	Contains the main routines and misc utility routines for dex.
 *
 *  FUNCTIONS
 *
 *	main           entry point for dex utility
 *	tolower        convert alpha characters to lower case
 *	options        process options list on cmd line
 *	usage          give usage message and abort
 *	indentation    set indentation level
 *	nofill         turn off fill mode
 *	fill           turn on fill mode
 *	asterisks      form box top or bottom
 *	emit_bline     emit cmds for blank line
 *	set_section    lookup and set current section
 *	reset_section  reset current section (no section)
 *	emit_filled    emit text in filled mode
 *	emit_unfilled  emit text in unfilled mode
 *	set_output     switch output stream
 *
 *  AUTHOR
 *
 *	Fred Fish
 *
 */

/*
 *  TOOL
 *
 *	dex   documentation extraction utility
 *
 *  SYNOPSIS
 *
 *	dex [-dhtv] [-r rcfile] file1 file2 file3 ...
 *
 *		d =>  enable debug mode
 *		h =>  print internal help message
 *		t =>  enable trace mode
 *		v =>  enable verbose mode
 *
 *		r =>  use reconfiguration file <rcfile>
 *
 *  DESCRIPTION
 *
 *	Dex extracts internal documentation from program source
 *	files, compiling it into a form suitable for input
 *	to the nroff text formatter.  It was developed primarily
 *	to aid the author in maintaining "up to date" documentation
 *	for each function in the portable math library (PML).
 *
 *	For further information consult the document
 *	"DEX - Documentation Extraction Utility".
 *
 *  EXAMPLE
 *
 *	The output of dex is typically collected in one
 *	or more files to be inserted in the appropriate
 *	places in other "skeleton" files during nroff
 *	processing.
 *
 *	For example, in the portable math library usage,
 *	the following sequence of commands will rebuild
 *	the library documentation to reflect the current
 *	state of the library.
 *
 *	   cd /usr/public/pml
 *	   dex *
 *	   nroff -mm pml.r >pml.doc
 *
 *	The command "dex *" extracts function and file documentation
 *	from all the files in the library, and builds two output
 *	files, "funcs.r" and "files.r".   The "funcs.r" file
 *	contains documentation on each of the library functions.
 *	The "files.r" file contains documentation on each of the
 *	files in the portable math library directory.  The "pml.r"
 *	file is an nroff file containing information common
 *	to all files and functions.  At the points where the
 *	"funcs.r" and "files.r" files are to be inserted, there
 *	is a ".so <filename>" nroff command.
 *
 */

/*
 *  FILES
 *
 *	".dexrc"   dex reconfiguration file [default]
 *
 *  BUGS
 *
 *	Items are currently collected as encountered in the files
 *	specified in the command line.  A planned future enhancement
 *	is to sort the collected documentation regions by name.
 *
 *	Lines which being with a "." character, such as the
 *	line in the "FILES" section above, cause nroff to
 *	occasionally do unintended things.
 *
 *	The dynamic reconfiguration capability is relatively
 *	primitive.  Once the requirements are more clear,
 *	a future version will be more flexible.
 *
 *  AUTHOR
 *
 *	Fred Fish
 *
 */

#include <stdio.h>
#include "hashtbl.h"
#include "dex.h"

/*
 *	Some misc global variables.
 */

struct tbl_data *section=NULL;	/* Current section being processed */
char *infile = "stdin";		/* Input file name if not stdin */
char *rcfile = ".dexrc";	/* Reconfiguration file */
int filled = TRUE;		/* Flag for fill mode on */
int trace = FALSE;		/* Trace flag */
int vflag = FALSE;		/* Verbose flag */
int debug = FALSE;		/* Debug flag */
extern int yydebug;		/* YACC internal debug flag */
int indent_level = 0;		/* Current indentation level */
int linenum = 1;		/* Current input file line number */
FILE *infp = stdin;		/* Lexical analysis input stream */
FILE *fp = stdout;		/* Current output stream */
char title[128];		/* Title of current region */

/*
 *	Some misc local variables.
 */

static int suppress = FALSE;	/* Flag for region suppression */
static int box_emitted = FALSE;	/* Flag for box has been emitted for region */


/*
 *	The following documentation will be printed out when DEX is
 *	invoked with the -h argument.
 */

static char *documentation[] = {
    "",
    "DESCRIPTION",
    "",
    "\tDex is a program for extracting documentation from program",
    "\tsource files, building files suitable for input to a",
    "\ttext formatter (such as nroff).",
    "",
    "USAGE",
    "",
    "\tdex [-dhtv] [-f rcfile] file1 file2 ...",
    "",
    "\t\t-d    enable debug output (implies -v)",
    "\t\t-h    print this help info",
    "\t\t-t    enable trace output",
    "\t\t-v    verbose mode",
    "",
    "\t\t-f    use specified reconfiguration file",
    "\t\t      (default \".dexrc\")",
    "",
    "OUTPUTS",
    "",
    "\tBy default DEX writes all output to stdout.",
    "\tThis can be changed via the reconfiguration file.",
    "",
    0
};


/*
 *  FUNCTION
 *
 *	main   DEX entry point
 *
 *  KEY WORDS
 *
 *	dex entry point
 *	main
 *
 *  SYNOPSIS
 *
 *	main(argc,argv)
 *	int argc;
 *	char *argv[];
 *
 *  DESCRIPTION
 *
 *	This is where DEX starts executing.  All argument list
 *	switches are processed first, then all the specified
 *	files are processed.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin main
 *	    Process command line options.
 *	    For each argument list field
 *		If field was not erased during option processing
 *		    If there is a previously processed file open
 *			Close the previous file.
 *		    End if
 *		    If the current file cannot be opened then
 *			Print error message.
 *		    Else
 *			Save input file name for future use.
 *			Reset current line number to 1.
 *			Do any reconfiguration requested.
 *			If verbose then tell user file name.
 *			Process the input file.
 *			Close the input file.
 *		    End if
 *		End if
 *	    End for
 *	    Exit with "normal" status.
 *	End main
 *
 */

main(argc, argv)
int argc;
char *argv[];
{
    FILE *fopen();
    char *argp;
    int argnum;

    options(argc,argv);
    for (argnum = 1; argnum < argc; argnum++) {
        if ((argp = argv[argnum]) != NULL) {
            if (infp != NULL) {
	        fclose(infp);
	    }
            if ((infp = fopen(argp,"r")) == NULL) {
		perror(argp);
	    } else {
	        infile = argp;
	        linenum = 1;
		reconfig(argp,rcfile);
		if (debug) {dump_tbl();}
		if (vflag) {printf("dex: processing \"%s\"\n",argp);}
	        yyparse();
		fclose(infp);
            }
        }
    }
    if (infp == stdin) {
	linenum = 1;
	reconfig(".",rcfile);
	yyparse();
    }
    exit(NORMAL_EXIT);
}

/*
 *  FUNCTION
 *
 *	tolower   force alphabetic characters to lower case
 *
 *  SYNOPSIS
 *
 *	char tolower(ch)
 *	char ch;
 *
 *  DESCRIPTION
 *
 *	Tolower forces alphabetic upper case characters to lower case.
 *	If the input character is already lower case, or is not
 *	alphabetic, then it is simply returned unchanged.
 *
 *  BUGS
 *
 *	Currently works only if native character set is ASCII.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin tolower
 *	    If character is an upper case alphabetic then
 *		Convert character to lower case and return it.
 *	    Else
 *		Return character unchanged.
 *	    End if
 *	End tolower
 *
 */

#ifndef tolower
char tolower(ch)
char ch;
{
    if ('A' <= ch && ch <= 'Z') {
	return(ch + 040);
    } else {
	return(ch);
    }
}
#endif

/*
 *  FUNCTION
 *
 *	options   process command line options
 *
 *  SYNOPSIS
 *
 *	options(argc,argv)
 *	int argc;
 *	char *argv[];
 *
 *  DESCRIPTION
 *
 *	Scans argument list, processing each switch as it is
 *	found.  The pointer to each switch string is then
 *	replaced with a NULL to effectively erase the switch
 *	argument.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin options
 *	    For each argument in the argument list
 *		Get pointer to first char of argument.
 *		If the argument is a switch then
 *		    Replace argument pointer with NULL.
 *		    Look at next argument character.
 *		    While there is another argument character
 *			Switch on the argument character
 *			Case "verbose":
 *			    Set verbose flag.
 *			    Break out of switch.
 *			Case "debug":
 *			    Set debug flag.
 *			    Set YACC debug flag.
 *			    Break out of switch.
 *			Case "reconfigure":
 *			    If reconfig file is next arg
 *				If there is no next arg
 *				    Abort with usage message.
 *				Else 
 *				    Save file name.
 *				    If file is actually switch
 *				        Abort with usage message.
 *				    Else
 *				        Erase reconfig switch
 *				    End if
 *				End if
 *			    Else
 *				Save pntr to reconfig filename.
 *			    End if
 *			    Break out of switch.
 *			Case "trace":
 *			    Set trace flag.
 *			    Break out of switch.
 *			Default:
 *			    Abort with usage message.
 *			End switch
 *		    End while
 *		End if
 *	    End for
 *	End options
 *
 */

options(argc, argv)
int argc;
char *argv[];
{
    int i;
    char c;		/* 1st char of current command-line argument */
    char *cp;		/* current argument pointer */

    for (i=1; i<argc; i++) {
        cp = argv[i];
        if (*cp == '-') {
            argv[i] = NULL;
	    cp++;
	    while (c = *cp++) {
	        switch (tolower(c)) {
	        case 'v':
	            vflag++;
	            break;
	        case 'd':
	            debug++;
		    yydebug++;
		    vflag++;
	            break;
	        case 'f':
	            if (*cp == NULL) {
		        if (++i >= argc) {
		            usage(c);
			} else {
			    rcfile = argv[i];
			    if (*rcfile == '-') {
		     	        usage(c);
			    } else {
		  	        argv[i] = NULL;
			    }
	       		}
	            } else {
		  	rcfile = cp;
	            }
	            break;
		case 't':
		    trace++;
		    break;
	        default:
	            usage(c);
	        }
            }
        }
    }
}

/*
 *  FUNCTION
 *
 *	usage   give usage message and abort
 *
 *  KEY WORDS
 *
 *	usage
 *	help processing
 *	abort locations
 *
 *  SYNOPSIS
 *
 *	usage(ch)
 *	char ch;       (is h for explicit help)
 *
 *  DESCRIPTION
 *
 *	Usage is typically called when a problem has been
 *	detected in the argument list, or usage help has
 *	been explicitly requested (via the -h flag).
 *	It prints an appropriate usage message and then aborts.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin usage
 *	    If character is not the explicit help character then
 *		Print a brief usage message.
 *		Print how to get more help.
 *	    Else
 *		Initialize internal documentation pointer.
 *		While there is a line of documentation to print
 *		    Print the line of documentation.
 *		End while
 *	    End if
 *	    Exit with error status.
 *	End usage
 *
 */

usage(c)
char c;		/* The char of the option */
{
    register char **dp;

    if (c != 'h') {
        printf("Usage: dex [-dhv] [-f rcfile]\n");
	printf("       dex -h for help.\n");
    } else {
	dp = documentation;
	while (*dp != NULL) {
	    printf("%s\n",*dp++);
	}
    }
    exit(ERROR_EXIT);
}

/*
 *  FUNCTION
 *
 *	indentation   set indentation to specified level
 *
 *  KEY WORDS
 *
 *	indent level
 *	emit functions
 *
 *  SYNOPSIS
 *
 *	indentation(where)
 *	int where;
 *
 *  DESCRIPTION
 *
 *	Checks requested indentation level against current
 *	indentation level.  If they do not match then
 *	emits appropriate nroff command to set indentation
 *	to the specified level.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin indentation
 *	    If current level is not that requested then
 *		Emit command to change indentation.
 *		Update the current indentation level.
 *	    End if
 *	End indentation
 *
 */

indentation(where)
int where;
{
    char buffer[16];

    if (indent_level != where) {
	sprintf(buffer,".in %d",where);
	emit(buffer,NULL);
	indent_level = where;
    }
}

/*
 *  FUNCTION
 *
 *	nofill   if filled mode is on, turn it off
 *
 *  KEY WORDS
 *
 *	fill mode
 *	emit functions
 *
 *  SYNOPSIS
 *
 *	nofill()
 *
 *  DESCRIPTION
 *
 *	Forces fill mode to be off.  If fill mode is currently
 *	on then emits suitable command to turn it off.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin nofill
 *	    If filled mode is on then
 *		Emit command to turn it off.
 *		Remember that it is now off.
 *	    End if
 *	End nofill
 *
 */

nofill()
{
	if (filled) {
	    emit(".nf",NULL);
	    filled = FALSE;
	}
}

/*
 *  FUNCTION
 *
 *	fill   if fill mode is off, turn it on
 *
 *  KEY WORDS
 *
 *	fill mode
 *	emit functions
 *
 *  SYNOPSIS
 *
 *	fill()
 *
 *  DESCRIPTION
 *
 *	Forces fill mode to be on.  If fill mode is currently
 *	off then emits suitable command to turn it on.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin fill
 *	    If filled mode is off then
 *		Emit command to turn it on.
 *		Remember that it is now on.
 *	    End if
 *	End fill
 *
 */

fill()
{
	if (!filled) {
	    emit(".fi",NULL);
	    filled = TRUE;
	}
}

/*
 *  FUNCTION
 *
 *	asterisks   emit command for centered asterisks line
 *
 *  KEY WORDS
 *
 *	emit functions
 *	box construction
 *
 *  SYNOPSIS
 *
 *	asterisks(how_many)
 *	int how_many;
 *
 *  DESCRIPTION
 *
 *	Emits a command to center a line with the specified
 *	number of asterisks.  This is used to form the top
 *	and bottom of a boxed item in the output.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin asterisks
 *	    For the specified number of asterisks
 *		Accumulate asterisk in buffer.
 *	    End for
 *	    Null terminate buffer string.
 *	    Emit command to cause line to be centered.
 *	    Emit asterisks buffer.
 *	End asterisks
 *
 */

asterisks(how_many)
int how_many;
{
    int count;
    char buffer[128];
    char *bp;

    for (bp = buffer, count = 0 ; count < how_many; count++) {
	*bp++ = '*';
    }
    *bp = NULL;
    emit(".ce",NULL);
    emit(buffer,'\\');
}

/*
 *  FUNCTION
 *
 *	emit_bline   emit command to form blank line
 *
 *  KEY WORDS
 *
 *	blank line
 *	emit functions
 *
 *  SYNOPSIS
 *
 *	emit_bline()
 *
 *  DESCRIPTION
 *
 *	Emits a suitable command to form a blank line in the
 *	output.  Checks first to verify that output is not
 *	being suppressed and the the current section is
 *	enabled for processing.  Finally, the current section
 *	must be enabled for text emission.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin emit_bline
 *	    If there is a current section being processed then
 *		If output is not suppressed and processing is enabled
 *		    If the section is enabled for text emission then
 *			Emit command to form blank line.
 *		    End if
 *		End if
 *	    End if
 *	End emit_bline
 *
 */

emit_bline ()
{
    if (section != NULL) {
	if (!suppress && (section->flags & PROCESS)) {
	    if (section->flags & EMITTEXT) {
		emit(".sp 1",NULL);
	    }
	}
    }
}

/*
 *  FUNCTION
 *
 *	set_section   lookup and set current section
 *
 *  KEY WORDS
 *
 *	emit functions
 *	section selection
 *	region suppression
 *
 *  SYNOPSIS
 *
 *	set_section(name)
 *	char *name;
 *
 *  DESCRIPTION
 *
 *	Looks up the specified section and makes it the
 *	current section.   Resets the region
 *	"box-emitted" and "suppress processing" flags.
 *	Also sets the indentation level back to the left
 *	margin and emits commands to print the section
 *	name (underlined).
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin set_section
 *	    Reset the box emitted flag for region.
 *	    Lookup the section and make it current.
 *	    If section name was found in table then
 *		If the section begins a region then
 *		    If the section is to be processed
 *			The region is processed also.
 *			Switch output stream.
 *			Emit cmd for indentation.
 *			Set indentation to zero.
 *			Emit cmd for filled mode.
 *			Set filled mode.
 *		    Else
 *			The region is suppressed.
 *		    End if
 *		End if
 *		If not suppressed and can be processed
 *		    Set indentation level back.
 *		    If new page is desired then
 *			Emit command to start new page.
 *		    End if
 *		    If text emission is enabled then
 *			If name is to be underlined
 *			    Emit underline command.
 *			End if
 *			Emit section name.
 *		    End if
 *		End if
 *	    End if
 *	End set_section
 *
 */

set_section(name)
char *name;
{
    struct tbl_data *tbl_find();

    box_emitted = FALSE;
    if (debug) {printf("set_section: looking for \"%s\"\n",name);}
    section = tbl_find(name);
    if (section != NULL) {
	if (section->flags & REGION) {
	    if (debug) {printf("set_section: REGION flag set\n");}
	    if (section->flags & PROCESS) {
	        if (debug) {printf("set_section: PROCESS flag set\n");}
		suppress = FALSE;
		set_output(section);
		emit(".in 0",NULL);
		indent_level = 0;
		emit(".fi",NULL);
		filled = TRUE;
	    } else {
		suppress = TRUE;
	    }
	}
	if (!suppress && (section->flags & PROCESS)) {
	    if (debug) {printf("set_section: PROCESS flag set\n");}
	    indentation(0);
	    if (section->flags & EMITBP) {
		emit(".bp",NULL);
	    }
	    if (section->flags & EMITTEXT) {
	        if (debug) {printf("set_section: EMITTEXT flag set\n");}
		if (section->flags & EMITUL) {
		    emit(".ul 1",NULL);
		}
		emit(name,'\\');
	    }
	}
    }
}

/*
 *  FUNCTION
 *
 *	reset_section   reset current section
 *
 *  KEY WORDS
 *
 *	sections
 *
 *  SYNOPSIS
 *
 *	reset_section();
 *
 *  DESCRIPTION
 *
 *	Resets current section so that there is no section selected.
 *	This is usually used when a non documentation line
 *	(I.E code) is encountered.  It terminates processing of the
 *	current section.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin reset_section
 *	    Set current section to NULL.
 *	End reset section
 *
 */

reset_section()
{
    section = NULL;
}

/*
 *  FUNCTION
 *
 *	emit_filled   emit text in filled mode
 *
 *  KEY WORDS
 *
 *	emit functions
 *	text emission
 *
 *  SYNOPSIS
 *
 *	emit_filled(text)
 *	char *text;
 *
 *  DESCRIPTION
 *
 *	Attempts to emit text in filled mode.  If filled
 *	mode is not enabled for the section then emits
 *	the text in unfilled mode.   Also attempts
 *	to print the first field of the text in a box,
 *	if the box output is enabled and has not yet been
 *	emitted for the current documentation section.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin emit_filled
 *	    If there is a current section then
 *		If section is not suppressed and can be processed
 *		    If box emission is enabled for section
 *			If box has not yet been emitted then
 *			    Get string to box.
 *			    Emit command for blank lines.
 *			    Emit commands for box top.
 *			    Emit commands to center string.
 *			    Emit commands for box bottom.
 *			    Emit commands for blank lines.
 *			    Set the box emitted flag.
 *			End if
 *		    End if
 *		    If text emission is enabled then
 *			Set indentation level.
 *			If section is enabled for fill then
 *			    Emit commands for fill.
 *			Else
 *			    Emit commands for no fill.
 *			End if
 *			Emit the text.
 *		    End if
 *		End if
 *	    End if
 *	End emit_filled
 *
 */

emit_filled(text)
char *text;
{
    char buffer[128];

    if (section != NULL) {
	if (!suppress && (section->flags & PROCESS)) {
	    if (section->flags & EMITBOX) {
		if (!box_emitted) {
		    xfield(title,text);
		    emit(".sp 3",NULL);
		    asterisks(strlen(title)+4);
		    emit(".ce",NULL);
		    sprintf(buffer,"* %s *",title);
		    emit(buffer,'\\');
		    asterisks(strlen(title)+4);
		    emit(".sp 3",NULL);
		    box_emitted = TRUE;
		}
	    }
	    if (section->flags & EMITTEXT) {
		indentation(8);
		if (section->flags & EMITFILL) {
		    fill();
		} else {
		    nofill();
		}
		emit(text,'\\');
	    }
	}
    }
}

/*
 *  FUNCTION
 *
 *	emit_unfilled   emit text in unfilled mode
 *
 *  KEY WORDS
 *
 *	text emission
 *	emit functions
 *
 *  SYNOPSIS
 *
 *	emit_unfilled(text)
 *	char *text;
 *
 *  DESCRIPTION
 *
 *	Emits text in unfilled mode providing there is
 *	a current section, it is not being suppressed,
 *	and it is enabled for processing and text emission.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin emit_unfilled
 *	    If there is a current section then
 *		If section is not suppressed and can be processed
 *		    If section is enabled for text emission
 *			Set nofill mode.
 *			Set indentation level.
 *			Emit text.
 *		    End if
 *		End if
 *	    End if
 *	End emit_unfilled
 *
 */

emit_unfilled(text)
char *text;
{
    if (section != NULL) {
	if (!suppress && (section->flags & PROCESS)) {
	    if (section->flags & EMITTEXT) {
		nofill();
		indentation(16);
		emit(text,'\\');
	    }
	}
    }
}

/*
 *  FUNCTION
 *
 *	set_output   switch output stream
 *
 *  KEY WORDS
 *
 *	output switching
 *
 *  SYNOPSIS
 *
 *	set_output(region)
 *	struct tbl_data *region;
 *
 *  DESCRIPTION
 *
 *	Tests to see if the region output has been redirected
 *	to a file.  If so, opens the file if necessary and
 *	remembers the file pointer in the table entry structure.
 *
 *	If no file is specified then the output goes to the
 *	standard output.
 *
 *	Initializes the global output pointer to point to
 *	the stream where output goes.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin set_output
 *	    If the table entry pointer is valid then
 *	        If the region has no output file
 *		    Direct output to standard out.
 *		Else
 *		    If the file is open then
 *			Set output to go to file.
 *		    Else
 *		        If file open succeeds
 *			    Remember file pointer.
 *			Else
 *			    Set output to stdout.
 *		        End if
 *		    End if
 *	        End if
 *	    End if
 *	End set_output
 *
 */

set_output(region)
struct tbl_data *region;
{
    if (region != NULL) {
	if (region->out == NULL) {
	    fp = stdout;
	} else {
	    if (region->ofp != NULL) {
		fp = region->ofp;
	    } else {
		if ((fp = fopen(region->out,"w")) != NULL) {
		    region->ofp = fp;
		} else {
		    fp = stdout;
		}
	    }
	}
    }
}

/*
 *  FUNCTION
 *
 *	emit   do actual output to current output stream
 *
 *  KEY WORDS
 *
 *	I/O functions
 *	emit
 *
 *  SYNOPSIS
 *
 *	emit(text,quote)
 *	char *text;
 *	char quote;
 *
 *  DESCRIPTION
 *
 *	Calls appropriate operating system and/or library
 *	functions to do the actual I/O.  Is localized here
 *	for both logical and portability reasons.
 *
 *	Emit will automatically append a newline to the output
 *	of each string.  Also, if the quote character is not
 *	null then all '\' and '.' characters will be preceeded by a
 *	'\' character.
 *
 */

/*
 *  PSEUDO CODE
 *
 *	Begin emit
 *	    If text pointer is not invalid pointer then
 *		While there is another character to emit
 *		    If quote flag set and char needs quoting
 *			Emit quote character.
 *		    End if
 *		    Emit character.
 *		End while
 *		Emit newline character.
 *	    End if
 *	End emit
 *
 */

emit(text,quote)
char *text;
char quote;
{
    if (text != NULL) {
	while (*text != NULL) {
	    if (quote != NULL && (*text == '.' || *text == '\\')) {
		fputc(quote,fp);
	    }
	    fputc(*text++,fp);
	}
	fputc('\n',fp);
    }
}

#ifndef unix
perror (err)
char *err;
{
	if (err && *err) {
		fprintf (stderr, "%s: ", err);
	}
	fprintf (stderr, "<unknown error!>\n");
}
#endif unix
