-
Notifications
You must be signed in to change notification settings - Fork 27
/
deftag.lisp
102 lines (92 loc) · 3.69 KB
/
deftag.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
(in-package #:spinneret)
(in-nomine:define-namespace deftag
:name-type symbol
:value-type (function (list) list))
(defun parse-deftag-body (body)
(multiple-value-bind (name attrs body)
(tag-parts (cons :tag body))
(declare (ignore name))
(multiple-value-bind (body decls)
(parse-body body)
(values body attrs decls))))
(defun splice-allow-other-keys (lambda-list)
(let ((keys (member '&key lambda-list)))
(if (null keys)
lambda-list
(let ((end-of-keys (member-if (lambda (x)
(and (symbolp x)
(starts-with-subseq "&" (string x))))
(cdr keys))))
(append (ldiff lambda-list keys)
(ldiff keys end-of-keys)
'(&allow-other-keys)
end-of-keys)))))
(defun allow-other-keys (lambda-list)
(cond ((null lambda-list)
'(&key &allow-other-keys))
((find '&allow-other-keys lambda-list)
lambda-list)
(t (splice-allow-other-keys lambda-list))))
(defun extract-lambda-list-keywords (lambda-list)
"Get the actual keywords from the lambda list."
(mapcar #'caar (nth-value 3 (parse-ordinary-lambda-list lambda-list))))
(defun lambda-list-vars (lambda-list)
(multiple-value-bind (req opt rest key aok aux)
(parse-ordinary-lambda-list lambda-list)
(declare (ignore aok))
(remove nil
(append req
(mapcar #'car opt)
(list rest)
(mapcar #'cadar key)
(mapcar #'car aux)))))
(defmacro deftag/keyword (name (body attrs-var &rest ll) &body tag)
"Base case for a deftag that does not define a macro."
(when (eql attrs-var '&key)
(error "Missing attributes variable."))
(mvlet* ((tag decls docstring
(parse-body tag :documentation t))
;; Remove the keywords from the attributes.
(attrs
`(remove-from-plist ,attrs-var ,@(extract-lambda-list-keywords ll))))
(with-gensyms (tmp-body)
`(progn
(eval-always
(setf (symbol-deftag ',name)
(lambda (,tmp-body)
,docstring
(multiple-value-bind (,tmp-body ,attrs-var)
(parse-deftag-body ,tmp-body)
(destructuring-bind ,(if (symbolp body) `(&rest ,body) body)
,tmp-body
,@decls
;; Bind the keywords to the provided arguments.
(destructuring-bind ,(allow-other-keys ll)
,attrs-var
(let ((,attrs-var ,attrs))
(list 'with-html ,@tag))))))))
',name))))
(defmacro deftag/macro (name (body attrs-var &rest ll) &body tag)
"A deftag that also defined a macro."
(mvlet* ((tag decls docstring
(parse-body tag :documentation t)))
(declare (ignore decls))
`(progn
(deftag/keyword ,name (,body ,attrs-var ,@ll) ,@tag)
(defmacro ,name (&body ,body)
,@(and docstring (list docstring))
(deftag-expand ',name ,body :error t)))))
(defmacro deftag (name (body attrs-var &rest ll) &body tag)
"Define NAME as a tag.
If NAME is not a keyword, it will also be defined as a macro with an
implicit `with-html'."
(let ((definer
(if (keywordp name)
'deftag/keyword
'deftag/macro)))
`(,definer ,name (,body ,attrs-var ,@ll) ,@tag)))
(defun deftag-expand (element args &key error)
(cond ((deftag-boundp element)
(funcall (symbol-deftag element) args))
(error (symbol-deftag element))
(t (cons element args))))