WASM wait for user input

I’m trying to build an interactive “expert system” in an html page with the wasm version of swi-prolog.

I have a textbox for the user input (that would normally come from the console), and a button to submit the text message.

I can write to the html page, and I can use bind/4 to add click events on the button, but is there a way I can suspend prolog until the value is read from the textbox?

Here is a snippet from my code.
add_message simply prints strings on screen, nothing more.
In the read_and_continue I can print the data read form the textbox, but ask_user does not wait for read_and_continue to end.

ask_user(Question)  :-
	get_by_id(submit, Button),
	add_message(expert_message, P),
	bind(Button, click, _, read_and_continue(Question)).

Good question. I think the answer is in creating a promise that waits for the (click) event and then use await/2. There are several code snippets on stack overflow for waiting for an event using a Promise. If you also want to process other stuff you should probably make sure that this “thread” runs in is own engine using Prolog.forEach() with the {engine: true} option as you find it (only) in the very latest (9.3.20) release.

So far, Prolog+WASM+JavaScript was mostly limited to using callbacks. With JavaScript Promise and Prolog engines we have something similar to JavaScript async functions. No example code and experience though. Please give it a try and share your experience!

I tried (copying some strange javascript code from stackoverflow for the js part).
The issue is if I run it with swipl-wasm I get the error in the picture. I’m not sure about what the input window is…

My understanding is that the prolog part should be something like this:

ask_user(P, Proof, Trace)  :-
	get_by_id(submit, Button),
	add_message(expert_message, P),
	FP := waitForClick(),
	await(FP, Text),
	_ := console.log(Text).

If I remove the last three lines I can print stuff to the html page (that is what add_message does).

waitForClick is the javascript code that adds the promise to the button click, and returns the value read from the input.

I’m sure I’m missing something obvious :upside_down_face:

The system error points at a GC error. Most likely caused by something fishy in JavaScript that wraps the WASM kernel.

Can you turn this into a reproducible thing we can run?

Sure. I made a few changes and included everything in the html file (prolog script included).
I’m not particularly familiar with Javascript, so that might very well be where the issue is.

This is basically the tweety code, with the addition of a user query tied to the html form.
The user is supposed to enter yes/no in the textfield.

Sorry for the delay. There are two issues.

  • There are two singleton variables. Apparently something goes wrong reporting these, causing the GC system error. If you fix these, you get a little further.
  • To run the query, you use Prolog.query().once(). But, this is synchronous. If you want async programming (which you need if you want to use Promises), you need Prolog.forEach(). That returns a promise, so you either need to add .then(...) or put it in an async function and add await.

With these two fixes it prints the answer to the question in the console, so that step works. I didn’t look at the rest.

Up to me is why printing compilation warnings leads to problems …

Fixed that one. Loading your original program in the latest version now gives this in the console, which looks pretty adequate to me.

