js-on-sx: JSON.stringify + JSON.parse

Recursive-descent parser/serializer in SX.

stringify: type-of dispatch for primitives, lists, dicts. Strings
escape \\ \" \n \r \t.

parse: {:s src :i idx} state dict threaded through helpers.
Handles primitives, strings (with escapes), numbers, arrays,
objects.

Wired into js-global.

391/393 unit (+10), 148/148 slice unchanged.
This commit is contained in:
2026-04-23 21:22:32 +00:00
parent 6f0b4fb476
commit ebaec1659e
3 changed files with 288 additions and 1 deletions

View File

@@ -1636,6 +1636,257 @@
parseFloat
(fn (&rest args) (if (= (len args) 0) 0 (js-to-number (nth args 0)))))
(define
js-json-stringify
(fn
(&rest args)
(if
(= (len args) 0)
js-undefined
(js-json-stringify-value (nth args 0)))))
(define
js-json-stringify-value
(fn
(v)
(cond
((= v nil) "null")
((js-undefined? v) js-undefined)
((= (type-of v) "boolean") (if v "true" "false"))
((number? v) (js-number-to-string v))
((= (type-of v) "string") (js-json-escape-string v))
((list? v)
(let
((parts (list)))
(for-each
(fn
(x)
(let
((s (js-json-stringify-value x)))
(if
(js-undefined? s)
(append! parts "null")
(append! parts s))))
v)
(str "[" (join "," parts) "]")))
((dict? v)
(let
((parts (list)))
(for-each
(fn
(k)
(let
((val (get v k)))
(let
((vs (js-json-stringify-value val)))
(if
(not (js-undefined? vs))
(append! parts (str (js-json-escape-string k) ":" vs))))))
(keys v))
(str "{" (join "," parts) "}")))
(else "null"))))
(define
js-json-escape-string
(fn (s) (str "\"" (js-json-escape-loop s 0 "") "\"")))
(define
js-json-escape-loop
(fn
(s i acc)
(if
(>= i (len s))
acc
(let
((c (char-at s i)))
(cond
((= c "\"") (js-json-escape-loop s (+ i 1) (str acc "\\\"")))
((= c "\\") (js-json-escape-loop s (+ i 1) (str acc "\\\\")))
((= c "\n") (js-json-escape-loop s (+ i 1) (str acc "\\n")))
((= c "\r") (js-json-escape-loop s (+ i 1) (str acc "\\r")))
((= c "\t") (js-json-escape-loop s (+ i 1) (str acc "\\t")))
(else (js-json-escape-loop s (+ i 1) (str acc c))))))))
(define
js-json-parse
(fn
(&rest args)
(if
(= (len args) 0)
js-undefined
(let
((st (dict)))
(dict-set! st "s" (js-to-string (nth args 0)))
(dict-set! st "i" 0)
(js-json-parse-value st)))))
(define
js-json-skip-ws!
(fn
(st)
(let
((s (get st "s")) (i (get st "i")))
(cond
((>= i (len s)) nil)
((or (= (char-at s i) " ") (= (char-at s i) "\t") (= (char-at s i) "\n") (= (char-at s i) "\r"))
(begin (dict-set! st "i" (+ i 1)) (js-json-skip-ws! st)))
(else nil)))))
(define
js-json-parse-value
(fn
(st)
(js-json-skip-ws! st)
(let
((s (get st "s")) (i (get st "i")))
(cond
((>= i (len s)) (error "JSON: unexpected end"))
((= (char-at s i) "\"") (js-json-parse-string st))
((= (char-at s i) "[") (js-json-parse-array st))
((= (char-at s i) "{") (js-json-parse-object st))
((= (char-at s i) "t") (begin (dict-set! st "i" (+ i 4)) true))
((= (char-at s i) "f") (begin (dict-set! st "i" (+ i 5)) false))
((= (char-at s i) "n") (begin (dict-set! st "i" (+ i 4)) nil))
(else (js-json-parse-number st))))))
(define
js-json-parse-string
(fn
(st)
(let
((s (get st "s")))
(dict-set! st "i" (+ (get st "i") 1))
(let
((buf (list)))
(js-json-parse-string-loop st s buf)
(dict-set! st "i" (+ (get st "i") 1))
(join "" buf)))))
(define
js-json-parse-string-loop
(fn
(st s buf)
(let
((i (get st "i")))
(cond
((>= i (len s)) nil)
((= (char-at s i) "\"") nil)
((= (char-at s i) "\\")
(begin
(when
(< (+ i 1) (len s))
(let
((e (char-at s (+ i 1))))
(cond
((= e "n") (append! buf "\n"))
((= e "t") (append! buf "\t"))
((= e "r") (append! buf "\r"))
((= e "\"") (append! buf "\""))
((= e "\\") (append! buf "\\"))
((= e "/") (append! buf "/"))
(else (append! buf e)))))
(dict-set! st "i" (+ i 2))
(js-json-parse-string-loop st s buf)))
(else
(begin
(append! buf (char-at s i))
(dict-set! st "i" (+ i 1))
(js-json-parse-string-loop st s buf)))))))
(define
js-json-parse-number
(fn
(st)
(let
((s (get st "s")) (i (get st "i")))
(let
((start i))
(js-json-parse-number-loop st s)
(js-to-number (js-string-slice s start (get st "i")))))))
(define
js-json-parse-number-loop
(fn
(st s)
(let
((i (get st "i")))
(cond
((>= i (len s)) nil)
((or (js-is-digit? (char-at s i)) (= (char-at s i) "-") (= (char-at s i) "+") (= (char-at s i) ".") (= (char-at s i) "e") (= (char-at s i) "E"))
(begin
(dict-set! st "i" (+ i 1))
(js-json-parse-number-loop st s)))
(else nil)))))
(define
js-json-parse-array
(fn
(st)
(let
((result (list)))
(dict-set! st "i" (+ (get st "i") 1))
(js-json-skip-ws! st)
(cond
((and (< (get st "i") (len (get st "s"))) (= (char-at (get st "s") (get st "i")) "]"))
(begin (dict-set! st "i" (+ (get st "i") 1)) result))
(else (begin (js-json-parse-array-loop st result) result))))))
(define
js-json-parse-array-loop
(fn
(st result)
(append! result (js-json-parse-value st))
(js-json-skip-ws! st)
(let
((c (char-at (get st "s") (get st "i"))))
(cond
((= c ",")
(begin
(dict-set! st "i" (+ (get st "i") 1))
(js-json-skip-ws! st)
(js-json-parse-array-loop st result)))
((= c "]") (dict-set! st "i" (+ (get st "i") 1)))
(else (error "JSON: expected , or ]"))))))
(define
js-json-parse-object
(fn
(st)
(let
((result (dict)))
(dict-set! st "i" (+ (get st "i") 1))
(js-json-skip-ws! st)
(cond
((and (< (get st "i") (len (get st "s"))) (= (char-at (get st "s") (get st "i")) "}"))
(begin (dict-set! st "i" (+ (get st "i") 1)) result))
(else (begin (js-json-parse-object-loop st result) result))))))
(define
js-json-parse-object-loop
(fn
(st result)
(js-json-skip-ws! st)
(let
((k (js-json-parse-string st)))
(js-json-skip-ws! st)
(when
(not (= (char-at (get st "s") (get st "i")) ":"))
(error "JSON: expected :"))
(dict-set! st "i" (+ (get st "i") 1))
(let ((v (js-json-parse-value st))) (dict-set! result k v))
(js-json-skip-ws! st)
(let
((c (char-at (get st "s") (get st "i"))))
(cond
((= c ",")
(begin
(dict-set! st "i" (+ (get st "i") 1))
(js-json-parse-object-loop st result)))
((= c "}") (dict-set! st "i" (+ (get st "i") 1)))
(else (error "JSON: expected , or }")))))))
(define JSON {:parse js-json-parse :stringify js-json-stringify})
(define
js-promise-flush-callbacks!
(fn
@@ -2050,4 +2301,4 @@
(str "/" (get rx "source") "/" (get rx "flags")))
(else js-undefined))))
(define js-global {:isFinite js-global-is-finite :console console :Number Number :Math Math :Array Array :String String :NaN 0 :Infinity inf :isNaN js-global-is-nan :Object Object :undefined js-undefined})
(define js-global {:isFinite js-global-is-finite :console console :Number Number :parseFloat parseFloat :Math Math :Array Array :String String :NaN 0 :Infinity inf :isNaN js-global-is-nan :Object Object :parseInt parseInt :JSON JSON :undefined js-undefined})