forked from ruricolist/serapeum
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmacro-tools.lisp
872 lines (753 loc) · 32.7 KB
/
macro-tools.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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
(in-package :serapeum)
(in-readtable :fare-quasiquote)
;;;# Basics
;;; Borrowed from the internals of Alexandria.
(defun extract-function-name (x)
"If possible, extract the name from X, a function designator."
(match x
(`(function ,name) name)
(otherwise x)))
(defmacro rebinding-functions (bindings &body body)
"Like `rebinding', but specifically for functions.
The value being rebound will be wrapped in `ensure-function'."
(loop for var in bindings
for name = (gensym)
collect `(,name ,var) into renames
collect ``(,,var (ensure-function ,,name)) into temps
finally (return `(let* ,renames
(with-unique-names ,bindings
`(let (,,@temps)
,,@body))))))
(defun normalize-cases (cases &key (allow-default t))
"Normalize CASES, clauses for a case-like macro.
Return each non-default clause normalized to `(keys . body)', where
keys is a *list* of keys.
Return the default clause as a second value.
If there is more than one default clause, signal an error."
(loop with default
for (keys . body) in cases
if (or (eql keys t)
(eql keys 'otherwise))
do (if default
(error "More than one default case in ~a" cases)
(if allow-default
(setf default body)
(error "Default disallowed in ~a" cases)))
else collect (cons (ensure-list keys) body) into cases-out
finally (return (values cases-out default))))
;;;## `string-gensym'
;;; I got sick of writing `(mapcar (compose #'gensym #'string) ...)'
;;; in every other macro.
(defun string-gensym (x)
"Equivalent to (gensym (string x)).
Generally preferable to calling GENSYM with a string, because it
respects the current read table.
The alternative to writing `(mapcar (compose #'gensym #'string) ...)'
in every other macro."
(gensym (string x)))
;;; These are more consistent with `with-unique-names'.
(defun unique-name (x)
"Alias for `string-gensym'."
(string-gensym x))
(defun make-unique-name-list (length &optional (x 'g))
"Alias for `alexandria:make-gensym-list'."
(make-gensym-list length (string x)))
;;;## `unsplice'
;;; Found this gem in the code for Lparallel.
(declaim (inline unsplice))
(defun unsplice (form)
"If FORM is non-nil, wrap it in a list.
This is useful with ,@ in macros, and with `mapcan'.
E.g., instead of writing:
`(.... ,@(when flag '((code))))
You can write:
`(.... ,@(unsplice (when flag '(code))))
It may be especially helpful when splicing in variables. Instead of
writing:
`(.... ,@(and docstring `(,docstring)))
You can simply write:
`(.... ,@(unsplice docstring))
From Lparallel."
(if form
(list form)
nil))
;;;## `with-thunk'
;;; This is useful, but the name could and should be improved.
(defmacro with-thunk ((var &rest args) &body body)
"A macro-writing macro for the `call-with-' style.
In the `call-with-' style of writing macros, the macro is simply a
syntactic convenience that wraps its body in a thunk and a call to the
function that does the actual work.
(defmacro with-foo (&body body)
`(call-with-foo (lambda () ,@body)))
The `call-with-' style has many advantages. Functions are easier to
write than macros; you can change the behavior of a function without
having to recompile all its callers; functions can be traced, appear
in backtraces, etc.
But meanwhile, all those thunks are being allocated on the heap. Can
we avoid this? Yes, but at a high cost in boilerplate: the closure has
to be given a name (using `flet') so it can be declared
`dynamic-extent'.
(defmacro with-foo (&body body)
(with-gensyms (thunk)
`(flet ((,thunk () ,@body))
(declare (dynamic-extent #',thunk))
(call-with-foo #',thunk))))
`with-thunk' avoids the boilerplate:
(defmacro with-foo (&body body)
(with-thunk (body)
`(call-with-foo ,body)))
It is also possible to construct a \"thunk\" with arguments.
(with-thunk (body foo)
`(call-with-foo ,body))
≡ `(flet ((,thunk (,foo)
,@body))
(declare (dynamic-extent #',thunk))
(call-with-foo #',thunk))
Someday this may have a better name."
(let* ((stack-thunk-prefix (string 'stack-fn-))
(stack-thunk-name
(concatenate 'string
stack-thunk-prefix
(string var)))
(stack-thunk
(gensym stack-thunk-name)))
(with-gensyms (b gargs)
`(let ((,b ,var)
(,var ',stack-thunk)
(,gargs (list ,@args)))
`(flet ((,',stack-thunk ,,gargs
,@,b))
(declare (dynamic-extent (function ,',stack-thunk)))
(symbol-macrolet ((,',stack-thunk (function ,',stack-thunk)))
,,@body))))))
;;;# Expanding macros
;;; Expanding macros, Swank-style. We use `labels' in these
;;; definitions because `nlet' hasn't been defined yet.
(defun expand-macro (form &optional env)
"Like `macroexpand-1', but also expand compiler macros.
From Swank."
(multiple-value-bind (expansion expanded?)
(macroexpand-1 form env)
(if expanded?
(values expansion t)
(compiler-macroexpand-1 form))))
(defun expand-macro-recursively (form &optional env)
"Like `macroexpand', but also expand compiler macros.
From Swank."
(labels ((expand (form count)
(multiple-value-bind (form expanded?)
(expand-macro form env)
(if (not expanded?)
(values form (> count 0))
(expand form (1+ count))))))
(expand form 0)))
;;;# Picking apart declarations
(defun partition-declarations (xs declarations &optional env)
"Split DECLARATIONS into those that do and do not apply to XS.
Return two values, one with each set.
Both sets of declarations are returned in a form that can be spliced
directly into Lisp code:
(locally ,@(partition-declarations vars decls) ...)"
(let ((env2 (parse-declarations declarations env)))
(flet ((build (env)
(build-declarations 'declare env)))
(if (null xs)
(values nil (build env2))
(values
(build (filter-declaration-env env2 :affecting xs))
(build (filter-declaration-env env2 :not-affecting xs)))))))
(defmacro seq-dispatch (seq &body (list-form array-form &optional other-form))
"Efficiently dispatch on the type of SEQ."
(declare (ignorable other-form))
(let* ((list-form
`(with-read-only-vars (,seq)
,list-form))
(array-form
`(with-read-only-vars (,seq)
,array-form))
(list-form
`(let ((,seq (truly-the list ,seq)))
(declare (ignorable ,seq))
,list-form))
(vector-form
;; Create a separate branch for simple vectors.
`(if (simple-vector-p ,seq)
(let ((,seq (truly-the simple-vector ,seq)))
(declare (ignorable ,seq))
(with-vref simple-vector
,array-form))
(let ((,seq (truly-the vector ,seq)))
(declare (ignorable ,seq))
,array-form))))
#+ccl `(ccl::seq-dispatch ,seq ,list-form ,vector-form)
;; Only SBCL and ABCL support extensible sequences right now.
#+(or sbcl abcl)
(once-only (seq)
`(if (listp ,seq)
,list-form
,(if other-form
`(if (arrayp ,seq)
,vector-form
,other-form)
;; Duplicate the array form so that, hopefully, `elt'
;; will be compiled to `aref', &c.
`(if (arrayp ,seq)
,vector-form
,other-form))))
#-(or sbcl abcl ccl)
`(if (listp ,seq) ,list-form ,vector-form)))
(defmacro vector-dispatch (vec &body (bit-vector-form vector-form))
"Efficiently dispatch on the type of VEC.
The first form provides special handling for bit vectors. The second
form provides generic handling for all types of vectors."
`(cond ((typep ,vec 'simple-bit-vector)
(let ((,vec (truly-the simple-bit-vector ,vec)))
(declare (ignorable ,vec))
(with-vref simple-bit-vector
,bit-vector-form)))
((typep ,vec 'bit-vector)
(let ((,vec (truly-the bit-vector ,vec)))
(declare (ignorable ,vec))
(with-vref bit-vector
,bit-vector-form)))
;; Omitted so we can safely nest within with-vector-dispatch.
;; ((typep ,vec 'simple-vector)
;; (let ((,vec (truly-the simple-vector ,vec)))
;; (declare (ignorable ,vec))
;; (with-vref simple-vector
;; ,vector-form)))
(t
(let ((,vec (truly-the vector ,vec)))
(declare (ignorable ,vec))
,vector-form))))
;;; `callf' and `callf2' are inspired by macros used in the
;;; implementation of Emacs Lisp's `cl' package.
(defmacro callf (function place &rest args &environment env)
"Set PLACE to the value of calling FUNCTION on PLACE, with ARGS."
(multiple-value-bind (vars vals stores setter getter)
(get-setf-expansion place env)
`(let* ,(mapcar #'list vars vals)
(multiple-value-bind ,stores
(funcall ,function ,getter ,@args)
,setter))))
(defmacro callf2 (function arg1 place &rest args)
"Like CALLF, but with the place as the second argument."
`(callf (curry ,function ,arg1) ,place ,@args))
(defmacro ensuring-functions (vars &body body)
`(let ,(loop for var in vars
collect `(,var (ensure-function ,var)))
,@body))
(defmacro define-do-macro (name binds &body body)
"Define an iteration macro like `dolist'.
Writing a macro like `dolist' is more complicated than it looks. For
consistency with the rest of CL, you have to do all of the following:
- The entire loop must be surrounded with an implicit `nil' block.
- The body of the loop must be an implicit `tagbody'.
- There must be an optional `return' form which, if given, supplies
the values to return from the loop.
- While this return form is being evaluated, the iteration variables
must be bound to `nil'.
Say you wanted to define a `do-hash' macro that iterates over hash
tables. A full implementation would look like this:
(defmacro do-hash ((key value hash-table &optional return) &body body)
(multiple-value-bind (body decls) (parse-body body)
`(block nil
(maphash (lambda (,key ,value)
,@decls
(tagbody
,@body))
,hash-table)
,(when return
`(let (,key ,value)
,return)))))
Using `define-do-macro' takes care of all of this for you.
(define-do-macro do-hash ((key value hash-table &optional return) &body body)
`(maphash (lambda (,key ,value)
,@body)
,hash-table))"
(let* ((opts (member '&optional (car binds)))
(ret-var (cadr opts))
;; Handle both (key value table) and ((key value) table) as
;; well as ((key &optional value) table).
(iter-vars (mappend (compose #'lambda-list-vars
#'ensure-list)
(butlast (ldiff (car binds) opts))))
(body-var (cadr (member '&body (cdr binds)))))
(unless ret-var
(error "No binding for return form in ~s" (car binds)))
(unless body-var
(error "No binding for body in ~s" binds))
(unless iter-vars
(error "No iteration vars in ~s" binds))
(multiple-value-bind (body decls doc) (parse-body body :documentation t)
`(defmacro ,name ,binds
,@(unsplice doc)
,@decls
(multiple-value-bind (,body-var decls)
(parse-body ,body-var)
(let ((,body-var
`(,@decls
(tagbody ,@,body-var))))
`(block nil
,,@body
,(when ,ret-var
`(let (,,@iter-vars)
(declare (ignorable ,,@iter-vars))
,,ret-var)))))))))
(defmacro define-post-modify-macro (name lambda-list function &optional documentation)
"Like `define-modify-macro', but arranges to return the original value."
(labels ((parse (ll) (parse-ordinary-lambda-list ll))
(pmm-lambda-list (ll)
(multiple-value-bind (req opt rest key aok? aux key?) (parse ll)
(declare (ignore key))
(when (or key? aok?) (error "&key arguments not allowed."))
(when aux (error "&aux arguments not allowed."))
(values (append req (mapcar #'car opt))
rest)))
(expand-pmm (args rest?)
(with-gensyms (ref env)
`(defmacro ,name (,ref ,@lambda-list &environment ,env)
,@(unsplice documentation)
(let ((fn ',function) (rest? ',rest?)
(args (list ,@args)))
(multiple-value-bind (vars vals stores setter getter)
(get-setf-expansion ,ref ,env)
(with-gensyms (temp)
`(let* ,`(,@(mapcar #'list vars vals)
(,temp ,getter)
(,(car stores) (,fn ,temp ,@args ,@(unsplice rest?))))
,setter
,temp))))))))
(multiple-value-bind (args rest?)
(pmm-lambda-list lambda-list)
(expand-pmm args rest?))))
(defun parse-leading-keywords (body)
"Given BODY, return two values: a list of the leading inline keyword
arguments, and the rest of the body.
Inline keywords are like the keyword arguments to individual cases in
`restart-case'."
(labels ((rec (keywords body)
(match body
((list* (and kw (type keyword)) val body)
(rec (list* val kw keywords)
body))
((list (and _ (type keyword)))
(error "Invalid leading keywords in ~s" body))
(otherwise
(values (nreverse keywords) body)))))
(rec nil body)))
(defmacro read-only-var (real-var &optional (name real-var))
(declare (ignore name))
`,real-var)
(defun (setf %read-only-var) (value var)
(declare (ignore value))
(error "~a is read-only in this environment"
var))
(define-setf-expander read-only-var (real-var &optional (name real-var) &environment env)
(warn "~a is read-only in this environment" name)
(get-setf-expansion `(%read-only-var ',real-var) env))
(defun variable-special? (var &optional env)
(if (fboundp 'trivial-cltl2:variable-information)
(eql (funcall 'trivial-cltl2:variable-information var env) :special)
nil))
(defun policy-quality (quality &optional env)
"Query ENV for optimization declaration information.
Returns 1 when the environment cannot be accessed."
(if (fboundp 'trivial-cltl2:declaration-information)
(let ((alist (funcall 'trivial-cltl2:declaration-information 'optimize env)))
(or (second (assoc quality alist))
(error "Unknown policy quality ~s" quality)))
(if (member quality '(speed safety space debug compilation-speed))
1
(error "Unknown policy quality ~s" quality))))
(defun policy> (env policy1 policy2)
(> (policy-quality policy1 env)
(policy-quality policy2 env)))
(defun variable-type (var &optional env)
(if (fboundp 'trivial-cltl2:variable-information)
(let ((alist (nth-value 2 (funcall 'trivial-cltl2:variable-information var env))))
(or (cdr (assoc 'type alist))
t))
t))
(defmacro with-read-only-vars ((&rest vars) &body body &environment env)
"Make VARS read-only within BODY.
That is, within BODY, each var in VARS is bound as a symbol macro,
which expands into a macro whose setf expander, in turn, is defined to
signal a warning at compile time, and an error at run time.
Depending on your Lisp implementation this may or may not do anything,
and may or may not have an effect when used on special variables."
(declare (ignorable env))
(case uiop:*implementation-type*
((:ccl :sbcl :cmu :acl)
;; The use of temps here, while it is ugly and annoying when
;; debugging, is necessary to prevent symbol-macrolet from going
;; into an infinite loop.
(let* ((vars (loop for var in vars
unless (variable-special? var env)
collect var))
(temps
(loop for var in vars
collect (gensym (string var)))))
`(let ,(mapcar #'list temps vars)
(declare (ignorable ,@temps))
(symbol-macrolet ,(loop for var in vars
for temp in temps
collect `(,var (read-only-var ,temp ,var)))
,@body))))
(t
`(locally ,@body))))
(defun expand-read-only-var (var env)
(ematch var
((and var (type symbol))
(let ((exp (macroexpand-1 var env)))
(ematch exp
((list 'read-only-var (and storage (type symbol)) name)
(assert (eql name var))
storage))))))
;;; Macro-writing macro for writing macros like `case'.
;;; TODO Would it be worthwhile to look for clause bodies that are
;;; "the same", and merge them together? Or should we expect that any
;;; reasonable Common Lisp compiler will already do that? SBCL
;;; doesn't. But what would be the right predicate?
(defmacro define-case-macro (name macro-args params &body macro-body)
"Define a macro like `case'.
A case-like macro is one that supports the following syntax:
- A list of keys is treated as matching any key in the list.
- An empty list matches nothing.
- The atoms T or `otherwise' introduce a default clause.
- There can only be one default clause.
- The default clause must come last.
- Any atom besides the empty list, T, or `otherwise' matches itself.
As a consequence of the above, to match against the empty list, T, or
`otherwise', they must be wrapped in a list.
(case x
((nil) \"Matched nil.\")
((t) \"Matched t.\")
((otherwise) \"Matched `otherwise'.\")
(otherwise \"Didn't match anything.\"))
A macro defined using `define-case-macro' can ignore all of the above.
It receives three arguments: the expression, already protected against
multiple evaluation; a normalized list of clauses; and, optionally, a
default clause.
The clauses are normalized as a list of `(key . body)', where each key
is an atom. (That includes nil, T, and `otherwise'.) Nonetheless, each
body passed to the macro will only appear once in the expansion; there
will be no duplicated code.
The body of the default clause is passed separately,
bound to the value of the `:default' keyword in PARAMS.
(define-case-macro my-case (expr &body clauses)
(:default default)
....)
Note that in this case, `default' will be bound to the clause's body
-- a list of forms -- and not to the whole clause. The key of the
default clause is discarded.
If no binding is specified for the default clause, then no default
clause is allowed.
One thing you do still have to consider is the handling of duplicated
keys. The macro defined by `define-case-macro' will reject case sets
that contains duplicate keys under `eql', but depending on the
semantics of your macro, you may need to check for duplicates under a
looser definition of equality.
As a final example, if the `case' macro did not already exist, you
could define it almost trivially using `define-case-macro':
(define-case-macro my-case (expr &body clause)
(:default default)
`(cond
,@(loop for (key . body) in clauses
collect `((eql ,expr ,key) ,@body))
(t ,@body)))"
(multiple-value-bind (expr other-args clauses)
(ematch macro-args
((list expr '&body clauses)
(values expr nil clauses))
((list expr other-arg '&body clauses)
(values expr (list other-arg) clauses)))
(destructuring-bind (&key
error
(default (and error (gensym)))
(default-keys '(t otherwise)))
params
(let ((default-sym (or default (gensym)))
(docstring (and (stringp (first macro-body))
(pop macro-body))))
`(defmacro ,name (,expr ,@other-args &body ,clauses)
,@(unsplice docstring)
(expand-case-macro
(lambda (,expr ,default-sym ,clauses)
(declare (ignorable ,default-sym))
;; If `default' is defined as `nil', then no default
;; clause is allowed.
,(when (null default)
`(when ,default-sym
(error "Default disallowed in ~a" ,clauses)))
,@macro-body)
,expr ,clauses
:default-keys ',default-keys
:error ',error
:macro-name ',name))))))
(defun clauses+default (clauses &key (default-keys '(t otherwise)))
(let ((default-clause-tails
(loop for tail on clauses
for clause = (first tail)
for key = (first clause)
when (member key default-keys :test #'eq)
collect tail)))
(cond ((null default-clause-tails)
(values clauses nil))
((rest default-clause-tails)
(error "Multiple default clauses in ~a" clauses))
(t
(let ((default-tail (first default-clause-tails)))
(if (rest default-tail)
(error "Default clause not last in ~a" clauses)
(let ((default (first default-tail)))
(values (remove default clauses)
(rest default)))))))))
(defun simplify-keylists (clauses)
"Simplify the keylists in CLAUSES.
If the keylist is an empty list, omit the clause.
If the keylist is a list with one element, unwrap that element.
Otherwise, leave the keylist alone."
(loop for clause in clauses
for (keylist . body) = clause
if (null keylist)
do (progn)
else if (and (listp keylist)
(null (rest keylist))
;; Protect the key if the key is itself a list.
(atom (first keylist)))
collect (cons (first keylist) body)
else
collect clause))
(defun expand-case-macro (cont expr clauses
&key (default-keys '(t otherwise)) error
(macro-name 'custom-case))
(check-type clauses list)
(when (eql error t)
(setf error 'case-failure))
(let ((cont
(lambda (expr-temp default clauses)
(assert (symbolp expr-temp))
(assert (listp default))
(assert (listp clauses))
(funcall cont expr-temp default clauses)))
(expr-temp (gensym (format nil "~a-~a"
macro-name 'key))))
;; Rebind expr.
`(let ((,expr-temp ,expr))
,(multiple-value-bind (clauses default)
(clauses+default clauses :default-keys default-keys)
(let* ((clauses (simplify-keylists clauses))
(keys (mapcar #'first clauses))
(flat-keys (mappend #'ensure-list keys))
(clauses
(or (and error
(or default-keys
(error "Cannot add an error clause without a default key."))
(append clauses
(list `(,(random-elt default-keys)
(,error ,expr-temp ',flat-keys)))))
clauses)))
(when (< (length (remove-duplicates flat-keys))
(length flat-keys))
(error "Duplicated keys in ~s" keys))
(if (every #'atom keys) ;NB Nil could be a key.
;; Easy case. No lists of keys; do nothing special.
(funcall cont expr-temp default clauses)
;; This could be done two ways: with flet or with
;; tagbody. Switching is straightforward: just swap
;; `expand-case-macro/flet' for
;; `expand-case-macro/tagbody', or vice versa. (It
;; might even be worth using different expansions on
;; different Lisps.)
(let ((expander
(case uiop:*implementation-type*
((:sbcl :cmu) #'expand-case-macro/flet)
(t #'expand-case-macro/tagbody))))
(funcall expander
cont expr-temp clauses default
:macro-name macro-name))))))))
(defun expand-case-macro/common (clauses &key jump macro-name)
(check-type jump function)
(check-type macro-name symbol)
(labels ((gen-fn-sym ()
(gensym (concatenate 'string (string macro-name) "-" #.(string 'fn))))
(rec (clauses dest-acc clauses-acc)
(if (null clauses)
(values (reverse dest-acc)
(reverse clauses-acc))
(destructuring-bind ((keys . body) . rest-clauses) clauses
(if (atom keys) ;Remember nil could be a key.
(rec rest-clauses
dest-acc
(cons (first clauses) clauses-acc))
(let* ((sym (gen-fn-sym))
(dest (cons sym body))
(body (list (funcall jump sym))))
(rec rest-clauses
(cons dest dest-acc)
(revappend (loop for key in keys
collect (cons key body))
clauses-acc))))))))
(rec clauses nil nil)))
(defun expand-case-macro/flet (cont expr-temp normal-clauses default &key macro-name)
(multiple-value-bind (dests clauses)
(expand-case-macro/common normal-clauses
:jump (lambda (sym)
`(,sym))
:macro-name macro-name)
(let ((fns (loop for (sym . body) in dests
collect `(,sym () ,@body))))
`(flet ,fns
,(funcall cont expr-temp default clauses)))))
(defun expand-case-macro/tagbody (cont expr-temp normal-clauses default &key macro-name)
(let ((case-block (gensym (format nil "~a-~a" macro-name 'block))))
(multiple-value-bind (dests clauses)
(expand-case-macro/common normal-clauses
:jump (lambda (sym)
`(go ,sym)))
`(block ,case-block
(tagbody
(return-from ,case-block
,(funcall cont expr-temp default clauses))
,@(loop for (sym . body) in dests
append `(,sym (return-from ,case-block
,@body))))))))
(define-condition case-failure (type-error)
()
(:documentation "A subtype of type-error specifically for case failures."))
(defun case-failure (expr keys)
"Signal an error of type `case-failure'."
(error 'case-failure
:datum expr
:expected-type `(member ,@keys)))
(defun lambda-list-vars (lambda-list)
"Return a list of the variables bound in LAMBDA-LIST, an ordinary
lambda list."
(multiple-value-bind (req opt rest keys allow-other-keys? aux keyp)
(parse-ordinary-lambda-list lambda-list)
(declare (ignore allow-other-keys? keyp))
(remove nil
(append req
(mapcar #'first opt)
(mapcar #'third opt)
(list rest)
(mapcar (compose #'first #'rest #'first) keys)
(mapcar #'third keys)
(mapcar #'first aux)))))
(defun eval-if-constant (form &optional env)
"Try to reduce FORM to a constant, using ENV.
If FORM cannot be reduced, return it unaltered.
Also return a second value, T if the form could be reduced to a
constant, or nil otherwise. \(Note that the second value may be T if
FORM was already a constant; think of it as a \"green light\" to treat
the value as a constant.)
This is equivalent to testing if FORM is constant, then evaluating it,
except that FORM is macro-expanded in ENV (taking compiler macros into
account) before doing the test.
Note that this function may treat a form as constant which would not
be recognized as such by `constantp', because we also expand compiler
macros."
(labels ((eval-if-constant (form env)
(cond ((constantp form)
(values (eval form) t))
((and env (constantp form env))
;; Use the implementation's expander via introspect-environment.
(let ((value (constant-form-value form env)))
(if (constantp value)
(values value t)
;; Not every Lisp has a functioning
;; `constant-form-value', so if it failed,
;; it's still worth trying macroexpansion
;; (compiler macros too).
(expand-and-retry form env))))
(t (expand-and-retry form env))))
(expand-and-retry (form env)
(multiple-value-bind (exp exp?)
(expand-macro-recursively form env)
(if (not exp?)
(values form nil)
(eval-if-constant exp env)))))
(eval-if-constant form env)))
(defmacro declaim-maybe-inline-1 (fn)
(declare (ignorable fn))
#+sbcl `(declaim (sb-ext:maybe-inline ,fn))
#+cmucl `(declaim (ext:maybe-inline ,fn)))
(defmacro declaim-maybe-inline (&rest fns)
`(progn
,@(loop for fn in fns
collect `(declaim-maybe-inline-1 ,fn))))
(define-constant +merge-tail-calls+
;; Cf. https://0branch.com/notes/tco-cl.html#sec-2-5.
;; On SBCL debug=0 is sufficient to deactivate insert-debug-catch,
;; and to trigger recognize-self-calls (as long as one of speed or
;; space is greater than 0).
;; CCL does TCO as long as debug<3.
;; LispWorks merges tail calls as long as debug<3.
;; Allegro will only optimize non-self tail calls if debug<3 and
;; speed>2.
'(declare (optimize (debug 0)
#+sbcl (space 1)
#+allegro (speed 3)))
:test 'equal
:documentation "Try to ensure that tail calls will be merged.
If you just want portable self-calls, for writing loops using
recursion, use `nlet' or `defloop' instead.
This may not work at all on some Lisps.")
(defparameter *forbidden-heads*
'(progn locally prog1 prog2 prog prog* declare tagbody
block tagbody progv
when unless cond if or and
case ecase ccase
typecase ctypecase etypecase
let let* multiple-value-bind)
"Symbols that should not occur in the head of a list of forms.
E.g. `progn', `locally'.")
(defun sane-body-for-splice (exp)
"Sanity-check EXP, a macro expansion, assuming it is supposed to be
a series of forms suitable for splicing into a progn (implicit or
explicit.)"
(if (or (not (listp exp))
(member (car exp) *forbidden-heads*))
(error "A list of forms was expected, but this appears to be a single form:~%~s"
exp)
exp))
(defun sane-form-for-eval (exp)
"Sanity-check EXP, a macro expansion, assuming it is supposed to be
a single form suitable for inserting intact."
(if (match exp
((and _ (type atom)) t)
((list* (list* 'lambda _) _) t)
((list* (and _ (type symbol)) _) t)
(otherwise nil))
exp
(error "A single form was expected, but this appears to be a list of forms:~%~s"
exp)))
(defun unparse-ordinary-lambda-list (&optional required optional rest keywords aok? aux key?)
"Put together an ordinary lambda list from its constituent parts.
This is the inverse of `alexandria:parse-ordinary-lambda-list'.
lambda-list
≡ (multiple-value-call #'unparse-ordinary-lambda-list
(parse-ordinary-lambda-list lambda-list)"
(let ((optional
(mapcar (lambda (spec)
(match spec
((list var init nil)
(list var init))
(otherwise spec)))
optional))
(keywords
(mapcar (lambda (spec)
(match spec
((list (list keyword-name name) init nil)
(list (list keyword-name name) init))
(otherwise spec)))
keywords)))
`(,@required
,@(and optional `(&optional ,@optional))
,@(and rest `(&rest ,rest))
,@(and (or key? keywords)
`(&key ,@keywords))
,@(and aok? '(&allow-other-keys))
,@(and aux `(&aux ,@aux)))))