#include "ScriptUtils.h"
#include "CommandTable.h"
#include "GameForms.h"
#include "GameObjects.h"
#include "Hooks_Script.h"
#include <string>

#if OBLIVION

static void ShowError(const char* msg)
{
	Console_Print(msg);
	_MESSAGE(msg);
}

static bool ShowWarning(const char* msg)
{
	Console_Print(msg);
	_MESSAGE(msg);
	return false;
}

#else

static void ShowError(const char* msg)
{
	MessageBox(NULL, msg, "OBSE", MB_ICONEXCLAMATION);
}

static bool ShowWarning(const char* msg)
{
	char msgText[0x400];
	sprintf_s(msgText, sizeof(msgText), "%s\n\nDisable this warning for the remainder of the session?", msg);
	int result = MessageBox(NULL, msgText, "OBSE", MB_YESNO | MB_ICONEXCLAMATION);
	return result == IDYES;
}

#endif

ErrOutput g_ErrOut(ShowError, ShowWarning);

#if OBLIVION

// run-time errors

enum {
	kScriptError_UnhandledOperator,
	kScriptError_DivisionByZero,
	kScriptError_InvalidArrayAccess,
	kScriptError_UninitializedArray,
	kScriptError_InvalidCallingObject,
	kScriptError_CommandFailed,
	kScriptError_MissingOperand,
	kScriptError_OperatorFailed,
	kScriptError_ExpressionFailed,
	kScriptError_UnexpectedTokenType,
	kScriptError_RefToTempArray,
};

// Operator routines

const char* OpTypeToSymbol(OperatorType op);

ScriptToken* Eval_Comp_Number_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	switch (op)
	{
	case kOpType_GreaterThan:
		return new ScriptToken(lh->value.num > rh->value.num);
	case kOpType_LessThan:
		return new ScriptToken(lh->value.num < rh->value.num);
	case kOpType_GreaterOrEqual:
		return new ScriptToken(lh->value.num >= rh->value.num);
	case kOpType_LessOrEqual:
		return new ScriptToken(lh->value.num <= rh->value.num);
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Comp_String_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	const char* lhs = lh->value.str.c_str();
	const char* rhs = rh->value.str.c_str();
	switch (op)
	{
	case kOpType_GreaterThan:
		return new ScriptToken(_stricmp(lhs, rhs) > 0);
	case kOpType_LessThan:
		return new ScriptToken(_stricmp(lhs, rhs) < 0);
	case kOpType_GreaterOrEqual:
		return new ScriptToken(_stricmp(lhs, rhs) >= 0);
	case kOpType_LessOrEqual:
		return new ScriptToken(_stricmp(lhs, rhs) <= 0);
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Eq_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	switch (op)
	{
	case kOpType_Equals:
		return new ScriptToken(lh->value.num == rh->value.num);
	case kOpType_NotEqual:
		return new ScriptToken(lh->value.num != rh->value.num);
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Eq_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	const char* lhs = lh->value.str.c_str();
	const char* rhs = rh->value.str.c_str();
	switch (op)
	{
	case kOpType_Equals:
		return new ScriptToken(_stricmp(lhs, rhs) == 0);
	case kOpType_NotEqual:
		return new ScriptToken(_stricmp(lhs, rhs) != 0);
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Eq_Form(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	bool result = false;
	TESForm* lhForm = lh->GetForm();
	TESForm* rhForm = rh->GetForm();
	if (lhForm == NULL && rhForm == NULL)
		result = true;
	else if (lhForm && rhForm && lhForm->refID == rhForm->refID)
		result = true;

	switch (op)
	{
	case kOpType_Equals:
		return new ScriptToken(result);
	case kOpType_NotEqual:
		return new ScriptToken(!result);
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Eq_Form_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	bool result = false;
	if (rh->value.num == 0 && lh->value.formID == 0)	// only makes sense to compare forms to zero
		result = true;
	switch (op)
	{
	case kOpType_Equals:
		return new ScriptToken(result);
	case kOpType_NotEqual:
		return new ScriptToken(!result);
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Logical(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	switch (op)
	{
	case kOpType_LogicalAnd:
		return new ScriptToken(lh->value.num && rh->value.num);
	case kOpType_LogicalOr:
		return new ScriptToken(lh->value.num || rh->value.num);
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Add_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	return new ScriptToken(lh->value.num + rh->value.num);
}

ScriptToken* Eval_Add_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	return new ScriptToken(lh->value.str + rh->value.str);
}

ScriptToken* Eval_Arithmetic(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	double l = lh->value.num;
	double r = rh->value.num;
	switch (op)
	{
	case kOpType_Subtract:
		return new ScriptToken(l - r);
	case kOpType_Multiply:
		return new ScriptToken(l * r);
	case kOpType_Divide:
		if (r != 0)
			return new ScriptToken(l / r);
		else {
			context->Error("Division by zero");
			return NULL;
		}
	case kOpType_Exponent:
		return new ScriptToken(pow(l, r));
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}

}

ScriptToken* Eval_Integer(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	SInt64 l = lh->value.num;
	SInt64 r = rh->value.num;

	switch (op)
	{
	case kOpType_Modulo:
		if (r != 0)
			return new ScriptToken(double(l % r));
		else {
			context->Error("Division by zero");
			return NULL;
		}
	case kOpType_BitwiseOr:
		return new ScriptToken(double(l | r));
	case kOpType_BitwiseAnd:
		return new ScriptToken(double(l & r));
	case kOpType_LeftShift:
		return new ScriptToken(double(l << r));
	case kOpType_RightShift:
		return new ScriptToken(double(l >> r));
	default:
		context->Error("Unhandled operator %s", OpTypeToSymbol(op));
		return NULL;
	}
}

ScriptToken* Eval_Assign_Numeric(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	double result = rh->value.num;
	if (lh->variableType == Script::eVarType_Integer)
		result = floor(result);

	lh->value.var->data = result;
	return new ScriptToken(result);
}

ScriptToken* Eval_Assign_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	UInt32 strID = lh->value.var->data;
	StringVar* strVar = g_StringMap.Get(strID);
	if (!strVar)
	{
		strID = g_StringMap.Add(context->script->GetModIndex(), rh->value.str.c_str());
		lh->value.var->data = strID;
	}
	else
		strVar->Set(rh->value.str.c_str());

	return new ScriptToken(rh->value.str);
}

ScriptToken* Eval_Assign_Form(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	UInt64* outRefID = (UInt64*)&lh->value.var->data;
	*outRefID = rh->value.formID;
	return new ScriptToken(rh->value.formID, kTokenType_Form);
}

ScriptToken* Eval_Assign_Form_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	// only direct numeric assignment we accept is zero
	lh->value.var->data = 0;
	return new ScriptToken(0, kTokenType_Form);
}

ScriptToken* Eval_Assign_Global(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	lh->value.global->data = rh->value.num;
	return new ScriptToken(rh->value.num);
}

ScriptToken* Eval_Assign_Array(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	g_ArrayMap.AddReference(&lh->value.var->data, rh->value.arrID, context->script->GetModIndex());
	return new ScriptToken(lh->value.var->data, kTokenType_Array);
}

ScriptToken* Eval_Assign_Elem_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	ArrayKey* key = lh->Key();
	if (!key)
		return NULL;

	return g_ArrayMap.SetElementNumber(lh->value.arrID, *key, rh->value.num) ? new ScriptToken(rh->value.num) : NULL;
}

ScriptToken* Eval_Assign_Elem_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	ArrayKey* key = lh->Key();
	if (!key)
		return NULL;

	return g_ArrayMap.SetElementString(lh->value.arrID, *key, rh->value.str) ? new ScriptToken(rh->value.str) : NULL;
}

ScriptToken* Eval_Assign_Elem_Form(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	ArrayKey* key = lh->Key();
	if (!key)
		return NULL;

	return g_ArrayMap.SetElementFormID(lh->value.arrID, *key, rh->value.formID) ? new ScriptToken(rh->value.formID, kTokenType_Form) : NULL;
}

ScriptToken* Eval_Assign_Elem_Array(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	ArrayKey* key = lh->Key();
	if (!key)
		return NULL;

	if (g_ArrayMap.SetElementArray(lh->value.arrID, *key, rh->value.arrID))
	{
		ArrayID newID;
		if (g_ArrayMap.GetElementArray(lh->value.arrID, *key, &newID))
			return new ScriptToken(newID, kTokenType_Array);
	}

	return NULL;
}

ScriptToken* Eval_Negation(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	return new ScriptToken(lh->value.num * -1);
}

ScriptToken* Eval_LogicalNot(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	return new ScriptToken(lh->value.num ? false : true);
}

ScriptToken* Eval_Subscript_Array_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	if (!lh->value.arrID || g_ArrayMap.GetKeyType(lh->value.arrID) != kDataType_Numeric)
	{
		context->Error("Invalid array access - expected string index, received numeric index or the array was not initialized.");
		return NULL;
	}

	ArrayKey key(rh->value.num);
	return ScriptToken::ElementToken(lh->value.arrID, &key);
}

ScriptToken* Eval_Subscript_Array_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	if (!lh->value.arrID || g_ArrayMap.GetKeyType(lh->value.arrID) != kDataType_String)
	{
		context->Error("Invalid array access - expected numeric index, received string index or the array was not initialized.");
		return NULL;
	}

	ArrayKey key(rh->value.str);
	return ScriptToken::ElementToken(lh->value.arrID, &key);
}

ScriptToken* Eval_Subscript_Array_Slice(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	UInt32 slicedID = g_ArrayMap.MakeSlice(lh->value.arrID, rh->GetSlice(), context->script->GetModIndex());
	if (!slicedID)
	{
		context->Error("Invalid array slice operation - array is uninitialized or supplied index does not match key type");
		return NULL;
	}

	return new ScriptToken(slicedID, kTokenType_Array);
}

ScriptToken* Eval_Subscript_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	UInt32 idx = (rh->value.num < 0) ? rh->value.str.length() + rh->value.num : rh->value.num;
	if (idx < lh->value.str.length())
		return new ScriptToken(lh->value.str.substr(idx, 1));
	else
		return new ScriptToken("");
}

