sx: step 9 — parser feature registry
Add `_hs-feature-registry` dict and `hs-register-feature!` to `lib/hyperscript/parser.sx`. Replace `parse-feat`'s hardcoded `cond` on feature names with a registry lookup; the paren-open and default-expression branches remain as fallthroughs. Each parse-fn receives a `ctx` dict (built per call by `parse-feat-ctx`) exposing parser internals (`:adv!`, `:tp-val`, `:tp-type`, `:at-end?`, `:parse-cmd-list`, `:parse-expr`) and the per-feature handlers (`:parse-on-feat` … `:parse-socket-feat`). All nine builtins (`on`, `init`, `def`, `behavior`, `live`, `when`, `worker`, `bind`, `socket`) are registered at file load time, so plugins added later via `hs-register-feature!` persist across `hs-parse` calls. Worker stub still raises identically. Mirror `shared/static/wasm/sx/hs-parser.sx` copied byte-identical. OCaml: 4545/1339, JS: 2591/2465 — both match baseline, zero regressions. First piece of plans/designs/hs-plugin-system.md (Steps 10/11 follow).
This commit is contained in:
@@ -3,6 +3,17 @@
|
|||||||
;; Input: list of {:type T :value V :pos P} tokens from hs-tokenize
|
;; Input: list of {:type T :value V :pos P} tokens from hs-tokenize
|
||||||
;; Output: SX AST forms that map to runtime primitives
|
;; Output: SX AST forms that map to runtime primitives
|
||||||
|
|
||||||
|
;; ── Feature plugin registry ───────────────────────────────────────
|
||||||
|
;; Plugins call (hs-register-feature! "name" parse-fn) at load time.
|
||||||
|
;; parse-fn is (fn (ctx) ...) where ctx is a dict exposing parser
|
||||||
|
;; helpers (:adv! :tp-val :tp-type :parse-cmd-list ...) and the
|
||||||
|
;; built-in parse-X-feat dispatch fns.
|
||||||
|
(begin
|
||||||
|
(define _hs-feature-registry {})
|
||||||
|
(define
|
||||||
|
hs-register-feature!
|
||||||
|
(fn (name parse-fn) (dict-set! _hs-feature-registry name parse-fn))))
|
||||||
|
|
||||||
;; ── Parser entry point ────────────────────────────────────────────
|
;; ── Parser entry point ────────────────────────────────────────────
|
||||||
(define
|
(define
|
||||||
hs-parse
|
hs-parse
|
||||||
@@ -3231,6 +3242,24 @@
|
|||||||
(do
|
(do
|
||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote socket) name-path url timeout on-message))))))))))
|
(list (quote socket) name-path url timeout on-message))))))))))
|
||||||
|
(define
|
||||||
|
parse-feat-ctx
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
{:adv! adv!
|
||||||
|
:tp-val tp-val
|
||||||
|
:tp-type tp-type
|
||||||
|
:at-end? at-end?
|
||||||
|
:parse-cmd-list parse-cmd-list
|
||||||
|
:parse-expr parse-expr
|
||||||
|
:parse-on-feat parse-on-feat
|
||||||
|
:parse-init-feat parse-init-feat
|
||||||
|
:parse-def-feat parse-def-feat
|
||||||
|
:parse-behavior-feat parse-behavior-feat
|
||||||
|
:parse-live-feat parse-live-feat
|
||||||
|
:parse-when-feat parse-when-feat
|
||||||
|
:parse-bind-feat parse-bind-feat
|
||||||
|
:parse-socket-feat parse-socket-feat}))
|
||||||
(define
|
(define
|
||||||
parse-feat
|
parse-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -3261,18 +3290,12 @@
|
|||||||
((unit (tp-val)))
|
((unit (tp-val)))
|
||||||
(do (adv!) (list (quote string-postfix) inner unit)))
|
(do (adv!) (list (quote string-postfix) inner unit)))
|
||||||
inner))))
|
inner))))
|
||||||
((= val "on") (do (adv!) (parse-on-feat)))
|
|
||||||
((= val "init") (do (adv!) (parse-init-feat)))
|
|
||||||
((= val "def") (do (adv!) (parse-def-feat)))
|
|
||||||
((= val "behavior") (do (adv!) (parse-behavior-feat)))
|
|
||||||
((= val "live") (do (adv!) (parse-live-feat)))
|
|
||||||
((= val "when") (do (adv!) (parse-when-feat)))
|
|
||||||
((= val "worker")
|
|
||||||
(error
|
|
||||||
"worker plugin is not installed — see https://hyperscript.org/features/worker"))
|
|
||||||
((= val "bind") (do (adv!) (parse-bind-feat)))
|
|
||||||
((= val "socket") (do (adv!) (parse-socket-feat)))
|
|
||||||
(true
|
(true
|
||||||
|
(let
|
||||||
|
((reg-fn (dict-get _hs-feature-registry val)))
|
||||||
|
(if
|
||||||
|
reg-fn
|
||||||
|
(reg-fn (parse-feat-ctx))
|
||||||
(if
|
(if
|
||||||
(= (tp-type) "keyword")
|
(= (tp-type) "keyword")
|
||||||
(parse-cmd-list)
|
(parse-cmd-list)
|
||||||
@@ -3283,7 +3306,7 @@
|
|||||||
(if
|
(if
|
||||||
(and expr (at-end?))
|
(and expr (at-end?))
|
||||||
expr
|
expr
|
||||||
(do (set! p saved-p) (parse-cmd-list)))))))))))
|
(do (set! p saved-p) (parse-cmd-list)))))))))))))
|
||||||
(define
|
(define
|
||||||
coll-feats
|
coll-feats
|
||||||
(fn
|
(fn
|
||||||
@@ -3326,3 +3349,36 @@
|
|||||||
(let
|
(let
|
||||||
((result (hs-parse (hs-tokenize src) src)))
|
((result (hs-parse (hs-tokenize src) src)))
|
||||||
(do (set! hs-span-mode false) result)))))
|
(do (set! hs-span-mode false) result)))))
|
||||||
|
|
||||||
|
;; ── Built-in feature registrations ────────────────────────────────
|
||||||
|
;; These mirror the original parse-feat cond branches. Registering at
|
||||||
|
;; load time means plugins can override or extend; ctx exposes the
|
||||||
|
;; parser internals each fn needs.
|
||||||
|
(begin
|
||||||
|
(hs-register-feature!
|
||||||
|
"on"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-on-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"init"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-init-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"def"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-def-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"behavior"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-behavior-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"live"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-live-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"when"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-when-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"worker"
|
||||||
|
(fn (ctx) (error "worker plugin is not installed — see https://hyperscript.org/features/worker")))
|
||||||
|
(hs-register-feature!
|
||||||
|
"bind"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-bind-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"socket"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-socket-feat))))))
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ these when operands are known numbers/lists.
|
|||||||
| 6 — JS AdtValue + define-type + match | [x] | fc8a3916 |
|
| 6 — JS AdtValue + define-type + match | [x] | fc8a3916 |
|
||||||
| 7 — nested patterns | [x] | 0679edf5 |
|
| 7 — nested patterns | [x] | 0679edf5 |
|
||||||
| 8 — exhaustiveness warnings | [x] | 6d391119 |
|
| 8 — exhaustiveness warnings | [x] | 6d391119 |
|
||||||
| 9 — parser feature registry | [ ] | — |
|
| 9 — parser feature registry | [x] | PENDING |
|
||||||
| 10 — compiler + as converter registry | [ ] | — |
|
| 10 — compiler + as converter registry | [ ] | — |
|
||||||
| 11 — plugin migration + worker | [ ] | — |
|
| 11 — plugin migration + worker | [ ] | — |
|
||||||
| 12 — frame records | [ ] | — |
|
| 12 — frame records | [ ] | — |
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
;; Input: list of {:type T :value V :pos P} tokens from hs-tokenize
|
;; Input: list of {:type T :value V :pos P} tokens from hs-tokenize
|
||||||
;; Output: SX AST forms that map to runtime primitives
|
;; Output: SX AST forms that map to runtime primitives
|
||||||
|
|
||||||
|
;; ── Feature plugin registry ───────────────────────────────────────
|
||||||
|
;; Plugins call (hs-register-feature! "name" parse-fn) at load time.
|
||||||
|
;; parse-fn is (fn (ctx) ...) where ctx is a dict exposing parser
|
||||||
|
;; helpers (:adv! :tp-val :tp-type :parse-cmd-list ...) and the
|
||||||
|
;; built-in parse-X-feat dispatch fns.
|
||||||
|
(begin
|
||||||
|
(define _hs-feature-registry {})
|
||||||
|
(define
|
||||||
|
hs-register-feature!
|
||||||
|
(fn (name parse-fn) (dict-set! _hs-feature-registry name parse-fn))))
|
||||||
|
|
||||||
;; ── Parser entry point ────────────────────────────────────────────
|
;; ── Parser entry point ────────────────────────────────────────────
|
||||||
(define
|
(define
|
||||||
hs-parse
|
hs-parse
|
||||||
@@ -3015,7 +3026,7 @@
|
|||||||
(fn
|
(fn
|
||||||
()
|
()
|
||||||
(let
|
(let
|
||||||
((every? (match-kw "every")) (first? (match-kw "first")))
|
((first? (match-kw "first")))
|
||||||
(let
|
(let
|
||||||
((event-name (parse-compound-event-name)))
|
((event-name (parse-compound-event-name)))
|
||||||
(let
|
(let
|
||||||
@@ -3028,7 +3039,27 @@
|
|||||||
((flt (if (= (tp-type) "bracket-open") (do (adv!) (let ((f (parse-expr))) (if (= (tp-type) "bracket-close") (adv!) nil) f)) nil)))
|
((flt (if (= (tp-type) "bracket-open") (do (adv!) (let ((f (parse-expr))) (if (= (tp-type) "bracket-close") (adv!) nil) f)) nil)))
|
||||||
(let
|
(let
|
||||||
((elsewhere? (cond ((match-kw "elsewhere") true) ((and (= (tp-type) "keyword") (= (tp-val) "from") (let ((nxt (if (< (+ p 1) tok-len) (nth tokens (+ p 1)) nil))) (and nxt (= (get nxt "type") "keyword") (= (get nxt "value") "elsewhere")))) (do (adv!) (adv!) true)) (true false)))
|
((elsewhere? (cond ((match-kw "elsewhere") true) ((and (= (tp-type) "keyword") (= (tp-val) "from") (let ((nxt (if (< (+ p 1) tok-len) (nth tokens (+ p 1)) nil))) (and nxt (= (get nxt "type") "keyword") (= (get nxt "value") "elsewhere")))) (do (adv!) (adv!) true)) (true false)))
|
||||||
(source (if (match-kw "from") (parse-expr) nil)))
|
(source
|
||||||
|
(if
|
||||||
|
(match-kw "from")
|
||||||
|
(parse-collection
|
||||||
|
(parse-cmp
|
||||||
|
(parse-arith (parse-poss (parse-atom)))))
|
||||||
|
nil)))
|
||||||
|
(define
|
||||||
|
collect-ors!
|
||||||
|
(fn
|
||||||
|
(acc)
|
||||||
|
(if
|
||||||
|
(match-kw "or")
|
||||||
|
(let
|
||||||
|
((or-evt (parse-compound-event-name))
|
||||||
|
(or-src
|
||||||
|
(if (match-kw "from") (parse-expr) nil)))
|
||||||
|
(collect-ors!
|
||||||
|
(append acc (list (list or-evt or-src)))))
|
||||||
|
acc)))
|
||||||
|
(define or-sources (collect-ors! (list)))
|
||||||
(let
|
(let
|
||||||
((h-margin nil) (h-threshold nil))
|
((h-margin nil) (h-threshold nil))
|
||||||
(define
|
(define
|
||||||
@@ -3058,6 +3089,8 @@
|
|||||||
(= (tp-type) "keyword")
|
(= (tp-type) "keyword")
|
||||||
(= (tp-val) "queue"))
|
(= (tp-val) "queue"))
|
||||||
(do (adv!) (adv!)))
|
(do (adv!) (adv!)))
|
||||||
|
(let
|
||||||
|
((every? (match-kw "every")))
|
||||||
(let
|
(let
|
||||||
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
|
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
|
||||||
(let
|
(let
|
||||||
@@ -3080,6 +3113,8 @@
|
|||||||
((parts (if elsewhere? (append parts (list :elsewhere true)) parts)))
|
((parts (if elsewhere? (append parts (list :elsewhere true)) parts)))
|
||||||
(let
|
(let
|
||||||
((parts (if source (append parts (list :from source)) parts)))
|
((parts (if source (append parts (list :from source)) parts)))
|
||||||
|
(let
|
||||||
|
((parts (if (> (len or-sources) 0) (append parts (list :or-sources or-sources)) parts)))
|
||||||
(let
|
(let
|
||||||
((parts (if count-filter (append parts (list :count-filter count-filter)) parts)))
|
((parts (if count-filter (append parts (list :count-filter count-filter)) parts)))
|
||||||
(let
|
(let
|
||||||
@@ -3092,7 +3127,7 @@
|
|||||||
((parts (if finally-clause (append parts (list :finally finally-clause)) parts)))
|
((parts (if finally-clause (append parts (list :finally finally-clause)) parts)))
|
||||||
(let
|
(let
|
||||||
((parts (append parts (list (if (> (len event-vars) 0) (cons (quote do) (append (map (fn (nm) (list (quote ref) nm)) event-vars) (if (and (list? body) (= (first body) (quote do))) (rest body) (list body)))) body)))))
|
((parts (append parts (list (if (> (len event-vars) 0) (cons (quote do) (append (map (fn (nm) (list (quote ref) nm)) event-vars) (if (and (list? body) (= (first body) (quote do))) (rest body) (list body)))) body)))))
|
||||||
parts))))))))))))))))))))))))
|
parts))))))))))))))))))))))))))
|
||||||
(define
|
(define
|
||||||
parse-init-feat
|
parse-init-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -3207,6 +3242,24 @@
|
|||||||
(do
|
(do
|
||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote socket) name-path url timeout on-message))))))))))
|
(list (quote socket) name-path url timeout on-message))))))))))
|
||||||
|
(define
|
||||||
|
parse-feat-ctx
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
{:adv! adv!
|
||||||
|
:tp-val tp-val
|
||||||
|
:tp-type tp-type
|
||||||
|
:at-end? at-end?
|
||||||
|
:parse-cmd-list parse-cmd-list
|
||||||
|
:parse-expr parse-expr
|
||||||
|
:parse-on-feat parse-on-feat
|
||||||
|
:parse-init-feat parse-init-feat
|
||||||
|
:parse-def-feat parse-def-feat
|
||||||
|
:parse-behavior-feat parse-behavior-feat
|
||||||
|
:parse-live-feat parse-live-feat
|
||||||
|
:parse-when-feat parse-when-feat
|
||||||
|
:parse-bind-feat parse-bind-feat
|
||||||
|
:parse-socket-feat parse-socket-feat}))
|
||||||
(define
|
(define
|
||||||
parse-feat
|
parse-feat
|
||||||
(fn
|
(fn
|
||||||
@@ -3237,18 +3290,12 @@
|
|||||||
((unit (tp-val)))
|
((unit (tp-val)))
|
||||||
(do (adv!) (list (quote string-postfix) inner unit)))
|
(do (adv!) (list (quote string-postfix) inner unit)))
|
||||||
inner))))
|
inner))))
|
||||||
((= val "on") (do (adv!) (parse-on-feat)))
|
|
||||||
((= val "init") (do (adv!) (parse-init-feat)))
|
|
||||||
((= val "def") (do (adv!) (parse-def-feat)))
|
|
||||||
((= val "behavior") (do (adv!) (parse-behavior-feat)))
|
|
||||||
((= val "live") (do (adv!) (parse-live-feat)))
|
|
||||||
((= val "when") (do (adv!) (parse-when-feat)))
|
|
||||||
((= val "worker")
|
|
||||||
(error
|
|
||||||
"worker plugin is not installed — see https://hyperscript.org/features/worker"))
|
|
||||||
((= val "bind") (do (adv!) (parse-bind-feat)))
|
|
||||||
((= val "socket") (do (adv!) (parse-socket-feat)))
|
|
||||||
(true
|
(true
|
||||||
|
(let
|
||||||
|
((reg-fn (dict-get _hs-feature-registry val)))
|
||||||
|
(if
|
||||||
|
reg-fn
|
||||||
|
(reg-fn (parse-feat-ctx))
|
||||||
(if
|
(if
|
||||||
(= (tp-type) "keyword")
|
(= (tp-type) "keyword")
|
||||||
(parse-cmd-list)
|
(parse-cmd-list)
|
||||||
@@ -3259,7 +3306,7 @@
|
|||||||
(if
|
(if
|
||||||
(and expr (at-end?))
|
(and expr (at-end?))
|
||||||
expr
|
expr
|
||||||
(do (set! p saved-p) (parse-cmd-list)))))))))))
|
(do (set! p saved-p) (parse-cmd-list)))))))))))))
|
||||||
(define
|
(define
|
||||||
coll-feats
|
coll-feats
|
||||||
(fn
|
(fn
|
||||||
@@ -3302,3 +3349,36 @@
|
|||||||
(let
|
(let
|
||||||
((result (hs-parse (hs-tokenize src) src)))
|
((result (hs-parse (hs-tokenize src) src)))
|
||||||
(do (set! hs-span-mode false) result)))))
|
(do (set! hs-span-mode false) result)))))
|
||||||
|
|
||||||
|
;; ── Built-in feature registrations ────────────────────────────────
|
||||||
|
;; These mirror the original parse-feat cond branches. Registering at
|
||||||
|
;; load time means plugins can override or extend; ctx exposes the
|
||||||
|
;; parser internals each fn needs.
|
||||||
|
(begin
|
||||||
|
(hs-register-feature!
|
||||||
|
"on"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-on-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"init"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-init-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"def"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-def-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"behavior"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-behavior-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"live"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-live-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"when"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-when-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"worker"
|
||||||
|
(fn (ctx) (error "worker plugin is not installed — see https://hyperscript.org/features/worker")))
|
||||||
|
(hs-register-feature!
|
||||||
|
"bind"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-bind-feat)))))
|
||||||
|
(hs-register-feature!
|
||||||
|
"socket"
|
||||||
|
(fn (ctx) (begin ((dict-get ctx :adv!)) ((dict-get ctx :parse-socket-feat))))))
|
||||||
|
|||||||
Reference in New Issue
Block a user