//	VDCompiler - Custom shader video filter for VirtualDub
//	Copyright (C) 2007 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "stdafx.h"
#include <math.h>
#include <vd2/system/vdstl.h>
#include <vd2/system/file.h>
#include <vd2/system/text.h>
#include <vd2/system/VDString.h>
#include <vd2/system/binary.h>
#include "filecreator.h"

using namespace nsVDCompilerTokens;

namespace {
	uint32 Checksum(const char *s, int len) {
		uint32 sum = 0;

		while(len--)
			sum += *s++;

		return sum;
	}
}

void VDCompilerWriteLogOutputF(IVDCompilerLogOutput& out, const char *format ...) {
	char buf[3072];
	va_list val;

	va_start(val, format);
	int len = _vsnprintf(buf, 3071, format, val);
	va_end(val);

	if ((unsigned)len <= 3071) {
		buf[len] = 0;
		out.WriteLogOutput(buf);
	}
}

void VDCompilerLexer::Init(const char *src, size_t len, const char *context, IVDCompilerLogOutput *pOutput) {
	mpSrc = src;
	mpSrcEnd = src + len;
	mpSrcLineStart = src;
	mpToken = src;
	mpTokenLineStart = src;
	mpContext = context;
	mLineNo = 1;
	mTokenLength = 0;
	mbError = false;
	mpOutput = pOutput;
}

bool VDCompilerLexer::GetErrorInfo(VDCompilerErrorInfo& errorInfo) {
	if (!mbError)
		return false;

	errorInfo = mErrorInfo;
	return true;
}

void VDCompilerLexer::AddToken(int token, const char *s) {
	int len = (int)strlen(s);
	uint32 checksum = Checksum(s, len);
	Keyword k;

	k.len = len;
	k.text = s;
	k.token = token;

	mKeywords.insert(Keywords::value_type(checksum, k));

}

int VDCompilerLexer::Token() {
	bool inComment = false;
	char c;

	// parse out whitespace and comments
	for(;;) {
		if (mpSrc == mpSrcEnd) {
			mpTokenLineStart = mpSrcLineStart;
			mpToken = mpSrc;
			mTokenLength = 0;
			return 0;
		}

		c = *mpSrc++;
		if (c == ' ' || c == '\t')
			continue;

		if (c == '\n' || c == '\r') {
			if (mpSrc != mpSrcEnd) {
				char d = *mpSrc;
				if ((c ^ d) == ('\n' ^ '\r'))
					++mpSrc;
			}
			++mLineNo;
			mpSrcLineStart = mpSrc;
			continue;
		}

		if (inComment) {
			if (c == '*' && mpSrc != mpSrcEnd && *mpSrc == '/') {
				++mpSrc;

				inComment = false;
			}
		} else {
			if (c == '/' && mpSrc != mpSrcEnd) {
				char d = *mpSrc;
				if (d == '/') {				// C++-style comment
					++mpSrc;
					while(mpSrc != mpSrcEnd) {
						c = *mpSrc;

						if (c == '\n' || c == '\r')
							break;

						++mpSrc;
					}
					continue;
				} else if (d == '*') {		// C-style comment
					++mpSrc;

					inComment = true;
					continue;
				}
			}

			break;
		}
	}

	mpToken = mpSrc - 1;
	mpTokenLineStart = mpSrcLineStart;

	// check for numeric tokens
	if ((unsigned char)(c - '0') < 10) {
		bool overflow = false;
		int value = c - '0';

		if (!value && mpSrc != mpSrcEnd && (*mpSrc == 'x' || *mpSrc == 'X')) {
			++mpSrc;

			int digits = 0;
			mIntVal = 0;
			while(mpSrc != mpSrcEnd) {
				c = *mpSrc;
				if (!isxdigit((unsigned char)c))
					break;
				++mpSrc;

				c = (c - '0') & 0x1f;
				if (c >= 10)
					c -= 7;

				mIntVal = (mIntVal << 4) + c;
				++digits;
			}

			if (!digits) {
				SetError("Invalid hex constant");
				return 0;
			}

			return kTokenInt;
		}

		while(mpSrc != mpSrcEnd) {
			c = *mpSrc;

			if (c == '.') {
				double fvalue = 0;

				for(const char *s = mpToken; s != mpSrc; ++s)
					fvalue = fvalue * 10.0 + (float)(*s - '0');

				++mpSrc;

				double scale = 0.1;
				while(mpSrc != mpSrcEnd) {
					c = *mpSrc;

					int digit = (c - '0');
					if ((unsigned)digit >= 10)
						break;

					++mpSrc;
					fvalue += scale * (float)digit;
					scale *= 0.1;
				}

				if (mpSrc != mpSrcEnd) {
					c = *mpSrc;
					if (c == 'e' || c == 'E') {
						++mpSrc;

						const char *pExpBase = mpSrc;
						int exp = 0;

						while(mpSrc != mpSrcEnd) {
							c = *mpSrc;

							int digit = (c - '0');
							if ((unsigned)digit >= 10)
								break;

							exp = (exp*10) + digit;

							++mpSrc;
						}

						if (mpSrc == pExpBase) {
							SetError("Invalid real number");
							return 0;
						}

						fvalue *= pow(10.0, exp);
					}
				}

				if (mpSrc != mpSrcEnd) {
					c = *mpSrc;
					if (c == 'f' || c == 'F')
						++mpSrc;
				}

				mFloatVal = (float)fvalue;
				mTokenLength = mpSrc - mpToken;
				return kTokenFloat;
			}

			int digit = (c - '0');
			if ((unsigned)digit >= 10)
				break;
			++mpSrc;

			if (value > 214748364)
				overflow = true;

			value *= 10;

			if (value > 2147483647 - digit)
				overflow = true;

			value += digit;
		
		}

		if (overflow) {
			SetError("Integer literal too large");
			return 0;
		}

		mIntVal = value;
		mTokenLength = mpSrc - mpToken;
		return kTokenInt;
	}

	// check for identifiers
	if (c == '_' || (unsigned char)((c & 0xdf) - 'A') < 26) {
		while(mpSrc != mpSrcEnd) {
			c = *mpSrc++;

			if (c != '_' && (unsigned char)((c & 0xdf) - 'A') >= 26 && (unsigned char)(c - '0') >= 10) {
				--mpSrc;
				break;
			}
		}

		mTokenLength = mpSrc - mpToken;

		uint32 checksum = Checksum(mpToken, mTokenLength);

		std::pair<Keywords::const_iterator, Keywords::const_iterator> range(mKeywords.equal_range(checksum));

		for(; range.first != range.second; ++range.first)
		{
			const Keyword& kw = range.first->second;

			if (kw.len == mTokenLength && !memcmp(kw.text, mpToken, mTokenLength))
				return kw.token;
		}

		return kTokenIdent;
	}

	// check for single character
	if (c == '\'') {
		if (mpSrc == mpSrcEnd) {
			SetError("End of file encountered in character literal");
			return 0;
		}

		c = *mpSrc++;
		bool escaped = false;
		if (c == '\\') {
			escaped = true;
			if (mpSrc == mpSrcEnd) {
				SetError("End of file encountered in character literal");
				return 0;
			}
			c = *mpSrc++;
		} else {
			if (c == '\'') {
				SetError("Empty character literal");
				return 0;
			}
		}

		mIntVal = (int)(uint8)c;

		if (mpSrc == mpSrcEnd) {
			SetError("End of file encountered in character literal");
			return 0;
		}

		c = *mpSrc++;
		if (c != '\'') {
			SetError("Expected \' at end of character literal");
			return 0;
		}

		mTokenLength = mpSrc - mpToken;
		return kTokenInt;
	}

	// check for string
	if (c == '"') {
		mString.clear();
		for(;;) {
			if (mpSrc == mpSrcEnd) {
				SetError("End of file encountered in string literal");
				return 0;
			}

			c = *mpSrc++;
			if (c == '\r' || c == '\n') {
				SetError("Newline encountered in string literal");
				return 0;
			}

			if (c == '"')
				break;

			if (c == '\\') {
				if (mpSrc == mpSrcEnd) {
					SetError("Invalid escape sequence");
					return 0;
				}

				c = *mpSrc++;
				switch(c) {
					case 'a':	c = '\a';	break;
					case 'b':	c = '\b';	break;
					case 'f':	c = '\f';	break;
					case 'n':	c = '\n';	break;
					case 'r':	c = '\r';	break;
					case 't':	c = '\t';	break;
					case 'u':	c = '\u';	break;
					case 'v':	c = '\v';	break;
					case 'x':
						c = 0;
						for(;;) {
							if (mpSrc == mpSrcEnd) {
								SetError("Unterminated hex escape sequence");
								return 0;
							}

							char d = *mpSrc;
							if (!isxdigit((unsigned char)d))
								break;
							++mpSrc;

							d = (d - 0x30) & 0x1f;
							if (d >= 10)
								d -= 7;

							c = (c << 4) + d;
						}
						break;
					case '\\':
						break;
					default:
						SetErrorF("Unknown escape sequence \\%c", c);
						return 0;
				}
			}
			mString.push_back(c);
		}

		mTokenLength = mpSrc - mpToken;
		return kTokenString;
	}

	if ((unsigned char)(c - 0x20) >= 0x5f) {
		SetErrorF("Unrecognized character '%c'", c);
		return 0;
	}

	mTokenLength = 1;
	return c;
}

