Click here to Skip to main content
15,885,309 members
Articles / Containers / Virtual Machine

Conscript IDE: An Integrated Development Environment (IDE) implementation for the Conscript scripting language

Rate me:
Please Sign up or sign in to vote.
4.90/5 (23 votes)
4 Sep 2008CPOL4 min read 100.3K   2.2K   158  
The third and final article in the Conscript .NET scripting engine article series.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;

using Conscript.Compiler;

namespace Conscript.Runtime
{
    /// <summary>
    /// Represents one of potentially many executable instances of a
    /// particular script.
    /// </summary>
    public class ScriptContext
    {
        #region Private Classes

        private class FunctionFrame
        {
            public ScriptFunction m_scriptFunction;
            public VariableDictionary m_variableDictionary;
            public int m_iNextInstruction;
        }

        #endregion

        #region Private Variables

        private ScriptFunction m_scriptFunction;
        private Script m_script;
        private ScriptExecutable m_scriptExecutable;
        private Stack<FunctionFrame> m_stackFunctionFrames;
        private Stack<object> m_stackParameters;
        private Dictionary<object, ScriptInstruction> m_dictLocks;
        private List<ScriptContext> m_listThreads;
        private ScriptInstruction m_scriptInstruction;
        private VariableDictionary m_variableDictionaryLocal;
        private bool m_bInterruptOnHostfunctionCall;
        private bool m_bInterruped;
        private bool m_bTerminated;

        #endregion

        #region Private Methods

        private HostFunctionHandler m_scriptHandler;

        private object ResolveOperand(Operand operand)
        {
            object objectSource = null;
            switch (operand.Type)
            {
                case OperandType.Literal:
                    return operand.Value;
                case OperandType.Variable:
                    return m_variableDictionaryLocal[(string) operand.Value];
                case OperandType.LiteralIndexedVariable:
                    objectSource
                        = m_variableDictionaryLocal[(String)operand.Value];

                    if (objectSource.GetType() == typeof(AssociativeArray))
                    {
                        AssociativeArray associativeArray
                            = (AssociativeArray)objectSource;
                        object objectValue = associativeArray[operand.IndexLiteral];
                        return objectValue;
                        //return associativeArray[operand.IndexLiteral];
                    }
                    else if (objectSource.GetType() == typeof(String))
                    {
                        String strSource = (String)objectSource;
                        object objectIndex = operand.IndexLiteral;

                        // handle string length
                        if (objectIndex.GetType() == typeof(String)
                            && ((String)objectIndex) == "length")
                            return strSource.Length;

                        // otherwise, must be a char index
                        if (objectIndex.GetType() != typeof(int))
                            throw new ExecutionException(
                                "Only integers are allowed for string indexing.");
                        return strSource[(int)objectIndex] + "";
                    }
                    else
                        throw new ExecutionException(
                            "Only associative arrays and strings can be indexed.");
                case OperandType.VariableIndexedVariable:
                    objectSource
                        = m_variableDictionaryLocal[(String)operand.Value];

                    if (objectSource.GetType() == typeof(AssociativeArray))
                    {
                        AssociativeArray associativeArray
                            = (AssociativeArray)objectSource;
                        object objectIndex
                            = m_variableDictionaryLocal[operand.IndexIdentifier];
                        return associativeArray[objectIndex];
                    }
                    else if (objectSource.GetType() == typeof(String))
                    {
                        String strSource = (String)objectSource;
                        object objectIndex
                            = m_variableDictionaryLocal[operand.IndexIdentifier];
                        if (objectIndex.GetType() != typeof(int))
                            throw new ExecutionException(
                                "Only integers are allowed for string indexing.");
                        return strSource[(int)objectIndex] + "";
                    }
                    else
                        throw new ExecutionException(
                            "Only associative arrays and strings can be indexed.");
                default:
                    throw new ExecutionException(
                        "Cannot resolve operand type '"+ operand.Type + "'.");
            }
        }

        private void AssignVariable(Operand operandDest, object objectValue)
        {
            // dest id
            String strIdentifierDest = (String)operandDest.Value;

            switch (operandDest.Type)
            {
                case OperandType.Variable:
                    // copy value (or assoc array ref)
                    m_variableDictionaryLocal[strIdentifierDest]
                        = objectValue;
                    break;
                case OperandType.LiteralIndexedVariable:
                case OperandType.VariableIndexedVariable:
                    // copy into array element
                    AssociativeArray associativeArray = null;

                    // dest value
                    object objectValueDest = null;
                    if (m_variableDictionaryLocal.IsDeclared(strIdentifierDest))
                        objectValueDest
                            = m_variableDictionaryLocal[strIdentifierDest];
                    else
                        objectValueDest = NullReference.Instance;

                    if (objectValueDest.GetType() != typeof(AssociativeArray))
                       // if old type not array and non-null assigned, throw error
                        throw new ExecutionException("Indexed destination "
                            + operandDest + " is not an associative array.");
                    else
                        associativeArray = (AssociativeArray)objectValueDest;

                    // assign into array
                    if (operandDest.Type == OperandType.LiteralIndexedVariable)
                    {
                        // literal-indexed array
                        object objectIndex = operandDest.IndexLiteral;
                        associativeArray[objectIndex] = objectValue;
                    }
                    else
                    {
                        // variable-indexed arrau
                        String strIdentifierIndex = operandDest.IndexIdentifier;
                        object objectIndex = m_variableDictionaryLocal[strIdentifierIndex];
                        associativeArray[objectIndex] = objectValue;
                    }
                    break;
                case OperandType.Literal:
                    throw new ExecutionException("MOV destination operand cannot be a literal.");
            }
        }

