Files
rose-ash/lib/js/stdlib.sx
giles 36e6762539
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 38s
phase-22 JS: stdlib.sx bitwise/Map/Set/RegExp + 25 tests
lib/js/stdlib.sx (36 forms):
- Bitwise ops (js-bitand/bitor/bitxor/lshift/rshift/urshift/bitnot) use
  truncate instead of js-num-to-int (which calls integer /0 and crashes).
- Map class: dict-backed list-of-pairs with linear-scan find, mutable via
  dict-set!; js-map-new/get/set!/has/delete!/clear/keys/vals/entries/for-each.
- Set class: backed by SX make-set primitive; set-member?/set-add!/set-remove!
  all take (set item) argument order — fixed from (item set) which threw.
- RegExp: callable lambda wrapping js-regex-new (not a dict, so directly callable).
- Wires Map/Set/RegExp into js-global.

lib/js/test.sh: epochs 6000-6032 (25 tests) — all pass.
Result: 492/585 tests pass (was 466/560 before this phase).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 22:33:27 +00:00

240 lines
8.1 KiB
Plaintext

;; lib/js/stdlib.sx — Phase 22 JS additions
;;
;; Adds to lib/js/runtime.sx (already loaded):
;; 1. Bitwise binary ops (js-bitand/bitor/bitxor/lshift/rshift/urshift/bitnot)
;; 2. Map class (arbitrary-key hash map via list of pairs)
;; 3. Set class (uniqueness collection via SX make-set)
;; 4. RegExp constructor (wraps js-regex-new already in runtime)
;; 5. Wires Map / Set / RegExp into js-global
;; ---------------------------------------------------------------------------
;; 1. Bitwise binary ops
;; JS coerces operands to 32-bit signed int before applying the op.
;; Use truncate (not js-num-to-int) since integer / 0 crashes the evaluator.
;; ---------------------------------------------------------------------------
(define
(js-bitand a b)
(bitwise-and (truncate (js-to-number a)) (truncate (js-to-number b))))
(define
(js-bitor a b)
(bitwise-or (truncate (js-to-number a)) (truncate (js-to-number b))))
(define
(js-bitxor a b)
(bitwise-xor (truncate (js-to-number a)) (truncate (js-to-number b))))
;; << : left-shift by (b mod 32) positions
(define
(js-lshift a b)
(arithmetic-shift
(truncate (js-to-number a))
(modulo (truncate (js-to-number b)) 32)))
;; >> : arithmetic right-shift (sign-extending)
(define
(js-rshift a b)
(arithmetic-shift
(truncate (js-to-number a))
(- 0 (modulo (truncate (js-to-number b)) 32))))
;; >>> : logical right-shift (zero-extending)
;; Convert to uint32 first, then divide by 2^n.
(define
(js-urshift a b)
(let
((u32 (modulo (truncate (js-to-number a)) 4294967296))
(n (modulo (truncate (js-to-number b)) 32)))
(quotient u32 (arithmetic-shift 1 n))))
;; ~ : bitwise NOT — equivalent to -(n+1) in 32-bit signed arithmetic
(define (js-bitnot a) (bitwise-not (truncate (js-to-number a))))
;; ---------------------------------------------------------------------------
;; 2. Map class
;; Stored as {:__js_map__ true :size N :_pairs (list (list key val) ...)}
;; Mutation via dict-set! on the underlying dict.
;; ---------------------------------------------------------------------------
(define
(js-map-new)
(let
((m (dict)))
(dict-set! m "__js_map__" true)
(dict-set! m "size" 0)
(dict-set! m "_pairs" (list))
m))
(define (js-map? v) (and (dict? v) (dict-has? v "__js_map__")))
;; Linear scan for key using ===; returns index or -1
(define
(js-map-find-idx pairs k)
(letrec
((go (fn (ps i) (cond ((= (len ps) 0) -1) ((js-strict-eq (first (first ps)) k) i) (else (go (rest ps) (+ i 1)))))))
(go pairs 0)))
(define
(js-map-get m k)
(letrec
((go (fn (ps) (if (= (len ps) 0) js-undefined (if (js-strict-eq (first (first ps)) k) (nth (first ps) 1) (go (rest ps)))))))
(go (get m "_pairs"))))
;; Replace element at index i in list
(define
(js-list-set-nth lst i newval)
(letrec
((go (fn (ps j) (if (= (len ps) 0) (list) (cons (if (= j i) newval (first ps)) (go (rest ps) (+ j 1)))))))
(go lst 0)))
;; Remove element at index i from list
(define
(js-list-remove-nth lst i)
(letrec
((go (fn (ps j) (if (= (len ps) 0) (list) (if (= j i) (go (rest ps) (+ j 1)) (cons (first ps) (go (rest ps) (+ j 1))))))))
(go lst 0)))
(define
(js-map-set! m k v)
(let
((pairs (get m "_pairs")) (idx (js-map-find-idx (get m "_pairs") k)))
(if
(= idx -1)
(begin
(dict-set! m "_pairs" (append pairs (list (list k v))))
(dict-set! m "size" (+ (get m "size") 1)))
(dict-set! m "_pairs" (js-list-set-nth pairs idx (list k v)))))
m)
(define
(js-map-has m k)
(not (= (js-map-find-idx (get m "_pairs") k) -1)))
(define
(js-map-delete! m k)
(let
((idx (js-map-find-idx (get m "_pairs") k)))
(when
(not (= idx -1))
(dict-set! m "_pairs" (js-list-remove-nth (get m "_pairs") idx))
(dict-set! m "size" (- (get m "size") 1))))
m)
(define
(js-map-clear! m)
(dict-set! m "_pairs" (list))
(dict-set! m "size" 0)
m)
(define (js-map-keys m) (map first (get m "_pairs")))
(define
(js-map-vals m)
(map (fn (p) (nth p 1)) (get m "_pairs")))
(define (js-map-entries m) (get m "_pairs"))
(define
(js-map-for-each m cb)
(for-each
(fn (p) (cb (nth p 1) (first p) m))
(get m "_pairs"))
js-undefined)
;; Map method dispatch (called from js-object-method-call in runtime)
(define
(js-map-method m name args)
(cond
((= name "set")
(js-map-set! m (nth args 0) (nth args 1)))
((= name "get") (js-map-get m (nth args 0)))
((= name "has") (js-map-has m (nth args 0)))
((= name "delete") (js-map-delete! m (nth args 0)))
((= name "clear") (js-map-clear! m))
((= name "keys") (js-map-keys m))
((= name "values") (js-map-vals m))
((= name "entries") (js-map-entries m))
((= name "forEach") (js-map-for-each m (nth args 0)))
((= name "toString") "[object Map]")
(else js-undefined)))
(define Map {:__callable__ (fn (&rest args) (let ((m (js-map-new))) (when (and (> (len args) 0) (list? (nth args 0))) (for-each (fn (entry) (js-map-set! m (nth entry 0) (nth entry 1))) (nth args 0))) m)) :prototype {:entries (fn (&rest a) (js-map-entries (js-this))) :delete (fn (&rest a) (js-map-delete! (js-this) (nth a 0))) :get (fn (&rest a) (js-map-get (js-this) (nth a 0))) :values (fn (&rest a) (js-map-vals (js-this))) :toString (fn () "[object Map]") :has (fn (&rest a) (js-map-has (js-this) (nth a 0))) :set (fn (&rest a) (js-map-set! (js-this) (nth a 0) (nth a 1))) :forEach (fn (&rest a) (js-map-for-each (js-this) (nth a 0))) :clear (fn (&rest a) (js-map-clear! (js-this))) :keys (fn (&rest a) (js-map-keys (js-this)))}})
;; ---------------------------------------------------------------------------
;; 3. Set class
;; {:__js_set__ true :size N :_set <sx-set>}
;; Note: set-member?/set-add!/set-remove! all take (set item) order.
;; ---------------------------------------------------------------------------
(define
(js-set-new)
(let
((s (dict)))
(dict-set! s "__js_set__" true)
(dict-set! s "size" 0)
(dict-set! s "_set" (make-set))
s))
(define (js-set? v) (and (dict? v) (dict-has? v "__js_set__")))
(define
(js-set-add! s v)
(let
((sx (get s "_set")))
(when
(not (set-member? sx v))
(set-add! sx v)
(dict-set! s "size" (+ (get s "size") 1))))
s)
(define (js-set-has s v) (set-member? (get s "_set") v))
(define
(js-set-delete! s v)
(let
((sx (get s "_set")))
(when
(set-member? sx v)
(set-remove! sx v)
(dict-set! s "size" (- (get s "size") 1))))
s)
(define
(js-set-clear! s)
(dict-set! s "_set" (make-set))
(dict-set! s "size" 0)
s)
(define (js-set-vals s) (set->list (get s "_set")))
(define
(js-set-for-each s cb)
(for-each (fn (v) (cb v v s)) (set->list (get s "_set")))
js-undefined)
(define Set {:__callable__ (fn (&rest args) (let ((s (js-set-new))) (when (and (> (len args) 0) (list? (nth args 0))) (for-each (fn (v) (js-set-add! s v)) (nth args 0))) s)) :prototype {:entries (fn (&rest a) (map (fn (v) (list v v)) (js-set-vals (js-this)))) :delete (fn (&rest a) (js-set-delete! (js-this) (nth a 0))) :values (fn (&rest a) (js-set-vals (js-this))) :add (fn (&rest a) (js-set-add! (js-this) (nth a 0))) :toString (fn () "[object Set]") :has (fn (&rest a) (js-set-has (js-this) (nth a 0))) :forEach (fn (&rest a) (js-set-for-each (js-this) (nth a 0))) :clear (fn (&rest a) (js-set-clear! (js-this))) :keys (fn (&rest a) (js-set-vals (js-this)))}})
;; ---------------------------------------------------------------------------
;; 4. RegExp constructor — callable lambda wrapping js-regex-new
;; ---------------------------------------------------------------------------
(define
RegExp
(fn
(&rest args)
(cond
((= (len args) 0) (js-regex-new "" ""))
((= (len args) 1)
(js-regex-new (js-to-string (nth args 0)) ""))
(else
(js-regex-new
(js-to-string (nth args 0))
(js-to-string (nth args 1)))))))
;; ---------------------------------------------------------------------------
;; 5. Wire new globals into js-global
;; ---------------------------------------------------------------------------
(dict-set! js-global "Map" Map)
(dict-set! js-global "Set" Set)
(dict-set! js-global "RegExp" RegExp)