View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2014-2025, VU University Amsterdam
    7                              CWI, Amsterdam
    8                              SWI-Prolog Solutions b.v.
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(pengines_io,
   38          [ pengine_writeln/1,          % +Term
   39            pengine_nl/0,
   40            pengine_tab/1,
   41            pengine_flush_output/0,
   42            pengine_format/1,           % +Format
   43            pengine_format/2,           % +Format, +Args
   44
   45            pengine_write_term/2,       % +Term, +Options
   46            pengine_write/1,            % +Term
   47            pengine_writeq/1,           % +Term
   48            pengine_display/1,          % +Term
   49            pengine_print/1,            % +Term
   50            pengine_write_canonical/1,  % +Term
   51
   52            pengine_listing/0,
   53            pengine_listing/1,          % +Spec
   54            pengine_portray_clause/1,   % +Term
   55
   56            pengine_read/1,             % -Term
   57            pengine_read_line_to_string/2, % +Stream, -LineAsString
   58            pengine_read_line_to_codes/2, % +Stream, -LineAsCodes
   59
   60            pengine_io_predicate/1,     % ?Head
   61            pengine_bind_io_to_html/1,  % +Module
   62            pengine_io_goal_expansion/2,% +Goal, -Expanded
   63
   64            message_lines_to_html/3     % +Lines, +Classes, -HTML
   65          ]).   66:- autoload(library(apply),[foldl/4,maplist/3,maplist/4]).   67:- autoload(library(backcomp),[thread_at_exit/1]).   68:- use_module(library(debug),[assertion/1]).   69:- autoload(library(error),[must_be/2]).   70:- autoload(library(listing),[listing/1,portray_clause/1]).   71:- autoload(library(lists),[append/2,append/3,subtract/3]).   72:- autoload(library(option),[option/3,merge_options/3]).   73:- use_module(library(pengines),
   74              [ pengine_self/1,
   75                pengine_output/1,
   76                pengine_input/2,
   77                pengine_property/2
   78              ]).   79:- autoload(library(prolog_stream),[open_prolog_stream/4]).   80:- autoload(library(readutil),[read_line_to_string/2]).   81:- autoload(library(http/term_html),[term/4]).   82
   83:- use_module(library(yall),[(>>)/4]).   84:- use_module(library(http/html_write),[html/3,print_html/1, op(_,_,_)]).   85:- use_module(library(settings),[setting/4,setting/2]).   86
   87:- use_module(library(sandbox), []).   88:- autoload(library(thread), [call_in_thread/2]).   89
   90:- html_meta send_html(html).   91:- public send_html/1.   92
   93:- meta_predicate
   94    pengine_format(+,:).

Provide Prolog I/O for HTML clients

This module redefines some of the standard Prolog I/O predicates to behave transparently for HTML clients. It provides two ways to redefine the standard predicates: using goal_expansion/2 and by redefining the system predicates using redefine_system_predicate/1. The latter is the preferred route because it gives a more predictable trace to the user and works regardless of the use of other expansion and meta-calling.

Redefining works by redefining the system predicates in the context of the pengine's module. This is configured using the following code snippet.

:- pengine_application(myapp).
:- use_module(myapp:library(pengines_io)).
pengines:prepare_module(Module, myapp, _Options) :-
      pengines_io:pengine_bind_io_to_html(Module).

Using goal_expansion/2 works by rewriting the corresponding goals using goal_expansion/2 and use the new definition to re-route I/O via pengine_input/2 and pengine_output/1. A pengine application is prepared for using this module with the following code:

:- pengine_application(myapp).
:- use_module(myapp:library(pengines_io)).
myapp:goal_expansion(In,Out) :-
      pengine_io_goal_expansion(In, Out).
  129:- setting(write_options, list(any), [max_depth(1000)],
  130           'Additional options for stringifying Prolog results').  131
  132
  133                 /*******************************
  134                 *            OUTPUT            *
  135                 *******************************/
 pengine_writeln(+Term)
