/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection.Emit;
using System.Text;
using System.Threading;

using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;

namespace OpenSim.Region.ScriptEngine.Yengine
{
    public class XMRInstArrays
    {
        public XMR_Array[] iarArrays;
        public char[] iarChars;
        public double[] iarFloats;
        public int[] iarIntegers;
        public LSL_List[] iarLists;
        public object[] iarObjects;
        public LSL_Rotation[] iarRotations;
        public string[] iarStrings;
        public LSL_Vector[] iarVectors;
        public XMRSDTypeClObj[] iarSDTClObjs;
        public Delegate[][] iarSDTIntfObjs;

        private XMRInstAbstract instance;
        public int arraysHeapUse;

        public static readonly XMR_Array[] noArrays = new XMR_Array[0];
        public static readonly char[] noChars = new char[0];
        public static readonly double[] noFloats = new double[0];
        public static readonly int[] noIntegers = new int[0];
        public static readonly LSL_List[] noLists = new LSL_List[0];
        public static readonly object[] noObjects = new object[0];
        public static readonly LSL_Rotation[] noRotations = new LSL_Rotation[0];
        public static readonly string[] noStrings = new string[0];
        public static readonly LSL_Vector[] noVectors = new LSL_Vector[0];
        public static readonly XMRSDTypeClObj[] noSDTClObjs = new XMRSDTypeClObj[0];
        public static readonly Delegate[][] noSDTIntfObjs = new Delegate[0][];

        public XMRInstArrays(XMRInstAbstract inst)
        {
            instance = inst;
        }

        /*
        ~XMRInstArrays()
        {
            arraysHeapUse = instance.UpdateArraysHeapUse(arraysHeapUse, 0);
        }
        */

        public void Clear()
        {
            int newheapUse = 0;
            if(iarArrays is not null)
            {
                foreach(XMR_Array xa in iarArrays)
                    xa.__pub_clear();
            }
            if(iarChars is not null)
                iarChars = new char[iarChars.Length];
            if (iarLists is not null)
                iarLists = new LSL_List[iarLists.Length];
            if (iarObjects is not null)
                iarObjects = new object[iarObjects.Length];
            if(iarStrings is not null)
                iarStrings = new string[iarStrings.Length];
            if (iarFloats is not null)
                newheapUse += iarFloats.Length * HeapTrackerObject.HT_DOUB;
            if (iarIntegers is not null)
                newheapUse += iarIntegers.Length * HeapTrackerObject.HT_INT;
            if (iarRotations is not null)
                newheapUse += iarRotations.Length * HeapTrackerObject.HT_ROT;
            if (iarVectors != null)
                newheapUse += iarVectors.Length * HeapTrackerObject.HT_VEC;

            arraysHeapUse = instance.UpdateArraysHeapUse(0, newheapUse);
        }

    public void AllocVarArrays(XMRInstArSizes ars)
        {
            ClearOldArrays();
            int newuse = arraysHeapUse +
               ars.iasChars* HeapTrackerObject.HT_CHAR +
               ars.iasFloats * HeapTrackerObject.HT_DOUB +
               ars.iasIntegers * HeapTrackerObject.HT_INT +
               ars.iasRotations * HeapTrackerObject.HT_ROT +
               ars.iasVectors * HeapTrackerObject.HT_VEC +
               ars.iasSDTIntfObjs * HeapTrackerObject.HT_DELE;

            arraysHeapUse = instance.UpdateArraysHeapUse(arraysHeapUse, newuse);

            iarArrays = (ars.iasArrays > 0) ? new XMR_Array[ars.iasArrays] : noArrays;
            iarChars = (ars.iasChars > 0) ? new char[ars.iasChars] : noChars;
            iarFloats = (ars.iasFloats > 0) ? new double[ars.iasFloats] : noFloats;
            iarIntegers = (ars.iasIntegers > 0) ? new int[ars.iasIntegers] : noIntegers;
            iarLists = (ars.iasLists > 0) ? new LSL_List[ars.iasLists] : noLists;
            iarObjects = (ars.iasObjects > 0) ? new object[ars.iasObjects] : noObjects;
            iarRotations = (ars.iasRotations > 0) ? new LSL_Rotation[ars.iasRotations] : noRotations;
            iarStrings = (ars.iasStrings > 0) ? new string[ars.iasStrings] : noStrings;
            iarVectors = (ars.iasVectors > 0) ? new LSL_Vector[ars.iasVectors] : noVectors;
            iarSDTClObjs = (ars.iasSDTClObjs > 0) ? new XMRSDTypeClObj[ars.iasSDTClObjs] : noSDTClObjs;
            iarSDTIntfObjs = (ars.iasSDTIntfObjs > 0) ? new Delegate[ars.iasSDTIntfObjs][] : noSDTIntfObjs;
        }

        /**
         * @brief Do not write directly to iarLists[index], rather use this method.
         */
        public void PopList(int index, LSL_List lis)
        {
            int delta = HeapTrackerObject.Size(lis) - HeapTrackerObject.Size(iarLists[index]);
            instance.UpdateArraysHeapUse(0, delta);
            Interlocked.Add(ref arraysHeapUse, delta);
            iarLists[index] = lis;
        }

        /**
         * @brief Do not write directly to iarObjects[index], rather use this method.
         */
        public void PopObject(int index, object obj)
        {
            int delta = HeapTrackerObject.Size(obj) - HeapTrackerObject.Size(iarObjects[index]);
            instance.UpdateArraysHeapUse(0, delta);
            Interlocked.Add(ref arraysHeapUse, delta);
            iarObjects[index] = obj;
        }

        /**
         * @brief Do not write directly to iarStrings[index], rather use this method.
         */
        public void PopString(int index, string str)
        {
            int delta = HeapTrackerString.Size(str) - HeapTrackerString.Size(iarStrings[index]);
            instance.UpdateArraysHeapUse(0, delta);
            Interlocked.Add(ref arraysHeapUse, delta);
            iarStrings[index] = str;
        }

        /**
         * @brief Write all arrays out to a file.
         */
        public delegate void Sender(object value);
        public void SendArrays(Sender sender)
        {
            sender(iarArrays);
            sender(iarChars);
            sender(iarFloats);
            sender(iarIntegers);
            sender(iarLists);
            sender(iarObjects);
            sender(iarRotations);
            sender(iarStrings);
            sender(iarVectors);
            sender(iarSDTClObjs);
            sender(iarSDTIntfObjs);
        }

        /**
         * @brief Read all arrays in from a file.
         */
        public delegate object Recver();
        public void RecvArrays(Recver recver)
        {
            ClearOldArrays();

            iarArrays = (XMR_Array[])recver();
            if(iarArrays is null)
                iarArrays = noArrays;

            int newheapuse = arraysHeapUse;

            char[] chrs = (char[])recver();
            if (chrs is not null)
            {
                newheapuse += chrs.Length * HeapTrackerObject.HT_CHAR;
                iarChars = chrs;
            }
            else
                iarChars = noChars;

            double[] flts = (double[])recver();
            if (flts is not null)
            {
                newheapuse += flts.Length * HeapTrackerObject.HT_DOUB;
                iarFloats = flts;
            }
            else
                iarFloats = noFloats;

            int[] ints = (int[])recver();
            if (ints is not null)
            {
                newheapuse += ints.Length * HeapTrackerObject.HT_INT;
                iarIntegers = ints;
            }
            else
                iarIntegers = noIntegers;

            LSL_List[] liss = (LSL_List[])recver();
            if (liss is not null)
            {
                foreach (LSL_List lis in liss)
                {
                    if(!(lis is null))
                        newheapuse += lis.Size;
                }
                iarLists = liss;
            }
            else
                iarLists = noLists;

            object[] objs = (object[])recver();
            if (objs is not null)
            {
                foreach (object obj in objs)
                    newheapuse += HeapTrackerObject.Size(obj);
                iarObjects = objs;
            }
            else
                iarObjects = noObjects;

            LSL_Rotation[] rots = (LSL_Rotation[])recver();
            if (rots is not null)
            {
                newheapuse += rots.Length * HeapTrackerObject.HT_ROT;
                iarRotations = rots;
            }
            else
                iarRotations = noRotations;

            string[] strs = (string[])recver();
            if (strs is not null)
            {
                foreach (string str in strs)
                    newheapuse += HeapTrackerString.Size(str);
                iarStrings = strs;
            }
            else
                iarStrings = noStrings;

            LSL_Vector[] vecs = (LSL_Vector[])recver();
            if (vecs != null)
            {
                newheapuse += vecs.Length * HeapTrackerObject.HT_VEC;
                iarVectors = vecs;
            }
            else
                iarVectors = noVectors;

            iarSDTClObjs = (XMRSDTypeClObj[])recver();
            if(iarSDTClObjs is null)
                iarSDTClObjs = noSDTClObjs;

            Delegate[][] dels = (Delegate[][])recver();
            if(dels is not null)
            {
                newheapuse += dels.Length * HeapTrackerObject.HT_DELE;
                iarSDTIntfObjs = dels;
            }
            else
                iarSDTIntfObjs = noSDTIntfObjs;

            // update script heap usage, throwing an exception before finalizing changes
            arraysHeapUse = instance.UpdateArraysHeapUse(arraysHeapUse, newheapuse);

        }

