/*
 * 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.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

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;

/**
 * Contains classes that disassemble or decompile an yobj file.
 * See xmrengcomp.cx utility program.
 */

namespace OpenSim.Region.ScriptEngine.Yengine
{
    /*
     * Encapsulate object code for a method.
     */
    public abstract class ObjectTokens
    {
        public ScriptObjCode scriptObjCode;

        public ObjectTokens(ScriptObjCode scriptObjCode)
        {
            this.scriptObjCode = scriptObjCode;
        }

        public abstract void Close();
        public abstract void BegMethod(DynamicMethod method);
        public abstract void EndMethod();
        public abstract void DefineLabel(int number, string name);
        public abstract void DefineLocal(int number, string name, string type, Type syType);
        public abstract void DefineMethod(string methName, Type retType, Type[] argTypes, string[] argNames);
        public abstract void MarkLabel(int offset, int number);
        public abstract void BegExcBlk(int offset);
        public abstract void BegCatBlk(int offset, Type excType);
        public abstract void BegFinBlk(int offset);
        public abstract void EndExcBlk(int offset);
        public abstract void EmitNull(int offset, OpCode opCode);
        public abstract void EmitField(int offset, OpCode opCode, FieldInfo field);
        public abstract void EmitLocal(int offset, OpCode opCode, int number);
        public abstract void EmitType(int offset, OpCode opCode, Type type);
        public abstract void EmitLabel(int offset, OpCode opCode, int number);
        public abstract void EmitLabels(int offset, OpCode opCode, int[] numbers);
        public abstract void EmitMethod(int offset, OpCode opCode, MethodInfo method);
        public abstract void EmitCtor(int offset, OpCode opCode, ConstructorInfo ctor);
        public abstract void EmitDouble(int offset, OpCode opCode, double value);
        public abstract void EmitFloat(int offset, OpCode opCode, float value);
        public abstract void EmitInteger(int offset, OpCode opCode, int value);
        public abstract void EmitString(int offset, OpCode opCode, string value);
    }

    /******************\
     *  DISASSEMBLER  *
    \******************/

    public class OTDisassemble: ObjectTokens
    {
        private static readonly int OPCSTRWIDTH = 12;

        private Dictionary<int, string> labelNames;
        private Dictionary<int, string> localNames;
        private StringBuilder lbuf = new StringBuilder();
        private TextWriter twout;

        public OTDisassemble(ScriptObjCode scriptObjCode, TextWriter twout) : base(scriptObjCode)
        {
            this.twout = twout;
        }

        public override void Close()
        {
            twout.WriteLine("TheEnd.");
        }

        /**
         * About to generate object code for this method.
         */
        public override void BegMethod(DynamicMethod method)
        {
            labelNames = new Dictionary<int, string>();
            localNames = new Dictionary<int, string>();

            twout.WriteLine("");

            lbuf.Append(method.ReturnType.Name);
            lbuf.Append(' ');
            lbuf.Append(method.Name);

            ParameterInfo[] parms = method.GetParameters();
            int nArgs = parms.Length;
            lbuf.Append(" (");
            for(int i = 0; i < nArgs; i++)
            {
                if(i > 0)
                    lbuf.Append(", ");
                lbuf.Append(parms[i].ParameterType.Name);
            }
            lbuf.Append(')');
            FlushLine();

            lbuf.Append('{');
            FlushLine();
        }

        /**
         * Dump out reconstructed source for this method.
         */
        public override void EndMethod()
        {
            lbuf.Append('}');
            FlushLine();
        }

        /**
         * Add instructions to stream.
         */
        public override void DefineLabel(int number, string name)
        {
            labelNames[number] = name + "$" + number;
        }

        public override void DefineLocal(int number, string name, string type, Type syType)
        {
            localNames[number] = name + "$" + number;

            lbuf.Append("          ");
            lbuf.Append(type.PadRight(OPCSTRWIDTH - 1));
            lbuf.Append(' ');
            lbuf.Append(localNames[number]);
            FlushLine();
        }

        public override void DefineMethod(string methName, Type retType, Type[] argTypes, string[] argNames)
        {
        }

        public override void MarkLabel(int offset, int number)
        {
            LinePrefix(offset);
            lbuf.Append(labelNames[number]);
            lbuf.Append(":");
            FlushLine();
        }

        public override void BegExcBlk(int offset)
        {
            LinePrefix(offset);
            lbuf.Append(" BeginExceptionBlock");
            FlushLine();
        }

        public override void BegCatBlk(int offset, Type excType)
        {
            LinePrefix(offset);
            lbuf.Append(" BeginCatchBlock ");
            lbuf.Append(excType.Name);
            FlushLine();
        }

        public override void BegFinBlk(int offset)
        {
            LinePrefix(offset);
            lbuf.Append(" BeginFinallyBlock");
            FlushLine();
        }

        public override void EndExcBlk(int offset)
        {
            LinePrefix(offset);
            lbuf.Append(" EndExceptionBlock");
            FlushLine();
        }

        public override void EmitNull(int offset, OpCode opCode)
        {
            LinePrefix(offset, opCode);
            FlushLine();
        }

        public override void EmitField(int offset, OpCode opCode, FieldInfo field)
        {
            LinePrefix(offset, opCode);
            lbuf.Append(field.DeclaringType.Name);
            lbuf.Append(':');
            lbuf.Append(field.Name);
            lbuf.Append(" -> ");
            lbuf.Append(field.FieldType.Name);
            lbuf.Append("   (field)");
            FlushLine();
        }

        public override void EmitLocal(int offset, OpCode opCode, int number)
        {
            LinePrefix(offset, opCode);
            lbuf.Append(localNames[number]);
            lbuf.Append("   (local)");
            FlushLine();
        }

        public override void EmitType(int offset, OpCode opCode, Type type)
        {
            LinePrefix(offset, opCode);
            lbuf.Append(type.Name);
            lbuf.Append("   (type)");
            FlushLine();
        }

        public override void EmitLabel(int offset, OpCode opCode, int number)
        {
            LinePrefix(offset, opCode);
            lbuf.Append(labelNames[number]);
            lbuf.Append("   (label)");
            FlushLine();
        }

        public override void EmitLabels(int offset, OpCode opCode, int[] numbers)
        {
            LinePrefix(offset, opCode);

            int lineLen = lbuf.Length;
            int nLabels = numbers.Length;
            for(int i = 0; i < nLabels; i++)
            {
                if(i > 0)
                {
                    lbuf.AppendLine();
                    lbuf.Append(",".PadLeft(lineLen));
                }
                lbuf.Append(labelNames[numbers[i]]);
            }

            FlushLine();
        }

        public override void EmitMethod(int offset, OpCode opCode, MethodInfo method)
        {
            LinePrefix(offset, opCode);

            ParameterInfo[] parms = method.GetParameters();
            int nArgs = parms.Length;
            if(method.DeclaringType != null)
            {
                lbuf.Append(method.DeclaringType.Name);
                lbuf.Append(':');
            }
            lbuf.Append(method.Name);
            lbuf.Append('(');
            for(int i = 0; i < nArgs; i++)
            {
                if(i > 0)
                    lbuf.Append(",");
                lbuf.Append(parms[i].ParameterType.Name);
            }
            lbuf.Append(") -> ");
            lbuf.Append(method.ReturnType.Name);

            FlushLine();
        }

        public override void EmitCtor(int offset, OpCode opCode, ConstructorInfo ctor)
        {
            LinePrefix(offset, opCode);

            ParameterInfo[] parms = ctor.GetParameters();
            int nArgs = parms.Length;
            lbuf.Append(ctor.DeclaringType.Name);
            lbuf.Append(":(");
            for(int i = 0; i < nArgs; i++)
            {
                if(i > 0)
                    lbuf.Append(",");
                lbuf.Append(parms[i].ParameterType.Name);
            }
            lbuf.Append(")");

            FlushLine();
        }

        public override void EmitDouble(int offset, OpCode opCode, double value)
        {
            LinePrefix(offset, opCode);
            lbuf.Append(value.ToString());
            lbuf.Append("   (double)");
            FlushLine();
        }

        public override void EmitFloat(int offset, OpCode opCode, float value)
        {
            LinePrefix(offset, opCode);
            lbuf.Append(value.ToString());
            lbuf.Append("   (float)");
            FlushLine();
        }

        public override void EmitInteger(int offset, OpCode opCode, int value)
        {
            LinePrefix(offset, opCode);
            lbuf.Append(value.ToString());
            lbuf.Append("   (int)");
            FlushLine();
        }

        public override void EmitString(int offset, OpCode opCode, string value)
        {
            LinePrefix(offset, opCode);
            lbuf.Append("\"");
            lbuf.Append(value);
            lbuf.Append("\"   (string)");
            FlushLine();
        }

        /**
         * Put offset and opcode at beginning of line.
         */
        private void LinePrefix(int offset, OpCode opCode)
        {
            LinePrefix(offset);
            lbuf.Append("  ");
            lbuf.Append(opCode.ToString().PadRight(OPCSTRWIDTH - 1));
            lbuf.Append(' ');
        }

        private void LinePrefix(int offset)
        {
            lbuf.Append("  ");
            lbuf.Append(offset.ToString("X4"));
            lbuf.Append("  ");
        }

        /**
         * Flush line buffer to output file.
         */
        private void FlushLine()
        {
            if(lbuf.Length > 0)
            {
                twout.WriteLine(lbuf.ToString());
                lbuf.Remove(0, lbuf.Length);
            }
        }
    }

    /****************\
     *  DECOMPILER  *
    \****************/

    /**
     * Note:  The decompiler does not handle any xmroption extensions
     *        such as &&&, |||, ? operators and switch statements, as
     *        they do branches with a non-empty stack, which is way
     *        beyond this code's ability to analyze.
     */

    public class OTDecompile: ObjectTokens
    {
        public const string _mainCallNo = "__mainCallNo$";
        public const string _callLabel = "__call_";
        public const string _callMode = "callMode";
        public const string _checkRunQuick = "CheckRunQuick";
        public const string _checkRunStack = "CheckRunStack";
        public const string _cmRestore = "__cmRestore";
        public const string _doBreak = "dobreak_";
        public const string _doCont = "docont_";
        public const string _doGblInit = "doGblInit";
        public const string _doLoop = "doloop_";
        public const string _ehArgs = "ehArgs";
        public const string _forBreak = "forbreak_";
        public const string _forCont = "forcont_";
        public const string _forLoop = "forloop_";
        public const string _globalvarinit = "$globalvarinit()";
        public const string _heapTrackerPop = "Pop";
        public const string _heapTrackerPush = "Push";
        public const string _ifDone = "ifdone_";
        public const string _ifElse = "ifelse_";
        public const string _llAbstemp = "llAbstemp";
        public const string _retlbl = "__retlbl";
        public const string _retval = "__retval$";
        public const string _whileBreak = "whilebreak_";
        public const string _whileCont = "whilecont_";
        public const string _whileLoop = "whileloop_";
        public const string _xmrinst = "__xmrinst";
        public const string _xmrinstlocal = "__xmrinst$";

        private const string INDENT = "    ";
        private const string LABELINDENT = "  ";

        private static Dictionary<string, string> typeTranslator = InitTypeTranslator();
        private static Dictionary<string, string> InitTypeTranslator()
        {
            Dictionary<string, string> d = new Dictionary<string, string>();
            d["Boolean"] = "integer";
            d["bool"] = "integer";
            d["Double"] = "float";
            d["double"] = "float";
            d["Int32"] = "integer";
            d["int"] = "integer";
            d["htlist"] = "list";
            d["htobject"] = "object";
            d["htstring"] = "string";
            d["lslfloat"] = "float";
            d["lslint"] = "integer";
            d["lsllist"] = "list";
            d["lslrot"] = "rotation";
            d["lslstr"] = "string";
            d["lslvec"] = "vector";
            d["Quaternion"] = "rotation";
            d["String"] = "string";
            d["Vector3"] = "vector";
            return d;
        }

        private Dictionary<int, OTLocal> eharglist;
        private Dictionary<int, OTLabel> labels;
        private Dictionary<int, OTLocal> locals;
        private Dictionary<string, string[]> methargnames;
        private LinkedList<OTCilInstr> cilinstrs;
        private OTStmtBlock topBlock;
        private Stack<OTOpnd> opstack;
        private Stack<OTStmtBegExcBlk> trystack;
        private Stack<OTStmtBlock> blockstack;

        private int dupNo;
        private DynamicMethod method;
        private string laststate;
        private TextWriter twout;

        public OTDecompile(ScriptObjCode scriptObjCode, TextWriter twout) : base(scriptObjCode)
        {
            this.twout = twout;
            twout.Write("xmroption dollarsigns;");
            methargnames = new Dictionary<string, string[]>();
        }

        public override void Close()
        {
            if(laststate != null)
            {
                twout.Write("\n}");
                laststate = null;
            }
            twout.Write('\n');
        }

        /**
         * About to generate object code for this method.
         */
        public override void BegMethod(DynamicMethod method)
        {
            this.method = method;

            eharglist = new Dictionary<int, OTLocal>();
            labels = new Dictionary<int, OTLabel>();
            locals = new Dictionary<int, OTLocal>();
            cilinstrs = new LinkedList<OTCilInstr>();
            opstack = new Stack<OTOpnd>();
            trystack = new Stack<OTStmtBegExcBlk>();
            blockstack = new Stack<OTStmtBlock>();

            dupNo = 0;
        }

        /**
         * Dump out reconstructed source for this method.
         */
        public override void EndMethod()
        {
             // Convert CIL code to primitive statements.
             // There are a bunch of labels and internal code such as call stack save restore.
            topBlock = new OTStmtBlock();
            blockstack.Push(topBlock);
            for(LinkedListNode<OTCilInstr> link = cilinstrs.First; link != null; link = link.Next)
            {
                link.Value.BuildStatements(this, link);
            }

             // Strip out stuff we don't want, such as references to callMode.
             // This strips out stack frame capture and restore code.
            topBlock.StripStuff(null);

            // including a possible final return statement
            // - delete if void return value
            // - delete if returning __retval cuz we converted all __retval assignments to return statements
            if((topBlock.blkstmts.Last != null) && (topBlock.blkstmts.Last.Value is OTStmtRet))
            {
                OTStmtRet finalret = (OTStmtRet)topBlock.blkstmts.Last.Value;
                if((finalret.value == null) ||
                        ((finalret.value is OTOpndLocal) &&
                                ((OTOpndLocal)finalret.value).local.name.StartsWith(_retval)))
                {
                    topBlock.blkstmts.RemoveLast();
                }
            }

             // At this point, all behind-the-scenes references are removed except
             // that the do/for/if/while blocks are represented by OTStmtCont-style
             // if/jumps.  So try to convert them to the higher-level structures.
            topBlock.DetectDoForIfWhile(null);

             // Final strip to get rid of unneeded @forbreak_<suffix>; labels and the like.
            topBlock.StripStuff(null);

             // Build reference counts so we don't output unneeded declarations,
             // especially temps and internal variables.
            foreach(OTLocal local in locals.Values)
            {
                local.nlclreads = 0;
                local.nlclwrites = 0;
            }
            topBlock.CountRefs();
            for(IEnumerator<int> localenum = locals.Keys.GetEnumerator(); localenum.MoveNext();)
            {
                OTLocal local = locals[localenum.Current];
                if(((local.nlclreads | local.nlclwrites) == 0) || local.name.StartsWith(_xmrinstlocal))
                {
                    locals.Remove(localenum.Current);
                    localenum = locals.Keys.GetEnumerator();
                }
            }

             // Strip the $n off of local vars that are not ambiguous.
             // Make sure they don't mask globals and arguments as well.
            Dictionary<string, int> namecounts = new Dictionary<string, int>();
            foreach(Dictionary<int, string> varnames in scriptObjCode.globalVarNames.Values)
            {
                foreach(string varname in varnames.Values)
                {
                    int count;
                    if(!namecounts.TryGetValue(varname, out count))
                        count = 0;
                    namecounts[varname] = count + 1;
                }
            }
            if(methargnames.ContainsKey(method.Name))
            {
                foreach(string argname in methargnames[method.Name])
                {
                    int count;
                    if(!namecounts.TryGetValue(argname, out count))
                        count = 0;
                    namecounts[argname] = count + 1;
                }
            }
            foreach(OTLocal local in locals.Values)
            {
                int i = local.name.LastIndexOf('$');
                string name = local.name.Substring(0, i);
                int count;
                if(!namecounts.TryGetValue(name, out count))
                    count = 0;
                namecounts[name] = count + 1;
            }
            foreach(OTLocal local in locals.Values)
            {
                int i = local.name.LastIndexOf('$');
                string name = local.name.Substring(0, i);
                int count = namecounts[name];
                if(count == 1)
                    local.name = name;
            }

             // Print out result.
            if(method.Name == _globalvarinit)
            {
                GlobalsDump();
            }
            else
            {
                MethodDump();
            }
        }

        /**
         * Add instructions to stream.
         */
        public override void DefineLabel(int number, string name)
        {
            labels.Add(number, new OTLabel(number, name));
        }
        public override void DefineLocal(int number, string name, string type, Type syType)
        {
            locals.Add(number, new OTLocal(number, name, type));
        }
        public override void DefineMethod(string methName, Type retType, Type[] argTypes, string[] argNames)
        {
            methargnames[methName] = argNames;
        }
        public override void MarkLabel(int offset, int number)
        {
            OTCilInstr label = labels[number];
            label.offset = offset;
            cilinstrs.AddLast(label);
        }
        public override void BegExcBlk(int offset)
        {
            cilinstrs.AddLast(new OTCilBegExcBlk(offset));
        }
        public override void BegCatBlk(int offset, Type excType)
        {
            cilinstrs.AddLast(new OTCilBegCatBlk(offset, excType));
        }
        public override void BegFinBlk(int offset)
        {
            cilinstrs.AddLast(new OTCilBegFinBlk(offset));
        }
        public override void EndExcBlk(int offset)
        {
            cilinstrs.AddLast(new OTCilEndExcBlk(offset));
        }
        public override void EmitNull(int offset, OpCode opCode)
        {
            cilinstrs.AddLast(new OTCilNull(offset, opCode));
        }
        public override void EmitField(int offset, OpCode opCode, FieldInfo field)
        {
            cilinstrs.AddLast(new OTCilField(offset, opCode, field));
        }
        public override void EmitLocal(int offset, OpCode opCode, int number)
        {
            cilinstrs.AddLast(new OTCilLocal(offset, opCode, locals[number]));
        }
        public override void EmitType(int offset, OpCode opCode, Type type)
        {
            cilinstrs.AddLast(new OTCilType(offset, opCode, type));
        }
        public override void EmitLabel(int offset, OpCode opCode, int number)
        {
            cilinstrs.AddLast(new OTCilLabel(offset, opCode, labels[number]));
        }
        public override void EmitLabels(int offset, OpCode opCode, int[] numbers)
        {
            OTLabel[] labelarray = new OTLabel[numbers.Length];
            for(int i = 0; i < numbers.Length; i++)
            {
                labelarray[i] = labels[numbers[i]];
            }
            cilinstrs.AddLast(new OTCilLabels(offset, opCode, labelarray));
        }
        public override void EmitMethod(int offset, OpCode opCode, MethodInfo method)
        {
            cilinstrs.AddLast(new OTCilMethod(offset, opCode, method));
        }
        public override void EmitCtor(int offset, OpCode opCode, ConstructorInfo ctor)
        {
            cilinstrs.AddLast(new OTCilCtor(offset, opCode, ctor));
        }
        public override void EmitDouble(int offset, OpCode opCode, double value)
        {
            cilinstrs.AddLast(new OTCilDouble(offset, opCode, value));
        }
        public override void EmitFloat(int offset, OpCode opCode, float value)
        {
            cilinstrs.AddLast(new OTCilFloat(offset, opCode, value));
        }
        public override void EmitInteger(int offset, OpCode opCode, int value)
        {
            cilinstrs.AddLast(new OTCilInteger(offset, opCode, value));
        }
        public override void EmitString(int offset, OpCode opCode, string value)
        {
            cilinstrs.AddLast(new OTCilString(offset, opCode, value));
        }

        /**
         * Add the given statement to the end of the currently open block.
         */
        public void AddLastStmt(OTStmt stmt)
        {
            blockstack.Peek().blkstmts.AddLast(stmt);
        }

