Compare commits
4 Commits
hs-e36-web
...
hs-e37-tok
| Author | SHA1 | Date | |
|---|---|---|---|
| 880503e2b6 | |||
| 3003c8a069 | |||
| 8c62137d32 | |||
| 8ac669c739 |
@@ -787,31 +787,6 @@
|
|||||||
(quote fn)
|
(quote fn)
|
||||||
(cons (quote me) (map make-symbol params))
|
(cons (quote me) (map make-symbol params))
|
||||||
(cons (quote do) (map hs-to-sx body)))))))
|
(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
|
(fn
|
||||||
(ast)
|
(ast)
|
||||||
(cond
|
(cond
|
||||||
@@ -917,6 +892,12 @@
|
|||||||
(< i n)
|
(< i n)
|
||||||
(let
|
(let
|
||||||
((ch (nth raw i)))
|
((ch (nth raw i)))
|
||||||
|
(if
|
||||||
|
(and (= ch "\\") (< (+ i 1) n) (= (nth raw (+ i 1)) "$"))
|
||||||
|
(do
|
||||||
|
(set! buf (str buf "$"))
|
||||||
|
(set! i (+ i 2))
|
||||||
|
(tpl-collect))
|
||||||
(if
|
(if
|
||||||
(and (= ch "$") (< (+ i 1) n))
|
(and (= ch "$") (< (+ i 1) n))
|
||||||
(if
|
(if
|
||||||
@@ -956,7 +937,7 @@
|
|||||||
(do
|
(do
|
||||||
(set! buf (str buf ch))
|
(set! buf (str buf ch))
|
||||||
(set! i (+ i 1))
|
(set! i (+ i 1))
|
||||||
(tpl-collect)))))))
|
(tpl-collect))))))))
|
||||||
(tpl-collect)
|
(tpl-collect)
|
||||||
(tpl-flush)
|
(tpl-flush)
|
||||||
(cons (quote str) parts))))
|
(cons (quote str) parts))))
|
||||||
@@ -2100,7 +2081,6 @@
|
|||||||
(quote _hs-def-val))
|
(quote _hs-def-val))
|
||||||
(quote _hs-def-val))))))
|
(quote _hs-def-val))))))
|
||||||
((= head (quote behavior)) (emit-behavior ast))
|
((= head (quote behavior)) (emit-behavior ast))
|
||||||
((= head (quote socket)) (emit-socket ast))
|
|
||||||
((= head (quote sx-eval))
|
((= head (quote sx-eval))
|
||||||
(let
|
(let
|
||||||
((src (nth ast 1)))
|
((src (nth ast 1)))
|
||||||
|
|||||||
@@ -2729,63 +2729,6 @@
|
|||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote when-feat-no-op)))))
|
(list (quote when-feat-no-op)))))
|
||||||
(do (pwf-skip) (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
|
(define
|
||||||
parse-feat
|
parse-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -2806,7 +2749,6 @@
|
|||||||
((= 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 "socket") (do (adv!) (parse-socket-feat)))
|
|
||||||
(true (parse-cmd-list))))))
|
(true (parse-cmd-list))))))
|
||||||
(define
|
(define
|
||||||
coll-feats
|
coll-feats
|
||||||
|
|||||||
@@ -2020,6 +2020,12 @@
|
|||||||
(< i n)
|
(< i n)
|
||||||
(let
|
(let
|
||||||
((ch (nth raw i)))
|
((ch (nth raw i)))
|
||||||
|
(if
|
||||||
|
(and (= ch "\\") (< (+ i 1) n) (= (nth raw (+ i 1)) "$"))
|
||||||
|
(do
|
||||||
|
(set! result (str result "$"))
|
||||||
|
(set! i (+ i 2))
|
||||||
|
(tpl-loop))
|
||||||
(if
|
(if
|
||||||
(and (= ch "$") (< (+ i 1) n))
|
(and (= ch "$") (< (+ i 1) n))
|
||||||
(if
|
(if
|
||||||
@@ -2089,7 +2095,7 @@
|
|||||||
(do
|
(do
|
||||||
(set! result (str result ch))
|
(set! result (str result ch))
|
||||||
(set! i (+ i 1))
|
(set! i (+ i 1))
|
||||||
(tpl-loop)))))))
|
(tpl-loop))))))))
|
||||||
(do (tpl-loop) result))))
|
(do (tpl-loop) result))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -2526,111 +2532,187 @@
|
|||||||
(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))))
|
||||||
|
|
||||||
;; ── WebSocket / socket feature ───────────────────────────────────
|
;; ── E37 Tokenizer-as-API ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define hs-eof-sentinel (fn () {:type "EOF" :value "<<<EOF>>>" :op false}))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-try-json-parse
|
hs-op-type
|
||||||
(fn (s) (host-call (host-global "JSON") "parse" s)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
hs-socket-resolve-rpc!
|
|
||||||
(fn
|
(fn
|
||||||
(wrapper msg)
|
(val)
|
||||||
(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
|
(cond
|
||||||
((and (not (nil? parsed)) (not (nil? (host-get parsed "iid"))))
|
((= val "+") "PLUS")
|
||||||
(hs-socket-resolve-rpc! wrapper parsed))
|
((= val "-") "MINUS")
|
||||||
((not (nil? handler))
|
((= val "*") "MULTIPLY")
|
||||||
(if
|
((= val "/") "SLASH")
|
||||||
json?
|
((= val "%") "PERCENT")
|
||||||
(if
|
((= val "|") "PIPE")
|
||||||
(not (nil? parsed))
|
((= val "!") "EXCLAMATION")
|
||||||
(handler parsed)
|
((= val "?") "QUESTION")
|
||||||
(error "Received non-JSON message"))
|
((= val "#") "POUND")
|
||||||
(handler event)))))))))
|
((= val "&") "AMPERSAND")
|
||||||
(host-call
|
((= val ";") "SEMI")
|
||||||
ws
|
((= val "=") "EQUALS")
|
||||||
"addEventListener"
|
((= val "<") "L_ANG")
|
||||||
"close"
|
((= val ">") "R_ANG")
|
||||||
(host-callback
|
((= val "<=") "LTE_ANG")
|
||||||
(fn
|
((= val ">=") "GTE_ANG")
|
||||||
(evt)
|
((= val "==") "EQ")
|
||||||
(host-set! wrapper "closedFlag" "1"))))
|
((= val "===") "EQQ")
|
||||||
(host-set!
|
((= val "\\") "BACKSLASH")
|
||||||
wrapper
|
(true (str "OP_" val)))))
|
||||||
"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
|
(define
|
||||||
bind-path!
|
hs-raw->api-token
|
||||||
(fn
|
(fn
|
||||||
(obj path)
|
(tok)
|
||||||
|
(let
|
||||||
|
((raw-type (get tok "type"))
|
||||||
|
(raw-val (get tok "value")))
|
||||||
|
(let
|
||||||
|
((up-type
|
||||||
|
(cond
|
||||||
|
((or (= raw-type "ident") (= raw-type "keyword")) "IDENTIFIER")
|
||||||
|
((= raw-type "number") "NUMBER")
|
||||||
|
((= raw-type "string") "STRING")
|
||||||
|
((= raw-type "class") "CLASS_REF")
|
||||||
|
((= raw-type "id") "ID_REF")
|
||||||
|
((= raw-type "attr") "ATTRIBUTE_REF")
|
||||||
|
((= raw-type "style") "STYLE_REF")
|
||||||
|
((= raw-type "selector") "QUERY_REF")
|
||||||
|
((= raw-type "eof") "EOF")
|
||||||
|
((= raw-type "paren-open") "L_PAREN")
|
||||||
|
((= raw-type "paren-close") "R_PAREN")
|
||||||
|
((= raw-type "bracket-open") "L_BRACKET")
|
||||||
|
((= raw-type "bracket-close") "R_BRACKET")
|
||||||
|
((= raw-type "brace-open") "L_BRACE")
|
||||||
|
((= raw-type "brace-close") "R_BRACE")
|
||||||
|
((= raw-type "comma") "COMMA")
|
||||||
|
((= raw-type "dot") "PERIOD")
|
||||||
|
((= raw-type "colon") "COLON")
|
||||||
|
((= raw-type "op") (hs-op-type raw-val))
|
||||||
|
(true (str "UNKNOWN_" raw-type))))
|
||||||
|
(up-val
|
||||||
|
(cond
|
||||||
|
((= raw-type "class") (str "." raw-val))
|
||||||
|
((= raw-type "id") (str "#" raw-val))
|
||||||
|
((= raw-type "eof") "<<<EOF>>>")
|
||||||
|
(true raw-val)))
|
||||||
|
(is-op
|
||||||
|
(or
|
||||||
|
(= raw-type "paren-open")
|
||||||
|
(= raw-type "paren-close")
|
||||||
|
(= raw-type "bracket-open")
|
||||||
|
(= raw-type "bracket-close")
|
||||||
|
(= raw-type "brace-open")
|
||||||
|
(= raw-type "brace-close")
|
||||||
|
(= raw-type "comma")
|
||||||
|
(= raw-type "dot")
|
||||||
|
(= raw-type "colon")
|
||||||
|
(= raw-type "op"))))
|
||||||
|
{:type up-type :value up-val :op is-op}))))
|
||||||
|
|
||||||
|
;; Expand "class" and "id" tokens that follow a closing bracket into
|
||||||
|
;; separate dot/hash + ident tokens, matching upstream context-sensitive
|
||||||
|
;; behaviour: after ) ] } the dot is property access, not a CLASS_REF.
|
||||||
|
(define
|
||||||
|
hs-normalize-raw-tokens
|
||||||
|
(fn
|
||||||
|
(raw-real)
|
||||||
|
(let
|
||||||
|
((result (list))
|
||||||
|
(prev-type nil))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(tok)
|
||||||
|
(let
|
||||||
|
((typ (get tok "type"))
|
||||||
|
(val (get tok "value"))
|
||||||
|
(tok-pos (get tok "pos")))
|
||||||
(if
|
(if
|
||||||
(= (len path) 1)
|
(and
|
||||||
(host-set! obj (first path) wrapper)
|
(or (= typ "class") (= typ "id"))
|
||||||
|
(or
|
||||||
|
(= prev-type "paren-close")
|
||||||
|
(= prev-type "bracket-close")
|
||||||
|
(= prev-type "brace-close")))
|
||||||
|
(do
|
||||||
|
(if
|
||||||
|
(= typ "class")
|
||||||
|
(do
|
||||||
|
(append! result {:type "dot" :value "." :pos tok-pos})
|
||||||
|
(append! result {:type "ident" :value val :pos (+ tok-pos 1)}))
|
||||||
|
(do
|
||||||
|
(append! result {:type "op" :value "#" :pos tok-pos})
|
||||||
|
(append! result {:type "ident" :value val :pos (+ tok-pos 1)})))
|
||||||
|
(set! prev-type "ident"))
|
||||||
|
(do
|
||||||
|
(append! result tok)
|
||||||
|
(set! prev-type typ)))))
|
||||||
|
raw-real)
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-tokens-of
|
||||||
|
(fn
|
||||||
|
(src &rest rest)
|
||||||
(let
|
(let
|
||||||
((key (first path)) (rest-path (rest path)))
|
((template? (and (> (len rest) 0) (= (first rest) :template)))
|
||||||
|
(raw (if template? (hs-tokenize-template src) (hs-tokenize src))))
|
||||||
|
(if
|
||||||
|
template?
|
||||||
|
{:source src :list (map hs-raw->api-token raw) :pos 0}
|
||||||
|
;; Normal mode: filter EOF, context-normalise, add trailing-WS sentinel
|
||||||
(let
|
(let
|
||||||
((next (or (host-get obj key) (host-new "Object"))))
|
((real (filter (fn (t) (not (= (get t "type") "eof"))) raw)))
|
||||||
(host-set! obj key next)
|
(let
|
||||||
(bind-path! next rest-path))))))
|
((norm (hs-normalize-raw-tokens real)))
|
||||||
(bind-path! (host-global "window") name-path)
|
(let
|
||||||
wrapper)))))
|
((api (map hs-raw->api-token norm)))
|
||||||
|
(let
|
||||||
|
((with-sep
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(> (len norm) 0)
|
||||||
|
(let
|
||||||
|
((last-tok (nth norm (- (len norm) 1))))
|
||||||
|
(let
|
||||||
|
((end-pos
|
||||||
|
(+ (get last-tok "pos")
|
||||||
|
(len (get last-tok "value")))))
|
||||||
|
(and
|
||||||
|
(< end-pos (len src))
|
||||||
|
(hs-ws? (nth src end-pos))))))
|
||||||
|
(append api (list {:type "WHITESPACE" :value " " :op false}))
|
||||||
|
api)))
|
||||||
|
{:source src :list with-sep :pos 0}))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-stream-token
|
||||||
|
(fn
|
||||||
|
(s i)
|
||||||
|
(let
|
||||||
|
((lst (get s "list"))
|
||||||
|
(pos (get s "pos")))
|
||||||
|
(or (nth lst (+ pos i))
|
||||||
|
(hs-eof-sentinel)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-stream-consume
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((tok (hs-stream-token s 0)))
|
||||||
|
(when
|
||||||
|
(not (= (get tok "type") "EOF"))
|
||||||
|
(dict-set! s "pos" (+ (get s "pos") 1)))
|
||||||
|
tok)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-stream-has-more
|
||||||
|
(fn (s) (not (= (get (hs-stream-token s 0) "type") "EOF"))))
|
||||||
|
|
||||||
|
(define hs-token-type (fn (tok) (get tok "type")))
|
||||||
|
(define hs-token-value (fn (tok) (get tok "value")))
|
||||||
|
(define hs-token-op? (fn (tok) (get tok "op")))
|
||||||
|
|||||||
@@ -28,6 +28,27 @@
|
|||||||
|
|
||||||
(define hs-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
(define hs-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-hex-digit?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(or
|
||||||
|
(and (>= c "0") (<= c "9"))
|
||||||
|
(and (>= c "a") (<= c "f"))
|
||||||
|
(and (>= c "A") (<= c "F")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-hex-val
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(let
|
||||||
|
((code (char-code c)))
|
||||||
|
(cond
|
||||||
|
((and (>= code 48) (<= code 57)) (- code 48))
|
||||||
|
((and (>= code 65) (<= code 70)) (- code 55))
|
||||||
|
((and (>= code 97) (<= code 102)) (- code 87))
|
||||||
|
(true 0)))))
|
||||||
|
|
||||||
;; ── Keyword set ───────────────────────────────────────────────────
|
;; ── Keyword set ───────────────────────────────────────────────────
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -235,10 +256,15 @@
|
|||||||
read-number
|
read-number
|
||||||
(fn
|
(fn
|
||||||
(start)
|
(start)
|
||||||
|
(define
|
||||||
|
read-int
|
||||||
|
(fn
|
||||||
|
()
|
||||||
(when
|
(when
|
||||||
(and (< pos src-len) (hs-digit? (hs-cur)))
|
(and (< pos src-len) (hs-digit? (hs-cur)))
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
(read-number start))
|
(read-int))))
|
||||||
|
(read-int)
|
||||||
(when
|
(when
|
||||||
(and
|
(and
|
||||||
(< pos src-len)
|
(< pos src-len)
|
||||||
@@ -246,15 +272,7 @@
|
|||||||
(< (+ pos 1) src-len)
|
(< (+ pos 1) src-len)
|
||||||
(hs-digit? (hs-peek 1)))
|
(hs-digit? (hs-peek 1)))
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
(define
|
(read-int))
|
||||||
read-frac
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (hs-digit? (hs-cur)))
|
|
||||||
(hs-advance! 1)
|
|
||||||
(read-frac))))
|
|
||||||
(read-frac))
|
|
||||||
(do
|
(do
|
||||||
(when
|
(when
|
||||||
(and
|
(and
|
||||||
@@ -272,15 +290,7 @@
|
|||||||
(< pos src-len)
|
(< pos src-len)
|
||||||
(or (= (hs-cur) "+") (= (hs-cur) "-")))
|
(or (= (hs-cur) "+") (= (hs-cur) "-")))
|
||||||
(hs-advance! 1))
|
(hs-advance! 1))
|
||||||
(define
|
(read-int))
|
||||||
read-exp-digits
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (hs-digit? (hs-cur)))
|
|
||||||
(hs-advance! 1)
|
|
||||||
(read-exp-digits))))
|
|
||||||
(read-exp-digits))
|
|
||||||
(let
|
(let
|
||||||
((num-end pos))
|
((num-end pos))
|
||||||
(when
|
(when
|
||||||
@@ -308,7 +318,7 @@
|
|||||||
()
|
()
|
||||||
(cond
|
(cond
|
||||||
(>= pos src-len)
|
(>= pos src-len)
|
||||||
nil
|
(error "Unterminated string")
|
||||||
(= (hs-cur) "\\")
|
(= (hs-cur) "\\")
|
||||||
(do
|
(do
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
@@ -318,15 +328,37 @@
|
|||||||
((ch (hs-cur)))
|
((ch (hs-cur)))
|
||||||
(cond
|
(cond
|
||||||
(= ch "n")
|
(= ch "n")
|
||||||
(append! chars "\n")
|
(do (append! chars "\n") (hs-advance! 1))
|
||||||
(= ch "t")
|
(= ch "t")
|
||||||
(append! chars "\t")
|
(do (append! chars "\t") (hs-advance! 1))
|
||||||
|
(= ch "r")
|
||||||
|
(do (append! chars "\r") (hs-advance! 1))
|
||||||
|
(= ch "b")
|
||||||
|
(do (append! chars (char-from-code 8)) (hs-advance! 1))
|
||||||
|
(= ch "f")
|
||||||
|
(do (append! chars (char-from-code 12)) (hs-advance! 1))
|
||||||
|
(= ch "v")
|
||||||
|
(do (append! chars (char-from-code 11)) (hs-advance! 1))
|
||||||
(= ch "\\")
|
(= ch "\\")
|
||||||
(append! chars "\\")
|
(do (append! chars "\\") (hs-advance! 1))
|
||||||
(= ch quote-char)
|
(= ch quote-char)
|
||||||
(append! chars quote-char)
|
(do (append! chars quote-char) (hs-advance! 1))
|
||||||
:else (do (append! chars "\\") (append! chars ch)))
|
(= ch "x")
|
||||||
(hs-advance! 1)))
|
(do
|
||||||
|
(hs-advance! 1)
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(hs-hex-digit? (hs-cur))
|
||||||
|
(hs-hex-digit? (hs-peek 1)))
|
||||||
|
(let
|
||||||
|
((d1 (hs-hex-val (hs-cur)))
|
||||||
|
(d2 (hs-hex-val (hs-peek 1))))
|
||||||
|
(append! chars (char-from-code (+ (* d1 16) d2)))
|
||||||
|
(hs-advance! 2))
|
||||||
|
(error "Invalid hexadecimal escape: \\x")))
|
||||||
|
:else
|
||||||
|
(do (append! chars "\\") (append! chars ch) (hs-advance! 1)))))
|
||||||
(loop))
|
(loop))
|
||||||
(= (hs-cur) quote-char)
|
(= (hs-cur) quote-char)
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
@@ -441,11 +473,7 @@
|
|||||||
(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!))
|
||||||
(and
|
(and (= ch "/") (< (+ pos 1) src-len) (= (hs-peek 1) "/"))
|
||||||
(= ch "/")
|
|
||||||
(< (+ pos 1) src-len)
|
|
||||||
(= (hs-peek 1) "/")
|
|
||||||
(not (and (> pos 0) (= (hs-peek -1) ":"))))
|
|
||||||
(do (hs-advance! 2) (skip-comment!) (scan!))
|
(do (hs-advance! 2) (skip-comment!) (scan!))
|
||||||
(and
|
(and
|
||||||
(= ch "<")
|
(= ch "<")
|
||||||
@@ -624,7 +652,82 @@
|
|||||||
(do (hs-emit! "colon" ":" start) (hs-advance! 1) (scan!))
|
(do (hs-emit! "colon" ":" start) (hs-advance! 1) (scan!))
|
||||||
(= ch "|")
|
(= ch "|")
|
||||||
(do (hs-emit! "op" "|" start) (hs-advance! 1) (scan!))
|
(do (hs-emit! "op" "|" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch "&")
|
||||||
|
(do (hs-emit! "op" "&" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch "#")
|
||||||
|
(do (hs-emit! "op" "#" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch "?")
|
||||||
|
(do (hs-emit! "op" "?" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch ";")
|
||||||
|
(do (hs-emit! "op" ";" start) (hs-advance! 1) (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)
|
||||||
tokens)))
|
tokens)))
|
||||||
|
|
||||||
|
;; ── Template-mode tokenizer (E37 API) ────────────────────────────────
|
||||||
|
;; Used by hs-tokens-of when :template flag is set.
|
||||||
|
;; Emits outer " chars as single STRING tokens; ${ ... } as $ { <inner-tokens> };
|
||||||
|
;; inner content is tokenized with the regular hs-tokenize.
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-tokenize-template
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(let
|
||||||
|
((tokens (list)) (pos 0) (src-len (len src)))
|
||||||
|
(define t-cur (fn () (if (< pos src-len) (nth src pos) nil)))
|
||||||
|
(define t-peek (fn (n) (if (< (+ pos n) src-len) (nth src (+ pos n)) nil)))
|
||||||
|
(define t-advance! (fn (n) (set! pos (+ pos n))))
|
||||||
|
(define t-emit! (fn (type value) (append! tokens (hs-make-token type value pos))))
|
||||||
|
(define
|
||||||
|
scan-to-close!
|
||||||
|
(fn
|
||||||
|
(depth)
|
||||||
|
(when
|
||||||
|
(and (< pos src-len) (> depth 0))
|
||||||
|
(cond
|
||||||
|
(= (t-cur) "{")
|
||||||
|
(do (t-advance! 1) (scan-to-close! (+ depth 1)))
|
||||||
|
(= (t-cur) "}")
|
||||||
|
(when (> (- depth 1) 0) (t-advance! 1) (scan-to-close! (- depth 1)))
|
||||||
|
:else (do (t-advance! 1) (scan-to-close! depth))))))
|
||||||
|
(define
|
||||||
|
scan-template!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(< pos src-len)
|
||||||
|
(let
|
||||||
|
((ch (t-cur)))
|
||||||
|
(cond
|
||||||
|
(= ch "\"")
|
||||||
|
(do (t-emit! "string" "\"") (t-advance! 1) (scan-template!))
|
||||||
|
(and (= ch "$") (= (t-peek 1) "{"))
|
||||||
|
(do
|
||||||
|
(t-emit! "op" "$")
|
||||||
|
(t-advance! 1)
|
||||||
|
(t-emit! "brace-open" "{")
|
||||||
|
(t-advance! 1)
|
||||||
|
(let
|
||||||
|
((inner-start pos))
|
||||||
|
(scan-to-close! 1)
|
||||||
|
(let
|
||||||
|
((inner-src (slice src inner-start pos))
|
||||||
|
(inner-toks (hs-tokenize inner-src)))
|
||||||
|
(for-each
|
||||||
|
(fn (tok)
|
||||||
|
(when (not (= (get tok "type") "eof"))
|
||||||
|
(append! tokens tok)))
|
||||||
|
inner-toks))
|
||||||
|
(t-emit! "brace-close" "}")
|
||||||
|
(when (< pos src-len) (t-advance! 1)))
|
||||||
|
(scan-template!))
|
||||||
|
(= ch "$")
|
||||||
|
(do (t-emit! "op" "$") (t-advance! 1) (scan-template!))
|
||||||
|
(hs-ws? ch)
|
||||||
|
(do (t-advance! 1) (scan-template!))
|
||||||
|
:else (do (t-advance! 1) (scan-template!)))))))
|
||||||
|
(scan-template!)
|
||||||
|
(t-emit! "eof" nil)
|
||||||
|
tokens)))
|
||||||
@@ -129,9 +129,9 @@ Orchestrator cherry-picks worktree commits onto `architecture` one at a time; re
|
|||||||
|
|
||||||
All five have design docs on their own worktree branches pending review + merge. After merge, status flips to `design-ready` and they become eligible for the loop.
|
All five have design docs on their own worktree branches pending review + merge. After merge, status flips to `design-ready` and they become eligible for the loop.
|
||||||
|
|
||||||
36. **[DONE +16 — branch `hs-e36-websocket`] WebSocket + `socket`** — 16/16 tests passing. `socket NAME URL [with timeout N] [on message [as JSON] …] end`, RPC proxy (dispatch-fn pattern), reconnect, dispatchEvent, timeout/noTimeout chains. All 16 upstream tests green.
|
36. **[design-done, pending review — `plans/designs/e36-websocket.md` on `worktree-agent-a9daf73703f520257`] WebSocket + `socket`** — 16 tests. Upstream shape is `socket NAME URL [with timeout N] [on message [as JSON] …] end` with an **implicit `.rpc` Proxy** (ES6 Proxy lives in JS, not SX), not `with proxy { send, receive }` as this row previously claimed. Design doc has 8-commit checklist, +12–16 delta estimate. Ship only with intentional design review.
|
||||||
|
|
||||||
37. **[design-done, pending review — `plans/designs/e37-tokenizer-api.md` on `worktree-agent-a6bb61d59cc0be8b4`] Tokenizer-as-API** — 17 tests. Expose tokens as inspectable SX data via `hs-tokens-of` / `hs-stream-token` / `hs-token-type` etc; type-map current `hs-tokenize` output to upstream SCREAMING_SNAKE_CASE. 8-step checklist, +16–17 delta.
|
37. **[done +17]** Tokenizer-as-API — `hs-tokens-of` / `hs-stream-token` / `hs-token-type` / `hs-token-value` / `hs-token-op?`; type-map + normalize; `read-number` dot-stop fix; `\$` template escape in compiler + runtime; generator pattern in `generate-sx-tests.py`. 17/17.
|
||||||
|
|
||||||
38. **[design-done, pending review — `plans/designs/e38-sourceinfo.md` on `agent-e38-sourceinfo`] SourceInfo API** — 4 tests. Inline span-wrapper strategy (not side-channel dict) with compiler-entry unwrap. 4-commit plan.
|
38. **[design-done, pending review — `plans/designs/e38-sourceinfo.md` on `agent-e38-sourceinfo`] SourceInfo API** — 4 tests. Inline span-wrapper strategy (not side-channel dict) with compiler-entry unwrap. 4-commit plan.
|
||||||
|
|
||||||
|
|||||||
@@ -787,31 +787,6 @@
|
|||||||
(quote fn)
|
(quote fn)
|
||||||
(cons (quote me) (map make-symbol params))
|
(cons (quote me) (map make-symbol params))
|
||||||
(cons (quote do) (map hs-to-sx body)))))))
|
(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
|
(fn
|
||||||
(ast)
|
(ast)
|
||||||
(cond
|
(cond
|
||||||
@@ -917,6 +892,12 @@
|
|||||||
(< i n)
|
(< i n)
|
||||||
(let
|
(let
|
||||||
((ch (nth raw i)))
|
((ch (nth raw i)))
|
||||||
|
(if
|
||||||
|
(and (= ch "\\") (< (+ i 1) n) (= (nth raw (+ i 1)) "$"))
|
||||||
|
(do
|
||||||
|
(set! buf (str buf "$"))
|
||||||
|
(set! i (+ i 2))
|
||||||
|
(tpl-collect))
|
||||||
(if
|
(if
|
||||||
(and (= ch "$") (< (+ i 1) n))
|
(and (= ch "$") (< (+ i 1) n))
|
||||||
(if
|
(if
|
||||||
@@ -956,7 +937,7 @@
|
|||||||
(do
|
(do
|
||||||
(set! buf (str buf ch))
|
(set! buf (str buf ch))
|
||||||
(set! i (+ i 1))
|
(set! i (+ i 1))
|
||||||
(tpl-collect)))))))
|
(tpl-collect))))))))
|
||||||
(tpl-collect)
|
(tpl-collect)
|
||||||
(tpl-flush)
|
(tpl-flush)
|
||||||
(cons (quote str) parts))))
|
(cons (quote str) parts))))
|
||||||
@@ -2100,7 +2081,6 @@
|
|||||||
(quote _hs-def-val))
|
(quote _hs-def-val))
|
||||||
(quote _hs-def-val))))))
|
(quote _hs-def-val))))))
|
||||||
((= head (quote behavior)) (emit-behavior ast))
|
((= head (quote behavior)) (emit-behavior ast))
|
||||||
((= head (quote socket)) (emit-socket ast))
|
|
||||||
((= head (quote sx-eval))
|
((= head (quote sx-eval))
|
||||||
(let
|
(let
|
||||||
((src (nth ast 1)))
|
((src (nth ast 1)))
|
||||||
|
|||||||
3
shared/static/wasm/sx/hs-compiler.sxbc
Normal file
3
shared/static/wasm/sx/hs-compiler.sxbc
Normal file
File diff suppressed because one or more lines are too long
@@ -2729,63 +2729,6 @@
|
|||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote when-feat-no-op)))))
|
(list (quote when-feat-no-op)))))
|
||||||
(do (pwf-skip) (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
|
(define
|
||||||
parse-feat
|
parse-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -2806,7 +2749,6 @@
|
|||||||
((= 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 "socket") (do (adv!) (parse-socket-feat)))
|
|
||||||
(true (parse-cmd-list))))))
|
(true (parse-cmd-list))))))
|
||||||
(define
|
(define
|
||||||
coll-feats
|
coll-feats
|
||||||
|
|||||||
3
shared/static/wasm/sx/hs-parser.sxbc
Normal file
3
shared/static/wasm/sx/hs-parser.sxbc
Normal file
File diff suppressed because one or more lines are too long
@@ -2020,6 +2020,12 @@
|
|||||||
(< i n)
|
(< i n)
|
||||||
(let
|
(let
|
||||||
((ch (nth raw i)))
|
((ch (nth raw i)))
|
||||||
|
(if
|
||||||
|
(and (= ch "\\") (< (+ i 1) n) (= (nth raw (+ i 1)) "$"))
|
||||||
|
(do
|
||||||
|
(set! result (str result "$"))
|
||||||
|
(set! i (+ i 2))
|
||||||
|
(tpl-loop))
|
||||||
(if
|
(if
|
||||||
(and (= ch "$") (< (+ i 1) n))
|
(and (= ch "$") (< (+ i 1) n))
|
||||||
(if
|
(if
|
||||||
@@ -2089,7 +2095,7 @@
|
|||||||
(do
|
(do
|
||||||
(set! result (str result ch))
|
(set! result (str result ch))
|
||||||
(set! i (+ i 1))
|
(set! i (+ i 1))
|
||||||
(tpl-loop)))))))
|
(tpl-loop))))))))
|
||||||
(do (tpl-loop) result))))
|
(do (tpl-loop) result))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -2526,111 +2532,187 @@
|
|||||||
(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))))
|
||||||
|
|
||||||
;; ── WebSocket / socket feature ───────────────────────────────────
|
;; ── E37 Tokenizer-as-API ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define hs-eof-sentinel (fn () {:type "EOF" :value "<<<EOF>>>" :op false}))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-try-json-parse
|
hs-op-type
|
||||||
(fn (s) (host-call (host-global "JSON") "parse" s)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
hs-socket-resolve-rpc!
|
|
||||||
(fn
|
(fn
|
||||||
(wrapper msg)
|
(val)
|
||||||
(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
|
(cond
|
||||||
((and (not (nil? parsed)) (not (nil? (host-get parsed "iid"))))
|
((= val "+") "PLUS")
|
||||||
(hs-socket-resolve-rpc! wrapper parsed))
|
((= val "-") "MINUS")
|
||||||
((not (nil? handler))
|
((= val "*") "MULTIPLY")
|
||||||
(if
|
((= val "/") "SLASH")
|
||||||
json?
|
((= val "%") "PERCENT")
|
||||||
(if
|
((= val "|") "PIPE")
|
||||||
(not (nil? parsed))
|
((= val "!") "EXCLAMATION")
|
||||||
(handler parsed)
|
((= val "?") "QUESTION")
|
||||||
(error "Received non-JSON message"))
|
((= val "#") "POUND")
|
||||||
(handler event)))))))))
|
((= val "&") "AMPERSAND")
|
||||||
(host-call
|
((= val ";") "SEMI")
|
||||||
ws
|
((= val "=") "EQUALS")
|
||||||
"addEventListener"
|
((= val "<") "L_ANG")
|
||||||
"close"
|
((= val ">") "R_ANG")
|
||||||
(host-callback
|
((= val "<=") "LTE_ANG")
|
||||||
(fn
|
((= val ">=") "GTE_ANG")
|
||||||
(evt)
|
((= val "==") "EQ")
|
||||||
(host-set! wrapper "closedFlag" "1"))))
|
((= val "===") "EQQ")
|
||||||
(host-set!
|
((= val "\\") "BACKSLASH")
|
||||||
wrapper
|
(true (str "OP_" val)))))
|
||||||
"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
|
(define
|
||||||
bind-path!
|
hs-raw->api-token
|
||||||
(fn
|
(fn
|
||||||
(obj path)
|
(tok)
|
||||||
|
(let
|
||||||
|
((raw-type (get tok "type"))
|
||||||
|
(raw-val (get tok "value")))
|
||||||
|
(let
|
||||||
|
((up-type
|
||||||
|
(cond
|
||||||
|
((or (= raw-type "ident") (= raw-type "keyword")) "IDENTIFIER")
|
||||||
|
((= raw-type "number") "NUMBER")
|
||||||
|
((= raw-type "string") "STRING")
|
||||||
|
((= raw-type "class") "CLASS_REF")
|
||||||
|
((= raw-type "id") "ID_REF")
|
||||||
|
((= raw-type "attr") "ATTRIBUTE_REF")
|
||||||
|
((= raw-type "style") "STYLE_REF")
|
||||||
|
((= raw-type "selector") "QUERY_REF")
|
||||||
|
((= raw-type "eof") "EOF")
|
||||||
|
((= raw-type "paren-open") "L_PAREN")
|
||||||
|
((= raw-type "paren-close") "R_PAREN")
|
||||||
|
((= raw-type "bracket-open") "L_BRACKET")
|
||||||
|
((= raw-type "bracket-close") "R_BRACKET")
|
||||||
|
((= raw-type "brace-open") "L_BRACE")
|
||||||
|
((= raw-type "brace-close") "R_BRACE")
|
||||||
|
((= raw-type "comma") "COMMA")
|
||||||
|
((= raw-type "dot") "PERIOD")
|
||||||
|
((= raw-type "colon") "COLON")
|
||||||
|
((= raw-type "op") (hs-op-type raw-val))
|
||||||
|
(true (str "UNKNOWN_" raw-type))))
|
||||||
|
(up-val
|
||||||
|
(cond
|
||||||
|
((= raw-type "class") (str "." raw-val))
|
||||||
|
((= raw-type "id") (str "#" raw-val))
|
||||||
|
((= raw-type "eof") "<<<EOF>>>")
|
||||||
|
(true raw-val)))
|
||||||
|
(is-op
|
||||||
|
(or
|
||||||
|
(= raw-type "paren-open")
|
||||||
|
(= raw-type "paren-close")
|
||||||
|
(= raw-type "bracket-open")
|
||||||
|
(= raw-type "bracket-close")
|
||||||
|
(= raw-type "brace-open")
|
||||||
|
(= raw-type "brace-close")
|
||||||
|
(= raw-type "comma")
|
||||||
|
(= raw-type "dot")
|
||||||
|
(= raw-type "colon")
|
||||||
|
(= raw-type "op"))))
|
||||||
|
{:type up-type :value up-val :op is-op}))))
|
||||||
|
|
||||||
|
;; Expand "class" and "id" tokens that follow a closing bracket into
|
||||||
|
;; separate dot/hash + ident tokens, matching upstream context-sensitive
|
||||||
|
;; behaviour: after ) ] } the dot is property access, not a CLASS_REF.
|
||||||
|
(define
|
||||||
|
hs-normalize-raw-tokens
|
||||||
|
(fn
|
||||||
|
(raw-real)
|
||||||
|
(let
|
||||||
|
((result (list))
|
||||||
|
(prev-type nil))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(tok)
|
||||||
|
(let
|
||||||
|
((typ (get tok "type"))
|
||||||
|
(val (get tok "value"))
|
||||||
|
(tok-pos (get tok "pos")))
|
||||||
(if
|
(if
|
||||||
(= (len path) 1)
|
(and
|
||||||
(host-set! obj (first path) wrapper)
|
(or (= typ "class") (= typ "id"))
|
||||||
|
(or
|
||||||
|
(= prev-type "paren-close")
|
||||||
|
(= prev-type "bracket-close")
|
||||||
|
(= prev-type "brace-close")))
|
||||||
|
(do
|
||||||
|
(if
|
||||||
|
(= typ "class")
|
||||||
|
(do
|
||||||
|
(append! result {:type "dot" :value "." :pos tok-pos})
|
||||||
|
(append! result {:type "ident" :value val :pos (+ tok-pos 1)}))
|
||||||
|
(do
|
||||||
|
(append! result {:type "op" :value "#" :pos tok-pos})
|
||||||
|
(append! result {:type "ident" :value val :pos (+ tok-pos 1)})))
|
||||||
|
(set! prev-type "ident"))
|
||||||
|
(do
|
||||||
|
(append! result tok)
|
||||||
|
(set! prev-type typ)))))
|
||||||
|
raw-real)
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-tokens-of
|
||||||
|
(fn
|
||||||
|
(src &rest rest)
|
||||||
(let
|
(let
|
||||||
((key (first path)) (rest-path (rest path)))
|
((template? (and (> (len rest) 0) (= (first rest) :template)))
|
||||||
|
(raw (if template? (hs-tokenize-template src) (hs-tokenize src))))
|
||||||
|
(if
|
||||||
|
template?
|
||||||
|
{:source src :list (map hs-raw->api-token raw) :pos 0}
|
||||||
|
;; Normal mode: filter EOF, context-normalise, add trailing-WS sentinel
|
||||||
(let
|
(let
|
||||||
((next (or (host-get obj key) (host-new "Object"))))
|
((real (filter (fn (t) (not (= (get t "type") "eof"))) raw)))
|
||||||
(host-set! obj key next)
|
(let
|
||||||
(bind-path! next rest-path))))))
|
((norm (hs-normalize-raw-tokens real)))
|
||||||
(bind-path! (host-global "window") name-path)
|
(let
|
||||||
wrapper)))))
|
((api (map hs-raw->api-token norm)))
|
||||||
|
(let
|
||||||
|
((with-sep
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(> (len norm) 0)
|
||||||
|
(let
|
||||||
|
((last-tok (nth norm (- (len norm) 1))))
|
||||||
|
(let
|
||||||
|
((end-pos
|
||||||
|
(+ (get last-tok "pos")
|
||||||
|
(len (get last-tok "value")))))
|
||||||
|
(and
|
||||||
|
(< end-pos (len src))
|
||||||
|
(hs-ws? (nth src end-pos))))))
|
||||||
|
(append api (list {:type "WHITESPACE" :value " " :op false}))
|
||||||
|
api)))
|
||||||
|
{:source src :list with-sep :pos 0}))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-stream-token
|
||||||
|
(fn
|
||||||
|
(s i)
|
||||||
|
(let
|
||||||
|
((lst (get s "list"))
|
||||||
|
(pos (get s "pos")))
|
||||||
|
(or (nth lst (+ pos i))
|
||||||
|
(hs-eof-sentinel)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-stream-consume
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((tok (hs-stream-token s 0)))
|
||||||
|
(when
|
||||||
|
(not (= (get tok "type") "EOF"))
|
||||||
|
(dict-set! s "pos" (+ (get s "pos") 1)))
|
||||||
|
tok)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-stream-has-more
|
||||||
|
(fn (s) (not (= (get (hs-stream-token s 0) "type") "EOF"))))
|
||||||
|
|
||||||
|
(define hs-token-type (fn (tok) (get tok "type")))
|
||||||
|
(define hs-token-value (fn (tok) (get tok "value")))
|
||||||
|
(define hs-token-op? (fn (tok) (get tok "op")))
|
||||||
|
|||||||
3
shared/static/wasm/sx/hs-runtime.sxbc
Normal file
3
shared/static/wasm/sx/hs-runtime.sxbc
Normal file
File diff suppressed because one or more lines are too long
@@ -28,6 +28,27 @@
|
|||||||
|
|
||||||
(define hs-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
(define hs-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-hex-digit?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(or
|
||||||
|
(and (>= c "0") (<= c "9"))
|
||||||
|
(and (>= c "a") (<= c "f"))
|
||||||
|
(and (>= c "A") (<= c "F")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-hex-val
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(let
|
||||||
|
((code (char-code c)))
|
||||||
|
(cond
|
||||||
|
((and (>= code 48) (<= code 57)) (- code 48))
|
||||||
|
((and (>= code 65) (<= code 70)) (- code 55))
|
||||||
|
((and (>= code 97) (<= code 102)) (- code 87))
|
||||||
|
(true 0)))))
|
||||||
|
|
||||||
;; ── Keyword set ───────────────────────────────────────────────────
|
;; ── Keyword set ───────────────────────────────────────────────────
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -235,10 +256,15 @@
|
|||||||
read-number
|
read-number
|
||||||
(fn
|
(fn
|
||||||
(start)
|
(start)
|
||||||
|
(define
|
||||||
|
read-int
|
||||||
|
(fn
|
||||||
|
()
|
||||||
(when
|
(when
|
||||||
(and (< pos src-len) (hs-digit? (hs-cur)))
|
(and (< pos src-len) (hs-digit? (hs-cur)))
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
(read-number start))
|
(read-int))))
|
||||||
|
(read-int)
|
||||||
(when
|
(when
|
||||||
(and
|
(and
|
||||||
(< pos src-len)
|
(< pos src-len)
|
||||||
@@ -246,15 +272,7 @@
|
|||||||
(< (+ pos 1) src-len)
|
(< (+ pos 1) src-len)
|
||||||
(hs-digit? (hs-peek 1)))
|
(hs-digit? (hs-peek 1)))
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
(define
|
(read-int))
|
||||||
read-frac
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (hs-digit? (hs-cur)))
|
|
||||||
(hs-advance! 1)
|
|
||||||
(read-frac))))
|
|
||||||
(read-frac))
|
|
||||||
(do
|
(do
|
||||||
(when
|
(when
|
||||||
(and
|
(and
|
||||||
@@ -272,15 +290,7 @@
|
|||||||
(< pos src-len)
|
(< pos src-len)
|
||||||
(or (= (hs-cur) "+") (= (hs-cur) "-")))
|
(or (= (hs-cur) "+") (= (hs-cur) "-")))
|
||||||
(hs-advance! 1))
|
(hs-advance! 1))
|
||||||
(define
|
(read-int))
|
||||||
read-exp-digits
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(and (< pos src-len) (hs-digit? (hs-cur)))
|
|
||||||
(hs-advance! 1)
|
|
||||||
(read-exp-digits))))
|
|
||||||
(read-exp-digits))
|
|
||||||
(let
|
(let
|
||||||
((num-end pos))
|
((num-end pos))
|
||||||
(when
|
(when
|
||||||
@@ -308,7 +318,7 @@
|
|||||||
()
|
()
|
||||||
(cond
|
(cond
|
||||||
(>= pos src-len)
|
(>= pos src-len)
|
||||||
nil
|
(error "Unterminated string")
|
||||||
(= (hs-cur) "\\")
|
(= (hs-cur) "\\")
|
||||||
(do
|
(do
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
@@ -318,15 +328,37 @@
|
|||||||
((ch (hs-cur)))
|
((ch (hs-cur)))
|
||||||
(cond
|
(cond
|
||||||
(= ch "n")
|
(= ch "n")
|
||||||
(append! chars "\n")
|
(do (append! chars "\n") (hs-advance! 1))
|
||||||
(= ch "t")
|
(= ch "t")
|
||||||
(append! chars "\t")
|
(do (append! chars "\t") (hs-advance! 1))
|
||||||
|
(= ch "r")
|
||||||
|
(do (append! chars "\r") (hs-advance! 1))
|
||||||
|
(= ch "b")
|
||||||
|
(do (append! chars (char-from-code 8)) (hs-advance! 1))
|
||||||
|
(= ch "f")
|
||||||
|
(do (append! chars (char-from-code 12)) (hs-advance! 1))
|
||||||
|
(= ch "v")
|
||||||
|
(do (append! chars (char-from-code 11)) (hs-advance! 1))
|
||||||
(= ch "\\")
|
(= ch "\\")
|
||||||
(append! chars "\\")
|
(do (append! chars "\\") (hs-advance! 1))
|
||||||
(= ch quote-char)
|
(= ch quote-char)
|
||||||
(append! chars quote-char)
|
(do (append! chars quote-char) (hs-advance! 1))
|
||||||
:else (do (append! chars "\\") (append! chars ch)))
|
(= ch "x")
|
||||||
(hs-advance! 1)))
|
(do
|
||||||
|
(hs-advance! 1)
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(hs-hex-digit? (hs-cur))
|
||||||
|
(hs-hex-digit? (hs-peek 1)))
|
||||||
|
(let
|
||||||
|
((d1 (hs-hex-val (hs-cur)))
|
||||||
|
(d2 (hs-hex-val (hs-peek 1))))
|
||||||
|
(append! chars (char-from-code (+ (* d1 16) d2)))
|
||||||
|
(hs-advance! 2))
|
||||||
|
(error "Invalid hexadecimal escape: \\x")))
|
||||||
|
:else
|
||||||
|
(do (append! chars "\\") (append! chars ch) (hs-advance! 1)))))
|
||||||
(loop))
|
(loop))
|
||||||
(= (hs-cur) quote-char)
|
(= (hs-cur) quote-char)
|
||||||
(hs-advance! 1)
|
(hs-advance! 1)
|
||||||
@@ -441,11 +473,7 @@
|
|||||||
(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!))
|
||||||
(and
|
(and (= ch "/") (< (+ pos 1) src-len) (= (hs-peek 1) "/"))
|
||||||
(= ch "/")
|
|
||||||
(< (+ pos 1) src-len)
|
|
||||||
(= (hs-peek 1) "/")
|
|
||||||
(not (and (> pos 0) (= (hs-peek -1) ":"))))
|
|
||||||
(do (hs-advance! 2) (skip-comment!) (scan!))
|
(do (hs-advance! 2) (skip-comment!) (scan!))
|
||||||
(and
|
(and
|
||||||
(= ch "<")
|
(= ch "<")
|
||||||
@@ -624,7 +652,82 @@
|
|||||||
(do (hs-emit! "colon" ":" start) (hs-advance! 1) (scan!))
|
(do (hs-emit! "colon" ":" start) (hs-advance! 1) (scan!))
|
||||||
(= ch "|")
|
(= ch "|")
|
||||||
(do (hs-emit! "op" "|" start) (hs-advance! 1) (scan!))
|
(do (hs-emit! "op" "|" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch "&")
|
||||||
|
(do (hs-emit! "op" "&" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch "#")
|
||||||
|
(do (hs-emit! "op" "#" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch "?")
|
||||||
|
(do (hs-emit! "op" "?" start) (hs-advance! 1) (scan!))
|
||||||
|
(= ch ";")
|
||||||
|
(do (hs-emit! "op" ";" start) (hs-advance! 1) (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)
|
||||||
tokens)))
|
tokens)))
|
||||||
|
|
||||||
|
;; ── Template-mode tokenizer (E37 API) ────────────────────────────────
|
||||||
|
;; Used by hs-tokens-of when :template flag is set.
|
||||||
|
;; Emits outer " chars as single STRING tokens; ${ ... } as $ { <inner-tokens> };
|
||||||
|
;; inner content is tokenized with the regular hs-tokenize.
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-tokenize-template
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(let
|
||||||
|
((tokens (list)) (pos 0) (src-len (len src)))
|
||||||
|
(define t-cur (fn () (if (< pos src-len) (nth src pos) nil)))
|
||||||
|
(define t-peek (fn (n) (if (< (+ pos n) src-len) (nth src (+ pos n)) nil)))
|
||||||
|
(define t-advance! (fn (n) (set! pos (+ pos n))))
|
||||||
|
(define t-emit! (fn (type value) (append! tokens (hs-make-token type value pos))))
|
||||||
|
(define
|
||||||
|
scan-to-close!
|
||||||
|
(fn
|
||||||
|
(depth)
|
||||||
|
(when
|
||||||
|
(and (< pos src-len) (> depth 0))
|
||||||
|
(cond
|
||||||
|
(= (t-cur) "{")
|
||||||
|
(do (t-advance! 1) (scan-to-close! (+ depth 1)))
|
||||||
|
(= (t-cur) "}")
|
||||||
|
(when (> (- depth 1) 0) (t-advance! 1) (scan-to-close! (- depth 1)))
|
||||||
|
:else (do (t-advance! 1) (scan-to-close! depth))))))
|
||||||
|
(define
|
||||||
|
scan-template!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(< pos src-len)
|
||||||
|
(let
|
||||||
|
((ch (t-cur)))
|
||||||
|
(cond
|
||||||
|
(= ch "\"")
|
||||||
|
(do (t-emit! "string" "\"") (t-advance! 1) (scan-template!))
|
||||||
|
(and (= ch "$") (= (t-peek 1) "{"))
|
||||||
|
(do
|
||||||
|
(t-emit! "op" "$")
|
||||||
|
(t-advance! 1)
|
||||||
|
(t-emit! "brace-open" "{")
|
||||||
|
(t-advance! 1)
|
||||||
|
(let
|
||||||
|
((inner-start pos))
|
||||||
|
(scan-to-close! 1)
|
||||||
|
(let
|
||||||
|
((inner-src (slice src inner-start pos))
|
||||||
|
(inner-toks (hs-tokenize inner-src)))
|
||||||
|
(for-each
|
||||||
|
(fn (tok)
|
||||||
|
(when (not (= (get tok "type") "eof"))
|
||||||
|
(append! tokens tok)))
|
||||||
|
inner-toks))
|
||||||
|
(t-emit! "brace-close" "}")
|
||||||
|
(when (< pos src-len) (t-advance! 1)))
|
||||||
|
(scan-template!))
|
||||||
|
(= ch "$")
|
||||||
|
(do (t-emit! "op" "$") (t-advance! 1) (scan-template!))
|
||||||
|
(hs-ws? ch)
|
||||||
|
(do (t-advance! 1) (scan-template!))
|
||||||
|
:else (do (t-advance! 1) (scan-template!)))))))
|
||||||
|
(scan-template!)
|
||||||
|
(t-emit! "eof" nil)
|
||||||
|
tokens)))
|
||||||
3
shared/static/wasm/sx/hs-tokenizer.sxbc
Normal file
3
shared/static/wasm/sx/hs-tokenizer.sxbc
Normal file
File diff suppressed because one or more lines are too long
@@ -2479,41 +2479,287 @@
|
|||||||
;; ── core/tokenizer (17 tests) ──
|
;; ── core/tokenizer (17 tests) ──
|
||||||
(defsuite "hs-upstream-core/tokenizer"
|
(defsuite "hs-upstream-core/tokenizer"
|
||||||
(deftest "handles $ in template properly"
|
(deftest "handles $ in template properly"
|
||||||
(error "SKIP (untranslated): handles $ in template properly"))
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"" :template) 0)) "\"")
|
||||||
|
)
|
||||||
(deftest "handles all special escapes properly"
|
(deftest "handles all special escapes properly"
|
||||||
(error "SKIP (untranslated): handles all special escapes properly"))
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\b\""))) (char-from-code 8))
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\f\""))) (char-from-code 12))
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\n\""))) "\n")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\r\""))) "\r")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\t\""))) "\t")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\v\""))) (char-from-code 11))
|
||||||
|
)
|
||||||
(deftest "handles basic token types"
|
(deftest "handles basic token types"
|
||||||
(error "SKIP (untranslated): handles basic token types"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "foo"))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1"))) "NUMBER")
|
||||||
|
(let ((s (hs-tokens-of "1.1")))
|
||||||
|
(let ((tok (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok) "NUMBER")
|
||||||
|
(assert= (hs-stream-has-more s) false)))
|
||||||
|
(let ((s (hs-tokens-of "1e6")))
|
||||||
|
(let ((tok (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok) "NUMBER")
|
||||||
|
(assert= (hs-stream-has-more s) false)))
|
||||||
|
(let ((s (hs-tokens-of "1e-6")))
|
||||||
|
(let ((tok (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok) "NUMBER")
|
||||||
|
(assert= (hs-stream-has-more s) false)))
|
||||||
|
(let ((s (hs-tokens-of "1.1e6")))
|
||||||
|
(let ((tok (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok) "NUMBER")
|
||||||
|
(assert= (hs-stream-has-more s) false)))
|
||||||
|
(let ((s (hs-tokens-of "1.1e-6")))
|
||||||
|
(let ((tok (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok) "NUMBER")
|
||||||
|
(assert= (hs-stream-has-more s) false)))
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of ".a"))) "CLASS_REF")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "#a"))) "ID_REF")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "\"asdf\""))) "STRING")
|
||||||
|
)
|
||||||
(deftest "handles class identifiers properly"
|
(deftest "handles class identifiers properly"
|
||||||
(error "SKIP (untranslated): handles class identifiers properly"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of ".a"))) "CLASS_REF")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of ".a"))) ".a")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of " .a"))) "CLASS_REF")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of " .a"))) ".a")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "a.a"))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "a.a"))) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "(a).a") "list") 4)) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "(a).a") "list") 4)) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "{a}.a") "list") 4)) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "{a}.a") "list") 4)) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "[a].a") "list") 4)) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "[a].a") "list") 4)) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "(a(.a") "list") 3)) "CLASS_REF")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "(a(.a") "list") 3)) ".a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "{a{.a") "list") 3)) "CLASS_REF")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "{a{.a") "list") 3)) ".a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "[a[.a") "list") 3)) "CLASS_REF")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "[a[.a") "list") 3)) ".a")
|
||||||
|
)
|
||||||
(deftest "handles comments properly"
|
(deftest "handles comments properly"
|
||||||
(error "SKIP (untranslated): handles comments properly"))
|
(assert= (len (get (hs-tokens-of "--") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "asdf--") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "-- asdf") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "--\nasdf") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "--\nasdf--") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "---asdf") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "----\n---asdf") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "----asdf----") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "---\nasdf---") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "// asdf") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "///asdf") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "asdf//") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "asdf\n//") "list")) 2)
|
||||||
|
)
|
||||||
(deftest "handles hex escapes properly"
|
(deftest "handles hex escapes properly"
|
||||||
(error "SKIP (untranslated): handles hex escapes properly"))
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\x1f\""))) (char-from-code 31))
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\x41\""))) "A")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"\\x41\\x61\""))) "Aa")
|
||||||
|
(let ((threw false))
|
||||||
|
(guard (e (true (set! threw true))) (hs-stream-consume (hs-tokens-of "\"\\x\"")))
|
||||||
|
(assert threw))
|
||||||
|
(let ((threw false))
|
||||||
|
(guard (e (true (set! threw true))) (hs-stream-consume (hs-tokens-of "\"\\xGG\"")))
|
||||||
|
(assert threw))
|
||||||
|
(let ((threw false))
|
||||||
|
(guard (e (true (set! threw true))) (hs-stream-consume (hs-tokens-of "\"\\x4\"")))
|
||||||
|
(assert threw))
|
||||||
|
)
|
||||||
(deftest "handles id references properly"
|
(deftest "handles id references properly"
|
||||||
(error "SKIP (untranslated): handles id references properly"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "#a"))) "ID_REF")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "#a"))) "#a")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of " #a"))) "ID_REF")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of " #a"))) "#a")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "a#a"))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "a#a"))) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "(a)#a") "list") 4)) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "(a)#a") "list") 4)) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "{a}#a") "list") 4)) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "{a}#a") "list") 4)) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "[a]#a") "list") 4)) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "[a]#a") "list") 4)) "a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "(a(#a") "list") 3)) "ID_REF")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "(a(#a") "list") 3)) "#a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "{a{#a") "list") 3)) "ID_REF")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "{a{#a") "list") 3)) "#a")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "[a[#a") "list") 3)) "ID_REF")
|
||||||
|
(assert= (hs-token-value (nth (get (hs-tokens-of "[a[#a") "list") 3)) "#a")
|
||||||
|
)
|
||||||
(deftest "handles identifiers properly"
|
(deftest "handles identifiers properly"
|
||||||
(error "SKIP (untranslated): handles identifiers properly"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "foo"))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "foo"))) "foo")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of " foo "))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of " foo "))) "foo")
|
||||||
|
(let ((s (hs-tokens-of " foo bar")))
|
||||||
|
(let ((tok1 (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok1) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value tok1) "foo")
|
||||||
|
(let ((tok2 (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok2) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value tok2) "bar"))))
|
||||||
|
(let ((s (hs-tokens-of " foo\n-- a comment\n bar")))
|
||||||
|
(let ((tok1 (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok1) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value tok1) "foo")
|
||||||
|
(let ((tok2 (hs-stream-consume s)))
|
||||||
|
(assert= (hs-token-type tok2) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value tok2) "bar"))))
|
||||||
|
)
|
||||||
(deftest "handles identifiers with numbers properly"
|
(deftest "handles identifiers with numbers properly"
|
||||||
(error "SKIP (untranslated): handles identifiers with numbers properly"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "f1oo"))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "f1oo"))) "f1oo")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "fo1o"))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "fo1o"))) "fo1o")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "foo1"))) "IDENTIFIER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "foo1"))) "foo1")
|
||||||
|
)
|
||||||
(deftest "handles look ahead property"
|
(deftest "handles look ahead property"
|
||||||
(error "SKIP (untranslated): handles look ahead property"))
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "a 1 + 1") 0)) "a")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "a 1 + 1") 1)) "1")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "a 1 + 1") 2)) "+")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "a 1 + 1") 3)) "1")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "a 1 + 1") 4)) "<<<EOF>>>")
|
||||||
|
)
|
||||||
(deftest "handles numbers properly"
|
(deftest "handles numbers properly"
|
||||||
(error "SKIP (untranslated): handles numbers properly"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1"))) "NUMBER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "1"))) "1")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1.1"))) "NUMBER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "1.1"))) "1.1")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1234567890.1234567890"))) "NUMBER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "1234567890.1234567890"))) "1234567890.1234567890")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1e6"))) "NUMBER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "1e6"))) "1e6")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1e-6"))) "NUMBER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "1e-6"))) "1e-6")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1.1e6"))) "NUMBER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "1.1e6"))) "1.1e6")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "1.1e-6"))) "NUMBER")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "1.1e-6"))) "1.1e-6")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "1.1.1") "list") 0)) "NUMBER")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "1.1.1") "list") 1)) "PERIOD")
|
||||||
|
(assert= (hs-token-type (nth (get (hs-tokens-of "1.1.1") "list") 2)) "NUMBER")
|
||||||
|
(assert= (len (get (hs-tokens-of "1.1.1") "list")) 3)
|
||||||
|
)
|
||||||
(deftest "handles operators properly"
|
(deftest "handles operators properly"
|
||||||
(error "SKIP (untranslated): handles operators properly"))
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "+"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "+"))) "+")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "-"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "-"))) "-")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "*"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "*"))) "*")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "."))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "."))) ".")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "\\"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\\"))) "\\")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of ":"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of ":"))) ":")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "%"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "%"))) "%")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "|"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "|"))) "|")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "!"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "!"))) "!")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "?"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "?"))) "?")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "#"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "#"))) "#")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "&"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "&"))) "&")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of ";"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of ";"))) ";")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of ","))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of ","))) ",")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "("))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "("))) "(")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of ")"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of ")"))) ")")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "<"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "<"))) "<")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of ">"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of ">"))) ">")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "{"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "{"))) "{")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "}"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "}"))) "}")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "["))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "["))) "[")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "]"))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "]"))) "]")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "="))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "="))) "=")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "<="))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "<="))) "<=")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of ">="))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of ">="))) ">=")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "=="))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "=="))) "==")
|
||||||
|
(assert= (hs-token-op? (hs-stream-consume (hs-tokens-of "==="))) true)
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "==="))) "===")
|
||||||
|
)
|
||||||
(deftest "handles strings properly"
|
(deftest "handles strings properly"
|
||||||
(error "SKIP (untranslated): handles strings properly"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "\"foo\""))) "STRING")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"foo\""))) "foo")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "\"fo'o\""))) "STRING")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"fo'o\""))) "fo'o")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "\"fo\\\"o\""))) "STRING")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "\"fo\\\"o\""))) "fo\"o")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "'foo'"))) "STRING")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "'foo'"))) "foo")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "'fo\"o'"))) "STRING")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "'fo\"o'"))) "fo\"o")
|
||||||
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "'fo\\'o'"))) "STRING")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "'fo\\'o'"))) "fo'o")
|
||||||
|
(let ((threw false))
|
||||||
|
(guard (e (true (set! threw true))) (hs-stream-consume (hs-tokens-of "'")))
|
||||||
|
(assert threw))
|
||||||
|
(let ((threw false))
|
||||||
|
(guard (e (true (set! threw true))) (hs-stream-consume (hs-tokens-of "\"")))
|
||||||
|
(assert threw))
|
||||||
|
)
|
||||||
(deftest "handles strings properly 2"
|
(deftest "handles strings properly 2"
|
||||||
(error "SKIP (untranslated): handles strings properly 2"))
|
(assert= (hs-token-type (hs-stream-consume (hs-tokens-of "'foo'"))) "STRING")
|
||||||
|
(assert= (hs-token-value (hs-stream-consume (hs-tokens-of "'foo'"))) "foo")
|
||||||
|
)
|
||||||
(deftest "handles template bootstrap properly"
|
(deftest "handles template bootstrap properly"
|
||||||
(error "SKIP (untranslated): handles template bootstrap properly"))
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"" :template) 0)) "\"")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"$" :template) 0)) "\"")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"$" :template) 1)) "$")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${" :template) 0)) "\"")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${" :template) 1)) "$")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${" :template) 2)) "{")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"" :template) 0)) "\"")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"" :template) 1)) "$")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"" :template) 2)) "{")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"" :template) 3)) "asdf")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"}\"" :template) 0)) "\"")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"}\"" :template) 1)) "$")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"}\"" :template) 2)) "{")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"}\"" :template) 3)) "asdf")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"}\"" :template) 4)) "}")
|
||||||
|
(assert= (hs-token-value (hs-stream-token (hs-tokens-of "\"${\"asdf\"}\"" :template) 5)) "\"")
|
||||||
|
)
|
||||||
(deftest "handles whitespace properly"
|
(deftest "handles whitespace properly"
|
||||||
(error "SKIP (untranslated): handles whitespace properly"))
|
(assert= (len (get (hs-tokens-of " ") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of " asdf") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of " asdf ") "list")) 2)
|
||||||
|
(assert= (len (get (hs-tokens-of "asdf ") "list")) 2)
|
||||||
|
(assert= (len (get (hs-tokens-of "\n") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "\nasdf") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "\nasdf\n") "list")) 2)
|
||||||
|
(assert= (len (get (hs-tokens-of "asdf\n") "list")) 2)
|
||||||
|
(assert= (len (get (hs-tokens-of "\r") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "\rasdf") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "\rasdf\r") "list")) 2)
|
||||||
|
(assert= (len (get (hs-tokens-of "asdf\r") "list")) 2)
|
||||||
|
(assert= (len (get (hs-tokens-of "\t") "list")) 0)
|
||||||
|
(assert= (len (get (hs-tokens-of "\tasdf") "list")) 1)
|
||||||
|
(assert= (len (get (hs-tokens-of "\tasdf\t") "list")) 2)
|
||||||
|
(assert= (len (get (hs-tokens-of "asdf\t") "list")) 2)
|
||||||
|
)
|
||||||
(deftest "string interpolation isnt surprising"
|
(deftest "string interpolation isnt surprising"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
(let ((_el-div (dom-create-element "div")))
|
(let ((_el-div (dom-create-element "div")))
|
||||||
(dom-set-attr _el-div "_" "on click set x to 42 then put `test${x} test ${x} test$x test $x test $x test ${x} test$x test_$x test_${x} test-$x test.$x` into my.innerHTML")
|
(dom-set-attr _el-div "_" "on click set x to 42 then put `test\\${x} test ${x} test\\$x test $x test \\$x test \\${x} test$x test_$x test_${x} test-$x test.$x` into my.innerHTML")
|
||||||
(dom-append (dom-body) _el-div)
|
(dom-append (dom-body) _el-div)
|
||||||
(hs-activate! _el-div)
|
(hs-activate! _el-div)
|
||||||
(dom-dispatch _el-div "click" nil)
|
(dom-dispatch _el-div "click" nil)
|
||||||
@@ -11508,166 +11754,37 @@
|
|||||||
;; ── socket (16 tests) ──
|
;; ── socket (16 tests) ──
|
||||||
(defsuite "hs-upstream-socket"
|
(defsuite "hs-upstream-socket"
|
||||||
(deftest "converts relative URL to ws:// on http pages"
|
(deftest "converts relative URL to ws:// on http pages"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): converts relative URL to ws:// on http pages"))
|
||||||
(host-set! (host-global "window") "__hs_ws_created" (list))
|
|
||||||
(eval-hs "socket RelSocket /my-ws end")
|
|
||||||
(let ((sock (host-get (host-global "__hs_ws_created") 0)))
|
|
||||||
(assert= (host-get sock "url") "ws://localhost/my-ws")))
|
|
||||||
(deftest "converts relative URL to wss:// on https pages"
|
(deftest "converts relative URL to wss:// on https pages"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): converts relative URL to wss:// on https pages"))
|
||||||
(host-set! (host-global "window") "__hs_ws_created" (list))
|
|
||||||
(host-set! (host-global "location") "protocol" "https:")
|
|
||||||
(eval-hs "socket RelSocket /my-ws end")
|
|
||||||
(host-set! (host-global "location") "protocol" "http:")
|
|
||||||
(let ((sock (host-get (host-global "__hs_ws_created") 0)))
|
|
||||||
(assert= (host-get sock "url") "wss://localhost/my-ws")))
|
|
||||||
(deftest "dispatchEvent sends JSON-encoded event over the socket"
|
(deftest "dispatchEvent sends JSON-encoded event over the socket"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): dispatchEvent sends JSON-encoded event over the socket"))
|
||||||
(eval-hs "socket DispatchSocket ws://localhost/ws end")
|
|
||||||
(let ((wrapper (host-get (host-global "window") "DispatchSocket")))
|
|
||||||
(let ((ws (host-get wrapper "raw"))
|
|
||||||
(evt (host-new "Object")))
|
|
||||||
(do
|
|
||||||
(host-set! evt "type" "foo-event")
|
|
||||||
(host-call wrapper "dispatchEvent" evt)
|
|
||||||
(assert (not (nil? (host-get (host-get ws "_sent") 0))))
|
|
||||||
(let ((parsed (hs-try-json-parse (host-get (host-get ws "_sent") 0))))
|
|
||||||
(assert= (host-get parsed "type") "foo-event"))))))
|
|
||||||
(deftest "namespaced sockets work"
|
(deftest "namespaced sockets work"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): namespaced sockets work"))
|
||||||
(eval-hs "socket MyApp.chat ws://localhost/ws end")
|
|
||||||
(let ((my-app (host-get (host-global "window") "MyApp")))
|
|
||||||
(let ((chat (host-get my-app "chat")))
|
|
||||||
(assert (not (nil? (host-get chat "raw")))))))
|
|
||||||
(deftest "on message as JSON handler decodes JSON payload"
|
(deftest "on message as JSON handler decodes JSON payload"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): on message as JSON handler decodes JSON payload"))
|
||||||
(eval-hs "socket JsonSocket ws://localhost/ws on message as JSON set window.socketFiredJson to true end")
|
|
||||||
(let ((sock (host-get (host-global "window") "JsonSocket")))
|
|
||||||
(let ((ws (host-get sock "raw")))
|
|
||||||
(do
|
|
||||||
(host-call ws "onmessage" {:data "{\"name\":\"Alice\"}"}))
|
|
||||||
(assert= (host-get (host-global "window") "socketFiredJson") true))))
|
|
||||||
(deftest "on message as JSON throws on non-JSON payload"
|
(deftest "on message as JSON throws on non-JSON payload"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): on message as JSON throws on non-JSON payload"))
|
||||||
(eval-hs "socket StrictJsonSocket ws://localhost/ws on message as JSON set window.strictFired to true end")
|
|
||||||
(let ((sock (host-get (host-global "window") "StrictJsonSocket")))
|
|
||||||
(let ((ws (host-get sock "raw")))
|
|
||||||
(do
|
|
||||||
(host-call ws "onmessage" {:data "not-json"})
|
|
||||||
(assert (nil? (host-get (host-global "window") "strictFired")))))))
|
|
||||||
(deftest "on message handler fires on incoming text message"
|
(deftest "on message handler fires on incoming text message"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): on message handler fires on incoming text message"))
|
||||||
(eval-hs "socket TextSocket ws://localhost/ws on message set window.socketFired to true end")
|
|
||||||
(let ((sock (host-get (host-global "window") "TextSocket")))
|
|
||||||
(let ((ws (host-get sock "raw")))
|
|
||||||
(do
|
|
||||||
(host-call ws "onmessage" {:data "hello socket"})
|
|
||||||
(assert= (host-get (host-global "window") "socketFired") true)))))
|
|
||||||
(deftest "parses socket with absolute ws:// URL"
|
(deftest "parses socket with absolute ws:// URL"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): parses socket with absolute ws:// URL"))
|
||||||
(host-set! (host-global "window") "__hs_ws_created" (list))
|
|
||||||
(eval-hs "socket MySocket ws://localhost:1234/ws end")
|
|
||||||
(let ((sock (host-get (host-global "__hs_ws_created") 0)))
|
|
||||||
(assert= (host-get sock "url") "ws://localhost:1234/ws")))
|
|
||||||
(deftest "rpc proxy blacklists then/catch/length/toJSON"
|
(deftest "rpc proxy blacklists then/catch/length/toJSON"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): rpc proxy blacklists then/catch/length/toJSON"))
|
||||||
(eval-hs "socket RpcSocket ws://localhost/ws end")
|
|
||||||
(let ((rpc (host-get (host-get (host-global "window") "RpcSocket") "rpc")))
|
|
||||||
(do
|
|
||||||
(assert (not (= (host-typeof (host-get rpc "then")) "function")))
|
|
||||||
(assert (not (= (host-typeof (host-get rpc "catch")) "function")))
|
|
||||||
(assert (not (= (host-typeof (host-get rpc "length")) "function")))
|
|
||||||
(assert (not (= (host-typeof (host-get rpc "toJSON")) "function"))))
|
|
||||||
(assert (not (nil? rpc)))))
|
|
||||||
(deftest "rpc proxy default timeout rejects the promise"
|
(deftest "rpc proxy default timeout rejects the promise"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): rpc proxy default timeout rejects the promise"))
|
||||||
(eval-hs "socket DefTOSocket ws://localhost/ws with timeout 50 end")
|
|
||||||
(let ((wrapper (host-get (host-global "window") "DefTOSocket")))
|
|
||||||
(let ((rpc (host-get wrapper "rpc")))
|
|
||||||
(do
|
|
||||||
(host-call rpc "neverReplies")
|
|
||||||
(let ((keys-before (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))
|
|
||||||
(assert= (host-get keys-before "length") 1))
|
|
||||||
(host-call (host-global "__hsFlushTimers") "call")
|
|
||||||
(let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))
|
|
||||||
(assert= (host-get keys-after "length") 0))))))
|
|
||||||
(deftest "rpc proxy noTimeout avoids timeout rejection"
|
(deftest "rpc proxy noTimeout avoids timeout rejection"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): rpc proxy noTimeout avoids timeout rejection"))
|
||||||
(eval-hs "socket NoTOSocket ws://localhost/ws with timeout 20 end")
|
|
||||||
(let ((wrapper (host-get (host-global "window") "NoTOSocket")))
|
|
||||||
(let ((rpc (host-get wrapper "rpc")))
|
|
||||||
(do
|
|
||||||
(let ((no-timeout (host-call rpc "noTimeout")))
|
|
||||||
(host-call no-timeout "slowCall" "x"))
|
|
||||||
(host-call (host-global "__hsFlushTimers") "call")
|
|
||||||
(let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))
|
|
||||||
(assert= (host-get keys-after "length") 1))))))
|
|
||||||
(deftest "rpc proxy reply with throw rejects the promise"
|
(deftest "rpc proxy reply with throw rejects the promise"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): rpc proxy reply with throw rejects the promise"))
|
||||||
(eval-hs "socket RpcThrowSocket ws://localhost/ws end")
|
|
||||||
(let ((wrapper (host-get (host-global "window") "RpcThrowSocket")))
|
|
||||||
(let ((ws (host-get wrapper "raw"))
|
|
||||||
(rpc (host-get wrapper "rpc")))
|
|
||||||
(do
|
|
||||||
(host-call rpc "greet" "world")
|
|
||||||
(let ((iid (host-get (hs-try-json-parse (host-get (host-get ws "_sent") 0)) "iid")))
|
|
||||||
(let ((resp (host-new "Object")))
|
|
||||||
(do
|
|
||||||
(host-set! resp "iid" iid)
|
|
||||||
(host-set! resp "throw" "SomeError")
|
|
||||||
(host-call ws "onmessage"
|
|
||||||
{:data (host-call (host-global "JSON") "stringify" resp)})
|
|
||||||
(assert (nil? (host-get (host-get wrapper "pending") iid))))))))))
|
|
||||||
(deftest "rpc proxy sends a message and resolves the reply"
|
(deftest "rpc proxy sends a message and resolves the reply"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): rpc proxy sends a message and resolves the reply"))
|
||||||
(eval-hs "socket RpcSendSocket ws://localhost/ws end")
|
|
||||||
(let ((wrapper (host-get (host-global "window") "RpcSendSocket")))
|
|
||||||
(let ((ws (host-get wrapper "raw"))
|
|
||||||
(rpc (host-get wrapper "rpc")))
|
|
||||||
(do
|
|
||||||
(host-call rpc "greet" "world")
|
|
||||||
(assert (not (nil? (host-get ws "_sent"))))
|
|
||||||
(let ((iid (host-get (hs-try-json-parse (host-get (host-get ws "_sent") 0)) "iid")))
|
|
||||||
(do
|
|
||||||
(let ((resp (host-new "Object")))
|
|
||||||
(do
|
|
||||||
(host-set! resp "iid" iid)
|
|
||||||
(host-set! resp "return" "hello")
|
|
||||||
(host-call ws "onmessage"
|
|
||||||
{:data (host-call (host-global "JSON") "stringify" resp)})))
|
|
||||||
(assert (nil? (host-get (host-get wrapper "pending") iid)))))))))
|
|
||||||
(deftest "rpc proxy timeout(n) rejects after a custom window"
|
(deftest "rpc proxy timeout(n) rejects after a custom window"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): rpc proxy timeout(n) rejects after a custom window"))
|
||||||
(eval-hs "socket CustomTOSocket ws://localhost/ws with timeout 60000 end")
|
|
||||||
(let ((wrapper (host-get (host-global "window") "CustomTOSocket")))
|
|
||||||
(let ((rpc (host-get wrapper "rpc")))
|
|
||||||
(do
|
|
||||||
(let ((timeout-fn (host-call rpc "timeout"))
|
|
||||||
(custom-proxy (host-call-fn timeout-fn (list 50))))
|
|
||||||
(host-call custom-proxy "willTimeOut"))
|
|
||||||
(let ((keys-before (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))
|
|
||||||
(assert= (host-get keys-before "length") 1))
|
|
||||||
(host-call (host-global "__hsFlushTimers") "call")
|
|
||||||
(let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))
|
|
||||||
(assert= (host-get keys-after "length") 0))))))
|
|
||||||
(deftest "rpc reconnects after the underlying socket closes"
|
(deftest "rpc reconnects after the underlying socket closes"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): rpc reconnects after the underlying socket closes"))
|
||||||
(host-set! (host-global "window") "__hs_ws_created" nil)
|
|
||||||
(eval-hs "socket ReconnSocket ws://localhost/ws end")
|
|
||||||
(let ((wrapper (host-get (host-global "window") "ReconnSocket")))
|
|
||||||
(let ((ws (host-get wrapper "raw"))
|
|
||||||
(rpc (host-get wrapper "rpc")))
|
|
||||||
(do
|
|
||||||
(host-call ws "close")
|
|
||||||
(host-call rpc "greet")
|
|
||||||
(assert= (host-get (host-global "__hs_ws_created") "_len") 2)))))
|
|
||||||
(deftest "with timeout parses and uses the configured timeout"
|
(deftest "with timeout parses and uses the configured timeout"
|
||||||
(hs-cleanup!)
|
(error "SKIP (untranslated): with timeout parses and uses the configured timeout"))
|
||||||
(eval-hs "socket TimedSocket ws://localhost/ws with timeout 1500 end")
|
|
||||||
(let ((sock (host-get (host-global "window") "TimedSocket")))
|
|
||||||
(do
|
|
||||||
(assert (not (nil? sock)))
|
|
||||||
(assert (not (nil? (host-get sock "rpc")))))))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
;; ── swap (4 tests) ──
|
;; ── swap (4 tests) ──
|
||||||
|
|||||||
@@ -14,32 +14,6 @@ const SX_DIR = path.join(WASM_DIR, 'sx');
|
|||||||
eval(fs.readFileSync(path.join(WASM_DIR, 'sx_browser.bc.js'), 'utf8'));
|
eval(fs.readFileSync(path.join(WASM_DIR, 'sx_browser.bc.js'), 'utf8'));
|
||||||
const K = globalThis.SxKernel;
|
const K = globalThis.SxKernel;
|
||||||
|
|
||||||
// Suppress unhandled promise rejections — the synchronous test harness never
|
|
||||||
// awaits RPC promises; rejections from timed-out or unresolved calls are expected.
|
|
||||||
process.on('unhandledRejection', () => {});
|
|
||||||
|
|
||||||
// ─── Fake timer (for RPC timeout tests) ────────────────────────────────────
|
|
||||||
// socket timeout tests need setTimeout to fire synchronously on demand.
|
|
||||||
// Replace global setTimeout with a queue; __hsFlushTimers fires all pending.
|
|
||||||
let _fakeTimers = [];
|
|
||||||
let _fakeTimerIdCtr = 0;
|
|
||||||
const _realSetTimeout = globalThis.setTimeout;
|
|
||||||
globalThis.setTimeout = function(cb, _delay) {
|
|
||||||
const id = ++_fakeTimerIdCtr;
|
|
||||||
_fakeTimers.push({ id, cb });
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
globalThis.clearTimeout = function(id) {
|
|
||||||
const idx = _fakeTimers.findIndex(t => t.id === id);
|
|
||||||
if (idx >= 0) _fakeTimers.splice(idx, 1);
|
|
||||||
};
|
|
||||||
// __hsFlushTimers — drain all pending timers synchronously.
|
|
||||||
// Exposed as a plain object so host-call o "call" works.
|
|
||||||
globalThis.__hsFlushTimers = { call: function() {
|
|
||||||
const batch = _fakeTimers.splice(0);
|
|
||||||
for (const { cb } of batch) { try { cb(); } catch (_) {} }
|
|
||||||
}};
|
|
||||||
|
|
||||||
// Step limit API — exposed from OCaml kernel
|
// Step limit API — exposed from OCaml kernel
|
||||||
const STEP_LIMIT = parseInt(process.env.HS_STEP_LIMIT || '200000');
|
const STEP_LIMIT = parseInt(process.env.HS_STEP_LIMIT || '200000');
|
||||||
|
|
||||||
@@ -554,80 +528,9 @@ class HsIntersectionObserver {
|
|||||||
}
|
}
|
||||||
globalThis.IntersectionObserver = HsIntersectionObserver;
|
globalThis.IntersectionObserver = HsIntersectionObserver;
|
||||||
globalThis.IntersectionObserverEntry = class {};
|
globalThis.IntersectionObserverEntry = class {};
|
||||||
globalThis.navigator={userAgent:'node'}; globalThis.location={href:'http://localhost/',pathname:'/',search:'',hash:'',protocol:'http:',host:'localhost',hostname:'localhost',port:''};
|
globalThis.navigator={userAgent:'node'}; globalThis.location={href:'http://localhost/',pathname:'/',search:'',hash:''};
|
||||||
globalThis.history={pushState(){},replaceState(){},back(){},forward(){}};
|
globalThis.history={pushState(){},replaceState(){},back(){},forward(){}};
|
||||||
globalThis.getSelection=()=>({toString:()=>(globalThis.__test_selection||'')});
|
globalThis.getSelection=()=>({toString:()=>(globalThis.__test_selection||'')});
|
||||||
// HsWebSocket — cluster-36 WebSocket mock. Records every constructed socket
|
|
||||||
// in globalThis.__hs_ws_created so tests can assert on URLs and sent frames.
|
|
||||||
// Tests may override globalThis.WebSocket before activating hyperscript.
|
|
||||||
// __hs_ws_created is a plain object with numeric keys (NOT a JS array).
|
|
||||||
// JS arrays are auto-converted to SX lists by host-global; plain objects stay foreign.
|
|
||||||
// host-get foreign 0 → foreign[0] → mock sock ✓
|
|
||||||
globalThis.__hs_ws_created = {_len: 0};
|
|
||||||
globalThis.WebSocket = function HsWebSocket(url) {
|
|
||||||
const sock = {
|
|
||||||
url,
|
|
||||||
onmessage: null,
|
|
||||||
_listeners: {},
|
|
||||||
_sent: {_len: 0},
|
|
||||||
send(msg) { sock._sent[sock._sent._len]=msg; sock._sent._len++; },
|
|
||||||
addEventListener(t, h) { (sock._listeners[t] = sock._listeners[t] || []).push(h); },
|
|
||||||
removeEventListener(t, h) { const a = sock._listeners[t]; if (a) { const i = a.indexOf(h); if (i >= 0) a.splice(i, 1); } },
|
|
||||||
close() { (sock._listeners['close'] || []).forEach(h => { try { h({}); } catch(_) {} }); }
|
|
||||||
};
|
|
||||||
// If the test reset __hs_ws_created to a SX list (via host-set! ... (list)), reinitialise.
|
|
||||||
if (typeof globalThis.__hs_ws_created?._len !== 'number') globalThis.__hs_ws_created = {_len: 0};
|
|
||||||
const idx = globalThis.__hs_ws_created._len;
|
|
||||||
globalThis.__hs_ws_created[idx] = sock;
|
|
||||||
globalThis.__hs_ws_created._len++;
|
|
||||||
return sock;
|
|
||||||
};
|
|
||||||
// _hs_make_rpc_proxy — cluster-36 RPC proxy factory. Called by the runtime
|
|
||||||
// via (host-call (host-global "_hs_make_rpc_proxy") "call" nil wrapper).
|
|
||||||
// wrapper is the SX dict: {raw, url, timeout, pending, ...}
|
|
||||||
// Returns a dispatch function; host-call detects _isRpcProxy and calls it as
|
|
||||||
// fn(method, ...args) rather than fn.method().
|
|
||||||
function _hsRpcCall(wrapper, fnName, args, timeoutMs) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// Lazy reconnect: if the underlying socket closed, open a fresh one
|
|
||||||
// closedFlag is set to "1" (string) by the SX close listener.
|
|
||||||
if (wrapper.closedFlag) {
|
|
||||||
const oldOnmessage = wrapper.raw && wrapper.raw.onmessage;
|
|
||||||
const newWs = new globalThis.WebSocket(wrapper.url);
|
|
||||||
newWs.onmessage = oldOnmessage;
|
|
||||||
wrapper.raw = newWs;
|
|
||||||
wrapper.closedFlag = null;
|
|
||||||
}
|
|
||||||
const iid = String(Math.random()).slice(2) + String(Date.now());
|
|
||||||
if (!wrapper.pending) wrapper.pending = {};
|
|
||||||
wrapper.pending[iid] = { resolve, reject };
|
|
||||||
const raw = wrapper.raw;
|
|
||||||
const msg = JSON.stringify({ iid, function: fnName, args });
|
|
||||||
raw.send(msg);
|
|
||||||
const ms = timeoutMs === undefined ? (typeof wrapper.timeout === 'number' ? wrapper.timeout : 0) : timeoutMs;
|
|
||||||
if (ms !== Infinity && typeof ms === 'number') {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (wrapper.pending && wrapper.pending[iid]) {
|
|
||||||
delete wrapper.pending[iid];
|
|
||||||
reject('Timed out');
|
|
||||||
}
|
|
||||||
}, ms);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function _hs_make_rpc_proxy(wrapper, overrides) {
|
|
||||||
overrides = overrides || {};
|
|
||||||
const fn = function _rpcDispatch(method, ...args) {
|
|
||||||
if (['then', 'catch', 'length', 'toJSON'].includes(method)) return null;
|
|
||||||
if (method === 'noTimeout') return _hs_make_rpc_proxy(wrapper, Object.assign({}, overrides, { timeout: Infinity }));
|
|
||||||
if (method === 'timeout') return function(n) { return _hs_make_rpc_proxy(wrapper, Object.assign({}, overrides, { timeout: n })); };
|
|
||||||
return _hsRpcCall(wrapper, method, args, overrides.timeout);
|
|
||||||
};
|
|
||||||
fn._isRpcProxy = true;
|
|
||||||
return fn;
|
|
||||||
}
|
|
||||||
// host-call passes args as (this_placeholder, ...rest); strip the nil first-arg.
|
|
||||||
globalThis._hs_make_rpc_proxy = { call: (_, w, overrides) => _hs_make_rpc_proxy(w, overrides) };
|
|
||||||
const _origLog = console.log;
|
const _origLog = console.log;
|
||||||
globalThis.console = { log: () => {}, error: () => {}, warn: () => {}, info: () => {}, debug: () => {} }; // suppress ALL console noise
|
globalThis.console = { log: () => {}, error: () => {}, warn: () => {}, info: () => {}, debug: () => {} }; // suppress ALL console noise
|
||||||
const _log = _origLog; // keep reference for our own output
|
const _log = _origLog; // keep reference for our own output
|
||||||
@@ -649,12 +552,10 @@ K.registerNative('host-get',a=>{
|
|||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
K.registerNative('host-set!',a=>{if(a[0]!=null){const v=a[2]; if(a[1]==='innerHTML'&&a[0] instanceof El){const s=v===null?'null':v===undefined?'':String(v);a[0]._setInnerHTML(s);a[0][a[1]]=a[0].innerHTML;} else if(a[1]==='textContent'&&a[0] instanceof El){const s=v===null?'null':v===undefined?'':String(v);a[0].textContent=s;a[0].innerHTML=s;for(const c of a[0].children){c.parentElement=null;c.parentNode=null;}a[0].children=[];a[0].childNodes=[];} else{a[0][a[1]]=v;}} return a[2];});
|
K.registerNative('host-set!',a=>{if(a[0]!=null){const v=a[2]; if(a[1]==='innerHTML'&&a[0] instanceof El){const s=v===null?'null':v===undefined?'':String(v);a[0]._setInnerHTML(s);a[0][a[1]]=a[0].innerHTML;} else if(a[1]==='textContent'&&a[0] instanceof El){const s=v===null?'null':v===undefined?'':String(v);a[0].textContent=s;a[0].innerHTML=s;for(const c of a[0].children){c.parentElement=null;c.parentNode=null;}a[0].children=[];a[0].childNodes=[];} else{a[0][a[1]]=v;}} return a[2];});
|
||||||
K.registerNative('host-call',a=>{if(_testDeadline&&Date.now()>_testDeadline)throw new Error('TIMEOUT: wall clock exceeded');const[o,m,...r]=a;if(o==null){const f=globalThis[m];return typeof f==='function'?f.apply(null,r):null;}// RPC dispatch function: plain JS function stored as _rpcProxy; call as fn(method, ...args)
|
K.registerNative('host-call',a=>{if(_testDeadline&&Date.now()>_testDeadline)throw new Error('TIMEOUT: wall clock exceeded');const[o,m,...r]=a;if(o==null){const f=globalThis[m];return typeof f==='function'?f.apply(null,r):null;}if(o&&typeof o[m]==='function'){try{const v=o[m].apply(o,r);return v===undefined?null:v;}catch(e){return null;}}return null;});
|
||||||
// because host-call normally does o[method]() which would return undefined on a function obj.
|
|
||||||
if(o&&o._isRpcProxy){try{const v=o(m,...r);return v===undefined?null:v;}catch(e){return null;}}if(o&&o.__sx_handle!==undefined){try{const v=K.callFn(o,[m,...r]);if(globalThis._driveAsync)globalThis._driveAsync(v);return v===undefined?null:v;}catch(e){return null;}}if(o&&typeof o[m]==='function'){try{const v=o[m].apply(o,r);return v===undefined?null:v;}catch(e){return null;}}return null;});
|
|
||||||
K.registerNative('host-call-fn',a=>{const[fn,argList]=a;if(typeof fn!=='function'&&!(fn&&fn.__sx_handle!==undefined))return null;const callArgs=(argList&&argList._type==='list'&&argList.items)?Array.from(argList.items):(Array.isArray(argList)?argList:[]);if(fn&&fn.__sx_handle!==undefined)return K.callFn(fn,callArgs);try{const v=fn.apply(null,callArgs);return v===undefined?null:v;}catch(e){return null;}});
|
K.registerNative('host-call-fn',a=>{const[fn,argList]=a;if(typeof fn!=='function'&&!(fn&&fn.__sx_handle!==undefined))return null;const callArgs=(argList&&argList._type==='list'&&argList.items)?Array.from(argList.items):(Array.isArray(argList)?argList:[]);if(fn&&fn.__sx_handle!==undefined)return K.callFn(fn,callArgs);try{const v=fn.apply(null,callArgs);return v===undefined?null:v;}catch(e){return null;}});
|
||||||
K.registerNative('host-new',a=>{const C=typeof a[0]==='string'?globalThis[a[0]]:a[0];return typeof C==='function'?new C(...a.slice(1)):null;});
|
K.registerNative('host-new',a=>{const C=typeof a[0]==='string'?globalThis[a[0]]:a[0];return typeof C==='function'?new C(...a.slice(1)):null;});
|
||||||
K.registerNative('host-callback',a=>{const fn=a[0];if(typeof fn==='function'&&fn.__sx_handle===undefined)return fn;if(fn&&fn.__sx_handle!==undefined){const _fn=fn;return function(){try{const r=K.callFn(_fn,Array.from(arguments));if(globalThis._driveAsync)globalThis._driveAsync(r);return r;}catch(e){}};} return function(){};});
|
K.registerNative('host-callback',a=>{const fn=a[0];if(typeof fn==='function'&&fn.__sx_handle===undefined)return fn;if(fn&&fn.__sx_handle!==undefined)return function(){const r=K.callFn(fn,Array.from(arguments));if(globalThis._driveAsync)globalThis._driveAsync(r);return r;};return function(){};});
|
||||||
K.registerNative('host-typeof',a=>{const o=a[0];if(o==null)return'nil';if(o instanceof El)return'element';if(o&&o.nodeType===3)return'text';if(o instanceof Ev)return'event';if(o instanceof Promise)return'promise';return typeof o;});
|
K.registerNative('host-typeof',a=>{const o=a[0];if(o==null)return'nil';if(o instanceof El)return'element';if(o&&o.nodeType===3)return'text';if(o instanceof Ev)return'event';if(o instanceof Promise)return'promise';return typeof o;});
|
||||||
K.registerNative('host-await',a=>{});
|
K.registerNative('host-await',a=>{});
|
||||||
K.registerNative('load-library!',()=>false);
|
K.registerNative('load-library!',()=>false);
|
||||||
@@ -782,7 +683,6 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
|||||||
globalThis.__hsMutationRegistry.length = 0;
|
globalThis.__hsMutationRegistry.length = 0;
|
||||||
globalThis.__hsMutationActive = false;
|
globalThis.__hsMutationActive = false;
|
||||||
globalThis.__currentHsTestName = name;
|
globalThis.__currentHsTestName = name;
|
||||||
_fakeTimers = []; // reset timer queue between tests
|
|
||||||
|
|
||||||
// Enable step limit for timeout protection
|
// Enable step limit for timeout protection
|
||||||
setStepLimit(STEP_LIMIT);
|
setStepLimit(STEP_LIMIT);
|
||||||
@@ -793,8 +693,7 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
|||||||
try{
|
try{
|
||||||
// Use SX-level guard to catch errors, avoiding __sxR side-channel issues
|
// Use SX-level guard to catch errors, avoiding __sxR side-channel issues
|
||||||
// Returns a dict with :ok and :error keys
|
// Returns a dict with :ok and :error keys
|
||||||
const _dbgR=K.eval(`(define _test-result (_run-test-thunk (get (nth _test-registry ${i}) "thunk")))`);
|
K.eval(`(define _test-result (_run-test-thunk (get (nth _test-registry ${i}) "thunk")))`);
|
||||||
if(suite==='hs-upstream-socket'&&i<=1310)process.stderr.write(`[D] i=${i} r=${JSON.stringify(_dbgR)?.slice(0,160)}\n`);
|
|
||||||
const isOk=K.eval('(get _test-result "ok")');
|
const isOk=K.eval('(get _test-result "ok")');
|
||||||
if(isOk===true){ok=true;}
|
if(isOk===true){ok=true;}
|
||||||
else{
|
else{
|
||||||
|
|||||||
@@ -1254,7 +1254,9 @@ def process_hs_val(hs_val):
|
|||||||
hs_val = hs_val.replace('\\n', '\n').replace('\\t', ' ')
|
hs_val = hs_val.replace('\\n', '\n').replace('\\t', ' ')
|
||||||
# Preserve escaped quotes (\" → placeholder), strip remaining backslashes, restore
|
# Preserve escaped quotes (\" → placeholder), strip remaining backslashes, restore
|
||||||
hs_val = hs_val.replace('\\"', '\x00QUOT\x00')
|
hs_val = hs_val.replace('\\"', '\x00QUOT\x00')
|
||||||
|
hs_val = hs_val.replace('\\$', '\x00DOLLAR\x00') # preserve \$ template escape
|
||||||
hs_val = hs_val.replace('\\', '')
|
hs_val = hs_val.replace('\\', '')
|
||||||
|
hs_val = hs_val.replace('\x00DOLLAR\x00', '\\$') # restore \$
|
||||||
hs_val = hs_val.replace('\x00QUOT\x00', '\\"')
|
hs_val = hs_val.replace('\x00QUOT\x00', '\\"')
|
||||||
# Strip line comments BEFORE newline collapse — once newlines become `then`,
|
# Strip line comments BEFORE newline collapse — once newlines become `then`,
|
||||||
# an unterminated `//` / ` --` comment would consume the rest of the input.
|
# an unterminated `//` / ` --` comment would consume the rest of the input.
|
||||||
@@ -1838,6 +1840,272 @@ def extract_hs_expr(raw):
|
|||||||
return expr
|
return expr
|
||||||
|
|
||||||
|
|
||||||
|
def generate_tokenizer_test(test, safe_name):
|
||||||
|
"""Hardcoded SX translation for _hyperscript.internals.tokenizer tests (E37)."""
|
||||||
|
name = test['name']
|
||||||
|
|
||||||
|
def to_(src, tmpl=False):
|
||||||
|
"""Return (hs-tokens-of <sx-str> [:template]) for HS source string src."""
|
||||||
|
escaped = (src
|
||||||
|
.replace('\\', '\\\\')
|
||||||
|
.replace('"', '\\"')
|
||||||
|
.replace('\n', '\\n')
|
||||||
|
.replace('\r', '\\r')
|
||||||
|
.replace('\t', '\\t'))
|
||||||
|
q = '"' + escaped + '"'
|
||||||
|
suffix = ' :template' if tmpl else ''
|
||||||
|
return f'(hs-tokens-of {q}{suffix})'
|
||||||
|
|
||||||
|
def consume(s):
|
||||||
|
return f'(hs-stream-consume {s})'
|
||||||
|
|
||||||
|
def tok_i(s, i):
|
||||||
|
return f'(hs-stream-token {s} {i})'
|
||||||
|
|
||||||
|
def has_more(s):
|
||||||
|
return f'(hs-stream-has-more {s})'
|
||||||
|
|
||||||
|
def t_type(t):
|
||||||
|
return f'(hs-token-type {t})'
|
||||||
|
|
||||||
|
def t_val(t):
|
||||||
|
return f'(hs-token-value {t})'
|
||||||
|
|
||||||
|
def t_op(t):
|
||||||
|
return f'(hs-token-op? {t})'
|
||||||
|
|
||||||
|
def nth_list(s, i):
|
||||||
|
return f'(nth (get {s} "list") {i})'
|
||||||
|
|
||||||
|
def list_len(s):
|
||||||
|
return f'(len (get {s} "list"))'
|
||||||
|
|
||||||
|
def ae(actual, expected):
|
||||||
|
return f' (assert= {actual} {expected})'
|
||||||
|
|
||||||
|
def throws(expr):
|
||||||
|
return (
|
||||||
|
f' (let ((threw false))\n'
|
||||||
|
f' (guard (e (true (set! threw true))) {expr})\n'
|
||||||
|
f' (assert threw))'
|
||||||
|
)
|
||||||
|
|
||||||
|
lines = [f' (deftest "{safe_name}"']
|
||||||
|
|
||||||
|
if name == 'handles $ in template properly':
|
||||||
|
s = to_('"', tmpl=True)
|
||||||
|
lines.append(ae(t_val(tok_i(s, 0)), sx_str('"')))
|
||||||
|
|
||||||
|
elif name == 'handles all special escapes properly':
|
||||||
|
for src, exp in [
|
||||||
|
('"\\b"', '(char-from-code 8)'),
|
||||||
|
('"\\f"', '(char-from-code 12)'),
|
||||||
|
('"\\n"', '"\\n"'),
|
||||||
|
('"\\r"', '"\\r"'),
|
||||||
|
('"\\t"', '"\\t"'),
|
||||||
|
('"\\v"', '(char-from-code 11)'),
|
||||||
|
]:
|
||||||
|
lines.append(ae(t_val(consume(to_(src))), exp))
|
||||||
|
|
||||||
|
elif name == 'handles basic token types':
|
||||||
|
lines.append(ae(t_type(consume(to_('foo'))), '"IDENTIFIER"'))
|
||||||
|
lines.append(ae(t_type(consume(to_('1'))), '"NUMBER"'))
|
||||||
|
for src in ['1.1', '1e6', '1e-6', '1.1e6', '1.1e-6']:
|
||||||
|
sq = to_(src)
|
||||||
|
lines.append(f' (let ((s {sq}))')
|
||||||
|
lines.append(f' (let ((tok (hs-stream-consume s)))')
|
||||||
|
lines.append(f' (assert= (hs-token-type tok) "NUMBER")')
|
||||||
|
lines.append(f' (assert= (hs-stream-has-more s) false)))')
|
||||||
|
lines.append(ae(t_type(consume(to_('.a'))), '"CLASS_REF"'))
|
||||||
|
lines.append(ae(t_type(consume(to_('#a'))), '"ID_REF"'))
|
||||||
|
lines.append(ae(t_type(consume(to_('"asdf"'))), '"STRING"'))
|
||||||
|
|
||||||
|
elif name == 'handles class identifiers properly':
|
||||||
|
for src, idx, exp_type, exp_val in [
|
||||||
|
('.a', None, 'CLASS_REF', '.a'),
|
||||||
|
(' .a', None, 'CLASS_REF', '.a'),
|
||||||
|
('a.a', None, 'IDENTIFIER', 'a'),
|
||||||
|
('(a).a', 4, 'IDENTIFIER', 'a'),
|
||||||
|
('{a}.a', 4, 'IDENTIFIER', 'a'),
|
||||||
|
('[a].a', 4, 'IDENTIFIER', 'a'),
|
||||||
|
('(a(.a', 3, 'CLASS_REF', '.a'),
|
||||||
|
('{a{.a', 3, 'CLASS_REF', '.a'),
|
||||||
|
('[a[.a', 3, 'CLASS_REF', '.a'),
|
||||||
|
]:
|
||||||
|
if idx is None:
|
||||||
|
tok_expr = consume(to_(src))
|
||||||
|
else:
|
||||||
|
tok_expr = nth_list(to_(src), idx)
|
||||||
|
lines.append(ae(t_type(tok_expr), f'"{exp_type}"'))
|
||||||
|
lines.append(ae(t_val(tok_expr), sx_str(exp_val)))
|
||||||
|
|
||||||
|
elif name == 'handles comments properly':
|
||||||
|
for src, expected in [
|
||||||
|
('--', 0),
|
||||||
|
('asdf--', 1),
|
||||||
|
('-- asdf', 0),
|
||||||
|
('--\nasdf', 1),
|
||||||
|
('--\nasdf--', 1),
|
||||||
|
('---asdf', 0),
|
||||||
|
('----\n---asdf', 0),
|
||||||
|
('----asdf----', 0),
|
||||||
|
('---\nasdf---', 1),
|
||||||
|
('// asdf', 0),
|
||||||
|
('///asdf', 0),
|
||||||
|
('asdf//', 1),
|
||||||
|
('asdf\n//', 2),
|
||||||
|
]:
|
||||||
|
lines.append(ae(list_len(to_(src)), str(expected)))
|
||||||
|
|
||||||
|
elif name == 'handles hex escapes properly':
|
||||||
|
lines.append(ae(t_val(consume(to_('"\\x1f"'))), '(char-from-code 31)'))
|
||||||
|
lines.append(ae(t_val(consume(to_('"\\x41"'))), '"A"'))
|
||||||
|
lines.append(ae(t_val(consume(to_('"\\x41\\x61"'))), '"Aa"'))
|
||||||
|
for bad in ['"\\x"', '"\\xGG"', '"\\x4"']:
|
||||||
|
lines.append(throws(consume(to_(bad))))
|
||||||
|
|
||||||
|
elif name == 'handles id references properly':
|
||||||
|
for src, idx, exp_type, exp_val in [
|
||||||
|
('#a', None, 'ID_REF', '#a'),
|
||||||
|
(' #a', None, 'ID_REF', '#a'),
|
||||||
|
('a#a', None, 'IDENTIFIER', 'a'),
|
||||||
|
('(a)#a', 4, 'IDENTIFIER', 'a'),
|
||||||
|
('{a}#a', 4, 'IDENTIFIER', 'a'),
|
||||||
|
('[a]#a', 4, 'IDENTIFIER', 'a'),
|
||||||
|
('(a(#a', 3, 'ID_REF', '#a'),
|
||||||
|
('{a{#a', 3, 'ID_REF', '#a'),
|
||||||
|
('[a[#a', 3, 'ID_REF', '#a'),
|
||||||
|
]:
|
||||||
|
if idx is None:
|
||||||
|
tok_expr = consume(to_(src))
|
||||||
|
else:
|
||||||
|
tok_expr = nth_list(to_(src), idx)
|
||||||
|
lines.append(ae(t_type(tok_expr), f'"{exp_type}"'))
|
||||||
|
lines.append(ae(t_val(tok_expr), sx_str(exp_val)))
|
||||||
|
|
||||||
|
elif name == 'handles identifiers properly':
|
||||||
|
lines.append(ae(t_type(consume(to_('foo'))), '"IDENTIFIER"'))
|
||||||
|
lines.append(ae(t_val(consume(to_('foo'))), '"foo"'))
|
||||||
|
lines.append(ae(t_type(consume(to_(' foo '))), '"IDENTIFIER"'))
|
||||||
|
lines.append(ae(t_val(consume(to_(' foo '))), '"foo"'))
|
||||||
|
for src, v1, v2 in [
|
||||||
|
(' foo bar', 'foo', 'bar'),
|
||||||
|
(' foo\n-- a comment\n bar', 'foo', 'bar'),
|
||||||
|
]:
|
||||||
|
sq = to_(src)
|
||||||
|
lines.append(f' (let ((s {sq}))')
|
||||||
|
lines.append(f' (let ((tok1 (hs-stream-consume s)))')
|
||||||
|
lines.append(f' (assert= (hs-token-type tok1) "IDENTIFIER")')
|
||||||
|
lines.append(f' (assert= (hs-token-value tok1) {sx_str(v1)})')
|
||||||
|
lines.append(f' (let ((tok2 (hs-stream-consume s)))')
|
||||||
|
lines.append(f' (assert= (hs-token-type tok2) "IDENTIFIER")')
|
||||||
|
lines.append(f' (assert= (hs-token-value tok2) {sx_str(v2)}))))')
|
||||||
|
|
||||||
|
elif name == 'handles identifiers with numbers properly':
|
||||||
|
for src in ['f1oo', 'fo1o', 'foo1']:
|
||||||
|
lines.append(ae(t_type(consume(to_(src))), '"IDENTIFIER"'))
|
||||||
|
lines.append(ae(t_val(consume(to_(src))), sx_str(src)))
|
||||||
|
|
||||||
|
elif name == 'handles look ahead property':
|
||||||
|
s = to_('a 1 + 1')
|
||||||
|
for i, v in [(0, 'a'), (1, '1'), (2, '+'), (3, '1'), (4, '<<<EOF>>>')]:
|
||||||
|
lines.append(ae(t_val(tok_i(s, i)), sx_str(v)))
|
||||||
|
|
||||||
|
elif name == 'handles numbers properly':
|
||||||
|
for src, v in [
|
||||||
|
('1', '1'),
|
||||||
|
('1.1', '1.1'),
|
||||||
|
('1234567890.1234567890', '1234567890.1234567890'),
|
||||||
|
('1e6', '1e6'),
|
||||||
|
('1e-6', '1e-6'),
|
||||||
|
('1.1e6', '1.1e6'),
|
||||||
|
('1.1e-6', '1.1e-6'),
|
||||||
|
]:
|
||||||
|
lines.append(ae(t_type(consume(to_(src))), '"NUMBER"'))
|
||||||
|
lines.append(ae(t_val(consume(to_(src))), sx_str(v)))
|
||||||
|
s = to_('1.1.1')
|
||||||
|
toks = f'(get {s} "list")'
|
||||||
|
lines.append(ae(f'(hs-token-type (nth {toks} 0))', '"NUMBER"'))
|
||||||
|
lines.append(ae(f'(hs-token-type (nth {toks} 1))', '"PERIOD"'))
|
||||||
|
lines.append(ae(f'(hs-token-type (nth {toks} 2))', '"NUMBER"'))
|
||||||
|
lines.append(ae(f'(len {toks})', '3'))
|
||||||
|
|
||||||
|
elif name == 'handles operators properly':
|
||||||
|
optable = [
|
||||||
|
('+', 'PLUS'), ('-', 'MINUS'), ('*', 'MULTIPLY'),
|
||||||
|
('.', 'PERIOD'), ('\\', 'BACKSLASH'), (':', 'COLON'),
|
||||||
|
('%', 'PERCENT'), ('|', 'PIPE'), ('!', 'EXCLAMATION'),
|
||||||
|
('?', 'QUESTION'), ('#', 'POUND'), ('&', 'AMPERSAND'),
|
||||||
|
(';', 'SEMI'), (',', 'COMMA'), ('(', 'L_PAREN'),
|
||||||
|
(')', 'R_PAREN'), ('<', 'L_ANG'), ('>', 'R_ANG'),
|
||||||
|
('{', 'L_BRACE'), ('}', 'R_BRACE'), ('[', 'L_BRACKET'),
|
||||||
|
(']', 'R_BRACKET'), ('=', 'EQUALS'),
|
||||||
|
('<=', 'LTE_ANG'), ('>=', 'GTE_ANG'),
|
||||||
|
('==', 'EQ'), ('===', 'EQQ'),
|
||||||
|
]
|
||||||
|
for op_char, _op_name in optable:
|
||||||
|
tok_expr = consume(to_(op_char))
|
||||||
|
lines.append(ae(t_op(tok_expr), 'true'))
|
||||||
|
lines.append(ae(t_val(tok_expr), sx_str(op_char)))
|
||||||
|
|
||||||
|
elif name == 'handles strings properly':
|
||||||
|
for src, v in [
|
||||||
|
('"foo"', 'foo'),
|
||||||
|
('"fo\'o"', "fo'o"),
|
||||||
|
('"fo\\"o"', 'fo"o'),
|
||||||
|
("'foo'", 'foo'),
|
||||||
|
("'fo\"o'", 'fo"o'),
|
||||||
|
("'fo\\'o'", "fo'o"),
|
||||||
|
]:
|
||||||
|
lines.append(ae(t_type(consume(to_(src))), '"STRING"'))
|
||||||
|
lines.append(ae(t_val(consume(to_(src))), sx_str(v)))
|
||||||
|
lines.append(throws(consume(to_("'"))))
|
||||||
|
lines.append(throws(consume(to_('"'))))
|
||||||
|
|
||||||
|
elif name == 'handles strings properly 2':
|
||||||
|
tok_expr = consume(to_("'foo'"))
|
||||||
|
lines.append(ae(t_type(tok_expr), '"STRING"'))
|
||||||
|
lines.append(ae(t_val(tok_expr), '"foo"'))
|
||||||
|
|
||||||
|
elif name == 'handles template bootstrap properly':
|
||||||
|
s1 = to_('"', tmpl=True)
|
||||||
|
lines.append(ae(t_val(tok_i(s1, 0)), sx_str('"')))
|
||||||
|
s2 = to_('"$', tmpl=True)
|
||||||
|
lines.append(ae(t_val(tok_i(s2, 0)), sx_str('"')))
|
||||||
|
lines.append(ae(t_val(tok_i(s2, 1)), '"$"'))
|
||||||
|
s3 = to_('"${', tmpl=True)
|
||||||
|
lines.append(ae(t_val(tok_i(s3, 0)), sx_str('"')))
|
||||||
|
lines.append(ae(t_val(tok_i(s3, 1)), '"$"'))
|
||||||
|
lines.append(ae(t_val(tok_i(s3, 2)), '"{"'))
|
||||||
|
s4 = to_('"${"asdf"', tmpl=True)
|
||||||
|
lines.append(ae(t_val(tok_i(s4, 0)), sx_str('"')))
|
||||||
|
lines.append(ae(t_val(tok_i(s4, 1)), '"$"'))
|
||||||
|
lines.append(ae(t_val(tok_i(s4, 2)), '"{"'))
|
||||||
|
lines.append(ae(t_val(tok_i(s4, 3)), '"asdf"'))
|
||||||
|
s5 = to_('"${"asdf"}"', tmpl=True)
|
||||||
|
lines.append(ae(t_val(tok_i(s5, 0)), sx_str('"')))
|
||||||
|
lines.append(ae(t_val(tok_i(s5, 1)), '"$"'))
|
||||||
|
lines.append(ae(t_val(tok_i(s5, 2)), '"{"'))
|
||||||
|
lines.append(ae(t_val(tok_i(s5, 3)), '"asdf"'))
|
||||||
|
lines.append(ae(t_val(tok_i(s5, 4)), '"}"'))
|
||||||
|
lines.append(ae(t_val(tok_i(s5, 5)), sx_str('"')))
|
||||||
|
|
||||||
|
elif name == 'handles whitespace properly':
|
||||||
|
for src, expected in [
|
||||||
|
(' ', 0), (' asdf', 1), (' asdf ', 2), ('asdf ', 2),
|
||||||
|
('\n', 0), ('\nasdf', 1), ('\nasdf\n', 2), ('asdf\n', 2),
|
||||||
|
('\r', 0), ('\rasdf', 1), ('\rasdf\r', 2), ('asdf\r', 2),
|
||||||
|
('\t', 0), ('\tasdf', 1), ('\tasdf\t', 2), ('asdf\t', 2),
|
||||||
|
]:
|
||||||
|
lines.append(ae(list_len(to_(src)), str(expected)))
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None # not a tokenizer test we handle
|
||||||
|
|
||||||
|
lines.append(' )')
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
def generate_eval_only_test(test, idx):
|
def generate_eval_only_test(test, idx):
|
||||||
"""Generate SX deftest for no-HTML tests using eval-hs.
|
"""Generate SX deftest for no-HTML tests using eval-hs.
|
||||||
Handles patterns:
|
Handles patterns:
|
||||||
@@ -1894,267 +2162,6 @@ def generate_eval_only_test(test, idx):
|
|||||||
f' (assert (nil? (eval-hs "cookies.foo"))))'
|
f' (assert (nil? (eval-hs "cookies.foo"))))'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Special case: cluster-36 socket URL tests. These check URL normalisation
|
|
||||||
# by running the socket feature with a mock WebSocket and asserting the
|
|
||||||
# URL passed to the constructor.
|
|
||||||
if test['name'] in (
|
|
||||||
'converts relative URL to ws:// on http pages',
|
|
||||||
'converts relative URL to wss:// on https pages',
|
|
||||||
'parses socket with absolute ws:// URL',
|
|
||||||
):
|
|
||||||
https_mode = 'wss' in test['name']
|
|
||||||
if test['name'] == 'parses socket with absolute ws:// URL':
|
|
||||||
hs_src = 'socket MySocket ws://localhost:1234/ws end'
|
|
||||||
expected_url = 'ws://localhost:1234/ws'
|
|
||||||
proto_setup = ''
|
|
||||||
proto_restore = ''
|
|
||||||
else:
|
|
||||||
hs_src = 'socket RelSocket /my-ws end'
|
|
||||||
expected_url = 'wss://localhost/my-ws' if https_mode else 'ws://localhost/my-ws'
|
|
||||||
if https_mode:
|
|
||||||
proto_setup = ' (host-set! (host-global "location") "protocol" "https:")\n'
|
|
||||||
proto_restore = ' (host-set! (host-global "location") "protocol" "http:")\n'
|
|
||||||
else:
|
|
||||||
proto_setup = ''
|
|
||||||
proto_restore = ''
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (host-set! (host-global "window") "__hs_ws_created" (list))\n'
|
|
||||||
+ proto_setup +
|
|
||||||
f' (eval-hs "{hs_src}")\n'
|
|
||||||
+ proto_restore +
|
|
||||||
f' (let ((sock (host-get (host-global "__hs_ws_created") 0)))\n'
|
|
||||||
f' (assert= (host-get sock "url") "{expected_url}")))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Special case: cluster-36 socket shape tests (step 4).
|
|
||||||
# Test 4: namespaced sockets work — dotted name path walks window.
|
|
||||||
if test['name'] == 'namespaced sockets work':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket MyApp.chat ws://localhost/ws end")\n'
|
|
||||||
f' (let ((my-app (host-get (host-global "window") "MyApp")))\n'
|
|
||||||
f' (let ((chat (host-get my-app "chat")))\n'
|
|
||||||
f' (assert (not (nil? (host-get chat "raw")))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 16: with timeout parses and uses the configured timeout —
|
|
||||||
# checks wrapper exists and .rpc is an object.
|
|
||||||
if test['name'] == 'with timeout parses and uses the configured timeout':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket TimedSocket ws://localhost/ws with timeout 1500 end")\n'
|
|
||||||
f' (let ((sock (host-get (host-global "window") "TimedSocket")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (assert (not (nil? sock)))\n'
|
|
||||||
f' (assert (not (nil? (host-get sock "rpc")))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Special case: cluster-36 socket on-message tests (step 5).
|
|
||||||
# Test 7: plain text message fires the handler.
|
|
||||||
if test['name'] == 'on message handler fires on incoming text message':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket TextSocket ws://localhost/ws on message set window.socketFired to true end")\n'
|
|
||||||
f' (let ((sock (host-get (host-global "window") "TextSocket")))\n'
|
|
||||||
f' (let ((ws (host-get sock "raw")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-call ws "onmessage" {{:data "hello socket"}})\n'
|
|
||||||
f' (assert= (host-get (host-global "window") "socketFired") true)))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 5: JSON message fires handler with parsed object.
|
|
||||||
if test['name'] == 'on message as JSON handler decodes JSON payload':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket JsonSocket ws://localhost/ws on message as JSON set window.socketFiredJson to true end")\n'
|
|
||||||
f' (let ((sock (host-get (host-global "window") "JsonSocket")))\n'
|
|
||||||
f' (let ((ws (host-get sock "raw")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-call ws "onmessage" {{:data "{{\\"name\\":\\"Alice\\"}}"}}))\n'
|
|
||||||
f' (assert= (host-get (host-global "window") "socketFiredJson") true))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 6: non-JSON data with as JSON raises error before handler body runs.
|
|
||||||
# We verify the handler body (set window.strictFired) was NOT executed.
|
|
||||||
if test['name'] == 'on message as JSON throws on non-JSON payload':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket StrictJsonSocket ws://localhost/ws on message as JSON set window.strictFired to true end")\n'
|
|
||||||
f' (let ((sock (host-get (host-global "window") "StrictJsonSocket")))\n'
|
|
||||||
f' (let ((ws (host-get sock "raw")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-call ws "onmessage" {{:data "not-json"}})\n'
|
|
||||||
f' (assert (nil? (host-get (host-global "window") "strictFired")))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 9: rpc proxy blacklists then/catch/length/toJSON
|
|
||||||
# Verify none of the blacklisted names return a function (the real requirement:
|
|
||||||
# rpc must not behave as a thenable or have a callable toJSON/length).
|
|
||||||
if test['name'] == 'rpc proxy blacklists then/catch/length/toJSON':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket RpcSocket ws://localhost/ws end")\n'
|
|
||||||
f' (let ((rpc (host-get (host-get (host-global "window") "RpcSocket") "rpc")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (assert (not (= (host-typeof (host-get rpc "then")) "function")))\n'
|
|
||||||
f' (assert (not (= (host-typeof (host-get rpc "catch")) "function")))\n'
|
|
||||||
f' (assert (not (= (host-typeof (host-get rpc "length")) "function")))\n'
|
|
||||||
f' (assert (not (= (host-typeof (host-get rpc "toJSON")) "function"))))\n'
|
|
||||||
f' (assert (not (nil? rpc)))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 13: rpc proxy sends a message and resolves the reply
|
|
||||||
# Verify: (a) calling rpc.method triggers ws.send, (b) injecting the reply
|
|
||||||
# clears the pending entry (hs-socket-resolve-rpc! ran).
|
|
||||||
if test['name'] == 'rpc proxy sends a message and resolves the reply':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket RpcSendSocket ws://localhost/ws end")\n'
|
|
||||||
f' (let ((wrapper (host-get (host-global "window") "RpcSendSocket")))\n'
|
|
||||||
f' (let ((ws (host-get wrapper "raw"))\n'
|
|
||||||
f' (rpc (host-get wrapper "rpc")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-call rpc "greet" "world")\n'
|
|
||||||
f' (assert (not (nil? (host-get ws "_sent"))))\n'
|
|
||||||
f' (let ((iid (host-get (hs-try-json-parse (host-get (host-get ws "_sent") 0)) "iid")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (let ((resp (host-new "Object")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-set! resp "iid" iid)\n'
|
|
||||||
f' (host-set! resp "return" "hello")\n'
|
|
||||||
f' (host-call ws "onmessage"\n'
|
|
||||||
f' {{:data (host-call (host-global "JSON") "stringify" resp)}})))\n'
|
|
||||||
f' (assert (nil? (host-get (host-get wrapper "pending") iid)))))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 3: dispatchEvent sends JSON-encoded event over the socket.
|
|
||||||
# Verifies the wrapper's dispatchEvent method sends a JSON payload including
|
|
||||||
# the event's type field.
|
|
||||||
if test['name'] == 'dispatchEvent sends JSON-encoded event over the socket':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket DispatchSocket ws://localhost/ws end")\n'
|
|
||||||
f' (let ((wrapper (host-get (host-global "window") "DispatchSocket")))\n'
|
|
||||||
f' (let ((ws (host-get wrapper "raw"))\n'
|
|
||||||
f' (evt (host-new "Object")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-set! evt "type" "foo-event")\n'
|
|
||||||
f' (host-call wrapper "dispatchEvent" evt)\n'
|
|
||||||
f' (assert (not (nil? (host-get (host-get ws "_sent") 0))))\n'
|
|
||||||
f' (let ((parsed (hs-try-json-parse (host-get (host-get ws "_sent") 0))))\n'
|
|
||||||
f' (assert= (host-get parsed "type") "foo-event"))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 12: rpc proxy reply with throw rejects the promise.
|
|
||||||
# Verifies hs-socket-resolve-rpc! calls resolver.reject when msg.throw is set,
|
|
||||||
# and clears the pending entry.
|
|
||||||
if test['name'] == 'rpc proxy reply with throw rejects the promise':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket RpcThrowSocket ws://localhost/ws end")\n'
|
|
||||||
f' (let ((wrapper (host-get (host-global "window") "RpcThrowSocket")))\n'
|
|
||||||
f' (let ((ws (host-get wrapper "raw"))\n'
|
|
||||||
f' (rpc (host-get wrapper "rpc")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-call rpc "greet" "world")\n'
|
|
||||||
f' (let ((iid (host-get (hs-try-json-parse (host-get (host-get ws "_sent") 0)) "iid")))\n'
|
|
||||||
f' (let ((resp (host-new "Object")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-set! resp "iid" iid)\n'
|
|
||||||
f' (host-set! resp "throw" "SomeError")\n'
|
|
||||||
f' (host-call ws "onmessage"\n'
|
|
||||||
f' {{:data (host-call (host-global "JSON") "stringify" resp)}})\n'
|
|
||||||
f' (assert (nil? (host-get (host-get wrapper "pending") iid))))))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 15: rpc reconnects after the underlying socket closes.
|
|
||||||
# Verifies the lazy-reconnect path: after ws.close() marks the wrapper dead,
|
|
||||||
# the next RPC call creates a fresh WebSocket (total created == 2).
|
|
||||||
if test['name'] == 'rpc reconnects after the underlying socket closes':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (host-set! (host-global "window") "__hs_ws_created" nil)\n'
|
|
||||||
f' (eval-hs "socket ReconnSocket ws://localhost/ws end")\n'
|
|
||||||
f' (let ((wrapper (host-get (host-global "window") "ReconnSocket")))\n'
|
|
||||||
f' (let ((ws (host-get wrapper "raw"))\n'
|
|
||||||
f' (rpc (host-get wrapper "rpc")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-call ws "close")\n'
|
|
||||||
f' (host-call rpc "greet")\n'
|
|
||||||
f' (assert= (host-get (host-global "__hs_ws_created") "_len") 2)))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 10: rpc proxy default timeout rejects the promise.
|
|
||||||
# With a socket created using `with timeout 50`, calling rpc.neverReplies()
|
|
||||||
# enqueues a fake setTimeout. After flushing timers, wrapper.pending should
|
|
||||||
# be empty (the timeout callback deleted the entry and rejected the promise).
|
|
||||||
if test['name'] == 'rpc proxy default timeout rejects the promise':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket DefTOSocket ws://localhost/ws with timeout 50 end")\n'
|
|
||||||
f' (let ((wrapper (host-get (host-global "window") "DefTOSocket")))\n'
|
|
||||||
f' (let ((rpc (host-get wrapper "rpc")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (host-call rpc "neverReplies")\n'
|
|
||||||
f' (let ((keys-before (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
|
||||||
f' (assert= (host-get keys-before "length") 1))\n'
|
|
||||||
f' (host-call (host-global "__hsFlushTimers") "call")\n'
|
|
||||||
f' (let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
|
||||||
f' (assert= (host-get keys-after "length") 0))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 11: rpc proxy noTimeout avoids timeout rejection.
|
|
||||||
# rpc.noTimeout returns a proxy with timeout=Infinity; no setTimeout is
|
|
||||||
# registered so flushing timers leaves the pending entry intact.
|
|
||||||
if test['name'] == 'rpc proxy noTimeout avoids timeout rejection':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket NoTOSocket ws://localhost/ws with timeout 20 end")\n'
|
|
||||||
f' (let ((wrapper (host-get (host-global "window") "NoTOSocket")))\n'
|
|
||||||
f' (let ((rpc (host-get wrapper "rpc")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (let ((no-timeout (host-call rpc "noTimeout")))\n'
|
|
||||||
f' (host-call no-timeout "slowCall" "x"))\n'
|
|
||||||
f' (host-call (host-global "__hsFlushTimers") "call")\n'
|
|
||||||
f' (let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
|
||||||
f' (assert= (host-get keys-after "length") 1))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test 14: rpc proxy timeout(n) rejects after a custom window.
|
|
||||||
# rpc.timeout(50) returns a proxy with overrideTimeout=50; calling a method
|
|
||||||
# on it enqueues a 50ms fake timer. After flushing, pending is empty.
|
|
||||||
if test['name'] == 'rpc proxy timeout(n) rejects after a custom window':
|
|
||||||
return (
|
|
||||||
f' (deftest "{safe_name}"\n'
|
|
||||||
f' (hs-cleanup!)\n'
|
|
||||||
f' (eval-hs "socket CustomTOSocket ws://localhost/ws with timeout 60000 end")\n'
|
|
||||||
f' (let ((wrapper (host-get (host-global "window") "CustomTOSocket")))\n'
|
|
||||||
f' (let ((rpc (host-get wrapper "rpc")))\n'
|
|
||||||
f' (do\n'
|
|
||||||
f' (let ((timeout-fn (host-call rpc "timeout"))\n'
|
|
||||||
f' (custom-proxy (host-call-fn timeout-fn (list 50))))\n'
|
|
||||||
f' (host-call custom-proxy "willTimeOut"))\n'
|
|
||||||
f' (let ((keys-before (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
|
||||||
f' (assert= (host-get keys-before "length") 1))\n'
|
|
||||||
f' (host-call (host-global "__hsFlushTimers") "call")\n'
|
|
||||||
f' (let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
|
||||||
f' (assert= (host-get keys-after "length") 0))))))'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Special case: cluster-29 init events. The two tractable tests both attach
|
# Special case: cluster-29 init events. The two tractable tests both attach
|
||||||
# listeners to a wa container, set its innerHTML to a hyperscript fragment,
|
# listeners to a wa container, set its innerHTML to a hyperscript fragment,
|
||||||
# then call `_hyperscript.processNode(wa)`. Hand-roll deftests using
|
# then call `_hyperscript.processNode(wa)`. Hand-roll deftests using
|
||||||
@@ -2276,6 +2283,9 @@ def generate_eval_only_test(test, idx):
|
|||||||
f' )'
|
f' )'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if '_hyperscript.internals.tokenizer' in body:
|
||||||
|
return generate_tokenizer_test(test, safe_name)
|
||||||
|
|
||||||
lines.append(f' (deftest "{safe_name}"')
|
lines.append(f' (deftest "{safe_name}"')
|
||||||
|
|
||||||
assertions = []
|
assertions = []
|
||||||
|
|||||||
Reference in New Issue
Block a user