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

module /+S:PACKAGE+/goldie/+E:PACKAGE+/.lang;

/+P:SET_VERSION+/

import std.stdio;
import std.string;

import semitwist.util.all;

import goldie.base;
/+S:REM+/import goldie.file;/+E:REM+/
import goldie.exception;
import goldie.lexer;
import goldie.parser;
import goldie.token;

import goldie.grmc.ast;
import goldie.langs.grm.all;

//TODO: Create a function to fix symbolLookup after changing the language details.
//      Maybe cause the accessors to set a "dirty" flag which will re-gen symbolLookup
//      next time symbolLookup is accessed.
//TODO: In SemiTwistLib, create an "accessor"/"property" mixin similar to "getter"

version(GoldieStaticLang)
{
	import goldie.lang;
	
	import /+P:PACKAGE+/DUMMY.lexer;
	import /+P:PACKAGE+/DUMMY.parser;

	// This mess is a workaround for the cyclic dependency probelms in static constructors
	private /+P:LANG_CLASSNAME+/DUMMY /+P:LANG_INSTNAME+/DUMMY_;
	private bool isInited = false;
	public @property ref /+P:LANG_CLASSNAME+/DUMMY /+P:LANG_INSTNAME+/DUMMY()
	{
		if(!isInited)
		{
			/+P:LANG_INSTNAME+/DUMMY_ = new /+P:LANG_CLASSNAME+/DUMMY();
			isInited = true;
		}
		return /+P:LANG_INSTNAME+/DUMMY_;
	}
	/+static this()
	{
		/+P:LANG_INSTNAME+/DUMMY = new /+P:LANG_CLASSNAME+/DUMMY();
	}+/
}