ScriptToken* Eval_Subscript_String_Slice(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	Slice* slice = rh->GetSlice();
	std::string str = lh->value.str;

	if (!slice || slice->bIsString)
	{
		context->Error("Invalid string slice operation");
		return NULL;
	}

	if (slice->m_lower < 0)
		slice->m_lower += str.length();
	if (slice->m_upper < 0)
		slice->m_upper += str.length();

	if (slice->m_lower >= 0 && slice->m_upper < str.length() && slice->m_lower < slice->m_upper)
		return new ScriptToken(str.substr(slice->m_lower, slice->m_upper - slice->m_lower + 1));
	else
		return new ScriptToken("");
}


ScriptToken* Eval_Slice_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	Slice slice;
	slice.bIsString = true;
	slice.m_lowerStr = lh->value.str;
	slice.m_upperStr = rh->value.str;
	return ScriptToken::CreateSliceToken(&slice);
}

ScriptToken* Eval_Slice_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	Slice slice;
	slice.bIsString = false;
	slice.m_lower = lh->value.num;
	slice.m_upper = rh->value.num;
	return ScriptToken::CreateSliceToken(&slice);
}

ScriptToken* Eval_ToString_String(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	return new ScriptToken(lh->value.str);
}

ScriptToken* Eval_ToString_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	char buf[0x20];
	sprintf_s(buf, sizeof(buf), "%g", lh->value.num);
	return new ScriptToken(std::string(buf));
}

ScriptToken* Eval_ToString_Form(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	return new ScriptToken(std::string(GetFullName(lh->GetForm())));
}

ScriptToken* Eval_ToString_Array(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	char buf[0x20];
	sprintf_s(buf, sizeof(buf), "Array ID %d", lh->value.arrID);
	return new ScriptToken(std::string(buf));
}

ScriptToken* Eval_In(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context)
{
	switch (lh->variableType)
	{
	case Script::eVarType_Array:
		{
			UInt32 iterID = g_ArrayMap.Create(kDataType_String, false, context->script->GetModIndex());
			g_ArrayMap.AddReference(&lh->value.var->data, iterID, context->script->GetModIndex());
			ScriptToken* forEach = ScriptToken::CreateForEachContext(rh->value.arrID, iterID, Script::eVarType_Array);
			if (!forEach)
				g_ArrayMap.RemoveReference(&lh->value.var->data, context->script->GetModIndex());

			return forEach;
		}
	case Script::eVarType_String:
		{
			UInt32 iterID = lh->value.var->data;
			StringVar* sv = g_StringMap.Get(iterID);
			if (!sv)
			{
				iterID = g_StringMap.Add(context->script->GetModIndex(), "");
				lh->value.var->data = iterID;
			}
			UInt32 srcID = g_StringMap.Add(context->script->GetModIndex(), rh->value.str.c_str(), true);
			return ScriptToken::CreateForEachContext(srcID, iterID, Script::eVarType_String);
		}
	}

	return NULL;
}

#define OP_HANDLER(x) x
#else
#define OP_HANDLER(x) NULL
#endif

// Operator Rules
OperationRule kOpRule_Comparison[] =
{
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Boolean, NULL	},
	{	kTokenType_Ambiguous, kTokenType_Number, kTokenType_Boolean, NULL	},
	{	kTokenType_Ambiguous, kTokenType_String, kTokenType_Boolean, NULL	},

	{	kTokenType_Number, kTokenType_Number, kTokenType_Boolean, OP_HANDLER(Eval_Comp_Number_Number)	},
	{	kTokenType_String, kTokenType_String, kTokenType_Boolean, OP_HANDLER(Eval_Comp_String_String)	},
};

OperationRule kOpRule_Equality[] =
{
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Boolean	},
	{	kTokenType_Ambiguous, kTokenType_Number, kTokenType_Boolean	},
	{	kTokenType_Ambiguous, kTokenType_Form, kTokenType_Boolean	},
	{	kTokenType_Ambiguous, kTokenType_String, kTokenType_Boolean	},

	{	kTokenType_Number, kTokenType_Number, kTokenType_Boolean, OP_HANDLER(Eval_Eq_Number)	},
	{	kTokenType_String, kTokenType_String, kTokenType_Boolean, OP_HANDLER(Eval_Eq_String)	},
	{	kTokenType_Form, kTokenType_Form, kTokenType_Boolean, OP_HANDLER(Eval_Eq_Form)	},
	{	kTokenType_Form, kTokenType_Number, kTokenType_Boolean, OP_HANDLER(Eval_Eq_Form_Number), true	},
};

OperationRule kOpRule_Logical[] =
{
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Boolean	},
	{	kTokenType_Ambiguous, kTokenType_Boolean, kTokenType_Boolean	},

	{	kTokenType_Boolean, kTokenType_Boolean, kTokenType_Boolean, OP_HANDLER(Eval_Logical)	},
};

OperationRule kOpRule_Addition[] =
{
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Ambiguous	},
	{	kTokenType_Ambiguous, kTokenType_Number, kTokenType_Number	},
	{	kTokenType_Ambiguous, kTokenType_String, kTokenType_String	},

	{	kTokenType_Number, kTokenType_Number, kTokenType_Number, OP_HANDLER(Eval_Add_Number)	},
	{	kTokenType_String, kTokenType_String, kTokenType_String, OP_HANDLER(Eval_Add_String)	},
};

OperationRule kOpRule_Arithmetic[] =
{
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Number },
	{	kTokenType_Number, kTokenType_Ambiguous, kTokenType_Number },

	{	kTokenType_Number, kTokenType_Number, kTokenType_Number, OP_HANDLER(Eval_Arithmetic)	}
};

OperationRule kOpRule_Integer[] =
{
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Number	},
	{	kTokenType_Number, kTokenType_Ambiguous, kTokenType_Number	},

	{	kTokenType_Number, kTokenType_Number, kTokenType_Number, OP_HANDLER(Eval_Integer)	},
};

OperationRule kOpRule_Assignment[] =
{
	{	kTokenType_Ambiguous,	kTokenType_Ambiguous,	kTokenType_Ambiguous, NULL, true	},
	{	kTokenType_Ambiguous,	kTokenType_String,		kTokenType_String,		NULL, true	},
	{	kTokenType_Ambiguous,	kTokenType_Number,		kTokenType_Number,		NULL, true	},
	{	kTokenType_Ambiguous,	kTokenType_Array,		kTokenType_Array,		NULL, true	},
	{	kTokenType_Ambiguous,	kTokenType_Form,		kTokenType_Form,		NULL, true	},

	{	kTokenType_NumericVar,	kTokenType_Ambiguous,	kTokenType_Number,	NULL, true	},
	{	kTokenType_RefVar,		kTokenType_Ambiguous,	kTokenType_Form,	NULL, true	},
	{	kTokenType_StringVar,	kTokenType_Ambiguous,	kTokenType_String,	NULL, true	},
	{	kTokenType_ArrayVar,	kTokenType_Ambiguous,	kTokenType_Array,	NULL, true	},
	{	kTokenType_ArrayElement,	kTokenType_Ambiguous,	kTokenType_Ambiguous,	NULL, true },

	{	kTokenType_NumericVar, kTokenType_Number, kTokenType_Number, OP_HANDLER(Eval_Assign_Numeric), true	},
	{	kTokenType_StringVar,	kTokenType_String, kTokenType_String, OP_HANDLER(Eval_Assign_String), true	},
	{	kTokenType_RefVar, kTokenType_Form, kTokenType_Form, OP_HANDLER(Eval_Assign_Form), true	},
	{	kTokenType_RefVar,		kTokenType_Number,	kTokenType_Form, OP_HANDLER(Eval_Assign_Form_Number), true },
	{	kTokenType_Global,	kTokenType_Number,	kTokenType_Number, OP_HANDLER(Eval_Assign_Global), true	},
	{	kTokenType_ArrayVar, kTokenType_Array, kTokenType_Array, OP_HANDLER(Eval_Assign_Array), true },
	{	kTokenType_ArrayElement,	kTokenType_Number, kTokenType_Number, OP_HANDLER(Eval_Assign_Elem_Number), true	},
	{	kTokenType_ArrayElement,	kTokenType_String,	kTokenType_String, OP_HANDLER(Eval_Assign_Elem_String), true	},
	{	kTokenType_ArrayElement,	kTokenType_Form,	kTokenType_Form, OP_HANDLER(Eval_Assign_Elem_Form), true	},
	{	kTokenType_ArrayElement, kTokenType_Array, kTokenType_Array, OP_HANDLER(Eval_Assign_Elem_Array), true	}
};

OperationRule kOpRule_Negation[] =
{
	{	kTokenType_Ambiguous, kTokenType_Invalid, kTokenType_Number, NULL, true	},
	
	{	kTokenType_Number, kTokenType_Invalid, kTokenType_Number, OP_HANDLER(Eval_Negation), true	},
};

OperationRule kOpRule_LogicalNot[] =
{
	{	kTokenType_Ambiguous, kTokenType_Invalid, kTokenType_Boolean, NULL, true },

	{	kTokenType_Boolean, kTokenType_Invalid, kTokenType_Boolean, OP_HANDLER(Eval_LogicalNot), true },
};

OperationRule kOpRule_LeftBracket[] =
{
	{	kTokenType_ArrayVar, kTokenType_Ambiguous, kTokenType_ArrayElement, NULL, true	},
	{	kTokenType_String, kTokenType_Ambiguous, kTokenType_String, NULL, true	},
	{	kTokenType_Ambiguous, kTokenType_String, kTokenType_ArrayElement, NULL, true	},
	{	kTokenType_Ambiguous, kTokenType_Number, kTokenType_Ambiguous, NULL, true	},
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Ambiguous, NULL, true	},
	{	kTokenType_Ambiguous, kTokenType_Slice, kTokenType_Ambiguous, NULL, true	},

	{	kTokenType_Array, kTokenType_Number, kTokenType_ArrayElement, OP_HANDLER(Eval_Subscript_Array_Number), true	},
	{	kTokenType_Array, kTokenType_String, kTokenType_ArrayElement, OP_HANDLER(Eval_Subscript_Array_String), true	},
	{	kTokenType_String, kTokenType_Number, kTokenType_String, OP_HANDLER(Eval_Subscript_String), true	},
	{	kTokenType_Array, kTokenType_Slice, kTokenType_Array, OP_HANDLER(Eval_Subscript_Array_Slice), true	},
	{	kTokenType_String, kTokenType_Slice, kTokenType_String, OP_HANDLER(Eval_Subscript_String_Slice), true	}
};


