I think ideally it should be both and on Node.js you should be able to select either of them. The actual WebAssembly binary should be exactly the same. The difference is in the wrapping JavaScript provided by Emscripten. Iām working slowly on the package. My main goal so far was actually TypeScript support and automated build through docker to make current code easier to use by others. Improving API, term representation etc. was secondary.
Besides Javascript/Typescript, thereās also Dart (used to implement Flutter, I think), which runs in the browser (Chrome, Edge, Firefox, Safari). Some years ago, I attended a talk that claimed Dart had more parallelism than Javascript, but I donāt remember the details; and Javascript might have adopted some or all of Dartās parallelism features in the interim.
Anyway, I found a few articles, in case theyāre useful:
That was the starting point. I have been discussing a new JSON format with @ericzinda at Consider adding an option to use a different JSON Format Ā· Issue #4 Ā· SWI-Prolog/packages-mqi Ā· GitHub
It is not very hard of course. The main issue is what you hint at: a lot of stuff will move and that has implications for the build process, users and documentation. We can minimize some of that by adding a library at the old location that loads and reexports the new library (and prints a deprecated warning). That is also what happened to the DCG support that started in the HTTP package as well. Iām still in doubt. The other option would be to simply minimize the HTTP package for the WASM version. That is far easier but doesnāt solve the long term issue.
One of the next steps is to be able to add the foreign libraries in the WASM version.
I have now added support for @ericzindaās proposal for JSON/Object representation to the prolog.js
module as Prolog.toJSON() and Prolog.toProlog() that is connected to the WASM version. Iām also working towards a better high level interface to call Prolog from JavaScript. You can see it at work at SWI-Prolog test playground. The source is in the src/wasm
directory as test.html
and test.pl
. Roughly, this adds:
- A
Prolog.consult(url1, url2, ...).then(function)
that downloads the urls from the server, puts them in/tmp
and loads them into Prolog. -
Prolog.query(...)
which has several signatures and returns an iterable object that represents the query. So, we can do call a goal from a string and get the bindings as an object that holds the JavaScript translation of the results, using the variables as keys. Variables starting with an_
are not included in this object.
Prolog.with_frame(() =>
{ let n = 0;
for(const r of Prolog.query("p(X)"))
{ println("stdout", r.X);
}
});
Or, provide input and do a once goal. The code below computes the sum of all numbers in a list in Prolog. It takes an object with input bindings that is translated to Prolog data. The output object does not include the input variables.
function sum_list(list)
{ return Prolog.with_frame(() =>
{ return Prolog.query("sum_list(List, Sum)", {List:list}).once().Sum;
});
}
There are still several issues with this stuff.
- Iād rather get rid of the
Prolog.with_frame()
that scopes the Prolog terms references. - Breaking out of the query due to an exception (not yet done) or a break from the forā¦of construct does not close it. AFAIK JavaScript has no destructor like C++ when an object gets out of scope.
- Errors are practically nowhere handled. Iām still doubting between return codes and JavaScript exceptions. The lack of destructors might make cleanup complicated
- It all looks pretty ugly and is not documented.
Still, feedback on the overall direction is welcome

- Iād rather get rid of the
Prolog.with_frame()
that scopes the Prolog terms references.- Breaking out of the query due to an exception (not yet done) or a break from the forā¦of construct does not close it. AFAIK JavaScript has no destructor like C++ when an object gets out of scope.
- Errors are practically nowhere handled. Iām still doubting between return codes and JavaScript exceptions. The lack of destructors might make cleanup complicated
![]()
- It all looks pretty ugly and is not documented.
In my opinion it looks pretty good. It is much more usable than it was a week ago. We could just require query to be closed and throw an error if a new query is opened without closing the previous one (another option is to queue them).
Your JavaScript is very good and pretty much self-documenting. Only the indentation is a bit odd but it matches the C code style in SWI
I would like to provide Typescript annotations. For JavaScript programmers they would be very helpful since modern IDE-s will display and autocomplete code by them. Things are moving very fast but I hope to find time to put into this.
I have pushed an update to #wasm and updated the wiki page. There are two big developments:
- Call between Prolog and JavaScript and JavaScript and Prolog are getting close to their final version.
- Calls from Prolog to JavaScript can represent DOM elements as Prolog blobs. The DOM elements known to Prolog are subject to Prolog garbage collection
For example, we can how define a Prolog predicate to add a paragraph to the shell output window:
add_par(Text) :-
Out := document.getElementById("output"),
More := document.getElementById("more"),
Par := document.createElement("p"),
Par.textContent := Text,
_ := Out.insertBefore(Par, More).
After which we can call
?- add_par("Hello world!").
We can also call the other way around nicely, for example writing all the results of p/1
:
for(const r of Prolog.query("p(X)")) {
console.log(r.X);
}
It is indeed inspired by @nicos work on R/real. I donāt know whether this approach has a name. This canāt deal with functions and other complex syntax. We notably need a way to add event listeners.

