Skip to content
This repository was archived by the owner on Nov 27, 2024. It is now read-only.

Commit ee64e95

Browse files
committed
v0.13-preview.120.47+86
1 parent ec4c61c commit ee64e95

17 files changed

+1337
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
_build
2+
*.install
3+
*.merlin
4+
_opam
5+

CONTRIBUTING.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
This repository contains open source software that is developed and
2+
maintained by [Jane Street][js].
3+
4+
Contributions to this project are welcome and should be submitted via
5+
GitHub pull requests.
6+
7+
Signing contributions
8+
---------------------
9+
10+
We require that you sign your contributions. Your signature certifies
11+
that you wrote the patch or otherwise have the right to pass it on as
12+
an open-source patch. The rules are pretty simple: if you can certify
13+
the below (from [developercertificate.org][dco]):
14+
15+
```
16+
Developer Certificate of Origin
17+
Version 1.1
18+
19+
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
20+
1 Letterman Drive
21+
Suite D4700
22+
San Francisco, CA, 94129
23+
24+
Everyone is permitted to copy and distribute verbatim copies of this
25+
license document, but changing it is not allowed.
26+
27+
28+
Developer's Certificate of Origin 1.1
29+
30+
By making a contribution to this project, I certify that:
31+
32+
(a) The contribution was created in whole or in part by me and I
33+
have the right to submit it under the open source license
34+
indicated in the file; or
35+
36+
(b) The contribution is based upon previous work that, to the best
37+
of my knowledge, is covered under an appropriate open source
38+
license and I have the right under that license to submit that
39+
work with modifications, whether created in whole or in part
40+
by me, under the same open source license (unless I am
41+
permitted to submit under a different license), as indicated
42+
in the file; or
43+
44+
(c) The contribution was provided directly to me by some other
45+
person who certified (a), (b) or (c) and I have not modified
46+
it.
47+
48+
(d) I understand and agree that this project and the contribution
49+
are public and that a record of the contribution (including all
50+
personal information I submit with it, including my sign-off) is
51+
maintained indefinitely and may be redistributed consistent with
52+
this project or the open source license(s) involved.
53+
```
54+
55+
Then you just add a line to every git commit message:
56+
57+
```
58+
Signed-off-by: Joe Smith <[email protected]>
59+
```
60+
61+
Use your real name (sorry, no pseudonyms or anonymous contributions.)
62+
63+
If you set your `user.name` and `user.email` git configs, you can sign
64+
your commit automatically with git commit -s.
65+
66+
[dco]: http://developercertificate.org/
67+
[js]: https://opensource.janestreet.com/

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright (c) 2019 Jane Street Group, LLC <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
INSTALL_ARGS := $(if $(PREFIX),--prefix $(PREFIX),)
2+
3+
default:
4+
dune build
5+
6+
install:
7+
dune install $(INSTALL_ARGS)
8+
9+
uninstall:
10+
dune uninstall $(INSTALL_ARGS)
11+
12+
reinstall: uninstall install
13+
14+
clean:
15+
dune clean
16+
17+
.PHONY: default install uninstall reinstall clean

