HS: extend parser/runtime + new node test runner; ignore test-results/

- Parser: `--` line comments, `|` op, `result` → `the-result`, query-scoped
  `<sel> in <expr>`, `is a/an <type>` predicate, multi-`as` chaining with `|`,
  `match`/`precede` keyword aliases, `[attr]` add/toggle, between attr forms
- Runtime: per-element listener registry + hs-deactivate!, attr toggle
  variants, set-inner-html boots subtree, hs-append polymorphic on
  string/list/element, default? / array-set! / query-all-in / list-set
  via take+drop, hs-script idempotence guard
- Integration: skip reserved (me/it/event/you/yourself) when collecting vars
- Tokenizer: emit `--` comments and `|` op
- Test framework + conformance runner updates; new tests/hs-run-filtered.js
  (single-process Node runner using OCaml VM step-limit to bound infinite
  loops); generate-sx-conformance-dev.py improvements
- mcp_tree.ml + run_tests.ml: harness extensions
- .gitignore: top-level test-results/ (Playwright artifacts)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 07:11:07 +00:00
parent b2ae80fb21
commit 0515295317
20 changed files with 15224 additions and 8120 deletions

View File

@@ -22,7 +22,13 @@
;; Stock hyperscript queues by default; "every" disables queuing.
(define
hs-on
(fn (target event-name handler) (dom-listen target event-name handler)))
(fn
(target event-name handler)
(let
((unlisten (dom-listen target event-name handler))
(prev (or (dom-get-data target "hs-unlisteners") (list))))
(dom-set-data target "hs-unlisteners" (append prev (list unlisten)))
unlisten)))
;; Run an initializer function immediately.
;; (hs-init thunk) — called at element boot time
@@ -88,7 +94,7 @@
((or (= prop "display") (= prop "opacity"))
(if
(or (= cur "none") (= cur "0"))
(dom-set-style target prop (if (= prop "opacity") "1" ""))
(dom-set-style target prop (if (= prop "opacity") "1" "block"))
(dom-set-style target prop (if (= prop "display") "none" "0"))))
(true
(if
@@ -167,6 +173,45 @@
(fn
(el name val)
(if (nil? val) (dom-remove-attr el name) (dom-set-attr el name val))))
(define
hs-toggle-attr!
(fn
(el name)
(if
(dom-has-attr? el name)
(dom-remove-attr el name)
(dom-set-attr el name ""))))
(define
hs-toggle-attr-val!
(fn
(el name val)
(if
(= (dom-get-attr el name) val)
(dom-remove-attr el name)
(dom-set-attr el name val))))
(define
hs-toggle-attr-between!
(fn
(el name val1 val2)
(if
(= (dom-get-attr el name) val1)
(dom-set-attr el name val2)
(dom-set-attr el name val1))))
(define
hs-toggle-attr-diff!
(fn
(el n1 v1 n2 v2)
(if
(dom-has-attr? el n1)
(do (dom-remove-attr el n1) (dom-set-attr el n2 v2))
(do
(when (dom-has-attr? el n2) (dom-remove-attr el n2))
(dom-set-attr el n1 v1)))))
(define
hs-set-inner-html!
(fn
(target value)
(do (dom-set-inner-html target value) (hs-boot-subtree! target))))
(define
hs-put!
(fn
@@ -407,19 +452,24 @@
hs-query-all
(fn (sel) (host-call (dom-body) "querySelectorAll" sel)))
(define
hs-query-all-in
(fn
(sel target)
(if
(nil? target)
(hs-query-all sel)
(host-call target "querySelectorAll" sel))))
(define
hs-list-set
(fn (lst idx val) (map-indexed (fn (i x) (if (= i idx) val x)) lst)))
(fn
(lst idx val)
(append (take lst idx) (cons val (drop lst (+ idx 1))))))
(define
hs-to-number
(fn
(v)
(cond
((number? v) v)
((string? v) (or (parse-number v) 0))
((nil? v) 0)
(true (or (parse-number (str v)) 0)))))
(fn (v) (if (number? v) v (or (parse-number (str v)) 0))))
(define
hs-query-first
@@ -490,6 +540,10 @@
((= signal "hs-continue") (hs-repeat-while cond-fn thunk))
(true (hs-repeat-while cond-fn thunk)))))))
(define
hs-repeat-until
(fn
@@ -502,10 +556,6 @@
(if (cond-fn) nil (hs-repeat-until cond-fn thunk)))
(true (if (cond-fn) nil (hs-repeat-until cond-fn thunk)))))))
(define
hs-for-each
(fn
@@ -525,27 +575,38 @@
((= signal "hs-continue") (do-loop (rest remaining)))
(true (do-loop (rest remaining))))))))
(do-loop items))))
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
(begin
(define
hs-append
(fn
(target value)
(cond
((nil? target) value)
((string? target) (str target value))
((list? target) (append target (list value)))
((hs-element? target)
(do
(dom-insert-adjacent-html target "beforeend" (str value))
target))
(true (str target value)))))
(define
hs-append!
(fn (value target) (dom-insert-adjacent-html target "beforeend" value))))
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
(fn
(value target)
(cond
((nil? target) nil)
((hs-element? target)
(dom-insert-adjacent-html target "beforeend" (str value)))
(true nil)))))
;; DOM query stub — sandbox returns empty list
(define
hs-fetch
(fn
(url format)
(perform (list "io-fetch" url (if format format "text")))))
;; DOM query stub — sandbox returns empty list
;; Method dispatch — obj.method(args)
(define
hs-coerce
(fn
@@ -636,7 +697,24 @@
(map (fn (k) (list k (get value k))) (keys value))
value))
(true value))))
;; Method dispatch — obj.method(args)
;; ── 0.9.90 features ─────────────────────────────────────────────
;; beep! — debug logging, returns value unchanged
(define
hs-default?
(fn
(v)
(cond
((nil? v) true)
((and (string? v) (= v "")) true)
(true false))))
;; Property-based is — check obj.key truthiness
(define
hs-array-set!
(fn
(arr i v)
(if (list? arr) (do (set-nth! arr i v) v) (host-set! arr i v))))
;; Array slicing (inclusive both ends)
(define
hs-add
(fn
@@ -646,9 +724,7 @@
((list? b) (cons a b))
((or (string? a) (string? b)) (str a b))
(true (+ a b)))))
;; ── 0.9.90 features ─────────────────────────────────────────────
;; beep! — debug logging, returns value unchanged
;; Collection: sorted by
(define
hs-make
(fn
@@ -659,13 +735,13 @@
((= type-name "Set") (list))
((= type-name "Map") (dict))
(true (dict)))))
;; Property-based is — check obj.key truthiness
;; Collection: sorted by descending
(define hs-install (fn (behavior-fn) (behavior-fn me)))
;; Array slicing (inclusive both ends)
;; Collection: split by
(define
hs-measure
(fn (target) (perform (list (quote io-measure) target))))
;; Collection: sorted by
;; Collection: joined by
(define
hs-transition
(fn
@@ -678,7 +754,7 @@
(str prop " " (/ duration 1000) "s")))
(dom-set-style target prop value)
(when duration (hs-settle target))))
;; Collection: sorted by descending
(define
hs-transition-from
(fn
@@ -692,7 +768,7 @@
(str prop " " (/ duration 1000) "s")))
(dom-set-style target prop (str to-val))
(when duration (hs-settle target))))
;; Collection: split by
(define
hs-type-check
(fn
@@ -712,7 +788,7 @@
(= (host-typeof value) "element")
(= (host-typeof value) "text")))
(true (= (host-typeof value) (downcase type-name)))))))
;; Collection: joined by
(define
hs-type-check-strict
(fn
@@ -745,11 +821,26 @@
((nil? suffix) false)
(true (ends-with? (str s) (str suffix))))))
(define
hs-scoped-set!
(fn (el name val) (dom-set-data el (str "hs-local-" name) val)))
(define
hs-scoped-get
(fn (el name) (dom-get-data el (str "hs-local-" name))))
(define
hs-precedes?
(fn
(a b)
(cond ((nil? a) false) ((nil? b) false) (true (< (str a) (str b))))))
(cond
((nil? a) false)
((nil? b) false)
((and (dict? a) (dict? b))
(let
((pos (host-call a "compareDocumentPosition" b)))
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
(true (< (str a) (str b))))))
(define
hs-follows?
@@ -840,7 +931,18 @@
(= obj (nth r 1))
(= obj nil)))))))
(define precedes? (fn (a b) (< (str a) (str b))))
(define
precedes?
(fn
(a b)
(cond
((nil? a) false)
((nil? b) false)
((and (dict? a) (dict? b))
(let
((pos (host-call a "compareDocumentPosition" b)))
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
(true (< (str a) (str b))))))
(define
hs-empty?
@@ -1124,33 +1226,109 @@
(host-call el "removeAttribute" "open")
(dom-set-prop el "open" false)))))))
(define
hs-hide!
(fn
(el strategy)
(let
((tag (dom-get-prop el "tagName")))
(cond
((= tag "DIALOG")
(when (dom-has-attr? el "open") (host-call el "close")))
((= tag "DETAILS") (dom-set-prop el "open" false))
((= strategy "opacity") (dom-set-style el "opacity" "0"))
((= strategy "visibility") (dom-set-style el "visibility" "hidden"))
(true (dom-set-style el "display" "none"))))))
(begin
(define
hs-hide-one!
(fn
(el strategy)
(let
((parts (split strategy ":")) (tag (dom-get-prop el "tagName")))
(let
((prop (first parts))
(val (if (> (len parts) 1) (nth parts 1) nil)))
(cond
((= tag "DIALOG")
(when (dom-has-attr? el "open") (host-call el "close")))
((= tag "DETAILS") (dom-set-prop el "open" false))
((= prop "opacity")
(dom-set-style el "opacity" (if val val "0")))
((= prop "visibility")
(dom-set-style el "visibility" (if val val "hidden")))
((= prop "hidden") (dom-set-attr el "hidden" ""))
((= prop "twDisplay") (dom-add-class el "hidden"))
((= prop "twVisibility") (dom-add-class el "invisible"))
((= prop "twOpacity") (dom-add-class el "opacity-0"))
(true (dom-set-style el "display" (if val val "none"))))))))
(define
hs-hide!
(fn
(target strategy)
(if
(list? target)
(do (for-each (fn (el) (hs-hide-one! el strategy)) target) target)
(do (hs-hide-one! target strategy) target)))))
(begin
(define
hs-show-one!
(fn
(el strategy)
(let
((parts (split strategy ":")) (tag (dom-get-prop el "tagName")))
(let
((prop (first parts))
(val (if (> (len parts) 1) (nth parts 1) nil)))
(cond
((= tag "DIALOG")
(when
(not (dom-has-attr? el "open"))
(host-call el "showModal")))
((= tag "DETAILS") (dom-set-prop el "open" true))
((= prop "opacity")
(dom-set-style el "opacity" (if val val "1")))
((= prop "visibility")
(dom-set-style el "visibility" (if val val "visible")))
((= prop "hidden") (dom-remove-attr el "hidden"))
((= prop "twDisplay") (dom-remove-class el "hidden"))
((= prop "twVisibility") (dom-remove-class el "invisible"))
((= prop "twOpacity") (dom-remove-class el "opacity-0"))
(true (dom-set-style el "display" (if val val "block"))))))))
(define
hs-show!
(fn
(target strategy)
(if
(list? target)
(do (for-each (fn (el) (hs-show-one! el strategy)) target) target)
(do (hs-show-one! target strategy) target)))))
(define
hs-show!
hs-show-when!
(fn
(el strategy)
(target strategy pred)
(let
((tag (dom-get-prop el "tagName")))
(cond
((= tag "DIALOG")
(when (not (dom-has-attr? el "open")) (host-call el "showModal")))
((= tag "DETAILS") (dom-set-prop el "open" true))
((= strategy "opacity") (dom-set-style el "opacity" "1"))
((= strategy "visibility") (dom-set-style el "visibility" "visible"))
(true (dom-set-style el "display" ""))))))
((items (if (list? target) target (list target))))
(let
((matched (list)))
(do
(for-each
(fn
(el)
(if
(pred el)
(do (hs-show-one! el strategy) (append! matched el))
(hs-hide-one! el strategy)))
items)
matched)))))
(define
hs-hide-when!
(fn
(target strategy pred)
(let
((items (if (list? target) target (list target))))
(let
((matched (list)))
(do
(for-each
(fn
(el)
(if
(pred el)
(do (hs-hide-one! el strategy) (append! matched el))
(hs-show-one! el strategy)))
items)
matched)))))
(define hs-first (fn (lst) (first lst)))
@@ -1390,7 +1568,7 @@
false
(let
((store (host-get el "__hs_vars")))
(if (nil? store) false (has-key? store name))))))
(if (nil? store) false (host-call store "hasOwnProperty" name))))))
(define
hs-dom-get-var-raw
@@ -1409,7 +1587,7 @@
(do
(when
(nil? (host-get el "__hs_vars"))
(host-set! el "__hs_vars" (dict)))
(host-set! el "__hs_vars" (host-new "Object")))
(host-set! (host-get el "__hs_vars") name val)
(when changed (hs-dom-fire-watchers! el name val))))))