Source file defunc.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
open Base
open Import
module Of_python = struct
type 'a t =
{ type_name : string
; conv : pyobject -> 'a
}
[@@deriving fields]
let create = Fields.create
end
module Arg = struct
type 'a t =
{ name : string
; of_python : 'a Of_python.t
; docstring : string
; kind : [ `positional | `keyword of 'a option ]
}
[@@deriving fields]
end
module Opt_arg = struct
type 'a t =
{ name : string
; of_python : 'a Of_python.t
; docstring : string
}
[@@deriving fields]
end
module T0 = struct
type _ t =
| Return : 'a -> 'a t
| Map : 'a t * ('a -> 'b) -> 'b t
| Both : 'a t * 'b t -> ('a * 'b) t
| Arg : 'a Arg.t -> 'a t
| Opt_arg : 'a Opt_arg.t -> 'a option t
let return x = Return x
let map t ~f = Map (t, f)
let both t t' = Both (t, t')
let apply f x = both f x |> map ~f:(fun (f, x) -> f x)
let map = `Custom map
end
module T = struct
include T0
include Applicative.Make (T0)
end
include T
module Open_on_rhs_intf = struct
module type S = Applicative.S
end
include Applicative.Make_let_syntax (T) (Open_on_rhs_intf) (T)
let valid_char c = Char.(is_alphanum c || c = '_')
let check_valid_arg_name name =
if String.is_empty name
then failwith "cannot use an empty name"
else (
let first_char = name.[0] in
if Char.(first_char < 'a' || first_char > 'z')
then Printf.failwithf "arg name %s does not start with a lowercase letter" name ()
else if String.exists name ~f:(fun c -> not (valid_char c))
then Printf.failwithf "arg name %s contains some invalid characters" name ()
else ())
;;
let apply (type a) (t : a t) args kwargs =
let try_of_python v ~of_python ~name =
try Of_python.conv of_python v with
| e ->
value_errorf !"error processing arg %s (%s): %{Exn}" name of_python.type_name e
in
let kwnames = Hash_set.create (module String) in
let positional_arguments () =
let rec loop : type a. a t -> string list = function
| Return _ -> []
| Map (t, _) -> loop t
| Both (t, t') ->
let args = loop t in
let args' = loop t' in
args @ args'
| Arg { name; kind = `positional; _ } -> [ name ]
| Arg { kind = `keyword _; _ } -> []
| Opt_arg _ -> []
in
loop t
in
let rec loop : type a. a t -> pos:int -> a * int =
fun t ~pos ->
match t with
| Return a -> a, pos
| Map (t, f) ->
let v, pos = loop t ~pos in
f v, pos
| Both (t, t') ->
let v, pos = loop t ~pos in
let v', pos = loop t' ~pos in
(v, v'), pos
| Arg { name; of_python; docstring = _; kind = `positional } ->
if pos >= Array.length args
then
value_errorf
"not enough arguments (got %d, expected %s)"
(Array.length args)
(positional_arguments () |> String.concat ~sep:", ");
try_of_python args.(pos) ~of_python ~name, pos + 1
| Opt_arg { name; of_python; docstring = _ } ->
if Hash_set.mem kwnames name
then value_errorf "multiple keyword arguments with name %s" name;
Hash_set.add kwnames name;
let v = Map.find kwargs name in
Option.map v ~f:(try_of_python ~of_python ~name), pos
| Arg { name; of_python; docstring = _; kind = `keyword default } ->
if Hash_set.mem kwnames name
then value_errorf "multiple keyword arguments with name %s" name;
Hash_set.add kwnames name;
(match Map.find kwargs name with
| Some v -> try_of_python v ~of_python ~name, pos
| None ->
(match default with
| Some default -> default, pos
| None -> value_errorf "missing keyword argument: %s" name))
in
let v, final_pos = loop t ~pos:0 in
Map.iter_keys kwargs ~f:(fun key ->
if not (Hash_set.mem kwnames key)
then value_errorf "unexpected keyword argument %s" key);
if final_pos <> Array.length args
then
value_errorf
"expected expected %d arguments (%s), got %d"
final_pos
(positional_arguments () |> String.concat ~sep:", ")
(Array.length args);
v
;;
let params_docstring t =
let sprintf = Printf.sprintf in
let arg_docstring arg ~pos =
match Arg.kind arg with
| `positional ->
[ sprintf
" :param %s: (positional %d) %s"
(Arg.name arg)
pos
(Arg.docstring arg)
; sprintf
" :type %s: %s"
(Arg.name arg)
(Arg.of_python arg |> Of_python.type_name)
]
|> String.concat ~sep:"\n"
| `keyword default ->
let default =
match default with
| None -> "mandatory keyword"
| Some _ -> "keyword with default"
in
[ sprintf " :param %s: (%s) %s" (Arg.name arg) default (Arg.docstring arg)
; sprintf
" :type %s: %s"
(Arg.name arg)
(Arg.of_python arg |> Of_python.type_name)
]
|> String.concat ~sep:"\n"
in
let opt_arg_docstring arg =
[ sprintf
" :param %s: (optional keyword) %s"
(Opt_arg.name arg)
(Opt_arg.docstring arg)
; sprintf
" :type %s: %s"
(Opt_arg.name arg)
(Opt_arg.of_python arg |> Of_python.type_name)
]
|> String.concat ~sep:"\n"
in
let rec loop : type a. a t -> pos:int -> string list * int =
fun t ~pos ->
match t with
| Return _ -> [], pos
| Map (t, _) -> loop t ~pos
| Both (t1, t2) ->
let params1, pos = loop t1 ~pos in
let params2, pos = loop t2 ~pos in
params1 @ params2, pos
| Arg ({ kind = `positional; _ } as arg) -> [ arg_docstring arg ~pos ], pos + 1
| Arg ({ kind = `keyword _; _ } as arg) -> [ arg_docstring arg ~pos ], pos
| Opt_arg opt_arg -> [ opt_arg_docstring opt_arg ], pos
in
let params, _pos = loop t ~pos:0 in
if List.is_empty params then None else String.concat params ~sep:"\n\n" |> Option.some
;;
module Param = struct
let positional name of_python ~docstring =
check_valid_arg_name name;
Arg { name; of_python; docstring; kind = `positional }
;;
let keyword ?default name of_python ~docstring =
check_valid_arg_name name;
Arg { name; of_python; docstring; kind = `keyword default }
;;
let keyword_opt name of_python ~docstring =
check_valid_arg_name name;
Opt_arg { name; of_python; docstring }
;;
let int = Of_python.create ~type_name:"int" ~conv:int_of_python
let float = Of_python.create ~type_name:"float" ~conv:float_of_python
let bool = Of_python.create ~type_name:"bool" ~conv:bool_of_python
let string = Of_python.create ~type_name:"string" ~conv:string_of_python
let pyobject = Of_python.create ~type_name:"obj" ~conv:Fn.id
let pair o1 o2 =
Of_python.create
~type_name:
(Printf.sprintf "(%s, %s)" (Of_python.type_name o1) (Of_python.type_name o2))
~conv:(fun pyobject ->
let p1, p2 = Py.Tuple.to_tuple2 pyobject in
Of_python.conv o1 p1, Of_python.conv o2 p2)
;;
let triple o1 o2 o3 =
Of_python.create
~type_name:
(Printf.sprintf
"(%s, %s, %s)"
(Of_python.type_name o1)
(Of_python.type_name o2)
(Of_python.type_name o3))
~conv:(fun pyobject ->
let p1, p2, p3 = Py.Tuple.to_tuple3 pyobject in
Of_python.conv o1 p1, Of_python.conv o2 p2, Of_python.conv o3 p3)
;;
let list o =
Of_python.create
~type_name:(Printf.sprintf "[%s]" (Of_python.type_name o))
~conv:(Py.List.to_list_map (Of_python.conv o))
;;
let one_or_tuple_or_list o =
Of_python.create
~type_name:(Printf.sprintf "[%s]" (Of_python.type_name o))
~conv:(One_or_tuple_or_list.t_of_python (Of_python.conv o))
;;
let dict ~key ~value =
Of_python.create
~type_name:
(Printf.sprintf "[%s: %s]" (Of_python.type_name key) (Of_python.type_name value))
~conv:(Py.Dict.to_bindings_map (Of_python.conv key) (Of_python.conv value))
;;
end