        private void ClearOldArrays()
        {
            int newheapuse = arraysHeapUse;

            iarArrays = null;
            if(iarChars is not null)
            {
                newheapuse -= iarChars.Length * HeapTrackerObject.HT_CHAR;
                iarChars = null;
            }
            if(iarFloats is not null)
            {
                newheapuse -= iarFloats.Length * HeapTrackerObject.HT_DOUB;
                iarFloats = null;
            }
            if(iarIntegers is not null)
            {
                newheapuse -= iarIntegers.Length * HeapTrackerObject.HT_INT;
                iarIntegers = null;
            }
            if(iarLists is not null)
            {
                foreach(LSL_List lis in iarLists)
                {
                    if ((lis is not null))
                        newheapuse += lis.Size;
                }
                iarLists = null;
            }
            if(iarObjects is not null)
            {
                foreach(object obj in iarObjects)
                    newheapuse -= HeapTrackerObject.Size(obj);
                iarObjects = null;
            }
            if(iarRotations is not null)
            {
                newheapuse -= iarRotations.Length * HeapTrackerObject.HT_ROT;
                iarRotations = null;
            }
            if(iarStrings is not null)
            {
                foreach(string str in iarStrings)
                    newheapuse -= HeapTrackerString.Size(str);
                iarStrings = null;
            }
            if(iarVectors is not null)
            {
                newheapuse -= iarVectors.Length * HeapTrackerObject.HT_VEC;
                iarVectors = null;
            }
            iarSDTClObjs = null;
            if(iarSDTIntfObjs is not null)
            {
                newheapuse -= iarSDTIntfObjs.Length * HeapTrackerObject.HT_DELE;
                iarSDTIntfObjs = null;
            }

            arraysHeapUse = instance.UpdateArraysHeapUse(arraysHeapUse, newheapuse);
        }
    }

    public class XMRInstArSizes
    {
        public int iasArrays;
        public int iasChars;
        public int iasFloats;
        public int iasIntegers;
        public int iasLists;
        public int iasObjects;
        public int iasRotations;
        public int iasStrings;
        public int iasVectors;
        public int iasSDTClObjs;
        public int iasSDTIntfObjs;

        public void WriteAsmFile(TextWriter asmFileWriter, string label)
        {
            asmFileWriter.WriteLine("  {0}Arrays       {1}", label, iasArrays);
            asmFileWriter.WriteLine("  {0}Chars        {1}", label, iasChars);
            asmFileWriter.WriteLine("  {0}Floats       {1}", label, iasFloats);
            asmFileWriter.WriteLine("  {0}Integers     {1}", label, iasIntegers);
            asmFileWriter.WriteLine("  {0}Lists        {1}", label, iasLists);
            asmFileWriter.WriteLine("  {0}Objects      {1}", label, iasObjects);
            asmFileWriter.WriteLine("  {0}Rotations    {1}", label, iasRotations);
            asmFileWriter.WriteLine("  {0}Strings      {1}", label, iasStrings);
            asmFileWriter.WriteLine("  {0}Vectors      {1}", label, iasVectors);
            asmFileWriter.WriteLine("  {0}SDTClObjs    {1}", label, iasSDTClObjs);
            asmFileWriter.WriteLine("  {0}SDTIntfObjs  {1}", label, iasSDTIntfObjs);
        }

        public void WriteToFile(BinaryWriter objFileWriter)
        {
            objFileWriter.Write(iasArrays);
            objFileWriter.Write(iasChars);
            objFileWriter.Write(iasFloats);
            objFileWriter.Write(iasIntegers);
            objFileWriter.Write(iasLists);
            objFileWriter.Write(iasObjects);
            objFileWriter.Write(iasRotations);
            objFileWriter.Write(iasStrings);
            objFileWriter.Write(iasVectors);
            objFileWriter.Write(iasSDTClObjs);
            objFileWriter.Write(iasSDTIntfObjs);
        }

        public void ReadFromFile(BinaryReader objFileReader)
        {
            iasArrays = objFileReader.ReadInt32();
            iasChars = objFileReader.ReadInt32();
            iasFloats = objFileReader.ReadInt32();
            iasIntegers = objFileReader.ReadInt32();
            iasLists = objFileReader.ReadInt32();
            iasObjects = objFileReader.ReadInt32();
            iasRotations = objFileReader.ReadInt32();
            iasStrings = objFileReader.ReadInt32();
            iasVectors = objFileReader.ReadInt32();
            iasSDTClObjs = objFileReader.ReadInt32();
            iasSDTIntfObjs = objFileReader.ReadInt32();
        }
    }

    public class XMRStackFrame
    {
        public XMRStackFrame nextSF;
        public string funcName;
        public int callNo;
        public object[] objArray;
    }

    /*
     * Contains only items required by the stand-alone compiler
     * so the compiler doesn't need to pull in all of OpenSim.
     *
     * Inherit from ScriptBaseClass so we can be used as 'this'
     * parameter for backend-API calls, eg llSay().
     */
    public abstract class XMRInstAbstract: ScriptBaseClass
    {
        public const int CallMode_NORMAL = 0;  // when function is called, it proceeds normally
        public const int CallMode_SAVE = 1;  // StackSaveException() was thrown, push args/locals to stackFrames
        public const int CallMode_RESTORE = 2;  // when function is called, it pops state from stackFrames

        public bool suspendOnCheckRunHold;  // suspend script execution until explicitly set false
        public bool suspendOnCheckRunTemp;  // suspend script execution for single step only
        public int stackLimit;              // stack must have at least this many bytes free on entry to functions
        public int m_StackLeft;             // total number of stack bytes yet to be used (init to stacksize)

        public ScriptObjCode m_ObjCode;     // script object code this instance was created from

        public object[] ehArgs;             // event handler argument array
        public bool doGblInit = true;       // default state_entry() needs to initialize global variables
        public int stateCode = 0;           // state the script is in (0 = 'default')
        public int newStateCode = -1;       // if >= 0, in the middle of exiting 'stateCode' and entering 'newStateCode'
        public ScriptEventCode eventCode = ScriptEventCode.None;
        // what event handler is executing (or None if not)

        public int callMode = CallMode_NORMAL;
        // to capture stack frames on stackFrames:
        //    set to CallMode_SAVE just before throwing StackSaveException()
        //    from within CheckRun() and cleared to CallMode_NORMAL when
        //    the exception is caught
        // to restore stack frames from stackFrames:
        //    set to CallMode_RESTORE just before calling CallSEH() and 
        //    cleared to CallMode_NORMAL by CheckRun()
        public XMRStackFrame stackFrames;   // stack frames being saved/restored

        private static readonly char[] justacomma = { ',' };

        /*
         * These arrays hold the global variable values for the script instance.
         * The array lengths are determined by the script compilation,
         * and are found in ScriptObjCode.glblSizes.
         */
        public XMRInstArrays glblVars;

        public XMRInstAbstract()
        {
            glblVars = new XMRInstArrays(this);
        }

        /****************************************************************\
         *  Abstract function prototypes.                               *
         *  These functions require access to the OpenSim environment.  *
        \****************************************************************/

        public abstract void CheckRunWork();
        public abstract void StateChange();

