Skip to content
New issue

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

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

Already on GitHub? # to your account

Emit clojure.lang.BigInt & applicable Long as JS BigInt #214

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
6 changes: 3 additions & 3 deletions build.edn
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
:npm-deps {:lodash "4.17.4"}
:closure-warnings {:non-standard-jsdoc :off :global-this :off}
:install-deps true
:language-in :es6
:language-out :es5
:language-in :ecmascript-2020
:language-out :ecmascript-2020
:foreign-libs [{:file "src/test/cljs/calculator_global.js"
:provides ["calculator"]
:global-exports {calculator Calculator}}
Expand All @@ -22,4 +22,4 @@
:provides ["calculator"]}
{:file "src/test/cljs/es6_default_hello.js"
:provides ["es6_default_hello"]
:module-type :es6}]}
:module-type :es6}]}
3 changes: 2 additions & 1 deletion resources/test.edn
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
:npm-deps {:lodash "4.17.4"}
:closure-warnings {:non-standard-jsdoc :off :global-this :off}
:install-deps true
:language-out :es5
:language-in :ecmascript-2020
:language-out :ecmascript-2020
:foreign-libs
[{:file "src/test/cljs/calculator_global.js"
:provides ["calculator"]
Expand Down
164 changes: 122 additions & 42 deletions src/main/cljs/cljs/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,20 @@
(.isArray js/Array x)
(instance? js/Array x)))

(declare Integer)

(defn ^boolean number?
"Returns true if x is a JavaScript number."
"Returns true if x is a JavaScript Number or BigInt"
[x]
(or (cljs.core/js-number? x)
(cljs.core/bigint? x)
(instance? Integer x)))

(defn ^boolean bigint?
"Returns true if x is a JavaScript Number or BigInt"
[x]
(cljs.core/number? x))
(or (cljs.core/bigint? x)
(instance? Integer x)))

(defn not
"Returns true if x is logical false, false otherwise."
Expand Down Expand Up @@ -341,8 +351,12 @@

(if (and (exists? js/Symbol)
(identical? (goog/typeOf js/Symbol) "function"))
(def ITER_SYMBOL (.-iterator js/Symbol))
(def ITER_SYMBOL "@@iterator"))
(do
(def ITER_SYMBOL (.-iterator js/Symbol))
(def TO_PRIM_SYMBOL (.-toPrimitive js/Symbol)))
(do
(def ITER_SYMBOL "@@iterator")
(def TO_PRIM_SYMBOL "@@toPrimitive")))

(def ^{:jsdoc ["@enum {string}"]}
CHAR_MAP
Expand Down Expand Up @@ -1011,41 +1025,42 @@
h
(add-to-string-hash-cache k)))))

(defn- safe-value? [n]
(and (<= n js/Number.MAX_SAFE_INTEGER)
(>= n js/Number.MIN_SAFE_INTEGER)))

(declare hash)

(defn hash-bigint [n]
(if (safe-value? n)
(hash (js/Number. n))
(hash-string (.toString n 32))))

(defn hash-number [n]
(if ^boolean (js/isFinite n)
(js-mod (Math/floor n) 2147483647)
(case n
##Inf 2146435072
##-Inf -1048576
2146959360)))

(defn hash
"Returns the hash code of its argument. Note this is the hash code
consistent with =."
[o]
(cond
(implements? IHash o)
(bit-xor (-hash o) 0)

(number? o)
(if ^boolean (js/isFinite o)
(js-mod (Math/floor o) 2147483647)
(case o
##Inf
2146435072
##-Inf
-1048576
2146959360))

(implements? IHash o) (bit-xor (-hash o) 0)
(bigint? o) (hash-bigint o)
(number? o) (hash-number o)
;; note: mirrors Clojure's behavior on the JVM, where the hashCode is
;; 1231 for true and 1237 for false
;; http://docs.oracle.com/javase/7/docs/api/java/lang/Boolean.html#hashCode%28%29
(true? o) 1231

(false? o) 1237

(string? o)
(m3-hash-int (hash-string o))

(instance? js/Date o)
(bit-xor (.valueOf o) 0)

(string? o) (m3-hash-int (hash-string o))
(instance? js/Date o) (bit-xor (.valueOf o) 0)
(nil? o) 0

:else
(bit-xor (-hash o) 0)))
:else (bit-xor (-hash o) 0)))

