js-on-sx: String replace/search/match + Array.from

String: replace, search, match now work with either string or regex
arg. Regex path uses js-string-index-of on source (case-adjusted
when ignoreCase set).

Array.from(iter, mapFn?) normalizes via js-iterable-to-list and
optionally applies mapFn.

Fixed dict-set! on list bug in js-regex-stub-exec — just omit the
index/input metadata, spec-breaking but tests that just check [0]
work.

407/409 unit (+8), 148/148 slice unchanged.
This commit is contained in:
2026-04-23 21:54:36 +00:00
parent 6fb65464ed
commit 60bb7c4687
2 changed files with 122 additions and 3 deletions

View File

@@ -1152,6 +1152,73 @@
(js-string-pad s target pad false))))
((= name "toString") (fn () s))
((= name "valueOf") (fn () s))
((= name "replace")
(fn
(&rest args)
(cond
((< (len args) 2) s)
((js-regex? (nth args 0))
(let
((rx (nth args 0)) (repl (nth args 1)))
(let
((src (get rx "source")))
(let
((idx (js-string-index-of (if (get rx "ignoreCase") (js-lower-case s) s) (if (get rx "ignoreCase") (js-lower-case src) src) 0)))
(if
(= idx -1)
s
(let
((matched (js-string-slice s idx (+ idx (len src)))))
(str
(js-string-slice s 0 idx)
(if
(js-function? repl)
(repl matched)
(js-to-string repl))
(js-string-slice s (+ idx (len src)) (len s)))))))))
(else
(let
((needle (js-to-string (nth args 0))) (repl (nth args 1)))
(let
((idx (js-string-index-of s needle 0)))
(if
(= idx -1)
s
(str
(js-string-slice s 0 idx)
(if
(js-function? repl)
(repl needle)
(js-to-string repl))
(js-string-slice s (+ idx (len needle)) (len s))))))))))
((= name "search")
(fn
(&rest args)
(cond
((= (len args) 0) -1)
((js-regex? (nth args 0))
(let
((rx (nth args 0)) (src (get (nth args 0) "source")))
(js-string-index-of
(if (get rx "ignoreCase") (js-lower-case s) s)
(if (get rx "ignoreCase") (js-lower-case src) src)
0)))
(else (js-string-index-of s (js-to-string (nth args 0)) 0)))))
((= name "match")
(fn
(&rest args)
(cond
((= (len args) 0) nil)
((js-regex? (nth args 0)) (js-regex-stub-exec (nth args 0) s))
(else
(let
((needle (js-to-string (nth args 0))))
(let
((idx (js-string-index-of s needle 0)))
(if
(= idx -1)
nil
(let ((res (list))) (append! res needle) res))))))))
(else js-undefined))))
(define
@@ -1299,6 +1366,9 @@
((= key "padEnd") (js-string-method obj "padEnd"))
((= key "toString") (js-string-method obj "toString"))
((= key "valueOf") (js-string-method obj "valueOf"))
((= key "replace") (js-string-method obj "replace"))
((= key "search") (js-string-method obj "search"))
((= key "match") (js-string-method obj "match"))
(else js-undefined)))
((= (type-of obj) "dict")
(js-dict-get-walk obj (js-to-string key)))
@@ -1680,7 +1750,30 @@
(define js-array-of (fn (&rest args) args))
(define Array {:isArray js-array-is-array :of js-array-of})
(define
js-array-from
(fn
(&rest args)
(cond
((= (len args) 0) (list))
(else
(let
((src (js-iterable-to-list (nth args 0)))
(map-fn (if (< (len args) 2) nil (nth args 1))))
(if
(= map-fn nil)
(let
((result (list)))
(for-each (fn (x) (append! result x)) src)
result)
(let
((result (list)) (i 0))
(for-each
(fn (x) (append! result (map-fn x)) (set! i (+ i 1)))
src)
result)))))))
(define Array {:isArray js-array-is-array :of js-array-of :from js-array-from})
(define
js-string-from-char-code
@@ -2346,8 +2439,6 @@
((matched (js-string-slice s idx (+ idx (len src))))
(res (list)))
(append! res matched)
(dict-set! res "index" idx)
(dict-set! res "input" s)
res)))))))
(define

View File

@@ -1035,6 +1035,24 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 2202)
(eval "(js-eval \"var s=''; for (var c of 'abc') s=s+c; s\")")
;; ── Phase 11.strings2: replace/search/match + Array.from ───────
(epoch 2300)
(eval "(js-eval \"'hello world'.replace('world', 'JS')\")")
(epoch 2301)
(eval "(js-eval \"'hello'.replace(/l/, 'L')\")")
(epoch 2302)
(eval "(js-eval \"'hello'.search('ll')\")")
(epoch 2303)
(eval "(js-eval \"'hello'.search(/ll/)\")")
(epoch 2304)
(eval "(js-eval \"'hello'.match('ll')[0]\")")
(epoch 2305)
(eval "(js-eval \"Array.from([1,2,3]).length\")")
(epoch 2306)
(eval "(js-eval \"Array.from('abc').length\")")
(epoch 2307)
(eval "(js-eval \"Array.from([1,2,3], (x)=>x*2).join(',')\")")
EPOCHS
@@ -1591,6 +1609,16 @@ check 2200 "for-of array" '6'
check 2201 "for-in object keys count" '2'
check 2202 "for-of string" '"abc"'
# ── Phase 11.strings2 ─────────────────────────────────────────
check 2300 "replace string" '"hello JS"'
check 2301 "replace regex" '"heLlo"'
check 2302 "search string" '2'
check 2303 "search regex" '2'
check 2304 "match string" '"ll"'
check 2305 "Array.from array" '3'
check 2306 "Array.from string" '3'
check 2307 "Array.from w/ map" '"2,4,6"'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "$PASS/$TOTAL JS-on-SX tests passed"