/*	lex.c
 *
 *		This is the lexical engine
 */

/*  RCompile - UNIX Resource compiler for YAAF Library
 *  Copyright (C) 1998 William Edward Woody
 *  
 *  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 2
 *  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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include "lex.h"

/************************************************************************/
/*																		*/
/*	Read Structures														*/
/*																		*/
/************************************************************************/

/*	ReadRecord
 *
 *		Record storing read/write data
 */

struct ReadRecord {
	char		file[64];
	long		line;
	int			push;
	FILE		*fptr;
};


/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

long						GInteger;
double						GReal;
char						GToken[256];

static short				GPushToken;
static int					GReadPtr;
static struct ReadRecord	GRead[8];

/************************************************************************/
/*																		*/
/*	Error management													*/
/*																		*/
/************************************************************************/

/*	PrintError
 *
 *		Print the error
 */

void PrintError(char *arg,...)
{
	va_list vlist;
	
	va_start(vlist,arg);
	if (GReadPtr >= 0) {
		fprintf(stderr,"[%s@%ld] ",GRead[GReadPtr].file,GRead[GReadPtr].line);
	}
	vfprintf(stderr,arg,vlist);
	fprintf(stderr,"\n");
	va_end(vlist);
}

/************************************************************************/
/*																		*/
/*	Token Search														*/
/*																		*/
/************************************************************************/

/*	TokenList
 *
 *		List of tokens
 */

struct TokenElement {
	int		value;
	char	*token;
} TokenList[] = {
	{	KTokenRaw,		"raw"		},
	{	KTokenRData,	"rdata"		},
	{	KTokenMenu,		"menu"		},
	{	KTokenBegin,	"begin"		},
	{	KTokenEnd,		"end"		},
	{	KTokenItem,		"item"		},
	{	KTokenSeparator,"separator"	},
	{	KTokenControl,	"control"	},
	{	-1,				NULL		}
};
	

/*	TokenSearch
 *
 *		It ain't the quickest, but it works
 */

static int TokenSearch(char *str)
{
	int i;
	
	for (i = 0; TokenList[i].token != NULL; i++) {
		if (!strcmp(TokenList[i].token,str)) return TokenList[i].value;
	}
	return KLexToken;
}

/*	TokenToString
 *
 *		Given a token (by it's identifier), this returns the string
 *	form of that token. This is used for error processing
 */

const char *TokenToString(int value)
{
	int i;
	static char b[2];
	
	if (value < 256) {
		b[0] = value;
		b[1] = 0;
		return b;
	}
	switch (value) {
		case KLexInteger:		return "<integer>";
		case KLexString:		return "<string>";
		case KLexToken:			return "<symbol>";
		case KLexChar:			return "<type>";
		case KLexReal:			return "<number>";
	}
	for (i = 0; TokenList[i].token != NULL; i++) {
		if (TokenList[i].value == value) return TokenList[i].token;
	}
	
	return "<unknown>";
}

/************************************************************************/
/*																		*/
/*	Lex Initialization													*/
/*																		*/
/************************************************************************/

/*	InitLexEngine
 *
 *		This handles initialization of the lexical engine
 */

void InitLexEngine(void)
{
	GReadPtr = -1;
	GPushToken = -1;
}

/*	EndLexEngine
 *
 *		End the lexical parser
 */

void EndLexEngine(void)
{
	while (GReadPtr >= 0) fclose(GRead[GReadPtr--].fptr);
	GPushToken = -1;
}

/*	ReadLexFile
 *
 *		Read a file
 */

void ReadLexFile(char *f)
{
	FILE *fptr;
	
	if (GReadPtr >= 7) PrintError("Can't open '%s': too many open files",f);
	fptr = fopen(f,"r");
	if (fptr == NULL) PrintError("Can't open '%s': unable to find file",f);

	GReadPtr++;
	strcpy(GRead[GReadPtr].file,f);
	GRead[GReadPtr].line = 1;
	GRead[GReadPtr].fptr = fptr;
	GRead[GReadPtr].push = -1;
}

/************************************************************************/
/*																		*/
/*	Low level char reader												*/
/*																		*/
/************************************************************************/

/*	ReadChar
 *
 *		Read a character--this strips out C and C++ style comments
 */

