Xanathar 10 years ago
parent
commit
f40bfd6dab
25 changed files with 1657 additions and 103 deletions
  1. 5 0
      src/MoonSharp.Interpreter.Tests/EmbeddableNUnitWrapper.cs
  2. 221 0
      src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataEventsTests.cs
  3. 198 8
      src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataFieldsTests.cs
  4. 423 5
      src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataPropertiesTests.cs
  5. 1 0
      src/MoonSharp.Interpreter.Tests/MoonSharp.Interpreter.Tests.net35-client.csproj
  6. 3 0
      src/MoonSharp.Interpreter.Tests/_Projects/MoonSharp.Interpreter.Tests.Embeddable.portable40/MoonSharp.Interpreter.Tests.Embeddable.portable40.csproj
  7. 3 0
      src/MoonSharp.Interpreter.Tests/_Projects/MoonSharp.Interpreter.Tests.net40-client/MoonSharp.Interpreter.Tests.net40-client.csproj
  8. 3 0
      src/MoonSharp.Interpreter.Tests/_Projects/MoonSharp.Interpreter.Tests.portable40/MoonSharp.Interpreter.Tests.portable40.csproj
  9. 73 4
      src/MoonSharp.Interpreter/DataStructs/MultiDictionary.cs
  10. 23 0
      src/MoonSharp.Interpreter/DataStructs/ReferenceEqualityComparer.cs
  11. 1 0
      src/MoonSharp.Interpreter/DataTypes/UserData.cs
  12. 1 0
      src/MoonSharp.Interpreter/Errors/ScriptRuntimeException.cs
  13. 1 1
      src/MoonSharp.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs
  14. 1 1
      src/MoonSharp.Interpreter/Interop/Attributes/MoonSharpVisibleAttribute.cs
  15. 69 69
      src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataDescriptor.cs
  16. 334 0
      src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataEventDescriptor.cs
  17. 55 1
      src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataFieldDescriptor.cs
  18. 22 2
      src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataMethodDescriptor.cs
  19. 7 2
      src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataOverloadedMethodDescriptor.cs
  20. 89 7
      src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataPropertyDescriptor.cs
  21. 2 0
      src/MoonSharp.Interpreter/MoonSharp.Interpreter.net35-client.csproj
  22. 10 3
      src/MoonSharp.Interpreter/REPL/ReplInterpreterScriptLoader.cs
  23. 9 0
      src/MoonSharp.Interpreter/_Projects/MoonSharp.Interpreter.net40-client/MoonSharp.Interpreter.net40-client.csproj
  24. 9 0
      src/MoonSharp.Interpreter/_Projects/MoonSharp.Interpreter.portable40/MoonSharp.Interpreter.portable40.csproj
  25. 94 0
      src/Tutorial/Tutorials/Chapters/Chapter6.cs

+ 5 - 0
src/MoonSharp.Interpreter.Tests/EmbeddableNUnitWrapper.cs

@@ -80,6 +80,11 @@ namespace NUnit.Framework
 		}
 
 
+
+		internal static void Fail()
+		{
+			Assert.IsTrue(false);
+		}
 	}
 
 

+ 221 - 0
src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataEventsTests.cs

