├── pyops.ml.405 ├── pyops.mli.405 ├── .merlin ├── pyutop.ml ├── pyml_arch.mli ├── META ├── pyml_tests_common.mli ├── pytypes.mli ├── pytop.ml ├── .gitignore ├── pyml_arch.ml.c ├── pyops.ml.new ├── dune-project ├── pytypes.ml ├── pyml.opam ├── LICENSE ├── pyops.mli.new ├── README ├── dune ├── pyutils.ml ├── pyutils.mli ├── pyml_tests_common.ml ├── numpy.mli ├── numpy.ml ├── numpy_stubs.c ├── numpy_tests.ml ├── pyml_stubs.h ├── CHANGES.md ├── Makefile ├── README.md ├── pycaml.ml ├── pyml_tests.ml ├── pycaml.mli └── pyml_stubs.c /pyops.ml.405: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyops.mli.405: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | PKG stdcompat -------------------------------------------------------------------------------- /pyutop.ml: -------------------------------------------------------------------------------- 1 | let () = UTop_main.main () 2 | -------------------------------------------------------------------------------- /pyml_arch.mli: -------------------------------------------------------------------------------- 1 | type t = Windows | Mac | Unix 2 | 3 | val os : t 4 | 5 | val fd_of_int: int -> Unix.file_descr 6 | -------------------------------------------------------------------------------- /META: -------------------------------------------------------------------------------- 1 | description = "py.ml: OCaml bindings for Python" 2 | requires = "unix stdcompat" 3 | version = "20250807" 4 | archive(byte) = "pyml.cma numpy.cma" 5 | archive(native) = "pyml.cmxa numpy.cmxa" 6 | plugin(byte) = "pyml.cma numpy.cma" 7 | plugin(native) = "pyml.cmxs numpy.cmxs" 8 | -------------------------------------------------------------------------------- /pyml_tests_common.mli: -------------------------------------------------------------------------------- 1 | type status = 2 | | Passed 3 | | Failed of string 4 | | Disabled of string 5 | 6 | val add_test: title:string -> (unit -> status) -> unit 7 | 8 | val use_version: (int option * int option) ref 9 | 10 | val enable_only_on_unix: ('a -> status) -> 'a -> status 11 | 12 | val launch_tests : unit -> unit 13 | 14 | val main: unit -> unit 15 | -------------------------------------------------------------------------------- /pytypes.mli: -------------------------------------------------------------------------------- 1 | type pyobject 2 | 3 | type compare = LT | LE | EQ | NE | GT | GE 4 | 5 | type input = Single | File | Eval 6 | 7 | val int_of_compare: compare -> int 8 | 9 | val compare_of_int: int -> compare 10 | 11 | val input_of_int: int -> input 12 | 13 | type 'a file = Filename of string | Channel of 'a 14 | 15 | val file_map: ('a -> 'b) -> 'a file -> 'b file 16 | -------------------------------------------------------------------------------- /pytop.ml: -------------------------------------------------------------------------------- 1 | let eval_exn str = 2 | let lexbuf = Lexing.from_string str in 3 | let phrase = !Toploop.parse_toplevel_phrase lexbuf in 4 | Toploop.execute_phrase false Format.err_formatter phrase 5 | 6 | let () = 7 | assert 8 | (eval_exn (Printf.sprintf "#directory \"%s\";;" Pymltop_libdir.libdir)); 9 | assert (eval_exn "#install_printer Py.Object.format_repr;;") 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _opam 2 | _build 3 | .merlin 4 | .depend 5 | *.cm[aioxt] 6 | *.cmti 7 | *.cmx[as] 8 | *.[ao] 9 | *.so 10 | *.swp 11 | *~ 12 | *.inc 13 | /generate 14 | /pyml.h 15 | /pyml_libdir.ml 16 | /pyml_compat.ml 17 | /pyml_arch.ml 18 | /pywrappers.mli 19 | /pywrappers.ml 20 | /pymlutop 21 | /pyml_tests.bytecode 22 | /numpy_tests.bytecode 23 | /pyml_tests.native 24 | /numpy_tests.native 25 | /pyops.ml 26 | /pyops.mli 27 | /pyml_arch_generate.exe 28 | -------------------------------------------------------------------------------- /pyml_arch.ml.c: -------------------------------------------------------------------------------- 1 | #if __APPLE__ 2 | #define PLATFORM_NAME Mac 3 | #elif defined(WIN32) || defined(_WIN32) 4 | #define PLATFORM_NAME Windows 5 | #define WIN_HANDLE_FD 6 | #elif unix 7 | #define PLATFORM_NAME Unix 8 | #else 9 | #error "Unknown platform" 10 | #endif 11 | 12 | type t = Windows | Mac | Unix 13 | 14 | let os = PLATFORM_NAME 15 | 16 | #ifdef WIN_HANDLE_FD 17 | external fd_of_int : int -> Unix.file_descr = "win_handle_fd" 18 | #else 19 | external fd_of_int : int -> Unix.file_descr = "%identity" 20 | #endif 21 | -------------------------------------------------------------------------------- /pyops.ml.new: -------------------------------------------------------------------------------- 1 | let ( .@() ) = Py.Object.find_attr 2 | 3 | let ( .@$() ) = Py.Object.find_attr_string 4 | 5 | let ( .@()<- ) = Py.Object.set_attr 6 | 7 | let ( .@$()<- ) = Py.Object.set_attr_string 8 | 9 | let ( .![] ) = Py.Object.find 10 | 11 | let ( .!$[] ) = Py.Object.find_string 12 | 13 | let ( .![]<- ) = Py.Object.set_item 14 | 15 | let ( .!$[]<- ) = Py.Object.set_item_string 16 | 17 | let ( .%[] ) = Py.Dict.find 18 | 19 | let ( .%$[] ) = Py.Dict.find_string 20 | 21 | let ( .%[]<- ) = Py.Dict.set_item 22 | 23 | let ( .%$[]<- ) = Py.Dict.set_item_string 24 | 25 | let ( .&() ) = Py.Module.get_function 26 | 27 | let ( .&()<- ) = Py.Module.set_function 28 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.8) 2 | 3 | (name pyml) 4 | 5 | (license BSD-2-Clause) 6 | 7 | (maintainers "Seb Hinderer ") 8 | 9 | (authors "Thierry Martinez ") 10 | 11 | (source 12 | (github ocamllibs/pyml)) 13 | 14 | (bug_reports "http://github.com/ocamllibs/pyml/issues") 15 | 16 | (homepage "http://github.com/ocamllibs/pyml") 17 | 18 | (documentation "https://github.com/ocamllibs/pyml") 19 | 20 | (generate_opam_files true) 21 | 22 | (package 23 | (name pyml) 24 | (synopsis "OCaml bindings for Python") 25 | (description "OCaml bindings for Python 2 and Python 3") 26 | (depends 27 | (ocaml (>= 4.11.0)) 28 | (ocamlfind :build) 29 | (stdcompat (>= 18)) 30 | (conf-python-3-dev :with-test)) 31 | (depopts utop)) 32 | -------------------------------------------------------------------------------- /pytypes.ml: -------------------------------------------------------------------------------- 1 | type pyobject 2 | 3 | type compare = LT | LE | EQ | NE | GT | GE 4 | 5 | type input = Single | File | Eval 6 | 7 | let int_of_compare c = 8 | match c with 9 | LT -> 0 10 | | LE -> 1 11 | | EQ -> 2 12 | | NE -> 3 13 | | GT -> 4 14 | | GE -> 5 15 | 16 | let compare_of_int c = 17 | match c with 18 | 0 -> LT 19 | | 1 -> LE 20 | | 2 -> EQ 21 | | 3 -> NE 22 | | 4 -> GT 23 | | 5 -> GE 24 | | _ -> failwith "Pytypes.compare_of_int" 25 | 26 | let input_of_int input = 27 | match input with 28 | 256 -> Single 29 | | 257 -> File 30 | | 258 -> Eval 31 | | _ -> failwith "Pytypes.input_of_int" 32 | 33 | type 'a file = Filename of string | Channel of 'a 34 | 35 | let file_map f x = 36 | match x with 37 | Filename filename -> Filename filename 38 | | Channel channel -> Channel (f channel) 39 | -------------------------------------------------------------------------------- /pyml.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "OCaml bindings for Python" 4 | description: "OCaml bindings for Python 2 and Python 3" 5 | maintainer: ["Seb Hinderer "] 6 | authors: ["Thierry Martinez "] 7 | license: "BSD-2-Clause" 8 | homepage: "http://github.com/ocamllibs/pyml" 9 | doc: "https://github.com/ocamllibs/pyml" 10 | bug-reports: "http://github.com/ocamllibs/pyml/issues" 11 | depends: [ 12 | "dune" {>= "2.8"} 13 | "ocaml" {>= "4.11.0"} 14 | "stdcompat" {>= "18"} 15 | "conf-python-3-dev" {with-test} 16 | "odoc" {with-doc} 17 | ] 18 | depopts: ["utop"] 19 | build: [ 20 | ["dune" "subst"] {dev} 21 | [ 22 | "dune" 23 | "build" 24 | "-p" 25 | name 26 | "-j" 27 | jobs 28 | "@install" 29 | "@runtest" {with-test} 30 | "@doc" {with-doc} 31 | ] 32 | ] 33 | dev-repo: "git+https://github.com/ocamllibs/pyml.git" 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2016-2021, Thierry Martinez. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /pyops.mli.new: -------------------------------------------------------------------------------- 1 | val ( .@() ) : Py.Object.t -> Py.Object.t -> Py.Object.t 2 | (** Equivalent to {!Py.Object.find_attr}. *) 3 | 4 | val ( .@$() ) : Py.Object.t -> string -> Py.Object.t 5 | (** Equivalent to {!Py.Object.find_attr_string}. *) 6 | 7 | val ( .@()<- ) : Py.Object.t -> Py.Object.t -> Py.Object.t -> unit 8 | (** Equivalent to {!Py.Object.set_attr}. *) 9 | 10 | val ( .@$()<- ) : Py.Object.t -> string -> Py.Object.t -> unit 11 | (** Equivalent to {!Py.Object.set_attr_string}. *) 12 | 13 | val ( .![] ) : Py.Object.t -> Py.Object.t -> Py.Object.t 14 | (** Equivalent to {!Py.Object.find}. *) 15 | 16 | val ( .!$[] ) : Py.Object.t -> string -> Py.Object.t 17 | (** Equivalent to {!Py.Object.find_string}. *) 18 | 19 | val ( .![]<- ) : Py.Object.t -> Py.Object.t -> Py.Object.t -> unit 20 | (** Equivalent to {!Py.Object.set_item}. *) 21 | 22 | val ( .!$[]<- ) : Py.Object.t -> string -> Py.Object.t -> unit 23 | (** Equivalent to {!Py.Object.set_item_string}. *) 24 | 25 | val ( .%[] ) : Py.Object.t -> Py.Object.t -> Py.Object.t 26 | (** Equivalent to {!Py.Dict.find}. *) 27 | 28 | val ( .%$[] ) : Py.Object.t -> string -> Py.Object.t 29 | (** Equivalent to {!Py.Dict.find_string}. *) 30 | 31 | val ( .%[]<- ) : Py.Object.t -> Py.Object.t -> Py.Object.t -> unit 32 | (** Equivalent to {!Py.Dict.set_item}. *) 33 | 34 | val ( .%$[]<- ) : Py.Object.t -> string -> Py.Object.t -> unit 35 | (** Equivalent to {!Py.Dict.set_item_string}. *) 36 | 37 | val ( .&() ) : Py.Object.t -> string -> Py.Object.t array -> Py.Object.t 38 | (** Equivalent to {!Py.Module.get_function}. *) 39 | 40 | val ( .&()<- ) : Py.Object.t -> string -> (Py.Object.t array -> Py.Object.t) 41 | -> unit 42 | (** Equivalent to {!Py.Module.set_function}. *) 43 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | py.ml: OCaml bindings for Python 2 | 3 | py.ml provides OCaml bindings for Python 2 and Python 4 | 3. This library subsumes the pycaml library, which is no longer 5 | actively maintained. 6 | 7 | Homepage: http://pyml.gforge.inria.fr 8 | 9 | Documentation: http://pyml.gforge.inria.fr/doc 10 | 11 | Git: git clone http://pyml.gforge.inria.fr/pyml.git 12 | 13 | Git Repository Browser: http://pyml.gforge.inria.fr/browser 14 | 15 | Tracker for bug reports and feature requests: 16 | http://pyml.gforge.inria.fr/tracker 17 | 18 | OPAM: opam install pyml 19 | 20 | The Python library is linked at runtime and the same executable can be 21 | run in a Python 2 or a Python 3 environment. py.ml does not 22 | require any Python library at compile time. 23 | The only compile time dependency is Stdcompat to ensure compatibility 24 | with all OCaml compiler versions from 3.12: 25 | https://github.com/thierry-martinez/stdcompat/ 26 | 27 | 28 | Bindings are split in three modules: 29 | 30 | - Py provides the initialization functions and some high-level 31 | bindings, with error handling and naming conventions closer to OCaml 32 | usages. 33 | 34 | - Pycaml provides a signature close to the old Pycaml 35 | module, so as to ease migration. 36 | 37 | - Pywrappers provides low-level bindings, which follow closely the 38 | conventions of the C bindings for Python. Submodules 39 | Pywrappers.Python2 and Pywrappers.Python3 contain version-specific 40 | bindings. 41 | 42 | Custom top-level 43 | 44 | A custom top-level with the C bindings can be compiled by make pymltop. 45 | 46 | If you have utop and ocamlfind, you can make pymlutop. 47 | 48 | For OPAM users: pymltop is installed by default by opam install pyml. 49 | pymlutop is installed whenever utop is available. 50 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name pyml) 3 | (modules numpy py pyops pycaml pyml_arch pytypes pywrappers pyutils) 4 | (foreign_stubs (language c) (names numpy_stubs pyml_stubs)) 5 | (wrapped false) 6 | (libraries unix bigarray stdcompat)) 7 | 8 | (executables 9 | (names generate) 10 | (modules generate) 11 | (libraries stdcompat)) 12 | 13 | (rule 14 | (targets pywrappers.ml pyml.h pyml_dlsyms.inc pyml_wrappers.inc) 15 | (deps (:gen generate.exe)) 16 | (action (run %{gen}))) 17 | 18 | (rule 19 | (target pyml_arch.ml.sharp) 20 | (deps pyml_arch.ml.c) 21 | (action (with-stdout-to %{target} 22 | (run %{ocaml-config:native_c_compiler} -E %{deps})))) 23 | 24 | (rule 25 | (target pyml_arch.ml) 26 | (deps pyml_arch.ml.sharp) 27 | (action (with-stdout-to %{target} 28 | (run sed "/^#/d" %{deps})))) 29 | 30 | (library 31 | (name pyml_tests_common) 32 | (modules pyml_tests_common) 33 | (libraries pyml stdcompat)) 34 | 35 | (test 36 | (name numpy_tests) 37 | (modules numpy_tests) 38 | (libraries pyml pyml_tests_common stdcompat)) 39 | 40 | (test 41 | (name pyml_tests) 42 | (modules pyml_tests) 43 | (libraries pyml pyml_tests_common stdcompat)) 44 | 45 | (rule 46 | (enabled_if (>= %{ocaml_version} 4.06)) 47 | (target pyops.mli) 48 | (deps pyops.mli.new) 49 | (action (copy %{deps} %{target}))) 50 | 51 | (rule 52 | (enabled_if (>= %{ocaml_version} 4.06)) 53 | (target pyops.ml) 54 | (deps pyops.ml.new) 55 | (action (copy %{deps} %{target}))) 56 | 57 | (rule 58 | (enabled_if (< %{ocaml_version} 4.06)) 59 | (target pyops.mli) 60 | (deps pyops.mli.405) 61 | (action (copy %{deps} %{target}))) 62 | 63 | (rule 64 | (enabled_if (< %{ocaml_version} 4.06)) 65 | (target pyops.ml) 66 | (deps pyops.ml.405) 67 | (action (copy %{deps} %{target}))) 68 | -------------------------------------------------------------------------------- /pyutils.ml: -------------------------------------------------------------------------------- 1 | open Stdcompat 2 | 3 | let option_find f x = 4 | try Some (f x) 5 | with Not_found -> None 6 | 7 | let substring_between string before after = 8 | String.sub string before (after - before) 9 | 10 | let int_of_octal octal = 11 | int_of_string ("0o" ^ octal) 12 | 13 | let int_of_hex hex = 14 | int_of_string ("0x" ^ hex) 15 | 16 | let split_left_on_char ?(from=0) char s = 17 | try substring_between s from (String.index_from s from char) 18 | with Not_found -> 19 | if from = 0 then s 20 | else substring_between s from (String.length s) 21 | 22 | let split_right_on_char ?(from=0) char s = 23 | try 24 | substring_between s (String.index_from s from char + 1) 25 | (String.length s) 26 | with Not_found -> 27 | if from = 0 then s 28 | else substring_between s from (String.length s) 29 | 30 | let trim_carriage_return line = 31 | let length = String.length line in 32 | if String.sub line (length - 1) 1 = "\r" then 33 | String.sub line 0 (length - 1) 34 | else 35 | line 36 | 37 | let input_lines channel = 38 | let accu = ref [] in 39 | try 40 | while true do 41 | accu := trim_carriage_return (input_line channel) :: !accu; 42 | done; 43 | assert false 44 | with End_of_file -> 45 | List.rev !accu 46 | 47 | let write_and_close channel f arg = 48 | try 49 | let result = f arg in 50 | close_out channel; 51 | result 52 | with e -> 53 | close_out_noerr channel; 54 | raise e 55 | 56 | let with_temp_file contents f = 57 | let (file, channel) = Filename.open_temp_file "pyml_tests" ".py" in 58 | Fun.protect begin fun () -> 59 | write_and_close channel (output_string channel) contents; 60 | Stdcompat.In_channel.with_open_bin file (f file) 61 | end 62 | ~finally:(fun () -> Sys.remove file) 63 | 64 | let with_pipe f = 65 | let (read, write) = Unix.pipe () in 66 | let in_channel = Unix.in_channel_of_descr read 67 | and out_channel = Unix.out_channel_of_descr write in 68 | Fun.protect begin fun () -> 69 | f in_channel out_channel 70 | end 71 | ~finally:begin fun () -> 72 | close_in_noerr in_channel; 73 | close_out_noerr out_channel 74 | end 75 | 76 | let with_stdin_from channel f arg = 77 | let stdin_backup = Unix.dup Unix.stdin in 78 | Unix.dup2 (Unix.descr_of_in_channel channel) Unix.stdin; 79 | Fun.protect begin fun () -> 80 | f arg 81 | end 82 | ~finally:begin fun () -> 83 | Unix.dup2 stdin_backup Unix.stdin 84 | end 85 | 86 | let with_channel_from_string s f = 87 | with_pipe begin fun in_channel out_channel -> 88 | output_string out_channel s; 89 | close_out out_channel; 90 | f in_channel 91 | end 92 | 93 | let with_stdin_from_string s f arg = 94 | with_channel_from_string s (fun channel -> with_stdin_from channel f arg) 95 | -------------------------------------------------------------------------------- /pyutils.mli: -------------------------------------------------------------------------------- 1 | (** This module declares utility functions that does not require Python to 2 | be initialized. *) 3 | 4 | val substring_between: string -> int -> int -> string 5 | (** [substring_between s i j] returns the substring of [s] between the indices 6 | [i] (included) and [j] (excluded). *) 7 | 8 | val int_of_octal: string -> int 9 | (** Returns the integer represented by the argument written in base 8. *) 10 | 11 | val int_of_hex: string -> int 12 | (** Returns the integer represented by the argument written in base 16. *) 13 | 14 | val split_left_on_char: ?from:int -> char -> string -> string 15 | (** If the character occurs in the substring beginning from [from], 16 | returns the prefix that precedes the first occurrence (excluded), 17 | else returns the whole substring beginning from [from]. *) 18 | 19 | val split_right_on_char: ?from:int -> char -> string -> string 20 | (** If the character occurs in the substring beginning from [from], 21 | returns the suffix that succedes the first occurrence (excluded), 22 | else returns the whole substring beginning from [from]. *) 23 | 24 | val trim_carriage_return: string -> string 25 | (** If the string ends with ['\r'], then returns the string without this 26 | character, else returns the whole string. *) 27 | 28 | val input_lines: in_channel -> string list 29 | (** Reads and returns all the lines from an input channel to the end of file. 30 | Carriage return characters are removed from the end of lines if any. *) 31 | 32 | val option_find: ('a -> 'b) -> 'a -> 'b option 33 | (** [option_find f x] returns [Some (f x)], or [None] if [f x] raises 34 | [Not_found]. *) 35 | 36 | val write_and_close: out_channel -> ('a -> 'b) -> 'a -> 'b 37 | (** [write_and_close channel f arg] calls [f arg], and returns the result of 38 | [f]. 39 | [channel] is always closed after [f] has been called, even if [f] raises 40 | an exception. *) 41 | 42 | val with_temp_file: string -> (string -> in_channel -> 'a) -> 'a 43 | (** [with_temp_file s f] creates a temporary file with [s] as contents and 44 | calls [f filename in_channel] where [filename] is the name of the 45 | temporary file and [in_channel] is an input channel opened to read the 46 | file. The file is deleted after the execution of [f] (even if [f] 47 | raised an exception. *) 48 | 49 | val with_pipe: (in_channel -> out_channel -> 'a) -> 'a 50 | (** [with_pipe f] creates a pipe and calls [f] with the two ends of the 51 | pipe. *) 52 | 53 | val with_stdin_from: in_channel -> ('a -> 'b) -> 'a -> 'b 54 | (** [with_stdin_from chan f arg] calls [f arg] with the standard input 55 | redirected for reading from [chan]. *) 56 | 57 | val with_channel_from_string: string -> (in_channel -> 'a) -> 'a 58 | (** [with_channel_from_string s f] calls [f in_channel] where [in_channel] 59 | is an input channel returning the contents of [s]. *) 60 | 61 | val with_stdin_from_string: string -> ('a -> 'b) -> 'a -> 'b 62 | (** [with_stdin_from_string s f arg] calls [f arg] with the standard input 63 | redirected for reading from the contents of [s]. *) 64 | -------------------------------------------------------------------------------- /pyml_tests_common.ml: -------------------------------------------------------------------------------- 1 | let use_version = ref (None, None) 2 | 3 | type status = 4 | | Passed 5 | | Failed of string 6 | | Disabled of string 7 | 8 | let tests = Queue.create () 9 | 10 | let add_test ~title f = 11 | Queue.add (title, f) tests 12 | 13 | let failed = ref false 14 | 15 | let launch_test (title, f) = 16 | Printf.printf "Test '%s' ... %!" title; 17 | try 18 | match f () with 19 | Passed -> Printf.printf "passed\n%!" 20 | | Failed reason -> 21 | Printf.printf "failed: %s\n%!" reason; 22 | failed := true 23 | | Disabled reason -> Printf.printf "disabled: %s\n%!" reason 24 | with 25 | Py.E (ty, value) -> 26 | Printf.printf 27 | "raised a Python exception: [%s] %s\n%!" 28 | (Py.Object.to_string ty) 29 | (Py.Object.to_string value); 30 | failed := true 31 | | e -> 32 | Printf.printf "raised an exception: %s\n%!" (Printexc.to_string e); 33 | failed := true 34 | 35 | let rec launch_tests () = 36 | match 37 | try Some (Queue.pop tests) 38 | with Queue.Empty -> None 39 | with 40 | None -> () 41 | | Some test -> 42 | launch_test test; 43 | launch_tests () 44 | 45 | let enable_only_on_unix f arg = 46 | if Sys.os_type = "Unix" then 47 | f arg 48 | else 49 | Disabled "only on Unix" 50 | 51 | let show_environment_variable envvar = 52 | try 53 | Printf.eprintf "%s=%s\n" envvar (Sys.getenv envvar) 54 | with Not_found -> 55 | Printf.eprintf "%s not set\n" envvar 56 | 57 | let main () = 58 | let library_name, version, minor = 59 | match Sys.argv with 60 | [| _ |] -> None, None, None 61 | | [| _; version |] -> 62 | begin 63 | match String.length version with 64 | 1 -> None, Some (int_of_string version), None 65 | | (3 | 4) when version.[1] = '.' -> 66 | None, 67 | Some (int_of_string (String.sub version 0 1)), 68 | Some 69 | (int_of_string (String.sub version 2 (String.length version - 2))) 70 | | _ -> Some version, None, None 71 | end 72 | | _ -> failwith "Argument should be a version number" in 73 | use_version := (version, minor); 74 | prerr_endline "Environment variables:"; 75 | show_environment_variable "PATH"; 76 | show_environment_variable "PYTHONHOME"; 77 | show_environment_variable "DYLD_LIBRARY_PATH"; 78 | show_environment_variable "DYLD_FALLBACK_LIBRARY_PATH"; 79 | prerr_endline "Initializing library..."; 80 | Py.initialize ?library_name ~verbose:true ?version ?minor ~debug_build:true (); 81 | begin 82 | match Py.get_library_filename () with 83 | None -> prerr_endline "No library has been loaded.\n" 84 | | Some filename -> Printf.eprintf "Library \"%s\" has been loaded.\n" filename 85 | end; 86 | Format.eprintf "platform: %s@." (Pywrappers.py_getplatform ()); 87 | Format.eprintf "build info: %s@." (Pywrappers.py_getbuildinfo ()); 88 | if Py.is_debug_build () then 89 | prerr_endline "Debug build." 90 | else 91 | prerr_endline "Not a debug build."; 92 | prerr_endline "Starting tests..."; 93 | launch_tests (); 94 | if !failed then 95 | exit 1 96 | -------------------------------------------------------------------------------- /numpy.mli: -------------------------------------------------------------------------------- 1 | (** OCaml Interface for Numpy. *) 2 | 3 | (** Arrays are passed in place (without copy): Python and OCaml programs 4 | can change the contents of the array and the changes are visible in 5 | the other language. *) 6 | 7 | (** The following table gives the correspondence between bigarray kinds 8 | and Numpy element types. 9 | {ul 10 | {li [float32] / [NPY_FLOAT]} 11 | {li [float64] / [NPY_DOUBLE]} 12 | {li [int8_signed] / [NPY_BYTE]} 13 | {li [int8_unsigned] / [NPY_UBYTE]} 14 | {li [int16_signed] / [NPY_SHORT]} 15 | {li [int16_unsigned] / [NPY_USHORT]} 16 | {li [int32] / [NPY_INT]} 17 | {li [int64] / [NPY_LONGLONG]} 18 | {li [nativeint] / [NPY_LONG]} 19 | {li [complex32] / [NPY_CFLOAT]} 20 | {li [complex64] / [NPY_CDOUBLE]} 21 | {li [char] / [NPY_CHAR]}} 22 | Other kinds/element types are not supported. In particular, OCaml 23 | integer kind, [int], has no equivalent type in Numpy. *) 24 | 25 | val of_bigarray: 26 | ('a, 'b, 'c) Bigarray.Genarray.t -> Py.Object.t 27 | (** [of_bigarray a] returns a Numpy array that shares the same contents 28 | than the OCaml array [a]. *) 29 | 30 | val to_bigarray: 31 | ('a, 'b) Bigarray.kind -> 'c Bigarray.layout -> Py.Object.t -> 32 | ('a, 'b, 'c) Bigarray.Genarray.t 33 | (** [to_bigarray kind layout a] returns a bigarray that shares the same 34 | contents than the Numpy array [a]. 35 | If `kind` and/or `layout` are unknown, you may use {!val:to_bigarray_k}. *) 36 | 37 | type ('a, 'b, 'c) to_bigarray = 38 | { kind : ('a, 'b) Bigarray.kind 39 | ; layout : 'c Bigarray.layout 40 | ; array : ('a, 'b, 'c) Bigarray.Genarray.t 41 | } 42 | 43 | type 'r to_bigarray_k = 44 | { f : 'a 'b 'c . ('a, 'b, 'c) to_bigarray -> 'r } 45 | 46 | val to_bigarray_k : 'r to_bigarray_k -> Py.Object.t -> 'r 47 | (** [to_bigarray_k k a] calls [k.f] with the contents of the Numpy array [a]. 48 | [k.f] has to be polymorphic in the kind and the layout of the bigarray: 49 | functions {!val:compare_kind}, {!val:compare_layout} and 50 | {!val:check_kind_and_layout} can be used to introspect the bigarray 51 | polymorphically. *) 52 | 53 | val compare_kind : ('a, 'b) Bigarray.kind -> ('c, 'd) Bigarray.kind -> int 54 | (** [compare_kind] provides a total order on {!val:Bigarray.kind}. 55 | As opposed to generic [compare] of OCaml standard libary, 56 | [compare_kind] is polymorphic in the kind of the bigarray. *) 57 | 58 | val compare_layout : 'a Bigarray.layout -> 'b Bigarray.layout -> int 59 | (** [compare_layout] provides a total order on {!val:Bigarray.layout}. 60 | As opposed to generic [compare] of OCaml standard libary, 61 | [compare_kind] is polymorphic in the layout of the bigarray. *) 62 | 63 | val check_kind_and_layout : 64 | ('a, 'b) Bigarray.kind -> 'c Bigarray.layout -> 65 | ('d, 'e, 'f) Bigarray.Genarray.t -> 66 | ('a, 'b, 'c) Bigarray.Genarray.t option 67 | (** [check_kind_and_layout kind layout a] returns [Some a] if [a] has the given 68 | [kind] and [layout] (that is to say, if we have the following type 69 | equalities, ['a = 'd], ['b = 'e] and ['c = 'f]). 70 | This function allows the callback of {!val:to_bigarray_k} to be polymorphic 71 | in the kind of the array. *) 72 | -------------------------------------------------------------------------------- /numpy.ml: -------------------------------------------------------------------------------- 1 | external pyarray_of_bigarray: Py.Object.t -> Py.Object.t 2 | -> ('a, 'b, 'c) Bigarray.Genarray.t 3 | -> Py.Object.t = "pyarray_of_bigarray_wrapper" 4 | 5 | external bigarray_of_pyarray: Py.Object.t -> Py.Object.t 6 | -> ('a, 'b) Bigarray.kind * 'c Bigarray.layout * ('a, 'b, 'c) Bigarray.Genarray.t 7 | = "bigarray_of_pyarray_wrapper" 8 | 9 | let pyarray_subtype_ref = ref None 10 | 11 | let () = Py.on_finalize (fun () -> pyarray_subtype_ref := None) 12 | 13 | let pyarray_subtype () = 14 | match !pyarray_subtype_ref with 15 | Some pyarray_subtype -> pyarray_subtype 16 | | None -> 17 | let pyarray_type = Py.Array.pyarray_type () in 18 | let pyarray_subtype = 19 | Py.Type.create "ocamlbigarray" [pyarray_type] 20 | [("ocamlbigarray", Py.none)] in 21 | pyarray_subtype_ref := Some pyarray_subtype; 22 | pyarray_subtype 23 | 24 | let of_bigarray bigarray = 25 | let result = 26 | pyarray_of_bigarray (Py.Array.numpy_api ()) (pyarray_subtype ()) bigarray in 27 | let result = Py.check_not_null result in 28 | let capsule = Py.Capsule.unsafe_wrap_value bigarray in 29 | Py.Object.set_attr_string result "ocamlbigarray" capsule; 30 | result 31 | 32 | (* written with equalities to support OCaml pre-GADT *) 33 | let string_of_kind kind = 34 | if kind = Obj.magic Bigarray.float32 then 35 | "float32/NPY_FLOAT" 36 | else if kind = Obj.magic Bigarray.float64 then 37 | "float64/NPY_DOUBLE" 38 | else if kind = Obj.magic Bigarray.int8_signed then 39 | "int8_signed/NPY_BYTE" 40 | else if kind = Obj.magic Bigarray.int8_unsigned then 41 | "int8_unsigned/NPY_UBYTE" 42 | else if kind = Obj.magic Bigarray.int16_signed then 43 | "int16_signed/NPY_SHORT" 44 | else if kind = Obj.magic Bigarray.int16_unsigned then 45 | "int16_unsigned/NPY_USHORT" 46 | else if kind = Obj.magic Bigarray.int32 then 47 | "int32/NPY_INT" 48 | else if kind = Obj.magic Bigarray.int64 then 49 | "int64/NPY_LONGLONG" 50 | else if kind = Obj.magic Bigarray.int then 51 | "int" 52 | else if kind = Obj.magic Bigarray.nativeint then 53 | "nativeint/NPY_LONG" 54 | else if kind = Obj.magic Bigarray.complex32 then 55 | "complex32/NPY_CFLOAT" 56 | else if kind = Obj.magic Bigarray.complex64 then 57 | "complex64/NPY_CDOUBLE" 58 | else if kind = Obj.magic Bigarray.char then 59 | "char/NPY_CHAR" 60 | else 61 | "unknown kind" 62 | 63 | let string_of_layout layout = 64 | if layout = Obj.magic Bigarray.c_layout then 65 | "C" 66 | else if layout = Obj.magic Bigarray.fortran_layout then 67 | "Fortran" 68 | else 69 | "unknown layout" 70 | 71 | let to_bigarray kind layout t = 72 | if not (Py.Object.is_instance t (Py.Array.pyarray_type ())) then 73 | invalid_arg "Numpy.to_bigarray"; 74 | let kind', layout', array = bigarray_of_pyarray (Py.Array.numpy_api ()) t in 75 | if kind <> kind' then 76 | invalid_arg (Printf.sprintf 77 | "Numpy.to_bigarray: Numpy array has elements of kind %s, but to_bigarray expected %s" 78 | (string_of_kind kind') (string_of_kind kind)); 79 | if layout <> layout' then 80 | invalid_arg (Printf.sprintf 81 | "Numpy.to_bigarray: Numpy array has %s layout, but to_bigarray expected %s" 82 | (string_of_layout layout') (string_of_layout layout)); 83 | array 84 | 85 | type ('a, 'b, 'c) to_bigarray = 86 | { kind : ('a, 'b) Bigarray.kind 87 | ; layout : 'c Bigarray.layout 88 | ; array : ('a, 'b, 'c) Bigarray.Genarray.t 89 | } 90 | 91 | type 'r to_bigarray_k = 92 | { f : 'a 'b 'c . ('a, 'b, 'c) to_bigarray -> 'r } 93 | 94 | let to_bigarray_k (k : 'r to_bigarray_k) t : 'r = 95 | if not (Py.Object.is_instance t (Py.Array.pyarray_type ())) then 96 | invalid_arg "Numpy.to_bigarray"; 97 | let kind, layout, array = bigarray_of_pyarray (Py.Array.numpy_api ()) t in 98 | k.f { kind; layout; array } 99 | 100 | external compare_kind : 101 | ('a, 'b) Bigarray.kind -> ('c, 'd) Bigarray.kind -> int = "%compare" 102 | 103 | external compare_layout : 104 | 'a Bigarray.layout -> 'b Bigarray.layout -> int = "%compare" 105 | 106 | let check_kind_and_layout (kind : ('a, 'b) Bigarray.kind) 107 | (layout : 'c Bigarray.layout) t : 108 | ('a, 'b, 'c) Bigarray.Genarray.t option = 109 | if compare_kind kind (Bigarray.Genarray.kind t) = 0 && 110 | compare_layout layout (Bigarray.Genarray.layout t) = 0 then 111 | Some (Obj.magic t) 112 | else 113 | None 114 | -------------------------------------------------------------------------------- /numpy_stubs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "pyml_stubs.h" 8 | 9 | value 10 | pyml_wrap(PyObject *object, bool steal); 11 | 12 | PyObject * 13 | pyml_unwrap(value v); 14 | 15 | struct numpy_custom_operations { 16 | struct custom_operations ops; 17 | PyObject *obj; 18 | }; 19 | 20 | static void numpy_finalize(value v) 21 | { 22 | struct numpy_custom_operations *ops = 23 | (struct numpy_custom_operations *) Custom_ops_val(v); 24 | Py_DECREF(ops->obj); 25 | free(ops); 26 | } 27 | 28 | CAMLprim value 29 | pyarray_of_bigarray_wrapper( 30 | value numpy_api_ocaml, value bigarray_type_ocaml, value bigarray_ocaml) 31 | { 32 | CAMLparam3(numpy_api_ocaml, bigarray_type_ocaml, bigarray_ocaml); 33 | pyml_assert_initialized(); 34 | PyObject *c_api = pyml_unwrap(numpy_api_ocaml); 35 | void **PyArray_API = pyml_get_pyarray_api(c_api); 36 | PyObject *(*PyArray_New) 37 | (PyTypeObject *, int, npy_intp *, int, npy_intp *, void *, int, int, 38 | PyObject *) = PyArray_API[93]; 39 | int nd = Caml_ba_array_val(bigarray_ocaml)->num_dims; 40 | npy_intp *dims = malloc(nd * sizeof(npy_intp)); 41 | int i; 42 | for (i = 0; i < nd; i++) { 43 | dims[i] = Caml_ba_array_val(bigarray_ocaml)->dim[i]; 44 | } 45 | int type_num; 46 | intnat flags = Caml_ba_array_val(bigarray_ocaml)->flags; 47 | switch (flags & CAML_BA_KIND_MASK) { 48 | case CAML_BA_FLOAT32: 49 | type_num = NPY_FLOAT; 50 | break; 51 | case CAML_BA_FLOAT64: 52 | type_num = NPY_DOUBLE; 53 | break; 54 | case CAML_BA_SINT8: 55 | type_num = NPY_BYTE; 56 | break; 57 | case CAML_BA_UINT8: 58 | type_num = NPY_UBYTE; 59 | break; 60 | case CAML_BA_SINT16: 61 | type_num = NPY_SHORT; 62 | break; 63 | case CAML_BA_UINT16: 64 | type_num = NPY_USHORT; 65 | break; 66 | case CAML_BA_INT32: 67 | type_num = NPY_INT; 68 | break; 69 | case CAML_BA_INT64: 70 | type_num = NPY_LONGLONG; 71 | break; 72 | case CAML_BA_CAML_INT: 73 | caml_failwith("Caml integers are unsupported for NumPy array"); 74 | break; 75 | case CAML_BA_NATIVE_INT: 76 | type_num = NPY_LONG; 77 | break; 78 | case CAML_BA_COMPLEX32: 79 | type_num = NPY_CFLOAT; 80 | break; 81 | case CAML_BA_COMPLEX64: 82 | type_num = NPY_CDOUBLE; 83 | break; 84 | #ifdef CAML_BA_CHAR /* introduced in 4.02.0 */ 85 | case CAML_BA_CHAR: 86 | type_num = NPY_CHAR; 87 | break; 88 | #endif 89 | default: 90 | caml_failwith("Unsupported bigarray kind for NumPy array"); 91 | } 92 | int np_flags; 93 | switch (flags & CAML_BA_LAYOUT_MASK) { 94 | case CAML_BA_C_LAYOUT: 95 | np_flags = NPY_ARRAY_CARRAY; 96 | break; 97 | case CAML_BA_FORTRAN_LAYOUT: 98 | np_flags = NPY_ARRAY_FARRAY; 99 | break; 100 | default: 101 | caml_failwith("Unsupported bigarray layout for NumPy array"); 102 | } 103 | void *data = Caml_ba_data_val(bigarray_ocaml); 104 | PyTypeObject (*PyArray_SubType) = 105 | (PyTypeObject *) pyml_unwrap(bigarray_type_ocaml); 106 | PyObject *result = PyArray_New( 107 | PyArray_SubType, nd, dims, type_num, NULL, data, 0, 108 | np_flags, NULL); 109 | free(dims); 110 | CAMLreturn(pyml_wrap(result, true)); 111 | } 112 | 113 | CAMLprim value 114 | bigarray_of_pyarray_wrapper( 115 | value numpy_api_ocaml, value pyarray_ocaml) 116 | { 117 | CAMLparam2(numpy_api_ocaml, pyarray_ocaml); 118 | CAMLlocal2(bigarray, result); 119 | pyml_assert_initialized(); 120 | PyObject *array = pyml_unwrap(pyarray_ocaml); 121 | PyArrayObject_fields *fields = 122 | (PyArrayObject_fields *) pyobjectdescr(array); 123 | int nd = fields->nd; 124 | npy_intp *shape = fields->dimensions; 125 | intnat *dims = malloc(nd * sizeof(intnat)); 126 | int i; 127 | for (i = 0; i < nd; i++) { 128 | dims[i] = shape[i]; 129 | } 130 | int type = fields->descr->type_num; 131 | enum caml_ba_kind kind; 132 | switch (type) { 133 | case NPY_BYTE: 134 | kind = CAML_BA_SINT8; 135 | break; 136 | case NPY_UBYTE: 137 | kind = CAML_BA_UINT8; 138 | break; 139 | case NPY_SHORT: 140 | kind = CAML_BA_SINT16; 141 | break; 142 | case NPY_USHORT: 143 | kind = CAML_BA_UINT16; 144 | break; 145 | case NPY_INT: 146 | kind = CAML_BA_INT32; 147 | break; 148 | case NPY_LONG: 149 | kind = CAML_BA_NATIVE_INT; 150 | break; 151 | case NPY_LONGLONG: 152 | kind = CAML_BA_INT64; 153 | break; 154 | case NPY_FLOAT: 155 | kind = CAML_BA_FLOAT32; 156 | break; 157 | case NPY_DOUBLE: 158 | kind = CAML_BA_FLOAT64; 159 | break; 160 | case NPY_CFLOAT: 161 | kind = CAML_BA_COMPLEX32; 162 | break; 163 | case NPY_CDOUBLE: 164 | kind = CAML_BA_COMPLEX64; 165 | break; 166 | case NPY_CHAR: 167 | #ifdef CAML_BA_CHAR /* introduced in 4.02.0 */ 168 | kind = CAML_BA_CHAR; 169 | #else 170 | kind = CAML_BA_UINT8; 171 | #endif 172 | break; 173 | default: 174 | caml_failwith("Unsupported NumPy kind for bigarray"); 175 | } 176 | int flags = fields->flags; 177 | enum caml_ba_layout layout; 178 | if (flags & NPY_ARRAY_C_CONTIGUOUS) { 179 | layout = CAML_BA_C_LAYOUT; 180 | } 181 | else if (flags & NPY_ARRAY_F_CONTIGUOUS) { 182 | layout = CAML_BA_FORTRAN_LAYOUT; 183 | } 184 | else { 185 | caml_failwith("Unsupported NumPy layout for bigarray"); 186 | } 187 | void *data = fields->data; 188 | bigarray = caml_ba_alloc(kind | layout, nd, data, dims); 189 | free(dims); 190 | Py_INCREF(array); 191 | const struct custom_operations *oldops = Custom_ops_val(bigarray); 192 | struct numpy_custom_operations *newops = (struct numpy_custom_operations *) 193 | malloc(sizeof(struct numpy_custom_operations)); 194 | newops->ops.identifier = oldops->identifier; 195 | newops->ops.finalize = numpy_finalize; 196 | newops->ops.compare = oldops->compare; 197 | newops->ops.hash = oldops->hash; 198 | newops->ops.serialize = oldops->serialize; 199 | newops->ops.deserialize = oldops->deserialize; 200 | newops->ops.compare_ext = oldops->compare_ext; 201 | newops->obj = array; 202 | Custom_ops_val(bigarray) = (struct custom_operations *) newops; 203 | result = caml_alloc_tuple(3); 204 | Store_field(result, 0, Val_int(kind)); 205 | Store_field(result, 1, Val_int(layout == CAML_BA_FORTRAN_LAYOUT ? 1 : 0)); 206 | Store_field(result, 2, bigarray); 207 | CAMLreturn(result); 208 | } 209 | -------------------------------------------------------------------------------- /numpy_tests.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | Pyml_tests_common.add_test ~title:"of_bigarray" 3 | (fun () -> 4 | if Py.Import.try_import_module "numpy" = None then 5 | Pyml_tests_common.Disabled "numpy is not available" 6 | else 7 | begin 8 | let array = [| 1.; 2. |] in 9 | let array1 = 10 | Bigarray.Array1.of_array (Bigarray.float64) (Bigarray.c_layout) array in 11 | let bigarray = Bigarray.genarray_of_array1 array1 in 12 | let a = Numpy.of_bigarray bigarray in 13 | let m = Py.Import.add_module "test" in 14 | Py.Module.set m "array" a; 15 | assert (Py.Run.simple_string " 16 | from test import array 17 | assert len(array) == 2 18 | assert array[0] == 1. 19 | assert array[1] == 2. 20 | array[0] = 42. 21 | array[1] = 43. 22 | "); 23 | assert (Bigarray.Array1.get array1 0 = 42.); 24 | assert (Bigarray.Array1.get array1 1 = 43.); 25 | Pyml_tests_common.Passed 26 | end) 27 | 28 | let () = 29 | Pyml_tests_common.add_test ~title:"of_bigarray2" 30 | (fun () -> 31 | if Py.Import.try_import_module "numpy" = None then 32 | Pyml_tests_common.Disabled "numpy is not available" 33 | else 34 | begin 35 | let array = [| [| 1.; 2.; 3. |]; [| -1.23; Stdcompat.Float.nan; 2.72 |] |] in 36 | let array2 = 37 | Bigarray.Array2.of_array (Bigarray.float64) (Bigarray.c_layout) array in 38 | let bigarray = Bigarray.genarray_of_array2 array2 in 39 | let a = Numpy.of_bigarray bigarray in 40 | let m = Py.Import.add_module "test" in 41 | Py.Module.set m "array" a; 42 | assert (Py.Run.simple_string " 43 | from test import array 44 | import numpy 45 | 46 | assert list(array.shape) == [2, 3] 47 | numpy.testing.assert_almost_equal(array[0], [1, 2, 3]) 48 | assert(numpy.isnan(array[1, 1])) 49 | array[0, 0] = 42. 50 | array[0, 1] = 43. 51 | array[1, 1] = 1. 52 | "); 53 | assert (Bigarray.Array2.get array2 0 0 = 42.); 54 | assert (Bigarray.Array2.get array2 0 1 = 43.); 55 | assert (Bigarray.Array2.get array2 1 1 = 1.); 56 | Pyml_tests_common.Passed 57 | end) 58 | 59 | let () = 60 | Pyml_tests_common.add_test ~title:"to_bigarray" 61 | (fun () -> 62 | if Py.Import.try_import_module "numpy" = None then 63 | Pyml_tests_common.Disabled "numpy is not available" 64 | else 65 | begin 66 | let m = Py.Import.add_module "test" in 67 | let callback arg = 68 | let bigarray = 69 | Numpy.to_bigarray Bigarray.nativeint Bigarray.c_layout arg.(0) in 70 | assert (Bigarray.Genarray.dims bigarray = [| 4 |]); 71 | let array1 = Bigarray.array1_of_genarray bigarray in 72 | assert (Bigarray.Array1.get array1 0 = 0n); 73 | assert (Bigarray.Array1.get array1 1 = 1n); 74 | assert (Bigarray.Array1.get array1 2 = 2n); 75 | assert (Bigarray.Array1.get array1 3 = 3n); 76 | Py.none in 77 | Py.Module.set m "callback" (Py.Callable.of_function callback); 78 | assert (Py.Run.simple_string " 79 | from test import callback 80 | import numpy 81 | callback(numpy.array([0,1,2,3])) 82 | "); 83 | Pyml_tests_common.Passed 84 | end) 85 | 86 | let assert_almost_eq ?(eps = 1e-7) f1 f2 = 87 | if Stdcompat.Float.abs (f1 -. f2) > eps then 88 | failwith (Printf.sprintf "%f <> %f" f1 f2) 89 | 90 | let () = 91 | Pyml_tests_common.add_test ~title:"to_bigarray2" 92 | (fun () -> 93 | if Py.Import.try_import_module "numpy" = None then 94 | Pyml_tests_common.Disabled "numpy is not available" 95 | else 96 | begin 97 | let m = Py.Import.add_module "test" in 98 | let callback arg = 99 | let bigarray = 100 | Numpy.to_bigarray Bigarray.float32 Bigarray.c_layout arg.(0) in 101 | assert (Bigarray.Genarray.dims bigarray = [| 2; 4 |]); 102 | let array2 = Bigarray.array2_of_genarray bigarray in 103 | let assert_almost_eq i j v = 104 | assert_almost_eq (Bigarray.Array2.get array2 i j) v in 105 | let assert_is_nan i j = 106 | let v = Bigarray.Array2.get array2 i j in 107 | assert (Stdcompat.Float.is_nan v) in 108 | assert_almost_eq 0 0 0.12; 109 | assert_almost_eq 0 1 1.23; 110 | assert_almost_eq 0 2 2.34; 111 | assert_almost_eq 0 3 3.45; 112 | assert_almost_eq 1 0 (-1.); 113 | assert_is_nan 1 1; 114 | assert_almost_eq 1 2 1.; 115 | assert_almost_eq 1 3 0.; 116 | Py.none in 117 | Py.Module.set m "callback" (Py.Callable.of_function callback); 118 | assert (Py.Run.simple_string " 119 | from test import callback 120 | import numpy 121 | callback(numpy.array([[0.12,1.23,2.34,3.45],[-1.,numpy.nan,1.,0.]], dtype=numpy.float32)) 122 | "); 123 | Pyml_tests_common.Passed 124 | end) 125 | 126 | let assert_invalid_argument f = 127 | try 128 | let () = f () in 129 | assert false 130 | with Invalid_argument _ -> 131 | () 132 | 133 | let () = 134 | Pyml_tests_common.add_test ~title:"to_bigarray invalid type" 135 | (fun () -> 136 | if Py.Import.try_import_module "numpy" = None then 137 | Pyml_tests_common.Disabled "numpy is not available" 138 | else 139 | begin 140 | assert_invalid_argument (fun () -> 141 | ignore (Numpy.to_bigarray Float64 C_layout Py.none)); 142 | assert_invalid_argument (fun () -> 143 | ignore (Numpy.to_bigarray Float64 C_layout (Py.Int.of_int 0))); 144 | let array = 145 | Numpy.of_bigarray (Bigarray.genarray_of_array1 ( 146 | Bigarray.Array1.of_array (Bigarray.float64) (Bigarray.c_layout) 147 | [| 1.; 2. |])) in 148 | ignore (Numpy.to_bigarray Float64 C_layout array); 149 | assert_invalid_argument (fun () -> 150 | ignore (Numpy.to_bigarray Float32 C_layout array)); 151 | assert_invalid_argument (fun () -> 152 | ignore (Numpy.to_bigarray Float64 Fortran_layout array)); 153 | Pyml_tests_common.Passed 154 | end) 155 | 156 | let () = 157 | Pyml_tests_common.add_test ~title:"to_bigarray_k" 158 | (fun () -> 159 | if Py.Import.try_import_module "numpy" = None then 160 | Pyml_tests_common.Disabled "numpy is not available" 161 | else 162 | begin 163 | let m = Py.Import.add_module "test" in 164 | let callback arg = 165 | let k { Numpy.kind; layout; array } = 166 | assert (Numpy.compare_kind kind Bigarray.nativeint = 0); 167 | assert (Numpy.compare_layout layout Bigarray.c_layout = 0); 168 | let bigarray = 169 | Stdcompat.Option.get (Numpy.check_kind_and_layout 170 | Bigarray.nativeint Bigarray.c_layout array) in 171 | assert (Bigarray.Genarray.dims bigarray = [| 4 |]); 172 | let array1 = Bigarray.array1_of_genarray bigarray in 173 | assert (Bigarray.Array1.get array1 0 = 0n); 174 | assert (Bigarray.Array1.get array1 1 = 1n); 175 | assert (Bigarray.Array1.get array1 2 = 2n); 176 | assert (Bigarray.Array1.get array1 3 = 3n) in 177 | Numpy.to_bigarray_k { Numpy.f = k } arg.(0); 178 | Py.none in 179 | Py.Module.set m "callback" (Py.Callable.of_function callback); 180 | assert (Py.Run.simple_string " 181 | from test import callback 182 | import numpy 183 | callback(numpy.array([0,1,2,3])) 184 | "); 185 | Pyml_tests_common.Passed 186 | end) 187 | 188 | let () = 189 | if not !Sys.interactive then 190 | Pyml_tests_common.main () 191 | -------------------------------------------------------------------------------- /pyml_stubs.h: -------------------------------------------------------------------------------- 1 | #ifndef _PYML_STUBS_H_ 2 | #define _PYML_STUBS_H_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* The following definitions are extracted and simplified from 9 | #include 10 | */ 11 | 12 | /* ssize_t is POSIX and not defined with Visual Studio */ 13 | /* See for instance https://github.com/vlm/asn1c/issues/159 */ 14 | #if defined(_MSC_VER) 15 | #include 16 | typedef SSIZE_T ssize_t; 17 | #else 18 | #include 19 | #endif 20 | 21 | typedef ssize_t Py_ssize_t; 22 | 23 | #define _PyObject_HEAD_EXTRA 24 | 25 | #define PyObject_HEAD \ 26 | _PyObject_HEAD_EXTRA \ 27 | Py_ssize_t ob_refcnt; \ 28 | PyObject *ob_type; 29 | 30 | typedef void PyObject; 31 | 32 | typedef struct { 33 | Py_ssize_t ob_refcnt; 34 | PyObject *ob_type; 35 | } PyObjectDescr; 36 | 37 | PyObjectDescr *pyobjectdescr(PyObject *obj); 38 | 39 | typedef struct { 40 | PyObjectDescr ob_base; 41 | Py_ssize_t ob_size; 42 | } PyVarObject; 43 | 44 | typedef void (*destructor)(PyObject *); 45 | 46 | typedef struct _typeobject { 47 | PyVarObject ob_base; 48 | const char *tp_name; 49 | Py_ssize_t tp_basicsize, tp_itemsize; 50 | destructor tp_dealloc; 51 | void *tp_print; 52 | void *tp_getattr; 53 | void *tp_setattr; 54 | void *tp_as_async; 55 | void *tp_repr; 56 | void *tp_as_number; 57 | void *tp_as_sequence; 58 | void *tp_as_mapping; 59 | void *tp_hash; 60 | void *tp_call; 61 | void *tp_str; 62 | void *tp_getattro; 63 | void *tp_setattro; 64 | void *tp_as_buffer; 65 | unsigned long tp_flags; 66 | const char *tp_doc; 67 | void *tp_traverse; 68 | void *tp_clear; 69 | void *tp_richcompare; 70 | Py_ssize_t tp_weaklistoffset; 71 | void *tp_iter; 72 | void *tp_iternext; 73 | void *tp_methods; 74 | void *tp_members; 75 | void *tp_getset; 76 | void *tp_base; 77 | PyObject *tp_dict; 78 | void *tp_descr_get; 79 | void *tp_descr_set; 80 | Py_ssize_t tp_dictoffset; 81 | void *tp_init; 82 | void *tp_alloc; 83 | void *tp_new; 84 | void *tp_free; 85 | void *tp_is_gc; 86 | PyObject *tp_bases; 87 | PyObject *tp_mro; 88 | PyObject *tp_cache; 89 | PyObject *tp_subclasses; 90 | PyObject *tp_weaklist; 91 | void *tp_del; 92 | unsigned int tp_version_tag; 93 | void *tp_finalize; 94 | /* #ifdef COUNT_ALLOCS */ 95 | Py_ssize_t tp_allocs; 96 | Py_ssize_t tp_frees; 97 | Py_ssize_t tp_maxalloc; 98 | struct _typeobject *tp_prev; 99 | struct _typeobject *tp_next; 100 | /* #endif */ 101 | } PyTypeObject; 102 | 103 | void 104 | pyml_assert_initialized(); 105 | 106 | void 107 | pyml_assert_python2(); 108 | 109 | void 110 | pyml_assert_ucs2(); 111 | 112 | void 113 | pyml_assert_ucs4(); 114 | 115 | void 116 | pyml_assert_python3(); 117 | 118 | /* Numpy */ 119 | 120 | /* from ndarraytypes.h */ 121 | 122 | enum NPY_TYPES { 123 | NPY_BOOL=0, 124 | NPY_BYTE, NPY_UBYTE, 125 | NPY_SHORT, NPY_USHORT, 126 | NPY_INT, NPY_UINT, 127 | NPY_LONG, NPY_ULONG, 128 | NPY_LONGLONG, NPY_ULONGLONG, 129 | NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE, 130 | NPY_CFLOAT, NPY_CDOUBLE, NPY_CLONGDOUBLE, 131 | NPY_OBJECT=17, 132 | NPY_STRING, NPY_UNICODE, 133 | NPY_VOID, 134 | /* 135 | * New 1.6 types appended, may be integrated 136 | * into the above in 2.0. 137 | */ 138 | NPY_DATETIME, NPY_TIMEDELTA, NPY_HALF, 139 | 140 | NPY_NTYPES, 141 | NPY_NOTYPE, 142 | NPY_CHAR, /* special flag */ 143 | NPY_USERDEF=256, /* leave room for characters */ 144 | 145 | /* The number of types not including the new 1.6 types */ 146 | NPY_NTYPES_ABI_COMPATIBLE=21 147 | }; 148 | 149 | #define NPY_ARRAY_C_CONTIGUOUS 0x0001 150 | #define NPY_ARRAY_F_CONTIGUOUS 0x0002 151 | #define NPY_ARRAY_OWNDATA 0x0004 152 | #define NPY_ARRAY_ALIGNED 0x0100 153 | #define NPY_ARRAY_WRITEABLE 0x0400 154 | 155 | #define NPY_ARRAY_BEHAVED (NPY_ARRAY_ALIGNED | \ 156 | NPY_ARRAY_WRITEABLE) 157 | #define NPY_ARRAY_CARRAY (NPY_ARRAY_C_CONTIGUOUS | \ 158 | NPY_ARRAY_BEHAVED) 159 | #define NPY_ARRAY_FARRAY (NPY_ARRAY_F_CONTIGUOUS | \ 160 | NPY_ARRAY_BEHAVED) 161 | 162 | /* From pyport.h */ 163 | typedef intptr_t Py_intptr_t; 164 | 165 | /* From npy_common.h */ 166 | typedef Py_intptr_t npy_intp; 167 | 168 | void ** 169 | pyml_get_pyarray_api(PyObject *c_api); 170 | 171 | #define Py_INCREF(op) \ 172 | ((pyobjectdescr(op))->ob_refcnt++) 173 | 174 | #define Py_XINCREF(op) \ 175 | do { \ 176 | PyObjectDescr *_py_xincref_tmp = \ 177 | pyobjectdescr((PyObject *)(op)); \ 178 | if (_py_xincref_tmp != NULL) \ 179 | Py_INCREF(_py_xincref_tmp); \ 180 | } while (0) 181 | 182 | #define Py_DECREF(op) \ 183 | do { \ 184 | PyObjectDescr *_py_decref_tmp = \ 185 | pyobjectdescr((PyObject *)(op)); \ 186 | if (--(_py_decref_tmp)->ob_refcnt == 0) \ 187 | ((struct _typeobject *) \ 188 | pyobjectdescr(_py_decref_tmp->ob_type)) \ 189 | ->tp_dealloc(op); \ 190 | } while (0) 191 | 192 | /* from ndarraytypes.h */ 193 | 194 | typedef struct _PyArray_Descr { 195 | PyObject_HEAD 196 | PyTypeObject *typeobj; 197 | char kind; 198 | char type; 199 | char byteorder; 200 | char flags; 201 | int type_num; 202 | int elsize; 203 | int alignment; 204 | struct _arr_descr *subarray; 205 | PyObject *fields; 206 | PyObject *names; 207 | void *f; 208 | PyObject *metadata; 209 | void *c_metadata; 210 | int hash; 211 | } PyArray_Descr; 212 | 213 | typedef struct _arr_descr { 214 | PyArray_Descr *base; 215 | PyObject *shape; 216 | } PyArray_ArrayDescr; 217 | 218 | typedef struct tagPyArrayObject_fields { 219 | PyObject_HEAD 220 | char *data; 221 | int nd; 222 | npy_intp *dimensions; 223 | npy_intp *strides; 224 | PyObject *base; 225 | PyArray_Descr *descr; 226 | int flags; 227 | PyObject *weakreflist; 228 | } PyArrayObject_fields; 229 | 230 | typedef struct { 231 | int cf_flags; 232 | int cf_feature_version; /* Python >=3.8 */ 233 | } PyCompilerFlags; 234 | 235 | #define Py_TPFLAGS_INT_SUBCLASS (1L<<23) 236 | #define Py_TPFLAGS_LONG_SUBCLASS (1UL << 24) 237 | #define Py_TPFLAGS_LIST_SUBCLASS (1UL << 25) 238 | #define Py_TPFLAGS_TUPLE_SUBCLASS (1UL << 26) 239 | #define Py_TPFLAGS_BYTES_SUBCLASS (1UL << 27) 240 | #define Py_TPFLAGS_UNICODE_SUBCLASS (1UL << 28) 241 | #define Py_TPFLAGS_DICT_SUBCLASS (1UL << 29) 242 | #define Py_TPFLAGS_BASE_EXC_SUBCLASS (1UL << 30) 243 | #define Py_TPFLAGS_TYPE_SUBCLASS (1UL << 31) 244 | 245 | #define Py_LT 0 246 | #define Py_LE 1 247 | #define Py_EQ 2 248 | #define Py_NE 3 249 | #define Py_GT 4 250 | #define Py_GE 5 251 | 252 | typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); 253 | 254 | typedef struct PyMethodDef { 255 | const char *ml_name; 256 | PyCFunction ml_meth; 257 | int ml_flags; 258 | const char *ml_doc; 259 | } PyMethodDef; 260 | 261 | typedef void (*PyCapsule_Destructor)(PyObject *); 262 | 263 | #endif /* _PYML_STUBS_H_ */ 264 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | [*] marks changes that break compatibility with previous versions. 2 | 3 | # 2025-08-07 4 | 5 | - Support OCaml only back to 4.11 6 | 7 | - Stop including stdcompat.h 8 | 9 | - Update package metadata 10 | 11 | # 2023-11-01 12 | 13 | - Compatibility with Python 3.13 14 | 15 | - Fix segmentation fault by forgetting objects on library unloading. 16 | Observed on Fedora Rawhide with address randomization, 17 | reported by Jerry James, 18 | https://github.com/thierry-martinez/pyml/issues/85 19 | 20 | - #93, #94: Fix `Py.Object.get_attr_string`: this function now returns 21 | `None` when attribute is missing (the former version raised an 22 | exception, despite the `option` return type and contrary to what was 23 | documented). 24 | Reported by Lindsay Errington, @dlindsaye, 25 | https://github.com/thierry-martinez/pyml/issues/93 26 | 27 | - #91, #92, #94: Better search heuristics for `python` library. 28 | Suggested by camlspotter and Et7f3. 29 | Use of `python-config`. 30 | Use of `otool -L` instead of `ldd` on Mac OS X. 31 | https://github.com/thierry-martinez/pyml/issues/91 32 | https://github.com/thierry-martinez/pyml/issues/92 33 | 34 | - #96: `find` functions (`Py.Object.find`, `Py.Object.find_string`, 35 | `Py.Dict.find`, `Py.Dict.find_string`, `Py.Object.find_attr_string` 36 | and `Py.Object.find_attr`) now consistently fail with `Not_found` 37 | exception, as it is said in the documentation. Functions 38 | `Py.Object.find_err`, `Py.Object.find_string_err`, 39 | `Py.Object.find_attr_err`, `Py.Object.find_attr_string_err` have 40 | been introduced for cases where keeping the underlying Python 41 | exception is preferable. For instance, `Py.Module.get` is now an 42 | alias for `Py.Object.find_attr_string_err` to keep the current 43 | behavior of failing with a Python exception. 44 | Reported by Jonathan Laurent, 45 | https://github.com/thierry-martinez/pyml/issues/96 46 | 47 | # 2022-09-05 48 | 49 | - Support for OCaml 5.0 50 | 51 | - Support for Python 3.11. 52 | All OCaml exceptions raised in callbacks are now encapsulated with their 53 | backtrace in Python exceptions instead of bypassing the Python interpreter. 54 | The former behavior led to segmentation faults in the test-suite, and nothing 55 | indicate that previous versions of Python were supposed to support that well. 56 | *The new behavior can break existing code*, especially code relying on 57 | `Py.Run.simple_string`, which now catches all exceptions, including OCaml 58 | exceptions. If you need proper exception handling, you can use `Py.Run.eval`. 59 | (reported by Jerry James, 60 | https://github.com/thierry-martinez/pyml/issues/84) 61 | 62 | - New function `Py.Object.dir`. 63 | 64 | - New functions `Py.Err.set_interrupt` and, for Python >=3.10, 65 | `Py.Err.set_interrupt_ex`. 66 | 67 | - New functions 68 | `Py.Dict.{to_bindings_seq, to_bindings_seq_map, to_bindings_string_seq}`. 69 | 70 | - New function `Py.Capsule.create`, equivalent to `Py.Capsule.make`, but 71 | returning the record `{ wrap; unwrap }` of the new type `'a Py.Capsule.t` 72 | instead of a pair. 73 | 74 | - Do not let `python` capture `sigint` by default (can be changed by passing 75 | `~python_sigint:true` to `Py.initialize`): `Ctrl+C` now interrupts the 76 | program, even after `Py.initialize` is called. 77 | (reported by Arulselvan Madhavan, 78 | https://github.com/thierry-martinez/pyml/issues/83) 79 | 80 | - Bindings for exceptions: `PyExc_EncodingWarning` (added in Python 3.10), 81 | `PyExc_ResourceWarning` (added in Python 3.2) 82 | (reported by Jerry James, 83 | https://github.com/thierry-martinez/pyml/issues/84) 84 | 85 | - Fixes in bindings for `PyCompilerFlags`, `PyMarshal_WriteObjectToFile`, 86 | and `PySet_Clear` 87 | (reported by Jerry James, 88 | https://github.com/thierry-martinez/pyml/issues/84) 89 | 90 | # 2022-06-15 91 | 92 | - `Numpy.to_bigarray_k` is continuation-passing-style version of `Numpy.to_bigarray`, 93 | allowing caller to convert Numpy arrays to bigarrays without having to know 94 | the kind and the layout of the array 95 | (suggested by Lindsay Errington and Andie Sigler, 96 | https://github.com/thierry-martinez/pyml/issues/81) 97 | 98 | # 2022-03-25 99 | 100 | - Fix debug build detection 101 | 102 | - Expose the function `Py.Callable.handle_errors` 103 | 104 | # 2022-03-22 105 | 106 | - New function `Py.Import.exec_code_module_from_string` 107 | (suggested by Francois Berenger, https://github.com/thierry-martinez/pyml/issues/78) 108 | 109 | - New function `Py.Module.compile` provides a better API than `Py.compile`. 110 | 111 | - `Py.Object.t` can now be serialized (with Marshal or output_value), using Python 112 | pickle module 113 | 114 | - Cross-compiling friendly architecture detection 115 | (suggested by @EduardoRFS, https://discuss.ocaml.org/t/a-zoo-of-values-for-system/8525/20) 116 | 117 | - Detect macro `unix` instead of `__linux__`, to handle *BSD OSes 118 | (reported by Chris Pinnock, https://github.com/thierry-martinez/pyml/issues/74) 119 | 120 | - Fix bug in Windows 121 | 122 | - Null checks for many functions raising OCaml exceptions, instead of segmentation fault 123 | (initial implementation by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/72) 124 | 125 | - Fix wide character conversion bugs leading to segmentation fault in Py_wfopen 126 | (fixed by Jerry James, https://github.com/thierry-martinez/pyml/pull/75) 127 | 128 | - `Gc.full_major ()` before unloading `libpython` in `Py.finalize`, to prevent 129 | segfaulting on finalizing dangling references to Python values after the library 130 | had been unloaded 131 | (reported by Denis Efremov on coccinelle mailing list) 132 | 133 | - Fix segmentation fault when `~debug_build:true` was passed to `Py.initialize` 134 | (reported by Stéphane Glondu, https://github.com/thierry-martinez/pyml/issues/79) 135 | 136 | # 2021-10-15 137 | 138 | - More portable architecture detection 139 | (inspired by the discussion https://discuss.ocaml.org/t/a-zoo-of-values-for-system/8525 140 | initiated by Olaf Hering, with helpful comments from Daniel Bünzli, 141 | @EduardoRFS, David Allsopp, kit-ty-kate and jbeckford) 142 | 143 | - Better compatibility with Windows 144 | 145 | - Correct version detection for Python 3.10 146 | 147 | # 2021-09-24 148 | 149 | - Use `dune` as default build system 150 | (dunification done by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/28) 151 | 152 | This should in particular fix build problems of reverse dependencies 153 | with the byte-code compiler 154 | (reported by @nicoTolly, https://github.com/thierry-martinez/pyml/issues/62) 155 | 156 | - Handle more platforms with dune 157 | (reported by Olaf Hering, https://github.com/thierry-martinez/pyml/issues/68) 158 | 159 | - `pyutils` is no longer used by generate and is shipped with `pyml` package 160 | as it was the case with Makefile-based build system 161 | (reported by Olaf Hering, https://github.com/thierry-martinez/pyml/issues/69) 162 | 163 | - Support for raising exceptions with traceback from OCaml 164 | (implemented by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/65) 165 | 166 | - Fix soundness bug with `numpy` 167 | (reported by Richard Alligier, https://github.com/thierry-martinez/pyml/pull/65) 168 | 169 | - Fix `Py.Array.numpy` arrays on 32-bit platforms 170 | (reported by Olaf Hering, https://github.com/thierry-martinez/pyml/pull/70) 171 | 172 | - Fix soundness bug on strings with OCaml <4.06 (reported by OCaml CI) 173 | 174 | # 2021-02-26 175 | 176 | - Compatibility with Python 3.10 (reported by Richard W.M. Jones) 177 | 178 | - `PyObject_AsCharBuffer`, `PyObject_AsReadBuffer`, 179 | `PyObject_AsWriteBuffer` bindings are marked optional as they have 180 | been removed in Python 3.10. 181 | 182 | - `Py_fopen` is optional and `Py_wfopen` is used instead if available. 183 | 184 | - More general handling of Unix architectures. 185 | (Fixed by Pino Toscano, https://github.com/thierry-martinez/pyml/pull/57) 186 | 187 | - Add `Py.Set` module for Python sets. 188 | (Added by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/58) 189 | 190 | - Fix #61: `Numpy.to_bigarray` raises an exception if source value is 191 | not a Numpy array instead of segfaulting. 192 | (Reported by Jonathan Laurent, 193 | https://github.com/thierry-martinez/pyml/issues/61) 194 | 195 | - Fix #56, #59: Add `python-config` heuristics to find Python library. 196 | (Reported by Anders Thuné and Nils Becker, 197 | https://github.com/thierry-martinez/pyml/issues/56 198 | https://github.com/thierry-martinez/pyml/issues/59) 199 | 200 | - Fix `import_module_opt` for Python <3.6 201 | (Reported by opam CI.) 202 | 203 | # 2020-05-18 204 | 205 | - Fix: Add an `__iter__` method to python iterators. 206 | (Fixed by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/47) 207 | 208 | - Add `Py.Seq.{of_seq_map, to_seq_map, unsafe_to_seq_map, of_list, 209 | of_list_map}` functions. 210 | 211 | - Remove `Py.Import.cleanup`, which has been removed from Python 3.9, and 212 | was marked "for internal use only" before. 213 | (Reported by Victor Stinner, 214 | https://github.com/thierry-martinez/pyml/issues/49) 215 | 216 | - Fix: memory leak in `pyml_wrap_closure` 217 | (Fixed by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/53) 218 | 219 | - Add `Py.Module.set_docstring`, for Python >=3.5. 220 | (Added by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/54) 221 | 222 | - Fix: install `.cmx` files 223 | (Reported by Jonathan Laurent, 224 | https://github.com/thierry-martinez/pyml/issues/55) 225 | 226 | # 2020-02-22 227 | 228 | - Fix: do not fail if GIL functions are unavailable 229 | 230 | - Fix: include `stdcompat.h` provided with stdcompat version 13 for the 231 | prototype of `caml_alloc_initialized_string`. 232 | 233 | - Fix: reference to the native plugin (`.cmxs`) in META 234 | 235 | # 2020-01-15 236 | 237 | - Compatible with OCaml 4.10.0. 238 | 239 | - [PR 36] GC issue when registering a function with a dynamically 240 | allocated docstring. 241 | (Fixed by Laurent Mazare, https://github.com/thierry-martinez/pyml/pull/36) 242 | 243 | - [PR 34] Ensure that every function starting with "CAMLparamK" ends with 244 | "CAMLreturnX". 245 | (Fixed by Xavier Clerc, https://github.com/thierry-martinez/pyml/pull/34) 246 | 247 | - [GitHub issue #37] Fix test suite: 'list' object has no attribute 'clear' 248 | (Reported by Olaf Hering, https://github.com/thierry-martinez/pyml/issues/37) 249 | 250 | - [PR 38] Check for executable called python3. 251 | (Fixed by Olaf Hering, https://github.com/thierry-martinez/pyml/pull/38) 252 | 253 | - [PR 39] Expose is-instance and is-subclass. 254 | (Contribution by Laurent Mazare, 255 | https://github.com/thierry-martinez/pyml/pull/39) 256 | 257 | - [PR 44] Expose some GIL functions (functions from the Python C API 258 | related to the global interpreter lock. 259 | (Contribution by Laurent Mazare, 260 | https://github.com/thierry-martinez/pyml/pull/44) 261 | 262 | - Fix dynamic loading of stubs. 263 | (Fixed by Stéphane Glondu) 264 | 265 | # 2019-06-26 266 | 267 | - Support for debug build of Python library 268 | (Suggested by Arlen Cox: 269 | https://github.com/thierry-martinez/pyml/issues/18) 270 | - Bug fix in pyml_check_symbol_available 271 | - `Py.compile` is a wrapper for the built-in function `compile` 272 | (Suggested by Dhruv Makwana: 273 | https://github.com/thierry-martinez/pyml/issues/25) 274 | - Guarantees for structural and physical equalities on `Py.Object.t` 275 | are now documented. New predicates Py.is_none, Py.is_null, Py.Bool.is_true, 276 | Py.Bool.is_false, Py.Tuple.is_empty. 277 | (Suggested by Laurent Mazare: 278 | https://github.com/thierry-martinez/pyml/pull/31) 279 | - Fix Py.Array.numpy to handle OCaml GC's moving the floatarray 280 | (Reported by Ilias Garnier: 281 | https://github.com/thierry-martinez/pyml/issues/30) 282 | 283 | # 2018-05-30 284 | 285 | - `Py.import` is an alias for `Py.Import.import_module`. 286 | - Use `*_opt` naming convention for the functions that return an option 287 | instead of an exception: `Py.import_opt`, `Py.Object.find_opt`,... 288 | - of_seq/to_seq converters 289 | - [*] get_attr/get_attr_string now returns option type 290 | - Indexing operators (for OCaml 4.06.0 and above) defined in Pyops 291 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX := /usr/local 2 | OCAMLFIND := ocamlfind 3 | INSTALL := install 4 | INSTALL_PROGRAM := $(INSTALL) 5 | bindir := $(PREFIX)/bin 6 | 7 | C_COMPILER := $(shell ocamlc -config | grep '^native_c_compiler:' | cut -d ' ' -f 2) 8 | EXT_LIB := $(shell ocamlc -config | grep '^ext_lib:' | cut -d ' ' -f 2) 9 | 10 | HAVE_OCAMLFIND := $(shell \ 11 | if $(OCAMLFIND) query -help >/dev/null 2>&1; then \ 12 | echo yes; \ 13 | else \ 14 | echo no; \ 15 | fi \ 16 | ) 17 | 18 | HAVE_UTOP := $(shell \ 19 | if [ "$(HAVE_OCAMLFIND)" = no ]; then \ 20 | echo no; \ 21 | elif $(OCAMLFIND) query utop >/dev/null 2>&1; then \ 22 | echo yes; \ 23 | else \ 24 | echo no; \ 25 | fi \ 26 | ) 27 | 28 | ifneq ($(MAKECMDGOALS),clean) 29 | ifneq ($(HAVE_OCAMLFIND),no) 30 | OCAMLC := $(OCAMLFIND) ocamlc 31 | ifneq ($(HAVE_OCAMLOPT),no) 32 | OCAMLOPTEXE := $(OCAMLFIND) ocamlopt 33 | endif 34 | OCAMLMKLIB := $(OCAMLFIND) ocamlmklib 35 | OCAMLMKTOP := $(OCAMLFIND) ocamlmktop 36 | OCAMLDEP := $(OCAMLFIND) ocamldep 37 | OCAMLDOC := $(OCAMLFIND) ocamldoc 38 | STDCOMPAT := $(shell $(OCAMLFIND) query stdcompat) 39 | else 40 | OCAMLC := $(shell \ 41 | if ocamlc.opt -version >/dev/null 2>&1; then \ 42 | echo ocamlc.opt; \ 43 | elif ocamlc -version >/dev/null 2>&1; then \ 44 | echo ocamlc; \ 45 | fi \ 46 | ) 47 | ifeq ($(OCAMLC),) 48 | $(error There is no OCaml compiler available in path) 49 | endif 50 | ifneq ($(HAVE_OCAMLOPT),no) 51 | OCAMLOPTEXE := $(shell \ 52 | if ocamlopt.opt -version >/dev/null 2>&1; then \ 53 | echo ocamlopt.opt; \ 54 | elif ocamlopt -version >/dev/null 2>&1; then \ 55 | echo ocamlopt; \ 56 | fi \ 57 | ) 58 | endif 59 | OCAMLMKLIB := ocamlmklib 60 | OCAMLMKTOP := ocamlmktop 61 | OCAMLDEP := ocamldep 62 | OCAMLDOC := ocamldoc 63 | STDCOMPAT := . 64 | endif 65 | 66 | OCAMLVERSION := $(shell $(OCAMLC) -version) 67 | OCAMLVERSION_LIST := $(subst ., ,$(OCAMLVERSION)) 68 | OCAMLVERSION_MAJOR := $(word 1,$(OCAMLVERSION_LIST)) 69 | 70 | LIBRARIES := unix stdcompat 71 | 72 | ifeq ($(OCAMLVERSION_MAJOR),5) 73 | LIBRARIES_NUMPY = $(LIBRARIES) 74 | OCAMLCFLAGS = -I +unix 75 | else 76 | LIBRARIES_NUMPY = $(LIBRARIES) bigarray 77 | endif 78 | 79 | null := 80 | space := $(null) # 81 | comma := , 82 | 83 | ifneq ($(HAVE_OCAMLFIND),no) 84 | OCAMLCFLAGS += -package stdcompat 85 | OCAMLLDFLAGS += -linkpkg 86 | PACKAGES := $(subst $(space),$(comma),$(LIBRARIES)) 87 | PACKAGES_NUMPY := $(subst $(space),$(comma),$(LIBRARIES_NUMPY)) 88 | OCAMLBYTECODELIBS := -package $(PACKAGES) 89 | OCAMLBYTECODELIBSNUMPY := -package $(PACKAGES_NUMPY) 90 | OCAMLNATIVELIBS := -package $(PACKAGES) 91 | OCAMLNATIVELIBSNUMPY := -package $(PACKAGES_NUMPY) 92 | else 93 | OCAMLCFLAGS += -I $(STDCOMPAT) 94 | OCAMLLDFLAGS += -I $(STDCOMPAT) 95 | OCAMLBYTECODELIBS := $(LIBRARIES:=.cma) 96 | OCAMLBYTECODELIBSNUMPY := $(LIBRARIES_NUMPY:=.cma) 97 | OCAMLNATIVELIBS := $(LIBRARIES:=.cmxa) 98 | OCAMLNATIVELIBSNUMPY := $(LIBRARIES_NUMPY:=.cmxa) 99 | endif 100 | 101 | ifeq ($(wildcard $(STDCOMPAT)/stdcompat.cma),) 102 | $(error stdcompat module not found: please specify the path with STDCOMPAT=...) 103 | endif 104 | 105 | OCAMLVERSION := $(shell $(OCAMLC) -version) 106 | endif 107 | 108 | ifeq ($(HAVE_UTOP),yes) 109 | PYMLUTOP := pymlutop 110 | else 111 | PYMLUTOP := 112 | endif 113 | 114 | ifeq ($(OCAMLOPTEXE),) 115 | OCAMLOPT = $(error There is no optimizing OCaml compiler available) 116 | OCAMLCOPT := $(OCAMLC) 117 | CMOX := cmo 118 | CMAX := cma 119 | ALLOPT := 120 | TESTOPT := 121 | OCAMLPREFERREDLIBS := $(OCAMLBYTECODELIBS) 122 | else 123 | OCAMLOPT := $(OCAMLOPTEXE) 124 | OCAMLCOPT := $(OCAMLOPT) 125 | CMOX := cmx 126 | CMAX := cmxa 127 | ALLOPT := all.native 128 | TESTOPT := test.native 129 | OCAMLPREFERREDLIBS := $(OCAMLNATIVELIBS) 130 | endif 131 | 132 | ifeq (4.06.0,$(word 1,$(sort 4.06.0 $(OCAMLVERSION)))) 133 | PYOPS=pyops 134 | else 135 | PYOPS= 136 | endif 137 | 138 | MODULES := pyml_arch pyutils pytypes pywrappers py pycaml $(PYOPS) 139 | 140 | VERSION := $(shell date "+%Y%m%d") 141 | 142 | OCAMLLIBFLAGS := -cclib "-L. -lpyml_stubs" 143 | OCAMLLIBNUMPYFLAGS := -cclib "-L. -lnumpy_stubs" 144 | 145 | OCAMLLIBFLAGSNATIVE := $(OCAMLLIBFLAGS) 146 | OCAMLLIBFLAGSBYTECODE := -custom $(OCAMLLIBFLAGS) 147 | 148 | INSTALL_FILES := \ 149 | py.mli numpy.mli $(MODULES:=.cmi) $(MODULES:=.cmx) \ 150 | numpy.cmi \ 151 | pyml.cma pyml.cmxa pyml.cmxs pyml$(EXT_LIB) \ 152 | numpy.cma numpy.cmxa numpy.cmxs numpy$(EXT_LIB) \ 153 | $(MODULES:=.cmx) numpy.cmx \ 154 | libpyml_stubs$(EXT_LIB) dllpyml_stubs.so \ 155 | libnumpy_stubs$(EXT_LIB) dllnumpy_stubs.so \ 156 | META 157 | 158 | .PHONY : all 159 | all : all.bytecode $(ALLOPT) 160 | @echo The py.ml library is compiled. 161 | @echo Run \`make doc\' to build the documentation. 162 | @echo Run \`make test\' to check the test suite. 163 | ifneq ($(HAVE_OCAMLFIND),no) 164 | @echo Run \`make install\' to install the library via ocamlfind. 165 | endif 166 | @echo Run \`make pymltop\' to build the toplevel. 167 | ifneq ($(HAVE_UTOP),no) 168 | @echo Run \`make pymlutop\' to build the utop toplevel. 169 | endif 170 | 171 | .PHONY : help 172 | help : 173 | @echo make [all] : build the library 174 | @echo make all.bytecode : build only the bytecode library 175 | @echo make all.native : build only the native library 176 | @echo make doc : build the documentation 177 | ifneq ($(HAVE_OCAMLFIND),no) 178 | @echo make install : install the library via ocamlfind 179 | endif 180 | @echo make clean : remove all the generated files 181 | @echo make tests : compile and run the test suite 182 | @echo make tests.bytecode : run only the bytecode version of the tests 183 | @echo make tests.native : run only the native version of the tests 184 | @echo make pymltop: build the toplevel 185 | ifneq ($(HAVE_UTOP),no) 186 | @echo make pymlutop: build the utop toplevel. 187 | endif 188 | @echo make HAVE_OCAMLFIND=no : disable ocamlfind 189 | @echo make HAVE_OCAMLOPT=no : disable ocamlopt 190 | @echo \ 191 | "make OCAMLC|OCAMLOPT|OCAMLMKLIB|OCAMLMKTOP|OCAMLDEP|OCAMLDOC=... :" 192 | @echo " set paths to OCaml tools" 193 | @echo make OCAMLCFLAGS=... : set flags to OCaml compiler for compiling 194 | @echo make OCAMLLDFLAGS=... : set flags to OCaml compiler for linking 195 | @echo make OCAMLLIBFLAGS=... : 196 | @echo " set flags to OCaml compiler for building the library" 197 | @echo make STDCOMPAT=... : set path to the stdcompat library 198 | 199 | .PHONY : all.bytecode 200 | all.bytecode : pyml.cma numpy.cma 201 | 202 | .PHONY : all.native 203 | all.native : pyml.cmxa pyml.cmxs numpy.cmxa numpy.cmxs 204 | 205 | .PHONY : test 206 | test : test.bytecode $(TESTOPT) 207 | 208 | .PHONY : test.bytecode 209 | test.bytecode : pyml_tests.bytecode numpy_tests.bytecode 210 | ./pyml_tests.bytecode $(TEST_OPTIONS) 211 | ./numpy_tests.bytecode $(TEST_OPTIONS) 212 | 213 | .PHONY : test.native 214 | test.native : pyml_tests.native numpy_tests.native 215 | ./pyml_tests.native $(TEST_OPTIONS) 216 | ./numpy_tests.native $(TEST_OPTIONS) 217 | 218 | .PHONY : install 219 | install : $(INSTALL_FILES) 220 | ifeq ($(HAVE_OCAMLFIND),no) 221 | $(error ocamlfind is needed for 'make install') 222 | endif 223 | $(OCAMLFIND) install pyml $(INSTALL_FILES) 224 | [ ! -f pymltop ] || $(INSTALL_PROGRAM) pymltop $(bindir)/pymltop 225 | [ ! -f pymlutop ] || $(INSTALL_PROGRAM) pymlutop $(bindir)/pymlutop 226 | 227 | .PHONY : uninstall 228 | uninstall : 229 | $(OCAMLFIND) remove pyml 230 | - rm $(bindir)/pymltop 231 | - rm $(bindir)/pymlutop 232 | 233 | .PHONY : clean 234 | clean : 235 | for module in $(MODULES) numpy generate pyml_tests_common pyml_tests \ 236 | numpy_tests; do \ 237 | rm -f $$module.cmi $$module.cmo $$module.cmx $$module$(EXT_LIB) \ 238 | $$module.o; \ 239 | done 240 | rm -f pyml.cma pyml.cmxa pyml.cmxs pyml$(EXT_LIB) 241 | rm -f numpy.cma numpy.cmxa numpy.cmxs numpy$(EXT_LIB) 242 | rm -f pywrappers.mli pywrappers.ml pyml_dlsyms.inc pyml_wrappers.inc 243 | rm -f pyml.h 244 | rm -f pyml_stubs.o dllpyml_stubs.so libpyml_stubs$(EXT_LIB) 245 | rm -f numpy_stubs.o dllnumpy_stubs.so libnumpy_stubs$(EXT_LIB) 246 | rm -f pyml_arch.ml 247 | rm -f generate pyml_tests.native pyml_tests.bytecode 248 | rm -f numpy_tests.native numpy_tests.bytecode 249 | rm -f .depend 250 | rm -rf doc 251 | rm -f pymltop pytop.cmo pymlutop pyutop.cmo 252 | rm -f pymltop_libdir.ml pymltop_libdir.cmo 253 | rm -f pyops.mli pyops.ml 254 | 255 | .PHONY : tarball 256 | tarball : 257 | git archive --format=tar.gz --prefix=pyml-$(VERSION)/ HEAD \ 258 | >pyml-$(VERSION).tar.gz 259 | 260 | doc : py.mli pycaml.mli numpy.mli pywrappers.ml 261 | mkdir -p $@ 262 | $(OCAMLDOC) $(OCAMLCFLAGS) -html -d $@ $^ 263 | touch $@ 264 | 265 | .depend : $(MODULES:=.ml) $(MODULES:=.mli) numpy.ml numpy.mli \ 266 | pyml_tests_common.mli pyml_tests_common.ml pyml_tests.ml numpy_tests.ml 267 | $(OCAMLDEP) $^ >$@ 268 | 269 | ifneq ($(MAKECMDGOALS),clean) 270 | -include .depend 271 | endif 272 | 273 | pyutils.cmo pyutils.cmx : pyutils.cmi 274 | 275 | generate : pyutils.$(CMOX) generate.$(CMOX) 276 | $(OCAMLCOPT) $(OCAMLLDFLAGS) $(OCAMLPREFERREDLIBS) $^ -o $@ 277 | 278 | generate.cmo : generate.ml 279 | 280 | generate.cmx : generate.ml 281 | 282 | pywrappers.ml pyml_wrappers.inc : generate 283 | ./generate 284 | 285 | pyml_wrappers.inc : pywrappers.ml 286 | 287 | pywrappers.mli : pywrappers.ml pytypes.cmi pyml_arch.cmi 288 | $(OCAMLC) $(OCAMLCFLAGS) -i $< >$@ 289 | 290 | pyml_tests.native : py.cmi pyml.cmxa pyml_tests_common.cmx pyml_tests.cmx 291 | $(OCAMLOPT) $(OCAMLLDFLAGS) $(OCAMLNATIVELIBS) pyml.cmxa \ 292 | pyml_tests_common.cmx pyml_tests.cmx -o $@ 293 | 294 | pyml_tests.bytecode : py.cmi pyml.cma pyml_tests_common.cmo pyml_tests.cmo 295 | $(OCAMLC) $(OCAMLLDFLAGS) $(OCAMLBYTECODELIBS) pyml.cma \ 296 | pyml_tests_common.cmo pyml_tests.cmo -o $@ 297 | 298 | numpy_tests.native : py.cmi pyml.cmxa numpy.cmxa \ 299 | pyml_tests_common.cmx numpy_tests.cmx 300 | $(OCAMLOPT) $(OCAMLLDFLAGS) $(OCAMLNATIVELIBSNUMPY) \ 301 | pyml.cmxa numpy.cmxa \ 302 | pyml_tests_common.cmx numpy_tests.cmx -o $@ 303 | 304 | numpy_tests.bytecode : py.cmi pyml.cma numpy.cma \ 305 | pyml_tests_common.cmo numpy_tests.cmo 306 | $(OCAMLC) $(OCAMLLDFLAGS) $(OCAMLBYTECODELIBSNUMPY) pyml.cma \ 307 | numpy.cma pyml_tests_common.cmo numpy_tests.cmo -o $@ 308 | 309 | pyml_arch.ml : pyml_arch.ml.c 310 | $(C_COMPILER) -E $< | sed '/^#/d' >$@ 311 | 312 | pyml_arch.cmo pyml_arch.cmx : pyml_arch.cmi 313 | 314 | %.cmi : %.mli 315 | $(OCAMLC) $(OCAMLCFLAGS) -c $< -o $@ 316 | 317 | %.cmo : %.ml 318 | $(OCAMLC) $(OCAMLCFLAGS) -c $< -o $@ 319 | 320 | %.cmx : %.ml 321 | $(OCAMLOPT) $(OCAMLCFLAGS) -c $< -o $@ 322 | 323 | %.o : %.c 324 | $(OCAMLC) $(OCAMLCFLAGS) -c $< -o $@ 325 | 326 | pyml_stubs.o : pyml_wrappers.inc 327 | 328 | pyml.cma : $(MODULES:=.cmo) libpyml_stubs$(EXT_LIB) 329 | $(OCAMLC) $(OCAMLLIBFLAGSBYTECODE) -a -dllib -lpyml_stubs $(MODULES:=.cmo) -o $@ 330 | 331 | pyml.cmxa : $(MODULES:=.cmx) libpyml_stubs$(EXT_LIB) 332 | $(OCAMLOPT) $(OCAMLLIBFLAGSNATIVE) -a $(MODULES:=.cmx) -o $@ 333 | 334 | pyml.cmxs : $(MODULES:=.cmx) libpyml_stubs$(EXT_LIB) 335 | $(OCAMLOPT) $(OCAMLLIBFLAGSNATIVE) -shared $(MODULES:=.cmx) -o $@ 336 | 337 | lib%$(EXT_LIB) : %.o 338 | $(OCAMLMKLIB) -o $(basename $<) $< 339 | 340 | numpy.cma : numpy.cmo libnumpy_stubs$(EXT_LIB) 341 | $(OCAMLC) $(OCAMLLIBNUMPYFLAGS) -a -dllib -lnumpy_stubs numpy.cmo -o $@ 342 | 343 | numpy.cmxa : numpy.cmx libnumpy_stubs$(EXT_LIB) 344 | $(OCAMLOPT) $(OCAMLLIBNUMPYFLAGS) -a numpy.cmx -o $@ 345 | 346 | numpy.cmxs : numpy.cmx libnumpy_stubs$(EXT_LIB) 347 | $(OCAMLOPT) $(OCAMLLIBNUMPYFLAGS) -shared numpy.cmx -o $@ 348 | 349 | pytop.cmo : pytop.ml pymltop_libdir.cmi 350 | $(OCAMLC) -I +compiler-libs -c $< 351 | 352 | pymltop_libdir.ml : 353 | if [ -z "$(PREFIX)" ]; then \ 354 | echo "let libdir=\"$(PWD)\""; \ 355 | else \ 356 | echo "let libdir=\"$(PREFIX)/lib/pyml/\""; \ 357 | fi >$@ 358 | 359 | pymltop : pyml.cma numpy.cma pymltop_libdir.cmo pytop.cmo 360 | $(OCAMLMKTOP) $(OCAMLLDFLAGS) $(OCAMLLIBNUMPYFLAGS) $(OCAMLBYTECODELIBSNUMPY) $^ -o $@ 361 | 362 | pyutop.cmo : pyutop.ml 363 | ifeq ($(HAVE_OCAMLFIND),no) 364 | $(error ocamlfind is needed for utop) 365 | endif 366 | $(OCAMLC) $(OCAMLCFLAGS) -thread -package utop -c $< -o $@ 367 | 368 | pymlutop : pyml.cma numpy.cma pymltop_libdir.cmo pytop.cmo pyutop.cmo 369 | ifeq ($(HAVE_OCAMLFIND),no) 370 | $(error ocamlfind is needed for utop) 371 | endif 372 | # ocamlmktop raises "Warning 31". See https://github.com/diml/utop/issues/212 373 | # $(OCAMLMKTOP) -o $@ -thread -linkpkg -package utop -dontlink compiler-libs $^ 374 | ocamlfind ocamlc -thread -linkpkg -linkall -predicates create_toploop \ 375 | -package compiler-libs.toplevel,utop,stdcompat $^ -o $@ 376 | 377 | pyops.ml: pyops.ml.new 378 | cp $< $@ 379 | 380 | pyops.mli: pyops.mli.new 381 | cp $< $@ 382 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``py.ml``: OCaml bindings for Python 2 | ==================================== 3 | 4 | ``py.ml`` provides OCaml bindings for Python 2 and Python 3. 5 | This library subsumes the ``pycaml`` library, which is no longer 6 | actively maintained. 7 | 8 | *OPAM:* ``opam install pyml`` 9 | 10 | The Python library is linked at runtime and the same executable can be 11 | run in a Python 2 or a Python 3 environment. ``py.ml`` does not 12 | require any Python library at compile time. 13 | The only compile time dependency is 14 | [``Stdcompat``](https://github.com/thierry-martinez/stdcompat) to ensure compatibility 15 | with all OCaml compiler versions from 3.12. 16 | 17 | Bindings are split in three modules: 18 | 19 | - ``Py`` provides the initialization functions and some high-level 20 | bindings, with error handling and naming conventions closer to OCaml 21 | usages. 22 | 23 | - ``Pycaml`` provides a signature close to the old ``Pycaml`` 24 | module, so as to ease migration. 25 | 26 | - ``Pywrappers`` provides low-level bindings, which follow closely the 27 | conventions of the C bindings for Python. Submodules 28 | ``Pywrappers.Python2`` and ``Pywrappers.Python3`` contain version-specific 29 | bindings. 30 | 31 | Custom top-level 32 | ---------------- 33 | 34 | A custom top-level with the C bindings can be compiled by ``make pymltop``. 35 | 36 | If you have ``utop`` and ``ocamlfind``, you can ``make pymlutop``. 37 | 38 | *For OPAM users:* ``pymltop`` is installed by default by ``opam install pyml``. 39 | ``pymlutop`` is installed whenever ``utop`` is available. 40 | 41 | Getting started 42 | --------------- 43 | 44 | ``Py.initialize ()`` loads the Python library. 45 | 46 | ``Py.Run.simple_string "print('Hello, world!')"`` executes a Python phrase 47 | and returns ``true`` if the execution succeeded, ``false`` otherwise. 48 | 49 | ``Py.Run.eval "18 + 42"`` evaluates a Python phrase and returns a value 50 | of type ``Py.Object.t``. Such a value can then be converted to an OCaml 51 | value: ``assert (Py.Int.to_int (Py.Run.eval "18 + 42") = 60)``. In case of 52 | error (either the phrase is syntactically incorrect or the evaluation raises 53 | an exception), an OCaml exception ``Py.E (type, msg)`` is raised. By default, 54 | ``Py.Run.eval`` evaluates an expression; use 55 | ``Py.Run.eval ~start:Py.File`` to evaluate a Python phrase as a module/file 56 | (with ``import`` directives and so on). 57 | 58 | To make an OCaml value accessible from Python, we create a module (called 59 | ``ocaml`` in this example, but it can be any valid Python module name): 60 | 61 | ```ocaml 62 | let m = Py.Import.add_module "ocaml" in 63 | Py.Module.set m "example_value" 64 | (Py.List.of_list_map Py.Int.of_int [1;2;3]); 65 | Py.Run.eval ~start:Py.File " 66 | from ocaml import example_value 67 | print(example_value)" 68 | ``` 69 | 70 | OCaml functions can be passed in the same way. 71 | 72 | ``` ocaml 73 | let m = Py.Import.add_module "ocaml" in 74 | let hello args = 75 | Printf.printf "Hello, %s!\n" (Py.String.to_string args.(0)); 76 | Py.none in 77 | Py.Module.set m "hello" (Py.Callable.of_function hello); 78 | Py.Run.eval ~start:Py.File " 79 | from ocaml import hello 80 | hello('World')" 81 | ``` 82 | 83 | ``Py.Module.set m "hello" (Py.Callable.of_function hello)`` 84 | can be written 85 | ``Py.Module.set_function m "hello" hello``. 86 | 87 | Python functions can be called from OCaml too. 88 | 89 | ```ocaml 90 | let builtins = Py.Eval.get_builtins () in 91 | let sorted_python = Py.Dict.find_string builtins "sorted" in 92 | let sorted = Py.Callable.to_function sorted_python in 93 | let result = 94 | sorted [| Py.List.of_list_map Py.Float.of_float [3.0; 2.0] |] in 95 | assert (Py.List.to_list_map Py.Float.to_float result = [2.0; 3.0]) 96 | ``` 97 | 98 | ``Py.Run.interactive ()`` runs the Python top-loop. 99 | It can be run after some OCaml values have been made accessible through a module 100 | as above. If IPython is available, the top-loop can be run with 101 | ``Py.Run.ipython ()``. 102 | 103 | 104 | With OCaml 4.06 and greater, the module ``Pyops`` declares indexing operators. 105 | 106 | | Indexing operator | Getter | Setter | 107 | |-------------------|--------|--------| 108 | | ``x.@(v)`` | ``Py.Object.find_attr`` | ``Py.Object.set_attr`` | 109 | | ``x.@$(v)`` | ``Py.Object.find_attr_string`` / ``Py.Module.get`` | ``Py.Object.set_attr_string`` / ``Py.Module.set `` | 110 | | ``x.![v]`` | ``Py.Object.find`` | ``Py.Object.set_item`` | 111 | | ``x.!$[v]`` | ``Py.Object.find_string`` | ``Py.Object.set_item_string`` | 112 | | ``x.%[v]`` | ``Py.Dict.find`` | ``Py.Dict.set_item`` | 113 | | ``x.%$[v]`` | ``Py.Dict.find_string`` | ``Py.Dict.set_item_string`` | 114 | | ``x.&(v)`` | ``Py.Module.get_function`` | ``Py.Module.set_function`` | 115 | 116 | The "hello world" example above can be written: 117 | 118 | ``` ocaml 119 | let m = Py.Import.add_module "ocaml" in 120 | let open Pyops in 121 | m.&("hello") <- (fun args -> 122 | Printf.printf "Hello, %s!\n" (Py.String.to_string args.(0)); 123 | Py.none); 124 | Py.Run.eval ~start:Py.File " 125 | from ocaml import hello 126 | hello('World')" 127 | ``` 128 | 129 | Error handling 130 | -------------- 131 | 132 | All Python exceptions are caught as OCaml exceptions ``Py.E (type, msg)``, where 133 | ``type`` and ``msg`` are two Python objects. Typically, one can convert ``type`` 134 | and ``msg`` to strings with ``Py.Object.to_string`` to display or analyse them. 135 | 136 | When an OCaml function ``f`` is called from Python 137 | (passed by ``Py.Callable.of_function``), 138 | ``f`` can raise a Python exception by raising an OCaml exception of the form 139 | ``Py.E (type, msg)``. To raise standard errors more conveniently, 140 | ``f`` can raise an exception of the form 141 | ``Py.Err (type, msg)`` instead, 142 | where ``type`` belongs to the enumeration ``Py.Err.t`` 143 | and ``msg`` is an OCaml string. 144 | If ``f`` raises an exception that is neither 145 | of the form ``Py.E`` nor ``Py.Err``, then 146 | this exception is encapsulated with its backtrace in a Python exception 147 | (of class `ocaml exception` derived from `BaseException` and not from 148 | `Exception`, so as not to be caught), leading the Python interpreter to 149 | be interrupted, and the exception is raised back in OCaml. 150 | 151 | ``Py.Run.simple_string`` catches all Python exceptions (and OCaml 152 | exceptions as well) and returns a single Boolean to indicate 153 | success. One can prefer ``Py.Run.eval`` to get proper error handling. 154 | 155 | Data types 156 | ---------- 157 | 158 | Python values have type ``Py.Object.t``. 159 | 160 | ``Py.String.of_string`` and ``Py.String.to_string`` convert back and forth 161 | OCaml to Python strings: Unicode strings and legacy strings are handled 162 | uniformly. 163 | 164 | ``Py.Int.of_int`` and ``Py.Int.to_int`` convert back and forth 165 | OCaml to Python integers. For big numbers, one should use an intermediate 166 | textual representation with ``Py.Int.of_string`` and ``Py.Int.to_string``. 167 | 168 | ``Py.Float.of_float`` and ``Py.Float.to_float`` convert back and forth 169 | OCaml to Python floating-point values. 170 | 171 | The module `Py.Number` define common arithmetic and bitwise operations on 172 | Python numbers. It can be open locally to take benefit from operator 173 | overloading. E.g.: 174 | 175 | ``` ocaml 176 | let m = Py.Import.add_module "ocaml" in 177 | let square args = Py.Number.(args.(0) ** of_int 2) in 178 | Py.Module.set_function m "square" square; 179 | ignore (Py.Run.eval ~start:Py.File " 180 | from ocaml import square 181 | print(square(3))") 182 | ``` 183 | 184 | ``Py.Tuple.of_array`` and ``Py.Tuple.to_array`` can be used to construct 185 | and destruct Python tuples. 186 | ``Py.Tuple.of_list`` and ``Py.Tuple.to_list`` are available as well. 187 | The empty tuple is ``Py.Tuple.empty``. 188 | Converters can be composed with the ``_map`` suffix: 189 | for example, ``Py.Tuple.of_list_map Py.Int.of_int`` converts a list of integers 190 | to a Python value. 191 | Short tuples can be constructed and destructed with ``Py.Tuple.of_tuple1``, 192 | ..., ``Py.Tuple.of_tuple5`` and ``Py.Tuple.to_tuple1``, ..., 193 | ``Py.Tuple.to_tuple5``. 194 | 195 | For Python lists, 196 | ``Py.List.of_array``, ``Py.List.to_array``, 197 | ``Py.List.of_list`` and ``Py.List.to_list`` are available 198 | (along with their ``_map`` variant). 199 | ``Py.List`` and ``Py.Tuple`` include the ``Py.Sequence`` module: 200 | along with ``to_array`` and ``to_list``, iterators like ``fold_left`` 201 | ``fold_right``, ``for_all``, etc. are available. 202 | 203 | Python iterators can be iterated with ``Py.Iter.next`` which returns an option 204 | value, where ``None`` represents the end of the iteration. ``Py.Iter.iter``, 205 | ``Py.Iter.fold_left``,``Py.Iter.fold_right`` and ``Py.Iter.to_list`` can be 206 | used as well. A Python iterator can be created with ``Py.Iter.create`` from an 207 | OCaml function that returns an option. 208 | 209 | Python dictionaries can be constructed and destructed with 210 | ``Py.Dict.of_bindings_string`` and ``Py.Dict.to_bindings_string`` from and to 211 | associative lists between string keys and Python values. 212 | ``Py.Dict.find_string`` can be used to find a single value (the exception 213 | ``Not_found`` is raised if the key is not in the dictionary; 214 | ``Py.Dict.get_item_string`` provides the option variant). 215 | 216 | Python closures can be called from OCaml with 217 | ``Py.Callable.to_function``, or ``Py.Callable.to_function_with_keywords`` to 218 | pass keywords as an associative list. 219 | Symmetrically, OCaml functions can be turned into Python closures 220 | with 221 | ``Py.Callable.of_function`` and ``Py.Callable.of_function_with_keywords`` 222 | (the latter function passes keywords as a dictionary to the OCaml callback: 223 | values can be retrieved efficiently with ``Py.Dict.find_string``). 224 | 225 | ```ocaml 226 | let m = Py.Import.add_module "ocaml" in 227 | Py.Module.set_function_with_keywords m "length" 228 | (fun args kw -> 229 | let x = Py.Dict.find_string kw "x" in 230 | let y = Py.Dict.find_string kw "y" in 231 | Py.Number.((x ** of_int 2 + y ** of_int 2) ** of_float 0.5)); 232 | ignore (Py.Run.eval ~start:Py.File " 233 | from ocaml import length 234 | print(length(x=3, y=4))") 235 | ``` 236 | 237 | 238 | Modules 239 | ------- 240 | 241 | New modules can be defined with ``Py.Import.add_module`` 242 | and existing modules can be imported with ``Py.Import.import_module`` 243 | (or the shorter ``Py.import`` alias). 244 | Trying to import a module that does not exist leads to a Python exception: 245 | use ``Py.Import.import_module_opt`` to get an option result instead 246 | (or the shorter ``Py.import_opt`` alias). 247 | 248 | ``Module.get`` and ``Module.set`` allow to retrieve and define module 249 | members. 250 | For function members, there are shortcuts to do the conversion with 251 | ``Py.Callable``: ``Module.get_function`` and ``Module.set_function`` 252 | (and ``Module.get_function_with_keywords`` 253 | and ``Module.set_function_with_keywords``). 254 | 255 | If we consider the following Python code taken from ``matplotlib`` 256 | documentation: 257 | 258 | ```python 259 | import numpy as np 260 | import matplotlib.pyplot as plt 261 | 262 | x = np.arange(0, 5, 0.1); 263 | y = np.sin(x) 264 | plt.plot(x, y) 265 | plt.show() 266 | ``` 267 | 268 | The code can be written directly in OCaml as such: 269 | 270 | ```ocaml 271 | let np = Py.import "numpy" in 272 | let plt = Py.import "matplotlib.pyplot" in 273 | let x = Py.Module.get_function np "arange" 274 | (Array.map Py.Float.of_float [| 0.; 5.; 0.1 |]) in 275 | let y = Py.Module.get_function np "sin" [| x |] in 276 | ignore (Py.Module.get_function plt "plot" [| x; y |]); 277 | assert (Py.Module.get_function plt "show" [| |] = Py.none) 278 | ``` 279 | 280 | or, using indexing operators (OCaml 4.06): 281 | 282 | ```ocaml 283 | let np = Py.import "numpy" in 284 | let plt = Py.import "matplotlib.pyplot" in 285 | let open Pyops in 286 | let x = np.&("arange")(Array.map Py.Float.of_float [| 0.; 5.; 0.1 |]) in 287 | let y = np.&("sin")[| x |] in 288 | ignore (plt.&("plot")[| x; y |]); 289 | assert (plt.&("show")[| |] = Py.none) 290 | ``` 291 | 292 | NumPy 293 | ----- 294 | 295 | If the NumPy library is installed, then OCaml float arrays and bigarrays 296 | can be shared in place with Python code as NumPy arrays (without copy). 297 | Python code can then directly read and write from and to the OCaml arrays 298 | and changes are readable from OCaml. 299 | 300 | ```ocaml 301 | let array = [| 1.; 2. ; 3. |] in 302 | let m = Py.Import.add_module "ocaml" in 303 | Py.Module.set m "array" (Py.Array.numpy array); 304 | ignore (Py.Run.eval ~start:Py.File " 305 | from ocaml import array 306 | array *= 2"); 307 | assert (array = [| 2.; 4.; 6. |]) 308 | ``` 309 | 310 | Bigarrays are handled by the ``Numpy`` module that is shipped in 311 | ``numpy.cma/cmxa`` and requires ``bigarray.cma/cmxa``. Numpy arrays can 312 | be obtained from bigarrays with ``Numpy.of_bigarray`` and bigarrays can 313 | be obtained from Numpy arrays with ``Numpy.to_bigarray`` (the provided 314 | kind and layout should match the format of the Numpy array). 315 | 316 | ```ocaml 317 | let m = Py.Import.add_module "test" in 318 | let callback arg = 319 | let bigarray = 320 | Numpy.to_bigarray Bigarray.nativeint Bigarray.c_layout arg.(0) in 321 | let array1 = Bigarray.array1_of_genarray bigarray in 322 | assert (Bigarray.Array1.get array1 0 = 0n); 323 | assert (Bigarray.Array1.get array1 1 = 1n); 324 | assert (Bigarray.Array1.get array1 2 = 2n); 325 | assert (Bigarray.Array1.get array1 3 = 3n); 326 | Py.none in 327 | Py.Module.set m "callback" (Py.Callable.of_function callback); 328 | assert (Py.Run.simple_string " 329 | from test import callback 330 | import numpy 331 | callback(numpy.array([0,1,2,3])) 332 | ") 333 | ``` 334 | -------------------------------------------------------------------------------- /pycaml.ml: -------------------------------------------------------------------------------- 1 | type pyobject = Py.Object.t 2 | 3 | type pyobject_type = 4 | | TupleType 5 | | BytesType 6 | | UnicodeType 7 | | BoolType 8 | | IntType 9 | | FloatType 10 | | ListType 11 | | NoneType 12 | | CallableType 13 | | ModuleType 14 | | ClassType 15 | | TypeType 16 | | DictType 17 | | NullType 18 | | CamlpillType 19 | | OtherType 20 | | EitherStringType (* Signifies that either of BytesType or UnicodeType is allowed. *) 21 | | CamlpillSubtype of string (* Signifies that only the particular Camlpill variety is allowed. *) 22 | | AnyType 23 | 24 | let pytype_name t = 25 | match t with 26 | | TupleType -> "Python-Tuple" 27 | | BytesType -> "Python-Bytes" 28 | | UnicodeType -> "Python-Unicode" 29 | | BoolType -> "Python-Bool" 30 | | IntType -> "Python-Int" 31 | | FloatType -> "Python-Float" 32 | | ListType -> "Python-List" 33 | | NoneType -> "Python-None" 34 | | CallableType -> "Python-Callable" 35 | | ModuleType -> "Python-Module" 36 | | ClassType -> "Python-Class" 37 | | NullType -> "Python-Null" 38 | | TypeType -> "Python-Type" 39 | | DictType -> "Python-Dict" 40 | | CamlpillType -> "Python-Camlpill" 41 | | OtherType -> "Python-Other" 42 | | EitherStringType -> "Python-EitherString" 43 | | CamlpillSubtype sym -> "Python-Camlpill-" ^ sym 44 | | AnyType -> "Python-Any" 45 | 46 | let _py_type_of_pyobject_type t = 47 | match t with 48 | | BoolType -> Py.Type.Bool 49 | | BytesType -> Py.Type.Bytes 50 | | CallableType -> Py.Type.Callable 51 | | CamlpillType 52 | | CamlpillSubtype _ -> Py.Type.Capsule 53 | | DictType -> Py.Type.Dict 54 | | FloatType -> Py.Type.Float 55 | | ListType -> Py.Type.List 56 | | IntType -> Py.Type.Long 57 | | ModuleType -> Py.Type.Module 58 | | NoneType -> Py.Type.None 59 | | NullType -> Py.Type.Null 60 | | TupleType -> Py.Type.Tuple 61 | | TypeType -> Py.Type.Type 62 | | EitherStringType 63 | | UnicodeType -> Py.Type.Unicode 64 | | AnyType 65 | | ClassType 66 | | OtherType -> Py.Type.Unknown 67 | 68 | let pyobject_type_of_py_type t = 69 | match t with 70 | Py.Type.Unknown | Py.Type.Iter | Py.Type.Set -> OtherType 71 | | Py.Type.Bool -> BoolType 72 | | Py.Type.Bytes -> BytesType 73 | | Py.Type.Callable -> CallableType 74 | | Py.Type.Capsule -> CamlpillType 75 | | Py.Type.Closure -> CallableType 76 | | Py.Type.Dict -> DictType 77 | | Py.Type.Float -> FloatType 78 | | Py.Type.List -> ListType 79 | | Py.Type.Int 80 | | Py.Type.Long -> IntType 81 | | Py.Type.Module -> ModuleType 82 | | Py.Type.None -> NoneType 83 | | Py.Type.Null -> NullType 84 | | Py.Type.Tuple -> TupleType 85 | | Py.Type.Type -> TypeType 86 | | Py.Type.Unicode -> UnicodeType 87 | 88 | 89 | type pyerror_type = 90 | Pyerr_Exception 91 | | Pyerr_StandardError 92 | | Pyerr_ArithmeticError 93 | | Pyerr_LookupError 94 | | Pyerr_AssertionError 95 | | Pyerr_AttributeError 96 | | Pyerr_EOFError 97 | | Pyerr_EnvironmentError 98 | | Pyerr_FloatingPointError 99 | | Pyerr_IOError 100 | | Pyerr_ImportError 101 | | Pyerr_IndexError 102 | | Pyerr_KeyError 103 | | Pyerr_KeyboardInterrupt 104 | | Pyerr_MemoryError 105 | | Pyerr_NameError 106 | | Pyerr_NotImplementedError 107 | | Pyerr_OSError 108 | | Pyerr_OverflowError 109 | | Pyerr_ReferenceError 110 | | Pyerr_RuntimeError 111 | | Pyerr_SyntaxError 112 | | Pyerr_SystemExit 113 | | Pyerr_TypeError 114 | | Pyerr_ValueError 115 | | Pyerr_ZeroDivisionError 116 | 117 | include Pywrappers.Pycaml 118 | 119 | exception Pycaml_exn of (pyerror_type * string) 120 | 121 | let make_pill_wrapping name _instance = Py.Capsule.make name 122 | 123 | let py_false () = Py.Bool.f 124 | 125 | let py_finalize = Py.finalize 126 | 127 | let py_initialize () = Py.initialize () 128 | 129 | let py_is_true = Py.Object.is_true 130 | 131 | let py_isinitialized () = 132 | if Py.is_initialized () then 1 133 | else 0 134 | 135 | let py_setprogramname = Py.set_program_name 136 | 137 | let py_setpythonhome = Py.set_python_home 138 | 139 | let py_getprogramname = Py.get_program_name 140 | 141 | let py_getpythonhome = Py.get_python_home 142 | 143 | let py_getprogramfullpath = Py.get_program_full_path 144 | 145 | let py_getprefix = Py.get_prefix 146 | 147 | let py_getexecprefix = Py.get_exec_prefix 148 | 149 | let py_getpath = Py.get_path 150 | 151 | let py_true () = Py.Bool.t 152 | 153 | let _pycaml_seterror error msg = 154 | let error' = 155 | match error with 156 | Pyerr_Exception -> Py.Err.Exception 157 | | Pyerr_StandardError -> Py.Err.StandardError 158 | | Pyerr_ArithmeticError -> Py.Err.ArithmeticError 159 | | Pyerr_LookupError -> Py.Err.LookupError 160 | | Pyerr_AssertionError -> Py.Err.AssertionError 161 | | Pyerr_AttributeError -> Py.Err.AttributeError 162 | | Pyerr_EOFError -> Py.Err.EOFError 163 | | Pyerr_EnvironmentError -> Py.Err.EnvironmentError 164 | | Pyerr_FloatingPointError -> Py.Err.FloatingPointError 165 | | Pyerr_IOError -> Py.Err.IOError 166 | | Pyerr_ImportError -> Py.Err.ImportError 167 | | Pyerr_IndexError -> Py.Err.IndexError 168 | | Pyerr_KeyError -> Py.Err.KeyError 169 | | Pyerr_KeyboardInterrupt -> Py.Err.KeyboardInterrupt 170 | | Pyerr_MemoryError -> Py.Err.MemoryError 171 | | Pyerr_NameError -> Py.Err.NameError 172 | | Pyerr_NotImplementedError -> Py.Err.NotImplementedError 173 | | Pyerr_OSError -> Py.Err.OSError 174 | | Pyerr_OverflowError -> Py.Err.OverflowError 175 | | Pyerr_ReferenceError -> Py.Err.ReferenceError 176 | | Pyerr_RuntimeError -> Py.Err.RuntimeError 177 | | Pyerr_SyntaxError -> Py.Err.SyntaxError 178 | | Pyerr_SystemExit -> Py.Err.SystemExit 179 | | Pyerr_TypeError -> Py.Err.TypeError 180 | | Pyerr_ValueError -> Py.Err.ValueError 181 | | Pyerr_ZeroDivisionError -> Py.Err.ZeroDivisionError in 182 | Py.Err.set_error error' msg 183 | 184 | let int_of_bool b = 185 | if b then -1 186 | else 0 187 | 188 | let _pybytes_check v = int_of_bool (Py.Type.get v = Py.Type.Bytes) 189 | 190 | let pybytes_asstring = Py.String.to_string 191 | 192 | let pybytes_format (fmt, args) = Py.String.format fmt args 193 | 194 | let pyiter_check v = int_of_bool (Py.Type.get v = Py.Type.Iter) 195 | 196 | let pymodule_getfilename = Py.Module.get_filename 197 | 198 | let pymodule_getname = Py.Module.get_name 199 | 200 | let _pyunicode_check v = int_of_bool (Py.Type.get v = Py.Type.Unicode) 201 | 202 | let pyerr_fetch _ = 203 | match Py.Err.fetch () with 204 | None -> (Py.null, Py.null, Py.null) 205 | | Some e -> e 206 | 207 | let pyerr_normalizeexception e = e 208 | 209 | let pylist_toarray = Py.Sequence.to_array 210 | 211 | let pyint_asint = Py.Long.to_int 212 | 213 | let pyint_fromint = Py.Long.of_int 214 | 215 | let pynone () = Py.none 216 | 217 | let pynull () = Py.null 218 | 219 | let pystring_asstring = Py.String.to_string 220 | 221 | let pystring_fromstring = Py.String.of_string 222 | 223 | let pytuple_fromarray = Py.Tuple.of_array 224 | 225 | let pytuple_fromsingle = Py.Tuple.singleton 226 | 227 | let pytuple_toarray = Py.Tuple.to_array 228 | 229 | let pytype v = pyobject_type_of_py_type (Py.Type.get v) 230 | 231 | let register_ocamlpill_types _array = () 232 | 233 | let pyeval_callobject (func, arg) = 234 | Pywrappers.pyeval_callobjectwithkeywords func arg Py.null 235 | 236 | let pyimport_execcodemodule (obj, s) = 237 | Pywrappers.pyimport_execcodemodule s obj 238 | 239 | let pyimport_importmoduleex (name, globals, locals, fromlist) = 240 | Pywrappers.pyimport_importmodulelevel name globals locals fromlist (-1) 241 | 242 | let py_compilestringflags (str, filename, start, flags) = 243 | if Py.version_major () <= 2 then 244 | py_compilestringflags (str, filename, start, flags) 245 | else 246 | py_compilestringexflags (str, filename, start, flags, -1) 247 | 248 | let py_compilestring (str, filename, start) = 249 | py_compilestringflags (str, filename, start, None) 250 | 251 | let pyrun_anyfile (fd, filename) = 252 | pyrun_anyfileexflags (fd, filename, 0, None) 253 | 254 | let pyrun_anyfileex (fd, filename, closeit) = 255 | pyrun_anyfileexflags (fd, filename, closeit, None) 256 | 257 | let pyrun_file (fd, filename, start, globals, locals) = 258 | pyrun_fileexflags (fd, filename, start, globals, locals, 0, None) 259 | 260 | let pyrun_fileex (fd, filename, start, globals, locals, closeit) = 261 | pyrun_fileexflags (fd, filename, start, globals, locals, closeit, None) 262 | 263 | let pyrun_interactiveone (fd, filename) = 264 | pyrun_interactiveoneflags (fd, filename, None) 265 | 266 | let pyrun_interactiveloop (fd, filename) = 267 | pyrun_interactiveloopflags (fd, filename, None) 268 | 269 | let pyrun_simplefile (fd, filename) = 270 | pyrun_simplefileexflags (fd, filename, 0, None) 271 | 272 | let pyrun_simplefileex (fd, filename, closeit) = 273 | pyrun_simplefileexflags (fd, filename, closeit, None) 274 | 275 | let pyrun_simplestring s = pyrun_simplestringflags (s, None) 276 | 277 | let pyrun_string (s, start, globals, locals) = 278 | pyrun_stringflags (s, start, globals, locals, None) 279 | 280 | let pywrap_closure f = Py.Callable.of_function_as_tuple f 281 | 282 | let pytuple_empty = Py.Tuple.empty 283 | 284 | let pytuple2 = Py.Tuple.of_tuple2 285 | 286 | let pytuple3 = Py.Tuple.of_tuple3 287 | 288 | let pytuple4 = Py.Tuple.of_tuple4 289 | 290 | let pytuple5 = Py.Tuple.of_tuple5 291 | 292 | let set_python_argv = Py.set_argv 293 | 294 | let py_optionally unwrapper py_value = 295 | if not (Py.List.check py_value) then 296 | Py.Type.mismatch "List" py_value; 297 | match Py.List.size py_value with 298 | 0 -> None 299 | | 1 -> Some (unwrapper (Py.List.get_item py_value 0)) 300 | | _ -> Py.Type.mismatch "List of size 0 or 1" py_value 301 | 302 | let pycallable_asfun = Py.Callable.to_function 303 | 304 | let guarded_pytuple_toarray = Py.Tuple.to_array 305 | 306 | let guarded_pylist_toarray = Py.List.to_array 307 | 308 | let guarded_pybytes_asstring = Py.String.to_string 309 | 310 | let guarded_pynumber_asfloat = Py.Number.to_float 311 | 312 | let guarded_pyfloat_asfloat = Py.Float.to_float 313 | 314 | let guarded_pyint_asint = Py.Long.to_int 315 | 316 | let ocamlpill_hard_unwrap v = snd (Py.Capsule.unsafe_unwrap_value v) 317 | 318 | let python_eval = pyrun_simplestring 319 | 320 | let python_load filename = 321 | ignore (Py.Run.load (Py.Filename filename) filename) 322 | 323 | let pybytes_asstringandsize = Py.String.to_string 324 | 325 | let pystring_asstringandsize = Py.String.to_string 326 | 327 | let pyunicode_decodeutf8 (s, errors) = Py.String.decode_UTF8 s ?errors 328 | 329 | let byteorder_of_int_option byteorder_int = 330 | match byteorder_int with 331 | None -> None 332 | | Some (-1) -> Some Py.LittleEndian 333 | | Some 1 -> Some Py.BigEndian 334 | | _ -> failwith "pyunicode_decodeutf: invalid byteorder" 335 | 336 | let pyunicode_decodeutf16 (s, errors, byteorder_int) = 337 | let byteorder = byteorder_of_int_option byteorder_int in 338 | fst (Py.String.decode_UTF16 s ?errors ?byteorder) 339 | 340 | let pyunicode_decodeutf32 (s, errors, byteorder_int) = 341 | let byteorder = byteorder_of_int_option byteorder_int in 342 | fst (Py.String.decode_UTF32 s ?errors ?byteorder) 343 | 344 | let pyunicode_fromunicode f size = Py.String.of_unicode (Array.init size f) 345 | 346 | let pyunicode_asunicode = Py.String.to_unicode 347 | 348 | let pyunicode_getsize = Py.String.length 349 | 350 | let pyobject_ascharbuffer = Py.Object.as_char_buffer 351 | 352 | let pyobject_asreadbuffer = Py.Object.as_read_buffer 353 | 354 | let pyobject_aswritebuffer = Py.Object.as_write_buffer 355 | 356 | let python () = 357 | Py.Run.interactive (); 358 | 0 359 | 360 | let ipython () = 361 | Py.Run.ipython (); 362 | 0 363 | 364 | let make_ocamlpill_wrapper_unwrapper = make_pill_wrapping 365 | 366 | let ocamlpill_type_of = Py.Capsule.type_of 367 | 368 | let type_mismatch_exception type_wanted type_here pos exn_name = 369 | Pycaml_exn 370 | (Pyerr_TypeError, 371 | (Printf.sprintf "Argument %d: Type wanted: %s -- Type provided: %s%s." 372 | (pos + 1) (pytype_name type_wanted) (pytype_name type_here) 373 | exn_name)) 374 | 375 | let pill_type_mismatch_exception ?position ?exn_name wanted gotten = 376 | let arg_no = 377 | match position with 378 | None -> "" 379 | | Some _p -> "Argument %d: " in 380 | let en = 381 | match exn_name with 382 | None -> "" 383 | | Some n -> n in 384 | Pycaml_exn 385 | (Pyerr_TypeError, 386 | Printf.sprintf 387 | "%sPython-Ocaml Pill Type mismatch: wanted: '%s' - got: '%s'%s" 388 | arg_no wanted gotten en) 389 | 390 | let check_pill_type ?position ?exn_name wanted pill = 391 | let gotten = ocamlpill_type_of pill in 392 | if not (gotten = wanted) then 393 | raise 394 | (pill_type_mismatch_exception 395 | ?position:position ?exn_name:exn_name wanted gotten) 396 | 397 | let unpythonizing_function ?name ?(catch_weird_exceptions = true) ?extra_guards 398 | ?(expect_tuple = false) wanted_types function_body = 399 | ignore catch_weird_exceptions; 400 | let exn_name = 401 | match name with 402 | None -> "" 403 | | Some s -> Printf.sprintf " (%s)" s in 404 | let work_fun python_args = 405 | let body () = 406 | let nr_args_given = 407 | if expect_tuple then pytuple_size python_args 408 | else 1 in 409 | let nr_args_wanted = Array.length wanted_types in 410 | let () = 411 | if nr_args_given <> nr_args_wanted then 412 | raise 413 | (Pycaml_exn 414 | (Pyerr_IndexError, 415 | (Printf.sprintf 416 | "Args given: %d Wanted: %d%s" 417 | nr_args_given nr_args_wanted exn_name))) 418 | in 419 | let arr_args = 420 | if expect_tuple then 421 | Py.Tuple.to_array python_args 422 | else 423 | [| python_args |] 424 | in 425 | let rec check_types pos = 426 | if pos = nr_args_given then 427 | function_body arr_args 428 | else 429 | let arg = arr_args.(pos) in 430 | let type_wanted = wanted_types.(pos) in 431 | let () = 432 | match type_wanted with 433 | | AnyType -> () 434 | | EitherStringType -> 435 | if not (Py.String.check arg) then 436 | raise 437 | (type_mismatch_exception 438 | type_wanted (pytype arg) pos exn_name) 439 | | CamlpillSubtype sym -> 440 | check_pill_type ~position:pos ~exn_name:exn_name sym arg 441 | | _ -> 442 | let type_here = pytype arg in 443 | if type_here <> type_wanted then 444 | raise 445 | (type_mismatch_exception type_wanted type_here pos 446 | exn_name) in 447 | begin 448 | match extra_guards with 449 | None -> () 450 | | Some guards -> 451 | let guard = guards.(pos) in 452 | let guard_error = guard arr_args.(pos) in 453 | match guard_error with 454 | None -> () 455 | | Some msg -> 456 | raise (Pycaml_exn 457 | (Pyerr_TypeError, 458 | (Printf.sprintf 459 | "Check for argument %d failed: %s%s" 460 | (pos + 1) msg exn_name))) 461 | end; 462 | check_types (pos+1) in 463 | check_types 0 in 464 | body () in 465 | work_fun 466 | 467 | let py_profiling_active = ref false 468 | 469 | let py_profile_hash = Stdcompat.Lazy.from_fun (fun () -> Hashtbl.create 100) 470 | 471 | let py_activate_profiling () = 472 | let old_value = !py_profiling_active in 473 | py_profiling_active := true; 474 | old_value 475 | 476 | let py_deactivate_profiling () = 477 | let old_value = !py_profiling_active in 478 | py_profiling_active := false; 479 | old_value 480 | 481 | let py_profile_report () = 482 | let add_entry name time_and_calls list = 483 | (name, time_and_calls.(0), time_and_calls.(1)) :: list in 484 | let items = Hashtbl.fold add_entry (Lazy.force py_profile_hash) [] in 485 | let array = Array.of_list items in 486 | let order (_, time_a, _) (_, time_b, _) = compare time_b time_a in 487 | Array.sort order array; 488 | array 489 | 490 | let py_profile_reset () = 491 | Hashtbl.clear (Lazy.force py_profile_hash) 492 | 493 | let python_interfaced_function ?name ?(catch_weird_exceptions = true) ?doc 494 | ?extra_guards wanted_types function_body = 495 | let exn_name = 496 | match name with 497 | None -> "" 498 | | Some s -> Printf.sprintf " (%s)" s in 499 | let closure = 500 | unpythonizing_function ?name ~catch_weird_exceptions ?extra_guards 501 | wanted_types function_body in 502 | let closure' args = 503 | try 504 | closure args 505 | with 506 | Not_found -> 507 | let msg = Printf.sprintf "OCaml exception 'Not_found'%s" exn_name in 508 | raise (Py.Err (Py.Err.LookupError, msg)) 509 | | Division_by_zero -> 510 | let msg = 511 | Printf.sprintf "OCaml exception 'Division_by_zero'%s" exn_name in 512 | raise (Py.Err (Py.Err.ZeroDivisionError, msg)) 513 | | Failure s -> 514 | let msg = Printf.sprintf "OCaml exception 'Failure: %s'%s" s exn_name in 515 | raise (Py.Err (Py.Err.StandardError, msg)) 516 | | Invalid_argument s -> 517 | let msg = 518 | Printf.sprintf 519 | "OCaml exception 'Invalid_argument: %s'%s" s exn_name in 520 | raise (Py.Err (Py.Err.StandardError, msg)) 521 | | Out_of_memory -> 522 | let msg = Printf.sprintf "OCaml exception 'Out_of_memory'%s" exn_name in 523 | raise (Py.Err (Py.Err.MemoryError, msg)) 524 | | Stack_overflow -> 525 | let msg = 526 | Printf.sprintf "OCaml exception 'Stack_overflow'%s" exn_name in 527 | raise (Py.Err (Py.Err.OverflowError, msg)) 528 | | Sys_error s -> 529 | let msg = 530 | Printf.sprintf "OCaml exception 'Sys_error: %s'%s" s exn_name in 531 | raise (Py.Err (Py.Err.StandardError, msg)) 532 | | End_of_file -> 533 | let msg = Printf.sprintf "OCaml exception 'End_of_file'%s" exn_name in 534 | raise (Py.Err (Py.Err.IOError, msg)) 535 | | Match_failure (filename, line, column) -> 536 | let msg = 537 | Printf.sprintf 538 | "OCaml exception 'Match_faiure file=%s line=%d(c. %d)'%s" 539 | filename line column exn_name in 540 | raise (Py.Err (Py.Err.StandardError, msg)) 541 | | Assert_failure (filename, line, column) -> 542 | let msg = 543 | Printf.sprintf 544 | "OCaml exception 'Assert_faiure file=%s line=%d(c. %d)'%s" 545 | filename line column exn_name in 546 | raise (Py.Err (Py.Err.StandardError, msg)) 547 | | Py.E (_, _) | Py.Err (_, _) as e -> raise e 548 | | something_else when catch_weird_exceptions -> 549 | let msg = 550 | Printf.sprintf "OCaml weird low-level exception '%s'%s" 551 | (Printexc.to_string something_else) exn_name in 552 | raise (Py.Err (Py.Err.StandardError, msg)) in 553 | let closure'' = 554 | match name with 555 | Some s when !py_profiling_active -> 556 | let closure'' args = 557 | let t0 = Unix.gettimeofday () in 558 | let stop_timer () = 559 | let t1 = Unix.gettimeofday () in 560 | let time_and_calls = 561 | let py_profile_hash' = Lazy.force py_profile_hash in 562 | try 563 | Hashtbl.find py_profile_hash' s 564 | with Not_found -> 565 | let x = [| 0.; 0. |] in 566 | Hashtbl.add py_profile_hash' s x; 567 | x in 568 | time_and_calls.(0) <- time_and_calls.(0) +. t1 -. t0; 569 | time_and_calls.(1) <- time_and_calls.(1) +. 1. in 570 | try 571 | let result = closure' args in 572 | stop_timer (); 573 | result 574 | with e -> 575 | stop_timer (); 576 | raise e in 577 | closure'' 578 | | _ -> closure' in 579 | Py.Callable.of_function_as_tuple ?docstring:doc closure'' 580 | 581 | let python_pre_interfaced_function ?catch_weird_exceptions ?doc ?extra_guards 582 | wanted_types function_body name = 583 | python_interfaced_function ~name ?catch_weird_exceptions ?doc ?extra_guards 584 | wanted_types function_body 585 | 586 | let pythonize_string = Py.String.of_string 587 | 588 | let unpythonize_string = Py.String.to_string 589 | 590 | let py_homogeneous_list_as_array ?error_label ?length type_name type_checker 591 | unwrapper list = 592 | let the_error_label = 593 | match error_label with 594 | None -> "" 595 | | Some x -> Printf.sprintf "%s: " x in 596 | let list_size = Py.List.size list in 597 | let () = 598 | match length with 599 | None -> () 600 | | Some length' -> 601 | if list_size <> length' then 602 | raise 603 | (Pycaml_exn 604 | (Pyerr_TypeError, 605 | (Printf.sprintf "%sExpected list of length %d, got length: %d" 606 | the_error_label length' list_size))) in 607 | for i = 0 to list_size - 1 do 608 | let item = Py.List.get list i in 609 | if not (type_checker item) then 610 | raise 611 | (Pycaml_exn 612 | (Pyerr_TypeError, 613 | Printf.sprintf 614 | "%sExpected homogeneous list of %s. Entry %d is of type %s (%s)!" 615 | the_error_label type_name (1 + i) 616 | (Py.Type.name (Py.Type.get item)) 617 | (Py.Object.string_of_repr item))) 618 | done; 619 | Py.List.to_array_map unwrapper list 620 | 621 | let py_float_list_as_array ?error_label ?length arr = 622 | py_homogeneous_list_as_array 623 | ?error_label ?length 624 | "float" Py.Float.check pyfloat_asdouble arr 625 | 626 | let py_int_list_as_array ?error_label ?length arr = 627 | py_homogeneous_list_as_array 628 | ?error_label ?length 629 | "int" Py.Long.check pyint_asint arr 630 | 631 | let py_number_list_as_float_array ?error_label ?length arr = 632 | py_homogeneous_list_as_array 633 | ?error_label ?length 634 | "number" Py.Number.check Py.Number.to_float arr 635 | 636 | let py_string_list_as_array ?error_label ?length arr = 637 | py_homogeneous_list_as_array 638 | ?error_label ?length 639 | "string" Py.String.check Py.String.to_string arr 640 | 641 | let py_list_list_as_array_map ?error_label ?length map arr = 642 | py_homogeneous_list_as_array 643 | ?error_label ?length 644 | "" Py.List.check map arr 645 | 646 | let py_list_list_as_array ?error_label ?length arr = 647 | py_list_list_as_array_map ?error_label ?length (fun x -> x) arr 648 | 649 | let py_list_list_as_array2 ?error_label ?length arr = 650 | py_list_list_as_array_map ?error_label ?length Py.List.to_array arr 651 | 652 | let py_float_list_list_as_array ?error_label ?length_outer ?length_inner arr = 653 | py_list_list_as_array_map ?error_label ?length:length_outer 654 | (py_float_list_as_array ?error_label ?length:length_inner) 655 | arr 656 | 657 | let py_number_list_list_as_float_array ?error_label ?length_outer 658 | ?length_inner arr = 659 | py_list_list_as_array_map ?error_label ?length:length_outer 660 | (py_number_list_as_float_array ?error_label ?length:length_inner) 661 | arr 662 | 663 | let py_int_list_list_as_array ?error_label ?length_outer ?length_inner arr = 664 | py_list_list_as_array_map ?error_label ?length:length_outer 665 | (py_int_list_as_array ?error_label ?length:length_inner) 666 | arr 667 | 668 | let py_string_list_list_as_array ?error_label ?length_outer ?length_inner arr = 669 | py_list_list_as_array_map ?error_label ?length:length_outer 670 | (py_string_list_as_array ?error_label ?length:length_inner) 671 | arr 672 | 673 | let py_float_tensor ?(init=(fun _ -> 0.0)) index_ranges = 674 | let nr_indices = Array.length index_ranges in 675 | let v_indices = Array.make nr_indices 0 in 676 | if nr_indices = 0 then 677 | (pyfloat_fromdouble (init v_indices), 678 | fun _ -> failwith "Cannot set rank-0 python float tensor!") 679 | else 680 | let rec build pos = 681 | let range = index_ranges.(pos) in 682 | Py.List.init range 683 | (fun ix_here -> 684 | let () = v_indices.(pos) <- ix_here in 685 | if pos = nr_indices-1 then 686 | pyfloat_fromdouble (init v_indices) 687 | else 688 | build (succ pos)) in 689 | let structure = build 0 in 690 | let setter indices value = 691 | let rec walk sub_structure pos = 692 | let i = indices.(pos) in 693 | if pos = nr_indices-1 then 694 | Py.List.set sub_structure i value 695 | else 696 | walk (Py.List.get sub_structure i) (succ pos) in 697 | walk structure 0 in 698 | (structure,setter) 699 | 700 | let int_array_to_python = Py.List.of_array_map Py.Long.of_int 701 | 702 | let float_array_to_python = Py.List.of_array_map Py.Float.of_float 703 | 704 | let register_for_python stuff = 705 | let ocaml_module = Py.Import.add_module "ocaml" in 706 | let register (python_name, value) = 707 | Py.Object.set_attr_string ocaml_module python_name value in 708 | Array.iter register stuff 709 | 710 | let register_pre_functions_for_python stuff = 711 | let prepare (python_name, pre_fun) = (python_name, pre_fun python_name) in 712 | register_for_python (Array.map prepare stuff) 713 | 714 | let python_last_value = Py.last_value 715 | 716 | let pywrap_closure_docstring docstring f = 717 | Py.Callable.of_function_as_tuple ~docstring f 718 | 719 | let pyrefcount = Py.Object.reference_count 720 | 721 | let pylist_get = Py.List.get 722 | 723 | let pylist_set = Py.List.set 724 | 725 | let pylist_fromarray = Py.List.of_array 726 | 727 | let py_repr obj = Py.Object.string_of_repr obj 728 | 729 | let pyunwrap_value = Py.Capsule.unsafe_unwrap_value 730 | 731 | let pywrap_value = Py.Capsule.unsafe_wrap_value 732 | 733 | type funcptr 734 | type funcent = funcptr * int * int * bool 735 | 736 | type pymodule_func = { 737 | pyml_name : string ; 738 | pyml_func : (pyobject -> pyobject) ; 739 | pyml_flags : int ; 740 | pyml_doc : string; 741 | } 742 | -------------------------------------------------------------------------------- /pyml_tests.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | Pyml_tests_common.add_test 3 | ~title:"version" 4 | (fun () -> 5 | Printf.printf "Python version %s\n%!" (Py.version ()); 6 | Pyml_tests_common.Passed 7 | ) 8 | 9 | let () = 10 | Pyml_tests_common.add_test 11 | ~title:"library version" 12 | (fun () -> 13 | Printf.printf "Python library version %s\n%!" (Py.get_version ()); 14 | Pyml_tests_common.Passed 15 | ) 16 | 17 | let () = 18 | Pyml_tests_common.add_test 19 | ~title:"hello world" 20 | (fun () -> 21 | assert (Py.Run.simple_string "print('Hello world!')"); 22 | Pyml_tests_common.Passed 23 | ) 24 | 25 | let () = 26 | Pyml_tests_common.add_test 27 | ~title:"class" 28 | (fun () -> 29 | let m = Py.Import.add_module "test" in 30 | let value_obtained = ref None in 31 | let callback arg = 32 | value_obtained := Some (Py.String.to_string (Py.Tuple.get_item arg 1)); 33 | Py.none in 34 | let c = 35 | Py.Class.init "myClass" 36 | ~methods:[("callback", Py.Callable.of_function_as_tuple callback)] in 37 | Py.Module.set m "myClass" c; 38 | assert (Py.Run.simple_string " 39 | from test import myClass 40 | myClass().callback('OK') 41 | "); 42 | assert (!value_obtained = Some "OK"); 43 | Pyml_tests_common.Passed 44 | ) 45 | 46 | let () = 47 | Pyml_tests_common.add_test 48 | ~title:"empty tuple" 49 | (fun () -> 50 | assert (Py.Tuple.create 0 = Py.Tuple.empty); 51 | Pyml_tests_common.Passed 52 | ) 53 | 54 | let () = 55 | Pyml_tests_common.add_test 56 | ~title:"make tuple" 57 | (fun () -> 58 | assert 59 | (Py.Tuple.to_singleton (Py.Tuple.singleton (Py.Long.of_int 0)) 60 | = Py.Long.of_int 0); 61 | Pyml_tests_common.Passed 62 | ) 63 | 64 | let () = 65 | Pyml_tests_common.add_test 66 | ~title:"module get/set/remove" 67 | (fun () -> 68 | let m = Py.Module.create "test" in 69 | Py.Module.set m "test" Py.none; 70 | assert (Py.Module.get m "test" = Py.none); 71 | Py.Module.remove m "test"; 72 | begin 73 | try 74 | ignore (Py.Module.get m "test"); 75 | Pyml_tests_common.Failed "Should have been removed" 76 | with Py.E _ -> Pyml_tests_common.Passed 77 | end) 78 | 79 | let () = 80 | Pyml_tests_common.add_test 81 | ~title:"capsule" 82 | (fun () -> 83 | let (wrap, unwrap) = Py.Capsule.make "string" in 84 | let m = Py.Import.add_module "test" in 85 | let pywrap args = 86 | let s = Py.String.to_string args.(0) in 87 | wrap s in 88 | let pyunwrap args = 89 | let s = unwrap args.(0) in 90 | Py.String.of_string s in 91 | Py.Module.set_function m "wrap" pywrap; 92 | Py.Module.set_function m "unwrap" pyunwrap; 93 | assert (Py.Run.simple_string " 94 | from test import wrap, unwrap 95 | x = wrap('OK') 96 | print('Capsule type: {0}'.format(x)) 97 | assert unwrap(x) == 'OK' 98 | "); 99 | Pyml_tests_common.Passed 100 | ) 101 | 102 | let () = 103 | Pyml_tests_common.add_test 104 | ~title:"capsule-conversion-error" 105 | (fun () -> 106 | let ref_str = "foobar" in 107 | let ref_pair1 = (3.141592, 42) in 108 | let ref_pair2 = (2.71828182846, 42) in 109 | let (wrap_str, unwrap_str) = Py.Capsule.make "string-1" in 110 | let (wrap_pair, unwrap_pair) = Py.Capsule.make "pair-1" in 111 | let s = wrap_str ref_str in 112 | let p1 = wrap_pair ref_pair1 in 113 | let p2 = wrap_pair ref_pair2 in 114 | assert (unwrap_str s = ref_str); 115 | assert (unwrap_pair p1 = ref_pair1); 116 | assert (unwrap_pair p2 = ref_pair2); 117 | let unwrap_failed = 118 | try 119 | ignore (unwrap_pair s : float * int); 120 | false 121 | with _ -> true 122 | in 123 | assert unwrap_failed; 124 | let unwrap_failed = 125 | try 126 | ignore (unwrap_pair (Py.Long.of_int 42) : float * int); 127 | false 128 | with _ -> true 129 | in 130 | assert unwrap_failed; 131 | Pyml_tests_common.Passed) 132 | 133 | let () = 134 | Pyml_tests_common.add_test 135 | ~title:"exception" 136 | (fun () -> 137 | try 138 | let _ = Py.Run.eval ~start:Py.File " 139 | raise Exception('Great') 140 | " in 141 | Pyml_tests_common.Failed "uncaught exception" 142 | with Py.E (_, value) -> 143 | assert (Py.Object.to_string value = "Great"); 144 | Pyml_tests_common.Passed) 145 | 146 | let () = 147 | Pyml_tests_common.add_test 148 | ~title:"ocaml exception" 149 | (fun () -> 150 | let m = Py.Import.add_module "test" in 151 | let mywrap _ = 152 | raise (Py.Err (Py.Err.Exception, "Great")) in 153 | Py.Module.set_function m "mywrap" mywrap; 154 | assert (Py.Run.simple_string " 155 | from test import mywrap 156 | try: 157 | mywrap() 158 | raise Exception('No exception raised') 159 | except Exception as err: 160 | assert str(err) == \"Great\" 161 | "); 162 | Pyml_tests_common.Passed 163 | ) 164 | 165 | let () = 166 | Pyml_tests_common.add_test 167 | ~title:"ocaml exception with traceback" 168 | (fun () -> 169 | let m = Py.Import.add_module "test" in 170 | let traceback = [ 171 | { Py.Traceback.filename = "file1.ml"; 172 | function_name = "func1"; 173 | line_number = 1}; 174 | { Py.Traceback.filename = "file2.ml"; 175 | function_name = "func2"; 176 | line_number = 2} 177 | ] in 178 | let mywrap _ = 179 | raise (Py.Err_with_traceback (Py.Err.Exception, "Great", traceback)) in 180 | Py.Module.set_function m "mywrap" mywrap; 181 | assert (Py.Run.simple_string " 182 | from test import mywrap 183 | import sys 184 | import traceback 185 | try: 186 | mywrap() 187 | raise Exception('No exception raised') 188 | except Exception as err: 189 | if sys.version_info.major == 3 and sys.version_info.minor >= 7: 190 | if sys.version_info.minor >= 11: 191 | filenames = [ 192 | f.filename for f in 193 | traceback.StackSummary.extract( 194 | traceback.walk_tb(err.__traceback__))] 195 | else: 196 | filenames = [ 197 | f.filename for f in traceback.extract_tb(err.__traceback__)] 198 | assert filenames == ['', 'file2.ml', 'file1.ml'] 199 | assert str(err) == \"Great\" 200 | "); 201 | Pyml_tests_common.Passed 202 | ) 203 | 204 | let () = 205 | Pyml_tests_common.add_test 206 | ~title:"restore with null" 207 | (fun () -> 208 | try 209 | let _ = Py.Run.eval ~start:Py.File " 210 | raise Exception('Great') 211 | " in 212 | Pyml_tests_common.Failed "uncaught exception" 213 | with Py.E (_, value) -> begin 214 | assert (Py.Object.to_string value = "Great"); 215 | match Py.Err.fetched () with 216 | | None -> Pyml_tests_common.Failed "unexpected none" 217 | | Some (err, _args, _traceback) -> 218 | (* Test that using [Py.Err.restore] on null works fine. *) 219 | Py.Err.restore err Py.null Py.null; 220 | Py.Err.clear (); 221 | Pyml_tests_common.Passed 222 | end) 223 | 224 | let () = 225 | Pyml_tests_common.add_test 226 | ~title:"ocaml other exception" 227 | (fun () -> 228 | let m = Py.Import.add_module "test" in 229 | let mywrap _ = raise Exit in 230 | Py.Module.set_function m "mywrap" mywrap; 231 | try 232 | ignore (Py.Run.eval ~start:File " 233 | from test import mywrap 234 | try: 235 | mywrap() 236 | except Exception as err: 237 | raise Exception('Should not be caught by Python') 238 | "); 239 | Pyml_tests_common.Failed "Uncaught exception" 240 | with Exit -> 241 | Pyml_tests_common.Passed 242 | ) 243 | 244 | let () = 245 | Pyml_tests_common.add_test 246 | ~title:"run file with filename" 247 | (fun () -> 248 | let result = Pyutils.with_temp_file "print(\"Hello, world!\")" 249 | begin fun file _channel -> 250 | Py.Run.load (Py.Filename file) "test.py" 251 | end in 252 | if result = Py.none then 253 | Pyml_tests_common.Passed 254 | else 255 | let result_str = Py.Object.to_string result in 256 | let msg = Printf.sprintf "Result None expected but got %s" result_str in 257 | Pyml_tests_common.Failed msg 258 | ) 259 | 260 | let () = 261 | Pyml_tests_common.add_test 262 | ~title:"run file with channel" 263 | (Pyml_tests_common.enable_only_on_unix 264 | (fun () -> 265 | let result = Pyutils.with_temp_file "print(\"Hello, world!\")" 266 | begin fun _file channel -> 267 | Py.Run.load (Py.Channel channel) "test.py" 268 | end in 269 | if result = Py.none then 270 | Pyml_tests_common.Passed 271 | else 272 | let result_str = Py.Object.to_string result in 273 | let msg = Printf.sprintf "Result None expected but got %s" result_str in 274 | Pyml_tests_common.Failed msg 275 | ) 276 | ) 277 | 278 | let () = 279 | Pyml_tests_common.add_test 280 | ~title:"boolean" 281 | (fun () -> 282 | try 283 | if not (Py.Bool.to_bool (Py.Run.eval "True")) then 284 | Pyml_tests_common.Failed "true is false" 285 | else if Py.Bool.to_bool (Py.Run.eval "False") then 286 | Pyml_tests_common.Failed "false is true" 287 | else 288 | Pyml_tests_common.Passed; 289 | with Py.E (_, value) -> 290 | Pyml_tests_common.Failed (Py.Object.to_string value)) 291 | 292 | let () = 293 | Pyml_tests_common.add_test 294 | ~title:"reinitialize" 295 | (fun () -> 296 | Gc.full_major (); 297 | Py.finalize (); 298 | begin 299 | try 300 | assert (Py.Run.simple_string "not initialized"); 301 | raise Exit 302 | with 303 | Failure _ -> () 304 | | Exit -> failwith "Uncaught not initialized" 305 | end; 306 | let (version, minor) = !Pyml_tests_common.use_version in 307 | Py.initialize ~verbose:true ?version ?minor (); 308 | Pyml_tests_common.Passed 309 | ) 310 | 311 | let () = 312 | Pyml_tests_common.add_test 313 | ~title:"string conversion error" 314 | (fun () -> 315 | try 316 | let _ = Py.String.to_string (Py.Long.of_int 0) in 317 | Pyml_tests_common.Failed "uncaught exception" 318 | with 319 | Py.E (_, value) -> 320 | Printf.printf "Caught exception: %s\n%!" (Py.Object.to_string value); 321 | Pyml_tests_common.Passed 322 | | Failure s -> 323 | Printf.printf "Caught failure: %s\n%!" s; 324 | Pyml_tests_common.Passed) 325 | 326 | let () = 327 | Pyml_tests_common.add_test 328 | ~title:"float conversion error" 329 | (fun () -> 330 | try 331 | let _ = Py.Float.to_float (Py.String.of_string "a") in 332 | Pyml_tests_common.Failed "uncaught exception" 333 | with Py.E (_, value) -> 334 | Printf.printf "Caught exception: %s\n%!" (Py.Object.to_string value); 335 | Pyml_tests_common.Passed) 336 | 337 | let () = 338 | Pyml_tests_common.add_test 339 | ~title:"long conversion error" 340 | (fun () -> 341 | try 342 | let _ = Py.Long.to_int (Py.String.of_string "a") in 343 | Pyml_tests_common.Failed "uncaught exception" 344 | with Py.E (_, value) -> 345 | Printf.printf "Caught exception: %s\n%!" (Py.Object.to_string value); 346 | Pyml_tests_common.Passed) 347 | 348 | let () = 349 | Pyml_tests_common.add_test 350 | ~title:"iterators" 351 | (fun () -> 352 | let iter = Py.Object.get_iter (Py.Run.eval "['a','b','c']") in 353 | let list = Py.Iter.to_list_map Py.String.to_string iter in 354 | assert (list = ["a"; "b"; "c"]); 355 | Pyml_tests_common.Passed) 356 | 357 | let () = 358 | Pyml_tests_common.add_test 359 | ~title:"Iterator.create" 360 | (fun () -> 361 | let m = Py.Import.add_module "test" in 362 | let iter = Py.Iter.of_list_map Py.Int.of_int [3; 1; 4; 1; 5] in 363 | Py.Module.set m "ocaml_iterator" iter; 364 | assert (Py.Run.simple_string " 365 | from test import ocaml_iterator 366 | res = 0 367 | for v in ocaml_iterator: res += v 368 | "); 369 | let main = Py.Module.get_dict (Py.Import.add_module "__main__") in 370 | let res = Py.Dict.find_string main "res" in 371 | assert (Py.Int.to_int res = 14); 372 | let iter = Py.Iter.of_list_map Py.String.of_string ["a"; "b"; "c"] in 373 | let list = Py.Iter.to_list_map Py.String.to_string iter in 374 | assert (list = ["a"; "b"; "c"]); 375 | Pyml_tests_common.Passed) 376 | 377 | let () = 378 | Pyml_tests_common.add_test 379 | ~title:"Iterator.create_call" 380 | (fun () -> 381 | let iter_of_list python_of list = 382 | let list = ref list in 383 | let next () = 384 | match !list with 385 | | [] -> None 386 | | p :: q -> 387 | list := q; 388 | Some (python_of p) 389 | in 390 | Py.Iter.create_call next 391 | in 392 | let m = Py.Import.add_module "test" in 393 | let iter = iter_of_list Py.Int.of_int [3; 1; 4; 1; 5] in 394 | Py.Module.set m "ocaml_iterator2" iter; 395 | assert (Py.Run.simple_string " 396 | from test import ocaml_iterator2 397 | res = 0 398 | for v in ocaml_iterator2: res += v 399 | "); 400 | let main = Py.Module.get_dict (Py.Import.add_module "__main__") in 401 | let res = Py.Dict.find_string main "res" in 402 | assert (Py.Int.to_int res = 14); 403 | let iter = iter_of_list Py.String.of_string ["a"; "b"; "c"] in 404 | let list = Py.Iter.to_list_map Py.String.to_string iter in 405 | assert (list = ["a"; "b"; "c"]); 406 | Pyml_tests_common.Passed) 407 | 408 | let () = 409 | Pyml_tests_common.add_test 410 | ~title:"Dict.iter" 411 | (fun () -> 412 | let dict = Py.Dict.create () in 413 | for i = 0 to 9 do 414 | Py.Dict.set_item_string dict (string_of_int i) (Py.Long.of_int i) 415 | done; 416 | let table = Array.make 10 None in 417 | Py.Dict.iter begin fun key value -> 418 | let index = Py.Long.to_int value in 419 | assert (table.(index) = None); 420 | table.(index) <- Some (Py.String.to_string key) 421 | end dict; 422 | Array.iteri begin fun i v -> 423 | match v with 424 | None -> failwith "None!" 425 | | Some v' -> assert (i = int_of_string v') 426 | end table; 427 | Pyml_tests_common.Passed) 428 | 429 | let () = 430 | Pyml_tests_common.add_test 431 | ~title:"unicode" 432 | (fun () -> 433 | let codepoints = [| 8203; 127; 83; 2384; 0; 12 |] in 434 | let python_string = Py.String.of_unicode codepoints in 435 | let ocaml_string = Py.String.to_string python_string in 436 | let python_string' = Py.String.decode_UTF8 ocaml_string in 437 | let codepoints' = Py.String.to_unicode python_string' in 438 | assert (codepoints = codepoints'); 439 | Pyml_tests_common.Passed 440 | ) 441 | 442 | let () = 443 | Pyml_tests_common.add_test 444 | ~title:"interactive loop" 445 | (Pyml_tests_common.enable_only_on_unix (fun () -> 446 | Pyutils.with_stdin_from_string "42" 447 | Py.Run.interactive (); 448 | assert (Py.Long.to_int (Py.last_value ()) = 42); 449 | Pyml_tests_common.Passed)) 450 | 451 | let () = 452 | Pyml_tests_common.add_test 453 | ~title:"IPython" 454 | (Pyml_tests_common.enable_only_on_unix 455 | (Py.Run.frame (Pyutils.with_stdin_from_string "exit" (fun () -> 456 | if Py.Import.try_import_module "IPython" = None then 457 | Pyml_tests_common.Disabled "IPython is not available" 458 | else 459 | begin 460 | Py.Run.ipython ~frame:false (); 461 | Pyml_tests_common.Passed 462 | end)))) 463 | 464 | let () = 465 | Pyml_tests_common.add_test 466 | ~title:"Marshal" 467 | (fun () -> 468 | let v = Py.Long.of_int 42 in 469 | let m = Py.Marshal.dumps v in 470 | let v' = Py.Marshal.loads m in 471 | assert (Py.Long.to_int v' = 42); 472 | Pyml_tests_common.Passed) 473 | 474 | let () = 475 | Pyml_tests_common.add_test 476 | ~title:"Py.List.of_list" 477 | (fun () -> 478 | let v = Py.List.of_list [Py.Long.of_int 42] in 479 | assert (Py.List.length v = 1); 480 | assert (Py.Long.to_int (Py.List.get v 0) = 42); 481 | Pyml_tests_common.Passed) 482 | 483 | let () = 484 | Pyml_tests_common.add_test 485 | ~title:"Py.List.sort" 486 | (fun () -> 487 | let pi_digits = [ 3; 1; 4; 1; 5; 9; 2; 6; 5; 3; 5; 8 ] in 488 | let v = Py.List.of_list [] in 489 | assert (Py.List.length v = 0); 490 | let count = Py.Object.call_method v "count" [|Py.Long.of_int 1|] in 491 | assert (Py.Long.to_int count = 0); 492 | List.iter (fun i -> 493 | ignore (Py.Object.call_method v "append" [|Py.Long.of_int i|])) 494 | pi_digits; 495 | let count = Py.Object.call_method v "count" [|Py.Long.of_int 1|] in 496 | assert (Py.Long.to_int count = 2); 497 | assert (Py.List.length v = List.length pi_digits); 498 | let _ = Py.Object.call_method v "sort" [||] in 499 | let sorted_digits = List.map Py.Int.to_int (Py.List.to_list v) in 500 | assert (sorted_digits = List.sort compare pi_digits); 501 | (* No `clear' method in lists in Python 2 *) 502 | if Py.version_major () >= 3 then 503 | begin 504 | let _ = Py.Object.call_method v "clear" [||] in 505 | assert (Py.List.length v = 0) 506 | end; 507 | Pyml_tests_common.Passed) 508 | 509 | let () = 510 | Pyml_tests_common.add_test ~title:"array" 511 | (fun () -> 512 | let array = [| 1; 2 |] in 513 | let a = Py.Array.of_array Py.Long.of_int Py.Long.to_int array in 514 | let m = Py.Import.add_module "test" in 515 | Py.Module.set m "array" a; 516 | assert (Py.Run.simple_string " 517 | from test import array 518 | assert len(array) == 2 519 | assert array[0] == 1 520 | assert array[1] == 2 521 | array[0] = 42 522 | array[1] = 43 523 | copy = [] 524 | for x in array: 525 | copy.append(x) 526 | assert copy == [42, 43] 527 | "); 528 | assert (array.(0) = 42); 529 | assert (array.(1) = 43); 530 | Pyml_tests_common.Passed) 531 | 532 | let () = 533 | Pyml_tests_common.add_test ~title:"numpy" 534 | (fun () -> 535 | if Py.Import.try_import_module "numpy" = None then 536 | Pyml_tests_common.Disabled "numpy is not available" 537 | else 538 | begin 539 | let array = Stdcompat.Array.Floatarray.create 2 in 540 | Stdcompat.Array.Floatarray.set array 0 1.; 541 | Stdcompat.Array.Floatarray.set array 1 2.; 542 | let a = Py.Array.numpy array in 543 | let m = Py.Import.add_module "test" in 544 | Py.Module.set m "array" a; 545 | assert (Py.Run.simple_string " 546 | from test import array 547 | assert len(array) == 2 548 | assert array[0] == 1. 549 | assert array[1] == 2. 550 | array[0] = 42. 551 | array[1] = 43. 552 | "); 553 | assert (Stdcompat.Array.Floatarray.get array 0 = 42.); 554 | assert (Stdcompat.Array.Floatarray.get array 1 = 43.); 555 | Pyml_tests_common.Passed 556 | end) 557 | 558 | let () = 559 | Pyml_tests_common.add_test ~title:"numpy crunch" 560 | (fun () -> 561 | if Py.Import.try_import_module "numpy" = None then 562 | Pyml_tests_common.Disabled "numpy is not available" 563 | else 564 | begin 565 | let array = Stdcompat.Float.Array.init 0x10000 float_of_int in 566 | let numpy_array = Py.Array.numpy array in 567 | let add = 568 | Py.Module.get_function (Py.Import.import_module "numpy") "add" in 569 | let rec crunch numpy_array n = 570 | if n <= 0 then 571 | numpy_array 572 | else 573 | let array = 574 | Stdcompat.Float.Array.map_from_array Stdcompat.Fun.id 575 | (Py.Sequence.to_array_map Py.Float.to_float 576 | (add [| numpy_array; numpy_array |])) in 577 | crunch (Py.Array.numpy array) (pred n) in 578 | ignore (crunch (Py.Array.numpy array) 0x100); 579 | assert (Stdcompat.Float.Array.length array = 0x10000); 580 | for i = 0 to 0x10000 - 1 do 581 | assert (Stdcompat.Float.Array.get array i = float_of_int i) 582 | done; 583 | Stdcompat.Float.Array.set array 1 42.; 584 | assert (Py.Float.to_float (Py.Sequence.get numpy_array 1) = 42.); 585 | Pyml_tests_common.Passed 586 | end) 587 | 588 | let () = 589 | Pyml_tests_common.add_test 590 | ~title:"none" 591 | (fun () -> 592 | let none = Py.none in 593 | assert (none = Py.none); 594 | assert (Py.is_none none); 595 | assert (Py.Type.get none = None); 596 | let none = Py.Run.eval "None" in 597 | assert (none = Py.none); 598 | assert (Py.is_none none); 599 | assert (Py.Type.get none = None); 600 | let not_none = Py.Long.of_int 42 in 601 | assert (not_none <> Py.none); 602 | assert (not (Py.is_none not_none)); 603 | assert (Py.Type.get not_none <> None); 604 | Pyml_tests_common.Passed 605 | ) 606 | 607 | let () = 608 | Pyml_tests_common.add_test 609 | ~title:"docstring" 610 | (fun () -> 611 | Gc.full_major (); 612 | let fn = 613 | let docstring = Printf.sprintf "test%d" 42 in 614 | Py.Callable.of_function ~docstring (fun _ -> Py.none) 615 | in 616 | Gc.full_major (); 617 | let other_string = Printf.sprintf "test%d" 43 in 618 | let doc = Py.Object.get_attr_string fn "__doc__" in 619 | begin 620 | match doc with 621 | None -> failwith "None!" 622 | | Some doc -> assert (Py.String.to_string doc = "test42") 623 | end; 624 | ignore other_string; 625 | Pyml_tests_common.Passed 626 | ) 627 | 628 | let () = 629 | Pyml_tests_common.add_test 630 | ~title:"function-name" 631 | (fun () -> 632 | let run make_name expect_name = 633 | Gc.full_major (); 634 | let fn = 635 | let name = make_name () in 636 | Py.Callable.of_function ?name (fun _ -> Py.none) 637 | in 638 | Gc.full_major (); 639 | let other_string = Printf.sprintf "test%d" 43 in 640 | let name = Py.Object.get_attr_string fn "__name__" in 641 | begin 642 | match name with 643 | None -> failwith "None!" 644 | | Some doc -> assert (Py.String.to_string doc = expect_name) 645 | end; 646 | ignore other_string 647 | in 648 | run (fun () -> Some (Printf.sprintf "test%d" 42)) "test42"; 649 | run (fun () -> None) "anonymous_closure"; 650 | Pyml_tests_common.Passed 651 | ) 652 | 653 | let () = 654 | Pyml_tests_common.add_test 655 | ~title:"is-instance" 656 | (fun () -> 657 | let forty_two = Py.Int.of_int 42 in 658 | let forty_two_str = Py.String.of_string "42" in 659 | let int = Py.Dict.find_string (Py.Eval.get_builtins ()) "int" in 660 | assert (Py.Object.is_instance forty_two int); 661 | assert (not (Py.Object.is_instance forty_two_str int)); 662 | Pyml_tests_common.Passed 663 | ) 664 | 665 | let () = 666 | Pyml_tests_common.add_test 667 | ~title:"is-subclass" 668 | (fun () -> 669 | let int = Py.Dict.find_string (Py.Eval.get_builtins ()) "int" in 670 | let cls1 = Py.Class.init ~parents:[int] "cls1" in 671 | let cls2 = Py.Class.init ~parents:[cls1] "cls2" in 672 | assert (Py.Object.is_subclass cls1 int); 673 | assert (not (Py.Object.is_subclass int cls1)); 674 | assert (Py.Object.is_subclass cls2 cls1); 675 | assert (not (Py.Object.is_subclass cls1 cls2)); 676 | assert (Py.Object.is_subclass cls2 int); 677 | assert (not (Py.Object.is_subclass int cls2)); 678 | Pyml_tests_common.Passed 679 | ) 680 | 681 | let () = 682 | Pyml_tests_common.add_test 683 | ~title:"Set" 684 | (fun () -> 685 | let set = Py.Set.create () in 686 | for i = 0 to 9 do 687 | Py.Set.add set (Py.Long.of_int i) 688 | done; 689 | assert (Py.Set.check set); 690 | assert (Py.Set.size set = 10); 691 | Py.Set.discard set (Py.Long.of_int 5); 692 | assert (Py.Set.size set = 9); 693 | let values = Py.Set.to_list_map Py.Long.to_int set in 694 | assert (values = [0; 1; 2; 3; 4; 6; 7; 8; 9]); 695 | let set' = Py.Set.copy set in 696 | Py.Set.add set' (Py.Long.of_int 42); 697 | Py.Set.add set' (Py.Long.of_int 42); 698 | assert (Py.Set.size set = 9); 699 | assert (Py.Set.size set' = 10); 700 | Pyml_tests_common.Passed) 701 | 702 | let () = 703 | Pyml_tests_common.add_test 704 | ~title:"serialize" 705 | (fun () -> 706 | let value = Py.String.of_string "hello" in 707 | let pickled = Marshal.to_string value [] in 708 | let unpickled = Marshal.from_string pickled 0 in 709 | assert (Py.String.to_string unpickled = "hello"); 710 | Pyml_tests_common.Passed) 711 | 712 | let () = 713 | Pyml_tests_common.add_test 714 | ~title:"get_attr_string" 715 | (fun () -> 716 | let bool_ty = Py.Object.get_type Py.Bool.t in 717 | assert (Py.Object.get_attr_string bool_ty "dtype" = None); 718 | Pyml_tests_common.Passed) 719 | 720 | let () = 721 | Pyml_tests_common.add_test 722 | ~title:"Dict.find fails with Not_found" 723 | (fun () -> 724 | let dict = Py.Dict.create() in 725 | try 726 | let _ = Py.Dict.find dict Py.Tuple.empty in 727 | Pyml_tests_common.Failed "Unexpected found" 728 | with Not_found -> 729 | Pyml_tests_common.Passed) 730 | 731 | let () = 732 | Pyml_tests_common.add_test 733 | ~title:"Dict.find_string fails with Not_found" 734 | (fun () -> 735 | let dict = Py.Dict.create() in 736 | try 737 | let _ = Py.Dict.find_string dict "key" in 738 | Pyml_tests_common.Failed "Unexpected found" 739 | with Not_found -> 740 | Pyml_tests_common.Passed) 741 | 742 | let () = 743 | Pyml_tests_common.add_test 744 | ~title:"Object.find_attr fails with Not_found if key is missing" 745 | (fun () -> 746 | try 747 | let _ = 748 | Py.Object.find_attr Py.Tuple.empty (Py.String.of_string "key") in 749 | Pyml_tests_common.Failed "Unexpected found" 750 | with Not_found -> 751 | Pyml_tests_common.Passed) 752 | 753 | let () = 754 | Pyml_tests_common.add_test 755 | ~title:"Object.find_attr fails with Python exception if wrong key type" 756 | (fun () -> 757 | try 758 | let _ = Py.Object.find_attr Py.Tuple.empty Py.Tuple.empty in 759 | Pyml_tests_common.Failed "Unexpected found" 760 | with Py.E _ -> 761 | Pyml_tests_common.Passed) 762 | 763 | let () = 764 | Pyml_tests_common.add_test 765 | ~title:"Object.find_attr_string fails with Not_found if key is missing" 766 | (fun () -> 767 | try 768 | let _ = Py.Object.find_attr_string Py.Tuple.empty "key" in 769 | Pyml_tests_common.Failed "Unexpected found" 770 | with Not_found -> 771 | Pyml_tests_common.Passed) 772 | 773 | let () = 774 | Pyml_tests_common.add_test 775 | ~title:"Object.find fails with Not_found if key is missing" 776 | (fun () -> 777 | try 778 | let _ = Py.Object.find (Py.Dict.create ()) (Py.String.of_string "key") in 779 | Pyml_tests_common.Failed "Unexpected found" 780 | with Not_found -> 781 | Pyml_tests_common.Passed) 782 | 783 | let () = 784 | Pyml_tests_common.add_test 785 | ~title:"Object.find fails with Not_found if wrong key type" 786 | (fun () -> 787 | try 788 | let _ = Py.Object.find Py.Tuple.empty Py.Tuple.empty in 789 | Pyml_tests_common.Failed "Unexpected found" 790 | with Py.E _ -> 791 | Pyml_tests_common.Passed) 792 | 793 | let () = 794 | if not !Sys.interactive then 795 | Pyml_tests_common.main () 796 | -------------------------------------------------------------------------------- /pycaml.mli: -------------------------------------------------------------------------------- 1 | (** Embedding Python into OCaml. 2 | 3 | (C) arty 2002 4 | 5 | This library is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation; either version 2.1 of the 8 | License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, but 11 | WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 18 | USA 19 | 20 | A Derivative of Art Yerkes' 2002 Pycaml module. 21 | 22 | 23 | Modifications (C) 2005 Dr. Thomas Fischbacher, Giuliano Bordignon, 24 | Dr. Hans Fangohr, SES, University of Southampton 25 | 26 | More modifications are by Barry Schwartz. 27 | Copyright (C) 2009 Barry Schwartz. 28 | 29 | Adapted for py.ml by Thierry Martinez. 30 | Copyright (C) 2016 Thierry Martinez. 31 | *) 32 | 33 | (** {2 Background Information} *) 34 | 35 | (** The original code is available in Debian as package "pycaml". 36 | 37 | For various reasons, we hijacked it so that we can easily both fix bugs 38 | and extend it. This is permitted by the Pycaml license (the GNU LGPL). 39 | 40 | Note: the layout and hierarchical structure of the documentation 41 | could need some more work. 42 | *) 43 | 44 | (** {3 OCaml Types, Python Types, and general issues of typing} *) 45 | 46 | (** Python objects are wrapped up within OCaml as entities of type [pyobject]. 47 | *) 48 | type pyobject = Py.Object.t 49 | 50 | (** The following types are slightly esoteric; normally, users of this 51 | module should not have any need to access them. 52 | *) 53 | 54 | type funcptr 55 | type funcent = (funcptr * int * int * bool) 56 | 57 | type pymodule_func = { 58 | pyml_name : string ; 59 | pyml_func : (pyobject -> pyobject) ; 60 | pyml_flags : int ; 61 | pyml_doc : string; 62 | } 63 | 64 | val py_profile_reset: unit -> unit 65 | val py_profile_report: unit -> (string * float * float) array 66 | (* name * total_time * nr_calls - Note that we use float to count nr_calls 67 | to work around fixnum limits! *) 68 | 69 | val py_activate_profiling: unit -> bool 70 | val py_deactivate_profiling: unit -> bool 71 | 72 | (** As Python is a dynamically typed language, [pyobject] values 73 | may represent entities of very different nature. The [pytype] 74 | function maps a pyobject to its type, or rather, a selection 75 | of types that have been made known to OCaml. The default for 76 | "unknown" values is OtherType. 77 | 78 | Note (for advanced users only): This in particular holds 79 | for [PyCObject] values, which are used at present to wrap 80 | up OCaml values opaquely within Python values: at present, 81 | these appear to be of type [OtherType], but there might be 82 | good reason to change this in the future. 83 | *) 84 | 85 | type pyobject_type = 86 | | TupleType 87 | | BytesType 88 | | UnicodeType 89 | | BoolType 90 | | IntType 91 | | FloatType 92 | | ListType 93 | | NoneType 94 | | CallableType 95 | | ModuleType 96 | | ClassType 97 | | TypeType 98 | | DictType 99 | | NullType 100 | | CamlpillType 101 | | OtherType 102 | | EitherStringType (* Signifies that either of BytesType or UnicodeType is allowed. *) 103 | | CamlpillSubtype of string (* Signifies that only the particular Camlpill variety is allowed. *) 104 | | AnyType (* Allow any python object. *) 105 | 106 | type pyerror_type = 107 | | Pyerr_Exception 108 | | Pyerr_StandardError 109 | | Pyerr_ArithmeticError 110 | | Pyerr_LookupError 111 | | Pyerr_AssertionError 112 | | Pyerr_AttributeError 113 | | Pyerr_EOFError 114 | | Pyerr_EnvironmentError 115 | | Pyerr_FloatingPointError 116 | | Pyerr_IOError 117 | | Pyerr_ImportError 118 | | Pyerr_IndexError 119 | | Pyerr_KeyError 120 | | Pyerr_KeyboardInterrupt 121 | | Pyerr_MemoryError 122 | | Pyerr_NameError 123 | | Pyerr_NotImplementedError 124 | | Pyerr_OSError 125 | | Pyerr_OverflowError 126 | | Pyerr_ReferenceError 127 | | Pyerr_RuntimeError 128 | | Pyerr_SyntaxError 129 | | Pyerr_SystemExit 130 | | Pyerr_TypeError 131 | | Pyerr_ValueError 132 | | Pyerr_ZeroDivisionError 133 | 134 | exception Pycaml_exn of (pyerror_type * string) 135 | 136 | val pytype : pyobject -> pyobject_type 137 | 138 | (** Also note the existence of [pytype_name], which maps python types to 139 | human-readable strings. *) 140 | 141 | (* Pycaml contains quite some stuff which we should not 142 | tell the outside world about. For now, this is in comments, 143 | but eventually, we should perhaps even remove it from there. 144 | *) 145 | 146 | (** {3 Initialization} *) 147 | 148 | (** The Python interpreter has to be initialized, 149 | which is done via [py_initialize]. Note that this module 150 | does call this function automatically when it is initialized itself, 151 | so the end user does not have to worry about this. 152 | 153 | Note that Python initialization seems to be idempotent, 154 | so there should not be any problems if one starts up 155 | a python interpreter first, and then loads a shared object 156 | via Python's foreign function interface which itself initializes 157 | OCaml and pycaml. (Note: However, this still needs more testing!) 158 | *) 159 | 160 | val py_initialize : unit -> unit 161 | val py_finalize : unit -> unit 162 | 163 | (** {3 Functions from the original Pycaml} *) 164 | 165 | (** There is a collection of functions from the original Pycaml module 166 | which are not-too-well-documented. For some of them, there are 167 | examples available, and often, one can guess what they are 168 | supposed to do from their name and type. (Admittedly, this 169 | is a quite unsatisfactory state of affairs, but on the other 170 | hand, as it turns out, we will have to use only very few 171 | of them. So for now, if there is a question, look at 172 | the source, or ask [t.fischbacher\@soton.ac.uk].) 173 | 174 | In order not to clutter the Pycaml documentation with a block of 175 | unreadable code, they have been moved to the last section. 176 | *) 177 | 178 | (** {3 On wrapping up Ocaml values for Python} *) 179 | 180 | (** The Pycaml functions [pywrap_value] and [pyunwrap_value] are 181 | elementary low-level primitives to make opaque Python values that hold 182 | OCaml values. As Python is a dynamically typed language, and OCaml is 183 | a statically typed language, and both achieve safety in a somewhat 184 | misaligned way, this interface may be considered as dangerous. In 185 | fact, it allows one to break OCaml type safety by mapping a statically 186 | typed value to a dynamically typed Python value and back. 187 | 188 | This means that a Python user handing a wrapped-up ocaml value of 189 | a different type than expected over to an OCaml callback may crash 190 | the system. 191 | 192 | This module provides an extension to the original Pycaml which 193 | will have added checks that prevent precisely such a situation and 194 | therefore is safer. Note however, that at the moment, it is only 195 | foolproof if one does not mix this up with other [PyCObject] 196 | Python values. (Presumably, it can be tightened up by introducing 197 | a new primitive Python type [PyCamlObject]. TODO.) 198 | *) 199 | 200 | val pywrap_value : 'a -> pyobject 201 | val pyunwrap_value : pyobject -> 'a 202 | 203 | (** {2 Genuine Extensions to the original Pycaml} *) 204 | 205 | (** {3 Converting values and handling errors} *) 206 | 207 | val py_repr : pyobject -> string 208 | 209 | val pylist_fromarray : pyobject array -> pyobject 210 | val pylist_toarray : pyobject -> pyobject array 211 | (** Map an OCaml array of Python values to a Python list and vice versa. 212 | (This was just missing.) 213 | *) 214 | 215 | val pylist_set : pyobject -> int -> pyobject -> unit 216 | val pylist_get : pyobject -> int -> pyobject 217 | 218 | val pyrefcount: pyobject -> int 219 | 220 | val pywrap_closure_docstring : string -> (pyobject -> pyobject) -> pyobject 221 | (** While the functions in ocaml.* should not be made visible 222 | to end users directly, it may nevertheless be helpful to be 223 | able to set docstrings on them. 224 | *) 225 | 226 | (** Return a name-string for an Ocaml Python-Object-Type value. 227 | Used mainly for debugging and in error messages. *) 228 | val pytype_name: pyobject_type -> string 229 | 230 | (** Return the last value that was computed interactively at the Python prompt *) 231 | val python_last_value: unit -> pyobject 232 | 233 | val py_true : unit -> pyobject 234 | val py_false : unit -> pyobject 235 | val py_is_true : pyobject -> bool 236 | 237 | (** 238 | A convenient function to make a collection of [pyobject] values 239 | (which usually will be OCaml callbacks) known to Python in one go. 240 | The strings give the names under which the corresponding values 241 | should appear in Python's "[ocaml]" module, which Pycaml will add 242 | to Python and automatically [import] on the Python side. 243 | 244 | Note that as a convention, one must not register names that start with 245 | the string "[example_]" or "[sys_]", as those are reserved for internal 246 | use by Pycaml. 247 | *) 248 | val register_for_python : (string * pyobject) array -> unit 249 | val register_pre_functions_for_python : (string * (string ->pyobject)) array -> unit 250 | 251 | val float_array_to_python : float array -> pyobject 252 | val int_array_to_python : int array -> pyobject 253 | (** These functions provides a quick and convenient way to pass a 254 | simple array of numbers to Python. Note that {i neither} on the 255 | OCaml nor on the Python side, the special data structure for efficient 256 | manipulation of large numerical arrays is used 257 | (OCaml: bigarray, Python: numarray). Rather, this just maps 258 | ordinary arrays. 259 | *) 260 | 261 | val py_float_tensor : ?init:(int array -> float) -> int array -> pyobject * (int array -> pyobject -> unit) 262 | 263 | (** This little helper creates a nested float array python structure 264 | that is supposed to represent a multi-indexed tensor, plus a function 265 | to set tensor entries. 266 | *) 267 | 268 | (* XXX These have to be documented! *) 269 | val py_homogeneous_list_as_array : 270 | ?error_label:string -> 271 | ?length:int -> 272 | string -> (pyobject -> bool) -> (pyobject -> 'a) -> pyobject -> 'a array 273 | 274 | val py_float_list_as_array : 275 | ?error_label:string -> ?length:int -> pyobject -> float array 276 | 277 | val py_number_list_as_float_array : 278 | ?error_label:string -> ?length:int -> pyobject -> float array 279 | 280 | val py_int_list_as_array : 281 | ?error_label:string -> ?length:int -> pyobject -> int array 282 | 283 | val py_string_list_as_array : 284 | ?error_label:string -> ?length:int -> pyobject -> string array 285 | 286 | val py_list_list_as_array : 287 | ?error_label:string -> ?length:int -> pyobject -> pyobject array 288 | 289 | val py_list_list_as_array2 : 290 | ?error_label:string -> ?length:int -> pyobject -> pyobject array array 291 | 292 | val py_float_list_list_as_array : 293 | ?error_label:string -> 294 | ?length_outer:int -> ?length_inner:int -> pyobject -> float array array 295 | 296 | val py_number_list_list_as_float_array : 297 | ?error_label:string -> 298 | ?length_outer:int -> ?length_inner:int -> pyobject -> float array array 299 | 300 | 301 | val py_int_list_list_as_array : 302 | ?error_label:string -> 303 | ?length_outer:int -> ?length_inner:int -> pyobject -> int array array 304 | 305 | val py_string_list_list_as_array : 306 | ?error_label:string -> 307 | ?length_outer:int -> ?length_inner:int -> pyobject -> string array array 308 | 309 | val unpythonizing_function : 310 | ?name:string -> 311 | ?catch_weird_exceptions:bool -> 312 | ?extra_guards:(pyobject -> string option) array -> 313 | ?expect_tuple:bool -> 314 | pyobject_type array -> (pyobject array -> 'a) -> pyobject -> 'a 315 | 316 | val pythonize_string : string -> pyobject 317 | val unpythonize_string : pyobject -> string 318 | 319 | (** This helper simplifies the creation of OCaml callbacks that 320 | can be registered in Python's "[ocaml]" module. 321 | 322 | First argument: An array of [pyobject_type] Python types. 323 | 324 | Second argument: A "body" function B mapping an OCaml array 325 | of Python values to a Python return value. 326 | 327 | Optional argument: An array of extra checks to be performed 328 | on the arguments, one by one, returning an optional error 329 | message. 330 | 331 | The body function (as well as the optional checks) will be wrapped 332 | up in code that first checks for the correct number and the specified 333 | Python types of arguments, so B can rely on the n'th entry of its Python 334 | argument array being of the Python type specified in the n'th 335 | position of the type array. 336 | 337 | XXX Note: we need examples in the documentation! 338 | *) 339 | val python_interfaced_function : 340 | ?name:string -> 341 | ?catch_weird_exceptions:bool -> 342 | ?doc:string -> 343 | ?extra_guards:(pyobject -> string option) array -> 344 | pyobject_type array -> 345 | (pyobject array -> pyobject) -> pyobject 346 | 347 | val python_pre_interfaced_function : 348 | ?catch_weird_exceptions:bool -> 349 | ?doc:string -> 350 | ?extra_guards:(pyobject -> string option) array -> 351 | pyobject_type array -> 352 | (pyobject array -> pyobject) -> 353 | (string -> pyobject) 354 | 355 | 356 | (** Sometimes, we want to manipulate complicated structures via Python 357 | which are implemented in OCaml, and about whose interna only OCaml 358 | should know and have to worry. So, all that one can do from Python is 359 | to place such values in containers (tuples, lists) and retrieve them back, 360 | pass them around, and hand them over to OCaml callbacks. 361 | 362 | In order to ensure type safety, we have to extend OCaml by a 363 | primitive dynamic type system for Python-wrapped OCaml 364 | values. This is based on the following assumptions: 365 | 366 | {ol 367 | {li The number of different OCaml types we might want to make visible 368 | to Python is quite limited. (In particular, we do not even try 369 | to properly support polymorphism.)} 370 | {li Python should be allowed to take a peek at the type name of a 371 | wrapped OCaml value at runtime} 372 | } 373 | 374 | Thus, before one can opaquely wrap up OCaml values in "ocamlpills" 375 | for Python, one has to register a type name with Pycaml. From the 376 | Python side, the function [ocaml.sys_ocamlpill_type(x)] will map 377 | the ocamlpill [x] to the registered type string. 378 | *) 379 | val register_ocamlpill_types : string array -> unit 380 | (** py.ml: Pill types are not required to be registered. 381 | This function does nothing and is provided for compatibility only. *) 382 | 383 | val ocamlpill_type_of : pyobject -> string 384 | 385 | (** Given an ocamlpill type name (which was registered before using 386 | [register_ocamlpill_type]), as well as a witness of the type in 387 | question in form of a prototypical OCaml value, make a function 388 | that maps other OCaml values of the same type as the prototype to 389 | Python ocamlpills. This function is exported to python as 390 | [ocaml.sys_ocamlpill_type]. 391 | 392 | Note: a simple type system hack is used to ensure that the wrapper 393 | function generated can only be applied to OCaml values of the proper 394 | type. One major drawback of this is that presumably, the prototypical 395 | object provided cannot be garbage collected until the wrapper function 396 | is. (A clever compiler might be able to figure out how to get rid of 397 | that, though.) 398 | 399 | XXX Provide example code! 400 | *) 401 | val pill_type_mismatch_exception : ?position:'a -> ?exn_name:string -> string -> string -> exn 402 | val check_pill_type : ?position:'a -> ?exn_name:string -> string -> pyobject -> unit 403 | val make_ocamlpill_wrapper_unwrapper : string -> 'a -> ('a -> pyobject) * (pyobject -> 'a) (* Deprecated, I guess. 404 | Use |make_pill_wrapping| 405 | instead. *) 406 | (** py.ml: the signature has been changed from 407 | [string -> 'a -> ('a -> pyobject) * (pyobject -> 'b)] 408 | to 409 | [string -> 'a -> ('a -> pyobject) * (pyobject -> 'a)]. 410 | The second argument is ignored and the function calls {!Py.Capsule.make}. 411 | Applying the function twice to the same type name raises a failure 412 | ([Failure _]). *) 413 | 414 | val make_pill_wrapping : string -> 'a -> ('a -> pyobject) * (pyobject -> 'a) (* A less cumbersome synonym. *) 415 | (** py.ml: the signature has been changed from 416 | [string -> 'a -> ('a -> pyobject) * (pyobject -> 'b)] 417 | to 418 | [string -> 'a -> ('a -> pyobject) * (pyobject -> 'a)]. 419 | The second argument is ignored and the function calls {!Py.Capsule.make}. 420 | Applying the function twice to the same type name raises a failure 421 | ([Failure _]). *) 422 | 423 | (** Also, we want to be able to pass optional arguments from python to OCaml. 424 | The convention which we use for now is as follows: 425 | 426 | - Conceptually, an optional argument has to be a container monadic type. 427 | - The only thing offered by python which looks like such a thing is the list. 428 | - Hence, optional values are represented as 0-element or 1-element lists 429 | on the python side. 430 | 431 | We then need ocaml functions that make it convenient to handle the automatic 432 | unpacking of such values. (XXX Note: we need examples in the documentation 433 | that show how to use this!) 434 | *) 435 | val py_optionally : (pyobject -> 'a) -> pyobject -> 'a option 436 | 437 | val guarded_pyint_asint: pyobject -> int 438 | val guarded_pyfloat_asfloat: pyobject -> float 439 | val guarded_pynumber_asfloat: pyobject -> float 440 | val guarded_pybytes_asstring: pyobject -> string 441 | val guarded_pylist_toarray: pyobject -> pyobject array 442 | val guarded_pytuple_toarray: pyobject -> pyobject array 443 | 444 | val pycallable_asfun : pyobject -> pyobject array -> pyobject 445 | 446 | 447 | (** This is semi-internal - It should only be used for writing other 448 | convenience type applicators that have their own way of doing the checking. 449 | *) 450 | val ocamlpill_hard_unwrap : pyobject -> 'a 451 | 452 | (** {3 Running and evaluating Python from within OCaml} *) 453 | 454 | (** This function allows us to set Python's [sys.argv] *) 455 | 456 | val set_python_argv : string array -> unit 457 | 458 | (** A convenience function for just letting the Python interpreter 459 | evaluate a block of Python code. 460 | *) 461 | val python_eval : string -> int 462 | 463 | (** One may use [python_eval "execfile(...)"] to load Python code 464 | into the interpreter. This function provides a slightly nicer way 465 | to do the same. 466 | 467 | Note 1: Internally, this uses [python_eval]. The [int] return value 468 | is ignored, however. 469 | 470 | Note 2: As we do not bother to properly escape quotation marks, 471 | this will not work as supposed on filenames containing double quotes. 472 | (Yes, this is a bug and should better be fixed!) 473 | *) 474 | val python_load: string -> unit 475 | 476 | (** Start the interactive python toplevel: *) 477 | val python : unit -> int 478 | 479 | (** Start the ipython toplevel. Note: for still unknown reasons, 480 | this does not seem to be 100% reliable, and especially seems to fail 481 | in many situations where [Pycaml.ipython()] is called not from the 482 | OCaml toplevel. May be some crazy terminal handling bug. 483 | 484 | Addition: 23/01/2006 fangohr: 485 | 486 | On Mac OS X, one of the problems is that there are often several Python 487 | installations. (One is provided by Apple, but usually a fink or Darwinport 488 | installation is actually meant to use.) For fink-python (the binary 489 | installed in /sw/bin, it helps to set the shell environment variable 490 | PYTHONHOME=/sw . 491 | 492 | Then the call to ipython works fine. 493 | 494 | *) 495 | val ipython : unit -> int 496 | 497 | (** {2 Python Functions} *) 498 | 499 | (** All functions which are made visible from OCaml to python by means of 500 | [register_for_python] go into the Python module [ocaml]. Usually, one wants 501 | to place low-level interface functions there and build higher levels of 502 | abstraction on the python side on top of it which are more convenient 503 | (maybe object-oriented) to the Python end user. So, the user of a Python 504 | library that uses OCaml callbacks internally should (ideally) never notice 505 | the existence of the [ocaml] Python module. 506 | 507 | The following names are pre-registered in the [ocaml] module. 508 | Note that they all start with the reserved prefixes [sys_] or [example_]. 509 | 510 | - [sys_ocamlpill_type]: Function that maps an OCaml pill to a type string, 511 | so that Python can find out what a given pill is supposed to be. (The OCaml 512 | function name is [ocamlpill_type_of].) 513 | 514 | - [sys_python]: Function that starts a recursive Python toplevel. 515 | This may seem strange at first, but actually is highly useful e.g. for 516 | providing some interactive control deep inside a contrived function during 517 | debugging. Return value is the value computed last on the recursive python 518 | command prompt. 519 | 520 | - [example_test_interface]: Function that just prints a test string. 521 | 522 | - [example_the_answer]: The number "42", put in the [ocaml] module by OCaml. 523 | 524 | - [example_make_powers]: A function mapping an integer [n] and a float [p] 525 | to the array {v [|1.0**p,2.0**p,...,(float_of_int n)**p|] v}. 526 | 527 | - [example_hypotenuse]: A function mapping two floatingpoint values [x,y] to 528 | [sqrt(x**2+y**2)]. 529 | 530 | It is instructive to have a look at the pycaml source providing the 531 | [example_] entries to see how one can publish other constants and 532 | functions to Python. 533 | *) 534 | 535 | (** {2 Code Examples} *) 536 | 537 | (** The implementations of [example_make_powers] and [example_hypotenuse] 538 | demonstrate how to use [python_interfaced_function]: 539 | 540 | {v 541 | let _py_make_powers = 542 | python_interfaced_function 543 | ~extra_guards: 544 | [|(fun py_len -> 545 | let len = pyint_asint py_len in 546 | if len < 0 547 | then Some "Negative Length" 548 | else None); 549 | (fun _ -> None); (* This check never fails *) 550 | |] 551 | [|IntType;FloatType|] 552 | (fun py_args -> 553 | let len = pyint_asint py_args.(0) 554 | and pow = pyfloat_asdouble py_args.(1) 555 | in 556 | float_array_to_python 557 | (Array.init len (fun n -> let nn = float_of_int (n+1) in nn**pow))) 558 | and 559 | _py_hypotenuse_2d = 560 | python_interfaced_function 561 | [|FloatType;FloatType|] 562 | (fun py_args -> 563 | let x = pyfloat_asdouble py_args.(0) 564 | and y = pyfloat_asdouble py_args.(1) 565 | in pyfloat_fromdouble (sqrt(x*.x+.y*.y))) 566 | in 567 | register_for_python 568 | [|("example_make_powers", _py_make_powers); 569 | ("example_hypotenuse", _py_hypotenuse_2d); 570 | |] 571 | ;; 572 | v} 573 | 574 | *) 575 | 576 | (** {2 Appendix: signatures of undocumented functions from the original Pycaml} *) 577 | 578 | val pyerr_print : unit -> unit 579 | val py_exit : int -> unit 580 | val pyerr_printex : int -> unit 581 | val py_setprogramname : string -> unit 582 | val py_setpythonhome : string -> unit 583 | val py_isinitialized : unit -> int 584 | val pyrun_simplestring : string -> int 585 | val pyrun_anyfile : int * string -> int 586 | val pyrun_simplefile : int * string -> int 587 | val pyrun_interactiveone : int * string -> int 588 | val pyrun_interactiveloop : int * string -> int 589 | val py_fdisinteractive : int * string -> int 590 | val pyrun_anyfileex : int * string * int -> int 591 | val pyrun_simplefileex : int * string * int -> int 592 | val py_getprogramname : unit -> string 593 | val py_getpythonhome : unit -> string 594 | val py_getprogramfullpath : unit -> string 595 | val py_getprefix : unit -> string 596 | val py_getexecprefix : unit -> string 597 | val py_getpath : unit -> string 598 | val py_getversion : unit -> string 599 | val py_getplatform : unit -> string 600 | val py_getcopyright : unit -> string 601 | val py_getcompiler : unit -> string 602 | val py_getbuildinfo : unit -> string 603 | val pyrun_string : string * int * pyobject * pyobject -> pyobject 604 | val pyrun_file : int * string * int * pyobject * pyobject -> pyobject 605 | val pyrun_fileex : int * string * int * pyobject * pyobject * int -> pyobject 606 | 607 | val py_compilestring : string * string * int -> pyobject 608 | 609 | val pyobject_print : pyobject * int * int -> int 610 | val pyobject_repr : pyobject -> pyobject 611 | val pyobject_str : pyobject -> pyobject 612 | val pyobject_unicode : pyobject -> pyobject 613 | val pyobject_richcompare : pyobject * pyobject * int -> pyobject 614 | val pyobject_getattrstring : pyobject * string -> pyobject 615 | val pyobject_getattr : pyobject * pyobject -> pyobject 616 | val pyobject_istrue : pyobject -> int 617 | val pyobject_not : pyobject -> int 618 | 619 | val pycallable_check : pyobject -> int 620 | 621 | val pyobject_hasattr : pyobject * pyobject -> int 622 | val pyobject_richcomparebool : pyobject * pyobject * int -> int 623 | val pyobject_setattrstring : pyobject * string * pyobject -> int 624 | val pyobject_hasattrstring : pyobject * string -> int 625 | 626 | (* IFDEF PYCAML2 THEN*) 627 | val pyobject_compare : pyobject * pyobject -> int 628 | (* END*) 629 | 630 | (* Currently not implemented. 631 | val pynumber_coerce : pyobject * pyobject -> (pyobject * pyobject) option 632 | val pynumber_coerceex : pyobject * pyobject -> (pyobject * pyobject) option 633 | *) 634 | 635 | val pyobject_setattr : pyobject * pyobject * pyobject -> int 636 | val pyobject_hash : pyobject -> int64 637 | 638 | val pybytes_size : pyobject -> int 639 | val pystring_size : pyobject -> int (* Legacy support *) 640 | 641 | val pybytes_asstring : pyobject -> string 642 | val pystring_asstring : pyobject -> string (* Legacy support *) 643 | 644 | val pybytes_asstringandsize : pyobject -> string 645 | val pystring_asstringandsize : pyobject -> string (* Legacy support *) 646 | 647 | val pybytes_fromstring : string -> pyobject 648 | val pystring_fromstring : string -> pyobject (* Legacy support *) 649 | 650 | (* IFDEF PYMAJOR2 THEN*) 651 | val pybytes_format : pyobject * pyobject -> pyobject 652 | val pystring_format : pyobject * pyobject -> pyobject (* Legacy support *) 653 | (* END*) 654 | 655 | val pyunicode_asutf8string : pyobject -> pyobject 656 | val pyunicode_asutf16string : pyobject -> pyobject 657 | val pyunicode_asutf32string : pyobject -> pyobject 658 | val pyunicode_decodeutf8 : (string * string option) -> pyobject 659 | val pyunicode_decodeutf16 : (string * string option * int option) -> pyobject 660 | val pyunicode_decodeutf32 : (string * string option * int option) -> pyobject 661 | val pyunicode_fromunicode : (int -> int) -> int -> pyobject 662 | val pyunicode_asunicode : pyobject -> int array 663 | val pyunicode_getsize : pyobject -> int 664 | 665 | val pydict_new : unit -> pyobject 666 | val pydict_getitem : pyobject * pyobject -> pyobject 667 | val pydict_setitem : pyobject * pyobject * pyobject -> int 668 | val pydict_delitem : pyobject * pyobject -> int 669 | val pydict_clear : pyobject -> unit 670 | (* val pydict_next : pyobject * int -> (pyobject * pyobject * int) option <-- currently not implemented *) 671 | val pydict_keys : pyobject -> pyobject 672 | val pydict_values : pyobject -> pyobject 673 | val pydict_items : pyobject -> pyobject 674 | val pydict_copy : pyobject -> pyobject 675 | val pydict_size : pyobject -> int 676 | val pydict_getitemstring : pyobject * string -> pyobject 677 | val pydict_delitemstring : pyobject * string -> int 678 | val pydict_setitemstring : pyobject * string * pyobject -> int 679 | 680 | val pyint_fromlong : int64 -> pyobject 681 | val pyint_aslong : pyobject -> int64 682 | (* IFDEF PYMAJOR2 THEN*) 683 | val pyint_getmax : unit -> int64 684 | (* END*) 685 | 686 | val pyfloat_fromdouble : float -> pyobject 687 | val pyfloat_asdouble : pyobject -> float 688 | 689 | val pymodule_new : string -> pyobject 690 | val pymodule_getdict : pyobject -> pyobject 691 | val pymodule_getname : pyobject -> string 692 | val pymodule_getfilename : pyobject -> string 693 | 694 | val pytuple_new : int -> pyobject 695 | val pytuple_size : pyobject -> int 696 | val pytuple_getitem : pyobject * int -> pyobject 697 | val pytuple_setitem : pyobject * int * pyobject -> int 698 | val pytuple_getslice : pyobject * int * int -> pyobject 699 | (** py.ml: the result type has been changed from [int] to [pyobject]. *) 700 | 701 | val pyslice_new : pyobject * pyobject * pyobject -> pyobject 702 | (* val pyslice_getindices : pyobject * int -> (int * int * int) option <- Currently not supported *) 703 | 704 | val pyerr_setnone : pyobject -> unit 705 | val pyerr_setobject : pyobject * pyobject -> unit 706 | val pyerr_setstring : pyobject * string -> unit 707 | val pyerr_occurred : unit -> pyobject 708 | val pyerr_clear : unit -> unit 709 | val pyerr_fetch : pyobject * pyobject * pyobject -> pyobject * pyobject * pyobject 710 | val pyerr_givenexceptionmatches : pyobject * pyobject -> int 711 | val pyerr_exceptionmatches : pyobject -> int 712 | val pyerr_normalizeexception : pyobject * pyobject * pyobject -> pyobject * pyobject * pyobject 713 | 714 | (* IFDEF PYMAJOR2 THEN*) 715 | val pyclass_new : pyobject * pyobject * pyobject -> pyobject 716 | val pyinstance_new : pyobject * pyobject * pyobject -> pyobject 717 | val pyinstance_newraw : pyobject * pyobject -> pyobject 718 | (* END*) 719 | 720 | (* IFDEF PYMAJOR2 THEN*) 721 | val pymethod_new : pyobject * pyobject * pyobject -> pyobject 722 | (* ELSE*) 723 | (*val pymethod_new : pyobject * pyobject -> pyobject *) 724 | (* END*) 725 | 726 | val pymethod_function : pyobject -> pyobject 727 | val pymethod_self : pyobject -> pyobject 728 | (* IFDEF PYMAJOR2 THEN*) 729 | val pymethod_class : pyobject -> pyobject 730 | (* END*) 731 | 732 | val pyimport_getmagicnumber : unit -> int64 733 | val pyimport_execcodemodule : pyobject * string -> pyobject 734 | val pyimport_execcodemoduleex : string * pyobject * string -> pyobject 735 | val pyimport_getmoduledict : unit -> pyobject 736 | val pyimport_addmodule : string -> pyobject 737 | val pyimport_importmodule : string -> pyobject 738 | val pyimport_importmoduleex : string * pyobject * pyobject * pyobject -> pyobject 739 | val pyimport_import : pyobject -> pyobject 740 | val pyimport_reloadmodule : pyobject -> pyobject 741 | val pyimport_cleanup : unit -> unit 742 | val pyimport_importfrozenmodule : string -> int 743 | 744 | val pyeval_callobjectwithkeywords : pyobject * pyobject * pyobject -> pyobject 745 | val pyeval_callobject : pyobject * pyobject -> pyobject 746 | val pyeval_getbuiltins : unit -> pyobject 747 | val pyeval_getglobals : unit -> pyobject 748 | val pyeval_getlocals : unit -> pyobject 749 | (* val pyeval_getframe : unit -> pyobject -- FIX: see comment in stubs code. *) 750 | 751 | (* IFDEF PYMAJOR2 THEN*) 752 | val pyeval_getrestricted : unit -> int 753 | (* END*) 754 | 755 | val pyobject_type : pyobject -> pyobject 756 | val pyobject_size : pyobject -> int 757 | val pyobject_getitem : pyobject * pyobject -> pyobject 758 | val pyobject_setitem : pyobject * pyobject * pyobject -> int 759 | val pyobject_delitem : pyobject * pyobject -> int 760 | val pyobject_ascharbuffer : pyobject -> string 761 | val pyobject_asreadbuffer : pyobject -> string 762 | val pyobject_aswritebuffer : pyobject -> string 763 | 764 | val pynumber_check : pyobject -> int 765 | val pynumber_add : pyobject * pyobject -> pyobject 766 | val pynumber_subtract : pyobject * pyobject -> pyobject 767 | val pynumber_multiply : pyobject * pyobject -> pyobject 768 | val pynumber_truedivide : pyobject * pyobject -> pyobject 769 | val pynumber_floordivide : pyobject * pyobject -> pyobject 770 | (* IFDEF PYMAJOR2 THEN*) 771 | val pynumber_divide : pyobject * pyobject -> pyobject 772 | (* END*) 773 | val pynumber_remainder : pyobject * pyobject -> pyobject 774 | val pynumber_divmod : pyobject * pyobject -> pyobject 775 | val pynumber_power : pyobject * pyobject * pyobject -> pyobject 776 | val pynumber_negative : pyobject -> pyobject 777 | val pynumber_positive : pyobject -> pyobject 778 | val pynumber_absolute : pyobject -> pyobject 779 | val pynumber_invert : pyobject -> pyobject 780 | val pynumber_lshift : pyobject * pyobject -> pyobject 781 | val pynumber_rshift : pyobject * pyobject -> pyobject 782 | val pynumber_and : pyobject * pyobject -> pyobject 783 | val pynumber_xor : pyobject * pyobject -> pyobject 784 | val pynumber_or : pyobject * pyobject -> pyobject 785 | (* IFDEF PYMAJOR2 THEN*) 786 | val pynumber_int : pyobject -> pyobject 787 | (* END*) 788 | val pynumber_long : pyobject -> pyobject 789 | val pynumber_float : pyobject -> pyobject 790 | val pynumber_inplaceadd : pyobject * pyobject -> pyobject 791 | val pynumber_inplacesubtract : pyobject * pyobject -> pyobject 792 | val pynumber_inplacemultiply : pyobject * pyobject -> pyobject 793 | val pynumber_inplacetruedivide : pyobject * pyobject -> pyobject 794 | val pynumber_inplacefloordivide : pyobject * pyobject -> pyobject 795 | (* IFDEF PYMAJOR2 THEN*) 796 | val pynumber_inplacedivide : pyobject * pyobject -> pyobject 797 | (* END*) 798 | val pynumber_inplaceremainder : pyobject * pyobject -> pyobject 799 | val pynumber_inplacelshift : pyobject * pyobject -> pyobject 800 | val pynumber_inplacershift : pyobject * pyobject -> pyobject 801 | val pynumber_inplaceand : pyobject * pyobject -> pyobject 802 | val pynumber_inplacexor : pyobject * pyobject -> pyobject 803 | val pynumber_inplaceor : pyobject * pyobject -> pyobject 804 | val pynumber_inplacepower : pyobject * pyobject * pyobject -> pyobject 805 | 806 | val pysequence_check : pyobject -> int 807 | val pysequence_size : pyobject -> int 808 | val pysequence_length : pyobject -> int 809 | val pysequence_concat : pyobject * pyobject -> pyobject 810 | val pysequence_repeat : pyobject * int -> pyobject 811 | val pysequence_getitem : pyobject * int -> pyobject 812 | (** py.ml: the result type has been changed from [int] to [pyobject]. *) 813 | 814 | val pysequence_getslice : pyobject * int * int -> pyobject 815 | val pysequence_setitem : pyobject * int * pyobject -> int 816 | val pysequence_delitem : pyobject * int -> int 817 | (** py.ml: one of the two [pyobject] arguments has been removed. *) 818 | 819 | val pysequence_setslice : pyobject * int * int * pyobject -> int 820 | val pysequence_delslice : pyobject * int * int -> int 821 | val pysequence_tuple : pyobject -> pyobject 822 | val pysequence_list : pyobject -> pyobject 823 | val pysequence_fast : pyobject * string -> pyobject 824 | val pysequence_count : pyobject * pyobject -> int 825 | val pysequence_contains : pyobject * pyobject -> int 826 | val pysequence_in : pyobject * pyobject -> int 827 | val pysequence_index : pyobject * pyobject -> int 828 | val pysequence_inplaceconcat : pyobject * pyobject -> pyobject 829 | val pysequence_inplacerepeat : pyobject * int -> pyobject 830 | 831 | val pymapping_check : pyobject -> int 832 | val pymapping_size : pyobject -> int 833 | val pymapping_length : pyobject -> int 834 | val pymapping_haskeystring : pyobject * string -> int 835 | val pymapping_haskey : pyobject * pyobject -> int 836 | val pymapping_getitemstring : pyobject * string -> pyobject 837 | val pymapping_setitemstring : pyobject * string * pyobject -> int 838 | 839 | val pyiter_check : pyobject -> int 840 | val pyiter_next : pyobject -> pyobject 841 | 842 | val pynull : unit -> pyobject 843 | val pynone : unit -> pyobject 844 | 845 | val pytuple_fromarray : pyobject array -> pyobject 846 | val pytuple_fromsingle : pyobject -> pyobject 847 | val pytuple_empty : pyobject 848 | val pytuple2 : pyobject * pyobject -> pyobject 849 | val pytuple3 : pyobject * pyobject * pyobject -> pyobject 850 | val pytuple4 : pyobject * pyobject * pyobject * pyobject -> pyobject 851 | val pytuple5 : pyobject * pyobject * pyobject * pyobject * pyobject -> pyobject 852 | 853 | val pyint_fromint : int -> pyobject 854 | val pyint_asint : pyobject -> int 855 | val pytuple_toarray : pyobject -> pyobject array 856 | val pywrap_closure : (pyobject -> pyobject) -> pyobject 857 | 858 | (* val version : unit -> string (* This function returns a unique code version string. *) *) 859 | -------------------------------------------------------------------------------- /pyml_stubs.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "pyml_stubs.h" 17 | 18 | static FILE *(*Python__Py_fopen)(const char *pathname, const char *mode); 19 | 20 | static FILE *(*Python__Py_wfopen)(const wchar_t *pathname, const wchar_t *mode); 21 | 22 | static void *xmalloc(size_t size) 23 | { 24 | void *p = malloc(size); 25 | if (!p) { 26 | caml_failwith("Virtual memory exhausted\n"); 27 | } 28 | return p; 29 | } 30 | 31 | #ifdef _WIN32 32 | #include 33 | 34 | typedef HINSTANCE library_t; 35 | 36 | static library_t 37 | open_library(const char *filename) 38 | { 39 | return LoadLibrary(filename); 40 | } 41 | 42 | static char * 43 | get_library_error() 44 | { 45 | return "Unable to load library"; 46 | } 47 | 48 | static void 49 | close_library(library_t library) 50 | { 51 | if (!FreeLibrary(library)) { 52 | fprintf(stderr, "close_library.\n"); 53 | exit(EXIT_FAILURE); 54 | } 55 | } 56 | 57 | static library_t 58 | get_default_library(void) 59 | { 60 | return GetModuleHandle(0); 61 | } 62 | 63 | static void * 64 | find_symbol(library_t library, const char *name) 65 | { 66 | return GetProcAddress(library, name); 67 | } 68 | 69 | int 70 | unsetenv(const char *name) 71 | { 72 | size_t len = strlen(name); 73 | char *string = xmalloc(len + 2); 74 | int result; 75 | snprintf(string, len + 2, "%s=", name); 76 | result = _putenv(string); 77 | free(string); 78 | return result; 79 | } 80 | 81 | extern int win_CRT_fd_of_filedescr(value handle); 82 | 83 | static FILE * 84 | file_of_file_descr(value file_descr, const char *mode) 85 | { 86 | CAMLparam1(file_descr); 87 | int fd = win_CRT_fd_of_filedescr(file_descr); 88 | FILE *result = _fdopen(_dup(fd), mode); 89 | CAMLreturnT(FILE *, result); 90 | } 91 | #else 92 | #include 93 | 94 | typedef void *library_t; 95 | 96 | static library_t 97 | open_library(const char *filename) 98 | { 99 | return dlopen(filename, RTLD_LAZY | RTLD_GLOBAL); 100 | } 101 | 102 | static char * 103 | get_library_error() 104 | { 105 | return dlerror(); 106 | } 107 | 108 | void 109 | close_library(library_t filename) 110 | { 111 | if (dlclose(filename)) { 112 | fprintf(stderr, "close_library: %s.\n", dlerror()); 113 | exit(EXIT_FAILURE); 114 | } 115 | } 116 | 117 | static library_t 118 | get_default_library(void) 119 | { 120 | return RTLD_DEFAULT; 121 | } 122 | 123 | static void * 124 | find_symbol(library_t library, const char *name) 125 | { 126 | return dlsym(library, name); 127 | } 128 | 129 | static FILE * 130 | file_of_file_descr(value file_descr, const char *mode) 131 | { 132 | CAMLparam1(file_descr); 133 | int fd = Int_val(file_descr); 134 | FILE *result = fdopen(dup(fd), mode); 135 | CAMLreturnT(FILE *, result); 136 | } 137 | #endif 138 | 139 | /* Global variables for the library */ 140 | 141 | /* version_major != 0 iff the library is initialized */ 142 | static int version_major; 143 | static int version_minor; 144 | 145 | static library_t library; 146 | 147 | /* Functions that are special enough to deserved to be wrapped specifically */ 148 | 149 | /* Wrapped by pywrap_closure */ 150 | static PyObject *(*Python_PyCFunction_NewEx) 151 | (PyMethodDef *, PyObject *, PyObject *); 152 | 153 | /* Wrapped by closure and capsule */ 154 | static void *(*Python27_PyCapsule_New) 155 | (void *, const char *, PyCapsule_Destructor); 156 | static void *(*Python27_PyCapsule_GetPointer)(PyObject *, const char *); 157 | static int (*Python27_PyCapsule_IsValid)(PyObject *, const char *); 158 | static void *(*Python2_PyCObject_FromVoidPtr)(void *, void (*)(void *)); 159 | static void *(*Python2_PyCObject_AsVoidPtr)(PyObject *); 160 | 161 | /* Hack for multi-arguments */ 162 | static PyObject *(*Python_PyObject_CallFunctionObjArgs)(PyObject *, ...); 163 | static PyObject *(*Python_PyObject_CallMethodObjArgs)( 164 | PyObject *, PyObject *, ...); 165 | 166 | /* Wrapped by PyErr_Fetch_wrapper */ 167 | static void (*Python_PyErr_Fetch)(PyObject **, PyObject **, PyObject **); 168 | static void (*Python_PyErr_Restore)(PyObject *, PyObject *, PyObject *); 169 | static void (*Python_PyErr_NormalizeException) 170 | (PyObject **, PyObject **, PyObject **); 171 | 172 | /* Resolved differently between Python 2 and Python 3 */ 173 | static PyObject *Python__Py_FalseStruct; 174 | 175 | /* Buffer and size */ 176 | static int (*Python_PyString_AsStringAndSize) 177 | (PyObject *, char **, Py_ssize_t *); 178 | static int (*Python_PyObject_AsCharBuffer) 179 | (PyObject *, const char **, Py_ssize_t *); 180 | static int (*Python_PyObject_AsReadBuffer) 181 | (PyObject *, const void **, Py_ssize_t *); 182 | static int (*Python_PyObject_AsWriteBuffer) 183 | (PyObject *, void **, Py_ssize_t *); 184 | 185 | /* Length argument */ 186 | static PyObject *(*Python_PyLong_FromString)(const char *, const char **, int); 187 | 188 | /* Internal use only */ 189 | static void (*Python_PyMem_Free)(void *); 190 | 191 | /* Generate traceback objects. */ 192 | static PyObject *(*Python_PyThreadState_Get)(); 193 | static PyObject *(*Python_PyFrame_New)(PyObject*, PyObject*, PyObject*, PyObject*); 194 | static PyObject *(*Python_PyCode_NewEmpty)(const char*, const char*, int); 195 | 196 | static enum UCS { UCS_NONE, UCS2, UCS4 } ucs; 197 | 198 | /* Single instance of () */ 199 | static PyObject *tuple_empty; 200 | 201 | #include "pyml.h" 202 | #include 203 | 204 | static void *getcustom( value v ) 205 | { 206 | return *((void **)Data_custom_val(v)); 207 | } 208 | 209 | static void pydecref( value v ) 210 | { 211 | if (getcustom(v)) { 212 | Py_DECREF((PyObject *)getcustom(v)); 213 | } 214 | } 215 | 216 | static int 217 | rich_compare_bool_nofail 218 | (PyObject *o1, PyObject *o2, int opid) 219 | { 220 | int result = Python_PyObject_RichCompareBool(o1, o2, opid); 221 | if (result == -1) { 222 | Python_PyErr_Clear(); 223 | result = 0; 224 | } 225 | return result; 226 | } 227 | 228 | static int 229 | pycompare(value v1, value v2) 230 | { 231 | int result; 232 | PyObject *o1 = getcustom(v1); 233 | PyObject *o2 = getcustom(v2); 234 | 235 | if (o1 && !o2) 236 | result = -1; 237 | else if (o2 && !o1) 238 | result = 1; 239 | else if (!o1 && !o2) 240 | result = 0; 241 | else if (version_major < 3) 242 | Python2_PyObject_Cmp(o1, o2, &result); 243 | else if (rich_compare_bool_nofail(o1, o2, Py_EQ)) 244 | result = 0; 245 | else if (rich_compare_bool_nofail(o1, o2, Py_LT)) 246 | result = -1; 247 | else if (rich_compare_bool_nofail(o1, o2, Py_GT)) 248 | result = 1; 249 | else 250 | result = -1; 251 | 252 | return result; 253 | } 254 | 255 | static intnat 256 | pyhash( value v ) 257 | { 258 | if (getcustom(v)) { 259 | intnat result = Python_PyObject_Hash((PyObject *)getcustom(v)); 260 | if (result == -1) { 261 | Python_PyErr_Clear(); 262 | } 263 | return result; 264 | } 265 | else { 266 | return 0; 267 | } 268 | } 269 | 270 | void 271 | pyml_assert_initialized() 272 | { 273 | if (!version_major) { 274 | caml_failwith("Run 'Py.initialize ()' first"); 275 | } 276 | } 277 | 278 | /** Creates a Python tuple initialized with a single element given by the 279 | argument. The reference to the argument is stolen. */ 280 | static PyObject * 281 | singleton(PyObject *value) 282 | { 283 | PyObject *result = Python_PyTuple_New(1); 284 | if (!result) { 285 | caml_failwith("PyTuple_New"); 286 | } 287 | if (Python_PyTuple_SetItem(result, 0, value)) { 288 | caml_failwith("PyTuple_SetItem"); 289 | } 290 | return result; 291 | } 292 | 293 | static void 294 | pyserialize(value v, uintnat *bsize_32, uintnat *bsize_64) 295 | { 296 | pyml_assert_initialized(); 297 | PyObject *value = getcustom(v); 298 | PyObject *pickle = Python_PyImport_ImportModule("pickle"); 299 | if (pickle == NULL) { 300 | caml_failwith("Cannot import pickle"); 301 | } 302 | PyObject *dumps = Python_PyObject_GetAttrString(pickle, "dumps"); 303 | if (dumps == NULL) { 304 | caml_failwith("pickle.dumps unavailable"); 305 | } 306 | PyObject *args = singleton(value); 307 | PyObject *bytes = Python_PyObject_Call(dumps, args, NULL); 308 | if (bytes == NULL) { 309 | caml_failwith("pickle.dumps failed"); 310 | } 311 | Py_ssize_t size; 312 | char *contents; 313 | if (version_major >= 3) { 314 | size = Python3_PyBytes_Size(bytes); 315 | contents = (char *) Python3_PyBytes_AsString(bytes); 316 | } 317 | else { 318 | size = Python2_PyString_Size(bytes); 319 | contents = (char *) Python2_PyString_AsString(bytes); 320 | } 321 | caml_serialize_int_8(size); 322 | caml_serialize_block_1(contents, size); 323 | *bsize_32 = 4; 324 | *bsize_64 = 8; 325 | /*Py_DECREF(bytes);*/ /* reference stolen by args */ 326 | Py_DECREF(args); 327 | Py_DECREF(dumps); 328 | Py_DECREF(pickle); 329 | } 330 | 331 | static uintnat 332 | pydeserialize(void *dst) 333 | { 334 | pyml_assert_initialized(); 335 | Py_ssize_t size = caml_deserialize_uint_8(); 336 | PyObject *bytes; 337 | char *contents; 338 | if (version_major >= 3) { 339 | bytes = Python3_PyBytes_FromStringAndSize(NULL, size); 340 | contents = (char *) Python3_PyBytes_AsString(bytes); 341 | } 342 | else { 343 | bytes = Python2_PyString_FromStringAndSize(NULL, size); 344 | contents = (char *) Python2_PyString_AsString(bytes); 345 | } 346 | caml_deserialize_block_1(contents, size); 347 | PyObject *pickle = Python_PyImport_ImportModule("pickle"); 348 | if (pickle == NULL) { 349 | caml_failwith("Cannot import pickle"); 350 | } 351 | PyObject *loads = Python_PyObject_GetAttrString(pickle, "loads"); 352 | if (loads == NULL) { 353 | caml_failwith("pickle.loads unavailable"); 354 | } 355 | PyObject *args = singleton(bytes); 356 | PyObject *value = Python_PyObject_Call(loads, args, NULL); 357 | if (value == NULL) { 358 | caml_failwith("pickle.loads failed"); 359 | } 360 | *((PyObject **) dst) = value; 361 | /*Py_DECREF(bytes);*/ /* reference stolen by args */ 362 | Py_DECREF(args); 363 | Py_DECREF(loads); 364 | Py_DECREF(pickle); 365 | return sizeof(PyObject *); 366 | } 367 | 368 | struct custom_operations pyops = 369 | { 370 | "PythonObject", 371 | pydecref, 372 | pycompare, 373 | pyhash, 374 | pyserialize, 375 | pydeserialize 376 | }; 377 | 378 | enum code { 379 | CODE_NULL, 380 | CODE_NONE, 381 | CODE_TRUE, 382 | CODE_FALSE, 383 | CODE_TUPLE_EMPTY 384 | }; 385 | 386 | static void * 387 | resolve(const char *symbol) 388 | { 389 | void *result = find_symbol(library, symbol); 390 | if (!result) { 391 | char *fmt = "Cannot resolve %s.\n"; 392 | ssize_t size = snprintf(NULL, 0, fmt, symbol); 393 | char *msg = xmalloc(size + 1); 394 | snprintf(msg, size + 1, fmt, symbol); 395 | caml_failwith(msg); 396 | } 397 | return result; 398 | } 399 | 400 | static void * 401 | resolve_optional(const char *symbol) 402 | { 403 | return find_symbol(library, symbol); 404 | } 405 | 406 | value 407 | pyml_wrap(PyObject *object, bool steal) 408 | { 409 | CAMLparam0(); 410 | CAMLlocal1(v); 411 | if (!object) { 412 | CAMLreturn(Val_int(CODE_NULL)); 413 | } 414 | if (object == Python__Py_NoneStruct) { 415 | CAMLreturn(Val_int(CODE_NONE)); 416 | } 417 | if (object == Python__Py_TrueStruct) { 418 | CAMLreturn(Val_int(CODE_TRUE)); 419 | } 420 | if (object == Python__Py_FalseStruct) { 421 | CAMLreturn(Val_int(CODE_FALSE)); 422 | } 423 | unsigned long flags = 424 | ((struct _typeobject *) pyobjectdescr(pyobjectdescr(object)->ob_type)) 425 | ->tp_flags; 426 | if (flags & Py_TPFLAGS_TUPLE_SUBCLASS 427 | && Python_PySequence_Length(object) == 0) { 428 | CAMLreturn(Val_int(CODE_TUPLE_EMPTY)); 429 | } 430 | if (!steal) { 431 | Py_INCREF(object); 432 | } 433 | v = caml_alloc_custom(&pyops, sizeof(PyObject *), 100, 30000000); 434 | *((PyObject **)Data_custom_val(v)) = object; 435 | CAMLreturn(v); 436 | } 437 | 438 | PyObject * 439 | pyml_unwrap(value v) 440 | { 441 | if (Is_long(v)) 442 | switch (Int_val(v)) { 443 | case CODE_NULL: 444 | return NULL; 445 | case CODE_NONE: 446 | return Python__Py_NoneStruct; 447 | case CODE_TRUE: 448 | return Python__Py_TrueStruct; 449 | case CODE_FALSE: 450 | return Python__Py_FalseStruct; 451 | case CODE_TUPLE_EMPTY: 452 | return tuple_empty; 453 | } 454 | 455 | return *((PyObject **)Data_custom_val(v)); 456 | } 457 | 458 | /* 459 | static value 460 | pyml_wrap_compilerflags(PyCompilerFlags *flags) 461 | { 462 | CAMLparam0(); 463 | CAMLlocal2(ref, some); 464 | if (!flags) { 465 | CAMLreturn(Val_int(0)); 466 | } 467 | else { 468 | ref = caml_alloc(0, 1); 469 | Store_field(ref, 0, Val_int(flags->cf_flags)); 470 | some = caml_alloc(0, 1); 471 | Store_field(some, 0, ref); 472 | CAMLreturn(some); 473 | } 474 | } 475 | */ 476 | 477 | static PyCompilerFlags * 478 | pyml_unwrap_compilerflags(value v) 479 | { 480 | CAMLparam1(v); 481 | if (Is_block(v)) { 482 | PyCompilerFlags *flags = malloc(sizeof(PyCompilerFlags)); 483 | flags->cf_flags = Int_val(Field(Field(v, 0), 0)); 484 | 485 | /* only useful for Python >= 3.8 */ 486 | flags->cf_feature_version = version_minor; 487 | 488 | CAMLreturnT(PyCompilerFlags *, flags); 489 | } 490 | else { 491 | CAMLreturnT(PyCompilerFlags *, NULL); 492 | } 493 | } 494 | 495 | /* 496 | static value 497 | pyml_wrap_intref(int v) 498 | { 499 | CAMLparam0(); 500 | CAMLlocal1(ref); 501 | ref = caml_alloc(0, 1); 502 | Store_field(ref, 0, Val_int(v)); 503 | CAMLreturn(ref); 504 | } 505 | */ 506 | 507 | static int 508 | pyml_unwrap_intref(value v) 509 | { 510 | CAMLparam1(v); 511 | CAMLreturnT(int, Int_val(Field(v, 0))); 512 | } 513 | 514 | static void * 515 | unwrap_capsule(PyObject *obj, const char *type) 516 | { 517 | if (Python27_PyCapsule_GetPointer) { 518 | return Python27_PyCapsule_GetPointer(obj, type); 519 | } 520 | else { 521 | return Python2_PyCObject_AsVoidPtr(obj); 522 | } 523 | } 524 | 525 | 526 | static PyObject * 527 | wrap_capsule(void *ptr, char *type, void (*destr)(PyObject *)) 528 | { 529 | if (Python27_PyCapsule_New) { 530 | return Python27_PyCapsule_New(ptr, type, destr); 531 | } 532 | else { 533 | return Python2_PyCObject_FromVoidPtr(ptr, (void(*)(void *))destr); 534 | } 535 | } 536 | 537 | static PyObject * 538 | pycall_callback(PyObject *obj, PyObject *args) 539 | { 540 | CAMLparam0(); 541 | CAMLlocal3(ml_out, ml_func, ml_args); 542 | PyObject *out; 543 | void *p = unwrap_capsule(obj, "ocaml-closure"); 544 | if (!p) { 545 | Py_INCREF(Python__Py_NoneStruct); 546 | CAMLreturnT(PyObject *, Python__Py_NoneStruct); 547 | } 548 | ml_func = *(value *) p; 549 | ml_args = pyml_wrap(args, false); 550 | ml_out = caml_callback(ml_func, ml_args); 551 | out = pyml_unwrap(ml_out); 552 | Py_XINCREF(out); 553 | CAMLreturnT(PyObject *, out); 554 | } 555 | 556 | static PyObject * 557 | pycall_callback_with_keywords(PyObject *obj, PyObject *args, PyObject *keywords) 558 | { 559 | CAMLparam0(); 560 | CAMLlocal4(ml_out, ml_func, ml_args, ml_keywords); 561 | PyObject *out; 562 | void *p = unwrap_capsule(obj, "ocaml-closure"); 563 | if (!p) { 564 | Py_INCREF(Python__Py_NoneStruct); 565 | CAMLreturnT(PyObject *, Python__Py_NoneStruct); 566 | } 567 | ml_func = *(value *) p; 568 | ml_args = pyml_wrap(args, false); 569 | ml_keywords = pyml_wrap(keywords, false); 570 | ml_out = caml_callback2(ml_func, ml_args, ml_keywords); 571 | out = pyml_unwrap(ml_out); 572 | Py_XINCREF(out); 573 | CAMLreturnT(PyObject *, out); 574 | } 575 | 576 | static void 577 | caml_destructor(PyObject *v, const char *capsule_name) 578 | { 579 | value *valptr = (value *) unwrap_capsule(v, capsule_name); 580 | caml_remove_global_root(valptr); 581 | free(valptr); 582 | } 583 | 584 | static void 585 | camldestr_capsule(PyObject *v) 586 | { 587 | caml_destructor(v, "ocaml-capsule"); 588 | } 589 | 590 | static PyObject * 591 | camlwrap_capsule(value val, void *aux_str, int size) 592 | { 593 | value *v = (value *) malloc(sizeof(value) + size); 594 | *v = val; 595 | memcpy((char *)v + sizeof(value), aux_str, size); 596 | caml_register_global_root(v); 597 | return wrap_capsule(v, "ocaml-capsule", camldestr_capsule); 598 | } 599 | 600 | static void * 601 | caml_aux(PyObject *obj) 602 | { 603 | value *v = (value *) unwrap_capsule(obj, "ocaml-closure"); 604 | return (char *) v + sizeof(value); 605 | } 606 | 607 | void 608 | pyml_assert_python2() 609 | { 610 | if (version_major != 2) { 611 | pyml_assert_initialized(); 612 | caml_failwith("Python 2 needed"); 613 | } 614 | } 615 | 616 | void 617 | pyml_assert_ucs2() 618 | { 619 | if (ucs != UCS2) { 620 | pyml_assert_initialized(); 621 | caml_failwith("Python with UCS2 needed"); 622 | } 623 | } 624 | 625 | void 626 | pyml_assert_ucs4() 627 | { 628 | if (ucs != UCS4) { 629 | pyml_assert_initialized(); 630 | caml_failwith("Python with UCS4 needed"); 631 | } 632 | } 633 | 634 | void 635 | pyml_assert_python3() 636 | { 637 | if (version_major != 3) { 638 | pyml_assert_initialized(); 639 | caml_failwith("Python 3 needed"); 640 | } 641 | } 642 | 643 | void 644 | pyml_check_symbol_available(void *symbol, char *symbol_name) 645 | { 646 | if (!symbol) { 647 | char *fmt = "Symbol unavailable with this version of Python: %s.\n"; 648 | ssize_t size = snprintf(NULL, 0, fmt, symbol_name); 649 | if (size < 0) { 650 | caml_failwith("Symbol unavailable with this version of Python.\n"); 651 | return; 652 | } 653 | char *msg = xmalloc(size + 1); 654 | size = snprintf(msg, size + 1, fmt, symbol_name); 655 | if (size < 0) { 656 | caml_failwith("Symbol unavailable with this version of Python.\n"); 657 | return; 658 | } 659 | caml_failwith(msg); 660 | } 661 | } 662 | 663 | void * 664 | deref_not_null(void *pointer) 665 | { 666 | if (pointer) { 667 | return *(void **) pointer; 668 | } 669 | else { 670 | return NULL; 671 | } 672 | } 673 | 674 | struct pyml_closure { 675 | value value; 676 | PyMethodDef method; 677 | }; 678 | 679 | static char *anon_closure = "anonymous_closure"; 680 | 681 | static void 682 | camldestr_closure(PyObject *v) 683 | { 684 | struct pyml_closure *valptr = unwrap_capsule(v, "ocaml-closure"); 685 | const char *ml_doc = valptr->method.ml_doc; 686 | const char *ml_name = valptr->method.ml_name; 687 | caml_remove_global_root((value *)valptr); 688 | free(valptr); 689 | free((void *) ml_doc); 690 | if (ml_name != anon_closure) free((void *) ml_name); 691 | } 692 | 693 | CAMLprim value 694 | pyml_wrap_closure(value name, value docstring, value closure) 695 | { 696 | CAMLparam3(name, docstring, closure); 697 | pyml_assert_initialized(); 698 | PyMethodDef ml; 699 | PyObject *obj; 700 | PyMethodDef *ml_def; 701 | ml.ml_name = anon_closure; 702 | if (name != Val_int(0)) { 703 | ml.ml_name = strdup(String_val(Field(name, 0))); 704 | } 705 | if (Tag_val(closure) == 0) { 706 | ml.ml_flags = 1; 707 | ml.ml_meth = pycall_callback; 708 | } 709 | else { 710 | ml.ml_flags = 3; 711 | ml.ml_meth = (PyCFunction) pycall_callback_with_keywords; 712 | } 713 | ml.ml_doc = strdup(String_val(docstring)); 714 | struct pyml_closure *v = malloc(sizeof(struct pyml_closure)); 715 | v->value = Field(closure, 0); 716 | v->method = ml; 717 | caml_register_global_root(&v->value); 718 | obj = wrap_capsule(v, "ocaml-closure", camldestr_closure); 719 | ml_def = (PyMethodDef *) caml_aux(obj); 720 | PyObject *f = Python_PyCFunction_NewEx(ml_def, obj, NULL); 721 | Py_DECREF(obj); 722 | CAMLreturn(pyml_wrap(f, true)); 723 | } 724 | 725 | int debug_build; 726 | 727 | int trace_refs_build; 728 | 729 | static void 730 | guess_debug_build() 731 | { 732 | PyObject *sysconfig = Python_PyImport_ImportModule("sysconfig"); 733 | if (!sysconfig) { 734 | caml_failwith("Cannot import sysconfig"); 735 | } 736 | PyObject *get_config_var = 737 | Python_PyObject_GetAttrString(sysconfig, "get_config_var"); 738 | assert(get_config_var); 739 | PyObject *args; 740 | PyObject *py_debug; 741 | PyObject *debug_build_py; 742 | char *py_debug_str = "Py_DEBUG"; 743 | if (version_major >= 3) { 744 | py_debug = Python3_PyUnicode_FromStringAndSize(py_debug_str, strlen(py_debug_str)); 745 | } 746 | else { 747 | py_debug = Python2_PyString_FromStringAndSize(py_debug_str, strlen(py_debug_str)); 748 | } 749 | assert(py_debug); 750 | args = singleton(py_debug); 751 | debug_build_py = Python_PyObject_Call(get_config_var, args, NULL); 752 | if (!debug_build_py) { 753 | Python_PyErr_Print(); 754 | caml_failwith("Cannot check for debug build"); 755 | } 756 | if (debug_build_py == Python__Py_NoneStruct) { 757 | debug_build = 0; 758 | } 759 | else { 760 | if (version_major >= 3) { 761 | debug_build = Python_PyLong_AsLong(debug_build_py); 762 | } 763 | else { 764 | debug_build = Python2_PyInt_AsLong(debug_build_py); 765 | } 766 | if (debug_build == -1) { 767 | caml_failwith("Cannot check for debug build"); 768 | } 769 | } 770 | Py_DECREF(args); 771 | Py_DECREF(get_config_var); 772 | Py_DECREF(sysconfig); 773 | } 774 | 775 | CAMLprim value 776 | py_load_library(value filename_ocaml, value debug_build_ocaml) 777 | { 778 | CAMLparam2(filename_ocaml, debug_build_ocaml); 779 | if (Is_block(filename_ocaml)) { 780 | const char *filename = String_val(Field(filename_ocaml, 0)); 781 | library = open_library(filename); 782 | if (!library) { 783 | caml_failwith(get_library_error()); 784 | } 785 | } 786 | else { 787 | library = get_default_library(); 788 | } 789 | Python_Py_GetVersion = find_symbol(library, "Py_GetVersion"); 790 | if (!Python_Py_GetVersion) { 791 | caml_failwith("No Python symbol"); 792 | } 793 | const char *version = Python_Py_GetVersion(); 794 | version_major = version[0] - '0'; 795 | version_minor = version[2] - '0'; 796 | Python_PyCFunction_NewEx = resolve("PyCFunction_NewEx"); 797 | if ((version_major == 2 && version_minor >= 7) || version_major >= 3) { 798 | Python27_PyCapsule_New = resolve("PyCapsule_New"); 799 | Python27_PyCapsule_GetPointer = resolve("PyCapsule_GetPointer"); 800 | Python27_PyCapsule_IsValid = resolve("PyCapsule_IsValid"); 801 | } 802 | Python_PyObject_CallFunctionObjArgs = 803 | resolve("PyObject_CallFunctionObjArgs"); 804 | Python_PyObject_CallMethodObjArgs = 805 | resolve("PyObject_CallMethodObjArgs"); 806 | Python_PyErr_Fetch = resolve("PyErr_Fetch"); 807 | Python_PyErr_Restore = resolve("PyErr_Restore"); 808 | Python_PyErr_NormalizeException = resolve("PyErr_NormalizeException"); 809 | Python_PyObject_AsCharBuffer = resolve_optional("PyObject_AsCharBuffer"); 810 | Python_PyObject_AsReadBuffer = resolve_optional("PyObject_AsReadBuffer"); 811 | Python_PyObject_AsWriteBuffer = resolve_optional("PyObject_AsWriteBuffer"); 812 | if (version_major >= 3) { 813 | Python__Py_FalseStruct = resolve("_Py_FalseStruct"); 814 | Python_PyString_AsStringAndSize = resolve("PyBytes_AsStringAndSize"); 815 | } 816 | else { 817 | Python__Py_FalseStruct = resolve("_Py_ZeroStruct"); 818 | Python_PyString_AsStringAndSize = resolve("PyString_AsStringAndSize"); 819 | } 820 | Python_PyLong_FromString = resolve("PyLong_FromString"); 821 | Python_PyMem_Free = resolve("PyMem_Free"); 822 | Python_PyThreadState_Get = resolve("PyThreadState_Get"); 823 | Python_PyFrame_New = resolve("PyFrame_New"); 824 | Python_PyCode_NewEmpty = resolve("PyCode_NewEmpty"); 825 | if (version_major >= 3) { 826 | Python__Py_wfopen = resolve_optional("_Py_wfopen"); /* Python >=3.10 */ 827 | Python__Py_fopen = resolve_optional("_Py_fopen"); 828 | } 829 | else { 830 | Python2_PyCObject_FromVoidPtr = resolve("PyCObject_FromVoidPtr"); 831 | Python2_PyCObject_AsVoidPtr = resolve("PyCObject_AsVoidPtr"); 832 | } 833 | if (find_symbol(library, "PyUnicodeUCS2_AsEncodedString")) { 834 | ucs = UCS2; 835 | } 836 | else if (find_symbol(library, "PyUnicodeUCS4_AsEncodedString")) { 837 | ucs = UCS4; 838 | } 839 | else { 840 | ucs = UCS_NONE; 841 | } 842 | #include "pyml_dlsyms.inc" 843 | Python_Py_Initialize(); 844 | PyObject *sys = Python_PyImport_ImportModule("sys"); 845 | if (!sys) { 846 | caml_failwith("cannot import module sys"); 847 | } 848 | trace_refs_build = Python_PyObject_HasAttrString(sys, "getobjects"); 849 | if (Is_block(debug_build_ocaml)) { 850 | debug_build = Int_val(Field(debug_build_ocaml, 0)); 851 | } 852 | else { 853 | guess_debug_build(); 854 | } 855 | tuple_empty = Python_PyTuple_New(0); 856 | caml_register_custom_operations(&pyops); 857 | CAMLreturn(Val_unit); 858 | } 859 | 860 | struct PyObjectDebug { 861 | PyObject *_ob_next; \ 862 | PyObject *_ob_prev; 863 | PyObjectDescr descr; 864 | }; 865 | 866 | PyObjectDescr *pyobjectdescr(PyObject *obj) { 867 | if (trace_refs_build) { 868 | return &((struct PyObjectDebug *) obj)->descr; 869 | } 870 | else { 871 | return (PyObjectDescr *) obj; 872 | } 873 | } 874 | 875 | CAMLprim value 876 | py_is_debug_build() 877 | { 878 | CAMLparam0(); 879 | CAMLreturn(Val_int(debug_build)); 880 | } 881 | 882 | CAMLprim value 883 | py_finalize_library(value unit) 884 | { 885 | CAMLparam1(unit); 886 | pyml_assert_initialized(); 887 | Py_DECREF(tuple_empty); 888 | if (library != get_default_library()) { 889 | close_library(library); 890 | } 891 | version_major = 0; 892 | ucs = UCS_NONE; 893 | CAMLreturn(Val_unit); 894 | } 895 | 896 | CAMLprim value 897 | py_unsetenv(value name_ocaml) 898 | { 899 | CAMLparam1(name_ocaml); 900 | const char *name = String_val(name_ocaml); 901 | if (unsetenv(name) == -1) { 902 | caml_failwith(strerror(errno)); 903 | } 904 | CAMLreturn(Val_unit); 905 | } 906 | 907 | CAMLprim value 908 | py_get_UCS(value unit) 909 | { 910 | CAMLparam1(unit); 911 | pyml_assert_initialized(); 912 | CAMLreturn(Val_int(ucs)); 913 | } 914 | 915 | CAMLprim value 916 | PyNull_wrapper(value unit) 917 | { 918 | CAMLparam1(unit); 919 | CAMLreturn(Val_int(CODE_NULL)); 920 | } 921 | 922 | CAMLprim value 923 | PyNone_wrapper(value unit) 924 | { 925 | CAMLparam1(unit); 926 | CAMLreturn(Val_int(CODE_NONE)); 927 | } 928 | 929 | CAMLprim value 930 | PyTrue_wrapper(value unit) 931 | { 932 | CAMLparam1(unit); 933 | CAMLreturn(Val_int(CODE_TRUE)); 934 | } 935 | 936 | CAMLprim value 937 | PyFalse_wrapper(value unit) 938 | { 939 | CAMLparam1(unit); 940 | CAMLreturn(Val_int(CODE_FALSE)); 941 | } 942 | 943 | CAMLprim value 944 | PyTuple_Empty_wrapper(value unit) 945 | { 946 | CAMLparam1(unit); 947 | CAMLreturn(Val_int(CODE_TUPLE_EMPTY)); 948 | } 949 | 950 | enum pytype_labels { 951 | PyUnknown, 952 | Bool, 953 | Bytes, 954 | Callable, 955 | Capsule, 956 | Closure, 957 | Dict, 958 | Float, 959 | List, 960 | Int, 961 | Long, 962 | Module, 963 | NoneType, 964 | Null, 965 | Tuple, 966 | Type, 967 | Unicode, 968 | Iter, 969 | Set 970 | }; 971 | 972 | static bool is_iterable(PyObject *obj) { 973 | PyObject *iter = Python_PyObject_GetIter(obj); 974 | if (iter) { 975 | Py_DECREF(iter); 976 | return true; 977 | } else { 978 | Python_PyErr_Clear(); 979 | return false; 980 | } 981 | } 982 | 983 | CAMLprim value 984 | pytype(value object_ocaml) 985 | { 986 | CAMLparam1(object_ocaml); 987 | pyml_assert_initialized(); 988 | PyObject *object = pyml_unwrap(object_ocaml); 989 | if (!object) { 990 | CAMLreturn(Val_int(Null)); 991 | } 992 | PyObject *ob_type = pyobjectdescr(object)->ob_type; 993 | struct _typeobject *typeobj = (struct _typeobject *) pyobjectdescr(ob_type); 994 | unsigned long flags = typeobj->tp_flags; 995 | int result; 996 | if (ob_type == Python_PyBool_Type) { 997 | result = Bool; 998 | } 999 | else if (flags & Py_TPFLAGS_BYTES_SUBCLASS) { 1000 | result = Bytes; 1001 | } 1002 | else if (Python_PyCallable_Check(object)) { 1003 | result = Callable; 1004 | } 1005 | else if (Python27_PyCapsule_IsValid 1006 | && Python27_PyCapsule_IsValid(object, "ocaml-capsule")) { 1007 | result = Capsule; 1008 | } 1009 | else if (Python27_PyCapsule_IsValid 1010 | && Python27_PyCapsule_IsValid(object, "ocaml-closure")) { 1011 | result = Closure; 1012 | } 1013 | else if (flags & Py_TPFLAGS_DICT_SUBCLASS) { 1014 | result = Dict; 1015 | } 1016 | else if (ob_type == Python_PyFloat_Type || 1017 | Python_PyType_IsSubtype(ob_type, Python_PyFloat_Type)) { 1018 | result = Float; 1019 | } 1020 | else if (flags & Py_TPFLAGS_LIST_SUBCLASS) { 1021 | result = List; 1022 | } 1023 | else if (flags & Py_TPFLAGS_INT_SUBCLASS) { 1024 | result = Int; 1025 | } 1026 | else if (flags & Py_TPFLAGS_LONG_SUBCLASS) { 1027 | result = Long; 1028 | } 1029 | else if (ob_type == Python_PyModule_Type || 1030 | Python_PyType_IsSubtype(ob_type, Python_PyModule_Type)) { 1031 | result = Module; 1032 | } 1033 | else if (object == Python__Py_NoneStruct) { 1034 | result = NoneType; 1035 | } 1036 | else if (flags & Py_TPFLAGS_TUPLE_SUBCLASS) { 1037 | result = Tuple; 1038 | } 1039 | else if (flags & Py_TPFLAGS_TYPE_SUBCLASS) { 1040 | result = Type; 1041 | } 1042 | else if (flags & Py_TPFLAGS_UNICODE_SUBCLASS) { 1043 | result = Unicode; 1044 | } 1045 | else if (ob_type == Python_PySet_Type) { 1046 | result = Set; 1047 | } 1048 | else if (is_iterable(object)) { 1049 | result = Iter; 1050 | } 1051 | else { 1052 | result = PyUnknown; 1053 | } 1054 | CAMLreturn(Val_int(result)); 1055 | } 1056 | 1057 | CAMLprim value 1058 | PyObject_CallFunctionObjArgs_wrapper( 1059 | value callable_ocaml, value arguments_ocaml) 1060 | { 1061 | CAMLparam2(callable_ocaml, arguments_ocaml); 1062 | pyml_assert_initialized(); 1063 | PyObject *callable = pyml_unwrap(callable_ocaml); 1064 | PyObject *result; 1065 | mlsize_t argument_count = Wosize_val(arguments_ocaml); 1066 | switch (argument_count) { 1067 | case 0: 1068 | result = Python_PyObject_CallFunctionObjArgs(callable, NULL); 1069 | break; 1070 | case 1: 1071 | result = Python_PyObject_CallFunctionObjArgs 1072 | (callable, 1073 | pyml_unwrap(Field(arguments_ocaml, 0)), 1074 | NULL); 1075 | break; 1076 | case 2: 1077 | result = Python_PyObject_CallFunctionObjArgs 1078 | (callable, 1079 | pyml_unwrap(Field(arguments_ocaml, 0)), 1080 | pyml_unwrap(Field(arguments_ocaml, 1)), 1081 | NULL); 1082 | break; 1083 | case 3: 1084 | result = Python_PyObject_CallFunctionObjArgs 1085 | (callable, 1086 | pyml_unwrap(Field(arguments_ocaml, 0)), 1087 | pyml_unwrap(Field(arguments_ocaml, 1)), 1088 | pyml_unwrap(Field(arguments_ocaml, 2)), 1089 | NULL); 1090 | break; 1091 | case 4: 1092 | result = Python_PyObject_CallFunctionObjArgs 1093 | (callable, 1094 | pyml_unwrap(Field(arguments_ocaml, 0)), 1095 | pyml_unwrap(Field(arguments_ocaml, 1)), 1096 | pyml_unwrap(Field(arguments_ocaml, 2)), 1097 | pyml_unwrap(Field(arguments_ocaml, 3)), 1098 | NULL); 1099 | break; 1100 | case 5: 1101 | result = Python_PyObject_CallFunctionObjArgs 1102 | (callable, 1103 | pyml_unwrap(Field(arguments_ocaml, 0)), 1104 | pyml_unwrap(Field(arguments_ocaml, 1)), 1105 | pyml_unwrap(Field(arguments_ocaml, 2)), 1106 | pyml_unwrap(Field(arguments_ocaml, 3)), 1107 | pyml_unwrap(Field(arguments_ocaml, 4)), 1108 | NULL); 1109 | break; 1110 | default: 1111 | fprintf(stderr, 1112 | "PyObject_CallFunctionObjArgs_wrapper not implemented for more " 1113 | "than 5 arguments\n"); 1114 | exit(EXIT_FAILURE); 1115 | } 1116 | 1117 | CAMLreturn(pyml_wrap(result, true)); 1118 | } 1119 | 1120 | CAMLprim value 1121 | PyObject_CallMethodObjArgs_wrapper( 1122 | value object_ocaml, value name_ocaml, value arguments_ocaml) 1123 | { 1124 | CAMLparam3(object_ocaml, name_ocaml, arguments_ocaml); 1125 | pyml_assert_initialized(); 1126 | PyObject *object = pyml_unwrap(object_ocaml); 1127 | PyObject *name = pyml_unwrap(name_ocaml); 1128 | PyObject *result; 1129 | mlsize_t argument_count = Wosize_val(arguments_ocaml); 1130 | switch (argument_count) { 1131 | case 0: 1132 | result = Python_PyObject_CallMethodObjArgs(object, name, NULL); 1133 | break; 1134 | case 1: 1135 | result = Python_PyObject_CallMethodObjArgs 1136 | (object, name, 1137 | pyml_unwrap(Field(arguments_ocaml, 0)), 1138 | NULL); 1139 | break; 1140 | case 2: 1141 | result = Python_PyObject_CallMethodObjArgs 1142 | (object, name, 1143 | pyml_unwrap(Field(arguments_ocaml, 0)), 1144 | pyml_unwrap(Field(arguments_ocaml, 1)), 1145 | NULL); 1146 | break; 1147 | case 3: 1148 | result = Python_PyObject_CallMethodObjArgs 1149 | (object, name, 1150 | pyml_unwrap(Field(arguments_ocaml, 0)), 1151 | pyml_unwrap(Field(arguments_ocaml, 1)), 1152 | pyml_unwrap(Field(arguments_ocaml, 2)), 1153 | NULL); 1154 | break; 1155 | case 4: 1156 | result = Python_PyObject_CallMethodObjArgs 1157 | (object, name, 1158 | pyml_unwrap(Field(arguments_ocaml, 0)), 1159 | pyml_unwrap(Field(arguments_ocaml, 1)), 1160 | pyml_unwrap(Field(arguments_ocaml, 2)), 1161 | pyml_unwrap(Field(arguments_ocaml, 3)), 1162 | NULL); 1163 | break; 1164 | case 5: 1165 | result = Python_PyObject_CallMethodObjArgs 1166 | (object, name, 1167 | pyml_unwrap(Field(arguments_ocaml, 0)), 1168 | pyml_unwrap(Field(arguments_ocaml, 1)), 1169 | pyml_unwrap(Field(arguments_ocaml, 2)), 1170 | pyml_unwrap(Field(arguments_ocaml, 3)), 1171 | pyml_unwrap(Field(arguments_ocaml, 4)), 1172 | NULL); 1173 | break; 1174 | default: 1175 | fprintf(stderr, 1176 | "PyObject_CallMethodObjArgs_wrapper not implemented for more " 1177 | "than 5 arguments\n"); 1178 | exit(EXIT_FAILURE); 1179 | } 1180 | 1181 | CAMLreturn(pyml_wrap(result, true)); 1182 | } 1183 | 1184 | CAMLprim value 1185 | pyml_capsule_check(value v) 1186 | { 1187 | CAMLparam1(v); 1188 | pyml_assert_initialized(); 1189 | PyObject *o = pyml_unwrap(v); 1190 | PyObject *ob_type = pyobjectdescr(o)->ob_type; 1191 | int check_result = ob_type == Python_PyCapsule_Type; 1192 | CAMLreturn(Val_int(check_result)); 1193 | } 1194 | 1195 | CAMLprim value 1196 | pyml_wrap_value(value v) 1197 | { 1198 | CAMLparam1(v); 1199 | pyml_assert_initialized(); 1200 | PyObject *result = camlwrap_capsule(v, NULL, 0); 1201 | CAMLreturn(pyml_wrap(result, true)); 1202 | } 1203 | 1204 | CAMLprim value 1205 | pyml_unwrap_value(value x_ocaml) 1206 | { 1207 | CAMLparam1(x_ocaml); 1208 | CAMLlocal1(v); 1209 | pyml_assert_initialized(); 1210 | PyObject *x = pyml_unwrap(x_ocaml); 1211 | void *p = unwrap_capsule(x, "ocaml-capsule"); 1212 | if (!p) { 1213 | fprintf(stderr, "pyml_unwrap_value: type mismatch"); 1214 | exit(EXIT_FAILURE); 1215 | } 1216 | v = *(value *) p; 1217 | CAMLreturn(v); 1218 | } 1219 | 1220 | CAMLprim value 1221 | PyErr_Fetch_wrapper(value unit) 1222 | { 1223 | CAMLparam1(unit); 1224 | CAMLlocal1(result); 1225 | pyml_assert_initialized(); 1226 | PyObject *excType, *excValue, *excTraceback; 1227 | Python_PyErr_Fetch(&excType, &excValue, &excTraceback); 1228 | Python_PyErr_NormalizeException(&excType, &excValue, &excTraceback); 1229 | result = caml_alloc_tuple(3); 1230 | Store_field(result, 0, pyml_wrap(excType, false)); 1231 | Store_field(result, 1, pyml_wrap(excValue, false)); 1232 | Store_field(result, 2, pyml_wrap(excTraceback, false)); 1233 | CAMLreturn(result); 1234 | } 1235 | 1236 | // PyErr_Restore steals the references. 1237 | // https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Restore 1238 | // However the objects can be null, so we do not want to run Py_INCREF if 1239 | // this is the case as this would trigger some segfaults. 1240 | CAMLprim value 1241 | PyErr_Restore_wrapper(value arg0_ocaml, value arg1_ocaml, value arg2_ocaml) 1242 | { 1243 | CAMLparam3(arg0_ocaml, arg1_ocaml, arg2_ocaml); 1244 | pyml_assert_initialized(); 1245 | PyObject *arg0 = pyml_unwrap(arg0_ocaml); 1246 | if (arg0) Py_INCREF(arg0); 1247 | PyObject *arg1 = pyml_unwrap(arg1_ocaml); 1248 | if (arg1) Py_INCREF(arg1); 1249 | PyObject *arg2 = pyml_unwrap(arg2_ocaml); 1250 | if (arg2) Py_INCREF(arg2); 1251 | Python_PyErr_Restore(arg0, arg1, arg2); 1252 | CAMLreturn(Val_unit); 1253 | } 1254 | 1255 | 1256 | CAMLprim value 1257 | pyml_wrap_string_option(const char *s) 1258 | { 1259 | CAMLparam0(); 1260 | CAMLlocal1(result); 1261 | if (!s) { 1262 | CAMLreturn(Val_int(0)); 1263 | } 1264 | result = caml_alloc_tuple(1); 1265 | Store_field(result, 0, caml_copy_string(s)); 1266 | CAMLreturn(result); 1267 | } 1268 | 1269 | CAMLprim value 1270 | pyrefcount(value pyobj) 1271 | { 1272 | CAMLparam1(pyobj); 1273 | PyObject *obj = pyml_unwrap(pyobj); 1274 | CAMLreturn(Val_int(pyobjectdescr(obj)->ob_refcnt)); 1275 | } 1276 | 1277 | static value 1278 | pyml_wrap_wide_string(wchar_t *ws) 1279 | { 1280 | CAMLparam0(); 1281 | CAMLlocal1(result); 1282 | size_t n = wcstombs(NULL, ws, 0); 1283 | if (n == (size_t) -1) { 1284 | fprintf(stderr, "pyml_wrap_wide_string failure.\n"); 1285 | exit(EXIT_FAILURE); 1286 | } 1287 | char *s = xmalloc((n + 1) * sizeof (char)); 1288 | wcstombs(s, ws, n); 1289 | result = caml_copy_string(s); 1290 | free(s); 1291 | CAMLreturn(result); 1292 | } 1293 | 1294 | static wchar_t * 1295 | wide_string_of_string(const char *s) 1296 | { 1297 | size_t n = mbstowcs(NULL, s, 0); 1298 | if (n == (size_t) -1) { 1299 | fprintf(stderr, "wide_string_of_string failure.\n"); 1300 | exit(EXIT_FAILURE); 1301 | } 1302 | wchar_t *ws = xmalloc((n + 1) * sizeof (wchar_t)); 1303 | mbstowcs(ws, s, n + 1); 1304 | return ws; 1305 | } 1306 | 1307 | static wchar_t * 1308 | pyml_unwrap_wide_string(value string_ocaml) 1309 | { 1310 | CAMLparam1(string_ocaml); 1311 | wchar_t *ws = wide_string_of_string(String_val(string_ocaml)); 1312 | CAMLreturnT(wchar_t *, ws); 1313 | } 1314 | 1315 | static int16_t * 1316 | pyml_unwrap_ucs2(value array_ocaml) 1317 | { 1318 | CAMLparam1(array_ocaml); 1319 | mlsize_t len = Wosize_val(array_ocaml); 1320 | int16_t *result = xmalloc(len * sizeof(int16_t)); 1321 | size_t i; 1322 | for (i = 0; i < len; i++) { 1323 | result[i] = Field(array_ocaml, i); 1324 | } 1325 | CAMLreturnT(int16_t *, result); 1326 | } 1327 | 1328 | static int32_t * 1329 | pyml_unwrap_ucs4(value array_ocaml) 1330 | { 1331 | CAMLparam1(array_ocaml); 1332 | mlsize_t len = Wosize_val(array_ocaml); 1333 | int32_t *result = xmalloc(len * sizeof(int32_t)); 1334 | size_t i; 1335 | for (i = 0; i < len; i++) { 1336 | result[i] = Field(array_ocaml, i); 1337 | } 1338 | CAMLreturnT(int32_t *, result); 1339 | } 1340 | 1341 | static value 1342 | pyml_wrap_ucs2_option(int16_t *buffer) 1343 | { 1344 | CAMLparam0(); 1345 | CAMLlocal2(result, array); 1346 | mlsize_t len; 1347 | if (buffer == NULL) { 1348 | CAMLreturn(Val_int(0)); 1349 | } 1350 | len = 0; 1351 | while (buffer[len]) { 1352 | len++; 1353 | } 1354 | array = caml_alloc_tuple(len); 1355 | size_t i; 1356 | for (i = 0; i < len; i++) { 1357 | Store_field(array, i, buffer[i]); 1358 | } 1359 | result = caml_alloc_tuple(1); 1360 | Store_field(result, 0, array); 1361 | CAMLreturn(result); 1362 | } 1363 | 1364 | static value 1365 | pyml_wrap_ucs4_option_and_free(int32_t *buffer, bool free) 1366 | { 1367 | CAMLparam0(); 1368 | CAMLlocal2(result, array); 1369 | mlsize_t len; 1370 | if (buffer == NULL) { 1371 | CAMLreturn(Val_int(0)); 1372 | } 1373 | len = 0; 1374 | while (buffer[len]) { 1375 | len++; 1376 | } 1377 | array = caml_alloc_tuple(len); 1378 | size_t i; 1379 | for (i = 0; i < len; i++) { 1380 | Store_field(array, i, buffer[i]); 1381 | } 1382 | result = caml_alloc_tuple(1); 1383 | Store_field(result, 0, array); 1384 | if (free) { 1385 | Python_PyMem_Free(buffer); 1386 | } 1387 | CAMLreturn(result); 1388 | } 1389 | 1390 | #define StringAndSize_wrapper(func, byte_type) \ 1391 | CAMLprim value \ 1392 | func##_wrapper(value arg_ocaml) \ 1393 | { \ 1394 | CAMLparam1(arg_ocaml); \ 1395 | CAMLlocal2(result, string); \ 1396 | PyObject *arg = pyml_unwrap(arg_ocaml); \ 1397 | byte_type *buffer; \ 1398 | Py_ssize_t length; \ 1399 | int return_value; \ 1400 | return_value = Python_##func(arg, &buffer, &length); \ 1401 | if (return_value == -1) { \ 1402 | CAMLreturn(Val_int(0)); \ 1403 | } \ 1404 | string = caml_alloc_initialized_string(length, buffer); \ 1405 | result = caml_alloc_tuple(1); \ 1406 | Store_field(result, 0, string); \ 1407 | CAMLreturn(result); \ 1408 | } 1409 | 1410 | StringAndSize_wrapper(PyString_AsStringAndSize, char); 1411 | StringAndSize_wrapper(PyObject_AsCharBuffer, const char); 1412 | StringAndSize_wrapper(PyObject_AsReadBuffer, const void); 1413 | StringAndSize_wrapper(PyObject_AsWriteBuffer, void); 1414 | 1415 | static FILE * 1416 | open_file(value file, const char *mode) 1417 | { 1418 | CAMLparam1(file); 1419 | FILE *result; 1420 | if (Tag_val(file) == 0) { 1421 | const char *filename = String_val(Field(file, 0)); 1422 | if (Python__Py_fopen != NULL) { 1423 | result = Python__Py_fopen(filename, mode); 1424 | } 1425 | else if (Python__Py_wfopen != NULL) { 1426 | wchar_t *wide_filename = wide_string_of_string(filename); 1427 | wchar_t *wide_mode = wide_string_of_string(mode); 1428 | result = Python__Py_wfopen(wide_filename, wide_mode); 1429 | free(wide_mode); 1430 | free(wide_filename); 1431 | } 1432 | else { 1433 | result = fopen(filename, mode); 1434 | } 1435 | } 1436 | else { 1437 | result = file_of_file_descr(Field(file, 0), mode); 1438 | } 1439 | CAMLreturnT(FILE *, result); 1440 | } 1441 | 1442 | static void 1443 | close_file(value file, FILE *file_struct) 1444 | { 1445 | CAMLparam1(file); 1446 | fclose(file_struct); 1447 | CAMLreturn0; 1448 | } 1449 | 1450 | /* Numpy */ 1451 | 1452 | void ** 1453 | pyml_get_pyarray_api(PyObject *c_api) 1454 | { 1455 | if (version_major >= 3) { 1456 | return (void **)Python27_PyCapsule_GetPointer(c_api, NULL); 1457 | } 1458 | else { 1459 | return (void **)Python2_PyCObject_AsVoidPtr(c_api); 1460 | } 1461 | } 1462 | 1463 | CAMLprim value 1464 | get_pyarray_type(value numpy_api_ocaml) 1465 | { 1466 | CAMLparam1(numpy_api_ocaml); 1467 | PyObject *c_api = pyml_unwrap(numpy_api_ocaml); 1468 | void **PyArray_API = pyml_get_pyarray_api(c_api); 1469 | PyObject *result = PyArray_API[2]; 1470 | CAMLreturn(pyml_wrap(result, false)); 1471 | } 1472 | 1473 | CAMLprim value 1474 | pyarray_of_floatarray_wrapper( 1475 | value numpy_api_ocaml, value array_type_ocaml, value array_ocaml) 1476 | { 1477 | CAMLparam3(numpy_api_ocaml, array_type_ocaml, array_ocaml); 1478 | pyml_assert_initialized(); 1479 | PyObject *c_api = pyml_unwrap(numpy_api_ocaml); 1480 | void **PyArray_API = pyml_get_pyarray_api(c_api); 1481 | PyObject *(*PyArray_New) 1482 | (PyTypeObject *, int, npy_intp *, int, npy_intp *, void *, int, int, 1483 | PyObject *) = PyArray_API[93]; 1484 | npy_intp length = Wosize_val(array_ocaml); 1485 | #ifndef ARCH_SIXTYFOUR 1486 | length /= 2; 1487 | #endif 1488 | void *data = (double *) array_ocaml; 1489 | PyTypeObject (*PyArray_SubType) = 1490 | (PyTypeObject *) pyml_unwrap(array_type_ocaml); 1491 | PyObject *result = PyArray_New( 1492 | PyArray_SubType, 1, &length, NPY_DOUBLE, NULL, data, 0, 1493 | NPY_ARRAY_CARRAY, NULL); 1494 | CAMLreturn(pyml_wrap(result, true)); 1495 | } 1496 | 1497 | CAMLprim value 1498 | pyarray_move_floatarray_wrapper(value numpy_array_ocaml, value array_ocaml) 1499 | { 1500 | CAMLparam2(numpy_array_ocaml, array_ocaml); 1501 | pyml_assert_initialized(); 1502 | PyObject *numpy_array = pyml_unwrap(numpy_array_ocaml); 1503 | PyArrayObject_fields *fields = 1504 | (PyArrayObject_fields *) pyobjectdescr(numpy_array); 1505 | fields->data = (void *) array_ocaml; 1506 | CAMLreturn(Val_unit); 1507 | } 1508 | 1509 | CAMLprim value 1510 | PyLong_FromString_wrapper(value str_ocaml, value base_ocaml) 1511 | { 1512 | CAMLparam2(str_ocaml, base_ocaml); 1513 | CAMLlocal1(result); 1514 | pyml_assert_initialized(); 1515 | const char *str = String_val(str_ocaml); 1516 | const char *pend; 1517 | int base = Int_val(base_ocaml); 1518 | PyObject *l = Python_PyLong_FromString(str, &pend, base); 1519 | ssize_t len = pend - str; 1520 | result = caml_alloc_tuple(2); 1521 | Store_field(result, 0, pyml_wrap(l, true)); 1522 | Store_field(result, 1, Val_int(len)); 1523 | CAMLreturn(result); 1524 | } 1525 | 1526 | CAMLprim value 1527 | Python27_PyCapsule_IsValid_wrapper(value arg0_ocaml, value arg1_ocaml) 1528 | { 1529 | CAMLparam2(arg0_ocaml, arg1_ocaml); 1530 | 1531 | pyml_assert_initialized(); 1532 | if (!Python27_PyCapsule_IsValid) { 1533 | caml_failwith("PyCapsule_IsValid is only available in Python >2.7"); 1534 | } 1535 | PyObject *arg0 = pyml_unwrap(arg0_ocaml); 1536 | const char *arg1 = String_val(arg1_ocaml); 1537 | int result = Python27_PyCapsule_IsValid(arg0, arg1); 1538 | CAMLreturn(Val_int(result)); 1539 | } 1540 | 1541 | CAMLprim value 1542 | pyml_pyframe_new(value filename_ocaml, value funcname_ocaml, value lineno_ocaml) { 1543 | CAMLparam3(filename_ocaml, funcname_ocaml, lineno_ocaml); 1544 | const char *filename = String_val(filename_ocaml); 1545 | const char *funcname = String_val(funcname_ocaml); 1546 | int lineno = Int_val(lineno_ocaml); 1547 | PyObject *code = Python_PyCode_NewEmpty(filename, funcname, lineno); 1548 | PyObject *globals = Python_PyDict_New(); 1549 | PyObject *result = Python_PyFrame_New( 1550 | Python_PyThreadState_Get(), 1551 | code, 1552 | globals, 1553 | NULL); 1554 | Py_DECREF(code); 1555 | Py_DECREF(globals); 1556 | CAMLreturn(pyml_wrap(result, true)); 1557 | } 1558 | 1559 | #include "pyml_wrappers.inc" 1560 | --------------------------------------------------------------------------------