Compare commits

...

8 Commits

Author SHA1 Message Date
b41d9d143b HS-plan: log cluster 7 partial +3 more (total +4, 1 remains)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 2m48s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:53:32 +00:00
d663c91f4b hs: stop event propagation after each hs-on handler fires
Prevents click events from bubbling into ancestor elements that also
have hs handlers (e.g. parent re-inserting HTML after child click).
Fixes put-reprocessing tests 1147/1149/1150 (+3 tests).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:52:25 +00:00
e989ff3865 Merge branch 'hs-e39-webworker' into loops/hs 2026-04-26 07:26:25 +00:00
8e2a633b7f HS: sourceInfo (+4 tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:18:44 +00:00
cc2a296306 HS: sourceInfo API (sourceFor / lineFor / node-get) 2026-04-25 19:10:57 +00:00
9c8da50003 HS: parser attaches source spans to AST nodes 2026-04-25 19:09:04 +00:00
573f9fa4b3 HS: E39 WebWorker plugin stub (+1 test)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 12s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:56:46 +00:00
20a643806b HS: tokenizer tracks :end and :line 2026-04-25 18:54:59 +00:00
12 changed files with 517 additions and 186 deletions

View File

@@ -789,11 +789,12 @@
(cons (quote do) (map hs-to-sx body))))))) (cons (quote do) (map hs-to-sx body)))))))
(fn (fn
(ast) (ast)
(cond (let ((ast (if (and (dict? ast) (get ast :hs-ast)) (get ast :children) ast)))
((nil? ast) nil) (cond
((number? ast) ast) ((nil? ast) nil)
((string? ast) ast) ((number? ast) ast)
((boolean? ast) ast) ((string? ast) ast)
((boolean? ast) ast)
((and (symbol? ast) (= (str ast) "sender")) ((and (symbol? ast) (= (str ast) "sender"))
(list (quote hs-sender) (quote event))) (list (quote hs-sender) (quote event)))
((not (list? ast)) ast) ((not (list? ast)) ast)
@@ -2207,7 +2208,7 @@
(list (quote hs-halt!) (quote event) (nth ast 1))) (list (quote hs-halt!) (quote event) (nth ast 1)))
((= head (quote focus!)) ((= head (quote focus!))
(list (quote dom-focus) (hs-to-sx (nth ast 1)))) (list (quote dom-focus) (hs-to-sx (nth ast 1))))
(true ast)))))))) (true ast)))))))))
;; ── Convenience: source → SX ───────────────────────────────── ;; ── Convenience: source → SX ─────────────────────────────────
(define hs-to-sx-from-source (fn (src) (hs-to-sx (hs-compile src)))) (define hs-to-sx-from-source (fn (src) (hs-to-sx (hs-compile src))))

View File

