js-on-sx: String.prototype extensions + Object/Array builtins
Strings: includes, startsWith, endsWith, trim, trimStart, trimEnd, repeat, padStart, padEnd, toString, valueOf. Object: keys, values, entries, assign, freeze (no-op). Array: isArray, of. All wired into js-global. 17 new unit tests. 357/359 unit (+17), 148/148 slice unchanged.
This commit is contained in:
@@ -885,6 +885,36 @@
|
||||
((>= i (len arr)) acc)
|
||||
(else (js-list-reduce-loop f (f acc (nth arr i)) arr (+ i 1))))))
|
||||
|
||||
(define
|
||||
js-string-repeat
|
||||
(fn
|
||||
(s n acc)
|
||||
(if (<= n 0) acc (js-string-repeat s (- n 1) (str acc s)))))
|
||||
|
||||
(define
|
||||
js-string-pad
|
||||
(fn
|
||||
(s target pad at-start)
|
||||
(let
|
||||
((slen (len s)))
|
||||
(if
|
||||
(or (<= target slen) (= (len pad) 0))
|
||||
s
|
||||
(let
|
||||
((needed (- target slen)))
|
||||
(let
|
||||
((padding (js-string-pad-build pad needed "")))
|
||||
(if at-start (str padding s) (str s padding))))))))
|
||||
|
||||
(define
|
||||
js-string-pad-build
|
||||
(fn
|
||||
(pad needed acc)
|
||||
(cond
|
||||
((<= needed 0) acc)
|
||||
((>= (len acc) needed) (js-string-slice acc 0 needed))
|
||||
(else (js-string-pad-build pad needed (str acc pad))))))
|
||||
|
||||
(define
|
||||
js-string-method
|
||||
(fn
|
||||
@@ -933,6 +963,51 @@
|
||||
((= name "split") (fn (sep) (js-string-split s (js-to-string sep))))
|
||||
((= name "concat")
|
||||
(fn (&rest args) (js-string-concat-loop s args 0)))
|
||||
((= name "includes")
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((needle (if (= (len args) 0) "" (js-to-string (nth args 0)))))
|
||||
(>= (js-string-index-of s needle 0) 0))))
|
||||
((= name "startsWith")
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((needle (if (= (len args) 0) "" (js-to-string (nth args 0))))
|
||||
(start (if (< (len args) 2) 0 (js-num-to-int (nth args 1)))))
|
||||
(js-string-matches? s needle start 0))))
|
||||
((= name "endsWith")
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((needle (if (= (len args) 0) "" (js-to-string (nth args 0)))))
|
||||
(let
|
||||
((end-len (len s)) (n-len (len needle)))
|
||||
(if
|
||||
(> n-len end-len)
|
||||
false
|
||||
(js-string-matches? s needle (- end-len n-len) 0))))))
|
||||
((= name "trim") (fn () (js-trim s)))
|
||||
((= name "trimStart") (fn () (js-trim-left s)))
|
||||
((= name "trimEnd") (fn () (js-trim-right s)))
|
||||
((= name "repeat")
|
||||
(fn (n) (js-string-repeat s (js-num-to-int n) "")))
|
||||
((= name "padStart")
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
|
||||
(pad (if (< (len args) 2) " " (js-to-string (nth args 1)))))
|
||||
(js-string-pad s target pad true))))
|
||||
((= name "padEnd")
|
||||
(fn
|
||||
(&rest args)
|
||||
(let
|
||||
((target (if (= (len args) 0) 0 (js-num-to-int (nth args 0))))
|
||||
(pad (if (< (len args) 2) " " (js-to-string (nth args 1)))))
|
||||
(js-string-pad s target pad false))))
|
||||
((= name "toString") (fn () s))
|
||||
((= name "valueOf") (fn () s))
|
||||
(else js-undefined))))
|
||||
|
||||
(define
|
||||
@@ -1061,6 +1136,17 @@
|
||||
((= key "toLowerCase") (js-string-method obj "toLowerCase"))
|
||||
((= key "split") (js-string-method obj "split"))
|
||||
((= key "concat") (js-string-method obj "concat"))
|
||||
((= key "includes") (js-string-method obj "includes"))
|
||||
((= key "startsWith") (js-string-method obj "startsWith"))
|
||||
((= key "endsWith") (js-string-method obj "endsWith"))
|
||||
((= key "trim") (js-string-method obj "trim"))
|
||||
((= key "trimStart") (js-string-method obj "trimStart"))
|
||||
((= key "trimEnd") (js-string-method obj "trimEnd"))
|
||||
((= key "repeat") (js-string-method obj "repeat"))
|
||||
((= key "padStart") (js-string-method obj "padStart"))
|
||||
((= key "padEnd") (js-string-method obj "padEnd"))
|
||||
((= key "toString") (js-string-method obj "toString"))
|
||||
((= key "valueOf") (js-string-method obj "valueOf"))
|
||||
(else js-undefined)))
|
||||
((= (type-of obj) "dict")
|
||||
(js-dict-get-walk obj (js-to-string key)))
|
||||
@@ -1347,6 +1433,80 @@
|
||||
(dict-set! p "value" reason)
|
||||
(js-promise-flush-callbacks! p))))))
|
||||
|
||||
(define
|
||||
js-object-keys
|
||||
(fn
|
||||
(o)
|
||||
(cond
|
||||
((dict? o)
|
||||
(let
|
||||
((result (list)))
|
||||
(for-each (fn (k) (append! result k)) (keys o))
|
||||
result))
|
||||
(else (list)))))
|
||||
|
||||
(define
|
||||
js-object-values
|
||||
(fn
|
||||
(o)
|
||||
(cond
|
||||
((dict? o)
|
||||
(let
|
||||
((result (list)))
|
||||
(for-each (fn (k) (append! result (get o k))) (keys o))
|
||||
result))
|
||||
(else (list)))))
|
||||
|
||||
(define
|
||||
js-object-entries
|
||||
(fn
|
||||
(o)
|
||||
(cond
|
||||
((dict? o)
|
||||
(let
|
||||
((result (list)))
|
||||
(for-each
|
||||
(fn
|
||||
(k)
|
||||
(let
|
||||
((pair (list)))
|
||||
(append! pair k)
|
||||
(append! pair (get o k))
|
||||
(append! result pair)))
|
||||
(keys o))
|
||||
result))
|
||||
(else (list)))))
|
||||
|
||||
(define
|
||||
js-object-assign
|
||||
(fn
|
||||
(&rest args)
|
||||
(cond
|
||||
((= (len args) 0) (dict))
|
||||
(else
|
||||
(let
|
||||
((target (nth args 0)))
|
||||
(for-each
|
||||
(fn
|
||||
(src)
|
||||
(when
|
||||
(dict? src)
|
||||
(for-each
|
||||
(fn (k) (dict-set! target k (get src k)))
|
||||
(keys src))))
|
||||
(rest args))
|
||||
target)))))
|
||||
|
||||
(define js-object-freeze (fn (o) o))
|
||||
|
||||
(define Object {:entries js-object-entries :values js-object-values :freeze js-object-freeze :assign js-object-assign :keys js-object-keys})
|
||||
|
||||
(define js-array-is-array (fn (v) (list? v)))
|
||||
|
||||
(define js-array-of (fn (&rest args) args))
|
||||
|
||||
(define Array {:isArray js-array-is-array :of js-array-of})
|
||||
|
||||
(define
|
||||
js-promise-flush-callbacks!
|
||||
(fn
|
||||
@@ -1761,4 +1921,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 :NaN 0 :Infinity inf :isNaN js-global-is-nan :undefined js-undefined})
|
||||
(define js-global {:isFinite js-global-is-finite :console console :Number Number :Math Math :Array Array :NaN 0 :Infinity inf :isNaN js-global-is-nan :Object Object :undefined js-undefined})
|
||||
|
||||
@@ -899,6 +899,44 @@ cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1310)
|
||||
(eval "(js-eval \"var sum = 0; for (var i = 1; i <= 5; i++) sum = sum + i; sum\")")
|
||||
|
||||
;; ── Phase 11.strings: extended String.prototype methods ─────────
|
||||
(epoch 1400)
|
||||
(eval "(js-eval \"'hello world'.includes('world')\")")
|
||||
(epoch 1401)
|
||||
(eval "(js-eval \"'hello world'.includes('xyz')\")")
|
||||
(epoch 1402)
|
||||
(eval "(js-eval \"'hello'.startsWith('hel')\")")
|
||||
(epoch 1403)
|
||||
(eval "(js-eval \"'hello'.startsWith('llo')\")")
|
||||
(epoch 1404)
|
||||
(eval "(js-eval \"'hello'.endsWith('llo')\")")
|
||||
(epoch 1405)
|
||||
(eval "(js-eval \"'hello'.endsWith('hel')\")")
|
||||
(epoch 1406)
|
||||
(eval "(js-eval \"' hi '.trim()\")")
|
||||
(epoch 1407)
|
||||
(eval "(js-eval \"'abc'.repeat(3)\")")
|
||||
(epoch 1408)
|
||||
(eval "(js-eval \"'5'.padStart(3, '0')\")")
|
||||
(epoch 1409)
|
||||
(eval "(js-eval \"'5'.padEnd(3, 'x')\")")
|
||||
(epoch 1410)
|
||||
(eval "(js-eval \"'hello'.toString()\")")
|
||||
|
||||
;; ── Phase 11.object: Object builtin ─────────────────────────────
|
||||
(epoch 1500)
|
||||
(eval "(js-eval \"Object.keys({a:1, b:2}).length\")")
|
||||
(epoch 1501)
|
||||
(eval "(js-eval \"Object.values({a:1, b:2}).length\")")
|
||||
(epoch 1502)
|
||||
(eval "(js-eval \"var o = Object.assign({a:1}, {b:2}); o.a + o.b\")")
|
||||
(epoch 1503)
|
||||
(eval "(js-eval \"Array.isArray([1,2])\")")
|
||||
(epoch 1504)
|
||||
(eval "(js-eval \"Array.isArray('abc')\")")
|
||||
(epoch 1505)
|
||||
(eval "(js-eval \"Array.of(1,2,3).length\")")
|
||||
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
@@ -1377,6 +1415,27 @@ check 1308 "a[1]++" '3'
|
||||
check 1309 "for-loop x++" '8'
|
||||
check 1310 "for-loop accumulator" '15'
|
||||
|
||||
# ── Phase 11.strings: extended String.prototype ─────────────────
|
||||
check 1400 "includes match" 'true'
|
||||
check 1401 "includes no match" 'false'
|
||||
check 1402 "startsWith match" 'true'
|
||||
check 1403 "startsWith no match" 'false'
|
||||
check 1404 "endsWith match" 'true'
|
||||
check 1405 "endsWith no match" 'false'
|
||||
check 1406 "trim whitespace" '"hi"'
|
||||
check 1407 "repeat 3x" '"abcabcabc"'
|
||||
check 1408 "padStart 0s" '"005"'
|
||||
check 1409 "padEnd x" '"5xx"'
|
||||
check 1410 "toString self" '"hello"'
|
||||
|
||||
# ── Phase 11.object: Object + Array ─────────────────────────────
|
||||
check 1500 "Object.keys count" '2'
|
||||
check 1501 "Object.values.length" '2'
|
||||
check 1502 "Object.assign merge" '3'
|
||||
check 1503 "Array.isArray yes" 'true'
|
||||
check 1504 "Array.isArray no" 'false'
|
||||
check 1505 "Array.of length" '3'
|
||||
|
||||
TOTAL=$((PASS + FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "✓ $PASS/$TOTAL JS-on-SX tests passed"
|
||||
|
||||
@@ -179,6 +179,8 @@ Append-only record of completed iterations. Loop writes one line per iteration:
|
||||
|
||||
- 2026-04-23 — **Postfix/prefix `++` / `--`.** Parser: postfix branch in `jp-parse-postfix` (matches `op ++`/`--` after the current expression and emits `(js-postfix op target)`), prefix branch in `jp-parse-primary` *before* the unary-`-/+/!/~` path emits `(js-prefix op target)`. Transpile: `js-transpile-prefix` emits `(set! sxname (+ (js-to-number sxname) ±1))` for idents, `(js-set-prop obj key (+ (js-to-number (js-get-prop obj key)) ±1))` for members/indices. `js-transpile-postfix` uses a `let` binding to cache the old value via `js-to-number`, then updates and returns the saved value — covers ident, member, and index targets. 11 new unit tests (ident inc/dec, pre vs post return value, obj.key, a[i], in `for(;; i++)`, accumulator loop), **340/342** (329→+11). Conformance unchanged.
|
||||
|
||||
- 2026-04-23 — **String.prototype extensions + Object/Array builtins.** Strings: added `includes`, `startsWith`, `endsWith`, `trim`, `trimStart`, `trimEnd`, `repeat`, `padStart`, `padEnd`, `toString`, `valueOf` to `js-string-method` dispatch and corresponding `js-get-prop` string-branch keys. Helpers: `js-string-repeat` (tail-recursive concat), `js-string-pad` + `js-string-pad-build`. Object: `Object.keys / .values / .entries / .assign / .freeze` (freeze is a no-op — we don't track sealed state). Array: `Array.isArray` (backed by `list?`), `Array.of` (varargs → list). Wired into `js-global`. 17 new unit tests, **357/359** (340→+17). Conformance unchanged. Gotcha: SX's `keys` primitive returns most-recently-inserted-first, so `Object.keys({a:1, b:2})` comes back `["b", "a"]`. Test assertion has to check `.length` rather than the literal pair. If spec order matters for a real app, Object.keys would need its own ordered traversal.
|
||||
|
||||
## Phase 3-5 gotchas
|
||||
|
||||
Worth remembering for later phases:
|
||||
|
||||
Reference in New Issue
Block a user