|
9 | 9 | [libpython-clj2.python.base :as py-base]
|
10 | 10 | [libpython-clj2.python.protocols :as py-proto]
|
11 | 11 | [libpython-clj2.python.gc :as pygc]
|
| 12 | + [libpython-clj2.python.copy :as py-copy] |
12 | 13 | [tech.v3.datatype.ffi :as dt-ffi]
|
13 | 14 | [tech.v3.datatype.ffi.size-t :as ffi-size-t]
|
14 | 15 | [tech.v3.datatype.struct :as dt-struct]
|
| 16 | + [tech.v3.datatype :as dtype] |
| 17 | + [tech.v3.datatype.protocols :as dt-proto] |
15 | 18 | [clojure.tools.logging :as log]
|
16 | 19 | [clojure.stacktrace :as st])
|
17 |
| - (:import [tech.v3.datatype.ffi Pointer])) |
| 20 | + (:import [tech.v3.datatype.ffi Pointer] |
| 21 | + [java.util Map Set] |
| 22 | + [libpython_clj2.python.protocols PBridgeToPython])) |
18 | 23 |
|
19 | 24 | (set! *warn-on-reflection* true)
|
20 | 25 |
|
|
26 | 31 | {:name :ml_doc :datatype (ffi-size-t/ptr-t-type)}]))
|
27 | 32 |
|
28 | 33 |
|
29 |
| -(def tuple-fn-iface* (delay (dt-ffi/define-foreign-interface :pointer? [:pointer :pointer]))) |
| 34 | +(def tuple-fn-iface* |
| 35 | + (delay (dt-ffi/define-foreign-interface :pointer? [:pointer :pointer]))) |
| 36 | +(def kw-fn-iface* |
| 37 | + (delay (dt-ffi/define-foreign-interface :pointer? [:pointer :pointer :pointer]))) |
30 | 38 |
|
31 | 39 |
|
32 | 40 | (def ^{:tag 'long} METH_VARARGS 0x0001)
|
|
35 | 43 | (def ^{:tag 'long} METH_NOARGS 0x0004)
|
36 | 44 |
|
37 | 45 |
|
| 46 | +(defn- internal-make-py-c-fn |
| 47 | + [ifn fn-iface raw-arg-converter meth-type |
| 48 | + {:keys [name doc result-converter] |
| 49 | + :or {name "_unamed" |
| 50 | + doc "no documentation provided"}}] |
| 51 | + (py-ffi/with-gil |
| 52 | + (let [fn-inst |
| 53 | + (dt-ffi/instantiate-foreign-interface |
| 54 | + fn-iface |
| 55 | + (fn [self tuple-args & [kw-args]] |
| 56 | + (try |
| 57 | + (let [retval (apply ifn (raw-arg-converter tuple-args kw-args))] |
| 58 | + (if result-converter |
| 59 | + (py-ffi/untracked->python retval result-converter) |
| 60 | + retval)) |
| 61 | + (catch Throwable e |
| 62 | + (log/error e "Error executing clojure function.") |
| 63 | + (py-ffi/PyErr_SetString |
| 64 | + (py-ffi/py-exc-type) |
| 65 | + (format "%s:%s" e (with-out-str |
| 66 | + (st/print-stack-trace e)))))))) |
| 67 | + fn-ptr (dt-ffi/foreign-interface-instance->c fn-iface fn-inst) |
| 68 | + ;;no resource tracking - we leak the struct |
| 69 | + method-def (dt-struct/new-struct :pymethoddef {:resource-type nil |
| 70 | + :container-type :native-heap}) |
| 71 | + name (dt-ffi/string->c name {:resource-type nil}) |
| 72 | + doc (dt-ffi/string->c doc {:resource-type nil})] |
| 73 | + (.put method-def :ml_name (.address (dt-ffi/->pointer name))) |
| 74 | + (.put method-def :ml_meth (.address (dt-ffi/->pointer fn-ptr))) |
| 75 | + (.put method-def :ml_flags meth-type) |
| 76 | + (.put method-def :ml_doc (.address (dt-ffi/->pointer doc))) |
| 77 | + ;;the method def cannot ever go out of scope |
| 78 | + (py-ffi/retain-forever (gensym) {:md method-def |
| 79 | + :name name |
| 80 | + :doc doc |
| 81 | + :fn-ptr fn-ptr |
| 82 | + :fn-inst fn-inst}) |
| 83 | + ;;no self, no module reference |
| 84 | + (-> (py-ffi/PyCFunction_NewEx method-def nil nil) |
| 85 | + (py-ffi/track-pyobject))))) |
| 86 | + |
| 87 | + |
| 88 | +(defn raw-tuple-arg-converter |
| 89 | + [arg-converter tuple-args kw-args] |
| 90 | + ;;no kw arguments |
| 91 | + (->> (range (py-ffi/PyTuple_Size tuple-args)) |
| 92 | + (mapv (fn [idx] |
| 93 | + (-> (py-ffi/PyTuple_GetItem tuple-args idx) |
| 94 | + (arg-converter)))))) |
| 95 | + |
| 96 | + |
| 97 | +(defn bridged-fn-arg->python |
| 98 | + "Slightly clever so we can pass ranges and such as function arguments." |
| 99 | + ([item opts] |
| 100 | + (cond |
| 101 | + (instance? PBridgeToPython item) |
| 102 | + (py-proto/as-python item opts) |
| 103 | + (dt-proto/convertible-to-range? item) |
| 104 | + (py-copy/->py-range item) |
| 105 | + (dtype/reader? item) |
| 106 | + (py-proto/->python (dtype/->reader item) opts) |
| 107 | + ;;There is one more case here for iterables that aren't anything else - |
| 108 | + ;; - specifically for sequences. |
| 109 | + (and (instance? Iterable item) |
| 110 | + (not (instance? Map item)) |
| 111 | + (not (instance? String item)) |
| 112 | + (not (instance? Set item))) |
| 113 | + (py-proto/as-python item opts) |
| 114 | + :else |
| 115 | + (py-base/->python item opts))) |
| 116 | + ([item] |
| 117 | + (bridged-fn-arg->python item nil))) |
| 118 | + |
| 119 | + |
| 120 | +(defn convert-kw-args |
| 121 | + [{:keys [arg-converter] :as options} tuple-args kw-args] |
| 122 | + [(raw-tuple-arg-converter arg-converter tuple-args nil) |
| 123 | + (->> (py-proto/as-jvm kw-args options) |
| 124 | + (into {}))]) |
| 125 | + |
| 126 | + |
38 | 127 | (defn make-tuple-fn
|
39 | 128 | ([ifn {:keys [arg-converter
|
40 | 129 | result-converter
|
41 | 130 | name doc]
|
42 | 131 | :or {arg-converter py-base/->jvm
|
43 | 132 | result-converter py-base/->python
|
44 | 133 | name "_unamed"
|
45 |
| - doc "no documentation provided"}}] |
46 |
| - (py-ffi/with-gil |
47 |
| - (let [arg-converter (or arg-converter identity) |
48 |
| - fn-inst |
49 |
| - (dt-ffi/instantiate-foreign-interface |
50 |
| - @tuple-fn-iface* |
51 |
| - (fn [self tuple-args] |
52 |
| - (try |
53 |
| - (let [retval |
54 |
| - (apply ifn |
55 |
| - (->> (range (py-ffi/PyTuple_Size tuple-args)) |
56 |
| - (map (fn [idx] |
57 |
| - (-> (py-ffi/PyTuple_GetItem tuple-args idx) |
58 |
| - (arg-converter))))))] |
59 |
| - (if result-converter |
60 |
| - (py-ffi/untracked->python retval result-converter) |
61 |
| - retval)) |
62 |
| - (catch Throwable e |
63 |
| - (log/error e "Error executing clojure function.") |
64 |
| - (py-ffi/PyErr_SetString |
65 |
| - (py-ffi/py-exc-type) |
66 |
| - (format "%s:%s" e (with-out-str |
67 |
| - (st/print-stack-trace e)))))))) |
68 |
| - fn-ptr (dt-ffi/foreign-interface-instance->c @tuple-fn-iface* fn-inst) |
69 |
| - ;;no resource tracking - we leak the struct |
70 |
| - method-def (dt-struct/new-struct :pymethoddef {:resource-type nil |
71 |
| - :container-type :native-heap}) |
72 |
| - name (dt-ffi/string->c name {:resource-type nil}) |
73 |
| - doc (dt-ffi/string->c doc {:resource-type nil})] |
74 |
| - (.put method-def :ml_name (.address (dt-ffi/->pointer name))) |
75 |
| - (.put method-def :ml_meth (.address (dt-ffi/->pointer fn-ptr))) |
76 |
| - (.put method-def :ml_flags METH_VARARGS) |
77 |
| - (.put method-def :ml_doc (.address (dt-ffi/->pointer doc))) |
78 |
| - ;;the method def cannot ever go out of scope |
79 |
| - (py-ffi/retain-forever (gensym) {:md method-def |
80 |
| - :name name |
81 |
| - :doc doc |
82 |
| - :fn-ptr fn-ptr |
83 |
| - :fn-inst fn-inst}) |
84 |
| - ;;no self, no module reference |
85 |
| - (-> (py-ffi/PyCFunction_NewEx method-def nil nil) |
86 |
| - (py-ffi/track-pyobject))))) |
| 134 | + doc "no documentation provided"} |
| 135 | + :as options}] |
| 136 | + (let [arg-converter (or arg-converter identity) |
| 137 | + ;;apply defaults to options map. |
| 138 | + options (assoc options |
| 139 | + :arg-converter arg-converter |
| 140 | + :result-converter result-converter |
| 141 | + :name name |
| 142 | + :doc doc)] |
| 143 | + (internal-make-py-c-fn ifn @tuple-fn-iface* |
| 144 | + #(raw-tuple-arg-converter arg-converter %1 %2) |
| 145 | + METH_VARARGS |
| 146 | + options))) |
87 | 147 | ([ifn]
|
88 | 148 | (make-tuple-fn ifn nil)))
|
89 | 149 |
|
90 | 150 |
|
| 151 | +(defn make-kw-fn |
| 152 | + ([ifn {:keys [arg-converter |
| 153 | + result-converter |
| 154 | + name doc |
| 155 | + kw-arg-converter] |
| 156 | + :or {arg-converter py-base/->jvm |
| 157 | + result-converter py-base/->python |
| 158 | + name "_unamed" |
| 159 | + doc "no documentation provided"} |
| 160 | + :as options}] |
| 161 | + (let [arg-converter (or arg-converter :identity) |
| 162 | + options (assoc options |
| 163 | + :arg-converter arg-converter |
| 164 | + :result-converter result-converter |
| 165 | + :name name |
| 166 | + :doc doc) |
| 167 | + kw-arg-converter (or kw-arg-converter #(convert-kw-args options %1 %2))] |
| 168 | + (internal-make-py-c-fn ifn @kw-fn-iface* |
| 169 | + kw-arg-converter |
| 170 | + (bit-or METH_VARARGS METH_KEYWORDS) |
| 171 | + options))) |
| 172 | + ([ifn] |
| 173 | + (make-kw-fn ifn nil))) |
| 174 | + |
| 175 | + |
91 | 176 | (defn call-py-fn
|
92 | 177 | [callable arglist kw-arg-map arg-converter]
|
93 | 178 | (py-ffi/with-gil
|
|
0 commit comments