OperationRule kOpRule_Slice[] =
{
	{	kTokenType_Ambiguous, kTokenType_Ambiguous, kTokenType_Slice	},
	{	kTokenType_Ambiguous, kTokenType_Number, kTokenType_Slice	},
	{	kTokenType_Ambiguous, kTokenType_String, kTokenType_Slice	},

	{	kTokenType_String, kTokenType_String, kTokenType_Slice, OP_HANDLER(Eval_Slice_String)	},
	{	kTokenType_Number, kTokenType_Number, kTokenType_Slice, OP_HANDLER(Eval_Slice_Number)	},
};

OperationRule kOpRule_In[] =
{
	{	kTokenType_ArrayVar,	kTokenType_Ambiguous,	kTokenType_ForEachContext,	NULL,	true	},
	
	{	kTokenType_ArrayVar,	kTokenType_Array,		kTokenType_ForEachContext,	OP_HANDLER(Eval_In), true },
	{	kTokenType_StringVar,	kTokenType_String,		kTokenType_ForEachContext,	OP_HANDLER(Eval_In), true },
};

OperationRule kOpRule_ToString[] =
{
	{	kTokenType_Ambiguous,	kTokenType_Invalid,		kTokenType_String,			NULL,	true	},

	{	kTokenType_String,		kTokenType_Invalid,		kTokenType_String,		OP_HANDLER(Eval_ToString_String),	true	},
	{	kTokenType_Number,		kTokenType_Invalid,		kTokenType_String,		OP_HANDLER(Eval_ToString_Number),	true	},
	{	kTokenType_Form,		kTokenType_Invalid,		kTokenType_String,		OP_HANDLER(Eval_ToString_Form),		true	},
	{	kTokenType_Array,		kTokenType_Invalid,		kTokenType_String,		OP_HANDLER(Eval_ToString_Array),	true	},
};

// Operator definitions
#define OP_RULES(x) SIZEOF_ARRAY(kOpRule_ ## x, OperationRule), kOpRule_ ## x

// ###TODO: symbols make output easier to verify but are unnecessary & require translation at run-time - discard
static Operator s_operators[] =
{
	{	2,	":=",	2,	kOpType_Assignment, OP_RULES(Assignment)	},
	{	5,	"||",	2,	kOpType_LogicalOr,	OP_RULES(Logical)		},
	{	7,	"&&",	2,	kOpType_LogicalAnd, OP_RULES(Logical)		},

	{	9,	":",	2,	kOpType_Slice,		OP_RULES(Slice)			},
	{	13,	"==",	2,	kOpType_Equals,		OP_RULES(Equality)		},
	{	13,	"!=",	2,	kOpType_NotEqual,	OP_RULES(Equality)		},

	{	15,	">",	2,	kOpType_GreaterThan,OP_RULES(Comparison)	},
	{	15,	"<",	2,	kOpType_LessThan,	OP_RULES(Comparison)	},
	{	15,	">=",	2,	kOpType_GreaterOrEqual,	OP_RULES(Comparison)	},			
	{	15,	"<=",	2,	kOpType_LessOrEqual,	OP_RULES(Comparison)	},									
									
	{	16,	"|",	2,	kOpType_BitwiseOr,	OP_RULES(Integer)		},		// ** higher precedence than in C++
	{	17,	"&",	2,	kOpType_BitwiseAnd,	OP_RULES(Integer)		},

	{	18,	"<<",	2,	kOpType_LeftShift,	OP_RULES(Integer)		},
	{	18,	">>",	2,	kOpType_RightShift,	OP_RULES(Integer)		},

	{	19,	"+",	2,	kOpType_Add,		OP_RULES(Addition)		},
	{	19,	"-",	2,	kOpType_Subtract,	OP_RULES(Arithmetic)	},

	{	21,	"*",	2,	kOpType_Multiply,	OP_RULES(Arithmetic)	},
	{	21,	"/",	2,	kOpType_Divide,		OP_RULES(Arithmetic)	},
	{	21,	"%",	2,	kOpType_Modulo,		OP_RULES(Integer)		},

	{	23,	"^",	2,	kOpType_Exponent,	OP_RULES(Arithmetic)	},		// exponentiation
	{	25,	"-",	1,	kOpType_Negation,	OP_RULES(Negation)		},		// unary minus in compiled script

	{	27, "!",	1,	kOpType_LogicalNot,	OP_RULES(LogicalNot)	},

	{	80,	"(",	0,	kOpType_LeftParen,	0,	NULL				},
	{	80,	")",	0,	kOpType_RightParen,	0,	NULL				},

	{	90, "[",	2,	kOpType_LeftBracket,	OP_RULES(LeftBracket)	},		// functions both as paren and operator
	{	90,	"]",	0,	kOpType_RightBracket,	0,	NULL				},		// functions only as paren

	{	2,	"<-",	2,	kOpType_In,			OP_RULES(In)			},			// 'foreach iter <- arr'
	{	25,	"$",	1,	kOpType_ToString,	OP_RULES(ToString)		},			// converts operand to string
};

STATIC_ASSERT(SIZEOF_ARRAY(s_operators, Operator) == kOpType_Max);

const char* OpTypeToSymbol(OperatorType op)
{
	if (op < kOpType_Max)
		return s_operators[op].symbol;
	return "<unknown>";
}

#if OBLIVION

// Operand conversion routines
ScriptToken Number_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.num ? true : false);
}

ScriptToken Number_To_String(ScriptToken* token, ExpressionEvaluator* context)
{
	char str[0x20];
	sprintf_s(str, sizeof(str), "%f", token->value.num);
	return ScriptToken(std::string(str));
}

ScriptToken Bool_To_Number(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.num);
}

ScriptToken Cmd_To_Number(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.num);
}

ScriptToken Cmd_To_Form(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(*((UInt64*)(&token->value.num)), kTokenType_Form);
}

ScriptToken Cmd_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.num ? true : false);
}

ScriptToken Ref_To_Form(ScriptToken* token, ExpressionEvaluator* context)
{
	token->value.refVar->Resolve(context->eventList);
	return ScriptToken(token->value.refVar->form ? token->value.refVar->form->refID : 0, kTokenType_Form);
}

ScriptToken Ref_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	token->value.refVar->Resolve(context->eventList);
	return ScriptToken(token->value.refVar->form ? true : false);
}

ScriptToken Ref_To_RefVar(ScriptToken* token, ExpressionEvaluator* context)
{
	ScriptEventList::Var* var = context->eventList->GetVariable(token->value.refVar->varIdx);
	if (var)
		return ScriptToken(var);
	else
		return ScriptToken::Bad();
}

ScriptToken Global_To_Number(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.global->data);
}

ScriptToken Global_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.global->data ? true : false);
}

ScriptToken Form_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.formID ? true : false);
}

ScriptToken NumericVar_To_Number(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var->data);
}

ScriptToken NumericVar_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var->data ? true : false);
}

ScriptToken NumericVar_To_Variable(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var);
}

ScriptToken StringVar_To_String(ScriptToken* token, ExpressionEvaluator* context)
{
	StringVar* strVar = g_StringMap.Get(token->value.var->data);
	if (strVar)
		return ScriptToken(strVar->String());
	else
		return ScriptToken("");
}

ScriptToken StringVar_To_Variable(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var);
}

ScriptToken StringVar_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var->data ? true : false);
}

ScriptToken ArrayVar_To_Array(ScriptToken* token, ExpressionEvaluator* context)
{
	ArrayID id = token->value.var->data;
	return g_ArrayMap.Exists(id) ? ScriptToken(id, kTokenType_Array) : ScriptToken::Bad();
}

ScriptToken ArrayVar_To_Variable(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var);
}

ScriptToken ArrayVar_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var->data ? true : false);
}

ScriptToken Elem_To_Number(ScriptToken* token, ExpressionEvaluator* context)
{
	double num;
	ArrayKey* key = token->Key();
	if (!key || !g_ArrayMap.GetElementNumber(token->value.arrID, *key, &num))
		return ScriptToken::Bad();

	return ScriptToken(num);
}

ScriptToken Elem_To_Form(ScriptToken* token, ExpressionEvaluator* context)
{
	UInt32 formID;
	ArrayKey* key = token->Key();
	if (!key || !g_ArrayMap.GetElementFormID(token->value.arrID, *key, &formID))
		return ScriptToken::Bad();

	return ScriptToken(formID, kTokenType_Form);
}

ScriptToken Elem_To_String(ScriptToken* token, ExpressionEvaluator* context)
{
	std::string str;
	ArrayKey* key = token->Key();
	if (!key || !g_ArrayMap.GetElementString(token->value.arrID, *key, str))
		return ScriptToken::Bad();

	return ScriptToken(str);
}

ScriptToken Elem_To_Array(ScriptToken* token, ExpressionEvaluator* context)
{
	ArrayID arr;
	ArrayKey* key = token->Key();
	if (!key || !g_ArrayMap.GetElementArray(token->value.arrID, *key, &arr))
		return ScriptToken::Bad();

	return ScriptToken(arr, kTokenType_Array);
}

ScriptToken Elem_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	bool result;
	ArrayKey* key = token->Key();
	if (!key || !g_ArrayMap.GetElementAsBool(token->value.arrID, *key, &result))
		return ScriptToken::Bad();

	return ScriptToken(result);
}

ScriptToken RefVar_To_Form(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(*((UInt64*)(&token->value.var->data)), kTokenType_Form);
}

ScriptToken RefVar_To_Bool(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var->data ? true : false);
}

ScriptToken RefVar_To_Variable(ScriptToken* token, ExpressionEvaluator* context)
{
	return ScriptToken(token->value.var);
}

// ExpressionEvaluator

bool ExpressionEvaluator::s_bSuppressErrorMessages = false;

void ExpressionEvaluator::Error(const char* fmt, ...)
{
	if (s_bSuppressErrorMessages)
		return;

	va_list args;
	va_start(args, fmt);

	char	errorMsg[0x400];
	vsprintf_s(errorMsg, 0x400, fmt, args);
	ShowRuntimeError(script, errorMsg);

	// ###TODO: halt script?
}

#define OPERAND_CONVERT(x) x
#else
#define OPERAND_CONVERT(x) NULL
#endif

// Rules for converting from one operand type to another
ConversionRule kConversions_Number[] =
{
	{	kTokenType_Boolean, OPERAND_CONVERT(Number_To_Bool)	},
//	{	kTokenType_String,	OPERAND_CONVERT(Number_To_String) }
};

ConversionRule kConversions_Boolean[] =
{
	{	kTokenType_Number,	OPERAND_CONVERT(Bool_To_Number)	},
};