(defn hash-combine [seed hash]
; a la boost
Expand Down Expand Up @@ -1084,6 +1099,45 @@

(declare get)

;; wrapper type to simplify bigint integration
;; Integer has two fields, if number is null then beyond the range of
;; JS safe integral values. bigint is set for comparisons.
(deftype Integer [number bigint ^:mutable __hash]
Object
(toString [_]
(.toString bigint))
(equiv [this other] (-equiv this other))

IEquiv
(-equiv [_ other]
(cond
(instance? Integer other) (if (nil? number)
(== bigint (.-bigint other))
(== number (.-number other)))
(js-number? other) (== number other)
(bigint? other) (== bigint other)
:else false))

IHash
(-hash [_]
(if (nil? __hash)
(if (nil? bigint)
(set! __hash (hash-number number))
(set! __hash (hash-bigint bigint))))
__hash)

IPrintWithWriter
(-pr-writer [_ writer _]
(-write writer (or number bigint))
(-write writer "N")))

(unchecked-set (.-prototype Integer) TO_PRIM_SYMBOL
(fn [hint]
(this-as this
(if (nil? (.-number this))
(.-bigint this)
(.-number this)))))

(deftype Symbol [ns name str ^:mutable _hash _meta]
Object
(toString [_] str)
Expand Down Expand Up @@ -1433,7 +1487,19 @@

(extend-type number
IEquiv
(-equiv [x o] (identical? x o)))
(-equiv [x o]
(cond
(bigint? o) (coercive-= x o)
(instance? Integer o) (-equiv o x)
:else (identical? x o))))

(extend-type bigint
IEquiv
(-equiv [x o]
(cond
(js-number? o) (coercive-= x o)
(instance? Integer o) (-equiv o x)
:else (identical? x o))))

(declare with-meta)

Expand Down Expand Up @@ -2313,7 +2379,7 @@ reduces them without incurring seq initialization"
"Returns true if n is a JavaScript number with no decimal part."
[n]
(and (number? n)
(not ^boolean (js/isNaN n))
(not ^boolean (js/Number.isNaN n))
(not (identical? n js/Infinity))
(== (js/parseFloat n) (js/parseInt n 10))))

Expand Down Expand Up @@ -6686,15 +6752,15 @@ reduces them without incurring seq initialization"

;;; PersistentArrayMap

(defn- array-index-of-nil? [arr]
(defn- array-index-of-nil [arr]
(let [len (alength arr)]
(loop [i 0]
(cond
(<= len i) -1
(nil? (aget arr i)) i
:else (recur (+ i 2))))))

(defn- array-index-of-keyword? [arr k]
(defn- array-index-of-keyword [arr k]
(let [len (alength arr)
kstr (.-fqn k)]
(loop [i 0]
Expand All @@ -6704,7 +6770,7 @@ reduces them without incurring seq initialization"
(identical? kstr (.-fqn (aget arr i)))) i
:else (recur (+ i 2))))))

(defn- array-index-of-symbol? [arr k]
(defn- array-index-of-symbol [arr k]
(let [len (alength arr)
kstr (.-str k)]
(loop [i 0]
Expand All @@ -6714,6 +6780,17 @@ reduces them without incurring seq initialization"
(identical? kstr (.-str (aget arr i)))) i
:else (recur (+ i 2))))))

(defn- equal-number? [x y]
(and (number? x) (number? y) (-equiv x y)))

(defn- array-index-of-number [arr k]
(let [len (alength arr)]
(loop [i 0]
(cond
(<= len i) -1
(equal-number? k (aget arr i)) i
:else (recur (+ i 2))))))

(defn- array-index-of-identical? [arr k]
(let [len (alength arr)]
(loop [i 0]
Expand All @@ -6722,7 +6799,7 @@ reduces them without incurring seq initialization"
(identical? k (aget arr i)) i
:else (recur (+ i 2))))))