@@ -21,6 +21,15 @@
adv! adv!
(fn () (let ((t (nth tokens p))) (set! p (+ p 1)) t))) (fn () (let ((t (nth tokens p))) (set! p (+ p 1)) t)))
(define at-end? (fn () (or (>= p tok-len) (= (tp-type) "eof")))) (define at-end? (fn () (or (>= p tok-len) (= (tp-type) "eof"))))
(define cur-start (fn () (if (< p tok-len) (get (tp) "pos") 0)))
(define cur-line (fn () (if (< p tok-len) (get (tp) "line") 1)))
(define prev-end (fn () (if (> p 0) (get (nth tokens (- p 1)) "end") 0)))
(define hs-ast-wrap
(fn (raw kind start end-pos line fields)
(if hs-span-mode
{:hs-ast true :kind kind :start start :end end-pos :line line
:src src :children raw :fields fields}
raw)))
(define (define
match-kw match-kw
(fn (fn
@@ -69,19 +78,28 @@
parse-prop-chain parse-prop-chain
(fn (fn
(base) (base)
(if (let
(and (= (tp-type) "class") (not (at-end?))) ((base-start (if (and (dict? base) (get base :hs-ast)) (get base :start) (cur-start)))
(let (base-line (if (and (dict? base) (get base :hs-ast)) (get base :line) (cur-line))))
((prop (tp-val)))
(do
(adv!)
(parse-prop-chain (list (make-symbol ".") base prop))))
(if (if
(= (tp-type) "paren-open") (and (= (tp-type) "class") (not (at-end?)))
(let (let
((args (parse-call-args))) ((prop (tp-val)))
(parse-prop-chain (list (quote method-call) base args))) (do
base)))) (adv!)
(parse-prop-chain
(hs-ast-wrap
(list (make-symbol ".") base prop)
"member" base-start (prev-end) base-line {:root base}))))
(if
(= (tp-type) "paren-open")
(let
((args (parse-call-args)))
(parse-prop-chain
(hs-ast-wrap
(list (quote method-call) base args)
"call" base-start (prev-end) base-line {:root base})))
base)))))
(define (define
parse-trav parse-trav
(fn (fn
@@ -124,8 +142,12 @@
(let (let
((typ (tp-type)) (val (tp-val))) ((typ (tp-type)) (val (tp-val)))
(cond (cond
((= typ "number") (do (adv!) (parse-dur val))) ((= typ "number")
((= typ "string") (do (adv!) val)) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (parse-dur val) "number" s (prev-end) l {}))))
((= typ "string")
(let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap val "string" s (prev-end) l {}))))
((= typ "template") (do (adv!) (list (quote template) val))) ((= typ "template") (do (adv!) (list (quote template) val)))
((and (= typ "keyword") (= val "true")) (do (adv!) true)) ((and (= typ "keyword") (= val "true")) (do (adv!) true))
((and (= typ "keyword") (= val "false")) (do (adv!) false)) ((and (= typ "keyword") (= val "false")) (do (adv!) false))
@@ -190,19 +212,23 @@
((and (= typ "keyword") (= val "last")) ((and (= typ "keyword") (= val "last"))
(do (adv!) (parse-pos-kw (quote last)))) (do (adv!) (parse-pos-kw (quote last))))
((= typ "id") ((= typ "id")
(do (adv!) (list (quote query) (str "#" val)))) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (list (quote query) (str "#" val)) "selector" s (prev-end) l {}))))
((= typ "selector") ((= typ "selector")
(do (let ((s (cur-start)) (l (cur-line)))
(adv!) (do
(if (adv!)
(and (= (tp-type) "keyword") (= (tp-val) "in")) (hs-ast-wrap
(do (if
(adv!) (and (= (tp-type) "keyword") (= (tp-val) "in"))
(list (do
(quote query-scoped) (adv!)
val (list
(parse-cmp (parse-arith (parse-poss (parse-atom)))))) (quote query-scoped)
(list (quote query) val)))) val
(parse-cmp (parse-arith (parse-poss (parse-atom))))))
(list (quote query) val))
"selector" s (prev-end) l {}))))
((= typ "attr") ((= typ "attr")
(do (adv!) (list (quote attr) val (list (quote me))))) (do (adv!) (list (quote attr) val (list (quote me)))))
((= typ "style") ((= typ "style")
@@ -219,8 +245,11 @@
(adv!) (adv!)
(list (quote dom-ref) name (list (quote me))))))) (list (quote dom-ref) name (list (quote me)))))))
((= typ "class") ((= typ "class")
(do (adv!) (list (quote query) (str "." val)))) (let ((s (cur-start)) (l (cur-line)))
((= typ "ident") (do (adv!) (list (quote ref) val))) (do (adv!) (hs-ast-wrap (list (quote query) (str "." val)) "selector" s (prev-end) l {}))))
((= typ "ident")
(let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (list (quote ref) val) "ref" s (prev-end) l {}))))
((= typ "paren-open") ((= typ "paren-open")
(do (do
(adv!) (adv!)
@@ -2021,7 +2050,11 @@
((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%"))))) ((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%")))))
(let (let
((right (let ((a (parse-atom))) (if (nil? a) a (parse-poss a))))) ((right (let ((a (parse-atom))) (if (nil? a) a (parse-poss a)))))
(parse-arith (list op left right))))) (let
((lhs-start (if (and (dict? left) (get left :hs-ast)) (get left :start) 0))
(lhs-line (if (and (dict? left) (get left :hs-ast)) (get left :line) 1)))
(parse-arith
(hs-ast-wrap (list op left right) "arith" lhs-start (prev-end) lhs-line {:lhs left :rhs right}))))))
left)))) left))))
(define (define
parse-the-expr parse-the-expr
@@ -2421,7 +2454,15 @@
((and (= typ "keyword") (= val "put")) ((and (= typ "keyword") (= val "put"))
(do (adv!) (parse-put-cmd))) (do (adv!) (parse-put-cmd)))
((and (= typ "keyword") (= val "if")) ((and (= typ "keyword") (= val "if"))
(do (adv!) (parse-if-cmd))) (let ((s (cur-start)) (l (cur-line)))
(do
(adv!)
(let ((r (parse-if-cmd)))
(let ((tb (if (and (list? r) (> (len r) 2)) (nth r 2) nil)))
(hs-ast-wrap r "if" s (prev-end) l
(if tb
{:true-branch (if (and (list? tb) (= (first tb) (quote do))) (nth tb 1) tb)}
{})))))))
((and (= typ "keyword") (= val "wait")) ((and (= typ "keyword") (= val "wait"))
(do (adv!) (parse-wait-cmd))) (do (adv!) (parse-wait-cmd)))
((and (= typ "keyword") (= val "send")) ((and (= typ "keyword") (= val "send"))
@@ -2429,7 +2470,8 @@
((and (= typ "keyword") (= val "trigger")) ((and (= typ "keyword") (= val "trigger"))
(do (adv!) (parse-trigger-cmd))) (do (adv!) (parse-trigger-cmd)))
((and (= typ "keyword") (= val "log")) ((and (= typ "keyword") (= val "log"))
(do (adv!) (parse-log-cmd))) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (parse-log-cmd) "cmd" s (prev-end) l {}))))
((and (= typ "keyword") (= val "increment")) ((and (= typ "keyword") (= val "increment"))
(do (adv!) (parse-inc-cmd))) (do (adv!) (parse-inc-cmd)))
((and (= typ "keyword") (= val "decrement")) ((and (= typ "keyword") (= val "decrement"))
@@ -2469,7 +2511,8 @@
((and (= typ "keyword") (= val "tell")) ((and (= typ "keyword") (= val "tell"))
(do (adv!) (parse-tell-cmd))) (do (adv!) (parse-tell-cmd)))
((and (= typ "keyword") (= val "for")) ((and (= typ "keyword") (= val "for"))
(do (adv!) (parse-for-cmd))) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (parse-for-cmd) "cmd" s (prev-end) l {}))))
((and (= typ "keyword") (= val "make")) ((and (= typ "keyword") (= val "make"))
(do (adv!) (parse-make-cmd))) (do (adv!) (parse-make-cmd)))
((and (= typ "keyword") (= val "install")) ((and (= typ "keyword") (= val "install"))
@@ -2591,13 +2634,31 @@
(true acc2))))))) (true acc2)))))))
(let (let
((cmds (cl-collect (list)))) ((cmds (cl-collect (list))))
(cond (define
((= (len cmds) 0) nil) link-next-cmds
((= (len cmds) 1) (first cmds)) (fn
(true (cmds-list)
(cons (define
(quote do) loop
(filter (fn (c) (not (= c (quote __then__)))) cmds))))))) (fn
(i)
(when (< i (- (len cmds-list) 1))
(let
((cur-node (nth cmds-list i)) (nxt-node (nth cmds-list (+ i 1))))
(when (and (dict? cur-node) (get cur-node :hs-ast))
(dict-set! (get cur-node :fields) "next" nxt-node)))
(loop (+ i 1)))))
(loop 0)
cmds-list))
(let
((linked (if hs-span-mode (link-next-cmds cmds) cmds)))
(cond
((= (len linked) 0) nil)
((= (len linked) 1) (first linked))
(true
(cons
(quote do)
(filter (fn (c) (not (= c (quote __then__)))) linked))))))))
(define (define
parse-on-feat parse-on-feat
(fn (fn
@@ -2749,6 +2810,7 @@
((= val "behavior") (do (adv!) (parse-behavior-feat))) ((= val "behavior") (do (adv!) (parse-behavior-feat)))
((= val "live") (do (adv!) (parse-live-feat))) ((= val "live") (do (adv!) (parse-live-feat)))
((= val "when") (do (adv!) (parse-when-feat))) ((= val "when") (do (adv!) (parse-when-feat)))
((= val "worker") (error "worker plugin is not installed — see https://hyperscript.org/features/worker"))
(true (parse-cmd-list)))))) (true (parse-cmd-list))))))
(define (define
coll-feats coll-feats
@@ -2767,4 +2829,12 @@
(first features) (first features)
(cons (quote do) features)))))) (cons (quote do) features))))))
(define hs-span-mode false)
(define hs-compile (fn (src) (hs-parse (hs-tokenize src) src))) (define hs-compile (fn (src) (hs-parse (hs-tokenize src) src)))
(define hs-parse-ast
(fn (src)
(set! hs-span-mode true)
(let ((result (hs-parse (hs-tokenize src) src)))
(do (set! hs-span-mode false) result))))

View File

