Skip to content

Commit 441f776

Browse files
authored
[#401] Support implementing IDeref, IAtom, IAtom2 (and CLJS equivalents)
1 parent 81a8d48 commit 441f776

10 files changed

+408
-37
lines changed

src/sci/impl/analyzer.cljc

+6-5
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@
7979
(when (when call? (get macros sym))
8080
[sym sym])
8181
(when-let [c (interop/resolve-class ctx sym)]
82-
[sym c])))))))
82+
[sym c])
83+
;; resolves record or protocol referenced as class
84+
;; e.g. clojure.lang.IDeref which is really a var in clojure.lang/IDeref
85+
(when-let [x (records/resolve-record-or-protocol-class ctx sym)]
86+
[sym x])))))))
8387

8488
(defn tag [_ctx expr]
8589
(when-let [m (meta expr)]
@@ -562,10 +566,7 @@
562566
(throw-error-with-location (str "Unable to resolve classname: " class-sym) class-sym))))
563567

564568
(defn expand-constructor [ctx [constructor-sym & args]]
565-
(let [;; TODO:
566-
;; here it strips the namespace, which is correct in the case of
567-
;; js/Error. but not in clj
568-
constructor-name (name constructor-sym)
569+
(let [constructor-name (name constructor-sym)
569570
class-sym (with-meta (symbol (subs constructor-name 0
570571
(dec (count constructor-name))))
571572
(meta constructor-sym))]

src/sci/impl/core_protocols.cljc

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
(ns sci.impl.core-protocols
2+
(:refer-clojure :exclude [deref -deref -swap! -reset!])
3+
(:require [sci.impl.types :as types]
4+
[sci.impl.vars :as vars]))
5+
6+
7+
;;;; IDeref
8+
9+
(defmulti #?(:clj deref :cljs -deref) types/type-impl)
10+
11+
(defmethod #?(:clj deref :cljs -deref) :sci.impl.protocols/reified [ref]
12+
(let [methods (types/getMethods ref)]
13+
((get methods #?(:clj 'deref :cljs '-deref)) ref)))
14+
15+
(defmethod #?(:clj deref :cljs -deref) :default [ref]
16+
(clojure.core/deref ref))
17+
18+
(defn deref*
19+
([x]
20+
#?(:clj (deref x)
21+
:cljs (-deref x)))
22+
#?(:clj
23+
([x & args]
24+
(apply clojure.core/deref x args))))
25+
26+
#?(:clj
27+
(def clj-lang-ns (vars/->SciNamespace 'clojure.lang nil)))
28+
#?(:cljs
29+
(def cljs-core-ns (vars/->SciNamespace 'cljs.core nil)))
30+
31+
(def deref-protocol
32+
#?(:clj
33+
(vars/new-var
34+
'clojure.lang.IDeref
35+
{:class clojure.lang.IDeref
36+
:methods #{deref}
37+
:ns clj-lang-ns}
38+
{:ns clj-lang-ns})
39+
:cljs
40+
(vars/new-var
41+
'cljs.core.IDeref
42+
{:methods #{-deref}
43+
:ns cljs-core-ns}
44+
{:ns cljs-core-ns})))
45+
46+
;;;; end IDeref
47+
48+
;;;; IAtom
49+
50+
;; ;; You can use multiarity in multimethods
51+
;; (defmulti foo (fn [x & _] x))
52+
53+
;; (defmethod foo :default [_ & _] "DEFAULT VALUE DISPACHED")
54+
55+
;; ;; Like a standar multi-arity function
56+
;; (defmethod foo :bar
57+
;; ([_ _] "ONE ARGUMENT")
58+
;; ([_ _ _] "TWO ARGUMENTs")
59+
;; ([_ _ _ _] "THREE ARGUMENTs")
60+
;; ([_ _ _ _ & more] (cl-format nil "~d ARGUMENTS" (+ 3 (count more)))))
61+
62+
(defmulti #?(:clj swap :cljs -swap!) types/type-impl)
63+
(defmulti #?(:clj reset :cljs -reset!) types/type-impl)
64+
#?(:clj (defmulti compareAndSet types/type-impl))
65+
#?(:clj (defmulti swapVals types/type-impl))
66+
#?(:clj (defmulti resetVals types/type-impl))
67+
68+
;;;; Protocol methods
69+
70+
(defmethod #?(:clj swap :cljs -swap!) :sci.impl.protocols/reified
71+
([ref f]
72+
(let [methods (types/getMethods ref)]
73+
((get methods #?(:clj 'swap :cljs '-swap!)) ref f)))
74+
([ref f a1]
75+
(let [methods (types/getMethods ref)]
76+
((get methods #?(:clj 'swap :cljs '-swap!)) ref f a1)))
77+
([ref f a1 a2]
78+
(let [methods (types/getMethods ref)]
79+
((get methods #?(:clj 'swap :cljs '-swap!)) ref f a1 a2)))
80+
([ref f a1 a2 & args]
81+
(let [methods (types/getMethods ref)]
82+
(apply (get methods #?(:clj 'swap :cljs '-swap!)) ref f a1 a2 args))))
83+
84+
(defmethod #?(:clj reset :cljs -reset!) :sci.impl.protocols/reified [ref v]
85+
(let [methods (types/getMethods ref)]
86+
((get methods #?(:clj 'reset :cljs '-reset!)) ref v)))
87+
88+
#?(:clj
89+
(defmethod compareAndSet :sci.impl.protocols/reified [ref old new]
90+
(let [methods (types/getMethods ref)]
91+
((get methods 'compareAndSet) ref old new))))
92+
93+
#?(:clj
94+
(defmethod swapVals :sci.impl.protocols/reified
95+
([ref f]
96+
(let [methods (types/getMethods ref)]
97+
((get methods 'swapVals) ref f)))
98+
([ref f a1]
99+
(let [methods (types/getMethods ref)]
100+
((get methods 'swapVals) ref f a1)))
101+
([ref f a1 a2]
102+
(let [methods (types/getMethods ref)]
103+
((get methods 'swapVals) ref f a1 a2)))
104+
([ref f a1 a2 & args]
105+
(let [methods (types/getMethods ref)]
106+
(apply (get methods 'swapVals) ref f a1 a2 args)))))
107+
108+
#?(:clj
109+
(defmethod resetVals :sci.impl.protocols/reified [ref v]
110+
(let [methods (types/getMethods ref)]
111+
((get methods 'resetVals) ref v))))
112+
113+
;;;; Defaults
114+
115+
(defmethod #?(:clj swap :cljs -swap!) :default [ref f & args]
116+
;; TODO: optimize arities
117+
(apply clojure.core/swap! ref f args))
118+
119+
(defmethod #?(:clj reset :cljs -reset!) :default [ref v]
120+
(reset! ref v))
121+
122+
#?(:clj
123+
(defmethod compareAndSet :default [ref old new]
124+
(compare-and-set! ref old new)))
125+
126+
#?(:clj
127+
(defmethod swapVals :default [ref & args]
128+
(apply swap-vals! ref args)))
129+
130+
#?(:clj
131+
(defmethod resetVals :default [ref v]
132+
(reset-vals! ref v)))
133+
134+
;;;; Re-routing
135+
136+
(defn swap!* [ref f & args]
137+
;; TODO: optimize arities - maybe test first how much this matters at all
138+
;; For CLJ I guess we can directly use the multimethods
139+
(if args
140+
(apply #?(:clj swap :cljs -swap!) ref f args)
141+
(#?(:clj swap :cljs -swap!) ref f)))
142+
143+
(defn reset!* [ref v]
144+
(#?(:clj reset :cljs -reset!) ref v))
145+
146+
#?(:clj
147+
(defn compare-and-set!* [ref old new]
148+
(compareAndSet ref old new)))
149+
150+
#?(:clj
151+
(defn swap-vals!* [ref f & args]
152+
(apply swapVals ref f args)))
153+
154+
#?(:clj
155+
(defn reset-vals!* [ref v]
156+
(resetVals ref v)))
157+
158+
;;;; Protocol vars
159+
160+
(def swap-protocol
161+
#?(:clj
162+
(vars/new-var
163+
'clojure.lang.IAtom
164+
{:class clojure.lang.IAtom
165+
:methods #{swap, reset, compareAndSet}
166+
:ns clj-lang-ns}
167+
{:ns clj-lang-ns})
168+
:cljs
169+
(vars/new-var
170+
'cljs.core.ISwap
171+
{:methods #{-swap!}
172+
:ns cljs-core-ns}
173+
{:ns cljs-core-ns})))
174+
175+
#?(:cljs
176+
(def reset-protocol
177+
(vars/new-var
178+
'cljs.core.IReset
179+
{:methods #{-reset!}
180+
:ns cljs-core-ns}
181+
{:ns cljs-core-ns})))
182+
183+
#?(:clj
184+
(def iatom2-protocol
185+
(vars/new-var
186+
'clojure.lang.IAtom2
187+
{:class clojure.lang.IAtom2
188+
:methods #{swap, reset, compareAndSet, swapVals, resetVals}
189+
:ns clj-lang-ns}
190+
{:ns clj-lang-ns})))
191+
192+
;;;; end IAtom

src/sci/impl/interpreter.cljc

+1-1
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@
467467
(if (interop/resolve-class ctx fq-class-name)
468468
(let [cnn (vars/current-ns-name)]
469469
(swap! env assoc-in [:namespaces cnn :imports class] fq-class-name))
470-
(if-let [rec (records/resolve-record-class ctx package class)]
470+
(if-let [rec (records/resolve-record-or-protocol-class ctx package class)]
471471
(let [cnn (vars/current-ns-name)]
472472
(swap! env assoc-in [:namespaces cnn class] rec))
473473
(throw (new #?(:clj Exception :cljs js/Error)

src/sci/impl/namespaces.cljc

+37-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
[clojure.tools.reader.reader-types :as r]
1212
#?(:clj [clojure.java.io :as jio])
1313
[clojure.walk :as walk]
14+
[sci.impl.core-protocols :as core-protocols]
1415
[sci.impl.hierarchies :as hierarchies]
1516
[sci.impl.io :as io]
1617
[sci.impl.macros :as macros]
@@ -549,6 +550,22 @@
549550

550551
;;;; End binding vars
551552

553+
#?(:clj
554+
(def clojure-lang
555+
{:obj (vars/->SciNamespace 'clojure.lang nil)
556+
;; IDeref as protocol instead of class
557+
'IDeref core-protocols/deref-protocol
558+
'deref core-protocols/deref
559+
;; IAtom as protocol instead of class
560+
'IAtom core-protocols/swap-protocol
561+
'swap core-protocols/swap
562+
'reset core-protocols/reset
563+
'compareAndSet core-protocols/compareAndSet
564+
'IAtom2 core-protocols/iatom2-protocol
565+
'resetVals core-protocols/resetVals
566+
'swapVals core-protocols/swapVals
567+
}))
568+
552569
(def clojure-core
553570
{:obj clojure-core-ns
554571
'*ns* vars/current-ns
@@ -614,6 +631,22 @@
614631
'protocol-type-impl types/type-impl
615632
'satisfies? protocols/satisfies?
616633
;; end protocols
634+
;; IDeref as protocol
635+
'deref core-protocols/deref*
636+
#?@(:cljs ['-deref core-protocols/-deref
637+
'IDeref core-protocols/deref-protocol])
638+
;; end IDeref as protocol
639+
;; IAtom / ISwap as protocol
640+
'swap! core-protocols/swap!*
641+
'compare-and-set! #?(:clj core-protocols/compare-and-set!*
642+
:cljs (copy-core-var compare-and-set!))
643+
#?@(:cljs ['IReset core-protocols/reset-protocol
644+
'ISwap core-protocols/swap-protocol
645+
'-swap! core-protocols/-swap!
646+
'-reset! core-protocols/-reset!])
647+
;; in CLJS swap-vals! and reset-vals! are going through the other protocols
648+
#?@(:clj ['swap-vals! core-protocols/swap-vals!*
649+
'reset-vals! core-protocols/reset-vals!*])
617650
'.. (macrofy double-dot)
618651
'= (copy-core-var =)
619652
'< (copy-core-var <)
@@ -708,7 +741,6 @@
708741
:sci.impl/op :needs-ctx})
709742
'delay (macrofy delay*)
710743
#?@(:clj ['deliver (copy-core-var deliver)])
711-
'deref (copy-core-var deref)
712744
'derive (with-meta hierarchies/derive* {:sci.impl/op :needs-ctx})
713745
'descendants (with-meta hierarchies/descendants* {:sci.impl/op :needs-ctx})
714746
'dissoc (copy-core-var dissoc)
@@ -773,7 +805,7 @@
773805
'ifn? (copy-core-var ifn?)
774806
'inc (copy-core-var inc)
775807
'inst? (copy-core-var inst?)
776-
'instance? types/instance-impl
808+
'instance? protocols/instance-impl
777809
'int-array (copy-core-var int-array)
778810
'interleave (copy-core-var interleave)
779811
'intern (with-meta sci-intern {:sci.impl/op :needs-ctx})
@@ -894,8 +926,7 @@
894926
'reduce-kv (copy-core-var reduce-kv)
895927
'reduced (copy-core-var reduced)
896928
'reduced? (copy-core-var reduced?)
897-
'reset! (copy-core-var reset!)
898-
'reset-vals! (copy-core-var reset-vals!)
929+
'reset! core-protocols/reset!*
899930
'reset-thread-binding-frame-impl vars/reset-thread-binding-frame
900931
'resolve (with-meta sci-resolve {:sci.impl/op :needs-ctx})
901932
'reversible? (copy-core-var reversible?)
@@ -949,8 +980,6 @@
949980
'sequence (copy-core-var sequence)
950981
'seqable? (copy-core-var seqable?)
951982
'shorts (copy-core-var shorts)
952-
'swap! (copy-core-var swap!)
953-
'swap-vals! (copy-core-var swap-vals!)
954983
'tagged-literal (copy-core-var tagged-literal)
955984
'tagged-literal? (copy-core-var tagged-literal?)
956985
'take (copy-core-var take)
@@ -1275,7 +1304,8 @@
12751304
'macroexpand-all macroexpand-all})
12761305

12771306
(def namespaces
1278-
{'clojure.core clojure-core
1307+
{#?@(:clj ['clojure.lang clojure-lang])
1308+
'clojure.core clojure-core
12791309
'clojure.string {:obj clojure-string-namespace
12801310
'blank? (copy-var str/blank? clojure-string-namespace)
12811311
'capitalize (copy-var str/capitalize clojure-string-namespace)

src/sci/impl/protocols.cljc

+16
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@
114114
(defn satisfies? [protocol obj]
115115
(boolean (some #(get-method % (types/type-impl obj)) (:methods protocol))))
116116

117+
(defn instance-impl [clazz x]
118+
(cond
119+
;; fast path for Clojure when using normal clazz
120+
#?@(:clj [(class? clazz) (instance? clazz x)])
121+
;; records are currenrly represented as a symbol with metadata
122+
(and (symbol? clazz) (let [m (meta clazz)] (:sci.impl/record m)))
123+
(= clazz (some-> x meta :sci.impl/type))
124+
;; only in Clojure, we could be referring to clojure.lang.IDeref as a sci protocol
125+
#?@(:clj [(map? clazz)
126+
(when-let [c (:class clazz)]
127+
;; this is a protocol which is an interface on the JVM
128+
(or (satisfies? clazz x)
129+
(instance? c x)))])
130+
;; could we have a fast path for CLJS too? please let me know!
131+
:else (instance? clazz x)))
132+
117133
(defn extends?
118134
"Returns true if atype extends protocol"
119135
[protocol atype]

0 commit comments

Comments
 (0)