void VDCompilerLexer::Push() {
	mpSrc = mpToken;
}

bool VDCompilerLexer::SetError(const char *s) {
	if (mbError)
		return false;

	mbError = true;
	mErrorInfo.mLine = mLineNo;
	mErrorInfo.mColumn = (mpToken - mpSrcLineStart) + 1;
	mErrorInfo.mLength = mTokenLength;

	char buf[2048];
	if (_snprintf(buf, 2047, "%s(%d,%d): Error! %s\r\n", mpContext, mLineNo, (int)(mpSrc - mpSrcLineStart) + 1, s) > 0) {
		buf[2047] = 0;
		mpOutput->WriteLogOutput(buf);
	}
	return false;
}

bool VDCompilerLexer::SetErrorF(const char *format, ...) {
	if (mbError)
		return false;

	mbError = true;
	mErrorInfo.mLine = mLineNo;
	mErrorInfo.mColumn = (mpToken - mpSrcLineStart) + 1;
	mErrorInfo.mLength = mTokenLength;

	va_list val;
	char buf[2048], *s = buf, *limit = s + 2047;
	int len;
	len = _snprintf(s, limit-s, "%s(%d,%d): Error! ", mpContext, mLineNo, (int)(mpSrc - mpSrcLineStart) + 1);
	if (len >= 0) {
		s += len;
		va_start(val, format);
		len = _vsnprintf(s, limit-s, format, val);
		va_end(val);

		if (len >= 0) {
			s += len;

			if (limit-s >= 2) {
				*s++ = '\r';
				*s++ = '\n';
				*s = 0;
				mpOutput->WriteLogOutput(buf);
			}
		}
	}
	return false;
}

///////////////////////////////////////////////////////////////////////////////

