Click here to Skip to main content
15,884,821 members
Articles / Containers / Virtual Machine

Conscript: An embeddable, compiled scripting language for .NET

Rate me:
Please Sign up or sign in to vote.
4.97/5 (58 votes)
5 Sep 2008CPOL15 min read 158.4K   1.4K   141  
An API for enhancing any .NET application with a scripting language
using System;
using System.Collections.Generic;
using System.Text;

using Conscript.Runtime;

namespace Conscript.Compiler
{
    internal class ScriptParser
    {
        #region Private Structs

        private struct Variable
        {
            public string m_strName;
            public VariableScope m_variablescope;
            public Type m_typeInferred;

            public Variable(String strName, VariableScope variableScope, Type typeInferred)
            {
                m_strName = strName;
                m_variablescope = variableScope;
                m_typeInferred = typeInferred;
            }
        }

        private struct FunctionDescriptor
        {
            public String m_strFunctionName;
            public uint m_uiParameterCount;
            public ScriptInstruction m_scriptInstruction;
        }

        private struct LoopControl
        {
            public ScriptInstruction m_scriptInstructionBreak;
            public ScriptInstruction m_scriptInstructionContinue;
        }

        #endregion

        #region Private Variables

        private Script m_script;
        private bool m_bDebugMode;
        private List<Token> m_listTokens;
        private int m_iNextToken;
        private Dictionary<String, bool> m_dictScriptVariables;
        private Dictionary<String, bool> m_dictFunctionVariables;
        private int m_iFunctionVariableLevel;
        private Dictionary<ScriptInstruction, FunctionDescriptor> m_dictUnresolvedFunctionCalls;
        private Stack<LoopControl> m_stackLoopControl;
        private TypeInferer m_typeInferer;
        private ScriptExecutable m_scriptExecutable;

        #endregion

        #region Private Methods

        private bool HasMoreTokens()
        {
            return m_iNextToken < m_listTokens.Count;
        }

        private Token ReadToken()
        {
            if (!HasMoreTokens())
                throw new ParserException("Unexpected end of token stream.");
            return m_listTokens[m_iNextToken++];
        }

        private Token PeekToken()
        {
            if (!HasMoreTokens())
                throw new ParserException("Unexpected end of token stream.");
            return m_listTokens[m_iNextToken];
        }

        private void UndoToken()
        {
            if (m_iNextToken <= 0)
                throw new ParserException("No more tokens to undo.");
            --m_iNextToken;
        }

        private bool IsAssignmentOperator(TokenType tokenType)
        {
            switch (tokenType)
            {
                case TokenType.Assign:
                case TokenType.AssignPlus:
                case TokenType.AssignMinus:
                case TokenType.AssignMultiply:
                case TokenType.AssignDivide:
                case TokenType.AssignPower:
                case TokenType.AssignModulo:
                    return true;
                default:
                    return false;
            }
        }

        private Opcode GetAssignmentOpcode(TokenType tokenType)
        {
            switch (tokenType)
            {
                case TokenType.Assign: return Opcode.MOV;
                case TokenType.AssignPlus: return Opcode.ADD;
                case TokenType.AssignMinus: return Opcode.SUB;
                case TokenType.AssignMultiply: return Opcode.MUL;
                case TokenType.AssignDivide: return Opcode.DIV;
                case TokenType.AssignPower: return Opcode.POW;
                case TokenType.AssignModulo: return Opcode.MOD;
                default:
                    throw new ParserException("Token '" + tokenType
                        + "' is not a valid assignment operator.");
            }
        }

        private Type GetLiteralType(TokenType tokenType)
        {
            switch (tokenType)
            {
                case TokenType.Integer: return typeof(int);
                case TokenType.Float: return typeof(float);
                case TokenType.Boolean: return typeof(bool);
                case TokenType.String: return typeof(String);
                default: throw new ParserException(
                    "Specified token type cannot be matched to a literal type.");
            }
        }

        private Type VerifyInferredType(
            Token tokenOperator, Type type1, Type type2)
        {
            return m_typeInferer.GetInferredType(tokenOperator, type1, type2);
        }

        private void DeclareLocalVar(String strIdentifier)
        {
            if (m_dictScriptVariables.ContainsKey(strIdentifier))
                throw new ParserException(
                    "Cannot declare local variable '" + strIdentifier
                    + "' as it is already defined at script or global level.");
            if (m_dictFunctionVariables.ContainsKey(strIdentifier))
                throw new ParserException(
                    "Local variable '" + strIdentifier
                    + "' already declared.");

            m_dictFunctionVariables[strIdentifier] = true;

            m_scriptExecutable.InstructionsInternal.Add
                (new ScriptInstruction(Opcode.DCL,
                    Operand.CreateVariable(strIdentifier)));
        }

        private void AllocateTemporaryVariableLevel()
        {
            ++m_iFunctionVariableLevel;
        }

        private String AllocateTemporaryVariable()
        {
            int iTempIndex = 0;
            while (true)
            {
                String strIdentifier = "__tmp_" + m_iFunctionVariableLevel + "_" + iTempIndex;
                if (!m_dictFunctionVariables.ContainsKey(strIdentifier)
                    && !m_dictScriptVariables.ContainsKey(strIdentifier))
                {
                    m_dictFunctionVariables[strIdentifier] = true;
                    return strIdentifier;
                }
                ++iTempIndex;
            }
        }

        private void FreeTemporaryVariableLevel()
        {
            if (m_iFunctionVariableLevel == 0) return;

            String strPattern = "__tmp_" + m_iFunctionVariableLevel + "_";

            List<String> listVariablesToRemove = new List<String>();
            foreach (String strIdentifier in m_dictFunctionVariables.Keys)
                if (strIdentifier.StartsWith(strPattern))
                    listVariablesToRemove.Add(strIdentifier);

            foreach (String strIdentifier in listVariablesToRemove)
                m_dictFunctionVariables.Remove(strIdentifier);

            --m_iFunctionVariableLevel;
        }

