/// \file
#include "lgi/common/Lgi.h"
#include "lgi/common/Scripting.h"
#include "lgi/common/LexCpp.h"
#include "lgi/common/LgiString.h"
#include "ScriptingPriv.h"

// #define DEBUG_SCRIPT_FILE	"Mail Filters Menu.script"
#define GetTok(c)			((c) < Tokens.Length() ? Tokens[c] : NULL)
#define GetTokType(c)		((c) < Tokens.Length() ? ExpTok.Find(Tokens[c]) : TNone)
#define GV_VARIANT			GV_MAX
const char *sDebugger		= "Debugger";

int LFunctionInfo::_Infos = 0;

enum GTokenType
{
	#define _(type, str) T##type,
	#include "TokenType.h"
	#undef _
};

struct LinkFixup
{
	int Tok;
	size_t Offset;
	int Args;
	LFunctionInfo *Func;
};

struct Node
{
	typedef LArray<Node> NodeExp;
	struct VariablePart
	{
		LVariant Name;
		NodeExp Array;
		bool Call;
		LArray<NodeExp*> Args;
		
		VariablePart()
		{
			Call = false;
		}
		
		~VariablePart()
		{
			Args.DeleteObjects();
		}
	};

	// Hierarchy
	NodeExp Child;

	// One of the following are valid:
	GOperator Op = OpNull;
	// -or-
	bool Constant = false;
	int Tok = -1;
	LArray<int> Lst;
	GTokenType ConstTok = TNone;
	// -or-
	LFunc *ContextFunc = NULL;
	LArray<NodeExp> Args;
	// -or-
	LFunctionInfo *ScriptFunc = NULL;
	// -or-
	LArray<VariablePart> Variable;
	
	// Used during building
	GVarRef Reg;
	GVarRef ArrayIdx;

	void Init()
	{
		Reg.Empty();
		ArrayIdx.Empty();
	}

	void SetOp(GOperator o, int t)
	{
		Init();
		Op = o;
		Tok = t;
	}

	void SetConst(int t, GTokenType e)
	{
		Init();
		Constant = true;
		Tok = t;
		ConstTok = e;
	}

	void SetConst(LArray<int> &list_tokens, GTokenType e)
	{
		Init();
		Constant = true;
		Lst = list_tokens;
		ConstTok = e;
	}

	void SetContextFunction(LFunc *m, int tok)
	{
		Init();
		ContextFunc = m;
		Tok = tok;
	}

	void SetScriptFunction(LFunctionInfo *m, int tok)
	{
		Init();
		ScriptFunc = m;
		Tok = tok;
	}

	void SetVar(char16 *Var, int t)
	{
		Init();
		Variable[0].Name = Var;
		Tok = t;
	}

	bool IsVar() { return Variable.Length() > 0; }
	bool IsContextFunc() { return ContextFunc != 0; }
	bool IsScriptFunc() { return ScriptFunc != 0; }
	bool IsConst() { return Constant; }
};

LCompiledCode::LCompiledCode() : Globals(SCOPE_GLOBAL), Debug(0, -1)
{
	SysContext = NULL;
	UserContext = NULL;
}

LCompiledCode::LCompiledCode(LCompiledCode &copy) : Globals(SCOPE_GLOBAL), Debug(0, -1)
{
	*this = copy;
}

LCompiledCode::~LCompiledCode()
{
	for (auto e: Externs)
		e->InUse = false;
	Externs.DeleteObjects();
}

LCompiledCode &LCompiledCode::operator =(const LCompiledCode &c)
{
	Globals = c.Globals;
	ByteCode = c.ByteCode;
	Types = c.Types;
	Debug = c.Debug;
	Methods = c.Methods;
	FileName = c.FileName;
	Source = c.Source;

	return *this;
}

LFunctionInfo *LCompiledCode::GetMethod(const char *Name, bool Create)
{
	for (auto &m: Methods)
	{
		const char *Fn = m->GetName();
		if (!strcmp(Fn, Name))
		{
			return m;
		}
	}

	if (Create)
	{
		GAutoRefPtr<LFunctionInfo> n(new LFunctionInfo(Name));
		if (n)
		{
			Methods.Add(n);
			return n;
		}
	}

	return 0;
}

int LCompiledCode::ObjectToSourceAddress(size_t ObjAddr)
{
	auto Idx = Debug.Find(ObjAddr);
	return Idx < 0 ? 1 : Idx;
}

const char *LCompiledCode::AddrToSourceRef(size_t ObjAddr)
{
	static char Status[256];
	size_t Addr = ObjAddr;
	int LineNum = ObjectToSourceAddress(Addr);
	
	// Search for the start of the instruction...
	while (Addr > 0 && LineNum < 0)
		LineNum = ObjectToSourceAddress(--Addr);

	char *Dir = FileName ? strrchr(FileName, DIR_CHAR) : NULL;
	size_t PathLen = Dir ? Dir - FileName : 0;
	LString FileRef = FileName ? (PathLen > 24 ? Dir + 1 : FileName.Get()) : "(untitled)";

	if (LineNum >= 0)
		sprintf_s(	Status, sizeof(Status),
					"%s:%i",
					FileRef.Get(),
					LineNum);
	else
		sprintf_s(Status, sizeof(Status), "%s:0x%x", FileRef.Get(), (int)ObjAddr);
	
	return Status;
}

LVariant *LCompiledCode::Set(const char *Name, LVariant &v)
{
	int i = Globals.Var(Name, true);
	if (i >= 0)
	{
		Globals[i] = v;
		return &Globals[i];
	}

	return 0;
}

template <class T>
void UnEscape(T *t)
{
	T *i = t, *o = t;
	while (*i)
	{
		if (*i == '\\')
		{
			i++;
			switch (*i)
			{
				case '\\':
					*o++ = '\\'; i++;
					break;
				case 'n':
					*o++ = '\n'; i++;
					break;
				case 'r':
					*o++ = '\r'; i++;
					break;
				case 't':
					*o++ = '\t'; i++;
					break;
				case 'b':
					*o++ = '\b'; i++;
					break;
			}
		}
		else *o++ = *i++;
	}

	*o++ = 0;
}

class TokenRanges
{
	LArray<LString> FileNames;

	struct Range
	{
		int Start, End;
		ssize_t File;
		int Line;
	};

	LArray<Range> Ranges;
	char fl[MAX_PATH + 32];

public:
	TokenRanges()
	{
		Empty();
	}

	void Empty()
	{
		Ranges.Length(0);
	}

	ssize_t Length()
	{
		return Ranges.Length();
	}

	/// Gets the file/line at a given token
	const char *operator [](ssize_t Tok)
	{
		Range *r = NULL;
		
		for (unsigned i=0; i<Ranges.Length(); i++)
		{
			Range *rng = &Ranges[i];
			if (Tok >= rng->Start && Tok <= rng->End)
			{
				r = rng;
				break;
			}
		}

		if (!r)
			r = &Ranges.Last();
		
		if (r->File >= (ssize_t)FileNames.Length())
		{
			LAssert(!"Invalid file index.");
			return "#err: invalid file index";
		}
		else
		{
			sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r->File].Get(), r->Line);
		}

		return fl;
	}

	const char *GetLine(int Line)
	{
		if (Ranges.Length() > 0)
		{
			Range &r = Ranges.Last();
			if (r.File < (ssize_t)FileNames.Length())
				sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r.File].Get(), Line);
			else
				return "#err: invalid file index.";
		}
		else
		{
			sprintf_s(fl, sizeof(fl), "$unknown:%i", Line);
		}
		return fl;
	}
	
	ssize_t GetFileIndex(const char *FileName)
	{
		for (unsigned i=0; i<FileNames.Length(); i++)
		{
			if (!_stricmp(FileName, FileNames[i]))
				return i;
		}
		
		// Add new filename
		ssize_t i = FileNames.Length();
		FileNames[i] = FileName;
		return i;
	}

	/// Add a file/line reference for the next token
	void Add(int TokIndex, ssize_t FileId, int Line)
	{
		Range *r = Ranges.Length() ? &Ranges.Last() : NULL;
		if (!r || r->File != FileId || r->Line != Line)
		{
			// Start a new range...
			r = &Ranges.New();
			r->Start = r->End = TokIndex;
			r->File = FileId;
			r->Line = Line;
		}
		else
		{
			r->End = TokIndex;
		}
	}
};