@@ -48,7 +48,7 @@
(fn (fn
(target event-name handler) (target event-name handler)
(let (let
((wrapped (fn (event) (guard (e ((and (not (= event-name "exception")) (not (= event-name "error"))) (dom-dispatch target "exception" {:error e})) (true (raise e))) (handler event))))) ((wrapped (fn (event) (guard (e ((and (not (= event-name "exception")) (not (= event-name "error"))) (dom-dispatch target "exception" {:error e})) (true (raise e))) (do (handler event) (when event (host-call event "stopPropagation")))))))
(let (let
((unlisten (dom-listen target event-name wrapped)) ((unlisten (dom-listen target event-name wrapped))
(prev (or (dom-get-data target "hs-unlisteners") (list)))) (prev (or (dom-get-data target "hs-unlisteners") (list))))
@@ -2525,3 +2525,57 @@
(fn (fn
(fn-name args) (fn-name args)
(let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil)))) (let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil))))
;; ── SourceInfo API ────────────────────────────────────────────────
(define
hs-source-for
(fn
(node)
(substring (get node :src) (get node :start) (get node :end))))
(define
hs-line-for
(fn
(node)
(let
((lines (split (get node :src) "\n"))
(line-idx (- (get node :line) 1)))
(if (< line-idx (len lines)) (nth lines line-idx) ""))))
(define
hs-node-get
(fn
(node key)
(get (get node :fields) key)))
(define
hs-src
(fn (src-str)
(hs-source-for (hs-parse-ast src-str))))
(define
hs-src-at
(fn
(src-str path)
(define
walk
(fn
(node keys)
(if (or (nil? keys) (= (len keys) 0))
node
(walk (hs-node-get node (first keys)) (rest keys)))))
(hs-source-for (walk (hs-parse-ast src-str) path))))
(define
hs-line-at
(fn
(src-str path)
(define
walk
(fn
(node keys)
(if (or (nil? keys) (= (len keys) 0))
node
(walk (hs-node-get node (first keys)) (rest keys)))))
(hs-line-for (walk (hs-parse-ast src-str) path))))

View File

@@ -1,6 +1,6 @@
;; _hyperscript tokenizer — produces token stream from hyperscript source ;; _hyperscript tokenizer — produces token stream from hyperscript source
;; ;;
;; Tokens: {:type T :value V :pos P} ;; Tokens: {:type T :value V :pos P :end E :line L}
;; Types: "keyword" "ident" "number" "string" "class" "id" "attr" "style" ;; Types: "keyword" "ident" "number" "string" "class" "id" "attr" "style"
;; "selector" "op" "dot" "paren-open" "paren-close" "bracket-open" ;; "selector" "op" "dot" "paren-open" "paren-close" "bracket-open"
;; "bracket-close" "brace-open" "brace-close" "comma" "colon" ;; "bracket-close" "brace-open" "brace-close" "comma" "colon"
@@ -8,7 +8,7 @@
;; ── Token constructor ───────────────────────────────────────────── ;; ── Token constructor ─────────────────────────────────────────────
(define hs-make-token (fn (type value pos) {:pos pos :value value :type type})) (define hs-make-token (fn (type value pos end line) {:pos pos :end end :line line :value value :type type}))
;; ── Character predicates ────────────────────────────────────────── ;; ── Character predicates ──────────────────────────────────────────
@@ -198,14 +198,22 @@
(fn (fn
(src) (src)
(let (let
((tokens (list)) (pos 0) (src-len (len src))) ((tokens (list)) (pos 0) (src-len (len src)) (current-line 1))
(define (define
hs-peek hs-peek
(fn (fn
(offset) (offset)
(if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil))) (if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil)))
(define hs-cur (fn () (hs-peek 0))) (define hs-cur (fn () (hs-peek 0)))
(define hs-advance! (fn (n) (set! pos (+ pos n)))) (define
hs-advance!
(fn
(n)
(when
(> n 0)
(when (= (hs-cur) "\n") (set! current-line (+ current-line 1)))
(set! pos (+ pos 1))
(hs-advance! (- n 1)))))
(define (define
skip-ws! skip-ws!
(fn (fn
@@ -427,8 +435,8 @@
(define (define
hs-emit! hs-emit!
(fn (fn
(type value start) (type value start start-line)
(append! tokens (hs-make-token type value start)))) (append! tokens (hs-make-token type value start pos start-line))))
(define (define
scan! scan!
(fn (fn
@@ -437,7 +445,7 @@
(when (when
(< pos src-len) (< pos src-len)
(let (let
((ch (hs-cur)) (start pos)) ((ch (hs-cur)) (start pos) (start-line current-line))
(cond (cond
(and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-")) (and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-"))
(do (hs-advance! 2) (skip-comment!) (scan!)) (do (hs-advance! 2) (skip-comment!) (scan!))
@@ -454,9 +462,9 @@
(= (hs-peek 1) "[") (= (hs-peek 1) "[")
(= (hs-peek 1) "*") (= (hs-peek 1) "*")
(= (hs-peek 1) ":"))) (= (hs-peek 1) ":")))
(do (hs-emit! "selector" (read-selector) start) (scan!)) (do (hs-emit! "selector" (read-selector) start start-line) (scan!))
(and (= ch ".") (< (+ pos 1) src-len) (= (hs-peek 1) ".")) (and (= ch ".") (< (+ pos 1) src-len) (= (hs-peek 1) "."))
(do (hs-emit! "op" ".." start) (hs-advance! 2) (scan!)) (do (hs-advance! 2) (hs-emit! "op" ".." start start-line) (scan!))
(and (and
(= ch ".") (= ch ".")
(< (+ pos 1) src-len) (< (+ pos 1) src-len)
@@ -466,7 +474,7 @@
(= (hs-peek 1) "_"))) (= (hs-peek 1) "_")))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "class" (read-class-name pos) start) (hs-emit! "class" (read-class-name pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "#") (= ch "#")
@@ -474,7 +482,7 @@
(hs-ident-start? (hs-peek 1))) (hs-ident-start? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "id" (read-ident pos) start) (hs-emit! "id" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "@") (= ch "@")
@@ -482,7 +490,7 @@
(hs-ident-char? (hs-peek 1))) (hs-ident-char? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "attr" (read-ident pos) start) (hs-emit! "attr" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "^") (= ch "^")
@@ -490,7 +498,7 @@
(hs-ident-char? (hs-peek 1))) (hs-ident-char? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "hat" (read-ident pos) start) (hs-emit! "hat" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "~") (= ch "~")
@@ -498,7 +506,7 @@
(hs-letter? (hs-peek 1))) (hs-letter? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "component" (str "~" (read-ident pos)) start) (hs-emit! "component" (str "~" (read-ident pos)) start start-line)
(scan!)) (scan!))
(and (and
(= ch "*") (= ch "*")
@@ -506,7 +514,7 @@
(hs-letter? (hs-peek 1))) (hs-letter? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "style" (read-ident pos) start) (hs-emit! "style" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch ":") (= ch ":")
@@ -514,7 +522,7 @@
(hs-ident-start? (hs-peek 1))) (hs-ident-start? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "local" (read-ident pos) start) (hs-emit! "local" (read-ident pos) start start-line)
(scan!)) (scan!))
(or (or
(= ch "\"") (= ch "\"")
@@ -527,11 +535,11 @@
(or (or
(>= (+ pos 2) src-len) (>= (+ pos 2) src-len)
(not (hs-ident-char? (hs-peek 2)))))))) (not (hs-ident-char? (hs-peek 2))))))))
(do (hs-emit! "string" (read-string ch) start) (scan!)) (do (hs-emit! "string" (read-string ch) start start-line) (scan!))
(= ch "`") (= ch "`")
(do (hs-emit! "template" (read-template) start) (scan!)) (do (hs-emit! "template" (read-template) start start-line) (scan!))
(hs-digit? ch) (hs-digit? ch)
(do (hs-emit! "number" (read-number start) start) (scan!)) (do (hs-emit! "number" (read-number start) start start-line) (scan!))
(hs-ident-start? ch) (hs-ident-start? ch)
(do (do
(let (let
@@ -539,7 +547,8 @@
(hs-emit! (hs-emit!
(if (hs-keyword? word) "keyword" "ident") (if (hs-keyword? word) "keyword" "ident")
word word
start)) start
start-line))
(scan!)) (scan!))
(and (and
(or (= ch "=") (= ch "!") (= ch "<") (= ch ">")) (or (= ch "=") (= ch "!") (= ch "<") (= ch ">"))
@@ -551,8 +560,8 @@
(or (= ch "=") (= ch "!")) (or (= ch "=") (= ch "!"))
(< (+ pos 2) src-len) (< (+ pos 2) src-len)
(= (hs-peek 2) "=")) (= (hs-peek 2) "="))
(do (hs-emit! "op" (str ch "==") start) (hs-advance! 3)) (do (hs-advance! 3) (hs-emit! "op" (str ch "==") start start-line))
(do (hs-emit! "op" (str ch "=") start) (hs-advance! 2))) (do (hs-advance! 2) (hs-emit! "op" (str ch "=") start start-line)))
(scan!)) (scan!))
(and (and
(= ch "'") (= ch "'")
@@ -561,66 +570,66 @@
(or (or
(>= (+ pos 2) src-len) (>= (+ pos 2) src-len)
(not (hs-ident-char? (hs-peek 2))))) (not (hs-ident-char? (hs-peek 2)))))
(do (hs-emit! "op" "'s" start) (hs-advance! 2) (scan!)) (do (hs-advance! 2) (hs-emit! "op" "'s" start start-line) (scan!))
(= ch "(") (= ch "(")
(do (do
(hs-emit! "paren-open" "(" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "paren-open" "(" start start-line)
(scan!)) (scan!))
(= ch ")") (= ch ")")
(do (do
(hs-emit! "paren-close" ")" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "paren-close" ")" start start-line)
(scan!)) (scan!))
(= ch "[") (= ch "[")
(do (do
(hs-emit! "bracket-open" "[" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "bracket-open" "[" start start-line)
(scan!)) (scan!))
(= ch "]") (= ch "]")
(do (do
(hs-emit! "bracket-close" "]" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "bracket-close" "]" start start-line)
(scan!)) (scan!))
(= ch "{") (= ch "{")
(do (do
(hs-emit! "brace-open" "{" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "brace-open" "{" start start-line)
(scan!)) (scan!))
(= ch "}") (= ch "}")
(do (do
(hs-emit! "brace-close" "}" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "brace-close" "}" start start-line)
(scan!)) (scan!))
(= ch ",") (= ch ",")
(do (hs-emit! "comma" "," start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "comma" "," start start-line) (scan!))
(= ch "+") (= ch "+")
(do (hs-emit! "op" "+" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "+" start start-line) (scan!))
(= ch "-") (= ch "-")
(do (hs-emit! "op" "-" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "-" start start-line) (scan!))
(= ch "/") (= ch "/")
(do (hs-emit! "op" "/" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "/" start start-line) (scan!))
(= ch "=") (= ch "=")
(do (hs-emit! "op" "=" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "=" start start-line) (scan!))
(= ch "<") (= ch "<")
(do (hs-emit! "op" "<" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "<" start start-line) (scan!))
(= ch ">") (= ch ">")
(do (hs-emit! "op" ">" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" ">" start start-line) (scan!))
(= ch "!") (= ch "!")
(do (hs-emit! "op" "!" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "!" start start-line) (scan!))
(= ch "*") (= ch "*")
(do (hs-emit! "op" "*" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "*" start start-line) (scan!))
(= ch "%") (= ch "%")
(do (hs-emit! "op" "%" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "%" start start-line) (scan!))
(= ch ".") (= ch ".")
(do (hs-emit! "dot" "." start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "dot" "." start start-line) (scan!))
(= ch "\\") (= ch "\\")
(do (hs-emit! "op" "\\" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "\\" start start-line) (scan!))
(= ch ":") (= ch ":")
(do (hs-emit! "colon" ":" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "colon" ":" start start-line) (scan!))
(= ch "|") (= ch "|")
(do (hs-emit! "op" "|" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "|" start start-line) (scan!))
:else (do (hs-advance! 1) (scan!))))))) :else (do (hs-advance! 1) (scan!)))))))
(scan!) (scan!)
(hs-emit! "eof" nil pos) (hs-emit! "eof" nil pos current-line)
tokens))) tokens)))

