package swipl

  1. Overview
  2. Docs

Source file swipl.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
(*
SWIPL-OCaml

Copyright (C) 2021  Kiran Gopinathan

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*)

module Raw = Raw

let module_sep = lazy (Raw.Functor.functor_ (Raw.Atom.atom ":") 2)
let conjunction = lazy (Raw.Functor.functor_ (Raw.Atom.atom ",") 2)
let disjunction = lazy (Raw.Functor.functor_ (Raw.Atom.atom ";") 2)
let apply = lazy (Raw.Functor.functor_ (Raw.Atom.atom "apply") 2)
let call1 = lazy (Raw.Functor.functor_ (Raw.Atom.atom "call") 1)

type ty  =
  [ `Atom | `Blob | `Bool | `Dict | `Float | `Integer
  | `ListPair | `Nil | `Rational | `String | `Term | `Variable ]

type ctx = Raw.ForeignFrame.t option
type query = Raw.Query.qid
type module_ = Raw.Module.t [@@deriving eq, ord]
let module_ str = Raw.Module.new_module (Raw.Atom.atom str)
type fn = Raw.Functor.t [@@deriving eq, ord]

type atom = Raw.Atom.t [@@deriving eq, ord]
type t =
  | Term of Raw.Term.t
  | Atom of Raw.Atom.t
  | App of Raw.Module.t option * Raw.Functor.t * t list
  | Conjunction of t * t
  | Disjunction of t * t
[@@deriving eq, ord]

let create_term (_ctx: ctx) =
  Raw.Term.new_ref ()

let create_terms (_ctx: ctx) n =
  Raw.Term.new_refs n


let of_term term = Term term

let encode_string ctx str =
  let t = create_term ctx in
  assert(Raw.Term.put_string_chars t str);
  t

let encode_list ctx ls =
  let rec loop result = function
    | [] -> assert (Raw.Term.put_nil result); result
    | h :: t ->
      let result = loop result t in
      assert (Raw.Term.cons_list result h result);
      result in
  loop (create_term ctx) ls

let array_of_list ctx args =
  let length = List.length args in
  if length = 0
  then Raw.Term.Array.empty
  else begin
    let terms = create_terms ctx length in
    List.iteri (fun ind term ->
      assert (Raw.Term.put_term (Raw.Term.Array.get terms ind) term)) args;
    terms
  end

let atom ctx v =
  let result = create_term ctx in
  Raw.Term.put_atom result v;
  result

let app2 ctx fn arg1 arg2 =
  let result = create_term ctx in
  assert (Raw.Term.cons_functor2 result fn arg1 arg2);
  result

let app ctx fn args =
  let result = create_term ctx in
  let args = array_of_list ctx args in
  assert (Raw.Term.cons_functor result fn args);
  result

let rec to_term ctx = function
  | Term t -> t
  | App (None, fn, args) -> app ctx fn (List.map (to_term ctx) args)
  | App (Some module_, fn, args) ->
    let fn = 
      let lazy sep =  module_sep in
      let module_ = Raw.Module.module_name module_ in
      let fn = Raw.Functor.name fn in
      app2 ctx sep (atom ctx module_) (atom ctx fn) in
    let args = List.map (to_term ctx) args in
    let args = encode_list ctx args in
    let lazy apply = apply in
    app2 ctx apply fn args
  | Conjunction (l,r) ->
    let lazy conjunction = conjunction in
    let l = to_term ctx l in
    let r = to_term ctx r in
    app2 ctx conjunction l r
  | Disjunction (l,r) ->
    let lazy disjunction = disjunction in
    let l = to_term ctx l in
    let r = to_term ctx r in
    app2 ctx disjunction l r
  | Atom a -> atom ctx a

let eval ctx =
  let flags = Raw.Q.(normal + ext_status + pass_exception) in
  function
  | Term t ->
    let lazy call = call1 in
    let pred = Raw.Predicate.pred call in
    Raw.Query.open_query ~flags pred (array_of_list ctx [t])
  | App (module_, fn, args) ->
    let pred = Raw.Predicate.pred ?module_ fn in
    let args = array_of_list ctx (List.map (to_term ctx) args) in
    Raw.Query.open_query ?module_ ~flags pred args
  | Atom a ->
    let fn = Raw.Functor.functor_ a 0 in
    let pred = Raw.Predicate.pred fn in
    Raw.Query.open_query ~flags pred Raw.Term.Array.empty
  | Conjunction (l,r) ->
    let lazy conjunction = conjunction in
    let conjunction = Raw.Predicate.pred conjunction in
    Raw.Query.open_query ~flags conjunction (array_of_list ctx [to_term ctx l; to_term ctx r])
  | Disjunction (l,r) ->
    let lazy disjunction = disjunction in
    let disjunction = Raw.Predicate.pred disjunction in
    Raw.Query.open_query ~flags disjunction (array_of_list ctx [to_term ctx l; to_term ctx r])

let initialise () = Raw.initialise ()

let with_ctx f =
  let frame = Raw.ForeignFrame.open_frame () in
  let res = f (Some frame) in
  Raw.ForeignFrame.close_frame frame;
  res