Emit Term as <span class=writeln>Term<br></span>.
  141pengine_writeln(Term) :-
  142    pengine_output,
  143    !,
  144    pengine_module(Module),
  145    send_html(span(class(writeln),
  146                   [ \term(Term,
  147                           [ module(Module)
  148                           ]),
  149                     br([])
  150                   ])).
  151pengine_writeln(Term) :-
  152    writeln(Term).
 pengine_nl
Emit a <br/> to the pengine.
  158pengine_nl :-
  159    pengine_output,
  160    !,
  161    send_html(br([])).
  162pengine_nl :-
  163    nl.
 pengine_tab(+N)
Emit N spaces
  169pengine_tab(Expr) :-
  170    pengine_output,
  171    !,
  172    N is Expr,
  173    length(List, N),
  174    maplist(=(&(nbsp)), List),
  175    send_html(List).
  176pengine_tab(N) :-
  177    tab(N).
 pengine_flush_output
No-op. Pengines do not use output buffering (maybe they should though).
  185pengine_flush_output :-
  186    pengine_output,
  187    \+ pengine_io(_,_),
  188    !.
  189pengine_flush_output :-
  190    flush_output.
  191
  192:- multifile
  193    pengines:pengine_flush_output_hook/0.  194
  195pengines:pengine_flush_output_hook :-
  196    pengine_flush_output.
 pengine_write_term(+Term, +Options)
Writes term as <span class=Class>Term</span>. In addition to the options of write_term/2, these options are processed:
class(+Class)
Specifies the class of the element. Default is write.
  206pengine_write_term(Term, Options) :-
  207    pengine_output,
  208    !,
  209    option(class(Class), Options, write),
  210    pengine_module(Module),
  211    send_html(span(class(Class), \term(Term,[module(Module)|Options]))).
  212pengine_write_term(Term, Options) :-
  213    write_term(Term, Options).
 pengine_write(+Term) is det
 pengine_writeq(+Term) is det
 pengine_display(+Term) is det
 pengine_print(+Term) is det
 pengine_write_canonical(+Term) is det
Redirect the corresponding Prolog output predicates.
  223pengine_write(Term) :-
  224    pengine_write_term(Term, [numbervars(true)]).
  225pengine_writeq(Term) :-
  226    pengine_write_term(Term, [quoted(true), numbervars(true)]).
  227pengine_display(Term) :-
  228    pengine_write_term(Term, [quoted(true), ignore_ops(true)]).
  229pengine_print(Term) :-
  230    current_prolog_flag(print_write_options, Options),
  231    pengine_write_term(Term, Options).
  232pengine_write_canonical(Term) :-
  233    pengine_output,
  234    !,
  235    with_output_to(string(String), write_canonical(Term)),
  236    send_html(span(class([write, cononical]), String)).
  237pengine_write_canonical(Term) :-
  238    write_canonical(Term).
 pengine_format(+Format) is det
 pengine_format(+Format, +Args) is det
As format/1,2. Emits a series of strings with <br/> for each newline encountered in the string.
To be done
- : handle ~w, ~q, etc using term//2. How can we do that??
  248pengine_format(Format) :-
  249    pengine_format(Format, []).
  250pengine_format(Format, Args) :-
  251    pengine_output,
  252    !,
  253    format(string(String), Format, Args),
  254    split_string(String, "\n", "", Lines),
  255    send_html(\lines(Lines, format)).
  256pengine_format(Format, Args) :-
  257    format(Format, Args).
  258
  259
  260                 /*******************************
  261                 *            LISTING           *
  262                 *******************************/
 pengine_listing is det
 pengine_listing(+Spec) is det
List the content of the current pengine or a specified predicate in the pengine.
  270pengine_listing :-
  271    pengine_listing(_).
  272
  273pengine_listing(Spec) :-
  274    pengine_self(Module),
  275    with_output_to(string(String), listing(Module:Spec)),
  276    split_string(String, "", "\n", [Pre]),
  277    send_html(pre(class(listing), Pre)).
  278
  279pengine_portray_clause(Term) :-
  280    pengine_output,
  281    !,
  282    with_output_to(string(String), portray_clause(Term)),
  283    split_string(String, "", "\n", [Pre]),
  284    send_html(pre(class(listing), Pre)).
  285pengine_portray_clause(Term) :-
  286    portray_clause(Term).
  287
  288
  289                 /*******************************
  290                 *         PRINT MESSAGE        *
  291                 *******************************/
  292
  293:- multifile user:message_hook/3.
 user:message_hook(+Term, +Kind, +Lines) is semidet