/// Scripting language compiler implementation
class LCompilerPriv :
	public GCompileTools,
	public LScriptUtils
{
	LHashTbl<ConstStrKey<char>, LVariantType> Types;
	size_t JumpLoc;
	LArray<LAutoPtr<LExternFunc>> FuncMem;

public:
	LScriptContext *SysCtx;
	LScriptContext *UserCtx;
	LCompiledCode *Code;
	LStream *Log;
	LArray<char16*> Tokens;	
	TokenRanges Lines;
	char16 *Script;
	LHashTbl<StrKey<char, false>, LFunc*> Methods;
	uint64_t Regs;
	LArray<LVariables*> Scopes;
	LArray<LinkFixup> Fixups;
	LHashTbl<StrKey<char16>, char16*> Defines;
	LHashTbl<ConstStrKey<char16,false>, GTokenType> ExpTok;
	LDom *ScriptArgs;
	GVarRef ScriptArgsRef;
	bool ErrShowFirstOnly;
	LArray<LString> ErrLog;
	bool Debug;

	#ifdef _DEBUG
	LArray<LVariant> RegAllocators;
	#endif

	LCompilerPriv()
	{
		Debug = false;
		ErrShowFirstOnly = true;
		SysCtx = NULL;
		UserCtx = NULL;
		Code = 0;
		Log = 0;
		Script = 0;
		Regs = 0;
		ScriptArgs = NULL;
		ScriptArgsRef.Empty();
		
		#define LNULL NULL
		#define _(type, str) if (str) ExpTok.Add(L##str, T##type);
		#include "TokenType.h"
		#undef _
		#undef LNULL

		Types.Add("int", GV_INT32);
		Types.Add("short", GV_INT32);
		Types.Add("long", GV_INT32);
		Types.Add("int8", GV_INT32);
		Types.Add("int16", GV_INT32);
		Types.Add("int32", GV_INT32);
		Types.Add("int64", GV_INT64);
		Types.Add("uint8", GV_INT32);
		Types.Add("uint16", GV_INT32);
		Types.Add("uint32", GV_INT32);
		Types.Add("uint64", GV_INT64);
		Types.Add("bool", GV_BOOL);
		Types.Add("boolean", GV_BOOL);
		Types.Add("double", GV_DOUBLE);
		Types.Add("float", GV_DOUBLE);
		Types.Add("char", GV_STRING);
		Types.Add("LDom", GV_DOM);
		Types.Add("void", GV_VOID_PTR);
		Types.Add("LDateTime", GV_DATETIME);
		Types.Add("GHashTable", GV_HASHTABLE);
		Types.Add("GOperator", GV_OPERATOR);
		Types.Add("LView", GV_GVIEW);
		Types.Add("LMouse", GV_LMOUSE);
		Types.Add("LKey", GV_LKEY);
		Types.Add("LVariant", GV_VARIANT);
		// Types.Add("binary", GV_BINARY);
		// Types.Add("List", GV_LIST);
		// Types.Add("LDom&", GV_DOMREF);
	}

	~LCompilerPriv()
	{
		Empty();
	}

	void Empty()
	{
		SysCtx = NULL;
		UserCtx = NULL;
		DeleteObj(Code);
		Log = 0;
		Tokens.DeleteArrays();
		Lines.Empty();
		DeleteArray(Script);
		Defines.DeleteArrays();
		Methods.Empty();
	}

	/// Prints the error message
	bool OnError
	(
		/// The line number (less than 0) or Token index (greater than 0)
		int LineOrTok,
		/// The format for the string (printf)
		const char *Msg,
		/// Variable argument list
		...
	)
	{
		if (!ErrShowFirstOnly || ErrLog.Length() == 0)
		{
			char Buf[512];
			va_list Arg;
			va_start(Arg, Msg);
			#ifndef WIN32
			#define _vsnprintf vsnprintf
			#endif
			vsprintf_s(Buf, sizeof(Buf), Msg, Arg);
			Log->Print("%s - Error: %s\n", LineOrTok < 0 ? Lines.GetLine(-LineOrTok) : Lines[LineOrTok], Buf);
			va_end(Arg);
			
			ErrLog.New() = Buf;
		}

		return false; // Always return false to previous caller
	}

	void DebugInfo(int Tok)
	{
		const char *Line = Lines[Tok];
		if (Line)
		{
			const char *c = strrchr(Line, ':');
			if (c)
			{
				Code->Debug.Add(Code->ByteCode.Length(), ::atoi(c+1));
			}
		}
	}

	/// Assemble a zero argument instruction
	bool Asm0(int Tok, uint8_t Op)
	{
		DebugInfo(Tok);

		ssize_t Len = Code->ByteCode.Length();
		if (Code->ByteCode.Length(Len + 1))
		{
			GPtr p;
			p.u8 = &Code->ByteCode[Len];
			*p.u8++ = Op;
		}
		else return false;

		return true;
	}

	/// Assemble one arg instruction
	bool Asm1(int Tok, uint8_t Op, GVarRef a)
	{
		DebugInfo(Tok);

		ssize_t Len = Code->ByteCode.Length();
		if (Code->ByteCode.Length(Len + 5))
		{
			GPtr p;
			p.u8 = &Code->ByteCode[Len];
			*p.u8++ = Op;
			*p.r++ = a;
		}
		else return false;

		return true;
	}

	/// Assemble two arg instruction
	bool Asm2(int Tok, uint8_t Op, GVarRef a, GVarRef b)
	{
		DebugInfo(Tok);

		ssize_t Len = Code->ByteCode.Length();
		if (Code->ByteCode.Length(Len + 9))
		{
			GPtr p;
			p.u8 = &Code->ByteCode[Len];
			*p.u8++ = Op;
			*p.r++ = a;
			*p.r++ = b;
		}
		else return false;

		return true;
	}

	/// Assemble three arg instruction
	bool Asm3(int Tok, uint8_t Op, GVarRef a, GVarRef b, GVarRef c)
	{
		DebugInfo(Tok);

		ssize_t Len = Code->ByteCode.Length();
		if (Code->ByteCode.Length(Len + 1 + (sizeof(GVarRef) * 3) ))
		{
			GPtr p;
			p.u8 = &Code->ByteCode[Len];
			*p.u8++ = Op;
			*p.r++ = a;
			*p.r++ = b;
			*p.r++ = c;
		}
		else return false;

		return true;
	}

	/// Assemble four arg instruction
	bool Asm4(int Tok, uint8_t Op, GVarRef a, GVarRef b, GVarRef c, GVarRef d)
	{
		DebugInfo(Tok);

		ssize_t Len = Code->ByteCode.Length();
		if (Code->ByteCode.Length(Len + 1 + (sizeof(GVarRef) * 4) ))
		{
			GPtr p;
			p.u8 = &Code->ByteCode[Len];
			*p.u8++ = Op;

			if (!a.Valid()) AllocNull(a);
			*p.r++ = a;
			if (!b.Valid()) AllocNull(b);
			*p.r++ = b;
			if (!c.Valid()) AllocNull(c);
			*p.r++ = c;
			if (!d.Valid()) AllocNull(d);
			*p.r++ = d;
		}
		else return false;

		return true;
	}

	/// Assemble 'n' length arg instruction
	bool AsmN(int Tok, uint8_t Op, LArray<GVarRef> &Args)
	{
		DebugInfo(Tok);

		ssize_t Len = Code->ByteCode.Length();
		if (Code->ByteCode.Length(Len + 1 + (sizeof(GVarRef) * Args.Length()) ))
		{
			GPtr p;
			p.u8 = &Code->ByteCode[Len];
			*p.u8++ = Op;

			for (unsigned i=0; i<Args.Length(); i++)
			{
				if (!Args[i].Valid())
					AllocNull(Args[i]);
				*p.r++ = Args[i];
			}
		}
		else return false;

		return true;
	}

	bool ResolveDefine(char16 *&t, ssize_t &FileIndex, int &Line)
	{
		char16 *DefineValue;
		if (!IsAlpha(*t) || !(DefineValue = Defines.Find(t)))
			return false;

		char16 *Def = DefineValue, *f;
		while ((f = LexCpp(Def, LexStrdup)))
		{
			if (!ResolveDefine(f, FileIndex, Line))
			{
				Lines.Add((int)Tokens.Length(), FileIndex, Line);
				Tokens.Add(f);
			}
		}

		DeleteArray(t);
		return true;
	}

	struct CompilerDefineSource : public LDom
	{
		LCompilerPriv *d;
		CompilerDefineSource(LCompilerPriv *priv) : d(priv) {}
		bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override
		{
			LAutoWString wName(Utf8ToWide(Name));
			auto v = d->Defines.Find(wName);
			if (v)
			{
				// Is it a number? Cause we should really convert to an int if possible...
				#define INVALID_CONVERSION -11111
				int64 i = INVALID_CONVERSION;
				if (!Strnicmp(v, L"0x", 2))
					i = Atoi(v, 16, INVALID_CONVERSION);
				else if (IsDigit(*v) || strchr("-.", *v))
					i = Atoi(v);

				if (i != INVALID_CONVERSION)
					Value = i;
				else
					Value = v;
				return true;
			}
			return false;
		}
	};


	/// This will look at resolving the expression to something simpler before storing it...
	void SimplifyDefine(LScriptEngine &Eng, char16 *Name, LAutoWString &Value)
	{
		LString Exp = Value.Get();
		if (Exp.Find("(") >= 0) // Filter out simple expressions...
		{
			LVariant Result;
			CompilerDefineSource Src(this);
			if (Eng.EvaluateExpression(&Result, &Src, Exp))
			{
				auto Val = Result.CastString();
				if (Val)
				{
					// LgiTrace("Simplify: %S -> %s\n", Value.Get(), Val);
					Value.Reset(Utf8ToWide(Val));
				}
			}
		}
	}

	/// Convert the source from one big string into an array of tokens
	bool Lex(char *Source, const char *FileName)
	{
		char16 *w = Utf8ToWide(Source);
		if (!w)
			return OnError(0, "Couldn't convert source to wide chars.");
		
		LScriptEngine Eng(NULL, NULL, NULL);
		ssize_t FileIndex = Lines.GetFileIndex(FileName);
		int Line = 1;
		char16 *s = w, *t;
		while ((t = LexCpp(s, LexStrdup, NULL, &Line)))
		{
			if (*t == '#')
			{
				ssize_t Len;
				if (!StrnicmpW(t + 1, sInclude, Len = StrlenW(sInclude)))
				{
					LAutoWString Raw(LexCpp(s, LexStrdup));
					LAutoWString File(TrimStrW(Raw, (char16*)L"\"\'"));
					if (File)
					{
						LVariant v;
						LAutoString IncCode;
						v.OwnStr(File.Release());

						if (UserCtx)
						{
							IncCode.Reset(UserCtx->GetIncludeFile(v.Str()));
							if (IncCode)
							{
								Lex(IncCode, v.Str());
							}
							else
							{
								DeleteArray(t);
								return OnError(-Line, "Ctx failed to include '%s'", v.Str());
							}
						}
						else
						{
							if (LIsRelativePath(v.Str()))
							{
								char p[MAX_PATH];
								LMakePath(p, sizeof(p), FileName, "..");
								LMakePath(p, sizeof(p), p, v.Str());
								v = p;
							}
							
							if (LFileExists(v.Str()))
							{
								IncCode.Reset(LReadTextFile(v.Str()));
								if (IncCode)
									Lex(IncCode, v.Str());
								else
								{
									DeleteArray(t);
									return OnError(-Line, "Couldn't read '%s'", v.Str());
								}
							}
							else
							{								
								DeleteArray(t);
								return OnError(-Line, "Couldn't include '%s'", v.Str());
							}
						}
					}
					else
					{
						OnError(-Line, "No file for #include.");
					}
				}
				else if (!StrnicmpW(t + 1, sDefine, Len = StrlenW(sDefine)))
				{
					LAutoWString Name(LexCpp(s, LexStrdup));
					if (Name && IsAlpha(*Name))
					{
						char16 *Start = s;
						while (*Start && strchr(WhiteSpace, *Start))
							Start++;
						char16 *Eol = StrchrW(Start, '\n');
						if (!Eol)
							Eol = Start + StrlenW(Start);
						while (Eol > Start && strchr(WhiteSpace, Eol[-1]))
							Eol--;
						
						LAutoWString Value(NewStrW(Start, Eol - Start));
						SimplifyDefine(Eng, Name, Value);
						Defines.Add(Name, Value.Release());
						
						s = Eol;
					}
				}

				DeleteArray(t);
				continue;
			}

			if (!ResolveDefine(t, FileIndex, Line))
			{
				Lines.Add((int)Tokens.Length(), FileIndex, Line);
				Tokens.Add(t);
			}
		} // end of "while (t = LexCpp)" loop

		if (!Script)
		{
			Script = w;
		}
		else
		{
			DeleteArray(w);
		}

		return true;
	}

	/// Create a null var ref
	void AllocNull(GVarRef &r)
	{
		r.Scope = SCOPE_GLOBAL;

		if (Code->Globals.NullIndex < 0)
			Code->Globals.NullIndex = (int)Code->Globals.Length();
		
		r.Index = Code->Globals.NullIndex;
		Code->Globals[r.Index].Type = GV_NULL;
	}

	/// Allocate a variant and ref
	LVariant *PreAllocVariant(GVarRef &r)
	{
		r.Scope = SCOPE_GLOBAL;
		r.Index = (int)Code->Globals.Length();
		return &Code->Globals[r.Index];
	}

	/// Allocate a constant double
	void AllocConst(GVarRef &r, double d)
	{
		r.Scope = SCOPE_GLOBAL;

		if (Code->Globals.Length())
		{
			// Check for existing int
			LVariant *p = &Code->Globals[0];
			LVariant *e = p + Code->Globals.Length();
			while (p < e)
			{
				if (p->Type == GV_DOUBLE &&
					p->Value.Dbl == d)
				{
					r.Index = (int) (p - &Code->Globals[0]);
					return;
				}
				p++;
			}
		}

		r.Index = (int)Code->Globals.Length();
		Code->Globals[r.Index] = d;
	}

	/// Allocate a constant bool
	void AllocConst(GVarRef &r, bool b)
	{
		r.Scope = SCOPE_GLOBAL;

		if (Code->Globals.Length())
		{
			// Check for existing int
			LVariant *p = &Code->Globals[0];
			LVariant *e = p + Code->Globals.Length();
			while (p < e)
			{
				if (p->Type == GV_BOOL &&
					p->Value.Bool == b)
				{
					r.Index = (int)(p - &Code->Globals[0]);
					return;
				}
				p++;
			}
		}

		r.Index = (int)Code->Globals.Length();
		Code->Globals[r.Index] = b;
	}

	/// Allocate a constant int
	void AllocConst(GVarRef &r, int64 i)
	{
		r.Scope = SCOPE_GLOBAL;

		LVariantType Type = i <= INT_MAX && i >= INT_MIN ? GV_INT32 : GV_INT64;

		if (Code->Globals.Length())
		{
			// Check for existing int
			LVariant *p = &Code->Globals[0];
			LVariant *e = p + Code->Globals.Length();
			while (p < e)
			{
				if (p->Type == Type)
				{
					if (Type == GV_INT32 &&
						p->Value.Int == i)
					{
						r.Index = (int) (p - &Code->Globals[0]);
						return;
					}
					else if (Type == GV_INT64 &&
						p->Value.Int64 == i)
					{
						r.Index = (int) (p - &Code->Globals[0]);
						return;
					}
				}
				p++;
			}
		}

		// Allocate new global
		r.Index = (int)Code->Globals.Length();
		if (Type == GV_INT32)
			Code->Globals[r.Index] = (int32)i;
		else
			Code->Globals[r.Index] = i;
	}

	void AllocConst(GVarRef &r, int i)
	{
		AllocConst(r, (int64)i);
	}

	/// Allocate a constant string
	void AllocConst(GVarRef &r, char *s, ssize_t len = -1)
	{
		LAssert(s != 0);
		if (len < 0)
			len = strlen(s);
		
		r.Scope = SCOPE_GLOBAL;
		r.Index = (int)Code->Globals.Length();

		for (unsigned i=0; i<Code->Globals.Length(); i++)
		{
			if (Code->Globals[i].Type == GV_STRING)
			{
				char *c = Code->Globals[i].Str();
				if (*s == *c && strcmp(s, c) == 0)
				{
					r.Index = i;
					return;
				}
			}
		}

		LVariant &v = Code->Globals[r.Index];
		v.Type = GV_STRING;
		if ((v.Value.String = NewStr(s, len)))
		{
			UnEscape<char>(v.Value.String);
		}
	}

	/// Allocate a constant wide string
	void AllocConst(GVarRef &r, char16 *s, ssize_t len)
	{
		LAssert(s != 0);

		char *utf = WideToUtf8(s, len);
		if (!utf)
			utf = NewStr("");

		r.Scope = SCOPE_GLOBAL;
		r.Index = (int)Code->Globals.Length();

		for (unsigned i=0; i<Code->Globals.Length(); i++)
		{
			if (Code->Globals[i].Type == GV_STRING)
			{
				char *c = Code->Globals[i].Str();
				if (*utf == *c && strcmp(utf, c) == 0)
				{
					r.Index = i;
					DeleteArray(utf);
					return;
				}
			}
		}

		LVariant &v = Code->Globals[r.Index];
		v.Type = GV_STRING;
		if ((v.Value.String = utf))
		{
			UnEscape<char>(v.Value.String);
		}
	}

	/// Find a variable by name, creating it if needed
	GVarRef FindVariable(LVariant &Name, bool Create)
	{
		GVarRef r = {0, -1};

		// Look for existing variable...
		ssize_t i;
		for (i=Scopes.Length()-1; i>=0; i--)
		{
			r.Index = Scopes[i]->Var(Name.Str(), false);
			if (r.Index >= 0)
			{
				r.Scope = Scopes[i]->Scope;
				return r;
			}
		}

		// Create new variable on most recent scope
		i = Scopes.Length() - 1;
		r.Index = Scopes[i]->Var(Name.Str(), Create);
		if (r.Index >= 0)
		{
			r.Scope = Scopes[i]->Scope;
		}

		return r;
	}

	/// Build asm to assign a var ref
	bool AssignVarRef(Node &n, GVarRef &Value)
	{
		/*
		Examples and what their assembly should look like:

			a = Value

				Assign a <- Value

			a[b] = Value

				R0 = AsmExpression(b);
				ArraySet a[R0] <- Value

			a[b].c = Value

				R0 = AsmExpression(b);
				R0 = ArrayGet a[R0]
				DomSet(R0, "c", Null, Value)

			a[b].c[d]
				
				R0 = AsmExpression(b);
				R0 = ArrayGet a[R0];
				R1 = AsmExpression(d);
				R0 = DomGet(R0, "c", R1);
				
				ArraySet R0[R1] = Value

			a.b[c].d = Value

				R1 = AsmExpression(c);
				R0 = DomGet(a, "b", R1);
				DomSet(R1, "d", Null, Value)


			// resolve initial array
			if (parts > 1 && part[0].array)
			{
				// 
				cur = ArrayGet(part[0].var, part[0].array)
			}
			else
			{
				cur = part[0]
			}

			// dom loop over
			loop (p = 0 to parts - 2)
			{
				if (part[p+1].array)
					arr = exp(part[p+1].array)
				else
					arr = null
				
				cur = DomGet(cur, part[p+1].var, arr)
			}

			// final part
			if (part[parts-1].arr)
			{
				arr = exp(part[parts-1].arr)
				ArraySet(cur, arr)
			}
			else
			{
				DomSet(cur, part[parts-1].var, null, value)
			}		
		*/

		if (!Value.Valid())
		{
			LAssert(!"Invalid value to assign.\n");
			return false;
		}

		if (!n.IsVar())
		{
			LAssert(!"Target must be a variable.");
			return false;
		}

		// Gets the first part of the variable.
		GVarRef Cur = FindVariable(n.Variable[0].Name, true);
		if (!Cur.Valid())
			return false;
		
		if (n.Variable.Length() > 1)
		{
			// Do any initial array dereference
			if (n.Variable[0].Array.Length())
			{
				// Assemble the array index's expression into 'Idx'
				GVarRef Idx;
				Idx.Empty();
				if (!AsmExpression(&Idx, n.Variable[0].Array))
					return OnError(n.Tok, "Error creating bytecode for array index.");

				GVarRef Dst = Cur;
				if (!Dst.IsReg())
				{
					AllocReg(Dst, n.Tok, _FL);
				}
				
				// Assemble array load instruction
				Asm3(n.Tok, IArrayGet, Dst, Cur, Idx);
				Cur = Dst;

				// Cleanup
				DeallocReg(Idx);
			}

			// Do all the DOM "get" instructions
			unsigned p;
			for (p=0; p<n.Variable.Length() - 2; p++)
			{
				GVarRef Idx;
				Idx.Empty();

				// Does the next part have an array deref?
				if (n.Variable[p+1].Array.Length())
				{
					// Evaluate the array indexing expression
					if (!AsmExpression(&Idx, n.Variable[p+1].Array))
					{
						return OnError(n.Tok, "Error creating bytecode for array index.");
					}
				}

				// Alloc constant string for the DOM get
				GVarRef DomName;
				AllocConst(DomName, n.Variable[p+1].Name.Str());

				// Move temp into register?
				if (Cur.Scope != SCOPE_REGISTER)
				{
					GVarRef Dest;
					AllocReg(Dest, n.Tok, _FL);
					Asm4(n.Tok, IDomGet, Dest, Cur, DomName, Idx);
					Cur = Dest;
				}
				else
				{
					// Assemble the DOM get instruction
					Asm4(n.Tok, IDomGet, Cur, Cur, DomName, Idx);
				}

				// Cleanup
				DeallocReg(Idx);
			}

			// Do final assignment
			GVarRef Idx;
			Idx.Empty();

			ssize_t Last = n.Variable.Length() - 1;
			if (n.Variable[Last].Array.Length())
			{
				// Evaluate the array indexing expression
				if (!AsmExpression(&Idx, n.Variable[Last].Array))
				{
					return OnError(n.Tok, "Error creating bytecode for array index.");
				}
			}

			// Alloc constant string for the DOM get
			GVarRef DomName;
			AllocConst(DomName, n.Variable[p+1].Name.Str());

			// Final instruction to set DOM value
			Asm4(n.Tok, IDomSet, Cur, DomName, Idx, Value);

			// Cleanup
			DeallocReg(Idx);
			DeallocReg(Cur);
		}
		else
		{
			// Look up the array index if any
			GVarRef This = { SCOPE_LOCAL, 0 };
			if (n.Variable[0].Array.Length())
			{
				// Assemble the array index's expression into 'Idx'
				GVarRef Idx;
				Idx.Empty();
				if (!AsmExpression(&Idx, n.Variable[0].Array))
					return OnError(n.Tok, "Error creating bytecode for array index.");

				if (Cur.Scope == SCOPE_OBJECT)
				{
					GVarRef Name;
					AllocConst(Name, n.Variable[0].Name.Str());
					Asm4(n.Tok, IDomSet, This, Name, Idx, Value);
				}
				else
				{
					// Assemble array store instruction
					Asm3(n.Tok, IArraySet, Cur, Idx, Value);
				}

				// Cleanup
				DeallocReg(Idx);
			}
			else
			{
				if (Cur.Scope == SCOPE_OBJECT)
				{
					GVarRef Name, Null;
					AllocNull(Null);
					AllocConst(Name, n.Variable[0].Name.Str());
					Asm4(n.Tok, IDomSet, This, Name, Null, Value);
				}
				else
				{
					// Non array based assignment
					LAssert(Cur != Value);
					Asm2(n.Tok, IAssign, Cur, Value);
				}
			}
		}

		n.Reg = Value;
		return true;
	}
	
	bool ConvertStringToVariant(LVariant *v, char16 *t)
	{
		if (!t)
			return false;
		
		if (*t == '\"' || *t == '\'')
		{
			// string
			ssize_t Len = StrlenW(t);
			v->OwnStr(NewStrW(t + 1, Len - 2));
		}
		else if (StrchrW(t, '.'))
		{
			// double
			*v = atof(t);
		}
		else if (t[0] == '0' && tolower(t[1]) == 'x')
		{
			// hex integer
			*v = htoi(t + 2);
		}
		else
		{
			// decimal integer
			int64 i = Atoi(t);
			if (i <= INT_MAX && i >= INT_MIN)
				*v = (int32)i;
			else
				*v = i;
		}
		
		return true;
	}

	/// Convert a token stream to a var ref
	bool TokenToVarRef(Node &n, GVarRef *&LValue)
	{
		if (n.Reg.Valid())
		{
			if (LValue && n.Reg != *LValue)
			{
				// Need to assign to LValue
				LAssert(*LValue != n.Reg);
				Asm2(n.Tok, IAssign, *LValue, n.Reg);
			}
		}
		else
		{
			if (n.IsVar())
			{
				// Variable
				unsigned p = 0;
				bool ArrayDeindexed = false;
				bool HasScriptArgs = Scopes.Length() <= 1 && ScriptArgs != NULL;
				GVarRef v = FindVariable(n.Variable[p].Name, /*!HasScriptArgs ||*/ LValue != NULL);
				if (v.Index < 0)
				{
					if (HasScriptArgs)
					{
						if (AllocReg(v, n.Tok, _FL))
						{
							char16 *VarName = GetTok((unsigned)n.Tok);

							if (!ScriptArgsRef.Valid())
							{
								// Setup the global variable to address the script argument variable
								ScriptArgsRef.Scope = SCOPE_GLOBAL;
								ScriptArgsRef.Index = (int)Code->Globals.Length();
								
								LVariant &v = Code->Globals[ScriptArgsRef.Index];
								v.Type = GV_DOM;
								v.Value.Dom = ScriptArgs;								
							}
							
							GVarRef Name, Array;
							AllocConst(Name, VarName, -1);
							if (n.Variable[p].Array.Length())
							{
								if (!AsmExpression(&Array, n.Variable[p].Array))
									return OnError(n.Tok, "Can't assemble array expression.");
								ArrayDeindexed = true;
							}
							else AllocNull(Array);
							
							Asm4(n.Tok, IDomGet, v, ScriptArgsRef, Name, Array);
						}
						else return false;
					}
					else
					{
						Node::VariablePart &vp = n.Variable[p];
						return OnError(n.Tok, "Undefined variable: %s", vp.Name.Str());
					}
				}
				else
				{
					if (v.Scope == SCOPE_OBJECT)
					{
						// We have to load the variable into a register
						GVarRef Reg, ThisPtr, MemberIndex, Null;
						if (!AllocReg(Reg, n.Tok, _FL))
							return OnError(n.Tok, "Couldn't alloc register.");
						
						ThisPtr.Scope = SCOPE_LOCAL;
						ThisPtr.Index = 0;
						AllocConst(MemberIndex, v.Index);
						AllocNull(Null);
												
						Asm4(n.Tok, IDomGet, Reg, ThisPtr, MemberIndex, Null);
						v = Reg; // Object variable now in 'Reg'
					}
					
					if (n.ConstTok == TTypeId)
					{
						if (!v.IsReg())
						{
							// Because we are casting to it's DOM ptr,
							// make sure it's a register first so we don't lose the
							// actual variable.
							GVarRef reg;
							if (!AllocReg(reg, n.Tok, _FL))
								return OnError(n.Tok, "Couldn't alloc register.");
							
							LAssert(reg != v);
							Asm2(n.Tok, IAssign, reg, v);
							v = reg;
						}

						// Casting to DOM will give as access to the type info for a LCustomType.
						// This currently doesn't work with other types :(					
						Asm1(n.Tok, ICast, v);
						Code->ByteCode.Add(GV_DOM);
					}
				}
				
				n.Reg = v;
				LValue = NULL;
				
				LAssert(v.Scope != SCOPE_OBJECT);

				// Does it have an array deref?
				if (!ArrayDeindexed &&
					n.Variable[p].Array.Length())
				{
					// Evaluate the array indexing expression
					if (!AsmExpression(&n.ArrayIdx, n.Variable[p].Array))
					{
						return OnError(n.Tok, "Error creating byte code for array index.");
					}

					// Do we need to create code to load the value from the array?
					GVarRef Val;
					if (AllocReg(Val, n.Tok, _FL))
					{
						Asm3(n.Tok, IArrayGet, Val, n.Reg, n.ArrayIdx);
						n.Reg = Val;
					}
					else return OnError(n.Tok, "Error allocating register.");
				}

				// Load DOM parts...
				for (++p; p<n.Variable.Length(); p++)
				{
					GVarRef Name, Arr;
					LArray<GVarRef> Args;
					Node::VariablePart &Part = n.Variable[p];
					
					Name.Empty();
					Arr.Empty();

					char *nm = Part.Name.Str();
					AllocConst(Name, nm, strlen(nm));
					
					if (Part.Array.Length())
					{
						if (!AsmExpression(&Arr, Part.Array))
						{
							return OnError(n.Tok, "Can't assemble array expression.");
						}
					}
					else if (Part.Call)
					{
						for (unsigned i=0; i<Part.Args.Length(); i++)
						{
							GVarRef &a = Args.New();
							a.Empty();
							if (!AsmExpression(&a, *Part.Args[i]))
							{
								return OnError(n.Tok, "Can't assemble argument expression.");
							}
						}
					}
					else
					{
						AllocNull(Arr);
					}

					GVarRef Dst;
					if (n.Reg.IsReg())
					{
						Dst = n.Reg;
					}
					else
					{
						AllocReg(Dst, n.Tok, _FL);
					}
					
					if (Part.Call)
					{
						DebugInfo(n.Tok);

						LArray<GVarRef> Call;
						Call[0] = Dst;
						Call[1] = n.Reg;
						Call[2] = Name;
						
						// This must always be a global, the decompiler requires access
						// to the constant to know how many arguments to print. And it
						// can only see the globals.
						AllocConst(Call[3], (int)Args.Length());
						
						Call.Add(Args);
						AsmN(n.Tok, IDomCall, Call);
					}
					else
					{
						Asm4(n.Tok, IDomGet, Dst, n.Reg, Name, Arr);
					}
					
					n.Reg = Dst;
				}
			}
			else if (n.IsConst())
			{
				// Constant
				switch (n.ConstTok)
				{
					case TTrue:
					{
						AllocConst(n.Reg, true);
						break;
					}
					case TFalse:
					{
						AllocConst(n.Reg, false);
						break;
					}
					case TNull:
					{
						AllocNull(n.Reg);
						break;
					}
					default:
					{
						if (n.Lst.Length())
						{
							// List/array constant
							LVariant *v = PreAllocVariant(n.Reg);
							if (v)
							{
								v->SetList();
								for (unsigned i=0; i<n.Lst.Length(); i++)
								{
									char16 *t = Tokens[n.Lst[i]];
									if (!t)
										break;
									LAutoPtr<LVariant> a(new LVariant);
									if (!ConvertStringToVariant(a, t))
										break;
									v->Value.Lst->Insert(a.Release());
								}
							}
						}
						else
						{						
							char16 *t = Tokens[n.Tok];
							if (*t == '\"' || *t == '\'')
							{
								// string
								ssize_t Len = StrlenW(t);
								AllocConst(n.Reg, t + 1, Len - 2);
							}
							else if (StrchrW(t, '.'))
							{
								// double
								AllocConst(n.Reg, atof(t));
							}
							else if (t[0] == '0' && tolower(t[1]) == 'x')
							{
								// hex integer
								AllocConst(n.Reg, htoi(t + 2));
							}
							else
							{
								// decimal integer
								AllocConst(n.Reg, Atoi(t));
							}
						}
						break;
					}
				}

				LValue = NULL;
			}
			else if (n.IsContextFunc())
			{
				// Method call, create byte codes to put func value into n.Reg
				GVarRef *OutRef;
				if (LValue)
				{
					OutRef = LValue;
				}
				else
				{
					if (!AllocReg(n.Reg, n.Tok, _FL))
					{
						return OnError(n.Tok, "Can't allocate register for method return value.");
					}
					OutRef = &n.Reg;
				}
				
				DebugInfo(n.Tok);

				LArray<GVarRef> a;
				for (unsigned i=0; i<n.Args.Length(); i++)
				{
					auto &AsmResult = a.New();
					AsmResult.Empty();
					if (!AsmExpression(&AsmResult, n.Args[i]))
						return OnError(n.Tok, "Error creating bytecode for context function argument.");
				}

				ssize_t Len = Code->ByteCode.Length();
				ssize_t Size = 1 + sizeof(LFunc*) + sizeof(GVarRef) + 2 + (a.Length() * sizeof(GVarRef));
				Code->ByteCode.Length(Len + Size);
				GPtr p;
				uint8_t *Start = &Code->ByteCode[Len];
				p.u8 = Start;
				*p.u8++ = ICallMethod;
				*p.fn++ = n.ContextFunc;
				n.ContextFunc->InUse = true;
				*p.r++ = *OutRef;
				*p.u16++ = (uint16) n.Args.Length();
				for (unsigned i=0; i<n.Args.Length(); i++)
				{
					*p.r++ = a[i];
				}
				LAssert(p.u8 == Start + Size);

				// Deallocate argument registers
				for (unsigned i=0; i<a.Length(); i++)
				{
					DeallocReg(a[i]);
				}
				
				n.Reg = *OutRef;
			}
			else if (n.IsScriptFunc())
			{
				// Check for debugger break
				char *FnName = n.ScriptFunc->GetName();
				if (*FnName == 'D' &&
					!_stricmp(FnName, sDebugger))
				{
					Asm0(n.Tok, IDebug);
					return true;
				}
				
				if (n.ScriptFunc->GetParams().Length() != n.Args.Length())
				{
					if (n.ScriptFunc->ValidStartAddr())
					{
						// Function is already defined and doesn't have the right arg count...
						return OnError(	n.Tok,
										"Wrong number of parameters: %i (%s expects %i)",
										n.Args.Length(),
										FnName,
										n.ScriptFunc->GetParams().Length());
					}
					else
					{
						// Maybe it's defined later or in a separate file?
						// Or maybe it's just not defined at all?
						// Can't know until later... flag it as a unresolved external...
						//
						// Allow the assembly for the call to occur.
					}
				}
				
				// Call to a script function, create byte code to call function
				GVarRef *OutRef;
				if (LValue)
				{
					OutRef = LValue;
				}
				else
				{
					if (!AllocReg(n.Reg, n.Tok, _FL))
					{
						return OnError(n.Tok, "Can't allocate register for method return value.");
					}
					OutRef = &n.Reg;
				}
				
				DebugInfo(n.Tok);

				LArray<GVarRef> a;
				for (unsigned i=0; i<n.Args.Length(); i++)
				{
					auto &r = a.New();
					r.Empty();
					if (!AsmExpression(&r, n.Args[i]))
					{
						return OnError(n.Tok, "Error creating byte code for script function argument.");
					}
				}

				size_t Len = Code->ByteCode.Length();
				size_t Size =	1 + // instruction
								sizeof(uint32_t) + // address of function
								sizeof(uint16) + // size of frame
								sizeof(GVarRef) + // return value
								2 + // number of args
								(a.Length() * sizeof(GVarRef)); // args

				Code->ByteCode.Length(Len + Size);
				GPtr p;
				uint8_t *Start = &Code->ByteCode[Len];
				p.u8 = Start;
				*p.u8++ = ICallScript;
				
				if (n.ScriptFunc->ValidStartAddr() &&
					n.ScriptFunc->ValidFrameSize())
				{
					// Compile func address straight into code...
					*p.u32++ = n.ScriptFunc->GetStartAddr();
					*p.u16++ = n.ScriptFunc->GetFrameSize();
				}
				else
				{
					// Add link time fix up
					LinkFixup &Fix = Fixups.New();
					Fix.Tok = n.Tok;
					Fix.Args = (int)n.Args.Length();
					Fix.Offset = p.u8 - &Code->ByteCode[0];
					Fix.Func = n.ScriptFunc;
					*p.u32++ = 0;
					*p.u16++ = 0;
				}

				*p.r++ = *OutRef;
				*p.u16++ = (uint16) n.Args.Length();
				for (unsigned i=0; i<n.Args.Length(); i++)
				{
					*p.r++ = a[i];
				}
				LAssert(p.u8 == Start + Size);

				// Deallocate argument registers
				for (unsigned i=0; i<a.Length(); i++)
				{
					DeallocReg(a[i]);
				}
				
				n.Reg = *OutRef;
			}
			else return false;
			
			LAssert(n.Reg.Valid());
		}

		return true;
	}
	
	bool DoVariableNode(uint32_t &Cur, Node &Var, char16 *&t)
	{
		Var.SetVar(t, Cur);

		while ((t = GetTok(Cur+1)))
		{
			// Get the last variable part...
			Node::VariablePart &vp = Var.Variable.Last();

			// Check for array index...
			if (StricmpW(t, sStartSqBracket) == 0)
			{
				// Got array index
				Cur += 2;
				if (!Expression(Cur, vp.Array))
					return OnError(Cur, "Couldn't parse array index expression.");
				if (!(t = GetTok(Cur)) || StricmpW(t, sEndSqBracket) != 0)
				{
					return OnError(Cur, "Expecting ']', didn't get it.");
				}

				t = GetTok(Cur+1);
			}
			else if (StricmpW(t, sStartRdBracket) == 0)
			{
				Cur += 2;
				t = GetTok(Cur);

				vp.Call = true;
				if (t && StricmpW(t, sEndRdBracket))
				{
					while (true)
					{
						LAutoPtr<Node::NodeExp> e(new Node::NodeExp);
						if (!e)
							return OnError(Cur, "Mem alloc error.");

						if (!Expression(Cur, *e))
							return OnError(Cur, "Couldn't parse func call argument expression.");
						
						vp.Args.Add(e.Release());
						
						t = GetTok(Cur);
						if (!t)
							return OnError(Cur, "Unexpected end of file.");

						if (!StricmpW(t, sComma))
							Cur++;
						else if (!StricmpW(t, sEndRdBracket))
							break;
						else
							return OnError(Cur, "Expecting ',', didn't get it.");
					}
				}
				
				t = GetTok(Cur+1);
			}
			
			// Check for DOM operator...
			if (StricmpW(t, sPeriod) == 0)
			{
				// Got Dom operator
				Cur += 2;
				t = GetTok(Cur);
				if (!t)
					return OnError(Cur, "Unexpected eof.");

				Var.Variable.New().Name = t;
			}
			else break;
		}
		
		return true;
	}

	/// Parse expression into a node tree
	bool Expression(uint32_t &Cur, LArray<Node> &n, int Depth = 0)
	{
		if (Cur >= Tokens.Length())
			return OnError(Cur, "Unexpected end of file.");

		char16 *t;
		bool PrevIsOp = true;
		while ((t = Tokens[Cur]))
		{
			GTokenType Tok = ExpTok.Find(t);
			
			if (Tok == TTypeId)
			{
				char16 *v;
				if (!DoTypeId(Cur, v))
					return false;

				Node &Var = n.New();
				t = v;
				Cur--;
				DoVariableNode(Cur, Var, t);
				Var.ConstTok = TTypeId;
				Cur++;
			}
			else if (Tok == TStartRdBracket)
			{
				Cur++;

				auto &Next = n.New();
				if (!Expression(Cur, Next.Child, Depth + 1))
					return false;
				PrevIsOp = false;
				if (Next.Child.Length() > 0)
					Next.Tok = Next.Child[0].Tok;
			}
			else if (Tok == TEndRdBracket)
			{
				if (Depth > 0)
					Cur++;
				break;
			}
			else if (Tok == TComma || Tok == TSemiColon)
			{
				break;
			}
			else if (Depth == 0 && Tok == TEndSqBracket)
			{
				break;
			}
			else if (Tok == TTrue || Tok == TFalse || Tok == TNull)
			{
				n.New().SetConst(Cur++, Tok);
			}
			else
			{
				GOperator o = IsOp(t, PrevIsOp);
				if (o != OpNull)
				{
					// Operator
					PrevIsOp = 1;
					n.New().SetOp(o, Cur);
				}
				else
				{
					PrevIsOp = 0;

					LVariant m;
					m = t;
					LFunc *f = Methods.Find(m.Str());
					LFunctionInfo *sf = 0;
					char16 *Next = GetTok(Cur+1);
					bool IsFunctionCall = !StricmpW(Next, sStartRdBracket);
					if (f && IsFunctionCall)
					{
						Node &Call = n.New();
						Call.SetContextFunction(f, Cur++);

						// Now parse arguments
						
						// Get the start bracket
						if ((t = GetTok(Cur)))
						{
							if (StricmpW(t, sStartRdBracket) == 0)
								Cur++;
							else
								return OnError(Cur, "Function missing '('");
						}
						else return OnError(Cur, "No token.");
						
						// Parse the args as expressions
						while ((t = GetTok(Cur)))
						{
							GTokenType Tok = ExpTok.Find(t);
							if (Tok == TComma)
							{
								// Do nothing...
								Cur++;
							}
							else if (Tok == TEndRdBracket)
							{
								break;
							}
							else if (Tok == TSemiColon)
							{
								return OnError(Cur, "Unexpected ';'");
							}
							else if (!Expression(Cur, Call.Args.New()))
							{
								return OnError(Cur, "Can't parse function argument.");
							}
						}
					}
					else if
					(
						(sf = Code->GetMethod(m.Str(),  IsFunctionCall))
					)
					{
						Node &Call = n.New();

						Call.SetScriptFunction(sf, Cur++);

						// Now parse arguments
						
						// Get the start bracket
						if ((t = GetTok(Cur)))
						{
							if (StricmpW(t, sStartRdBracket) == 0)
								Cur++;
							else
								return OnError(Cur, "Function missing '('");
						}
						else return OnError(Cur, "No token.");
						
						// Parse the args as expressions
						while ((t = GetTok(Cur)))
						{
							if (StricmpW(t, sComma) == 0)
							{
								// Do nothing...
								Cur++;
							}
							else if (StricmpW(t, sEndRdBracket) == 0)
							{
								break;
							}
							else if (!Expression(Cur, Call.Args[Call.Args.Length()]))
							{
								return OnError(Cur, "Can't parse function argument.");
							}
						}
					}
					else if (IsAlpha(*t))
					{
						// Variable...
						Node &Var = n.New();
						if (!DoVariableNode(Cur, Var, t))
							return false;
					}
					else if (*t == '\'' ||
							*t == '\"' ||
							LIsNumber(t))
					{
						// Constant string or number
						n.New().SetConst(Cur, TLiteral);
					}
					else if (Tok == TStartCurlyBracket)
					{
						// List definition
						LArray<int> Values;
						Cur++;
						int Index = 0;
						while ((t = Tokens[Cur]))
						{
							GTokenType Tok = ExpTok.Find(t);
							if (Tok == TComma)
							{
								Index++;
							}
							else if (Tok == TEndCurlyBracket)
							{
								break;
							}
							else if (*t == '\'' ||
									*t == '\"' ||
									LIsNumber(t))
							{
								Values[Index] = Cur;
							}
							else
							{
								return OnError(Cur, "Unexpected token '%S' in list definition", t);
							}
							Cur++;
						}
						n.New().SetConst(Values, TLiteral);
					}
					else
					{
						// Unknown
						return OnError(Cur, "Unknown token '%S'", t);
					}
				}

				Cur++;
			}
		}

		return true;
	}

	/// Allocate a register (must be mirrored with DeallocReg)
	bool AllocReg(GVarRef &r, int LineOrTok, const char *file, int line)
	{
		for (int i=0; i<MAX_REGISTER; i++)
		{
			int m = 1 << i;
			if ((Regs & m) == 0)
			{
				Regs |= m;
				r.Index = i;
				r.Scope = SCOPE_REGISTER;
				
				#ifdef _DEBUG
				auto Src = LineOrTok < 0 ? Lines.GetLine(-LineOrTok) : Lines[LineOrTok];
				auto Leaf = Strrchr(Src, DIR_CHAR);

				char s[256];
				sprintf_s(s, sizeof(s), "%s:%i (%s)", file, line, Leaf?Leaf+1:Src);
				RegAllocators[i] = s;
				#endif
				return true;
			}
		}

		#ifdef _DEBUG
		if (Log)
		{
			for (unsigned n=0; n<RegAllocators.Length(); n++)
			{
				char *a = RegAllocators[n].Str();
				if (a)
				{
					Log->Print("CompileError:Register[%i] allocated by %s\n", n, a);
				}
			}
		}
		#endif

		return false;
	}

	/// Deallocate a register
	bool DeallocReg(GVarRef &r)
	{
		if (r.Scope == SCOPE_REGISTER && r.Index >= 0)
		{
			int Bit = 1 << r.Index;
			// LAssert((Bit & Regs) != 0);
			Regs &= ~Bit;

			#ifdef _DEBUG
			RegAllocators[r.Index].Empty();
			#endif
		}

		return true;
	}

	/// Count allocated registers
	int RegAllocCount()
	{
		int c = 0;

		for (int i=0; i<MAX_REGISTER; i++)
		{
			int m = 1 << i;
			if (Regs & m)
			{
				c++;
			}
		}

		return c;
	}

	char *DumpExp(LArray<Node> &n)
	{
		LStringPipe e;
		for (unsigned i=0; i<n.Length(); i++)
		{
			if (i)
				e.Print(".");
			if (n[i].Op)
			{
				e.Print("op(%i)", n[i].Op);
			}
			else if (n[i].Variable.Length())
			{
				e.Print("%s", n[i].Variable[0].Name.Str());
			}
			else if (n[i].Constant)
			{
				Node &nd = n[i];
				if (nd.Lst.Length())
				{
					e.Print("{");
					for (unsigned j=0; j<nd.Lst.Length(); j++)
					{
						char16 *t = Tokens[nd.Lst[j]];
						e.Print("%s%S", j?", ":"", t);
					}
					e.Print("}");
				}
				else
				{
					char16 *t = Tokens[nd.Tok];
					e.Print("%S", t);
				}
			}
			else if (n[i].ContextFunc)
			{
				e.Print("%s(...)", n[i].ContextFunc->Method.Get());
			}
			else if (n[i].ScriptFunc)
			{
				e.Print("%s(...)", n[i].ScriptFunc->GetName());
			}
			else
			{
				e.Print("#err#");
			}
		}

		return e.NewStr();
	}
	
	/// Creates byte code to evaluate an expression
	bool AsmExpression
	(
		/// Where the result got stored
		GVarRef *Result,
		/// The nodes to create code for
		LArray<Node> &n,
		/// The depth of recursion
		int Depth = 0
	)
	{
		// Resolve any sub-expressions and store their values
		for (unsigned i = 0; i < n.Length(); i++)
		{
			auto &k = n[i];
			if (!k.IsVar() &&
				k.Child.Length())
			{
				AllocReg(k.Reg, k.Tok, _FL);
				AsmExpression(&k.Reg, k.Child, Depth + 1);
			}
		}

		while (n.Length() > 1)
		{
			// Find which operator to handle first
			#ifdef _DEBUG
			size_t StartLength = n.Length();
			#endif
			int OpIdx = -1;
			int Prec = -1;
			int Ops = 0;
			for (unsigned i=0; i<n.Length(); i++)
			{
				if (n[i].Op)
				{
					Ops++;
					int p = GetPrecedence(n[i].Op);
					if (OpIdx < 0 || p < Prec)
					{
						Prec = p;
						OpIdx = i;
					}
				}
			}

			if (OpIdx < 0)
			{
				// Is there a negative integer in the node list?
				// The lexer can turn 'n-2' into {'n', '-2'}. At which
				// point the operator has been lost. We can recreate it here
				// though.
				for (unsigned i=1; i<n.Length(); i++)
				{
					if (n[i].IsConst())
					{
						char16 *t = GetTok((unsigned)n[i].Tok);
						if (*t == '-')
						{
							Node Addition;
							Addition.SetOp(OpPlus, n[i].Tok);
							n.AddAt(i, Addition);
							OpIdx = i;
							break;
						}
					}
				}
				
				if (OpIdx < 0)
				{	
					#if 1
					LAutoString e(DumpExp(n));
					return OnError(n[0].Tok, "No operator found in expression '%s'.", e.Get());
					#else
					GVarRef *NullRef = NULL;
					return TokenToVarRef(n[0], NullRef);
					#endif
				}
			}
			
			// Evaluate
			GOperator Op = n[OpIdx].Op;
			OperatorType Type = OpType(Op);
			if (Type == OpPrefix ||
				Type == OpPostfix)
			{
				Node &a = n[OpIdx + (Type == OpPrefix ? 1 : -1)];

				GVarRef *NullRef = NULL;
				if (TokenToVarRef(a, NullRef))
				{
					GVarRef Reg;
					if (a.Reg.Scope != SCOPE_REGISTER)
					{
						if (AllocReg(Reg, a.Tok, _FL))
						{
							LAssert(Reg != a.Reg);
							Asm2(a.Tok, IAssign, Reg, a.Reg);
							a.Reg = Reg;
						}
						else
						{
							LAssert(!"Can't alloc register");
							return OnError(a.Tok, "No operator found in expression.");
						}
					}

					Asm1(a.Tok, Op, a.Reg);

					if (Op == OpPostInc || Op == OpPostDec)
					{
						// Write value back to origin
						LVariant &VarName = a.Variable[0].Name;

						GVarRef n = FindVariable(VarName, false);
						if (n.Valid())
						{
							if (a.Child.Length())
							{
								Asm3(a.Tok, IArraySet, n, a.ArrayIdx, a.Reg);
							}
							else if (n != a.Reg)
							{
								LAssert(n != a.Reg);
								Asm2(a.Tok, IAssign, n, a.Reg);
							}
						}
						else
							return OnError(a.Tok, "Symbol '%s' not found.", VarName.Str());
					}

					n.DeleteAt(OpIdx, true);
				}
				else
				{
					LAssert(!"Can't turn tokens to refs.");
					return OnError(a.Tok, "Can't turn tokens to refs.");
				}
			}
			else if (Type == OpInfix)
			{
				if (OpIdx - 1 < 0)
				{
					return OnError(n[OpIdx].Tok, "Index %i is not a valid infix location", OpIdx);
				}

				Node &a = n[OpIdx-1];
				Node &b = n[OpIdx+1];
				
				GVarRef *LValue = NULL;
				GVarRef LRef;
				if (Op == OpAssign &&
					OpIdx == 1 &&
					a.Variable.Length() == 1)
				{
					// Can the node 'a' be considered an L value?
					LRef = FindVariable(a.Variable[0].Name, true);
					if (LRef.Valid())
						LValue = &LRef;
				}

				if (Op == OpAssign)
				{
					if (!TokenToVarRef(b, LValue))
						return OnError(b.Tok, "Can't convert right token to var ref.");
						
					if (LValue)
					{
						// We already did the assignment as part of the
						// L value optimization.
					}
					else
					{	
						// However without the L value we have to do the output
						// assignment.
						AssignVarRef(a, b.Reg);
					}
				}
				else
				{
					GVarRef *NullRef = NULL;
					LAutoPtr<GJumpZero> Jmp;
					
					if (!TokenToVarRef(a, NullRef))
						return OnError(a.Tok, "Can't convert left token to var ref.");

					if (Op == OpAnd)
					{
						// Jump over 'b' if 'a' is FALSE
						Jmp.Reset(new GJumpZero(this, a.Tok, a.Reg, false));
					}
					else if (Op == OpOr)
					{
						// Jump over 'b' if 'a' is TRUE
					}					
					
					if (!TokenToVarRef(b, LValue))
						return OnError(b.Tok, "Can't convert right token to var ref.");
					
					GVarRef Reg;
					Reg.Empty();
					if (a.Reg.Scope != SCOPE_REGISTER)
					{
						if (AllocReg(Reg, a.Tok, _FL))
						{
							LAssert(Reg != a.Reg);
							Asm2(a.Tok, IAssign, Reg, a.Reg);
							a.Reg = Reg;
						}
						else return OnError(a.Tok, "Can't alloc register, Regs=0x%x", Regs);
					}

					Asm2(a.Tok, Op, a.Reg, b.Reg);

					if (Op == OpPlusEquals ||
						Op == OpMinusEquals ||
						Op == OpMulEquals ||
						Op == OpDivEquals)
					{
						AssignVarRef(a, a.Reg);
					}
				}

				if (a.Reg != b.Reg)
					DeallocReg(b.Reg);
				n.DeleteAt(OpIdx+1, true);
				n.DeleteAt(OpIdx, true);
				// Result is in n[OpIdx-1] (ie the 'a' node)
			}
			else
			{
				LAssert(!"Not a valid type");
				return OnError(n[0].Tok, "Not a valid type.");
			}

			#ifdef _DEBUG
			if (StartLength == n.Length())
			{
				// No nodes removed... infinite loop!
				LAssert(!"No nodes removed.");
				return false;
			}
			#endif
		}

		if (n.Length() == 1)
		{
			auto &k = n[0];
			if (!k.Reg.Valid())
			{
				GVarRef *NullRef = NULL;
				if (!TokenToVarRef(k, NullRef))
				{
					return false;
				}
			}

			if (Result)
			{
				if (Result->Valid() && *Result != k.Reg)
					DeallocReg(*Result);
				*Result = k.Reg;
			}
			else
			{
				DeallocReg(k.Reg);
			}

			return true;
		}

		return false;
	}

	/// Parses and assembles an expression
	bool DoExpression(uint32_t &Cur, GVarRef *Result)
	{
		LArray<Node> n;
		if (Expression(Cur, n))
		{
			bool Status = AsmExpression(Result, n);
			return Status;
		}

		return false;
	}

	/// Converts a variable to it's type information
	bool DoTypeId(uint32_t &Cur, char16 *&Var)
	{
		if (GetTokType(Cur) != TTypeId)
			return OnError(Cur, "Expecting 'typeid'.");
		Cur++;
		if (GetTokType(Cur) != TStartRdBracket)
			return OnError(Cur, "Expecting '('.");
		Cur++;
		char16 *t = GetTok(Cur);
		if (!t || !IsAlpha(*t))
			return OnError(Cur, "Expecting variable name.");
		Cur++;
		if (GetTokType(Cur) != TEndRdBracket)
			return OnError(Cur, "Expecting ')'.");
		Cur++;

		Var = t;
		return true;
	}

	/// Parses statements
	bool DoStatements(uint32_t &Cur, bool *LastWasReturn, bool MoreThanOne = true)
	{
		while (Cur < Tokens.Length())
		{
			char16 *t = GetTok(Cur);
			if (!t)
				break;
			GTokenType Tok = ExpTok.Find(t);
			switch (Tok)
			{
				case TTypeId:
				{
					return OnError(Cur, "typeif only valid in an expression.");
				}
				case TSemiColon:
				{
					Cur++;
					break;
				}
				case TDebug:
				{
					Asm0(Cur++, IDebug);
					break;
				}
				case TBreakPoint:
				{
					Asm0(Cur++, IBreakPoint);
					break;
				}
				case TReturn:
				{
					Cur++;
					if (!DoReturn(Cur))
						return false;
					if (LastWasReturn)
						*LastWasReturn = true;
					break;
				}
				case TEndCurlyBracket:
				case TFunction:
				{
					return true;
				}
				case TIf:
				{
					if (!DoIf(Cur))
						return false;
					if (LastWasReturn)
						*LastWasReturn = false;
					break;
				}
				case TFor:
				{
					if (!DoFor(Cur))
						return false;
					if (LastWasReturn)
						*LastWasReturn = false;
					break;
				}
				case TWhile:
				{
					if (!DoWhile(Cur))
						return false;
					if (LastWasReturn)
						*LastWasReturn = false;
					break;
				}
				case TExtern:
				{
					if (!DoExtern(Cur))
						return false;
					if (LastWasReturn)
						*LastWasReturn = false;
					break;
				}
				default:
				{
					if (!DoExpression(Cur, 0))
						return false;

					GTokenType Tok = ExpTok.Find(GetTok(Cur));
					if (Tok == TSemiColon)
						Cur++;

					if (LastWasReturn)
						*LastWasReturn = false;
					break;
				}
			}

			if (!MoreThanOne)
				break;
		}

		return true;
	}

	/// Parses if/else if/else construct
	bool DoIf(uint32_t &Cur)
	{
		Cur++;
		char16 *t;
		if (GetTokType(Cur) == TStartRdBracket)
		{
			Cur++;

			// Compile and asm code to evaluate the expression
			GVarRef Result;
			int ExpressionTok = Cur;
			if (DoExpression(Cur, &Result))
			{
				t = GetTok(Cur);
				if (!t || StricmpW(t, sEndRdBracket))
					return OnError(Cur, "if missing ')'.");
				Cur++;
				t = GetTok(Cur);
				if (!t)
					return OnError(Cur, "if missing body statement.");

				// Output the jump instruction
				LAutoPtr<GJumpZero> Jmp(new GJumpZero(this, ExpressionTok, Result));
				if (!StricmpW(t, sStartCurlyBracket))
				{
					// Statement block
					Cur++;
					while ((t = GetTok(Cur)))
					{
						if (!StricmpW(t, sSemiColon))
						{
							Cur++;
						}
						else if (!StricmpW(t, sEndCurlyBracket))
						{
							Cur++;
							break;
						}
						else if (!DoStatements(Cur, NULL))
						{
							return false;
						}
					}

				}
				else
				{
					// Single statement
					if (!DoStatements(Cur, NULL, false))
						return false;
				}

				// Check for else...
				if ((t = GetTok(Cur)) && StricmpW(t, sElse) == 0)
				{
					// Add a jump for the "true" part of the expression to
					// jump over the "else" part.
					Asm0(Cur, IJump);
					size_t JOffset = Code->ByteCode.Length();
					if (Code->ByteCode.Length(JOffset + 4))
					{
						// Initialize the ptr to zero
						int32 *Ptr = (int32*)&Code->ByteCode[JOffset];
						*Ptr = 0;

						// Resolve jz to here...
						Jmp.Reset();

						// Compile the else block
						Cur++;
						if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0)
						{
							// 'Else' Statement block
							Cur++;
							while ((t = GetTok(Cur)))
							{
								if (!StricmpW(t, sSemiColon))
								{
									Cur++;
								}
								else if (!StricmpW(t, sEndCurlyBracket))
								{
									Cur++;
									break;
								}
								else if (!DoStatements(Cur, NULL))
								{
									return false;
								}
							}
						}
						else
						{
							// Single statement
							if (!DoStatements(Cur, NULL, false))
								return false;
						}

						// Resolve the "JOffset" jump that takes execution of
						// the 'true' part over the 'else' part
						Ptr = (int32*)&Code->ByteCode[JOffset];
						*Ptr = (int32) (Code->ByteCode.Length() - JOffset - sizeof(int32));
						if (*Ptr == 0)
						{
							// Empty statement... so delete the Jump instruction
							Code->ByteCode.Length(JOffset - 1);
						}
					}
					else OnError(Cur, "Mem alloc");
				}
				
				return true;
			}
		}
		else return OnError(Cur, "if missing '('");

		return false;
	}

	LArray<uint8_t> &GetByteCode()
	{
		return Code->ByteCode;
	}

	class GJumpZero
	{
		LCompilerPriv *Comp;
		int JzOffset;

	public:
		GJumpZero(LCompilerPriv *d, int Tok, GVarRef &r, bool DeallocReg = true)
		{
			// Create jump instruction to jump over the body if the expression evaluates to false
			Comp = d;
			Comp->Asm1(Tok, IJumpZero, r);
			if (DeallocReg)
				Comp->DeallocReg(r);
			JzOffset = (int) Comp->GetByteCode().Length();
			Comp->GetByteCode().Length(JzOffset + sizeof(int32));
		}

		~GJumpZero()
		{
			// Resolve jump
			int32 *Ptr = (int32*) &Comp->GetByteCode()[JzOffset];
			*Ptr = (int32) (Comp->GetByteCode().Length() - (JzOffset + sizeof(int32)));
		}
	};

	/// Parses while construct
	bool DoWhile(uint32_t &Cur)
	{
		Cur++;
		char16 *t = GetTok(Cur);
		if (!t || StricmpW(t, sStartRdBracket))
			return OnError(Cur, "Expecting '(' after 'while'");
		Cur++;

		// Store start of condition code
		size_t ConditionStart = Code->ByteCode.Length();

		// Compile condition evalulation
		GVarRef r;
		if (!DoExpression(Cur, &r))
			return false;

		// Create jump instruction to jump over the body if the expression evaluates to false
		{
			GJumpZero Jump(this, Cur, r);

			if (!(t = GetTok(Cur)) || StricmpW(t, sEndRdBracket))
				return OnError(Cur, "Expecting ')'");
			Cur++;

			// Compile the body of the loop
			if (!(t = GetTok(Cur)))
				return OnError(Cur, "Unexpected eof.");
			Cur++;

			if (StricmpW(t, sStartCurlyBracket) == 0)
			{
				// Block
				while ((t = GetTok(Cur)))
				{
					if (!StricmpW(t, sSemiColon))
					{
						Cur++;
					}
					else if (!StricmpW(t, sEndCurlyBracket))
					{
						Cur++;
						break;
					}
					else if (!DoStatements(Cur, NULL))
					{
						return false;
					}
				}
			}
			else
			{
				// Single statement
				DoStatements(Cur, NULL, false);
			}

			// Jump to condition evaluation at 'ConditionStart'
			Asm0(Cur, IJump);
			size_t JOffset = Code->ByteCode.Length();
			Code->ByteCode.Length(JOffset + sizeof(int32));
			int32 *Ptr = (int32*) &Code->ByteCode[JOffset];
			*Ptr = (int32) (ConditionStart - Code->ByteCode.Length());
		}
		
		return true;
	}

	/// Parses for construct
	bool DoFor(uint32_t &Cur)
	{
		/*
			For loop asm structure:

				+---------------------------+
				| Pre-condition expression  |
				+---------------------------+
				| Eval loop contdition exp. |<--+
				|                           |   |
				| JUMP ZERO (jumpz)         |---+-+
				+---------------------------+   | |
				| Body of loop...           |   | |
				|                           |   | |
				|                           |   | |
				|                           |   | |
				|                           |   | |
				|                           |   | |
				+---------------------------+   | |
				| Post-cond. expression     |   | |
				|                           |   | |
				| JUMP                      |---+ |
				+---------------------------+     |
				| Following code...         |<----+
				.                           .
		
		*/

		Cur++;
		char16 *t = GetTok(Cur);
		if (!t || StricmpW(t, sStartRdBracket))
			return OnError(Cur, "Expecting '(' after 'for'");
		Cur++;
		
		// Compile initial statement
		GVarRef r;
		t = GetTok(Cur);
		if (!t)
			return false;
		if (StricmpW(t, sSemiColon) && !DoExpression(Cur, 0))
			return false;
		t = GetTok(Cur);

		// Look for ';'
		if (!t || StricmpW(t, sSemiColon))
			return OnError(Cur, "Expecting ';'");
		Cur++;
		
		// Store start of condition code
		size_t ConditionStart = Code->ByteCode.Length();
		
		// Compile condition evalulation
		if (!DoExpression(Cur, &r))
			return false;

		{
			GJumpZero Jmp(this, Cur, r);
			t = GetTok(Cur);

			// Look for ';'
			if (!t || StricmpW(t, sSemiColon))
				return OnError(Cur, "Expecting ';'");
			Cur++;

			// Compile the post expression code
			size_t PostCodeStart = Code->ByteCode.Length();
			t = GetTok(Cur);
			if (StricmpW(t, sEndRdBracket) && !DoExpression(Cur, 0))
				return false;

			// Store post expression code in temp variable
			LArray<uint8_t> PostCode;
			size_t PostCodeLen = Code->ByteCode.Length() - PostCodeStart;
			if (PostCodeLen)
			{
				PostCode.Length(PostCodeLen);
				memcpy(&PostCode[0], &Code->ByteCode[PostCodeStart], PostCodeLen);
				
				// Delete the post expression off the byte code, we are putting it after the block code
				Code->ByteCode.Length(PostCodeStart);
			}

			// Look for ')'
			t = GetTok(Cur);
			if (!t || StricmpW(t, sEndRdBracket))
				return OnError(Cur, "Expecting ')'");
			Cur++;
			
			// Compile body of loop
			if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0)
			{
				Cur++;
				while ((t = GetTok(Cur)))
				{
					if (!StricmpW(t, sSemiColon))
					{
						Cur++;
					}
					else if (!StricmpW(t, sEndCurlyBracket))
					{
						Cur++;
						break;
					}
					else if (!DoStatements(Cur, NULL))
					{
						return false;
					}
				}
			}
			
			// Add post expression code
			if (PostCodeLen)
			{
				size_t Len = Code->ByteCode.Length();
				Code->ByteCode.Length(Len + PostCodeLen);
				memcpy(&Code->ByteCode[Len], &PostCode[0], PostCodeLen);
			}
			
			// Jump to condition evaluation at 'ConditionStart'
			Asm0(Cur, IJump);
			size_t JOffset = Code->ByteCode.Length();
			Code->ByteCode.Length(JOffset + sizeof(int32));
			int32 *Ptr = (int32*) &Code->ByteCode[JOffset];
			*Ptr = (int32) (ConditionStart - Code->ByteCode.Length());
		}
		
		return true;
	}

	/// Compiles return construct
	bool DoReturn(uint32_t &Cur)
	{
		GVarRef ReturnValue;
		char16 *t;
		int StartTok = Cur;
		ReturnValue.Empty();

		LArray<Node> Exp;
		if (!Expression(Cur, Exp))
		{
			return OnError(Cur, "Failed to compile return expression.");
		}
		else if (!AsmExpression(&ReturnValue, Exp))
		{
			return OnError(Cur, "Failed to assemble return expression.");
		}

		if (!(t = GetTok(Cur)) ||
			StricmpW(t, sSemiColon))
		{
			return OnError(Cur, "Expecting ';' after return expression.");
		}

		Asm1(StartTok, IRet, ReturnValue);
		DeallocReg(ReturnValue);
		Cur++;
		return true;
	}

	// Compile a method definition
	bool DoFunction
	(
		/// Cursor token index
		uint32_t &Cur,
		/// [Optional] Current struct / class.
		/// If not NULL this is a method definition for said class.
		LCustomType *Struct = NULL
	)
	{
		bool Status = false;
		bool LastInstIsReturn = false;

		if (!JumpLoc)
		{
			size_t Len = Code->ByteCode.Length();
			if (Code->ByteCode.Length(Len + 5))
			{
				GPtr p;
				p.u8 = &Code->ByteCode[Len];
				*p.u8++ = IJump;
				*p.i32++ = 0;
				JumpLoc = Len + 1;
			}
			else OnError(Cur, "Mem alloc failed.");
		}

		LString FunctionName;
		LCustomType::Method *StructMethod = NULL; // Member function of script struct/class
		LFunctionInfo *ScriptMethod = NULL; // Standalone scripting function
		
		char16 *Name = GetTok(Cur);
		if (Name)
		{
			Cur++;
			char16 *t = GetTok(Cur);
			if (!t || StricmpW(t, sStartRdBracket))
				return OnError(Cur, "Expecting '(' in function.");

			FunctionName = Name;
		
			// Parse parameters
			LArray<LString> Params;
			Cur++;
			while ((t = GetTok(Cur)))
			{
				if (IsAlpha(*t))
				{
					Params.New() = t;
					Cur++;
					if (!(t = GetTok(Cur)))
						goto UnexpectedFuncEof;
				}

				if (!StricmpW(t, sComma))
					;
				else if (!StricmpW(t, sEndRdBracket))
				{
					Cur++;
					break;
				}
				else
					goto UnexpectedFuncEof;
				
				Cur++;
			}

			if (Struct)
			{
				StructMethod = Struct->DefineMethod(FunctionName, Params, Code->ByteCode.Length());
			}
			else
			{
				ScriptMethod = Code->GetMethod(FunctionName, true);
				if (!ScriptMethod)
					return OnError(Cur, "Can't define method '%s'.", FunctionName.Get());

				ScriptMethod->GetParams() = Params;
				ScriptMethod->SetStartAddr((int32_t)Code->ByteCode.Length());
			}

			// Parse start of body
			if (!(t = GetTok(Cur)))
				goto UnexpectedFuncEof;
			if (StricmpW(t, sStartCurlyBracket))
				return OnError(Cur, "Expecting '{'.");

			// Setup new scope(s)
			LAutoPtr<LVariables> ObjectScope;
			if (Struct)
			{
				// The object scope has to be first so that local variables take
				// precedence over object member variables.
				if (ObjectScope.Reset(new LVariables(Struct)))
					Scopes.Add(ObjectScope);
			}

			LVariables LocalScope(SCOPE_LOCAL);
			if (Struct)
				LocalScope.Var("This", true);
			for (unsigned i=0; i<Params.Length(); i++)
			{
				LocalScope.Var(Params[i], true);
			}
			Scopes.Add(&LocalScope);

			// Parse contents of function body
			Cur++;
			while ((t = GetTok(Cur)))
			{
				// End of the function?
				if (!StricmpW(t, sEndCurlyBracket))
				{
					if (StructMethod)
						StructMethod->FrameSize = (uint16)LocalScope.Length();
					else if (ScriptMethod)
						ScriptMethod->SetFrameSize(LocalScope.Length());
					else
						LAssert(!"What are you defining exactly?");
					
					Status = true;
					Cur++;

					// LgiTrace("Added method %s @ %i, stack=%i, args=%i\n", f->Name.Str(), f->StartAddr, f->FrameSize, f->Params.Length());
					break;
				}
				
				// Parse statement in the body
				if (!DoStatements(Cur, &LastInstIsReturn))
					return OnError(Cur, "Can't compile function body.");
			}
			
			// Remove any scopes we created
			Scopes.PopLast();
			if (Struct)
				Scopes.PopLast();
		}

		if (!LastInstIsReturn)
		{
			GVarRef RetVal;
			AllocNull(RetVal);
			Asm1(Cur, IRet, RetVal);
		}
		
		return Status;

	UnexpectedFuncEof:
		return OnError(Cur, "Unexpected EOF in function.");
	}
	
	LExternFunc::ExternType ParseExternType(LArray<const char16*> &Strs)
	{
		LExternFunc::ExternType Type;
		
		Type.Out = false;
		Type.Ptr = 0;
		Type.ArrayLen = 1;
		Type.Base = GV_NULL;
		Type.Unsigned = false;
		
		bool InArray = false;
		for (unsigned i=0; i<Strs.Length(); i++)
		{
			const char16 *t = Strs[i];
			if (t[0] == '*' && t[1] == 0)
				Type.Ptr++;
			else if (!StricmpW(t, sOutParam))
				Type.Out = true;
			else if (!StricmpW(t, sStartSqBracket))
				InArray = true;
			else if (!StricmpW(t, sEndSqBracket))
				InArray = false;
			else if (IsDigit(*t))
			{
				if (InArray)
					Type.ArrayLen = AtoiW(t);
			}
			else
			{
				LAutoString u(WideToUtf8(t));
				LVariantType tok_type = Types.Find(u);
				if (tok_type != GV_NULL)
				{
					Type.Base = tok_type;
				}
			}
		}
		
		if (Type.Ptr && Type.Base == GV_NULL)
			Type.Base = GV_VOID_PTR;
		
		Strs.Length(0);
		return Type;
	}

	bool DoExtern(uint32_t &Cur)
	{
		LArray<const char16*> Tok;
		const char16 *t;
		while ((t = GetTok(Cur)))
		{
			if (!StricmpW(t, sStartRdBracket))
			{
				Cur++;
				break;
			}
			else Tok.Add(t);
			Cur++;
		}
		
		if (Tok.Length() < 3)
		{
			return OnError(Cur, "Not enough tokens in extern decl.");
		}
		
		// First token is library name
		LExternFunc *e = new LExternFunc;
		if (!e)
			return OnError(Cur, "Alloc error.");
		
		Code->Externs.Add(e);
		e->Type = ExternFunc;
		char16 sQuotes[] = {'\'','\"',0};
		LAutoWString LibName(TrimStrW(Tok[0], sQuotes));
		e->Lib.Reset(WideToUtf8(LibName));
		Tok.DeleteAt(0, true);

		e->Method = Tok.Last();
		Tok.DeleteAt(Tok.Length()-1, true);

		if (!ValidStr(e->Method))
		{
			Code->Externs.Length(Code->Externs.Length()-1);
			return OnError(Cur, "Failed to get extern method name.");
		}
		
		// Parse return type
		e->ReturnType = ParseExternType(Tok);

		// Parse argument types
		Tok.Length(0);
		while ((t = GetTok(Cur)))
		{
			if (!StricmpW(t, sEndRdBracket))
			{
				e->ArgType.New() = ParseExternType(Tok);
				Cur++;
				break;
			}
			else if (!StricmpW(t, sComma))
			{
				e->ArgType.New() = ParseExternType(Tok);
			}
			else Tok.Add(t);
			Cur++;
		}
		
		if ((t = GetTok(Cur)))
		{
			if (StricmpW(t, sSemiColon))
				return OnError(Cur, "Expecting ';' in extern decl.");
			Cur++;
		}
		
		Methods.Add(e->Method, e);
		return true;
	}

	struct ExpPart
	{
		GOperator Op;
		int Value;
		ExpPart()
		{
			Op = OpNull;
			Value = 0;
		}
	};

	int Evaluate(LArray<char16*> &Exp, size_t Start, size_t End)
	{
		LArray<ExpPart> p;		

		// Find outer brackets
		ssize_t e;
		for (e = End; e >= (ssize_t)Start; e--)
		{
			if (Exp[e][0] == ')') break;
		}
		
		for (size_t i = Start; i <= End; i++)
		{
			char16 *t = Exp[i];
			if (*t == '(')
			{
				p.New().Value = Evaluate(Exp, i + 1, e - 1);
				i = e;
			}
			else
			{
				GOperator op = IsOp(t, 0);
				if (op)
				{
					p.New().Op = op;
				}
				else if (IsDigit(*t))
				{
					p.New().Value = AtoiW(t);
				}
				else
				{
					LAssert(0);
					break;
				}
			}
		}

		while (p.Length() > 1)
		{
			int HighPrec = 32;
			int Idx = -1;
			for (unsigned i=0; i<p.Length(); i++)
			{
				ExpPart &ep = p[i];
				if (ep.Op)
				{
					int Prec = GetPrecedence(ep.Op);
					if (Prec < HighPrec)
					{
						Idx = i;
						HighPrec = Prec;
					}
				}
			}
			if (Idx < 0)
			{
				LAssert(0);
				break;
			}			
			
			OperatorType Type = OpType(p[Idx].Op);
			if (Type == OpInfix)
			{
				if (Idx > 0 && Idx < (int)p.Length() - 1)
				{
					switch (p[Idx].Op)
					{
						case OpPlus:
							p[Idx-1].Value += p[Idx+1].Value;
							break;
						case OpMinus:
							p[Idx-1].Value -= p[Idx+1].Value;
							break;
						case OpMul:
							p[Idx-1].Value *= p[Idx+1].Value;
							break;
						case OpDiv:
							if (p[Idx+1].Value)
								p[Idx-1].Value /= p[Idx+1].Value;
							else
								LAssert(!"Div 0");
							break;
						default:
							LAssert(!"Impl me.");
							break;
					}
					p.DeleteAt(Idx, true);
					p.DeleteAt(Idx, true);
				}
				else
				{
					LAssert(0);
					break;
				}
			}
			else
			{
				LAssert(!"Impl me.");
			}
		}
		
		if (p.Length() == 1)
		{
			LAssert(p[0].Op == OpNull);
			return p[0].Value;
		}
		
		LAssert(0);
		return 0;
	}
	
	int ByteSizeFromType(char *s, LVariantType t)
	{
		switch (t)
		{
			case GV_INT32:
			{
				char n[16], *o = n;
				for (char *c = s; *c; c++)
				{
					if (IsDigit(*c) && o < n + sizeof(n) - 1)
						*o++ = *c;
				}
				*o++ = 0;
				
				int bits = ::atoi(n);
				switch (bits)
				{
					case 8:
						return 1;
					case 16:
						return 2;
				}
				return 4;
				break;
			}
			case GV_INT64:
			{
				return 8;
			}
			case GV_WSTRING:
			{
				return sizeof(char16);
			}
			default:
				break;
		}
		
		return 1;
	}

	/// Compiles struct construct
	bool DoStruct(uint32_t &Cur)
	{
		bool Status = false;

		// Parse struct name and setup a type
		char16 *t;
		LCustomType *Def = Code->Types.Find(t = GetTok(Cur));
		if (!Def)
			Code->Types.Add(t, Def = new LCustomType(t));
		Cur++;
		t = GetTok(Cur);
		if (!t || StricmpW(t, sStartCurlyBracket))
			return OnError(Cur, "Expecting '{'");
		Cur++;

		// Parse members
		while ((t = GetTok(Cur)))
		{
			// End of type def?
			GTokenType tt = ExpTok.Find(t);
			if (tt == TEndCurlyBracket)
			{
				Cur++;
				tt = GetTokType(Cur);
				if (!tt || tt != TSemiColon)
					return OnError(Cur, "Expecting ';' after '}'");

				Status = true;
				break;
			}
			
			if (tt == TFunction)
			{
				Cur++;
				if (!DoFunction(Cur, Def))
					return false;
			}
			else
			{
				// Parse member field
				LVariant TypeName = t;

				LCustomType *NestedType = 0;
				LVariantType Type = Types.Find(TypeName.Str());
				if (!Type)
				{
					// Check other custom types
					NestedType = Code->GetType(t);
					if (!NestedType)
						return OnError(Cur, "Unknown type '%S' in struct definition.", t);

					// Ok, nested type.
					Type = GV_CUSTOM;
				}

				Cur++;
				if (!(t = GetTok(Cur)))
					goto EofError;

				bool Pointer = false;
				if (t[0] == '*' && t[1] == 0)
				{
					Pointer = true;
					Cur++;
					if (!(t = GetTok(Cur)))
						goto EofError;
				}

				LVariant Name = t;
				Cur++;
				if (!(t = GetTok(Cur)))
					goto EofError;

				int Array = 1;
				if (!StricmpW(t, sStartSqBracket))
				{
					// Array
					Cur++;
					
					LArray<char16*> Exp;
					while ((t = GetTok(Cur)))
					{
						Cur++;
						if (!StricmpW(t, sEndSqBracket))
							break;
						Exp.Add(t);
					}

					Array = Evaluate(Exp, 0, Exp.Length()-1);
				}

				int MemberAddr = Def->AddressOf(Name.Str());
				if (MemberAddr >= 0)
					return OnError(Cur, "Member '%s' can't be defined twice.", Name.Str());

				if (NestedType)
				{
					if (!Def->DefineField(Name.Str(), NestedType, Array))
						return OnError(Cur, "Failed to define field '%s'.", Name.Str());
				}
				else
				{
					int Bytes = ByteSizeFromType(TypeName.Str(), Type);
					if (!Def->DefineField(Name.Str(), Type, Bytes, Array))
						return OnError(Cur, "Failed to define field '%s'.", Name.Str());
				}
				
				t = GetTok(Cur);
				if (StricmpW(t, sSemiColon))
					return OnError(Cur, "Expecting ';'");
				Cur++;
			}
		}

		return Status;

	EofError:
		return OnError(Cur, "Unexpected EOF.");
	}

	/// Compiler entry point
	bool Compile()
	{
		uint32_t Cur = 0;
		JumpLoc = 0;

		// Setup the global scope
		Scopes.Length(0);
		Scopes.Add(&Code->Globals);

		// Compile the code...
		while (Cur < Tokens.Length())
		{
			char16 *t = GetTok(Cur);
			if (!t)
				break;

			if (*t == '#' ||
				StricmpW(t, sSemiColon) == 0)
			{
				Cur++;
			}
			else if (!StricmpW(t, sFunction))
			{
				if (!DoFunction(++Cur))
					return false;
			}
			else if (!StricmpW(t, sStruct))
			{
				if (!DoStruct(++Cur))
					return false;
			}
			else if (!StricmpW(t, sEndCurlyBracket))
			{
				return OnError(Cur, "Not expecting '}'.");
			}
			else if (!StricmpW(t, sExtern))
			{
				if (!DoExtern(++Cur))
					return false;
			}
			else
			{
				if (JumpLoc)
				{
					GPtr p;
					p.u8 = &Code->ByteCode[JumpLoc];
					*p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4));
					JumpLoc = 0;
				}

				if (!DoStatements(Cur, NULL))
				{
					return OnError(Cur, "Statement compilation failed.");
				}
			}
		}

		if (JumpLoc)
		{
			GPtr p;
			p.u8 = &Code->ByteCode[JumpLoc];
			*p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4));
			JumpLoc = 0;
		}

		// Do link time fix ups...
		for (unsigned i=0; i<Fixups.Length(); i++)
		{
			LinkFixup &f = Fixups[i];
			if (f.Func->ValidStartAddr())
			{
				if (f.Args != f.Func->GetParams().Length())
				{
					return OnError(f.Tok, "Function call '%s' has wrong arg count (caller=%i, method=%i).",
											f.Func->GetName(),
											f.Args,
											f.Func->GetParams().Length());
				}
				else if (!f.Func->ValidFrameSize())
				{
					return OnError(f.Tok, "Function call '%s' has no frame size.",
											f.Func->GetName());
				}
				else
				{
					GPtr p;
					p.u8 = &Code->ByteCode[f.Offset];
					LAssert(*p.u32 == 0);
					*p.u32++ = f.Func->GetStartAddr();
					*p.u16++ = f.Func->GetFrameSize();
				}
			}
			else
			{
				return OnError(f.Tok, "Function '%s' not defined.", f.Func->GetName());
			}
		}
		Fixups.Length(0);

		return true;
	}
};