        /**
         * Generate output for $globalvarinit() function.
         * Also outputs declarations for global variables.
         */
        private void GlobalsDump()
        {
             // Scan $globalvarinit().  It should only have global var assignments in it.
             // Also gather up list of variables it initializes.
            bool badinit = false;
            Dictionary<string, string> inittypes = new Dictionary<string, string>();
            foreach(OTStmt stmt in topBlock.blkstmts)
            {
                if(!(stmt is OTStmtStore))
                {
                    badinit = true;
                    break;
                }
                OTStmtStore store = (OTStmtStore)stmt;
                if(!(store.varwr is OTOpndGlobal))
                {
                    badinit = true;
                    break;
                }
                OTOpndGlobal globalop = (OTOpndGlobal)store.varwr;
                inittypes[globalop.PrintableString] = "";
            }

             // Scan through list of all global variables in the script.
             // Output declarations for those what don't have any init statement for them.
             // Save the type for those that do have init statements.
            bool first = true;
            foreach(string iartypename in scriptObjCode.globalVarNames.Keys)
            {
                Dictionary<int, string> varnames = scriptObjCode.globalVarNames[iartypename];
                string typename = iartypename.ToLowerInvariant();
                if(typename.StartsWith("iar"))
                    typename = typename.Substring(3);
                if(typename.EndsWith("s"))
                    typename = typename.Substring(0, typename.Length - 1);
                foreach(string varname in varnames.Values)
                {
                    if(!badinit && inittypes.ContainsKey(varname))
                    {
                        inittypes[varname] = typename;
                    }
                    else
                    {
                        if(first)
                            twout.Write('\n');
                        twout.Write('\n' + typename + ' ' + varname + ';');
                        first = false;
                    }
                }
            }

             // If $globalvarinit() has anything bad in it, output it as a function.
             // Otherwise, output it as a series of global declarations with init values.
            if(badinit)
            {
                MethodDump();
            }
            else
            {
                foreach(OTStmt stmt in topBlock.blkstmts)
                {
                    OTStmtStore store = (OTStmtStore)stmt;
                    OTOpndGlobal globalop = (OTOpndGlobal)store.varwr;
                    string name = globalop.PrintableString;
                    if(first)
                        twout.Write('\n');
                    twout.Write('\n' + inittypes[name] + ' ');
                    store.PrintStmt(twout, "");
                    first = false;
                }
            }
        }

        /**
         * Generate output for other functions.
         */
        private void MethodDump()
        {
            string indent;

             // Event handlers don't have an argument list as such in the original
             // code.  Instead they have a series of assignments from ehargs[] to
             // local variables.  So make those local variables look like they are
             // an argument list.
            int i = method.Name.IndexOf(' ');
            if(i >= 0)
            {
                 // Maybe we have to output the state name.
                string statename = method.Name.Substring(0, i);
                string eventname = method.Name.Substring(++i);

                if(laststate != statename)
                {
                    if(laststate != null)
                        twout.Write("\n}");
                    if(statename == "default")
                    {
                        twout.Write("\n\ndefault {");
                    }
                    else
                    {
                        twout.Write("\n\nstate " + statename + " {");
                    }
                    laststate = statename;
                }
                else
                {
                    twout.Write('\n');
                }

                 // Output event name and argument list.
                 // Remove from locals list so they don't print below.
                twout.Write('\n' + INDENT + eventname + " (");
                MethodInfo meth = typeof(IEventHandlers).GetMethod(eventname);
                i = 0;
                foreach(ParameterInfo pi in meth.GetParameters())
                {
                    // skip the first param cuz it's the XMRInstance arg
                    if(i > 0)
                        twout.Write(", ");
                    OTLocal local;
                    if(eharglist.TryGetValue(i, out local) && locals.ContainsKey(local.number))
                    {
                        twout.Write(local.DumpString());
                        locals.Remove(local.number);
                    }
                    else
                    {
                        // maybe the assignment was removed
                        // eg, because the local was write-only (not referenced)
                        // so substitute in placeholder that won't be referenced
                        twout.Write(AbbrType(pi.ParameterType) + " arg$" + (i + 1));
                    }
                    i++;
                }
                twout.Write(')');

                 // Indent method body by 4 spaces.
                indent = INDENT;
            }
            else
            {
                 // Maybe need to close out previous state.
                if(laststate != null)
                {
                    twout.Write("\n}");
                    laststate = null;
                }

                 // Output blank line and return type (if any).
                twout.Write("\n\n");
                if(method.ReturnType != typeof(void))
                {
                    twout.Write(AbbrType(method.ReturnType) + ' ');
                }

                 // Output method name and argument list.
                int j = method.Name.IndexOf('(');
                if(j < 0)
                {
                    twout.Write(method.Name);
                }
                else
                {
                    twout.Write(method.Name.Substring(0, j) + " (");
                    bool first = true;
                    j = 0;
                    foreach(ParameterInfo pi in method.GetParameters())
                    {
                        if(j > 0)
                        {  // skip the XMRInstance arg$0 parameter
                            if(!first)
                                twout.Write(", ");
                            twout.Write(AbbrType(pi.ParameterType) + ' ' + MethArgName(j));
                            first = false;
                        }
                        j++;
                    }
                    twout.Write(')');
                }

                 // Don't indent method body at all.
                indent = "";
            }

             // Output local variable declarations.
            twout.Write('\n' + indent + '{');
            bool didOne = false;
            foreach(OTLocal local in locals.Values)
            {
                twout.Write('\n' + indent + INDENT + local.DumpString() + ";  // r:" + local.nlclreads + " w:" + local.nlclwrites);
                didOne = true;
            }
            if(didOne)
                twout.Write('\n');

             // Output statements.
            if(topBlock.blkstmts.Count == 0)
            {
                twout.Write(" }");
            }
            else
            {
                topBlock.PrintBodyAndEnd(twout, indent);
            }
        }

        /**
         * Get abbreviated type string.
         */
        public static string AbbrType(Type type)
        {
            if(type == null)
                return "null";
            return AbbrType(type.Name);
        }
        public static string AbbrType(string type)
        {
            if(type.StartsWith("OpenSim.Region.ScriptEngine.YEngine."))
            {
                type = type.Substring(38);
                int i = type.IndexOf(',');
                if(i > 0)
                    type = type.Substring(0, i);
            }
            if(typeTranslator.ContainsKey(type))
            {
                type = typeTranslator[type];
            }
            return type;
        }

        /**
         * Get current method's argument name.
         */
        public string MethArgName(int index)
        {
            string[] argnames;
            if(methargnames.TryGetValue(method.Name, out argnames) && (index < argnames.Length))
            {
                return argnames[index];
            }
            return "arg$" + index;
        }

        /**
         * Strip svperflvovs (float) cast from rotation/vector values.
         */
        public static OTOpnd StripFloatCast(OTOpnd op)
        {
            if(op is OTOpndCast)
            {
                OTOpndCast opcast = (OTOpndCast)op;
                if((opcast.type == typeof(double)) && (opcast.value is OTOpndInt))
                {
                    return opcast.value;
                }
            }
            return op;
        }

        /**
         * Strip svperflvovs Brtrues so we don't end up with stuff like 'if (!! someint) ...'.
         */
        public static OTOpnd StripBrtrue(OTOpnd op)
        {
            if(op is OTOpndUnOp)
            {
                OTOpndUnOp opunop = (OTOpndUnOp)op;
                if(opunop.opCode == MyOp.Brtrue)
                    return opunop.value;
            }
            return op;
        }

        /*
         * Local variable declaration.
         */
        private class OTLocal
        {
            public int number;
            public string name;
            public string type;

            public int nlclreads;
            public int nlclwrites;

            public OTLocal(int number, string name, string type)
            {
                this.number = number;
                this.name = name.StartsWith("tmp$") ? name : name + "$" + number;
                this.type = type;
            }

            public string DumpString()
            {
                return AbbrType(type) + ' ' + name;
            }
        }

        /***********************************************\
         *  Tokens that are one-for-one with CIL code  *
        \***********************************************/

        /*
         * Part of instruction stream.
         */
        public abstract class OTCilInstr
        {
            public int offset;     // cil offset

            public OTCilInstr(int offset)
            {
                this.offset = offset;
            }

            public abstract string DumpString();
            public abstract void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link);

            protected void CheckEmptyStack(OTDecompile decompile, string opMnemonic)
            {
                if(decompile.opstack.Count > 0)
                {
                    Console.Error.WriteLine("CheckEmptyStack: " + decompile.method.Name + " 0x" + offset.ToString("X") + ": " +
                            opMnemonic + " stack depth " + decompile.opstack.Count);
                }
            }
        }

        /*
         * Label mark point.
         */
        private class OTLabel: OTCilInstr
        {
            public int number;
            public string name;

            public int lbljumps;

            public OTLabel(int number, string name) : base(-1)
            {
                this.number = number;
                this.name = name;
            }

            public string PrintableName
            {
                get
                {
                    if(name.StartsWith(_doBreak))
                        return _doBreak + "$" + number;
                    if(name.StartsWith(_doCont))
                        return _doCont + "$" + number;
                    if(name.StartsWith(_forBreak))
                        return _forBreak + "$" + number;
                    if(name.StartsWith(_forCont))
                        return _forCont + "$" + number;
                    if(name.StartsWith(_whileBreak))
                        return _whileBreak + "$" + number;
                    if(name.StartsWith(_whileCont))
                        return _whileCont + "$" + number;
                    return name;
                }
            }

            public override string DumpString()
            {
                return name + ":";
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                OTStmtLabel.AddLast(decompile, this);
            }
        }

        /*
         * 'try {'
         */
        private class OTCilBegExcBlk: OTCilInstr
        {
            public LinkedList<OTCilBegCatBlk> catches = new LinkedList<OTCilBegCatBlk>();

            public OTCilBegExcBlk(int offset) : base(offset)
            {
            }

            public override string DumpString()
            {
                return "try {";
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                CheckEmptyStack(decompile, "try");

                // link the try itself onto outer block
                OTStmtBegExcBlk trystmt = new OTStmtBegExcBlk();
                decompile.AddLastStmt(trystmt);

                // subsequent statements go to the try block
                trystmt.tryblock = new OTStmtBlock();
                decompile.trystack.Push(trystmt);
                decompile.blockstack.Push(trystmt.tryblock);
            }
        }

        /*
         * '} catch (...) {'
         */
        private class OTCilBegCatBlk: OTCilInstr
        {
            public Type excType;

            public OTCilBegCatBlk(int offset, Type excType) : base(offset)
            {
                this.excType = excType;
            }

            public override string DumpString()
            {
                return "} catch (" + AbbrType(excType) + ") {";
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                CheckEmptyStack(decompile, "catch");

                // link the catch itself onto the try statement
                OTStmtBegExcBlk trystmt = decompile.trystack.Peek();
                OTStmtBegCatBlk catstmt = new OTStmtBegCatBlk(excType);
                trystmt.catches.AddLast(catstmt);

                // start capturing statements into the catch block
                catstmt.tryblock = trystmt;
                catstmt.catchblock = new OTStmtBlock();
                decompile.blockstack.Pop();
                decompile.blockstack.Push(catstmt.catchblock);

                // fill the stack slot with something for the exception argument
                OTOpndDup dup = new OTOpndDup(++decompile.dupNo);
                decompile.opstack.Push(dup);
            }
        }

        /*
         * '} finally {'
         */
        private class OTCilBegFinBlk: OTCilInstr
        {
            public OTCilBegFinBlk(int offset) : base(offset)
            {
            }

            public override string DumpString()
            {
                return "} finally {";
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                CheckEmptyStack(decompile, "finally");

                // link the finally itself to the try statement
                OTStmtBegExcBlk trystmt = decompile.trystack.Peek();
                OTStmtBegFinBlk finstmt = new OTStmtBegFinBlk();
                trystmt.finblock = finstmt;

                // start capturing statements into the finally block
                finstmt.tryblock = trystmt;
                finstmt.finblock = new OTStmtBlock();
                decompile.blockstack.Pop();
                decompile.blockstack.Push(finstmt.finblock);
            }
        }

        /*
         * '}' end of try
         */
        private class OTCilEndExcBlk: OTCilInstr
        {
            public OTCilEndExcBlk(int offset) : base(offset)
            {
            }

            public override string DumpString()
            {
                return "} // end try";
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                CheckEmptyStack(decompile, "endtry");

                // pop the try/catch/finally blocks from stacks
                decompile.blockstack.Pop();
                decompile.trystack.Pop();

                // subsequent statements collect following the try
            }
        }

        /*
         * Actual opcodes (instructions).
         */
        private class OTCilNull: OTCilInstr
        {
            public MyOp opCode;

            public OTCilNull(int offset, OpCode opCode) : base(offset)
            {
                this.opCode = MyOp.GetByName(opCode.Name);
            }

