Skip to content

Commit

Permalink
v2.4.0 - added support for delegate function bindings, see #15
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhail-barg committed Sep 26, 2023
1 parent 84f8008 commit 042266d
Show file tree
Hide file tree
Showing 8 changed files with 4,050 additions and 3,749 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Version>2.3.1</Version>
<Version>2.4.0</Version>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\sgKey.snk</AssemblyOriginatorKeyFile>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Version>2.3.0</Version>
<Version>2.4.0</Version>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\sgKey.snk</AssemblyOriginatorKeyFile>

Expand Down
7,652 changes: 3,932 additions & 3,720 deletions src/Jsonata.Net.Native.TestSuite/TestReport/Jsonata.Net.Native.TestSuite.xml

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions src/Jsonata.Net.Native.Tests/FuncBindingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using Jsonata.Net.Native;
using Jsonata.Net.Native.Json;
using NUnit.Framework;

namespace Jsonata.Net.Native.Tests
{
public sealed class FuncBindingTests
{
[Test]
public void ViaMethodInfo()
{
EvaluationEnvironment env = new EvaluationEnvironment();
env.BindFunction(typeof(FuncBindingTests).GetMethod(nameof(Mult3))!);
JsonataQuery query = new JsonataQuery("( $Mult3(1, 2, 3); )");
JToken result = query.Eval(JValue.CreateNull(), env);
Assert.AreEqual(JTokenType.Integer, result.Type);
Assert.AreEqual(6, (int)result);
}

[Test]
public void ViaFuncStatic()
{
EvaluationEnvironment env = new EvaluationEnvironment();
Func<int, int, int, int> func = Mult3;
env.BindFunction(nameof(Mult3), func);
JsonataQuery query = new JsonataQuery("( $Mult3(1, 2, 3); )");
JToken result = query.Eval(JValue.CreateNull(), env);
Assert.AreEqual(JTokenType.Integer, result.Type);
Assert.AreEqual(6, (int)result);
}

[Test]
public void ViaInplaceStatic()
{
EvaluationEnvironment env = new EvaluationEnvironment();
env.BindFunction(nameof(Mult3), Mult3);
JsonataQuery query = new JsonataQuery("( $Mult3(1, 2, 3); )");
JToken result = query.Eval(JValue.CreateNull(), env);
Assert.AreEqual(JTokenType.Integer, result.Type);
Assert.AreEqual(6, (int)result);
}

[Test]
public void ViaFuncLambda()
{
EvaluationEnvironment env = new EvaluationEnvironment();
Func<int, int, int, int> func = (int a, int b, int c) => a * b * c;
env.BindFunction(nameof(Mult3), func);
JsonataQuery query = new JsonataQuery("( $Mult3(1, 2, 3); )");
JToken result = query.Eval(JValue.CreateNull(), env);
Assert.AreEqual(JTokenType.Integer, result.Type);
Assert.AreEqual(6, (int)result);
}

[Test]
public void ViaInplaceLambda()
{
EvaluationEnvironment env = new EvaluationEnvironment();
env.BindFunction(nameof(Mult3), (int a, int b, int c) => a * b * c);
JsonataQuery query = new JsonataQuery("( $Mult3(1, 2, 3); )");
JToken result = query.Eval(JValue.CreateNull(), env);
Assert.AreEqual(JTokenType.Integer, result.Type);
Assert.AreEqual(6, (int)result);
}

public static int Mult3(int a, int b, int c)
{
return a * b * c;
}
}
}
1 change: 1 addition & 0 deletions src/Jsonata.Net.Native.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -402,5 +402,6 @@ public void Test_Issue14_8()
string value = (string)((Jsonata.Net.Native.Json.JObject)jToken).Properties["key"];
Assert.AreEqual("00000000000000000000000000000000", value);
}

}
}
63 changes: 37 additions & 26 deletions src/Jsonata.Net.Native/Eval/FunctionTokenCsharp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,43 @@ namespace Jsonata.Net.Native.Eval

