Add reactive islands spec: signals.sx + defisland across all adapters

New spec file signals.sx defines the signal runtime: signal, computed,
effect, deref, reset!, swap!, batch, dispose, and island scope tracking.

eval.sx: defisland special form + island? type predicate in eval-call.
boundary.sx: signal primitive declarations (Tier 3).
render.sx: defisland in definition-form?.
adapter-dom.sx: render-dom-island with reactive context, reactive-text,
  reactive-attr, reactive-fragment, reactive-list helpers.
adapter-html.sx: render-html-island for SSR with data-sx-island/state.
adapter-sx.sx: island? handling in wire format serialization.
special-forms.sx: defisland declaration with docs and example.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 09:34:47 +00:00
parent b2aaa3786d
commit a97f4c0e39
8 changed files with 646 additions and 9 deletions

View File

@@ -35,12 +35,14 @@
;; lambda — closure: {params, body, closure-env, name?}
;; macro — AST transformer: {params, rest-param, body, closure-env}
;; component — UI component: {name, params, has-children, body, closure-env}
;; island — reactive component: like component but with island flag
;; thunk — deferred eval for TCO: {expr, env}
;;
;; Each target must provide:
;; (type-of x) → one of the strings above
;; (make-lambda ...) → platform Lambda value
;; (make-component ..) → platform Component value
;; (make-island ...) → platform Island value (component + island flag)
;; (make-macro ...) → platform Macro value
;; (make-thunk ...) → platform Thunk value
;;
@@ -141,6 +143,7 @@
(= name "fn") (sf-lambda args env)
(= name "define") (sf-define args env)
(= name "defcomp") (sf-defcomp args env)
(= name "defisland") (sf-defisland args env)
(= name "defmacro") (sf-defmacro args env)
(= name "defstyle") (sf-defstyle args env)
(= name "defhandler") (sf-defhandler args env)
@@ -192,7 +195,7 @@
(evaluated-args (map (fn (a) (trampoline (eval-expr a env))) args)))
(cond
;; Native callable (primitive function)
(and (callable? f) (not (lambda? f)) (not (component? f)))
(and (callable? f) (not (lambda? f)) (not (component? f)) (not (island? f)))
(apply f evaluated-args)
;; Lambda
@@ -203,6 +206,10 @@
(component? f)
(call-component f args env)
;; Island (reactive component) — same calling convention
(island? f)
(call-component f args env)
:else (error (str "Not callable: " (inspect f)))))))
@@ -543,6 +550,24 @@
(list params has-children))))
(define sf-defisland
(fn (args env)
;; (defisland ~name (params) body)
;; Like defcomp but creates an island (reactive component).
;; Islands have the same calling convention as components but
;; render with a reactive context on the client.
(let ((name-sym (first args))
(params-raw (nth args 1))
(body (last args))
(comp-name (strip-prefix (symbol-name name-sym) "~"))
(parsed (parse-comp-params params-raw))
(params (first parsed))
(has-children (nth parsed 1)))
(let ((island (make-island comp-name params has-children body env)))
(env-set! env (symbol-name name-sym) island)
island))))
(define sf-defmacro
(fn (args env)
(let ((name-sym (first args))
@@ -903,6 +928,11 @@
;; (component-closure c) → env
;; (component-has-children? c) → boolean
;; (component-affinity c) → "auto" | "client" | "server"
;;
;; (make-island name params has-children body env) → Island
;; (island? x) → boolean
;; ;; Islands reuse component accessors: component-params, component-body, etc.
;;
;; (macro-params m) → list of strings
;; (macro-rest-param m) → string or nil
;; (macro-body m) → expr
@@ -915,6 +945,7 @@
;; (callable? x) → boolean (native function or lambda)
;; (lambda? x) → boolean
;; (component? x) → boolean
;; (island? x) → boolean
;; (macro? x) → boolean
;; (primitive? name) → boolean (is name a registered primitive?)
;; (get-primitive name) → function