Send output from print_message/2 to the pengine. Messages are embedded in a <pre class=msg-Kind></pre> environment.
  300user:message_hook(Term, Kind, Lines) :-
  301    Kind \== silent,
  302    pengine_self(_),
  303    atom_concat('msg-', Kind, Class),
  304    message_lines_to_html(Lines, [Class], HTMlString),
  305    (   source_location(File, Line)
  306    ->  Src = File:Line
  307    ;   Src = (-)
  308    ),
  309    pengine_output(message(Term, Kind, HTMlString, Src)).
 message_lines_to_html(+MessageLines, +Classes, -HTMLString) is det
Helper that translates the Lines argument from user:message_hook/3 into an HTML string. The HTML is a <pre> object with the class 'prolog-message' and the given Classes.
  317message_lines_to_html(Lines, Classes, HTMlString) :-
  318    phrase(html(pre(class(['prolog-message'|Classes]),
  319                    \message_lines(Lines))), Tokens),
  320    with_output_to(string(HTMlString), print_html(Tokens)).
  321
  322message_lines([]) -->
  323    !.
  324message_lines([nl|T]) -->
  325    !,
  326    html('\n'),                     % we are in a <pre> environment
  327    message_lines(T).
  328message_lines([flush]) -->
  329    !.
  330message_lines([ansi(Attributes, Fmt, Args)|T]) -->
  331    !,
  332    {  is_list(Attributes)
  333    -> foldl(style, Attributes, Fmt-Args, HTML)
  334    ;  style(Attributes, Fmt-Args, HTML)
  335    },
  336    html(HTML),
  337    message_lines(T).
  338message_lines([url(Pos)|T]) -->
  339    !,
  340    location(Pos),
  341    message_lines(T).
  342message_lines([url(HREF, Label)|T]) -->
  343    !,
  344    { msg_label(Label, Text) },
  345    (   { atomic(HREF) }
  346    ->  html(a(href(HREF), Text))
  347    ;   html([Text])                    % a source location: not a web link
  348    ),
  349    message_lines(T).
  350message_lines([H|T]) -->
  351    html(H),
  352    message_lines(T).
 msg_label(+Label, -Text) is det
Text is the text of the label of an url/2 message element. See print_message_lines/3.
  359msg_label(ansi(_Style, Fmt, Args), Text) :-
  360    !,
  361    format(string(Text), Fmt, Args).
  362msg_label(ansi(_Style, Fmt, Args, _Ctx), Text) :-
  363    !,
  364    format(string(Text), Fmt, Args).
  365msg_label(Fmt-Args, Text) :-
  366    !,
  367    format(string(Text), Fmt, Args).
  368msg_label(Text, Text).
  369
  370location(File:Line:Column) -->
  371    !,
  372    html([File, :, Line, :, Column]).
  373location(File:Line) -->
  374    !,
  375    html([File, :, Line]).
  376location(File) -->
  377    html([File]).
  378
  379style(bold, Content, b(Content)) :- !.
  380style(fg(default), Content, span(style('color: black'), Content)) :- !.
  381style(fg(Color), Content, span(style('color:'+Color), Content)) :- !.
  382style(_, Content, Content).
  383
  384
  385                 /*******************************
  386                 *             INPUT            *
  387                 *******************************/
  388
  389pengine_read(Term) :-
  390    pengine_input,
  391    !,
  392    prompt(Prompt, Prompt),
  393    pengine_input(Prompt, Term).
  394pengine_read(Term) :-
  395    read(Term).
  396
  397pengine_read_line_to_string(From, String) :-
  398    pengine_input,
  399    !,
  400    must_be(oneof([current_input,user_input]), From),
  401    (   prompt(Prompt, Prompt),
  402        Prompt \== ''
  403    ->  true
  404    ;   Prompt = 'line> '
  405    ),
  406    pengine_input(_{type: console, prompt:Prompt}, StringNL),
  407    string_concat(String, "\n", StringNL).
  408pengine_read_line_to_string(From, String) :-
  409    read_line_to_string(From, String).
  410
  411pengine_read_line_to_codes(From, Codes) :-
  412    pengine_read_line_to_string(From, String),
  413    string_codes(String, Codes).
  414
  415
  416                 /*******************************
  417                 *             HTML             *
  418                 *******************************/
  419
  420lines([], _) --> [].
  421lines([H|T], Class) -->
  422    html(span(class(Class), H)),
  423    (   { T == [] }
  424    ->  []
  425    ;   html(br([])),
  426        lines(T, Class)
  427    ).
 send_html(+HTML) is det
