1.4 Embedded Mode: Integrating the Machine Query Interface Into a New Programming Language
The most common way to use the Machine Query Interface is to find a
library that wraps and exposes it as a native part of another
programming language such as the Python swiplserver
library
(section 1.1). This section describes
how to build one if there isn't yet a library for your language. To do
this, you'll need to familiarize yourself with the MQI protocol as
described in the mqi_start/1
documentation. However, to give an idea of the scope of work required,
below is a typical interaction done (invisibly to the user) in the
implementation of any programming language library:
- Launch the SWI Prolog process using (along with any other options
the user requests):
swipl mqi --write_connection_values=true
. To work, theswipl
Prolog executable will need to be on the path or the path needs to be specified in the command. This launches SWI Prolog, starts the MQI, and writes the chosen port and password to STDOUT. This way of launching invokes the mqi_start/0 predicate that turns off theint
(i.e. Interrupt/SIGINT) signal to Prolog. This is because some languages (such as Python) use that signal during debugging and it would be otherwise passed to the client Prolog process and switch it into the debugger. See the mqi_start/0 predicate for more information on other command line options. - Read the SWI Prolog STDOUT to retrieve the TCP/IP port and password.
They are sent in that order, delimited by’
\n
’.
$ swipl mqi --write_connection_values=true 54501 185786669688147744015809740744888120144
Now the server is started. To create a connection:
- Use the language's TCP/IP sockets library to open a socket on the
specified port of localhost and send the password as a message. Messages
to and from the MQI are in the form
<stringByteLength>.\n<stringBytes>.\n
wherestringByteLength
includes the.\n
from the string. For example:7.\nhello.\n
More information on the message format (section 1.6.1) is below. - Listen on the socket for a response message of
true([[threads(Comm_Thread_ID, Goal_Thread_ID), version(Major, Minor)]])
(which will be in JSON form) indicating successful creation of the connection. Comm_Thread_ID and Goal_Thread_ID are the internal Prolog IDs of the two threads that are used for the connection. They are sent solely for monitoring and debugging purposes.version
was introduced in version 1.0 of the protocol to allow for detecting the protocol version and should be checked to ensure the protocol version is supported by your library. See mqi_version/2 for more information and a version history.
We can try all of this using the Unix tool nc
(netcat)
(also available for Windows) to interactively connect to the MQI. In nc
hitting enter
sends \n
which is what the
message format requires. The server responses are show indented inline.
We'll use the port and password that were sent to STDOUT above:
$ nc 127.0.0.1 54501 41. 185786669688147744015809740744888120144. 173. { "args": [ [ [ { "args": ["mqi1_conn2_comm", "mqi1_conn2_goal" ], "functor":"threads" }, { "args": ["1", "0" ], "functor":"version" } ] ] ], "functor":"true" }
Now the connection is established. To run queries and shutdown:
- Any of the messages described in the Machine Query Interface
messages documentation (section 1.6)
can now be sent to run queries and retrieve their answers. For example,
send the message
run(atom(a), -1)
to run the synchronous queryatom(a)
with no timeout and wait for the response message. It will betrue([[]])
(in JSON form). - Shutting down the connection is accomplished by sending the message
close
, waiting for the response message oftrue([[]])
(in JSON form), and then closing the socket using the socket API of the language. If the socket is closed (or fails) before theclose
message is sent, the default behavior of the MQI is to exit the SWI Prolog process to avoid leaving the process around. This is to support scenarios where the user is running and halting their language debugger without cleanly exiting. - Shutting down the launched SWI Prolog process is accomplished by
sending the
quit
message and waiting for the response message oftrue([[]])
(in JSON form). This will cause an orderly shutdown and exit of the process.
Continuing with the nc
session (the quit
message isn't shown since the close
message closes the
connection):
18. run(atom(a), -1). 39. {"args": [ [ [] ] ], "functor":"true"} 7. close. 39. {"args": [ [ [] ] ], "functor":"true"}
Note that Unix Domain Sockets can be used instead of a TCP/IP port. How to do this is described with mqi_start/1.
Here's the same example running in the R language. Note that this is not an example of how to use the MQI from R, it just shows the first code a developer would write as they begin to build a nice library to connect R to Prolog using the MQI:
# Server run with: swipl mqi.pl --port=40001 --password=123 # R Source print("# Establish connection") sck = make.socket('localhost', 40001) print("# Send password") write.socket(sck, '5.\n') # message length write.socket(sck, '123.\n') # password print(read.socket(sck)) print("# Run query") query = 'run(member(X, [1, 2, 3]), -1).\n' write.socket(sck, paste(nchar(query), '.\n', sep='')) # message length write.socket(sck, query) # query print(read.socket(sck)) print("# Close session") close.socket(sck)
And here's the output:
[1] "# Establish connection" [1] "# Send password" [1] "172.\n{\n "args": [\n [\n [\n\t{\n\t "args": ["mqi1_conn1_comm", "mqi1_conn1_goal" ],\n\t "functor":"threads"\n\t}\n ]\n ]\n ],\n "functor":"true"\n}" [1] "# Run query" [1] "188.\n{\n "args": [\n [\n [ {"args": ["X", 1 ], "functor":"="} ],\n [ {"args": ["X", 2 ], "functor":"="} ],\n [ {"args": ["X", 3 ], "functor":"="} ]\n ]\n ],\n "functor":"true"\n}" [1] "# Close session"
Other notes about creating a new library to communicate with the MQI:
- Where appropriate, use similar names and approaches to the Python library when designing your language library. This will give familiarity and faster learning for users that use more than one language.
- Use the debug/1 predicate described in the mqi_start/1 documentation to turn on debug tracing. It can really speed up debugging.
- Read the STDOUT and STDERR output of the SWI Prolog process and output them to the debugging console of the native language to help users debug their Prolog application.