/*
-----------------------------------------------------------------------------
This source file is part of OGRE
    (Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.stevestreeting.com/ogre/

Copyright (c) 2000-2005 The OGRE Team
Also see acknowledgements in Readme.html

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, or go to
http://www.gnu.org/copyleft/gpl.html.
-----------------------------------------------------------------------------
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Compiler2Pass.h"

Compiler2Pass::Compiler2Pass()
{
	// reserve some memory space in the containers being used
	mTokenInstructions.reserve(100);
	mConstants.reserve(80);
	// default contexts allows all contexts
	// subclass should change it to fit the language being compiled
	mActiveContexts = 0xffffffff;

}



void Compiler2Pass::InitSymbolTypeLib()
{
	uint token_ID;
	// find a default text for all Symbol Types in library

	// scan through all the rules and initialize TypeLib with index to text and index to rules for non-terminal tokens
	for(int i = 0; i < mRulePathLibCnt; i++) {
		token_ID = mRootRulePath[i].mTokenID;
		// make sure SymbolTypeLib holds valid token
		assert(mSymbolTypeLib[token_ID].mID == token_ID);
		switch(mRootRulePath[i].mOperation) {
			case otRULE:
				// if operation is a rule then update typelib
				mSymbolTypeLib[token_ID].mRuleID = i;

			case otAND:
			case otOR:
			case otOPTIONAL:
				// update text index in typelib
				if (mRootRulePath[i].mSymbol != NULL) mSymbolTypeLib[token_ID].mDefTextID = i;
				break;
		}
	}

}


bool Compiler2Pass::compile(const char* source)
{
	bool Passed = false;

	mSource = source;
	// start compiling if there is a rule base to work with
	if(mRootRulePath != NULL) {
		 Passed = doPass1();

		if(Passed) {
			Passed = doPass2();
		}
	}
	return Passed;
}


bool Compiler2Pass::doPass1()
{
	// scan through Source string and build a token list using TokenInstructions
	// this is a simple brute force lexical scanner/analyzer that also parses the formed
	// token for proper semantics and context in one pass

	mCurrentLine = 1;
	mCharPos = 0;
	// reset position in Constants container
	mConstants.clear();
	mEndOfSource = strlen(mSource);

	// start with a clean slate
	mTokenInstructions.clear();
	// tokenize and check semantics untill an error occurs or end of source is reached
	// assume RootRulePath has pointer to rules so start at index + 1 for first rule path
	// first rule token would be a rule definition so skip over it
	bool passed = processRulePath(0);
	// if a symbol in source still exists then the end of source was not reached and there was a problem some where
	if (positionToNextSymbol()) passed = false;
	return passed;

}


bool Compiler2Pass::processRulePath( uint rulepathIDX)
{
	// rule path determines what tokens and therefore what symbols are acceptable from the source
	// it is assumed that the tokens with the longest similar symbols are arranged first so
	// if a match is found it is accepted and no further searching is done

	// record position of last token in container
	// to be used as the rollback position if a valid token is not found
	uint TokenContainerOldSize = mTokenInstructions.size();
	int OldCharPos = mCharPos;
	int OldLinePos = mCurrentLine;
	uint OldConstantsSize = mConstants.size();

	// keep track of what non-terminal token activated the rule
	uint ActiveNTTRule = mRootRulePath[rulepathIDX].mTokenID;
	// start rule path at next position for definition
	rulepathIDX++;

	// assume the rule will pass
	bool Passed = true;
	bool EndFound = false;

	// keep following rulepath until the end is reached
	while (EndFound == false) {
		switch (mRootRulePath[rulepathIDX].mOperation) {

			case otAND:
				// only validate if the previous rule passed
				if(Passed) Passed = ValidateToken(rulepathIDX, ActiveNTTRule); 
				break;

			case otOR:
				// only validate if the previous rule failed
				if ( Passed == false ) {
					// clear previous tokens from entry and try again
					mTokenInstructions.resize(TokenContainerOldSize);
					Passed = ValidateToken(rulepathIDX, ActiveNTTRule);
				}
				else { // path passed up to this point therefore finished so pretend end marker found
					EndFound = true;
				}
				break;

			case otOPTIONAL:
				// if previous passed then try this rule but it does not effect succes of rule since its optional
				if(Passed) ValidateToken(rulepathIDX, ActiveNTTRule); 
				break;

			case otREPEAT:
				// repeat until no tokens of this type found 
				// at least one must be found
				if(Passed) {
					int TokensPassed = 0;
					// keep calling until failure
					while ( Passed = ValidateToken(rulepathIDX, ActiveNTTRule)) {
						// increment count for previous passed token
						TokensPassed++;
					}
					// defaults to Passed = fail
					// if at least one token found then return passed = true
					if (TokensPassed > 0) Passed = true;
				}
				break;

			case otEND:
				// end of rule found so time to return
				EndFound = true;
				if(Passed == false) {
					// the rule did not validate so get rid of tokens decoded
					// roll back the token container end position to what it was when rule started
					// this will get rid of all tokens that had been pushed on the container while
					// trying to validating this rule
					mTokenInstructions.resize(TokenContainerOldSize);
					mConstants.resize(OldConstantsSize);
					mCharPos = OldCharPos;
					mCurrentLine = OldLinePos;
				}
				break;

			default:
				// an exception should be raised since the code should never get here
				Passed = false;
				EndFound = true;
				break;

		}


		// move on to the next rule in the path
		rulepathIDX++;
	}

	return Passed;

}


bool Compiler2Pass::ValidateToken(const uint rulepathIDX, const uint activeRuleID)
{
	int tokenlength = 0;
	// assume the test is going to fail
	bool Passed = false;
	uint TokenID = mRootRulePath[rulepathIDX].mTokenID;
	// only validate token if context is correct
	if (mSymbolTypeLib[TokenID].mContextKey & mActiveContexts) {
	
		// if terminal token then compare text of symbol with what is in source
		if ( mSymbolTypeLib[TokenID].mRuleID == 0){

			if (positionToNextSymbol()) {
				// if Token is supposed to be a number then check if its a numerical constant
				if (TokenID == mValueID) {
					float constantvalue;
					if(Passed = isFloatValue(constantvalue, tokenlength)) {
						mConstants.push_back(constantvalue);
					}
					
				}
				// compare token symbol text with source text
				else Passed = isSymbol(mRootRulePath[rulepathIDX].mSymbol, tokenlength);
					
				if(Passed) {
					TokenInst newtoken;
					// push token onto end of container
					newtoken.mID = TokenID;
					newtoken.mNTTRuleID = activeRuleID;
					newtoken.mLine = mCurrentLine;
					newtoken.mPos = mCharPos;

					mTokenInstructions.push_back(newtoken);
					// update source position
					mCharPos += tokenlength;

					// allow token instruction to change the ActiveContexts
					// use token contexts pattern to clear ActiveContexts pattern bits
					mActiveContexts &= ~mSymbolTypeLib[TokenID].mContextPatternClear;
					// use token contexts pattern to set ActiveContexts pattern bits
					mActiveContexts |= mSymbolTypeLib[TokenID].mContextPatternSet;
				}
			}

		}
		// else a non terminal token was found
		else {

			// execute rule for non-terminal
			// get rule_ID for index into  rulepath to be called
			Passed = processRulePath(mSymbolTypeLib[TokenID].mRuleID);
		}
	}


	return Passed;

}


char* Compiler2Pass::getTypeDefText(const uint sid)
{
	return mRootRulePath[mSymbolTypeLib[sid].mDefTextID].mSymbol;
}


bool Compiler2Pass::isFloatValue(float& fvalue, int& charsize)
{
	// check to see if it is a numeric float value
	bool valuefound = false;

	const char* startptr = mSource + mCharPos;
	char* endptr = NULL;

	fvalue = (float)strtod(startptr, &endptr);
	// if a valid float was found then endptr will have the pointer to the first invalid character
	if(endptr) {
		if(endptr>startptr) {
			// a valid value was found so process it
			charsize = endptr - startptr;
			valuefound = true;
		}
	}

	return valuefound;
}


bool Compiler2Pass::isSymbol(const char* symbol, int& symbolsize)
{
	// compare text at source+charpos with the symbol : limit testing to symbolsize
	bool symbolfound = false;
	symbolsize = strlen(symbol);
	if(strncmp(mSource + mCharPos, symbol, symbolsize)==0) {
		symbolfound = true;
	}

	return symbolfound;
}


bool Compiler2Pass::positionToNextSymbol()
{
	bool validsymbolfound = false;
	bool endofsource = false;
	while(!validsymbolfound && !endofsource) {
		skipWhiteSpace();
		skipEOL();
		skipComments();
		// have we reached the end of the string?
		if (mCharPos == mEndOfSource) endofsource = true;
		else {
			// if ASCII > space then assume valid character is found
			if (mSource[mCharPos] > ' ') validsymbolfound = true;
		}
	}// end of while

	return validsymbolfound;
}



void Compiler2Pass::skipComments()
{
  // if current char and next are // then search for EOL
	if(mCharPos < mEndOfSource) {
		if( ((mSource[mCharPos] == '/') && (mSource[mCharPos + 1] == '/')) ||
			(mSource[mCharPos] == ';') ||
			(mSource[mCharPos] == '#') ) findEOL();
	}
}


void Compiler2Pass::findEOL()
{
	// find eol charter and move to this position
	const char* newpos = strchr(&mSource[mCharPos], '\n');
	if(newpos) {
		mCharPos += newpos - &mSource[mCharPos];
	}
	// couldn't find end of line so skip to the end
	else mCharPos = mEndOfSource - 1;

}


void Compiler2Pass::skipEOL()
{
	if ((mSource[mCharPos] == '\n') || (mSource[mCharPos] == '\r')) {
		mCurrentLine++;
		mCharPos++;
		if ((mSource[mCharPos] == '\n') || (mSource[mCharPos] == '\r')) {
			mCharPos++;
		}
	}
}


void Compiler2Pass::skipWhiteSpace()
{
	// FIX - this method kinda slow
	while((mSource[mCharPos] == ' ') || (mSource[mCharPos] == '\t')) mCharPos++; // find first non white space character
}

