%{
#include <ctype.h>
#include "SimpleParser.hpp"

struct SqlKeyword {
    const char* m_name;
    int m_value;
    int m_state;
    static const SqlKeyword* find(Ctx& ctx, const char* name, int state);
};

%}

%option c++
%option yyclass="SimpleParser"
%option stack
%option noyywrap

space		[\040\t\n\r\f]
digit		[0-9]
letter		[A-Za-z_]
special		("$")
identifier	({letter}({letter}|{digit}|{special})*)
integer		{digit}++
decimal		(({digit}*\.{digit}+)|({digit}+\.{digit}*))
real		((({digit}*\.{digit}+)|({digit}+\.{digit}*)|({digit}+))([Ee][-+]?{digit}+))

%s	StateEval
%s	StateType
%s	StatePhys
%x	StateString
%x	StateQuoted

%%

{space}		{
    }
{identifier}	{
	const SqlKeyword* key = SqlKeyword::find(m_ctx, (char*)yytext, YYSTATE);
	if (key != 0)
	    return key->m_value;
	for (unsigned char* a = (unsigned char*)yytext; *a != 0; a++) {
	    if (islower(*a))
		*a = toupper(*a);
	}
	m_yylval.m_string = strcpy(new char[yyleng + 1], yytext);
	return T_IDENTIFIER;
    }
{integer}	{
	m_yylval.m_string = strcpy(new char[yyleng + 1], yytext);
	return T_LINTEGER;
    }
{decimal}	{
	m_yylval.m_string = strcpy(new char[yyleng + 1], yytext);
	return T_LDECIMAL;
    }
{real}		{
	m_yylval.m_string = strcpy(new char[yyleng + 1], yytext);
	return T_LREAL;
    }
"--".*		{
    }
"/*"		{
	int c = 0, d;
	while (1) {
	    d = c;
	    if ((c = yyinput()) == EOF) {
		parseError("unterminated comment");
		yyterminate();
	    }
	    if (d == '*' && c == '/')
		break;
	}
    }
<StateEval>{
"+"	return T_PLUS;
"-"	return T_MINUS;
"*"	return T_TIMES;
"/"	return T_DIVIDE;
"="	return T_EQ;
"!="	return T_NOTEQ;
"^="	return T_NOTEQ;
"<>"	return T_NOTEQ;
"<"	return T_LT;
"<="	return T_LTEQ;
">"	return T_GT;
">="	return T_GTEQ;
"?"	m_paramNumber++; return T_QUES;
}

"."	return T_PERIOD;
","	return T_COMMA;
"("	return T_PARENLEFT;
")"	return T_PARENRIGHT;
"*"	return T_ASTERISK;
"="	return T_ASSIGN;

"'"		{
	pushState(StateString);
	m_string.assign("");
    }
<StateString>{
[^']*		{
    	m_string.append(yytext);
    }
"''"		{
    	m_string.append("'");
    }
"'"		{
	m_yylval.m_string = strcpy(new char[m_string.length() + 1], m_string.c_str());
	popState();
	return T_STRING;
    }
}

\"		{
	pushState(StateQuoted);
	m_string.assign("");
    }
<StateQuoted>{
[^"]*		{
    	m_string.append(yytext);
    }
\\\"		{
    	m_string.append("\"");
    }
\"		{
	m_yylval.m_string = strcpy(new char[m_string.length() + 1], m_string.c_str());
	popState();
	return T_IDENTIFIER;
    }
}

%%

// scan states
int SimpleParser_stateEval = StateEval;
int SimpleParser_stateType = StateType;
int SimpleParser_statePhys = StatePhys;

// keep sorted

