;; ==========================================================================
;; stdlib.sx — Pure SX standard library functions
;;
;; Loaded by test runners after primitives. These functions are implemented
;; in SX and require no host-specific code.
;;
;; IMPORTANT: SX let/when bodies evaluate only the LAST expression.
;;   Multi-step bodies must be wrapped in (do expr1 expr2 ...).
;; ==========================================================================

;; --------------------------------------------------------------------------
;; format — CL-style string formatting
;;
;; Directives:
;;   ~a  display (no quotes)       ~s  write (with quotes)
;;   ~d  decimal  ~x  hex  ~o  octal  ~b  binary
;;   ~f  fixed-point (6dp)          ~%  newline
;;   ~&  fresh line                 ~~  literal tilde
;;   ~t  tab
;;
;; Signature: (format template arg...) -> string
;; --------------------------------------------------------------------------

(define
  (format template &rest args)
  (let
    ((buf (make-string-buffer)) (n (string-length template)))
    (define
      (consume-arg args)
      (if
        (nil? args)
        (list "" nil)
        (list (display-to-string (first args)) (rest args))))
    (define
      (consume-num args radix)
      (if
        (nil? args)
        (list "" nil)
        (list (number->string (first args) radix) (rest args))))
    (define
      (loop i args)
      (cond
        ((>= i n) (string-buffer->string buf))
        ((and (= (substring template i (+ i 1)) "~") (< (+ i 1) n))
          (let
            ((dir (substring template (+ i 1) (+ i 2))))
            (cond
              ((= dir "a")
                (let
                  ((p (consume-arg args)))
                  (do
                    (string-buffer-append! buf (first p))
                    (loop (+ i 2) (first (rest p))))))
              ((= dir "s")
                (if
                  (nil? args)
                  (loop (+ i 2) args)
                  (do
                    (string-buffer-append!
                      buf
                      (write-to-string (first args)))
                    (loop (+ i 2) (rest args)))))
              ((= dir "d")
                (let
                  ((p (consume-num args 10)))
                  (do
                    (string-buffer-append! buf (first p))
                    (loop (+ i 2) (first (rest p))))))
              ((= dir "x")
                (let
                  ((p (consume-num args 16)))
                  (do
                    (string-buffer-append! buf (first p))
                    (loop (+ i 2) (first (rest p))))))
              ((= dir "o")
                (let
                  ((p (consume-num args 8)))
                  (do
                    (string-buffer-append! buf (first p))
                    (loop (+ i 2) (first (rest p))))))
              ((= dir "b")
                (let
                  ((p (consume-num args 2)))
                  (do
                    (string-buffer-append! buf (first p))
                    (loop (+ i 2) (first (rest p))))))
              ((= dir "f")
                (if
                  (nil? args)
                  (loop (+ i 2) args)
                  (do
                    (string-buffer-append!
                      buf
                      (format-decimal (first args) 6))
                    (loop (+ i 2) (rest args)))))
              ((= dir "%")
                (do
                  (string-buffer-append! buf "\n")
                  (loop (+ i 2) args)))
              ((= dir "&")
                (do
                  (let
                    ((so-far (string-buffer->string buf)))
                    (when
                      (or
                        (= (string-length so-far) 0)
                        (not
                          (=
                            (substring
                              so-far
                              (- (string-length so-far) 1)
                              (string-length so-far))
                            "\n")))
                      (string-buffer-append! buf "\n")))
                  (loop (+ i 2) args)))
              ((= dir "~")
                (do
                  (string-buffer-append! buf "~")
                  (loop (+ i 2) args)))
              ((= dir "t")
                (do
                  (string-buffer-append! buf "\t")
                  (loop (+ i 2) args)))
              (else
                (do
                  (string-buffer-append! buf "~")
                  (loop (+ i 1) args))))))
        (else
          (do
            (string-buffer-append!
              buf
              (substring template i (+ i 1)))
            (loop (+ i 1) args)))))
    (loop 0 args)))
