Przeglądaj źródła

Property marshalling between moon# and c#

Xanathar 11 lat temu
rodzic
commit
8a6d4e22db

+ 406 - 0
src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataPropertiesTests.cs

@@ -0,0 +1,406 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MoonSharp.Interpreter.Interop;
+using NUnit.Framework;
+
+namespace MoonSharp.Interpreter.Tests.EndToEnd
+{
+	[TestFixture]
+	public class UserDataPropertiesTests
+	{
+		public class SomeClass
+		{
+			public int IntProp { get; set; }
+			public int? NIntProp { get; set; }
+			public object ObjProp { get; set; }
+			public static string StaticProp { get; set; }
+
+			public static IEnumerable<int> Numbers
+			{
+				get
+				{
+					for (int i = 1; i <= 4; i++)
+						yield return i;
+				}
+			}
+		}
+
+		public void Test_IntPropertyGetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				x = myobj.IntProp;
+				return x;";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass() {  IntProp = 321 };
+			
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj"] = S.UserDataRepository.CreateUserData(obj);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Number, res.Type);
+			Assert.AreEqual(321, res.Number);
+		}
+
+		public void Test_NIntPropertyGetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				x = myobj1.NIntProp;
+				y = myobj2.NIntProp;
+				return x,y;";
+
+			Script S = new Script();
+
+			SomeClass obj1 = new SomeClass() { NIntProp = 321 };
+			SomeClass obj2 = new SomeClass() { NIntProp = null };
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj1"] = S.UserDataRepository.CreateUserData(obj1);
+			S.Globals["myobj2"] = S.UserDataRepository.CreateUserData(obj2);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Tuple, res.Type);
+			Assert.AreEqual(321.0, res.Tuple[0].Number);
+			Assert.AreEqual(DataType.Number, res.Tuple[0].Type);
+			Assert.AreEqual(DataType.Nil, res.Tuple[1].Type);
+		}
+
+		public void Test_ObjPropertyGetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				x = myobj1.ObjProp;
+				y = myobj2.ObjProp;
+				z = myobj2.ObjProp.ObjProp;
+				return x,y,z;";
+
+			Script S = new Script();
+
+			SomeClass obj1 = new SomeClass() { ObjProp="ciao" };
+			SomeClass obj2 = new SomeClass() { ObjProp = obj1 };
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj1"] = S.UserDataRepository.CreateUserData(obj1);
+			S.Globals["myobj2"] = S.UserDataRepository.CreateUserData(obj2);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Tuple, res.Type);
+			Assert.AreEqual(DataType.String, res.Tuple[0].Type);
+			Assert.AreEqual("ciao", res.Tuple[0].String);
+			Assert.AreEqual(DataType.String, res.Tuple[2].Type);
+			Assert.AreEqual("ciao", res.Tuple[2].String);
+			Assert.AreEqual(DataType.UserData, res.Tuple[1].Type);
+			Assert.AreEqual(obj1, res.Tuple[1].UserData.Object);
+		}
+
+		public void Test_IntPropertySetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				myobj.IntProp = 19;";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass() { IntProp = 321 };
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj"] = S.UserDataRepository.CreateUserData(obj);
+
+			Assert.AreEqual(321, obj.IntProp);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(19, obj.IntProp);
+		}
+
+		public void Test_NIntPropertySetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				myobj1.NIntProp = nil;
+				myobj2.NIntProp = 19;";
+
+			Script S = new Script();
+
+			SomeClass obj1 = new SomeClass() { NIntProp = 321 };
+			SomeClass obj2 = new SomeClass() { NIntProp = null };
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj1"] = S.UserDataRepository.CreateUserData(obj1);
+			S.Globals["myobj2"] = S.UserDataRepository.CreateUserData(obj2);
+
+			Assert.AreEqual(321, obj1.NIntProp);
+			Assert.AreEqual(null, obj2.NIntProp);
+
+			DynValue res = S.DoString(script);
+	
+			Assert.AreEqual(null, obj1.NIntProp);
+			Assert.AreEqual(19, obj2.NIntProp);
+		}
+
+		public void Test_ObjPropertySetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				myobj1.ObjProp = myobj2;
+				myobj2.ObjProp = 'hello';";
+
+			Script S = new Script();
+
+			SomeClass obj1 = new SomeClass() { ObjProp = "ciao" };
+			SomeClass obj2 = new SomeClass() { ObjProp = obj1 };
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj1"] = S.UserDataRepository.CreateUserData(obj1);
+			S.Globals["myobj2"] = S.UserDataRepository.CreateUserData(obj2);
+
+			Assert.AreEqual("ciao", obj1.ObjProp);
+			Assert.AreEqual(obj1, obj2.ObjProp);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(obj2, obj1.ObjProp);
+			Assert.AreEqual("hello", obj2.ObjProp);
+		}
+
+		public void Test_InvalidPropertySetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				myobj.IntProp = '19';";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass() { IntProp = 321 };
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj"] = S.UserDataRepository.CreateUserData(obj);
+
+			Assert.AreEqual(321, obj.IntProp);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(19, obj.IntProp);
+		}
+
+		public void Test_StaticPropertyAccess(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				static.StaticProp = 'asdasd' .. static.StaticProp;";
+
+			Script S = new Script();
+
+			SomeClass.StaticProp = "qweqwe";
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["static"] = S.UserDataRepository.CreateStaticUserData<SomeClass>();
+
+			Assert.AreEqual("qweqwe", SomeClass.StaticProp);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual("asdasdqweqwe", SomeClass.StaticProp);
+		}
+
+		public void Test_IteratorPropertyGetter(UserDataOptimizationMode opt)
+		{
+			string script = @"    
+				x = 0;
+				for i in myobj.Numbers do
+					x = x + i;
+				end
+
+				return x;";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass();
+
+			S.UserDataRepository.RegisterType<SomeClass>(opt);
+
+			S.Globals["myobj"] = S.UserDataRepository.CreateUserData(obj);
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Number, res.Type);
+			Assert.AreEqual(10, res.Number);
+		}
+
+		[Test]
+		public void Interop_IntPropertyGetter_None()
+		{
+			Test_IntPropertyGetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_IntPropertyGetter_Lazy()
+		{
+			Test_IntPropertyGetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_IntPropertyGetter_Precomputed()
+		{
+			Test_IntPropertyGetter(UserDataOptimizationMode.Precomputed);
+		}
+
+		[Test]
+		public void Interop_NIntPropertyGetter_None()
+		{
+			Test_NIntPropertyGetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_NIntPropertyGetter_Lazy()
+		{
+			Test_NIntPropertyGetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_NIntPropertyGetter_Precomputed()
+		{
+			Test_NIntPropertyGetter(UserDataOptimizationMode.Precomputed);
+		}
+
+		[Test]
+		public void Interop_ObjPropertyGetter_None()
+		{
+			Test_ObjPropertyGetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_ObjPropertyGetter_Lazy()
+		{
+			Test_ObjPropertyGetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_ObjPropertyGetter_Precomputed()
+		{
+			Test_ObjPropertyGetter(UserDataOptimizationMode.Precomputed);
+		}
+
+		[Test]
+		public void Interop_IntPropertySetter_None()
+		{
+			Test_IntPropertySetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_IntPropertySetter_Lazy()
+		{
+			Test_IntPropertySetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_IntPropertySetter_Precomputed()
+		{
+			Test_IntPropertySetter(UserDataOptimizationMode.Precomputed);
+		}
+
+		[Test]
+		public void Interop_NIntPropertySetter_None()
+		{
+			Test_NIntPropertySetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_NIntPropertySetter_Lazy()
+		{
+			Test_NIntPropertySetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_NIntPropertySetter_Precomputed()
+		{
+			Test_NIntPropertySetter(UserDataOptimizationMode.Precomputed);
+		}
+
+		[Test]
+		public void Interop_ObjPropertySetter_None()
+		{
+			Test_ObjPropertySetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_ObjPropertySetter_Lazy()
+		{
+			Test_ObjPropertySetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_ObjPropertySetter_Precomputed()
+		{
+			Test_ObjPropertySetter(UserDataOptimizationMode.Precomputed);
+		}
+
+
+		[Test]
+		[ExpectedException(typeof(ScriptRuntimeException))]
+		public void Interop_InvalidPropertySetter_None()
+		{
+			Test_InvalidPropertySetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		[ExpectedException(typeof(ScriptRuntimeException))]
+		public void Interop_InvalidPropertySetter_Lazy()
+		{
+			Test_InvalidPropertySetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		[ExpectedException(typeof(ScriptRuntimeException))]
+		public void Interop_InvalidPropertySetter_Precomputed()
+		{
+			Test_InvalidPropertySetter(UserDataOptimizationMode.Precomputed);
+		}
+
+
+		[Test]
+		public void Interop_StaticPropertyAccess_None()
+		{
+			Test_StaticPropertyAccess(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_StaticPropertyAccess_Lazy()
+		{
+			Test_StaticPropertyAccess(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_StaticPropertyAccess_Precomputed()
+		{
+			Test_StaticPropertyAccess(UserDataOptimizationMode.Precomputed);
+		}
+
+		[Test]
+		public void Interop_IteratorPropertyGetter_None()
+		{
+			Test_IteratorPropertyGetter(UserDataOptimizationMode.None);
+		}
+
+		[Test]
+		public void Interop_IteratorPropertyGetter_Lazy()
+		{
+			Test_IteratorPropertyGetter(UserDataOptimizationMode.Lazy);
+		}
+
+		[Test]
+		public void Interop_IteratorPropertyGetter_Precomputed()
+		{
+			Test_IteratorPropertyGetter(UserDataOptimizationMode.Precomputed);
+		}
+
+	}
+}

+ 3 - 0
src/MoonSharp.Interpreter.Tests/MoonSharp.Interpreter.Tests.csproj

@@ -79,6 +79,9 @@
     </Compile>
     <Compile Include="EndToEnd\StringLibTests.cs" />
     <Compile Include="EndToEnd\TailCallTests.cs" />
+    <Compile Include="EndToEnd\UserDataPropertiesTests.cs">
+      <SubType>Code</SubType>
+    </Compile>
     <Compile Include="EndToEnd\Utils.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="EndToEnd\SimpleTests.cs" />

+ 1 - 1
src/MoonSharp.Interpreter/CoreLib/TableModule.cs

@@ -134,7 +134,7 @@ namespace MoonSharp.Interpreter.CoreLib
 			}
 
 			if (vpos.Type != DataType.Number)
-				args.ThrowBadArgument(1, "table.insert", DataType.Number, vpos.Type, false);
+				throw ScriptRuntimeException.BadArgument(1, "table.insert", DataType.Number, vpos.Type, false);
 
 			int pos = (int)vpos.Number;
 

+ 1 - 8
src/MoonSharp.Interpreter/DataTypes/CallbackArguments.cs

@@ -37,20 +37,13 @@ namespace MoonSharp.Interpreter
 			return List.ToArray();
 		}
 
-		public void ThrowBadArgument(int argNum, string funcName, object expected, object got, bool allowNil)
-		{
-			// bad argument #1 to 'next' (table expected, got number)
-			throw new ScriptRuntimeException("bad argument #{0} to '{1}' ({2}{3} expected, got {4})",
-				argNum + 1, funcName, allowNil ? "nil or " : "", expected.ToString().ToLowerInvariant(), got.ToString().ToLowerInvariant());
-		}
-
 		public DynValue AsType(int argNum, string funcName, DataType type, bool allowNil = false)
 		{
 			if (allowNil && this[argNum].Type == DataType.Nil)
 				return this[argNum];
 
 			if (this[argNum].Type != type)
-				ThrowBadArgument(argNum, funcName, type, this[argNum].Type, allowNil);
+				throw ScriptRuntimeException.BadArgument(argNum, funcName, type, this[argNum].Type, allowNil);
 
 			return this[argNum];
 		}

+ 13 - 0
src/MoonSharp.Interpreter/DataTypes/DynValue.cs

@@ -270,6 +270,19 @@ namespace MoonSharp.Interpreter
 			};
 		}
 
+
+		/// <summary>
+		/// Creates a new userdata value
+		/// </summary>
+		public static DynValue NewUserData(UserData userData)
+		{
+			return new DynValue()
+			{
+				m_Object = userData,
+				m_Type = DataType.UserData,
+			};
+		}
+
 		/// <summary>
 		/// Returns this value as readonly - eventually cloning it in the process if it isn't readonly to start with.
 		/// </summary>

+ 2 - 3
src/MoonSharp.Interpreter/DataTypes/UserData.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using MoonSharp.Interpreter.Interop;
 
 namespace MoonSharp.Interpreter
 {
@@ -9,8 +10,6 @@ namespace MoonSharp.Interpreter
 	{
 		public object Object { get; set; }
 
-		public string Id { get; set;  }
-
-		public Table Metatable { get; set; }
+		public UserDataDescriptor Descriptor { get; set; }
 	}
 }

+ 32 - 0
src/MoonSharp.Interpreter/Errors/ScriptRuntimeException.cs

@@ -57,6 +57,18 @@ namespace MoonSharp.Interpreter
 				return new ScriptRuntimeException("attempt to compare {0} with {1}", l.Type.ToLuaTypeString(), r.Type.ToLuaTypeString());
 		}
 
+		public static ScriptRuntimeException BadArgument(int argNum, string funcName, DataType expected, DataType got, bool allowNil)
+		{
+			return BadArgument(argNum, funcName, expected.ToString().ToLowerInvariant(), got.ToString().ToLowerInvariant(), allowNil);
+		}
+
+		public static ScriptRuntimeException BadArgument(int argNum, string funcName, string expected, string got, bool allowNil)
+		{
+			return new ScriptRuntimeException("bad argument #{0} to '{1}' ({2}{3} expected, got {4})",
+				argNum + 1, funcName, allowNil ? "nil or " : "", expected, got);
+		}
+
+
 		public static ScriptRuntimeException IndexType(DynValue obj)
 		{
 			return new ScriptRuntimeException("attempt to index a {0} value", obj.Type.ToLuaTypeString());
@@ -96,5 +108,25 @@ namespace MoonSharp.Interpreter
 					return new ScriptRuntimeException("value must be a number");
 			}
 		}
+
+		public static ScriptRuntimeException ConvertObjectFailed(object obj)
+		{
+			return new ScriptRuntimeException("cannot convert clr type {0}", obj.GetType());
+		}
+
+		public static ScriptRuntimeException ConvertObjectFailed(DataType t)
+		{
+			return new ScriptRuntimeException("cannot convert a {0} to a clr type", t.ToString().ToLowerInvariant());
+		}
+
+		public static ScriptRuntimeException UserDataArgumentTypeMismatch(DataType t, Type clrType)
+		{
+			return new ScriptRuntimeException("cannot find a conversion from a moon# {0} to a clr {1}", t.ToString().ToLowerInvariant(), clrType.FullName);
+		}
+
+		public static ScriptRuntimeException UserDataMissingField(string typename, string fieldname)
+		{
+			return new ScriptRuntimeException("cannot access field {0} of userdata<{1}>", fieldname, typename);
+		}
 	}
 }

+ 21 - 0
src/MoonSharp.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs

@@ -1006,6 +1006,16 @@ namespace MoonSharp.Interpreter.Execution.VM
 						return instructionPtr;
 					}
 				}
+				else if (obj.Type == DataType.UserData)
+				{
+					UserData ud = obj.UserData;
+
+					if (idx.Type != DataType.String)
+						throw ScriptRuntimeException.BadArgument(1, string.Format("userdata<{0}>.__newindex", ud.Descriptor.Name), "string", idx.Type.ToLuaTypeString(), false);
+
+					ud.Descriptor.SetIndex(ud.Object, idx.String, value);
+					return instructionPtr;
+				}
 				else
 				{
 					h = GetMetamethod(obj, "__newindex");
@@ -1063,6 +1073,17 @@ namespace MoonSharp.Interpreter.Execution.VM
 						return instructionPtr;
 					}
 				}
+				else if (obj.Type == DataType.UserData)
+				{
+					UserData ud = obj.UserData;
+
+					if (idx.Type != DataType.String)
+						throw ScriptRuntimeException.BadArgument(1, string.Format("userdata<{0}>.__index", ud.Descriptor.Name), "string", idx.Type.ToLuaTypeString(), false);
+
+					var v = ud.Descriptor.Index(ud.Object, idx.String);
+					m_ValueStack.Push(v.AsReadOnly());
+					return instructionPtr;
+				}
 				else
 				{
 					h = GetMetamethod(obj, "__index");

+ 95 - 33
src/MoonSharp.Interpreter/Interop/Converter.cs

@@ -13,7 +13,6 @@ namespace MoonSharp.Interpreter.Interop
 		{
 			typeof(sbyte), 
 			typeof(byte), 
-			//typeof(char),  // should we ?
 			typeof(short), 
 			typeof(ushort), 
 			typeof(int), 
@@ -23,7 +22,7 @@ namespace MoonSharp.Interpreter.Interop
 			typeof(float), 
 			typeof(decimal), 
 			typeof(double)
-		};
+		}; 
 
 		public static bool CheckCallbackSignature(MethodInfo mi)
 		{
@@ -33,40 +32,15 @@ namespace MoonSharp.Interpreter.Interop
 				&& pi[1].ParameterType == typeof(CallbackArguments) && mi.ReturnType == typeof(DynValue));
 		}
 
-		public static DataType? TryGetSimpleScriptTypeForClrType(Type t, out bool nullable)
-		{
-			Type tv = Nullable.GetUnderlyingType(t);
-
-			nullable = false;
-
-			if (tv != null)
-			{
-				nullable = true;
-				t = tv;
-			}
-
-			if (NumericTypes.Contains(t))
-				return DataType.Number;
-
-			if (t == typeof(bool))
-				return DataType.Boolean;
-
-			if (t == typeof(string) || t == typeof(StringBuilder) || t == typeof(char))
-				return DataType.String;
-
-			return null;
-		}
-
-
-
-
-		public static DynValue CreateValueFromSupportedObject(this Script script, object obj)
+		public static DynValue TryClrObjectToSimpleMoonSharpValue(this Script script, object obj)
 		{
 			if (obj == null)
 				return DynValue.Nil;
 
-			if (NumericTypes.Contains(obj.GetType()))
-				return DynValue.NewNumber((double)obj);
+			Type t = obj.GetType();
+
+			if (NumericTypes.Contains(t))
+				return DynValue.NewNumber(TypeToDouble(t, obj));
 
 			if (obj is bool)
 				return DynValue.NewBoolean((bool)obj);
@@ -86,14 +60,102 @@ namespace MoonSharp.Interpreter.Interop
 				MethodInfo mi = d.Method;
 
 				if (CheckCallbackSignature(mi))
-					return DynValue.NewCallback((Func<ScriptExecutionContext, CallbackArguments, DynValue>)d); 
+					return DynValue.NewCallback((Func<ScriptExecutionContext, CallbackArguments, DynValue>)d);
 			}
 
 			return null;
 		}
 
 
+		public static DynValue ClrObjectToComplexMoonSharpValue(this Script script, object obj)
+		{
+			DynValue v = TryClrObjectToSimpleMoonSharpValue(script, obj);
+
+			if (v != null) return v;
+
+			v = script.UserDataRepository.CreateUserData(obj);
 
+			if (v != null) return v;
 
+			if (obj is Type)
+			{
+				v = script.UserDataRepository.CreateStaticUserData(obj as Type);
+			}
+
+			if (obj is System.Collections.IEnumerable)
+			{
+				var enumer = (System.Collections.IEnumerable)obj;
+				return EnumerableIterator.ConvertIterator(script, enumer.GetEnumerator());
+			}
+
+			if (obj is System.Collections.IEnumerator)
+			{
+				var enumer = (System.Collections.IEnumerator)obj;
+				return EnumerableIterator.ConvertIterator(script, enumer);
+			}
+
+			throw ScriptRuntimeException.ConvertObjectFailed(obj);
+		}
+
+		internal static object MoonSharpValueToClrObject(DynValue value)
+		{
+			switch (value.Type)
+			{
+				case DataType.Nil:
+					return null;
+				case DataType.Boolean:
+					return value.Boolean;
+				case DataType.Number:
+					return value.Number;
+				case DataType.String:
+					return value.String;
+				case DataType.Function:
+					return value.Function;
+				case DataType.Table:
+					return value.Table;
+				case DataType.Tuple:
+					return value.Tuple;
+				case DataType.UserData:
+					return value.UserData.Object;
+				case DataType.ClrFunction:
+					return value.Callback;
+				default:
+					throw ScriptRuntimeException.ConvertObjectFailed(value.Type);
+			}
+		}
+
+		public static object DoubleToType(Type type, double d)
+		{
+			type = Nullable.GetUnderlyingType(type) ?? type;
+
+			if (type == typeof(double)) return d;
+			if (type == typeof(sbyte)) return (sbyte)d;
+			if (type == typeof(byte)) return (byte)d;
+			if (type == typeof(short)) return (short)d;
+			if (type == typeof(ushort)) return (ushort)d;
+			if (type == typeof(int)) return (int)d;
+			if (type == typeof(uint)) return (uint)d;
+			if (type == typeof(long)) return (long)d;
+			if (type == typeof(ulong)) return (ulong)d;
+			if (type == typeof(float)) return (float)d;
+			if (type == typeof(decimal)) return (decimal)d;
+			return d;
+		}
+
+		public static double TypeToDouble(Type type, object d)
+		{
+			if (type == typeof(double)) return (double)d;
+			if (type == typeof(sbyte)) return (double)(sbyte)d;
+			if (type == typeof(byte)) return (double)(byte)d;
+			if (type == typeof(short)) return (double)(short)d;
+			if (type == typeof(ushort)) return (double)(ushort)d;
+			if (type == typeof(int)) return (double)(int)d;
+			if (type == typeof(uint)) return (double)(uint)d;
+			if (type == typeof(long)) return (double)(long)d;
+			if (type == typeof(ulong)) return (double)(ulong)d;
+			if (type == typeof(float)) return (double)(float)d;
+			if (type == typeof(decimal)) return (double)(decimal)d;
+			return (double)d;
+		}
 	}
 }

+ 76 - 0
src/MoonSharp.Interpreter/Interop/EnumerableIterator.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MoonSharp.Interpreter.Execution;
+
+namespace MoonSharp.Interpreter.Interop
+{
+	public class EnumerableIterator
+	{
+		IEnumerator m_Enumerator;
+		Script m_Script;
+		bool m_HasTurnOnce = false;
+
+		private EnumerableIterator(Script script, IEnumerator enumerator)
+		{
+			m_Script = script;
+			m_Enumerator = enumerator;
+		}
+
+		private DynValue GetNext(DynValue prev)
+		{
+			if (prev.IsNil())
+				Reset();
+
+			while (m_Enumerator.MoveNext())
+			{
+				DynValue v = Converter.ClrObjectToComplexMoonSharpValue(m_Script, m_Enumerator.Current);
+
+				if (!v.IsNil())
+					return v;
+			}
+
+			return DynValue.Nil;
+		}
+
+		private void Reset()
+		{
+			if (m_HasTurnOnce)
+				m_Enumerator.Reset();
+
+			m_HasTurnOnce = true;
+		}
+
+		private static DynValue clr_iterator_next(ScriptExecutionContext executionContext, CallbackArguments args)
+		{
+			DynValue userdata = args.AsType(0, "clr_iterator_next", DataType.UserData);
+			DynValue prev = args[1];
+
+			UserData ud = userdata.UserData;
+			EnumerableIterator iterator = ud.Object as EnumerableIterator;
+
+			if (iterator == null)
+			{
+				throw ScriptRuntimeException.BadArgument(0, "clr_iterator_next",
+					(ud.Object != null) ? ud.Object.GetType().FullName : "null",
+					typeof(EnumerableIterator).FullName,
+					false);
+			}
+
+			return iterator.GetNext(prev);
+		}
+
+		public static DynValue ConvertIterator(Script script, IEnumerator enumerator)
+		{
+			EnumerableIterator ei = new EnumerableIterator(script, enumerator);
+
+			return DynValue.NewTuple(
+				DynValue.NewCallback(clr_iterator_next),
+				script.UserDataRepository.CreateUserData(ei),
+				DynValue.Nil);
+		}
+
+	}
+}

+ 0 - 31
src/MoonSharp.Interpreter/Interop/MethodAdapter.cs

@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-
-namespace MoonSharp.Interpreter.Interop
-{
-	public class MethodAdapter
-	{
-		private class ArgType
-		{
-			public DataType Type;
-			public bool Nullable;
-		}
-
-		public MethodAdapter(MethodInfo mi)
-		{
-			foreach (var arg in mi.GetParameters())
-			{
-				
-			}
-		}
-
-
-
-
-
-
-	}
-}

+ 0 - 52
src/MoonSharp.Interpreter/Interop/ReflectionMetatableBuilder.cs

@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using MoonSharp.Interpreter.Execution;
-
-namespace MoonSharp.Interpreter.Interop
-{
-	// Only __eq, __tostring, __index and __newindex are supported.
-	public class ReflectionMetatableBuilder
-	{
-		private static UserData GetUserData(CallbackArguments args)
-		{
-			DynValue v = args.AsType(0, "::ReflectionMetatableBuilder", DataType.UserData, false);
-			return v.UserData;
-		}
-
-
-		public static DynValue reflection_tostring(ScriptExecutionContext executionContext, CallbackArguments args)
-		{
-			UserData u = GetUserData(args);
-			return DynValue.NewString(u.Object.ToString());
-		}
-
-		public static DynValue reflection_eq(ScriptExecutionContext executionContext, CallbackArguments args)
-		{
-			UserData u = GetUserData(args);
-			DynValue a = args[1];
-
-			if (a.Type != DataType.UserData)
-				return DynValue.False;
-
-			return DynValue.NewBoolean(u.Object.Equals(a.UserData.Object));
-		}
-
-		public static DynValue reflection_index(ScriptExecutionContext executionContext, CallbackArguments args)
-		{
-			return DynValue.Nil;
-		}
-
-
-
-
-
-
-
-
-
-
-
-	}
-}

+ 77 - 0
src/MoonSharp.Interpreter/Interop/UserDataDescriptor.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using MoonSharp.Interpreter.Execution;
+
+namespace MoonSharp.Interpreter.Interop
+{
+	public class UserDataDescriptor
+	{
+		public string Name { get; private set; }
+		public Type Type { get; private set; }
+		public UserDataOptimizationMode OptimizationMode { get; private set; }
+		public UserDataRepository Repository { get; private set; }
+
+		private Dictionary<string, UserDataMethodDescriptor> m_Methods = new Dictionary<string, UserDataMethodDescriptor>();
+		private Dictionary<string, UserDataPropertyDescriptor> m_Properties = new Dictionary<string, UserDataPropertyDescriptor>();
+
+		internal UserDataDescriptor(UserDataRepository repository, Type type, UserDataOptimizationMode optimizationMode)
+		{
+			Repository = repository;
+			Type = type;
+			Name = type.FullName;
+			OptimizationMode = optimizationMode;
+
+			if (OptimizationMode != UserDataOptimizationMode.HideMembers)
+			{
+				foreach (MethodInfo mi in type.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
+				{
+					var md = new UserDataMethodDescriptor(mi, this);
+
+					if (m_Methods.ContainsKey(md.Name))
+						throw new ArgumentException(string.Format("{0}.{1} has overloads", Name, md.Name));
+
+					m_Methods.Add(md.Name, md);
+				}
+
+				foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
+				{
+					var pd = new UserDataPropertyDescriptor(pi, this);
+					m_Properties.Add(pd.Name, pd);
+				}
+			}
+		}
+
+
+		public DynValue Index(object obj, string idxname)
+		{
+			if (m_Methods.ContainsKey(idxname))
+			{
+				return DynValue.NewCallback(m_Methods[idxname].GetCallback(obj));
+			}
+
+			if (m_Properties.ContainsKey(idxname))
+			{
+				object o = m_Properties[idxname].GetValue(obj);
+				return Converter.ClrObjectToComplexMoonSharpValue(this.Repository.OwnerScript, o);
+			}
+
+			throw ScriptRuntimeException.UserDataMissingField(this.Name, idxname);
+		}
+
+		public void SetIndex(object obj, string idxname, DynValue value)
+		{
+			if (m_Properties.ContainsKey(idxname))
+			{
+				object o = Converter.MoonSharpValueToClrObject(value);
+				m_Properties[idxname].SetValue(obj, o, value.Type);
+			}
+			else
+			{
+				throw ScriptRuntimeException.UserDataMissingField(this.Name, idxname);
+			}
+		}
+	}
+}

+ 31 - 0
src/MoonSharp.Interpreter/Interop/UserDataMethodDescriptor.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using MoonSharp.Interpreter.Execution;
+
+namespace MoonSharp.Interpreter.Interop
+{
+	public class UserDataMethodDescriptor
+	{
+		public MethodInfo MethodInfo { get; private set; }
+		public UserDataDescriptor UserDataDescriptor { get; private set; }
+		public bool IsStatic { get; private set; }
+		public string Name { get; private set; }
+
+		internal UserDataMethodDescriptor(MethodInfo mi, UserDataDescriptor userDataDescriptor)
+		{
+			this.MethodInfo = mi;
+			this.UserDataDescriptor = userDataDescriptor;
+			this.Name = mi.Name;
+			this.IsStatic = mi.IsStatic;
+		}
+
+
+		internal Func<ScriptExecutionContext, CallbackArguments, DynValue> GetCallback(object obj)
+		{
+			throw new NotImplementedException();
+		}
+	}
+}

+ 6 - 8
src/MoonSharp.Interpreter/Interop/MoonSharpInterop.cs → src/MoonSharp.Interpreter/Interop/UserDataOptimizationMode.cs

@@ -5,14 +5,12 @@ using System.Text;
 
 namespace MoonSharp.Interpreter.Interop
 {
-	public static class MoonSharpInterop
+	[Flags]
+	public enum UserDataOptimizationMode
 	{
-
-
-
-
-
-
-
+		None,
+		Lazy,
+		Precomputed,
+		HideMembers
 	}
 }

+ 130 - 0
src/MoonSharp.Interpreter/Interop/UserDataPropertyDescriptor.cs

@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+
+namespace MoonSharp.Interpreter.Interop
+{
+	public class UserDataPropertyDescriptor
+	{
+		public PropertyInfo PropertyInfo { get; private set; }
+		public UserDataDescriptor UserDataDescriptor { get; private set; }
+		public bool IsStatic { get; private set; }
+		public string Name { get; private set; }
+
+		Func<object, object> m_OptimizedGetter = null;
+		Action<object, object> m_OptimizedSetter = null;
+		
+
+		internal UserDataPropertyDescriptor(PropertyInfo pi, UserDataDescriptor userDataDescriptor)
+		{
+			this.PropertyInfo = pi;
+			this.UserDataDescriptor = userDataDescriptor;
+			this.Name = pi.Name;
+			this.IsStatic = (this.PropertyInfo.GetGetMethod() ?? this.PropertyInfo.GetSetMethod()).IsStatic;
+
+			if (userDataDescriptor.OptimizationMode == UserDataOptimizationMode.Precomputed)
+			{
+				this.OptimizeGetter();
+				this.OptimizeSetter();
+			}
+		}
+
+
+		public object GetValue(object obj)
+		{
+			if (UserDataDescriptor.OptimizationMode == UserDataOptimizationMode.Lazy && m_OptimizedGetter == null)
+				OptimizeGetter();
+
+			if (m_OptimizedGetter != null)
+				return m_OptimizedGetter(obj);
+
+			return PropertyInfo.GetValue(IsStatic ? null : obj, null);
+		}
+
+		private void OptimizeGetter()
+		{
+			if (PropertyInfo.CanRead)
+			{
+				if (IsStatic)
+				{
+					var paramExp = Expression.Parameter(typeof(object), "dummy");
+					var propAccess = Expression.Property(null, PropertyInfo);
+					var castPropAccess = Expression.Convert(propAccess, typeof(object));
+					var lambda = Expression.Lambda<Func<object, object>>(castPropAccess, paramExp);
+					m_OptimizedGetter = lambda.Compile();
+				}
+				else
+				{
+					var paramExp = Expression.Parameter(typeof(object), "obj");
+					var castParamExp = Expression.Convert(paramExp, this.UserDataDescriptor.Type);
+					var propAccess = Expression.Property(castParamExp, PropertyInfo);
+					var castPropAccess = Expression.Convert(propAccess, typeof(object));
+					var lambda = Expression.Lambda<Func<object, object>>(castPropAccess, paramExp);
+					m_OptimizedGetter = lambda.Compile();
+				}
+			}
+		}
+
+		private void OptimizeSetter()
+		{
+			if (PropertyInfo.CanWrite)
+			{
+				MethodInfo setterMethod = PropertyInfo.GetSetMethod();
+
+				if (IsStatic)
+				{
+					var paramExp = Expression.Parameter(typeof(object), "dummy");
+					var paramValExp = Expression.Parameter(typeof(object), "val");
+					var castParamValExp = Expression.Convert(paramValExp, this.PropertyInfo.PropertyType);
+					var callExpression = Expression.Call(setterMethod, castParamValExp);
+					var lambda = Expression.Lambda<Action<object, object>>(callExpression, paramExp, paramValExp);
+					m_OptimizedSetter = lambda.Compile();
+				}
+				else
+				{
+					var paramExp = Expression.Parameter(typeof(object), "obj");
+					var paramValExp = Expression.Parameter(typeof(object), "val");
+					var castParamExp = Expression.Convert(paramExp, this.UserDataDescriptor.Type);
+					var castParamValExp = Expression.Convert(paramValExp, this.PropertyInfo.PropertyType);
+					var callExpression = Expression.Call(castParamExp, setterMethod, castParamValExp);
+					var lambda = Expression.Lambda<Action<object, object>>(callExpression, paramExp, paramValExp);
+					m_OptimizedSetter = lambda.Compile();
+				}
+			}
+		}
+
+		public void SetValue(object obj, object value, DataType originalType)
+		{
+			try
+			{
+				if (value is double)
+					value = Converter.DoubleToType(PropertyInfo.PropertyType, (double)value);
+
+				if (UserDataDescriptor.OptimizationMode == UserDataOptimizationMode.Lazy && m_OptimizedSetter == null)
+					OptimizeSetter();
+
+				if (m_OptimizedSetter != null)
+				{
+					m_OptimizedSetter(obj, value);
+				}
+				else
+				{
+					PropertyInfo.SetValue(IsStatic ? null : obj, value, null);
+				}
+			}
+			catch (ArgumentException)
+			{
+				// non-optimized setters fall here
+				throw ScriptRuntimeException.UserDataArgumentTypeMismatch(originalType, PropertyInfo.PropertyType);
+			}
+			catch (InvalidCastException)
+			{
+				// optimized setters fall here
+				throw ScriptRuntimeException.UserDataArgumentTypeMismatch(originalType, PropertyInfo.PropertyType);
+			}
+		}
+	}
+}

+ 103 - 0
src/MoonSharp.Interpreter/Interop/UserDataRepository.cs

@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MoonSharp.Interpreter.Interop
+{
+	public class UserDataRepository
+	{
+		private Dictionary<Type, UserDataDescriptor> m_ByType = new Dictionary<Type, UserDataDescriptor>();
+		private Dictionary<string, UserDataDescriptor> m_ByName = new Dictionary<string, UserDataDescriptor>();
+
+		public Script OwnerScript { get; private set; }
+
+		internal UserDataRepository(Script script)
+		{
+			OwnerScript = script;
+			this.RegisterType<EnumerableIterator>(UserDataOptimizationMode.HideMembers);
+		}
+
+		public UserDataDescriptor RegisterType<T>(UserDataOptimizationMode optimizationMode = UserDataOptimizationMode.None)
+		{
+			return RegisterType(typeof(T), optimizationMode);
+		}
+
+
+		public UserDataDescriptor RegisterType(Type type, UserDataOptimizationMode optimizationMode = UserDataOptimizationMode.None)
+		{
+			if (m_ByType.ContainsKey(type))
+				return m_ByType[type];
+
+			UserDataDescriptor udd = new UserDataDescriptor(this, type, optimizationMode);
+			m_ByType.Add(udd.Type, udd);
+			m_ByName.Add(udd.Name, udd);
+			return udd;
+		}
+
+		public UserDataDescriptor GetDescriptorForType<T>(bool deepSearch = true)
+		{
+			return GetDescriptorForType(typeof(T), deepSearch);
+		}
+
+		public UserDataDescriptor GetDescriptorForType(Type type, bool deepSearch = true)
+		{
+			if (!deepSearch)
+				return m_ByType.ContainsKey(type) ? m_ByType[type] : null;
+
+			for (Type t = type; t != typeof(object); t = t.BaseType)
+			{
+				if (m_ByType.ContainsKey(t))
+					return m_ByType[t];
+			}
+
+			foreach (Type t in type.GetInterfaces())
+			{
+				if (m_ByType.ContainsKey(t))
+					return m_ByType[t];
+			}
+
+			if (m_ByType.ContainsKey(typeof(object)))
+				return m_ByType[type];
+
+			return null;
+		}
+
+		public UserDataDescriptor GetDescriptorForObject(object o)
+		{
+			return GetDescriptorForType(o.GetType(), true);
+		}
+
+		public DynValue CreateUserData(object o)
+		{
+			var descr = GetDescriptorForObject(o);
+			if (descr == null) return null;
+				
+			return DynValue.NewUserData(new UserData()
+				{
+					Descriptor = descr,
+					Object = o
+				});
+		}
+
+		public DynValue CreateStaticUserData(Type t)
+		{
+			var descr = GetDescriptorForType(t, false);
+			if (descr == null) return null;
+
+			return DynValue.NewUserData(new UserData()
+			{
+				Descriptor = descr,
+				Object = null
+			});
+		}
+
+		public DynValue CreateStaticUserData<T>()
+		{
+			return CreateStaticUserData(typeof(T));
+		}
+
+
+
+	}
+}

+ 10 - 3
src/MoonSharp.Interpreter/MoonSharp.Interpreter.csproj

@@ -133,9 +133,16 @@
     <Compile Include="Execution\VM\Processor\Processor_Errors.cs" />
     <Compile Include="Interface\IteratorHelper.cs" />
     <Compile Include="Interop\Converter.cs" />
-    <Compile Include="Interop\MethodAdapter.cs" />
-    <Compile Include="Interop\MoonSharpInterop.cs" />
-    <Compile Include="Interop\ReflectionMetatableBuilder.cs" />
+    <Compile Include="Interop\EnumerableIterator.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Interop\UserDataDescriptor.cs" />
+    <Compile Include="Interop\UserDataOptimizationMode.cs" />
+    <Compile Include="Interop\UserDataMethodDescriptor.cs" />
+    <Compile Include="Interop\UserDataPropertyDescriptor.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Interop\UserDataRepository.cs" />
     <Compile Include="Loaders\ClassicLuaScriptLoader.cs" />
     <Compile Include="Loaders\IScriptLoader.cs" />
     <Compile Include="Execution\Scopes\ClosureContext.cs" />

+ 7 - 0
src/MoonSharp.Interpreter/Script.cs

@@ -8,6 +8,7 @@ using MoonSharp.Interpreter.CoreLib;
 using MoonSharp.Interpreter.Debugging;
 using MoonSharp.Interpreter.Diagnostics;
 using MoonSharp.Interpreter.Execution.VM;
+using MoonSharp.Interpreter.Interop;
 using MoonSharp.Interpreter.Loaders;
 using MoonSharp.Interpreter.Tree;
 using MoonSharp.Interpreter.Tree.Statements;
@@ -52,6 +53,7 @@ namespace MoonSharp.Interpreter
 		{
 			DebugPrint = s => { Console.WriteLine(s); };
 			m_ByteCode = new ByteCode();
+			UserDataRepository = new UserDataRepository(this);
 			m_GlobalTable = new Table(this).RegisterCoreModules(coreModules);
 			m_MainRoutine = new Processor(this, m_GlobalTable, m_ByteCode);
 			ReseedRandomGenerator(DateTime.Now.Millisecond);
@@ -77,6 +79,11 @@ namespace MoonSharp.Interpreter
 		/// </summary>
 		public static IScriptLoader DefaultScriptLoader { get; set; }
 
+		/// <summary>
+		/// Gets the userdata type repository.
+		/// </summary>
+		public UserDataRepository UserDataRepository { get; private set; }
+
 		/// <summary>
 		/// Gets the default global table for this script. Unless a different table is intentionally passed (or setfenv has been used)
 		/// execution uses this table.

+ 0 - 3
src/moonsharp.sln

@@ -85,7 +85,4 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
-	GlobalSection(Performance) = preSolution
-		HasPerformanceSessions = true
-	EndGlobalSection
 EndGlobal