class_wrapper.ml

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
open Base
2+
open Import
3+
4+
module Id : sig
5+
type t
6+
7+
val create : unit -> t
8+
val to_string : t -> string
9+
end = struct
10+
type t = int
11+
12+
let create =
13+
let current = ref 0 in
14+
fun () ->
15+
Int.incr current;
16+
!current
17+
;;
18+
19+
let to_string = Int.to_string
20+
end
21+
22+
let content_field = "_content"
23+
24+
type 'a t =
25+
{ wrap : 'a -> Py.Object.t
26+
; unwrap : Py.Object.t -> 'a
27+
; name : string
28+
; mutable cls_object : Py.Object.t option
29+
}
30+
31+
let set_cls_object_exn t pyobject =
32+
if Option.is_some t.cls_object
33+
then Printf.failwithf "cls_object for %s has already been set" t.name ();
34+
t.cls_object <- Some pyobject
35+
;;
36+
37+
module Init = struct
38+
type 'a cls = 'a t
39+
40+
type 'a t =
41+
{ fn : 'a cls -> args:pyobject list -> 'a
42+
; docstring : string option
43+
}
44+
[@@deriving fields]
45+
46+
let create ?docstring fn = { docstring; fn }
47+
end
48+
49+
module Method = struct
50+
type 'a cls = 'a t
51+
52+
type 'a fn =
53+
| No_keywords of ('a cls -> self:'a * pyobject -> args:pyobject list -> pyobject)
54+
| With_keywords of
55+
('a cls
56+
-> self:'a * pyobject
57+
-> args:pyobject list
58+
-> keywords:(string, pyobject, String.comparator_witness) Map.t
59+
-> pyobject)
60+
61+
type 'a t =
62+
{ name : string
63+
; fn : 'a fn
64+
; docstring : string option
65+
}
66+
[@@deriving fields]
67+
68+
let create ?docstring name fn = { name; fn = No_keywords fn; docstring }
69+
70+
let create_with_keywords ?docstring name fn =
71+
{ name; fn = With_keywords fn; docstring }
72+
;;
73+
74+
let defunc ?docstring name fn =
75+
let fn cls ~self ~args ~keywords =
76+
Defunc.apply (fn cls ~self) (Array.of_list args) keywords
77+
in
78+
create_with_keywords ?docstring name fn
79+
;;
80+
end
81+
82+
let wrap_capsule t obj = t.wrap obj
83+
84+
let unwrap_exn t pyobj =
85+
let pyobj =
86+
match Py.Object.get_attr_string pyobj content_field with
87+
| None -> Printf.failwithf "no %s field in object" content_field ()
88+
| Some content -> content
89+
in
90+
if not (Py.Capsule.check pyobj) then failwith "not an ocaml capsule";
91+
t.unwrap pyobj
92+
;;
93+
94+
let unwrap t pyobj =
95+
try Some (unwrap_exn t pyobj) with
96+
| _ -> None
97+
;;
98+
99+
let wrap t obj =
100+
let cls = Option.value_exn t.cls_object in
101+
let pyobject = Py.Object.call_function_obj_args cls [||] in
102+
Py.Object.set_attr_string pyobject content_field (wrap_capsule t obj);
103+
pyobject
104+
;;
105+
106+
let make ?to_string_repr ?to_string ?eq ?init name ~methods =
107+
let id = Id.create () in
108+
let t =
109+
let wrap, unwrap = Py.Capsule.make (Printf.sprintf !"%s-%{Id}" name id) in
110+
{ wrap; unwrap; cls_object = None; name }
111+
in
112+
let methods =
113+
let to_string =
114+
Option.map to_string ~f:(fun fn t ~self ~args:_ ->
115+
fn t (fst self) |> Py.String.of_string)
116+
in
117+
let to_string_repr =
118+
Option.map to_string_repr ~f:(fun fn t ~self ~args:_ ->
119+
fn t (fst self) |> Py.String.of_string)
120+
in
121+
let to_string_repr = Option.first_some to_string_repr to_string in
122+
let eq =
123+
Option.map eq ~f:(fun fn t ~self ~args ->
124+
let rhs =
125+
match args with
126+
| [] -> failwith "eq with no argument"
127+
| _ :: _ :: _ ->
128+
Printf.failwithf "eq with %d arguments" (List.length args) ()
129+
| [ rhs ] -> rhs
130+
in
131+
fn t (fst self) (unwrap_exn t rhs) |> Py.Bool.of_bool)
132+
in
133+
List.filter_map
134+
[ "__str__", to_string; "__repr__", to_string_repr; "__eq__", eq ]
135+
~f:(fun (name, fn) -> Option.map fn ~f:(fun fn -> Method.create name fn))
136+
@ methods
137+
in
138+
let methods =
139+
List.map methods ~f:(fun { Method.name; fn; docstring } ->
140+
let fn =
141+
let self_and_args args =
142+
let args = Array.to_list args in
143+
match args with
144+
| [] -> failwith "empty input"
145+
| p :: q -> p, q
146+
in
147+
match (fn : _ Method.fn) with
148+
| No_keywords fn ->
149+
Py.Callable.of_function ?docstring (fun args ->
150+
let self, args = self_and_args args in
151+
try fn t ~self:(unwrap_exn t self, self) ~args with
152+
| Py.Err _ as pyerr -> raise pyerr
153+
| exn ->
154+
let msg = Printf.sprintf !"ocaml error %{Exn#mach}" exn in
155+
raise (Py.Err (ValueError, msg)))
156+
| With_keywords fn ->
157+
Py.Callable.of_function_with_keywords ?docstring (fun args keywords ->
158+
try
159+
let self, args = self_and_args args in
160+
let keywords =
161+
Py_module.keywords_of_python keywords |> Or_error.ok_exn
162+
in
163+
fn t ~self:(unwrap_exn t self, self) ~args ~keywords
164+
with
165+
| Py.Err _ as pyerr -> raise pyerr
166+
| exn ->
167+
let msg = Printf.sprintf !"ocaml error %{Exn#mach}" exn in
168+
raise (Py.Err (ValueError, msg)))
169+
in
170+
name, fn)
171+
in
172+
let init =
173+
let fn =
174+
let docstring = Option.bind init ~f:Init.docstring in
175+
Py.Callable.of_function_as_tuple ?docstring (fun tuple ->
176+
try
177+
let self, args =
178+
match Py.Tuple.to_list tuple with
179+
| [] -> failwith "empty input"
180+
| p :: q -> p, q
181+
in
182+
let content =
183+
match init with
184+
| Some init -> init.fn t ~args |> wrap_capsule t
185+
| None -> Py.none
186+
in
187+
Py.Object.set_attr_string self content_field content;
188+
Py.none
189+
with
190+
| Py.Err _ as pyerr -> raise pyerr
191+
| exn ->
192+
let msg = Printf.sprintf !"ocaml error %{Exn#mach}" exn in
193+
raise (Py.Err (ValueError, msg)))
194+
in
195+
"__init__", fn
196+
in
197+
let cls_object =
198+
Py.Class.init name ~fields:[ content_field, Py.none ] ~methods:(init :: methods)
199+
in
200+
set_cls_object_exn t cls_object;
201+
t
202+
;;
203+
204+
let register_in_module t modl =
205+
Py_module.set_value modl t.name (Option.value_exn t.cls_object)
206+
;;

