/*
 * Utility routines for 'mp', the C macro preprocessor.
 */

/*
 * Include compile-time constants, and external and structure defintions.
 */

#ifdef	decus
#define stdio
#define	Digital
#endif

#ifdef	vms
#define	stdio
#define	Digital
#endif

#ifdef	stdio
#include <stdio.h>
#else
#define	NULL	(0)
#endif

#include "mpdefs.h"

/*
 * These are defined here for error messages.
 */
extern int lineno[];		/* Line number stack */
extern int pageno[];		/* Page number stack */
extern char fname[][40];	/* File name stack */
extern int inclvl;		/* #include depth */
extern int nocomp;		/* Flag: don't do auto #LINE's */
extern int linefeeds;		/* Running count of LF's in output */
extern int in_com;		/* If TRUE, currently inside a comment */

/*
 * Print error message on standard output
 */

printerr(msg)
char *msg;
{
	extern int err_cnt;		/* Error count */
	register int i, j;

	for (i = 0; i <= inclvl; i++) {
		for (j = 0; j < i; j++)
			putchar('\t');
		printf("Page %d, line %d %s\n",
			 pageno[i], lineno[i], fname[i]);
	}
	printf("\t%s\n", msg);
	err_cnt++;
	return;
}

/*
 * Print diagnostic message on standard output
 */

diagnostic(msg)
char *msg;
{
	extern int err_cnt;		/* Error count */
	printerr(msg);
	err_cnt--;			/* Undo printerr's count */
	return;
}

/*
 * Internal bug catcher
 */

screech(msg)
char *msg;
{
	printerr("ANTI-BUG CHECK FAILURE:");
	printerr(msg);
	abort();
}

/*
 * Skip a string of blanks and tabs
 */

char *skipblnk(ptr)
register char *ptr;
{
	while (*ptr == ' ' || *ptr == '\t')
		ptr++;
	return(ptr);
}

/*
 * String comparison -- replace with "streq()"
 */

lexeq(cp1, cp2)
register char *cp1, *cp2;
{
	while (*cp1 != EOS && *cp2 != EOS) {
		if (*cp1++ != *cp2++)
			return(FALSE);
	}
	return(*cp1 == *cp2);
}

/*
 * get_mem - Allocate 'nbytes' of dynamic memory.  If none is available, report
 *	an error and exit with nonzero status; otherwise return pointer to
 *	the node.
 */

char
*get_mem(nbytes)
int nbytes;
{
	register char *p;

	if ((p = malloc(nbytes)) == NULL) {
		printerr("No dynamic memory available");
		exits(4);
	}
	else return(p);
}

/*
 * Concatenate two strings, return pointer to result in local static buffer
 * replace with strcat().
 */

char
*concat(str1, str2)
register char *str1;
char *str2;
{
	register int limit;
	register char *t;
	static char buffer[CATMAX];

	limit = buffer+CATMAX-1;	/* Buffer limit */

	for (t = buffer; t <= limit && (*t = *str1) != EOS; t++, str1++)
		;
	while (t <= limit && (*t = *str2) != EOS) {
		t++; str2++;
	}
	if (t > limit)
		screech("Insufficient space in concat()");
	else return(buffer);
}

/*
 * Push an item on the stack specified
 */

int
push(item, stkaddr)
int item;
register struct stack *stkaddr;
{
	if (stkaddr->spcnt == 0)
		return(ERROR);
	stkaddr->spcnt-- , stkaddr->usecnt++;
	*stkaddr->stackptr++ = item;
	return(OK);
}

/*
 * Pop an item off of the stack
 */

int
pop(stkaddr)
register struct stack *stkaddr;
{
	if (empty(stkaddr))
		screech("Stack underflow!");
	stkaddr->spcnt++, stkaddr->usecnt--;
	return(*--stkaddr->stackptr);
}

/*
 * Test whether a stack is empty
 */

int
empty(stkaddr)
struct stack *stkaddr;
{
	return(stkaddr->usecnt == 0);
}

/*
 * skipq - Routine to return pointer to next unquoted character starting from
 *	"src". All quoted text is flushed to either a text buffer or the
 *	output file, as determined by "dst".  If "dst" == NIL, quoted text
 *	is written to "outfil"; if "dst" is non-NIL, then any bypassed
 *	characters are moved to the buffer specified.  The parameter "dstmax"
 *	specified the last address into which a character may be deposited.
 *	All comments are also flushed here.  For multi-line comments, a
 *	pointer to the end of line is returned.  When end of comment is
 *	encountered, scanning is resumed immediately afterward.
 */

int unmagic = FALSE;

