Skip to content

Commit 8528937

Browse files
swannodettequoll
andauthored
CLJS-3348: Implement new functions for parity with Clojure 1.11 (#165)
Co-authored-by: Paula Gearon <[email protected]>
1 parent 6e0bd78 commit 8528937

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

src/main/cljs/cljs/core.cljs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12025,6 +12025,66 @@ reduces them without incurring seq initialization"
1202512025
[x]
1202612026
(instance? goog.Uri x))
1202712027

12028+
(defn ^boolean NaN?
12029+
"Returns true if num is NaN, else false"
12030+
[val]
12031+
(js/isNaN val))
12032+
12033+
(defn ^:private parsing-err
12034+
"Construct message for parsing for non-string parsing error"
12035+
[val]
12036+
(str "Expected string, got: " (if (nil? val) "nil" (goog/typeOf val))))
12037+
12038+
(defn ^number parse-long
12039+
"Parse string of decimal digits with optional leading -/+ and return an
12040+
integer value, or nil if parse fails"
12041+
[s]
12042+
(if (string? s)
12043+
(and (re-matches #"[+-]?\d+" s)
12044+
(let [i (js/parseInt s)]
12045+
(when (and (<= i js/Number.MAX_SAFE_INTEGER)
12046+
(>= i js/Number.MIN_SAFE_INTEGER))
12047+
i)))
12048+
(throw (js/Error. (parsing-err s)))))
12049+
12050+
(defn ^number parse-double
12051+
"Parse string with floating point components and return a floating point value,
12052+
or nil if parse fails.
12053+
Grammar: https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang.String-"
12054+
[s]
12055+
(if (string? s)
12056+
(cond
12057+
^boolean (re-matches #"[\x00-\x20]*[+-]?NaN[\x00-\x20]*" s) ##NaN
12058+
^boolean (re-matches
12059+
#"[\x00-\x20]*[+-]?(Infinity|((\d+\.?\d*|\.\d+)([eE][+-]?\d+)?)[dDfF]?)[\x00-\x20]*"
12060+
s) (js/parseFloat s)
12061+
:default nil)
12062+
(throw (js/Error. (parsing-err s)))))
12063+
12064+
(def ^:private uuid-regex
12065+
#"^[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]$")
12066+
12067+
(defn parse-uuid
12068+
"Parse a string representing a UUID and return a UUID instance,
12069+
or nil if parse fails.
12070+
Grammar: https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#toString--"
12071+
[s]
12072+
(if (string? s)
12073+
(when ^boolean (re-matches uuid-regex s)
12074+
(uuid s))
12075+
(throw (js/Error. (parsing-err s)))))
12076+
12077+
(defn parse-boolean
12078+
"Parse strings \"true\" or \"false\" and return a boolean, or nil if invalid. Note that this explicitly
12079+
excludes strings with different cases, or space characters."
12080+
[s]
12081+
(if (string? s)
12082+
(case s
12083+
"true" true
12084+
"false" false
12085+
nil)
12086+
(throw (js/Error. (parsing-err s)))))
12087+
1202812088
(defn- maybe-enable-print! []
1202912089
(cond
1203012090
(exists? js/console)

src/test/cljs/cljs/parse_test.cljs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
(ns cljs.parse-test
2+
(:require
3+
[clojure.test :refer [deftest is are]]
4+
[clojure.test.check :as chk]
5+
[clojure.test.check.generators :as gen]
6+
[clojure.test.check.properties :as prop]))
7+
8+
(deftest test-parse-long
9+
(are [s expected]
10+
(= expected (parse-long s))
11+
"100" 100
12+
"+100" 100
13+
"0" 0
14+
"+0" 0
15+
"-0" 0
16+
"-42" -42
17+
"9007199254740991" js/Number.MAX_SAFE_INTEGER ;; largest parsable: 999999999999999934463
18+
"+9007199254740991" js/Number.MAX_SAFE_INTEGER
19+
"-9007199254740991" js/Number.MIN_SAFE_INTEGER
20+
"077" 77) ;; leading 0s are ignored! (not octal)
21+
22+
(are [s] ;; do not parse
23+
(nil? (parse-long s))
24+
"0.3" ;; no float
25+
"9007199254740992" ;; past max long
26+
"-9007199254740992" ;; past min long
27+
"0xA0" ;; no hex
28+
"2r010")) ;; no radix support
29+
30+
;; generative test - gen long -> str -> parse, compare
31+
(deftest test-gen-parse-long
32+
(let [res (chk/quick-check
33+
100000
34+
(prop/for-all* [gen/large-integer]
35+
#(= % (-> % str parse-long))))]
36+
(if (:result res)
37+
(is true) ;; pass
38+
(is (:result res) (pr-str res)))))
39+
40+
(deftest test-parse-double
41+
(are [s expected]
42+
(= expected (parse-double s))
43+
"1.234" 1.234
44+
"+1.234" 1.234
45+
"-1.234" -1.234
46+
"+0" +0.0
47+
"-0.0" -0.0
48+
"0.0" 0.0
49+
"5" 5.0
50+
".5" 0.5
51+
"Infinity" ##Inf
52+
"-Infinity" ##-Inf
53+
"1.7976931348623157E308" js/Number.MAX_VALUE
54+
"4.9E-324" js/Number.MIN_VALUE
55+
"1.7976931348623157E309" js/Number.POSITIVE_INFINITY ;; past max double
56+
"2.5e-324" js/Number.MIN_VALUE ;; past min double, above half minimum
57+
"2.4e-324" 0.0) ;; below minimum double
58+
(is (js/isNaN (parse-double "NaN")))
59+
(are [s] ;; nil on invalid string
60+
(nil? (parse-double s))
61+
"double" ;; invalid string
62+
"1.7976931348623157G309")) ;; close, but not valid
63+
64+
;; generative test - gen double -> str -> parse, compare
65+
(deftest test-gen-parse-double
66+
(let [res (chk/quick-check
67+
100000
68+
(prop/for-all* [gen/double]
69+
#(let [parsed (-> % str parse-double)]
70+
(if (js/isNaN %)
71+
(js/isNaN parsed)
72+
(= % parsed)))))]
73+
(if (:result res)
74+
(is true) ;; pass
75+
(is (:result res) (pr-str res)))))
76+
77+
(deftest test-parse-uuid
78+
(is (parse-uuid (str (random-uuid))))
79+
(is (nil? (parse-uuid "BOGUS"))) ;; nil on invalid uuid string
80+
(are [s] ;; throw on invalid type (not string)
81+
(try (parse-uuid s) (is false) (catch :default _ (is true)))
82+
123
83+
nil))
84+
85+
(deftest test-parse-boolean
86+
(is (identical? true (parse-boolean "true")))
87+
(is (identical? false (parse-boolean "false")))
88+
89+
(are [s] ;; nil on invalid string
90+
(nil? (parse-boolean s))
91+
"abc"
92+
"TRUE"
93+
"FALSE"
94+
" true ")
95+
96+
(are [s] ;; throw on invalid type (not string)
97+
(try (parse-boolean s) (is false) (catch :default _ (is true)))
98+
nil
99+
false
100+
true
101+
100))

src/test/cljs/test_runner.cljs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
[cljs.core-test :as core-test]
2020
[cljs.reader-test]
2121
[cljs.binding-test]
22+
[cljs.parse-test]
2223
[cljs.ns-test]
2324
[clojure.set-test]
2425
[clojure.string-test]
@@ -76,6 +77,7 @@
7677
'cljs.hashing-test
7778
'cljs.core-test
7879
'cljs.reader-test
80+
'cljs.parse-test
7981
'clojure.set-test
8082
'clojure.string-test
8183
'clojure.data-test

src/test/self/self_parity/test.cljs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@
280280
[cljs.core-test :as core-test]
281281
[cljs.reader-test]
282282
[cljs.binding-test]
283+
[cljs.parse-test]
283284
#_[cljs.ns-test]
284285
[clojure.set-test]
285286
[clojure.string-test]
@@ -331,6 +332,7 @@
331332
'cljs.hashing-test
332333
'cljs.core-test
333334
'cljs.reader-test
335+
'cljs.parse-test
334336
'clojure.set-test
335337
'clojure.string-test
336338
'clojure.data-test

0 commit comments

Comments
 (0)