Convert html//1 term into a string and send it to the client using pengine_output/1.
  434send_html(HTML) :-
  435    phrase(html(HTML), Tokens),
  436    with_output_to(string(HTMlString), print_html(Tokens)),
  437    pengine_output(HTMlString).
 pengine_module(-Module) is det
Module (used for resolving operators).
  444pengine_module(Module) :-
  445    pengine_self(Pengine),
  446    !,
  447    pengine_property(Pengine, module(Module)).
  448pengine_module(user).
  449
  450                 /*******************************
  451                 *        OUTPUT FORMAT         *
  452                 *******************************/
 pengines:event_to_json(+Event, -JSON, +Format, +VarNames) is semidet
Provide additional translations for Prolog terms to output. Defines formats are:
'json-s'
Simple or string format: Prolog terms are sent using quoted write.
'json-html'
Serialize responses as HTML string. This is intended for applications that emulate the Prolog toplevel. This format carries the following data:
data
List if answers, where each answer is an object with
variables
Array of objects, each describing a variable. These objects contain these fields:
  • variables: Array of strings holding variable names
  • value: HTML-ified value of the variables
  • substitutions: Array of objects for substitutions that break cycles holding:
    • var: Name of the inserted variable
    • value: HTML-ified value
residuals
Array of strings representing HTML-ified residual goals.
  481:- multifile
  482    pengines:event_to_json/3.
 pengines:event_to_json(+PrologEvent, -JSONEvent, +Format, +VarNames)
If Format equals 'json-s' or 'json-html', emit a simplified JSON representation of the data, suitable for notably SWISH. This deals with Prolog answers and output messages. If a message originates from print_message/3, it gets several additional properties:
message:Kind
Indicate the kind of the message (error, warning, etc.)
location:_219212{ch:CharPos, file:File, line:Line}
If the message is related to a source location, indicate the file and line and, if available, the character location.
  499pengines:event_to_json(success(ID, Answers0, Projection, Time, More), JSON,
  500                       'json-s') :-
  501    !,
  502    JSON0 = json{event:success, id:ID, time:Time, data:Answers, more:More},
  503    maplist(answer_to_json_strings(ID), Answers0, Answers),
  504    add_projection(Projection, JSON0, JSON).
  505pengines:event_to_json(output(ID, Term), JSON, 'json-s') :-
  506    !,
  507    map_output(ID, Term, JSON).
  508
  509add_projection([], JSON, JSON) :- !.
  510add_projection(VarNames, JSON0, JSON0.put(projection, VarNames)).
 answer_to_json_strings(+Pengine, +AnswerDictIn, -AnswerDict)
Translate answer dict with Prolog term values into answer dict with string values.
  518answer_to_json_strings(Pengine, DictIn, DictOut) :-
  519    dict_pairs(DictIn, Tag, Pairs),
  520    maplist(term_string_value(Pengine), Pairs, BindingsOut),
  521    dict_pairs(DictOut, Tag, BindingsOut).
  522
  523term_string_value(Pengine, N-V, N-A) :-
  524    with_output_to(string(A),
  525                   write_term(V,
  526                              [ module(Pengine),
  527                                quoted(true)
  528                              ])).
 pengines:event_to_json(+Event, -JSON, +Format, +VarNames)
Implement translation of a Pengine event to json-html format. This format represents the answer as JSON, but the variable bindings are (structured) HTML strings rather than JSON objects.

