/*
 * Copyright (c) 1998, 1999, 2000, 2002, 2003
 *	Tama Communications Corporation
 *
 * This file is part of GNU GLOBAL.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include "gparam.h"
#include "strlimcpy.h"
#include "token.h"

/*
 * File input method.
 */
int lineno;
const char *sp, *cp, *lp;
int crflag;			/* 1: return '\n', 0: doesn't return */
int cmode;			/* allow token which start with '#' */
int cppmode;			/* allow '::' as a token */
int ymode;			/* allow token which start with '%' */
char token[MAXTOKEN];
char curfile[MAXPATHLEN];
int continued_line;		/* previous line ends with '\\' */

static char ptok[MAXTOKEN];
static int lasttok;
static FILE *ip;
static STRBUF *ib;

#define tlen	(p - &token[0])
static void pushbackchar(void);

/*
 * opentoken:
 */
int
opentoken(const char *file)
{
	/*
	 * b flag is needed for WIN32 environment. Almost unix ignore it.
	 */
	if ((ip = fopen(file, "rb")) == NULL)
		return 0;
	ib = strbuf_open(MAXBUFLEN);
	strlimcpy(curfile, file, sizeof(curfile));
	sp = cp = lp = NULL; ptok[0] = '\0'; lineno = 0;
	crflag = cmode = cppmode = ymode = 0;
	continued_line = 0;
	return 1;
}
/*
 * closetoken:
 */
void
closetoken(void)
{
	strbuf_close(ib);
	fclose(ip);
}

/*
 * nexttoken: get next token
 *
 *	i)	interested	interested special character
 *				if NULL then all character.
 *	i)	reserved	converter from token to token number
 *				if this is specified, nexttoken() return
 *				word number, else return symbol.
 *	r)	EOF(-1)	end of file
 *		c ==0		symbol ('tok' has the value.)
 *		c < 256		interested special character
 *		c > 1000	reserved word
 *
 * nexttoken() doesn't return followings.
 *
 * o comment
 * o space (' ', '\t', '\f', '\v', '\r')
 * o quoted string ("...", '.')
 * o number
 */

int
nexttoken(const char *interested, int (*reserved)(const char *, int))
{
	int c;
	char *p;
	int sharp = 0;
	int percent = 0;

	/* check push back buffer */
	if (ptok[0]) {
		strlimcpy(token, ptok, sizeof(token));
		ptok[0] = '\0';
		return lasttok;
	}

	for (;;) {
		/* skip spaces */
		if (!crflag)
			while ((c = nextchar()) != EOF && isspace(c))
				;
		else
			while ((c = nextchar()) != EOF && isspace(c) && c != '\n')
				;
		if (c == EOF || c == '\n')
			break;

		if (c == '"' || c == '\'') {	/* quoted string */
			int quote = c;

			while ((c = nextchar()) != EOF) {
				if (c == quote)
					break;
				if (quote == '\'' && c == '\n')
					break;
				if (c == '\\' && (c = nextchar()) == EOF)
					break;
			}
		} else if (c == '/') {			/* comment */
			if ((c = nextchar()) == '/') {
				while ((c = nextchar()) != EOF)
					if (c == '\n') {
						pushbackchar();
						break;
					}
			} else if (c == '*') {
				while ((c = nextchar()) != EOF) {
					if (c == '*') {
						if ((c = nextchar()) == '/')
							break;
						pushbackchar();
					}
				}
			} else
				pushbackchar();
		} else if (c == '\\') {
			if (nextchar() == '\n')
				continued_line = 1;
		} else if (isdigit(c)) {		/* digit */
			while ((c = nextchar()) != EOF && (c == '.' || isalnum(c)))
				;
			pushbackchar();
		} else if (c == '#' && cmode) {
			/* recognize '##' as a token if it is reserved word. */
			if (peekc(1) == '#') {
				p = token;
				*p++ = c;
				*p++ = nextchar();
				*p   = 0;
				if (reserved && (c = (*reserved)(token, tlen)) == 0)
					break;
			} else if (!continued_line && atfirst_exceptspace()) {
				sharp = 1;
				continue;
			}
		} else if (c == ':' && cppmode) {
			if (peekc(1) == ':') {
				p = token;
				*p++ = c;
				*p++ = nextchar();
				*p   = 0;
				if (reserved && (c = (*reserved)(token, tlen)) == 0)
					break;
			}
		} else if (c == '%' && ymode) {
			/* recognize '%%' as a token if it is reserved word. */
			if (atfirst) {
				p = token;
				*p++ = c;
				if ((c = peekc(1)) == '%' || c == '{' || c == '}') {
					*p++ = nextchar();
					*p   = 0;
					if (reserved && (c = (*reserved)(token, tlen)) != 0)
						break;
				} else if (!isspace(c)) {
					percent = 1;
					continue;
				}
			}
		} else if (c & 0x80 || isalpha(c) || c == '_') {/* symbol */
			p = token;
			if (sharp) {
				sharp = 0;
				*p++ = '#';
			} else if (percent) {
				percent = 0;
				*p++ = '%';
			} else if (c == 'L') {
				int tmp = peekc(1);

				if (tmp == '\"' || tmp == '\'')
					continue;
			}
			for (*p++ = c; (c = nextchar()) != EOF && (c & 0x80 || isalnum(c) || c == '_'); *p++ = c)
				;
			*p = 0;
	
			if (c != EOF)
				pushbackchar();
			/* convert token string into token number */
			c = SYMBOL;
			if (reserved)
				c = (*reserved)(token, tlen);
			break;
		} else {				/* special char */
			if (interested == NULL || strchr(interested, c))
				break;
			/* otherwise ignore it */
		}
		sharp = percent = 0;
	}
	return lasttok = c;
}
/*
 * pushbacktoken: push back token
 *
 *	following nexttoken() return same token again.
 */
void
pushbacktoken(void)
{
	strlimcpy(ptok, token, sizeof(ptok));
}
/*
 * peekc: peek next char
 *
 *	i)	immediate	0: ignore blank, 1: include blank
 *
 * Peekc() read ahead following blanks but doesn't change line.
 */
int
peekc(int immediate)
{
	int c;
	long pos;

	if (cp != NULL) {
		if (immediate)
			c = nextchar();
		else
			while ((c = nextchar()) != EOF && c != '\n' && isspace(c))
				;
		if (c != EOF)
			pushbackchar();
		if (c != '\n' || immediate)
			return c;
	}
	pos = ftell(ip);
	if (immediate)
		c = getc(ip);
	else
		while ((c = getc(ip)) != EOF && isspace(c))
			;
	(void)fseek(ip, pos, SEEK_SET);

	return c;
}
/*
 * atfirst_exceptspace: return if current position is the first column
 *			except for space.
 *	|      1 0
 *      |      v v
 *	|      # define
 */
int
atfirst_exceptspace(void)
{
	const char *start = sp;
	const char *end = cp ? cp - 1 : lp;

	while (start < end && *start && isspace(*start))
		start++;
	return (start == end) ? 1 : 0;
}
/*
 * pushbackchar: push back character.
 *
 *	following nextchar() return same character again.
 * 
 */
static void
pushbackchar(void)
{
        if (sp == NULL)
                return;         /* nothing to do */
        if (cp == NULL)
                cp = lp;
        else
                --cp;
}