        private void ReadSemicolon()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.SemiColon)
                throw new ParserException(
                    "Closing semicolon expected.", token);
        }

        private void ReadComma()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Comma)
                throw new ParserException(
                    "Comma ',' expected.", token);
        }

        private String ReadIdentifier()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Identifier)
                throw new ParserException(
                    "Identifier expected.", token);
            return token.Lexeme.ToString();
        }

        private String ReadDeclaredIdentifier()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Identifier)
                throw new ParserException(
                    "Identifier expected.", token);

            String strIdentifier = token.Lexeme.ToString();

            if (!m_dictScriptVariables.ContainsKey(strIdentifier)
                && !m_dictFunctionVariables.ContainsKey(strIdentifier))
                throw new ParserException(
                    "Undeclared variable identifier '" + strIdentifier + "'.",
                    token);

            return strIdentifier;
        }

        private void ReadLeftParenthesis()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.LeftPar)
                throw new ParserException("Left parenthesis '(' expected.", token);
        }

        private void ReadRightParenthesis()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.RightPar)
                throw new ParserException("Right parenthesis ')' expected.", token);
        }

        private void ReadLeftBrace()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.LeftBrace)
                throw new ParserException("Left brace '{' expected.", token);
        }

        private void ReadRightBrace()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.RightBrace)
                throw new ParserException("Right brace '}' expected.", token);
        }

        private void ReadLeftBracket()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.LeftBracket)
                throw new ParserException(
                    "Left bracket '[' expected for array indexing expression.",
                    token);
        }

        private void ReadRightBracket()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.RightBracket)
                throw new ParserException(
                    "Right bracket ']' expected for array indexing expression.",
                    token);
        }

        private void ReadPeriod()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Period)
                throw new ParserException(
                    "Period '.' expected for member variable expression.",
                    token);
        }

        private void InsertDebugInfo(Token token)
        {
            if (!m_bDebugMode) return;

            String strSourceText = token.SourceText;
            strSourceText = strSourceText.Replace("\r", "").Replace("\n", "");

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.DBG,
                    Operand.CreateLiteral(token.SourceLine),
                    Operand.CreateLiteral(strSourceText)));
        }

        private void ParseYield()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Yield)
                throw new ParserException(
                    "Yield statement must start with the 'yield' keyword.",
                    token);

            ReadSemicolon();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.INT));
        }

        private void ParseWait()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Wait)
                throw new ParserException(
                    "Wait statement must start with the 'wait' keyword.",
                    token);

            String strIdentifier = ReadDeclaredIdentifier();
            ReadSemicolon();

            List<ScriptInstruction> listInstructions
                = m_scriptExecutable.InstructionsInternal;
             
            // 0000: JT  id 0003
            // 0001: INT
            // 0002: JMP 0000
            // 0003: NOP

            ScriptInstruction scriptInstructionNOP
                = new ScriptInstruction(Opcode.NOP);

            ScriptInstruction scriptInstructionJEQ
                = new ScriptInstruction(
                    Opcode.JT,
                    Operand.CreateVariable(strIdentifier),
                    Operand.CreateInstructionRef(scriptInstructionNOP));
            listInstructions.Add(scriptInstructionJEQ);

            listInstructions.Add(
                new ScriptInstruction(Opcode.INT));

            listInstructions.Add(
                new ScriptInstruction(
                    Opcode.JMP,
                    Operand.CreateInstructionRef(scriptInstructionJEQ)));

            listInstructions.Add(scriptInstructionNOP);

        }

        private void ParseNotify()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Notify)
                throw new ParserException(
                    "Notify statement must start with the 'notify' keyword.",
                    token);

            String strIdentifier = ReadDeclaredIdentifier();
            ReadSemicolon();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(
                    Opcode.MOV,
                    Operand.CreateVariable(strIdentifier),
                    Operand.CreateLiteral(true)));
        }

        private void ParseScriptOrGlobalVariableDeclaration()
        {
            // get var
            Token token = ReadToken();
            if (token.Type != TokenType.Global && token.Type != TokenType.Var)
                throw new ParserException(
                    "Script variable declaration must start with the 'global' or 'var' keyword.",
                    token);

            InsertDebugInfo(token);

            Opcode opcodeDeclare = token.Type == TokenType.Global ? Opcode.DCG : Opcode.DCL;

            // get first variable identifier
            String strIdentifier = ReadIdentifier();

            while (true)
            {
                if (m_dictScriptVariables.ContainsKey(strIdentifier))
                    throw new ParserException(
                        "Cannot declare script variable '" + strIdentifier
                        + "' as it is already defined at script or global level.",
                        token);
                m_dictScriptVariables[strIdentifier] = true;

                // 0000 DCG identifier
                // or
                // 0000 DCL identifier
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(opcodeDeclare,
                        Operand.CreateVariable(strIdentifier)));

                // also handle declarations on the fly
                if (opcodeDeclare == Opcode.DCG)
                    m_script.Manager.GlobalDictionary[strIdentifier]
                        = NullReference.Instance;
                else
                    m_scriptExecutable.ScriptDictionary[strIdentifier]
                        = NullReference.Instance;

                // if next token is semicolon, end of statement
                token = ReadToken();
                if (token.Type == TokenType.SemiColon) return;

                // otherwise, expect comma
                if (token.Type != TokenType.Comma)
                    throw new ParserException(
                        "Comma required between identifiers in variable declaration.",
                        token);

                // read next variable id
                strIdentifier = ReadIdentifier();
            }
        }

        private void ParseLocalVariableDeclaration()
        {
            // get var
            Token token = ReadToken();
            if (token.Type != TokenType.Var)
                throw new ParserException(
                    "Variable declaration must start with the 'var' keyword.",
                    token);

            // get first variable identifier
            String strIdentifier = ReadIdentifier();

            while (true)
            {
                DeclareLocalVar(strIdentifier);

                // if next token is semicolon, end of statement
                token = ReadToken();
                if (token.Type == TokenType.SemiColon) return;

                // check for declarative assignment
                if (token.Type == TokenType.Assign)
                {
                    UndoToken();
                    UndoToken();
                    ParseAssignment();
                    token = ReadToken();
                    if (token.Type == TokenType.SemiColon) return;
                }

                // otherwise, expect comma
                if (token.Type != TokenType.Comma)
                    throw new ParserException(
                        "Comma ',' required between identifiers in variable declaration.",
                        token);

                // read next variable id
                strIdentifier = ReadIdentifier();
            }
        }

        private void ParseThreadCall()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Thread)
                throw new ParserException(
                    "Keyword 'thread' expected for thread call.", token);

            ParseScriptFunctionCall(true);

            ReadSemicolon();
        }

        private Variable ParseHostFunctionCall()
        {
            String strFunctionName = ReadIdentifier();

            ReadLeftParenthesis();

            List<object> listParameters = new List<object>();

            // parse parameters

            // 0000 PUSH tmp1
            // 0001 PUSH tmp2
            // :
            // 0010 PUSH tmpN

            int iParameterCount = 0;

            if (PeekToken().Type != TokenType.RightPar)
            {
                while (true)
                {
                    Variable variableParameter = ParseExpression();

                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(
                            Opcode.PUSH,
                            Operand.CreateVariable(variableParameter.m_strName)));

                    ++iParameterCount;

                    if (PeekToken().Type == TokenType.RightPar)
                        break;
                    else
                        ReadComma();
                }
            }

            ReadRightParenthesis();

            ScriptManager scriptManager
                = m_scriptExecutable.Script.Manager;
            if (!scriptManager.HostFunctions.ContainsKey(strFunctionName))
                throw new ParserException(
                    "Host function '" + strFunctionName
                    + "' not registered with the associated ScriptManager.");

            HostFunctionPrototype hostFunctionPrototype
                = scriptManager.HostFunctions[strFunctionName];

            // validate parameter count
            if (hostFunctionPrototype.ParameterTypes.Count > iParameterCount)
                throw new ParserException(
                    "Call to host function '" + strFunctionName
                    + "' has missing parameters.");

            if (hostFunctionPrototype.ParameterTypes.Count < iParameterCount)
                throw new ParserException(
                    "Call to host function '" + strFunctionName
                    + "' has too many parameters.");

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.HOST,
                    Operand.CreateHostFunctionRef(hostFunctionPrototype)));

            // 0000 POP tmp

            Variable variable = new Variable();
            variable.m_strName = AllocateTemporaryVariable();
            variable.m_variablescope = VariableScope.Local;
            variable.m_typeInferred = null;

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.POP,
                    Operand.CreateVariable(variable.m_strName)));

            return variable;
        }

        private Variable ParseScriptFunctionCall(bool bThread)
        {
            String strFunctionName = ReadIdentifier();

            // check if function already declared
            bool bFunctionDeclared = true;
            if (!m_scriptExecutable.Functions.ContainsKey(strFunctionName))
                bFunctionDeclared = false;

            ReadLeftParenthesis();

            // parse and push parameters

            uint uiParameterCount = 0;

            // 0000 PUSH tmp1
            // 0001 PUSH tmp2
            // :
            // 0010 PUSH tmpN

            if (PeekToken().Type != TokenType.RightPar)
            {
                while (true)
                {
                    Variable variableParameter = ParseExpression();

                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(
                            Opcode.PUSH,
                            Operand.CreateVariable(variableParameter.m_strName)));

                    ++uiParameterCount;

                    if (PeekToken().Type == TokenType.RightPar)
                        break;
                    else
                        ReadComma();
                }
            }

            ReadRightParenthesis();

            ScriptInstruction scriptInstructionCall = null;
            //ScriptInstruction scriptInstructionRef = null;
            ScriptFunction scriptFunction = null;

            if (bFunctionDeclared)
            {
                // if declared, get function entry point
                scriptFunction
                    = m_scriptExecutable.Functions[strFunctionName];

                if (scriptFunction.ParameterCount > uiParameterCount)
                    throw new ParserException(
                        "Call to function '" + strFunctionName
                        + "' has missing parameters.");

                if (scriptFunction.ParameterCount < uiParameterCount)
                    throw new ParserException(
                        "Call to function '" + strFunctionName
                        + "' has too many parameters.");

                //scriptInstructionRef = scriptFunction.EntryPoint;
            }

            Variable variable = new Variable();

            if (bThread)
            {
                // 0000 THRD (function)
                scriptInstructionCall
                    = new ScriptInstruction(Opcode.THRD,
                        Operand.CreateScriptFunctionRef(scriptFunction));
                m_scriptExecutable.InstructionsInternal.Add(
                    scriptInstructionCall);
            }
            else
            {
                // 0000 CALL (function)
                scriptInstructionCall
                    = new ScriptInstruction(Opcode.CALL,
                        Operand.CreateScriptFunctionRef(scriptFunction));
                m_scriptExecutable.InstructionsInternal.Add(
                    scriptInstructionCall);

                // 0001 POP tmp
                variable.m_strName = AllocateTemporaryVariable();
                variable.m_variablescope = VariableScope.Local;
                variable.m_typeInferred = null;

                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.POP,
                        Operand.CreateVariable(variable.m_strName)));
            }

            if (!bFunctionDeclared)
            {
                // keep forward reference for eventual resolution

                FunctionDescriptor functionDescriptor = new FunctionDescriptor();
                functionDescriptor.m_strFunctionName = strFunctionName;
                functionDescriptor.m_uiParameterCount = uiParameterCount;
                functionDescriptor.m_scriptInstruction = null; // not known yet
                m_dictUnresolvedFunctionCalls[scriptInstructionCall] = functionDescriptor;
                //scriptInstructionRef = null;
            }

            return variable;
        }

        private Variable ParseFunctionCall()
        {
            String strFunctionName = ReadIdentifier();
            UndoToken();

            // check if function name matches host function
            if (m_scriptExecutable.Script.Manager.IsHostFunctionRegistered(
                strFunctionName))
                return ParseHostFunctionCall();
            else
                return ParseScriptFunctionCall(false);
        }

        private Variable ParseIndexedVariable()
        {
            String strIdentifier = ReadIdentifier();

            String strIdentifierArray = strIdentifier;
            String strIdentifierTemp = null;

            // 0000 MOV tmp1, var[idxexpr1]
            // 0001 MOV tmp2, tmp1[idxexpr2]
            // :
            // 000N MOV tmpN, tmpN-1[idxexprN]

            while (PeekToken().Type == TokenType.LeftBracket)
            {
                ReadLeftBracket();

                // (do indexing expression)
                Variable variableIndex = ParseExpression();

                ReadRightBracket();

                strIdentifierTemp = AllocateTemporaryVariable();

                //0000 MOV tmp, var[index expression]
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(strIdentifierTemp),
                        Operand.CreateVariableIndexedVariable(strIdentifierArray, variableIndex.m_strName)));

                strIdentifierArray = strIdentifierTemp;
            }

            Variable variable = new Variable(strIdentifierTemp, VariableScope.Local, null);
            return variable;
        }

        private Variable ParseMemberVariable()
        {
            String strIdentifier = ReadIdentifier();

            String strIdentifierArray = strIdentifier;
            String strIdentifierTemp = null;

            // 0000 MOV tmp1, var[member1]
            // 0001 MOV tmp2, tmp1[member2]
            // :
            // 000N MOV tmpN, tmpN-1[memberN]

            while (PeekToken().Type == TokenType.Period)
            {
                ReadPeriod();

                // (do indexing expression)
                String strMemberValue = ReadIdentifier();

                strIdentifierTemp = AllocateTemporaryVariable();

                //0000 MOV tmp2, var["member"]

                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(strIdentifierTemp),
                        Operand.CreateLiteralIndexedVariable(strIdentifierArray, strMemberValue)));

                strIdentifierArray = strIdentifierTemp;
            }

            Variable variable = new Variable(strIdentifierTemp, VariableScope.Local, null);
            return variable;
        }

        private Variable ParsePreIncrement()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Increment)
                throw new ParserException(
                    "Pre-increment must start with the '++' increment operator.",
                    token);

            String strIdentifier = ReadDeclaredIdentifier();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.INC, Operand.CreateVariable(strIdentifier)));

            return new Variable(strIdentifier, VariableScope.Local, null);
        }

        private Variable ParsePreDecrement()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Decrement)
                throw new ParserException(
                    "Pre-increment must start with the '--' decrement operator.",
                    token);

            String strIdentifier = ReadDeclaredIdentifier();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.DEC, Operand.CreateVariable(strIdentifier)));

            return new Variable(strIdentifier, VariableScope.Local, null);
        }

        private Variable ParsePostIncrement()
        {
            String strIdentifier = ReadDeclaredIdentifier();

            Token token = ReadToken();
            if (token.Type != TokenType.Increment)
                throw new ParserException(
                    "Post-increment must follow the indentifier with the '++' increment operator.",
                    token);

            String strIdentifierTemp = AllocateTemporaryVariable();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierTemp),
                    Operand.CreateVariable(strIdentifier)));

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.INC, Operand.CreateVariable(strIdentifier)));

            return new Variable(strIdentifierTemp, VariableScope.Local, null);
        }

        private Variable ParsePostDecrement()
        {
            String strIdentifier = ReadDeclaredIdentifier();

            Token token = ReadToken();
            if (token.Type != TokenType.Decrement)
                throw new ParserException(
                    "Post-decrement must follow the indentifier with the '--' decrement operator.",
                    token);

            String strIdentifierTemp = AllocateTemporaryVariable();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierTemp),
                    Operand.CreateVariable(strIdentifier)));

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.DEC, Operand.CreateVariable(strIdentifier)));

            return new Variable(strIdentifierTemp, VariableScope.Local, null);
        }

        private Variable ParsePrimary()
        {
            Token token = ReadToken();

            Variable variable = new Variable();

            switch (token.Type)
            {
                case TokenType.Minus:
                    variable = ParsePrimary();

                    //0000 MOV tmp, (expr)
                    //0001 NEG tmp

                    String strIdentifierTemp = AllocateTemporaryVariable();
                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(strIdentifierTemp),
                        Operand.CreateVariable(variable.m_strName)));
                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(Opcode.NEG,
                        Operand.CreateVariable(strIdentifierTemp)));

                    variable.m_strName = strIdentifierTemp;

                    return variable;
                case TokenType.Increment:
                    // pre-increment
                    UndoToken();
                    variable = ParsePreIncrement();
                    return variable;
                case TokenType.Decrement:
                    // pre-decrement
                    UndoToken();
                    variable = ParsePreDecrement();
                    return variable;
                case TokenType.Null:
                    // null reference
                    variable.m_strName = AllocateTemporaryVariable();
                    variable.m_variablescope = VariableScope.Local;
                    variable.m_typeInferred = typeof(NullReference);

                    //0000 MOV tmp, NULL
                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(variable.m_strName),
                        Operand.CreateLiteral(NullReference.Instance)));

                    return variable;
                case TokenType.Integer:
                case TokenType.Float:
                case TokenType.Boolean:
                case TokenType.String:
                    // literal value
                    variable.m_strName = AllocateTemporaryVariable();
                    variable.m_variablescope = VariableScope.Local;
                    variable.m_typeInferred = GetLiteralType(token.Type);

                    //0000 MOV tmp, (literal)
                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(variable.m_strName),
                        Operand.CreateLiteral(token.Lexeme)));

                    return variable;
                case TokenType.Identifier:
                    // simple variable, indexed variable or function call
                    String strIdentifier = token.Lexeme.ToString();

                    switch (PeekToken().Type)
                    {
                        case TokenType.Increment:
                            // post-increment
                            UndoToken(); // undo id
                            return ParsePostIncrement();
                        case TokenType.Decrement:
                            // post-decrement
                            UndoToken(); // undo id
                            return ParsePostDecrement();
                        case TokenType.LeftBracket:
                            // indexed variable
                            UndoToken(); // undo id
                            return ParseIndexedVariable();
                        case TokenType.Period:
                            // indexed variable
                            UndoToken(); // undo id
                            return ParseMemberVariable();
                        case TokenType.LeftPar:
                            // function call
                            UndoToken(); // undo id
                            return ParseFunctionCall();
                        default:
                            // simple variable
                            UndoToken();
                            strIdentifier = ReadDeclaredIdentifier();

                            variable.m_strName = AllocateTemporaryVariable();
                            variable.m_variablescope = VariableScope.Local;
                            variable.m_typeInferred = null;

                            //0000 MOV tmp, var
                            m_scriptExecutable.InstructionsInternal.Add(
                                new ScriptInstruction(Opcode.MOV,
                                    Operand.CreateVariable(variable.m_strName),
                                    Operand.CreateVariable(strIdentifier)));

                            return variable;
                    }
                case TokenType.LeftPar:
                    variable = ParseExpression();
                    ReadRightParenthesis();
                    return variable;
                default:
                    throw new ParserException(
                        "Invalid token '" + token + "' in expression.",
                        token);
            }
        }

        private Variable ParseArrayExpression()
        {
            ReadLeftBrace();

            int iArrayIndex = 0;

            String strIdentifierArray = AllocateTemporaryVariable();
            String strIdentifierIndex = AllocateTemporaryVariable();

            // initialise array variable
            // CLA tmparray

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.CLRA,
                    Operand.CreateVariable(strIdentifierArray)));

            if (PeekToken().Type != TokenType.RightBrace)
            {
                while (true)
                {
                    Variable variableValue = ParseExpression();

                    Token token = PeekToken();
                    if (token.Type == TokenType.Comma
                        || token.Type == TokenType.RightBrace)
                    {
                        // MOV tmparray[idx++], tmpvalue
                        m_scriptExecutable.InstructionsInternal.Add(
                            new ScriptInstruction(Opcode.MOV,
                                Operand.CreateLiteralIndexedVariable(
                                    strIdentifierArray, iArrayIndex++),
                                Operand.CreateVariable(variableValue.m_strName)));

                        if (token.Type == TokenType.RightBrace)
                            break;

                        ReadComma();
                    }
                    else if (token.Type == TokenType.Colon)
                    {
                        ReadToken();

                        Variable variableKey = variableValue;
                        variableValue = ParseExpression();

                        // MOV tmparray[tmpkey], tmpvalue
                        m_scriptExecutable.InstructionsInternal.Add(
                            new ScriptInstruction(Opcode.MOV,
                                Operand.CreateVariableIndexedVariable(
                                    strIdentifierArray, variableKey.m_strName),
                                Operand.CreateVariable(variableValue.m_strName)));

                        if (PeekToken().Type == TokenType.RightBrace)
                            break;

                        ReadComma();
                    }
                    else
                        throw new ParserException(
                            "Comma ',' or colon ':' expected in array expression.");
                }
            }

            ReadRightBrace();

            Variable variableArray = new Variable(strIdentifierArray,
                VariableScope.Local, typeof(AssociativeArray));
            return variableArray;
        }

        private Variable ParseFactor()
        {
            // array is a 'factor' that can be added to
            // or substracted from
            if (PeekToken().Type == TokenType.LeftBrace)
                return ParseArrayExpression();

            Variable variableBase = ParsePrimary();

            Token token = PeekToken();

            if (token.Type == TokenType.Power)
            {
                ReadToken();

                Variable variablePower = new Variable();
                if (PeekToken().Type == TokenType.LeftPar)
                {
                    // exponent is expression in parentheses
                    ReadToken();
                    variablePower = ParseExpression();
                    ReadRightParenthesis();

                    variableBase.m_typeInferred
                        = VerifyInferredType(token,
                            variableBase.m_typeInferred,
                            variablePower.m_typeInferred);
                }
                else
                    variablePower = ParsePrimary();

                //0000 POW tmpBase, tmpPower
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.POW,
                        Operand.CreateVariable(variableBase.m_strName),
                        Operand.CreateVariable(variablePower.m_strName)));
            }

            // if no power, leave as is
            return variableBase;
        }

        private Variable ParseTerm()
        {
            List<ScriptInstruction> listInstructions
                = m_scriptExecutable.InstructionsInternal;

            Variable variableTemp1 = ParseFactor();
            Variable variableTemp2 = new Variable();

            while (true)
            {
                Token token = ReadToken();

                switch (token.Type)
                {
                    case TokenType.Multiply:
                        variableTemp2 = ParseFactor();

                        //0000 MUL tmp1, tmp2
                        listInstructions.Add(
                            new ScriptInstruction(Opcode.MUL,
                                Operand.CreateVariable(variableTemp1.m_strName),
                                Operand.CreateVariable(variableTemp2.m_strName)));

                        variableTemp1.m_typeInferred = VerifyInferredType(token,
                            variableTemp1.m_typeInferred, variableTemp2.m_typeInferred);
                        break;
                    case TokenType.Divide:
                        variableTemp2 = ParseFactor();

                        //0000 DIV tmp1, tmp2
                        listInstructions.Add(
                            new ScriptInstruction(Opcode.DIV,
                                Operand.CreateVariable(variableTemp1.m_strName),
                                Operand.CreateVariable(variableTemp2.m_strName)));

                        variableTemp1.m_typeInferred = VerifyInferredType(token,
                            variableTemp1.m_typeInferred, variableTemp2.m_typeInferred);
                        break;
                    case TokenType.Modulo:
                        variableTemp2 = ParseFactor();

                        //0000 MOD tmp1, tmp2
                        listInstructions.Add(
                            new ScriptInstruction(Opcode.MOD,
                                Operand.CreateVariable(variableTemp1.m_strName),
                                Operand.CreateVariable(variableTemp2.m_strName)));

                        variableTemp1.m_typeInferred = VerifyInferredType(token,
                            variableTemp1.m_typeInferred, variableTemp2.m_typeInferred);
                        break;
                    default:
                        UndoToken();
                        return variableTemp1;
                }
            }
        }

        private Variable ParseArithmeticExpression()
        {
            List<ScriptInstruction> listInstructions
                = m_scriptExecutable.InstructionsInternal;

            Variable variableTemp1 = ParseTerm();
            Variable variableTemp2 = new Variable();

            while(true)
            {
                Token token = ReadToken();

                switch (token.Type)
                {
                    case TokenType.Plus:
                        variableTemp2 = ParseTerm();

                        //0000 ADD tmp1, tmp2
                        listInstructions.Add(
                            new ScriptInstruction(Opcode.ADD,
                                Operand.CreateVariable(variableTemp1.m_strName),
                                Operand.CreateVariable(variableTemp2.m_strName)));

                        variableTemp1.m_typeInferred = VerifyInferredType(token,
                            variableTemp1.m_typeInferred, variableTemp2.m_typeInferred);
                        break;
                    case TokenType.Minus:
                        variableTemp2 = ParseTerm();

                        //0000 SUB tmp1, tmp2
                        listInstructions.Add(
                            new ScriptInstruction(Opcode.SUB,
                                Operand.CreateVariable(variableTemp1.m_strName),
                                Operand.CreateVariable(variableTemp2.m_strName)));
                        break;
                    default:
                        UndoToken();
                        return variableTemp1;
                }
            }
        }

        private Variable ParseRelation()
        {
            List<ScriptInstruction> listInstructions
                = m_scriptExecutable.InstructionsInternal;

            Variable variableTemp1 = ParseArithmeticExpression();
            Variable variableTemp2 = new Variable();

            Token token = ReadToken();

            Opcode opcodeRelational = Opcode.CEQ;
            bool bRelationalOperator = true;
            switch (token.Type)
            {
                case TokenType.Equal: opcodeRelational = Opcode.CEQ; break;
                case TokenType.NotEqual: opcodeRelational = Opcode.CNE; break;
                case TokenType.Greater: opcodeRelational = Opcode.CG; break;
                case TokenType.GreaterOrEqual: opcodeRelational = Opcode.CGE; break;
                case TokenType.Less: opcodeRelational = Opcode.CL; break;
                case TokenType.LessOrEqual: opcodeRelational = Opcode.CLE; break;
                default: bRelationalOperator = false; break;
            }

            if (bRelationalOperator)
            {
                variableTemp2 = ParseArithmeticExpression();

                //0000 (cmp) tmp1, tmp2
                listInstructions.Add(
                    new ScriptInstruction(opcodeRelational,
                        Operand.CreateVariable(variableTemp1.m_strName),
                        Operand.CreateVariable(variableTemp2.m_strName)));

                variableTemp1.m_typeInferred = VerifyInferredType(token,
                    variableTemp1.m_typeInferred, variableTemp2.m_typeInferred);

                return variableTemp1;
            }
            else
            {
                UndoToken();
                return variableTemp1;
            }
        }

        private Variable ParseProposition()
        {
            Variable variable = new Variable();

            if (PeekToken().Type == TokenType.Not)
            {
                ReadToken();

                if (PeekToken().Type == TokenType.LeftPar)
                    variable = ParseExpression();
                else
                    variable = ParseRelation();

                //0000 NOT tmp1
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.NOT,
                        Operand.CreateVariable(variable.m_strName)));

                return variable;
            }
            else
                return ParseRelation();
        }

        private Variable ParseConjunction()
        {
            List<ScriptInstruction> listInstructions
                = m_scriptExecutable.InstructionsInternal;

            Variable variableTemp1 = ParseProposition();
            
            while (true)
            {
                Token token = ReadToken();

                if (token.Type == TokenType.And)
                {
                    Variable variableTemp2 = ParseProposition();

                    //0000 AND tmp1, tmp2
                    listInstructions.Add(
                        new ScriptInstruction(Opcode.AND,
                            Operand.CreateVariable(variableTemp1.m_strName),
                            Operand.CreateVariable(variableTemp2.m_strName)));

                    variableTemp1.m_typeInferred = VerifyInferredType(token,
                        variableTemp1.m_typeInferred, variableTemp2.m_typeInferred);
                    break;
                }
                else
                {
                    UndoToken();
                    return variableTemp1;
                }
            }

            return variableTemp1;
        }

        private Variable ParseDisjunction()
        {
            List<ScriptInstruction> listInstructions
                = m_scriptExecutable.InstructionsInternal;

            Variable variableTemp1 = ParseConjunction();

            while (true)
            {
                Token token = ReadToken();

                if (token.Type == TokenType.Or)
                {
                    Variable variableTemp2 = ParseConjunction();

                    //0000 OR tmp1, tmp2
                    listInstructions.Add(
                        new ScriptInstruction(Opcode.OR,
                            Operand.CreateVariable(variableTemp1.m_strName),
                            Operand.CreateVariable(variableTemp2.m_strName)));

                    variableTemp1.m_typeInferred = VerifyInferredType(token,
                        variableTemp1.m_typeInferred, variableTemp2.m_typeInferred);

                    break;
                }
                else
                {
                    UndoToken();
                    return variableTemp1;
                }
            }

            return variableTemp1;
        }

        private Variable ParseSimpleVariableAssignment()
        {
            String strIdentifier = ReadDeclaredIdentifier();

            Token tokenAssign = ReadToken();

            if (!IsAssignmentOperator(tokenAssign.Type))
                throw new ParserException(
                    "Assignment operator '=' or one of its variants expected in assignment statement.",
                    tokenAssign);

            // do expression
            Variable variableExpression = ParseExpression();

            // (op) dest, tmpexpr
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(GetAssignmentOpcode(tokenAssign.Type),
                    Operand.CreateVariable(strIdentifier),
                    Operand.CreateVariable(variableExpression.m_strName)));

            // alloc tmp var for assign expr tmp
            String strIdentifierTmp = AllocateTemporaryVariable();
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierTmp),
                    Operand.CreateVariable(strIdentifier)));
            Variable variable = new Variable(strIdentifierTmp, VariableScope.Local,
                variableExpression.m_typeInferred);
            return variable;
        }

        private Variable ParseIndexedVariableAssignment()
        {
            /*
             * x[e1][e2][e3] (OP)= (expr)
             * 
             * MOV  tmp1e,       (e1)
             * MOV  tmp1,        x[tmp1e]
             * MOV  tmp2e,       (e2)
             * MOV  tmp2,        tmp1[tmp2e]
             * MOV  tmp3e,       (e3)
             * 
             * MOV  tmpexpr,     (expr)
             * (op) tmp2[tmp3e], tmpexpr;
             */

            String strIdentifier = ReadDeclaredIdentifier();

            List<ScriptInstruction> listInstructions
                = m_scriptExecutable.InstructionsInternal;

            Variable variableElementTmp = new Variable();
            String strIdentifierSource = strIdentifier;
            String strIdentifierDest = null;
            while (!IsAssignmentOperator(PeekToken().Type))
            {
                ReadLeftBracket();
                variableElementTmp = ParseExpression();
                ReadRightBracket();

                if (!IsAssignmentOperator(PeekToken().Type))
                {
                    strIdentifierDest = AllocateTemporaryVariable();
                    // if not last element, dereference one level
                    listInstructions.Add(
                        new ScriptInstruction(Opcode.MOV,
                            Operand.CreateVariable(strIdentifierDest),
                            Operand.CreateVariableIndexedVariable(
                                strIdentifierSource, variableElementTmp.m_strName)));
                    strIdentifierSource = strIdentifierDest;
                }
            }

            Token tokenAssign = ReadToken();

            Variable variableExpression = ParseExpression();

            // handle one-level index
            if (strIdentifierDest == null)
                strIdentifierDest = strIdentifier;

            // innermost level assignment
            listInstructions.Add(
                new ScriptInstruction(GetAssignmentOpcode(tokenAssign.Type),
                    Operand.CreateVariableIndexedVariable(strIdentifierDest, variableElementTmp.m_strName),
                    Operand.CreateVariable(variableExpression.m_strName)));

            // alloc tmp var for assign expr tmp
            String strIdentifierTmp = AllocateTemporaryVariable();
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierTmp),
                   Operand.CreateVariableIndexedVariable(strIdentifierDest, variableElementTmp.m_strName)));
            Variable variable = new Variable(strIdentifierTmp, VariableScope.Local,
                variableExpression.m_typeInferred);
            return variable;
        }

        private Variable ParseMemberVariableAssignment()
        {
            /* x.a.b.c.d (op)= val;
             * 
             * 0000 MOV tmp1, x["a"]
             * 0001 MOV tmp2, tmp1["b"]
             * 0002 MOV tmp3, tmp2["c"]
             * 0004 (op) tmp3["d"], val
             */

            // single or multi-indexed assignment

            String strIdentifier = ReadDeclaredIdentifier();

            List<String> listMemberValues = new List<String>();

            while (PeekToken().Type == TokenType.Period)
            {
                ReadPeriod();
                listMemberValues.Add(ReadIdentifier());
            }

            Token tokenAssign = ReadToken();
            if (!IsAssignmentOperator(tokenAssign.Type))
                throw new ParserException(
                    "Assign '=' operator or assignment variant expected in member variable assignment.",
                    tokenAssign);

            // do expression
            Variable variableExpression = ParseExpression();

            String strIdentifierDest = null;
            String strIdentifierSource = strIdentifier;
            for (int iIndex = 0; iIndex < listMemberValues.Count - 1; iIndex++)
            {
                strIdentifierDest = AllocateTemporaryVariable();
                String strIdentifierSourceIndex = listMemberValues[iIndex];

                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(strIdentifierDest),
                        Operand.CreateLiteralIndexedVariable(strIdentifierSource,
                            strIdentifierSourceIndex)));

                strIdentifierSource = strIdentifierDest;
            }

            // handle simple 1-level case
            if (strIdentifierDest == null)
                strIdentifierDest = strIdentifier;

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(GetAssignmentOpcode(tokenAssign.Type),
                    Operand.CreateLiteralIndexedVariable(strIdentifierDest,
                        listMemberValues[listMemberValues.Count - 1]),
                    Operand.CreateVariable(variableExpression.m_strName)));



            // alloc tmp var for assign expr tmp
            String strIdentifierTmp = AllocateTemporaryVariable();
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierTmp),
                    Operand.CreateLiteralIndexedVariable(strIdentifierDest,
                        listMemberValues[listMemberValues.Count - 1])));
            Variable variable = new Variable(strIdentifierTmp, VariableScope.Local,
                variableExpression.m_typeInferred);
            return variable;
        }

        private Variable ParseAssignment()
        {
            String strIdentifier = ReadDeclaredIdentifier();

            Token token = PeekToken();
            switch (token.Type)
            {
                case TokenType.Assign:
                case TokenType.AssignPlus:
                case TokenType.AssignMinus:
                case TokenType.AssignMultiply:
                case TokenType.AssignDivide:
                case TokenType.AssignPower:
                case TokenType.AssignModulo:
                    // simple variable assignment
                    UndoToken(); // undo id
                    return ParseSimpleVariableAssignment();
                case TokenType.LeftBracket:
                    // single or multi-indexed assignment
                    UndoToken(); // undo id
                    return ParseIndexedVariableAssignment();
                default:
                    // single or multi-member assignment
                    UndoToken(); // undo id
                    return ParseMemberVariableAssignment();
            }
        }

        private bool IsArrayLValue()
        {
            Token tokenStart = PeekToken();
            if (tokenStart.Type != TokenType.Identifier)
                return false;

            // checkpoint
            int iInstructionCheckpoint = m_scriptExecutable.InstructionsInternal.Count;

            String strIdentifier = ReadIdentifier();

            while (PeekToken().Type == TokenType.LeftBracket)
            {
                ReadLeftBracket();
                ParseExpression();
                ReadRightBracket();
            }

            Token tokenNext = ReadToken();

            while (PeekToken() != tokenStart)
                UndoToken();

            // rollback instructions
            m_scriptExecutable.InstructionsInternal.RemoveRange(
                iInstructionCheckpoint,
                m_scriptExecutable.InstructionsInternal.Count - iInstructionCheckpoint);

            return IsAssignmentOperator(tokenNext.Type);
        }

        private bool IsMemberLValue()
        {
            Token tokenStart = PeekToken();
            if (tokenStart.Type != TokenType.Identifier)
                return false;

            String strIdentifier = ReadIdentifier();

            while (PeekToken().Type == TokenType.Period)
            {
                ReadPeriod();
                Token token = ReadToken();
                if (token.Type != TokenType.Identifier)
                {
                    while (PeekToken() != tokenStart)
                        UndoToken();
                    return false;
                }
            }

            Token tokenNext = ReadToken();

            while (PeekToken() != tokenStart)
                UndoToken();

            return IsAssignmentOperator(tokenNext.Type);
        }

        private Variable ParseExpression()
        {
            if (IsArrayLValue() || IsMemberLValue())
                return ParseAssignment();
            else
                return ParseDisjunction();
        }

        private void ParseConditionalStatement()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.If)
                throw new ParserException(
                    "Keyword 'if' expected for conditional statement.",
                    token);

            ReadLeftParenthesis();

            Variable variableCondition = ParseExpression();

            ReadRightParenthesis();

            // 0000 (condition expr)
            // :
            // 0010 JF (expr) 0021
            // 0011 (true block)
            // :
            // 0020 JMP 0030
            // 0021 NOP (false addr)
            // 0022 (false block - or empty)
            // :
            // 0030 NOP (end addr)

            ScriptInstruction scriptInstructionNOPFalse
                = new ScriptInstruction(Opcode.NOP);

            ScriptInstruction scriptInstructionNOPEnd
                = new ScriptInstruction(Opcode.NOP);

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JF,
                    Operand.CreateVariable(variableCondition.m_strName),
                    Operand.CreateInstructionRef(scriptInstructionNOPFalse)));

            ParseStatementBlock();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JMP,
                    Operand.CreateInstructionRef(scriptInstructionNOPEnd)));

            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPFalse);

            if (PeekToken().Type == TokenType.Else)
            {
                ReadToken();
                ParseStatementBlock();
            }

            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPEnd);
        }

        private void ParseWhileStatement()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.While)
                throw new ParserException(
                    "Keyword 'while' expected for while() loop.",
                    token);

            // 0000 NOP (start)
            // 0001 (condition expr)
            // :
            // 0010 JF  (cond) 0021
            // 0011 (while block)
            // :
            // 0020 JMP [0000]
            // 0021 NOP (end addr)

            ScriptInstruction scriptInstructionNOPStart
                = new ScriptInstruction(Opcode.NOP);
            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPStart);

            ReadLeftParenthesis();

            Variable variableCondition = ParseExpression();

            if (variableCondition.m_typeInferred != null
                && variableCondition.m_typeInferred != typeof(bool))
                throw new ParserException(
                    "For statement condition must be a logical expression.",
                    token);

            ReadRightParenthesis();

            ScriptInstruction scriptInstructionNOPEnd
                = new ScriptInstruction(Opcode.NOP);

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JF,
                    Operand.CreateVariable(variableCondition.m_strName),
                    Operand.CreateInstructionRef(scriptInstructionNOPEnd)));

            // loop control for break/continue statements
            LoopControl loopControl = new LoopControl();
            loopControl.m_scriptInstructionBreak = scriptInstructionNOPEnd;
            loopControl.m_scriptInstructionContinue = scriptInstructionNOPStart;
            m_stackLoopControl.Push(loopControl);

            ParseStatementBlock();

            // pop loop control from stack
            m_stackLoopControl.Pop();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JMP,
                    Operand.CreateInstructionRef(scriptInstructionNOPStart)));

            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPEnd);
        }

        private void ParseForStatement()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.For)
                throw new ParserException(
                    "Keyword 'for' expected for for() loop.",
                    token);

            // 0000 (initial assignment)
            // :
            // 0010 NOP (for start)
            // 0011 (termination expr)
            // :
            // 0020 JF (expr) 0041
            // 0021 (inner block)
            // :
            // 0030 NOP (loop expr - continue)
            // 0031 (loop expression)
            // :
            // 0040 JMP [0010]
            // 0041 NOP (end addr)

            ReadLeftParenthesis();

            // initialiser is optional
            if (PeekToken().Type == TokenType.SemiColon)
                ReadSemicolon();
            else if (PeekToken().Type == TokenType.Var)
                ParseLocalVariableDeclaration();
            else
            {
                ParseAssignment();
                ReadSemicolon();
            }

            ScriptInstruction scriptInstructionNOPStart
                = new ScriptInstruction(Opcode.NOP);
            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPStart);

            ScriptInstruction scriptInstructionNOPContinue
                = new ScriptInstruction(Opcode.NOP);

            // condition expression is optional
            Variable variableCondition = new Variable();
            if (PeekToken().Type == TokenType.SemiColon)
            {
                // MOV tmp, TRUE
                variableCondition = new Variable(AllocateTemporaryVariable(),
                    VariableScope.Local, typeof(bool));
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(variableCondition.m_strName),
                        Operand.CreateLiteral(true)));
                ReadSemicolon();
            }
            else
            {
                // MOV tmp, (cond expr)
                variableCondition = ParseExpression();

                if (variableCondition.m_typeInferred != null
                    && variableCondition.m_typeInferred != typeof(bool))
                    throw new ParserException(
                        "For statement condition must be a logical expression.",
                        token);

                ReadSemicolon();
            }

            // looping assignment is optional
            List<ScriptInstruction> listInstructionsLoopExpression
                = null;
            if (PeekToken().Type != TokenType.RightPar)
            {
                // track looping assignment block for eventual displacement
                int iLoopExpressionStart = m_scriptExecutable.InstructionsInternal.Count;

                ParseExpression();

                // looping expression instruction count
                int iLoopExpressionCount
                    = m_scriptExecutable.InstructionsInternal.Count - iLoopExpressionStart; ;

                // extract looping assignment
                listInstructionsLoopExpression
                    = m_scriptExecutable.InstructionsInternal.GetRange(
                        iLoopExpressionStart, iLoopExpressionCount);
                m_scriptExecutable.InstructionsInternal.RemoveRange(
                    iLoopExpressionStart, iLoopExpressionCount);
            }
            else
                listInstructionsLoopExpression = new List<ScriptInstruction>();

            ReadRightParenthesis();

            ScriptInstruction scriptInstructionNOPEnd
                = new ScriptInstruction(Opcode.NOP);

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JF,
                    Operand.CreateVariable(variableCondition.m_strName),
                    Operand.CreateInstructionRef(scriptInstructionNOPEnd)));

            // loop control for break/continue statements
            LoopControl loopControl = new LoopControl();
            loopControl.m_scriptInstructionBreak = scriptInstructionNOPEnd;
            loopControl.m_scriptInstructionContinue = scriptInstructionNOPContinue;
            m_stackLoopControl.Push(loopControl);

            // parse for block
            ParseStatementBlock();

            // pop loop control from stack
            m_stackLoopControl.Pop();

            // NOP (loop expression)
            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPContinue);

            // re-insert looping expression before jump
            m_scriptExecutable.InstructionsInternal.AddRange(
                listInstructionsLoopExpression);

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JMP,
                    Operand.CreateInstructionRef(scriptInstructionNOPStart)));

            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPEnd);
        }

        private void ParseForeachStatement()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Foreach)
                throw new ParserException(
                    "Keyword 'foreach' expected for foreach() loop.", token);

            ReadLeftParenthesis();

            // iterator
            String strIdentifierKey = null;
            String strIdentifierValue = ReadDeclaredIdentifier();

            token = ReadToken();

            // optional key
            if (token.Type == TokenType.Comma)
            {
                strIdentifierKey = strIdentifierValue;
                strIdentifierValue = ReadDeclaredIdentifier();
                token = ReadToken();
            }

            if (token.Type != TokenType.In)
                throw new ParserException(
                    "Keyword 'in' expected after iterator variable.",
                    token);

            // tmpkey := tmkpkey or key
            if (strIdentifierKey == null)
                strIdentifierKey = AllocateTemporaryVariable();

            // 0000 MOV tmpkey, NULL
            // 0001 NOP (expr start)
            // 0002 (array expr)
            // :
            // 0011 NXT tmpkey, (array expr)
            // 0012 MOV tmp, tmpkey
            // 0012 CNL tmp
            // 0012 JT  tmp, [0021]
            // 0013 MOV value, (array expr)[tmpkey]
            // 0013 (inner block)
            // :
            // 0020 JMP [0001]
            // 0021 NOP (end addr)

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierKey),
                    Operand.CreateLiteral(NullReference.Instance)));

            ScriptInstruction scriptInstrucitonNOPArrayExpression
                = new ScriptInstruction(Opcode.NOP);
            m_scriptExecutable.InstructionsInternal.Add(
                scriptInstrucitonNOPArrayExpression);

            // associative array
            Variable variableArray = ParseExpression();
            if (variableArray.m_typeInferred != null
                && variableArray.m_typeInferred != typeof(AssociativeArray))
                throw new ParserException(
                    "Invalid inferred array type detected in 'foreach' statement.",
                    token);

            ReadRightParenthesis();

             m_scriptExecutable.InstructionsInternal.Add(
                 new ScriptInstruction(Opcode.NEXT,
                    Operand.CreateVariable(strIdentifierKey),
                    Operand.CreateVariable(variableArray.m_strName)));

            String strIdentifierTemp = AllocateTemporaryVariable();

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierTemp),
                    Operand.CreateVariable(strIdentifierKey)));

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.CNL,
                    Operand.CreateVariable(strIdentifierTemp)));

            ScriptInstruction scriptInstructionNOPEnd
                = new ScriptInstruction(Opcode.NOP);
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JT,
                    Operand.CreateVariable(strIdentifierTemp),
                    Operand.CreateInstructionRef(scriptInstructionNOPEnd)));

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierValue),
                    Operand.CreateVariableIndexedVariable(variableArray.m_strName, strIdentifierKey)));

            // loop control for break/continue statements
            LoopControl loopControl = new LoopControl();
            loopControl.m_scriptInstructionBreak = scriptInstructionNOPEnd;
            loopControl.m_scriptInstructionContinue = scriptInstrucitonNOPArrayExpression;
            m_stackLoopControl.Push(loopControl);

            // inner block
            ParseStatementBlock();

            // pop loop control from stack
            m_stackLoopControl.Pop();

            // loop back
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JMP,
                    Operand.CreateInstructionRef(scriptInstrucitonNOPArrayExpression)));

            m_scriptExecutable.InstructionsInternal.Add(
                scriptInstructionNOPEnd);
        }

        private void ParseBreak()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Break)
                throw new ParserException(
                    "Keyword 'break' expected for break instruction.",
                    token);
            ReadSemicolon();

            if (m_stackLoopControl.Count == 0)
                throw new ParserException(
                    "Keyword 'break' can only be used in a looping construct.",
                    token);

            ScriptInstruction scriptInstructionBreak
                = m_stackLoopControl.Peek().m_scriptInstructionBreak;

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JMP,
                Operand.CreateInstructionRef(scriptInstructionBreak)));
        }

        private void ParseContinue()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Continue)
                throw new ParserException(
                    "Keyword 'continue' expected for continue instruction.",
                    token);
            ReadSemicolon();

            if (m_stackLoopControl.Count == 0)
                throw new ParserException(
                    "Keyword 'continue' can only be used in a looping construct.",
                    token);

            ScriptInstruction scriptInstructionContinue
                = m_stackLoopControl.Peek().m_scriptInstructionContinue;

            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.JMP,
                Operand.CreateInstructionRef(scriptInstructionContinue)));
        }

        private void ParseSwitchStatement()
        {
            // switch (var)
            // {
            //   case v1:
            //   case v2:
            //   case v3:
            //       (stmt1)
            //   case v4:
            //       (stmt2)
            //   default:
            //       (stmt3)
            // }
            //
            //     MOV tmpcasemulti, FALSE
            //     (v1)
            //     MOV tmpcase, var
            //     CEQ tmpcase, (v1)
            //     OR tmpcasemulti, tmpcase
            //     (v2)
            //     MOV tmpcase, var
            //     CEQ tmpcase, (v2)
            //     OR tmpcasemulti, tmpcase
            //     (v3)
            //     MOV tmpcase, var
            //     CEQ tmpcase, (v3)
            //     OR tmpcasemulti, tmpcase
            //     JF tmpcasemulti, No_Stmt1
            //     (stmt1)
            //     JMP End_Switch
            // No_Stmt1:
            //     MOV tmpcasemulti, FALSE
            //     (v4)
            //     MOV tmpcase, var
            //     CEQ tmpcase, (v4)
            //     OR tmpcasemulti, tmpcase
            //     JF tmpcasemulti, No_Stmt2 (default)
            //     (stmt2)
            //     JMP End_Switch
            // No_Stmt2:
            //     (stmt3)
            // End_Switch:
            //
            Token token = ReadToken();
            if (token.Type != TokenType.Switch)
                throw new ParserException(
                    "Keyword 'switch' expected for switch statement.",
                    token);

            ReadLeftParenthesis();
            String strIdentifierSwitch = ReadDeclaredIdentifier();
            ReadRightParenthesis();

            ReadLeftBrace();

            token = PeekToken();
            if (token.Type != TokenType.Case && token.Type != TokenType.Default)
                throw new ParserException(
                    "Keyword 'case' or 'default' expected in switch statement.",
                    token);

            String strIdentifierTmpCaseMulti = AllocateTemporaryVariable();
            String strIdentifierTmpCase = AllocateTemporaryVariable();

            ScriptInstruction scriptInstructionNOPEndSwith
                = new ScriptInstruction(Opcode.NOP);

            // MOV tmpcasemulti, FALSE
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.MOV,
                    Operand.CreateVariable(strIdentifierTmpCaseMulti),
                    Operand.CreateLiteral(false)));

            while (PeekToken().Type != TokenType.Default
                && PeekToken().Type != TokenType.RightBrace)
            {
                token = ReadToken();
                if (token.Type != TokenType.Case)
                    throw new ParserException(
                        "Keyword 'case' expected in switch statement.",
                        token);

                InsertDebugInfo(token);

                // (vi)
                Variable variableExpression = ParseExpression();

                token = ReadToken();
                if (token.Type != TokenType.Colon)
                    throw new ParserException(
                        "Colon ':' expected after case expression.",
                        token);

                // MOV tmpcase, var
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.MOV,
                        Operand.CreateVariable(strIdentifierTmpCase),
                        Operand.CreateVariable(strIdentifierSwitch)));

                // CEQ tmpcase, (vi)
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.CEQ,
                        Operand.CreateVariable(strIdentifierTmpCase),
                        Operand.CreateVariable(variableExpression.m_strName)));

                // OR tmpcasemulti, tmpcase
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.OR,
                        Operand.CreateVariable(strIdentifierTmpCaseMulti),
                        Operand.CreateVariable(strIdentifierTmpCase)));

                if (PeekToken().Type != TokenType.Case)
                {
                    // assume start of case statement block
                    ScriptInstruction scriptInstructionNOPAfterBlock
                        = new ScriptInstruction(Opcode.NOP);

                    //JF tmpcasemulti, No_Stmti
                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(Opcode.JF,
                            Operand.CreateVariable(strIdentifierTmpCaseMulti),
                            Operand.CreateInstructionRef(scriptInstructionNOPAfterBlock)));

                    // (stmti)
                    ParseStatement();

                    // JMP End_Switch
                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(Opcode.JMP,
                            Operand.CreateInstructionRef(scriptInstructionNOPEndSwith)));

                    // No_stmti:
                    m_scriptExecutable.InstructionsInternal.Add(scriptInstructionNOPAfterBlock);

                    // MOV tmpcasemulti, FALSE  (reset multi-case)
                    m_scriptExecutable.InstructionsInternal.Add(
                        new ScriptInstruction(Opcode.MOV,
                            Operand.CreateVariable(strIdentifierTmpCaseMulti),
                            Operand.CreateLiteral(false)));
                }
            }

            token = ReadToken();

            // end if no default case
            if (token.Type == TokenType.RightBrace)
            {
                // EndSwitch:
                m_scriptExecutable.InstructionsInternal.Add(
                    scriptInstructionNOPEndSwith);
                return;
            }

            if (token.Type != TokenType.Default)
                throw new ParserException(
                    "Default case or closing brace '}' expected at end of switch statement.",
                    token);

            token = ReadToken();
            if (token.Type != TokenType.Colon)
                throw new ParserException(
                    "Colon ':' expected in default case expression.", token);

            // default stmt block
            ParseStatement();

            // closing brace (when default case included)
            ReadRightBrace();

            // EndSwitch:
            m_scriptExecutable.InstructionsInternal.Add(
                scriptInstructionNOPEndSwith);
        }

        private void ParseReturn()
        {
            Token token = ReadToken();
            if (token.Type != TokenType.Return)
                throw new ParserException(
                    "Return statement must start with the 'notify' keyword.",
                    token);

            if (PeekToken().Type == TokenType.SemiColon)
            {
                // return;
                ReadToken();
                // 0000 PUSH NULL
                // 0001 RET
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.PUSH,
                        Operand.CreateLiteral(NullReference.Instance)));
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.RET));
                return;
            }
            else
            {
                // return (expr);
                Variable variableReturn = ParseExpression();

                ReadSemicolon();

                // 0000 (expression)
                // :
                // 0010 PUSH tmp
                // 0002 RET
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.PUSH,
                        Operand.CreateVariable(variableReturn.m_strName)));
                m_scriptExecutable.InstructionsInternal.Add(
                    new ScriptInstruction(Opcode.RET));
                return;
            }
        }

        private void ParseStatement()
        {
            AllocateTemporaryVariableLevel();

            Token token = PeekToken();

            InsertDebugInfo(token);

            switch (token.Type)
            {
                case TokenType.SemiColon: ReadToken(); break; // empty statement
                case TokenType.Var: ParseLocalVariableDeclaration(); break;
                case TokenType.Yield: ParseYield(); break;
                case TokenType.Wait: ParseWait(); break;
                case TokenType.Notify: ParseNotify(); break;
                case TokenType.Lock: ParseLockedStatementBlock(); break;
                case TokenType.LeftBrace: ParseStatementBlock(); break;
                case TokenType.Increment:
                case TokenType.Decrement:
                case TokenType.LeftPar:
                case TokenType.Identifier:
                case TokenType.Null:
                case TokenType.Integer:
                case TokenType.Float:
                case TokenType.Boolean:
                case TokenType.String:
                    ParseExpression(); ReadSemicolon(); break; // throw away result
                case TokenType.If: ParseConditionalStatement(); break;
                case TokenType.While: ParseWhileStatement(); break;
                case TokenType.For: ParseForStatement(); break;
                case TokenType.Foreach: ParseForeachStatement(); break;
                case TokenType.Break: ParseBreak(); break;
                case TokenType.Continue: ParseContinue(); break;
                case TokenType.Switch: ParseSwitchStatement(); break;
                case TokenType.Return: ParseReturn(); break;
                case TokenType.Thread: ParseThreadCall(); break;
                default:
                    throw new ParserException(
                        "Unexpected token '" + token.Lexeme + "'.", token);
            }

            FreeTemporaryVariableLevel();
        }

        private void ParseStatementBlock()
        {
            Token token = PeekToken();

            // check if single statement
            if (token.Type != TokenType.LeftBrace)
            {
                ParseStatement();
                return;
            }

            // otherwise, statement block
            ReadToken();

            while (PeekToken().Type != TokenType.RightBrace)
                ParseStatement();

            ReadRightBrace();
        }

        private void ParseLockedStatementBlock()
        {
            Token token = ReadToken();

            // check if single statement
            if (token.Type != TokenType.Lock)
                throw new ExecutionException(
                    "Keyword 'lock' expected for lock statement block.");

            Variable variable = ParseExpression();

            // LOCK (tmpexpr)
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.LOCK,
                    Operand.CreateVariable(variable.m_strName)));

            ParseStatement();

            // ULCK (tmpexpr)
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.ULCK,
                    Operand.CreateVariable(variable.m_strName)));
        }

        private void ParseFunctionDeclaration()
        {
            // get function
            Token token = ReadToken();
            if (token.Type != TokenType.Function)
                throw new ParserException(
                    "Function declaration must start with the 'function' keyword.",
                    token);

            // function name
            String strFunctionName = ReadIdentifier();

            if (m_scriptExecutable.Functions.ContainsKey(strFunctionName))
                throw new ParserException(
                    "Function '" + strFunctionName + "' already declared.",
                    token);

            ReadLeftParenthesis();

            InsertDebugInfo(token);

            // function parameters

            List<String> listParameters = new List<string>();

            if (PeekToken().Type != TokenType.RightPar)
            {
                while (true)
                {
                    token = ReadToken();
                    if (token.Type != TokenType.Identifier)
                        throw new ParserException(
                            "Unexpected token '" + token.Lexeme
                            + "' in function declaration.", token);

                    String strParameterName = token.Lexeme.ToString();

                    DeclareLocalVar(strParameterName);
                    listParameters.Add(strParameterName);

                    token = PeekToken();
                    if (token.Type == TokenType.Comma)
                        ReadComma();
                    else if (token.Type == TokenType.RightPar)
                        break;
                    else
                        throw new ParserException(
                           "Comma ',' or right parenthesis ')' expected in function declararion.");
                }
            }

            ReadRightParenthesis();

            // 0000 NOP
            ScriptInstruction scriptInstructionFunctionEntry
                = new ScriptInstruction(Opcode.NOP);
            m_scriptExecutable.InstructionsInternal.Add(scriptInstructionFunctionEntry);

            // add function descriptor
            ScriptFunction scriptFunction
                = new ScriptFunction(m_scriptExecutable, strFunctionName,
                    listParameters, scriptInstructionFunctionEntry);
            m_scriptExecutable.Functions[strFunctionName] = scriptFunction;

            // 0000 POP IdN
            // 0001 POP Id(N-1)
            // :
            // 000N PoP Id0
            listParameters.Reverse();
            foreach (String strParameter in listParameters)
            {
                ScriptInstruction scriptInstructionPop
                    = new ScriptInstruction(
                        Opcode.POP,
                        Operand.CreateVariable(strParameter));
                m_scriptExecutable.InstructionsInternal.Add(scriptInstructionPop);
            }

            // function block
            ParseStatementBlock();

            // 0000 PUSH NULL
            // 0001 RET
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.PUSH,
                    Operand.CreateLiteral(NullReference.Instance)));
            m_scriptExecutable.InstructionsInternal.Add(
                new ScriptInstruction(Opcode.RET));

            // clear fuunction-scope variables
            m_dictFunctionVariables.Clear();
        }

        private void ParseScript()
        {
            while (HasMoreTokens())
            {
                Token token = PeekToken();

                if (token.Type == TokenType.Global || token.Type == TokenType.Var)
                    ParseScriptOrGlobalVariableDeclaration();
                else
                    break;
            }

            // do nothing if only var decls
            if (!HasMoreTokens())
                return;

            while (HasMoreTokens())
            {
                Token token = PeekToken();

                if (token.Type != TokenType.Function)
                    throw new ParserException(
                        "Only variable and function declarations expected in the outer scope.",
                        token);

                ParseFunctionDeclaration();
            }
        }

        private void ResolveForwardFunctionDeclarations()
        {
            foreach (ScriptInstruction scriptInstructionCall in m_dictUnresolvedFunctionCalls.Keys)
            {
                // get unresolved call
                FunctionDescriptor functionDescriptorUnresolved
                    = m_dictUnresolvedFunctionCalls[scriptInstructionCall];
                // get function name
                String strFunctionName = functionDescriptorUnresolved.m_strFunctionName;

                // attempt lookup
                if (!m_scriptExecutable.Functions.ContainsKey(strFunctionName))
                    throw new ParserException(
                        "Cannot call undeclared function '" + strFunctionName + "'.");

                ScriptFunction scriptFunction
                    = m_scriptExecutable.Functions[strFunctionName];

                // resolve function reference for CALL / THRD opcode
                scriptInstructionCall.Operand0.ScriptFunctionRef
                    = scriptFunction;
            }
        }

        #endregion

        #region Internal Properties

        internal bool DebugMode
        {
            get { return m_bDebugMode; }
            set { m_bDebugMode = value; }
        }

        #endregion

        #region Public Methods

        public ScriptParser(Script script, List<Token> listTokens)
        {
            m_script = script;
            m_bDebugMode = false;
            m_iNextToken = 0;
            m_dictScriptVariables = new Dictionary<String, bool>();
            m_dictFunctionVariables = new Dictionary<String, bool>();
            m_iFunctionVariableLevel = 0;
            m_dictUnresolvedFunctionCalls = new Dictionary<ScriptInstruction, FunctionDescriptor>();
            m_stackLoopControl = new Stack<LoopControl>();
            m_listTokens = new List<Token>(listTokens);
            m_typeInferer = new TypeInferer();
            m_scriptExecutable = null;
        }

        public ScriptExecutable Parse()
        {
            m_iNextToken = 0;
            m_dictScriptVariables.Clear();
            m_dictFunctionVariables.Clear();
            m_iFunctionVariableLevel = -1;
            m_dictUnresolvedFunctionCalls.Clear();
            m_stackLoopControl.Clear();

            m_scriptExecutable = new ScriptExecutable(m_script);

            ParseScript();

            // resolve forward function declarations
            ResolveForwardFunctionDeclarations();

            // eliminate null opcodes
            m_scriptExecutable.EliminateNullOpcodes();

            return m_scriptExecutable;
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Malta Malta
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions