js-on-sx: JSON.stringify replacer (fn+array), space, toJSON
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s

This commit is contained in:
2026-05-10 07:04:14 +00:00
parent 769559bae7
commit fb8bb9f105
2 changed files with 195 additions and 40 deletions

View File

@@ -5240,54 +5240,207 @@
js-json-stringify
(fn
(&rest args)
(if
(= (len args) 0)
js-undefined
(js-json-stringify-value (nth args 0)))))
(let
((value (if (= (len args) 0) :js-undefined (nth args 0)))
(replacer (if (< (len args) 2) :js-undefined (nth args 1)))
(space-raw (if (< (len args) 3) :js-undefined (nth args 2))))
(let
((rep-fn (if (js-function? replacer) replacer nil))
(rep-keys (if (list? replacer) (js-json-prop-list replacer) nil))
(gap (js-json-space-gap space-raw)))
(let
((wrapper (dict)))
(begin
(dict-set! wrapper "" value)
(js-json-serialize-property "" wrapper rep-fn rep-keys gap "")))))))
(define
js-json-stringify-value
js-json-prop-list
(fn
(arr)
(let
((out (list)))
(begin
(for-each
(fn
(k)
(cond
((= (type-of k) "string")
(if (js-list-contains? out k) nil (append! out k)))
((number? k)
(let ((s (js-number-to-string k)))
(if (js-list-contains? out s) nil (append! out s))))
((dict? k)
(cond
((contains? (keys k) "__js_string_value__")
(let ((s (get k "__js_string_value__")))
(if (js-list-contains? out s) nil (append! out s))))
((contains? (keys k) "__js_number_value__")
(let ((s (js-number-to-string (get k "__js_number_value__"))))
(if (js-list-contains? out s) nil (append! out s))))
(else nil)))
(else nil)))
arr)
out))))
(define
js-list-contains?
(fn
(lst v)
(cond
((empty? lst) false)
((= (first lst) v) true)
(else (js-list-contains? (rest lst) v)))))
(define
js-json-space-gap
(fn
(sp)
(cond
((js-undefined? sp) "")
((= sp nil) "")
((number? sp)
(let
((n (cond ((js-number-is-nan sp) 0) ((< sp 0) 0) ((> sp 10) 10) (else (floor sp)))))
(js-string-repeat " " n)))
((and (dict? sp) (contains? (keys sp) "__js_number_value__"))
(js-json-space-gap (get sp "__js_number_value__")))
((and (dict? sp) (contains? (keys sp) "__js_string_value__"))
(js-json-space-gap (get sp "__js_string_value__")))
((= (type-of sp) "string")
(if (> (len sp) 10) (js-string-slice sp 0 10) sp))
(else ""))))
(define
js-string-repeat
(fn
(s n)
(if (<= n 0) "" (str s (js-string-repeat s (- n 1))))))
(define
js-json-unwrap-primitive
(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)
((not (dict? v)) v)
((contains? (keys v) "__js_number_value__")
(get v "__js_number_value__"))
((contains? (keys v) "__js_string_value__")
(get v "__js_string_value__"))
((contains? (keys v) "__js_boolean_value__")
(get v "__js_boolean_value__"))
(else v))))
(define
js-json-serialize-property
(fn
(key holder rep-fn rep-keys gap indent)
(let
((value0 (if (dict? holder) (get holder key) (if (list? holder) (nth holder (js-num-to-int (js-to-number key))) :js-undefined))))
(let
((value1
(cond
((and
(or (dict? value0) (list? value0))
(let ((tj (js-get-prop value0 "toJSON")))
(and (not (js-undefined? tj)) (js-function? tj))))
(js-call-with-this value0 (js-get-prop value0 "toJSON") (list key)))
(else value0))))
(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)
((value
(if rep-fn
(js-call-with-this holder rep-fn (list key value1))
value1)))
(let
((vu (js-json-unwrap-primitive value)))
(cond
((= vu nil) "null")
((js-undefined? vu) :js-undefined)
((= (type-of vu) "boolean") (if vu "true" "false"))
((or (number? vu) (= (type-of vu) "rational"))
(let ((n (if (= (type-of vu) "rational") (exact->inexact vu) vu)))
(cond
((js-number-is-nan n) "null")
((= n (js-infinity-value)) "null")
((= n (- 0 (js-infinity-value))) "null")
(else (js-number-to-string n)))))
((= (type-of vu) "string") (js-json-escape-string vu))
((js-function? vu) :js-undefined)
((list? vu)
(js-json-serialize-array vu rep-fn rep-keys gap indent))
((dict? vu)
(js-json-serialize-object vu rep-fn rep-keys gap indent))
(else :js-undefined))))))))
(define
js-json-serialize-array
(fn
(arr rep-fn rep-keys gap indent)
(let
((step-back indent) (new-indent (str indent gap)) (parts (list)))
(begin
(js-json-array-loop arr rep-fn rep-keys gap new-indent 0 parts)
(cond
((empty? parts) "[]")
((= gap "")
(str "[" (join "," parts) "]"))
(else
(str
"[\n"
new-indent
(join (str ",\n" new-indent) parts)
"\n"
step-back
"]")))))))
(define
js-json-array-loop
(fn
(arr rep-fn rep-keys gap new-indent i parts)
(cond
((>= i (len arr)) nil)
(else
(let
((parts (list)))
(for-each
(fn
(k)
(if
(js-key-internal? k)
nil
((s (js-json-serialize-property (js-number-to-string i) arr rep-fn rep-keys gap new-indent)))
(begin
(if (js-undefined? s) (append! parts "null") (append! parts s))
(js-json-array-loop arr rep-fn rep-keys gap new-indent (+ i 1) parts)))))))
(define
js-json-serialize-object
(fn
(obj rep-fn rep-keys gap indent)
(let
((step-back indent) (new-indent (str indent gap)) (parts (list))
(sep (if (= gap "") ":" ": "))
(key-list (if rep-keys rep-keys (js-object-keys obj))))
(begin
(for-each
(fn
(k)
(cond
((js-key-internal? k) nil)
(else
(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)))))))
(js-object-keys v))
(str "{" (join "," parts) "}")))
(else "null"))))
((s (js-json-serialize-property k obj rep-fn rep-keys gap new-indent)))
(if
(js-undefined? s)
nil
(append! parts (str (js-json-escape-string k) sep s)))))))
key-list)
(cond
((empty? parts) "{}")
((= gap "")
(str "{" (join "," parts) "}"))
(else
(str
"{\n"
new-indent
(join (str ",\n" new-indent) parts)
"\n"
step-back
"}")))))))
(define
js-json-escape-string

View File

@@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green.
Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta.
- 2026-05-10 — **`JSON.stringify` honours `replacer` (function + array forms), `space`, and `toJSON`.** Previous impl ignored the second/third arguments entirely and never called `toJSON`. Rewrote around a `js-json-serialize-property(key, holder, rep-fn, rep-keys, gap, indent)` core: walks `toJSON` first, then replacer-fn (with `holder` as `this`); arrays-as-replacer become a property-name allowlist; numeric `space` clamped to 0..10 spaces, string `space` truncated to 10 chars, non-empty gap activates indented output with `:``: ` separator. Number wrapper / String wrapper / Boolean wrapper unwrap before serialization; non-finite numbers serialize as `"null"`; functions serialize as `undefined`. Result: built-ins/JSON/stringify 6/30 → 14/30 (+8). conformance.sh: 148/148.
- 2026-05-10 — **`JSON.parse` raises spec-correct `SyntaxError` instances and rejects malformed input.** Previously `JSON.parse("12 34")` silently returned `12` (no trailing-content check), `JSON.parse('""')` accepted control chars in strings, an unterminated string read off the end, and the inner `(error "JSON: ...")` calls produced generic Errors not `instanceof SyntaxError`. Added: (1) post-value whitespace skip + trailing-content check in `js-json-parse`; (2) control-char rejection (code < 0x20) and unterminated-string check in `js-json-parse-string-loop`; (3) all internal "JSON: ..." errors now `(raise (js-new-call SyntaxError ...))`. Result: built-ins/JSON/parse 7/30 → 25/30 (+18). JSON 26/30. conformance.sh: 148/148.
- 2026-05-10 — **`arguments` object inside functions is now a mutable list.** `js-arguments-build-form` produced `(cons p1 (cons p2 __extra_args__))` which yielded a structurally-shared (immutable) list — `arguments[1] = 7; arguments[1]++` raised "set-nth!: list is immutable". Wrapping the build in `js-list-copy` so each function entry constructs a fresh mutable list. Existing reads (`arguments.length`, `arguments[i]`) unaffected. Result: language/expressions/postfix-increment 14/30 → 15/30. conformance.sh: 148/148.