        [xmrMethodCallsCheckRunAttribute] // calls CheckRun()
        [xmrMethodIsNoisyAttribute]       // calls Stub<somethingorother>()
        public abstract LSL_List xmrEventDequeue(double timeout, int returnMask1, int returnMask2,
                                                  int backgroundMask1, int backgroundMask2);

        [xmrMethodIsNoisyAttribute]       // calls Stub<somethingorother>()
        public abstract void xmrEventEnqueue(LSL_List ev);

        [xmrMethodIsNoisyAttribute]       // calls Stub<somethingorother>()
        public abstract LSL_List xmrEventSaveDets();

        [xmrMethodIsNoisyAttribute]       // calls Stub<somethingorother>()
        public abstract void xmrEventLoadDets(LSL_List dpList);


        /**************************************************\
         *  Functions what don't require runtime support  *
         *  beyond what the compiler provides.            *
        \**************************************************/

        protected int heapLimit;
        public int m_localsHeapUsed;

        public virtual int UpdateLocalsHeapUse(int olduse, int newuse)
        {
            int newtotal = Interlocked.Add(ref m_localsHeapUsed, newuse - olduse);
            if (newtotal + glblVars.arraysHeapUse > heapLimit)
                throw new OutOfHeapException(glblVars.arraysHeapUse + newtotal + olduse - newuse, newtotal + glblVars.arraysHeapUse, heapLimit);
            return newuse;
        }
        // not in use
        public virtual int UpdateArraysHeapUse(int olduse, int newuse)
        {
            if(newuse + glblVars.arraysHeapUse > heapLimit)
                throw new OutOfHeapException(glblVars.arraysHeapUse, glblVars.arraysHeapUse + newuse, heapLimit);
            return newuse;
        }

        public virtual void AddLocalsHeapUse(int delta)
        {
            Interlocked.Add(ref m_localsHeapUsed, delta);
        }

        public int xmrHeapLeft()
        {
            return heapLimit - m_localsHeapUsed - glblVars.arraysHeapUse;
        }

        public int xmrHeapUsed()
        {
            return m_localsHeapUsed + glblVars.arraysHeapUse;
        }

        /**
         * @brief Call script's event handler function from the very beginning.
         * @param instance.stateCode = which state the event is happening in
         * @param instance.eventCode = which event is happening in that state
         * @returns when event handler has completed or throws an exception
         *          with instance.eventCode = ScriptEventCode.None
         */
        public void CallSEH()
        {
            ScriptEventHandler seh;

            // CallMode_NORMAL:  run event handler from the beginning normally
            // CallMode_RESTORE: restore event handler stack from stackFrames
            callMode = (stackFrames is null) ? XMRInstAbstract.CallMode_NORMAL :
                                               XMRInstAbstract.CallMode_RESTORE;

            while(true)
            {
                if(newStateCode < 0)
                {
                    // Process event given by 'stateCode' and 'eventCode'.
                    // The event handler should call CheckRun() as often as convenient.
                    int newState = stateCode;
                    seh = m_ObjCode.scriptEventHandlerTable[newState, (int)eventCode];
                    if(seh is not null)
                    {
                        try
                        {
                            seh(this);
                        }
                        catch(ScriptChangeStateException scse)
                        {
                            newState = scse.newState;
                        }
                    }
                    ehArgs = null;  // we are done with them and no args for
                                         // exit_state()/enter_state() anyway

                    // The usual case is no state change.
                    // Even a 'state <samestate>;' statement has no effect except to exit out.
                    // It does not execute the state_exit() or state_entry() handlers.
                    // See http://wiki.secondlife.com/wiki/State
                    if(newState == stateCode)
                        break;

                    // Save new state in a more permanent location in case we
                    // get serialized out while in the state_exit() handler.
                    newStateCode = newState;
                }

                // Call old state's state_exit() handler.
                eventCode = ScriptEventCode.state_exit;
                seh = m_ObjCode.scriptEventHandlerTable[stateCode, (int)ScriptEventCode.state_exit];
                if(seh is not null)
                {
                    try
                    {
                        seh(this);
                    }
                    catch(ScriptChangeStateException scse)
                    {
                        newStateCode = scse.newState;
                    }
                }

                // Switch over to the new state's state_entry() handler.
                stateCode = newStateCode;
                eventCode = ScriptEventCode.state_entry;
                newStateCode = -1;

                // Now that the old state can't possibly start any more activity,
                // cancel any listening handlers, etc, of the old state.
                StateChange();

                // Loop back to execute new state's state_entry() handler.
            }

            // Event no longer being processed.
            stackFrames = null;
            eventCode = ScriptEventCode.None;
        }

        /**
         * @brief For compatibility with old code.
         */
        public void CheckRun(int line)
        {
            CheckRunStack();
        }

        /**
         * @brief Called at beginning of complex functions to see if they
         *        are nested too deep possibly in a recursive loop.
         */
        public void CheckRunStack()
        {
            if(m_StackLeft < stackLimit)
                throw new OutOfStackException();

            CheckRunQuick();
        }

        /**
         * @brief Called in each iteration of a loop to see if running too long.
         */
        public void CheckRunQuick()
        {
            //if (suspendOnCheckRunHold || suspendOnCheckRunTemp)
            CheckRunWork();
        }

        /**
         * @brief Called during CallMode_SAVE to create a stackframe save object that saves 
         *        local variables and calling point within the function.
         * @param funcName = name of function whose frame is being saved
         * @param callNo = call number (ie, return address) within function to restart at
         * @param nSaves = number of variables the function will save
         * @returns an object[nSaves] where function can save variables
         */
        public object[] CaptureStackFrame(string funcName, int callNo, int nSaves)
        {
            XMRStackFrame sf = new XMRStackFrame();
            sf.nextSF = stackFrames;
            sf.funcName = funcName;
            sf.callNo = callNo;
            sf.objArray = new object[nSaves];
            stackFrames = sf;
            return sf.objArray;
        }

        /**
         * @brief Called during CallMode_RESTORE to pop a stackframe object to restore 
         *        local variables and calling point within the function.
         * @param funcName = name of function whose frame is being restored
         * @returns the object[nSaves] where function can retrieve variables
         *          callNo = as passed to CaptureStackFrame() indicating restart point
         */
        public object[] RestoreStackFrame(string funcName, out int callNo)
        {
            XMRStackFrame sf = stackFrames;
            if(sf.funcName != funcName)
                throw new Exception("frame mismatch " + sf.funcName + " vs " + funcName);

            callNo = sf.callNo;
            stackFrames = sf.nextSF;
            sf.nextSF = null;
            return sf.objArray;
        }

        /**
         * @brief Convert all LSL_Integers in a list to System.Int32s, 
         *        as required by llParcelMediaQuery().
         */
        public static LSL_List FixLLParcelMediaQuery(LSL_List oldlist)
        {
            object[] oldarray = oldlist.Data;
            object[] newarray = new object[oldarray.Length];
            for(int i = 0; i < oldarray.Length; i++)
            {
                object obj = oldarray[i];
                if(obj is LSL_Integer io)
                    obj = io.value;
                newarray[i] = obj;
            }
            return new LSL_List(newarray);
        }

        /**
         * @brief Convert *SOME* LSL_Integers in a list to System.Int32s, 
         *        as required by llParcelMediaCommandList().
         */
        public static LSL_List FixLLParcelMediaCommandList(LSL_List oldlist)
        {
            object[] oldarray = oldlist.Data;
            object[] newarray = new object[oldarray.Length];
            int verbatim = 0;
            for(int i = 0; i < oldarray.Length; i++)
            {
                object obj = oldarray[i];
                if(--verbatim < 0)
                {
                    if(obj is LSL_Integer lio)
                        obj = lio.value;
                    if(obj is int io)
                    {
                        switch(io)
                        {
                            case ScriptBaseClass.PARCEL_MEDIA_COMMAND_AUTO_ALIGN:
                                // leave next integer as LSL_Integer
                                verbatim = 1;
                                break;

                            case ScriptBaseClass.PARCEL_MEDIA_COMMAND_SIZE:
                                // leave next two integers as LSL_Integer
                                verbatim = 2;
                                break;

                        }
                    }
                }
                newarray[i] = obj;
            }
            return new LSL_List(newarray);
        }

