View source with formatted comments or as raw
    1/*  Part of sCASP
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    Copyright (c)  2021, SWI-Prolog Solutions b.v.
    6    All rights reserved.
    7
    8    Redistribution and use in source and binary forms, with or without
    9    modification, are permitted provided that the following conditions
   10    are met:
   11
   12    1. Redistributions of source code must retain the above copyright
   13       notice, this list of conditions and the following disclaimer.
   14
   15    2. Redistributions in binary form must reproduce the above copyright
   16       notice, this list of conditions and the following disclaimer in
   17       the documentation and/or other materials provided with the
   18       distribution.
   19
   20    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   21    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   22    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   23    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   24    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   25    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   26    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   27    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   28    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   29    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   30    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   31    POSSIBILITY OF SUCH DAMAGE.
   32*/
   33
   34:- use_module(library(scasp)).   35:- use_module(library(scasp/html)).   36:- use_module(library(scasp/output)).   37:- use_module(library(scasp/json)).   38
   39:- use_module(library(http/http_server)).   40:- use_module(library(http/html_write)).   41:- use_module(library(http/js_write)).   42:- use_module(library(http/html_head)).   43:- use_module(library(http/http_path)).   44:- use_module(library(http/http_error)).   45:- use_module(library(http/jquery)).   46:- use_module(library(http/http_dispatch)).   47:- use_module(library(dcg/high_order)).   48:- use_module(library(http/term_html)).   49:- use_module(library(http/http_json)).   50:- use_module(library(http/http_client)).   51:- use_module(library(http/http_host)).   52
   53% Become Unix daemon process when on Unix and not attached to a
   54% terminal.  Otherwise be a normal commandline application.
   55
   56:- if((current_prolog_flag(unix,true),
   57       \+ stream_property(current_input, tty(true)))).   58
   59:- format(user_error, '% Loading as Unix daemon~n', []).   60:- use_module(library(http/http_unix_daemon)).   61
   62:- else.   63
   64:- initialization(main,main).   65
   66main(Argv) :-
   67    argv_options(Argv, _, Options),
   68    (   select_option(examples(Ex), Options, Options1)
   69    ->  attach_examples(Ex)
   70    ;   Options1 = Options
   71    ),
   72    server(Options1),
   73    (   option(interactive(true), Options1)
   74    ->  cli_enable_development_system
   75    ;   thread_get_message(quit)  % do not terminate
   76    ).
   77
   78opt_type(port,     port,        natural).
   79opt_type(p,        port,        natural).
   80opt_type(examples, examples,    atom).
   81opt_type(i,        interactive, boolean).
   82
   83opt_help(port,        "Port used by the server").
   84opt_help(interactive, "Start interactive toplevel").
   85opt_help(examples,    "':' separated list of files and directories from \c
   86                       which to load examples").
   87
   88opt_meta(port,     'PORT').
   89opt_meta(examples, 'DIRECTORY').
   90
   91%!  server(+Options)
   92%
   93%   Start HTTP server at the indicated port.
   94
   95server(Options) :-
   96    (   option(port(_), Options)
   97    ->  http_server(Options)
   98    ;   http_server([port(8080)|Options])
   99    ).
  100:- endif.  101
  102:- multifile http:location/3.  103:- dynamic   http:location/3.  104
  105http:location(scasp, root(scasp), []).
  106http:location(js,    scasp(js),   []).
  107http:location(css,   scasp(css),  []).
  108
  109:- http_handler(root(.),        http_redirect(see_other, scasp(.)), []).  110:- http_handler(scasp(.),       home,         []).  111:- http_handler(scasp(solve),   solve,        [id(solve)]).  112:- http_handler(scasp(help),    scasp_help,   [id(help)]).  113:- http_handler(scasp(example), example_data, [id(example_data)]).  114
  115		 /*******************************
  116		 *        HTML FORM PAGE	*
  117		 *******************************/
  118
  119%!  home(+Request)
  120%
  121%   HTML handler to generate the home page.   This  is a simple form the
  122%   can send requests and display the result.
  123
  124home(_Request) :-
  125    http_link_to_id(help, [], Help),
  126    reply_html_page([ title('s(CASP) web server')
  127                    ],
  128                    [ h1(['s(CASP) web server ', a(href(Help), 'Help')]),
  129                      \query_page
  130                    ]).
  131
  132query_page -->
  133    html_requires(jquery),
  134    html_requires(scasp),
  135    styles,
  136    html([ div(class(section),
  137               [ div(class(select), \example_data),
  138                 textarea([id(data), rows(10), cols(80),
  139                           placeholder('s(CASP) program')], '')
  140               ]),
  141           h4('Query'),
  142           '?- ',     input([id(query), size(50),
  143                             placeholder('Query')]),
  144           ' Limit ', input([type(number), min(1), id(limit), value(1),
  145                             size(3),
  146                             placeholder('Empty means all answer sets')]),
  147           \html_json_radio,
  148           h4(''),
  149           button(id(solve), 'Solve'),
  150           button(id(clear), 'Clear'),
  151           div(id('results-html'), []),
  152           pre(id('results-json'), [])
  153         ]),
  154    button_actions.
  155
  156%!  example_data
  157%
  158%   Fill the example data
  159
  160example_data -->
  161    { \+ example(_,_,_) },
  162    !.
  163example_data -->
  164    { findall(Id-Comment, example(Id,_File,Comment), Pairs),
  165      http_link_to_id(example_data, [], ExDataURL)
  166    },
  167    html([ select([name(example), id(example)],
  168                  \sequence(example_option, Pairs))
  169         ]),
  170    js_script({|javascript(ExDataURL)||
  171$(function() {
  172$("#example").change(function() {
  173  var ex = $(this).val();
  174
  175  $.get(ExDataURL,
  176        {"example": ex},
  177        function(reply) {
  178            var rows = reply.data.split("\n").length + 1;
  179            if ( rows > 20 ) rows = 20;
  180            $("#data").val(reply.data)
  181                      .attr("rows", rows);
  182            $("#query").val(reply.query || "");
  183        });
  184});
  185
  186});
  187               |}).
  188
  189example_option(Id-Comment) -->
  190    html(option(value(Id), Comment)).
  191
  192example_data(Request) :-
  193    http_parameters(Request, [example(Id, [atom])]),
  194    example(Id, File, Comment),
  195    read_file_to_string(File, Data, []),
  196    Reply0 = _{comment:Comment, data:Data},
  197    (   re_matchsub('^\\?-\\s*(.*?)\\.($|\\s)'/m, Data, Dict, [])
  198    ->  Query = Dict.1,
  199        Reply = Reply0.put(query, Query)
  200    ;   Reply = Reply0
  201    ),
  202    reply_json(Reply).
  203
  204styles -->
  205    html({|html||
  206<style>
  207div.html-json-switch { margin-top: 10px; margin-left: 2ex; }
  208</style>
  209         |}).
  210
  211
  212html_json_radio -->
  213    html(div(class('html-json-switch'),
  214             [ 'Display results as: ',
  215               input([type(radio), id(rhtml), name(format), value(html),
  216                      checked(checked)]),
  217               label(for(rhtml), 'HTML'),
  218               input([type(radio), id(rjson), name(format), value(json)]),
  219               label(for(rjson), 'JSON')
  220             ])).
  221
  222
  223button_actions -->
  224    { http_link_to_id(solve, [], SolveURL) },
  225    js_script({|javascript(SolveURL)||
  226$(function() {
  227
  228$("#solve").on("click", function() {
  229  var data = $("#data").val();
  230  var query = $("#query").val();
  231  var limit = $("#limit").val();
  232  var format = $('input[type="radio"][name="format"]:checked').val();
  233  $("#results-html").empty();
  234  $("#results-json").empty();
  235  $.get(SolveURL,
  236        { data: data,
  237          query: query,
  238          limit: limit,
  239          format: format
  240        },
  241        function(reply) {
  242
  243          if ( typeof(reply) == "string" ) {
  244            var results = $("#results-html");
  245            results.html(reply);
  246            results.sCASP('swish_answer');
  247          } else if ( typeof(reply) == "object" ) {
  248            var results = $("#results-json");
  249            results.text(JSON.stringify(reply, undefined, 2));
  250          }
  251        })
  252  .fail(function(error) {
  253    if ( error.responseJSON ) {
  254      $("#results").text(error.responseJSON.message);
  255    } else {
  256      console.log(error);
  257    }
  258  });
  259});
  260
  261$("#clear").on("click", function() {
  262  $("#results").empty();
  263});
  264
  265});
  266              |}).
  267
  268
  269%!  solve(+Request)
  270%
  271%   Service is s(CASP) request. See the  help   page  of  the server for
  272%   details.
  273
  274solve(Request) :-
  275    option(method(post), Request),
  276    option(content_type(Type), Request),
  277    sub_atom(Type, 0, _, _, 'text/x-scasp'),
  278    !,
  279    http_read_data(Request, Data, [to(string)]),
  280    solve(Request, #{ data:Data, format:json }).
  281solve(Request) :-
  282    option(method(post), Request),
  283    option(content_type(Type), Request),
  284    sub_atom(Type, 0, _, _, 'application/json'),
  285    !,
  286    http_read_json(Request, Dict0, [json_object(dict)]),
  287    foldl(to_atom, [format], Dict0, Dict1),
  288    foldl(dict_default, [format(json)], Dict1, Dict),
  289    solve(Request, Dict).
  290solve(Request) :-
  291    http_parameters(Request,
  292                    [ data(Data, []),
  293                      query(QueryS, [optional(true)]),
  294                      limit(Limit, [optional(true), integer]),
  295                      format(Format, [default(html)]),
  296                      tree(Tree, [boolean,optional(true)])
  297                    ]),
  298    solve(Request,
  299          #{ data:Data,
  300             query:QueryS,
  301             limit:Limit,
  302             format:Format,
  303             tree:Tree}).
  304
  305to_atom(Key, Dict0, Dict) :-
  306    (   Value = Dict0.get(Key)
  307    ->  atom_string(Atom, Value),
  308        Dict = Dict0.put(Key, Atom)
  309    ;   Dict = Dict0
  310    ).
  311
  312dict_default(Term, Dict0, Dict) :-
  313    Term =.. [Name,Value],
  314    (   _ = Dict0.get(Name)
  315    ->  Dict = Dict0
  316    ;   Dict = Dict0.put(Name, Value)
  317    ).
  318
  319%!  solve(+Request, +Dict)
  320%
  321%   The actual solver.
  322
  323solve(_Request, Dict) :-
  324    _{ data:Data,
  325       query:QueryS,
  326       limit:Limit,
  327       format:Format,
  328       tree:Tree } >:< Dict,
  329
  330    ignore(Limit = infinite),
  331    solve_options(Format, Tree, Options),
  332
  333    % prepare the program
  334    Error = error(Formal,_),
  335    catch(( setup_call_cleanup(
  336                open_string(Data, In),
  337                read_terms(In, Terms0),
  338                close(In)),
  339            query(QueryS, Query, Bindings, Terms0, Terms)
  340          ),
  341          Error,
  342          true),
  343
  344    % On error, display the error.  Else solve the query.
  345    (   nonvar(Formal)
  346    ->  reply_html_page([],
  347                        \error(Error))
  348    ;   in_temporary_module(
  349            M,
  350            ( use_module(library(scasp)),
  351              maplist(add_to_program(M), Terms)
  352            ),
  353            ( call_time(findall(Result,
  354                                scasp_result(M:Query, Bindings, Result, Options),
  355                                Results),
  356                        TotalTime),
  357              ovar_set_bindings(Bindings),
  358              ovar_analyze_term(Query, []),
  359              inline_constraints(Query, []),
  360              reply(Format, M:Query, TotalTime, Results)
  361            ))
  362    ).
  363
  364error(Error) -->
  365    { message_to_string(Error, Message) },
  366    html(div(class(error), Message)).
  367
  368solve_options(html, _, Options) =>
  369    Options = [tree(true), unknown(fail)].
  370solve_options(json, Tree, Options) =>
  371    ignore(Tree=true),
  372    Options = [tree(Tree), unknown(fail), inline_constraints(false)].
  373
  374%!  read_terms(+In:stream, -Terms) is det.
  375%
  376%   Read the program terms and (optional) query from In.
  377
  378read_terms(In, Terms) :-
  379    read_one_term(In, Term0, Pos0),
  380    read_terms(Term0, Pos0, In, Terms).
  381
  382read_terms(end_of_file, _, _, []) :-
  383    !.
  384read_terms(Term0, Pos0, In, [Term0-Pos0|T]) :-
  385    read_one_term(In, Term, Pos),
  386    read_terms(Term, Pos, In, T).
  387
  388read_one_term(In, Term, Pos) :-
  389    read_term(In, Term,
  390              [ module(scasp_dyncall),
  391                variable_names(Bindings),
  392                term_position(Pos)
  393              ]),
  394    fixup_pred(Term, Bindings).
  395
  396%!  fixup_pred(+Term, +Bindings) is det.
  397%
  398%   If Term is a `pred` directive, bind the variables in the atom
  399%   to '$VAR'(Name) if possible.
  400
  401fixup_pred((#pred Atom :: _Template), Bindings) =>
  402    fixup_atom(Atom, Bindings).
  403fixup_pred((:-pred Atom :: _Template), Bindings) =>
  404    fixup_atom(Atom, Bindings).
  405fixup_pred((?- Query), Bindings) =>
  406    fixup_atom(Query, Bindings).
  407fixup_pred(_, _) =>
  408    true.
  409
  410fixup_atom(Var, Bindings), var(Var) =>
  411    (   member(Name = V, Bindings),
  412        V == Var
  413    ->  Var = '$VAR'(Name)
  414    ;   true
  415    ).
  416fixup_atom(Term, Bindings), compound(Term) =>
  417    compound_name_arguments(Term, _Name, Args),
  418    maplist(fixup_atom_r(Bindings), Args).
  419fixup_atom(_, _) =>
  420    true.
  421
  422fixup_atom_r(Bindings, Atom) :-
  423    fixup_atom(Atom, Bindings).
  424
  425%!  add_to_program(+Module, +Term)
  426%
  427%   Add clauses to the program.  Also handles s(CASP) directives.
  428
  429add_to_program(M, (# Directive)-Pos) =>
  430    #(M:Directive, Pos).
  431add_to_program(M, (:- show Spec)-_) =>
  432    show(M:Spec).
  433add_to_program(M, (:- pred Spec)-_) =>
  434    pred(M:Spec).
  435add_to_program(M, (:- abducible Spec)-Pos) =>
  436    abducible(M:Spec, Pos).
  437add_to_program(M, Term-Pos) =>
  438    scasp_assert(M:Term, Pos).
  439
  440%!  query(+String, -Query, -Bindings, +ProgramIn, -ProgramOut) is det.
  441
  442query(QueryS, Query, Bindings, Terms, Terms) :-
  443    atomic(QueryS), QueryS \== '',
  444    !,
  445    term_string(Query, QueryS, [variable_names(Bindings)]).
  446query(_, Query, Bindings, TermsIn, TermsOut) :-
  447    partition(is_query, TermsIn, Queries, TermsOut),
  448    last(Queries, (?- Query0)-_),
  449    !,
  450    varnumbers_names(Query0, Query, Bindings).
  451query(_, _, _, _, _) :-
  452    existence_error(scasp_query, program).
  453
  454is_query((?-_)-_) => true.
  455is_query(_) => false.
  456
  457%!  scasp_result(+Query, +Bindings, -Dict, +Options) is nondet.
  458%
  459%   True when we succeeded proving Query.  Dict contains details such as
  460%   the model and justification tree.
  461
  462scasp_result(Query,
  463             Bindings,
  464             scasp{ query:Query,
  465                    answer:Counter,
  466                    bindings:Bindings,
  467                    model:Model,
  468                    tree:Tree,
  469                    time:Time
  470                  },
  471             Options) :-
  472    option(tree(true), Options),
  473    !,
  474    Query = M:_,
  475    call_nth(call_time(scasp(Query,
  476                             [ model(M:Model),
  477                               tree(M:Tree)
  478                             | Options
  479                             ]),
  480                       Time), Counter),
  481    analyze_variables(t(Bindings, Model, Tree), Bindings, Options).
  482scasp_result(Query,
  483             Bindings,
  484             scasp{ query:Query,
  485                    answer:Counter,
  486                    bindings:Bindings,
  487                    model:Model,
  488                    time:Time
  489                  },
  490             Options) :-
  491    Query = M:_,
  492    call_nth(call_time(scasp(Query,
  493                             [ model(M:Model)
  494                             | Options
  495                             ]), Time),
  496             Counter),
  497    analyze_variables(t(Bindings, Model), Bindings, Options).
  498
  499analyze_variables(Term, Bindings, Options) :-
  500    ovar_set_bindings(Bindings),
  501    (   option(inline_constraints(false), Options)
  502    ->  ovar_analyze_term(Term, [name_constraints(true)])
  503    ;   ovar_analyze_term(Term, []),
  504        inline_constraints(Term, [])
  505    ).
  506
  507%!  reply(+Format, :Query, +TotalTime, +Results)
  508%
  509%   Reply in either `html` or `json` format.
  510
  511reply(html, M:_Query, TotalTime, Results) =>
  512    reply_html_page([],
  513                    \results(Results, M, TotalTime)).
  514reply(json, Query, TotalTime, Results) =>
  515    scasp_results_json(#{query:Query,
  516                         answers:Results,
  517                         cpu:TotalTime},
  518                       JSON),
  519    reply_json_dict(JSON, []).
  520
  521
  522		 /*******************************
  523		 *        HTML GENERATION	*
  524		 *******************************/
  525
  526results([], _, Time) -->
  527    !,
  528    html(h3('No models (~3f sec)'-[Time.cpu])).
  529results(Results, M, _Time) -->
  530    sequence(result(M), Results).
  531
  532result(M, Result) -->
  533    html(div(class(result),
  534             [ h3('Result #~D (~3f sec)'-[Result.answer, Result.time.cpu]),
  535               \binding_section(Result.bindings),
  536               \html_model(M:Result.model, [class('collapsable-content')]),
  537               \justification_section(M:Result)
  538             ])).
  539
  540%!  binding_section(+Bindings)//
  541
  542binding_section([]) -->
  543    !.
  544binding_section(Bindings) -->
  545    { copy_term(Bindings, Bindings1, Constraints0),
  546      var_names(Constraints0, Constraints1),
  547      exclude(no_op_binding, Bindings1, Bindings2)
  548    },
  549    html(div(class(bindings),
  550             [ h4('Bindings'),
  551               \bindings(Bindings2, Constraints1)
  552             ])).
  553
  554var_names([], []).
  555var_names([H|T0], T) :-
  556    var_name(H),
  557    !,
  558    var_names(T0, T).
  559var_names([H|T0], [H|T]) :-
  560    var_names(T0, T).
  561
  562var_name(put_attr(Var, scasp_output, name(Name))) :-
  563    Var = '$VAR'(Name).
  564var_name(put_attr(Var, scasp_output, singleton)) :-
  565    Var = '$VAR'('_').
  566
  567no_op_binding(Name = '$VAR'(Name)) => true.
  568no_op_binding(_) => false.
  569
  570
  571%!  bindings(+Bindings, +Constraints)//
  572%
  573%   Report on the bindings.
  574
  575bindings([], []) -->
  576    !.
  577bindings([], Constraints) -->
  578    !,
  579    constraints(Constraints).
  580bindings([H|T], Constraints) -->
  581    (   {T == [], Constraints == []}
  582    ->  binding(H, '')
  583    ;   binding(H, ',')
  584    ),
  585    bindings(T, Constraints).
  586
  587binding(Name=Value, End) -->
  588    html(div(class(binding),
  589             [ var(Name),
  590               ' = ',
  591               \term(Value),
  592               End
  593             ])).
  594
  595constraints([]) -->
  596    [].
  597constraints([H|T]) -->
  598    (   {T==[]}
  599    ->  constraint(H, '')
  600    ;   constraint(H, ','),
  601        constraints(T)
  602    ).
  603
  604constraint(H, End) -->
  605    html(div(class(constraint),
  606             [ \constraint(H),
  607               End
  608             ])).
  609
  610constraint('\u2209'(V,S)) -->
  611    !,
  612    html([ \term(V), ' \u2209 ', \term(S) ]).
  613constraint({ClpQ}) -->
  614    !,
  615    { comma_list(ClpQ, List),
  616      maplist(to_clpq, List, Constraints)
  617    },
  618    constraints(Constraints).
  619constraint(C) -->
  620    term(C).
  621
  622to_clpq(A > B,   C) => C = (A #>  B).
  623to_clpq(A < B,   C) => C = (A #<  B).
  624to_clpq(A >= B,  C) => C = (A #>= B).
  625to_clpq(A =< B,  C) => C = (A #=< B).
  626to_clpq(A = B,   C) => C = (A #=  B).
  627to_clpq(A =\= B, C) => C = (A #<> B).
  628to_clpq(X, Y)       => Y = X.
  629
  630term(T) -->
  631    term(T,
  632         [ quoted(true),
  633           numbervars(true)
  634         ]).
  635
  636justification_section(M:Results) -->
  637    { Tree = Results.get(tree) },
  638    !,
  639    html_justification_tree(M:Tree, []).
  640justification_section(_) -->
  641    [].
  642
  643
  644		 /*******************************
  645		 *             HELP		*
  646		 *******************************/
  647
  648scasp_help(Request) :-
  649    reply_html_page([ title('s(CASP) web server -- help')
  650                    ],
  651                    [ h1(['The s(CASP) web server']),
  652                      \help_page(Request)
  653                    ]).
  654
  655help_page(Request) -->
  656    { http_public_host_url(Request, Home),
  657      http_link_to_id(solve, [], URL),
  658      atom_concat(Home, URL, Solve)
  659    },
  660    html({|html(Solve)||
  661<h2>Diclaimer</h2>
  662
  663<blockquote>
  664This is a <b>demonstrator</b>.  This service is likely to change functionality and location.
  665This service runs on <a href="https://www.swi-prolog.org">SWI-Prolog</a> using the
  666<a href="https://github.com/JanWielemaker/sCASP">[GitHub] SWI-Prolog port of s(CASP)</a>.  Bugs
  667should be reported at the issue tracker of the the GIT repository.  Discussion can use the
  668<a href="https://swi-prolog.discourse.group/">SWI-Prolog forum</a>
  669
  670</blockquote>
  671
  672<h2>About</h2>
  673
  674<p>The <code>s(CASP)</code> system is a top-down interpreter for ASP programs with
  675constraints.</p>
  676
  677<p>This work was presented at ICLP'18 (<a href="https://www.cambridge.org/core/journals/theory-and-practice-of-logic-programming/article/constraint-answer-set-programming-without-grounding/55A678C618EF54487777F021D89B3FE7">Arias et al. 2018</a>), also available <a href="https://arxiv.org/abs/1804.11162">here</a>.</p>
  678
  679<p>And extended description of the justification trees was presented at ICLP'20 (<a href="http://www.cliplab.org/papers/sCASP-ICLP2020/TC-explainCASP.pdf">Arias et al. 2020</a>).</p>
  680
  681<p><code>s(CASP)</code> by <a href="mailto:joaquin.arias@urjc.es">Joaquin Arias</a>, is based on
  682<a href="https://sourceforge.net/projects/sasp-system/"><code>s(ASP)</code></a> by
  683Kyle Marple.</p>
  684
  685<p><code>s(CASP)</code> is an implementation of the stable model semantics of
  686constraint logic programming. Unlike similar systems, it does not
  687employ any form of grounding. This allows <code>s(CASP)</code> to execute programs
  688that are not finitely groundable, including those which make use of
  689lists and terms.</p>
  690
  691<h2>Using as a service</h2>
  692
  693<p>
  694The server accepts data requests on <span>Solve</span>.  It deals with several
  695formats:
  696</p>
  697
  698  <ul>
  699    <li>POST using <code>Content-type: text/x-scasp</code><br>
  700        In this case the document is plain text containing both the
  701        program and the query as <code>?- Query</code>.  Using <code>curl</code>
  702        we can use this script:
  703
  704        <pre>
  705        curl --data-binary @file.pl -H "Content-Type: text/x-scasp" -X POST <code>Solve</code>
  706        </pre>
  707    </li><li>POST using <code>Content-type: application/json</code><br>
  708        In this case the request is a JSON document containing these keys:
  709        <ul>
  710          <li> <code>data</code><br>
  711               This key is required and contains the program.
  712          </li><li><code>query</code><br>
  713               This key is required if the program does not contain a query.  If
  714               the program contains a query, the specified query in the JSON
  715               request is used, <b>not</b> the one in the program.
  716          </li><li><code>limit</code><br>
  717               Max number of answers to return.  Default is all.
  718          </li><li><code>format</code><br>
  719               One of <code>html</code> or <code>json</code>.  Default is <code>json</code>.
  720          </li><li><code>tree</code><br>
  721               Boolean that indicates whether the justification is produced.  Default
  722               <code>true</code>.
  723          </li>
  724        </ul>
  725    </li><li>POST using <code>Content-type: application/x-www-form-urlencoded</code><br>
  726        As above.  The default reply format is <code>html</code>.
  727    </li><li>GET<br>
  728        Interpret the query parameters as above.  The default reply format is <code>html</code>.
  729    </li>
  730  </ul>
  731         |}).
  732
  733
  734		 /*******************************
  735		 *           EXAMPLES		*
  736		 *******************************/
  737
  738:- dynamic
  739    example/3.  740
  741attach_examples(Spec) :-
  742    atomic_list_concat(Ex, ':', Spec),
  743    maplist(attach_example, Ex).
  744
  745attach_example(Dir) :-
  746    exists_directory(Dir),
  747    !,
  748    forall(directory_member(Dir, File,
  749                            [ recursive(true),
  750                              extensions([pl]),
  751                              access(read)
  752                            ]),
  753           attach_ex(File)).
  754attach_example(File) :-
  755    exists_file(File),
  756    !,
  757    attach_ex(File).
  758attach_example(File) :-
  759    print_message(warning, error(existence_error(file, File),_)).
  760
  761attach_ex(File) :-
  762    variant_sha1(File, Id),
  763    ex_comment(File, Comment),
  764    assertz(example(Id, File, Comment)).
  765
  766
  767ex_comment(File, Comment) :-
  768    setup_call_cleanup(
  769        open(File, read, In),
  770        ( read_line_to_string(In, Line),
  771          sub_atom(Line, 0, _, _, '%'),
  772          sub_atom(Line, 1, _, 0, Comment0),
  773          split_string(Comment0, "", " \t%", [Title])
  774        ),
  775        close(In)),
  776    !,
  777    file_base_name(File, Base),
  778    format(string(Comment), '~w -- ~w', [Base, Title]).
  779ex_comment(File, Comment) :-
  780    file_base_name(File, Comment)