From 089c64bc271310a54761ce9f8bec6d97cfb34187 Mon Sep 17 00:00:00 2001 From: G3Kappa Date: Tue, 21 May 2024 03:12:02 +0200 Subject: [PATCH] add Hook.MarshallDelegate --- Ergo/Entry.cs | 42 +++++++++++++++++-- .../Libraries/_Shared/{HookDef.cs => Hook.cs} | 40 +++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) rename Ergo/Interpreter/Libraries/_Shared/{HookDef.cs => Hook.cs} (71%) diff --git a/Ergo/Entry.cs b/Ergo/Entry.cs index 41b35b1b..274a9163 100644 --- a/Ergo/Entry.cs +++ b/Ergo/Entry.cs @@ -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("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>((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 MessageSent = _ => { }; + public void SendMessage(string msg) + { + MessageSent?.Invoke(msg); + } } \ No newline at end of file diff --git a/Ergo/Interpreter/Libraries/_Shared/HookDef.cs b/Ergo/Interpreter/Libraries/_Shared/Hook.cs similarity index 71% rename from Ergo/Interpreter/Libraries/_Shared/HookDef.cs rename to Ergo/Interpreter/Libraries/_Shared/Hook.cs index 3dd21870..834913c8 100644 --- a/Ergo/Interpreter/Libraries/_Shared/HookDef.cs +++ b/Ergo/Interpreter/Libraries/_Shared/Hook.cs @@ -63,7 +63,7 @@ public void Call(ErgoVM vm, params object[] args) /// The target of the event handler. /// The functor to use for this hook. /// The module in which this hook is defined. - /// + /// A wrapper function that produces a disposable hook that binds to the given vm and unbinds once disposed. public static Func MarshallEvent(EventInfo evt, object target, Atom functor, Maybe module = default) => vm => { var handlerType = evt.EventHandlerType; @@ -94,6 +94,44 @@ public void Call(ErgoVM vm, params object[] args) return hook; }; + /// + /// Creates a patched delegate that calls a hook automatically whenever it is invoked. + /// + /// 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. + /// The functor to use for this hook. + /// The module in which this hook is defined. + /// A wrapper function that produces a delegate calls the specified hook when it is invoked. + public static Func MarshallDelegate(T del, Atom functor, Maybe 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; + }; + /// /// Creates a wrapper that compiles the hook just in time when it is first called. ///