        public static int xmrHashCode(int i)
        {
            return i.GetHashCode();
        }

        public static int xmrHashCode(double f)
        {
            return f.GetHashCode();
        }

        public static int xmrHashCode(object o)
        {
            return o.GetHashCode();
        }

        public static int xmrHashCode(string s)
        {
            return s.GetHashCode();
        }

        public string xmrTypeName(object o)
        {
            /*
             * Basic types return constant strings of the script-visible type name.
             */
            if(o is XMR_Array)
                return "array";
            if(o is bool)
                return "bool";
            if(o is char)
                return "char";
            if(o is Exception)
                return "exception";
            if(o is double)
                return "float";
            if(o is float)
                return "float";
            if(o is LSL_Float)
                return "float";
            if(o is int)
                return "integer";
            if(o is LSL_Integer)
                return "integer";
            if(o is LSL_List)
                return "list";
            if(o is LSL_Rotation)
                return "rotation";
            if(o is LSL_String)
                return "string";
            if(o is string)
                return "string";
            if(o is LSL_Vector)
                return "vector";

            // A script-defined interface is represented as an array of delegates.
            // If that is the case, convert it to the object of the script-defined 
            // class that is implementing the interface.  This should let the next 
            // step get the script-defined type name of the object.
            if(o is Delegate[] dao)
                o = dao[0].Target;

            // If script-defined class instance, get the script-defined 
            // type name.
            if(o is XMRSDTypeClObj XMRSDTypeClObjo) 
                return XMRSDTypeClObjo.sdtcClass.longName.val;

            // If it's a delegate, maybe we can look up its script-defined type name.
            Type ot = o.GetType();
            if(o is Delegate)
            {
                if(m_ObjCode.sdDelTypes.TryGetValue(ot, out string os))
                    return os;
            }

            // Don't know what it is, get the C#-level type name.
            return ot.ToString();
        }

        /**
         * @brief Call the current state's event handler.
         * @param ev = as returned by xmrEventDequeue saying which event handler to call
         *             and what argument list to pass to it.  The llDetect...() parameters
         *             are as currently set for the script (use xmrEventLoadDets to set how
         *             you want them to be different).
         */
        public void xmrEventCallHandler(LSL_List ev)
        {
            object[] data = ev.Data;
            int evc = ev.GetIntegerItem(0);
            ScriptEventHandler seh = m_ObjCode.scriptEventHandlerTable[stateCode, evc];
            if(seh is not null)
            {
                int nargs = data.Length - 1;
                object[] args = new object[nargs];
                Array.Copy(data, 1, args, 0, nargs);

                object[] saveEHArgs = this.ehArgs;
                ScriptEventCode saveEventCode = this.eventCode;

                ehArgs = args;
                eventCode = (ScriptEventCode)evc;

                seh(this);

                ehArgs = saveEHArgs;
                eventCode = saveEventCode;
            }
        }

        /**
         * @brief These conversions throw exceptions if there is anything stinky...
         */
        public double xmrString2Float(string s)
        {
            return double.Parse(s, CultureInfo.InvariantCulture);
        }

        public int xmrString2Integer(string s)
        {
            s = s.Trim();
            if(s.StartsWith("0x") || s.StartsWith("0X"))
                return int.Parse(s.Substring(2), NumberStyles.HexNumber);

            return int.Parse(s, CultureInfo.InvariantCulture);
        }

        public LSL_Rotation xmrString2Rotation(string s)
        {
            s = s.Trim();
            if(!s.StartsWith("<") || !s.EndsWith(">"))
                throw new FormatException("doesn't begin with < and end with >");

            s = s.Substring(1, s.Length - 2);
            string[] splitup = s.Split(justacomma, 5);
            if(splitup.Length != 4)
                throw new FormatException("doesn't have exactly 3 commas");

            double x = double.Parse(splitup[0], CultureInfo.InvariantCulture);
            double y = double.Parse(splitup[1], CultureInfo.InvariantCulture);
            double z = double.Parse(splitup[2], CultureInfo.InvariantCulture);
            double w = double.Parse(splitup[3], CultureInfo.InvariantCulture);
            return new LSL_Rotation(x, y, z, w);
        }

        public LSL_Vector xmrString2Vector(string s)
        {
            s = s.Trim();
            if(!s.StartsWith("<") || !s.EndsWith(">"))
                throw new FormatException("doesn't begin with < and end with >");

            s = s.Substring(1, s.Length - 2);
            string[] splitup = s.Split(justacomma, 4);
            if(splitup.Length != 3)
                throw new FormatException("doesn't have exactly 2 commas");

            double x = double.Parse(splitup[0], CultureInfo.InvariantCulture);
            double y = double.Parse(splitup[1], CultureInfo.InvariantCulture);
            double z = double.Parse(splitup[2], CultureInfo.InvariantCulture);
            return new LSL_Vector(x, y, z);
        }

        /**
         * @brief Access C#-style formatted numeric conversions.
         */
        public string xmrFloat2String(double val, string fmt)
        {
            return val.ToString(fmt, CultureInfo.InvariantCulture);
        }

        public string xmrInteger2String(int val, string fmt)
        {
            return val.ToString(fmt, CultureInfo.InvariantCulture);
        }

        public string xmrRotation2String(LSL_Rotation val, string fmt)
        {
            return "<" + val.x.ToString(fmt, CultureInfo.InvariantCulture) + "," +
                         val.y.ToString(fmt, CultureInfo.InvariantCulture) + "," +
                         val.z.ToString(fmt, CultureInfo.InvariantCulture) + "," +
                         val.s.ToString(fmt, CultureInfo.InvariantCulture) + ">";
        }

        public string xmrVector2String(LSL_Vector val, string fmt)
        {
            return "<" + val.x.ToString(fmt, CultureInfo.InvariantCulture) + "," +
                         val.y.ToString(fmt, CultureInfo.InvariantCulture) + "," +
                         val.z.ToString(fmt, CultureInfo.InvariantCulture) + ">";
        }

        /**
         * @brief Get a delegate for a script-defined function.
         * @param name = name of the function including arg types, eg,
         *               "Verify(array,list,string)"
         * @param sig  = script-defined type name
         * @param targ = function's 'this' pointer or null if static
         * @returns delegate for the script-defined function
         */
        public Delegate GetScriptMethodDelegate(string name, string sig, object targ)
        {
            DynamicMethod dm = m_ObjCode.dynamicMethods[name];
            TokenDeclSDTypeDelegate dt = (TokenDeclSDTypeDelegate)m_ObjCode.sdObjTypesName[sig];
            return dm.CreateDelegate(dt.GetSysType(), targ);
        }

        /**
         * @brief Try to cast the thrown object to the given script-defined type.
         * @param thrown = what object was thrown
         * @param inst = what script instance we are running in
         * @param sdtypeindex = script-defined type to try to cast it to
         * @returns null: thrown is not castable to sdtypename
         *          else: an object casted to sdtypename
         */
        public static object XMRSDTypeCatchTryCastToSDType(object thrown, XMRInstAbstract inst, int sdtypeindex)
        {
            TokenDeclSDType sdType = inst.m_ObjCode.sdObjTypesIndx[sdtypeindex];

            // If it is a script-defined interface object, convert to the original XMRSDTypeClObj.
            if(thrown is Delegate[] dt)
            {
                thrown = dt[0].Target;
            }

            // If it is a script-defined delegate object, make sure it is an instance of the expected type.
            if(thrown is Delegate)
            {
                Type ot = thrown.GetType();
                Type tt = sdType.GetSysType();
                return (ot == tt) ? thrown : null;
            }

            // If it is a script-defined class object, make sure it is an instance of the expected class.
            if(thrown is XMRSDTypeClObj XMRSDTypeClObjthrown)
            {
                // Step from the object's actual class rootward.
                // If we find the requested class along the way, the cast is valid.
                // If we run off the end of the root, the cast is not valid.
                for(TokenDeclSDTypeClass ac = XMRSDTypeClObjthrown.sdtcClass; ac is not null; ac = ac.extends)
                {
                    if(ac == sdType)
                        return thrown;
                }
            }

