spec: format — CL-style string formatting (~a ~s ~d ~x ~o ~b ~f ~% ~& ~~ ~t)

28 tests, passes on both JS and OCaml.
- spec/stdlib.sx: pure SX format function
- spec/primitives.sx: format primitive declaration
- lib/r7rs.sx: fix number->string to support optional radix arg
- hosts/ocaml: add format-decimal primitive, load stdlib.sx in test runner
- hosts/javascript: load stdlib.sx in test runner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 19:58:54 +00:00
parent 6a34ae3ae1
commit 4d7b3e299c
7 changed files with 243 additions and 2 deletions

90
spec/tests/test-format.sx Normal file
View File

@@ -0,0 +1,90 @@
;; ==========================================================================
;; test-format.sx — Tests for CL-style format function
;; ==========================================================================
;; --------------------------------------------------------------------------
;; basic directives
;; --------------------------------------------------------------------------
(defsuite
"format:basic"
(deftest "format returns string" (assert (string? (format "hello"))))
(deftest
"format no directives"
(assert= (format "hello world") "hello world"))
(deftest "format empty template" (assert= (format "") ""))
(deftest "~a display string" (assert= (format "~a" "hello") "hello"))
(deftest "~a display number" (assert= (format "~a" 42) "42"))
(deftest "~a display nil" (assert= (format "~a" nil) "()"))
(deftest
"~s write string (with quotes)"
(assert= (format "~s" "hi") "\"hi\""))
(deftest "~s write number" (assert= (format "~s" 42) "42"))
(deftest
"multiple args"
(assert= (format "~a and ~a" "foo" "bar") "foo and bar")))
;; --------------------------------------------------------------------------
;; numeric directives
;; --------------------------------------------------------------------------
(defsuite
"format:numeric"
(deftest "~d decimal" (assert= (format "~d" 255) "255"))
(deftest "~x hex" (assert= (format "~x" 255) "ff"))
(deftest "~o octal" (assert= (format "~o" 8) "10"))
(deftest "~b binary" (assert= (format "~b" 10) "1010"))
(deftest "~d zero" (assert= (format "~d" 0) "0"))
(deftest
"~x uppercase digits"
(assert= (format "value: ~x" 16) "value: 10")))
;; --------------------------------------------------------------------------
;; float directives
;; --------------------------------------------------------------------------
(defsuite
"format:float"
(deftest "~f fixed point" (assert= (format "~f" 3.14) "3.140000"))
(deftest "~f zero" (assert= (format "~f" 0) "0.000000")))
;; --------------------------------------------------------------------------
;; control directives
;; --------------------------------------------------------------------------
(defsuite
"format:control"
(deftest "~% newline" (assert= (format "a~%b") "a\nb"))
(deftest "~~ literal tilde" (assert= (format "100~~") "100~"))
(deftest "~t tab" (assert= (format "a~tb") "a\tb"))
(deftest "~& fresh line at start" (assert= (format "~&hello") "\nhello"))
(deftest
"~& no newline if already at newline"
(assert= (format "line~%~&next") "line\nnext")))
;; --------------------------------------------------------------------------
;; mixed / compound
;; --------------------------------------------------------------------------
(defsuite
"format:compound"
(deftest
"name and age"
(assert=
(format "Hello ~a, age ~d" "Alice" 30)
"Hello Alice, age 30"))
(deftest
"hex dump style"
(assert=
(format "~d = 0x~x = 0b~b" 10 10 10)
"10 = 0xa = 0b1010"))
(deftest "multiple newlines" (assert= (format "~%~%") "\n\n"))
(deftest "text with no args" (assert= (format "status: ok") "status: ok"))
(deftest
"tilde at end (unknown directive)"
(assert (string? (format "test~"))))
(deftest
"nested strings in ~a"
(assert=
(format "got: ~a" (list 1 2 3))
"got: (1 2 3)")))