            public override string DumpString()
            {
                return opCode.ToString();
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "conv.i1":
                    case "conv.i2":
                    case "conv.i4":
                    case "conv.i8":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            decompile.opstack.Push(new OTOpndCast(typeof(int), value));
                            break;
                        }
                    case "conv.r4":
                    case "conv.r8":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            decompile.opstack.Push(new OTOpndCast(typeof(double), value));
                            break;
                        }
                    case "dup":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            if(!(value is OTOpndDup))
                            {
                                OTOpndDup dup = new OTOpndDup(++decompile.dupNo);
                                OTStmtStore.AddLast(decompile, dup, value);
                                value = dup;
                            }
                            decompile.opstack.Push(value);
                            decompile.opstack.Push(value);
                            break;
                        }
                    case "endfinally":
                        break;
                    case "ldarg.0":
                        {
                            decompile.opstack.Push(new OTOpndArg(0, false, decompile));
                            break;
                        }
                    case "ldarg.1":
                        {
                            decompile.opstack.Push(new OTOpndArg(1, false, decompile));
                            break;
                        }
                    case "ldarg.2":
                        {
                            decompile.opstack.Push(new OTOpndArg(2, false, decompile));
                            break;
                        }
                    case "ldarg.3":
                        {
                            decompile.opstack.Push(new OTOpndArg(3, false, decompile));
                            break;
                        }
                    case "ldc.i4.0":
                        {
                            decompile.opstack.Push(new OTOpndInt(0));
                            break;
                        }
                    case "ldc.i4.1":
                        {
                            decompile.opstack.Push(new OTOpndInt(1));
                            break;
                        }
                    case "ldc.i4.2":
                        {
                            decompile.opstack.Push(new OTOpndInt(2));
                            break;
                        }
                    case "ldc.i4.3":
                        {
                            decompile.opstack.Push(new OTOpndInt(3));
                            break;
                        }
                    case "ldc.i4.4":
                        {
                            decompile.opstack.Push(new OTOpndInt(4));
                            break;
                        }
                    case "ldc.i4.5":
                        {
                            decompile.opstack.Push(new OTOpndInt(5));
                            break;
                        }
                    case "ldc.i4.6":
                        {
                            decompile.opstack.Push(new OTOpndInt(6));
                            break;
                        }
                    case "ldc.i4.7":
                        {
                            decompile.opstack.Push(new OTOpndInt(7));
                            break;
                        }
                    case "ldc.i4.8":
                        {
                            decompile.opstack.Push(new OTOpndInt(8));
                            break;
                        }
                    case "ldc.i4.m1":
                        {
                            decompile.opstack.Push(new OTOpndInt(-1));
                            break;
                        }
                    case "ldelem.i4":
                    case "ldelem.r4":
                    case "ldelem.r8":
                    case "ldelem.ref":
                        {
                            OTOpnd index = decompile.opstack.Pop();
                            OTOpnd array = decompile.opstack.Pop();
                            decompile.opstack.Push(OTOpndArrayElem.Make(array, index, false, decompile));
                            break;
                        }
                    case "ldnull":
                        {
                            decompile.opstack.Push(new OTOpndNull());
                            break;
                        }
                    case "neg":
                    case "not":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            decompile.opstack.Push(OTOpndUnOp.Make(opCode, value));
                            break;
                        }
                    case "pop":
                        {
                            OTStmtVoid.AddLast(decompile, decompile.opstack.Pop());
                            break;
                        }
                    case "ret":
                        {
                            OTOpnd value = null;
                            if(decompile.method.ReturnType != typeof(void))
                            {
                                value = decompile.opstack.Pop();
                            }
                            CheckEmptyStack(decompile);
                            decompile.AddLastStmt(new OTStmtRet(value));
                            break;
                        }
                    case "stelem.i4":
                    case "stelem.r8":
                    case "stelem.ref":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            OTOpnd index = decompile.opstack.Pop();
                            OTOpnd array = decompile.opstack.Pop();
                            OTStmtStore.AddLast(decompile, OTOpndArrayElem.Make(array, index, false, decompile), value);
                            break;
                        }
                    case "throw":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            CheckEmptyStack(decompile);
                            decompile.AddLastStmt(new OTStmtThrow(value, decompile));
                            break;
                        }
                    case "add":
                    case "and":
                    case "ceq":
                    case "cgt":
                    case "cgt.un":
                    case "clt":
                    case "clt.un":
                    case "div":
                    case "div.un":
                    case "mul":
                    case "or":
                    case "rem":
                    case "rem.un":
                    case "shl":
                    case "shr":
                    case "shr.un":
                    case "sub":
                    case "xor":
                        {
                            OTOpnd rite = decompile.opstack.Pop();
                            OTOpnd left = decompile.opstack.Pop();
                            decompile.opstack.Push(OTOpndBinOp.Make(left, opCode, rite));
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }

            protected void CheckEmptyStack(OTDecompile decompile)
            {
                CheckEmptyStack(decompile, opCode.ToString());
            }
        }

        private class OTCilField: OTCilNull
        {
            public FieldInfo field;

            public OTCilField(int offset, OpCode opCode, FieldInfo field) : base(offset, opCode)
            {
                this.field = field;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + field.Name;
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "ldfld":
                        {
                            OTOpnd obj = decompile.opstack.Pop();
                            decompile.opstack.Push(OTOpndField.Make(obj, field));
                            break;
                        }
                    case "ldsfld":
                        {
                            decompile.opstack.Push(new OTOpndSField(field));
                            break;
                        }
                    case "stfld":
                        {
                            OTOpnd val = decompile.opstack.Pop();
                            OTOpnd obj = decompile.opstack.Pop();
                            OTStmtStore.AddLast(decompile, OTOpndField.Make(obj, field), val);
                            break;
                        }
                    case "stsfld":
                        {
                            OTOpnd val = decompile.opstack.Pop();
                            OTStmtStore.AddLast(decompile, new OTOpndSField(field), val);
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilLocal: OTCilNull
        {
            public OTLocal local;

            public OTCilLocal(int offset, OpCode opCode, OTLocal local) : base(offset, opCode)
            {
                this.local = local;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + local.name;
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "ldloc":
                        {
                            decompile.opstack.Push(new OTOpndLocal(local));
                            break;
                        }
                    case "ldloca":
                        {
                            decompile.opstack.Push(new OTOpndLocalRef(local));
                            break;
                        }
                    case "stloc":
                        {
                            OTOpnd val = decompile.opstack.Pop();
                            OTStmtStore.AddLast(decompile, new OTOpndLocal(local), val);
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilType: OTCilNull
        {
            public Type type;

            public OTCilType(int offset, OpCode opCode, Type type) : base(offset, opCode)
            {
                this.type = type;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + AbbrType(type);
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "box":
                        {
                            break;
                        }
                    case "castclass":
                    case "unbox.any":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            decompile.opstack.Push(new OTOpndCast(type, value));
                            break;
                        }
                    case "ldelem":
                        {
                            OTOpnd index = decompile.opstack.Pop();
                            OTOpnd array = decompile.opstack.Pop();
                            decompile.opstack.Push(OTOpndArrayElem.Make(array, index, false, decompile));
                            break;
                        }
                    case "ldelema":
                        {
                            OTOpnd index = decompile.opstack.Pop();
                            OTOpnd array = decompile.opstack.Pop();
                            decompile.opstack.Push(OTOpndArrayElem.Make(array, index, true, decompile));
                            break;
                        }
                    case "newarr":
                        {
                            OTOpnd index = decompile.opstack.Pop();
                            decompile.opstack.Push(new OTOpndNewarr(type, index));
                            break;
                        }
                    case "stelem":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            OTOpnd index = decompile.opstack.Pop();
                            OTOpnd array = decompile.opstack.Pop();
                            OTStmtStore.AddLast(decompile, OTOpndArrayElem.Make(array, index, false, decompile), value);
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilLabel: OTCilNull
        {
            public OTLabel label;

            public OTCilLabel(int offset, OpCode opCode, OTLabel label) : base(offset, opCode)
            {
                this.label = label;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + label.name;
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                     // We don't handle non-empty stack at branch points.
                     //
                     // So handle this case specially:
                     //
                     //    dup
                     //    ldc.i4.0
                     //    bge.s  llAbstemp  << we are here
                     //    neg
                     //  llAbstemp:
                     //
                     // becomes:
                     //
                     //    call llAbs
                    case "bge.s":
                        {
                            OTOpnd rite = decompile.opstack.Pop();  // alleged zero
                            OTOpnd left = decompile.opstack.Pop();  // alleged dup

                            if((label.name == _llAbstemp) && (decompile.opstack.Count > 0))
                            {
                                LinkedListNode<OTCilInstr> linkneg = link.Next;
                                if((left is OTOpndDup) && (rite is OTOpndInt) &&
                                        (linkneg != null) && (linkneg.Value is OTCilNull) &&
                                        (((OTCilNull)linkneg.Value).opCode == MyOp.Neg))
                                {
                                    OTOpndInt riteint = (OTOpndInt)rite;
                                    LinkedListNode<OTCilInstr> linklbl = linkneg.Next;
                                    if((riteint.value == 0) && (linklbl != null) && (linklbl.Value is OTLabel) &&
                                            (((OTLabel)linklbl.Value) == label))
                                    {
                                        linkneg.List.Remove(linkneg);
                                        linklbl.List.Remove(linklbl);
                                        MethodInfo method = typeof(ScriptBaseClass).GetMethod("llAbs");
                                        OTOpnd[] args = new OTOpnd[] { new OTOpndNull(), decompile.opstack.Pop() };
                                        OTOpndCall.AddLast(decompile, method, args);
                                        break;
                                    }
                                }
                            }

                            CheckEmptyStack(decompile);
                            OTOpnd valu = OTOpndBinOp.Make(left, opCode, rite);
                            OTStmt jump = OTStmtJump.Make(label);
                            decompile.AddLastStmt(new OTStmtCond(valu, jump));
                            break;
                        }

                    case "beq":
                    case "bge":
                    case "bgt":
                    case "ble":
                    case "blt":
                    case "bne.un":
                    case "beq.s":
                    case "bgt.s":
                    case "ble.s":
                    case "blt.s":
                    case "bne.un.s":
                        {
                            OTOpnd rite = decompile.opstack.Pop();
                            OTOpnd left = decompile.opstack.Pop();
                            CheckEmptyStack(decompile);
                            OTOpnd valu = OTOpndBinOp.Make(left, opCode, rite);
                            OTStmt jump = OTStmtJump.Make(label);
                            decompile.AddLastStmt(new OTStmtCond(valu, jump));
                            break;
                        }
                    case "brfalse":
                    case "brfalse.s":
                    case "brtrue":
                    case "brtrue.s":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            CheckEmptyStack(decompile);
                            OTOpnd valu = OTOpndUnOp.Make(opCode, value);
                            OTStmt jump = OTStmtJump.Make(label);
                            decompile.AddLastStmt(new OTStmtCond(valu, jump));
                            break;
                        }
                    case "br":
                    case "br.s":
                    case "leave":
                        {
                            CheckEmptyStack(decompile);
                            OTStmt jump = OTStmtJump.Make(label);
                            decompile.AddLastStmt(jump);
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilLabels: OTCilNull
        {
            public OTLabel[] labels;

            public OTCilLabels(int offset, OpCode opCode, OTLabel[] labels) : base(offset, opCode)
            {
                this.labels = labels;
            }

            public override string DumpString()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(opCode.ToString());
                foreach(OTLabel label in labels)
                {
                    sb.Append(' ');
                    sb.Append(label.name);
                }
                return sb.ToString();
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "switch":
                        {
                            OTOpnd value = decompile.opstack.Pop();
                            CheckEmptyStack(decompile);
                            decompile.AddLastStmt(new OTStmtSwitch(value, labels));
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilMethod: OTCilNull
        {
            public MethodInfo method;

            public OTCilMethod(int offset, OpCode opCode, MethodInfo method) : base(offset, opCode)
            {
                this.method = method;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + method.Name;
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "call":
                    case "callvirt":
                        {
                            int nargs = method.GetParameters().Length;
                            if(!method.IsStatic)
                                nargs++;
                            OTOpnd[] args = new OTOpnd[nargs];
                            for(int i = nargs; --i >= 0;)
                            {
                                args[i] = decompile.opstack.Pop();
                            }
                            OTOpndCall.AddLast(decompile, method, args);
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilCtor: OTCilNull
        {
            public ConstructorInfo ctor;

            public OTCilCtor(int offset, OpCode opCode, ConstructorInfo ctor) : base(offset, opCode)
            {
                this.ctor = ctor;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + AbbrType(ctor.DeclaringType);
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "newobj":
                        {
                            int nargs = ctor.GetParameters().Length;
                            OTOpnd[] args = new OTOpnd[nargs];
                            for(int i = nargs; --i >= 0;)
                            {
                                args[i] = decompile.opstack.Pop();
                            }
                            decompile.opstack.Push(OTOpndNewobj.Make(ctor, args));
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilDouble: OTCilNull
        {
            public double value;

            public OTCilDouble(int offset, OpCode opCode, double value) : base(offset, opCode)
            {
                this.value = value;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + value;
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "ldc.r8":
                        {
                            decompile.opstack.Push(new OTOpndDouble(value));
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilFloat: OTCilNull
        {
            public float value;

            public OTCilFloat(int offset, OpCode opCode, float value) : base(offset, opCode)
            {
                this.value = value;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + value;
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "ldc.r4":
                        {
                            decompile.opstack.Push(new OTOpndFloat(value));
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilInteger: OTCilNull
        {
            public int value;

            public OTCilInteger(int offset, OpCode opCode, int value) : base(offset, opCode)
            {
                this.value = value;
            }

            public override string DumpString()
            {
                return opCode.ToString() + ' ' + value;
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "ldarg":
                    case "ldarg.s":
                        {
                            decompile.opstack.Push(new OTOpndArg(value, false, decompile));
                            break;
                        }
                    case "ldarga":
                    case "ldarga.s":
                        {
                            decompile.opstack.Push(new OTOpndArg(value, true, decompile));
                            break;
                        }
                    case "ldc.i4":
                    case "ldc.i4.s":
                        {
                            decompile.opstack.Push(new OTOpndInt(value));
                            break;
                        }
                    case "starg":
                        {
                            OTOpnd val = decompile.opstack.Pop();
                            OTStmtStore.AddLast(decompile, new OTOpndArg(value, false, decompile), val);
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        private class OTCilString: OTCilNull
        {
            public string value;

            public OTCilString(int offset, OpCode opCode, string value) : base(offset, opCode)
            {
                this.value = value;
            }

            public override string DumpString()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(opCode.ToString());
                sb.Append(' ');
                TokenDeclInline.PrintParamString(sb, value);
                return sb.ToString();
            }

            public override void BuildStatements(OTDecompile decompile, LinkedListNode<OTCilInstr> link)
            {
                switch(opCode.ToString())
                {
                    case "ldstr":
                        {
                            decompile.opstack.Push(new OTOpndString(value));
                            break;
                        }
                    default:
                        throw new Exception("unknown opcode " + opCode.ToString());
                }
            }
        }

        /***************************************\
         *  Tokens what are on operand stack.  *
        \***************************************/

        public abstract class OTOpnd
        {

            /**
             * See if it possibly has any side effects.
             */
            public abstract bool HasSideEffects
            {
                get;
            }

            /**
             * Increment reference counts.
             */
            public virtual void CountRefs(bool writing)
            {
            }

            /**
             * If this operand is a 'by reference' operand,
             * return the corresponding 'by value' operand.
             */
            public virtual OTOpnd GetNonByRefOpnd()
            {
                return this;
            }

            /**
             * If this operand is same as oldopnd, replace it with newopnd.
             *
             * This default just does a shallow search which is ok if this operand does not have any sub-operands.
             * But it must be overridden for a deep search if this operand has any sub-operands.
             */
            public virtual OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                return this;
            }

            /**
             * See if the two operands are the same value.
             * Note that calls might have side-effects so are never the same.
             */
            public abstract bool SameAs(OTOpnd other);

            /**
             * Get a printable string representation of the operand.
             */
            public abstract string PrintableString
            {
                get;
            }
        }

        /**
         * Argument variable.
         */
        private class OTOpndArg: OTOpnd
        {
            public int index;
            public bool byref;

            private OTDecompile decompile;

            public OTOpndArg(int index, bool byref, OTDecompile decompile)
            {
                this.index = index;
                this.byref = byref;
                this.decompile = decompile;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }

            public override OTOpnd GetNonByRefOpnd()
            {
                if(!byref)
                    return this;
                return new OTOpndArg(index, false, decompile);
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndArg))
                    return false;
                return (((OTOpndArg)other).byref == byref) && (((OTOpndArg)other).index == index);
            }

            public override string PrintableString
            {
                get
                {
                    string argname = decompile.MethArgName(index);
                    return byref ? ("ref " + argname) : argname;
                }
            }
        }

        /**
         * Element of an array.
         */
        private class OTOpndArrayElem: OTOpnd
        {
            public bool byref;
            public OTOpnd array;
            public OTOpnd index;

            public static OTOpnd Make(OTOpnd array, OTOpnd index, bool byref, OTDecompile decompile)
            {
                 // arg$0.glblVars.iar<type>[<intconst>] is a reference to a global variable
                 // likewise so is __xmrinst.glblVars.iar<type>[<intconst>]
                if((array is OTOpndField) && (index is OTOpndInt))
                {
                     // arrayfield = (arg$0.glblVars).iar<type>
                     // arrayfieldobj = arg$0.glblVars
                     // iartypename = iar<type>
                    OTOpndField arrayfield = (OTOpndField)array;
                    OTOpnd arrayfieldobj = arrayfield.obj;
                    string iartypename = arrayfield.field.Name;

                     // See if they are what they are supposed to be.
                    if((arrayfieldobj is OTOpndField) && iartypename.StartsWith("iar"))
                    {
                         // arrayfieldobjfield = arg$0.glblVars
                        OTOpndField arrayfieldobjfield = (OTOpndField)arrayfieldobj;

                         // See if the parts are what they are supposed to be.
                        if(IsArg0OrXMRInst(arrayfieldobjfield.obj) && (arrayfieldobjfield.field.Name == "glblVars"))
                        {
                             // Everything matches up, make a global variable instead of an array reference.
                            return new OTOpndGlobal(iartypename, ((OTOpndInt)index).value, byref, decompile.scriptObjCode);
                        }
                    }
                }

                 // Other array reference.
                OTOpndArrayElem it = new OTOpndArrayElem();
                it.array = array;
                it.index = index;
                it.byref = byref;
                return it;
            }

            private OTOpndArrayElem()
            {
            }

            public override bool HasSideEffects
            {
                get
                {
                    return array.HasSideEffects || index.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                array.CountRefs(false);
                index.CountRefs(false);
            }

            public override OTOpnd GetNonByRefOpnd()
            {
                if(!byref)
                    return this;
                OTOpndArrayElem it = new OTOpndArrayElem();
                it.array = array;
                it.index = index;
                return it;
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                array = array.ReplaceOperand(oldopnd, newopnd, ref rc);
                index = index.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndArrayElem))
                    return false;
                OTOpndArrayElem otherae = (OTOpndArrayElem)other;
                return array.SameAs(otherae.array) && index.SameAs(otherae.index);
            }

            public override string PrintableString
            {
                get
                {
                    return (byref ? "ref " : "") + array.PrintableString + "[" + index.PrintableString + "]";
                }
            }

            /**
             * See if the argument is a reference to arg$0 or __xmrinst
             */
            public static bool IsArg0OrXMRInst(OTOpnd obj)
            {
                if(obj is OTOpndArg)
                {
                    OTOpndArg objarg = (OTOpndArg)obj;
                    return objarg.index == 0;
                }
                if(obj is OTOpndLocal)
                {
                    OTOpndLocal objlcl = (OTOpndLocal)obj;
                    return objlcl.local.name.StartsWith(_xmrinstlocal);
                }
                return false;
            }
        }

        /**
         * Binary operator.
         */
        private class OTOpndBinOp: OTOpnd
        {
            public OTOpnd left;
            public MyOp opCode;
            public OTOpnd rite;

            private static Dictionary<string, string> xor1ops = InitXor1Ops();

            private static Dictionary<string, string> InitXor1Ops()
            {
                Dictionary<string, string> d = new Dictionary<string, string>();
                d["ceq"] = "cne";
                d["cge"] = "clt";
                d["cgt"] = "cle";
                d["cle"] = "cgt";
                d["clt"] = "cge";
                d["cne"] = "ceq";
                return d;
            }

            public static OTOpnd Make(OTOpnd left, MyOp opCode, OTOpnd rite)
            {
                // ((x clt y) xor 1)  =>  (x cge y)  etc
                string xor1op;
                if((left is OTOpndBinOp) && xor1ops.TryGetValue(((OTOpndBinOp)left).opCode.name, out xor1op) &&
                    (opCode == MyOp.Xor) &&
                    (rite is OTOpndInt) && (((OTOpndInt)rite).value == 1))
                {
                    opCode = MyOp.GetByName(xor1op);
                }

                // handle strcmp() cases (see OTOpndStrCmp)
                if(left is OTOpndStrCmp)
                {
                    OTOpnd strcmp = ((OTOpndStrCmp)left).MakeBinOp(opCode, rite);
                    if(strcmp != null)
                        return strcmp;
                }

                // nothing special, make as is
                OTOpndBinOp it = new OTOpndBinOp();
                it.left = left;
                it.opCode = opCode;
                it.rite = rite;
                return it;
            }

            private OTOpndBinOp()
            {
            }

            public override bool HasSideEffects
            {
                get
                {
                    return left.HasSideEffects || rite.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                left.CountRefs(false);
                rite.CountRefs(false);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                left = left.ReplaceOperand(oldopnd, newopnd, ref rc);
                rite = rite.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndBinOp))
                    return false;
                OTOpndBinOp otherbo = (OTOpndBinOp)other;
                return left.SameAs(otherbo.left) && (opCode.ToString() == otherbo.opCode.ToString()) && rite.SameAs(otherbo.rite);
            }

            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();

                    bool leftneedsparen = ItNeedsParentheses(left, true);
                    if(leftneedsparen)
                        sb.Append('(');
                    sb.Append(left.PrintableString);
                    if(leftneedsparen)
                        sb.Append(')');

                    sb.Append(' ');
                    sb.Append(opCode.source);
                    sb.Append(' ');

                    bool riteneedsparen = ItNeedsParentheses(rite, false);
                    if(riteneedsparen)
                        sb.Append('(');
                    sb.Append(rite.PrintableString);
                    if(riteneedsparen)
                        sb.Append(')');

                    return sb.ToString();
                }
            }

            /**
             * See if source code representation requires parentheses around the given operand.
             * @param it = the other operand to decide about
             * @param itleft = true: 'it' is on the left of this operand   (A $ B) # C
             *                false: 'it' is on the right of this operand  A $ (B # C)
             */
            private bool ItNeedsParentheses(OTOpnd it, bool itleft)
            {
                if(!(it is OTOpndBinOp))
                    return false;
                string itop = ((OTOpndBinOp)it).opCode.source;
                string myop = opCode.source;

                // find them in table.  higher number is for *, lower is for +.
                int itpi, mypi;
                if(!precedence.TryGetValue(itop, out itpi))
                    return true;
                if(!precedence.TryGetValue(myop, out mypi))
                    return true;
                int itpiabs = Math.Abs(itpi);
                int mypiabs = Math.Abs(mypi);

                // if its precedence is lower (eg +) than my precedence (eg *), it needs parentheses
                if(itpiabs < mypiabs)
                    return true;

                // if its precedence is higher (eg *) than my precedence (eg +), it doesn't needs parentheses
                if(itpiabs > mypiabs)
                    return false;

                // if (A $ B) # C, we can safely go without the parentheses
                if(itleft)
                    return false;

                //  my   it
                // A $ (B # C) only works without parentheses for commutative $
                // A - (B + C) and A - (B - C) require parentheses
                // A + (B - C) does not
                return mypi < 0;  // neg: things like -, /, etc require parentheses
                                  // pos: things like +, *, etc do not need parens
            }

            // see MMRScriptReduce.PrecedenceInit()
            private static Dictionary<string, int> precedence = InitPrecedence();
            private static Dictionary<string, int> InitPrecedence()
            {
                Dictionary<string, int> d = new Dictionary<string, int>();
                d["|"] = 140;
                d["^"] = 160;
                d["&"] = 180;
                d["<<"] = -260;
                d[">>"] = -260;
                d["+"] = 280;
                d["-"] = -280;
                d["*"] = 320;
                d["/"] = -320;
                d["%"] = -320;
                return d;
            }
        }

        /**
         * Call with or without return value.
         */
        private class OTOpndCall: OTOpnd
        {
            private static Dictionary<string, MethodInfo> mathmeths = InitMathMeths();
            private static Dictionary<string, MethodInfo> InitMathMeths()
            {
                Dictionary<string, MethodInfo> d = new Dictionary<string, MethodInfo>();
                d["Acos"] = typeof(ScriptBaseClass).GetMethod("llAcos");
                d["Asin"] = typeof(ScriptBaseClass).GetMethod("llAsin");
                d["Atan"] = typeof(ScriptBaseClass).GetMethod("llAtan");
                d["Cos"] = typeof(ScriptBaseClass).GetMethod("llCos");
                d["Abs"] = typeof(ScriptBaseClass).GetMethod("llFabs");
                d["Log"] = typeof(ScriptBaseClass).GetMethod("llLog");
                d["Log10"] = typeof(ScriptBaseClass).GetMethod("llLog10");
                d["Round"] = typeof(ScriptBaseClass).GetMethod("llRound");
                d["Sin"] = typeof(ScriptBaseClass).GetMethod("llSin");
                d["Sqrt"] = typeof(ScriptBaseClass).GetMethod("llSqrt");
                d["Tan"] = typeof(ScriptBaseClass).GetMethod("llTan");
                return d;
            }

            public MethodInfo method;
            public OTOpnd[] args;

            // pushes on stack for return-value functions
            // pushes to end of instruction stream for return-void functions
            public static void AddLast(OTDecompile decompile, MethodInfo method, OTOpnd[] args)
            {
                int nargs = args.Length;

                // heap tracker push is just the single arg value as far as we're concerned
                if((nargs == 1) && (method.Name == _heapTrackerPush) && method.DeclaringType.Name.StartsWith("HeapTracker"))
                {
                    decompile.opstack.Push(args[0]);
                    return;
                }

                // heap tracker pop is just a store as far as we're concerned
                if((nargs == 2) && (method.Name == _heapTrackerPop) && method.DeclaringType.Name.StartsWith("HeapTracker"))
                {
                    OTStmtStore.AddLast(decompile, args[0], args[1]);
                    return;
                }

                // string.Compare() is its own thing cuz it has to decompile many ways
                if((nargs == 2) && (method.DeclaringType == typeof(string)) && (method.Name == "Compare"))
                {
                    decompile.opstack.Push(new OTOpndStrCmp(args[0], args[1]));
                    return;
                }

                // ObjectToString, etc, should appear as casts
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.EndsWith("ToBool"))
                {
                    MethodInfo meth = typeof(XMRInstAbstract).GetMethod("xmr" + method.Name);
                    AddLast(decompile, meth, new OTOpnd[] { new OTOpndNull(), args[0] });
                    return;
                }
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.EndsWith("ToFloat"))
                {
                    decompile.opstack.Push(new OTOpndCast(typeof(double), args[0]));
                    return;
                }
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.EndsWith("ToInteger"))
                {
                    decompile.opstack.Push(new OTOpndCast(typeof(int), args[0]));
                    return;
                }
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.EndsWith("ToList"))
                {
                    decompile.opstack.Push(new OTOpndCast(typeof(LSL_List), args[0]));
                    return;
                }
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.EndsWith("ToRotation"))
                {
                    decompile.opstack.Push(new OTOpndCast(typeof(LSL_Rotation), args[0]));
                    return;
                }
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.EndsWith("ToString"))
                {
                    decompile.opstack.Push(new OTOpndCast(typeof(string), args[0]));
                    return;
                }
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.EndsWith("ToVector"))
                {
                    decompile.opstack.Push(new OTOpndCast(typeof(LSL_Vector), args[0]));
                    return;
                }

                if((method.DeclaringType == typeof(XMRInstAbstract)) && (method.Name == "xmrHeapLeft"))
                {
                    AddLast(decompile, typeof(ScriptBaseClass).GetMethod("llGetFreeMemory"), new OTOpnd[] { new OTOpndNull() });
                    return;
                }

                // pop to entry in the list/object/string array
                if(PopToGlobalArray(decompile, method, args))
                    return;

                // strip off event handler argument unwrapper calls
                if((nargs == 1) && (method.DeclaringType == typeof(TypeCast)) && method.Name.StartsWith("EHArgUnwrap"))
                {
                    decompile.opstack.Push(args[0]);
                    return;
                }

                // translate Math method to ll method
                MethodInfo mathmeth;
                if((method.DeclaringType == typeof(Math)) && mathmeths.TryGetValue(method.Name, out mathmeth))
                {
                    AddLast(decompile, mathmeth, new OTOpnd[] { new OTOpndNull(), args[0] });
                    return;
                }
                if((method.DeclaringType == typeof(Math)) && (method.Name == "Atan2"))
                {
                    AddLast(decompile, typeof(ScriptBaseClass).GetMethod("llAtan2"), new OTOpnd[] { new OTOpndNull(), args[0], args[1] });
                    return;
                }
                if((method.DeclaringType == typeof(Math)) && (method.Name == "Pow"))
                {
                    AddLast(decompile, typeof(ScriptBaseClass).GetMethod("llPow"), new OTOpnd[] { new OTOpndNull(), args[0], args[1] });
                    return;
                }

                // string concat should be a bunch of adds
                if((method.Name == "Concat") && (method.DeclaringType == typeof(string)))
                {
                    int k = args.Length;
                    while(k > 1)
                    {
                        int j = 0;
                        int i;
                        for(i = 0; i + 2 <= k; i += 2)
                        {
                            args[j++] = OTOpndBinOp.Make(args[i + 0], MyOp.Add, args[i + 1]);
                        }
                        while(i < k)
                            args[j++] = args[i++];
                        k = j;
                    }
                    if(k > 0)
                        decompile.opstack.Push(args[0]);
                    return;
                }

                // bunch of calls for rotation and vector arithmetic
                if((method.DeclaringType == typeof(BinOpStr)) && BinOpStrCall(decompile, method, args))
                    return;
                if((method.DeclaringType == typeof(ScriptCodeGen)) && (method.Name == "LSLRotationNegate"))
                {
                    decompile.opstack.Push(OTOpndUnOp.Make(MyOp.Neg, args[0]));
                    return;
                }
                if((method.DeclaringType == typeof(ScriptCodeGen)) && (method.Name == "LSLVectorNegate"))
                {
                    decompile.opstack.Push(OTOpndUnOp.Make(MyOp.Neg, args[0]));
                    return;
                }

                // otherwise process it as a call
                OTOpndCall call = new OTOpndCall();
                call.method = method;
                call.args = args;
                if(method.ReturnType == typeof(void))
                {
                    OTStmtVoid.AddLast(decompile, call);
                }
                else
                {
                    decompile.opstack.Push(call);
                }
            }

            public override bool HasSideEffects
            {
                get
                {
                    return true;
                }
            }

            /**
             * Handle a call to XMRInstArrays.Pop<List,Object,String>
             * by converting it to a store directly into the array.
             */
            private static bool PopToGlobalArray(OTDecompile decompile, MethodInfo method, OTOpnd[] args)
            {
                if(method.DeclaringType != typeof(XMRInstArrays))
                    return false;
                if(args.Length != 3)
                    return false;

                string array = null;
                if(method.Name == "PopList")
                    array = "iarLists";
                if(method.Name == "PopObject")
                    array = "iarObjects";
                if(method.Name == "PopString")
                    array = "iarStrings";
                if(array == null)
                    return false;

                // make token that points to the iar<whatever> array
                FieldInfo field = typeof(XMRInstArrays).GetField(array);
                OTOpnd arrayfield = OTOpndField.Make(args[0], field);

                // make token that points to the element to be popped to
                OTOpnd element = OTOpndArrayElem.Make(arrayfield, args[1], false, decompile);

                // make a statement to store value in that element
                OTStmtStore.AddLast(decompile, element, args[2]);

                return true;
            }

            /**
             * BinOpStr has a bunch of calls to do funky arithmetic.
             * Instead of generating a call, put back the original source.
             */
            private static bool BinOpStrCall(OTDecompile decompile, MethodInfo method, OTOpnd[] args)
            {
                switch(method.Name)
                {
                    case "MethFloatAddList":
                    case "MethIntAddList":
                    case "MethKeyAddList":
                    case "MethListAddFloat":
                    case "MethListAddInt":
                    case "MethListAddKey":
                    case "MethListAddList":
                    case "MethListAddObj":
                    case "MethListAddRot":
                    case "MethListAddStr":
                    case "MethListAddVec":
                    case "MethObjAddList":
                    case "MethRotAddList":
                    case "MethRotAddRot":
                    case "MethStrAddList":
                    case "MethVecAddList":
                    case "MethVecAddVec":
                        {
                            decompile.opstack.Push(OTOpndBinOp.Make(args[0], MyOp.Add, args[1]));
                            return true;
                        }

                    case "MethListEqList":
                    case "MethRotEqRot":
                    case "MethVecEqVec":
                        {
                            decompile.opstack.Push(OTOpndBinOp.Make(args[0], MyOp.Ceq, args[1]));
                            return true;
                        }

                    case "MethListNeList":
                    case "MethRotNeRot":
                    case "MethVecNeVec":
                        {
                            decompile.opstack.Push(OTOpndBinOp.Make(args[0], MyOp.Cne, args[1]));
                            return true;
                        }

                    case "MethRotSubRot":
                    case "MethVecSubVec":
                        {
                            decompile.opstack.Push(OTOpndBinOp.Make(args[0], MyOp.Sub, args[1]));
                            return true;
                        }

                    case "MethFloatMulVec":
                    case "MethIntMulVec":
                    case "MethRotMulRot":
                    case "MethVecMulFloat":
                    case "MethVecMulInt":
                    case "MethVecMulRot":
                    case "MethVecMulVec":
                        {
                            decompile.opstack.Push(OTOpndBinOp.Make(args[0], MyOp.Mul, args[1]));
                            return true;
                        }

                    case "MethRotDivRot":
                    case "MethVecDivFloat":
                    case "MethVecDivInt":
                    case "MethVecDivRot":
                        {
                            decompile.opstack.Push(OTOpndBinOp.Make(args[0], MyOp.Div, args[1]));
                            return true;
                        }

                    default:
                        return false;
                }
            }

            private OTOpndCall()
            {
            }

            public override void CountRefs(bool writing)
            {
                foreach(OTOpnd arg in args)
                {
                    arg.CountRefs(false);
                }
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                for(int i = 0; i < args.Length; i++)
                {
                    args[i] = args[i].ReplaceOperand(oldopnd, newopnd, ref rc);
                }
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                return false;
            }

            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();

                    // GetByKey(a,i) => a[i]
                    if((method.DeclaringType == typeof(XMR_Array)) && (method.Name == "GetByKey") && (args.Length == 2))
                    {
                        sb.Append(args[0].PrintableString);
                        sb.Append('[');
                        sb.Append(args[1].PrintableString);
                        sb.Append(']');
                        return sb.ToString();
                    }

                    // SetByKey(a,i,v) => a[i] = v
                    if((method.DeclaringType == typeof(XMR_Array)) && (method.Name == "SetByKey") && (args.Length == 3))
                    {
                        sb.Append(args[0].PrintableString);
                        sb.Append('[');
                        sb.Append(args[1].PrintableString);
                        sb.Append("] = ");
                        sb.Append(args[2].PrintableString);
                        return sb.ToString();
                    }

                    // CompValuListEl.GetElementFromList accesses list elements like an array.
                    if((method.DeclaringType == typeof(CompValuListEl)) && (method.Name == "GetElementFromList"))
                    {
                        sb.Append(args[0].PrintableString);
                        sb.Append('[');
                        sb.Append(args[1].PrintableString);
                        sb.Append(']');
                        return sb.ToString();
                    }

                    // methods that are part of ScriptBaseClass are LSL functions such as llSay()
                    // so we want to skip outputting "arg$0," as it is the hidden "this" argument.
                    // and there are also XMRInstAbstract functions such as xmrEventDequeue().
                    int starti = 0;
                    if((method.DeclaringType == typeof(ScriptBaseClass)) && !method.IsStatic)
                        starti = 1;
                    if((method.DeclaringType == typeof(XMRInstAbstract)) && !method.IsStatic)
                        starti = 1;

                    // likewise, method that have null as the declaring type are script-defined
                    // dynamic methods which have a hidden "this" argument passed as "arg$0".
                    if(method.DeclaringType == null)
                        starti = 1;

                    // all others we want to show the type name (such as Math.Abs, String.Compare, etc)
                    if(starti == 0)
                    {
                        sb.Append(AbbrType(method.DeclaringType));
                        sb.Append('.');
                    }

                    // script-defined functions have the param types as part of their name
                    // so strip them off here so they don't clutter things up
                    int i = method.Name.IndexOf('(');
                    if(i < 0)
                        sb.Append(method.Name);
                    else
                        sb.Append(method.Name.Substring(0, i));

                    // now add the call arguments
                    sb.Append(" (");
                    bool first = true;
                    foreach(OTOpnd arg in args)
                    {
                        if(--starti < 0)
                        {
                            if(!first)
                                sb.Append(", ");
                            sb.Append(arg.PrintableString);
                            first = false;
                        }
                    }
                    sb.Append(')');
                    return sb.ToString();
                }
            }
        }

        /**
         * Cast value to the given type.
         */
        private class OTOpndCast: OTOpnd
        {
            public Type type;
            public OTOpnd value;

            public OTOpndCast(Type type, OTOpnd value)
            {
                this.type = type;
                this.value = value;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return value.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                value.CountRefs(false);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                value = value.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndCast))
                    return false;
                OTOpndCast othercast = (OTOpndCast)other;
                return (type == othercast.type) && value.SameAs(othercast.value);
            }

            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append('(');
                    sb.Append(AbbrType(type));
                    sb.Append(") ");
                    if(value is OTOpndBinOp)
                        sb.Append('(');
                    sb.Append(value.PrintableString);
                    if(value is OTOpndBinOp)
                        sb.Append(')');
                    return sb.ToString();
                }
            }
        }

        /**
         * Duplicate stack value without re-performing computation.
         * Semantics just like local var except it doesn't have a declaration.
         */
        private class OTOpndDup: OTOpnd
        {
            public int index;
            public int ndupreads;

            public OTOpndDup(int index)
            {
                this.index = index;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }

            public override void CountRefs(bool writing)
            {
                if(!writing)
                    ndupreads++;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndDup))
                    return false;
                return ((OTOpndDup)other).index == index;
            }

            public override string PrintableString
            {
                get
                {
                    return "dup$" + index;
                }
            }
        }

        /**
         * Field of an object.
         */
        private class OTOpndField: OTOpnd
        {
            public OTOpnd obj;
            public FieldInfo field;

            public static OTOpnd Make(OTOpnd obj, FieldInfo field)
            {
                //  LSL_Float.value => the object itself
                if((field.DeclaringType == typeof(LSL_Float)) && (field.Name == "value"))
                {
                    return obj;
                }

                //  LSL_Integer.value => the object itself
                if((field.DeclaringType == typeof(LSL_Integer)) && (field.Name == "value"))
                {
                    return obj;
                }

                // LSL_String.m_string => the object itself
                if((field.DeclaringType == typeof(LSL_String)) && (field.Name == "m_string"))
                {
                    return obj;
                }

                // some other field, output code to access it
                // sometimes the object comes as by reference (value types), so we might need to deref it first
                OTOpndField it = new OTOpndField();
                it.obj = obj.GetNonByRefOpnd();
                it.field = field;
                return it;
            }

            private OTOpndField()
            {
            }

            public override bool HasSideEffects
            {
                get
                {
                    return obj.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                // the field may be getting written to, but the object is being read
                obj.CountRefs(false);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                obj = obj.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndField))
                    return false;
                OTOpndField otherfield = (OTOpndField)other;
                return (field.Name == otherfield.field.Name) && obj.SameAs(otherfield.obj);
            }

            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();
                    if(obj is OTOpndBinOp)
                        sb.Append('(');
                    sb.Append(obj.PrintableString);
                    if(obj is OTOpndBinOp)
                        sb.Append(')');
                    sb.Append('.');
                    sb.Append(field.Name);
                    return sb.ToString();
                }
            }
        }

        /**
         * Script-level global variable.
         */
        private class OTOpndGlobal: OTOpnd
        {
            public string iartypename;
            public int iararrayidx;
            public bool byref;
            public ScriptObjCode scriptObjCode;

            public OTOpndGlobal(string iartypename, int iararrayidx, bool byref, ScriptObjCode scriptObjCode)
            {
                this.iartypename = iartypename;
                this.iararrayidx = iararrayidx;
                this.byref = byref;
                this.scriptObjCode = scriptObjCode;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }

            public override OTOpnd GetNonByRefOpnd()
            {
                if(!byref)
                    return this;
                return new OTOpndGlobal(iartypename, iararrayidx, false, scriptObjCode);
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndGlobal))
                    return false;
                OTOpndGlobal otherglobal = (OTOpndGlobal)other;
                return (iartypename == otherglobal.iartypename) && (iararrayidx == otherglobal.iararrayidx);
            }

            public override string PrintableString
            {
                get
                {
                    return (byref ? "ref " : "") + scriptObjCode.globalVarNames[iartypename][iararrayidx];
                }
            }
        }

        /**
         * List initialization.
         */
        private class OTOpndListIni: OTOpnd
        {
            public OTOpnd[] values;

            /**
             * Try to detect list initialization building idiom:
             *    dup$<n> = newarr object[<m>]      << link points here
             *    dup$<n>[0] = bla
             *    dup$<n>[1] = bla
             *        ...
             *    ... newobj list (dup$<n>) ...
             */
            public static bool Detect(LinkedListNode<OTStmt> link)
            {
                if(link == null)
                    return false;

                /*
                 * Check for 'dup$<n> = newarr object[<m>]' and get listsize from <m>.
                 */
                OTStmtStore store = (OTStmtStore)link.Value;
                if(!(store.varwr is OTOpndDup))
                    return false;
                if(!(store.value is OTOpndNewarr))
                    return false;
                OTOpndDup storevar = (OTOpndDup)store.varwr;
                OTOpndNewarr storeval = (OTOpndNewarr)store.value;
                if(storeval.type != typeof(object))
                    return false;
                if(!(storeval.index is OTOpndInt))
                    return false;
                int listsize = ((OTOpndInt)storeval.index).value;

                 // Good chance of having list initializer, malloc an object to hold it.
                OTOpndListIni it = new OTOpndListIni();
                it.values = new OTOpnd[listsize];

                 // There should be exactly listsize statements following that of the form:
                 //    dup$<n>[<i>] = bla
                 // If so, save the bla values in the values[] array.
                LinkedListNode<OTStmt> vallink = link;
                for(int i = 0; i < listsize; i++)
                {
                    vallink = vallink.Next;
                    if(vallink == null)
                        return false;
                    if(!(vallink.Value is OTStmtStore))
                        return false;
                    OTStmtStore valstore = (OTStmtStore)vallink.Value;
                    if(!(valstore.varwr is OTOpndArrayElem))
                        return false;
                    OTOpndArrayElem varelem = (OTOpndArrayElem)valstore.varwr;
                    if(varelem.array != storevar)
                        return false;
                    if(!(varelem.index is OTOpndInt))
                        return false;
                    if(((OTOpndInt)varelem.index).value != i)
                        return false;
                    it.values[i] = valstore.value;
                }

                 // The next statement should have a 'newobj list (dup$<n>)' in it somewhere
                 // that we want to replace with 'it'.
                ConstructorInfo protoctor = typeof(LSL_List).GetConstructor(new Type[] { typeof(object[]) });
                OTOpnd[] protoargs = new OTOpnd[] { storevar };
                OTOpnd proto = OTOpndNewobj.Make(protoctor, protoargs);

                vallink = vallink.Next;
                bool rc = vallink.Value.ReplaceOperand(proto, it);

                 // If successful, delete 'dup$n =' and all 'dup$n[i] =' statements.
                if(rc)
                {
                    do
                    {
                        LinkedListNode<OTStmt> nextlink = link.Next;
                        link.List.Remove(link);
                        link = nextlink;
                    } while(link != vallink);
                }

                return rc;
            }

            public override bool HasSideEffects
            {
                get
                {
                    foreach(OTOpnd value in values)
                    {
                        if(value.HasSideEffects)
                            return true;
                    }
                    return false;
                }
            }

            public override void CountRefs(bool writing)
            {
                foreach(OTOpnd value in values)
                {
                    value.CountRefs(false);
                }
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                for(int i = 0; i < values.Length; i++)
                {
                    values[i] = values[i].ReplaceOperand(oldopnd, newopnd, ref rc);
                }
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndListIni))
                    return false;
                OTOpndListIni otherli = (OTOpndListIni)other;
                if(otherli.values.Length != values.Length)
                    return false;
                for(int i = 0; i < values.Length; i++)
                {
                    if(!values[i].SameAs(otherli.values[i]))
                        return false;
                }
                return true;
            }

            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append('[');
                    for(int i = 0; i < values.Length; i++)
                    {
                        if(i > 0)
                            sb.Append(',');
                        sb.Append(' ');
                        sb.Append(values[i].PrintableString);
                    }
                    sb.Append(" ]");
                    return sb.ToString();
                }
            }
        }

        /**
         * Local variable.
         */
        private class OTOpndLocal: OTOpnd
        {
            public OTLocal local;

            public OTOpndLocal(OTLocal local)
            {
                this.local = local;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }

            public override void CountRefs(bool writing)
            {
                if(writing)
                    local.nlclwrites++;
                else
                    local.nlclreads++;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndLocal))
                    return false;
                OTOpndLocal otherlocal = (OTOpndLocal)other;
                return local == otherlocal.local;
            }

            public override string PrintableString
            {
                get
                {
                    return local.name;
                }
            }
        }
        private class OTOpndLocalRef: OTOpnd
        {
            public OTLocal local;

            public OTOpndLocalRef(OTLocal local)
            {
                this.local = local;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return true;
                }
            }

            public override void CountRefs(bool writing)
            {
                local.nlclreads++;
                local.nlclwrites++;
            }

            public override OTOpnd GetNonByRefOpnd()
            {
                return new OTOpndLocal(local);
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndLocal))
                    return false;
                OTOpndLocal otherlocal = (OTOpndLocal)other;
                return local == otherlocal.local;
            }

            public override string PrintableString
            {
                get
                {
                    return "ref " + local.name;
                }
            }
        }

        /**
         * New C#-level array.
         */
        private class OTOpndNewarr: OTOpnd
        {
            public Type type;
            public OTOpnd index;

            public OTOpndNewarr(Type type, OTOpnd index)
            {
                this.type = type;
                this.index = index;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return index.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                index.CountRefs(false);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                index = index.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                return false;
            }

            public override string PrintableString
            {
                get
                {
                    return "newarr " + type.Name + "[" + index.PrintableString + "]";
                }
            }
        }

        /**
         * New C#-level object.
         */
        private class OTOpndNewobj: OTOpnd
        {
            public ConstructorInfo ctor;
            public OTOpnd[] args;

            public static OTOpnd Make(ConstructorInfo ctor, OTOpnd[] args)
            {
                // newobj LSL_Float (x)  =>  x
                if((ctor.DeclaringType == typeof(LSL_Float)) && (args.Length == 1))
                {
                    Type ptype = ctor.GetParameters()[0].ParameterType;
                    if(ptype == typeof(string))
                    {
                        return new OTOpndCast(typeof(double), args[0]);
                    }
                    return args[0];
                }

                // newobj LSL_Integer (x)  =>  x
                if((ctor.DeclaringType == typeof(LSL_Integer)) && (args.Length == 1))
                {
                    Type ptype = ctor.GetParameters()[0].ParameterType;
                    if(ptype == typeof(string))
                    {
                        return new OTOpndCast(typeof(int), args[0]);
                    }
                    return args[0];
                }

                // newobj LSL_String (x)  =>  x
                if((ctor.DeclaringType == typeof(LSL_String)) && (args.Length == 1))
                {
                    return args[0];
                }

                // newobj LSL_Rotation (x, y, z, w)  =>  <x, y, z, w>
                if((ctor.DeclaringType == typeof(LSL_Rotation)) && (args.Length == 4))
                {
                    return new OTOpndRot(args[0], args[1], args[2], args[3]);
                }

                // newobj LSL_Vector (x, y, z)  =>  <x, y, z>
                if((ctor.DeclaringType == typeof(LSL_Vector)) && (args.Length == 3))
                {
                    return new OTOpndVec(args[0], args[1], args[2]);
                }

                // newobj LSL_Rotation (string)  => (rotation) string
                if((ctor.DeclaringType == typeof(LSL_Rotation)) && (args.Length == 1))
                {
                    return new OTOpndCast(typeof(LSL_Rotation), args[0]);
                }

                // newobj LSL_Vector (string)  => (rotation) string
                if((ctor.DeclaringType == typeof(LSL_Vector)) && (args.Length == 1))
                {
                    return new OTOpndCast(typeof(LSL_Vector), args[0]);
                }

                // newobj LSL_List (newarr object[0])  =>  [ ]
                if((ctor.DeclaringType == typeof(LSL_List)) && (args.Length == 1) && (args[0] is OTOpndNewarr))
                {
                    OTOpndNewarr arg0 = (OTOpndNewarr)args[0];
                    if((arg0.type == typeof(object)) && (arg0.index is OTOpndInt) && (((OTOpndInt)arg0.index).value == 0))
                    {
                        OTOpndListIni listini = new OTOpndListIni();
                        listini.values = new OTOpnd[0];
                        return listini;
                    }
                }

                // something else, output as is
                OTOpndNewobj it = new OTOpndNewobj();
                it.ctor = ctor;
                it.args = args;
                return it;
            }

            private OTOpndNewobj()
            {
            }

            public override bool HasSideEffects
            {
                get
                {
                    foreach(OTOpnd arg in args)
                    {
                        if(arg.HasSideEffects)
                            return true;
                    }
                    return false;
                }
            }

            public override void CountRefs(bool writing)
            {
                foreach(OTOpnd arg in args)
                {
                    arg.CountRefs(false);
                }
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                for(int i = 0; i < args.Length; i++)
                {
                    args[i] = args[i].ReplaceOperand(oldopnd, newopnd, ref rc);
                }
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndNewobj))
                    return false;
                OTOpndNewobj otherno = (OTOpndNewobj)other;
                if(otherno.ctor.DeclaringType != ctor.DeclaringType)
                    return false;
                if(otherno.args.Length != args.Length)
                    return false;
                for(int i = 0; i < args.Length; i++)
                {
                    if(!args[i].SameAs(otherno.args[i]))
                        return false;
                }
                return true;
            }

            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append("newobj ");
                    sb.Append(ctor.DeclaringType.Name);
                    sb.Append(" (");
                    bool first = true;
                    foreach(OTOpnd arg in args)
                    {
                        if(!first)
                            sb.Append(", ");
                        sb.Append(arg.PrintableString);
                        first = false;
                    }
                    sb.Append(')');
                    return sb.ToString();
                }
            }
        }

        /**
         * Rotation value.
         */
        private class OTOpndRot: OTOpnd
        {
            private OTOpnd x, y, z, w;

            public OTOpndRot(OTOpnd x, OTOpnd y, OTOpnd z, OTOpnd w)
            {
                this.x = StripFloatCast(x);
                this.y = StripFloatCast(y);
                this.z = StripFloatCast(z);
                this.w = StripFloatCast(w);
            }

            public override bool HasSideEffects
            {
                get
                {
                    return x.HasSideEffects || y.HasSideEffects || z.HasSideEffects || w.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                x.CountRefs(false);
                y.CountRefs(false);
                z.CountRefs(false);
                w.CountRefs(false);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                x = x.ReplaceOperand(oldopnd, newopnd, ref rc);
                y = y.ReplaceOperand(oldopnd, newopnd, ref rc);
                z = z.ReplaceOperand(oldopnd, newopnd, ref rc);
                w = w.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndRot))
                    return false;
                OTOpndRot otherv = (OTOpndRot)other;
                return otherv.x.SameAs(x) && otherv.y.SameAs(y) && otherv.z.SameAs(z) && otherv.w.SameAs(w);
            }

            public override string PrintableString
            {
                get
                {
                    return "<" + x.PrintableString + ", " + y.PrintableString + ", " + z.PrintableString + ", " + w.PrintableString + ">";
                }
            }
        }

        /**
         * Static field.
         */
        private class OTOpndSField: OTOpnd
        {
            private FieldInfo field;

            public OTOpndSField(FieldInfo field)
            {
                this.field = field;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndSField))
                    return false;
                OTOpndSField othersfield = (OTOpndSField)other;
                return (field.Name == othersfield.field.Name) && (field.DeclaringType == othersfield.field.DeclaringType);
            }

            public override string PrintableString
            {
                get
                {
                    if(field.DeclaringType == typeof(ScriptBaseClass))
                        return field.Name;
                    return field.DeclaringType.Name + "." + field.Name;
                }
            }
        }

        /**
         * Call to string.Compare().
         * See use cases in BinOpStr:
         *   strcmp (a, b) ceq  0
         *   (strcmp (a, b) ceq 0) xor 1  =>  we translate to:  strcmp (a, b) cne 0
         *   strcmp (a, b) clt  0
         *   strcmp (a, b) clt  1  // <=
         *   strcmp (a, b) cgt  0
         *   strcmp (a, b) cgt -1  // >=
         * ...but then optimized by ScriptCollector if followed by br{false,true}:
         *   ceq + xor 1 + brtrue  => bne.un
         *   ceq + xor 1 + brfalse => beq
         *   ceq + brtrue  => beq
         *   ceq + brfalse => bne.un
         *   cgt + brtrue  => bgt
         *   cgt + brfalse => ble
         *   clt + brtrue  => blt
         *   clt + brfalse => bge
         * So we end up with these cases:
         *   strcmp (a, b) ceq  0
         *   strcmp (a, b) cne  0
         *   strcmp (a, b) clt  0
         *   strcmp (a, b) clt  1
         *   strcmp (a, b) cgt  0
         *   strcmp (a, b) cgt -1
         *   strcmp (a, b) beq    0
         *   strcmp (a, b) bne.un 0
         *   strcmp (a, b) bgt  0
         *   strcmp (a, b) ble  0
         *   strcmp (a, b) bgt -1
         *   strcmp (a, b) ble -1
         *   strcmp (a, b) blt  0
         *   strcmp (a, b) bge  0
         *   strcmp (a, b) blt  1
         *   strcmp (a, b) bge  1
         * ... so we pretty them up in OTOpndBinOp
         */
        private class OTOpndStrCmp: OTOpnd
        {
            private static Dictionary<string, string> binops = InitBinops();
            private static Dictionary<string, string> InitBinops()
            {
                Dictionary<string, string> d = new Dictionary<string, string>();
                d["ceq 0"] = "ceq";
                d["cne 0"] = "cne";
                d["clt 0"] = "clt";
                d["clt 1"] = "cle";
                d["cgt 0"] = "cgt";
                d["cgt -1"] = "cge";
                d["beq 0"] = "ceq";
                d["bne.un 0"] = "cne";
                d["bgt 0"] = "cgt";
                d["ble 0"] = "cle";
                d["bgt -1"] = "cge";
                d["ble -1"] = "clt";
                d["blt 0"] = "clt";
                d["bge 0"] = "cge";
                d["blt 1"] = "cle";
                d["bge 1"] = "cgt";
                return d;
            }

            private OTOpnd arg0;
            private OTOpnd arg1;

            public OTOpndStrCmp(OTOpnd arg0, OTOpnd arg1)
            {
                this.arg0 = arg0;
                this.arg1 = arg1;
            }

            /**
             * Try to make something a script writer would recognize.
             * If we can't, then we leave it as a call to xmrStringCompare().
             *    this   = some strcmp(a,b)
             *    opCode = hopefully some cxx or bxx from above table
             *    rite   = hopefully some constant from above table
             */
            public OTOpnd MakeBinOp(MyOp opCode, OTOpnd rite)
            {
                if(!(rite is OTOpndInt))
                    return null;
                int riteint = ((OTOpndInt)rite).value;
                string key = opCode.name + ' ' + riteint;
                string cxxopname;
                if(!binops.TryGetValue(key, out cxxopname))
                    return null;
                return OTOpndBinOp.Make(arg0, MyOp.GetByName(cxxopname), arg1);
            }
            public OTOpnd MakeUnOp(MyOp opCode)
            {
                if(opCode == MyOp.Brfalse)
                    return OTOpndBinOp.Make(arg0, MyOp.Ceq, arg1);
                if(opCode == MyOp.Brtrue)
                    return OTOpndBinOp.Make(arg0, MyOp.Cne, arg1);
                return null;
            }

            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }

            public override void CountRefs(bool writing)
            {
                arg0.CountRefs(writing);
                arg1.CountRefs(writing);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                arg0 = arg0.ReplaceOperand(oldopnd, newopnd, ref rc);
                arg1 = arg1.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndStrCmp))
                    return false;
                return arg0.SameAs(((OTOpndStrCmp)other).arg0) && arg1.SameAs(((OTOpndStrCmp)other).arg1);
            }

            public override string PrintableString
            {
                get
                {
                    return "xmrStringCompare (" + arg0.PrintableString + ", " + arg1.PrintableString + ")";
                }
            }
        }

        /**
         * Unary operator.
         */
        private class OTOpndUnOp: OTOpnd
        {
            public MyOp opCode;
            public OTOpnd value;

            private static Dictionary<string, string> brfops = InitBrfOps();
            private static Dictionary<string, string> InitBrfOps()
            {
                Dictionary<string, string> d = new Dictionary<string, string>();
                d["beq"] = "cne";
                d["bge"] = "clt";
                d["bgt"] = "cle";
                d["ble"] = "cgt";
                d["blt"] = "cge";
                d["bne.un"] = "ceq";
                d["ceq"] = "cne";
                d["cge"] = "clt";
                d["cgt"] = "cle";
                d["cle"] = "cgt";
                d["clt"] = "cge";
                d["cne"] = "ceq";
                return d;
            }

            public static OTOpnd Make(MyOp opCode, OTOpnd value)
            {
                // (brfalse (brfalse (x)))  =>  (brtrue (x))
                if((opCode == MyOp.Brfalse) && (value is OTOpndUnOp) && (((OTOpndUnOp)value).opCode == MyOp.Brfalse))
                {
                    ((OTOpndUnOp)value).opCode = MyOp.Brtrue;
                    return value;
                }

                // (brfalse (brtrue (x)))  =>  (brfalse (x))
                if((opCode == MyOp.Brfalse) && (value is OTOpndUnOp) && (((OTOpndUnOp)value).opCode == MyOp.Brtrue))
                {
                    ((OTOpndUnOp)value).opCode = MyOp.Brfalse;
                    return value;
                }

                // (brtrue (brfalse (x)))  =>  (brfalse (x))
                if((opCode == MyOp.Brtrue) && (value is OTOpndUnOp) && (((OTOpndUnOp)value).opCode == MyOp.Brfalse))
                {
                    return value;
                }

                // (brtrue (brtrue (x)))  =>  (brtrue (x))
                if((opCode == MyOp.Brtrue) && (value is OTOpndUnOp) && (((OTOpndUnOp)value).opCode == MyOp.Brtrue))
                {
                    return value;
                }

                // (brfalse (x beq y))  =>  (x bne y)  etc
                string brfop;
                if((opCode == MyOp.Brfalse) && (value is OTOpndBinOp) && brfops.TryGetValue(((OTOpndBinOp)value).opCode.name, out brfop))
                {
                    ((OTOpndBinOp)value).opCode = MyOp.GetByName(brfop);
                    return value;
                }

                // (brtrue  (x beq y))  =>  (x beq y)  etc
                if((opCode == MyOp.Brtrue) && (value is OTOpndBinOp) && brfops.ContainsKey(((OTOpndBinOp)value).opCode.name))
                {
                    return value;
                }

                // strcmp() can be a special case
                if(value is OTOpndStrCmp)
                {
                    OTOpnd strcmp = ((OTOpndStrCmp)value).MakeUnOp(opCode);
                    if(strcmp != null)
                        return strcmp;
                }

                // nothing special, save opcode and value
                OTOpndUnOp it = new OTOpndUnOp();
                it.opCode = opCode;
                it.value = value;
                return it;
            }

            private OTOpndUnOp()
            {
            }

            public override bool HasSideEffects
            {
                get
                {
                    return value.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                value.CountRefs(false);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                value = value.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndUnOp))
                    return false;
                OTOpndUnOp otherop = (OTOpndUnOp)other;
                return (opCode.ToString() == otherop.opCode.ToString()) && value.SameAs(otherop.value);
            }

            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append(opCode.source);
                    sb.Append(' ');
                    if(value is OTOpndBinOp)
                        sb.Append('(');
                    sb.Append(value.PrintableString);
                    if(value is OTOpndBinOp)
                        sb.Append(')');
                    return sb.ToString();
                }
            }
        }

        /**
         * Vector value.
         */
        private class OTOpndVec: OTOpnd
        {
            private OTOpnd x, y, z;

            public OTOpndVec(OTOpnd x, OTOpnd y, OTOpnd z)
            {
                this.x = StripFloatCast(x);
                this.y = StripFloatCast(y);
                this.z = StripFloatCast(z);
            }

            public override bool HasSideEffects
            {
                get
                {
                    return x.HasSideEffects || y.HasSideEffects || z.HasSideEffects;
                }
            }

            public override void CountRefs(bool writing)
            {
                x.CountRefs(false);
                y.CountRefs(false);
                z.CountRefs(false);
            }

            public override OTOpnd ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd, ref bool rc)
            {
                if(SameAs(oldopnd))
                {
                    rc = true;
                    return newopnd;
                }
                x = x.ReplaceOperand(oldopnd, newopnd, ref rc);
                y = y.ReplaceOperand(oldopnd, newopnd, ref rc);
                z = z.ReplaceOperand(oldopnd, newopnd, ref rc);
                return this;
            }

            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndVec))
                    return false;
                OTOpndVec otherv = (OTOpndVec)other;
                return otherv.x.SameAs(x) && otherv.y.SameAs(y) && otherv.z.SameAs(z);
            }

            public override string PrintableString
            {
                get
                {
                    return "<" + x.PrintableString + ", " + y.PrintableString + ", " + z.PrintableString + ">";
                }
            }
        }

        /**
         * Constants.
         */
        private class OTOpndDouble: OTOpnd
        {
            public double value;
            public OTOpndDouble(double value)
            {
                this.value = value;
            }
            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }
            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndDouble))
                    return false;
                return ((OTOpndDouble)other).value == value;
            }
            public override string PrintableString
            {
                get
                {
                    string s = value.ToString();
                    long i;
                    if(long.TryParse(s, out i))
                    {
                        s += ".0";
                    }
                    return s;
                }
            }
        }
        private class OTOpndFloat: OTOpnd
        {
            public float value;
            public OTOpndFloat(float value)
            {
                this.value = value;
            }
            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }
            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndFloat))
                    return false;
                return ((OTOpndFloat)other).value == value;
            }
            public override string PrintableString
            {
                get
                {
                    string s = value.ToString();
                    long i;
                    if(long.TryParse(s, out i))
                    {
                        s += ".0";
                    }
                    return s;
                }
            }
        }
        private class OTOpndInt: OTOpnd
        {
            public int value;
            public OTOpndInt(int value)
            {
                this.value = value;
            }
            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }
            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndInt))
                    return false;
                return ((OTOpndInt)other).value == value;
            }
            public override string PrintableString
            {
                get
                {
                    return value.ToString();
                }
            }
        }
        private class OTOpndNull: OTOpnd
        {
            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }
            public override bool SameAs(OTOpnd other)
            {
                return other is OTOpndNull;
            }
            public override string PrintableString
            {
                get
                {
                    return "undef";
                }
            }
        }
        private class OTOpndString: OTOpnd
        {
            public string value;
            public OTOpndString(string value)
            {
                this.value = value;
            }
            public override bool HasSideEffects
            {
                get
                {
                    return false;
                }
            }
            public override bool SameAs(OTOpnd other)
            {
                if(!(other is OTOpndString))
                    return false;
                return ((OTOpndString)other).value == value;
            }
            public override string PrintableString
            {
                get
                {
                    StringBuilder sb = new StringBuilder();
                    TokenDeclInline.PrintParamString(sb, value);
                    return sb.ToString();
                }
            }
        }

        /****************************************\
         *  Tokens what are in statement list.  *
        \****************************************/

        public abstract class OTStmt
        {

            /**
             * Increment reference counts.
             */
            public abstract void CountRefs();

            /**
             * Strip out any of the behind-the-scenes code such as stack capture/restore.
             * By default, there is no change.
             */
            public virtual bool StripStuff(LinkedListNode<OTStmt> link)
            {
                return false;
            }

            /**
             * Replace the oldopnd operand with the newopnd operand if it is present.
             * Return whether or not it was found and replaced.
             */
            public abstract bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd);

            /**
             * Detect and modify for do/for/if/while structures.
             */
            public virtual bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                return false;
            }

            /**
             * If this statement is the old statement, replace it with the given new statement.
             * Also search any sub-ordinate statements.
             * **NOTE**: minimally implemented to replace a Jump with a Break or Continue
             */
            public abstract OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt);

            /**
             * Print the statement out on the given printer with the given indenting.
             * The first line is already indented, subsequent lines must be indented as given.
             * This method should leave the printer at the end of the line.
             */
            public abstract void PrintStmt(TextWriter twout, string indent);

            /**
             * Strip all statements following this statement
             * because this statement jumps somewhere.
             */
            protected bool StripStuffForTerminal(LinkedListNode<OTStmt> link)
            {
                // strip all statements following jump until seeing some label
                bool rc = false;
                if(link != null)
                {
                    LinkedListNode<OTStmt> nextlink;
                    while((nextlink = link.Next) != null)
                    {
                        if(nextlink.Value is OTStmtLabel)
                            break;
                        nextlink.List.Remove(nextlink);
                        rc = true;
                    }
                }
                return rc;
            }
        }

        /**************************\
         *  Primitive statements  *
        \**************************/

        /**
         * Begin catch block (catch).
         */
        private class OTStmtBegCatBlk: OTStmt
        {
            public OTStmtBegExcBlk tryblock;
            public OTStmtBlock catchblock;

            private Type excType;

            public OTStmtBegCatBlk(Type excType)
            {
                this.excType = excType;
            }

            public override void CountRefs()
            {
                catchblock.CountRefs();
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                return catchblock.StripStuff(null);
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                return catchblock.ReplaceOperand(oldopnd, newopnd);
            }

            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                return catchblock.DetectDoForIfWhile(link);
            }

            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                catchblock = (OTStmtBlock)catchblock.ReplaceStatement(oldstmt, newstmt);
                return this;
            }

            /**
             * Print out the catch block including its enclosed statements.
             */
            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("catch (" + excType.Name + ") ");
                catchblock.PrintStmt(twout, indent);
            }
        }

        /**
         * Begin exception block (try).
         */
        private class OTStmtBegExcBlk: OTStmt
        {

            // statements within the try { } not including any catch or finally
            public OTStmtBlock tryblock;

            // list of all catch { } blocks associated with this try { }
            public LinkedList<OTStmtBegCatBlk> catches = new LinkedList<OTStmtBegCatBlk>();

            // possible single finally { } associated with this try
            public OTStmtBegFinBlk finblock;  // might be null

            public override void CountRefs()
            {
                tryblock.CountRefs();
                foreach(OTStmtBegCatBlk catblock in catches)
                {
                    catblock.CountRefs();
                }
                if(finblock != null)
                    finblock.CountRefs();
            }

            /**
             * Strip behind-the-scenes info from all the sub-blocks.
             */
            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                // strip behind-the-scenes info from all the sub-blocks.
                bool rc = tryblock.StripStuff(null);
                foreach(OTStmtBegCatBlk catblk in catches)
                {
                    rc |= catblk.StripStuff(null);
                }
                if(finblock != null)
                    rc |= finblock.StripStuff(null);
                if(rc)
                    return true;

                // change:
                //    try {
                //       ...
                //    }
                // to:
                //    {
                //       ...
                //    }
                // note that an empty catch () { } has meaning so can't be stripped
                // empty finally { } blocks strips itself from the try
                if((catches.Count == 0) && (finblock == null) && (link != null))
                {
                    link.List.AddAfter(link, tryblock);
                    tryblock = null;
                    link.List.Remove(link);
                    return true;
                }

                return false;
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = tryblock.ReplaceOperand(oldopnd, newopnd);
                foreach(OTStmtBegCatBlk catblk in catches)
                {
                    rc |= catblk.ReplaceOperand(oldopnd, newopnd);
                }
                if(finblock != null)
                    rc |= finblock.ReplaceOperand(oldopnd, newopnd);
                return rc;
            }

            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                bool rc = tryblock.DetectDoForIfWhile(link);
                foreach(OTStmtBegCatBlk catblk in catches)
                {
                    rc |= catblk.DetectDoForIfWhile(link);
                }
                if(finblock != null)
                    rc |= finblock.DetectDoForIfWhile(link);
                return rc;
            }

            /**
             * Assume we will never try to replace the try block itself.
             * But go through all our sub-ordinates statements.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                tryblock = (OTStmtBlock)tryblock.ReplaceStatement(oldstmt, newstmt);
                for(LinkedListNode<OTStmtBegCatBlk> catlink = catches.First; catlink != null; catlink = catlink.Next)
                {
                    catlink.Value = (OTStmtBegCatBlk)catlink.Value.ReplaceStatement(oldstmt, newstmt);
                }
                if(finblock != null)
                    finblock = (OTStmtBegFinBlk)finblock.ReplaceStatement(oldstmt, newstmt);
                return this;
            }

            /**
             * Print out the try block including its enclosed statements.
             * And since the try is the only thing pushed to the outer block,
             * we also print out all the catch and finally blocks.
             */
            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("try ");
                tryblock.PrintStmt(twout, indent);
                foreach(OTStmtBegCatBlk catblk in catches)
                {
                    twout.Write(' ');
                    catblk.PrintStmt(twout, indent);
                }
                if(finblock != null)
                {
                    twout.Write(' ');
                    finblock.PrintStmt(twout, indent);
                }
            }
        }

        /**
         * Begin finally block (finally).
         */
        private class OTStmtBegFinBlk: OTStmt
        {
            public OTStmtBegExcBlk tryblock;
            public OTStmtBlock finblock;

            public override void CountRefs()
            {
                finblock.CountRefs();
            }

            /**
             * Strip behind-the-scene parts from the finally block.
             */
            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                // strip behind-the-scenes parts from finally block itself
                if(finblock.StripStuff(null))
                    return true;

                // if finblock is empty, delete the finally from the try
                if(finblock.blkstmts.Count == 0)
                {
                    tryblock.finblock = null;
                    return true;
                }

                return false;
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                return finblock.ReplaceOperand(oldopnd, newopnd);
            }

            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                return finblock.DetectDoForIfWhile(link);
            }

            /**
             * Assume we will never try to replace the finally block itself.
             * But go through all our sub-ordinates statements.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                finblock = (OTStmtBlock)finblock.ReplaceStatement(oldstmt, newstmt);
                return this;
            }

            /**
             * Print out the finally block including its enclosed statements.
             */
            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("finally ");
                finblock.PrintStmt(twout, indent);
            }
        }

        /**
         * Simple if jump/break/continue statement.
         */
        private class OTStmtCond: OTStmt
        {
            public OTOpnd valu;
            public OTStmt stmt;  // jump, break, continue only

            public OTStmtCond(OTOpnd valu, OTStmt stmt)
            {
                this.valu = valu;
                this.stmt = stmt;
            }

            public override void CountRefs()
            {
                valu.CountRefs(false);
                stmt.CountRefs();
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                // we assume that callMode is always CallMode_NORMAL, ie, not doing a stack capture or restore
                // so the 'if (arg$0.callMode bne.un 0) ...' is deleted
                // and the 'if (arg$0.callMode bne.un 1) ...' becomes unconditional
                // it can also be __xmrinst.callMode instead of arg$0
                if(valu is OTOpndBinOp)
                {
                    OTOpndBinOp binop = (OTOpndBinOp)valu;
                    if((binop.left is OTOpndField) && (binop.opCode.ToString() == "bne.un") && (binop.rite is OTOpndInt))
                    {
                        OTOpndField leftfield = (OTOpndField)binop.left;
                        if(leftfield.field.Name == _callMode)
                        {
                            bool ok = false;
                            if(leftfield.obj is OTOpndArg)
                            {
                                ok = ((OTOpndArg)leftfield.obj).index == 0;
                            }
                            if(leftfield.obj is OTOpndLocal)
                            {
                                ok = ((OTOpndLocal)leftfield.obj).local.name.StartsWith(_xmrinstlocal);
                            }
                            if(ok)
                            {
                                OTOpndInt riteint = (OTOpndInt)binop.rite;

                                // delete 'if ((arg$0).callMode bne.un 0) ...'
                                if(riteint.value == XMRInstAbstract.CallMode_NORMAL)
                                {
                                    link.List.Remove(link);
                                    return true;
                                }

                                // make 'if ((arg$0).callMode bne.un 1) ...' unconditional
                                if(riteint.value == XMRInstAbstract.CallMode_SAVE)
                                {
                                    link.Value = stmt;
                                    return true;
                                }
                            }
                        }
                    }
                }

                // similarly we assume that doGblInit is always 0 to eliminate the code at beginning of default state_entry()
                // so the 'if (brfalse __xmrinst.doGblInit) ...' is made unconditional
                if(valu is OTOpndUnOp)
                {
                    OTOpndUnOp unop = (OTOpndUnOp)valu;
                    if((unop.opCode == MyOp.Brfalse) && (unop.value is OTOpndField))
                    {
                        OTOpndField valuefield = (OTOpndField)unop.value;
                        if(valuefield.field.Name == _doGblInit)
                        {
                            bool ok = false;
                            if(valuefield.obj is OTOpndLocal)
                            {
                                ok = ((OTOpndLocal)valuefield.obj).local.name.StartsWith(_xmrinstlocal);
                            }
                            if(ok)
                            {

                                // make 'if (brfalse __xmrinst.doGblInit) ...' unconditional
                                link.Value = stmt;
                                return true;
                            }
                        }
                    }
                }

                return false;
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = stmt.ReplaceOperand(oldopnd, newopnd);
                valu = valu.ReplaceOperand(oldopnd, newopnd, ref rc);
                return rc;
            }

            /**
             * Maybe this simple if statement is part of a script-level if/then/else statement.
             */
            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                return OTStmtIf.Detect(link);
            }

            /**
             * Assume we won't replace the if statement itself.
             * But search all our sub-ordinate statements.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                stmt = stmt.ReplaceStatement(oldstmt, newstmt);
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("if (" + StripBrtrue(valu).PrintableString + ") ");
                stmt.PrintStmt(twout, indent);
            }

            /**
             * Scan forward for a given label definition.
             * Put intervening statements in a statement block.
             * @param link = start scanning after this statement
             * @param label = look for this label definition
             * @param block = where to return intervening statement block
             * @returns null: label definition not found
             *          else: label definition statement
             */
            private static LinkedListNode<OTStmt> ScanForLabel(LinkedListNode<OTStmt> link,
                        OTLabel label, out OTStmtBlock block)
            {
                block = new OTStmtBlock();
                while((link = link.Next) != null)
                {
                    if(link.Value is OTStmtLabel)
                    {
                        if(((OTStmtLabel)link.Value).label == label)
                            break;
                    }
                    block.blkstmts.AddLast(link.Value);
                }
                return link;
            }

            /**
             * Strip statements after link up to and including donelink.
             */
            private static void StripInterveningStatements(LinkedListNode<OTStmt> link, LinkedListNode<OTStmt> donelink)
            {
                LinkedListNode<OTStmt> striplink;
                do
                {
                    striplink = link.Next;
                    striplink.List.Remove(striplink);
                } while(striplink != donelink);
            }
        }

        /**
         * Jump to a label.
         */
        private class OTStmtJump: OTStmt
        {
            public OTLabel label;

            public static OTStmt Make(OTLabel label)
            {
                // jumps to __retlbl are return statements
                // note that is is safe to say it is a valueless return because
                // valued returns are done with this construct:
                //    __retval = ....;
                //    jump __retlbl;
                // and those __retval = statements have been changed to return statements already
                if(label.name.StartsWith(_retlbl))
                    return new OTStmtRet(null);

                // other jumps are really jumps
                OTStmtJump it = new OTStmtJump();
                it.label = label;
                return it;
            }

            private OTStmtJump()
            {
            }

            public override void CountRefs()
            {
                label.lbljumps++;
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                if(link == null)
                    return false;

                // strip statements following unconditional jump until next label
                bool rc = StripStuffForTerminal(link);

                // if we (now) have:
                //      jump label;
                //   @label;
                // ... delete this jump
                if(link.Next != null)
                {
                    OTStmtLabel nextlabel = (OTStmtLabel)link.Next.Value;
                    if(nextlabel.label == label)
                    {
                        link.List.Remove(link);
                        rc = true;
                    }
                }

                return rc;
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                return false;
            }

            /**
             * This is actually what ReplaceStatement() is currently used for.
             * It replaces a jump with a break or a continue.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                if((oldstmt is OTStmtJump) && (((OTStmtJump)oldstmt).label == label))
                    return newstmt;
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("jump " + label.PrintableName + ';');
            }
        }

        /**
         * Label definition point.
         */
        private class OTStmtLabel: OTStmt
        {
            public OTLabel label;

            private OTDecompile decompile;

            public static void AddLast(OTDecompile decompile, OTLabel label)
            {
                OTStmtLabel it = new OTStmtLabel();
                it.label = label;
                it.decompile = decompile;
                decompile.AddLastStmt(it);
            }

            private OTStmtLabel()
            {
            }

            public override void CountRefs()
            {
                // don't increment label.lbljumps
                // cuz we don't want the positioning
                // to count as a reference, only jumps
                // to the label should count
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                // if label has nothing jumping to it, remove the label
                if(link != null)
                {
                    label.lbljumps = 0;
                    decompile.topBlock.CountRefs();
                    if(label.lbljumps == 0)
                    {
                        link.List.Remove(link);
                        return true;
                    }
                }

                return false;
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                return false;
            }

            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                if(OTStmtDo.Detect(link))
                    return true;
                if(OTStmtFor.Detect(link, true))
                    return true;
                if(OTStmtFor.Detect(link, false))
                    return true;
                return false;
            }

            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("@" + label.PrintableName + ';');
            }
        }

        /**
         * Return with or without value.
         */
        private class OTStmtRet: OTStmt
        {
            public OTOpnd value;  // might be null

            public OTStmtRet(OTOpnd value)
            {
                this.value = value;
            }

            public override void CountRefs()
            {
                if(value != null)
                    value.CountRefs(false);
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                return StripStuffForTerminal(link);
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = false;
                if(value != null)
                    value = value.ReplaceOperand(oldopnd, newopnd, ref rc);
                return rc;
            }

            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                if(value == null)
                {
                    twout.Write("return;");
                }
                else
                {
                    twout.Write("return " + value.PrintableString + ';');
                }
            }
        }

        /**
         * Store value in variable.
         */
        private class OTStmtStore: OTStmt
        {
            public OTOpnd varwr;
            public OTOpnd value;

            private OTDecompile decompile;

            public static void AddLast(OTDecompile decompile, OTOpnd varwr, OTOpnd value)
            {
                OTStmtStore it = new OTStmtStore(varwr, value, decompile);
                decompile.AddLastStmt(it);
            }

            public OTStmtStore(OTOpnd varwr, OTOpnd value, OTDecompile decompile)
            {
                this.varwr = varwr;
                this.value = value;
                this.decompile = decompile;
            }

            public override void CountRefs()
            {
                varwr.CountRefs(true);
                value.CountRefs(false);
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                // strip out stores to __mainCallNo
                if(varwr is OTOpndLocal)
                {
                    OTOpndLocal local = (OTOpndLocal)varwr;
                    if(local.local.name.StartsWith(_mainCallNo))
                    {
                        link.List.Remove(link);
                        return true;
                    }
                }

                // strip out stores to local vars where the var is not read
                // but convert the value to an OTStmtVoid in case it is a call
                if(varwr is OTOpndLocal)
                {
                    OTOpndLocal local = (OTOpndLocal)varwr;
                    local.local.nlclreads = 0;
                    decompile.topBlock.CountRefs();
                    if(local.local.nlclreads == 0)
                    {
                        OTStmt voidstmt = OTStmtVoid.Make(value);
                        if(voidstmt == null)
                            link.List.Remove(link);
                        else
                            link.Value = voidstmt;
                        return true;
                    }
                }

                // strip out bla = newobj HeapTrackerList (...);
                if(value is OTOpndNewobj)
                {
                    OTOpndNewobj valueno = (OTOpndNewobj)value;
                    if(valueno.ctor.DeclaringType == typeof(HeapTrackerList))
                    {
                        link.List.Remove(link);
                        return true;
                    }
                }

                // strip out bla = newobj HeapTrackerObject (...);
                if(value is OTOpndNewobj)
                {
                    OTOpndNewobj valueno = (OTOpndNewobj)value;
                    if(valueno.ctor.DeclaringType == typeof(HeapTrackerObject))
                    {
                        link.List.Remove(link);
                        return true;
                    }
                }

                // strip out bla = newobj HeapTrackerString (...);
                if(value is OTOpndNewobj)
                {
                    OTOpndNewobj valueno = (OTOpndNewobj)value;
                    if(valueno.ctor.DeclaringType == typeof(HeapTrackerString))
                    {
                        link.List.Remove(link);
                        return true;
                    }
                }

                // convert tmp$n = bla bla;
                //         ....  tmp$n  ....;
                // to
                //         ....  bla bla  ....;
                // gets rid of vast majority of temps
                if(varwr is OTOpndLocal)
                {
                    OTOpndLocal temp = (OTOpndLocal)varwr;
                    if(temp.local.name.StartsWith("tmp$"))
                    {
                        temp.local.nlclreads = 0;
                        temp.local.nlclwrites = 0;
                        decompile.topBlock.CountRefs();
                        if((temp.local.nlclreads == 1) && (temp.local.nlclwrites == 1) && (link.Next != null))
                        {
                            OTStmt nextstmt = link.Next.Value;
                            if(!(nextstmt is OTStmtBlock))
                            {
                                if(nextstmt.ReplaceOperand(varwr, value))
                                {
                                    link.List.Remove(link);
                                    return true;
                                }
                            }
                        }

                        // also try to convert:
                        //    tmp$n = ... asdf ...  << we are here (link)
                        //    lcl = tmp$n;          << nextstore
                        //    ... qwer tmp$n ...
                        //    ... no further references to tmp$n
                        // to:
                        //    lcl = ... asdf ...
                        //    ... qwer lcl ...
                        if((temp.local.nlclreads == 2) && (temp.local.nlclwrites == 1) &&
                                (link.Next != null) && (link.Next.Value is OTStmtStore))
                        {
                            OTStmtStore nextstore = (OTStmtStore)link.Next.Value;
                            if((nextstore.varwr is OTOpndLocal) && (nextstore.value is OTOpndLocal) && (link.Next.Next != null))
                            {
                                OTOpndLocal localopnd = (OTOpndLocal)nextstore.varwr;
                                OTOpndLocal tempopnd = (OTOpndLocal)nextstore.value;
                                if(tempopnd.local == temp.local)
                                {
                                    OTStmt finalstmt = link.Next.Next.Value;
                                    if(finalstmt.ReplaceOperand(tempopnd, localopnd))
                                    {
                                        nextstore.value = value;
                                        link.List.Remove(link);
                                        return true;
                                    }
                                }
                            }
                        }
                    }
                }

                // convert:
                //    dup$n = ... asdf ...  << we are here
                //    lcl = dup$n;
                //    ... qwer dup$n ...
                //    ... no further references to dup$n
                // to:
                //    lcl = ... asdf ...
                //    ... qwer lcl ...
                if((varwr is OTOpndDup) && (link != null))
                {
                    OTOpndDup vardup = (OTOpndDup)varwr;
                    LinkedListNode<OTStmt> nextlink = link.Next;
                    vardup.ndupreads = 0;
                    decompile.topBlock.CountRefs();
                    if((vardup.ndupreads == 2) && (nextlink != null) && (nextlink.Value is OTStmtStore))
                    {

                        // point to the supposed lcl = dup$n statement
                        OTStmtStore nextstore = (OTStmtStore)nextlink.Value;
                        LinkedListNode<OTStmt> nextlink2 = nextlink.Next;
                        if((nextstore.varwr is OTOpndLocal) && (nextstore.value == vardup) && (nextlink2 != null))
                        {

                            // get the local var being written and point to the ... qwer dup$n ... statement
                            OTOpndLocal varlcl = (OTOpndLocal)nextstore.varwr;
                            OTStmt nextstmt2 = nextlink2.Value;

                            // try to replace dup$n in qwer with lcl
                            if(nextstmt2.ReplaceOperand(vardup, varlcl))
                            {

                                // successful, replace dup$n in asdf with lcl
                                // and delete the lcl = dup$n statement
                                varwr = varlcl;
                                nextlink.List.Remove(nextlink);
                                return true;
                            }
                        }
                    }
                }

                // convert:
                //    dup$n = ... asdf ...  << we are here
                //    ... qwer dup$n ...
                //    ... no further references to dup$n
                // to:
                //    ... qwer ... asdf ... ...
                if((varwr is OTOpndDup) && (link != null))
                {
                    OTOpndDup vardup = (OTOpndDup)varwr;
                    LinkedListNode<OTStmt> nextlink = link.Next;
                    vardup.ndupreads = 0;
                    decompile.topBlock.CountRefs();
                    if((vardup.ndupreads == 1) && (nextlink != null))
                    {

                        // point to the ... qwer dup$n ... statement
                        OTStmt nextstmt = nextlink.Value;

                        // try to replace dup$n in qwer with ... asdf ...
                        if(nextstmt.ReplaceOperand(vardup, value))
                        {

                            // successful, delete the dup$n = ... asdf ... statement
                            link.List.Remove(link);
                            return true;
                        }
                    }
                }

                // look for list initialization [ ... ]
                if(OTOpndListIni.Detect(link))
                    return true;

                // __xmrinst = (XMRInstAbstract) arg$0 indicates this is an event handler
                // so strip it out and set the flag
                if((varwr is OTOpndLocal) && (value is OTOpndCast))
                {
                    OTOpndLocal lcl = (OTOpndLocal)varwr;
                    OTOpndCast cast = (OTOpndCast)value;
                    if(lcl.local.name.StartsWith(_xmrinstlocal) && (cast.value is OTOpndArg))
                    {
                        link.List.Remove(link);
                        return true;
                    }
                }

                // local = [ (optional cast) ] __xmrinst.ehArgs[n] is a definition of event handler arg #n
                // if found, make it event handler arg list definition
                OTOpnd valuenocast = value;
                if(valuenocast is OTOpndCast)
                    valuenocast = ((OTOpndCast)value).value;
                if((varwr is OTOpndLocal) && (valuenocast is OTOpndArrayElem))
                {
                    OTOpndArrayElem array = (OTOpndArrayElem)valuenocast;
                    if((array.array is OTOpndField) && (array.index is OTOpndInt))
                    {
                        OTOpndField arrayfield = (OTOpndField)array.array;
                        if((arrayfield.obj is OTOpndLocal) &&
                                ((OTOpndLocal)arrayfield.obj).local.name.StartsWith(_xmrinstlocal) &&
                                (arrayfield.field.Name == _ehArgs))
                        {
                            int index = ((OTOpndInt)array.index).value;
                            decompile.eharglist[index] = ((OTOpndLocal)varwr).local;
                            link.List.Remove(link);
                            return true;
                        }
                    }
                }

                // __retval$n = ...;  =>  return ...;
                if(varwr is OTOpndLocal)
                {
                    OTOpndLocal lcl = (OTOpndLocal)varwr;
                    if(lcl.local.name.StartsWith(_retval))
                    {
                        link.Value = new OTStmtRet(value);
                        return true;
                    }
                }

                return false;
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = false;
                if(value != null)
                    value = value.ReplaceOperand(oldopnd, newopnd, ref rc);
                return rc;
            }

            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                // print x = x + 1 as x += 1, but don't print x = x < 3 as x <= 3
                if(value is OTOpndBinOp)
                {
                    OTOpndBinOp valuebo = (OTOpndBinOp)value;
                    if(varwr.SameAs(valuebo.left) && " add and div mul or rem shl shr sub xor ".Contains(' ' + valuebo.opCode.name + ' '))
                    {
                        twout.Write(varwr.PrintableString + ' ' + valuebo.opCode.source + "= " + valuebo.rite.PrintableString + ';');
                        return;
                    }
                }

                twout.Write(varwr.PrintableString + " = " + value.PrintableString + ';');
            }
        }

        /**
         * Dispatch to a table of labels.
         */
        private class OTStmtSwitch: OTStmt
        {
            private OTOpnd index;
            private OTLabel[] labels;

            public OTStmtSwitch(OTOpnd index, OTLabel[] labels)
            {
                this.index = index;
                this.labels = labels;
            }

            public override void CountRefs()
            {
                index.CountRefs(false);
                foreach(OTLabel label in labels)
                {
                    label.lbljumps++;
                }
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = false;
                if(index != null)
                    index = index.ReplaceOperand(oldopnd, newopnd, ref rc);
                return rc;
            }

            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("switch (" + index.PrintableString + ") {\n");
                for(int i = 0; i < labels.Length; i++)
                {
                    twout.Write(indent + INDENT + "case " + i + ": jump " + labels[i].name + ";\n");
                }
                twout.Write(indent + '}');
            }
        }

        /**
         * Throw an exception.
         */
        private class OTStmtThrow: OTStmt
        {
            private OTOpnd value;
            private OTDecompile decompile;

            public OTStmtThrow(OTOpnd value, OTDecompile decompile)
            {
                this.value = value;
                this.decompile = decompile;
            }

            public override void CountRefs()
            {
                value.CountRefs(false);
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                return StripStuffForTerminal(link);
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = false;
                if(value != null)
                    value = value.ReplaceOperand(oldopnd, newopnd, ref rc);
                return rc;
            }

            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                // throw newobj ScriptUndefinedStateException ("x")  =>  state x
                if(value is OTOpndNewobj)
                {
                    OTOpndNewobj valueno = (OTOpndNewobj)value;
                    if((valueno.ctor.DeclaringType == typeof(ScriptUndefinedStateException)) &&
                        (valueno.args.Length == 1) && (valueno.args[0] is OTOpndString))
                    {
                        OTOpndString arg0 = (OTOpndString)valueno.args[0];
                        twout.Write("state " + arg0.value + ";  /* throws undefined state exception */");
                        return;
                    }
                }

                // throw newobj ScriptChangeStateException (n)  =>  state n
                if(value is OTOpndNewobj)
                {
                    OTOpndNewobj valueno = (OTOpndNewobj)value;
                    if((valueno.ctor.DeclaringType == typeof(ScriptChangeStateException)) &&
                        (valueno.args.Length == 1) && (valueno.args[0] is OTOpndInt))
                    {
                        OTOpndInt arg0 = (OTOpndInt)valueno.args[0];
                        twout.Write("state " + decompile.scriptObjCode.stateNames[arg0.value] + ';');
                        return;
                    }
                }

                // throwing something else, output as is
                twout.Write("throw " + value.PrintableString + ';');
            }
        }

        /**
         * Call with void return, or really anything that we discard the value of after computing it.
         */
        private class OTStmtVoid: OTStmt
        {
            private OTOpnd value;

            public static void AddLast(OTDecompile decompile, OTOpnd value)
            {
                OTStmt it = OTStmtVoid.Make(value);
                if(it != null)
                    decompile.AddLastStmt(it);
            }

            public static OTStmt Make(OTOpnd value)
            {
                if(!value.HasSideEffects)
                    return null;
                OTStmtVoid it = new OTStmtVoid();
                it.value = value;
                return it;
            }

            private OTStmtVoid()
            {
            }

            public override void CountRefs()
            {
                value.CountRefs(false);
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = false;
                value = value.ReplaceOperand(oldopnd, newopnd, ref rc);
                return rc;
            }

            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                // strip out calls to CheckRunQuick() and CheckRunStack()
                if(value is OTOpndCall)
                {
                    OTOpndCall call = (OTOpndCall)value;
                    MethodInfo method = call.method;
                    if((method.Name == _checkRunQuick) || (method.Name == _checkRunStack))
                    {
                        link.List.Remove(link);
                        return true;
                    }
                }

                return false;
            }

            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write(value.PrintableString + ';');
            }
        }

        /***************************\
         *  Structured statements  *
        \***************************/

        /**
         * Block of statements.
         */
        private class OTStmtBlock: OTStmt
        {
            public LinkedList<OTStmt> blkstmts = new LinkedList<OTStmt>();

            public override void CountRefs()
            {
                foreach(OTStmt stmt in blkstmts)
                {
                    stmt.CountRefs();
                }
            }

            /**
             * Scrub out all references to behind-the-scenes parts and simplify.
             */
            public override bool StripStuff(LinkedListNode<OTStmt> link)
            {
                // loop through all sub-statements to strip out behind-the-scenes references
                bool rc = false;
                loop:
                for(LinkedListNode<OTStmt> stmtlink = blkstmts.First; stmtlink != null; stmtlink = stmtlink.Next)
                {
                    if(stmtlink.Value.StripStuff(stmtlink))
                    {
                        rc = true;
                        goto loop;
                    }
                }
                if(rc)
                    return true;

                // try to merge this block into outer block
                // change:
                //   {
                //       ...
                //       {          << link points here
                //           ...
                //       }
                //       ...
                //   }
                // to:
                //   {
                //       ...
                //       ...
                //       ...
                //   }
                if(link != null)
                {
                    LinkedListNode<OTStmt> nextlink;
                    while((nextlink = blkstmts.Last) != null)
                    {
                        nextlink.List.Remove(nextlink);
                        link.List.AddAfter(link, nextlink);
                    }
                    link.List.Remove(link);
                    return true;
                }

                return rc;
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = false;
                foreach(OTStmt stmt in blkstmts)
                {
                    rc |= stmt.ReplaceOperand(oldopnd, newopnd);
                }
                return rc;
            }

            /**
             * Check each statement in the block to see if it starts a do/for/if/while statement.
             */
            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                bool rc = false;
                loop:
                for(link = blkstmts.First; link != null; link = link.Next)
                {
                    if(link.Value.DetectDoForIfWhile(link))
                    {
                        rc = true;
                        goto loop;
                    }
                }
                return rc;
            }

            /**
             * Assume we will never try to replace the block itself.
             * But go through all our sub-ordinates statements.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                for(LinkedListNode<OTStmt> childlink = blkstmts.First; childlink != null; childlink = childlink.Next)
                {
                    childlink.Value = childlink.Value.ReplaceStatement(oldstmt, newstmt);
                }
                return this;
            }

            /**
             * Print out the block including its enclosed statements.
             */
            public override void PrintStmt(TextWriter twout, string indent)
            {
                switch(blkstmts.Count)
                {
                    case 0:
                        {
                            twout.Write("{ }");
                            break;
                        }
                    ////case 1: {
                    ////    blkstmts.First.Value.PrintStmt (twout, indent);
                    ////    break;
                    ////}
                    default:
                        {
                            twout.Write('{');
                            PrintBodyAndEnd(twout, indent);
                            break;
                        }
                }
            }

            public void PrintBodyAndEnd(TextWriter twout, string indent)
            {
                string newindent = indent + INDENT;
                foreach(OTStmt stmt in blkstmts)
                {
                    twout.Write('\n' + indent);
                    if(!(stmt is OTStmtLabel))
                        twout.Write(INDENT);
                    else
                        twout.Write(LABELINDENT);
                    stmt.PrintStmt(twout, newindent);
                }
                twout.Write('\n' + indent + '}');
            }
        }

        /**
         * 'do' statement.
         */
        private class OTStmtDo: OTStmt
        {
            private OTOpnd dotest;
            private OTStmtBlock dobody;

            /**
             * See if we have a do loop...
             *   @doloop_<suffix>;     << link points here
             *     ... <dobody> ...
             *     [ if (dotest) ] jump doloop_<suffix>;
             */
            public static bool Detect(LinkedListNode<OTStmt> link)
            {
                // see if we have label starting with 'doloop_'
                OTLabel looplabel = ((OTStmtLabel)link.Value).label;
                if(!looplabel.name.StartsWith(_doLoop))
                    return false;

                // good chance we have a do loop
                OTStmtDo it = new OTStmtDo();

                // scan ahead looking for the terminating cond/jump loop
                // also gather up the statements for the do body block
                it.dobody = new OTStmtBlock();
                LinkedListNode<OTStmt> nextlink;
                for(nextlink = link.Next; nextlink != null; nextlink = nextlink.Next)
                {
                    OTStmt nextstmt = nextlink.Value;

                    // add statement to do body
                    it.dobody.blkstmts.AddLast(nextlink.Value);

                    // check for something what jumps to loop label
                    // that gives us the end of the loop
                    OTStmt maybejump = nextstmt;
                    if(nextstmt is OTStmtCond)
                    {
                        maybejump = ((OTStmtCond)nextstmt).stmt;
                    }
                    if((maybejump is OTStmtJump) && (((OTStmtJump)maybejump).label == looplabel))
                    {
                        break;
                    }
                }

                // make sure we found the jump back to the loop label
                if(nextlink == null)
                    return false;

                // remove all statements from caller's block including the continue label if any
                // but leave the break label alone it will be removed later if unreferenced
                // and leave the initial loop label intact for now
                for(LinkedListNode<OTStmt> remlink = null; (remlink = link.Next) != null;)
                {
                    link.List.Remove(remlink);
                    if(remlink == nextlink)
                        break;
                }

                // take test condition from last statement of body
                // it should be an cond/jump or just a jump to the loop label
                LinkedListNode<OTStmt> lastlink = it.dobody.blkstmts.Last;
                OTStmt laststmt = lastlink.Value;
                if(laststmt is OTStmtCond)
                {
                    it.dotest = ((OTStmtCond)laststmt).valu;
                }
                else
                {
                    it.dotest = new OTOpndInt(1);
                }
                lastlink.List.Remove(lastlink);

                // finally replace the loop label with the whole do statement
                link.Value = it;

                // tell caller we made a change
                return true;
            }

            public override void CountRefs()
            {
                if(dotest != null)
                    dotest.CountRefs(false);
                if(dobody != null)
                    dobody.CountRefs();
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                return dobody.ReplaceOperand(oldopnd, newopnd);
            }

            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                return dobody.DetectDoForIfWhile(link);
            }

            /**
             * Assume we won't replace the do statement itself.
             * But search all our sub-ordinate statements.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                dobody = (OTStmtBlock)dobody.ReplaceStatement(oldstmt, newstmt);
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                // output do body
                twout.Write("do ");
                dobody.PrintStmt(twout, indent);

                // output while part
                twout.Write(" while (" + StripBrtrue(dotest).PrintableString + ");");
            }
        }

        /**
         * 'for' or 'while' statement.
         */
        private class OTStmtFor: OTStmt
        {
            private bool iswhile;
            private OTOpnd fortest;
            private OTStmtBlock forbody;
            private OTStmt forinit;
            private OTStmt forstep;

            /**
             * See if we have a for or while loop...
             *     <forinit>
             *   @forloop_<suffix>;     << link points here
             *     [ if (<fortest>) jump forbreak_<suffix>; ]
             *     ... <forbody> ...
             *     jump forloop_<suffix>;
             *   [ @forbreak_<suffix>; ]
             */
            public static bool Detect(LinkedListNode<OTStmt> link, bool iswhile)
            {
                string loopname = iswhile ? _whileLoop : _forLoop;
                string breakname = iswhile ? _whileBreak : _forBreak;

                // see if we have label starting with 'forloop_'
                OTLabel looplabel = ((OTStmtLabel)link.Value).label;
                if(!looplabel.name.StartsWith(loopname))
                    return false;

                // good chance we have a for loop
                OTStmtFor it = new OTStmtFor();
                it.iswhile = iswhile;

                // all labels end with this suffix
                string suffix = looplabel.name.Substring(loopname.Length);

                // scan ahead looking for the 'jump forloop_<suffix>;' statement
                // also gather up the statements for the for body block
                it.forbody = new OTStmtBlock();
                LinkedListNode<OTStmt> lastlink;
                for(lastlink = link; (lastlink = lastlink.Next) != null;)
                {

                    // check for jump forloop that tells us where loop ends
                    if(lastlink.Value is OTStmtJump)
                    {
                        OTStmtJump lastjump = (OTStmtJump)lastlink.Value;
                        if(lastjump.label == looplabel)
                            break;
                    }

                    // add to body block
                    it.forbody.blkstmts.AddLast(lastlink.Value);
                }

                // make sure we found the 'jump forloop' where the for loop ends
                if(lastlink == null)
                    return false;

                // remove all statements from caller's block including final jump
                // but leave the loop label in place
                for(LinkedListNode<OTStmt> nextlink = null; (nextlink = link.Next) != null;)
                {
                    link.List.Remove(nextlink);
                    if(nextlink == lastlink)
                        break;
                }

                // if statement before loop label is an assignment, use it for the init statement
                if(!iswhile && (link.Previous != null) && (link.Previous.Value is OTStmtStore))
                {
                    it.forinit = link.Previous.Value;
                    link.List.Remove(link.Previous);
                }

                // if first statement of for body is 'if (...) jump breaklabel' use it for the test value
                if((it.forbody.blkstmts.First != null) && (it.forbody.blkstmts.First.Value is OTStmtCond))
                {
                    OTStmtCond condstmt = (OTStmtCond)it.forbody.blkstmts.First.Value;
                    if((condstmt.stmt is OTStmtJump) && (((OTStmtJump)condstmt.stmt).label.name == breakname + suffix))
                    {
                        it.fortest = OTOpndUnOp.Make(MyOp.Brfalse, condstmt.valu);
                        it.forbody.blkstmts.RemoveFirst();
                    }
                }

                // if last statement of body is an assigment,
                // use the assignment as the step statement
                if(!iswhile && (it.forbody.blkstmts.Last != null) &&
                        (it.forbody.blkstmts.Last.Value is OTStmtStore))
                {
                    LinkedListNode<OTStmt> storelink = it.forbody.blkstmts.Last;
                    storelink.List.Remove(storelink);
                    it.forstep = storelink.Value;
                }

                // finally replace the loop label with the whole for statement
                link.Value = it;

                // tell caller we made a change
                return true;
            }

            public override void CountRefs()
            {
                if(fortest != null)
                    fortest.CountRefs(false);
                if(forbody != null)
                    forbody.CountRefs();
                if(forinit != null)
                    forinit.CountRefs();
                if(forstep != null)
                    forstep.CountRefs();
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                return forbody.ReplaceOperand(oldopnd, newopnd) |
                        ((forinit != null) && forinit.ReplaceOperand(oldopnd, newopnd)) |
                        ((forstep != null) && forstep.ReplaceOperand(oldopnd, newopnd));
            }

            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                return forbody.DetectDoForIfWhile(link) |
                            ((forinit != null) && forinit.DetectDoForIfWhile(link)) |
                            ((forstep != null) && forstep.DetectDoForIfWhile(link));
            }

            /**
             * Assume we won't replace the for statement itself.
             * But search all our sub-ordinate statements.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                forbody = (OTStmtBlock)forbody.ReplaceStatement(oldstmt, newstmt);
                if(forinit != null)
                    forinit = forinit.ReplaceStatement(oldstmt, newstmt);
                if(forstep != null)
                    forstep = forstep.ReplaceStatement(oldstmt, newstmt);
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                if(iswhile)
                {
                    twout.Write("while (");
                    if(fortest == null)
                    {
                        twout.Write("TRUE");
                    }
                    else
                    {
                        twout.Write(StripBrtrue(fortest).PrintableString);
                    }
                }
                else
                {
                    twout.Write("for (");
                    if(forinit != null)
                    {
                        forinit.PrintStmt(twout, indent + INDENT);
                    }
                    else
                    {
                        twout.Write(';');
                    }
                    if(fortest != null)
                    {
                        twout.Write(' ' + StripBrtrue(fortest).PrintableString);
                    }
                    twout.Write(';');
                    if(forstep != null)
                    {
                        StringWriter sw = new StringWriter();
                        sw.Write(' ');
                        forstep.PrintStmt(sw, indent + INDENT);
                        StringBuilder sb = sw.GetStringBuilder();
                        int sl = sb.Length;
                        if((sl > 0) && (sb[sl - 1] == ';'))
                            sb.Remove(--sl, 1);
                        twout.Write(sb.ToString());
                    }
                }

                twout.Write(") ");
                forbody.PrintStmt(twout, indent);
            }
        }

        /**
         * if/then/else block.
         */
        private class OTStmtIf: OTStmt
        {
            private OTOpnd testvalu;
            private OTStmt thenstmt;
            private OTStmt elsestmt;  // might be null

            /**
             * Try to detect a structured if statement.
             *
             *   if (condition) jump ifdone_<suffix>;   << link points here
             *      ... then body ...
             * @ifdone_<suffix>;
             *
             *   if (condition) jump ifelse_<suffix>;
             *      ... then body ...
             *   jump ifdone_<suffix>;  << optional if true body doesn't fall through
             * @ifelse_<suffix>;
             *      ... else body ...
             * @ifdone_<suffix>;
             */
            public static bool Detect(LinkedListNode<OTStmt> link)
            {
                OTStmtCond condstmt = (OTStmtCond)link.Value;
                if(!(condstmt.stmt is OTStmtJump))
                    return false;

                OTStmtJump jumpstmt = (OTStmtJump)condstmt.stmt;
                if(jumpstmt.label.name.StartsWith(_ifDone))
                {

                    // then-only if

                    // skip forward to find the ifdone_<suffix> label
                    // also save the intervening statements for the then body
                    OTStmtBlock thenbody;
                    LinkedListNode<OTStmt> donelink = ScanForLabel(link, jumpstmt.label, out thenbody);

                    // make sure we found matching label
                    if(donelink == null)
                        return false;

                    // replace the jump ifdone_<suffix> with the <then body>
                    OTStmtIf it = new OTStmtIf();
                    it.thenstmt = thenbody;

                    // replace the test value with the opposite
                    it.testvalu = OTOpndUnOp.Make(MyOp.Brfalse, condstmt.valu);
                    condstmt.valu = null;

                    // strip out the true body statements from the main code including the ifdone_<suffix> label
                    StripInterveningStatements(link, donelink);

                    // replace the simple conditional with the if/then/else block
                    link.Value = it;

                    // tell caller we changed something
                    return true;
                }

                if(jumpstmt.label.name.StartsWith(_ifElse))
                {
                    string suffix = jumpstmt.label.name.Substring(_ifElse.Length);

                    // if/then/else
                    OTStmtIf it = new OTStmtIf();

                    // skip forward to find the ifelse_<suffix> label
                    // also save the intervening statements for the true body
                    OTStmtBlock thenbody;
                    LinkedListNode<OTStmt> elselink = ScanForLabel(link, jumpstmt.label, out thenbody);

                    // make sure we found matching label
                    if(elselink != null)
                    {

                        // the last statement of the then body might be a jump ifdone_<suffix>
                        LinkedListNode<OTStmt> lastthenlink = thenbody.blkstmts.Last;
                        if((lastthenlink != null) && (lastthenlink.Value is OTStmtJump))
                        {
                            OTStmtJump jumpifdone = (OTStmtJump)lastthenlink.Value;
                            if(jumpifdone.label.name == _ifDone + suffix)
                            {

                                lastthenlink.List.Remove(lastthenlink);

                                // skip forward to find the ifdone_<suffix> label
                                // also save the intervening statements for the else body
                                OTStmtBlock elsebody;
                                LinkedListNode<OTStmt> donelink = ScanForLabel(elselink, jumpifdone.label, out elsebody);
                                if(donelink != null)
                                {

                                    // replace the jump ifdone_<suffix> with the <true body>
                                    it.thenstmt = thenbody;

                                    // save the else body as well
                                    it.elsestmt = elsebody;

                                    // replace the test value with the opposite
                                    it.testvalu = OTOpndUnOp.Make(MyOp.Brfalse, condstmt.valu);
                                    condstmt.valu = null;

                                    // strip out the true and else body statements from the main code including the ifdone_<suffix> label
                                    StripInterveningStatements(link, donelink);

                                    // replace the simple conditional with the if/then/else block
                                    link.Value = it;

                                    // tell caller we changed something
                                    return true;
                                }
                            }
                        }

                        // missing the jump _ifDone_<suffix>, so make it a simple if/then
                        //   if (condition) jump ifelse_<suffix>;   << link
                        //      ... then body ...                   << encapsulated in block thenbody
                        // @ifelse_<suffix>;                        << elselink
                        //      ... else body ...                   << still inline and leave it there
                        // @ifdone_<suffix>;                        << strip this out

                        // replace the jump ifelse_<suffix> with the <true body>
                        it.thenstmt = thenbody;

                        // replace the test value with the opposite
                        it.testvalu = OTOpndUnOp.Make(MyOp.Brfalse, condstmt.valu);
                        condstmt.valu = null;

                        // strip out the then body statements from the main code including the ifelse_<suffix> label
                        StripInterveningStatements(link, elselink);

                        // there's a dangling unused ifdone_<suffix> label ahead that has to be stripped
                        for(LinkedListNode<OTStmt> donelink = link; (donelink = donelink.Next) != null;)
                        {
                            if((donelink.Value is OTStmtLabel) && (((OTStmtLabel)donelink.Value).label.name == _ifDone + suffix))
                            {
                                donelink.List.Remove(donelink);
                                break;
                            }
                        }

                        // replace the simple conditional with the if/then/else block
                        link.Value = it;

                        // tell caller we changed something
                        return true;
                    }
                }

                return false;
            }

            private OTStmtIf()
            {
            }

            public override void CountRefs()
            {
                if(testvalu != null)
                    testvalu.CountRefs(false);
                if(thenstmt != null)
                    thenstmt.CountRefs();
                if(elsestmt != null)
                    elsestmt.CountRefs();
            }

            public override bool ReplaceOperand(OTOpnd oldopnd, OTOpnd newopnd)
            {
                bool rc = thenstmt.ReplaceOperand(oldopnd, newopnd);
                testvalu = testvalu.ReplaceOperand(oldopnd, newopnd, ref rc);
                return rc;
            }

            public override bool DetectDoForIfWhile(LinkedListNode<OTStmt> link)
            {
                return ((thenstmt != null) && thenstmt.DetectDoForIfWhile(link)) |
                       ((elsestmt != null) && elsestmt.DetectDoForIfWhile(link));
            }

            /**
             * Assume we won't replace the if statement itself.
             * But search all our sub-ordinate statements.
             */
            public override OTStmt ReplaceStatement(OTStmt oldstmt, OTStmt newstmt)
            {
                thenstmt = thenstmt.ReplaceStatement(oldstmt, newstmt);
                if(elsestmt != null)
                    elsestmt = elsestmt.ReplaceStatement(oldstmt, newstmt);
                return this;
            }

            public override void PrintStmt(TextWriter twout, string indent)
            {
                twout.Write("if (" + StripBrtrue(testvalu).PrintableString + ") ");
                OTStmt thenst = ReduceStmtBody(thenstmt, false);
                thenst.PrintStmt(twout, indent);
                if(elsestmt != null)
                {
                    twout.Write('\n' + indent + "else ");
                    OTStmt elsest = ReduceStmtBody(elsestmt, true);
                    elsest.PrintStmt(twout, indent);
                }
            }

            // strip block off a single jump so it prints inline instead of with braces around it
            // also, if this is part of else, strip block for ifs to make else if statement
            private static OTStmt ReduceStmtBody(OTStmt statement, bool stripif)
            {
                OTStmt onestmt = statement;
                if((onestmt is OTStmtBlock) && (((OTStmtBlock)onestmt).blkstmts.Count == 1))
                {
                    onestmt = ((OTStmtBlock)onestmt).blkstmts.First.Value;
                    if((onestmt is OTStmtJump) || (stripif && (onestmt is OTStmtIf)))
                    {
                        return onestmt;
                    }
                }
                return statement;
            }

            /**
             * Scan forward for a given label definition.
             * Put intervening statements in a statement block.
             * @param link = start scanning after this statement
             * @param label = look for this label definition
             * @param block = where to return intervening statement block
             * @returns null: label definition not found
             *          else: label definition statement
             */
            private static LinkedListNode<OTStmt> ScanForLabel(LinkedListNode<OTStmt> link,
                        OTLabel label, out OTStmtBlock block)
            {
                block = new OTStmtBlock();
                while((link = link.Next) != null)
                {
                    if(link.Value is OTStmtLabel)
                    {
                        if(((OTStmtLabel)link.Value).label == label)
                            break;
                    }
                    block.blkstmts.AddLast(link.Value);
                }
                return link;
            }

            /**
             * Strip statements after link up to and including donelink.
             */
            private static void StripInterveningStatements(LinkedListNode<OTStmt> link, LinkedListNode<OTStmt> donelink)
            {
                LinkedListNode<OTStmt> striplink;
                do
                {
                    striplink = link.Next;
                    striplink.List.Remove(striplink);
                } while(striplink != donelink);
            }
        }

        private class MyOp
        {
            public int index;
            public OpCode sysop;
            public string name;
            public string source;

            private static Dictionary<string, MyOp> myopsbyname = new Dictionary<string, MyOp>();
            private static int nextindex = 0;

            public MyOp(OpCode sysop)
            {
                this.index = nextindex++;
                this.sysop = sysop;
                this.name = sysop.Name;
                myopsbyname.Add(name, this);
            }

            public MyOp(OpCode sysop, string source)
            {
                this.index = nextindex++;
                this.sysop = sysop;
                this.name = sysop.Name;
                this.source = source;
                myopsbyname.Add(name, this);
            }

            public MyOp(string name)
            {
                this.index = nextindex++;
                this.name = name;
                myopsbyname.Add(name, this);
            }

            public MyOp(string name, string source)
            {
                this.index = nextindex++;
                this.name = name;
                this.source = source;
                myopsbyname.Add(name, this);
            }

            public static MyOp GetByName(string name)
            {
                return myopsbyname[name];
            }

            public override string ToString()
            {
                return name;
            }

            // these copied from OpCodes.cs
            public static readonly MyOp Nop = new MyOp(OpCodes.Nop);
            public static readonly MyOp Break = new MyOp(OpCodes.Break);
            public static readonly MyOp Ldarg_0 = new MyOp(OpCodes.Ldarg_0);
            public static readonly MyOp Ldarg_1 = new MyOp(OpCodes.Ldarg_1);
            public static readonly MyOp Ldarg_2 = new MyOp(OpCodes.Ldarg_2);
            public static readonly MyOp Ldarg_3 = new MyOp(OpCodes.Ldarg_3);
            public static readonly MyOp Ldloc_0 = new MyOp(OpCodes.Ldloc_0);
            public static readonly MyOp Ldloc_1 = new MyOp(OpCodes.Ldloc_1);
            public static readonly MyOp Ldloc_2 = new MyOp(OpCodes.Ldloc_2);
            public static readonly MyOp Ldloc_3 = new MyOp(OpCodes.Ldloc_3);
            public static readonly MyOp Stloc_0 = new MyOp(OpCodes.Stloc_0);
            public static readonly MyOp Stloc_1 = new MyOp(OpCodes.Stloc_1);
            public static readonly MyOp Stloc_2 = new MyOp(OpCodes.Stloc_2);
            public static readonly MyOp Stloc_3 = new MyOp(OpCodes.Stloc_3);
            public static readonly MyOp Ldarg_S = new MyOp(OpCodes.Ldarg_S);
            public static readonly MyOp Ldarga_S = new MyOp(OpCodes.Ldarga_S);
            public static readonly MyOp Starg_S = new MyOp(OpCodes.Starg_S);
            public static readonly MyOp Ldloc_S = new MyOp(OpCodes.Ldloc_S);
            public static readonly MyOp Ldloca_S = new MyOp(OpCodes.Ldloca_S);
            public static readonly MyOp Stloc_S = new MyOp(OpCodes.Stloc_S);
            public static readonly MyOp Ldnull = new MyOp(OpCodes.Ldnull);
            public static readonly MyOp Ldc_I4_M1 = new MyOp(OpCodes.Ldc_I4_M1);
            public static readonly MyOp Ldc_I4_0 = new MyOp(OpCodes.Ldc_I4_0);
            public static readonly MyOp Ldc_I4_1 = new MyOp(OpCodes.Ldc_I4_1);
            public static readonly MyOp Ldc_I4_2 = new MyOp(OpCodes.Ldc_I4_2);
            public static readonly MyOp Ldc_I4_3 = new MyOp(OpCodes.Ldc_I4_3);
            public static readonly MyOp Ldc_I4_4 = new MyOp(OpCodes.Ldc_I4_4);
            public static readonly MyOp Ldc_I4_5 = new MyOp(OpCodes.Ldc_I4_5);
            public static readonly MyOp Ldc_I4_6 = new MyOp(OpCodes.Ldc_I4_6);
            public static readonly MyOp Ldc_I4_7 = new MyOp(OpCodes.Ldc_I4_7);
            public static readonly MyOp Ldc_I4_8 = new MyOp(OpCodes.Ldc_I4_8);
            public static readonly MyOp Ldc_I4_S = new MyOp(OpCodes.Ldc_I4_S);
            public static readonly MyOp Ldc_I4 = new MyOp(OpCodes.Ldc_I4);
            public static readonly MyOp Ldc_I8 = new MyOp(OpCodes.Ldc_I8);
            public static readonly MyOp Ldc_R4 = new MyOp(OpCodes.Ldc_R4);
            public static readonly MyOp Ldc_R8 = new MyOp(OpCodes.Ldc_R8);
            public static readonly MyOp Dup = new MyOp(OpCodes.Dup);
            public static readonly MyOp Pop = new MyOp(OpCodes.Pop);
            public static readonly MyOp Jmp = new MyOp(OpCodes.Jmp);
            public static readonly MyOp Call = new MyOp(OpCodes.Call);
            public static readonly MyOp Calli = new MyOp(OpCodes.Calli);
            public static readonly MyOp Ret = new MyOp(OpCodes.Ret);
            public static readonly MyOp Br_S = new MyOp(OpCodes.Br_S);
            public static readonly MyOp Brfalse_S = new MyOp(OpCodes.Brfalse_S);
            public static readonly MyOp Brtrue_S = new MyOp(OpCodes.Brtrue_S);
            public static readonly MyOp Beq_S = new MyOp(OpCodes.Beq_S, "==");
            public static readonly MyOp Bge_S = new MyOp(OpCodes.Bge_S, ">=");
            public static readonly MyOp Bgt_S = new MyOp(OpCodes.Bgt_S, ">");
            public static readonly MyOp Ble_S = new MyOp(OpCodes.Ble_S, "<=");
            public static readonly MyOp Blt_S = new MyOp(OpCodes.Blt_S, "<");
            public static readonly MyOp Bne_Un_S = new MyOp(OpCodes.Bne_Un_S, "!=");
            public static readonly MyOp Bge_Un_S = new MyOp(OpCodes.Bge_Un_S);
            public static readonly MyOp Bgt_Un_S = new MyOp(OpCodes.Bgt_Un_S);
            public static readonly MyOp Ble_Un_S = new MyOp(OpCodes.Ble_Un_S);
            public static readonly MyOp Blt_Un_S = new MyOp(OpCodes.Blt_Un_S);
            public static readonly MyOp Br = new MyOp(OpCodes.Br);
            public static readonly MyOp Brfalse = new MyOp(OpCodes.Brfalse, "!");
            public static readonly MyOp Brtrue = new MyOp(OpCodes.Brtrue, "!!");
            public static readonly MyOp Beq = new MyOp(OpCodes.Beq, "==");
            public static readonly MyOp Bge = new MyOp(OpCodes.Bge, ">=");
            public static readonly MyOp Bgt = new MyOp(OpCodes.Bgt, ">");
            public static readonly MyOp Ble = new MyOp(OpCodes.Ble, "<=");
            public static readonly MyOp Blt = new MyOp(OpCodes.Blt, "<");
            public static readonly MyOp Bne_Un = new MyOp(OpCodes.Bne_Un, "!=");
            public static readonly MyOp Bge_Un = new MyOp(OpCodes.Bge_Un);
            public static readonly MyOp Bgt_Un = new MyOp(OpCodes.Bgt_Un);
            public static readonly MyOp Ble_Un = new MyOp(OpCodes.Ble_Un);
            public static readonly MyOp Blt_Un = new MyOp(OpCodes.Blt_Un);
            public static readonly MyOp Switch = new MyOp(OpCodes.Switch);
            public static readonly MyOp Ldind_I1 = new MyOp(OpCodes.Ldind_I1);
            public static readonly MyOp Ldind_U1 = new MyOp(OpCodes.Ldind_U1);
            public static readonly MyOp Ldind_I2 = new MyOp(OpCodes.Ldind_I2);
            public static readonly MyOp Ldind_U2 = new MyOp(OpCodes.Ldind_U2);
            public static readonly MyOp Ldind_I4 = new MyOp(OpCodes.Ldind_I4);
            public static readonly MyOp Ldind_U4 = new MyOp(OpCodes.Ldind_U4);
            public static readonly MyOp Ldind_I8 = new MyOp(OpCodes.Ldind_I8);
            public static readonly MyOp Ldind_I = new MyOp(OpCodes.Ldind_I);
            public static readonly MyOp Ldind_R4 = new MyOp(OpCodes.Ldind_R4);
            public static readonly MyOp Ldind_R8 = new MyOp(OpCodes.Ldind_R8);
            public static readonly MyOp Ldind_Ref = new MyOp(OpCodes.Ldind_Ref);
            public static readonly MyOp Stind_Ref = new MyOp(OpCodes.Stind_Ref);
            public static readonly MyOp Stind_I1 = new MyOp(OpCodes.Stind_I1);
            public static readonly MyOp Stind_I2 = new MyOp(OpCodes.Stind_I2);
            public static readonly MyOp Stind_I4 = new MyOp(OpCodes.Stind_I4);
            public static readonly MyOp Stind_I8 = new MyOp(OpCodes.Stind_I8);
            public static readonly MyOp Stind_R4 = new MyOp(OpCodes.Stind_R4);
            public static readonly MyOp Stind_R8 = new MyOp(OpCodes.Stind_R8);
            public static readonly MyOp Add = new MyOp(OpCodes.Add, "+");
            public static readonly MyOp Sub = new MyOp(OpCodes.Sub, "-");
            public static readonly MyOp Mul = new MyOp(OpCodes.Mul, "*");
            public static readonly MyOp Div = new MyOp(OpCodes.Div, "/");
            public static readonly MyOp Div_Un = new MyOp(OpCodes.Div_Un);
            public static readonly MyOp Rem = new MyOp(OpCodes.Rem, "%");
            public static readonly MyOp Rem_Un = new MyOp(OpCodes.Rem_Un);
            public static readonly MyOp And = new MyOp(OpCodes.And, "&");
            public static readonly MyOp Or = new MyOp(OpCodes.Or, "|");
            public static readonly MyOp Xor = new MyOp(OpCodes.Xor, "^");
            public static readonly MyOp Shl = new MyOp(OpCodes.Shl, "<<");
            public static readonly MyOp Shr = new MyOp(OpCodes.Shr, ">>");
            public static readonly MyOp Shr_Un = new MyOp(OpCodes.Shr_Un);
            public static readonly MyOp Neg = new MyOp(OpCodes.Neg, "-");
            public static readonly MyOp Not = new MyOp(OpCodes.Not, "~");
            public static readonly MyOp Conv_I1 = new MyOp(OpCodes.Conv_I1);
            public static readonly MyOp Conv_I2 = new MyOp(OpCodes.Conv_I2);
            public static readonly MyOp Conv_I4 = new MyOp(OpCodes.Conv_I4);
            public static readonly MyOp Conv_I8 = new MyOp(OpCodes.Conv_I8);
            public static readonly MyOp Conv_R4 = new MyOp(OpCodes.Conv_R4);
            public static readonly MyOp Conv_R8 = new MyOp(OpCodes.Conv_R8);
            public static readonly MyOp Conv_U4 = new MyOp(OpCodes.Conv_U4);
            public static readonly MyOp Conv_U8 = new MyOp(OpCodes.Conv_U8);
            public static readonly MyOp Callvirt = new MyOp(OpCodes.Callvirt);
            public static readonly MyOp Cpobj = new MyOp(OpCodes.Cpobj);
            public static readonly MyOp Ldobj = new MyOp(OpCodes.Ldobj);
            public static readonly MyOp Ldstr = new MyOp(OpCodes.Ldstr);
            public static readonly MyOp Newobj = new MyOp(OpCodes.Newobj);
            public static readonly MyOp Castclass = new MyOp(OpCodes.Castclass);
            public static readonly MyOp Isinst = new MyOp(OpCodes.Isinst);
            public static readonly MyOp Conv_R_Un = new MyOp(OpCodes.Conv_R_Un);
            public static readonly MyOp Unbox = new MyOp(OpCodes.Unbox);
            public static readonly MyOp Throw = new MyOp(OpCodes.Throw);
            public static readonly MyOp Ldfld = new MyOp(OpCodes.Ldfld);
            public static readonly MyOp Ldflda = new MyOp(OpCodes.Ldflda);
            public static readonly MyOp Stfld = new MyOp(OpCodes.Stfld);
            public static readonly MyOp Ldsfld = new MyOp(OpCodes.Ldsfld);
            public static readonly MyOp Ldsflda = new MyOp(OpCodes.Ldsflda);
            public static readonly MyOp Stsfld = new MyOp(OpCodes.Stsfld);
            public static readonly MyOp Stobj = new MyOp(OpCodes.Stobj);
            public static readonly MyOp Conv_Ovf_I1_Un = new MyOp(OpCodes.Conv_Ovf_I1_Un);
            public static readonly MyOp Conv_Ovf_I2_Un = new MyOp(OpCodes.Conv_Ovf_I2_Un);
            public static readonly MyOp Conv_Ovf_I4_Un = new MyOp(OpCodes.Conv_Ovf_I4_Un);
            public static readonly MyOp Conv_Ovf_I8_Un = new MyOp(OpCodes.Conv_Ovf_I8_Un);
            public static readonly MyOp Conv_Ovf_U1_Un = new MyOp(OpCodes.Conv_Ovf_U1_Un);
            public static readonly MyOp Conv_Ovf_U2_Un = new MyOp(OpCodes.Conv_Ovf_U2_Un);
            public static readonly MyOp Conv_Ovf_U4_Un = new MyOp(OpCodes.Conv_Ovf_U4_Un);
            public static readonly MyOp Conv_Ovf_U8_Un = new MyOp(OpCodes.Conv_Ovf_U8_Un);
            public static readonly MyOp Conv_Ovf_I_Un = new MyOp(OpCodes.Conv_Ovf_I_Un);
            public static readonly MyOp Conv_Ovf_U_Un = new MyOp(OpCodes.Conv_Ovf_U_Un);
            public static readonly MyOp Box = new MyOp(OpCodes.Box);
            public static readonly MyOp Newarr = new MyOp(OpCodes.Newarr);
            public static readonly MyOp Ldlen = new MyOp(OpCodes.Ldlen);
            public static readonly MyOp Ldelema = new MyOp(OpCodes.Ldelema);
            public static readonly MyOp Ldelem_I1 = new MyOp(OpCodes.Ldelem_I1);
            public static readonly MyOp Ldelem_U1 = new MyOp(OpCodes.Ldelem_U1);
            public static readonly MyOp Ldelem_I2 = new MyOp(OpCodes.Ldelem_I2);
            public static readonly MyOp Ldelem_U2 = new MyOp(OpCodes.Ldelem_U2);
            public static readonly MyOp Ldelem_I4 = new MyOp(OpCodes.Ldelem_I4);
            public static readonly MyOp Ldelem_U4 = new MyOp(OpCodes.Ldelem_U4);
            public static readonly MyOp Ldelem_I8 = new MyOp(OpCodes.Ldelem_I8);
            public static readonly MyOp Ldelem_I = new MyOp(OpCodes.Ldelem_I);
            public static readonly MyOp Ldelem_R4 = new MyOp(OpCodes.Ldelem_R4);
            public static readonly MyOp Ldelem_R8 = new MyOp(OpCodes.Ldelem_R8);
            public static readonly MyOp Ldelem_Ref = new MyOp(OpCodes.Ldelem_Ref);
            public static readonly MyOp Stelem_I = new MyOp(OpCodes.Stelem_I);
            public static readonly MyOp Stelem_I1 = new MyOp(OpCodes.Stelem_I1);
            public static readonly MyOp Stelem_I2 = new MyOp(OpCodes.Stelem_I2);
            public static readonly MyOp Stelem_I4 = new MyOp(OpCodes.Stelem_I4);
            public static readonly MyOp Stelem_I8 = new MyOp(OpCodes.Stelem_I8);
            public static readonly MyOp Stelem_R4 = new MyOp(OpCodes.Stelem_R4);
            public static readonly MyOp Stelem_R8 = new MyOp(OpCodes.Stelem_R8);
            public static readonly MyOp Stelem_Ref = new MyOp(OpCodes.Stelem_Ref);
            public static readonly MyOp Ldelem = new MyOp(OpCodes.Ldelem);
            public static readonly MyOp Stelem = new MyOp(OpCodes.Stelem);
            public static readonly MyOp Unbox_Any = new MyOp(OpCodes.Unbox_Any);
            public static readonly MyOp Conv_Ovf_I1 = new MyOp(OpCodes.Conv_Ovf_I1);
            public static readonly MyOp Conv_Ovf_U1 = new MyOp(OpCodes.Conv_Ovf_U1);
            public static readonly MyOp Conv_Ovf_I2 = new MyOp(OpCodes.Conv_Ovf_I2);
            public static readonly MyOp Conv_Ovf_U2 = new MyOp(OpCodes.Conv_Ovf_U2);
            public static readonly MyOp Conv_Ovf_I4 = new MyOp(OpCodes.Conv_Ovf_I4);
            public static readonly MyOp Conv_Ovf_U4 = new MyOp(OpCodes.Conv_Ovf_U4);
            public static readonly MyOp Conv_Ovf_I8 = new MyOp(OpCodes.Conv_Ovf_I8);
            public static readonly MyOp Conv_Ovf_U8 = new MyOp(OpCodes.Conv_Ovf_U8);
            public static readonly MyOp Refanyval = new MyOp(OpCodes.Refanyval);
            public static readonly MyOp Ckfinite = new MyOp(OpCodes.Ckfinite);
            public static readonly MyOp Mkrefany = new MyOp(OpCodes.Mkrefany);
            public static readonly MyOp Ldtoken = new MyOp(OpCodes.Ldtoken);
            public static readonly MyOp Conv_U2 = new MyOp(OpCodes.Conv_U2);
            public static readonly MyOp Conv_U1 = new MyOp(OpCodes.Conv_U1);
            public static readonly MyOp Conv_I = new MyOp(OpCodes.Conv_I);
            public static readonly MyOp Conv_Ovf_I = new MyOp(OpCodes.Conv_Ovf_I);
            public static readonly MyOp Conv_Ovf_U = new MyOp(OpCodes.Conv_Ovf_U);
            public static readonly MyOp Add_Ovf = new MyOp(OpCodes.Add_Ovf);
            public static readonly MyOp Add_Ovf_Un = new MyOp(OpCodes.Add_Ovf_Un);
            public static readonly MyOp Mul_Ovf = new MyOp(OpCodes.Mul_Ovf);
            public static readonly MyOp Mul_Ovf_Un = new MyOp(OpCodes.Mul_Ovf_Un);
            public static readonly MyOp Sub_Ovf = new MyOp(OpCodes.Sub_Ovf);
            public static readonly MyOp Sub_Ovf_Un = new MyOp(OpCodes.Sub_Ovf_Un);
            public static readonly MyOp Endfinally = new MyOp(OpCodes.Endfinally);
            public static readonly MyOp Leave = new MyOp(OpCodes.Leave);
            public static readonly MyOp Leave_S = new MyOp(OpCodes.Leave_S);
            public static readonly MyOp Stind_I = new MyOp(OpCodes.Stind_I);
            public static readonly MyOp Conv_U = new MyOp(OpCodes.Conv_U);
            public static readonly MyOp Prefix7 = new MyOp(OpCodes.Prefix7);
            public static readonly MyOp Prefix6 = new MyOp(OpCodes.Prefix6);
            public static readonly MyOp Prefix5 = new MyOp(OpCodes.Prefix5);
            public static readonly MyOp Prefix4 = new MyOp(OpCodes.Prefix4);
            public static readonly MyOp Prefix3 = new MyOp(OpCodes.Prefix3);
            public static readonly MyOp Prefix2 = new MyOp(OpCodes.Prefix2);
            public static readonly MyOp Prefix1 = new MyOp(OpCodes.Prefix1);
            public static readonly MyOp Prefixref = new MyOp(OpCodes.Prefixref);
            public static readonly MyOp Arglist = new MyOp(OpCodes.Arglist);
            public static readonly MyOp Ceq = new MyOp(OpCodes.Ceq, "==");
            public static readonly MyOp Cgt = new MyOp(OpCodes.Cgt, ">");
            public static readonly MyOp Cgt_Un = new MyOp(OpCodes.Cgt_Un);
            public static readonly MyOp Clt = new MyOp(OpCodes.Clt, "<");
            public static readonly MyOp Clt_Un = new MyOp(OpCodes.Clt_Un);
            public static readonly MyOp Ldftn = new MyOp(OpCodes.Ldftn);
            public static readonly MyOp Ldvirtftn = new MyOp(OpCodes.Ldvirtftn);
            public static readonly MyOp Ldarg = new MyOp(OpCodes.Ldarg);
            public static readonly MyOp Ldarga = new MyOp(OpCodes.Ldarga);
            public static readonly MyOp Starg = new MyOp(OpCodes.Starg);
            public static readonly MyOp Ldloc = new MyOp(OpCodes.Ldloc);
            public static readonly MyOp Ldloca = new MyOp(OpCodes.Ldloca);
            public static readonly MyOp Stloc = new MyOp(OpCodes.Stloc);
            public static readonly MyOp Localloc = new MyOp(OpCodes.Localloc);
            public static readonly MyOp Endfilter = new MyOp(OpCodes.Endfilter);
            public static readonly MyOp Unaligned = new MyOp(OpCodes.Unaligned);
            public static readonly MyOp Volatile = new MyOp(OpCodes.Volatile);
            public static readonly MyOp Tailcall = new MyOp(OpCodes.Tailcall);
            public static readonly MyOp Initobj = new MyOp(OpCodes.Initobj);
            public static readonly MyOp Constrained = new MyOp(OpCodes.Constrained);
            public static readonly MyOp Cpblk = new MyOp(OpCodes.Cpblk);
            public static readonly MyOp Initblk = new MyOp(OpCodes.Initblk);
            public static readonly MyOp Rethrow = new MyOp(OpCodes.Rethrow);
            public static readonly MyOp Sizeof = new MyOp(OpCodes.Sizeof);
            public static readonly MyOp Refanytype = new MyOp(OpCodes.Refanytype);
            public static readonly MyOp Readonly = new MyOp(OpCodes.Readonly);

            // used internally
            public static readonly MyOp Cge = new MyOp("cge", ">=");
            public static readonly MyOp Cle = new MyOp("cle", "<=");
            public static readonly MyOp Cne = new MyOp("cne", "!=");
        }
    }
}