I’m using: SWI-Prolog version 8.0.2 on Ubuntu Linux 18.04.
If I want to expose predicates in the code I pass to PEngines during a PEngines create() call, via the src_text module, can I simply add a module declaration at the top of the code in the src-text parameter? If so, should I use the exact name user to declare the module, since it appears that PEngines assigns that name to the code loaded via src_text?
For example, putting something like this at the top of the code in src_text:
So that could that is pre-loaded on to the server could call back into the code loaded via src_text like this: user:callable_by_non_user_code(X)?
If not, what is the correct procedure for declaring predicates that will be called by code that was loaded into the PEngines server at startup? Also, any problems or warnings I should know about when doing this?
The src_text value is loaded into the Pengines temporary module. This cannot be a global module. Simply consider that Pengines are designed to be safe. Using a global module with a user name would lead to conflicts and global modules may have dependencies anywhere and thus cannot be destroyed safely.
The only way to create a module playing a role is to write this module as a normal module file on the server and load it during application startup. Here, the server manager is responsible for the safety. Now you have two options. You can use
:- use_module(myapp:myfile).
to make its predicates available as default predicates to all pengine instances. Or you use
:- use_module(myfile, []).
to load the module but do not expose its interface. Now a Pengine can use use_module/1,2 to make the module available to the pengine. The module must be preloaded into the server such that the use_module in the pengine doesn’t trigger actual loading. Loading files is allowed from Pengines, but with severe sandbox limitations that allow it to load only really clean code with completely safe declarations.
Thanks. Then how does someone load code via src_text that has predicates you want to call in the ask() calls you want to make right after creating the PEngine instance? Or is that simply not possible?
I don’t understand. You can call the predicates you defined in src_text as well as anything that was made available to the application module. No more, no less.
But that’s the problem. I can’t call the predicates in the code I load via the src_text property via an ask() call as I stated in this thread:
That’s what prompted me to try turning the code I load in the src_text property into a module. I know the pengine_execute_single_user_action() predicate is definitely there as you can see in the `src_text property here:
Error: procedure `'7cd30558-35dc-4ba6-9d64-348ba0837c11':pengine_execute_single_user_action(A,B)' does not exist
Object received:{
"arg1": "procedure",
"arg2": "'7cd30558-35dc-4ba6-9d64-348ba0837c11':pengine_execute_single_user_action('$VAR'(0),'$VAR'(1))",
"code": "existence_error",
"data": "procedure `'7cd30558-35dc-4ba6-9d64-348ba0837c11':pengine_execute_single_user_action(A,B)' does not exist",
"event": "error",
"id": "7cd30558-35dc-4ba6-9d64-348ba0837c11",
"pengine": {
"options": {
"server": "http://localhost:3030/pengine",
"application": "tsll",
"destroy": false,
"src_text": "% ===================== EXECUTE A USER COMMAND =======================\n% ----->>>>> execute_user_action_list\n%\n%\tThis predicate executes a list of actions and then accumulates the\n%\t\tevents generated in the per-volley temporary database predicate\n%\t\tin order to build a full response to the caller.\n\n% All actions executed\nexecute_user_action_list_1([]) :- !.\n\n% Execute the next action.\nexecute_user_action_list_1([Action | Action_T]) :-\n\tnonvar(Action),\n\t% Execute the next action.\n\tcall(Action),\n\t!,\n\texecute_user_action_list_1(Action_T).\n\n% This predicate is here to skip failed actions so that\n% other actions can be tried.\n% TODO: Revisit the soundness of the above strategy.\nexecute_user_action_list_1([_ | Action_T]) :-\n\t!,\n\t% Skip the action.\n\texecute_user_action_list_1(Action_T).\n\nexecute_user_action_list(ActionList, Response) :-\n\tnonvar(ActionList),\n\t% Clear out the database predicate clauses that accumulate\n\t%\tas events are added to the database during the current\n\t%\tvolley.\n\tu_retractall(temp(_)),\n\t% Execute the given list of actions. The events generated\n\t%\twill accumulate in the temp predicate.\n\texecute_user_action_list_1(ActionList),\n\t% Build our response for this volley.\n\tcreate_response(Response),\n\t!.\n\n% ----->>>>> execute_single_user_action\n%\tHelper function to execute just one user action.\nexecute_single_user_action(Action, Response) :-\n\tnonvar(Action),\n\texecute_user_action_list([Action], Response).\n\n% ----->>>>> pengine_execute_single_user_action\n\n%\tHelper function to execute just one user action.\n**pengine_execute_single_user_action**(Action, Response) :-\n\tnonvar(Action),\n\texecute_user_action_list([Action], Response),\n\t!.\n\t% item_to_dict(Response, JsonTerm).\n",
"format": "json"
},
"id": "7cd30558-35dc-4ba6-9d64-348ba0837c11",
"request": {}
}
}
But as I show in that other thread, I get an existence error when I try to call it from an ask() call, with or without having a module declaration at the top of the src_text code.
You don’t really expect people to dig into this long JavaScript string, do you? As an exception I did this, decoding the long JavaScript string and we see:
...
% ----->>>>> execute_single_user_action
% Helper function to execute just one user action.
execute_single_user_action(Action, Response) :-
nonvar(Action),
execute_user_action_list([Action], Response).
% ----->>>>> pengine_execute_single_user_action
% Helper function to execute just one user action.
**pengine_execute_single_user_action**(Action, Response) :-
nonvar(Action),
execute_user_action_list([Action], Response),
!.
% item_to_dict(Response, JsonTerm).
That is including the double ** around the predicate. Now I do not know where these come from (possibly from copy/paste in thus forum), but if they are really in your code you should have got an onoutput event that captures errors and warnings during compilation.
Of course not. I only put that there in the exact format so you could see that I had not made the simple mistake of not actually having a predicate of that name in the src_text property.
That is why I tried highlighting the predicate name in bold so you did not have to visually parse that long string to see that the predicate’s name is indeed in it. Unfortunately the forum software does not support additional styles inside of a code block so it shows the raw bold characters around the predicate name, that of a pair of double asterisks, instead of making it bold. So it was not a copy and paste error nor the error of actually having those characters in the actual source code. It was my (failed) attempt to highlight the predicate name in the src_text parameter.
To recapitulate, I can’t call predicates that are definitely found in the src_text parameter from a PEngines ask() call. I do not get any errors during during the PEngines create() call so I assume the code is sound. If you have any tips on how I might fix or debug this problem I would appreciate it.
If I look in pengines.js I see it is processing options.src and, if the thing is embedded in a document, the <script type="text/x-prolog"> elements are added to this. This translates into an HTTP requests using the src_text property. I don’t know where this is documented. I never really looked at the whole of the JavaScript interface.
It might explain what is going wrong though. You probably want to load library(pengines_io) as well, which captures I/O and makes sure it gets to the client as onoutput and onprompt events. Using the wrong attribute name in JavaScript generally simply causes things to be ignored. What I do in such cases is to use something really simple or use something that should really cause an error and see what happens.
You could also run ?- tspy(http_pengine_create). and see what happens.
That’s the problem right there! The PEngines.js code has the wrong property name. It has src instead of src_text as your code points out. You have to change:
let src = this.options.src ? [this.options.src] : [];
to
let src = this.options.src_text ? [this.options.src_text] : [];
At least if you want to match the documentation for the pengine_create call:
In other words, if you don’t make this change then adding a src_text option to your pengine_create statement does nothing. I’m able to make proper calls now. I’m having a strange problem where variables in my query are not getting bound to anything yet, but at least I’m up and running. Thank you for your help.
The link documents the Prolog API. That is not necessarily the same as the JavaScript API. I do agree that the differences do not make it any easier. There is some merit in it. In Prolog we can choose between representing the source as text or terms. In JavaScript we do not have the choice.
Hi Jan. That’s a good point. But be aware, the Javascript API docs use srctextnotsrc_text with an underscore. That still ends up with the Javascript developer passing something that Pengines.js won’t recognize since it is looking for src specifically. My apologies in advance if I am somehow wrong on this. I’ll stick with my change to PEngines.js because I no longer get existence errors when I make calls to predicates in my src_text code since I altered Pengines.js to look for src_text and not src. Now I just have to figure out why call() doesn’t work for me when in the PEngines sandbox:
I see the docs are at https://pengines.swi-prolog.org/docs/documentation.html. That is all pretty much outdated. Quite a few people have been hacking on pengines.js and nobody updated the above page. I’d leave pengines.js alone and use src.
You can only safely execute code in the sandboxed environment if the static code analysis can keep track of your code. You can use call/1, but only in very simple scenarios.