@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+
+namespace MoonSharp.Interpreter.Tests.EndToEnd
+{
+#pragma warning disable 169 // unused private field
+
+	[TestFixture]
+	public class UserDataEventsTests
+	{
+		public class SomeClass
+		{
+			public event EventHandler MyEvent;
+			public static event EventHandler MySEvent;
+
+			public bool Trigger_MyEvent()
+			{
+				if (MyEvent != null)
+				{
+					MyEvent(this, EventArgs.Empty);
+					return true;
+				}
+				return false;
+			}
+
+			public static bool Trigger_MySEvent()
+			{
+				if (MySEvent != null)
+				{
+					MySEvent(null, EventArgs.Empty);
+					return true;
+				}
+				return false;
+			}
+		}
+
+
+		[Test]
+		public void Interop_Event_Simple()
+		{
+			int invocationCount = 0;
+			UserData.RegisterType<SomeClass>();
+			UserData.RegisterType<EventArgs>();
+
+			Script s = new Script(CoreModules.None);
+
+			var obj = new SomeClass();
+			s.Globals["myobj"] = obj;
+			s.Globals["ext"] = DynValue.NewCallback((c, a) => { invocationCount += 1; return DynValue.Void; });
+
+			s.DoString(@"
+				function handler(o, a)
+					ext();
+				end
+
+				myobj.MyEvent.add(handler);
+				");
+
+			obj.Trigger_MyEvent();
+
+			Assert.AreEqual(1, invocationCount);
+		}
+
+		[Test]
+		public void Interop_Event_TwoObjects()
+		{
+			int invocationCount = 0;
+			UserData.RegisterType<SomeClass>();
+			UserData.RegisterType<EventArgs>();
+
+			Script s = new Script(CoreModules.None);
+
+			var obj = new SomeClass();
+			var obj2 = new SomeClass();
+			s.Globals["myobj"] = obj;
+			s.Globals["myobj2"] = obj2;
+			s.Globals["ext"] = DynValue.NewCallback((c, a) => { invocationCount += 1; return DynValue.Void; });
+
+			s.DoString(@"
+				function handler(o, a)
+					ext();
+				end
+
+				myobj.MyEvent.add(handler);
+				");
+
+			obj.Trigger_MyEvent();
+			obj2.Trigger_MyEvent();
+
+			Assert.AreEqual(1, invocationCount);
+		}
+
+
+		[Test]
+		public void Interop_Event_Multi()
+		{
+			int invocationCount = 0;
+			UserData.RegisterType<SomeClass>();
+			UserData.RegisterType<EventArgs>();
+
+			Script s = new Script(CoreModules.None);
+
+			var obj = new SomeClass();
+			s.Globals["myobj"] = obj;
+			s.Globals["ext"] = DynValue.NewCallback((c, a) => { invocationCount += 1; return DynValue.Void; });
+
+			s.DoString(@"
+				function handler(o, a)
+					ext();
+				end
+
+				myobj.MyEvent.add(handler);
+				myobj.MyEvent.add(handler);
+				");
+
+			obj.Trigger_MyEvent();
+
+			Assert.AreEqual(2, invocationCount);
+		}
+
+		[Test]
+		public void Interop_Event_MultiAndDetach()
+		{
+			int invocationCount = 0;
+			UserData.RegisterType<SomeClass>();
+			UserData.RegisterType<EventArgs>();
+
+			Script s = new Script(CoreModules.None);
+
+			var obj = new SomeClass();
+			s.Globals["myobj"] = obj;
+			s.Globals["ext"] = DynValue.NewCallback((c, a) => { invocationCount += 1; return DynValue.Void; });
+
+			s.DoString(@"
+				function handler(o, a)
+					ext();
+				end
+
+				myobj.MyEvent.add(handler);
+				myobj.MyEvent.add(handler);
+				myobj.Trigger_MyEvent();
+				myobj.MyEvent.remove(handler);
+				myobj.Trigger_MyEvent();
+				");
+
+			Assert.AreEqual(3, invocationCount);
+		}
+
+		[Test]
+		public void Interop_Event_DetachAndDeregister()
+		{
+			int invocationCount = 0;
+			UserData.RegisterType<SomeClass>();
+			UserData.RegisterType<EventArgs>();
+
+			Script s = new Script(CoreModules.None);
+
+			var obj = new SomeClass();
+			s.Globals["myobj"] = obj;
+			s.Globals["ext"] = DynValue.NewCallback((c, a) => { invocationCount += 1; return DynValue.Void; });
+
+			s.DoString(@"
+				function handler(o, a)
+					ext();
+				end
+
+				myobj.MyEvent.add(handler);
+				myobj.MyEvent.add(handler);
+				myobj.Trigger_MyEvent();
+				myobj.MyEvent.remove(handler);
+				myobj.Trigger_MyEvent();
+				myobj.MyEvent.remove(handler);
+				");
+
+			Assert.IsFalse(obj.Trigger_MyEvent(), "deregistration");
+			Assert.AreEqual(3, invocationCount);
+		}
+
+
+		[Test]
+		public void Interop_SEvent_DetachAndDeregister()
+		{
+			int invocationCount = 0;
+			UserData.RegisterType<SomeClass>();
+			UserData.RegisterType<EventArgs>();
+
+			Script s = new Script(CoreModules.None);
+
+			s.Globals["myobj"] = typeof(SomeClass);
+			s.Globals["ext"] = DynValue.NewCallback((c, a) => { invocationCount += 1; return DynValue.Void; });
+
+			s.DoString(@"
+				function handler(o, a)
+					ext();
+				end
+
+				myobj.MySEvent.add(handler);
+				myobj.MySEvent.add(handler);
+				myobj.Trigger_MySEvent();
+				myobj.MySEvent.remove(handler);
+				myobj.Trigger_MySEvent();
+				myobj.MySEvent.remove(handler);
+				");
+
+			Assert.IsFalse(SomeClass.Trigger_MySEvent(), "deregistration");
+			Assert.AreEqual(3, invocationCount);
+		}
+
+
+
+
+
+
+
+
+
+	}
+}

+ 198 - 8
src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataFieldsTests.cs

@@ -14,12 +14,119 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 		public class SomeClass
 		{
 			public int IntProp;
+			public const int ConstIntProp = 115;
+			public readonly int RoIntProp = 123;
 			public int? NIntProp;
 			public object ObjProp;
 			public static string StaticProp;
 			private string PrivateProp;
 		}
 
+		public void Test_ConstIntFieldGetter(InteropAccessMode opt)
+		{
+			string script = @"    
+				x = myobj.ConstIntProp;
+				return x;";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass() { IntProp = 321 };
+
+			UserData.UnregisterType<SomeClass>();
+			UserData.RegisterType<SomeClass>(opt);
+
+			S.Globals.Set("myobj", UserData.Create(obj));
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Number, res.Type);
+			Assert.AreEqual(115, res.Number);
+		}
+
+		public void Test_ReadOnlyIntFieldGetter(InteropAccessMode opt)
+		{
+			string script = @"    
+				x = myobj.RoIntProp;
+				return x;";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass() { IntProp = 321 };
+
+			UserData.UnregisterType<SomeClass>();
+			UserData.RegisterType<SomeClass>(opt);
+
+			S.Globals.Set("myobj", UserData.Create(obj));
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Number, res.Type);
+			Assert.AreEqual(123, res.Number);
+		}
+
+		public void Test_ConstIntFieldSetter(InteropAccessMode opt)
+		{
+			try
+			{
+				string script = @"    
+				myobj.ConstIntProp = 1;
+				return myobj.ConstIntProp;";
+
+				Script S = new Script();
+
+				SomeClass obj = new SomeClass() { IntProp = 321 };
+
+				UserData.UnregisterType<SomeClass>();
+				UserData.RegisterType<SomeClass>(opt);
+
+				S.Globals.Set("myobj", UserData.Create(obj));
+
+				DynValue res = S.DoString(script);
+
+				Assert.AreEqual(DataType.Number, res.Type);
+				Assert.AreEqual(115, res.Number);
+			}
+			catch (ScriptRuntimeException)
+			{
+				return;
+			}
+
+			Assert.Fail();
+		}
+
+		public void Test_ReadOnlyIntFieldSetter(InteropAccessMode opt)
+		{
+			try
+			{
+				string script = @"    
+				myobj.RoIntProp = 1;
+				return myobj.RoIntProp;";
+
+				Script S = new Script();
+
+				SomeClass obj = new SomeClass() { IntProp = 321 };
+
+				UserData.UnregisterType<SomeClass>();
+				UserData.RegisterType<SomeClass>(opt);
+
+				S.Globals.Set("myobj", UserData.Create(obj));
+
+				DynValue res = S.DoString(script);
+
+				Assert.AreEqual(DataType.Number, res.Type);
+				Assert.AreEqual(123, res.Number);
+			}
+			catch (ScriptRuntimeException)
+			{
+				return;
+			}
+
+			Assert.Fail();
+		}
+
+
+
+
 		public void Test_IntFieldGetter(InteropAccessMode opt)
 		{
 			string script = @"    
@@ -28,7 +135,7 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 
 			Script S = new Script();
 
-			SomeClass obj = new SomeClass() {  IntProp = 321 };
+			SomeClass obj = new SomeClass() { IntProp = 321 };
 
 			UserData.UnregisterType<SomeClass>();
 			UserData.RegisterType<SomeClass>(opt);
@@ -77,7 +184,7 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 
 			Script S = new Script();
 
-			SomeClass obj1 = new SomeClass() { ObjProp="ciao" };
+			SomeClass obj1 = new SomeClass() { ObjProp = "ciao" };
 			SomeClass obj2 = new SomeClass() { ObjProp = obj1 };
 
 			UserData.UnregisterType<SomeClass>();
@@ -139,7 +246,7 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 			Assert.AreEqual(null, obj2.NIntProp);
 
 			DynValue res = S.DoString(script);
-	
+
 			Assert.AreEqual(null, obj1.NIntProp);
 			Assert.AreEqual(19, obj2.NIntProp);
 		}
@@ -386,14 +493,97 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 			Assert.AreEqual(19, obj.IntProp);
 		}
 
+
+
+
+		[Test]
+		public void Interop_ConstIntFieldGetter_None()
+		{
+			Test_ConstIntFieldGetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_ConstIntFieldGetter_Lazy()
+		{
+			Test_ConstIntFieldGetter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_ConstIntFieldGetter_Precomputed()
+		{
+			Test_ConstIntFieldGetter(InteropAccessMode.Preoptimized);
+		}
+
+
+
+		[Test]
+		public void Interop_ConstIntFieldSetter_None()
+		{
+			Test_ConstIntFieldSetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_ConstIntFieldSetter_Lazy()
+		{
+			Test_ConstIntFieldSetter(InteropAccessMode.LazyOptimized);
+		}
+
 		[Test]
-		public void Interop_Boh()
+		public void Interop_ConstIntFieldSetter_Precomputed()
 		{
-			Script s = new Script();
-			long big = long.MaxValue;
-			var v = DynValue.FromObject(s, big);
-			Assert.IsNotNull(v);
+			Test_ConstIntFieldSetter(InteropAccessMode.Preoptimized);
 		}
 
+
+
+		[Test]
+		public void Interop_ReadOnlyIntFieldGetter_None()
+		{
+			Test_ReadOnlyIntFieldGetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_ReadOnlyIntFieldGetter_Lazy()
+		{
+			Test_ReadOnlyIntFieldGetter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_ReadOnlyIntFieldGetter_Precomputed()
+		{
+			Test_ReadOnlyIntFieldGetter(InteropAccessMode.Preoptimized);
+		}
+
+
+
+		[Test]
+		public void Interop_ReadOnlyIntFieldSetter_None()
+		{
+			Test_ReadOnlyIntFieldSetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_ReadOnlyIntFieldSetter_Lazy()
+		{
+			Test_ReadOnlyIntFieldSetter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_ReadOnlyIntFieldSetter_Precomputed()
+		{
+			Test_ReadOnlyIntFieldSetter(InteropAccessMode.Preoptimized);
+		}
+
+
+
+
+
+
+
+
+
+
+
+
 	}
 }

+ 423 - 5
src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataPropertiesTests.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using MoonSharp.Interpreter.Interop;
 using NUnit.Framework;
 
 namespace MoonSharp.Interpreter.Tests.EndToEnd
@@ -15,7 +16,27 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 			public int? NIntProp { get; set; }
 			public object ObjProp { get; set; }
 			public static string StaticProp { get; set; }
-			private string PrivateProp { get { return "Hello"; } }
+
+			public int RoIntProp { get { return 5; } }
+			public int RoIntProp2 { get; private set; }
+
+			public int WoIntProp { set { IntProp = value; } }
+			public int WoIntProp2 { internal get; set; }
+
+			[MoonSharpVisible(false)]
+			internal int AccessOverrProp
+			{
+				get;
+				[MoonSharpVisible(true)]
+				set;
+			}
+
+
+			public SomeClass()
+			{
+				RoIntProp2 = 1234;
+				WoIntProp2 = 1235;
+			}
 
 			public static IEnumerable<int> Numbers
 			{
@@ -35,7 +56,7 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 
 			Script S = new Script();
 
-			SomeClass obj = new SomeClass() {  IntProp = 321 };
+			SomeClass obj = new SomeClass() { IntProp = 321 };
 
 			UserData.UnregisterType<SomeClass>();
 			UserData.RegisterType<SomeClass>(opt);
@@ -84,7 +105,7 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 
 			Script S = new Script();
 
-			SomeClass obj1 = new SomeClass() { ObjProp="ciao" };
+			SomeClass obj1 = new SomeClass() { ObjProp = "ciao" };
 			SomeClass obj2 = new SomeClass() { ObjProp = obj1 };
 
 			UserData.UnregisterType<SomeClass>();
@@ -146,7 +167,7 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 			Assert.AreEqual(null, obj2.NIntProp);
 
 			DynValue res = S.DoString(script);
-	
+
 			Assert.AreEqual(null, obj1.NIntProp);
 			Assert.AreEqual(19, obj2.NIntProp);
 		}
@@ -244,6 +265,239 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 			Assert.AreEqual(10, res.Number);
 		}
 
+		public void Test_RoIntPropertyGetter(InteropAccessMode opt)
+		{
+			string script = @"    
+				x = myobj.RoIntProp;
+				return x;";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass();
+
+			UserData.UnregisterType<SomeClass>();
+			UserData.RegisterType<SomeClass>(opt);
+
+			S.Globals.Set("myobj", UserData.Create(obj));
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Number, res.Type);
+			Assert.AreEqual(5, res.Number);
+		}
+
+		public void Test_RoIntProperty2Getter(InteropAccessMode opt)
+		{
+			string script = @"    
+				x = myobj.RoIntProp2;
+				return x;";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass();
+
+			UserData.UnregisterType<SomeClass>();
+			UserData.RegisterType<SomeClass>(opt);
+
+			S.Globals.Set("myobj", UserData.Create(obj));
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(DataType.Number, res.Type);
+			Assert.AreEqual(1234, res.Number);
+		}
+
+		public void Test_RoIntPropertySetter(InteropAccessMode opt)
+		{
+			try
+			{
+				string script = @"    
+				myobj.RoIntProp = 19;
+				return myobj.RoIntProp;
+			";
+
+				Script S = new Script();
+
+				SomeClass obj = new SomeClass();
+
+				UserData.UnregisterType<SomeClass>();
+				UserData.RegisterType<SomeClass>(opt);
+
+				S.Globals.Set("myobj", UserData.Create(obj));
+
+				DynValue res = S.DoString(script);
+			}
+			catch (ScriptRuntimeException)
+			{
+				return;
+			}
+
+			Assert.Fail();
+		}
+
+		public void Test_RoIntProperty2Setter(InteropAccessMode opt)
+		{
+			try
+			{
+				string script = @"    
+				myobj.RoIntProp2 = 19;
+				return myobj.RoIntProp2;
+			";
+
+				Script S = new Script();
+
+				SomeClass obj = new SomeClass();
+
+				UserData.UnregisterType<SomeClass>();
+				UserData.RegisterType<SomeClass>(opt);
+
+				S.Globals.Set("myobj", UserData.Create(obj));
+
+				DynValue res = S.DoString(script);
+			}
+			catch (ScriptRuntimeException)
+			{
+				return;
+			}
+
+			Assert.Fail();
+		}
+
+
+		public void Test_WoIntPropertySetter(InteropAccessMode opt)
+		{
+			string script = @"    
+				myobj.WoIntProp = 19;
+			";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass();
+
+			UserData.UnregisterType<SomeClass>();
+			UserData.RegisterType<SomeClass>(opt);
+
+			S.Globals.Set("myobj", UserData.Create(obj));
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(19, obj.IntProp);
+		}
+
+		public void Test_WoIntProperty2Setter(InteropAccessMode opt)
+		{
+			string script = @"    
+				myobj.WoIntProp2 = 19;
+			";
+
+			Script S = new Script();
+
+			SomeClass obj = new SomeClass();
+
+			UserData.UnregisterType<SomeClass>();
+			UserData.RegisterType<SomeClass>(opt);
+
+			S.Globals.Set("myobj", UserData.Create(obj));
+
+			DynValue res = S.DoString(script);
+
+			Assert.AreEqual(19, obj.WoIntProp2);
+		}
+
+
+		public void Test_WoIntPropertyGetter(InteropAccessMode opt)
+		{
+			try
+			{
+				string script = @"    
+				x = myobj.WoIntProp;
+				return x;";
+
+				Script S = new Script();
+
+				SomeClass obj = new SomeClass();
+
+				UserData.UnregisterType<SomeClass>();
+				UserData.RegisterType<SomeClass>(opt);
+
+				S.Globals.Set("myobj", UserData.Create(obj));
+
+				DynValue res = S.DoString(script);
+
+				Assert.AreEqual(DataType.Number, res.Type);
+				Assert.AreEqual(5, res.Number);
+			}
+			catch (ScriptRuntimeException)
+			{
+				return;
+			}
+
+			Assert.Fail();
+		}
+
+		public void Test_WoIntProperty2Getter(InteropAccessMode opt)
+		{
+			try
+			{
+				string script = @"    
+				x = myobj.WoIntProp2;
+				return x;";
+
+				Script S = new Script();
+
+				SomeClass obj = new SomeClass();
+
+				UserData.UnregisterType<SomeClass>();
+				UserData.RegisterType<SomeClass>(opt);
+
+				S.Globals.Set("myobj", UserData.Create(obj));
+
+				DynValue res = S.DoString(script);
+
+				Assert.AreEqual(DataType.Number, res.Type);
+				Assert.AreEqual(1234, res.Number);
+			}
+			catch (ScriptRuntimeException)
+			{
+				return;
+			}
+
+			Assert.Fail();
+		}
+
+
+		public void Test_PropertyAccessOverrides(InteropAccessMode opt)
+		{
+			SomeClass obj = new SomeClass();
+
+			try
+			{
+				string script = @"    
+				myobj.AccessOverrProp = 19;
+				return myobj.AccessOverrProp;
+			";
+
+				Script S = new Script();
+
+				obj.AccessOverrProp = 13;
+
+				UserData.UnregisterType<SomeClass>();
+				UserData.RegisterType<SomeClass>(opt);
+
+				S.Globals.Set("myobj", UserData.Create(obj));
+
+				DynValue res = S.DoString(script);
+			}
+			catch (ScriptRuntimeException)
+			{
+				Assert.AreEqual(19, obj.AccessOverrProp);
+				return;
+			}
+
+			Assert.Fail();
+		}
+
+
 		[Test]
 		public void Interop_IntPropertyGetter_None()
 		{
@@ -411,6 +665,170 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 			Test_IteratorPropertyGetter(InteropAccessMode.Preoptimized);
 		}
 
+		[Test]
+		public void Interop_RoIntPropertyGetter_None()
+		{
+			Test_RoIntPropertyGetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_RoIntPropertyGetter_Lazy()
+		{
+			Test_RoIntPropertyGetter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_RoIntPropertyGetter_Precomputed()
+		{
+			Test_RoIntPropertyGetter(InteropAccessMode.Preoptimized);
+		}
+
+		[Test]
+		public void Interop_RoIntProperty2Getter_None()
+		{
+			Test_RoIntProperty2Getter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_RoIntProperty2Getter_Lazy()
+		{
+			Test_RoIntProperty2Getter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_RoIntProperty2Getter_Precomputed()
+		{
+			Test_RoIntProperty2Getter(InteropAccessMode.Preoptimized);
+		}
+
+		[Test]
+		public void Interop_RoIntPropertySetter_None()
+		{
+			Test_RoIntPropertySetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_RoIntPropertySetter_Lazy()
+		{
+			Test_RoIntPropertySetter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_RoIntPropertySetter_Precomputed()
+		{
+			Test_RoIntPropertySetter(InteropAccessMode.Preoptimized);
+		}
+
+		[Test]
+		public void Interop_RoIntProperty2Setter_None()
+		{
+			Test_RoIntProperty2Setter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_RoIntProperty2Setter_Lazy()
+		{
+			Test_RoIntProperty2Setter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_RoIntProperty2Setter_Precomputed()
+		{
+			Test_RoIntProperty2Setter(InteropAccessMode.Preoptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntPropertyGetter_None()
+		{
+			Test_WoIntPropertyGetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_WoIntPropertyGetter_Lazy()
+		{
+			Test_WoIntPropertyGetter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntPropertyGetter_Precomputed()
+		{
+			Test_WoIntPropertyGetter(InteropAccessMode.Preoptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntProperty2Getter_None()
+		{
+			Test_WoIntProperty2Getter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_WoIntProperty2Getter_Lazy()
+		{
+			Test_WoIntProperty2Getter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntProperty2Getter_Precomputed()
+		{
+			Test_WoIntProperty2Getter(InteropAccessMode.Preoptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntPropertySetter_None()
+		{
+			Test_WoIntPropertySetter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_WoIntPropertySetter_Lazy()
+		{
+			Test_WoIntPropertySetter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntPropertySetter_Precomputed()
+		{
+			Test_WoIntPropertySetter(InteropAccessMode.Preoptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntProperty2Setter_None()
+		{
+			Test_WoIntProperty2Setter(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_WoIntProperty2Setter_Lazy()
+		{
+			Test_WoIntProperty2Setter(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_WoIntProperty2Setter_Precomputed()
+		{
+			Test_WoIntProperty2Setter(InteropAccessMode.Preoptimized);
+		}
+		
+		[Test]
+		public void Interop_PropertyAccessOverrides_None()
+		{
+			Test_PropertyAccessOverrides(InteropAccessMode.Reflection);
+		}
+
+		[Test]
+		public void Interop_PropertyAccessOverrides_Lazy()
+		{
+			Test_PropertyAccessOverrides(InteropAccessMode.LazyOptimized);
+		}
+
+		[Test]
+		public void Interop_PropertyAccessOverrides_Precomputed()
+		{
+			Test_PropertyAccessOverrides(InteropAccessMode.Preoptimized);
+		}		
+		
+		
+		
 		[Test]
 		public void Interop_IntPropertySetterWithSimplifiedSyntax()
 		{
@@ -435,7 +853,7 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd
 		}
 
 		[Test]
-		public void Interop_Boh()
+		public void Interop_OutOfRangeNumber()
 		{
 			Script s = new Script();
 			long big = long.MaxValue;

+ 1 - 0
src/MoonSharp.Interpreter.Tests/MoonSharp.Interpreter.Tests.net35-client.csproj

@@ -126,6 +126,7 @@
     </Compile>
     <Compile Include="EndToEnd\StringLibTests.cs" />
     <Compile Include="EndToEnd\TailCallTests.cs" />
+    <Compile Include="EndToEnd\UserDataEventsTests.cs" />
     <Compile Include="EndToEnd\UserDataMetaTests.cs" />
     <Compile Include="EndToEnd\UserDataIndexerTests.cs" />
     <Compile Include="EndToEnd\UserDataMethodsTests.cs" />

+ 3 - 0
src/MoonSharp.Interpreter.Tests/_Projects/MoonSharp.Interpreter.Tests.Embeddable.portable40/MoonSharp.Interpreter.Tests.Embeddable.portable40.csproj

@@ -92,6 +92,9 @@
     <Compile Include="..\..\EndToEnd\TailCallTests.cs">
       <Link>TailCallTests.cs</Link>
     </Compile>
+    <Compile Include="..\..\EndToEnd\UserDataEventsTests.cs">
+      <Link>UserDataEventsTests.cs</Link>
+    </Compile>
     <Compile Include="..\..\EndToEnd\UserDataMetaTests.cs">
       <Link>UserDataMetaTests.cs</Link>
     </Compile>

+ 3 - 0
src/MoonSharp.Interpreter.Tests/_Projects/MoonSharp.Interpreter.Tests.net40-client/MoonSharp.Interpreter.Tests.net40-client.csproj

@@ -108,6 +108,9 @@
     <Compile Include="..\..\EndToEnd\TailCallTests.cs">
       <Link>TailCallTests.cs</Link>
     </Compile>
+    <Compile Include="..\..\EndToEnd\UserDataEventsTests.cs">
+      <Link>UserDataEventsTests.cs</Link>
+    </Compile>
     <Compile Include="..\..\EndToEnd\UserDataMetaTests.cs">
       <Link>UserDataMetaTests.cs</Link>
     </Compile>

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

@@ -108,6 +108,9 @@
     <Compile Include="..\..\EndToEnd\TailCallTests.cs">
       <Link>TailCallTests.cs</Link>
     </Compile>
+    <Compile Include="..\..\EndToEnd\UserDataEventsTests.cs">
+      <Link>UserDataEventsTests.cs</Link>
+    </Compile>
     <Compile Include="..\..\EndToEnd\UserDataMetaTests.cs">
       <Link>UserDataMetaTests.cs</Link>
     </Compile>

+ 73 - 4
src/MoonSharp.Interpreter/DataStructs/MultiDictionary.cs

@@ -8,28 +8,59 @@ namespace MoonSharp.Interpreter.DataStructs
 	/// <summary>
 	/// A Dictionary where multiple values can be associated to the same key
 	/// </summary>
-	/// <typeparam name="K"></typeparam>
-	/// <typeparam name="V"></typeparam>
+	/// <typeparam name="K">The key type</typeparam>
+	/// <typeparam name="V">The value type</typeparam>
 	internal class MultiDictionary<K, V>
 	{
-		Dictionary<K, List<V>> m_Map = new Dictionary<K, List<V>>();
+		Dictionary<K, List<V>> m_Map;
 		V[] m_DefaultRet = new V[0];
 
-		public void Add(K key, V value)
+		/// <summary>
+		/// Initializes a new instance of the <see cref="MultiDictionary{K, V}"/> class.
+		/// </summary>
+		public MultiDictionary()
+		{
+			m_Map = new Dictionary<K, List<V>>();
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="MultiDictionary{K, V}"/> class.
+		/// </summary>
+		/// <param name="eqComparer">The equality comparer to use in the underlying dictionary.</param>
+		public MultiDictionary(IEqualityComparer<K> eqComparer)
+		{
+			m_Map = new Dictionary<K, List<V>>(eqComparer);
+		}
+
+
+		/// <summary>
+		/// Adds the specified key. Returns true if this is the first value for a given key
+		/// </summary>
+		/// <param name="key">The key.</param>
+		/// <param name="value">The value.</param>
+		/// <returns></returns>
+		public bool Add(K key, V value)
 		{
 			List<V> list;
 			if (m_Map.TryGetValue(key, out list))
 			{
 				list.Add(value);
+				return false;
 			}
 			else
 			{
 				list = new List<V>();
 				list.Add(value);
 				m_Map.Add(key, list);
+				return true;
 			}
 		}
 
+		/// <summary>
+		/// Finds all the values associated with the specified key. 
+		/// An empty collection is returned if not found.
+		/// </summary>
+		/// <param name="key">The key.</param>
 		public IEnumerable<V> Find(K key)
 		{
 			List<V> list;
@@ -39,25 +70,63 @@ namespace MoonSharp.Interpreter.DataStructs
 				return m_DefaultRet;
 		}
 
+		/// <summary>
+		/// Determines whether this contains the specified key 
+		/// </summary>
+		/// <param name="key">The key.</param>
 		public bool ContainsKey(K key)
 		{
 			return m_Map.ContainsKey(key);
 		}
 
+		/// <summary>
+		/// Gets the keys.
+		/// </summary>
 		public IEnumerable<K> Keys
 		{
 			get { return m_Map.Keys; }
 		}
 
+		/// <summary>
+		/// Clears this instance.
+		/// </summary>
 		public void Clear()
 		{
 			m_Map.Clear();
 		}
 
+		/// <summary>
+		/// Removes the specified key and all its associated values from the multidictionary
+		/// </summary>
+		/// <param name="key">The key.</param>
 		public void Remove(K key)
 		{
 			m_Map.Remove(key);
 		}
 
+		/// <summary>
+		/// Removes the value. Returns true if the removed value was the last of a given key
+		/// </summary>
+		/// <param name="key">The key.</param>
+		/// <param name="value">The value.</param>
+		/// <returns></returns>
+		public bool RemoveValue(K key, V value)
+		{
+			List<V> list;
+
+			if (m_Map.TryGetValue(key, out list))
+			{
+				list.Remove(value);
+
+				if (list.Count == 0)
+				{
+					Remove(key);
+					return true;
+				}
+			}
+
+			return false;
+		}
+
 	}
 }

+ 23 - 0
src/MoonSharp.Interpreter/DataStructs/ReferenceEqualityComparer.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MoonSharp.Interpreter.DataStructs
+{
+	/// <summary>
+	/// Implementation of IEqualityComparer enforcing reference equality
+	/// </summary>
+	internal class ReferenceEqualityComparer : IEqualityComparer<object>
+	{
+		bool IEqualityComparer<object>.Equals(object x, object y)
+		{
+			return object.ReferenceEquals(x, y);
+		}
+
+		int IEqualityComparer<object>.GetHashCode(object obj)
+		{
+			return obj.GetHashCode();
+		}
+	}
+}

+ 1 - 0
src/MoonSharp.Interpreter/DataTypes/UserData.cs

@@ -45,6 +45,7 @@ namespace MoonSharp.Interpreter
 
 		static UserData()
 		{
+			RegisterType<StandardUserDataEventDescriptor.EventFacade>(InteropAccessMode.Reflection);
 			RegisterType<AnonWrapper>(InteropAccessMode.HideMembers);
 			RegisterType<EnumerableWrapper>(InteropAccessMode.HideMembers);
 			s_DefaultAccessMode = InteropAccessMode.LazyOptimized;

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

@@ -475,5 +475,6 @@ namespace MoonSharp.Interpreter
 			else
 				return new ScriptRuntimeException("attempt to call a {0} value", functype);
 		}
+
 	}
 }

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

@@ -628,7 +628,7 @@ namespace MoonSharp.Interpreter.Execution.VM
 				{
 					Instruction I = this.m_RootChunk.Code[instructionPtr];
 										
-					// and we are followed by a blatant RET 1
+					// and we are followed *exactly* by a RET 1
 					if (I.OpCode == OpCode.Ret && I.NumVal == 1)
 					{
 						CallStackItem csi = m_ExecutionStack.Peek();

+ 1 - 1
src/MoonSharp.Interpreter/Interop/Attributes/MoonSharpVisibleAttribute.cs

@@ -8,7 +8,7 @@ namespace MoonSharp.Interpreter.Interop
 	/// <summary>
 	/// Forces a class member visibility to scripts. Can be used to hide public members or to expose non-public ones.
 	/// </summary>
-	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
+	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event, Inherited = false, AllowMultiple = false)]
 	public sealed class MoonSharpVisibleAttribute : Attribute
 	{
 		/// <summary>

+ 69 - 69
src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataDescriptor.cs

@@ -31,12 +31,12 @@ namespace MoonSharp.Interpreter.Interop
 		/// </summary>
 		public string FriendlyName { get; private set; }
 
-		private object m_Lock = new object();
 		private int m_ExtMethodsVersion = 0;
 		private Dictionary<string, StandardUserDataOverloadedMethodDescriptor> m_MetaMethods = new Dictionary<string, StandardUserDataOverloadedMethodDescriptor>();
 		private Dictionary<string, StandardUserDataOverloadedMethodDescriptor> m_Methods = new Dictionary<string, StandardUserDataOverloadedMethodDescriptor>();
 		private Dictionary<string, StandardUserDataPropertyDescriptor> m_Properties = new Dictionary<string, StandardUserDataPropertyDescriptor>();
 		private Dictionary<string, StandardUserDataFieldDescriptor> m_Fields = new Dictionary<string, StandardUserDataFieldDescriptor>();
+		private Dictionary<string, StandardUserDataEventDescriptor> m_Events = new Dictionary<string, StandardUserDataEventDescriptor>();
 
 		const string SPECIAL_GETITEM = "get_Item";
 		const string SPECIAL_SETITEM = "set_Item";
@@ -71,9 +71,10 @@ namespace MoonSharp.Interpreter.Interop
 
 				foreach (ConstructorInfo ci in type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
 				{
-					if (CheckVisibility(ci.GetCustomAttributes(true), ci.IsPublic))
+					StandardUserDataMethodDescriptor md = StandardUserDataMethodDescriptor.TryCreateIfVisible(ci, this.AccessMode);
+
+					if (md != null)
 					{
-						var md = new StandardUserDataMethodDescriptor(ci, this.AccessMode);
 						if (constructors == null) constructors = new StandardUserDataOverloadedMethodDescriptor("__new", this.Type) { IgnoreExtensionMethods = true };
 						constructors.AddOverload(md);
 					}
@@ -84,7 +85,9 @@ namespace MoonSharp.Interpreter.Interop
 				// get methods
 				foreach (MethodInfo mi in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
 				{
-					if (CheckVisibility(mi.GetCustomAttributes(true), mi.IsPublic))
+					StandardUserDataMethodDescriptor md = StandardUserDataMethodDescriptor.TryCreateIfVisible(mi, this.AccessMode);
+
+					if (md != null)
 					{
 						if (!StandardUserDataMethodDescriptor.CheckMethodIsCompatible(mi, false))
 							continue;
@@ -97,8 +100,6 @@ namespace MoonSharp.Interpreter.Interop
 							name = GetConversionMethodName(mi.ReturnType);
 						}
 
-						var md = new StandardUserDataMethodDescriptor(mi, this.AccessMode);
-
 						if (m_Methods.ContainsKey(name))
 						{
 							m_Methods[name].AddOverload(md);
@@ -108,7 +109,7 @@ namespace MoonSharp.Interpreter.Interop
 							m_Methods.Add(name, new StandardUserDataOverloadedMethodDescriptor(name, this.Type, md));
 						}
 
-						foreach(string metaname in GetMetaNamesFromAttributes(mi))
+						foreach (string metaname in GetMetaNamesFromAttributes(mi))
 						{
 							if (m_MetaMethods.ContainsKey(metaname))
 							{
@@ -125,27 +126,32 @@ namespace MoonSharp.Interpreter.Interop
 				// get properties
 				foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
 				{
-					if (CheckVisibility(pi.GetCustomAttributes(true), IsPropertyInfoPublic(pi)))
-					{
-						if (pi.IsSpecialName || pi.GetIndexParameters().Any())
-							continue;
+					if (pi.IsSpecialName || pi.GetIndexParameters().Any())
+						continue;
 
-						var pd = new StandardUserDataPropertyDescriptor(pi, this.AccessMode);
+					var pd = StandardUserDataPropertyDescriptor.TryCreateIfVisible(pi, this.AccessMode);
+					if (pd != null)
 						m_Properties.Add(pd.Name, pd);
-					}
 				}
 
 				// get fields
 				foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
 				{
-					if (CheckVisibility(fi.GetCustomAttributes(true), fi.IsPublic))
-					{
-						if (fi.IsSpecialName)
-							continue;
+					if (fi.IsSpecialName)
+						continue;
 
-						var pd = new StandardUserDataFieldDescriptor(fi, this.AccessMode);
-						m_Fields.Add(pd.Name, pd);
-					}
+					var fd = StandardUserDataFieldDescriptor.TryCreateIfVisible(fi, this.AccessMode);
+					if (fd != null) m_Fields.Add(fd.Name, fd);
+				}
+
+				// get events
+				foreach (EventInfo ei in type.GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
+				{
+					if (ei.IsSpecialName)
+						continue;
+
+					var ed = StandardUserDataEventDescriptor.TryCreateIfVisible(ei, this.AccessMode);
+					if (ed != null) m_Events.Add(ei.Name, ed);
 				}
 			}
 		}
@@ -176,14 +182,28 @@ namespace MoonSharp.Interpreter.Interop
 			return (getter != null && getter.IsPublic) || (setter != null && setter.IsPublic);
 		}
 
-		private bool CheckVisibility(object[] attributes, bool isPublic)
+		/// <summary>
+		/// Determines whether a 
+		/// <see cref="MoonSharpVisibleAttribute" /> is changing visibility of a member
+		/// to scripts.
+		/// </summary>
+		/// <param name="mi">The member to check.</param>
+		/// <returns>
+		/// <c>true</c> if visibility is forced visible, 
+		/// <c>false</c> if visibility is forced hidden or the specified MemberInfo is null,
+		/// <c>if no attribute was found</c>
+		/// </returns>
+		public static bool? GetVisibilityFromAttributes(MemberInfo mi)
 		{
-			MoonSharpVisibleAttribute va = attributes.OfType<MoonSharpVisibleAttribute>().SingleOrDefault();
+			if (mi == null)
+				return false;
+
+			MoonSharpVisibleAttribute va = mi.GetCustomAttributes(true).OfType<MoonSharpVisibleAttribute>().SingleOrDefault();
 
 			if (va != null)
 				return va.Visible;
 			else
-				return isPublic;
+				return null;
 		}
 
 
@@ -199,10 +219,7 @@ namespace MoonSharp.Interpreter.Interop
 		{
 			if (!isDirectIndexing)
 			{
-				StandardUserDataOverloadedMethodDescriptor mdesc;
-
-				lock (m_Lock) 
-					mdesc = m_Methods.GetOrDefault(SPECIAL_GETITEM);
+				StandardUserDataOverloadedMethodDescriptor mdesc = m_Methods.GetOrDefault(SPECIAL_GETITEM);
 
 				if (mdesc != null)
 					return ExecuteIndexer(mdesc, script, obj, index, null);
@@ -213,24 +230,19 @@ namespace MoonSharp.Interpreter.Interop
 			if (index.Type != DataType.String)
 				throw ScriptRuntimeException.BadArgument(1, string.Format("userdata<{0}>.__index", this.Name), "string", index.Type.ToLuaTypeString(), false);
 
-			DynValue v = null;
+			DynValue v = TryIndex(script, obj, index.String);
+			if (v == null) v = TryIndex(script, obj, UpperFirstLetter(index.String));
+			if (v == null) v = TryIndex(script, obj, Camelify(index.String));
+			if (v == null) v = TryIndex(script, obj, UpperFirstLetter(Camelify(index.String)));
 
-			lock (m_Lock)
+			if (v == null && m_ExtMethodsVersion < UserData.GetExtensionMethodsChangeVersion())
 			{
-				v =TryIndex(script, obj, index.String);
-				if (v == null) v = TryIndex(script, obj, UpperFirstLetter(index.String));
-				if (v == null) v = TryIndex(script, obj, Camelify(index.String));
-				if (v == null) v = TryIndex(script, obj, UpperFirstLetter(Camelify(index.String)));
-
-				if (v == null && m_ExtMethodsVersion < UserData.GetExtensionMethodsChangeVersion())
-				{
-					m_ExtMethodsVersion = UserData.GetExtensionMethodsChangeVersion();
+				m_ExtMethodsVersion = UserData.GetExtensionMethodsChangeVersion();
 
-					v = TryIndexOnExtMethod(script, obj, index.String);
-					if (v == null) v = TryIndexOnExtMethod(script, obj, UpperFirstLetter(index.String));
-					if (v == null) v = TryIndexOnExtMethod(script, obj, Camelify(index.String));
-					if (v == null) v = TryIndexOnExtMethod(script, obj, UpperFirstLetter(Camelify(index.String)));
-				}
+				v = TryIndexOnExtMethod(script, obj, index.String);
+				if (v == null) v = TryIndexOnExtMethod(script, obj, UpperFirstLetter(index.String));
+				if (v == null) v = TryIndexOnExtMethod(script, obj, Camelify(index.String));
+				if (v == null) v = TryIndexOnExtMethod(script, obj, UpperFirstLetter(Camelify(index.String)));
 			}
 
 			return v;
@@ -258,7 +270,7 @@ namespace MoonSharp.Interpreter.Interop
 				return DynValue.NewCallback(ext.GetCallback(script, obj));
 			}
 
-			return null;			
+			return null;
 		}
 
 		/// <summary>
@@ -285,6 +297,11 @@ namespace MoonSharp.Interpreter.Interop
 			if (m_Fields.TryGetValue(indexName, out fdesc))
 				return fdesc.GetValue(script, obj);
 
+			StandardUserDataEventDescriptor edesc;
+
+			if (m_Events.TryGetValue(indexName, out edesc))
+				return edesc.GetValue(script, obj);
+
 			return null;
 		}
 
@@ -301,10 +318,7 @@ namespace MoonSharp.Interpreter.Interop
 		{
 			if (!isDirectIndexing)
 			{
-				StandardUserDataOverloadedMethodDescriptor mdesc;
-				
-				lock(m_Lock)
-					mdesc = m_Methods.GetOrDefault(SPECIAL_SETITEM);
+				StandardUserDataOverloadedMethodDescriptor mdesc = m_Methods.GetOrDefault(SPECIAL_SETITEM);
 
 				if (mdesc != null)
 				{
@@ -318,15 +332,10 @@ namespace MoonSharp.Interpreter.Interop
 			if (index.Type != DataType.String)
 				throw ScriptRuntimeException.BadArgument(1, string.Format("userdata<{0}>.__setindex", this.Name), "string", index.Type.ToLuaTypeString(), false);
 
-			bool v = false;
-
-			lock (m_Lock)
-			{
-				v = TrySetIndex(script, obj, index.String, value);
-				if (!v) v = TrySetIndex(script, obj, UpperFirstLetter(index.String), value);
-				if (!v) v = TrySetIndex(script, obj, Camelify(index.String), value);
-				if (!v) v = TrySetIndex(script, obj, UpperFirstLetter(Camelify(index.String)), value);
-			}
+			bool v = TrySetIndex(script, obj, index.String, value);
+			if (!v) v = TrySetIndex(script, obj, UpperFirstLetter(index.String), value);
+			if (!v) v = TrySetIndex(script, obj, Camelify(index.String), value);
+			if (!v) v = TrySetIndex(script, obj, UpperFirstLetter(Camelify(index.String)), value);
 
 			return v;
 		}
@@ -341,22 +350,16 @@ namespace MoonSharp.Interpreter.Interop
 		/// <returns></returns>
 		protected virtual bool TrySetIndex(Script script, object obj, string indexName, DynValue value)
 		{
-			StandardUserDataPropertyDescriptor pdesc;
-
-			lock(m_Lock)
-				pdesc = m_Properties.GetOrDefault(indexName);
+			StandardUserDataPropertyDescriptor pdesc = m_Properties.GetOrDefault(indexName);
 
 			if (pdesc != null)
 			{
 				pdesc.SetValue(script, obj, value);
 				return true;
 			}
-			else 
+			else
 			{
-				StandardUserDataFieldDescriptor fdesc;
-
-				lock (m_Lock)
-					fdesc = m_Fields.GetOrDefault(indexName);
+				StandardUserDataFieldDescriptor fdesc = m_Fields.GetOrDefault(indexName);
 
 				if (fdesc != null)
 				{
@@ -512,10 +515,7 @@ namespace MoonSharp.Interpreter.Interop
 		/// <returns></returns>
 		public virtual DynValue MetaIndex(Script script, object obj, string metaname)
 		{
-			StandardUserDataOverloadedMethodDescriptor desc;
-			
-			lock(m_Lock)
-				desc = m_MetaMethods.GetOrDefault(metaname);
+			StandardUserDataOverloadedMethodDescriptor desc = m_MetaMethods.GetOrDefault(metaname);
 
 			if (desc != null)
 				return desc.GetCallbackAsDynValue(script, obj);

+ 334 - 0
src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataEventDescriptor.cs

@@ -0,0 +1,334 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using MoonSharp.Interpreter.DataStructs;
+
+namespace MoonSharp.Interpreter.Interop
+{
+	/// <summary>
+	/// Class providing easier marshalling of CLR events. Handling is limited to a narrow range of handler signatures, which,
+	/// however, covers in practice most of all available events.
+	/// </summary>
+	public class StandardUserDataEventDescriptor
+	{
+		/// <summary>
+		/// The maximum number of arguments supported in an event handler delegate
+		/// </summary>
+		public const int MAX_ARGS_IN_DELEGATE = 16;
+
+		#region Wrapper as DynValue
+		internal class EventFacade : IUserDataType
+		{
+			StandardUserDataEventDescriptor m_Parent;
+			object m_Object;
+
+			public EventFacade(StandardUserDataEventDescriptor parent, object obj)
+			{
+				m_Parent = parent;
+				m_Object = obj;
+			}
+
+			public DynValue Index(Script script, DynValue index, bool isDirectIndexing)
+			{
+				if (index.Type == DataType.String)
+				{
+					if (index.String == "add")
+						return DynValue.NewCallback((c, a) => m_Parent.AddCallback(m_Object, c, a));
+					else if (index.String == "remove")
+						return DynValue.NewCallback((c, a) => m_Parent.RemoveCallback(m_Object, c, a));
+				}
+
+				throw new ScriptRuntimeException("Events only support add and remove methods");
+			}
+
+			public bool SetIndex(Script script, DynValue index, DynValue value, bool isDirectIndexing)
+			{
+				throw new ScriptRuntimeException("Events do not have settable fields");
+			}
+
+			public DynValue MetaIndex(Script script, string metaname)
+			{
+				return null;
+			}
+		}
+		#endregion
+
+
+		object m_Lock = new object();
+		MultiDictionary<object, Closure> m_Callbacks = new MultiDictionary<object, Closure>(new ReferenceEqualityComparer());
+		Dictionary<object, Delegate> m_Delegates = new Dictionary<object, Delegate>(new ReferenceEqualityComparer());
+
+		/// <summary>
+		/// Tries to create a new StandardUserDataEventDescriptor, returning <c>null</c> in case the method is not 
+		/// visible to script code.
+		/// </summary>
+		/// <param name="ei">The EventInfo.</param>
+		/// <param name="accessMode">The <see cref="InteropAccessMode" /></param>
+		/// <returns>A new StandardUserDataEventDescriptor or null.</returns>
+		public static StandardUserDataEventDescriptor TryCreateIfVisible(EventInfo ei, InteropAccessMode accessMode)
+		{
+			if (!CheckEventIsCompatible(ei, false))
+				return null;
+
+			if (StandardUserDataDescriptor.GetVisibilityFromAttributes(ei) ?? (ei.GetAddMethod().IsPublic && ei.GetRemoveMethod().IsPublic))
+				return new StandardUserDataEventDescriptor(ei, accessMode);
+
+			return null;
+		}
+
+
+		/// <summary>
+		/// Checks if the event is compatible with a standard descriptor
+		/// </summary>
+		/// <param name="ei">The EventInfo.</param>
+		/// <param name="throwException">if set to <c>true</c> an exception with the proper error message is thrown if not compatible.</param>
+		/// <returns></returns>
+		/// <exception cref="System.ArgumentException">
+		/// Thrown if throwException is <c>true</c> and one of this applies:
+		/// The event is declared in a value type
+		/// or
+		/// The event does not have both add and remove methods 
+		/// or
+		/// The event handler type doesn't implement a public Invoke method
+		/// or
+		/// The event handler has a return type which is not System.Void
+		/// or
+		/// The event handler has more than MAX_ARGS_IN_DELEGATE parameters
+		/// or
+		/// The event handler has a value type parameter or a by ref parameter
+		/// or
+		/// The event handler signature is not a valid method according to <see cref="StandardUserDataMethodDescriptor.CheckMethodIsCompatible"/>
+		/// </exception>
+		public static bool CheckEventIsCompatible(EventInfo ei, bool throwException)
+		{
+			if (ei.DeclaringType.IsValueType)
+			{
+				if (throwException) throw new ArgumentException("Events are not supported on value types");
+				return false;
+			}
+
+			if ((ei.GetAddMethod(true) == null) || (ei.GetRemoveMethod(true) == null))
+			{
+				if (throwException) throw new ArgumentException("Event must have add and remove methods");
+				return false;
+			}
+
+			MethodInfo invoke = ei.EventHandlerType.GetMethod("Invoke");
+
+			if (invoke == null)
+			{
+				if (throwException) throw new ArgumentException("Event handler type doesn't seem to be a delegate");
+				return false;
+			}
+
+			if (!StandardUserDataMethodDescriptor.CheckMethodIsCompatible(invoke, throwException))
+				return false;
+
+			if (invoke.ReturnType != typeof(void))
+			{
+				if (throwException) throw new ArgumentException("Event handler cannot have a return type");
+				return false;
+			}
+
+			ParameterInfo[] pars = invoke.GetParameters();
+
+			if (pars.Length > MAX_ARGS_IN_DELEGATE)
+			{
+				if (throwException) throw new ArgumentException(string.Format("Event handler cannot have more than {0} parameters", MAX_ARGS_IN_DELEGATE));
+				return false;
+			}
+
+			foreach (ParameterInfo pi in pars)
+			{
+				if (pi.ParameterType.IsValueType)
+				{
+					if (throwException) throw new ArgumentException("Event handler cannot have value type parameters");
+					return false;
+				}
+				else if (pi.ParameterType.IsByRef)
+				{
+					if (throwException) throw new ArgumentException("Event handler cannot have by-ref type parameters");
+					return false;
+				}
+			}
+
+			return true;
+		}
+
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="StandardUserDataEventDescriptor"/> class.
+		/// </summary>
+		/// <param name="ei">The ei.</param>
+		/// <param name="accessMode">The access mode.</param>
+		public StandardUserDataEventDescriptor(EventInfo ei, InteropAccessMode accessMode = InteropAccessMode.Default)
+		{
+			CheckEventIsCompatible(ei, true);
+			EventInfo = ei;
+			m_Add = ei.GetAddMethod(true);
+			m_Remove = ei.GetRemoveMethod(true);
+			IsStatic = m_Add.IsStatic;
+		}
+
+		/// <summary>
+		/// Gets the EventInfo object of the event described by this descriptor
+		/// </summary>
+		public EventInfo EventInfo { get; private set; }
+		/// <summary>
+		/// Gets a value indicating whether the event described by this descriptor is static.
+		/// </summary>
+		public bool IsStatic { get; private set; }
+
+		private MethodInfo m_Add, m_Remove;
+		private List<DynValue> m_Handlers = new List<DynValue>();
+
+		/// <summary>
+		/// Gets a dynvalue which is a facade supporting add/remove methods which is callable from scripts
+		/// </summary>
+		/// <param name="script">The script.</param>
+		/// <param name="obj">The object for which the facade should be written.</param>
+		/// <returns></returns>
+		public DynValue GetValue(Script script, object obj)
+		{
+			if (IsStatic) 
+				obj = this;
+
+			return UserData.Create(new EventFacade(this, obj));
+		}
+
+
+		private DynValue AddCallback(object o, ScriptExecutionContext context, CallbackArguments args)
+		{
+			lock (m_Lock)
+			{
+				Closure closure = args.AsType(0, string.Format("userdata<{0}>.{1}.add", EventInfo.DeclaringType, EventInfo.Name),
+					DataType.Function, false).Function;
+
+				if (m_Callbacks.Add(o, closure))
+					RegisterCallback(o);
+
+				return DynValue.Void;
+			}
+		}
+
+		private DynValue RemoveCallback(object o, ScriptExecutionContext context, CallbackArguments args)
+		{
+			lock (m_Lock)
+			{
+				Closure closure = args.AsType(0, string.Format("userdata<{0}>.{1}.remove", EventInfo.DeclaringType, EventInfo.Name),
+					DataType.Function, false).Function;
+
+				if (m_Callbacks.RemoveValue(o, closure))
+					UnregisterCallback(o);
+
+				return DynValue.Void;
+			}
+		}
+
+		private void RegisterCallback(object o)
+		{
+			m_Delegates.GetOrCreate(o, () =>
+				{
+					Delegate d = CreateDelegate(o);
+					Delegate handler = Delegate.CreateDelegate(EventInfo.EventHandlerType, d.Target, d.Method);
+					m_Add.Invoke(o, new object[] { handler });
+					return handler;
+				});
+		}
+
+		private void UnregisterCallback(object o)
+		{
+			Delegate handler = m_Delegates.GetOrDefault(o);
+
+			if (handler == null)
+				throw new InternalErrorException("can't unregister null delegate");
+
+			m_Delegates.Remove(handler);
+			m_Remove.Invoke(o, new object[] { handler });
+		}
+
+
+		private Delegate CreateDelegate(object sender)
+		{
+			switch (EventInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Length)
+			{
+				case 0:
+					return (EventWrapper00)(() => DispatchEvent(sender));
+				case 1:
+					return (EventWrapper01)((o1) => DispatchEvent(sender, o1));
+				case 2:
+					return (EventWrapper02)((o1, o2) => DispatchEvent(sender, o1, o2));
+				case 3:
+					return (EventWrapper03)((o1, o2, o3) => DispatchEvent(sender, o1, o2, o3));
+				case 4:
+					return (EventWrapper04)((o1, o2, o3, o4) => DispatchEvent(sender, o1, o2, o3, o4));
+				case 5: 
+					return (EventWrapper05)((o1, o2, o3, o4, o5) => DispatchEvent(sender, o1, o2, o3, o4, o5));
+				case 6: 
+					return (EventWrapper06)((o1, o2, o3, o4, o5, o6) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6));
+				case 7: 
+					return (EventWrapper07)((o1, o2, o3, o4, o5, o6, o7) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7));
+				case 8: 
+					return (EventWrapper08)((o1, o2, o3, o4, o5, o6, o7, o8) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8));
+				case 9: 
+					return (EventWrapper09)((o1, o2, o3, o4, o5, o6, o7, o8, o9) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9));
+				case 10: 
+					return (EventWrapper10)((o1, o2, o3, o4, o5, o6, o7, o8, o9, o10) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10));
+				case 11: 
+					return (EventWrapper11)((o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11));
+				case 12: 
+					return (EventWrapper12)((o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12));
+				case 13: 
+					return (EventWrapper13)((o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13));
+				case 14: 
+					return (EventWrapper14)((o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14));
+				case 15: 
+					return (EventWrapper15)((o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15));
+				case 16: 
+					return (EventWrapper16)((o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16) => DispatchEvent(sender, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16));
+				default:
+					throw new InternalErrorException("too many args in delegate type");
+			}
+		}
+
+		private void DispatchEvent(object sender, 
+			object o01 = null, object o02 = null, object o03 = null, object o04 = null,
+			object o05 = null, object o06 = null, object o07 = null, object o08 = null,
+			object o09 = null, object o10 = null, object o11 = null, object o12 = null,
+			object o13 = null, object o14 = null, object o15 = null, object o16 = null)
+		{
+			Closure[] closures = null;
+			lock (m_Lock)
+			{
+				closures = m_Callbacks.Find(sender).ToArray();
+			}
+
+			foreach (Closure c in closures)
+			{
+				c.Call(o01, o02, o03, o04, o05, o06, o07, o08, o09, o10, o11, o12, o13, o14, o15, o16);
+			}
+		}
+
+		private delegate void EventWrapper00();
+		private delegate void EventWrapper01(object o1);
+		private delegate void EventWrapper02(object o1, object o2);
+		private delegate void EventWrapper03(object o1, object o2, object o3);
+		private delegate void EventWrapper04(object o1, object o2, object o3, object o4);
+		private delegate void EventWrapper05(object o1, object o2, object o3, object o4, object o5);
+		private delegate void EventWrapper06(object o1, object o2, object o3, object o4, object o5, object o6);
+		private delegate void EventWrapper07(object o1, object o2, object o3, object o4, object o5, object o6, object o7);
+		private delegate void EventWrapper08(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8);
+		private delegate void EventWrapper09(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9);
+		private delegate void EventWrapper10(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9, object o10);
+		private delegate void EventWrapper11(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9, object o10, object o11);
+		private delegate void EventWrapper12(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9, object o10, object o11, object o12);
+		private delegate void EventWrapper13(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9, object o10, object o11, object o12, object o13);
+		private delegate void EventWrapper14(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9, object o10, object o11, object o12, object o13, object o14);
+		private delegate void EventWrapper15(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9, object o10, object o11, object o12, object o13, object o14, object o15);
+		private delegate void EventWrapper16(object o1, object o2, object o3, object o4, object o5, object o6, object o7, object o8, object o9, object o10, object o11, object o12, object o13, object o14, object o15, object o16);
+	}
+}
+
+

+ 55 - 1
src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataFieldDescriptor.cs

@@ -31,16 +31,43 @@ namespace MoonSharp.Interpreter.Interop
 		/// Gets the name of the property
 		/// </summary>
 		public string Name { get; private set; }
+		/// <summary>
+		/// Gets a value indicating whether this instance is a constant 
+		/// </summary>
+		public bool IsConst { get; private set; }
+		/// <summary>
+		/// Gets a value indicating whether this instance is readonly 
+		/// </summary>
+		public bool IsReadonly { get; private set; }
+
+
+		object m_ConstValue = null;
 
 		Func<object, object> m_OptimizedGetter = null;
 
 
+		/// <summary>
+		/// Tries to create a new StandardUserDataFieldDescriptor, returning <c>null</c> in case the field is not 
+		/// visible to script code.
+		/// </summary>
+		/// <param name="fi">The FieldInfo.</param>
+		/// <param name="accessMode">The <see cref="InteropAccessMode" /></param>
+		/// <returns>A new StandardUserDataFieldDescriptor or null.</returns>
+		public static StandardUserDataFieldDescriptor TryCreateIfVisible(FieldInfo fi, InteropAccessMode accessMode)
+		{
+			if (StandardUserDataDescriptor.GetVisibilityFromAttributes(fi) ?? fi.IsPublic)
+				return new StandardUserDataFieldDescriptor(fi, accessMode);
+
+			return null;
+		}
+
+
 		/// <summary>
 		/// Initializes a new instance of the <see cref="StandardUserDataPropertyDescriptor"/> class.
 		/// </summary>
 		/// <param name="fi">The FieldInfo.</param>
 		/// <param name="accessMode">The <see cref="InteropAccessMode" /> </param>
-		internal StandardUserDataFieldDescriptor(FieldInfo fi, InteropAccessMode accessMode)
+		public StandardUserDataFieldDescriptor(FieldInfo fi, InteropAccessMode accessMode)
 		{
 			if (Script.GlobalOptions.Platform.IsRunningOnAOT())
 				accessMode = InteropAccessMode.Reflection;
@@ -50,6 +77,16 @@ namespace MoonSharp.Interpreter.Interop
 			this.Name = fi.Name;
 			this.IsStatic = this.FieldInfo.IsStatic;
 
+			if (this.FieldInfo.IsLiteral)
+			{
+				IsConst = true;
+				m_ConstValue = FieldInfo.GetValue(null);
+			}
+			else
+			{
+				IsReadonly = this.FieldInfo.IsInitOnly;
+			}
+
 			if (AccessMode == InteropAccessMode.Preoptimized)
 			{
 				this.OptimizeGetter();
@@ -65,6 +102,10 @@ namespace MoonSharp.Interpreter.Interop
 		/// <returns></returns>
 		public DynValue GetValue(Script script, object obj)
 		{
+			// optimization+workaround of Unity bug.. 
+			if (IsConst)
+				return ClrToScriptConversions.ObjectToDynValue(script, m_ConstValue);
+
 			if (AccessMode == InteropAccessMode.LazyOptimized && m_OptimizedGetter == null)
 				OptimizeGetter();
 
@@ -80,6 +121,9 @@ namespace MoonSharp.Interpreter.Interop
 
 		internal void OptimizeGetter()
 		{
+			if (this.IsConst)
+				return;
+
 			using (PerformanceStatistics.StartGlobalStopwatch(PerformanceCounter.AdaptersCompilation))
 			{
 				if (IsStatic)
@@ -110,6 +154,9 @@ namespace MoonSharp.Interpreter.Interop
 		/// <param name="v">The value to set.</param>
 		public void SetValue(Script script, object obj, DynValue v)
 		{
+			if (IsReadonly || IsConst)
+				throw new ScriptRuntimeException("userdata field '{0}.{1}' cannot be written to.", this.FieldInfo.DeclaringType.Name, this.Name);
+
 			object value = ScriptToClrConversions.DynValueToObjectOfType(v, this.FieldInfo.FieldType, null, false);
 
 			try
@@ -129,6 +176,12 @@ namespace MoonSharp.Interpreter.Interop
 				// optimized setters fall here
 				throw ScriptRuntimeException.UserDataArgumentTypeMismatch(v.Type, FieldInfo.FieldType);
 			}
+#if !PCL
+			catch (FieldAccessException ex)
+			{
+				throw new ScriptRuntimeException(ex);
+			}
+#endif
 		}
 
 		/// <summary>
@@ -141,5 +194,6 @@ namespace MoonSharp.Interpreter.Interop
 		{
 			return DynValue.NewCallback((p1, p2) => GetValue(script, obj));
 		}
+
 	}
 }

+ 22 - 2
src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataMethodDescriptor.cs

@@ -57,6 +57,25 @@ namespace MoonSharp.Interpreter.Interop
 		private bool m_IsAction = false;
 
 
+		/// <summary>
+		/// Tries to create a new StandardUserDataMethodDescriptor, returning <c>null</c> in case the method is not 
+		/// visible to script code.
+		/// </summary>
+		/// <param name="methodBase">The MethodBase.</param>
+		/// <param name="accessMode">The <see cref="InteropAccessMode" /></param>
+		/// <returns>A new StandardUserDataMethodDescriptor or null.</returns>
+		public static StandardUserDataMethodDescriptor TryCreateIfVisible(MethodBase methodBase, InteropAccessMode accessMode)
+		{
+			if (!CheckMethodIsCompatible(methodBase, false))
+				return null;
+
+			if (StandardUserDataDescriptor.GetVisibilityFromAttributes(methodBase) ?? methodBase.IsPublic)
+				return new StandardUserDataMethodDescriptor(methodBase, accessMode);
+
+			return null;
+		}
+
+
 		/// <summary>
 		/// Initializes a new instance of the <see cref="StandardUserDataMethodDescriptor"/> class.
 		/// </summary>
@@ -115,9 +134,10 @@ namespace MoonSharp.Interpreter.Interop
 		/// <param name="throwException">if set to <c>true</c> an exception with the proper error message is thrown if not compatible.</param>
 		/// <returns></returns>
 		/// <exception cref="System.ArgumentException">
-		/// Method cannot contain unresolved generic parameters
+		/// Thrown if throwException is <c>true</c> and one of this applies:
+		/// The method contains unresolved generic parameters, or has an unresolved generic return type
 		/// or
-		/// Method cannot contain by-ref parameters
+		/// The method contains pointer parameters, or has a pointer return type
 		/// </exception>
 		public static bool CheckMethodIsCompatible(MethodBase methodBase, bool throwException)
 		{

+ 7 - 2
src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataOverloadedMethodDescriptor.cs

@@ -1,4 +1,6 @@
-using System;
+#define DEBUG_OVERLOAD_RESOLVER
+
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -148,7 +150,9 @@ namespace MoonSharp.Interpreter.Interop
 				{
 					if (m_Cache[i] != null && CheckMatch(obj != null, args, m_Cache[i]))
 					{
+#if DEBUG_OVERLOAD_RESOLVER
 						System.Diagnostics.Debug.WriteLine(string.Format("[OVERLOAD] : CACHED! slot {0}, hits: {1}", i, m_CacheHits));
+#endif
 						return m_Cache[i].Method.Callback(script, obj, context, args);
 					}
 				}
@@ -314,8 +318,9 @@ namespace MoonSharp.Interpreter.Interop
 				}
 			}
 
+#if DEBUG_OVERLOAD_RESOLVER
 			System.Diagnostics.Debug.WriteLine(string.Format("[OVERLOAD] : Score {0} for method {1}", totalScore, method.SortDiscriminant));
-
+#endif
 			return totalScore;
 		}
 

+ 89 - 7
src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataPropertyDescriptor.cs

@@ -31,25 +31,101 @@ namespace MoonSharp.Interpreter.Interop
 		/// Gets the name of the property
 		/// </summary>
 		public string Name { get; private set; }
+		/// <summary>
+		/// Gets a value indicating whether this instance can be read from
+		/// </summary>
+		/// <value>
+		///   <c>true</c> if this instance can be read from; otherwise, <c>false</c>.
+		/// </value>
+		public bool CanRead { get { return m_Getter != null; } }
+		/// <summary>
+		/// Gets a value indicating whether this instance can be written to.
+		/// </summary>
+		/// <value>
+		///   <c>true</c> if this instance can be written to; otherwise, <c>false</c>.
+		/// </value>
+		public bool CanWrite { get { return m_Setter != null; } }
 
+
+		private MethodInfo m_Getter, m_Setter;
 		Func<object, object> m_OptimizedGetter = null;
 		Action<object, object> m_OptimizedSetter = null;
 
 
+		/// <summary>
+		/// Tries to create a new StandardUserDataPropertyDescriptor, returning <c>null</c> in case the property is not 
+		/// visible to script code.
+		/// </summary>
+		/// <param name="pi">The PropertyInfo.</param>
+		/// <param name="accessMode">The <see cref="InteropAccessMode" /></param>
+		/// <returns>A new StandardUserDataPropertyDescriptor or null.</returns>
+		public static StandardUserDataPropertyDescriptor TryCreateIfVisible(PropertyInfo pi, InteropAccessMode accessMode)
+		{
+			MethodInfo getter = pi.GetGetMethod(true);
+			MethodInfo setter = pi.GetSetMethod(true);
+
+			bool? pvisible = StandardUserDataDescriptor.GetVisibilityFromAttributes(pi);
+			bool? gvisible = StandardUserDataDescriptor.GetVisibilityFromAttributes(getter);
+			bool? svisible = StandardUserDataDescriptor.GetVisibilityFromAttributes(setter);
+
+			if (pvisible.HasValue)
+			{
+				return StandardUserDataPropertyDescriptor.TryCreate(pi, accessMode,
+					(gvisible ?? pvisible.Value) ? getter : null,
+					(svisible ?? pvisible.Value) ? setter : null);
+			}
+			else 
+			{
+				return StandardUserDataPropertyDescriptor.TryCreate(pi, accessMode,
+					(gvisible ?? getter.IsPublic) ? getter : null,
+					(svisible ?? setter.IsPublic) ? setter : null);
+			}
+		}
+
+		private static StandardUserDataPropertyDescriptor TryCreate(PropertyInfo pi, InteropAccessMode accessMode, MethodInfo getter, MethodInfo setter)
+		{
+			if (getter == null && setter == null)
+				return null;
+			else
+				return new StandardUserDataPropertyDescriptor(pi, accessMode, getter, setter);
+		}
+
+
 		/// <summary>
 		/// Initializes a new instance of the <see cref="StandardUserDataPropertyDescriptor"/> class.
+		/// NOTE: This constructor gives get/set visibility based exclusively on the CLR visibility of the 
+		/// getter and setter methods.
+		/// </summary>
+		/// <param name="pi">The pi.</param>
+		/// <param name="accessMode">The access mode.</param>
+		public StandardUserDataPropertyDescriptor(PropertyInfo pi, InteropAccessMode accessMode)
+			: this(pi, accessMode, pi.GetGetMethod(), pi.GetSetMethod())
+		{
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="StandardUserDataPropertyDescriptor" /> class.
 		/// </summary>
 		/// <param name="pi">The PropertyInfo.</param>
-		/// <param name="accessMode">The <see cref="InteropAccessMode" /> </param>
-		internal StandardUserDataPropertyDescriptor(PropertyInfo pi, InteropAccessMode accessMode)
+		/// <param name="accessMode">The <see cref="InteropAccessMode" /></param>
+		/// <param name="getter">The getter method. Use null to make the property writeonly.</param>
+		/// <param name="setter">The setter method. Use null to make the property readonly.</param>
+		public StandardUserDataPropertyDescriptor(PropertyInfo pi, InteropAccessMode accessMode, MethodInfo getter, MethodInfo setter)
 		{
+			if (getter == null && setter == null)
+				throw new ArgumentNullException("getter and setter cannot both be null");
+
 			if (Script.GlobalOptions.Platform.IsRunningOnAOT())
 				accessMode = InteropAccessMode.Reflection;
 
 			this.PropertyInfo = pi;
 			this.AccessMode = accessMode;
 			this.Name = pi.Name;
-			this.IsStatic = (this.PropertyInfo.GetGetMethod() ?? this.PropertyInfo.GetSetMethod()).IsStatic;
+
+			m_Getter = getter;
+			m_Setter = setter;
+
+			this.IsStatic = (m_Getter ?? m_Setter).IsStatic;
 
 			if (AccessMode == InteropAccessMode.Preoptimized)
 			{
@@ -67,6 +143,9 @@ namespace MoonSharp.Interpreter.Interop
 		/// <returns></returns>
 		public DynValue GetValue(Script script, object obj)
 		{
+			if (m_Getter == null)
+				throw new ScriptRuntimeException("userdata property '{0}.{1}' cannot be read from.", this.PropertyInfo.DeclaringType.Name, this.Name);
+
 			if (AccessMode == InteropAccessMode.LazyOptimized && m_OptimizedGetter == null)
 				OptimizeGetter();
 
@@ -75,7 +154,7 @@ namespace MoonSharp.Interpreter.Interop
 			if (m_OptimizedGetter != null)
 				result = m_OptimizedGetter(obj);
 			else
-				result = PropertyInfo.GetGetMethod().Invoke(IsStatic ? null : obj, null); // convoluted workaround for --full-aot Mono execution
+				result = m_Getter.Invoke(IsStatic ? null : obj, null); // convoluted workaround for --full-aot Mono execution
 
 			return ClrToScriptConversions.ObjectToDynValue(script, result);
 		}
@@ -84,7 +163,7 @@ namespace MoonSharp.Interpreter.Interop
 		{
 			using (PerformanceStatistics.StartGlobalStopwatch(PerformanceCounter.AdaptersCompilation))
 			{
-				if (PropertyInfo.CanRead)
+				if (m_Getter != null)
 				{
 					if (IsStatic)
 					{
@@ -111,9 +190,9 @@ namespace MoonSharp.Interpreter.Interop
 		{
 			using (PerformanceStatistics.StartGlobalStopwatch(PerformanceCounter.AdaptersCompilation))
 			{
-				if (PropertyInfo.CanWrite)
+				if (m_Setter != null)
 				{
-					MethodInfo setterMethod = PropertyInfo.GetSetMethod();
+					MethodInfo setterMethod = PropertyInfo.GetSetMethod(true);
 
 					if (IsStatic)
 					{
@@ -146,6 +225,9 @@ namespace MoonSharp.Interpreter.Interop
 		/// <param name="v">The value to set.</param>
 		public void SetValue(Script script, object obj, DynValue v)
 		{
+			if (m_Setter == null)
+				throw new ScriptRuntimeException("userdata property '{0}.{1}' cannot be written to.", this.PropertyInfo.DeclaringType.Name, this.Name);
+
 			object value = ScriptToClrConversions.DynValueToObjectOfType(v, this.PropertyInfo.PropertyType, null, false);
 
 			try

+ 2 - 0
src/MoonSharp.Interpreter/MoonSharp.Interpreter.net35-client.csproj

@@ -136,6 +136,7 @@
     <Compile Include="CoreLib\OsTimeModule.cs" />
     <Compile Include="CoreLib\StringLib\KopiLua_StrLib.cs" />
     <Compile Include="DataStructs\MultiDictionary.cs" />
+    <Compile Include="DataStructs\ReferenceEqualityComparer.cs" />
     <Compile Include="Interop\Attributes\MoonSharpUserDataMetamethodAttribute.cs" />
     <Compile Include="Interop\Converters\ClrToScriptConversions.cs" />
     <Compile Include="Interop\Converters\NumericConversions.cs" />
@@ -143,6 +144,7 @@
     <Compile Include="Interop\Converters\StringConversions.cs" />
     <Compile Include="Interop\Converters\TableConversions.cs" />
     <Compile Include="Interop\CustomConvertersCollection.cs" />
+    <Compile Include="Interop\StandardDescriptors\StandardUserDataEventDescriptor.cs" />
     <Compile Include="Interop\StandardDescriptors\StandardUserDataOverloadedMethodDescriptor.cs" />
     <Compile Include="Interop\StandardDescriptors\StandardUserDataFieldDescriptor.cs" />
     <Compile Include="IO\BinDumpBinaryReader.cs" />

+ 10 - 3
src/MoonSharp.Interpreter/REPL/ReplInterpreterScriptLoader.cs

@@ -12,9 +12,10 @@ namespace MoonSharp.Interpreter.REPL
 	/// AND starts with module paths taken from environment variables (again, not going through the platform object).
 	/// 
 	/// The paths are preconstructed using :
-	///		* The MOONSHARP_PATH variable if it exists
-	///		* The LUA_PATH otherwise
-	///		* The '?;?.lua" path otherwise again
+	///		* The MOONSHARP_PATH environment variable if it exists
+	///		* The LUA_PATH_5_2 environment variable if MOONSHARP_PATH does not exists
+	///		* The LUA_PATH environment variable if LUA_PATH_5_2 and MOONSHARP_PATH do not exists
+	///		* The "?;?.lua" path if all the above fail
 	///		
 	/// Also, everytime a module is require(d), the "LUA_PATH" global variable is checked. If it exists, those paths
 	/// will be used to load the module instead of the global ones.
@@ -29,6 +30,12 @@ namespace MoonSharp.Interpreter.REPL
 			string env = Environment.GetEnvironmentVariable("MOONSHARP_PATH");
 			if (!string.IsNullOrEmpty(env)) ModulePaths = UnpackStringPaths(env);
 
+			if (ModulePaths == null)
+			{
+				env = Environment.GetEnvironmentVariable("LUA_PATH_5_2");
+				if (!string.IsNullOrEmpty(env)) ModulePaths = UnpackStringPaths(env);
+			}
+
 			if (ModulePaths == null)
 			{
 				env = Environment.GetEnvironmentVariable("LUA_PATH");

+ 9 - 0
src/MoonSharp.Interpreter/_Projects/MoonSharp.Interpreter.net40-client/MoonSharp.Interpreter.net40-client.csproj

@@ -62,6 +62,9 @@
     <Compile Include="..\..\AsyncExtensions.cs">
       <Link>AsyncExtensions.cs</Link>
     </Compile>
+    <Compile Include="..\..\CodeAnalysis\AstNode.cs">
+      <Link>AstNode.cs</Link>
+    </Compile>
     <Compile Include="..\..\CoreLib\CoroutineModule.cs">
       <Link>CoroutineModule.cs</Link>
     </Compile>
@@ -119,6 +122,9 @@
     <Compile Include="..\..\DataStructs\MultiDictionary.cs">
       <Link>MultiDictionary.cs</Link>
     </Compile>
+    <Compile Include="..\..\DataStructs\ReferenceEqualityComparer.cs">
+      <Link>ReferenceEqualityComparer.cs</Link>
+    </Compile>
     <Compile Include="..\..\Interop\Attributes\MoonSharpUserDataMetamethodAttribute.cs">
       <Link>MoonSharpUserDataMetamethodAttribute.cs</Link>
     </Compile>
@@ -140,6 +146,9 @@
     <Compile Include="..\..\Interop\CustomConvertersCollection.cs">
       <Link>CustomConvertersCollection.cs</Link>
     </Compile>
+    <Compile Include="..\..\Interop\StandardDescriptors\StandardUserDataEventDescriptor.cs">
+      <Link>StandardUserDataEventDescriptor.cs</Link>
+    </Compile>
     <Compile Include="..\..\Interop\StandardDescriptors\StandardUserDataOverloadedMethodDescriptor.cs">
       <Link>StandardUserDataOverloadedMethodDescriptor.cs</Link>
     </Compile>

+ 9 - 0
src/MoonSharp.Interpreter/_Projects/MoonSharp.Interpreter.portable40/MoonSharp.Interpreter.portable40.csproj

@@ -59,6 +59,9 @@
     <Compile Include="..\..\AsyncExtensions.cs">
       <Link>AsyncExtensions.cs</Link>
     </Compile>
+    <Compile Include="..\..\CodeAnalysis\AstNode.cs">
+      <Link>AstNode.cs</Link>
+    </Compile>
     <Compile Include="..\..\CoreLib\CoroutineModule.cs">
       <Link>CoroutineModule.cs</Link>
     </Compile>
@@ -116,6 +119,9 @@
     <Compile Include="..\..\DataStructs\MultiDictionary.cs">
       <Link>MultiDictionary.cs</Link>
     </Compile>
+    <Compile Include="..\..\DataStructs\ReferenceEqualityComparer.cs">
+      <Link>ReferenceEqualityComparer.cs</Link>
+    </Compile>
     <Compile Include="..\..\Interop\Attributes\MoonSharpUserDataMetamethodAttribute.cs">
       <Link>MoonSharpUserDataMetamethodAttribute.cs</Link>
     </Compile>
@@ -137,6 +143,9 @@
     <Compile Include="..\..\Interop\CustomConvertersCollection.cs">
       <Link>CustomConvertersCollection.cs</Link>
     </Compile>
+    <Compile Include="..\..\Interop\StandardDescriptors\StandardUserDataEventDescriptor.cs">
+      <Link>StandardUserDataEventDescriptor.cs</Link>
+    </Compile>
     <Compile Include="..\..\Interop\StandardDescriptors\StandardUserDataOverloadedMethodDescriptor.cs">
       <Link>StandardUserDataOverloadedMethodDescriptor.cs</Link>
     </Compile>

+ 94 - 0
src/Tutorial/Tutorials/Chapters/Chapter6.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using MoonSharp.Interpreter;
+using MoonSharp.Interpreter.Interop;
 
 namespace Tutorials.Chapters
 {
@@ -15,6 +16,14 @@ namespace Tutorials.Chapters
 		[MoonSharpUserData]
 		class MyClass
 		{
+			public event EventHandler SomethingHappened;
+
+			public void RaiseTheEvent()
+			{
+				if (SomethingHappened != null)
+					SomethingHappened(this, EventArgs.Empty);
+			}
+
 			public double calcHypotenuse(double a, double b)
 			{
 				return Math.Sqrt(a * a + b * b);
@@ -378,5 +387,90 @@ namespace Tutorials.Chapters
 		}
 
 
+		[Tutorial]
+		static void Events()
+		{
+			string scriptCode = @"    
+				function handler(o, a)
+					print('handled!', o, a);
+				end
+
+				myobj.somethingHappened.add(handler);
+				myobj.raiseTheEvent();
+				myobj.somethingHappened.remove(handler);
+				myobj.raiseTheEvent();
+			";
+
+			UserData.RegisterType<EventArgs>();
+			UserData.RegisterType<MyClass>();
+			Script script = new Script();
+			script.Globals["myobj"] = new MyClass();
+			script.DoString(scriptCode);
+		}
+
+
+
+
+
+
+#pragma warning disable 414 
+		public class SampleClass
+		{
+			// Not visible - it's private
+			private void Method1() { }
+			// Visible - it's public
+			public void Method2() { }
+			// Visible - it's private but forced visible by attribute
+			[MoonSharpVisible(true)]
+			private void Method3() { }
+			// Not visible - it's public but forced hidden by attribute
+			[MoonSharpVisible(false)]
+			public void Method4() { }
+
+			// Not visible - it's private
+			private int Field1 = 0;
+			// Visible - it's public
+			public int Field2 = 0;
+			// Visible - it's private but forced visible by attribute
+			[MoonSharpVisible(true)]
+			private int Field3 = 0;
+			// Not visible - it's public but forced hidden by attribute
+			[MoonSharpVisible(false)]
+			public int Field4 = 0;
+
+			// Not visible at all - it's private
+			private int Property1 { get; set; }
+			// Read/write - it's public
+			public int Property2 { get; set; }
+			// Readonly - it's public, but the setter is private
+			public int Property3 { get; private set; }
+			// Write only! - the MoonSharpVisible makes the getter hidden and the setter visible!
+			public int Property4 { [MoonSharpVisible(false)] get; [MoonSharpVisible(true)] private set; }
+			// Write only! - the MoonSharpVisible makes the whole property hidden but another attribute resets the setter as visible!
+			[MoonSharpVisible(false)]
+			public int Property5 { get; [MoonSharpVisible(true)] private set; }
+			// Not visible at all - the MoonSharpVisible hides everything
+			[MoonSharpVisible(false)]
+			public int Property6 { get; set; }
+
+			// Not visible - it's private
+			private event EventHandler Event1;
+			// Visible - it's public
+			public event EventHandler Event2;
+			// Visible - it's private but forced visible by attribute
+			[MoonSharpVisible(true)]
+			private event EventHandler Event3;
+			// Not visible - it's public but forced hidden by attribute
+			[MoonSharpVisible(false)]
+			public event EventHandler Event4;
+			// Not visible - visibility modifiers over add and remove are not currently supported!
+			[MoonSharpVisible(false)]
+			public event EventHandler Event5 { [MoonSharpVisible(true)] add { } [MoonSharpVisible(true)] remove { } }
+
+			
+
+		}
+#pragma warning restore 414
+
 	}
 }