Files
rose-ash/shared/sx/ref/forms.sx
giles d076fc1465 Spec server definition forms (defhandler/defquery/defaction/defpage) in forms.sx
Previously defhandler routed to sf-define which tried to evaluate
(&key ...) params as expressions. Now each form has its own spec
with parse-key-params and platform constructors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:36:56 +00:00

119 lines
4.7 KiB
Plaintext

;; ==========================================================================
;; forms.sx — Server-side definition forms
;;
;; Platform-specific special forms for declaring handlers, pages, queries,
;; and actions. These parse &key parameter lists and create typed definition
;; objects that the server runtime uses for routing and execution.
;;
;; When SX moves to isomorphic execution, these forms will have different
;; platform bindings on client vs server. The spec stays the same — only
;; the constructors (make-handler-def, make-query-def, etc.) change.
;;
;; Platform functions required:
;; make-handler-def(name, params, body, env) → HandlerDef
;; make-query-def(name, params, doc, body, env) → QueryDef
;; make-action-def(name, params, doc, body, env) → ActionDef
;; make-page-def(name, slots, env) → PageDef
;; ==========================================================================
;; --------------------------------------------------------------------------
;; Shared: parse (&key param1 param2 ...) → list of param name strings
;; --------------------------------------------------------------------------
(define parse-key-params
(fn (params-expr)
(let ((params (list))
(in-key false))
(for-each
(fn (p)
(when (= (type-of p) "symbol")
(let ((name (symbol-name p)))
(cond
(= name "&key") (set! in-key true)
in-key (append! params name)
:else (append! params name)))))
params-expr)
params)))
;; --------------------------------------------------------------------------
;; defhandler — (defhandler name (&key param...) body)
;; --------------------------------------------------------------------------
(define sf-defhandler
(fn (args env)
(let ((name-sym (first args))
(params-raw (nth args 1))
(body (nth args 2))
(name (symbol-name name-sym))
(params (parse-key-params params-raw)))
(let ((hdef (make-handler-def name params body env)))
(env-set! env (str "handler:" name) hdef)
hdef))))
;; --------------------------------------------------------------------------
;; defquery — (defquery name (&key param...) "docstring" body)
;; --------------------------------------------------------------------------
(define sf-defquery
(fn (args env)
(let ((name-sym (first args))
(params-raw (nth args 1))
(name (symbol-name name-sym))
(params (parse-key-params params-raw))
;; Optional docstring before body
(has-doc (and (>= (len args) 4) (= (type-of (nth args 2)) "string")))
(doc (if has-doc (nth args 2) ""))
(body (if has-doc (nth args 3) (nth args 2))))
(let ((qdef (make-query-def name params doc body env)))
(env-set! env (str "query:" name) qdef)
qdef))))
;; --------------------------------------------------------------------------
;; defaction — (defaction name (&key param...) "docstring" body)
;; --------------------------------------------------------------------------
(define sf-defaction
(fn (args env)
(let ((name-sym (first args))
(params-raw (nth args 1))
(name (symbol-name name-sym))
(params (parse-key-params params-raw))
(has-doc (and (>= (len args) 4) (= (type-of (nth args 2)) "string")))
(doc (if has-doc (nth args 2) ""))
(body (if has-doc (nth args 3) (nth args 2))))
(let ((adef (make-action-def name params doc body env)))
(env-set! env (str "action:" name) adef)
adef))))
;; --------------------------------------------------------------------------
;; defpage — (defpage name :path "/..." :auth :public :content expr ...)
;;
;; Keyword-slot form: all values after the name are :key value pairs.
;; Values are stored as unevaluated AST — resolved at request time.
;; --------------------------------------------------------------------------
(define sf-defpage
(fn (args env)
(let ((name-sym (first args))
(name (symbol-name name-sym))
(slots {}))
;; Parse keyword slots from remaining args
(let ((i 1)
(max-i (len args)))
(for-each
(fn (idx)
(when (and (< idx max-i)
(= (type-of (nth args idx)) "keyword"))
(when (< (+ idx 1) max-i)
(dict-set! slots (keyword-name (nth args idx))
(nth args (+ idx 1))))))
(range 1 max-i 2)))
(let ((pdef (make-page-def name slots env)))
(env-set! env (str "page:" name) pdef)
pdef))))