/*
Copyright (C) 2003-2006 Andrey Nazarov

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.

*/

//
// lex.c
//

#include <setjmp.h>
#include "q_shared.h"
#include "com_public.h"
#include "q_list.h"
#include "q_lex.h"

lexContext_t	lex;

lexImm_t		lex_immediate;
lexImmType_t	lex_immediateType;

jmp_buf			lex_jmpbuf;

static char *lex_punctuation[] = {	"&&", "||", "<=", ">=","==", "!=", "+=", "-=",
									"*=", "/=", "|=", "&=", "^=", "++", "--", "(*",
									";", ",", "!", "*", "/", "(", ")", "-",
									"+", "=", "[", "]", "{", "}", ".", "<",
									">", "#", "&", "$", "|", "~", "^", "%", NULL };


void Lex_Error( const char *fmt, ... ) {
	va_list argptr;
	char buffer[MAXPRINTMSG];

	va_start( argptr, fmt );
	Q_vsnprintf( buffer, sizeof( buffer ), fmt, argptr );
	va_end( argptr );

	Com_Printf( "*************\n" );
	Com_Printf( "ERROR: line %i: %s\n", lex.currentLine, buffer );
	Com_Printf( "*************\n" );

	longjmp( lex_jmpbuf, -1 );
}

/*
===============
Lex_NewLine
===============
*/
void Lex_NewLine( void ) {
	if( lex.rejectNewLines ) {
		Lex_Error( "No newlines expected" );
	}
	lex.currentLine++;
}

/*
===============
Lex_Whitespace
===============
*/
static void Lex_Whitespace( void ) {
	while( 1 ) {
		while( *lex.data <= ' ' ) {
			if( !lex.data[0] ) {
				return;
			}
			if( *lex.data == '\n' ) {
				Lex_NewLine();
			}
			lex.data++;
		}

		if( lex.data[0] == '/' && lex.data[1] == '/' ) {
			lex.data += 2;
			while( *lex.data ) {
				if( *lex.data == '\n' ) {
					lex.data++;
					Lex_NewLine();
					break;
				}
				lex.data++;
			}
			if( !lex.data[0] ) {
				return;
			}
			continue;
		}

		if( lex.data[0] == '/' && lex.data[1] == '*' ) {
			lex.data += 2;
			while( *lex.data ) {
				if( *lex.data == '\n' ) {
					Lex_NewLine();
				} else if( lex.data[0] == '*' && lex.data[1] == '/' ) {
					lex.data += 2;
					break;
				}
				lex.data++;
			}
			if( !lex.data[0] ) {
				return;
			}
			continue;
		}

		break;
	}

}


/*
===============
Lex_String
===============
*/
static void Lex_String( void ) {
	int	c;
	int stringLength;
	int integer;

	lex.token[0] = 0;
	lex.tokenLength = 0;
	lex.tokenType = TT_IMMEDIATE;
	lex_immediateType = IT_STRING;
	lex_immediate.string[0] = 0;
	stringLength = 0;

	while( 1 ) {
		lex.data++;
		
		while( 1 ) {
			c = *lex.data++;

			if( c == '\"' ) {
				break;
			}

			switch( c ) {
			case '\0':
				Lex_Error( "EOF inside quoted string" );
				break;
			case '\n':
				Lex_Error( "Newline inside quoted string" );
				break;
			case '\\':
				c = *lex.data++;
			
				switch( c ) {
				case '\0':
					Lex_Error( "EOF inside quoted string" );
					break;
				case '\n':
					Lex_Error( "Newline inside quoted string" );
					break;
				case 'n':
					c = '\n';
					break;
				case 'r':
					c = '\r';
					break;
				case 't':
					c = '\t';
					break;
				case 'b':
					c = '\b';
					break;
				case '\"':
					c = '\"';
					break;
				case '\\':
					c = '\\';
					break;
				//case '\n':
				//	break;
				case 'x':
					integer = 0;
					while( 1 ) {
						c = *lex.data;

						if( c >= '0' && c <= '9' ) {
							c = ( c - '0' );
						} else if( c >= 'a' && c <= 'f' ) {
							c = ( c - 'a' ) + 10;
						} else if( c >= 'A' && c <= 'F' ) {
							c = ( c - 'A' ) + 10;
						} else {
							break;
						}

						integer = ( integer << 4 ) + c;

						lex.data++;
					}
					if( integer > 0xff ) {
						Lex_Error( "\\x%x does not fit in a character", integer );
					}
					c = integer;
					break;
				default:
					Lex_Error( "Unrecognized escape char" );
					break;
				}
				break;
			default:
				break;
			}

			if( stringLength == MAX_STRING_CHARS - 1 ) {
				Lex_Error( "String exceeded %i chars", MAX_STRING_CHARS );
			}
			lex_immediate.string[stringLength++] = c;
		
		}

		lex_immediate.string[stringLength] = 0;
		Lex_Whitespace();

		c = *lex.data;
		if( c != '\"' ) {
			break;
		}
	}
}