namespace {
	enum Opcode {
		kInsnEnd,
		kInsnLoadConstI4,
		kInsnLoadConstI8,
		kInsnLoadConstR8,
		kInsnDupI4,
		kInsnWriteI1,
		kInsnWriteI2,
		kInsnWriteI4,
		kInsnWriteI8,
		kInsnWriteR4,
		kInsnWriteR8,
		kInsnSetIndexAnchor,
		kInsnWriteIndex,
		kInsnBeginChunk,
		kInsnEndChunk,
		kInsnEndIndexedChunk
	};
}

class VDFileCreator : public vdrefcounted<IVDFileCreator> {
public:
	VDFileCreator();

	void Create(const wchar_t *filename);

	void Emit8(uint8 c);
	void Emit16(uint16 c);
	void Emit32(uint32 c);
	void Emit64(uint64 c);
	void EmitR8(double c);

protected:
	sint64	mIndexOffset;

	typedef vdfastvector<uint8> Bytecode;
	Bytecode	mBytecode;

	union StackVal {
		uint32	i4;
		uint64	i8;
		double	r8;
		const char *s;
	};

	typedef vdfastvector<StackVal> Stack;
	Stack	mStack;

	struct IndexEntry {
		uint32 pos;
		uint32 size;
		uint32 flags;
		uint32 ckid;
	};

	typedef vdfastvector<IndexEntry> Index;
	Index mIndex;

	typedef vdfastvector<sint64> Chunks;
	Chunks mChunks;
};

VDFileCreator::VDFileCreator()
	: mIndexOffset(0)
{
}

void VDFileCreator::Create(const wchar_t *filename) {
	VDFile file(filename, nsVDFile::kWrite | nsVDFile::kDenyRead | nsVDFile::kCreateAlways);

	const uint8 *ip = mBytecode.data();
	while(uint8 op = *ip++) {
		switch(op) {
			case kInsnLoadConstI4:
				mStack.push_back().i4 = VDReadUnalignedS32(ip);
				ip += 4;
				break;

			case kInsnLoadConstI8:
				mStack.push_back().i8 = VDReadUnalignedS64(ip);
				ip += 8;
				break;

			case kInsnLoadConstR8:
				mStack.push_back().r8 = VDReadUnalignedD(ip);
				ip += 8;
				break;

			case kInsnDupI4:
				{
					sint32 v = mStack.back().i4;
					mStack.push_back().i4 = v;
				}
				break;

			case kInsnWriteI1:
				{
					uint8 c = (uint8)mStack.back().i4;
					mStack.pop_back();

					file.write(&c, 1);
				}
				break;

			case kInsnWriteI2:
				{
					uint16 c = (uint16)mStack.back().i4;
					mStack.pop_back();

					file.write(&c, 2);
				}
				break;

			case kInsnWriteI4:
				{
					uint32 c = mStack.back().i4;
					mStack.pop_back();

					file.write(&c, 4);
				}
				break;

			case kInsnWriteI8:
				{
					uint64 c = mStack.back().i8;
					mStack.pop_back();

					file.write(&c, 8);
				}
				break;

			case kInsnWriteR4:
				{
					float c = (float)mStack.back().r8;
					mStack.pop_back();

					file.write(&c, 4);
				}
				break;

			case kInsnWriteR8:
				{
					double c = mStack.back().r8;
					mStack.pop_back();

					file.write(&c, 8);
				}
				break;

			case kInsnSetIndexAnchor:
				mIndexOffset = file.tell();
				break;

			case kInsnWriteIndex:
				{
					Index::const_iterator it(mIndex.begin()), itEnd(mIndex.end());
					for(; it!=itEnd; ++it) {
						const IndexEntry& ie = *it;

						uint32 t[4];
						t[0] = ie.ckid;
						t[1] = ie.flags;
						t[2] = ie.pos;
						t[3] = ie.size;

						file.write(t, sizeof t);
					}
				}
				break;

			case kInsnBeginChunk:
				{
					mChunks.push_back(file.tell());

					uint32 c = 0;
					file.write(&c, 4);
				}
				break;

			case kInsnEndChunk:
				{
					sint64 pos = file.tell();
					sint64 cpos = mChunks.back();
					mChunks.pop_back();
					file.seek(cpos);

					uint32 size = (uint32)(pos - (cpos + 4));
					file.write(&size, 4);

					uint8 pad = 0;
					if (pos & 1)
						file.write(&pad, 1);

					file.seek(pos);
				}
				break;

			case kInsnEndIndexedChunk:
				{
					uint32 flags = mStack.back().i4;
					mStack.pop_back();

					uint32 ckid = mStack.back().i4;
					mStack.pop_back();

					sint64 pos = file.tell();
					sint64 cpos = mChunks.back();
					mChunks.pop_back();
					file.seek(cpos);

					uint32 size = (uint32)(pos - (cpos + 4));
					file.write(&size, 4);

					uint8 pad = 0;
					if (pos & 1)
						file.write(&pad, 1);

					file.seek(pos);

					IndexEntry ie;
					ie.pos = (uint32)((cpos - 4) - mIndexOffset);
					ie.size = size;
					ie.flags = flags;
					ie.ckid = ckid;

					mIndex.push_back(ie);
				}
				break;
		}
	}
}

void VDFileCreator::Emit8(uint8 c) {
	mBytecode.push_back(c);
}

void VDFileCreator::Emit16(uint16 c) {
	mBytecode.insert(mBytecode.end(), (const uint8 *)&c, (const uint8 *)(&c+1));
}

void VDFileCreator::Emit32(uint32 c) {
	mBytecode.insert(mBytecode.end(), (const uint8 *)&c, (const uint8 *)(&c+1));
}

void VDFileCreator::Emit64(uint64 c) {
	mBytecode.insert(mBytecode.end(), (const uint8 *)&c, (const uint8 *)(&c+1));
}

