Repository: chrovis/cljam (clojure)

Issue:
Case insensitive support for NaN/Infinity
The current cljam implementation of VCF reader throws if it encounters a lowercased `nan` as a `Float` value.

The VCF spec says that the  lexical syntax of `Float` [allows for case insensitive `[-+]?(INF|INFINITY|NAN)`](https://github.com/samtools/hts-specs/blob/7cfd7893b0f6d6ee23474847f0239c47524c7f22/VCFv4.4.tex#L98-L99), and in practice, there are sometimes VCFs out there (e.g. some of [ToMMo’s VCFs](https://jmorp.megabank.tohoku.ac.jp/downloads)) that contain such values.

Pull request:
This PR fixes #270 and adds case insensitive float parser.

-  For inputs without Inf or NaN  strings, this parser has less than 1%  time overhead.
-  For inputs like Inf and NaN, this parser has 5% time overhead.

- Additionally updated cljfmt version

Patch:
diff --git a/project.clj b/project.clj
index a900e4a0..e5761eb5 100644
--- a/project.clj
+++ b/project.clj
@@ -23,7 +23,7 @@
                              [lein-marginalia "0.9.1" :exclusions [org.clojure/clojure]]
                              [lein-cloverage "1.1.2"]
                              [net.totakke/lein-libra "0.1.2"]
-                             [lein-cljfmt "0.6.8"]]
+                             [lein-cljfmt "0.9.2"]]
                    :test-selectors {:default #(not-any? % [:slow :remote])
                                     :slow :slow ; Slow tests with local resources
                                     :remote :remote ; Tests with remote resources
diff --git a/src/cljam/io/bam_index/core.clj b/src/cljam/io/bam_index/core.clj
index bab6f652..b6540ada 100644
--- a/src/cljam/io/bam_index/core.clj
+++ b/src/cljam/io/bam_index/core.clj
@@ -54,10 +54,8 @@
   [^BAMIndex bai ^long ref-idx ^long beg ^long end]
   (util-bin/get-spans bai ref-idx beg end))
 
-
 ;; ## Writing
 
-
 (defn writer ^BAIWriter
   [f refs]
   (BAIWriter. (DataOutputStream. (FileOutputStream. (cio/file f)))
diff --git a/src/cljam/io/bam_index/writer.clj b/src/cljam/io/bam_index/writer.clj
index 0fbc9240..77dc1998 100644
--- a/src/cljam/io/bam_index/writer.clj
+++ b/src/cljam/io/bam_index/writer.clj
@@ -22,13 +22,11 @@
   (close [_]
     (.close writer)))
 
-
 ;; ### Intermediate data definitions
 ;;
 ;; Use record for performance.
 ;; Record is faster than map for retrieving elements.
 
-
 (defrecord MetaData [^long first-offset ^long last-offset ^long aligned-alns ^long unaligned-alns])
 
 (defrecord IndexStatus [^MetaData meta-data bin-index linear-index])
diff --git a/src/cljam/io/pileup.clj b/src/cljam/io/pileup.clj
index 66a208a9..24455053 100644
--- a/src/cljam/io/pileup.clj
+++ b/src/cljam/io/pileup.clj
@@ -119,11 +119,9 @@
        line-seq
        (map parse-pileup-line)))
 
-
 ;; Writer
 ;; ------
 
