package rdf_ppx

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file ppx_rdf.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
(*********************************************************************************)
(*                OCaml-RDF                                                      *)
(*                                                                               *)
(*    Copyright (C) 2012-2021 Institut National de Recherche en Informatique     *)
(*    et en Automatique. All rights reserved.                                    *)
(*                                                                               *)
(*    This program is free software; you can redistribute it and/or modify       *)
(*    it under the terms of the GNU Lesser General Public License version        *)
(*    3 as published by the Free Software Foundation.                            *)
(*                                                                               *)
(*    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, write to the Free Software                *)
(*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA                   *)
(*    02111-1307  USA                                                            *)
(*                                                                               *)
(*    Contact: Maxence.Guesdon@inria.fr                                          *)
(*                                                                               *)
(*********************************************************************************)

(** OCaml syntax extension to check syntax of Sparql queries at compile time. *)

module Re = Str


exception Error of Location.t * string

let mkloc = Location.mkloc
let mknoloc = Location.mknoloc

let () =
  Location.register_error_of_exn (fun exn ->
    match exn with
    | Error (loc, msg) -> Some (Location.error ~loc msg)
    | _ -> None)


let longident_of_string ?loc str =
  let b = Lexing.from_string str in
  let b =
    match loc with
    | None -> b
    | Some loc ->
        let p = loc.Location.loc_start in
        { b with Lexing.lex_start_p = p; lex_curr_p = p }
  in
  Parse.longident b


let lid_sprintf = Location.mknoloc (longident_of_string "Printf.sprintf")
let lid_sparql_error = Location.mknoloc (longident_of_string "Rdf.Sparql.Error")
let lid_sparql_parse_error = Location.mknoloc (longident_of_string "Rdf.Sparql.Parse_error")

open Ppxlib
open Ast_helper

module Location = Ppxlib_ast__Import.Location

let sparql_node = "sparql"
let re_fmt = Re.regexp
  "\\([^%]?\\)%\\([-+0# ]*[0-9]*\\(\\.[0-9]+\\)?[lLn]?\\(\\({term}\\)\\|[!%@,diXxoSsCcFfEeGgBbat]\\)\\)"

let check_query_fmt loc fmt =
  let f s =
    let res =
      match Re.matched_group 4 s with
        "{term}" -> "<http://foo/bar>"
      | "d" | "i" -> "0"
      | "s" -> "string"
      | "S" -> "\"String\""
      | "o" -> "0"
      | "c" -> "c"
      | "C" -> "'C'"
      | "f" -> "0.0"
      | "F" | "E" | "e" | "G" | "g" -> "0.0e-0"
      | "X" -> "ABCD123"
      | "x" -> "abcd123"
      | "B" | "b" -> "true"
      | "a" -> "\"%a\""
      | "t" -> "\"%t\""
      | "!" -> ""
      | "@" -> "@"
      | "%" -> "%"
      | "," -> ""
      | _ -> "%"^(Re.matched_group 2 s)
    in
    (try Re.matched_group 1 s with _ -> "")^res
  in
  let q = Re.global_substitute re_fmt f fmt in
  try ignore(Rdf.Sparql.query_from_string q)
  with Rdf.Sparql.Error e ->
      let q = Re.global_replace (Re.regexp_string "\n") "\n  " q in
      let msg = Printf.sprintf "Checking syntax of query\n  %s\n%s" q (Rdf.Sparql.string_of_error e) in
      raise (Error (loc, msg))


let gen_code ~loc ~attrs fmt args =
  let args =
    (Nolabel, Exp.constant ~loc (Pconst_string (fmt, loc, None))) :: args
  in
  let f =
    let e =
      Exp.apply ~loc (Exp.ident (mknoloc (longident_of_string "Rdf.Sparql.query_from_string")))
        [
          Nolabel, Exp.ident(mknoloc (longident_of_string "_q")) ;
        ]
    in
    let case =
      let pat = Pat.construct lid_sparql_error
        (Some (Pat.construct lid_sparql_parse_error
          (Some (Pat.tuple [ Pat.var (mknoloc "eloc") ; Pat.var (mknoloc "msg")]))
         ))
      in
      let e =
        Exp.let_ Nonrecursive
          [ Vb.mk (Pat.var (mknoloc "msg"))
            (Exp.apply (Exp.ident lid_sprintf)
             [ Nolabel, Exp.constant (Pconst_string ("%s\nin %s", Location.none, None)) ;
               Nolabel, Exp.ident (mknoloc (longident_of_string "msg")) ;
               Nolabel, Exp.ident (mknoloc (longident_of_string "_q")) ;
             ]
            )
          ]
          (Exp.apply (Exp.ident (mknoloc (longident_of_string "raise")))
           [ Nolabel,
             Exp.construct lid_sparql_error
               (Some (Exp.construct lid_sparql_parse_error
                 (Some (Exp.tuple [
                     Exp.ident (mknoloc (longident_of_string  "eloc")) ;
                     Exp.ident (mknoloc (longident_of_string  "msg")) ;
                   ]))
                ))
           ]
          )
      in
      Exp.case pat e
    in
    let body = Exp.try_ ~loc ~attrs e [case] in
    Exp.fun_ Nolabel None (Pat.var (mknoloc "_q")) body
  in
  Exp.apply ~loc ~attrs
    (Exp.ident (mknoloc (longident_of_string "Printf.ksprintf")))
    ((Nolabel, f) :: args)
;;

let expand ~loc ~path e =
  match
    match e.pexp_desc with
    | Pexp_constant (Pconst_string (s, _, _)) -> Some (e.pexp_loc, s, [])
    | Pexp_apply ({ pexp_desc = Pexp_constant (Pconst_string (s, _, _)) ; pexp_loc}, args) ->
        Some (pexp_loc, s, args)
    | _ -> None
  with
  | None -> Location.raise_errorf ~loc:e.pexp_loc "Invalid payload for sparql extension"
  | Some (loc, fmt, args) ->
      check_query_fmt loc fmt ;
      let terms = ref [] in
      let n = ref (-1) in
      let f s =
        incr n;
        let g = Re.matched_group 4 s in
        match g with
          "{term}" -> terms := !n :: !terms ; (Re.matched_group 1 s)^"%s"
        | _ -> Re.matched_string s
      in
      let fmt = Re.global_substitute re_fmt f fmt in
      let rec iter i = function
        [] -> []
      | (l, e) :: q when List.mem i !terms ->
                    let e = Exp.apply
            (Exp.ident (mknoloc (longident_of_string "Rdf.Term.string_of_term")))
              [ Nolabel, e ]
          in
          (l, e) :: iter (i+1) q
      | x :: q -> x :: iter (i+1) q
      in
      let args = iter 0 args in
      gen_code ~loc: e.pexp_loc ~attrs: e.pexp_attributes fmt args


let my_extension =
  Extension.declare
    sparql_node
    Extension.Context.expression
    Ast_pattern.(single_expr_payload __)
    expand

let rule = Ppxlib.Context_free.Rule.extension my_extension

let () =
  Driver.register_transformation
    ~rules:[rule]
    sparql_node