HS: count-filtered events + first modifier (+5 tests)

Parser: parse-on-feat now consumes `first` keyword before event-name (sets
count-min/max to 1) and a count expression after event-name — `N` (single),
`N to M` (range), `N and on` (unbounded above). Number tokens are coerced
via parse-number. Emits :count-filter {"min" N "max" M | -1} part.

Compiler: scan-on threads count-filter-info; the handler binding wraps the
fn body in a let-bound __hs-count counter. Each event fire increments the
counter and (when count is in range) executes the original body. Each
on-clause registers an independent handler with its own counter, so
`on click 1 ... on click 2 ... on click 3` produces three handlers that
fire on their respective Nth click (mix-ranges test).

Generator: dropped 5 cluster-34 tests from skip-list — `can filter events
based on count`, `... count range`, `... unbounded count range`, `can mix
ranges`, `on first click fires only once`.

hs-upstream-on: 43/70 → 48/70. Smoke 0-195 unchanged at 172/195.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 12:08:40 +00:00
parent ff38499bd5
commit 19c97989d7
6 changed files with 195 additions and 122 deletions

View File

@@ -165,7 +165,8 @@
catch-info
finally-info
having-info
of-filter-info)
of-filter-info
count-filter-info)
(cond
((<= (len items) 1)
(let
@@ -183,7 +184,7 @@
(let
((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (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))))
(let
((handler (let ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) (list (quote fn) (list (quote event)) (if (uses-the-result? wrapped-body) (list (quote let) (list (list (quote the-result) nil)) wrapped-body) wrapped-body)))))
((handler (let ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) (let ((base-handler (list (quote fn) (list (quote event)) (if (uses-the-result? wrapped-body) (list (quote let) (list (list (quote the-result) nil)) wrapped-body) wrapped-body)))) (if count-filter-info (let ((mn (get count-filter-info "min")) (mx (get count-filter-info "max"))) (list (quote let) (list (list (quote __hs-count) 0)) (list (quote fn) (list (quote event)) (list (quote begin) (list (quote set!) (quote __hs-count) (list (quote +) (quote __hs-count) 1)) (list (quote when) (if (= mx -1) (list (quote >=) (quote __hs-count) mn) (list (quote and) (list (quote >=) (quote __hs-count) mn) (list (quote <=) (quote __hs-count) mx))) (nth base-handler 2)))))) base-handler)))))
(let
((on-call (if every? (list (quote hs-on-every) target event-name handler) (list (quote hs-on) target event-name handler))))
(cond
@@ -233,7 +234,8 @@
catch-info
finally-info
having-info
of-filter-info))
of-filter-info
count-filter-info))
((= (first items) :filter)
(scan-on
(rest (rest items))
@@ -243,7 +245,8 @@
catch-info
finally-info
having-info
of-filter-info))
of-filter-info
count-filter-info))
((= (first items) :every)
(scan-on
(rest (rest items))
@@ -253,7 +256,8 @@
catch-info
finally-info
having-info
of-filter-info))
of-filter-info
count-filter-info))
((= (first items) :catch)
(scan-on
(rest (rest items))
@@ -263,7 +267,8 @@
(nth items 1)
finally-info
having-info
of-filter-info))
of-filter-info
count-filter-info))
((= (first items) :finally)
(scan-on
(rest (rest items))
@@ -273,7 +278,8 @@
catch-info
(nth items 1)
having-info
of-filter-info))
of-filter-info
count-filter-info))
((= (first items) :having)
(scan-on
(rest (rest items))
@@ -283,7 +289,8 @@
catch-info
finally-info
(nth items 1)
of-filter-info))
of-filter-info
count-filter-info))
((= (first items) :of-filter)
(scan-on
(rest (rest items))
@@ -293,6 +300,18 @@
catch-info
finally-info
having-info
(nth items 1)
count-filter-info))
((= (first items) :count-filter)
(scan-on
(rest (rest items))
source
filter
every?
catch-info
finally-info
having-info
of-filter-info
(nth items 1)))
(true
(scan-on
@@ -303,8 +322,9 @@
catch-info
finally-info
having-info
of-filter-info)))))
(scan-on (rest parts) nil nil false nil nil nil nil)))))
of-filter-info
count-filter-info)))))
(scan-on (rest parts) nil nil false nil nil nil nil nil)))))
(define
emit-send
(fn