/
wrapgen.clj
215 lines (190 loc) · 12.8 KB
/
wrapgen.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
(ns chromex.wrapgen
(:require [chromex.config :refer [get-static-config]]
[chromex.support :refer [gen-call-hook
gen-logging-if-verbose
gen-missing-api-check
get-api-id
get-item-by-id
print-debug]]
[clojure.string :as string]))
; This file is responsible for generating code wrapping Chrome API calls.
; Each Chrome API method will get generated one representing ClojureScript stub method (stubs have star postfix).
;
; The gen-wrap-from-table must be called at compile time from a macro and it generates code in 3 layers:
;
; 1) generates code converting callbacks to channels (gen-callback-function-wrap)
; 2) generates code performing marshalling (gen-marshalling)
; 3) generates code performing logging (gen-logging-if-verbose)
; 4) generates code performing actual call/access of native Chrome API (gen-api-access-or-call)
;
; Some implementation notes:
;
; * Generated code slightly differs for different API method types (property, function, event).
; * Generated code also does some sanity checking (optional). See :elide* keys in static config.
; * Marshalling has to deal not only with input parameters and return value, but also arguments passed into callbacks.
; * Logging has to be performed not only before actual Chrome API call, but also in callbacks (wrap-callback-with-logging).
; * Generated code must use string names when doing Javascript interop to be compatible with advanced compilation:
; https://github.com/binaryage/chromex/#advanced-mode-compilation
; * Chrome API supports optional arguments, but we rely on argument positions for marshalling. That is why we introduced
; a special parameter value :omit, which marks arguments which should be omitted from final native API call.
; omitting arguments is done during runtime, see method `prepare-final-args-array`.
; Note: for convenience we generate arities of API methods with trailing optional arguments omitted,
; for example see `connect` macro in https://github.com/binaryage/chromex/blob/master/src/exts/chromex/ext/runtime.clj
;
; ---------------------------------------------------------------------------------------------------------------------------
(defn wrap-callback-with-logging [static-config label api config [callback-sym callback-info]]
(let [{:keys [params]} callback-info
param-syms (map #(gensym (str "cb-param-" (:name %) "-")) params)]
`(fn [~@param-syms]
~(gen-logging-if-verbose static-config config label api `(into-array [~@param-syms]))
(~callback-sym ~@param-syms))))
(defn wrap-callback-args-with-logging [static-config api config args params]
(assert (= (count params) (count args))
(str "a mismatch between parameters and arguments passed into wrap-callback-args-with-logging\n"
"api: " api "\n"
"params: " params "\n"
"args: " args))
(for [[callback-sym callback-info] (partition 2 (interleave args (map :callback-info params)))]
(if callback-info ; only callback params have callback info,
(wrap-callback-with-logging static-config "callback:" api config [callback-sym callback-info])
callback-sym)))
; ---------------------------------------------------------------------------------------------------------------------------
(defn gen-api-access-or-call [static-config api-table descriptor config & args]
(let [{:keys [namespace]} api-table
{:keys [name params property?]} descriptor
api (get-api-id api-table descriptor)
namespace-path (string/split namespace #"\.")
wrapped-args (wrap-callback-args-with-logging static-config api config args params)
param-names (map :name params)
param-optionalities (map :optional? params)
arg-descriptors (vec (map vec (partition 3 (interleave wrapped-args param-names param-optionalities))))
operation (if property? "accessing:" "calling:")
final-args-array-sym (gensym "final-args-array-")
ns-sym (gensym "ns-")
missing-api-sym (gensym "missing-api-")
target-sym (gensym "target-")]
`(let [~final-args-array-sym (chromex.support/prepare-final-args-array ~arg-descriptors ~api)
~ns-sym (oops.core/oget (:root ~config) ~@namespace-path)
~missing-api-sym ~(if-not property?
(gen-missing-api-check static-config config api ns-sym name))]
(when-not (true? ~missing-api-sym) ; don't do anything if missing, gen-missing-api-check should report the trouble
~(gen-logging-if-verbose static-config config operation api final-args-array-sym)
(let [~target-sym (oops.core/oget ~ns-sym ~(str "?" name))]
~(if property?
target-sym
`(.apply ~target-sym ~ns-sym ~final-args-array-sym)))))))
; ---------------------------------------------------------------------------------------------------------------------------
(defn marshall [static-config config & args]
(let [marshaller (:gen-marshalling static-config)]
(assert (and marshaller (fn? marshaller))
(str "invalid :gen-marshalling in static-config\n"
"static-config: " static-config))
(let [marshalled-code (apply marshaller config args)]
(if (:debug-marshalling static-config)
(print-debug (str "marshalling request " args " => " marshalled-code)))
marshalled-code)))
(defn marshall-callback-param [static-config config api [callback-param-sym type]]
(marshall static-config config :from-chrome api type callback-param-sym))
(defn marshall-callback [static-config config api [callback-sym callback-info]]
(let [{:keys [params]} callback-info
param-syms (map #(gensym (str "cb-" (:name %) "-")) params)
param-types (map :type params)
sym+type-pairs (partition 2 (interleave param-syms param-types))
marshalled-params (map (partial marshall-callback-param static-config config api) sym+type-pairs)]
(if (empty? param-syms)
callback-sym ; a special case of callback with no parameters, no marshalling needed
`(fn [~@param-syms] (~callback-sym ~@marshalled-params)))))
(defn marshall-result [static-config config api [result-sym type]]
(marshall static-config config :from-chrome api type result-sym))
(defn marshall-param [static-config config api [param-sym type]]
(let [sym (gensym "omit-test-")]
`(let [~sym ~param-sym]
(if (cljs.core/keyword-identical? ~sym :omit)
:omit
~(marshall static-config config :to-chrome api type sym)))))
(defn marshall-function-param [static-config config api [sym param]]
(let [{:keys [name type]} param
callback-api (str api ".callback")]
(assert name (str "parameter has missing 'name': " api " " param))
(assert type (str "parameter has missing 'type': " api " " param))
(if (= type :callback)
(marshall-callback static-config config callback-api [sym (:callback param)])
(marshall-param static-config config api [sym type]))))
(defn marshall-function-params [static-config config api args params]
(assert (= (count params) (count args))
(str "a mismatch between parameters and arguments passed into marshall-function-params\n"
"api: " api "\n"
"args: " args
"params:" params))
(for [arg+param (partition 2 (interleave args params))]
(marshall-function-param static-config config api arg+param)))
(defn gen-marshalling [static-config api-table descriptor config & args]
(let [api (get-api-id api-table descriptor)
{:keys [params return-type]} descriptor
marshalled-params (marshall-function-params static-config config api args params)
marshalled-param-syms (map #(gensym (str "marshalled-" (:name %) "-")) params)
result-sym (gensym "result-")]
`(let [~@(interleave marshalled-param-syms marshalled-params)
~result-sym ~(apply gen-api-access-or-call static-config api-table descriptor config marshalled-param-syms)]
~(marshall-result static-config config api [result-sym return-type]))))
; ---------------------------------------------------------------------------------------------------------------------------
(defn gen-event [static-config api-table descriptor config & [chan extra-args]]
(let [api (get-api-id api-table descriptor)
event-id (:id descriptor)
event-fn-sym (gensym "event-fn-")
handler-fn-sym (gensym "handler-fn-")
logging-fn-sym (gensym "logging-fn-")
event-obj-sym (gensym "event-obj-")
result-sym (gensym "result-")
ns-obj-sym (gensym "ns-obj-")
event-path (string/split api #"\.")
ns-path (butlast event-path)
missing-api-sym (gensym "missing-api-")
event-key (last event-path)]
`(let [~event-fn-sym ~(gen-call-hook config :event-listener-factory event-id chan)
~handler-fn-sym ~(marshall-callback static-config config (str api ".handler") [event-fn-sym descriptor])
~logging-fn-sym ~(wrap-callback-with-logging static-config "event:" api config [handler-fn-sym descriptor])
~ns-obj-sym (oops.core/oget (:root ~config) ~@ns-path)
~missing-api-sym ~(gen-missing-api-check static-config config api ns-obj-sym event-key)]
(when-not (true? ~missing-api-sym) ; don't do anything if missing, gen-missing-api-check should report the trouble
(let [~event-obj-sym (oops.core/oget ~ns-obj-sym ~event-key)
~result-sym (chromex.chrome-event-subscription/make-chrome-event-subscription ~event-obj-sym ~logging-fn-sym ~chan)]
(chromex.protocols.chrome-event-subscription/subscribe! ~result-sym ~extra-args)
~result-sym)))))
; ---------------------------------------------------------------------------------------------------------------------------
(defn gen-callback-function-wrap [static-config api-table descriptor config & args]
(let [callback-chan-sym (gensym "callback-chan-")
callback-fn (gen-call-hook config :callback-fn-factory descriptor callback-chan-sym)
args+callback (concat args [callback-fn])
marshalled-call-with-callback (apply gen-marshalling static-config api-table descriptor config args+callback)]
`(let [~callback-chan-sym ~(gen-call-hook config :callback-channel-factory)]
~marshalled-call-with-callback
~callback-chan-sym)))
(defn gen-plain-function-wrap [static-config api-table descriptor config & args]
(apply gen-marshalling static-config api-table descriptor config args))
(defn gen-function-wrap [static-config api-table item-id config & args]
(let [descriptor (get-item-by-id item-id (:functions api-table))
_ (assert descriptor (str "unable to find function with id " item-id " in:\n" api-table))
tagged-descriptor (assoc descriptor :function? true)]
(if (:callback? descriptor)
(apply gen-callback-function-wrap static-config api-table tagged-descriptor config args)
(apply gen-plain-function-wrap static-config api-table tagged-descriptor config args))))
(defn gen-property-wrap [static-config api-table item-id config & args]
(let [descriptor (get-item-by-id item-id (:properties api-table))
_ (assert descriptor (str "unable to find property with id " item-id " in:\n" api-table))
tagged-descriptor (assoc descriptor :property? true)]
(apply gen-marshalling static-config api-table tagged-descriptor config args)))
(defn gen-event-wrap [static-config api-table item-id config & args]
(let [descriptor (get-item-by-id item-id (:events api-table))
_ (assert descriptor (str "unable to find event with id " item-id " in:\n" api-table))
tagged-descriptor (assoc descriptor :event? true)]
(apply gen-event static-config api-table tagged-descriptor config args)))
; ---------------------------------------------------------------------------------------------------------------------------
(defn gen-wrap-from-table [static-config api-table kind item-id config & args]
(case kind
:function (apply gen-function-wrap static-config api-table item-id config args)
:property (apply gen-property-wrap static-config api-table item-id config args)
:event (apply gen-event-wrap static-config api-table item-id config args)))
(defn gen-wrap-helper [api-table kind item-id config & args]
(let [static-config (get-static-config)]
(apply gen-wrap-from-table static-config api-table kind item-id config args)))