OCaml evaluator for page dispatch + handler aser, 83/83 Playwright tests
Major architectural change: page function dispatch and handler execution
now go through the OCaml kernel instead of the Python bootstrapped evaluator.
OCaml integration:
- Page dispatch: bridge.eval() evaluates SX URL expressions (geography, marshes, etc.)
- Handler aser: bridge.aser() serializes handler responses as SX wire format
- _ensure_components loads all .sx files into OCaml kernel (spec, web adapter, handlers)
- defhandler/defpage registered as no-op special forms so handler files load
- helper IO primitive dispatches to Python page helpers + IO handlers
- ok-raw response format for SX wire format (no double-escaping)
- Natural list serialization in eval (no (list ...) wrapper)
- Clean pipe: _read_until_ok always sends io-response on error
SX adapter (aser):
- scope-emit!/scope-peek aliases to avoid CEK special form conflict
- aser-fragment/aser-call: strings starting with "(" pass through unserialized
- Registered cond-scheme?, is-else-clause?, primitive?, get-primitive in kernel
- random-int, parse-int as kernel primitives; json-encode, into via IO bridge
Handler migration:
- All IO calls converted to (helper "name" args...) pattern
- request-arg, request-form, state-get, state-set!, now, component-source etc.
- Fixed bare (effect ...) in island bodies leaking disposer functions as text
- Fixed lower-case → lower, ~search-results → ~examples/search-results
Reactive islands:
- sx-hydrate-islands called after client-side navigation swap
- force-dispose-islands-in for outerHTML swaps (clears hydration markers)
- clear-processed! platform primitive for re-hydration
Content restructuring:
- Design, event bridge, named stores, phase 2 consolidated into reactive overview
- Marshes split into overview + 5 example sub-pages
- Nav links use sx-get/sx-target for client-side navigation
Playwright test suite (sx/tests/test_demos.py):
- 83 tests covering hypermedia demos, reactive islands, marshes, spec explorer
- Server-side rendering, handler interactions, island hydration, navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,12 +50,12 @@
|
||||
(aser-list expr env))
|
||||
|
||||
;; Spread — emit attrs to nearest element provider
|
||||
"spread" (do (emit! "element-attrs" (spread-attrs expr)) nil)
|
||||
"spread" (do (scope-emit! "element-attrs" (spread-attrs expr)) nil)
|
||||
|
||||
:else expr)))
|
||||
;; Catch spread values from function calls and symbol lookups
|
||||
(if (spread? result)
|
||||
(do (emit! "element-attrs" (spread-attrs result)) nil)
|
||||
(do (scope-emit! "element-attrs" (spread-attrs result)) nil)
|
||||
result))))
|
||||
|
||||
|
||||
@@ -119,18 +119,34 @@
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((result (aser c env)))
|
||||
(if (= (type-of result) "list")
|
||||
(for-each
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(append! parts (serialize item))))
|
||||
result)
|
||||
(when (not (nil? result))
|
||||
(append! parts (serialize result))))))
|
||||
(cond
|
||||
(nil? result) nil
|
||||
;; Serialized SX from aser (tags, components, fragments)
|
||||
;; starts with "(" — use directly without re-quoting
|
||||
(and (= (type-of result) "string")
|
||||
(> (string-length result) 0)
|
||||
(starts-with? result "("))
|
||||
(append! parts result)
|
||||
;; list results (from map etc.)
|
||||
(= (type-of result) "list")
|
||||
(for-each
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(if (and (= (type-of item) "string")
|
||||
(> (string-length item) 0)
|
||||
(starts-with? item "("))
|
||||
(append! parts item)
|
||||
(append! parts (serialize item)))))
|
||||
result)
|
||||
;; Everything else — serialize normally (quotes strings)
|
||||
:else
|
||||
(append! parts (serialize result)))))
|
||||
children)
|
||||
(if (empty? parts)
|
||||
""
|
||||
(str "(<> " (join " " parts) ")")))))
|
||||
(if (= (len parts) 1)
|
||||
(first parts)
|
||||
(str "(<> " (join " " parts) ")"))))))
|
||||
|
||||
|
||||
(define aser-call :effects [render]
|
||||
@@ -160,13 +176,26 @@
|
||||
(set! i (inc i)))
|
||||
(let ((val (aser arg env)))
|
||||
(when (not (nil? val))
|
||||
(if (= (type-of val) "list")
|
||||
(for-each
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(append! child-parts (serialize item))))
|
||||
val)
|
||||
(append! child-parts (serialize val))))
|
||||
(cond
|
||||
;; Serialized SX (tags, components) — use directly
|
||||
(and (= (type-of val) "string")
|
||||
(> (string-length val) 0)
|
||||
(starts-with? val "("))
|
||||
(append! child-parts val)
|
||||
;; List results (from map etc.)
|
||||
(= (type-of val) "list")
|
||||
(for-each
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(if (and (= (type-of item) "string")
|
||||
(> (string-length item) 0)
|
||||
(starts-with? item "("))
|
||||
(append! child-parts item)
|
||||
(append! child-parts (serialize item)))))
|
||||
val)
|
||||
;; Plain values — serialize normally
|
||||
:else
|
||||
(append! child-parts (serialize val))))
|
||||
(set! i (inc i))))))
|
||||
args)
|
||||
;; Collect emitted spread attrs — goes after explicit attrs, before children
|
||||
@@ -178,7 +207,7 @@
|
||||
(append! attr-parts (str ":" k))
|
||||
(append! attr-parts (serialize v))))
|
||||
(keys spread-dict)))
|
||||
(emitted "element-attrs"))
|
||||
(scope-peek "element-attrs"))
|
||||
(scope-pop! "element-attrs")
|
||||
(let ((parts (concat (list name) attr-parts child-parts)))
|
||||
(str "(" (join " " parts) ")")))))
|
||||
|
||||
Reference in New Issue
Block a user