ConversionRule kConversions_Command[] =
{
	{	kTokenType_Ambiguous, NULL	},

	{	kTokenType_Number,	OPERAND_CONVERT(Cmd_To_Number)	},
	{	kTokenType_Form,	OPERAND_CONVERT(Cmd_To_Form)		},
	{	kTokenType_Boolean,	OPERAND_CONVERT(Cmd_To_Bool)	},
};

ConversionRule kConversions_Ref[] =
{
	{	kTokenType_Form,	OPERAND_CONVERT(Ref_To_Form)	},
	{	kTokenType_Boolean,	OPERAND_CONVERT(Ref_To_Bool)	},
//	{	kTokenType_RefVar,	OPERAND_CONVERT(Ref_To_RefVar)	},
};

ConversionRule kConversions_Global[] =
{
	{	kTokenType_Number,		OPERAND_CONVERT(Global_To_Number)	},
	{	kTokenType_Boolean,		OPERAND_CONVERT(Global_To_Bool)		},
};

ConversionRule kConversions_Form[] =
{
	{	kTokenType_Boolean,	OPERAND_CONVERT(Form_To_Bool)	},
};

ConversionRule kConversions_NumericVar[] =
{
	{	kTokenType_Number,		OPERAND_CONVERT(NumericVar_To_Number)	},
	{	kTokenType_Boolean,		OPERAND_CONVERT(NumericVar_To_Bool)		},
	{	kTokenType_Variable,	OPERAND_CONVERT(NumericVar_To_Variable)	},
};

ConversionRule kConversions_ArrayElement[] =
{
	{	kTokenType_Ambiguous,	OPERAND_CONVERT(NULL)	},

	{	kTokenType_Number,		OPERAND_CONVERT(Elem_To_Number)	},
	{	kTokenType_Form,		OPERAND_CONVERT(Elem_To_Form)	},
	{	kTokenType_String,		OPERAND_CONVERT(Elem_To_String)	},
	{	kTokenType_Array,		OPERAND_CONVERT(Elem_To_Array)	},
	{	kTokenType_Boolean,		OPERAND_CONVERT(Elem_To_Bool)	},
};

ConversionRule kConversions_RefVar[] =
{
	{	kTokenType_Form,		OPERAND_CONVERT(RefVar_To_Form)	},
	{	kTokenType_Boolean,		OPERAND_CONVERT(RefVar_To_Bool)	},
	{	kTokenType_Variable,	OPERAND_CONVERT(RefVar_To_Variable)	},
};

ConversionRule kConversions_StringVar[] =
{
	{	kTokenType_String,		OPERAND_CONVERT(StringVar_To_String)	},
	{	kTokenType_Variable,	OPERAND_CONVERT(StringVar_To_Variable)	},
	{	kTokenType_Boolean,		OPERAND_CONVERT(StringVar_To_Bool)	},
};

ConversionRule kConversions_ArrayVar[] =
{
	{	kTokenType_Array,		OPERAND_CONVERT(ArrayVar_To_Array)	},
	{	kTokenType_Variable,	OPERAND_CONVERT(ArrayVar_To_Variable)	},
	{	kTokenType_Boolean,		OPERAND_CONVERT(ArrayVar_To_Bool)	},
};

// Operand definitions
#define OPERAND(x) kConversions_ ## x, SIZEOF_ARRAY(kConversions_ ## x, ConversionRule)

static Operand s_operands[] =
{
	{	OPERAND(Number)		},
	{	OPERAND(Boolean)	},
	{	NULL,	0			},	// string has no conversions
	{	OPERAND(Form)		},
	{	OPERAND(Ref)		},
	{	OPERAND(Global)		},
	{	NULL,	0			},	// array
	{	OPERAND(ArrayElement)	},
	{	NULL,	0			},	// slice
	{	OPERAND(Command)	},
	{	NULL,	0			},	// variable
	{	OPERAND(NumericVar)	},
	{	OPERAND(RefVar)		},
	{	OPERAND(StringVar)	},
	{	OPERAND(ArrayVar)	},
	{	NULL,	0			},  // operator
	{	NULL,	0			},	// ambiguous
	{	NULL,	0			},	// forEachContext
};

STATIC_ASSERT(SIZEOF_ARRAY(s_operands, Operand) == kTokenType_Max);

bool CanConvertOperand(Token_Type from, Token_Type to)
{
	if (from == to)
		return true;
	else if (from >= kTokenType_Invalid || to >= kTokenType_Invalid)
		return false;

	Operand* op = &s_operands[from];
	for (UInt32 i = 0; i < op->numRules; i++)
	{
		ConversionRule* rule = &op->rules[i];
		if (rule->to == to)
			return true;
	}

	return false;
}

// Operator
Token_Type Operator::GetResult(Token_Type lhs, Token_Type rhs)
{
	for (UInt32 i = 0; i < numRules; i++)
	{
		OperationRule* rule = &rules[i];
		if (CanConvertOperand(lhs, rule->lhs) && CanConvertOperand(rhs, rule->rhs))
			return rule->result;
		else if (!rule->bAsymmetric && CanConvertOperand(lhs, rule->rhs) && CanConvertOperand(rhs, rule->lhs))
			return rule->result;
	}

	return kTokenType_Invalid;
}

// ScriptToken
ScriptToken::ScriptToken() : type(kTokenType_Invalid), refIdx(0)
{
	value.num = 0;
}

ScriptToken::ScriptToken(bool boolean) : type(kTokenType_Boolean), refIdx(0)
{
	value.num = boolean ? 1 : 0;
}

ScriptToken::ScriptToken(double num) : type(kTokenType_Number), refIdx(0)
{
	value.num = num;
}

ScriptToken::ScriptToken(Script::RefVariable* refVar, UInt16 refIdx) : type(kTokenType_Ref), refIdx(refIdx)
{
	value.refVar = refVar;
}

ScriptToken::ScriptToken(std::string& str) : type(kTokenType_String), refIdx(0)
{
	value.str = str;
}

ScriptToken::ScriptToken(TESGlobal* global, UInt16 refIdx) : type(kTokenType_Global), refIdx(refIdx)
{
	value.global = global;
}

ScriptToken::ScriptToken(UInt32 id, Token_Type asType) : refIdx(0)
{
	switch (asType)
	{
	case kTokenType_Form:
		value.formID = id;
		type = kTokenType_Form;
		break;
#if OBLIVION
	case kTokenType_Array:
		value.arrID = id;
		type = (g_ArrayMap.Exists(id) || id == 0) ? kTokenType_Array : kTokenType_Invalid;
		break;
#endif
	default:
		type = kTokenType_Invalid;
	}
}

ScriptToken::ScriptToken(Operator* op) : type(kTokenType_Operator), refIdx(0)
{
	value.op = op;
}

ScriptToken::ScriptToken(Script::VariableInfo* varInfo, UInt16 refIdx, UInt32 varType) : refIdx(refIdx), variableType(varType)
{
	value.varInfo = varInfo;
	switch (varType)
	{
	case Script::eVarType_Array:
		type = kTokenType_ArrayVar;
		break;
	case Script::eVarType_String:
		type = kTokenType_StringVar;
		break;
	case Script::eVarType_Integer:
	case Script::eVarType_Float:
		type = kTokenType_NumericVar;
		break;
	case Script::eVarType_Ref:
		type = kTokenType_RefVar;
		break;
	default:
		type = kTokenType_Invalid;
	}
}

ScriptToken::ScriptToken(CommandInfo* cmdInfo, UInt16 refIdx) : type(kTokenType_Command), refIdx(refIdx)
{
	value.cmd = cmdInfo;
}

#if OBLIVION

ScriptToken* ScriptToken::CreateForEachContext(UInt32 srcID, UInt32 iterID, UInt32 varType)
{
	ForEachContextToken* foreachToken = new ForEachContextToken(srcID, iterID, varType);
	if (!foreachToken->IsGood())
	{
		delete foreachToken;
		foreachToken = NULL;
	}

	return foreachToken;
}

ForEachContextToken::ForEachContextToken(UInt32 srcID, UInt32 iterID, UInt32 varType)
{
	variableType = varType;

	if (varType == Script::eVarType_String)
	{
		if (!g_StringMap.Get(srcID) || !g_StringMap.Get(iterID))
		{
			type = kTokenType_Invalid;
			return;
		}
	}
	else if (varType == Script::eVarType_Array)
	{
		if (!g_ArrayMap.Exists(srcID) || !g_ArrayMap.Exists(iterID))
		{
			type = kTokenType_Invalid;
			return;
		}
	}
	else
	{
		type = kTokenType_Invalid;
		return;
	}

	type = kTokenType_ForEachContext;
	context.variableType = varType;
	context.sourceID = srcID;
	context.iteratorID = iterID;
}

ScriptToken::ScriptToken(ScriptEventList::Var* var) : refIdx(0), type(kTokenType_Variable)
{
	value.var = var;
}

ScriptToken* ScriptToken::ElementToken(ArrayID arr, ArrayKey* key)
{
	return key ? new ArrayElementToken(arr, key) : NULL;
}

ScriptToken* ScriptToken::CreateSliceToken(Slice* slice)
{
	return slice ? new SliceToken(slice) : NULL;
}

ArrayElementToken::ArrayElementToken(ArrayID arr, ArrayKey* _key)
{
	if (_key)
	{
		type = kTokenType_ArrayElement;
		key = *_key;
		value.arrID = arr;
	}
	else
		type = kTokenType_Invalid;
}