char
*skipq(src, dst, dstmax)
register char *src;
char **dst, *dstmax;
{
	register char	quote;
	register int	full;
	extern char	*skipcom();

	if (in_com)
		src = skipcom(src);

	switch(*src) {
	case '\\':
		unmagic = !unmagic;
		return(src);
	case '\'':
	case '"':
		if (unmagic)
			break;
		putch(quote = *src++, dst, dstmax);
		for(;;) {
			if (*src == EOS) {
				printerr("Unterminated quoted string");
				break;
			}
			if (*src == '\\') {
				putch(*src++, dst, dstmax);
				unmagic = !unmagic;
				continue;
			}
			full = putch(*src, dst, dstmax);
			if (*src++ == quote && !unmagic)
				break;
			unmagic = FALSE;
		}
		return((full == ERROR) ? ERROR : src);
	case '/':
		if (*(src+1) == '*') {
			in_com = TRUE;
			return(skipcom(src+2));
		}
		else break;
	}
	unmagic = FALSE;
	return(src);
}

/*
 * skipcom - Routine to skip past any comment characters, until either end of
 * comment or end of line.  If end of comment is encountered, in_com is set to
 * FALSE.
 */

char
*skipcom(src)
register char *src;
{
	while ((*src != '*' || *(src+1) != '/')) {
		if (*src == EOS)
			return(src);
		if ((*src == '/' && *(src+1) == '*'))
			diagnostic("Warning: nested comments");
		src++;
		}
	src += 2;
	in_com = FALSE;
	return(src);
}

/*
 *	Routine to emit a flushed character to either the current output
 *	file or to the buffer whose address is specified.  Returns ERROR if
 *	the buffer is overrun.
 */

int
putch(ch, where, limit)
register char ch;
char **where, *limit;
{
#ifdef stdio
	extern FILE		*outfil;
#else
	extern struct buf *outfil;
#endif

	if (where == NIL) {
		if ((ch == '\n') & !nocomp) {
			if (linefeeds++ == 0)
				putc(ch, outfil);
		}
		else {
			if (linefeeds > 1) {
				if (linefeeds == 2)
					putc('\n', outfil);
			    	else {
#ifdef UNIMATION
#ifdef PREPRE
					fprintf(outfil,
						"#define pg #%d p.%d %s\npg\n",
#else
					fprintf(outfil,"#%d p.%d %s\n",
#endif
						lineno[inclvl],
						pageno[inclvl],
						fname[inclvl]);
#else
					fprintf(outfil, "#%d %s\n",
						lineno[inclvl], fname[inclvl]);
#endif
				}
			}
			linefeeds = 0;
			putc(ch, outfil);
		}
		return;
	}
	else {
		if (*where > limit)
			return(ERROR);
		else
			*(*where)++ = ch;
		return(OK);
	}
}

/*
 *	Routine to release entries in the call_ala.  The last entry to free
 *	is specified by 'last'.
 */

rlse_ala(last)
register int last;
{
	register int i;
	extern char *call_ala[];

	for(i = 0; i <= last; i++)
		free(call_ala[i]);
	return;
}

/*
 * get_id - Get an identifier (1-MAXIDLEN alphanumerics + '_', must start with
 *	an alpha).  Note that alpha's include '_' and (in Dec-land) '$'.
 */

char
*get_id(src, dst)
register char *src, *dst;
{
	register int	i;

	for(i = 0; i < MAXIDLEN && c_alnum(*src); i++) {
		*dst++ = *src++;
	}
	while (c_alnum(*src))
		src++;
	*dst = EOS;
	return(src);
}

/*
 *	Increment the character string representation of a number.  First
 *	argument specifies the address of the string, second argument
 *	specifies the index in the string of the character to be incremented.
 *	Calls itself recursively if we carry to the next digit.  Wraps around
 *	to 0 if the largest unsigned number is incremented.
 */

charincr(string, index)
register char string[];
register int index;
{
	if (index <= 0)
		return;				/* Allow wrap-around	*/

	switch (string[index]) {
	case ' ':
		string[index] = '1';		/* ' ' == '0'		*/
		break;
	case '9':
		string[index] = '0';
		charincr(string, index-1);
		break;
	default:
		string[index] += 1;
		break;
	}

	return;
}

c_alpha(c)
register int	c;
/*
 * return(TRUE if c is a C-language alpha (A-Z,a-z, "_", "$")
 */
{
	return (isalpha(c) || c == '_' || c == '$');
}

c_alnum(c)
register int	c;
/*
 * Return TRUE if c is a C-language alpha (A-Z, a-z, "_", "$")
 * or a digit.
 */
{
	return (isalnum(c) || c == '_' || c == '$');
}