GCompiler::GCompiler()
{
	d = new LCompilerPriv;
}

GCompiler::~GCompiler()
{
	DeleteObj(d);
}

bool GCompiler::Compile
(
	LAutoPtr<LCompiledCode> &Code,
	LScriptContext *SysContext,
	LScriptContext *UserContext,
	const char *FileName,
	const char *Script,
	LDom *Args
)
{
	if (!Script)
		return false;

	LStringPipe p;

	#ifdef DEBUG_SCRIPT_FILE
	d->Debug = Stristr(FileName, DEBUG_SCRIPT_FILE) != NULL;
	#endif
	
	if (SysContext && SysContext->GetLog())
		d->Log = SysContext->GetLog();
	else if (UserContext && UserContext->GetLog())
		d->Log = UserContext->GetLog();
	else
		d->Log = &p;

	d->Methods.Empty();
	if (SysContext)
	{
		GHostFunc *f = SysContext->GetCommands();
		for (int i=0; f[i].Method; i++)
		{
			f[i].Context = SysContext;
			d->Methods.Add(f[i].Method, &f[i]);
		}
	}

	d->SysCtx = SysContext;
	d->UserCtx = UserContext;
	if (d->UserCtx)
	{
		GHostFunc *f = d->UserCtx->GetCommands();
		for (int i=0; f[i].Method; i++)
		{
			f[i].Context = d->UserCtx;
			
			if (!d->Methods.Find(f[i].Method))
				d->Methods.Add(f[i].Method, f+i);
			else
			{
				LgiTrace("%s:%i - Conflicting name of method in application's context: '%s'\n", _FL, f[i].Method.Get());
				LAssert(!"Conflicting name of method in application's context.");
			}
		}
	}

	if (!Code)
		Code.Reset(new LCompiledCode);
	
	bool Status = false;
	d->Code = dynamic_cast<LCompiledCode*>(Code.Get());
	if (d->Code)
	{
		d->Code->UserContext = UserContext;
		d->Code->SysContext = SysContext;
		d->Code->SetSource(FileName, Script);
		
		bool LexResult = d->Lex((char*)Script, FileName);
		if (LexResult)
		{
			d->ScriptArgs = Args;
			Status = d->Compile();
		}
		else
		{
			d->OnError(0, "Failed to lex script.\n");
		}
	}
	else
	{
		d->OnError(0, "Allocation failed.\n");
	}

	d->Code = NULL;
	return Status;
}