            // Don't know what it is, assume it is not what caller wants.
            return null;
        }

        /**
         * @brief Allocate and access fixed-dimension arrays.
         */
        public static object xmrFixedArrayAllocC(int len)
        {
            return new char[len];
        }
        public static object xmrFixedArrayAllocF(int len)
        {
            return new double[len];
        }
        public static object xmrFixedArrayAllocI(int len)
        {
            return new int[len];
        }
        public static object xmrFixedArrayAllocO(int len)
        {
            return new object[len];
        }

        public static char xmrFixedArrayGetC(object arr, int idx)
        {
            return ((char[])arr)[idx];
        }
        public static double xmrFixedArrayGetF(object arr, int idx)
        {
            return ((double[])arr)[idx];
        }
        public static int xmrFixedArrayGetI(object arr, int idx)
        {
            return ((int[])arr)[idx];
        }
        public static object xmrFixedArrayGetO(object arr, int idx)
        {
            return ((object[])arr)[idx];
        }

        public static void xmrFixedArraySetC(object arr, int idx, char val)
        {
            ((char[])arr)[idx] = val;
        }
        public static void xmrFixedArraySetF(object arr, int idx, double val)
        {
            ((double[])arr)[idx] = val;
        }
        public static void xmrFixedArraySetI(object arr, int idx, int val)
        {
            ((int[])arr)[idx] = val;
        }
        public static void xmrFixedArraySetO(object arr, int idx, object val)
        {
            ((object[])arr)[idx] = val;
        }

        /**
         * @brief Copy from one script-defined array to another.
         * @param srcobj = source script-defined array class object pointer
         * @param srcstart = offset in source array to start copying from
         * @param dstobj = destination script-defined array class object pointer
         * @param dststart = offset in destination arry to start copying to
         * @param count = number of elements to copy
         */
        public static void xmrArrayCopy(object srcobj, int srcstart, object dstobj, int dststart, int count)
        {
            // The script writer should only pass us script-defined class objects.
            // Throw exception otherwise.
            XMRSDTypeClObj srcsdt = (XMRSDTypeClObj)srcobj;
            XMRSDTypeClObj dstsdt = (XMRSDTypeClObj)dstobj;

            // Get the script-visible type name of the arrays, brackets and all.
            string srctypename = srcsdt.sdtcClass.longName.val;
            string dsttypename = dstsdt.sdtcClass.longName.val;

            // The part before the first '[' of each should match exactly,
            // meaning the basic data type (eg, float, List<string>) is the same.
            // And there must be a '[' in each meaning that it is a script-defined array type.
            int i = srctypename.IndexOf('[');
            int j = dsttypename.IndexOf('[');
            if((i < 0) || (j < 0))
                throw new InvalidCastException("non-array passed: " + srctypename + " and/or " + dsttypename);
            if((i != j) || !srctypename.StartsWith(dsttypename.Substring(0, j)))
                throw new ArrayTypeMismatchException(srctypename + " vs " + dsttypename);

            // The number of brackets must match exactly.
            // This permits copying from something like a float[,][] to something like a float[][].
            // But you cannot copy from a float[][] to a float[] or wisa wersa.
            // Counting either '[' or ']' would work equally well.
            int srclen = srctypename.Length;
            int dstlen = dsttypename.Length;
            int srcjags = 0;
            int dstjags = 0;
            while(++i < srclen)
                if(srctypename[i] == ']')
                    srcjags++;
            while(++j < dstlen)
                if(dsttypename[j] == ']')
                    dstjags++;
            if(dstjags != srcjags)
                throw new ArrayTypeMismatchException(srctypename + " vs " + dsttypename);

            // Perform the copy.
            Array srcarray = (Array)srcsdt.instVars.iarObjects[0];
            Array dstarray = (Array)dstsdt.instVars.iarObjects[0];
            Array.Copy(srcarray, srcstart, dstarray, dststart, count);
        }

        /**
         * @brief Copy from an array to a list.
         * @param srcar = the array to copy from
         * @param start = where to start in the array
         * @param count = number of elements
         * @returns the list
         */
        public static LSL_List xmrArray2List(object srcar, int start, int count)
        {
            // Get the script-visible type of the array.
            // We only do arrays.
            XMRSDTypeClObj array = (XMRSDTypeClObj)srcar;
            TokenDeclSDTypeClass sdtClass = array.sdtcClass;
            if(sdtClass.arrayOfRank == 0)
                throw new InvalidCastException("only do arrays not " + sdtClass.longName.val);

            // Validate objects they want to put in the list.
            // We can't allow anything funky that OpenSim runtime doesn't expect.
            Array srcarray = (Array)array.instVars.iarObjects[0];
            object[] output = new object[count];
            for(int i = 0; i < count; i++)
            {
                object src = srcarray.GetValue(i + start);
                if(src == null)
                    throw new NullReferenceException("null element " + i);
                if(src is double dsrc)
                {
                    output[i] = new LSL_Float(dsrc);
                    continue;
                }
                if(src is int isrc)
                {
                    output[i] = new LSL_Integer(isrc);
                    continue;
                }
                if(src is LSL_Rotation)
                {
                    output[i] = src;
                    continue;
                }
                if(src is LSL_Vector)
                {
                    output[i] = src;
                    continue;
                }
                if(src is string ssrc)
                {
                    output[i] = new LSL_String(ssrc);
                    continue;
                }
                throw new InvalidCastException("invalid element " + i + " type " + src.GetType().Name);
            }

            // Make a list out of that now immutable array.
            return new LSL_List(output);
        }

        /**
         * @brief Copy from a list to an array.
         * @param srclist  = list to copy from
         * @param srcstart = where to start in the list
         * @param dstobj   = array to copy to
         * @param dststart = where to start in the array
         * @param count    = number of elements
         */
        public static void xmrList2Array(LSL_List srclist, int srcstart, object dstobj, int dststart, int count)
        {
            // Get the script-visible type of the destination.
            // We only do arrays.
            XMRSDTypeClObj dstarray = (XMRSDTypeClObj)dstobj;
            TokenDeclSDTypeClass sdtClass = dstarray.sdtcClass;
            if(sdtClass.arrayOfType == null)
                throw new InvalidCastException("only do arrays not " + sdtClass.longName.val);

            // Copy from the immutable array to the mutable array.
            // Strip off any LSL wrappers as the script code doesn't expect any.
            object[] srcarr = srclist.Data;
            Array dstarr = (Array)dstarray.instVars.iarObjects[0];

            for(int i = 0; i < count; i++)
            {
                object obj = srcarr[i + srcstart];
                if(obj is LSL_Float lfo)
                    obj = lfo.value;
                else if(obj is LSL_Integer lio)
                    obj = lio.value;
                else if(obj is LSL_String lso)
                    obj = lso.m_string;
                dstarr.SetValue(obj, i + dststart);
            }
        }

        /**
         * @brief Copy from an array of characters to a string.
         * @param srcar = the array to copy from
         * @param start = where to start in the array
         * @param count = number of elements
         * @returns the string
         */
        public static string xmrChars2String(object srcar, int start, int count)
        {
            // Make sure they gave us a script-defined array object.
            XMRSDTypeClObj array = (XMRSDTypeClObj)srcar;
            TokenDeclSDTypeClass sdtClass = array.sdtcClass;
            if(sdtClass.arrayOfRank == 0)
                throw new InvalidCastException("only do arrays not " + sdtClass.longName.val);

            // We get a type cast error from mono if they didn't give us a character array.
            // But if it is ok, create a string from the requested characters.
            char[] srcarray = (char[])array.instVars.iarObjects[0];
            return new string(srcarray, start, count);
        }

        /**
         * @brief Copy from a string to a character array.
         * @param srcstr   = string to copy from
         * @param srcstart = where to start in the string
         * @param dstobj   = array to copy to
         * @param dststart = where to start in the array
         * @param count    = number of elements
         */
        public static void xmrString2Chars(string srcstr, int srcstart, object dstobj, int dststart, int count)
        {
            // Make sure they gave us a script-defined array object.
            XMRSDTypeClObj dstarray = (XMRSDTypeClObj)dstobj;
            TokenDeclSDTypeClass sdtClass = dstarray.sdtcClass;
            if(sdtClass.arrayOfType == null)
                throw new InvalidCastException("only do arrays not " + sdtClass.longName.val);

            // We get a type cast error from mono if they didn't give us a character array.
            // But if it is ok, copy from the string to the character array.
            char[] dstarr = (char[])dstarray.instVars.iarObjects[0];
            for(int i = 0; i < count; i++)
                dstarr[i + dststart] = srcstr[i + srcstart];
        }