void VDFileCreator::EmitR8(double c) {
	mBytecode.insert(mBytecode.end(), (const uint8 *)&c, (const uint8 *)(&c+1));
}

///////////////////////////////////////////////////////////////////////////////

namespace {
	enum {
		kKeywordWrite_i1 = kTokenKeywordBase,
		kKeywordWrite_i2,
		kKeywordWrite_i4,
		kKeywordWrite_i8,
		kKeywordWrite_u1,
		kKeywordWrite_u2,
		kKeywordWrite_u4,
		kKeywordWrite_u8,
		kKeywordWrite_r4,
		kKeywordWrite_r8,
		kKeywordWrite_fcc,
		kKeywordSet_index_anchor,
		kKeywordAdd_to_index,
		kKeywordWrite_index,
		kKeywordChunk,
		kKeywordIndexed_chunk
	};

	enum ExpressionOp {
		kOpNone,
		kOpAdd,
		kOpSub,
		kOpMul,
		kOpDiv,
		kOpNegate,
		kOpSwizzle,
	};

	enum VDFileCreatorValueType {
		kTypeI1,
		kTypeI2,
		kTypeI4,
		kTypeI8,
		kTypeU1,
		kTypeU2,
		kTypeU4,
		kTypeU8,
		kTypeR4,
		kTypeR8,
		kTypeS
	};

	const char *GetTypeName(VDFileCreatorValueType type) {
		switch(type) {
			case kTypeI1:
				return "int8";

			case kTypeI2:
				return "int16";

			case kTypeI4:
				return "int32";

			case kTypeI8:
				return "int64";

			case kTypeU1:
				return "uint8";

			case kTypeU2:
				return "uint16";

			case kTypeU4:
				return "uint32";

			case kTypeU8:
				return "uint64";

			case kTypeR4:
				return "float";

			case kTypeR8:
				return "double";

			case kTypeS:
				return "string";

			default:
				return "???";
		}
	}

	VDFileCreatorValueType GetBinaryResultType(VDFileCreatorValueType type1, VDFileCreatorValueType type2) {
		// promote to at least double or int32/uint32
		switch(type1) {
			case kTypeI1:
			case kTypeI2:
				type1 = kTypeI4;
				break;
			case kTypeU1:
			case kTypeU2:
				type1 = kTypeU4;
				break;
			case kTypeR4:
				type1 = kTypeR8;
				break;
		}

		switch(type2) {
			case kTypeI1:
			case kTypeI2:
				type2 = kTypeI4;
				break;
			case kTypeU1:
			case kTypeU2:
				type2 = kTypeU4;
				break;
			case kTypeR4:
				type2 = kTypeR8;
				break;
		}

		// promote to double if either is double
		if (type1 == kTypeR8 || type2 == kTypeR8)
			return kTypeR8;

		// promote to uint64 if either is uint64
		if (type1 == kTypeU8 || type2 == kTypeU8)
			return kTypeU8;

		// promote to int64 if either is int64
		if (type1 == kTypeI8 || type2 == kTypeI8)
			return kTypeI8;

		// promote to int64 if uint32 and int32 are mixed
		if ((type1 == kTypeI4 && type2 == kTypeU4) || (type1 == kTypeU4 && type2 == kTypeI4))
			return kTypeI8;

		VDASSERT(type1 == type2);
		return type1;
	}
}

struct VDFileCreatorValue {
	VDFileCreatorValueType	mType;
	union {
		sint32 i4;
		sint64 i8;
		double r8;
		const char *s;
	};

	bool ConvertToType(VDFileCreatorValueType type);
};

