for the browser, start the node http-server in the root of the cloned repo using http-server ., open your browser at the indicated location and navigate to examples/browser.html. That should print a to the browser window. Why not “hello world”?
For node, simply run node examples/run-on-node.js, which again prints a.
I was confused about the development environment, where after the build you can run node src/swipl.js to get a normal interactive SWI-Prolog instance that “sees” the physical file system. Looks like this does work in my local built, so something may be wrong with the way it is build in docker. I’ll sync both.
I don’t get it. In my local build as well as when looking into @rla’s build container I can run node swipl.js and when all is in the right place, it runs the normal Prolog toplevel. Without everything in the right place I end up with an error message, either not being able to load swipl.wasm or from Prolog not being able to find its startup resources.
When I go to the dist directory though, node swipl.js exits immediately without any message. Using strace to trace its system call suggests it doesn’t even try to find the .wasm file Maybe someone more familiar with the node and JavaScript eco system can solve this?
I debugged these issues a bit. I don’t seem to be able to run toplevel and I think that’s expected behavior since PL_initialise does not run toplevel. https://www.swi-prolog.org/pldoc/doc_for?object=c(‘PL_initialise’) PL_initialise will print the banner and then exit as nothing keeps the nodejs process around.
The MODULARIZE option (which we also use) for Emscripten turns generated SWI-Prolog module code into a factory function that needs to be imported and then executed. This will locate (at least it will try) .wasm and .data files. That’s why the example in the package also uses:
const SWIPL = require("./swipl/swipl"); // imports the module
// ...
const swipl = await SWIPL({ options }); // runs the factory function and places the instance into swipl variable
With Qt 6.4 we are taking the support for WebAssembly out of technology preview. With Qt for WebAssembly, Qt developers can use their existing skills, and often existing code, to target the web. Applications targeting the web assembly platform can run in most modern web browsers, and are easily distributed like any other web content. Thanks to near-native performance, and the rich UI and 3D features of Qt Quick and Qt Quick 3D, solutions requiring heavy data processing and demanding visualization can now easily be built for the web.
Replace the <textarea> with CodeMirror and use the Prolog mode that is part of SWISH. Doesn’t use SWISH’s enhanced Prolog mode using Prolog support (which should be a lot simpler using the WASM version than server interaction).
Allow yield while loading local files. This does keep the browser responsive while loading large files and it allows loading from URLs from a local file. So, we can copy paste this: and use it:
:- use_module('https://raw.githubusercontent.com/SWI-Prolog/sCASP/master/prolog/scasp.pl').
p :- not q.
q :- not p.
Provide server-side example programs. These are added to the file dropdown and when selected copied to your local store, so you can load and edit them. Currently there are only two, one loading s(CASP) from Github and one manipulating the shell itself through DOM manipulations.
If you have nice examples, please share them.
Changed the console to use a more structured DOM. The console now has a sequence of div elements that each reflect a query and its answers. This allows collapsing and removing queries and in theory a lot more. As experiment it now collapses all old queries. That gives much more overview of the history and allows selectively opening them. Please share whether you like it.
support the edit/1 interface.
A mayor problem when you do fancy stuff is that it is easy to get Prolog involved in multiple asynchronous queries. That is fine, as long as they are strictly nested, i.e., we can start a new asynchronous query while yielded from an older one. We must however complete the new one before we can continue the old one. That cannot be solved without engines. All we can hope for on the short term is to detect this (Prolog now crashes badly).
Hello, I wrote a little online prolog editor along the lines of SWISH but using this WASM build. I mainly needed it to draw the trace as a graphical tree, but it might be useful to someone.
P.S. I implemented “asynchronous” queries by running them in separate workers. Live Version
You are correct, I did mean to write parallel queries. Some times the two terms are used interchangeably, but it is better to be precise. More in detail, Javascript is run as a single thread in a page (for the most part), so blocking queries such as sleep would freeze the interface entirely, which was not great. As far as I know workers are the most straightforward way to perform parallel tasks in JS, so I created a pool of workers which then execute a single query each before reinstantiating. They are entirely separated from each other and the main thread (besides messages of course).
The WASM implementation offers asynchronous queries (using forEach), however very long to execute steps (Such as sleep, or Example 4 in the editor) will slow down or freeze the page if they are in the main thread. Again, using workers they do no longer affect the editor itself.
Testing the library on Node, calling JS from Prolog does not seem to work (:=/2 or js_script/2). The error is “window object undefined”, so it’s still expecting to be on a browser.
I tried to create a dummy global window object and assign names to it, but it didn’t work either.
Then I found that in the repository examplejs_run_script/1 is used rather than js_script/2 and this seems to work, provided one assigns functions and variables to the global scope first.
Functions/variables must be assigned globally when using CommonJS modules or to a global object when using ES modules (I found this through experimenting).
Example with CommonJS modules:
const SWIPL = require("swipl-wasm");
const data = [];
// Just add some data
add = function () {
data.push(1);
};
(async function () {
const swipl = await SWIPL({ arguments: ["-q"] });
swipl.prolog.call(`js_run_script("add()")`); // js_script/1 throws, js_script/2 requires 'window'
console.log(data); // Correctly outputs [1]
})();
Example with ES modules:
import SWIPL from "swipl-wasm";
const data = [];
// Just add some data
global.add = function () {
data.push(1);
};
const swipl = await SWIPL({ arguments: ["-q"] });
swipl.prolog.call(`js_run_script("add()")`); // js_script/1 throws, js_script/2 requires 'window'
console.log(data); // Correctly outputs [1]
So, I don’t know whether js_run_script/1 (which is undocumented) is the right predicate for calling JS from Prolog rather than js_script/2. Also, the example provided in the documentation seems to have the wrong arity (js_script/1 is used).
Thank you Jesse @jeswr . I did try your bundle. Very useful. It went well cloning, npm install, npm run build. Then I started testing the examples. This runs fine npm run test:serve-http. But the node’s example does not:
$node --unhandled-rejections=strict examples/run-on-node.js
---/npm-swipl-wasm/examples/run-on-node.js:5
console.log(swipl.prolog.query("member(X, [a, b, c]).").once().X);
^
TypeError: Cannot read property 'query' of undefined
at ---/npm-swipl-wasm/examples/run-on-node.js:5:28
The code in that js file is not the same that in the wiki:
wiki:
const SWIPL = require("swipl-wasm/dist/swipl");
in run-on-node.js
const SWIPL = require("../dist/swipl-node");
Can you confirm which is the correct module to require?
Note that if you clone the repo (which you should not do unless you are contributing to the project) then you can just use const SWIPL = require("../dist") for node and browser as we export an isomorphic bundle there.
If you are importing swipl-wasm in your own project like discussed in the getting started guide above; you get the same files by doing const SWIPL = require("swipl-wasm") or import SWIPL from "swipl-wasm"
Thank you for the getting started guide @jeswr - I carefully followed the instructions and got the same error. But then I noticed I was using an old version of node. I updated to v20.1.0 and it worked as expected.
But, how can I test the second example if I don´t have the scripts settings npm run test:serve-http nor the examples dir in there? (because I am not cloning the repo).
Btw, what I want is to bundle in my own prolog code, which is not a single file but a whole pack with prolog modules. Is that possible?
It is a bit unclear which versions deal with the latest Emscripten output. Emscripten 3.1.37 bundles Node.js 14, which cannot run the result. Node.js 18 (latest stable AFAIK) is fine.
Most likely, yes. Hopefully @jeswr knows the answer how. With qcompile/2 you can compile a project into a single .qlf file. If there is no other way, that should probably provide a work-around.
@jacintodavila The current build is tested against 16.x, 18.x, 19.x and 20.x so all
of those versions should work.
Please note that I aim to support all versions of Node that have not been end-of-lifed per the NODEJS release cycle. (@jan this is also why we are targeting to build with Node 16 at the moment - it is the oldest currently supported version).
Btw, what I want is to bundle in my own prolog code, which is not a single file but a whole pack with prolog modules. Is that possible?
Thank you @jan and @jeswr . I succesfully replicated the max example.
I copied the whole ./generation folder from the original examples/generation into my own folder, corrected "swipl-wasm": "^3.3.0" in package.json and run npm install from ./generation/
Then, I discover that I was missing a dependency: "cross-fetch": "^3.1.5", and installed it.
Then:
npm run build:image
> generation@1.0.0 build:image
> swipl-generate ./max.pl ./dist/max.ts
Warning: /swipl/library/qsave.pl:47:
Warning: library(shlib): No such file
only showed those warning, so I could run:
npm run test
> generation@1.0.0 test
> ts-node dist/main.ts
which is not very informative, but it means that it is allright! I changed main.ts to:
import SWIPL from './max';
async function main() {
const Module = await SWIPL();
const res = Module.prolog.query('myMax(A, B, C).', { A: 1, B: 2 });
if ((res.once() as { C: number }).C === 2) {
console.log('the maximum between 1 and 2 is 2, of course')
} else {
throw new Error('Failed to find max')
}
}
main();
and, of course, it runs well too:
npm run test
> generation@1.0.0 test
> ts-node dist/main.ts
the maximum between 1 and 2 is 2, of course
However, the experiment with my own pack did not work out. I tried with folder with the whole pack and with the main module in it and none could be made into a new image. So, I went for @jan 's workaround using a qlf file. To the effect, I added: "build:image:le": "swipl-generate ./le_answer.qlf ./dist/le.ts", to package.json’s scripts and did: