/*
**	msql_lex.c	- 
**
**
** Copyright (c) 1993-95  David J. Hughes
** Copyright (c) 1996-97  Hughes Technologies Pty Ltd.
**
** Permission to use, copy, and distribute for non-commercial purposes,
** is hereby granted without fee, providing that the above copyright
** notice appear in all copies and that both the copyright notice and this
** permission notice appear in supporting documentation.
**
*/

/*
** This is a hand crafted scanner that looks and smells like a lex
** generated scanner.  I've kept the same interface so that unmodified
** yacc parsers can run with this.
**
** This scanner uses a state machine to translate the input data into
** tokens.  Failed matches cause a fallback to the start of the token
** scan and a possible transition to a known alternate state.  The
** state structure is defined in doc/scanner.doc
**
** NOTE : Because the scanner must revert back to the start of the
**	token on a failure it can only work from an input buffer. It
**	cannot work from a file or anything else.
*/


#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#ifdef WIN32
#  include <winsock.h>
#endif

#ifndef OS2
#  include <unistd.h>
#  include <stdlib.h>
#endif

#include <common/portability.h>

#define	_MSQL_SERVER_SOURCE
#include "msql_priv.h"

#if defined(WIN32)
#  include "msql_yacc.h"
#else
#  include "y.tab.h"
#endif

#define	REG		register
#define NUM_HASH	16

/*
** Macros for handling the scanner's internal pointers
*/
#define yyGet()		(*tokPtr++); yytoklen++
#define yyUnget()	tokPtr--; yytoklen--
#define yySkip()	(*tokPtr++); tokStart++
#define yyRevert()	{tokPtr=tokStart; yytoklen=0;}
#define yyReturn(t)	{tokStart=tokPtr; yyrep=tokenDup(yytext,yytoklen);return(t);}


/*
** Macros for matching character classes.  These are in addition to
** those provided in <ctypes.h>
*/
#ifdef	iswhite
# undef iswhite
#endif
#define iswhite(c)	(c == ' ' || c == '\t' || c == '\n')

#ifdef	iscompop
# undef iscompop
#endif
#define iscompop(c)	(c == '<' || c == '>' || c == '=')


/*
** Debugging macros.
*/

/* Define this to watch the state transitions */
/* #define DEBUG_STATE	*/

#ifdef DEBUG
#  define token(x)	(int) "x"
#else
#  define token(x)	x
#endif /* DEBUG */

#ifdef DEBUG_STATE
#  define CASE(x)	case x: if (x) printf("%c -> state %d\n",c,x); \
				else printf("Scanner starting at state 0\n");
#else
#  define CASE(x)	case x:
#endif

u_char	*yytext 	= NULL,
	*yyprev		= NULL,
	*yyrep		= NULL;
u_int	yytoklen	= 0;
int	yylineno 	= 1;
static	u_char 		*tokPtr,
			*tokStart;
static	int		state;


#ifdef DEBUG
	YYSTYPE		yylval;
#else
	extern	YYSTYPE		yylval;
#endif



typedef struct symtab_s {
	char	*name;
	int	tok;
} symtab_t;