So basically you would write
add_par() in JavaScript and register it to Prolog.
There is no need to register anything. Just define the function and call _ := add_par("Hello world!")
Of course you can define a wrapper predicate or we can add something that creates these wrappers.
(Nothing of Importance here)
Aside from the above, in EYE we have to do
:- catch(use_module(library(sha)), _, true).
:- catch(use_module(library(pcre)), _, true).
:- catch(use_module(library(http/http_open)), _, true).
:- catch(use_module(library(semweb/rdf_turtle)), _, true).
because those libraries are not yet available in WASM.
This is not a burning issue, I just wondered if there is a plan to support those libraries in WASM?

The result is, so one is free to do whatever one wants with
(:=)/2
?!
Yes. What is wrong with that? You can do whatever JavaScript can do except for stuff that requires more complex syntax such as functions. In other words you can access methods, setters and getters and chain these using a.b.c⦠In fact, internally they are chained as a[b][c]⦠because . is a function in SWI-Prolog. goal_expansion on a.b.c⦠translates this to a[b][c]⦠to avoid the function evaluation. Next step :=/2 translates this into a Prolog term that expresses the actions that need to be done as a list of primitive actions. This is passed through ā$js_callā/2 which is defined using EM_ASM_INT(), passing the raw Prolog terms to JavaScript. JavaScript calls Prolog.toJSON() (should that be Prolog.toObject()? as it is not really JSON). JavaScript executes the (now) array of actions resulting in a return value. This is handed to Prolog.toProlog() and made available as the result. If the arguments are sufficiently instantiated we could do most of the translation at compile time and simply pass the shared JavaScript object.

because those libraries are not yet available in WASM.
This is not a burning issue, I just wondered if there is a plan to support those libraries in WASM?
Most of them depend on foreign components. I have not yet looked into how easy or hard it is to support these. It seems emscripten has a notion of shared libraries, so it should be possible. Opening a URL as a stream as done by library(http/http_open) may be a real problem. A quick search suggests that emscripten has sockets, but they are a wrapper around websockets, not raw TCP/IP sockets. The obvious solution is to download in JavaScript, similar to Prolog.consult().then(), which downloads a file asynchronously using fetch(), compiles the file and runs the then(). This would not allow streaming parsing of e.g. Turtle input.

Wasnāt SWISH a client sandbox? Or mainly a server sandbox?
SWISH is just a server side sandbox. And yes, doing eval() on strings is dangerous if you do not control where the string comes from. Otherwise I donāt see the difference with <script>
elements.

:- catch(use_module(library(sha)), _, true). :- catch(use_module(library(pcre)), _, true). :- catch(use_module(library(http/http_open)), _, true). :- catch(use_module(library(semweb/rdf_turtle)), _, true).
I pushed a starting point to deal with this. After reading all the warning on emscripten against using dynamic linking I decided for another route I had in mind to resolve this issue: allow adding the foreign extensions to the core system. This is now a new option -DSTATIC_EXTENSIONS
to cmake
which is enabled by default for Emscripten. This allows building fully static SWI-Prolog executables with extensions, which surely has value on its own
Currently includes some of the clib
libraries, the sgml
library and some of the http
libraries.
An overall worry is the size of the whole thing
Pushed to #wasm_demo
After seeing
commit 7d8f1e2eba12b0d245ac92ca7a5547f1c6f0589a
Author: Jan Wielemaker <J.Wielemaker@vu.nl>
Date: Sat Aug 20 10:09:42 2022 +0200
Included turtle and ntriple libraries in WASM build.
we tested the latest version but get
ERROR: source_sink `library(semweb/rdf_turtle)' does not exist

ERROR: source_sink `library(semweb/rdf_turtle)' does not exist
I didnāt include this deprecated library. It just loads `library(semweb/turtle)'. Note that the writing part of this library does not yet work as it depends on semweb/rdf_db and the C part of the RDF store heavily depends on threading.

I didnāt include this deprecated library. It just loads `library(semweb/turtle)'.
Aha, that is nice and I will use library(semweb/turtle)
from now on. Thanks!

Par.textContent := Text,
Can I also use, like in JavaScript?
Par["textContent"] := Text
It seems to me from the term_expansion/2
I find in wasm.pl,
and the operator definition op(50, yf, [])
.
But I am not 100% sure, whether term_expansion/2
has another
purpose, like avoiding the dot expansion for dicts?

Par["textContent"] := Text
Par[textContent] := Text
is the output of the expansion. That should work.
The instructions note
wget https://gmplib.org/download/gmp/gmp-6.1.2.tar.lz
tar xf gmp-6.1.2.tar.lz
cd gmp-6.1.2
In checking the GMP latest version it is 6.2.1, was that a typo?

In checking the GMP latest version it is 6.2.1, was that a typo?
I copied that from somewhere ⦠Guess I should have checked (and tested) the latest version.
In the meanwhile #wasm_demo is updated to include GMP, (6.1.2) providing unbounded integers and rational numbers. Note that the GMP library is untested. It might be wise not to trust the results blindly.