//////////////////////////////////////////////////////////////////////
class LScriptEnginePrivate
{
public:
	LViewI *Parent;
	SystemFunctions SysContext;
	LScriptContext *UserContext;
	LCompiledCode *Code;
	LVmDebuggerCallback *Callback;
	LVariant ReturnValue;

	LScriptEnginePrivate()
	{
		UserContext = NULL;
		Parent = NULL;
		Code = NULL;
		Callback = NULL;
	}
};

LScriptEngine::LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmDebuggerCallback *Callback)
{
	d = new LScriptEnginePrivate;
	d->Parent = parent;
	d->UserContext = UserContext;
	d->Callback = Callback;
	d->SysContext.SetEngine(this);
}

LScriptEngine::~LScriptEngine()
{
	DeleteObj(d);
}

LCompiledCode *LScriptEngine::GetCurrentCode()
{
	return d->Code;
}

bool LScriptEngine::Compile(LAutoPtr<LCompiledCode> &Obj, LScriptContext *UserContext, const char *Script, const char *FileName, LDom *Args)
{
	if (!Script)
	{
		LAssert(!"Param error");
		return NULL;
	}

	GCompiler Comp;
	return Comp.Compile(Obj,
						&d->SysContext,
						UserContext ? UserContext : d->UserContext,
						FileName,
						Script,
						Args);
}

