Skip to content

Extremely rudimentary auto-generated Clojure wrapper. (DO NOT MERGE) #338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
(ns rx.lang.clojure.interop
"Functions an macros for instantiating rx Func* and Action* interfaces."
(:refer-clojure :exclude [fn]))
(:refer-clojure :exclude [fn])
(:require [clojure.reflect :as r])
)

(def ^:private -ns- *ns*)
(set! *warn-on-reflection* true)
Expand Down Expand Up @@ -100,3 +102,129 @@
(meta &form)))

;################################################################################

(defn ^:private dashify
[name]
(let [s (str name)
gsub (clojure.core/fn [s re sub] (.replaceAll (re-matcher re s) sub))]
(-> s
(gsub #"([A-Z]+)([A-Z][a-z])" "$1-$2")
(gsub #"([a-z]+)([A-Z])" "$1-$2")
(.replace "_" "-")
(clojure.string/lower-case)
symbol)))

(defn ^:private wrap-parameter
[parameter-name parameter-type]
(let [[parameter-type varargs?] (if (.endsWith (str parameter-type) "<>")
[(symbol (.replace (str parameter-type) "<>" "")) true]
[parameter-type false])
param-class (if parameter-type (resolve parameter-type))]
;(binding [*out* *err*] (println "HERE " parameter-name "->" (pr-str param-class)))
(cond
varargs? `(into-array ~param-class ~parameter-name)
(nil? param-class) parameter-name
(var? param-class) parameter-name
(.isAssignableFrom rx.util.functions.Action param-class) `(rx.lang.clojure.interop/action* ~parameter-name)

(.isAssignableFrom rx.util.functions.Function param-class) `(rx.lang.clojure.interop/fn* ~parameter-name)

:else parameter-name )))

(defn ^:private wrap-method
[class-name {:keys [name parameter-types flags] :as method}]
;(binding [*out* *err*](println "M -> " (pr-str method)))
(let [static? (:static flags)
this (if static? class-name 'this)
param-names (->> parameter-types
count
range
(map #(symbol (str "p" %))))
param-types (zipmap param-names parameter-types)
formal-params (if static?
param-names
(cons this param-names))
call-params (mapv #(wrap-parameter % (param-types %)) param-names)]
`(~(vec formal-params)
(. ~this ~name ~@call-params))))

(defn ^:private count-params
[method]
(+ (-> method :parameter-types count)
(if (-> method :flags :static)
0
1)))

(defn ^:private wrap-method-group
"Generate a defn for a bunch of arities"
[class-name [name methods]]
`(defn ~name
~@(map (partial wrap-method class-name) methods)))

(defn ^:private split-fixed-args-methods
[name methods]
; For now, only generate functions for overloads with different arities
(let [groups (group-by count-params methods)
keepers (->> groups
(filter #(= 1 (count (val %))))
(mapcat val)
seq)]
(println "Skipping ambiguous overloads: " name "," (- (count methods) (count keepers)))
(when keepers
[[(dashify name) keepers]])))

(defn ^:private split-method-group
[[name methods]]
(let [{:keys [varargs fixedargs]} (group-by (clojure.core/fn [m] (if (-> m :flags :varargs)
:varargs
:fixedargs))
methods)]
; we may generate several different functions depending on what's found
(concat (condp = (count varargs)
0 nil
1 [[(symbol (str (str (dashify name)) "-&")) varargs]]
(println "Skipping multi-arity varargs method: " varargs))
; if every overload has a distinct number of params...
(if (or (= 1 (count fixedargs))
(and (not-empty fixedargs)
(= (count fixedargs)
(count (set (map count-params fixedargs))))))
[[(dashify name) fixedargs]]
; otherwise, see what we can do
(split-fixed-args-methods name fixedargs)))))

(defn ^:private wrap-class*
[^Class class]
(let [class-sym (symbol (.getName class))
methods (->> (:members (r/reflect class))
(filter (comp :public :flags))
(group-by :name)
(sort-by key))]
(->> methods
(mapcat split-method-group)
(map (clojure.core/fn [group]
(wrap-method-group class-sym group)))
(filter identity))))

(defmacro wrap-class
"Given a class, def a wrapper function for each public method in the class.

Does the following:

* Dashifies the name of the method, i.e. mapMany -> map-many
* If an argument of the method is an rx Func or Action, assumes the argument
will be a Clojure Ifn and automaticall wraps it appropriately
* If the method takes varargs, the name of the method will have a -& suffix and will
take a seq as an argument, automatically converting it to an array as needed.
* Method overloads only distinguishable by argument type (i.e. same arity) are
not wrapped. THIS IS THE TRICKY PART.

Note that this may def functions that conflict with those in clojure.core so
clojure.core should not be referred into the current ns.

"
[class]
`(do ~@(wrap-class* (resolve class))))

(comment (doseq [m (wrap-class* rx.Observable)] (println m)))

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(in-ns 'rx.lang.clojure.observable)
(clojure.core/require 'clojure.core
'[rx.lang.clojure.interop :as interop])

(interop/wrap-class rx.Observable)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(ns rx.lang.clojure.observable-test
(:require [rx.lang.clojure.observable :as rx]
[clojure.test :refer [deftest testing is]]))

(deftest test-exercise-generated-wrapper
(is (= 12 (-> (rx/just 6)
(rx/map #(* 2 %))
rx.observables.BlockingObservable/single)))

(is (= (+ 1 4 9 16 25)
(-> (rx/from-& [1 2 3 4 5])
(rx/map (fn [v] (* v v)))
(rx/reduce +)
rx.observables.BlockingObservable/single)))

(is (= [1 2 3]
(-> (rx/from-& [3 2 1])
rx/to-sorted-list
rx.observables.BlockingObservable/single)))

(is (= [1 2 3 4 5 6]
(-> (rx/concat-& [(rx/from-& [1 2 3]) (rx/from-& [4 5 6])])
rx/to-list
rx.observables.BlockingObservable/single)))

(is (= [{:a 1 :b 2 :c 3} {:a 4 :b 5 :c 6} {:a 7 :b 8 :c 9}]
(-> (rx/zip (rx/from-& [1 4 7]) (rx/from-& [2 5 8]) (rx/from-& [3 6 9])
(fn [a b c] {:a a :b b :c c}))
rx/to-list
rx.observables.BlockingObservable/single))))