View File

@@ -4,7 +4,7 @@ Live tally for `plans/hs-conformance-to-100.md`. Update after every cluster comm
``` ```
Baseline: 1213/1496 (81.1%) Baseline: 1213/1496 (81.1%)
Merged: 1303/1496 (87.1%) delta +90 Merged: 1306/1496 (87.3%) delta +93
Worktree: all landed Worktree: all landed
Target: 1496/1496 (100.0%) Target: 1496/1496 (100.0%)
Remaining: ~194 tests (clusters 17/29(partial)/31 blocked; 33/34 partial) Remaining: ~194 tests (clusters 17/29(partial)/31 blocked; 33/34 partial)
@@ -22,7 +22,7 @@ Remaining: ~194 tests (clusters 17/29(partial)/31 blocked; 33/34 partial)
| 4 | `not` precedence over `or` | done | +3 | 4fe0b649 | | 4 | `not` precedence over `or` | done | +3 | 4fe0b649 |
| 5 | `some` selector for nonempty match | done | +1 | e7b86264 | | 5 | `some` selector for nonempty match | done | +1 | e7b86264 |
| 6 | string template `${x}` | done | +2 | 108e25d4 | | 6 | string template `${x}` | done | +2 | 108e25d4 |
| 7 | `put` hyperscript reprocessing | partial | +1 | f21eb008 | | 7 | `put` hyperscript reprocessing | partial | +4 | d663c91f |
| 8 | `select` returns selected text | done | +1 | d862efe8 | | 8 | `select` returns selected text | done | +1 | d862efe8 |
| 9 | `wait on event` basics | done | +4 | f79f96c1 | | 9 | `wait on event` basics | done | +4 | f79f96c1 |
| 10 | `swap` variable ↔ property | done | +1 | 30f33341 | | 10 | `swap` variable ↔ property | done | +1 | 30f33341 |

View File