bool VDFileCreatorValue::ConvertToType(VDFileCreatorValueType type) {
	switch(type) {
	case kTypeI1:
		switch(mType) {
		case kTypeU1:	i4 = (sint8 )(uint8 )i4;	break;
		case kTypeI1:	i4 = (sint8 )(sint8 )i4;	break;
		case kTypeI2:	i4 = (sint8 )(sint16)i4;	break;
		case kTypeU2:	i4 = (sint8 )(uint16)i4;	break;
		case kTypeI4:	i4 = (sint8 )(sint32)i4;	break;
		case kTypeU4:	i4 = (sint8 )(uint32)i4;	break;
		case kTypeI8:	i4 = (sint8 )(sint64)i8;	break;
		case kTypeU8:	i4 = (sint8 )(uint64)i8;	break;
		case kTypeR4:	i4 = (sint8 )(float )r8;	break;
		case kTypeR8:	i4 = (sint8 )(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeU1:
		switch(mType) {
		case kTypeU1:	i4 = (uint8 )(uint8 )i4;	break;
		case kTypeI1:	i4 = (uint8 )(sint8 )i4;	break;
		case kTypeI2:	i4 = (uint8 )(sint16)i4;	break;
		case kTypeU2:	i4 = (uint8 )(uint16)i4;	break;
		case kTypeI4:	i4 = (uint8 )(sint32)i4;	break;
		case kTypeU4:	i4 = (uint8 )(uint32)i4;	break;
		case kTypeI8:	i4 = (uint8 )(sint64)i8;	break;
		case kTypeU8:	i4 = (uint8 )(uint64)i8;	break;
		case kTypeR4:	i4 = (uint8 )(float )r8;	break;
		case kTypeR8:	i4 = (uint8 )(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeI2:
		switch(mType) {
		case kTypeU1:	i4 = (sint16)(uint8 )i4;	break;
		case kTypeI1:	i4 = (sint16)(sint8 )i4;	break;
		case kTypeI2:	i4 = (sint16)(sint16)i4;	break;
		case kTypeU2:	i4 = (sint16)(uint16)i4;	break;
		case kTypeI4:	i4 = (sint16)(sint32)i4;	break;
		case kTypeU4:	i4 = (sint16)(uint32)i4;	break;
		case kTypeI8:	i4 = (sint16)(sint64)i8;	break;
		case kTypeU8:	i4 = (sint16)(uint64)i8;	break;
		case kTypeR4:	i4 = (sint16)(float )r8;	break;
		case kTypeR8:	i4 = (sint16)(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeU2:
		switch(mType) {
		case kTypeU1:	i4 = (uint16)(uint8 )i4;	break;
		case kTypeI1:	i4 = (uint16)(sint8 )i4;	break;
		case kTypeI2:	i4 = (uint16)(sint16)i4;	break;
		case kTypeU2:	i4 = (uint16)(uint16)i4;	break;
		case kTypeI4:	i4 = (uint16)(sint32)i4;	break;
		case kTypeU4:	i4 = (uint16)(uint32)i4;	break;
		case kTypeI8:	i4 = (uint16)(sint64)i8;	break;
		case kTypeU8:	i4 = (uint16)(uint64)i8;	break;
		case kTypeR4:	i4 = (uint16)(float )r8;	break;
		case kTypeR8:	i4 = (uint16)(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeI4:
		switch(mType) {
		case kTypeU1:	i4 = (sint32)(uint8 )i4;	break;
		case kTypeI1:	i4 = (sint32)(sint8 )i4;	break;
		case kTypeI2:	i4 = (sint32)(sint16)i4;	break;
		case kTypeU2:	i4 = (sint32)(uint16)i4;	break;
		case kTypeI4:	i4 = (sint32)(sint32)i4;	break;
		case kTypeU4:	i4 = (sint32)(uint32)i4;	break;
		case kTypeI8:	i4 = (sint32)(sint64)i8;	break;
		case kTypeU8:	i4 = (sint32)(uint64)i8;	break;
		case kTypeR4:	i4 = (sint32)(float )r8;	break;
		case kTypeR8:	i4 = (sint32)(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeU4:
		switch(mType) {
		case kTypeU1:	i4 = (uint32)(uint8 )i4;	break;
		case kTypeI1:	i4 = (uint32)(sint8 )i4;	break;
		case kTypeI2:	i4 = (uint32)(sint16)i4;	break;
		case kTypeU2:	i4 = (uint32)(uint16)i4;	break;
		case kTypeI4:	i4 = (uint32)(sint32)i4;	break;
		case kTypeU4:	i4 = (uint32)(uint32)i4;	break;
		case kTypeI8:	i4 = (uint32)(sint64)i8;	break;
		case kTypeU8:	i4 = (uint32)(uint64)i8;	break;
		case kTypeR4:	i4 = (uint32)(float )r8;	break;
		case kTypeR8:	i4 = (uint32)(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeI8:
		switch(mType) {
		case kTypeU1:	i8 = (sint64)(uint8 )i4;	break;
		case kTypeI1:	i8 = (sint64)(sint8 )i4;	break;
		case kTypeI2:	i8 = (sint64)(sint16)i4;	break;
		case kTypeU2:	i8 = (sint64)(uint16)i4;	break;
		case kTypeI4:	i8 = (sint64)(sint32)i4;	break;
		case kTypeU4:	i8 = (sint64)(uint32)i4;	break;
		case kTypeI8:	i8 = (sint64)(sint64)i8;	break;
		case kTypeU8:	i8 = (sint64)(uint64)i8;	break;
		case kTypeR4:	i8 = (sint64)(float )r8;	break;
		case kTypeR8:	i8 = (sint64)(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeU8:
		switch(mType) {
		case kTypeU1:	i8 = (uint64)(uint8 )i4;	break;
		case kTypeI1:	i8 = (uint64)(sint8 )i4;	break;
		case kTypeI2:	i8 = (uint64)(sint16)i4;	break;
		case kTypeU2:	i8 = (uint64)(uint16)i4;	break;
		case kTypeI4:	i8 = (uint64)(sint32)i4;	break;
		case kTypeU4:	i8 = (uint64)(uint32)i4;	break;
		case kTypeI8:	i8 = (uint64)(sint64)i8;	break;
		case kTypeU8:	i8 = (uint64)(uint64)i8;	break;
		case kTypeR4:	i8 = (uint64)(float )r8;	break;
		case kTypeR8:	i8 = (uint64)(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeR4:
		switch(mType) {
		case kTypeU1:	r8 = (float )(uint8 )i4;	break;
		case kTypeI1:	r8 = (float )(sint8 )i4;	break;
		case kTypeI2:	r8 = (float )(sint16)i4;	break;
		case kTypeU2:	r8 = (float )(uint16)i4;	break;
		case kTypeI4:	r8 = (float )(sint32)i4;	break;
		case kTypeU4:	r8 = (float )(uint32)i4;	break;
		case kTypeI8:	r8 = (float )(sint64)i8;	break;
		case kTypeU8:	r8 = (float )(uint64)i8;	break;
		case kTypeR4:	r8 = (float )(float )r8;	break;
		case kTypeR8:	r8 = (float )(double)r8;	break;
		default:		return false;
		}
		break;

	case kTypeR8:
		switch(mType) {
		case kTypeU1:	r8 = (double)(uint8 )i4;	break;
		case kTypeI1:	r8 = (double)(sint8 )i4;	break;
		case kTypeI2:	r8 = (double)(sint16)i4;	break;
		case kTypeU2:	r8 = (double)(uint16)i4;	break;
		case kTypeI4:	r8 = (double)(sint32)i4;	break;
		case kTypeU4:	r8 = (double)(uint32)i4;	break;
		case kTypeI8:	r8 = (double)(sint64)i8;	break;
		case kTypeU8:	r8 = (double)(uint64)i8;	break;
		case kTypeR4:	r8 = (double)(float )r8;	break;
		case kTypeR8:	r8 = (double)(double)r8;	break;
		default:		return false;
		}
		break;

	default:
		return false;
	}

	mType = type;
	return true;
}

VDFileCreatorParser::VDFileCreatorParser() {
	mLexer.AddToken(kKeywordWrite_i1, "write_i1");
	mLexer.AddToken(kKeywordWrite_i2, "write_i2");
	mLexer.AddToken(kKeywordWrite_i4, "write_i4");
	mLexer.AddToken(kKeywordWrite_i8, "write_i8");
	mLexer.AddToken(kKeywordWrite_u1, "write_u1");
	mLexer.AddToken(kKeywordWrite_u2, "write_u2");
	mLexer.AddToken(kKeywordWrite_u4, "write_u4");
	mLexer.AddToken(kKeywordWrite_u8, "write_u8");
	mLexer.AddToken(kKeywordWrite_r4, "write_r4");
	mLexer.AddToken(kKeywordWrite_r8, "write_r8");
	mLexer.AddToken(kKeywordWrite_fcc, "write_fcc");
	mLexer.AddToken(kKeywordSet_index_anchor, "set_index_anchor");
	mLexer.AddToken(kKeywordIndexed_chunk, "indexed_chunk");
	mLexer.AddToken(kKeywordWrite_index, "write_index");
	mLexer.AddToken(kKeywordChunk, "chunk");
}

VDFileCreatorParser::~VDFileCreatorParser() {
}

bool VDFileCreatorParser::Parse(const char *src, size_t len, const char *context, IVDCompilerLogOutput *pOutput, IVDFileCreator **ppCreator) {
	mpCreator = new VDFileCreator;

	mLexer.Init(src, len, context, pOutput);

	while(TryParseGlobalStatement())
		Collect();

	mpCreator->Emit8(kInsnEnd);

	if (mLexer.IsErrorFlagSet())
		return false;

	*ppCreator = mpCreator.release();
	return true;
}

bool VDFileCreatorParser::GetErrorInfo(VDCompilerErrorInfo& errorInfo) {
	return mLexer.GetErrorInfo(errorInfo);
}

bool VDFileCreatorParser::TryParseGlobalStatement() {
	int tok = mLexer.Token();

	if (!tok)
		return false;

	switch(tok) {
		case '{':
			for(;;) {
				int tok = mLexer.Token();

				if (!tok)
					return mLexer.SetError("Unmatched '{'");

				if (tok == '}')
					break;

				mLexer.Push();
				if (!TryParseGlobalStatement())
					return false;
			}
			return true;

		case kKeywordWrite_i1:
			if (!EmitExpressionI1())
				return false;
			mpCreator->Emit8(kInsnWriteI1);
			break;

		case kKeywordWrite_i2:
			if (!EmitExpressionI2())
				return false;
			mpCreator->Emit8(kInsnWriteI2);
			break;

		case kKeywordWrite_i4:
			if (!EmitExpressionI4())
				return false;
			mpCreator->Emit8(kInsnWriteI4);
			break;

		case kKeywordWrite_i8:
			if (!EmitExpressionI8())
				return false;
			mpCreator->Emit8(kInsnWriteI8);
			break;

		case kKeywordWrite_u1:
			if (!EmitExpressionU1())
				return false;
			mpCreator->Emit8(kInsnWriteI1);
			break;

		case kKeywordWrite_u2:
			if (!EmitExpressionU2())
				return false;
			mpCreator->Emit8(kInsnWriteI2);
			break;

		case kKeywordWrite_u4:
			if (!EmitExpressionU4())
				return false;
			mpCreator->Emit8(kInsnWriteI4);
			break;

		case kKeywordWrite_u8:
			if (!EmitExpressionU8())
				return false;
			mpCreator->Emit8(kInsnWriteI8);
			break;

		case kKeywordWrite_r4:
			if (!EmitExpressionR4())
				return false;
			mpCreator->Emit8(kInsnWriteR4);
			break;

		case kKeywordWrite_r8:
			if (!EmitExpressionR8())
				return false;
			mpCreator->Emit8(kInsnWriteR8);
			break;

		case kKeywordWrite_fcc:
			if (!EmitExpressionFCC())
				return false;
			mpCreator->Emit8(kInsnWriteI4);
			break;

		case kKeywordSet_index_anchor:
			mpCreator->Emit8(kInsnSetIndexAnchor);
			break;

		case kKeywordWrite_index:
			mpCreator->Emit8(kInsnWriteIndex);
			break;

		case kKeywordChunk:
			if (!ParseChunk())
				return false;
			return true;

		case kKeywordIndexed_chunk:
			if (!ParseIndexedChunk())
				return false;
			return true;

		default:
			return mLexer.SetError("Syntax error");
	}

	tok = mLexer.Token();
	if (tok != ';')
		return mLexer.SetError("Expected end of statement");

	return true;
}

bool VDFileCreatorParser::ParseChunk() {
	if (!EmitExpressionFCC())
		return false;
	mpCreator->Emit8(kInsnWriteI4);
	mpCreator->Emit8(kInsnBeginChunk);
	if (!TryParseGlobalStatement())
		return false;
	mpCreator->Emit8(kInsnEndChunk);
	return true;
}

bool VDFileCreatorParser::ParseIndexedChunk() {
	if (!EmitExpressionFCC())
		return false;
	mpCreator->Emit8(kInsnDupI4);
	mpCreator->Emit8(kInsnWriteI4);

	int tok = mLexer.Token();
	if (tok != ',')
		return mLexer.SetError("Expected ','");

	if (!EmitExpressionU4())
		return false;

	mpCreator->Emit8(kInsnBeginChunk);
	if (!TryParseGlobalStatement())
		return false;
	mpCreator->Emit8(kInsnEndIndexedChunk);
	return true;
}

bool VDFileCreatorParser::ParseExpression(VDFileCreatorValue& value) {
	vdfastvector<uint32> opStack;
	vdfastvector<VDFileCreatorValue> valStack;
	bool expectValue = true;
	int parenLevel = 0;

	for(;;) {
		int tok = mLexer.Token();

		if (expectValue) {
			if (tok == '+') {		// unary +
				continue;
			} else if (tok == '-') {
				opStack.push_back(kOpNegate + (9<<8));
				continue;
			} else if (tok == '(') {
				parenLevel += 0x10000;
			} else if (tok == kTokenFloat) {
				VDFileCreatorValue val;
				val.mType = kTypeR8;
				val.r8 = mLexer.mFloatVal;

				valStack.push_back(val);
				expectValue = false;
			} else if (tok == kTokenInt) {
				VDFileCreatorValue val;
				val.mType = kTypeI4;
				val.i4 = mLexer.mIntVal;

				valStack.push_back(val);
				expectValue = false;
			} else if (tok == kTokenString) {
				int len = (int)mLexer.mString.size();
				char *s = new char[len + 1];
				mTempStrings.push_back(s);
				memcpy(s, mLexer.mString.data(), len);
				s[len] = 0;

				VDFileCreatorValue val;
				val.mType = kTypeS;
				val.s = s;

				valStack.push_back(val);
				expectValue = false;
			} else {
				return mLexer.SetError("Expected expression value");
			}
		} else {
			int op = 0;
			if (tok == '+')
				op = kOpAdd + (1 << 8) + parenLevel;
			else if (tok == '-')
				op = kOpSub + (1 << 8) + parenLevel;
			else if (tok == '*')
				op = kOpMul + (2 << 8) + parenLevel;
			else if (tok == '/')
				op = kOpDiv + (2 << 8) + parenLevel;
			else if (tok == ')') {
				if (parenLevel) {
					parenLevel -= 0x10000;
					continue;
				}
			}

			// reduce as necessary
			while(!opStack.empty()) {
				int otherop = opStack.back();

				if (op > otherop)
					break;

				opStack.pop_back();

				ExpressionOp baseop = (ExpressionOp)(otherop & 0xff);

				if (baseop == kOpNegate) {
					VDFileCreatorValue& v = valStack.back();

					switch(v.mType) {
						case kTypeU1:
						case kTypeI1:
						case kTypeU2:
						case kTypeI2:
						case kTypeU4:
						case kTypeI4:
							v.i4 = -v.i4;
							break;
						case kTypeU8:
						case kTypeI8:
							v.i8 = -v.i8;
							break;
						case kTypeR4:
						case kTypeR8:
							v.r8 = -v.r8;
							break;
					}
				} else {
					// coerce
					VDFileCreatorValue& var1 = *(valStack.end() - 2);
					VDFileCreatorValue& var2 = valStack.back();

					VDFileCreatorValueType commonType = GetBinaryResultType(var1.mType, var2.mType);
					if (!commonType)
						return mLexer.SetErrorF("Cannot operate between types '%s' and '%s'", GetTypeName(var1.mType), GetTypeName(var2.mType));

					if (!var1.ConvertToType(commonType))
						return mLexer.SetErrorF("Cannot convert value from '%s' to '%s'", GetTypeName(var1.mType), GetTypeName(commonType));

					if (!var2.ConvertToType(commonType))
						return mLexer.SetErrorF("Cannot convert value from '%s' to '%s'", GetTypeName(var2.mType), GetTypeName(commonType));

					switch(baseop) {
						case kOpAdd:
							switch(commonType) {
								case kTypeI4:
								case kTypeU4:
									var1.i4 += var2.i4;
									break;
								case kTypeI8:
								case kTypeU8:
									var1.i8 += var2.i8;
									break;
								case kTypeR8:
									var1.r8 += var2.r8;
									break;
								default:
									return mLexer.SetErrorF("Cannot add values of type '%s'", GetTypeName(commonType));
							}
							break;
						case kOpSub:
							switch(commonType) {
								case kTypeI4:
								case kTypeU4:
									var1.i4 -= var2.i4;
									break;
								case kTypeI8:
								case kTypeU8:
									var1.i8 -= var2.i8;
									break;
								case kTypeR8:
									var1.r8 -= var2.r8;
									break;
								default:
									return mLexer.SetErrorF("Cannot subtract values of type '%s'", GetTypeName(commonType));
							}
							break;
						case kOpMul:
							switch(commonType) {
								case kTypeI4:
									var1.i4 *= var2.i4;
									break;
								case kTypeU4:
									var1.i4 = (uint32)var1.i4 * (uint32)var2.i4;
									break;
								case kTypeI8:
									var1.i8 *= var2.i8;
									break;
								case kTypeU8:
									var1.i8 = (uint64)var1.i8 * (uint64)var2.i8;
									break;
								case kTypeR8:
									var1.r8 *= var2.r8;
									break;
								default:
									return mLexer.SetErrorF("Cannot multiply values of type '%s'", GetTypeName(commonType));
							}
							break;
						case kOpDiv:
							switch(commonType) {
								case kTypeI4:
									if (!var2.i4)
										return mLexer.SetError("Divide by zero");
									var1.i4 /= var2.i4;
									break;
								case kTypeU4:
									if (!var2.i4)
										return mLexer.SetError("Divide by zero");
									var1.i4 = (sint32)((uint32)var1.i4 / (uint32)var2.i4);
									break;
								case kTypeI8:
									if (!var2.i8)
										return mLexer.SetError("Divide by zero");
									var1.i8 /= var2.i8;
									break;
								case kTypeU8:
									if (!var2.i8)
										return mLexer.SetError("Divide by zero");
									var1.i8 = (sint64)((uint64)var1.i8 / (uint64)var2.i8);
									break;
								case kTypeR8:
									if (var2.r8 == 0.0)
										return mLexer.SetError("Divide by zero");
									var1.r8 /= var2.r8;
									break;
								default:
									return mLexer.SetErrorF("Cannot divide values of type '%s'", GetTypeName(commonType));
							}
							break;
					}

					var1.mType = commonType;
					valStack.pop_back();
				}
			}

			expectValue = true;

			if (!op)
				break;

			opStack.push_back(op);
		}
	}

	if (parenLevel)
		return mLexer.SetError("Unmatched '(' in expression");

	mLexer.Push();

	VDASSERT(valStack.size() == 1);
	value = valStack.back();
	return true;
}

bool VDFileCreatorParser::EmitExpressionI1() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeI1))
		return mLexer.SetError("Cannot convert value to type 'int8'");
	mpCreator->Emit8(kInsnLoadConstI4);
	mpCreator->Emit32(value.i4);
	return true;
}

bool VDFileCreatorParser::EmitExpressionI2() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeI2))
		return mLexer.SetError("Cannot convert value to type 'int16'");
	mpCreator->Emit8(kInsnLoadConstI4);
	mpCreator->Emit32(value.i4);
	return true;
}

bool VDFileCreatorParser::EmitExpressionI4() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeI4))
		return mLexer.SetError("Cannot convert value to type 'int32'");
	mpCreator->Emit8(kInsnLoadConstI4);
	mpCreator->Emit32(value.i4);
	return true;
}

bool VDFileCreatorParser::EmitExpressionI8() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeI8))
		return mLexer.SetError("Cannot convert value to type 'int64'");
	mpCreator->Emit8(kInsnLoadConstI8);
	mpCreator->Emit64(value.i8);
	return true;
}

bool VDFileCreatorParser::EmitExpressionU1() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeU1))
		return mLexer.SetError("Cannot convert value to type 'uint8'");
	mpCreator->Emit8(kInsnLoadConstI4);
	mpCreator->Emit32(value.i4);
	return true;
}

bool VDFileCreatorParser::EmitExpressionU2() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeU2))
		return mLexer.SetError("Cannot convert value to type 'uint16'");
	mpCreator->Emit8(kInsnLoadConstI4);
	mpCreator->Emit32(value.i4);
	return true;
}

