Then, I discover that I was missing a dependency:
"cross-fetch": "^3.1.5",
and installed it.
Ah - good point; have opened a bug report and will action it bug: `cross-fetch` is not a dependency · Issue #165 · SWI-Prolog/npm-swipl-wasm · GitHub
Then, I discover that I was missing a dependency:
"cross-fetch": "^3.1.5",
and installed it.
Ah - good point; have opened a bug report and will action it bug: `cross-fetch` is not a dependency · Issue #165 · SWI-Prolog/npm-swipl-wasm · GitHub
Ah right - I had skim read your original message. swipl-generate
only works to bundle a single .pl
file into your project and preload it.
Do you have a link to your project that I can play with to see if we can support your use case natively in npm-swipl-wasm?
Yes, please!. The project is GitHub - LogicalContracts/LogicalEnglish but you can use the pack from here LogicalEnglish/logicalenglish-0.0.2.zip at 777ab90f9a36058de69a2637c66670318099df07 · LogicalContracts/LogicalEnglish · GitHub
the topmost module is le_answer.pl
Thanks a lot @jeswr - Any hint is welcome!.
There is no prolog.pl file under my pack at all. That must be prolog.pl pack handler, right? Any hints?
The current code for swipl-generate
uses this function to generate the image. In particular it copies whichever file you point to (in your case le_answer.qlf
) into a file in the virtual file system called prolog.pl
loads that prolog.pl
file and then qsave
s the program.
To make this work with multiple files starting from le_answer.pl
we will need to extend the logic in this preRun
function to make sure that copies all of your imported .pl
files. For your specific case you should be able to add something along the lines of
for (const file of ['api.pl', 'drafter.pl', /* rest of file names here */]) {
module.FS.writeFile(file, fs.readFileSync(path.join(__dirname, file)))
}
to that function.
Let us know how that goes; if it works I would suggest opening up an issue here generalizing that logic to work for all use cases (or for extra points a PR adding the feature :)).
Unfortunately my knowledge of prolog is too limited to know how to correctly detect all the files (and no more) that we need to copy into the virtual file system before building; but if yourself @jan or others can point me in the right direction to help get this working in general for multiple files - then we an investigate doing so.
To make this work for qlf files, you’ll need to preserve the extension. If you save the .qlf as prolog.pl it will try to read it as source code Otherwise, qlf files are a reasonable way to deal with some of the issues of dealing with multiple files. They are architecture independent, but quite strongly tied to the Prolog version. The code should also not use conditional compilation (
:- if(...).
) where conditions differ between the system generating the qlf and the (wasm) target.
I guess that ideally it copies a directory (preserving the structure), at least as one of the options.
Dear @jan and @jeswr . I had a go, but found myself very confused. Not only with the whole setup to rebuild the node package, but with the JS itself. I think I understand what one has to do and I tried this:
function copyDirectory(Module, source, destination) {
if (!Module.FS.existsSync(destination)) {
Module.FS.mkdirSync(destination);
}
const files = Module.FS.readdirSync(source);
files.forEach((file) => {
const sourcePath = path.join(source, file);
const destinationPath = path.join(destination, file);
if (Module.FS.lstatSync(sourcePath).isDirectory()) {
copyDirectory(sourcePath, destinationPath);
} else {
Module.FS.writeFile(file, destinationPath);
}
});
}
and modified your function like this:
function generateImageBuffer(prolog) {
return __awaiter(this, void 0, void 0, function* () {
const Module = yield (0, swipl_bundle_1.default)({
arguments: ['-q', '-f', 'prolog.pl'],
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
preRun: [(module) => {
copyDirectory(module, __dirname, prolog);
module.FS.writeFile('prolog.pl', prolog); }],
});
Module.prolog.query("qsave_program('prolog.pvm')").once();
return Module.FS.readFile('prolog.pvm');
});
}
But only to get this error:
$ npm run build:image:le
> generation@1.0.0 build:image:le
> swipl-generate ./logicalenglish/prolog/ ./dist/le.ts
node:fs:757
handleErrorFromBinding(ctx);
^
Error: EISDIR: illegal operation on a directory, read
at Object.readSync (node:fs:757:3)
at tryReadSync (node:fs:438:20)
at Object.readFileSync (node:fs:484:19)
at dereference (/home/jacinto/git/le-swipl-wasm/generation/node_modules/swipl-wasm/dist/generateImage.js:86:24)
at /home/jacinto/git/le-swipl-wasm/generation/node_modules/swipl-wasm/dist/generateImage.js:96:86
at Generator.next (<anonymous>)
at /home/jacinto/git/le-swipl-wasm/generation/node_modules/swipl-wasm/dist/generateImage.js:9:71
at new Promise (<anonymous>)
at __awaiter (/home/jacinto/git/le-swipl-wasm/generation/node_modules/swipl-wasm/dist/generateImage.js:5:12)
at generateLoadedImageFile (/home/jacinto/git/le-swipl-wasm/generation/node_modules/swipl-wasm/dist/generateImage.js:95:12) {
errno: -21,
syscall: 'read',
code: 'EISDIR'
}
Node.js v20.1.0
I confess, I don’t really understand your API. Please, help!.
Thank you
It seems the node code for copying the directory hierarchy is wrong as it appears to be trying to open a directory for reading. The overall design copies the Prolog source to the Emscripten file system and calls Prolog to create a saved state from that.
OK. I backtracked and tried this simpler change to the generating code:
function generateImageBuffer(prolog) {
return __awaiter(this, void 0, void 0, function* () {
const Module = yield (0, swipl_bundle_1.default)({
arguments: ['-q', '-f', 'prolog.pl'],
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
preRun: [(module) => { module.FS.writeFile('prolog.qlf', prolog); }],
});
Module.prolog.query("qsave_program('prolog.pvm', [stand_alone(false)])").once();
return Module.FS.readFile('prolog.pvm');
});
}
and fed the generator with a qlf qsaved with [stand_alone(true] which can be tested the show predicate from LE:
./le_all.qlf
Welcome to SWI-Prolog (threaded, 64 bits, version 9.1.8-30-g569e650ef)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.
For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).
?- show(prolog).
is_(A, B) :-
nonvar(B),
A is B.
true.
I, then, generated the image with "build:image:le": "swipl-generate ./le_all.qlf ./dist/le.ts"
and it went ok:
$ npm run build:image:le
> generation@1.0.0 build:image:le
> swipl-generate ./le_all.qlf ./dist/le.ts
Warning: /swipl/library/qsave.pl:47:
Warning: library(shlib): No such file
and tested with:
import SWIPL from './le';
async function main() {
const Module = await SWIPL();
const res = Module.prolog.query('show(prolog).', {});
if (res) {
console.log('queried:', res.once())
} else {
throw new Error('Failed to find max')
}
}
main();
and this ran. But it did not load LE:
npm run test:le
> generation@1.0.0 test:le
> ts-node dist/letest.ts
wasm:wasm_call_string/3: Unknown procedure: show/1
queried: {
error: true,
message: 'wasm:wasm_call_string/3: Unknown procedure: show/1'
}
I tried changing this line in the generating function to rebuilt the image:
Module.prolog.query("qsave_program('prolog.pvm', [stand_alone(true)])").once();
but it says:
npm run build:image:le
> generation@1.0.0 build:image:le
> swipl-generate ./le_all.qlf ./dist/le.ts
Warning: /swipl/library/qsave.pl:47:
Warning: library(shlib): No such file
copy_stream_data/2: I/O error in read on stream <stream>(0x8e8f18) (Is a directory)
Any advice?
Thank you
I think that is not good. At least, assuming you created the saved state on the non-WASM version. A state is to some extend portable, but it also contains code that is conditionally compiled depending on the platform. You also do not wish stand-alone as you want to run it in another environment.
What could work is to use qcompile/2 with the option include(user)
, which creates a .qlf file for all code of your application, but excluding the core system.
The line const files = Module.FS.readdirSync(source);
in your copyDirectory
function is incorrect. Module.FS
is used to interact with the virtual file system in WebAssembly. But you want to be reading the original file from your actual file system which is done with fs.readFileSync(source)
.
That is fs
interacts with your actual file system Module.FS
interacts with the virtual one in WebAssembly. Both have a similar API.
I’m starting to work on a branch to do generation with multiple files here but won’t have time to pick it up for a couple of weeks.
Hello. I tried this too, without success
function generateImageBuffer(prolog) {
return __awaiter(this, void 0, void 0, function* () {
const Module = yield (0, swipl_bundle_1.default)({
arguments: ['-q', '-f', 'prolog.pl'],
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
preRun: [(module) => { module.FS.writeFile('prolog.qlf', prolog); }],
});
Module.prolog.query("qcompile('prolog.qlf', [include(user)])").once();
return Module.FS.readFile('prolog.pvm');
});
}
> generation@1.0.0 build:image:le
> swipl-generate ./le_answer.qlf ./dist/le.ts
qcompile/1: No permission to compile qlf `'/prolog.qlf'' (No Prolog source file)
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "ErrnoError: FS error".] {
code: 'ERR_UNHANDLED_REJECTION'
}
Not sure I read this correctly, but I think this is
le_answer.qlf
to ‘prolog.qlf’ on the virtual file systemqcompile('prolog.qlf', [include(user)])
That is not going to work. You can’t qcompile a .qlf file as that is already compiled. The story doesn’t say how you created le_answer.qlf
. That is the thing that should have been created from le_answer.pl
using qcompile/2.
At least, that is roughly what I can extract from the error and the code …
I’m very sorry. I tried what you described first. Same result. I’ll give a try on the new branch. Thank you.
At a glance I think your argument also needs to be prolog.qlf
not prolog.pl
.