        /**
         * @brief Exception-related runtime calls.
         */
        // Return exception message (no type information just the message)
        public static string xmrExceptionMessage(Exception ex)
        {
            return ex.Message;
        }

        public static string yExceptionMessage(Exception ex)
        {
            return ex.Message;
        }

        // Return stack trace (no type or message, just stack trace lines: at ... \n)
        public string xmrExceptionStackTrace(Exception ex)
        {
            return XMRExceptionStackString(ex);
        }

        public string yExceptionStackTrace(Exception ex)
        {
            return XMRExceptionStackString(ex);
        }

        // Return value thrown by a throw statement
        public static object xmrExceptionThrownValue(Exception ex)
        {
            return ((ScriptThrownException)ex).thrown;
        }

        public static object yExceptionThrownValue(Exception ex)
        {
            return ((ScriptThrownException)ex).thrown;
        }

        // Return exception's short type name, eg, NullReferenceException, ScriptThrownException, etc.
        public static string xmrExceptionTypeName(Exception ex)
        {
            return ex.GetType().Name;
        }

        public static string yExceptionTypeName(Exception ex)
        {
            return ex.GetType().Name;
        }

        // internal use only: converts any IL addresses in script-defined methods to source location equivalent
        // Mono ex.StackTrace:
        //   at OpenSim.Region.ScriptEngine.YEngine.TypeCast.ObjectToInteger (System.Object x) [0x0005e] in /home/kunta/opensim-0.9/addon-modules/YEngine/Module/MMRScriptTypeCast.cs:750
        //   at (wrapper dynamic-method) System.Object:default state_entry (OpenSim.Region.ScriptEngine.YEngine.XMRInstAbstract) [0x00196]

        // Microsoft ex.StackTrace:
        //    at OpenSim.Region.ScriptEngine.YEngine.TypeCast.ObjectToInteger(Object x) in C:\Users\mrieker\opensim-0.9-source\addon-modules\YEngine\Module\MMRScriptTypeCast.cs:line 750
        //    at default state_entry (XMRInstAbstract )
        public string XMRExceptionStackString(Exception ex)
        {
            string stwhole = ex.StackTrace;
            string[] stlines = stwhole.Split(new char[] { '\n' });
            StringBuilder sb = new StringBuilder();
            foreach(string st in stlines)
            {
                string stline = st.Trim();
                if(stline.Length == 0)
                    continue;

                // strip 'at' off the front of line
                if(stline.StartsWith("at "))
                {
                    stline = stline.Substring(3);
                }

                // strip '(wrapper ...' off front of line
                if(stline.StartsWith("(wrapper dynamic-method) System.Object:"))
                {
                    stline = stline.Substring(39);
                }

                // strip the (systemargtypes...) from our dynamic method names cuz it's messy
                //  'default state_entry (XMRInstAbstract )'
                //      => 'default state_entry'
                //  'CallSomethingThatThrows(string) (OpenSim.Region.ScriptEngine.YEngine.XMRInstance,string)'
                //      => 'CallSomethingThatThrows(string)'
                int kwin = stline.IndexOf(" in ");
                int br0x = stline.IndexOf(" [0x");
                int pastCloseParen = stline.Length;
                if((kwin >= 0) && (br0x >= 0))
                    pastCloseParen = Math.Min(kwin, br0x);
                else if(kwin >= 0)
                    pastCloseParen = kwin;
                else if(br0x >= 0)
                    pastCloseParen = br0x;
                else
                    pastCloseParen = stline.Length;
                int endFuncName = pastCloseParen;
                while(endFuncName > 0)
                {
                    if(stline[--endFuncName] == '(')
                        break;
                }
                while(endFuncName > 0)
                {
                    if(stline[endFuncName - 1] != ' ')
                        break;
                    --endFuncName;
                }
                string funcName = stline.Substring(0, endFuncName);
                KeyValuePair<int, ScriptSrcLoc>[] srcLocs;
                if(m_ObjCode.scriptSrcLocss.TryGetValue(funcName, out srcLocs))
                {
                    stline = stline.Substring(0, endFuncName) + stline.Substring(pastCloseParen);
                    kwin = stline.IndexOf(" in ");
                    br0x = stline.IndexOf(" [0x");
                }

                // keyword 'in' is just before filename:linenumber that goes to end of line
                // trim up the corresponding filename (ie, remove useless path info)
                if(kwin >= 0)
                {
                    int begfn = kwin + 4;
                    int slash = begfn;
                    for(int i = begfn; i < stline.Length; i++)
                    {
                        char c = stline[i];
                        if((c == '/') || (c == '\\'))
                            slash = i + 1;
                    }
                    stline = stline.Substring(0, begfn) + stline.Substring(slash);
                }
                else if(srcLocs != null)
                {

                    // no filename:linenumber info, try to convert IL offset
                    if(br0x >= 0)
                    {
                        try
                        {
                            int begiloffs = br0x + 4;
                            int endiloffs = stline.IndexOf("]", begiloffs);
                            int iloffset = int.Parse(stline.Substring(begiloffs, endiloffs - begiloffs),
                                                       System.Globalization.NumberStyles.HexNumber);

                            int srcLocIdx;
                            int srcLocLen = srcLocs.Length;
                            for(srcLocIdx = 0; ++srcLocIdx < srcLocLen;)
                            {
                                if(iloffset < srcLocs[srcLocIdx].Key)
                                    break;
                            }
                            ScriptSrcLoc srcLoc = srcLocs[--srcLocIdx].Value;

                            stline = stline.Substring(0, br0x) + " <" +
                                        srcLoc.file + '(' + srcLoc.line + ',' + srcLoc.posn + ")>";
                        }
                        catch
                        {
                        }
                    }
                }

                // put edited line in output string
                if(sb.Length > 0)
                    sb.AppendLine();
                sb.Append("  at ");
                sb.Append(stline);
            }
            return sb.ToString();
        }

        /**
         * @brief List fonts available.
         */
        public LSL_List xmrFontsAvailable()
        {
            System.Drawing.FontFamily[] families = System.Drawing.FontFamily.Families;
            object[] output = new object[families.Length];
            for(int i = 0; i < families.Length; i++)
                output[i] = new LSL_String(families[i].Name);

            return new LSL_List(output);
        }

        /************************\
         *  Used by decompiler  *
        \************************/

        public bool xmrRotationToBool(LSL_Rotation x)
        {
            return TypeCast.RotationToBool(x);
        }
        public bool xmrStringToBool(string x)
        {
            return TypeCast.StringToBool(x);
        }
        public bool xmrVectorToBool(LSL_Vector x)
        {
            return TypeCast.VectorToBool(x);
        }
        public bool xmrKeyToBool(string x)
        {
            return TypeCast.KeyToBool(x);
        }
        public bool xmrListToBool(LSL_List x)
        {
            return TypeCast.ListToBool(x);
        }

        public int xmrStringCompare(string x, string y)
        {
            return string.Compare(x, y);
        }

        /**
         * @brief types of data we serialize
         */
        private enum Ser: byte
        {
            NULL,
            EVENTCODE,
            LSLFLOAT,
            LSLINT,
            LSLKEY,
            LSLLIST,
            LSLROT,
            LSLSTR,
            LSLVEC,
            SYSARRAY,
            SYSDOUB,
            SYSFLOAT,
            SYSINT,
            SYSSTR,
            XMRARRAY,
            DUPREF,
            SYSBOOL,
            XMRINST,
            DELEGATE,
            SDTCLOBJ,
            SYSCHAR,
            SYSERIAL,
            THROWNEX
        }