        private void ProcessArithmeticInstruction()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);
            // source value
            object objectValueSource
                = ResolveOperand(m_scriptInstruction.Operand1);

            Type typeDest = objectValueDest.GetType();
            Type typeSource = objectValueSource.GetType();

            // handle arrays and string concatenation
            if (m_scriptInstruction.Opcode == Opcode.ADD)
            {
                if (typeDest == typeof(String))
                {
                    AssignVariable(m_scriptInstruction.Operand0,
                        objectValueDest.ToString() + objectValueSource.ToString());
                    return;
                }

                if (typeDest == typeof(AssociativeArray))
                {
                    ((AssociativeArray)objectValueDest).Add(objectValueSource);
                    return;
                }
            }

            // handle array and string subtraction
            if (m_scriptInstruction.Opcode == Opcode.SUB)
            {
                if (typeDest == typeof(String))
                {
                    AssignVariable(m_scriptInstruction.Operand0,
                        objectValueDest.ToString().Replace(objectValueSource.ToString(), ""));
                    return;
                }
                if (typeDest == typeof(AssociativeArray))
                {
                    ((AssociativeArray)objectValueDest).Subtract(objectValueSource);
                    return;
                }
            }

            float fValueDest = 0.0f;
            float fValueSource = 0.0f;
            float fResult = 0.0f;

            if (typeDest == typeof(int))
                fValueDest = (float)(int)objectValueDest;
            else if (typeDest == typeof(float))
                fValueDest = (float)objectValueDest;
            else
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in arithmetic instructions.");

            if (typeSource == typeof(int))
                fValueSource = (float)(int)objectValueSource;
            else if (typeSource == typeof(float))
                fValueSource = (float)objectValueSource;
            else
                throw new ExecutionException(
                    "Values of type '" + typeSource.Name
                    + "' cannot be used in arithmetic instructions.");

            switch (m_scriptInstruction.Opcode)
            {
                case Opcode.ADD: fResult = fValueDest + fValueSource; break;
                case Opcode.SUB: fResult = fValueDest - fValueSource; break;
                case Opcode.MUL: fResult = fValueDest * fValueSource; break;
                case Opcode.DIV: fResult = fValueDest / fValueSource; break;
                case Opcode.POW: fResult = (float)Math.Pow(fValueDest, fValueSource); break;
                case Opcode.MOD: fResult = fValueDest % fValueSource; break;
                default:
                    throw new ExecutionException(
                        "Invalid arithmetic instruction '"
                        + m_scriptInstruction.Opcode + "'.");
            }