Token_Type ScriptToken::Read(ExpressionEvaluator* context)
{
	UInt8 typeCode = context->ReadByte();

	switch (typeCode)
	{
	case 'Z':
		type = kTokenType_Number;
		value.num = context->ReadFloat();
		break;
	case 'S':
		{
			type = kTokenType_String;
			value.str = context->ReadString();
			break;
		}
	case 'R':
		type = kTokenType_Ref;
		refIdx = context->Read16();
		value.refVar = context->script->GetVariable(refIdx);
		if (!value.refVar)
			type = kTokenType_Invalid;
		break;
	case 'G':
		{
			type = kTokenType_Global;
			refIdx = context->Read16();
			Script::RefVariable* refVar = context->script->GetVariable(refIdx);
			if (!refVar)
			{
				type = kTokenType_Invalid;
				break;
			}
			refVar->Resolve(context->eventList);
			value.global = OBLIVION_CAST(refVar->form, TESForm, TESGlobal);
			if (!value.global)
				type = kTokenType_Invalid;
			break;
		}
	case 'X':
		{
			type = kTokenType_Command;
			refIdx = context->Read16();
			UInt16 opcode = context->Read16();
			value.cmd = g_scriptCommands.GetByOpcode(opcode);
			if (!value.cmd)
				type = kTokenType_Invalid;
			break;
		}
	case 'V':
		{
			variableType = context->ReadByte();
			switch (variableType)
			{
			case Script::eVarType_Array:
				type = kTokenType_ArrayVar;
				break;
			case Script::eVarType_Integer:
			case Script::eVarType_Float:
				type = kTokenType_NumericVar;
				break;
			case Script::eVarType_Ref:
				type = kTokenType_RefVar;
				break;
			case Script::eVarType_String:
				type = kTokenType_StringVar;
				break;
			default:
				type = kTokenType_Invalid;
			}

			refIdx = context->Read16();

			ScriptEventList* eventList = context->eventList;
			if (refIdx)
			{
				Script::RefVariable* refVar = context->script->GetVariable(refIdx);
				if (refVar)
				{
					refVar->Resolve(context->eventList);
					if (refVar->form)
						eventList = EventListFromForm(refVar->form);
				}
			}

			UInt16 varIdx = context->Read16();
			value.var = NULL;
			if (eventList)
				value.var = eventList->GetVariable(varIdx);

			if (!value.var)
				type = kTokenType_Invalid;
			break;
		}
	default:
		{
			if (typeCode < kOpType_Max)
			{
				type = kTokenType_Operator;
				value.op = &s_operators[typeCode];
			}
			else
			{
				context->Error("Unexpected token type %d (%02x) encountered", typeCode, typeCode);
				type = kTokenType_Invalid;
			}
		}
	}

	return type;
}

void Slice::GetArrayBounds(ArrayKey& lo, ArrayKey& hi)
{
	if (bIsString)
	{
		lo = ArrayKey(m_lowerStr);
		hi = ArrayKey(m_upperStr);
	}
	else
	{
		lo = ArrayKey(m_lower);
		hi = ArrayKey(m_upper);
	}
}

#endif

// compiling typecodes to printable chars just makes verifying parser output much easier
static char TokenCodes[kTokenType_Max] =
{ 'Z', '!', 'S', '!', 'R', 'G', '!', '!', '!', 'X', 'V', 'V', 'V', 'V', 'V', '!', 'O' };

inline char TokenTypeToCode(Token_Type type)
{
	return type < kTokenType_Max ? TokenCodes[type] : 0;
}

bool ScriptToken::Write(ScriptLineBuffer* buf)
{
	if (type != kTokenType_Operator)
		buf->WriteByte(TokenTypeToCode(type));

	switch (type)
	{
	case kTokenType_Number:
		return buf->WriteFloat(value.num);
	case kTokenType_String:
		return buf->WriteString(value.str.c_str());
	case kTokenType_Ref:
	case kTokenType_Global:
		return buf->Write16(refIdx);
	case kTokenType_Command:
		buf->Write16(refIdx);
		return buf->Write16(value.cmd->opcode);
	case kTokenType_NumericVar:
	case kTokenType_RefVar:
	case kTokenType_StringVar:
	case kTokenType_ArrayVar:
		buf->WriteByte(variableType);
		buf->Write16(refIdx);
		return buf->Write16(value.varInfo->idx);
	case kTokenType_Operator:
		return buf->WriteByte(value.op->type);

	// the rest are run-time only
	default:
		return false;
	}
}

// ExpressionParser

void PrintCompiledCode(ScriptLineBuffer* buf)
{
#if _DEBUG
	std::string bytes;
	char byte[5];

	for (UInt32 i = 0; i < buf->dataOffset; i++)
	{
		if (isprint(buf->dataBuf[i]))
			sprintf_s(byte, 4, "%c", buf->dataBuf[i]);
		else
			sprintf_s(byte, 4, "%02X", buf->dataBuf[i]);

		bytes.append(byte);
		bytes.append(" ");
	}

	ShowCompilerError(buf, "COMPILER OUTPUT\n\n%s", bytes.c_str());
#endif
}

bool ExpressionParser::ParseArgs(ParamInfo* params, UInt32 numParams)
{
	// reserve space for UInt8 numargs at beginning of compiled code
	UInt8* numArgsPtr = m_lineBuf->dataBuf + m_lineBuf->dataOffset;
	m_lineBuf->dataOffset += 1;

	UInt8* dataStart = m_lineBuf->dataBuf + m_lineBuf->dataOffset;

	while (m_numArgsParsed < numParams && Offset() < m_len)
	{
		// reserve space to store expr length
		m_lineBuf->dataOffset += 2;

		Token_Type argType = ParseSubExpression(m_len - Offset());
		if (argType == kTokenType_Invalid)
			return false;
		else 		// is arg of expected type(s)? each bit in paramInfo->type corresponds to a Token_Type
		{
			bool bTypesMatch = false;
			if (params[m_numArgsParsed].typeID == kOBSEParamType_NoTypeCheck)
				bTypesMatch = true;
			else
			{
				for (UInt32 i = 0; i < kTokenType_Max; i++)
				{
					if (params[m_numArgsParsed].typeID & (1 << i))
					{
						Token_Type type = (Token_Type)(i);
						if (CanConvertOperand(argType, type))
						{
							bTypesMatch = true;
							break;
						}
					}
				}
			}

			if (!bTypesMatch)
			{
				ShowCompilerError(m_lineBuf, "Invalid expression for parameter %d. Expected %s", m_numArgsParsed + 1, params[m_numArgsParsed].typeStr);
				return false;
			}
		}

		m_argTypes[m_numArgsParsed++] = argType;

		// store expr length for this arg
		*((UInt16*)dataStart) = (m_lineBuf->dataBuf + m_lineBuf->dataOffset) - dataStart;
		dataStart = m_lineBuf->dataBuf + m_lineBuf->dataOffset;
	}

	if (Offset() < m_len)	// some leftover stuff in text
	{
		Message(kError_TooManyArgs);
		return false;
	}

	// did we get all required args?
	UInt32 numExpectedArgs = 0;
	for (UInt32 i = 0; i < numParams && !params[i].isOptional; i++)
		numExpectedArgs++;

	if (numExpectedArgs > m_numArgsParsed)
	{
		ParamInfo* missingParam = &params[m_numArgsParsed + 1];
		Message(kError_MissingParam, missingParam->typeStr, m_numArgsParsed + 2);
		return false;
	}

	*numArgsPtr = m_numArgsParsed;
	PrintCompiledCode(m_lineBuf);
	return true;
}

Token_Type ExpressionParser::Parse()
{
	UInt8* dataStart = m_lineBuf->dataBuf + m_lineBuf->dataOffset;
	m_lineBuf->dataOffset += 2;

	Token_Type result = ParseSubExpression(m_len);

	*((UInt16*)dataStart) = (m_lineBuf->dataBuf + m_lineBuf->dataOffset) - dataStart;

	PrintCompiledCode(m_lineBuf);
	return result;
}

static ErrOutput::Message s_expressionErrors[] =
{
	// errors
	{	"Could not parse this line."	},
	{	"Too many operators."	},
	{	"Too many operands.",	},
	{	"Mismatched brackets."	},
	{	"Invalid operands for operator %s."	},
	{	"Mismatched quotes."	},
	{	"Left of dot must be quest or persistent reference."	},
	{	"Unknown variable '%s'."	},
	{	"Expected string variable after '$'."	},
	{	"Cannot access variable on unscripted object '%s'."	},
	{	"More args provided than expected by command '%s'."	},
	{	"Commands '%s' must be called on a reference."		},
	{	"Missing required parameter '%s' for parameter #'%d'"	},

	// warnings
	{	"Unquoted argument '%s' will be treated as string by default. Check spelling if a form or variable was intended.", true	},

	// default
	{	"Undefined error."	}
};

ErrOutput::Message * ExpressionParser::s_Messages = s_expressionErrors;

void ExpressionParser::Message(UInt32 errorCode, ...)
{
	errorCode = errorCode > kError_Max ? kError_Max : errorCode;
	va_list args;
	va_start(args, errorCode);
	ErrOutput::Message* msg = &s_Messages[errorCode];
	if (msg->bCanDisable)
		g_ErrOut.vShow(s_Messages[errorCode], args);
	else		// prepend line # to message
	{
		char msgText[0x400];
		sprintf_s(msgText, sizeof(msgText), "Error line %d\n\n%s", m_lineBuf->lineNumber, msg->fmt);
		g_ErrOut.vShow(msgText, args);
	}

	va_end(args);
}

UInt32	ExpressionParser::MatchOpenBracket(Operator* openBracOp)
{
	char closingBrac = openBracOp->GetMatchedBracket();
	char openBrac = openBracOp->symbol[0];
	UInt32 openBracCount = 1;
	const char* text = Text();
	UInt32 i;
	for (i = Offset(); i < m_len && text[i]; i++)
	{
		if (text[i] == openBrac)
			openBracCount++;
		else if (text[i] == closingBrac)
			openBracCount--;

		if (openBracCount == 0)
			break;
	}

	return openBracCount ? -1 : i;
}

