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
@@ -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]
Loading
Oops, something went wrong.