From 83e2fee71be695b78438e0c9dc50b649a539d0e3 Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Fri, 2 Feb 2018 12:49:40 +0000 Subject: add experimental script engine XMRengine donated by mrieker (DreamNation) And our Melanie. ***DANGER*** ***TESTONLY*** ***disable HG*** dont leave running when not looking... tp/crossing to Xengine will reset scripts. i do see a few issues but should be testable, so we can decide if we should invest more on it. --- .../Api/Implementation/AsyncCommandManager.cs | 28 +- .../ScriptEngine/XMREngine/MMRDelegateCommon.cs | 106 + .../ScriptEngine/XMREngine/MMRIEventHandlers.cs | 77 + .../ScriptEngine/XMREngine/MMRInternalFuncDict.cs | 94 + .../ScriptEngine/XMREngine/MMRScriptBinOpStr.cs | 1559 ++++ .../ScriptEngine/XMREngine/MMRScriptCodeGen.cs | 6262 ++++++++++++++++ .../ScriptEngine/XMREngine/MMRScriptCollector.cs | 2637 +++++++ .../ScriptEngine/XMREngine/MMRScriptCompValu.cs | 1677 +++++ .../ScriptEngine/XMREngine/MMRScriptCompile.cs | 216 + .../ScriptEngine/XMREngine/MMRScriptConsts.cs | 250 + .../ScriptEngine/XMREngine/MMRScriptEventCode.cs | 95 + .../ScriptEngine/XMREngine/MMRScriptInlines.cs | 664 ++ .../ScriptEngine/XMREngine/MMRScriptMyILGen.cs | 81 + .../ScriptEngine/XMREngine/MMRScriptObjCode.cs | 256 + .../ScriptEngine/XMREngine/MMRScriptObjWriter.cs | 947 +++ .../ScriptEngine/XMREngine/MMRScriptReduce.cs | 7719 ++++++++++++++++++++ .../ScriptEngine/XMREngine/MMRScriptTokenize.cs | 1729 +++++ .../ScriptEngine/XMREngine/MMRScriptTypeCast.cs | 819 +++ .../ScriptEngine/XMREngine/MMRScriptVarDict.cs | 371 + .../Region/ScriptEngine/XMREngine/MMRWebRequest.cs | 269 + .../ScriptEngine/XMREngine/MonoTaskletsDummy.cs | 55 + OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs | 534 ++ .../ScriptEngine/XMREngine/XMREngXmrTestLs.cs | 491 ++ OpenSim/Region/ScriptEngine/XMREngine/XMREngine.cs | 1979 +++++ OpenSim/Region/ScriptEngine/XMREngine/XMREvents.cs | 369 + .../ScriptEngine/XMREngine/XMRHeapTracker.cs | 172 + .../ScriptEngine/XMREngine/XMRInstAbstract.cs | 2031 +++++ .../ScriptEngine/XMREngine/XMRInstBackend.cs | 644 ++ .../ScriptEngine/XMREngine/XMRInstCapture.cs | 436 ++ .../Region/ScriptEngine/XMREngine/XMRInstCtor.cs | 878 +++ .../Region/ScriptEngine/XMREngine/XMRInstMain.cs | 230 + .../Region/ScriptEngine/XMREngine/XMRInstMisc.cs | 384 + .../Region/ScriptEngine/XMREngine/XMRInstQueue.cs | 186 + .../Region/ScriptEngine/XMREngine/XMRInstRun.cs | 1051 +++ .../Region/ScriptEngine/XMREngine/XMRInstSorpra.cs | 76 + .../ScriptEngine/XMREngine/XMRObjectTokens.cs | 5476 ++++++++++++++ .../ScriptEngine/XMREngine/XMRSDTypeClObj.cs | 259 + .../ScriptEngine/XMREngine/XMRScriptThread.cs | 262 + .../ScriptEngine/XMREngine/XMRScriptUThread.cs | 557 ++ 39 files changed, 41913 insertions(+), 13 deletions(-) create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRDelegateCommon.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRIEventHandlers.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRInternalFuncDict.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptBinOpStr.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCodeGen.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCollector.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompValu.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompile.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptConsts.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptEventCode.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptInlines.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptMyILGen.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjCode.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjWriter.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptReduce.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTokenize.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTypeCast.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRScriptVarDict.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MMRWebRequest.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/MonoTaskletsDummy.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMREngXmrTestLs.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMREngine.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMREvents.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRHeapTracker.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstAbstract.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstBackend.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstCapture.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstCtor.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstMain.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstMisc.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstQueue.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRInstSorpra.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRObjectTokens.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRSDTypeClObj.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRScriptThread.cs create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs (limited to 'OpenSim/Region') diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/AsyncCommandManager.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/AsyncCommandManager.cs index e01d2e4..5eb3c5c 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/AsyncCommandManager.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/AsyncCommandManager.cs @@ -284,22 +284,24 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api // Remove from: Timers m_Timer[engine].UnSetTimerEvents(localID, itemID); - // Remove from: HttpRequest - IHttpRequestModule iHttpReq = engine.World.RequestModuleInterface(); - if (iHttpReq != null) - iHttpReq.StopHttpRequest(localID, itemID); + if(engine.World != null) + { + // Remove from: HttpRequest + IHttpRequestModule iHttpReq = engine.World.RequestModuleInterface(); + if (iHttpReq != null) + iHttpReq.StopHttpRequest(localID, itemID); - IWorldComm comms = engine.World.RequestModuleInterface(); - if (comms != null) - comms.DeleteListener(itemID); + IWorldComm comms = engine.World.RequestModuleInterface(); + if (comms != null) + comms.DeleteListener(itemID); - IXMLRPC xmlrpc = engine.World.RequestModuleInterface(); - if (xmlrpc != null) - { - xmlrpc.DeleteChannels(itemID); - xmlrpc.CancelSRDRequests(itemID); + IXMLRPC xmlrpc = engine.World.RequestModuleInterface(); + if (xmlrpc != null) + { + xmlrpc.DeleteChannels(itemID); + xmlrpc.CancelSRDRequests(itemID); + } } - // Remove Sensors m_SensorRepeat[engine].UnSetSenseRepeaterEvents(localID, itemID); } diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRDelegateCommon.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRDelegateCommon.cs new file mode 100644 index 0000000..48b665b --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRDelegateCommon.cs @@ -0,0 +1,106 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public class DelegateCommon { + private string sig; // rettype(arg1type,arg2type,...), eg, "void(list,string,integer)" + private Type type; // resultant delegate type + + private static Dictionary delegateCommons = new Dictionary (); + private static Dictionary delegateCommonsBySysType = new Dictionary (); + private static ModuleBuilder delegateModuleBuilder = null; + public static Type[] constructorArgTypes = new Type[] { typeof (object), typeof (IntPtr) }; + + private DelegateCommon () { } + + public static Type GetType (System.Type ret, System.Type[] args, string sig) + { + DelegateCommon dc; + lock (delegateCommons) { + if (!delegateCommons.TryGetValue (sig, out dc)) { + dc = new DelegateCommon (); + dc.sig = sig; + dc.type = CreateDelegateType (sig, ret, args); + delegateCommons.Add (sig, dc); + delegateCommonsBySysType.Add (dc.type, dc); + } + } + return dc.type; + } + + public static Type TryGetType (string sig) + { + DelegateCommon dc; + lock (delegateCommons) { + if (!delegateCommons.TryGetValue (sig, out dc)) dc = null; + } + return (dc == null) ? null : dc.type; + } + + public static string TryGetName (Type t) + { + DelegateCommon dc; + lock (delegateCommons) { + if (!delegateCommonsBySysType.TryGetValue (t, out dc)) dc = null; + } + return (dc == null) ? null : dc.sig; + } + + // http://blog.bittercoder.com/PermaLink,guid,a770377a-b1ad-4590-9145-36381757a52b.aspx + private static Type CreateDelegateType (string name, Type retType, Type[] argTypes) + { + if (delegateModuleBuilder == null) { + AssemblyName assembly = new AssemblyName(); + assembly.Name = "CustomDelegateAssembly"; + AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run); + delegateModuleBuilder = assemblyBuilder.DefineDynamicModule("CustomDelegateModule"); + } + + TypeBuilder typeBuilder = delegateModuleBuilder.DefineType(name, + TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class | + TypeAttributes.AnsiClass | TypeAttributes.AutoClass, typeof (MulticastDelegate)); + + ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor( + MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, + CallingConventions.Standard, constructorArgTypes); + constructorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); + + MethodBuilder methodBuilder = typeBuilder.DefineMethod("Invoke", + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | + MethodAttributes.Virtual, retType, argTypes); + methodBuilder.SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.Runtime); + + return typeBuilder.CreateType(); + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRIEventHandlers.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRIEventHandlers.cs new file mode 100644 index 0000000..440beb3 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRIEventHandlers.cs @@ -0,0 +1,77 @@ +/* + * 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 LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public interface IEventHandlers { + void at_rot_target (int tnum, LSL_Rotation targetrot, LSL_Rotation ourrot); + void at_target (int tnum, LSL_Vector targetpos, LSL_Vector ourpos); + void attach (string id); + void changed (int change); + void collision (int num_detected); + void collision_end (int num_detected); + void collision_start (int num_detected); + void control (string id, int held, int change); + void dataserver (string queryid, string data); + void email (string time, string address, string subj, string message, int num_left); + void http_request (string request_id, string method, string body); + void http_response (string request_id, int status, LSL_List metadata, string body); + void land_collision (LSL_Vector pos); + void land_collision_end (LSL_Vector pos); + void land_collision_start (LSL_Vector pos); + void link_message (int sender_num, int num, string str, string id); + void listen (int channel, string name, string id, string message); + void money (string id, int amount); + void moving_end (); + void moving_start (); + void no_sensor (); + void not_at_rot_target (); + void not_at_target (); + void object_rez (string id); + void on_rez (int start_param); + void remote_data (int event_type, string channel, string message_id, string sender, int idata, string sdata); + void run_time_permissions (int perm); + void sensor (int num_detected); + void state_entry (); + void state_exit (); + void timer (); + void touch (int num_detected); + void touch_start (int num_detected); + void touch_end (int num_detected); + void transaction_result(string id, int success, string data); + void path_update(int type, LSL_List data); + void region_cross(LSL_Vector newpos, LSL_Vector oldpos); + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRInternalFuncDict.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRInternalFuncDict.cs new file mode 100644 index 0000000..c2c01b5 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRInternalFuncDict.cs @@ -0,0 +1,94 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public class InternalFuncDict : VarDict { + + /** + * @brief build dictionary of internal functions from an interface. + * @param iface = interface with function definitions + * @param inclSig = true: catalog by name with arg sig, eg, llSay(integer,string) + * false: catalog by simple name only, eg, state_entry + * @returns dictionary of function definition tokens + */ + public InternalFuncDict (Type iface, bool inclSig) + : base (false) + { + /* + * Loop through list of all methods declared in the interface. + */ + System.Reflection.MethodInfo[] ifaceMethods = iface.GetMethods (); + foreach (System.Reflection.MethodInfo ifaceMethod in ifaceMethods) { + string key = ifaceMethod.Name; + + /* + * Only do ones that begin with lower-case letters... + * as any others can't be referenced by scripts + */ + if ((key[0] < 'a') || (key[0] > 'z')) continue; + + try { + + /* + * Create a corresponding TokenDeclVar struct. + */ + System.Reflection.ParameterInfo[] parameters = ifaceMethod.GetParameters (); + TokenArgDecl argDecl = new TokenArgDecl (null); + for (int i = 0; i < parameters.Length; i++) { + System.Reflection.ParameterInfo param = parameters[i]; + TokenType type = TokenType.FromSysType (null, param.ParameterType); + TokenName name = new TokenName (null, param.Name); + argDecl.AddArg (type, name); + } + TokenDeclVar declFunc = new TokenDeclVar (null, null, null); + declFunc.name = new TokenName (null, key); + declFunc.retType = TokenType.FromSysType (null, ifaceMethod.ReturnType); + declFunc.argDecl = argDecl; + + /* + * Add the TokenDeclVar struct to the dictionary. + */ + this.AddEntry (declFunc); + } catch (Exception except) { + + string msg = except.ToString (); + int i = msg.IndexOf ("\n"); + if (i > 0) msg = msg.Substring (0, i); + Console.WriteLine ("InternalFuncDict*: {0}: {1}", key, msg); + + ///??? IGNORE ANY THAT FAIL - LIKE UNRECOGNIZED TYPE ???/// + } + } + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptBinOpStr.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptBinOpStr.cs new file mode 100644 index 0000000..f8c9b22 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptBinOpStr.cs @@ -0,0 +1,1559 @@ +/* + * 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 OpenSim.Region.ScriptEngine.XMREngine; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Text.RegularExpressions; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + /** + * @brief This class is used to catalog the code emit routines based on a key string + * The key string has the two types (eg, "integer", "rotation") and the operator (eg, "*", "!=") + */ + public delegate void BinOpStrEmitBO (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result); + public class BinOpStr { + public static readonly Dictionary defined = DefineBinOps (); + + public Type outtype; // type of result of computation + public BinOpStrEmitBO emitBO; // how to compute result + public bool rmwOK; // is the = form valid? + + public BinOpStr (Type outtype, BinOpStrEmitBO emitBO) + { + this.outtype = outtype; + this.emitBO = emitBO; + this.rmwOK = false; + } + + public BinOpStr (Type outtype, BinOpStrEmitBO emitBO, bool rmwOK) + { + this.outtype = outtype; + this.emitBO = emitBO; + this.rmwOK = rmwOK; + } + + private static TokenTypeBool tokenTypeBool = new TokenTypeBool (null); + private static TokenTypeChar tokenTypeChar = new TokenTypeChar (null); + private static TokenTypeFloat tokenTypeFloat = new TokenTypeFloat (null); + private static TokenTypeInt tokenTypeInt = new TokenTypeInt (null); + private static TokenTypeList tokenTypeList = new TokenTypeList (null); + private static TokenTypeRot tokenTypeRot = new TokenTypeRot (null); + private static TokenTypeStr tokenTypeStr = new TokenTypeStr (null); + private static TokenTypeVec tokenTypeVec = new TokenTypeVec (null); + + private static MethodInfo stringAddStringMethInfo = ScriptCodeGen.GetStaticMethod (typeof (string), "Concat", new Type[] { typeof (string), typeof (string) }); + private static MethodInfo stringCmpStringMethInfo = ScriptCodeGen.GetStaticMethod (typeof (string), "Compare", new Type[] { typeof (string), typeof (string) }); + + private static MethodInfo infoMethListAddFloat = GetBinOpsMethod ("MethListAddFloat", new Type[] { typeof (LSL_List), typeof (double) }); + private static MethodInfo infoMethListAddInt = GetBinOpsMethod ("MethListAddInt", new Type[] { typeof (LSL_List), typeof (int) }); + private static MethodInfo infoMethListAddKey = GetBinOpsMethod ("MethListAddKey", new Type[] { typeof (LSL_List), typeof (string) }); + private static MethodInfo infoMethListAddRot = GetBinOpsMethod ("MethListAddRot", new Type[] { typeof (LSL_List), typeof (LSL_Rotation) }); + private static MethodInfo infoMethListAddStr = GetBinOpsMethod ("MethListAddStr", new Type[] { typeof (LSL_List), typeof (string) }); + private static MethodInfo infoMethListAddVec = GetBinOpsMethod ("MethListAddVec", new Type[] { typeof (LSL_List), typeof (LSL_Vector) }); + private static MethodInfo infoMethListAddList = GetBinOpsMethod ("MethListAddList", new Type[] { typeof (LSL_List), typeof (LSL_List) }); + private static MethodInfo infoMethFloatAddList = GetBinOpsMethod ("MethFloatAddList", new Type[] { typeof (double), typeof (LSL_List) }); + private static MethodInfo infoMethIntAddList = GetBinOpsMethod ("MethIntAddList", new Type[] { typeof (int), typeof (LSL_List) }); + private static MethodInfo infoMethKeyAddList = GetBinOpsMethod ("MethKeyAddList", new Type[] { typeof (string), typeof (LSL_List) }); + private static MethodInfo infoMethRotAddList = GetBinOpsMethod ("MethRotAddList", new Type[] { typeof (LSL_Rotation), typeof (LSL_List) }); + private static MethodInfo infoMethStrAddList = GetBinOpsMethod ("MethStrAddList", new Type[] { typeof (string), typeof (LSL_List) }); + private static MethodInfo infoMethVecAddList = GetBinOpsMethod ("MethVecAddList", new Type[] { typeof (LSL_Vector), typeof (LSL_List) }); + private static MethodInfo infoMethListEqList = GetBinOpsMethod ("MethListEqList", new Type[] { typeof (LSL_List), typeof (LSL_List) }); + private static MethodInfo infoMethListNeList = GetBinOpsMethod ("MethListNeList", new Type[] { typeof (LSL_List), typeof (LSL_List) }); + private static MethodInfo infoMethRotEqRot = GetBinOpsMethod ("MethRotEqRot", new Type[] { typeof (LSL_Rotation), typeof (LSL_Rotation) }); + private static MethodInfo infoMethRotNeRot = GetBinOpsMethod ("MethRotNeRot", new Type[] { typeof (LSL_Rotation), typeof (LSL_Rotation) }); + private static MethodInfo infoMethRotAddRot = GetBinOpsMethod ("MethRotAddRot", new Type[] { typeof (LSL_Rotation), typeof (LSL_Rotation) }); + private static MethodInfo infoMethRotSubRot = GetBinOpsMethod ("MethRotSubRot", new Type[] { typeof (LSL_Rotation), typeof (LSL_Rotation) }); + private static MethodInfo infoMethRotMulRot = GetBinOpsMethod ("MethRotMulRot", new Type[] { typeof (LSL_Rotation), typeof (LSL_Rotation) }); + private static MethodInfo infoMethRotDivRot = GetBinOpsMethod ("MethRotDivRot", new Type[] { typeof (LSL_Rotation), typeof (LSL_Rotation) }); + private static MethodInfo infoMethVecEqVec = GetBinOpsMethod ("MethVecEqVec", new Type[] { typeof (LSL_Vector), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecNeVec = GetBinOpsMethod ("MethVecNeVec", new Type[] { typeof (LSL_Vector), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecAddVec = GetBinOpsMethod ("MethVecAddVec", new Type[] { typeof (LSL_Vector), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecSubVec = GetBinOpsMethod ("MethVecSubVec", new Type[] { typeof (LSL_Vector), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecMulVec = GetBinOpsMethod ("MethVecMulVec", new Type[] { typeof (LSL_Vector), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecModVec = GetBinOpsMethod ("MethVecModVec", new Type[] { typeof (LSL_Vector), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecMulFloat = GetBinOpsMethod ("MethVecMulFloat", new Type[] { typeof (LSL_Vector), typeof (double) }); + private static MethodInfo infoMethFloatMulVec = GetBinOpsMethod ("MethFloatMulVec", new Type[] { typeof (double), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecDivFloat = GetBinOpsMethod ("MethVecDivFloat", new Type[] { typeof (LSL_Vector), typeof (double) }); + private static MethodInfo infoMethVecMulInt = GetBinOpsMethod ("MethVecMulInt", new Type[] { typeof (LSL_Vector), typeof (int) }); + private static MethodInfo infoMethIntMulVec = GetBinOpsMethod ("MethIntMulVec", new Type[] { typeof (int), typeof (LSL_Vector) }); + private static MethodInfo infoMethVecDivInt = GetBinOpsMethod ("MethVecDivInt", new Type[] { typeof (LSL_Vector), typeof (int) }); + private static MethodInfo infoMethVecMulRot = GetBinOpsMethod ("MethVecMulRot", new Type[] { typeof (LSL_Vector), typeof (LSL_Rotation) }); + private static MethodInfo infoMethVecDivRot = GetBinOpsMethod ("MethVecDivRot", new Type[] { typeof (LSL_Vector), typeof (LSL_Rotation) }); + + private static MethodInfo GetBinOpsMethod (string name, Type[] types) + { + return ScriptCodeGen.GetStaticMethod (typeof (BinOpStr), name, types); + } + + /** + * @brief Create a dictionary for processing binary operators. + * This tells us, for a given type, an operator and another type, + * is the operation permitted, and if so, what is the type of the result? + * The key is , + * where and are strings returned by (TokenType...).ToString() + * and is string returned by (TokenKw...).ToString() + * The value is a BinOpStr struct giving the resultant type and a method to generate the code. + */ + private static Dictionary DefineBinOps () + { + Dictionary bos = new Dictionary (); + + string[] booltypes = new string[] { "bool", "char", "float", "integer", "key", "list", "string" }; + + /* + * Get the && and || all out of the way... + * Simply cast their left and right operands to boolean then process. + */ + for (int i = 0; i < booltypes.Length; i ++) { + for (int j = 0; j < booltypes.Length; j ++) { + bos.Add (booltypes[i] + "&&" + booltypes[j], + new BinOpStr (typeof (bool), BinOpStrAndAnd)); + bos.Add (booltypes[i] + "||" + booltypes[j], + new BinOpStr (typeof (bool), BinOpStrOrOr)); + } + } + + /* + * Pound through all the other combinations we support. + */ + + // boolean : somethingelse + DefineBinOpsBoolX (bos, "bool"); + DefineBinOpsBoolX (bos, "char"); + DefineBinOpsBoolX (bos, "float"); + DefineBinOpsBoolX (bos, "integer"); + DefineBinOpsBoolX (bos, "key"); + DefineBinOpsBoolX (bos, "list"); + DefineBinOpsBoolX (bos, "string"); + + // stuff with chars + DefineBinOpsChar (bos); + + // somethingelse : boolean + DefineBinOpsXBool (bos, "char"); + DefineBinOpsXBool (bos, "float"); + DefineBinOpsXBool (bos, "integer"); + DefineBinOpsXBool (bos, "key"); + DefineBinOpsXBool (bos, "list"); + DefineBinOpsXBool (bos, "string"); + + // float : somethingelse + DefineBinOpsFloatX (bos, "float"); + DefineBinOpsFloatX (bos, "integer"); + + // integer : float + DefineBinOpsXFloat (bos, "integer"); + + // anything else with integers + DefineBinOpsInteger (bos); + + // key : somethingelse + DefineBinOpsKeyX (bos, "key"); + DefineBinOpsKeyX (bos, "string"); + + // string : key + DefineBinOpsXKey (bos, "string"); + + // things with lists + DefineBinOpsList (bos); + + // things with rotations + DefineBinOpsRotation (bos); + + // things with strings + DefineBinOpsString (bos); + + // things with vectors + DefineBinOpsVector (bos); + + // Contrary to some beliefs, scripts do things like string+integer and integer+string + bos.Add ("bool+string", new BinOpStr (typeof (string), BinOpStrStrAddStr)); + bos.Add ("char+string", new BinOpStr (typeof (string), BinOpStrStrAddStr)); + bos.Add ("float+string", new BinOpStr (typeof (string), BinOpStrStrAddStr)); + bos.Add ("integer+string", new BinOpStr (typeof (string), BinOpStrStrAddStr)); + bos.Add ("string+bool", new BinOpStr (typeof (string), BinOpStrStrAddStr, true)); + bos.Add ("string+char", new BinOpStr (typeof (string), BinOpStrStrAddStr, true)); + bos.Add ("string+float", new BinOpStr (typeof (string), BinOpStrStrAddStr, true)); + bos.Add ("string+integer", new BinOpStr (typeof (string), BinOpStrStrAddStr, true)); + + // Now for our final slight-of-hand, we're going to scan through all those. + // And wherever we see an 'integer' in the key, we are going to make another + // entry with 'bool', as we want to accept a bool as having a value of 0 or 1. + // This lets us do things like 3.5 * (x > 0). + + Dictionary bos2 = new Dictionary (); + foreach (KeyValuePair kvp in bos) { + string key = kvp.Key; + BinOpStr val = kvp.Value; + bos2.Add (key, val); + } + Regex wordReg = new Regex("\\w+"); + Regex opReg = new Regex("\\W+"); + foreach (KeyValuePair kvp in bos) { + string key = kvp.Key; + BinOpStr val = kvp.Value; + MatchCollection matches = wordReg.Matches(key); + if (matches.Count != 2) + continue; + Match opM = opReg.Match(key); + if (!opM.Success) + continue; + string left = matches[0].Value; + string right = matches[1].Value; + string op = opM.Value; + string key2; + if (left == "integer" && right == "integer") + { + key2 = "bool"+op+"bool"; + if (!bos2.ContainsKey (key2)) bos2.Add (key2, val); + key2 = "bool"+op+"integer"; + if (!bos2.ContainsKey (key2)) bos2.Add (key2, val); + key2 = "integer"+op+"bool"; + if (!bos2.ContainsKey (key2)) bos2.Add (key2, val); + } + else + { + key2 = key.Replace("integer", "bool"); + if (!bos2.ContainsKey (key2)) bos2.Add (key2, val); + } + } + return bos2; + } + + private static void DefineBinOpsBoolX (Dictionary bos, string x) + { + bos.Add ("bool|" + x, new BinOpStr (typeof (int), BinOpStrBoolOrX)); + bos.Add ("bool^" + x, new BinOpStr (typeof (int), BinOpStrBoolXorX)); + bos.Add ("bool&" + x, new BinOpStr (typeof (int), BinOpStrBoolAndX)); + bos.Add ("bool==" + x, new BinOpStr (typeof (bool), BinOpStrBoolEqX)); + bos.Add ("bool!=" + x, new BinOpStr (typeof (bool), BinOpStrBoolNeX)); + } + + private static void DefineBinOpsXBool (Dictionary bos, string x) + { + bos.Add (x + "|bool", new BinOpStr (typeof (int), BinOpStrBoolOrX)); + bos.Add (x + "^bool", new BinOpStr (typeof (int), BinOpStrBoolXorX)); + bos.Add (x + "&bool", new BinOpStr (typeof (int), BinOpStrBoolAndX)); + bos.Add (x + "==bool", new BinOpStr (typeof (bool), BinOpStrBoolEqX)); + bos.Add (x + "!=bool", new BinOpStr (typeof (bool), BinOpStrBoolNeX)); + } + + private static void DefineBinOpsFloatX (Dictionary bos, string x) + { + bos.Add ("float==" + x, new BinOpStr (typeof (bool), BinOpStrFloatEqX)); + bos.Add ("float!=" + x, new BinOpStr (typeof (bool), BinOpStrFloatNeX)); + bos.Add ("float<" + x, new BinOpStr (typeof (bool), BinOpStrFloatLtX)); + bos.Add ("float<=" + x, new BinOpStr (typeof (bool), BinOpStrFloatLeX)); + bos.Add ("float>" + x, new BinOpStr (typeof (bool), BinOpStrFloatGtX)); + bos.Add ("float>=" + x, new BinOpStr (typeof (bool), BinOpStrFloatGeX)); + bos.Add ("float+" + x, new BinOpStr (typeof (double), BinOpStrFloatAddX, true)); + bos.Add ("float-" + x, new BinOpStr (typeof (double), BinOpStrFloatSubX, true)); + bos.Add ("float*" + x, new BinOpStr (typeof (double), BinOpStrFloatMulX, true)); + bos.Add ("float/" + x, new BinOpStr (typeof (double), BinOpStrFloatDivX, true)); + bos.Add ("float%" + x, new BinOpStr (typeof (double), BinOpStrFloatModX, true)); + } + + private static void DefineBinOpsXFloat (Dictionary bos, string x) + { + bos.Add (x + "==float", new BinOpStr (typeof (bool), BinOpStrXEqFloat)); + bos.Add (x + "!=float", new BinOpStr (typeof (bool), BinOpStrXNeFloat)); + bos.Add (x + "float", new BinOpStr (typeof (bool), BinOpStrXGtFloat)); + bos.Add (x + ">=float", new BinOpStr (typeof (bool), BinOpStrXGeFloat)); + bos.Add (x + "+float", new BinOpStr (typeof (double), BinOpStrXAddFloat, true)); + bos.Add (x + "-float", new BinOpStr (typeof (double), BinOpStrXSubFloat, true)); + bos.Add (x + "*float", new BinOpStr (typeof (double), BinOpStrXMulFloat, true)); + bos.Add (x + "/float", new BinOpStr (typeof (double), BinOpStrXDivFloat, true)); + bos.Add (x + "%float", new BinOpStr (typeof (double), BinOpStrXModFloat, true)); + } + + private static void DefineBinOpsChar (Dictionary bos) + { + bos.Add ("char==char", new BinOpStr (typeof (bool), BinOpStrCharEqChar)); + bos.Add ("char!=char", new BinOpStr (typeof (bool), BinOpStrCharNeChar)); + bos.Add ("charchar", new BinOpStr (typeof (bool), BinOpStrCharGtChar)); + bos.Add ("char>=char", new BinOpStr (typeof (bool), BinOpStrCharGeChar)); + bos.Add ("char+integer", new BinOpStr (typeof (char), BinOpStrCharAddInt, true)); + bos.Add ("char-integer", new BinOpStr (typeof (char), BinOpStrCharSubInt, true)); + bos.Add ("char-char", new BinOpStr (typeof (int), BinOpStrCharSubChar)); + } + + private static void DefineBinOpsInteger (Dictionary bos) + { + bos.Add ("integer==integer", new BinOpStr (typeof (bool), BinOpStrIntEqInt)); + bos.Add ("integer!=integer", new BinOpStr (typeof (bool), BinOpStrIntNeInt)); + bos.Add ("integerinteger", new BinOpStr (typeof (bool), BinOpStrIntGtInt)); + bos.Add ("integer>=integer", new BinOpStr (typeof (bool), BinOpStrIntGeInt)); + bos.Add ("integer|integer", new BinOpStr (typeof (int), BinOpStrIntOrInt, true)); + bos.Add ("integer^integer", new BinOpStr (typeof (int), BinOpStrIntXorInt, true)); + bos.Add ("integer&integer", new BinOpStr (typeof (int), BinOpStrIntAndInt, true)); + bos.Add ("integer+integer", new BinOpStr (typeof (int), BinOpStrIntAddInt, true)); + bos.Add ("integer-integer", new BinOpStr (typeof (int), BinOpStrIntSubInt, true)); + bos.Add ("integer*integer", new BinOpStr (typeof (int), BinOpStrIntMulInt, true)); + bos.Add ("integer/integer", new BinOpStr (typeof (int), BinOpStrIntDivInt, true)); + bos.Add ("integer%integer", new BinOpStr (typeof (int), BinOpStrIntModInt, true)); + bos.Add ("integer<>integer", new BinOpStr (typeof (int), BinOpStrIntShrInt, true)); + } + + private static void DefineBinOpsKeyX (Dictionary bos, string x) + { + bos.Add ("key==" + x, new BinOpStr (typeof (bool), BinOpStrKeyEqX)); + bos.Add ("key!=" + x, new BinOpStr (typeof (bool), BinOpStrKeyNeX)); + } + + private static void DefineBinOpsXKey (Dictionary bos, string x) + { + bos.Add (x + "==key", new BinOpStr (typeof (bool), BinOpStrKeyEqX)); + bos.Add (x + "!=key", new BinOpStr (typeof (bool), BinOpStrKeyNeX)); + } + + private static void DefineBinOpsList (Dictionary bos) + { + bos.Add ("list+float", new BinOpStr (typeof (LSL_List), BinOpStrListAddFloat, true)); + bos.Add ("list+integer", new BinOpStr (typeof (LSL_List), BinOpStrListAddInt, true)); + bos.Add ("list+key", new BinOpStr (typeof (LSL_List), BinOpStrListAddKey, true)); + bos.Add ("list+list", new BinOpStr (typeof (LSL_List), BinOpStrListAddList, true)); + bos.Add ("list+rotation", new BinOpStr (typeof (LSL_List), BinOpStrListAddRot, true)); + bos.Add ("list+string", new BinOpStr (typeof (LSL_List), BinOpStrListAddStr, true)); + bos.Add ("list+vector", new BinOpStr (typeof (LSL_List), BinOpStrListAddVec, true)); + + bos.Add ("float+list", new BinOpStr (typeof (LSL_List), BinOpStrFloatAddList)); + bos.Add ("integer+list", new BinOpStr (typeof (LSL_List), BinOpStrIntAddList)); + bos.Add ("key+list", new BinOpStr (typeof (LSL_List), BinOpStrKeyAddList)); + bos.Add ("rotation+list", new BinOpStr (typeof (LSL_List), BinOpStrRotAddList)); + bos.Add ("string+list", new BinOpStr (typeof (LSL_List), BinOpStrStrAddList)); + bos.Add ("vector+list", new BinOpStr (typeof (LSL_List), BinOpStrVecAddList)); + + bos.Add ("list==list", new BinOpStr (typeof (bool), BinOpStrListEqList)); + bos.Add ("list!=list", new BinOpStr (typeof (int), BinOpStrListNeList)); + } + + // all operations allowed by LSL_Rotation definition + private static void DefineBinOpsRotation (Dictionary bos) + { + bos.Add ("rotation==rotation", new BinOpStr (typeof (bool), BinOpStrRotEqRot)); + bos.Add ("rotation!=rotation", new BinOpStr (typeof (bool), BinOpStrRotNeRot)); + bos.Add ("rotation+rotation", new BinOpStr (typeof (LSL_Rotation), BinOpStrRotAddRot, true)); + bos.Add ("rotation-rotation", new BinOpStr (typeof (LSL_Rotation), BinOpStrRotSubRot, true)); + bos.Add ("rotation*rotation", new BinOpStr (typeof (LSL_Rotation), BinOpStrRotMulRot, true)); + bos.Add ("rotation/rotation", new BinOpStr (typeof (LSL_Rotation), BinOpStrRotDivRot, true)); + } + + private static void DefineBinOpsString (Dictionary bos) + { + bos.Add ("string==string", new BinOpStr (typeof (bool), BinOpStrStrEqStr)); + bos.Add ("string!=string", new BinOpStr (typeof (bool), BinOpStrStrNeStr)); + bos.Add ("stringstring", new BinOpStr (typeof (bool), BinOpStrStrGtStr)); + bos.Add ("string>=string", new BinOpStr (typeof (bool), BinOpStrStrGeStr)); + bos.Add ("string+string", new BinOpStr (typeof (string), BinOpStrStrAddStr, true)); + } + + // all operations allowed by LSL_Vector definition + private static void DefineBinOpsVector (Dictionary bos) + { + bos.Add ("vector==vector", new BinOpStr (typeof (bool), BinOpStrVecEqVec)); + bos.Add ("vector!=vector", new BinOpStr (typeof (bool), BinOpStrVecNeVec)); + bos.Add ("vector+vector", new BinOpStr (typeof (LSL_Vector), BinOpStrVecAddVec, true)); + bos.Add ("vector-vector", new BinOpStr (typeof (LSL_Vector), BinOpStrVecSubVec, true)); + bos.Add ("vector*vector", new BinOpStr (typeof (double), BinOpStrVecMulVec)); + bos.Add ("vector%vector", new BinOpStr (typeof (LSL_Vector), BinOpStrVecModVec, true)); + + bos.Add ("vector*float", new BinOpStr (typeof (LSL_Vector), BinOpStrVecMulFloat, true)); + bos.Add ("float*vector", new BinOpStr (typeof (LSL_Vector), BinOpStrFloatMulVec)); + bos.Add ("vector/float", new BinOpStr (typeof (LSL_Vector), BinOpStrVecDivFloat, true)); + + bos.Add ("vector*integer", new BinOpStr (typeof (LSL_Vector), BinOpStrVecMulInt, true)); + bos.Add ("integer*vector", new BinOpStr (typeof (LSL_Vector), BinOpStrIntMulVec)); + bos.Add ("vector/integer", new BinOpStr (typeof (LSL_Vector), BinOpStrVecDivInt, true)); + + bos.Add ("vector*rotation", new BinOpStr (typeof (LSL_Vector), BinOpStrVecMulRot, true)); + bos.Add ("vector/rotation", new BinOpStr (typeof (LSL_Vector), BinOpStrVecDivRot, true)); + } + + /** + * @brief These methods actually emit the code to perform the arithmetic. + * @param scg = what script we are compiling + * @param left = left-hand operand location in memory (type as given by BinOpStr entry) + * @param right = right-hand operand location in memory (type as given by BinOpStr entry) + * @param result = result location in memory (type as given by BinOpStr entry) + */ + private static void BinOpStrAndAnd (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeBool); + right.PushVal (scg, errorAt, tokenTypeBool); + scg.ilGen.Emit (errorAt, OpCodes.And); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrOrOr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeBool); + right.PushVal (scg, errorAt, tokenTypeBool); + scg.ilGen.Emit (errorAt, OpCodes.Or); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrBoolOrX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Or); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrBoolXorX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrBoolAndX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.And); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrBoolEqX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeBool); + right.PushVal (scg, errorAt, tokenTypeBool); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrBoolNeX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeBool); + right.PushVal (scg, errorAt, tokenTypeBool); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrFloatEqX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrFloatNeX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrFloatLtX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrFloatLeX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrFloatGtX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrFloatGeX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrFloatAddX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Add); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrFloatSubX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Sub); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrFloatMulX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Mul); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrFloatDivX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Div); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrFloatModX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Rem); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrXEqFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrXNeFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrXLtFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrXLeFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrXGtFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrXGeFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrXAddFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Add); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrXSubFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Sub); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrXMulFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Mul); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrXDivFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Div); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrXModFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Rem); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrCharEqChar (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeChar); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrCharNeChar (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeChar); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrCharLtChar (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeChar); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrCharLeChar (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeChar); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrCharGtChar (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeChar); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrCharGeChar (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeChar); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrCharAddInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Add); + result.PopPost (scg, errorAt, tokenTypeChar); + } + + private static void BinOpStrCharSubInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Sub); + result.PopPost (scg, errorAt, tokenTypeChar); + } + + private static void BinOpStrCharSubChar (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeChar); + right.PushVal (scg, errorAt, tokenTypeChar); + scg.ilGen.Emit (errorAt, OpCodes.Sub); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntEqInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrIntNeInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrIntLtInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrIntLeInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrIntGtInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrIntGeInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrIntOrInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Or); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntXorInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntAndInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.And); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntAddInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Add); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntSubInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Sub); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntMulInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Mul); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntDivInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + // note that we must allow 0x800000/-1 -> 0x80000000 for lslangtest1.lsl + // so sign-extend the operands to 64-bit then divide and truncate result + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Conv_I8); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Conv_I8); + scg.ilGen.Emit (errorAt, OpCodes.Div); + scg.ilGen.Emit (errorAt, OpCodes.Conv_I4); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntModInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + // note that we must allow 0x800000%-1 -> 0 for lslangtest1.lsl + // so sign-extend the operands to 64-bit then mod and truncate result + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Conv_I8); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Conv_I8); + scg.ilGen.Emit (errorAt, OpCodes.Rem); + scg.ilGen.Emit (errorAt, OpCodes.Conv_I4); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntShlInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Shl); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrIntShrInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Shr); + result.PopPost (scg, errorAt, tokenTypeInt); + } + + private static void BinOpStrKeyEqX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrKeyNeX (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrListAddFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListAddFloat); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrListAddInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListAddInt); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrListAddKey (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListAddKey); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrListAddList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListAddList); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrListAddRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListAddRot); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrListAddStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListAddStr); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrListAddVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListAddVec); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrFloatAddList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethFloatAddList); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrIntAddList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethIntAddList); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrKeyAddList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethKeyAddList); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrRotAddList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeRot); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethRotAddList); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrStrAddList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethStrAddList); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrVecAddList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecAddList); + result.PopPost (scg, errorAt, tokenTypeList); + } + + private static void BinOpStrListEqList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListEqList); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrListNeList (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeList); + right.PushVal (scg, errorAt, tokenTypeList); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethListNeList); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrRotEqRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeRot); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethRotEqRot); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrRotNeRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeRot); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethRotNeRot); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrRotAddRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeRot); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethRotAddRot); + result.PopPost (scg, errorAt, tokenTypeRot); + } + + private static void BinOpStrRotSubRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeRot); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethRotSubRot); + result.PopPost (scg, errorAt, tokenTypeRot); + } + + private static void BinOpStrRotMulRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeRot); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethRotMulRot); + result.PopPost (scg, errorAt, tokenTypeRot); + } + + private static void BinOpStrRotDivRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeRot); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethRotDivRot); + result.PopPost (scg, errorAt, tokenTypeRot); + } + + private static void BinOpStrStrEqStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrStrNeStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrStrLtStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrStrLeStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Clt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrStrGtStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrStrGeStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringCmpStringMethInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_M1); + scg.ilGen.Emit (errorAt, OpCodes.Cgt); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + // Called by many type combinations so both operands need to be cast to strings + private static void BinOpStrStrAddStr (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeStr); + right.PushVal (scg, errorAt, tokenTypeStr); + scg.ilGen.Emit (errorAt, OpCodes.Call, stringAddStringMethInfo); + result.PopPost (scg, errorAt, tokenTypeStr); + } + + private static void BinOpStrVecEqVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecEqVec); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrVecNeVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecNeVec); + result.PopPost (scg, errorAt, tokenTypeBool); + } + + private static void BinOpStrVecAddVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecAddVec); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecSubVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecSubVec); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecMulVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecMulVec); + result.PopPost (scg, errorAt, tokenTypeFloat); + } + + private static void BinOpStrVecModVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecModVec); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecMulFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecMulFloat); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrFloatMulVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeFloat); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethFloatMulVec); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecDivFloat (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeFloat); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecDivFloat); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecMulInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecMulInt); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrIntMulVec (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeInt); + right.PushVal (scg, errorAt, tokenTypeVec); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethIntMulVec); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecDivInt (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeInt); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecDivInt); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecMulRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecMulRot); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + private static void BinOpStrVecDivRot (ScriptCodeGen scg, Token errorAt, CompValu left, CompValu right, CompValu result) + { + result.PopPre (scg, errorAt); + left.PushVal (scg, errorAt, tokenTypeVec); + right.PushVal (scg, errorAt, tokenTypeRot); + scg.ilGen.Emit (errorAt, OpCodes.Call, infoMethVecDivRot); + result.PopPost (scg, errorAt, tokenTypeVec); + } + + /** + * @brief These methods are called at runtime as helpers. + * Needed to pick up functionality defined by overloaded operators of LSL_ types. + * They need to be marked public or runtime says they are inaccessible. + */ + public static LSL_List MethListAddFloat (LSL_List left, double right) + { + return MethListAddObj (left, new LSL_Float (right)); + } + public static LSL_List MethListAddInt (LSL_List left, int right) + { + return MethListAddObj (left, new LSL_Integer (right)); + } + public static LSL_List MethListAddKey (LSL_List left, string right) + { + return MethListAddObj (left, new LSL_Key (right)); + } + public static LSL_List MethListAddRot (LSL_List left, LSL_Rotation right) + { + return MethListAddObj (left, right); + } + public static LSL_List MethListAddStr (LSL_List left, string right) + { + return MethListAddObj (left, new LSL_String (right)); + } + public static LSL_List MethListAddVec (LSL_List left, LSL_Vector right) + { + return MethListAddObj (left, right); + } + public static LSL_List MethListAddObj (LSL_List left, object right) + { + int oldlen = left.Length; + object[] newarr = new object[oldlen+1]; + Array.Copy (left.Data, newarr, oldlen); + newarr[oldlen] = right; + return new LSL_List (newarr); + } + + public static LSL_List MethListAddList (LSL_List left, LSL_List right) + { + int leftlen = left.Length; + int ritelen = right.Length; + object[] newarr = new object[leftlen+ritelen]; + Array.Copy (left.Data, newarr, leftlen); + Array.Copy (right.Data, 0, newarr, leftlen, ritelen); + return new LSL_List (newarr); + } + + public static LSL_List MethFloatAddList (double left, LSL_List right) + { + return MethObjAddList (new LSL_Float (left), right); + } + public static LSL_List MethIntAddList (int left, LSL_List right) + { + return MethObjAddList (new LSL_Integer (left), right); + } + public static LSL_List MethKeyAddList (string left, LSL_List right) + { + return MethObjAddList (new LSL_Key (left), right); + } + public static LSL_List MethRotAddList (LSL_Rotation left, LSL_List right) + { + return MethObjAddList (left, right); + } + public static LSL_List MethStrAddList (string left, LSL_List right) + { + return MethObjAddList (new LSL_String (left), right); + } + public static LSL_List MethVecAddList (LSL_Vector left, LSL_List right) + { + return MethObjAddList (left, right); + } + public static LSL_List MethObjAddList (object left, LSL_List right) + { + int oldlen = right.Length; + object[] newarr = new object[oldlen+1]; + newarr[0] = left; + Array.Copy (right.Data, 0, newarr, 1, oldlen); + return new LSL_List (newarr); + } + + public static bool MethListEqList (LSL_List left, LSL_List right) + { + return left == right; + } + + // According to http://wiki.secondlife.com/wiki/LlGetListLength + // jackassed LSL allows 'somelist != []' to get the length of a list + public static int MethListNeList (LSL_List left, LSL_List right) + { + int leftlen = left.Length; + int ritelen = right.Length; + return leftlen - ritelen; + } + + public static bool MethRotEqRot (LSL_Rotation left, LSL_Rotation right) + { + return left == right; + } + + public static bool MethRotNeRot (LSL_Rotation left, LSL_Rotation right) + { + return left != right; + } + + public static LSL_Rotation MethRotAddRot (LSL_Rotation left, LSL_Rotation right) + { + return left + right; + } + + public static LSL_Rotation MethRotSubRot (LSL_Rotation left, LSL_Rotation right) + { + return left - right; + } + + public static LSL_Rotation MethRotMulRot (LSL_Rotation left, LSL_Rotation right) + { + return left * right; + } + + public static LSL_Rotation MethRotDivRot (LSL_Rotation left, LSL_Rotation right) + { + return left / right; + } + + public static bool MethVecEqVec (LSL_Vector left, LSL_Vector right) + { + return left == right; + } + + public static bool MethVecNeVec (LSL_Vector left, LSL_Vector right) + { + return left != right; + } + + public static LSL_Vector MethVecAddVec (LSL_Vector left, LSL_Vector right) + { + return left + right; + } + + public static LSL_Vector MethVecSubVec (LSL_Vector left, LSL_Vector right) + { + return left - right; + } + + public static double MethVecMulVec (LSL_Vector left, LSL_Vector right) + { + return (double)(left * right).value; + } + + public static LSL_Vector MethVecModVec (LSL_Vector left, LSL_Vector right) + { + return left % right; + } + + public static LSL_Vector MethVecMulFloat (LSL_Vector left, double right) + { + return left * right; + } + + public static LSL_Vector MethFloatMulVec (double left, LSL_Vector right) + { + return left * right; + } + + public static LSL_Vector MethVecDivFloat (LSL_Vector left, double right) + { + return left / right; + } + + public static LSL_Vector MethVecMulInt (LSL_Vector left, int right) + { + return left * right; + } + + public static LSL_Vector MethIntMulVec (int left, LSL_Vector right) + { + return left * right; + } + + public static LSL_Vector MethVecDivInt (LSL_Vector left, int right) + { + return left / right; + } + + public static LSL_Vector MethVecMulRot (LSL_Vector left, LSL_Rotation right) + { + return left * right; + } + + public static LSL_Vector MethVecDivRot (LSL_Vector left, LSL_Rotation right) + { + return left / right; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCodeGen.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCodeGen.cs new file mode 100644 index 0000000..5219fa8 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCodeGen.cs @@ -0,0 +1,6262 @@ +/* + * 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 OpenSim.Region.ScriptEngine.XMREngine; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +/** + * @brief translate a reduced script token into corresponding CIL code. + * The single script token contains a tokenized and textured version of the whole script file. + */ + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public interface IScriptCodeGen + { + ScriptMyILGen ilGen { get; } // the output instruction stream + void ErrorMsg (Token token, string message); + void PushDefaultValue (TokenType type); + void PushXMRInst (); + } + + public class ScriptCodeGen : IScriptCodeGen + { + private static readonly bool DEBUG_STACKCAPRES = false; + private static readonly bool DEBUG_TRYSTMT = false; + + public static readonly string OBJECT_CODE_MAGIC = "XMRObjectCode"; + public static int COMPILED_VERSION_VALUE = 20; // incremented when compiler changes for compatibility testing + + public static readonly int CALL_FRAME_MEMUSE = 64; + public static readonly int STRING_LEN_TO_MEMUSE = 2; + + public static Type xmrInstSuperType = null; // typeof whatever is actually malloc'd for script instances + // - must inherit from XMRInstAbstract + + /* + * Static tables that there only needs to be one copy of for all. + */ + private static VarDict legalEventHandlers = CreateLegalEventHandlers (); + private static CompValu[] zeroCompValus = new CompValu[0]; + private static TokenType[] zeroArgs = new TokenType[0]; + private static TokenTypeBool tokenTypeBool = new TokenTypeBool (null); + private static TokenTypeExc tokenTypeExc = new TokenTypeExc (null); + private static TokenTypeFloat tokenTypeFlt = new TokenTypeFloat (null); + private static TokenTypeInt tokenTypeInt = new TokenTypeInt (null); + private static TokenTypeObject tokenTypeObj = new TokenTypeObject (null); + private static TokenTypeRot tokenTypeRot = new TokenTypeRot (null); + private static TokenTypeStr tokenTypeStr = new TokenTypeStr (null); + private static TokenTypeVec tokenTypeVec = new TokenTypeVec (null); + private static Type[] instanceTypeArg = new Type[] { typeof (XMRInstAbstract) }; + private static string[] instanceNameArg = new string[] { "$xmrthis" }; + + private static ConstructorInfo lslFloatConstructorInfo = typeof (LSL_Float).GetConstructor (new Type[] { typeof (double) }); + private static ConstructorInfo lslIntegerConstructorInfo = typeof (LSL_Integer).GetConstructor (new Type[] { typeof (int) }); + private static ConstructorInfo lslListConstructorInfo = typeof (LSL_List).GetConstructor (new Type[] { typeof (object[]) }); + public static ConstructorInfo lslRotationConstructorInfo = typeof (LSL_Rotation).GetConstructor (new Type[] { typeof (double), typeof (double), typeof (double), typeof (double) }); + private static ConstructorInfo lslStringConstructorInfo = typeof (LSL_String).GetConstructor (new Type[] { typeof (string) }); + public static ConstructorInfo lslVectorConstructorInfo = typeof (LSL_Vector).GetConstructor (new Type[] { typeof (double), typeof (double), typeof (double) }); + private static ConstructorInfo scriptBadCallNoExceptionConstructorInfo = typeof (ScriptBadCallNoException).GetConstructor (new Type[] { typeof (int) }); + private static ConstructorInfo scriptChangeStateExceptionConstructorInfo = typeof (ScriptChangeStateException).GetConstructor (new Type[] { typeof (int) }); + private static ConstructorInfo scriptRestoreCatchExceptionConstructorInfo = typeof (ScriptRestoreCatchException).GetConstructor (new Type[] { typeof (Exception) }); + private static ConstructorInfo scriptUndefinedStateExceptionConstructorInfo = typeof (ScriptUndefinedStateException).GetConstructor (new Type[] { typeof (string) }); + private static ConstructorInfo sdtClassConstructorInfo = typeof (XMRSDTypeClObj).GetConstructor (new Type[] { typeof (XMRInstAbstract), typeof (int) }); + private static ConstructorInfo xmrArrayConstructorInfo = typeof (XMR_Array).GetConstructor (new Type[] { typeof (XMRInstAbstract) }); + private static FieldInfo callModeFieldInfo = typeof (XMRInstAbstract).GetField ("callMode"); + private static FieldInfo doGblInitFieldInfo = typeof (XMRInstAbstract).GetField ("doGblInit"); + private static FieldInfo ehArgsFieldInfo = typeof (XMRInstAbstract).GetField ("ehArgs"); + private static FieldInfo rotationXFieldInfo = typeof (LSL_Rotation).GetField ("x"); + private static FieldInfo rotationYFieldInfo = typeof (LSL_Rotation).GetField ("y"); + private static FieldInfo rotationZFieldInfo = typeof (LSL_Rotation).GetField ("z"); + private static FieldInfo rotationSFieldInfo = typeof (LSL_Rotation).GetField ("s"); + private static FieldInfo sdtXMRInstFieldInfo = typeof (XMRSDTypeClObj).GetField ("xmrInst"); + private static FieldInfo vectorXFieldInfo = typeof (LSL_Vector).GetField ("x"); + private static FieldInfo vectorYFieldInfo = typeof (LSL_Vector).GetField ("y"); + private static FieldInfo vectorZFieldInfo = typeof (LSL_Vector).GetField ("z"); + + private static MethodInfo arrayClearMethodInfo = typeof (XMR_Array).GetMethod ("__pub_clear", new Type[] { }); + private static MethodInfo arrayCountMethodInfo = typeof (XMR_Array).GetMethod ("__pub_count", new Type[] { }); + private static MethodInfo arrayIndexMethodInfo = typeof (XMR_Array).GetMethod ("__pub_index", new Type[] { typeof (int) }); + private static MethodInfo arrayValueMethodInfo = typeof (XMR_Array).GetMethod ("__pub_value", new Type[] { typeof (int) }); + private static MethodInfo checkRunStackMethInfo = typeof (XMRInstAbstract).GetMethod ("CheckRunStack", new Type[] { }); + private static MethodInfo checkRunQuickMethInfo = typeof (XMRInstAbstract).GetMethod ("CheckRunQuick", new Type[] { }); + private static MethodInfo ehArgUnwrapFloat = GetStaticMethod (typeof (TypeCast), "EHArgUnwrapFloat", new Type[] { typeof (object) }); + private static MethodInfo ehArgUnwrapInteger = GetStaticMethod (typeof (TypeCast), "EHArgUnwrapInteger", new Type[] { typeof (object) }); + private static MethodInfo ehArgUnwrapRotation = GetStaticMethod (typeof (TypeCast), "EHArgUnwrapRotation", new Type[] { typeof (object) }); + private static MethodInfo ehArgUnwrapString = GetStaticMethod (typeof (TypeCast), "EHArgUnwrapString", new Type[] { typeof (object) }); + private static MethodInfo ehArgUnwrapVector = GetStaticMethod (typeof (TypeCast), "EHArgUnwrapVector", new Type[] { typeof (object) }); + private static MethodInfo xmrArrPubIndexMethod = typeof (XMR_Array).GetMethod ("__pub_index", new Type[] { typeof (int) }); + private static MethodInfo xmrArrPubValueMethod = typeof (XMR_Array).GetMethod ("__pub_value", new Type[] { typeof (int) }); + private static MethodInfo captureStackFrameMethodInfo = typeof (XMRInstAbstract).GetMethod ("CaptureStackFrame", new Type[] { typeof (string), typeof (int), typeof (int) }); + private static MethodInfo restoreStackFrameMethodInfo = typeof (XMRInstAbstract).GetMethod ("RestoreStackFrame", new Type[] { typeof (string), typeof (int).MakeByRefType () }); + private static MethodInfo stringCompareMethodInfo = GetStaticMethod (typeof (String), "Compare", new Type[] { typeof (string), typeof (string), typeof (StringComparison) }); + private static MethodInfo stringConcat2MethodInfo = GetStaticMethod (typeof (String), "Concat", new Type[] { typeof (string), typeof (string) }); + private static MethodInfo stringConcat3MethodInfo = GetStaticMethod (typeof (String), "Concat", new Type[] { typeof (string), typeof (string), typeof (string) }); + private static MethodInfo stringConcat4MethodInfo = GetStaticMethod (typeof (String), "Concat", new Type[] { typeof (string), typeof (string), typeof (string), typeof (string) }); + private static MethodInfo lslRotationNegateMethodInfo = GetStaticMethod (typeof (ScriptCodeGen), + "LSLRotationNegate", + new Type[] { typeof (LSL_Rotation) }); + private static MethodInfo lslVectorNegateMethodInfo = GetStaticMethod (typeof (ScriptCodeGen), + "LSLVectorNegate", + new Type[] { typeof (LSL_Vector) }); + private static MethodInfo scriptRestoreCatchExceptionUnwrap = GetStaticMethod (typeof (ScriptRestoreCatchException), "Unwrap", new Type[] { typeof (Exception) }); + private static MethodInfo thrownExceptionWrapMethodInfo = GetStaticMethod (typeof (ScriptThrownException), "Wrap", new Type[] { typeof (object) }); + private static MethodInfo heapTrackerListPush = typeof (HeapTrackerList). GetMethod ("Push", new Type[0]); + private static MethodInfo heapTrackerObjectPush = typeof (HeapTrackerObject).GetMethod ("Push", new Type[0]); + private static MethodInfo heapTrackerStringPush = typeof (HeapTrackerString).GetMethod ("Push", new Type[0]); + + private static MethodInfo catchExcToStrMethodInfo = GetStaticMethod (typeof (ScriptCodeGen), + "CatchExcToStr", + new Type[] { typeof (Exception) }); + + private static MethodInfo consoleWriteMethodInfo = GetStaticMethod (typeof (ScriptCodeGen), "ConsoleWrite", new Type[] { typeof (object) }); + public static void ConsoleWrite (object o) + { + if (o == null) o = "<>"; + Console.Write (o.ToString ()); + } + + public static bool CodeGen (TokenScript tokenScript, BinaryWriter objFileWriter, string sourceHash) + { + /* + * Run compiler such that it has a 'this' context for convenience. + */ + ScriptCodeGen scg = new ScriptCodeGen (tokenScript, objFileWriter, sourceHash); + + /* + * Return pointer to resultant script object code. + */ + return !scg.youveAnError; + } + + /* + * There is one set of these variables for each script being compiled. + */ + private bool mightGetHere = false; + private bool youveAnError = false; + private BreakContTarg curBreakTarg = null; + private BreakContTarg curContTarg = null; + private int lastErrorLine = 0; + private int nStates = 0; + private string sourceHash; + private string lastErrorFile = ""; + private string[] stateNames; + private XMRInstArSizes glblSizes = new XMRInstArSizes (); + private Token errorMessageToken = null; + private TokenDeclVar curDeclFunc = null; + private TokenStmtBlock curStmtBlock = null; + private BinaryWriter objFileWriter = null; + private TokenScript tokenScript = null; + public int tempCompValuNum = 0; + private TokenDeclSDTypeClass currentSDTClass = null; + + private Dictionary stateIndices = null; + + // These get cleared at beginning of every function definition + private ScriptMyLocal instancePointer; // holds XMRInstanceSuperType pointer + private ScriptMyLabel retLabel = null; // where to jump to exit function + private ScriptMyLocal retValue = null; + private ScriptMyLocal actCallNo = null; // for the active try/catch/finally stack or the big one outside them all + private LinkedList actCallLabels = new LinkedList (); // for the active try/catch/finally stack or the big one outside them all + private LinkedList allCallLabels = new LinkedList (); // this holds each and every one for all stacks in total + public CallLabel openCallLabel = null; // only one call label can be open at a time + // - the call label is open from the time of CallPre() until corresponding CallPost() + // - so no non-trivial pushes/pops etc allowed between a CallPre() and a CallPost() + + private ScriptMyILGen _ilGen; + public ScriptMyILGen ilGen { get { return _ilGen; } } + + private ScriptCodeGen (TokenScript tokenScript, BinaryWriter objFileWriter, string sourceHash) + { + this.tokenScript = tokenScript; + this.objFileWriter = objFileWriter; + this.sourceHash = sourceHash; + + try { + PerformCompilation (); + } catch { + // if we've an error, just punt on any exception + // it's probably just a null reference from something + // not being filled in etc. + if (!youveAnError) throw; + } finally { + objFileWriter = null; + } + } + + /** + * @brief Convert 'tokenScript' to 'objFileWriter' format. + * 'tokenScript' is a parsed/reduced abstract syntax tree of the script source file + * 'objFileWriter' is a serialized form of the CIL code that we generate + */ + private void PerformCompilation () + { + /* + * errorMessageToken is used only when the given token doesn't have a + * output delegate associated with it such as for backend API functions + * that only have one copy for the whole system. It is kept up-to-date + * approximately but is rarely needed so going to assume it doesn't have + * to be exact. + */ + errorMessageToken = tokenScript; + + /* + * Set up dictionary to translate state names to their index number. + */ + stateIndices = new Dictionary (); + + /* + * Assign each state its own unique index. + * The default state gets 0. + */ + nStates = 0; + tokenScript.defaultState.body.index = nStates ++; + stateIndices.Add ("default", 0); + foreach (KeyValuePair kvp in tokenScript.states) { + TokenDeclState declState = kvp.Value; + declState.body.index = nStates ++; + stateIndices.Add (declState.name.val, declState.body.index); + } + + /* + * Make up an array that translates state indices to state name strings. + */ + stateNames = new string[nStates]; + stateNames[0] = "default"; + foreach (KeyValuePair kvp in tokenScript.states) { + TokenDeclState declState = kvp.Value; + stateNames[declState.body.index] = declState.name.val; + } + + /* + * Make sure we have delegates for all script-defined functions and methods, + * creating anonymous ones if needed. Note that this includes all property + * getter and setter methods. + */ + foreach (TokenDeclVar declFunc in tokenScript.variablesStack) { + if (declFunc.retType != null) { + declFunc.GetDelType (); + } + } + while (true) { + bool itIsAGoodDayToDie = true; + try { + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + itIsAGoodDayToDie = false; + if (sdType is TokenDeclSDTypeClass) { + TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; + foreach (TokenDeclVar declFunc in sdtClass.members) { + if (declFunc.retType != null) { + declFunc.GetDelType (); + if (declFunc.funcNameSig.val.StartsWith ("$ctor(")) { + // this is for the "$new()" static method that we create below. + // See GenerateStmtNewobj() etc. + new TokenTypeSDTypeDelegate (declFunc, sdtClass.MakeRefToken (declFunc), + declFunc.argDecl.types, tokenScript); + } + } + } + } + if (sdType is TokenDeclSDTypeInterface) { + TokenDeclSDTypeInterface sdtIFace = (TokenDeclSDTypeInterface)sdType; + foreach (TokenDeclVar declFunc in sdtIFace.methsNProps) { + if (declFunc.retType != null) { + declFunc.GetDelType (); + } + } + } + itIsAGoodDayToDie = true; + } + break; + } catch (InvalidOperationException) { + if (!itIsAGoodDayToDie) throw; + // fetching the delegate created an anonymous entry in tokenScript.sdSrcTypesValues + // which made the foreach statement puque, so start over... + } + } + + /* + * No more types can be defined or we won't be able to write them to the object file. + */ + tokenScript.sdSrcTypesSeal (); + + /* + * Assign all global variables a slot in its corresponding XMRInstance.gbls[] array. + * Global variables are simply elements of those arrays at runtime, thus we don't need to create + * an unique class for each script, we can just use XMRInstance as is for all. + */ + foreach (TokenDeclVar declVar in tokenScript.variablesStack) { + + /* + * Omit 'constant' variables as they are coded inline so don't need a slot. + */ + if (declVar.constant) continue; + + /* + * Do functions later. + */ + if (declVar.retType != null) continue; + + /* + * Create entry in the value array for the variable or property. + */ + declVar.location = new CompValuGlobalVar (declVar, glblSizes); + } + + /* + * Likewise for any static fields in script-defined classes. + * They can be referenced anywhere by ., see + * GenerateFromLValSField(). + */ + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (!(sdType is TokenDeclSDTypeClass)) continue; + TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; + + foreach (TokenDeclVar declVar in sdtClass.members) { + + /* + * Omit 'constant' variables as they are coded inline so don't need a slot. + */ + if (declVar.constant) continue; + + /* + * Do methods later. + */ + if (declVar.retType != null) continue; + + /* + * Ignore non-static fields for now. + * They get assigned below. + */ + if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) == 0) continue; + + /* + * Create entry in the value array for the static field or static property. + */ + declVar.location = new CompValuGlobalVar (declVar, glblSizes); + } + } + + /* + * Assign slots for all interface method prototypes. + * These indices are used to index the array of delegates that holds a class' implementation of an + * interface. + * Properties do not get a slot because they aren't called as such. But their corresponding + * $get() and $set() methods are in the table and they each get a slot. + */ + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (!(sdType is TokenDeclSDTypeInterface)) continue; + TokenDeclSDTypeInterface sdtIFace = (TokenDeclSDTypeInterface)sdType; + int vti = 0; + foreach (TokenDeclVar im in sdtIFace.methsNProps) { + if ((im.getProp == null) && (im.setProp == null)) { + im.vTableIndex = vti ++; + } + } + } + + /* + * Assign slots for all instance fields and virtual methods of script-defined classes. + */ + int maxExtends = tokenScript.sdSrcTypesCount; + bool didOne; + do { + didOne = false; + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (!(sdType is TokenDeclSDTypeClass)) continue; + TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; + if (sdtClass.slotsAssigned) continue; + + /* + * If this class extends another, the extended class has to already + * be set up, because our slots add on to the end of the extended class. + */ + TokenDeclSDTypeClass extends = sdtClass.extends; + if (extends != null) { + if (!extends.slotsAssigned) continue; + sdtClass.instSizes = extends.instSizes; + sdtClass.numVirtFuncs = extends.numVirtFuncs; + sdtClass.numInterfaces = extends.numInterfaces; + + int n = maxExtends; + for (TokenDeclSDTypeClass ex = extends; ex != null; ex = ex.extends) { + if (-- n < 0) break; + } + if (n < 0) { + ErrorMsg (sdtClass, "loop in extended classes"); + sdtClass.slotsAssigned = true; + continue; + } + } + + /* + * Extended class's slots all assigned, assign our instance fields + * slots in the XMRSDTypeClObj arrays. + */ + foreach (TokenDeclVar declVar in sdtClass.members) { + if (declVar.retType != null) continue; + if (declVar.constant) continue; + if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) continue; + if ((declVar.getProp == null) && (declVar.setProp == null)) { + declVar.type.AssignVarSlot (declVar, sdtClass.instSizes); + } + } + + /* + * ... and assign virtual method vtable slots. + * + * - : error if any overridden method, doesn't need a slot + * abstract : error if any overridden method, alloc new slot but leave it empty + * new : ignore any overridden method, doesn't need a slot + * new abstract : ignore any overridden method, alloc new slot but leave it empty + * override : must have overridden abstract/virtual, use old slot + * override abstract : must have overridden abstract, use old slot but it is still empty + * static : error if any overridden method, doesn't need a slot + * static new : ignore any overridden method, doesn't need a slot + * virtual : error if any overridden method, alloc new slot and fill it in + * virtual new : ignore any overridden method, alloc new slot and fill it in + */ + foreach (TokenDeclVar declFunc in sdtClass.members) { + if (declFunc.retType == null) continue; + curDeclFunc = declFunc; + + /* + * See if there is a method in an extended class that this method overshadows. + * If so, check for various conflicts. + * In any case, SDT_NEW on our method means to ignore any overshadowed method. + */ + string declLongName = sdtClass.longName.val + "." + declFunc.funcNameSig.val; + uint declFlags = declFunc.sdtFlags; + TokenDeclVar overridden = null; + if ((declFlags & ScriptReduce.SDT_NEW) == 0) { + for (TokenDeclSDTypeClass sdtd = extends; sdtd != null; sdtd = sdtd.extends) { + overridden = FindExactWithRet (sdtd.members, declFunc.name, declFunc.retType, declFunc.argDecl.types); + if (overridden != null) break; + } + } + if (overridden != null) do { + string overLongName = overridden.sdtClass.longName.val; + uint overFlags = overridden.sdtFlags; + + /* + * See if overridden method allows itself to be overridden. + */ + if ((overFlags & ScriptReduce.SDT_ABSTRACT) != 0) { + if ((declFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_OVERRIDE)) == 0) { + ErrorMsg (declFunc, declLongName + " overshadows abstract " + overLongName + " but is not marked abstract, new or override"); + break; + } + } else if ((overFlags & ScriptReduce.SDT_FINAL) != 0) { + ErrorMsg (declFunc, declLongName + " overshadows final " + overLongName + " but is not marked new"); + } else if ((overFlags & (ScriptReduce.SDT_OVERRIDE | ScriptReduce.SDT_VIRTUAL)) != 0) { + if ((declFlags & (ScriptReduce.SDT_NEW | ScriptReduce.SDT_OVERRIDE)) == 0) { + ErrorMsg (declFunc, declLongName + " overshadows virtual " + overLongName + " but is not marked new or override"); + break; + } + } else { + ErrorMsg (declFunc, declLongName + " overshadows non-virtual " + overLongName + " but is not marked new"); + break; + } + + /* + * See if our method is capable of overriding the other method. + */ + if ((declFlags & ScriptReduce.SDT_ABSTRACT) != 0) { + if ((overFlags & ScriptReduce.SDT_ABSTRACT) == 0) { + ErrorMsg (declFunc, declLongName + " abstract overshadows non-abstract " + overLongName + " but is not marked new"); + break; + } + } else if ((declFlags & ScriptReduce.SDT_OVERRIDE) != 0) { + if ((overFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_OVERRIDE | ScriptReduce.SDT_VIRTUAL)) == 0) { + ErrorMsg (declFunc, declLongName + " override overshadows non-abstract/non-virtual " + overLongName); + break; + } + } else { + ErrorMsg (declFunc, declLongName + " overshadows " + overLongName + " but is not marked new"); + break; + } + } while (false); + + /* + * Now we can assign it a vtable slot if it needs one (ie, it is virtual). + */ + declFunc.vTableIndex = -1; + if (overridden != null) { + declFunc.vTableIndex = overridden.vTableIndex; + } else if ((declFlags & ScriptReduce.SDT_OVERRIDE) != 0) { + ErrorMsg (declFunc, declLongName + " marked override but nothing matching found that it overrides"); + } + if ((declFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_VIRTUAL)) != 0) { + declFunc.vTableIndex = sdtClass.numVirtFuncs ++; + } + } + curDeclFunc = null; + + /* + * ... and assign implemented interface slots. + * Note that our implementations of a given interface is completely independent of any + * rootward class's implementation of that same interface. + */ + int nIFaces = sdtClass.numInterfaces + sdtClass.implements.Count; + sdtClass.iFaces = new TokenDeclSDTypeInterface[nIFaces]; + sdtClass.iImplFunc = new TokenDeclVar[nIFaces][]; + for (int i = 0; i < sdtClass.numInterfaces; i ++) { + sdtClass.iFaces[i] = extends.iFaces[i]; + sdtClass.iImplFunc[i] = extends.iImplFunc[i]; + } + + foreach (TokenDeclSDTypeInterface intf in sdtClass.implements) { + int i = sdtClass.numInterfaces ++; + sdtClass.iFaces[i] = intf; + sdtClass.intfIndices.Add (intf.longName.val, i); + int nMeths = 0; + foreach (TokenDeclVar m in intf.methsNProps) { + if ((m.getProp == null) && (m.setProp == null)) nMeths ++; + } + sdtClass.iImplFunc[i] = new TokenDeclVar[nMeths]; + } + + foreach (TokenDeclVar classMeth in sdtClass.members) { + if (classMeth.retType == null) continue; + curDeclFunc = classMeth; + for (TokenIntfImpl intfImpl = classMeth.implements; intfImpl != null; intfImpl = (TokenIntfImpl)intfImpl.nextToken) { + + /* + * One of the class methods implements an interface method. + * Try to find the interface method that is implemented and verify its signature. + */ + TokenDeclSDTypeInterface intfType = intfImpl.intfType.decl; + TokenDeclVar intfMeth = FindExactWithRet (intfType.methsNProps, intfImpl.methName, classMeth.retType, classMeth.argDecl.types); + if (intfMeth == null) { + ErrorMsg (intfImpl, "interface does not define method " + intfImpl.methName.val + classMeth.argDecl.GetArgSig ()); + continue; + } + + /* + * See if this class was declared to implement that interface. + */ + bool found = false; + foreach (TokenDeclSDTypeInterface intf in sdtClass.implements) { + if (intf == intfType) { + found = true; + break; + } + } + if (!found) { + ErrorMsg (intfImpl, "class not declared to implement " + intfType.longName.val); + continue; + } + + /* + * Get index in iFaces[] and iImplFunc[] arrays. + * Start scanning from the end in case one of our rootward classes also implements the interface. + * We should always be successful because we know by now that this class implements the interface. + */ + int i; + for (i = sdtClass.numInterfaces; -- i >= 0;) { + if (sdtClass.iFaces[i] == intfType) break; + } + + /* + * Now remember which of the class methods implements that interface method. + */ + int j = intfMeth.vTableIndex; + if (sdtClass.iImplFunc[i][j] != null) { + ErrorMsg (intfImpl, "also implemented by " + sdtClass.iImplFunc[i][j].funcNameSig.val); + continue; + } + sdtClass.iImplFunc[i][j] = classMeth; + } + } + curDeclFunc = null; + + /* + * Now make sure this class implements all methods for all declared interfaces. + */ + for (int i = sdtClass.numInterfaces - sdtClass.implements.Count; i < sdtClass.numInterfaces; i ++) { + TokenDeclVar[] implementations = sdtClass.iImplFunc[i]; + for (int j = implementations.Length; -- j >= 0;) { + if (implementations[j] == null) { + TokenDeclSDTypeInterface intf = sdtClass.iFaces[i]; + TokenDeclVar meth = null; + foreach (TokenDeclVar im in intf.methsNProps) { + if (im.vTableIndex == j) { + meth = im; + break; + } + } + ErrorMsg (sdtClass, "does not implement " + intf.longName.val + "." + meth.funcNameSig.val); + } + } + } + + /* + * All slots for this class have been assigned. + */ + sdtClass.slotsAssigned = true; + didOne = true; + } + } while (didOne); + + /* + * Compute final values for all variables/fields declared as 'constant'. + * Note that there may be forward references. + */ + do { + didOne = false; + foreach (TokenDeclVar tdv in tokenScript.variablesStack) { + if (tdv.constant && !(tdv.init is TokenRValConst)) { + tdv.init = tdv.init.TryComputeConstant (LookupInitConstants, ref didOne); + } + } + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (!(sdType is TokenDeclSDTypeClass)) continue; + currentSDTClass = (TokenDeclSDTypeClass)sdType; + foreach (TokenDeclVar tdv in currentSDTClass.members) { + if (tdv.constant && !(tdv.init is TokenRValConst)) { + tdv.init = tdv.init.TryComputeConstant (LookupInitConstants, ref didOne); + } + } + } + currentSDTClass = null; + } while (didOne); + + /* + * Now we should be able to assign all those constants their type and location. + */ + foreach (TokenDeclVar tdv in tokenScript.variablesStack) { + if (tdv.constant) { + if (tdv.init is TokenRValConst) { + TokenRValConst rvc = (TokenRValConst)tdv.init; + tdv.type = rvc.tokType; + tdv.location = rvc.GetCompValu (); + } else { + ErrorMsg (tdv, "value is not constant"); + } + } + } + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (!(sdType is TokenDeclSDTypeClass)) continue; + currentSDTClass = (TokenDeclSDTypeClass)sdType; + foreach (TokenDeclVar tdv in currentSDTClass.members) { + if (tdv.constant) { + if (tdv.init is TokenRValConst) { + TokenRValConst rvc = (TokenRValConst)tdv.init; + tdv.type = rvc.tokType; + tdv.location = rvc.GetCompValu (); + } else { + ErrorMsg (tdv, "value is not constant"); + } + } + } + } + currentSDTClass = null; + + /* + * For all classes that define all the methods needed for the class, ie, they aren't abstract, + * define a static class.$new() method with same args as the $ctor(s). This will allow the + * class to be instantiated via the new operator. + */ + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (!(sdType is TokenDeclSDTypeClass)) continue; + TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; + + /* + * See if the class as it stands would be able to fill every slot of its vtable. + */ + bool[] filled = new bool[sdtClass.numVirtFuncs]; + int numFilled = 0; + for (TokenDeclSDTypeClass sdtc = sdtClass; sdtc != null; sdtc = sdtc.extends) { + foreach (TokenDeclVar tdf in sdtc.members) { + if ((tdf.retType != null) && (tdf.vTableIndex >= 0) && ((tdf.sdtFlags & ScriptReduce.SDT_ABSTRACT) == 0)) { + if (!filled[tdf.vTableIndex]) { + filled[tdf.vTableIndex] = true; + numFilled ++; + } + } + } + } + + /* + * If so, define a static class.$new() method for every constructor defined for the class. + * Give it the same access (private/protected/public) as the script declared for the constructor. + * Note that the reducer made sure there is at least a default constructor for every class. + */ + if (numFilled >= sdtClass.numVirtFuncs) { + List newobjDeclFuncs = new List (); + foreach (TokenDeclVar ctorDeclFunc in sdtClass.members) { + if ((ctorDeclFunc.funcNameSig != null) && ctorDeclFunc.funcNameSig.val.StartsWith ("$ctor(")) { + TokenDeclVar newobjDeclFunc = DefineNewobjFunc (ctorDeclFunc); + newobjDeclFuncs.Add (newobjDeclFunc); + } + } + foreach (TokenDeclVar newobjDeclFunc in newobjDeclFuncs) { + sdtClass.members.AddEntry (newobjDeclFunc); + } + } + } + + /* + * Write fixed portion of object file. + */ + objFileWriter.Write (OBJECT_CODE_MAGIC.ToCharArray ()); + objFileWriter.Write (COMPILED_VERSION_VALUE); + objFileWriter.Write (sourceHash); + objFileWriter.Write (tokenScript.expiryDays); + glblSizes.WriteToFile (objFileWriter); + + objFileWriter.Write (nStates); + for (int i = 0; i < nStates; i ++) { + objFileWriter.Write (stateNames[i]); + } + + /* + * For debugging, we also write out global variable array slot assignments. + */ + foreach (TokenDeclVar declVar in tokenScript.variablesStack) { + if (declVar.retType == null) { + WriteOutGblAssignment ("", declVar); + } + } + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (!(sdType is TokenDeclSDTypeClass)) continue; + TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; + foreach (TokenDeclVar declVar in sdtClass.members) { + if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) { + WriteOutGblAssignment (sdtClass.longName.val + ".", declVar); + } + } + } + objFileWriter.Write (""); + + /* + * Write out script-defined types. + */ + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + objFileWriter.Write (sdType.longName.val); + sdType.WriteToFile (objFileWriter); + } + objFileWriter.Write (""); + + /* + * Output function headers then bodies. + * Do all headers first in case bodies do forward references. + * Do both global functions, script-defined class static methods and + * script-defined instance methods, as we handle the differences + * during compilation of the functions/methods themselves. + */ + for (int pass = 0; pass < 2; pass ++) { + foreach (TokenDeclVar declFunc in tokenScript.variablesStack) { + if (declFunc.retType != null) { + if (pass == 0) GenerateMethodHeader (declFunc); + else GenerateMethodBody (declFunc); + } + } + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (sdType is TokenDeclSDTypeClass) { + TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; + foreach (TokenDeclVar declFunc in sdtClass.members) { + if ((declFunc.retType != null) && ((declFunc.sdtFlags & ScriptReduce.SDT_ABSTRACT) == 0)) { + if (pass == 0) GenerateMethodHeader (declFunc); + else GenerateMethodBody (declFunc); + } + } + } + } + } + + /* + * Output default state event handler functions. + * Each event handler is a private static method named 'default '. + * Splice in a default state_entry() handler if none defined so we can init global vars. + */ + TokenDeclVar defaultStateEntry = null; + for (defaultStateEntry = tokenScript.defaultState.body.eventFuncs; + defaultStateEntry != null; + defaultStateEntry = (TokenDeclVar)defaultStateEntry.nextToken) { + if (defaultStateEntry.funcNameSig.val == "state_entry()") break; + } + if (defaultStateEntry == null) { + defaultStateEntry = new TokenDeclVar (tokenScript.defaultState.body, null, tokenScript); + defaultStateEntry.name = new TokenName (tokenScript.defaultState.body, "state_entry"); + defaultStateEntry.retType = new TokenTypeVoid (tokenScript.defaultState.body); + defaultStateEntry.argDecl = new TokenArgDecl (tokenScript.defaultState.body); + defaultStateEntry.body = new TokenStmtBlock (tokenScript.defaultState.body); + defaultStateEntry.body.function = defaultStateEntry; + + defaultStateEntry.nextToken = tokenScript.defaultState.body.eventFuncs; + tokenScript.defaultState.body.eventFuncs = defaultStateEntry; + } + GenerateStateEventHandlers ("default", tokenScript.defaultState.body); + + /* + * Output script-defined state event handler methods. + * Each event handler is a private static method named + */ + foreach (KeyValuePair kvp in tokenScript.states) { + TokenDeclState declState = kvp.Value; + GenerateStateEventHandlers (declState.name.val, declState.body); + } + + ScriptObjWriter.TheEnd (objFileWriter); + } + + /** + * @brief Write out what slot was assigned for a global or sdtclass static variable. + * Constants, functions, instance fields, methods, properties do not have slots in the global variables arrays. + */ + private void WriteOutGblAssignment (string pfx, TokenDeclVar declVar) + { + if (!declVar.constant && (declVar.retType == null) && (declVar.getProp == null) && (declVar.setProp == null)) { + objFileWriter.Write (pfx + declVar.name.val); // string + objFileWriter.Write (declVar.vTableArray.Name); // string + objFileWriter.Write (declVar.vTableIndex); // int + } + } + + /** + * @brief generate event handler code + * Writes out a function definition for each state handler + * named + * + * However, each has just 'XMRInstance __sw' as its single argument + * and each of its user-visible argments is extracted from __sw.ehArgs[]. + * + * So we end up generating something like this: + * + * private static void (XMRInstance __sw) + * { + * = ()__sw.ehArgs[0]; + * = ()__sw.ehArgs[1]; + * + * ... script code ... + * } + * + * The continuations code assumes there will be no references to ehArgs[] + * after the first call to CheckRun() as CheckRun() makes no attempt to + * serialize the ehArgs[] array, as doing so would be redundant. Any values + * from ehArgs[] that are being used will be in local stack variables and + * thus preserved that way. + */ + private void GenerateStateEventHandlers (string statename, TokenStateBody body) + { + Dictionary statehandlers = new Dictionary (); + for (Token t = body.eventFuncs; t != null; t = t.nextToken) { + TokenDeclVar tdv = (TokenDeclVar)t; + string eventname = tdv.GetSimpleName (); + if (statehandlers.ContainsKey (eventname)) { + ErrorMsg (tdv, "event handler " + eventname + " already defined for state " + statename); + } else { + statehandlers.Add (eventname, tdv); + GenerateEventHandler (statename, tdv); + } + } + } + + private void GenerateEventHandler (string statename, TokenDeclVar declFunc) + { + string eventname = declFunc.GetSimpleName (); + TokenArgDecl argDecl = declFunc.argDecl; + + /* + * Make sure event handler name is valid and that number and type of arguments is correct. + * Apparently some scripts exist with fewer than correct number of args in their declaration + * so allow for that. It is ok because the handlers are called with the arguments in an + * object[] array, and we just won't access the missing argments in the vector. But the + * specified types must match one of the prototypes in legalEventHandlers. + */ + TokenDeclVar protoDeclFunc = legalEventHandlers.FindExact (eventname, argDecl.types); + if (protoDeclFunc == null) { + ErrorMsg (declFunc, "unknown event handler " + eventname + argDecl.GetArgSig ()); + return; + } + + /* + * Output function header. + * They just have the XMRInstAbstract pointer as the one argument. + */ + string functionName = statename + " " + eventname; + _ilGen = new ScriptObjWriter (tokenScript, + functionName, + typeof (void), + instanceTypeArg, + instanceNameArg, + objFileWriter); + StartFunctionBody (declFunc); + + /* + * Create a temp to hold XMRInstanceSuperType version of arg 0. + */ + instancePointer = ilGen.DeclareLocal (xmrInstSuperType, "__xmrinst"); + ilGen.Emit (declFunc, OpCodes.Ldarg_0); + ilGen.Emit (declFunc, OpCodes.Castclass, xmrInstSuperType); + ilGen.Emit (declFunc, OpCodes.Stloc, instancePointer); + + /* + * Output args as variable definitions and initialize each from __sw.ehArgs[]. + * If the script writer goofed, the typecast will complain. + */ + int nArgs = argDecl.vars.Length; + for (int i = 0; i < nArgs; i ++) { + + /* + * Say that the argument variable is going to be located in a local var. + */ + TokenDeclVar argVar = argDecl.vars[i]; + TokenType argTokType = argVar.type; + CompValuLocalVar local = new CompValuLocalVar (argTokType, argVar.name.val, this); + argVar.location = local; + + /* + * Copy from the ehArgs[i] element to the temp var. + * Cast as needed, there is a lot of craziness like OpenMetaverse.Quaternion. + */ + local.PopPre (this, argVar.name); + PushXMRInst (); // instance + ilGen.Emit (declFunc, OpCodes.Ldfld, ehArgsFieldInfo); // instance.ehArgs (array of objects) + ilGen.Emit (declFunc, OpCodes.Ldc_I4, i); // array index = i + ilGen.Emit (declFunc, OpCodes.Ldelem, typeof (object)); // select the argument we want + TokenType stkTokType = tokenTypeObj; // stack has a type 'object' on it now + Type argSysType = argTokType.ToSysType (); // this is the type the script expects + if (argSysType == typeof (double)) { // LSL_Float/double -> double + ilGen.Emit (declFunc, OpCodes.Call, ehArgUnwrapFloat); + stkTokType = tokenTypeFlt; // stack has a type 'double' on it now + } + if (argSysType == typeof (int)) { // LSL_Integer/int -> int + ilGen.Emit (declFunc, OpCodes.Call, ehArgUnwrapInteger); + stkTokType = tokenTypeInt; // stack has a type 'int' on it now + } + if (argSysType == typeof (LSL_List)) { // LSL_List -> LSL_List + TypeCast.CastTopOfStack (this, argVar.name, stkTokType, argTokType, true); + stkTokType = argTokType; // stack has a type 'LSL_List' on it now + } + if (argSysType == typeof (LSL_Rotation)) { // OpenMetaverse.Quaternion/LSL_Rotation -> LSL_Rotation + ilGen.Emit (declFunc, OpCodes.Call, ehArgUnwrapRotation); + stkTokType = tokenTypeRot; // stack has a type 'LSL_Rotation' on it now + } + if (argSysType == typeof (string)) { // LSL_Key/LSL_String/string -> string + ilGen.Emit (declFunc, OpCodes.Call, ehArgUnwrapString); + stkTokType = tokenTypeStr; // stack has a type 'string' on it now + } + if (argSysType == typeof (LSL_Vector)) { // OpenMetaverse.Vector3/LSL_Vector -> LSL_Vector + ilGen.Emit (declFunc, OpCodes.Call, ehArgUnwrapVector); + stkTokType = tokenTypeVec; // stack has a type 'LSL_Vector' on it now + } + local.PopPost (this, argVar.name, stkTokType); // pop stack type into argtype + } + + /* + * Output code for the statements and clean up. + */ + GenerateFuncBody (); + } + + /** + * @brief generate header for an arbitrary script-defined global function. + * @param declFunc = function being defined + */ + private void GenerateMethodHeader (TokenDeclVar declFunc) + { + curDeclFunc = declFunc; + + /* + * Make up array of all argument types as seen by the code generator. + * We splice in XMRInstanceSuperType or XMRSDTypeClObj for the first + * arg as the function itself is static, followed by script-visible + * arg types. + */ + TokenArgDecl argDecl = declFunc.argDecl; + int nArgs = argDecl.vars.Length; + Type[] argTypes = new Type[nArgs+1]; + string[] argNames = new string[nArgs+1]; + if (IsSDTInstMethod ()) { + argTypes[0] = typeof (XMRSDTypeClObj); + argNames[0] = "$sdtthis"; + } else { + argTypes[0] = xmrInstSuperType; + argNames[0] = "$xmrthis"; + } + for (int i = 0; i < nArgs; i ++) { + argTypes[i+1] = argDecl.vars[i].type.ToSysType (); + argNames[i+1] = argDecl.vars[i].name.val; + } + + /* + * Set up entrypoint. + */ + string objCodeName = declFunc.GetObjCodeName (); + declFunc.ilGen = new ScriptObjWriter (tokenScript, + objCodeName, + declFunc.retType.ToSysType (), + argTypes, + argNames, + objFileWriter); + + /* + * This says how to generate a call to the function and to get a delegate. + */ + declFunc.location = new CompValuGlobalMeth (declFunc); + + curDeclFunc = null; + } + + /** + * @brief generate code for an arbitrary script-defined function. + * @param name = name of the function + * @param argDecl = argument declarations + * @param body = function's code body + */ + private void GenerateMethodBody (TokenDeclVar declFunc) + { + /* + * Set up code generator for the function's contents. + */ + _ilGen = declFunc.ilGen; + StartFunctionBody (declFunc); + + /* + * Create a temp to hold XMRInstanceSuperType version of arg 0. + * For most functions, arg 0 is already XMRInstanceSuperType. + * But for script-defined class instance methods, arg 0 holds + * the XMRSDTypeClObj pointer and so we read the XMRInstAbstract + * pointer from its XMRSDTypeClObj.xmrInst field then cast it to + * XMRInstanceSuperType. + */ + if (IsSDTInstMethod ()) { + instancePointer = ilGen.DeclareLocal (xmrInstSuperType, "__xmrinst"); + ilGen.Emit (declFunc, OpCodes.Ldarg_0); + ilGen.Emit (declFunc, OpCodes.Ldfld, sdtXMRInstFieldInfo); + ilGen.Emit (declFunc, OpCodes.Castclass, xmrInstSuperType); + ilGen.Emit (declFunc, OpCodes.Stloc, instancePointer); + } + + /* + * Define location of all script-level arguments so script body can access them. + * The argument indices need to have +1 added to them because XMRInstance or + * XMRSDTypeClObj is spliced in at arg 0. + */ + TokenArgDecl argDecl = declFunc.argDecl; + int nArgs = argDecl.vars.Length; + for (int i = 0; i < nArgs; i ++) { + TokenDeclVar argVar = argDecl.vars[i]; + argVar.location = new CompValuArg (argVar.type, i + 1); + } + + /* + * Output code for the statements and clean up. + */ + GenerateFuncBody (); + } + + private void StartFunctionBody (TokenDeclVar declFunc) + { + /* + * Start current function being processed. + * Set 'mightGetHere' as the code at the top is always executed. + */ + instancePointer = null; + mightGetHere = true; + curBreakTarg = null; + curContTarg = null; + curDeclFunc = declFunc; + + /* + * Start generating code. + */ + ((ScriptObjWriter)ilGen).BegMethod (); + } + + /** + * @brief Define function for a script-defined type's .$new() method. + * See GenerateStmtNewobj() for more info. + */ + private TokenDeclVar DefineNewobjFunc (TokenDeclVar ctorDeclFunc) + { + /* + * Set up 'static classname $new(params-same-as-ctor) { }'. + */ + TokenDeclVar newobjDeclFunc = new TokenDeclVar (ctorDeclFunc, null, tokenScript); + newobjDeclFunc.name = new TokenName (newobjDeclFunc, "$new"); + newobjDeclFunc.retType = ctorDeclFunc.sdtClass.MakeRefToken (newobjDeclFunc); + newobjDeclFunc.argDecl = ctorDeclFunc.argDecl; + newobjDeclFunc.sdtClass = ctorDeclFunc.sdtClass; + newobjDeclFunc.sdtFlags = ScriptReduce.SDT_STATIC | ctorDeclFunc.sdtFlags; + + /* + * Declare local variable named '$objptr' in a frame just under + * what the '$new(...)' function's arguments are declared in. + */ + TokenDeclVar objptrVar = new TokenDeclVar (newobjDeclFunc, newobjDeclFunc, tokenScript); + objptrVar.type = newobjDeclFunc.retType; + objptrVar.name = new TokenName (newobjDeclFunc, "$objptr"); + VarDict newFrame = new VarDict (false); + newFrame.outerVarDict = ctorDeclFunc.argDecl.varDict; + newFrame.AddEntry (objptrVar); + + /* + * Set up '$objptr.$ctor' + */ + TokenLValName objptrLValName = new TokenLValName (objptrVar.name, newFrame); + // ref a var by giving its name + TokenLValIField objptrDotCtor = new TokenLValIField (newobjDeclFunc); // an instance member reference + objptrDotCtor.baseRVal = objptrLValName; // '$objptr' + objptrDotCtor.fieldName = ctorDeclFunc.name; // '.' '$ctor' + + /* + * Set up '$objptr.$ctor(arglist)' call for use in the '$new(...)' body. + * Copy the arglist from the constructor declaration so triviality + * processing will pick the correct overloaded constructor. + */ + TokenRValCall callCtorRVal = new TokenRValCall (newobjDeclFunc); // doing a call of some sort + callCtorRVal.meth = objptrDotCtor; // calling $objptr.$ctor() + TokenDeclVar[] argList = newobjDeclFunc.argDecl.vars; // get args $new() was declared with + callCtorRVal.nArgs = argList.Length; // ...that is nArgs we are passing to $objptr.$ctor() + for (int i = argList.Length; -- i >= 0;) { + TokenDeclVar arg = argList[i]; // find out about one of the args + TokenLValName argLValName = new TokenLValName (arg.name, ctorDeclFunc.argDecl.varDict); + // pass arg of that name to $objptr.$ctor() + argLValName.nextToken = callCtorRVal.args; // link to list of args passed to $objptr.$ctor() + callCtorRVal.args = argLValName; + } + + /* + * Set up a funky call to the constructor for the code body. + * This will let code generator know there is some craziness. + * See GenerateStmtNewobj(). + * + * This is in essence: + * { + * classname $objptr = newobj (classname); + * $objptr.$ctor (...); + * return $objptr; + * } + */ + TokenStmtNewobj newobjStmtBody = new TokenStmtNewobj (ctorDeclFunc); + newobjStmtBody.objptrVar = objptrVar; + newobjStmtBody.rValCall = callCtorRVal; + TokenStmtBlock newobjBody = new TokenStmtBlock (ctorDeclFunc); + newobjBody.statements = newobjStmtBody; + + /* + * Link that code as the body of the function. + */ + newobjDeclFunc.body = newobjBody; + + /* + * Say the function calls '$objptr.$ctor(arglist)' so we will inherit ctor's triviality. + */ + newobjDeclFunc.unknownTrivialityCalls.AddLast (callCtorRVal); + return newobjDeclFunc; + } + + private class TokenStmtNewobj : TokenStmt { + public TokenDeclVar objptrVar; + public TokenRValCall rValCall; + public TokenStmtNewobj (Token original) : base (original) { } + } + + /** + * @brief Output function body (either event handler or script-defined method). + */ + private void GenerateFuncBody () + { + /* + * We want to know if the function's code is trivial, ie, + * if it doesn't have anything that might be an infinite + * loop and that is doesn't call anything that might have + * an infinite loop. If it is, we don't need any CheckRun() + * stuff or any of the frame save/restore stuff. + */ + bool isTrivial = curDeclFunc.IsFuncTrivial (this); + + /* + * Clear list of all call labels. + * A call label is inserted just before every call that can possibly + * call CheckRun(), including any direct calls to CheckRun(). + * Then, when restoring stack, we can just switch to this label to + * resume at the correct spot. + */ + actCallLabels.Clear (); + allCallLabels.Clear (); + openCallLabel = null; + + /* + * Alloc stack space for local vars. + */ + AllocLocalVarStackSpace (); + + /* + * Any return statements inside function body jump to this label + * after putting return value in __retval. + */ + retLabel = ilGen.DefineLabel ("__retlbl"); + retValue = null; + if (!(curDeclFunc.retType is TokenTypeVoid)) { + retValue = ilGen.DeclareLocal (curDeclFunc.retType.ToSysType (), "__retval"); + } + + /* + * Output: + * int __mainCallNo = -1; + * try { + * if (instance.callMode != CallMode_NORMAL) goto __cmRestore; + */ + actCallNo = null; + ScriptMyLabel cmRestore = null; + if (!isTrivial) { + actCallNo = ilGen.DeclareLocal (typeof (int), "__mainCallNo"); + SetCallNo (curDeclFunc, actCallNo, -1); + cmRestore = ilGen.DefineLabel ("__cmRestore"); + ilGen.BeginExceptionBlock (); + PushXMRInst (); + ilGen.Emit (curDeclFunc, OpCodes.Ldfld, ScriptCodeGen.callModeFieldInfo); + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_NORMAL); + ilGen.Emit (curDeclFunc, OpCodes.Bne_Un, cmRestore); + } + + /* + * Splice in the code optimizer for the body of the function. + */ + ScriptCollector collector = new ScriptCollector ((ScriptObjWriter)ilGen); + _ilGen = collector; + + /* + * If this is the default state_entry() handler, output code to set all global + * variables to their initial values. Note that every script must have a + * default state_entry() handler, we provide one if the script doesn't explicitly + * define one. + */ + string methname = ilGen.methName; + if (methname == "default state_entry") { + + // if (!doGblInit) goto skipGblInit; + ScriptMyLabel skipGblInitLabel = ilGen.DefineLabel ("__skipGblInit"); + PushXMRInst (); // instance + ilGen.Emit (curDeclFunc, OpCodes.Ldfld, doGblInitFieldInfo); // instance.doGblInit + ilGen.Emit (curDeclFunc, OpCodes.Brfalse, skipGblInitLabel); + + // $globalvarinit(); + TokenDeclVar gviFunc = tokenScript.globalVarInit; + if (gviFunc.body.statements != null) { + gviFunc.location.CallPre (this, gviFunc); + gviFunc.location.CallPost (this, gviFunc); + } + + // various $staticfieldinit(); + foreach (TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) { + if (sdType is TokenDeclSDTypeClass) { + TokenDeclVar sfiFunc = ((TokenDeclSDTypeClass)sdType).staticFieldInit; + if ((sfiFunc != null) && (sfiFunc.body.statements != null)) { + sfiFunc.location.CallPre (this, sfiFunc); + sfiFunc.location.CallPost (this, sfiFunc); + } + } + } + + // doGblInit = 0; + PushXMRInst (); // instance + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4_0); + ilGen.Emit (curDeclFunc, OpCodes.Stfld, doGblInitFieldInfo); // instance.doGblInit + + //skipGblInit: + ilGen.MarkLabel (skipGblInitLabel); + } + + /* + * If this is a script-defined type constructor, call the base constructor and call + * this class's $instfieldinit() method to initialize instance fields. + */ + if ((curDeclFunc.sdtClass != null) && curDeclFunc.funcNameSig.val.StartsWith ("$ctor(")) { + if (curDeclFunc.baseCtorCall != null) { + GenerateFromRValCall (curDeclFunc.baseCtorCall); + } + TokenDeclVar ifiFunc = ((TokenDeclSDTypeClass)curDeclFunc.sdtClass).instFieldInit; + if (ifiFunc.body.statements != null) { + CompValu thisCompValu = new CompValuArg (ifiFunc.sdtClass.MakeRefToken (ifiFunc), 0); + CompValu ifiFuncLocn = new CompValuInstMember (ifiFunc, thisCompValu, true); + ifiFuncLocn.CallPre (this, ifiFunc); + ifiFuncLocn.CallPost (this, ifiFunc); + } + } + + /* + * See if time to suspend in case they are doing a loop with recursion. + */ + if (!isTrivial) EmitCallCheckRun (curDeclFunc, true); + + /* + * Output code body. + */ + GenerateStmtBlock (curDeclFunc.body); + + /* + * If code falls through to this point, means they are missing + * a return statement. And that is legal only if the function + * returns 'void'. + */ + if (mightGetHere) { + if (!(curDeclFunc.retType is TokenTypeVoid)) { + ErrorMsg (curDeclFunc.body, "missing final return statement"); + } + ilGen.Emit (curDeclFunc, OpCodes.Leave, retLabel); + } + + /* + * End of the code to be optimized. + * Do optimizations then write it all out to object file. + * After this, all code gets written directly to object file. + * Optimization must be completed before we scan the allCallLabels + * list below to look for active locals and temps. + */ + collector.Optimize (); + _ilGen = collector.WriteOutAll (); + collector = null; + + /* + * Output code to restore stack frame from stream. + * It jumps back to the call labels within the function body. + */ + List activeTemps = null; + if (!isTrivial) { + + /* + * Build list of locals and temps active at all the call labels. + */ + activeTemps = new List (); + foreach (CallLabel cl in allCallLabels) { + foreach (ScriptMyLocal lcl in cl.callLabel.whereAmI.localsReadBeforeWritten) { + if (!activeTemps.Contains (lcl)) { + activeTemps.Add (lcl); + } + } + } + + /* + * Output code to restore the args, locals and temps then jump to + * the call label that we were interrupted at. + */ + ilGen.MarkLabel (cmRestore); + GenerateFrameRestoreCode (activeTemps); + } + + /* + * Output epilog that saves stack frame state if CallMode_SAVE. + * + * finally { + * if (instance.callMode != CallMode_SAVE) goto __endFin; + * GenerateFrameCaptureCode(); + * __endFin: + * } + */ + ScriptMyLabel endFin = null; + if (!isTrivial) { + ilGen.BeginFinallyBlock (); + endFin = ilGen.DefineLabel ("__endFin"); + PushXMRInst (); + ilGen.Emit (curDeclFunc, OpCodes.Ldfld, callModeFieldInfo); + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_SAVE); + ilGen.Emit (curDeclFunc, OpCodes.Bne_Un, endFin); + GenerateFrameCaptureCode (activeTemps); + ilGen.MarkLabel (endFin); + ilGen.Emit (curDeclFunc, OpCodes.Endfinally); + ilGen.EndExceptionBlock (); + } + + /* + * Output the 'real' return opcode. + */ + ilGen.MarkLabel (retLabel); + if (!(curDeclFunc.retType is TokenTypeVoid)) { + ilGen.Emit (curDeclFunc, OpCodes.Ldloc, retValue); + } + ilGen.Emit (curDeclFunc, OpCodes.Ret); + retLabel = null; + retValue = null; + + /* + * No more instructions for this method. + */ + ((ScriptObjWriter)ilGen).EndMethod (); + _ilGen = null; + + /* + * Not generating function code any more. + */ + curBreakTarg = null; + curContTarg = null; + curDeclFunc = null; + } + + /** + * @brief Allocate stack space for all local variables, regardless of + * which { } statement block they are actually defined in. + */ + private void AllocLocalVarStackSpace () + { + foreach (TokenDeclVar localVar in curDeclFunc.localVars) { + + /* + * Skip all 'constant' vars as they were handled by the reducer. + */ + if (localVar.constant) continue; + + /* + * Get a stack location for the local variable. + */ + localVar.location = new CompValuLocalVar (localVar.type, localVar.name.val, this); + } + } + + /** + * @brief Generate code to write all arguments and locals to the capture stack frame. + * This includes temp variables. + * We only need to save what is active at the point of callLabels through because + * those are the only points we will jump to on restore. This saves us from saving + * all the little temp vars we create. + * @param activeTemps = list of locals and temps that we care about, ie, which + * ones get restored by GenerateFrameRestoreCode(). + */ + private void GenerateFrameCaptureCode (List activeTemps) + { + /* + * Compute total number of slots we need to save stuff. + * Assume we need to save all call arguments. + */ + int nSaves = curDeclFunc.argDecl.vars.Length + activeTemps.Count; + + /* + * Output code to allocate a stack frame object with an object array. + * This also pushes the stack frame object on the instance.stackFrames list. + * It returns a pointer to the object array it allocated. + */ + PushXMRInst (); + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, ilGen.methName); + GetCallNo (curDeclFunc, actCallNo); + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4, nSaves); + ilGen.Emit (curDeclFunc, OpCodes.Call, captureStackFrameMethodInfo); + + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, ilGen.methName + "*: capture mainCallNo="); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (curDeclFunc, OpCodes.Ldloc, actCallNo); + ilGen.Emit (curDeclFunc, OpCodes.Box, typeof (int)); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + + /* + * Copy arg values to object array, boxing as needed. + */ + int i = 0; + foreach (TokenDeclVar argVar in curDeclFunc.argDecl.varDict) { + ilGen.Emit (curDeclFunc, OpCodes.Dup); + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4, i); + argVar.location.PushVal (this, argVar.name, tokenTypeObj); + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, "\n arg:" + argVar.name.val + "="); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (curDeclFunc, OpCodes.Dup); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + ilGen.Emit (curDeclFunc, OpCodes.Stelem_Ref); + i ++; + } + + /* + * Copy local and temp values to object array, boxing as needed. + */ + foreach (ScriptMyLocal lcl in activeTemps) { + ilGen.Emit (curDeclFunc, OpCodes.Dup); + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4, i ++); + ilGen.Emit (curDeclFunc, OpCodes.Ldloc, lcl); + Type t = lcl.type; + if (t == typeof (HeapTrackerList)) { + ilGen.Emit (curDeclFunc, OpCodes.Call, heapTrackerListPush); + t = typeof (LSL_List); + } + if (t == typeof (HeapTrackerObject)) { + ilGen.Emit (curDeclFunc, OpCodes.Call, heapTrackerObjectPush); + t = typeof (object); + } + if (t == typeof (HeapTrackerString)) { + ilGen.Emit (curDeclFunc, OpCodes.Call, heapTrackerStringPush); + t = typeof (string); + } + if (t.IsValueType) { + ilGen.Emit (curDeclFunc, OpCodes.Box, t); + } + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, "\n lcl:" + lcl.name + "="); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (curDeclFunc, OpCodes.Dup); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + ilGen.Emit (curDeclFunc, OpCodes.Stelem_Ref); + } + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, "\n"); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + + ilGen.Emit (curDeclFunc, OpCodes.Pop); + } + + /** + * @brief Generate code to restore all arguments and locals from the restore stack frame. + * This includes temp variables. + */ + private void GenerateFrameRestoreCode (List activeTemps) + { + ScriptMyLocal objArray = ilGen.DeclareLocal (typeof (object[]), "__restObjArray"); + + /* + * Output code to pop stack frame from instance.stackFrames. + * It returns a pointer to the object array that contains values to be restored. + */ + PushXMRInst (); + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, ilGen.methName); + ilGen.Emit (curDeclFunc, OpCodes.Ldloca, actCallNo); // __mainCallNo + ilGen.Emit (curDeclFunc, OpCodes.Call, restoreStackFrameMethodInfo); + ilGen.Emit (curDeclFunc, OpCodes.Stloc, objArray); + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, ilGen.methName + "*: restore mainCallNo="); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (curDeclFunc, OpCodes.Ldloc, actCallNo); + ilGen.Emit (curDeclFunc, OpCodes.Box, typeof (int)); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + + /* + * Restore argument values from object array, unboxing as needed. + * Although the caller has restored them to what it called us with, it's possible that this + * function has modified them since, so we need to do our own restore. + */ + int i = 0; + foreach (TokenDeclVar argVar in curDeclFunc.argDecl.varDict) { + CompValu argLoc = argVar.location; + argLoc.PopPre (this, argVar.name); + ilGen.Emit (curDeclFunc, OpCodes.Ldloc, objArray); + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4, i); + ilGen.Emit (curDeclFunc, OpCodes.Ldelem_Ref); + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, "\n arg:" + argVar.name.val + "="); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (curDeclFunc, OpCodes.Dup); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + TypeCast.CastTopOfStack (this, argVar.name, tokenTypeObj, argLoc.type, true); + argLoc.PopPost (this, argVar.name); + i ++; + } + + /* + * Restore local and temp values from object array, unboxing as needed. + */ + foreach (ScriptMyLocal lcl in activeTemps) { + Type t = lcl.type; + Type u = t; + if (t == typeof (HeapTrackerList)) u = typeof (LSL_List); + if (t == typeof (HeapTrackerObject)) u = typeof (object); + if (t == typeof (HeapTrackerString)) u = typeof (string); + if (u != t) { + ilGen.Emit (curDeclFunc, OpCodes.Ldloc, lcl); + } + ilGen.Emit (curDeclFunc, OpCodes.Ldloc, objArray); + ilGen.Emit (curDeclFunc, OpCodes.Ldc_I4, i ++); + ilGen.Emit (curDeclFunc, OpCodes.Ldelem_Ref); + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, "\n lcl:" + lcl.name + "="); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (curDeclFunc, OpCodes.Dup); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + if (u.IsValueType) { + ilGen.Emit (curDeclFunc, OpCodes.Unbox_Any, u); + } else if (u != typeof (object)) { + ilGen.Emit (curDeclFunc, OpCodes.Castclass, u); + } + if (u != t) { + ilGen.Emit (curDeclFunc, OpCodes.Call, t.GetMethod ("Pop", new Type[] { u })); + } else { + ilGen.Emit (curDeclFunc, OpCodes.Stloc, lcl); + } + } + if (DEBUG_STACKCAPRES) { + ilGen.Emit (curDeclFunc, OpCodes.Ldstr, "\n"); + ilGen.Emit (curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); + } + + OutputCallNoSwitchStmt (); + } + + /** + * @brief Output a switch statement with a case for each possible + * value of whatever callNo is currently active, either + * __mainCallNo or one of the try/catch/finally's callNos. + * + * switch (callNo) { + * case 0: goto __call_0; + * case 1: goto __call_1; + * ... + * } + * throw new ScriptBadCallNoException (callNo); + */ + private void OutputCallNoSwitchStmt () + { + ScriptMyLabel[] callLabels = new ScriptMyLabel[actCallLabels.Count]; + foreach (CallLabel cl in actCallLabels) { + callLabels[cl.index] = cl.callLabel; + } + GetCallNo (curDeclFunc, actCallNo); + ilGen.Emit (curDeclFunc, OpCodes.Switch, callLabels); + + GetCallNo (curDeclFunc, actCallNo); + ilGen.Emit (curDeclFunc, OpCodes.Newobj, scriptBadCallNoExceptionConstructorInfo); + ilGen.Emit (curDeclFunc, OpCodes.Throw); + } + + /** + * @brief There is one of these per call that can possibly call CheckRun(), + * including direct calls to CheckRun(). + * They mark points that the stack capture/restore code will save & restore to. + * All object-code level local vars active at the call label's point will + * be saved & restored. + * + * callNo = 5; + * __call_5: + * push call arguments from temps + * call SomethingThatCallsCheckRun() + * + * If SomethingThatCallsCheckRun() actually calls CheckRun(), our restore code + * will restore our args, locals & temps, then jump to __call_5, which will then + * call SomethingThatCallsCheckRun() again, which will restore its stuff likewise. + * When eventually the actual CheckRun() call is restored, it will turn off restore + * mode (by changing callMode from CallMode_RESTORE to CallMode_NORMAL) and return, + * allowing the code to run normally from that point. + */ + public class CallLabel { + public int index; // sequential integer, starting at 0, within actCallLabels + // - used for the switch statement + public ScriptMyLabel callLabel; // the actual label token + + public CallLabel (ScriptCodeGen scg, Token errorAt) + { + if (scg.openCallLabel != null) throw new Exception ("call label already open"); + + if (!scg.curDeclFunc.IsFuncTrivial (scg)) { + this.index = scg.actCallLabels.Count; + string name = "__call_" + index + "_" + scg.allCallLabels.Count; + + /* + * Make sure eval stack is empty because the frame capture/restore + * code expects such (restore switch stmt has an empty stack). + */ + int depth = ((ScriptCollector)scg.ilGen).stackDepth.Count; + if (depth > 0) { + // maybe need to call Trivialize() + throw new Exception ("call label stack depth " + depth + " at " + errorAt.SrcLoc); + } + + /* + * Eval stack is empty so the restore code can handle it. + */ + this.index = scg.actCallLabels.Count; + scg.actCallLabels.AddLast (this); + scg.allCallLabels.AddLast (this); + this.callLabel = scg.ilGen.DefineLabel (name); + scg.SetCallNo (errorAt, scg.actCallNo, this.index); + scg.ilGen.MarkLabel (this.callLabel); + } + + scg.openCallLabel = this; + } + }; + + /** + * @brief generate code for an arbitrary statement. + */ + private void GenerateStmt (TokenStmt stmt) + { + errorMessageToken = stmt; + if (stmt is TokenDeclVar) { GenerateDeclVar ((TokenDeclVar)stmt); return; } + if (stmt is TokenStmtBlock) { GenerateStmtBlock ((TokenStmtBlock)stmt); return; } + if (stmt is TokenStmtBreak) { GenerateStmtBreak ((TokenStmtBreak)stmt); return; } + if (stmt is TokenStmtCont) { GenerateStmtCont ((TokenStmtCont)stmt); return; } + if (stmt is TokenStmtDo) { GenerateStmtDo ((TokenStmtDo)stmt); return; } + if (stmt is TokenStmtFor) { GenerateStmtFor ((TokenStmtFor)stmt); return; } + if (stmt is TokenStmtForEach) { GenerateStmtForEach ((TokenStmtForEach)stmt); return; } + if (stmt is TokenStmtIf) { GenerateStmtIf ((TokenStmtIf)stmt); return; } + if (stmt is TokenStmtJump) { GenerateStmtJump ((TokenStmtJump)stmt); return; } + if (stmt is TokenStmtLabel) { GenerateStmtLabel ((TokenStmtLabel)stmt); return; } + if (stmt is TokenStmtNewobj) { GenerateStmtNewobj ((TokenStmtNewobj)stmt); return; } + if (stmt is TokenStmtNull) { return; } + if (stmt is TokenStmtRet) { GenerateStmtRet ((TokenStmtRet)stmt); return; } + if (stmt is TokenStmtRVal) { GenerateStmtRVal ((TokenStmtRVal)stmt); return; } + if (stmt is TokenStmtState) { GenerateStmtState ((TokenStmtState)stmt); return; } + if (stmt is TokenStmtSwitch) { GenerateStmtSwitch ((TokenStmtSwitch)stmt); return; } + if (stmt is TokenStmtThrow) { GenerateStmtThrow ((TokenStmtThrow)stmt); return; } + if (stmt is TokenStmtTry) { GenerateStmtTry ((TokenStmtTry)stmt); return; } + if (stmt is TokenStmtVarIniDef) { GenerateStmtVarIniDef ((TokenStmtVarIniDef)stmt); return; } + if (stmt is TokenStmtWhile) { GenerateStmtWhile ((TokenStmtWhile)stmt); return; } + throw new Exception ("unknown TokenStmt type " + stmt.GetType ().ToString ()); + } + + /** + * @brief generate statement block (ie, with braces) + */ + private void GenerateStmtBlock (TokenStmtBlock stmtBlock) + { + if (!mightGetHere) return; + + /* + * Push new current statement block pointer for anyone who cares. + */ + TokenStmtBlock oldStmtBlock = curStmtBlock; + curStmtBlock = stmtBlock; + + /* + * Output the statements that make up the block. + */ + for (Token t = stmtBlock.statements; t != null; t = t.nextToken) { + GenerateStmt ((TokenStmt)t); + } + + /* + * Pop the current statement block. + */ + curStmtBlock = oldStmtBlock; + } + + /** + * @brief output code for a 'break' statement + */ + private void GenerateStmtBreak (TokenStmtBreak breakStmt) + { + if (!mightGetHere) return; + + /* + * Make sure we are in a breakable situation. + */ + if (curBreakTarg == null) { + ErrorMsg (breakStmt, "not in a breakable situation"); + return; + } + + /* + * Tell anyone who cares that the break target was actually used. + */ + curBreakTarg.used = true; + + /* + * Output the instructions. + */ + EmitJumpCode (curBreakTarg.label, curBreakTarg.block, breakStmt); + } + + /** + * @brief output code for a 'continue' statement + */ + private void GenerateStmtCont (TokenStmtCont contStmt) + { + if (!mightGetHere) return; + + /* + * Make sure we are in a contable situation. + */ + if (curContTarg == null) { + ErrorMsg (contStmt, "not in a continueable situation"); + return; + } + + /* + * Tell anyone who cares that the continue target was actually used. + */ + curContTarg.used = true; + + /* + * Output the instructions. + */ + EmitJumpCode (curContTarg.label, curContTarg.block, contStmt); + } + + /** + * @brief output code for a 'do' statement + */ + private void GenerateStmtDo (TokenStmtDo doStmt) + { + if (!mightGetHere) return; + + BreakContTarg oldBreakTarg = curBreakTarg; + BreakContTarg oldContTarg = curContTarg; + ScriptMyLabel loopLabel = ilGen.DefineLabel ("doloop_" + doStmt.Unique); + + curBreakTarg = new BreakContTarg (this, "dobreak_" + doStmt.Unique); + curContTarg = new BreakContTarg (this, "docont_" + doStmt.Unique); + + ilGen.MarkLabel (loopLabel); + GenerateStmt (doStmt.bodyStmt); + if (curContTarg.used) { + ilGen.MarkLabel (curContTarg.label); + mightGetHere = true; + } + + if (mightGetHere) { + EmitCallCheckRun (doStmt, false); + CompValu testRVal = GenerateFromRVal (doStmt.testRVal); + if (IsConstBoolExprTrue (testRVal)) { + + /* + * Unconditional looping, unconditional branch and + * say we never fall through to next statement. + */ + ilGen.Emit (doStmt, OpCodes.Br, loopLabel); + mightGetHere = false; + } else { + + /* + * Conditional looping, test and brach back to top of loop. + */ + testRVal.PushVal (this, doStmt.testRVal, tokenTypeBool); + ilGen.Emit (doStmt, OpCodes.Brtrue, loopLabel); + } + } + + /* + * If 'break' statement was used, output target label. + * And assume that since a 'break' statement was used, it's possible for the code to get here. + */ + if (curBreakTarg.used) { + ilGen.MarkLabel (curBreakTarg.label); + mightGetHere = true; + } + + curBreakTarg = oldBreakTarg; + curContTarg = oldContTarg; + } + + /** + * @brief output code for a 'for' statement + */ + private void GenerateStmtFor (TokenStmtFor forStmt) + { + if (!mightGetHere) return; + + BreakContTarg oldBreakTarg = curBreakTarg; + BreakContTarg oldContTarg = curContTarg; + ScriptMyLabel loopLabel = ilGen.DefineLabel ("forloop_" + forStmt.Unique); + + curBreakTarg = new BreakContTarg (this, "forbreak_" + forStmt.Unique); + curContTarg = new BreakContTarg (this, "forcont_" + forStmt.Unique); + + if (forStmt.initStmt != null) { + GenerateStmt (forStmt.initStmt); + } + ilGen.MarkLabel (loopLabel); + + /* + * See if we have a test expression that is other than a constant TRUE. + * If so, test it and conditionally branch to end if false. + */ + if (forStmt.testRVal != null) { + CompValu testRVal = GenerateFromRVal (forStmt.testRVal); + if (!IsConstBoolExprTrue (testRVal)) { + testRVal.PushVal (this, forStmt.testRVal, tokenTypeBool); + ilGen.Emit (forStmt, OpCodes.Brfalse, curBreakTarg.label); + curBreakTarg.used = true; + } + } + + /* + * Output loop body. + */ + GenerateStmt (forStmt.bodyStmt); + + /* + * Here's where a 'continue' statement jumps to. + */ + if (curContTarg.used) { + ilGen.MarkLabel (curContTarg.label); + mightGetHere = true; + } + + if (mightGetHere) { + + /* + * After checking for excessive CPU time, output increment statement, if any. + */ + EmitCallCheckRun (forStmt, false); + if (forStmt.incrRVal != null) { + GenerateFromRVal (forStmt.incrRVal); + } + + /* + * Unconditional branch back to beginning of loop. + */ + ilGen.Emit (forStmt, OpCodes.Br, loopLabel); + } + + /* + * If test needs label, output label for it to jump to. + * Otherwise, clear mightGetHere as we know loop never + * falls out the bottom. + */ + mightGetHere = curBreakTarg.used; + if (mightGetHere) { + ilGen.MarkLabel (curBreakTarg.label); + } + + curBreakTarg = oldBreakTarg; + curContTarg = oldContTarg; + } + + private void GenerateStmtForEach (TokenStmtForEach forEachStmt) + { + if (!mightGetHere) return; + + BreakContTarg oldBreakTarg = curBreakTarg; + BreakContTarg oldContTarg = curContTarg; + CompValu keyLVal = null; + CompValu valLVal = null; + CompValu arrayRVal = GenerateFromRVal (forEachStmt.arrayRVal); + + if (forEachStmt.keyLVal != null) { + keyLVal = GenerateFromLVal (forEachStmt.keyLVal); + if (!(keyLVal.type is TokenTypeObject)) { + ErrorMsg (forEachStmt.arrayRVal, "must be object"); + } + } + if (forEachStmt.valLVal != null) { + valLVal = GenerateFromLVal (forEachStmt.valLVal); + if (!(valLVal.type is TokenTypeObject)) { + ErrorMsg (forEachStmt.arrayRVal, "must be object"); + } + } + if (!(arrayRVal.type is TokenTypeArray)) { + ErrorMsg (forEachStmt.arrayRVal, "must be an array"); + } + + curBreakTarg = new BreakContTarg (this, "foreachbreak_" + forEachStmt.Unique); + curContTarg = new BreakContTarg (this, "foreachcont_" + forEachStmt.Unique); + + CompValuTemp indexVar = new CompValuTemp (new TokenTypeInt (forEachStmt), this); + ScriptMyLabel loopLabel = ilGen.DefineLabel ("foreachloop_" + forEachStmt.Unique); + + // indexVar = 0 + ilGen.Emit (forEachStmt, OpCodes.Ldc_I4_0); + indexVar.Pop (this, forEachStmt); + + ilGen.MarkLabel (loopLabel); + + // key = array.__pub_index (indexVar); + // if (key == null) goto curBreakTarg; + if (keyLVal != null) { + keyLVal.PopPre (this, forEachStmt.keyLVal); + arrayRVal.PushVal (this, forEachStmt.arrayRVal); + indexVar.PushVal (this, forEachStmt); + ilGen.Emit (forEachStmt, OpCodes.Call, xmrArrPubIndexMethod); + keyLVal.PopPost (this, forEachStmt.keyLVal); + keyLVal.PushVal (this, forEachStmt.keyLVal); + ilGen.Emit (forEachStmt, OpCodes.Brfalse, curBreakTarg.label); + curBreakTarg.used = true; + } + + // val = array._pub_value (indexVar); + // if (val == null) goto curBreakTarg; + if (valLVal != null) { + valLVal.PopPre (this, forEachStmt.valLVal); + arrayRVal.PushVal (this, forEachStmt.arrayRVal); + indexVar.PushVal (this, forEachStmt); + ilGen.Emit (forEachStmt, OpCodes.Call, xmrArrPubValueMethod); + valLVal.PopPost (this, forEachStmt.valLVal); + if (keyLVal == null) { + valLVal.PushVal (this, forEachStmt.valLVal); + ilGen.Emit (forEachStmt, OpCodes.Brfalse, curBreakTarg.label); + curBreakTarg.used = true; + } + } + + // indexVar ++; + indexVar.PushVal (this, forEachStmt); + ilGen.Emit (forEachStmt, OpCodes.Ldc_I4_1); + ilGen.Emit (forEachStmt, OpCodes.Add); + indexVar.Pop (this, forEachStmt); + + // body statement + GenerateStmt (forEachStmt.bodyStmt); + + // continue label + if (curContTarg.used) { + ilGen.MarkLabel (curContTarg.label); + mightGetHere = true; + } + + // call CheckRun() + if (mightGetHere) { + EmitCallCheckRun (forEachStmt, false); + ilGen.Emit (forEachStmt, OpCodes.Br, loopLabel); + } + + // break label + ilGen.MarkLabel (curBreakTarg.label); + mightGetHere = true; + + curBreakTarg = oldBreakTarg; + curContTarg = oldContTarg; + } + + /** + * @brief output code for an 'if' statement + * Braces are necessary because what may be one statement for trueStmt or elseStmt in + * the script may translate to more than one statement in the resultant C# code. + */ + private void GenerateStmtIf (TokenStmtIf ifStmt) + { + if (!mightGetHere) return; + + bool constVal; + + /* + * Test condition and see if constant test expression. + */ + CompValu testRVal = GenerateFromRVal (ifStmt.testRVal); + if (IsConstBoolExpr (testRVal, out constVal)) { + + /* + * Constant, output just either the true or else part. + */ + if (constVal) { + GenerateStmt (ifStmt.trueStmt); + } else if (ifStmt.elseStmt != null) { + GenerateStmt (ifStmt.elseStmt); + } + } else if (ifStmt.elseStmt == null) { + + /* + * This is an 'if' statement without an 'else' clause. + */ + testRVal.PushVal (this, ifStmt.testRVal, tokenTypeBool); + ScriptMyLabel doneLabel = ilGen.DefineLabel ("ifdone_" + ifStmt.Unique); + ilGen.Emit (ifStmt, OpCodes.Brfalse, doneLabel); // brfalse doneLabel + GenerateStmt (ifStmt.trueStmt); // generate true body code + ilGen.MarkLabel (doneLabel); + mightGetHere = true; // there's always a possibility of getting here + } else { + + /* + * This is an 'if' statement with an 'else' clause. + */ + testRVal.PushVal (this, ifStmt.testRVal, tokenTypeBool); + ScriptMyLabel elseLabel = ilGen.DefineLabel ("ifelse_" + ifStmt.Unique); + ilGen.Emit (ifStmt, OpCodes.Brfalse, elseLabel); // brfalse elseLabel + GenerateStmt (ifStmt.trueStmt); // generate true body code + bool trueMightGetHere = mightGetHere; // save whether or not true falls through + ScriptMyLabel doneLabel = ilGen.DefineLabel ("ifdone_" + ifStmt.Unique); + ilGen.Emit (ifStmt, OpCodes.Br, doneLabel); // branch to done + ilGen.MarkLabel (elseLabel); // beginning of else code + mightGetHere = true; // the top of the else might be executed + GenerateStmt (ifStmt.elseStmt); // output else code + ilGen.MarkLabel (doneLabel); // where end of true clause code branches to + mightGetHere |= trueMightGetHere; // gets this far if either true or else falls through + } + } + + /** + * @brief output code for a 'jump' statement + */ + private void GenerateStmtJump (TokenStmtJump jumpStmt) + { + if (!mightGetHere) return; + + /* + * Make sure the target label is defined somewhere in the function. + */ + TokenStmtLabel stmtLabel; + if (!curDeclFunc.labels.TryGetValue (jumpStmt.label.val, out stmtLabel)) { + ErrorMsg (jumpStmt, "undefined label " + jumpStmt.label.val); + return; + } + if (!stmtLabel.labelTagged) { + stmtLabel.labelStruct = ilGen.DefineLabel ("jump_" + stmtLabel.name.val); + stmtLabel.labelTagged = true; + } + + /* + * Emit instructions to do the jump. + */ + EmitJumpCode (stmtLabel.labelStruct, stmtLabel.block, jumpStmt); + } + + /** + * @brief Emit code to jump to a label + * @param target = label being jumped to + * @param targetsBlock = { ... } the label is defined in + */ + private void EmitJumpCode (ScriptMyLabel target, TokenStmtBlock targetsBlock, Token errorAt) + { + /* + * Jumps never fall through. + */ + mightGetHere = false; + + /* + * Find which block the target label is in. Must be in this or an outer block, + * no laterals allowed. And if we exit a try/catch block, use Leave instead of Br. + * + * jump lateral; + * { + * @lateral; + * } + */ + bool useLeave = false; + TokenStmtBlock stmtBlock; + Stack finallyBlocksCalled = new Stack (); + for (stmtBlock = curStmtBlock; stmtBlock != targetsBlock; stmtBlock = stmtBlock.outerStmtBlock) { + if (stmtBlock == null) { + ErrorMsg (errorAt, "no lateral jumps allowed"); + return; + } + if (stmtBlock.isFinally) { + ErrorMsg (errorAt, "cannot jump out of finally"); + return; + } + if (stmtBlock.isTry || stmtBlock.isCatch) useLeave = true; + if ((stmtBlock.tryStmt != null) && (stmtBlock.tryStmt.finallyStmt != null)) { + finallyBlocksCalled.Push (stmtBlock.tryStmt); + } + } + + /* + * If popping through more than one finally block, we have to break it down for the stack + * capture and restore code, one finally block at a time. + * + * try { + * try { + * try { + * jump exit; + * } finally { + * llOwnerSay ("exiting inner"); + * } + * } finally { + * llOwnerSay ("exiting middle"); + * } + * } finally { + * llOwnerSay ("exiting outer"); + * } + * @exit; + * + * try { + * try { + * try { + * jump intr2_exit; <<< gets its own tryNo call label so inner try knows where to restore to + * } finally { + * llOwnerSay ("exiting inner"); + * } + * jump outtry2; + * @intr2_exit; jump intr1_exit; <<< gets its own tryNo call label so middle try knows where to restore to + * @outtry2; + * } finally { + * llOwnerSay ("exiting middle"); + * } + * jump outtry1; + * @intr1_exit: jump exit; <<< gets its own tryNo call label so outer try knows where to restore to + * @outtry1; + * } finally { + * llOwnerSay ("exiting outer"); + * } + * @exit; + */ + int level = 0; + while (finallyBlocksCalled.Count > 1) { + TokenStmtTry finallyBlock = finallyBlocksCalled.Pop (); + string intername = "intr" + (++ level) + "_" + target.name; + IntermediateLeave iLeave; + if (!finallyBlock.iLeaves.TryGetValue (intername, out iLeave)) { + iLeave = new IntermediateLeave (); + iLeave.jumpIntoLabel = ilGen.DefineLabel (intername); + iLeave.jumpAwayLabel = target; + finallyBlock.iLeaves.Add (intername, iLeave); + } + target = iLeave.jumpIntoLabel; + } + + /* + * Finally output the branch/leave opcode. + * If using Leave, prefix with a call label in case the corresponding finally block + * calls CheckRun() and that CheckRun() captures the stack, it will have a point to + * restore to that will properly jump back into the finally block. + */ + if (useLeave) { + new CallLabel (this, errorAt); + ilGen.Emit (errorAt, OpCodes.Leave, target); + openCallLabel = null; + } else { + ilGen.Emit (errorAt, OpCodes.Br, target); + } + } + + /** + * @brief output code for a jump target label statement. + * If there are any backward jumps to the label, do a CheckRun() also. + */ + private void GenerateStmtLabel (TokenStmtLabel labelStmt) + { + if (!labelStmt.labelTagged) { + labelStmt.labelStruct = ilGen.DefineLabel ("jump_" + labelStmt.name.val); + labelStmt.labelTagged = true; + } + ilGen.MarkLabel (labelStmt.labelStruct); + if (labelStmt.hasBkwdRefs) { + EmitCallCheckRun (labelStmt, false); + } + + /* + * We are going to say that the label falls through. + * It would be nice if we could analyze all referencing + * goto's to see if all of them are not used but we are + * going to assume that if the script writer put a label + * somewhere, it is probably going to be used. + */ + mightGetHere = true; + } + + /** + * @brief Generate code for a script-defined type's .$new() method. + * It is used to malloc the object and initialize it. + * It is defined as a script-defined type static method, so the object level + * method gets the XMRInstance pointer passed as arg 0, and the method is + * supposed to return the allocated and constructed XMRSDTypeClObj + * object pointer. + */ + private void GenerateStmtNewobj (TokenStmtNewobj newobjStmt) + { + /* + * First off, malloc a new empty XMRSDTypeClObj object + * then call the XMRSDTypeClObj()-level constructor. + * Store the result in local var $objptr. + */ + newobjStmt.objptrVar.location.PopPre (this, newobjStmt); + ilGen.Emit (newobjStmt, OpCodes.Ldarg_0); + ilGen.Emit (newobjStmt, OpCodes.Ldc_I4, curDeclFunc.sdtClass.sdTypeIndex); + ilGen.Emit (newobjStmt, OpCodes.Newobj, sdtClassConstructorInfo); + newobjStmt.objptrVar.location.PopPost (this, newobjStmt); + + /* + * Now call the script-level constructor. + * Pass the object pointer in $objptr as it's 'this' argument. + * The rest of the args are the script-visible args and are just copied from $new() call. + */ + GenerateFromRValCall (newobjStmt.rValCall); + + /* + * Put object pointer in retval so it gets returned to caller. + */ + newobjStmt.objptrVar.location.PushVal (this, newobjStmt); + ilGen.Emit (newobjStmt, OpCodes.Stloc, retValue); + + /* + * Exit the function like a return statement. + * And thus we don't fall through. + */ + ilGen.Emit (newobjStmt, OpCodes.Leave, retLabel); + mightGetHere = false; + } + + /** + * @brief output code for a return statement. + * @param retStmt = return statement token, including return value if any + */ + private void GenerateStmtRet (TokenStmtRet retStmt) + { + if (!mightGetHere) return; + + for (TokenStmtBlock stmtBlock = curStmtBlock; stmtBlock != null; stmtBlock = stmtBlock.outerStmtBlock) { + if (stmtBlock.isFinally) { + ErrorMsg (retStmt, "cannot return out of finally"); + return; + } + } + + if (curDeclFunc.retType is TokenTypeVoid) { + if (retStmt.rVal != null) { + ErrorMsg (retStmt, "function returns void, no value allowed"); + return; + } + } else { + if (retStmt.rVal == null) { + ErrorMsg (retStmt, "function requires return value type " + curDeclFunc.retType.ToString ()); + return; + } + CompValu rVal = GenerateFromRVal (retStmt.rVal); + rVal.PushVal (this, retStmt.rVal, curDeclFunc.retType); + ilGen.Emit (retStmt, OpCodes.Stloc, retValue); + } + + /* + * Use a OpCodes.Leave instruction to break out of any try { } blocks. + * All Leave's inside script-defined try { } need call labels (see GenerateStmtTry()). + */ + bool brokeOutOfTry = false; + for (TokenStmtBlock stmtBlock = curStmtBlock; stmtBlock != null; stmtBlock = stmtBlock.outerStmtBlock) { + if (stmtBlock.isTry) { + brokeOutOfTry = true; + break; + } + } + if (brokeOutOfTry) new CallLabel (this, retStmt); + ilGen.Emit (retStmt, OpCodes.Leave, retLabel); + if (brokeOutOfTry) openCallLabel = null; + + /* + * 'return' statements never fall through. + */ + mightGetHere = false; + } + + /** + * @brief the statement is just an expression, most likely an assignment or a ++ or -- thing. + */ + private void GenerateStmtRVal (TokenStmtRVal rValStmt) + { + if (!mightGetHere) return; + + GenerateFromRVal (rValStmt.rVal); + } + + /** + * @brief generate code for a 'state' statement that transitions state. + * It sets the new state by throwing a ScriptChangeStateException. + */ + private void GenerateStmtState (TokenStmtState stateStmt) + { + if (!mightGetHere) return; + + int index = 0; // 'default' state + + /* + * Set new state value by throwing an exception. + * These exceptions aren't catchable by script-level try { } catch { }. + */ + if ((stateStmt.state != null) && !stateIndices.TryGetValue (stateStmt.state.val, out index)) { + // The moron XEngine compiles scripts that reference undefined states. + // So rather than produce a compile-time error, we'll throw an exception at runtime. + // ErrorMsg (stateStmt, "undefined state " + stateStmt.state.val); + + // throw new UndefinedStateException (stateStmt.state.val); + ilGen.Emit (stateStmt, OpCodes.Ldstr, stateStmt.state.val); + ilGen.Emit (stateStmt, OpCodes.Newobj, scriptUndefinedStateExceptionConstructorInfo); + } else { + ilGen.Emit (stateStmt, OpCodes.Ldc_I4, index); // new state's index + ilGen.Emit (stateStmt, OpCodes.Newobj, scriptChangeStateExceptionConstructorInfo); + } + ilGen.Emit (stateStmt, OpCodes.Throw); + + /* + * 'state' statements never fall through. + */ + mightGetHere = false; + } + + /** + * @brief output code for a 'switch' statement + */ + private void GenerateStmtSwitch (TokenStmtSwitch switchStmt) + { + if (!mightGetHere) return; + + /* + * Output code to calculate index. + */ + CompValu testRVal = GenerateFromRVal (switchStmt.testRVal); + + /* + * Generate code based on string or integer index. + */ + if ((testRVal.type is TokenTypeKey) || (testRVal.type is TokenTypeStr)) { + GenerateStmtSwitchStr (testRVal, switchStmt); + } else { + GenerateStmtSwitchInt (testRVal, switchStmt); + } + } + + private void GenerateStmtSwitchInt (CompValu testRVal, TokenStmtSwitch switchStmt) + { + testRVal.PushVal (this, switchStmt.testRVal, tokenTypeInt); + + BreakContTarg oldBreakTarg = curBreakTarg; + ScriptMyLabel defaultLabel = null; + TokenSwitchCase sortedCases = null; + TokenSwitchCase defaultCase = null; + + curBreakTarg = new BreakContTarg (this, "switchbreak_" + switchStmt.Unique); + + /* + * Build list of cases sorted by ascending values. + * There should not be any overlapping of values. + */ + for (TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) { + thisCase.label = ilGen.DefineLabel ("case_" + thisCase.Unique); + + /* + * The default case if any, goes in its own separate slot. + */ + if (thisCase.rVal1 == null) { + if (defaultCase != null) { + ErrorMsg (thisCase, "only one default case allowed"); + ErrorMsg (defaultCase, "...prior default case"); + return; + } + defaultCase = thisCase; + defaultLabel = thisCase.label; + continue; + } + + /* + * Evaluate case operands, they must be compile-time integer constants. + */ + CompValu rVal = GenerateFromRVal (thisCase.rVal1); + if (!IsConstIntExpr (rVal, out thisCase.val1)) { + ErrorMsg (thisCase.rVal1, "must be compile-time char or integer constant"); + return; + } + thisCase.val2 = thisCase.val1; + if (thisCase.rVal2 != null) { + rVal = GenerateFromRVal (thisCase.rVal2); + if (!IsConstIntExpr (rVal, out thisCase.val2)) { + ErrorMsg (thisCase.rVal2, "must be compile-time char or integer constant"); + return; + } + } + if (thisCase.val2 < thisCase.val1) { + ErrorMsg (thisCase.rVal2, "must be .ge. first value for the case"); + return; + } + + /* + * Insert into list, sorted by value. + * Note that both limits are inclusive. + */ + TokenSwitchCase lastCase = null; + TokenSwitchCase nextCase; + for (nextCase = sortedCases; nextCase != null; nextCase = nextCase.nextSortedCase) { + if (nextCase.val1 > thisCase.val2) break; + if (nextCase.val2 >= thisCase.val1) { + ErrorMsg (thisCase, "value used by previous case"); + ErrorMsg (nextCase, "...previous case"); + return; + } + lastCase = nextCase; + } + thisCase.nextSortedCase = nextCase; + if (lastCase == null) { + sortedCases = thisCase; + } else { + lastCase.nextSortedCase = thisCase; + } + } + + if (defaultLabel == null) { + defaultLabel = ilGen.DefineLabel ("default_" + switchStmt.Unique); + } + + /* + * Output code to jump to the case statement's labels based on integer index on stack. + * Note that each case still has the integer index on stack when jumped to. + */ + int offset = 0; + for (TokenSwitchCase thisCase = sortedCases; thisCase != null;) { + + /* + * Scan through list of cases to find the maximum number of cases who's numvalues-to-case ratio + * is from 0.5 to 2.0. If such a group is found, use a CIL switch for them. If not, just use a + * compare-and-branch for the current case. + */ + int numCases = 0; + int numFound = 0; + int lowValue = thisCase.val1; + int numValues = 0; + for (TokenSwitchCase scanCase = thisCase; scanCase != null; scanCase = scanCase.nextSortedCase) { + int nVals = scanCase.val2 - thisCase.val1 + 1; + double ratio = (double)nVals / (double)(++ numCases); + if ((ratio >= 0.5) && (ratio <= 2.0)) { + numFound = numCases; + numValues = nVals; + } + } + if (numFound > 1) { + + /* + * There is a group of case's, starting with thisCase, that fall within our criteria, ie, + * that have a nice density of meaningful jumps. + * + * So first generate an array of jumps to the default label (explicit or implicit). + */ + ScriptMyLabel[] labels = new ScriptMyLabel[numValues]; + for (int i = 0; i < numValues; i ++) { + labels[i] = defaultLabel; + } + + /* + * Next, for each case in that group, fill in the corresponding array entries to jump to + * that case's label. + */ + do { + for (int i = thisCase.val1; i <= thisCase.val2; i ++) { + labels[i-lowValue] = thisCase.label; + } + thisCase = thisCase.nextSortedCase; + } while (-- numFound > 0); + + /* + * Subtract the low value and do the computed jump. + * The OpCodes.Switch falls through if out of range (unsigned compare). + */ + if (offset != lowValue) { + ilGen.Emit (switchStmt, OpCodes.Ldc_I4, lowValue - offset); + ilGen.Emit (switchStmt, OpCodes.Sub); + offset = lowValue; + } + ilGen.Emit (switchStmt, OpCodes.Dup); + ilGen.Emit (switchStmt, OpCodes.Switch, labels); + } else { + + /* + * It's not economical to do with a computed jump, so output a subtract/compare/branch + * for thisCase. + */ + if (lowValue == thisCase.val2) { + ilGen.Emit (switchStmt, OpCodes.Dup); + ilGen.Emit (switchStmt, OpCodes.Ldc_I4, lowValue - offset); + ilGen.Emit (switchStmt, OpCodes.Beq, thisCase.label); + } else { + if (offset != lowValue) { + ilGen.Emit (switchStmt, OpCodes.Ldc_I4, lowValue - offset); + ilGen.Emit (switchStmt, OpCodes.Sub); + offset = lowValue; + } + ilGen.Emit (switchStmt, OpCodes.Dup); + ilGen.Emit (switchStmt, OpCodes.Ldc_I4, thisCase.val2 - offset); + ilGen.Emit (switchStmt, OpCodes.Ble_Un, thisCase.label); + } + thisCase = thisCase.nextSortedCase; + } + } + ilGen.Emit (switchStmt, OpCodes.Br, defaultLabel); + + /* + * Output code for the cases themselves, in the order given by the programmer, + * so they fall through as programmer wants. This includes the default case, if any. + * + * Each label is jumped to with the index still on the stack. So pop it off in case + * the case body does a goto outside the switch or a return. If the case body might + * fall through to the next case or the bottom of the switch, push a zero so the stack + * matches in all cases. + */ + for (TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) { + ilGen.MarkLabel (thisCase.label); // the branch comes here + ilGen.Emit (thisCase, OpCodes.Pop); // pop the integer index off stack + mightGetHere = true; // it's possible to get here + for (TokenStmt stmt = thisCase.stmts; stmt != null; stmt = (TokenStmt)(stmt.nextToken)) { + GenerateStmt (stmt); // output the case/explicit default body + } + if (mightGetHere) { + ilGen.Emit (thisCase, OpCodes.Ldc_I4_0); + // in case we fall through, push a dummy integer index + } + } + + /* + * If no explicit default case, output the default label here. + */ + if (defaultCase == null) { + ilGen.MarkLabel (defaultLabel); + mightGetHere = true; + } + + /* + * If the last case of the switch falls through out the bottom, + * we have to pop the index still on the stack. + */ + if (mightGetHere) { + ilGen.Emit (switchStmt, OpCodes.Pop); + } + + /* + * Output the 'break' statement target label. + * Note that the integer index is not on the stack at this point. + */ + if (curBreakTarg.used) { + ilGen.MarkLabel (curBreakTarg.label); + mightGetHere = true; + } + + curBreakTarg = oldBreakTarg; + } + + private void GenerateStmtSwitchStr (CompValu testRVal, TokenStmtSwitch switchStmt) + { + BreakContTarg oldBreakTarg = curBreakTarg; + ScriptMyLabel defaultLabel = null; + TokenSwitchCase caseTreeTop = null; + TokenSwitchCase defaultCase = null; + + curBreakTarg = new BreakContTarg (this, "switchbreak_" + switchStmt.Unique); + + /* + * Make sure value is in a temp so we don't compute it more than once. + */ + if (!(testRVal is CompValuTemp)) { + CompValuTemp temp = new CompValuTemp (testRVal.type, this); + testRVal.PushVal (this, switchStmt); + temp.Pop (this, switchStmt); + testRVal = temp; + } + + /* + * Build tree of cases. + * There should not be any overlapping of values. + */ + for (TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) { + thisCase.label = ilGen.DefineLabel ("case"); + + /* + * The default case if any, goes in its own separate slot. + */ + if (thisCase.rVal1 == null) { + if (defaultCase != null) { + ErrorMsg (thisCase, "only one default case allowed"); + ErrorMsg (defaultCase, "...prior default case"); + return; + } + defaultCase = thisCase; + defaultLabel = thisCase.label; + continue; + } + + /* + * Evaluate case operands, they must be compile-time string constants. + */ + CompValu rVal = GenerateFromRVal (thisCase.rVal1); + if (!IsConstStrExpr (rVal, out thisCase.str1)) { + ErrorMsg (thisCase.rVal1, "must be compile-time string constant"); + continue; + } + thisCase.str2 = thisCase.str1; + if (thisCase.rVal2 != null) { + rVal = GenerateFromRVal (thisCase.rVal2); + if (!IsConstStrExpr (rVal, out thisCase.str2)) { + ErrorMsg (thisCase.rVal2, "must be compile-time string constant"); + continue; + } + } + if (String.Compare (thisCase.str2, thisCase.str1, StringComparison.Ordinal) < 0) { + ErrorMsg (thisCase.rVal2, "must be .ge. first value for the case"); + continue; + } + + /* + * Insert into list, sorted by value. + * Note that both limits are inclusive. + */ + caseTreeTop = InsertCaseInTree (caseTreeTop, thisCase); + } + + /* + * Balance tree so we end up generating code that does O(log2 n) comparisons. + */ + caseTreeTop = BalanceTree (caseTreeTop); + + /* + * Output compare and branch instructions in a tree-like fashion so we do O(log2 n) comparisons. + */ + if (defaultLabel == null) { + defaultLabel = ilGen.DefineLabel ("default"); + } + OutputStrCase (testRVal, caseTreeTop, defaultLabel); + + /* + * Output code for the cases themselves, in the order given by the programmer, + * so they fall through as programmer wants. This includes the default case, if any. + */ + for (TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) { + ilGen.MarkLabel (thisCase.label); // the branch comes here + mightGetHere = true; // it's possible to get here + for (TokenStmt stmt = thisCase.stmts; stmt != null; stmt = (TokenStmt)(stmt.nextToken)) { + GenerateStmt (stmt); // output the case/explicit default body + } + } + + /* + * If no explicit default case, output the default label here. + */ + if (defaultCase == null) { + ilGen.MarkLabel (defaultLabel); + mightGetHere = true; + } + + /* + * Output the 'break' statement target label. + */ + if (curBreakTarg.used) { + ilGen.MarkLabel (curBreakTarg.label); + mightGetHere = true; + } + + curBreakTarg = oldBreakTarg; + } + + /** + * @brief Insert a case in a tree of cases + * @param r = root of existing cases to insert into + * @param n = new case being inserted + * @returns new root with new case inserted + */ + private TokenSwitchCase InsertCaseInTree (TokenSwitchCase r, TokenSwitchCase n) + { + if (r == null) return n; + + TokenSwitchCase t = r; + while (true) { + if (String.Compare (n.str2, t.str1, StringComparison.Ordinal) < 0) { + if (t.lowerCase == null) { + t.lowerCase = n; + break; + } + t = t.lowerCase; + continue; + } + if (String.Compare (n.str1, t.str2, StringComparison.Ordinal) > 0) { + if (t.higherCase == null) { + t.higherCase = n; + break; + } + t = t.higherCase; + continue; + } + ErrorMsg (n, "duplicate case"); + ErrorMsg (r, "...duplicate of"); + break; + } + return r; + } + + /** + * @brief Balance a tree so left & right halves contain same number within +-1 + * @param r = root of tree to balance + * @returns new root + */ + private static TokenSwitchCase BalanceTree (TokenSwitchCase r) + { + if (r == null) return r; + + int lc = CountTree (r.lowerCase); + int hc = CountTree (r.higherCase); + TokenSwitchCase n, x; + + /* + * If lower side is heavy, move highest nodes from lower side to + * higher side until balanced. + */ + while (lc > hc + 1) { + x = ExtractHighest (r.lowerCase, out n); + n.lowerCase = x; + n.higherCase = r; + r.lowerCase = null; + r = n; + lc --; + hc ++; + } + + /* + * If higher side is heavy, move lowest nodes from higher side to + * lower side until balanced. + */ + while (hc > lc + 1) { + x = ExtractLowest (r.higherCase, out n); + n.higherCase = x; + n.lowerCase = r; + r.higherCase = null; + r = n; + lc ++; + hc --; + } + + /* + * Now balance each side because they can be lopsided individually. + */ + r.lowerCase = BalanceTree (r.lowerCase); + r.higherCase = BalanceTree (r.higherCase); + return r; + } + + /** + * @brief Get number of nodes in a tree + * @param n = root of tree to count + * @returns number of nodes including root + */ + private static int CountTree (TokenSwitchCase n) + { + if (n == null) return 0; + return 1 + CountTree (n.lowerCase) + CountTree (n.higherCase); + } + + // Extract highest node from a tree + // @param r = root of tree to extract highest from + // @returns new root after node has been extracted + // n = node that was extracted from tree + private static TokenSwitchCase ExtractHighest (TokenSwitchCase r, out TokenSwitchCase n) + { + if (r.higherCase == null) { + n = r; + return r.lowerCase; + } + r.higherCase = ExtractHighest (r.higherCase, out n); + return r; + } + + // Extract lowest node from a tree + // @param r = root of tree to extract lowest from + // @returns new root after node has been extracted + // n = node that was extracted from tree + private static TokenSwitchCase ExtractLowest (TokenSwitchCase r, out TokenSwitchCase n) + { + if (r.lowerCase == null) { + n = r; + return r.higherCase; + } + r.lowerCase = ExtractLowest (r.lowerCase, out n); + return r; + } + + /** + * Output code for string-style case of a switch/case to jump to the script code associated with the case. + * @param testRVal = value being switched on + * @param thisCase = case that the code is being output for + * @param defaultLabel = where the default clause is (or past all cases if none) + * Note: + * Outputs code for this case and the lowerCase and higherCases if any. + * If no lowerCase or higherCase, outputs a br to defaultLabel so this code never falls through. + */ + private void OutputStrCase (CompValu testRVal, TokenSwitchCase thisCase, ScriptMyLabel defaultLabel) + { + /* + * If nothing lower on tree and there is a single case value, + * just do one compare for equality. + */ + if ((thisCase.lowerCase == null) && (thisCase.higherCase == null) && (thisCase.str1 == thisCase.str2)) { + testRVal.PushVal (this, thisCase, tokenTypeStr); + ilGen.Emit (thisCase, OpCodes.Ldstr, thisCase.str1); + ilGen.Emit (thisCase, OpCodes.Ldc_I4, (int)StringComparison.Ordinal); + ilGen.Emit (thisCase, OpCodes.Call, stringCompareMethodInfo); + ilGen.Emit (thisCase, OpCodes.Brfalse, thisCase.label); + ilGen.Emit (thisCase, OpCodes.Br, defaultLabel); + return; + } + + /* + * Determine where to jump if switch value is lower than lower case value. + */ + ScriptMyLabel lowerLabel = defaultLabel; + if (thisCase.lowerCase != null) { + lowerLabel = ilGen.DefineLabel ("lower"); + } + + /* + * If single case value, put comparison result in this temp. + */ + CompValuTemp cmpv1 = null; + if (thisCase.str1 == thisCase.str2) { + cmpv1 = new CompValuTemp (tokenTypeInt, this); + } + + /* + * If switch value .lt. lower case value, jump to lower label. + * Maybe save comparison result in a temp. + */ + testRVal.PushVal (this, thisCase, tokenTypeStr); + ilGen.Emit (thisCase, OpCodes.Ldstr, thisCase.str1); + ilGen.Emit (thisCase, OpCodes.Ldc_I4, (int)StringComparison.Ordinal); + ilGen.Emit (thisCase, OpCodes.Call, stringCompareMethodInfo); + if (cmpv1 != null) { + ilGen.Emit (thisCase, OpCodes.Dup); + cmpv1.Pop (this, thisCase); + } + ilGen.Emit (thisCase, OpCodes.Ldc_I4_0); + ilGen.Emit (thisCase, OpCodes.Blt, lowerLabel); + + /* + * If switch value .le. higher case value, jump to case code. + * Maybe get comparison from the temp. + */ + if (cmpv1 == null) { + testRVal.PushVal (this, thisCase, tokenTypeStr); + ilGen.Emit (thisCase, OpCodes.Ldstr, thisCase.str2); + ilGen.Emit (thisCase, OpCodes.Ldc_I4, (int)StringComparison.Ordinal); + ilGen.Emit (thisCase, OpCodes.Call, stringCompareMethodInfo); + } else { + cmpv1.PushVal (this, thisCase); + } + ilGen.Emit (thisCase, OpCodes.Ldc_I4_0); + ilGen.Emit (thisCase, OpCodes.Ble, thisCase.label); + + /* + * Output code for higher comparison if any. + */ + if (thisCase.higherCase == null) { + ilGen.Emit (thisCase, OpCodes.Br, defaultLabel); + } else { + OutputStrCase (testRVal, thisCase.higherCase, defaultLabel); + } + + /* + * Output code for lower comparison if any. + */ + if (thisCase.lowerCase != null) { + ilGen.MarkLabel (lowerLabel); + OutputStrCase (testRVal, thisCase.lowerCase, defaultLabel); + } + } + + /** + * @brief output code for a throw statement. + * @param throwStmt = throw statement token, including value to be thrown + */ + private void GenerateStmtThrow (TokenStmtThrow throwStmt) + { + if (!mightGetHere) return; + + /* + * 'throw' statements never fall through. + */ + mightGetHere = false; + + /* + * Output code for either a throw or a rethrow. + */ + if (throwStmt.rVal == null) { + for (TokenStmtBlock blk = curStmtBlock; blk != null; blk = blk.outerStmtBlock) { + if (curStmtBlock.isCatch) { + ilGen.Emit (throwStmt, OpCodes.Rethrow); + return; + } + } + ErrorMsg (throwStmt, "rethrow allowed only in catch clause"); + } else { + CompValu rVal = GenerateFromRVal (throwStmt.rVal); + rVal.PushVal (this, throwStmt.rVal, tokenTypeObj); + ilGen.Emit (throwStmt, OpCodes.Call, thrownExceptionWrapMethodInfo); + ilGen.Emit (throwStmt, OpCodes.Throw); + } + } + + /** + * @brief output code for a try/catch/finally block + */ + private void GenerateStmtTry (TokenStmtTry tryStmt) + { + if (!mightGetHere) return; + + /* + * Reducer should make sure we have exactly one of catch or finally. + */ + if ((tryStmt.catchStmt == null) && (tryStmt.finallyStmt == null)) { + throw new Exception ("must have a catch or a finally on try"); + } + if ((tryStmt.catchStmt != null) && (tryStmt.finallyStmt != null)) { + throw new Exception ("can't have both catch and finally on same try"); + } + + /* + * Stack the call labels. + * Try blocks have their own series of call labels. + */ + ScriptMyLocal saveCallNo = actCallNo; + LinkedList saveCallLabels = actCallLabels; + + /* + * Generate code for either try { } catch { } or try { } finally { }. + */ + if (tryStmt.catchStmt != null) GenerateStmtTryCatch (tryStmt); + if (tryStmt.finallyStmt != null) GenerateStmtTryFinally (tryStmt); + + /* + * Restore call labels. + */ + actCallNo = saveCallNo; + actCallLabels = saveCallLabels; + } + + + /** + * @brief output code for a try/catch block + * + * int __tryCallNo = -1; // call number within try { } subblock + * int __catCallNo = -1; // call number within catch { } subblock + * Exception __catThrown = null; // caught exception + * : // the outside world jumps here to restore us no matter ... + * try { // ... where we actually were inside of try/catch + * if (__tryCallNo >= 0) goto tryCallSw; // maybe go do restore + * // execute script-defined code + * // ...stack capture WILL run catch { } subblock + * leave tryEnd; // exits + * tryThrow:: + * throw new ScriptRestoreCatchException(__catThrown); // catch { } was running, jump to its beginning + * tryCallSw: // restoring... + * switch (__tryCallNo) back up into // not catching, jump back inside try + * } catch (Exception exc) { + * exc = ScriptRestoreCatchException.Unwrap(exc); // unwrap possible ScriptRestoreCatchException + * if (exc == null) goto catchRetro; // rethrow if IXMRUncatchable (eg, StackCaptureException) + * __catThrown = exc; // save what was thrown so restoring try { } will throw it again + * catchVar = exc; // set up script-visible variable + * __tryCallNo = tryThrow: + * if (__catCallNo >= 0) goto catchCallSw; // if restoring, go check below + * // normal, execute script-defined code + * leave tryEnd; // all done, exit catch { } + * catchRetro: + * rethrow; + * catchCallSw: + * switch (__catCallNo) back up into // restart catch { } code wherever it was + * } + * tryEnd: + */ + private void GenerateStmtTryCatch (TokenStmtTry tryStmt) + { + CompValuTemp tryCallNo = new CompValuTemp (tokenTypeInt, this); + CompValuTemp catCallNo = new CompValuTemp (tokenTypeInt, this); + CompValuTemp catThrown = new CompValuTemp (tokenTypeExc, this); + + ScriptMyLabel tryCallSw = ilGen.DefineLabel ("__tryCallSw_" + tryStmt.Unique); + ScriptMyLabel catchRetro = ilGen.DefineLabel ("__catchRetro_" + tryStmt.Unique); + ScriptMyLabel catchCallSw = ilGen.DefineLabel ("__catchCallSw_" + tryStmt.Unique); + ScriptMyLabel tryEnd = ilGen.DefineLabel ("__tryEnd_" + tryStmt.Unique); + + SetCallNo (tryStmt, tryCallNo, -1); + SetCallNo (tryStmt, catCallNo, -1); + ilGen.Emit (tryStmt, OpCodes.Ldnull); + catThrown.Pop (this, tryStmt); + + new CallLabel (this, tryStmt); // : + ilGen.BeginExceptionBlock (); // try { + openCallLabel = null; + if (DEBUG_TRYSTMT) { + ilGen.Emit (tryStmt, OpCodes.Ldstr, "enter try*: " + tryStmt.line + " callMode="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + PushXMRInst (); + ilGen.Emit (tryStmt, OpCodes.Ldfld, callModeFieldInfo); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, " tryCallNo="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + tryCallNo.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, " catThrown.IsNull="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + catThrown.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Ldnull); + ilGen.Emit (tryStmt, OpCodes.Ceq); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, " catCallNo="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + catCallNo.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, "\n"); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + } + + GetCallNo (tryStmt, tryCallNo); // if (__tryCallNo >= 0) goto tryCallSw; + ilGen.Emit (tryStmt, OpCodes.Ldc_I4_0); + ilGen.Emit (tryStmt, OpCodes.Bge, tryCallSw); + + actCallNo = tryCallNo.localBuilder; // set up __tryCallNo for call labels + actCallLabels = new LinkedList (); + + GenerateStmtBlock (tryStmt.tryStmt); // output the try block statement subblock + + bool tryBlockFallsOutBottom = mightGetHere; + if (tryBlockFallsOutBottom) { + new CallLabel (this, tryStmt); // : + ilGen.Emit (tryStmt, OpCodes.Leave, tryEnd); // leave tryEnd; + openCallLabel = null; + } + + CallLabel tryThrow = new CallLabel (this, tryStmt); // tryThrow:: + if (DEBUG_TRYSTMT) { + ilGen.Emit (tryStmt, OpCodes.Ldstr, "tryThrow*: " + tryStmt.line + " catThrown="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + catThrown.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, "\n"); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + } + catThrown.PushVal (this, tryStmt); // throw new ScriptRestoreCatchException (__catThrown); + ilGen.Emit (tryStmt, OpCodes.Newobj, scriptRestoreCatchExceptionConstructorInfo); + ilGen.Emit (tryStmt, OpCodes.Throw); + openCallLabel = null; + + ilGen.MarkLabel (tryCallSw); // tryCallSw: + if (DEBUG_TRYSTMT) { + ilGen.Emit (tryStmt, OpCodes.Ldstr, "tryCallSw*: " + tryStmt.line + " tryCallNo="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + tryCallNo.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, "\n"); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + } + OutputCallNoSwitchStmt (); // switch (tryCallNo) ... + + CompValuLocalVar catchVarLocExc = null; + CompValuTemp catchVarLocStr = null; + + if (tryStmt.catchVar.type.ToSysType () == typeof (Exception)) { + catchVarLocExc = new CompValuLocalVar (tryStmt.catchVar.type, tryStmt.catchVar.name.val, this); + } else if (tryStmt.catchVar.type.ToSysType () == typeof (String)) { + catchVarLocStr = new CompValuTemp (tryStmt.catchVar.type, this); + } + + ScriptMyLocal excLocal = ilGen.DeclareLocal (typeof (String), "catchstr_" + tryStmt.Unique); + + ilGen.BeginCatchBlock (typeof (Exception)); // start of the catch block that can catch any exception + if (DEBUG_TRYSTMT) { + ilGen.Emit (tryStmt.catchStmt, OpCodes.Ldstr, "enter catch*: " + tryStmt.line + " callMode="); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); + PushXMRInst (); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Ldfld, callModeFieldInfo); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Ldstr, " catCallNo="); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); + catCallNo.PushVal (this, tryStmt); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Ldstr, " exc="); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Dup); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Ldstr, "\n"); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); + } + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, scriptRestoreCatchExceptionUnwrap); + // exc = ScriptRestoreCatchException.Unwrap (exc); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Dup); // rethrow if IXMRUncatchable (eg, StackCaptureException) + ilGen.Emit (tryStmt.catchStmt, OpCodes.Brfalse, catchRetro); + if (tryStmt.catchVar.type.ToSysType () == typeof (Exception)) { + tryStmt.catchVar.location = catchVarLocExc; + ilGen.Emit (tryStmt.catchStmt, OpCodes.Dup); + catThrown.Pop (this, tryStmt); // store exception object in catThrown + catchVarLocExc.Pop (this, tryStmt.catchVar.name); // also store in script-visible variable + } else if (tryStmt.catchVar.type.ToSysType () == typeof (String)) { + tryStmt.catchVar.location = catchVarLocStr; + ilGen.Emit (tryStmt.catchStmt, OpCodes.Dup); + catThrown.Pop (this, tryStmt); // store exception object in catThrown + ilGen.Emit (tryStmt.catchStmt, OpCodes.Call, catchExcToStrMethodInfo); + + ilGen.Emit (tryStmt.catchStmt, OpCodes.Stloc, excLocal); + catchVarLocStr.PopPre (this, tryStmt.catchVar.name); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Ldloc, excLocal); + catchVarLocStr.PopPost (this, tryStmt.catchVar.name, tokenTypeStr); + } else { + throw new Exception ("bad catch var type " + tryStmt.catchVar.type.ToString ()); + } + + SetCallNo (tryStmt, tryCallNo, tryThrow.index); // __tryCallNo = tryThrow so it knows to do 'throw catThrown' on restore + + GetCallNo (tryStmt, catCallNo); // if (__catCallNo >= 0) goto catchCallSw; + ilGen.Emit (tryStmt.catchStmt, OpCodes.Ldc_I4_0); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Bge, catchCallSw); + + actCallNo = catCallNo.localBuilder; // set up __catCallNo for call labels + actCallLabels.Clear (); + mightGetHere = true; // if we can get to the 'try' assume we can get to the 'catch' + GenerateStmtBlock (tryStmt.catchStmt); // output catch clause statement subblock + + if (mightGetHere) { + new CallLabel (this, tryStmt.catchStmt); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Leave, tryEnd); + openCallLabel = null; + } + + ilGen.MarkLabel (catchRetro); // not a script-visible exception, rethrow it + ilGen.Emit (tryStmt.catchStmt, OpCodes.Pop); + ilGen.Emit (tryStmt.catchStmt, OpCodes.Rethrow); + + ilGen.MarkLabel (catchCallSw); + OutputCallNoSwitchStmt (); // restoring, jump back inside script-defined body + + ilGen.EndExceptionBlock (); + ilGen.MarkLabel (tryEnd); + + mightGetHere |= tryBlockFallsOutBottom; // also get here if try body falls out bottom + } + + /** + * @brief output code for a try/finally block + * + * This is such a mess because there is hidden state for the finally { } that we have to recreate. + * The finally { } can be entered either via an exception being thrown in the try { } or a leave + * being executed in the try { } whose target is outside the try { } finally { }. + * + * For the thrown exception case, we slip in a try { } catch { } wrapper around the original try { } + * body. This will sense any thrown exception that would execute the finally { }. Then we have our + * try { } throw the exception on restore which gets the finally { } called and on its way again. + * + * For the leave case, we prefix all leave instructions with a call label and we explicitly chain + * all leaves through each try { } that has an associated finally { } that the leave would unwind + * through. This gets each try { } to simply jump to the correct leave instruction which immediately + * invokes the corresponding finally { } and then chains to the next leave instruction on out until + * it gets to its target. + * + * int __finCallNo = -1; // call number within finally { } subblock + * int __tryCallNo = -1; // call number within try { } subblock + * Exception __catThrown = null; // caught exception + * : // the outside world jumps here to restore us no matter ... + * try { // ... where we actually were inside of try/finally + * try { + * if (__tryCallNo >= 0) goto tryCallSw; // maybe go do restore + * // execute script-defined code + * // ...stack capture WILL run catch/finally { } subblock + * leave tryEnd; // executes finally { } subblock and exits + * tryThrow:: + * throw new ScriptRestoreCatchException(__catThrown); // catch { } was running, jump to its beginning + * tryCallSw: // restoring... + * switch (__tryCallNo) back up into // jump back inside try, ... + * // ... maybe to a leave if we were doing finally { } subblock + * } catch (Exception exc) { // in case we're getting to finally { } via a thrown exception: + * exc = ScriptRestoreCatchException.Unwrap(exc); // unwrap possible ScriptRestoreCatchException + * if (callMode == CallMode_SAVE) goto catchRetro; // don't touch anything if capturing stack + * __catThrown = exc; // save exception so try { } can throw it on restore + * __tryCallNo = tryThrow:; // tell try { } to throw it on restore + * catchRetro: + * rethrow; // in any case, go on to finally { } subblock now + * } + * } finally { + * if (callMode == CallMode_SAVE) goto finEnd; // don't touch anything if capturing stack + * if (__finCallNo >= 0) goto finCallSw; // maybe go do restore + * // normal, execute script-defined code + * finEnd: + * endfinally // jump to leave/throw target or next outer finally { } + * finCallSw: + * switch (__finCallNo) back up into // restoring, restart finally { } code wherever it was + * } + * tryEnd: + */ + private void GenerateStmtTryFinally (TokenStmtTry tryStmt) + { + CompValuTemp finCallNo = new CompValuTemp (tokenTypeInt, this); + CompValuTemp tryCallNo = new CompValuTemp (tokenTypeInt, this); + CompValuTemp catThrown = new CompValuTemp (tokenTypeExc, this); + + ScriptMyLabel tryCallSw = ilGen.DefineLabel ( "__tryCallSw_" + tryStmt.Unique); + ScriptMyLabel catchRetro = ilGen.DefineLabel ( "__catchRetro_" + tryStmt.Unique); + ScriptMyLabel finCallSw = ilGen.DefineLabel ( "__finCallSw_" + tryStmt.Unique); + BreakContTarg finEnd = new BreakContTarg (this, "__finEnd_" + tryStmt.Unique); + ScriptMyLabel tryEnd = ilGen.DefineLabel ( "__tryEnd_" + tryStmt.Unique); + + SetCallNo (tryStmt, finCallNo, -1); + SetCallNo (tryStmt, tryCallNo, -1); + ilGen.Emit (tryStmt, OpCodes.Ldnull); + catThrown.Pop (this, tryStmt); + + new CallLabel (this, tryStmt); // : + ilGen.BeginExceptionBlock (); // try { + ilGen.BeginExceptionBlock (); // try { + openCallLabel = null; + if (DEBUG_TRYSTMT) { + ilGen.Emit (tryStmt, OpCodes.Ldstr, "enter try*: " + tryStmt.line + " callMode="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + PushXMRInst (); + ilGen.Emit (tryStmt, OpCodes.Ldfld, callModeFieldInfo); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, " tryCallNo="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + tryCallNo.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, " finCallNo="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + finCallNo.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, " catThrown.IsNull="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + catThrown.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Ldnull); + ilGen.Emit (tryStmt, OpCodes.Ceq); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, "\n"); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + } + + GetCallNo (tryStmt, tryCallNo); // if (__tryCallNo >= 0) goto tryCallSw; + ilGen.Emit (tryStmt, OpCodes.Ldc_I4_0); + ilGen.Emit (tryStmt, OpCodes.Bge, tryCallSw); + + actCallNo = tryCallNo.localBuilder; // set up __tryCallNo for call labels + actCallLabels = new LinkedList (); + + GenerateStmtBlock (tryStmt.tryStmt); // output the try block statement subblock + + if (mightGetHere) { + new CallLabel (this, tryStmt); // : + ilGen.Emit (tryStmt, OpCodes.Leave, tryEnd); // leave tryEnd; + openCallLabel = null; + } + + foreach (IntermediateLeave iLeave in tryStmt.iLeaves.Values) { + ilGen.MarkLabel (iLeave.jumpIntoLabel); // intr2_exit: + new CallLabel (this, tryStmt); // tryCallNo = n; + ilGen.Emit (tryStmt, OpCodes.Leave, iLeave.jumpAwayLabel); // __callNo_n_: leave int1_exit; + openCallLabel = null; + } + + CallLabel tryThrow = new CallLabel (this, tryStmt); // tryThrow:: + if (DEBUG_TRYSTMT) { + ilGen.Emit (tryStmt, OpCodes.Ldstr, "tryThrow*: " + tryStmt.line + " catThrown="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + catThrown.PushVal (this, tryStmt); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, "\n"); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + } + catThrown.PushVal (this, tryStmt); // throw new ScriptRestoreCatchException (__catThrown); + ilGen.Emit (tryStmt, OpCodes.Newobj, scriptRestoreCatchExceptionConstructorInfo); + ilGen.Emit (tryStmt, OpCodes.Throw); + openCallLabel = null; + + ilGen.MarkLabel (tryCallSw); // tryCallSw: + OutputCallNoSwitchStmt (); // switch (tryCallNo) ... + // } + + ilGen.BeginCatchBlock (typeof (Exception)); // start of the catch block that can catch any exception + if (DEBUG_TRYSTMT) { + ilGen.Emit (tryStmt, OpCodes.Ldstr, "enter catch*: " + tryStmt.line + " callMode="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + PushXMRInst (); + ilGen.Emit (tryStmt, OpCodes.Ldfld, callModeFieldInfo); + ilGen.Emit (tryStmt, OpCodes.Box, typeof (int)); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, " exc="); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Dup); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + ilGen.Emit (tryStmt, OpCodes.Ldstr, "\n"); + ilGen.Emit (tryStmt, OpCodes.Call, consoleWriteMethodInfo); + } + ilGen.Emit (tryStmt, OpCodes.Call, scriptRestoreCatchExceptionUnwrap); // exc = ScriptRestoreCatchException.Unwrap (exc); + PushXMRInst (); // if (callMode == CallMode_SAVE) goto catchRetro; + ilGen.Emit (tryStmt, OpCodes.Ldfld, callModeFieldInfo); + ilGen.Emit (tryStmt, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_SAVE); + ilGen.Emit (tryStmt, OpCodes.Beq, catchRetro); + + catThrown.Pop (this, tryStmt); // __catThrown = exc; + SetCallNo (tryStmt, tryCallNo, tryThrow.index); // __tryCallNo = tryThrow:; + ilGen.Emit (tryStmt, OpCodes.Rethrow); + + ilGen.MarkLabel (catchRetro); // catchRetro: + ilGen.Emit (tryStmt, OpCodes.Pop); + ilGen.Emit (tryStmt, OpCodes.Rethrow); // rethrow; + + ilGen.EndExceptionBlock (); // } + + ilGen.BeginFinallyBlock (); // start of the finally block + + PushXMRInst (); // if (callMode == CallMode_SAVE) goto finEnd; + ilGen.Emit (tryStmt, OpCodes.Ldfld, callModeFieldInfo); + ilGen.Emit (tryStmt, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_SAVE); + ilGen.Emit (tryStmt, OpCodes.Beq, finEnd.label); + + GetCallNo (tryStmt, finCallNo); // if (__finCallNo >= 0) goto finCallSw; + ilGen.Emit (tryStmt, OpCodes.Ldc_I4_0); + ilGen.Emit (tryStmt, OpCodes.Bge, finCallSw); + + actCallNo = finCallNo.localBuilder; // set up __finCallNo for call labels + actCallLabels.Clear (); + mightGetHere = true; // if we can get to the 'try' assume we can get to the 'finally' + GenerateStmtBlock (tryStmt.finallyStmt); // output finally clause statement subblock + + ilGen.MarkLabel (finEnd.label); // finEnd: + ilGen.Emit (tryStmt, OpCodes.Endfinally); // return out to next finally { } or catch { } or leave target + + ilGen.MarkLabel (finCallSw); // restore mode, switch (finCallNo) ... + OutputCallNoSwitchStmt (); + + ilGen.EndExceptionBlock (); + ilGen.MarkLabel (tryEnd); + + mightGetHere |= finEnd.used; // get here if finally body falls through or has a break statement + } + + /** + * @brief Generate code to initialize a variable to its default value. + */ + private void GenerateStmtVarIniDef (TokenStmtVarIniDef varIniDefStmt) + { + if (!mightGetHere) return; + + CompValu left = GenerateFromLVal (varIniDefStmt.var); + left.PopPre (this, varIniDefStmt); + PushDefaultValue (left.type); + left.PopPost (this, varIniDefStmt); + } + + /** + * @brief generate code for a 'while' statement including the loop body. + */ + private void GenerateStmtWhile (TokenStmtWhile whileStmt) + { + if (!mightGetHere) return; + + BreakContTarg oldBreakTarg = curBreakTarg; + BreakContTarg oldContTarg = curContTarg; + ScriptMyLabel loopLabel = ilGen.DefineLabel ("whileloop_" + whileStmt.Unique); + + curBreakTarg = new BreakContTarg (this, "whilebreak_" + whileStmt.Unique); + curContTarg = new BreakContTarg (this, "whilecont_" + whileStmt.Unique); + + ilGen.MarkLabel (loopLabel); // loop: + CompValu testRVal = GenerateFromRVal (whileStmt.testRVal); // testRVal = while test expression + if (!IsConstBoolExprTrue (testRVal)) { + testRVal.PushVal (this, whileStmt.testRVal, tokenTypeBool); // if (!testRVal) + ilGen.Emit (whileStmt, OpCodes.Brfalse, curBreakTarg.label); // goto break + curBreakTarg.used = true; + } + GenerateStmt (whileStmt.bodyStmt); // while body statement + if (curContTarg.used) { + ilGen.MarkLabel (curContTarg.label); // cont: + mightGetHere = true; + } + if (mightGetHere) { + EmitCallCheckRun (whileStmt, false); // __sw.CheckRun() + ilGen.Emit (whileStmt, OpCodes.Br, loopLabel); // goto loop + } + mightGetHere = curBreakTarg.used; + if (mightGetHere) { + ilGen.MarkLabel (curBreakTarg.label); // done: + } + + curBreakTarg = oldBreakTarg; + curContTarg = oldContTarg; + } + + /** + * @brief process a local variable declaration statement, possibly with initialization expression. + * Note that the function header processing allocated stack space (CompValuTemp) for the + * variable and now all we do is write its initialization value. + */ + private void GenerateDeclVar (TokenDeclVar declVar) + { + /* + * Script gave us an initialization value, so just store init value in var like an assignment statement. + * If no init given, set it to its default value. + */ + CompValu local = declVar.location; + if (declVar.init != null) { + CompValu rVal = GenerateFromRVal (declVar.init, local.GetArgTypes ()); + local.PopPre (this, declVar); + rVal.PushVal (this, declVar.init, declVar.type); + local.PopPost (this, declVar); + } else { + local.PopPre (this, declVar); + PushDefaultValue (declVar.type); + local.PopPost (this, declVar); + } + } + + /** + * @brief Get the type and location of an L-value (eg, variable) + * @param lVal = L-value expression to evaluate + * @param argsig = null: it's a field/property + * else: select overload method that fits these arg types + */ + private CompValu GenerateFromLVal (TokenLVal lVal) + { + return GenerateFromLVal (lVal, null); + } + private CompValu GenerateFromLVal (TokenLVal lVal, TokenType[] argsig) + { + if (lVal is TokenLValArEle) return GenerateFromLValArEle ((TokenLValArEle)lVal); + if (lVal is TokenLValBaseField) return GenerateFromLValBaseField ((TokenLValBaseField)lVal, argsig); + if (lVal is TokenLValIField) return GenerateFromLValIField ((TokenLValIField)lVal, argsig); + if (lVal is TokenLValName) return GenerateFromLValName ((TokenLValName)lVal, argsig); + if (lVal is TokenLValSField) return GenerateFromLValSField ((TokenLValSField)lVal, argsig); + throw new Exception ("bad lval class"); + } + + /** + * @brief we have an L-value token that is an element within an array. + * @returns a CompValu giving the type and location of the element of the array. + */ + private CompValu GenerateFromLValArEle (TokenLValArEle lVal) + { + CompValu subCompValu; + + /* + * Compute location of array itself. + */ + CompValu baseCompValu = GenerateFromRVal (lVal.baseRVal); + + /* + * Maybe it is a fixed array access. + */ + string basetypestring = baseCompValu.type.ToString (); + if (basetypestring.EndsWith ("]")) { + TokenRVal subRVal = lVal.subRVal; + int nSubs = 1; + if (subRVal is TokenRValList) { + nSubs = ((TokenRValList)subRVal).nItems; + subRVal = ((TokenRValList)subRVal).rVal; + } + + int rank = basetypestring.IndexOf (']') - basetypestring.IndexOf ('['); + if (nSubs != rank) { + ErrorMsg (lVal.baseRVal, "expect " + rank + " subscript" + ((rank == 1) ? "" : "s") + " but have " + nSubs); + } + CompValu[] subCompValus = new CompValu[rank]; + int i; + for (i = 0; (subRVal != null) && (i < rank); i ++) { + subCompValus[i] = GenerateFromRVal (subRVal); + subRVal = (TokenRVal)subRVal.nextToken; + } + while (i < rank) subCompValus[i++] = new CompValuInteger (new TokenTypeInt (lVal.subRVal), 0); + return new CompValuFixArEl (this, baseCompValu, subCompValus); + } + + /* + * Maybe it is accessing the $idxprop property of a script-defined class. + */ + if (baseCompValu.type is TokenTypeSDTypeClass) { + TokenName name = new TokenName (lVal, "$idxprop"); + TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseCompValu.type; + TokenDeclSDTypeClass sdtDecl = sdtType.decl; + TokenDeclVar idxProp = FindThisMember (sdtDecl, name, null); + if (idxProp == null) { + ErrorMsg (lVal, "no index property in class " + sdtDecl.longName.val); + return new CompValuVoid (lVal); + } + if ((idxProp.sdtFlags & ScriptReduce.SDT_STATIC) != 0) { + ErrorMsg (lVal, "non-static reference to static member " + idxProp.name.val); + return new CompValuVoid (idxProp); + } + CheckAccess (idxProp, name); + + TokenType[] argTypes = IdxPropArgTypes (idxProp); + CompValu[] compValus = IdxPropCompValus (lVal, argTypes.Length); + return new CompValuIdxProp (idxProp, baseCompValu, argTypes, compValus); + + } + + /* + * Maybe they are accessing $idxprop property of a script-defined interface. + */ + if (baseCompValu.type is TokenTypeSDTypeInterface) { + TokenName name = new TokenName (lVal, "$idxprop"); + TokenTypeSDTypeInterface sdtType = (TokenTypeSDTypeInterface)baseCompValu.type; + TokenDeclVar idxProp = FindInterfaceMember (sdtType, name, null, ref baseCompValu); + if (idxProp == null) { + ErrorMsg (lVal, "no index property defined for interface " + sdtType.decl.longName.val); + return baseCompValu; + } + + TokenType[] argTypes = IdxPropArgTypes (idxProp); + CompValu[] compValus = IdxPropCompValus (lVal, argTypes.Length); + return new CompValuIdxProp (idxProp, baseCompValu, argTypes, compValus); + } + + /* + * Maybe it is extracting a character from a string. + */ + if ((baseCompValu.type is TokenTypeKey) || (baseCompValu.type is TokenTypeStr)) { + subCompValu = GenerateFromRVal (lVal.subRVal); + return new CompValuStrChr (new TokenTypeChar (lVal), baseCompValu, subCompValu); + } + + /* + * Maybe it is extracting an element from a list. + */ + if (baseCompValu.type is TokenTypeList) { + subCompValu = GenerateFromRVal (lVal.subRVal); + return new CompValuListEl (new TokenTypeObject (lVal), baseCompValu, subCompValu); + } + + /* + * Access should be to XMR_Array otherwise. + */ + if (!(baseCompValu.type is TokenTypeArray)) { + ErrorMsg (lVal, "taking subscript of non-array"); + return baseCompValu; + } + subCompValu = GenerateFromRVal (lVal.subRVal); + return new CompValuArEle (new TokenTypeObject (lVal), baseCompValu, subCompValu); + } + + /** + * @brief Get number and type of arguments required by an index property. + */ + private static TokenType[] IdxPropArgTypes (TokenDeclVar idxProp) + { + TokenType[] argTypes; + if (idxProp.getProp != null) { + int nArgs = idxProp.getProp.argDecl.varDict.Count; + argTypes = new TokenType[nArgs]; + foreach (TokenDeclVar var in idxProp.getProp.argDecl.varDict) { + argTypes[var.vTableIndex] = var.type; + } + } else { + int nArgs = idxProp.setProp.argDecl.varDict.Count - 1; + argTypes = new TokenType[nArgs]; + foreach (TokenDeclVar var in idxProp.setProp.argDecl.varDict) { + if (var.vTableIndex < nArgs) { + argTypes[var.vTableIndex] = var.type; + } + } + } + return argTypes; + } + + /** + * @brief Get number and computed value of index property arguments. + * @param lVal = list of arguments + * @param nArgs = number of arguments required + * @returns null: argument count mismatch + * else: array of index property argument values + */ + private CompValu[] IdxPropCompValus (TokenLValArEle lVal, int nArgs) + { + TokenRVal subRVal = lVal.subRVal; + int nSubs = 1; + if (subRVal is TokenRValList) { + nSubs = ((TokenRValList)subRVal).nItems; + subRVal = ((TokenRValList)subRVal).rVal; + } + + if (nSubs != nArgs) { + ErrorMsg (lVal, "index property requires " + nArgs + " subscript(s)"); + return null; + } + + CompValu[] subCompValus = new CompValu[nArgs]; + for (int i = 0; i < nArgs; i ++) { + subCompValus[i] = GenerateFromRVal (subRVal); + subRVal = (TokenRVal)subRVal.nextToken; + } + return subCompValus; + } + + /** + * @brief using 'base' within a script-defined instance method to refer to an instance field/method + * of the class being extended. + */ + private CompValu GenerateFromLValBaseField (TokenLValBaseField baseField, TokenType[] argsig) + { + string fieldName = baseField.fieldName.val; + + TokenDeclSDType sdtDecl = curDeclFunc.sdtClass; + if ((sdtDecl == null) || ((curDeclFunc.sdtFlags & ScriptReduce.SDT_STATIC) != 0)) { + ErrorMsg (baseField, "cannot use 'base' outside instance method body"); + return new CompValuVoid (baseField); + } + if (!IsSDTInstMethod ()) { + ErrorMsg (baseField, "cannot access instance member of base class from static method"); + return new CompValuVoid (baseField); + } + + TokenDeclVar declVar = FindThisMember (sdtDecl.extends, baseField.fieldName, argsig); + if (declVar != null) { + CheckAccess (declVar, baseField.fieldName); + TokenType baseType = declVar.sdtClass.MakeRefToken (baseField); + CompValu basePtr = new CompValuArg (baseType, 0); + return AccessInstanceMember (declVar, basePtr, baseField, true); + } + + ErrorMsg (baseField, "no member " + fieldName + ArgSigString (argsig) + " rootward of " + sdtDecl.longName.val); + return new CompValuVoid (baseField); + } + + /** + * @brief We have an L-value token that is an instance field/method within a struct. + * @returns a CompValu giving the type and location of the field/method in the struct. + */ + private CompValu GenerateFromLValIField (TokenLValIField lVal, TokenType[] argsig) + { + CompValu baseRVal = GenerateFromRVal (lVal.baseRVal); + string fieldName = lVal.fieldName.val + ArgSigString (argsig); + + /* + * Maybe they are accessing an instance field, method or property of a script-defined class. + */ + if (baseRVal.type is TokenTypeSDTypeClass) { + TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseRVal.type; + TokenDeclSDTypeClass sdtDecl = sdtType.decl; + TokenDeclVar declVar = FindThisMember (sdtDecl, lVal.fieldName, argsig); + if (declVar != null) { + CheckAccess (declVar, lVal.fieldName); + return AccessInstanceMember (declVar, baseRVal, lVal, false); + } + ErrorMsg (lVal.fieldName, "no member " + fieldName + " in class " + sdtDecl.longName.val); + return new CompValuVoid (lVal.fieldName); + } + + /* + * Maybe they are accessing a method or property of a script-defined interface. + */ + if (baseRVal.type is TokenTypeSDTypeInterface) { + TokenTypeSDTypeInterface sdtType = (TokenTypeSDTypeInterface)baseRVal.type; + TokenDeclVar declVar = FindInterfaceMember (sdtType, lVal.fieldName, argsig, ref baseRVal); + if (declVar != null) { + return new CompValuIntfMember (declVar, baseRVal); + } + ErrorMsg (lVal.fieldName, "no member " + fieldName + " in interface " + sdtType.decl.longName.val); + return new CompValuVoid (lVal.fieldName); + } + + /* + * Since we only have a few built-in types with fields, just pound them out. + */ + if (baseRVal.type is TokenTypeArray) { + + // no arguments, no parentheses, just the field name, returning integer + // but internally, it is a call to a method() + if (fieldName == "count") { + return new CompValuIntInstROProp (tokenTypeInt, baseRVal, arrayCountMethodInfo); + } + + // no arguments but with the parentheses, returning void + if (fieldName == "clear()") { + return new CompValuIntInstMeth (XMR_Array.clearDelegate, baseRVal, arrayClearMethodInfo); + } + + // single integer argument, returning an object + if (fieldName == "index(integer)") { + return new CompValuIntInstMeth (XMR_Array.indexDelegate, baseRVal, arrayIndexMethodInfo); + } + if (fieldName == "value(integer)") { + return new CompValuIntInstMeth (XMR_Array.valueDelegate, baseRVal, arrayValueMethodInfo); + } + } + if (baseRVal.type is TokenTypeRot) { + FieldInfo fi = null; + if (fieldName == "x") fi = rotationXFieldInfo; + if (fieldName == "y") fi = rotationYFieldInfo; + if (fieldName == "z") fi = rotationZFieldInfo; + if (fieldName == "s") fi = rotationSFieldInfo; + if (fi != null) { + return new CompValuField (new TokenTypeFloat (lVal), baseRVal, fi); + } + } + if (baseRVal.type is TokenTypeVec) { + FieldInfo fi = null; + if (fieldName == "x") fi = vectorXFieldInfo; + if (fieldName == "y") fi = vectorYFieldInfo; + if (fieldName == "z") fi = vectorZFieldInfo; + if (fi != null) { + return new CompValuField (new TokenTypeFloat (lVal), baseRVal, fi); + } + } + + ErrorMsg (lVal, "type " + baseRVal.type.ToString () + " does not define member " + fieldName); + return baseRVal; + } + + /** + * @brief We have an L-value token that is a function, method or variable name. + * @param lVal = name we are looking for + * @param argsig = null: just look for name as a variable + * else: look for name as a function/method being called with the given argument types + * eg, "(string,integer,list)" + * @returns a CompValu giving the type and location of the function, method or variable. + */ + private CompValu GenerateFromLValName (TokenLValName lVal, TokenType[] argsig) + { + /* + * Look in variable stack then look for built-in constants and functions. + */ + TokenDeclVar var = FindNamedVar (lVal, argsig); + if (var == null) { + ErrorMsg (lVal, "undefined constant/function/variable " + lVal.name.val + ArgSigString (argsig)); + return new CompValuVoid (lVal); + } + + /* + * Maybe it has an implied 'this.' on the front. + */ + if ((var.sdtClass != null) && ((var.sdtFlags & ScriptReduce.SDT_STATIC) == 0)) { + + if (!IsSDTInstMethod ()) { + ErrorMsg (lVal, "cannot access instance member of class from static method"); + return new CompValuVoid (lVal); + } + + /* + * Don't allow something such as: + * + * class A { + * integer I; + * class B { + * Print () + * { + * llOwnerSay ("I=" + (string)I); <- access to I not allowed inside class B. + * explicit reference required as we don't + * have a valid reference to class A. + * } + * } + * } + * + * But do allow something such as: + * + * class A { + * integer I; + * } + * class B : A { + * Print () + * { + * llOwnerSay ("I=" + (string)I); + * } + * } + */ + for (TokenDeclSDType c = curDeclFunc.sdtClass; c != var.sdtClass; c = c.extends) { + if (c == null) { + // our arg0 points to an instance of curDeclFunc.sdtClass, not var.sdtClass + ErrorMsg (lVal, "cannot access instance member of outer class with implied 'this'"); + break; + } + } + + CompValu thisCompValu = new CompValuArg (var.sdtClass.MakeRefToken (lVal), 0); + return AccessInstanceMember (var, thisCompValu, lVal, false); + } + + /* + * It's a local variable, static field, global, constant, etc. + */ + return var.location; + } + + /** + * @brief Access a script-defined type's instance member + * @param declVar = which member (field,method,property) to access + * @param basePtr = points to particular object instance + * @param ignoreVirt = true: access declVar's method directly; else: maybe use vTable + * @returns where the field/method/property is located + */ + private CompValu AccessInstanceMember (TokenDeclVar declVar, CompValu basePtr, Token errorAt, bool ignoreVirt) + { + if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) { + ErrorMsg (errorAt, "non-static reference to static member " + declVar.name.val); + return new CompValuVoid (declVar); + } + return new CompValuInstMember (declVar, basePtr, ignoreVirt); + } + + /** + * @brief we have an L-value token that is a static member within a struct. + * @returns a CompValu giving the type and location of the member in the struct. + */ + private CompValu GenerateFromLValSField (TokenLValSField lVal, TokenType[] argsig) + { + TokenType stType = lVal.baseType; + string fieldName = lVal.fieldName.val + ArgSigString (argsig); + + /* + * Maybe they are accessing a static member of a script-defined class. + */ + if (stType is TokenTypeSDTypeClass) { + TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)stType; + TokenDeclVar declVar = FindThisMember (sdtType.decl, lVal.fieldName, argsig); + if (declVar != null) { + CheckAccess (declVar, lVal.fieldName); + if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) == 0) { + ErrorMsg (lVal.fieldName, "static reference to non-static member " + fieldName); + return new CompValuVoid (lVal.fieldName); + } + return declVar.location; + } + } + + ErrorMsg (lVal.fieldName, "no member " + fieldName + " in " + stType.ToString ()); + return new CompValuVoid (lVal.fieldName); + } + + /** + * @brief generate code from an RVal expression and return its type and where the result is stored. + * For anything that has side-effects, statements are generated that perform the computation then + * the result it put in a temp var and the temp var name is returned. + * For anything without side-effects, they are returned as an equivalent sequence of Emits. + * @param rVal = rVal token to be evaluated + * @param argsig = null: not being used in an function/method context + * else: string giving argument types, eg, "(string,integer,list,vector)" + * that can be used to select among overloaded methods + * @returns resultant type and location + */ + private CompValu GenerateFromRVal (TokenRVal rVal) + { + return GenerateFromRVal (rVal, null); + } + private CompValu GenerateFromRVal (TokenRVal rVal, TokenType[] argsig) + { + errorMessageToken = rVal; + + /* + * Maybe the expression can be converted to a constant. + */ + bool didOne; + do { + didOne = false; + rVal = rVal.TryComputeConstant (LookupBodyConstants, ref didOne); + } while (didOne); + + /* + * Generate code for the computation and return resulting type and location. + */ + CompValu cVal = null; + if (rVal is TokenRValAsnPost) cVal = GenerateFromRValAsnPost ((TokenRValAsnPost)rVal); + if (rVal is TokenRValAsnPre) cVal = GenerateFromRValAsnPre ((TokenRValAsnPre)rVal); + if (rVal is TokenRValCall) cVal = GenerateFromRValCall ((TokenRValCall)rVal); + if (rVal is TokenRValCast) cVal = GenerateFromRValCast ((TokenRValCast)rVal); + if (rVal is TokenRValCondExpr) cVal = GenerateFromRValCondExpr ((TokenRValCondExpr)rVal); + if (rVal is TokenRValConst) cVal = GenerateFromRValConst ((TokenRValConst)rVal); + if (rVal is TokenRValInitDef) cVal = GenerateFromRValInitDef ((TokenRValInitDef)rVal); + if (rVal is TokenRValIsType) cVal = GenerateFromRValIsType ((TokenRValIsType)rVal); + if (rVal is TokenRValList) cVal = GenerateFromRValList ((TokenRValList)rVal); + if (rVal is TokenRValNewArIni) cVal = GenerateFromRValNewArIni ((TokenRValNewArIni)rVal); + if (rVal is TokenRValOpBin) cVal = GenerateFromRValOpBin ((TokenRValOpBin)rVal); + if (rVal is TokenRValOpUn) cVal = GenerateFromRValOpUn ((TokenRValOpUn)rVal); + if (rVal is TokenRValParen) cVal = GenerateFromRValParen ((TokenRValParen)rVal); + if (rVal is TokenRValRot) cVal = GenerateFromRValRot ((TokenRValRot)rVal); + if (rVal is TokenRValThis) cVal = GenerateFromRValThis ((TokenRValThis)rVal); + if (rVal is TokenRValUndef) cVal = GenerateFromRValUndef ((TokenRValUndef)rVal); + if (rVal is TokenRValVec) cVal = GenerateFromRValVec ((TokenRValVec)rVal); + if (rVal is TokenLVal) cVal = GenerateFromLVal ((TokenLVal)rVal, argsig); + + if (cVal == null) throw new Exception ("bad rval class " + rVal.GetType ().ToString ()); + + /* + * Sanity check. + */ + if (!youveAnError) { + if (cVal.type == null) throw new Exception ("cVal has no type " + cVal.GetType ()); + string cValType = cVal.type.ToString (); + string rValType = rVal.GetRValType (this, argsig).ToString (); + if (cValType == "bool") cValType = "integer"; + if (rValType == "bool") rValType = "integer"; + if (cValType != rValType) { + throw new Exception ("cVal.type " + cValType + " != rVal.type " + rValType + + " (" + rVal.GetType ().Name + " " + rVal.SrcLoc + ")"); + } + } + + return cVal; + } + + /** + * @brief compute the result of a binary operator (eg, add, subtract, multiply, lessthan) + * @param token = binary operator token, includes the left and right operands + * @returns where the resultant R-value is as something that doesn't have side effects + */ + private CompValu GenerateFromRValOpBin (TokenRValOpBin token) + { + CompValu left, right; + string opcodeIndex = token.opcode.ToString (); + + /* + * Comma operators are special, as they say to compute the left-hand value and + * discard it, then compute the right-hand argument and that is the result. + */ + if (opcodeIndex == ",") { + + /* + * Compute left-hand operand but throw away result. + */ + GenerateFromRVal (token.rValLeft); + + /* + * Compute right-hand operand and that is the value of the expression. + */ + return GenerateFromRVal (token.rValRight); + } + + /* + * Simple overwriting assignments are their own special case, + * as we want to cast the R-value to the type of the L-value. + * And in the case of delegates, we want to use the arg signature + * of the delegate to select which overloaded method to use. + */ + if (opcodeIndex == "=") { + if (!(token.rValLeft is TokenLVal)) { + ErrorMsg (token, "invalid L-value for ="); + return GenerateFromRVal (token.rValLeft); + } + left = GenerateFromLVal ((TokenLVal)token.rValLeft); + right = Trivialize (GenerateFromRVal (token.rValRight, left.GetArgTypes ()), token.rValRight); + left.PopPre (this, token.rValLeft); + right.PushVal (this, token.rValRight, left.type); // push (left.type)right + left.PopPost (this, token.rValLeft); // pop to left + return left; + } + + /* + * There are String.Concat() methods available for 2, 3 and 4 operands. + * So see if we have a string concat op and optimize if so. + */ + if ((opcodeIndex == "+") || + ((opcodeIndex == "+=") && + (token.rValLeft is TokenLVal) && + (token.rValLeft.GetRValType (this, null) is TokenTypeStr))) { + + /* + * We are adding something. Maybe it's a bunch of strings together. + */ + List scorvs = new List (); + if (StringConcatOperands (token.rValLeft, token.rValRight, scorvs, token.opcode)) { + + /* + * Evaluate all the operands, right-to-left on purpose per LSL scripting. + */ + int i; + int n = scorvs.Count; + CompValu[] scocvs = new CompValu[n]; + for (i = n; -- i >= 0;) { + scocvs[i] = GenerateFromRVal (scorvs[i]); + if (i > 0) scocvs[i] = Trivialize (scocvs[i], scorvs[i]); + } + + /* + * Figure out where to put the result. + * A temp if '+', or back in original L-value if '+='. + */ + CompValu retcv; + if (opcodeIndex == "+") { + retcv = new CompValuTemp (new TokenTypeStr (token.opcode), this); + } else { + retcv = GenerateFromLVal ((TokenLVal)token.rValLeft); + } + retcv.PopPre (this, token); + + /* + * Call the String.Concat() methods, passing operands in left-to-right order. + * Force a cast to string (retcv.type) for each operand. + */ + ++ i; scocvs[i].PushVal (this, scorvs[i], retcv.type); + while (i + 3 < n) { + ++ i; scocvs[i].PushVal (this, scorvs[i], retcv.type); + ++ i; scocvs[i].PushVal (this, scorvs[i], retcv.type); + ++ i; scocvs[i].PushVal (this, scorvs[i], retcv.type); + ilGen.Emit (scorvs[i], OpCodes.Call, stringConcat4MethodInfo); + } + if (i + 2 < n) { + ++ i; scocvs[i].PushVal (this, scorvs[i], retcv.type); + ++ i; scocvs[i].PushVal (this, scorvs[i], retcv.type); + ilGen.Emit (scorvs[i], OpCodes.Call, stringConcat3MethodInfo); + } + if (i + 1 < n) { + ++ i; scocvs[i].PushVal (this, scorvs[i], retcv.type); + ilGen.Emit (scorvs[i], OpCodes.Call, stringConcat2MethodInfo); + } + + /* + * Put the result where we want it and return where we put it. + */ + retcv.PopPost (this, token); + return retcv; + } + } + + /* + * If "&&&", it is a short-circuiting AND. + * Compute left-hand operand and if true, compute right-hand operand. + */ + if (opcodeIndex == "&&&") { + bool leftVal, rightVal; + left = GenerateFromRVal (token.rValLeft); + if (!IsConstBoolExpr (left, out leftVal)) { + ScriptMyLabel falseLabel = ilGen.DefineLabel ("ssandfalse"); + left.PushVal (this, tokenTypeBool); + ilGen.Emit (token, OpCodes.Brfalse, falseLabel); + right = GenerateFromRVal (token.rValRight); + if (!IsConstBoolExpr (right, out rightVal)) { + right.PushVal (this, tokenTypeBool); + goto donessand; + } + if (!rightVal) { + ilGen.MarkLabel (falseLabel); + return new CompValuInteger (new TokenTypeInt (token.rValLeft), 0); + } + ilGen.Emit (token, OpCodes.Ldc_I4_1); + donessand: + ScriptMyLabel doneLabel = ilGen.DefineLabel ("ssanddone"); + ilGen.Emit (token, OpCodes.Br, doneLabel); + ilGen.MarkLabel (falseLabel); + ilGen.Emit (token, OpCodes.Ldc_I4_0); + ilGen.MarkLabel (doneLabel); + CompValuTemp retRVal = new CompValuTemp (new TokenTypeInt (token), this); + retRVal.Pop (this, token); + return retRVal; + } + + if (!leftVal) { + return new CompValuInteger (new TokenTypeInt (token.rValLeft), 0); + } + + right = GenerateFromRVal (token.rValRight); + if (!IsConstBoolExpr (right, out rightVal)) { + right.PushVal (this, tokenTypeBool); + CompValuTemp retRVal = new CompValuTemp (new TokenTypeInt (token), this); + retRVal.Pop (this, token); + return retRVal; + } + return new CompValuInteger (new TokenTypeInt (token), rightVal ? 1 : 0); + } + + /* + * If "|||", it is a short-circuiting OR. + * Compute left-hand operand and if false, compute right-hand operand. + */ + if (opcodeIndex == "|||") { + bool leftVal, rightVal; + left = GenerateFromRVal (token.rValLeft); + if (!IsConstBoolExpr (left, out leftVal)) { + ScriptMyLabel trueLabel = ilGen.DefineLabel ("ssortrue"); + left.PushVal (this, tokenTypeBool); + ilGen.Emit (token, OpCodes.Brtrue, trueLabel); + right = GenerateFromRVal (token.rValRight); + if (!IsConstBoolExpr (right, out rightVal)) { + right.PushVal (this, tokenTypeBool); + goto donessor; + } + if (rightVal) { + ilGen.MarkLabel (trueLabel); + return new CompValuInteger (new TokenTypeInt (token.rValLeft), 1); + } + ilGen.Emit (token, OpCodes.Ldc_I4_0); + donessor: + ScriptMyLabel doneLabel = ilGen.DefineLabel ("ssanddone"); + ilGen.Emit (token, OpCodes.Br, doneLabel); + ilGen.MarkLabel (trueLabel); + ilGen.Emit (token, OpCodes.Ldc_I4_1); + ilGen.MarkLabel (doneLabel); + CompValuTemp retRVal = new CompValuTemp (new TokenTypeInt (token), this); + retRVal.Pop (this, token); + return retRVal; + } + + if (leftVal) { + return new CompValuInteger (new TokenTypeInt (token.rValLeft), 1); + } + + right = GenerateFromRVal (token.rValRight); + if (!IsConstBoolExpr (right, out rightVal)) { + right.PushVal (this, tokenTypeBool); + CompValuTemp retRVal = new CompValuTemp (new TokenTypeInt (token), this); + retRVal.Pop (this, token); + return retRVal; + } + return new CompValuInteger (new TokenTypeInt (token), rightVal ? 1 : 0); + } + + /* + * Computation of some sort, compute right-hand operand value then left-hand value + * because LSL is supposed to be right-to-left evaluation. + */ + right = Trivialize (GenerateFromRVal (token.rValRight), token.rValRight); + + /* + * If left is a script-defined class and there is a method with the operator's name, + * convert this to a call to that method with the right value as its single parameter. + * Except don't if the right value is 'undef' so they can always compare to undef. + */ + TokenType leftType = token.rValLeft.GetRValType (this, null); + if ((leftType is TokenTypeSDTypeClass) && !(right.type is TokenTypeUndef)) { + TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)leftType; + TokenDeclSDTypeClass sdtDecl = sdtType.decl; + TokenType[] argsig = new TokenType[] { right.type }; + TokenName funcName = new TokenName (token.opcode, "$op" + opcodeIndex); + TokenDeclVar declFunc = FindThisMember (sdtDecl, funcName, argsig); + if (declFunc != null) { + CheckAccess (declFunc, funcName); + left = GenerateFromRVal (token.rValLeft); + CompValu method = AccessInstanceMember (declFunc, left, token, false); + CompValu[] argRVals = new CompValu[] { right }; + return GenerateACall (method, argRVals, token); + } + } + + /* + * Formulate key string for binOpStrings = (lefttype)(operator)(righttype) + */ + string leftIndex = leftType.ToString (); + string rightIndex = right.type.ToString (); + string key = leftIndex + opcodeIndex + rightIndex; + + /* + * If that key exists in table, then the operation is defined between those types + * ... and it produces an R-value of type as given in the table. + */ + BinOpStr binOpStr; + if (BinOpStr.defined.TryGetValue (key, out binOpStr)) { + + /* + * If table contained an explicit assignment type like +=, output the statement without + * casting the L-value, then return the L-value as the resultant value. + * + * Make sure we don't include comparisons (such as ==, >=, etc). + * Nothing like +=, -=, %=, etc, generate a boolean, only the comparisons. + */ + if ((binOpStr.outtype != typeof (bool)) && opcodeIndex.EndsWith ("=") && (opcodeIndex != "!=")) { + if (!(token.rValLeft is TokenLVal)) { + ErrorMsg (token.rValLeft, "invalid L-value"); + return GenerateFromRVal (token.rValLeft); + } + left = GenerateFromLVal ((TokenLVal)token.rValLeft); + binOpStr.emitBO (this, token, left, right, left); + return left; + } + + /* + * It's of the form left binop right. + * Compute left, perform operation then put result in a temp. + */ + left = GenerateFromRVal (token.rValLeft); + CompValu retRVal = new CompValuTemp (TokenType.FromSysType (token.opcode, binOpStr.outtype), this); + binOpStr.emitBO (this, token, left, right, retRVal); + return retRVal; + } + + /* + * Nothing in the table, check for comparing object pointers because of the myriad of types possible. + * This will compare list pointers, null pointers, script-defined type pointers, array pointers, etc. + * It will show equal iff the memory addresses are equal and that is good enough. + */ + if (!leftType.ToSysType().IsValueType && !right.type.ToSysType().IsValueType && ((opcodeIndex == "==") || (opcodeIndex == "!="))) { + CompValuTemp retRVal = new CompValuTemp (new TokenTypeInt (token), this); + left = GenerateFromRVal (token.rValLeft); + left.PushVal (this, token.rValLeft); + right.PushVal (this, token.rValRight); + ilGen.Emit (token, OpCodes.Ceq); + if (opcodeIndex == "!=") { + ilGen.Emit (token, OpCodes.Ldc_I4_1); + ilGen.Emit (token, OpCodes.Xor); + } + retRVal.Pop (this, token); + return retRVal; + } + + /* + * If the opcode ends with "=", it may be something like "+=". + * So look up the key as if we didn't have the "=" to tell us if the operation is legal. + * Also, the binary operation's output type must be the same as the L-value type. + * Likewise, integer += float not allowed because result is float, but float += integer is ok. + */ + if (opcodeIndex.EndsWith ("=")) { + key = leftIndex + opcodeIndex.Substring (0, opcodeIndex.Length - 1) + rightIndex; + if (BinOpStr.defined.TryGetValue (key, out binOpStr)) { + if (!(token.rValLeft is TokenLVal)) { + ErrorMsg (token, "invalid L-value for ="); + return GenerateFromRVal (token.rValLeft); + } + if (!binOpStr.rmwOK) { + ErrorMsg (token, "= not allowed: " + leftIndex + " " + opcodeIndex + " " + rightIndex); + return new CompValuVoid (token); + } + + /* + * Now we know for something like %= that left%right is legal for the types given. + */ + left = GenerateFromLVal ((TokenLVal)token.rValLeft); + if (binOpStr.outtype == leftType.ToSysType ()) { + binOpStr.emitBO (this, token, left, right, left); + } else { + CompValu temp = new CompValuTemp (TokenType.FromSysType (token, binOpStr.outtype), this); + binOpStr.emitBO (this, token, left, right, temp); + left.PopPre (this, token); + temp.PushVal (this, token, leftType); + left.PopPost (this, token); + } + return left; + } + } + + /* + * Can't find it, oh well. + */ + ErrorMsg (token, "op not defined: " + leftIndex + " " + opcodeIndex + " " + rightIndex); + return new CompValuVoid (token); + } + + /** + * @brief Queue the given operands to the end of the scos list. + * If it can be broken down into more string concat operands, do so. + * Otherwise, just push it as one operand. + * @param leftRVal = left-hand operand of a '+' operation + * @param rightRVal = right-hand operand of a '+' operation + * @param scos = left-to-right list of operands for the string concat so far + * @param addop = the add operator token (either '+' or '+=') + * @returns false: neither operand is a string, nothing added to scos + * true: scos = updated with leftRVal then rightRVal added onto the end, possibly broken down further + */ + private bool StringConcatOperands (TokenRVal leftRVal, TokenRVal rightRVal, List scos, TokenKw addop) + { + /* + * If neither operand is a string (eg, float+integer), then the result isn't going to be a string. + */ + TokenType leftType = leftRVal.GetRValType (this, null); + TokenType rightType = rightRVal.GetRValType (this, null); + if (!(leftType is TokenTypeStr) && !(rightType is TokenTypeStr)) return false; + + /* + * Also, list+string => list so reject that too. + * Also, string+list => list so reject that too. + */ + if (leftType is TokenTypeList) return false; + if (rightType is TokenTypeList) return false; + + /* + * Append values to the end of the list in left-to-right order. + * If value is formed from a something+something => string, + * push them as separate values, otherwise push as one value. + */ + StringConcatOperand (leftType, leftRVal, scos); + StringConcatOperand (rightType, rightRVal, scos); + + /* + * Maybe constant strings can be concatted. + */ + try { + int len; + while (((len = scos.Count) >= 2) && + ((leftRVal = scos[len-2]) is TokenRValConst) && + ((rightRVal = scos[len-1]) is TokenRValConst)) { + object sum = addop.binOpConst (((TokenRValConst)leftRVal).val, + ((TokenRValConst)rightRVal).val); + scos[len-2] = new TokenRValConst (addop, sum); + scos.RemoveAt (len - 1); + } + } catch { + } + + /* + * We pushed some string stuff. + */ + return true; + } + + /** + * @brief Queue the given operand to the end of the scos list. + * If it can be broken down into more string concat operands, do so. + * Otherwise, just push it as one operand. + * @param type = rVal's resultant type + * @param rVal = operand to examine + * @param scos = left-to-right list of operands for the string concat so far + * @returns with scos = updated with rVal added onto the end, possibly broken down further + */ + private void StringConcatOperand (TokenType type, TokenRVal rVal, List scos) + { + bool didOne; + do { + didOne = false; + rVal = rVal.TryComputeConstant (LookupBodyConstants, ref didOne); + } while (didOne); + + if (!(type is TokenTypeStr)) goto pushasis; + if (!(rVal is TokenRValOpBin)) goto pushasis; + TokenRValOpBin rValOpBin = (TokenRValOpBin)rVal; + if (!(rValOpBin.opcode is TokenKwAdd)) goto pushasis; + if (StringConcatOperands (rValOpBin.rValLeft, rValOpBin.rValRight, scos, rValOpBin.opcode)) return; + pushasis: + scos.Add (rVal); + } + + /** + * @brief compute the result of an unary operator + * @param token = unary operator token, includes the operand + * @returns where the resultant R-value is + */ + private CompValu GenerateFromRValOpUn (TokenRValOpUn token) + { + CompValu inRVal = GenerateFromRVal (token.rVal); + + /* + * Script-defined types can define their own methods to handle unary operators. + */ + if (inRVal.type is TokenTypeSDTypeClass) { + TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)inRVal.type; + TokenDeclSDTypeClass sdtDecl = sdtType.decl; + TokenName funcName = new TokenName (token.opcode, "$op" + token.opcode.ToString ()); + TokenDeclVar declFunc = FindThisMember (sdtDecl, funcName, zeroArgs); + if (declFunc != null) { + CheckAccess (declFunc, funcName); + CompValu method = AccessInstanceMember (declFunc, inRVal, token, false); + return GenerateACall (method, zeroCompValus, token); + } + } + + /* + * Otherwise use the default. + */ + return UnOpGenerate (inRVal, token.opcode); + } + + /** + * @brief postfix operator -- this returns the type and location of the resultant value + */ + private CompValu GenerateFromRValAsnPost (TokenRValAsnPost asnPost) + { + CompValu lVal = GenerateFromLVal (asnPost.lVal); + + /* + * Make up a temp to save original value in. + */ + CompValuTemp result = new CompValuTemp (lVal.type, this); + + /* + * Prepare to pop incremented value back into variable being incremented. + */ + lVal.PopPre (this, asnPost.lVal); + + /* + * Copy original value to temp and leave value on stack. + */ + lVal.PushVal (this, asnPost.lVal); + ilGen.Emit (asnPost.lVal, OpCodes.Dup); + result.Pop (this, asnPost.lVal); + + /* + * Perform the ++/--. + */ + if ((lVal.type is TokenTypeChar) || (lVal.type is TokenTypeInt)) { + ilGen.Emit (asnPost, OpCodes.Ldc_I4_1); + } else if (lVal.type is TokenTypeFloat) { + ilGen.Emit (asnPost, OpCodes.Ldc_R4, 1.0f); + } else { + lVal.PopPost (this, asnPost.lVal); + ErrorMsg (asnPost, "invalid type for " + asnPost.postfix.ToString ()); + return lVal; + } + switch (asnPost.postfix.ToString ()) { + case "++": { + ilGen.Emit (asnPost, OpCodes.Add); + break; + } + case "--": { + ilGen.Emit (asnPost, OpCodes.Sub); + break; + } + default: throw new Exception ("unknown asnPost op"); + } + + /* + * Store new value in original variable. + */ + lVal.PopPost (this, asnPost.lVal); + + return result; + } + + /** + * @brief prefix operator -- this returns the type and location of the resultant value + */ + private CompValu GenerateFromRValAsnPre (TokenRValAsnPre asnPre) + { + CompValu lVal = GenerateFromLVal (asnPre.lVal); + + /* + * Make up a temp to put result in. + */ + CompValuTemp result = new CompValuTemp (lVal.type, this); + + /* + * Prepare to pop incremented value back into variable being incremented. + */ + lVal.PopPre (this, asnPre.lVal); + + /* + * Push original value. + */ + lVal.PushVal (this, asnPre.lVal); + + /* + * Perform the ++/--. + */ + if ((lVal.type is TokenTypeChar) || (lVal.type is TokenTypeInt)) { + ilGen.Emit (asnPre, OpCodes.Ldc_I4_1); + } else if (lVal.type is TokenTypeFloat) { + ilGen.Emit (asnPre, OpCodes.Ldc_R4, 1.0f); + } else { + lVal.PopPost (this, asnPre.lVal); + ErrorMsg (asnPre, "invalid type for " + asnPre.prefix.ToString ()); + return lVal; + } + switch (asnPre.prefix.ToString ()) { + case "++": { + ilGen.Emit (asnPre, OpCodes.Add); + break; + } + case "--": { + ilGen.Emit (asnPre, OpCodes.Sub); + break; + } + default: throw new Exception ("unknown asnPre op"); + } + + /* + * Store new value in temp variable, keeping new value on stack. + */ + ilGen.Emit (asnPre.lVal, OpCodes.Dup); + result.Pop (this, asnPre.lVal); + + /* + * Store new value in original variable. + */ + lVal.PopPost (this, asnPre.lVal); + + return result; + } + + /** + * @brief Generate code that calls a function or object's method. + * @returns where the call's return value is stored (a TokenTypeVoid if void) + */ + private CompValu GenerateFromRValCall (TokenRValCall call) + { + CompValu method; + CompValu[] argRVals; + int i, nargs; + TokenRVal arg; + TokenType[] argTypes; + + /* + * Compute the values of all the function's call arguments. + * Save where the computation results are in the argRVals[] array. + * Might as well build the argument signature from the argument types, too. + */ + nargs = call.nArgs; + argRVals = new CompValu[nargs]; + argTypes = new TokenType[nargs]; + if (nargs > 0) { + i = 0; + for (arg = call.args; arg != null; arg = (TokenRVal)arg.nextToken) { + argRVals[i] = GenerateFromRVal (arg); + argTypes[i] = argRVals[i].type; + i ++; + } + } + + /* + * Get function/method's entrypoint that matches the call argument types. + */ + method = GenerateFromRVal (call.meth, argTypes); + if (method == null) return null; + + return GenerateACall (method, argRVals, call); + } + + /** + * @brief Generate call to a function/method. + * @param method = function/method being called + * @param argVRVals = its call parameters (zero length if none) + * @param call = where in source code call is being made from (for error messages) + * @returns type and location of return value (CompValuVoid if none) + */ + private CompValu GenerateACall (CompValu method, CompValu[] argRVals, Token call) + { + CompValuTemp result; + int i, nArgs; + TokenType retType; + TokenType[] argTypes; + + /* + * Must be some kind of callable. + */ + retType = method.GetRetType (); // TokenTypeVoid if void; null means a variable + if (retType == null) { + ErrorMsg (call, "must be a delegate, function or method"); + return new CompValuVoid (call); + } + + /* + * Get a location for return value. + */ + if (retType is TokenTypeVoid) { + result = new CompValuVoid (call); + } else { + result = new CompValuTemp (retType, this); + } + + /* + * Make sure all arguments are trivial, ie, don't involve their own call labels. + * For any that aren't, output code to calculate the arg and put in a temporary. + */ + nArgs = argRVals.Length; + for (i = 0; i < nArgs; i ++) { + if (!argRVals[i].IsReadTrivial (this, call)) { + argRVals[i] = Trivialize (argRVals[i], call); + } + } + + /* + * Inline functions know how to generate their own call. + */ + if (method is CompValuInline) { + CompValuInline inline = (CompValuInline)method; + inline.declInline.CodeGen (this, call, result, argRVals); + return result; + } + + /* + * Push whatever the function/method needs as a this argument, if anything. + */ + method.CallPre (this, call); + + /* + * Push the script-visible args, left-to-right. + */ + argTypes = method.GetArgTypes (); + for (i = 0; i < nArgs; i ++) { + if (argTypes == null) { + argRVals[i].PushVal (this, call); + } else { + argRVals[i].PushVal (this, call, argTypes[i]); + } + } + + /* + * Now output call instruction. + */ + method.CallPost (this, call); + + /* + * Deal with the return value (if any), by putting it in 'result'. + */ + result.Pop (this, call, retType); + return result; + } + + /** + * @brief This is needed to avoid nesting call labels around non-trivial properties. + * It should be used for the second (and later) operands. + * Note that a 'call' is considered an operator, so all arguments of a call + * should be trivialized, but the method itself does not need to be. + */ + public CompValu Trivialize (CompValu operand, Token errorAt) + { + if (operand.IsReadTrivial (this, errorAt)) return operand; + CompValuTemp temp = new CompValuTemp (operand.type, this); + operand.PushVal (this, errorAt); + temp.Pop (this, errorAt); + return temp; + } + + /** + * @brief Generate code that casts a value to a particular type. + * @returns where the result of the conversion is stored. + */ + private CompValu GenerateFromRValCast (TokenRValCast cast) + { + /* + * If casting to a delegate type, use the argment signature + * of the delegate to help select the function/method, eg, + * '(delegate string(integer))ToString' + * will select 'string ToString(integer x)' + * instaead of 'string ToString(float x)' or anything else + */ + TokenType[] argsig = null; + TokenType outType = cast.castTo; + if (outType is TokenTypeSDTypeDelegate) { + argsig = ((TokenTypeSDTypeDelegate)outType).decl.GetArgTypes (); + } + + /* + * Generate the value that is being cast. + * If the value is already the requested type, just use it as is. + */ + CompValu inRVal = GenerateFromRVal (cast.rVal, argsig); + if (inRVal.type == outType) return inRVal; + + /* + * Different type, generate casting code, putting the result in a temp of the output type. + */ + CompValu outRVal = new CompValuTemp (outType, this); + outRVal.PopPre (this, cast); + inRVal.PushVal (this, cast, outType, true); + outRVal.PopPost (this, cast); + return outRVal; + } + + /** + * @brief Compute conditional expression value. + * @returns type and location of computed value. + */ + private CompValu GenerateFromRValCondExpr (TokenRValCondExpr rValCondExpr) + { + bool condVal; + CompValu condValu = GenerateFromRVal (rValCondExpr.condExpr); + if (IsConstBoolExpr (condValu, out condVal)) { + return GenerateFromRVal (condVal ? rValCondExpr.trueExpr : rValCondExpr.falseExpr); + } + + ScriptMyLabel falseLabel = ilGen.DefineLabel ("condexfalse"); + ScriptMyLabel doneLabel = ilGen.DefineLabel ("condexdone"); + + condValu.PushVal (this, rValCondExpr.condExpr, tokenTypeBool); + ilGen.Emit (rValCondExpr, OpCodes.Brfalse, falseLabel); + + CompValu trueValu = GenerateFromRVal (rValCondExpr.trueExpr); + trueValu.PushVal (this, rValCondExpr.trueExpr); + ilGen.Emit (rValCondExpr, OpCodes.Br, doneLabel); + + ilGen.MarkLabel (falseLabel); + CompValu falseValu = GenerateFromRVal (rValCondExpr.falseExpr); + falseValu.PushVal (this, rValCondExpr.falseExpr); + + if (trueValu.type.GetType () != falseValu.type.GetType ()) { + ErrorMsg (rValCondExpr, "? operands " + trueValu.type.ToString () + " : " + + falseValu.type.ToString () + " must be of same type"); + } + + ilGen.MarkLabel (doneLabel); + CompValuTemp retRVal = new CompValuTemp (trueValu.type, this); + retRVal.Pop (this, rValCondExpr); + return retRVal; + } + + /** + * @brief Constant in the script somewhere + * @returns where the constants value is stored + */ + private CompValu GenerateFromRValConst (TokenRValConst rValConst) + { + switch (rValConst.type) { + case TokenRValConstType.CHAR: { + return new CompValuChar (new TokenTypeChar (rValConst), (char)(rValConst.val)); + } + case TokenRValConstType.FLOAT: { + return new CompValuFloat (new TokenTypeFloat (rValConst), (double)(rValConst.val)); + } + case TokenRValConstType.INT: { + return new CompValuInteger (new TokenTypeInt (rValConst), (int)(rValConst.val)); + } + case TokenRValConstType.KEY: { + return new CompValuString (new TokenTypeKey (rValConst), (string)(rValConst.val)); + } + case TokenRValConstType.STRING: { + return new CompValuString (new TokenTypeStr (rValConst), (string)(rValConst.val)); + } + } + throw new Exception ("unknown constant type " + rValConst.val.GetType ()); + } + + /** + * @brief generate a new list object + * @param rValList = an rVal to create it from + */ + private CompValu GenerateFromRValList (TokenRValList rValList) + { + /* + * Compute all element values and remember where we put them. + * Do it right-to-left as customary for LSL scripts. + */ + int i = 0; + TokenRVal lastRVal = null; + for (TokenRVal val = rValList.rVal; val != null; val = (TokenRVal)val.nextToken) { + i ++; + val.prevToken = lastRVal; + lastRVal = val; + } + CompValu[] vals = new CompValu[i]; + for (TokenRVal val = lastRVal; val != null; val = (TokenRVal)val.prevToken) { + vals[--i] = GenerateFromRVal (val); + } + + /* + * This is the temp that will hold the created list. + */ + CompValuTemp newList = new CompValuTemp (new TokenTypeList (rValList.rVal), this); + + /* + * Create a temp object[] array to hold all the initial values. + */ + ilGen.Emit (rValList, OpCodes.Ldc_I4, rValList.nItems); + ilGen.Emit (rValList, OpCodes.Newarr, typeof (object)); + + /* + * Populate the array. + */ + i = 0; + for (TokenRVal val = rValList.rVal; val != null; val = (TokenRVal)val.nextToken) { + + /* + * Get pointer to temp array object. + */ + ilGen.Emit (rValList, OpCodes.Dup); + + /* + * Get index in that array. + */ + ilGen.Emit (rValList, OpCodes.Ldc_I4, i); + + /* + * Store initialization value in array location. + * However, floats and ints need to be converted to LSL_Float and LSL_Integer, + * or things like llSetPayPrice() will puque when they try to cast the elements + * to LSL_Float or LSL_Integer. Likewise with string/LSL_String. + * + * Maybe it's already LSL-boxed so we don't do anything with it except make sure + * it is an object, not a struct. + */ + CompValu eRVal = vals[i++]; + eRVal.PushVal (this, val); + if (eRVal.type.ToLSLWrapType () == null) { + if (eRVal.type is TokenTypeFloat) { + ilGen.Emit (val, OpCodes.Newobj, lslFloatConstructorInfo); + ilGen.Emit (val, OpCodes.Box, typeof (LSL_Float)); + } else if (eRVal.type is TokenTypeInt) { + ilGen.Emit (val, OpCodes.Newobj, lslIntegerConstructorInfo); + ilGen.Emit (val, OpCodes.Box, typeof (LSL_Integer)); + } else if ((eRVal.type is TokenTypeKey) || (eRVal.type is TokenTypeStr)) { + ilGen.Emit (val, OpCodes.Newobj, lslStringConstructorInfo); + ilGen.Emit (val, OpCodes.Box, typeof (LSL_String)); + } else if (eRVal.type.ToSysType ().IsValueType) { + ilGen.Emit (val, OpCodes.Box, eRVal.type.ToSysType ()); + } + } else if (eRVal.type.ToLSLWrapType ().IsValueType) { + + // Convert the LSL value structs to an object of the LSL-boxed type + ilGen.Emit (val, OpCodes.Box, eRVal.type.ToLSLWrapType ()); + } + ilGen.Emit (val, OpCodes.Stelem, typeof (object)); + } + + /* + * Create new list object from temp initial value array (whose ref is still on the stack). + */ + ilGen.Emit (rValList, OpCodes.Newobj, lslListConstructorInfo); + newList.Pop (this, rValList); + return newList; + } + + /** + * @brief New array allocation with initializer expressions. + */ + private CompValu GenerateFromRValNewArIni (TokenRValNewArIni rValNewArIni) + { + return MallocAndInitArray (rValNewArIni.arrayType, rValNewArIni.valueList); + } + + /** + * @brief Mallocate and initialize an array from its initialization list. + * @param arrayType = type of the array to be allocated and initialized + * @param values = initialization value list used to size and initialize the array. + * @returns memory location of the resultant initialized array. + */ + private CompValu MallocAndInitArray (TokenType arrayType, TokenList values) + { + TokenDeclSDTypeClass arrayDecl = ((TokenTypeSDTypeClass)arrayType).decl; + TokenType eleType = arrayDecl.arrayOfType; + int rank = arrayDecl.arrayOfRank; + + // Get size of each of the dimensions by scanning the initialization value list + int[] dimSizes = new int[rank]; + FillInDimSizes (dimSizes, 0, rank, values); + + // Figure out where the array's $new() method is + TokenType[] newargsig = new TokenType[rank]; + for (int k = 0; k < rank; k ++) { + newargsig[k] = tokenTypeInt; + } + TokenDeclVar newMeth = FindThisMember (arrayDecl, new TokenName (null, "$new"), newargsig); + + // Output a call to malloc the array with all default values + // array = ArrayType.$new (dimSizes[0], dimSizes[1], ...) + CompValuTemp array = new CompValuTemp (arrayType, this); + PushXMRInst (); + for (int k = 0; k < rank; k ++) { + ilGen.Emit (values, OpCodes.Ldc_I4, dimSizes[k]); + } + ilGen.Emit (values, OpCodes.Call, newMeth.ilGen); + array.Pop (this, arrayType); + + // Figure out where the array's Set() method is + TokenType[] setargsig = new TokenType[rank+1]; + for (int k = 0; k < rank; k ++) { + setargsig[k] = tokenTypeInt; + } + setargsig[rank] = eleType; + TokenDeclVar setMeth = FindThisMember (arrayDecl, new TokenName (null, "Set"), setargsig); + + // Fill in the array with the initializer values + FillInInitVals (array, setMeth, dimSizes, 0, rank, values, eleType); + + // The array is our resultant value + return array; + } + + /** + * @brief Compute an array's dimensions given its initialization value list + * @param dimSizes = filled in with array's dimensions + * @param dimNo = what dimension the 'values' list applies to + * @param rank = total number of dimensions of the array + * @param values = list of values to initialize the array's 'dimNo' dimension with + * @returns with dimSizes[dimNo..rank-1] filled in + */ + private static void FillInDimSizes (int[] dimSizes, int dimNo, int rank, TokenList values) + { + // the size of a dimension is the largest number of initializer elements at this level + // for dimNo 0, this is the number of elements in the top-level list + if (dimSizes[dimNo] < values.tl.Count) dimSizes[dimNo] = values.tl.Count; + + // see if there is another dimension to calculate + if (++ dimNo < rank) { + + // its size is the size of the largest initializer list at the next inner level + foreach (Token val in values.tl) { + if (val is TokenList) { + TokenList subvals = (TokenList)val; + FillInDimSizes (dimSizes, dimNo, rank, subvals); + } + } + } + } + + /** + * @brief Output code to fill in array's initialization values + * @param array = array to be filled in + * @param setMeth = the array's Set() method + * @param subscripts = holds subscripts being built + * @param dimNo = which dimension the 'values' are for + * @param values = list of initialization values for dimension 'dimNo' + * @param rank = number of dimensions of 'array' + * @param values = list of values to initialize the array's 'dimNo' dimension with + * @param eleType = the element's type + * @returns with code emitted to initialize array's [subscripts[0], ..., subscripts[dimNo-1], *, *, ...] + * dimNo and up completely filled ---^ + */ + private void FillInInitVals (CompValu array, TokenDeclVar setMeth, int[] subscripts, int dimNo, int rank, TokenList values, TokenType eleType) + { + subscripts[dimNo] = 0; + foreach (Token val in values.tl) { + CompValu initValue = null; + + /* + * If it is a sublist, process it. + * If we don't have enough subscripts yet, hopefully that sublist will have enough. + * If we already have enough subscripts, then that sublist can be for an element of this supposedly jagged array. + */ + if (val is TokenList) { + TokenList sublist = (TokenList)val; + if (dimNo + 1 < rank) { + + /* + * We don't have enough subscripts yet, hopefully the sublist has the rest. + */ + FillInInitVals (array, setMeth, subscripts, dimNo + 1, rank, sublist, eleType); + } else if ((eleType is TokenTypeSDTypeClass) && (((TokenTypeSDTypeClass)eleType).decl.arrayOfType == null)) { + + /* + * If we aren't a jagged array either, we can't do anything with the sublist. + */ + ErrorMsg (val, "too many brace levels"); + } else { + + /* + * We are a jagged array, so malloc a subarray and initialize it with the sublist. + * Then we can use that subarray to fill this array's element. + */ + initValue = MallocAndInitArray (eleType, sublist); + } + } + + /* + * If it is a value expression, then output code to compute the value. + */ + if (val is TokenRVal) { + if (dimNo + 1 < rank) { + ErrorMsg ((Token)val, "not enough brace levels"); + } else { + initValue = GenerateFromRVal ((TokenRVal)val); + } + } + + /* + * If there is an initValue, output "array.Set (subscript[0], subscript[1], ..., initValue)" + */ + if (initValue != null) { + array.PushVal (this, val); + for (int i = 0; i <= dimNo; i ++) { + ilGen.Emit (val, OpCodes.Ldc_I4, subscripts[i]); + } + initValue.PushVal (this, val, eleType); + ilGen.Emit (val, OpCodes.Call, setMeth.ilGen); + } + + /* + * That subscript is processed one way or another, on to the next. + */ + subscripts[dimNo] ++; + } + } + + /** + * @brief parenthesized expression + * @returns type and location of the result of the computation. + */ + private CompValu GenerateFromRValParen (TokenRValParen rValParen) + { + return GenerateFromRVal (rValParen.rVal); + } + + /** + * @brief create a rotation object from the x,y,z,w value expressions. + */ + private CompValu GenerateFromRValRot (TokenRValRot rValRot) + { + CompValu xRVal, yRVal, zRVal, wRVal; + + xRVal = Trivialize (GenerateFromRVal (rValRot.xRVal), rValRot); + yRVal = Trivialize (GenerateFromRVal (rValRot.yRVal), rValRot); + zRVal = Trivialize (GenerateFromRVal (rValRot.zRVal), rValRot); + wRVal = Trivialize (GenerateFromRVal (rValRot.wRVal), rValRot); + return new CompValuRot (new TokenTypeRot (rValRot), xRVal, yRVal, zRVal, wRVal); + } + + /** + * @brief Using 'this' as a pointer to the current script-defined instance object. + * The value is located in arg #0 of the current instance method. + */ + private CompValu GenerateFromRValThis (TokenRValThis zhis) + { + if (!IsSDTInstMethod ()) { + ErrorMsg (zhis, "cannot access instance member of class from static method"); + return new CompValuVoid (zhis); + } + return new CompValuArg (curDeclFunc.sdtClass.MakeRefToken (zhis), 0); + } + + /** + * @brief 'undefined' constant. + * If this constant gets written to an array element, it will delete that element from the array. + * If the script retrieves an element by key that is not defined, it will get this value. + * This value can be stored in and retrieved from variables of type 'object' or script-defined classes. + * It is a runtime error to cast this value to any other type, eg, + * we don't allow list or string variables to be null pointers. + */ + private CompValu GenerateFromRValUndef (TokenRValUndef rValUndef) + { + return new CompValuNull (new TokenTypeUndef (rValUndef)); + } + + /** + * @brief create a vector object from the x,y,z value expressions. + */ + private CompValu GenerateFromRValVec (TokenRValVec rValVec) + { + CompValu xRVal, yRVal, zRVal; + + xRVal = Trivialize (GenerateFromRVal (rValVec.xRVal), rValVec); + yRVal = Trivialize (GenerateFromRVal (rValVec.yRVal), rValVec); + zRVal = Trivialize (GenerateFromRVal (rValVec.zRVal), rValVec); + return new CompValuVec (new TokenTypeVec (rValVec), xRVal, yRVal, zRVal); + } + + /** + * @brief Generate code to get the default initialization value for a variable. + */ + private CompValu GenerateFromRValInitDef (TokenRValInitDef rValInitDef) + { + TokenType type = rValInitDef.type; + + if (type is TokenTypeChar) { + return new CompValuChar (type, (char)0); + } + if (type is TokenTypeRot) { + CompValuFloat x = new CompValuFloat (type, ScriptBaseClass.ZERO_ROTATION.x); + CompValuFloat y = new CompValuFloat (type, ScriptBaseClass.ZERO_ROTATION.y); + CompValuFloat z = new CompValuFloat (type, ScriptBaseClass.ZERO_ROTATION.z); + CompValuFloat s = new CompValuFloat (type, ScriptBaseClass.ZERO_ROTATION.s); + return new CompValuRot (type, x, y, z, s); + } + if ((type is TokenTypeKey) || (type is TokenTypeStr)) { + return new CompValuString (type, ""); + } + if (type is TokenTypeVec) { + CompValuFloat x = new CompValuFloat (type, ScriptBaseClass.ZERO_VECTOR.x); + CompValuFloat y = new CompValuFloat (type, ScriptBaseClass.ZERO_VECTOR.y); + CompValuFloat z = new CompValuFloat (type, ScriptBaseClass.ZERO_VECTOR.z); + return new CompValuVec (type, x, y, z); + } + if (type is TokenTypeInt) { + return new CompValuInteger (type, 0); + } + if (type is TokenTypeFloat) { + return new CompValuFloat (type, 0); + } + if (type is TokenTypeVoid) { + return new CompValuVoid (type); + } + + /* + * Default for 'object' type is 'undef'. + * Likewise for script-defined classes and interfaces. + */ + if ((type is TokenTypeObject) || (type is TokenTypeSDTypeClass) || (type is TokenTypeSDTypeDelegate) || + (type is TokenTypeSDTypeInterface) || (type is TokenTypeExc)) { + return new CompValuNull (type); + } + + /* + * array and list + */ + CompValuTemp temp = new CompValuTemp (type, this); + PushDefaultValue (type); + temp.Pop (this, rValInitDef, type); + return temp; + } + + /** + * @brief Generate code to process an is expression, and produce a boolean value. + */ + private CompValu GenerateFromRValIsType (TokenRValIsType rValIsType) + { + /* + * Expression we want to know the type of. + */ + CompValu val = GenerateFromRVal (rValIsType.rValExp); + + /* + * Pass it in to top-level type expression decoder. + */ + return GenerateFromTypeExp (val, rValIsType.typeExp); + } + + /** + * @brief See if the type of the given value matches the type expression. + * @param val = where the value to be evaluated is stored + * @param typeExp = script tokens representing type expression + * @returns location where the boolean result is stored + */ + private CompValu GenerateFromTypeExp (CompValu val, TokenTypeExp typeExp) + { + if (typeExp is TokenTypeExpBinOp) { + CompValu left = GenerateFromTypeExp (val, ((TokenTypeExpBinOp)typeExp).leftOp); + CompValu right = GenerateFromTypeExp (val, ((TokenTypeExpBinOp)typeExp).rightOp); + CompValuTemp result = new CompValuTemp (tokenTypeBool, this); + Token op = ((TokenTypeExpBinOp)typeExp).binOp; + left.PushVal (this, ((TokenTypeExpBinOp)typeExp).leftOp); + right.PushVal (this, ((TokenTypeExpBinOp)typeExp).rightOp); + if (op is TokenKwAnd) { + ilGen.Emit (typeExp, OpCodes.And); + } else if (op is TokenKwOr) { + ilGen.Emit (typeExp, OpCodes.Or); + } else { + throw new Exception ("unknown TokenTypeExpBinOp " + op.GetType ()); + } + result.Pop (this, typeExp); + return result; + } + if (typeExp is TokenTypeExpNot) { + CompValu interm = GenerateFromTypeExp (val, ((TokenTypeExpNot)typeExp).typeExp); + CompValuTemp result = new CompValuTemp (tokenTypeBool, this); + interm.PushVal (this, ((TokenTypeExpNot)typeExp).typeExp, tokenTypeBool); + ilGen.Emit (typeExp, OpCodes.Ldc_I4_1); + ilGen.Emit (typeExp, OpCodes.Xor); + result.Pop (this, typeExp); + return result; + } + if (typeExp is TokenTypeExpPar) { + return GenerateFromTypeExp (val, ((TokenTypeExpPar)typeExp).typeExp); + } + if (typeExp is TokenTypeExpType) { + CompValuTemp result = new CompValuTemp (tokenTypeBool, this); + val.PushVal (this, typeExp); + ilGen.Emit (typeExp, OpCodes.Isinst, ((TokenTypeExpType)typeExp).typeToken.ToSysType ()); + ilGen.Emit (typeExp, OpCodes.Ldnull); + ilGen.Emit (typeExp, OpCodes.Ceq); + ilGen.Emit (typeExp, OpCodes.Ldc_I4_1); + ilGen.Emit (typeExp, OpCodes.Xor); + result.Pop (this, typeExp); + return result; + } + if (typeExp is TokenTypeExpUndef) { + CompValuTemp result = new CompValuTemp (tokenTypeBool, this); + val.PushVal (this, typeExp); + ilGen.Emit (typeExp, OpCodes.Ldnull); + ilGen.Emit (typeExp, OpCodes.Ceq); + result.Pop (this, typeExp); + return result; + } + throw new Exception ("unknown TokenTypeExp type " + typeExp.GetType ()); + } + + /** + * @brief Push the default (null) value for a particular variable + * @param var = variable to get the default value for + * @returns with value pushed on stack + */ + public void PushVarDefaultValue (TokenDeclVar var) + { + PushDefaultValue (var.type); + } + public void PushDefaultValue (TokenType type) + { + if (type is TokenTypeArray) { + PushXMRInst (); // instance + ilGen.Emit (type, OpCodes.Newobj, xmrArrayConstructorInfo); + return; + } + if (type is TokenTypeChar) { + ilGen.Emit (type, OpCodes.Ldc_I4_0); + return; + } + if (type is TokenTypeList) { + ilGen.Emit (type, OpCodes.Ldc_I4_0); + ilGen.Emit (type, OpCodes.Newarr, typeof (object)); + ilGen.Emit (type, OpCodes.Newobj, lslListConstructorInfo); + return; + } + if (type is TokenTypeRot) { + // Mono is tOO stOOpid to allow: ilGen.Emit (OpCodes.Ldsfld, zeroRotationFieldInfo); + ilGen.Emit (type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.x); + ilGen.Emit (type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.y); + ilGen.Emit (type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.z); + ilGen.Emit (type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.s); + ilGen.Emit (type, OpCodes.Newobj, lslRotationConstructorInfo); + return; + } + if ((type is TokenTypeKey) || (type is TokenTypeStr)) { + ilGen.Emit (type, OpCodes.Ldstr, ""); + return; + } + if (type is TokenTypeVec) { + // Mono is tOO stOOpid to allow: ilGen.Emit (OpCodes.Ldsfld, zeroVectorFieldInfo); + ilGen.Emit (type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_VECTOR.x); + ilGen.Emit (type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_VECTOR.y); + ilGen.Emit (type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_VECTOR.z); + ilGen.Emit (type, OpCodes.Newobj, lslVectorConstructorInfo); + return; + } + if (type is TokenTypeInt) { + ilGen.Emit (type, OpCodes.Ldc_I4_0); + return; + } + if (type is TokenTypeFloat) { + ilGen.Emit (type, OpCodes.Ldc_R4, 0.0f); + return; + } + + /* + * Default for 'object' type is 'undef'. + * Likewise for script-defined classes and interfaces. + */ + if ((type is TokenTypeObject) || (type is TokenTypeSDTypeClass) || (type is TokenTypeSDTypeInterface) || (type is TokenTypeExc)) { + ilGen.Emit (type, OpCodes.Ldnull); + return; + } + + /* + * Void is pushed as the default return value of a void function. + * So just push nothing as expected of void functions. + */ + if (type is TokenTypeVoid) { + return; + } + + /* + * Default for 'delegate' type is 'undef'. + */ + if (type is TokenTypeSDTypeDelegate) { + ilGen.Emit (type, OpCodes.Ldnull); + return; + } + + throw new Exception ("unknown type " + type.GetType ().ToString ()); + } + + /** + * @brief Determine if the expression has a constant boolean value + * and if so, if the value is true or false. + * @param expr = expression to evaluate + * @returns true: expression is contant and has boolean value true + * false: otherwise + */ + private bool IsConstBoolExprTrue (CompValu expr) + { + bool constVal; + return IsConstBoolExpr (expr, out constVal) && constVal; + } + + private bool IsConstBoolExpr (CompValu expr, out bool constVal) + { + if (expr is CompValuChar) { + constVal = ((CompValuChar)expr).x != 0; + return true; + } + if (expr is CompValuFloat) { + constVal = ((CompValuFloat)expr).x != (double)0; + return true; + } + if (expr is CompValuInteger) { + constVal = ((CompValuInteger)expr).x != 0; + return true; + } + if (expr is CompValuString) { + string s = ((CompValuString)expr).x; + constVal = s != ""; + if (constVal && (expr.type is TokenTypeKey)) { + constVal = s != ScriptBaseClass.NULL_KEY; + } + return true; + } + + constVal = false; + return false; + } + + /** + * @brief Determine if the expression has a constant integer value + * and if so, return the integer value. + * @param expr = expression to evaluate + * @returns true: expression is contant and has integer value + * false: otherwise + */ + private bool IsConstIntExpr (CompValu expr, out int constVal) + { + if (expr is CompValuChar) { + constVal = (int)((CompValuChar)expr).x; + return true; + } + if (expr is CompValuInteger) { + constVal = ((CompValuInteger)expr).x; + return true; + } + + constVal = 0; + return false; + } + + /** + * @brief Determine if the expression has a constant string value + * and if so, return the string value. + * @param expr = expression to evaluate + * @returns true: expression is contant and has string value + * false: otherwise + */ + private bool IsConstStrExpr (CompValu expr, out string constVal) + { + if (expr is CompValuString) { + constVal = ((CompValuString)expr).x; + return true; + } + constVal = ""; + return false; + } + + /** + * @brief create table of legal event handler prototypes. + * This is used to make sure script's event handler declrations are valid. + */ + private static VarDict CreateLegalEventHandlers () + { + /* + * Get handler prototypes with full argument lists. + */ + VarDict leh = new InternalFuncDict (typeof (IEventHandlers), false); + + /* + * We want the scripts to be able to declare their handlers with + * fewer arguments than the full argument lists. So define additional + * prototypes with fewer arguments. + */ + TokenDeclVar[] fullArgProtos = new TokenDeclVar[leh.Count]; + int i = 0; + foreach (TokenDeclVar fap in leh) fullArgProtos[i++] = fap; + + foreach (TokenDeclVar fap in fullArgProtos) { + TokenArgDecl fal = fap.argDecl; + int fullArgCount = fal.vars.Length; + for (i = 0; i < fullArgCount; i ++) { + TokenArgDecl shortArgList = new TokenArgDecl (null); + for (int j = 0; j < i; j ++) { + TokenDeclVar var = fal.vars[j]; + shortArgList.AddArg (var.type, var.name); + } + TokenDeclVar shortArgProto = new TokenDeclVar (null, null, null); + shortArgProto.name = new TokenName (null, fap.GetSimpleName ()); + shortArgProto.retType = fap.retType; + shortArgProto.argDecl = shortArgList; + leh.AddEntry (shortArgProto); + } + } + + return leh; + } + + /** + * @brief Emit a call to CheckRun(), (voluntary multitasking switch) + */ + public void EmitCallCheckRun (Token errorAt, bool stack) + { + if (curDeclFunc.IsFuncTrivial (this)) throw new Exception (curDeclFunc.fullName + " is supposed to be trivial"); + new CallLabel (this, errorAt); // jump here when stack restored + PushXMRInst (); // instance + ilGen.Emit (errorAt, OpCodes.Call, stack ? checkRunStackMethInfo : checkRunQuickMethInfo); + openCallLabel = null; + } + + /** + * @brief Emit code to push a callNo var on the stack. + */ + public void GetCallNo (Token errorAt, ScriptMyLocal callNoVar) + { + ilGen.Emit (errorAt, OpCodes.Ldloc, callNoVar); + //ilGen.Emit (errorAt, OpCodes.Ldloca, callNoVar); + //ilGen.Emit (errorAt, OpCodes.Volatile); + //ilGen.Emit (errorAt, OpCodes.Ldind_I4); + } + public void GetCallNo (Token errorAt, CompValu callNoVar) + { + callNoVar.PushVal (this, errorAt); + //callNoVar.PushRef (this, errorAt); + //ilGen.Emit (errorAt, OpCodes.Volatile); + //ilGen.Emit (errorAt, OpCodes.Ldind_I4); + } + + /** + * @brief Emit code to set a callNo var to a given constant. + */ + public void SetCallNo (Token errorAt, ScriptMyLocal callNoVar, int val) + { + ilGen.Emit (errorAt, OpCodes.Ldc_I4, val); + ilGen.Emit (errorAt, OpCodes.Stloc, callNoVar); + //ilGen.Emit (errorAt, OpCodes.Ldloca, callNoVar); + //ilGen.Emit (errorAt, OpCodes.Ldc_I4, val); + //ilGen.Emit (errorAt, OpCodes.Volatile); + //ilGen.Emit (errorAt, OpCodes.Stind_I4); + } + public void SetCallNo (Token errorAt, CompValu callNoVar, int val) + { + callNoVar.PopPre (this, errorAt); + ilGen.Emit (errorAt, OpCodes.Ldc_I4, val); + callNoVar.PopPost (this, errorAt); + //callNoVar.PushRef (this, errorAt); + //ilGen.Emit (errorAt, OpCodes.Ldc_I4, val); + //ilGen.Emit (errorAt, OpCodes.Volatile); + //ilGen.Emit (errorAt, OpCodes.Stind_I4); + } + + /** + * @brief handle a unary operator, such as -x. + */ + private CompValu UnOpGenerate (CompValu inRVal, Token opcode) + { + /* + * - Negate + */ + if (opcode is TokenKwSub) { + if (inRVal.type is TokenTypeFloat) { + CompValuTemp outRVal = new CompValuTemp (new TokenTypeFloat (opcode), this); + inRVal.PushVal (this, opcode, outRVal.type); // push value to negate, make sure not LSL-boxed + ilGen.Emit (opcode, OpCodes.Neg); // compute the negative + outRVal.Pop (this, opcode); // pop into result + return outRVal; // tell caller where we put it + } + if (inRVal.type is TokenTypeInt) { + CompValuTemp outRVal = new CompValuTemp (new TokenTypeInt (opcode), this); + inRVal.PushVal (this, opcode, outRVal.type); // push value to negate, make sure not LSL-boxed + ilGen.Emit (opcode, OpCodes.Neg); // compute the negative + outRVal.Pop (this, opcode); // pop into result + return outRVal; // tell caller where we put it + } + if (inRVal.type is TokenTypeRot) { + CompValuTemp outRVal = new CompValuTemp (inRVal.type, this); + inRVal.PushVal (this, opcode); // push rotation, then call negate routine + ilGen.Emit (opcode, OpCodes.Call, lslRotationNegateMethodInfo); + outRVal.Pop (this, opcode); // pop into result + return outRVal; // tell caller where we put it + } + if (inRVal.type is TokenTypeVec) { + CompValuTemp outRVal = new CompValuTemp (inRVal.type, this); + inRVal.PushVal (this, opcode); // push vector, then call negate routine + ilGen.Emit (opcode, OpCodes.Call, lslVectorNegateMethodInfo); + outRVal.Pop (this, opcode); // pop into result + return outRVal; // tell caller where we put it + } + ErrorMsg (opcode, "can't negate a " + inRVal.type.ToString ()); + return inRVal; + } + + /* + * ~ Complement (bitwise integer) + */ + if (opcode is TokenKwTilde) { + if (inRVal.type is TokenTypeInt) { + CompValuTemp outRVal = new CompValuTemp (new TokenTypeInt (opcode), this); + inRVal.PushVal (this, opcode, outRVal.type); // push value to negate, make sure not LSL-boxed + ilGen.Emit (opcode, OpCodes.Not); // compute the complement + outRVal.Pop (this, opcode); // pop into result + return outRVal; // tell caller where we put it + } + ErrorMsg (opcode, "can't complement a " + inRVal.type.ToString ()); + return inRVal; + } + + /* + * ! Not (boolean) + * + * We stuff the 0/1 result in an int because I've seen x+!y in scripts + * and we don't want to have to create tables to handle int+bool and + * everything like that. + */ + if (opcode is TokenKwExclam) { + CompValuTemp outRVal = new CompValuTemp (new TokenTypeInt (opcode), this); + inRVal.PushVal (this, opcode, tokenTypeBool); // anything converts to boolean + ilGen.Emit (opcode, OpCodes.Ldc_I4_1); // then XOR with 1 to flip it + ilGen.Emit (opcode, OpCodes.Xor); + outRVal.Pop (this, opcode); // pop into result + return outRVal; // tell caller where we put it + } + + throw new Exception ("unhandled opcode " + opcode.ToString ()); + } + + /** + * @brief This is called while trying to compute the value of constant initializers. + * It is passed a name and that name is looked up in the constant tables. + */ + private TokenRVal LookupInitConstants (TokenRVal rVal, ref bool didOne) + { + /* + * If it is a static field of a script-defined type, look it up and hopefully we find a constant there. + */ + TokenDeclVar gblVar; + if (rVal is TokenLValSField) { + TokenLValSField lvsf = (TokenLValSField)rVal; + if (lvsf.baseType is TokenTypeSDTypeClass) { + TokenDeclSDTypeClass sdtClass = ((TokenTypeSDTypeClass)lvsf.baseType).decl; + gblVar = sdtClass.members.FindExact (lvsf.fieldName.val, null); + if (gblVar != null) { + if (gblVar.constant && (gblVar.init is TokenRValConst)) { + didOne = true; + return gblVar.init; + } + } + } + return rVal; + } + + /* + * Only other thing we handle is stand-alone names. + */ + if (!(rVal is TokenLValName)) return rVal; + string name = ((TokenLValName)rVal).name.val; + + /* + * If we are doing the initializations for a script-defined type, + * look for the constant among the fields for that type. + */ + if (currentSDTClass != null) { + gblVar = currentSDTClass.members.FindExact (name, null); + if (gblVar != null) { + if (gblVar.constant && (gblVar.init is TokenRValConst)) { + didOne = true; + return gblVar.init; + } + return rVal; + } + } + + /* + * Look it up as a script-defined global variable. + * Then if the variable is defined as a constant and has a constant value, + * we are successful. If it is defined as something else, return failure. + */ + gblVar = tokenScript.variablesStack.FindExact (name, null); + if (gblVar != null) { + if (gblVar.constant && (gblVar.init is TokenRValConst)) { + didOne = true; + return gblVar.init; + } + return rVal; + } + + /* + * Maybe it is a built-in symbolic constant. + */ + ScriptConst scriptConst = ScriptConst.Lookup (name); + if (scriptConst != null) { + rVal = CompValuConst2RValConst (scriptConst.rVal, rVal); + if (rVal is TokenRValConst) { + didOne = true; + return rVal; + } + } + + /* + * Don't know what it is, return failure. + */ + return rVal; + } + + /** + * @brief This is called while trying to compute the value of constant expressions. + * It is passed a name and that name is looked up in the constant tables. + */ + private TokenRVal LookupBodyConstants (TokenRVal rVal, ref bool didOne) + { + /* + * If it is a static field of a script-defined type, look it up and hopefully we find a constant there. + */ + TokenDeclVar gblVar; + if (rVal is TokenLValSField) { + TokenLValSField lvsf = (TokenLValSField)rVal; + if (lvsf.baseType is TokenTypeSDTypeClass) { + TokenDeclSDTypeClass sdtClass = ((TokenTypeSDTypeClass)lvsf.baseType).decl; + gblVar = sdtClass.members.FindExact (lvsf.fieldName.val, null); + if ((gblVar != null) && gblVar.constant && (gblVar.init is TokenRValConst)) { + didOne = true; + return gblVar.init; + } + } + return rVal; + } + + /* + * Only other thing we handle is stand-alone names. + */ + if (!(rVal is TokenLValName)) return rVal; + string name = ((TokenLValName)rVal).name.val; + + /* + * Scan through the variable stack and hopefully we find a constant there. + * But we stop as soon as we get a match because that's what the script is referring to. + */ + CompValu val; + for (VarDict vars = ((TokenLValName)rVal).stack; vars != null; vars = vars.outerVarDict) { + TokenDeclVar var = vars.FindExact (name, null); + if (var != null) { + val = var.location; + goto foundit; + } + + TokenDeclSDTypeClass baseClass = vars.thisClass; + if (baseClass != null) { + while ((baseClass = baseClass.extends) != null) { + var = baseClass.members.FindExact (name, null); + if (var != null) { + val = var.location; + goto foundit; + } + } + } + } + + /* + * Maybe it is a built-in symbolic constant. + */ + ScriptConst scriptConst = ScriptConst.Lookup (name); + if (scriptConst != null) { + val = scriptConst.rVal; + goto foundit; + } + + /* + * Don't know what it is, return failure. + */ + return rVal; + + /* + * Found a CompValu. If it's a simple constant, then use it. + * Otherwise tell caller we failed to simplify. + */ + foundit: + rVal = CompValuConst2RValConst (val, rVal); + if (rVal is TokenRValConst) { + didOne = true; + } + return rVal; + } + + private static TokenRVal CompValuConst2RValConst (CompValu val, TokenRVal rVal) + { + if (val is CompValuChar) rVal = new TokenRValConst (rVal, ((CompValuChar)val).x); + if (val is CompValuFloat) rVal = new TokenRValConst (rVal, ((CompValuFloat)val).x); + if (val is CompValuInteger) rVal = new TokenRValConst (rVal, ((CompValuInteger)val).x); + if (val is CompValuString) rVal = new TokenRValConst (rVal, ((CompValuString)val).x); + return rVal; + } + + /** + * @brief Generate code to push XMRInstanceSuperType pointer on stack. + */ + public void PushXMRInst () + { + if (instancePointer == null) { + ilGen.Emit (null, OpCodes.Ldarg_0); + } else { + ilGen.Emit (null, OpCodes.Ldloc, instancePointer); + } + } + + /** + * @returns true: Ldarg_0 gives XMRSDTypeClObj pointer + * - this is the case for instance methods + * false: Ldarg_0 gives XMR_Instance pointer + * - this is the case for both global functions and static methods + */ + public bool IsSDTInstMethod () + { + return (curDeclFunc.sdtClass != null) && + ((curDeclFunc.sdtFlags & ScriptReduce.SDT_STATIC) == 0); + } + + /** + * @brief Look for a simply named function or variable (not a field or method) + */ + public TokenDeclVar FindNamedVar (TokenLValName lValName, TokenType[] argsig) + { + /* + * Look in variable stack for the given name. + */ + for (VarDict vars = lValName.stack; vars != null; vars = vars.outerVarDict) { + + // first look for it possibly with an argument signature + // so we pick the correct overloaded method + TokenDeclVar var = FindSingleMember (vars, lValName.name, argsig); + if (var != null) return var; + + // if that fails, try it without the argument signature. + // delegates get entered like any other variable, ie, + // no signature on their name. + if (argsig != null) { + var = FindSingleMember (vars, lValName.name, null); + if (var != null) return var; + } + + // if this is the frame for some class members, try searching base class members too + TokenDeclSDTypeClass baseClass = vars.thisClass; + if (baseClass != null) { + while ((baseClass = baseClass.extends) != null) { + var = FindSingleMember (baseClass.members, lValName.name, argsig); + if (var != null) return var; + if (argsig != null) { + var = FindSingleMember (baseClass.members, lValName.name, null); + if (var != null) return var; + } + } + } + } + + /* + * If not found, try one of the built-in constants or functions. + */ + if (argsig == null) { + ScriptConst scriptConst = ScriptConst.Lookup (lValName.name.val); + if (scriptConst != null) { + TokenDeclVar var = new TokenDeclVar (lValName.name, null, tokenScript); + var.name = lValName.name; + var.type = scriptConst.rVal.type; + var.location = scriptConst.rVal; + return var; + } + } else { + TokenDeclVar inline = FindSingleMember (TokenDeclInline.inlineFunctions, lValName.name, argsig); + if (inline != null) return inline; + } + + return null; + } + + + /** + * @brief Find a member of an interface. + * @param sdType = interface type + * @param name = name of member to find + * @param argsig = null: field/property; else: script-visible method argument types + * @param baseRVal = pointer to interface object + * @returns null: no such member + * else: pointer to member + * baseRVal = possibly modified to point to type-casted interface object + */ + private TokenDeclVar FindInterfaceMember (TokenTypeSDTypeInterface sdtType, TokenName name, TokenType[] argsig, ref CompValu baseRVal) + { + TokenDeclSDTypeInterface sdtDecl = sdtType.decl; + TokenDeclSDTypeInterface impl; + TokenDeclVar declVar = sdtDecl.FindIFaceMember (this, name, argsig, out impl); + if ((declVar != null) && (impl != sdtDecl)) { + + /* + * Accessing a method or propterty of another interface that the primary interface says it implements. + * In this case, we have to cast from the primary interface to that secondary interface. + * + * interface IEnumerable { + * IEnumerator GetEnumerator (); + * } + * interface ICountable : IEnumerable { + * integer GetCount (); + * } + * class List : ICountable { + * public GetCount () : ICountable { ... } + * public GetEnumerator () : IEnumerable { ... } + * } + * + * ICountable aList = new List (); + * IEnumerator anEnumer = aList.GetEnumerator (); << we are here + * << baseRVal = aList + * << sdtDecl = ICountable + * << impl = IEnumerable + * << name = GetEnumerator + * << argsig = () + * So we have to cast aList from ICountable to IEnumerable. + */ + + // make type token for the secondary interface type + TokenType subIntfType = impl.MakeRefToken (name); + + // make a temp variable of the secondary interface type + CompValuTemp castBase = new CompValuTemp (subIntfType, this); + + // output code to cast from the primary interface to the secondary interface + // this is 2 basic steps: + // 1) cast from primary interface object -> class object + // ...gets it from interfaceObject.delegateArray[0].Target + // 2) cast from class object -> secondary interface object + // ...gets it from classObject.sdtcITable[interfaceIndex] + baseRVal.PushVal (this, name, subIntfType); + + // save result of casting in temp + castBase.Pop (this, name); + + // return temp reference + baseRVal = castBase; + } + + return declVar; + } + + /** + * @brief Find a member of a script-defined type class. + * @param sdtType = reference to class declaration + * @param name = name of member to find + * @param argsig = argument signature used to select among overloaded members + * @returns null: no such member found + * else: the member found + */ + public TokenDeclVar FindThisMember (TokenTypeSDTypeClass sdtType, TokenName name, TokenType[] argsig) + { + return FindThisMember (sdtType.decl, name, argsig); + } + public TokenDeclVar FindThisMember (TokenDeclSDTypeClass sdtDecl, TokenName name, TokenType[] argsig) + { + for (TokenDeclSDTypeClass sdtd = sdtDecl; sdtd != null; sdtd = sdtd.extends) { + TokenDeclVar declVar = FindSingleMember (sdtd.members, name, argsig); + if (declVar != null) return declVar; + } + return null; + } + + /** + * @brief Look for a single member that matches the given name and argument signature + * @param where = which dictionary to look in + * @param name = basic name of the field or method, eg, "Printable" + * @param argsig = argument types the method is being called with, eg, "(string)" + * or null to find a field + * @returns null: no member found + * else: the member found + */ + public TokenDeclVar FindSingleMember (VarDict where, TokenName name, TokenType[] argsig) + { + TokenDeclVar[] members = where.FindCallables (name.val, argsig); + if (members == null) return null; + if (members.Length > 1) { + ErrorMsg (name, "more than one matching member"); + for (int i = 0; i < members.Length; i ++) { + ErrorMsg (members[i], " " + members[i].argDecl.GetArgSig ()); + } + } + return members[0]; + } + + /** + * @brief Find an exact function name and argument signature match. + * Also verify that the return value type is an exact match. + * @param where = which method dictionary to look in + * @param name = basic name of the method, eg, "Printable" + * @param ret = expected return value type + * @param argsig = argument types the method is being called with, eg, "(string)" + * @returns null: no exact match found + * else: the matching function + */ + private TokenDeclVar FindExactWithRet (VarDict where, TokenName name, TokenType ret, TokenType[] argsig) + { + TokenDeclVar func = where.FindExact (name.val, argsig); + if ((func != null) && (func.retType.ToString () != ret.ToString ())) { + ErrorMsg (name, "return type mismatch, have " + func.retType.ToString () + ", expect " + ret.ToString ()); + } + if (func != null) CheckAccess (func, name); + return func; + } + + /** + * @brief Check the private/protected/public access flags of a member. + */ + private void CheckAccess (TokenDeclVar var, Token errorAt) + { + TokenDeclSDType nested; + TokenDeclSDType definedBy = var.sdtClass; + TokenDeclSDType accessedBy = curDeclFunc.sdtClass; + + /*******************************\ + * Check member-level access * + \*******************************/ + + /* + * Note that if accessedBy is null, ie, accessing from global function (or event handlers), + * anything tagged as SDT_PRIVATE or SDT_PROTECTED will fail. + */ + + /* + * Private means accessed by the class that defined the member or accessed by a nested class + * of the class that defined the member. + */ + if ((var.sdtFlags & ScriptReduce.SDT_PRIVATE) != 0) { + for (nested = accessedBy; nested != null; nested = nested.outerSDType) { + if (nested == definedBy) goto acc1ok; + } + ErrorMsg (errorAt, "private member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName); + return; + } + + /* + * Protected means: + * If being accessed by an inner class, the inner class has access to it if the inner class derives + * from the declaring class. It also has access to it if an outer class derives from the declaring + * class. + */ + if ((var.sdtFlags & ScriptReduce.SDT_PROTECTED) != 0) { + for (nested = accessedBy; nested != null; nested = nested.outerSDType) { + for (TokenDeclSDType rootward = nested; rootward != null; rootward = rootward.extends) { + if (rootward == definedBy) goto acc1ok; + } + } + ErrorMsg (errorAt, "protected member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName); + return; + } + acc1ok: + + /******************************\ + * Check class-level access * + \******************************/ + + /* + * If being accessed by same or inner class than where defined, it is ok. + * + * class DefiningClass { + * varBeingAccessed; + * . + * . + * . + * class AccessingClass { + * functionDoingAccess() { } + * } + * . + * . + * . + * } + */ + nested = accessedBy; + while (true) { + if (nested == definedBy) return; + if (nested == null) break; + nested = (TokenDeclSDTypeClass)nested.outerSDType; + } + + /* + * It is being accessed by an outer class than where defined, + * check for a 'private' or 'protected' class tag that blocks. + */ + do { + + /* + * If the field's class is defined directly inside the accessing class, + * access is allowed regardless of class-level private or protected tags. + * + * class AccessingClass { + * functionDoingAccess() { } + * class DefiningClass { + * varBeingAccessed; + * } + * } + */ + if (definedBy.outerSDType == accessedBy) return; + + /* + * If the field's class is defined two or more levels inside the accessing class, + * access is denied if the defining class is tagged private. + * + * class AccessingClass { + * functionDoingAccess() { } + * . + * . + * . + * class IntermediateClass { + * private class DefiningClass { + * varBeingAccessed; + * } + * } + * . + * . + * . + * } + */ + if ((definedBy.accessLevel & ScriptReduce.SDT_PRIVATE) != 0) { + ErrorMsg (errorAt, "member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName + + " because of private class " + definedBy.longName.val); + return; + } + + /* + * Likewise, if DefiningClass is tagged protected, the AccessingClass must derive from the + * IntermediateClass or access is denied. + */ + if ((definedBy.accessLevel & ScriptReduce.SDT_PROTECTED) != 0) { + for (TokenDeclSDType extends = accessedBy; extends != definedBy.outerSDType; extends = extends.extends) { + if (extends == null) { + ErrorMsg (errorAt, "member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName + + " because of protected class " + definedBy.longName.val); + return; + } + } + } + + /* + * Check next outer level. + */ + definedBy = definedBy.outerSDType; + } while (definedBy != null); + } + + /** + * @brief Convert a list of argument types to printable string, eg, "(list,string,float,integer)" + * If given a null, return "" indicating it is a field not a method + */ + public static string ArgSigString (TokenType[] argsig) + { + if (argsig == null) return ""; + StringBuilder sb = new StringBuilder ("("); + for (int i = 0; i < argsig.Length; i ++) { + if (i > 0) sb.Append (","); + sb.Append (argsig[i].ToString ()); + } + sb.Append (")"); + return sb.ToString (); + } + + /** + * @brief output error message and remember that we did + */ + public void ErrorMsg (Token token, string message) + { + if ((token == null) || (token.emsg == null)) token = errorMessageToken; + if (!youveAnError || (token.file != lastErrorFile) || (token.line > lastErrorLine)) { + token.ErrorMsg (message); + youveAnError = true; + lastErrorFile = token.file; + lastErrorLine = token.line; + } + } + + /** + * @brief Find a private static method. + * @param owner = class the method is part of + * @param name = name of method to find + * @param args = array of argument types + * @returns pointer to method + */ + public static MethodInfo GetStaticMethod (Type owner, string name, Type[] args) + { + MethodInfo mi = owner.GetMethod (name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null); + if (mi == null) { + throw new Exception ("undefined method " + owner.ToString () + "." + name); + } + return mi; + } + + // http://wiki.secondlife.com/wiki/Rotation 'negate a rotation' says just negate .s component + // but http://wiki.secondlife.com/wiki/LSL_Language_Test (lslangtest1.lsl) says negate all 4 values + public static LSL_Rotation LSLRotationNegate (LSL_Rotation r) { return new LSL_Rotation (-r.x,-r.y,-r.z,-r.s); } + public static LSL_Vector LSLVectorNegate (LSL_Vector v) { return -v; } + public static string CatchExcToStr (Exception exc) { return exc.ToString(); } + //public static void ConsoleWrite (string str) { Console.Write(str); } + + /** + * @brief Defines an internal label that is used as a target for 'break' and 'continue' statements. + */ + private class BreakContTarg { + public bool used; + public ScriptMyLabel label; + public TokenStmtBlock block; + + public BreakContTarg (ScriptCodeGen scg, string name) { + used = false; // assume it isn't referenced at all + label = scg.ilGen.DefineLabel (name); // label that the break/continue jumps to + block = scg.curStmtBlock; // { ... } that the break/continue label is in + } + } + } + + /** + * @brief Marker interface indicates an exception that can't be caught by a script-level try/catch. + */ + public interface IXMRUncatchable { } + + /** + * @brief Thrown by a script when it attempts to change to an undefined state. + * These can be detected at compile time but the moron XEngine compiles + * such things, so we compile them as runtime errors. + */ + [SerializableAttribute] + public class ScriptUndefinedStateException : Exception, ISerializable { + public string stateName; + public ScriptUndefinedStateException (string stateName) : base ("undefined state " + stateName) { + this.stateName = stateName; + } + protected ScriptUndefinedStateException (SerializationInfo info, StreamingContext context) : base (info, context) + { } + } + + /** + * @brief Created by a throw statement. + */ + [SerializableAttribute] + public class ScriptThrownException : Exception, ISerializable { + public object thrown; + + /** + * @brief Called by a throw statement to wrap the object in a unique + * tag that capable of capturing a stack trace. Script can + * unwrap it by calling xmrExceptionThrownValue(). + */ + public static Exception Wrap (object thrown) + { + return new ScriptThrownException (thrown); + } + private ScriptThrownException (object thrown) : base (thrown.ToString ()) + { + this.thrown = thrown; + } + + /** + * @brief Used by serialization/deserialization. + */ + protected ScriptThrownException (SerializationInfo info, StreamingContext context) : base (info, context) + { } + } + + /** + * @brief Thrown by a script when it attempts to change to a defined state. + */ + [SerializableAttribute] + public class ScriptChangeStateException : Exception, ISerializable, IXMRUncatchable { + public int newState; + public ScriptChangeStateException (int newState) { + this.newState = newState; + } + protected ScriptChangeStateException (SerializationInfo info, StreamingContext context) : base (info, context) + { } + } + + /** + * @brief We are restoring to the body of a catch { } so we need to + * wrap the original exception in an outer exception, so the + * system won't try to refill the stack trace. + * + * We don't mark this one serializable as it should never get + * serialized out. It only lives from the throw to the very + * beginning of the catch handler where it is promptly unwrapped. + * No CheckRun() call can possibly intervene. + */ + public class ScriptRestoreCatchException : Exception { + + // old code uses these + private object e; + public ScriptRestoreCatchException (object e) { + this.e = e; + } + public static object Unwrap (object o) + { + if (o is IXMRUncatchable) return null; + if (o is ScriptRestoreCatchException) return ((ScriptRestoreCatchException)o).e; + return o; + } + + // new code uses these + private Exception ee; + public ScriptRestoreCatchException (Exception ee) { + this.ee = ee; + } + public static Exception Unwrap (Exception oo) + { + if (oo is IXMRUncatchable) return null; + if (oo is ScriptRestoreCatchException) return ((ScriptRestoreCatchException)oo).ee; + return oo; + } + } + + [SerializableAttribute] + public class ScriptBadCallNoException : Exception { + public ScriptBadCallNoException (int callNo) : base ("bad callNo " + callNo) { } + protected ScriptBadCallNoException (SerializationInfo info, StreamingContext context) : base (info, context) + { } + } + + public class CVVMismatchException : Exception { + public int oldcvv; + public int newcvv; + + public CVVMismatchException (int oldcvv, int newcvv) : base ("object version is " + oldcvv.ToString () + + " but accept only " + newcvv.ToString ()) + { + this.oldcvv = oldcvv; + this.newcvv = newcvv; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCollector.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCollector.cs new file mode 100644 index 0000000..9c0d621 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCollector.cs @@ -0,0 +1,2637 @@ +/* + * 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; + + +/** + * @brief Wrapper class for ScriptMyILGen to do simple optimizations. + * The main one is to figure out which locals are active at the labels + * so the stack capture/restore code doesn't have to do everything. + * Second is it removes unnecessary back-to-back stloc/ldloc's. + */ + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + /** + * @brief This is a list that keeps track of types pushed on the evaluation stack. + */ + public class StackDepth : List { + public List isBoxeds = new List (); + + /** + * @brief Clear both stacks. + */ + public new void Clear () + { + base.Clear (); + isBoxeds.Clear (); + } + + /** + * @brief Pop call parameters and validate the types. + */ + public void Pop (ParameterInfo[] pis) + { + int n = pis.Length; + int c = this.Count; + if (n > c) throw new Exception ("stack going negative"); + for (int i = n; -- i >= 0;) { + -- c; + ExpectedVsOnStack (pis[i].ParameterType, this[c], isBoxeds[c]); + } + Pop (n); + } + + /** + * @brief Pop values and validate the types. + */ + public void Pop (Type[] ts) + { + int n = ts.Length; + int c = this.Count; + if (n > c) throw new Exception ("stack going negative"); + for (int i = ts.Length; -- i >= 0;) { + -- c; + ExpectedVsOnStack (ts[i], this[c], isBoxeds[c]); + } + Pop (n); + } + + /** + * @brief Pop a single value and validate the type. + */ + public void Pop (Type t) + { + int c = this.Count; + if (c < 1) throw new Exception ("stack going negative"); + ExpectedVsOnStack (t, this[c-1], isBoxeds[c-1]); + Pop (1); + } + + /** + * @brief Pop a single value and validate that it is a numeric type. + */ + public Type PopNumVal () + { + int c = this.Count; + if (c < 1) throw new Exception ("stack going negative"); + Type st = this[--c]; + if (st == null) { + throw new Exception ("stack has null, expecting a numeric"); + } + if (isBoxeds[c]) { + throw new Exception ("stack is boxed " + st.Name + ", expecting a numeric"); + } + if ((st != typeof (bool)) && (st != typeof (char)) && (st != typeof (int)) && + (st != typeof (long)) && (st != typeof (float)) && (st != typeof (double))) { + throw new Exception ("stack has " + st.Name + ", expecting a numeric"); + } + return Pop (1); + } + + /** + * @brief Pop a single value and validate that it is a reference type + */ + public Type PopRef () + { + int c = this.Count; + if (c < 1) throw new Exception ("stack going negative"); + Type st = this[--c]; + if ((st != null) && !isBoxeds[c] && st.IsValueType) { + throw new Exception ("stack has " + st.Name + ", expecting a ref type"); + } + return Pop (1); + } + + /** + * @brief Pop a single value and validate that it is a value type + */ + public Type PopValue () + { + int c = this.Count; + if (c < 1) throw new Exception ("stack going negative"); + Type st = this[--c]; + if (st == null) { + throw new Exception ("stack has null, expecting a value type"); + } + if (!st.IsValueType) { + throw new Exception ("stack has " + st.Name + ", expecting a value type"); + } + if (isBoxeds[c]) { + throw new Exception ("stack has boxed " + st.Name + ", expecting an unboxed value type"); + } + return Pop (1); + } + + // ex = what is expected to be on stack + // st = what is actually on stack (null for ldnull) + // stBoxed = stack value is boxed + public static void ExpectedVsOnStack (Type ex, Type st, bool stBoxed) + { + // ldnull pushed on stack can go into any pointer type + if (st == null) { + if (ex.IsByRef || ex.IsPointer || ex.IsClass || ex.IsInterface) return; + throw new Exception ("stack has null, expect " + ex.Name); + } + + // simple case of expecting an object + // ...so the stack can have object,string, etc + // but we cant allow int = boxed int here + if (ex.IsAssignableFrom (st) && !stBoxed) return; + + // case of expecting an enum on the stack + // but all the CIL code knows about are ints etc + // so convert the Enum type to integer or whatever + // and that should be assignable from what's on stack + if (ex.IsEnum && typeof (int).IsAssignableFrom (st)) return; + + // bool, char, int are interchangeable on the stack + if ((ex == typeof (bool) || ex == typeof (char) || ex == typeof (int)) && + (st == typeof (bool) || st == typeof (char) || st == typeof (int))) return; + + // float and double are interchangeable on the stack + if ((ex == typeof (float) || ex == typeof (double)) && + (st == typeof (float) || st == typeof (double))) return; + + // object can accept any boxed type + if ((ex == typeof (object)) && stBoxed) return; + + // otherwise, it is disallowed + throw new Exception ("stack has " + StackTypeString (st, stBoxed) + ", expect " + ex.Name); + } + + /** + * @brief Pop values without any validation. + */ + public Type Pop (int n) + { + if (this.Count != isBoxeds.Count) throw new Exception ("isBoxeds count bad"); + Type lastPopped = null; + int c = this.Count; + if (n > c) throw new Exception ("stack going negative"); + if (n > 0) { + lastPopped = this[c-n]; + this.RemoveRange (c - n, n); + isBoxeds.RemoveRange (c - n, n); + } + if (this.Count != isBoxeds.Count) throw new Exception ("isBoxeds count bad"); + return lastPopped; + } + + /** + * @brief Peek at the n'th stack value. + * n = 0 : top of stack + * 1 : next to top + * ... + */ + public Type Peek (int n) + { + int c = this.Count; + if (n > c - 1) throw new Exception ("stack going negative"); + if (this.Count != isBoxeds.Count) throw new Exception ("isBoxeds count bad"); + return this[c-n-1]; + } + public bool PeekBoxed (int n) + { + int c = isBoxeds.Count; + if (n > c - 1) throw new Exception ("stack going negative"); + if (this.Count != isBoxeds.Count) throw new Exception ("isBoxeds count bad"); + return isBoxeds[c-n-1]; + } + + /** + * @brief Push a single value of the given type. + */ + public void Push (Type t) + { + Push (t, false); + } + public void Push (Type t, bool isBoxed) + { + if (this.Count != isBoxeds.Count) throw new Exception ("isBoxeds count bad"); + this.Add (t); + isBoxeds.Add (isBoxed); + } + + /** + * @brief See if the types at a given label exactly match those on the stack. + * We should have the stack types be the same no matter how we branched + * or fell through to a particular label. + */ + public void Matches (ScriptMyLabel label) + { + Type[] ts = label.stackDepth; + bool[] tsBoxeds = label.stackBoxeds; + int i; + + if (this.Count != isBoxeds.Count) throw new Exception ("isBoxeds count bad"); + + if (ts == null) { + label.stackDepth = this.ToArray (); + label.stackBoxeds = isBoxeds.ToArray (); + } else if (ts.Length != this.Count) { + throw new Exception ("stack depth mismatch"); + } else { + for (i = this.Count; -- i >= 0;) { + if (tsBoxeds[i] != this.isBoxeds[i]) goto mismatch; + if (ts[i] == this[i]) continue; + if ((ts[i] == typeof (bool) || ts[i] == typeof (char) || ts[i] == typeof (int)) && + (this[i] == typeof (bool) || this[i] == typeof (char) || this[i] == typeof (int))) continue; + if ((ts[i] == typeof (double) || ts[i] == typeof (float)) && + (this[i] == typeof (double) || this[i] == typeof (float))) continue; + goto mismatch; + } + } + return; + mismatch: + throw new Exception ("stack type mismatch: " + StackTypeString (ts[i], tsBoxeds[i]) + " vs " + StackTypeString (this[i], this.isBoxeds[i])); + } + + private static string StackTypeString (Type ts, bool isBoxed) + { + if (!isBoxed) return ts.Name; + return "[" + ts.Name + "]"; + } + } + + /** + * @brief One of these per opcode and label in the function plus other misc markers. + * They form the CIL instruction stream of the function. + */ + public abstract class GraphNode { + private static readonly bool DEBUG = false; + + public const int OPINDENT = 4; + public const int OPDEBLEN = 12; + + public ScriptCollector coll; + public GraphNodeBeginExceptionBlock tryBlock; // start of enclosing try block + // valid in the try section + // null in the catch/finally sections + // null outside of try block + // for the try node itself, links to outer try block + public GraphNodeBeginExceptionBlock excBlock; // start of enclosing try block + // valid in the try/catch/finally sections + // null outside of try/catch/finally block + // for the try node itself, links to outer try block + + /* + * List of nodes in order as originally given. + */ + public GraphNode nextLin, prevLin; + public int linSeqNo; + + /** + * @brief Save pointer to collector. + */ + public GraphNode (ScriptCollector coll) + { + this.coll = coll; + } + + /** + * @brief Chain graph node to end of linear list. + */ + public virtual void ChainLin () + { + coll.lastLin.nextLin = this; + this.prevLin = coll.lastLin; + coll.lastLin = this; + this.tryBlock = coll.curTryBlock; + this.excBlock = coll.curExcBlock; + + if (DEBUG) { + StringBuilder sb = new StringBuilder ("ChainLin*:"); + sb.Append (coll.stackDepth.Count.ToString("D2")); + sb.Append (' '); + this.DebString (sb); + Console.WriteLine (sb.ToString ()); + } + } + + /** + * @brief Append full info to debugging string for printing out the instruction. + */ + public void DebStringExt (StringBuilder sb) + { + int x = sb.Length; + sb.Append (this.linSeqNo.ToString ().PadLeft (5)); + sb.Append (": "); + this.DebString (sb); + + if (this.ReadsLocal () != null) ScriptCollector.PadToLength (sb, x + 60, " [read]"); + if (this.WritesLocal () != null) ScriptCollector.PadToLength (sb, x + 68, " [write]"); + ScriptCollector.PadToLength (sb, x + 72, " ->"); + bool first = true; + foreach (GraphNode nn in this.NextNodes) { + if (first) { + sb.Append (nn.linSeqNo.ToString ().PadLeft (5)); + first = false; + } else { + sb.Append (','); + sb.Append (nn.linSeqNo); + } + } + } + + /** + * @brief See if it's possible for it to fall through to the next inline (nextLin) instruction. + */ + public virtual bool CanFallThrough () + { + return true; + } + + /** + * @brief Append to debugging string for printing out the instruction. + */ + public abstract void DebString (StringBuilder sb); + public override string ToString () + { + StringBuilder sb = new StringBuilder (); + this.DebString (sb); + return sb.ToString (); + } + + /** + * @brief See if this instruction reads a local variable. + */ + public virtual ScriptMyLocal ReadsLocal () { return null; } + + /** + * @brief See if this instruction writes a local variable. + */ + public virtual ScriptMyLocal WritesLocal () { return null; } + + /** + * @brief Write this instruction out to the wrapped object file. + */ + public abstract void WriteOutOne (ScriptMyILGen ilGen); + + /** + * @brief Iterate through all the possible next nodes, including the next inline node, if any. + * The next inline code is excluded if the instruction never falls through, eg, return, unconditional branch. + * It includes a possible conditional branch to the beginning of the corresponding catch/finally of every + * instruction in a try section. + */ + private System.Collections.Generic.IEnumerable nextNodes, nextNodesCatchFinally; + public System.Collections.Generic.IEnumerable NextNodes + { get { + if (nextNodes == null) { + nextNodes = GetNNEnumerable (); + nextNodesCatchFinally = new NNEnumerableCatchFinally (this); + } + return nextNodesCatchFinally; + } } + + /** + * @brief This acts as a wrapper around all the other NNEnumerable's below. + * It assumes every instruction in a try { } can throw an exception so it + * says that every instruction in a try { } can conditionally branch to + * the beginning of the corresponding catch { } or finally { }. + */ + private class NNEnumerableCatchFinally : System.Collections.Generic.IEnumerable { + private GraphNode gn; + public NNEnumerableCatchFinally (GraphNode gn) + { + this.gn = gn; + } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator () + { + return new NNEnumeratorCatchFinally (gn); + } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return new NNEnumeratorCatchFinally (gn); + } + } + private class NNEnumeratorCatchFinally : NNEnumeratorBase { + private GraphNode gn; + private int index = 0; + private System.Collections.Generic.IEnumerator realEnumerator; + public NNEnumeratorCatchFinally (GraphNode gn) + { + this.gn = gn; + this.realEnumerator = gn.nextNodes.GetEnumerator (); + } + public override bool MoveNext () + { + /* + * First off, return any targets the instruction can come up with. + */ + if (realEnumerator.MoveNext ()) { + nn = realEnumerator.Current; + return true; + } + + /* + * Then if this instruction is in a try section, say this instruction + * can potentially branch to the beginning of the corresponding + * catch/finally. + */ + if ((index == 0) && (gn.tryBlock != null)) { + index ++; + nn = gn.tryBlock.catchFinallyBlock; + return true; + } + + /* + * That's all we can do. + */ + nn = null; + return false; + } + public override void Reset () + { + realEnumerator.Reset (); + index = 0; + nn = null; + } + } + + /** + * @brief This default iterator always returns the next inline node as the one-and-only next node. + * Other instructions need to override it if they can possibly do other than that. + */ + + /** + * @brief GetNNEnumerable() gets the nextnode enumerable part of a GraphNode, + * which in turn gives the list of nodes that can possibly be next in + * a flow-control sense. It simply instantiates the NNEnumerator sub- + * class which does the actual enumeration. + */ + protected virtual System.Collections.Generic.IEnumerable GetNNEnumerable () + { + return new NNEnumerable (this, typeof (NNEnumerator)); + } + + private class NNEnumerator : NNEnumeratorBase { + private GraphNode gn; + private int index; + public NNEnumerator (GraphNode gn) + { + this.gn = gn; + } + public override bool MoveNext () + { + switch (index) { + case 0: { + index ++; + nn = gn.nextLin; + return nn != null; + } + case 1: { + nn = null; + return false; + } + } + throw new Exception (); + } + public override void Reset () + { + index = 0; + nn = null; + } + } + } + + /** + * @brief Things that derive from this are the beginning of a block. + * A block of code is that which begins with a label or is the beginning of all code + * and it contains no labels, ie, it can't be jumped into other than at its beginning. + */ + public abstract class GraphNodeBlock : GraphNode { + public List localsWrittenBeforeRead = new List (); + public List localsReadBeforeWritten = new List (); + public int hasBeenResolved; + public GraphNodeBlock (ScriptCollector coll) : base (coll) { } + } + + /** + * @brief This placeholder is at the beginning of the code so the first few instructions + * belong to some block. + */ + public class GraphNodeBegin : GraphNodeBlock { + public GraphNodeBegin (ScriptCollector coll) : base (coll) { } + public override void DebString (StringBuilder sb) { sb.Append ("begin"); } + public override void WriteOutOne (ScriptMyILGen ilGen) { } + } + + /** + * @brief Beginning of try block. + */ + public class GraphNodeBeginExceptionBlock : GraphNodeBlock { + public GraphNodeBeginExceptionBlock outerTryBlock; // next outer try opcode or null + public GraphNodeCatchFinallyBlock catchFinallyBlock; // start of associated catch or finally + public GraphNodeEndExceptionBlock endExcBlock; // end of associated catch or finally + public int excBlkSeqNo; // debugging + + public GraphNodeBeginExceptionBlock (ScriptCollector coll) : base (coll) + { } + + public override void ChainLin () + { + base.ChainLin (); + + // we should always start try blocks with nothing on stack + // ...as CLI wipes stack for various conditions + if (coll.stackDepth.Count != 0) { + throw new Exception ("stack depth " + coll.stackDepth.Count); + } + } + + public override void DebString (StringBuilder sb) + { + sb.Append (" beginexceptionblock_"); + sb.Append (excBlkSeqNo); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.BeginExceptionBlock (); + } + } + + /** + * @brief Beginning of catch or finally block. + */ + public abstract class GraphNodeCatchFinallyBlock : GraphNodeBlock { + public GraphNodeCatchFinallyBlock (ScriptCollector coll) : base (coll) + { } + + public override void ChainLin () + { + base.ChainLin (); + + // we should always start catch/finally blocks with nothing on stack + // ...as CLI wipes stack for various conditions + if (coll.stackDepth.Count != 0) { + throw new Exception ("stack depth " + coll.stackDepth.Count); + } + } + } + + /** + * @brief Beginning of catch block. + */ + public class GraphNodeBeginCatchBlock : GraphNodeCatchFinallyBlock { + public Type excType; + + public GraphNodeBeginCatchBlock (ScriptCollector coll, Type excType) : base (coll) + { + this.excType = excType; + } + + public override void ChainLin () + { + base.ChainLin (); + + // catch block always enters with one value on stack + if (coll.stackDepth.Count != 0) { + throw new Exception ("stack depth " + coll.stackDepth.Count); + } + coll.stackDepth.Push (excType); + } + + public override void DebString (StringBuilder sb) + { + sb.Append (" begincatchblock_"); + sb.Append (excBlock.excBlkSeqNo); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.BeginCatchBlock (excType); + } + + /** + * @brief The beginning of every catch { } conditinally branches to the beginning + * of all outer catch { }s up to and including the next outer finally { }. + */ + protected override System.Collections.Generic.IEnumerable GetNNEnumerable () + { + return new NNEnumerable (this, typeof (NNEnumerator)); + } + + private class NNEnumerator : NNEnumeratorBase { + private GraphNodeBeginCatchBlock gn; + private int index; + public NNEnumerator (GraphNodeBeginCatchBlock gn) + { + this.gn = gn; + } + public override bool MoveNext () + { + while (true) { + switch (index) { + case 0: { + // start with the fallthru + nn = gn.nextLin; + index ++; + return true; + } + + case 1: { + // get the first outer catch { } or finally { } + // pretend we last returned beginning of this catch { } + // then loop back to get next outer catch { } or finally { } + nn = gn; + break; + } + + case 2: { + // nn points to a catch { } previously returned + // get the corresponding try { } + GraphNodeBeginExceptionBlock nntry = nn.excBlock; + + // step out to next outer try { } + nntry = nntry.excBlock; + if (nntry == null) break; + + // return corresponding catch { } or finally { } + nn = nntry.catchFinallyBlock; + + // if it's a finally { } we don't do anything after that + if (nn is GraphNodeBeginFinallyBlock) index ++; + return true; + } + + case 3: { + // we've returned the fallthru, catches and one finally + // so there's nothing more to say + nn = null; + return false; + } + + default: throw new Exception (); + } + index ++; + } + } + public override void Reset () + { + index = 0; + nn = null; + } + } + } + + /** + * @brief Beginning of finally block. + */ + public class GraphNodeBeginFinallyBlock : GraphNodeCatchFinallyBlock { + + // leaveTargets has a list of all the targets of any contained + // leave instructions, ie, where an endfinally can possibly jump. + // But only those targets within the next outer finally { }, we + // don't contain any targets outside of that, those targets are + // stored in the actual finally that will jump to the target. + // The endfinally enumerator assumes that it is always possible + // for it to jump to the next outer finally (as would happen for + // an uncaught exception), so no need to do anything special. + public List leaveTargets = new List (); + + public GraphNodeBeginFinallyBlock (ScriptCollector coll) : base (coll) + { } + + public override void DebString (StringBuilder sb) + { + sb.Append (" beginfinallyblock_"); + sb.Append (excBlock.excBlkSeqNo); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.BeginFinallyBlock (); + } + } + + /** + * @brief End of try/catch/finally block. + */ + public class GraphNodeEndExceptionBlock : GraphNode { + public GraphNodeEndExceptionBlock (ScriptCollector coll) : base (coll) + { } + + public override void ChainLin () + { + base.ChainLin (); + + // we should always end exception blocks with nothing on stack + // ...as CLI wipes stack for various conditions + if (coll.stackDepth.Count != 0) { + throw new Exception ("stack depth " + coll.stackDepth.Count); + } + } + + public override void DebString (StringBuilder sb) + { + sb.Append (" endexceptionblock_"); + sb.Append (excBlock.excBlkSeqNo); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.EndExceptionBlock (); + } + } + + /** + * @brief Actual instruction emits... + */ + public abstract class GraphNodeEmit : GraphNode { + public OpCode opcode; + public Token errorAt; + + public GraphNodeEmit (ScriptCollector coll, Token errorAt, OpCode opcode) : base (coll) + { + this.opcode = opcode; + this.errorAt = errorAt; + } + + public override void ChainLin () + { + base.ChainLin (); + + // compute resultant stack depth + int stack = coll.stackDepth.Count; + + if ((stack != 0) && ((opcode == OpCodes.Endfinally) || (opcode == OpCodes.Leave) || (opcode == OpCodes.Rethrow))) { + throw new Exception (opcode + " stack depth " + stack); + } + if ((stack != 1) && (opcode == OpCodes.Throw)) { + throw new Exception (opcode + " stack depth " + stack); + } + } + + /** + * @brief See if it's possible for it to fall through to the next inline (nextLin) instruction. + */ + public override bool CanFallThrough () + { + switch (opcode.FlowControl) { + case FlowControl.Branch: return false; // unconditional branch + case FlowControl.Break: return true; // break + case FlowControl.Call: return true; // call + case FlowControl.Cond_Branch: return true; // conditional branch + case FlowControl.Next: return true; // falls through to next instruction + case FlowControl.Return: return false; // return + case FlowControl.Throw: return false; // throw + default: { + string op = opcode.ToString (); + if (op == "volatile.") return true; + throw new Exception ("unknown flow control " + opcode.FlowControl + " for " + op); + } + } + } + + // if followed by OpCodes.Pop, it can be discarded + public bool isPoppable + { get { + return + ((opcode.StackBehaviourPop == StackBehaviour.Pop0) && // ldarg,ldloc,ldsfld + (opcode.StackBehaviourPush == StackBehaviour.Push1)) || + ((opcode.StackBehaviourPop == StackBehaviour.Pop0) && // ldarga,ldloca,ldc,ldsflda,... + (opcode.StackBehaviourPush == StackBehaviour.Pushi)) || + (opcode == OpCodes.Ldnull) || + (opcode == OpCodes.Ldc_R4) || + (opcode == OpCodes.Ldc_R8) || + (opcode == OpCodes.Ldstr) || + (opcode == OpCodes.Ldc_I8) || + (opcode == OpCodes.Dup); + } } + + public override void DebString (StringBuilder sb) + { + sb.Append ("".PadRight (OPINDENT)); + sb.Append (opcode.ToString ().PadRight (OPDEBLEN)); + } + + /** + * @brief If instruction is terminating, we say there is nothing following (eg, return). + * Otherwise, say the one-and-only next instruction is the next instruction inline. + */ + protected override System.Collections.Generic.IEnumerable GetNNEnumerable () + { + return new NNEnumerable (this, typeof (NNEnumerator)); + } + + private class NNEnumerator : NNEnumeratorBase { + private GraphNodeEmit gn; + private int index; + public NNEnumerator (GraphNodeEmit gn) + { + this.gn = gn; + } + public override bool MoveNext () + { + switch (index) { + case 0: { + if (gn.CanFallThrough ()) { + index ++; + nn = gn.nextLin; + return nn != null; + } + return false; + } + case 1: { + nn = null; + return false; + } + } + throw new Exception (); + } + public override void Reset () + { + index = 0; + nn = null; + } + } + } + + public class GraphNodeEmitNull : GraphNodeEmit { + public GraphNodeEmitNull (ScriptCollector coll, Token errorAt, OpCode opcode) : base (coll, errorAt, opcode) + { } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "nop": break; + case "break": break; + case "volatile.": break; + case "ldarg.0": coll.stackDepth.Push (coll.wrapped.argTypes[0]); break; + case "ldarg.1": coll.stackDepth.Push (coll.wrapped.argTypes[1]); break; + case "ldarg.2": coll.stackDepth.Push (coll.wrapped.argTypes[2]); break; + case "ldarg.3": coll.stackDepth.Push (coll.wrapped.argTypes[3]); break; + case "ldnull": coll.stackDepth.Push (null); break; + case "ldc.i4.m1": + case "ldc.i4.0": + case "ldc.i4.1": + case "ldc.i4.2": + case "ldc.i4.3": + case "ldc.i4.4": + case "ldc.i4.5": + case "ldc.i4.6": + case "ldc.i4.7": + case "ldc.i4.8": { + coll.stackDepth.Push (typeof (int)); + break; + } + case "dup": { + Type t = coll.stackDepth.Peek (0); + bool b = coll.stackDepth.PeekBoxed (0); + coll.stackDepth.Push (t, b); + break; + } + case "pop": { + coll.stackDepth.Pop (1); + break; + } + case "ret": { + int sd = (coll.wrapped.retType != typeof (void)) ? 1 : 0; + if (coll.stackDepth.Count != sd) throw new Exception ("bad stack depth"); + if (sd > 0) { + coll.stackDepth.Pop (coll.wrapped.retType); + } + break; + } + case "add": + case "sub": + case "mul": + case "div": + case "div.un": + case "rem": + case "rem.un": + case "and": + case "or": + case "xor": + case "shl": + case "shr": + case "shr.un": + case "add.ovf": + case "add.ovf.un": + case "mul.ovf": + case "mul.ovf.un": + case "sub.ovf": + case "sub.ovf.un": { + coll.stackDepth.PopNumVal (); + Type t = coll.stackDepth.PopNumVal (); + coll.stackDepth.Push (t); + break; + } + case "neg": + case "not": { + Type t = coll.stackDepth.PopNumVal (); + coll.stackDepth.Push (t); + break; + } + case "conv.i1": + case "conv.i2": + case "conv.i4": + case "conv.i8": + case "conv.r4": + case "conv.r8": + case "conv.u4": + case "conv.u8": + case "conv.r.un": + case "conv.ovf.i1.un": + case "conv.ovf.i2.un": + case "conv.ovf.i4.un": + case "conv.ovf.i8.un": + case "conv.ovf.u1.un": + case "conv.ovf.u2.un": + case "conv.ovf.u4.un": + case "conv.ovf.u8.un": + case "conv.ovf.i.un": + case "conv.ovf.u.un": + case "conv.ovf.i1": + case "conv.ovf.u1": + case "conv.ovf.i2": + case "conv.ovf.u2": + case "conv.ovf.i4": + case "conv.ovf.u4": + case "conv.ovf.i8": + case "conv.ovf.u8": + case "conv.u2": + case "conv.u1": + case "conv.i": + case "conv.ovf.i": + case "conv.ovf.u": + case "conv.u": { + coll.stackDepth.PopNumVal (); + coll.stackDepth.Push (ConvToType (opcode)); + break; + } + case "throw": { + if (coll.stackDepth.Count != 1) throw new Exception ("bad stack depth " + coll.stackDepth.Count); + coll.stackDepth.PopRef (); + break; + } + case "ldlen": { + coll.stackDepth.Pop (typeof (string)); + coll.stackDepth.Push (typeof (int)); + break; + } + case "ldelem.i1": + case "ldelem.u1": + case "ldelem.i2": + case "ldelem.u2": + case "ldelem.i4": + case "ldelem.u4": + case "ldelem.i8": + case "ldelem.i": + case "ldelem.r4": + case "ldelem.r8": + case "ldelem.ref": { + Type t = coll.stackDepth.Peek (1).GetElementType (); + coll.stackDepth.Pop (typeof (int)); + coll.stackDepth.Pop (t.MakeArrayType ()); + coll.stackDepth.Push (t); + break; + } + case "stelem.i": + case "stelem.i1": + case "stelem.i2": + case "stelem.i4": + case "stelem.i8": + case "stelem.r4": + case "stelem.r8": + case "stelem.ref": { + Type t = coll.stackDepth.Peek (2).GetElementType (); + coll.stackDepth.Pop (t); + coll.stackDepth.Pop (typeof (int)); + coll.stackDepth.Pop (t.MakeArrayType ()); + break; + } + case "endfinally": + case "rethrow": { + if (coll.stackDepth.Count != 0) throw new Exception ("bad stack depth " + coll.stackDepth.Count); + break; + } + case "ceq": { + Type t = coll.stackDepth.Pop (1); + if (t == null) { + coll.stackDepth.PopRef (); + } else { + coll.stackDepth.Pop (t); + } + coll.stackDepth.Push (typeof (int)); + break; + } + case "cgt": + case "cgt.un": + case "clt": + case "clt.un": { + coll.stackDepth.PopNumVal (); + coll.stackDepth.PopNumVal (); + coll.stackDepth.Push (typeof (int)); + break; + } + case "ldind.i4": { + coll.stackDepth.Pop (typeof (int).MakeByRefType ()); + coll.stackDepth.Push (typeof (int)); + break; + } + case "stind.i4": { + coll.stackDepth.Pop (typeof (int)); + coll.stackDepth.Pop (typeof (int).MakeByRefType ()); + break; + } + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + private static Type ConvToType (OpCode opcode) + { + string s = opcode.ToString (); + s = s.Substring (5); // strip off "conv." + if (s.StartsWith ("ovf.")) s = s.Substring (4); + if (s.EndsWith (".un")) s = s.Substring (0, s.Length - 3); + + switch (s) { + case "i": return typeof (IntPtr); + case "i1": return typeof (sbyte); + case "i2": return typeof (short); + case "i4": return typeof (int); + case "i8": return typeof (long); + case "r": + case "r4": return typeof (float); + case "r8": return typeof (double); + case "u1": return typeof (byte); + case "u2": return typeof (ushort); + case "u4": return typeof (uint); + case "u8": return typeof (ulong); + case "u": return typeof (UIntPtr); + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode); + } + } + + public class GraphNodeEmitNullEndfinally : GraphNodeEmitNull { + public GraphNodeEmitNullEndfinally (ScriptCollector coll, Token errorAt) : base (coll, errorAt, OpCodes.Endfinally) + { } + + /** + * @brief Endfinally can branch to: + * 1) the corresponding EndExceptionBlock + * 2) any of the corresponding BeginFinallyBlock's leaveTargets + * 3) the next outer BeginFinallyBlock + */ + protected override System.Collections.Generic.IEnumerable GetNNEnumerable () + { + return new NNEnumerable (this, typeof (NNEnumerator)); + } + + private class NNEnumerator : NNEnumeratorBase { + private GraphNodeEmitNullEndfinally gn; + private IEnumerator leaveTargetEnumerator; + private int index; + public NNEnumerator (GraphNodeEmitNullEndfinally gn) + { + this.gn = gn; + + // endfinally instruction must be within some try/catch/finally mess + GraphNodeBeginExceptionBlock thistry = gn.excBlock; + + // endfinally instruction must be within some finally { } mess + GraphNodeBeginFinallyBlock thisfin = (GraphNodeBeginFinallyBlock)thistry.catchFinallyBlock; + + // get the list of the finally { } leave instruction targets + this.leaveTargetEnumerator = thisfin.leaveTargets.GetEnumerator (); + } + public override bool MoveNext () + { + while (true) { + switch (index) { + + // to start, return end of our finally { } + case 0: { + GraphNodeBeginExceptionBlock thistry = gn.excBlock; + nn = thistry.endExcBlock; + if (nn == null) throw new NullReferenceException ("thistry.endExcBlock"); + index ++; + return true; + } + + // return next one of our finally { }'s leave targets + // ie, where any leave instructions in the try { } want + // the finally { } to go to when it finishes + case 1: { + if (this.leaveTargetEnumerator.MoveNext ()) { + nn = this.leaveTargetEnumerator.Current; + if (nn == null) throw new NullReferenceException ("this.leaveTargetEnumerator.Current"); + return true; + } + break; + } + + // return beginning of next outer finally { } + case 2: { + GraphNodeBeginExceptionBlock nntry = gn.excBlock; + while ((nntry = nntry.excBlock) != null) { + if (nntry.catchFinallyBlock is GraphNodeBeginFinallyBlock) { + nn = nntry.catchFinallyBlock; + if (nn == null) throw new NullReferenceException ("nntry.catchFinallyBlock"); + index ++; + return true; + } + } + break; + } + + // got nothing more + case 3: { + return false; + } + + default: throw new Exception (); + } + index ++; + } + } + public override void Reset () + { + leaveTargetEnumerator.Reset (); + index = 0; + nn = null; + } + } + } + + public class GraphNodeEmitField : GraphNodeEmit { + public FieldInfo field; + + public GraphNodeEmitField (ScriptCollector coll, Token errorAt, OpCode opcode, FieldInfo field) : base (coll, errorAt, opcode) + { + this.field = field; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "ldfld": PopPointer (); coll.stackDepth.Push (field.FieldType); break; + case "ldflda": PopPointer (); coll.stackDepth.Push (field.FieldType.MakeByRefType ()); break; + case "stfld": coll.stackDepth.Pop (field.FieldType); PopPointer (); break; + case "ldsfld": coll.stackDepth.Push (field.FieldType); break; + case "ldsflda": coll.stackDepth.Push (field.FieldType.MakeByRefType ()); break; + case "stsfld": coll.stackDepth.Pop (field.FieldType); break; + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + private void PopPointer () + { + Type t = field.DeclaringType; // get class/field type + if (t.IsValueType) { + Type brt = t.MakeByRefType (); // if value type, eg Vector, it can be pushed by reference or by value + int c = coll.stackDepth.Count; + if ((c > 0) && (coll.stackDepth[c-1] == brt)) t = brt; + } + coll.stackDepth.Pop (t); // type of what should be on the stack pointing to object or struct + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (field.Name); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, field); + } + } + + public class GraphNodeEmitLocal : GraphNodeEmit { + public ScriptMyLocal myLocal; + + public GraphNodeEmitLocal (ScriptCollector coll, Token errorAt, OpCode opcode, ScriptMyLocal myLocal) : base (coll, errorAt, opcode) + { + this.myLocal = myLocal; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "ldloc": coll.stackDepth.Push (myLocal.type); break; + case "ldloca": coll.stackDepth.Push (myLocal.type.MakeByRefType ()); break; + case "stloc": coll.stackDepth.Pop (myLocal.type); break; + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (myLocal.name); + } + + public override ScriptMyLocal ReadsLocal () + { + if (opcode == OpCodes.Ldloc) return myLocal; + if (opcode == OpCodes.Ldloca) return myLocal; + if (opcode == OpCodes.Stloc) return null; + throw new Exception ("unknown opcode " + opcode); + } + public override ScriptMyLocal WritesLocal () + { + if (opcode == OpCodes.Ldloc) return null; + if (opcode == OpCodes.Ldloca) return myLocal; + if (opcode == OpCodes.Stloc) return myLocal; + throw new Exception ("unknown opcode " + opcode); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, myLocal); + } + } + + public class GraphNodeEmitType : GraphNodeEmit { + public Type type; + + public GraphNodeEmitType (ScriptCollector coll, Token errorAt, OpCode opcode, Type type) : base (coll, errorAt, opcode) + { + this.type = type; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "castclass": + case "isinst": { + coll.stackDepth.PopRef (); + coll.stackDepth.Push (type, type.IsValueType); + break; + } + case "box": { + if (!type.IsValueType) throw new Exception ("can't box a non-value type"); + coll.stackDepth.Pop (type); + coll.stackDepth.Push (type, true); + break; + } + case "unbox": + case "unbox.any": { + if (!type.IsValueType) throw new Exception ("can't unbox to a non-value type"); + coll.stackDepth.PopRef (); + coll.stackDepth.Push (type); + break; + } + case "newarr": { + coll.stackDepth.Pop (typeof (int)); + coll.stackDepth.Push (type.MakeArrayType ()); + break; + } + case "sizeof": { + coll.stackDepth.Pop (1); + coll.stackDepth.Push (typeof (int)); + break; + } + case "ldelem": { + coll.stackDepth.Pop (typeof (int)); + coll.stackDepth.Pop (type.MakeArrayType ()); + coll.stackDepth.Push (type); + break; + } + case "ldelema": { + coll.stackDepth.Pop (typeof (int)); + coll.stackDepth.Pop (type.MakeArrayType ()); + coll.stackDepth.Push (type.MakeByRefType ()); + break; + } + case "stelem": { + coll.stackDepth.Pop (type); + coll.stackDepth.Pop (typeof (int)); + coll.stackDepth.Pop (type.MakeArrayType ()); + break; + } + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (type.Name); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, type); + } + } + + public class GraphNodeEmitLabel : GraphNodeEmit { + public ScriptMyLabel myLabel; + + public GraphNodeEmitLabel (ScriptCollector coll, Token errorAt, OpCode opcode, ScriptMyLabel myLabel) : base (coll, errorAt, opcode) + { + this.myLabel = myLabel; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "brfalse.s": + case "brtrue.s": + case "brfalse": + case "brtrue": { + coll.stackDepth.Pop (1); + break; + } + case "beq.s": + case "bge.s": + case "bgt.s": + case "ble.s": + case "blt.s": + case "bne.un.s": + case "bge.un.s": + case "bgt.un.s": + case "ble.un.s": + case "blt.un.s": + case "beq": + case "bge": + case "bgt": + case "ble": + case "blt": + case "bne.un": + case "bge.un": + case "bgt.un": + case "ble.un": + case "blt.un": { + coll.stackDepth.PopNumVal (); + coll.stackDepth.PopNumVal (); + break; + } + case "br": + case "br.s": break; + case "leave": { + if (coll.stackDepth.Count != 0) throw new Exception ("bad stack depth " + coll.stackDepth.Count); + break; + } + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + + // if a target doesn't have a depth yet, set its depth to the depth after instruction executes + // otherwise, make sure it matches all other branches to that target and what fell through to it + coll.stackDepth.Matches (myLabel); + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (myLabel.name); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, myLabel); + } + + /** + * @brief Conditional branches return the next inline followed by the branch target + * Unconditional branches return only the branch target + * But if the target is outside our scope (eg __retlbl), omit it from the list + */ + protected override System.Collections.Generic.IEnumerable GetNNEnumerable () + { + return new NNEnumerable (this, typeof (NNEnumerator)); + } + + private class NNEnumerator : NNEnumeratorBase { + private GraphNodeEmitLabel gn; + private int index; + public NNEnumerator (GraphNodeEmitLabel gn) + { + this.gn = gn; + } + public override bool MoveNext () + { + switch (gn.opcode.FlowControl) { + case FlowControl.Branch: { + // unconditional branch just goes to target and nothing else + switch (index) { + case 0: { + nn = gn.myLabel.whereAmI; + index ++; + return nn != null; + } + case 1: { + return false; + } + } + throw new Exception (); + } + case FlowControl.Cond_Branch: { + // conditional branch goes inline and to target + switch (index) { + case 0: { + nn = gn.nextLin; + index ++; + return true; + } + case 1: { + nn = gn.myLabel.whereAmI; + index ++; + return nn != null; + } + case 2: { + return false; + } + } + throw new Exception (); + } + default: throw new Exception ("unknown flow control " + gn.opcode.FlowControl.ToString () + + " of " + gn.opcode.ToString ()); + } + } + public override void Reset () + { + index = 0; + nn = null; + } + } + } + + public class GraphNodeEmitLabelLeave : GraphNodeEmitLabel { + public GraphNodeBlock unwindTo; // if unwinding, innermost finally block being unwound + // else, same as myTarget.whereAmI + // null if unwinding completely out of scope, eg, __retlbl + + public GraphNodeEmitLabelLeave (ScriptCollector coll, Token errorAt, ScriptMyLabel myLabel) : base (coll, errorAt, OpCodes.Leave, myLabel) + { } + + /** + * @brief Leave instructions have exactly one unconditional next node. + * Either the given target if within the same try block + * or the beginning of the intervening finally block. + */ + protected override System.Collections.Generic.IEnumerable GetNNEnumerable () + { + return new NNEnumerable (this, typeof (NNEnumerator)); + } + + private class NNEnumerator : NNEnumeratorBase { + private GraphNodeEmitLabelLeave gn; + private int index; + public NNEnumerator (GraphNodeEmitLabelLeave gn) + { + this.gn = gn; + } + public override bool MoveNext () + { + if (index == 0) { + nn = gn.unwindTo; + index ++; + return nn != null; + } + nn = null; + return false; + } + public override void Reset () + { + index = 0; + nn = null; + } + } + } + + public class GraphNodeEmitLabels : GraphNodeEmit { + public ScriptMyLabel[] myLabels; + + public GraphNodeEmitLabels (ScriptCollector coll, Token errorAt, OpCode opcode, ScriptMyLabel[] myLabels) : base (coll, errorAt, opcode) + { + this.myLabels = myLabels; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "switch": { + coll.stackDepth.Pop (typeof (int)); + break; + } + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + + // if a target doesn't have a depth yet, set its depth to the depth after instruction executes + // otherwise, make sure it matches all other branches to that target and what fell through to it + foreach (ScriptMyLabel myLabel in myLabels) { + coll.stackDepth.Matches (myLabel); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + bool first = true; + foreach (ScriptMyLabel lbl in myLabels) { + if (!first) sb.Append (','); + sb.Append (lbl.name); + first = false; + } + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, myLabels); + } + + /** + * @brief Return list of all labels followed by the next linear instruction + * But if the target is outside our scope (eg __retlbl), omit it from the list + */ + protected override System.Collections.Generic.IEnumerable GetNNEnumerable () + { + return new NNEnumerable (this, typeof (NNEnumerator)); + } + + private class NNEnumerator : NNEnumeratorBase { + private GraphNodeEmitLabels gn; + private int index; + public NNEnumerator (GraphNodeEmitLabels gn) + { + this.gn = gn; + } + public override bool MoveNext () + { + /* + * Return next from list of switch case labels. + */ + while (index < gn.myLabels.Length) { + nn = gn.myLabels[index++].whereAmI; + if (nn != null) return true; + } + + /* + * If all ran out, the switch instruction falls through. + */ + if (index == gn.myLabels.Length) { + index ++; + nn = gn.nextLin; + return true; + } + + /* + * Even ran out of that, say there's nothing more. + */ + nn = null; + return false; + } + public override void Reset () + { + index = 0; + nn = null; + } + } + } + + public class GraphNodeEmitIntMeth : GraphNodeEmit { + public ScriptObjWriter method; + + public GraphNodeEmitIntMeth (ScriptCollector coll, Token errorAt, OpCode opcode, ScriptObjWriter method) : base (coll, errorAt, opcode) + { + this.method = method; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "call": { + + // calls have Varpop so pop the number of arguments + // they are all static so there is no separate 'this' parameter + coll.stackDepth.Pop (this.method.argTypes); + + // calls are also Varpush so they push a return value iff non-void + if (this.method.retType != typeof (void)) coll.stackDepth.Push (this.method.retType); + break; + } + + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (method.methName); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, method); + } + } + + public class GraphNodeEmitExtMeth : GraphNodeEmit { + public MethodInfo method; + + public GraphNodeEmitExtMeth (ScriptCollector coll, Token errorAt, OpCode opcode, MethodInfo method) : base (coll, errorAt, opcode) + { + this.method = method; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "call": + case "callvirt": { + + // calls have Varpop so pop the number of arguments + coll.stackDepth.Pop (this.method.GetParameters ()); + if ((this.method.CallingConvention & CallingConventions.HasThis) != 0) { + coll.stackDepth.Pop (method.DeclaringType); + } + + // calls are also Varpush so they push a return value iff non-void + if (this.method.ReturnType != typeof (void)) coll.stackDepth.Push (this.method.ReturnType); + break; + } + + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (method.Name); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, method); + } + } + + public class GraphNodeEmitCtor : GraphNodeEmit { + public ConstructorInfo ctor; + + public GraphNodeEmitCtor (ScriptCollector coll, Token errorAt, OpCode opcode, ConstructorInfo ctor) : base (coll, errorAt, opcode) + { + this.ctor = ctor; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "newobj": { + coll.stackDepth.Pop (ctor.GetParameters ()); + coll.stackDepth.Push (ctor.DeclaringType); + break; + } + + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (ctor.ReflectedType.Name); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, ctor); + } + } + + public class GraphNodeEmitDouble : GraphNodeEmit { + public double value; + + public GraphNodeEmitDouble (ScriptCollector coll, Token errorAt, OpCode opcode, double value) : base (coll, errorAt, opcode) + { + this.value = value; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "ldc.r8": coll.stackDepth.Push (typeof (double)); break; + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (value); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, value); + } + } + + public class GraphNodeEmitFloat : GraphNodeEmit { + public float value; + + public GraphNodeEmitFloat (ScriptCollector coll, Token errorAt, OpCode opcode, float value) : base (coll, errorAt, opcode) + { + this.value = value; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "ldc.r4": coll.stackDepth.Push (typeof (float)); break; + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (value); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, value); + } + } + + public class GraphNodeEmitInt : GraphNodeEmit { + public int value; + + public GraphNodeEmitInt (ScriptCollector coll, Token errorAt, OpCode opcode, int value) : base (coll, errorAt, opcode) + { + this.value = value; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "ldarg": + case "ldarg.s": coll.stackDepth.Push (coll.wrapped.argTypes[value]); break; + case "ldarga": + case "ldarga.s": coll.stackDepth.Push (coll.wrapped.argTypes[value].MakeByRefType ()); break; + case "starg": + case "starg.s": coll.stackDepth.Pop (coll.wrapped.argTypes[value]); break; + case "ldc.i4": + case "ldc.i4.s": coll.stackDepth.Push (typeof (int)); break; + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append (value); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, value); + } + } + + public class GraphNodeEmitString : GraphNodeEmit { + public string value; + + public GraphNodeEmitString (ScriptCollector coll, Token errorAt, OpCode opcode, string value) : base (coll, errorAt, opcode) + { + this.value = value; + } + + public override void ChainLin () + { + base.ChainLin (); + + switch (opcode.ToString ()) { + case "ldstr": coll.stackDepth.Push (typeof (string)); break; + default: throw new Exception ("unknown opcode " + opcode.ToString ()); + } + } + + public override void DebString (StringBuilder sb) + { + base.DebString (sb); + sb.Append ("\""); + sb.Append (value); + sb.Append ("\""); + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.Emit (errorAt, opcode, value); + } + } + + public class GraphNodeMarkLabel : GraphNodeBlock { + public ScriptMyLabel myLabel; + + public GraphNodeMarkLabel (ScriptCollector coll, ScriptMyLabel myLabel) : base (coll) + { + this.myLabel = myLabel; + } + + public override void ChainLin () + { + base.ChainLin (); + + // if previous instruction can fall through to this label, + // if the label doesn't yet have a stack depth, mark it with current stack depth + // else, the label's stack depth from forward branches and current stack depth must match + // else, + // label must have had a forward branch to it so we can know stack depth + // set the current stack depth to the label's stack depth as of that forward branch + if (myLabel.whereAmI.prevLin.CanFallThrough ()) { + coll.stackDepth.Matches (myLabel); + } else { + if (myLabel.stackDepth == null) { + throw new Exception ("stack depth unknown at " + myLabel.name); + } + coll.stackDepth.Clear (); + int n = myLabel.stackDepth.Length; + for (int i = 0; i < n; i ++) { + coll.stackDepth.Push (myLabel.stackDepth[i], myLabel.stackBoxeds[i]); + } + } + } + + public override void DebString (StringBuilder sb) + { + sb.Append (myLabel.name); + sb.Append (':'); + if (myLabel.stackDepth != null) { + sb.Append (" ["); + sb.Append (myLabel.stackDepth.Length); + sb.Append (']'); + } + } + + public override void WriteOutOne (ScriptMyILGen ilGen) + { + ilGen.MarkLabel (myLabel); + } + } + + + /** + * @brief Generates enumerator that steps through list of nodes that can + * possibly be next in a flow-control sense. + */ + public class NNEnumerable : System.Collections.Generic.IEnumerable { + private object[] cps; + private ConstructorInfo ci; + + public NNEnumerable (GraphNode gn, Type nnEnumeratorType) + { + this.cps = new object[] { gn }; + this.ci = nnEnumeratorType.GetConstructor (new Type[] { gn.GetType () }); + } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator () + { + return (System.Collections.Generic.IEnumerator) ci.Invoke (cps); + } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return (System.Collections.IEnumerator) ci.Invoke (cps); + } + } + + + /** + * @brief Steps through list of nodes that can possible be next in a flow-control sense. + */ + public abstract class NNEnumeratorBase : System.Collections.Generic.IEnumerator { + protected GraphNode nn; + + public abstract bool MoveNext (); + public abstract void Reset (); + + GraphNode System.Collections.Generic.IEnumerator.Current { + get { return this.nn; } + } + object System.Collections.IEnumerator.Current { + get { return this.nn; } + } + void System.IDisposable.Dispose() { } + } + + + public class ScriptCollector : ScriptMyILGen { + public static readonly bool DEBUG = false; + + public ScriptObjWriter wrapped; + public GraphNode firstLin, lastLin; + private bool resolvedSomething; + private int resolveSequence; + private int excBlkSeqNos; + public StackDepth stackDepth = new StackDepth (); + + public GraphNodeBeginExceptionBlock curTryBlock = null; // pushed at beginning of try + // popped at BEGINNING of catch/finally + public GraphNodeBeginExceptionBlock curExcBlock = null; // pushed at beginning of try + // popped at END of catch/finally + + private List declaredLocals = new List (); + private List definedLabels = new List (); + + public string methName { get { return wrapped.methName; } } + + /** + * @brief Wrap the optimizer around the ScriptObjWriter to collect the instruction stream. + * All stream-writing calls get saved to our graph nodes instead of being written to object file. + */ + public ScriptCollector (ScriptObjWriter wrapped) + { + this.wrapped = wrapped; + GraphNodeBegin gnb = new GraphNodeBegin (this); + this.firstLin = gnb; + this.lastLin = gnb; + } + + public ScriptMyLocal DeclareLocal (Type type, string name) + { + ScriptMyLocal loc = new ScriptMyLocal (); + loc.name = name; + loc.type = type; + loc.number = wrapped.localNumber ++; + declaredLocals.Add (loc); + return loc; + } + + public ScriptMyLabel DefineLabel (string name) + { + ScriptMyLabel lbl = new ScriptMyLabel (); + lbl.name = name; + lbl.number = wrapped.labelNumber ++; + definedLabels.Add (lbl); + return lbl; + } + + public void BeginExceptionBlock () + { + GraphNodeBeginExceptionBlock tryBlock = new GraphNodeBeginExceptionBlock (this); + tryBlock.ChainLin (); + tryBlock.excBlkSeqNo = ++ this.excBlkSeqNos; + this.curExcBlock = tryBlock; + this.curTryBlock = tryBlock; + } + + public void BeginCatchBlock (Type excType) + { + GraphNodeBeginCatchBlock catchBlock = new GraphNodeBeginCatchBlock (this, excType); + catchBlock.ChainLin (); + if (curExcBlock.catchFinallyBlock != null) throw new Exception ("only one catch/finally allowed per try"); + curExcBlock.catchFinallyBlock = catchBlock; + curTryBlock = curExcBlock.tryBlock; + } + + public void BeginFinallyBlock () + { + GraphNodeBeginFinallyBlock finallyBlock = new GraphNodeBeginFinallyBlock (this); + finallyBlock.ChainLin (); + if (curExcBlock.catchFinallyBlock != null) throw new Exception ("only one catch/finally allowed per try"); + curExcBlock.catchFinallyBlock = finallyBlock; + curTryBlock = curExcBlock.tryBlock; + } + + public void EndExceptionBlock () + { + GraphNodeEndExceptionBlock endExcBlock = new GraphNodeEndExceptionBlock (this); + endExcBlock.ChainLin (); + curExcBlock.endExcBlock = endExcBlock; + curTryBlock = curExcBlock.tryBlock; + curExcBlock = curExcBlock.excBlock; + } + + public void Emit (Token errorAt, OpCode opcode) + { + if (opcode == OpCodes.Endfinally) { + new GraphNodeEmitNullEndfinally (this, errorAt).ChainLin (); + } else { + new GraphNodeEmitNull (this, errorAt, opcode).ChainLin (); + } + } + + public void Emit (Token errorAt, OpCode opcode, FieldInfo field) + { + if (field == null) throw new ArgumentNullException ("field"); + new GraphNodeEmitField (this, errorAt, opcode, field).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, ScriptMyLocal myLocal) + { + new GraphNodeEmitLocal (this, errorAt, opcode, myLocal).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, Type type) + { + new GraphNodeEmitType (this, errorAt, opcode, type).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, ScriptMyLabel myLabel) + { + if (opcode == OpCodes.Leave) { + new GraphNodeEmitLabelLeave (this, errorAt, myLabel).ChainLin (); + } else { + new GraphNodeEmitLabel (this, errorAt, opcode, myLabel).ChainLin (); + } + } + + public void Emit (Token errorAt, OpCode opcode, ScriptMyLabel[] myLabels) + { + new GraphNodeEmitLabels (this, errorAt, opcode, myLabels).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, ScriptObjWriter method) + { + if (method == null) throw new ArgumentNullException ("method"); + new GraphNodeEmitIntMeth (this, errorAt, opcode, method).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, MethodInfo method) + { + if (method == null) throw new ArgumentNullException ("method"); + new GraphNodeEmitExtMeth (this, errorAt, opcode, method).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, ConstructorInfo ctor) + { + if (ctor == null) throw new ArgumentNullException ("ctor"); + new GraphNodeEmitCtor (this, errorAt, opcode, ctor).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, double value) + { + new GraphNodeEmitDouble (this, errorAt, opcode, value).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, float value) + { + new GraphNodeEmitFloat (this, errorAt, opcode, value).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, int value) + { + new GraphNodeEmitInt (this, errorAt, opcode, value).ChainLin (); + } + + public void Emit (Token errorAt, OpCode opcode, string value) + { + new GraphNodeEmitString (this, errorAt, opcode, value).ChainLin (); + } + + public void MarkLabel (ScriptMyLabel myLabel) + { + myLabel.whereAmI = new GraphNodeMarkLabel (this, myLabel); + myLabel.whereAmI.ChainLin (); + } + + /** + * @brief Write the whole graph out to the object file. + */ + public ScriptMyILGen WriteOutAll () + { + foreach (ScriptMyLocal loc in declaredLocals) { + if (loc.isReferenced) wrapped.DeclareLocal (loc); + } + foreach (ScriptMyLabel lbl in definedLabels) { + wrapped.DefineLabel (lbl); + } + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + gn.WriteOutOne (wrapped); + } + return wrapped; + } + + /** + * @brief Perform optimizations. + */ + public void Optimize () + { + if (curExcBlock != null) throw new Exception ("exception block still open"); + + /* + * If an instruction says it doesn't fall through, remove all instructions to + * the end of the block. + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if (!gn.CanFallThrough ()) { + GraphNode nn; + while (((nn = gn.nextLin) != null) && !(nn is GraphNodeBlock) && + !(nn is GraphNodeEndExceptionBlock)) { + if ((gn.nextLin = nn.nextLin) != null) { + nn.nextLin.prevLin = gn; + } + } + } + } + + /* + * Scan for OpCodes.Leave instructions. + * For each found, its target for flow analysis purposes is the beginning of the corresponding + * finally block. And the end of the finally block gets a conditional branch target of the + * leave instruction's target. A leave instruction can unwind zero or more finally blocks. + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if (gn is GraphNodeEmitLabelLeave) { + GraphNodeEmitLabelLeave leaveInstr = (GraphNodeEmitLabelLeave)gn; // the leave instruction + GraphNodeMarkLabel leaveTarget = leaveInstr.myLabel.whereAmI; // label being targeted by leave + GraphNodeBeginExceptionBlock leaveTargetsTryBlock = // try block directly enclosing leave target + (leaveTarget == null) ? null : leaveTarget.tryBlock; // ...it must not be unwound + + /* + * Step through try { }s from the leave instruction towards its target looking for try { }s with finally { }s. + * The leave instruction unconditionally branches to the beginning of the innermost one found. + * The end of the last one found conditionally branches to the leave instruction's target. + * If none found, the leave is a simple unconditional branch to its target. + */ + GraphNodeBeginFinallyBlock innerFinallyBlock = null; + for (GraphNodeBeginExceptionBlock tryBlock = leaveInstr.tryBlock; + tryBlock != leaveTargetsTryBlock; + tryBlock = tryBlock.tryBlock) { + if (tryBlock == null) throw new Exception ("leave target not at or outer to leave instruction"); + GraphNodeCatchFinallyBlock cfb = tryBlock.catchFinallyBlock; + if (cfb is GraphNodeBeginFinallyBlock) { + if (innerFinallyBlock == null) { + leaveInstr.unwindTo = cfb; + } + innerFinallyBlock = (GraphNodeBeginFinallyBlock)cfb; + } + } + + /* + * The end of the outermost finally being unwound can conditionally jump to the target of the leave instruction. + * In the case of no finallies being unwound, the leave is just a simple unconditional branch. + */ + if (innerFinallyBlock == null) { + leaveInstr.unwindTo = leaveTarget; + } else if (!innerFinallyBlock.leaveTargets.Contains (leaveTarget)) { + innerFinallyBlock.leaveTargets.Add (leaveTarget); + } + } + } + + /* + * See which variables a particular block reads before writing. + * This just considers the block itself and nothing that it branches to or fallsthru to. + */ + GraphNodeBlock currentBlock = null; + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if (gn is GraphNodeBlock) currentBlock = (GraphNodeBlock)gn; + ScriptMyLocal rdlcl = gn.ReadsLocal (); + if ((rdlcl != null) && + !currentBlock.localsWrittenBeforeRead.Contains (rdlcl) && + !currentBlock.localsReadBeforeWritten.Contains (rdlcl)) { + currentBlock.localsReadBeforeWritten.Add (rdlcl); + } + ScriptMyLocal wrlcl = gn.WritesLocal (); + if ((wrlcl != null) && + !currentBlock.localsWrittenBeforeRead.Contains (wrlcl) && + !currentBlock.localsReadBeforeWritten.Contains (wrlcl)) { + currentBlock.localsWrittenBeforeRead.Add (wrlcl); + } + } + + /* + * For every block we branch to, add that blocks readables to our list of readables, + * because we need to have those values valid on entry to our block. But if we write the + * variable before we can possibly branch to that block, then we don't need to have it valid + * on entry to our block. So basically it looks like the branch instruction is reading + * everything required by any blocks it can branch to. + */ + do { + this.resolvedSomething = false; + this.resolveSequence ++; + this.ResolveBlock ((GraphNodeBlock)firstLin); + } while (this.resolvedSomething); + + /* + * Repeat the cutting loops as long as we keep finding stuff. + */ + bool didSomething; + do { + didSomething = false; + + /* + * Strip out ldc.i4.1/xor/ldc.i4.1/xor + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if (!(gn is GraphNodeEmit)) continue; + GraphNodeEmit xor2 = (GraphNodeEmit)gn; + if (xor2.opcode != OpCodes.Xor) continue; + if (!(xor2.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit ld12 = (GraphNodeEmit)xor2.prevLin; + if (ld12.opcode != OpCodes.Ldc_I4_1) continue; + if (!(ld12.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit xor1 = (GraphNodeEmit)ld12.prevLin; + if (xor1.opcode != OpCodes.Xor) continue; + if (!(xor2.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit ld11 = (GraphNodeEmit)xor1.prevLin; + if (ld11.opcode != OpCodes.Ldc_I4_1) continue; + ld11.prevLin.nextLin = xor2.nextLin; + xor2.nextLin.prevLin = ld11.prevLin; + didSomething = true; + } + + /* + * Replace c{cond}/ldc.i4.1/xor/br{false,true} -> c{cond}/br{true,false} + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if (!(gn is GraphNodeEmit)) continue; + GraphNodeEmit brft = (GraphNodeEmit)gn; + if ((brft.opcode != OpCodes.Brfalse) && (brft.opcode != OpCodes.Brtrue)) continue; + if (!(brft.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit xor = (GraphNodeEmit)brft.prevLin; + if (xor.opcode != OpCodes.Xor) continue; + if (!(xor.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit ldc = (GraphNodeEmit)xor.prevLin; + if (ldc.opcode != OpCodes.Ldc_I4_1) continue; + if (!(ldc.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit cmp = (GraphNodeEmit)ldc.prevLin; + if (cmp.opcode.StackBehaviourPop != StackBehaviour.Pop1_pop1) continue; + if (cmp.opcode.StackBehaviourPush != StackBehaviour.Pushi) continue; + cmp.nextLin = brft; + brft.prevLin = cmp; + brft.opcode = (brft.opcode == OpCodes.Brfalse) ? OpCodes.Brtrue : OpCodes.Brfalse; + didSomething = true; + } + + /* + * Replace c{cond}/br{false,true} -> b{!,}{cond} + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if (!(gn is GraphNodeEmit)) continue; + GraphNodeEmit brft = (GraphNodeEmit)gn; + if ((brft.opcode != OpCodes.Brfalse) && (brft.opcode != OpCodes.Brtrue)) continue; + if (!(brft.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit cmp = (GraphNodeEmit)brft.prevLin; + if (cmp.opcode.StackBehaviourPop != StackBehaviour.Pop1_pop1) continue; + if (cmp.opcode.StackBehaviourPush != StackBehaviour.Pushi) continue; + cmp.prevLin.nextLin = brft; + brft.prevLin = cmp.prevLin; + bool brtru = (brft.opcode == OpCodes.Brtrue); + if (cmp.opcode == OpCodes.Ceq) brft.opcode = brtru ? OpCodes.Beq : OpCodes.Bne_Un; + else if (cmp.opcode == OpCodes.Cgt) brft.opcode = brtru ? OpCodes.Bgt : OpCodes.Ble; + else if (cmp.opcode == OpCodes.Cgt_Un) brft.opcode = brtru ? OpCodes.Bgt_Un : OpCodes.Ble_Un; + else if (cmp.opcode == OpCodes.Clt) brft.opcode = brtru ? OpCodes.Blt : OpCodes.Bge; + else if (cmp.opcode == OpCodes.Clt_Un) brft.opcode = brtru ? OpCodes.Blt_Un : OpCodes.Bge_Un; + else throw new Exception (); + didSomething = true; + } + + /* + * Replace ld{c.i4.0,null}/br{ne.un,eq} -> br{true,false} + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if (!(gn is GraphNodeEmit)) continue; + GraphNodeEmit brcc = (GraphNodeEmit)gn; + if ((brcc.opcode != OpCodes.Bne_Un) && (brcc.opcode != OpCodes.Beq)) continue; + if (!(brcc.prevLin is GraphNodeEmit)) continue; + GraphNodeEmit ldc0 = (GraphNodeEmit)brcc.prevLin; + if ((ldc0.opcode != OpCodes.Ldc_I4_0) && (ldc0.opcode != OpCodes.Ldnull)) continue; + ldc0.prevLin.nextLin = brcc; + brcc.prevLin = ldc0.prevLin; + brcc.opcode = (brcc.opcode == OpCodes.Bne_Un) ? OpCodes.Brtrue : OpCodes.Brfalse; + didSomething = true; + } + + /* + * Replace: + * ldloc v1 + * stloc v2 + * ld except ld v2 + * ldloc v2 + * ...v2 unreferenced hereafter + * With: + * ld except ld v2 + * ldloc v1 + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + + // check for 'ldloc v1' instruction + if (!(gn is GraphNodeEmitLocal)) continue; + GraphNodeEmitLocal ldlv1 = (GraphNodeEmitLocal)gn; + if (ldlv1.opcode != OpCodes.Ldloc) continue; + + // check for 'stloc v2' instruction + if (!(ldlv1.nextLin is GraphNodeEmitLocal)) continue; + GraphNodeEmitLocal stlv2 = (GraphNodeEmitLocal)ldlv1.nextLin; + if (stlv2.opcode != OpCodes.Stloc) continue; + + // check for 'ld except ld v2' instruction + if (!(stlv2.nextLin is GraphNodeEmit)) continue; + GraphNodeEmit ldany = (GraphNodeEmit)stlv2.nextLin; + if (!ldany.opcode.ToString ().StartsWith ("ld")) continue; + if ((ldany is GraphNodeEmitLocal) && + ((GraphNodeEmitLocal)ldany).myLocal == stlv2.myLocal) continue; + + // check for 'ldloc v2' instruction + if (!(ldany.nextLin is GraphNodeEmitLocal)) continue; + GraphNodeEmitLocal ldlv2 = (GraphNodeEmitLocal)ldany.nextLin; + if (ldlv2.opcode != OpCodes.Ldloc) continue; + if (ldlv2.myLocal != stlv2.myLocal) continue; + + // check that v2 is not needed after this at all + if (IsLocalNeededAfterThis (ldlv2, ldlv2.myLocal)) continue; + + // make 'ld...' the first instruction + ldany.prevLin = ldlv1.prevLin; + ldany.prevLin.nextLin = ldany; + + // make 'ldloc v1' the second instruction + ldany.nextLin = ldlv1; + ldlv1.prevLin = ldany; + + // and make 'ldloc v1' the last instruction + ldlv1.nextLin = ldlv2.nextLin; + ldlv1.nextLin.prevLin = ldlv1; + + didSomething = true; + } + + /* + * Remove all the stloc/ldloc that are back-to-back without the local + * being needed afterwards. If it is needed afterwards, replace the + * stloc/ldloc with dup/stloc. + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if ((gn is GraphNodeEmitLocal) && + (gn.prevLin is GraphNodeEmitLocal)) { + GraphNodeEmitLocal stloc = (GraphNodeEmitLocal)gn.prevLin; + GraphNodeEmitLocal ldloc = (GraphNodeEmitLocal)gn; + if ((stloc.opcode == OpCodes.Stloc) && + (ldloc.opcode == OpCodes.Ldloc) && + (stloc.myLocal == ldloc.myLocal)) { + if (IsLocalNeededAfterThis (ldloc, ldloc.myLocal)) { + GraphNodeEmitNull dup = new GraphNodeEmitNull (this, stloc.errorAt, OpCodes.Dup); + dup.nextLin = stloc; + dup.prevLin = stloc.prevLin; + stloc.nextLin = ldloc.nextLin; + stloc.prevLin = dup; + dup.prevLin.nextLin = dup; + stloc.nextLin.prevLin = stloc; + gn = stloc; + } else { + stloc.prevLin.nextLin = ldloc.nextLin; + ldloc.nextLin.prevLin = stloc.prevLin; + gn = stloc.prevLin; + } + didSomething = true; + } + } + } + + /* + * Remove all write-only local variables, ie, those with no ldloc[a] references. + * Replace any stloc instructions with pops. + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + ScriptMyLocal rdlcl = gn.ReadsLocal (); + if (rdlcl != null) rdlcl.isReferenced = true; + } + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + ScriptMyLocal wrlcl = gn.WritesLocal (); + if ((wrlcl != null) && !wrlcl.isReferenced) { + if (!(gn is GraphNodeEmitLocal) || (((GraphNodeEmitLocal)gn).opcode != OpCodes.Stloc)) { + throw new Exception ("expecting stloc"); + } + GraphNodeEmitNull pop = new GraphNodeEmitNull (this, ((GraphNodeEmit)gn).errorAt, OpCodes.Pop); + pop.nextLin = gn.nextLin; + pop.prevLin = gn.prevLin; + gn.nextLin.prevLin = pop; + gn.prevLin.nextLin = pop; + gn = pop; + didSomething = true; + } + } + + /* + * Remove any Ld/Dup,Pop. + */ + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + if ((gn is GraphNodeEmit) && + (gn.nextLin is GraphNodeEmit)) { + GraphNodeEmit gne = (GraphNodeEmit)gn; + GraphNodeEmit nne = (GraphNodeEmit)gn.nextLin; + if (gne.isPoppable && (nne.opcode == OpCodes.Pop)) { + gne.prevLin.nextLin = nne.nextLin; + nne.nextLin.prevLin = gne.prevLin; + gn = gne.prevLin; + didSomething = true; + } + } + } + } while (didSomething); + + /* + * Dump out the results. + */ + if (DEBUG) { + Console.WriteLine (""); + Console.WriteLine (methName); + Console.WriteLine (" resolveSequence=" + this.resolveSequence); + + Console.WriteLine (" Locals:"); + foreach (ScriptMyLocal loc in declaredLocals) { + Console.WriteLine (" " + loc.type.Name + " " + loc.name); + } + + Console.WriteLine (" Labels:"); + foreach (ScriptMyLabel lbl in definedLabels) { + Console.WriteLine (" " + lbl.name); + } + + Console.WriteLine (" Code:"); + DumpCode (); + } + } + + private void DumpCode () + { + int linSeqNos = 0; + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + gn.linSeqNo = ++ linSeqNos; + } + for (GraphNode gn = firstLin; gn != null; gn = gn.nextLin) { + StringBuilder sb = new StringBuilder (); + gn.DebStringExt (sb); + Console.WriteLine (sb.ToString ()); + if (gn is GraphNodeBlock) { + GraphNodeBlock gnb = (GraphNodeBlock)gn; + foreach (ScriptMyLocal lcl in gnb.localsReadBeforeWritten) { + Console.WriteLine (" reads " + lcl.name); + } + } + } + } + + /** + * @brief Scan the given block for branches to other blocks. + * For any locals read by those blocks, mark them as being read by this block, + * provided this block has not written them by that point. This makes it look + * as though the branch instruction is reading all the locals needed by any + * target blocks. + */ + private void ResolveBlock (GraphNodeBlock currentBlock) + { + if (currentBlock.hasBeenResolved == this.resolveSequence) return; + + /* + * So we don't recurse forever on a backward branch. + */ + currentBlock.hasBeenResolved = this.resolveSequence; + + /* + * Assume we haven't written any locals yet. + */ + List localsWrittenSoFar = new List (); + + /* + * Scan through the instructions in this block. + */ + for (GraphNode gn = currentBlock; gn != null;) { + + /* + * See if the instruction writes a local we don't know about yet. + */ + ScriptMyLocal wrlcl = gn.WritesLocal (); + if ((wrlcl != null) && !localsWrittenSoFar.Contains (wrlcl)) { + localsWrittenSoFar.Add (wrlcl); + } + + /* + * Scan through all the possible next instructions after this. + * Note that if we are in the first part of a try/catch/finally block, + * every instruction conditionally branches to the beginning of the + * second part (the catch/finally block). + */ + GraphNode nextFallthruNode = null; + foreach (GraphNode nn in gn.NextNodes) { + if (nn is GraphNodeBlock) { + + /* + * Start of a block, go through all locals needed by that block on entry. + */ + GraphNodeBlock nextBlock = (GraphNodeBlock)nn; + ResolveBlock (nextBlock); + foreach (ScriptMyLocal readByNextBlock in nextBlock.localsReadBeforeWritten) { + + /* + * If this block hasn't written it by now and this block doesn't already + * require it on entry, say this block requires it on entry. + */ + if (!localsWrittenSoFar.Contains (readByNextBlock) && + !currentBlock.localsReadBeforeWritten.Contains (readByNextBlock)) { + currentBlock.localsReadBeforeWritten.Add (readByNextBlock); + this.resolvedSomething = true; + } + } + } else { + + /* + * Not start of a block, should be normal fallthru instruction. + */ + if (nextFallthruNode != null) throw new Exception ("more than one fallthru from " + gn.ToString ()); + nextFallthruNode = nn; + } + } + + /* + * Process next instruction if it isn't the start of a block. + */ + if (nextFallthruNode == gn) throw new Exception ("can't fallthru to self"); + gn = nextFallthruNode; + } + } + + /** + * @brief Figure out whether the value in a local var is needed after the given instruction. + * True if we reach the end of the program on all branches before reading it + * True if we write the local var on all branches before reading it + * False otherwise + */ + private bool IsLocalNeededAfterThis (GraphNode node, ScriptMyLocal local) + { + do { + GraphNode nextFallthruNode = null; + foreach (GraphNode nn in node.NextNodes) { + if (nn is GraphNodeBlock) { + if (((GraphNodeBlock)nn).localsReadBeforeWritten.Contains (local)) { + return true; + } + } else { + nextFallthruNode = nn; + } + } + node = nextFallthruNode; + if (node == null) return false; + if (node.ReadsLocal () == local) return true; + } while (node.WritesLocal () != local); + return false; + } + + public static void PadToLength (StringBuilder sb, int len, string str) + { + int pad = len - sb.Length; + if (pad < 0) pad = 0; + sb.Append (str.PadLeft (pad)); + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompValu.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompValu.cs new file mode 100644 index 0000000..fd3174d --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompValu.cs @@ -0,0 +1,1677 @@ +/* + * 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 OpenSim.Region.ScriptEngine.XMREngine; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; + +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; + +/** + * @brief Compute values used during code generation to keep track of where computed values are stored. + * + * Conceptually holds the memory address and type of the value + * such as that used for a local variable, global variable, temporary variable. + * Also used for things like constants and function/method entrypoints, + * they are basically treated as read-only variables. + * + * cv.type - type of the value + * + * cv.PushVal() - pushes the value on the CIL stack + * cv.PushRef() - pushes address of the value on the CIL stack + * + * cv.PopPre() - gets ready to pop from the CIL stack + * ...by possibly pushing something + * + * cv.PushPre() - pops value from the CIL stack + * + * If the type is a TokenTypeSDTypeDelegate, the location is callable, + * so you get these additional functions: + * + * cv.GetRetType() - gets function/method's return value type + * TokenTypeVoid if void + * null if not a delegate + * cv.GetArgTypes() - gets array of argument types + * as seen by script level, ie, + * does not include any hidden 'this' type + * cv.GetArgSig() - gets argument signature eg, "(integer,list)" + * null if not a delegate + * + * cv.CallPre() - gets ready to call the function/method + * ...by possibly pushing something + * such as a 'this' pointer + * + * cv.CallPost() - calls the function/method + */ + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + + /** + * @brief Location of a value + * Includes constants, expressions and temp variables. + */ + public abstract class CompValu { + protected static readonly MethodInfo gsmdMethodInfo = + typeof (XMRInstAbstract).GetMethod ("GetScriptMethodDelegate", + new Type[] { typeof (string), typeof (string), typeof (object) }); + + private static readonly MethodInfo avpmListMethInfo = typeof (XMRInstArrays).GetMethod ("PopList", new Type[] { typeof (int), typeof (LSL_List) }); + private static readonly MethodInfo avpmObjectMethInfo = typeof (XMRInstArrays).GetMethod ("PopObject", new Type[] { typeof (int), typeof (object) }); + private static readonly MethodInfo avpmStringMethInfo = typeof (XMRInstArrays).GetMethod ("PopString", new Type[] { typeof (int), typeof (string) }); + + public TokenType type; // type of the value and where in the source it was used + + public CompValu (TokenType type) + { + this.type = type; + } + + public Type ToSysType() + { + return (type.ToLSLWrapType () != null) ? type.ToLSLWrapType () : type.ToSysType (); + } + + // if a field of an XMRInstArrays array cannot be directly written, + // get the method that can write it + private static MethodInfo ArrVarPopMeth (FieldInfo fi) + { + if (fi.Name == "iarLists") return avpmListMethInfo; + if (fi.Name == "iarObjects") return avpmObjectMethInfo; + if (fi.Name == "iarStrings") return avpmStringMethInfo; + return null; + } + + // emit code to push value onto stack + public void PushVal (ScriptCodeGen scg, Token errorAt, TokenType stackType) + { + this.PushVal (scg, errorAt, stackType, false); + } + public void PushVal (ScriptCodeGen scg, Token errorAt, TokenType stackType, bool explicitAllowed) + { + this.PushVal (scg, errorAt); + TypeCast.CastTopOfStack (scg, errorAt, this.type, stackType, explicitAllowed); + } + public abstract void PushVal (ScriptCodeGen scg, Token errorAt); + public abstract void PushRef (ScriptCodeGen scg, Token errorAt); + + // emit code to pop value from stack + public void PopPost (ScriptCodeGen scg, Token errorAt, TokenType stackType) + { + TypeCast.CastTopOfStack (scg, errorAt, stackType, this.type, false); + this.PopPost (scg, errorAt); + } + public virtual void PopPre (ScriptCodeGen scg, Token errorAt) { } // call this before pushing value to be popped + public abstract void PopPost (ScriptCodeGen scg, Token errorAt); // call this after pushing value to be popped + + // return true: doing a PushVal() does not involve CheckRun() + // false: otherwise + public virtual bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return true; + } + + /* + * These additional functions are available if the type is a delegate + */ + public TokenType GetRetType () + { + if (!(type is TokenTypeSDTypeDelegate)) return null; + return ((TokenTypeSDTypeDelegate)type).decl.GetRetType (); + } + public TokenType[] GetArgTypes () + { + if (!(type is TokenTypeSDTypeDelegate)) return null; + return ((TokenTypeSDTypeDelegate)type).decl.GetArgTypes (); + } + public string GetArgSig () + { + if (!(type is TokenTypeSDTypeDelegate)) return null; + return ((TokenTypeSDTypeDelegate)type).decl.GetArgSig (); + } + + // These are used only if type is a delegate too + // - but it is a real delegate pointer in a global or local variable or a field, etc + // ie, PushVal() pushes a delegate pointer + // - so we must have CallPre() push the delegate pointer as a 'this' for this.Invoke(...) + // - and CallPost() call the delegate's Invoke() method + // - we assume the target function is non-trivial so we always use a call label + public virtual void CallPre (ScriptCodeGen scg, Token errorAt) // call this before pushing arguments + { + new ScriptCodeGen.CallLabel (scg, errorAt); + this.PushVal (scg, errorAt); + } + public virtual void CallPost (ScriptCodeGen scg, Token errorAt) // call this after pushing arguments + { + TokenTypeSDTypeDelegate ttd = (TokenTypeSDTypeDelegate)type; + MethodInfo invokeMethodInfo = ttd.decl.GetInvokerInfo (); + scg.ilGen.Emit (errorAt, OpCodes.Callvirt, invokeMethodInfo); + scg.openCallLabel = null; + } + + /* + * Utilities used by CompValuGlobalVar and CompValuInstField + * where the value is located in a type-dependent array. + */ + protected void EmitFieldPushVal (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, var.vTableArray); // which array + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); // which array element + if (type is TokenTypeFloat) { + scg.ilGen.Emit (errorAt, OpCodes.Ldelem_R8); + } else if (type is TokenTypeInt) { + scg.ilGen.Emit (errorAt, OpCodes.Ldelem_I4); + } else if (type is TokenTypeSDTypeDelegate) { + scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (object)); + scg.ilGen.Emit (errorAt, OpCodes.Castclass, ToSysType ()); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Ldelem, ToSysType ()); + } + } + + protected void EmitFieldPushRef (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) + { + if (ArrVarPopMeth (var.vTableArray) != null) { + scg.ErrorMsg (errorAt, "can't take address of this variable"); + } + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, var.vTableArray); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); + scg.ilGen.Emit (errorAt, OpCodes.Ldelema, ToSysType()); + } + + protected void EmitFieldPopPre (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) + { + if (ArrVarPopMeth (var.vTableArray) != null) { + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, var.vTableArray); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); + } + } + + protected void EmitFieldPopPost (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) + { + if (ArrVarPopMeth (var.vTableArray) != null) { + scg.ilGen.Emit (errorAt, OpCodes.Call, ArrVarPopMeth (var.vTableArray)); + } else if (type is TokenTypeFloat) { + scg.ilGen.Emit (errorAt, OpCodes.Stelem_R8); + } else if (type is TokenTypeInt) { + scg.ilGen.Emit (errorAt, OpCodes.Stelem_I4); + } else if (type is TokenTypeSDTypeDelegate) { + scg.ilGen.Emit (errorAt, OpCodes.Stelem, typeof (object)); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Stelem, ToSysType ()); + } + } + + /** + * @brief With value pushed on stack, emit code to set a property by calling its setter() method. + * @param scg = which script is being compiled + * @param errorAt = for error messages + * @param type = property type + * @param setProp = setter() method + */ + protected void EmitPopPostProp (ScriptCodeGen scg, Token errorAt, TokenType type, CompValu setProp) + { + ScriptMyLocal temp = scg.ilGen.DeclareLocal (type.ToSysType (), "__spr_" + errorAt.Unique); + scg.ilGen.Emit (errorAt, OpCodes.Stloc, temp); + setProp.CallPre (scg, errorAt); + scg.ilGen.Emit (errorAt, OpCodes.Ldloc, temp); + setProp.CallPost (scg, errorAt); + } + } + + // The value is kept in an (XMR_Array) array element + public class CompValuArEle : CompValu { + public CompValu arr; + private CompValu idx; + private TokenTypeObject tto; + + private static readonly MethodInfo getByKeyMethodInfo = typeof (XMR_Array).GetMethod ("GetByKey", + new Type[] { typeof (object) }); + private static readonly MethodInfo setByKeyMethodInfo = typeof (XMR_Array).GetMethod ("SetByKey", + new Type[] { typeof (object), + typeof (object) }); + + // type = TokenTypeObject always, as our array elements are always of type 'object' + // arr = where the array object itself is stored + // idx = where the index value is stored + public CompValuArEle (TokenType type, CompValu arr, CompValu idx) : base (type) + { + this.arr = arr; + this.idx = idx; + this.tto = new TokenTypeObject (this.type); + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + arr.PushVal (scg, errorAt); // array + idx.PushVal (scg, errorAt, this.tto); // key + scg.ilGen.Emit (errorAt, OpCodes.Call, getByKeyMethodInfo); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "array element not allowed here"); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + arr.PushVal (scg, errorAt); // array + idx.PushVal (scg, errorAt, this.tto); // key + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, setByKeyMethodInfo); + } + + // non-trivial because it needs to be copied into a temp + // in case the idiot does dumb-ass side effects tricks + // eg, (x = 0) + x + 2 + // should read old value of x not 0 + // but if 'xmroption norighttoleft;' in effect, + // we can read it in any order so reading an + // XMR_Array element is trivial + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return readAt.nr2l; + } + } + + // The value is kept in the current function's argument list + public class CompValuArg : CompValu { + public int index; + public bool readOnly; + + private static OpCode[] ldargs = { OpCodes.Ldarg_0, OpCodes.Ldarg_1, + OpCodes.Ldarg_2, OpCodes.Ldarg_3 }; + + public CompValuArg (TokenType type, int index) : base (type) + { + this.index = index; + } + public CompValuArg (TokenType type, int index, bool ro) : base (type) + { + this.index = index; + this.readOnly = ro; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + if (index < ldargs.Length) scg.ilGen.Emit (errorAt, ldargs[index]); + else if (index <= 255) scg.ilGen.Emit (errorAt, OpCodes.Ldarg_S, index); + else scg.ilGen.Emit (errorAt, OpCodes.Ldarg, index); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + if (readOnly) { + scg.ErrorMsg (errorAt, "location cannot be written to"); + } + if (index <= 255) scg.ilGen.Emit (errorAt, OpCodes.Ldarga_S, index); + else scg.ilGen.Emit (errorAt, OpCodes.Ldarga, index); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + if (readOnly) { + scg.ErrorMsg (errorAt, "location cannot be written to"); + } + scg.ilGen.Emit (errorAt, OpCodes.Starg, index); + } + + // non-trivial because it needs to be copied into a temp + // in case the idiot does dumb-ass side effects tricks + // eg, (x = 0) + x + 2 + // should read old value of x not 0 + // but if 'xmroption norighttoleft;' in effect, + // we can read it in any order so reading an + // argument is trivial + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return readAt.nr2l; + } + } + + // The value is a character constant + public class CompValuChar : CompValu { + public char x; + + public CompValuChar (TokenType type, char x) : base (type) + { + if (!(this.type is TokenTypeChar)) { + this.type = new TokenTypeChar (this.type); + } + this.x = x; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, (int)x); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get constant's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into contant"); + } + } + + // The value is kept in a struct/class field of an internal struct/class + public class CompValuField : CompValu { + CompValu obj; + FieldInfo field; + + public CompValuField (TokenType type, CompValu obj, FieldInfo field) : base (type) + { + this.obj = obj; + this.field = field; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + if (field.ReflectedType.IsValueType) { + obj.PushRef (scg, errorAt); + } else { + obj.PushVal (scg, errorAt); + } + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, field); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + if (field.ReflectedType.IsValueType) { + obj.PushRef (scg, errorAt); + } else { + obj.PushVal (scg, errorAt); + } + scg.ilGen.Emit (errorAt, OpCodes.Ldflda, field); + } + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + if (field.ReflectedType.IsValueType) { + obj.PushRef (scg, errorAt); + } else { + obj.PushVal (scg, errorAt); + } + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Stfld, field); + } + + // non-trivial because it needs to be copied into a temp + // in case the idiot does dumb-ass side effects tricks + // eg, (x = 0) + x + 2 + // should read old value of x not 0 + // but if 'xmroption norighttoleft;' in effect, + // we can read it in any order so reading an + // field of a class/struct is trivial + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return readAt.nr2l; + } + } + + // Accessing an element of a fixed-dimension array + public class CompValuFixArEl : CompValu { + private CompValu baseRVal; + private CompValu[] subRVals; + + private int nSubs; + private TokenDeclVar getFunc; + private TokenDeclVar setFunc; + private TokenTypeInt tokenTypeInt; + + /** + * @brief Set up to access an element of an array. + * @param scg = what script we are compiling + * @param baseRVal = what array we are accessing + * @param subRVals = the subscripts being applied + */ + public CompValuFixArEl (ScriptCodeGen scg, CompValu baseRVal, CompValu[] subRVals) : base (GetElementType (scg, baseRVal, subRVals)) + { + this.baseRVal = baseRVal; // location of the array itself + this.subRVals = subRVals; // subscript values + this.nSubs = subRVals.Length; + + TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseRVal.type; + TokenDeclSDTypeClass sdtDecl = sdtType.decl; + tokenTypeInt = new TokenTypeInt (sdtType); + + TokenName name = new TokenName (sdtType, "Get"); + TokenType[] argsig = new TokenType[nSubs]; + for (int i = 0; i < nSubs; i ++) { + argsig[i] = tokenTypeInt; + } + getFunc = scg.FindThisMember (sdtDecl, name, argsig); + + name = new TokenName (sdtType, "Set"); + argsig = new TokenType[nSubs+1]; + for (int i = 0; i < nSubs; i ++) { + argsig[i] = tokenTypeInt; + } + argsig[nSubs] = getFunc.retType; + setFunc = scg.FindThisMember (sdtDecl, name, argsig); + } + + /** + * @brief Read array element and push value on stack. + */ + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + // call script-defined class' Get() method to fetch the value + baseRVal.PushVal (scg, errorAt); + for (int i = 0; i < nSubs; i ++) { + subRVals[i].PushVal (scg, errorAt, tokenTypeInt); + } + scg.ilGen.Emit (errorAt, OpCodes.Call, getFunc.ilGen); + } + + /** + * @brief Push address of array element on stack. + */ + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("tu stOOpid to get array element address"); + } + + /** + * @brief Prepare to write array element. + */ + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + // set up call to script-defined class' Set() method to write the value + baseRVal.PushVal (scg, errorAt); + for (int i = 0; i < nSubs; i ++) { + subRVals[i].PushVal (scg, errorAt, tokenTypeInt); + } + } + + /** + * @brief Pop value from stack and write array element. + */ + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + // call script-defined class' Set() method to write the value + scg.ilGen.Emit (errorAt, OpCodes.Call, setFunc.ilGen); + } + + /** + * @brief Get the array element type by getting the Get() functions return type. + * Crude but effective. + * @param scg = what script we are compiling + * @param baseRVal = what array we are accessing + * @param subRVals = the subscripts being applied + * @returns array element type + */ + private static TokenType GetElementType (ScriptCodeGen scg, CompValu baseRVal, CompValu[] subRVals) + { + TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseRVal.type; + TokenDeclSDTypeClass sdtDecl = sdtType.decl; + TokenName name = new TokenName (sdtType, "Get"); + int nSubs = subRVals.Length; + TokenType[] argsig = new TokenType[nSubs]; + argsig[0] = new TokenTypeInt (sdtType); + for (int i = 0; ++ i < nSubs;) { + argsig[i] = argsig[0]; + } + TokenDeclVar getFunc = scg.FindThisMember (sdtDecl, name, argsig); + return getFunc.retType; + } + + // non-trivial because it needs to be copied into a temp + // in case the idiot does dumb-ass side effects tricks + // eg, (x = 0) + x + 2 + // should read old value of x not 0 + // but if 'xmroption norighttoleft;' in effect, + // we can read it in any order so reading an + // fixed-dimension array element is trivial + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return readAt.nr2l; + } + } + + // The value is a float constant + public class CompValuFloat : CompValu { + public double x; + + public CompValuFloat (TokenType type, double x) : base (type) + { + if (!(this.type is TokenTypeFloat)) { + this.type = new TokenTypeFloat (this.type); + } + this.x = x; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, x); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get constant's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into constant"); + } + } + + // The value is the entrypoint of a script-defined global function. + // These are also used for script-defined type static methods as the calling convention is the same, + // ie, the XMRInstance pointer is a hidden first argument. + // There is just one of these created when the function is being compiled as there is only one value + // of the function. + public class CompValuGlobalMeth : CompValu { + private TokenDeclVar func; + + public CompValuGlobalMeth (TokenDeclVar declFunc) : base (declFunc.GetDelType ()) + { + this.func = declFunc; + } + + /** + * @brief PushVal for a function/method means push a delegate on the stack. + * We build a call to the DynamicMethod's CreateDelegate() function + * to create the delegate. Slip the scriptinstance pointer as the + * function's arg 0 so it will get passed to the function when called. + */ + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + string dtn = type.ToString (); + if (dtn.StartsWith ("delegate ")) dtn = dtn.Substring (9); + + // delegateinstance = (signature)scriptinstance.GetScriptMethodDelegate (methName, signature, arg0); + // where methName = [.]() + // signature = () + // arg0 = scriptinstance (XMRInstance) + scg.PushXMRInst (); // [0] scriptinstance + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, func.ilGen.methName); // [1] method name + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, dtn); // [2] delegate type name + scg.PushXMRInst (); // [3] scriptinstance + scg.ilGen.Emit (errorAt, OpCodes.Callvirt, gsmdMethodInfo); // [0] delegate instance + scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // [0] cast to correct delegate class + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get ref to global method"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into global method"); + } + + /** + * @brief A direct call is much simpler than pushing a delegate. + * Just push the XMRInstance pointer, push the args and finally call the function. + */ + public override void CallPre (ScriptCodeGen scg, Token errorAt) + { + if (!this.func.IsFuncTrivial (scg)) new ScriptCodeGen.CallLabel (scg, errorAt); + + // all script-defined global functions are static methods created by DynamicMethod() + // and the first argument is always the XMR_Instance pointer + scg.PushXMRInst (); + } + public override void CallPost (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, func.ilGen); + if (!this.func.IsFuncTrivial (scg)) scg.openCallLabel = null; + } + } + + // The value is in a script-global variable = ScriptModule instance variable + // It could also be a script-global property + public class CompValuGlobalVar : CompValu { + private static readonly FieldInfo glblVarsFieldInfo = typeof (XMRInstAbstract).GetField ("glblVars"); + + private TokenDeclVar declVar; + + public CompValuGlobalVar (TokenDeclVar declVar, XMRInstArSizes glblSizes) : base (declVar.type) + { + this.declVar = declVar; + if ((declVar.getProp == null) && (declVar.setProp == null)) { + declVar.type.AssignVarSlot (declVar, glblSizes); + } + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + if ((declVar.getProp == null) && (declVar.setProp == null)) { + scg.PushXMRInst (); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, glblVarsFieldInfo); + EmitFieldPushVal (scg, errorAt, declVar); + } else if (declVar.getProp != null) { + declVar.getProp.location.CallPre (scg, errorAt); + declVar.getProp.location.CallPost (scg, errorAt); + } else { + scg.ErrorMsg (errorAt, "property not readable"); + scg.PushDefaultValue (declVar.type); + } + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + if ((declVar.getProp == null) && (declVar.setProp == null)) { + scg.PushXMRInst (); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, glblVarsFieldInfo); + EmitFieldPushRef (scg, errorAt, declVar); + } else { + scg.ErrorMsg (errorAt, "cannot get address of property"); + } + } + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + if ((declVar.getProp == null) && (declVar.setProp == null)) { + scg.PushXMRInst (); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, glblVarsFieldInfo); + EmitFieldPopPre (scg, errorAt, declVar); + } else if (declVar.setProp == null) { + scg.ErrorMsg (errorAt, "property not writable"); + } + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + if ((declVar.getProp == null) && (declVar.setProp == null)) { + EmitFieldPopPost (scg, errorAt, declVar); + } else if (declVar.setProp != null) { + EmitPopPostProp (scg, errorAt, declVar.type, declVar.setProp.location); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + } + + // non-trivial because it needs to be copied into a temp + // in case the idiot does dumb-ass side effects tricks + // eg, (x = 0) + x + 2 + // should read old value of x not 0 + // but if 'xmroption norighttoleft;' in effect, + // we can read it in any order so reading an + // global variable is trivial provided it is + // not a property or the property function is + // trivial. + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return readAt.nr2l && ((declVar.getProp == null) || declVar.getProp.IsFuncTrivial (scg)); + } + } + + // The value is in an $idxprop property of a script-defined type class or interface instance. + // Reading and writing is via a method call. + public class CompValuIdxProp : CompValu { + private TokenDeclVar idxProp; // $idxprop property within baseRVal + private CompValu baseRVal; // pointer to class or interface object containing property + private TokenType[] argTypes; // argument types as required by $idxprop declaration + private CompValu[] indices; // actual index values to pass to getter/setter method + private CompValu setProp; // location of setter method + + public CompValuIdxProp (TokenDeclVar idxProp, CompValu baseRVal, TokenType[] argTypes, CompValu[] indices) : base (idxProp.type) + { + this.idxProp = idxProp; + this.baseRVal = baseRVal; + this.argTypes = argTypes; + this.indices = indices; + } + + /** + * @brief Pushing the property's value is a matter of calling the getter method + * with the supplied argument list as is. + */ + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + if (idxProp.getProp != null) { + if (!idxProp.getProp.IsFuncTrivial (scg)) { + for (int i = indices.Length; -- i >= 0;) { + indices[i] = scg.Trivialize (indices[i], errorAt); + } + } + CompValu getProp = GetIdxPropMeth (idxProp.getProp); + getProp.CallPre (scg, errorAt); + for (int i = 0; i < indices.Length; i ++) { + indices[i].PushVal (scg, errorAt, argTypes[i]); + } + getProp.CallPost (scg, errorAt); + } else { + // write-only property + scg.ErrorMsg (errorAt, "member not readable"); + scg.PushDefaultValue (idxProp.type); + } + } + + /** + * @brief A property does not have a memory address. + */ + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "member has no address"); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + + /** + * @brief Preparing to write a property consists of preparing to call the setter method + * then pushing the index arguments. + */ + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + if (idxProp.setProp != null) { + if (!idxProp.setProp.IsFuncTrivial (scg)) { + for (int i = indices.Length; -- i >= 0;) { + indices[i] = scg.Trivialize (indices[i], errorAt); + } + } + this.setProp = GetIdxPropMeth (idxProp.setProp); + this.setProp.CallPre (scg, errorAt); + for (int i = 0; i < indices.Length; i ++) { + indices[i].PushVal (scg, errorAt, argTypes[i]); + } + } else { + // read-only property + scg.ErrorMsg (errorAt, "member not writable"); + } + } + + /** + * @brief Finishing writing a property consists of finishing the call to the setter method + * now that the value to be written has been pushed by our caller. + */ + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + if (idxProp.setProp != null) { + this.setProp.CallPost (scg, errorAt); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + } + + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + // if no getter, reading would throw an error, so doesn't really matter what we say + if (idxProp.getProp == null) return true; + + // assume interface methods are always non-trivial because we don't know anything about the actual implementation + if (baseRVal.type is TokenTypeSDTypeInterface) return false; + + // accessing it in any way can't be trivial if reading the pointer isn't trivial + if (!baseRVal.IsReadTrivial (scg, readAt)) return false; + + // likewise with the indices + foreach (CompValu idx in indices) { + if (!idx.IsReadTrivial (scg, readAt)) return false; + } + + // now the only way it can be non-trivial to read is if the getter() method itself is non-trivial. + return idxProp.getProp.IsFuncTrivial (scg); + } + + /** + * @brief Get how to call the getter or setter method. + */ + private CompValu GetIdxPropMeth (TokenDeclVar meth) + { + if (baseRVal.type is TokenTypeSDTypeClass) { + return new CompValuInstMember (meth, baseRVal, false); + } + return new CompValuIntfMember (meth, baseRVal); + } + } + + // This represents the type and location of an internally-defined function + // that a script can call + public class CompValuInline : CompValu { + public TokenDeclInline declInline; + + public CompValuInline (TokenDeclInline declInline) : base (declInline.GetDelType ()) + { + this.declInline = declInline; + } + + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + } + + // The value is the entrypoint of a script-defined type's interface method combined with + // the pointer used to access the method. Thus there is one of these per call site. + // They also handle accessing interface properties. + public class CompValuIntfMember : CompValu { + private TokenDeclVar declVar; + private CompValu baseRVal; + + public CompValuIntfMember (TokenDeclVar declVar, CompValu baseRVal) : base (declVar.type) + { + if (this.type == null) throw new Exception ("interface member type is null"); + this.declVar = declVar; // which element of the baseRVal vector to be accessed + this.baseRVal = baseRVal; // the vector of delegates implementing the interface + } + + /** + * @brief Reading a method's value means getting a delegate to that method. + * Reading a property's value means calling the getter method for that property. + */ + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + if (declVar.retType != null) { + baseRVal.PushVal (scg, errorAt); // push pointer to delegate array on stack + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, declVar.vTableIndex); // select which delegate to access + scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (Delegate)); // push delegate on stack + scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // cast to correct delegate class + } else if (declVar.getProp != null) { + CompValu getProp = new CompValuIntfMember (declVar.getProp, baseRVal); + getProp.CallPre (scg, errorAt); // reading property, call its getter + getProp.CallPost (scg, errorAt); // ... with no arguments + } else { + scg.ErrorMsg (errorAt, "member not readable"); + scg.PushDefaultValue (declVar.type); + } + } + + /** + * @brief Can't get the address of either a method or a property. + */ + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "member has no address"); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + + /** + * @brief Can't write a method. + * For property, it means calling the setter method for that property. + */ + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + if (declVar.setProp == null) { + // read-only property + scg.ErrorMsg (errorAt, "member not writable"); + } + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + if (declVar.setProp != null) { + CompValu setProp = new CompValuIntfMember (declVar.setProp, baseRVal); + EmitPopPostProp (scg, errorAt, declVar.type, setProp); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + } + + /** + * @brief Reading a method (ie, it's delegate) is always trivial, it's just retrieving + * an element from the delegate array that make up the interface object. + * + * Reading a property is always non-trivial because we don't know which implementation + * the interface is pointing to, so we don't know if it's trivial or not, so assume + * the worst, ie, that it is non-trivial and might call CheckRun(). + * + * But all that assumes that locating the interface object in the first place is + * trivial, ie, baseRVal.PushVal() must not call CheckRun() either. + */ + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return baseRVal.IsReadTrivial (scg, readAt) && (declVar.getProp == null); + } + + /** + * @brief We just defer to the default CallPre() and CallPost() methods. + * They expect this.PushVal() to push a delegate to the method to be called. + * If this member is a method, our PushVal() will read the correct element + * of the iTable array and push it on the stack, ready for Invoke() to be + * called. If this member is a property, the only way it can be called is + * if the property is a delegate, in which case PushVal() will retrieve the + * delegate by calling the property's getter method. + */ + } + + // The value is the entrypoint of an internal instance method + // such as XMR_Array.index() + public class CompValuIntInstMeth : CompValu { + private TokenTypeSDTypeDelegate delType; + private CompValu baseRVal; + private MethodInfo methInfo; + + public CompValuIntInstMeth (TokenTypeSDTypeDelegate delType, CompValu baseRVal, MethodInfo methInfo) : base (delType) + { + this.delType = delType; + this.baseRVal = baseRVal; + this.methInfo = methInfo; + } + + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + // its value, ie, without applying the (arglist), is a delegate... + baseRVal.PushVal (scg, errorAt); + scg.ilGen.Emit (errorAt, OpCodes.Ldftn, methInfo); + scg.ilGen.Emit (errorAt, OpCodes.Newobj, delType.decl.GetConstructorInfo ()); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get ref to instance method"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into instance method"); + } + + public override void CallPre (ScriptCodeGen scg, Token errorAt) + { + // internal instance methods are always trivial so never need a CallLabel. + baseRVal.PushVal (scg, errorAt); + } + public override void CallPost (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, methInfo); + } + } + + // The value is fetched by calling an internal instance method + // such as XMR_Array.count + public class CompValuIntInstROProp : CompValu { + private CompValu baseRVal; + private MethodInfo methInfo; + + public CompValuIntInstROProp (TokenType valType, CompValu baseRVal, MethodInfo methInfo) : base (valType) + { + this.baseRVal = baseRVal; + this.methInfo = methInfo; + } + + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + baseRVal.PushVal (scg, errorAt); + scg.ilGen.Emit (errorAt, OpCodes.Call, methInfo); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot get ref to read-only property"); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot store into read-only property"); + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + } + + // The value is in a member of a script-defined type class instance. + // field: value is in one of the arrays contained within XMRSDTypeClObj.instVars + // method: value is a delegate; can be called + // property: reading and writing is via a method call + public class CompValuInstMember : CompValu { + private static readonly FieldInfo instVarsFieldInfo = typeof (XMRSDTypeClObj).GetField ("instVars"); + private static readonly FieldInfo vTableFieldInfo = typeof (XMRSDTypeClObj).GetField ("sdtcVTable"); + + private TokenDeclVar declVar; // member being accessed + private CompValu baseRVal; // pointer to particular object instance + private bool ignoreVirt; // ignore virtual attribute; use declVar's non-virtual method/property + + public CompValuInstMember (TokenDeclVar declVar, CompValu baseRVal, bool ignoreVirt) : base (declVar.type) + { + this.declVar = declVar; + this.baseRVal = baseRVal; + this.ignoreVirt = ignoreVirt; + } + + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + if (declVar.retType != null) { + // a method's value, ie, without applying the (arglist), is a delegate... + PushValMethod (scg, errorAt); + } else if (declVar.vTableArray != null) { + // a field's value is its XMRSDTypeClObj.instVars array element + baseRVal.PushVal (scg, errorAt); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, instVarsFieldInfo); + EmitFieldPushVal (scg, errorAt, declVar); + } else if (declVar.getProp != null) { + // a property's value is calling its get method with no arguments + CompValu getProp = new CompValuInstMember (declVar.getProp, baseRVal, ignoreVirt); + getProp.CallPre (scg, errorAt); + getProp.CallPost (scg, errorAt); + } else { + // write-only property + scg.ErrorMsg (errorAt, "member not readable"); + scg.PushDefaultValue (declVar.type); + } + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + if (declVar.vTableArray != null) { + // a field's value is its XMRSDTypeClObj.instVars array element + baseRVal.PushVal (scg, errorAt); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, instVarsFieldInfo); + EmitFieldPushRef (scg, errorAt, declVar); + } else { + scg.ErrorMsg (errorAt, "member has no address"); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + } + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + if (declVar.vTableArray != null) { + // a field's value is its XMRSDTypeClObj.instVars array element + baseRVal.PushVal (scg, errorAt); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, instVarsFieldInfo); + EmitFieldPopPre (scg, errorAt, declVar); + } else if (declVar.setProp == null) { + // read-only property + scg.ErrorMsg (errorAt, "member not writable"); + } + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + if (declVar.vTableArray != null) { + EmitFieldPopPost (scg, errorAt, declVar); + } else if (declVar.setProp != null) { + CompValu setProp = new CompValuInstMember (declVar.setProp, baseRVal, ignoreVirt); + EmitPopPostProp (scg, errorAt, declVar.type, setProp); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + } + + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + // accessing it in any way can't be trivial if reading the pointer isn't trivial. + // this also handles strict right-to-left mode detection as the side-effect can + // only apply to the pointer (it can't change which field or method we access). + if (!baseRVal.IsReadTrivial (scg, readAt)) return false; + + // now the only way it can be non-trivial to read is if it is a property and the + // getter() method is non-trivial. reading a method means getting a delegate + // which is always trivial, and reading a simple field is always trivial, ie, no + // CheckRun() call can possibly be involved. + if (declVar.retType != null) { + // a method's value, ie, without applying the (arglist), is a delegate... + return true; + } + if (declVar.vTableArray != null) { + // a field's value is its XMRSDTypeClObj.instVars array element + return true; + } + if (declVar.getProp != null) { + // a property's value is calling its get method with no arguments + return declVar.getProp.IsFuncTrivial (scg); + } + + // write-only property + return true; + } + + public override void CallPre (ScriptCodeGen scg, Token errorAt) + { + if (declVar.retType != null) { + CallPreMethod (scg, errorAt); + } else { + base.CallPre (scg, errorAt); + } + } + public override void CallPost (ScriptCodeGen scg, Token errorAt) + { + if (declVar.retType != null) { + CallPostMethod (scg, errorAt); + } else { + base.CallPost (scg, errorAt); + } + } + + /** + * @brief A PushVal() for a method means to push a delegate for the method on the stack. + */ + private void PushValMethod (ScriptCodeGen scg, Token errorAt) + { + if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) throw new Exception ("dont use for statics"); + + if (ignoreVirt || (declVar.vTableIndex < 0)) { + + /* + * Non-virtual instance method, create a delegate that references the method. + */ + string dtn = type.ToString (); + + // delegateinstance = (signature)scriptinstance.GetScriptMethodDelegate (methName, signature, arg0); + // where methName = .() + // signature = () + // arg0 = sdt istance (XMRSDTypeClObj) 'this' value + scg.PushXMRInst (); // [0] scriptinstance + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, declVar.ilGen.methName); // [1] method name + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, dtn); // [2] delegate type name + baseRVal.PushVal (scg, errorAt); // [3] sdtinstance + scg.ilGen.Emit (errorAt, OpCodes.Callvirt, gsmdMethodInfo); // [0] delegate instance + scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // [0] cast to correct delegate class + } else { + + /* + * Virtual instance method, get the delegate from the vtable. + */ + baseRVal.PushVal (scg, errorAt); // 'this' selecting the instance + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, vTableFieldInfo); // get pointer to instance's vtable array + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, declVar.vTableIndex); // select vtable element + scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (Delegate)); // get delegate pointer = 'this' for 'Invoke()' + scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // cast to correct delegate class + } + } + + private void CallPreMethod (ScriptCodeGen scg, Token errorAt) + { + if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) throw new Exception ("dont use for statics"); + + if (!this.declVar.IsFuncTrivial (scg)) new ScriptCodeGen.CallLabel (scg, errorAt); + + if (ignoreVirt || (declVar.vTableIndex < 0)) { + baseRVal.PushVal (scg, errorAt); // 'this' being passed directly to method + } else { + baseRVal.PushVal (scg, errorAt); // 'this' selecting the instance + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, vTableFieldInfo); // get pointer to instance's vtable array + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, declVar.vTableIndex); // select vtable element + scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (Delegate)); // get delegate pointer = 'this' for 'Invoke()' + scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // cast to correct delegate class + } + } + private void CallPostMethod (ScriptCodeGen scg, Token errorAt) + { + if (ignoreVirt || (declVar.vTableIndex < 0)) { + // non-virt instance, just call function directly + scg.ilGen.Emit (errorAt, OpCodes.Call, declVar.ilGen); + } else { + // virtual, call via delegate Invoke(...) method + TokenTypeSDTypeDelegate ttd = (TokenTypeSDTypeDelegate)type; + MethodInfo invokeMethodInfo = ttd.decl.GetInvokerInfo (); + scg.ilGen.Emit (errorAt, OpCodes.Callvirt, invokeMethodInfo); + } + + if (!this.declVar.IsFuncTrivial (scg)) scg.openCallLabel = null; + } + } + + // The value is an integer constant + public class CompValuInteger : CompValu { + public int x; + + public CompValuInteger (TokenType type, int x) : base (type) + { + if (!(this.type is TokenTypeInt)) { + this.type = new TokenTypeInt (this.type); + } + this.x = x; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, x); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get constant's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into constant"); + } + } + + // The value is an element of a list + public class CompValuListEl : CompValu { + private static readonly MethodInfo getElementFromListMethodInfo = + typeof (CompValuListEl).GetMethod ("GetElementFromList", new Type[] { typeof (LSL_List), typeof (int) }); + + private CompValu theList; + private CompValu subscript; + + public CompValuListEl (TokenType type, CompValu theList, CompValu subscript) : base (type) + { + this.theList = theList; + this.subscript = subscript; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + theList.PushVal (scg, errorAt, new TokenTypeList (type)); + subscript.PushVal (scg, errorAt, new TokenTypeInt (type)); + scg.ilGen.Emit (errorAt, OpCodes.Call, getElementFromListMethodInfo); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get list element's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot store into list element"); + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + + public static object GetElementFromList (LSL_List lis, int idx) + { + object element = lis.Data[idx]; + if (element is LSL_Float) return TypeCast.EHArgUnwrapFloat (element); + if (element is LSL_Integer) return TypeCast.EHArgUnwrapInteger (element); + if (element is LSL_String) return TypeCast.EHArgUnwrapString (element); + if (element is OpenMetaverse.Quaternion) return TypeCast.EHArgUnwrapRotation (element); + if (element is OpenMetaverse.Vector3) return TypeCast.EHArgUnwrapVector (element); + return element; + } + } + + // The value is kept in a script-addressable local variable + public class CompValuLocalVar : CompValu { + private static int htpopseq = 0; + + private ScriptMyLocal localBuilder; + + public CompValuLocalVar (TokenType type, string name, ScriptCodeGen scg) : base (type) + { + if (type.ToHeapTrackerType () != null) { + this.localBuilder = scg.ilGen.DeclareLocal (type.ToHeapTrackerType (), name); + scg.PushXMRInst (); + scg.ilGen.Emit (type, OpCodes.Newobj, type.GetHeapTrackerCtor ()); + scg.ilGen.Emit (type, OpCodes.Stloc, localBuilder); + } else { + this.localBuilder = scg.ilGen.DeclareLocal (ToSysType (), name); + } + } + + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); + if (type.ToHeapTrackerType () != null) { + scg.ilGen.Emit (errorAt, OpCodes.Call, type.GetHeapTrackerPushMeth ()); + } + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + if (type.ToHeapTrackerType () != null) { + scg.ErrorMsg (errorAt, "can't take ref of heap-tracked type " + type.ToString ()); + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Ldloca, localBuilder); + } + } + + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + if (type.ToHeapTrackerType () != null) { + scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); + } + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + if (type.ToHeapTrackerType () != null) { + scg.ilGen.Emit (errorAt, OpCodes.Call, type.GetHeapTrackerPopMeth ()); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Stloc, localBuilder); + } + } + + public void Pop (ScriptCodeGen scg, Token errorAt) + { + if (type.ToHeapTrackerType () != null) { + /* + * Popping into a heap tracker wrapped local variable. + * First pop value into a temp var, then call the heap tracker's pop method. + */ + ScriptMyLocal htpop = scg.ilGen.DeclareLocal (type.ToSysType (), "htpop$" + (++ htpopseq).ToString ()); + scg.ilGen.Emit (errorAt, OpCodes.Stloc, htpop); + scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); + scg.ilGen.Emit (errorAt, OpCodes.Ldloc, htpop); + scg.ilGen.Emit (errorAt, OpCodes.Call, type.GetHeapTrackerPopMeth ()); + } else { + + /* + * Not a heap-tracked local var, just pop directly into it. + */ + scg.ilGen.Emit (errorAt, OpCodes.Stloc, localBuilder); + } + } + + // non-trivial because it needs to be copied into a temp + // in case the idiot does dumb-ass side effects tricks + // eg, (x = 0) + x + 2 + // should read old value of x not 0 + // but if 'xmroption norighttoleft;' in effect, + // we can read it in any order so reading a + // local variable is trivial. + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return readAt.nr2l; + } + } + + // The value is a null + public class CompValuNull : CompValu { + public CompValuNull (TokenType type) : base (type) { } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldnull); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get null's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into null"); + } + } + + // The value is a rotation + public class CompValuRot : CompValu { + public CompValu x; + public CompValu y; + public CompValu z; + public CompValu w; + + private static readonly ConstructorInfo lslRotConstructorInfo = + typeof (LSL_Rotation).GetConstructor (new Type[] { typeof (double), + typeof (double), + typeof (double), + typeof (double) }); + + public CompValuRot (TokenType type, CompValu x, CompValu y, CompValu z, CompValu w) : + base (type) + { + if (!(type is TokenTypeRot)) { + this.type = new TokenTypeRot (type); + } + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + this.x.PushVal (scg, errorAt, new TokenTypeFloat (this.x.type)); + this.y.PushVal (scg, errorAt, new TokenTypeFloat (this.y.type)); + this.z.PushVal (scg, errorAt, new TokenTypeFloat (this.z.type)); + this.w.PushVal (scg, errorAt, new TokenTypeFloat (this.w.type)); + scg.ilGen.Emit (errorAt, OpCodes.Newobj, lslRotConstructorInfo); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get constant's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into constant"); + } + + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + // the supplied values must be trivial because when we call their PushVal()s + // there will be stuff on the stack for all but the first PushVal() and so + // they would have a non-empty stack at their call label. + if (!this.w.IsReadTrivial (scg, readAt) || + !this.x.IsReadTrivial (scg, readAt) || + !this.y.IsReadTrivial (scg, readAt) || + !this.z.IsReadTrivial (scg, readAt)) { + throw new Exception ("rotation values must be trivial"); + } + + return true; + } + } + + // The value is in a static field of an internally defined struct/class + public class CompValuSField : CompValu { + public FieldInfo field; + + public CompValuSField (TokenType type, FieldInfo field) : base (type) + { + this.field = field; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + if ((field.Attributes & FieldAttributes.Literal) == 0) { + scg.ilGen.Emit (errorAt, OpCodes.Ldsfld, field); + return; + } + if (field.FieldType == typeof (LSL_Rotation)) { + LSL_Rotation rot = (LSL_Rotation)field.GetValue (null); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.x); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.y); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.z); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.s); + scg.ilGen.Emit (errorAt, OpCodes.Newobj, ScriptCodeGen.lslRotationConstructorInfo); + return; + } + if (field.FieldType == typeof (LSL_Vector)) { + LSL_Vector vec = (LSL_Vector)field.GetValue (null); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, vec.x); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, vec.y); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, vec.z); + scg.ilGen.Emit (errorAt, OpCodes.Newobj, ScriptCodeGen.lslRotationConstructorInfo); + return; + } + if (field.FieldType == typeof (string)) { + string str = (string)field.GetValue (null); + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, str); + return; + } + throw new Exception ("unsupported literal type " + field.FieldType.Name); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + if ((field.Attributes & FieldAttributes.Literal) != 0) { + throw new Exception ("can't write a constant"); + } + scg.ilGen.Emit (errorAt, OpCodes.Ldflda, field); + } + public override void PopPre (ScriptCodeGen scg, Token errorAt) + { + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + if ((field.Attributes & FieldAttributes.Literal) != 0) { + throw new Exception ("can't write a constant"); + } + scg.ilGen.Emit (errorAt, OpCodes.Stsfld, field); + } + + // non-trivial because it needs to be copied into a temp + // in case the idiot does dumb-ass side effects tricks + // eg, (x = 0) + x + 2 + // should read old value of x not 0 + // but if 'xmroption norighttoleft;' in effect, + // we can read it in any order so reading a + // local variable is trivial. + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + return readAt.nr2l; + } + } + + // The value is a character within a string + public class CompValuStrChr : CompValu { + private static readonly MethodInfo getCharFromStringMethodInfo = + typeof (CompValuStrChr).GetMethod ("GetCharFromString", new Type[] { typeof (string), typeof (int) }); + + private CompValu theString; + private CompValu subscript; + + public CompValuStrChr (TokenType type, CompValu theString, CompValu subscript) : base (type) + { + this.theString = theString; + this.subscript = subscript; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + theString.PushVal (scg, errorAt, new TokenTypeStr (type)); + subscript.PushVal (scg, errorAt, new TokenTypeInt (type)); + scg.ilGen.Emit (errorAt, OpCodes.Call, getCharFromStringMethodInfo); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get string character's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + scg.ErrorMsg (errorAt, "cannot store into string character"); + scg.ilGen.Emit (errorAt, OpCodes.Pop); + } + + public static char GetCharFromString (string s, int i) + { + return s[i]; + } + } + + // The value is a key or string constant + public class CompValuString : CompValu { + public string x; + + public CompValuString (TokenType type, string x) : base (type) + { + if (!(type is TokenTypeKey) && !(this.type is TokenTypeStr)) { + throw new Exception ("bad type " + type.ToString ()); + } + this.x = x; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, x); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get constant's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into constant"); + } + } + + // The value is kept in a temp local variable + public class CompValuTemp : CompValu { + public ScriptMyLocal localBuilder; + + public CompValuTemp (TokenType type, ScriptCodeGen scg) : base (type) + { + string name = "tmp$" + (++ scg.tempCompValuNum); + this.localBuilder = scg.ilGen.DeclareLocal (ToSysType(), name); + } + protected CompValuTemp (TokenType type) : base (type) { } // CompValuVoid uses this + + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldloca, localBuilder); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Stloc, localBuilder); + } + public void Pop (ScriptCodeGen scg, Token errorAt, TokenType stackType) + { + TypeCast.CastTopOfStack (scg, errorAt, stackType, this.type, false); + this.PopPost (scg, errorAt); // in case PopPost() overridden eg by CompValuVoid + } + public void Pop (ScriptCodeGen scg, Token errorAt) + { + this.PopPost (scg, errorAt); // in case PopPost() overridden eg by CompValuVoid + } + } + + // The value is a vector + public class CompValuVec : CompValu { + public CompValu x; + public CompValu y; + public CompValu z; + + private static readonly ConstructorInfo lslVecConstructorInfo = + typeof (LSL_Vector).GetConstructor (new Type[] { typeof (double), + typeof (double), + typeof (double) }); + + public CompValuVec (TokenType type, CompValu x, CompValu y, CompValu z) : base (type) + { + if (!(type is TokenTypeVec)) { + this.type = new TokenTypeVec (type); + } + this.x = x; + this.y = y; + this.z = z; + } + public override void PushVal (ScriptCodeGen scg, Token errorAt) + { + this.x.PushVal (scg, errorAt, new TokenTypeFloat (this.x.type)); + this.y.PushVal (scg, errorAt, new TokenTypeFloat (this.y.type)); + this.z.PushVal (scg, errorAt, new TokenTypeFloat (this.z.type)); + scg.ilGen.Emit (errorAt, OpCodes.Newobj, lslVecConstructorInfo); + } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get constant's address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot store into constant"); + } + + public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) + { + // the supplied values must be trivial because when we call their PushVal()s + // there will be stuff on the stack for all but the first PushVal() and so + // they would have a non-empty stack at their call label. + if (!this.x.IsReadTrivial (scg, readAt) || + !this.y.IsReadTrivial (scg, readAt) || + !this.z.IsReadTrivial (scg, readAt)) { + throw new Exception ("vector values must be trivial"); + } + + return true; + } + } + + // Used to indicate value will be discarded (eg, where to put return value from a call) + public class CompValuVoid : CompValuTemp { + public CompValuVoid (Token token) : base ((token is TokenTypeVoid) ? (TokenTypeVoid)token : new TokenTypeVoid (token)) + { } + public override void PushVal (ScriptCodeGen scg, Token errorAt) { } + public override void PushRef (ScriptCodeGen scg, Token errorAt) + { + throw new Exception ("cannot get void address"); + } + public override void PopPost (ScriptCodeGen scg, Token errorAt) { } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompile.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompile.cs new file mode 100644 index 0000000..017d2c5 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptCompile.cs @@ -0,0 +1,216 @@ +/* + * 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. + */ + +/** + * @brief Compile a script to produce a ScriptObjCode object + */ + +using System; +using System.IO; +using System.IO.Compression; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public partial class XMRInstance + { + /** + * @brief Compile a script to produce a ScriptObjCode object + * @returns object code pointer or null if compile error + * also can throw compile error exception + */ + public ScriptObjCode Compile () + { + bool oldObjFile = false; + Stream objFileStream = null; + StreamWriter asmFileWriter = null; + string envar = null; + string sourceHash = null; + TextWriter saveSource = null; + + string asmFileName = GetScriptFileName (m_ScriptObjCodeKey + ".xmrasm"); + string lslFileName = GetScriptFileName (m_ScriptObjCodeKey + ".lsl"); + string objFileName = GetScriptFileName (m_ScriptObjCodeKey + ".xmrobj"); + string tmpFileName = GetScriptFileName (m_ScriptObjCodeKey + ".xmrtmp"); + + /* + * If we already have an object file, don't bother compiling. + */ + if (!m_ForceRecomp && File.Exists (objFileName)) { + objFileStream = File.OpenRead (objFileName); + oldObjFile = true; + } else { + + /* + * If source file empty, try to read from asset server. + */ + if (EmptySource (m_SourceCode)) { + m_SourceCode = FetchSource (m_CameFrom); + } + + /* + * Maybe write script source to a file for debugging. + */ + envar = Environment.GetEnvironmentVariable ("MMRScriptCompileSaveSource"); + if ((envar != null) && ((envar[0] & 1) != 0)) { + m_log.Debug ("[XMREngine]: MMRScriptCompileSaveSource: saving to " + lslFileName); + saveSource = File.CreateText (lslFileName); + } + + /* + * Parse source string into tokens. + */ + TokenBegin tokenBegin; + try { + tokenBegin = TokenBegin.Construct(m_CameFrom, saveSource, ErrorHandler, m_SourceCode, out sourceHash); + } finally { + if (saveSource != null) saveSource.Close (); + } + if (tokenBegin == null) { + m_log.Debug ("[XMREngine]: parsing errors on " + m_ScriptObjCodeKey); + return null; + } + + /* + * Create object file one way or another. + */ + try { + objFileStream = File.Create (tmpFileName); + + /* + * Create abstract syntax tree from raw tokens. + */ + TokenScript tokenScript = ScriptReduce.Reduce(tokenBegin); + if (tokenScript == null) { + m_log.Warn ("[XMREngine]: reduction errors on " + m_ScriptObjCodeKey + " (" + m_CameFrom + ")"); + PrintCompilerErrors (); + return null; + } + + /* + * Compile abstract syntax tree to write object file. + */ + BinaryWriter objFileWriter = new BinaryWriter (objFileStream); + bool ok = ScriptCodeGen.CodeGen(tokenScript, objFileWriter, sourceHash); + if (!ok) { + m_log.Warn ("[XMREngine]: compile error on " + m_ScriptObjCodeKey + " (" + m_CameFrom + ")"); + PrintCompilerErrors (); + objFileStream.Close (); + return null; + } + objFileStream.Close (); + + /* + * File has been completely written. + * If there is an old one laying around, delete it now. + * Then re-open the new file for reading from the beginning. + */ + if (File.Exists (objFileName)) { + File.Replace (tmpFileName, objFileName, null); + } else { + File.Move (tmpFileName, objFileName); + } + objFileStream = File.OpenRead (objFileName); + } finally { + + /* + * In case something went wrong writing temp file, delete it. + */ + try { + File.Delete (tmpFileName); + } catch { + } + } + + /* + * Since we just wrote the .xmrobj file, maybe save disassembly. + */ + envar = Environment.GetEnvironmentVariable ("MMRScriptCompileSaveILGen"); + if ((envar != null) && ((envar[0] & 1) != 0)) { + m_log.Debug ("[XMREngine]: MMRScriptCompileSaveILGen: saving to " + asmFileName); + asmFileWriter = File.CreateText (asmFileName); + } + } + + /* + * Read object file to create ScriptObjCode object. + * Maybe also write disassembly to a file for debugging. + */ + BinaryReader objFileReader = new BinaryReader (objFileStream); + ScriptObjCode scriptObjCode = null; + try { + scriptObjCode = new ScriptObjCode (objFileReader, asmFileWriter, null); + if (scriptObjCode != null) { + scriptObjCode.fileDateUtc = File.GetLastWriteTimeUtc (objFileName); + } + } finally { + objFileReader.Close (); + if (asmFileWriter != null) { + asmFileWriter.Flush (); + asmFileWriter.Close (); + } + } + + /* + * Maybe an old object file has reached its expiration date. + */ + if (oldObjFile && (scriptObjCode != null) && scriptObjCode.IsExpired ()) { + m_log.Debug ("[XMREngine]: expiration reached on " + m_ScriptObjCodeKey + ", reloading"); + m_ForceRecomp = true; + scriptObjCode = Compile (); + } + + return scriptObjCode; + } + + private void PrintCompilerErrors () + { + m_log.Info ("[XMREngine]: - " + m_Part.GetWorldPosition () + " " + m_DescName); + foreach (string error in m_CompilerErrors) { + m_log.Info ("[XMREngine]: - " + error); + } + } + + /** + * @brief Check for empty source, allowing for a first line of //... script engine selector. + */ + public static bool EmptySource (string source) + { + int len = source.Length; + bool skipeol = false; + for (int i = 0; i < len; i ++) { + char c = source[i]; + skipeol &= c != '\n'; + skipeol |= (c == '/') && (i + 1 < len) && (source[i+1] == '/'); + if ((c > ' ') && !skipeol) return false; + } + return true; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptConsts.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptConsts.cs new file mode 100644 index 0000000..4cbb19c --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptConsts.cs @@ -0,0 +1,250 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Reflection; +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; + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public class ScriptConst { + + public static Dictionary scriptConstants = Init (); + + /** + * @brief look up the value of a given built-in constant. + * @param name = name of constant + * @returns null: no constant by that name defined + * else: pointer to ScriptConst struct + */ + public static ScriptConst Lookup (string name) + { + ScriptConst sc; + if (!scriptConstants.TryGetValue (name, out sc)) sc = null; + return sc; + } + + private static Dictionary Init () + { + Dictionary sc = new Dictionary (); + + /* + * For every event code, define XMREVENTCODE_ and XMREVENTMASKn_ symbols. + */ + for (int i = 0; i < 64; i ++) { + try { + string s = ((ScriptEventCode)i).ToString (); + if ((s.Length > 0) && (s[0] >= 'a') && (s[0] <= 'z')) { + new ScriptConst (sc, + "XMREVENTCODE_" + s, + new CompValuInteger (new TokenTypeInt (null), i)); + int n = i / 32 + 1; + int m = 1 << (i % 32); + new ScriptConst (sc, + "XMREVENTMASK" + n + "_" + s, + new CompValuInteger (new TokenTypeInt (null), m)); + } + } catch { } + } + + /* + * Also get all the constants from XMRInstAbstract and ScriptBaseClass etc as well. + */ + for (Type t = typeof (XMRInstAbstract); t != typeof (object); t = t.BaseType) { + AddInterfaceConstants (sc, t.GetFields ()); + } + + return sc; + } + + /** + * @brief Add all constants defined by the given interface. + */ + // this one accepts only upper-case named fields + public static void AddInterfaceConstants (Dictionary sc, FieldInfo[] allFields) + { + List ucfs = new List (allFields.Length); + foreach (FieldInfo f in allFields) { + string fieldName = f.Name; + int i; + for (i = fieldName.Length; -- i >= 0;) { + if ("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".IndexOf (fieldName[i]) < 0) break; + } + if (i < 0) ucfs.Add (f); + } + AddInterfaceConstants (sc, ucfs.GetEnumerator ()); + } + + // this one accepts all fields given to it + public static void AddInterfaceConstants (Dictionary sc, IEnumerator fields) + { + if (sc == null) sc = scriptConstants; + + for (fields.Reset (); fields.MoveNext ();) { + FieldInfo constField = fields.Current; + Type fieldType = constField.FieldType; + CompValu cv; + + /* + * The location of a simple number is the number itself. + * Access to the value gets compiled as an ldc instruction. + */ + if (fieldType == typeof (double)) { + cv = new CompValuFloat (new TokenTypeFloat (null), + (double)(double)constField.GetValue (null)); + } else if (fieldType == typeof (int)) { + cv = new CompValuInteger (new TokenTypeInt (null), + (int)constField.GetValue (null)); + } else if (fieldType == typeof (LSL_Integer)) { + cv = new CompValuInteger (new TokenTypeInt (null), + ((LSL_Integer)constField.GetValue (null)).value); + } + + /* + * The location of a string is the string itself. + * Access to the value gets compiled as an ldstr instruction. + */ + else if (fieldType == typeof (string)) { + cv = new CompValuString (new TokenTypeStr (null), + (string)constField.GetValue (null)); + } else if (fieldType == typeof (LSL_String)) { + cv = new CompValuString (new TokenTypeStr (null), + (string)(LSL_String)constField.GetValue (null)); + } + + /* + * The location of everything else (objects) is the static field in the interface definition. + * Access to the value gets compiled as an ldsfld instruction. + */ + else { + cv = new CompValuSField (TokenType.FromSysType (null, fieldType), constField); + } + + /* + * Add to dictionary. + */ + new ScriptConst (sc, constField.Name, cv); + } + } + + /** + * @brief Add arbitrary constant available to script compilation. + * CAUTION: These values get compiled-in to a script and must not + * change over time as previously compiled scripts will + * still have the old values. + */ + public static ScriptConst AddConstant (string name, object value) + { + CompValu cv = null; + + if (value is char) { + cv = new CompValuChar (new TokenTypeChar (null), (char)value); + } + if (value is double) { + cv = new CompValuFloat (new TokenTypeFloat (null), (double)(double)value); + } + if (value is float) { + cv = new CompValuFloat (new TokenTypeFloat (null), (double)(float)value); + } + if (value is int) { + cv = new CompValuInteger (new TokenTypeInt (null), (int)value); + } + if (value is string) { + cv = new CompValuString (new TokenTypeStr (null), (string)value); + } + + if (value is LSL_Float) { + cv = new CompValuFloat (new TokenTypeFloat (null), (double)((LSL_Float)value).value); + } + if (value is LSL_Integer) { + cv = new CompValuInteger (new TokenTypeInt (null), ((LSL_Integer)value).value); + } + if (value is LSL_Rotation) { + LSL_Rotation r = (LSL_Rotation)value; + CompValu x = new CompValuFloat (new TokenTypeFloat (null), r.x); + CompValu y = new CompValuFloat (new TokenTypeFloat (null), r.y); + CompValu z = new CompValuFloat (new TokenTypeFloat (null), r.z); + CompValu s = new CompValuFloat (new TokenTypeFloat (null), r.s); + cv = new CompValuRot (new TokenTypeRot (null), x, y, z, s); + } + if (value is LSL_String) { + cv = new CompValuString (new TokenTypeStr (null), (string)(LSL_String)value); + } + if (value is LSL_Vector) { + LSL_Vector v = (LSL_Vector)value; + CompValu x = new CompValuFloat (new TokenTypeFloat (null), v.x); + CompValu y = new CompValuFloat (new TokenTypeFloat (null), v.y); + CompValu z = new CompValuFloat (new TokenTypeFloat (null), v.z); + cv = new CompValuVec (new TokenTypeVec (null), x, y, z); + } + + if (value is OpenMetaverse.Quaternion) { + OpenMetaverse.Quaternion r = (OpenMetaverse.Quaternion)value; + CompValu x = new CompValuFloat (new TokenTypeFloat (null), r.X); + CompValu y = new CompValuFloat (new TokenTypeFloat (null), r.Y); + CompValu z = new CompValuFloat (new TokenTypeFloat (null), r.Z); + CompValu s = new CompValuFloat (new TokenTypeFloat (null), r.W); + cv = new CompValuRot (new TokenTypeRot (null), x, y, z, s); + } + if (value is OpenMetaverse.UUID) { + cv = new CompValuString (new TokenTypeKey (null), value.ToString ()); + } + if (value is OpenMetaverse.Vector3) { + OpenMetaverse.Vector3 v = (OpenMetaverse.Vector3)value; + CompValu x = new CompValuFloat (new TokenTypeFloat (null), v.X); + CompValu y = new CompValuFloat (new TokenTypeFloat (null), v.Y); + CompValu z = new CompValuFloat (new TokenTypeFloat (null), v.Z); + cv = new CompValuVec (new TokenTypeVec (null), x, y, z); + } + + if (cv == null) throw new Exception ("bad type " + value.GetType ().Name); + return new ScriptConst (scriptConstants, name, cv); + } + + /* + * Instance variables + */ + public string name; + public CompValu rVal; + + private ScriptConst (Dictionary lc, string name, CompValu rVal) + { + lc.Add (name, this); + this.name = name; + this.rVal = rVal; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptEventCode.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptEventCode.cs new file mode 100644 index 0000000..8e8b755 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptEventCode.cs @@ -0,0 +1,95 @@ +/* + * 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. + */ + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + /** + * @brief List of event codes that can be passed to StartEventHandler(). + * Must have same name as corresponding event handler name, so + * the compiler will know what column in the seht to put the + * event handler entrypoint in. + * + * Also, ScriptConst.Init() builds symbols of name XMREVENTCODE_ + * and XMREVENTMASK_ with the values and masks of all symbols + * in range 0..63 that begin with a lower-case letter for scripts to + * reference. + */ + public enum ScriptEventCode : int { + + // used by XMRInstance to indicate no event being processed + None = -1, + + // must be bit numbers of equivalent values in ... + // OpenSim.Region.ScriptEngine.Shared.ScriptBase.scriptEvents + // ... so they can be passed to m_Part.SetScriptEvents(). + attach = 0, + state_exit = 1, + timer = 2, + touch = 3, + collision = 4, + collision_end = 5, + collision_start = 6, + control = 7, + dataserver = 8, + email = 9, + http_response = 10, + land_collision = 11, + land_collision_end = 12, + land_collision_start = 13, + at_target = 14, + listen = 15, + money = 16, + moving_end = 17, + moving_start = 18, + not_at_rot_target = 19, + not_at_target = 20, + touch_start = 21, + object_rez = 22, + remote_data = 23, + at_rot_target = 24, + transaction_result = 25, + run_time_permissions = 28, + touch_end = 29, + state_entry = 30, + + // events not passed to m_Part.SetScriptEvents(). + changed = 33, + link_message = 34, + no_sensor = 35, + on_rez = 36, + sensor = 37, + http_request = 38, + + path_update = 40, + + // XMRE specific + region_cross = 63, + + // marks highest numbered event, ie, number of columns in seht. + Size = 64 + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptInlines.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptInlines.cs new file mode 100644 index 0000000..dc00001 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptInlines.cs @@ -0,0 +1,664 @@ +/* + * 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 System; +using System.Collections.Generic; +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; + +/** + * @brief Generate code for the backend API calls. + */ +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public abstract class TokenDeclInline : TokenDeclVar { + public static VarDict inlineFunctions = CreateDictionary (); + + public abstract void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args); + + private static string[] noCheckRuns; + private static string[] keyReturns; + + protected bool isTaggedCallsCheckRun; + + /** + * @brief Create a dictionary of inline backend API functions. + */ + private static VarDict CreateDictionary () + { + /* + * For those listed in noCheckRun, we just generate the call (simple computations). + * For all others, we generate the call then a call to CheckRun(). + */ + noCheckRuns = new string[] { + "llBase64ToString", + "llCSV2List", + "llDeleteSubList", + "llDeleteSubString", + "llDumpList2String", + "llEscapeURL", + "llEuler2Rot", + "llGetListEntryType", + "llGetListLength", + "llGetSubString", + "llGetUnixTime", + "llInsertString", + "llList2CSV", + "llList2Float", + "llList2Integer", + "llList2Key", + "llList2List", + "llList2ListStrided", + "llList2Rot", + "llList2String", + "llList2Vector", + "llListFindList", + "llListInsertList", + "llListRandomize", + "llListReplaceList", + "llListSort", + "llListStatistics", + "llMD5String", + "llParseString2List", + "llParseStringKeepNulls", + "llRot2Euler", + "llStringLength", + "llStringToBase64", + "llStringTrim", + "llSubStringIndex", + "llUnescapeURL" + }; + + /* + * These functions really return a 'key' even though we see them as + * returning 'string' because OpenSim has key and string as same type. + */ + keyReturns = new string[] { + "llAvatarOnLinkSitTarget", + "llAvatarOnSitTarget", + "llDetectedKey", + "llDetectedOwner", + "llGenerateKey", + "llGetCreator", + "llGetInventoryCreator", + "llGetInventoryKey", + "llGetKey", + "llGetLandOwnerAt", + "llGetLinkKey", + "llGetNotecardLine", + "llGetNumberOfNotecardLines", + "llGetOwner", + "llGetOwnerKey", + "llGetPermissionsKey", + "llHTTPRequest", + "llList2Key", + "llRequestAgentData", + "llRequestDisplayName", + "llRequestInventoryData", + "llRequestSecureURL", + "llRequestSimulatorData", + "llRequestURL", + "llRequestUsername", + "llSendRemoteData", + "llTransferLindenDollars" + }; + + VarDict ifd = new VarDict (false); + + Type[] oneDoub = new Type[] { typeof (double) }; + Type[] twoDoubs = new Type[] { typeof (double), typeof (double) }; + + /* + * Mono generates an FPU instruction for many math calls. + */ + new TokenDeclInline_LLAbs (ifd); + new TokenDeclInline_Math (ifd, "llAcos(float)", "Acos", oneDoub); + new TokenDeclInline_Math (ifd, "llAsin(float)", "Asin", oneDoub); + new TokenDeclInline_Math (ifd, "llAtan2(float,float)", "Atan2", twoDoubs); + new TokenDeclInline_Math (ifd, "llCos(float)", "Cos", oneDoub); + new TokenDeclInline_Math (ifd, "llFabs(float)", "Abs", oneDoub); + new TokenDeclInline_Math (ifd, "llLog(float)", "Log", oneDoub); + new TokenDeclInline_Math (ifd, "llLog10(float)", "Log10", oneDoub); + new TokenDeclInline_Math (ifd, "llPow(float,float)", "Pow", twoDoubs); + new TokenDeclInline_LLRound (ifd); + new TokenDeclInline_Math (ifd, "llSin(float)", "Sin", oneDoub); + new TokenDeclInline_Math (ifd, "llSqrt(float)", "Sqrt", oneDoub); + new TokenDeclInline_Math (ifd, "llTan(float)", "Tan", oneDoub); + + /* + * Something weird about the code generation for these calls, so they all have their own handwritten code generators. + */ + new TokenDeclInline_GetFreeMemory (ifd); + new TokenDeclInline_GetUsedMemory (ifd); + + /* + * These are all the xmr...() calls directly in XMRInstAbstract. + * Includes the calls from ScriptBaseClass that has all the stubs + * which convert XMRInstAbstract to the various _Api contexts. + */ + MethodInfo[] absmeths = typeof (XMRInstAbstract).GetMethods (); + AddInterfaceMethods (ifd, absmeths, null); + + return ifd; + } + + /** + * @brief Add API functions from the given interface to list of built-in functions. + * Only functions beginning with a lower-case letter are entered, all others ignored. + * @param ifd = internal function dictionary to add them to + * @param ifaceMethods = list of API functions + * @param acf = which field in XMRInstanceSuperType holds method's 'this' pointer + */ + // this one accepts only names beginning with a lower-case letter + public static void AddInterfaceMethods (VarDict ifd, MethodInfo[] ifaceMethods, FieldInfo acf) + { + List lcms = new List (ifaceMethods.Length); + foreach (MethodInfo meth in ifaceMethods) + { + string name = meth.Name; + if ((name[0] >= 'a') && (name[0] <= 'z')) { + lcms.Add (meth); + } + } + AddInterfaceMethods (ifd, lcms.GetEnumerator (), acf); + } + + // this one accepts all methods given to it + public static void AddInterfaceMethods (VarDict ifd, IEnumerator ifaceMethods, FieldInfo acf) + { + if (ifd == null) ifd = inlineFunctions; + + for (ifaceMethods.Reset (); ifaceMethods.MoveNext ();) { + MethodInfo ifaceMethod = ifaceMethods.Current; + string key = ifaceMethod.Name; + + try { + /* + * See if we will generate a call to CheckRun() right + * after we generate a call to the function. + * If function begins with xmr, assume we will not call CheckRun() + * Otherwise, assume we will call CheckRun() + */ + bool dcr = !key.StartsWith ("xmr"); + foreach (string ncr in noCheckRuns) { + if (ncr == key) { + dcr = false; + break; + } + } + + /* + * Add function to dictionary. + */ + new TokenDeclInline_BEApi (ifd, dcr, ifaceMethod, acf); + } catch { + ///??? IGNORE ANY THAT FAIL - LIKE UNRECOGNIZED TYPE ???/// + ///??? and OVERLOADED NAMES ???/// + } + } + } + + /** + * @brief Add an inline function definition to the dictionary. + * @param ifd = dictionary to add inline definition to + * @param doCheckRun = true iff the generated code or the function itself can possibly call CheckRun() + * @param nameArgSig = inline function signature string, in form (,...) + * @param retType = return type, use TokenTypeVoid if no return value + */ + protected TokenDeclInline (VarDict ifd, + bool doCheckRun, + string nameArgSig, + TokenType retType) + : base (null, null, null) + { + this.retType = retType; + this.triviality = doCheckRun ? Triviality.complex : Triviality.trivial; + + int j = nameArgSig.IndexOf ('('); + this.name = new TokenName (null, nameArgSig.Substring (0, j ++)); + + this.argDecl = new TokenArgDecl (null); + if (nameArgSig[j] != ')') { + int i; + TokenName name; + TokenType type; + + for (i = j; nameArgSig[i] != ')'; i ++) { + if (nameArgSig[i] == ',') { + type = TokenType.FromLSLType (null, nameArgSig.Substring (j, i - j)); + name = new TokenName (null, "arg" + this.argDecl.varDict.Count); + this.argDecl.AddArg (type, name); + j = i + 1; + } + } + + type = TokenType.FromLSLType (null, nameArgSig.Substring (j, i - j)); + name = new TokenName (null, "arg" + this.argDecl.varDict.Count); + this.argDecl.AddArg (type, name); + } + + this.location = new CompValuInline (this); + if (ifd == null) ifd = inlineFunctions; + ifd.AddEntry (this); + } + + protected TokenDeclInline (VarDict ifd, + bool doCheckRun, + MethodInfo methInfo) + : base (null, null, null) + { + TokenType retType = TokenType.FromSysType (null, methInfo.ReturnType); + + this.isTaggedCallsCheckRun = IsTaggedCallsCheckRun (methInfo); + this.name = new TokenName (null, methInfo.Name); + this.retType = GetRetType (methInfo, retType); + this.argDecl = GetArgDecl (methInfo.GetParameters ()); + this.triviality = (doCheckRun || this.isTaggedCallsCheckRun) ? Triviality.complex : Triviality.trivial; + this.location = new CompValuInline (this); + + if (ifd == null) ifd = inlineFunctions; + ifd.AddEntry (this); + } + + private static TokenArgDecl GetArgDecl (ParameterInfo[] parameters) + { + TokenArgDecl argDecl = new TokenArgDecl (null); + foreach (ParameterInfo pi in parameters) { + TokenType type = TokenType.FromSysType (null, pi.ParameterType); + TokenName name = new TokenName (null, pi.Name); + argDecl.AddArg (type, name); + } + return argDecl; + } + + /** + * @brief The above code assumes all methods beginning with 'xmr' are trivial, ie, + * they do not call CheckRun() and also we do not generate a CheckRun() + * call after they return. So if an 'xmr' method does call CheckRun(), it + * must be tagged with attribute 'xmrMethodCallsCheckRunAttribute' so we know + * the method is not trivial. But in neither case do we emit our own call + * to CheckRun(), the 'xmr' method must do its own. We do however set up a + * call label before the call to the non-trivial 'xmr' method so when we are + * restoring the call stack, the restore will call directly in to the 'xmr' + * method without re-executing any code before the call to the 'xmr' method. + */ + private static bool IsTaggedCallsCheckRun (MethodInfo methInfo) + { + return (methInfo != null) && + Attribute.IsDefined (methInfo, typeof (xmrMethodCallsCheckRunAttribute)); + } + + /** + * @brief The dumbass OpenSim has key and string as the same type so non-ll + * methods must be tagged with xmrMethodReturnsKeyAttribute if we + * are to think they return a key type, otherwise we will think they + * return string. + */ + private static TokenType GetRetType (MethodInfo methInfo, TokenType retType) + { + if ((methInfo != null) && (retType != null) && (retType is TokenTypeStr)) { + if (Attribute.IsDefined (methInfo, typeof (xmrMethodReturnsKeyAttribute))) { + return ChangeToKeyType (retType); + } + + string mn = methInfo.Name; + foreach (string kr in keyReturns) { + if (kr == mn) return ChangeToKeyType (retType); + } + + } + return retType; + } + private static TokenType ChangeToKeyType (TokenType retType) + { + if (retType is TokenTypeLSLString) { + retType = new TokenTypeLSLKey (null); + } else { + retType = new TokenTypeKey (null); + } + return retType; + } + + public virtual MethodInfo GetMethodInfo () + { + return null; + } + + /** + * @brief Print out a list of all the built-in functions and constants. + */ + public delegate void WriteLine (string str); + public static void PrintBuiltins (bool inclNoisyTag, WriteLine writeLine) + { + writeLine ("\nBuilt-in functions:\n"); + SortedDictionary bifs = new SortedDictionary (); + foreach (TokenDeclVar bif in TokenDeclInline.inlineFunctions) { + bifs.Add (bif.fullName, (TokenDeclInline)bif); + } + foreach (TokenDeclInline bif in bifs.Values) { + char noisy = (!inclNoisyTag || !IsTaggedNoisy (bif.GetMethodInfo ())) ? ' ' : (bif.retType is TokenTypeVoid) ? 'N' : 'R'; + writeLine (noisy + " " + bif.retType.ToString ().PadLeft (8) + " " + bif.fullName); + } + if (inclNoisyTag) { + writeLine ("\nN - stub that writes name and arguments to stdout"); + writeLine ("R - stub that writes name and arguments to stdout then reads return value from stdin"); + writeLine (" format is: function_name : return_value"); + writeLine (" example: llKey2Name:\"Kunta Kinte\""); + } + + writeLine ("\nBuilt-in constants:\n"); + SortedDictionary scs = new SortedDictionary (); + int widest = 0; + foreach (ScriptConst sc in ScriptConst.scriptConstants.Values) { + if (widest < sc.name.Length) widest = sc.name.Length; + scs.Add (sc.name, sc); + } + foreach (ScriptConst sc in scs.Values) { + writeLine (" " + sc.rVal.type.ToString ().PadLeft (8) + " " + sc.name.PadRight (widest) + " = " + BuiltInConstVal (sc.rVal)); + } + } + + public static bool IsTaggedNoisy (MethodInfo methInfo) + { + return (methInfo != null) && Attribute.IsDefined (methInfo, typeof (xmrMethodIsNoisyAttribute)); + } + + public static string BuiltInConstVal (CompValu rVal) + { + if (rVal is CompValuInteger) { + int x = ((CompValuInteger)rVal).x; + return "0x" + x.ToString ("X8") + " = " + x.ToString ().PadLeft (11); + } + if (rVal is CompValuFloat) return ((CompValuFloat)rVal).x.ToString (); + if (rVal is CompValuString) { + StringBuilder sb = new StringBuilder (); + PrintParam (sb, ((CompValuString)rVal).x); + return sb.ToString (); + } + if (rVal is CompValuSField) { + FieldInfo fi = ((CompValuSField)rVal).field; + StringBuilder sb = new StringBuilder (); + PrintParam (sb, fi.GetValue (null)); + return sb.ToString (); + } + return rVal.ToString (); // just prints the type + } + + public static void PrintParam (StringBuilder sb, object p) + { + if (p == null) { + sb.Append ("null"); + } else if (p is LSL_List) { + sb.Append ('['); + object[] d = ((LSL_List)p).Data; + for (int i = 0; i < d.Length; i ++) { + if (i > 0) sb.Append (','); + PrintParam (sb, d[i]); + } + sb.Append (']'); + } else if (p is LSL_Rotation) { + LSL_Rotation r = (LSL_Rotation)p; + sb.Append ('<'); + sb.Append (r.x); + sb.Append (','); + sb.Append (r.y); + sb.Append (','); + sb.Append (r.z); + sb.Append (','); + sb.Append (r.s); + sb.Append ('>'); + } else if (p is LSL_String) { + PrintParamString (sb, (string)(LSL_String)p); + } else if (p is LSL_Vector) { + LSL_Vector v = (LSL_Vector)p; + sb.Append ('<'); + sb.Append (v.x); + sb.Append (','); + sb.Append (v.y); + sb.Append (','); + sb.Append (v.z); + sb.Append ('>'); + } else if (p is string) { + PrintParamString (sb, (string)p); + } else { + sb.Append (p.ToString ()); + } + } + + public static void PrintParamString (StringBuilder sb, string p) + { + sb.Append ('"'); + foreach (char c in p) { + if (c == '\b') { + sb.Append ("\\b"); + continue; + } + if (c == '\n') { + sb.Append ("\\n"); + continue; + } + if (c == '\r') { + sb.Append ("\\r"); + continue; + } + if (c == '\t') { + sb.Append ("\\t"); + continue; + } + if (c == '"') { + sb.Append ("\\\""); + continue; + } + if (c == '\\') { + sb.Append ("\\\\"); + continue; + } + sb.Append (c); + } + sb.Append ('"'); + } + } + + /** + * @brief Code generators... + * @param scg = script we are generating code for + * @param result = type/location for result (type matches function definition) + * @param args = type/location of arguments (types match function definition) + */ + + public class TokenDeclInline_LLAbs : TokenDeclInline { + public TokenDeclInline_LLAbs (VarDict ifd) + : base (ifd, false, "llAbs(integer)", new TokenTypeInt (null)) { } + + public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) + { + ScriptMyLabel itsPosLabel = scg.ilGen.DefineLabel ("llAbstemp"); + + args[0].PushVal (scg, errorAt); + scg.ilGen.Emit (errorAt, OpCodes.Dup); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Bge_S, itsPosLabel); + scg.ilGen.Emit (errorAt, OpCodes.Neg); + scg.ilGen.MarkLabel (itsPosLabel); + result.Pop (scg, errorAt, retType); + } + } + + public class TokenDeclInline_Math : TokenDeclInline { + private MethodInfo methInfo; + + public TokenDeclInline_Math (VarDict ifd, string sig, string name, Type[] args) + : base (ifd, false, sig, new TokenTypeFloat (null)) + { + methInfo = ScriptCodeGen.GetStaticMethod (typeof (System.Math), name, args); + } + + public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) + { + for (int i = 0; i < args.Length; i ++) { + args[i].PushVal (scg, errorAt, argDecl.types[i]); + } + scg.ilGen.Emit (errorAt, OpCodes.Call, methInfo); + result.Pop (scg, errorAt, retType); + } + } + + public class TokenDeclInline_LLRound : TokenDeclInline { + + private static MethodInfo roundMethInfo = ScriptCodeGen.GetStaticMethod (typeof (System.Math), "Round", + new Type[] { typeof (double), typeof (MidpointRounding) }); + + public TokenDeclInline_LLRound (VarDict ifd) + : base (ifd, false, "llRound(float)", new TokenTypeInt (null)) { } + + public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) + { + args[0].PushVal (scg, errorAt, new TokenTypeFloat (null)); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, (int)System.MidpointRounding.AwayFromZero); + scg.ilGen.Emit (errorAt, OpCodes.Call, roundMethInfo); + result.Pop (scg, errorAt, new TokenTypeFloat (null)); + } + } + + public class TokenDeclInline_GetFreeMemory : TokenDeclInline { + private static readonly MethodInfo getFreeMemMethInfo = typeof (XMRInstAbstract).GetMethod ("xmrHeapLeft", new Type[] { }); + + public TokenDeclInline_GetFreeMemory (VarDict ifd) + : base (ifd, false, "llGetFreeMemory()", new TokenTypeInt (null)) { } + + // appears as llGetFreeMemory() in script source code + // but actually calls xmrHeapLeft() + public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) + { + scg.PushXMRInst (); + scg.ilGen.Emit (errorAt, OpCodes.Call, getFreeMemMethInfo); + result.Pop (scg, errorAt, new TokenTypeInt (null)); + } + } + + public class TokenDeclInline_GetUsedMemory : TokenDeclInline { + private static readonly MethodInfo getUsedMemMethInfo = typeof (XMRInstAbstract).GetMethod ("xmrHeapUsed", new Type[] { }); + + public TokenDeclInline_GetUsedMemory (VarDict ifd) + : base (ifd, false, "llGetUsedMemory()", new TokenTypeInt (null)) { } + + // appears as llGetUsedMemory() in script source code + // but actually calls xmrHeapUsed() + public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) + { + scg.PushXMRInst (); + scg.ilGen.Emit (errorAt, OpCodes.Call, getUsedMemMethInfo); + result.Pop (scg, errorAt, new TokenTypeInt (null)); + } + } + + /** + * @brief Generate code for the usual ll...() functions. + */ + public class TokenDeclInline_BEApi : TokenDeclInline { + private static readonly MethodInfo fixLLParcelMediaQuery = ScriptCodeGen.GetStaticMethod + (typeof (XMRInstAbstract), "FixLLParcelMediaQuery", new Type[] { typeof (LSL_List) }); + + private static readonly MethodInfo fixLLParcelMediaCommandList = ScriptCodeGen.GetStaticMethod + (typeof (XMRInstAbstract), "FixLLParcelMediaCommandList", new Type[] { typeof (LSL_List) }); + + public bool doCheckRun; + private FieldInfo apiContextField; + private MethodInfo methInfo; + + /** + * @brief Constructor + * @param ifd = dictionary to add the function to + * @param dcr = append a call to CheckRun() + * @param methInfo = ll...() method to be called + */ + public TokenDeclInline_BEApi (VarDict ifd, bool dcr, MethodInfo methInfo, FieldInfo acf) + : base (ifd, dcr, methInfo) + { + this.methInfo = methInfo; + doCheckRun = dcr; + apiContextField = acf; + } + + public override MethodInfo GetMethodInfo () + { + return methInfo; + } + + /** + * @brief Generate call to backend API function (eg llSay()) maybe followed by a call to CheckRun(). + * @param scg = script being compiled + * @param result = where to place result (might be void) + * @param args = script-visible arguments to pass to API function + */ + public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) + { + if (isTaggedCallsCheckRun) { // see if 'xmr' method that calls CheckRun() internally + new ScriptCodeGen.CallLabel (scg, errorAt); // if so, put a call label immediately before it + // .. so restoring the frame will jump immediately to the + // .. call without re-executing any code before this + } + if (!methInfo.IsStatic) { + scg.PushXMRInst (); // XMRInstanceSuperType pointer + if (apiContextField != null) { + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, apiContextField); + // 'this' pointer for API function + } + } + for (int i = 0; i < args.Length; i ++) { // push arguments, boxing/unboxing as needed + args[i].PushVal (scg, errorAt, argDecl.types[i]); + } + if (methInfo.Name == "llParcelMediaQuery") { + scg.ilGen.Emit (errorAt, OpCodes.Call, fixLLParcelMediaQuery); + } + if (methInfo.Name == "llParcelMediaCommandList") { + scg.ilGen.Emit (errorAt, OpCodes.Call, fixLLParcelMediaCommandList); + } + if (methInfo.IsVirtual) { // call API function + scg.ilGen.Emit (errorAt, OpCodes.Callvirt, methInfo); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Call, methInfo); + } + result.Pop (scg, errorAt, retType); // pop result, boxing/unboxing as needed + if (isTaggedCallsCheckRun) { + scg.openCallLabel = null; + } + if (doCheckRun) { + scg.EmitCallCheckRun (errorAt, false); // maybe call CheckRun() + } + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptMyILGen.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptMyILGen.cs new file mode 100644 index 0000000..ecc217e --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptMyILGen.cs @@ -0,0 +1,81 @@ +/* + * 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 System; +using System.Reflection; +using System.Reflection.Emit; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public interface ScriptMyILGen + { + string methName { get; } + ScriptMyLocal DeclareLocal (Type type, string name); + ScriptMyLabel DefineLabel (string name); + void BeginExceptionBlock (); + void BeginCatchBlock (Type excType); + void BeginFinallyBlock (); + void EndExceptionBlock (); + void Emit (Token errorAt, OpCode opcode); + void Emit (Token errorAt, OpCode opcode, FieldInfo field); + void Emit (Token errorAt, OpCode opcode, ScriptMyLocal myLocal); + void Emit (Token errorAt, OpCode opcode, Type type); + void Emit (Token errorAt, OpCode opcode, ScriptMyLabel myLabel); + void Emit (Token errorAt, OpCode opcode, ScriptMyLabel[] myLabels); + void Emit (Token errorAt, OpCode opcode, ScriptObjWriter method); + void Emit (Token errorAt, OpCode opcode, MethodInfo method); + void Emit (Token errorAt, OpCode opcode, ConstructorInfo ctor); + void Emit (Token errorAt, OpCode opcode, double value); + void Emit (Token errorAt, OpCode opcode, float value); + void Emit (Token errorAt, OpCode opcode, int value); + void Emit (Token errorAt, OpCode opcode, string value); + void MarkLabel (ScriptMyLabel myLabel); + } + + /** + * @brief One of these per label defined in the function. + */ + public class ScriptMyLabel { + public string name; + public int number; + + public GraphNodeMarkLabel whereAmI; + public Type[] stackDepth; + public bool[] stackBoxeds; + } + + /** + * @brief One of these per local variable defined in the function. + */ + public class ScriptMyLocal { + public string name; + public Type type; + public int number; + + public bool isReferenced; + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjCode.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjCode.cs new file mode 100644 index 0000000..038dfcd --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjCode.cs @@ -0,0 +1,256 @@ +/* + * 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.XMREngine; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public delegate void ScriptEventHandler (XMRInstAbstract instance); + + /* + * This object represents the output of the compilation. + * Once the compilation is complete, its contents should be + * considered 'read-only', so it can be shared among multiple + * instances of the script. + * + * It gets created by ScriptCodeGen. + * It gets used by XMRInstance to create script instances. + */ + public class ScriptObjCode + { + public string sourceHash; // source text hash code + + public XMRInstArSizes glblSizes = new XMRInstArSizes (); + // number of global variables of various types + + public string[] stateNames; // convert state number to corresponding string + + public ScriptEventHandler[,] scriptEventHandlerTable; + // entrypoints to all event handler functions + // 1st subscript = state code number (0=default) + // 2nd subscript = event code number + // null entry means no handler defined for that state,event + + public Dictionary sdObjTypesName; + // all script-defined types by name + public TokenDeclSDType[] sdObjTypesIndx; + // all script-defined types by sdTypeIndex + + public Dictionary sdDelTypes; + // all script-defined delegates (including anonymous) + + public Dictionary dynamicMethods; + // all dyanmic methods + + public Dictionary[]> scriptSrcLocss; + // method,iloffset -> source file,line,posn + + public int refCount; // used by engine to keep track of number of + // instances that are using this object code + + public Dictionary> globalVarNames = new Dictionary> (); + + public DateTime fileDateUtc; + public int expiryDays = Int32.MaxValue; + public bool IsExpired () + { + return (DateTime.UtcNow.Ticks - fileDateUtc.Ticks) / 10000000 / 86400 >= expiryDays; + } + + /** + * @brief Fill in ScriptObjCode from an XMREngine object file. + * 'objFileReader' is a serialized form of the CIL code we generated + * 'asmFileWriter' is where we write the disassembly to (or null if not wanted) + * 'srcFileWriter' is where we write the decompilation to (or null if not wanted) + * Throws an exception if there is any error (theoretically). + */ + public ScriptObjCode (BinaryReader objFileReader, TextWriter asmFileWriter, TextWriter srcFileWriter) + { + /* + * Check version number to make sure we know how to process file contents. + */ + char[] ocm = objFileReader.ReadChars (ScriptCodeGen.OBJECT_CODE_MAGIC.Length); + if (new String (ocm) != ScriptCodeGen.OBJECT_CODE_MAGIC) { + throw new Exception ("not an XMR object file (bad magic)"); + } + int cvv = objFileReader.ReadInt32 (); + if (cvv != ScriptCodeGen.COMPILED_VERSION_VALUE) { + throw new CVVMismatchException (cvv, ScriptCodeGen.COMPILED_VERSION_VALUE); + } + + /* + * Fill in simple parts of scriptObjCode object. + */ + sourceHash = objFileReader.ReadString (); + expiryDays = objFileReader.ReadInt32 (); + glblSizes.ReadFromFile (objFileReader); + + int nStates = objFileReader.ReadInt32 (); + + stateNames = new string[nStates]; + for (int i = 0; i < nStates; i ++) { + stateNames[i] = objFileReader.ReadString (); + if (asmFileWriter != null) { + asmFileWriter.WriteLine (" state[{0}] = {1}", i, stateNames[i]); + } + } + + if (asmFileWriter != null) { + glblSizes.WriteAsmFile (asmFileWriter, "numGbl"); + } + + string gblName; + while ((gblName = objFileReader.ReadString ()) != "") { + string gblType = objFileReader.ReadString (); + int gblIndex = objFileReader.ReadInt32 (); + Dictionary names; + if (!globalVarNames.TryGetValue (gblType, out names)) { + names = new Dictionary (); + globalVarNames.Add (gblType, names); + } + names.Add (gblIndex, gblName); + if (asmFileWriter != null) { + asmFileWriter.WriteLine (" {0} = {1}[{2}]", gblName, gblType, gblIndex); + } + } + + /* + * Read in script-defined types. + */ + sdObjTypesName = new Dictionary (); + sdDelTypes = new Dictionary (); + int maxIndex = -1; + while ((gblName = objFileReader.ReadString ()) != "") { + TokenDeclSDType sdt = TokenDeclSDType.ReadFromFile (sdObjTypesName, + gblName, objFileReader, asmFileWriter); + sdObjTypesName.Add (gblName, sdt); + if (maxIndex < sdt.sdTypeIndex) maxIndex = sdt.sdTypeIndex; + if (sdt is TokenDeclSDTypeDelegate) { + sdDelTypes.Add (sdt.GetSysType (), gblName); + } + } + sdObjTypesIndx = new TokenDeclSDType[maxIndex+1]; + foreach (TokenDeclSDType sdt in sdObjTypesName.Values) { + sdObjTypesIndx[sdt.sdTypeIndex] = sdt; + } + + /* + * Now fill in the methods (the hard part). + */ + scriptEventHandlerTable = new ScriptEventHandler[nStates,(int)ScriptEventCode.Size]; + dynamicMethods = new Dictionary (); + scriptSrcLocss = new Dictionary[]> (); + + ObjectTokens objectTokens = null; + if (asmFileWriter != null) { + objectTokens = new OTDisassemble (this, asmFileWriter); + } else if (srcFileWriter != null) { + objectTokens = new OTDecompile (this, srcFileWriter); + } + + try { + ScriptObjWriter.CreateObjCode (sdObjTypesName, objFileReader, this, objectTokens); + } finally { + if (objectTokens != null) objectTokens.Close (); + } + + /* + * We enter all script event handler methods in the ScriptEventHandler table. + * They are named: + */ + foreach (KeyValuePair kvp in dynamicMethods) { + string methName = kvp.Key; + int i = methName.IndexOf (' '); + if (i < 0) continue; + string stateName = methName.Substring (0, i); + string eventName = methName.Substring (++ i); + int stateCode; + for (stateCode = stateNames.Length; -- stateCode >= 0;) { + if (stateNames[stateCode] == stateName) break; + } + int eventCode = (int)Enum.Parse (typeof (ScriptEventCode), eventName); + scriptEventHandlerTable[stateCode,eventCode] = + (ScriptEventHandler)kvp.Value.CreateDelegate (typeof (ScriptEventHandler)); + } + + /* + * Fill in all script-defined class vtables. + */ + foreach (TokenDeclSDType sdt in sdObjTypesIndx) { + if ((sdt != null) && (sdt is TokenDeclSDTypeClass)) { + TokenDeclSDTypeClass sdtc = (TokenDeclSDTypeClass)sdt; + sdtc.FillVTables (this); + } + } + } + + /** + * @brief Called once for every method found in objFileReader file. + * It enters the method in the ScriptObjCode object table so it can be called. + */ + public void EndMethod (DynamicMethod method, Dictionary srcLocs) + { + /* + * Save method object code pointer. + */ + dynamicMethods.Add (method.Name, method); + + /* + * Build and sort iloffset -> source code location array. + */ + int n = srcLocs.Count; + KeyValuePair[] srcLocArray = new KeyValuePair[n]; + n = 0; + foreach (KeyValuePair kvp in srcLocs) srcLocArray[n++] = kvp; + Array.Sort (srcLocArray, endMethodWrapper); + + /* + * Save sorted array. + */ + scriptSrcLocss.Add (method.Name, srcLocArray); + } + + /** + * @brief Called once for every method found in objFileReader file. + * It enters the method in the ScriptObjCode object table so it can be called. + */ + private static EndMethodWrapper endMethodWrapper = new EndMethodWrapper (); + private class EndMethodWrapper : System.Collections.IComparer { + public int Compare (object x, object y) + { + KeyValuePair kvpx = (KeyValuePair)x; + KeyValuePair kvpy = (KeyValuePair)y; + return kvpx.Key - kvpy.Key; + } + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjWriter.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjWriter.cs new file mode 100644 index 0000000..e4e0ac8 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptObjWriter.cs @@ -0,0 +1,947 @@ +/* + * 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; + +/** + * @brief Wrapper class for ILGenerator. + * It writes the object code to a file and can then make real ILGenerator calls + * based on the file's contents. + */ +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public enum ScriptObjWriterCode : byte { + BegMethod, EndMethod, TheEnd, + DclLabel, DclLocal, DclMethod, MarkLabel, + EmitNull, EmitField, EmitLocal, EmitType, EmitLabel, EmitMethodExt, + EmitMethodInt, EmitCtor, EmitDouble, EmitFloat, EmitInteger, EmitString, + EmitLabels, + BegExcBlk, BegCatBlk, BegFinBlk, EndExcBlk + } + + public class ScriptObjWriter : ScriptMyILGen + { + private static Dictionary opCodes = PopulateOpCodes (); + private static Dictionary string2Type = PopulateS2T (); + private static Dictionary type2String = PopulateT2S (); + + private static MethodInfo monoGetCurrentOffset = typeof (ILGenerator).GetMethod ("Mono_GetCurrentOffset", + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, + new Type[] { typeof (ILGenerator) }, null); + + private static readonly OpCode[] opCodesLdcI4M1P8 = new OpCode[] { + OpCodes.Ldc_I4_M1, OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3, + OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8 + }; + + private BinaryWriter objFileWriter; + private string lastErrorAtFile = ""; + private int lastErrorAtLine = 0; + private int lastErrorAtPosn = 0; + + private Dictionary sdTypesRev = new Dictionary (); + public int labelNumber = 0; + public int localNumber = 0; + + private string _methName; + public string methName { get { return _methName; } } + + public Type retType; + public Type[] argTypes; + + /** + * @brief Begin function declaration + * @param sdTypes = script-defined types + * @param methName = name of the method being declared, eg, "Verify(array,list,string)" + * @param retType = its return value type + * @param argTypes[] = its argument types + * @param objFileWriter = file to write its object code to + * + * After calling this function, the following functions should be called: + * this.BegMethod (); + * this. (); + * this.EndMethod (); + * + * The design of this object is such that many constructors may be called, + * but once a BegMethod() is called for one of the objects, no method may + * called for any of the other objects until EndMethod() is called (or it + * would break up the object stream for that method). But we need to have + * many constructors possible so we get function headers at the beginning + * of the object file in case there are forward references to the functions. + */ + public ScriptObjWriter (TokenScript tokenScript, string methName, Type retType, Type[] argTypes, string[] argNames, BinaryWriter objFileWriter) + { + this._methName = methName; + this.retType = retType; + this.argTypes = argTypes; + this.objFileWriter = objFileWriter; + + /* + * Build list that translates system-defined types to script defined types. + */ + foreach (TokenDeclSDType sdt in tokenScript.sdSrcTypesValues) { + Type sys = sdt.GetSysType(); + if (sys != null) sdTypesRev[sys] = sdt.longName.val; + } + + /* + * This tells the reader to call 'new DynamicMethod()' to create + * the function header. Then any forward reference calls to this + * method will have a MethodInfo struct to call. + */ + objFileWriter.Write ((byte)ScriptObjWriterCode.DclMethod); + objFileWriter.Write (methName); + objFileWriter.Write (GetStrFromType (retType)); + + int nArgs = argTypes.Length; + objFileWriter.Write (nArgs); + for (int i = 0; i < nArgs; i ++) { + objFileWriter.Write (GetStrFromType (argTypes[i])); + objFileWriter.Write (argNames[i]); + } + } + + /** + * @brief Begin outputting object code for the function + */ + public void BegMethod () + { + /* + * This tells the reader to call methodInfo.GetILGenerator() + * so it can start writing CIL code for the method. + */ + objFileWriter.Write ((byte)ScriptObjWriterCode.BegMethod); + objFileWriter.Write (methName); + } + + /** + * @brief End of object code for the function + */ + public void EndMethod () + { + /* + * This tells the reader that all code for the method has + * been written and so it will typically call CreateDelegate() + * to finalize the method and create an entrypoint. + */ + objFileWriter.Write ((byte)ScriptObjWriterCode.EndMethod); + + objFileWriter = null; + } + + /** + * @brief Declare a local variable for use by the function + */ + public ScriptMyLocal DeclareLocal (Type type, string name) + { + ScriptMyLocal myLocal = new ScriptMyLocal (); + myLocal.type = type; + myLocal.name = name; + myLocal.number = localNumber ++; + myLocal.isReferenced = true; // so ScriptCollector won't optimize references away + return DeclareLocal (myLocal); + } + public ScriptMyLocal DeclareLocal (ScriptMyLocal myLocal) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.DclLocal); + objFileWriter.Write (myLocal.number); + objFileWriter.Write (myLocal.name); + objFileWriter.Write (GetStrFromType (myLocal.type)); + return myLocal; + } + + /** + * @brief Define a label for use by the function + */ + public ScriptMyLabel DefineLabel (string name) + { + ScriptMyLabel myLabel = new ScriptMyLabel (); + myLabel.name = name; + myLabel.number = labelNumber ++; + return DefineLabel (myLabel); + } + public ScriptMyLabel DefineLabel (ScriptMyLabel myLabel) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.DclLabel); + objFileWriter.Write (myLabel.number); + objFileWriter.Write (myLabel.name); + return myLabel; + } + + /** + * @brief try/catch blocks. + */ + public void BeginExceptionBlock () + { + objFileWriter.Write ((byte)ScriptObjWriterCode.BegExcBlk); + } + + public void BeginCatchBlock (Type excType) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.BegCatBlk); + objFileWriter.Write (GetStrFromType (excType)); + } + + public void BeginFinallyBlock () + { + objFileWriter.Write ((byte)ScriptObjWriterCode.BegFinBlk); + } + + public void EndExceptionBlock () + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EndExcBlk); + } + + public void Emit (Token errorAt, OpCode opcode) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitNull); + WriteOpCode (errorAt, opcode); + } + + public void Emit (Token errorAt, OpCode opcode, FieldInfo field) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitField); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (GetStrFromType (field.ReflectedType)); + objFileWriter.Write (field.Name); + } + + public void Emit (Token errorAt, OpCode opcode, ScriptMyLocal myLocal) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitLocal); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (myLocal.number); + } + + public void Emit (Token errorAt, OpCode opcode, Type type) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitType); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (GetStrFromType (type)); + } + + public void Emit (Token errorAt, OpCode opcode, ScriptMyLabel myLabel) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitLabel); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (myLabel.number); + } + + public void Emit (Token errorAt, OpCode opcode, ScriptMyLabel[] myLabels) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitLabels); + WriteOpCode (errorAt, opcode); + int nLabels = myLabels.Length; + objFileWriter.Write (nLabels); + for (int i = 0; i < nLabels; i ++) { + objFileWriter.Write (myLabels[i].number); + } + } + + public void Emit (Token errorAt, OpCode opcode, ScriptObjWriter method) + { + if (method == null) throw new ArgumentNullException ("method"); + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitMethodInt); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (method.methName); + } + + public void Emit (Token errorAt, OpCode opcode, MethodInfo method) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitMethodExt); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (method.Name); + objFileWriter.Write (GetStrFromType (method.ReflectedType)); + ParameterInfo[] parms = method.GetParameters (); + int nArgs = parms.Length; + objFileWriter.Write (nArgs); + for (int i = 0; i < nArgs; i ++) { + objFileWriter.Write (GetStrFromType (parms[i].ParameterType)); + } + } + + public void Emit (Token errorAt, OpCode opcode, ConstructorInfo ctor) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitCtor); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (GetStrFromType (ctor.ReflectedType)); + ParameterInfo[] parms = ctor.GetParameters (); + int nArgs = parms.Length; + objFileWriter.Write (nArgs); + for (int i = 0; i < nArgs; i ++) { + objFileWriter.Write (GetStrFromType (parms[i].ParameterType)); + } + } + + public void Emit (Token errorAt, OpCode opcode, double value) + { + if (opcode != OpCodes.Ldc_R8) { + throw new Exception ("bad opcode " + opcode.ToString ()); + } + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitDouble); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (value); + } + + public void Emit (Token errorAt, OpCode opcode, float value) + { + if (opcode != OpCodes.Ldc_R4) { + throw new Exception ("bad opcode " + opcode.ToString ()); + } + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitFloat); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (value); + } + + public void Emit (Token errorAt, OpCode opcode, int value) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitInteger); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (value); + } + + public void Emit (Token errorAt, OpCode opcode, string value) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.EmitString); + WriteOpCode (errorAt, opcode); + objFileWriter.Write (value); + } + + /** + * @brief Declare that the target of a label is the next instruction. + */ + public void MarkLabel (ScriptMyLabel myLabel) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.MarkLabel); + objFileWriter.Write (myLabel.number); + } + + /** + * @brief Write end-of-file marker to binary file. + */ + public static void TheEnd (BinaryWriter objFileWriter) + { + objFileWriter.Write ((byte)ScriptObjWriterCode.TheEnd); + } + + /** + * @brief Take an object file created by ScriptObjWriter() and convert it to a series of dynamic methods. + * @param sdTypes = script-defined types + * @param objReader = where to read object file from (as written by ScriptObjWriter above). + * @param scriptObjCode.EndMethod = called for each method defined at the end of the methods definition + * @param objectTokens = write disassemble/decompile data (or null if not wanted) + */ + public static void CreateObjCode (Dictionary sdTypes, BinaryReader objReader, + ScriptObjCode scriptObjCode, ObjectTokens objectTokens) + { + Dictionary methods = new Dictionary (); + DynamicMethod method = null; + ILGenerator ilGen = null; + Dictionary labels = new Dictionary (); + Dictionary locals = new Dictionary (); + Dictionary labelNames = new Dictionary (); + Dictionary localNames = new Dictionary (); + object[] ilGenArg = new object[1]; + int offset = 0; + Dictionary srcLocs = null; + string srcFile = ""; + int srcLine = 0; + int srcPosn = 0; + + while (true) { + + /* + * Get IL instruction offset at beginning of instruction. + */ + offset = 0; + if ((ilGen != null) && (monoGetCurrentOffset != null)) { + offset = (int)monoGetCurrentOffset.Invoke (null, ilGenArg); + } + + /* + * Read and decode next internal format code from input file (.xmrobj file). + */ + ScriptObjWriterCode code = (ScriptObjWriterCode)objReader.ReadByte (); + switch (code) { + + /* + * Reached end-of-file so we are all done. + */ + case ScriptObjWriterCode.TheEnd: { + return; + } + + /* + * Beginning of method's contents. + * Method must have already been declared via DclMethod + * so all we need is its name to retrieve from methods[]. + */ + case ScriptObjWriterCode.BegMethod: { + string methName = objReader.ReadString (); + + method = methods[methName]; + ilGen = method.GetILGenerator (); + ilGenArg[0] = ilGen; + + labels.Clear (); + locals.Clear (); + labelNames.Clear (); + localNames.Clear (); + + srcLocs = new Dictionary (); + if (objectTokens != null) objectTokens.BegMethod (method); + break; + } + + /* + * End of method's contents (ie, an OpCodes.Ret was probably just output). + * Call the callback to tell it the method is complete, and it can do whatever + * it wants with the method. + */ + case ScriptObjWriterCode.EndMethod: { + ilGen = null; + ilGenArg[0] = null; + scriptObjCode.EndMethod (method, srcLocs); + srcLocs = null; + if (objectTokens != null) objectTokens.EndMethod (); + break; + } + + /* + * Declare a label for branching to. + */ + case ScriptObjWriterCode.DclLabel: { + int number = objReader.ReadInt32 (); + string name = objReader.ReadString (); + + labels.Add (number, ilGen.DefineLabel ()); + labelNames.Add (number, name + "_" + number.ToString ()); + if (objectTokens != null) objectTokens.DefineLabel (number, name); + break; + } + + /* + * Declare a local variable to store into. + */ + case ScriptObjWriterCode.DclLocal: { + int number = objReader.ReadInt32 (); + string name = objReader.ReadString (); + string type = objReader.ReadString (); + Type syType = GetTypeFromStr (sdTypes, type); + + locals.Add (number, ilGen.DeclareLocal (syType)); + localNames.Add (number, name + "_" + number.ToString ()); + if (objectTokens != null) objectTokens.DefineLocal (number, name, type, syType); + break; + } + + /* + * Declare a method that will subsequently be defined. + * We create the DynamicMethod object at this point in case there + * are forward references from other method bodies. + */ + case ScriptObjWriterCode.DclMethod: { + string methName = objReader.ReadString (); + Type retType = GetTypeFromStr (sdTypes, objReader.ReadString ()); + int nArgs = objReader.ReadInt32 (); + + Type[] argTypes = new Type[nArgs]; + string[] argNames = new string[nArgs]; + for (int i = 0; i < nArgs; i ++) { + argTypes[i] = GetTypeFromStr (sdTypes, objReader.ReadString ()); + argNames[i] = objReader.ReadString (); + } + methods.Add (methName, new DynamicMethod (methName, retType, argTypes)); + if (objectTokens != null) objectTokens.DefineMethod (methName, retType, argTypes, argNames); + break; + } + + /* + * Mark a previously declared label at this spot. + */ + case ScriptObjWriterCode.MarkLabel: { + int number = objReader.ReadInt32 (); + + ilGen.MarkLabel (labels[number]); + + if (objectTokens != null) objectTokens.MarkLabel (offset, number); + break; + } + + /* + * Try/Catch blocks. + */ + case ScriptObjWriterCode.BegExcBlk: { + ilGen.BeginExceptionBlock (); + if (objectTokens != null) objectTokens.BegExcBlk (offset); + break; + } + + case ScriptObjWriterCode.BegCatBlk: { + Type excType = GetTypeFromStr (sdTypes, objReader.ReadString ()); + ilGen.BeginCatchBlock (excType); + if (objectTokens != null) objectTokens.BegCatBlk (offset, excType); + break; + } + + case ScriptObjWriterCode.BegFinBlk: { + ilGen.BeginFinallyBlock (); + if (objectTokens != null) objectTokens.BegFinBlk (offset); + break; + } + + case ScriptObjWriterCode.EndExcBlk: { + ilGen.EndExceptionBlock (); + if (objectTokens != null) objectTokens.EndExcBlk (offset); + break; + } + + /* + * Emit an opcode with no operand. + */ + case ScriptObjWriterCode.EmitNull: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode); + + if (objectTokens != null) objectTokens.EmitNull (offset, opCode); + break; + } + + /* + * Emit an opcode with a FieldInfo operand. + */ + case ScriptObjWriterCode.EmitField: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + Type reflectedType = GetTypeFromStr (sdTypes, objReader.ReadString ()); + string fieldName = objReader.ReadString (); + + FieldInfo field = reflectedType.GetField (fieldName); + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, field); + + if (objectTokens != null) objectTokens.EmitField (offset, opCode, field); + break; + } + + /* + * Emit an opcode with a LocalBuilder operand. + */ + case ScriptObjWriterCode.EmitLocal: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + int number = objReader.ReadInt32 (); + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, locals[number]); + + if (objectTokens != null) objectTokens.EmitLocal (offset, opCode, number); + break; + } + + /* + * Emit an opcode with a Type operand. + */ + case ScriptObjWriterCode.EmitType: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + string name = objReader.ReadString (); + Type type = GetTypeFromStr (sdTypes, name); + + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, type); + + if (objectTokens != null) objectTokens.EmitType (offset, opCode, type); + break; + } + + /* + * Emit an opcode with a Label operand. + */ + case ScriptObjWriterCode.EmitLabel: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + int number = objReader.ReadInt32 (); + + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, labels[number]); + + if (objectTokens != null) objectTokens.EmitLabel (offset, opCode, number); + break; + } + + /* + * Emit an opcode with a Label array operand. + */ + case ScriptObjWriterCode.EmitLabels: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + int nLabels = objReader.ReadInt32 (); + Label[] lbls = new Label[nLabels]; + int[] nums = new int[nLabels]; + for (int i = 0; i < nLabels; i ++) { + nums[i] = objReader.ReadInt32 (); + lbls[i] = labels[nums[i]]; + } + + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, lbls); + + if (objectTokens != null) objectTokens.EmitLabels (offset, opCode, nums); + break; + } + + /* + * Emit an opcode with a MethodInfo operand (such as a call) of an external function. + */ + case ScriptObjWriterCode.EmitMethodExt: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + string methName = objReader.ReadString (); + Type methType = GetTypeFromStr (sdTypes, objReader.ReadString ()); + int nArgs = objReader.ReadInt32 (); + + Type[] argTypes = new Type[nArgs]; + for (int i = 0; i < nArgs; i ++) { + argTypes[i] = GetTypeFromStr (sdTypes, objReader.ReadString ()); + } + MethodInfo methInfo = methType.GetMethod (methName, argTypes); + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, methInfo); + + if (objectTokens != null) objectTokens.EmitMethod (offset, opCode, methInfo); + break; + } + + /* + * Emit an opcode with a MethodInfo operand of an internal function + * (previously declared via DclMethod). + */ + case ScriptObjWriterCode.EmitMethodInt: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + string methName = objReader.ReadString (); + + MethodInfo methInfo = methods[methName]; + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, methInfo); + + if (objectTokens != null) objectTokens.EmitMethod (offset, opCode, methInfo); + break; + } + + /* + * Emit an opcode with a ConstructorInfo operand. + */ + case ScriptObjWriterCode.EmitCtor: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + Type ctorType = GetTypeFromStr (sdTypes, objReader.ReadString ()); + int nArgs = objReader.ReadInt32 (); + Type[] argTypes = new Type[nArgs]; + for (int i = 0; i < nArgs; i ++) { + argTypes[i] = GetTypeFromStr (sdTypes, objReader.ReadString ()); + } + + ConstructorInfo ctorInfo = ctorType.GetConstructor (argTypes); + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, ctorInfo); + + if (objectTokens != null) objectTokens.EmitCtor (offset, opCode, ctorInfo); + break; + } + + /* + * Emit an opcode with a constant operand of various types. + */ + case ScriptObjWriterCode.EmitDouble: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + double value = objReader.ReadDouble (); + + if (opCode != OpCodes.Ldc_R8) { + throw new Exception ("bad opcode " + opCode.ToString ()); + } + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, value); + + if (objectTokens != null) objectTokens.EmitDouble (offset, opCode, value); + break; + } + + case ScriptObjWriterCode.EmitFloat: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + float value = objReader.ReadSingle (); + + if (opCode != OpCodes.Ldc_R4) { + throw new Exception ("bad opcode " + opCode.ToString ()); + } + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, value); + + if (objectTokens != null) objectTokens.EmitFloat (offset, opCode, value); + break; + } + + case ScriptObjWriterCode.EmitInteger: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + int value = objReader.ReadInt32 (); + + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + + if (opCode == OpCodes.Ldc_I4) { + if ((value >= -1) && (value <= 8)) { + opCode = opCodesLdcI4M1P8[value+1]; + ilGen.Emit (opCode); + if (objectTokens != null) objectTokens.EmitNull (offset, opCode); + break; + } + if ((value >= 0) && (value <= 127)) { + opCode = OpCodes.Ldc_I4_S; + ilGen.Emit (OpCodes.Ldc_I4_S, (sbyte)value); + goto pemitint; + } + } + + ilGen.Emit (opCode, value); + pemitint: + if (objectTokens != null) objectTokens.EmitInteger (offset, opCode, value); + break; + } + + case ScriptObjWriterCode.EmitString: { + OpCode opCode = ReadOpCode (objReader, ref srcFile, ref srcLine, ref srcPosn); + string value = objReader.ReadString (); + + SaveSrcLoc (srcLocs, offset, srcFile, srcLine, srcPosn); + ilGen.Emit (opCode, value); + + if (objectTokens != null) objectTokens.EmitString (offset, opCode, value); + break; + } + + /* + * Who knows what? + */ + default: throw new Exception ("bad ScriptObjWriterCode " + ((byte)code).ToString ()); + } + } + } + + /** + * @brief Generate array to quickly translate OpCode.Value to full OpCode struct. + */ + private static Dictionary PopulateOpCodes () + { + Dictionary opCodeDict = new Dictionary (); + FieldInfo[] fields = typeof (OpCodes).GetFields (); + for (int i = 0; i < fields.Length; i ++) { + OpCode opcode = (OpCode)fields[i].GetValue (null); + opCodeDict.Add (opcode.Value, opcode); + } + return opCodeDict; + } + + /** + * @brief Write opcode out to file. + */ + private void WriteOpCode (Token errorAt, OpCode opcode) + { + if (errorAt == null) { + objFileWriter.Write (""); + objFileWriter.Write (lastErrorAtLine); + objFileWriter.Write (lastErrorAtPosn); + } else { + if (errorAt.file != lastErrorAtFile) { + objFileWriter.Write (errorAt.file); + lastErrorAtFile = errorAt.file; + } else { + objFileWriter.Write (""); + } + objFileWriter.Write (errorAt.line); + objFileWriter.Write (errorAt.posn); + lastErrorAtLine = errorAt.line; + lastErrorAtPosn = errorAt.posn; + } + objFileWriter.Write (opcode.Value); + } + + /** + * @brief Read opcode in from file. + */ + private static OpCode ReadOpCode (BinaryReader objReader, ref string srcFile, ref int srcLine, ref int srcPosn) + { + string f = objReader.ReadString (); + if (f != "") srcFile = f; + srcLine = objReader.ReadInt32 (); + srcPosn = objReader.ReadInt32 (); + + short value = objReader.ReadInt16 (); + return opCodes[value]; + } + + /** + * @brief Save an IL_offset -> source location translation entry + * @param srcLocs = saved entries for the current function + * @param offset = offset in IL object code for next instruction + * @param src{File,Line,Posn} = location in source file corresponding to opcode + * @returns with entry added to srcLocs + */ + private static void SaveSrcLoc (Dictionary srcLocs, int offset, string srcFile, int srcLine, int srcPosn) + { + ScriptSrcLoc srcLoc = new ScriptSrcLoc (); + srcLoc.file = srcFile; + srcLoc.line = srcLine; + srcLoc.posn = srcPosn; + srcLocs[offset] = srcLoc; + } + + /** + * @brief Create type<->string conversions. + * Using Type.AssemblyQualifiedName is horribly inefficient + * and all our types should be known. + */ + private static Dictionary PopulateS2T () + { + Dictionary s2t = new Dictionary (); + + s2t.Add ("badcallx", typeof (ScriptBadCallNoException)); + s2t.Add ("binopstr", typeof (BinOpStr)); + s2t.Add ("bool", typeof (bool)); + s2t.Add ("char", typeof (char)); + s2t.Add ("delegate", typeof (Delegate)); + s2t.Add ("delarr[]", typeof (Delegate[])); + s2t.Add ("double", typeof (double)); + s2t.Add ("exceptn", typeof (Exception)); + s2t.Add ("float", typeof (float)); + s2t.Add ("htlist", typeof (HeapTrackerList)); + s2t.Add ("htobject", typeof (HeapTrackerObject)); + s2t.Add ("htstring", typeof (HeapTrackerString)); + s2t.Add ("inlfunc", typeof (CompValuInline)); + s2t.Add ("int", typeof (int)); + s2t.Add ("int*", typeof (int).MakeByRefType ()); + s2t.Add ("intrlokd", typeof (System.Threading.Interlocked)); + s2t.Add ("lslfloat", typeof (LSL_Float)); + s2t.Add ("lslint", typeof (LSL_Integer)); + s2t.Add ("lsllist", typeof (LSL_List)); + s2t.Add ("lslrot", typeof (LSL_Rotation)); + s2t.Add ("lslstr", typeof (LSL_String)); + s2t.Add ("lslvec", typeof (LSL_Vector)); + s2t.Add ("math", typeof (Math)); + s2t.Add ("midround", typeof (MidpointRounding)); + s2t.Add ("object", typeof (object)); + s2t.Add ("object*", typeof (object).MakeByRefType ()); + s2t.Add ("object[]", typeof (object[])); + s2t.Add ("scrbase", typeof (ScriptBaseClass)); + s2t.Add ("scrcode", typeof (ScriptCodeGen)); + s2t.Add ("sdtclobj", typeof (XMRSDTypeClObj)); + s2t.Add ("string", typeof (string)); + s2t.Add ("typecast", typeof (TypeCast)); + s2t.Add ("undstatx", typeof (ScriptUndefinedStateException)); + s2t.Add ("void", typeof (void)); + s2t.Add ("xmrarray", typeof (XMR_Array)); + s2t.Add ("xmrinst", typeof (XMRInstAbstract)); + + return s2t; + } + + private static Dictionary PopulateT2S () + { + Dictionary s2t = PopulateS2T (); + Dictionary t2s = new Dictionary (); + foreach (KeyValuePair kvp in s2t) { + t2s.Add (kvp.Value, kvp.Key); + } + return t2s; + } + + /** + * @brief Add to list of internally recognized types. + */ + public static void DefineInternalType (string name, Type type) + { + if (!string2Type.ContainsKey(name)) + { + string2Type.Add (name, type); + type2String.Add (type, name); + } + } + + private string GetStrFromType (Type t) + { + string s = GetStrFromTypeWork (t); + return s; + } + private string GetStrFromTypeWork (Type t) + { + string s; + + // internal fixed types like int and xmrarray etc + if (type2String.TryGetValue (t, out s)) return s; + + // script-defined types + if (sdTypesRev.TryGetValue (t, out s)) return "sdt$" + s; + + // inline function types + s = TokenDeclSDTypeDelegate.TryGetInlineName (t); + if (s != null) return s; + + // last resort + return t.AssemblyQualifiedName; + } + + private static Type GetTypeFromStr (Dictionary sdTypes, string s) + { + Type t; + + // internal fixed types like int and xmrarray etc + if (string2Type.TryGetValue (s, out t)) return t; + + // script-defined types + if (s.StartsWith ("sdt$")) return sdTypes[s.Substring(4)].GetSysType (); + + // inline function types + t = TokenDeclSDTypeDelegate.TryGetInlineSysType (s); + if (t != null) return t; + + // last resort + return Type.GetType (s, true); + } + } + + public class ScriptSrcLoc { + public string file; + public int line; + public int posn; + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptReduce.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptReduce.cs new file mode 100644 index 0000000..a8af740 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptReduce.cs @@ -0,0 +1,7719 @@ +/* + * 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. + */ + +/** + * @brief Reduce parser tokens to abstract syntax tree tokens. + * + * Usage: + * + * tokenBegin = returned by TokenBegin.Analyze () + * representing the whole script source + * as a flat list of tokens + * + * TokenScript tokenScript = Reduce.Analyze (TokenBegin tokenBegin); + * + * tokenScript = represents the whole script source + * as a tree of tokens + */ + +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; + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public class ScriptReduce { + public const uint SDT_PRIVATE = 1; + public const uint SDT_PROTECTED = 2; + public const uint SDT_PUBLIC = 4; + public const uint SDT_ABSTRACT = 8; + public const uint SDT_FINAL = 16; + public const uint SDT_NEW = 32; + public const uint SDT_OVERRIDE = 64; + public const uint SDT_STATIC = 128; + public const uint SDT_VIRTUAL = 256; + + private const int ASNPR = 50; + + private static Dictionary precedence = PrecedenceInit (); + + private static readonly Type[] brkCloseOnly = new Type[] { typeof (TokenKwBrkClose) }; + private static readonly Type[] cmpGTOnly = new Type[] { typeof (TokenKwCmpGT) }; + private static readonly Type[] colonOnly = new Type[] { typeof (TokenKwColon) }; + private static readonly Type[] commaOrBrcClose = new Type[] { typeof (TokenKwComma), typeof (TokenKwBrcClose) }; + private static readonly Type[] colonOrDotDotDot = new Type[] { typeof (TokenKwColon), typeof (TokenKwDotDotDot) }; + private static readonly Type[] parCloseOnly = new Type[] { typeof (TokenKwParClose) }; + private static readonly Type[] semiOnly = new Type[] { typeof (TokenKwSemi) }; + + /** + * @brief Initialize operator precedence table + * @returns with precedence table pointer + */ + private static Dictionary PrecedenceInit () + { + Dictionary p = new Dictionary (); + + // http://www.lslwiki.net/lslwiki/wakka.php?wakka=operators + + p.Add (typeof (TokenKwComma), 30); + + p.Add (typeof (TokenKwAsnLSh), ASNPR); // all assignment operators of equal precedence + p.Add (typeof (TokenKwAsnRSh), ASNPR); // ... so they get processed strictly right-to-left + p.Add (typeof (TokenKwAsnAdd), ASNPR); + p.Add (typeof (TokenKwAsnAnd), ASNPR); + p.Add (typeof (TokenKwAsnSub), ASNPR); + p.Add (typeof (TokenKwAsnMul), ASNPR); + p.Add (typeof (TokenKwAsnDiv), ASNPR); + p.Add (typeof (TokenKwAsnMod), ASNPR); + p.Add (typeof (TokenKwAsnOr), ASNPR); + p.Add (typeof (TokenKwAsnXor), ASNPR); + p.Add (typeof (TokenKwAssign), ASNPR); + + p.Add (typeof (TokenKwQMark), 60); + + p.Add (typeof (TokenKwOrOrOr), 70); + p.Add (typeof (TokenKwAndAndAnd), 80); + + p.Add (typeof (TokenKwOrOr), 100); + + p.Add (typeof (TokenKwAndAnd), 120); + + p.Add (typeof (TokenKwOr), 140); + + p.Add (typeof (TokenKwXor), 160); + + p.Add (typeof (TokenKwAnd), 180); + + p.Add (typeof (TokenKwCmpEQ), 200); + p.Add (typeof (TokenKwCmpNE), 200); + + p.Add (typeof (TokenKwCmpLT), 240); + p.Add (typeof (TokenKwCmpLE), 240); + p.Add (typeof (TokenKwCmpGT), 240); + p.Add (typeof (TokenKwCmpGE), 240); + + p.Add (typeof (TokenKwRSh), 260); + p.Add (typeof (TokenKwLSh), 260); + + p.Add (typeof (TokenKwAdd), 280); + p.Add (typeof (TokenKwSub), 280); + + p.Add (typeof (TokenKwMul), 320); + p.Add (typeof (TokenKwDiv), 320); + p.Add (typeof (TokenKwMod), 320); + + return p; + } + + /** + * @brief Reduce raw token stream to a single script token. + * Performs a little semantic testing, ie, undefined variables, etc. + * @param tokenBegin = points to a TokenBegin + * followed by raw tokens + * and last token is a TokenEnd + * @returns null: not a valid script, error messages have been output + * else: valid script top token + */ + public static TokenScript Reduce (TokenBegin tokenBegin) + { + return new ScriptReduce (tokenBegin).tokenScript; + } + + /* + * Instance variables. + */ + private bool errors = false; + private string lastErrorFile = ""; + private int lastErrorLine = 0; + private int numTypedefs = 0; + private TokenDeclVar currentDeclFunc = null; + private TokenDeclSDType currentDeclSDType = null; + private TokenScript tokenScript; + private TokenStmtBlock currentStmtBlock = null; + + /** + * @brief the constructor does all the processing. + * @param token = first token of script after the TokenBegin token + * @returns tokenScript = null: there were errors + * else: successful + */ + private ScriptReduce (TokenBegin tokenBegin) + { + /* + * Create a place to put the top-level script components, + * eg, state bodies, functions, global variables. + */ + tokenScript = new TokenScript (tokenBegin.nextToken); + tokenScript.expiryDays = tokenBegin.expiryDays; + + /* + * 'class', 'delegate', 'instance' all define types. + * So we pre-scan the source tokens for those keywords + * to build a script-defined type table and substitute + * type tokens for those names in the source. This is + * done as a separate scan so they can cross-reference + * each other. Also does likewise for fixed array types. + * + * Also, all 'typedef's are processed here. Their definitions + * remain in the source token stream after this, but they can + * be skipped over, because their bodies have been substituted + * in the source for any references. + */ + ParseSDTypePreScanPassOne (tokenBegin); // catalog definitions + ParseSDTypePreScanPassTwo (tokenBegin); // substitute references + + /* + int braces = 0; + Token prevTok = null; + for (Token token = tokenBegin; token != null; token = token.nextToken) { + if (token is TokenKwParClose) braces -= 2; + if (token is TokenKwBrcClose) braces -= 4; + StringBuilder sb = new StringBuilder ("ScriptReduce*: "); + sb.Append (token.GetHashCode ().ToString ("X8")); + sb.Append (" "); + sb.Append (token.line.ToString ().PadLeft (3)); + sb.Append ("."); + sb.Append (token.posn.ToString ().PadLeft (3)); + sb.Append (" "); + sb.Append (token.GetType ().Name.PadRight (24)); + sb.Append (" : "); + for (int i = 0; i < braces; i ++) sb.Append (' '); + token.DebString (sb); + Console.WriteLine (sb.ToString ()); + if (token.prevToken != prevTok) { + Console.WriteLine ("ScriptReduce*: -- prevToken link bad => " + token.prevToken.GetHashCode ().ToString ("X8")); + } + if (token is TokenKwBrcOpen) braces += 4; + if (token is TokenKwParOpen) braces += 2; + prevTok = token; + } + */ + + /* + * Create a function $globalvarinit to hold all explicit + * global variable initializations. + */ + TokenDeclVar gviFunc = new TokenDeclVar (tokenBegin, null, tokenScript); + gviFunc.name = new TokenName (gviFunc, "$globalvarinit"); + gviFunc.retType = new TokenTypeVoid (gviFunc); + gviFunc.argDecl = new TokenArgDecl (gviFunc); + TokenStmtBlock gviBody = new TokenStmtBlock (gviFunc); + gviBody.function = gviFunc; + gviFunc.body = gviBody; + tokenScript.globalVarInit = gviFunc; + tokenScript.AddVarEntry (gviFunc); + + /* + * Scan through the tokens until we reach the end. + */ + for (Token token = tokenBegin.nextToken; !(token is TokenEnd);) { + if (token is TokenKwSemi) { + token = token.nextToken; + continue; + } + + /* + * Script-defined type declarations. + */ + if (ParseDeclSDTypes (ref token, null, SDT_PUBLIC)) continue; + + /* + * constant = ; + */ + if (token is TokenKwConst) { + ParseDeclVar (ref token, null); + continue; + } + + /* + * ; + * = ; + */ + if ((token is TokenType) && + (token.nextToken is TokenName) && + ((token.nextToken.nextToken is TokenKwSemi) || + (token.nextToken.nextToken is TokenKwAssign))) { + TokenDeclVar var = ParseDeclVar (ref token, gviFunc); + if (var != null) { + // = ; + TokenLValName left = new TokenLValName (var.name, tokenScript.variablesStack); + DoVarInit (gviFunc, left, var.init); + } + continue; + } + + /* + * { [ get { } ] [ set { } ] } + */ + if ((token is TokenType) && + (token.nextToken is TokenName) && + (token.nextToken.nextToken is TokenKwBrcOpen)) { + ParseProperty (ref token, false, true); + continue; + } + + /* + * + * global function returning specified type + */ + if (token is TokenType) { + TokenType tokenType = (TokenType)token; + + token = token.nextToken; + if (!(token is TokenName)) { + ErrorMsg (token, "expecting variable/function name"); + token = SkipPastSemi (token); + continue; + } + TokenName tokenName = (TokenName)token; + token = token.nextToken; + if (!(token is TokenKwParOpen)) { + ErrorMsg (token, " must be followed by ; = or ("); + token = SkipPastSemi (token); + continue; + } + token = tokenType; + TokenDeclVar tokenDeclFunc = ParseDeclFunc (ref token, false, false, false); + if (tokenDeclFunc == null) continue; + if (!tokenScript.AddVarEntry (tokenDeclFunc)) { + ErrorMsg (tokenName, "duplicate function " + tokenDeclFunc.funcNameSig.val); + } + continue; + } + + /* + * + * global function returning void + */ + if (token is TokenName) { + TokenName tokenName = (TokenName)token; + token = token.nextToken; + if (!(token is TokenKwParOpen)) { + ErrorMsg (token, "looking for open paren after assuming " + + tokenName.val + " is a function name"); + token = SkipPastSemi (token); + continue; + } + token = tokenName; + TokenDeclVar tokenDeclFunc = ParseDeclFunc (ref token, false, false, false); + if (tokenDeclFunc == null) continue; + if (!tokenScript.AddVarEntry (tokenDeclFunc)) { + ErrorMsg (tokenName, "duplicate function " + tokenDeclFunc.funcNameSig.val); + } + continue; + } + + /* + * default + */ + if (token is TokenKwDefault) { + TokenDeclState tokenDeclState = new TokenDeclState (token); + token = token.nextToken; + tokenDeclState.body = ParseStateBody (ref token); + if (tokenDeclState.body == null) continue; + if (tokenScript.defaultState != null) { + ErrorMsg (tokenDeclState, "default state already declared"); + continue; + } + tokenScript.defaultState = tokenDeclState; + continue; + } + + /* + * state + */ + if (token is TokenKwState) { + TokenDeclState tokenDeclState = new TokenDeclState (token); + token = token.nextToken; + if (!(token is TokenName)) { + ErrorMsg (token, "state must be followed by state name"); + token = SkipPastSemi (token); + continue; + } + tokenDeclState.name = (TokenName)token; + token = token.nextToken; + tokenDeclState.body = ParseStateBody (ref token); + if (tokenDeclState.body == null) continue; + if (tokenScript.states.ContainsKey (tokenDeclState.name.val)) { + ErrorMsg (tokenDeclState.name, "duplicate state definition"); + continue; + } + tokenScript.states.Add (tokenDeclState.name.val, tokenDeclState); + continue; + } + + /* + * Doesn't fit any of those forms, output message and skip to next statement. + */ + ErrorMsg (token, "looking for var name, type, state or default, script-defined type declaration"); + token = SkipPastSemi (token); + continue; + } + + /* + * Must have a default state to start in. + */ + if (!errors && (tokenScript.defaultState == null)) { + ErrorMsg (tokenScript, "no default state defined"); + } + + /* + * If any error messages were written out, set return value to null. + */ + if (errors) tokenScript = null; + } + + /** + * @brief Pre-scan the source for class, delegate, interface, typedef definition keywords. + * Clump the keywords and name being defined together, but leave the body intact. + * In the case of a delegate with an explicit return type, it reverses the name and return type. + * After this completes there shouldn't be any TokenKw{Class,Delegate,Interface,Typedef} + * keywords in the source, they are all replaced by TokenDeclSDType{Class,Delegate,Interface, + * Typedef} tokens which also encapsulate the name of the type being defined and any generic + * parameter names. The body remains intact in the source token stream following the + * TokenDeclSDType* token. + */ + private void ParseSDTypePreScanPassOne (Token tokenBegin) + { + Stack braceLevels = new Stack (); + Stack outerLevels = new Stack (); + int openBraceLevel = 0; + braceLevels.Push (-1); + outerLevels.Push (null); + + for (Token t = tokenBegin; !((t = t.nextToken) is TokenEnd);) { + + /* + * Keep track of nested definitions so we can link them up. + * We also need to detect the end of class and interface definitions. + */ + if (t is TokenKwBrcOpen) { + openBraceLevel ++; + continue; + } + if (t is TokenKwBrcClose) { + if (-- openBraceLevel < 0) { + ErrorMsg (t, "{ } mismatch"); + return; + } + if (braceLevels.Peek () == openBraceLevel) { + braceLevels.Pop (); + outerLevels.Pop ().endToken = t; + } + continue; + } + + /* + * Check for 'class' or 'interface'. + * They always define a new class or interface. + * They can contain nested script-defined type definitions. + */ + if ((t is TokenKwClass) || (t is TokenKwInterface)) { + Token kw = t; + t = t.nextToken; + if (!(t is TokenName)) { + ErrorMsg (t, "expecting class or interface name"); + t = SkipPastSemi (t).prevToken; + continue; + } + TokenName name = (TokenName)t; + t = t.nextToken; + + /* + * Malloc the script-defined type object. + */ + TokenDeclSDType decl; + if (kw is TokenKwClass) decl = new TokenDeclSDTypeClass (name, kw.prevToken is TokenKwPartial); + else decl = new TokenDeclSDTypeInterface (name); + decl.outerSDType = outerLevels.Peek (); + + /* + * Check for generic parameter list. + */ + if (!ParseGenProtoParamList (ref t, decl)) continue; + + /* + * Splice in a TokenDeclSDType token that replaces the keyword and the name tokens + * and any generic parameters including the '<', ','s and '>'. + * kw = points to 'class' or 'interface' keyword. + * t = points to just past last part of class name parsed, hopefully a ':' or '{'. + */ + decl.prevToken = decl.isPartial ? kw.prevToken.prevToken : kw.prevToken; + decl.nextToken = t; + decl.prevToken.nextToken = decl; + decl.nextToken.prevToken = decl; + + /* + * Enter it in name lists so it can be seen by others. + */ + Token partialNewBody = CatalogSDTypeDecl (decl); + + /* + * Start inner type definitions. + */ + braceLevels.Push (openBraceLevel); + outerLevels.Push (decl); + + /* + * Scan the body starting on for before the '{'. + * + * If this body had an old partial merged into it, + * resume scanning at the beginning of the new body, + * ie, what used to be the first token after the '{' + * before the old body was spliced in. + */ + if (partialNewBody != null) { + + /* + * We have a partial that has had old partial body merged + * into new partial body. So resume scanning at the beginning + * of the new partial body so we don't get any duplicate scanning + * of the old partial body. + * + * ... { } + * ^- resume scanning here + * but inc openBraceLevel because + * we skipped scanning the '{' + */ + openBraceLevel ++; + t = partialNewBody; + } + t = t.prevToken; + continue; + } + + /* + * Check for 'delegate'. + * It always defines a new delegate. + * Delegates never define nested types. + */ + if (t is TokenKwDelegate) { + Token kw = t; + t = t.nextToken; + + /* + * Next thing might be an explicit return type or the delegate's name. + * If it's a type token, then it's the return type, simple enough. + * But if it's a name token, it might be the name of some other script-defined type. + * The way to tell is that the delegate name is followed by a '(', whereas an + * explicit return type is followed by the delegate name. + */ + Token retType = t; + TokenName delName = null; + Token u; + int angles = 0; + for (u = t; !(u is TokenKwParOpen); u = u.nextToken) { + if ((u is TokenKwSemi) || (u is TokenEnd)) break; + if (u is TokenKwCmpLT) angles ++; + if (u is TokenKwCmpGT) angles --; + if (u is TokenKwRSh) angles -= 2; // idiot >> + if ((angles == 0) && (u is TokenName)) delName = (TokenName)u; + } + if (!(u is TokenKwParOpen)) { + ErrorMsg (u, "expecting ( for delegate parameter list"); + t = SkipPastSemi (t).prevToken; + continue; + } + if (delName == null) { + ErrorMsg (u, "expecting delegate name"); + t = SkipPastSemi (t).prevToken; + continue; + } + if (retType == delName) retType = null; + + /* + * Malloc the script-defined type object. + */ + TokenDeclSDTypeDelegate decl = new TokenDeclSDTypeDelegate (delName); + decl.outerSDType = outerLevels.Peek (); + + /* + * Check for generic parameter list. + */ + t = delName.nextToken; + if (!ParseGenProtoParamList (ref t, decl)) continue; + + /* + * Enter it in name lists so it can be seen by others. + */ + CatalogSDTypeDecl (decl); + + /* + * Splice in the token that replaces the 'delegate' keyword and the whole name + * (including the '<' name ... '>' parts). The return type token(s), if any, + * follow the splice token and come before the '('. + */ + decl.prevToken = kw.prevToken; + kw.prevToken.nextToken = decl; + + if (retType == null) { + decl.nextToken = t; + t.prevToken = decl; + } else { + decl.nextToken = retType; + retType.prevToken = decl; + retType.nextToken = t; + t.prevToken = retType; + } + + /* + * Scan for terminating ';'. + * There cannot be an intervening class, delegate, interfate, typedef, { or }. + */ + for (t = decl; !(t is TokenKwSemi); t = u) { + u = t.nextToken; + if ((u is TokenEnd) || + (u is TokenKwClass) || + (u is TokenKwDelegate) || + (u is TokenKwInterface) || + (u is TokenKwTypedef) || + (u is TokenKwBrcOpen) || + (u is TokenKwBrcClose)) { + ErrorMsg (t, "delegate missing terminating ;"); + break; + } + } + decl.endToken = t; + continue; + } + + /* + * Check for 'typedef'. + * It always defines a new macro. + * Typedefs never define nested types. + */ + if (t is TokenKwTypedef) { + Token kw = t; + t = t.nextToken; + + if (!(t is TokenName)) { + ErrorMsg (t, "expecting typedef name"); + t = SkipPastSemi (t).prevToken; + continue; + } + TokenName tdName = (TokenName)t; + t = t.nextToken; + + /* + * Malloc the script-defined type object. + */ + TokenDeclSDTypeTypedef decl = new TokenDeclSDTypeTypedef (tdName); + decl.outerSDType = outerLevels.Peek (); + + /* + * Check for generic parameter list. + */ + if (!ParseGenProtoParamList (ref t, decl)) continue; + + /* + * Enter it in name lists so it can be seen by others. + */ + CatalogSDTypeDecl (decl); + numTypedefs ++; + + /* + * Splice in the token that replaces the 'typedef' keyword and the whole name + * (including the '<' name ... '>' parts). + */ + decl.prevToken = kw.prevToken; + kw.prevToken.nextToken = decl; + decl.nextToken = t; + t.prevToken = decl; + + /* + * Scan for terminating ';'. + * There cannot be an intervening class, delegate, interfate, typedef, { or }. + */ + Token u; + for (t = decl; !(t is TokenKwSemi); t = u) { + u = t.nextToken; + if ((u is TokenEnd) || + (u is TokenKwClass) || + (u is TokenKwDelegate) || + (u is TokenKwInterface) || + (u is TokenKwTypedef) || + (u is TokenKwBrcOpen) || + (u is TokenKwBrcClose)) { + ErrorMsg (t, "typedef missing terminating ;"); + break; + } + } + decl.endToken = t; + continue; + } + } + } + + /** + * @brief Parse a possibly generic type definition's parameter list. + * @param t = points to the possible opening '<' on entry + * points just past the closing '>' on return + * @param decl = the generic type being declared + * @returns false: parse error + * true: decl.genParams = filled in with parameter list + * decl.innerSDTypes = filled in with parameter list + */ + private bool ParseGenProtoParamList (ref Token t, TokenDeclSDType decl) + { + /* + * Maybe there aren't any generic parameters. + * If so, leave decl.genParams = null. + */ + if (!(t is TokenKwCmpLT)) return true; + + /* + * Build list of generic parameter names. + */ + Dictionary parms = new Dictionary (); + do { + t = t.nextToken; + if (!(t is TokenName)) { + ErrorMsg (t, "expecting generic parameter name"); + break; + } + TokenName tn = (TokenName)t; + if (parms.ContainsKey (tn.val)) { + ErrorMsg (tn, "duplicate use of generic parameter name"); + } else { + parms.Add (tn.val, parms.Count); + } + t = t.nextToken; + } while (t is TokenKwComma); + if (!(t is TokenKwCmpGT)) { + ErrorMsg (t, "expecting , for more params or > to end param list"); + return false; + } + t = t.nextToken; + decl.genParams = parms; + + return true; + } + + /** + * @brief Catalog a script-defined type. + * Its short name (eg, 'Node') gets put in the next outer level (eg, 'List')'s inner type definition table. + * Its long name (eg, 'List.Node') gets put in the global type definition table. + */ + public Token CatalogSDTypeDecl (TokenDeclSDType decl) + { + string longName = decl.longName.val; + TokenDeclSDType dupDecl; + if (!tokenScript.sdSrcTypesTryGetValue (longName, out dupDecl)) { + tokenScript.sdSrcTypesAdd (longName, decl); + if (decl.outerSDType != null) { + decl.outerSDType.innerSDTypes.Add (decl.shortName.val, decl); + } + return null; + } + + if (!dupDecl.isPartial || !decl.isPartial) { + ErrorMsg (decl, "duplicate definition of type " + longName); + ErrorMsg (dupDecl, "previous definition here"); + return null; + } + + if (!GenericParametersMatch (decl, dupDecl)) { + ErrorMsg (decl, "all partial class generic parameters must match"); + } + + /* + * Have new declaration be the cataloged one because body is going to get + * snipped out of old declaration and pasted into new declaration. + */ + tokenScript.sdSrcTypesRep (longName, decl); + if (decl.outerSDType != null) { + decl.outerSDType.innerSDTypes[decl.shortName.val] = decl; + } + + /* + * Find old partial definition's opening brace. + */ + Token dupBrcOpen; + for (dupBrcOpen = dupDecl; !(dupBrcOpen is TokenKwBrcOpen); dupBrcOpen = dupBrcOpen.nextToken) { + if (dupBrcOpen == dupDecl.endToken) { + ErrorMsg (dupDecl, "missing {"); + return null; + } + } + + /* + * Find new partial definition's opening brace. + */ + Token brcOpen; + for (brcOpen = decl; !(brcOpen is TokenKwBrcOpen); brcOpen = brcOpen.nextToken) { + if (brcOpen is TokenEnd) { + ErrorMsg (decl, "missing {"); + return null; + } + } + Token body = brcOpen.nextToken; + + /* + * Stick old partial definition's extends/implementeds list just + * in front of new partial definition's extends/implementeds list. + * + * class oldextimp { oldbody } ... + * dupDecl dupBrcOpen dupDecl.endToken + * + * class newextimp { newbody } ... + * decl brcOpen body decl.endToken + * + * becomes + * + * class ... + * dupDecl + * dupDecl.endToken + * + * class oldextimp newextimp { oldbody newbody } ... + * decl brcOpen body decl.endToken + */ + if (dupBrcOpen != dupDecl.nextToken) { + dupBrcOpen.prevToken.nextToken = decl.nextToken; + dupDecl.nextToken.prevToken = decl; + decl.nextToken.prevToken = dupBrcOpen.prevToken; + decl.nextToken = dupDecl.nextToken; + } + + /* + * Stick old partial definition's body just + * in front of new partial definition's body. + */ + if (dupBrcOpen.nextToken != dupDecl.endToken) { + dupBrcOpen.nextToken.prevToken = brcOpen; + dupDecl.endToken.prevToken.nextToken = body; + body.prevToken = dupDecl.endToken.prevToken; + brcOpen.nextToken = dupBrcOpen.nextToken; + } + + /* + * Null out old definition's extends/implementeds list and body + * by having the declaration token be the only thing left. + */ + dupDecl.nextToken = dupDecl.endToken.nextToken; + dupDecl.nextToken.prevToken = dupDecl; + dupDecl.endToken = dupDecl; + + return body; + } + + /** + * @brief Determine whether or not the generic parameters of two class declarations match exactly. + */ + private static bool GenericParametersMatch (TokenDeclSDType c1, TokenDeclSDType c2) + { + if ((c1.genParams == null) && (c2.genParams == null)) return true; + if ((c1.genParams == null) || (c2.genParams == null)) return false; + Dictionary gp1 = c1.genParams; + Dictionary gp2 = c2.genParams; + if (gp1.Count != gp2.Count) return false; + foreach (KeyValuePair kvp1 in gp1) { + int v2; + if (!gp2.TryGetValue (kvp1.Key, out v2)) return false; + if (v2 != kvp1.Value) return false; + } + return true; + } + + /** + * @brief Replace all TokenName tokens that refer to the script-defined types with + * corresponding TokenTypeSDType{Class,Delegate,GenParam,Interface} tokens. + * Also handle generic references, ie, recognize that 'List' is an + * instantiation of 'List<>' and instantiate the generic. + */ + private const uint REPEAT_NOTYPE = 1; + private const uint REPEAT_INSTGEN = 2; + private const uint REPEAT_SUBST = 4; + + private void ParseSDTypePreScanPassTwo (Token tokenBegin) + { + List noTypes = new List (); + TokenDeclSDType outerSDType; + uint repeat; + + do { + repeat = 0; + outerSDType = null; + noTypes.Clear (); + + for (Token t = tokenBegin; !((t = t.nextToken) is TokenEnd);) { + + /* + * Maybe it's time to pop out of an outer class definition. + */ + if ((outerSDType != null) && (outerSDType.endToken == t)) { + outerSDType = outerSDType.outerSDType; + continue; + } + + /* + * Skip completely over any script-defined generic prototypes. + * We only need to process their instantiations which are non- + * generic versions of the generics. + */ + if ((t is TokenDeclSDType) && (((TokenDeclSDType)t).genParams != null)) { + t = ((TokenDeclSDType)t).endToken; + continue; + } + + /* + * Check for beginning of non-generic script-defined type definitions. + * They can have nested definitions in their innerSDTypes[] that match + * name tokens, so add them to the stack. + * + * But just ignore any preliminary partial definitions as they have had + * their entire contents spliced out and spliced into a subsequent partial + * definition. So if we originally had: + * partial class Abc { public intenger one; } + * partial class Abc { public intenger two; } + * We now have: + * partial_class_Abc <== if we are here, just ignore the partial_class_Abc token + * partial_class_Abc { public intenger one; public intenger two; } + */ + if (t is TokenDeclSDType) { + if (((TokenDeclSDType)t).endToken != t) { + outerSDType = (TokenDeclSDType)t; + } + continue; + } + + /* + * For names not preceded by a '.', scan the script-defined type definition + * stack for that name. Splice the name out and replace with equivalent token. + */ + if ((t is TokenName) && !(t.prevToken is TokenKwDot)) { + t = TrySpliceTypeRef (t, outerSDType, ref repeat, noTypes); + } + + /* + * This handles types such as integer[,][], List[], etc. + * They are an instantiation of an internally generated type of the same name, brackets and all. + * Note that to malloc an array, use something like 'new float[,][](3,5)', not 'new float[3,5][]'. + * + * Note that we must not get confused by $idxprop property declarations such as: + * float [string kee] { get { ... } } + * ... and try to convert 'float' '[' to an array type. + */ + if ((t is TokenType) && (t.nextToken is TokenKwBrkOpen)) { + if ((t.nextToken.nextToken is TokenKwBrkClose) || + (t.nextToken.nextToken is TokenKwComma)) { + t = InstantiateJaggedArray (t, tokenBegin, ref repeat); + } + } + } + + /* + * If we instantiated a generic, loop back to process its contents + * just as if the source code had the instantiated code to begin with. + * Also repeat if we found a non-type inside the <> of a generic reference + * provided we have made at least one name->type substitution. + */ + } while (((repeat & REPEAT_INSTGEN) != 0) || + ((repeat & (REPEAT_NOTYPE | REPEAT_SUBST)) == (REPEAT_NOTYPE | REPEAT_SUBST))); + + /* + * These are places where we required a type be present, + * eg, a generic type argument or the body of a typedef. + */ + foreach (Token t in noTypes) { + ErrorMsg (t, "looking for type"); + } + } + + /** + * @brief Try to convert the source token string to a type reference + * and splice the type reference into the source token string + * replacing the original token(s). + * @param t = points to the initial TokenName token + * @param outerSDType = null: this is a top-level code reference + * else: this code is within outerSDType + * @returns pointer to last token parsed + * possibly with spliced-in type token + * repeat = possibly set true if need to do another pass + */ + private Token TrySpliceTypeRef (Token t, TokenDeclSDType outerSDType, ref uint repeat, List noTypes) + { + Token start = t; + string tnamestr = ((TokenName)t).val; + + /* + * Look for the name as a type declared by outerSDType or anything + * even farther out than that. If not found, simply return + * without updating t, meaning that t isn't the name of a type. + */ + TokenDeclSDType decl = null; + while (outerSDType != null) { + if (outerSDType.innerSDTypes.TryGetValue (tnamestr, out decl)) break; + outerSDType = outerSDType.outerSDType; + } + if ((outerSDType == null) && !tokenScript.sdSrcTypesTryGetValue (tnamestr, out decl)) return t; + + TokenDeclSDType instdecl; + while (true) { + + /* + * If it is a generic type, it must be followed by instantiation arguments. + */ + instdecl = decl; + if (decl.genParams != null) { + t = t.nextToken; + if (!(t is TokenKwCmpLT)) { + ErrorMsg (t, "expecting < for generic argument list"); + return t; + } + tnamestr += "<"; + int nArgs = decl.genParams.Count; + TokenType[] genArgs = new TokenType[nArgs]; + for (int i = 0; i < nArgs;) { + t = t.nextToken; + if (!(t is TokenType)) { + repeat |= REPEAT_NOTYPE; + noTypes.Add (t); + return t.prevToken; // make sure name gets processed + // so substitution can occur on it + } + TokenType ga = (TokenType)t; + genArgs[i] = ga; + tnamestr += ga.ToString (); + t = t.nextToken; + if (++ i < nArgs) { + if (!(t is TokenKwComma)) { + ErrorMsg (t, "expecting , for more generic arguments"); + return t; + } + tnamestr += ","; + } + } + if (t is TokenKwRSh) { // idiot >> + Token u = new TokenKwCmpGT (t); + Token v = new TokenKwCmpGT (t); + v.posn ++; + u.prevToken = t.prevToken; + u.nextToken = v; + v.nextToken = t.nextToken; + v.prevToken = u; + u.prevToken.nextToken = u; + v.nextToken.prevToken = v; + t = u; + } + if (!(t is TokenKwCmpGT)) { + ErrorMsg (t, "expecting > at end of generic argument list"); + return t; + } + tnamestr += ">"; + if (outerSDType != null) { + outerSDType.innerSDTypes.TryGetValue (tnamestr, out instdecl); + } else { + tokenScript.sdSrcTypesTryGetValue (tnamestr, out instdecl); + } + + /* + * Couldn't find 'List' but found 'List' and we have genArgs = 'string'. + * Instantiate the generic to create 'List'. This splices the definition + * of 'List' into the source token stream just as if it had been there all + * along. We have to then repeat the scan to process the instance's contents. + */ + if (instdecl == null) { + instdecl = decl.InstantiateGeneric (tnamestr, genArgs, this); + CatalogSDTypeDecl (instdecl); + repeat |= REPEAT_INSTGEN; + } + } + + /* + * Maybe caller wants a subtype by putting a '.' following all that. + */ + if (!(t.nextToken is TokenKwDot)) break; + if (!(t.nextToken.nextToken is TokenName)) break; + tnamestr = ((TokenName)t.nextToken.nextToken).val; + if (!instdecl.innerSDTypes.TryGetValue (tnamestr, out decl)) break; + t = t.nextToken.nextToken; + outerSDType = instdecl; + } + + /* + * Create a reference in the source to the definition + * that encapsulates the long dotted type name given in + * the source, and replace the long dotted type name in + * the source with the reference token, eg, replace + * 'Dictionary' '<' 'string' ',' 'integer' '>' '.' 'ValueList' + * with 'Dictionary.ValueList'. + */ + TokenType refer = instdecl.MakeRefToken (start); + if (refer == null) { + // typedef body is not yet a type + noTypes.Add (start); + repeat |= REPEAT_NOTYPE; + return start; + } + refer.prevToken = start.prevToken; // start points right at the first TokenName + refer.nextToken = t.nextToken; // t points at the last TokenName or TokenKwCmpGT + refer.prevToken.nextToken = refer; + refer.nextToken.prevToken = refer; + repeat |= REPEAT_SUBST; + + return refer; + } + + /** + * @brief We are known to have '[' so make an equivalent array type. + * @param t = points to the TokenType + * @param tokenBegin = where we can safely splice in new array class definitions + * @param repeat = set REPEAT_INSTGEN if new type created + * @returns pointer to last token parsed + * possibly with spliced-in type token + * repeat = possibly set true if need to do another pass + */ + private Token InstantiateJaggedArray (Token t, Token tokenBegin, ref uint repeat) + { + Token start = t; + TokenType ofType = (TokenType)t; + + Stack ranks = new Stack (); + + /* + * When script specifies 'float[,][]' it means a two-dimensional matrix + * that points to one-dimensional vectors of floats. So we would push + * a 2 then a 1 in this parsing code... + */ + do { + t = t.nextToken; // point at '[' + int rank = 0; + do { + rank ++; // count '[' and ','s + t = t.nextToken; // point at ',' or ']' + } while (t is TokenKwComma); + if (!(t is TokenKwBrkClose)) { + ErrorMsg (t, "expecting only [ , or ] for array type specification"); + return t; + } + ranks.Push (rank); + } while (t.nextToken is TokenKwBrkOpen); + + /* + * Now we build the types in reverse order. For the example above we will: + * first, create a type that is a one-dimensional vector of floats, float[] + * second, create a type that is a two-dimensional matrix of that. + * This keeps declaration and referencing similar, eg, + * float[,][] jag = new float[,][] (3,4); + * jag[i,j][k] ... is used to access the elements + */ + do { + int rank = ranks.Pop (); + TokenDeclSDType decl = InstantiateFixedArray (rank, ofType, tokenBegin, ref repeat); + ofType = decl.MakeRefToken (ofType); + } while (ranks.Count > 0); + + /* + * Finally splice in the resultant array type to replace the original tokens. + */ + ofType.prevToken = start.prevToken; + ofType.nextToken = t.nextToken; + ofType.prevToken.nextToken = ofType; + ofType.nextToken.prevToken = ofType; + + /* + * Resume parsing just after the spliced-in array type token. + */ + return ofType; + } + + /** + * @brief Instantiate a script-defined class type to handle fixed-dimension arrays. + * @param rank = number of dimensions for the array + * @param ofType = type of each element of the array + * @returns script-defined class declaration created to handle the array + */ + private TokenDeclSDType InstantiateFixedArray (int rank, TokenType ofType, Token tokenBegin, ref uint repeat) + { + /* + * Create the array type's name. + * If starting with a non-array type, just append the rank to it, eg, float + rank=1 -> float[] + * If starting with an array type, slip this rank in front of existing array, eg, float[] + rank=2 -> float[,][]. + * This makes it consistent with what the script-writer sees for both a type specification and when + * referencing elements in a jagged array. + */ + string name = ofType.ToString (); + StringBuilder sb = new StringBuilder (name); + int ix = name.IndexOf ('['); + if (ix < 0) ix = name.Length; + sb.Insert (ix ++, '['); + for (int i = 0; ++ i < rank;) { + sb.Insert (ix ++, ','); + } + sb.Insert (ix, ']'); + name = sb.ToString (); + + TokenDeclSDType fa; + if (!tokenScript.sdSrcTypesTryGetValue (name, out fa)) { + char suffix = 'O'; + if (ofType is TokenTypeChar) suffix = 'C'; + if (ofType is TokenTypeFloat) suffix = 'F'; + if (ofType is TokenTypeInt) suffix = 'I'; + + /* + * Don't already have one, create a new skeleton struct. + * Splice in a definition for the class at beginning of source file. + * + * class { + */ + fa = new TokenDeclSDTypeClass (new TokenName (tokenScript, name), false); + CatalogSDTypeDecl (fa); + repeat |= REPEAT_INSTGEN; + ((TokenDeclSDTypeClass)fa).arrayOfType = ofType; + ((TokenDeclSDTypeClass)fa).arrayOfRank = rank; + + Token t = SpliceAfter (tokenBegin, fa); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + /* + * public integer len0; + * public integer len1; + * ... + * public object obj; + */ + for (int i = 0; i < rank; i ++) { + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + } + + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, new TokenTypeObject (t)); + t = SpliceAfter (t, new TokenName (t, "obj")); + t = SpliceAfter (t, new TokenKwSemi (t)); + + /* + * public constructor (integer len0, integer len1, ...) { + * this.len0 = len0; + * this.len1 = len1; + * ... + * this.obj = xmrFixedArrayAlloc (len0 * len1 * ...); + * } + */ + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, new TokenKwConstructor (t)); + t = SpliceAfter (t, new TokenKwParOpen (t)); + for (int i = 0; i < rank; i ++) { + if (i > 0) t = SpliceAfter (t, new TokenKwComma (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + } + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + for (int i = 0; i < rank; i ++) { + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + t = SpliceAfter (t, new TokenKwAssign (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + } + + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "obj")); + t = SpliceAfter (t, new TokenKwAssign (t)); + t = SpliceAfter (t, new TokenName (t, "xmrFixedArrayAlloc" + suffix)); + t = SpliceAfter (t, new TokenKwParOpen (t)); + for (int i = 0; i < rank; i ++) { + if (i > 0) t = SpliceAfter (t, new TokenKwMul (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + } + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwSemi (t)); + t = SpliceAfter (t, new TokenKwBrcClose (t)); + + /* + * public integer Length { get { + * return this.len0 * this.len1 * ... ; + * } } + */ + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "Length")); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + t = SpliceAfter (t, new TokenKwGet (t)); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + t = SpliceAfter (t, new TokenKwRet (t)); + for (int i = 0; i < rank; i ++) { + if (i > 0) t = SpliceAfter (t, new TokenKwMul (t)); + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + } + t = SpliceAfter (t, new TokenKwSemi (t)); + + t = SpliceAfter (t, new TokenKwBrcClose (t)); + t = SpliceAfter (t, new TokenKwBrcClose (t)); + + /* + * public integer Length (integer dim) { + * switch (dim) { + * case 0: return this.len0; + * case 1: return this.len1; + * ... + * } + * return 0; + * } + */ + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "Length")); + t = SpliceAfter (t, new TokenKwParOpen (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "dim")); + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + t = SpliceAfter (t, new TokenKwSwitch (t)); + t = SpliceAfter (t, new TokenKwParOpen (t)); + t = SpliceAfter (t, new TokenName (t, "dim")); + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + for (int i = 0; i < rank; i ++) { + t = SpliceAfter (t, new TokenKwCase (t)); + t = SpliceAfter (t, new TokenInt (t, i)); + t = SpliceAfter (t, new TokenKwColon (t)); + t = SpliceAfter (t, new TokenKwRet (t)); + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + } + t = SpliceAfter (t, new TokenKwBrcClose (t)); + + t = SpliceAfter (t, new TokenKwRet (t)); + t = SpliceAfter (t, new TokenInt (t, 0)); + t = SpliceAfter (t, new TokenKwSemi (t)); + t = SpliceAfter (t, new TokenKwBrcClose (t)); + + /* + * public integer Index (integer idx0, integet idx1, ...) { + * integer idx = idx0; + * idx *= this.len1; idx += idx1; + * idx *= this.len2; idx += idx2; + * ... + * return idx; + * } + */ + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "Index")); + t = SpliceAfter (t, new TokenKwParOpen (t)); + for (int i = 0; i < rank; i ++) { + if (i > 0) t = SpliceAfter (t, new TokenKwComma (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "idx" + i)); + } + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAssign (t)); + t = SpliceAfter (t, new TokenName (t, "idx0")); + t = SpliceAfter (t, new TokenKwSemi (t)); + + for (int i = 1; i < rank; i ++) { + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAsnMul (t)); + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAsnAdd (t)); + t = SpliceAfter (t, new TokenName (t, "idx" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + } + + t = SpliceAfter (t, new TokenKwRet (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwSemi (t)); + t = SpliceAfter (t, new TokenKwBrcClose (t)); + + /* + * public Get (integer idx0, integet idx1, ...) { + * integer idx = idx0; + * idx *= this.len1; idx += idx1; + * idx *= this.len2; idx += idx2; + * ... + * return () xmrFixedArrayGet (this.obj, idx); + * } + */ + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, ofType.CopyToken (t)); + t = SpliceAfter (t, new TokenName (t, "Get")); + t = SpliceAfter (t, new TokenKwParOpen (t)); + for (int i = 0; i < rank; i ++) { + if (i > 0) t = SpliceAfter (t, new TokenKwComma (t)); + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "idx" + i)); + } + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAssign (t)); + t = SpliceAfter (t, new TokenName (t, "idx0")); + t = SpliceAfter (t, new TokenKwSemi (t)); + + for (int i = 1; i < rank; i ++) { + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAsnMul (t)); + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAsnAdd (t)); + t = SpliceAfter (t, new TokenName (t, "idx" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + } + + t = SpliceAfter (t, new TokenKwRet (t)); + if (suffix == 'O') { + t = SpliceAfter (t, new TokenKwParOpen (t)); + t = SpliceAfter (t, ofType.CopyToken (t)); + t = SpliceAfter (t, new TokenKwParClose (t)); + } + t = SpliceAfter (t, new TokenName (t, "xmrFixedArrayGet" + suffix)); + t = SpliceAfter (t, new TokenKwParOpen (t)); + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "obj")); + t = SpliceAfter (t, new TokenKwComma (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwSemi (t)); + t = SpliceAfter (t, new TokenKwBrcClose (t)); + + /* + * public void Set (integer idx0, integer idx1, ..., val) { + * integer idx = idx0; + * idx *= this.len1; idx += idx1; + * idx *= this.len2; idx += idx2; + * ... + * xmrFixedArraySet (this.obj, idx, val); + * } + */ + t = SpliceAfter (t, new TokenKwPublic (t)); + t = SpliceAfter (t, new TokenTypeVoid (t)); + t = SpliceAfter (t, new TokenName (t, "Set")); + t = SpliceAfter (t, new TokenKwParOpen (t)); + for (int i = 0; i < rank; i ++) { + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "idx" + i)); + t = SpliceAfter (t, new TokenKwComma (t)); + } + t = SpliceAfter (t, ofType.CopyToken (t)); + t = SpliceAfter (t, new TokenName (t, "val")); + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwBrcOpen (t)); + + t = SpliceAfter (t, new TokenTypeInt (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAssign (t)); + t = SpliceAfter (t, new TokenName (t, "idx0")); + t = SpliceAfter (t, new TokenKwSemi (t)); + for (int i = 1; i < rank; i ++) { + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAsnMul (t)); + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "len" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwAsnAdd (t)); + t = SpliceAfter (t, new TokenName (t, "idx" + i)); + t = SpliceAfter (t, new TokenKwSemi (t)); + } + + t = SpliceAfter (t, new TokenName (t, "xmrFixedArraySet" + suffix)); + t = SpliceAfter (t, new TokenKwParOpen (t)); + t = SpliceAfter (t, new TokenKwThis (t)); + t = SpliceAfter (t, new TokenKwDot (t)); + t = SpliceAfter (t, new TokenName (t, "obj")); + t = SpliceAfter (t, new TokenKwComma (t)); + t = SpliceAfter (t, new TokenName (t, "idx")); + t = SpliceAfter (t, new TokenKwComma (t)); + t = SpliceAfter (t, new TokenName (t, "val")); + t = SpliceAfter (t, new TokenKwParClose (t)); + t = SpliceAfter (t, new TokenKwSemi (t)); + + t = SpliceAfter (t, new TokenKwBrcClose (t)); + t = SpliceAfter (t, new TokenKwBrcClose (t)); + } + return fa; + } + private Token SpliceAfter (Token before, Token after) + { + after.nextToken = before.nextToken; + after.prevToken = before; + before.nextToken = after; + after.nextToken.prevToken = after; + return after; + } + + /** + * @brief Parse script-defined type declarations. + * @param token = points to possible script-defined type keyword + * @param outerSDType = null: top-level type + * else: sub-type of this type + * @param flags = access level (SDT_{PRIVATE,PROTECTED,PUBLIC}) + * @returns true: something defined; else: not a sd type def + */ + private bool ParseDeclSDTypes (ref Token token, TokenDeclSDType outerSDType, uint flags) + { + if (!(token is TokenDeclSDType)) return false; + + TokenDeclSDType decl = (TokenDeclSDType)token; + + /* + * If declaration of generic type, skip it. + * The instantiations get parsed (ie, when we know the concrete types) + * below because they appear as non-generic types. + */ + if (decl.genParams != null) { + token = decl.endToken.nextToken; + return true; + } + + /* + * Also skip over any typedefs. They were all processed in + * ParseSDTypePreScanPassTwo(). + */ + if (decl is TokenDeclSDTypeTypedef) { + token = decl.endToken.nextToken; + return true; + } + + /* + * Non-generic types get parsed inline because we know all their types. + */ + if (decl is TokenDeclSDTypeClass) { + ParseDeclClass (ref token, outerSDType, flags); + return true; + } + if (decl is TokenDeclSDTypeDelegate) { + ParseDeclDelegate (ref token, outerSDType, flags); + return true; + } + if (decl is TokenDeclSDTypeInterface) { + ParseDeclInterface (ref token, outerSDType, flags); + return true; + } + + throw new Exception ("unhandled token " + token.GetType ().ToString ()); + } + + /** + * @brief Parse a class declaration. + * @param token = points to TokenDeclSDTypeClass token + * points just past closing '}' on return + * @param outerSDType = null: this is a top-level class + * else: this class is being defined inside this type + * @param flags = SDT_{PRIVATE,PROTECTED,PUBLIC} + */ + private void ParseDeclClass (ref Token token, TokenDeclSDType outerSDType, uint flags) + { + bool haveExplicitConstructor = false; + Token u = token; + TokenDeclSDTypeClass tokdeclcl; + + tokdeclcl = (TokenDeclSDTypeClass)u; + tokdeclcl.outerSDType = outerSDType; + tokdeclcl.accessLevel = flags; + u = u.nextToken; + + // maybe it is a partial class that had its body snipped out + // by a later partial class declaration of the same class + if (tokdeclcl.endToken == tokdeclcl) { + token = u; + return; + } + + // make this class the currently compiled class + // used for retrieving stuff like 'this' possibly + // in field initialization code + TokenDeclSDType saveCurSDType = currentDeclSDType; + currentDeclSDType = tokdeclcl; + + // next can be ':' followed by list of implemented + // interfaces and one extended class + if (u is TokenKwColon) { + u = u.nextToken; + while (true) { + if (u is TokenTypeSDTypeClass) { + TokenDeclSDTypeClass c = ((TokenTypeSDTypeClass)u).decl; + if (tokdeclcl.extends == null) { + tokdeclcl.extends = c; + } else if (tokdeclcl.extends != c) { + ErrorMsg (u, "can extend from only one class"); + } + } else if (u is TokenTypeSDTypeInterface) { + TokenDeclSDTypeInterface i = ((TokenTypeSDTypeInterface)u).decl; + i.AddToClassDecl (tokdeclcl); + } else { + ErrorMsg (u, "expecting class or interface name"); + if (u is TokenKwBrcOpen) break; + } + u = u.nextToken; + + // allow : in case it is spliced from multiple partial class definitions + if (!(u is TokenKwComma) && !(u is TokenKwColon)) break; + u = u.nextToken; + } + } + + // next must be '{' to open class declaration body + if (!(u is TokenKwBrcOpen)) { + ErrorMsg (u, "expecting { to open class declaration body"); + token = SkipPastSemi (token); + goto ret; + } + token = u.nextToken; + + // push a var frame to put all the class members in + tokdeclcl.members.thisClass = tokdeclcl; + tokenScript.PushVarFrame (tokdeclcl.members); + + /* + * Create a function $instfieldnit to hold all explicit + * instance field initializations. + */ + TokenDeclVar ifiFunc = new TokenDeclVar (tokdeclcl, null, tokenScript); + ifiFunc.name = new TokenName (ifiFunc, "$instfieldinit"); + ifiFunc.retType = new TokenTypeVoid (ifiFunc); + ifiFunc.argDecl = new TokenArgDecl (ifiFunc); + ifiFunc.sdtClass = tokdeclcl; + ifiFunc.sdtFlags = SDT_PUBLIC | SDT_NEW; + TokenStmtBlock ifiBody = new TokenStmtBlock (ifiFunc); + ifiBody.function = ifiFunc; + ifiFunc.body = ifiBody; + tokdeclcl.instFieldInit = ifiFunc; + tokenScript.AddVarEntry (ifiFunc); + + /* + * Create a function $staticfieldnit to hold all explicit + * static field initializations. + */ + TokenDeclVar sfiFunc = new TokenDeclVar (tokdeclcl, null, tokenScript); + sfiFunc.name = new TokenName (sfiFunc, "$staticfieldinit"); + sfiFunc.retType = new TokenTypeVoid (sfiFunc); + sfiFunc.argDecl = new TokenArgDecl (sfiFunc); + sfiFunc.sdtClass = tokdeclcl; + sfiFunc.sdtFlags = SDT_PUBLIC | SDT_STATIC | SDT_NEW; + TokenStmtBlock sfiBody = new TokenStmtBlock (sfiFunc); + sfiBody.function = sfiFunc; + sfiFunc.body = sfiBody; + tokdeclcl.staticFieldInit = sfiFunc; + tokenScript.AddVarEntry (sfiFunc); + + // process declaration statements until '}' + while (!(token is TokenKwBrcClose)) { + if (token is TokenKwSemi) { + token = token.nextToken; + continue; + } + + /* + * Check for all qualifiers. + * typedef has an implied 'public' qualifier. + */ + flags = SDT_PUBLIC; + if (!(token is TokenDeclSDTypeTypedef)) { + flags = ParseQualifierFlags (ref token); + } + + /* + * Parse nested script-defined type definitions. + */ + if (ParseDeclSDTypes (ref token, tokdeclcl, flags)) continue; + + /* + * constant = ; + */ + if (token is TokenKwConst) { + if ((flags & (SDT_ABSTRACT | SDT_NEW | SDT_OVERRIDE | SDT_VIRTUAL)) != 0) { + ErrorMsg (token, "cannot have abstract, new, override or virtual field"); + } + TokenDeclVar var = ParseDeclVar (ref token, null); + if (var != null) { + var.sdtClass = tokdeclcl; + var.sdtFlags = flags | SDT_STATIC; + } + continue; + } + + /* + * ; + * = ; + */ + if ((token is TokenType) && + (token.nextToken is TokenName) && + ((token.nextToken.nextToken is TokenKwSemi) || + (token.nextToken.nextToken is TokenKwAssign))) { + if ((flags & (SDT_ABSTRACT | SDT_FINAL | SDT_NEW | SDT_OVERRIDE | SDT_VIRTUAL)) != 0) { + ErrorMsg (token, "cannot have abstract, final, new, override or virtual field"); + } + TokenDeclVar var = ParseDeclVar (ref token, ifiFunc); + if (var != null) { + var.sdtClass = tokdeclcl; + var.sdtFlags = flags; + if ((flags & SDT_STATIC) != 0) { + // . = ; + TokenLValSField left = new TokenLValSField (var.init); + left.baseType = tokdeclcl.MakeRefToken (var); + left.fieldName = var.name; + DoVarInit (sfiFunc, left, var.init); + } else if (var.init != null) { + // this. = ; + TokenLValIField left = new TokenLValIField (var.init); + left.baseRVal = new TokenRValThis (var.init, tokdeclcl); + left.fieldName = var.name; + DoVarInit (ifiFunc, left, var.init); + } + } + continue; + } + + /* + * [ : ] { [ get { } ] [ set { } ] } + * '[' ... ']' [ : ] { [ get { } ] [ set { } ] } + */ + bool prop = (token is TokenType) && + (token.nextToken is TokenName) && + (token.nextToken.nextToken is TokenKwBrcOpen || + token.nextToken.nextToken is TokenKwColon); + prop |= (token is TokenType) && (token.nextToken is TokenKwBrkOpen); + if (prop) { + TokenDeclVar var = ParseProperty (ref token, (flags & SDT_ABSTRACT) != 0, true); + if (var != null) { + var.sdtClass = tokdeclcl; + var.sdtFlags = flags; + if (var.getProp != null) { + var.getProp.sdtClass = tokdeclcl; + var.getProp.sdtFlags = flags; + } + if (var.setProp != null) { + var.setProp.sdtClass = tokdeclcl; + var.setProp.sdtFlags = flags; + } + } + continue; + } + + /* + * 'constructor' '(' arglist ')' [ ':' [ 'base' ] '(' baseconstructorcall ')' ] '{' body '}' + */ + if (token is TokenKwConstructor) { + ParseSDTClassCtorDecl (ref token, flags, tokdeclcl); + haveExplicitConstructor = true; + continue; + } + + /* + * + * method with explicit return type + */ + if (token is TokenType) { + ParseSDTClassMethodDecl (ref token, flags, tokdeclcl); + continue; + } + + /* + * + * method returning void + */ + if ((token is TokenName) || ((token is TokenKw) && ((TokenKw)token).sdtClassOp)) { + ParseSDTClassMethodDecl (ref token, flags, tokdeclcl); + continue; + } + + /* + * That's all we support in a class declaration. + */ + ErrorMsg (token, "expecting field or method declaration"); + token = SkipPastSemi (token); + } + + /* + * If script didn't specify any constructor, create a default no-argument one. + */ + if (!haveExplicitConstructor) { + TokenDeclVar tokenDeclFunc = new TokenDeclVar (token, null, tokenScript); + tokenDeclFunc.name = new TokenName (token, "$ctor"); + tokenDeclFunc.retType = new TokenTypeVoid (token); + tokenDeclFunc.argDecl = new TokenArgDecl (token); + tokenDeclFunc.sdtClass = tokdeclcl; + tokenDeclFunc.sdtFlags = SDT_PUBLIC | SDT_NEW; + tokenDeclFunc.body = new TokenStmtBlock (token); + tokenDeclFunc.body.function = tokenDeclFunc; + + if (tokdeclcl.extends != null) { + SetUpDefaultBaseCtorCall (tokenDeclFunc); + } else { + // default constructor that doesn't do anything is trivial + tokenDeclFunc.triviality = Triviality.trivial; + } + + tokenScript.AddVarEntry (tokenDeclFunc); + } + + /* + * Skip over the closing brace and pop corresponding var frame. + */ + token = token.nextToken; + tokenScript.PopVarFrame (); + ret: + currentDeclSDType = saveCurSDType; + } + + /** + * @brief Parse out abstract/override/private/protected/public/static/virtual keywords. + * @param token = first token to evaluate + * @returns flags found; token = unprocessed token + */ + private Dictionary foundFlags = new Dictionary (); + private uint ParseQualifierFlags (ref Token token) + { + foundFlags.Clear (); + while (true) { + if (token is TokenKwPrivate) { + token = AddQualifierFlag (token, SDT_PRIVATE, SDT_PROTECTED | SDT_PUBLIC); + continue; + } + if (token is TokenKwProtected) { + token = AddQualifierFlag (token, SDT_PROTECTED, SDT_PRIVATE | SDT_PUBLIC); + continue; + } + if (token is TokenKwPublic) { + token = AddQualifierFlag (token, SDT_PUBLIC, SDT_PRIVATE | SDT_PROTECTED); + continue; + } + + if (token is TokenKwAbstract) { + token = AddQualifierFlag (token, SDT_ABSTRACT, SDT_FINAL | SDT_STATIC | SDT_VIRTUAL); + continue; + } + if (token is TokenKwFinal) { + token = AddQualifierFlag (token, SDT_FINAL, SDT_ABSTRACT | SDT_VIRTUAL); + continue; + } + if (token is TokenKwNew) { + token = AddQualifierFlag (token, SDT_NEW, SDT_OVERRIDE); + continue; + } + if (token is TokenKwOverride) { + token = AddQualifierFlag (token, SDT_OVERRIDE, SDT_NEW | SDT_STATIC); + continue; + } + if (token is TokenKwStatic) { + token = AddQualifierFlag (token, SDT_STATIC, SDT_ABSTRACT | SDT_OVERRIDE | SDT_VIRTUAL); + continue; + } + if (token is TokenKwVirtual) { + token = AddQualifierFlag (token, SDT_VIRTUAL, SDT_ABSTRACT | SDT_STATIC); + continue; + } + break; + } + + uint flags = 0; + foreach (uint flag in foundFlags.Keys) flags |= flag; + if ((flags & (SDT_PRIVATE | SDT_PROTECTED | SDT_PUBLIC)) == 0) { + ErrorMsg (token, "must specify exactly one of private, protected or public"); + } + return flags; + } + private Token AddQualifierFlag (Token token, uint add, uint confs) + { + while (confs != 0) { + uint conf = (uint)(confs & -confs); + Token confToken; + if (foundFlags.TryGetValue (conf, out confToken)) { + ErrorMsg (token, "conflicts with " + confToken.ToString ()); + } + confs -= conf; + } + foundFlags[add] = token; + return token.nextToken; + } + + /** + * @brief Parse a property declaration. + * @param token = points to the property type token on entry + * points just past the closing brace on return + * @param abs = true: property is abstract + * false: property is concrete + * @param imp = allow implemented interface specs + * @returns null: parse failure + * else: property + * + * [ : ] { [ get { } ] [ set { } ] } + * '[' ... ']' [ : ] { [ get { } ] [ set { } ] } + */ + private TokenDeclVar ParseProperty (ref Token token, bool abs, bool imp) + { + /* + * Parse out the property's type and name. + * + */ + TokenType type = (TokenType)token; + TokenName name; + TokenArgDecl args; + Token argTokens = null; + token = token.nextToken; + if (token is TokenKwBrkOpen) { + argTokens = token; + name = new TokenName (token, "$idxprop"); + args = ParseFuncArgs (ref token, typeof (TokenKwBrkClose)); + } else { + name = (TokenName)token; + token = token.nextToken; + args = new TokenArgDecl (token); + } + + /* + * Maybe it claims to implement some interface properties. + * [ ':' [.] ',' ... ] + */ + TokenIntfImpl implements = null; + if (token is TokenKwColon) { + implements = ParseImplements (ref token, name); + if (implements == null) return null; + if (!imp) { + ErrorMsg (token, "cannot implement interface property"); + } + } + + /* + * Should have an opening brace. + */ + if (!(token is TokenKwBrcOpen)) { + ErrorMsg (token, "expect { to open property definition"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + + /* + * Parse out the getter and/or setter. + * 'get' { | ';' } + * 'set' { | ';' } + */ + TokenDeclVar getFunc = null; + TokenDeclVar setFunc = null; + while (!(token is TokenKwBrcClose)) { + + /* + * Maybe create a getter function. + */ + if (token is TokenKwGet) { + getFunc = new TokenDeclVar (token, null, tokenScript); + getFunc.name = new TokenName (token, name.val + "$get"); + getFunc.retType = type; + getFunc.argDecl = args; + getFunc.implements = MakePropertyImplements (implements, "$get"); + + token = token.nextToken; + if (!ParseFunctionBody (ref token, getFunc, abs)) { + getFunc = null; + } else if (!tokenScript.AddVarEntry (getFunc)) { + ErrorMsg (getFunc, "duplicate getter"); + } + continue; + } + + /* + * Maybe create a setter function. + */ + if (token is TokenKwSet) { + TokenArgDecl argDecl = args; + if (getFunc != null) { + argDecl = (argTokens == null) ? new TokenArgDecl (token) : + ParseFuncArgs (ref argTokens, typeof (TokenKwBrkClose)); + } + argDecl.AddArg (type, new TokenName (token, "value")); + + setFunc = new TokenDeclVar (token, null, tokenScript); + setFunc.name = new TokenName (token, name.val + "$set"); + setFunc.retType = new TokenTypeVoid (token); + setFunc.argDecl = argDecl; + setFunc.implements = MakePropertyImplements (implements, "$set"); + + token = token.nextToken; + if (!ParseFunctionBody (ref token, setFunc, abs)) { + setFunc = null; + } else if (!tokenScript.AddVarEntry (setFunc)) { + ErrorMsg (setFunc, "duplicate setter"); + } + continue; + } + + ErrorMsg (token, "expecting get or set"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + + if ((getFunc == null) && (setFunc == null)) { + ErrorMsg (name, "must specify at least one of get, set"); + return null; + } + + /* + * Set up a variable for the property. + */ + TokenDeclVar tokenDeclVar = new TokenDeclVar (name, null, tokenScript); + tokenDeclVar.type = type; + tokenDeclVar.name = name; + tokenDeclVar.getProp = getFunc; + tokenDeclVar.setProp = setFunc; + + /* + * Can't be same name already in block. + */ + if (!tokenScript.AddVarEntry (tokenDeclVar)) { + ErrorMsg (tokenDeclVar, "duplicate member " + name.val); + return null; + } + return tokenDeclVar; + } + + /** + * @brief Given a list of implemented interface methods, create a similar list with suffix added to all the names + * @param implements = original list of implemented interface methods + * @param suffix = string to be added to end of implemented interface method names + * @returns list similar to implements with suffix added to end of implemented interface method names + */ + private TokenIntfImpl MakePropertyImplements (TokenIntfImpl implements, string suffix) + { + TokenIntfImpl gsimpls = null; + for (TokenIntfImpl impl = implements; impl != null; impl = (TokenIntfImpl)impl.nextToken) { + TokenIntfImpl gsimpl = new TokenIntfImpl (impl.intfType, + new TokenName (impl.methName, impl.methName.val + suffix)); + gsimpl.nextToken = gsimpls; + gsimpls = gsimpl; + } + return gsimpls; + } + + /** + * @brief Parse a constructor definition for a script-defined type class. + * @param token = points to 'constructor' keyword + * @param flags = abstract/override/static/virtual flags + * @param tokdeclcl = which script-defined type class this method is in + * @returns with method parsed and cataloged (or error message(s) printed) + */ + private void ParseSDTClassCtorDecl (ref Token token, uint flags, TokenDeclSDTypeClass tokdeclcl) + { + if ((flags & (SDT_ABSTRACT | SDT_OVERRIDE | SDT_STATIC | SDT_VIRTUAL)) != 0) { + ErrorMsg (token, "cannot have abstract, override, static or virtual constructor"); + } + + TokenDeclVar tokenDeclFunc = new TokenDeclVar (token, null, tokenScript); + tokenDeclFunc.name = new TokenName (tokenDeclFunc, "$ctor"); + tokenDeclFunc.retType = new TokenTypeVoid (token); + tokenDeclFunc.sdtClass = tokdeclcl; + tokenDeclFunc.sdtFlags = flags | SDT_NEW; + + token = token.nextToken; + if (!(token is TokenKwParOpen)) { + ErrorMsg (token, "expecting ( for constructor argument list"); + token = SkipPastSemi (token); + return; + } + tokenDeclFunc.argDecl = ParseFuncArgs (ref token, typeof (TokenKwParClose)); + if (tokenDeclFunc.argDecl == null) return; + + TokenDeclVar saveDeclFunc = currentDeclFunc; + currentDeclFunc = tokenDeclFunc; + tokenScript.PushVarFrame (tokenDeclFunc.argDecl.varDict); + try { + /* + * Set up reference to base constructor. + */ + TokenLValBaseField baseCtor = new TokenLValBaseField (token, + new TokenName (token, "$ctor"), + tokdeclcl); + + /* + * Parse any base constructor call as if it were the first statement of the + * constructor itself. + */ + if (token is TokenKwColon) { + token = token.nextToken; + if (token is TokenKwBase) { + token = token.nextToken; + } + if (!(token is TokenKwParOpen)) { + ErrorMsg (token, "expecting ( for base constructor call arguments"); + token = SkipPastSemi (token); + return; + } + TokenRValCall rvc = ParseRValCall (ref token, baseCtor); + if (rvc == null) return; + if (tokdeclcl.extends != null) { + tokenDeclFunc.baseCtorCall = rvc; + tokenDeclFunc.unknownTrivialityCalls.AddLast (rvc); + } else { + ErrorMsg (rvc, "base constructor call cannot be specified if not extending anything"); + } + } else if (tokdeclcl.extends != null) { + + /* + * Caller didn't specify a constructor but we are extending, so we will + * call the extended class's default constructor. + */ + SetUpDefaultBaseCtorCall (tokenDeclFunc); + } + + /* + * Parse the constructor body. + */ + tokenDeclFunc.body = ParseStmtBlock (ref token); + if (tokenDeclFunc.body == null) return; + if (tokenDeclFunc.argDecl == null) return; + } finally { + tokenScript.PopVarFrame (); + currentDeclFunc = saveDeclFunc; + } + + /* + * Add to list of methods defined by this class. + * It has the name "$ctor(argsig)". + */ + if (!tokenScript.AddVarEntry (tokenDeclFunc)) { + ErrorMsg (tokenDeclFunc, "duplicate constructor definition"); + } + } + + /** + * @brief Set up a call from a constructor to its default base constructor. + */ + private void SetUpDefaultBaseCtorCall (TokenDeclVar thisCtor) + { + TokenLValBaseField baseCtor = new TokenLValBaseField (thisCtor, + new TokenName (thisCtor, "$ctor"), + (TokenDeclSDTypeClass)thisCtor.sdtClass); + TokenRValCall rvc = new TokenRValCall (thisCtor); + rvc.meth = baseCtor; + thisCtor.baseCtorCall = rvc; + thisCtor.unknownTrivialityCalls.AddLast (rvc); + } + + /** + * @brief Parse a method definition for a script-defined type class. + * @param token = points to return type (or method name for implicit return type of void) + * @param flags = abstract/override/static/virtual flags + * @param tokdeclcl = which script-defined type class this method is in + * @returns with method parsed and cataloged (or error message(s) printed) + */ + private void ParseSDTClassMethodDecl (ref Token token, uint flags, TokenDeclSDTypeClass tokdeclcl) + { + TokenDeclVar tokenDeclFunc = ParseDeclFunc (ref token, + (flags & SDT_ABSTRACT) != 0, + (flags & SDT_STATIC) == 0, + (flags & SDT_STATIC) == 0); + if (tokenDeclFunc != null) { + tokenDeclFunc.sdtClass = tokdeclcl; + tokenDeclFunc.sdtFlags = flags; + if (!tokenScript.AddVarEntry (tokenDeclFunc)) { + string funcNameSig = tokenDeclFunc.funcNameSig.val; + ErrorMsg (tokenDeclFunc.funcNameSig, "duplicate method name " + funcNameSig); + } + } + } + + /** + * @brief Parse a delegate declaration statement. + * @param token = points to TokenDeclSDTypeDelegate token on entry + * points just past ';' on return + * @param outerSDType = null: this is a top-level delegate + * else: this delegate is being defined inside this type + * @param flags = SDT_{PRIVATE,PROTECTED,PUBLIC} + */ + private void ParseDeclDelegate (ref Token token, TokenDeclSDType outerSDType, uint flags) + { + Token u = token; + TokenDeclSDTypeDelegate tokdecldel; + TokenType retType; + + tokdecldel = (TokenDeclSDTypeDelegate)u; + tokdecldel.outerSDType = outerSDType; + tokdecldel.accessLevel = flags; + + // first thing following that should be return type + // but we will fill in 'void' if it is missing + u = u.nextToken; + if (u is TokenType) { + retType = (TokenType)u; + u = u.nextToken; + } else { + retType = new TokenTypeVoid (u); + } + + // get list of argument types until we see a ')' + List args = new List (); + bool first = true; + do { + if (first) { + + // first time should have '(' ')' or '(' + if (!(u is TokenKwParOpen)) { + ErrorMsg (u, "expecting ( after delegate name"); + token = SkipPastSemi (token); + return; + } + first = false; + u = u.nextToken; + if (u is TokenKwParClose) break; + } else { + + // other times should have ',' + if (!(u is TokenKwComma)) { + ErrorMsg (u, "expecting , separating arg types"); + token = SkipPastSemi (token); + return; + } + u = u.nextToken; + } + if (!(u is TokenType)) { + ErrorMsg (u, "expecting argument type"); + token = SkipPastSemi (token); + return; + } + args.Add ((TokenType)u); + u = u.nextToken; + + // they can put in a dummy name that we toss out + if (u is TokenName) u = u.nextToken; + + // scanning ends on a ')' + } while (!(u is TokenKwParClose)); + + // fill in the return type and argment type array + tokdecldel.SetRetArgTypes (retType, args.ToArray ()); + + // and finally must have ';' to finish the delegate declaration statement + u = u.nextToken; + if (!(u is TokenKwSemi)) { + ErrorMsg (u, "expecting ; after ) in delegate"); + token = SkipPastSemi (token); + return; + } + token = u.nextToken; + } + + /** + * @brief Parse an interface declaration. + * @param token = points to TokenDeclSDTypeInterface token on entry + * points just past closing '}' on return + * @param outerSDType = null: this is a top-level interface + * else: this interface is being defined inside this type + * @param flags = SDT_{PRIVATE,PROTECTED,PUBLIC} + */ + private void ParseDeclInterface (ref Token token, TokenDeclSDType outerSDType, uint flags) + { + Token u = token; + TokenDeclSDTypeInterface tokdeclin; + + tokdeclin = (TokenDeclSDTypeInterface)u; + tokdeclin.outerSDType = outerSDType; + tokdeclin.accessLevel = flags; + u = u.nextToken; + + // next can be ':' followed by list of implemented interfaces + if (u is TokenKwColon) { + u = u.nextToken; + while (true) { + if (u is TokenTypeSDTypeInterface) { + TokenDeclSDTypeInterface i = ((TokenTypeSDTypeInterface)u).decl; + if (!tokdeclin.implements.Contains (i)) { + tokdeclin.implements.Add (i); + } + } else { + ErrorMsg (u, "expecting interface name"); + if (u is TokenKwBrcOpen) break; + } + u = u.nextToken; + if (!(u is TokenKwComma)) break; + u = u.nextToken; + } + } + + // next must be '{' to open interface declaration body + if (!(u is TokenKwBrcOpen)) { + ErrorMsg (u, "expecting { to open interface declaration body"); + token = SkipPastSemi (token); + return; + } + token = u.nextToken; + + // start a var definition frame to collect the interface members + tokenScript.PushVarFrame (false); + tokdeclin.methsNProps = tokenScript.variablesStack; + + // process declaration statements until '}' + while (!(token is TokenKwBrcClose)) { + if (token is TokenKwSemi) { + token = token.nextToken; + continue; + } + + /* + * Parse nested script-defined type definitions. + */ + if (ParseDeclSDTypes (ref token, tokdeclin, SDT_PUBLIC)) continue; + + /* + * ; + * abstract method with explicit return type + */ + if ((token is TokenType) && + (token.nextToken is TokenName) && + (token.nextToken.nextToken is TokenKwParOpen)) { + Token name = token.nextToken; + TokenDeclVar tokenDeclFunc = ParseDeclFunc (ref token, true, false, false); + if (tokenDeclFunc == null) continue; + if (!tokenScript.AddVarEntry (tokenDeclFunc)) { + ErrorMsg (name, "duplicate method name"); + continue; + } + continue; + } + + /* + * ; + * abstract method returning void + */ + if ((token is TokenName) && + (token.nextToken is TokenKwParOpen)) { + Token name = token; + TokenDeclVar tokenDeclFunc = ParseDeclFunc (ref token, true, false, false); + if (tokenDeclFunc == null) continue; + if (!tokenScript.AddVarEntry (tokenDeclFunc)) { + ErrorMsg (name, "duplicate method name"); + } + continue; + } + + /* + * { [ get ; ] [ set ; ] } + * '[' ... ']' { [ get ; ] [ set ; ] } + * abstract property + */ + bool prop = (token is TokenType) && + (token.nextToken is TokenName) && + (token.nextToken.nextToken is TokenKwBrcOpen || + token.nextToken.nextToken is TokenKwColon); + prop |= (token is TokenType) && (token.nextToken is TokenKwBrkOpen); + if (prop) { + ParseProperty (ref token, true, false); + continue; + } + + /* + * That's all we support in an interface declaration. + */ + ErrorMsg (token, "expecting method or property prototype"); + token = SkipPastSemi (token); + } + + /* + * Skip over the closing brace and pop the corresponding var frame. + */ + token = token.nextToken; + tokenScript.PopVarFrame (); + } + + /** + * @brief parse state body (including all its event handlers) + * @param token = points to TokenKwBrcOpen + * @returns null: state body parse error + * else: token representing state + * token = points past close brace + */ + private TokenStateBody ParseStateBody (ref Token token) + { + TokenStateBody tokenStateBody = new TokenStateBody (token); + + if (!(token is TokenKwBrcOpen)) { + ErrorMsg (token, "expecting { at beg of state"); + token = SkipPastSemi (token); + return null; + } + + token = token.nextToken; + while (!(token is TokenKwBrcClose)) { + if (token is TokenEnd) { + ErrorMsg (tokenStateBody, "eof parsing state body"); + return null; + } + TokenDeclVar tokenDeclFunc = ParseDeclFunc (ref token, false, false, false); + if (tokenDeclFunc == null) return null; + if (!(tokenDeclFunc.retType is TokenTypeVoid)) { + ErrorMsg (tokenDeclFunc.retType, "event handlers don't have return types"); + return null; + } + tokenDeclFunc.nextToken = tokenStateBody.eventFuncs; + tokenStateBody.eventFuncs = tokenDeclFunc; + } + token = token.nextToken; + return tokenStateBody; + } + + /** + * @brief Parse a function declaration, including its arg list and body + * @param token = points to function return type token (or function name token if return type void) + * @param abs = false: concrete function; true: abstract declaration + * @param imp = allow implemented interface specs + * @param ops = accept operators (==, +, etc) for function name + * @returns null: error parsing function definition + * else: function declaration + * token = advanced just past function, ie, just past the closing brace + */ + private TokenDeclVar ParseDeclFunc (ref Token token, bool abs, bool imp, bool ops) + { + TokenType retType; + if (token is TokenType) { + retType = (TokenType)token; + token = token.nextToken; + } else { + retType = new TokenTypeVoid (token); + } + + TokenName simpleName; + if ((token is TokenKw) && ((TokenKw)token).sdtClassOp) { + if (!ops) ErrorMsg (token, "operator functions disallowed in static contexts"); + simpleName = new TokenName (token, "$op" + token.ToString ()); + } else if (!(token is TokenName)) { + ErrorMsg (token, "expecting function name"); + token = SkipPastSemi (token); + return null; + } else { + simpleName = (TokenName)token; + } + token = token.nextToken; + + return ParseDeclFunc (ref token, abs, imp, retType, simpleName); + } + + /** + * @brief Parse a function declaration, including its arg list and body + * This version enters with token pointing to the '(' at beginning of arg list + * @param token = points to the '(' of the arg list + * @param abs = false: concrete function; true: abstract declaration + * @param imp = allow implemented interface specs + * @param retType = return type (TokenTypeVoid if void, never null) + * @param simpleName = function name without any signature + * @returns null: error parsing remainder of function definition + * else: function declaration + * token = advanced just past function, ie, just past the closing brace + */ + private TokenDeclVar ParseDeclFunc (ref Token token, bool abs, bool imp, TokenType retType, TokenName simpleName) + { + TokenDeclVar tokenDeclFunc = new TokenDeclVar (simpleName, null, tokenScript); + tokenDeclFunc.name = simpleName; + tokenDeclFunc.retType = retType; + tokenDeclFunc.argDecl = ParseFuncArgs (ref token, typeof (TokenKwParClose)); + if (tokenDeclFunc.argDecl == null) return null; + + if (token is TokenKwColon) { + tokenDeclFunc.implements = ParseImplements (ref token, simpleName); + if (tokenDeclFunc.implements == null) return null; + if (!imp) { + ErrorMsg (tokenDeclFunc.implements, "cannot implement interface method"); + tokenDeclFunc.implements = null; + } + } + + if (!ParseFunctionBody (ref token, tokenDeclFunc, abs)) return null; + if (tokenDeclFunc.argDecl == null) return null; + return tokenDeclFunc; + } + + /** + * @brief Parse interface implementation list. + * @param token = points to ':' on entry + * points just past list on return + * @param simpleName = simple name (no arg signature) of method/property that + * is implementing the interface method/property + * @returns list of implemented interface methods/properties + */ + private TokenIntfImpl ParseImplements (ref Token token, TokenName simpleName) + { + TokenIntfImpl implements = null; + do { + token = token.nextToken; + if (!(token is TokenTypeSDTypeInterface)) { + ErrorMsg (token, "expecting interface type"); + token = SkipPastSemi (token); + return null; + } + TokenTypeSDTypeInterface intfType = (TokenTypeSDTypeInterface)token; + token = token.nextToken; + TokenName methName = simpleName; + if ((token is TokenKwDot) && (token.nextToken is TokenName)) { + methName = (TokenName)token.nextToken; + token = token.nextToken.nextToken; + } + TokenIntfImpl intfImpl = new TokenIntfImpl (intfType, methName); + intfImpl.nextToken = implements; + implements = intfImpl; + } while (token is TokenKwComma); + return implements; + } + + + /** + * @brief Parse function declaration's body + * @param token = points to body, ie, ';' or '{' + * @param tokenDeclFunc = function being declared + * @param abs = false: concrete function; true: abstract declaration + * @returns whether or not the function definition parsed correctly + */ + private bool ParseFunctionBody (ref Token token, TokenDeclVar tokenDeclFunc, bool abs) + { + if (token is TokenKwSemi) { + if (!abs) { + ErrorMsg (token, "concrete function must have body"); + token = SkipPastSemi (token); + return false; + } + token = token.nextToken; + return true; + } + + /* + * Declare this function as being the one currently being processed + * for anything that cares. We also start a variable frame that + * includes all the declared parameters. + */ + TokenDeclVar saveDeclFunc = currentDeclFunc; + currentDeclFunc = tokenDeclFunc; + tokenScript.PushVarFrame (tokenDeclFunc.argDecl.varDict); + + /* + * Now parse the function statement block. + */ + tokenDeclFunc.body = ParseStmtBlock (ref token); + + /* + * Pop the var frame that contains the arguments. + */ + tokenScript.PopVarFrame (); + currentDeclFunc = saveDeclFunc; + + /* + * Check final errors. + */ + if (tokenDeclFunc.body == null) return false; + if (abs) { + ErrorMsg (tokenDeclFunc.body, "abstract function must not have body"); + tokenDeclFunc.body = null; + return false; + } + return true; + } + + + /** + * @brief Parse statement + * @param token = first token of statement + * @returns null: parse error + * else: token representing whole statement + * token = points past statement + */ + private TokenStmt ParseStmt (ref Token token) + { + /* + * Statements that begin with a specific keyword. + */ + if (token is TokenKwAt) return ParseStmtLabel (ref token); + if (token is TokenKwBrcOpen) return ParseStmtBlock (ref token); + if (token is TokenKwBreak) return ParseStmtBreak (ref token); + if (token is TokenKwCont) return ParseStmtCont (ref token); + if (token is TokenKwDo) return ParseStmtDo (ref token); + if (token is TokenKwFor) return ParseStmtFor (ref token); + if (token is TokenKwForEach) return ParseStmtForEach (ref token); + if (token is TokenKwIf) return ParseStmtIf (ref token); + if (token is TokenKwJump) return ParseStmtJump (ref token); + if (token is TokenKwRet) return ParseStmtRet (ref token); + if (token is TokenKwSemi) return ParseStmtNull (ref token); + if (token is TokenKwState) return ParseStmtState (ref token); + if (token is TokenKwSwitch) return ParseStmtSwitch (ref token); + if (token is TokenKwThrow) return ParseStmtThrow (ref token); + if (token is TokenKwTry) return ParseStmtTry (ref token); + if (token is TokenKwWhile) return ParseStmtWhile (ref token); + + /* + * Try to parse anything else as an expression, possibly calling + * something and/or writing to a variable. + */ + TokenRVal tokenRVal = ParseRVal (ref token, semiOnly); + if (tokenRVal != null) { + TokenStmtRVal tokenStmtRVal = new TokenStmtRVal (tokenRVal); + tokenStmtRVal.rVal = tokenRVal; + return tokenStmtRVal; + } + + /* + * Who knows what it is... + */ + ErrorMsg (token, "unknown statement"); + token = SkipPastSemi (token); + return null; + } + + /** + * @brief parse a statement block, ie, group of statements between braces + * @param token = points to { token + * @returns null: error parsing + * else: statements bundled in this token + * token = advanced just past the } token + */ + private TokenStmtBlock ParseStmtBlock (ref Token token) + { + if (!(token is TokenKwBrcOpen)) { + ErrorMsg (token, "statement block body must begin with a {"); + token = SkipPastSemi (token); + return null; + } + TokenStmtBlock tokenStmtBlock = new TokenStmtBlock (token); + tokenStmtBlock.function = currentDeclFunc; + tokenStmtBlock.outerStmtBlock = currentStmtBlock; + currentStmtBlock = tokenStmtBlock; + VarDict outerVariablesStack = tokenScript.variablesStack; + try { + Token prevStmt = null; + token = token.nextToken; + while (!(token is TokenKwBrcClose)) { + if (token is TokenEnd) { + ErrorMsg (tokenStmtBlock, "missing }"); + return null; + } + Token thisStmt; + if (((token is TokenType) && (token.nextToken is TokenName)) || + (token is TokenKwConst)) { + thisStmt = ParseDeclVar (ref token, null); + } else { + thisStmt = ParseStmt (ref token); + } + if (thisStmt == null) return null; + if (prevStmt == null) tokenStmtBlock.statements = thisStmt; + else prevStmt.nextToken = thisStmt; + prevStmt = thisStmt; + } + token = token.nextToken; + } finally { + tokenScript.variablesStack = outerVariablesStack; + currentStmtBlock = tokenStmtBlock.outerStmtBlock; + } + return tokenStmtBlock; + } + + /** + * @brief parse a 'break' statement + * @param token = points to break keyword token + * @returns null: error parsing + * else: statements bundled in this token + * token = advanced just past the ; token + */ + private TokenStmtBreak ParseStmtBreak (ref Token token) + { + TokenStmtBreak tokenStmtBreak = new TokenStmtBreak (token); + token = token.nextToken; + if (!(token is TokenKwSemi)) { + ErrorMsg (token, "expecting ;"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + return tokenStmtBreak; + } + + /** + * @brief parse a 'continue' statement + * @param token = points to continue keyword token + * @returns null: error parsing + * else: statements bundled in this token + * token = advanced just past the ; token + */ + private TokenStmtCont ParseStmtCont (ref Token token) + { + TokenStmtCont tokenStmtCont = new TokenStmtCont (token); + token = token.nextToken; + if (!(token is TokenKwSemi)) { + ErrorMsg (token, "expecting ;"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + return tokenStmtCont; + } + + /** + * @brief parse a 'do' statement + * @params token = points to 'do' keyword token + * @returns null: parse error + * else: pointer to token encapsulating the do statement, including body + * token = advanced just past the body statement + */ + private TokenStmtDo ParseStmtDo (ref Token token) + { + currentDeclFunc.triviality = Triviality.complex; + TokenStmtDo tokenStmtDo = new TokenStmtDo (token); + token = token.nextToken; + tokenStmtDo.bodyStmt = ParseStmt (ref token); + if (tokenStmtDo.bodyStmt == null) return null; + if (!(token is TokenKwWhile)) { + ErrorMsg (token, "expecting while clause"); + return null; + } + token = token.nextToken; + tokenStmtDo.testRVal = ParseRValParen (ref token); + if (tokenStmtDo.testRVal == null) return null; + if (!(token is TokenKwSemi)) { + ErrorMsg (token, "while clause must terminate on semicolon"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + return tokenStmtDo; + } + + /** + * @brief parse a for statement + * @param token = points to 'for' keyword token + * @returns null: parse error + * else: pointer to encapsulated for statement token + * token = advanced just past for body statement + */ + private TokenStmt ParseStmtFor (ref Token token) + { + currentDeclFunc.triviality = Triviality.complex; + + /* + * Create encapsulating token and skip past 'for (' + */ + TokenStmtFor tokenStmtFor = new TokenStmtFor (token); + token = token.nextToken; + if (!(token is TokenKwParOpen)) { + ErrorMsg (token, "for must be followed by ("); + return null; + } + token = token.nextToken; + + /* + * If a plain for, ie, not declaring a variable, it's straightforward. + */ + if (!(token is TokenType)) { + tokenStmtFor.initStmt = ParseStmt (ref token); + if (tokenStmtFor.initStmt == null) return null; + return ParseStmtFor2 (tokenStmtFor, ref token) ? tokenStmtFor : null; + } + + /* + * Initialization declares a variable, so encapsulate it in a block so + * variable has scope only in the for statement, including its body. + */ + TokenStmtBlock forStmtBlock = new TokenStmtBlock (tokenStmtFor); + forStmtBlock.outerStmtBlock = currentStmtBlock; + forStmtBlock.function = currentDeclFunc; + currentStmtBlock = forStmtBlock; + tokenScript.PushVarFrame (true); + + TokenDeclVar tokenDeclVar = ParseDeclVar (ref token, null); + if (tokenDeclVar == null) { + tokenScript.PopVarFrame (); + currentStmtBlock = forStmtBlock.outerStmtBlock; + return null; + } + + forStmtBlock.statements = tokenDeclVar; + tokenDeclVar.nextToken = tokenStmtFor; + + bool ok = ParseStmtFor2 (tokenStmtFor, ref token); + tokenScript.PopVarFrame (); + currentStmtBlock = forStmtBlock.outerStmtBlock; + return ok ? forStmtBlock : null; + } + + /** + * @brief parse rest of 'for' statement starting with the test expression. + * @param tokenStmtFor = token encapsulating the for statement + * @param token = points to test expression + * @returns false: parse error + * true: successful + * token = points just past body statement + */ + private bool ParseStmtFor2 (TokenStmtFor tokenStmtFor, ref Token token) + { + if (token is TokenKwSemi) { + token = token.nextToken; + } else { + tokenStmtFor.testRVal = ParseRVal (ref token, semiOnly); + if (tokenStmtFor.testRVal == null) return false; + } + if (token is TokenKwParClose) { + token = token.nextToken; + } else { + tokenStmtFor.incrRVal = ParseRVal (ref token, parCloseOnly); + if (tokenStmtFor.incrRVal == null) return false; + } + tokenStmtFor.bodyStmt = ParseStmt (ref token); + return tokenStmtFor.bodyStmt != null; + } + + /** + * @brief parse a foreach statement + * @param token = points to 'foreach' keyword token + * @returns null: parse error + * else: pointer to encapsulated foreach statement token + * token = advanced just past for body statement + */ + private TokenStmt ParseStmtForEach (ref Token token) + { + currentDeclFunc.triviality = Triviality.complex; + + /* + * Create encapsulating token and skip past 'foreach (' + */ + TokenStmtForEach tokenStmtForEach = new TokenStmtForEach (token); + token = token.nextToken; + if (!(token is TokenKwParOpen)) { + ErrorMsg (token, "foreach must be followed by ("); + return null; + } + token = token.nextToken; + + if (token is TokenName) { + tokenStmtForEach.keyLVal = new TokenLValName ((TokenName)token, tokenScript.variablesStack); + token = token.nextToken; + } + if (!(token is TokenKwComma)) { + ErrorMsg (token, "expecting comma"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + if (token is TokenName) { + tokenStmtForEach.valLVal = new TokenLValName ((TokenName)token, tokenScript.variablesStack); + token = token.nextToken; + } + if (!(token is TokenKwIn)) { + ErrorMsg (token, "expecting 'in'"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + tokenStmtForEach.arrayRVal = GetOperand (ref token); + if (tokenStmtForEach.arrayRVal == null) return null; + if (!(token is TokenKwParClose)) { + ErrorMsg (token, "expecting )"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + tokenStmtForEach.bodyStmt = ParseStmt (ref token); + if (tokenStmtForEach.bodyStmt == null) return null; + return tokenStmtForEach; + } + + private TokenStmtIf ParseStmtIf (ref Token token) + { + TokenStmtIf tokenStmtIf = new TokenStmtIf (token); + token = token.nextToken; + tokenStmtIf.testRVal = ParseRValParen (ref token); + if (tokenStmtIf.testRVal == null) return null; + tokenStmtIf.trueStmt = ParseStmt (ref token); + if (tokenStmtIf.trueStmt == null) return null; + if (token is TokenKwElse) { + token = token.nextToken; + tokenStmtIf.elseStmt = ParseStmt (ref token); + if (tokenStmtIf.elseStmt == null) return null; + } + return tokenStmtIf; + } + + private TokenStmtJump ParseStmtJump (ref Token token) + { + /* + * Create jump statement token to encapsulate the whole statement. + */ + TokenStmtJump tokenStmtJump = new TokenStmtJump (token); + token = token.nextToken; + if (!(token is TokenName) || !(token.nextToken is TokenKwSemi)) { + ErrorMsg (token, "expecting label;"); + token = SkipPastSemi (token); + return null; + } + tokenStmtJump.label = (TokenName)token; + token = token.nextToken.nextToken; + + /* + * If label is already defined, it means this is a backward (looping) + * jump, so remember the label has backward jump references. + * We also then assume the function is complex, ie, it has a loop. + */ + if (currentDeclFunc.labels.ContainsKey (tokenStmtJump.label.val)) { + currentDeclFunc.labels[tokenStmtJump.label.val].hasBkwdRefs = true; + currentDeclFunc.triviality = Triviality.complex; + } + + return tokenStmtJump; + } + + /** + * @brief parse a jump target label statement + * @param token = points to the '@' token + * @returns null: error parsing + * else: the label + * token = advanced just past the ; + */ + private TokenStmtLabel ParseStmtLabel (ref Token token) + { + if (!(token.nextToken is TokenName) || + !(token.nextToken.nextToken is TokenKwSemi)) { + ErrorMsg (token, "invalid label"); + token = SkipPastSemi (token); + return null; + } + TokenStmtLabel stmtLabel = new TokenStmtLabel (token); + stmtLabel.name = (TokenName)token.nextToken; + stmtLabel.block = currentStmtBlock; + if (currentDeclFunc.labels.ContainsKey (stmtLabel.name.val)) { + ErrorMsg (token.nextToken, "duplicate label"); + ErrorMsg (currentDeclFunc.labels[stmtLabel.name.val], "previously defined here"); + token = SkipPastSemi (token); + return null; + } + currentDeclFunc.labels.Add (stmtLabel.name.val, stmtLabel); + token = token.nextToken.nextToken.nextToken; + return stmtLabel; + } + + private TokenStmtNull ParseStmtNull (ref Token token) + { + TokenStmtNull tokenStmtNull = new TokenStmtNull (token); + token = token.nextToken; + return tokenStmtNull; + } + + private TokenStmtRet ParseStmtRet (ref Token token) + { + TokenStmtRet tokenStmtRet = new TokenStmtRet (token); + token = token.nextToken; + if (token is TokenKwSemi) { + token = token.nextToken; + } else { + tokenStmtRet.rVal = ParseRVal (ref token, semiOnly); + if (tokenStmtRet.rVal == null) return null; + } + return tokenStmtRet; + } + + private TokenStmtSwitch ParseStmtSwitch (ref Token token) + { + TokenStmtSwitch tokenStmtSwitch = new TokenStmtSwitch (token); + token = token.nextToken; + tokenStmtSwitch.testRVal = ParseRValParen (ref token); + if (tokenStmtSwitch.testRVal == null) return null; + if (!(token is TokenKwBrcOpen)) { + ErrorMsg (token, "expecting open brace"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + TokenSwitchCase tokenSwitchCase = null; + bool haveComplained = false; + while (!(token is TokenKwBrcClose)) { + if (token is TokenKwCase) { + tokenSwitchCase = new TokenSwitchCase (token); + if (tokenStmtSwitch.lastCase == null) { + tokenStmtSwitch.cases = tokenSwitchCase; + } else { + tokenStmtSwitch.lastCase.nextCase = tokenSwitchCase; + } + tokenStmtSwitch.lastCase = tokenSwitchCase; + + token = token.nextToken; + tokenSwitchCase.rVal1 = ParseRVal (ref token, colonOrDotDotDot); + if (tokenSwitchCase.rVal1 == null) return null; + if (token is TokenKwDotDotDot) { + token = token.nextToken; + tokenSwitchCase.rVal2 = ParseRVal (ref token, colonOnly); + if (tokenSwitchCase.rVal2 == null) return null; + } else { + if (!(token is TokenKwColon)) { + ErrorMsg (token, "expecting : or ..."); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + } + } else if (token is TokenKwDefault) { + tokenSwitchCase = new TokenSwitchCase (token); + if (tokenStmtSwitch.lastCase == null) { + tokenStmtSwitch.cases = tokenSwitchCase; + } else { + tokenStmtSwitch.lastCase.nextCase = tokenSwitchCase; + } + tokenStmtSwitch.lastCase = tokenSwitchCase; + + token = token.nextToken; + if (!(token is TokenKwColon)) { + ErrorMsg (token, "expecting :"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + } else if (tokenSwitchCase != null) { + TokenStmt bodyStmt = ParseStmt (ref token); + if (bodyStmt == null) return null; + if (tokenSwitchCase.lastStmt == null) { + tokenSwitchCase.stmts = bodyStmt; + } else { + tokenSwitchCase.lastStmt.nextToken = bodyStmt; + } + tokenSwitchCase.lastStmt = bodyStmt; + bodyStmt.nextToken = null; + } else if (!haveComplained) { + ErrorMsg (token, "expecting case or default label"); + token = SkipPastSemi (token); + haveComplained = true; + } + } + token = token.nextToken; + return tokenStmtSwitch; + } + + private TokenStmtState ParseStmtState (ref Token token) + { + TokenStmtState tokenStmtState = new TokenStmtState (token); + token = token.nextToken; + if ((!(token is TokenName) && !(token is TokenKwDefault)) || !(token.nextToken is TokenKwSemi)) { + ErrorMsg (token, "expecting state;"); + token = SkipPastSemi (token); + return null; + } + if (token is TokenName) { + tokenStmtState.state = (TokenName)token; + } + token = token.nextToken.nextToken; + return tokenStmtState; + } + + private TokenStmtThrow ParseStmtThrow (ref Token token) + { + TokenStmtThrow tokenStmtThrow = new TokenStmtThrow (token); + token = token.nextToken; + if (token is TokenKwSemi) { + token = token.nextToken; + } else { + tokenStmtThrow.rVal = ParseRVal (ref token, semiOnly); + if (tokenStmtThrow.rVal == null) return null; + } + return tokenStmtThrow; + } + + /** + * @brief Parse a try { ... } catch { ... } finally { ... } statement block + * @param token = point to 'try' keyword on entry + * points past last '}' processed on return + * @returns encapsulated try/catch/finally or null if parsing error + */ + private TokenStmtTry ParseStmtTry (ref Token token) + { + /* + * Parse out the 'try { ... }' part + */ + Token tryKw = token; + token = token.nextToken; + TokenStmt body = ParseStmtBlock (ref token); + + while (true) { + TokenStmtTry tokenStmtTry; + if (token is TokenKwCatch) { + if (!(token.nextToken is TokenKwParOpen) || + !(token.nextToken.nextToken is TokenType) || + !(token.nextToken.nextToken.nextToken is TokenName) || + !(token.nextToken.nextToken.nextToken.nextToken is TokenKwParClose)) { + ErrorMsg (token, "catch must be followed by ( ) { ... }"); + return null; + } + token = token.nextToken.nextToken; // skip over 'catch' '(' + TokenDeclVar tag = new TokenDeclVar (token.nextToken, currentDeclFunc, tokenScript); + tag.type = (TokenType)token; + token = token.nextToken; // skip over + tag.name = (TokenName)token; + token = token.nextToken.nextToken; // skip over ')' + + if ((!(tag.type is TokenTypeExc)) && (!(tag.type is TokenTypeStr))) { + ErrorMsg (tag.type, "must be type 'exception' or 'string'"); + } + + tokenStmtTry = new TokenStmtTry (tryKw); + tokenStmtTry.tryStmt = WrapTryCatFinInBlock (body); + tokenStmtTry.catchVar = tag; + tokenScript.PushVarFrame (false); + tokenScript.AddVarEntry (tag); + tokenStmtTry.catchStmt = ParseStmtBlock (ref token); + tokenScript.PopVarFrame (); + if (tokenStmtTry.catchStmt == null) return null; + tokenStmtTry.tryStmt.isTry = true; + tokenStmtTry.tryStmt.tryStmt = tokenStmtTry; + tokenStmtTry.catchStmt.isCatch = true; + tokenStmtTry.catchStmt.tryStmt = tokenStmtTry; + } + else if (token is TokenKwFinally) { + token = token.nextToken; + + tokenStmtTry = new TokenStmtTry (tryKw); + tokenStmtTry.tryStmt = WrapTryCatFinInBlock (body); + tokenStmtTry.finallyStmt = ParseStmtBlock (ref token); + if (tokenStmtTry.finallyStmt == null) return null; + tokenStmtTry.tryStmt.isTry = true; + tokenStmtTry.tryStmt.tryStmt = tokenStmtTry; + tokenStmtTry.finallyStmt.isFinally = true; + tokenStmtTry.finallyStmt.tryStmt = tokenStmtTry; + } + else break; + + body = tokenStmtTry; + } + + if (!(body is TokenStmtTry)) { + ErrorMsg (body, "try must have a matching catch and/or finally"); + return null; + } + return (TokenStmtTry)body; + } + + /** + * @brief Wrap a possible try/catch/finally statement block in a block statement. + * + * Given body = try { } catch (string s) { } + * + * we return { try { } catch (string s) { } } + * + * @param body = a TokenStmtTry or a TokenStmtBlock + * @returns a TokenStmtBlock + */ + private TokenStmtBlock WrapTryCatFinInBlock (TokenStmt body) + { + if (body is TokenStmtBlock) return (TokenStmtBlock)body; + + TokenStmtTry innerTry = (TokenStmtTry)body; + + TokenStmtBlock wrapper = new TokenStmtBlock (body); + wrapper.statements = innerTry; + wrapper.outerStmtBlock = currentStmtBlock; + wrapper.function = currentDeclFunc; + + innerTry.tryStmt.outerStmtBlock = wrapper; + if (innerTry.catchStmt != null) innerTry.catchStmt.outerStmtBlock = wrapper; + if (innerTry.finallyStmt != null) innerTry.finallyStmt.outerStmtBlock = wrapper; + + return wrapper; + } + + private TokenStmtWhile ParseStmtWhile (ref Token token) + { + currentDeclFunc.triviality = Triviality.complex; + TokenStmtWhile tokenStmtWhile = new TokenStmtWhile (token); + token = token.nextToken; + tokenStmtWhile.testRVal = ParseRValParen (ref token); + if (tokenStmtWhile.testRVal == null) return null; + tokenStmtWhile.bodyStmt = ParseStmt (ref token); + if (tokenStmtWhile.bodyStmt == null) return null; + return tokenStmtWhile; + } + + /** + * @brief parse a variable declaration statement, including init value if any. + * @param token = points to type or 'constant' token + * @param initFunc = null: parsing a local var declaration + * put initialization code in .init + * else: parsing a global var or field var declaration + * put initialization code in initFunc.body + * @returns null: parsing error + * else: variable declaration encapulating token + * token = advanced just past semi-colon + * variables = modified to include the new variable + */ + private TokenDeclVar ParseDeclVar (ref Token token, TokenDeclVar initFunc) + { + TokenDeclVar tokenDeclVar = new TokenDeclVar (token.nextToken, currentDeclFunc, tokenScript); + + /* + * Handle constant declaration. + * It ends up in the declared variables list for the statement block just like + * any other variable, except it has .constant = true. + * The code generator will test that the initialization expression is constant. + * + * constant = ; + */ + if (token is TokenKwConst) { + token = token.nextToken; + if (!(token is TokenName)) { + ErrorMsg (token, "expecting constant name"); + token = SkipPastSemi (token); + return null; + } + tokenDeclVar.name = (TokenName)token; + token = token.nextToken; + if (!(token is TokenKwAssign)) { + ErrorMsg (token, "expecting ="); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + TokenRVal rVal = ParseRVal (ref token, semiOnly); + if (rVal == null) return null; + tokenDeclVar.init = rVal; + tokenDeclVar.constant = true; + } + + /* + * Otherwise, normal variable declaration with optional initialization value. + */ + else { + /* + * Build basic encapsulating token with type and name. + */ + tokenDeclVar.type = (TokenType)token; + token = token.nextToken; + if (!(token is TokenName)) { + ErrorMsg (token, "expecting variable name"); + token = SkipPastSemi (token); + return null; + } + tokenDeclVar.name = (TokenName)token; + token = token.nextToken; + + /* + * If just a ;, there is no explicit initialization value. + * Otherwise, look for an =RVal; expression that has init value. + */ + if (token is TokenKwSemi) { + token = token.nextToken; + if (initFunc != null) { + tokenDeclVar.init = TokenRValInitDef.Construct (tokenDeclVar); + } + } else if (token is TokenKwAssign) { + token = token.nextToken; + if (initFunc != null) { + currentDeclFunc = initFunc; + tokenDeclVar.init = ParseRVal (ref token, semiOnly); + currentDeclFunc = null; + } else { + tokenDeclVar.init = ParseRVal (ref token, semiOnly); + } + if (tokenDeclVar.init == null) return null; + } else { + ErrorMsg (token, "expecting = or ;"); + token = SkipPastSemi (token); + return null; + } + } + + /* + * If doing local vars, each var goes in its own var frame, + * to make sure no code before this point can reference it. + */ + if (currentStmtBlock != null) { + tokenScript.PushVarFrame (true); + } + + /* + * Can't be same name already in block. + */ + if (!tokenScript.AddVarEntry (tokenDeclVar)) { + ErrorMsg (tokenDeclVar, "duplicate variable " + tokenDeclVar.name.val); + return null; + } + return tokenDeclVar; + } + + /** + * @brief Add variable initialization to $globalvarinit, $staticfieldinit or $instfieldinit function. + * @param initFunc = $globalvarinit, $staticfieldinit or $instfieldinit function + * @param left = variable being initialized + * @param init = null: initialize to default value + * else: initialize to this value + */ + private void DoVarInit (TokenDeclVar initFunc, TokenLVal left, TokenRVal init) + { + /* + * Make a statement that assigns the initialization value to the variable. + */ + TokenStmt stmt; + if (init == null) { + TokenStmtVarIniDef tsvid = new TokenStmtVarIniDef (left); + tsvid.var = left; + stmt = tsvid; + } else { + TokenKw op = new TokenKwAssign (left); + TokenStmtRVal tsrv = new TokenStmtRVal (init); + tsrv.rVal = new TokenRValOpBin (left, op, init); + stmt = tsrv; + } + + /* + * Add statement to end of initialization function. + * Be sure to execute them in same order as in source + * as some doofus scripts depend on it. + */ + Token lastStmt = initFunc.body.statements; + if (lastStmt == null) { + initFunc.body.statements = stmt; + } else { + Token nextStmt; + while ((nextStmt = lastStmt.nextToken) != null) { + lastStmt = nextStmt; + } + lastStmt.nextToken = stmt; + } + } + + /** + * @brief parse function declaration argument list + * @param token = points to TokenKwParOpen + * @returns null: parse error + * else: points to token with types and names + * token = updated past the TokenKw{Brk,Par}Close + */ + private TokenArgDecl ParseFuncArgs (ref Token token, Type end) + { + TokenArgDecl tokenArgDecl = new TokenArgDecl (token); + + bool first = true; + do { + token = token.nextToken; + if ((token.GetType () == end) && first) break; + if (!(token is TokenType)) { + ErrorMsg (token, "expecting arg type"); + token = SkipPastSemi (token); + return null; + } + TokenType type = (TokenType)token; + token = token.nextToken; + if (!(token is TokenName)) { + ErrorMsg (token, "expecting arg name"); + token = SkipPastSemi (token); + return null; + } + TokenName name = (TokenName)token; + token = token.nextToken; + + if (!tokenArgDecl.AddArg (type, name)) { + ErrorMsg (name, "duplicate arg name"); + } + first = false; + } while (token is TokenKwComma); + + if (token.GetType () != end) { + ErrorMsg (token, "expecting comma or close bracket/paren"); + token = SkipPastSemi (token); + return null; + } + token = token.nextToken; + + return tokenArgDecl; + } + + /** + * @brief parse right-hand value expression + * this is where arithmetic-like expressions are processed + * @param token = points to first token expression + * @param termTokenType = expression termination token type + * @returns null: not an RVal + * else: single token representing whole expression + * token = if termTokenType.Length == 1, points just past terminating token + * else, points right at terminating token + */ + public TokenRVal ParseRVal (ref Token token, Type[] termTokenTypes) + { + /* + * Start with pushing the first operand on operand stack. + */ + BinOp binOps = null; + TokenRVal operands = GetOperand (ref token); + if (operands == null) return null; + + /* + * Keep scanning until we hit the termination token. + */ + while (true) { + Type tokType = token.GetType (); + for (int i = termTokenTypes.Length; -- i >= 0;) { + if (tokType == termTokenTypes[i]) goto done; + } + + /* + * Special form: + * is + */ + if (token is TokenKwIs) { + TokenRValIsType tokenRValIsType = new TokenRValIsType (token); + token = token.nextToken; + + /* + * Parse the . + */ + tokenRValIsType.typeExp = ParseTypeExp (ref token); + if (tokenRValIsType.typeExp == null) return null; + + /* + * Replace top operand with result of is + */ + tokenRValIsType.rValExp = operands; + tokenRValIsType.nextToken = operands.nextToken; + operands = tokenRValIsType; + + /* + * token points just past so see if it is another operator. + */ + continue; + } + + /* + * Peek at next operator. + */ + BinOp binOp = GetOperator (ref token); + if (binOp == null) return null; + + /* + * If there are stacked operators of higher or same precedence than new one, + * perform their computation then push result back on operand stack. + * + * higher or same = left-to-right application of operators + * eg, a - b - c becomes (a - b) - c + * + * higher precedence = right-to-left application of operators + * eg, a - b - c becomes a - (b - c) + * + * Now of course, there is some ugliness necessary: + * we want: a - b - c => (a - b) - c so we do 'higher or same' + * but we want: a += b = c => a += (b = c) so we do 'higher only' + * + * binOps is the first operator (or null if only one) + * binOp is the second operator (or first if only one) + */ + while (binOps != null) { + if (binOps.preced < binOp.preced) break; // 1st operator lower than 2nd, so leave 1st on stack to do later + if (binOps.preced > binOp.preced) goto do1st; // 1st op higher than 2nd, so we always do 1st op first + if (binOps.preced == ASNPR) break; // equal preced, if assignment type, leave 1st on stack to do later + // if non-asn type, do 1st op first (ie left-to-right) + do1st: + TokenRVal result = PerformBinOp ((TokenRVal)operands.prevToken, binOps, (TokenRVal)operands); + result.prevToken = operands.prevToken.prevToken; + operands = result; + binOps = binOps.pop; + } + + /* + * Handle conditional expression as a special form: + * ? : + */ + if (binOp.token is TokenKwQMark) { + TokenRValCondExpr condExpr = new TokenRValCondExpr (binOp.token); + condExpr.condExpr = operands; + condExpr.trueExpr = ParseRVal (ref token, new Type[] { typeof (TokenKwColon) }); + condExpr.falseExpr = ParseRVal (ref token, termTokenTypes); + condExpr.prevToken = operands.prevToken; + operands = condExpr; + termTokenTypes = new Type[0]; + goto done; + } + + /* + * Push new operator on its stack. + */ + binOp.pop = binOps; + binOps = binOp; + + /* + * Push next operand on its stack. + */ + TokenRVal operand = GetOperand (ref token); + if (operand == null) return null; + operand.prevToken = operands; + operands = operand; + } + done: + + /* + * At end of expression, perform any stacked computations. + */ + while (binOps != null) { + TokenRVal result = PerformBinOp ((TokenRVal)operands.prevToken, binOps, (TokenRVal)operands); + result.prevToken = operands.prevToken.prevToken; + operands = result; + binOps = binOps.pop; + } + + /* + * There should be exactly one remaining operand on the stack which is our final result. + */ + if (operands.prevToken != null) throw new Exception ("too many operands"); + + /* + * If only one terminator type possible, advance past the terminator. + */ + if (termTokenTypes.Length == 1) token = token.nextToken; + + return operands; + } + + private TokenTypeExp ParseTypeExp (ref Token token) + { + TokenTypeExp leftOperand = GetTypeExp (ref token); + if (leftOperand == null) return null; + + while ((token is TokenKwAnd) || (token is TokenKwOr)) { + Token typeBinOp = token; + token = token.nextToken; + TokenTypeExp rightOperand = GetTypeExp (ref token); + if (rightOperand == null) return null; + TokenTypeExpBinOp typeExpBinOp = new TokenTypeExpBinOp (typeBinOp); + typeExpBinOp.leftOp = leftOperand; + typeExpBinOp.binOp = typeBinOp; + typeExpBinOp.rightOp = rightOperand; + leftOperand = typeExpBinOp; + } + return leftOperand; + } + + private TokenTypeExp GetTypeExp (ref Token token) + { + if (token is TokenKwTilde) { + TokenTypeExpNot typeExpNot = new TokenTypeExpNot (token); + token = token.nextToken; + typeExpNot.typeExp = GetTypeExp (ref token); + if (typeExpNot.typeExp == null) return null; + return typeExpNot; + } + if (token is TokenKwParOpen) { + TokenTypeExpPar typeExpPar = new TokenTypeExpPar (token); + token = token.nextToken; + typeExpPar.typeExp = GetTypeExp (ref token); + if (typeExpPar.typeExp == null) return null; + if (!(token is TokenKwParClose)) { + ErrorMsg (token, "expected close parenthesis"); + token = SkipPastSemi (token); + return null; + } + return typeExpPar; + } + if (token is TokenKwUndef) { + TokenTypeExpUndef typeExpUndef = new TokenTypeExpUndef (token); + token = token.nextToken; + return typeExpUndef; + } + if (token is TokenType) { + TokenTypeExpType typeExpType = new TokenTypeExpType (token); + typeExpType.typeToken = (TokenType)token; + token = token.nextToken; + return typeExpType; + } + ErrorMsg (token, "expected type"); + token = SkipPastSemi (token); + return null; + } + + /** + * @brief get a right-hand operand expression token + * @param token = first token of operand to parse + * @returns null: invalid operand + * else: token that bundles or wraps the operand + * token = points to token following last operand token + */ + private TokenRVal GetOperand (ref Token token) + { + /* + * Prefix unary operators (eg ++, --) requiring an L-value. + */ + if ((token is TokenKwIncr) || (token is TokenKwDecr)) { + TokenRValAsnPre asnPre = new TokenRValAsnPre (token); + asnPre.prefix = token; + token = token.nextToken; + TokenRVal op = GetOperand (ref token); + if (op == null) return null; + if (!(op is TokenLVal)) { + ErrorMsg (op, "can pre{in,de}crement only an L-value"); + return null; + } + asnPre.lVal = (TokenLVal)op; + return asnPre; + } + + /* + * Get the bulk of the operand, ie, without any of the below suffixes. + */ + TokenRVal operand = GetOperandNoMods (ref token); + if (operand == null) return null; + modifiers: + + /* + * If followed by '++' or '--', it is post-{in,de}cremented. + */ + if ((token is TokenKwIncr) || (token is TokenKwDecr)) { + TokenRValAsnPost asnPost = new TokenRValAsnPost (token); + asnPost.postfix = token; + token = token.nextToken; + if (!(operand is TokenLVal)) { + ErrorMsg (operand, "can post{in,de}crement only an L-value"); + return null; + } + asnPost.lVal = (TokenLVal)operand; + return asnPost; + } + + /* + * If followed by a '.', it is an instance field or instance method reference. + */ + if (token is TokenKwDot) { + token = token.nextToken; + if (!(token is TokenName)) { + ErrorMsg (token, ". must be followed by field/method name"); + return null; + } + TokenLValIField field = new TokenLValIField (token); + field.baseRVal = operand; + field.fieldName = (TokenName)token; + operand = field; + token = token.nextToken; + goto modifiers; + } + + /* + * If followed by a '[', it is an array subscript. + */ + if (token is TokenKwBrkOpen) { + TokenLValArEle tokenLValArEle = new TokenLValArEle (token); + token = token.nextToken; + + /* + * Parse subscript(s) expression. + */ + tokenLValArEle.subRVal = ParseRVal (ref token, brkCloseOnly); + if (tokenLValArEle.subRVal == null) { + ErrorMsg (tokenLValArEle, "invalid subscript"); + return null; + } + + /* + * See if comma-separated list of values. + */ + TokenRVal subscriptRVals; + int numSubscripts = SplitCommaRVals (tokenLValArEle.subRVal, out subscriptRVals); + if (numSubscripts > 1) { + + /* + * If so, put the values in an LSL_List object. + */ + TokenRValList rValList = new TokenRValList (tokenLValArEle); + rValList.rVal = subscriptRVals; + rValList.nItems = numSubscripts; + tokenLValArEle.subRVal = rValList; + } + + /* + * Either way, save array variable name + * and substitute whole reference for L-value + */ + tokenLValArEle.baseRVal = operand; + operand = tokenLValArEle; + goto modifiers; + } + + /* + * If followed by a '(', it is a function/method call. + */ + if (token is TokenKwParOpen) { + operand = ParseRValCall (ref token, operand); + goto modifiers; + } + + /* + * If 'new' arraytipe '{', it is an array initializer. + */ + if ((token is TokenKwBrcOpen) && (operand is TokenLValSField) && + (((TokenLValSField)operand).fieldName.val == "$new") && + ((TokenLValSField)operand).baseType.ToString ().EndsWith ("]")) { + operand = ParseRValNewArIni (ref token, (TokenLValSField)operand); + if (operand != null) goto modifiers; + } + + return operand; + } + + /** + * @brief same as GetOperand() except doesn't check for any modifiers + */ + private TokenRVal GetOperandNoMods (ref Token token) + { + /* + * Simple unary operators. + */ + if ((token is TokenKwSub) || + (token is TokenKwTilde) || + (token is TokenKwExclam)) { + Token uop = token; + token = token.nextToken; + TokenRVal rVal = GetOperand (ref token); + if (rVal == null) return null; + return PerformUnOp (uop, rVal); + } + + /* + * Type casting. + */ + if ((token is TokenKwParOpen) && + (token.nextToken is TokenType) && + (token.nextToken.nextToken is TokenKwParClose)) { + TokenType type = (TokenType)token.nextToken; + token = token.nextToken.nextToken.nextToken; + TokenRVal rVal = GetOperand (ref token); + if (rVal == null) return null; + return new TokenRValCast (type, rVal); + } + + /* + * Parenthesized expression. + */ + if (token is TokenKwParOpen) { + return ParseRValParen (ref token); + } + + /* + * Constants. + */ + if (token is TokenChar) { + TokenRValConst rValConst = new TokenRValConst (token, ((TokenChar)token).val); + token = token.nextToken; + return rValConst; + } + if (token is TokenFloat) { + TokenRValConst rValConst = new TokenRValConst (token, ((TokenFloat)token).val); + token = token.nextToken; + return rValConst; + } + if (token is TokenInt) { + TokenRValConst rValConst = new TokenRValConst (token, ((TokenInt)token).val); + token = token.nextToken; + return rValConst; + } + if (token is TokenStr) { + TokenRValConst rValConst = new TokenRValConst (token, ((TokenStr)token).val); + token = token.nextToken; + return rValConst; + } + if (token is TokenKwUndef) { + TokenRValUndef rValUndef = new TokenRValUndef ((TokenKwUndef)token); + token = token.nextToken; + return rValUndef; + } + + /* + * '<'value,...'>', ie, rotation or vector + */ + if (token is TokenKwCmpLT) { + Token openBkt = token; + token = token.nextToken; + TokenRVal rValAll = ParseRVal (ref token, cmpGTOnly); + if (rValAll == null) return null; + TokenRVal rVals; + int nVals = SplitCommaRVals (rValAll, out rVals); + switch (nVals) { + case 3: { + TokenRValVec rValVec = new TokenRValVec (openBkt); + rValVec.xRVal = rVals; + rValVec.yRVal = (TokenRVal)rVals.nextToken; + rValVec.zRVal = (TokenRVal)rVals.nextToken.nextToken; + return rValVec; + } + case 4: { + TokenRValRot rValRot = new TokenRValRot (openBkt); + rValRot.xRVal = rVals; + rValRot.yRVal = (TokenRVal)rVals.nextToken; + rValRot.zRVal = (TokenRVal)rVals.nextToken.nextToken; + rValRot.wRVal = (TokenRVal)rVals.nextToken.nextToken.nextToken; + return rValRot; + } + default: { + ErrorMsg (openBkt, "bad rotation/vector"); + token = SkipPastSemi (token); + return null; + } + } + } + + /* + * '['value,...']', ie, list + */ + if (token is TokenKwBrkOpen) { + TokenRValList rValList = new TokenRValList (token); + token = token.nextToken; + if (token is TokenKwBrkClose) { + token = token.nextToken; // empty list + } else { + TokenRVal rValAll = ParseRVal (ref token, brkCloseOnly); + if (rValAll == null) return null; + rValList.nItems = SplitCommaRVals (rValAll, out rValList.rVal); + } + return rValList; + } + + /* + * Maybe we have . referencing a static field or method of some type. + */ + if ((token is TokenType) && (token.nextToken is TokenKwDot) && (token.nextToken.nextToken is TokenName)) { + TokenLValSField field = new TokenLValSField (token.nextToken.nextToken); + field.baseType = (TokenType)token; + field.fieldName = (TokenName)token.nextToken.nextToken; + token = token.nextToken.nextToken.nextToken; + return field; + } + + /* + * Maybe we have 'this' referring to the object of the instance method. + */ + if (token is TokenKwThis) { + if ((currentDeclSDType == null) || !(currentDeclSDType is TokenDeclSDTypeClass)) { + ErrorMsg (token, "using 'this' outside class definition"); + token = SkipPastSemi (token); + return null; + } + TokenRValThis zhis = new TokenRValThis (token, (TokenDeclSDTypeClass)currentDeclSDType); + token = token.nextToken; + return zhis; + } + + /* + * Maybe we have 'base' referring to a field/method of the extended class. + */ + if (token is TokenKwBase) { + if ((currentDeclFunc == null) || (currentDeclFunc.sdtClass == null) || !(currentDeclFunc.sdtClass is TokenDeclSDTypeClass)) { + ErrorMsg (token, "using 'base' outside method"); + token = SkipPastSemi (token); + return null; + } + if (!(token.nextToken is TokenKwDot) || !(token.nextToken.nextToken is TokenName)) { + ErrorMsg (token, "base must be followed by . then field or method name"); + TokenRValThis zhis = new TokenRValThis (token, (TokenDeclSDTypeClass)currentDeclFunc.sdtClass); + token = token.nextToken; + return zhis; + } + TokenLValBaseField baseField = new TokenLValBaseField (token, + (TokenName)token.nextToken.nextToken, + (TokenDeclSDTypeClass)currentDeclFunc.sdtClass); + token = token.nextToken.nextToken.nextToken; + return baseField; + } + + /* + * Maybe we have 'new ' saying to create an object instance. + * This ends up generating a call to static function .$new(...) + * whose CIL code is generated by GenerateNewobjBody(). + */ + if (token is TokenKwNew) { + if (!(token.nextToken is TokenType)) { + ErrorMsg (token.nextToken, "new must be followed by type"); + token = SkipPastSemi (token); + return null; + } + TokenLValSField field = new TokenLValSField (token.nextToken.nextToken); + field.baseType = (TokenType)token.nextToken; + field.fieldName = new TokenName (token, "$new"); + token = token.nextToken.nextToken; + return field; + } + + /* + * All we got left is , eg, arg, function, global or local variable reference + */ + if (token is TokenName) { + TokenLValName name = new TokenLValName ((TokenName)token, tokenScript.variablesStack); + token = token.nextToken; + return name; + } + + /* + * Who knows what it is supposed to be? + */ + ErrorMsg (token, "invalid operand token"); + token = SkipPastSemi (token); + return null; + } + + /** + * @brief Parse a call expression + * @param token = points to arg list '(' + * @param meth = points to method name being called + * @returns call expression value + * token = points just past arg list ')' + */ + private TokenRValCall ParseRValCall (ref Token token, TokenRVal meth) + { + /* + * Set up basic function call struct with function name. + */ + TokenRValCall rValCall = new TokenRValCall (token); + rValCall.meth = meth; + + /* + * Parse the call parameters, if any. + */ + token = token.nextToken; + if (token is TokenKwParClose) { + token = token.nextToken; + } else { + rValCall.args = ParseRVal (ref token, parCloseOnly); + if (rValCall.args == null) return null; + rValCall.nArgs = SplitCommaRVals (rValCall.args, out rValCall.args); + } + + currentDeclFunc.unknownTrivialityCalls.AddLast (rValCall); + + return rValCall; + } + + /** + * @brief decode binary operator token + * @param token = points to token to decode + * @returns null: invalid operator token + * else: operator token and precedence + */ + private BinOp GetOperator (ref Token token) + { + BinOp binOp = new BinOp (); + if (precedence.TryGetValue (token.GetType (), out binOp.preced)) { + binOp.token = (TokenKw)token; + token = token.nextToken; + return binOp; + } + + if ((token is TokenKwSemi) || (token is TokenKwBrcOpen) || (token is TokenKwBrcClose)) { + ErrorMsg (token, "premature expression end"); + } else { + ErrorMsg (token, "invalid operator"); + } + token = SkipPastSemi (token); + return null; + } + + private class BinOp { + public BinOp pop; + public TokenKw token; + public int preced; + } + + /** + * @brief Return an R-value expression token that will be used to + * generate code to perform the operation at runtime. + * @param left = left-hand operand + * @param binOp = operator + * @param right = right-hand operand + * @returns resultant expression + */ + private TokenRVal PerformBinOp (TokenRVal left, BinOp binOp, TokenRVal right) + { + return new TokenRValOpBin (left, binOp.token, right); + } + + /** + * @brief Return an R-value expression token that will be used to + * generate code to perform the operation at runtime. + * @param unOp = operator + * @param right = right-hand operand + * @returns resultant constant or expression + */ + private TokenRVal PerformUnOp (Token unOp, TokenRVal right) + { + return new TokenRValOpUn ((TokenKw)unOp, right); + } + + /** + * @brief Parse an array initialization expression. + * @param token = points to '{' on entry + * @param newCall = encapsulates a '$new' call + * @return resultant operand encapsulating '$new' call and initializers + * token = points just past terminating '}' + * ...or null if parse error + */ + private TokenRVal ParseRValNewArIni (ref Token token, TokenLValSField newCall) + { + Stack stack = new Stack (); + TokenRValNewArIni arini = new TokenRValNewArIni (token); + arini.arrayType = newCall.baseType; + TokenList values = null; + while (true) { + + // open brace means start a (sub-)list + if (token is TokenKwBrcOpen) { + stack.Push (values); + values = new TokenList (token); + token = token.nextToken; + continue; + } + + // close brace means end of (sub-)list + // if final '}' all done parsing + if (token is TokenKwBrcClose) { + token = token.nextToken; // skip over the '}' + TokenList innerds = values; // save the list just closed + arini.valueList = innerds; // it's the top list if it's the last closed + values = stack.Pop (); // pop to next outer list + if (values == null) return arini; // final '}', we are done + values.tl.Add (innerds); // put the inner list on end of outer list + if (token is TokenKwComma) { // should have a ',' or '}' next + token = token.nextToken; // skip over the ',' + } else if (!(token is TokenKwBrcClose)) { + ErrorMsg (token, "expecting , or } after sublist"); + } + continue; + } + + // this is a comma that doesn't have a value expression before it + // so we take it to mean skip initializing element (leave it zeroes/null etc) + if (token is TokenKwComma) { + values.tl.Add (token); + token = token.nextToken; + continue; + } + + // parse value expression and skip terminating ',' if any + TokenRVal append = ParseRVal (ref token, commaOrBrcClose); + if (append == null) return null; + values.tl.Add (append); + if (token is TokenKwComma) { + token = token.nextToken; + } + } + } + + /** + * @brief parse out a parenthesized expression. + * @param token = points to open parenthesis + * @returns null: invalid expression + * else: parenthesized expression token or constant token + * token = points past the close parenthesis + */ + private TokenRValParen ParseRValParen (ref Token token) + { + if (!(token is TokenKwParOpen)) { + ErrorMsg (token, "expecting ("); + token = SkipPastSemi (token); + return null; + } + TokenRValParen tokenRValParen = new TokenRValParen (token); + token = token.nextToken; + tokenRValParen.rVal = ParseRVal (ref token, parCloseOnly); + if (tokenRValParen.rVal == null) return null; + return tokenRValParen; + } + + /** + * @brief Split a comma'd RVal into separate expressions + * @param rValAll = expression containing commas + * @returns number of comma separated values + * rVals = values in a null-terminated list linked by rVals.nextToken + */ + private int SplitCommaRVals (TokenRVal rValAll, out TokenRVal rVals) + { + if (!(rValAll is TokenRValOpBin) || !(((TokenRValOpBin)rValAll).opcode is TokenKwComma)) { + rVals = rValAll; + if (rVals.nextToken != null) throw new Exception ("expected null"); + return 1; + } + TokenRValOpBin opBin = (TokenRValOpBin)rValAll; + TokenRVal rValLeft, rValRight; + int leftCount = SplitCommaRVals (opBin.rValLeft, out rValLeft); + int rightCount = SplitCommaRVals (opBin.rValRight, out rValRight); + rVals = rValLeft; + while (rValLeft.nextToken != null) rValLeft = (TokenRVal)rValLeft.nextToken; + rValLeft.nextToken = rValRight; + return leftCount + rightCount; + } + + /** + * @brief output error message and remember that there is an error. + * @param token = what token is associated with the error + * @param message = error message string + */ + private void ErrorMsg (Token token, string message) + { + if (!errors || (token.file != lastErrorFile) || (token.line > lastErrorLine)) { + errors = true; + lastErrorFile = token.file; + lastErrorLine = token.line; + token.ErrorMsg (message); + } + } + + /** + * @brief Skip past the next semicolon (or matched braces) + * @param token = points to token to skip over + * @returns token just after the semicolon or close brace + */ + private Token SkipPastSemi (Token token) + { + int braceLevel = 0; + + while (!(token is TokenEnd)) { + if ((token is TokenKwSemi) && (braceLevel == 0)) { + return token.nextToken; + } + if (token is TokenKwBrcOpen) { + braceLevel ++; + } + if ((token is TokenKwBrcClose) && (-- braceLevel <= 0)) { + return token.nextToken; + } + token = token.nextToken; + } + return token; + } + } + + /** + * @brief Script-defined type declarations + */ + public abstract class TokenDeclSDType : Token { + protected const byte CLASS = 0; + protected const byte DELEGATE = 1; + protected const byte INTERFACE = 2; + protected const byte TYPEDEF = 3; + + // stuff that gets cloned/copied/transformed when instantiating a generic + // see InstantiateGeneric() below + public TokenDeclSDType outerSDType; // null if top-level + // else points to defining script-defined type + public Dictionary innerSDTypes = new Dictionary (); + // indexed by shortName + public Token begToken; // token that begins the definition (might be this or something like 'public') + public Token endToken; // the '}' or ';' that ends the definition + + // generic instantiation assumes none of the rest needs to be cloned (well except for the shortName) + public int sdTypeIndex = -1; // index in scriptObjCode.sdObjTypesIndx[] array + public TokenDeclSDTypeClass extends; // only non-null for TokenDeclSDTypeClass's + public uint accessLevel; // SDT_PRIVATE, SDT_PROTECTED or SDT_PUBLIC + // ... all top-level types are SDT_PUBLIC + public VarDict members = new VarDict (false); // declared fields, methods, properties if any + + public Dictionary genParams; // list of parameters for generic prototypes + // null for non-generic prototypes + // eg, for 'Dictionary' + // ...genParams gives K->0; V->1 + + public bool isPartial; // was declared with 'partial' keyword + // classes only, all others always false + + /* + * Name of the type. + * shortName = doesn't include outer class type names + * eg, 'Engine' for non-generic + * 'Dictionary<,>' for generic prototype + * 'Dictionary' for generic instantiation + * longName = includes all outer class type names if any + */ + private TokenName _shortName; + private TokenName _longName; + + public TokenName shortName { + get { + return _shortName; + } + set { + _shortName = value; + _longName = null; + } + } + + public TokenName longName { + get { + if (_longName == null) { + _longName = _shortName; + if (outerSDType != null) { + _longName = new TokenName (_shortName, outerSDType.longName.val + "." + _shortName.val); + } + } + return _longName; + } + } + + /* + * Dictionary used when reading from object file that holds all script-defined types. + * Not complete though until all types have been read from the object file. + */ + private Dictionary sdTypes; + + public TokenDeclSDType (Token t) : base (t) { } + protected abstract TokenDeclSDType MakeBlank (TokenName shortName); + public abstract TokenType MakeRefToken (Token t); + public abstract Type GetSysType (); + public abstract void WriteToFile (BinaryWriter objFileWriter); + public abstract void ReadFromFile (BinaryReader objFileReader, TextWriter asmFileWriter); + + /** + * @brief Given that this is a generic prototype, apply the supplied genArgs + * to create an equivalent instantiated non-generic. This basically + * makes a copy replacing all the parameter types with the actual + * argument types. + * @param this = the prototype to be instantiated, eg, 'Dictionary.Converter' + * @param name = short name with arguments, eg, 'Converter'. + * @param genArgs = argument types of just this level, eg, 'float'. + * @returns clone of this but with arguments applied and spliced in source token stream + */ + public TokenDeclSDType InstantiateGeneric (string name, TokenType[] genArgs, ScriptReduce reduce) + { + /* + * Malloc the struct and give it a name. + */ + TokenDeclSDType instdecl = this.MakeBlank (new TokenName (this, name)); + + /* + * If the original had an outer type, then so does the new one. + * The outer type will never be a generic prototype, eg, if this + * is 'ValueList' it will always be inside 'Dictionary' + * not 'Dictionary' at this point. + */ + if ((this.outerSDType != null) && (this.outerSDType.genParams != null)) throw new Exception (); + instdecl.outerSDType = this.outerSDType; + + /* + * The generic prototype may have stuff like 'public' just before it and we need to copy that too. + */ + Token prefix; + for (prefix = this; (prefix = prefix.prevToken) != null;) { + if (!(prefix is TokenKwPublic) && !(prefix is TokenKwProtected) && !(prefix is TokenKwPrivate)) break; + } + this.begToken = prefix.nextToken; + + /* + * Splice in a copy of the prefix tokens, just before the beginning token of prototype (this.begToken). + */ + while ((prefix = prefix.nextToken) != this) { + SpliceSourceToken (prefix.CopyToken (prefix)); + } + + /* + * Splice instantiation (instdecl) in just before the beginning token of prototype (this.begToken). + */ + SpliceSourceToken (instdecl); + + /* + * Now for the fun part... Copy the rest of the prototype body to the + * instantiated body, replacing all generic parameter type tokens with + * the corresponding generic argument types. Note that the parameters + * are numbered starting with the outermost so we need the full genArgs + * array. Eg if we are doing 'Converter' from + * 'Dictionary.Converter', any V's are + * numbered [2]. Any [0]s or [1]s should be gone by now but it doesn't + * matter. + */ + int index; + Token it, pt; + TokenDeclSDType innerProto = this; + TokenDeclSDType innerInst = instdecl; + for (pt = this; (pt = pt.nextToken) != this.endToken;) { + + /* + * Coming across a sub-type's declaration involves a deep copy of the + * declaration token. Fortunately we are early on in parsing, so there + * really isn't much to copy: + * 1) short name is the same, eg, doing List of Dictionary.List is same short name as Dictionary.List + * if generic, eg doing Converter of Dictionary.Converter, we have to manually copy the W as well. + * 2) outerSDType is transformed from Dictionary to Dictionary. + * 3) innerSDTypes is rebuilt when/if we find classes that are inner to this one. + */ + if (pt is TokenDeclSDType) { + + /* + * Make a new TokenDeclSDType{Class,Delegate,Interface}. + */ + TokenDeclSDType ptSDType = (TokenDeclSDType)pt; + TokenDeclSDType itSDType = ptSDType.MakeBlank (new TokenName (ptSDType.shortName, ptSDType.shortName.val)); + + /* + * Set up the transformed outerSDType. + * Eg, if we are creating Enumerator of Dictionary.Enumerator, + * innerProto = Dictionary and innerInst = Dictionary. + */ + itSDType.outerSDType = innerInst; + + /* + * This clone is an inner type of its next outer level. + */ + reduce.CatalogSDTypeDecl (itSDType); + + /* + * We need to manually copy any generic parameters of the class declaration being cloned. + * eg, if we are cloning Converter, this is where the W gets copied. + * Since it is an immutable array of strings, just copy the array pointer, if any. + */ + itSDType.genParams = ptSDType.genParams; + + /* + * We are now processing tokens for this cloned type declaration. + */ + innerProto = ptSDType; + innerInst = itSDType; + + /* + * Splice this clone token in. + */ + it = itSDType; + } + + /* + * Check for an generic parameter to substitute out. + */ + else if ((pt is TokenName) && this.genParams.TryGetValue (((TokenName)pt).val, out index)) { + it = genArgs[index].CopyToken (pt); + } + + /* + * Everything else is a simple copy. + */ + else it = pt.CopyToken (pt); + + /* + * Whatever we came up with, splice it into the source token stream. + */ + SpliceSourceToken (it); + + /* + * Maybe we just finished copying an inner type definition. + * If so, remember where it ends and pop it from the stack. + */ + if (innerProto.endToken == pt) { + innerInst.endToken = it; + innerProto = innerProto.outerSDType; + innerInst = innerInst.outerSDType; + } + } + + /* + * Clone and insert the terminator, either '}' or ';' + */ + it = pt.CopyToken (pt); + SpliceSourceToken (it); + instdecl.endToken = it; + + return instdecl; + } + + /** + * @brief Splice a source token in just before the type's beginning keyword. + */ + private void SpliceSourceToken (Token it) + { + it.nextToken = this.begToken; + (it.prevToken = this.begToken.prevToken).nextToken = it; + this.begToken.prevToken = it; + } + + /** + * @brief Read one of these in from the object file. + * @param sdTypes = dictionary of script-defined types, not yet complete + * @param name = script-visible name of this type + * @param objFileReader = reads from the object file + * @param asmFileWriter = writes to the disassembly file (might be null) + */ + public static TokenDeclSDType ReadFromFile (Dictionary sdTypes, string name, + BinaryReader objFileReader, TextWriter asmFileWriter) + { + string file = objFileReader.ReadString (); + int line = objFileReader.ReadInt32 (); + int posn = objFileReader.ReadInt32 (); + byte code = objFileReader.ReadByte (); + TokenName n = new TokenName (null, file, line, posn, name); + TokenDeclSDType sdt; + switch (code) { + case CLASS: { + sdt = new TokenDeclSDTypeClass (n, false); + break; + } + case DELEGATE: { + sdt = new TokenDeclSDTypeDelegate (n); + break; + } + case INTERFACE: { + sdt = new TokenDeclSDTypeInterface (n); + break; + } + case TYPEDEF: { + sdt = new TokenDeclSDTypeTypedef (n); + break; + } + default: throw new Exception (); + } + sdt.sdTypes = sdTypes; + sdt.sdTypeIndex = objFileReader.ReadInt32 (); + sdt.ReadFromFile (objFileReader, asmFileWriter); + return sdt; + } + + /** + * @brief Convert a typename string to a type token + * @param name = script-visible name of token to create, + * either a script-defined type or an LSL-defined type + * @returns type token + */ + protected TokenType MakeTypeToken (string name) + { + TokenDeclSDType sdtdecl; + if (sdTypes.TryGetValue (name, out sdtdecl)) return sdtdecl.MakeRefToken (this); + return TokenType.FromLSLType (this, name); + } + + // debugging - returns, eg, 'Dictionary.Enumerator.Node' + public override void DebString (StringBuilder sb) + { + // get long name broken down into segments from outermost to this + Stack declStack = new Stack (); + for (TokenDeclSDType decl = this; decl != null; decl = decl.outerSDType) { + declStack.Push (decl); + } + + // output each segment's name followed by our args for it + // starting with outermost and ending with this + while (declStack.Count > 0) { + TokenDeclSDType decl = declStack.Pop (); + sb.Append (decl.shortName.val); + if (decl.genParams != null) { + sb.Append ('<'); + string[] parms = new string[decl.genParams.Count]; + foreach (KeyValuePair kvp in decl.genParams) { + parms[kvp.Value] = kvp.Key; + } + for (int j = 0; j < parms.Length;) { + sb.Append (parms[j]); + if (++ j < parms.Length) sb.Append (','); + } + sb.Append ('>'); + } + if (declStack.Count > 0) sb.Append ('.'); + } + } + } + + public class TokenDeclSDTypeClass : TokenDeclSDType { + public List implements = new List (); + public TokenDeclVar instFieldInit; // $instfieldinit function to do instance field initializations + public TokenDeclVar staticFieldInit; // $staticfieldinit function to do static field initializations + + public Dictionary intfIndices = new Dictionary (); // longname => this.iFaces index + public TokenDeclSDTypeInterface[] iFaces; // array of implemented interfaces + // low-end entries copied from rootward classes + public TokenDeclVar[][] iImplFunc; // iImplFunc[i][j]: + // low-end [i] entries copied from rootward classes + // i = interface number from this.intfIndices[name] + // j = method of interface from iface.methods[name].vTableIndex + + public TokenType arrayOfType; // if array, it's an array of this type, else null + public int arrayOfRank; // if array, it has this number of dimensions, else zero + + public bool slotsAssigned; // set true when slots have been assigned... + public XMRInstArSizes instSizes = new XMRInstArSizes (); + // number of instance fields of various types + public int numVirtFuncs; // number of virtual functions + public int numInterfaces; // number of implemented interfaces + + private string extendsStr; + private string arrayOfTypeStr; + private List stackedMethods; + private List stackedIFaces; + + public DynamicMethod[] vDynMeths; // virtual method entrypoints + public Type[] vMethTypes; // virtual method delegate types + public DynamicMethod[][] iDynMeths; // interface method entrypoints + public Type[][] iMethTypes; // interface method types + // low-end [i] entries copied from rootward classes + // i = interface number from this.intfIndices[name] + // j = method of interface from iface.methods[name].vTableIndex + + public TokenDeclSDTypeClass (TokenName shortName, bool isPartial) : base (shortName) + { + this.shortName = shortName; + this.isPartial = isPartial; + } + + protected override TokenDeclSDType MakeBlank (TokenName shortName) + { + return new TokenDeclSDTypeClass (shortName, false); + } + + public override TokenType MakeRefToken (Token t) + { + return new TokenTypeSDTypeClass (t, this); + } + + public override Type GetSysType () + { + return typeof (XMRSDTypeClObj); + } + + /** + * @brief See if the class implements the interface. + * Do a recursive (deep) check in all rootward classes. + */ + public bool CanCastToIntf (TokenDeclSDTypeInterface intf) + { + if (this.implements.Contains (intf)) return true; + if (this.extends == null) return false; + return this.extends.CanCastToIntf (intf); + } + + /** + * @brief Write enough out so we can reconstruct with ReadFromFile. + */ + public override void WriteToFile (BinaryWriter objFileWriter) + { + objFileWriter.Write (this.file); + objFileWriter.Write (this.line); + objFileWriter.Write (this.posn); + objFileWriter.Write ((byte)CLASS); + objFileWriter.Write (this.sdTypeIndex); + + this.instSizes.WriteToFile (objFileWriter); + objFileWriter.Write (numVirtFuncs); + + if (extends == null) { + objFileWriter.Write (""); + } else { + objFileWriter.Write (extends.longName.val); + } + + objFileWriter.Write (arrayOfRank); + if (arrayOfRank > 0) objFileWriter.Write (arrayOfType.ToString ()); + + foreach (TokenDeclVar meth in members) { + if ((meth.retType != null) && (meth.vTableIndex >= 0)) { + objFileWriter.Write (meth.vTableIndex); + objFileWriter.Write (meth.GetObjCodeName ()); + objFileWriter.Write (meth.GetDelType ().decl.GetWholeSig ()); + } + } + objFileWriter.Write (-1); + + int numIFaces = iImplFunc.Length; + objFileWriter.Write (numIFaces); + for (int i = 0; i < numIFaces; i ++) { + objFileWriter.Write (iFaces[i].longName.val); + TokenDeclVar[] meths = iImplFunc[i]; + int numMeths = 0; + if (meths != null) numMeths = meths.Length; + objFileWriter.Write (numMeths); + for (int j = 0; j < numMeths; j ++) { + TokenDeclVar meth = meths[j]; + objFileWriter.Write (meth.vTableIndex); + objFileWriter.Write (meth.GetObjCodeName ()); + objFileWriter.Write (meth.GetDelType ().decl.GetWholeSig ()); + } + } + } + + /** + * @brief Reconstruct from the file. + */ + public override void ReadFromFile (BinaryReader objFileReader, TextWriter asmFileWriter) + { + instSizes.ReadFromFile (objFileReader); + numVirtFuncs = objFileReader.ReadInt32 (); + + extendsStr = objFileReader.ReadString (); + arrayOfRank = objFileReader.ReadInt32 (); + if (arrayOfRank > 0) arrayOfTypeStr = objFileReader.ReadString (); + + if (asmFileWriter != null) { + instSizes.WriteAsmFile (asmFileWriter, extendsStr + "." + shortName.val + ".numInst"); + } + + stackedMethods = new List (); + int vTableIndex; + while ((vTableIndex = objFileReader.ReadInt32 ()) >= 0) { + StackedMethod sm; + sm.methVTI = vTableIndex; + sm.methName = objFileReader.ReadString (); + sm.methSig = objFileReader.ReadString (); + stackedMethods.Add (sm); + } + + int numIFaces = objFileReader.ReadInt32 (); + if (numIFaces > 0) { + iDynMeths = new DynamicMethod[numIFaces][]; + iMethTypes = new Type[numIFaces][]; + stackedIFaces = new List (); + for (int i = 0; i < numIFaces; i ++) { + string iFaceName = objFileReader.ReadString (); + intfIndices[iFaceName] = i; + int numMeths = objFileReader.ReadInt32 (); + iDynMeths[i] = new DynamicMethod[numMeths]; + iMethTypes[i] = new Type[numMeths]; + for (int j = 0; j < numMeths; j ++) { + StackedIFace si; + si.iFaceIndex = i; + si.methIndex = j; + si.vTableIndex = objFileReader.ReadInt32 (); + si.methName = objFileReader.ReadString (); + si.methSig = objFileReader.ReadString (); + stackedIFaces.Add (si); + } + } + } + } + + private struct StackedMethod { + public int methVTI; + public string methName; + public string methSig; + } + + private struct StackedIFace { + public int iFaceIndex; // which implemented interface + public int methIndex; // which method of that interface + public int vTableIndex; // <0: implemented by non-virtual; else: implemented by virtual + public string methName; // object code name of implementing method (GetObjCodeName) + public string methSig; // method signature incl return type (GetWholeSig) + } + + /** + * @brief Called after all dynamic method code has been generated to fill in vDynMeths and vMethTypes + * Also fills in iDynMeths, iMethTypes. + */ + public void FillVTables (ScriptObjCode scriptObjCode) + { + if (extendsStr != null) { + if (extendsStr != "") { + extends = (TokenDeclSDTypeClass)scriptObjCode.sdObjTypesName[extendsStr]; + extends.FillVTables (scriptObjCode); + } + extendsStr = null; + } + if (arrayOfTypeStr != null) { + arrayOfType = MakeTypeToken (arrayOfTypeStr); + arrayOfTypeStr = null; + } + + if ((numVirtFuncs > 0) && (stackedMethods != null)) { + + /* + * Allocate arrays big enough for mine plus type we are extending. + */ + vDynMeths = new DynamicMethod[numVirtFuncs]; + vMethTypes = new Type[numVirtFuncs]; + + /* + * Fill in low parts from type we are extending. + */ + if (extends != null) { + int n = extends.numVirtFuncs; + for (int i = 0; i < n; i ++) { + vDynMeths[i] = extends.vDynMeths[i]; + vMethTypes[i] = extends.vMethTypes[i]; + } + } + + /* + * Fill in high parts with my own methods. + * Might also overwrite lower ones with 'override' methods. + */ + foreach (StackedMethod sm in stackedMethods) { + int i = sm.methVTI; + string methName = sm.methName; + DynamicMethod dm; + if (scriptObjCode.dynamicMethods.TryGetValue (methName, out dm)) { + // method is not abstract + vDynMeths[i] = dm; + vMethTypes[i] = GetDynamicMethodDelegateType (dm, sm.methSig); + } + } + stackedMethods = null; + } + + if (stackedIFaces != null) { + foreach (StackedIFace si in stackedIFaces) { + int i = si.iFaceIndex; + int j = si.methIndex; + int vti = si.vTableIndex; + string methName = si.methName; + DynamicMethod dm = scriptObjCode.dynamicMethods[methName]; + iDynMeths[i][j] = (vti < 0) ? dm : vDynMeths[vti]; + iMethTypes[i][j] = GetDynamicMethodDelegateType (dm, si.methSig); + } + stackedIFaces = null; + } + } + + private Type GetDynamicMethodDelegateType (DynamicMethod dm, string methSig) + { + Type retType = dm.ReturnType; + ParameterInfo[] pi = dm.GetParameters (); + Type[] argTypes = new Type[pi.Length]; + for (int j = 0; j < pi.Length; j ++) { + argTypes[j] = pi[j].ParameterType; + } + return DelegateCommon.GetType (retType, argTypes, methSig); + } + + public override void DebString (StringBuilder sb) + { + /* + * Don't output if array of some type. + * They will be re-instantiated as referenced by rest of script. + */ + if (arrayOfType != null) return; + + /* + * This class name and extended/implemented type declaration. + */ + sb.Append ("class "); + sb.Append (shortName.val); + bool first = true; + if (extends != null) { + sb.Append (" : "); + sb.Append (extends.longName); + first = false; + } + foreach (TokenDeclSDType impld in implements) { + sb.Append (first ? " : " : ", "); + sb.Append (impld.longName); + first = false; + } + sb.Append (" {"); + + /* + * Inner type definitions. + */ + foreach (TokenDeclSDType subs in innerSDTypes.Values) { + subs.DebString (sb); + } + + /* + * Members (fields, methods, properties). + */ + foreach (TokenDeclVar memb in members) { + if ((memb == instFieldInit) || (memb == staticFieldInit)) { + memb.DebStringInitFields (sb); + } else if (memb.retType != null) { + memb.DebString (sb); + } + } + + sb.Append ('}'); + } + } + + public class TokenDeclSDTypeDelegate : TokenDeclSDType { + private TokenType retType; + private TokenType[] argTypes; + + private string argSig; + private string wholeSig; + private Type sysType; + private Type retSysType; + private Type[] argSysTypes; + + private string retStr; + private string[] argStrs; + + private static Dictionary inlines = new Dictionary (); + private static Dictionary inlrevs = new Dictionary (); + + public TokenDeclSDTypeDelegate (TokenName shortName) : base (shortName) + { + this.shortName = shortName; + } + public void SetRetArgTypes (TokenType retType, TokenType[] argTypes) + { + this.retType = retType; + this.argTypes = argTypes; + } + + protected override TokenDeclSDType MakeBlank (TokenName shortName) + { + return new TokenDeclSDTypeDelegate (shortName); + } + + public override TokenType MakeRefToken (Token t) + { + return new TokenTypeSDTypeDelegate (t, this); + } + + /** + * @brief Get system type for the whole delegate. + */ + public override Type GetSysType () + { + if (sysType == null) FillInStuff (); + return sysType; + } + + /** + * @brief Get the function's return value type (TokenTypeVoid if void, never null) + */ + public TokenType GetRetType () + { + if (retType == null) FillInStuff (); + return retType; + } + + /** + * @brief Get the function's argument types + */ + public TokenType[] GetArgTypes () + { + if (argTypes == null) FillInStuff (); + return argTypes; + } + + /** + * @brief Get signature for the whole delegate, eg, "void(integer,list)" + */ + public string GetWholeSig () + { + if (wholeSig == null) FillInStuff (); + return wholeSig; + } + + /** + * @brief Get signature for the arguments, eg, "(integer,list)" + */ + public string GetArgSig () + { + if (argSig == null) FillInStuff (); + return argSig; + } + + /** + * @brief Find out how to create one of these delegates. + */ + public ConstructorInfo GetConstructorInfo () + { + if (sysType == null) FillInStuff (); + return sysType.GetConstructor (DelegateCommon.constructorArgTypes); + } + + /** + * @brief Find out how to call what one of these delegates points to. + */ + public MethodInfo GetInvokerInfo () + { + if (sysType == null) FillInStuff (); + return sysType.GetMethod ("Invoke", argSysTypes); + } + + /** + * @brief Write enough out to a file so delegate can be reconstructed in ReadFromFile(). + */ + public override void WriteToFile (BinaryWriter objFileWriter) + { + objFileWriter.Write (this.file); + objFileWriter.Write (this.line); + objFileWriter.Write (this.posn); + objFileWriter.Write ((byte)DELEGATE); + objFileWriter.Write (this.sdTypeIndex); + + objFileWriter.Write (retType.ToString ()); + int nArgs = argTypes.Length; + objFileWriter.Write (nArgs); + for (int i = 0; i < nArgs; i ++) { + objFileWriter.Write (argTypes[i].ToString ()); + } + } + + /** + * @brief Read that data from file so we can reconstruct. + * Don't actually reconstruct yet in case any forward-referenced types are undefined. + */ + public override void ReadFromFile (BinaryReader objFileReader, TextWriter asmFileWriter) + { + retStr = objFileReader.ReadString (); + int nArgs = objFileReader.ReadInt32 (); + if (asmFileWriter != null) { + asmFileWriter.Write (" delegate " + retStr + " " + longName.val + "("); + } + argStrs = new string[nArgs]; + for (int i = 0; i < nArgs; i ++) { + argStrs[i] = objFileReader.ReadString (); + if (asmFileWriter != null) { + if (i > 0) asmFileWriter.Write (","); + asmFileWriter.Write (argStrs[i]); + } + } + if (asmFileWriter != null) { + asmFileWriter.WriteLine (");"); + } + } + + /** + * @brief Fill in missing internal data. + */ + private void FillInStuff () + { + int nArgs; + + /* + * This happens when the node was restored via ReadFromFile(). + * It leaves the types in retStr/argStrs for resolution after + * all definitions have been read from the object file in case + * there are forward references. + */ + if (retType == null) { + retType = MakeTypeToken (retStr); + } + if (argTypes == null) { + nArgs = argStrs.Length; + argTypes = new TokenType[nArgs]; + for (int i = 0; i < nArgs; i ++) { + argTypes[i] = MakeTypeToken (argStrs[i]); + } + } + + /* + * Fill in system types from token types. + * Might as well build the signature strings too from token types. + */ + retSysType = retType.ToSysType(); + + nArgs = argTypes.Length; + StringBuilder sb = new StringBuilder (); + argSysTypes = new Type[nArgs]; + sb.Append ('('); + for (int i = 0; i < nArgs; i ++) { + if (i > 0) sb.Append (','); + sb.Append (argTypes[i].ToString ()); + argSysTypes[i] = argTypes[i].ToSysType (); + } + sb.Append (')'); + argSig = sb.ToString (); + wholeSig = retType.ToString () + argSig; + + /* + * Now we can create a system delegate type from the given + * return and argument types. Give it an unique name using + * the whole signature string. + */ + sysType = DelegateCommon.GetType (retSysType, argSysTypes, wholeSig); + } + + /** + * @brief create delegate reference token for inline functions. + * there is just one instance of these per inline function + * shared by all scripts, and it is just used when the + * script engine is loaded. + */ + public static TokenDeclSDTypeDelegate CreateInline (TokenType retType, TokenType[] argTypes) + { + TokenDeclSDTypeDelegate decldel; + + /* + * Name it after the whole signature string. + */ + StringBuilder sb = new StringBuilder ("$inline"); + sb.Append (retType.ToString ()); + sb.Append ("("); + bool first = true; + foreach (TokenType at in argTypes) { + if (!first) sb.Append (","); + sb.Append (at.ToString ()); + first = false; + } + sb.Append (")"); + string inlname = sb.ToString (); + if (!inlines.TryGetValue (inlname, out decldel)) { + + /* + * Create the corresponding declaration and link to it + */ + TokenName name = new TokenName (null, inlname); + decldel = new TokenDeclSDTypeDelegate (name); + decldel.retType = retType; + decldel.argTypes = argTypes; + inlines.Add (inlname, decldel); + inlrevs.Add (decldel.GetSysType (), inlname); + } + return decldel; + } + + public static string TryGetInlineName (Type sysType) + { + string name; + if (!inlrevs.TryGetValue (sysType, out name)) return null; + return name; + } + + public static Type TryGetInlineSysType (string name) + { + TokenDeclSDTypeDelegate decl; + if (!inlines.TryGetValue (name, out decl)) return null; + return decl.GetSysType (); + } + } + + public class TokenDeclSDTypeInterface : TokenDeclSDType { + public VarDict methsNProps = new VarDict (false); + // any class that implements this interface + // must implement all of these methods & properties + + public List implements = new List (); + // any class that implements this interface + // must also implement all of the methods & properties + // of all of these interfaces + + public TokenDeclSDTypeInterface (TokenName shortName) : base (shortName) + { + this.shortName = shortName; + } + + protected override TokenDeclSDType MakeBlank (TokenName shortName) + { + return new TokenDeclSDTypeInterface (shortName); + } + + public override TokenType MakeRefToken (Token t) + { + return new TokenTypeSDTypeInterface (t, this); + } + + public override Type GetSysType () + { + // interfaces are implemented as arrays of delegates + // they are taken from iDynMeths[interfaceIndex] of a script-defined class object + return typeof (Delegate[]); + } + + public override void WriteToFile (BinaryWriter objFileWriter) + { + objFileWriter.Write (this.file); + objFileWriter.Write (this.line); + objFileWriter.Write (this.posn); + objFileWriter.Write ((byte)INTERFACE); + objFileWriter.Write (this.sdTypeIndex); + } + + public override void ReadFromFile (BinaryReader objFileReader, TextWriter asmFileWriter) + { } + + /** + * @brief Add this interface to the list of interfaces implemented by a class if not already. + * And also add this interface's implemented interfaces to the class for those not already there, + * just as if the class itself had declared to implement those interfaces. + */ + public void AddToClassDecl (TokenDeclSDTypeClass tokdeclcl) + { + if (!tokdeclcl.implements.Contains (this)) { + tokdeclcl.implements.Add (this); + foreach (TokenDeclSDTypeInterface subimpl in this.implements) { + subimpl.AddToClassDecl (tokdeclcl); + } + } + } + + /** + * @brief See if the 'this' interface implements the new interface. + * Do a recursive (deep) check. + */ + public bool Implements (TokenDeclSDTypeInterface newDecl) + { + foreach (TokenDeclSDTypeInterface ii in this.implements) { + if (ii == newDecl) return true; + if (ii.Implements (newDecl)) return true; + } + return false; + } + + /** + * @brief Scan an interface and all its implemented interfaces for a method or property + * @param scg = script code generator (ie, which script is being compiled) + * @param fieldName = name of the member being looked for + * @param argsig = the method's argument signature + * @returns null: no such member; intf = undefined + * else: member; intf = which interface actually found in + */ + public TokenDeclVar FindIFaceMember (ScriptCodeGen scg, TokenName fieldName, TokenType[] argsig, out TokenDeclSDTypeInterface intf) + { + intf = this; + TokenDeclVar var = scg.FindSingleMember (this.methsNProps, fieldName, argsig); + if (var == null) { + foreach (TokenDeclSDTypeInterface ii in this.implements) { + var = ii.FindIFaceMember (scg, fieldName, argsig, out intf); + if (var != null) break; + } + } + return var; + } + } + + public class TokenDeclSDTypeTypedef : TokenDeclSDType { + + public TokenDeclSDTypeTypedef (TokenName shortName) : base (shortName) + { + this.shortName = shortName; + } + + protected override TokenDeclSDType MakeBlank (TokenName shortName) + { + return new TokenDeclSDTypeTypedef (shortName); + } + + public override TokenType MakeRefToken (Token t) + { + // if our body is a single type token, that is what we return + // otherwise return null saying maybe our body needs some substitutions + if (!(this.nextToken is TokenType)) return null; + if (this.nextToken.nextToken != this.endToken) { + this.nextToken.nextToken.ErrorMsg ("extra tokens for typedef"); + return null; + } + return (TokenType)this.nextToken.CopyToken (t); + } + + public override Type GetSysType () + { + // we are just a macro + // we are asked for system type because we are cataloged + // but we don't really have one so return null + return null; + } + + public override void WriteToFile (BinaryWriter objFileWriter) + { + objFileWriter.Write (this.file); + objFileWriter.Write (this.line); + objFileWriter.Write (this.posn); + objFileWriter.Write ((byte)TYPEDEF); + objFileWriter.Write (this.sdTypeIndex); + } + + public override void ReadFromFile (BinaryReader objFileReader, TextWriter asmFileWriter) + { } + } + + /** + * @brief Script-defined type references. + * These occur in the source code wherever it specifies (eg, variable declaration) a script-defined type. + * These must be copyable via CopyToken(). + */ + public abstract class TokenTypeSDType : TokenType { + public TokenTypeSDType (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeSDType (Token t) : base (t) { } + public abstract TokenDeclSDType GetDecl (); + public abstract void SetDecl (TokenDeclSDType decl); + } + + public class TokenTypeSDTypeClass : TokenTypeSDType { + private static readonly FieldInfo iarSDTClObjsFieldInfo = typeof (XMRInstArrays).GetField ("iarSDTClObjs"); + + public TokenDeclSDTypeClass decl; + + public TokenTypeSDTypeClass (Token t, TokenDeclSDTypeClass decl) : base (t) + { + this.decl = decl; + } + public override TokenDeclSDType GetDecl () + { + return decl; + } + public override void SetDecl (TokenDeclSDType decl) + { + this.decl = (TokenDeclSDTypeClass)decl; + } + public override string ToString () + { + return decl.longName.val; + } + public override Type ToSysType () + { + return typeof (XMRSDTypeClObj); + } + + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes ias) + { + declVar.vTableArray = iarSDTClObjsFieldInfo; + declVar.vTableIndex = ias.iasSDTClObjs ++; + } + + // debugging + public override void DebString (StringBuilder sb) + { + sb.Append (decl.longName); + } + } + + public class TokenTypeSDTypeDelegate : TokenTypeSDType { + private static readonly FieldInfo iarObjectsFieldInfo = typeof (XMRInstArrays).GetField ("iarObjects"); + + public TokenDeclSDTypeDelegate decl; + + /** + * @brief create a reference to an explicitly declared delegate + * @param t = where the reference is being made in the source file + * @param decl = the explicit delegate declaration + */ + public TokenTypeSDTypeDelegate (Token t, TokenDeclSDTypeDelegate decl) : base (t) + { + this.decl = decl; + } + public override TokenDeclSDType GetDecl () + { + return decl; + } + public override void SetDecl (TokenDeclSDType decl) + { + this.decl = (TokenDeclSDTypeDelegate)decl; + } + + /** + * @brief create a reference to a possibly anonymous delegate + * @param t = where the reference is being made in the source file + * @param retType = return type (TokenTypeVoid if void, never null) + * @param argTypes = script-visible argument types + * @param tokenScript = what script this is part of + */ + public TokenTypeSDTypeDelegate (Token t, TokenType retType, TokenType[] argTypes, TokenScript tokenScript) : base (t) + { + TokenDeclSDTypeDelegate decldel; + + /* + * See if we already have a matching declared one cataloged. + */ + int nArgs = argTypes.Length; + foreach (TokenDeclSDType decl in tokenScript.sdSrcTypesValues) { + if (decl is TokenDeclSDTypeDelegate) { + decldel = (TokenDeclSDTypeDelegate)decl; + TokenType rt = decldel.GetRetType (); + TokenType[] ats = decldel.GetArgTypes (); + if ((rt.ToString () == retType.ToString ()) && (ats.Length == nArgs)) { + for (int i = 0; i < nArgs; i ++) { + if (ats[i].ToString () != argTypes[i].ToString ()) goto nomatch; + } + this.decl = decldel; + return; + } + } + nomatch:; + } + + /* + * No such luck, create a new anonymous declaration. + */ + StringBuilder sb = new StringBuilder ("$anondel$"); + sb.Append (retType.ToString ()); + sb.Append ("("); + bool first = true; + foreach (TokenType at in argTypes) { + if (!first) sb.Append (","); + sb.Append (at.ToString ()); + first = false; + } + sb.Append (")"); + TokenName name = new TokenName (t, sb.ToString ()); + decldel = new TokenDeclSDTypeDelegate (name); + decldel.SetRetArgTypes (retType, argTypes); + tokenScript.sdSrcTypesAdd (name.val, decldel); + this.decl = decldel; + } + + public override Type ToSysType () + { + return decl.GetSysType (); + } + + public override string ToString () + { + return decl.longName.val; + } + + /** + * @brief Assign slots in the gblObjects[] array because we have to typecast out in any case. + * Likewise with the sdtcObjects[] array. + */ + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes ias) + { + declVar.vTableArray = iarObjectsFieldInfo; + declVar.vTableIndex = ias.iasObjects ++; + } + + /** + * @brief create delegate reference token for inline functions. + */ + public TokenTypeSDTypeDelegate (TokenType retType, TokenType[] argTypes) : base (null) + { + this.decl = TokenDeclSDTypeDelegate.CreateInline (retType, argTypes); + } + + // debugging + public override void DebString (StringBuilder sb) + { + sb.Append (decl.longName); + } + } + + public class TokenTypeSDTypeInterface : TokenTypeSDType { + private static readonly FieldInfo iarSDTIntfObjsFieldInfo = typeof (XMRInstArrays).GetField ("iarSDTIntfObjs"); + + public TokenDeclSDTypeInterface decl; + + public TokenTypeSDTypeInterface (Token t, TokenDeclSDTypeInterface decl) : base (t) + { + this.decl = decl; + } + public override TokenDeclSDType GetDecl () + { + return decl; + } + public override void SetDecl (TokenDeclSDType decl) + { + this.decl = (TokenDeclSDTypeInterface)decl; + } + + public override string ToString () + { + return decl.longName.val; + } + public override Type ToSysType () + { + return typeof (Delegate[]); + } + + /** + * @brief Assign slots in the gblSDTIntfObjs[] array + * Likewise with the sdtcSDTIntfObjs[] array. + */ + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes ias) + { + declVar.vTableArray = iarSDTIntfObjsFieldInfo; + declVar.vTableIndex = ias.iasSDTIntfObjs ++; + } + + // debugging + public override void DebString (StringBuilder sb) + { + sb.Append (decl.longName); + } + } + + /** + * @brief function argument list declaration + */ + public class TokenArgDecl : Token + { + public VarDict varDict = new VarDict (false); + + public TokenArgDecl (Token original) : base (original) { } + + public bool AddArg (TokenType type, TokenName name) + { + TokenDeclVar var = new TokenDeclVar (name, null, null); + var.name = name; + var.type = type; + var.vTableIndex = varDict.Count; + return varDict.AddEntry (var); + } + + /** + * @brief Get an array of the argument types. + */ + private TokenType[] _types; + public TokenType[] types { + get { + if (_types == null) { + _types = new TokenType[varDict.Count]; + foreach (TokenDeclVar var in varDict) { + _types[var.vTableIndex] = var.type; + } + } + return _types; + } + } + + /** + * @brief Access the arguments as an array of variables. + */ + private TokenDeclVar[] _vars; + public TokenDeclVar[] vars { + get { + if (_vars == null) { + _vars = new TokenDeclVar[varDict.Count]; + foreach (TokenDeclVar var in varDict) { + _vars[var.vTableIndex] = var; + } + } + return _vars; + } + } + + /** + * @brief Get argument signature string, eg, "(list,vector,integer)" + */ + private string argSig = null; + public string GetArgSig () + { + if (argSig == null) { + argSig = ScriptCodeGen.ArgSigString (types); + } + return argSig; + } + } + + /** + * @brief encapsulate a state declaration in a single token + */ + public class TokenDeclState : Token { + + public TokenName name; // null for default state + public TokenStateBody body; + + public TokenDeclState (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + if (name == null) { + sb.Append ("default"); + } else { + sb.Append ("state "); + sb.Append (name); + } + body.DebString (sb); + } + } + + /** + * @brief encapsulate the declaration of a field/function/method/property/variable. + */ + + public enum Triviality { // function triviality: has no loops and doesn't call anything that has loops + // such a function does not need all the CheckRun() and stack serialization stuff + unknown, // function's Triviality unknown as of yet + // - it does not have any loops or backward gotos + // - nothing it calls is known to be complex + trivial, // function known to be trivial + // - it does not have any loops or backward gotos + // - everything it calls is known to be trivial + complex, // function known to be complex + // - it has loops or backward gotos + // - something it calls is known to be complex + analyzing // triviality is being analyzed (used to detect recursive loops) + }; + + public class TokenDeclVar : TokenStmt { + public TokenName name; // vars: name; funcs: bare name, ie, no signature + public TokenRVal init; // vars: null if none; funcs: null + public bool constant; // vars: 'constant'; funcs: false + public uint sdtFlags; // SDT_<*> flags + + public CompValu location; // used by codegen to keep track of location + public FieldInfo vTableArray; + public int vTableIndex = -1; // local vars: not used (-1) + // arg vars: index in the arg list + // global vars: which slot in gbls[] array it is stored + // instance vars: which slot in insts[] array it is stored + // static vars: which slot in gbls[] array it is stored + // global funcs: not used (-1) + // virt funcs: which slot in vTable[] array it is stored + // instance func: not used (-1) + public TokenDeclVar getProp; // if property, function that reads value + public TokenDeclVar setProp; // if property, function that writes value + + public TokenScript tokenScript; // what script this function is part of + public TokenDeclSDType sdtClass; // null: script global member + // else: member is part of this script-defined type + + // function-only data: + + public TokenType retType; // vars: null; funcs: TokenTypeVoid if void + public TokenArgDecl argDecl; // vars: null; funcs: argument list prototypes + public TokenStmtBlock body; // vars: null; funcs: statements (null iff abstract) + public Dictionary labels = new Dictionary (); + // all labels defined in the function + public LinkedList localVars = new LinkedList (); + // all local variables declared by this function + // - doesn't include argument variables + public TokenIntfImpl implements; // if script-defined type method, what interface method(s) this func implements + public TokenRValCall baseCtorCall; // if script-defined type constructor, call to base constructor, if any + public Triviality triviality = Triviality.unknown; + // vars: unknown (not used for any thing); funcs: unknown/trivial/complex + public LinkedList unknownTrivialityCalls = new LinkedList (); + // reduction puts all calls here + // compilation sorts it all out + + public ScriptObjWriter ilGen; // codegen stores emitted code here + + /** + * @brief Set up a variable declaration token. + * @param original = original source token that triggered definition + * (for error messages) + * @param func = null: global variable + * else: local to the given function + */ + public TokenDeclVar (Token original, TokenDeclVar func, TokenScript ts) : base (original) + { + if (func != null) { + func.localVars.AddLast (this); + } + tokenScript = ts; + } + + /** + * @brief Get/Set overall type + * For vars, this is the type of the location + * For funcs, this is the delegate type + */ + private TokenType _type; + public TokenType type { + get { + if (_type == null) { + GetDelType (); + } + return _type; + } + set { + _type = value; + } + } + + /** + * @brief Full name: .() + * () missing for fields/variables + * . missing for top-level functions/variables + */ + public string fullName { + get { + if (sdtClass == null) { + if (retType == null) return name.val; + return funcNameSig.val; + } + string ln = sdtClass.longName.val; + if (retType == null) return ln + "." + name.val; + return ln + "." + funcNameSig.val; + } + } + + /** + * @brief See if reading or writing the variable is trivial. + * Note that for functions, this is reading the function itself, + * as in 'someDelegate = SomeFunction;', not calling it as such. + * The triviality of actually calling the function is IsFuncTrivial(). + */ + public bool IsVarTrivial (ScriptCodeGen scg) + { + // reading or writing a property involves a function call however + // so we need to check the triviality of the property functions + if ((getProp != null) && !getProp.IsFuncTrivial (scg)) return false; + if ((setProp != null) && !setProp.IsFuncTrivial (scg)) return false; + + // otherwise for variables it is a trivial access + // and likewise for getting a delegate that points to a function + return true; + } + + /***************************\ + * FUNCTION-only methods * + \***************************/ + + private TokenName _funcNameSig; // vars: null; funcs: function name including argumet signature, eg, "PrintStuff(list,string)" + public TokenName funcNameSig { + get { + if (_funcNameSig == null) { + if (argDecl == null) return null; + _funcNameSig = new TokenName (name, name.val + argDecl.GetArgSig ()); + } + return _funcNameSig; + } + } + + /** + * @brief The bare function name, ie, without any signature info + */ + public string GetSimpleName () + { + return name.val; + } + + /** + * @brief The function name as it appears in the object code, + * ie, script-defined type name if any, + * bare function name and argument signature, + * eg, "MyClass.PrintStuff(string)" + */ + public string GetObjCodeName () + { + string objCodeName = ""; + if (sdtClass != null) { + objCodeName += sdtClass.longName.val + "."; + } + objCodeName += funcNameSig.val; + return objCodeName; + } + + /** + * @brief Get delegate type. + * This is the function's script-visible type, + * It includes return type and all script-visible argument types. + * @returns null for vars; else delegate type for funcs + */ + public TokenTypeSDTypeDelegate GetDelType () + { + if (argDecl == null) return null; + if (_type == null) { + if (tokenScript == null) { + // used during startup to define inline function delegate types + _type = new TokenTypeSDTypeDelegate (retType, argDecl.types); + } else { + // used for normal script processing + _type = new TokenTypeSDTypeDelegate (this, retType, argDecl.types, tokenScript); + } + } + if (!(_type is TokenTypeSDTypeDelegate)) return null; + return (TokenTypeSDTypeDelegate)_type; + } + + /** + * @brief See if the function's code itself is trivial or not. + * If it contains any loops (calls to CheckRun()), it is not trivial. + * If it calls anything that is not trivial, it is not trivial. + * Otherwise it is trivial. + */ + public bool IsFuncTrivial (ScriptCodeGen scg) + { + /* + * If not really a function, assume it's a delegate. + * And since we don't really know what functions it can point to, + * assume it can point to a non-trivial one. + */ + if (retType == null) return false; + + /* + * All virtual functions are non-trivial because although a particular + * one might be trivial, it might be overidden with a non-trivial one. + */ + if ((sdtFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_OVERRIDE | + ScriptReduce.SDT_VIRTUAL)) != 0) { + return false; + } + + /* + * Check the triviality status of the function. + */ + switch (triviality) { + + /* + * Don't yet know if it is trivial. + * We know at this point it doesn't have any direct looping. + * But if it calls something that has loops, it isn't trivial. + * Otherwise it is trivial. + */ + case Triviality.unknown: { + + /* + * Mark that we are analyzing this function now. So if there are + * any recursive call loops, that will show that the function is + * non-trivial and the analysis will terminate at that point. + */ + triviality = Triviality.analyzing; + + /* + * Check out everything else this function calls. If any say they + * aren't trivial, then we say this function isn't trivial. + */ + foreach (TokenRValCall call in unknownTrivialityCalls) { + if (!call.IsRValTrivial (scg, null)) { + triviality = Triviality.complex; + return false; + } + } + + /* + * All functions called by this function are trivial, and this + * function's code doesn't have any loops, so we can mark this + * function as being trivial. + */ + triviality = Triviality.trivial; + return true; + } + + /* + * We already know that it is trivial. + */ + case Triviality.trivial: { + return true; + } + + /* + * We either know it is complex or are trying to analyze it already. + * If we are already analyzing it, it means it has a recursive loop + * and we assume those are non-trivial. + */ + default: return false; + } + } + + // debugging + public override void DebString (StringBuilder sb) + { + DebStringSDTFlags (sb); + + if (retType == null) { + sb.Append (constant ? "constant" : type.ToString ()); + sb.Append (' '); + sb.Append (name.val); + if (init != null) { + sb.Append (" = "); + init.DebString (sb); + } + sb.Append (';'); + } else { + if (!(retType is TokenTypeVoid)) { + sb.Append (retType.ToString ()); + sb.Append (' '); + } + string namestr = name.val; + if (namestr == "$ctor") namestr = "constructor"; + sb.Append (namestr); + sb.Append (" ("); + for (int i = 0; i < argDecl.vars.Length; i ++) { + if (i > 0) sb.Append (", "); + sb.Append (argDecl.vars[i].type.ToString ()); + sb.Append (' '); + sb.Append (argDecl.vars[i].name.val); + } + sb.Append (')'); + if (body == null) sb.Append (';'); + else { + sb.Append (' '); + body.DebString (sb); + } + } + } + + // debugging + // - used to output contents of a $globalvarinit(), $instfieldinit() or $statisfieldinit() function + // as a series of variable declaration statements with initial value assignments + // so we get the initial value assignments done in same order as specified in script + public void DebStringInitFields (StringBuilder sb) + { + if ((retType == null) || !(retType is TokenTypeVoid)) throw new Exception ("bad return type " + retType.GetType ().Name); + if (argDecl.vars.Length != 0) throw new Exception ("has " + argDecl.vars.Length + " arg(s)"); + + for (Token stmt = body.statements; stmt != null; stmt = stmt.nextToken) { + + /* + * Body of the function should all be arithmetic statements (not eg for loops, if statements etc). + */ + TokenRVal rval = ((TokenStmtRVal)stmt).rVal; + + /* + * And the opcode should be a simple assignment operator. + */ + TokenRValOpBin rvob = (TokenRValOpBin)rval; + if (!(rvob.opcode is TokenKwAssign)) throw new Exception ("bad op type " + rvob.opcode.GetType ().Name); + + /* + * Get field or variable being assigned to. + */ + TokenDeclVar var = null; + TokenRVal left = rvob.rValLeft; + if (left is TokenLValIField) { + TokenLValIField ifield = (TokenLValIField)left; + TokenRValThis zhis = (TokenRValThis)ifield.baseRVal; + TokenDeclSDTypeClass sdt = zhis.sdtClass; + var = sdt.members.FindExact (ifield.fieldName.val, null); + } + if (left is TokenLValName) { + TokenLValName global = (TokenLValName)left; + var = global.stack.FindExact (global.name.val, null); + } + if (left is TokenLValSField) { + TokenLValSField sfield = (TokenLValSField)left; + TokenTypeSDTypeClass sdtc = (TokenTypeSDTypeClass)sfield.baseType; + TokenDeclSDTypeClass decl = sdtc.decl; + var = decl.members.FindExact (sfield.fieldName.val, null); + } + if (var == null) throw new Exception ("unknown var type " + left.GetType ().Name); + + /* + * Output flags, type name and bare variable name. + * This should look like a declaration in the 'sb' + * as it is not enclosed in a function. + */ + var.DebStringSDTFlags (sb); + var.type.DebString (sb); + sb.Append (' '); + sb.Append (var.name.val); + + /* + * Maybe it has a non-default initialization value. + */ + if ((var.init != null) && !(var.init is TokenRValInitDef)) { + sb.Append (" = "); + var.init.DebString (sb); + } + + /* + * End of declaration statement. + */ + sb.Append (';'); + } + } + + private void DebStringSDTFlags (StringBuilder sb) + { + if ((sdtFlags & ScriptReduce.SDT_PRIVATE) != 0) sb.Append ("private "); + if ((sdtFlags & ScriptReduce.SDT_PROTECTED) != 0) sb.Append ("protected "); + if ((sdtFlags & ScriptReduce.SDT_PUBLIC) != 0) sb.Append ("public "); + if ((sdtFlags & ScriptReduce.SDT_ABSTRACT) != 0) sb.Append ("abstract "); + if ((sdtFlags & ScriptReduce.SDT_FINAL) != 0) sb.Append ("final "); + if ((sdtFlags & ScriptReduce.SDT_NEW) != 0) sb.Append ("new "); + if ((sdtFlags & ScriptReduce.SDT_OVERRIDE) != 0) sb.Append ("override "); + if ((sdtFlags & ScriptReduce.SDT_STATIC) != 0) sb.Append ("static "); + if ((sdtFlags & ScriptReduce.SDT_VIRTUAL) != 0) sb.Append ("virtual "); + } + } + + /** + * @brief Indicates an interface type.method that is implemented by the function + */ + public class TokenIntfImpl : Token { + public TokenTypeSDTypeInterface intfType; + public TokenName methName; // simple name, no arg signature + + public TokenIntfImpl (TokenTypeSDTypeInterface intfType, TokenName methName) : base (intfType) + { + this.intfType = intfType; + this.methName = methName; + } + } + + /** + * @brief any expression that can go on left side of an "=" + */ + public abstract class TokenLVal : TokenRVal { + public TokenLVal (Token original) : base (original) { } + public abstract override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig); + public abstract override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig); + } + + /** + * @brief an element of an array is an L-value + */ + public class TokenLValArEle : TokenLVal { + public TokenRVal baseRVal; + public TokenRVal subRVal; + + public TokenLValArEle (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + TokenType baseType = baseRVal.GetRValType (scg, null); + + /* + * Maybe referencing element of a fixed-dimension array. + */ + if ((baseType is TokenTypeSDTypeClass) && (((TokenTypeSDTypeClass)baseType).decl.arrayOfType != null)) { + return ((TokenTypeSDTypeClass)baseType).decl.arrayOfType; + } + + /* + * Maybe referencing $idxprop property of script-defined class or interface. + */ + if (baseType is TokenTypeSDTypeClass) { + TokenDeclSDTypeClass sdtDecl = ((TokenTypeSDTypeClass)baseType).decl; + TokenDeclVar idxProp = scg.FindSingleMember (sdtDecl.members, new TokenName (this, "$idxprop"), null); + if (idxProp != null) return idxProp.type; + } + if (baseType is TokenTypeSDTypeInterface) { + TokenDeclSDTypeInterface sdtDecl = ((TokenTypeSDTypeInterface)baseType).decl; + TokenDeclVar idxProp = sdtDecl.FindIFaceMember (scg, new TokenName (this, "$idxprop"), null, out sdtDecl); + if (idxProp != null) return idxProp.type; + } + + /* + * Maybe referencing single character of a string. + */ + if ((baseType is TokenTypeKey) || (baseType is TokenTypeStr)) { + return new TokenTypeChar (this); + } + + /* + * Assume XMR_Array element or extracting element from list. + */ + if ((baseType is TokenTypeArray) || (baseType is TokenTypeList)) { + return new TokenTypeObject (this); + } + + scg.ErrorMsg (this, "unknown array reference"); + return new TokenTypeVoid (this); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return baseRVal.IsRValTrivial (scg, null) && subRVal.IsRValTrivial (scg, null); + } + + public override void DebString (StringBuilder sb) + { + baseRVal.DebString (sb); + sb.Append ('['); + subRVal.DebString (sb); + sb.Append (']'); + } + } + + /** + * @brief 'base.' being used to reference a field/method of the extended class. + */ + public class TokenLValBaseField : TokenLVal { + public TokenName fieldName; + private TokenDeclSDTypeClass thisClass; + + public TokenLValBaseField (Token original, TokenName fieldName, TokenDeclSDTypeClass thisClass) : base (original) + { + this.fieldName = fieldName; + this.thisClass = thisClass; + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + TokenDeclVar var = scg.FindThisMember (thisClass.extends, fieldName, argsig); + if (var != null) return var.type; + scg.ErrorMsg (fieldName, "unknown member of " + thisClass.extends.ToString ()); + return new TokenTypeVoid (fieldName); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + TokenDeclVar var = scg.FindThisMember (thisClass.extends, fieldName, argsig); + return (var != null) && var.IsVarTrivial (scg); + } + + public override bool IsCallTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + TokenDeclVar var = scg.FindThisMember (thisClass.extends, fieldName, argsig); + return (var != null) && var.IsFuncTrivial (scg); + } + } + + /** + * @brief a field within an struct is an L-value + */ + public class TokenLValIField : TokenLVal { + public TokenRVal baseRVal; + public TokenName fieldName; + + public TokenLValIField (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + TokenType baseType = baseRVal.GetRValType (scg, null); + if (baseType is TokenTypeSDTypeClass) { + TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig); + if (var != null) return var.type; + } + if (baseType is TokenTypeSDTypeInterface) { + TokenDeclSDTypeInterface baseIntfDecl = ((TokenTypeSDTypeInterface)baseType).decl; + TokenDeclVar var = baseIntfDecl.FindIFaceMember (scg, fieldName, argsig, out baseIntfDecl); + if (var != null) return var.type; + } + if (baseType is TokenTypeArray) { + return XMR_Array.GetRValType (fieldName); + } + if ((baseType is TokenTypeRot) || (baseType is TokenTypeVec)) { + return new TokenTypeFloat (fieldName); + } + scg.ErrorMsg (fieldName, "unknown member of " + baseType.ToString ()); + return new TokenTypeVoid (fieldName); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + /* + * If getting pointer to instance isn't trivial, then accessing the member isn't trivial either. + */ + if (!baseRVal.IsRValTrivial (scg, null)) return false; + + /* + * Accessing a member of a class depends on the member. + * In the case of a method, this is accessing it as a delegate, not calling it, and + * argsig simply serves as selecting which of possibly overloaded methods to select. + * The case of accessing a property, however, depends on the property implementation, + * as there could be looping inside the property code. + */ + TokenType baseType = baseRVal.GetRValType (scg, null); + if (baseType is TokenTypeSDTypeClass) { + TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig); + return (var != null) && var.IsVarTrivial (scg); + } + + /* + * Accessing the members of anything else (arrays, rotations, vectors) is always trivial. + */ + return true; + } + + /** + * @brief Check to see if the case of calling an instance method of some object is trivial. + * @param scg = script making the call + * @param argsig = argument types of the call (used to select among overloads) + * @returns true iff we can tell at compile time that the call will always call a trivial method + */ + public override bool IsCallTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + /* + * If getting pointer to instance isn't trivial, then calling the method isn't trivial either. + */ + if (!baseRVal.IsRValTrivial (scg, null)) return false; + + /* + * Calling a method of a class depends on the method. + */ + TokenType baseType = baseRVal.GetRValType (scg, null); + if (baseType is TokenTypeSDTypeClass) { + TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig); + return (var != null) && var.IsFuncTrivial (scg); + } + + /* + * Calling via a pointer to an interface instance is never trivial. + * (It is really a pointer to an array of delegates). + * We can't tell for this call site whether the actual method being called is trivial or not, + * so we have to assume it isn't. + * ??? We could theoretically check to see if *all* implementations of this method of + * this interface are trivial, then we could conclude that this call is trivial. + */ + if (baseType is TokenTypeSDTypeInterface) return false; + + /* + * Calling a method of anything else (arrays, rotations, vectors) is always trivial. + * Even methods of delegates, such as ".GetArgTypes()" that we may someday do is trivial. + */ + return true; + } + + // debugging + public override void DebString (StringBuilder sb) + { + baseRVal.DebString (sb); + sb.Append ('.'); + sb.Append (fieldName.val); + } + } + + /** + * @brief a name is being used as an L-value + */ + public class TokenLValName : TokenLVal { + public TokenName name; + public VarDict stack; + + public TokenLValName (TokenName name, VarDict stack) : base (name) + { + /* + * Save name of variable/method/function/field. + */ + this.name = name; + + /* + * Save where in the stack it can be looked up. + * If the current stack is for locals, do not allow forward references. + * this allows idiocy like: + * list buttons = [ 1, 2, 3 ]; + * x () { + * list buttons = llList2List (buttons, 0, 1); + * llOwnerSay (llList2CSV (buttons)); + * } + * If it is not locals, allow forward references. + * this allows function X() to call Y() and Y() to call X(). + */ + this.stack = stack.FreezeLocals (); + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + TokenDeclVar var = scg.FindNamedVar (this, argsig); + if (var != null) return var.type; + scg.ErrorMsg (name, "undefined name " + name.val + ScriptCodeGen.ArgSigString (argsig)); + return new TokenTypeVoid (name); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + TokenDeclVar var = scg.FindNamedVar (this, argsig); + return (var != null) && var.IsVarTrivial (scg); + } + + /** + * @brief Check to see if the case of calling a global method is trivial. + * @param scg = script making the call + * @param argsig = argument types of the call (used to select among overloads) + * @returns true iff we can tell at compile time that the call will always call a trivial method + */ + public override bool IsCallTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + TokenDeclVar var = scg.FindNamedVar (this, argsig); + return (var != null) && var.IsFuncTrivial (scg); + } + + // debugging + public override void DebString (StringBuilder sb) + { + sb.Append (name.val); + } + } + + /** + * @brief a static field within a struct is an L-value + */ + public class TokenLValSField : TokenLVal { + public TokenType baseType; + public TokenName fieldName; + + public TokenLValSField (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + if (baseType is TokenTypeSDTypeClass) { + TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig); + if (var != null) return var.type; + } + scg.ErrorMsg (fieldName, "unknown member of " + baseType.ToString ()); + return new TokenTypeVoid (fieldName); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + /* + * Accessing a member of a class depends on the member. + * In the case of a method, this is accessing it as a delegate, not calling it, and + * argsig simply serves as selecting which of possibly overloaded methods to select. + * The case of accessing a property, however, depends on the property implementation, + * as there could be looping inside the property code. + */ + if (baseType is TokenTypeSDTypeClass) { + TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig); + return (var != null) && var.IsVarTrivial (scg); + } + + /* + * Accessing the fields/methods/properties of anything else (arrays, rotations, vectors) is always trivial. + */ + return true; + } + + /** + * @brief Check to see if the case of calling a class' static method is trivial. + * @param scg = script making the call + * @param argsig = argument types of the call (used to select among overloads) + * @returns true iff we can tell at compile time that the call will always call a trivial method + */ + public override bool IsCallTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + /* + * Calling a static method of a class depends on the method. + */ + if (baseType is TokenTypeSDTypeClass) { + TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig); + return (var != null) && var.IsFuncTrivial (scg); + } + + /* + * Calling a static method of anything else (arrays, rotations, vectors) is always trivial. + */ + return true; + } + + public override void DebString (StringBuilder sb) + { + if (fieldName.val == "$new") { + sb.Append ("new "); + baseType.DebString (sb); + } else { + baseType.DebString (sb); + sb.Append ('.'); + fieldName.DebString (sb); + } + } + } + + /** + * @brief any expression that can go on right side of "=" + */ + public delegate TokenRVal TCCLookup (TokenRVal rVal, ref bool didOne); + public abstract class TokenRVal : Token { + public TokenRVal (Token original) : base (original) { } + + /** + * @brief Tell us the type of the expression. + */ + public abstract TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig); + + /** + * @brief Tell us if reading and writing the value is trivial. + * + * @param scg = script code generator of script making the access + * @param argsig = argument types of the call (used to select among overloads) + * @returns true: we can tell at compile time that reading/writing this location + * will always be trivial (no looping or CheckRun() calls possible). + * false: otherwise + */ + public abstract bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig); + + /** + * @brief Tell us if calling the method is trivial. + * + * This is the default implementation that returns false. + * It is only used if the location is holding a delegate + * and the method that the delegate is pointing to is being + * called. Since we can't tell if the actual runtime method + * is trivial or not, we assume it isn't. + * + * For the more usual ways of calling functions, see the + * various overrides of IsCallTrivial(). + * + * @param scg = script code generator of script making the call + * @param argsig = argument types of the call (used to select among overloads) + * @returns true: we can tell at compile time that this call will always + * be to a trivial function/method (no looping or CheckRun() + * calls possible). + * false: otherwise + */ + public virtual bool IsCallTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return false; + } + + /** + * @brief If the result of the expression is a constant, + * create a TokenRValConst equivalent, set didOne, and return that. + * Otherwise, just return the original without changing didOne. + */ + public virtual TokenRVal TryComputeConstant (TCCLookup lookup, ref bool didOne) + { + return lookup (this, ref didOne); + } + } + + /** + * @brief a postfix operator and corresponding L-value + */ + public class TokenRValAsnPost : TokenRVal { + public TokenLVal lVal; + public Token postfix; + + public TokenRValAsnPost (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return lVal.GetRValType (scg, argsig); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return lVal.IsRValTrivial (scg, null); + } + + public override void DebString (StringBuilder sb) + { + lVal.DebString (sb); + sb.Append (' '); + postfix.DebString (sb); + } + } + + /** + * @brief a prefix operator and corresponding L-value + */ + public class TokenRValAsnPre : TokenRVal { + public Token prefix; + public TokenLVal lVal; + + public TokenRValAsnPre (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return lVal.GetRValType (scg, argsig); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return lVal.IsRValTrivial (scg, null); + } + + public override void DebString (StringBuilder sb) + { + prefix.DebString (sb); + sb.Append (' '); + lVal.DebString (sb); + } + } + + /** + * @brief calling a function or method, ie, may have side-effects + */ + public class TokenRValCall : TokenRVal { + + public TokenRVal meth; // points to the function to be called + // - might be a reference to a global function (TokenLValName) + // - or an instance method of a class (TokenLValIField) + // - or a static method of a class (TokenLValSField) + // - or a delegate stored in a variable (assumption for anything else) + public TokenRVal args; // null-terminated TokenRVal list + public int nArgs; // number of elements in args + + public TokenRValCall (Token original) : base (original) { } + + private TokenType[] myArgSig; + + /** + * @brief The type of a call is the type of the return value. + */ + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + /* + * Build type signature so we select correct overloaded function. + */ + if (myArgSig == null) { + myArgSig = new TokenType[nArgs]; + int i = 0; + for (Token t = args; t != null; t = t.nextToken) { + myArgSig[i++] = ((TokenRVal)t).GetRValType (scg, null); + } + } + + /* + * Get the type of the method itself. This should get us a delegate type. + */ + TokenType delType = meth.GetRValType (scg, myArgSig); + if (!(delType is TokenTypeSDTypeDelegate)) { + scg.ErrorMsg (meth, "must be function or method"); + return new TokenTypeVoid (meth); + } + + /* + * Get the return type from the delegate type. + */ + return ((TokenTypeSDTypeDelegate)delType).decl.GetRetType (); + } + + /** + * @brief See if the call to the function/method is trivial. + * It is trivial if all the argument computations are trivial and + * the function is not being called via virtual table or delegate + * and the function body is trivial. + */ + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + /* + * Build type signature so we select correct overloaded function. + */ + if (myArgSig == null) { + myArgSig = new TokenType[nArgs]; + int i = 0; + for (Token t = args; t != null; t = t.nextToken) { + myArgSig[i++] = ((TokenRVal)t).GetRValType (scg, null); + } + } + + /* + * Make sure all arguments can be computed trivially. + */ + for (Token t = args; t != null; t = t.nextToken) { + if (!((TokenRVal)t).IsRValTrivial (scg, null)) return false; + } + + /* + * See if the function call itself and the function body are trivial. + */ + return meth.IsCallTrivial (scg, myArgSig); + } + + // debugging + public override void DebString (StringBuilder sb) + { + meth.DebString (sb); + sb.Append (" ("); + bool first = true; + for (Token t = args; t != null; t = t.nextToken) { + if (!first) sb.Append (", "); + t.DebString (sb); + first = false; + } + sb.Append (")"); + } + } + + /** + * @brief encapsulates a typecast, ie, (type) + */ + public class TokenRValCast : TokenRVal { + public TokenType castTo; + public TokenRVal rVal; + + public TokenRValCast (TokenType type, TokenRVal value) : base (type) + { + castTo = type; + rVal = value; + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return castTo; + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + argsig = null; + if (castTo is TokenTypeSDTypeDelegate) { + argsig = ((TokenTypeSDTypeDelegate)castTo).decl.GetArgTypes (); + } + return rVal.IsRValTrivial (scg, argsig); + } + + /** + * @brief If operand is constant, maybe we can say the whole thing is a constant. + */ + public override TokenRVal TryComputeConstant (TCCLookup lookup, ref bool didOne) + { + rVal = rVal.TryComputeConstant (lookup, ref didOne); + if (rVal is TokenRValConst) { + try { + object val = ((TokenRValConst)rVal).val; + object nval = null; + if (castTo is TokenTypeChar) { + if (val is char) return rVal; + if (val is int) nval = (char)(int)val; + } + if (castTo is TokenTypeFloat) { + if (val is double) return rVal; + if (val is int) nval = (double)(int)val; + if (val is string) nval = new LSL_Float ((string)val).value; + } + if (castTo is TokenTypeInt) { + if (val is int) return rVal; + if (val is char) nval = (int)(char)val; + if (val is double) nval = (int)(double)val; + if (val is string) nval = new LSL_Integer ((string)val).value; + } + if (castTo is TokenTypeRot) { + if (val is LSL_Rotation) return rVal; + if (val is string) nval = new LSL_Rotation ((string)val); + } + if ((castTo is TokenTypeKey) || (castTo is TokenTypeStr)) { + if (val is string) nval = val; // in case of key/string conversion + if (val is char) nval = TypeCast.CharToString ((char)val); + if (val is double) nval = TypeCast.FloatToString ((double)val); + if (val is int) nval = TypeCast.IntegerToString ((int)val); + if (val is LSL_Rotation) nval = TypeCast.RotationToString ((LSL_Rotation)val); + if (val is LSL_Vector) nval = TypeCast.VectorToString ((LSL_Vector)val); + } + if (castTo is TokenTypeVec) { + if (val is LSL_Vector) return rVal; + if (val is string) nval = new LSL_Vector ((string)val); + } + if (nval != null) { + TokenRVal rValConst = new TokenRValConst (castTo, nval); + didOne = true; + return rValConst; + } + } catch { + } + } + return this; + } + + public override void DebString (StringBuilder sb) + { + sb.Append ('('); + castTo.DebString (sb); + sb.Append (')'); + rVal.DebString (sb); + } + } + + /** + * @brief Encapsulate a conditional expression: + * ? : + */ + public class TokenRValCondExpr : TokenRVal { + public TokenRVal condExpr; + public TokenRVal trueExpr; + public TokenRVal falseExpr; + + public TokenRValCondExpr (Token original) : base (original) + { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + TokenType trueType = trueExpr.GetRValType (scg, argsig); + TokenType falseType = falseExpr.GetRValType (scg, argsig); + if (trueType.ToString () != falseType.ToString ()) { + scg.ErrorMsg (condExpr, "true & false expr types don't match"); + } + return trueType; + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return condExpr.IsRValTrivial (scg, null) && + trueExpr.IsRValTrivial (scg, argsig) && + falseExpr.IsRValTrivial (scg, argsig); + } + + /** + * @brief If condition is constant, then the whole expression is constant + * iff the corresponding trueExpr or falseExpr is constant. + */ + public override TokenRVal TryComputeConstant (TCCLookup lookup, ref bool didOne) + { + TokenRVal rValCond = condExpr.TryComputeConstant (lookup, ref didOne); + if (rValCond is TokenRValConst) { + didOne = true; + bool isTrue = ((TokenRValConst)rValCond).IsConstBoolTrue (); + return (isTrue ? trueExpr : falseExpr).TryComputeConstant (lookup, ref didOne); + } + return this; + } + + // debugging + public override void DebString (StringBuilder sb) + { + condExpr.DebString (sb); + sb.Append (" ? "); + trueExpr.DebString (sb); + sb.Append (" : "); + falseExpr.DebString (sb); + } + } + + /** + * @brief all constants supposed to end up here + */ + public enum TokenRValConstType : byte { CHAR = 0, FLOAT = 1, INT = 2, KEY = 3, STRING = 4 }; + public class TokenRValConst : TokenRVal { + public object val; // always a system type (char, int, double, string), never LSL-wrapped + public TokenRValConstType type; + public TokenType tokType; + + public TokenRValConst (Token original, object value) : base (original) + { + val = value; + + TokenType tt = null; + if (val is char) { + type = TokenRValConstType.CHAR; + tt = new TokenTypeChar (this); + } else if (val is int) { + type = TokenRValConstType.INT; + tt = new TokenTypeInt (this); + } else if (val is double) { + type = TokenRValConstType.FLOAT; + tt = new TokenTypeFloat (this); + } else if (val is string) { + type = TokenRValConstType.STRING; + tt = new TokenTypeStr (this); + } else { + throw new Exception ("invalid constant type " + val.GetType ()); + } + + tokType = (original is TokenType) ? (TokenType)original : tt; + if (tokType is TokenTypeKey) { + type = TokenRValConstType.KEY; + } + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return tokType; + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return true; + } + + public CompValu GetCompValu () + { + switch (type) { + case TokenRValConstType.CHAR: { return new CompValuChar (tokType, (char)val); } + case TokenRValConstType.FLOAT: { return new CompValuFloat (tokType, (double)val); } + case TokenRValConstType.INT: { return new CompValuInteger (tokType, (int)val); } + case TokenRValConstType.KEY: + case TokenRValConstType.STRING: { return new CompValuString (tokType, (string)val); } + default: throw new Exception ("unknown type"); + } + } + + public override TokenRVal TryComputeConstant (TCCLookup lookup, ref bool didOne) + { + // gotta end somewhere + return this; + } + + public bool IsConstBoolTrue () + { + switch (type) { + case TokenRValConstType.CHAR: { return (char)val != 0; } + case TokenRValConstType.FLOAT: { return (double)val != 0; } + case TokenRValConstType.INT: { return (int)val != 0; } + case TokenRValConstType.KEY: { return (string)val != "" && (string)val != ScriptBaseClass.NULL_KEY; } + case TokenRValConstType.STRING: { return (string)val != ""; } + default: throw new Exception ("unknown type"); + } + } + + public override void DebString (StringBuilder sb) + { + if (val is char) { + sb.Append ('\''); + EscapeQuotes (sb, new string (new char [] { (char)val })); + sb.Append ('\''); + } else if (val is int) { + sb.Append ((int)val); + } else if (val is double) { + string str = ((double)val).ToString (); + sb.Append (str); + if ((str.IndexOf ('.') < 0) && + (str.IndexOf ('E') < 0) && + (str.IndexOf ('e') < 0)) { + sb.Append (".0"); + } + } else if (val is string) { + sb.Append ('"'); + EscapeQuotes (sb, (string)val); + sb.Append ('"'); + } else { + throw new Exception ("invalid constant type " + val.GetType ()); + } + } + private static void EscapeQuotes (StringBuilder sb, string s) + { + foreach (char c in s) { + switch (c) { + case '\n': { + sb.Append ("\\n"); + break; + } + case '\t': { + sb.Append ("\\t"); + break; + } + case '\\': { + sb.Append ("\\\\"); + break; + } + case '\'': { + sb.Append ("\\'"); + break; + } + case '\"': { + sb.Append ("\\\""); + break; + } + default: { + sb.Append (c); + break; + } + } + } + } + } + + /** + * @brief Default initialization value for the corresponding variable. + */ + public class TokenRValInitDef : TokenRVal { + public TokenType type; + + public static TokenRValInitDef Construct (TokenDeclVar tokenDeclVar) + { + TokenRValInitDef zhis = new TokenRValInitDef (tokenDeclVar); + zhis.type = tokenDeclVar.type; + return zhis; + } + private TokenRValInitDef (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return type; + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + // it's always just a constant so it's always very trivial + return true; + } + + public override void DebString (StringBuilder sb) + { + sb.Append ("'); + } + } + + /** + * @brief encapsulation of is + */ + public class TokenRValIsType : TokenRVal { + public TokenRVal rValExp; + public TokenTypeExp typeExp; + + public TokenRValIsType (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return new TokenTypeBool (rValExp); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return rValExp.IsRValTrivial (scg, argsig); + } + } + + /** + * @brief an R-value enclosed in brackets is an LSLList + */ + public class TokenRValList : TokenRVal { + + public TokenRVal rVal; // null-terminated list of TokenRVal objects + public int nItems; + + public TokenRValList (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return new TokenTypeList (rVal); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + for (Token t = rVal; t != null; t = t.nextToken) { + if (!((TokenRVal)t).IsRValTrivial (scg, null)) return false; + } + return true; + } + + public override void DebString (StringBuilder sb) + { + bool first = true; + sb.Append ('['); + for (Token t = rVal; t != null; t = t.nextToken) { + if (!first) sb.Append (','); + sb.Append (' '); + t.DebString (sb); + first = false; + } + sb.Append (" ]"); + } + } + + /** + * @brief encapsulates '$new' arraytype '{' ... '}' + */ + public class TokenRValNewArIni : TokenRVal { + public TokenType arrayType; + public TokenList valueList; // TokenList : a sub-list + // TokenKwComma : a default value + // TokenRVal : init expression + + public TokenRValNewArIni (Token original) : base (original) + { + valueList = new TokenList (original); + } + + // type of the expression = the array type allocated by $new() + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return arrayType; + } + + // The expression is trivial if all the initializers are trivial. + // An array's constructor is always trivial (no CheckRun() calls). + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return ListIsTrivial (scg, valueList); + } + private bool ListIsTrivial (ScriptCodeGen scg, TokenList valList) + { + foreach (Token val in valList.tl) { + if (val is TokenRVal) { + if (!((TokenRVal)val).IsRValTrivial (scg, null)) return false; + } + if (val is TokenList) { + if (!ListIsTrivial (scg, (TokenList)val)) return false; + } + } + return true; + } + + public override void DebString (StringBuilder sb) + { + sb.Append ("new "); + arrayType.DebString (sb); + sb.Append (' '); + valueList.DebString (sb); + } + } + public class TokenList : Token { + public List tl = new List (); + public TokenList (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ('{'); + bool first = true; + foreach (Token t in tl) { + if (!first) sb.Append (", "); + t.DebString (sb); + first = false; + } + sb.Append ('}'); + } + } + + /** + * @brief a binary operator and its two operands + */ + public class TokenRValOpBin : TokenRVal { + public TokenRVal rValLeft; + public TokenKw opcode; + public TokenRVal rValRight; + + public TokenRValOpBin (TokenRVal left, TokenKw op, TokenRVal right) : base (op) + { + rValLeft = left; + opcode = op; + rValRight = right; + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + /* + * Comparisons and the like always return bool. + */ + string opstr = opcode.ToString (); + if ((opstr == "==") || (opstr == "!=") || (opstr == ">=") || (opstr == ">") || + (opstr == "&&") || (opstr == "||") || (opstr == "<=") || (opstr == "<") || + (opstr == "&&&") || (opstr == "|||")) { + return new TokenTypeBool (opcode); + } + + /* + * Comma is always type of right-hand operand. + */ + if (opstr == ",") return rValRight.GetRValType (scg, argsig); + + /* + * Assignments are always the type of the left-hand operand, + * including stuff like "+=". + */ + if (opstr.EndsWith ("=")) { + return rValLeft.GetRValType (scg, argsig); + } + + /* + * string+something or something+string is always string. + * except list+something or something+list is always a list. + */ + string lType = rValLeft.GetRValType (scg, argsig).ToString (); + string rType = rValRight.GetRValType (scg, argsig).ToString (); + if ((opstr == "+") && ((lType == "list") || (rType == "list"))) { + return new TokenTypeList (opcode); + } + if ((opstr == "+") && ((lType == "key") || (lType == "string") || + (rType == "key") || (rType == "string"))) { + return new TokenTypeStr (opcode); + } + + /* + * Everything else depends on both operands. + */ + string key = lType + opstr + rType; + BinOpStr binOpStr; + if (BinOpStr.defined.TryGetValue (key, out binOpStr)) { + return TokenType.FromSysType (opcode, binOpStr.outtype); + } + + scg.ErrorMsg (opcode, "undefined operation " + key); + return new TokenTypeVoid (opcode); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return rValLeft.IsRValTrivial (scg, null) && rValRight.IsRValTrivial (scg, null); + } + + /** + * @brief If both operands are constants, maybe we can say the whole thing is a constant. + */ + public override TokenRVal TryComputeConstant (TCCLookup lookup, ref bool didOne) + { + rValLeft = rValLeft.TryComputeConstant (lookup, ref didOne); + rValRight = rValRight.TryComputeConstant (lookup, ref didOne); + if ((rValLeft is TokenRValConst) && (rValRight is TokenRValConst)) { + try { + object val = opcode.binOpConst (((TokenRValConst)rValLeft).val, + ((TokenRValConst)rValRight).val); + TokenRVal rValConst = new TokenRValConst (opcode, val); + didOne = true; + return rValConst; + } catch { + } + } + return this; + } + + // debugging + public override void DebString (StringBuilder sb) + { + rValLeft.DebString (sb); + sb.Append (' '); + sb.Append (opcode.ToString ()); + sb.Append (' '); + rValRight.DebString (sb); + } + } + + /** + * @brief an unary operator and its one operand + */ + public class TokenRValOpUn : TokenRVal { + public TokenKw opcode; + public TokenRVal rVal; + + public TokenRValOpUn (TokenKw op, TokenRVal right) : base (op) + { + opcode = op; + rVal = right; + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + if (opcode is TokenKwExclam) return new TokenTypeInt (opcode); + return rVal.GetRValType (scg, null); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return rVal.IsRValTrivial (scg, null); + } + + /** + * @brief If operand is constant, maybe we can say the whole thing is a constant. + */ + public override TokenRVal TryComputeConstant (TCCLookup lookup, ref bool didOne) + { + rVal = rVal.TryComputeConstant (lookup, ref didOne); + if (rVal is TokenRValConst) { + try { + object val = opcode.unOpConst (((TokenRValConst)rVal).val); + TokenRVal rValConst = new TokenRValConst (opcode, val); + didOne = true; + return rValConst; + } catch { + } + } + return this; + } + + /** + * @brief Serialization/Deserialization. + */ + public TokenRValOpUn (Token original) : base (original) { } + + // debugging + public override void DebString (StringBuilder sb) + { + sb.Append (opcode.ToString ()); + rVal.DebString (sb); + } + } + + /** + * @brief an R-value enclosed in parentheses + */ + public class TokenRValParen : TokenRVal { + + public TokenRVal rVal; + + public TokenRValParen (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + // pass argsig through in this simple case, ie, let + // them do something like (llOwnerSay)("blabla..."); + return rVal.GetRValType (scg, argsig); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + // pass argsig through in this simple case, ie, let + // them do something like (llOwnerSay)("blabla..."); + return rVal.IsRValTrivial (scg, argsig); + } + + /** + * @brief If operand is constant, we can say the whole thing is a constant. + */ + public override TokenRVal TryComputeConstant (TCCLookup lookup, ref bool didOne) + { + rVal = rVal.TryComputeConstant (lookup, ref didOne); + if (rVal is TokenRValConst) { + didOne = true; + return rVal; + } + return this; + } + + public override void DebString (StringBuilder sb) + { + sb.Append ('('); + rVal.DebString (sb); + sb.Append (')'); + } + } + + public class TokenRValRot : TokenRVal { + + public TokenRVal xRVal; + public TokenRVal yRVal; + public TokenRVal zRVal; + public TokenRVal wRVal; + + public TokenRValRot (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return new TokenTypeRot (xRVal); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return xRVal.IsRValTrivial (scg, null) && + yRVal.IsRValTrivial (scg, null) && + zRVal.IsRValTrivial (scg, null) && + wRVal.IsRValTrivial (scg, null); + } + + public override void DebString (StringBuilder sb) + { + sb.Append ('<'); + xRVal.DebString (sb); + sb.Append (','); + yRVal.DebString (sb); + sb.Append (','); + zRVal.DebString (sb); + sb.Append (','); + wRVal.DebString (sb); + sb.Append ('>'); + } + } + + /** + * @brief 'this' is being used as an rval inside an instance method. + */ + public class TokenRValThis : TokenRVal { + public Token original; + public TokenDeclSDTypeClass sdtClass; + + public TokenRValThis (Token original, TokenDeclSDTypeClass sdtClass) : base (original) + { + this.original = original; + this.sdtClass = sdtClass; + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return sdtClass.MakeRefToken (original); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return true; // ldarg.0/starg.0 can't possibly loop + } + + // debugging + public override void DebString (StringBuilder sb) + { + sb.Append ("this"); + } + } + + /** + * @brief the 'undef' keyword is being used as a value in an expression. + * It is the null object pointer and has type TokenTypeUndef. + */ + public class TokenRValUndef : TokenRVal { + Token original; + + public TokenRValUndef (Token original) : base (original) + { + this.original = original; + } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return new TokenTypeUndef (original); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return true; + } + + public override void DebString (StringBuilder sb) + { + sb.Append ("undef"); + } + } + + /** + * @brief put 3 RVals together as a Vector value. + */ + public class TokenRValVec : TokenRVal { + + public TokenRVal xRVal; + public TokenRVal yRVal; + public TokenRVal zRVal; + + public TokenRValVec (Token original) : base (original) { } + + public override TokenType GetRValType (ScriptCodeGen scg, TokenType[] argsig) + { + return new TokenTypeVec (xRVal); + } + + public override bool IsRValTrivial (ScriptCodeGen scg, TokenType[] argsig) + { + return xRVal.IsRValTrivial (scg, null) && + yRVal.IsRValTrivial (scg, null) && + zRVal.IsRValTrivial (scg, null); + } + + public override void DebString (StringBuilder sb) + { + sb.Append ('<'); + xRVal.DebString (sb); + sb.Append (','); + yRVal.DebString (sb); + sb.Append (','); + zRVal.DebString (sb); + sb.Append ('>'); + } + } + + /** + * @brief encapsulates the whole script in a single token + */ + public class TokenScript : Token { + public int expiryDays = Int32.MaxValue; + public TokenDeclState defaultState; + public Dictionary states = new Dictionary (); + public VarDict variablesStack = new VarDict (false); // initial one is used for global functions and variables + public TokenDeclVar globalVarInit; // $globalvarinit function + // - performs explicit global var and static field inits + + private Dictionary sdSrcTypes = new Dictionary (); + private bool sdSrcTypesSealed = false; + + public TokenScript (Token original) : base (original) { } + + /* + * Handle variable definition stack. + * Generally a '{' pushes a new frame and a '}' pops the frame. + * Function parameters are pushed in an additional frame (just outside the body's { ... } block) + */ + public void PushVarFrame (bool locals) + { + PushVarFrame (new VarDict (locals)); + } + public void PushVarFrame (VarDict newFrame) + { + newFrame.outerVarDict = variablesStack; + variablesStack = newFrame; + } + public void PopVarFrame () + { + variablesStack = variablesStack.outerVarDict; + } + public bool AddVarEntry (TokenDeclVar var) + { + return variablesStack.AddEntry (var); + } + + /* + * Handle list of script-defined types. + */ + public void sdSrcTypesSeal () + { + sdSrcTypesSealed = true; + } + public bool sdSrcTypesContainsKey (string key) + { + return sdSrcTypes.ContainsKey (key); + } + public bool sdSrcTypesTryGetValue (string key, out TokenDeclSDType value) + { + return sdSrcTypes.TryGetValue (key, out value); + } + public void sdSrcTypesAdd (string key, TokenDeclSDType value) + { + if (sdSrcTypesSealed) throw new Exception ("sdSrcTypes is sealed"); + value.sdTypeIndex = sdSrcTypes.Count; + sdSrcTypes.Add (key, value); + } + public void sdSrcTypesRep (string key, TokenDeclSDType value) + { + if (sdSrcTypesSealed) throw new Exception ("sdSrcTypes is sealed"); + value.sdTypeIndex = sdSrcTypes[key].sdTypeIndex; + sdSrcTypes[key] = value; + } + public void sdSrcTypesReplace (string key, TokenDeclSDType value) + { + if (sdSrcTypesSealed) throw new Exception ("sdSrcTypes is sealed"); + sdSrcTypes[key] = value; + } + public Dictionary.ValueCollection sdSrcTypesValues + { + get { + return sdSrcTypes.Values; + } + } + public int sdSrcTypesCount + { + get { + return sdSrcTypes.Count; + } + } + + /** + * @brief Debug output. + */ + public override void DebString (StringBuilder sb) + { + /* + * Script-defined types. + */ + foreach (TokenDeclSDType srcType in sdSrcTypes.Values) { + srcType.DebString (sb); + } + + /* + * Global constants. + * Variables are handled by outputting the $globalvarinit function. + */ + foreach (TokenDeclVar var in variablesStack) { + if (var.constant) { + var.DebString (sb); + } + } + + /* + * Global functions. + */ + foreach (TokenDeclVar var in variablesStack) { + if (var == globalVarInit) { + var.DebStringInitFields (sb); + } else if (var.retType != null) { + var.DebString (sb); + } + } + + /* + * States and their event handler functions. + */ + defaultState.DebString (sb); + foreach (TokenDeclState st in states.Values) { + st.DebString (sb); + } + } + } + + /** + * @brief state body declaration + */ + public class TokenStateBody : Token { + + public TokenDeclVar eventFuncs; + + public int index = -1; // (codegen) row in ScriptHandlerEventTable (0=default) + + public TokenStateBody (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append (" { "); + for (Token t = eventFuncs; t != null; t = t.nextToken) { + t.DebString (sb); + } + sb.Append (" } "); + } + } + + /** + * @brief a single statement, such as ending on a semicolon or enclosed in braces + * TokenStmt includes the terminating semicolon or the enclosing braces + * Also includes @label; for jump targets. + * Also includes stray ; null statements. + * Also includes local variable declarations with or without initialization value. + */ + public class TokenStmt : Token { + public TokenStmt (Token original) : base (original) { } + } + + /** + * @brief a group of statements enclosed in braces + */ + public class TokenStmtBlock : TokenStmt { + + public Token statements; // null-terminated list of statements, can also have TokenDeclVar's in here + public TokenStmtBlock outerStmtBlock; // next outer stmtBlock or null if top-level, ie, function definition + public TokenDeclVar function; // function it is part of + public bool isTry; // true iff it's a try statement block + public bool isCatch; // true iff it's a catch statement block + public bool isFinally; // true iff it's a finally statement block + public TokenStmtTry tryStmt; // set iff isTry|isCatch|isFinally is set + + public TokenStmtBlock (Token original) : base (original) { } + + // debugging + public override void DebString (StringBuilder sb) + { + sb.Append ("{ "); + for (Token stmt = statements; stmt != null; stmt = stmt.nextToken) { + stmt.DebString (sb); + } + sb.Append ("} "); + } + } + + /** + * @brief definition of branch target name + */ + public class TokenStmtLabel : TokenStmt { + + public TokenName name; // the label's name + public TokenStmtBlock block; // which block it is defined in + public bool hasBkwdRefs = false; + + public bool labelTagged; // code gen: location of label + public ScriptMyLabel labelStruct; + + public TokenStmtLabel (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ('@'); + name.DebString (sb); + sb.Append (';'); + } + } + + /** + * @brief those types of RVals with a semi-colon on the end + * that are allowed to stand alone as statements + */ + public class TokenStmtRVal : TokenStmt { + public TokenRVal rVal; + + public TokenStmtRVal (Token original) : base (original) { } + + // debugging + public override void DebString (StringBuilder sb) + { + rVal.DebString (sb); + sb.Append ("; "); + } + } + + public class TokenStmtBreak : TokenStmt { + public TokenStmtBreak (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("break;"); + } + } + + public class TokenStmtCont : TokenStmt { + public TokenStmtCont (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("continue;"); + } + } + + /** + * @brief "do" statement + */ + public class TokenStmtDo : TokenStmt { + + public TokenStmt bodyStmt; + public TokenRValParen testRVal; + + public TokenStmtDo (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("do "); + bodyStmt.DebString (sb); + sb.Append (" while "); + testRVal.DebString (sb); + sb.Append (';'); + } + } + + /** + * @brief "for" statement + */ + public class TokenStmtFor : TokenStmt { + + public TokenStmt initStmt; // there is always an init statement, though it may be a null statement + public TokenRVal testRVal; // there may or may not be a test (null if not) + public TokenRVal incrRVal; // there may or may not be an increment (null if not) + public TokenStmt bodyStmt; // there is always a body statement, though it may be a null statement + + public TokenStmtFor (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("for ("); + if (initStmt != null) initStmt.DebString (sb); + else sb.Append (';'); + if (testRVal != null) testRVal.DebString (sb); + sb.Append (';'); + if (incrRVal != null) incrRVal.DebString (sb); + sb.Append (") "); + bodyStmt.DebString (sb); + } + } + + /** + * @brief "foreach" statement + */ + public class TokenStmtForEach : TokenStmt { + + public TokenLVal keyLVal; + public TokenLVal valLVal; + public TokenRVal arrayRVal; + public TokenStmt bodyStmt; // there is always a body statement, though it may be a null statement + + public TokenStmtForEach (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("foreach ("); + if (keyLVal != null) keyLVal.DebString (sb); + sb.Append (','); + if (valLVal != null) valLVal.DebString (sb); + sb.Append (" in "); + arrayRVal.DebString (sb); + sb.Append (')'); + bodyStmt.DebString (sb); + } + } + + public class TokenStmtIf : TokenStmt { + + public TokenRValParen testRVal; + public TokenStmt trueStmt; + public TokenStmt elseStmt; + + public TokenStmtIf (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("if "); + testRVal.DebString (sb); + sb.Append (" "); + trueStmt.DebString (sb); + if (elseStmt != null) { + sb.Append (" else "); + elseStmt.DebString (sb); + } + } + } + + public class TokenStmtJump : TokenStmt { + + public TokenName label; + + public TokenStmtJump (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("jump "); + label.DebString (sb); + sb.Append (';'); + } + } + + public class TokenStmtNull : TokenStmt { + + public TokenStmtNull (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append (';'); + } + } + + public class TokenStmtRet : TokenStmt { + + public TokenRVal rVal; // null if void + + public TokenStmtRet (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("return"); + if (rVal != null) { + sb.Append (' '); + rVal.DebString (sb); + } + sb.Append (';'); + } + } + + /** + * @brief statement that changes the current state. + */ + public class TokenStmtState : TokenStmt { + + public TokenName state; // null for default + + public TokenStmtState (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("state "); + sb.Append ((state == null) ? "default" : state.val); + sb.Append (';'); + } + } + + /** + * @brief Encapsulates a whole switch statement including the body and all cases. + */ + public class TokenStmtSwitch : TokenStmt { + + public TokenRValParen testRVal; // the integer index expression + public TokenSwitchCase cases = null; // list of all cases, linked by .nextCase + public TokenSwitchCase lastCase = null; // used during reduce to point to last in 'cases' list + + public TokenStmtSwitch (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("switch "); + testRVal.DebString (sb); + sb.Append ('{'); + for (TokenSwitchCase kase = cases; kase != null; kase = kase.nextCase) { + kase.DebString (sb); + } + sb.Append ('}'); + } + } + + /** + * @brief Encapsulates a case/default clause from a switch statement including the + * two values and the corresponding body statements. + */ + public class TokenSwitchCase : Token { + public TokenSwitchCase nextCase; // next case in source-code order + public TokenRVal rVal1; // null means 'default', else 'case' + public TokenRVal rVal2; // null means 'case expr:', else 'case expr ... expr:' + public TokenStmt stmts; // statements associated with the case + public TokenStmt lastStmt; // used during reduce for building statement list + + public int val1; // codegen: value of rVal1 here + public int val2; // codegen: value of rVal2 here + public ScriptMyLabel label; // codegen: target label here + public TokenSwitchCase nextSortedCase; // codegen: next case in ascending val order + + public string str1; + public string str2; + public TokenSwitchCase lowerCase; + public TokenSwitchCase higherCase; + + public TokenSwitchCase (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + if (rVal1 == null) { + sb.Append ("default: "); + } else { + sb.Append ("case "); + rVal1.DebString (sb); + if (rVal2 != null) { + sb.Append (" ... "); + rVal2.DebString (sb); + } + sb.Append (": "); + } + for (Token t = stmts; t != null; t = t.nextToken) { + t.DebString (sb); + } + } + } + + public class TokenStmtThrow : TokenStmt { + + public TokenRVal rVal; // null if rethrow style + + public TokenStmtThrow (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("throw "); + rVal.DebString (sb); + sb.Append (';'); + } + } + + /** + * @brief Encapsulates related try, catch and finally statements. + */ + public class TokenStmtTry : TokenStmt { + + public TokenStmtBlock tryStmt; + public TokenDeclVar catchVar; // null iff catchStmt is null + public TokenStmtBlock catchStmt; // can be null + public TokenStmtBlock finallyStmt; // can be null + public Dictionary iLeaves = new Dictionary (); + + public TokenStmtTry (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("try "); + tryStmt.DebString (sb); + if (catchStmt != null) { + sb.Append ("catch ("); + sb.Append (catchVar.type.ToString ()); + sb.Append (' '); + sb.Append (catchVar.name.val); + sb.Append (") "); + catchStmt.DebString (sb); + } + if (finallyStmt != null) { + sb.Append ("finally "); + finallyStmt.DebString (sb); + } + } + } + + public class IntermediateLeave { + public ScriptMyLabel jumpIntoLabel; + public ScriptMyLabel jumpAwayLabel; + } + + public class TokenStmtVarIniDef : TokenStmt { + public TokenLVal var; + public TokenStmtVarIniDef (Token original) : base (original) { } + } + + public class TokenStmtWhile : TokenStmt { + + public TokenRValParen testRVal; + public TokenStmt bodyStmt; + + public TokenStmtWhile (Token original) : base (original) { } + + public override void DebString (StringBuilder sb) + { + sb.Append ("while "); + testRVal.DebString (sb); + sb.Append (' '); + bodyStmt.DebString (sb); + } + } + + /** + * @brief type expressions (right-hand of 'is' keyword). + */ + public class TokenTypeExp : Token { + public TokenTypeExp (Token original) : base (original) { } + } + + public class TokenTypeExpBinOp : TokenTypeExp { + public TokenTypeExp leftOp; + public Token binOp; + public TokenTypeExp rightOp; + + public TokenTypeExpBinOp (Token original) : base (original) { } + } + + public class TokenTypeExpNot : TokenTypeExp { + public TokenTypeExp typeExp; + + public TokenTypeExpNot (Token original) : base (original) { } + } + + public class TokenTypeExpPar : TokenTypeExp { + public TokenTypeExp typeExp; + + public TokenTypeExpPar (Token original) : base (original) { } + } + + public class TokenTypeExpType : TokenTypeExp { + public TokenType typeToken; + + public TokenTypeExpType (Token original) : base (original) { } + } + + public class TokenTypeExpUndef : TokenTypeExp { + public TokenTypeExpUndef (Token original) : base (original) { } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTokenize.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTokenize.cs new file mode 100644 index 0000000..a767dcf --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTokenize.cs @@ -0,0 +1,1729 @@ +/* + * 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. + */ + +/** + * @brief Parse raw source file string into token list. + * + * Usage: + * + * emsg = some function to output error messages to + * source = string containing entire source file + * + * TokenBegin tokenBegin = TokenBegin.Construct (emsg, source); + * + * tokenBegin = null: tokenizing error + * else: first (dummy) token in file + * the rest are chained by nextToken,prevToken + * final token is always a (dummy) TokenEnd + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +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; + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public delegate void TokenErrorMessage (Token token, string message); + + /** + * @brief base class for all tokens + */ + public class Token { + public static readonly int MAX_NAME_LEN = 255; + public static readonly int MAX_STRING_LEN = 4096; + + public Token nextToken; + public Token prevToken; + public bool nr2l; + + // used for error message printing + public TokenErrorMessage emsg; + public string file = ""; + public int line; + public int posn; + public Token copiedFrom; + + /** + * @brief construct a token coming directly from a source file + * @param emsg = object that error messages get sent to + * @param file = source file name (or "" if none) + * @param line = source file line number + * @param posn = token's position within that source line + */ + public Token (TokenErrorMessage emsg, string file, int line, int posn) + { + this.emsg = emsg; + this.file = file; + this.line = line; + this.posn = posn; + } + + /** + * @brief construct a token with same error message parameters + * @param original = original token to create from + */ + public Token (Token original) + { + if (original != null) { + this.emsg = original.emsg; + this.file = original.file; + this.line = original.line; + this.posn = original.posn; + this.nr2l = original.nr2l; + } + } + + /** + * @brief output an error message associated with this token + * sends the message to the token's error object + * @param message = error message string + */ + public void ErrorMsg (string message) + { + if (emsg != null) { + emsg (this, message); + } + } + + /* + * Generate a unique string (for use in CIL label names, etc) + */ + public string Unique + { + get { return file + "_" + line + "_" + posn; } + } + + /* + * Generate source location string (for use in error messages) + */ + public string SrcLoc + { + get { + string loc = file + "(" + line + "," + posn + ")"; + if (copiedFrom == null) return loc; + string fromLoc = copiedFrom.SrcLoc; + if (fromLoc.StartsWith (loc)) return fromLoc; + return loc + ":" + fromLoc; + } + } + + /* + * Used in generic instantiation to copy token. + * Only valid for parsing tokens, not reduction tokens + * because it is a shallow copy. + */ + public Token CopyToken (Token src) + { + Token t = (Token)this.MemberwiseClone (); + t.file = src.file; + t.line = src.line; + t.posn = src.posn; + t.copiedFrom = this; + return t; + } + + /* + * Generate debugging string - should look like source code. + */ + public virtual void DebString (StringBuilder sb) + { + sb.Append (this.ToString ()); + } + } + + + /** + * @brief token that begins a source file + * Along with TokenEnd, it keeps insertion/removal of intermediate tokens + * simple as the intermediate tokens always have non-null nextToken,prevToken. + */ + public class TokenBegin : Token { + + public int expiryDays = Int32.MaxValue; // has seen 'XMROption expiryDays;' + + private class Options { + public bool arrays; // has seen 'XMROption arrays;' + public bool advFlowCtl; // has seen 'XMROption advFlowCtl;' + public bool tryCatch; // has seen 'XMROption tryCatch;' + public bool objects; // has seen 'XMROption objects;' + public bool chars; // has seen 'XMROption chars;' + public bool noRightToLeft; // has seen 'XMROption noRightToLeft;' + public bool dollarsigns; // has seen 'XMROption dollarsigns;' + } + + private bool youveAnError; // there was some error tokenizing + private int bolIdx; // index in 'source' at begining of current line + private int lineNo; // current line in source file, starting at 0 + private string filNam; // current source file name + private string source; // the whole script source code + private Token lastToken; // last token created so far + private string cameFrom; // where the source came from + private TextWriter saveSource; // save copy of source here (or null) + private Options options = new Options (); + + /** + * @brief convert a source file in the form of a string + * to a list of raw tokens + * @param cameFrom = where the source came from + * @param emsg = where to output messages to + * @param source = whole source file contents + * @returns null: conversion error, message already output + * else: list of tokens, starting with TokenBegin, ending with TokenEnd. + */ + public static TokenBegin Construct (string cameFrom, TextWriter saveSource, TokenErrorMessage emsg, string source, out string sourceHash) + { + sourceHash = null; + + /* + * Now do the tokenization. + */ + TokenBegin tokenBegin = new TokenBegin (emsg, "", 0, 0); + tokenBegin.cameFrom = cameFrom; + tokenBegin.saveSource = saveSource; + tokenBegin.lastToken = tokenBegin; + tokenBegin.source = source; + tokenBegin.filNam = cameFrom; + if (saveSource != null) saveSource.WriteLine (source); + tokenBegin.Tokenize (); + if (tokenBegin.youveAnError) return null; + tokenBegin.AppendToken (new TokenEnd (emsg, tokenBegin.filNam, ++ tokenBegin.lineNo, 0)); + + /* + * Return source hash so caller can know if source changes. + */ + System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create (); + byte[] hashBytes = md5.ComputeHash (new TokenStream (tokenBegin)); + int hashBytesLen = hashBytes.Length; + StringBuilder sb = new StringBuilder (hashBytesLen * 2); + for (int i = 0; i < hashBytesLen; i ++) { + sb.Append (hashBytes[i].ToString ("X2")); + } + sourceHash = sb.ToString (); + if (saveSource != null) { + saveSource.WriteLine (" "); + saveSource.WriteLine ("********************************************************************************"); + saveSource.WriteLine ("**** source hash: " + sourceHash); + saveSource.WriteLine ("********************************************************************************"); + } + + return tokenBegin; + } + + private TokenBegin (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + + /* + * Stream consisting of all the tokens. + * Null delimeters between the tokens. + * Used for creating the source hash. + */ + private class TokenStream : Stream { + private Token curTok; + private bool delim; + private byte[] curBuf; + private int curOfs; + private int curLen; + + public TokenStream (Token t) + { + curTok = t; + } + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return false; } } + public override long Length { get { return 0; } } + public override long Position { get { return 0; } set { } } + + public override void Write (byte[] buffer, int offset, int count) { } + public override void Flush () { } + public override long Seek (long offset, SeekOrigin origin) { return 0; } + public override void SetLength (long value) { } + + public override int Read (byte[] buffer, int offset, int count) + { + int len, total; + for (total = 0; total < count; total += len) { + while ((len = curLen - curOfs) <= 0) { + if (curTok is TokenEnd) goto done; + curTok = curTok.nextToken; + if (curTok is TokenEnd) goto done; + curBuf = System.Text.Encoding.UTF8.GetBytes (curTok.ToString ()); + curOfs = 0; + curLen = curBuf.Length; + delim = true; + } + if (delim) { + buffer[offset+total] = 0; + delim = false; + len = 1; + } else { + if (len > count - total) len = count - total; + Array.Copy (curBuf, curOfs, buffer, offset + total, len); + curOfs += len; + } + } + done: + return total; + } + } + + /* + * Produces raw token stream: names, numbers, strings, keywords/delimeters. + * @param this.source = whole source file in one string + * @returns this.nextToken = filled in with tokens + * this.youveAnError = true: some tokenizing error + * false: successful + */ + private void Tokenize () + { + bolIdx = 0; + lineNo = 0; + for (int i = 0; i < source.Length; i ++) { + char c = source[i]; + if (c == '\n') { + + /* + * Increment source line number and set char index of beg of next line. + */ + lineNo ++; + bolIdx = i + 1; + + /* + * Check for '#' lineno filename newline + * lineno is line number of next line in file + * If found, save values and remove tokens from stream + */ + if ((lastToken is TokenStr) && + (lastToken.prevToken is TokenInt) && + (lastToken.prevToken.prevToken is TokenKwHash)) { + filNam = ((TokenStr)lastToken).val; + lineNo = ((TokenInt)lastToken.prevToken).val; + lastToken = lastToken.prevToken.prevToken.prevToken; + lastToken.nextToken = null; + } + continue; + } + + /* + * Skip over whitespace. + */ + if (c <= ' ') continue; + + /* + * Skip over comments. + */ + if ((i + 2 <= source.Length) && source.Substring (i, 2).Equals ("//")) { + while ((i < source.Length) && (source[i] != '\n')) i ++; + lineNo ++; + bolIdx = i + 1; + continue; + } + if ((i + 2 <= source.Length) && (source.Substring (i, 2).Equals ("/*"))) { + i += 2; + while ((i + 1 < source.Length) && (((c = source[i]) != '*') || (source[i+1] != '/'))) { + if (c == '\n') { + lineNo ++; + bolIdx = i + 1; + } + i ++; + } + i ++; + continue; + } + + /* + * Check for numbers. + */ + if ((c >= '0') && (c <= '9')) { + int j = TryParseFloat (i); + if (j == 0) j = TryParseInt (i); + i = -- j; + continue; + } + if ((c == '.') && (source[i+1] >= '0') && (source[i+1] <= '9')) { + int j = TryParseFloat (i); + if (j > 0) i = -- j; + continue; + } + + /* + * Check for quoted strings. + */ + if (c == '"') { + StringBuilder sb = new StringBuilder (); + bool backslash; + int j; + + backslash = false; + for (j = i; ++ j < source.Length;) { + c = source[j]; + if (c == '\\' && !backslash) { + backslash = true; + continue; + } + if (c == '\n') { + lineNo ++; + bolIdx = j + 1; + } else { + if (!backslash && (c == '"')) break; + if (backslash && (c == 'n')) c = '\n'; + if (backslash && (c == 't')) { + sb.Append (" "); + c = ' '; + } + } + backslash = false; + sb.Append (c); + } + if (j - i > MAX_STRING_LEN) { + TokenError (i, "string too long, max " + MAX_STRING_LEN); + } else { + AppendToken (new TokenStr (emsg, filNam, lineNo, i - bolIdx, sb.ToString ())); + } + i = j; + continue; + } + + /* + * Check for quoted characters. + */ + if (c == '\'') { + char cb = (char)0; + bool backslash, overflow, underflow; + int j; + + backslash = false; + overflow = false; + underflow = true; + for (j = i; ++ j < source.Length;) { + c = source[j]; + if (c == '\\' && !backslash) { + backslash = true; + continue; + } + if (c == '\n') { + lineNo ++; + bolIdx = j + 1; + } else { + if (!backslash && (c == '\'')) break; + if (backslash && (c == 'n')) c = '\n'; + if (backslash && (c == 't')) c = '\t'; + } + backslash = false; + overflow = !underflow; + underflow = false; + cb = c; + } + if (underflow || overflow) { + TokenError (i, "character must be exactly one character"); + } else { + AppendToken (new TokenChar (emsg, filNam, lineNo, i - bolIdx, cb)); + } + i = j; + continue; + } + + /* + * Check for keywords/names. + */ + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') || (c == '$' && options.dollarsigns)) { + int j; + + for (j = i; ++ j < source.Length;) { + c = source[j]; + if (c >= 'a' && c <= 'z') continue; + if (c >= 'A' && c <= 'Z') continue; + if (c >= '0' && c <= '9') continue; + if (c == '$' && options.dollarsigns) continue; + if (c != '_') break; + } + if (j - i > MAX_NAME_LEN) { + TokenError (i, "name too long, max " + MAX_NAME_LEN); + } else { + string name = source.Substring (i, j - i); + if (name == "quaternion") name = "rotation"; // see lslangtest1.lsl + if (keywords.ContainsKey (name)) { + Object[] args = new Object[] { emsg, filNam, lineNo, i - bolIdx }; + AppendToken ((Token)keywords[name].Invoke (args)); + } else if (options.arrays && arrayKeywords.ContainsKey (name)) { + Object[] args = new Object[] { emsg, filNam, lineNo, i - bolIdx }; + AppendToken ((Token)arrayKeywords[name].Invoke (args)); + } else if (options.advFlowCtl && advFlowCtlKeywords.ContainsKey (name)) { + Object[] args = new Object[] { emsg, filNam, lineNo, i - bolIdx }; + AppendToken ((Token)advFlowCtlKeywords[name].Invoke (args)); + } else if (options.tryCatch && tryCatchKeywords.ContainsKey (name)) { + Object[] args = new Object[] { emsg, filNam, lineNo, i - bolIdx }; + AppendToken ((Token)tryCatchKeywords[name].Invoke (args)); + } else if (options.objects && objectsKeywords.ContainsKey (name)) { + Object[] args = new Object[] { emsg, filNam, lineNo, i - bolIdx }; + AppendToken ((Token)objectsKeywords[name].Invoke (args)); + } else if (options.chars && charsKeywords.ContainsKey (name)) { + Object[] args = new Object[] { emsg, filNam, lineNo, i - bolIdx }; + AppendToken ((Token)charsKeywords[name].Invoke (args)); + } else { + AppendToken (new TokenName (emsg, filNam, lineNo, i - bolIdx, name)); + } + } + i = -- j; + continue; + } + + /* + * Check for option enables. + */ + if ((c == ';') && (lastToken is TokenName) && + (lastToken.prevToken is TokenName) && + (strcasecmp(((TokenName)lastToken.prevToken).val, "xmroption") == 0)) { + string opt = ((TokenName)lastToken).val; + if (strcasecmp (opt, "arrays") == 0) { + options.arrays = true; + } else if (strcasecmp (opt, "advflowctl") == 0) { + options.advFlowCtl = true; + } else if (strcasecmp (opt, "trycatch") == 0) { + options.tryCatch = true; + } else if (strcasecmp (opt, "objects") == 0) { + options.objects = true; + } else if (strcasecmp (opt, "chars") == 0) { + options.chars = true; + } else if (strcasecmp (opt, "norighttoleft") == 0) { + options.noRightToLeft = true; + } else if (strcasecmp (opt, "dollarsigns") == 0) { + options.dollarsigns = true; + } else { + lastToken.ErrorMsg ("unknown XMROption"); + } + lastToken = lastToken.prevToken.prevToken; + lastToken.nextToken = null; + continue; + } + + /* + * Handle 'xmroption' 'expirydays' numberofdays ';' + */ + if ((c == ';') && + (lastToken is TokenInt) && + (lastToken.prevToken is TokenName) && + (lastToken.prevToken.prevToken is TokenName) && + (strcasecmp(((TokenName)lastToken.prevToken.prevToken).val, "xmroption") == 0) && + (strcasecmp(((TokenName)lastToken.prevToken).val, "expirydays") == 0)) { + expiryDays = ((TokenInt)lastToken).val; + this.lastToken = lastToken.prevToken.prevToken.prevToken; + this.lastToken.nextToken = null; + continue; + } + + + /* + * Handle 'xmroption' 'include' sourceurl ';' + */ + if ((c == ';') && + (lastToken is TokenStr) && + (lastToken.prevToken is TokenName) && + (lastToken.prevToken.prevToken is TokenName) && + (strcasecmp(((TokenName)lastToken.prevToken.prevToken).val, "xmroption") == 0) && + (strcasecmp(((TokenName)lastToken.prevToken).val, "include") == 0)) { + + string newURL = ((TokenStr)lastToken).val; + if (newURL == "") { + lastToken.ErrorMsg ("empty URL string"); + continue; + } + string newCameFrom = CreateURL (this.cameFrom, newURL); + string newSource = ReadSourceFromURL (newCameFrom); + + this.lastToken = lastToken.prevToken.prevToken.prevToken; + this.lastToken.nextToken = null; + + if (newSource != null) { + if (saveSource != null) { + saveSource.WriteLine (" "); + saveSource.WriteLine ("********************************************************************************"); + saveSource.WriteLine ("**** include url: " + newCameFrom); + saveSource.WriteLine ("********************************************************************************"); + saveSource.WriteLine (newSource); + } + + string saveSourc = this.source; + string saveFilNam = this.filNam; + string saveCameFrom = this.cameFrom; + int saveBolIdx = this.bolIdx; + int saveLineNo = this.lineNo; + Options saveOptions = this.options; + this.source = newSource; + this.filNam = newURL; + this.cameFrom = newCameFrom; + this.options = new Options (); + this.Tokenize (); + this.source = saveSourc; + this.filNam = saveFilNam; + this.cameFrom = saveCameFrom; + this.bolIdx = saveBolIdx; + this.lineNo = saveLineNo; + this.options = saveOptions; + } + continue; + } + + /* + * Lastly, check for delimeters. + */ + { + int j; + int len = 0; + + for (j = 0; j < delims.Length; j ++) { + len = delims[j].str.Length; + if ((i + len <= source.Length) && (source.Substring (i, len).Equals (delims[j].str))) break; + } + if (j < delims.Length) { + Object[] args = { emsg, filNam, lineNo, i - bolIdx }; + Token kwToken = (Token)delims[j].ctorInfo.Invoke (args); + AppendToken (kwToken); + i += -- len; + continue; + } + } + + /* + * Don't know what it is! + */ + TokenError (i, "unknown character '" + c + "'"); + } + } + + private static int strcasecmp (String s, String t) + { + return String.Compare(s,t,StringComparison.OrdinalIgnoreCase); + } + + /** + * @brief try to parse a floating-point number from the source + * @param i = starting position within this.source of number + * @returns 0: not a floating point number, try something else + * else: position in this.source of terminating character, ie, past number + * TokenFloat appended to token list + * or error message has been output + */ + private int TryParseFloat (int i) + { + bool decimals, error, negexp, nulexp; + char c; + double f, f10; + int exponent, j, x, y; + ulong m, mantissa; + + decimals = false; + error = false; + exponent = 0; + mantissa = 0; + for (j = i; j < source.Length; j ++) { + c = source[j]; + if ((c >= '0') && (c <= '9')) { + m = mantissa * 10 + (ulong)(c - '0'); + if (m / 10 != mantissa) { + if (!decimals) exponent ++; + } else { + mantissa = m; + if (decimals) exponent --; + } + continue; + } + if (c == '.') { + if (decimals) { + TokenError (i, "more than one decimal point"); + return j; + } + decimals = true; + continue; + } + if ((c == 'E') || (c == 'e')) { + if (++ j >= source.Length) { + TokenError (i, "floating exponent off end of source"); + return j; + } + c = source[j]; + negexp = (c == '-'); + if (negexp || (c == '+')) j ++; + y = 0; + nulexp = true; + for (; j < source.Length; j ++) { + c = source[j]; + if ((c < '0') || (c > '9')) break; + x = y * 10 + (c - '0'); + if (x / 10 != y) { + if (!error) TokenError (i, "floating exponent overflow"); + error = true; + } + y = x; + nulexp = false; + } + if (nulexp) { + TokenError (i, "bad or missing floating exponent"); + return j; + } + if (negexp) { + x = exponent - y; + if (x > exponent) { + if (!error) TokenError (i, "floating exponent overflow"); + error = true; + } + } else { + x = exponent + y; + if (x < exponent) { + if (!error) TokenError (i, "floating exponent overflow"); + error = true; + } + } + exponent = x; + } + break; + } + if (!decimals) { + return 0; + } + + f = mantissa; + if ((exponent != 0) && (mantissa != 0) && !error) { + f10 = 10.0; + if (exponent < 0) { + exponent = -exponent; + while (exponent > 0) { + if ((exponent & 1) != 0) { + f /= f10; + } + exponent /= 2; + f10 *= f10; + } + } else { + while (exponent > 0) { + if ((exponent & 1) != 0) { + f *= f10; + } + exponent /= 2; + f10 *= f10; + } + } + } + if (!error) { + AppendToken (new TokenFloat (emsg, filNam, lineNo, i - bolIdx, f)); + } + return j; + } + + /** + * @brief try to parse an integer number from the source + * @param i = starting position within this.source of number + * @returns 0: not an integer number, try something else + * else: position in this.source of terminating character, ie, past number + * TokenInt appended to token list + * or error message has been output + */ + private int TryParseInt (int i) + { + bool error; + char c; + int j; + uint basse, m, mantissa; + + basse = 10; + error = false; + mantissa = 0; + for (j = i; j < source.Length; j ++) { + c = source[j]; + if ((c >= '0') && (c <= '9')) { + m = mantissa * basse + (uint)(c - '0'); + if (m / basse != mantissa) { + if (!error) TokenError (i, "integer overflow"); + error = true; + } + mantissa = m; + continue; + } + if ((basse == 16) && ((c >= 'A') && (c <= 'F'))) { + m = mantissa * basse + (uint)(c - 'A') + 10U; + if (m / basse != mantissa) { + if (!error) TokenError (i, "integer overflow"); + error = true; + } + mantissa = m; + continue; + } + if ((basse == 16) && ((c >= 'a') && (c <= 'f'))) { + m = mantissa * basse + (uint)(c - 'a') + 10U; + if (m / basse != mantissa) { + if (!error) TokenError (i, "integer overflow"); + error = true; + } + mantissa = m; + continue; + } + if (((c == 'x') || (c == 'X')) && (mantissa == 0) && (basse == 10)) { + basse = 16; + continue; + } + break; + } + if (!error) { + AppendToken (new TokenInt (emsg, filNam, lineNo, i - bolIdx, (int)mantissa)); + } + return j; + } + + /** + * @brief append token on to end of list + * @param newToken = token to append + * @returns with token appended onto this.lastToken + */ + private void AppendToken (Token newToken) + { + newToken.nextToken = null; + newToken.prevToken = lastToken; + newToken.nr2l = this.options.noRightToLeft; + lastToken.nextToken = newToken; + lastToken = newToken; + } + + /** + * @brief print tokenizing error message + * and remember that we've an error + * @param i = position within source file of the error + * @param message = error message text + * @returns with this.youveAnError set + */ + private void TokenError (int i, string message) + { + Token temp = new Token (this.emsg, this.filNam, this.lineNo, i - this.bolIdx); + temp.ErrorMsg (message); + youveAnError = true; + } + + /** + * @brief get a token's constructor + * @param tokenType = token's type + * @returns token's constructor + */ + private static Type[] constrTypes = new Type[] { + typeof (TokenErrorMessage), typeof (string), typeof (int), typeof (int) + }; + + private static System.Reflection.ConstructorInfo GetTokenCtor (Type tokenType) + { + return tokenType.GetConstructor (constrTypes); + } + + /** + * @brief delimeter table + */ + private class Delim { + public string str; + public System.Reflection.ConstructorInfo ctorInfo; + public Delim (string str, Type type) + { + this.str = str; + ctorInfo = GetTokenCtor (type); + } + } + + private static Delim[] delims = new Delim[] { + new Delim ("...", typeof (TokenKwDotDotDot)), + new Delim ("&&&", typeof (TokenKwAndAndAnd)), + new Delim ("|||", typeof (TokenKwOrOrOr)), + new Delim ("<<=", typeof (TokenKwAsnLSh)), + new Delim (">>=", typeof (TokenKwAsnRSh)), + new Delim ("<=", typeof (TokenKwCmpLE)), + new Delim (">=", typeof (TokenKwCmpGE)), + new Delim ("==", typeof (TokenKwCmpEQ)), + new Delim ("!=", typeof (TokenKwCmpNE)), + new Delim ("++", typeof (TokenKwIncr)), + new Delim ("--", typeof (TokenKwDecr)), + new Delim ("&&", typeof (TokenKwAndAnd)), + new Delim ("||", typeof (TokenKwOrOr)), + new Delim ("+=", typeof (TokenKwAsnAdd)), + new Delim ("&=", typeof (TokenKwAsnAnd)), + new Delim ("-=", typeof (TokenKwAsnSub)), + new Delim ("*=", typeof (TokenKwAsnMul)), + new Delim ("/=", typeof (TokenKwAsnDiv)), + new Delim ("%=", typeof (TokenKwAsnMod)), + new Delim ("|=", typeof (TokenKwAsnOr)), + new Delim ("^=", typeof (TokenKwAsnXor)), + new Delim ("<<", typeof (TokenKwLSh)), + new Delim (">>", typeof (TokenKwRSh)), + new Delim ("~", typeof (TokenKwTilde)), + new Delim ("!", typeof (TokenKwExclam)), + new Delim ("@", typeof (TokenKwAt)), + new Delim ("%", typeof (TokenKwMod)), + new Delim ("^", typeof (TokenKwXor)), + new Delim ("&", typeof (TokenKwAnd)), + new Delim ("*", typeof (TokenKwMul)), + new Delim ("(", typeof (TokenKwParOpen)), + new Delim (")", typeof (TokenKwParClose)), + new Delim ("-", typeof (TokenKwSub)), + new Delim ("+", typeof (TokenKwAdd)), + new Delim ("=", typeof (TokenKwAssign)), + new Delim ("{", typeof (TokenKwBrcOpen)), + new Delim ("}", typeof (TokenKwBrcClose)), + new Delim ("[", typeof (TokenKwBrkOpen)), + new Delim ("]", typeof (TokenKwBrkClose)), + new Delim (";", typeof (TokenKwSemi)), + new Delim (":", typeof (TokenKwColon)), + new Delim ("<", typeof (TokenKwCmpLT)), + new Delim (">", typeof (TokenKwCmpGT)), + new Delim (",", typeof (TokenKwComma)), + new Delim (".", typeof (TokenKwDot)), + new Delim ("?", typeof (TokenKwQMark)), + new Delim ("/", typeof (TokenKwDiv)), + new Delim ("|", typeof (TokenKwOr)), + new Delim ("#", typeof (TokenKwHash)) + }; + + /** + * @brief keyword tables + * The keyword tables translate a keyword string + * to the corresponding token constructor. + */ + private static Dictionary keywords = BuildKeywords (); + private static Dictionary arrayKeywords = BuildArrayKeywords (); + private static Dictionary advFlowCtlKeywords = BuildAdvFlowCtlKeywords (); + private static Dictionary tryCatchKeywords = BuildTryCatchKeywords (); + private static Dictionary objectsKeywords = BuildObjectsKeywords (); + private static Dictionary charsKeywords = BuildCharsKeywords (); + + private static Dictionary BuildKeywords () + { + Dictionary kws = new Dictionary (); + + kws.Add ("default", GetTokenCtor (typeof (TokenKwDefault))); + kws.Add ("do", GetTokenCtor (typeof (TokenKwDo))); + kws.Add ("else", GetTokenCtor (typeof (TokenKwElse))); + kws.Add ("float", GetTokenCtor (typeof (TokenTypeFloat))); + kws.Add ("for", GetTokenCtor (typeof (TokenKwFor))); + kws.Add ("if", GetTokenCtor (typeof (TokenKwIf))); + kws.Add ("integer", GetTokenCtor (typeof (TokenTypeInt))); + kws.Add ("list", GetTokenCtor (typeof (TokenTypeList))); + kws.Add ("jump", GetTokenCtor (typeof (TokenKwJump))); + kws.Add ("key", GetTokenCtor (typeof (TokenTypeKey))); + kws.Add ("return", GetTokenCtor (typeof (TokenKwRet))); + kws.Add ("rotation", GetTokenCtor (typeof (TokenTypeRot))); + kws.Add ("state", GetTokenCtor (typeof (TokenKwState))); + kws.Add ("string", GetTokenCtor (typeof (TokenTypeStr))); + kws.Add ("vector", GetTokenCtor (typeof (TokenTypeVec))); + kws.Add ("while", GetTokenCtor (typeof (TokenKwWhile))); + + return kws; + } + + private static Dictionary BuildArrayKeywords () + { + Dictionary kws = new Dictionary (); + + kws.Add ("array", GetTokenCtor (typeof (TokenTypeArray))); + kws.Add ("foreach", GetTokenCtor (typeof (TokenKwForEach))); + kws.Add ("in", GetTokenCtor (typeof (TokenKwIn))); + kws.Add ("is", GetTokenCtor (typeof (TokenKwIs))); + kws.Add ("object", GetTokenCtor (typeof (TokenTypeObject))); + kws.Add ("undef", GetTokenCtor (typeof (TokenKwUndef))); + + return kws; + } + + private static Dictionary BuildAdvFlowCtlKeywords () + { + Dictionary kws = new Dictionary (); + + kws.Add ("break", GetTokenCtor (typeof (TokenKwBreak))); + kws.Add ("case", GetTokenCtor (typeof (TokenKwCase))); + kws.Add ("constant", GetTokenCtor (typeof (TokenKwConst))); + kws.Add ("continue", GetTokenCtor (typeof (TokenKwCont))); + kws.Add ("switch", GetTokenCtor (typeof (TokenKwSwitch))); + + return kws; + } + + private static Dictionary BuildTryCatchKeywords () + { + Dictionary kws = new Dictionary (); + + kws.Add ("catch", GetTokenCtor (typeof (TokenKwCatch))); + kws.Add ("exception", GetTokenCtor (typeof (TokenTypeExc))); + kws.Add ("finally", GetTokenCtor (typeof (TokenKwFinally))); + kws.Add ("throw", GetTokenCtor (typeof (TokenKwThrow))); + kws.Add ("try", GetTokenCtor (typeof (TokenKwTry))); + + return kws; + } + + private static Dictionary BuildObjectsKeywords () + { + Dictionary kws = new Dictionary (); + + kws.Add ("abstract", GetTokenCtor (typeof (TokenKwAbstract))); + kws.Add ("base", GetTokenCtor (typeof (TokenKwBase))); + kws.Add ("class", GetTokenCtor (typeof (TokenKwClass))); + kws.Add ("constructor", GetTokenCtor (typeof (TokenKwConstructor))); + kws.Add ("delegate", GetTokenCtor (typeof (TokenKwDelegate))); + kws.Add ("destructor", GetTokenCtor (typeof (TokenKwDestructor))); + kws.Add ("final", GetTokenCtor (typeof (TokenKwFinal))); + kws.Add ("get", GetTokenCtor (typeof (TokenKwGet))); + kws.Add ("interface", GetTokenCtor (typeof (TokenKwInterface))); + kws.Add ("new", GetTokenCtor (typeof (TokenKwNew))); + kws.Add ("override", GetTokenCtor (typeof (TokenKwOverride))); + kws.Add ("partial", GetTokenCtor (typeof (TokenKwPartial))); + kws.Add ("private", GetTokenCtor (typeof (TokenKwPrivate))); + kws.Add ("protected", GetTokenCtor (typeof (TokenKwProtected))); + kws.Add ("public", GetTokenCtor (typeof (TokenKwPublic))); + kws.Add ("set", GetTokenCtor (typeof (TokenKwSet))); + kws.Add ("static", GetTokenCtor (typeof (TokenKwStatic))); + kws.Add ("this", GetTokenCtor (typeof (TokenKwThis))); + kws.Add ("typedef", GetTokenCtor (typeof (TokenKwTypedef))); + kws.Add ("virtual", GetTokenCtor (typeof (TokenKwVirtual))); + + return kws; + } + + private static Dictionary BuildCharsKeywords () + { + Dictionary kws = new Dictionary (); + + kws.Add ("char", GetTokenCtor (typeof (TokenTypeChar))); + + return kws; + } + + /** + * @brief Create a URL from a base URL and a relative string + * @param oldurl = base url string + * @param relurl = relative url + * @returns new url string + */ + private static string CreateURL (string oldurl, string relurl) + { + if (relurl.IndexOf ("://") >= 0) return relurl; + StringBuilder newurl = new StringBuilder (oldurl.Length + relurl.Length); + if (relurl[0] == '/') { + // file:///oldname + /newname => file:///newname + // http://webserver.com/oldname + /newname => http://webserver.com/newname + int i = oldurl.IndexOf ("://") + 3; + int j = oldurl.IndexOf ('/', i); + if (j < 0) j = oldurl.Length; + newurl.Append (oldurl.Substring (0, j)); + newurl.Append (relurl); + } else { + // file:///oldname + newname => file:///newname + // http://webserver.com/oldname + newname => http://webserver.com/newname + int i = oldurl.LastIndexOf ('/') + 1; + newurl.Append (oldurl.Substring (0, i)); + newurl.Append (relurl); + } + return newurl.ToString (); + } + + /** + * @brief Read source file from a webserver somewhere out there. + */ + private const int MAX_INCLUDE_SIZE = 100000; + private Dictionary scriptIncludes = new Dictionary (); + private string ReadSourceFromURL (string url) + { + Stream stream = null; + StreamReader reader = null; + + try { + + /* + * Get stream to read from webserver. + */ + stream = MMRWebRequest.MakeRequest ("GET", url, null, 0); + reader = new StreamReader (stream); + + /* + * Read file from stream. + */ + char[] buf = new char[4000]; + int len = 0; + int total = 0; + List fullBuffs = new List (); + string signature = null; + + while (true) { + + /* + * Read a big chunk of characters. + */ + len = reader.ReadBlock (buf, 0, buf.Length); + + /* + * Signature is first line of the first chunk read and must be contained therein. + * If an include file with the same signature has already been seen by this script, + * this include file is ignored. + */ + if (signature == null) { + signature = new String (buf, 0, len); + int siglen = signature.IndexOf ('\n'); + if (siglen <= 0) { + throw new Exception ("missing signature in first " + len + " characters: " + url); + } + signature = signature.Substring (0, siglen); + if (scriptIncludes.ContainsKey (signature)) return null; + scriptIncludes.Add (signature, ""); + } + + /* + * Signature is ok, stash full blocks away and keep reading. + * If short read, means we have hit the end of the stream. + */ + total += len; + if (total > MAX_INCLUDE_SIZE) { + throw new Exception ("script include exceeds maximum " + MAX_INCLUDE_SIZE + ": " + url); + } + if (len < buf.Length) break; + fullBuffs.Add (buf); + buf = new char[4000]; + } + + /* + * Return the whole thing as one string. + */ + StringBuilder sb = new StringBuilder (total + url.Length + 20); + sb.Append ("# 1 \""); + sb.Append (url); + sb.Append ("\"\n"); + foreach (char[] fullBuff in fullBuffs) { + sb.Append (fullBuff); + } + sb.Append (buf, 0, len); + return sb.ToString (); + } finally { + if (reader != null) reader.Close (); + else if (stream != null) stream.Close (); + } + } + } + + /** + * @brief All output token types in addition to TokenBegin. + * They are all sub-types of Token. + */ + + public class TokenChar : Token { + public char val; + public TokenChar (TokenErrorMessage emsg, string file, int line, int posn, char val) : base (emsg, file, line, posn) + { + this.val = val; + } + public TokenChar (Token original, char val) : base (original) + { + this.val = val; + } + public override string ToString () + { + switch (val) { + case '\'': return "'\\''"; + case '\\': return "'\\\\'"; + case '\n': return "'\\n'"; + case '\t': return "'\\t'"; + default: return "'" + val + "'"; + } + } + } + + public class TokenFloat : Token { + public double val; + public TokenFloat (TokenErrorMessage emsg, string file, int line, int posn, double val) : base (emsg, file, line, posn) + { + this.val = val; + } + public override string ToString () + { + return val.ToString (); + } + } + + public class TokenInt : Token { + public int val; + public TokenInt (TokenErrorMessage emsg, string file, int line, int posn, int val) : base (emsg, file, line, posn) + { + this.val = val; + } + public TokenInt (Token original, int val) : base (original) + { + this.val = val; + } + public override string ToString () + { + return val.ToString (); + } + } + + public class TokenName : Token { + public string val; + public TokenName (TokenErrorMessage emsg, string file, int line, int posn, string val) : base (emsg, file, line, posn) + { + this.val = val; + } + public TokenName (Token original, string val) : base (original) + { + this.val = val; + } + public override string ToString () + { + return this.val; + } + } + + public class TokenStr : Token { + public string val; + public TokenStr (TokenErrorMessage emsg, string file, int line, int posn, string val) : base (emsg, file, line, posn) + { + this.val = val; + } + public override string ToString () + { + if ((val.IndexOf ('"') < 0) && + (val.IndexOf ('\\') < 0) && + (val.IndexOf ('\n') < 0) && + (val.IndexOf ('\t') < 0)) return "\"" + val + "\""; + + int len = val.Length; + StringBuilder sb = new StringBuilder (len * 2 + 2); + sb.Append ('"'); + for (int i = 0; i < len; i ++) { + char c = val[i]; + switch (c) { + case '"': { + sb.Append ('\\'); + sb.Append ('"'); + break; + } + case '\\': { + sb.Append ('\\'); + sb.Append ('\\'); + break; + } + case '\n': { + sb.Append ('\\'); + sb.Append ('n'); + break; + } + case '\t': { + sb.Append ('\\'); + sb.Append ('t'); + break; + } + default: { + sb.Append (c); + break; + } + } + } + return sb.ToString (); + } + } + + /* + * This one marks the end-of-file. + */ + public class TokenEnd : Token { public TokenEnd (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } } + + /* + * Various keywords and delimeters. + */ + public delegate object TokenRValConstBinOpDelegate (object left, object right); + public delegate object TokenRValConstUnOpDelegate (object right); + + public class TokenKw : Token { + public TokenRValConstBinOpDelegate binOpConst; + public TokenRValConstUnOpDelegate unOpConst; + public bool sdtClassOp; + public TokenKw (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenKw (Token original) : base (original) { } + } + + public class TokenKwDotDotDot : TokenKw { public TokenKwDotDotDot (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwDotDotDot (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "..."; } } + public class TokenKwAndAndAnd : TokenKw { public TokenKwAndAndAnd (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwAndAndAnd (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "&&&"; } } + public class TokenKwOrOrOr : TokenKw { public TokenKwOrOrOr (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwOrOrOr (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "|||"; } } + public class TokenKwAsnLSh : TokenKw { public TokenKwAsnLSh (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnLSh (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "<<="; } } + public class TokenKwAsnRSh : TokenKw { public TokenKwAsnRSh (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnRSh (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return ">>="; } } + public class TokenKwCmpLE : TokenKw { public TokenKwCmpLE (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwCmpLE (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "<="; } } + public class TokenKwCmpGE : TokenKw { public TokenKwCmpGE (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwCmpGE (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return ">="; } } + public class TokenKwCmpEQ : TokenKw { public TokenKwCmpEQ (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwCmpEQ (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "=="; } } + public class TokenKwCmpNE : TokenKw { public TokenKwCmpNE (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwCmpNE (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "!="; } } + public class TokenKwIncr : TokenKw { public TokenKwIncr (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwIncr (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "++"; } } + public class TokenKwDecr : TokenKw { public TokenKwDecr (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwDecr (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "--"; } } + public class TokenKwAndAnd : TokenKw { public TokenKwAndAnd (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAndAnd (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "&&"; } } + public class TokenKwOrOr : TokenKw { public TokenKwOrOr (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwOrOr (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "||"; } } + public class TokenKwAsnAdd : TokenKw { public TokenKwAsnAdd (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnAdd (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "+="; } } + public class TokenKwAsnAnd : TokenKw { public TokenKwAsnAnd (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnAnd (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "&="; } } + public class TokenKwAsnSub : TokenKw { public TokenKwAsnSub (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnSub (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "-="; } } + public class TokenKwAsnMul : TokenKw { public TokenKwAsnMul (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnMul (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "*="; } } + public class TokenKwAsnDiv : TokenKw { public TokenKwAsnDiv (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnDiv (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "/="; } } + public class TokenKwAsnMod : TokenKw { public TokenKwAsnMod (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnMod (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "%="; } } + public class TokenKwAsnOr : TokenKw { public TokenKwAsnOr (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnOr (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "|="; } } + public class TokenKwAsnXor : TokenKw { public TokenKwAsnXor (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAsnXor (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "^="; } } + public class TokenKwLSh : TokenKw { public TokenKwLSh (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.LSh; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwLSh (Token original) : base (original) { binOpConst = TokenRValConstOps.LSh; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "<<"; } } + public class TokenKwRSh : TokenKw { public TokenKwRSh (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.RSh; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwRSh (Token original) : base (original) { binOpConst = TokenRValConstOps.RSh; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return ">>"; } } + public class TokenKwTilde : TokenKw { public TokenKwTilde (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Not; sdtClassOp = true; } public TokenKwTilde (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Not; sdtClassOp = true; } public override string ToString () { return "~"; } } + public class TokenKwExclam : TokenKw { public TokenKwExclam (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwExclam (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "!"; } } + public class TokenKwAt : TokenKw { public TokenKwAt (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwAt (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "@"; } } + public class TokenKwMod : TokenKw { public TokenKwMod (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Mod; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwMod (Token original) : base (original) { binOpConst = TokenRValConstOps.Mod; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "%"; } } + public class TokenKwXor : TokenKw { public TokenKwXor (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Xor; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwXor (Token original) : base (original) { binOpConst = TokenRValConstOps.Xor; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "^"; } } + public class TokenKwAnd : TokenKw { public TokenKwAnd (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.And; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAnd (Token original) : base (original) { binOpConst = TokenRValConstOps.And; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "&"; } } + public class TokenKwMul : TokenKw { public TokenKwMul (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Mul; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwMul (Token original) : base (original) { binOpConst = TokenRValConstOps.Mul; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "*"; } } + public class TokenKwParOpen : TokenKw { public TokenKwParOpen (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwParOpen (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "("; } } + public class TokenKwParClose : TokenKw { public TokenKwParClose (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwParClose (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return ")"; } } + public class TokenKwSub : TokenKw { public TokenKwSub (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Sub; unOpConst = TokenRValConstOps.Neg; sdtClassOp = true; } public TokenKwSub (Token original) : base (original) { binOpConst = TokenRValConstOps.Sub; unOpConst = TokenRValConstOps.Neg; sdtClassOp = true; } public override string ToString () { return "-"; } } + public class TokenKwAdd : TokenKw { public TokenKwAdd (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Add; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwAdd (Token original) : base (original) { binOpConst = TokenRValConstOps.Add; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "+"; } } + public class TokenKwAssign : TokenKw { public TokenKwAssign (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwAssign (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "="; } } + public class TokenKwBrcOpen : TokenKw { public TokenKwBrcOpen (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwBrcOpen (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "{"; } } + public class TokenKwBrcClose : TokenKw { public TokenKwBrcClose (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwBrcClose (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "}"; } } + public class TokenKwBrkOpen : TokenKw { public TokenKwBrkOpen (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwBrkOpen (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "["; } } + public class TokenKwBrkClose : TokenKw { public TokenKwBrkClose (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwBrkClose (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "]"; } } + public class TokenKwSemi : TokenKw { public TokenKwSemi (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwSemi (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return ";"; } } + public class TokenKwColon : TokenKw { public TokenKwColon (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwColon (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return ":"; } } + public class TokenKwCmpLT : TokenKw { public TokenKwCmpLT (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwCmpLT (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "<"; } } + public class TokenKwCmpGT : TokenKw { public TokenKwCmpGT (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwCmpGT (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return ">"; } } + public class TokenKwComma : TokenKw { public TokenKwComma (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwComma (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return ","; } } + public class TokenKwDot : TokenKw { public TokenKwDot (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwDot (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "."; } } + public class TokenKwQMark : TokenKw { public TokenKwQMark (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwQMark (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "?"; } } + public class TokenKwDiv : TokenKw { public TokenKwDiv (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Div; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwDiv (Token original) : base (original) { binOpConst = TokenRValConstOps.Div; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "/"; } } + public class TokenKwOr : TokenKw { public TokenKwOr (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Or; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public TokenKwOr (Token original) : base (original) { binOpConst = TokenRValConstOps.Or; unOpConst = TokenRValConstOps.Null; sdtClassOp = true; } public override string ToString () { return "|"; } } + public class TokenKwHash : TokenKw { public TokenKwHash (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwHash (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "#"; } } + + public class TokenKwAbstract : TokenKw { public TokenKwAbstract (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwAbstract (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "abstract"; } } + public class TokenKwBase : TokenKw { public TokenKwBase (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwBase (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "base"; } } + public class TokenKwBreak : TokenKw { public TokenKwBreak (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwBreak (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "break"; } } + public class TokenKwCase : TokenKw { public TokenKwCase (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwCase (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "case"; } } + public class TokenKwCatch : TokenKw { public TokenKwCatch (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwCatch (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "catch"; } } + public class TokenKwClass : TokenKw { public TokenKwClass (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwClass (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "class"; } } + public class TokenKwConst : TokenKw { public TokenKwConst (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwConst (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "constant"; } } + public class TokenKwConstructor : TokenKw { public TokenKwConstructor (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwConstructor (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "constructor"; } } + public class TokenKwCont : TokenKw { public TokenKwCont (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwCont (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "continue"; } } + public class TokenKwDelegate : TokenKw { public TokenKwDelegate (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwDelegate (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "delegate"; } } + public class TokenKwDefault : TokenKw { public TokenKwDefault (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwDefault (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "default"; } } + public class TokenKwDestructor : TokenKw { public TokenKwDestructor (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwDestructor (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "destructor"; } } + public class TokenKwDo : TokenKw { public TokenKwDo (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwDo (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "do"; } } + public class TokenKwElse : TokenKw { public TokenKwElse (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwElse (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "else"; } } + public class TokenKwFinal : TokenKw { public TokenKwFinal (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwFinal (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "final"; } } + public class TokenKwFinally : TokenKw { public TokenKwFinally (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwFinally (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "finally"; } } + public class TokenKwFor : TokenKw { public TokenKwFor (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwFor (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "for"; } } + public class TokenKwForEach : TokenKw { public TokenKwForEach (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwForEach (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "foreach"; } } + public class TokenKwGet : TokenKw { public TokenKwGet (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwGet (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "get"; } } + public class TokenKwIf : TokenKw { public TokenKwIf (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwIf (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "if"; } } + public class TokenKwIn : TokenKw { public TokenKwIn (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwIn (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "in"; } } + public class TokenKwInterface : TokenKw { public TokenKwInterface (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwInterface (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "interface"; } } + public class TokenKwIs : TokenKw { public TokenKwIs (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwIs (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "is"; } } + public class TokenKwJump : TokenKw { public TokenKwJump (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwJump (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "jump"; } } + public class TokenKwNew : TokenKw { public TokenKwNew (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwNew (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "new"; } } + public class TokenKwOverride : TokenKw { public TokenKwOverride (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwOverride (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "override"; } } + public class TokenKwPartial : TokenKw { public TokenKwPartial (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwPartial (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "partial"; } } + public class TokenKwPrivate : TokenKw { public TokenKwPrivate (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwPrivate (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "private"; } } + public class TokenKwProtected : TokenKw { public TokenKwProtected (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwProtected (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "protected"; } } + public class TokenKwPublic : TokenKw { public TokenKwPublic (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwPublic (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "public"; } } + public class TokenKwRet : TokenKw { public TokenKwRet (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwRet (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "return"; } } + public class TokenKwSet : TokenKw { public TokenKwSet (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwSet (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "set"; } } + public class TokenKwState : TokenKw { public TokenKwState (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwState (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "state"; } } + public class TokenKwStatic : TokenKw { public TokenKwStatic (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwStatic (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "static"; } } + public class TokenKwSwitch : TokenKw { public TokenKwSwitch (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwSwitch (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "switch"; } } + public class TokenKwThis : TokenKw { public TokenKwThis (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwThis (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "this"; } } + public class TokenKwThrow : TokenKw { public TokenKwThrow (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwThrow (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "throw"; } } + public class TokenKwTry : TokenKw { public TokenKwTry (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwTry (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "try"; } } + public class TokenKwTypedef : TokenKw { public TokenKwTypedef (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwTypedef (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "typedef"; } } + public class TokenKwUndef : TokenKw { public TokenKwUndef (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwUndef (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "undef"; } } + public class TokenKwVirtual : TokenKw { public TokenKwVirtual (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwVirtual (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "virtual"; } } + public class TokenKwWhile : TokenKw { public TokenKwWhile (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public TokenKwWhile (Token original) : base (original) { binOpConst = TokenRValConstOps.Null; unOpConst = TokenRValConstOps.Null; sdtClassOp = false; } public override string ToString () { return "while"; } } + + /** + * @brief These static functions attempt to perform arithmetic on two constant + * operands to generate the resultant constant. + * Likewise for unary operators. + * + * @param left = left-hand value + * @param right = right-hand value + * @returns null: not able to perform computation + * else: resultant value object + * + * Note: it is ok for these to throw any exception (such as overflow or div-by-zero), + * and it will be treated as the 'not able to perform computation' case. + */ + public class TokenRValConstOps { + public static object Null (object left, object right) { return null; } + public static object Div (object left, object right) { if ((left is int) && (right is int)) { return (int)left / (int)right; } if ((left is int) && (right is double)) { return (int)left / (double)right; } if ((left is double) && (right is int)) { return (double)left / (int)right; } if ((left is double) && (right is double)) { return (double)left / (double)right; } return null; } + public static object Mod (object left, object right) { if ((left is int) && (right is int)) { return (int)left % (int)right; } if ((left is int) && (right is double)) { return (int)left % (double)right; } if ((left is double) && (right is int)) { return (double)left % (int)right; } if ((left is double) && (right is double)) { return (double)left % (double)right; } return null; } + public static object Mul (object left, object right) { if ((left is int) && (right is int)) { return (int)left * (int)right; } if ((left is int) && (right is double)) { return (int)left * (double)right; } if ((left is double) && (right is int)) { return (double)left * (int)right; } if ((left is double) && (right is double)) { return (double)left * (double)right; } return null; } + public static object And (object left, object right) { if ((left is int) && (right is int)) { return (int)left & (int)right; } if ((left is int) && (right is double)) { return (int)left & (int)(double)right; } if ((left is double) && (right is int)) { return (int)(double)left & (int)right; } if ((left is double) && (right is double)) { return (int)(double)left & (int)(double)right; } return null; } + public static object LSh (object left, object right) { if ((left is int) && (right is int)) { return (int)left << (int)right; } if ((left is int) && (right is double)) { return (int)left << (int)(double)right; } if ((left is double) && (right is int)) { return (int)(double)left << (int)right; } if ((left is double) && (right is double)) { return (int)(double)left << (int)(double)right; } return null; } + public static object Or (object left, object right) { if ((left is int) && (right is int)) { return (int)left | (int)right; } if ((left is int) && (right is double)) { return (int)left | (int)(double)right; } if ((left is double) && (right is int)) { return (int)(double)left | (int)right; } if ((left is double) && (right is double)) { return (int)(double)left | (int)(double)right; } return null; } + public static object RSh (object left, object right) { if ((left is int) && (right is int)) { return (int)left >> (int)right; } if ((left is int) && (right is double)) { return (int)left >> (int)(double)right; } if ((left is double) && (right is int)) { return (int)(double)left >> (int)right; } if ((left is double) && (right is double)) { return (int)(double)left >> (int)(double)right; } return null; } + public static object Xor (object left, object right) { if ((left is int) && (right is int)) { return (int)left ^ (int)right; } if ((left is int) && (right is double)) { return (int)left ^ (int)(double)right; } if ((left is double) && (right is int)) { return (int)(double)left ^ (int)right; } if ((left is double) && (right is double)) { return (int)(double)left ^ (int)(double)right; } return null; } + public static object Add (object left, object right) { if ((left is char) && (right is int)) { return (char)((char)left + (int)right); } if ((left is double) && (right is double)) { return (double)left + (double)right; } if ((left is double) && (right is int)) { return (double)left + (int)right; } if ((left is double) && (right is string)) { return TypeCast.FloatToString((double)left) + (string)right; } if ((left is int) && (right is double)) { return (int)left + (double)right; } if ((left is int) && (right is int)) { return (int)left + (int)right; } if ((left is int) && (right is string)) { return TypeCast.IntegerToString((int)left) + (string)right; } if ((left is string) && (right is char)) { return (string)left + (char)right; } if ((left is string) && (right is double)) { return (string)left + TypeCast.FloatToString((double)right); } if ((left is string) && (right is int)) { return (string)left + TypeCast.IntegerToString ((int)right); } if ((left is string) && (right is string)) { return (string)left + (string)right; } return null; } + public static object Sub (object left, object right) { if ((left is char) && (right is int)) { return (char)((char)left - (int)right); } if ((left is int) && (right is int)) { return (int)left - (int)right; } if ((left is int) && (right is double)) { return (int)left - (double)right; } if ((left is double) && (right is int)) { return (double)left - (int)right; } if ((left is double) && (right is double)) { return (double)left - (double)right; } return null; } + public static object Null (object right) { return null; } + public static object Neg (object right) { if (right is int) { return - (int)right; } if (right is double) { return - (double)right; } return null; } + public static object Not (object right) { if (right is int) { return ~ (int)right; } return null; } + } + + /* + * Various datatypes. + */ + public abstract class TokenType : Token { + + public TokenType (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenType (Token original) : base (original) { } + + public static TokenType FromSysType (Token original, System.Type typ) + { + if (typ == typeof (LSL_List)) return new TokenTypeList (original); + if (typ == typeof (LSL_Rotation)) return new TokenTypeRot (original); + if (typ == typeof (void)) return new TokenTypeVoid (original); + if (typ == typeof (LSL_Vector)) return new TokenTypeVec (original); + if (typ == typeof (float)) return new TokenTypeFloat (original); + if (typ == typeof (int)) return new TokenTypeInt (original); + if (typ == typeof (string)) return new TokenTypeStr (original); + if (typ == typeof (double)) return new TokenTypeFloat (original); + if (typ == typeof (bool)) return new TokenTypeBool (original); + if (typ == typeof (object)) return new TokenTypeObject (original); + if (typ == typeof (XMR_Array)) return new TokenTypeArray (original); + if (typ == typeof (LSL_Integer)) return new TokenTypeLSLInt (original); + if (typ == typeof (LSL_Float)) return new TokenTypeLSLFloat (original); + if (typ == typeof (LSL_String)) return new TokenTypeLSLString (original); + if (typ == typeof (char)) return new TokenTypeChar (original); + if (typ == typeof (Exception)) return new TokenTypeExc (original); + + throw new Exception ("unknown script type " + typ.ToString ()); + } + + public static TokenType FromLSLType (Token original, string typ) + { + if (typ == "list") return new TokenTypeList (original); + if (typ == "rotation") return new TokenTypeRot (original); + if (typ == "vector") return new TokenTypeVec (original); + if (typ == "float") return new TokenTypeFloat (original); + if (typ == "integer") return new TokenTypeInt (original); + if (typ == "key") return new TokenTypeKey (original); + if (typ == "string") return new TokenTypeStr (original); + if (typ == "object") return new TokenTypeObject (original); + if (typ == "array") return new TokenTypeArray (original); + if (typ == "bool") return new TokenTypeBool (original); + if (typ == "void") return new TokenTypeVoid (original); + if (typ == "char") return new TokenTypeChar (original); + if (typ == "exception") return new TokenTypeExc (original); + + throw new Exception ("unknown type " + typ); + } + + /** + * @brief Estimate the number of bytes of memory taken by one of these + * objects. For objects with widely varying size, return the + * smallest it can be. + */ + public static int StaticSize (System.Type typ) + { + if (typ == typeof (LSL_List)) return 96; + if (typ == typeof (LSL_Rotation)) return 80; + if (typ == typeof (void)) return 0; + if (typ == typeof (LSL_Vector)) return 72; + if (typ == typeof (float)) return 8; + if (typ == typeof (int)) return 8; + if (typ == typeof (string)) return 40; + if (typ == typeof (double)) return 8; + if (typ == typeof (bool)) return 8; + if (typ == typeof (XMR_Array)) return 96; + if (typ == typeof (object)) return 32; + if (typ == typeof (char)) return 2; + + if (typ == typeof (LSL_Integer)) return 32; + if (typ == typeof (LSL_Float)) return 32; + if (typ == typeof (LSL_String)) return 40; + + throw new Exception ("unknown type " + typ.ToString ()); + } + + /** + * @brief Return the corresponding system type. + */ + public abstract Type ToSysType (); + + /** + * @brief Return the equivalent LSL wrapping type. + * + * null: normal + * else: LSL-style wrapping, ie, LSL_Integer, LSL_Float, LSL_String + * ToSysType()=System.Int32; lslWrapping=LSL_Integer + * ToSysType()=System.Float; lslWrapping=LSL_Float + * ToSysType()=System.String; lslWrapping=LSL_String + */ + public virtual Type ToLSLWrapType () + { + return null; + } + + /** + * @brief Assign slots in either the global variable arrays or the script-defined type instance arrays. + * These only need to be implemented for script-visible types, ie, those that a script writer + * can actually define a variable as. + */ + public virtual void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + throw new Exception ("not implemented for " + ToString () + " (" + GetType () + ")"); + } + + /** + * @brief Get heap tracking type. + */ + public virtual Type ToHeapTrackerType () + { + return null; + } + public virtual ConstructorInfo GetHeapTrackerCtor () + { + return null; + } + public virtual MethodInfo GetHeapTrackerPopMeth () + { + return null; + } + public virtual MethodInfo GetHeapTrackerPushMeth () + { + return null; + } + } + + public class TokenTypeArray : TokenType { + private static readonly FieldInfo iarArraysFieldInfo = typeof (XMRInstArrays).GetField ("iarArrays"); + + public TokenTypeArray (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeArray (Token original) : base (original) { } + public override Type ToSysType () { return typeof (XMR_Array); } + public override string ToString () { return "array"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarArraysFieldInfo; + declVar.vTableIndex = arSizes.iasArrays ++; + } + } + public class TokenTypeBool : TokenType { + public TokenTypeBool (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeBool (Token original) : base (original) { } + public override Type ToSysType () { return typeof (bool); } + public override string ToString () { return "bool"; } + } + public class TokenTypeChar : TokenType { + private static readonly FieldInfo iarCharsFieldInfo = typeof (XMRInstArrays).GetField ("iarChars"); + + public TokenTypeChar (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeChar (Token original) : base (original) { } + public override Type ToSysType () { return typeof (char); } + public override string ToString () { return "char"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarCharsFieldInfo; + declVar.vTableIndex = arSizes.iasChars ++; + } + } + public class TokenTypeExc : TokenType { + private static readonly FieldInfo iarObjectsFieldInfo = typeof (XMRInstArrays).GetField ("iarObjects"); + + public TokenTypeExc (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeExc (Token original) : base (original) { } + public override Type ToSysType () { return typeof (Exception); } + public override string ToString () { return "exception"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarObjectsFieldInfo; + declVar.vTableIndex = arSizes.iasObjects ++; + } + } + public class TokenTypeFloat : TokenType { + private static readonly FieldInfo iarFloatsFieldInfo = typeof (XMRInstArrays).GetField ("iarFloats"); + + public TokenTypeFloat (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeFloat (Token original) : base (original) { } + public override Type ToSysType () { return typeof (double); } + public override string ToString () { return "float"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarFloatsFieldInfo; + declVar.vTableIndex = arSizes.iasFloats ++; + } + } + public class TokenTypeInt : TokenType { + private static readonly FieldInfo iarIntegersFieldInfo = typeof (XMRInstArrays).GetField ("iarIntegers"); + + public TokenTypeInt (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeInt (Token original) : base (original) { } + public override Type ToSysType () { return typeof (int); } + public override string ToString () { return "integer"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarIntegersFieldInfo; + declVar.vTableIndex = arSizes.iasIntegers ++; + } + } + public class TokenTypeKey : TokenType { + private static readonly FieldInfo iarStringsFieldInfo = typeof (XMRInstArrays).GetField ("iarStrings"); + + public TokenTypeKey (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeKey (Token original) : base (original) { } + public override Type ToSysType () { return typeof (string); } + public override string ToString () { return "key"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarStringsFieldInfo; + declVar.vTableIndex = arSizes.iasStrings ++; + } + } + public class TokenTypeList : TokenType { + private static readonly FieldInfo iarListsFieldInfo = typeof (XMRInstArrays).GetField ("iarLists"); + private static readonly ConstructorInfo htListCtor = typeof (HeapTrackerList).GetConstructor (new Type [] { typeof (XMRInstAbstract) }); + private static readonly MethodInfo htListPopMeth = typeof (HeapTrackerList).GetMethod ("Pop", new Type[] { typeof (LSL_List) }); + private static readonly MethodInfo htListPushMeth = typeof (HeapTrackerList).GetMethod ("Push", new Type[0]); + + public TokenTypeList (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeList (Token original) : base (original) { } + public override Type ToSysType () { return typeof (LSL_List); } + public override string ToString () { return "list"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarListsFieldInfo; + declVar.vTableIndex = arSizes.iasLists ++; + } + public override Type ToHeapTrackerType () { return typeof (HeapTrackerList); } + public override ConstructorInfo GetHeapTrackerCtor () { return htListCtor; } + public override MethodInfo GetHeapTrackerPopMeth () { return htListPopMeth; } + public override MethodInfo GetHeapTrackerPushMeth () { return htListPushMeth; } + } + public class TokenTypeObject : TokenType { + private static readonly FieldInfo iarObjectsFieldInfo = typeof (XMRInstArrays).GetField ("iarObjects"); + private static readonly ConstructorInfo htObjectCtor = typeof (HeapTrackerObject).GetConstructor (new Type [] { typeof (XMRInstAbstract) }); + private static readonly MethodInfo htObjectPopMeth = typeof (HeapTrackerObject).GetMethod ("Pop", new Type[] { typeof (object) }); + private static readonly MethodInfo htObjectPushMeth = typeof (HeapTrackerObject).GetMethod ("Push", new Type[0]); + + public TokenTypeObject (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeObject (Token original) : base (original) { } + public override Type ToSysType () { return typeof (object); } + public override string ToString () { return "object"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarObjectsFieldInfo; + declVar.vTableIndex = arSizes.iasObjects ++; + } + public override Type ToHeapTrackerType () { return typeof (HeapTrackerObject); } + public override ConstructorInfo GetHeapTrackerCtor () { return htObjectCtor; } + public override MethodInfo GetHeapTrackerPopMeth () { return htObjectPopMeth; } + public override MethodInfo GetHeapTrackerPushMeth () { return htObjectPushMeth; } + } + public class TokenTypeRot : TokenType { + private static readonly FieldInfo iarRotationsFieldInfo = typeof (XMRInstArrays).GetField ("iarRotations"); + + public TokenTypeRot (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeRot (Token original) : base (original) { } + public override Type ToSysType () { return typeof (LSL_Rotation); } + public override string ToString () { return "rotation"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarRotationsFieldInfo; + declVar.vTableIndex = arSizes.iasRotations ++; + } + } + public class TokenTypeStr : TokenType { + private static readonly FieldInfo iarStringsFieldInfo = typeof (XMRInstArrays).GetField ("iarStrings"); + private static readonly ConstructorInfo htStringCtor = typeof (HeapTrackerString).GetConstructor (new Type [] { typeof (XMRInstAbstract) }); + private static readonly MethodInfo htStringPopMeth = typeof (HeapTrackerString).GetMethod ("Pop", new Type[] { typeof (string) }); + private static readonly MethodInfo htStringPushMeth = typeof (HeapTrackerString).GetMethod ("Push", new Type[0]); + + public TokenTypeStr (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeStr (Token original) : base (original) { } + public override Type ToSysType () { return typeof (string); } + public override string ToString () { return "string"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarStringsFieldInfo; + declVar.vTableIndex = arSizes.iasStrings ++; + } + public override Type ToHeapTrackerType () { return typeof (HeapTrackerString); } + public override ConstructorInfo GetHeapTrackerCtor () { return htStringCtor; } + public override MethodInfo GetHeapTrackerPopMeth () { return htStringPopMeth; } + public override MethodInfo GetHeapTrackerPushMeth () { return htStringPushMeth; } + } + public class TokenTypeUndef : TokenType { // for the 'undef' constant, ie, null object pointer + public TokenTypeUndef (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeUndef (Token original) : base (original) { } + public override Type ToSysType () { return typeof (object); } + public override string ToString () { return "undef"; } + } + public class TokenTypeVec : TokenType { + private static readonly FieldInfo iarVectorsFieldInfo = typeof (XMRInstArrays).GetField ("iarVectors"); + + public TokenTypeVec (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeVec (Token original) : base (original) { } + public override Type ToSysType () { return typeof (LSL_Vector); } + public override string ToString () { return "vector"; } + public override void AssignVarSlot (TokenDeclVar declVar, XMRInstArSizes arSizes) + { + declVar.vTableArray = iarVectorsFieldInfo; + declVar.vTableIndex = arSizes.iasVectors ++; + } + } + public class TokenTypeVoid : TokenType { // used only for function/method return types + public TokenTypeVoid (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeVoid (Token original) : base (original) { } + public override Type ToSysType () { return typeof (void); } + public override string ToString () { return "void"; } + } + + public class TokenTypeLSLFloat : TokenTypeFloat { + public TokenTypeLSLFloat (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeLSLFloat (Token original) : base (original) { } + public override Type ToLSLWrapType () { return typeof (LSL_Float); } + } + public class TokenTypeLSLInt : TokenTypeInt { + public TokenTypeLSLInt (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeLSLInt (Token original) : base (original) { } + public override Type ToLSLWrapType () { return typeof (LSL_Integer); } + } + public class TokenTypeLSLKey : TokenTypeKey { + public TokenTypeLSLKey (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeLSLKey (Token original) : base (original) { } + public override Type ToLSLWrapType () { return typeof (LSL_Key); } + } + public class TokenTypeLSLString : TokenTypeStr { + public TokenTypeLSLString (TokenErrorMessage emsg, string file, int line, int posn) : base (emsg, file, line, posn) { } + public TokenTypeLSLString (Token original) : base (original) { } + public override Type ToLSLWrapType () { return typeof (LSL_String); } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTypeCast.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTypeCast.cs new file mode 100644 index 0000000..c8be7bb --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptTypeCast.cs @@ -0,0 +1,819 @@ +/* + * 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 OpenSim.Region.ScriptEngine.XMREngine; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +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; + +/** + * @brief Generate script object code to perform type casting + */ + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + + public class TypeCast { + private delegate void CastDelegate (IScriptCodeGen scg, Token errorAt); + + private static ConstructorInfo floatConstructorStringInfo = typeof (LSL_Float).GetConstructor (new Type[] { typeof (string) }); + private static ConstructorInfo integerConstructorStringInfo = typeof (LSL_Integer).GetConstructor (new Type[] { typeof (string) }); + private static ConstructorInfo lslFloatConstructorInfo = typeof (LSL_Float).GetConstructor (new Type[] { typeof (double) }); + private static ConstructorInfo lslIntegerConstructorInfo = typeof (LSL_Integer).GetConstructor (new Type[] { typeof (int) }); + private static ConstructorInfo lslStringConstructorInfo = typeof (LSL_String).GetConstructor (new Type[] { typeof (string) }); + private static ConstructorInfo rotationConstrucorStringInfo = typeof (LSL_Rotation).GetConstructor (new Type[] { typeof (string) }); + private static ConstructorInfo vectorConstrucorStringInfo = typeof (LSL_Vector).GetConstructor (new Type[] { typeof (string) }); + private static FieldInfo lslFloatValueFieldInfo = typeof (LSL_Float).GetField ("value"); + private static FieldInfo lslIntegerValueFieldInfo = typeof (LSL_Integer).GetField ("value"); + private static FieldInfo lslStringValueFieldInfo = typeof (LSL_String).GetField ("m_string"); + private static FieldInfo sdtcITableFieldInfo = typeof (XMRSDTypeClObj).GetField ("sdtcITable"); + private static MethodInfo boolToListMethodInfo = typeof (TypeCast).GetMethod ("BoolToList", new Type[] { typeof (bool) }); + private static MethodInfo boolToStringMethodInfo = typeof (TypeCast).GetMethod ("BoolToString", new Type[] { typeof (bool) }); + private static MethodInfo charToStringMethodInfo = typeof (TypeCast).GetMethod ("CharToString", new Type[] { typeof (char) }); + private static MethodInfo excToStringMethodInfo = typeof (TypeCast).GetMethod ("ExceptionToString", new Type[] { typeof (Exception), typeof (XMRInstAbstract) }); + private static MethodInfo floatToStringMethodInfo = typeof (TypeCast).GetMethod ("FloatToString", new Type[] { typeof (double) }); + private static MethodInfo intToStringMethodInfo = typeof (TypeCast).GetMethod ("IntegerToString", new Type[] { typeof (int) }); + private static MethodInfo keyToBoolMethodInfo = typeof (TypeCast).GetMethod ("KeyToBool", new Type[] { typeof (string) }); + private static MethodInfo listToBoolMethodInfo = typeof (TypeCast).GetMethod ("ListToBool", new Type[] { typeof (LSL_List) }); + private static MethodInfo listToStringMethodInfo = typeof (TypeCast).GetMethod ("ListToString", new Type[] { typeof (LSL_List) }); + private static MethodInfo objectToFloatMethodInfo = typeof (TypeCast).GetMethod ("ObjectToFloat", new Type[] { typeof (object) }); + private static MethodInfo objectToIntegerMethodInfo = typeof (TypeCast).GetMethod ("ObjectToInteger", new Type[] { typeof (object) }); + private static MethodInfo objectToListMethodInfo = typeof (TypeCast).GetMethod ("ObjectToList", new Type[] { typeof (object) }); + private static MethodInfo objectToRotationMethodInfo = typeof (TypeCast).GetMethod ("ObjectToRotation", new Type[] { typeof (object) }); + private static MethodInfo objectToStringMethodInfo = typeof (TypeCast).GetMethod ("ObjectToString", new Type[] { typeof (object) }); + private static MethodInfo objectToVectorMethodInfo = typeof (TypeCast).GetMethod ("ObjectToVector", new Type[] { typeof (object) }); + private static MethodInfo rotationToBoolMethodInfo = typeof (TypeCast).GetMethod ("RotationToBool", new Type[] { typeof (LSL_Rotation) }); + private static MethodInfo rotationToStringMethodInfo = typeof (TypeCast).GetMethod ("RotationToString", new Type[] { typeof (LSL_Rotation) }); + private static MethodInfo stringToBoolMethodInfo = typeof (TypeCast).GetMethod ("StringToBool", new Type[] { typeof (string) }); + private static MethodInfo vectorToBoolMethodInfo = typeof (TypeCast).GetMethod ("VectorToBool", new Type[] { typeof (LSL_Vector) }); + private static MethodInfo vectorToStringMethodInfo = typeof (TypeCast).GetMethod ("VectorToString", new Type[] { typeof (LSL_Vector) }); + private static MethodInfo sdTypeClassCastClass2ClassMethodInfo = typeof (XMRSDTypeClObj).GetMethod ("CastClass2Class", new Type[] { typeof (object), typeof (int) }); + private static MethodInfo sdTypeClassCastIFace2ClassMethodInfo = typeof (XMRSDTypeClObj).GetMethod ("CastIFace2Class", new Type[] { typeof (Delegate[]), typeof (int) }); + private static MethodInfo sdTypeClassCastObj2IFaceMethodInfo = typeof (XMRSDTypeClObj).GetMethod ("CastObj2IFace", new Type[] { typeof (object), typeof (string) }); + private static MethodInfo charToListMethodInfo = typeof (TypeCast).GetMethod ("CharToList", new Type[] { typeof (char) }); + private static MethodInfo excToListMethodInfo = typeof (TypeCast).GetMethod ("ExcToList", new Type[] { typeof (Exception) }); + private static MethodInfo vectorToListMethodInfo = typeof (TypeCast).GetMethod ("VectorToList", new Type[] { typeof (LSL_Vector) }); + private static MethodInfo floatToListMethodInfo = typeof (TypeCast).GetMethod ("FloatToList", new Type[] { typeof (double) }); + private static MethodInfo integerToListMethodInfo = typeof (TypeCast).GetMethod ("IntegerToList", new Type[] { typeof (int) }); + private static MethodInfo rotationToListMethodInfo = typeof (TypeCast).GetMethod ("RotationToList", new Type[] { typeof (LSL_Rotation) }); + private static MethodInfo stringToListMethodInfo = typeof (TypeCast).GetMethod ("StringToList", new Type[] { typeof (string) }); + + /* + * List of all allowed type casts and how to perform the casting. + */ + private static Dictionary legalTypeCasts = CreateLegalTypeCasts (); + + /** + * @brief create a dictionary of legal type casts. + * Defines what EXPLICIT type casts are allowed in addition to the IMPLICIT ones. + * Key is of the form for IMPLICIT casting. + * Key is of the form * for EXPLICIT casting. + * Value is a delegate that generates code to perform the type cast. + */ + private static Dictionary CreateLegalTypeCasts () + { + Dictionary ltc = new Dictionary (); + + // IMPLICIT type casts (a space is in middle of the key) + // EXPLICIT type casts (an * is in middle of the key) + // In general, only mark explicit if it might throw an exception + ltc.Add ("array object", TypeCastArray2Object); + ltc.Add ("bool float", TypeCastBool2Float); + ltc.Add ("bool integer", TypeCastBool2Integer); + ltc.Add ("bool list", TypeCastBool2List); + ltc.Add ("bool object", TypeCastBool2Object); + ltc.Add ("bool string", TypeCastBool2String); + ltc.Add ("char integer", TypeCastChar2Integer); + ltc.Add ("char list", TypeCastChar2List); + ltc.Add ("char object", TypeCastChar2Object); + ltc.Add ("char string", TypeCastChar2String); + ltc.Add ("exception list", TypeCastExc2List); + ltc.Add ("exception object", TypeCastExc2Object); + ltc.Add ("exception string", TypeCastExc2String); + ltc.Add ("float bool", TypeCastFloat2Bool); + ltc.Add ("float integer", TypeCastFloat2Integer); + ltc.Add ("float list", TypeCastFloat2List); + ltc.Add ("float object", TypeCastFloat2Object); + ltc.Add ("float string", TypeCastFloat2String); + ltc.Add ("integer bool", TypeCastInteger2Bool); + ltc.Add ("integer char", TypeCastInteger2Char); + ltc.Add ("integer float", TypeCastInteger2Float); + ltc.Add ("integer list", TypeCastInteger2List); + ltc.Add ("integer object", TypeCastInteger2Object); + ltc.Add ("integer string", TypeCastInteger2String); + ltc.Add ("list bool", TypeCastList2Bool); + ltc.Add ("list object", TypeCastList2Object); + ltc.Add ("list string", TypeCastList2String); + ltc.Add ("object*array", TypeCastObject2Array); + ltc.Add ("object*bool", TypeCastObject2Bool); + ltc.Add ("object*char", TypeCastObject2Char); + ltc.Add ("object*exception", TypeCastObject2Exc); + ltc.Add ("object*float", TypeCastObject2Float); + ltc.Add ("object*integer", TypeCastObject2Integer); + ltc.Add ("object*list", TypeCastObject2List); + ltc.Add ("object*rotation", TypeCastObject2Rotation); + ltc.Add ("object string", TypeCastObject2String); + ltc.Add ("object*vector", TypeCastObject2Vector); + ltc.Add ("rotation bool", TypeCastRotation2Bool); + ltc.Add ("rotation list", TypeCastRotation2List); + ltc.Add ("rotation object", TypeCastRotation2Object); + ltc.Add ("rotation string", TypeCastRotation2String); + ltc.Add ("string bool", TypeCastString2Bool); + ltc.Add ("string float", TypeCastString2Float); + ltc.Add ("string integer", TypeCastString2Integer); + ltc.Add ("string list", TypeCastString2List); + ltc.Add ("string object", TypeCastString2Object); + ltc.Add ("string rotation", TypeCastString2Rotation); + ltc.Add ("string vector", TypeCastString2Vector); + ltc.Add ("vector bool", TypeCastVector2Bool); + ltc.Add ("vector list", TypeCastVector2List); + ltc.Add ("vector object", TypeCastVector2Object); + ltc.Add ("vector string", TypeCastVector2String); + + return ltc; + } + + /** + * @brief See if the given type can be cast to the other implicitly. + * @param dstType = type being cast to + * @param srcType = type being cast from + * @returns false: implicit cast not allowed + * true: implicit cast allowed + */ + public static bool IsAssignableFrom (TokenType dstType, TokenType srcType) + { + /* + * Do a 'dry run' of the casting operation, discarding any emits and not printing any errors. + * But if the casting tries to print error(s), return false. + * Otherwise assume the cast is allowed and return true. + */ + SCGIAF scg = new SCGIAF (); + scg.ok = true; + scg._ilGen = migiaf; + CastTopOfStack (scg, null, srcType, dstType, false); + return scg.ok; + } + + private struct SCGIAF : IScriptCodeGen { + public bool ok; + public ScriptMyILGen _ilGen; + + // IScriptCodeGen + public ScriptMyILGen ilGen { get { return _ilGen; } } + public void ErrorMsg (Token token, string message) { ok = false; } + public void PushDefaultValue (TokenType type) { } + public void PushXMRInst () { } + } + + private static readonly MIGIAF migiaf = new MIGIAF (); + private struct MIGIAF : ScriptMyILGen { + // ScriptMyILGen + public string methName { get { return null; } } + public ScriptMyLocal DeclareLocal (Type type, string name) { return null; } + public ScriptMyLabel DefineLabel (string name) { return null; } + public void BeginExceptionBlock () { } + public void BeginCatchBlock (Type excType) { } + public void BeginFinallyBlock () { } + public void EndExceptionBlock () { } + public void Emit (Token errorAt, OpCode opcode) { } + public void Emit (Token errorAt, OpCode opcode, FieldInfo field) { } + public void Emit (Token errorAt, OpCode opcode, ScriptMyLocal myLocal) { } + public void Emit (Token errorAt, OpCode opcode, Type type) { } + public void Emit (Token errorAt, OpCode opcode, ScriptMyLabel myLabel) { } + public void Emit (Token errorAt, OpCode opcode, ScriptMyLabel[] myLabels) { } + public void Emit (Token errorAt, OpCode opcode, ScriptObjWriter method) { } + public void Emit (Token errorAt, OpCode opcode, MethodInfo method) { } + public void Emit (Token errorAt, OpCode opcode, ConstructorInfo ctor) { } + public void Emit (Token errorAt, OpCode opcode, double value) { } + public void Emit (Token errorAt, OpCode opcode, float value) { } + public void Emit (Token errorAt, OpCode opcode, int value) { } + public void Emit (Token errorAt, OpCode opcode, string value) { } + public void MarkLabel (ScriptMyLabel myLabel) { } + } + + /** + * @brief Emit code that converts the top stack item from 'oldType' to 'newType' + * @param scg = what script we are compiling + * @param errorAt = token used for source location for error messages + * @param oldType = type of item currently on the stack + * @param newType = type to convert it to + * @param explicitAllowed = false: only consider implicit casts + * true: consider both implicit and explicit casts + * @returns with code emitted for conversion (or error message output if not allowed, and stack left unchanged) + */ + public static void CastTopOfStack (IScriptCodeGen scg, Token errorAt, TokenType oldType, TokenType newType, bool explicitAllowed) + { + CastDelegate castDelegate; + string oldString = oldType.ToString (); + string newString = newType.ToString (); + + /* + * 'key' -> 'bool' is the only time we care about key being different than string. + */ + if ((oldString == "key") && (newString == "bool")) { + LSLUnwrap (scg, errorAt, oldType); + scg.ilGen.Emit (errorAt, OpCodes.Call, keyToBoolMethodInfo); + LSLWrap (scg, errorAt, newType); + return; + } + + /* + * Treat key and string as same type for all other type casts. + */ + if (oldString == "key") oldString = "string"; + if (newString == "key") newString = "string"; + + /* + * If the types are the same, there is no conceptual casting needed. + * However, there may be wraping/unwraping to/from the LSL wrappers. + */ + if (oldString == newString) { + if (oldType.ToLSLWrapType () != newType.ToLSLWrapType ()) { + LSLUnwrap (scg, errorAt, oldType); + LSLWrap (scg, errorAt, newType); + } + return; + } + + /* + * Script-defined classes can be cast up and down the tree. + */ + if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeSDTypeClass)) { + TokenDeclSDTypeClass oldSDTC = ((TokenTypeSDTypeClass)oldType).decl; + TokenDeclSDTypeClass newSDTC = ((TokenTypeSDTypeClass)newType).decl; + + // implicit cast allowed from leaf toward root + for (TokenDeclSDTypeClass sdtc = oldSDTC; sdtc != null; sdtc = sdtc.extends) { + if (sdtc == newSDTC) return; + } + + // explicit cast allowed from root toward leaf + for (TokenDeclSDTypeClass sdtc = newSDTC; sdtc != null; sdtc = sdtc.extends) { + if (sdtc == oldSDTC) { + ExplCheck (scg, errorAt, explicitAllowed, oldString, newString); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, newSDTC.sdTypeIndex); + scg.ilGen.Emit (errorAt, OpCodes.Call, sdTypeClassCastClass2ClassMethodInfo); + return; + } + } + + // not on same branch + goto illcast; + } + + /* + * One script-defined interface type cannot be cast to another script-defined interface type, + * unless the old interface declares that it implements the new interface. That proves that + * the underlying object, no matter what type, implements the new interface. + */ + if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeSDTypeInterface)) { + TokenDeclSDTypeInterface oldDecl = ((TokenTypeSDTypeInterface)oldType).decl; + TokenDeclSDTypeInterface newDecl = ((TokenTypeSDTypeInterface)newType).decl; + if (!oldDecl.Implements (newDecl)) goto illcast; + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, newType.ToString ()); + scg.ilGen.Emit (errorAt, OpCodes.Call, sdTypeClassCastObj2IFaceMethodInfo); + return; + } + + /* + * A script-defined class type can be implicitly cast to a script-defined interface type that it + * implements. The result is an array of delegates that give the class's implementation of the + * various methods defined by the interface. + */ + if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeSDTypeInterface)) { + TokenDeclSDTypeClass oldSDTC = ((TokenTypeSDTypeClass)oldType).decl; + int intfIndex; + if (!oldSDTC.intfIndices.TryGetValue (newType.ToString (), out intfIndex)) goto illcast; + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, sdtcITableFieldInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, intfIndex); + scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (Delegate[])); + return; + } + + /* + * A script-defined interface type can be explicitly cast to a script-defined class type by + * extracting the Target property from element 0 of the delegate array that is the interface + * object and making sure it casts to the correct script-defined class type. + * + * But then only if the class type implements the interface type. + */ + if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeSDTypeClass)) { + TokenTypeSDTypeInterface oldSDTI = (TokenTypeSDTypeInterface)oldType; + TokenTypeSDTypeClass newSDTC = (TokenTypeSDTypeClass) newType; + + if (!newSDTC.decl.CanCastToIntf (oldSDTI.decl)) goto illcast; + + ExplCheck (scg, errorAt, explicitAllowed, oldString, newString); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, newSDTC.decl.sdTypeIndex); + scg.ilGen.Emit (errorAt, OpCodes.Call, sdTypeClassCastIFace2ClassMethodInfo); + return; + } + + /* + * A script-defined interface type can be implicitly cast to object. + */ + if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeObject)) { + return; + } + + /* + * An object can be explicitly cast to a script-defined interface. + */ + if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeInterface)) { + ExplCheck (scg, errorAt, explicitAllowed, oldString, newString); + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, newString); + scg.ilGen.Emit (errorAt, OpCodes.Call, sdTypeClassCastObj2IFaceMethodInfo); + return; + } + + /* + * Cast to void is always allowed, such as discarding value from 'i++' or function return value. + */ + if (newType is TokenTypeVoid) { + scg.ilGen.Emit (errorAt, OpCodes.Pop); + return; + } + + /* + * Cast from undef to object or script-defined type is always allowed. + */ + if ((oldType is TokenTypeUndef) && + ((newType is TokenTypeObject) || + (newType is TokenTypeSDTypeClass) || + (newType is TokenTypeSDTypeInterface))) { + return; + } + + /* + * Script-defined classes can be implicitly cast to objects. + */ + if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeObject)) { + return; + } + + /* + * Script-defined classes can be explicitly cast from objects and other script-defined classes. + * Note that we must manually check that it is the correct SDTypeClass however because as far as + * mono is concerned, all SDTypeClass's are the same. + */ + if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeClass)) { + ExplCheck (scg, errorAt, explicitAllowed, oldString, newString); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, ((TokenTypeSDTypeClass)newType).decl.sdTypeIndex); + scg.ilGen.Emit (errorAt, OpCodes.Call, sdTypeClassCastClass2ClassMethodInfo); + return; + } + + /* + * Delegates can be implicitly cast to/from objects. + */ + if ((oldType is TokenTypeSDTypeDelegate) && (newType is TokenTypeObject)) { + return; + } + if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeDelegate)) { + scg.ilGen.Emit (errorAt, OpCodes.Castclass, newType.ToSysType ()); + return; + } + + /* + * Some actual conversion is needed, see if it is in table of legal casts. + */ + string key = oldString + " " + newString; + if (!legalTypeCasts.TryGetValue (key, out castDelegate)) { + key = oldString + "*" + newString; + if (!legalTypeCasts.TryGetValue (key, out castDelegate)) goto illcast; + ExplCheck (scg, errorAt, explicitAllowed, oldString, newString); + } + + /* + * Ok, output cast. But make sure it is in native form without any LSL wrapping + * before passing to our casting routine. Then if caller is expecting an LSL- + * wrapped value on the stack upon return, wrap it up after our casting. + */ + LSLUnwrap (scg, errorAt, oldType); + castDelegate (scg, errorAt); + LSLWrap (scg, errorAt, newType); + return; + + illcast: + scg.ErrorMsg (errorAt, "illegal to cast from " + oldString + " to " + newString); + if (!(oldType is TokenTypeVoid)) scg.ilGen.Emit (errorAt, OpCodes.Pop); + scg.PushDefaultValue (newType); + } + private static void ExplCheck (IScriptCodeGen scg, Token errorAt, bool explicitAllowed, string oldString, string newString) + { + if (!explicitAllowed) { + scg.ErrorMsg (errorAt, "must explicitly cast from " + oldString + " to " + newString); + } + } + + /** + * @brief If value on the stack is an LSL-style wrapped value, unwrap it. + */ + public static void LSLUnwrap (IScriptCodeGen scg, Token errorAt, TokenType type) + { + if (type.ToLSLWrapType () == typeof (LSL_Float)) { + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, lslFloatValueFieldInfo); + } + if (type.ToLSLWrapType () == typeof (LSL_Integer)) { + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, lslIntegerValueFieldInfo); + } + if (type.ToLSLWrapType () == typeof (LSL_String)) { + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, lslStringValueFieldInfo); + } + } + + /** + * @brief If caller wants the unwrapped value on stack wrapped LSL-style, wrap it. + */ + private static void LSLWrap (IScriptCodeGen scg, Token errorAt, TokenType type) + { + if (type.ToLSLWrapType () == typeof (LSL_Float)) { + scg.ilGen.Emit (errorAt, OpCodes.Newobj, lslFloatConstructorInfo); + } + if (type.ToLSLWrapType () == typeof (LSL_Integer)) { + scg.ilGen.Emit (errorAt, OpCodes.Newobj, lslIntegerConstructorInfo); + } + if (type.ToLSLWrapType () == typeof (LSL_String)) { + scg.ilGen.Emit (errorAt, OpCodes.Newobj, lslStringConstructorInfo); + } + } + + /** + * @brief These routines output code to perform casting. + * They can assume there are no LSL wrapped values on input + * and they should not output an LSL wrapped value. + */ + private static void TypeCastArray2Object (IScriptCodeGen scg, Token errorAt) + { + } + private static void TypeCastBool2Float (IScriptCodeGen scg, Token errorAt) + { + if (typeof (double) == typeof (float)) { + scg.ilGen.Emit (errorAt, OpCodes.Conv_R4); + } else if (typeof (double) == typeof (double)) { + scg.ilGen.Emit (errorAt, OpCodes.Conv_R8); + } else { + throw new Exception ("unknown type"); + } + } + private static void TypeCastBool2Integer (IScriptCodeGen scg, Token errorAt) + { + } + private static void TypeCastBool2Object (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Box, typeof (bool)); + } + private static void TypeCastChar2Integer (IScriptCodeGen scg, Token errorAt) + { + } + private static void TypeCastChar2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, charToListMethodInfo); + } + private static void TypeCastChar2Object (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Box, typeof (char)); + } + private static void TypeCastChar2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, charToStringMethodInfo); + } + private static void TypeCastExc2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, excToListMethodInfo); + } + private static void TypeCastExc2Object (IScriptCodeGen scg, Token errorAt) + { + } + private static void TypeCastExc2String (IScriptCodeGen scg, Token errorAt) + { + scg.PushXMRInst (); + scg.ilGen.Emit (errorAt, OpCodes.Call, excToStringMethodInfo); + } + private static void TypeCastFloat2Bool (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldc_R4, 0.0f); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + } + private static void TypeCastFloat2Integer (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Conv_I4); + } + private static void TypeCastFloat2Object (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Box, typeof (double)); + } + private static void TypeCastInteger2Bool (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_0); + scg.ilGen.Emit (errorAt, OpCodes.Ceq); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4_1); + scg.ilGen.Emit (errorAt, OpCodes.Xor); + } + private static void TypeCastInteger2Char (IScriptCodeGen scg, Token errorAt) + { + } + private static void TypeCastInteger2Float (IScriptCodeGen scg, Token errorAt) + { + if (typeof (double) == typeof (float)) { + scg.ilGen.Emit (errorAt, OpCodes.Conv_R4); + } else if (typeof (double) == typeof (double)) { + scg.ilGen.Emit (errorAt, OpCodes.Conv_R8); + } else { + throw new Exception ("unknown type"); + } + } + private static void TypeCastInteger2Object (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Box, typeof (int)); + } + private static void TypeCastList2Bool (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, listToBoolMethodInfo); + } + private static void TypeCastList2Object (IScriptCodeGen scg, Token errorAt) + { + if (typeof (LSL_List).IsValueType) { + scg.ilGen.Emit (errorAt, OpCodes.Box, typeof (LSL_List)); + } + } + private static void TypeCastObject2Array (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Castclass, typeof (XMR_Array)); + } + private static void TypeCastObject2Bool (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Unbox_Any, typeof (bool)); + } + private static void TypeCastObject2Char (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Unbox_Any, typeof (char)); + } + private static void TypeCastObject2Exc (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Castclass, typeof (Exception)); + } + private static void TypeCastObject2Float (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, objectToFloatMethodInfo); + } + private static void TypeCastObject2Integer (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, objectToIntegerMethodInfo); + } + private static void TypeCastObject2List (IScriptCodeGen scg, Token errorAt) + { + if (typeof (LSL_List).IsValueType) { + scg.ilGen.Emit (errorAt, OpCodes.Call, objectToListMethodInfo); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Castclass, typeof (LSL_List)); + } + } + private static void TypeCastObject2Rotation (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, objectToRotationMethodInfo); + } + private static void TypeCastObject2Vector (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, objectToVectorMethodInfo); + } + private static void TypeCastRotation2Bool (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, rotationToBoolMethodInfo); + } + private static void TypeCastRotation2Object (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Box, typeof (LSL_Rotation)); + } + private static void TypeCastString2Bool (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, stringToBoolMethodInfo); + } + private static void TypeCastString2Object (IScriptCodeGen scg, Token errorAt) + { + } + private static void TypeCastString2Rotation (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Newobj, rotationConstrucorStringInfo); + } + private static void TypeCastString2Vector (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Newobj, vectorConstrucorStringInfo); + } + private static void TypeCastVector2Bool (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, vectorToBoolMethodInfo); + } + private static void TypeCastVector2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, vectorToListMethodInfo); + } + private static void TypeCastVector2Object (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Box, typeof (LSL_Vector)); + } + private static void TypeCastBool2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, boolToListMethodInfo); + } + private static void TypeCastBool2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, boolToStringMethodInfo); + } + private static void TypeCastFloat2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, floatToListMethodInfo); + } + private static void TypeCastFloat2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, floatToStringMethodInfo); + } + private static void TypeCastInteger2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, integerToListMethodInfo); + } + private static void TypeCastInteger2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, intToStringMethodInfo); + } + private static void TypeCastList2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, listToStringMethodInfo); + } + private static void TypeCastObject2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, objectToStringMethodInfo); + } + private static void TypeCastRotation2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, rotationToListMethodInfo); + } + private static void TypeCastRotation2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, rotationToStringMethodInfo); + } + private static void TypeCastString2Float (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Newobj, floatConstructorStringInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, lslFloatValueFieldInfo); + } + private static void TypeCastString2Integer (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Newobj, integerConstructorStringInfo); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, lslIntegerValueFieldInfo); + } + private static void TypeCastString2List (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, stringToListMethodInfo); + } + private static void TypeCastVector2String (IScriptCodeGen scg, Token errorAt) + { + scg.ilGen.Emit (errorAt, OpCodes.Call, vectorToStringMethodInfo); + } + + /* + * Because the calls are funky, let the compiler handle them. + */ + public static bool RotationToBool (LSL_Rotation x) { return !x.Equals (ScriptBaseClass.ZERO_ROTATION); } + public static bool StringToBool (string x) { return x.Length > 0; } + public static bool VectorToBool (LSL_Vector x) { return !x.Equals (ScriptBaseClass.ZERO_VECTOR); } + public static string BoolToString (bool x) { return x ? "1" : "0"; } + public static string CharToString (char x) { return x.ToString (); } + public static string FloatToString (double x) { return x.ToString ("0.000000"); } + public static string IntegerToString (int x) { return x.ToString (); } + public static bool KeyToBool (string x) { return (x != "") && (x != ScriptBaseClass.NULL_KEY); } + public static bool ListToBool (LSL_List x) { return x.Length != 0; } + public static string ListToString (LSL_List x) { return x.ToString (); } + public static string ObjectToString (object x) { return (x == null) ? null : x.ToString (); } + public static string RotationToString (LSL_Rotation x) { return x.ToString (); } + public static string VectorToString (LSL_Vector x) { return x.ToString (); } + public static LSL_List BoolToList (bool b) { return new LSL_List (new object[] { new LSL_Integer (b ? 1 : 0) }); } + public static LSL_List CharToList (char c) { return new LSL_List (new object[] { new LSL_Integer (c) }); } + public static LSL_List ExcToList (Exception e) { return new LSL_List (new object[] { e }); } + public static LSL_List VectorToList (LSL_Vector v) { return new LSL_List (new object[] { v }); } + public static LSL_List FloatToList (double f) { return new LSL_List (new object[] { new LSL_Float (f) }); } + public static LSL_List IntegerToList (int i) { return new LSL_List (new object[] { new LSL_Integer (i) }); } + public static LSL_List RotationToList (LSL_Rotation r) { return new LSL_List (new object[] { r }); } + public static LSL_List StringToList (string s) { return new LSL_List (new object[] { new LSL_String (s) }); } + + public static double ObjectToFloat (object x) + { + if (x is LSL_String) return double.Parse (((LSL_String)x).m_string); + if (x is string) return double.Parse ((string)x); + if (x is LSL_Float) return (double)(LSL_Float)x; + if (x is LSL_Integer) return (double)(int)(LSL_Integer)x; + if (x is int) return (double)(int)x; + return (double)x; + } + + public static int ObjectToInteger (object x) + { + if (x is LSL_String) return int.Parse (((LSL_String)x).m_string); + if (x is string) return int.Parse ((string)x); + if (x is LSL_Integer) return (int)(LSL_Integer)x; + return (int)x; + } + + public static LSL_List ObjectToList (object x) + { + return (LSL_List)x; + } + + public static LSL_Rotation ObjectToRotation (object x) + { + if (x is LSL_String) return new LSL_Rotation (((LSL_String)x).m_string); + if (x is string) return new LSL_Rotation ((string)x); + return (LSL_Rotation)x; + } + + public static LSL_Vector ObjectToVector (object x) + { + if (x is LSL_String) return new LSL_Vector (((LSL_String)x).m_string); + if (x is string) return new LSL_Vector ((string)x); + return (LSL_Vector)x; + } + + public static string ExceptionToString (Exception x, XMRInstAbstract inst) + { + return XMRInstAbstract.xmrExceptionTypeName (x) + ": " + XMRInstAbstract.xmrExceptionMessage (x) + + "\n" + inst.xmrExceptionStackTrace (x); + } + + /* + * These are used by event handler entrypoints to remove any LSL wrapping + * from the argument list and return the unboxed/unwrapped value. + */ + public static double EHArgUnwrapFloat (object x) + { + if (x is LSL_Float) return (double)(LSL_Float)x; + return (double)x; + } + + public static int EHArgUnwrapInteger (object x) + { + if (x is LSL_Integer) return (int)(LSL_Integer)x; + return (int)x; + } + + public static LSL_Rotation EHArgUnwrapRotation (object x) + { + if (x is OpenMetaverse.Quaternion) { + OpenMetaverse.Quaternion q = (OpenMetaverse.Quaternion)x; + return new LSL_Rotation(q.X, q.Y, q.Z, q.W); + } + return (LSL_Rotation)x; + } + + public static string EHArgUnwrapString (object x) + { + if (x is LSL_Key) return (string)(LSL_Key)x; + if (x is LSL_String) return (string)(LSL_String)x; + return (string)x; + } + + public static LSL_Vector EHArgUnwrapVector (object x) + { + if (x is OpenMetaverse.Vector3) { + OpenMetaverse.Vector3 v = (OpenMetaverse.Vector3)x; + return new LSL_Vector(v.X, v.Y, v.Z); + } + return (LSL_Vector)x; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptVarDict.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptVarDict.cs new file mode 100644 index 0000000..67e1c34 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRScriptVarDict.cs @@ -0,0 +1,371 @@ +/* + * 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 System; +using System.Collections; +using System.Collections.Generic; + +/** + * @brief Collection of variable/function/method definitions + */ + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public class VarDict : IEnumerable { + public VarDict outerVarDict; // next outer VarDict to search + public TokenDeclSDTypeClass thisClass; // this VarDict is for members of thisClass + + private struct ArgTypes { + public TokenType[] argTypes; + + public bool CanBeCalledBy (TokenType[] calledBy) + { + if ((argTypes == null) && (calledBy == null)) return true; + if ((argTypes == null) || (calledBy == null)) return false; + if (argTypes.Length != calledBy.Length) return false; + for (int i = argTypes.Length; -- i >= 0;) { + if (!TypeCast.IsAssignableFrom (argTypes[i], calledBy[i])) return false; + } + return true; + } + + public override bool Equals (Object that) + { + if (that == null) return false; + if (that.GetType () != typeof (ArgTypes)) return false; + TokenType[] at = this.argTypes; + TokenType[] bt = ((ArgTypes)that).argTypes; + if ((at == null) && (bt == null)) return true; + if ((at == null) || (bt == null)) return false; + if (at.Length != bt.Length) return false; + for (int i = at.Length; -- i >= 0;) { + if (at[i].ToString () != bt[i].ToString ()) return false; + } + return true; + } + + public override int GetHashCode () + { + TokenType[] at = this.argTypes; + if (at == null) return -1; + int hc = 0; + for (int i = at.Length; -- i >= 0;) { + int c = (hc < 0) ? 1 : 0; + hc = hc * 2 + c; + hc ^= at[i].ToString ().GetHashCode (); + } + return hc; + } + } + + private struct TDVEntry { + public int count; + public TokenDeclVar var; + } + + private bool isFrozen = false; + private bool locals; + private Dictionary> master = new Dictionary> (); + private int count = 0; + private VarDict frozenLocals = null; + + /** + * @brief Constructor. + * @param locals = false: cannot be frozen, allows forward references + * true: can be frozen, thus forbidding forward references + */ + public VarDict (bool locals) + { + this.locals = locals; + } + + /** + * @brief Add new variable to the dictionary. + */ + public bool AddEntry (TokenDeclVar var) + { + if (isFrozen) { + throw new Exception ("var dict is frozen"); + } + + /* + * Make sure we have a sub-dictionary based on the bare name (ie, no signature) + */ + Dictionary typedic; + if (!master.TryGetValue (var.name.val, out typedic)) { + typedic = new Dictionary (); + master.Add (var.name.val, typedic); + } + + /* + * See if there is an entry in the sub-dictionary that matches the argument signature. + * Note that fields have null argument lists. + * Methods always have a non-null argument list, even if only 0 entries long. + */ + ArgTypes types; + types.argTypes = (var.argDecl == null) ? null : KeyTypesToStringTypes (var.argDecl.types); + if (typedic.ContainsKey (types)) return false; + + /* + * It is unique, add to its name-specific sub-dictionary. + */ + TDVEntry entry; + entry.count = ++ count; + entry.var = var; + typedic.Add (types, entry); + return true; + } + + public int Count { get { return count; } } + + /** + * @brief If this is not a local variable frame, just return the frame as is. + * If this is a local variable frame, return a version that is frozen, + * ie, one that does not contain any future additions. + */ + public VarDict FreezeLocals () + { + /* + * If not local var frame, return original frame as is. + * This will allow forward references as the future additions + * will be seen by lookups done in this dictionary. + */ + if (!locals) return this; + + /* + * If local var frame, return a copy frozen at this point. + * This disallows forward referenes as those future additions + * will not be seen by lookups done in the frozen dictionary. + */ + if ((frozenLocals == null) || (frozenLocals.count != this.count)) { + + /* + * Make a copy of the current var dictionary frame. + * We copy a reference to the dictionary, and though it may + * contain additions made after this point, those additions + * will have a count .gt. frozen count and will be ignored. + */ + frozenLocals = new VarDict (true); + + frozenLocals.outerVarDict = this.outerVarDict; + frozenLocals.thisClass = this.thisClass; + frozenLocals.master = this.master; + frozenLocals.count = this.count; + frozenLocals.frozenLocals = frozenLocals; + + /* + * Mark it as being frozen. + * - assert fail if any attempt is made to add to it + * - ignore any additions to the dictionary with greater count + */ + frozenLocals.isFrozen = true; + } + return frozenLocals; + } + + /** + * @brief Find all functions/variables that are callable + * @param name = name of function/variable to look for + * @param argTypes = the argument types the function is being called with + * null to look for a variable + * @returns null: no matching function/variable found + * else: list of matching functions/variables + * for variables, always of length 1 + */ + private List found = new List (); + public TokenDeclVar[] FindCallables (string name, TokenType[] argTypes) + { + argTypes = KeyTypesToStringTypes (argTypes); + TokenDeclVar var = FindExact (name, argTypes); + if (var != null) return new TokenDeclVar[] { var }; + + Dictionary typedic; + if (!master.TryGetValue (name, out typedic)) return null; + + found.Clear (); + foreach (KeyValuePair kvp in typedic) { + if ((kvp.Value.count <= this.count) && kvp.Key.CanBeCalledBy (argTypes)) { + found.Add (kvp.Value.var); + } + } + return (found.Count > 0) ? found.ToArray () : null; + } + + /** + * @brief Find exact matching function/variable + * @param name = name of function to look for + * @param argTypes = argument types the function was declared with + * null to look for a variable + * @returns null: no matching function/variable found + * else: the matching function/variable + */ + public TokenDeclVar FindExact (string name, TokenType[] argTypes) + { + /* + * Look for list of stuff that matches the given name. + */ + Dictionary typedic; + if (!master.TryGetValue (name, out typedic)) return null; + + /* + * Loop through all fields/methods declared by that name, regardless of arg signature. + */ + foreach (TDVEntry entry in typedic.Values) { + if (entry.count > this.count) continue; + TokenDeclVar var = entry.var; + + /* + * Get argument types of declaration. + * fields are always null + * methods are always non-null, though may be zero-length + */ + TokenType[] declArgs = (var.argDecl == null) ? null : var.argDecl.types; + + /* + * Convert any key args to string args. + */ + declArgs = KeyTypesToStringTypes (declArgs); + + /* + * If both are null, they are signature-less (ie, both are fields), and so match. + */ + if ((declArgs == null) && (argTypes == null)) return var; + + /* + * If calling a delegate, it is a match, regardless of delegate arg types. + * If it turns out the arg types do not match, the compiler will give an error + * trying to cast the arguments to the delegate arg types. + * We don't allow overloading same field name with different delegate types. + */ + if ((declArgs == null) && (argTypes != null)) { + TokenType fieldType = var.type; + if (fieldType is TokenTypeSDTypeDelegate) return var; + } + + /* + * If not both null, no match, keep looking. + */ + if ((declArgs == null) || (argTypes == null)) continue; + + /* + * Both not null, match argument types to make sure we have correct overload. + */ + int i = declArgs.Length; + if (i != argTypes.Length) continue; + while (-- i >= 0) { + string da = declArgs[i].ToString (); + string ga = argTypes[i].ToString (); + if (da == "key") da = "string"; + if (ga == "key") ga = "string"; + if (da != ga) break; + } + if (i < 0) return var; + } + + /* + * No match. + */ + return null; + } + + /** + * @brief Replace any TokenTypeKey elements with TokenTypeStr so that + * it doesn't matter if functions are declared with key or string, + * they will accept either. + * @param argTypes = argument types as declared in source code + * @returns argTypes with any key replaced by string + */ + private static TokenType[] KeyTypesToStringTypes (TokenType[] argTypes) + { + if (argTypes != null) { + int i; + int nats = argTypes.Length; + for (i = nats; -- i >= 0;) { + if (argTypes[i] is TokenTypeKey) break; + } + if (i >= 0) { + TokenType[] at = new TokenType[nats]; + for (i = nats; -- i >= 0;) { + at[i] = argTypes[i]; + if (argTypes[i] is TokenTypeKey) { + at[i] = new TokenTypeStr (argTypes[i]); + } + } + return at; + } + } + return argTypes; + } + + // foreach goes through all the TokenDeclVars that were added + + // IEnumerable + public IEnumerator GetEnumerator () + { + return new VarDictEnumerator (this.master, this.count); + } + + private class VarDictEnumerator : IEnumerator { + private IEnumerator masterEnum; + private IEnumerator typedicEnum; + private int count; + + public VarDictEnumerator (Dictionary> master, int count) + { + masterEnum = master.Values.GetEnumerator (); + this.count = count; + } + + // IEnumerator + public void Reset () + { + masterEnum.Reset (); + typedicEnum = null; + } + + // IEnumerator + public bool MoveNext () + { + while (true) { + if (typedicEnum != null) { + while (typedicEnum.MoveNext ()) { + if (((TDVEntry)typedicEnum.Current).count <= this.count) return true; + } + typedicEnum = null; + } + if (!masterEnum.MoveNext ()) return false; + Dictionary ctd; + ctd = (Dictionary)masterEnum.Current; + typedicEnum = ctd.Values.GetEnumerator (); + } + } + + // IEnumerator + public object Current { get { return ((TDVEntry)typedicEnum.Current).var; } } + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MMRWebRequest.cs b/OpenSim/Region/ScriptEngine/XMREngine/MMRWebRequest.cs new file mode 100644 index 0000000..3579332 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MMRWebRequest.cs @@ -0,0 +1,269 @@ +/* + * 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. + */ + +/** + * @brief Perform web request + */ + +using System; +using System.IO; +using System.Net; +using System.Text; + +namespace OpenSim.Region.ScriptEngine.XMREngine { + public class MMRWebRequest { + public static bool allowFileURL = false; + + public static Stream MakeRequest (string verb, string requestUrl, string obj, int timeoutms) + { + /* + * Pick apart the given URL and make sure we support it. + * For file:// URLs, just return a read-only stream of the file. + */ + Uri uri = new Uri (requestUrl); + string supported = "http and https"; + if (allowFileURL && (verb == "GET")) { + supported = "file, http and https"; + if (uri.Scheme == "file") { + return File.OpenRead (requestUrl.Substring (7)); + } + } + bool https = uri.Scheme == "https"; + if (!https && (uri.Scheme != "http")) { + throw new WebException ("only support " + supported + ", not " + uri.Scheme); + } + string host = uri.Host; + int port = uri.Port; + if (port < 0) port = https ? 443 : 80; + string path = uri.AbsolutePath; + + /* + * Connect to the web server. + */ + System.Net.Sockets.TcpClient tcpconnection = new System.Net.Sockets.TcpClient (host, port); + if (timeoutms > 0) { + tcpconnection.SendTimeout = timeoutms; + tcpconnection.ReceiveTimeout = timeoutms; + } + + try { + + /* + * Get TCP stream to/from web server. + * If HTTPS, wrap stream with SSL encryption. + */ + Stream tcpstream = tcpconnection.GetStream (); + if (https) { + System.Net.Security.SslStream sslstream = new System.Net.Security.SslStream (tcpstream, false); + sslstream.AuthenticateAsClient (host); + tcpstream = sslstream; + } + + /* + * Write request header to the web server. + * There might be some POST data as well to write to web server. + */ + WriteStream (tcpstream, verb + " " + path + " HTTP/1.1\r\n"); + WriteStream (tcpstream, "Host: " + host + "\r\n"); + if (obj != null) { + byte[] bytes = Encoding.UTF8.GetBytes (obj); + + WriteStream (tcpstream, "Content-Length: " + bytes.Length + "\r\n"); + WriteStream (tcpstream, "Content-Type: application/x-www-form-urlencoded\r\n"); + WriteStream (tcpstream, "\r\n"); + tcpstream.Write (bytes, 0, bytes.Length); + } else { + WriteStream (tcpstream, "\r\n"); + } + tcpstream.Flush (); + + /* + * Check for successful reply status line. + */ + string headerline = ReadStreamLine (tcpstream).Trim (); + if (headerline != "HTTP/1.1 200 OK") throw new WebException ("status line " + headerline); + + /* + * Scan through header lines. + * The only ones we care about are Content-Length and Transfer-Encoding. + */ + bool chunked = false; + int contentlength = -1; + while ((headerline = ReadStreamLine (tcpstream).Trim ().ToLowerInvariant ()) != "") { + if (headerline.StartsWith ("content-length:")) { + contentlength = int.Parse (headerline.Substring (15)); + } + if (headerline.StartsWith ("transfer-encoding:") && (headerline.Substring (18).Trim () == "chunked")) { + chunked = true; + } + } + + /* + * Read response byte array as a series of chunks. + */ + if (chunked) { + return new ChunkedStreamReader (tcpstream); + } + + /* + * Read response byte array with the exact length given by Content-Length. + */ + if (contentlength >= 0) { + return new LengthStreamReader (tcpstream, contentlength); + } + + /* + * Don't know how it is being transferred. + */ + throw new WebException ("header missing content-length or transfer-encoding: chunked"); + } catch { + tcpconnection.Close (); + throw; + } + } + + /** + * @brief Write the string out as ASCII bytes. + */ + private static void WriteStream (Stream stream, string line) + { + byte[] bytes = Encoding.ASCII.GetBytes (line); + stream.Write (bytes, 0, bytes.Length); + } + + /** + * @brief Read the next text line from a stream. + * @returns string with \r\n trimmed off + */ + private static string ReadStreamLine (Stream stream) + { + StringBuilder sb = new StringBuilder (); + while (true) { + int b = stream.ReadByte (); + if (b < 0) break; + if (b == '\n') break; + if (b == '\r') continue; + sb.Append ((char)b); + } + return sb.ToString (); + } + + private class ChunkedStreamReader : Stream { + private int chunklen; + private Stream tcpstream; + + public ChunkedStreamReader (Stream tcpstream) + { + this.tcpstream = tcpstream; + } + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override bool CanTimeout { get { return false; } } + public override bool CanWrite { get { return false; } } + public override long Length { get { return 0; } } + public override long Position { get { return 0; } set { } } + public override void Flush () { } + public override long Seek (long offset, SeekOrigin origin) { return 0; } + public override void SetLength (long length) { } + public override void Write (byte[] buffer, int offset, int length) { } + + public override int Read (byte[] buffer, int offset, int length) + { + if (length <= 0) return 0; + + if (chunklen == 0) { + chunklen = int.Parse (ReadStreamLine (tcpstream), System.Globalization.NumberStyles.HexNumber); + if (chunklen < 0) throw new WebException ("negative chunk length"); + if (chunklen == 0) chunklen = -1; + } + if (chunklen < 0) return 0; + + int maxread = (length < chunklen) ? length : chunklen; + int lenread = tcpstream.Read (buffer, offset, maxread); + chunklen -= lenread; + if (chunklen == 0) { + int b = tcpstream.ReadByte (); + if (b == '\r') b = tcpstream.ReadByte (); + if (b != '\n') throw new WebException ("chunk not followed by \\r\\n"); + } + return lenread; + } + + public override void Close () + { + chunklen = -1; + if (tcpstream != null) { + tcpstream.Close (); + tcpstream = null; + } + } + } + + private class LengthStreamReader : Stream { + private int contentlength; + private Stream tcpstream; + + public LengthStreamReader (Stream tcpstream, int contentlength) + { + this.tcpstream = tcpstream; + this.contentlength = contentlength; + } + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override bool CanTimeout { get { return false; } } + public override bool CanWrite { get { return false; } } + public override long Length { get { return 0; } } + public override long Position { get { return 0; } set { } } + public override void Flush () { } + public override long Seek (long offset, SeekOrigin origin) { return 0; } + public override void SetLength (long length) { } + public override void Write (byte[] buffer, int offset, int length) { } + + public override int Read (byte[] buffer, int offset, int length) + { + if (length <= 0) return 0; + if (contentlength <= 0) return 0; + + int maxread = (length < contentlength) ? length : contentlength; + int lenread = tcpstream.Read (buffer, offset, maxread); + contentlength -= lenread; + return lenread; + } + + public override void Close () + { + contentlength = -1; + if (tcpstream != null) { + tcpstream.Close (); + tcpstream = null; + } + } + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/MonoTaskletsDummy.cs b/OpenSim/Region/ScriptEngine/XMREngine/MonoTaskletsDummy.cs new file mode 100644 index 0000000..98910ae --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/MonoTaskletsDummy.cs @@ -0,0 +1,55 @@ +/* + * 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. + */ + +// Used to build a dummy Mono.Tasklets.dll file when running on Windows +// Will also work if running with mono, it will just not allow use of +// the "con" and "mmr" thread models, only "sys" will work. + +using System; + +namespace Mono.Tasklets { + public class Continuation : IDisposable + { + public Continuation () + { + throw new NotSupportedException ("'con' thread model requires mono"); + } + public void Dispose () + { } + + public void Mark () + { } + + public int Store (int state) + { + return 0; + } + + public void Restore (int state) + { } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs new file mode 100644 index 0000000..36d95d3 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs @@ -0,0 +1,534 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.IO; +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; + +// This class exists in the main app domain +// +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + /** + * @brief Array objects. + */ + public class XMR_Array { + private const int EMPTYHEAP = 64; + private const int ENTRYHEAP = 24; + + private bool enumrValid; // true: enumr set to return array[arrayValid] + // false: array[0..arrayValid-1] is all there is + private SortedDictionary dnary; + private SortedDictionary.Enumerator enumr; + // enumerator used to fill 'array' past arrayValid to end of dictionary + private int arrayValid; // number of elements in 'array' that have been filled in + private KeyValuePair[] array; // list of kvp's that have been returned by ForEach() since last modification + private XMRInstAbstract inst; // script instance debited with heap use + private int heapUse; // current heap use debit amount + + public static TokenTypeSDTypeDelegate countDelegate = new TokenTypeSDTypeDelegate (new TokenTypeInt (null), new TokenType[0]); + public static TokenTypeSDTypeDelegate clearDelegate = new TokenTypeSDTypeDelegate (new TokenTypeVoid (null), new TokenType[0]); + public static TokenTypeSDTypeDelegate indexDelegate = new TokenTypeSDTypeDelegate (new TokenTypeObject (null), new TokenType[] { new TokenTypeInt (null) }); + public static TokenTypeSDTypeDelegate valueDelegate = new TokenTypeSDTypeDelegate (new TokenTypeObject (null), new TokenType[] { new TokenTypeInt (null) }); + + public XMR_Array (XMRInstAbstract inst) + { + this.inst = inst; + dnary = new SortedDictionary (XMRArrayKeyComparer.singleton); + heapUse = inst.UpdateHeapUse (0, EMPTYHEAP); + } + + ~XMR_Array () + { + heapUse = inst.UpdateHeapUse (heapUse, 0); + } + + public static TokenType GetRValType (TokenName name) + { + if (name.val == "count") return new TokenTypeInt (name); + if (name.val == "clear") return clearDelegate; + if (name.val == "index") return indexDelegate; + if (name.val == "value") return valueDelegate; + return new TokenTypeVoid (name); + } + + /** + * @brief Handle 'array[index]' syntax to get or set an element of the dictionary. + * Get returns null if element not defined, script sees type 'undef'. + * Setting an element to null removes it. + */ + public object GetByKey(object key) + { + object val; + key = FixKey (key); + if (!dnary.TryGetValue (key, out val)) val = null; + return val; + } + + public void SetByKey(object key, object value) + { + key = FixKey (key); + + /* + * Update heap use throwing an exception on failure + * before making any changes to the array. + */ + int keysize = HeapTrackerObject.Size (key); + int newheapuse = heapUse; + object oldval; + if (dnary.TryGetValue (key, out oldval)) { + newheapuse -= keysize + HeapTrackerObject.Size (oldval); + } + if (value != null) { + newheapuse += keysize + HeapTrackerObject.Size (value); + } + heapUse = inst.UpdateHeapUse (heapUse, newheapuse); + + /* + * Save new value in array, replacing one of same key if there. + * null means remove the value, ie, script did array[key] = undef. + */ + if (value != null) { + dnary[key] = value; + } else { + dnary.Remove (key); + + /* + * Shrink the enumeration array, but always leave at least one element. + */ + if ((array != null) && (dnary.Count < array.Length / 2)) { + Array.Resize> (ref array, array.Length / 2); + } + } + + /* + * The enumeration array is invalid because the dictionary has been modified. + * Next time a ForEach() call happens, it will repopulate 'array' as elements are retrieved. + */ + arrayValid = 0; + } + + /** + * @brief Converts an 'object' type to array, key, list, string, but disallows null, + * as our language doesn't allow types other than 'object' to be null. + * Value types (float, rotation, etc) don't need explicit check for null as + * the C# runtime can't convert a null to a value type, and throws an exception. + * But for any reference type (array, key, etc) we must manually check for null. + */ + public static XMR_Array Obj2Array (object obj) + { + if (obj == null) throw new NullReferenceException (); + return (XMR_Array)obj; + } + public static LSL_Key Obj2Key (object obj) + { + if (obj == null) throw new NullReferenceException (); + return (LSL_Key)obj; + } + public static LSL_List Obj2List (object obj) + { + if (obj == null) throw new NullReferenceException (); + return (LSL_List)obj; + } + public static LSL_String Obj2String (object obj) + { + if (obj == null) throw new NullReferenceException (); + return obj.ToString (); + } + + /** + * @brief remove all elements from the array. + * sets everything to its 'just constructed' state. + */ + public void __pub_clear () + { + heapUse = inst.UpdateHeapUse (heapUse, EMPTYHEAP); + dnary.Clear (); + enumrValid = false; + arrayValid = 0; + array = null; + } + + /** + * @brief return number of elements in the array. + */ + public int __pub_count () + { + return dnary.Count; + } + + /** + * @brief Retrieve index (key) of an arbitrary element. + * @param number = number of the element (0 based) + * @returns null: array doesn't have that many elements + * else: index (key) for that element + */ + public object __pub_index (int number) + { + return ForEach (number) ? UnfixKey (array[number].Key) : null; + } + + /** + * @brief Retrieve value of an arbitrary element. + * @param number = number of the element (0 based) + * @returns null: array doesn't have that many elements + * else: value for that element + */ + public object __pub_value (int number) + { + return ForEach (number) ? array[number].Value : null; + } + + /** + * @brief Called in each iteration of a 'foreach' statement. + * @param number = index of element to retrieve (0 = first one) + * @returns false: element does not exist + * true: element exists + */ + private bool ForEach (int number) + { + /* + * If we don't have any array, we can't have ever done + * any calls here before, so allocate an array big enough + * and set everything else to the beginning. + */ + if (array == null) { + array = new KeyValuePair[dnary.Count]; + arrayValid = 0; + } + + /* + * If dictionary modified since last enumeration, get a new enumerator. + */ + if (arrayValid == 0) { + enumr = dnary.GetEnumerator (); + enumrValid = true; + } + + /* + * Make sure we have filled the array up enough for requested element. + */ + while ((arrayValid <= number) && enumrValid && enumr.MoveNext ()) { + if (arrayValid >= array.Length) { + Array.Resize> (ref array, dnary.Count); + } + array[arrayValid++] = enumr.Current; + } + + /* + * If we don't have that many elements, return end-of-array status. + */ + return number < arrayValid; + } + + /** + * @brief Transmit array out in such a way that it can be reconstructed, + * including any in-progress ForEach() enumerations. + */ + public delegate void SendArrayObjDelegate (object graph); + public void SendArrayObj (SendArrayObjDelegate sendObj) + { + /* + * Set the count then the elements themselves. + * UnfixKey() because sendObj doesn't handle XMRArrayListKeys. + */ + sendObj (dnary.Count); + foreach (KeyValuePair kvp in dnary) { + sendObj (UnfixKey (kvp.Key)); + sendObj (kvp.Value); + } + } + + /** + * @brief Receive array in. Any previous contents are erased. + * Set up such that any enumeration in progress will resume + * at the exact spot and in the exact same order as they + * were in on the sending side. + */ + public delegate object RecvArrayObjDelegate (); + public void RecvArrayObj (RecvArrayObjDelegate recvObj) + { + heapUse = inst.UpdateHeapUse (heapUse, EMPTYHEAP); + + /* + * Cause any enumeration to refill the array from the sorted dictionary. + * Since it is a sorted dictionary, any enumerations will be in the same + * order as on the sending side. + */ + arrayValid = 0; + enumrValid = false; + + /* + * Fill dictionary. + */ + dnary.Clear (); + int count = (int)recvObj (); + while (-- count >= 0) { + object key = FixKey (recvObj ()); + object val = recvObj (); + int htuse = HeapTrackerObject.Size (key) + HeapTrackerObject.Size (val); + heapUse = inst.UpdateHeapUse (heapUse, heapUse + htuse); + dnary.Add (key, val); + } + } + + /** + * We want our index values to be of consistent type, otherwise we get things like (LSL_Integer)1 != (int)1. + * So strip off any LSL-ness from the types. + * We also deep-strip any given lists used as keys (multi-dimensional arrays). + */ + public static object FixKey (object key) + { + if (key is LSL_Integer) return (int)(LSL_Integer)key; + if (key is LSL_Float) return (double)(LSL_Float)key; + if (key is LSL_Key) return (string)(LSL_Key)key; + if (key is LSL_String) return (string)(LSL_String)key; + if (key is LSL_List) { + object[] data = ((LSL_List)key).Data; + if (data.Length == 1) return FixKey (data[0]); + return new XMRArrayListKey ((LSL_List)key); + } + return key; // int, double, string, LSL_Vector, LSL_Rotation, etc are ok as is + } + + /** + * @brief When returning a key, such as for array.index(), we want to return the original + * LSL_List, not the sanitized one, as the script compiler expects an LSL_List. + * Any other sanitized types can remain as is (int, string, etc). + */ + private static object UnfixKey (object key) + { + if (key is XMRArrayListKey) key = ((XMRArrayListKey)key).GetOriginal (); + return key; + } + } + + public class XMRArrayKeyComparer : IComparer { + + public static XMRArrayKeyComparer singleton = new XMRArrayKeyComparer (); + + /** + * @brief Compare two keys + */ + public int Compare (object x, object y) // IComparer + { + /* + * Use short type name (eg, String, Int32, XMRArrayListKey) as most significant part of key. + */ + string xtn = x.GetType ().Name; + string ytn = y.GetType ().Name; + int ctn = String.CompareOrdinal (xtn, ytn); + if (ctn != 0) return ctn; + + ComparerDelegate cd; + if (!comparers.TryGetValue (xtn, out cd)) { + throw new Exception ("unsupported key type " + xtn); + } + return cd (x, y); + } + + private delegate int ComparerDelegate (object a, object b); + + private static Dictionary comparers = BuildComparers (); + + private static Dictionary BuildComparers () + { + Dictionary cmps = new Dictionary (); + cmps.Add (typeof (double).Name, MyFloatComparer); + cmps.Add (typeof (int).Name, MyIntComparer); + cmps.Add (typeof (XMRArrayListKey).Name, MyListKeyComparer); + cmps.Add (typeof (LSL_Rotation).Name, MyRotationComparer); + cmps.Add (typeof (string).Name, MyStringComparer); + cmps.Add (typeof (LSL_Vector).Name, MyVectorComparer); + return cmps; + } + + private static int MyFloatComparer (object a, object b) + { + double af = (double)a; + double bf = (double)b; + if (af < bf) return -1; + if (af > bf) return 1; + return 0; + } + private static int MyIntComparer (object a, object b) + { + return (int)a - (int)b; + } + private static int MyListKeyComparer (object a, object b) + { + XMRArrayListKey alk = (XMRArrayListKey)a; + XMRArrayListKey blk = (XMRArrayListKey)b; + return XMRArrayListKey.Compare (alk, blk); + } + private static int MyRotationComparer (object a, object b) + { + LSL_Rotation ar = (LSL_Rotation)a; + LSL_Rotation br = (LSL_Rotation)b; + if (ar.x < br.x) return -1; + if (ar.x > br.x) return 1; + if (ar.y < br.y) return -1; + if (ar.y > br.y) return 1; + if (ar.z < br.z) return -1; + if (ar.z > br.z) return 1; + if (ar.s < br.s) return -1; + if (ar.s > br.s) return 1; + return 0; + } + private static int MyStringComparer (object a, object b) + { + return String.CompareOrdinal ((string)a, (string)b); + } + private static int MyVectorComparer (object a, object b) + { + LSL_Vector av = (LSL_Vector)a; + LSL_Vector bv = (LSL_Vector)b; + if (av.x < bv.x) return -1; + if (av.x > bv.x) return 1; + if (av.y < bv.y) return -1; + if (av.y > bv.y) return 1; + if (av.z < bv.z) return -1; + if (av.z > bv.z) return 1; + return 0; + } + } + + /** + * @brief Lists used as keys must be sanitized first. + * List gets converted to an object[] and each element is converted from LSL_ types to system types where possible. + * And we also need an equality operator that compares the values of all elements of the list, not just the lengths. + * Note that just like LSL_Lists, we consider these objects to be immutable, so they can be directly used as keys in + * the dictionary as they don't ever change. + */ + public class XMRArrayListKey { + private LSL_List original; + private object[] cleaned; + private int length; + private int hashCode; + + /** + * @brief Construct a sanitized object[] from a list. + * Also save the original list in case we need it later. + */ + public XMRArrayListKey (LSL_List key) + { + original = key; + object[] given = key.Data; + int len = given.Length; + length = len; + cleaned = new object[len]; + int hc = len; + for (int i = 0; i < len; i ++) { + object v = XMR_Array.FixKey (given[i]); + hc += hc + ((hc < 0) ? 1 : 0); + hc ^= v.GetHashCode (); + cleaned[i] = v; + } + hashCode = hc; + } + + /** + * @brief Get heap tracking size. + */ + public int Size { + get { + return original.Size; + } + } + + /** + * @brief See if the given object is an XMRArrayListKey and every value is equal to our own. + */ + public override bool Equals (object o) + { + if (!(o is XMRArrayListKey)) return false; + XMRArrayListKey a = (XMRArrayListKey)o; + int len = a.length; + if (len != length) return false; + if (a.hashCode != hashCode) return false; + for (int i = 0; i < len; i ++) { + if (!cleaned[i].Equals (a.cleaned[i])) return false; + } + return true; + } + + /** + * @brief Get an hash code. + */ + public override int GetHashCode () + { + return hashCode; + } + + /** + * @brief Compare for key sorting. + */ + public static int Compare (XMRArrayListKey x, XMRArrayListKey y) + { + int j = x.length - y.length; + if (j == 0) { + for (int i = 0; i < x.length; i ++) { + object xo = x.cleaned[i]; + object yo = y.cleaned[i]; + j = XMRArrayKeyComparer.singleton.Compare (xo, yo); + if (j != 0) break; + } + } + return j; + } + + /** + * @brief Get the original LSL_List we were built from. + */ + public LSL_List GetOriginal () + { + return original; + } + + /** + * @brief Debugging + */ + public override string ToString () + { + StringBuilder sb = new StringBuilder (); + for (int i = 0; i < length; i ++) { + if (i > 0) sb.Append (','); + sb.Append (cleaned[i].ToString ()); + } + return sb.ToString (); + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMREngXmrTestLs.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMREngXmrTestLs.cs new file mode 100644 index 0000000..266c5aa --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMREngXmrTestLs.cs @@ -0,0 +1,491 @@ +/* + * 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 log4net; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public partial class XMREngine { + + private void XmrTestLs (string[] args, int indx) + { + bool flagFull = false; + bool flagQueues = false; + bool flagTopCPU = false; + int maxScripts = 0x7FFFFFFF; + int numScripts = 0; + string outName = null; + XMRInstance[] instances; + + /* + * Decode command line options. + */ + for (int i = indx; i < args.Length; i ++) { + if (args[i] == "-full") { + flagFull = true; + continue; + } + if (args[i] == "-help") { + m_log.Info ("[XMREngine]: xmr ls -full -max= -out= -queues -topcpu"); + return; + } + if (args[i].StartsWith("-max=")) { + try { + maxScripts = Convert.ToInt32(args[i].Substring(5)); + } catch (Exception e) { + m_log.Error("[XMREngine]: bad max " + args[i].Substring(5) + ": " + e.Message); + return; + } + continue; + } + if (args[i].StartsWith("-out=")) { + outName = args[i].Substring(5); + continue; + } + if (args[i] == "-queues") { + flagQueues = true; + continue; + } + if (args[i] == "-topcpu") { + flagTopCPU = true; + continue; + } + if (args[i][0] == '-') { + m_log.Error("[XMREngine]: unknown option " + args[i] + ", try 'xmr ls -help'"); + return; + } + } + + TextWriter outFile = null; + if (outName != null) { + try { + outFile = File.CreateText(outName); + } catch (Exception e) { + m_log.Error("[XMREngine]: error creating " + outName + ": " + e.Message); + return; + } + } else { + outFile = new LogInfoTextWriter(m_log); + } + + try { + for (int i = 0; i < numThreadScriptWorkers; i ++) { + XMRScriptThread th = m_ScriptThreads[i]; + outFile.WriteLine("Script thread ID: " + th.m_ScriptThreadTID); + long execTime = th.m_ScriptExecTime; + if (execTime < 0) { + execTime += (long)(DateTime.UtcNow - DateTime.MinValue).TotalMilliseconds; + } + outFile.WriteLine(" execution time: " + execTime + " mS"); + outFile.WriteLine(" last ran at: " + th.m_LastRanAt.ToString()); + XMRInstance rins = th.m_RunInstance; + if (rins != null) { + outFile.WriteLine(" running: " + rins.ItemID.ToString() + " " + rins.m_DescName); + if (flagFull) { + outFile.WriteLine (rins.RunTestLs (true)); + } + } + } + + /* + * Scan instance list to find those that match selection criteria. + */ + if (!Monitor.TryEnter(m_InstancesDict, 100)) { + m_log.Error("[XMREngine]: deadlock m_LockedDict=" + m_LockedDict); + return; + } + try + { + instances = new XMRInstance[m_InstancesDict.Count]; + foreach (XMRInstance ins in m_InstancesDict.Values) + { + if (InstanceMatchesArgs(ins, args, indx)) { + instances[numScripts++] = ins; + } + } + } finally { + Monitor.Exit(m_InstancesDict); + } + + /* + * Maybe sort by descending CPU time. + */ + if (flagTopCPU) { + Array.Sort(instances, CompareInstancesByCPUTime); + } + + /* + * Print the entries. + */ + if (!flagFull) { + outFile.WriteLine(" ItemID" + + " CPU(ms)" + + " NumEvents" + + " Status " + + " World Position " + + " :"); + } + for (int i = 0; (i < numScripts) && (i < maxScripts); i ++) { + outFile.WriteLine(instances[i].RunTestLs(flagFull)); + } + + /* + * Print number of scripts that match selection criteria, + * even if we were told to print fewer. + */ + outFile.WriteLine("total of {0} script(s)", numScripts); + + /* + * If -queues given, print out queue contents too. + */ + if (flagQueues) { + LsQueue(outFile, "start", m_StartQueue, args, indx); + LsQueue(outFile, "sleep", m_SleepQueue, args, indx); + LsQueue(outFile, "yield", m_YieldQueue, args, indx); + } + } finally { + outFile.Close(); + } + } + + private void XmrTestPev (string[] args, int indx) + { + bool flagAll = false; + int numScripts = 0; + XMRInstance[] instances; + + /* + * Decode command line options. + */ + int i, j; + List selargs = new List (args.Length); + MethodInfo[] eventmethods = typeof (IEventHandlers).GetMethods (); + MethodInfo eventmethod; + for (i = indx; i < args.Length; i ++) { + string arg = args[i]; + if (arg == "-all") { + flagAll = true; + continue; + } + if (arg == "-help") { + m_log.Info ("[XMREngine]: xmr pev -all | "); + return; + } + if (arg[0] == '-') { + m_log.Error ("[XMREngine]: unknown option " + arg + ", try 'xmr pev -help'"); + return; + } + for (j = 0; j < eventmethods.Length; j ++) { + eventmethod = eventmethods[j]; + if (eventmethod.Name == arg) goto gotevent; + } + selargs.Add (arg); + } + m_log.Error ("[XMREngine]: missing , try 'xmr pev -help'"); + return; + gotevent: + string eventname = eventmethod.Name; + StringBuilder sourcesb = new StringBuilder (); + while (++ i < args.Length) { + sourcesb.Append (' '); + sourcesb.Append (args[i]); + } + string sourcest = sourcesb.ToString (); + string sourcehash; + youveanerror = false; + Token t = TokenBegin.Construct ("", null, ErrorMsg, sourcest, out sourcehash); + if (youveanerror) return; + ParameterInfo[] paraminfos = eventmethod.GetParameters (); + object[] paramvalues = new object[paraminfos.Length]; + i = 0; + while (!((t = t.nextToken) is TokenEnd)) { + if (i >= paramvalues.Length) { + ErrorMsg (t, "extra parameter(s)"); + return; + } + paramvalues[i] = ParseParamValue (ref t); + if (paramvalues[i] == null) return; + i ++; + } + OpenSim.Region.ScriptEngine.Shared.EventParams eps = + new OpenSim.Region.ScriptEngine.Shared.EventParams (eventname, paramvalues, zeroDetectParams); + + /* + * Scan instance list to find those that match selection criteria. + */ + if (!Monitor.TryEnter(m_InstancesDict, 100)) { + m_log.Error("[XMREngine]: deadlock m_LockedDict=" + m_LockedDict); + return; + } + + try { + instances = new XMRInstance[m_InstancesDict.Count]; + foreach (XMRInstance ins in m_InstancesDict.Values) { + if (flagAll || InstanceMatchesArgs (ins, selargs.ToArray (), 0)) { + instances[numScripts++] = ins; + } + } + } finally { + Monitor.Exit(m_InstancesDict); + } + + /* + * Post event to the matching instances. + */ + for (i = 0; i < numScripts; i ++) { + XMRInstance inst = instances[i]; + m_log.Info ("[XMREngine]: post " + eventname + " to " + inst.m_DescName); + inst.PostEvent (eps); + } + } + + private object ParseParamValue (ref Token token) + { + if (token is TokenFloat) { + return new LSL_Float (((TokenFloat)token).val); + } + if (token is TokenInt) { + return new LSL_Integer (((TokenInt)token).val); + } + if (token is TokenStr) { + return new LSL_String (((TokenStr)token).val); + } + if (token is TokenKwCmpLT) { + List valuelist = new List (); + while (!((token = token.nextToken) is TokenKwCmpGT)) { + if (!(token is TokenKwComma)) { + object value = ParseParamValue (ref token); + if (value == null) return null; + if (value is int) value = (double)(int)value; + if (!(value is double)) { + ErrorMsg (token, "must be float or integer constant"); + return null; + } + valuelist.Add ((double)value); + } else if (token.prevToken is TokenKwComma) { + ErrorMsg (token, "missing constant"); + return null; + } + } + double[] values = valuelist.ToArray (); + switch (values.Length) { + case 3: { + return new LSL_Vector (values[0], values[1], values[2]); + } + case 4: { + return new LSL_Rotation (values[0], values[1], values[2], values[3]); + } + default: { + ErrorMsg (token, "not rotation or vector"); + return null; + } + } + } + if (token is TokenKwBrkOpen) { + List valuelist = new List (); + while (!((token = token.nextToken) is TokenKwBrkClose)) { + if (!(token is TokenKwComma)) { + object value = ParseParamValue (ref token); + if (value == null) return null; + valuelist.Add (value); + } else if (token.prevToken is TokenKwComma) { + ErrorMsg (token, "missing constant"); + return null; + } + } + return new LSL_List (valuelist.ToArray ()); + } + if (token is TokenName) { + FieldInfo field = typeof (OpenSim.Region.ScriptEngine.Shared.ScriptBase.ScriptBaseClass).GetField (((TokenName)token).val); + if ((field != null) && field.IsPublic && (field.IsLiteral || (field.IsStatic && field.IsInitOnly))) { + return field.GetValue (null); + } + } + ErrorMsg (token, "invalid constant"); + return null; + } + + private bool youveanerror; + private void ErrorMsg (Token token, string message) + { + youveanerror = true; + m_log.Info ("[XMREngine]: " + token.posn + " " + message); + } + + private void XmrTestReset (string[] args, int indx) + { + bool flagAll = false; + int numScripts = 0; + XMRInstance[] instances; + + if (args.Length <= indx) { + m_log.Error("[XMREngine]: must specify part of script name or -all for all scripts"); + return; + } + + /* + * Decode command line options. + */ + for (int i = indx; i < args.Length; i ++) { + if (args[i] == "-all") { + flagAll = true; + continue; + } + if (args[i] == "-help") { + m_log.Info ("[XMREngine]: xmr reset -all | "); + return; + } + if (args[i][0] == '-') { + m_log.Error ("[XMREngine]: unknown option " + args[i] + ", try 'xmr reset -help'"); + return; + } + } + + /* + * Scan instance list to find those that match selection criteria. + */ + if (!Monitor.TryEnter(m_InstancesDict, 100)) { + m_log.Error("[XMREngine]: deadlock m_LockedDict=" + m_LockedDict); + return; + } + + try { + instances = new XMRInstance[m_InstancesDict.Count]; + foreach (XMRInstance ins in m_InstancesDict.Values) { + if (flagAll || InstanceMatchesArgs (ins, args, indx)) { + instances[numScripts++] = ins; + } + } + } finally { + Monitor.Exit(m_InstancesDict); + } + + /* + * Reset the instances as if someone clicked their "Reset" button. + */ + for (int i = 0; i < numScripts; i ++) { + XMRInstance inst = instances[i]; + m_log.Info ("[XMREngine]: resetting " + inst.m_DescName); + inst.Reset(); + } + } + + private static int CompareInstancesByCPUTime(XMRInstance a, XMRInstance b) + { + if (a == null) { + return (b == null) ? 0 : 1; + } + if (b == null) { + return -1; + } + if (b.m_CPUTime < a.m_CPUTime) return -1; + if (b.m_CPUTime > a.m_CPUTime) return 1; + return 0; + } + + private void LsQueue(TextWriter outFile, string name, XMRInstQueue queue, string[] args, int indx) + { + outFile.WriteLine("Queue " + name + ":"); + lock (queue) { + for (XMRInstance inst = queue.PeekHead(); inst != null; inst = inst.m_NextInst) { + try { + + /* + * Try to print instance name. + */ + if (InstanceMatchesArgs(inst, args, indx)) { + outFile.WriteLine(" " + inst.ItemID.ToString() + " " + inst.m_DescName); + } + } catch (Exception e) { + + /* + * Sometimes there are instances in the queue that are disposed. + */ + outFile.WriteLine(" " + inst.ItemID.ToString() + " " + inst.m_DescName + ": " + e.Message); + } + } + } + } + + private bool InstanceMatchesArgs(XMRInstance ins, string[] args, int indx) + { + bool hadSomethingToCompare = false; + + for (int i = indx; i < args.Length; i ++) + { + if (args[i][0] != '-') { + hadSomethingToCompare = true; + if (ins.m_DescName.Contains(args[i])) return true; + if (ins.ItemID.ToString().Contains(args[i])) return true; + if (ins.AssetID.ToString().Contains(args[i])) return true; + } + } + return !hadSomethingToCompare; + } + } + + /** + * @brief Make m_log.Info look like a text writer. + */ + public class LogInfoTextWriter : TextWriter { + private StringBuilder sb = new StringBuilder(); + private ILog m_log; + public LogInfoTextWriter (ILog m_log) + { + this.m_log = m_log; + } + public override void Write (char c) + { + if (c == '\n') { + m_log.Info("[XMREngine]: " + sb.ToString()); + sb.Remove(0, sb.Length); + } else { + sb.Append(c); + } + } + public override void Close () { } + public override Encoding Encoding { + get { + return Encoding.UTF8; + } + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMREngine.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMREngine.cs new file mode 100644 index 0000000..24d49f8 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMREngine.cs @@ -0,0 +1,1979 @@ +/* + * 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 log4net; +using Mono.Addins; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Monitoring; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenMetaverse; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading; +using System.Timers; +using System.Xml; + +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; + +[assembly: Addin("XMREngine", OpenSim.VersionInfo.VersionNumber)] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "XMREngine")] + public partial class XMREngine : INonSharedRegionModule, IScriptEngine, + IScriptModule + { + public static readonly DetectParams[] zeroDetectParams = new DetectParams[0]; + private static ArrayList noScriptErrors = new ArrayList(); + public static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string[] scriptReferencedAssemblies = new string[0]; + + private bool m_LateInit; + private bool m_TraceCalls; + public bool m_Verbose; + public bool m_ScriptDebug; + public Scene m_Scene; + private IConfigSource m_ConfigSource; + private IConfig m_Config; + private string m_ScriptBasePath; + private bool m_Enabled = false; + public bool m_StartProcessing = false; + public bool m_UseSourceHashCode = false; + public ConstructorInfo uThreadCtor; + private Dictionary m_ScriptErrors = + new Dictionary(); + private Dictionary> m_ObjectItemList = + new Dictionary>(); + private Dictionary m_ObjectInstArray = + new Dictionary(); + public Dictionary m_XMRInstanceApiCtxFieldInfos = + new Dictionary (); + private int m_StackSize; + private int m_HeapSize; + private XMRScriptThread[] m_ScriptThreads; + private Thread m_SleepThread = null; + private Thread m_SliceThread = null; + private bool m_Exiting = false; + + private int m_MaintenanceInterval = 10; + private System.Timers.Timer m_MaintenanceTimer; + public int numThreadScriptWorkers; + + private object m_FrameUpdateLock = new object (); + private event ThreadStart m_FrameUpdateList = null; + + /* + * Various instance lists: + * m_InstancesDict = all known instances + * find an instance given its itemID + * m_StartQueue = instances that have just had event queued to them + * m_YieldQueue = instances that are ready to run right now + * m_SleepQueue = instances that have m_SleepUntil valid + * sorted by ascending m_SleepUntil + */ + private Dictionary m_InstancesDict = + new Dictionary(); + public Queue m_ThunkQueue = new Queue (); + public XMRInstQueue m_StartQueue = new XMRInstQueue(); + public XMRInstQueue m_YieldQueue = new XMRInstQueue(); + public XMRInstQueue m_SleepQueue = new XMRInstQueue(); + private string m_LockedDict = "nobody"; + + public XMREngine() + { + string envar; + + envar = Environment.GetEnvironmentVariable ("XMREngineTraceCalls"); + m_TraceCalls = (envar != null) && ((envar[0] & 1) != 0); + m_log.Info ("[XMREngine]: m_TraceCalls=" + m_TraceCalls); + + envar = Environment.GetEnvironmentVariable ("XMREngineVerbose"); + m_Verbose = (envar != null) && ((envar[0] & 1) != 0); + m_log.Info ("[XMREngine]: m_Verbose=" + m_Verbose); + + envar = Environment.GetEnvironmentVariable ("XMREngineScriptDebug"); + m_ScriptDebug = (envar != null) && ((envar[0] & 1) != 0); + m_log.Info ("[XMREngine]: m_ScriptDebug=" + m_ScriptDebug); + } + + public string Name + { + get { return "XMREngine"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public string ScriptEnginePath + { + get { return m_ScriptBasePath; } + } + + public string ScriptClassName + { + get { return "XMREngineScript"; } + } + + public string ScriptBaseClassName + { + get { return typeof (XMRInstance).FullName; } + } + + public ParameterInfo[] ScriptBaseClassParameters + { + get { return typeof(XMRInstance).GetConstructor (new Type[] { typeof (WaitHandle) }).GetParameters (); } + } + + public string[] ScriptReferencedAssemblies + { + get { return scriptReferencedAssemblies; } + } + + public void Initialise(IConfigSource config) + { + TraceCalls("[XMREngine]: Initialize entry"); + m_ConfigSource = config; + + ////foreach (IConfig icfg in config.Configs) { + //// m_log.Debug("[XMREngine]: Initialise: configs[" + icfg.Name + "]"); + //// foreach (string key in icfg.GetKeys ()) { + //// m_log.Debug("[XMREngine]: Initialise: " + key + "=" + icfg.GetExpanded (key)); + //// } + ////} + + m_Enabled = false; + m_Config = config.Configs["XMREngine"]; + if (m_Config == null) { + m_log.Info("[XMREngine]: no config, assuming disabled"); + return; + } + m_Enabled = m_Config.GetBoolean("Enabled", false); + m_log.InfoFormat("[XMREngine]: config enabled={0}", m_Enabled); + if (!m_Enabled) { + return; + } + + string uThreadModel = "sys"; // will work anywhere + uThreadModel = m_Config.GetString ("UThreadModel", uThreadModel); + + Type uThreadType = null; + switch (uThreadModel.ToLower ()) { + + // mono continuations - memcpy()s the stack + case "con": { + uThreadType = typeof (ScriptUThread_Con); + break; + } + + // patched mono microthreads - switches stack pointer + case "mmr": { + Exception e = ScriptUThread_MMR.LoadMono (); + if (e != null) { + m_log.Error ("[XMREngine]: mmr thread model not available\n", e); + m_Enabled = false; + return; + } + uThreadType = typeof (ScriptUThread_MMR); + break; + } + + // system threads - works on mono and windows + case "sys": { + uThreadType = typeof (ScriptUThread_Sys); + break; + } + + // who knows what + default: { + m_log.Error ("[XMREngine]: unknown thread model " + uThreadModel); + m_Enabled = false; + return; + } + } + + uThreadCtor = uThreadType.GetConstructor (new Type[] { typeof (XMRInstance) }); + m_log.Info ("[XMREngine]: using thread model " + uThreadModel); + + m_UseSourceHashCode = m_Config.GetBoolean ("UseSourceHashCode", false); + numThreadScriptWorkers = m_Config.GetInt ("NumThreadScriptWorkers", 1); + m_ScriptThreads = new XMRScriptThread[numThreadScriptWorkers]; + + for (int i = 0; i < numThreadScriptWorkers; i ++) { + m_ScriptThreads[i] = new XMRScriptThread(this); + } + + m_SleepThread = StartMyThread (RunSleepThread, "xmrengine sleep", ThreadPriority.Normal); + m_SliceThread = StartMyThread (RunSliceThread, "xmrengine slice", ThreadPriority.Normal); + + /* + * Verify that our ScriptEventCode's match OpenSim's scriptEvent's. + */ + bool err = false; + for (int i = 0; i < 32; i ++) { + string mycode = "undefined"; + string oscode = "undefined"; + try { + mycode = ((ScriptEventCode)i).ToString(); + Convert.ToInt32(mycode); + mycode = "undefined"; + } catch { + } + try { + oscode = ((OpenSim.Region.Framework.Scenes.scriptEvents)(1 << i)).ToString(); + Convert.ToInt32(oscode); + oscode = "undefined"; + } catch { + } + if (mycode != oscode) { + m_log.ErrorFormat("[XMREngine]: {0} mycode={1}, oscode={2}", i, mycode, oscode); + err = true; + } + } + if (err) { + m_Enabled = false; + return; + } + + m_StackSize = m_Config.GetInt("ScriptStackSize", 2048) << 10; + m_HeapSize = m_Config.GetInt("ScriptHeapSize", 1024) << 10; + + m_log.InfoFormat("[XMREngine]: Enabled, {0}.{1} Meg (0x{2}) stacks", + (m_StackSize >> 20).ToString (), + (((m_StackSize % 0x100000) * 1000) + >> 20).ToString ("D3"), + m_StackSize.ToString ("X")); + + m_log.InfoFormat("[XMREngine]: ... {0}.{1} Meg (0x{2}) heaps", + (m_HeapSize >> 20).ToString (), + (((m_HeapSize % 0x100000) * 1000) + >> 20).ToString ("D3"), + m_HeapSize.ToString ("X")); + + m_MaintenanceInterval = m_Config.GetInt("MaintenanceInterval", 10); + + if (m_MaintenanceInterval > 0) + { + m_MaintenanceTimer = new System.Timers.Timer(m_MaintenanceInterval * 60000); + m_MaintenanceTimer.Elapsed += DoMaintenance; + m_MaintenanceTimer.Start(); + } + + MainConsole.Instance.Commands.AddCommand("xmr", false, + "xmr", + "xmr [...|help|...] ...", + "Run xmr script engine commands", + RunTest); + + TraceCalls("[XMREngine]: Initialize successful"); + } + + public void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + TraceCalls("[XMREngine]: XMREngine.AddRegion({0})", scene.RegionInfo.RegionName); + + m_Scene = scene; + + m_Scene.RegisterModuleInterface(this); + + m_ScriptBasePath = m_Config.GetString ("ScriptBasePath", "ScriptData"); + m_ScriptBasePath = Path.Combine (m_ScriptBasePath, scene.RegionInfo.RegionID.ToString()); + + Directory.CreateDirectory(m_ScriptBasePath); + + m_Scene.EventManager.OnRezScript += OnRezScript; + + m_Scene.StackModuleInterface(this); + } + + private void OneTimeLateInitialization () + { + /* + * Build list of defined APIs and their 'this' types and define a field in XMRInstanceSuperType. + */ + ApiManager am = new ApiManager (); + Dictionary apiCtxTypes = new Dictionary (); + foreach (string api in am.GetApis ()) { + m_log.Debug ("[XMREngine]: adding api " + api); + IScriptApi scriptApi = am.CreateApi (api); + Type apiCtxType = scriptApi.GetType (); + if (api == "LSL") apiCtxType = typeof (XMRLSL_Api); + apiCtxTypes[api] = apiCtxType; + } + + if (ScriptCodeGen.xmrInstSuperType == null) // Only create type once! + { + /* + * Start creating type XMRInstanceSuperType that contains a field + * m_ApiManager_ that points to the per-instance context + * struct for that API, ie, the 'this' value passed to all methods + * in that API. It is in essence: + * + * public class XMRInstanceSuperType : XMRInstance { + * public XMRLSL_Api m_ApiManager_LSL; // 'this' value for all ll...() functions + * public MOD_Api m_ApiManager_MOD; // 'this' value for all mod...() functions + * public OSSL_Api m_ApiManager_OSSL; // 'this' value for all os...() functions + * .... + * } + */ + AssemblyName assemblyName = new AssemblyName (); + assemblyName.Name = "XMRInstanceSuperAssembly"; + AssemblyBuilder assemblyBuilder = Thread.GetDomain ().DefineDynamicAssembly (assemblyName, AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule ("XMRInstanceSuperModule"); + TypeBuilder typeBuilder = moduleBuilder.DefineType ("XMRInstanceSuperType", TypeAttributes.Public | TypeAttributes.Class); + typeBuilder.SetParent (typeof (XMRInstance)); + + foreach (string apiname in apiCtxTypes.Keys) + { + string fieldName = "m_ApiManager_" + apiname; + typeBuilder.DefineField (fieldName, apiCtxTypes[apiname], FieldAttributes.Public); + } + + /* + * Finalize definition of XMRInstanceSuperType. + * Give the compiler a short name to reference it by, + * otherwise it will try to use the AssemblyQualifiedName + * and fail miserably. + */ + ScriptCodeGen.xmrInstSuperType = typeBuilder.CreateType (); + ScriptObjWriter.DefineInternalType ("xmrsuper", ScriptCodeGen.xmrInstSuperType); + } + + /* + * Tell the compiler about all the constants and methods for each API. + * We also tell the compiler how to get the per-instance context for each API + * by reading the corresponding m_ApiManager_ field of XMRInstanceSuperType. + */ + foreach (KeyValuePair kvp in apiCtxTypes) { + + // get API name and the corresponding per-instance context type + string api = kvp.Key; + Type apiCtxType = kvp.Value; + + // give script compiler an abbreviated name for the API context type + ScriptObjWriter.DefineInternalType ("apimanager_" + api, apiCtxType); + + // this field tells the compiled code where the per-instance API context object is + // eg, for the OSSL API, it is in ((XMRInstanceSuperType)inst).m_ApiManager_OSSL + string fieldName = "m_ApiManager_" + api; + FieldInfo fieldInfo = ScriptCodeGen.xmrInstSuperType.GetField (fieldName); + m_XMRInstanceApiCtxFieldInfos[api] = fieldInfo; + + // now tell the compiler about the constants and methods for the API + ScriptConst.AddInterfaceConstants (null, apiCtxType.GetFields ()); + TokenDeclInline.AddInterfaceMethods (null, apiCtxType.GetMethods (), fieldInfo); + } + + /* + * Add sim-specific APIs to the compiler. + */ + IScriptModuleComms comms = m_Scene.RequestModuleInterface (); + if (comms != null) { + + /* + * Add methods to list of built-in functions. + */ + Delegate[] methods = comms.GetScriptInvocationList (); + foreach (Delegate m in methods) { + MethodInfo mi = m.Method; + try { + CommsCallCodeGen cccg = new CommsCallCodeGen (mi, comms, m_XMRInstanceApiCtxFieldInfos["MOD"]); + Verbose ("[XMREngine]: added comms function " + cccg.fullName); + } catch (Exception e) { + m_log.Error ("[XMREngine]: failed to add comms function " + mi.Name); + m_log.Error ("[XMREngine]: - " + e.ToString ()); + } + } + + /* + * Add constants to list of built-in constants. + */ + Dictionary consts = comms.GetConstants (); + foreach (KeyValuePair kvp in consts) { + try { + ScriptConst sc = ScriptConst.AddConstant (kvp.Key, kvp.Value); + Verbose ("[XMREngine]: added comms constant " + sc.name); + } catch (Exception e) { + m_log.Error ("[XMREngine]: failed to add comms constant " + kvp.Key); + m_log.Error ("[XMREngine]: - " + e.Message); + } + } + } else { + Verbose ("[XMREngine]: comms not enabled"); + } + } + + /** + * @brief Generate code for the calls to the comms functions. + * It is a tRUlY EvIL interface. + * To call the function we must call an XMRInstanceSuperType.m_ApiManager_MOD.modInvoker?() + * method passing it the name of the function as a string and the script + * argument list wrapped up in an object[] array. The modInvoker?() methods + * do some sick type conversions (with corresponding mallocs) so we can't + * call the methods directly. + */ + private class CommsCallCodeGen : TokenDeclInline { + private static Type[] modInvokerArgTypes = new Type[] { typeof (string), typeof (object[]) }; + public static FieldInfo xmrInstModApiCtxField; + + private MethodInfo modInvokerMeth; + private string methName; + + /** + * @brief Constructor + * @param mi = method to make available to scripts + * mi.Name = name that is used by scripts + * mi.GetParameters() = parameter list as defined by module + * includes the 'UUID host','UUID script' parameters that script does not see + * allowed types for script-visible parameters are as follows: + * Single -> float + * Int32 -> integer + * OpenMetaverse.UUID -> key + * Object[] -> list + * OpenMetaverse.Quaternion -> rotation + * String -> string + * OpenMetaverse.Vector3 -> vector + * mi.ReturnType = return type as defined by module + * types are same as allowed for parameters + * @param comms = comms module the method came from + * @param apictxfi = what field in XMRInstanceSuperType the 'this' value is for this method + */ + public CommsCallCodeGen (MethodInfo mi, IScriptModuleComms comms, FieldInfo apictxfi) + : base (null, false, NameArgSig (mi), RetType (mi)) + { + methName = mi.Name; + string modInvokerName = comms.LookupModInvocation (methName); + if (modInvokerName == null) throw new Exception ("cannot find comms method " + methName); + modInvokerMeth = typeof (MOD_Api).GetMethod (modInvokerName, modInvokerArgTypes); + xmrInstModApiCtxField = apictxfi; + } + + // script-visible name(argtype,...) signature string + private static string NameArgSig (MethodInfo mi) + { + StringBuilder sb = new StringBuilder (); + sb.Append (mi.Name); + sb.Append ('('); + ParameterInfo[] mps = mi.GetParameters (); + for (int i = 2; i < mps.Length; i ++) { + ParameterInfo pi = mps[i]; + if (i > 2) sb.Append (','); + sb.Append (ParamType (pi.ParameterType)); + } + sb.Append (')'); + return sb.ToString (); + } + + // script-visible return type + // note that although we support void, the comms stuff does not + private static TokenType RetType (MethodInfo mi) + { + Type rt = mi.ReturnType; + if (rt == typeof (float)) return new TokenTypeFloat (null); + if (rt == typeof (int)) return new TokenTypeInt (null); + if (rt == typeof (object[])) return new TokenTypeList (null); + if (rt == typeof (OpenMetaverse.UUID)) return new TokenTypeKey (null); + if (rt == typeof (OpenMetaverse.Quaternion)) return new TokenTypeRot (null); + if (rt == typeof (string)) return new TokenTypeStr (null); + if (rt == typeof (OpenMetaverse.Vector3)) return new TokenTypeVec (null); + if (rt == null || rt == typeof (void)) return new TokenTypeVoid (null); + throw new Exception ("unsupported return type " + rt.Name); + } + + // script-visible parameter type + private static string ParamType (Type t) + { + if (t == typeof (float)) return "float"; + if (t == typeof (int)) return "integer"; + if (t == typeof (OpenMetaverse.UUID)) return "key"; + if (t == typeof (object[])) return "list"; + if (t == typeof (OpenMetaverse.Quaternion)) return "rotation"; + if (t == typeof (string)) return "string"; + if (t == typeof (OpenMetaverse.Vector3)) return "vector"; + throw new Exception ("unsupported parameter type " + t.Name); + } + + /** + * @brief Called by the compiler to generate a call to the comms function. + * @param scg = which script is being compiled + * @param errorAt = where in the source code the call is being made (for error messages) + * @param result = a temp location to put the return value in if any + * @param args = array of script-visible arguments being passed to the function + */ + public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) + { + /* + * Set up 'this' pointer for modInvoker?() = value from ApiManager.CreateApi("MOD"). + */ + scg.PushXMRInst (); + scg.ilGen.Emit (errorAt, OpCodes.Castclass, xmrInstModApiCtxField.DeclaringType); + scg.ilGen.Emit (errorAt, OpCodes.Ldfld, xmrInstModApiCtxField); + + /* + * Set up 'fname' argument to modInvoker?() = name of the function to be called. + */ + scg.ilGen.Emit (errorAt, OpCodes.Ldstr, methName); + + /* + * Set up 'parms' argument to modInvoker?() = object[] of the script-visible parameters, + * in their LSL-wrapped form. Of course, the modInvoker?() method will malloc yet another + * object[] and type-convert these parameters one-by-one with another round of unwrapping + * and wrapping. + * Types allowed in this object[]: + * LSL_Float, LSL_Integer, LSL_Key, LSL_List, LSL_Rotation, LSL_String, LSL_Vector + */ + int nargs = args.Length; + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, nargs); + scg.ilGen.Emit (errorAt, OpCodes.Newarr, typeof (object)); + + for (int i = 0; i < nargs; i ++) { + scg.ilGen.Emit (errorAt, OpCodes.Dup); + scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, i); + + // get location and type of argument + + CompValu arg = args[i]; + TokenType argtype = arg.type; + + // if already in a form acceptable to modInvoker?(), + // just push it to the stack and convert to object + // by boxing it if necessary + + // but if something like a double, int, string, etc + // push to stack converting to the LSL-wrapped type + // then convert to object by boxing if necessary + + Type boxit = null; + if (argtype is TokenTypeLSLFloat) { + args[i].PushVal (scg, errorAt); + boxit = typeof (LSL_Float); + } else if (argtype is TokenTypeLSLInt) { + args[i].PushVal (scg, errorAt); + boxit = typeof (LSL_Integer); + } else if (argtype is TokenTypeLSLKey) { + args[i].PushVal (scg, errorAt); + boxit = typeof (LSL_Key); + } else if (argtype is TokenTypeList) { + args[i].PushVal (scg, errorAt); + boxit = typeof (LSL_List); + } else if (argtype is TokenTypeRot) { + args[i].PushVal (scg, errorAt); + boxit = typeof (LSL_Rotation); + } else if (argtype is TokenTypeLSLString) { + args[i].PushVal (scg, errorAt); + boxit = typeof (LSL_String); + } else if (argtype is TokenTypeVec) { + args[i].PushVal (scg, errorAt); + boxit = typeof (LSL_Vector); + } else if (argtype is TokenTypeFloat) { + args[i].PushVal (scg, errorAt, new TokenTypeLSLFloat (argtype)); + boxit = typeof (LSL_Float); + } else if (argtype is TokenTypeInt) { + args[i].PushVal (scg, errorAt, new TokenTypeLSLInt (argtype)); + boxit = typeof (LSL_Integer); + } else if (argtype is TokenTypeKey) { + args[i].PushVal (scg, errorAt, new TokenTypeLSLKey (argtype)); + boxit = typeof (LSL_Key); + } else if (argtype is TokenTypeStr) { + args[i].PushVal (scg, errorAt, new TokenTypeLSLString (argtype)); + boxit = typeof (LSL_String); + } else { + throw new Exception ("unsupported arg type " + argtype.GetType ().Name); + } + if (boxit.IsValueType) { + scg.ilGen.Emit (errorAt, OpCodes.Box, boxit); + } + + // pop the object into the object[] + + scg.ilGen.Emit (errorAt, OpCodes.Stelem, typeof (object)); + } + + /* + * Call the modInvoker?() method. + * It leaves an LSL-wrapped type on the stack. + */ + if (modInvokerMeth.IsVirtual) { + scg.ilGen.Emit (errorAt, OpCodes.Callvirt, modInvokerMeth); + } else { + scg.ilGen.Emit (errorAt, OpCodes.Call, modInvokerMeth); + } + + /* + * The 3rd arg to Pop() is the type on the stack, + * ie, what modInvoker?() actually returns. + * The Pop() method will wrap/unwrap as needed. + */ + Type retSysType = modInvokerMeth.ReturnType; + if (retSysType == null) retSysType = typeof (void); + TokenType retTokType = TokenType.FromSysType (errorAt, retSysType); + result.Pop (scg, errorAt, retTokType); + } + } + + /** + * @brief Called late in shutdown procedure, + * after the 'Shutting down..." message. + */ + public void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + + TraceCalls("[XMREngine]: XMREngine.RemoveRegion({0})", scene.RegionInfo.RegionName); + + /* + * Write script states out to .state files so it will be + * available when the region is restarted. + */ + DoMaintenance(null, null); + + /* + * Stop executing script threads and wait for final + * one to finish (ie, script gets to CheckRun() call). + */ + m_Exiting = true; + for (int i = 0; i < numThreadScriptWorkers; i ++) { + XMRScriptThread scriptThread = m_ScriptThreads[i]; + if (scriptThread != null) { + scriptThread.Terminate(); + m_ScriptThreads[i] = null; + } + } + if (m_SleepThread != null) { + lock (m_SleepQueue) { + Monitor.PulseAll (m_SleepQueue); + } + m_SleepThread.Join(); + m_SleepThread = null; + } + if (m_SliceThread != null) { + m_SliceThread.Join(); + m_SliceThread = null; + } + + m_Scene.EventManager.OnFrame -= OnFrame; + m_Scene.EventManager.OnRezScript -= OnRezScript; + m_Scene.EventManager.OnRemoveScript -= OnRemoveScript; + m_Scene.EventManager.OnScriptReset -= OnScriptReset; + m_Scene.EventManager.OnStartScript -= OnStartScript; + m_Scene.EventManager.OnStopScript -= OnStopScript; + m_Scene.EventManager.OnGetScriptRunning -= OnGetScriptRunning; + m_Scene.EventManager.OnShutdown -= OnShutdown; + + m_Enabled = false; + m_Scene = null; + } + + public void RegionLoaded(Scene scene) + { + if (!m_Enabled) + return; + + TraceCalls("[XMREngine]: XMREngine.RegionLoaded({0})", scene.RegionInfo.RegionName); + + m_Scene.EventManager.OnFrame += OnFrame; + m_Scene.EventManager.OnRemoveScript += OnRemoveScript; + m_Scene.EventManager.OnScriptReset += OnScriptReset; + m_Scene.EventManager.OnStartScript += OnStartScript; + m_Scene.EventManager.OnStopScript += OnStopScript; + m_Scene.EventManager.OnGetScriptRunning += OnGetScriptRunning; + m_Scene.EventManager.OnShutdown += OnShutdown; + + InitEvents(); + } + + public void StartProcessing() + { + m_log.Debug ("[XMREngine]: StartProcessing entry"); + m_Scene.EventManager.TriggerEmptyScriptCompileQueue (0, ""); + m_StartProcessing = true; + for (int i = 0; i < numThreadScriptWorkers; i ++) { + XMRScriptThread.WakeUpOne(); + } + m_log.Debug ("[XMREngine]: StartProcessing return"); + } + + public void Close() + { + TraceCalls("[XMREngine]: XMREngine.Close()"); + } + + private void RunTest (string module, string[] args) + { + if (args.Length < 2) { + m_log.Info ("[XMREngine]: missing command, try 'xmr help'"); + return; + } + + switch (args[1]) { + case "cvv": { + switch (args.Length) { + case 2: { + m_log.InfoFormat ("[XMREngine]: compiled version value = {0}", + ScriptCodeGen.COMPILED_VERSION_VALUE); + break; + } + case 3: { + try { + ScriptCodeGen.COMPILED_VERSION_VALUE = Convert.ToInt32 (args[2]); + } catch { + m_log.Error ("[XMREngine]: bad/missing version number"); + } + break; + } + default: { + m_log.Error ("[XMREngine]: xmr cvv []"); + break; + } + } + break; + } + case "echo": { + for (int i = 0; i < args.Length; i ++) { + m_log.Info ("[XMREngine]: echo[" + i + "]=<" + args[i] + ">"); + } + break; + } + case "gc": { + GC.Collect(); + break; + } + case "help": + case "?": { + m_log.Info ("[XMREngine]: xmr cvv [] - show/set compiled version value"); + m_log.Info ("[XMREngine]: xmr gc"); + m_log.Info ("[XMREngine]: xmr ls [-help ...]"); + m_log.Info ("[XMREngine]: xmr mvv [] - show/set migration version value"); + m_log.Info ("[XMREngine]: xmr pev [-help ...] - post event"); + m_log.Info ("[XMREngine]: xmr reset [-help ...]"); + m_log.Info ("[XMREngine]: xmr resume - resume script processing"); + m_log.Info ("[XMREngine]: xmr suspend - suspend script processing"); + m_log.Info ("[XMREngine]: xmr tracecalls [yes | no]"); + m_log.Info ("[XMREngine]: xmr verbose [yes | no]"); + break; + } + case "ls": { + XmrTestLs (args, 2); + break; + } + case "mvv": { + switch (args.Length) { + case 2: { + m_log.InfoFormat ("[XMREngine]: migration version value = {0}", + XMRInstance.migrationVersion); + break; + } + case 3: { + try { + int mvv = Convert.ToInt32 (args[2]); + if ((mvv < 0) || (mvv > 255)) throw new Exception ("out of range"); + XMRInstance.migrationVersion = (byte) mvv; + } catch (Exception e) { + m_log.Error ("[XMREngine]: bad/missing version number (" + e.Message + ")"); + } + break; + } + default: { + m_log.Error ("[XMREngine]: xmr mvv []"); + break; + } + } + break; + } + case "pev": { + XmrTestPev (args, 2); + break; + } + case "reset": { + XmrTestReset (args, 2); + break; + } + case "resume": { + m_log.Info ("[XMREngine]: resuming scripts"); + for (int i = 0; i < numThreadScriptWorkers; i ++) { + m_ScriptThreads[i].ResumeThread(); + } + break; + } + case "suspend": { + m_log.Info ("[XMREngine]: suspending scripts"); + for (int i = 0; i < numThreadScriptWorkers; i ++) { + m_ScriptThreads[i].SuspendThread(); + } + break; + } + case "tracecalls": { + if (args.Length > 2) { + m_TraceCalls = (args[2][0] & 1) != 0; + } + m_log.Info ("[XMREngine]: tracecalls " + (m_TraceCalls ? "yes" : "no")); + break; + } + case "verbose": { + if (args.Length > 2) { + m_Verbose = (args[2][0] & 1) != 0; + } + m_log.Info ("[XMREngine]: verbose " + (m_Verbose ? "yes" : "no")); + break; + } + default: { + m_log.Error ("[XMREngine]: unknown command " + args[1] + ", try 'xmr help'"); + break; + } + } + } + + // Not required when not using IScriptInstance + // + public IScriptWorkItem QueueEventHandler(object parms) + { + return null; + } + + public Scene World + { + get { return m_Scene; } + } + + public IScriptModule ScriptModule + { + get { return this; } + } + + public void SaveAllState() + { + m_log.Error("[XMREngine]: XMREngine.SaveAllState() called!!"); + } + + public event ScriptRemoved OnScriptRemoved; + public event ObjectRemoved OnObjectRemoved; + + // Events targeted at a specific script + // ... like listen() for an llListen() call + // + public bool PostScriptEvent(UUID itemID, EventParams parms) + { + XMRInstance instance = GetInstance (itemID); + if (instance == null) return false; + + TraceCalls("[XMREngine]: XMREngine.PostScriptEvent({0},{1})", itemID.ToString(), parms.EventName); + + instance.PostEvent(parms); + return true; + } + + // Events targeted at all scripts in the given prim. + // localID = which prim + // parms = event to post + // + public bool PostObjectEvent (uint localID, EventParams parms) + { + SceneObjectPart part = m_Scene.GetSceneObjectPart(localID); + + if (part == null) + return false; + + TraceCalls("[XMREngine]: XMREngine.PostObjectEvent({0},{1})", localID.ToString(), parms.EventName); + + /* + * In SecondLife, attach events go to all scripts of all prims + * in a linked object. So here we duplicate that functionality, + * as all we ever get is a single attach event for the whole + * object. + */ + if (parms.EventName == "attach") { + bool posted = false; + foreach (SceneObjectPart primpart in part.ParentGroup.Parts) { + posted |= PostPrimEvent (primpart, parms); + } + return posted; + } + + /* + * Other events go to just the scripts in that prim. + */ + return PostPrimEvent (part, parms); + } + + private bool PostPrimEvent (SceneObjectPart part, EventParams parms) + { + UUID partUUID = part.UUID; + + /* + * Get list of script instances running in the object. + */ + XMRInstance[] objInstArray; + lock (m_InstancesDict) { + if (!m_ObjectInstArray.TryGetValue (partUUID, out objInstArray)) { + return false; + } + if (objInstArray == null) { + objInstArray = RebuildObjectInstArray (partUUID); + m_ObjectInstArray[partUUID] = objInstArray; + } + } + + /* + * Post event to all script instances in the object. + */ + if (objInstArray.Length <= 0) return false; + foreach (XMRInstance inst in objInstArray) { + inst.PostEvent (parms); + } + return true; + } + + public DetectParams GetDetectParams(UUID itemID, int number) + { + XMRInstance instance = GetInstance (itemID); + if (instance == null) return null; + return instance.GetDetectParams(number); + } + + public void SetMinEventDelay(UUID itemID, double delay) + { + } + + public int GetStartParameter(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance == null) return 0; + return instance.StartParam; + } + + // This is the "set running" method + // + public void SetScriptState(UUID itemID, bool state, bool self) + { + SetScriptState (itemID, state); + } + public void SetScriptState(UUID itemID, bool state) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + instance.Running = state; + } + } + + // Control display of the "running" checkbox + // + public bool GetScriptState(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance == null) return false; + return instance.Running; + } + + public void SetState(UUID itemID, string newState) + { + TraceCalls("[XMREngine]: XMREngine.SetState({0},{1})", itemID.ToString(), newState); + } + + public void ApiResetScript(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + instance.ApiReset(); + } + } + + public void ResetScript(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + IUrlModule urlModule = m_Scene.RequestModuleInterface(); + if (urlModule != null) + urlModule.ScriptRemoved(itemID); + + instance.Reset(); + } + } + + public IConfig Config + { + get { return m_Config; } + } + + public IConfigSource ConfigSource + { + get { return m_ConfigSource; } + } + + public string ScriptEngineName + { + get { return "XMREngine"; } + } + + public IScriptApi GetApi(UUID itemID, string name) + { + FieldInfo fi; + if (!m_XMRInstanceApiCtxFieldInfos.TryGetValue (name, out fi)) return null; + XMRInstance inst = GetInstance (itemID); + if (inst == null) return null; + return (IScriptApi)fi.GetValue (inst); + } + + /** + * @brief Get script's current state as an XML string + * - called by "Take", "Take Copy" and when object deleted (ie, moved to Trash) + * This includes the .state file + */ + public string GetXMLState(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance == null) return String.Empty; + + TraceCalls("[XMREngine]: XMREngine.GetXMLState({0})", itemID.ToString()); + + if (!instance.m_HasRun) return String.Empty; + + XmlDocument doc = new XmlDocument(); + + /* + * Set up tag. + */ + XmlElement stateN = doc.CreateElement("", "State", ""); + doc.AppendChild(stateN); + + XmlAttribute engineA = doc.CreateAttribute("", "Engine", ""); + engineA.Value = ScriptEngineName; + stateN.Attributes.Append(engineA); + + XmlAttribute uuidA = doc.CreateAttribute("", "UUID", ""); + uuidA.Value = itemID.ToString(); + stateN.Attributes.Append(uuidA); + + XmlAttribute assetA = doc.CreateAttribute("", "Asset", ""); + string assetID = instance.AssetID.ToString(); + assetA.Value = assetID; + stateN.Attributes.Append(assetA); + + /* + * Get ... item that hold's script's state. + * This suspends the script if necessary then takes a snapshot. + */ + XmlElement scriptStateN = instance.GetExecutionState(doc); + stateN.AppendChild(scriptStateN); + + return doc.OuterXml; + } + + // Set script's current state from an XML string + // - called just before a script is instantiated + // So we write the .state file so the .state file will be seen when + // the script is instantiated. + public bool SetXMLState(UUID itemID, string xml) + { + XmlDocument doc = new XmlDocument(); + + try + { + doc.LoadXml(xml); + } + catch + { + return false; + } + TraceCalls("[XMREngine]: XMREngine.SetXMLState({0})", itemID.ToString()); + + // Make sure so we know it is in our + // format. + XmlElement stateN = (XmlElement)doc.SelectSingleNode("State"); + if (stateN == null) + return false; + + if (stateN.GetAttribute("Engine") != ScriptEngineName) + return false; + + // ... contains contents of .state file. + XmlElement scriptStateN = (XmlElement)stateN.SelectSingleNode("ScriptState"); + if (scriptStateN == null) { + return false; + } + string sen = stateN.GetAttribute("Engine"); + if ((sen == null) || (sen != ScriptEngineName)) { + return false; + } + + XmlAttribute assetA = doc.CreateAttribute("", "Asset", ""); + assetA.Value = stateN.GetAttribute("Asset"); + scriptStateN.Attributes.Append(assetA); + + // Write out the .state file with the ... XML text + string statePath = XMRInstance.GetStateFileName(m_ScriptBasePath, itemID); + FileStream ss = File.Create(statePath); + StreamWriter sw = new StreamWriter(ss); + sw.Write(scriptStateN.OuterXml); + sw.Close(); + ss.Close(); + + return true; + } + + public bool PostScriptEvent(UUID itemID, string name, Object[] p) + { + if (!m_Enabled) + return false; + + TraceCalls("[XMREngine]: XMREngine.PostScriptEvent({0},{1})", itemID.ToString(), name); + + return PostScriptEvent(itemID, new EventParams(name, p, zeroDetectParams)); + } + + public bool PostObjectEvent(UUID itemID, string name, Object[] p) + { + if (!m_Enabled) + return false; + + TraceCalls("[XMREngine]: XMREngine.PostObjectEvent({0},{1})", itemID.ToString(), name); + + SceneObjectPart part = m_Scene.GetSceneObjectPart(itemID); + if (part == null) + return false; + + return PostObjectEvent(part.LocalId, new EventParams(name, p, zeroDetectParams)); + } + + // about the 3523rd entrypoint for a script to put itself to sleep + public void SleepScript(UUID itemID, int delay) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + instance.Sleep (delay); + } + } + + // Get a script instance loaded, compiling it if necessary + // + // localID = the object as a whole, may contain many scripts + // itemID = this instance of the script in this object + // script = script source code + // startParam = value passed to 'on_rez' event handler + // postOnRez = true to post an 'on_rez' event to script on load + // defEngine = default script engine + // stateSource = post this event to script on load + + public void OnRezScript(uint localID, UUID itemID, string script, + int startParam, bool postOnRez, string defEngine, int stateSource) + { + SceneObjectPart part = m_Scene.GetSceneObjectPart(localID); + TaskInventoryItem item = part.Inventory.GetInventoryItem(itemID); + + if (!m_LateInit) { + m_LateInit = true; + OneTimeLateInitialization (); + } + + TraceCalls("[XMREngine]: OnRezScript(...,{0},...)", itemID.ToString()); + + /* + * Assume script uses the default engine, whatever that is. + */ + string engineName = defEngine; + + /* + * Very first line might contain "//" scriptengine ":". + */ + string firstline = ""; + if (script.StartsWith("//")) { + int lineEnd = script.IndexOf('\n'); + if (lineEnd > 1) firstline = script.Substring(0, lineEnd).Trim(); + int colon = firstline.IndexOf(':'); + if (colon >= 2) { + engineName = firstline.Substring(2, colon - 2).Trim(); + if (engineName == "") engineName = defEngine; + } + } + + /* + * Make sure the default or requested engine is us. + */ + if (engineName != ScriptEngineName) { + + /* + * Not us, if requested engine exists, silently ignore script and let + * requested engine handle it. + */ + IScriptModule[] engines = m_Scene.RequestModuleInterfaces (); + foreach (IScriptModule eng in engines) { + if (eng.ScriptEngineName == engineName) { + return; + } + } + + /* + * Requested engine not defined, warn on console. + * Then we try to handle it if we're the default engine, else we ignore it. + */ + m_log.Warn ("[XMREngine]: " + itemID.ToString() + " requests undefined/disabled engine " + engineName); + m_log.Info ("[XMREngine]: - " + part.GetWorldPosition ()); + m_log.Info ("[XMREngine]: first line: " + firstline); + if (defEngine != ScriptEngineName) { + m_log.Info ("[XMREngine]: leaving it to the default script engine (" + defEngine + ") to process it"); + return; + } + m_log.Info ("[XMREngine]: will attempt to processing it anyway as default script engine"); + } + + /* + * Put on object/instance lists. + */ + XMRInstance instance = (XMRInstance)Activator.CreateInstance (ScriptCodeGen.xmrInstSuperType); + instance.m_LocalID = localID; + instance.m_ItemID = itemID; + instance.m_SourceCode = script; + instance.m_StartParam = startParam; + instance.m_PostOnRez = postOnRez; + instance.m_StateSource = (StateSource)stateSource; + instance.m_Part = part; + instance.m_PartUUID = part.UUID; + instance.m_Item = item; + instance.m_DescName = part.Name + ":" + item.Name; + instance.m_IState = XMRInstState.CONSTRUCT; + + lock (m_InstancesDict) { + m_LockedDict = "RegisterInstance"; + + // Insert on internal list of all scripts being handled by this engine instance. + m_InstancesDict[instance.m_ItemID] = instance; + + // Insert on internal list of all scripts being handled by this engine instance + // that are part of the object. + List itemIDList; + if (!m_ObjectItemList.TryGetValue(instance.m_PartUUID, out itemIDList)) { + itemIDList = new List(); + m_ObjectItemList[instance.m_PartUUID] = itemIDList; + } + if (!itemIDList.Contains(instance.m_ItemID)) { + itemIDList.Add(instance.m_ItemID); + m_ObjectInstArray[instance.m_PartUUID] = null; + } + + m_LockedDict = "~RegisterInstance"; + } + + /* + * Compile and load it. + */ + lock (m_ScriptErrors) { + m_ScriptErrors.Remove (instance.m_ItemID); + } + LoadThreadWork (instance); + } + + /** + * @brief This routine instantiates one script. + */ + private void LoadThreadWork (XMRInstance instance) + { + /* + * Compile and load the script in memory. + */ + ArrayList errors = new ArrayList(); + Exception initerr = null; + try { + instance.Initialize(this, m_ScriptBasePath, m_StackSize, m_HeapSize, errors); + } catch (Exception e1) { + initerr = e1; + } + if ((initerr != null) && !instance.m_ForceRecomp) { + UUID itemID = instance.m_ItemID; + Verbose ("[XMREngine]: {0}/{2} first load failed ({1}), retrying after recompile", + itemID.ToString(), initerr.Message, instance.m_Item.AssetID.ToString()); + Verbose ("[XMREngine]:\n{0}", initerr.ToString ()); + initerr = null; + errors = new ArrayList(); + instance.m_ForceRecomp = true; + try { + instance.Initialize(this, m_ScriptBasePath, m_StackSize, m_HeapSize, errors); + } catch (Exception e2) { + initerr = e2; + } + } + if (initerr != null) { + UUID itemID = instance.m_ItemID; + Verbose ("[XMREngine]: Error starting script {0}/{2}: {1}", + itemID.ToString(), initerr.Message, instance.m_Item.AssetID.ToString()); + if (initerr.Message != "compilation errors") { + Verbose ("[XMREngine]: - " + instance.m_Part.GetWorldPosition () + " " + instance.m_DescName); + Verbose ("[XMREngine]: exception:\n{0}", initerr.ToString()); + } + + OnRemoveScript (0, itemID); + + /* + * Post errors where GetScriptErrors() can see them. + */ + if (errors.Count == 0) { + errors.Add(initerr.Message); + } else { + foreach (Object err in errors) { + if (m_ScriptDebug) + m_log.DebugFormat ("[XMREngine]: {0}", err.ToString()); + } + } + lock (m_ScriptErrors) { + m_ScriptErrors[instance.m_ItemID] = errors; + } + + return; + } + + /* + * Tell GetScriptErrors() that we have finished compiling/loading + * successfully (by posting a 0 element array). + */ + lock (m_ScriptErrors) { + if (instance.m_IState != XMRInstState.CONSTRUCT) throw new Exception("bad state"); + m_ScriptErrors[instance.m_ItemID] = noScriptErrors; + } + + /* + * Transition from CONSTRUCT->ONSTARTQ and give to RunScriptThread(). + * Put it on the start queue so it will run any queued event handlers, + * such as state_entry() or on_rez(). If there aren't any queued, it + * will just go to idle state when RunOne() tries to dequeue an event. + */ + lock (instance.m_QueueLock) { + if (instance.m_IState != XMRInstState.CONSTRUCT) throw new Exception("bad state"); + instance.m_IState = XMRInstState.ONSTARTQ; + if (!instance.m_Running) { + instance.EmptyEventQueues (); + } + } + QueueToStart(instance); + } + + public void OnRemoveScript(uint localID, UUID itemID) + { + TraceCalls("[XMREngine]: OnRemoveScript(...,{0})", itemID.ToString()); + + /* + * Remove from our list of known scripts. + * After this, no more events can queue because we won't be + * able to translate the itemID to an XMRInstance pointer. + */ + XMRInstance instance = null; + lock (m_InstancesDict) + { + m_LockedDict = "OnRemoveScript:" + itemID.ToString(); + + /* + * Tell the instance to free off everything it can. + */ + if (!m_InstancesDict.TryGetValue(itemID, out instance)) + { + m_LockedDict = "~OnRemoveScript"; + return; + } + + /* + * Tell it to stop executing anything. + */ + instance.suspendOnCheckRunHold = true; + + /* + * Remove it from our list of known script instances + * mostly so no more events can queue to it. + */ + m_InstancesDict.Remove(itemID); + + List itemIDList; + if (m_ObjectItemList.TryGetValue (instance.m_PartUUID, out itemIDList)) { + itemIDList.Remove(itemID); + if (itemIDList.Count == 0) { + m_ObjectItemList.Remove(instance.m_PartUUID); + m_ObjectInstArray.Remove(instance.m_PartUUID); + } else { + m_ObjectInstArray[instance.m_PartUUID] = null; + } + } + + /* + * Delete the .state file as any needed contents were fetched with GetXMLState() + * and stored on the database server. + */ + string stateFileName = XMRInstance.GetStateFileName(m_ScriptBasePath, itemID); + File.Delete(stateFileName); + + ScriptRemoved handlerScriptRemoved = OnScriptRemoved; + if (handlerScriptRemoved != null) { + handlerScriptRemoved(itemID); + } + + m_LockedDict = "~~OnRemoveScript"; + } + + /* + * Free off its stack and fun things like that. + * If it is running, abort it. + */ + instance.Dispose (); + } + + public void OnScriptReset(uint localID, UUID itemID) + { + TraceCalls("[XMREngine]: XMREngine.OnScriptReset({0},{1})", localID.ToString(), itemID.ToString()); + ResetScript(itemID); + } + + public void OnStartScript(uint localID, UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + instance.Running = true; + } + } + + public void OnStopScript(uint localID, UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + instance.Running = false; + } + } + + public void OnGetScriptRunning(IClientAPI controllingClient, + UUID objectID, UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + TraceCalls("[XMREngine]: XMREngine.OnGetScriptRunning({0},{1})", objectID.ToString(), itemID.ToString()); + + IEventQueue eq = World.RequestModuleInterface(); + if (eq == null) { + controllingClient.SendScriptRunningReply(objectID, itemID, + instance.Running); + } else { + eq.Enqueue(EventQueueHelper.ScriptRunningReplyEvent(objectID, + itemID, instance.Running, true), + controllingClient.AgentId); + } + } + } + + public bool HasScript(UUID itemID, out bool running) + { + XMRInstance instance = GetInstance (itemID); + if (instance == null) { + running = true; + return false; + } + running = instance.Running; + return true; + } + + /** + * @brief Called once per frame update to see if scripts have + * any such work to do. + */ + private void OnFrame () + { + if (m_FrameUpdateList != null) { + ThreadStart frameupdates; + lock (m_FrameUpdateLock) { + frameupdates = m_FrameUpdateList; + m_FrameUpdateList = null; + } + frameupdates (); + } + } + + /** + * @brief Add a one-shot delegate to list of things to do + * synchronized with frame updates. + */ + public void AddOnFrameUpdate (ThreadStart thunk) + { + lock (m_FrameUpdateLock) { + m_FrameUpdateList += thunk; + } + } + + /** + * @brief Gets called early as part of shutdown, + * right after "Persisting changed objects" message. + */ + public void OnShutdown() + { + TraceCalls("[XMREngine]: XMREngine.OnShutdown()"); + } + + /** + * @brief Queue an instance to the StartQueue so it will run. + * This queue is used for instances that have just had + * an event queued to them when they were previously + * idle. It must only be called by the thread that + * transitioned the thread to XMRInstState.ONSTARTQ so + * we don't get two threads trying to queue the same + * instance to the m_StartQueue at the same time. + */ + public void QueueToStart(XMRInstance inst) + { + lock (m_StartQueue) { + if (inst.m_IState != XMRInstState.ONSTARTQ) throw new Exception("bad state"); + m_StartQueue.InsertTail(inst); + } + XMRScriptThread.WakeUpOne(); + } + + /** + * @brief A script may be sleeping, in which case we wake it. + */ + public void WakeFromSleep(XMRInstance inst) + { + /* + * Remove from sleep queue unless someone else already woke it. + */ + lock (m_SleepQueue) { + if (inst.m_IState != XMRInstState.ONSLEEPQ) { + return; + } + m_SleepQueue.Remove(inst); + inst.m_IState = XMRInstState.REMDFROMSLPQ; + } + + /* + * Put on end of list of scripts that are ready to run. + */ + lock (m_YieldQueue) { + inst.m_IState = XMRInstState.ONYIELDQ; + m_YieldQueue.InsertTail(inst); + } + + /* + * Make sure the OS thread is running so it will see the script. + */ + XMRScriptThread.WakeUpOne(); + } + + /** + * @brief An instance has just finished running for now, + * figure out what to do with it next. + * @param inst = instance in question, not on any queue at the moment + * @param newIState = its new state + * @returns with instance inserted onto proper queue (if any) + */ + public void HandleNewIState(XMRInstance inst, XMRInstState newIState) + { + /* + * RunOne() should have left the instance in RUNNING state. + */ + if (inst.m_IState != XMRInstState.RUNNING) throw new Exception("bad state"); + + /* + * Now see what RunOne() wants us to do with the instance next. + */ + switch (newIState) { + + /* + * Instance has set m_SleepUntil to when it wants to sleep until. + * So insert instance in sleep queue by ascending wake time. + * Then wake the timer thread if this is the new first entry + * so it will reset its timer. + */ + case XMRInstState.ONSLEEPQ: { + lock (m_SleepQueue) { + XMRInstance after; + + inst.m_IState = XMRInstState.ONSLEEPQ; + for (after = m_SleepQueue.PeekHead(); after != null; after = after.m_NextInst) { + if (after.m_SleepUntil > inst.m_SleepUntil) break; + } + m_SleepQueue.InsertBefore(inst, after); + if (m_SleepQueue.PeekHead() == inst) { + Monitor.Pulse (m_SleepQueue); + } + } + break; + } + + /* + * Instance just took a long time to run and got wacked by the + * slicer. So put on end of yield queue to let someone else + * run. If there is no one else, it will run again right away. + */ + case XMRInstState.ONYIELDQ: { + lock (m_YieldQueue) { + inst.m_IState = XMRInstState.ONYIELDQ; + m_YieldQueue.InsertTail(inst); + } + break; + } + + /* + * Instance finished executing an event handler. So if there is + * another event queued for it, put it on the start queue so it + * will process the new event. Otherwise, mark it idle and the + * next event to queue to it will start it up. + */ + case XMRInstState.FINISHED: { + Monitor.Enter(inst.m_QueueLock); + if (!inst.m_Suspended && (inst.m_EventQueue.Count > 0)) { + Monitor.Exit(inst.m_QueueLock); + lock (m_StartQueue) { + inst.m_IState = XMRInstState.ONSTARTQ; + m_StartQueue.InsertTail (inst); + } + } else { + inst.m_IState = XMRInstState.IDLE; + Monitor.Exit(inst.m_QueueLock); + } + break; + } + + /* + * Its m_SuspendCount > 0. + * Don't put it on any queue and it won't run. + * Since it's not IDLE, even queuing an event won't start it. + */ + case XMRInstState.SUSPENDED: { + inst.m_IState = XMRInstState.SUSPENDED; + break; + } + + /* + * It has been disposed of. + * Just set the new state and all refs should theoretically drop off + * as the instance is no longer in any list. + */ + case XMRInstState.DISPOSED: { + inst.m_IState = XMRInstState.DISPOSED; + break; + } + + /* + * RunOne returned something bad. + */ + default: throw new Exception("bad new state"); + } + } + + /** + * @brief Thread that moves instances from the Sleep queue to the Yield queue. + */ + private void RunSleepThread() + { + double deltaTS; + int deltaMS; + XMRInstance inst; + + while (true) { + lock (m_SleepQueue) { + + /* + * Wait here until there is a script on the timer queue that has expired. + */ + while (true) { + UpdateMyThread (); + if (m_Exiting) { + MyThreadExiting (); + return; + } + inst = m_SleepQueue.PeekHead(); + if (inst == null) { + Monitor.Wait (m_SleepQueue, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2); + continue; + } + if (inst.m_IState != XMRInstState.ONSLEEPQ) throw new Exception("bad state"); + deltaTS = (inst.m_SleepUntil - DateTime.UtcNow).TotalMilliseconds; + if (deltaTS <= 0.0) break; + deltaMS = Int32.MaxValue; + if (deltaTS < Int32.MaxValue) deltaMS = (int)deltaTS; + if (deltaMS > Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2) { + deltaMS = Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2; + } + Monitor.Wait (m_SleepQueue, deltaMS); + } + + /* + * Remove the expired entry from the timer queue. + */ + m_SleepQueue.RemoveHead(); + inst.m_IState = XMRInstState.REMDFROMSLPQ; + } + + /* + * Post the script to the yield queue so it will run and wake a script thread to run it. + */ + lock (m_YieldQueue) { + inst.m_IState = XMRInstState.ONYIELDQ; + m_YieldQueue.InsertTail(inst); + } + XMRScriptThread.WakeUpOne (); + } + } + + /** + * @brief Thread that runs a time slicer. + */ + private void RunSliceThread() + { + int ms = m_Config.GetInt ("TimeSlice", 50); + while (!m_Exiting) { + UpdateMyThread (); + + /* + * Let script run for a little bit. + */ + System.Threading.Thread.Sleep (ms); + + /* + * If some script is running, flag it to suspend + * next time it calls CheckRun(). + */ + for (int i = 0; i < numThreadScriptWorkers; i ++) { + XMRScriptThread st = m_ScriptThreads[i]; + if (st != null) st.TimeSlice(); + } + } + MyThreadExiting (); + } + + public void Suspend(UUID itemID, int ms) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + instance.Sleep(ms); + } + } + + public void Die(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + TraceCalls("[XMREngine]: XMREngine.Die({0})", itemID.ToString()); + instance.Die(); + } + } + + /** + * @brief Get specific script instance for which OnRezScript() + * has been called for an XMREngine script, and that + * OnRemoveScript() has not been called since. + * @param itemID = as passed to OnRezScript() identifying a specific script instance + * @returns null: not one of our scripts (maybe XEngine etc) + * else: points to the script instance + */ + public XMRInstance GetInstance(UUID itemID) + { + XMRInstance instance; + lock (m_InstancesDict) { + if (!m_InstancesDict.TryGetValue(itemID, out instance)) { + instance = null; + } + } + return instance; + } + + // Called occasionally to write script state to .state file so the + // script will restart from its last known state if the region crashes + // and gets restarted. + private void DoMaintenance(object source, ElapsedEventArgs e) + { + XMRInstance[] instanceArray; + + lock (m_InstancesDict) { + instanceArray = System.Linq.Enumerable.ToArray(m_InstancesDict.Values); + } + foreach (XMRInstance ins in instanceArray) + { + // Don't save attachments + if (ins.m_Part.ParentGroup.IsAttachment) + continue; + ins.GetExecutionState(new XmlDocument()); + } + } + + /** + * @brief Retrieve errors generated by a previous call to OnRezScript(). + * We are guaranteed this routine will not be called before the + * corresponding OnRezScript() has returned. It blocks until the + * compile has completed. + */ + public ArrayList GetScriptErrors(UUID itemID) + { + ArrayList errors; + + lock (m_ScriptErrors) { + while (!m_ScriptErrors.TryGetValue (itemID, out errors)) { + Monitor.Wait (m_ScriptErrors); + } + m_ScriptErrors.Remove (itemID); + } + return errors; + } + + /** + * @brief Return a list of all script execution times. + */ + public Dictionary GetObjectScriptsExecutionTimes () + { + Dictionary topScripts = new Dictionary (); + lock (m_InstancesDict) { + foreach (XMRInstance instance in m_InstancesDict.Values) { + uint rootLocalID = instance.m_Part.ParentGroup.LocalId; + float oldTotal; + if (!topScripts.TryGetValue (rootLocalID, out oldTotal)) { + oldTotal = 0; + } + topScripts[rootLocalID] = (float)instance.m_CPUTime + oldTotal; + } + } + return topScripts; + } + + /** + * @brief A float the value is a representative execution time in + * milliseconds of all scripts in the link set. + * @param itemIDs = list of scripts in the link set + * @returns milliseconds for all those scripts + */ + public float GetScriptExecutionTime (List itemIDs) + { + if ((itemIDs == null) || (itemIDs.Count == 0)) { + return 0; + } + float time = 0; + foreach (UUID itemID in itemIDs) { + XMRInstance instance = GetInstance (itemID); + if ((instance != null) && instance.Running) { + time += (float) instance.m_CPUTime; + } + } + return time; + } + + /** + * @brief Block script from dequeuing events. + */ + public void SuspendScript(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + TraceCalls("[XMREngine]: XMREngine.SuspendScript({0})", itemID.ToString()); + instance.SuspendIt(); + } + } + + /** + * @brief Allow script to dequeue events. + */ + public void ResumeScript(UUID itemID) + { + XMRInstance instance = GetInstance (itemID); + if (instance != null) { + TraceCalls("[XMREngine]: XMREngine.ResumeScript({0})", itemID.ToString()); + instance.ResumeIt(); + } else { + // probably an XEngine script + } + } + + /** + * @brief Rebuild m_ObjectInstArray[partUUID] from m_ObjectItemList[partUUID] + * @param partUUID = which object in scene to rebuild for + */ + private XMRInstance[] RebuildObjectInstArray (UUID partUUID) + { + List itemIDList = m_ObjectItemList[partUUID]; + int n = 0; + foreach (UUID itemID in itemIDList) { + if (m_InstancesDict.ContainsKey (itemID)) n ++; + } + XMRInstance[] a = new XMRInstance[n]; + n = 0; + foreach (UUID itemID in itemIDList) { + if (m_InstancesDict.TryGetValue (itemID, out a[n])) n ++; + } + m_ObjectInstArray[partUUID] = a; + return a; + } + + public void TraceCalls (string format, params object[] args) + { + if (m_TraceCalls) m_log.DebugFormat (format, args); + } + public void Verbose (string format, params object[] args) + { + if (m_Verbose) m_log.DebugFormat (format, args); + } + + /** + * @brief Manage our threads. + */ + public static Thread StartMyThread (ThreadStart start, string name, ThreadPriority priority) + { + m_log.Debug ("[XMREngine]: starting thread " + name); + Thread thread = new Thread (start); + thread.Name = name; + thread.Priority = priority; + thread.Start (); + + Watchdog.ThreadWatchdogInfo info = new Watchdog.ThreadWatchdogInfo (thread, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS, name); + Watchdog.AddThread (info, name, true); + + return thread; + } + + public static void UpdateMyThread () + { + Watchdog.UpdateThread (); + } + + public static void MyThreadExiting () + { + Watchdog.RemoveThread (true); + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMREvents.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMREvents.cs new file mode 100644 index 0000000..f6c2d73 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMREvents.cs @@ -0,0 +1,369 @@ +/* + * 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 System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Interfaces; +using log4net; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + /// + /// Prepares events so they can be directly executed upon a script by EventQueueManager, then queues it. + /// + public partial class XMREngine + { + public static readonly object[] zeroObjectArray = new object[0]; + public static readonly object[] oneObjectArrayOne = new object[1] { 1 }; + + private void InitEvents() + { + m_log.Info("[XMREngine] Hooking up to server events"); + this.World.EventManager.OnAttach += attach; + this.World.EventManager.OnObjectGrab += touch_start; + this.World.EventManager.OnObjectGrabbing += touch; + this.World.EventManager.OnObjectDeGrab += touch_end; + this.World.EventManager.OnScriptChangedEvent += changed; + this.World.EventManager.OnScriptAtTargetEvent += at_target; + this.World.EventManager.OnScriptNotAtTargetEvent += not_at_target; + this.World.EventManager.OnScriptAtRotTargetEvent += at_rot_target; + this.World.EventManager.OnScriptNotAtRotTargetEvent += not_at_rot_target; + this.World.EventManager.OnScriptMovingStartEvent += moving_start; + this.World.EventManager.OnScriptMovingEndEvent += moving_end; + this.World.EventManager.OnScriptControlEvent += control; + this.World.EventManager.OnScriptColliderStart += collision_start; + this.World.EventManager.OnScriptColliding += collision; + this.World.EventManager.OnScriptCollidingEnd += collision_end; + this.World.EventManager.OnScriptLandColliderStart += land_collision_start; + this.World.EventManager.OnScriptLandColliding += land_collision; + this.World.EventManager.OnScriptLandColliderEnd += land_collision_end; + IMoneyModule money=this.World.RequestModuleInterface(); + if (money != null) + { + money.OnObjectPaid+=HandleObjectPaid; + } + } + + /// + /// When an object gets paid by an avatar and generates the paid event, + /// this will pipe it to the script engine + /// + /// Object ID that got paid + /// Agent Id that did the paying + /// Amount paid + private void HandleObjectPaid(UUID objectID, UUID agentID, + int amount) + { + // Add to queue for all scripts in ObjectID object + DetectParams[] det = new DetectParams[1]; + det[0] = new DetectParams(); + det[0].Key = agentID; + det[0].Populate(this.World); + + // Since this is an event from a shared module, all scenes will + // get it. But only one has the object in question. The others + // just ignore it. + // + SceneObjectPart part = + this.World.GetSceneObjectPart(objectID); + + if (part == null) + return; + + if ((part.ScriptEvents & scriptEvents.money) == 0) + part = part.ParentGroup.RootPart; + + Verbose ("Paid: " + objectID + " from " + agentID + ", amount " + amount); + + if (part != null) + { + money(part.LocalId, agentID, amount, det); + } + } + + /// + /// Handles piping the proper stuff to The script engine for touching + /// Including DetectedParams + /// + /// + /// + /// + /// + /// + public void touch_start(uint localID, uint originalID, Vector3 offsetPos, + IClientAPI remoteClient, SurfaceTouchEventArgs surfaceArgs) + { + touches(localID, originalID, offsetPos, remoteClient, surfaceArgs, "touch_start"); + } + + public void touch(uint localID, uint originalID, Vector3 offsetPos, + IClientAPI remoteClient, SurfaceTouchEventArgs surfaceArgs) + { + touches(localID, originalID, offsetPos, remoteClient, surfaceArgs, "touch"); + } + + private static Vector3 zeroVec3 = new Vector3(0,0,0); + public void touch_end(uint localID, uint originalID, IClientAPI remoteClient, + SurfaceTouchEventArgs surfaceArgs) + { + touches(localID, originalID, zeroVec3, remoteClient, surfaceArgs, "touch_end"); + } + + private void touches(uint localID, uint originalID, Vector3 offsetPos, + IClientAPI remoteClient, SurfaceTouchEventArgs surfaceArgs, string eventname) + { + SceneObjectPart part; + if (originalID == 0) { + part = this.World.GetSceneObjectPart(localID); + if (part == null) return; + } else { + part = this.World.GetSceneObjectPart(originalID); + } + + DetectParams det = new DetectParams(); + det.Key = remoteClient.AgentId; + det.Populate(this.World); + det.OffsetPos = new LSL_Vector(offsetPos.X, + offsetPos.Y, + offsetPos.Z); + det.LinkNum = part.LinkNum; + + if (surfaceArgs != null) { + det.SurfaceTouchArgs = surfaceArgs; + } + + // Add to queue for all scripts in ObjectID object + this.PostObjectEvent(localID, new EventParams( + eventname, oneObjectArrayOne, + new DetectParams[] { det })); + } + + public void changed(uint localID, uint change) + { + int ch = (int)change; + // Add to queue for all scripts in localID, Object pass change. + this.PostObjectEvent(localID, new EventParams( + "changed",new object[] { ch }, + zeroDetectParams)); + } + + // state_entry: not processed here + // state_exit: not processed here + + public void money(uint localID, UUID agentID, int amount, DetectParams[] det) + { + this.PostObjectEvent(localID, new EventParams( + "money", new object[] { + agentID.ToString(), + amount }, + det)); + } + + public void collision_start(uint localID, ColliderArgs col) + { + collisions(localID, col, "collision_start"); + } + + public void collision(uint localID, ColliderArgs col) + { + collisions(localID, col, "collision"); + } + + public void collision_end(uint localID, ColliderArgs col) + { + collisions(localID, col, "collision_end"); + } + + private void collisions(uint localID, ColliderArgs col, string eventname) + { + int dc = col.Colliders.Count; + if (dc > 0) { + DetectParams[] det = new DetectParams[dc]; + int i = 0; + foreach (DetectedObject detobj in col.Colliders) { + DetectParams d = new DetectParams(); + det[i++] = d; + + d.Key = detobj.keyUUID; + d.Populate (this.World); + + /* not done by XEngine... + d.Position = detobj.posVector; + d.Rotation = detobj.rotQuat; + d.Velocity = detobj.velVector; + ... */ + } + + this.PostObjectEvent(localID, new EventParams( + eventname, + new Object[] { dc }, + det)); + } + } + + public void land_collision_start(uint localID, ColliderArgs col) + { + land_collisions(localID, col, "land_collision_start"); + } + + public void land_collision(uint localID, ColliderArgs col) + { + land_collisions(localID, col, "land_collision"); + } + + public void land_collision_end(uint localID, ColliderArgs col) + { + land_collisions(localID, col, "land_collision_end"); + } + + private void land_collisions(uint localID, ColliderArgs col, string eventname) + { + foreach (DetectedObject detobj in col.Colliders) { + LSL_Vector vec = new LSL_Vector(detobj.posVector.X, + detobj.posVector.Y, + detobj.posVector.Z); + EventParams eps = new EventParams(eventname, + new Object[] { vec }, + zeroDetectParams); + this.PostObjectEvent(localID, eps); + } + } + + // timer: not handled here + // listen: not handled here + + public void control(UUID itemID, UUID agentID, uint held, uint change) + { + this.PostScriptEvent(itemID, new EventParams( + "control",new object[] { + agentID.ToString(), + (int)held, + (int)change}, + zeroDetectParams)); + } + + public void email(uint localID, UUID itemID, string timeSent, + string address, string subject, string message, int numLeft) + { + this.PostObjectEvent(localID, new EventParams( + "email",new object[] { + timeSent, + address, + subject, + message, + numLeft}, + zeroDetectParams)); + } + + public void at_target(uint localID, uint handle, Vector3 targetpos, + Vector3 atpos) + { + this.PostObjectEvent(localID, new EventParams( + "at_target", new object[] { + (int)handle, + new LSL_Vector(targetpos.X,targetpos.Y,targetpos.Z), + new LSL_Vector(atpos.X,atpos.Y,atpos.Z) }, + zeroDetectParams)); + } + + public void not_at_target(uint localID) + { + this.PostObjectEvent(localID, new EventParams( + "not_at_target",zeroObjectArray, + zeroDetectParams)); + } + + public void at_rot_target(uint localID, uint handle, OpenMetaverse.Quaternion targetrot, OpenMetaverse.Quaternion atrot) + { + this.PostObjectEvent( + localID, + new EventParams( + "at_rot_target", + new object[] { + new LSL_Integer(handle), + new LSL_Rotation(targetrot.X, targetrot.Y, targetrot.Z, targetrot.W), + new LSL_Rotation(atrot.X, atrot.Y, atrot.Z, atrot.W) + }, + zeroDetectParams + ) + ); + } + + public void not_at_rot_target(uint localID) + { + this.PostObjectEvent(localID, new EventParams( + "not_at_rot_target",zeroObjectArray, + zeroDetectParams)); + } + + // run_time_permissions: not handled here + + public void attach(uint localID, UUID itemID, UUID avatar) + { + this.PostObjectEvent(localID, new EventParams( + "attach",new object[] { + avatar.ToString() }, + zeroDetectParams)); + } + + // dataserver: not handled here + // link_message: not handled here + + public void moving_start(uint localID) + { + this.PostObjectEvent(localID, new EventParams( + "moving_start",zeroObjectArray, + zeroDetectParams)); + } + + public void moving_end(uint localID) + { + this.PostObjectEvent(localID, new EventParams( + "moving_end",zeroObjectArray, + zeroDetectParams)); + } + + // object_rez: not handled here + // remote_data: not handled here + // http_response: not handled here + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRHeapTracker.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRHeapTracker.cs new file mode 100644 index 0000000..c906f21 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRHeapTracker.cs @@ -0,0 +1,172 @@ +/* + * 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 OpenSim.Region.ScriptEngine.XMREngine; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public class HeapTrackerBase { + private int usage; + private XMRInstAbstract instance; + + public HeapTrackerBase (XMRInstAbstract inst) + { + if (inst == null) throw new ArgumentNullException ("inst"); + instance = inst; + } + + ~HeapTrackerBase () + { + usage = instance.UpdateHeapUse (usage, 0); + } + + protected void NewUse (int newuse) + { + usage = instance.UpdateHeapUse (usage, newuse); + } + } + + public class HeapTrackerList : HeapTrackerBase { + private LSL_List value; + + public HeapTrackerList (XMRInstAbstract inst) : base (inst) { } + + public void Pop (LSL_List lis) + { + NewUse (Size (lis)); + value = lis; + } + + public LSL_List Push () + { + return value; + } + + public static int Size (LSL_List lis) + { + return (!typeof (LSL_List).IsValueType && (lis == null)) ? 0 : lis.Size; + } + } + + public class HeapTrackerObject : HeapTrackerBase { + public const int HT_CHAR = 2; + public const int HT_DELE = 8; + public const int HT_DOUB = 8; + public const int HT_SING = 4; + public const int HT_SFLT = 4; + public const int HT_INT = 4; + public const int HT_VEC = HT_DOUB * 3; + public const int HT_ROT = HT_DOUB * 4; + + private object value; + + public HeapTrackerObject (XMRInstAbstract inst) : base (inst) { } + + public void Pop (object obj) + { + NewUse (Size (obj)); + value = obj; + } + + public object Push () + { + return value; + } + + public static int Size (object obj) + { + if (obj == null) return 0; + + if (obj is char) return HT_CHAR; + if (obj is Delegate) return HT_DELE; + if (obj is double) return HT_DOUB; + if (obj is float) return HT_SING; + if (obj is int) return HT_INT; + if (obj is LSL_Float) return HT_SFLT; + if (obj is LSL_Integer) return HT_INT; + if (obj is LSL_List) return ((LSL_List)obj).Size; + if (obj is LSL_Rotation) return HT_ROT; + if (obj is LSL_String) return ((LSL_String)obj).m_string.Length * HT_CHAR; + if (obj is LSL_Vector) return HT_VEC; + if (obj is string) return ((string)obj).Length * HT_CHAR; + if (obj is XMR_Array) return 0; + if (obj is XMRArrayListKey) return ((XMRArrayListKey)obj).Size; + if (obj is XMRSDTypeClObj) return 0; + + if (obj is Array) { + Array ar = (Array)obj; + int len = ar.Length; + if (len == 0) return 0; + Type et = ar.GetType ().GetElementType (); + if (et.IsValueType) return Size (ar.GetValue (0)) * len; + int size = 0; + for (int i = 0; i < len; i ++) { + size += Size (ar.GetValue (i)); + } + return size; + } + + throw new Exception ("unknown size of type " + obj.GetType ().Name); + } + } + + public class HeapTrackerString : HeapTrackerBase { + private string value; + + public HeapTrackerString (XMRInstAbstract inst) : base (inst) { } + + public void Pop (string str) + { + NewUse (Size (str)); + value = str; + } + + public string Push () + { + return value; + } + + public static int Size (string str) + { + return (str == null) ? 0 : str.Length * HeapTrackerObject.HT_CHAR; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstAbstract.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstAbstract.cs new file mode 100644 index 0000000..802eac2 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstAbstract.cs @@ -0,0 +1,2031 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection.Emit; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public class XMRInstArrays { + public XMR_Array[] iarArrays; + public char[] iarChars; + public double[] iarFloats; + public int[] iarIntegers; + public LSL_List[] iarLists; + public object[] iarObjects; + public LSL_Rotation[] iarRotations; + public string[] iarStrings; + public LSL_Vector[] iarVectors; + public XMRSDTypeClObj[] iarSDTClObjs; + public Delegate[][] iarSDTIntfObjs; + + private XMRInstAbstract instance; + private int heapUse; + + private static readonly XMR_Array[] noArrays = new XMR_Array[0]; + private static readonly char[] noChars = new char[0]; + private static readonly double[] noFloats = new double[0]; + private static readonly int[] noIntegers = new int[0]; + private static readonly LSL_List[] noLists = new LSL_List[0]; + private static readonly object[] noObjects = new object[0]; + private static readonly LSL_Rotation[] noRotations = new LSL_Rotation[0]; + private static readonly string[] noStrings = new string[0]; + private static readonly LSL_Vector[] noVectors = new LSL_Vector[0]; + private static readonly XMRSDTypeClObj[] noSDTClObjs = new XMRSDTypeClObj[0]; + private static readonly Delegate[][] noSDTIntfObjs = new Delegate[0][]; + + public XMRInstArrays (XMRInstAbstract inst) + { + instance = inst; + } + + ~XMRInstArrays () + { + heapUse = instance.UpdateHeapUse (heapUse, 0); + } + + public void AllocVarArrays (XMRInstArSizes ars) + { + ClearOldArrays (); + + heapUse = instance.UpdateHeapUse (heapUse, + ars.iasChars * HeapTrackerObject.HT_CHAR + + ars.iasFloats * HeapTrackerObject.HT_SFLT + + ars.iasIntegers * HeapTrackerObject.HT_INT + + ars.iasRotations * HeapTrackerObject.HT_ROT + + ars.iasVectors * HeapTrackerObject.HT_VEC + + ars.iasSDTIntfObjs * HeapTrackerObject.HT_DELE); + + iarArrays = (ars.iasArrays > 0) ? new XMR_Array [ars.iasArrays] : noArrays; + iarChars = (ars.iasChars > 0) ? new char [ars.iasChars] : noChars; + iarFloats = (ars.iasFloats > 0) ? new double [ars.iasFloats] : noFloats; + iarIntegers = (ars.iasIntegers > 0) ? new int [ars.iasIntegers] : noIntegers; + iarLists = (ars.iasLists > 0) ? new LSL_List [ars.iasLists] : noLists; + iarObjects = (ars.iasObjects > 0) ? new object [ars.iasObjects] : noObjects; + iarRotations = (ars.iasRotations > 0) ? new LSL_Rotation [ars.iasRotations] : noRotations; + iarStrings = (ars.iasStrings > 0) ? new string [ars.iasStrings] : noStrings; + iarVectors = (ars.iasVectors > 0) ? new LSL_Vector [ars.iasVectors] : noVectors; + iarSDTClObjs = (ars.iasSDTClObjs > 0) ? new XMRSDTypeClObj[ars.iasSDTClObjs] : noSDTClObjs; + iarSDTIntfObjs = (ars.iasSDTIntfObjs > 0) ? new Delegate [ars.iasSDTIntfObjs][] : noSDTIntfObjs; + } + + /** + * @brief Do not write directly to iarLists[index], rather use this method. + */ + public void PopList (int index, LSL_List lis) + { + LSL_List old = iarLists[index]; + int newheapuse = heapUse + HeapTrackerList.Size (lis) - HeapTrackerList.Size (old); + heapUse = instance.UpdateHeapUse (heapUse, newheapuse); + iarLists[index] = lis; + } + + /** + * @brief Do not write directly to iarObjects[index], rather use this method. + */ + public void PopObject (int index, object obj) + { + object old = iarObjects[index]; + int newheapuse = heapUse + HeapTrackerObject.Size (obj) - HeapTrackerObject.Size (old); + heapUse = instance.UpdateHeapUse (heapUse, newheapuse); + iarObjects[index] = obj; + } + + /** + * @brief Do not write directly to iarStrings[index], rather use this method. + */ + public void PopString (int index, string str) + { + string old = iarStrings[index]; + int newheapuse = heapUse + HeapTrackerString.Size (str) - HeapTrackerString.Size (old); + heapUse = instance.UpdateHeapUse (heapUse, newheapuse); + iarStrings[index] = str; + } + + /** + * @brief Write all arrays out to a file. + */ + public delegate void Sender (object value); + public void SendArrays (Sender sender) + { + sender (iarArrays); + sender (iarChars); + sender (iarFloats); + sender (iarIntegers); + sender (iarLists); + sender (iarObjects); + sender (iarRotations); + sender (iarStrings); + sender (iarVectors); + sender (iarSDTClObjs); + sender (iarSDTIntfObjs); + } + + /** + * @brief Read all arrays in from a file. + */ + public delegate object Recver (); + public void RecvArrays (Recver recver) + { + ClearOldArrays (); + + iarArrays = (XMR_Array[]) recver (); + char[] chrs = (char[]) recver (); + double[] flts = (double[]) recver (); + int[] ints = (int[]) recver (); + LSL_List[] liss = (LSL_List[]) recver (); + object[] objs = (object[]) recver (); + LSL_Rotation[] rots = (LSL_Rotation[]) recver (); + string[] strs = (string[]) recver (); + LSL_Vector[] vecs = (LSL_Vector[]) recver (); + iarSDTClObjs = (XMRSDTypeClObj[]) recver (); + Delegate[][] dels = (Delegate[][]) recver (); + + int newheapuse = heapUse; + + // value types simply are the size of the value * number of values + newheapuse += chrs.Length * HeapTrackerObject.HT_CHAR; + newheapuse += flts.Length * HeapTrackerObject.HT_SFLT; + newheapuse += ints.Length * HeapTrackerObject.HT_INT; + newheapuse += rots.Length * HeapTrackerObject.HT_ROT; + newheapuse += vecs.Length * HeapTrackerObject.HT_VEC; + newheapuse += dels.Length * HeapTrackerObject.HT_DELE; + + // lists, objects, strings are the sum of the size of each element + foreach (LSL_List lis in liss) { + newheapuse += HeapTrackerList.Size (lis); + } + foreach (object obj in objs) { + newheapuse += HeapTrackerObject.Size (obj); + } + foreach (string str in strs) { + newheapuse += HeapTrackerString.Size (str); + } + + // others (XMR_Array, XMRSDTypeClObj) keep track of their own heap usage + + // update script heap usage, throwing an exception before finalizing changes + heapUse = instance.UpdateHeapUse (heapUse, newheapuse); + + iarChars = chrs; + iarFloats = flts; + iarIntegers = ints; + iarLists = liss; + iarObjects = objs; + iarRotations = rots; + iarStrings = strs; + iarVectors = vecs; + iarSDTIntfObjs = dels; + } + + private void ClearOldArrays () + { + int newheapuse = heapUse; + + iarArrays = null; + if (iarChars != null) { + newheapuse -= iarChars.Length * HeapTrackerObject.HT_CHAR; + iarChars = null; + } + if (iarFloats != null) { + newheapuse -= iarFloats.Length * HeapTrackerObject.HT_SFLT; + iarFloats = null; + } + if (iarIntegers != null) { + newheapuse -= iarIntegers.Length * HeapTrackerObject.HT_INT; + iarIntegers = null; + } + if (iarLists != null) { + foreach (LSL_List lis in iarLists) { + newheapuse -= HeapTrackerList.Size (lis); + } + iarLists = null; + } + if (iarObjects != null) { + foreach (object obj in iarObjects) { + newheapuse -= HeapTrackerObject.Size (obj); + } + iarObjects = null; + } + if (iarRotations != null) { + newheapuse -= iarRotations.Length * HeapTrackerObject.HT_ROT; + iarRotations = null; + } + if (iarStrings != null) { + foreach (string str in iarStrings) { + newheapuse -= HeapTrackerString.Size (str); + } + iarStrings = null; + } + if (iarVectors != null) { + newheapuse -= iarVectors.Length * HeapTrackerObject.HT_VEC; + iarVectors = null; + } + iarSDTClObjs = null; + if (iarSDTIntfObjs != null) { + newheapuse -= iarSDTIntfObjs.Length * HeapTrackerObject.HT_DELE; + iarSDTIntfObjs = null; + } + + heapUse = instance.UpdateHeapUse (heapUse, newheapuse); + } + } + + public class XMRInstArSizes { + public int iasArrays; + public int iasChars; + public int iasFloats; + public int iasIntegers; + public int iasLists; + public int iasObjects; + public int iasRotations; + public int iasStrings; + public int iasVectors; + public int iasSDTClObjs; + public int iasSDTIntfObjs; + + public void WriteAsmFile (TextWriter asmFileWriter, string label) + { + asmFileWriter.WriteLine (" {0}Arrays {1}", label, iasArrays); + asmFileWriter.WriteLine (" {0}Chars {1}", label, iasChars); + asmFileWriter.WriteLine (" {0}Floats {1}", label, iasFloats); + asmFileWriter.WriteLine (" {0}Integers {1}", label, iasIntegers); + asmFileWriter.WriteLine (" {0}Lists {1}", label, iasLists); + asmFileWriter.WriteLine (" {0}Objects {1}", label, iasObjects); + asmFileWriter.WriteLine (" {0}Rotations {1}", label, iasRotations); + asmFileWriter.WriteLine (" {0}Strings {1}", label, iasStrings); + asmFileWriter.WriteLine (" {0}Vectors {1}", label, iasVectors); + asmFileWriter.WriteLine (" {0}SDTClObjs {1}", label, iasSDTClObjs); + asmFileWriter.WriteLine (" {0}SDTIntfObjs {1}", label, iasSDTIntfObjs); + } + public void WriteToFile (BinaryWriter objFileWriter) + { + objFileWriter.Write (iasArrays); + objFileWriter.Write (iasChars); + objFileWriter.Write (iasFloats); + objFileWriter.Write (iasIntegers); + objFileWriter.Write (iasLists); + objFileWriter.Write (iasObjects); + objFileWriter.Write (iasRotations); + objFileWriter.Write (iasStrings); + objFileWriter.Write (iasVectors); + objFileWriter.Write (iasSDTClObjs); + objFileWriter.Write (iasSDTIntfObjs); + } + public void ReadFromFile (BinaryReader objFileReader) + { + iasArrays = objFileReader.ReadInt32 (); + iasChars = objFileReader.ReadInt32 (); + iasFloats = objFileReader.ReadInt32 (); + iasIntegers = objFileReader.ReadInt32 (); + iasLists = objFileReader.ReadInt32 (); + iasObjects = objFileReader.ReadInt32 (); + iasRotations = objFileReader.ReadInt32 (); + iasStrings = objFileReader.ReadInt32 (); + iasVectors = objFileReader.ReadInt32 (); + iasSDTClObjs = objFileReader.ReadInt32 (); + iasSDTIntfObjs = objFileReader.ReadInt32 (); + } + } + + public class XMRStackFrame { + public XMRStackFrame nextSF; + public string funcName; + public int callNo; + public object[] objArray; + } + + /* + * Contains only items required by the stand-alone compiler + * so the compiler doesn't need to pull in all of OpenSim. + * + * Inherit from ScriptBaseClass so we can be used as 'this' + * parameter for backend-API calls, eg llSay(). + */ + public abstract class XMRInstAbstract : ScriptBaseClass + { + public const int CallMode_NORMAL = 0; // when function is called, it proceeds normally + public const int CallMode_SAVE = 1; // StackSaveException() was thrown, push args/locals to stackFrames + public const int CallMode_RESTORE = 2; // when function is called, it pops state from stackFrames + + public bool suspendOnCheckRunHold; // suspend script execution until explicitly set false + public bool suspendOnCheckRunTemp; // suspend script execution for single step only + public int stackLimit; // stack must have at least this many bytes free on entry to functions + + public ScriptObjCode m_ObjCode; // script object code this instance was created from + + public object[] ehArgs; // event handler argument array + public bool doGblInit = true; // default state_entry() needs to initialize global variables + public int stateCode = 0; // state the script is in (0 = 'default') + public int newStateCode = -1; // if >= 0, in the middle of exiting 'stateCode' and entering 'newStateCode' + public ScriptEventCode eventCode = ScriptEventCode.None; + // what event handler is executing (or None if not) + + public int callMode = CallMode_NORMAL; + // to capture stack frames on stackFrames: + // set to CallMode_SAVE just before throwing StackSaveException() + // from within CheckRun() and cleared to CallMode_NORMAL when + // the exception is caught + // to restore stack frames from stackFrames: + // set to CallMode_RESTORE just before calling CallSEH() and + // cleared to CallMode_NORMAL by CheckRun() + public XMRStackFrame stackFrames; // stack frames being saved/restored + + private static readonly char[] justacomma = { ',' }; + + /* + * These arrays hold the global variable values for the script instance. + * The array lengths are determined by the script compilation, + * and are found in ScriptObjCode.glblSizes. + */ + public XMRInstArrays glblVars; + + public XMRInstAbstract () + { + glblVars = new XMRInstArrays (this); + } + + /****************************************************************\ + * Abstract function prototypes. * + * These functions require access to the OpenSim environment. * + \****************************************************************/ + + public abstract void CheckRunWork (); + public abstract void StateChange (); + public abstract int xmrStackLeft (); + + [xmrMethodCallsCheckRunAttribute] // calls CheckRun() + [xmrMethodIsNoisyAttribute] // calls Stub() + public abstract LSL_List xmrEventDequeue (double timeout, int returnMask1, int returnMask2, + int backgroundMask1, int backgroundMask2); + + [xmrMethodIsNoisyAttribute] // calls Stub() + public abstract void xmrEventEnqueue (LSL_List ev); + + [xmrMethodIsNoisyAttribute] // calls Stub() + public abstract LSL_List xmrEventSaveDets (); + + [xmrMethodIsNoisyAttribute] // calls Stub() + public abstract void xmrEventLoadDets (LSL_List dpList); + + [xmrMethodIsNoisyAttribute] // calls Stub() + public abstract void xmrTrapRegionCrossing (int en); + + [xmrMethodIsNoisyAttribute] // calls Stub() + public abstract bool xmrSetObjRegPosRotAsync (LSL_Vector pos, LSL_Rotation rot, int options, int evcode, LSL_List evargs); + + /************************************\ + * Constants available to scripts * + \************************************/ + + public const int XMRSORPRA_FLYACROSS = 0x00000001; + + /**************************************************\ + * Functions what don't require runtime support * + * beyond what the compiler provides. * + \**************************************************/ + + protected int heapLimit; + private int heapUsed; + + public virtual int UpdateHeapUse (int olduse, int newuse) + { + if (newuse <= olduse) { + Interlocked.Add (ref heapUsed, newuse - olduse); + } else { + int newtotal, oldtotal; + do { + oldtotal = Interlocked.Add (ref heapUsed, 0); + newtotal = oldtotal + newuse - olduse; + if (newtotal > heapLimit) { + System.GC.Collect (); + System.GC.WaitForPendingFinalizers (); + oldtotal = Interlocked.Add (ref heapUsed, 0); + newtotal = oldtotal + newuse - olduse; + if (newtotal > heapLimit) { + throw new OutOfHeapException (oldtotal, newtotal, heapLimit); + } + } + } while (Interlocked.CompareExchange (ref heapUsed, newtotal, oldtotal) != oldtotal); + } + + return newuse; + } + + public int xmrHeapLeft () + { + return heapLimit - heapUsed; + } + public int xmrHeapUsed () + { + return heapUsed; + } + + /** + * @brief Call script's event handler function from the very beginning. + * @param instance.stateCode = which state the event is happening in + * @param instance.eventCode = which event is happening in that state + * @returns when event handler has completed or throws an exception + * with instance.eventCode = ScriptEventCode.None + */ + public void CallSEH () + { + ScriptEventHandler seh; + + /* + * CallMode_NORMAL: run event handler from the beginning normally + * CallMode_RESTORE: restore event handler stack from stackFrames + */ + callMode = (stackFrames == null) ? XMRInstAbstract.CallMode_NORMAL : + XMRInstAbstract.CallMode_RESTORE; + + while (true) { + if (this.newStateCode < 0) { + + /* + * Process event given by 'stateCode' and 'eventCode'. + * The event handler should call CheckRun() as often as convenient. + */ + int newState = this.stateCode; + seh = this.m_ObjCode.scriptEventHandlerTable[newState,(int)this.eventCode]; + if (seh != null) { + try { + seh (this); + } catch (ScriptChangeStateException scse) { + newState = scse.newState; + } + } + this.ehArgs = null; // we are done with them and no args for + // exit_state()/enter_state() anyway + + /* + * The usual case is no state change. + * Even a 'state ;' statement has no effect except to exit out. + * It does not execute the state_exit() or state_entry() handlers. + * See http://wiki.secondlife.com/wiki/State + */ + if (newState == this.stateCode) break; + + /* + * Save new state in a more permanent location in case we + * get serialized out while in the state_exit() handler. + */ + this.newStateCode = newState; + } + + /* + * Call old state's state_exit() handler. + */ + this.eventCode = ScriptEventCode.state_exit; + seh = this.m_ObjCode.scriptEventHandlerTable[this.stateCode,(int)ScriptEventCode.state_exit]; + if (seh != null) { + try { + seh (this); + } catch (ScriptChangeStateException scse) { + this.newStateCode = scse.newState; + } + } + + /* + * Switch over to the new state's state_entry() handler. + */ + this.stateCode = this.newStateCode; + this.eventCode = ScriptEventCode.state_entry; + this.newStateCode = -1; + + /* + * Now that the old state can't possibly start any more activity, + * cancel any listening handlers, etc, of the old state. + */ + this.StateChange (); + + /* + * Loop back to execute new state's state_entry() handler. + */ + } + + /* + * Event no longer being processed. + */ + this.eventCode = ScriptEventCode.None; + } + + /** + * @brief For compatibility with old code. + */ + public void CheckRun (int line) + { + CheckRunStack (); + } + + /** + * @brief Called at beginning of complex functions to see if they + * are nested too deep possibly in a recursive loop. + */ + public void CheckRunStack () + { + if (xmrStackLeft () < stackLimit) { + throw new OutOfStackException (); + } + CheckRunQuick (); + } + + /** + * @brief Called in each iteration of a loop to see if running too long. + */ + public void CheckRunQuick () + { + if (suspendOnCheckRunHold || suspendOnCheckRunTemp) { + CheckRunWork (); + } + } + + /** + * @brief Called during CallMode_SAVE to create a stackframe save object that saves + * local variables and calling point within the function. + * @param funcName = name of function whose frame is being saved + * @param callNo = call number (ie, return address) within function to restart at + * @param nSaves = number of variables the function will save + * @returns an object[nSaves] where function can save variables + */ + public object[] CaptureStackFrame (string funcName, int callNo, int nSaves) + { + XMRStackFrame sf = new XMRStackFrame (); + sf.nextSF = stackFrames; + sf.funcName = funcName; + sf.callNo = callNo; + sf.objArray = new object[nSaves]; + stackFrames = sf; + return sf.objArray; + } + + /** + * @brief Called during CallMode_RESTORE to pop a stackframe object to restore + * local variables and calling point within the function. + * @param funcName = name of function whose frame is being restored + * @returns the object[nSaves] where function can retrieve variables + * callNo = as passed to CaptureStackFrame() indicating restart point + */ + public object[] RestoreStackFrame (string funcName, out int callNo) + { + XMRStackFrame sf = stackFrames; + if (sf.funcName != funcName) { + throw new Exception ("frame mismatch " + sf.funcName + " vs " + funcName); + } + callNo = sf.callNo; + stackFrames = sf.nextSF; + return sf.objArray; + } + + /** + * @brief Convert all LSL_Integers in a list to System.Int32s, + * as required by llParcelMediaQuery(). + */ + public static LSL_List FixLLParcelMediaQuery (LSL_List oldlist) + { + object[] oldarray = oldlist.Data; + int len = oldarray.Length; + object[] newarray = new object[len]; + for (int i = 0; i < len; i ++) { + object obj = oldarray[i]; + if (obj is LSL_Integer) obj = (int)(LSL_Integer)obj; + newarray[i] = obj; + } + return new LSL_List (newarray); + } + + /** + * @brief Convert *SOME* LSL_Integers in a list to System.Int32s, + * as required by llParcelMediaCommandList(). + */ + public static LSL_List FixLLParcelMediaCommandList (LSL_List oldlist) + { + object[] oldarray = oldlist.Data; + int len = oldarray.Length; + object[] newarray = new object[len]; + int verbatim = 0; + for (int i = 0; i < len; i ++) { + object obj = oldarray[i]; + if (-- verbatim < 0) { + if (obj is LSL_Integer) obj = (int)(LSL_Integer)obj; + if (obj is int) { + switch ((int)obj) { + case ScriptBaseClass.PARCEL_MEDIA_COMMAND_AUTO_ALIGN: { + // leave next integer as LSL_Integer + verbatim = 1; + break; + } + case ScriptBaseClass.PARCEL_MEDIA_COMMAND_SIZE: { + // leave next two integers as LSL_Integer + verbatim = 2; + break; + } + } + } + } + newarray[i] = obj; + } + return new LSL_List (newarray); + } + + public static int xmrHashCode (int i) + { + return i.GetHashCode (); + } + public static int xmrHashCode (double f) + { + return f.GetHashCode (); + } + public static int xmrHashCode (object o) + { + return o.GetHashCode (); + } + public static int xmrHashCode (string s) + { + return s.GetHashCode (); + } + + public bool xmrSetObjRegPosRotAsync (LSL_Vector pos, LSL_Rotation rot, int evcode, LSL_List evargs) + { + return xmrSetObjRegPosRotAsync (pos, rot, 0, evcode, evargs); + } + + public string xmrTypeName (object o) + { + /* + * Basic types return constant strings of the script-visible type name. + */ + if (o is XMR_Array) return "array"; + if (o is bool) return "bool"; + if (o is char) return "char"; + if (o is Exception) return "exception"; + if (o is double) return "float"; + if (o is float) return "float"; + if (o is LSL_Float) return "float"; + if (o is int) return "integer"; + if (o is LSL_Integer) return "integer"; + if (o is LSL_List) return "list"; + if (o is LSL_Rotation) return "rotation"; + if (o is LSL_String) return "string"; + if (o is string) return "string"; + if (o is LSL_Vector) return "vector"; + + /* + * A script-defined interface is represented as an array of delegates. + * If that is the case, convert it to the object of the script-defined + * class that is implementing the interface. This should let the next + * step get the script-defined type name of the object. + */ + if (o is Delegate[]) { + o = ((Delegate[])o)[0].Target; + } + + /* + * If script-defined class instance, get the script-defined + * type name. + */ + if (o is XMRSDTypeClObj) { + return ((XMRSDTypeClObj)o).sdtcClass.longName.val; + } + + /* + * If it's a delegate, maybe we can look up its script-defined type name. + */ + Type ot = o.GetType (); + if (o is Delegate) { + String os; + if (m_ObjCode.sdDelTypes.TryGetValue (ot, out os)) return os; + } + + /* + * Don't know what it is, get the C#-level type name. + */ + return ot.ToString (); + } + + /** + * @brief Call the current state's event handler. + * @param ev = as returned by xmrEventDequeue saying which event handler to call + * and what argument list to pass to it. The llDetect...() parameters + * are as currently set for the script (use xmrEventLoadDets to set how + * you want them to be different). + */ + public void xmrEventCallHandler (LSL_List ev) + { + object[] data = ev.Data; + int evc = (int)(ev.GetLSLIntegerItem (0).value & 0xFFFFFFFF); + ScriptEventHandler seh = m_ObjCode.scriptEventHandlerTable[stateCode,evc]; + if (seh != null) { + int nargs = data.Length - 1; + object[] args = new object[nargs]; + Array.Copy (data, 1, args, 0, nargs); + + object[] saveEHArgs = this.ehArgs; + ScriptEventCode saveEventCode = this.eventCode; + + this.ehArgs = args; + this.eventCode = (ScriptEventCode)evc; + + seh (this); + + this.ehArgs = saveEHArgs; + this.eventCode = saveEventCode; + } + } + + /** + * @brief Sane substring functions. + */ + public string xmrSubstring (string s, int offset) + { + if (offset >= s.Length) return ""; + return s.Substring (offset); + } + // C# style + public string xmrSubstring (string s, int offset, int length) + { + if (length <= 0) return ""; + if (offset >= s.Length) return ""; + if (length > s.Length - offset) length = s.Length - offset; + return s.Substring (offset, length); + } + // java style + public string xmrJSubstring (string s, int beg, int end) + { + if (end <= beg) return ""; + if (beg >= s.Length) return ""; + if (end > s.Length) end = s.Length; + return s.Substring (beg, end - beg); + } + + /** + * @brief String begins and ends with test. + */ + public bool xmrStringStartsWith (string s, string t) + { + return s.StartsWith (t); + } + public bool xmrStringEndsWith (string s, string t) + { + return s.EndsWith (t); + } + + /** + * @brief [Last]IndexOf with starting position (just like C#) + */ + public int xmrStringIndexOf (string haystack, string needle) + { + return haystack.IndexOf (needle); + } + public int xmrStringIndexOf (string haystack, string needle, int startat) + { + return haystack.IndexOf (needle, startat); + } + public int xmrStringLastIndexOf (string haystack, string needle) + { + return haystack.LastIndexOf (needle); + } + public int xmrStringLastIndexOf (string haystack, string needle, int startat) + { + return haystack.LastIndexOf (needle, startat); + } + + /** + * @brief These conversions throw exceptions if there is anything stinky... + */ + public double xmrString2Float (string s) + { + return double.Parse (s, CultureInfo.InvariantCulture); + } + public int xmrString2Integer (string s) + { + s = s.Trim (); + if (s.StartsWith ("0x") || s.StartsWith ("0X")) { + return int.Parse (s.Substring (2), NumberStyles.HexNumber); + } + return int.Parse (s, CultureInfo.InvariantCulture); + } + public LSL_Rotation xmrString2Rotation (string s) + { + s = s.Trim (); + if (!s.StartsWith ("<") || !s.EndsWith (">")) { + throw new FormatException ("doesn't begin with < and end with >"); + } + s = s.Substring (1, s.Length - 2); + string[] splitup = s.Split (justacomma, 5); + if (splitup.Length != 4) { + throw new FormatException ("doesn't have exactly 3 commas"); + } + double x = double.Parse (splitup[0], CultureInfo.InvariantCulture); + double y = double.Parse (splitup[1], CultureInfo.InvariantCulture); + double z = double.Parse (splitup[2], CultureInfo.InvariantCulture); + double w = double.Parse (splitup[3], CultureInfo.InvariantCulture); + return new LSL_Rotation (x, y, z, w); + } + public LSL_Vector xmrString2Vector (string s) + { + s = s.Trim (); + if (!s.StartsWith ("<") || !s.EndsWith (">")) { + throw new FormatException ("doesn't begin with < and end with >"); + } + s = s.Substring (1, s.Length - 2); + string[] splitup = s.Split (justacomma, 4); + if (splitup.Length != 3) { + throw new FormatException ("doesn't have exactly 2 commas"); + } + double x = double.Parse (splitup[0], CultureInfo.InvariantCulture); + double y = double.Parse (splitup[1], CultureInfo.InvariantCulture); + double z = double.Parse (splitup[2], CultureInfo.InvariantCulture); + return new LSL_Vector (x, y, z); + } + + /** + * @brief Access C#-style formatted numeric conversions. + */ + public string xmrFloat2String (double val, string fmt) + { + return val.ToString (fmt, CultureInfo.InvariantCulture); + } + public string xmrInteger2String (int val, string fmt) + { + return val.ToString (fmt, CultureInfo.InvariantCulture); + } + public string xmrRotation2String (LSL_Rotation val, string fmt) + { + return "<" + val.x.ToString (fmt, CultureInfo.InvariantCulture) + "," + + val.y.ToString (fmt, CultureInfo.InvariantCulture) + "," + + val.z.ToString (fmt, CultureInfo.InvariantCulture) + "," + + val.s.ToString (fmt, CultureInfo.InvariantCulture) + ">"; + } + public string xmrVector2String (LSL_Vector val, string fmt) + { + return "<" + val.x.ToString (fmt, CultureInfo.InvariantCulture) + "," + + val.y.ToString (fmt, CultureInfo.InvariantCulture) + "," + + val.z.ToString (fmt, CultureInfo.InvariantCulture) + ">"; + } + + /** + * @brief Get a delegate for a script-defined function. + * @param name = name of the function including arg types, eg, + * "Verify(array,list,string)" + * @param sig = script-defined type name + * @param targ = function's 'this' pointer or null if static + * @returns delegate for the script-defined function + */ + public Delegate GetScriptMethodDelegate (string name, string sig, object targ) + { + DynamicMethod dm = m_ObjCode.dynamicMethods[name]; + TokenDeclSDTypeDelegate dt = (TokenDeclSDTypeDelegate)m_ObjCode.sdObjTypesName[sig]; + return dm.CreateDelegate (dt.GetSysType (), targ); + } + + /** + * @brief Try to cast the thrown object to the given script-defined type. + * @param thrown = what object was thrown + * @param inst = what script instance we are running in + * @param sdtypeindex = script-defined type to try to cast it to + * @returns null: thrown is not castable to sdtypename + * else: an object casted to sdtypename + */ + public static object XMRSDTypeCatchTryCastToSDType (object thrown, XMRInstAbstract inst, int sdtypeindex) + { + TokenDeclSDType sdType = inst.m_ObjCode.sdObjTypesIndx[sdtypeindex]; + + /* + * If it is a script-defined interface object, convert to the original XMRSDTypeClObj. + */ + if (thrown is Delegate[]) { + thrown = ((Delegate[])thrown)[0].Target; + } + + /* + * If it is a script-defined delegate object, make sure it is an instance of the expected type. + */ + if (thrown is Delegate) { + Type ot = thrown.GetType (); + Type tt = sdType.GetSysType (); + return (ot == tt) ? thrown : null; + } + + /* + * If it is a script-defined class object, make sure it is an instance of the expected class. + */ + if (thrown is XMRSDTypeClObj) { + + /* + * Step from the object's actual class rootward. + * If we find the requested class along the way, the cast is valid. + * If we run off the end of the root, the cast is not valid. + */ + for (TokenDeclSDTypeClass ac = ((XMRSDTypeClObj)thrown).sdtcClass; ac != null; ac = ac.extends) { + if (ac == sdType) return thrown; + } + } + + /* + * Don't know what it is, assume it is not what caller wants. + */ + return null; + } + + /** + * @brief Allocate and access fixed-dimension arrays. + */ + public static object xmrFixedArrayAllocC (int len) { return new char[len]; } + public static object xmrFixedArrayAllocF (int len) { return new double[len]; } + public static object xmrFixedArrayAllocI (int len) { return new int[len]; } + public static object xmrFixedArrayAllocO (int len) { return new object[len]; } + + public static char xmrFixedArrayGetC (object arr, int idx) { return ( (char[])arr)[idx]; } + public static double xmrFixedArrayGetF (object arr, int idx) { return ((double[])arr)[idx]; } + public static int xmrFixedArrayGetI (object arr, int idx) { return ( (int[])arr)[idx]; } + public static object xmrFixedArrayGetO (object arr, int idx) { return ((object[])arr)[idx]; } + + public static void xmrFixedArraySetC (object arr, int idx, char val) { ((char[])arr)[idx] = val; } + public static void xmrFixedArraySetF (object arr, int idx, double val) { ((double[])arr)[idx] = val; } + public static void xmrFixedArraySetI (object arr, int idx, int val) { ((int[])arr)[idx] = val; } + public static void xmrFixedArraySetO (object arr, int idx, object val) { ((object[])arr)[idx] = val; } + + /** + * @brief Copy from one script-defined array to another. + * @param srcobj = source script-defined array class object pointer + * @param srcstart = offset in source array to start copying from + * @param dstobj = destination script-defined array class object pointer + * @param dststart = offset in destination arry to start copying to + * @param count = number of elements to copy + */ + public static void xmrArrayCopy (object srcobj, int srcstart, object dstobj, int dststart, int count) + { + /* + * The script writer should only pass us script-defined class objects. + * Throw exception otherwise. + */ + XMRSDTypeClObj srcsdt = (XMRSDTypeClObj)srcobj; + XMRSDTypeClObj dstsdt = (XMRSDTypeClObj)dstobj; + + /* + * Get the script-visible type name of the arrays, brackets and all. + */ + string srctypename = srcsdt.sdtcClass.longName.val; + string dsttypename = dstsdt.sdtcClass.longName.val; + + /* + * The part before the first '[' of each should match exactly, + * meaning the basic data type (eg, float, List) is the same. + * And there must be a '[' in each meaning that it is a script-defined array type. + */ + int i = srctypename.IndexOf ('['); + int j = dsttypename.IndexOf ('['); + if ((i < 0) || (j < 0)) throw new InvalidCastException ("non-array passed: " + srctypename + " and/or " + dsttypename); + if ((i != j) || !srctypename.StartsWith (dsttypename.Substring (0, j))) { + throw new ArrayTypeMismatchException (srctypename + " vs " + dsttypename); + } + + /* + * The number of brackets must match exactly. + * This permits copying from something like a float[,][] to something like a float[][]. + * But you cannot copy from a float[][] to a float[] or wisa wersa. + * Counting either '[' or ']' would work equally well. + */ + int srclen = srctypename.Length; + int dstlen = dsttypename.Length; + int srcjags = 0; + int dstjags = 0; + while (++ i < srclen) if (srctypename[i] == ']') srcjags ++; + while (++ j < dstlen) if (dsttypename[j] == ']') dstjags ++; + if (dstjags != srcjags) { + throw new ArrayTypeMismatchException (srctypename + " vs " + dsttypename); + } + + /* + * Perform the copy. + */ + Array srcarray = (Array)srcsdt.instVars.iarObjects[0]; + Array dstarray = (Array)dstsdt.instVars.iarObjects[0]; + Array.Copy (srcarray, srcstart, dstarray, dststart, count); + } + + /** + * @brief Copy from an array to a list. + * @param srcar = the array to copy from + * @param start = where to start in the array + * @param count = number of elements + * @returns the list + */ + public static LSL_List xmrArray2List (object srcar, int start, int count) + { + /* + * Get the script-visible type of the array. + * We only do arrays. + */ + XMRSDTypeClObj array = (XMRSDTypeClObj)srcar; + TokenDeclSDTypeClass sdtClass = array.sdtcClass; + if (sdtClass.arrayOfRank == 0) { + throw new InvalidCastException ("only do arrays not " + sdtClass.longName.val); + } + + /* + * Validate objects they want to put in the list. + * We can't allow anything funky that OpenSim runtime doesn't expect. + */ + Array srcarray = (Array)array.instVars.iarObjects[0]; + object[] output = new object[count]; + for (int i = 0; i < count; i ++) { + object src = srcarray.GetValue (i + start); + if (src == null) throw new NullReferenceException ("null element " + i); + if (src is double) { + output[i] = new LSL_Float ((double)src); + continue; + } + if (src is int) { + output[i] = new LSL_Integer ((int)src); + continue; + } + if (src is LSL_Rotation) { + output[i] = src; + continue; + } + if (src is LSL_Vector) { + output[i] = src; + continue; + } + if (src is string) { + output[i] = new LSL_String ((string)src); + continue; + } + throw new InvalidCastException ("invalid element " + i + " type " + src.GetType ().Name); + } + + /* + * Make a list out of that now immutable array. + */ + return new LSL_List (output); + } + + /** + * @brief Copy from a list to an array. + * @param srclist = list to copy from + * @param srcstart = where to start in the list + * @param dstobj = array to copy to + * @param dststart = where to start in the array + * @param count = number of elements + */ + public static void xmrList2Array (LSL_List srclist, int srcstart, object dstobj, int dststart, int count) + { + /* + * Get the script-visible type of the destination. + * We only do arrays. + */ + XMRSDTypeClObj dstarray = (XMRSDTypeClObj)dstobj; + TokenDeclSDTypeClass sdtClass = dstarray.sdtcClass; + if (sdtClass.arrayOfType == null) { + throw new InvalidCastException ("only do arrays not " + sdtClass.longName.val); + } + + /* + * Copy from the immutable array to the mutable array. + * Strip off any LSL wrappers as the script code doesn't expect any. + */ + object[] srcarr = srclist.Data; + Array dstarr = (Array)dstarray.instVars.iarObjects[0]; + + for (int i = 0; i < count; i ++) { + object obj = srcarr[i+srcstart]; + if (obj is LSL_Float) obj = ((LSL_Float)obj).value; + if (obj is LSL_Integer) obj = ((LSL_Integer)obj).value; + if (obj is LSL_String) obj = ((LSL_String)obj).m_string; + dstarr.SetValue (obj, i + dststart); + } + } + + /** + * @brief Copy from an array of characters to a string. + * @param srcar = the array to copy from + * @param start = where to start in the array + * @param count = number of elements + * @returns the string + */ + public static string xmrChars2String (object srcar, int start, int count) + { + /* + * Make sure they gave us a script-defined array object. + */ + XMRSDTypeClObj array = (XMRSDTypeClObj)srcar; + TokenDeclSDTypeClass sdtClass = array.sdtcClass; + if (sdtClass.arrayOfRank == 0) { + throw new InvalidCastException ("only do arrays not " + sdtClass.longName.val); + } + + /* + * We get a type cast error from mono if they didn't give us a character array. + * But if it is ok, create a string from the requested characters. + */ + char[] srcarray = (char[])array.instVars.iarObjects[0]; + return new string (srcarray, start, count); + } + + /** + * @brief Copy from a string to a character array. + * @param srcstr = string to copy from + * @param srcstart = where to start in the string + * @param dstobj = array to copy to + * @param dststart = where to start in the array + * @param count = number of elements + */ + public static void xmrString2Chars (string srcstr, int srcstart, object dstobj, int dststart, int count) + { + /* + * Make sure they gave us a script-defined array object. + */ + XMRSDTypeClObj dstarray = (XMRSDTypeClObj)dstobj; + TokenDeclSDTypeClass sdtClass = dstarray.sdtcClass; + if (sdtClass.arrayOfType == null) { + throw new InvalidCastException ("only do arrays not " + sdtClass.longName.val); + } + + /* + * We get a type cast error from mono if they didn't give us a character array. + * But if it is ok, copy from the string to the character array. + */ + char[] dstarr = (char[])dstarray.instVars.iarObjects[0]; + for (int i = 0; i < count; i ++) { + dstarr[i+dststart] = srcstr[i+srcstart]; + } + } + + /** + * @brief Implement osParseJSON() so we return an array to the script. + * No coherent example of its use in scripts on web found. + * see http://www.json.org/ for more details on JSON + */ + private static LSL_List nullList = new LSL_List (new object[0]); + public new XMR_Array osParseJSON (string json) + { + XMR_Array dict = new XMR_Array (this); + int idx = ParseJSON (dict, nullList, json, 0); + while (idx < json.Length) { + if (json[idx] > ' ') throw new Exception ("left-over json " + json); + idx ++; + } + return dict; + } + + private static int ParseJSON (XMR_Array dict, LSL_List keys, string json, int idx) + { + char c; + + while ((c = json[idx++]) <= ' ') { } + switch (c) { + + // '{' ':' [ ',' ':' ... ] '}' + case '{': { + do { + string key = ParseJSONString (json, ref idx); + while ((c = json[idx++]) <= ' ') { } + if (c != ':') throw new Exception ("missing : after key"); + idx = ParseJSON (dict, ParseJSONKeyAdd (keys, key), json, idx); + while ((c = json[idx++]) <= ' ') { } + } while (c == ','); + if (c != '}') throw new Exception ("missing , or } after value"); + break; + } + + // '[' [ ',' ... ] ']' + case '[': { + int index = 0; + do { + object key = index ++; + idx = ParseJSON (dict, ParseJSONKeyAdd (keys, key), json, idx); + while ((c = json[idx++]) <= ' ') { } + } while (c == ','); + if (c != ']') throw new Exception ("missing , or ] after value"); + break; + } + + // '"''"' + case '"': { + -- idx; + string val = ParseJSONString (json, ref idx); + dict.SetByKey (keys, val); + break; + } + + // true false null + case 't': { + if (json.Substring (idx, 3) != "rue") throw new Exception ("bad true in json"); + idx += 3; + dict.SetByKey (keys, 1); + break; + } + + case 'f': { + if (json.Substring (idx, 4) != "alse") throw new Exception ("bad false in json"); + idx += 4; + dict.SetByKey (keys, 0); + break; + } + + case 'n': { + if (json.Substring (idx, 3) != "ull") throw new Exception ("bad null in json"); + idx += 3; + dict.SetByKey (keys, null); + break; + } + + // otherwise assume it's a number + default: { + -- idx; + object val = ParseJSONNumber (json, ref idx); + dict.SetByKey (keys, val); + break; + } + } + + return idx; + } + + // Given the key for a whole array, create a key for a given element of the array + private static LSL_List ParseJSONKeyAdd (LSL_List oldkeys, object key) + { + int oldkeyslen = oldkeys.Length; + object[] array = oldkeys.Data; + Array.Resize (ref array, oldkeyslen + 1); + array[oldkeyslen] = key; + return new LSL_List (array); + } + + // Parse out a JSON string + private static string ParseJSONString (string json, ref int idx) + { + char c; + + while ((c = json[idx++]) <= ' ') { } + if (c != '"') throw new Exception ("bad start of json string"); + + StringBuilder sb = new StringBuilder (); + while ((c = json[idx++]) != '"') { + if (c == '\\') { + c = json[idx++]; + switch (c) { + case 'b': { + c = '\b'; + break; + } + case 'f': { + c = '\f'; + break; + } + case 'n': { + c = '\n'; + break; + } + case 'r': { + c = '\r'; + break; + } + case 't': { + c = '\t'; + break; + } + case 'u': { + c = (char) Int32.Parse (json.Substring (idx, 4), + System.Globalization.NumberStyles.HexNumber); + idx += 4; + break; + } + default: break; + } + } + sb.Append (c); + } + return sb.ToString (); + } + + // Parse out a JSON number + private static object ParseJSONNumber (string json, ref int idx) + { + char c; + + while ((c = json[idx++]) <= ' ') { } + + bool expneg = false; + bool isneg = false; + int decpt = -1; + int expon = 0; + int ival = 0; + double dval = 0; + + if (c == '-') { + isneg = true; + c = json[idx++]; + } + if ((c < '0') || (c > '9')) { + throw new Exception ("bad json number"); + } + while ((c >= '0') && (c <= '9')) { + dval *= 10; + ival *= 10; + dval += c - '0'; + ival += c - '0'; + c = '\0'; + if (idx < json.Length) c = json[idx++]; + } + if (c == '.') { + decpt = 0; + c = '\0'; + if (idx < json.Length) c = json[idx++]; + while ((c >= '0') && (c <= '9')) { + dval *= 10; + dval += c - '0'; + decpt ++; + c = '\0'; + if (idx < json.Length) c = json[idx++]; + } + } + if ((c == 'e') || (c == 'E')) { + if (decpt < 0) decpt = 0; + c = json[idx++]; + if (c == '-') expneg = true; + if ((c == '-') || (c == '+')) c = json[idx++]; + while ((c >= '0') && (c <= '9')) { + expon *= 10; + expon += c - '0'; + c = '\0'; + if (idx < json.Length) c = json[idx++]; + } + if (expneg) expon = -expon; + } + + if (c != 0) -- idx; + if (decpt < 0) { + if (isneg) ival = -ival; + return ival; + } else { + if (isneg) dval = -dval; + dval *= Math.Pow (10, expon - decpt); + return dval; + } + } + + /** + * @brief Exception-related runtime calls. + */ + // Return exception message (no type information just the message) + public static string xmrExceptionMessage (Exception ex) + { + return ex.Message; + } + + // Return stack trace (no type or message, just stack trace lines: at ... \n) + public string xmrExceptionStackTrace (Exception ex) + { + return XMRExceptionStackString (ex); + } + + // Return value thrown by a throw statement + public static object xmrExceptionThrownValue (Exception ex) + { + return ((ScriptThrownException)ex).thrown; + } + + // Return exception's short type name, eg, NullReferenceException, ScriptThrownException, etc. + public static string xmrExceptionTypeName (Exception ex) + { + return ex.GetType ().Name; + } + + // internal use only: converts any IL addresses in script-defined methods to source location equivalent + // at (wrapper dynamic-method) object.__seh_0_30_default_state_entry (OpenSim.Region.ScriptEngine.XMREngine.XMRInstAbstract) + public string XMRExceptionStackString (Exception ex) + { + string st = ex.StackTrace; + StringBuilder sb = new StringBuilder (); + int wrapDynMethObj = 0; + int leftOffAt = 0; + while ((wrapDynMethObj = st.IndexOf ("(wrapper dynamic-method) System.Object:", ++ wrapDynMethObj)) >= 0) { + try { + int begFuncName = wrapDynMethObj + 39; + int endFuncName = st.IndexOf (" (", begFuncName); + string funcName = st.Substring (begFuncName, endFuncName - begFuncName); + KeyValuePair[] srcLocs = m_ObjCode.scriptSrcLocss[funcName]; + + int il0xPrefix = st.IndexOf (" [0x", endFuncName); + int begILHex = il0xPrefix + 4; + int endILHex = st.IndexOf (']', begILHex); + string ilHex = st.Substring (begILHex, endILHex - begILHex); + int offset = Int32.Parse (ilHex, System.Globalization.NumberStyles.HexNumber); + + int srcLocIdx; + int srcLocLen = srcLocs.Length; + for (srcLocIdx = 0; ++ srcLocIdx < srcLocLen;) { + if (offset < srcLocs[srcLocIdx].Key) break; + } + ScriptSrcLoc srcLoc = srcLocs[--srcLocIdx].Value; + + sb.Append (st.Substring (leftOffAt, wrapDynMethObj - leftOffAt)); + sb.Append (st.Substring (begFuncName, endFuncName - begFuncName)); + sb.Append (" <"); + sb.Append (srcLoc.file); + sb.Append ('('); + sb.Append (srcLoc.line); + sb.Append (','); + sb.Append (srcLoc.posn); + sb.Append (")>"); + + leftOffAt = ++ endILHex; + } catch { + } + } + sb.Append (st.Substring (leftOffAt)); + return sb.ToString (); + } + + /** + * @brief List fonts available. + */ + public LSL_List xmrFontsAvailable () + { + System.Drawing.FontFamily[] families = System.Drawing.FontFamily.Families; + object[] output = new object[families.Length]; + for (int i = 0; i < families.Length; i ++) { + output[i] = new LSL_String (families[i].Name); + } + return new LSL_List (output); + } + + /************************\ + * Used by decompiler * + \************************/ + + public bool xmrRotationToBool (LSL_Rotation x) { return TypeCast.RotationToBool (x); } + public bool xmrStringToBool (string x) { return TypeCast.StringToBool (x); } + public bool xmrVectorToBool (LSL_Vector x) { return TypeCast.VectorToBool (x); } + public bool xmrKeyToBool (string x) { return TypeCast.KeyToBool (x); } + public bool xmrListToBool (LSL_List x) { return TypeCast.ListToBool (x); } + + public int xmrStringCompare (string x, string y) { return string.Compare (x, y); } + + /** + * @brief types of data we serialize + */ + private enum Ser : byte { + NULL, + EVENTCODE, + LSLFLOAT, + LSLINT, + LSLKEY, + LSLLIST, + LSLROT, + LSLSTR, + LSLVEC, + SYSARRAY, + SYSDOUB, + SYSFLOAT, + SYSINT, + SYSSTR, + XMRARRAY, + DUPREF, + SYSBOOL, + XMRINST, + DELEGATE, + SDTCLOBJ, + SYSCHAR, + SYSERIAL, + THROWNEX + } + + /** + * @brief Write state out to a stream. + * Do not change script state. + */ + public void MigrateOut (BinaryWriter mow) + { + try { + this.migrateOutWriter = mow; + this.migrateOutObjects = new Dictionary (); + this.migrateOutLists = new Dictionary (); + this.SendObjValue (this.ehArgs); + mow.Write (this.doGblInit); + mow.Write (this.stateCode); + mow.Write ((int)this.eventCode); + this.glblVars.SendArrays (this.SendObjValue); + if (this.newStateCode >= 0) { + mow.Write ("**newStateCode**"); + mow.Write (this.newStateCode); + } + for (XMRStackFrame thisSF = this.stackFrames; thisSF != null; thisSF = thisSF.nextSF) { + mow.Write (thisSF.funcName); + mow.Write (thisSF.callNo); + this.SendObjValue (thisSF.objArray); + } + mow.Write (""); + } finally { + this.migrateOutWriter = null; + this.migrateOutObjects = null; + this.migrateOutLists = null; + } + } + + /** + * @brief Write an object to the output stream. + * @param graph = object to send + */ + private BinaryWriter migrateOutWriter; + private Dictionary migrateOutObjects; + private Dictionary migrateOutLists; + public void SendObjValue (object graph) + { + BinaryWriter mow = this.migrateOutWriter; + + /* + * Value types (including nulls) are always output directly. + */ + if (graph == null) { + mow.Write ((byte)Ser.NULL); + return; + } + if (graph is ScriptEventCode) { + mow.Write ((byte)Ser.EVENTCODE); + mow.Write ((int)graph); + return; + } + if (graph is LSL_Float) { + mow.Write ((byte)Ser.LSLFLOAT); + mow.Write ((double)((LSL_Float)graph).value); + return; + } + if (graph is LSL_Integer) { + mow.Write ((byte)Ser.LSLINT); + mow.Write ((int)((LSL_Integer)graph).value); + return; + } + if (graph is LSL_Key) { + mow.Write ((byte)Ser.LSLKEY); + LSL_Key key = (LSL_Key)graph; + SendObjValue (key.m_string); // m_string can be null + return; + } + if (graph is LSL_Rotation) { + mow.Write ((byte)Ser.LSLROT); + mow.Write ((double)((LSL_Rotation)graph).x); + mow.Write ((double)((LSL_Rotation)graph).y); + mow.Write ((double)((LSL_Rotation)graph).z); + mow.Write ((double)((LSL_Rotation)graph).s); + return; + } + if (graph is LSL_String) { + mow.Write ((byte)Ser.LSLSTR); + LSL_String str = (LSL_String)graph; + SendObjValue (str.m_string); // m_string can be null + return; + } + if (graph is LSL_Vector) { + mow.Write ((byte)Ser.LSLVEC); + mow.Write ((double)((LSL_Vector)graph).x); + mow.Write ((double)((LSL_Vector)graph).y); + mow.Write ((double)((LSL_Vector)graph).z); + return; + } + if (graph is bool) { + mow.Write ((byte)Ser.SYSBOOL); + mow.Write ((bool)graph); + return; + } + if (graph is double) { + mow.Write ((byte)Ser.SYSDOUB); + mow.Write ((double)graph); + return; + } + if (graph is float) { + mow.Write ((byte)Ser.SYSFLOAT); + mow.Write ((float)graph); + return; + } + if (graph is int) { + mow.Write ((byte)Ser.SYSINT); + mow.Write ((int)graph); + return; + } + if (graph is char) { + mow.Write ((byte)Ser.SYSCHAR); + mow.Write ((char)graph); + return; + } + + /* + * Script instance pointer is always just that. + */ + if (graph == this) { + mow.Write ((byte)Ser.XMRINST); + return; + } + + /* + * Convert lists to object type. + * This is compatible with old migration data and also + * two vars pointing to same list won't duplicate it. + */ + if (graph is LSL_List) { + object[] data = ((LSL_List) graph).Data; + ObjLslList oll; + if (!this.migrateOutLists.TryGetValue (data, out oll)) { + oll = new ObjLslList (); + oll.objarray = data; + this.migrateOutLists[data] = oll; + } + graph = oll; + } + + /* + * If this same exact object was already serialized, + * just output an index telling the receiver to use + * that same old object, rather than creating a whole + * new object with the same values. Also this prevents + * self-referencing objects (like arrays) from causing + * an infinite loop. + */ + int ident; + if (this.migrateOutObjects.TryGetValue (graph, out ident)) { + mow.Write ((byte)Ser.DUPREF); + mow.Write (ident); + return; + } + + /* + * Object not seen before, save its address with an unique + * ident number that the receiver can easily regenerate. + */ + ident = this.migrateOutObjects.Count; + this.migrateOutObjects.Add (graph, ident); + + /* + * Now output the object's value(s). + * If the object self-references, the object is alreay entered + * in the dictionary and so the self-reference will just emit + * a DUPREF tag instead of trying to output the whole object + * again. + */ + if (graph is ObjLslList) { + mow.Write ((byte)Ser.LSLLIST); + ObjLslList oll = (ObjLslList) graph; + SendObjValue (oll.objarray); + } else if (graph is XMR_Array) { + mow.Write ((byte)Ser.XMRARRAY); + ((XMR_Array)graph).SendArrayObj (this.SendObjValue); + } else if (graph is Array) { + Array array = (Array)graph; + mow.Write ((byte)Ser.SYSARRAY); + mow.Write (SysType2String (array.GetType ().GetElementType ())); + mow.Write ((int)array.Length); + for (int i = 0; i < array.Length; i ++) { + this.SendObjValue (array.GetValue (i)); + } + } else if (graph is string) { + mow.Write ((byte)Ser.SYSSTR); + mow.Write ((string)graph); + } else if (graph is Delegate) { + Delegate del = (Delegate)graph; + mow.Write ((byte)Ser.DELEGATE); + mow.Write (del.Method.Name); + Type delType = del.GetType (); + foreach (KeyValuePair kvp in m_ObjCode.sdObjTypesName) { + TokenDeclSDType sdt = kvp.Value; + if (sdt is TokenDeclSDTypeDelegate) { + TokenDeclSDTypeDelegate sdtd = (TokenDeclSDTypeDelegate)sdt; + if (sdtd.GetSysType () == delType) { + mow.Write (kvp.Key); + goto found; + } + } + } + throw new Exception ("cant find script-defined delegate for " + del.Method.Name + " type " + del.GetType ()); + found: + SendObjValue (del.Target); + } else if (graph is XMRSDTypeClObj) { + mow.Write ((byte)Ser.SDTCLOBJ); + ((XMRSDTypeClObj)graph).Capture (this.SendObjValue); + } else if (graph is ScriptThrownException) { + MemoryStream memoryStream = new MemoryStream (); + System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter = + new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (); + bformatter.Serialize (memoryStream, graph); + byte[] rawBytes = memoryStream.ToArray (); + mow.Write ((byte)Ser.THROWNEX); + mow.Write ((int)rawBytes.Length); + mow.Write (rawBytes); + SendObjValue (((ScriptThrownException)graph).thrown); + } else { + MemoryStream memoryStream = new MemoryStream (); + System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter = + new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (); + bformatter.Serialize (memoryStream, graph); + byte[] rawBytes = memoryStream.ToArray (); + mow.Write ((byte)Ser.SYSERIAL); + mow.Write ((int)rawBytes.Length); + mow.Write (rawBytes); + } + } + + /** + * @brief Use short strings for known type names. + */ + private static string SysType2String (Type type) + { + if (type.IsArray && (type.GetArrayRank () == 1)) { + string str = KnownSysType2String (type.GetElementType ()); + if (str != null) return str + "[]"; + } else { + string str = KnownSysType2String (type); + if (str != null) return str; + } + return type.ToString (); + } + private static string KnownSysType2String (Type type) + { + if (type == typeof (bool)) return "bo"; + if (type == typeof (char)) return "ch"; + if (type == typeof (Delegate)) return "de"; + if (type == typeof (double)) return "do"; + if (type == typeof (float)) return "fl"; + if (type == typeof (int)) return "in"; + if (type == typeof (LSL_List)) return "li"; + if (type == typeof (object)) return "ob"; + if (type == typeof (LSL_Rotation)) return "ro"; + if (type == typeof (XMRSDTypeClObj)) return "sc"; + if (type == typeof (string)) return "st"; + if (type == typeof (LSL_Vector)) return "ve"; + if (type == typeof (XMR_Array)) return "xa"; + return null; + } + private static Type String2SysType (string str) + { + if (str.EndsWith ("[]")) { + return String2SysType (str.Substring (0, str.Length - 2)).MakeArrayType (); + } + if (str == "bo") return typeof (bool); + if (str == "ch") return typeof (char); + if (str == "de") return typeof (Delegate); + if (str == "do") return typeof (double); + if (str == "fl") return typeof (float); + if (str == "in") return typeof (int); + if (str == "li") return typeof (LSL_List); + if (str == "ob") return typeof (object); + if (str == "ro") return typeof (LSL_Rotation); + if (str == "sc") return typeof (XMRSDTypeClObj); + if (str == "st") return typeof (string); + if (str == "ve") return typeof (LSL_Vector); + if (str == "xa") return typeof (XMR_Array); + return Type.GetType (str, true); + } + + /** + * @brief Read state in from a stream. + */ + public void MigrateIn (BinaryReader mir) + { + try { + this.migrateInReader = mir; + this.migrateInObjects = new Dictionary (); + this.ehArgs = (object[])this.RecvObjValue (); + this.doGblInit = mir.ReadBoolean (); + this.stateCode = mir.ReadInt32 (); + this.eventCode = (ScriptEventCode)mir.ReadInt32 (); + this.newStateCode = -1; + this.glblVars.RecvArrays (this.RecvObjValue); + XMRStackFrame lastSF = null; + string funcName; + while ((funcName = mir.ReadString ()) != "") { + if (funcName == "**newStateCode**") { + this.newStateCode = mir.ReadInt32 (); + continue; + } + XMRStackFrame thisSF = new XMRStackFrame (); + thisSF.funcName = funcName; + thisSF.callNo = mir.ReadInt32 (); + thisSF.objArray = (object[])this.RecvObjValue (); + if (lastSF == null) this.stackFrames = thisSF; + else lastSF.nextSF = thisSF; + lastSF = thisSF; + } + } finally { + this.migrateInReader = null; + this.migrateInObjects = null; + } + } + + /** + * @brief Read a single value from the stream. + * @returns value (boxed as needed) + */ + private BinaryReader migrateInReader; + private Dictionary migrateInObjects; + public object RecvObjValue () + { + BinaryReader mir = this.migrateInReader; + int ident = this.migrateInObjects.Count; + Ser code = (Ser)mir.ReadByte (); + switch (code) { + case Ser.NULL: { + return null; + } + case Ser.EVENTCODE: { + return (ScriptEventCode)mir.ReadInt32 (); + } + case Ser.LSLFLOAT: { + return new LSL_Float (mir.ReadDouble ()); + } + case Ser.LSLINT: { + return new LSL_Integer (mir.ReadInt32 ()); + } + case Ser.LSLKEY: { + return new LSL_Key ((string)RecvObjValue ()); + } + case Ser.LSLLIST: { + this.migrateInObjects.Add (ident, null); // placeholder + object[] data = (object[])RecvObjValue (); // read data, maybe using another index + LSL_List list = new LSL_List (data); // make LSL-level list + this.migrateInObjects[ident] = list; // fill in slot + return list; + } + case Ser.LSLROT: { + double x = mir.ReadDouble (); + double y = mir.ReadDouble (); + double z = mir.ReadDouble (); + double s = mir.ReadDouble (); + return new LSL_Rotation (x, y, z, s); + } + case Ser.LSLSTR: { + return new LSL_String ((string)RecvObjValue ()); + } + case Ser.LSLVEC: { + double x = mir.ReadDouble (); + double y = mir.ReadDouble (); + double z = mir.ReadDouble (); + return new LSL_Vector (x, y, z); + } + case Ser.SYSARRAY: { + Type eletype = String2SysType (mir.ReadString ()); + int length = mir.ReadInt32 (); + Array array = Array.CreateInstance (eletype, length); + this.migrateInObjects.Add (ident, array); + for (int i = 0; i < length; i ++) { + array.SetValue (RecvObjValue (), i); + } + return array; + } + case Ser.SYSBOOL: { + return mir.ReadBoolean (); + } + case Ser.SYSDOUB: { + return mir.ReadDouble (); + } + case Ser.SYSFLOAT: { + return mir.ReadSingle (); + } + case Ser.SYSINT: { + return mir.ReadInt32 (); + } + case Ser.SYSCHAR: { + return mir.ReadChar (); + } + case Ser.SYSSTR: { + string s = mir.ReadString (); + this.migrateInObjects.Add (ident, s); + return s; + } + case Ser.XMRARRAY: { + XMR_Array array = new XMR_Array (this); + this.migrateInObjects.Add (ident, array); + array.RecvArrayObj (this.RecvObjValue); + return array; + } + case Ser.DUPREF: { + ident = mir.ReadInt32 (); + object obj = this.migrateInObjects[ident]; + if (obj is ObjLslList) obj = new LSL_List (((ObjLslList) obj).objarray); + return obj; + } + case Ser.XMRINST: { + return this; + } + case Ser.DELEGATE: { + this.migrateInObjects.Add (ident, null); // placeholder + string name = mir.ReadString (); // function name + string sig = mir.ReadString (); // delegate type + object targ = this.RecvObjValue (); // 'this' object + Delegate del = this.GetScriptMethodDelegate (name, sig, targ); + this.migrateInObjects[ident] = del; // actual value + return del; + } + case Ser.SDTCLOBJ: { + XMRSDTypeClObj clobj = new XMRSDTypeClObj (); + this.migrateInObjects.Add (ident, clobj); + clobj.Restore (this, this.RecvObjValue); + return clobj; + } + case Ser.SYSERIAL: { + int rawLength = mir.ReadInt32 (); + byte[] rawBytes = mir.ReadBytes (rawLength); + MemoryStream memoryStream = new MemoryStream (rawBytes); + System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter = + new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (); + object graph = bformatter.Deserialize (memoryStream); + this.migrateInObjects.Add (ident, graph); + return graph; + } + case Ser.THROWNEX: { + int rawLength = mir.ReadInt32 (); + byte[] rawBytes = mir.ReadBytes (rawLength); + MemoryStream memoryStream = new MemoryStream (rawBytes); + System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bformatter = + new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (); + object graph = bformatter.Deserialize (memoryStream); + this.migrateInObjects.Add (ident, graph); + ((ScriptThrownException)graph).thrown = RecvObjValue (); + return graph; + } + default: throw new Exception ("bad stream code " + code.ToString ()); + } + } + + // wrapper around list object arrays to make sure they are always object types for migration purposes + private class ObjLslList { + public object[] objarray; + } + } + + /** + * @brief Common access to script microthread. + */ + public interface IScriptUThread : IDisposable + { + Exception ResumeEx (); // called by macrothread to resume execution at most recent Hiber() + Exception StartEx (); // called by macrothread to start execution at CallSEH() + int Active (); // called by macrothread to query state of microthread + int StackLeft (); // called by microthread to query amount of remaining stack space + void Hiber (); // called by microthread to hibernate + } + + // Any xmr...() methods that call CheckRun() must be tagged with this attribute + // so the ScriptCodeGen will know the method is non-trivial. + public class xmrMethodCallsCheckRunAttribute : Attribute { } + + // Any xmr...() methods in xmrengtest that call Stub() must be + // tagged with this attribute so the -builtins option will tell the user that + // they are a stub function. + public class xmrMethodIsNoisyAttribute : Attribute { } + + // Any script callable methods that really return a key not a string should be + // tagged with this attribute so the compiler will know they return type key and + // not type string. + public class xmrMethodReturnsKeyAttribute : Attribute { } + + [SerializableAttribute] + public class OutOfHeapException : Exception { + public OutOfHeapException (int oldtotal, int newtotal, int limit) + : base ("oldtotal=" + oldtotal + ", newtotal=" + newtotal + ", limit=" + limit) + { } + } + + [SerializableAttribute] + public class OutOfStackException : Exception { } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstBackend.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstBackend.cs new file mode 100644 index 0000000..acf1e66 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstBackend.cs @@ -0,0 +1,644 @@ +/* + * 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 System; +using System.Threading; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Remoting.Lifetime; +using System.Security.Policy; +using System.IO; +using System.Xml; +using System.Text; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Region.ScriptEngine.XMREngine; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Scripting; +using OpenSim.Region.Framework.Interfaces; +using log4net; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + /****************************************************\ + * This file contains routines called by scripts. * + \****************************************************/ + + public class XMRLSL_Api : LSL_Api + { + public AsyncCommandManager acm; + private XMRInstance inst; + + public void InitXMRLSLApi(XMRInstance i) + { + acm = AsyncCommands; + inst = i; + } + + protected override void ScriptSleep(int ms) + { + inst.Sleep(ms); + } + + public override void llSleep(double sec) + { + inst.Sleep((int)(sec * 1000.0)); + } + + public override void llDie() + { + inst.Die(); + } + + /** + * @brief Seat avatar on prim. + * @param owner = true: owner of prim script is running in + * false: avatar that has given ANIMATION permission on the prim + * @returns 0: successful + * -1: no permission to animate + * -2: no av granted perms + * -3: av not in region + */ + public int xmrSeatAvatar (bool owner) + { + // Get avatar to be seated and make sure they have given us ANIMATION permission + + UUID avuuid; + if (owner) { + avuuid = inst.m_Part.OwnerID; + } else { + if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TRIGGER_ANIMATION) == 0) { + return -1; + } + avuuid = m_item.PermsGranter; + } + if (avuuid == UUID.Zero) { + return -2; + } + + ScenePresence presence = World.GetScenePresence (avuuid); + if (presence == null) { + return -3; + } + + // remoteClient = not used by ScenePresence.HandleAgentRequestSit() + // agentID = not used by ScenePresence.HandleAgentRequestSit() + // targetID = UUID of prim to sit on + // offset = offset of sitting position + + presence.HandleAgentRequestSit (null, UUID.Zero, m_host.UUID, OpenMetaverse.Vector3.Zero); + return 0; + } + + /** + * @brief llTeleportAgent() is broken in that if you pass it a landmark, + * it still subjects the position to spawn points, as it always + * calls RequestTeleportLocation() with TeleportFlags.ViaLocation. + * See llTeleportAgent() and CheckAndAdjustTelehub(). + * + * @param agent = what agent to teleport + * @param landmark = inventory name or UUID of a landmark object + * @param lookat = looking direction after teleport + */ + public void xmrTeleportAgent2Landmark (string agent, string landmark, LSL_Vector lookat) + { + // find out about agent to be teleported + UUID agentId; + if (!UUID.TryParse (agent, out agentId)) throw new ApplicationException ("bad agent uuid"); + + ScenePresence presence = World.GetScenePresence (agentId); + if (presence == null) throw new ApplicationException ("agent not present in scene"); + if (presence.IsNPC) throw new ApplicationException ("agent is an NPC"); + if (presence.IsGod) throw new ApplicationException ("agent is a god"); + + // prim must be owned by land owner or prim must be attached to agent + if (m_host.ParentGroup.AttachmentPoint == 0) { + if (m_host.OwnerID != World.LandChannel.GetLandObject (presence.AbsolutePosition).LandData.OwnerID) { + throw new ApplicationException ("prim not owned by land's owner"); + } + } else { + if (m_host.OwnerID != presence.UUID) throw new ApplicationException ("prim not attached to agent"); + } + + // find landmark in inventory or by UUID + UUID assetID = ScriptUtils.GetAssetIdFromKeyOrItemName (m_host, landmark); + if (assetID == UUID.Zero) throw new ApplicationException ("no such landmark"); + + // read it in and make sure it is a landmark + AssetBase lma = World.AssetService.Get (assetID.ToString ()); + if ((lma == null) || (lma.Type != (sbyte)AssetType.Landmark)) throw new ApplicationException ("not a landmark"); + + // parse the record + AssetLandmark lm = new AssetLandmark (lma); + + // the regionhandle (based on region's world X,Y) might be out of date + // re-read the handle so we can pass it to RequestTeleportLocation() + var region = World.GridService.GetRegionByUUID (World.RegionInfo.ScopeID, lm.RegionID); + if (region == null) throw new ApplicationException ("no such region"); + + // finally ready to teleport + World.RequestTeleportLocation (presence.ControllingClient, + region.RegionHandle, + lm.Position, + lookat, + (uint)TeleportFlags.ViaLandmark); + } + + /** + * @brief Allow any member of group given by config SetParcelMusicURLGroup to set music URL. + * Code modelled after llSetParcelMusicURL(). + * @param newurl = new URL to set (or "" to leave it alone) + * @returns previous URL string + */ + public string xmrSetParcelMusicURLGroup (string newurl) + { + string groupname = m_ScriptEngine.Config.GetString ("SetParcelMusicURLGroup", ""); + if (groupname == "") throw new ApplicationException ("no SetParcelMusicURLGroup config param set"); + + IGroupsModule igm = World.RequestModuleInterface (); + if (igm == null) throw new ApplicationException ("no GroupsModule loaded"); + + GroupRecord grouprec = igm.GetGroupRecord (groupname); + if (grouprec == null) throw new ApplicationException ("no such group " + groupname); + + GroupMembershipData gmd = igm.GetMembershipData (grouprec.GroupID, m_host.OwnerID); + if (gmd == null) throw new ApplicationException ("not a member of group " + groupname); + + ILandObject land = World.LandChannel.GetLandObject (m_host.AbsolutePosition); + if (land == null) throw new ApplicationException ("no land at " + m_host.AbsolutePosition.ToString ()); + string oldurl = land.GetMusicUrl (); + if (oldurl == null) oldurl = ""; + if ((newurl != null) && (newurl != "")) land.SetMusicUrl (newurl); + return oldurl; + } + } + + public partial class XMRInstance + { + /** + * @brief The script is calling llReset(). + * We throw an exception to unwind the script out to its main + * causing all the finally's to execute and it will also set + * eventCode = None to indicate event handler has completed. + */ + public void ApiReset() + { + ClearQueueExceptLinkMessages(); + throw new ScriptResetException(); + } + + /** + * @brief The script is calling one of the llDetected...(int number) + * functions. Return corresponding DetectParams pointer. + */ + public DetectParams GetDetectParams(int number) + { + DetectParams dp = null; + if ((number >= 0) && (m_DetectParams != null) && (number < m_DetectParams.Length)) { + dp = m_DetectParams[number]; + } + return dp; + } + + /** + * @brief Script is calling llDie, so flag the run loop to delete script + * once we are off the microthread stack, and throw an exception + * to unwind the stack asap. + */ + public void Die() + { + // llDie doesn't work in attachments! + if (m_Part.ParentGroup.IsAttachment || m_DetachQuantum > 0) + return; + + throw new ScriptDieException(); + } + + /** + * @brief Called by script to sleep for the given number of milliseconds. + */ + public void Sleep(int ms) + { + lock (m_QueueLock) { + + /* + * Say how long to sleep. + */ + m_SleepUntil = DateTime.UtcNow + TimeSpan.FromMilliseconds(ms); + + /* + * Don't wake on any events. + */ + m_SleepEventMask1 = 0; + m_SleepEventMask2 = 0; + } + + /* + * The compiler follows all calls to llSleep() with a call to CheckRun(). + * So tell CheckRun() to suspend the microthread. + */ + suspendOnCheckRunTemp = true; + } + + /** + * Block script execution until an event is queued or a timeout is reached. + * @param timeout = maximum number of seconds to wait + * @param returnMask = if event is queued that matches these mask bits, + * the script is woken, that event is dequeued and + * returned to the caller. The event handler is not + * executed. + * @param backgroundMask = if any of these events are queued while waiting, + * execute their event handlers. When any such event + * handler exits, continue waiting for events or the + * timeout. + * @returns empty list: no event was queued that matched returnMask and the timeout was reached + * or a background event handler changed state (eg, via 'state' statement) + * else: list giving parameters of the event: + * [0] = event code (integer) + * [1..n] = call parameters to the event, if any + * Notes: + * 1) Scrips should use XMREVENTMASKn_ symbols for the mask arguments, + * where n is 1 or 2 for mask1 or mask2 arguments. + * The list[0] return argument can be decoded by using XMREVENTCODE_ symbols. + * 2) If all masks are zero, the call ends up acting like llSleep. + * 3) If an event is enabled in both returnMask and backgroundMask, the returnMask bit + * action takes precedence, ie, the event is returned. This allows a simple specification + * of -1 for both backgroundMask arguments to indicate that all events not listed in + * the returnMask argumetns should be handled in the background. + * 4) Any events not listed in either returnMask or backgroundMask arguments will be + * queued for later processing (subject to normal queue limits). + * 5) Background event handlers execute as calls from within xmrEventDequeue, they do + * not execute as separate threads. Thus any background event handlers must return + * before the call to xmrEventDequeue will return. + * 6) If a background event handler changes state (eg, via 'state' statement), the state + * is immediately changed and the script-level xmrEventDequeue call does not return. + * 7) For returned events, the detect parameters are overwritten by the returned event. + * For background events, the detect parameters are saved and restored. + * 8) Scripts must contain dummy event handler definitions for any event types that may + * be returned by xmrEventDequeue, to let the runtime know that the script is capable + * of processing that event type. Otherwise, the event may not be queued to the script. + */ + private static LSL_List emptyList = new LSL_List (new object[0]); + + public override LSL_List xmrEventDequeue (double timeout, int returnMask1, int returnMask2, + int backgroundMask1, int backgroundMask2) + { + DateTime sleepUntil = DateTime.UtcNow + TimeSpan.FromMilliseconds (timeout * 1000.0); + EventParams evt = null; + int callNo, evc2; + int evc1 = 0; + int mask1 = returnMask1 | backgroundMask1; // codes 00..31 + int mask2 = returnMask2 | backgroundMask2; // codes 32..63 + LinkedListNode lln = null; + object[] sv; + ScriptEventCode evc = ScriptEventCode.None; + + callNo = -1; + try { + if (callMode == CallMode_NORMAL) goto findevent; + + /* + * Stack frame is being restored as saved via CheckRun...(). + * Restore necessary values then jump to __call label to resume processing. + */ + sv = RestoreStackFrame ("xmrEventDequeue", out callNo); + sleepUntil = DateTime.Parse ((string)sv[0]); + returnMask1 = (int)sv[1]; + returnMask2 = (int)sv[2]; + mask1 = (int)sv[3]; + mask2 = (int)sv[4]; + switch (callNo) { + case 0: goto __call0; + case 1: { + evc1 = (int)sv[5]; + evc = (ScriptEventCode)(int)sv[6]; + DetectParams[] detprms = ObjArrToDetPrms ((object[])sv[7]); + object[] ehargs = (object[])sv[8]; + evt = new EventParams (evc.ToString (), ehargs, detprms); + goto __call1; + } + } + throw new ScriptBadCallNoException (callNo); + + /* + * Find first event that matches either the return or background masks. + */ + findevent: + Monitor.Enter (m_QueueLock); + for (lln = m_EventQueue.First; lln != null; lln = lln.Next) { + evt = lln.Value; + evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode), evt.EventName); + evc1 = (int)evc; + evc2 = evc1 - 32; + if ((((uint)evc1 < (uint)32) && (((mask1 >> evc1) & 1) != 0)) || + (((uint)evc2 < (uint)32) && (((mask2 >> evc2) & 1) != 0))) goto remfromq; + } + + /* + * Nothing found, sleep while one comes in. + */ + m_SleepUntil = sleepUntil; + m_SleepEventMask1 = mask1; + m_SleepEventMask2 = mask2; + Monitor.Exit (m_QueueLock); + suspendOnCheckRunTemp = true; + callNo = 0; + __call0: + CheckRunQuick (); + goto checktmo; + + /* + * Found one, remove it from queue. + */ + remfromq: + m_EventQueue.Remove (lln); + if ((uint)evc1 < (uint)m_EventCounts.Length) { + m_EventCounts[evc1] --; + } + Monitor.Exit (m_QueueLock); + m_InstEHEvent ++; + + /* + * See if returnable or background event. + */ + if ((((uint)evc1 < (uint)32) && (((returnMask1 >> evc1) & 1) != 0)) || + (((uint)evc2 < (uint)32) && (((returnMask2 >> evc2) & 1) != 0))) { + + /* + * Returnable event, return its parameters in a list. + * Also set the detect parameters to what the event has. + */ + int plen = evt.Params.Length; + object[] plist = new object[plen+1]; + plist[0] = (LSL_Integer)evc1; + for (int i = 0; i < plen;) { + object ob = evt.Params[i]; + if (ob is int) ob = (LSL_Integer)(int)ob; + else if (ob is double) ob = (LSL_Float)(double)ob; + else if (ob is string) ob = (LSL_String)(string)ob; + plist[++i] = ob; + } + m_DetectParams = evt.DetectParams; + return new LSL_List (plist); + } + + /* + * It is a background event, simply call its event handler, + * then check event queue again. + */ + callNo = 1; + __call1: + ScriptEventHandler seh = m_ObjCode.scriptEventHandlerTable[stateCode,evc1]; + if (seh == null) goto checktmo; + + DetectParams[] saveDetParams = this.m_DetectParams; + object[] saveEHArgs = this.ehArgs; + ScriptEventCode saveEventCode = this.eventCode; + + this.m_DetectParams = evt.DetectParams; + this.ehArgs = evt.Params; + this.eventCode = evc; + + try { + seh (this); + } finally { + this.m_DetectParams = saveDetParams; + this.ehArgs = saveEHArgs; + this.eventCode = saveEventCode; + } + + /* + * Keep waiting until we find a returnable event or timeout. + */ + checktmo: + if (DateTime.UtcNow < sleepUntil) goto findevent; + + /* + * We timed out, return an empty list. + */ + return emptyList; + } finally { + if (callMode != CallMode_NORMAL) { + + /* + * Stack frame is being saved by CheckRun...(). + * Save everything we need at the __call labels so we can restore it + * when we need to. + */ + sv = CaptureStackFrame ("xmrEventDequeue", callNo, 9); + sv[0] = sleepUntil.ToString (); // needed at __call0,__call1 + sv[1] = returnMask1; // needed at __call0,__call1 + sv[2] = returnMask2; // needed at __call0,__call1 + sv[3] = mask1; // needed at __call0,__call1 + sv[4] = mask2; // needed at __call0,__call1 + if (callNo == 1) { + sv[5] = evc1; // needed at __call1 + sv[6] = (int)evc; // needed at __call1 + sv[7] = DetPrmsToObjArr (evt.DetectParams); // needed at __call1 + sv[8] = evt.Params; // needed at __call1 + } + } + } + } + + /** + * @brief Enqueue an event + * @param ev = as returned by xmrEventDequeue saying which event type to queue + * and what argument list to pass to it. The llDetect...() parameters + * are as currently set for the script (use xmrEventLoadDets to set how + * you want them to be different). + */ + public override void xmrEventEnqueue (LSL_List ev) + { + object[] data = ev.Data; + ScriptEventCode evc = (ScriptEventCode)ListInt (data[0]); + + int nargs = data.Length - 1; + object[] args = new object[nargs]; + Array.Copy (data, 1, args, 0, nargs); + + PostEvent (new EventParams (evc.ToString (), args, m_DetectParams)); + } + + /** + * @brief Save current detect params into a list + * @returns a list containing current detect param values + */ + private const int saveDPVer = 1; + + public override LSL_List xmrEventSaveDets () + { + object[] obs = DetPrmsToObjArr (m_DetectParams); + return new LSL_List (obs); + } + + private static object[] DetPrmsToObjArr (DetectParams[] dps) + { + int len = dps.Length; + object[] obs = new object[len*16+1]; + int j = 0; + obs[j++] = (LSL_Integer)saveDPVer; + for (int i = 0; i < len; i ++) { + DetectParams dp = dps[i]; + obs[j++] = (LSL_String)dp.Key.ToString(); // UUID + obs[j++] = dp.OffsetPos; // vector + obs[j++] = (LSL_Integer)dp.LinkNum; // integer + obs[j++] = (LSL_String)dp.Group.ToString(); // UUID + obs[j++] = (LSL_String)dp.Name; // string + obs[j++] = (LSL_String)dp.Owner.ToString(); // UUID + obs[j++] = dp.Position; // vector + obs[j++] = dp.Rotation; // rotation + obs[j++] = (LSL_Integer)dp.Type; // integer + obs[j++] = dp.Velocity; // vector + obs[j++] = dp.TouchST; // vector + obs[j++] = dp.TouchNormal; // vector + obs[j++] = dp.TouchBinormal; // vector + obs[j++] = dp.TouchPos; // vector + obs[j++] = dp.TouchUV; // vector + obs[j++] = (LSL_Integer)dp.TouchFace; // integer + } + return obs; + } + + + /** + * @brief Load current detect params from a list + * @param dpList = as returned by xmrEventSaveDets() + */ + public override void xmrEventLoadDets (LSL_List dpList) + { + m_DetectParams = ObjArrToDetPrms (dpList.Data); + } + + private static DetectParams[] ObjArrToDetPrms (object[] objs) + { + int j = 0; + if ((objs.Length % 16 != 1) || (ListInt (objs[j++]) != saveDPVer)) { + throw new Exception ("invalid detect param format"); + } + + int len = objs.Length / 16; + DetectParams[] dps = new DetectParams[len]; + + for (int i = 0; i < len; i ++) { + DetectParams dp = new DetectParams (); + + dp.Key = new UUID (ListStr (objs[j++])); + dp.OffsetPos = (LSL_Vector)objs[j++]; + dp.LinkNum = ListInt (objs[j++]); + dp.Group = new UUID (ListStr (objs[j++])); + dp.Name = ListStr (objs[j++]); + dp.Owner = new UUID (ListStr (objs[j++])); + dp.Position = (LSL_Vector)objs[j++]; + dp.Rotation = (LSL_Rotation)objs[j++]; + dp.Type = ListInt (objs[j++]); + dp.Velocity = (LSL_Vector)objs[j++]; + + SurfaceTouchEventArgs stea = new SurfaceTouchEventArgs (); + + stea.STCoord = LSLVec2OMVec ((LSL_Vector)objs[j++]); + stea.Normal = LSLVec2OMVec ((LSL_Vector)objs[j++]); + stea.Binormal = LSLVec2OMVec ((LSL_Vector)objs[j++]); + stea.Position = LSLVec2OMVec ((LSL_Vector)objs[j++]); + stea.UVCoord = LSLVec2OMVec ((LSL_Vector)objs[j++]); + stea.FaceIndex = ListInt (objs[j++]); + + dp.SurfaceTouchArgs = stea; + + dps[i] = dp; + } + + return dps; + } + + /** + * @brief The script is executing a 'state ;' command. + * Tell outer layers to cancel any event triggers, like llListen(), + * then tell outer layers which events the new state has handlers for. + * We also clear the event queue as per http://wiki.secondlife.com/wiki/State + */ + public override void StateChange() + { + /* + * Cancel any llListen()s etc. + * But llSetTimerEvent() should persist. + */ + object[] timers = m_XMRLSLApi.acm.TimerPlugin.GetSerializationData(m_ItemID); + AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID); + m_XMRLSLApi.acm.TimerPlugin.CreateFromData(m_LocalID, m_ItemID, UUID.Zero, timers); + + /* + * Tell whoever cares which event handlers the new state has. + */ + m_Part.SetScriptEvents(m_ItemID, GetStateEventFlags(stateCode)); + + /* + * Clear out any old events from the queue. + */ + lock (m_QueueLock) { + m_EventQueue.Clear(); + for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0; + } + } + + /** + * @brief Script is calling xmrStackLeft(). + */ + public override int xmrStackLeft () + { + return microthread.StackLeft (); + } + } + + /** + * @brief Thrown by things like llResetScript() to unconditionally + * unwind as script and reset it to the default state_entry + * handler. We don't want script-level try/catch to intercept + * these so scripts can't interfere with the behavior. + */ + public class ScriptResetException : Exception, IXMRUncatchable { } + + /** + * @brief Thrown by things like llDie() to unconditionally unwind as + * script. We don't want script-level try/catch to intercept + * these so scripts can't interfere with the behavior. + */ + public class ScriptDieException : Exception, IXMRUncatchable { } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstCapture.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstCapture.cs new file mode 100644 index 0000000..8950d63 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstCapture.cs @@ -0,0 +1,436 @@ +/* + * 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 System; +using System.Threading; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Remoting.Lifetime; +using System.Security.Policy; +using System.IO; +using System.Xml; +using System.Text; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Region.ScriptEngine.XMREngine; +using OpenSim.Region.Framework.Scenes; +using log4net; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public partial class XMRInstance + { + /********************************************************************************\ + * The only method of interest to outside this module is GetExecutionState() * + * which captures the current state of the script into an XML document. * + * * + * The rest of this module contains support routines for GetExecutionState(). * + \********************************************************************************/ + + /** + * @brief Create an XML element that gives the current state of the script. + * + * globalsandstackdump + * m_Running + * + * + * Updates the .state file while we're at it. + */ + public XmlElement GetExecutionState(XmlDocument doc) + { + /* + * When we're detaching an attachment, we need to wait here. + */ + + // Change this to a 5 second timeout. If things do mess up, + // we don't want to be stuck forever. + // + m_DetachReady.WaitOne (5000, false); + + XmlElement scriptStateN = doc.CreateElement("", "ScriptState", ""); + scriptStateN.SetAttribute("Engine", m_Engine.ScriptEngineName); + scriptStateN.SetAttribute("Asset", m_Item.AssetID.ToString()); + scriptStateN.SetAttribute ("SourceHash", m_ObjCode.sourceHash); + + /* + * Make sure we aren't executing part of the script so it stays + * stable. Setting suspendOnCheckRun tells CheckRun() to suspend + * and return out so RunOne() will release the lock asap. + */ + suspendOnCheckRunHold = true; + lock (m_RunLock) + { + m_RunOnePhase = "GetExecutionState enter"; + CheckRunLockInvariants(true); + + /* + * Get copy of script globals and stack in relocateable form. + */ + MemoryStream snapshotStream = new MemoryStream(); + MigrateOutEventHandler(snapshotStream); + Byte[] snapshotBytes = snapshotStream.ToArray(); + snapshotStream.Close(); + string snapshotString = Convert.ToBase64String(snapshotBytes); + XmlElement snapshotN = doc.CreateElement("", "Snapshot", ""); + snapshotN.AppendChild(doc.CreateTextNode(snapshotString)); + scriptStateN.AppendChild(snapshotN); + m_RunOnePhase = "GetExecutionState B"; CheckRunLockInvariants(true); + + /* + * "Running" says whether or not we are accepting new events. + */ + XmlElement runningN = doc.CreateElement("", "Running", ""); + runningN.AppendChild(doc.CreateTextNode(m_Running.ToString())); + scriptStateN.AppendChild(runningN); + m_RunOnePhase = "GetExecutionState C"; CheckRunLockInvariants(true); + + /* + * "DoGblInit" says whether or not default:state_entry() will init global vars. + */ + XmlElement doGblInitN = doc.CreateElement("", "DoGblInit", ""); + doGblInitN.AppendChild(doc.CreateTextNode(doGblInit.ToString())); + scriptStateN.AppendChild(doGblInitN); + m_RunOnePhase = "GetExecutionState D"; CheckRunLockInvariants(true); + + /* + * More misc data. + */ + XmlNode permissionsN = doc.CreateElement("", "Permissions", ""); + scriptStateN.AppendChild(permissionsN); + + XmlAttribute granterA = doc.CreateAttribute("", "granter", ""); + granterA.Value = m_Item.PermsGranter.ToString(); + permissionsN.Attributes.Append(granterA); + + XmlAttribute maskA = doc.CreateAttribute("", "mask", ""); + maskA.Value = m_Item.PermsMask.ToString(); + permissionsN.Attributes.Append(maskA); + m_RunOnePhase = "GetExecutionState E"; CheckRunLockInvariants(true); + + /* + * "DetectParams" are returned by llDetected...() script functions + * for the currently active event, if any. + */ + if (m_DetectParams != null) + { + XmlElement detParArrayN = doc.CreateElement("", "DetectArray", ""); + AppendXMLDetectArray(doc, detParArrayN, m_DetectParams); + scriptStateN.AppendChild(detParArrayN); + } + m_RunOnePhase = "GetExecutionState F"; CheckRunLockInvariants(true); + + /* + * Save any events we have in the queue. + * + * + * ... ... + * ... ... + * + * ... + * + */ + XmlElement queuedEventsN = doc.CreateElement("", "EventQueue", ""); + lock (m_QueueLock) + { + foreach (EventParams evt in m_EventQueue) + { + XmlElement singleEventN = doc.CreateElement("", "Event", ""); + singleEventN.SetAttribute("Name", evt.EventName); + AppendXMLObjectArray(doc, singleEventN, evt.Params, "param"); + AppendXMLDetectArray(doc, singleEventN, evt.DetectParams); + queuedEventsN.AppendChild(singleEventN); + } + } + scriptStateN.AppendChild(queuedEventsN); + m_RunOnePhase = "GetExecutionState G"; CheckRunLockInvariants(true); + + /* + * "Plugins" indicate enabled timers and listens, etc. + */ + Object[] pluginData = + AsyncCommandManager.GetSerializationData(m_Engine, + m_ItemID); + + XmlNode plugins = doc.CreateElement("", "Plugins", ""); + AppendXMLObjectArray(doc, plugins, pluginData, "plugin"); + scriptStateN.AppendChild(plugins); + m_RunOnePhase = "GetExecutionState H"; CheckRunLockInvariants(true); + + /* + * Let script run again. + */ + suspendOnCheckRunHold = false; + + m_RunOnePhase = "GetExecutionState leave"; + CheckRunLockInvariants(true); + } + + /* + * scriptStateN represents the contents of the .state file so + * write the .state file while we are here. + */ + FileStream fs = File.Create(m_StateFileName); + StreamWriter sw = new StreamWriter(fs); + sw.Write(scriptStateN.OuterXml); + sw.Close(); + fs.Close(); + + return scriptStateN; + } + + /** + * @brief Write script state to output stream. + * The script microthread is at same state on return, + * ie, either inactive or suspended inside CheckRun(). + * + * Input: + * stream = stream to write event handler state information to + */ + private void MigrateOutEventHandler (Stream stream) + { + moehexcep = null; + + // do all the work in the MigrateOutEventHandlerThread() method below + moehstream = stream; + + XMRScriptThread cst = XMRScriptThread.CurrentScriptThread (); + if (cst != null) { + + // we might be getting called inside some LSL Api function + // so we are already in script thread and thus must do + // migration directly + MigrateOutEventHandlerThread (); + } else { + + // some other thread, do migration via a script thread + lock (XMRScriptThread.m_WakeUpLock) { + m_Engine.m_ThunkQueue.Enqueue (this.MigrateOutEventHandlerThread); + } + XMRScriptThread.WakeUpOne (); + + // wait for it to complete + lock (moehdone) { + while (moehstream != null) { + Monitor.Wait (moehdone); + } + } + } + + // maybe it threw up + if (moehexcep != null) throw moehexcep; + } + private Exception moehexcep; + private object moehdone = new object (); + private Stream moehstream; + private void MigrateOutEventHandlerThread () + { + Exception except; + + try { + + /* + * Resume the microthread and it will throw a StackCaptureException() + * with the stack frames saved to this.stackFrames. + * Then write the saved stack frames to the output stream. + * + * There is a stack only if the event code is not None. + */ + if (this.eventCode != ScriptEventCode.None) { + + // tell microthread to continue + // it should see captureStackFrames and throw StackCaptureException() + // ...generating XMRStackFrames as it unwinds + this.captureStackFrames = true; + except = this.microthread.ResumeEx (); + this.captureStackFrames = false; + if (except == null) { + throw new Exception ("stack save did not complete"); + } + if (!(except is StackCaptureException)) { + throw except; + } + } + + /* + * Write script state out, frames and all, to the stream. + * Does not change script state. + */ + moehstream.WriteByte (migrationVersion); + moehstream.WriteByte ((byte)16); + this.MigrateOut (new BinaryWriter (moehstream)); + + /* + * Now restore script stack. + * Microthread will suspend inside CheckRun() when restore is complete. + */ + if (this.eventCode != ScriptEventCode.None) { + this.stackFramesRestored = false; + except = this.microthread.StartEx (); + if (except != null) { + throw except; + } + if (!this.stackFramesRestored) { + throw new Exception ("restore after save did not complete"); + } + } + } catch (Exception e) { + moehexcep = e; + } finally { + + // make sure CheckRunLockInvariants() won't puque + if (this.microthread.Active () == 0) { + this.eventCode = ScriptEventCode.None; + } + + // wake the MigrateOutEventHandler() method above + lock (moehdone) { + moehstream = null; + Monitor.Pulse (moehdone); + } + } + } + + /** + * @brief Convert an DetectParams[] to corresponding XML. + * DetectParams[] holds the values retrievable by llDetected...() for + * a given event. + */ + private static void AppendXMLDetectArray(XmlDocument doc, XmlElement parent, DetectParams[] detect) + { + foreach (DetectParams d in detect) + { + XmlElement detectParamsN = GetXMLDetect(doc, d); + parent.AppendChild(detectParamsN); + } + } + + private static XmlElement GetXMLDetect(XmlDocument doc, DetectParams d) + { + XmlElement detectParamsN = doc.CreateElement("", "DetectParams", ""); + + XmlAttribute d_key = doc.CreateAttribute("", "key", ""); + d_key.Value = d.Key.ToString(); + detectParamsN.Attributes.Append(d_key); + + XmlAttribute pos = doc.CreateAttribute("", "pos", ""); + pos.Value = d.OffsetPos.ToString(); + detectParamsN.Attributes.Append(pos); + + XmlAttribute d_linkNum = doc.CreateAttribute("", "linkNum", ""); + d_linkNum.Value = d.LinkNum.ToString(); + detectParamsN.Attributes.Append(d_linkNum); + + XmlAttribute d_group = doc.CreateAttribute("", "group", ""); + d_group.Value = d.Group.ToString(); + detectParamsN.Attributes.Append(d_group); + + XmlAttribute d_name = doc.CreateAttribute("", "name", ""); + d_name.Value = d.Name.ToString(); + detectParamsN.Attributes.Append(d_name); + + XmlAttribute d_owner = doc.CreateAttribute("", "owner", ""); + d_owner.Value = d.Owner.ToString(); + detectParamsN.Attributes.Append(d_owner); + + XmlAttribute d_position = doc.CreateAttribute("", "position", ""); + d_position.Value = d.Position.ToString(); + detectParamsN.Attributes.Append(d_position); + + XmlAttribute d_rotation = doc.CreateAttribute("", "rotation", ""); + d_rotation.Value = d.Rotation.ToString(); + detectParamsN.Attributes.Append(d_rotation); + + XmlAttribute d_type = doc.CreateAttribute("", "type", ""); + d_type.Value = d.Type.ToString(); + detectParamsN.Attributes.Append(d_type); + + XmlAttribute d_velocity = doc.CreateAttribute("", "velocity", ""); + d_velocity.Value = d.Velocity.ToString(); + detectParamsN.Attributes.Append(d_velocity); + + return detectParamsN; + } + + /** + * @brief Append elements of an array of objects to an XML parent. + * @param doc = document the parent is part of + * @param parent = parent to append the items to + * @param array = array of objects + * @param tag = ... for each element + */ + private static void AppendXMLObjectArray(XmlDocument doc, XmlNode parent, object[] array, string tag) + { + foreach (object o in array) + { + XmlElement element = GetXMLObject(doc, o, tag); + parent.AppendChild(element); + } + } + + /** + * @brief Get and XML representation of an object. + * @param doc = document the tag will be put in + * @param o = object to be represented + * @param tag = ... + */ + private static XmlElement GetXMLObject(XmlDocument doc, object o, string tag) + { + XmlAttribute typ = doc.CreateAttribute("", "type", ""); + XmlElement n = doc.CreateElement("", tag, ""); + + if (o is LSL_List) + { + typ.Value = "list"; + n.Attributes.Append(typ); + AppendXMLObjectArray(doc, n, ((LSL_List)o).Data, "item"); + } + else + { + typ.Value = o.GetType().ToString(); + n.Attributes.Append(typ); + n.AppendChild(doc.CreateTextNode(o.ToString())); + } + return n; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstCtor.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstCtor.cs new file mode 100644 index 0000000..d9c578a --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstCtor.cs @@ -0,0 +1,878 @@ +/* + * 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 System; +using System.Threading; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Remoting.Lifetime; +using System.Security.Policy; +using System.IO; +using System.Xml; +using System.Text; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Region.ScriptEngine.XMREngine; +using OpenSim.Region.Framework.Scenes; +using log4net; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public partial class XMRInstance + { + /****************************************************************************\ + * The only method of interest to outside this module is the Initializer. * + * * + * The rest of this module contains support routines for the Initializer. * + \****************************************************************************/ + + /** + * @brief Initializer, loads script in memory and all ready for running. + * @param engine = XMREngine instance this is part of + * @param scriptBasePath = directory name where files are + * @param stackSize = number of bytes to allocate for stacks + * @param errors = return compiler errors in this array + * @param forceRecomp = force recompile + * Throws exception if any error, so it was successful if it returns. + */ + public void Initialize(XMREngine engine, string scriptBasePath, + int stackSize, int heapSize, ArrayList errors) + { + if (stackSize < 16384) stackSize = 16384; + if (heapSize < 16384) heapSize = 16384; + + /* + * Save all call parameters in instance vars for easy access. + */ + m_Engine = engine; + m_ScriptBasePath = scriptBasePath; + m_StackSize = stackSize; + m_HeapSize = heapSize; + m_CompilerErrors = errors; + m_StateFileName = GetStateFileName(scriptBasePath, m_ItemID); + + /* + * Not in any XMRInstQueue. + */ + m_NextInst = this; + m_PrevInst = this; + + /* + * Set up list of API calls it has available. + * This also gets the API modules ready to accept setup data, such as + * active listeners being restored. + */ + IScriptApi scriptApi; + ApiManager am = new ApiManager(); + foreach (string api in am.GetApis()) + { + /* + * Instantiate the API for this script instance. + */ + if (api != "LSL") { + scriptApi = am.CreateApi(api); + } else { + scriptApi = m_XMRLSLApi = new XMRLSL_Api(); + } + + /* + * Connect it up to the instance. + */ + InitScriptApi (engine, api, scriptApi); + } + + m_XMRLSLApi.InitXMRLSLApi(this); + + /* + * Get object loaded, compiling script and reading .state file as + * necessary to restore the state. + */ + suspendOnCheckRunHold = true; + InstantiateScript(); + m_SourceCode = null; + if (m_ObjCode == null) throw new ArgumentNullException ("m_ObjCode"); + if (m_ObjCode.scriptEventHandlerTable == null) { + throw new ArgumentNullException ("m_ObjCode.scriptEventHandlerTable"); + } + + suspendOnCheckRunHold = false; + suspendOnCheckRunTemp = false; + + /* + * Declare which events the script's current state can handle. + */ + int eventMask = GetStateEventFlags(stateCode); + m_Part.SetScriptEvents(m_ItemID, eventMask); + } + + private void InitScriptApi (XMREngine engine, string api, IScriptApi scriptApi) + { + /* + * Set up m_ApiManager_ = instance pointer. + */ + engine.m_XMRInstanceApiCtxFieldInfos[api].SetValue (this, scriptApi); + + /* + * Initialize the API instance. + */ + scriptApi.Initialize(m_Engine, m_Part, m_Item); + this.InitApi (api, scriptApi); + } + + + // Get script object code loaded in memory and all ready to run, + // ready to resume it from where the .state file says it was last + private void InstantiateScript() + { + bool compiledIt = false; + ScriptObjCode objCode; + + /* + * If source code string is empty, use the asset ID as the object file name. + * Allow lines of // comments at the beginning (for such as engine selection). + */ + int i, j, len; + if (m_SourceCode == null) m_SourceCode = String.Empty; + for (len = m_SourceCode.Length; len > 0; -- len) { + if (m_SourceCode[len-1] > ' ') break; + } + for (i = 0; i < len; i ++) { + char c = m_SourceCode[i]; + if (c <= ' ') continue; + if (c != '/') break; + if ((i + 1 >= len) || (m_SourceCode[i+1] != '/')) break; + i = m_SourceCode.IndexOf ('\n', i); + if (i < 0) i = len - 1; + } + if ((i >= len) || !m_Engine.m_UseSourceHashCode) { + + /* + * Source consists of nothing but // comments and whitespace, + * or we are being forced to use the asset-id as the key, to + * open an already existing object code file. + */ + m_ScriptObjCodeKey = m_Item.AssetID.ToString (); + if (i >= len) m_SourceCode = ""; + } else { + + /* + * Make up dictionary key for the object code. + * Use the same object code for identical source code + * regardless of asset ID, so we don't care if they + * copy scripts or not. + */ + byte[] scbytes = System.Text.Encoding.UTF8.GetBytes (m_SourceCode); + StringBuilder sb = new StringBuilder ((256 + 5) / 6); + ByteArrayToSixbitStr (sb, System.Security.Cryptography.SHA256.Create ().ComputeHash (scbytes)); + m_ScriptObjCodeKey = sb.ToString (); + + /* + * But source code can be just a sixbit string itself + * that identifies an already existing object code file. + */ + if (len - i == m_ScriptObjCodeKey.Length) { + for (j = len; -- j >= i;) { + if (sixbit.IndexOf (m_SourceCode[j]) < 0) break; + } + if (j < i) { + m_ScriptObjCodeKey = m_SourceCode.Substring (i, len - i); + m_SourceCode = ""; + } + } + } + + /* + * There may already be an ScriptObjCode struct in memory that + * we can use. If not, try to compile it. + */ + lock (m_CompileLock) { + if (!m_CompiledScriptObjCode.TryGetValue (m_ScriptObjCodeKey, out objCode) || m_ForceRecomp) { + objCode = TryToCompile (); + compiledIt = true; + } + + /* + * Loaded successfully, increment reference count. + * + * If we just compiled it though, reset count to 0 first as + * this is the one-and-only existance of this objCode struct, + * and we want any old ones for this source code to be garbage + * collected. + */ + if (compiledIt) { + m_CompiledScriptObjCode[m_ScriptObjCodeKey] = objCode; + objCode.refCount = 0; + } + objCode.refCount ++; + + /* + * Now set up to decrement ref count on dispose. + */ + m_ObjCode = objCode; + } + + try { + + /* + * Fill in script instance from object code + * Script instance is put in a "never-ever-has-run-before" state. + */ + LoadObjCode(); + + /* + * Fill in script intial state + * - either as loaded from a .state file + * - or initial default state_entry() event + */ + LoadInitialState(); + } catch { + + /* + * If any error loading, decrement object code reference count. + */ + DecObjCodeRefCount (); + throw; + } + } + + private const string sixbit = "0123456789_abcdefghijklmnopqrstuvwxyz@ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static void ByteArrayToSixbitStr (StringBuilder sb, byte[] bytes) + { + int bit = 0; + int val = 0; + foreach (byte b in bytes) { + val |= (int)((uint)b << bit); + bit += 8; + while (bit >= 6) { + sb.Append (sixbit[val&63]); + val >>= 6; + bit -= 6; + } + } + if (bit > 0) { + sb.Append (sixbit[val&63]); + } + } + + // Try to create object code from source code + // If error, just throw exception + private ScriptObjCode TryToCompile () + { + m_CompilerErrors.Clear(); + + /* + * If object file exists, create ScriptObjCode directly from that. + * Otherwise, compile the source to create object file then create + * ScriptObjCode from that. + */ + string assetID = m_Item.AssetID.ToString(); + m_CameFrom = "asset://" + assetID; + ScriptObjCode objCode = Compile (); + if (m_CompilerErrors.Count != 0) + { + throw new Exception ("compilation errors"); + } + if (objCode == null) + { + throw new Exception ("compilation failed"); + } + + return objCode; + } + + /* + * Retrieve source from asset server. + */ + private string FetchSource (string cameFrom) + { + m_log.Debug ("[XMREngine]: fetching source " + cameFrom); + if (!cameFrom.StartsWith ("asset://")) { + throw new Exception ("unable to retrieve source from " + cameFrom); + } + string assetID = cameFrom.Substring (8); + AssetBase asset = m_Engine.World.AssetService.Get(assetID); + if (asset == null) { + throw new Exception ("source not found " + cameFrom); + } + string source = Encoding.UTF8.GetString (asset.Data); + if (EmptySource (source)) { + throw new Exception ("fetched source empty " + cameFrom); + } + return source; + } + + /* + * Fill in script object initial contents. + * Set the initial state to "default". + */ + private void LoadObjCode () + { + /* + * Script must leave this much stack remaining on calls to CheckRun(). + */ + this.stackLimit = m_StackSize / 2; + + /* + * This is how many total heap bytes script is allowed to use. + */ + this.heapLimit = m_HeapSize; + + /* + * Allocate global variable arrays. + */ + this.glblVars.AllocVarArrays (m_ObjCode.glblSizes); + + /* + * Script can handle these event codes. + */ + m_HaveEventHandlers = new bool[m_ObjCode.scriptEventHandlerTable.GetLength(1)]; + for (int i = m_ObjCode.scriptEventHandlerTable.GetLength(0); -- i >= 0;) { + for (int j = m_ObjCode.scriptEventHandlerTable.GetLength(1); -- j >= 0;) { + if (m_ObjCode.scriptEventHandlerTable[i,j] != null) { + m_HaveEventHandlers[j] = true; + } + } + } + + /* + * Set up microthread object which actually calls the script event handler functions. + */ + this.microthread = (IScriptUThread)m_Engine.uThreadCtor.Invoke (new object[] { this }); + } + + // LoadInitialState() + // if no state XML file exists for the asset, + // post initial default state events + // else + // try to restore from .state file + // If any error, throw exception + // + private void LoadInitialState() + { + /* + * If no .state file exists, start from default state + * Otherwise, read initial state from the .state file + */ + if (!File.Exists(m_StateFileName)) { + m_Running = true; // event processing is enabled + eventCode = ScriptEventCode.None; // not processing any event + + // default state_entry() must initialize global variables + doGblInit = true; + stateCode = 0; + + PostEvent(new EventParams("state_entry", + zeroObjectArray, + zeroDetectParams)); + } else { + FileStream fs = File.Open(m_StateFileName, + FileMode.Open, + FileAccess.Read); + StreamReader ss = new StreamReader(fs); + string xml = ss.ReadToEnd(); + ss.Close(); + fs.Close(); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + LoadScriptState(doc); + } + + /* + * Post event(s) saying what caused the script to start. + */ + if (m_PostOnRez) { + PostEvent(new EventParams("on_rez", + new Object[] { m_StartParam }, + zeroDetectParams)); + } + + switch (m_StateSource) { + case StateSource.AttachedRez: { +// PostEvent(new EventParams("attach", +// new object[] { m_Part.ParentGroup.AttachedAvatar.ToString() }, +// zeroDetectParams)); + break; + } + + case StateSource.PrimCrossing: { + PostEvent(new EventParams("changed", + sbcCR, + zeroDetectParams)); + break; + } + + case StateSource.Teleporting: { + PostEvent(new EventParams("changed", + sbcCR, + zeroDetectParams)); + PostEvent(new EventParams("changed", + sbcCT, + zeroDetectParams)); + break; + } + + case StateSource.RegionStart: { + PostEvent(new EventParams("changed", + sbcCRS, + zeroDetectParams)); + break; + } + } + } + + private static Object[] sbcCRS = new Object[] { ScriptBaseClass.CHANGED_REGION_START }; + private static Object[] sbcCR = new Object[] { ScriptBaseClass.CHANGED_REGION }; + private static Object[] sbcCT = new Object[] { ScriptBaseClass.CHANGED_TELEPORT }; + + /** + * @brief Save compilation error messages for later retrieval + * via GetScriptErrors(). + */ + private void ErrorHandler(Token token, string message) + { + if (token != null) { + string srcloc = token.SrcLoc; + if (srcloc.StartsWith (m_CameFrom)) { + srcloc = srcloc.Substring (m_CameFrom.Length); + } + m_CompilerErrors.Add(srcloc + " Error: " + message); + } else if (message != null) { + m_CompilerErrors.Add("(0,0) Error: " + message); + } else { + m_CompilerErrors.Add("(0,0) Error compiling, see exception in log"); + } + } + + /** + * @brief Load script state from the given XML doc into the script memory + * + * ... + * ... + * + * RestoreDetectParams() + * + * ExtractXMLObjectArray("plugin") + * + * + * MigrateInEventHandler() + * + * + */ + private void LoadScriptState(XmlDocument doc) + { + DetectParams[] detParams; + LinkedList eventQueue; + + // Everything we know is enclosed in ... + XmlElement scriptStateN = (XmlElement)doc.SelectSingleNode("ScriptState"); + if (scriptStateN == null) { + throw new Exception("no tag"); + } + string sen = scriptStateN.GetAttribute("Engine"); + if ((sen == null) || (sen != m_Engine.ScriptEngineName)) { + throw new Exception(" missing Engine=\"XMREngine\" attribute"); + } + + // AssetID is unique for the script source text so make sure the + // state file was written for that source file + string assetID = scriptStateN.GetAttribute("Asset"); + if (assetID != m_Item.AssetID.ToString()) + { + throw new Exception(" assetID mismatch"); + } + + // Also match the sourceHash in case script was + // loaded via 'xmroption fetchsource' and has changed + string sourceHash = scriptStateN.GetAttribute ("SourceHash"); + if ((sourceHash == null) || (sourceHash != m_ObjCode.sourceHash)) { + throw new Exception (" SourceHash mismatch"); + } + + // Get various attributes + XmlElement runningN = (XmlElement)scriptStateN.SelectSingleNode("Running"); + m_Running = bool.Parse(runningN.InnerText); + + XmlElement doGblInitN = (XmlElement)scriptStateN.SelectSingleNode("DoGblInit"); + doGblInit = bool.Parse(doGblInitN.InnerText); + + XmlElement permissionsN = (XmlElement)scriptStateN.SelectSingleNode("Permissions"); + m_Item.PermsGranter = new UUID(permissionsN.GetAttribute("granter")); + m_Item.PermsMask = Convert.ToInt32(permissionsN.GetAttribute("mask")); + m_Part.Inventory.UpdateInventoryItem(m_Item, false, false); + + // get values used by stuff like llDetectedGrab, etc. + detParams = RestoreDetectParams(scriptStateN.SelectSingleNode("DetectArray")); + + // Restore queued events + eventQueue = RestoreEventQueue(scriptStateN.SelectSingleNode("EventQueue")); + + // Restore timers and listeners + XmlElement pluginN = (XmlElement)scriptStateN.SelectSingleNode("Plugins"); + Object[] pluginData = ExtractXMLObjectArray(pluginN, "plugin"); + + // Script's global variables and stack contents + XmlElement snapshotN = + (XmlElement)scriptStateN.SelectSingleNode("Snapshot"); + + Byte[] data = Convert.FromBase64String(snapshotN.InnerText); + MemoryStream ms = new MemoryStream(); + ms.Write(data, 0, data.Length); + ms.Seek(0, SeekOrigin.Begin); + MigrateInEventHandler(ms); + ms.Close(); + + // Restore event queues, preserving any events that queued + // whilst we were restoring the state + lock (m_QueueLock) { + m_DetectParams = detParams; + foreach (EventParams evt in m_EventQueue) { + eventQueue.AddLast (evt); + } + m_EventQueue = eventQueue; + for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0; + foreach (EventParams evt in m_EventQueue) + { + ScriptEventCode eventCode = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode), + evt.EventName); + m_EventCounts[(int)eventCode] ++; + } + } + + // Requeue timer and listeners (possibly queuing new events) + AsyncCommandManager.CreateFromData(m_Engine, + m_LocalID, m_ItemID, m_Part.UUID, + pluginData); + } + + /** + * @brief Read llDetectedGrab, etc, values from XML + * + * ... + * . + * . + * . + * + */ + private LinkedList RestoreEventQueue(XmlNode eventsN) + { + LinkedList eventQueue = new LinkedList(); + if (eventsN != null) { + XmlNodeList eventL = eventsN.SelectNodes("Event"); + foreach (XmlNode evnt in eventL) + { + string name = ((XmlElement)evnt).GetAttribute("Name"); + object[] parms = ExtractXMLObjectArray(evnt, "param"); + DetectParams[] detects = RestoreDetectParams(evnt); + + if (parms == null) parms = zeroObjectArray; + if (detects == null) detects = zeroDetectParams; + + EventParams evt = new EventParams(name, parms, detects); + eventQueue.AddLast(evt); + } + } + return eventQueue; + } + + /** + * @brief Read llDetectedGrab, etc, values from XML + * + * ... + * . + * . + * . + * + */ + private DetectParams[] RestoreDetectParams(XmlNode detectedN) + { + if (detectedN == null) return null; + + List detected = new List(); + XmlNodeList detectL = detectedN.SelectNodes("DetectParams"); + + DetectParams detprm = new DetectParams(); + foreach (XmlNode detxml in detectL) { + try { + detprm.Group = new UUID(detxml.Attributes.GetNamedItem("group").Value); + detprm.Key = new UUID(detxml.Attributes.GetNamedItem("key").Value); + detprm.Owner = new UUID(detxml.Attributes.GetNamedItem("owner").Value); + + detprm.LinkNum = Int32.Parse(detxml.Attributes.GetNamedItem("linkNum").Value); + detprm.Type = Int32.Parse(detxml.Attributes.GetNamedItem("type").Value); + + detprm.Name = detxml.Attributes.GetNamedItem("name").Value; + + detprm.OffsetPos = new LSL_Types.Vector3(detxml.Attributes.GetNamedItem("pos").Value); + detprm.Position = new LSL_Types.Vector3(detxml.Attributes.GetNamedItem("position").Value); + detprm.Velocity = new LSL_Types.Vector3(detxml.Attributes.GetNamedItem("velocity").Value); + + detprm.Rotation = new LSL_Types.Quaternion(detxml.Attributes.GetNamedItem("rotation").Value); + + detected.Add(detprm); + detprm = new DetectParams(); + } catch (Exception e) { + m_log.Warn("[XMREngine]: RestoreDetectParams bad XML: " + detxml.ToString()); + m_log.Warn("[XMREngine]: ... " + e.ToString()); + } + } + + return detected.ToArray(); + } + + /** + * @brief Extract elements of an array of objects from an XML parent. + * Each element is of form ... + * @param parent = XML parent to extract them from + * @param tag = what the value's tag is + * @returns object array of the values + */ + private static object[] ExtractXMLObjectArray(XmlNode parent, string tag) + { + List olist = new List(); + + XmlNodeList itemL = parent.SelectNodes(tag); + foreach (XmlNode item in itemL) + { + olist.Add(ExtractXMLObjectValue(item)); + } + + return olist.ToArray(); + } + + private static object ExtractXMLObjectValue(XmlNode item) + { + string itemType = item.Attributes.GetNamedItem("type").Value; + + if (itemType == "list") + { + return new LSL_List(ExtractXMLObjectArray(item, "item")); + } + + if (itemType == "OpenMetaverse.UUID") + { + UUID val = new UUID(); + UUID.TryParse(item.InnerText, out val); + return val; + } + + Type itemT = Type.GetType(itemType); + if (itemT == null) + { + Object[] args = new Object[] { item.InnerText }; + + string assembly = itemType + ", OpenSim.Region.ScriptEngine.Shared"; + itemT = Type.GetType(assembly); + if (itemT == null) + { + return null; + } + return Activator.CreateInstance(itemT, args); + } + + return Convert.ChangeType(item.InnerText, itemT); + } + + /* + * Migrate an event handler in from a stream. + * + * Input: + * stream = as generated by MigrateOutEventHandler() + */ + private void MigrateInEventHandler (Stream stream) + { + miehexcep = null; + + // do all the work in the MigrateInEventHandlerThread() method below + miehstream = stream; + + XMRScriptThread cst = XMRScriptThread.CurrentScriptThread (); + if (cst != null) { + + // in case we are getting called inside some LSL Api function + MigrateInEventHandlerThread (); + } else { + + // some other thread, do migration via a script thread + lock (XMRScriptThread.m_WakeUpLock) { + m_Engine.m_ThunkQueue.Enqueue (this.MigrateInEventHandlerThread); + } + XMRScriptThread.WakeUpOne (); + + // wait for it to complete + lock (miehdone) { + while (miehstream != null) { + Monitor.Wait (miehdone); + } + } + } + + // maybe it threw up + if (miehexcep != null) throw miehexcep; + } + private Exception miehexcep; + private object miehdone = new object (); + private Stream miehstream; + private void MigrateInEventHandlerThread () + { + try { + int mv = miehstream.ReadByte (); + if (mv != migrationVersion) { + throw new Exception ("incoming migration version " + mv + " but accept only " + migrationVersion); + } + miehstream.ReadByte (); // ignored + + /* + * Restore script variables and stack and other state from stream. + * And it also marks us busy (by setting this.eventCode) so we can't be + * started again and this event lost. + */ + BinaryReader br = new BinaryReader (miehstream); + this.MigrateIn (br); + + /* + * If eventCode is None, it means the script was idle when migrated. + */ + if (this.eventCode != ScriptEventCode.None) { + + /* + * So microthread.Start() calls XMRScriptUThread.Main() which calls the + * event handler function. The event handler function sees the stack + * frames in this.stackFrames and restores its args and locals, then calls + * whatever it was calling when the snapshot was taken. That function also + * sees this.stackFrames and restores its args and locals, and so on... + * Eventually it gets to the point of calling CheckRun() which sees we are + * doing a restore and it suspends, returning here with the microthread + * stack all restored. It shouldn't ever throw an exception. + */ + this.stackFramesRestored = false; + Exception te = microthread.StartEx (); + if (te != null) throw te; + if (!this.stackFramesRestored) throw new Exception ("migrate in did not complete"); + } + } catch (Exception e) { + miehexcep = e; + } finally { + + /* + * Wake the MigrateInEventHandler() method above. + */ + lock (miehdone) { + miehstream = null; + Monitor.Pulse (miehdone); + } + } + } + + /** + * See if permitted by configuration file. + * See OSSL_Api.CheckThreatLevelTest(). + */ + public string CheckFetchbinaryAllowed () + { + string ownerPerm = m_Engine.Config.GetString ("Allow_fetchbinary", ""); + UUID ownerID = m_Item.OwnerID; + string[] ids = ownerPerm.Split (new char[] { ',' }); + foreach (string id in ids) { + string curuc = id.Trim ().ToUpperInvariant (); + + switch (curuc) { + case "ESTATE_MANAGER": { + if (m_Engine.m_Scene.RegionInfo.EstateSettings.IsEstateManagerOrOwner (ownerID) && + (m_Engine.m_Scene.RegionInfo.EstateSettings.EstateOwner != ownerID)) { + return null; + } + break; + } + + case "ESTATE_OWNER": { + if (m_Engine.m_Scene.RegionInfo.EstateSettings.EstateOwner == ownerID) { + return null; + } + break; + } + + case "PARCEL_GROUP_MEMBER": { + ILandObject land = m_Engine.m_Scene.LandChannel.GetLandObject (m_Part.AbsolutePosition); + if (land.LandData.GroupID == m_Item.GroupID && land.LandData.GroupID != UUID.Zero) { + return null; + } + break; + } + + case "PARCEL_OWNER": { + ILandObject land = m_Engine.m_Scene.LandChannel.GetLandObject (m_Part.AbsolutePosition); + if (land.LandData.OwnerID == ownerID) { + return null; + } + break; + } + + case "TRUE": { + return null; + } + + default: { + UUID uuid; + if (UUID.TryParse (curuc, out uuid)) { + if (uuid == ownerID) return null; + } + break; + } + } + } + + string creatorPerm = m_Engine.Config.GetString ("Creators_fetchbinary", ""); + UUID creatorID = m_Item.CreatorID; + ids = creatorPerm.Split (new char[] { ',' }); + foreach (string id in ids) { + string current = id.Trim (); + UUID uuid; + if (UUID.TryParse (current, out uuid)) { + if (uuid != UUID.Zero) { + if (creatorID == uuid) return null; + } + } + } + + return "fetchbinary not enabled for owner " + ownerID + " creator " + creatorID; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstMain.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstMain.cs new file mode 100644 index 0000000..436434a --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstMain.cs @@ -0,0 +1,230 @@ +/* + * 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 System; +using System.Threading; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Remoting.Lifetime; +using System.Security.Policy; +using System.IO; +using System.Xml; +using System.Text; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Region.ScriptEngine.XMREngine; +using OpenSim.Region.Framework.Scenes; +using log4net; + +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; + +// This class exists in the main app domain +// +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + /** + * @brief Which queue it is in as far as running is concerned, + * ie, m_StartQueue, m_YieldQueue, m_SleepQueue, etc. + * Allowed transitions: + * Starts in CONSTRUCT when constructed + * CONSTRUCT->ONSTARTQ : only by thread that constructed and compiled it + * IDLE->ONSTARTQ,RESETTING : by any thread but must have m_QueueLock when transitioning + * ONSTARTQ->RUNNING,RESETTING : only by thread that removed it from m_StartQueue + * ONYIELDQ->RUNNING,RESETTING : only by thread that removed it from m_YieldQueue + * ONSLEEPQ->REMDFROMSLPQ : by any thread but must have m_SleepQueue when transitioning + * REMDFROMSLPQ->ONYIELDQ,RESETTING : only by thread that removed it from m_SleepQueue + * RUNNING->whatever1 : only by thread that transitioned it to RUNNING + * whatever1 = IDLE,ONSLEEPQ,ONYIELDQ,ONSTARTQ,SUSPENDED,FINISHED + * FINSHED->whatever2 : only by thread that transitioned it to FINISHED + * whatever2 = IDLE,ONSTARTQ,DISPOSED + * SUSPENDED->ONSTARTQ : by any thread (NOT YET IMPLEMENTED, should be under some kind of lock?) + * RESETTING->ONSTARTQ : only by the thread that transitioned it to RESETTING + */ + public enum XMRInstState { + CONSTRUCT, // it is being constructed + IDLE, // nothing happening (finished last event and m_EventQueue is empty) + ONSTARTQ, // inserted on m_Engine.m_StartQueue + RUNNING, // currently being executed by RunOne() + ONSLEEPQ, // inserted on m_Engine.m_SleepQueue + REMDFROMSLPQ, // removed from m_SleepQueue but not yet on m_YieldQueue + ONYIELDQ, // inserted on m_Engine.m_YieldQueue + FINISHED, // just finished handling an event + SUSPENDED, // m_SuspendCount > 0 + RESETTING, // being reset via external call + DISPOSED // has been disposed + } + + public partial class XMRInstance : XMRInstAbstract, IDisposable + { + /******************************************************************\ + * This module contains the instance variables for XMRInstance. * + \******************************************************************/ + + public const int MAXEVENTQUEUE = 64; + + public static readonly DetectParams[] zeroDetectParams = new DetectParams[0]; + public static readonly object[] zeroObjectArray = new object[0]; + + public static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // For a given m_Item.AssetID, do we have the compiled object code and where + // is it? + public static object m_CompileLock = new object(); + private static Dictionary m_CompiledScriptObjCode = new Dictionary(); + + public XMRInstance m_NextInst; // used by XMRInstQueue + public XMRInstance m_PrevInst; + public XMRInstState m_IState; + + public bool m_ForceRecomp = false; + public SceneObjectPart m_Part = null; + public uint m_LocalID = 0; + public TaskInventoryItem m_Item = null; + public UUID m_ItemID; + public UUID m_PartUUID; + private string m_CameFrom; + private string m_ScriptObjCodeKey; + + private XMREngine m_Engine = null; + private string m_ScriptBasePath; + private string m_StateFileName; + public string m_SourceCode; + public bool m_PostOnRez; + private DetectParams[] m_DetectParams = null; + public int m_StartParam = 0; + public StateSource m_StateSource; + public string m_DescName; + private bool[] m_HaveEventHandlers; + public int m_StackSize; + public int m_HeapSize; + private ArrayList m_CompilerErrors; + private DateTime m_LastRanAt = DateTime.MinValue; + private string m_RunOnePhase = "hasn't run"; + private string m_CheckRunPhase = "hasn't checked"; + public int m_InstEHEvent = 0; // number of events dequeued (StartEventHandler called) + public int m_InstEHSlice = 0; // number of times handler timesliced (ResumeEx called) + public double m_CPUTime = 0; // accumulated CPU time (milliseconds) + + // If code needs to have both m_QueueLock and m_RunLock, + // be sure to lock m_RunLock first then m_QueueLock, as + // that is the order used in RunOne(). + // These locks are currently separated to allow the script + // to call API routines that queue events back to the script. + // If we just had one lock, then the queuing would deadlock. + + // guards m_DetachQuantum, m_EventQueue, m_EventCounts, m_Running, m_Suspended + public Object m_QueueLock = new Object(); + + // true iff allowed to accept new events + public bool m_Running = true; + + // queue of events that haven't been acted upon yet + public LinkedList m_EventQueue = new LinkedList (); + + // number of events of each code currently in m_EventQueue. + private int[] m_EventCounts = new int[(int)ScriptEventCode.Size]; + + // locked whilst running on the microthread stack (or about to run on it or just ran on it) + private Object m_RunLock = new Object(); + + // script won't step while > 0. bus-atomic updates only. + private int m_SuspendCount = 0; + + // don't run any of script until this time + // or until one of these events are queued + public DateTime m_SleepUntil = DateTime.MinValue; + public int m_SleepEventMask1 = 0; + public int m_SleepEventMask2 = 0; + + private XMRLSL_Api m_XMRLSLApi; + + /* + * We will use this microthread to run the scripts event handlers. + */ + private IScriptUThread microthread; + + /* + * Set to perform migration. + */ + public bool stackFramesRestored; // set true by CheckRun() when stack has been + // restored and is about to suspend the microthread + public bool captureStackFrames; // set true to tell CheckRun() to throw a + // StackCaptureException() causing it to capture a + // snapshot of the script's stack + + /* + * Makes sure migration data version is same on both ends. + */ + public static byte migrationVersion = 10; + + // Incremented each time script gets reset. + public int m_ResetCount = 0; + + // Scripts start suspended now. This means that event queues will + // accept events, but will not actually run them until the core + // tells it it's OK. This is needed to prevent loss of link messages + // in complex objects, where no event can be allowed to run until + // all possible link message receivers' queues are established. + // Guarded by m_QueueLock. + public bool m_Suspended = true; + + // We really don't want to save state for a script that hasn't had + // a chance to run, because it's state will be blank. That would + // cause attachment state loss. + public bool m_HasRun = false; + + // When llDie is executed within the attach(NULL_KEY) event of + // a script being detached to inventory, the DeleteSceneObject call + // it causes will delete the script instances before their state can + // be saved. Therefore, the instance needs to know that it's being + // detached to inventory, rather than to ground. + // Also, the attach(NULL_KEY) event needs to run with priority, and + // it also needs to have a limited quantum. + // If this is nonzero, we're detaching to inventory. + // Guarded by m_QueueLock. + private int m_DetachQuantum = 0; + + // Finally, we need to wait until the quantum is done, or the script + // suspends itself. This should be efficient, so we use an event + // for it instead of spinning busy. + // It's born ready, but will be reset when the detach is posted. + // It will then be set again on suspend/completion + private ManualResetEvent m_DetachReady = new ManualResetEvent(true); + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstMisc.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstMisc.cs new file mode 100644 index 0000000..6ff486a --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstMisc.cs @@ -0,0 +1,384 @@ +/* + * 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 System; +using System.Threading; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Reflection.Emit; +using System.Runtime.Remoting.Lifetime; +using System.Security.Policy; +using System.IO; +using System.Xml; +using System.Text; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Region.ScriptEngine.XMREngine; +using OpenSim.Region.Framework.Scenes; +using log4net; + +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; + +// This class exists in the main app domain +// +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public partial class XMRInstance + { + + // In case Dispose() doesn't get called, we want to be sure to clean + // up. This makes sure we decrement m_CompiledScriptRefCount. + ~XMRInstance() + { + Dispose(); + } + + /** + * @brief Clean up stuff. + * We specifically leave m_DescName intact for 'xmr ls' command. + */ + public void Dispose() + { + /* + * Tell script stop executing next time it calls CheckRun(). + */ + suspendOnCheckRunHold = true; + + /* + * Wait for it to stop executing and prevent it from starting again + * as it can't run without a microthread. + */ + lock (m_RunLock) + { + if (microthread != null) + { + m_RunOnePhase = "disposing"; + CheckRunLockInvariants(true); + microthread.Dispose (); + microthread = null; + } + } + + /* + * Don't send us any more events. + */ + if (m_Part != null) + { + xmrTrapRegionCrossing (0); + m_Part.RemoveScriptEvents(m_ItemID); + AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID); + m_Part = null; + } + + /* + * Let script methods get garbage collected if no one else is using + * them. + */ + DecObjCodeRefCount (); + } + + private void DecObjCodeRefCount () + { + if (m_ObjCode != null) { + lock (m_CompileLock) { + ScriptObjCode objCode; + + if (m_CompiledScriptObjCode.TryGetValue (m_ScriptObjCodeKey, out objCode) && + (objCode == m_ObjCode) && + (-- objCode.refCount == 0)) { + m_CompiledScriptObjCode.Remove (m_ScriptObjCodeKey); + } + } + m_ObjCode = null; + } + } + + public void Verbose (string format, params object[] args) + { + if (m_Engine.m_Verbose) m_log.DebugFormat (format, args); + } + + // Called by 'xmr top' console command + // to dump this script's state to console + // Sacha + public void RunTestTop() + { + if (m_InstEHSlice > 0){ + Console.WriteLine(m_DescName); + Console.WriteLine(" m_LocalID = " + m_LocalID); + Console.WriteLine(" m_ItemID = " + m_ItemID); + Console.WriteLine(" m_Item.AssetID = " + m_Item.AssetID); + Console.WriteLine(" m_StartParam = " + m_StartParam); + Console.WriteLine(" m_PostOnRez = " + m_PostOnRez); + Console.WriteLine(" m_StateSource = " + m_StateSource); + Console.WriteLine(" m_SuspendCount = " + m_SuspendCount); + Console.WriteLine(" m_SleepUntil = " + m_SleepUntil); + Console.WriteLine(" m_IState = " + m_IState.ToString()); + Console.WriteLine(" m_StateCode = " + GetStateName(stateCode)); + Console.WriteLine(" eventCode = " + eventCode.ToString()); + Console.WriteLine(" m_LastRanAt = " + m_LastRanAt.ToString()); + Console.WriteLine(" heapUsed/Limit = " + xmrHeapUsed () + "/" + heapLimit); + Console.WriteLine(" m_InstEHEvent = " + m_InstEHEvent.ToString()); + Console.WriteLine(" m_InstEHSlice = " + m_InstEHSlice.ToString()); + } + } + + // Called by 'xmr ls' console command + // to dump this script's state to console + public string RunTestLs(bool flagFull) + { + if (flagFull) { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(m_DescName); + sb.AppendLine(" m_LocalID = " + m_LocalID); + sb.AppendLine(" m_ItemID = " + m_ItemID + " (.state file)"); + sb.AppendLine(" m_Item.AssetID = " + m_Item.AssetID); + sb.AppendLine(" m_Part.WorldPosition = " + m_Part.GetWorldPosition ()); + sb.AppendLine(" m_ScriptObjCodeKey = " + m_ScriptObjCodeKey + " (source text)"); + sb.AppendLine(" m_StartParam = " + m_StartParam); + sb.AppendLine(" m_PostOnRez = " + m_PostOnRez); + sb.AppendLine(" m_StateSource = " + m_StateSource); + sb.AppendLine(" m_SuspendCount = " + m_SuspendCount); + sb.AppendLine(" m_SleepUntil = " + m_SleepUntil); + sb.AppendLine(" m_SleepEvMask1 = 0x" + m_SleepEventMask1.ToString("X")); + sb.AppendLine(" m_SleepEvMask2 = 0x" + m_SleepEventMask2.ToString("X")); + sb.AppendLine(" m_IState = " + m_IState.ToString()); + sb.AppendLine(" m_StateCode = " + GetStateName(stateCode)); + sb.AppendLine(" eventCode = " + eventCode.ToString()); + sb.AppendLine(" m_LastRanAt = " + m_LastRanAt.ToString()); + sb.AppendLine(" m_RunOnePhase = " + m_RunOnePhase); + sb.AppendLine(" suspOnCkRunHold = " + suspendOnCheckRunHold); + sb.AppendLine(" suspOnCkRunTemp = " + suspendOnCheckRunTemp); + sb.AppendLine(" m_CheckRunPhase = " + m_CheckRunPhase); + sb.AppendLine(" heapUsed/Limit = " + xmrHeapUsed () + "/" + heapLimit); + sb.AppendLine(" m_InstEHEvent = " + m_InstEHEvent.ToString()); + sb.AppendLine(" m_InstEHSlice = " + m_InstEHSlice.ToString()); + sb.AppendLine(" m_CPUTime = " + m_CPUTime); + sb.AppendLine(" callMode = " + callMode); + sb.AppendLine(" captureStackFrames = " + captureStackFrames); + sb.AppendLine(" stackFramesRestored = " + stackFramesRestored); + lock (m_QueueLock) + { + sb.AppendLine(" m_Running = " + m_Running); + foreach (EventParams evt in m_EventQueue) + { + sb.AppendLine(" evt.EventName = " + evt.EventName); + } + } + return sb.ToString(); + } else { + return String.Format("{0} {1} {2} {3} {4} {5}", + m_ItemID, + m_CPUTime.ToString("F3").PadLeft(9), + m_InstEHEvent.ToString().PadLeft(9), + m_IState.ToString().PadRight(10), + m_Part.GetWorldPosition().ToString().PadRight(32), + m_DescName); + } + } + + /** + * @brief For a given stateCode, get a mask of the low 32 event codes + * that the state has handlers defined for. + */ + public int GetStateEventFlags(int stateCode) + { + if ((stateCode < 0) || + (stateCode >= m_ObjCode.scriptEventHandlerTable.GetLength(0))) + { + return 0; + } + + int code = 0; + for (int i = 0 ; i < 32; i ++) + { + if (m_ObjCode.scriptEventHandlerTable[stateCode, i] != null) + { + code |= 1 << i; + } + } + + return code; + } + + /** + * @brief Get the .state file name. + */ + public static string GetStateFileName (string scriptBasePath, UUID itemID) + { + return GetScriptFileName (scriptBasePath, itemID.ToString() + ".state"); + } + + public string GetScriptFileName (string filename) + { + return GetScriptFileName (m_ScriptBasePath, filename); + } + + public static string GetScriptFileName (string scriptBasePath, string filename) + { + /* + * Get old path, ie, all files lumped in a single huge directory. + */ + string oldPath = Path.Combine (scriptBasePath, filename); + + /* + * Get new path, ie, files split up based on first 2 chars of name. + */ + string subdir = filename.Substring (0, 2); + filename = filename.Substring (2); + scriptBasePath = Path.Combine (scriptBasePath, subdir); + Directory.CreateDirectory (scriptBasePath); + string newPath = Path.Combine (scriptBasePath, filename); + + /* + * If file exists only in old location, move to new location. + * If file exists in both locations, delete old location. + */ + if (File.Exists (oldPath)) { + if (File.Exists (newPath)) { + File.Delete (oldPath); + } else { + File.Move (oldPath, newPath); + } + } + + /* + * Always return new location. + */ + return newPath; + } + + /** + * @brief Decode state code (int) to state name (string). + */ + public string GetStateName (int stateCode) + { + try { + return m_ObjCode.stateNames[stateCode]; + } catch { + return stateCode.ToString (); + } + } + + /** + * @brief various gets & sets. + */ + public int StartParam + { + get { return m_StartParam; } + set { m_StartParam = value; } + } + + public SceneObjectPart SceneObject + { + get { return m_Part; } + } + + public DetectParams[] DetectParams + { + get { return m_DetectParams; } + set { m_DetectParams = value; } + } + + public UUID ItemID + { + get { return m_ItemID; } + } + + public UUID AssetID + { + get { return m_Item.AssetID; } + } + + public bool Running + { + get { return m_Running; } + set + { + lock (m_QueueLock) + { + m_Running = value; + if (!value) + { + EmptyEventQueues (); + } + } + } + } + + /** + * @brief Empty out the event queues. + * Assumes caller has the m_QueueLock locked. + */ + public void EmptyEventQueues () + { + m_EventQueue.Clear(); + for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0; + } + + /** + * @brief Convert an LSL vector to an Openmetaverse vector. + */ + public static OpenMetaverse.Vector3 LSLVec2OMVec (LSL_Vector lslVec) + { + return new OpenMetaverse.Vector3 ((float)lslVec.x, (float)lslVec.y, (float)lslVec.z); + } + + /** + * @brief Extract an integer from an element of an LSL_List. + */ + public static int ListInt (object element) + { + if (element is LSL_Integer) { + return (int)(LSL_Integer)element; + } + return (int)element; + } + + /** + * @brief Extract a string from an element of an LSL_List. + */ + public static string ListStr (object element) + { + if (element is LSL_String) { + return (string)(LSL_String)element; + } + return (string)element; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstQueue.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstQueue.cs new file mode 100644 index 0000000..41acac8 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstQueue.cs @@ -0,0 +1,186 @@ +/* + * 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 System; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + /** + * @brief Implements a queue of XMRInstance's. + * Do our own queue to avoid shitty little mallocs. + * + * Note: looping inst.m_NextInst and m_PrevInst back to itself + * when inst is removed from a queue is purely for debug. + */ + public class XMRInstQueue + { + private XMRInstance m_Head = null; + private XMRInstance m_Tail = null; + + /** + * @brief Insert instance at head of queue (in front of all others) + * @param inst = instance to insert + */ + public void InsertHead(XMRInstance inst) + { + if ((inst.m_PrevInst != inst) || (inst.m_NextInst != inst)) { + throw new Exception("already in list"); + } + inst.m_PrevInst = null; + if ((inst.m_NextInst = m_Head) == null) { + m_Tail = inst; + } else { + m_Head.m_PrevInst = inst; + } + m_Head = inst; + } + + /** + * @brief Insert instance at tail of queue (behind all others) + * @param inst = instance to insert + */ + public void InsertTail(XMRInstance inst) + { + if ((inst.m_PrevInst != inst) || (inst.m_NextInst != inst)) { + throw new Exception("already in list"); + } + inst.m_NextInst = null; + if ((inst.m_PrevInst = m_Tail) == null) { + m_Head = inst; + } else { + m_Tail.m_NextInst = inst; + } + m_Tail = inst; + } + + /** + * @brief Insert instance before another element in queue + * @param inst = instance to insert + * @param after = element that is to come after one being inserted + */ + public void InsertBefore(XMRInstance inst, XMRInstance after) + { + if ((inst.m_PrevInst != inst) || (inst.m_NextInst != inst)) { + throw new Exception("already in list"); + } + if (after == null) { + InsertTail(inst); + } else { + inst.m_NextInst = after; + inst.m_PrevInst = after.m_PrevInst; + if (inst.m_PrevInst == null) { + m_Head = inst; + } else { + inst.m_PrevInst.m_NextInst = inst; + } + after.m_PrevInst = inst; + } + } + + /** + * @brief Peek to see if anything in queue + * @returns first XMRInstance in queue but doesn't remove it + * null if queue is empty + */ + public XMRInstance PeekHead() + { + return m_Head; + } + + /** + * @brief Remove first element from queue, if any + * @returns null if queue is empty + * else returns first element in queue and removes it + */ + public XMRInstance RemoveHead() + { + XMRInstance inst = m_Head; + if (inst != null) { + if ((m_Head = inst.m_NextInst) == null) { + m_Tail = null; + } else { + m_Head.m_PrevInst = null; + } + inst.m_NextInst = inst; + inst.m_PrevInst = inst; + } + return inst; + } + + /** + * @brief Remove last element from queue, if any + * @returns null if queue is empty + * else returns last element in queue and removes it + */ + public XMRInstance RemoveTail() + { + XMRInstance inst = m_Tail; + if (inst != null) { + if ((m_Tail = inst.m_PrevInst) == null) { + m_Head = null; + } else { + m_Tail.m_NextInst = null; + } + inst.m_NextInst = inst; + inst.m_PrevInst = inst; + } + return inst; + } + + /** + * @brief Remove arbitrary element from queue, if any + * @param inst = element to remove (assumed to be in the queue) + * @returns with element removed + */ + public void Remove(XMRInstance inst) + { + XMRInstance next = inst.m_NextInst; + XMRInstance prev = inst.m_PrevInst; + if ((prev == inst) || (next == inst)) { + throw new Exception("not in a list"); + } + if (next == null) { + if (m_Tail != inst) { + throw new Exception("not in this list"); + } + m_Tail = prev; + } else { + next.m_PrevInst = prev; + } + if (prev == null) { + if (m_Head != inst) { + throw new Exception("not in this list"); + } + m_Head = next; + } else { + prev.m_NextInst = next; + } + inst.m_NextInst = inst; + inst.m_PrevInst = inst; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs new file mode 100644 index 0000000..61ae549 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs @@ -0,0 +1,1051 @@ +/* + * 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 System; +using System.Threading; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Reflection.Emit; +using System.Runtime.Remoting.Lifetime; +using System.Security.Policy; +using System.IO; +using System.Xml; +using System.Text; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Region.ScriptEngine.XMREngine; +using OpenSim.Region.Framework.Scenes; +using log4net; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public partial class XMRInstance + { + /************************************************************************************\ + * This module contains these externally useful methods: * + * PostEvent() - queues an event to script and wakes script thread to process it * + * RunOne() - runs script for a time slice or until it volunteers to give up cpu * + * CallSEH() - runs in the microthread to call the event handler * + \************************************************************************************/ + + /** + * @brief This can be called in any thread (including the script thread itself) + * to queue event to script for processing. + */ + public void PostEvent(EventParams evt) + { + ScriptEventCode evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode), + evt.EventName); + + /* + * Put event on end of event queue. + */ + bool startIt = false; + bool wakeIt = false; + lock (m_QueueLock) + { + bool construct = (m_IState == XMRInstState.CONSTRUCT); + + /* + * Ignore event if we don't even have such an handler in any state. + * We can't be state-specific here because state might be different + * by the time this event is dequeued and delivered to the script. + */ + if (!construct && // make sure m_HaveEventHandlers is filled in + ((uint)evc < (uint)m_HaveEventHandlers.Length) && + !m_HaveEventHandlers[(int)evc]) { // don't bother if we don't have such a handler in any state + return; + } + + /* + * Not running means we ignore any incoming events. + * But queue if still constructing because m_Running is not yet valid. + */ + if (!m_Running && !construct) { + return; + } + + /* + * Only so many of each event type allowed to queue. + */ + if ((uint)evc < (uint)m_EventCounts.Length) { + int maxAllowed = MAXEVENTQUEUE; + if (evc == ScriptEventCode.timer) maxAllowed = 1; + if (m_EventCounts[(int)evc] >= maxAllowed) + { + return; + } + m_EventCounts[(int)evc] ++; + } + + /* + * Put event on end of instance's event queue. + */ + LinkedListNode lln = new LinkedListNode(evt); + switch (evc) { + + /* + * These need to go first. The only time we manually + * queue them is for the default state_entry() and we + * need to make sure they go before any attach() events + * so the heapLimit value gets properly initialized. + */ + case ScriptEventCode.state_entry: { + m_EventQueue.AddFirst(lln); + break; + } + + /* + * The attach event sneaks to the front of the queue. + * This is needed for quantum limiting to work because + * we want the attach(NULL_KEY) event to come in front + * of all others so the m_DetachQuantum won't run out + * before attach(NULL_KEY) is executed. + */ + case ScriptEventCode.attach: { + if (evt.Params[0].ToString() == UUID.Zero.ToString()) + { + LinkedListNode lln2 = null; + for (lln2 = m_EventQueue.First; lln2 != null; lln2 = lln2.Next) { + EventParams evt2 = lln2.Value; + ScriptEventCode evc2 = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode), + evt2.EventName); + if ((evc2 != ScriptEventCode.state_entry) && + (evc2 != ScriptEventCode.attach)) break; + } + if (lln2 == null) { + m_EventQueue.AddLast(lln); + } else { + m_EventQueue.AddBefore(lln2, lln); + } + /* If we're detaching, limit the qantum. This will also + * cause the script to self-suspend after running this + * event + */ + + m_DetachReady.Reset(); + m_DetachQuantum = 100; + } + else + { + m_EventQueue.AddLast(lln); + } + break; + } + + /* + * All others just go on end in the order queued. + */ + default: { + m_EventQueue.AddLast(lln); + break; + } + } + + /* + * If instance is idle (ie, not running or waiting to run), + * flag it to be on m_StartQueue as we are about to do so. + * Flag it now before unlocking so another thread won't try + * to do the same thing right now. + * Dont' flag it if it's still suspended! + */ + if ((m_IState == XMRInstState.IDLE) && !m_Suspended) { + m_IState = XMRInstState.ONSTARTQ; + startIt = true; + } + + /* + * If instance is sleeping (ie, possibly in xmrEventDequeue), + * wake it up if event is in the mask. + */ + if ((m_SleepUntil > DateTime.UtcNow) && !m_Suspended) { + int evc1 = (int)evc; + int evc2 = evc1 - 32; + if ((((uint)evc1 < (uint)32) && (((m_SleepEventMask1 >> evc1) & 1) != 0)) || + (((uint)evc2 < (uint)32) && (((m_SleepEventMask2 >> evc2) & 1) != 0))) { + wakeIt = true; + } + } + } + + /* + * If transitioned from IDLE->ONSTARTQ, actually go insert it + * on m_StartQueue and give the RunScriptThread() a wake-up. + */ + if (startIt) { + m_Engine.QueueToStart(this); + } + + /* + * Likewise, if the event mask triggered a wake, wake it up. + */ + if (wakeIt) { + m_SleepUntil = DateTime.MinValue; + m_Engine.WakeFromSleep(this); + } + } + + /* + * This is called in the script thread to step script until it calls + * CheckRun(). It returns what the instance's next state should be, + * ONSLEEPQ, ONYIELDQ, SUSPENDED or FINISHED. + */ + public XMRInstState RunOne() + { + DateTime now = DateTime.UtcNow; + + /* + * If script has called llSleep(), don't do any more until time is + * up. + */ + m_RunOnePhase = "check m_SleepUntil"; + if (m_SleepUntil > now) + { + m_RunOnePhase = "return is sleeping"; + return XMRInstState.ONSLEEPQ; + } + + /* + * Also, someone may have called Suspend(). + */ + m_RunOnePhase = "check m_SuspendCount"; + if (m_SuspendCount > 0) { + m_RunOnePhase = "return is suspended"; + return XMRInstState.SUSPENDED; + } + + /* + * Make sure we aren't being migrated in or out and prevent that + * whilst we are in here. If migration has it locked, don't call + * back right away, delay a bit so we don't get in infinite loop. + */ + m_RunOnePhase = "lock m_RunLock"; + if (!Monitor.TryEnter (m_RunLock)) { + m_SleepUntil = now.AddMilliseconds(3); + m_RunOnePhase = "return was locked"; + return XMRInstState.ONSLEEPQ; + } + try + { + m_RunOnePhase = "check entry invariants"; + CheckRunLockInvariants(true); + Exception e = null; + + /* + * Maybe we have been disposed. + */ + m_RunOnePhase = "check disposed"; + if (microthread == null) + { + m_RunOnePhase = "return disposed"; + return XMRInstState.DISPOSED; + } + + /* + * Do some more of the last event if it didn't finish. + */ + else if (this.eventCode != ScriptEventCode.None) + { + lock (m_QueueLock) + { + if (m_DetachQuantum > 0 && --m_DetachQuantum == 0) + { + m_Suspended = true; + m_DetachReady.Set(); + m_RunOnePhase = "detach quantum went zero"; + CheckRunLockInvariants(true); + return XMRInstState.FINISHED; + } + } + + m_RunOnePhase = "resume old event handler"; + m_LastRanAt = now; + m_InstEHSlice ++; + callMode = CallMode_NORMAL; + e = microthread.ResumeEx (); + } + + /* + * Otherwise, maybe we can dequeue a new event and start + * processing it. + */ + else + { + m_RunOnePhase = "lock event queue"; + EventParams evt = null; + ScriptEventCode evc = ScriptEventCode.None; + + lock (m_QueueLock) + { + + /* We can't get here unless the script has been resumed + * after creation, then suspended again, and then had + * an event posted to it. We just pretend there is no + * event int he queue and let the normal mechanics + * carry out the suspension. A Resume will handle the + * restarting gracefully. This is taking the easy way + * out and may be improved in the future. + */ + + if (m_Suspended) + { + m_RunOnePhase = "m_Suspended is set"; + CheckRunLockInvariants(true); + return XMRInstState.FINISHED; + } + + m_RunOnePhase = "dequeue event"; + if (m_EventQueue.First != null) + { + evt = m_EventQueue.First.Value; + if (m_DetachQuantum > 0) + { + evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode), + evt.EventName); + if (evc != ScriptEventCode.attach) + { + /* + * This is the case where the attach event + * has completed and another event is queued + * Stop it from running and suspend + */ + m_Suspended = true; + m_DetachReady.Set(); + m_DetachQuantum = 0; + m_RunOnePhase = "nothing to do #3"; + CheckRunLockInvariants(true); + return XMRInstState.FINISHED; + } + } + m_EventQueue.RemoveFirst(); + evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode), + evt.EventName); + if ((int)evc >= 0) m_EventCounts[(int)evc] --; + } + + /* + * If there is no event to dequeue, don't run this script + * until another event gets queued. + */ + if (evt == null) + { + if (m_DetachQuantum > 0) + { + /* + * This will happen if the attach event has run + * and exited with time slice left. + */ + m_Suspended = true; + m_DetachReady.Set(); + m_DetachQuantum = 0; + } + m_RunOnePhase = "nothing to do #4"; + CheckRunLockInvariants(true); + return XMRInstState.FINISHED; + } + } + + /* + * Dequeued an event, so start it going until it either + * finishes or it calls CheckRun(). + */ + m_RunOnePhase = "start event handler"; + m_DetectParams = evt.DetectParams; + m_LastRanAt = now; + m_InstEHEvent ++; + e = StartEventHandler (evc, evt.Params); + } + m_RunOnePhase = "done running"; + m_CPUTime += DateTime.UtcNow.Subtract(now).TotalMilliseconds; + + /* + * Maybe it puqued. + */ + if (e != null) + { + m_RunOnePhase = "handling exception " + e.Message; + HandleScriptException(e); + m_RunOnePhase = "return had exception " + e.Message; + CheckRunLockInvariants(true); + return XMRInstState.FINISHED; + } + + /* + * If event handler completed, get rid of detect params. + */ + if (this.eventCode == ScriptEventCode.None) + { + m_DetectParams = null; + } + } + finally + { + m_RunOnePhase += "; checking exit invariants and unlocking"; + CheckRunLockInvariants(false); + Monitor.Exit(m_RunLock); + } + + /* + * Cycle script through the yield queue and call it back asap. + */ + m_RunOnePhase = "last return"; + return XMRInstState.ONYIELDQ; + } + + /** + * @brief Immediately after taking m_RunLock or just before releasing it, check invariants. + */ + private ScriptEventCode lastEventCode = ScriptEventCode.None; + private int lastActive = 0; + private string lastRunPhase = ""; + + public void CheckRunLockInvariants(bool throwIt) + { + /* + * If not executing any event handler, active should be 0 indicating the microthread stack is not in use. + * If executing an event handler, active should be -1 indicating stack is in use but suspended. + */ + IScriptUThread uth = microthread; + if (uth != null) { + int active = uth.Active (); + ScriptEventCode ec = this.eventCode; + if (((ec == ScriptEventCode.None) && (active != 0)) || + ((ec != ScriptEventCode.None) && (active >= 0))) { + Console.WriteLine("CheckRunLockInvariants: script=" + m_DescName); + Console.WriteLine("CheckRunLockInvariants: eventcode=" + ec.ToString() + ", active=" + active.ToString()); + Console.WriteLine("CheckRunLockInvariants: m_RunOnePhase=" + m_RunOnePhase); + Console.WriteLine("CheckRunLockInvariants: lastec=" + lastEventCode + ", lastAct=" + lastActive + ", lastPhase=" + lastRunPhase); + if (throwIt) { + throw new Exception("CheckRunLockInvariants: eventcode=" + ec.ToString() + ", active=" + active.ToString()); + } + } + lastEventCode = ec; + lastActive = active; + lastRunPhase = m_RunOnePhase; + } + } + + /* + * Start event handler. + * + * Input: + * eventCode = code of event to be processed + * ehArgs = arguments for the event handler + * + * Caution: + * It is up to the caller to make sure ehArgs[] is correct for + * the particular event handler being called. The first thing + * a script event handler method does is to unmarshall the args + * from ehArgs[] and will throw an array bounds or cast exception + * if it can't. + */ + private Exception StartEventHandler (ScriptEventCode eventCode, object[] ehArgs) + { + /* + * We use this.eventCode == ScriptEventCode.None to indicate we are idle. + * So trying to execute ScriptEventCode.None might make a mess. + */ + if (eventCode == ScriptEventCode.None) { + return new Exception ("Can't process ScriptEventCode.None"); + } + + /* + * Silly to even try if there is no handler defined for this event. + */ + if (((int)eventCode >= 0) && (m_ObjCode.scriptEventHandlerTable[this.stateCode,(int)eventCode] == null)) { + return null; + } + + /* + * The microthread shouldn't be processing any event code. + * These are assert checks so we throw them directly as exceptions. + */ + if (this.eventCode != ScriptEventCode.None) { + throw new Exception ("still processing event " + this.eventCode.ToString ()); + } + int active = microthread.Active (); + if (active != 0) { + throw new Exception ("microthread is active " + active.ToString ()); + } + + /* + * Save eventCode so we know what event handler to run in the microthread. + * And it also marks us busy so we can't be started again and this event lost. + */ + this.eventCode = eventCode; + this.ehArgs = ehArgs; + + /* + * This calls ScriptUThread.Main() directly, and returns when Main() [indirectly] + * calls Suspend() or when Main() returns, whichever occurs first. + * Setting stackFrames = null means run the event handler from the beginning + * without doing any stack frame restores first. + */ + this.stackFrames = null; + Exception e; + e = microthread.StartEx (); + return e; + } + + + /** + * @brief There was an exception whilst starting/running a script event handler. + * Maybe we handle it directly or just print an error message. + */ + private void HandleScriptException(Exception e) + { + /* + * The script threw some kind of exception that was not caught at + * script level, so the script is no longer running an event handler. + */ + eventCode = ScriptEventCode.None; + + if (e is ScriptDeleteException) + { + /* + * Script did something like llRemoveInventory(llGetScriptName()); + * ... to delete itself from the object. + */ + m_SleepUntil = DateTime.MaxValue; + Verbose ("[XMREngine]: script self-delete {0}", m_ItemID); + m_Part.Inventory.RemoveInventoryItem(m_ItemID); + } + else if (e is ScriptDieException) + { + /* + * Script did an llDie() + */ + m_RunOnePhase = "dying..."; + m_SleepUntil = DateTime.MaxValue; + m_Engine.World.DeleteSceneObject(m_Part.ParentGroup, false); + } + else if (e is ScriptResetException) + { + /* + * Script did an llResetScript(). + */ + m_RunOnePhase = "resetting..."; + ResetLocked("HandleScriptResetException"); + } + else + { + /* + * Some general script error. + */ + SendErrorMessage(e); + } + return; + } + + /** + * @brief There was an exception running script event handler. + * Display error message and disable script (in a way + * that the script can be reset to be restarted). + */ + private void SendErrorMessage(Exception e) + { + StringBuilder msg = new StringBuilder(); + + msg.Append ("[XMREngine]: Exception while running "); + msg.Append (m_ItemID); + msg.Append ('\n'); + + /* + * Add exception message. + */ + string des = e.Message; + des = (des == null) ? "" : (": " + des); + msg.Append (e.GetType ().Name + des + "\n"); + + /* + * Tell script owner what to do. + */ + msg.Append ("Prim: <"); + msg.Append (m_Part.Name); + msg.Append (">, Script: <"); + msg.Append (m_Item.Name); + msg.Append (">, Location: "); + msg.Append (m_Engine.World.RegionInfo.RegionName); + msg.Append (" <"); + Vector3 pos = m_Part.AbsolutePosition; + msg.Append ((int) Math.Floor (pos.X)); + msg.Append (','); + msg.Append ((int) Math.Floor (pos.Y)); + msg.Append (','); + msg.Append ((int) Math.Floor (pos.Z)); + msg.Append (">\nScript must be Reset to re-enable.\n"); + + /* + * Display full exception message in log. + */ + m_log.Info (msg.ToString() + XMRExceptionStackString (e), e); + + /* + * Give script owner the stack dump. + */ + msg.Append (XMRExceptionStackString (e)); + + /* + * Send error message to owner. + * Suppress internal code stack trace lines. + */ + string msgst = msg.ToString(); + if (!msgst.EndsWith ("\n")) msgst += '\n'; + int j = 0; + StringBuilder imstr = new StringBuilder (); + for (int i = 0; (i = msgst.IndexOf ('\n', i)) >= 0; j = ++ i) { + string line = msgst.Substring (j, i - j); + if (line.StartsWith ("at ")) { + if (line.StartsWith ("at (wrapper")) continue; // at (wrapper ... + int k = line.LastIndexOf (".cs:"); // ... .cs:linenumber + if (Int32.TryParse (line.Substring (k + 4), out k)) continue; + } + this.llOwnerSay (line); + imstr.Append (line); + imstr.Append ('\n'); + } + + /* + * Send as instant message in case user not online. + * Code modelled from llInstantMessage(). + */ + IMessageTransferModule transferModule = m_Engine.World.RequestModuleInterface(); + if (transferModule != null) { + UUID friendTransactionID = UUID.Random(); + GridInstantMessage gim = new GridInstantMessage(); + gim.fromAgentID = new Guid (m_Part.UUID.ToString()); + gim.toAgentID = new Guid (m_Part.OwnerID.ToString ()); + gim.imSessionID = new Guid(friendTransactionID.ToString()); + gim.timestamp = (uint)Util.UnixTimeSinceEpoch(); + gim.message = imstr.ToString (); + gim.dialog = (byte)19; // messgage from script + gim.fromGroup = false; + gim.offline = (byte)0; + gim.ParentEstateID = 0; + gim.Position = pos; + gim.RegionID = m_Engine.World.RegionInfo.RegionID.Guid; + gim.binaryBucket = Util.StringToBytes256( + "{0}/{1}/{2}/{3}", + m_Engine.World.RegionInfo.RegionName, + (int)Math.Floor(pos.X), + (int)Math.Floor(pos.Y), + (int)Math.Floor(pos.Z)); + transferModule.SendInstantMessage(gim, delegate(bool success) {}); + } + + /* + * Say script is sleeping for a very long time. + * Reset() is able to cancel this sleeping. + */ + m_SleepUntil = DateTime.MaxValue; + } + + /** + * @brief The user clicked the Reset Script button. + * We want to reset the script to a never-has-ever-run-before state. + */ + public void Reset() + { + checkstate: + XMRInstState iState = m_IState; + switch (iState) { + + /* + * If it's really being constructed now, that's about as reset as we get. + */ + case XMRInstState.CONSTRUCT: { + return; + } + + /* + * If it's idle, that means it is ready to receive a new event. + * So we lock the event queue to prevent another thread from taking + * it out of idle, verify that it is still in idle then transition + * it to resetting so no other thread will touch it. + */ + case XMRInstState.IDLE: { + lock (m_QueueLock) { + if (m_IState == XMRInstState.IDLE) { + m_IState = XMRInstState.RESETTING; + break; + } + } + goto checkstate; + } + + /* + * If it's on the start queue, that means it is about to dequeue an + * event and start processing it. So we lock the start queue so it + * can't be started and transition it to resetting so no other thread + * will touch it. + */ + case XMRInstState.ONSTARTQ: { + lock (m_Engine.m_StartQueue) { + if (m_IState == XMRInstState.ONSTARTQ) { + m_Engine.m_StartQueue.Remove(this); + m_IState = XMRInstState.RESETTING; + break; + } + } + goto checkstate; + } + + /* + * If it's running, tell CheckRun() to suspend the thread then go back + * to see what it got transitioned to. + */ + case XMRInstState.RUNNING: { + suspendOnCheckRunHold = true; + lock (m_QueueLock) { } + goto checkstate; + } + + /* + * If it's sleeping, remove it from sleep queue and transition it to + * resetting so no other thread will touch it. + */ + case XMRInstState.ONSLEEPQ: { + lock (m_Engine.m_SleepQueue) { + if (m_IState == XMRInstState.ONSLEEPQ) { + m_Engine.m_SleepQueue.Remove(this); + m_IState = XMRInstState.RESETTING; + break; + } + } + goto checkstate; + } + + /* + * It was just removed from the sleep queue and is about to be put + * on the yield queue (ie, is being woken up). + * Let that thread complete transition and try again. + */ + case XMRInstState.REMDFROMSLPQ: { + Sleep (10); + goto checkstate; + } + + /* + * If it's yielding, remove it from yield queue and transition it to + * resetting so no other thread will touch it. + */ + case XMRInstState.ONYIELDQ: { + lock (m_Engine.m_YieldQueue) { + if (m_IState == XMRInstState.ONYIELDQ) { + m_Engine.m_YieldQueue.Remove(this); + m_IState = XMRInstState.RESETTING; + break; + } + } + goto checkstate; + } + + /* + * If it just finished running something, let that thread transition it + * to its next state then check again. + */ + case XMRInstState.FINISHED: { + Sleep (10); + goto checkstate; + } + + /* + * If it's disposed, that's about as reset as it gets. + */ + case XMRInstState.DISPOSED: { + return; + } + + /* + * Some other thread is already resetting it, let it finish. + */ + case XMRInstState.RESETTING: { + return; + } + + default: throw new Exception("bad state"); + } + + /* + * This thread transitioned the instance to RESETTING so reset it. + */ + lock (m_RunLock) { + CheckRunLockInvariants(true); + + /* + * No other thread should have transitioned it from RESETTING. + */ + if (m_IState != XMRInstState.RESETTING) throw new Exception("bad state"); + + /* + * If the microthread is active, that means it has call frame + * context that we don't want. Throw it out and get a fresh one. + */ + if (microthread.Active () < 0) { + microthread.Dispose (); + microthread = (IScriptUThread)m_Engine.uThreadCtor.Invoke (new object[] { this }); + } + + /* + * Mark it idle now so it can get queued to process new stuff. + */ + m_IState = XMRInstState.IDLE; + + /* + * Reset everything and queue up default's start_entry() event. + */ + ClearQueue(); + ResetLocked("external Reset"); + + CheckRunLockInvariants(true); + } + } + + private void ClearQueueExceptLinkMessages() + { + lock (m_QueueLock) { + EventParams[] linkMessages = new EventParams[m_EventQueue.Count]; + int n = 0; + foreach (EventParams evt2 in m_EventQueue) { + if (evt2.EventName == "link_message") { + linkMessages[n++] = evt2; + } + } + + m_EventQueue.Clear(); + for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0; + + for (int i = 0; i < n; i ++) { + m_EventQueue.AddLast(linkMessages[i]); + } + + m_EventCounts[(int)ScriptEventCode.link_message] = n; + } + } + + private void ClearQueue() + { + lock (m_QueueLock) + { + m_EventQueue.Clear(); // no events queued + for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0; + } + } + + /** + * @brief The script called llResetScript() while it was running and + * has suspended. We want to reset the script to a never-has- + * ever-run-before state. + * + * Caller must have m_RunLock locked so we know script isn't + * running. + */ + private void ResetLocked(string from) + { + m_RunOnePhase = "ResetLocked: releasing controls"; + ReleaseControls(); + + m_RunOnePhase = "ResetLocked: removing script"; + m_Part.Inventory.GetInventoryItem(m_ItemID).PermsMask = 0; + m_Part.Inventory.GetInventoryItem(m_ItemID).PermsGranter = UUID.Zero; + IUrlModule urlModule = m_Engine.World.RequestModuleInterface(); + if (urlModule != null) + urlModule.ScriptRemoved(m_ItemID); + + AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID); + + m_RunOnePhase = "ResetLocked: clearing current event"; + this.eventCode = ScriptEventCode.None; // not processing an event + m_DetectParams = null; // not processing an event + m_SleepUntil = DateTime.MinValue; // not doing llSleep() + m_ResetCount ++; // has been reset once more + + /* + * Tell next call to 'default state_entry()' to reset all global + * vars to their initial values. + */ + doGblInit = true; + + /* + * Set script to 'default' state and queue call to its + * 'state_entry()' event handler. + */ + m_RunOnePhase = "ResetLocked: posting default:state_entry() event"; + stateCode = 0; + m_Part.SetScriptEvents(m_ItemID, GetStateEventFlags(0)); + PostEvent(new EventParams("state_entry", + zeroObjectArray, + zeroDetectParams)); + + /* + * Tell CheckRun() to let script run. + */ + suspendOnCheckRunHold = false; + suspendOnCheckRunTemp = false; + m_RunOnePhase = "ResetLocked: reset complete"; + } + + private void ReleaseControls() + { + if (m_Part != null) + { + bool found; + int permsMask; + UUID permsGranter; + + try { + permsGranter = m_Part.TaskInventory[m_ItemID].PermsGranter; + permsMask = m_Part.TaskInventory[m_ItemID].PermsMask; + found = true; + } catch { + permsGranter = UUID.Zero; + permsMask = 0; + found = false; + } + + if (found && ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0)) { + ScenePresence presence = m_Engine.World.GetScenePresence(permsGranter); + if (presence != null) { + presence.UnRegisterControlEventsToScript(m_LocalID, m_ItemID); + } + } + } + } + + /** + * @brief The script code should call this routine whenever it is + * convenient to perform a migation or switch microthreads. + */ + public override void CheckRunWork () + { + m_CheckRunPhase = "entered"; + + /* + * Stay stuck in this loop as long as something wants us suspended. + */ + while (suspendOnCheckRunHold || suspendOnCheckRunTemp) { + m_CheckRunPhase = "top of while"; + + /* + * See if MigrateOutEventHandler() has been called. + * If so, dump our stack to stackFrames and unwind. + */ + if (this.captureStackFrames) { + + /* + * Puque our stack to the output stream. + * But otherwise, our state remains intact. + */ + m_CheckRunPhase = "saving"; + this.callMode = CallMode_SAVE; + this.stackFrames = null; + throw new StackCaptureException (); + } + + /* + * We get here when the script state has been read in by MigrateInEventHandler(). + * Since the stack is completely restored at this point, any subsequent calls + * within the functions should do their normal processing instead of trying to + * restore their state. + */ + if (this.callMode == CallMode_RESTORE) { + stackFramesRestored = true; + this.callMode = CallMode_NORMAL; + } + + /* + * Now we are ready to suspend the microthread. + * This is like a longjmp() to the most recent StartEx() or ResumeEx() + * with a simultaneous setjmp() so ResumeEx() can longjmp() back here. + */ + m_CheckRunPhase = "suspending"; + suspendOnCheckRunTemp = false; + microthread.Hiber (); + m_CheckRunPhase = "resumed"; + } + + m_CheckRunPhase = "returning"; + + /* + * Upon return from CheckRun() it should always be the case that the script is + * going to process calls normally, neither saving nor restoring stack frame state. + */ + if (callMode != CallMode_NORMAL) throw new Exception ("bad callMode " + callMode); + } + + /** + * @brief Allow script to dequeue events. + */ + public void ResumeIt() + { + lock (m_QueueLock) + { + m_Suspended = false; + if ((m_EventQueue != null) && + (m_EventQueue.First != null) && + (m_IState == XMRInstState.IDLE)) { + m_IState = XMRInstState.ONSTARTQ; + m_Engine.QueueToStart(this); + } + m_HasRun = true; + } + } + + /** + * @brief Block script from dequeuing events. + */ + public void SuspendIt() + { + lock (m_QueueLock) + { + m_Suspended = true; + } + } + } + + /** + * @brief Thrown by CheckRun() to unwind the script stack, capturing frames to + * instance.stackFrames as it unwinds. We don't want scripts to be able + * to intercept this exception as it would block the stack capture + * functionality. + */ + public class StackCaptureException : Exception, IXMRUncatchable { } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstSorpra.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstSorpra.cs new file mode 100644 index 0000000..dd60cb2 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstSorpra.cs @@ -0,0 +1,76 @@ +/* + * 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 OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using System; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public partial class XMRInstance + { + /** + * @brief If RegionCrossing trapping is enabled, any attempt to move the object + * outside its current region will cause the event to fire and the object + * will remain in its current region. + */ + public override void xmrTrapRegionCrossing (int en) + { } + + /** + * @brief Move object to new position and rotation asynchronously. + * Can move object across region boundary. + * @param pos = new position within current region (same coords as llGetPos()) + * @param rot = new rotation within current region (same coords as llGetRot()) + * @param options = not used + * @param evcode = not used + * @param evargs = arguments to pass to event handler + * @returns false: completed synchronously, no event will be queued + */ + public const double Sorpra_MIN_CROSS = 1.0 / 512.0; // ie, ~2mm + public const int Sorpra_TIMEOUT_MS = 30000; // ie, 30sec + public override bool xmrSetObjRegPosRotAsync (LSL_Vector pos, LSL_Rotation rot, int options, int evcode, LSL_List evargs) + { + // do the move + SceneObjectGroup sog = m_Part.ParentGroup; + sog.UpdateGroupRotationPR (pos, rot); + + // it is always synchronous + return false; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRObjectTokens.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRObjectTokens.cs new file mode 100644 index 0000000..f214f28 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRObjectTokens.cs @@ -0,0 +1,5476 @@ +/* + * 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 xmrobj file. + * See xmrengcomp.cx utility program. + */ + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + /* + * 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 labelNames; + private Dictionary 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 (); + localNames = new Dictionary (); + + 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 typeTranslator = InitTypeTranslator (); + private static Dictionary InitTypeTranslator () + { + Dictionary d = new Dictionary (); + 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 eharglist; + private Dictionary labels; + private Dictionary locals; + private Dictionary methargnames; + private LinkedList cilinstrs; + private OTStmtBlock topBlock; + private Stack opstack; + private Stack trystack; + private Stack 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 (); + } + + 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 (); + labels = new Dictionary (); + locals = new Dictionary (); + cilinstrs = new LinkedList (); + opstack = new Stack (); + trystack = new Stack (); + blockstack = new Stack (); + + 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 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_; 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 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 namecounts = new Dictionary (); + foreach (Dictionary 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 inittypes = new Dictionary (); + 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 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.XMREngine.")) { + 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 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 link) + { + OTStmtLabel.AddLast (decompile, this); + } + } + + /* + * 'try {' + */ + private class OTCilBegExcBlk : OTCilInstr { + public LinkedList catches = new LinkedList (); + + public OTCilBegExcBlk (int offset) : base (offset) + { } + + public override string DumpString () + { + return "try {"; + } + + public override void BuildStatements (OTDecompile decompile, LinkedListNode 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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[] is a reference to a global variable + * likewise so is __xmrinst.glblVars.iar[] + */ + if ((array is OTOpndField) && (index is OTOpndInt)) { + + /* + * arrayfield = (arg$0.glblVars).iar + * arrayfieldobj = arg$0.glblVars + * iartypename = iar + */ + 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 xor1ops = InitXor1Ops (); + + private static Dictionary InitXor1Ops () + { + Dictionary d = new Dictionary (); + 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 precedence = InitPrecedence (); + private static Dictionary InitPrecedence () + { + Dictionary d = new Dictionary (); + 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 mathmeths = InitMathMeths (); + private static Dictionary InitMathMeths () + { + Dictionary d = new Dictionary (); + 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 + * 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 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$ = newarr object[] << link points here + * dup$[0] = bla + * dup$[1] = bla + * ... + * ... newobj list (dup$) ... + */ + public static bool Detect (LinkedListNode link) + { + if (link == null) return false; + + /* + * Check for 'dup$ = newarr object[]' and get listsize from . + */ + 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$[] = bla + * If so, save the bla values in the values[] array. + */ + LinkedListNode 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$)' 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 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) => + 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) => + 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 binops = InitBinops (); + private static Dictionary InitBinops () + { + Dictionary d = new Dictionary (); + 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 brfops = InitBrfOps (); + private static Dictionary InitBrfOps () + { + Dictionary d = new Dictionary (); + 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 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 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 link) + { + // strip all statements following jump until seeing some label + bool rc = false; + if (link != null) { + LinkedListNode 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 link) + { + return catchblock.StripStuff (null); + } + + public override bool ReplaceOperand (OTOpnd oldopnd, OTOpnd newopnd) + { + return catchblock.ReplaceOperand (oldopnd, newopnd); + } + + public override bool DetectDoForIfWhile (LinkedListNode 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 catches = new LinkedList (); + + // 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 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 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 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 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 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 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 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 ScanForLabel (LinkedListNode 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 link, LinkedListNode donelink) + { + LinkedListNode 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 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 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 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 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 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 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 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 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 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 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 blkstmts = new LinkedList (); + + 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 link) + { + // loop through all sub-statements to strip out behind-the-scenes references + bool rc = false; + loop: + for (LinkedListNode 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 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 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 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_; << link points here + * ... ... + * [ if (dotest) ] jump doloop_; + */ + public static bool Detect (LinkedListNode 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 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 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 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 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... + * + * @forloop_; << link points here + * [ if () jump forbreak_; ] + * ... ... + * jump forloop_; + * [ @forbreak_; ] + */ + public static bool Detect (LinkedListNode 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_;' statement + // also gather up the statements for the for body block + it.forbody = new OTStmtBlock (); + LinkedListNode 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 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 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 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_; << link points here + * ... then body ... + * @ifdone_; + * + * if (condition) jump ifelse_; + * ... then body ... + * jump ifdone_; << optional if true body doesn't fall through + * @ifelse_; + * ... else body ... + * @ifdone_; + */ + public static bool Detect (LinkedListNode 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_ label + // also save the intervening statements for the then body + OTStmtBlock thenbody; + LinkedListNode donelink = ScanForLabel (link, jumpstmt.label, out thenbody); + + // make sure we found matching label + if (donelink == null) return false; + + // replace the jump ifdone_ with the + 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_ 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_ label + // also save the intervening statements for the true body + OTStmtBlock thenbody; + LinkedListNode 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_ + LinkedListNode 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_ label + // also save the intervening statements for the else body + OTStmtBlock elsebody; + LinkedListNode donelink = ScanForLabel (elselink, jumpifdone.label, out elsebody); + if (donelink != null) { + + // replace the jump ifdone_ with the + 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_ 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_, so make it a simple if/then + // if (condition) jump ifelse_; << link + // ... then body ... << encapsulated in block thenbody + // @ifelse_; << elselink + // ... else body ... << still inline and leave it there + // @ifdone_; << strip this out + + // replace the jump ifelse_ with the + 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_ label + StripInterveningStatements (link, elselink); + + // there's a dangling unused ifdone_ label ahead that has to be stripped + for (LinkedListNode 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 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 ScanForLabel (LinkedListNode 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 link, LinkedListNode donelink) + { + LinkedListNode 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 myopsbyname = new Dictionary (); + 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", "!="); + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRSDTypeClObj.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRSDTypeClObj.cs new file mode 100644 index 0000000..cbb8f96 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRSDTypeClObj.cs @@ -0,0 +1,259 @@ +/* + * 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 System; +using System.Reflection.Emit; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + public class XMRSDTypeClObj + { + /* + * Which script instance we are part of so we can access + * the script's global variables and functions. + */ + public XMRInstAbstract xmrInst; + + /* + * What class we actually are in the hierarchy + * used for casting. + */ + public TokenDeclSDTypeClass sdtcClass; + + /* + * Our VTable array, used for calling virtual functions. + * And ITable array, used for calling our implementation of interface functions. + */ + public Delegate[] sdtcVTable; + public Delegate[][] sdtcITable; + + /* + * These arrays hold the insance variable values. + * The array lengths are determined by the script compilation, + * and are found in TokenDeclSDTypeClass.instSizes. + */ + public XMRInstArrays instVars; + + /** + * @brief Called by script's $new() to initialize a new object. + */ + public XMRSDTypeClObj (XMRInstAbstract inst, int classindex) + { + Construct (inst, classindex); + instVars.AllocVarArrays (sdtcClass.instSizes); + } + + /** + * @brief Set up everything except the instVars arrays. + * @param inst = script instance this object is part of + * @param classindex = which script-defined type class this object is an onstance of + * @returns with the vtables filled in + */ + private void Construct (XMRInstAbstract inst, int classindex) + { + Delegate[] thisMid = null; + TokenDeclSDTypeClass clas = (TokenDeclSDTypeClass)inst.m_ObjCode.sdObjTypesIndx[classindex]; + + xmrInst = inst; + sdtcClass = clas; + instVars = new XMRInstArrays (inst); + + /* + * VTable consists of delegates built from DynamicMethods and the 'this' pointer. + * Yes, yes, lots of shitty little mallocs. + */ + DynamicMethod[] vDynMeths = clas.vDynMeths; + if (vDynMeths != null) { + int n = vDynMeths.Length; + Type[] vMethTypes = clas.vMethTypes; + sdtcVTable = new Delegate[n]; + for (int i = 0; i < n; i ++) { + sdtcVTable[i] = vDynMeths[i].CreateDelegate (vMethTypes[i], this); + } + } + + /* + * Fill in interface vtables. + * There is one array of delegates for each implemented interface. + * The array of delegates IS the interface's object, ie, the 'this' value of the interface. + * To cast from the class type to the interface type, just get the correct array from the table. + * To cast from the interface type to the class type, just get Target of entry 0. + * + * So we end up with this: + * sdtcITable[interfacenumber][methodofintfnumber] = delegate of this.ourimplementationofinterfacesmethod + */ + if (clas.iDynMeths != null) { + int nIFaces = clas.iDynMeths.Length; + sdtcITable = new Delegate[nIFaces][]; + for (int i = 0; i < nIFaces; i ++) { + + // get vector of entrypoints of our instance methods that implement that interface + DynamicMethod[] iDynMeths = clas.iDynMeths[i]; + Type[] iMethTypes = clas.iMethTypes[i]; + + // allocate an array with a slot for each method the interface defines + int nMeths = iDynMeths.Length; + Delegate[] ivec; + if (nMeths > 0) { + // fill in the array with delegates that reference back to this class instance + ivec = new Delegate[nMeths]; + for (int j = 0; j < nMeths; j ++) { + ivec[j] = iDynMeths[j].CreateDelegate (iMethTypes[j], this); + } + } else { + // just a marker interface with no methods, + // allocate a one-element array and fill + // with a dummy entry. this will allow casting + // back to the original class instance (this) + // by reading Target of entry 0. + if (thisMid == null) { + thisMid = new Delegate[1]; + thisMid[0] = markerInterfaceDummy.CreateDelegate (typeof (MarkerInterfaceDummy), this); + } + ivec = thisMid; + } + + // save whatever we ended up allocating + sdtcITable[i] = ivec; + } + } + } + + private delegate void MarkerInterfaceDummy (); + private static DynamicMethod markerInterfaceDummy = MakeMarkerInterfaceDummy (); + private static DynamicMethod MakeMarkerInterfaceDummy () + { + DynamicMethod dm = new DynamicMethod ("XMRSDTypeClObj.MarkerInterfaceDummy", null, new Type[] { typeof (XMRSDTypeClObj) }); + ILGenerator ilGen = dm.GetILGenerator (); + ilGen.Emit (OpCodes.Ret); + return dm; + } + + /** + * @brief Perform runtime casting of script-defined interface object to + * its corresponding script-defined class object. + * @param da = interface object (array of delegates pointing to class's implementations of interface's methods) + * @param classindex = what class those implementations are supposedly part of + * @returns original script-defined class object + */ + public static XMRSDTypeClObj CastIFace2Class (Delegate[] da, int classindex) + { + return CastClass2Class (da[0].Target, classindex); + } + + /** + * @brief Perform runtime casting of XMRSDTypeClObj's. + * @param ob = XMRSDTypeClObj of unknown script-defined class to cast + * @param classindex = script-defined class to cast it to + * @returns ob is a valid instance of classindex; else exception thrown + */ + public static XMRSDTypeClObj CastClass2Class (object ob, int classindex) + { + /* + * Let mono check to see if we at least have an XMRSDTypeClObj. + */ + XMRSDTypeClObj ci = (XMRSDTypeClObj)ob; + if (ci != null) { + + /* + * This is the target class, ie, what we are hoping the object can cast to. + */ + TokenDeclSDTypeClass tc = (TokenDeclSDTypeClass)ci.xmrInst.m_ObjCode.sdObjTypesIndx[classindex]; + + /* + * Step from the object's actual class rootward. + * If we find the target class along the way, the cast is valid. + * If we run off the end of the root, the cast is not valid. + */ + for (TokenDeclSDTypeClass ac = ci.sdtcClass; ac != tc; ac = ac.extends) { + if (ac == null) throw new InvalidCastException ("invalid cast from " + ci.sdtcClass.longName.val + + " to " + tc.longName.val); + } + + /* + * The target class is at or rootward of the actual class, + * so the cast is valid. + */ + } + return ci; + } + + /** + * @brief Cast an arbitrary object to the given interface. + * @param ob = object to be cast of unknown type + * @returns ob cast to the interface type + */ + public static Delegate[] CastObj2IFace (object ob, string ifacename) + { + if (ob == null) return null; + + /* + * If it is already one of our interfaces, extract the script-defined class object from it. + */ + if (ob is Delegate[]) { + Delegate[] da = (Delegate[])ob; + ob = da[0].Target; + } + + /* + * Now that we have a presumed script-defined class object, cast that to the requested interface + * by picking the array of delegates that corresponds to the requested interface. + */ + XMRSDTypeClObj ci = (XMRSDTypeClObj)ob; + int iFaceIndex = ci.sdtcClass.intfIndices[ifacename]; + return ci.sdtcITable[iFaceIndex]; + } + + /** + * @brief Write the whole thing out to a stream. + */ + public void Capture (XMRInstArrays.Sender sendValue) + { + sendValue (this.sdtcClass.sdTypeIndex); + this.instVars.SendArrays (sendValue); + } + + /** + * @brief Read the whole thing in from a stream. + */ + public XMRSDTypeClObj () { } + public void Restore (XMRInstAbstract inst, XMRInstArrays.Recver recvValue) + { + int classindex = (int)recvValue (); + Construct (inst, classindex); + this.instVars.RecvArrays (recvValue); + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptThread.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptThread.cs new file mode 100644 index 0000000..933b478 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptThread.cs @@ -0,0 +1,262 @@ +/* + * 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 Mono.Tasklets; +using OpenSim.Framework.Monitoring; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace OpenSim.Region.ScriptEngine.XMREngine +{ + + /** + * @brief There are NUMSCRIPTHREADWKRS of these. + * Each sits in a loop checking the Start and Yield queues for + * a script to run and calls the script as a microthread. + */ + public class XMRScriptThread { + private static int m_WakeUpOne = 0; + public static object m_WakeUpLock = new object(); + private static Dictionary m_AllThreads = new Dictionary (); + + /** + * @brief Something was just added to the Start or Yield queue so + * wake one of the XMRScriptThread instances to run it. + */ + public static void WakeUpOne() + { + lock (m_WakeUpLock) + { + m_WakeUpOne ++; + Monitor.Pulse (m_WakeUpLock); + } + } + + public static XMRScriptThread CurrentScriptThread () + { + XMRScriptThread st; + lock (m_AllThreads) { + m_AllThreads.TryGetValue (Thread.CurrentThread, out st); + } + return st; + } + + private bool m_Exiting = false; + private bool m_SuspendScriptThreadFlag = false; + private bool m_WakeUpThis = false; + private bool m_Continuations = false; + public DateTime m_LastRanAt = DateTime.MinValue; + public int m_ScriptThreadTID = 0; + public long m_ScriptExecTime = 0; + private Thread thd; + private XMREngine engine; + public XMRInstance m_RunInstance = null; + + public XMRScriptThread(XMREngine eng) + { + engine = eng; + m_Continuations = engine.uThreadCtor.DeclaringType == typeof (ScriptUThread_Con); + thd = XMREngine.StartMyThread (RunScriptThread, "xmrengine script", ThreadPriority.BelowNormal); + lock (m_AllThreads) { + m_AllThreads.Add (thd, this); + } + } + + public void SuspendThread() + { + m_SuspendScriptThreadFlag = true; + WakeUpScriptThread(); + } + + public void ResumeThread() + { + m_SuspendScriptThreadFlag = false; + WakeUpScriptThread(); + } + + public void Terminate() + { + m_Exiting = true; + WakeUpScriptThread(); + thd.Join(); + lock (m_AllThreads) { + m_AllThreads.Remove (thd); + } + thd = null; + } + + public void TimeSlice() + { + XMRInstance instance = m_RunInstance; + if (instance != null) { + instance.suspendOnCheckRunTemp = true; + } + } + + /** + * @brief Wake up this XMRScriptThread instance. + */ + private void WakeUpScriptThread() + { + lock (m_WakeUpLock) { + m_WakeUpThis = true; + Monitor.PulseAll (m_WakeUpLock); + } + } + + /** + * @brief Thread that runs the scripts. + */ + private void RunScriptThread() + { + XMRInstance inst; + Mono.Tasklets.Continuation engstack = null; + if (m_Continuations) { + engstack = new Mono.Tasklets.Continuation (); + engstack.Mark (); + } + m_ScriptThreadTID = System.Threading.Thread.CurrentThread.ManagedThreadId; + + while (!m_Exiting) { + XMREngine.UpdateMyThread (); + + /* + * Handle 'xmr resume/suspend' commands. + */ + if (m_SuspendScriptThreadFlag) { + lock (m_WakeUpLock) { + while (m_SuspendScriptThreadFlag && + !m_Exiting && + (engine.m_ThunkQueue.Count == 0)) { + Monitor.Wait (m_WakeUpLock, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2); + XMREngine.UpdateMyThread (); + } + } + } + + /* + * Maybe there are some scripts waiting to be migrated in or out. + */ + ThreadStart thunk = null; + lock (m_WakeUpLock) { + if (engine.m_ThunkQueue.Count > 0) { + thunk = engine.m_ThunkQueue.Dequeue (); + } + } + if (thunk != null) { + inst = (XMRInstance)thunk.Target; + if (m_Continuations && (inst.scrstack == null)) { + inst.engstack = engstack; + inst.scrstack = new Mono.Tasklets.Continuation (); + inst.scrstack.Mark (); + } + thunk (); + continue; + } + + if (engine.m_StartProcessing) { + + /* + * If event just queued to any idle scripts + * start them right away. But only start so + * many so we can make some progress on yield + * queue. + */ + int numStarts; + for (numStarts = 5; -- numStarts >= 0;) { + lock (engine.m_StartQueue) { + inst = engine.m_StartQueue.RemoveHead(); + } + if (inst == null) break; + if (inst.m_IState != XMRInstState.ONSTARTQ) throw new Exception("bad state"); + if (m_Continuations && (inst.scrstack == null)) { + inst.engstack = engstack; + inst.scrstack = new Mono.Tasklets.Continuation (); + inst.scrstack.Mark (); + } + RunInstance (inst); + } + + /* + * If there is something to run, run it + * then rescan from the beginning in case + * a lot of things have changed meanwhile. + * + * These are considered lower priority than + * m_StartQueue as they have been taking at + * least one quantum of CPU time and event + * handlers are supposed to be quick. + */ + lock (engine.m_YieldQueue) { + inst = engine.m_YieldQueue.RemoveHead(); + } + if (inst != null) { + if (inst.m_IState != XMRInstState.ONYIELDQ) throw new Exception("bad state"); + RunInstance (inst); + numStarts = -1; + } + + /* + * If we left something dangling in the m_StartQueue or m_YieldQueue, go back to check it. + */ + if (numStarts < 0) continue; + } + + /* + * Nothing to do, sleep. + */ + lock (m_WakeUpLock) { + if (!m_WakeUpThis && (m_WakeUpOne <= 0) && !m_Exiting) { + Monitor.Wait (m_WakeUpLock, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2); + } + m_WakeUpThis = false; + if ((m_WakeUpOne > 0) && (-- m_WakeUpOne > 0)) { + Monitor.Pulse (m_WakeUpLock); + } + } + } + XMREngine.MyThreadExiting (); + } + + /** + * @brief A script instance was just removed from the Start or Yield Queue. + * So run it for a little bit then stick in whatever queue it should go in. + */ + private void RunInstance (XMRInstance inst) + { + m_LastRanAt = DateTime.UtcNow; + m_ScriptExecTime -= (long)(m_LastRanAt - DateTime.MinValue).TotalMilliseconds; + inst.m_IState = XMRInstState.RUNNING; + m_RunInstance = inst; + XMRInstState newIState = inst.RunOne(); + m_RunInstance = null; + engine.HandleNewIState(inst, newIState); + m_ScriptExecTime += (long)(DateTime.UtcNow - DateTime.MinValue).TotalMilliseconds; + } + } +} diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs new file mode 100644 index 0000000..2e290dd --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRScriptUThread.cs @@ -0,0 +1,557 @@ +/* + * 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 Mono.Tasklets; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; + + + +/***************************\ + * Use standard C# code * + * - uses system threads * +\***************************/ + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public class ScriptUThread_Sys : IScriptUThread, IDisposable + { + private Exception except; + private int active; // -1: hibernating + // 0: exited + // 1: running + private object activeLock = new object (); + private XMRInstance instance; + + public ScriptUThread_Sys (XMRInstance instance) + { + this.instance = instance; + } + + /** + * @brief Start script event handler from the beginning. + * Return when either the script event handler completes + * or the script calls Hiber(). + * @returns null: script did not throw any exception so far + * else: script threw an exception + */ + public Exception StartEx () + { + lock (activeLock) { + + /* + * We should only be called when script is inactive. + */ + if (active != 0) throw new Exception ("active=" + active); + + /* + * Tell CallSEHThread() to run script event handler in a thread. + */ + active = 1; + TredPoo.RunSomething (CallSEHThread); + + /* + * Wait for script to call Hiber() or for script to + * return back out to CallSEHThread(). + */ + while (active > 0) { + Monitor.Wait (activeLock); + } + } + + /* + * Return whether or not script threw an exception. + */ + return except; + } + + /** + * @brief We now want to run some more script code from where it last hibernated + * until it either finishes the script event handler or until the script + * calls Hiber() again. + */ + public Exception ResumeEx () + { + lock (activeLock) { + + /* + * We should only be called when script is hibernating. + */ + if (active >= 0) throw new Exception ("active=" + active); + + /* + * Tell Hiber() to return back to script. + */ + active = 1; + Monitor.PulseAll (activeLock); + + /* + * Wait for script to call Hiber() again or for script to + * return back out to CallSEHThread(). + */ + while (active > 0) { + Monitor.Wait (activeLock); + } + } + + /* + * Return whether or not script threw an exception. + */ + return except; + } + + /** + * @brief Script is being closed out. + * Terminate thread asap. + */ + public void Dispose () + { + lock (activeLock) { + instance = null; + Monitor.PulseAll (activeLock); + } + } + + /** + * @brief Determine if script is active. + * Returns: 0: nothing started or has returned + * Resume() must not be called + * Start() may be called + * Hiber() must not be called + * -1: thread has called Hiber() + * Resume() may be called + * Start() may be called + * Hiber() must not be called + * 1: thread is running + * Resume() must not be called + * Start() must not be called + * Hiber() may be called + */ + public int Active () + { + return active; + } + + /** + * @brief This thread executes the script event handler code. + */ + private void CallSEHThread () + { + lock (activeLock) { + if (active <= 0) throw new Exception ("active=" + active); + + except = null; // assume completion without exception + try { + instance.CallSEH (); // run script event handler + } catch (Exception e) { + except = e; // threw exception, save for Start()/Resume() + } + + active = 0; // tell Start() or Resume() we're done + Monitor.PulseAll (activeLock); + } + } + + /** + * @brief Called by the script event handler whenever it wants to hibernate. + */ + public void Hiber () + { + if (active <= 0) throw new Exception ("active=" + active); + + // tell Start() or Resume() we are hibernating + active = -1; + Monitor.PulseAll (activeLock); + + // wait for Resume() or Dispose() to be called + while ((active < 0) && (instance != null)) { + Monitor.Wait (activeLock); + } + + // don't execute any more script code, just exit + if (instance == null) { + throw new AbortedByDisposeException (); + } + } + + /** + * @brief Number of remaining stack bytes. + */ + public int StackLeft () + { + return 0x7FFFFFFF; + } + + public class AbortedByDisposeException : Exception, IXMRUncatchable { } + + /** + * @brief Pool of threads that run script event handlers. + */ + private class TredPoo { + private static readonly TimeSpan idleTimeSpan = new TimeSpan (0, 0, 1, 0, 0); // 1 minute + + private static int tredPooAvail = 0; + private static object tredPooLock = new object (); + private static Queue tredPooQueue = new Queue (); + + /** + * @brief Queue a function for execution in a system thread. + */ + public static void RunSomething (ThreadStart entry) + { + lock (tredPooLock) { + tredPooQueue.Enqueue (entry); + Monitor.Pulse (tredPooLock); + if (tredPooAvail < tredPooQueue.Count) { + new TredPoo (); + } + } + } + + /** + * @brief Start a new system thread. + * It will shortly attempt to dequeue work or if none, + * add itself to the available thread list. + */ + private TredPoo () + { + Thread thread = new Thread (Main); + thread.Name = "XMRUThread_sys"; + thread.IsBackground = true; + thread.Start (); + tredPooAvail ++; + } + + /** + * @brief Executes items from the queue or waits a little while + * if nothing. If idle for a while, it exits. + */ + private void Main () + { + int first = 1; + ThreadStart entry; + while (true) { + lock (tredPooLock) { + tredPooAvail -= first; + first = 0; + while (tredPooQueue.Count <= 0) { + tredPooAvail ++; + bool keepgoing = Monitor.Wait (tredPooLock, idleTimeSpan); + -- tredPooAvail; + if (!keepgoing) return; + } + entry = tredPooQueue.Dequeue (); + } + entry (); + } + } + } + } +} + + + +/*************************************\ + * Use Mono.Tasklets.Continuations * + * - memcpy's stack * +\*************************************/ + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public partial class XMRInstance { + public Mono.Tasklets.Continuation engstack; + public Mono.Tasklets.Continuation scrstack; + } + + public class ScriptUThread_Con : IScriptUThread, IDisposable + { + private XMRInstance instance; + + public ScriptUThread_Con (XMRInstance instance) + { + this.instance = instance; + } + + private const int SAVEENGINESTACK = 0; + private const int LOADENGINESTACK = 1; + private const int SAVESCRIPTSTACK = 2; + private const int LOADSCRIPTSTACK = 3; + + private Exception except; + private int active; + + /** + * @brief Start script event handler from the beginning. + * Return when either the script event handler completes + * or the script calls Hiber(). + * @returns null: script did not throw any exception so far + * else: script threw an exception + */ + public Exception StartEx () + { + /* + * Save engine stack so we know how to jump back to engine in case + * the script calls Hiber(). + */ + switch (instance.engstack.Store (SAVEENGINESTACK)) { + + /* + * Engine stack has been saved, start running the event handler. + */ + case SAVEENGINESTACK: { + + /* + * Run event handler according to stackFrames. + * In either case it is assumed that stateCode and eventCode + * indicate which event handler is to be called and that ehArgs + * points to the event handler argument list. + */ + active = 1; + except = null; + try { + instance.CallSEH (); + } catch (Exception e) { + except = e; + } + + /* + * We now want to return to the script engine. + * Setting active = 0 means the microthread has exited. + * We need to call engstack.Restore() in case the script called Hiber() + * anywhere, we want to return out the corresponding Restore() and not the + * Start(). + */ + active = 0; + instance.engstack.Restore (LOADENGINESTACK); + throw new Exception ("returned from Restore()"); + } + + /* + * Script called Hiber() somewhere so just return back out. + */ + case LOADENGINESTACK: { + break; + } + + default: throw new Exception ("bad engstack code"); + } + + return except; + } + + public void Dispose () + { } + + /** + * @brief Determine if script is active. + * Returns: 0: nothing started or has returned + * Resume() must not be called + * Start() may be called + * Hiber() must not be called + * -1: thread has called Hiber() + * Resume() may be called + * Start() may be called + * Hiber() must not be called + * 1: thread is running + * Resume() must not be called + * Start() must not be called + * Hiber() may be called + */ + public int Active () + { + return active; + } + + /** + * @brief Called by the script wherever it wants to hibernate. + * So this means to save the scripts stack in 'instance.scrstack' then + * restore the engstack to cause us to return back to the engine. + */ + public void Hiber () + { + /* + * Save where we are in the script's code in 'instance.scrstack' + * so we can wake the script when Resume() is called. + */ + switch (instance.scrstack.Store (SAVESCRIPTSTACK)) { + + /* + * Script's stack is now saved in 'instance.scrstack'. + * Reload the engine's stack from 'instance.engstack' and jump to it. + */ + case SAVESCRIPTSTACK: { + active = -1; + instance.engstack.Restore (LOADENGINESTACK); + throw new Exception ("returned from Restore()"); + } + + /* + * Resume() was just called and we want to resume executing script code. + */ + case LOADSCRIPTSTACK: { + break; + } + + default: throw new Exception ("bad scrstack code"); + } + } + + /** + * @brief We now want to run some more script code from where it last hibernated + * until it either finishes the script event handler or until the script + * calls Hiber() again. + */ + public Exception ResumeEx () + { + /* + * Save where we are in the engine's code in 'instance.engstack' + * so if the script calls Hiber() again or exits, we know how to get + * back to the engine. + */ + switch (instance.engstack.Store (SAVEENGINESTACK)) { + + /* + * This is original call to Resume() from the engine, + * jump to where we left off within Hiber(). + */ + case SAVEENGINESTACK: { + active = 1; + instance.scrstack.Restore (LOADSCRIPTSTACK); + throw new Exception ("returned from Restore()"); + } + + /* + * Script has called Hiber() again, so return back to + * script engine code. + */ + case LOADENGINESTACK: { + break; + } + + default: throw new Exception ("bad engstack code"); + } + + return except; + } + + /** + * @brief Number of remaining stack bytes. + */ + public int StackLeft () + { + return 0x7FFFFFFF; + } + } +} + + + +/***********************************\ + * Use Mono.Tasklets.MMRUThreads * + * - switches stack pointer * +\***********************************/ + +namespace OpenSim.Region.ScriptEngine.XMREngine { + + public class ScriptUThread_MMR : IScriptUThread, IDisposable + { + private static Exception uthread_looked; + private static Type uttype; + private static Type uthread_entry; + private static MethodInfo uthread_dispose; + private static MethodInfo uthread_startex; + private static MethodInfo uthread_resumex; + private static MethodInfo uthread_suspend; + private static MethodInfo uthread_active; + private static MethodInfo uthread_stackleft; + + public static Exception LoadMono () + { + if ((uthread_looked == null) && (uthread_stackleft == null)) { + try { + Assembly mt = Assembly.Load ("Mono.Tasklets"); + uttype = mt.GetType ("Mono.Tasklets.MMRUThread", true); + uthread_entry = mt.GetType ("Mono.Tasklets.MMRUThread+Entry", true); + + uthread_dispose = uttype.GetMethod ("Dispose"); // no parameters, no return value + uthread_startex = uttype.GetMethod ("StartEx"); // takes uthread_entry delegate as parameter, returns exception + uthread_resumex = uttype.GetMethod ("ResumeEx"); // takes exception as parameter, returns exception + uthread_suspend = uttype.GetMethod ("Suspend", new Type[] { }); // no return value + uthread_active = uttype.GetMethod ("Active"); // no parameters, returns int + uthread_stackleft = uttype.GetMethod ("StackLeft"); // no parameters, returns IntPtr + } catch (Exception e) { + uthread_looked = new NotSupportedException ("'mmr' thread model requires patched mono", e); + } + } + return uthread_looked; + } + + private static object[] resumex_args = new object[] { null }; + + private object uthread; // type MMRUThread + private object[] startex_args = new object[1]; + + public ScriptUThread_MMR (XMRInstance instance) + { + this.uthread = Activator.CreateInstance (uttype, new object[] { (IntPtr) instance.m_StackSize, instance.m_DescName }); + startex_args[0] = Delegate.CreateDelegate (uthread_entry, instance, "CallSEH"); + } + + public void Dispose () + { + uthread_dispose.Invoke (uthread, null); + uthread = null; + } + + public Exception StartEx () + { + return (Exception) uthread_startex.Invoke (uthread, startex_args); + } + + public Exception ResumeEx () + { + return (Exception) uthread_resumex.Invoke (uthread, resumex_args); + } + + public void Hiber () + { + uthread_suspend.Invoke (null, null); + } + + public int Active () + { + return (int) uthread_active.Invoke (uthread, null); + } + + public int StackLeft () + { + return (int) (IntPtr) uthread_stackleft.Invoke (null, null); + } + } +} -- cgit v1.1