class_wrapper.mli

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
open Base
2+
open Import
3+
4+
type _ t
5+
6+
val wrap : 'a t -> 'a -> pyobject
7+
val unwrap_exn : 'a t -> pyobject -> 'a
8+
val unwrap : 'a t -> pyobject -> 'a option
9+
10+
module Init : sig
11+
type 'a cls = 'a t
12+
type 'a t
13+
14+
val create : ?docstring:string -> ('a cls -> args:pyobject list -> 'a) -> 'a t
15+
end
16+
17+
module Method : sig
18+
type 'a cls = 'a t
19+
type 'a t
20+
21+
val create
22+
: ?docstring:string
23+
-> string
24+
(** In the [methods] callbacks, [self] contains both the embeded ocaml
25+
value as well as the Python wrapper object. *)
26+
-> ('a cls -> self:'a * pyobject -> args:pyobject list -> pyobject)
27+
-> 'a t
28+
29+
val create_with_keywords
30+
: ?docstring:string
31+
-> string
32+
-> ('a cls
33+
-> self:'a * pyobject
34+
-> args:pyobject list
35+
-> keywords:(string, pyobject, String.comparator_witness) Map.t
36+
-> pyobject)
37+
-> 'a t
38+
39+
val defunc
40+
: ?docstring:string
41+
-> string
42+
-> ('a cls -> self:'a * pyobject -> pyobject Defunc.t)
43+
-> 'a t
44+
end
45+
46+
val make
47+
: ?to_string_repr:('a t -> 'a -> string)
48+
-> ?to_string:('a t -> 'a -> string)
49+
-> ?eq:('a t -> 'a -> 'a -> bool)
50+
-> ?init:'a Init.t
51+
-> string
52+
-> methods:'a Method.t list
53+
-> 'a t
54+
55+
val register_in_module : 'a t -> Py_module.t -> unit

0 commit comments

Comments
 (0)