Skip to content

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

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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
6 changes: 3 additions & 3 deletions build.edn
Original file line number Diff line number Diff line change
@@ -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}}
@@ -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
@@ -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"]
164 changes: 122 additions & 42 deletions src/main/cljs/cljs/core.cljs
Original file line number Diff line number Diff line change
@@ -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."
@@ -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
@@ -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
@@ -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)
@@ -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)

@@ -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))))

@@ -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]
@@ -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]
@@ -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]
@@ -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
@@ -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))
@@ -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
@@ -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"
15 changes: 13 additions & 2 deletions src/main/clojure/cljs/compiler.cljc
Original file line number Diff line number Diff line change
@@ -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
@@ -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))))
7 changes: 5 additions & 2 deletions src/main/clojure/cljs/core.cljc
Original file line number Diff line number Diff line change
@@ -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)))

@@ -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]
49 changes: 49 additions & 0 deletions src/test/cljs/cljs/bigint_test.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
;; 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.bigint-test
(:require [cljs.test :refer-macros [deftest is testing]]))

(deftest test-bigint
(testing "BigInt Basics"
(is (bigint? 1N))
(is (bigint? 9007199254740992))
(is (bigint? -9007199254740992)))
(testing "BigInt & Number equality"
(is (= 1 1N))
(is (= 1N 1))
(is (= 1 1N 1))
(is (== 1 1N))
(is (== 1N 1))
(is (== 1 1N 1)))
(testing "BigInt Hashing"
(is (= (hash 1N) (hash 1)))
(is (= (hash 9007199254740992) (hash 9007199254740992)))
(is (= (hash -9007199254740992) (hash -9007199254740992))))
(testing "BigInt as HashMap keys"
(let [m {1N 2}]
(is (= 2 (get m 1N)))
(is (= 2 (get m 1)))))
(testing "Interop"
(is (= (js/BigInt 1) 1N))
(is (= 1N (js/BigInt 1)))
(is (= (js/BigInt 1) 1N))
(is (= (js/BigInt 1) 1))
(is (= 1 (js/BigInt 1))))
(testing "Interaction with core"
(is (= (range 1 5) (range 1 5N))))
(testing "Arithmetic"
(is (= 2 (+ 1 1N)))
(is (= 0.5 (/ 1N 2)))
(is (= 4N (* 2N 2)))))

(comment

(cljs.test/run-tests)

)
4 changes: 2 additions & 2 deletions src/test/cljs/cljs/core_test.cljs
Original file line number Diff line number Diff line change
@@ -1040,8 +1040,8 @@

(deftest test-807
(testing "Testing CLJS-807, big int, float, big dec literals"
(is (= -1 -1N))
(is (= 9.007199254740996E15 9007199254740995N))
;(is (= -1 -1N)) ;; invalid, -1N is now JS BigInt
;(is (= 9.007199254740996E15 9007199254740995N)) ;; invalid, we emit JS BigInt now
(is (= 1.5 1.5M))
(is (= 4.9E-324 5E-324M))))