symtab_t symtab[16][16] = {
	{ /* 0 */
		{ "select",	token(MSQL_SELECT)},
		{ "values",	token(VALUES)},
		{ "uint",	token(MSQL_UINT)},
		{ 0,		0}
	},
	{ /* 1 */
		{ "or",		token(OR)},
		{ "not",	token(NOT)},
		{ 0,		0}
	},
	{ /* 2 */
		{ "distinct",	token(DISTINCT)},
		{ 0,		0}
	},
	{ /* 3 */
		{ "and",	token(AND)},
		{ "delete",	token(MSQL_DELETE)},
		{ "update",	token(MSQL_UPDATE)},
		{ "avl",	token(AVL_INDEX)},
		{ 0,		0}
	},
	{ /* 4 */
		{ "from",	token(FROM)},
		{ "create",	token(MSQL_CREATE)},
		{ "primary",	token(PRIMARY)},
		{ "smallint",	token(MSQL_INT)},
		{ "real",	token(MSQL_REAL)},
		{ "as",		token(AS)},
		{ "min",	token(SET_FUNCT)},
		{ 0,		0}
	},
	{ /* 5 */
		{ "drop",	token(MSQL_DROP)},
		{ "insert",	token(MSQL_INSERT)},
		{ "like",	token(LIKE)},
		{ "text",	token(MSQL_TEXT)},
		{ "sum",	token(SET_FUNCT)},
		{ 0,		0}
	},
	{ /* 6 */
		{ "NULL",	token(NULLSYM)},
		{ "max",	token(SET_FUNCT)},
		{ "float",	token(MSQL_REAL)},
		{ 0,		0}
	},
	{ /* 7 */
		{ "asc",	token(ASC)},
		{ "unique",	token(UNIQUE)},
		{ "rlike",	token(RLIKE)},
		{ 0,		0}
	},
	{ /* 8 */
		{ "table",	token(TABLE)},
		{ "index",	token(INDEX)},
		{ "clike",	token(CLIKE)},
		{ "money",	token(MSQL_MONEY)},
		{ "slike",	token(SLIKE)},
		{ 0,		0}
	},
	{ /* 9 */
		{ "<=",		token(LE)},
		{ "all",	token(ALL)},
		{ "key",	token(KEY)},
		{ "count",	token(SET_FUNCT)},
		{ "sequence",	token(SEQUENCE)},
		{ 0,		0}
	},
	{ /* 10 */
		{ "<>",         token(NE)},
		{ "into",	token(INTO)},
		{ 0,		0}
	},
	{ /* 11 */
		{ "where",	token(WHERE)},
		{ ">=",		token(GE)},
		{ "by",		token(BY)},
		{ "null",	token(NULLSYM)},
		{ "int",	token(MSQL_INT)},
		{ "double",	token(MSQL_REAL)},
		{ 0,		0}
	},
	{ /* 12 */
		{ "<",		token(LT)},
		{ "order",	token(ORDER)},
		{ "set",	token(SET)},
		{ "step",	token(STEP)},
		{ 0,		0}
	},
	{ /* 13 */
		{ "=",          token(EQ)},
		{ "on",        	token(ON)},
		{ "value",      token(VALUE)},
		{ "character",  token(MSQL_CHAR)},
		{ "bigint",  	token(MSQL_INT)},
		{ 0,		0}
	},
	{ /* 14 */
		{ ">",          token(GT)},
		{ "integer",	token(MSQL_INT)},
		{ "char",	token(MSQL_CHAR)},
		{ "avg",	token(SET_FUNCT)},
		{ "date",	token(MSQL_DATE)},
		{ "float8",	token(MSQL_REAL)},
		{ 0,		0}
	},
	{ /* 15 */
		{ "desc",	token(DESC)},
		{ "limit",	token(LIMIT)},
		{ "time",	token(MSQL_TIME)},
		{ "tinyint",	token(MSQL_INT)},
		{ "int4",	token(MSQL_INT)},
		{ 0,		0}
	}
};





void msqlInitScanner(buf)
	u_char	*buf;
{
	tokStart = buf;
	state = 0;
	yylineno = 1;
}


int findKeyword(tok,len)
	char	*tok;
	int	len;
{
	REG	char	*cp1,
			*cp2,
			tmp;
	REG	symtab_t *stab;
	int	found;
        REG 	int     hash=0,
			index=0;


        cp1 = tok;
        while(*cp1 && index++ < len)
        {
                hash += *cp1++;
        }
        hash = hash & (NUM_HASH - 1);

	stab = symtab[hash];
	while(stab->name)
	{
		cp1 = stab->name;
		cp2 = tok;
		found = 1;
		while(cp2 - tok < len)
		{
			if (!(*cp1))
			{
				found = 0;
				break;
			}
			tmp = *cp2++;
			if (tmp >64 && tmp<91)
				tmp+=32;
			if (tmp != *cp1++)
			{
				found = 0;
				break;
			}
		}
		if (*cp1)
		{
			found = 0;
		}
		if (found)
		{
			yytext = (u_char *)stab->name;
			yylval = (YYSTYPE)stab->name;
			return(stab->tok);
		}
		stab++;
	}
	return(0);
}

u_char *tokenDup(tok,len)
	u_char	*tok;
	int	len;
{
	u_char	*new;

	new = (u_char *)malloc(len+1);
	bcopy(tok,new,len);
	*(new + len) = 0;
	return(new);
}


u_char *readTextLiteral(tok)
	u_char	*tok;
{
	REG 	u_char c;
	int	bail;

	bail = 0;
	while(!bail)
	{
		c = yyGet();
		switch(c)
		{
			case 0:
				return(NULL);

			case '\\':
				c = yyGet();
				if (!c)
					return(NULL);
				break;
	
			case '\'':
				bail=1;
				break;
		}
	}
	return(tokenDup(tok,yytoklen));
}


int yylex()
{
	REG	u_char	c;
	REG	u_char	t;
	int	tokval;
	static	u_char dummyBuf[2];
	static	char EOI[] = "*end of input*";


	/*
	** Handle the end of input.  We return an EOI token when we hit
	** the end and then return a 0 on the next call to yylex.  This
	** allows the parser to do the right thing with trailing garbage
	** in the expression.
	*/
	if (yyprev)
		free(yyprev);
	yyprev = yyrep;
	if (state == 1000)
	{
		yyprev = NULL;
		return(0);
	}
	state = 0;

	/*
	** Dive into the state machine
	*/
	while(1)
	{
		switch(state)
		{
			/* State 0 : Start of token */
			CASE(0)
				tokPtr = tokStart;
				yytext = NULL;
				yytoklen = 0;
				c = yyGet();
				while (iswhite(c))
				{
					if (c == '\n')
						yylineno++;
					c = yySkip();
				}
				if (c == '\'')
				{
					state = 12;
					break;
				}
				if (c == '_')
				{
					state = 18;
					break;
				}
				if (isalpha(c))
				{
					state = 1;
					break;
				}
				if (isdigit(c))
				{
					state = 5;
					break;
				}
				if (c == '.')
				{
					t = yyGet();
					if ( isdigit(t) ) 
					{
						yyUnget();
						state = 7;
						break;
					} 
					else
						yyUnget();
				}
				if (c == '-' || c == '+')
				{
					state = 9;
					break;
				}
				if (iscompop(c))
				{
					state = 10;
					break;
				}
				if (c == '#')
				{
					state = 14;
					break;
				}
				if (c == 0)
				{
					state = 1000;
					break;
				}
				state = 999;
				break;

			/* State 1 : Incomplete keyword or ident */
			CASE(1)
				c = yyGet();
				if (isalpha(c))
				{
					state = 1;
					break;
				}
				if (isdigit(c) || c == '_')
				{
					state = 3;
					break;
				}
				state = 2;
				break;


			/* State 2 : Complete keyword or ident */
			CASE(2)
				yyUnget();
				tokval = findKeyword(tokStart,yytoklen);
				if (tokval)
				{
					yyReturn(tokval);
				}
				else
				{
					yytext = tokenDup(tokStart,yytoklen);
					yylval = (YYSTYPE) yytext;
					yyReturn(token(IDENT));
				}
				break;


			/* State 3 : Incomplete ident */
			CASE(3)
				c = yyGet();
				if (isalnum(c) || c == '_')
				{
					state = 3;
					break;
				}
				state = 4;
				break;


			/* State 4: Complete ident */
			CASE(4)
				yyUnget();
				yytext = tokenDup(tokStart,yytoklen);
				yylval = (YYSTYPE) yytext;
				yyReturn(token(IDENT));


			/* State 5: Incomplete real or int number */
			CASE(5)
				c = yyGet();
				if (isdigit(c))
				{
					state = 5;
					break;
				}
				if (c == '.')
				{
					state = 7;
					break;
				}
				state = 6;
				break;


			/* State 6: Complete integer number */
			CASE(6)
				yyUnget();
				yytext = tokenDup(tokStart,yytoklen);
				yylval = (YYSTYPE) yytext;
				yyReturn(token(NUM));
				break;


			/* State 7: Incomplete real number */
			CASE(7)
				c = yyGet();

		                /* Analogy Start */
                                if(c == 'e' || c == 'E')
                                {
                                        state = 15;
                                        break;
                                }
                		/* Analogy End   */
 
				if (isdigit(c))
				{
					state = 7;
					break;
				}
				state = 8;
				break;


			/* State 8: Complete real number */
			CASE(8)
				yyUnget();
				yytext = tokenDup(tokStart,yytoklen);
				yylval = (YYSTYPE) yytext;
				yyReturn(token(REAL_NUM));


			/* State 9: Incomplete signed number */
			CASE(9)
				c = yyGet();
				if (isdigit(c))
				{
					state = 5;
					break;
				}
				if (c == '.')
				{
					state = 7;
					break;
				}
				state = 999;
				break;


			/* State 10: Incomplete comparison operator */
			CASE(10)
				c = yyGet();
				if (iscompop(c))
				{
					state = 10;
					break;
				}
				state = 11;
				break;


			/* State 11: Complete comparison operator */
			CASE(11)
				yyUnget();
				tokval = findKeyword(tokStart,yytoklen);
				if (tokval)
				{
					yyReturn(tokval);
				}
				state = 999;
				break;

	
			/* State 12: Incomplete text string */
			CASE(12)
				yytext = readTextLiteral(tokStart);
				yylval = (YYSTYPE) yytext;
				if (yytext)
				{
					state = 13;
					break;
				}
				state = 999;
				break;



			/* State 13: Complete text string */
			CASE(13)
				yyReturn(token(MSQL_TEXT));
				break;


			/* State 14: Comment */
			CASE(14)
				c = yySkip();
				if (c == '\n')
				{
					state = 0;
				}
				else
				{
					state = 14;
				}
				break;

			/* Analogy Start */
                        /* State 15: Exponent Sign in Scientific Notation */
                        CASE(15)
                                c = yyGet();
                                if(c == '-' || c == '+')
                                {
                                      state = 16;
                                      break;
                                }
                                state = 999;
                                break;

                        /* State 16: Exponent Value-first digit in Scientific 
			** Notation */
                        CASE(16)
                                c = yyGet();
                                if (isdigit(c))
                                {
                                        state = 17;
                                        break;
                                }
                                state = 999;  	/* if no digit, then token 
						** is unknown */
                                break;

                        /* State 17: Exponent Value in Scientific Notation */
                        CASE(17)
                                c = yyGet();
                                if (isdigit(c))
                                {
                                        state = 17;
                                        break;
                                }
                                state = 8;     	/* At least 1 exponent 
						** digit was required */
                                break;
			/* Analogy End */


			/* State 18 : Incomplete System Variable */
			CASE(18)
				c = yyGet();
				if (isalnum(c) || c == '_')
				{
					state = 18;
					break;
				}
				state = 19;
				break;


			/* State 19: Complete Sys Var */
			CASE(19)
				yyUnget();
				yytext = tokenDup(tokStart,yytoklen);
				yylval = (YYSTYPE) yytext;
				yyReturn(token(SYS_VAR));
				

			/* State 999 : Unknown token.  Revert to single char */
			CASE(999)
				yyRevert();
				c = yyGet();
				*dummyBuf = c;
				*(dummyBuf+1) = 0;
				yytext = dummyBuf;
				yylval = (YYSTYPE) yytext;
				yyReturn(token(yytext[0]));


			/* State 1000 : End Of Input */
			CASE(1000)
				yytext = EOI;
				yylval = (YYSTYPE) EOI;
				yyReturn(token(END_OF_INPUT));

		}
	}
}


#ifdef DEBUG

#undef malloc
#undef free

char *Malloc(size,file,line)
	int	size;
	char	*file;
	int	line;
{
	return((char *)malloc(size));
}


void Free(ptr,file,line)
	char	*ptr;
	char	*file;
	int	line;
{
	free(ptr);
}

main()
{
	char	*p,
		tmpBuf[10 * 1024];

	bzero(tmpBuf,sizeof(tmpBuf));
	read(fileno(stdin),tmpBuf,sizeof(tmpBuf));
	msqlInitScanner(tmpBuf);
	while(p = (char *) yylex())
	{
		printf("%-15.15s of length %u is \"%s\"\n", p, yytoklen,
			yytext?yytext:(u_char *)"(null)");
	}
}

#endif
