1% * -*- Mode: Prolog -*- */

emulate builtins

This module allows you to write programs that can be executed on a triplestore or directly on the in-memory SWI-Prolog rdf database.

Sparqlprog defines predicates such as rdf_path/3 and str_starts/2. These are usually compiled down to SPARQL queries. This module provides prolog implementations for these predicates using predicates such as rdf/3

Many of the predicates here take an argument strlike - this can be either an atom or a string.

*/

   15:- module(emulate_builtins,
   16          [
   17           ensure_atom/2,
   18           ensure_string/2,
   19           ensure_atoms/2,
   20           lcase/2,
   21           regex/2,
   22           regex/3,
   23           str_starts/2,
   24           str_ends/2,
   25           str_before/3,
   26           str_after/3,
   27
   28           str_replace/4,
   29           concat/3,
   30           concat/4,
   31
   32           agg_max/2,
   33           count/2,
   34           group_concat/3,
   35           aggregate_group/4,
   36
   37           iri_prefix_curie/3,
   38           iri_prefix/2,
   39           iri_curie/2,
   40           curie_prefix/2,
   41
   42           optional/1,
   43           rdf_path/3,
   44           rdf_path/4,
   45
   46           if/4,
   47           
   48           bind/2,
   49           seval/2,
   50           eval_to_atom/2
   51           ]).   52
   53:- use_module(library(semweb/rdf11)).   54:- use_module(library(pcre)).   55
   56
   57/*
   58https://www.w3.org/TR/sparql11-query/#expressions
   59
   60*/
   61
   62
   63/* ----------
   64 https://www.w3.org/TR/sparql11-query/#func-strings
   65
   66 all string functions take as arguments either:
   67
   68   - prolog string
   69   - prolog atom (converted to strong)
   70   - prolog rdf11 literal: either String@Lang or String^^xsd:string
   71   
   72*/
 lcase(+S:strlike, ?V:str)
   78lcase(S,V) :-
   79        ensure_atom(S,S1),
   80        downcase_atom(S1,V1),
   81        ensure_string(V1,V).
 ucase(+S:strlike, ?V:str)
   83ucase(S,V) :-
   84        ensure_atom(S,S1),
   85        upcase_atom(S1,V1),
   86        ensure_string(V1,V).
   87
   88% 17.4.2.5 str
   89str(X,V) :-
   90        ensure_atom(X,A),
   91        atom_string(A,V).
   92
   93% 17.4.2.6 lang
   94lang(_^^L,L).
   95
   96%17.4.2.7 datatype
   97datatype(_^^D, D) :- !.
   98datatype(_ @ _, xsd:string) :- !.
   99datatype(_, xsd:string) :- !.
  100
  101
  102% 17.4.2.8 iri
  103iri(X,V) :-
  104        ensure_atom(X,A),
  105        atom_string(A,V).
  106uri(X,V) :- iri(X,V).
 regex(?String, +Pattern, +Flag) is nondet
 regex(?String, +Pattern) is nondet
equivalent to REGEX in SPARQL

corresponds to re_match/2 in SPARQL

  117regex(S,P) :-
  118        regex(S,P,"").
  119
  120regex(S,P,Flag) :-
  121        eval_to_string(S,S1),
  122        eval_to_string(P,P1),
  123        eval_to_atom(Flag,Flag1),
  124        re_match(P1/Flag1, S1).
 eval_to_atom(?X, ?A) is nondet
evaluates expression X ensuring A is an atom
  131eval_to_atom(X,A) :-
  132        seval(X,Y),
  133        ensure_atom(Y,A).
  134
  135eval_to_string(X,A) :-
  136        seval(X,Y),
  137        ensure_string(Y,A).
 str_starts(+S:strlike, +Sub:strlike) is semidet
  144str_starts(S,Sub) :-
  145        ensure_atom(S,S1),
  146        ensure_atom(Sub,Sub1),
  147        atom_concat(Sub1,_,S1).
 str_ends(+S:strlike, +Sub:strlike) is semidet
  153str_ends(S,Sub) :-
  154        ensure_atom(S,S1),
  155        ensure_atom(Sub,Sub1),
  156        atom_concat(_,Sub1,S1).
 str_before(+S:strlike, +Sep:strlike, ?Sub:strlike) is det
  162str_before(S,Sep,Sub) :-
  163        ensure_atom(S,S1),
  164        ensure_atom(Sep,Sep1),
  165        atomic_list_concat([Sub1|_],Sep1,S1),
  166        (   var(Sub)
  167        ->  atom_string(Sub1,Sub)
  168        ;   ensure_atom(Sub,Sub1)).
 str_after(+S:strlike, +Sep:strlike, ?Sub:strlike) is det
  174str_after(S,Sep,Sub) :-
  175        ensure_atom(S,S1),
  176        ensure_atom(Sep,Sep1),
  177        atomic_list_concat([_|Toks],Sep1,S1),
  178        atomic_list_concat(Toks,Sep1,Sub1),
  179        ensure_atom(Sub,Sub1).
 str_replace(+S:strlike, +Match:strlike, +Replace:strlike, ?NewStr:strlike) is det
  185str_replace(S,In,Out,NewS) :-
  186        ensure_atom(S,S1),
  187        ensure_atom(In,In1),
  188        ensure_atom(Out,Out1),
  189        atomic_list_concat(Toks,In1,S1),
  190        atomic_list_concat(Toks,Out1,NewS1),
  191        ensure_string(NewS1,NewS).
 concat(?S1, ?S2, ?S) is nondet