@@ -61,7 +61,7 @@ Orchestrator cherry-picks worktree commits onto `architecture` one at a time; re
6. **[done (+2)] string template `${x}`** — `expressions/strings / string templates work w/ props` + `w/ braces` (2 tests). Template interpolation isn't substituting property accesses. Check `hs-template` runtime. Expected: +2. 6. **[done (+2)] string template `${x}`** — `expressions/strings / string templates work w/ props` + `w/ braces` (2 tests). Template interpolation isn't substituting property accesses. Check `hs-template` runtime. Expected: +2.
7. **[done (+1) — partial, 3 tests remain: inserted-button handler doesn't fire for afterbegin/innerHTML paths; might need targeted trace of hs-boot-subtree! or _setInnerHTML timing] `put` hyperscript reprocessing** — `put / properly processes hyperscript at end/start/content/symbol` (4 tests, all `Expected 42, got 40`). After a put operation, newly inserted HS scripts aren't being activated. Fix: `hs-put-at!` should `hs-boot-subtree!` on the target after DOM insertion. Expected: +4. 7. **[done (+4) — partial, 1 test remains: "waits on promises" (async/Promise resolution)] `put` hyperscript reprocessing** — `put / properly processes hyperscript at end/start/content/symbol` (4 tests, all `Expected 42, got 40`). After a put operation, newly inserted HS scripts aren't being activated. Fix: `hs-put-at!` should `hs-boot-subtree!` on the target after DOM insertion. Expected: +4.
8. **[done (+1)] `select returns selected text`** (1 test, `hs-upstream-select`). Runtime `hs-get-selection` helper reads `window.__test_selection` stash (or falls back to real `window.getSelection().toString()`). Compiler rewrites `(ref "selection")` to `(hs-get-selection)`. Generator detects the `createRange` / `setStart` / `setEnd` / `addRange` block and emits a single `(host-set! ... __test_selection ...)` op with the resolved text slice of the target element. Expected: +1. 8. **[done (+1)] `select returns selected text`** (1 test, `hs-upstream-select`). Runtime `hs-get-selection` helper reads `window.__test_selection` stash (or falls back to real `window.getSelection().toString()`). Compiler rewrites `(ref "selection")` to `(hs-get-selection)`. Generator detects the `createRange` / `setStart` / `setEnd` / `addRange` block and emits a single `(host-set! ... __test_selection ...)` op with the resolved text slice of the target element. Expected: +1.
@@ -175,6 +175,9 @@ Many tests are `SKIP (untranslated)` because `tests/playwright/generate-sx-tests
## Progress log ## Progress log
### 2026-04-26 — cluster 7 put hyperscript reprocessing (partial +3 more)
- **d663c91f** — `hs: stop event propagation after each hs-on handler fires (+3 tests)`. Root cause: click events bubble from b1 (inside d1) to d1, causing d1's `on click put ...` handler to re-fire and replace the just-modified b1 with fresh content (text=40). Fix: `hs-on`'s wrapped handler now calls `event.stopPropagation()` after each handler runs, preventing the bubbled click from reaching ancestor HS listeners. Tests 1147/1149/1150 now pass. Suite hs-upstream-put: 34/38 → 37/38. Smoke 0-195: 173/195 unchanged. One test remains: "waits on promises" (async/Promise issue).
(Reverse chronological — newest at top.) (Reverse chronological — newest at top.)
### 2026-04-25 — Bucket F: in-expression filter semantics (+1) ### 2026-04-25 — Bucket F: in-expression filter semantics (+1)

View File

@@ -789,11 +789,12 @@
(cons (quote do) (map hs-to-sx body))))))) (cons (quote do) (map hs-to-sx body)))))))
(fn (fn
(ast) (ast)
(cond (let ((ast (if (and (dict? ast) (get ast :hs-ast)) (get ast :children) ast)))
((nil? ast) nil) (cond
((number? ast) ast) ((nil? ast) nil)
((string? ast) ast) ((number? ast) ast)
((boolean? ast) ast) ((string? ast) ast)
((boolean? ast) ast)
((and (symbol? ast) (= (str ast) "sender")) ((and (symbol? ast) (= (str ast) "sender"))
(list (quote hs-sender) (quote event))) (list (quote hs-sender) (quote event)))
((not (list? ast)) ast) ((not (list? ast)) ast)
@@ -2207,7 +2208,7 @@
(list (quote hs-halt!) (quote event) (nth ast 1))) (list (quote hs-halt!) (quote event) (nth ast 1)))
((= head (quote focus!)) ((= head (quote focus!))
(list (quote dom-focus) (hs-to-sx (nth ast 1)))) (list (quote dom-focus) (hs-to-sx (nth ast 1))))
(true ast)))))))) (true ast)))))))))
;; ── Convenience: source → SX ───────────────────────────────── ;; ── Convenience: source → SX ─────────────────────────────────
(define hs-to-sx-from-source (fn (src) (hs-to-sx (hs-compile src)))) (define hs-to-sx-from-source (fn (src) (hs-to-sx (hs-compile src))))

View File

@@ -21,6 +21,15 @@
adv! adv!
(fn () (let ((t (nth tokens p))) (set! p (+ p 1)) t))) (fn () (let ((t (nth tokens p))) (set! p (+ p 1)) t)))
(define at-end? (fn () (or (>= p tok-len) (= (tp-type) "eof")))) (define at-end? (fn () (or (>= p tok-len) (= (tp-type) "eof"))))
(define cur-start (fn () (if (< p tok-len) (get (tp) "pos") 0)))
(define cur-line (fn () (if (< p tok-len) (get (tp) "line") 1)))
(define prev-end (fn () (if (> p 0) (get (nth tokens (- p 1)) "end") 0)))
(define hs-ast-wrap
(fn (raw kind start end-pos line fields)
(if hs-span-mode
{:hs-ast true :kind kind :start start :end end-pos :line line
:src src :children raw :fields fields}
raw)))
(define (define
match-kw match-kw
(fn (fn
@@ -69,19 +78,28 @@
parse-prop-chain parse-prop-chain
(fn (fn
(base) (base)
(if (let
(and (= (tp-type) "class") (not (at-end?))) ((base-start (if (and (dict? base) (get base :hs-ast)) (get base :start) (cur-start)))
(let (base-line (if (and (dict? base) (get base :hs-ast)) (get base :line) (cur-line))))
((prop (tp-val)))
(do
(adv!)
(parse-prop-chain (list (make-symbol ".") base prop))))
(if (if
(= (tp-type) "paren-open") (and (= (tp-type) "class") (not (at-end?)))
(let (let
((args (parse-call-args))) ((prop (tp-val)))
(parse-prop-chain (list (quote method-call) base args))) (do
base)))) (adv!)
(parse-prop-chain
(hs-ast-wrap
(list (make-symbol ".") base prop)
"member" base-start (prev-end) base-line {:root base}))))
(if
(= (tp-type) "paren-open")
(let
((args (parse-call-args)))
(parse-prop-chain
(hs-ast-wrap
(list (quote method-call) base args)
"call" base-start (prev-end) base-line {:root base})))
base)))))
(define (define
parse-trav parse-trav
(fn (fn
@@ -124,8 +142,12 @@
(let (let
((typ (tp-type)) (val (tp-val))) ((typ (tp-type)) (val (tp-val)))
(cond (cond
((= typ "number") (do (adv!) (parse-dur val))) ((= typ "number")
((= typ "string") (do (adv!) val)) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (parse-dur val) "number" s (prev-end) l {}))))
((= typ "string")
(let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap val "string" s (prev-end) l {}))))
((= typ "template") (do (adv!) (list (quote template) val))) ((= typ "template") (do (adv!) (list (quote template) val)))
((and (= typ "keyword") (= val "true")) (do (adv!) true)) ((and (= typ "keyword") (= val "true")) (do (adv!) true))
((and (= typ "keyword") (= val "false")) (do (adv!) false)) ((and (= typ "keyword") (= val "false")) (do (adv!) false))
@@ -190,19 +212,23 @@
((and (= typ "keyword") (= val "last")) ((and (= typ "keyword") (= val "last"))
(do (adv!) (parse-pos-kw (quote last)))) (do (adv!) (parse-pos-kw (quote last))))
((= typ "id") ((= typ "id")
(do (adv!) (list (quote query) (str "#" val)))) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (list (quote query) (str "#" val)) "selector" s (prev-end) l {}))))
((= typ "selector") ((= typ "selector")
(do (let ((s (cur-start)) (l (cur-line)))
(adv!) (do
(if (adv!)
(and (= (tp-type) "keyword") (= (tp-val) "in")) (hs-ast-wrap
(do (if
(adv!) (and (= (tp-type) "keyword") (= (tp-val) "in"))
(list (do
(quote query-scoped) (adv!)
val (list
(parse-cmp (parse-arith (parse-poss (parse-atom)))))) (quote query-scoped)
(list (quote query) val)))) val
(parse-cmp (parse-arith (parse-poss (parse-atom))))))
(list (quote query) val))
"selector" s (prev-end) l {}))))
((= typ "attr") ((= typ "attr")
(do (adv!) (list (quote attr) val (list (quote me))))) (do (adv!) (list (quote attr) val (list (quote me)))))
((= typ "style") ((= typ "style")
@@ -219,8 +245,11 @@
(adv!) (adv!)
(list (quote dom-ref) name (list (quote me))))))) (list (quote dom-ref) name (list (quote me)))))))
((= typ "class") ((= typ "class")
(do (adv!) (list (quote query) (str "." val)))) (let ((s (cur-start)) (l (cur-line)))
((= typ "ident") (do (adv!) (list (quote ref) val))) (do (adv!) (hs-ast-wrap (list (quote query) (str "." val)) "selector" s (prev-end) l {}))))
((= typ "ident")
(let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (list (quote ref) val) "ref" s (prev-end) l {}))))
((= typ "paren-open") ((= typ "paren-open")
(do (do
(adv!) (adv!)
@@ -2021,7 +2050,11 @@
((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%"))))) ((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%")))))
(let (let
((right (let ((a (parse-atom))) (if (nil? a) a (parse-poss a))))) ((right (let ((a (parse-atom))) (if (nil? a) a (parse-poss a)))))
(parse-arith (list op left right))))) (let
((lhs-start (if (and (dict? left) (get left :hs-ast)) (get left :start) 0))
(lhs-line (if (and (dict? left) (get left :hs-ast)) (get left :line) 1)))
(parse-arith
(hs-ast-wrap (list op left right) "arith" lhs-start (prev-end) lhs-line {:lhs left :rhs right}))))))
left)))) left))))
(define (define
parse-the-expr parse-the-expr
@@ -2421,7 +2454,15 @@
((and (= typ "keyword") (= val "put")) ((and (= typ "keyword") (= val "put"))
(do (adv!) (parse-put-cmd))) (do (adv!) (parse-put-cmd)))
((and (= typ "keyword") (= val "if")) ((and (= typ "keyword") (= val "if"))
(do (adv!) (parse-if-cmd))) (let ((s (cur-start)) (l (cur-line)))
(do
(adv!)
(let ((r (parse-if-cmd)))
(let ((tb (if (and (list? r) (> (len r) 2)) (nth r 2) nil)))
(hs-ast-wrap r "if" s (prev-end) l
(if tb
{:true-branch (if (and (list? tb) (= (first tb) (quote do))) (nth tb 1) tb)}
{})))))))
((and (= typ "keyword") (= val "wait")) ((and (= typ "keyword") (= val "wait"))
(do (adv!) (parse-wait-cmd))) (do (adv!) (parse-wait-cmd)))
((and (= typ "keyword") (= val "send")) ((and (= typ "keyword") (= val "send"))
@@ -2429,7 +2470,8 @@
((and (= typ "keyword") (= val "trigger")) ((and (= typ "keyword") (= val "trigger"))
(do (adv!) (parse-trigger-cmd))) (do (adv!) (parse-trigger-cmd)))
((and (= typ "keyword") (= val "log")) ((and (= typ "keyword") (= val "log"))
(do (adv!) (parse-log-cmd))) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (parse-log-cmd) "cmd" s (prev-end) l {}))))
((and (= typ "keyword") (= val "increment")) ((and (= typ "keyword") (= val "increment"))
(do (adv!) (parse-inc-cmd))) (do (adv!) (parse-inc-cmd)))
((and (= typ "keyword") (= val "decrement")) ((and (= typ "keyword") (= val "decrement"))
@@ -2469,7 +2511,8 @@
((and (= typ "keyword") (= val "tell")) ((and (= typ "keyword") (= val "tell"))
(do (adv!) (parse-tell-cmd))) (do (adv!) (parse-tell-cmd)))
((and (= typ "keyword") (= val "for")) ((and (= typ "keyword") (= val "for"))
(do (adv!) (parse-for-cmd))) (let ((s (cur-start)) (l (cur-line)))
(do (adv!) (hs-ast-wrap (parse-for-cmd) "cmd" s (prev-end) l {}))))
((and (= typ "keyword") (= val "make")) ((and (= typ "keyword") (= val "make"))
(do (adv!) (parse-make-cmd))) (do (adv!) (parse-make-cmd)))
((and (= typ "keyword") (= val "install")) ((and (= typ "keyword") (= val "install"))
@@ -2591,13 +2634,31 @@
(true acc2))))))) (true acc2)))))))
(let (let
((cmds (cl-collect (list)))) ((cmds (cl-collect (list))))
(cond (define
((= (len cmds) 0) nil) link-next-cmds
((= (len cmds) 1) (first cmds)) (fn
(true (cmds-list)
(cons (define
(quote do) loop
(filter (fn (c) (not (= c (quote __then__)))) cmds))))))) (fn
(i)
(when (< i (- (len cmds-list) 1))
(let
((cur-node (nth cmds-list i)) (nxt-node (nth cmds-list (+ i 1))))
(when (and (dict? cur-node) (get cur-node :hs-ast))
(dict-set! (get cur-node :fields) "next" nxt-node)))
(loop (+ i 1)))))
(loop 0)
cmds-list))
(let
((linked (if hs-span-mode (link-next-cmds cmds) cmds)))
(cond
((= (len linked) 0) nil)
((= (len linked) 1) (first linked))
(true
(cons
(quote do)
(filter (fn (c) (not (= c (quote __then__)))) linked))))))))
(define (define
parse-on-feat parse-on-feat
(fn (fn
@@ -2749,6 +2810,7 @@
((= val "behavior") (do (adv!) (parse-behavior-feat))) ((= val "behavior") (do (adv!) (parse-behavior-feat)))
((= val "live") (do (adv!) (parse-live-feat))) ((= val "live") (do (adv!) (parse-live-feat)))
((= val "when") (do (adv!) (parse-when-feat))) ((= val "when") (do (adv!) (parse-when-feat)))
((= val "worker") (error "worker plugin is not installed — see https://hyperscript.org/features/worker"))
(true (parse-cmd-list)))))) (true (parse-cmd-list))))))
(define (define
coll-feats coll-feats
@@ -2767,4 +2829,12 @@
(first features) (first features)
(cons (quote do) features)))))) (cons (quote do) features))))))
(define hs-span-mode false)
(define hs-compile (fn (src) (hs-parse (hs-tokenize src) src))) (define hs-compile (fn (src) (hs-parse (hs-tokenize src) src)))
(define hs-parse-ast
(fn (src)
(set! hs-span-mode true)
(let ((result (hs-parse (hs-tokenize src) src)))
(do (set! hs-span-mode false) result))))

View File

@@ -48,7 +48,7 @@
(fn (fn
(target event-name handler) (target event-name handler)
(let (let
((wrapped (fn (event) (guard (e ((and (not (= event-name "exception")) (not (= event-name "error"))) (dom-dispatch target "exception" {:error e})) (true (raise e))) (handler event))))) ((wrapped (fn (event) (guard (e ((and (not (= event-name "exception")) (not (= event-name "error"))) (dom-dispatch target "exception" {:error e})) (true (raise e))) (do (handler event) (when event (host-call event "stopPropagation")))))))
(let (let
((unlisten (dom-listen target event-name wrapped)) ((unlisten (dom-listen target event-name wrapped))
(prev (or (dom-get-data target "hs-unlisteners") (list)))) (prev (or (dom-get-data target "hs-unlisteners") (list))))
@@ -2525,3 +2525,57 @@
(fn (fn
(fn-name args) (fn-name args)
(let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil)))) (let ((fn (host-global fn-name))) (if fn (host-call-fn fn args) nil))))
;; ── SourceInfo API ────────────────────────────────────────────────
(define
hs-source-for
(fn
(node)
(substring (get node :src) (get node :start) (get node :end))))
(define
hs-line-for
(fn
(node)
(let
((lines (split (get node :src) "\n"))
(line-idx (- (get node :line) 1)))
(if (< line-idx (len lines)) (nth lines line-idx) ""))))
(define
hs-node-get
(fn
(node key)
(get (get node :fields) key)))
(define
hs-src
(fn (src-str)
(hs-source-for (hs-parse-ast src-str))))
(define
hs-src-at
(fn
(src-str path)
(define
walk
(fn
(node keys)
(if (or (nil? keys) (= (len keys) 0))
node
(walk (hs-node-get node (first keys)) (rest keys)))))
(hs-source-for (walk (hs-parse-ast src-str) path))))
(define
hs-line-at
(fn
(src-str path)
(define
walk
(fn
(node keys)
(if (or (nil? keys) (= (len keys) 0))
node
(walk (hs-node-get node (first keys)) (rest keys)))))
(hs-line-for (walk (hs-parse-ast src-str) path))))

View File

@@ -1,6 +1,6 @@
;; _hyperscript tokenizer — produces token stream from hyperscript source ;; _hyperscript tokenizer — produces token stream from hyperscript source
;; ;;
;; Tokens: {:type T :value V :pos P} ;; Tokens: {:type T :value V :pos P :end E :line L}
;; Types: "keyword" "ident" "number" "string" "class" "id" "attr" "style" ;; Types: "keyword" "ident" "number" "string" "class" "id" "attr" "style"
;; "selector" "op" "dot" "paren-open" "paren-close" "bracket-open" ;; "selector" "op" "dot" "paren-open" "paren-close" "bracket-open"
;; "bracket-close" "brace-open" "brace-close" "comma" "colon" ;; "bracket-close" "brace-open" "brace-close" "comma" "colon"
@@ -8,7 +8,7 @@
;; ── Token constructor ───────────────────────────────────────────── ;; ── Token constructor ─────────────────────────────────────────────
(define hs-make-token (fn (type value pos) {:pos pos :value value :type type})) (define hs-make-token (fn (type value pos end line) {:pos pos :end end :line line :value value :type type}))
;; ── Character predicates ────────────────────────────────────────── ;; ── Character predicates ──────────────────────────────────────────
@@ -198,14 +198,22 @@
(fn (fn
(src) (src)
(let (let
((tokens (list)) (pos 0) (src-len (len src))) ((tokens (list)) (pos 0) (src-len (len src)) (current-line 1))
(define (define
hs-peek hs-peek
(fn (fn
(offset) (offset)
(if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil))) (if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil)))
(define hs-cur (fn () (hs-peek 0))) (define hs-cur (fn () (hs-peek 0)))
(define hs-advance! (fn (n) (set! pos (+ pos n)))) (define
hs-advance!
(fn
(n)
(when
(> n 0)
(when (= (hs-cur) "\n") (set! current-line (+ current-line 1)))
(set! pos (+ pos 1))
(hs-advance! (- n 1)))))
(define (define
skip-ws! skip-ws!
(fn (fn
@@ -427,8 +435,8 @@
(define (define
hs-emit! hs-emit!
(fn (fn
(type value start) (type value start start-line)
(append! tokens (hs-make-token type value start)))) (append! tokens (hs-make-token type value start pos start-line))))
(define (define
scan! scan!
(fn (fn
@@ -437,7 +445,7 @@
(when (when
(< pos src-len) (< pos src-len)
(let (let
((ch (hs-cur)) (start pos)) ((ch (hs-cur)) (start pos) (start-line current-line))
(cond (cond
(and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-")) (and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-"))
(do (hs-advance! 2) (skip-comment!) (scan!)) (do (hs-advance! 2) (skip-comment!) (scan!))
@@ -454,9 +462,9 @@
(= (hs-peek 1) "[") (= (hs-peek 1) "[")
(= (hs-peek 1) "*") (= (hs-peek 1) "*")
(= (hs-peek 1) ":"))) (= (hs-peek 1) ":")))
(do (hs-emit! "selector" (read-selector) start) (scan!)) (do (hs-emit! "selector" (read-selector) start start-line) (scan!))
(and (= ch ".") (< (+ pos 1) src-len) (= (hs-peek 1) ".")) (and (= ch ".") (< (+ pos 1) src-len) (= (hs-peek 1) "."))
(do (hs-emit! "op" ".." start) (hs-advance! 2) (scan!)) (do (hs-advance! 2) (hs-emit! "op" ".." start start-line) (scan!))
(and (and
(= ch ".") (= ch ".")
(< (+ pos 1) src-len) (< (+ pos 1) src-len)
@@ -466,7 +474,7 @@
(= (hs-peek 1) "_"))) (= (hs-peek 1) "_")))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "class" (read-class-name pos) start) (hs-emit! "class" (read-class-name pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "#") (= ch "#")
@@ -474,7 +482,7 @@
(hs-ident-start? (hs-peek 1))) (hs-ident-start? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "id" (read-ident pos) start) (hs-emit! "id" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "@") (= ch "@")
@@ -482,7 +490,7 @@
(hs-ident-char? (hs-peek 1))) (hs-ident-char? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "attr" (read-ident pos) start) (hs-emit! "attr" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "^") (= ch "^")
@@ -490,7 +498,7 @@
(hs-ident-char? (hs-peek 1))) (hs-ident-char? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "hat" (read-ident pos) start) (hs-emit! "hat" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch "~") (= ch "~")
@@ -498,7 +506,7 @@
(hs-letter? (hs-peek 1))) (hs-letter? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "component" (str "~" (read-ident pos)) start) (hs-emit! "component" (str "~" (read-ident pos)) start start-line)
(scan!)) (scan!))
(and (and
(= ch "*") (= ch "*")
@@ -506,7 +514,7 @@
(hs-letter? (hs-peek 1))) (hs-letter? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "style" (read-ident pos) start) (hs-emit! "style" (read-ident pos) start start-line)
(scan!)) (scan!))
(and (and
(= ch ":") (= ch ":")
@@ -514,7 +522,7 @@
(hs-ident-start? (hs-peek 1))) (hs-ident-start? (hs-peek 1)))
(do (do
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "local" (read-ident pos) start) (hs-emit! "local" (read-ident pos) start start-line)
(scan!)) (scan!))
(or (or
(= ch "\"") (= ch "\"")
@@ -527,11 +535,11 @@
(or (or
(>= (+ pos 2) src-len) (>= (+ pos 2) src-len)
(not (hs-ident-char? (hs-peek 2)))))))) (not (hs-ident-char? (hs-peek 2))))))))
(do (hs-emit! "string" (read-string ch) start) (scan!)) (do (hs-emit! "string" (read-string ch) start start-line) (scan!))
(= ch "`") (= ch "`")
(do (hs-emit! "template" (read-template) start) (scan!)) (do (hs-emit! "template" (read-template) start start-line) (scan!))
(hs-digit? ch) (hs-digit? ch)
(do (hs-emit! "number" (read-number start) start) (scan!)) (do (hs-emit! "number" (read-number start) start start-line) (scan!))
(hs-ident-start? ch) (hs-ident-start? ch)
(do (do
(let (let
@@ -539,7 +547,8 @@
(hs-emit! (hs-emit!
(if (hs-keyword? word) "keyword" "ident") (if (hs-keyword? word) "keyword" "ident")
word word
start)) start
start-line))
(scan!)) (scan!))
(and (and
(or (= ch "=") (= ch "!") (= ch "<") (= ch ">")) (or (= ch "=") (= ch "!") (= ch "<") (= ch ">"))
@@ -551,8 +560,8 @@
(or (= ch "=") (= ch "!")) (or (= ch "=") (= ch "!"))
(< (+ pos 2) src-len) (< (+ pos 2) src-len)
(= (hs-peek 2) "=")) (= (hs-peek 2) "="))
(do (hs-emit! "op" (str ch "==") start) (hs-advance! 3)) (do (hs-advance! 3) (hs-emit! "op" (str ch "==") start start-line))
(do (hs-emit! "op" (str ch "=") start) (hs-advance! 2))) (do (hs-advance! 2) (hs-emit! "op" (str ch "=") start start-line)))
(scan!)) (scan!))
(and (and
(= ch "'") (= ch "'")
@@ -561,66 +570,66 @@
(or (or
(>= (+ pos 2) src-len) (>= (+ pos 2) src-len)
(not (hs-ident-char? (hs-peek 2))))) (not (hs-ident-char? (hs-peek 2)))))
(do (hs-emit! "op" "'s" start) (hs-advance! 2) (scan!)) (do (hs-advance! 2) (hs-emit! "op" "'s" start start-line) (scan!))
(= ch "(") (= ch "(")
(do (do
(hs-emit! "paren-open" "(" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "paren-open" "(" start start-line)
(scan!)) (scan!))
(= ch ")") (= ch ")")
(do (do
(hs-emit! "paren-close" ")" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "paren-close" ")" start start-line)
(scan!)) (scan!))
(= ch "[") (= ch "[")
(do (do
(hs-emit! "bracket-open" "[" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "bracket-open" "[" start start-line)
(scan!)) (scan!))
(= ch "]") (= ch "]")
(do (do
(hs-emit! "bracket-close" "]" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "bracket-close" "]" start start-line)
(scan!)) (scan!))
(= ch "{") (= ch "{")
(do (do
(hs-emit! "brace-open" "{" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "brace-open" "{" start start-line)
(scan!)) (scan!))
(= ch "}") (= ch "}")
(do (do
(hs-emit! "brace-close" "}" start)
(hs-advance! 1) (hs-advance! 1)
(hs-emit! "brace-close" "}" start start-line)
(scan!)) (scan!))
(= ch ",") (= ch ",")
(do (hs-emit! "comma" "," start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "comma" "," start start-line) (scan!))
(= ch "+") (= ch "+")
(do (hs-emit! "op" "+" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "+" start start-line) (scan!))
(= ch "-") (= ch "-")
(do (hs-emit! "op" "-" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "-" start start-line) (scan!))
(= ch "/") (= ch "/")
(do (hs-emit! "op" "/" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "/" start start-line) (scan!))
(= ch "=") (= ch "=")
(do (hs-emit! "op" "=" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "=" start start-line) (scan!))
(= ch "<") (= ch "<")
(do (hs-emit! "op" "<" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "<" start start-line) (scan!))
(= ch ">") (= ch ">")
(do (hs-emit! "op" ">" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" ">" start start-line) (scan!))
(= ch "!") (= ch "!")
(do (hs-emit! "op" "!" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "!" start start-line) (scan!))
(= ch "*") (= ch "*")
(do (hs-emit! "op" "*" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "*" start start-line) (scan!))
(= ch "%") (= ch "%")
(do (hs-emit! "op" "%" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "%" start start-line) (scan!))
(= ch ".") (= ch ".")
(do (hs-emit! "dot" "." start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "dot" "." start start-line) (scan!))
(= ch "\\") (= ch "\\")
(do (hs-emit! "op" "\\" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "\\" start start-line) (scan!))
(= ch ":") (= ch ":")
(do (hs-emit! "colon" ":" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "colon" ":" start start-line) (scan!))
(= ch "|") (= ch "|")
(do (hs-emit! "op" "|" start) (hs-advance! 1) (scan!)) (do (hs-advance! 1) (hs-emit! "op" "|" start start-line) (scan!))
:else (do (hs-advance! 1) (scan!))))))) :else (do (hs-advance! 1) (scan!)))))))
(scan!) (scan!)
(hs-emit! "eof" nil pos) (hs-emit! "eof" nil pos current-line)
tokens))) tokens)))

View File

@@ -2467,13 +2467,28 @@
;; ── core/sourceInfo (4 tests) ── ;; ── core/sourceInfo (4 tests) ──
(defsuite "hs-upstream-core/sourceInfo" (defsuite "hs-upstream-core/sourceInfo"
(deftest "debug" (deftest "debug"
(error "SKIP (untranslated): debug")) (assert= (hs-src "<button.foo/>") "<button.foo/>"))
(deftest "get line works for statements" (deftest "get line works for statements"
(error "SKIP (untranslated): get line works for statements")) (assert= (hs-line-at "if true\n log 'it was true'\n log 'it was true'" (list)) "if true")
(assert= (hs-line-at "if true\n log 'it was true'\n log 'it was true'" (list :true-branch)) " log 'it was true'")
(assert= (hs-line-at "if true\n log 'it was true'\n log 'it was true'" (list :true-branch :next)) " log 'it was true'"))
(deftest "get source works for expressions" (deftest "get source works for expressions"
(error "SKIP (untranslated): get source works for expressions")) (assert= (hs-src "1") "1")
(assert= (hs-src "a.b") "a.b")
(assert= (hs-src-at "a.b" (list :root)) "a")
(assert= (hs-src "a.b()") "a.b()")
(assert= (hs-src-at "a.b()" (list :root)) "a.b")
(assert= (hs-src-at "a.b()" (list :root :root)) "a")
(assert= (hs-src "<button.foo/>") "<button.foo/>")
(assert= (hs-src "x + y") "x + y")
(assert= (hs-src-at "x + y" (list :lhs)) "x")
(assert= (hs-src-at "x + y" (list :rhs)) "y")
(assert= (hs-src "'foo'") "'foo'")
(assert= (hs-src ".foo") ".foo")
(assert= (hs-src "#bar") "#bar"))
(deftest "get source works for statements" (deftest "get source works for statements"
(error "SKIP (untranslated): get source works for statements")) (assert= (hs-src "if true log 'it was true'") "if true log 'it was true'")
(assert= (hs-src "for x in [1, 2, 3] log x then log x end") "for x in [1, 2, 3] log x then log x end"))
) )
;; ── core/tokenizer (17 tests) ── ;; ── core/tokenizer (17 tests) ──
@@ -13595,5 +13610,9 @@ end")
;; ── worker (1 tests) ── ;; ── worker (1 tests) ──
(defsuite "hs-upstream-worker" (defsuite "hs-upstream-worker"
(deftest "raises a helpful error when the worker plugin is not installed" (deftest "raises a helpful error when the worker plugin is not installed"
(error "SKIP (untranslated): raises a helpful error when the worker plugin is not installed")) (let ((result (guard (e (true (if (string? e) e (str e))))
(hs-compile "worker MyWorker def noop() end end")
"")))
(assert (contains? result "worker plugin"))
(assert (contains? result "hyperscript.org/features/worker"))))
) )

View File

@@ -2015,6 +2015,47 @@ def generate_eval_only_test(test, idx):
f' )' f' )'
) )
# Special case: cluster-38 sourceInfo tests.
if test['name'] == 'debug':
return (
f' (deftest "{safe_name}"\n'
f' (assert= (hs-src "<button.foo/>") "<button.foo/>"))'
)
if test['name'] == 'get source works for expressions':
return (
f' (deftest "{safe_name}"\n'
f' (assert= (hs-src "1") "1")\n'
f' (assert= (hs-src "a.b") "a.b")\n'
f' (assert= (hs-src-at "a.b" (list :root)) "a")\n'
f' (assert= (hs-src "a.b()") "a.b()")\n'
f' (assert= (hs-src-at "a.b()" (list :root)) "a.b")\n'
f' (assert= (hs-src-at "a.b()" (list :root :root)) "a")\n'
f' (assert= (hs-src "<button.foo/>") "<button.foo/>")\n'
f' (assert= (hs-src "x + y") "x + y")\n'
f' (assert= (hs-src-at "x + y" (list :lhs)) "x")\n'
f' (assert= (hs-src-at "x + y" (list :rhs)) "y")\n'
f" (assert= (hs-src \"'foo'\") \"'foo'\")\n"
f' (assert= (hs-src ".foo") ".foo")\n'
f' (assert= (hs-src "#bar") "#bar"))'
)
if test['name'] == 'get source works for statements':
return (
f' (deftest "{safe_name}"\n'
f" (assert= (hs-src \"if true log 'it was true'\") \"if true log 'it was true'\")\n"
f' (assert= (hs-src "for x in [1, 2, 3] log x then log x end") "for x in [1, 2, 3] log x then log x end"))'
)
if test['name'] == 'get line works for statements':
src = "if true\\n log 'it was true'\\n log 'it was true'"
return (
f' (deftest "{safe_name}"\n'
f' (assert= (hs-line-at "{src}" (list)) "if true")\n'
f" (assert= (hs-line-at \"{src}\" (list :true-branch)) \" log 'it was true'\")\n"
f" (assert= (hs-line-at \"{src}\" (list :true-branch :next)) \" log 'it was true'\"))"
)
lines.append(f' (deftest "{safe_name}"') lines.append(f' (deftest "{safe_name}"')
assertions = [] assertions = []