CHR residual goals are not bound to the projection variables. We hacked a bypass to fetch these by returning them in a variable named _residuals, which must be bound to a term '$residuals'(List). Such a variable is removed from the projection and added to residual goals.

  542pengines:event_to_json(success(ID, Answers0, Projection, Time, More),
  543                       JSON, 'json-html') :-
  544    !,
  545    JSON0 = json{event:success, id:ID, time:Time, data:Answers, more:More},
  546    maplist(map_answer(ID), Answers0, ResVars, Answers),
  547    add_projection(Projection, ResVars, JSON0, JSON).
  548pengines:event_to_json(output(ID, Term), JSON, 'json-html') :-
  549    !,
  550    map_output(ID, Term, JSON).
  551
  552map_answer(ID, Bindings0, ResVars, Answer) :-
  553    dict_bindings(Bindings0, Bindings1),
  554    select_residuals(Bindings1, Bindings2, ResVars, Residuals0, Clauses),
  555    append(Residuals0, Residuals1),
  556    prolog:translate_bindings(Bindings2, Bindings3, [], Residuals1,
  557                              ID:Residuals-_HiddenResiduals),
  558    maplist(binding_to_html(ID), Bindings3, VarBindings),
  559    final_answer(ID, VarBindings, Residuals, Clauses, Answer).
  560
  561final_answer(_Id, VarBindings, [], [], Answer) :-
  562    !,
  563    Answer = json{variables:VarBindings}.
  564final_answer(ID, VarBindings, Residuals, [], Answer) :-
  565    !,
  566    residuals_html(Residuals, ID, ResHTML),
  567    Answer = json{variables:VarBindings, residuals:ResHTML}.
  568final_answer(ID, VarBindings, [], Clauses, Answer) :-
  569    !,
  570    clauses_html(Clauses, ID, ClausesHTML),
  571    Answer = json{variables:VarBindings, wfs_residual_program:ClausesHTML}.
  572final_answer(ID, VarBindings, Residuals, Clauses, Answer) :-
  573    !,
  574    residuals_html(Residuals, ID, ResHTML),
  575    clauses_html(Clauses, ID, ClausesHTML),
  576    Answer = json{variables:VarBindings,
  577                  residuals:ResHTML,
  578                  wfs_residual_program:ClausesHTML}.
  579
  580residuals_html([], _, []).
  581residuals_html([H0|T0], Module, [H|T]) :-
  582    term_html_string(H0, [], Module, H, [priority(999)]),
  583    residuals_html(T0, Module, T).
  584
  585clauses_html(Clauses, _ID, HTMLString) :-
  586    with_output_to(string(Program), list_clauses(Clauses)),
  587    phrase(html(pre([class('wfs-residual-program')], Program)), Tokens),
  588    with_output_to(string(HTMLString), print_html(Tokens)).
  589
  590list_clauses([]).
  591list_clauses([H|T]) :-
  592    (   system_undefined(H)
  593    ->  true
  594    ;   portray_clause(H)
  595    ),
  596    list_clauses(T).
  597
  598system_undefined((undefined :- tnot(undefined))).
  599system_undefined((answer_count_restraint :- tnot(answer_count_restraint))).
  600system_undefined((radial_restraint :- tnot(radial_restraint))).
  601
  602dict_bindings(Dict, Bindings) :-
  603    dict_pairs(Dict, _Tag, Pairs),
  604    maplist([N-V,N=V]>>true, Pairs, Bindings).
  605
  606select_residuals([], [], [], [], []).
  607select_residuals([H|T], Bindings, Vars, Residuals, Clauses) :-
  608    binding_residual(H, Var, Residual),
  609    !,
  610    Vars = [Var|TV],
  611    Residuals = [Residual|TR],
  612    select_residuals(T, Bindings, TV, TR, Clauses).
  613select_residuals([H|T], Bindings, Vars, Residuals, Clauses) :-
  614    binding_residual_clauses(H, Var, Delays, Clauses0),
  615    !,
  616    Vars = [Var|TV],
  617    Residuals = [Delays|TR],
  618    append(Clauses0, CT, Clauses),
  619    select_residuals(T, Bindings, TV, TR, CT).
  620select_residuals([H|T0], [H|T], Vars, Residuals, Clauses) :-
  621    select_residuals(T0, T, Vars, Residuals, Clauses).
  622
  623binding_residual('_residuals' = '$residuals'(Residuals), '_residuals', Residuals) :-
  624    is_list(Residuals).
  625binding_residual('Residuals' = '$residuals'(Residuals), 'Residuals', Residuals) :-
  626    is_list(Residuals).
  627binding_residual('Residual'  = '$residual'(Residual),   'Residual', [Residual]) :-
  628    callable(Residual).
  629
  630binding_residual_clauses(
  631    '_wfs_residual_program' = '$wfs_residual_program'(Delays, Clauses),
  632    '_wfs_residual_program', Residuals, Clauses) :-
  633    phrase(delay_list(Delays), Residuals).
  634
  635delay_list(true) --> !.
  636delay_list((A,B)) --> !, delay_list(A), delay_list(B).
  637delay_list(M:A) --> !, [M:'$wfs_undefined'(A)].
  638delay_list(A) --> ['$wfs_undefined'(A)].
  639
  640add_projection(-, _, JSON, JSON) :- !.
  641add_projection(VarNames0, ResVars0, JSON0, JSON) :-
  642    append(ResVars0, ResVars1),
  643    sort(ResVars1, ResVars),
  644    subtract(VarNames0, ResVars, VarNames),
  645    add_projection(VarNames, JSON0, JSON).
 binding_to_html(+Pengine, +Binding, -Dict) is det
Convert a variable binding into a JSON Dict. Note that this code assumes that the module associated with Pengine has the same name as the Pengine. The module is needed to
Arguments:
Binding- is a term binding(Vars,Term,Substitutions)
  656binding_to_html(ID, binding(Vars,Term,Substitutions), JSON) :-
  657    JSON0 = json{variables:Vars, value:HTMLString},
  658    binding_write_options(ID, Options),
  659    term_html_string(Term, Vars, ID, HTMLString, Options),
  660    (   Substitutions == []
  661    ->  JSON = JSON0
  662    ;   maplist(subst_to_html(ID), Substitutions, HTMLSubst),
  663        JSON = JSON0.put(substitutions, HTMLSubst)
  664    ).
  665
  666binding_write_options(Pengine, Options) :-
  667    (   current_predicate(Pengine:screen_property/1),
  668        Pengine:screen_property(tabled(true))
  669    ->  Options = []
  670    ;   Options = [priority(699)]
  671    ).
 term_html_string(+Term, +VarNames, +Module, -HTMLString, +Options) is det
Translate Term into an HTML string using the operator declarations from Module. VarNames is a list of variable names that have this value.
  680term_html_string(Term, Vars, Module, HTMLString, Options) :-
  681    setting(write_options, WOptions),
  682    merge_options(WOptions,
  683                  [ quoted(true),
  684                    numbervars(true),
  685                    module(Module)
  686                  | Options
  687                  ], WriteOptions),
  688    phrase(term_html(Term, Vars, WriteOptions), Tokens),
  689    with_output_to(string(HTMLString), print_html(Tokens)).
 binding_term(+Term, +Vars, +WriteOptions)// is semidet
Hook to render a Prolog result term as HTML. This hook is called for each non-variable binding, passing the binding value as Term, the names of the variables as Vars and a list of options for write_term/3. If the hook fails, term//2 is called.
Arguments:
Vars- is a list of variable names or [] if Term is a residual goal.
  701:- multifile binding_term//3.  702
  703term_html(Term, Vars, WriteOptions) -->
  704    { nonvar(Term) },
  705    binding_term(Term, Vars, WriteOptions),
  706    !.
  707term_html(Undef, _Vars, WriteOptions) -->
  708    { nonvar(Undef),
  709      Undef = '$wfs_undefined'(Term),
  710      !
  711    },
  712    html(span(class(wfs_undefined), \term(Term, WriteOptions))).
  713term_html(Term, _Vars, WriteOptions) -->
  714    term(Term, WriteOptions).
 subst_to_html(+Module, +Binding, -JSON) is det
Render a variable substitution resulting from term factorization, in this case breaking a cycle.
  721subst_to_html(ID, '$VAR'(Name)=Value, json{var:Name, value:HTMLString}) :-
  722    !,
  723    binding_write_options(ID, Options),
  724    term_html_string(Value, [Name], ID, HTMLString, Options).
  725subst_to_html(_, Term, _) :-
  726    assertion(Term = '$VAR'(_)).
 map_output(+ID, +Term, -JSON) is det
Map an output term. This is the same for json-s and json-html.
  733map_output(ID, message(Term, Kind, HTMLString, Src), JSON) :-
  734    atomic(HTMLString),
  735    !,
  736    JSON0 = json{event:output, id:ID, message:Kind, data:HTMLString},
  737    pengines:add_error_details(Term, JSON0, JSON1),
  738    (   Src = File:Line,
  739        \+ JSON1.get(location) = _
  740    ->  JSON = JSON1.put(_{location:_{file:File, line:Line}})
  741    ;   JSON = JSON1
  742    ).
  743map_output(ID, Term, json{event:output, id:ID, data:Data}) :-
  744    (   atomic(Term)
  745    ->  Data = Term
  746    ;   is_dict(Term, json),
  747        ground(json)                % TBD: Check proper JSON object?
  748    ->  Data = Term
  749    ;   term_string(Term, Data)
  750    ).
 prolog_help:show_html_hook(+HTML)
Hook into help/1 to render the help output in the SWISH console.
  757:- multifile
  758    prolog_help:show_html_hook/1.  759
  760prolog_help:show_html_hook(HTML) :-
  761    pengine_output,
  762    pengine_output(HTML).
  763
  764
  765                 /*******************************
  766                 *          SANDBOXING          *
  767                 *******************************/
  768
  769:- multifile
  770    sandbox:safe_primitive/1,       % Goal
  771    sandbox:safe_meta/2.            % Goal, Called
  772
  773sandbox:safe_primitive(pengines_io:pengine_listing(_)).
  774sandbox:safe_primitive(pengines_io:pengine_nl).
  775sandbox:safe_primitive(pengines_io:pengine_tab(_)).
  776sandbox:safe_primitive(pengines_io:pengine_flush_output).
  777sandbox:safe_primitive(pengines_io:pengine_print(_)).
  778sandbox:safe_primitive(pengines_io:pengine_write(_)).
  779sandbox:safe_primitive(pengines_io:pengine_read(_)).
  780sandbox:safe_primitive(pengines_io:pengine_read_line_to_string(_,_)).
  781sandbox:safe_primitive(pengines_io:pengine_read_line_to_codes(_,_)).
  782sandbox:safe_primitive(pengines_io:pengine_write_canonical(_)).
  783sandbox:safe_primitive(pengines_io:pengine_write_term(_,_)).
  784sandbox:safe_primitive(pengines_io:pengine_writeln(_)).
  785sandbox:safe_primitive(pengines_io:pengine_writeq(_)).
  786sandbox:safe_primitive(pengines_io:pengine_portray_clause(_)).
  787sandbox:safe_primitive(system:write_term(_,_)).
  788sandbox:safe_primitive(system:prompt(_,_)).
  789sandbox:safe_primitive(system:statistics(_,_)).
  790sandbox:safe_primitive(system:put_code(_)).
  791sandbox:safe_primitive(system:put_char(_)).
  792
  793sandbox:safe_meta(pengines_io:pengine_format(Format, Args), Calls) :-
  794    sandbox:format_calls(Format, Args, Calls).
  795
  796
  797                 /*******************************
  798                 *         REDEFINITION         *
  799                 *******************************/
 pengine_io_predicate(?Head)
