Installing and using third party packs in WASM swipl

As the title says, I didn’t find a way to install and use a third party pack while using WASM swipl. Can this be done?

You mean a Prolog pack? pack_install/1,2 won’t work. Typically I’d clone the pack as a git submodule of your application and use attach_packs/1,2 to make them available.

Thank you @jan. I’m not sure how to use the attach_packs/1,2 family of predicates. I put the pack in static/prolog/packs in my webapp, and am trying to include them with:

:- attach_packs('static/prolog/packs').
:- use_module(library(mavis)).

but this doesn’t work, and pack_attach/1 doesn’t really take a string as an argument, and I’m unsure what the right way of setting this up is.

attach_packs/1 takes a directory name as argument. Is the directory really there in the WASM version? And is mavis below that dir with a file pack.pl and a directory prolog holding mavis.pl?

I’m not sure, here’s how my directory structure looks like:

...
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ app.css
β”‚   β”œβ”€β”€ app.html
β”‚   β”œβ”€β”€ lib
β”‚   β”‚   β”œβ”€β”€ components
β”‚   β”‚       β”œβ”€β”€ ... js specific components ...
β”‚   β”‚   └── index.js
β”‚   └── routes
β”‚       β”œβ”€β”€ +layout.svelte
β”‚       └── +page.svelte
β”œβ”€β”€ static
β”‚   β”œβ”€β”€ favicon.png
β”‚   β”œβ”€β”€ prolog
β”‚   β”‚   β”œβ”€β”€ ... application specific prolog code ...
β”‚   └── prolog_packs
β”‚       └── mavis-0.2.3
β”‚           β”œβ”€β”€ pack.pl
β”‚           β”œβ”€β”€ prolog
β”‚           β”‚   └── mavis.pl
β”‚           β”œβ”€β”€ README.md
β”‚           └── t
β”‚               β”œβ”€β”€ intersection.pl
β”‚               β”œβ”€β”€ predicates.pl
β”‚               └── subtypes.pl
...

I am passing the prolog_packs directory, in different variations, and the command doesn’t error out, but then the command:

:- attach_packs('/prolog_packs').
:- use_module(library(mavis)).

Fails because source_sink library(mavis) does not exist. I suppose the static directory could not be the right place, because that’s for js to find stuff, not prolog, but I installed swipl wasm version from npm, so I’m not sure where else to put the files.

I’m afraid I know very little about web packaging. What matters is what ends up in the WASM file system. Or, do you plan to server the Prolog files from the server?

A good way to package many applications for the wasm version is to use

 swipl qlf compile --include <mainfile.pl>

That should give a stand-alone .qlf file that you can then load from the WASM app. Does require swipl to be the same version as the WASM version you target.

There may be other ways. The docs may help. @jeswr, how do you package?

Sorry to resurrect this thread after more than a year but I am also struggling with this issue.

My setup involves serving the following files to access as a web app in the browser:

pkg/
  web.pl          % the Prolog entry point for my web app
  ...             % other Prolog source code files
  swipl-bundle.js % the bundled WASM build of swipl
  index.html      % HTML entry point
  style.css       % web page styling
  packs/          % directory with packs such as reif
    reif/
      pack.pl
      prolog/
        reif.pl

My index.html contains a small Javascript <script> which loads the WASM module and uses Prolog.forEach() to query the main. goal defined in web.pl. This seems to work very well and loads all the other Prolog source files in the served directory.

However, this breaks down when I introduce packs (stored in the served subdirectory packs). No permutation of attach_packs/1 seems to work, it only silently fails and then I will get source_sink errors when I try to use_module(library(reif)).

After doing a bit of tracing I’m wondering if the issue is that, for example when I try attach_packs(packs) it is attempting to add ./packs from the Emcripten virtual file system, rather than the URL location https://.../packs? I can see that library(wasm) defines a user:prolog_load_file/2 specifically for handling files over the internet, but perhaps file_search_path/2 is only using the Emscripten file system?

Am I missing an obvious way to use packs that are served as part of the web app? If I wanted to add my packs directory to the Emscripten file system I assume I would need to compile swipl from scratch (NB: I am currently using the very helpful pre-made builds from GitHub - SWI-Prolog/npm-swipl-wasm: SWI-Prolog WebAssembly build as a NPM package Β· GitHub).

(I’m not sure how to make the qlf pre-compilation approach work since I use library(wasm) which appears to only be available when running in WASM? Is there a way to β€œcross-compile” for a different target?)

If you want to do it manual way through the Emscripten file system, then this old example should show how to put files into the filesystem before loading them with use_module (check source): SWI-Prolog Virtual DOM demo

You might need to write a script that generates either JS code to load all files of a pack or emits some configuration file with the location of files. Then you can iterate them and load them into the filesystem.

The problem is that the pack infrastructure heavily relies on file search and this works rather poorly if you have to deal with a large number of candidate HTTP locations :frowning: . You should be able to load the required files directly from the pack’s content. As @rla says, adding to the virtual file system is another option. You can also download them to the filesystem using JavaScript and than add the directory using attach_packs/1.

Suggestions to fix this otherwise are welcome … Some kind of overall pack index we could download, so we do not need searching?

You should be able to do so using the WASM version under node. Not sure how to get that running though. If you build from source you get src/swipl.js that runs the CLI version under node.

Ah I made the incorrect assumption that the virtual filesystem would be immutable once the WASM module is compiled, I wasn’t aware of the Emscripten Filesystem API and the ability to dynamically update it from Javascript. This might be the easiest solution as then I can keep my existing (non-virtual) file structure for other (non-WASM) contexts and write a bit of JS glue to make swipl happy. Thanks @rla for sharing the example!

Yes this is tricky! I’m not familiar with the underlying implementation but my (probably overly simplistic) first thought was to wonder whether file_search_path/2 could be used with URLs like:

file_search_path(library, 'http://.../library/')

such that library(reif) would be expanded to 'http://.../library/reif.pl' and the file fetched rather than read from the virtual file system. However that is probably not straightforward to implement!

Ah yes of course, my second incorrect assumption of the day. Thanks @jan I should have thought to use node for this.

For anyone else searching on this topic, I ended up using something along the following lines (inspired by @rla’s approach):

// Fetch packs and write to Emscripten virtual file system
function loadPacks(module) {
  const dirs = ["packs/reif/prolog"];
  const files = [
    "packs/reif/pack.pl",
    "packs/reif/prolog/reif.pl"
  ];
  dirs.map((d) => module.FS.mkdirTree(d));
  return Promise.all(files.map((f) => {
    fetch(f).then((response) => response.arrayBuffer())
            .then((content) => module.FS.writeFile(f, content))
  }));
}

This means that attach_packs(packs) now works as expected.