static const SqlKeyword sqlKeyword[] = {
    {	"AND",			T_AND,			-1		},
    {	"ASC",			T_ASC,			-1		},
    {	"AUTO_INCREMENT",	T_AUTO_INCREMENT,	-1		},
    {	"BIGINT",		T_BIGINT,		StateType	},
    {	"BINARY",		T_BINARY,		StateType	},
    {	"BLOB",			T_BLOB,			StateType	},
    {	"BY",			T_BY,			-1		},
    {	"CHAR",			T_CHAR,			StateType	},
    {	"CONSTRAINT",		T_CONSTRAINT,		-1		},
    {	"CREATE",		T_CREATE,		-1		},
    {	"DATETIME",		T_DATETIME,		StateType	},
    {	"DEFAULT",		T_DEFAULT,		-1		},
    {	"DELETE",		T_DELETE,		-1		},
    {	"DESC",			T_DESC,			-1		},
    {	"DISTINCT",		T_DISTINCT,		-1		},
    {	"DOUBLE",		T_DOUBLE,		StateType	},
    {	"DROP",			T_DROP,			-1		},
    {	"FLOAT",		T_FLOAT,		StateType	},
    {	"FOREIGN",		T_FOREIGN,		-1		},
    {	"FROM",			T_FROM,			-1		},
    {	"GROUP",		T_GROUP,		-1		},
    {	"HASH",			T_HASH,			-1		},
    {	"HAVING",		T_HAVING,		-1		},
    {	"IN",			T_IN,			-1		},
    {	"INDEX",		T_INDEX,		-1		},
    {	"INSERT",		T_INSERT,		-1		},
    {	"INT",			T_INT,			StateType	},
    {	"INTEGER",		T_INTEGER,		StateType	},
    {	"INTO",			T_INTO,			-1		},
    {	"IS",			T_IS,			-1		},
    {	"KEY",			T_KEY,			-1		},
    {	"LARGE",		T_LARGE,		StatePhys	},
    {	"LIKE",			T_LIKE,			-1		},
    {	"LIMIT",		T_LIMIT,		-1		},
    {	"LOGGING",		T_LOGGING,		StatePhys	},
    {	"LONGBLOB",		T_LONGBLOB,		StateType	},
    {	"MEDIUM",		T_MEDIUM,		StatePhys	},
    {	"NOLOGGING",		T_NOLOGGING,		StatePhys	},
    {	"NOT",			T_NOT,			-1		},
    {	"NULL",			T_NULL,			-1		},
    {	"OFFSET",		T_OFFSET,		-1		},
    {	"ON",			T_ON,			-1		},
    {	"OR",			T_OR,			-1		},
    {	"ORDER",		T_ORDER,		-1		},
    {	"PRECISION",		T_PRECISION,		StateType	},
    {	"PRIMARY",		T_PRIMARY,		-1		},
    {	"REAL",			T_REAL,			StateType	},
    {	"REFERENCES",		T_REFERENCES,		-1		},
    {	"ROWNUM",		T_ROWNUM,		-1		},
    {	"SELECT",		T_SELECT,		-1		},
    {	"SET",			T_SET,			-1		},
    {	"SINGLE",		T_SINGLE,		StatePhys	},
    {	"SMALL",		T_SMALL,		StatePhys	},
    {	"SMALLINT",		T_SMALLINT,		StateType	},
    {	"STORAGE",		T_STORAGE,		StatePhys	},
    {	"SYSDATE",		T_SYSDATE,		-1		},
    {	"TABLE",		T_TABLE,		-1		},
    {	"UNIQUE",		T_UNIQUE,		-1		},
    {	"UNSIGNED",		T_UNSIGNED,		-1		},
    {	"UPDATE",		T_UPDATE,		-1		},
    {	"VALUES",		T_VALUES,		-1		},
    {	"VARBINARY",		T_VARBINARY,		StateType	},
    {	"VARCHAR",		T_VARCHAR,		StateType	},
    {	"WHERE",		T_WHERE,		-1		},
    {	"WRITE",		T_WRITE,		-1		}
};

static const unsigned sqlKeywordCount = sizeof(sqlKeyword) / sizeof(sqlKeyword[0]);

const SqlKeyword*
SqlKeyword::find(Ctx& ctx, const char* name, int state)
{
    ctx_log4(("find keyword '%s' lex state = %d", name, state));
    const unsigned maxlen = 99;
    char buf[maxlen + 1];
    char* a = buf;
    const char* b = name;
    while (*b != 0) {
	if (a >= buf + maxlen)		// will not be found
	    break;
	char c = *b++;
	if ('a' <= c && c <= 'z')	// locale independent
	    c -= 'a' - 'A';
	*a++ = c;
    }
    *a = 0;
    for (unsigned i = 0; i < sqlKeywordCount; i++) {
	const SqlKeyword* key = &sqlKeyword[i];
	if (strcmp(key->m_name, buf) == 0) {
	    if (key->m_state != -1 && key->m_state != state)
		return 0;
	    return key;
	}
    }
    return 0;
}

/* vim: set filetype=lex: */
