Skip to content
New issue

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

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

Already on GitHub? Sign in 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