forked from babashka/sci
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[babashka#279, babashka#319] defprotocol and defrecord
- Loading branch information
Showing
8 changed files
with
328 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
(ns sci.impl.protocols | ||
{:no-doc true} | ||
(:refer-clojure :exclude [defprotocol extend-protocol | ||
extend extend-type reify satisfies? | ||
extends? implements?]) | ||
(:require [sci.impl.multimethods :as mms] | ||
[sci.impl.utils :as utils] | ||
[sci.impl.vars :as vars])) | ||
|
||
(clojure.core/defprotocol IReified | ||
(getInterface [_]) | ||
(getMethods [_])) | ||
|
||
(deftype Reified [interface meths] | ||
IReified | ||
(getInterface [_] interface) | ||
(getMethods [_] meths)) | ||
|
||
(defn defprotocol [_ _ _ctx protocol-name & signatures] | ||
(let [[docstring signatures] | ||
(let [sig (first signatures)] | ||
(if (string? sig) [sig (rest signatures)] | ||
[nil signatures])) | ||
expansion | ||
`(do | ||
(def ~(with-meta protocol-name | ||
{:doc docstring}) {:methods #{} | ||
:ns *ns*}) | ||
~@(map (fn [[method-name & _]] | ||
`(do | ||
(defmulti ~method-name clojure.core/protocol-type-impl) | ||
(defmethod ~method-name :sci.impl.protocols/reified [x# & args#] | ||
(let [methods# (clojure.core/-reified-methods x#)] | ||
(apply (get methods# '~method-name) x# args#))) | ||
#?(:clj (alter-var-root (var ~protocol-name) | ||
update :methods conj ~method-name) | ||
:cljs (def ~protocol-name | ||
(update ~protocol-name :methods conj ~method-name))))) | ||
signatures))] | ||
;; (prn expansion) | ||
expansion)) | ||
|
||
(defn extend-protocol [_ _ ctx protocol-name & impls] | ||
(let [impls (utils/split-when #(not (seq? %)) impls) | ||
protocol-var (@utils/eval-resolve-state ctx protocol-name) | ||
protocol-ns (-> protocol-var deref :ns) | ||
pns (str (vars/getName protocol-ns)) | ||
fq-meth-name #(symbol pns %) | ||
expansion | ||
`(do ~@(map (fn [[type & meths]] | ||
`(do | ||
~@(map (fn [meth] | ||
`(defmethod ~(fq-meth-name (str (first meth))) | ||
~type | ||
~(second meth) ~@(nnext meth))) | ||
meths))) | ||
impls))] | ||
#_(prn expansion) | ||
expansion)) | ||
|
||
(defn extend [ctx atype & proto+mmaps] | ||
(doseq [[proto mmap] (partition 2 proto+mmaps) | ||
:let [proto-ns (:ns proto) | ||
pns (vars/getName proto-ns)]] | ||
#_(when-not (protocol? proto) | ||
(throw (new #?(:clj IllegalArgumentException | ||
:cljs js/Error) | ||
(str proto " is not a protocol")))) | ||
#_(when (implements? proto atype) | ||
(throw (new #?(:clj IllegalArgumentException | ||
:cljs js/Error) | ||
(str atype " already directly implements " (:on-interface proto) " for protocol:" | ||
(:var proto))))) | ||
(doseq [[fn-name f] mmap] | ||
(let [fn-sym (symbol (name fn-name)) | ||
env @(:env ctx) | ||
multi-method-var (get-in env [:namespaces pns fn-sym]) | ||
multi-method @multi-method-var] | ||
(mms/multi-fn-add-method-impl multi-method atype f)) | ||
) | ||
#_(-reset-methods (vars/alter-var-root (:var proto) assoc-in [:impls atype] mmap)))) | ||
|
||
(defn extend-type [_ _ ctx type & proto+meths] | ||
(let [proto+meths (utils/split-when #(not (seq? %)) proto+meths)] | ||
`(do ~@(map (fn [[proto & meths]] | ||
(let [protocol-var (@utils/eval-resolve-state ctx proto) | ||
protocol-ns (-> protocol-var deref :ns) | ||
pns (str (vars/getName protocol-ns)) | ||
fq-meth-name #(symbol pns %)] | ||
`(do | ||
~@(map (fn [meth] | ||
`(defmethod ~(fq-meth-name (str (first meth))) | ||
~type ~(second meth) ~@(nnext meth))) | ||
meths)))) proto+meths)))) | ||
|
||
(defn reify [_ _ _ctx interface & meths] | ||
(let [meths (into {} (map (fn [meth] | ||
`['~(first meth) (fn ~(second meth) ~@(nnext meth))]) | ||
meths))] | ||
`(clojure.core/-reified ~interface ~meths))) | ||
|
||
(defn type-impl [x & _xs] | ||
(or (when (instance? sci.impl.protocols.Reified x) | ||
:sci.impl.protocols/reified) | ||
(some-> x meta :sci.impl/type) | ||
(type x))) | ||
|
||
(defn satisfies? [protocol obj] | ||
(boolean (some #(get-method % (type-impl obj)) (:methods protocol)))) | ||
|
||
(defn extends? | ||
"Returns true if atype extends protocol" | ||
[protocol atype] | ||
(boolean (some #(get-method % atype) (:methods protocol)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
(ns sci.impl.records | ||
{:no-doc true} | ||
(:refer-clojure :exclude [defrecord]) | ||
(:require [sci.impl.utils :as utils] | ||
[sci.impl.vars :as vars])) | ||
|
||
(defn defrecord [_ _ ctx record-name fields & protocol-impls] | ||
(let [factory-fn-sym (symbol (str "->" record-name)) | ||
constructor-sym (symbol (str record-name ".")) | ||
keys (mapv keyword fields) | ||
protocol-impls (utils/split-when symbol? protocol-impls) | ||
protocol-impls | ||
(mapv (fn [[protocol-name impl]] | ||
(let [protocol-var (@utils/eval-resolve-state ctx protocol-name) | ||
protocol-ns (-> protocol-var deref :ns) | ||
pns (str (vars/getName protocol-ns)) | ||
fq-meth-name #(symbol pns %) | ||
args (second impl) | ||
this (first args) | ||
bindings (vec (mapcat (fn [field] | ||
[field (list (keyword field) this)]) | ||
fields))] | ||
`(defmethod ~(fq-meth-name (str (first impl))) '~record-name ~(second impl) | ||
(let ~bindings | ||
~@(nnext impl))))) | ||
protocol-impls)] | ||
`(do | ||
;; (prn '~record-name) | ||
(def ~record-name '~record-name) | ||
(defn ~factory-fn-sym [& args#] | ||
(vary-meta (zipmap ~keys args#) | ||
assoc | ||
:sci.impl/record true | ||
:sci.impl/type '~record-name)) | ||
(def ~constructor-sym ~factory-fn-sym) | ||
~@protocol-impls))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
(ns sci.protocols-test | ||
(:require #?(:cljs [clojure.string :as str]) | ||
[clojure.test :refer [deftest is]] | ||
[sci.test-utils :as tu])) | ||
|
||
(deftest protocol-test | ||
(let [prog " | ||
(ns foo) | ||
(defprotocol AbstractionA | ||
(foo [obj]) | ||
(bar [obj])) | ||
(ns bar) | ||
(defprotocol AbstractionB | ||
\"A cool protocol\" | ||
(fooB [obj x])) | ||
(ns baz) | ||
(require '[foo :as f :refer [AbstractionA]] | ||
'[bar :refer [AbstractionB fooB]]) | ||
(extend Number AbstractionA | ||
{:foo (fn [_] :number) | ||
:bar (fn [_] :bar/number)}) | ||
(extend-protocol AbstractionA | ||
nil | ||
(foo [s] (str \"foo-A!\")) | ||
(bar [s] (str \"bar-A!\")) | ||
String | ||
(foo [s] (str \"foo-A-\" (.toUpperCase s))) | ||
(bar [s] (str \"bar-A-\" (.toUpperCase s)))) | ||
(extend-type Object | ||
AbstractionA | ||
(foo [_] :foo/object) | ||
(bar [_] :bar/object) | ||
AbstractionB | ||
(fooB [_ x] x)) | ||
[(f/foo nil) | ||
(f/bar nil) | ||
(f/foo \"Bar\") | ||
(f/bar \"Bar\") | ||
(f/foo 1) | ||
(f/bar 1) | ||
(f/foo {}) | ||
(f/bar {}) | ||
(fooB {} :fooB/object) | ||
(satisfies? AbstractionA 1)]" | ||
prog #?(:clj prog | ||
:cljs (-> prog | ||
(str/replace "String" "js/String") | ||
(str/replace "Number" "js/Number") | ||
(str/replace "Object" ":default")))] | ||
(is (= ["foo-A!" | ||
"bar-A!" | ||
"foo-A-BAR" | ||
"bar-A-BAR" | ||
:number | ||
:bar/number | ||
:foo/object | ||
:bar/object | ||
:fooB/object | ||
true] | ||
(tu/eval* prog #?(:clj {} | ||
:cljs {:classes {:allow :all | ||
'js #js {:String js/String | ||
:Number js/Number}}})))))) | ||
|
||
(deftest docstring-test | ||
(is (= "-------------------------\nuser/Foo\n cool protocol\n" (tu/eval* " | ||
(defprotocol Foo \"cool protocol\" (foo [_])) | ||
(with-out-str (clojure.repl/doc Foo)) | ||
" {})))) | ||
|
||
(deftest reify-test | ||
(let [prog " | ||
(defprotocol Fruit (subtotal [item])) | ||
(def x (reify Fruit (subtotal [_] 1))) | ||
(subtotal x)"] | ||
(is (= 1 (tu/eval* prog {}))))) | ||
|
||
(deftest extends-test | ||
(let [prog " | ||
(defprotocol Area (get-area [this])) | ||
(extend-type String Area (get-area [_] 0)) | ||
(extends? Area String)" | ||
prog #?(:clj prog | ||
:cljs (-> prog | ||
(str/replace "String" "js/String")))] | ||
(is (true? (tu/eval* prog #?(:clj {} | ||
:cljs {:classes {:allow :all | ||
'js #js {:String js/String}}})))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
(ns sci.records-test | ||
(:require [clojure.test :refer [deftest is testing]] | ||
[sci.test-utils :as tu])) | ||
|
||
(deftest protocol-test | ||
(let [prog " | ||
(defrecord Foo [a b]) | ||
(let [r (->Foo 1 2)] | ||
[(:a r) (:b r)])"] | ||
(is (= [1 2] (tu/eval* prog {})))) | ||
(testing "protocols" | ||
(let [prog " | ||
(ns foo) | ||
(defprotocol Foo (foo [_] 42)) | ||
(defprotocol Graph (graph [_])) | ||
(ns bar (:require [foo :as f])) | ||
(defrecord FooRecord [a b] | ||
f/Foo (foo [_] (dec a))) | ||
(defrecord BarRecord [a b] | ||
f/Foo (foo [_] (inc b))) | ||
(extend FooRecord | ||
f/Graph {:graph (fn [x] {:from (:a x) :to (:b x)})}) | ||
(let [a (->FooRecord 1 2) b (BarRecord. 1 2)] | ||
[(f/foo a) (f/foo b) (f/graph a) (satisfies? f/Graph a)])"] | ||
(is (= [0 3 {:from 1, :to 2} true] (tu/eval* prog {})))))) | ||
|
||
(deftest extends-test | ||
(let [prog " | ||
(defprotocol Area (get-area [this])) | ||
(defrecord Rectangle [width height] | ||
Area | ||
(get-area [this] | ||
(* width height))) | ||
(extends? Area Rectangle)"] | ||
(is (true? (tu/eval* prog {}))))) |