            if (typeDest == typeof(int) && typeSource == typeof(int))
                AssignVariable(m_scriptInstruction.Operand0, (int)fResult);
            else
                AssignVariable(m_scriptInstruction.Operand0, fResult);
        }

        private void ProcessRelationalInstruction()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);
            // source value
            object objectValueSource
                = ResolveOperand(m_scriptInstruction.Operand1);

            Type typeDest = objectValueDest.GetType();
            Type typeSource = objectValueSource.GetType();

            bool bResult = false;

            // handle null comparisons
            if (typeDest == typeof(NullReference) || typeSource == typeof(NullReference))
            {
                switch (m_scriptInstruction.Opcode)
                {
                    case Opcode.CEQ: bResult = objectValueDest == objectValueSource; break;
                    case Opcode.CNE: bResult = objectValueDest != objectValueSource; break;
                    default:
                        throw new ExecutionException(
                            "Only CEQ, CNE and CNL instructions may reference NULL values.");
                }
                AssignVariable(m_scriptInstruction.Operand0, bResult);
                return;
            }

            // handle string comparisons
            if (typeDest == typeof(String) && typeSource == typeof(String))
            {
                String strDest = (String) objectValueDest;
                String strSource = (String) objectValueSource;
                switch (m_scriptInstruction.Opcode)
                {
                    case Opcode.CEQ: bResult = strDest == strSource; break;
                    case Opcode.CNE: bResult = strDest != strSource; break;
                    case Opcode.CG: bResult = strDest.CompareTo(strSource) > 0; break;
                    case Opcode.CGE: bResult = strDest.CompareTo(strSource) >= 0; break;
                    case Opcode.CL: bResult = strDest.CompareTo(strSource) < 0; break;
                    case Opcode.CLE: bResult = strDest.CompareTo(strSource) <= 0; break;
                }
                AssignVariable(m_scriptInstruction.Operand0, bResult);
                return;
            }

            float fValueDest = 0.0f;
            float fValueSource = 0.0f;

            if (typeDest == typeof(int))
                fValueDest = (float)(int)objectValueDest;
            else if (typeDest == typeof(float))
                fValueDest = (float)objectValueDest;
            else
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in relational instructions.");

            if (typeSource == typeof(int))
                fValueSource = (float)(int)objectValueSource;
            else if (typeSource == typeof(float))
                fValueSource = (float)objectValueSource;
            else
                throw new ExecutionException(
                    "Values of type '" + typeSource.Name
                    + "' cannot be used in relational instructions.");

            switch (m_scriptInstruction.Opcode)
            {
                case Opcode.CEQ: bResult = fValueDest == fValueSource; break;
                case Opcode.CNE: bResult = fValueDest != fValueSource; break;
                case Opcode.CG: bResult = fValueDest > fValueSource; break;
                case Opcode.CGE: bResult = fValueDest >= fValueSource; break;
                case Opcode.CL: bResult = fValueDest < fValueSource; break;
                case Opcode.CLE: bResult = fValueDest <= fValueSource; break;
                default:
                    throw new ExecutionException(
                        "Invalid relational instruction '"
                        + m_scriptInstruction.Opcode + "'.");
            }

            AssignVariable(m_scriptInstruction.Operand0, bResult);
        }

        private void ProcessLogicalInstruction()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);
            // source value
            object objectValueSource
                = ResolveOperand(m_scriptInstruction.Operand1);

            Type typeDest = objectValueDest.GetType();
            Type typeSource = objectValueSource.GetType();

            if (typeDest != typeof(bool))
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in logical expressions.");

            if (typeSource != typeof(bool))
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in logical expressions.");

            bool bResult = false;
            bool bValueDest = (bool)objectValueDest;
            bool bValueSource = (bool)objectValueSource;

            switch (m_scriptInstruction.Opcode)
            {
                case Opcode.AND: bResult = bValueDest && bValueSource; break;
                case Opcode.OR: bResult = bValueDest || bValueSource; break;
                default:
                    throw new ExecutionException(
                        "Invalid relational instruction '"
                        + m_scriptInstruction.Opcode + "'.");
            }

            AssignVariable(m_scriptInstruction.Operand0, bResult);
        }

        private void ProcessIterator(AssociativeArray associativeArray)
        {
            if (associativeArray.Count == 0) return;

            object objectIterator = ResolveOperand(m_scriptInstruction.Operand0);

            bool bFoundKey = false;
            object objectNextKey = null;
            foreach (object objectKey in associativeArray.Keys)
            {
                if (bFoundKey)
                {
                    objectNextKey = objectKey;
                    break;
                }

                if (objectKey == objectIterator)
                    bFoundKey = true;
            }

            if (!bFoundKey)
            {
                // if no matching iterator found, set it to first
                Dictionary<object, object>.KeyCollection.Enumerator enumKeys
                    = associativeArray.Keys.GetEnumerator();
                enumKeys.MoveNext();
                objectNextKey = enumKeys.Current;
            }

            if (objectNextKey == null)
                objectNextKey = NullReference.Instance;

            m_variableDictionaryLocal[m_scriptInstruction.Operand0.Value.ToString()]
                = objectNextKey;
        }

        private void ProcessIterator(String strValue)
        {
            if (strValue.Length == 0) return;

            object objectIterator = ResolveOperand(m_scriptInstruction.Operand0);

            if (objectIterator.GetType() != typeof(int))
            {
                // if type not int, treat as mismatch and set to first
                m_variableDictionaryLocal[m_scriptInstruction.Operand0.Value.ToString()] = 0;
                return;
            }

            int iIterator = (int)objectIterator;

            if (iIterator < strValue.Length - 1)
                m_variableDictionaryLocal[m_scriptInstruction.Operand0.Value.ToString()]
                    = iIterator + 1;
            else
                m_variableDictionaryLocal[m_scriptInstruction.Operand0.Value.ToString()]
                    = NullReference.Instance;
        }

        private void ProcessDBG()
        {
        }

        private void ProcessNOP()
        {
        }

        private void ProcessDCG()
        {
            // should not run in functions
            throw new ExecutionException(
                "DCG opcodes cannot be executed within a function frame.");
        }

        private void ProcessDCL()
        {
            String strIdentifier
                = (String) m_scriptInstruction.Operand0.Value;

            m_variableDictionaryLocal[strIdentifier]
                = NullReference.Instance;
        }

        private void ProcessINT()
        {
            m_bInterruped = true;
        }

        private void ProcessLOCK()
        {
            object objectValue
                = ResolveOperand(m_scriptInstruction.Operand0);

            if (objectValue.GetType() == typeof(NullReference))
                throw new ExecutionException("Lock key must be a literal value.");

            if (m_script.Manager.Locks.ContainsKey(objectValue))
            {
                ScriptContext scriptContextLocks
                    = m_script.Manager.Locks[objectValue];
                if (scriptContextLocks == this && m_dictLocks[objectValue] != m_scriptInstruction)
                    throw new ExecutionException(
                        "Nested locks cannot share the same locking key.");

                // repeat instruction
                FunctionFrame functionFrame = m_stackFunctionFrames.Peek();
                --functionFrame.m_iNextInstruction;
                

                // interrupt
                m_bInterruped = true;
            }
            else
            {
                m_script.Manager.Locks[objectValue] = this;
                m_dictLocks[objectValue] = m_scriptInstruction;
            }
        }

        private void ProcessULCK()
        {
            object objectValue
                = ResolveOperand(m_scriptInstruction.Operand0);

            if (objectValue.GetType() == typeof(NullReference))
                throw new ExecutionException("Lock key must be a literal value.");


            if (!m_script.Manager.Locks.ContainsKey(objectValue))
                throw new ExecutionException("Lock '" + objectValue + "' is already unlocked.");

            m_dictLocks.Remove(objectValue);
            m_script.Manager.Locks.Remove(objectValue);
        }

        private void ProcessMOV()
        {
            object objectValue
                = ResolveOperand(m_scriptInstruction.Operand1);

            AssignVariable(m_scriptInstruction.Operand0,
                objectValue);
        }

        private void ProcessINC()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);

            Type typeDest = objectValueDest.GetType();

            if (typeDest == typeof(int))
            {
                int iValue = (int)objectValueDest;
                m_variableDictionaryLocal[strIdentifierDest] = ++iValue;
            }
            else if (typeDest == typeof(float))
            {
                float fValue = (float)objectValueDest;
                m_variableDictionaryLocal[strIdentifierDest] = ++fValue;
            }
            else
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in arithmetic increment instruction.");
        }

        private void ProcessDEC()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);

            Type typeDest = objectValueDest.GetType();

            if (typeDest == typeof(int))
            {
                int iValue = (int)objectValueDest;
                m_variableDictionaryLocal[strIdentifierDest] = --iValue;
            }
            else if (typeDest == typeof(float))
            {
                float fValue = (float)objectValueDest;
                m_variableDictionaryLocal[strIdentifierDest] = --fValue;
            }
            else
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in arithmetic decrement instruction.");
        }

        private void ProcessNEG()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);

            Type typeDest = objectValueDest.GetType();

            if (typeDest == typeof(int))
            {
                int iValue = (int)objectValueDest;
                m_variableDictionaryLocal[strIdentifierDest] = -iValue;
            }
            else if (typeDest == typeof(float))
            {
                float fValue = (float)objectValueDest;
                m_variableDictionaryLocal[strIdentifierDest] = -fValue;
            }
            else
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in arithmetic negation instruction.");
        }

        private void ProcessADD()
        {
            ProcessArithmeticInstruction();
        }

        private void ProcessSUB()
        {
            ProcessArithmeticInstruction();
        }

        private void ProcessMUL()
        {
            ProcessArithmeticInstruction();
        }

        private void ProcessDIV()
        {
            ProcessArithmeticInstruction();
        }

        private void ProcessPOW()
        {
            ProcessArithmeticInstruction();
        }

        private void ProcessMOD()
        {
            ProcessArithmeticInstruction();
        }

        private void ProcessCNL()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);

            m_variableDictionaryLocal[strIdentifierDest]
                = objectValueDest == NullReference.Instance;
        }

        private void ProcessCEQ()
        {
            ProcessRelationalInstruction();
        }

        private void ProcessCNE()
        {
            ProcessRelationalInstruction();
        }

        private void ProcessCG()
        {
            ProcessRelationalInstruction();
        }

        private void ProcessCGE()
        {
            ProcessRelationalInstruction();
        }

        private void ProcessCL()
        {
            ProcessRelationalInstruction();
        }

        private void ProcessCLE()
        {
            ProcessRelationalInstruction();
        }

        private void ProcessOR()
        {
            ProcessLogicalInstruction();
        }

        private void ProcessAND()
        {
            ProcessLogicalInstruction();
        }

        private void ProcessNOT()
        {
            // dest id
            String strIdentifierDest
                = (String)m_scriptInstruction.Operand0.Value;
            // dest value
            object objectValueDest
                = ResolveOperand(m_scriptInstruction.Operand0);
 
            Type typeDest = objectValueDest.GetType();

            if (typeDest != typeof(bool))
                throw new ExecutionException(
                    "Values of type '" + typeDest.Name
                    + "' cannot be used in logical negation instruction.");

            m_variableDictionaryLocal[strIdentifierDest] = !((bool)objectValueDest);
        }

        private void ProcessJMP()
        {
            ScriptInstruction scriptInstructionTarget
                = m_scriptInstruction.Operand0.InstructionRef;
            FunctionFrame functionFrame = m_stackFunctionFrames.Peek();
            functionFrame.m_iNextInstruction
                = (int) scriptInstructionTarget.Address;
        }
        
        private void ProcessJT()
        {
            object objectCondition
                = ResolveOperand(m_scriptInstruction.Operand0);
            bool bCondition = (bool)objectCondition;
            if (!bCondition) return;

            ScriptInstruction scriptInstructionTarget
                = m_scriptInstruction.Operand1.InstructionRef;
            FunctionFrame functionFrame = m_stackFunctionFrames.Peek();
            functionFrame.m_iNextInstruction
                = (int)scriptInstructionTarget.Address;
        }

        private void ProcessJF()
        {
            object objectCondition
                = ResolveOperand(m_scriptInstruction.Operand0);
            bool bCondition = (bool)objectCondition;
            if (bCondition) return;

            ScriptInstruction scriptInstructionTarget
                = m_scriptInstruction.Operand1.InstructionRef;
            FunctionFrame functionFrame = m_stackFunctionFrames.Peek();
            functionFrame.m_iNextInstruction
                = (int)scriptInstructionTarget.Address;
        }

        private void ProcessCLRA()
        {
            if (m_scriptInstruction.Operand0.Type != OperandType.Variable)
                throw new ExecutionException(
                    "Operand in CLA instruction must be a simple variable.");
            AssociativeArray associativeArray
                = new AssociativeArray();
            String strIdentifierArray
                = m_scriptInstruction.Operand0.Value.ToString();
            m_variableDictionaryLocal[strIdentifierArray] = associativeArray;
        }

        private void ProcessNEXT()
        {
            if (m_scriptInstruction.Operand0.Type != OperandType.Variable)
                throw new ExecutionException(
                    "Iterator operand in NEXT instruction must be a simple variable.");

            if (m_scriptInstruction.Operand1.Type != OperandType.Variable)
                throw new ExecutionException(
                    "Array operand reference in NEXT instruction must be a simple variable.");

            object objectEnumerable = m_variableDictionaryLocal[
                m_scriptInstruction.Operand1.Value.ToString()];

            if (objectEnumerable.GetType() == typeof(AssociativeArray))
                ProcessIterator((AssociativeArray)objectEnumerable);
            else if (objectEnumerable.GetType() == typeof(String))
                ProcessIterator((String)objectEnumerable);
            else
                throw new ExecutionException(
                    "Enumerable operand in NEXT instruction must be an associative array or a string.");
        }

        private void ProcessPUSH()
        {
            object objectValue
                = ResolveOperand(m_scriptInstruction.Operand0);
            m_stackParameters.Push(objectValue);
        }

        private void ProcessPOP()
        {
            String strIdentifier = null;
            object objectValue = m_stackParameters.Pop();
            Operand operand = m_scriptInstruction.Operand0;
            switch (operand.Type)
            {
                case OperandType.Variable:
                    strIdentifier
                        = operand.Value.ToString();
                    m_variableDictionaryLocal[strIdentifier] = objectValue;
                    break;
                case OperandType.LiteralIndexedVariable:
                case OperandType.VariableIndexedVariable:
                    strIdentifier = operand.Value.ToString();
                    object objectArray = m_variableDictionaryLocal[strIdentifier];
                    if (objectArray.GetType() != typeof(AssociativeArray))
                        throw new ExecutionException(
                            "Associative array expected for POP instruction with indexed operand.");
                    AssociativeArray associativeArray = (AssociativeArray)objectArray;

                    if (operand.Type == OperandType.LiteralIndexedVariable)
                        // pop into literal-indexed array
                        associativeArray[operand.IndexLiteral] = objectValue;
                    else
                    {
                        // pop into variable-indexed array
                        object objectIndexValue = m_variableDictionaryLocal[operand.IndexIdentifier];
                        associativeArray[objectIndexValue] = objectValue;
                    }
                    break;
                default:
                    throw new ExecutionException(
                        "Operand type '" + operand.Type + "' not supported by POP instruction.");

            }
        }

        private void ProcessCALL()
        {
            // get function
            ScriptFunction scriptFunction
                = m_scriptInstruction.Operand0.ScriptFunctionRef;
            ScriptInstruction scriptInstructionTarget
                = scriptFunction.EntryPoint;

            FunctionFrame functionFrame = new FunctionFrame();
            functionFrame.m_scriptFunction = scriptFunction;
            functionFrame.m_variableDictionary
                = VariableDictionary.CreateLocalDictionary(
                    m_scriptExecutable.ScriptDictionary);
            m_variableDictionaryLocal = functionFrame.m_variableDictionary;
            functionFrame.m_iNextInstruction
                = (int) scriptInstructionTarget.Address;

            m_stackFunctionFrames.Push(functionFrame);
        }

        private void ProcessRET()
        {
            m_stackFunctionFrames.Pop();

            if (m_stackFunctionFrames.Count == 0)
            {
                m_bTerminated = true;
                return;
            }

            m_variableDictionaryLocal
                = m_stackFunctionFrames.Peek().m_variableDictionary;
        }

        private void ProcessHOST()
        {
            // get function proto
            HostFunctionPrototype hostFunctionPrototype
                = m_scriptInstruction.Operand0.HostFunctionRef;

            // determine which handler to use
            HostFunctionHandler hostFunctionHandler = null;
            if (hostFunctionPrototype.Handler == null)
            {
                // if prototype handler not set, but
                // context handler set, use it
                if (m_scriptHandler != null)
                    hostFunctionHandler = m_scriptHandler;
            }
            else
                // prefer prototype handler over context handler
                hostFunctionHandler = hostFunctionPrototype.Handler;

            // pop values from stack into list
            List<object> listParameters = new List<object>();
            for (int iIndex = 0; iIndex < hostFunctionPrototype.ParameterTypes.Count; iIndex++)
                listParameters.Insert(0, m_stackParameters.Pop());

            // verify param list against proto
            hostFunctionPrototype.VerifyParameters(listParameters);

            // delegate call to handler (if set)
            object objectResult = null;
            if (hostFunctionHandler != null)
            {
                objectResult = hostFunctionHandler.OnHostFunctionCall(
                    hostFunctionPrototype.Name, listParameters);

                // verify result against proto
                hostFunctionPrototype.VerifyResult(objectResult);
            }

            // push result into stack
            if (objectResult == null)
                objectResult = NullReference.Instance;
            m_stackParameters.Push(objectResult);

            if (m_bInterruptOnHostfunctionCall)
                m_bInterruped = true;
        }

        private void ProcessTHRD()
        {
            // get function
            ScriptFunction scriptFunction
                = m_scriptInstruction.Operand0.ScriptFunctionRef;

            // pop values from stack into list
            List<object> listParameters = new List<object>();
            for (int iIndex = 0; iIndex < scriptFunction.ParameterCount; iIndex++)
                listParameters.Insert(0, m_stackParameters.Pop());

            // create new sub-context to act as a thread
            ScriptContext scriptContextThread
                = new ScriptContext(scriptFunction, listParameters);

            // assign host function handler from parent
            scriptContextThread.Handler = m_scriptHandler;

            // add to thread list
            m_listThreads.Add(scriptContextThread);
        }

        private uint ExecuteThreads()
        {
            uint uiExecuted = 0;
            // execute all active threads 'concurrently'
            foreach (ScriptContext scriptContextThread in m_listThreads)
                if (!scriptContextThread.Terminated)
                    uiExecuted += scriptContextThread.Execute(1);

            // elimiate terminated threads
            for (int iIndex = m_listThreads.Count - 1; iIndex >= 0; iIndex--)
                if (m_listThreads[iIndex].Terminated)
                    m_listThreads.RemoveAt(iIndex);

            return uiExecuted;
        }

        private void ExecuteInstruction()
        {
            // get top frame
            FunctionFrame functionFrame = m_stackFunctionFrames.Peek();

            // fetch next instruction
            m_scriptInstruction
                = m_scriptExecutable.InstructionsInternal[
                    functionFrame.m_iNextInstruction++];

            switch (m_scriptInstruction.Opcode)
            {
                case Opcode.DBG:  ProcessDBG();  break;
                case Opcode.NOP:  ProcessNOP();  break;
                case Opcode.DCG:  ProcessDCG();  break;
                case Opcode.DCL:  ProcessDCL();  break;
                case Opcode.INT:  ProcessINT();  break;
                case Opcode.LOCK: ProcessLOCK(); break;
                case Opcode.ULCK: ProcessULCK(); break;
                case Opcode.MOV:  ProcessMOV();  break;
                case Opcode.INC:  ProcessINC(); break;
                case Opcode.DEC:  ProcessDEC(); break;
                case Opcode.NEG:  ProcessNEG(); break;
                case Opcode.ADD:  ProcessADD();  break;
                case Opcode.SUB:  ProcessSUB();  break;
                case Opcode.MUL:  ProcessMUL();  break;
                case Opcode.DIV:  ProcessDIV();  break;
                case Opcode.POW:  ProcessPOW();  break;
                case Opcode.MOD:  ProcessMOD(); break;
                case Opcode.CNL: ProcessCNL(); break;
                case Opcode.CEQ:  ProcessCEQ();  break;
                case Opcode.CNE:  ProcessCNE();  break;
                case Opcode.CG:   ProcessCG();   break;
                case Opcode.CGE:  ProcessCGE();  break;
                case Opcode.CL:   ProcessCL();   break;
                case Opcode.CLE:  ProcessCLE();  break;
                case Opcode.OR:   ProcessOR();   break;
                case Opcode.AND:  ProcessAND();  break;
                case Opcode.NOT:  ProcessNOT();  break;
                case Opcode.JMP:  ProcessJMP();  break;
                case Opcode.JT:   ProcessJT();   break;
                case Opcode.JF:   ProcessJF();   break;
                case Opcode.CLRA: ProcessCLRA(); break;
                case Opcode.NEXT: ProcessNEXT(); break;
                case Opcode.PUSH: ProcessPUSH(); break;
                case Opcode.POP:  ProcessPOP();  break;
                case Opcode.CALL: ProcessCALL(); break;
                case Opcode.RET:  ProcessRET();  break;
                case Opcode.HOST: ProcessHOST(); break;
                case Opcode.THRD: ProcessTHRD(); break;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Constructs a script context with the given <see cref="ScriptFunction"/>
        /// entry point and function parameters.
        /// </summary>
        /// <param name="scriptFunction">Script function to execute.</param>
        /// <param name="listParameters">Script function parameters.</param>
        public ScriptContext(ScriptFunction scriptFunction, List<object> listParameters)
        {
            if (scriptFunction.ParameterCount > listParameters.Count)
                throw new ExecutionException("Missing function parameters.");
            if (scriptFunction.ParameterCount < listParameters.Count)
                throw new ExecutionException("Too many function parameters.");

            m_scriptFunction = scriptFunction;
            m_script = scriptFunction.Executable.Script;
            m_scriptExecutable = m_script.Executable;
            m_stackFunctionFrames = new Stack<FunctionFrame>();
            m_stackParameters = new Stack<object>();
            m_dictLocks = new Dictionary<object, ScriptInstruction>();
            m_listThreads = new List<ScriptContext>();
            m_scriptHandler = null;

            m_bInterruptOnHostfunctionCall = false;

            Reset();

            // push any passed parameters
            foreach (object objectParameter in listParameters)
            {
                if (objectParameter == null)
                    m_stackParameters.Push(NullReference.Instance);
                else
                {
                    Type typeParameter = objectParameter.GetType();
                    if (typeParameter == typeof(NullReference))
                        m_stackParameters.Push(NullReference.Instance);
                    else if (typeParameter == typeof(int)
                        || typeParameter == typeof(float)
                        || typeParameter == typeof(bool)
                        || typeParameter == typeof(String)
                        || typeParameter == typeof(AssociativeArray))
                        m_stackParameters.Push(objectParameter);
                    else
                        throw new ExecutionException("Parameters of type '"
                            + typeParameter.Name + "' not allowed.");
                }
            }
        }

        /// <summary>
        /// Constructs a script context with the given <see cref="ScriptFunction"/>
        /// entry point. No parameters are assumed.
        /// </summary>
        /// <param name="scriptFunction">Script function to execute.</param>
        public ScriptContext(ScriptFunction scriptFunction)
            : this(scriptFunction, new List<object>())
        {
        }

        /// <summary>
        /// Constructs a script context for the given <see cref="Script"/> assuming
        /// a main() function with the given parameter values.
        /// </summary>
        /// <param name="script">Script to execute.</param>
        /// <param name="listParameters">Script function parameters.</param>
        public ScriptContext(Script script, List<object> listParameters)
            : this(script.Executable.MainFunction, listParameters)
        {
        }

        /// <summary>
        /// Constructs a script context for the given <see cref="Script"/> assuming
        /// a main() function with no parameters.
        /// </summary>
        /// <param name="script">Script to execute.</param>
        public ScriptContext(Script script)
            : this(script.Executable.MainFunction,
                new List<object>())
        {
        }

        /// <summary>
        /// Resets execution of the script context.
        /// </summary>
        public void Reset()
        {
            m_stackFunctionFrames.Clear();
            FunctionFrame functionFrame = new FunctionFrame();
            functionFrame.m_scriptFunction = m_scriptFunction;
            functionFrame.m_variableDictionary
                = VariableDictionary.CreateLocalDictionary(
                    m_scriptExecutable.ScriptDictionary);
            functionFrame.m_iNextInstruction = (int) m_scriptFunction.EntryPoint.Address;
            m_stackFunctionFrames.Push(functionFrame);

            m_stackParameters.Clear();

            m_scriptInstruction = null;
            m_variableDictionaryLocal = functionFrame.m_variableDictionary;

            // release all locks held by this context
            foreach (object objectLock in m_dictLocks.Keys)
                m_script.Manager.Locks.Remove(objectLock);
            m_dictLocks.Clear();

            m_bTerminated = false;
            m_bInterruped = false;
        }

        /// <summary>
        /// Executes up to the given number of instructions and returns
        /// the actual number of instructions executed.
        /// </summary>
        /// <param name="uiMaxInstructions">Maximum instructions to
        /// execute.</param>
        /// <returns>Actual number of instructions executed.</returns>
        public uint Execute(uint uiMaxInstructions)
        {
            m_variableDictionaryLocal.ExposeTemporaryVariables();

            m_bInterruped = false;
            uint uiExecuted = 0;
            while (!Terminated && !m_bInterruped && uiExecuted < uiMaxInstructions)
            {
                ExecuteInstruction();
                ++uiExecuted;
                uiExecuted += ExecuteThreads();
            }

            m_variableDictionaryLocal.HideTemporaryVariables();
            return uiExecuted;
        }

        /// <summary>
        /// Executes the script context for up to the given time
        /// interval and returns the number of instructions executed.
        /// </summary>
        /// <param name="tsInterval">Time interval allowed for
        /// execution.</param>
        /// <returns>Actual number of instructions executed.</returns>
        public uint Execute(TimeSpan tsInterval)
        {
            DateTime dtIntervalEnd = DateTime.Now + tsInterval;

            m_variableDictionaryLocal.ExposeTemporaryVariables();

            m_bInterruped = false;
            uint uiExecuted = 0;
            while (!Terminated && !m_bInterruped)
            {
                ExecuteInstruction();
                ++uiExecuted;
                uiExecuted += ExecuteThreads();

                if (DateTime.Now >= dtIntervalEnd) break;
            }

            m_variableDictionaryLocal.HideTemporaryVariables();
            return uiExecuted;
        }

        /// <summary>
        /// Executes the script context until the end of the initial
        /// function specified or an interrupt is generated.
        /// </summary>
        /// <returns>Actual number of instructions executed.</returns>
        public uint Execute()
        {
            m_variableDictionaryLocal.ExposeTemporaryVariables();

            m_bInterruped = false;
            uint uiExecuted = 0;
            while (!Terminated && !m_bInterruped)
            {
                ExecuteInstruction();
                ++uiExecuted;
                uiExecuted += ExecuteThreads();
            }

            m_variableDictionaryLocal.HideTemporaryVariables();
            return uiExecuted;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// <see cref="Script"/> associated with this context.
        /// </summary>
        public Script Script
        {
            get { return m_script; }
        }

        /// <summary>
        /// Boolean flag to enable or disable interrupts whenever
        /// a host function is invoked.
        /// </summary>
        public bool InterruptOnHostfunctionCall
        {
            get { return m_bInterruptOnHostfunctionCall; }
            set { m_bInterruptOnHostfunctionCall = value; }
        }

        /// <summary>
        /// Child thread contexts generated by the script.
        /// </summary>
        public ReadOnlyCollection<ScriptContext> ChildThreads
        {
            get { return m_listThreads.AsReadOnly(); }
        }

        /// <summary>
        /// Boolean flag indicating if an interrupt was generated
        /// after the last execution run. The flag is cleared in
        /// subsequent executions.
        /// </summary>
        public bool Interrupted
        {
            get { return m_bInterruped; }
        }

        /// <summary>
        /// Boolean flag indicating if the initially specified
        /// function has completed execution or otherwise.
        /// </summary>
        public bool Terminated
        {
            get { return m_bTerminated; }
        }

        /// <summary>
        /// Index to the next instruction to be executed.
        /// </summary>
        public int NextInstruction
        {
            get
            {
                if (m_stackFunctionFrames.Count == 0) return -1;
                return m_stackFunctionFrames.Peek().m_iNextInstruction;
            }
        }

        /// <summary>
        /// Current execution function stack.
        /// </summary>
        public ReadOnlyCollection<ScriptFunction> FunctionStack
        {
            get
            {
                List<ScriptFunction> listFunctions = new List<ScriptFunction>();
                foreach (FunctionFrame functionFrame in m_stackFunctionFrames)
                    listFunctions.Add(functionFrame.m_scriptFunction);
                return new List<ScriptFunction>(listFunctions).AsReadOnly();
            }
        }

        /// <summary>
        /// Current parameter value stack.
        /// </summary>
        public ReadOnlyCollection<object> ParameterStack
        {
            get { return new List<object>(m_stackParameters).AsReadOnly(); }
        }

        /// <summary>
        /// Variable dictionary defined at local scope.
        /// </summary>
        public VariableDictionary LocalDictionary
        {
            get { return m_variableDictionaryLocal; }
        }

        /// <summary>
        /// Context-level host function handler. This handler
        /// is ignored by host functions with handlers defined
        /// at <see cref="ScriptManager"/> level.
        /// </summary>
        public HostFunctionHandler Handler
        {
            get { return m_scriptHandler; }
            set { m_scriptHandler = value; }
        }

        #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