Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 42s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
184 lines
5.0 KiB
Plaintext
184 lines
5.0 KiB
Plaintext
;; lib/dream/json.sx — Dream-on-SX JSON encode/parse (pure SX).
|
|
;; The host JSON primitives live in the ocaml-on-sx runtime, not the base env, so
|
|
;; Dream ships its own. Depends on types.sx. (number? is unreliable in this env —
|
|
;; type-of "number" is used instead.)
|
|
|
|
;; ── encoding ───────────────────────────────────────────────────────
|
|
(define
|
|
dr/json-escape
|
|
(fn
|
|
(s)
|
|
(replace
|
|
(replace
|
|
(replace (replace (replace s "\\" "\\\\") "\"" "\\\"") "\n" "\\n")
|
|
"\r"
|
|
"\\r")
|
|
"\t"
|
|
"\\t")))
|
|
(define dr/json-quote (fn (s) (str "\"" (dr/json-escape s) "\"")))
|
|
|
|
(define
|
|
dream-json-encode
|
|
(fn
|
|
(v)
|
|
(cond
|
|
((nil? v) "null")
|
|
((boolean? v) (if v "true" "false"))
|
|
((= (type-of v) "number") (str v))
|
|
((string? v) (dr/json-quote v))
|
|
((list? v) (str "[" (join "," (map dream-json-encode v)) "]"))
|
|
((dict? v)
|
|
(str
|
|
"{"
|
|
(join
|
|
","
|
|
(map
|
|
(fn
|
|
(k)
|
|
(str (dr/json-quote k) ":" (dream-json-encode (get v k))))
|
|
(keys v)))
|
|
"}"))
|
|
(else (dr/json-quote (str v))))))
|
|
|
|
;; ── parsing (recursive descent; returns {:val :pos}) ───────────────
|
|
(define
|
|
dr/json-space?
|
|
(fn (c) (or (= c " ") (= c "\n") (= c "\r") (= c "\t"))))
|
|
(define
|
|
dr/json-ws
|
|
(fn
|
|
(s i)
|
|
(if
|
|
(and (< i (string-length s)) (dr/json-space? (char-at s i)))
|
|
(dr/json-ws s (+ i 1))
|
|
i)))
|
|
|
|
(define
|
|
dr/json-digit?
|
|
(fn
|
|
(c)
|
|
(let ((n (char-code c))) (and (>= n 48) (<= n 57)))))
|
|
(define
|
|
dr/json-num-char?
|
|
(fn
|
|
(c)
|
|
(or
|
|
(dr/json-digit? c)
|
|
(= c "-")
|
|
(= c "+")
|
|
(= c ".")
|
|
(= c "e")
|
|
(= c "E"))))
|
|
(define
|
|
dr/json-num-end
|
|
(fn
|
|
(s i)
|
|
(if
|
|
(and (< i (string-length s)) (dr/json-num-char? (char-at s i)))
|
|
(dr/json-num-end s (+ i 1))
|
|
i)))
|
|
(define
|
|
dr/json-to-number
|
|
(fn
|
|
(str-val)
|
|
(if
|
|
(or
|
|
(contains? str-val ".")
|
|
(contains? str-val "e")
|
|
(contains? str-val "E"))
|
|
(parse-float str-val)
|
|
(parse-int str-val))))
|
|
|
|
(define
|
|
dr/json-str
|
|
(fn
|
|
(s i acc)
|
|
(let
|
|
((c (char-at s i)))
|
|
(cond
|
|
((= c "\"") {:val acc :pos (+ i 1)})
|
|
((= c "\\")
|
|
(let
|
|
((e (char-at s (+ i 1))))
|
|
(cond
|
|
((= e "n") (dr/json-str s (+ i 2) (str acc "\n")))
|
|
((= e "r") (dr/json-str s (+ i 2) (str acc "\r")))
|
|
((= e "t") (dr/json-str s (+ i 2) (str acc "\t")))
|
|
(else (dr/json-str s (+ i 2) (str acc e))))))
|
|
(else (dr/json-str s (+ i 1) (str acc c)))))))
|
|
|
|
(define
|
|
dr/json-num
|
|
(fn (s i) (let ((j (dr/json-num-end s i))) {:val (dr/json-to-number (substr s i (- j i))) :pos j})))
|
|
|
|
(define
|
|
dr/json-arr
|
|
(fn
|
|
(s i acc)
|
|
(let
|
|
((i (dr/json-ws s i)))
|
|
(if
|
|
(= (char-at s i) "]")
|
|
{:val acc :pos (+ i 1)}
|
|
(let
|
|
((r (dr/json-val s i)))
|
|
(let
|
|
((i2 (dr/json-ws s (get r :pos))))
|
|
(if
|
|
(= (char-at s i2) ",")
|
|
(dr/json-arr
|
|
s
|
|
(+ i2 1)
|
|
(concat acc (list (get r :val))))
|
|
{:val (concat acc (list (get r :val))) :pos (+ i2 1)})))))))
|
|
|
|
(define
|
|
dr/json-obj
|
|
(fn
|
|
(s i acc)
|
|
(let
|
|
((i (dr/json-ws s i)))
|
|
(if
|
|
(= (char-at s i) "}")
|
|
{:val acc :pos (+ i 1)}
|
|
(let
|
|
((kr (dr/json-str s (+ i 1) "")))
|
|
(let
|
|
((i2 (dr/json-ws s (get kr :pos))))
|
|
(let
|
|
((vr (dr/json-val s (+ i2 1))))
|
|
(let
|
|
((i3 (dr/json-ws s (get vr :pos))))
|
|
(if
|
|
(= (char-at s i3) ",")
|
|
(dr/json-obj
|
|
s
|
|
(+ i3 1)
|
|
(assoc acc (get kr :val) (get vr :val)))
|
|
{:val (assoc acc (get kr :val) (get vr :val)) :pos (+ i3 1)})))))))))
|
|
|
|
(define
|
|
dr/json-val
|
|
(fn
|
|
(s i)
|
|
(let
|
|
((i (dr/json-ws s i)))
|
|
(let
|
|
((c (char-at s i)))
|
|
(cond
|
|
((= c "{") (dr/json-obj s (+ i 1) {}))
|
|
((= c "[") (dr/json-arr s (+ i 1) (list)))
|
|
((= c "\"") (dr/json-str s (+ i 1) ""))
|
|
((= c "t") {:val true :pos (+ i 4)})
|
|
((= c "f") {:val false :pos (+ i 5)})
|
|
((= c "n") {:val nil :pos (+ i 4)})
|
|
(else (dr/json-num s i)))))))
|
|
|
|
(define dream-json-parse (fn (s) (get (dr/json-val s 0) :val)))
|
|
|
|
;; ── responses ──────────────────────────────────────────────────────
|
|
;; encode a value into a JSON response (dream-json takes a raw string body)
|
|
(define dream-json-value (fn (v) (dream-json (dream-json-encode v))))
|
|
;; read + parse the request body as JSON
|
|
(define dream-json-body (fn (req) (dream-json-parse (dream-body req))))
|