let show t =
  with_ctx (fun ctx -> to_term ctx t |> Raw.Term.get_chars) |> Option.value ~default:"None"
let pp fmt t = Format.pp_print_string fmt (show t)

let fresh ctx = of_term (create_term ctx)

module Syntax = struct
  let (/@) fn x = Raw.Functor.functor_ (Raw.Atom.atom fn) x
  let (!) x = Atom (Raw.Atom.atom x)
  let app ?module_ fn args = App (module_, fn, args)
  let (&&) l r = Conjunction (l,r)
  let (||) l r = Disjunction (l,r)
end

let fold_solutions fn query =
  let any_seen = ref false in
  let rec loop () =
    match Raw.Query.next_solution query with
    | Raw.Query.Result.Bool true ->
      any_seen := true;
      begin match fn `Solution with
      | Some `Close -> assert (Raw.Query.close_query query)
      | Some `Cut -> assert (Raw.Query.cut_query query)
      | None -> loop ()
      end
    | Raw.Query.Result.Bool false -> assert (Raw.Query.close_query query)
    | Raw.Query.Result.Last ->
      any_seen := true;
      begin match fn `Last with
      | Some `Close -> assert (Raw.Query.close_query query)
      | Some `Cut -> assert (Raw.Query.cut_query query)
      | None -> assert (Raw.Query.close_query query)
      end
    | Raw.Query.Result.Exception  ->
      match Raw.Exception.exn query with
      | None -> assert (Raw.Query.close_query query)
      | Some exn ->
        ignore @@ begin
          try
            fn (`Exception (Term exn))
          with
            e -> (Raw.Exception.clear_exn (); raise e)
        end;
        Raw.Exception.clear_exn ();
        assert (Raw.Query.close_query query) in
  begin
    try
      loop ()
    with
    | e -> assert (Raw.Query.close_query query); raise e
  end;
  !any_seen

let iter_solutions ?(on_error=fun _ -> ()) query fn =
  fold_solutions (function
    | `Exception e -> on_error e; None
    | _ -> fn (); None
  ) query

let first_solution query =
  fold_solutions (function
    | `Exception e -> failwith ("Prolog query raised exception: " ^ show e)
    | `Last -> Some `Cut
    | `Solution -> Some `Cut
  ) query

let last_solution query =
  fold_solutions (function
    | `Exception e -> failwith ("Prolog query raised exception" ^ show e)
    | `Last -> Some `Cut
    | `Solution -> None
  ) query

let call ctx t =
  let query = eval ctx t in
  ignore @@ first_solution query

let extract_list ctx term =
  let term = to_term ctx term in
  let rec loop term = 
    if Raw.Term.get_nil term
    then []
    else
      let hd = create_term ctx in
      let tl = create_term ctx in
      assert (Raw.Term.get_list term hd tl);
      (of_term hd) :: (loop tl) in
  loop term

let atom t = Raw.Atom.atom t

let extract_atom ctx term = Raw.Term.get_atom (to_term ctx term) |> Option.get
let show_atom atom = Raw.Atom.chars atom
let pp_atom fmt atom = Format.pp_print_string fmt (show_atom atom)
let extract_bool ctx term = Raw.Term.get_bool (to_term ctx term) |> Option.get
let extract_int ctx term = Raw.Term.get_integer (to_term ctx term) |> Option.get
let extract_float ctx term = Raw.Term.get_float (to_term ctx term) |> Option.get
let extract_string ctx term = Raw.Term.get_string_chars (to_term ctx term) |> Option.get
let extract_functor ctx term =
  let (name, arity) = Raw.Term.get_name_arity (to_term ctx term) |> Option.get in
  let rec loop acc ind =
    if ind < arity
    then
      let arg = create_term ctx in
      assert (Raw.Term.get_arg (ind + 1) (to_term ctx term) arg);
      loop ((of_term arg) :: acc) (ind + 1)
    else List.rev acc in
  (name, loop [] 0)

let typeof = function
  | Term t -> Raw.Term.term_type t
  | Atom _ -> `Atom
  | App (_, _, _)
  | Conjunction (_, _)
  | Disjunction (_, _) -> `Term

let encode_list ctx args = of_term (List.map (to_term ctx) args |> encode_list ctx)
let encode_string ctx str = of_term (encode_string ctx str)

let load_source txt =
  let user = lazy Syntax.(! "user") in
  let open_string = lazy Syntax.("open_string" /@ 2) in
  let load_files = lazy Syntax.("load_files" /@ 2) in
  let stream = lazy Syntax.("stream" /@ 1) in
  let open_string txt out = Syntax.(app (Lazy.force open_string) [txt; out]) in
  let load_files int opts = Syntax.(app (Lazy.force load_files) [int; opts]) in
  let stream out = Syntax.(app (Lazy.force stream) [out]) in
  let open_source ctx src =
    let out = fresh ctx in
    Syntax.(open_string (encode_string ctx src) out &&
            load_files (Lazy.force user) (encode_list ctx [stream out])) in
  with_ctx (fun ctx ->
    call ctx (open_source ctx txt)
  )
OCaml

Innovation. Community. Security.