bool VDFileCreatorParser::EmitExpressionU4() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeU4))
		return mLexer.SetError("Cannot convert value to type 'uint32'");
	mpCreator->Emit8(kInsnLoadConstI4);
	mpCreator->Emit32(value.i4);
	return true;
}

bool VDFileCreatorParser::EmitExpressionU8() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeU8))
		return mLexer.SetError("Cannot convert value to type 'uint64'");
	mpCreator->Emit8(kInsnLoadConstI8);
	mpCreator->Emit64(value.i8);
	return true;
}

bool VDFileCreatorParser::EmitExpressionR4() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeR4))
		return mLexer.SetError("Cannot convert value to type 'float'");
	mpCreator->Emit8(kInsnLoadConstR8);
	mpCreator->EmitR8(value.r8);
	return true;
}

bool VDFileCreatorParser::EmitExpressionR8() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;
	if (!value.ConvertToType(kTypeR8))
		return mLexer.SetError("Cannot convert value to type 'double'");
	mpCreator->Emit8(kInsnLoadConstR8);
	mpCreator->EmitR8(value.r8);
	return true;
}

bool VDFileCreatorParser::EmitExpressionFCC() {
	VDFileCreatorValue value;
	if (!ParseExpression(value))
		return false;

	if (value.mType == kTypeS) {
		const char *s = value.s;
		int len = (int)strlen(s);

		if (len > 4)
			return mLexer.SetError("String too long for FOURCC");

		char buf[4]={' ', ' ', ' ', ' '};
		memcpy(buf, s, len);

		value.i4 = *(uint32 *)buf;
	} else if (!value.ConvertToType(kTypeU4))
		return mLexer.SetError("Cannot convert value to FOURCC");

	mpCreator->Emit8(kInsnLoadConstI4);
	mpCreator->Emit32(value.i4);
	return true;
}

void VDFileCreatorParser::Collect() {
	while(!mTempStrings.empty()) {
		const char *s = mTempStrings.back();
		mTempStrings.pop_back();
		delete[] s;
	}
}

///////////////////////////////////////////////////////////////////////////////

class VDCompilerLogOutputStdout : public IVDCompilerLogOutput {
public:
	void WriteLogOutput(const char *s) {
		fputs(s, stdout);
	}
};

void tool_filecreate(const vdfastvector<const char *>& args, const vdfastvector<const char *>& switches) {
	if (args.size() != 2) {
		puts("usage: asuka filecreate source.filescript target.bin");
		exit(5);
	}
	const char *fnin = args[0];
	const char *fnout = args[1];

	VDFile file(fnin);
	vdfastvector<char> buf((uint32)file.size());
	file.read(buf.data(), buf.size());
	file.close();

	VDFileCreatorParser parser;
	VDCompilerLogOutputStdout out;

	vdrefptr<IVDFileCreator> creator;
	if (!parser.Parse(buf.data(), buf.size(), fnin, &out, ~creator))
		exit(5);

	creator->Create(VDTextAToW(fnout).c_str());
}
