HS: ask/answer + prompt/confirm mock (+4 tests)

Wire up the `ask` and `answer` commands end-to-end:

- tokenizer.sx: register `ask` and `answer` as hs-keywords.

- parser.sx: cmd-kw? gains both; parse-cmd dispatches to new
  parse-ask-cmd (emits `(ask MSG)`) and parse-answer-cmd, which
  reads `answer MSG [with YES or NO]`. The with/or pair reads
  yes/no via parse-atom — parse-expr would collapse
  `"Yes" or "No"` into `(or "Yes" "No")` before match-kw "or"
  could fire. The no-`with` form emits `(answer-alert MSG)`.

- compiler.sx: three new cond branches (ask, answer, answer-alert)
  compile to a let that binds __hs-a, sets `the-result` and `it`,
  and returns the value — so `then put it into ...` works.

- runtime.sx: hs-ask / hs-answer / hs-answer-alert call
  window.prompt / confirm / alert via host-call + host-global.

- tests/hs-run-filtered.js: test-name-keyed globalThis.{alert,
  confirm,prompt}; __currentHsTestName is updated before each
  test. Host-set! for innerHTML/textContent now coerces JS
  null → "null" (browser behaviour) so `prompt → null` →
  `put it into #out` renders literal text "null", which the
  fourth test depends on.

Suite hs-upstream-askAnswer: 1/5 -> 5/5.
Smoke 0-195: 166/195 -> 170/195.
This commit is contained in:
2026-04-24 14:08:25 +00:00
parent 30fca2dd19
commit 6c1da9212a
11 changed files with 244 additions and 60 deletions

View File

@@ -468,6 +468,35 @@
;; `window.getSelection().toString()`. In the mock test runner, a test
;; setup stashes the desired selection text at `window.__test_selection`
;; and the fallback path returns that so tests can assert on the result.
(define
hs-ask
(fn
(msg)
(let
((w (host-global "window")))
(if w (host-call w "prompt" msg) nil))))
;; ── Transition ──────────────────────────────────────────────────
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
(define
hs-answer
(fn
(msg yes-val no-val)
(let
((w (host-global "window")))
(if w (if (host-call w "confirm" msg) yes-val no-val) no-val))))
(define
hs-answer-alert
(fn
(msg)
(let
((w (host-global "window")))
(if w (begin (host-call w "alert" msg) nil) nil))))
(define
hs-scroll!
(fn
@@ -480,11 +509,6 @@
((= position "bottom") (dict :block "end"))
(true (dict :block "start")))))))
;; ── Transition ──────────────────────────────────────────────────
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
(define
hs-halt!
(fn
@@ -619,6 +643,10 @@
(hs-query-all sel)
(host-call target "querySelectorAll" sel))))
(define
hs-list-set
(fn
@@ -628,15 +656,12 @@
(define
hs-to-number
(fn (v) (if (number? v) v (or (parse-number (str v)) 0))))
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
(define
hs-query-first
(fn (sel) (host-call (host-global "document") "querySelector" sel)))
;; DOM query stub — sandbox returns empty list
(define
hs-query-last
(fn
@@ -644,10 +669,11 @@
(let
((all (dom-query-all (dom-body) sel)))
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
;; Method dispatch — obj.method(args)
(define hs-first (fn (scope sel) (dom-query-all scope sel)))
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
;; ── 0.9.90 features ─────────────────────────────────────────────
;; beep! — debug logging, returns value unchanged
(define
hs-last
(fn
@@ -655,7 +681,7 @@
(let
((all (dom-query-all scope sel)))
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
;; DOM query stub — sandbox returns empty list
;; Property-based is — check obj.key truthiness
(define
hs-repeat-times
(fn
@@ -673,7 +699,7 @@
((= signal "hs-continue") (do-repeat (+ i 1)))
(true (do-repeat (+ i 1))))))))
(do-repeat 0)))
;; Method dispatch — obj.method(args)
;; Array slicing (inclusive both ends)
(define
hs-repeat-forever
(fn
@@ -689,9 +715,7 @@
((= signal "hs-continue") (do-forever))
(true (do-forever))))))
(do-forever)))
;; ── 0.9.90 features ─────────────────────────────────────────────
;; beep! — debug logging, returns value unchanged
;; Collection: sorted by
(define
hs-repeat-while
(fn
@@ -704,7 +728,7 @@
((= signal "hs-break") nil)
((= signal "hs-continue") (hs-repeat-while cond-fn thunk))
(true (hs-repeat-while cond-fn thunk)))))))
;; Property-based is — check obj.key truthiness
;; Collection: sorted by descending
(define
hs-repeat-until
(fn
@@ -716,7 +740,7 @@
((= signal "hs-continue")
(if (cond-fn) nil (hs-repeat-until cond-fn thunk)))
(true (if (cond-fn) nil (hs-repeat-until cond-fn thunk)))))))
;; Array slicing (inclusive both ends)
;; Collection: split by
(define
hs-for-each
(fn
@@ -736,7 +760,7 @@
((= signal "hs-continue") (do-loop (rest remaining)))
(true (do-loop (rest remaining))))))))
(do-loop items))))
;; Collection: sorted by
;; Collection: joined by
(begin
(define
hs-append
@@ -764,7 +788,7 @@
((hs-element? target)
(dom-insert-adjacent-html target "beforeend" (str value)))
(true nil)))))
;; Collection: sorted by descending
(define
hs-sender
(fn
@@ -772,7 +796,7 @@
(let
((detail (host-get event "detail")))
(if detail (host-get detail "sender") nil))))
;; Collection: split by
(define
hs-host-to-sx
(fn
@@ -826,7 +850,7 @@
(dict-set! out k (hs-host-to-sx (host-get v k))))
(host-call (host-global "Object") "keys" v))
out)))))))))))
;; Collection: joined by
(define
hs-fetch
(fn