True when Head describes the head of a (system) IO predicate that is redefined by the HTML binding.
  806pengine_io_predicate(writeln(_)).
  807pengine_io_predicate(nl).
  808pengine_io_predicate(tab(_)).
  809pengine_io_predicate(flush_output).
  810pengine_io_predicate(format(_)).
  811pengine_io_predicate(format(_,_)).
  812pengine_io_predicate(read(_)).
  813pengine_io_predicate(read_line_to_string(_,_)).
  814pengine_io_predicate(read_line_to_codes(_,_)).
  815pengine_io_predicate(write_term(_,_)).
  816pengine_io_predicate(write(_)).
  817pengine_io_predicate(writeq(_)).
  818pengine_io_predicate(display(_)).
  819pengine_io_predicate(print(_)).
  820pengine_io_predicate(write_canonical(_)).
  821pengine_io_predicate(listing).
  822pengine_io_predicate(listing(_)).
  823pengine_io_predicate(portray_clause(_)).
  824
  825term_expansion(pengine_io_goal_expansion(_,_),
  826               Clauses) :-
  827    findall(Clause, io_mapping(Clause), Clauses).
  828
  829io_mapping(pengine_io_goal_expansion(Head, Mapped)) :-
  830    pengine_io_predicate(Head),
  831    Head =.. [Name|Args],
  832    atom_concat(pengine_, Name, BodyName),
  833    Mapped =.. [BodyName|Args].
  834
  835pengine_io_goal_expansion(_, _).
  836
  837
  838                 /*******************************
  839                 *      REBIND PENGINE I/O      *
  840                 *******************************/
  841
  842:- public
  843    stream_write/2,
  844    stream_read/2,
  845    stream_close/1.  846
  847:- thread_local
  848    pengine_io/2.  849
  850stream_write(Stream, Out) :-
  851    (   pengine_io(_,_)
  852    ->  send_html(pre(class(console), Out))
  853    ;   current_prolog_flag(pengine_main_thread, TID),
  854        thread_signal(TID, stream_write(Stream, Out))
  855    ).
  856stream_read(Stream, Data) :-
  857    (   pengine_io(_,_)
  858    ->  prompt(Prompt, Prompt),
  859        pengine_input(_{type:console, prompt:Prompt}, Data)
  860    ;   current_prolog_flag(pengine_main_thread, TID),
  861        call_in_thread(TID, stream_read(Stream, Data))
  862    ).
  863stream_close(_Stream).
 pengine_bind_user_streams
Bind the pengine user I/O streams to a Prolog stream that redirects the input and output to pengine_input/2 and pengine_output/1. This results in less pretty behaviour then redefining the I/O predicates to produce nice HTML, but does provide functioning I/O from included libraries.
  873pengine_bind_user_streams :-
  874    Err = Out,
  875    open_prolog_stream(pengines_io, write, Out, []),
  876    set_stream(Out, buffer(line)),
  877    open_prolog_stream(pengines_io, read,  In, []),
  878    set_stream(In,  alias(user_input)),
  879    set_stream(Out, alias(user_output)),
  880    set_stream(Err, alias(user_error)),
  881    set_stream(In,  alias(current_input)),
  882    set_stream(Out, alias(current_output)),
  883    assertz(pengine_io(In, Out)),
  884    thread_self(Me),
  885    thread_property(Me, id(Id)),
  886    set_prolog_flag(pengine_main_thread, Id),
  887    thread_at_exit(close_io).
  888
  889close_io :-
  890    retract(pengine_io(In, Out)),
  891    !,
  892    close(In, [force(true)]),
  893    close(Out, [force(true)]).
  894close_io.
 pengine_output is semidet
 pengine_input is semidet
True when output (input) is redirected to a pengine.
  901pengine_output :-
  902    current_output(Out),
  903    pengine_io(_, Out).
  904
  905pengine_input :-
  906    current_input(In),
  907    pengine_io(In, _).
 pengine_bind_io_to_html(+Module)
Redefine the built-in predicates for IO to send HTML messages using pengine_output/1.
  915pengine_bind_io_to_html(Module) :-
  916    forall(pengine_io_predicate(Head),
  917           bind_io(Head, Module)),
  918    pengine_bind_user_streams.
  919
  920bind_io(Head, Module) :-
  921    prompt(_, ''),
  922    redefine_system_predicate(Module:Head),
  923    functor(Head, Name, Arity),
  924    Head =.. [Name|Args],
  925    atom_concat(pengine_, Name, BodyName),
  926    Body =.. [BodyName|Args],
  927    assertz(Module:(Head :- Body)),
  928    compile_predicates([Module:Name/Arity])