-
Notifications
You must be signed in to change notification settings - Fork 42
/
definitions.lisp
245 lines (207 loc) · 8.96 KB
/
definitions.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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
(in-package :serapeum)
;;; For internal use.
(defstruct-read-only (unbound (:constructor unbound (var)))
"Placeholder for an unbound variable."
(var :type symbol))
(defmethod make-load-form ((self unbound) &optional env)
(make-load-form-saving-slots self
:slot-names '(var)
:environment env))
;;;# Lexical globals
;;; `def' and `defconst' are both applications of the same idea: using
;;; symbol macros to get lexical behavior from global variables (via
;;; `defparameter' and `defconstant', respectively).
(defmacro def (var &body (&optional val documentation))
"The famous \"deflex\".
Define a top level (global) lexical VAR with initial value VAL,
which is assigned unconditionally as with DEFPARAMETER. If a DOC
string is provided, it is attached to both the name |VAR| and the name
*STORAGE-FOR-DEFLEX-VAR-|VAR|* as a documentation string of kind
'VARIABLE. The new VAR will have lexical scope and thus may be
shadowed by LET bindings without affecting its dynamic (global) value.
The original `deflex' is due to Rob Warnock.
This version of `deflex' differs from the original in the following ways:
- It is possible for VAL to close over VAR.
- On implementations that support it (SBCL, CCL, and LispWorks, at the
moment) this version creates a backing variable that is \"global\" or
\"static\", so there is not just a change in semantics, but also a
gain in efficiency.
- If VAR is a list that starts with `values`, each element is treated as
a separate variable and initialized as if by `(setf (values VAR...)
VAL)`."
;; From the ISO-COMPATIBILITY issue writeup: "DEFINE-SYMBOL-MACRO
;; can be used to define global lexicals, by having a global lexical
;; be a symbol macro that expands into a reference to a globally
;; allocated cell that is not subject to dynamic binding." So not
;; only was this use of `define-symbol-macro' foreseen, it is also a
;; major reason why it was included in CL at all. (The same goes for
;; `defconst' below.)
(ematch var
((list 'values)
`(progn ,val))
((list 'values var)
`(def ,var ,val ,@(unsplice documentation)))
((list* 'values vars)
`(mvdef ,vars ,val ,@(unsplice documentation)))
((and var (type (and symbol (not null))))
(let* ((s0 (symbol-name '#:*storage-for-deflex-var-))
(s1 (symbol-name var))
(s2 (symbol-name '#:*))
(s3 (symbol-package var)) ; BUGFIX [see above]
(backing-var (intern (concatenate 'string s0 s1 s2) s3)))
;; Note: The DEFINE-SYMBOL-MACRO must precede VAL so VAL can close
;; over VAR.
`(progn
(define-symbol-macro ,var ,backing-var)
(global-vars:define-global-parameter* ,backing-var ,val
,@(unsplice documentation))
',var)))))
(defmacro mvdef (vars &body (&optional expr documentation))
`(progn
,@(loop for var in vars
collect `(def ,var (unbound ',var) ,@(unsplice documentation)))
(setf (values ,@vars) ,expr)
',vars))
(defmacro define-values (values &body (expr))
"Like `def', but for multiple values.
Each variable in VALUES is given a global, lexical binding, as with
`def', then set all at once, as with `multiple-value-setq'."
`(progn
,@(loop for v in values collect `(def ,v nil))
(setf (values ,@values) ,expr)
(values ,@(loop for v in values collect `(quote ,v)))))
(defun same-literal-p (x y)
"A predicate that compares whether X and Y have the same literal
representation."
(or (equal x y)
;; Crude, but reliable.
(handler-case
;; "If ‘*read-eval*’ is false and ‘*print-readably*’ is true,
;; any such method that would output a reference to the
;; "‘#.’" reader macro will either output something else or
;; will signal an error (as described above)."
(let ((*read-eval* t))
(string= (write-to-string x :readably t)
(write-to-string y :readably t)))
(print-not-readable () nil))))
(defmacro defconst (symbol init &optional docstring)
"Define a constant, lexically.
`defconst' defines a constant using a strategy similar to `def', so
you don’t have to +cage+ your constants.
The constant is only redefined on re-evaluation if INIT has a
different literal representation than the old value.
A constant defined with `defconst' is guaranteed to be available as
soon as it has been defined (for example, for use with reader macros
later in the same file). This is not guaranteed to be portably true
for `defconstant'.
The name is from Emacs Lisp."
(let ((backing-var (symbolicate '#:+storage-for-deflex-var- symbol '+)))
`(eval-when (:compile-toplevel :load-toplevel :execute)
(define-constant ,backing-var ,init
:test 'same-literal-p
:documentation ,docstring)
(define-symbol-macro ,symbol ,backing-var))))
;;;# Emacs-alikes
;;;## `defsubst'
;;; The name is of course by way of Emacs, although it's actually much
;;; older.
(defmacro defsubst (name params &body body)
"Define an inline function.
(defsubst fn ...)
≡ (declaim (inline fn))
(defun fn ...)
The advantage of a separate defining form for inline functions is that
you can't forget to declaim the function inline before defining it –
without which it may not actually end up being inlined.
From Emacs and other ancient Lisps."
`(progn
(declaim (inline ,name))
(defun ,name ,params
,@body)))
;;;## `defalias'
;;; In Emacs `defalias' is really just `fset': both the name and the
;;; value are evaluated. Here we only evaluate the value.
(defmacro defalias (alias &body (def &optional docstring)
&environment env)
"Define a value as a top-level function.
(defalias string-gensym (compose #'gensym #'string))
Like (setf (fdefinition ALIAS) DEF), but with a place to put
documentation and some niceties to placate the compiler.
Note that a function defined with `defalias' is declared `notinline'.
This is a matter of semantics: before we can assign to the function,
we must make it assignable (which is what `notinline' means).
Name from Emacs Lisp."
`(progn
(declaim (notinline ,alias))
,(multiple-value-bind (env decls lambda)
(let-over-lambda def env)
;; If we can expand DEF into a lambda (possibly with some
;; variable bindings around it) then splice that lambda in
;; directly as the body of the function. (This is the same
;; function we use to resolve lambdas in `fbind'.)
(if lambda
(ematch lambda
((list* (and _ (eql 'lambda)) args body)
`(let ,env
(declare ,@decls)
(defun ,alias ,args
,@(unsplice docstring)
,@body))))
(with-unique-names (temp)
`(let ((,temp (ensure-function ,def)))
(declare (type function ,temp))
;; Give the function a temporary definition at
;; compile time so the compiler doesn't complain
;; about its being undefined.
(defun ,alias (&rest args)
(apply ,temp args))
(setf (fdefinition ',alias) ,temp)
,@(unsplice
(and docstring
`(setf (documentation ',alias 'function) ,docstring)))))))))
;;;# Etc
(defmacro defplace (name args &body body)
"Define NAME and (SETF NAME) in one go.
BODY is a list of forms, starting with an optional docstring. The last
form in BODY, however, must be a single, setf-able expression."
(match body
((list (type string))
(error "No form for ~s: ~a" 'defplace body))
((last (cons (type string) nil) 1)
`(defplace ,name ,args ,@(reverse body)))
(otherwise
(with-gensyms (value)
`(progn
(defun ,name ,args
,@body)
(defun (setf ,name) (,value ,@args)
,@(butlast body)
(setf ,(lastcar body) ,value)))))))
(defmacro defvar-unbound (var &body (docstring))
"Define VAR as if by `defvar' with no init form, and set DOCSTRING
as its documentation.
I believe the name comes from Edi Weitz."
`(progn
(defvar ,var)
(setf (documentation ',var 'variable) ,docstring)
',var))
(defmacro defparameter-unbound (var &body (docstring))
"Like `defvar-unbound', but ensures VAR is unbound when evaluated."
`(progn
(defvar ,var)
(makunbound ',var)
(setf (documentation ',var 'variable) ,docstring)
',var))
(defmacro defloop (name args &body body)
"Define a function, ensuring proper tail recursion.
This is entirely equivalent to `defun' over `nlet'."
(multiple-value-bind (body decls docstring)
(parse-body body :documentation t)
(let ((vars (lambda-list-vars args)))
`(defun ,name ,args
,@(unsplice docstring)
,@decls
(nlet ,name ,(mapcar #'list vars vars)
(flet ((,name ,args (,name ,@vars)))
(declare (inline ,name))
,@body))))))