Merge branch 'hs-e36-websocket' into loops/hs
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s

This commit is contained in:
2026-04-26 18:31:16 +00:00
16 changed files with 921 additions and 83 deletions

View File

@@ -787,6 +787,31 @@
(quote fn)
(cons (quote me) (map make-symbol params))
(cons (quote do) (map hs-to-sx body)))))))
(define
emit-socket
(fn
(ast)
(let
((name-path (nth ast 1))
(url (nth ast 2))
(timeout-ms (nth ast 3))
(on-msg (nth ast 4)))
(let
((handler
(if
(nil? on-msg)
nil
(let
((body (hs-to-sx (nth on-msg 2))))
(list (quote fn) (list (quote event)) body))))
(json?-val (if (nil? on-msg) false (nth on-msg 1))))
(list
(quote hs-socket-register!)
(cons (quote list) name-path)
url
(if (nil? timeout-ms) nil (hs-to-sx timeout-ms))
handler
json?-val)))))
(fn
(ast)
(cond
@@ -2081,6 +2106,7 @@
(quote _hs-def-val))
(quote _hs-def-val))))))
((= head (quote behavior)) (emit-behavior ast))
((= head (quote socket)) (emit-socket ast))
((= head (quote sx-eval))
(let
((src (nth ast 1)))

View File

@@ -2906,6 +2906,63 @@
(match-kw "end")
(list (quote when-feat-no-op)))))
(do (pwf-skip) (match-kw "end") (list (quote when-feat-no-op))))))
(define
parse-socket-feat
(fn
()
(let
((seg0 (tp-val)))
(adv!)
(define
collect-segs
(fn
(acc)
(if
(= (tp-type) "class")
(let
((seg (tp-val)))
(adv!)
(collect-segs (append acc (list seg))))
acc)))
(let
((name-path (collect-segs (list seg0))))
(define
url-cont?
(fn
()
(or
(= (tp-type) "ident")
(= (tp-type) "number")
(= (tp-type) "op")
(= (tp-type) "colon")
(= (tp-type) "dot")
(and
(= (tp-type) "keyword")
(not
(or
(= (tp-val) "end")
(= (tp-val) "with")
(= (tp-val) "on")
(= (tp-val) "as")))))))
(define
collect-url
(fn
(parts)
(if
(and (not (at-end?)) (url-cont?))
(let
((v (tp-val)))
(adv!)
(collect-url (append parts (list v))))
(join "" parts))))
(let
((url (cond ((and (= (tp-type) "op") (= (tp-val) "/")) (do (adv!) (collect-url (list "/")))) ((= (tp-type) "ident") (let ((scheme (tp-val))) (adv!) (if (= (tp-type) "colon") (collect-url (list scheme)) (parse-arith (parse-poss (list (quote ref) scheme)))))) (true (parse-atom)))))
(let
((timeout-ms (if (match-kw "with") (do (adv!) (parse-expr)) nil)))
(let
((on-msg (if (match-kw "on") (do (adv!) (let ((json? (if (match-kw "as") (do (adv!) true) false))) (let ((body (parse-cmd-list))) (list (quote on-message) json? body)))) nil)))
(match-kw "end")
(list (quote socket) name-path url timeout-ms on-msg))))))))
(define
parse-feat
(fn
@@ -2926,6 +2983,7 @@
((= val "behavior") (do (adv!) (parse-behavior-feat)))
((= val "live") (do (adv!) (parse-live-feat)))
((= val "when") (do (adv!) (parse-when-feat)))
((= val "socket") (do (adv!) (parse-socket-feat)))
((= val "worker")
(error
"worker plugin is not installed — see https://hyperscript.org/features/worker"))

View File

@@ -2592,3 +2592,113 @@
node
(walk (hs-node-get node (first keys)) (rest keys)))))
(hs-line-for (walk (hs-parse-ast src-str) path))))
;; ── WebSocket / socket feature ───────────────────────────────────
(define
hs-try-json-parse
(fn (s) (host-call (host-global "JSON") "parse" s)))
(define
hs-socket-resolve-rpc!
(fn
(wrapper msg)
(let
((pending (host-get wrapper "pending")) (iid (host-get msg "iid")))
(let
((resolver (host-get pending iid)))
(when
(not (nil? resolver))
(if
(not (nil? (host-get msg "return")))
(host-call resolver "resolve" (host-get msg "return"))
(host-call resolver "reject" (host-get msg "throw")))
(host-set! pending iid nil))))))
(define
hs-socket-register!
(fn
(name-path url timeout-ms handler json?)
(let
((ws-url (cond ((or (starts-with? url "ws://") (starts-with? url "wss://")) url) (true (let ((proto (host-get (host-global "location") "protocol")) (h (host-get (host-global "location") "host"))) (str (if (= proto "https:") "wss:" "ws:") "//" h url))))))
(let
((ws (host-new "WebSocket" ws-url)))
(let
((wrapper (host-new "Object")))
(host-set! wrapper "raw" ws)
(host-set! wrapper "url" ws-url)
(host-set! wrapper "timeout" timeout-ms)
(host-set! wrapper "pending" (host-new "Object"))
(host-set! wrapper "handler" handler)
(host-set! wrapper "json?" json?)
(host-set! wrapper "closed?" false)
(host-set! wrapper "closedFlag" nil)
(let
((proxy-factory (host-global "_hs_make_rpc_proxy")))
(when
proxy-factory
(host-set!
wrapper
"rpc"
(host-call proxy-factory "call" nil wrapper))))
(host-set!
ws
"onmessage"
(host-callback
(fn
(event)
(let
((data (host-get event "data")))
(let
((parsed (hs-try-json-parse data)))
(cond
((and (not (nil? parsed)) (not (nil? (host-get parsed "iid"))))
(hs-socket-resolve-rpc! wrapper parsed))
((not (nil? handler))
(if
json?
(if
(not (nil? parsed))
(handler parsed)
(error "Received non-JSON message"))
(handler event)))))))))
(host-call
ws
"addEventListener"
"close"
(host-callback
(fn
(evt)
(host-set! wrapper "closedFlag" "1"))))
(host-set!
wrapper
"dispatchEvent"
(host-callback
(fn
(evt)
(let
((payload (host-new "Object")))
(host-set! payload "type" (host-get evt "type"))
(host-call
(host-get wrapper "raw")
"send"
(host-call
(host-global "JSON")
"stringify"
payload))))))
(define
bind-path!
(fn
(obj path)
(if
(= (len path) 1)
(host-set! obj (first path) wrapper)
(let
((key (first path)) (rest-path (rest path)))
(let
((next (or (host-get obj key) (host-new "Object"))))
(host-set! obj key next)
(bind-path! next rest-path))))))
(bind-path! (host-global "window") name-path)
wrapper)))))

View File

@@ -473,7 +473,11 @@
(cond
(and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-"))
(do (hs-advance! 2) (skip-comment!) (scan!))
(and (= ch "/") (< (+ pos 1) src-len) (= (hs-peek 1) "/"))
(and
(= ch "/")
(< (+ pos 1) src-len)
(= (hs-peek 1) "/")
(not (and (> pos 0) (= (hs-peek -1) ":"))))
(do (hs-advance! 2) (skip-comment!) (scan!))
(and
(= ch "<")