-
Notifications
You must be signed in to change notification settings - Fork 16
/
define-api.lisp
63 lines (54 loc) · 2.88 KB
/
define-api.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
(in-package :named-readtables)
(defmacro define-api (name lambda-list type-list &body body)
(flet ((parse-type-list (type-list)
(let ((pos (position '=> type-list)))
(assert pos () "You forgot to specify return type (`=>' missing.)")
(values (subseq type-list 0 pos)
`(values ,@(nthcdr (1+ pos) type-list) &optional)))))
(multiple-value-bind (body decls docstring)
(parse-body body :documentation t :whole `(define-api ,name))
(multiple-value-bind (arg-typespec value-typespec)
(parse-type-list type-list)
(multiple-value-bind (reqs opts rest keys)
(parse-ordinary-lambda-list lambda-list)
(declare (ignorable reqs opts rest keys))
`(progn
(declaim (ftype (function ,arg-typespec ,value-typespec) ,name))
(locally
;;; Muffle the annoying "&OPTIONAL and &KEY found in
;;; the same lambda list" style-warning
#+sbcl (declare (sb-ext:muffle-conditions style-warning))
(defun ,name ,lambda-list
,docstring
#+sbcl (declare (sb-ext:unmuffle-conditions style-warning))
,@decls
;; SBCL will interpret the ftype declaration as
;; assertion and will insert type checks for us.
#-sbcl
(progn
;; CHECK-TYPE required parameters
,@(loop for req-arg in reqs
for req-type = (pop type-list)
do (assert req-type)
collect `(check-type ,req-arg ,req-type))
;; CHECK-TYPE optional parameters
,@(loop initially (assert (or (null opts)
(eq (pop type-list) '&optional)))
for (opt-arg . nil) in opts
for opt-type = (pop type-list)
do (assert opt-type)
collect `(check-type ,opt-arg ,opt-type))
;; CHECK-TYPE rest parameter
,@(when rest
(assert (eq (pop type-list) '&rest))
(let ((rest-type (pop type-list)))
(assert rest-type)
`((dolist (x ,rest)
(check-type x ,rest-type)))))
;; CHECK-TYPE key parameters
,@(loop initially (assert (or (null keys)
(eq (pop type-list) '&key)))
for ((keyword key-arg) . nil) in keys
for (nil key-type) = (find keyword type-list :key #'car)
collect `(check-type ,key-arg ,key-type)))
,@body))))))))