mthom / scryer-prolog

A modern Prolog implementation written mostly in Rust.
BSD 3-Clause "New" or "Revised" License
1.93k stars 116 forks source link

Expose Rust function as a predicate when using Scryer as library #2373

Open tatut opened 3 months ago

tatut commented 3 months ago

There exists a FFI to load dynamic native code from the Prolog side, but I would like to expose Rust functions as predicates when creating a new prolog machine.

The use case is for my experimental prolog language handler PostgreSQL extension. I would like to expose some pgrx functions to be used in prolog (mainly the SPI query execution).

Also there seems to be a Value enum in the parsed_results module, can that be used the other way (from Rust->Prolog when doing a query, currently I'm just passing a query as string).

triska commented 3 months ago

This may be interesting: https://github.com/mthom/scryer-prolog/wiki/Flying-Roll-%231:-instructions.rs-and-the-Dispatch-Loop

Here is a recent example of adding the single predicate '$integer_in_radix'/3 that makes Rust functionality available: https://github.com/mthom/scryer-prolog/pull/2362/commits/8b9ab9c3ae4c48dfd155f480eaf106c1088262b5

bakaq commented 3 months ago

Implementing a new instruction is a change that needs to fork Scryer. I think what this issue is referring to is an API to expose user defined functions to the machine used as a library. This is a common use case for other embedded languages so that they could be used as plugin languages. Here is an example with the Lua bindings in Rust.

tatut commented 3 months ago

Yes, I wouldn't want to fork... Prolog extension is a little trickier than most languages that are just params in / return value out.

For my case, I think a simple customfn(In,Out) (where In is fully instantiated and Out is the return value prolog term) would suffice.

bakaq commented 3 months ago

While it's not ideal and really impractical, I think you could just use the FFI as a workaround if you really need to do this. You compile a .so that exposes the function that implements the predicate you want and then load it with the FFI library inside Scryer.

tatut commented 3 months ago

Perhaps... but the SPI functions are already loaded in the pgrx extension, I don't know if I can load another version of them as they are already provided by PostgreSQL to be used in extensions.

bakaq commented 3 months ago

Ok, if I'm understanding this correctly (I never used pgrx or PostgreSQL) you have to use the functions given by the library because they are dynamically generated from the connection with the database. In this case the .so wouldn't work because it would have a different instance of the library and no access to the connection. If this or something similar is the case, then here is the big brain 1000 IQ workaround I can think of:

Really really convoluted but should work unless there are restrictions on IPC for PostgreSQL extensions for some reason.

If having to carry around an .so is not feasible for some reason you could use a build script to compile it and a macro to embed it into your extension. Then when the extension is loaded you could just write the whole .so into a file in a temporary directory (like /tmp) so that the embedded Scryer can load it.

Having an interface for exposing Rust functions to embedded Scryer would make this trivial, lighter and much more elegant, but I don't think this is going to happen anytime soon.

bakaq commented 3 months ago

In fact, with this whole socket thing you could skip the .so and just use library(sockets) (or library(http/http_open) if you are feeling less low level).

tatut commented 3 months ago

Another idea that comes to mind is adding std::sync::mpsc channels to the machine and add primitives to rcv(Data) and send(Data) to communicate with the code that created the machine (using canonical Prolog term format).

This also requires forking, but feels like it would be simpler to add than support for arbitrary Rust functions.