spec: read/write/display — S-expression reader/writer on ports
Adds read, write, display, newline, write-to-string, display-to-string
and current-*-port primitives to both JS and OCaml hosts.
JS: sxReadNormalize (#t/#f→true/false), sxReadConvert (()→nil),
sxEq array comparison, sxWriteVal symbol/keyword name fix,
readerMacroGet/readerMacroSet registry in parser platform.
OCaml: sx_write_val/sx_display_val helpers, read/write/display/newline
primitives on port types; parser extended for #t/#f and N/D rationals.
42 new tests (test-read-write.sx), all passing on JS and OCaml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -948,6 +948,61 @@
|
||||
:returns "boolean"
|
||||
:doc "True if a char is immediately available on the port.")
|
||||
|
||||
|
||||
(define-primitive
|
||||
"read"
|
||||
:params (&rest (p :as input-port))
|
||||
:returns "any"
|
||||
:doc "Read one datum from port; returns eof-object at end.")
|
||||
|
||||
(define-primitive
|
||||
"write"
|
||||
:params (v &rest (p :as output-port))
|
||||
:returns "nil"
|
||||
:doc "Serialize v to port with quoting — strings quoted, chars as #\\a notation.")
|
||||
|
||||
(define-primitive
|
||||
"display"
|
||||
:params (v &rest (p :as output-port))
|
||||
:returns "nil"
|
||||
:doc "Serialize v to port without quoting — strings unquoted, chars as characters.")
|
||||
|
||||
(define-primitive
|
||||
"newline"
|
||||
:params (&rest (p :as output-port))
|
||||
:returns "nil"
|
||||
:doc "Write a newline to port.")
|
||||
|
||||
(define-primitive
|
||||
"write-to-string"
|
||||
:params (v)
|
||||
:returns "string"
|
||||
:doc "Serialize v with write quoting, return as string.")
|
||||
|
||||
(define-primitive
|
||||
"display-to-string"
|
||||
:params (v)
|
||||
:returns "string"
|
||||
:doc "Serialize v with display format, return as string.")
|
||||
|
||||
(define-primitive
|
||||
"current-input-port"
|
||||
:params ()
|
||||
:returns "any"
|
||||
:doc "Return current default input port.")
|
||||
|
||||
(define-primitive
|
||||
"current-output-port"
|
||||
:params ()
|
||||
:returns "any"
|
||||
:doc "Return current default output port.")
|
||||
|
||||
(define-primitive
|
||||
"current-error-port"
|
||||
:params ()
|
||||
:returns "any"
|
||||
:doc "Return current error port.")
|
||||
|
||||
(define-module :stdlib.math)
|
||||
|
||||
(define-primitive
|
||||
|
||||
212
spec/tests/test-read-write.sx
Normal file
212
spec/tests/test-read-write.sx
Normal file
@@ -0,0 +1,212 @@
|
||||
;; ==========================================================================
|
||||
;; 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))))))
|
||||
Reference in New Issue
Block a user