Token_Type ExpressionParser::ParseSubExpression(UInt32 exprLen)
{
	std::stack<Operator*> ops;
	std::stack<Token_Type> operands;

	UInt32 exprEnd = Offset() + exprLen;
	bool bLastTokenWasOperand = false;	// if this is true, we expect binary operator, else unary operator or an operand

	char ch;
	while (Offset() < exprEnd && (ch = Peek()))
	{
		if (isspace(ch))
		{
			Offset()++;
			continue;
		}

		Token_Type operandType = kTokenType_Invalid;

		// is it an operator?
		Operator* op = ParseOperator(bLastTokenWasOperand);
		if (op)
		{
			// if it's an open bracket, parse subexpression within
			if (op->IsOpenBracket())
			{
				if (op->numOperands)
				{
					// handles array subscript operator
					while (ops.size() && ops.top()->Precedes(op))
					{
						PopOperator(ops, operands);
					}
					ops.push(op);
				}

				UInt32 endBracPos = MatchOpenBracket(op);
				if (endBracPos == -1)
				{
					Message(kError_MismatchedBrackets);
					return kTokenType_Invalid;
				}

				// replace closing bracket with 0 to ensure subexpression doesn't try to read past end of expr
				m_lineBuf->paramText[endBracPos] = 0;

				operandType = ParseSubExpression(endBracPos - Offset());
				Offset() = endBracPos + 1;	// skip the closing bracket
				bLastTokenWasOperand = true;
			}
			else if (op->IsClosingBracket())
			{
				Message(kError_MismatchedBrackets);
				return kTokenType_Invalid;
			}
			else		// normal operator, handle or push
			{
				while (ops.size() && ops.top()->Precedes(op))
				{
					PopOperator(ops, operands);
				}

				ops.push(op);
				bLastTokenWasOperand = false;
				continue;
			}
		}
		else if (bLastTokenWasOperand)		// treat as arg delimiter?
			break;
		else	// must be an operand (or a syntax error)
		{
			ScriptToken operand = ParseOperand();

			// write it to postfix expression, we'll check validity below
			operand.Write(m_lineBuf);
			operandType = operand.type;

			// if command, parse it. also adjust operand type if return value of command is known
			if (operandType == kTokenType_Command)
			{
				CommandReturnType retnType = g_scriptCommands.GetReturnType(operand.value.cmd);
				if (retnType == kRetnType_String)		
					operandType = kTokenType_String;
				else if (retnType == kRetnType_Array)	
					operandType = kTokenType_Array;
				else if (retnType == kRetnType_Form)
					operandType = kTokenType_Form;

				bool bParsed = ParseFunctionCall(operand.value.cmd);
				if (!bParsed)
				{
					Message(kError_CantParse);
					return kTokenType_Invalid;
				}
			}
			bLastTokenWasOperand = true;
		}

		// operandType is an operand or result of a subexpression
		if (operandType == kTokenType_Invalid)
		{
			Message(kError_CantParse);
			return kTokenType_Invalid;
		}
		else
			operands.push(operandType);
	}

	// No more operands, clean off the operator stack
	while (ops.size())
	{
		if (PopOperator(ops, operands) == kTokenType_Invalid)
			return kTokenType_Invalid;
	}

	// done, make sure we've got a result
	if (ops.size() != 0)
	{
		Message(kError_TooManyOperators);
		return kTokenType_Invalid;
	}
	else if (operands.size() != 1)
	{
		Message(kError_TooManyOperands);
		return kTokenType_Invalid;
	}

	return operands.top();
}

Token_Type ExpressionParser::PopOperator(std::stack<Operator*> & ops, std::stack<Token_Type> & operands)
{
	Operator* topOp = ops.top();
	ops.pop();

	// pop the operands
	Token_Type lhType, rhType = kTokenType_Invalid;
	if (operands.size() < topOp->numOperands)
	{
		Message(kError_TooManyOperators);
		return kTokenType_Invalid;
	}

	switch (topOp->numOperands)
	{
	case 2:
		rhType = operands.top();
		operands.pop();
		// fall-through intentional
	case 1:
		lhType = operands.top();
		operands.pop();
		break;
	default:		// a paren or right bracket ended up on stack somehow
		Message(kError_CantParse);
		return kTokenType_Invalid;
	}

	// get result of operation
	Token_Type result = topOp->GetResult(lhType, rhType);
	if (result == kTokenType_Invalid)
	{
		Message(kError_InvalidOperands, topOp->symbol);
		return kTokenType_Invalid;
	}

	operands.push(result);

	// write operator to postfix expression
	ScriptToken(topOp).Write(m_lineBuf);
	return result;
}
					
Operator* ExpressionParser::ParseOperator(bool bExpectBinaryOperator)
{
	// if bExpectBinary true, we expect a binary operator or a closing paren
	// if false, we expect unary operator or an open paren

	// If this returns NULL when we expect a binary operator, it likely indicates end of arg
	// Commas can optionally be used to separate expressions as args

	std::vector<Operator*> ops;	// a list of possible matches
	Operator* op = NULL;

	// check first character
	char ch = Peek();
	if (ch == ',')		// arg expression delimiter
	{
		Offset() += 1;
		return NULL;
	}

	for (UInt32 i = 0; i < kOpType_Max; i++)
	{
		Operator* curOp = &s_operators[i];
		if (bExpectBinaryOperator)
		{
			if (!curOp->IsBinary() && !curOp->IsClosingBracket())
				continue;
		}
		else if (!curOp->IsUnary() && !curOp->IsOpenBracket())
			continue;
		if (ch == curOp->symbol[0])
			ops.push_back(curOp);
	}

	ch = Peek(Offset() + 1);
	if (ch && ispunct(ch))		// possibly a two-character operator, check second char
	{
		std::vector<Operator*>::iterator iter = ops.begin();
		while (iter != ops.end())
		{
			Operator* cur = *iter;
			if (cur->symbol[1] == ch)		// definite match
			{
				op = cur;
				break;
			}
			else if (cur->symbol[1] != 0)	// remove two-character operators which don't match
				iter = ops.erase(iter);
			else
				iter++;
		}
	}
	else			// definitely single-character
	{
		for (UInt32 i = 0; i < ops.size(); i++)
		{
			if (ops[i]->symbol[1] == 0)
			{
				op = ops[i];
				break;
			}
		}
	}

	if (!op && ops.size() == 1)
		op = ops[0];

	if (op)
		Offset() += strlen(op->symbol);

	return op;
}	

// format a string argument
static void FormatString(std::string& str)
{
	UInt32 pos = 0;

	while (((pos = str.find('%', pos)) != -1) && pos < str.length() - 1)
	{
		char toInsert = 0;
		switch (str[pos + 1])
		{
		case '%':
			pos += 2;
			continue;
		case 'r':
		case 'R':
			toInsert = '\n';
			break;
		case 'q':
		case 'Q':
			toInsert = '"';
			break;
		default:
			pos += 1;
			continue;
		}
		
		str.insert(pos, 1, toInsert);	// insert char at current pos
		str.erase(pos + 1, 2);			// erase format specifier
		pos += 1;
	}
}

ScriptToken ExpressionParser::ParseOperand()
{
	char firstChar = Peek();
	bool bExpectStringVar = false;

	if (!firstChar)
	{
		Message(kError_CantParse);
		return ScriptToken::Bad();
	}
	else if (firstChar == '"')			// string literal
	{
		Offset()++;
		const char* endQuotePtr = strchr(CurText(), '"');
		if (!endQuotePtr)
		{
			Message(kError_MismatchedQuotes);
			return ScriptToken::Bad();
		}
		else
		{
			std::string strLit(CurText(), endQuotePtr - CurText());
			Offset() = endQuotePtr - Text() + 1;
			FormatString(strLit);
			return ScriptToken(strLit);
		}
	}
	else if (firstChar == '$')	// string vars passed to vanilla cmds as '$var'; not necessary here but allowed for consistency
	{
		bExpectStringVar = true;
		Offset()++;
	}

	std::string token = GetCurToken();
	std::string refToken = token;

	// try to convert to a number
	char* leftOvers = NULL;
	double dVal = strtod(token.c_str(), &leftOvers);
	if (*leftOvers == 0)	// entire string parsed as a double
		return ScriptToken(dVal);

	// check for a calling object
	Script::RefVariable* callingObj = NULL;
	UInt16 refIdx = 0;
	UInt32 dotPos = token.find('.');
	if (dotPos != -1)
	{
		refToken = token.substr(0, dotPos);
		token = token.substr(dotPos + 1);
	}

	// "player" can be base object or ref. Assume base object unless called with dot syntax
	if (!_stricmp(refToken.c_str(), "player") && dotPos != -1)
		refToken = "playerRef";

	Script::RefVariable* refVar = m_scriptBuf->ResolveRef(refToken.c_str());
	if (dotPos != -1 && !refVar)
	{
		Message(kError_InvalidDotSyntax);
		return ScriptToken::Bad();
	}
	else if (refVar)
		refIdx = m_scriptBuf->GetRefIdx(refVar);

	if (refVar)
	{
		if (dotPos == -1)
		{
			if (refVar->varIdx)			// it's a variable
				return ScriptToken(m_scriptBuf->vars.GetVariableByName(refVar->name.m_data), 0, Script::eVarType_Ref);
			else if (refVar->form && refVar->form->typeID == kFormType_Global)
				return ScriptToken((TESGlobal*)refVar->form, refIdx);
			else						// literal reference to a form
				return ScriptToken(refVar, refIdx);
		}
		else if (refVar->form && refVar->form->typeID != kFormType_REFR && refVar->form->typeID != kFormType_Quest)
		{
			Message(kError_InvalidDotSyntax);
			return ScriptToken::Bad();
		}
	}

	// command?
	if (!bExpectStringVar)
	{
		CommandInfo* cmdInfo = g_scriptCommands.GetByName(token.c_str());
		if (cmdInfo)
		{
			// if quest script, check that calling obj supplied for cmds requiring it
			if (m_scriptBuf->scriptType == Script::eType_Quest && cmdInfo->unk0 && !refVar)
			{
				Message(kError_RefRequired, cmdInfo->longName);
				return ScriptToken::Bad();
			}
			if (refVar && refVar->form && refVar->form->typeID != kFormType_REFR)	// make sure we're calling it on a reference
				return ScriptToken::Bad();

			return ScriptToken(cmdInfo, refIdx);
		}
	}

	// variable?
	Script::VariableInfo* varInfo = LookupVariable(token.c_str(), refVar);
	if (!varInfo && dotPos != -1)
	{
		Message(kError_CantFindVariable, token.c_str());
		return ScriptToken::Bad();
	}
	else if (varInfo)
	{
		ScriptToken varToken(varInfo, refIdx, m_scriptBuf->GetVariableType(varInfo, refVar));
		if (bExpectStringVar && varToken.variableType != Script::eVarType_String)
		{
			Message(kError_ExpectedStringVariable);
			return ScriptToken::Bad();
		}
		else
			return varToken;
	}
	else if (bExpectStringVar)
	{
		Message(kError_ExpectedStringVariable);
		return ScriptToken::Bad();
	}

	if (refVar != NULL)
	{
		Message(kError_InvalidDotSyntax);
		return ScriptToken::Bad();
	}

	// anything else that makes it this far is treated as string
	Message(kWarning_UnquotedString, token.c_str());
	FormatString(token);
	return ScriptToken(token);
}