/*
===============
Lex_Punctuation
===============
*/
static void Lex_Punctuation( void ) {
	char **p;
	int len = 0;

	for( p = lex_punctuation; *p; p++ ) {
		len = strlen( *p );
		if( !strncmp( lex.data, *p, len ) ) {
			break;
		}
	}

	if( !p[0] ) {
		Lex_Error( "Unknown punctuation" );
	}

	lex.data += len;
	strcpy( lex.token, *p );
	lex.tokenLength = len;
	lex.tokenType = TT_PUNCTUATION;
}

/*
===============
Lex_Name
===============
*/
static void Lex_Name( void ) {
	int	c;

	lex.token[0] = 0;
	lex.tokenLength = 0;
	lex.tokenType = TT_NAME;
	
	c = *lex.data++;
	while( 1 ) {
		if( lex.tokenLength == MAX_TOKEN_CHARS - 1 ) {
			Lex_Error( "Name exceeded %i chars", MAX_TOKEN_CHARS );
		}
		lex.token[lex.tokenLength++] = c;

		c = *lex.data;
		if( ( c < 'A' || c > 'Z' ) && ( c < 'a' || c > 'z' ) && ( c < '0' || c > '9' ) && c != '_' ) {
			break;
		}
		
		lex.data++;
	}

	lex.token[lex.tokenLength] = 0;

}


/*
===============
Lex_VectorToken
===============
*/
#define MAX_VECTOR_TOKEN	32

char *Lex_VectorToken( void ) {
	static char token[MAX_VECTOR_TOKEN];
	int tokenLength;
	int	c;

	token[0] = 0;
	tokenLength = 0;

	Lex_Whitespace();

	c = *lex.data++;
	if( c == '\'' ) {
		return NULL;
	}
	
	while( 1 ) {

		if( ( c < '0' || c > '9' ) && c != '.' && c != '-' ) {
			Lex_Error( "Illegal char inside vector" );
		}
		if( tokenLength == MAX_VECTOR_TOKEN - 1 ) {
			Lex_Error( "Vector component exceeded %i chars", MAX_VECTOR_TOKEN );
		}
		token[tokenLength++] = c;

		c = *lex.data;
		if( c <= ' ' || c == '\'' ) {
			break;
		}

		lex.data++;
	}

	token[tokenLength] = 0;

	return token;
}


/*
===============
Lex_Vector
===============
*/
static void Lex_Vector( void ) {
	char *token;
	vec4_t	vector;
	int numComponents;

	lex.token[0] = 0;
	lex.tokenLength = 0;
	lex.tokenType = TT_IMMEDIATE;
	
	lex.data++;
	lex.rejectNewLines = qtrue;

	numComponents = 0;
	while( 1 ) {
		if( !( token = Lex_VectorToken() ) ) {
			break;
		}
		
		if( numComponents == 4 ) {
			Lex_Error( "Too many vector components" );
		}

		vector[numComponents++] = atof( token );

	}

	lex.rejectNewLines = qfalse;

	switch( numComponents ) {
	case 0:
		Lex_Error( "Empty vectors not allowed" );
		break;
	case 1:
		lex_immediate.vector[0] = vector[0];
		lex_immediate.vector[1] = 0;
		lex_immediate.vector[2] = 0;
		lex_immediate.vector[3] = 0;
		lex_immediateType = IT_VECTOR1;
		break;
	case 2:
		lex_immediate.vector[0] = vector[0];
		lex_immediate.vector[1] = vector[1];
		lex_immediate.vector[2] = 0;
		lex_immediate.vector[3] = 0;
		lex_immediateType = IT_VECTOR2;
		break;
	case 3:
		lex_immediate.vector[0] = vector[0];
		lex_immediate.vector[1] = vector[1];
		lex_immediate.vector[2] = vector[2];
		lex_immediate.vector[3] = 0;
		lex_immediateType = IT_VECTOR3;
		break;
	case 4:
		lex_immediate.vector[0] = vector[0];
		lex_immediate.vector[1] = vector[1];
		lex_immediate.vector[2] = vector[2];
		lex_immediate.vector[3] = vector[3];
		lex_immediateType = IT_VECTOR4;
		break;
	}

}

