This repository was archived by the owner on Mar 7, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathlet-plus.lisp
332 lines (293 loc) · 14.6 KB
/
let-plus.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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
;;;; let-plus.lisp
(in-package #:let-plus)
;;; LET+ recognizes three general kinds of syntax for accessing elements in
;;; some structure (in the abstract sense):
;;;
;;; 1. "slots", of the form (VARIABLE &optional (SLOT VARIABLE)) SLOT is used
;;; in the general sense, it can also be an accessor. This is similar to
;;; the syntax of WITH-SLOTS etc.
;;;
;;; 2. "entries", of the form (VARIABLE &optional (KEY VARIABLE) DEFAULT),
;;; which allows a default value. This is used for hash tables, property
;;; lists, etc. If KEY is NIL, VARIABLE is used instead, if another
;;; symbol, it is quoted.
;;;
;;; 3. array-like reference (VARIABLE &rest SUBSCRIPTS). This is used for
;;; array elements.
;;;
;;; If a single symbol is given, it is used as a variable for entries and
;;; slots.
;;; Ignored variables
;;;
;;; The preferred method is expanding into LET+ forms which handle ignored
;;; values automatically -- LET+ just ignores these variables. Use
;;; REPLACE-IGNORED only when this is not feasible or desirable (eg using
;;; destructuring provided by CL).
(defun ignored? (symbol)
"Return a boolean determining if a variable is to be ignored.
NOTE: It is unlikely that you need to used this function, see the note above its definition."
(eq symbol '&ign))
(defun replace-ignored (tree)
"Replace ignored variables in TREE with a gensym, return a list of these as the second value.
NOTE: It is unlikely that you need to used this function, see the note above its definition."
(let (ignored)
(labels ((traverse (tree)
(if (atom tree)
(if (ignored? tree)
(aprog1 (gensym)
(push it ignored))
tree)
(cons (traverse (car tree))
(awhen (cdr tree) (traverse it))))))
(values (traverse tree) (nreverse ignored)))))
;;; LET+ uses generic functions for expansion. They are dispatched on the
;;; FORM, and further on the first element if it is a list. LET+-EXPANSION
;;; should wrap BODY in the desired forms, implementing the expansion. The
;;; recursive expansion of multiple forms is done by the LET+ macro.
;;;
;;; LET+ forms start with & (except for those expanding into LET(*) and
;;; DESTRUCTURING-BIND), although this convention is not enfored.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun &-symbol? (symbol)
"Test whether the symbol's name starts with a & character."
(char= (aref (symbol-name symbol) 0) #\&)))
(defgeneric let+-expansion (form value body)
(:documentation "Return an expansion for a LET+ form.")
(:method (form value body)
(declare (ignore value body))
(error "LET+ could not recognize ~A." form))
(:method ((variable null) value body)
`(destructuring-bind nil ,value
,@body))
(:method ((variable symbol) value body)
(cond
((ignored? variable) `(progn ,@body))
((&-symbol? variable)
(warn "Possibly left out one level of nesting in LET+ form (~A ~A)."
variable value))
(t `(let ((,variable ,@(when value `(,value))))
,@body))))
(:method ((form list) value body)
(let+-expansion-for-list (first form) (rest form) value body)))
(defgeneric let+-expansion-for-list (first rest value body)
(:documentation "LET+-EXPANSION calls this for lists, see the latter for semantics of returned values.")
(:method (first rest value body)
;; forms not recognized as anything else are destructured
(when (and (symbolp first) (not (ignored? first)) (&-symbol? first)
(not (find first lambda-list-keywords)))
(warn "~A looks like a LET+ keyword, but it has no expansion method defined. Treating it as a lambda list." first))
(let ((form (cons first rest)))
(multiple-value-bind (form ignored) (replace-ignored form)
`(destructuring-bind ,form ,value
(declare (ignore ,@ignored))
,@body)))))
(defmacro let+ (bindings &body body)
"Destructuring bindings. See the documentation of the LET-PLUS library. Most accepted forms start with &."
(labels ((expand (bindings)
(destructuring-bind (binding &rest other-bindings) bindings
(destructuring-bind (form &optional value)
(ensure-list binding)
(let+-expansion form value (aif other-bindings
(list (expand it))
body))))))
(if bindings
(expand bindings)
`(progn ,@body))))
(defmacro define-let+-expansion ((name arguments &key
(value-var 'value)
(body-var 'body)
(uses-value? t)
(once-only? uses-value?))
&body body)
"Define an expansion for LET+ forms which are lists, starting with NAME. ARGUMENTS is destructured if a list. A placeholder macro is defined with NAME, using DOCSTRING and ARGUMENTS. The value form is bound to VALUE-VAR (wrapped in ONCE-ONLY when ONCE-ONLY?), while the body is bound to BODY-VAR. USES-VALUE? determines if the form uses a value, and generates the appropriate checks."
(let ((arguments-var (gensym "ARGUMENTS"))
(arguments (if (listp arguments)
arguments
`(&rest ,arguments)))
(whole (gensym "WHOLE")))
(multiple-value-bind (remaining-forms declarations docstring)
(parse-body body :documentation t)
(sunless docstring (setf it (format nil "LET+ form ~A." name)))
`(progn
(defmacro ,name (&whole ,whole ,@arguments)
,docstring
(declare (ignore
,@(remove-if (lambda (symbol)
(or (not symbol)
(not (symbolp symbol))
(keywordp symbol)
(find symbol lambda-list-keywords)
(&-symbol? symbol)))
(flatten arguments))))
,@declarations
,whole)
(defmethod let+-expansion-for-list ((first (eql ',name))
,arguments-var ,value-var
,body-var)
,(if uses-value?
`(assert ,value-var () "Missing value form in ~A." ',name)
`(assert (not ,value-var) ()
"~A forms don't take a value." ',name))
,(let ((core `(destructuring-bind ,arguments ,arguments-var
,@declarations
,@remaining-forms)))
(if once-only? ; basically once-only, with ignorable value
(with-unique-names (value-once-var)
`(let ((,value-once-var (gensym "VALUE")))
`(let ((,,value-once-var ,,value-var))
(declare (ignorable ,,value-once-var))
,(let ((,value-var ,value-once-var))
,core))))
core)))))))
;;; Definitions for particular LET+ forms.
;;;
;;; When both read only and read/write forms make sense, the former should
;;; have the suffix -r/o and the latter should be without the suffix in order
;;; to maintain a consistent naming scheme.
;;; helper functions
(defun expand-slot-forms (slots accessor-generator)
"Return a list of expanded bindings, calling (ACCESSOR-GENERATOR KEY)"
(let (bindings)
(loop for entry :in slots do
(destructuring-bind (variable &optional (key variable))
(ensure-list entry)
(when variable
(push `(,variable ,(funcall accessor-generator key)) bindings))))
(nreverse bindings)))
(defun expand-entry-forms (entries accessor-generator)
"Return a list of expanded bindings from ENTRIES, calling (ACESSOR-GENERATOR KEY DEFAULT). Each entry is (VARIABLE &OPTIONAL KEY DEFAULT). When KEY is NIL, VARIABLE is used."
(mapcar (lambda (entry)
(destructuring-bind (variable &optional key default)
(ensure-list entry)
`(,variable ,(funcall accessor-generator
(typecase key
(null `',variable)
(symbol `',key)
(t key))
default))))
entries))
(defun expand-array-elements (value array-elements &optional (accessor 'aref))
"Expand a list of (BINDING &REST SUBSCRIPTS) forms to a list of bindings of the form (ACCESSOR VALUE SUBSCRIPTS)."
(mapcar (lambda (array-element)
`(,(first array-element)
(,accessor ,value ,@(rest array-element))))
array-elements))
(define-let+-expansion (&accessors accessors)
"LET+ form, similar to WITH-ACCESSORS."
`(symbol-macrolet ,(expand-slot-forms accessors (lambda (accessor)
`(,accessor ,value)))
,@body))
(define-let+-expansion (&accessors-r/o accessors)
"LET+ form, similar to WITH-ACCESSORS, but read-only."
`(let+ ,(expand-slot-forms accessors (lambda (accessor)
`(,accessor ,value)))
,@body))
(define-let+-expansion (&slots slots :once-only? nil)
"LET+ form, similar to WITH-SLOTS."
`(with-slots ,slots ,value
,@body))
(define-let+-expansion (&slots-r/o slots)
"LET+ form, similar to WITH-SLOTS but read-only."
`(let+ ,(expand-slot-forms slots
(lambda (slot) `(slot-value ,value ',slot)))
,@body))
(define-let+-expansion (&structure (conc-name &rest slots))
"LET+ form for slots of a structure, with accessors generated using CONC-NAME."
(check-type conc-name symbol)
`(symbol-macrolet
,(expand-slot-forms slots
(lambda (slot) `(,(symbolicate conc-name slot)
,value)))
,@body))
(define-let+-expansion (&structure-r/o (conc-name &rest slots))
"LET+ form for slots of a structure, with accessors generated using CONC-NAME. Read-only version."
(check-type conc-name symbol)
`(let+ ,(expand-slot-forms slots
(lambda (slot)
`(,(symbolicate conc-name slot) ,value)))
,@body))
(define-let+-expansion (&values values :once-only? nil)
"LET+ form for multiple values."
(let ((values-and-temps (mapcar (lambda (v) (list v (gensym))) values)))
`(multiple-value-bind ,(mapcar #'second values-and-temps) ,value
(declare (ignore ,@(loop for (v g) in values-and-temps
when (ignored? v)
collect g)))
(let+ ,(remove-if (compose #'ignored? #'car) values-and-temps)
,@body))))
(defmethod let+-expansion ((array array) value body)
"LET+ expansion for mapping array elements to variables."
(let (bindings
(value-var (gensym "VALUE")))
(dotimes (row-major-index (array-total-size array))
(let ((variable (row-major-aref array row-major-index)))
(unless (ignored? variable)
(push `(,variable
(row-major-aref ,value-var ,row-major-index))
bindings))))
`(let ((,value-var ,value))
(assert (equal (array-dimensions ,value-var)
',(array-dimensions array)))
(let+ ,(nreverse bindings)
,@body))))
(define-let+-expansion (&array-elements array-elements)
"LET+ form, mapping (variable &rest subscripts) specifications to array-elements. VARIABLE is an accessor, which can be used for reading and writing array elements."
`(symbol-macrolet ,(expand-array-elements value array-elements)
,@body))
(define-let+-expansion (&array-elements-r/o array-elements)
"LET+ form, mapping (variable &rest subscripts) specifications to array-elements. Read-only accessor, values assigned to VARIABLEs."
(once-only (value)
`(let+ ,(expand-array-elements value array-elements)
,@body)))
(define-let+-expansion (&flet (function-name lambda-list
&body function-body)
:uses-value? nil)
"LET+ form for function definitions. Expands into an FLET."
`(flet ((,function-name ,lambda-list ,@function-body))
,@body))
(define-let+-expansion (&labels (function-name lambda-list
&body function-body)
:uses-value? nil)
"LET+ form for function definitions. Expands into an LABELS, thus allowing recursive functions."
(if (typep (first body) '(cons (eql cl:labels)))
(destructuring-bind (bindings &rest first-body) (rest (first body))
`(labels ((,function-name ,lambda-list ,@function-body)
,@bindings)
,@first-body
,@(rest body)))
`(labels ((,function-name ,lambda-list ,@function-body))
,@body)))
(define-let+-expansion (¯olet (macro-name lambda-list &body macro-body)
:uses-value? nil)
"LET+ form for local macro definitions. Expands into an MACROLET."
`(macrolet ((,macro-name ,lambda-list ,@macro-body))
,@body))
(define-let+-expansion (&symbol-macrolet (symbol expansion) :uses-value? nil)
"LET+ form for local symbol macros. Expands into a SYMBOL-MACROLET."
`(symbol-macrolet ((,symbol ,expansion))
,@body))
(define-let+-expansion (&plist entries)
"LET+ form for property lists. Each entry is (variable &optional key default)."
`(symbol-macrolet
,(expand-entry-forms entries
(lambda (key default)
`(getf ,value ,key ,default)))
,@body))
(define-let+-expansion (&plist-r/o entries)
"LET+ form for property lists, read only version. Each entry is (variable &optional key default)."
`(let* ,(expand-entry-forms entries
(lambda (key default)
`(getf ,value ,key ,default)))
,@body))
(define-let+-expansion (&hash-table entries)
"LET+ form for hash tables. Each entry is (variable &optional key default)."
`(symbol-macrolet
,(expand-entry-forms entries
(lambda (key default)
`(gethash ,key ,value ,default)))
,@body))
(define-let+-expansion (&hash-table-r/o entries)
"LET+ form for hash tables. Each entry is (variable &optional key default). Read only version."
`(let+ ,(expand-entry-forms entries
(lambda (key default) `(gethash ,key ,value ,default)))
,@body))