bool ExpressionParser::ParseFunctionCall(CommandInfo* cmdInfo)
{
	// trick Cmd_Parse into thinking it is parsing the only command on this line
	UInt32 oldOffset = Offset();
	UInt32 oldOpcode = m_lineBuf->cmdOpcode;

	// reserve space to record total # of bytes used for cmd args
	UInt16 oldDataOffset = m_lineBuf->dataOffset;
	UInt16* argsLenPtr = (UInt16*)(m_lineBuf->dataBuf + m_lineBuf->dataOffset);
	m_lineBuf->dataOffset += 2;

	// save the original paramText, overwrite with params following this function call
	char oldLineText[0x200];
	memcpy(oldLineText, m_lineBuf->paramText, 0x200);
	memset(m_lineBuf->paramText, 0, 0x200);
	memcpy(m_lineBuf->paramText, oldLineText + oldOffset, 0x200 - oldOffset);

	// rig ScriptLineBuffer fields
	m_lineBuf->cmdOpcode = cmdInfo->opcode;
	m_lineBuf->callingRef = NULL;
	m_lineBuf->lineOffset = 0;
	
	// parse the command if numParams > 0
	bool bParsed = ParseNestedFunction(cmdInfo, m_lineBuf, m_scriptBuf);

	// restore original state, save args length
	m_lineBuf->lineOffset += oldOffset;		// skip any text used as command arguments
	*argsLenPtr = m_lineBuf->dataOffset - oldDataOffset;
	m_lineBuf->cmdOpcode = oldOpcode;
	memcpy(m_lineBuf->paramText, oldLineText, 0x200);

	return bParsed;
}

Script::VariableInfo* ExpressionParser::LookupVariable(const char* varName, Script::RefVariable* refVar)
{
	Script::VarInfoEntry* vars = &m_scriptBuf->vars;

	if (refVar)
	{
		if (!refVar->form)	// it's a ref variable, can't get var
			return NULL;

		Script* script = GetScriptFromForm(refVar->form);
		if (script)
			vars = &script->varList;
		else		// not a scripted object
			return NULL;
	}

	if (!vars)
		return NULL;

	return vars->GetVariableByName(varName);
}

std::string ExpressionParser::GetCurToken()
{
	char ch;
	const char* tokStart = CurText();
	while ((ch = Peek()))
	{
		if (isspace(ch) || (ispunct(ch) && ch != '_' && ch != '.'))
			break;
		Offset()++;
	}

	return std::string(tokStart, CurText() - tokStart);
}

// error routines

#if OBLIVION

void ShowRuntimeError(Script* script, const char* fmt, ...)
{
	char errorHeader[0x400];
	sprintf_s(errorHeader, 0x400, "Error in script %08x", script ? script->refID : 0);

	va_list args;
	va_start(args, fmt);

	char	errorMsg[0x400];
	vsprintf_s(errorMsg, 0x400, fmt, args);

	Console_Print(errorHeader);
	_MESSAGE(errorHeader);
	Console_Print(errorMsg);
	_MESSAGE(errorMsg);

	va_end(args);
}

UInt8 ExpressionEvaluator::ReadByte()
{
	UInt8 byte = *Data();
	Data()++;
	return byte;
}

UInt16 ExpressionEvaluator::Read16()
{
	UInt16 data = *((UInt16*)Data());
	Data() += 2;
	return data;
}

double ExpressionEvaluator::ReadFloat()
{
	double data = *((double*)Data());
	Data() += sizeof(double);
	return data;
}

std::string ExpressionEvaluator::ReadString()
{
	UInt16 len = Read16();
	char* buf = new char[len + 1];
	memcpy(buf, Data(), len);
	buf[len] = 0;
	std::string str = buf;
	Data() += len;
	delete buf;
	return str;
}

CommandReturnType ExpressionEvaluator::s_expectedReturnType = kRetnType_Default;

ExpressionEvaluator::ExpressionEvaluator(COMMAND_ARGS) : m_opcodeOffsetPtr(opcodeOffsetPtr), m_result(result), 
	m_thisObj(thisObj), script(scriptObj), eventList(eventList), m_params(paramInfo), m_numArgsExtracted(0)
{
	s_numInstances++;
	memset(m_args, 0, sizeof(m_args));

	m_scriptData = (UInt8*)arg1;
	m_data = m_scriptData + *m_opcodeOffsetPtr;
	m_containingObj = (TESObjectREFR*)arg3;
}

ExpressionEvaluator::~ExpressionEvaluator()
{
	s_numInstances--;
	for (UInt32 i = 0; i < kMaxArgs; i++)
		delete m_args[i];
}

bool ExpressionEvaluator::ExtractArgs()
{
	UInt32 numArgs = ReadByte();
	UInt32 curArg = 0;

	while (curArg < numArgs)
	{
		ScriptToken* arg = Evaluate();
		if (!arg)
			break;

		m_args[curArg++] = arg;
	}

	// clean up any temporary vars created during evaluation
	// This can't occur here - do it outside of script execution
//	g_ArrayMap.Clean();
//	g_StringMap.Clean();

	if (numArgs == curArg)			// all args extracted
	{
		m_numArgsExtracted = curArg;
		return true;
	}
	else
		return false;
}

ScriptToken* ExpressionEvaluator::Evaluate()
{
	std::stack<ScriptToken*> operands;
	
	UInt16 argLen = Read16();
	UInt8* endData = Data() + argLen - sizeof(UInt16);
	while (Data() < endData)
	{
		ScriptToken* curToken = new ScriptToken;
		if (curToken->Read(this) == kTokenType_Invalid)
		{
			delete curToken;
			break;
		}

		if (curToken->type == kTokenType_Command)
		{
			// execute the command
			CommandInfo* cmdInfo = curToken->value.cmd;
			if (!cmdInfo)
			{
				delete curToken;
				break;
			}

			TESObjectREFR* callingObj = m_thisObj;
			Script::RefVariable* callingRef = script->GetVariable(curToken->refIdx);
			if (callingRef)
			{
				callingRef->Resolve(eventList);
				if (callingRef->form && callingRef->form->IsReference())
					callingObj = OBLIVION_CAST(callingRef->form, TESForm, TESObjectREFR);
				else
				{
					delete curToken;
					Error("Attempting to call a function on a NULL reference or base object");
					break;
				}
			}

			TESObjectREFR* contObj = callingRef ? NULL : m_containingObj;
			double cmdResult = 0;

			UInt16 argsLen = Read16();
			UInt32 numBytesRead = 0;
			UInt8* scrData = Data();
			bool bExecuted = cmdInfo->unk1(cmdInfo->params, scrData, callingObj, (UInt32)contObj, script, eventList, &cmdResult, &numBytesRead);
			if (!bExecuted)
			{
				delete curToken;
				Error("Command %s failed to execute", cmdInfo->longName);
				break;
			}

			Data() += argsLen - 2;
			curToken->value.num = cmdResult;

			// adjust token type if we know command return type
			CommandReturnType retnType = g_scriptCommands.GetReturnType(cmdInfo);
			if (retnType == kRetnType_ArrayIndex)	// return type ambiguous, cmd will inform us of type to expect
				retnType = GetExpectedReturnType();

			if (retnType == kRetnType_String)
			{
				curToken->type = kTokenType_String;
				StringVar* strVar = g_StringMap.Get(cmdResult);
				curToken->value.str = strVar ? strVar->String() : "";
			}
			else if (retnType == kRetnType_Array)
			{
				if (g_ArrayMap.Exists(cmdResult) || !cmdResult)	// cmds can return arrayID '0', not necessarily an error
				{
					curToken->type = kTokenType_Array;
					curToken->value.arrID = cmdResult;
				}
				else
				{
					delete curToken;
					Error("A command returned an invalid array");
					break;
				}
			}
			else if (retnType == kRetnType_Form)
			{
				curToken->type = kTokenType_Form;
				curToken->value.formID = *((UInt64*)&cmdResult);
			}
		}

		if (!(curToken->type == kTokenType_Operator))
			operands.push(curToken);
		else
		{
			Operator* op = curToken->value.op;
			ScriptToken* lhOperand, *rhOperand = NULL;

			if (op->numOperands > operands.size())
			{
				delete curToken;
				Error("Too few operands for operator %s", op->symbol);
				break;
			}

			switch (op->numOperands)
			{
			case 2:
				rhOperand = operands.top();
				operands.pop();
			case 1:
				lhOperand = operands.top();
				operands.pop();
			}

			ScriptToken* opResult = op->Evaluate(lhOperand, rhOperand, this);
			delete lhOperand;
			delete rhOperand;
			delete curToken;

			if (!opResult)
			{
				Error("Operator %s failed to evaluate to a valid result", op->symbol);
				break;
			}

			operands.push(opResult);
		}
	}

	// adjust opcode offset ptr (important for recursive calls to Evaluate()
	*m_opcodeOffsetPtr = m_data - m_scriptData;

	if (operands.size() != 1)		// should have one operand remaining - result of expression
	{
		Error("An expression failed to evaluate to a valid result");
		while (operands.size())
		{
			delete operands.top();
			operands.pop();
		}
		return NULL;
	}

	return operands.top();
}

ConversionRule* GetConversionRule(Token_Type from, Token_Type to)
{
	if (from == to)
		return NULL;

	Operand op = s_operands[from];
	for (UInt32 i = 0; i < op.numRules; i++)
	{
		ConversionRule* rule = &op.rules[i];
		if (!rule->convert)
			continue;
		if (rule->to == to)
			return rule;
	}

	return NULL;
}

