etaf/etaf-component.el
Kinneyzhang 0185c4e05a feat: establish unified etaf view foundation
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.
2026-08-05 00:36:52 +08:00

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