internal sealed class FunctionTokenCsharp : FunctionToken
{
internal readonly MethodInfo methodInfo;
internal IReadOnlyList<ArgumentInfo> parameters;
internal readonly string functionName;
internal readonly bool hasContextParameter;
internal readonly bool hasEnvParameter;
private readonly object? m_target;
private readonly MethodInfo m_methodInfo;
private IReadOnlyList<ArgumentInfo> m_parameters;
private readonly string m_functionName;
private readonly bool m_hasContextParameter;
private readonly bool m_hasEnvParameter;

internal FunctionTokenCsharp(string funcName, MethodInfo methodInfo)
: base($"{methodInfo.DeclaringType?.Name}.{methodInfo.Name}", methodInfo.GetParameters().Length)
internal FunctionTokenCsharp(string funcName, MethodInfo methodInfo)
:this(funcName, methodInfo, null)
{
if (!methodInfo.IsStatic)
{
throw new ArgumentException("Only static methods are allowed to be bound as Jsonata functions");
{
throw new ArgumentException("Only static methods are allowed to be bound as Jsonata functions via MethodInfo");
}
}

internal FunctionTokenCsharp(string funcName, Delegate delegateFunc)
: this(funcName, delegateFunc.Method, delegateFunc.Target)
{
}

this.functionName = funcName;
this.methodInfo = methodInfo;
this.parameters = this.methodInfo.GetParameters()
private FunctionTokenCsharp(string funcName, MethodInfo methodInfo, object? target)
: base($"{methodInfo.DeclaringType?.Name}.{methodInfo.Name}", methodInfo.GetParameters().Length)
{
this.m_functionName = funcName;
this.m_methodInfo = methodInfo;
this.m_target = target;
this.m_parameters = this.m_methodInfo.GetParameters()
.Select(pi => new ArgumentInfo(funcName, pi))
.ToList();
this.hasContextParameter = this.parameters.Any(p => p.allowContextAsValue);
this.hasEnvParameter = this.parameters.Any(p => p.isEvaluationSupplement);
this.m_hasContextParameter = this.m_parameters.Any(p => p.allowContextAsValue);
this.m_hasEnvParameter = this.m_parameters.Any(p => p.isEvaluationSupplement);

this.RequiredArgsCount = this.parameters.Where(p => !p.isOptional && !p.isEvaluationSupplement).Count();
this.RequiredArgsCount = this.m_parameters.Where(p => !p.isOptional && !p.isEvaluationSupplement).Count();
}

internal sealed class ArgumentInfo
internal sealed class ArgumentInfo
{
internal readonly string name;
internal readonly Type parameterType;
Expand Down Expand Up @@ -94,7 +105,7 @@ internal override JToken Invoke(List<JToken> args, JToken? context, EvaluationEn
object? resultObj;
try
{
resultObj = this.methodInfo.Invoke(null, parameters);
resultObj = this.m_methodInfo.Invoke(this.m_target, parameters);
}
catch (TargetInvocationException ti)
{
Expand All @@ -104,7 +115,7 @@ internal override JToken Invoke(List<JToken> args, JToken? context, EvaluationEn
}
else
{
throw new Exception($"Error evaluating function '{this.functionName}': {(ti.InnerException?.Message ?? "?")}", ti);
throw new Exception($"Error evaluating function '{this.m_functionName}': {(ti.InnerException?.Message ?? "?")}", ti);
}
throw;
}
Expand All @@ -121,7 +132,7 @@ internal override JToken Invoke(List<JToken> args, JToken? context, EvaluationEn
catch (JsonataException)
{
//try binding with context if possible
if (context != null && this.hasContextParameter)
if (context != null && this.m_hasContextParameter)
{
return this.TryBindFunctionArguments(args, context, env, out returnUndefined);
}
Expand All @@ -136,11 +147,11 @@ internal override JToken Invoke(List<JToken> args, JToken? context, EvaluationEn
private object?[] TryBindFunctionArguments(List<JToken> args, JToken? context, EvaluationEnvironment env, out bool returnUndefined)
{
returnUndefined = false;
object?[] result = new object[this.parameters.Count];
object?[] result = new object[this.m_parameters.Count];
int sourceIndex = 0;
for (int targetIndex = 0; targetIndex < this.parameters.Count; ++targetIndex)
for (int targetIndex = 0; targetIndex < this.m_parameters.Count; ++targetIndex)
{
ArgumentInfo argumentInfo = this.parameters[targetIndex];
ArgumentInfo argumentInfo = this.m_parameters[targetIndex];
if (context != null && argumentInfo.allowContextAsValue)
{
//if we explicitly provide context, then hurry and use it!
Expand All @@ -163,7 +174,7 @@ internal override JToken Invoke(List<JToken> args, JToken? context, EvaluationEn
}
else
{
throw new JsonataException("T0410", $"Function '{functionName}' requires {this.parameters.Count + (this.hasEnvParameter? -1 : 0)} arguments. Passed {args.Count} arguments");
throw new JsonataException("T0410", $"Function '{m_functionName}' requires {this.m_parameters.Count + (this.m_hasEnvParameter? -1 : 0)} arguments. Passed {args.Count} arguments");
}
}
else if (argumentInfo.isVariableArgumentsArray)
Expand Down Expand Up @@ -191,7 +202,7 @@ internal override JToken Invoke(List<JToken> args, JToken? context, EvaluationEn

if (sourceIndex < args.Count)
{
throw new JsonataException("T0410", $"Function '{functionName}' requires {this.parameters.Count + (this.hasEnvParameter ? -1 : 0)} arguments. Passed {args.Count} arguments");
throw new JsonataException("T0410", $"Function '{m_functionName}' requires {this.m_parameters.Count + (this.m_hasEnvParameter ? -1 : 0)} arguments. Passed {args.Count} arguments");
};

return result;
Expand Down Expand Up @@ -294,7 +305,7 @@ internal override JToken Invoke(List<JToken> args, JToken? context, EvaluationEn
return (bool)argToken;
}
}
throw new JsonataException("T0410", $"Argument {parameterIndex + 1} ('{argumentInfo.name}') of function {this.functionName} should be {argumentInfo.parameterType.Name} but incompatible value of type {argToken.Type} was specified");
throw new JsonataException("T0410", $"Argument {parameterIndex + 1} ('{argumentInfo.name}') of function {this.m_functionName} should be {argumentInfo.parameterType.Name} but incompatible value of type {argToken.Type} was specified");
}

private JToken ConvertFunctionResult(object? resultObj)
Expand Down Expand Up @@ -343,7 +354,7 @@ private JToken ConvertFunctionResult(object? resultObj)

public override JToken DeepClone()
{
return new FunctionTokenCsharp(this.functionName, this.methodInfo);
return new FunctionTokenCsharp(this.m_functionName, this.m_methodInfo, this.m_target);
}
}
}
5 changes: 5 additions & 0 deletions src/Jsonata.Net.Native/EvaluationEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ public void BindFunction(string name, MethodInfo mi)
this.m_bindings.Add(name, new FunctionTokenCsharp(name, mi));
}

public void BindFunction(string name, Delegate funcDelegate)
{
this.m_bindings.Add(name, new FunctionTokenCsharp(name, funcDelegate));
}

internal JToken Lookup(string name)
{
if (this.m_bindings.TryGetValue(name, out JToken? result))
Expand Down
2 changes: 1 addition & 1 deletion src/Jsonata.Net.Native/Jsonata.Net.Native.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
-->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Version>2.3.0</Version>
<Version>2.4.0</Version>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\sgKey.snk</AssemblyOriginatorKeyFile>

Expand Down

0 comments on commit 042266d

Please sign in to comment.