ScriptToken* Operator::Evaluate(ScriptToken* lhs, ScriptToken* rhs, ExpressionEvaluator* context)
{
	// ###TODO: much of this could be collapsed
	if (numOperands == 0)	// how'd we get here?
	{
		context->Error("Attempting to evaluate %s but this operator takes no operands", this->symbol);
		return NULL;
	}
	else if (numOperands == 1)
	{
		for (UInt32 i = 0; i < numRules; i++)
		{
			ScriptToken convertedOperand;
			ScriptToken* operand = NULL;

			OperationRule* rule = &rules[i];
			if (!rule->eval)
				continue;

			if (lhs->type == rule->lhs)
				operand = lhs;
			else
			{
				ConversionRule* conv = GetConversionRule(lhs->type, rule->lhs);
				if (!conv)
					continue;
				
				convertedOperand = conv->convert(lhs, context);
				if (convertedOperand.type != kTokenType_Invalid)
					operand = &convertedOperand;
			}

			if (operand)
				return rule->eval(type, operand, NULL, context);
		}

		return NULL;
	}

	for (UInt32 i = 0; i < numRules; i++)
	{
		ScriptToken		lhConverted, rhConverted;
		ScriptToken*	lhOperand = NULL;
		ScriptToken*	rhOperand = NULL;
		ConversionRule* lhRule = NULL;
		ConversionRule* rhRule = NULL;

		OperationRule* rule = &rules[i];
		if (!rule->eval)
			continue;
	
		// ###TODO: collapse this and (!bAsymmetric) bit below
		lhRule = GetConversionRule(lhs->type, rule->lhs);
		rhRule = GetConversionRule(rhs->type, rule->rhs);
		if ((lhRule || lhs->type == rule->lhs) && (rhRule || rhs->type == rule->rhs))
		{
			if (lhRule)
			{
				lhConverted = lhRule->convert(lhs, context);
				if (lhConverted.type != kTokenType_Invalid)
					lhOperand = &lhConverted;
			}
			else
				lhOperand = lhs;
		
			if (rhRule)
			{
				rhConverted = rhRule->convert(rhs, context);
				if (rhConverted.type != kTokenType_Invalid)
					rhOperand = &rhConverted;
			}
			else
				rhOperand = rhs;
		
			if (lhOperand && rhOperand)
				return rule->eval(type, lhOperand, rhOperand, context);
		}

		if (!rule->bAsymmetric)
		{
			lhRule = GetConversionRule(lhs->type, rule->rhs);
			rhRule = GetConversionRule(rhs->type, rule->lhs);
			if ((lhRule || lhs->type == rule->rhs) && (rhRule || rhs->type == rule->lhs))
			{
				if (lhRule)
				{
					lhConverted = lhRule->convert(lhs, context);
					if (lhConverted.type != kTokenType_Invalid)
						lhOperand = &lhConverted;
				}
				else
					lhOperand = lhs;

				if (rhRule)
				{
					rhConverted = rhRule->convert(rhs, context);
					if (rhConverted.type != kTokenType_Invalid)
						rhOperand = &rhConverted;
				}
				else
					rhOperand = rhs;

				if (lhOperand && rhOperand)
					return rule->eval(type, lhOperand, rhOperand, context);
			}
		}
	}

	return NULL;
}

ScriptToken ScriptToken::ConvertTo(Token_Type to, ExpressionEvaluator* context)
{
	if (type == to)
		return *this;

	ConversionRule* rule = GetConversionRule(type, to);
	if (!rule)
		return ScriptToken::Bad();
	
	ScriptToken result = rule->convert(this, context);
	if (result.type == to)
		return result;

	return ScriptToken::Bad();
}

UInt32 ExpressionEvaluator::s_numInstances = 0;

#else			// CS only

static enum BlockType
{
	kBlockType_Invalid	= 0,
	kBlockType_ScriptBlock,
	kBlockType_Loop,
	kBlockType_If
};

struct Block
{
	enum {
		kFunction_Open		= 1,
		kFunction_Terminate	= 2,
		kFunction_Dual		= kFunction_Open | kFunction_Terminate
	};

	const char	* keyword;
	BlockType	type;
	UInt8		function;

	bool IsOpener() { return (function & kFunction_Open) == kFunction_Open; }
	bool IsTerminator() { return (function & kFunction_Terminate) == kFunction_Terminate; }
};

struct BlockInfo
{
	BlockType	type;
	UInt32		scriptLine;
};

static Block s_blocks[] =
{
	{	"begin",	kBlockType_ScriptBlock,	Block::kFunction_Open	},
	{	"end",		kBlockType_ScriptBlock,	Block::kFunction_Terminate	},
	{	"while",	kBlockType_Loop,		Block::kFunction_Open	},
	{	"foreach",	kBlockType_Loop,		Block::kFunction_Open	},
	{	"loop",		kBlockType_Loop,		Block::kFunction_Terminate	},
	{	"if",		kBlockType_If,			Block::kFunction_Open	},
	{	"elseif",	kBlockType_If,			Block::kFunction_Dual	},
	{	"else",		kBlockType_If,			Block::kFunction_Dual	},
	{	"endif",	kBlockType_If,			Block::kFunction_Terminate	},
};

static UInt32 s_numBlocks = SIZEOF_ARRAY(s_blocks, Block);

// Preprocessor
//	is used to check loop integrity, syntax before script is compiled
class Preprocessor
{
private:
	static std::string	s_delims;
	static ErrOutput::Message	s_Warning_SetStringVar;

	ScriptBuffer		* m_buf;
	UInt32				m_loopDepth;
	std::string			m_curLineText;
	UInt32				m_curLineNo;
	UInt32				m_curBlockStartingLineNo;
	std::string			m_scriptText;
	UInt32				m_scriptTextOffset;

	bool		ProcessBlock(BlockType blockType);
	bool		AdvanceLine();
	const char	* BlockTypeAsString(BlockType type);
public:
	Preprocessor(ScriptBuffer* buf);

	bool Process();		// returns false if an error is detected
};

std::string Preprocessor::s_delims = " \t\r\n(;";

ErrOutput::Message Preprocessor::s_Warning_SetStringVar =
{
	"Warning line %d:\nUsing 'set' to assign to a string_var is supported, but using 'Let' instead is recommended.", true
};

const char* Preprocessor::BlockTypeAsString(BlockType type)
{
	switch (type)
	{
	case kBlockType_ScriptBlock:
		return "Begin/End";
	case kBlockType_Loop:
		return "Loop";
	case kBlockType_If:
		return "If/EndIf";
	default:
		return "Unknown block type";
	}
}

Preprocessor::Preprocessor(ScriptBuffer* buf) : m_buf(buf), m_loopDepth(0), m_curLineText(""), m_curLineNo(0),
	m_curBlockStartingLineNo(1), m_scriptText(buf->scriptText), m_scriptTextOffset(0)
{
	AdvanceLine();
}

bool Preprocessor::AdvanceLine()
{
	if (m_scriptTextOffset >= m_scriptText.length())
		return false;

	m_curLineNo++;

	UInt32 endPos = m_scriptText.find("\r\n", m_scriptTextOffset);
	if (endPos == -1)						// last line, no CRLF
	{
		m_curLineText = m_scriptText.substr(m_scriptTextOffset, m_scriptText.length() - m_scriptTextOffset);
		m_scriptTextOffset = m_scriptText.length();
		return true;
	}
	else if (m_scriptTextOffset == endPos)		// empty line
	{
		m_scriptTextOffset += 2;
		return AdvanceLine();
	}
	else									// line contains text
	{
		m_curLineText = m_scriptText.substr(m_scriptTextOffset, endPos - m_scriptTextOffset);

		// strip comments
		for (UInt32 i = 0; i < m_curLineText.length(); i++)
		{
			if (m_curLineText[i] == '"')
			{
				i = m_curLineText.find(i, '"');
				if (i == -1)		// mismatched quotes, CS compiler will catch
					break;
				else
					i++;
			}
			else if (m_curLineText[i] == ';')
			{
				m_curLineText = m_curLineText.substr(0, i);
				break;
			}
		}

		m_scriptTextOffset = endPos + 2;
		return true;
	}
}

bool Preprocessor::Process()
{
	std::string token;
	std::stack<BlockType> blockStack;

	bool bContinue = true;
	while (bContinue)
	{
		Tokenizer tokens(m_curLineText.c_str(), s_delims.c_str());
		if (tokens.NextToken(token) == -1)		// empty line
		{
			bContinue = AdvanceLine();
			continue;
		}
		
		const char* tok = token.c_str();
		bool bIsBlockKeyword = false;
		for (UInt32 i = 0; i < s_numBlocks; i++)
		{
			Block* cur = &s_blocks[i];
			if (!_stricmp(tok, cur->keyword))
			{
				bIsBlockKeyword = true;
				if (cur->IsTerminator())
				{
					if (!blockStack.size() || blockStack.top() != cur->type)
					{
						const char* blockStr = BlockTypeAsString(blockStack.size() ? blockStack.top() : cur->type);
						g_ErrOut.Show("Invalid %s block structure on line %d.", blockStr, m_curLineNo);
						return false;
					}
					
					blockStack.pop();
					if (cur->type == kBlockType_Loop)
						m_loopDepth--;
				}

				if (cur->IsOpener())
				{
					blockStack.push(cur->type);
					if (cur->type == kBlockType_Loop)
						m_loopDepth++;
				}
			}
		}
		
		if (!bIsBlockKeyword)
		{
			if (!_stricmp(tok, "continue") || !_stricmp(tok, "break"))
			{
				if (!m_loopDepth)
				{
					g_ErrOut.Show("Error line %d:\nFunction %s must be called from within a loop.", m_curLineNo, tok);
					return false;
				}
			}
			else if (!_stricmp(tok, "set"))
			{
				std::string varToken;
				if (tokens.NextToken(varToken) != -1)
				{
					std::string varName = varToken;
					const char* scriptText = m_buf->scriptText;

					UInt32 dotPos = varToken.find('.');
					if (dotPos != -1)
					{
						scriptText = NULL;
						TESForm* refForm = GetFormByID(varToken.substr(0, dotPos).c_str());
						if (refForm)
						{
							Script* refScript = GetScriptFromForm(refForm);
							if (refScript)
								scriptText = refScript->text;
						}
						varName = varToken.substr(dotPos + 1);
					}

					if (scriptText)
					{
						UInt32 varType = GetDeclaredVariableType(varName.c_str(), scriptText);
						if (varType == Script::eVarType_Array)
						{
							g_ErrOut.Show("Error line %d:\nSet may not be used to assign to an array variable", m_curLineNo);
							return false;
						}
						else if (varType == Script::eVarType_String && 0)	// don't currently need this
						{
							g_ErrOut.Show(s_Warning_SetStringVar, m_curLineNo); 
						}
					}
				}
			}
			else
			{
				// ###TODO: check for ResetAllVariables
				const char* scriptText = m_buf->scriptText;
			}
		}

		if (bContinue)
			bContinue = AdvanceLine();
	}

	if (blockStack.size())
	{
		g_ErrOut.Show("Error: Mismatched block structure.");
		return false;
	}

	return true;
}

bool PrecompileScript(ScriptBuffer* buf)
{
	return Preprocessor(buf).Process();
}

#endif

bool Cmd_Expression_Parse(UInt32 numParams, ParamInfo* paramInfo, ScriptLineBuffer* lineBuf, ScriptBuffer* scriptBuf)
{
#if OBLIVION
	Console_Print("This command cannot be called from the console.");
	return false;
#endif

	ExpressionParser parser(scriptBuf, lineBuf);
	return (parser.ParseArgs(paramInfo, numParams));
}