equivalent to CONCAT in SPARQL
  199concat(S1,S2,S) :-
  200        concatl([S1,S2],S).
 concat(+S1, +S2, +S3, ?S) is nondet
equivalent to CONCAT in SPARQL
  206concat(S1,S2,S3,S) :-
  207        concatl([S1,S2,S3],S).
  208
  209
  210concatl(L,S) :-
  211        maplist(ensure_atom,L,LA),
  212        concat_atom(LA,SA),
  213        ensure_string(SA,S).        
  214
  215% for compat
 count(?L, ?N) is nondet
  221count(L,N) :- length(L,N).
 agg_max(?L, ?N) is nondet
  227agg_max(L,N) :- aggregate(max(X),member(X,L),N).
 group_concat(?L, ?Sep, ?V) is nondet
  235group_concat(L, Sep, V) :-
  236        maplist(ensure_atom,L,L1),
  237        ensure_atom(Sep,Sep1),
  238        atomic_list_concat(L1,Sep1,V1),
  239        ensure_string(V1,V).
  240
  241:- module_transparent(aggregate_group/4).
 aggregate_group(+AggExpression, +GroupBys:list, +Goal, ?Val) is det
perform an aggregate query

equivalent to GROUP BY queries in SPARQL

maps to aggregate/3 in prolog

  250aggregate_group(AggExpr, _GroupBys, Goal, Val) :-
  251        aggregate(AggExpr, Goal, Val).
  252
  253        
  254
  255/*
  256
  257%! aggregate_group(?AggExpr, ?[Witness], ?Goal, ?RWitness-RVal) is nondet.
  258aggregate_group(AggExpr, [Witness], Goal, RWitness-RVal) :-
  259        AggExpr =.. [Pred, Val],
  260        GTerm =.. [Pred, Witness, Val],
  261        RTerm =.. [Pred, RWitness, RVal],
  262        aggregate(GTerm,Goal,RTerm).
  263*/
  264
  265%:- rdf_meta rdf_path(r,r,r).
  266%:- rdf_meta rdf_path(r,r,r,g).
 rdf_path(?S, +Path, ?O, ?G) is nondet
 rdf_path(?S, +Path, ?O) is nondet
Evaluate an rdf path expression in terms of rdf/3.

See https://www.w3.org/TR/sparql11-query/#propertypaths

Path = Pred OR \Path OR P|Q OR P\Q OR zeroOrMore(Path) OR oneOrMore(Path) OR inverseOf(Path)
  279rdf_path(A,P,B) :-  rdf_path(A,P,B,_).
  280
  281rdf_path(A,(\P),B,G) :-  rdf_path(B,P,A,G).
  282rdf_path(A,inverseOf(P),B,G) :-  rdf_path(B,P,A,G).
  283rdf_path(A,(P|Q),B,G) :-  (rdf_path(A,P,B,G) ;rdf_path(A,Q,B,G)).
  284rdf_path(A,(P/Q),B,G) :-  rdf_path(A,P,Z,G), rdf_path(Z,Q,B,G).
  285
  286rdf_path(A,zeroOrMore(_),A,_).
  287rdf_path(A,zeroOrMore(P),B,G) :-  rdf_path(A,oneOrMore(P),B,G).
  288
  289rdf_path(A,oneOrMore(P),B,G) :-  rdf_path(A,P,B,G).
  290rdf_path(A,oneOrMore(P),B,G) :-  rdfx(A,P,Z,G),rdf_path(Z,oneOrMore(P),B,G).
  291
  292rdf_path(A,P,B,G) :- rdfx(A,P,B,G).
  293
  294% true if there is a triple A-P-B in G
  295% where P is either a URI or an atom representation of a CURIE
  296rdfx(A,P,B,G) :-
  297        ground(P),
  298        rdf_global_id(P,Px),
  299        atomic(Px),
  300        rdf(A,Px,B,G).
  301
  302:- rdf_meta iri_prefix_curie(r,o,o).
  303iri_prefix_curie(IRI, Prefix, CURIE) :-
  304        ground(IRI),
  305        !,
  306        rdf_global_id(Prefix:Local, IRI),
  307        concat_atom([Prefix,Local],':',CURIE).
  308iri_prefix_curie(IRI, Prefix, CURIE) :-
  309        ground(CURIE),
  310        !,
  311        concat_atom([Prefix,Local],':',CURIE),
  312        rdf_global_id(Prefix:Local, IRI).
  313iri_curie(IRI, CURIE) :-
  314        iri_prefix_curie(IRI, _, CURIE).
  315iri_prefix(IRI, Prefix) :-
  316        iri_prefix_curie(IRI, Prefix, _).
  317curie_prefix(CURIE, Prefix) :-
  318        str_before(CURIE,":",Prefix).
 optional(?G) is det
