;; ==========================================================================
;; test-read-write.sx — Tests for read / write / display / newline
;; ==========================================================================

;; --------------------------------------------------------------------------
;; read — parse one datum from an input port
;; --------------------------------------------------------------------------

(defsuite
  "read:basics"
  (deftest
    "read integer"
    (let ((p (open-input-string "42"))) (assert= (read p) 42)))
  (deftest
    "read float"
    (let ((p (open-input-string "3.14"))) (assert= (read p) 3.14)))
  (deftest
    "read string"
    (let ((p (open-input-string "\"hello\""))) (assert= (read p) "hello")))
  (deftest
    "read boolean true"
    (let ((p (open-input-string "#t"))) (assert (read p))))
  (deftest
    "read boolean false"
    (let ((p (open-input-string "#f"))) (assert (not (read p)))))
  (deftest
    "read nil"
    (let ((p (open-input-string "()"))) (assert-nil (read p))))
  (deftest
    "read list"
    (let
      ((p (open-input-string "(1 2 3)")))
      (assert= (read p) (list 1 2 3))))
  (deftest
    "read nested list"
    (let
      ((p (open-input-string "(+ 1 (* 2 3))")))
      (assert=
        (read p)
        (list (quote +) 1 (list (quote *) 2 3))))))

;; --------------------------------------------------------------------------
;; read — eof and multi-read
;; --------------------------------------------------------------------------

(defsuite
  "read:eof"
  (deftest
    "read eof returns eof-object"
    (let ((p (open-input-string ""))) (assert (eof-object? (read p)))))
  (deftest
    "read whitespace-only returns eof"
    (let ((p (open-input-string "   "))) (assert (eof-object? (read p)))))
  (deftest
    "read two forms"
    (let
      ((p (open-input-string "1 2")))
      (let
        ((a (read p)) (b (read p)))
        (assert (and (= a 1) (= b 2))))))
  (deftest
    "read returns eof after last form"
    (let
      ((p (open-input-string "42")))
      (read p)
      (assert (eof-object? (read p))))))

;; --------------------------------------------------------------------------
;; write — serialize with quoting
;; --------------------------------------------------------------------------

(defsuite
  "write:basics"
  (deftest "write integer" (assert= (write-to-string 42) "42"))
  (deftest
    "write negative integer"
    (assert= (write-to-string -5) "-5"))
  (deftest "write float" (assert= (write-to-string 3.14) "3.14"))
  (deftest "write true" (assert= (write-to-string true) "#t"))
  (deftest "write false" (assert= (write-to-string false) "#f"))
  (deftest "write nil" (assert= (write-to-string nil) "()"))
  (deftest
    "write string quotes"
    (assert= (write-to-string "hello") "\"hello\""))
  (deftest
    "write string with escapes"
    (assert= (write-to-string "a\"b") "\"a\\\"b\""))
  (deftest
    "write list"
    (assert=
      (write-to-string (list 1 2 3))
      "(1 2 3)"))
  (deftest
    "write nested list"
    (assert=
      (write-to-string (list 1 (list 2 3)))
      "(1 (2 3))"))
  (deftest "write symbol" (assert= (write-to-string (quote foo)) "foo"))
  (deftest "write rational" (assert= (write-to-string 1/3) "1/3")))

;; --------------------------------------------------------------------------
;; display — serialize without quoting
;; --------------------------------------------------------------------------

(defsuite
  "display:basics"
  (deftest "display integer" (assert= (display-to-string 42) "42"))
  (deftest
    "display string no quotes"
    (assert= (display-to-string "hello") "hello"))
  (deftest "display true" (assert= (display-to-string true) "#t"))
  (deftest "display nil" (assert= (display-to-string nil) "()"))
  (deftest
    "display list"
    (assert=
      (display-to-string (list 1 2 3))
      "(1 2 3)")))

;; --------------------------------------------------------------------------
;; write vs display distinction
;; --------------------------------------------------------------------------

(defsuite
  "write-vs-display"
  (deftest
    "write quotes string, display does not"
    (let
      ((s "hello"))
      (assert
        (and
          (= (write-to-string s) "\"hello\"")
          (= (display-to-string s) "hello")))))
  (deftest
    "write and display same for numbers"
    (assert= (write-to-string 42) (display-to-string 42)))
  (deftest
    "write and display same for lists"
    (assert=
      (write-to-string (list 1 2))
      (display-to-string (list 1 2)))))

;; --------------------------------------------------------------------------
;; write/display/newline to port
;; --------------------------------------------------------------------------

(defsuite
  "write-to-port"
  (deftest
    "write to output port"
    (let
      ((p (open-output-string)))
      (write 42 p)
      (assert= (get-output-string p) "42")))
  (deftest
    "display to output port"
    (let
      ((p (open-output-string)))
      (display "hi" p)
      (assert= (get-output-string p) "hi")))
  (deftest
    "newline to output port"
    (let
      ((p (open-output-string)))
      (newline p)
      (assert= (get-output-string p) "\n")))
  (deftest
    "write then newline"
    (let
      ((p (open-output-string)))
      (write "hello" p)
      (newline p)
      (assert= (get-output-string p) "\"hello\"\n")))
  (deftest
    "display multiple values"
    (let
      ((p (open-output-string)))
      (display 1 p)
      (display " " p)
      (display 2 p)
      (assert= (get-output-string p) "1 2"))))

;; --------------------------------------------------------------------------
;; write round-trip
;; --------------------------------------------------------------------------

(defsuite
  "write:round-trip"
  (deftest
    "integer round-trips"
    (let
      ((p (open-input-string (write-to-string 42))))
      (assert= (read p) 42)))
  (deftest
    "string round-trips"
    (let
      ((p (open-input-string (write-to-string "hello world"))))
      (assert= (read p) "hello world")))
  (deftest
    "list round-trips"
    (let
      ((p (open-input-string (write-to-string (list 1 2 3)))))
      (assert= (read p) (list 1 2 3))))
  (deftest
    "boolean true round-trips"
    (let
      ((p (open-input-string (write-to-string true))))
      (assert (read p))))
  (deftest
    "boolean false round-trips"
    (let
      ((p (open-input-string (write-to-string false))))
      (assert (not (read p))))))