/*
	TSScanInit() - initialize scanner to scan  a buffer.
	TSScan() - pull successive tokens out of a character.  Returns
	NULL when there are no more.  Has the property that once it returns
	NULL, succeeding calls continue to return NULL until TSScanInit() is
	called again.
	Default behavior is that tokens are sequences of non-blank characters,
	or quotes can be used to allow whitespace characters in the token.
	The '\' character turns off any special meaning of the following
	character.

	TSSetScanner () - install scanner.
	TSGetScanner () - get current scanner.
	TSSetScanPos() - set current position within scan buffer.
	TSGetScanPos() - get current position within scan buffer.
	TSIsScanDelim() - test whether character is in current delimiter set.

	The original buffer is destroyed by scanning, since null bytes are
	placed after each token as they are found.

	12 Apr 90 V1.0.  Created (as part of NIO).
	25 Sep 90 Reorganized to allow accommodating alternate scanners while
		preserving current scan position within buffer.
	26 Oct 90 Removed from NIO and made into separate library.
	25 Sep 91 V1.02.  Parameterized the quote and escape characters.
*/

# include	<stdio.h>

# include	"tokenscan.h"

# ifdef SYSV
# define	index	strchr
# endif /* SYSV */

extern char	*index ();


static char *CanonScan ();

static TSScanner	defScan = { NULL, CanonScan, " \t\n", "\"'", "\\" };
static TSScanner	curScan = { NULL, CanonScan, " \t\n", "\"'", "\\" };

static char	*curPos;


void TSScanInit (p)
char	*p;
{
	curPos = p;
	if (curScan.scanInit != NULL)
		(*curScan.scanInit) (p);
}


void TSSetScanner (p)
TSScanner	*p;
{
	if (p == NULL || p->scanInit == NULL)
		curScan.scanInit = defScan.scanInit;
	else
		curScan.scanInit = p->scanInit;
	if (p == NULL || p->scanScan == NULL)
		curScan.scanScan = defScan.scanScan;
	else
		curScan.scanScan = p->scanScan;
	if (p == NULL || p->scanDelim == NULL)
		curScan.scanDelim = defScan.scanDelim;
	else
		curScan.scanDelim = p->scanDelim;
	if (p == NULL || p->scanQuote == NULL)
		curScan.scanQuote = defScan.scanQuote;
	else
		curScan.scanQuote = p->scanQuote;
	if (p == NULL || p->scanEscape == NULL)
		curScan.scanEscape = defScan.scanEscape;
	else
		curScan.scanEscape = p->scanEscape;
}


void TSGetScanner (p)
TSScanner	*p;
{
	p->scanInit = curScan.scanInit;
	p->scanScan = curScan.scanScan;
	p->scanDelim = curScan.scanDelim;
	p->scanQuote = curScan.scanQuote;
	p->scanEscape = curScan.scanEscape;
}


void TSSetScanPos (p)
char	*p;
{
	curPos = p;
}


char *TSGetScanPos ()
{
	return (curPos);
}


int TSIsScanDelim (c)
char	c;
{
char	c2, *p;

	if ((p = curScan.scanDelim) != (char *) NULL)
	{
		while ((c2 = *p++) != '\0')
		{
			if (c == c2)
				return (1);
		}
	}
	return (0);
}


int TSIsScanQuote (c)
char	c;
{
char	c2, *p;

	if ((p = curScan.scanQuote) != (char *) NULL)
	{
		while ((c2 = *p++) != '\0')
		{
			if (c == c2)
				return (1);
		}
	}
	return (0);
}


int TSIsScanEscape (c)
char	c;
{
char	c2, *p;

	if ((p = curScan.scanEscape) != (char *) NULL)
	{
		while ((c2 = *p++) != '\0')
		{
			if (c == c2)
				return (1);
		}
	}
	return (0);
}


char *TSScan ()
{
	return ((*curScan.scanScan) ());
}


/* probably could use some optimization */

static char *CanonScan ()
{
char	*pos, *start, *p, c, quote = 0;
int	escape = 0;

	pos = TSGetScanPos ();

	while (TSIsScanDelim (*pos))		/* skip delimiters */
		pos++;
	if ((c = *pos) == '\0')		/* end of line - no token */
	{
		TSSetScanPos (pos);
		return (NULL);
	}
	start = p = pos;			/* start of token */
	for (;;)
	{
		if ((c = *pos) == '\0' || c == '\n')	/* eol terminates */
		{					/* all tokens */
			*p = *pos = '\0';
			break;
		}
		if (escape)			/* previous char was escape; */
		{				/* pass this one literally */
			escape = 0;
			*p++ = *pos++;
		}
		else if (TSIsScanEscape (c))	/* this char is escape; */
		{				/* pass next one literally */
			escape = 1;
			pos++;
		}
		else if (quote)		/* quote mode */
		{
			if (c != quote)	/* no end quote yet */
			{
				*p++ = *pos++;	/* add char, continue */
				continue;
			}
			/*
			 * Peek at next char.  If it's another quote, go right
			 * back into quote mode.  If it's white or eol, token
			 * token is done.  Else continue in noquote mode.
			 */
			quote = 0;
			c = *++pos;
			if (TSIsScanQuote (c))	/* begin quote mode again */
			{
				++pos;
				quote = c;
			}
			else if (c == '\0' || c == '\n')
			{
				*p = *pos = '\0';
				break;
			}
			else if (TSIsScanDelim (c))
			{
				*p = *pos++ = '\0';
				break;
			}
			else
				*p++ = *pos++;
		}
		else
		{
			if (TSIsScanQuote (c))	/* begin quote mode */
			{
				++pos;
				quote = c;
			}
			else if (TSIsScanDelim (c))
			{
				*p = *pos++ = '\0';
				break;
			}
			else
				*p++ = *pos++;
		}
	}
	TSSetScanPos (pos);
	return (start);
}