        /**
         * @brief Write state out to a stream.
         *        Do not change script state.
         */
        public void MigrateOut(BinaryWriter mow)
        {
            try
            {
                migrateOutWriter = mow;
                migrateOutObjects = new Dictionary<object, int>();
                migrateOutLists = new Dictionary<object[], ObjLslList>();
                SendObjValue(ehArgs);
                mow.Write(doGblInit);
                mow.Write(stateCode);
                mow.Write((int)eventCode);
                glblVars.SendArrays(SendObjValue);
                if(newStateCode >= 0)
                {
                    mow.Write("**newStateCode**");
                    mow.Write(newStateCode);
                }
                for(XMRStackFrame thisSF = stackFrames; thisSF is not null; thisSF = thisSF.nextSF)
                {
                    mow.Write(thisSF.funcName);
                    mow.Write(thisSF.callNo);
                    SendObjValue(thisSF.objArray);
                }
                mow.Write("");
            }
            finally
            {
                migrateOutWriter = null;
                migrateOutObjects = null;
                migrateOutLists = null;
            }
        }

        /**
         * @brief Write an object to the output stream.
         * @param graph = object to send
         */
        private BinaryWriter migrateOutWriter;
        private Dictionary<object, int> migrateOutObjects;
        private Dictionary<object[], ObjLslList> migrateOutLists;
        public void SendObjValue(object graph)
        {
            BinaryWriter mow = migrateOutWriter;

            // Value types (including nulls) are always output directly.
            if(graph is null)
            {
                mow.Write((byte)Ser.NULL);
                return;
            }
            if(graph is ScriptEventCode)
            {
                mow.Write((byte)Ser.EVENTCODE);
                mow.Write((int)graph);
                return;
            }
            if(graph is LSL_Float lfg)
            {
                mow.Write((byte)Ser.LSLFLOAT);
                mow.Write(lfg.value);
                return;
            }
            if(graph is LSL_Integer lig)
            {
                mow.Write((byte)Ser.LSLINT);
                mow.Write(lig.value);
                return;
            }
            if(graph is LSL_Key lkg)
            {
                mow.Write((byte)Ser.LSLKEY);
                SendObjValue(lkg.m_string);  // m_string can be null
                return;
            }
            if(graph is LSL_Rotation lrg)
            {
                mow.Write((byte)Ser.LSLROT);
                mow.Write(lrg.x);
                mow.Write(lrg.y);
                mow.Write(lrg.z);
                mow.Write(lrg.s);
                return;
            }
            if(graph is LSL_String lsg)
            {
                mow.Write((byte)Ser.LSLSTR);
                SendObjValue(lsg.m_string);  // m_string can be null
                return;
            }
            if(graph is LSL_Vector lvg)
            {
                mow.Write((byte)Ser.LSLVEC);
                mow.Write(lvg.x);
                mow.Write(lvg.y);
                mow.Write(lvg.z);
                return;
            }
            if(graph is bool)
            {
                mow.Write((byte)Ser.SYSBOOL);
                mow.Write((bool)graph);
                return;
            }
            if(graph is double dg)
            {
                mow.Write((byte)Ser.SYSDOUB);
                mow.Write((double)graph);
                return;
            }
            if(graph is float fg)
            {
                mow.Write((byte)Ser.SYSFLOAT);
                mow.Write(fg);
                return;
            }
            if(graph is int ig)
            {
                mow.Write((byte)Ser.SYSINT);
                mow.Write((int)graph);
                return;
            }
            if(graph is char cg)
            {
                mow.Write((byte)Ser.SYSCHAR);
                mow.Write(cg);
                return;
            }

            // Script instance pointer is always just that.
            if(graph == this)
            {
                mow.Write((byte)Ser.XMRINST);
                return;
            }

            // Convert lists to object type.
            // This is compatible with old migration data and also
            // two vars pointing to same list won't duplicate it.
            if(graph is LSL_List llg)
            {
                object[] data = llg.Data;
                if(!migrateOutLists.TryGetValue(data, out ObjLslList ollg))
                {
                    ollg = new ObjLslList();
                    ollg.objarray = data;
                    migrateOutLists[data] = ollg;
                }
                graph = ollg;
            }

            // If this same exact object was already serialized,
            // just output an index telling the receiver to use
            // that same old object, rather than creating a whole
            // new object with the same values.  Also this prevents
            // self-referencing objects (like arrays) from causing
            // an infinite loop.
            if(migrateOutObjects.TryGetValue(graph, out int ident))
            {
                mow.Write((byte)Ser.DUPREF);
                mow.Write(ident);
                return;
            }

            // Object not seen before, save its address with an unique
            // ident number that the receiver can easily regenerate.
            ident = migrateOutObjects.Count;
            migrateOutObjects.Add(graph, ident);

            // Now output the object's value(s).
            // If the object self-references, the object is alreay entered
            // in the dictionary and so the self-reference will just emit
            // a DUPREF tag instead of trying to output the whole object 
            // again.
            if(graph is ObjLslList oll)
            {
                mow.Write((byte)Ser.LSLLIST);
                SendObjValue(oll.objarray);
            }
            else if(graph is XMR_Array xg)
            {
                mow.Write((byte)Ser.XMRARRAY);
                xg.SendArrayObj(SendObjValue);
            }
            else if(graph is Array array)
            {
                mow.Write((byte)Ser.SYSARRAY);
                mow.Write(SysType2String(array.GetType().GetElementType()));
                mow.Write(array.Length);
                for(int i = 0; i < array.Length; i++)
                    SendObjValue(array.GetValue(i));
            }
            else if(graph is string sg)
            {
                mow.Write((byte)Ser.SYSSTR);
                mow.Write(sg);
            }
            else if(graph is Delegate del)
            {
                mow.Write((byte)Ser.DELEGATE);
                mow.Write(del.Method.Name);
                Type delType = del.GetType();
                foreach(KeyValuePair<string, TokenDeclSDType> kvp in m_ObjCode.sdObjTypesName)
                {
                    TokenDeclSDType sdt = kvp.Value;
                    if(sdt is TokenDeclSDTypeDelegate sdtd)
                    {
                        if(sdtd.GetSysType() == delType)
                        {
                            mow.Write(kvp.Key);
                            goto found;
                        }
                    }
                }
                throw new Exception("cant find script-defined delegate for " + del.Method.Name + " type " + del.GetType());
                found:
                SendObjValue(del.Target);
            }
            else if(graph is XMRSDTypeClObj XMRSDTypeClObjgraph)
            {
                mow.Write((byte)Ser.SDTCLOBJ);
                XMRSDTypeClObjgraph.Capture(this.SendObjValue);
            }
            else if(graph is ScriptThrownException ScriptThrownExceptiongraph)
            {
                MemoryStream memoryStream = new MemoryStream();
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter =
                        new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                bformatter.Serialize(memoryStream, graph);
                byte[] rawBytes = memoryStream.ToArray();
                mow.Write((byte)Ser.THROWNEX);
                mow.Write((int)rawBytes.Length);
                mow.Write(rawBytes);
                SendObjValue(ScriptThrownExceptiongraph.thrown);
            }
            else
            {
                MemoryStream memoryStream = new MemoryStream();
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter =
                        new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                bformatter.Serialize(memoryStream, graph);
                byte[] rawBytes = memoryStream.ToArray();
                mow.Write((byte)Ser.SYSERIAL);
                mow.Write(rawBytes.Length);
                mow.Write(rawBytes);
            }
        }

