Skip to content

Commit

Permalink
add Hook.MarshallDelegate
Browse files Browse the repository at this point in the history
  • Loading branch information
G3Kappa committed May 21, 2024
1 parent d366979 commit 089c64b
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 4 deletions.
42 changes: 39 additions & 3 deletions Ergo/Entry.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
using Ergo.Facade;
using Ergo.Interpreter;
using Ergo.Interpreter.Libraries;

// The "Standard" Ergo Facade is the recommended pre-configured default environment.
// You can extend it, modify it, or start from an empty facade.
var facade = ErgoFacade.Standard;
var shell = facade.BuildShell();
await foreach (var _ in shell.Repl())

var interpreter = facade.BuildInterpreter(InterpreterFlags.Default);
var interpreterScope = interpreter.CreateScope();

var kb = interpreterScope.BuildKnowledgeBase(CompilerFlags.Default, beforeCompile: kb =>
{
if (interpreterScope.Parse<Predicate>("message_sent(M) :- write('User: ', M), nl.")
.TryGetValue(out var pred))
kb.AssertA(pred);
});
var vm = facade.BuildVM(kb);

var eventTest = new EventTest();
using var hook = Hook.MarshallEvent(typeof(EventTest).GetEvent(nameof(EventTest.MessageSent)), eventTest, new Atom("message_sent"), WellKnown.Modules.User)(vm);
eventTest.SendMessage("hello,");
eventTest.SendMessage("world!");

var del = Hook.MarshallDelegate<Action<string>>((s) => { }, new Atom("message_sent"), WellKnown.Modules.User)(vm);
del("also hello,");
del("world!!!!!!");

// You can use the Ergo VM directly or through the Ergo Shell, which provides a
// bunch of useful commands to interact with knowledge bases and module files.
//var shell = facade.BuildShell();

//await foreach (var _ in shell.Repl())
//{
// ;
//}


public class EventTest
{
;
public event Action<string> MessageSent = _ => { };
public void SendMessage(string msg)
{
MessageSent?.Invoke(msg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void Call(ErgoVM vm, params object[] args)
/// <param name="target">The target of the event handler.</param>
/// <param name="functor">The functor to use for this hook.</param>
/// <param name="module">The module in which this hook is defined.</param>
/// <returns></returns>
/// <returns>A wrapper function that produces a disposable hook that binds to the given vm and unbinds once disposed.</returns>
public static Func<ErgoVM, DisposableHook> MarshallEvent(EventInfo evt, object target, Atom functor, Maybe<Atom> module = default) => vm =>
{
var handlerType = evt.EventHandlerType;
Expand Down Expand Up @@ -94,6 +94,44 @@ public void Call(ErgoVM vm, params object[] args)
return hook;
};

/// <summary>
/// Creates a patched delegate that calls a hook automatically whenever it is invoked.
/// </summary>
/// <param name="del">The delegate to marshall. It can be any C# delegate as long as its parameters can be marshalled and as long as it doesn't return anything.</param>
/// <param name="functor">The functor to use for this hook.</param>
/// <param name="module">The module in which this hook is defined.</param>
/// <returns>A wrapper function that produces a delegate calls the specified hook when it is invoked.</returns>
public static Func<ErgoVM, T> MarshallDelegate<T>(T del, Atom functor, Maybe<Atom> module = default, bool insertAfterCall = true)
where T : Delegate
=> vm =>
{
var handlerType = del.GetType();
var invokeMethod = handlerType.GetMethod("Invoke");
var parms = invokeMethod.GetParameters()
.Select(p => Expression.Parameter(p.ParameterType, p.Name))
.ToArray();
var hook = new Hook(new(functor, parms.Length, module, default));
var hookInvokeMethod = typeof(DisposableHook).GetMethod(nameof(Hook.Call), BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
var hookInvokeCall = Expression.Call(
Expression.Constant(hook),
hookInvokeMethod,
Expression.Constant(vm),
Expression.NewArrayInit(
typeof(object),
parms.Select(p => Expression.Convert(p, typeof(object)))
)
);
var delegateInvokeCall = Expression.Invoke(Expression.Constant(del), parms);
var combinedBody = insertAfterCall
? Expression.Block(delegateInvokeCall, hookInvokeCall)
: Expression.Block(hookInvokeCall, delegateInvokeCall);
// Create the lambda expression
var lambda = Expression.Lambda(handlerType, combinedBody, parms);
Delegate combinedHandler = lambda.Compile();
return (T)combinedHandler;
};

/// <summary>
/// Creates a wrapper that compiles the hook just in time when it is first called.
/// </summary>
Expand Down

0 comments on commit 089c64b

Please sign in to comment.