// Goldie: GOLD Engine for D
// Written in the D programming language.

module goldie.parser;

import tango.io.Stdout;
import tango.text.Util;

import semitwist.util.all;

import goldie.base;
import goldie.lang;
import goldie.lexer;

//TODO? Generate recording of steps taken
class GoldParser
{
	int[] rulesUsed;
	TokGold[] tokenStack;
	int[] stateStack;
	
	TokGold parseTree;
	GoldLexer lexer;
	GoldLanguage lang;
	
	TokGold[] process(TokGold[] tokens, GoldLanguage lang, char[] filename="", GoldLexer lexer=null)
	{
		tokenStack.length = 0;
		stateStack.length = 0;
		rulesUsed.length = 0;
		parseTree = null;
		this.lexer = lexer;
		this.lang = lang;
		
		int tokIndex=0;
		int state=lang.initialLALRState;
		bool done=false;
		
		TokGold getPendingToken()
		{
			TokGold tok;
			bool inComment=false;
			bool lineComment;
			do
			{
				tok = cast(TokGold)tokens[tokIndex];
				if(!tok)
					throw new Exception(
						"Internal Error: Non-TokGold found in token list: #{}".sformat(tokIndex)
					);
				tokIndex++;
				
				// Handle comments
				if(!inComment)
				{
					switch(tok.type)
					{
					case GoldSymbolType.CommentLine:
						inComment = true;
						lineComment = true;
						break;
					case GoldSymbolType.CommentStart:
						inComment = true;
						lineComment = false;
						break;
					default:
						break;
					}
				}
				else
				{
					if(lineComment)
					{
						auto tokContent = tok.toStringRaw();
						if(tokContent.contains('\n') || tokContent.contains('\r'))
							inComment = false;
					}
					else
					{
						if(tok.type == GoldSymbolType.CommentEnd)
							inComment = false;
					}
				}
			}
			while(inComment ||
			         (
				         tok.type != GoldSymbolType.Terminal &&
			             tok.type != GoldSymbolType.EOF
			         )
			     );
			tokIndex--;
			return tok;
		}

		void shift()
		{
			if(tokIndex >= tokens.length)
				throw new Exception("Unexpected EOF");
				
			tokenStack ~= getPendingToken();
			stateStack ~= state;
			tokIndex++;
		}
		
		GoldLALRAction chosenAction;
		int chosenActionId;
		bool actionFound;

		void lookupAction(TokGold tok)
		{
			actionFound=false;
			foreach(int id, GoldLALRAction action; lang.lalrTable[state].actions)
			{
				if(tok.id == action.symbolId)
				{
					chosenAction = action;
					chosenActionId = id;
					actionFound = true;
					break;
				}
			}
			//mixin(traceVal!("tok", "tok.id", "actionFound", "chosenActionId", "chosenAction"));
			//mixin(traceVal!("actionFound", "chosenAction.type"));
		}
		
		void displayStack()
		{
			foreach(TokGold tok; tokenStack)
			{
				Stdout.formatln("    {}({}): {,-25} '{}'",
								tok.file, tok.line,
								tok.fullName ~ ":",
								tok.toStringRaw());
			}
		}
		
		while(!done)
		{
			auto tok = getPendingToken();
			lookupAction(tok);
			
			if(actionFound)
			{
				switch(chosenAction.type)
				{
				case GoldLALRAction.Type.Shift:
					shift();
					state = chosenAction.target;
					//mixin(traceVal!("chosenActionId"));
					//Stdout.formatln(`Shift:  {,-10} new state: {}`,
					//                `"`~tok.toStringRaw()~`"`, state);
					//displayStack();
					break;

				case GoldLALRAction.Type.Reduce:
					auto rule = lang.ruleTable[chosenAction.target];
					auto reductionSize = rule.terminalIndicies.length;
					if(tokenStack.length < reductionSize)
						throw new Exception(
							"Internal Error: Tried to reduce more tokens than exist on the stack. Have {}, need {}, rule #{}"
								.sformat(tokenStack.length, reductionSize, chosenAction.target)
						);
					rulesUsed ~= chosenAction.target;
					
					// Perform Reduction
					TokGold[] reducedTokens;
					if(reductionSize > 0)
					{
						auto reductionStart = tokenStack.length - reductionSize;
						reducedTokens = tokenStack[reductionStart..$].dup;
						tokenStack = tokenStack[0..reductionStart];
						state = stateStack[reductionStart];
						stateStack = stateStack[0..reductionStart];
					}

					auto reducedToken = new TokGold(lang.symbolTable[rule.nonTerminalIndex], reducedTokens);
					tokenStack ~= reducedToken;
					stateStack ~= state;
					
					// Ensure reduced tokens symbols match the rule's right-side
					if(reducedTokens.length != reductionSize)
						throw new Exception(
							"Internal Error: Reduced an incorrent number of tokens. Expected {}, Actual {}"
								.sformat(reductionSize, reducedTokens.length)
						);
						
					foreach(int i, TokGold t; reducedTokens)
					{
						if(t.id() != rule.terminalIndicies[i])
							throw new Exception(
								`Internal Error: Reduced token #{} does not match rule. Expected "{}", Actual "{}"`
									.sformat(i, lang.symbolTable[rule.terminalIndicies[i]].name, t.name)
							);
					}
					//mixin(traceVal!("chosenActionId"));
					//Stdout.format(`Reduce: {,-15} (next: "{}") new state: {}`,
					//              `"`~reducedToken.name~`"`, tok.toStringRaw(), state);
					
					// Find and perform goto
					lookupAction(reducedToken);
					if(!actionFound)
						throw new Exception("Couldn't find appropriate goto after reduction.");
					if(chosenAction.type != GoldLALRAction.Type.Goto)
						throw new Exception(
							"Internal Error: After reduction, found action type #{} instead of goto."
								.sformat(chosenAction.type)
						);
					
					state = chosenAction.target;
					//Stdout.formatln(`, {}`, state);
					//displayStack();
					break;

				case GoldLALRAction.Type.Goto:
/*					
					state = chosenAction.target;
					Stdout.formatln(`Goto: "{}", state -> {}`,
					                tok.name, state);
*/
					throw new Exception("Internal Error: Unexpected Goto");

				case GoldLALRAction.Type.Accept:
					if(tokIndex < tokens.length-1)
						throw new Exception(
							"Internal Error: Extra tokens exist beyond end of parsing. Current token: #{} of {}"
								.sformat(tokIndex, tokens.length)
						);
					done = true;
					//Stdout.formatln(`Accept!`);
					break;

				default:
					throw new Exception(
						"Internal Error: Unknown action type (#{}) found when parsing. LALR state #{}"
							.sformat(chosenAction.type, state));
				}
			}
			else
			{
				throw new Exception(
					`Parse error: LALR state #{}, Token #{} ("{}") "{}"`
						.sformat(state, tokIndex, tok.name, tok.toStringRaw())
				);
			}
			
			if(tokenStack.length != stateStack.length)
				throw new Exception(
					"Internal Error: tokenStack[{}] and stateStack[{}] sizes out of sync."
						.sformat(tokenStack.length, stateStack.length)
				);
		}
		
		if(tokenStack.length == 1)
			parseTree = tokenStack[0];

		return tokenStack;
	}
}