swipl-web.js:9 Warning: /script/1:79:
swipl-web.js:9 Warning:    Singleton variables: [Button]
swipl-web.js:9 Warning: /script/1:109:
swipl-web.js:9 Warning:    Singleton variables: [C]
expert.html:182 Consulted
swipl-web.js:9 await/2 is only allowed in Prolog.forEach() queries
```

Thanks Jan!
Now it works. I hadn’t realized how the forEach worked.

There are still a few things to fix with more complex question/answering systems, but I can work with it now!

I’m basically re-implementing parts of the webconsole pack for an expert system.

(I’m sure there are better ways to go about doing this…)

Probably. We should probably have a closer look at other browser based Prolog systems such as Tau Prolog. Note there seem to be three ways for Prolog in the browser:

  • Build it all in JavaScript (e.g., Tau)
  • Compile to WASM. As is, WASM is synchronous unless you compile it as async code, but Emscripten says there is a high price both wrt size and performance. WASM engine support seems to be on the way, which may change things. If not, we have two options
    • Put Prolog in a web worker and communicate with it using messages. That is what e.g. Ciao does (AFAIK)
    • Support yield from the VM, so you can return to the browser event loop, optionally wait for something and resume. That is what SWI-Prolog does.

The first and last allows quite direct manipulation of the DOM, something which is a lot harder when using a web worker. These two result (I think) in a different approach for integrating Prolog into the browser.

At least, that is my current understanding. I’m not a browser/JavaScript/WASM expert though. If I missed some useful path, I’d be glad to hear.

I’m no browser/javascript expert either. Far from it in fact.

I’m mainly trying to use it for simple demonstrations of user interaction aimed at students.
I have to admit that I’d also like something more robust to come out of this, but that’s a long term plan…

Somehow this is an old Problem, isn’t it? There was a prototype of
Web Prolog by @torbjorn.lager which even showcased a working
terminal! Maybe from some library? But I don’t find it anymore…

I was playing with the new WAM shell. The announcement says:

I tried a query [user]. Is this supposed to work? A ask dialog
appears, I entered “foo(bar).” pressed ok and then entered
“end_of_file.” and pressed ok. And then I pressed cancel:

But the result is only:

Nope. That should probably be disabled. We could hook it by hooking read_clause/3. Probably there is a better alternative by showing an editor inline, but why not just create a new file and load that? So, I think the most sensible thing is to display a message hinting at that.

In general, all predicate that read from user_input must be hooked. I now do that by wrapping the predicate using wrap_predicate/4. The wrapper uses one of the low-level yields or creates a JavaScript Promise to get the required info from the user and uses await/2 to wait for it.

The screen splitting is an additional use case, its for example also
seen in Ciao Prolog playground. The use case there is not console
respectively shell, but rather further management and possibly sharing
of Prolog texts. So if you say WASM shell its a misnomer in my opinion,

its already WASM playground, just like Ciao Prolog playground.

Because you wrote:

Its just a technical test, to check how far read_term/2 goes.
Usually [user] does read_term/2 or something similar.
Also I didn’t expect the ask dialog, rather some inline reading.
This was a feature of the Prolog console I saw at @torbjorn.lager .

It basically provided reading without the interruption of ask dialog,
which I havent got my head around how to implement this console
reading in JavaScript in a few lines. The ask dialog is also only a few
lines, but invokes the prompt() JavaScript call. A proper web console is

a little bit more tricky, since the user_input would be shared
with the user_output. Most programming languages come nowadasys
with a proper web console, the screen splitting is less often seen in
programming languages as a first encounter. This is what you get in Python:

My idea is to have the same, but locally in the browser client. The
Prolog system will be locally in the browser and the top-level wouldn’t
notice a thing, it would just attach to a web console , which
I once saw together with the Web Prolog prototype of @torbjorn.lager .

Spit what into what?

That is possible if you run SWI-Prolog as a web worker and use messages to talk to “terminal” in the browser. That is what Ciao does (AFAIK). I guess you can do the same using SWI-Prolog’s WASM binary. The SWI-Prolog playground runs in main browser task though. That implies it has to yield for anything that requires synchronous input. The advantage is that you can create a much nicer user experience and even allow the user to interact with the browser (we could add a JavaScript editor as well :slight_smile: ). The disadvantage is that you need to redefine all predicates that read from user_input. If you don’t, you get this input prompt from Emscripten (not sure where this is defined).

Pushed a version that blocks ?- [user]. with a message and adds dealing with the terminal code extension for hyperlinks, such that many messages from Prolog result in clickable links, including errors and warnings from loading files.

Yes, something else, how can I make multi-line input? I find that read(X)
doesn’t show as an ask pop-up. But somehow multi-line input is not supported:

Normal Console:

WASM Shell:

As yet, that is not supported. The plan is to replace the simple <input> with a CodeMirror based editor as it is used in SWISH. That can also reuse completion, etc. I’m still a bit unsure where this should go. I created a tentative TODO at swipl-devel/src/wasm/demos/shell/README.md at 4de058d21bffd41b26059e3a8cc92b8cb4e6360f · SWI-Prolog/swipl-devel · GitHub

(Nothing of interest here)

Hi Jan!

I personally think that Prolog in a web worker and JavaScript communicating with it using messages is the approach that every system that wants to be on the Web should take. I appreciate that it might be slower than direct manipulation of the DOM from Prolog, but is seems to me that is should be fast enough for most purposes. Perhaps a PIP for this is called for? It should be possible to agree on a protocol that might be standardized.

It seems to me that building a large and complex end-user application on top of it all will often require adopting a JavaScript framework of some kind, and if done in a commercial project, programmers that know their ways around it. And who knows, in a perhaps not too distant future, the front-end might be written by an AI - check out Bolt (at https://bolt.new) for an example of such a system. In this future, perhaps all one needs to do is to feed such as system with a description of dedicated web-worker protocol and instructions for how it should be used for the target application.

Ideally of course, a Prolog system might support both the web-worker approach and the DOM-manipulation approach, but I think code for the latter should be in the form of system-dependent libraries until the Prolog community can agree on how it should be done.

Those are my €0.02.

You may well be right, I haven’t looked at this in detail. But I find it hard to believe that something like that would be impossible to implement.

(Nothing of interest here)