/+S:LANG_INHERIT+/public class Language/+E:LANG_INHERIT+/
{
	/+S:REM+/
	
	string name;
	string ver;
	string author;
	string about;
	bool   caseSensitive;
	string filename;
	
	protected Symbol[][string] symbolLookup;
	
	Symbol[]    symbolTable;
	CharSet[]   charSetTable;
	Rule[]      ruleTable;
	DFAState[]  dfaTable;
	LALRState[] lalrTable;
	
	int startSymbolIndex;
	int initialDFAState;
	int initialLALRState;
	
	int eofSymbolIndex;
	int errorSymbolIndex;
	
	/// The NFA and DFA in Graphviz DOT format.
	///
	/// These are always empty unless the Langauge was created
	/// via Goldie.compileGrammarDebug or Goldie.compileGrammarFileDebug.
	///
	/// Languages loaded from a CGT or via staticlang will never have these filled in.
	string nfaDot;
	//string dfaRawDot;
	string dfaDot;

	int nfaNumStates;
	//int dfaRawNumStates;
	//int dfaNumStates;

	protected Lexer lexer;
	protected Parser parser;

	/// Only use this if you're going to manually create a language
	/// without using Goldie's CGT-loading or staticlang.
	public this()
	{
	}
	
	package this(string cgtFilename)
	{
		auto goldFile = new CGTFile(cgtFilename);
		filename = cgtFilename;
		
		this.symbolLookup = goldFile.symbolLookup;
		mixin(initMemberX("this.%s = goldFile.%s",
			"name", "ver", "author", "about", "caseSensitive",
			"startSymbolIndex", "initialDFAState", "initialLALRState",
			"eofSymbolIndex", "errorSymbolIndex",
			"symbolTable", "charSetTable", "ruleTable",
			"dfaTable", "lalrTable"
		));
	}
	
	void save(string cgtFilename)
	{
		auto goldFile = new CGTFile();
		
		goldFile.symbolLookup = this.symbolLookup;
		mixin(initMemberX("goldFile.%s = this.%s",
			"name", "ver", "author", "about", "caseSensitive",
			"startSymbolIndex", "initialDFAState", "initialLALRState",
			"eofSymbolIndex", "errorSymbolIndex",
			"symbolTable", "charSetTable", "ruleTable",
			"dfaTable", "lalrTable"
		));
		
		goldFile.save(cgtFilename);
	}
	/+E:REM+/
	
	/+P:LANG_STATICDATA+/
	
	version(GoldieStaticLang)
	{
		private this()
		{
			super();
			/+P:LANG_INIT+/
		}

		public /+P:LEXER_CLASSNAME+/DUMMY lexCode(string source, string filename="")
		{
			return cast(/+P:LEXER_CLASSNAME+/DUMMY)lexCodeX(source, filename);
		}
		
		public /+P:PARSER_CLASSNAME+/DUMMY parseTokens(Token[] tokens, string filename="", Lexer lexerUsed=null)
		{
			return cast(/+P:PARSER_CLASSNAME+/DUMMY)parseTokensX(tokens, filename, lexerUsed);
		}

		public /+P:LEXER_CLASSNAME+/DUMMY lexFile(string filename)
		{
			return cast(/+P:LEXER_CLASSNAME+/DUMMY)lexFileX(filename);
		}

		public /+P:PARSER_CLASSNAME+/DUMMY parseFile(string filename)
		{
			return cast(/+P:PARSER_CLASSNAME+/DUMMY)parseFileX(filename);
		}

		public /+P:PARSER_CLASSNAME+/DUMMY parseCode(string source, string filename="")
		{
			return cast(/+P:PARSER_CLASSNAME+/DUMMY)parseCodeX(source, filename);
		}
	}

	public /+P:OVERRIDE+/ Lexer lexCodeX(string source, string filename="")
	{
		lexer = new /+S:LEXER_CLASSNAME+/Lexer/+E:LEXER_CLASSNAME+/();
		lexer.process(source, this, filename);
		return lexer;
	}
	
	public /+P:OVERRIDE+/ Parser parseTokensX(Token[] tokens, string filename="", Lexer lexerUsed=null)
	{
		parser = new /+S:PARSER_CLASSNAME+/Parser/+E:PARSER_CLASSNAME+/();
		parser.process(tokens, this, filename, lexerUsed);
		return parser;
	}

	/+S:REM+/
	public Lexer lexFileX(string filename)
	{
		return lexCodeX(readUTFFile!string(filename), filename);
	}

	public Parser parseFileX(string filename)
	{
		return parseTokensX(lexFileX(filename).tokens, filename, lexer);
	}

	public Parser parseCodeX(string source, string filename="")
	{
		return parseTokensX(lexCodeX(source, filename).tokens, filename, lexer);
	}

	protected Symbol[] lookupSymbol(string symName)
	{
		if(symName in symbolLookup)
			return symbolLookup[symName];
		else
			return [];
	}

	public Symbol[] symbolsByName(string name)
	{
		return lookupSymbol(name).dup;
	}
	
	public SymbolType[] symbolTypesByName(string name)
	{
		return
			lookupSymbol(name).map(
				(Symbol sym) { return sym.type; }
			);
	}
	
	public string symbolTypesStrByName(string name)
	{
		return symbolTypesByName(name).symbolTypesToString();
	}
	
	//TODO: This is just a quick-n-dirty implementation atm, probably runs slow
	public int ruleIdOf(string parentSymbol, string[] subSymbols...)
	{
		void throwNoSuchRule()
		{
			throw new Exception("Rule does not exist: %s ::= %s".format(parentSymbol, subSymbols));
		}
		
		foreach(string symName; subSymbols)
		{
			if(isSymbolNameAmbiguous(symName))
				throw new Exception(
					"Symbol '%s' is ambiguous, it could be any of the following types: %s\nGoldie does not yet support disambiguation of symbol names at runtime."
						.format(symName, symbolTypesStrByName(symName))
				);
			
			if(!isSymbolNameValid(symName) && symName !is null)
				throwNoSuchRule();
		}
		
		foreach(int ruleId, Rule rule; ruleTable)
		{
			// Wrong parentSymbol?
			if(rule.symbolIndex != lookupSymbol(parentSymbol)[0].id)
				continue;
			
			if(rule.subSymbolIndicies.length == 0)
			{
				// Can't require an explicit null for consistency with static-mode,
				// without requiring it be '[null]', because it'll get mistaken
				// for a string[] instead of a string.
				if((subSymbols.length == 1 && subSymbols[0] is null) || subSymbols.length == 0)
					return ruleId;
				else
					continue;
			}

			// Wrong subSymbols length?
			if(subSymbols.length != rule.subSymbolIndicies.length)
				continue;

			bool foundMatch = true;
			foreach(int i, int subSymbolIndex; rule.subSymbolIndicies)
			if(subSymbolIndex != lookupSymbol(subSymbols[i])[0].id)
			{
				foundMatch = false;
				break;
			}
			
			if(foundMatch)
				return ruleId;
		}

		throwNoSuchRule();
		assert(0);
	}
	
	string ruleToString(int ruleId)
	{
		auto rule = ruleTable[ruleId];
		string str = symbolTable[rule.symbolIndex].name;
		str ~= " ::=";
		foreach(subSymId; rule.subSymbolIndicies)
		{
			str ~= " ";
			str ~= symbolTable[subSymId].name;
		}
		return str;
	}
	/+E:REM+/
	
	public /+P:STATIC+/ bool /+S:TO:STATIC_IDENT+/isSymbolNameValid/+E:TO:STATIC_IDENT+/(string name)
	{
		return /+S:TO:STATIC_IDENT+/lookupSymbol/+E:TO:STATIC_IDENT+/(name).length >= 1;
	}
	
	public /+P:STATIC+/ bool /+S:TO:STATIC_IDENT+/isSymbolNameAmbiguous/+E:TO:STATIC_IDENT+/(string name)
	{
		return /+S:TO:STATIC_IDENT+/lookupSymbol/+E:TO:STATIC_IDENT+/(name).length > 1;
	}
	
	public /+P:STATIC+/ string[] /+S:TO:STATIC_IDENT+/uniqueSymbolNames/+E:TO:STATIC_IDENT+/()
	{
		version(GoldieStaticLang)
			return staticUniqueSymbolNameArray;
		else
			return symbolLookup.keys;
	}
	
	public /+P:STATIC+/ Symbol /+S:TO:STATIC_IDENT+/eofSymbol/+E:TO:STATIC_IDENT+/()
	{
		version(GoldieStaticLang)
			return staticSymbolTable[staticEofSymbolIndex];
		else
			return symbolTable[eofSymbolIndex];
	}
	
	public /+P:STATIC+/ Symbol /+S:TO:STATIC_IDENT+/errorSymbol/+E:TO:STATIC_IDENT+/()
	{
		version(GoldieStaticLang)
			return staticSymbolTable[staticErrorSymbolIndex];
		else
			return symbolTable[errorSymbolIndex];
	}
	
	version(GoldieStaticLang)
	{
		public /+P:OVERRIDE+/ bool isSymbolNameValid(string name)
		{
			return typeof(this)./+S:TO:STATIC_IDENT+/isSymbolNameValid/+E:TO:STATIC_IDENT+/(name);
		}
		
		public /+P:OVERRIDE+/ bool isSymbolNameAmbiguous(string name)
		{
			return typeof(this)./+S:TO:STATIC_IDENT+/isSymbolNameAmbiguous/+E:TO:STATIC_IDENT+/(name);
		}
		
		public /+P:OVERRIDE+/ string[] uniqueSymbolNames()
		{
			return typeof(this)./+S:TO:STATIC_IDENT+/uniqueSymbolNames/+E:TO:STATIC_IDENT+/();
		}
		
		public /+P:OVERRIDE+/ Symbol eofSymbol()
		{
			return typeof(this)./+S:TO:STATIC_IDENT+/eofSymbol/+E:TO:STATIC_IDENT+/();
		}
		
		public /+P:OVERRIDE+/ Symbol errorSymbol()
		{
			return typeof(this)./+S:TO:STATIC_IDENT+/errorSymbol/+E:TO:STATIC_IDENT+/();
		}

		protected static Symbol[] /+S:TO:STATIC_IDENT+/lookupSymbol/+E:TO:STATIC_IDENT+/(string symName)
		{
			switch(symName)
			{
			/+P:LOOKUP_SYMBOL+/
			}
		}

		protected /+P:OVERRIDE+/ Symbol[] lookupSymbol(string symName)
		{
			return typeof(this)./+S:TO:STATIC_IDENT+/lookupSymbol/+E:TO:STATIC_IDENT+/(symName);
		}
	}

	/+S:REM+/
	static Language loadCGT(string filename)
	{
		return new Language(filename);
	}

	static bool compileGrammarGoldCompatibility = false;
	
	static Language compileGrammarFile(string filename)
	{
		return compileGrammarFileImpl(filename, false);
	}

	static Language compileGrammar(string grammarDefinition, string filename="")
	{
		return compileGrammarImpl(grammarDefinition, false, filename);
	}

	static Language compileGrammarFileDebug(string filename)
	{
		return compileGrammarFileImpl(filename, true);
	}

	static Language compileGrammarDebug(string grammarDefinition, string filename="")
	{
		return compileGrammarImpl(grammarDefinition, true, filename);
	}

	private static Language compileGrammarFileImpl(string filename, bool keepDebugInfo)
	{
		return compileGrammarImpl(readUTFFile!string(filename), keepDebugInfo, filename);
	}

	private static Language compileGrammarImpl(string grammarDefinition, bool keepDebugInfo, string filename)
	{
		Parser parser;
		AST ast;
		try
		{
			parser = language_grm.parseCodeX(grammarDefinition, filename);

			scope(failure)
				if(ast) ast.writeErrorMsg();
				
			ast = new AST(parser, keepDebugInfo);
			ast.goldCompat = compileGrammarGoldCompatibility;
			ast.genLanguage(parser.parseTreeX);
		}
		catch(ParseException e)
		{
			writeln(e.msg);
			return null;
		}

		ast.writeErrorMsg();
		
		if(ast.numErrors > 0)
			return null;
		
		//mixin(traceVal!("ast.lang.author"));
		//foreach(sym; ast.lang.symbolTable)
		//	mixin(traceVal!("sym"));

		return ast.lang;
	}
	/+E:REM+/
}
