diff --git a/deps.edn b/deps.edn index 012e22069..e3a236c2a 100644 --- a/deps.edn +++ b/deps.edn @@ -19,6 +19,8 @@ "-e" "(cljs.test-runner/-main)"]} :runtime.test.build {:extra-paths ["src/test/cljs"] :main-opts ["-m" "cljs.main" "-co" "resources/test.edn" "-c"]} + :lite.test.build {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-co" "resources/lite_test.edn" "-c"]} :selfhost.test.build {:extra-paths ["src/test/self"] :main-opts ["-m" "cljs.main" "-co" "resources/self_host_test.edn" "-c"]} :selfparity.test.build {:extra-paths ["src/test/self"] diff --git a/resources/lite_test.edn b/resources/lite_test.edn new file mode 100644 index 000000000..11b3d3f9f --- /dev/null +++ b/resources/lite_test.edn @@ -0,0 +1,13 @@ +{:optimizations :advanced + :main lite-test-runner + :lite-mode true + :output-to "builds/out-lite/lite-test.js" + :output-dir "builds/out-lite" + :output-wrapper true + :verbose true + :compiler-stats true + :parallel-build true + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :language-out :es5 + :pseudo-names true + :pretty-print true} diff --git a/src/main/cljs/cljs/analyzer/passes/lite.cljc b/src/main/cljs/cljs/analyzer/passes/lite.cljc new file mode 100644 index 000000000..08a7e03de --- /dev/null +++ b/src/main/cljs/cljs/analyzer/passes/lite.cljc @@ -0,0 +1,32 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.passes.lite + (:refer-clojure :exclude [var?])) + +(defn var? [ast] + (= :var (:op ast))) + +(def ctor->simple-ctor + '{cljs.core/vector cljs.core/simple-vector + cljs.core/vec cljs.core/simple-vec}) + +(defn update-var [{:keys [name] :as ast}] + (let [replacement (get ctor->simple-ctor name)] + (-> ast + (assoc :name replacement) + (assoc-in [:info :name] replacement)))) + +(defn replace-var? [ast] + (and (var? ast) + (contains? ctor->simple-ctor (:name ast)))) + +(defn use-lite-types + [env ast _] + (cond-> ast + (replace-var? ast) update-var)) \ No newline at end of file diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs index 4305440a8..f2fd26dbf 100644 --- a/src/main/cljs/cljs/core.cljs +++ b/src/main/cljs/cljs/core.cljs @@ -53,6 +53,11 @@ , and \"global\" supported. "} *global* "default") +(goog-define + ^{:doc "Boolean flag for LITE_MODE" + :jsdoc ["@type {boolean}"]} + LITE_MODE false) + (def ^{:dynamic true :doc "Var bound to the current namespace. Only used for bootstrapping." @@ -2070,7 +2075,7 @@ reduces them without incurring seq initialization" (-assoc coll k v) (if-not (nil? coll) (-assoc coll k v) - (array-map k v)))) + {k v}))) ([coll k v & kvs] (let [ret (assoc coll k v)] (if kvs @@ -2262,7 +2267,10 @@ reduces them without incurring seq initialization" (defn chunked-seq? "Return true if x satisfies IChunkedSeq." - [x] (implements? IChunkedSeq x)) + [x] + (if-not ^boolean LITE_MODE + (implements? IChunkedSeq x) + false)) ;;;;;;;;;;;;;;;;;;;; js primitives ;;;;;;;;;;;; (defn js-obj @@ -6561,163 +6569,6 @@ reduces them without incurring seq initialization" i (recur (+ i incr))))))) -; The keys field is an array of all keys of this map, in no particular -; order. Any string, keyword, or symbol key is used as a property name -; to store the value in strobj. If a key is assoc'ed when that same -; key already exists in strobj, the old value is overwritten. If a -; non-string key is assoc'ed, return a HashMap object instead. - -(defn- obj-map-compare-keys [a b] - (let [a (hash a) - b (hash b)] - (cond - (< a b) -1 - (> a b) 1 - :else 0))) - -(defn- obj-map->hash-map [m k v] - (let [ks (.-keys m) - len (alength ks) - so (.-strobj m) - mm (meta m)] - (loop [i 0 - out (transient (.-EMPTY PersistentHashMap))] - (if (< i len) - (let [k (aget ks i)] - (recur (inc i) (assoc! out k (gobject/get so k)))) - (-with-meta (persistent! (assoc! out k v)) mm))))) - -;;; ObjMap - DEPRECATED - -(defn- obj-clone [obj ks] - (let [new-obj (js-obj) - l (alength ks)] - (loop [i 0] - (when (< i l) - (let [k (aget ks i)] - (gobject/set new-obj k (gobject/get obj k)) - (recur (inc i))))) - new-obj)) - -(deftype ObjMap [meta keys strobj update-count ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - (equiv [this other] - (-equiv this other)) - - IWithMeta - (-with-meta [coll new-meta] - (if (identical? new-meta meta) - coll - (ObjMap. new-meta keys strobj update-count __hash))) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (-with-meta (.-EMPTY ObjMap) meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) - - ISeqable - (-seq [coll] - (when (pos? (alength keys)) - (map #(vector % (unchecked-get strobj %)) - (.sort keys obj-map-compare-keys)))) - - ICounted - (-count [coll] (alength keys)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (unchecked-get strobj k) - not-found)) - - IAssociative - (-assoc [coll k v] - (if (string? k) - (if (or (> update-count (.-HASHMAP_THRESHOLD ObjMap)) - (>= (alength keys) (.-HASHMAP_THRESHOLD ObjMap))) - (obj-map->hash-map coll k v) - (if-not (nil? (scan-array 1 k keys)) - (let [new-strobj (obj-clone strobj keys)] - (gobject/set new-strobj k v) - (ObjMap. meta keys new-strobj (inc update-count) nil)) ; overwrite - (let [new-strobj (obj-clone strobj keys) ; append - new-keys (aclone keys)] - (gobject/set new-strobj k v) - (.push new-keys k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)))) - ;; non-string key. game over. - (obj-map->hash-map coll k v))) - (-contains-key? [coll k] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - true - false)) - - IFind - (-find [coll k] - (when (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (MapEntry. k (unchecked-get strobj k) nil))) - - IKVReduce - (-kv-reduce [coll f init] - (let [len (alength keys)] - (loop [keys (.sort keys obj-map-compare-keys) - init init] - (if (seq keys) - (let [k (first keys) - init (f init k (unchecked-get strobj k))] - (if (reduced? init) - @init - (recur (rest keys) init))) - init)))) - - IMap - (-dissoc [coll k] - (if (and (string? k) - (not (nil? (scan-array 1 k keys)))) - (let [new-keys (aclone keys) - new-strobj (obj-clone strobj keys)] - (.splice new-keys (scan-array 1 k new-keys) 1) - (js-delete new-strobj k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)) - coll)) ; key not found, return coll unchanged - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IEditableCollection - (-as-transient [coll] - (transient (into (hash-map) coll)))) - -(set! (.-EMPTY ObjMap) (ObjMap. nil (array) (js-obj) 0 empty-unordered-hash)) - -(set! (.-HASHMAP_THRESHOLD ObjMap) 8) - -(set! (.-fromObject ObjMap) (fn [ks obj] (ObjMap. nil ks obj 0 nil))) - ;; Record Iterator (deftype RecordIter [^:mutable i record base-count fields ext-map-iter] Object @@ -9191,19 +9042,6 @@ reduces them without incurring seq initialization" (.createAsIfByAssoc PersistentArrayMap (to-array s)) (if (seq s) (first s) (.-EMPTY PersistentArrayMap)))) -(defn obj-map - "keyval => key val - Returns a new object map with supplied mappings." - [& keyvals] - (let [ks (array) - obj (js-obj)] - (loop [kvs (seq keyvals)] - (if kvs - (do (.push ks (first kvs)) - (gobject/set obj (first kvs) (second kvs)) - (recur (nnext kvs))) - (.fromObject ObjMap ks obj))))) - (defn sorted-map "keyval => key val Returns a new sorted map with supplied mappings." @@ -10509,8 +10347,10 @@ reduces them without incurring seq initialization" (-write writer end))))) (defn write-all [writer & ss] - (doseq [s ss] - (-write writer s))) + (loop [ss (seq ss)] + (when-not (nil? ss) + (-write writer (first ss)) + (recur (next ss))))) (defn string-print [x] (when (nil? *print-fn*) @@ -10545,13 +10385,26 @@ reduces them without incurring seq initialization" (implements? IMeta obj) (not (nil? (meta obj))))) -(defn- pr-map-entry [k v] +(defn- simple-map-entry [k v] (reify + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + IVector + (-assoc-n [_ n x] + (case n + 0 (simple-map-entry x v) + 1 (simple-map-entry k x) + (throw (js/Error. "Index out of bounds")))) IMapEntry (-key [_] k) (-val [_] v) ISeqable - (-seq [_] (IndexedSeq. #js [k v] 0 nil)))) + (-seq [_] (IndexedSeq. #js [k v] 0 nil)) + IIndexed + (-nth [_ i] + (case i, 0 k, 1 v, (throw (js/Error. "Index out of bounds")))) + (-nth [_ i not-found] + (case i, 0 k, 1 v, not-found)))) (defn- pr-writer-impl [obj writer opts] @@ -10591,7 +10444,7 @@ reduces them without incurring seq initialization" (.map (js-keys obj) (fn [k] - (pr-map-entry + (simple-map-entry (cond-> k (some? (.match k #"^[A-Za-z_\*\+\?!\-'][\w\*\+\?!\-']*$")) keyword) (unchecked-get obj k)))) pr-writer writer opts)) @@ -10660,9 +10513,11 @@ reduces them without incurring seq initialization" (defn pr-seq-writer [objs writer opts] (pr-writer (first objs) writer opts) - (doseq [obj (next objs)] - (-write writer " ") - (pr-writer obj writer opts))) + (loop [objs (next objs)] + (when-not (nil? objs) + (-write writer " ") + (pr-writer (first objs) writer opts) + (recur (next objs))))) (defn- pr-sb-with-opts [objs opts] (let [sb (StringBuffer.) @@ -10772,10 +10627,10 @@ reduces them without incurring seq initialization" (when (or (keyword? k) (symbol? k)) (if ns (when (= ns (namespace k)) - (.push lm (pr-map-entry (strip-ns k) v)) + (.push lm (simple-map-entry (strip-ns k) v)) (recur ns entries)) (when-let [new-ns (namespace k)] - (.push lm (pr-map-entry (strip-ns k) v)) + (.push lm (simple-map-entry (strip-ns k) v)) (recur new-ns entries)))) #js [ns lm]))))) @@ -10855,10 +10710,6 @@ reduces them without incurring seq initialization" MapEntry (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) - ObjMap - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts)) - KeySeq (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) @@ -12400,3 +12251,534 @@ reduces them without incurring seq initialization" (identical? "window" *global*) (set! goog/global js/window) (identical? "self" *global*) (set! goog/global js/self) (identical? "global" *global*) (set! goog/global js/global))) + +;; ----------------------------------------------------------------------------- +;; Original 2011 Copy-on-Write Types + +;;; Vector + +(deftype Vector [meta array ^:mutable __hash] + IWithMeta + (-with-meta [coll meta] (Vector. meta array __hash)) + + IMeta + (-meta [coll] meta) + + IStack + (-peek [coll] + (let [count (.-length array)] + (when (> count 0) + (aget array (dec count))))) + (-pop [coll] + (if (> (.-length array) 0) + (let [new-array (aclone array)] + (. new-array (pop)) + (Vector. meta new-array nil)) + (throw (js/Error. "Can't pop empty vector")))) + + ICollection + (-conj [coll o] + (let [new-array (aclone array)] + (.push new-array o) + (Vector. meta new-array nil))) + + IEmptyableCollection + (-empty [coll] (with-meta (. Vector -EMPTY) meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (hash-coll coll)) + + ISeqable + (-seq [coll] + (when (> (.-length array) 0) + (let [vector-seq + (fn vector-seq [i] + (lazy-seq + (when (< i (.-length array)) + (cons (aget array i) (vector-seq (inc i))))))] + (vector-seq 0)))) + + ICounted + (-count [coll] (.-length array)) + + IIndexed + (-nth [coll n] + (if (and (<= 0 n) (< n (.-length array))) + (aget array n) + #_(throw (js/Error. (str "No item " n " in vector of length " (.-length array)))))) + (-nth [coll n not-found] + (if (and (<= 0 n) (< n (.-length array))) + (aget array n) + not-found)) + + ILookup + (-lookup [coll k] (-nth coll k nil)) + (-lookup [coll k not-found] (-nth coll k not-found)) + + IAssociative + (-assoc [coll k v] + (let [new-array (aclone array)] + (aset new-array k v) + (Vector. meta new-array nil))) + + IVector + (-assoc-n [coll n val] (-assoc coll n val)) + + IReduce + (-reduce [v f] + (array-reduce array f)) + (-reduce [v f start] + (array-reduce array f start)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll))) + +(set! (. Vector -EMPTY) (Vector. nil (array) nil)) + +(set! (. Vector -fromArray) (fn [xs] (Vector. nil xs nil))) + +(defn simple-vector + [& args] + (if (and (instance? IndexedSeq args) (zero? (.-i args))) + (.fromArray Vector (.-arr args) (not (array? (.-arr args)))) + (Vector. nil (into-array args) nil))) + +(defn simple-vec + [coll] + (cond + (map-entry? coll) + [(key coll) (val coll)] + + (vector? coll) + (with-meta coll nil) + + (array? coll) + (.fromArray Vector coll) + + :else + (into [] coll))) + +; The keys field is an array of all keys of this map, in no particular +; order. Any string, keyword, or symbol key is used as a property name +; to store the value in strobj. If a key is assoc'ed when that same +; key already exists in strobj, the old value is overwritten. If a +; non-string key is assoc'ed, return a HashMap object instead. + +(defn- obj-map-compare-keys [a b] + (let [a (hash a) + b (hash b)] + (cond + (< a b) -1 + (> a b) 1 + :else 0))) + +(defn- obj-clone [obj ks] + (let [new-obj (js-obj) + l (alength ks)] + (loop [i 0] + (when (< i l) + (let [k (aget ks i)] + (gobject/set new-obj k (gobject/get obj k)) + (recur (inc i))))) + new-obj)) + +(declare simple-hash-map HashMap) + +(defn- keyword->obj-map-key + [k] + (str "\uFDD0" "'" (. k -fqn))) + +(defn- obj-map-key->keyword + [k] + (if (.startsWith k "\uFDD0") + (keyword (.substring k 2 (. k -length))) + k)) + +(deftype ObjMap [meta keys strobj ^:mutable __hash] + IWithMeta + (-with-meta [coll meta] (ObjMap. meta keys strobj __hash)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj coll entry))) + + IEmptyableCollection + (-empty [coll] (-with-meta (. ObjMap -EMPTY) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? (alength keys)) + (prim-seq + (.map (.sort keys obj-map-compare-keys) + #(simple-map-entry (obj-map-key->keyword %) (unchecked-get strobj %)))))) + + ICounted + (-count [coll] (alength keys)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k keys)))) + (unchecked-get strobj k) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (string? k) + (if-not (nil? (scan-array 1 k keys)) + (let [new-strobj (obj-clone strobj keys)] + (gobject/set new-strobj k v) + (ObjMap. meta keys new-strobj nil)) ;overwrite + (let [new-strobj (obj-clone strobj keys) ; append + new-keys (aclone keys)] + (gobject/set new-strobj k v) + (.push new-keys k) + (ObjMap. meta new-keys new-strobj nil))) + ; non-string key. game over. + (-with-meta + (-kv-reduce coll + (fn [ret k v] + (-assoc ret k v)) + (. HashMap -EMPTY)) + meta)))) + (-contains-key? [coll k] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k keys)))) + true + false))) + + IFind + (-find [coll k] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (when (and (string? k) + (not (nil? (scan-array 1 k keys)))) + (MapEntry. k (unchecked-get strobj k) nil)))) + + IKVReduce + (-kv-reduce [coll f init] + (let [len (alength keys)] + (loop [keys (.sort keys obj-map-compare-keys) + init init] + (if (seq keys) + (let [k (first keys) + init (f init (obj-map-key->keyword k) (unchecked-get strobj k))] + (if (reduced? init) + @init + (recur (rest keys) init))) + init)))) + + IMap + (-dissoc [coll k] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k keys)))) + (let [new-keys (aclone keys) + new-strobj (obj-clone strobj keys)] + (.splice new-keys (scan-array 1 k new-keys) 1) + (js-delete new-strobj k) + (ObjMap. meta new-keys new-strobj nil)) + coll))) ; key not found, return coll unchanged + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [coll key val] + (-assoc coll key val)) + + ITransientMap + (-dissoc! [coll key] + (-dissoc coll key)) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) + +(set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj) empty-ordered-hash)) + +(set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj nil))) + +(defn obj-map + "keyval => key val + Returns a new object map with supplied mappings." + [& keyvals] + (let [ks (array) + obj (js-obj)] + (loop [kvs (seq keyvals)] + (if kvs + (let [k (-> kvs first keyword->obj-map-key)] + (.push ks k) + (gobject/set obj k (second kvs)) + (recur (nnext kvs))) + (.fromObject ObjMap ks obj))))) + +; The keys field is an array of all keys of this map, in no particular +; order. Each key is hashed and the result used as a property name of +; hashobj. Each values in hashobj is actually a bucket in order to handle hash +; collisions. A bucket is an array of alternating keys (not their hashes) and +; vals. +(deftype HashMap [meta count hashobj ^:mutable __hash] + IWithMeta + (-with-meta [coll meta] (HashMap. meta count hashobj __hash)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj coll entry))) + + IEmptyableCollection + (-empty [coll] (with-meta (. HashMap -EMPTY) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? count) + (let [hashes (.sort (js-keys hashobj)) + cnt (alength hashes) + arr (array)] + (loop [i 0] + (if (< i cnt) + (let [bckt (unchecked-get hashobj (aget hashes i)) + len (alength bckt)] + (loop [j 0] + (when (< j len) + (do + (.push arr (simple-map-entry (aget bckt j) (aget bckt (inc j)))) + (recur (+ j 2))))) + (recur (inc i))) + (prim-seq arr)))))) + + ICounted + (-count [coll] count) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [bucket (aget hashobj (hash k)) + i (when bucket (scan-array 2 k bucket))] + (if i + (aget bucket (inc i)) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [h (hash k) + bucket (aget hashobj h)] + (if bucket + (let [new-bucket (aclone bucket) + new-hashobj (gobject/clone hashobj)] + (aset new-hashobj h new-bucket) + (if-let [i (scan-array 2 k new-bucket)] + (do ; found key, replace + (aset new-bucket (inc i) v) + (HashMap. meta count new-hashobj nil)) + (do ; did not find key, append + (.push new-bucket k v) + (HashMap. meta (inc count) new-hashobj nil)))) + (let [new-hashobj (gobject/clone hashobj)] ; did not find bucket + (unchecked-set new-hashobj h (array k v)) + (HashMap. meta (inc count) new-hashobj nil))))) + (-contains-key? [coll k] + (let [bucket (unchecked-get hashobj (hash k)) + i (when bucket (scan-array 2 k bucket))] + (if i + true + false))) + + IMap + (-dissoc [coll k] + (let [h (hash k) + bucket (unchecked-get hashobj h) + i (when bucket (scan-array 2 k bucket))] + (if (not i) + coll ; key not found, return coll unchanged + (let [new-hashobj (gobject/clone hashobj)] + (if (> 3 (alength bucket)) + (js-delete new-hashobj h) + (let [new-bucket (aclone bucket)] + (.splice new-bucket i 2) + (unchecked-set new-hashobj h new-bucket))) + (HashMap. meta (dec count) new-hashobj nil))))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [coll key val] + (-assoc coll key val)) + + ITransientMap + (-dissoc! [coll key] + (-dissoc coll key)) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) + +(set! (. HashMap -EMPTY) (HashMap. nil 0 (js-obj) empty-unordered-hash)) + +(set! (. HashMap -fromArrays) (fn [ks vs] + (let [len (.-length ks)] + (loop [i 0, out (. HashMap -EMPTY)] + (if (< i len) + (recur (inc i) (assoc out (aget ks i) (aget vs i))) + out))))) + +(defn simple-hash-map + "keyval => key val + Returns a new hash map with supplied mappings." + [& keyvals] + (loop [in (seq keyvals), out (. HashMap -EMPTY)] + (if in + (recur (nnext in) (-assoc out (first in) (second in))) + out))) + +(deftype Set [meta hash-map ^:mutable __hash] + IWithMeta + (-with-meta [coll meta] (Set. meta hash-map __hash)) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll o] + (Set. meta (assoc hash-map o nil) nil)) + + IEmptyableCollection + (-empty [coll] (with-meta (. Set -EMPTY) meta)) + + IEquiv + (-equiv [coll other] + (and + (set? other) + (= (-count coll) (count other)) + (every? #(contains? coll %) + other))) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] (keys hash-map)) + + ICounted + (-count [coll] (-count (-seq coll))) + + ILookup + (-lookup [coll v] + (-lookup coll v nil)) + (-lookup [coll v not-found] + (if (-contains-key? hash-map v) + v + not-found)) + + ISet + (-disjoin [coll v] + (Set. meta (-dissoc hash-map v) nil)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientSet + (-disjoin! [coll key] + (-disjoin coll key)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll))) + +(set! (. Set -EMPTY) (Set. nil (. HashMap -EMPTY) empty-unordered-hash)) + +(defn simple-set + [coll] + (if (set? coll) + (-with-meta coll nil) + (let [in (seq coll)] + (if (nil? in) + #{} + (loop [in in out (. Set -EMPTY)] + (if-not (nil? in) + (recur (next in) (-conj out (first in))) + out)))))) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 709531e59..87733cf05 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -16,6 +16,7 @@ #?(:clj (:require [cljs.analyzer.impl :as impl] [cljs.analyzer.impl.namespaces :as nses] [cljs.analyzer.passes.and-or :as and-or] + [cljs.analyzer.passes.lite :as lite] [cljs.env :as env :refer [ensure]] [cljs.externs :as externs] [cljs.js-deps :as deps] @@ -30,6 +31,7 @@ :cljs (:require [cljs.analyzer.impl :as impl] [cljs.analyzer.impl.namespaces :as nses] [cljs.analyzer.passes.and-or :as and-or] + [cljs.analyzer.passes.lite :as lite] [cljs.env :as env] [cljs.reader :as edn] [cljs.tagged-literals :as tags] @@ -492,6 +494,9 @@ (def ^:dynamic *cljs-warning-handlers* [default-warning-handler]) +(defn lite-mode? [] + (get-in @env/*compiler* [:options :lite-mode])) + #?(:clj (defmacro with-warning-handlers [handlers & body] `(binding [*cljs-warning-handlers* ~handlers] @@ -4072,8 +4077,10 @@ (if (and (some? nsym) (symbol? nsym)) (.findInternedVar ^clojure.lang.Namespace #?(:clj (find-ns nsym) :cljs (find-macros-ns nsym)) sym) - (.findInternedVar ^clojure.lang.Namespace - #?(:clj (find-ns 'cljs.core) :cljs (find-macros-ns impl/CLJS_CORE_MACROS_SYM)) sym))))))) + ;; can't be done as compiler pass because macros get to run first + (when-not (and (lite-mode?) (= 'vector sym)) + (.findInternedVar ^clojure.lang.Namespace + #?(:clj (find-ns 'cljs.core) :cljs (find-macros-ns impl/CLJS_CORE_MACROS_SYM)) sym)))))))) (defn get-expander "Given a sym, a symbol identifying a macro, and env, an analysis environment @@ -4452,10 +4459,8 @@ :cljs [infer-type and-or/optimize check-invoke-arg-types])) (defn analyze* [env form name opts] - (let [passes *passes* - passes (if (nil? passes) - default-passes - passes) + (let [passes (cond-> (or *passes* default-passes) + (lite-mode?) (conj lite/use-lite-types)) form (if (instance? LazySeq form) (if (seq form) form ()) form) diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj index b215573f6..5dc7db1a3 100644 --- a/src/main/clojure/cljs/closure.clj +++ b/src/main/clojure/cljs/closure.clj @@ -211,7 +211,7 @@ :watch :watch-error-fn :watch-fn :install-deps :process-shim :rename-prefix :rename-prefix-namespace :closure-variable-map-in :closure-property-map-in :closure-variable-map-out :closure-property-map-out :stable-names :ignore-js-module-exts :opts-cache :aot-cache :elide-strict :fingerprint :spec-skip-macros - :nodejs-rt :target-fn :deps-cmd :bundle-cmd :global-goog-object&array :node-modules-dirs}) + :nodejs-rt :target-fn :deps-cmd :bundle-cmd :global-goog-object&array :node-modules-dirs :lite-mode}) (def string->charset {"iso-8859-1" StandardCharsets/ISO_8859_1 @@ -2519,6 +2519,10 @@ :cache-analysis-format (:cache-analysis-format opts :transit)) (update-in [:preamble] #(into (or % []) ["cljs/imul.js"]))) + (:lite-mode opts) + (assoc-in [:closure-defines (str (comp/munge 'cljs.core/LITE_MODE))] + (:lite-mode opts)) + (:target opts) (assoc-in [:closure-defines (str (comp/munge 'cljs.core/*target*))] (name (:target opts))) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc index fcc03ab96..faba462b5 100644 --- a/src/main/clojure/cljs/compiler.cljc +++ b/src/main/clojure/cljs/compiler.cljc @@ -522,6 +522,27 @@ (and (every? #(= (:op %) :const) keys) (= (count (into #{} keys)) (count keys))))) +(defn obj-map-key [x] + (if (keyword? x) + (str \" "\\uFDD0" \' + (if (namespace x) + (str (namespace x) "/") "") + (name x) + \") + (str \" x \"))) + +(defn emit-obj-map [str-keys vals comma-sep distinct-keys?] + (if (zero? (count str-keys)) + (emits "cljs.core.ObjMap.EMPTY") + (emits "cljs.core.ObjMap.fromObject([" (comma-sep str-keys) "], {" + (comma-sep (map (fn [k v] (str k ":" (emit-str v))) str-keys vals)) + "})"))) + +(defn emit-lite-map [keys vals comma-sep distinct-keys?] + (if (zero? (count keys)) + (emits "cljs.core.HashMap.EMPTY") + (emits "cljs.core.HashMap.fromArrays([" (comma-sep keys) "], [" (comma-sep vals) "])"))) + (defn emit-map [keys vals comma-sep distinct-keys?] (cond (zero? (count keys)) @@ -544,9 +565,14 @@ "])"))) (defmethod emit* :map - [{:keys [env keys vals]}] + [{:keys [env form keys vals]}] (emit-wrap env - (emit-map keys vals comma-sep distinct-keys?))) + (if (ana/lite-mode?) + (let [form-keys (clojure.core/keys form)] + (if (every? #(or (string? %) (keyword? %)) form-keys) + (emit-obj-map (map obj-map-key form-keys) vals comma-sep distinct-keys?) + (emit-lite-map keys vals comma-sep distinct-keys?))) + (emit-map keys vals comma-sep distinct-keys?)))) (defn emit-list [items comma-sep] (if (empty? items) @@ -562,10 +588,17 @@ ", 5, cljs.core.PersistentVector.EMPTY_NODE, [" (comma-sep items) "], null)") (emits "cljs.core.PersistentVector.fromArray([" (comma-sep items) "], true)"))))) +(defn emit-lite-vector [items comma-sep] + (if (empty? items) + (emits "cljs.core.Vector.EMPTY") + (emits "new cljs.core.Vector(null, [" (comma-sep items) "], null)"))) + (defmethod emit* :vector [{:keys [items env]}] (emit-wrap env - (emit-vector items comma-sep))) + (if (ana/lite-mode?) + (emit-lite-vector items comma-sep) + (emit-vector items comma-sep)))) (defn distinct-constants? [items] (let [items (map ana/unwrap-quote items)] @@ -583,10 +616,17 @@ :else (emits "cljs.core.PersistentHashSet.createAsIfByAssoc([" (comma-sep items) "])"))) +(defn emit-lite-set [items comma-sep distinct-constants?] + (if (empty? items) + (emits "cljs.core.Set.EMPTY") + (emits "cljs.core.simple_set([" (comma-sep items) "])"))) + (defmethod emit* :set [{:keys [items env]}] (emit-wrap env - (emit-set items comma-sep distinct-constants?))) + (if (ana/lite-mode?) + (emit-lite-set items comma-sep distinct-constants?) + (emit-set items comma-sep distinct-constants?)))) (defn emit-js-object [items emit-js-object-val] (emits "({") diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 8393a1a67..70ef0d267 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -1507,13 +1507,18 @@ ~@body)))) (core/defn- add-obj-methods [type type-sym sigs] - (map (core/fn [[f & meths :as form]] - (core/let [[f meths] (if (vector? (first meths)) - [f [(rest form)]] - [f meths])] - `(set! ~(extend-prefix type-sym f) - ~(with-meta `(fn ~@(map #(adapt-obj-params type %) meths)) (meta form))))) - sigs)) + (core/->> sigs + ;; Elide all toString methods in :lite-mode + (remove + (core/fn [[f]] + (core/and (ana/lite-mode?) (core/= 'toString f)))) + (map + (core/fn [[f & meths :as form]] + (core/let [[f meths] (if (vector? (first meths)) + [f [(rest form)]] + [f meths])] + `(set! ~(extend-prefix type-sym f) + ~(with-meta `(fn ~@(map #(adapt-obj-params type %) meths)) (meta form)))))))) (core/defn- ifn-invoke-methods [type type-sym [f & meths :as form]] (map diff --git a/src/test/cljs/cljs/collections_test.cljs b/src/test/cljs/cljs/collections_test.cljs index 44d5e3f46..6027a3acf 100644 --- a/src/test/cljs/cljs/collections_test.cljs +++ b/src/test/cljs/cljs/collections_test.cljs @@ -9,12 +9,9 @@ (ns cljs.collections-test (:refer-clojure :exclude [iter]) (:require [cljs.test :refer-macros [deftest testing is are run-tests]] - [clojure.test.check :as tc] [clojure.test.check.clojure-test :refer-macros [defspec]] [clojure.test.check.generators :as gen] - [clojure.test.check.properties :as prop :include-macros true] - [clojure.string :as s] - [clojure.set :as set])) + [clojure.test.check.properties :as prop :include-macros true])) (deftest test-map-operations (testing "Test basic map collection operations" @@ -1157,6 +1154,22 @@ (let [things (zipmap (range 15000) (repeat 0))] (is (zero? (count (filter #(-> % key string?) things)))))) +(deftest test-obj-map + (let [a (obj-map)] + (is (empty? a)) + (is (zero? (count a)))) + (let [b (obj-map :a 1)] + (is (not (empty? b))) + (is (== 1 (count b)))) + (let [c (obj-map :a 1 :b 2 :c 3)] + (is (== 3 (count c))) + (is (= 1 (get c :a))) + (is (= 1 (:a c))) + (is (every? keyword? (keys c))) + (is (= (set [:a :b :c]) (set (keys c))))) + (is (= (obj-map :a 1 :b 2 :c 3) + (obj-map :a 1 :b 2 :c 3)))) + (comment (run-tests) diff --git a/src/test/cljs/cljs/lite_collections_test.cljs b/src/test/cljs/cljs/lite_collections_test.cljs new file mode 100644 index 000000000..474099617 --- /dev/null +++ b/src/test/cljs/cljs/lite_collections_test.cljs @@ -0,0 +1,17 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.lite-collections-test + (:require [cljs.test :refer-macros [deftest testing is are run-tests]])) + +;; NOTE: ** this name space must be tested with :lite-mode true ** + +(deftest test-obj-map + (let [a (. ObjMap -EMPTY) + b {}] + (is (identical? a b)))) \ No newline at end of file diff --git a/src/test/cljs/lite_test_runner.cljs b/src/test/cljs/lite_test_runner.cljs new file mode 100644 index 000000000..baa7b0435 --- /dev/null +++ b/src/test/cljs/lite_test_runner.cljs @@ -0,0 +1,22 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns lite-test-runner + (:require [cljs.test :refer-macros [run-tests]] + [cljs.lite-collections-test])) + +(set! *print-newline* false) + +;; When testing Windows we default to Node.js +(if (exists? js/print) + (set-print-fn! js/print) + (enable-console-print!)) + +(run-tests + 'cljs.lite-collections-test + ) diff --git a/src/test/clojure/cljs/analyzer_pass_tests.clj b/src/test/clojure/cljs/analyzer_pass_tests.clj index c87ec7bce..1a451d491 100644 --- a/src/test/clojure/cljs/analyzer_pass_tests.clj +++ b/src/test/clojure/cljs/analyzer_pass_tests.clj @@ -178,8 +178,27 @@ (map (fn [x] x) s))))]))))] (is (empty? (re-seq #"or_" code)))))) +(deftest test-lite-mode-pass + (let [aenv (assoc (ana/empty-env) :context :expr) + env (env/default-compiler-env {:lite-mode true})] + (let [ast (env/with-compiler-env env + (comp/with-core-cljs {} + (fn [] + (analyze aenv 'cljs.core/vec))))] + (is (= 'cljs.core/simple-vec + (-> ast :name) + (-> ast :info :name)))) + (let [ast (env/with-compiler-env env + (comp/with-core-cljs {} + (fn [] + (analyze aenv 'cljs.core/vector))))] + (is (= 'cljs.core/simple-vector + (-> ast :name) + (-> ast :info :name)))))) + (comment (test/run-tests) (require '[clojure.pprint :refer [pprint]]) + )