/*
===============
Lex_Number
===============
*/
static void Lex_Number( void ) {
	int	c;
	int integer;
	double value, frac;

	lex.token[0] = 0;
	lex.tokenLength = 0;
	lex.tokenType = TT_IMMEDIATE;

	integer = 0;
	value = 0;
	
	c = *lex.data++;
	if( c == '0' ) {
		c = *lex.data++;

		switch( c ) {
		case '\0':
			Lex_Error( "EOF inside number" );
			break;
		case '.':
			frac = 0.1;
			while( 1 ) {
				c = *lex.data;

				if( c < '0' || c > '9' ) {
					break;
				}

				value += frac * ( c - '0' );
				frac *= 0.1;

				lex.data++;
			}
			lex_immediateType = IT_FLOAT;
			lex_immediate.value = value;
			return;
		case 'x':
			while( 1 ) {
				c = *lex.data;

				if( c >= '0' && c <= '9' ) {
					c = ( c - '0' );
				} else if( c >= 'a' && c <= 'f' ) {
					c = ( c - 'a' ) + 10;
				} else if( c >= 'A' && c <= 'F' ) {
					c = ( c - 'A' ) + 10;
				} else {
					break;
				}

				integer = ( integer << 4 ) + c;

				lex.data++;
			}
			lex_immediateType = IT_INT;
			lex_immediate.integer = integer;
			return;
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
			while( 1 ) {
				integer = ( integer * 8 ) + ( c - '0' );

				c = *lex.data;

				if( c < '0' || c > '9' ) {
					break;
				}

				if( c == 8 || c == 9 ) {
					Lex_Error( "Bad octal digit" );
				}

				lex.data++;
			}
			lex_immediateType = IT_INT;
			lex_immediate.integer = integer;
			return;

		
		case '0':
		case '8':
		case '9':
			Lex_Error( "Bad octal digit" );
			break;
		default:
			lex.data--;
			break;
		}

		lex_immediateType = IT_INT;
		lex_immediate.integer = 0;
		return;
	}


	while( 1 ) {
		integer = ( integer * 10 ) + ( c - '0' );

		c = *lex.data;
		if( c == '.' ) {
			lex.data++;

			frac = 0.1;
			value = integer;
			while( 1 ) {
				c = *lex.data;

				if( c < '0' || c > '9' ) {
					break;
				}

				value += frac * ( c - '0' );

				frac *= 0.1;
				lex.data++;
			}

			lex_immediateType = IT_FLOAT;
			lex_immediate.value = value;

			return;
		}

		if( c < '0' || c > '9' ) {
			break;
		}

		lex.data++;
		
	}

	lex_immediateType = IT_INT;
	lex_immediate.integer = integer;

}



/*
===============
Lex_Whitespace
===============
*/
static void Lex_Eof( void ) {
	lex.token[0] = 0;
	lex.tokenLength = 0;
	lex.tokenType = TT_EOF;
}

/*
===============
Lex_Check
===============
*/
qboolean Lex_Check( const char *check ) {
	if( !strcmp( lex.token, check ) ) {
		Lex();
		return qtrue;
	}

	return qfalse;
}

/*
===============
Lex_Expect
===============
*/
void Lex_Expect( const char *expect ) {
	if( strcmp( lex.token, expect ) ) {
		Lex_Error( "Expected '%s', found '%s'", expect, lex.token );
	}

	Lex();
	
}

/*
===============
Lex_ExpectToken
===============
*/
void Lex_ExpectToken( const char *expect ) {
	if( strcmp( lex.token, expect ) ) {
		Lex_Error( "Expected '%s', found '%s'", expect, lex.token );
	}
}

/*
===============
Lex_ExpectImmediate
===============
*/
void Lex_ExpectImmediate( lexImmType_t expect ) {
	if( lex.tokenType != TT_IMMEDIATE ) {
		Lex_Error( "Expected immediate, found '%s'", lex.token );
	}
	if( ( lex_immediateType & expect ) == 0 ) {
		Lex_Error( "Expected different immediate type" );
	}
	Lex();
}

/*
===============
Lex
===============
*/
void Lex( void ) {
	int c;

	Lex_Whitespace();

	c = *lex.data;
	if( c >= 'A' && c <= 'z' ) {
		Lex_Name();
		return;
	}
	if( c >= '0' && c <= '9' ) {
		Lex_Number();
		return;
	}
	
	switch( c ) {
	case '\0':
		Lex_Eof();
		break;
	case '_':
		Lex_Name();
		break;
	case '\'':
		Lex_Vector();
		break;
	case '\"':
		Lex_String();
		break;
	default:
		Lex_Punctuation();
		break;
	}

}

void Lex_BeginParse( const char *data ) {
	memset( &lex, 0, sizeof( lex ) );
	lex.data = data;
	lex.currentLine = 1;

	memset( &lex_immediate, 0, sizeof( lex_immediate ) );
	lex_immediateType = IT_INT;

	Lex();
}