call G once, succeed if G fails

equivalent to OPTIONAL in SPARQL

  329:- module_transparent(optional/1).  330optional(G) :- G,!.
  331optional(_).
  332
  333entailed(G,AssertedG) :-
  334        entailed(G,AssertedG,_,[]).
  335entailed(G,AssertedG,Goals,_Opts) :-
  336        G =.. [P|Args],
  337        map_args_to_equiv(Args,Args2,Goals),
  338        % TODO: query optimization
  339        all_succeed(Goals),
  340        AssertedG =.. [P|Args2],
  341        AssertedG.
  342
  343map_args_to_equiv([],[],[]).
  344map_args_to_equiv([A|L],[A2|L],[G|GL]) :-
  345        G=owl_equivalent_class(A,A2),
  346        map_args_to_equiv(L,L,GL).
  347
  348all_succeed([]).
  349all_succeed([G|L]) :-
  350        G,
  351        all_succeed(L).
  352
  353
  354        
  355
  356
  357%! ensure_atom(?Str:strlike, ?Atom:atom) is det
  358%
  359% convert a string, literal, or any strlike entity to an atom
  360ensure_atom(S@_, A) :- !, atom_string(A,S).
  361
  362ensure_atom(S^^_, A) :- !, string(S), atom_string(A,S).
  363
  364ensure_atom(A, A) :- atom(A), !.
  365
  366ensure_atom(S, A) :- string(S), !, atom_string(A,S).
 ensure_string(?A, ?Str:string) is det
convert an entity to its string representation
  371ensure_string(S,S) :- string(S),!.
  372
  373ensure_string(A,S) :- atom(A), !, atom_string(A,S).
  374
  375ensure_string(A,_) :- throw(ensure_string(A)).
 ensure_atoms(?Input:list, ?Atoms:list) is det
  382ensure_atoms(L, L2) :- maplist([A,B]>>ensure_atom(A,B),L,L2).
  383
  384
  385ensure_number(N, N) :- number(N),!.
  386ensure_number(S^^_, N) :- !, atom_string(A,S), atom_number(A,N). % todo: fail if incorrect datatype?
  387ensure_number(literal(type(_,S)), N) :- !, atom_string(A,S), atom_number(A,N). % todo: fail if incorrect datatype?
  388ensure_number(S, N) :- string(S), !, atom_string(A,S), atom_number(A,N).
  389ensure_number(A, N) :- atom(A), !, atom_number(A,N).
  390
  391% 17.4.1.1 bound
  392bound(Expr) :- nonvar(Expr).
  393
  394% 17.4.1.2 IF
 if(?E1, ?E2, ?E3, ?R) is nondet
  400if(E1, E2, E3, R) :-
  401        (   E1
  402        ->  bind(E2, R)
  403        ;   bind(E3, R)).
  404
  405% 17.4.1.3 COALESCE
  406coalesce([H|_],R) :-
  407        bind(H, R),
  408        nonvar(R),
  409        !.
  410coalesce([_|T],R) :-
  411        coalesce(T,R).
 exists(E) is semidet
  414exists(E) :- \+ \+ E.
  415
  416% 17.4.1.8 sameTerm : use ==
  417
  418% 17.4.1.9 IN : use member/2
  419
  420% 17.4.2.1 isIRI : use rdf_is_iri
 bind(?ReturnValue, +FunctionTerm)
evaluates a function term. The function term can be a:
  433bind(Expr,Result) :-
  434        seval(Expr, Result).
 seval(+FunctionTerm, ?ReturnValue)
evaluates a function term

this is the same as bind/2 with args reversed.

  442:- module_transparent(seval/2).  443
  444seval(L,L2) :-
  445        is_list(L),
  446        !,
  447        maplist(seval,L,L2).
  448
  449
  450seval(V, V) :- var(V),!.
  451
  452seval(T, T) :- T = _@_, !.
  453
  454seval(T, T) :- T = _^^_, !.
  455
  456seval(T, T) :- T = literal(_), !.
  457
  458seval(T, T) :- is_aggregate_goal(T),!.
  459
  460seval(Term, Ret) :-
  461        compound(Term),
  462        !,
  463        Term =.. [P|Args],
  464        maplist(seval,Args,EArgs),
  465        (   ArithTerm =.. [P|EArgs],
  466            current_arithmetic_function(ArithTerm),
  467            maplist(ensure_number,EArgs,NArgs),
  468            ArithTerm2 =.. [P|NArgs],
  469            arithmetic_expression_value(ArithTerm2,Ret)
  470        ->  true
  471        ;   append(EArgs,[Ret],GoalArgs),
  472            Goal =.. [P|GoalArgs],
  473            Goal).
  474seval(X,X).
  475
  476is_aggregate_goal(aggregate_group(_,_,_,_)).
  477is_aggregate_goal(aggregate(_,_,_))