(defn- array-index-of-equiv? [arr k]
(defn- array-index-of-equiv [arr k]
(let [len (alength arr)]
(loop [i 0]
(cond
Expand All @@ -6732,17 +6809,20 @@ reduces them without incurring seq initialization"

(defn array-index-of [arr k]
(cond
(keyword? k) (array-index-of-keyword? arr k)
(keyword? k) (array-index-of-keyword arr k)

(or (string? k) (number? k))
(string? k)
(array-index-of-identical? arr k)

(symbol? k) (array-index-of-symbol? arr k)
(number? k)
(array-index-of-number arr k)

(symbol? k) (array-index-of-symbol arr k)

(nil? k)
(array-index-of-nil? arr)
(array-index-of-nil arr)

:else (array-index-of-equiv? arr k)))
:else (array-index-of-equiv arr k)))

(defn- array-map-index-of [m k]
(array-index-of (.-arr m) k))
Expand Down Expand Up @@ -10509,10 +10589,10 @@ reduces them without incurring seq initialization"
(number? obj)
(-write writer
(cond
^boolean (js/isNaN obj) "##NaN"
^boolean (js/Number.isNaN obj) "##NaN"
(identical? obj js/Number.POSITIVE_INFINITY) "##Inf"
(identical? obj js/Number.NEGATIVE_INFINITY) "##-Inf"
:else (str obj)))
:else (str obj (when (bigint? obj) "N"))))

(object? obj)
(do
Expand Down Expand Up @@ -12211,7 +12291,7 @@ reduces them without incurring seq initialization"
(defn ^boolean NaN?
"Returns true if num is NaN, else false"
[val]
(js/isNaN val))
(js/Number.isNaN val))

(defn ^:private parsing-err
"Construct message for parsing for non-string parsing error"
Expand Down
15 changes: 13 additions & 2 deletions src/main/clojure/cljs/compiler.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,11 @@
(defmethod emit-constant* nil [x] (emits "null"))

#?(:clj
(defmethod emit-constant* Long [x] (emits "(" x ")")))
(defmethod emit-constant* Long [x]
(if (or (> x 9007199254740991)
(< x -9007199254740991))
(emits "new cljs.core.Integer(null," x "n," (hash x) ")")
(emits "(" x ")"))))

#?(:clj
(defmethod emit-constant* Integer [x] (emits x))) ; reader puts Integers in metadata
Expand Down Expand Up @@ -345,7 +349,14 @@
(defmethod emit-constant* BigDecimal [x] (emits (.doubleValue ^BigDecimal x))))

#?(:clj
(defmethod emit-constant* clojure.lang.BigInt [x] (emits (.doubleValue ^clojure.lang.BigInt x))))
(defmethod emit-constant* clojure.lang.BigInt [x]
(if (or (> x 9007199254740991)
(< x -9007199254740991))
;; not we don't set hash code at compile time because this is difficult to replicate
(emits "new cljs.core.Integer(null, " (.toString ^clojure.lang.BigInt x) "n, null)")
(emits "new cljs.core.Integer("
(.toString ^clojure.lang.BigInt x) ", " (.toString ^clojure.lang.BigInt x)
"n, null)"))))

(defmethod emit-constant* #?(:clj String :cljs js/String) [x]
(emits (wrap-in-double-quotes (escape-string x))))
Expand Down
7 changes: 5 additions & 2 deletions src/main/clojure/cljs/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -1007,9 +1007,12 @@
`(let [c# ~c x# ~x]
(~'js* "(~{} instanceof ~{})" x# c#)))))

(core/defmacro number? [x]
(core/defmacro js-number? [x]
(bool-expr (core/list 'js* "typeof ~{} === 'number'" x)))

(core/defmacro bigint? [x]
(bool-expr (core/list 'js* "typeof ~{} === 'bigint'" x)))

(core/defmacro symbol? [x]
(bool-expr `(instance? Symbol ~x)))

Expand Down Expand Up @@ -1159,7 +1162,7 @@

(core/defmacro ^::ana/numeric ==
([x] true)
([x y] (bool-expr (core/list 'js* "(~{} === ~{})" x y)))
([x y] (bool-expr (core/list 'js* "(~{} == ~{})" x y)))
([x y & more] `(and (== ~x ~y) (== ~y ~@more))))

(core/defmacro ^::ana/numeric dec [x]
Expand Down
Loading
Loading