diff --git a/PorygonSharp/EvalValues/EvalValue.cs b/PorygonSharp/EvalValues/EvalValue.cs index 4737771..ad7bf3b 100644 --- a/PorygonSharp/EvalValues/EvalValue.cs +++ b/PorygonSharp/EvalValues/EvalValue.cs @@ -57,7 +57,7 @@ namespace PorygonSharp.EvalValues return str; } - public object EvaluateGenericObject() + private object EvaluateGenericObject() { var ptr = EvaluateUserDataObj(_handle); return GCHandle.FromIntPtr(ptr).Target; diff --git a/PorygonSharp/Script.cs b/PorygonSharp/Script.cs index 74fe14e..9614175 100644 --- a/PorygonSharp/Script.cs +++ b/PorygonSharp/Script.cs @@ -131,9 +131,11 @@ namespace PorygonSharp public string GetBoundTreeString() { var length = GetTreeStringLength(_internalScriptHandle); - var sb = new StringBuilder(length - 4); + var sb = new StringBuilder(length); GetTreeString(_internalScriptHandle, sb); - return sb.ToString(); + var s = sb.ToString(); + s = s.Substring(0, length); + return s; } [DllImport("PorygonLang", EntryPoint = "CreateScript", CallingConvention = CallingConvention.Cdecl)] diff --git a/PorygonSharp/UserData/BinaryOperationKind.cs b/PorygonSharp/UserData/BinaryOperationKind.cs new file mode 100644 index 0000000..4941784 --- /dev/null +++ b/PorygonSharp/UserData/BinaryOperationKind.cs @@ -0,0 +1,23 @@ +namespace PorygonSharp.UserData +{ + internal enum BinaryOperationKind : byte + { + // Math + Addition, + Subtraction, + Multiplication, + Division, + + // Equality + Equality, + Inequality, + Less, + LessOrEquals, + Greater, + GreaterOrEquals, + + // Logical + LogicalAnd, + LogicalOr, + } +} \ No newline at end of file diff --git a/PorygonSharp/UserData/UserData.cs b/PorygonSharp/UserData/UserData.cs index 10f6b0c..2b8242b 100644 --- a/PorygonSharp/UserData/UserData.cs +++ b/PorygonSharp/UserData/UserData.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using PorygonSharp.EvalValues; using PorygonSharp.Utilities; namespace PorygonSharp.UserData @@ -43,6 +44,7 @@ namespace PorygonSharp.UserData SetIsCastableFunc(id, Marshal.GetFunctionPointerForDelegate(_isCastableFunc)); SetCastFunc(id, Marshal.GetFunctionPointerForDelegate(_castFunc)); + } internal void RegisterFields() @@ -79,6 +81,8 @@ namespace PorygonSharp.UserData { RegisterFunction(method); } + + RegisterOperations(); } private void RegisterField(FieldInfo field) @@ -150,9 +154,9 @@ namespace PorygonSharp.UserData var obj = GCHandle.FromIntPtr(objPtr).Target; var objType = obj.GetType(); if (expectedType.IsAssignableFrom(objType)) - return objPtr; + return EvalValueCreator.CreateValue(obj).GetPointer(); if (objType.IsAssignableFrom(expectedType)) - return objPtr; + return EvalValueCreator.CreateValue(obj).GetPointer(); if (_implicitCasts.TryGetValue(expectedType, out var func)) { var castVal = func.Invoke(null, new[] {obj}); @@ -168,6 +172,54 @@ namespace PorygonSharp.UserData throw new Exception("Invalid cast kind"); } + private void RegisterOperations() + { + var methods = Type.GetMethods(BindingFlags.Public | BindingFlags.Static); + foreach (var method in methods) + { + switch (method.Name) + { + case "op_Addition": + RegisterOperation(BinaryOperationKind.Addition, method); + break; + case "op_Subtraction": + RegisterOperation(BinaryOperationKind.Subtraction, method); + break; + case "op_Multiply": + RegisterOperation(BinaryOperationKind.Multiplication, method); + break; + case "op_Division": + RegisterOperation(BinaryOperationKind.Division, method); + break; + case "op_Equality": + RegisterOperation(BinaryOperationKind.Equality, method); + break; + case "op_Inequality": + RegisterOperation(BinaryOperationKind.Inequality, method); + break; + case "op_LessThan": + RegisterOperation(BinaryOperationKind.Less, method); + break; + case "op_GreaterThan": + RegisterOperation(BinaryOperationKind.Greater, method); + break; + case "op_LessThanOrEqual": + RegisterOperation(BinaryOperationKind.LessOrEquals, method); + break; + case "op_GreaterThanOrEqual": + RegisterOperation(BinaryOperationKind.GreaterOrEquals, method); + break; + default: continue; + } + } + } + + private void RegisterOperation(BinaryOperationKind kind, MethodInfo info) + { + var operation = UserDataBinaryOperation.Create(info); + AddUserdataBinaryOperation(Id, (byte)kind, operation.Handle); + } + [DllImport("PorygonLang", EntryPoint = "RegisterUserDataField", CallingConvention = CallingConvention.Cdecl)] internal static extern void RegisterUserDataField(uint hashId, uint fieldId, IntPtr field); @@ -179,6 +231,10 @@ namespace PorygonSharp.UserData [DllImport("PorygonLang", EntryPoint = "SetCastFunc", CallingConvention = CallingConvention.Cdecl)] private static extern void SetCastFunc(uint id, IntPtr castFunc); + + [DllImport("PorygonLang", EntryPoint = "AddUserdataBinaryOperation", CallingConvention = CallingConvention.Cdecl)] + private static extern void AddUserdataBinaryOperation(uint id, byte kind, IntPtr operation); + } } \ No newline at end of file diff --git a/PorygonSharp/UserData/UserDataBinaryOperation.cs b/PorygonSharp/UserData/UserDataBinaryOperation.cs new file mode 100644 index 0000000..e3fd74d --- /dev/null +++ b/PorygonSharp/UserData/UserDataBinaryOperation.cs @@ -0,0 +1,60 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using PorygonSharp.EvalValues; + +namespace PorygonSharp.UserData +{ + public class UserDataBinaryOperation + { + private delegate IntPtr BinaryFunctionDelegate(IntPtr objectPtr, IntPtr variable); + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly BinaryFunctionDelegate _func; + private readonly MethodInfo _info; + private readonly Type _parameterType; + internal IntPtr Handle { get; private set; } + private UserDataBinaryOperation(MethodInfo info) + { + _info = info; + _func = Invoke; + _parameterType = info.GetParameters()[1].ParameterType; + } + + public static UserDataBinaryOperation Create(MethodInfo info) + { + var op = new UserDataBinaryOperation(info); + var funcPtr = Marshal.GetFunctionPointerForDelegate(op._func); + var secParType = ScriptType.ScriptTypeHandler.GetScriptType(op._parameterType); + var returnType = ScriptType.ScriptTypeHandler.GetScriptType(op._info.ReturnType); + if (!secParType.HasValue || !returnType.HasValue) + { + return null; + } + op.Handle = CreateUserdataBinaryOperation(funcPtr, secParType.Value, returnType.Value); + return op; + } + + private IntPtr Invoke(IntPtr objectPtr, IntPtr variable) + { + var a = GCHandle.FromIntPtr(objectPtr).Target; + var b = new EvalValue(variable); + var bVal = b.GetObjectValue(); + if (_parameterType.IsEnum) + { + var baseType = _parameterType.GetEnumUnderlyingType(); + bVal = Convert.ChangeType(bVal, baseType); + } + else if (bVal is IConvertible) + { + var convertedType = Convert.ChangeType(bVal, _parameterType); + bVal = convertedType; + } + var o = _info.Invoke(null, new[] {a, bVal}); + return EvalValueCreator.CreateValue(o).GetPointer(); + } + + + [DllImport("PorygonLang", EntryPoint = "CreateUserdataBinaryOperation", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr CreateUserdataBinaryOperation(IntPtr func, IntPtr secondParameterType, IntPtr returnType); + } +} \ No newline at end of file diff --git a/PorygonSharp/UserData/UserDataField.cs b/PorygonSharp/UserData/UserDataField.cs index c6ab92d..ca269f9 100644 --- a/PorygonSharp/UserData/UserDataField.cs +++ b/PorygonSharp/UserData/UserDataField.cs @@ -72,7 +72,12 @@ namespace PorygonSharp.UserData var eval = new EvalValue(parameters[i]); var val = eval.GetObjectValue(); var desiredType = parameterTypes[i]; - if (val is IConvertible) + if (desiredType.IsEnum) + { + var baseType = desiredType.GetEnumUnderlyingType(); + evaluatedParameters[i] = Convert.ChangeType(val, baseType); + } + else if (val is IConvertible) { var convertedType = Convert.ChangeType(val, desiredType); evaluatedParameters[i] = convertedType; diff --git a/PorygonSharp/libPorygonLang.so b/PorygonSharp/libPorygonLang.so index 0eb2be7..f0fcddf 100755 Binary files a/PorygonSharp/libPorygonLang.so and b/PorygonSharp/libPorygonLang.so differ diff --git a/PorygonSharpTests/UserDataTests.cs b/PorygonSharpTests/UserDataTests.cs index 19f64d1..bac7353 100644 --- a/PorygonSharpTests/UserDataTests.cs +++ b/PorygonSharpTests/UserDataTests.cs @@ -27,6 +27,18 @@ namespace PorygonSharpTests { return a + b; } + + public static UserDataTestObject operator + (UserDataTestObject a, UserDataTestObject b) + { + a.Foo += b.Foo; + return a; + } + + public static UserDataTestObject operator + (UserDataTestObject a, int b) + { + a.Foo += b; + return a; + } } [Test] @@ -311,5 +323,53 @@ end Assert.AreEqual(347807, result.EvaluateInteger()); } } + + [Test] + public void CanUseBinaryOperations() + { + UserDataHandler.RegisterType("testObject", typeof(UserDataTestObject)); + using (var script = new Script(@" +function test(testObject a, testObject b) + return a + b +end +")) + { + var diags = script.Diagnostics.GetDiagnostics(); + foreach (var diag in diags) + { + throw new Exception(script.Diagnostics.GetFullDiagnosticMessage(diag)); + } + + script.Evaluate(); + var a = new UserDataTestObject(); + var b = new UserDataTestObject(); + var result = script.CallFunction("test", a, b); + Assert.AreEqual(400, ((UserDataTestObject)result.GetObjectValue()).Foo); + } + } + + [Test] + public void CanUseBinaryOperationsWithScriptTypes() + { + UserDataHandler.RegisterType("testObject", typeof(UserDataTestObject)); + using (var script = new Script(@" +function test(testObject a) + return a + 500 +end +")) + { + var diags = script.Diagnostics.GetDiagnostics(); + foreach (var diag in diags) + { + throw new Exception(script.Diagnostics.GetFullDiagnosticMessage(diag)); + } + + script.Evaluate(); + var a = new UserDataTestObject(); + var result = script.CallFunction("test", a); + Assert.AreEqual(700, ((UserDataTestObject)result.GetObjectValue()).Foo); + } + } + } } \ No newline at end of file