        /**
         * @brief Use short strings for known type names.
         */
        private static string SysType2String(Type type)
        {
            if(type.IsArray && (type.GetArrayRank() == 1))
            {
                string str = KnownSysType2String(type.GetElementType());
                if(str is not null)
                    return str + "[]";
            }
            else
            {
                string str = KnownSysType2String(type);
                if(str is not null)
                    return str;
            }
            return type.ToString();
        }
        private static string KnownSysType2String(Type type)
        {
            if(type == typeof(bool))
                return "bo";
            if(type == typeof(char))
                return "ch";
            if(type == typeof(Delegate))
                return "de";
            if(type == typeof(double))
                return "do";
            if(type == typeof(float))
                return "fl";
            if(type == typeof(int))
                return "in";
            if(type == typeof(LSL_List))
                return "li";
            if(type == typeof(object))
                return "ob";
            if(type == typeof(LSL_Rotation))
                return "ro";
            if(type == typeof(XMRSDTypeClObj))
                return "sc";
            if(type == typeof(string))
                return "st";
            if(type == typeof(LSL_Vector))
                return "ve";
            if(type == typeof(XMR_Array))
                return "xa";
            return null;
        }
        private static Type String2SysType(string str)
        {
            if(str.EndsWith("[]"))
                return String2SysType(str.Substring(0, str.Length - 2)).MakeArrayType();

            if(str == "bo")
                return typeof(bool);
            if(str == "ch")
                return typeof(char);
            if(str == "de")
                return typeof(Delegate);
            if(str == "do")
                return typeof(double);
            if(str == "fl")
                return typeof(float);
            if(str == "in")
                return typeof(int);
            if(str == "li")
                return typeof(LSL_List);
            if(str == "ob")
                return typeof(object);
            if(str == "ro")
                return typeof(LSL_Rotation);
            if(str == "sc")
                return typeof(XMRSDTypeClObj);
            if(str == "st")
                return typeof(string);
            if(str == "ve")
                return typeof(LSL_Vector);
            if(str == "xa")
                return typeof(XMR_Array);
            return Type.GetType(str, true);
        }

        /**
         * @brief Read state in from a stream.
         */
        public void MigrateIn(BinaryReader mir)
        {
            try
            {
                migrateInReader = mir;
                migrateInObjects = new Dictionary<int, object>();
                ehArgs = (object[])RecvObjValue();
                doGblInit = mir.ReadBoolean();
                stateCode = mir.ReadInt32();
                eventCode = (ScriptEventCode)mir.ReadInt32();
                newStateCode = -1;
                glblVars.RecvArrays(RecvObjValue);
                XMRStackFrame lastSF = null;
                string funcName;
                while((funcName = mir.ReadString()) != "")
                {
                    if(funcName == "**newStateCode**")
                    {
                        newStateCode = mir.ReadInt32();
                        continue;
                    }
                    XMRStackFrame thisSF = new XMRStackFrame();
                    thisSF.funcName = funcName;
                    thisSF.callNo = mir.ReadInt32();
                    thisSF.objArray = (object[])RecvObjValue();
                    if(lastSF == null)
                        stackFrames = thisSF;
                    else
                        lastSF.nextSF = thisSF;
                    lastSF = thisSF;
                }
            }
            finally
            {
                migrateInReader = null;
                migrateInObjects = null;
            }
        }

        /**
         * @brief Read a single value from the stream.
         * @returns value (boxed as needed)
         */
        private BinaryReader migrateInReader;
        private Dictionary<int, object> migrateInObjects;
        public object RecvObjValue()
        {
            BinaryReader mir = this.migrateInReader;
            int ident = this.migrateInObjects.Count;
            Ser code = (Ser)mir.ReadByte();
            switch(code)
            {
                case Ser.NULL:
                    return null;

                case Ser.EVENTCODE:
                    return (ScriptEventCode)mir.ReadInt32();

                case Ser.LSLFLOAT:
                    return new LSL_Float(mir.ReadDouble());

                case Ser.LSLINT:
                    return new LSL_Integer(mir.ReadInt32());

                case Ser.LSLKEY:
                    return new LSL_Key((string)RecvObjValue());

                case Ser.LSLLIST:
                {
                    this.migrateInObjects.Add(ident, null);    // placeholder
                    object[] data = (object[])RecvObjValue();  // read data, maybe using another index
                    LSL_List list = new LSL_List(data);        // make LSL-level list
                    this.migrateInObjects[ident] = list;        // fill in slot
                    return list;
                }

                case Ser.LSLROT:
                {
                    double x = mir.ReadDouble();
                    double y = mir.ReadDouble();
                    double z = mir.ReadDouble();
                    double w = mir.ReadDouble();
                    return new LSL_Rotation(x, y, z, w);
                }
                case Ser.LSLSTR:
                    return new LSL_String((string)RecvObjValue());

                case Ser.LSLVEC:
                {
                    double x = mir.ReadDouble();
                    double y = mir.ReadDouble();
                    double z = mir.ReadDouble();
                    return new LSL_Vector(x, y, z);
                }

                case Ser.SYSARRAY:
                {
                    Type eletype = String2SysType(mir.ReadString());
                    int length = mir.ReadInt32();
                    Array array = Array.CreateInstance(eletype, length);
                    this.migrateInObjects.Add(ident, array);
                    for(int i = 0; i < length; i++)
                        array.SetValue(RecvObjValue(), i);
                    return array;
                }

                case Ser.SYSBOOL:
                    return mir.ReadBoolean();

                case Ser.SYSDOUB:
                    return mir.ReadDouble();

                case Ser.SYSFLOAT:
                    return mir.ReadSingle();

                case Ser.SYSINT:
                    return mir.ReadInt32();

                case Ser.SYSCHAR:
                    return mir.ReadChar();

                case Ser.SYSSTR:
                    string s = mir.ReadString();
                    this.migrateInObjects.Add(ident, s);
                    return s;

                case Ser.XMRARRAY:
                {
                    XMR_Array array = new XMR_Array(this);
                    this.migrateInObjects.Add(ident, array);
                    array.RecvArrayObj(this.RecvObjValue);
                    return array;
                }

                case Ser.DUPREF:
                {
                    ident = mir.ReadInt32();
                    object obj = this.migrateInObjects[ident];
                    if(obj is ObjLslList)
                        obj = new LSL_List(((ObjLslList)obj).objarray);
                    return obj;
                }

                case Ser.XMRINST:
                    return this;

                case Ser.DELEGATE:
                    this.migrateInObjects.Add(ident, null);  // placeholder
                    string name = mir.ReadString();         // function name
                    string sig = mir.ReadString();         // delegate type
                    object targ = this.RecvObjValue();      // 'this' object
                    Delegate del = this.GetScriptMethodDelegate(name, sig, targ);
                    this.migrateInObjects[ident] = del;       // actual value
                    return del;

                case Ser.SDTCLOBJ:
                    XMRSDTypeClObj clobj = new XMRSDTypeClObj();
                    this.migrateInObjects.Add(ident, clobj);
                    clobj.Restore(this, this.RecvObjValue);
                    return clobj;

                case Ser.SYSERIAL:
                {
                    int rawLength = mir.ReadInt32();
                    byte[] rawBytes = mir.ReadBytes(rawLength);
                    MemoryStream memoryStream = new MemoryStream(rawBytes);
                    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter =
                            new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                    object graph = bformatter.Deserialize(memoryStream);
                    this.migrateInObjects.Add(ident, graph);
                    return graph;
                }

                case Ser.THROWNEX:
                {
                    int rawLength = mir.ReadInt32();
                    byte[] rawBytes = mir.ReadBytes(rawLength);
                    MemoryStream memoryStream = new MemoryStream(rawBytes);
                    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter =
                            new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                    object graph = bformatter.Deserialize(memoryStream);
                    this.migrateInObjects.Add(ident, graph);
                    ((ScriptThrownException)graph).thrown = RecvObjValue();
                    return graph;
                }

                default:
                    throw new Exception("bad stream code " + code.ToString());
            }
        }

        // wrapper around list object arrays to make sure they are always object types for migration purposes
        private class ObjLslList
        {
            public object[] objarray;
        }
    }

    // Any xmr...() methods that call CheckRun() must be tagged with this attribute
    // so the ScriptCodeGen will know the method is non-trivial.
    public class xmrMethodCallsCheckRunAttribute: Attribute
    {
    }

    // Any xmr...() methods in xmrengtest that call Stub<somethingorother>() must be 
    // tagged with this attribute so the -builtins option will tell the user that 
    // they are a stub function.
    public class xmrMethodIsNoisyAttribute: Attribute
    {
    }

    // Any script callable methods that really return a key not a string should be
    // tagged with this attribute so the compiler will know they return type key and
    // not type string.
    public class xmrMethodReturnsKeyAttribute: Attribute
    {
    }

    [SerializableAttribute]
    public class OutOfHeapException: Exception
    {
        public OutOfHeapException(int oldtotal, int newtotal, int limit)
                : base("(OWNER)oldtotal=" + oldtotal + ", newtotal=" + newtotal + ", limit=" + limit)
        {
        }
    }

    [SerializableAttribute]
    public class OutOfStackException: Exception
    {
    }
}