LExecutionStatus LScriptEngine::Run(LCompiledCode *Obj, LVariant *Ret, const char *TempPath)
{
	LExecutionStatus Status = ScriptError;

	d->Code = Obj;
	if (d->Code)
	{
		LVirtualMachine Vm(d->Callback);
		if (TempPath)
			Vm.SetTempPath(TempPath);
		Status = Vm.Execute(d->Code, 0, NULL, true, Ret ? Ret : &d->ReturnValue);
		d->Code = NULL;
	}

	return Status;
}

LExecutionStatus LScriptEngine::RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret)
{
	LExecutionStatus Status = ScriptError;
	LCompiledCode *Code = dynamic_cast<LCompiledCode*>(Obj);
	if (Script && Code)
	{
		LAutoPtr<LCompiledCode> Temp(new LCompiledCode(*Code));
		uint32_t TempLen = (uint32_t) Temp->Length();
		d->Code = Temp;
		
		GCompiler Comp;
		if (Comp.Compile(Temp, &d->SysContext, d->UserContext, Temp->GetFileName(), Script, NULL))
		{
			LVirtualMachine Vm(d->Callback);
			Status = Vm.Execute(dynamic_cast<LCompiledCode*>(Temp.Get()), TempLen, NULL, true, Ret ? Ret : &d->ReturnValue);
		}
		
		d->Code = NULL;
	}

	return Status;
}