static int ReadChar(void)
{
	int c;
	int d;
	
	/*
	 *	Simple screening
	 */
	
	if (GReadPtr < 0) return -1;
	if (GRead[GReadPtr].push != -1) {
		c = GRead[GReadPtr].push;
		GRead[GReadPtr].push = -1;
		return c;
	}

	/*
	 *	Pop files that are at EOL
	 */
	
	c = fgetc(GRead[GReadPtr].fptr);
	if (c == -1) {
		fclose(GRead[GReadPtr--].fptr);
		return ' ';
	}
	
	/*
	 *	Update line count
	 */
	
	if (c == '\n') GRead[GReadPtr].line++;

	/*
	 *	Strip comments
	 */
	
	if (c == '/') {
		d = fgetc(GRead[GReadPtr].fptr);
		if (d == '/') {
			/*
			 *	Scan for C++ style comment
			 */
			
			while ((d != -1) && (d != '\n')) {
				d = fgetc(GRead[GReadPtr].fptr);
			}
			GRead[GReadPtr].line++;
			return '\n';
		}
		
		if (d == '*') {
			/*
			 *	Scan for C style comment
			 */
			
			for (;;) {
				c = fgetc(GRead[GReadPtr].fptr);
				if (c == '\n') {
					GRead[GReadPtr].line++;
				}
				if (c == -1) {
					PrintError("End of comment reached in file");
					GRead[GReadPtr].line++;
					return ' ';
				}
				if (c == '*') {
					c = fgetc(GRead[GReadPtr].fptr);
					if (c == '/') return ' ';
					ungetc(c,GRead[GReadPtr].fptr);
				}
			}
		}
		
		/*
		 *	Not a comment
		 */
		
		ungetc(d,GRead[GReadPtr].fptr);
	}
	return c;
}

/*	PushChar
 *
 *		Push character back; store on the GPushChar variable because
 *	we're using the available slot in the ungetc for pushing comments
 */

static void PushChar(int c)
{
	if (GReadPtr >= 0) GRead[GReadPtr].push = c;
}

/************************************************************************/
/*																		*/
/*	High level token reader												*/
/*																		*/
/************************************************************************/

/*	PushBackToken
 *
 *		Push token back
 */

void PushBackToken(int c)
{
	GPushToken = c;
}

/*	GetNextToken
 *
 *		Get the next token
 */

int GetNextToken(void)
{
	int c;
	int d;
	
	/*
	 *	Get pushed tokens
	 */
	
	if (GPushToken != -1) {
		c = GPushToken;
		GPushToken = -1;
		return c;
	}

	/*
	 *	Get the token
	 */
	
	do {
		c = ReadChar();
	} while (isspace(c));
	
	/*
	 *	Handle EOF by sticking token <EOF> into the token field
	 */
	
	if (c == -1) {
		strcpy(GToken,"<eof>");
		return -1;
	}
	
	/*
	 *	What type of character do I have?
	 */
	 
	if (c == '"') {
		/*
		 *	Read rest of string (don't use ReadChar--get comments too)
		 */
		
		d = 0;
		c = fgetc(GRead[GReadPtr].fptr);
		while ((c != '"') && (c != '\n') && (c != -1)) {
			GToken[d++] = c;
			if (d >= 255) break;
			c = fgetc(GRead[GReadPtr].fptr);
		}
		
		/*
		 *	Return type
		 */
		
		GToken[d] = 0;
		return KLexString;
	}
	
	if (c == '\'') {
		/*
		 *	Read the next four characters (or so)
		 */
		
		d = 0;
		c = fgetc(GRead[GReadPtr].fptr);
		while ((c != '\'') && (c != '\n') && (c != -1)) {
			GToken[d++] = c;
			if (d >= 255) break;
			c = fgetc(GRead[GReadPtr].fptr);
		}
		
		/*
		 *	Return type. Pad to 4 characters with NULL
		 */
		
		while (d < 4) GToken[d++] = 0;
		GToken[d] = 0;
		return KLexChar;
	}
	
	if ((c == '$') || (c == '_') || isalpha(c)) {
		/*
		 *	Read rest of token
		 */
		
		d = 0;
		while ((c == '$') || (c == '_') || isalnum(c)) {
			GToken[d++] = c;
			if (d >= 255) break;
			c = ReadChar();
		}
		PushChar(c);
		
		/*
		 *	Null terminate token and return
		 */
		
		GToken[d] = 0;
		return TokenSearch(GToken);
	}
	
	if ((c == '-') || (c == '.') || isdigit(c)) {
		/*
		 *	Look forward if '-' or '.' for number component
		 */
		
		if (c == '-') {
			d = ReadChar();				/* Quick readahead */
			PushChar(d);
			if ((d != '.') && !isdigit(d)) {
				GToken[0] = '-';
				GToken[1] = 0;
				return '-';
			}
		} else if (c == '.') {
			d = ReadChar();
			PushChar(d);
			if (!isdigit(d)) {
				GToken[0] = '.';
				GToken[1] = 0;
				return '.';
			}
		}
		
		/*
		 *	Read the rest of the number
		 */
		
		d = 0;
		do {
			GToken[d++] = c;
			if (d >= 255) break;
			c = ReadChar();
		} while ((c == '.') || isdigit(c));
		PushChar(c);
		
		/*
		 *	Conversion
		 */
		
		GToken[d] = 0;
		for (c = 0; GToken[c]; c++) {
			if (GToken[c] == '.') break;
		}
		GInteger = atol(GToken);
		GReal    = atof(GToken);
		if (GToken[c]) return KLexReal;
		else return KLexInteger;
	}
	
	/*
	 *	If we get here, we have a simple token.
	 */
	
	GToken[0] = c;
	GToken[1] = 0;
	return c;
}

			
