Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Features:
* Runs on IL2CPP converted code
* No external dependencies, implemented in as few targets as possible
* Easy and performant interop with CLR objects, with runtime code generation where supported
* Interop with methods, extension methods, overloads, fields, properties and indexers supported
* Interop with methods, generic methods, extension methods, overloads, fields, properties and indexers supported
* Support for the complete Lua standard library with very few exceptions (mostly located on the 'debug' module) and a few extensions (in the string library, mostly)
* Async method support
* Supports dumping/loading bytecode for obfuscation and quicker parsing at runtime
Expand Down
75 changes: 75 additions & 0 deletions src/MoonSharp.Interpreter.Tests/EndToEnd/CoroutineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,56 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MoonSharp.Interpreter.Interop;
using NUnit.Framework;

namespace MoonSharp.Interpreter.Tests.EndToEnd
{
[TestFixture]
class CoroutineTests
{
private class YieldingIndexTarget
{
}

private class YieldingIndexDescriptor : IUserDataDescriptor
{
public string Name
{
get { return "YieldingIndexTarget"; }
}

public Type Type
{
get { return typeof(YieldingIndexTarget); }
}

public DynValue Index(Script script, object obj, DynValue index, bool isDirectIndexing)
{
return DynValue.NewYieldReq(new[] { DynValue.NewString("yielded:" + index.String) });
}

public bool SetIndex(Script script, object obj, DynValue index, DynValue value, bool isDirectIndexing)
{
return false;
}

public string AsString(object obj)
{
return null;
}

public DynValue MetaIndex(Script script, object obj, string metaname)
{
return null;
}

public bool IsTypeCompatible(Type type, object obj)
{
return type.IsInstanceOfType(obj);
}
}

[Test]
public void Coroutine_Basic()
{
Expand Down Expand Up @@ -202,6 +245,38 @@ public void Coroutine_Direct_Resume()
Assert.AreEqual("1234567", ret);
}

[Test]
public void Coroutine_UserDataIndexCanYield()
{
string code = @"
return function(target)
state = 'before'
local value = target.value
state = 'after'
return value
end
";

Script script = new Script();
DynValue function = script.DoString(code);
DynValue coroutine = script.CreateCoroutine(function);
DynValue target = UserData.Create(new YieldingIndexTarget(), new YieldingIndexDescriptor());

DynValue yielded = coroutine.Coroutine.Resume(target);

Assert.AreEqual(CoroutineState.Suspended, coroutine.Coroutine.State);
Assert.AreEqual(DataType.String, yielded.Type);
Assert.AreEqual("yielded:value", yielded.String);
Assert.AreEqual("before", script.Globals.Get("state").String);

DynValue returned = coroutine.Coroutine.Resume(DynValue.NewString("resumed"));

Assert.AreEqual(CoroutineState.Dead, coroutine.Coroutine.State);
Assert.AreEqual(DataType.String, returned.Type);
Assert.AreEqual("resumed", returned.String);
Assert.AreEqual("after", script.Globals.Get("state").String);
}


[Test]
public void Coroutine_Direct_AsEnumerable()
Expand Down
175 changes: 175 additions & 0 deletions src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataMethodsTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using MoonSharp.Interpreter.Compatibility;
using MoonSharp.Interpreter.Interop;
Expand Down Expand Up @@ -218,6 +219,31 @@ public static StringBuilder ConcatS(int p1, string p2, IComparable p3, bool p4,
return p7;
}

public static string GenericStaticDescribe<T>(T value)
{
return typeof(T).Name + ":" + value;
}

public static string GenericStaticDescribeTypes<T1, T2>()
{
return typeof(T1).Name + "+" + typeof(T2).Name;
}

public static string GenericStaticDescribeParams<T>(params T[] values)
{
return typeof(T).Name + ":" + string.Join(",", values.Select(v => v == null ? "<null>" : v.ToString()).ToArray());
}

public static string GenericOverloadStealing(string value)
{
return "nongeneric:" + value;
}

public static string GenericOverloadStealing<T>(T value)
{
return "generic:" + typeof(T).Name + ":" + value;
}

public string Format(string s, params object[] args)
{
return string.Format(s, args);
Expand All @@ -230,6 +256,16 @@ public StringBuilder ConcatI(Script s, int p1, string p2, IComparable p3, bool p
return ConcatS(p1, p2, p3, p4, p5, p6, p7, p8, this, p10);
}

public string GenericInstanceDescribe<T>(T value)
{
return ToString() + ":" + typeof(T).Name + ":" + value;
}

public string GenericInstanceTypeName<T>()
{
return ToString() + ":" + typeof(T).Name;
}

public override string ToString()
{
return "!SOMECLASS!";
Expand All @@ -250,6 +286,21 @@ public int CompareTo(object obj)
}
}

public class GenericMethodPayload
{
private readonly string m_Value;

public GenericMethodPayload(string value)
{
m_Value = value;
}

public override string ToString()
{
return m_Value;
}
}

public interface Interface1
{
string Test1();
Expand Down Expand Up @@ -661,6 +712,130 @@ public void Test_DelegateMethod(InteropAccessMode opt)
Assert.AreEqual("1%2", res.String);
}

[Test]
public void Interop_GenericMethods()
{
Script S = new Script();
SomeClass obj = new SomeClass();

UserData.UnregisterType<SomeClass>();
UserData.UnregisterType<GenericMethodPayload>();
UserData.UnregisterType<SomeOtherClass>();
UserData.UnregisterType<int>();
UserData.RegisterType<SomeClass>();
UserData.RegisterType<GenericMethodPayload>();
UserData.RegisterType<SomeOtherClass>();
UserData.RegisterType<int>();

S.Globals.Set("static", UserData.CreateStatic<SomeClass>());
S.Globals.Set("obj", UserData.Create(obj));
S.Globals["payloadType"] = typeof(GenericMethodPayload);
S.Globals["otherPayloadType"] = typeof(SomeOtherClass);
S.Globals["intType"] = typeof(int);
S.Globals.Set("payload", UserData.Create(new GenericMethodPayload("value")));

DynValue res = S.DoString(@"
a = static.GenericStaticDescribe(payloadType, payload);
b = obj:GenericInstanceDescribe(payloadType, payload);
c = static.GenericStaticDescribeTypes(payloadType, otherPayloadType);
return a .. '|' .. b .. '|' .. c;");

Assert.AreEqual(DataType.String, res.Type);
Assert.AreEqual("GenericMethodPayload:value|!SOMECLASS!:GenericMethodPayload:value|GenericMethodPayload+SomeOtherClass", res.String);

res = S.DoString("return obj.GenericInstanceTypeName(payloadType);");

Assert.AreEqual(DataType.String, res.Type);
Assert.AreEqual("!SOMECLASS!:GenericMethodPayload", res.String);

res = S.DoString("return static.GenericStaticDescribe(intType, 42);");

Assert.AreEqual(DataType.String, res.Type);
Assert.AreEqual("Int32:42", res.String);
}

[Test]
public void Interop_GenericMethodDescriptor_InstanceDotCall_UsesFirstArgumentAsType()
{
Script S = new Script();

UserData.UnregisterType<SomeClass>();
UserData.UnregisterType<GenericMethodPayload>();
UserData.RegisterType<SomeClass>();
UserData.RegisterType<GenericMethodPayload>();

MethodInfo mi = typeof(SomeClass).GetMethod("GenericInstanceTypeName");
GenericMethodMemberDescriptor descriptor = new GenericMethodMemberDescriptor(mi);
CallbackArguments args = new CallbackArguments(new[] { UserData.CreateStatic<GenericMethodPayload>() }, false);

DynValue res = descriptor.Execute(S, new SomeClass(), null, args);

Assert.AreEqual(DataType.String, res.Type);
Assert.AreEqual("!SOMECLASS!:GenericMethodPayload", res.String);
}

[Test]
public void Interop_GenericMethodDescriptor_UsesConstructedParameterTypes()
{
Script S = new Script();

UserData.UnregisterType<SomeClass>();
UserData.UnregisterType<int>();
UserData.RegisterType<SomeClass>();
UserData.RegisterType<int>();

MethodInfo mi = typeof(SomeClass).GetMethod("GenericStaticDescribe");
GenericMethodMemberDescriptor descriptor = new GenericMethodMemberDescriptor(mi);
CallbackArguments args = new CallbackArguments(new[] { UserData.CreateStatic<int>(), DynValue.NewNumber(42) }, false);

DynValue res = descriptor.Execute(S, null, null, args);

Assert.AreEqual(DataType.String, res.Type);
Assert.AreEqual("Int32:42", res.String);
}

[Test]
public void Interop_GenericMethodDescriptor_PacksConstructedParamsArguments()
{
Script S = new Script();

UserData.UnregisterType<SomeClass>();
UserData.UnregisterType<int>();
UserData.RegisterType<SomeClass>();
UserData.RegisterType<int>();

MethodInfo mi = typeof(SomeClass).GetMethod("GenericStaticDescribeParams");
GenericMethodMemberDescriptor descriptor = new GenericMethodMemberDescriptor(mi);
CallbackArguments args = new CallbackArguments(new[]
{
UserData.CreateStatic<int>(),
DynValue.NewNumber(1),
DynValue.NewNumber(2),
DynValue.NewNumber(3)
}, false);

DynValue res = descriptor.Execute(S, null, null, args);

Assert.AreEqual(DataType.String, res.Type);
Assert.AreEqual("Int32:1,2,3", res.String);
}

[Test]
public void Interop_GenericMethodOverload_DoesNotStealNonGenericCall()
{
Script S = new Script();

UserData.UnregisterType<SomeClass>();
UserData.RegisterType<SomeClass>();

S.Globals.Set("static", UserData.CreateStatic<SomeClass>());

DynValue res = S.DoString("return static.GenericOverloadStealing('value');");

Assert.AreEqual(DataType.String, res.Type);
Assert.AreEqual("nongeneric:value", res.String);
}

public void Test_ListMethod(InteropAccessMode opt)
{
UserData.UnregisterType<SomeClass>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ public static IEnumerable<int> Numbers
}
}

public class ConstructorCallClass
{
public ConstructorCallClass()
{
OptionalProp = 77;
}

public ConstructorCallClass(int intProp)
: this()
{
IntProp = intProp;
}

public int IntProp { get; set; }
public int OptionalProp { get; set; }
public string Name { get; set; }
}

public void Test_IntPropertyGetter(InteropAccessMode opt)
{
string script = @"
Expand Down Expand Up @@ -852,6 +870,45 @@ public void Interop_IntPropertySetterWithSimplifiedSyntax()
Assert.AreEqual(19, obj.IntProp);
}

[Test]
public void Interop_TypeCallInvokesConstructor()
{
Script S = new Script();

UserData.UnregisterType<ConstructorCallClass>();
UserData.RegisterType<ConstructorCallClass>();

S.Globals["mytype"] = typeof(ConstructorCallClass);

DynValue res = S.DoString(@"
local obj = mytype(12);
return obj.IntProp, obj.OptionalProp;");

Assert.AreEqual(DataType.Tuple, res.Type);
Assert.AreEqual(12, res.Tuple[0].Number);
Assert.AreEqual(77, res.Tuple[1].Number);
}

[Test]
public void Interop_TypeCallTableInitializerSetsProperties()
{
Script S = new Script();

UserData.UnregisterType<ConstructorCallClass>();
UserData.RegisterType<ConstructorCallClass>();

S.Globals["mytype"] = typeof(ConstructorCallClass);

DynValue res = S.DoString(@"
local obj = mytype{ IntProp = 12, OptionalProp = nil, Name = 'ok' };
return obj.IntProp, obj.OptionalProp, obj.Name;");

Assert.AreEqual(DataType.Tuple, res.Type);
Assert.AreEqual(12, res.Tuple[0].Number);
Assert.AreEqual(77, res.Tuple[1].Number);
Assert.AreEqual("ok", res.Tuple[2].String);
}

[Test]
public void Interop_OutOfRangeNumber()
{
Expand Down
Loading