-regex
  (is (= (str #"(?i)abc")
         (str (read-string "#\"(?i)abc\""))))
  (is (= (str #"\[\]?(\")\\")
         (str (read-string "#\"\\[\\]?(\\\")\\\\\"")))))

(deftest read-quote
  (is (= ''foo (read-string "'foo"))))

(deftest read-syntax-quote
  (is (= (binding [resolve-symbol (constantly 'cljs.user/x)]
           (read-string "`x"))
         ''cljs.user/x))

  #_(let [q (read-string "quote")]
    (is (= 'bar/bar (binding [*alias-map* '{foo bar}]
                      (second (read-string "`foo/bar")))))
    (is (= q (first (read-string "`foo"))))
    (is (= 'foo (second (read-string "`foo"))))
    (is (= q (first (read-string "`+"))))
    (is (= '+ (second (read-string "`+"))))
    (is (= q (first (read-string "`foo/bar"))))
    (is (= 'foo/bar (second (read-string "`foo/bar"))))
    (is (= 1 (read-string "`1")))))

(deftest read-deref
  (is (= '@foo (read-string "@foo"))))

(deftest read-var
  (is (= '(var foo) (read-string "#'foo"))))

(deftest read-fn
  (is (= '(fn* [] (foo bar baz)) (read-string "#(foo bar baz)"))))

(defn inst [s]
  (js/Date. s))

(deftest read-tagged
  (binding [*data-readers* {'inst inst 'uuid uuid}]
    (is (= #inst "2010-11-12T13:14:15.666"
           (read-string "#inst \"2010-11-12T13:14:15.666\"")))
    (is (= #inst "2010-11-12T13:14:15.666"
           (read-string "#inst\"2010-11-12T13:14:15.666\"")))
    (is (= (uuid "550e8400-e29b-41d4-a716-446655440000")
           (read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")))
    (is (= (uuid "550e8400-e29b-41d4-a716-446655440000")
           (read-string "#uuid\"550e8400-e29b-41d4-a716-446655440000\"")))
    (when *default-data-reader-fn*
      (let [my-unknown (fn [tag val] {:unknown-tag tag :value val})]
        (is (= {:unknown-tag 'foo :value 'bar}
               (binding [*default-data-reader-fn* my-unknown]
                 (read-string "#foo bar"))))))))

(defrecord ^:export foo [])
(defrecord ^:export bar [baz buz])

#_(deftest read-record
    (is (= (foo.)
           (read-string "#cljs.tools.reader_test.foo[]")))
    (is (= (foo.)
           (read-string "#cljs.tools.reader_test.foo []"))) ;; not valid in clojure
    (is (= (foo.)
           (read-string "#cljs.tools.reader_test.foo{}")))
    (is (= (assoc (foo.) :foo 'bar)
           (read-string "#cljs.tools.reader_test.foo{:foo bar}")))

    (is (= (map->bar {:baz 1})
           (read-string "#cljs.tools.reader_test.bar{:baz 1}")))
    (is (= (bar. 1 nil)
           (read-string "#cljs.tools.reader_test.bar[1 nil]")))
    (is (= (bar. 1 2)
           (read-string "#cljs.tools.reader_test.bar[1 2]"))))

(deftest source-logging-meta-test
  (-> (loop [r (cljs.tools.reader.reader-types/source-logging-push-back-reader "(def test 8)\n(def test2 9)\n")
             forms []]
        (if-let [form (reader/read r false nil)]
          (recur r (conj forms [(meta form) form]))
          forms))
      (= [[{:line 1 :column 1 :end-line 1 :end-column 13} '(def test 8)]
          [{:line 2 :column 0 :end-line 2 :end-column 1}]
          [{:line 2, :column 1, :end-line 2, :end-column 14} '(def test2 9)]
          [{:line 3, :column 0, :end-line 3, :end-column 1}]])))

(defrecord JSValue [v])

(extend-protocol IPrintWithWriter
  JSValue
  (-pr-writer [coll writer opts]
    (-write writer "#js")
    (print-map coll pr-writer writer opts)))

(deftest reader-conditionals
  (let [opts {:read-cond :allow :features #{:clj}}]
    (are [out s opts] (= out (read-string opts s))
         ;; basic read-cond
         '[foo-form] "[#?(:foo foo-form :bar bar-form)]" {:read-cond :allow :features #{:foo}}
         '[bar-form] "[#?(:foo foo-form :bar bar-form)]" {:read-cond :allow :features #{:bar}}
         '[foo-form] "[#?(:foo foo-form :bar bar-form)]" {:read-cond :allow :features #{:foo :bar}}
         '[] "[#?(:foo foo-form :bar bar-form)]" {:read-cond :allow :features #{:baz}}
         'nil "#?(:default nil)" opts

         ;; environmental features
         "clojure" "#?(:clj \"clojure\" :cljs \"clojurescript\" :default \"default\")"  opts

         ;; default features
         "default" "#?(:cljr \"clr\" :cljs \"cljs\" :default \"default\")" opts

         ;; splicing
         [] "[#?@(:clj [])]" opts
         [:a] "[#?@(:clj [:a])]" opts
         [:a :b] "[#?@(:clj [:a :b])]" opts
         [:a :b :c] "[#?@(:clj [:a :b :c])]" opts

         ;; nested splicing
         [:a :b :c :d :e] "[#?@(:clj [:a #?@(:clj [:b #?@(:clj [:c]) :d]):e])]" opts
         '(+ 1 (+ 2 3)) "(+ #?@(:clj [1 (+ #?@(:clj [2 3]))]))" opts
         '(+ (+ 2 3) 1) "(+ #?@(:clj [(+ #?@(:clj [2 3])) 1]))" opts
         [:a [:b [:c] :d] :e] "[#?@(:clj [:a [#?@(:clj [:b #?@(:clj [[:c]]) :d])] :e])]" opts

         ;; bypass unknown tagged literals
         [1 2 3] "#?(:cljs #js [1 2 3] :clj [1 2 3])" opts
         :clojure "#?(:foo #some.nonexistent.Record {:x 1} :clj :clojure)" opts)

    (are [re s opts] (is (thrown-with-msg? js/Error re (read-string opts s)))
         #"Feature should be a keyword" "#?((+ 1 2) :a)" opts
         #"even number of forms" "#?(:cljs :a :clj)" opts
         #"cond-splice not in list" "#?@(:clj :a)" opts
         #"cond-splice not in list" "#?@(:foo :a :else :b)" opts
         #"must be a list" "#?[:foo :a :else :b]" opts
         #"Conditional read not allowed" "#?[:clj :a :default nil]" {:read-cond :BOGUS}
