Implement the P0 View grammar, expr bridge, stateless view Components, and Ebox mount path in a new independent package. Include bilingual architecture and implementation documents plus contract tests.
131 lines
4.8 KiB
EmacsLisp
131 lines
4.8 KiB
EmacsLisp
;;; etaf-component.el --- Stateless ETAF Component definitions -*- lexical-binding: t; -*-
|
|
|
|
;; SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
;;; Commentary:
|
|
|
|
;; This first implementation slice owns only the beginner `:view' Component
|
|
;; form. Stateful `:setup', Context, Behaviors, and lifecycle will be added
|
|
;; behind the same Component boundary in later milestones; they are rejected
|
|
;; here instead of silently receiving different semantics.
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-lib)
|
|
(require 'etaf-view)
|
|
|
|
(define-error 'etaf-component-definition-error
|
|
"Invalid ETAF Component definition"
|
|
'etaf-view-error)
|
|
|
|
(defun etaf--component-definition-error (format-string &rest arguments)
|
|
"Signal a Component definition error formatted from FORMAT-STRING."
|
|
(signal 'etaf-component-definition-error
|
|
(list (apply #'format format-string arguments))))
|
|
|
|
(defun etaf--parse-component-props (arguments)
|
|
"Return prop names from Component argument declaration ARGUMENTS."
|
|
(unless (proper-list-p arguments)
|
|
(etaf--component-definition-error
|
|
"Component prop declaration must be a proper list: %S" arguments))
|
|
(let ((tail arguments)
|
|
prop-names)
|
|
(when tail
|
|
(unless (eq (pop tail) '&key)
|
|
(etaf--component-definition-error
|
|
"Component props must use (&key NAME ...), got %S" arguments)))
|
|
(dolist (entry tail)
|
|
(unless (symbolp entry)
|
|
(etaf--component-definition-error
|
|
"Component props must be symbols without defaults: %S" entry))
|
|
(when (keywordp entry)
|
|
(etaf--component-definition-error
|
|
"Component prop names must not be keywords: %S" entry))
|
|
(when (memq entry '(nil t))
|
|
(etaf--component-definition-error
|
|
"Component prop name cannot be a constant: %S" entry))
|
|
(when (memq entry prop-names)
|
|
(etaf--component-definition-error
|
|
"Duplicate Component prop: %S" entry))
|
|
(push entry prop-names))
|
|
(nreverse prop-names)))
|
|
|
|
(defun etaf--component-view-variable-bindings (props)
|
|
"Return lexical bindings for Component PROP names."
|
|
(mapcar (lambda (prop)
|
|
`(,prop
|
|
(plist-get etaf--component-props
|
|
,(etaf--component-prop-key prop))))
|
|
props))
|
|
|
|
;;;###autoload
|
|
(defmacro etaf-define-component (name arguments &rest clauses)
|
|
"Define stateless Component NAME with ARGUMENTS and CLAUSES.
|
|
|
|
The implemented P0 form is:
|
|
|
|
(etaf-define-component NAME (&key PROPS)
|
|
:view VIEW)
|
|
|
|
VIEW is already structural View syntax and does not need quote. Attribute
|
|
values inside VIEW are ordinary Elisp expressions. `:setup', `:styles',
|
|
slots, and lifecycle are intentionally rejected until their owning runtime
|
|
contracts are implemented."
|
|
(declare (indent 2) (debug defun))
|
|
(unless (symbolp name)
|
|
(etaf--component-definition-error
|
|
"Component name must be a symbol: %S" name))
|
|
(let ((docstring (when (stringp (car clauses)) (pop clauses)))
|
|
view-form
|
|
saw-view)
|
|
(while clauses
|
|
(let ((keyword (pop clauses)))
|
|
(unless (keywordp keyword)
|
|
(etaf--component-definition-error
|
|
"Expected a Component definition keyword, got %S" keyword))
|
|
(pcase keyword
|
|
(:view
|
|
(when saw-view
|
|
(etaf--component-definition-error
|
|
"Component %S has duplicate :view" name))
|
|
(unless clauses
|
|
(etaf--component-definition-error
|
|
"Component %S :view has no View" name))
|
|
(setq view-form (pop clauses)
|
|
saw-view t))
|
|
(:setup
|
|
(etaf--component-definition-error
|
|
"Component %S :setup is reserved for the stateful milestone"
|
|
name))
|
|
(:styles
|
|
(etaf--component-definition-error
|
|
"Component %S :styles is reserved for the style milestone"
|
|
name))
|
|
(_
|
|
(etaf--component-definition-error
|
|
"Unknown Component definition keyword %S" keyword)))))
|
|
(unless saw-view
|
|
(etaf--component-definition-error
|
|
"Component %S requires exactly one :view clause" name))
|
|
(let* ((props (etaf--parse-component-props arguments))
|
|
(definition-symbol
|
|
(intern (format "%s--etaf-component-definition" name)))
|
|
(render-lambda
|
|
`(lambda (etaf--component-props _etaf--children)
|
|
(let ,(etaf--component-view-variable-bindings props)
|
|
,(etaf--compile-view-form view-form)))))
|
|
`(progn
|
|
(defconst ,definition-symbol
|
|
(etaf--component-spec-create
|
|
:name ',name
|
|
:props ',props
|
|
:render ,render-lambda)
|
|
,(or docstring
|
|
(format "Definition of ETAF Component `%s'." name)))
|
|
(etaf--register-component ',name ,definition-symbol)
|
|
',name))))
|
|
|
|
(provide 'etaf-component)
|
|
|
|
;;; etaf-component.el ends here
|