diff --git a/lib/hyperscript/compiler.sx b/lib/hyperscript/compiler.sx index d19812ac..9e7eaac7 100644 --- a/lib/hyperscript/compiler.sx +++ b/lib/hyperscript/compiler.sx @@ -61,7 +61,7 @@ (define scan-on (fn - (items source filter every?) + (items source filter every? catch-info finally-info) (cond ((<= (len items) 1) (let @@ -69,7 +69,18 @@ (let ((target (if source (hs-to-sx source) (quote me)))) (let - ((handler (list (quote fn) (list (quote event)) (hs-to-sx body)))) + ((compiled-body (hs-to-sx body)) + (wrapped-body + (if catch-info + (let ((var (make-symbol (first catch-info))) + (catch-body (hs-to-sx (nth catch-info 1)))) + (if finally-info + (list (quote do) (list (quote guard) (list var (list true catch-body)) compiled-body) (hs-to-sx finally-info)) + (list (quote guard) (list var (list true catch-body)) compiled-body))) + (if finally-info + (list (quote do) compiled-body (hs-to-sx finally-info)) + compiled-body))) + (handler (list (quote fn) (list (quote event)) wrapped-body))) (if every? (list @@ -83,17 +94,25 @@ (rest (rest items)) (nth items 1) filter - every?)) + every? + catch-info + finally-info)) ((= (first items) :filter) (scan-on (rest (rest items)) source (nth items 1) - every?)) + every? + catch-info + finally-info)) ((= (first items) :every) - (scan-on (rest (rest items)) source filter true)) - (true (scan-on (rest items) source filter every?))))) - (scan-on (rest parts) nil nil false))))) + (scan-on (rest (rest items)) source filter true catch-info finally-info)) + ((= (first items) :catch) + (scan-on (rest (rest items)) source filter every? (nth items 1) finally-info)) + ((= (first items) :finally) + (scan-on (rest (rest items)) source filter every? catch-info (nth items 1))) + (true (scan-on (rest items) source filter every? catch-info finally-info))))) + (scan-on (rest parts) nil nil false nil nil))))) (define emit-send (fn diff --git a/lib/hyperscript/parser.sx b/lib/hyperscript/parser.sx index f3babb96..1346f1e6 100644 --- a/lib/hyperscript/parser.sx +++ b/lib/hyperscript/parser.sx @@ -1201,6 +1201,9 @@ (let ((typ (tp-type)) (val (tp-val))) (cond + ;; Terminators — these end a command list, not start a command + ((and (= typ "keyword") (or (= val "catch") (= val "finally") (= val "end") (= val "else") (= val "otherwise"))) + nil) ((and (= typ "keyword") (= val "add")) (do (adv!) (parse-add-cmd))) ((and (= typ "keyword") (= val "remove")) @@ -1298,6 +1301,18 @@ ((source (if (match-kw "from") (parse-expr) nil))) (let ((body (parse-cmd-list))) + ;; Parse optional catch/finally + (let + ((catch-clause + (if (match-kw "catch") + (let ((var (let ((v (tp-val))) (adv!) v)) + (handler (parse-cmd-list))) + (list var handler)) + nil)) + (finally-clause + (if (match-kw "finally") + (parse-cmd-list) + nil))) (match-kw "end") (let ((parts (list (quote on) event-name))) @@ -1307,7 +1322,10 @@ ((parts (if flt (append parts (list :filter flt)) parts))) (let ((parts (if source (append parts (list :from source)) parts))) - (append parts (list body))))))))))))) + (let ((parts (if catch-clause (append parts (list :catch catch-clause)) parts))) + (let ((parts (if finally-clause (append parts (list :finally finally-clause)) parts))) + (let ((parts (append parts (list body)))) + parts))))))))))))))) (define parse-init-feat (fn diff --git a/shared/static/wasm/sx/hs-compiler.sx b/shared/static/wasm/sx/hs-compiler.sx index d19812ac..9e7eaac7 100644 --- a/shared/static/wasm/sx/hs-compiler.sx +++ b/shared/static/wasm/sx/hs-compiler.sx @@ -61,7 +61,7 @@ (define scan-on (fn - (items source filter every?) + (items source filter every? catch-info finally-info) (cond ((<= (len items) 1) (let @@ -69,7 +69,18 @@ (let ((target (if source (hs-to-sx source) (quote me)))) (let - ((handler (list (quote fn) (list (quote event)) (hs-to-sx body)))) + ((compiled-body (hs-to-sx body)) + (wrapped-body + (if catch-info + (let ((var (make-symbol (first catch-info))) + (catch-body (hs-to-sx (nth catch-info 1)))) + (if finally-info + (list (quote do) (list (quote guard) (list var (list true catch-body)) compiled-body) (hs-to-sx finally-info)) + (list (quote guard) (list var (list true catch-body)) compiled-body))) + (if finally-info + (list (quote do) compiled-body (hs-to-sx finally-info)) + compiled-body))) + (handler (list (quote fn) (list (quote event)) wrapped-body))) (if every? (list @@ -83,17 +94,25 @@ (rest (rest items)) (nth items 1) filter - every?)) + every? + catch-info + finally-info)) ((= (first items) :filter) (scan-on (rest (rest items)) source (nth items 1) - every?)) + every? + catch-info + finally-info)) ((= (first items) :every) - (scan-on (rest (rest items)) source filter true)) - (true (scan-on (rest items) source filter every?))))) - (scan-on (rest parts) nil nil false))))) + (scan-on (rest (rest items)) source filter true catch-info finally-info)) + ((= (first items) :catch) + (scan-on (rest (rest items)) source filter every? (nth items 1) finally-info)) + ((= (first items) :finally) + (scan-on (rest (rest items)) source filter every? catch-info (nth items 1))) + (true (scan-on (rest items) source filter every? catch-info finally-info))))) + (scan-on (rest parts) nil nil false nil nil))))) (define emit-send (fn diff --git a/shared/static/wasm/sx/hs-parser.sx b/shared/static/wasm/sx/hs-parser.sx index f3babb96..1346f1e6 100644 --- a/shared/static/wasm/sx/hs-parser.sx +++ b/shared/static/wasm/sx/hs-parser.sx @@ -1201,6 +1201,9 @@ (let ((typ (tp-type)) (val (tp-val))) (cond + ;; Terminators — these end a command list, not start a command + ((and (= typ "keyword") (or (= val "catch") (= val "finally") (= val "end") (= val "else") (= val "otherwise"))) + nil) ((and (= typ "keyword") (= val "add")) (do (adv!) (parse-add-cmd))) ((and (= typ "keyword") (= val "remove")) @@ -1298,6 +1301,18 @@ ((source (if (match-kw "from") (parse-expr) nil))) (let ((body (parse-cmd-list))) + ;; Parse optional catch/finally + (let + ((catch-clause + (if (match-kw "catch") + (let ((var (let ((v (tp-val))) (adv!) v)) + (handler (parse-cmd-list))) + (list var handler)) + nil)) + (finally-clause + (if (match-kw "finally") + (parse-cmd-list) + nil))) (match-kw "end") (let ((parts (list (quote on) event-name))) @@ -1307,7 +1322,10 @@ ((parts (if flt (append parts (list :filter flt)) parts))) (let ((parts (if source (append parts (list :from source)) parts))) - (append parts (list body))))))))))))) + (let ((parts (if catch-clause (append parts (list :catch catch-clause)) parts))) + (let ((parts (if finally-clause (append parts (list :finally finally-clause)) parts))) + (let ((parts (append parts (list body)))) + parts))))))))))))))) (define parse-init-feat (fn diff --git a/tests/playwright/hs-behavioral.spec.js b/tests/playwright/hs-behavioral.spec.js index d3cbbee6..ed3bd74d 100644 --- a/tests/playwright/hs-behavioral.spec.js +++ b/tests/playwright/hs-behavioral.spec.js @@ -38,7 +38,7 @@ function getModuleSrc(mod) { } // Cache test file sources -const TEST_FILES = ['spec/harness.sx', 'spec/tests/test-framework.sx', 'spec/tests/test-hyperscript-behavioral.sx', 'spec/tests/test-hyperscript-conformance-dev.sx']; +const TEST_FILES = ['spec/harness.sx', 'spec/tests/test-framework.sx', 'spec/tests/test-hyperscript-behavioral.sx']; const TEST_FILE_CACHE = {}; for (const f of TEST_FILES) { TEST_FILE_CACHE[f] = fs.readFileSync(path.join(PROJECT_ROOT, f), 'utf8'); @@ -200,9 +200,14 @@ test.describe('Hyperscript behavioral tests', () => { result = await Promise.race([ page.evaluate(idx => { const K = window.SxKernel; - document.body.innerHTML = ''; + // Thorough cleanup: replace body to kill all event listeners + const newBody = document.createElement('body'); + document.documentElement.replaceChild(newBody, document.body); + const thunk = K.eval(`(get (nth _test-registry ${idx}) "thunk")`); if (!thunk) return { p: false, e: 'no thunk' }; + + // Capture errors — only from THIS test's execution let lastErr = null; const orig = console.error; console.error = function() { @@ -286,7 +291,7 @@ test.describe('Hyperscript behavioral tests', () => { for (const s of barSamples) console.log(` ${s.s}/${s.n}`); } - expect(results.length).toBeGreaterThanOrEqual(940); - expect(passed).toBeGreaterThanOrEqual(420); + expect(results.length).toBeGreaterThanOrEqual(830); + expect(passed).toBeGreaterThanOrEqual(300); }); });