bool LScriptEngine::EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression)
{
	if (!Result || !VariableSource || !Expression)
	{
		LAssert(!"Param error");
		return false;
	}

	// Create trivial script to evaluate the expression
	LString a;
	a.Printf("return %s;", Expression);
	
	// Compile the script
	GCompiler Comp;
	LAutoPtr<LCompiledCode> Obj;
	if (!Comp.Compile(Obj, NULL, NULL, NULL, a, VariableSource))
	{
		LAssert(0);
		return false;
	}
	
	// Execute the script
	LVirtualMachine Vm(d->Callback);
	LCompiledCode *Code = dynamic_cast<LCompiledCode*>(Obj.Get());
	auto ReturnVal = Result ? Result : &d->ReturnValue;
	LExecutionStatus s = Vm.Execute(Code, 0, NULL, true, ReturnVal);
	if (s != ScriptSuccess)
	{
		return false;
	}
	
	if (ReturnVal->Type == GV_INT64 &&
		!(ReturnVal->Value.Int64 >> 32))
	{
		*ReturnVal = ReturnVal->CastInt32();
	}

	return true;
}

LStream *LScriptEngine::GetConsole()
{
	if (d->SysContext.GetLog())
		return d->SysContext.GetLog();

	if (d->UserContext && d->UserContext->GetLog())
		return d->UserContext->GetLog();

	return NULL;
}

bool LScriptEngine::SetConsole(LStream *t)
{
	d->SysContext.SetLog(t);

	if (d->UserContext)
		d->UserContext->SetLog(t);

	return true;
}

bool LScriptEngine::CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args)
{
	LCompiledCode *Code = dynamic_cast<LCompiledCode*>(Obj);
	if (!Code || !Method)
		return false;

	LFunctionInfo *i = Code->GetMethod(Method);
	if (!i)
		return false;

	LVirtualMachine Vm(d->Callback);
	Args.Vm = &Vm;
	LExecutionStatus Status = Vm.ExecuteFunction(Code, i, Args, NULL);
	Args.Vm = NULL;
	return Status != ScriptError;
}

LScriptContext *LScriptEngine::GetSystemContext()
{
	return &d->SysContext;
}