-
 (defn- write-mpileup-alignment!
   [^BufferedWriter w ref-reader rname ref-pos ref
    {:keys [base reverse? mapq start? end? insertion deletion]}]
diff --git a/src/cljam/io/twobit/reader.clj b/src/cljam/io/twobit/reader.clj
index e143cd2d..28e43b5a 100644
--- a/src/cljam/io/twobit/reader.clj
+++ b/src/cljam/io/twobit/reader.clj
@@ -47,10 +47,10 @@
             len (.getInt buf)
             _ (.reset ^Buffer buf)
             header (delay
-                    (let [buf' (.duplicate buf)]
-                      (.order buf' (.order buf))
-                      (.position ^Buffer buf' (+ offset Integer/BYTES))
-                      (read-sequence-header! buf')))]
+                     (let [buf' (.duplicate buf)]
+                       (.order buf' (.order buf))
+                       (.position ^Buffer buf' (+ offset Integer/BYTES))
+                       (read-sequence-header! buf')))]
         (.put m chr (Chrom. chr len offset i header))))
     m))
 
diff --git a/src/cljam/io/vcf/util.clj b/src/cljam/io/vcf/util.clj
index a17a2022..b3b128e6 100644
--- a/src/cljam/io/vcf/util.clj
+++ b/src/cljam/io/vcf/util.clj
@@ -10,13 +10,23 @@
        (and (= 1 (.length ~s))
             (= \. (.charAt ~s 0)))))
 
+(defn- parse-float [^String s]
+  (let [m (re-matcher #"(?i)[+-]?(nan|inf(?:inity)?)" s)]
+    (if (.matches m)
+      (case (.charAt (.group m 1) 0)
+        (\n \N) Float/NaN
+        (\i \I) (if (= (.charAt s 0) \-)
+                  Float/NEGATIVE_INFINITY
+                  Float/POSITIVE_INFINITY))
+      (Float/parseFloat s))))
+
 (defn- value-parser
   "Returns a parser function for given type identifier."
   [type]
   (case type
     "Flag" (constantly :exists)
     "Integer" #(when-not (dot-or-nil? ^String %) (Integer/parseInt %))
-    "Float" #(when-not (dot-or-nil? ^String %) (Float/parseFloat %))
+    "Float" #(when-not (dot-or-nil? ^String %) (parse-float %))
     "Character" #(when-not (dot-or-nil? ^String %) (first %))
     "String" #(when-not (dot-or-nil? ^String %) %)))
 
diff --git a/src/cljam/util.clj b/src/cljam/util.clj
index d765e249..1038f9c1 100644
--- a/src/cljam/util.clj
+++ b/src/cljam/util.clj
@@ -90,11 +90,9 @@
   [c]
   (not (nil? (#{\space \tab \newline \formfeed \return (char 0x0b)} c))))
 
-
 ;; file utils
 ;; ---------
 
-
 (defn as-url ^URL
   [x]
   (try


Test patch:
diff --git a/test/cljam/io/pileup_test.clj b/test/cljam/io/pileup_test.clj
index a9adc65f..74e26701 100644
--- a/test/cljam/io/pileup_test.clj
+++ b/test/cljam/io/pileup_test.clj
@@ -268,11 +268,9 @@
                                   :pile [{:base \A :qual 40 :alignment {:flag 0 :pos 5}}]}])
            (.toString sw)))))
 
-
 ;; Read & Write
 ;; ------------
 
-
 (deftest regression
   (testing "without-ref"
     (are [?input]
diff --git a/test/cljam/io/vcf/util_test.clj b/test/cljam/io/vcf/util_test.clj
index 88d01b13..025cbce7 100644
--- a/test/cljam/io/vcf/util_test.clj
+++ b/test/cljam/io/vcf/util_test.clj
@@ -1,5 +1,5 @@
 (ns cljam.io.vcf.util-test
-  (:require [clojure.test :refer [deftest are]]
+  (:require [clojure.test :refer [deftest are is]]
             [cljam.test-common :refer
              [test-vcf-v4_3-meta-info
               test-vcf-v4_3-header
@@ -455,3 +455,14 @@
     "TG" "TAC" {:type :complex}
     "TCA" "GGGG" {:type :complex}
     "AAAA" "CAC" {:type :complex}))
+
+(deftest parse-float-test
+  (is (= (#'vcf-util/parse-float "3.2") (Float/parseFloat "3.2")))
+  (is (Float/isNaN (#'vcf-util/parse-float "nan")))
+  (is (Float/isNaN (#'vcf-util/parse-float "NAN")))
+  (is (Float/isNaN (#'vcf-util/parse-float "NaN")))
+  (is (= (#'vcf-util/parse-float "+INF") Float/POSITIVE_INFINITY))
+  (is (= (#'vcf-util/parse-float "-INF") Float/NEGATIVE_INFINITY))
+  (is (= (#'vcf-util/parse-float "INF") Float/POSITIVE_INFINITY))
+  (is (= (#'vcf-util/parse-float "inf") Float/POSITIVE_INFINITY))
+  (is (= (#'vcf-util/parse-float "infinity") Float/POSITIVE_INFINITY)))
diff --git a/test/cljam/test_common.clj b/test/cljam/test_common.clj
index 2be02949..eca6a88d 100644
--- a/test/cljam/test_common.clj
+++ b/test/cljam/test_common.clj
@@ -231,10 +231,8 @@
 (def test-large-vcf-tbi-file (cavia/resource mycavia "large.vcf.gz.tbi"))
 (def test-large-vcf-csi-file (cavia/resource mycavia "large.vcf.gz.csi"))
 
-
 ;; ### pileup files
 
-
 (def test-pileup-file "test-resources/pileup/test.pileup")
 (def test-pileup-dir "test-resources/pileup/")
 
@@ -254,7 +252,6 @@
 
 ;; ### GFF3 files
 
-
 (def test-gff3-file "test-resources/gff3/example.gff3")
 
 ;; ### WIG files