changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/std/macs/ana.lisp

changeset 698: 96958d3eb5b0
parent: 5bd0eb9fa1fa
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 ;;; ana.lisp --- anaphoric macros
2 
3 ;;; Code:
4 (in-package :std/macs)
5 
6 (in-readtable :std)
7 
8 ;;; Named Lambdas
9 ;; (reexport-from :sb-int :include '(:make-macro-lambda :parse-lambda-list))
10 
11 ;; LoL tlist
12 ;; (declaim (inline make-tlist tlist-left
13 ;; tlist-right tlist-empty-p))
14 
15 ;; (defun make-tlist () (cons nil nil))
16 ;; (defun tlist-left (tl) (caar tl))
17 ;; (defun tlist-right (tl) (cadr tl))
18 ;; (defun tlist-empty-p (tl) (null (car tl)))
19 
20 ;; (declaim (inline tlist-add-left
21 ;; tlist-add-right))
22 
23 ;; (defun tlist-add-left (tl it)
24 ;; (let ((x (cons it (car tl))))
25 ;; (if (tlist-empty-p tl)
26 ;; (setf (cdr tl) x))
27 ;; (setf (car tl) x)))
28 
29 ;; (defun tlist-add-right (tl it)
30 ;; (let ((x (cons it nil)))
31 ;; (if (tlist-empty-p tl)
32 ;; (setf (car tl) x)
33 ;; (setf (cddr tl) x))
34 ;; (setf (cdr tl) x)))
35 
36 ;; (declaim (inline tlist-rem-left))
37 
38 ;; (defun tlist-rem-left (tl)
39 ;; (if (tlist-empty-p tl)
40 ;; (error "Remove from empty tlist")
41 ;; (let ((x (car tl)))
42 ;; (setf (car tl) (cdar tl))
43 ;; (if (tlist-empty-p tl)
44 ;; (setf (cdr tl) nil)) ;; For gc
45 ;; (car x))))
46 
47 ;; (declaim (inline tlist-update))
48 
49 ;; (defun tlist-update (tl)
50 ;; (setf (cdr tl) (last (car tl))))
51 
52 (defun build-batcher-sn (n)
53  (let* (network
54  (tee (ceiling (log n 2)))
55  (p (ash 1 (- tee 1))))
56  (loop while (> p 0) do
57  (let ((q (ash 1 (- tee 1)))
58  (r 0)
59  (d p))
60  (loop while (> d 0) do
61  (loop for i from 0 to (- n d 1) do
62  (if (= (logand i p) r)
63  (push (list i (+ i d))
64  network)))
65  (setf d (- q p)
66  q (ash q -1)
67  r p)))
68  (setf p (ash p -1)))
69  (nreverse network)))
70 
71 (defmacro! sortf (comparator &rest places)
72  (if places
73  `(tagbody
74  ,@(mapcar
75  #`(let ((,g!a #1=,(nth (car a1) places))
76  (,g!b #2=,(nth (cadr a1) places)))
77  (if (,comparator ,g!b ,g!a)
78  (setf #1# ,g!b
79  #2# ,g!a)))
80  (build-batcher-sn (length places))))))
81 
82 #+cl-ppcre
83 (defun dollar-symbol-p (s)
84  (and (symbolp s)
85  (> (length (symbol-name s)) 1)
86  (string= (symbol-name s)
87  "$"
88  :start1 0
89  :end1 1)
90  (ignore-errors (parse-integer (subseq (symbol-name s) 1)))))
91 
92 
93 #+cl-ppcre
94 (defmacro! if-match ((match-regex str) then &optional else)
95  (let* ((dollars (remove-duplicates
96  (remove-if-not #'dollar-symbol-p
97  (flatten then))))
98  (top (or (car (sort (mapcar #'dollar-symbol-p dollars) #'>))
99  0)))
100  `(multiple-value-bind (,g!matches ,g!captures) (,match-regex ,str)
101  (declare (ignorable ,g!matches ,g!captures))
102  (let ((,g!captures-len (length ,g!captures)))
103  (declare (ignorable ,g!captures-len))
104  (symbol-macrolet ,(mapcar #`(,(symb "$" a1)
105  (if (< ,g!captures-len ,a1)
106  (error "Too few matchs: ~a unbound." ,(mkstr "$" a1))
107  (aref ,g!captures ,(1- a1))))
108  (loop for i from 1 to top collect i))
109  (if ,g!matches
110  ,then
111  ,else))))))
112 
113 #+cl-ppcre
114 (defmacro when-match ((match-regex str) &body forms)
115  `(if-match (,match-regex ,str)
116  (progn ,@forms)))
117 
118 (defmacro once-only (specs &body forms)
119  "Constructs code whose primary goal is to help automate the handling of
120 multiple evaluation within macros. Multiple evaluation is handled by introducing
121 intermediate variables, in order to reuse the result of an expression.
122 
123 The returned value is a list of the form
124 
125  (let ((<gensym-1> <expr-1>)
126  ...
127  (<gensym-n> <expr-n>))
128  <res>)
129 
130 where GENSYM-1, ..., GENSYM-N are the intermediate variables introduced in order
131 to evaluate EXPR-1, ..., EXPR-N once, only. RES is code that is the result of
132 evaluating the implicit progn FORMS within a special context determined by
133 SPECS. RES should make use of (reference) the intermediate variables.
134 
135 Each element within SPECS is either a symbol SYMBOL or a pair (SYMBOL INITFORM).
136 Bare symbols are equivalent to the pair (SYMBOL SYMBOL).
137 
138 Each pair (SYMBOL INITFORM) specifies a single intermediate variable:
139 
140 - INITFORM is an expression evaluated to produce EXPR-i
141 
142 - SYMBOL is the name of the variable that will be bound around FORMS to the
143  corresponding gensym GENSYM-i, in order for FORMS to generate RES that
144  references the intermediate variable
145 
146 The evaluation of INITFORMs and binding of SYMBOLs resembles LET. INITFORMs of
147 all the pairs are evaluated before binding SYMBOLs and evaluating FORMS.
148 
149 Example:
150 
151  The following expression
152 
153  (let ((x '(incf y)))
154  (once-only (x)
155  `(cons ,x ,x)))
156 
157  ;;; =>
158  ;;; (let ((#1=#:X123 (incf y)))
159  ;;; (cons #1# #1#))
160 
161  could be used within a macro to avoid multiple evaluation like so
162 
163  (defmacro cons1 (x)
164  (once-only (x)
165  `(cons ,x ,x)))
166 
167  (let ((y 0))
168  (cons1 (incf y)))
169 
170  ;;; => (1 . 1)
171 
172 Example:
173 
174  The following expression demonstrates the usage of the INITFORM field
175 
176  (let ((expr '(incf y)))
177  (once-only ((var `(1+ ,expr)))
178  `(list ',expr ,var ,var)))
179 
180  ;;; =>
181  ;;; (let ((#1=#:VAR123 (1+ (incf y))))
182  ;;; (list '(incf y) #1# #1))
183 
184  which could be used like so
185 
186  (defmacro print-succ-twice (expr)
187  (once-only ((var `(1+ ,expr)))
188  `(format t \"Expr: ~s, Once: ~s, Twice: ~s~%\" ',expr ,var ,var)))
189 
190  (let ((y 10))
191  (print-succ-twice (incf y)))
192 
193  ;;; >>
194  ;;; Expr: (INCF Y), Once: 12, Twice: 12"
195  (let ((gensyms (make-gensym-list (length specs) "ONCE-ONLY"))
196  (names-and-forms (mapcar (lambda (spec)
197  (etypecase spec
198  (list
199  (destructuring-bind (name form) spec
200  (cons name form)))
201  (symbol
202  (cons spec spec))))
203  specs)))
204  ;; bind in user-macro
205  `(let ,(mapcar (lambda (g n) (list g `(gensym ,(string (car n)))))
206  gensyms names-and-forms)
207  ;; bind in final expansion
208  `(let (,,@(mapcar (lambda (g n)
209  ``(,,g ,,(cdr n)))
210  gensyms names-and-forms))
211  ;; bind in user-macro
212  ,(let ,(mapcar (lambda (n g) (list (car n) g))
213  names-and-forms gensyms)
214  ,@forms)))))
215 
216 ;;;; DESTRUCTURING-*CASE
217 
218 (defun expand-destructuring-case (key clauses case)
219  (once-only (key)
220  `(if (typep ,key 'cons)
221  (,case (car ,key)
222  ,@(mapcar (lambda (clause)
223  (destructuring-bind ((keys . lambda-list) &body body) clause
224  `(,keys
225  (destructuring-bind ,lambda-list (cdr ,key)
226  ,@body))))
227  clauses))
228  (error "Invalid key to DESTRUCTURING-~S: ~S" ',case ,key))))
229 
230 (defmacro destructuring-case (keyform &body clauses)
231  "DESTRUCTURING-CASE, -CCASE, and -ECASE are a combination of CASE and DESTRUCTURING-BIND.
232 KEYFORM must evaluate to a CONS.
233 
234 Clauses are of the form:
235 
236  ((CASE-KEYS . DESTRUCTURING-LAMBDA-LIST) FORM*)
237 
238 The clause whose CASE-KEYS matches CAR of KEY, as if by CASE, CCASE, or ECASE,
239 is selected, and FORMs are then executed with CDR of KEY is destructured and
240 bound by the DESTRUCTURING-LAMBDA-LIST.
241 
242 Example:
243 
244  (defun dcase (x)
245  (destructuring-case x
246  ((:foo a b)
247  (format nil \"foo: ~S, ~S\" a b))
248  ((:bar &key a b)
249  (format nil \"bar: ~S, ~S\" a b))
250  (((:alt1 :alt2) a)
251  (format nil \"alt: ~S\" a))
252  ((t &rest rest)
253  (format nil \"unknown: ~S\" rest))))
254 
255  (dcase (list :foo 1 2)) ; => \"foo: 1, 2\"
256  (dcase (list :bar :a 1 :b 2)) ; => \"bar: 1, 2\"
257  (dcase (list :alt1 1)) ; => \"alt: 1\"
258  (dcase (list :alt2 2)) ; => \"alt: 2\"
259  (dcase (list :quux 1 2 3)) ; => \"unknown: 1, 2, 3\"
260 
261  (defun decase (x)
262  (destructuring-case x
263  ((:foo a b)
264  (format nil \"foo: ~S, ~S\" a b))
265  ((:bar &key a b)
266  (format nil \"bar: ~S, ~S\" a b))
267  (((:alt1 :alt2) a)
268  (format nil \"alt: ~S\" a))))
269 
270  (decase (list :foo 1 2)) ; => \"foo: 1, 2\"
271  (decase (list :bar :a 1 :b 2)) ; => \"bar: 1, 2\"
272  (decase (list :alt1 1)) ; => \"alt: 1\"
273  (decase (list :alt2 2)) ; => \"alt: 2\"
274  (decase (list :quux 1 2 3)) ; =| error
275 "
276  (expand-destructuring-case keyform clauses 'case))
277 
278 (defmacro destructuring-ccase (keyform &body clauses)
279  (expand-destructuring-case keyform clauses 'ccase))
280 
281 (defmacro destructuring-ecase (keyform &body clauses)
282  (expand-destructuring-case keyform clauses 'ecase))
283 
284 (dolist (name '(destructuring-ccase destructuring-ecase))
285  (setf (documentation name 'function) (documentation 'destructuring-case 'function)))
286 
287 ;;; *-let --- control-flow let-binding macros
288 ;; based on https://stevelosh.com/blog/2018/07/fun-with-macros-if-let/
289 
290 (defmacro when-let (bindings &body body)
291  "Bind `bindings` in parallel and execute `body`, short-circuiting on `nil`.
292 
293  This macro combines `when` and `let`. It takes a list of bindings and
294  binds them like `let` before executing `body`, but if any binding's value
295  evaluates to `nil` the process stops and `nil` is immediately returned.
296 
297  Examples:
298 
299  (when-let ((a (progn (print :a) 1))
300  (b (progn (print :b) 2))
301  (list a b))
302  ; =>
303  :A
304  :B
305  (1 2)
306 
307  (when-let ((a (progn (print :a) nil))
308  (b (progn (print :b) 2)))
309  (list a b))
310  ; =>
311  :A
312  NIL
313 
314  "
315  (with-gensyms (block)
316  `(block ,block
317  (let ,(loop :for (symbol value) :in bindings
318  :collect `(,symbol (or ,value
319  (return-from ,block nil))))
320  ,@body))))
321 
322 (defmacro when-let* (bindings &body body)
323  "Bind `bindings` serially and execute `body`, short-circuiting on `nil`.
324 
325  This macro combines `when` and `let*`. It takes a list of bindings
326  and binds them like `let*` before executing `body`, but if any
327  binding's value evaluates to `nil` the process stops and `nil` is
328  immediately returned.
329 
330  Examples:
331 
332  (when-let* ((a (progn (print :a) 1))
333  (b (progn (print :b) (1+ a)))
334  (list a b))
335  ; =>
336  :A
337  :B
338  (1 2)
339 
340  (when-let* ((a (progn (print :a) nil))
341  (b (progn (print :b) (1+ a))))
342  (list a b))
343  ; =>
344  :A
345  NIL
346 
347  "
348  (with-gensyms (block)
349  `(block ,block
350  (let* ,(loop :for (symbol value) :in bindings
351  :collect `(,symbol (or ,value
352  (return-from ,block nil))))
353  ,@body))))
354 
355 (defmacro if-let (bindings &body body)
356  "Bind `bindings` in parallel and execute `then` if all are true, or `else` otherwise.
357 
358  `body` must be of the form `(...optional-declarations... then else)`.
359 
360  This macro combines `if` and `let`. It takes a list of bindings and
361  binds them like `let` before executing the `then` branch of `body`, but
362  if any binding's value evaluates to `nil` the process stops there and the
363  `else` branch is immediately executed (with no bindings in effect).
364 
365  If any `optional-declarations` are included they will only be in effect
366  for the `then` branch.
367 
368  Examples:
369 
370  (if-let ((a (progn (print :a) 1))
371  (b (progn (print :b) 2)))
372  (list a b)
373  'nope)
374  ; =>
375  :A
376  :B
377  (1 2)
378 
379  (if-let ((a (progn (print :a) nil))
380  (b (progn (print :b) 2)))
381  (list a b)
382  'nope)
383  ; =>
384  :A
385  NOPE
386 
387  "
388  (with-gensyms (outer inner)
389  (multiple-value-bind (body declarations) (parse-body body)
390  (destructuring-bind (then else) body
391  `(block ,outer
392  (block ,inner
393  (let ,(loop :for (symbol value) :in bindings
394  :collect `(,symbol (or ,value
395  (return-from ,inner nil))))
396  ,@declarations
397  (return-from ,outer ,then)))
398  ,else)))))
399 
400 (defmacro if-let* (bindings then else)
401  "Bind `bindings` serially and execute `then` if all are true, or `else` otherwise.
402 
403  This macro combines `if` and `let*`. It takes a list of bindings and
404  binds them like `let*` before executing `then`, but if any binding's
405  value evaluates to `nil` the process stops and the `else` branch is
406  immediately executed (with no bindings in effect).
407 
408  Examples:
409 
410  (if-let* ((a (progn (print :a) 1))
411  (b (progn (print :b) (1+ a)))
412  (list a b)
413  'nope)
414  ; =>
415  :A
416  :B
417  (1 2)
418 
419  (if-let* ((a (progn (print :a) nil))
420  (b (progn (print :b) (1+ a))))
421  (list a b)
422  'nope)
423  ; =>
424  :A
425  NOPE
426 
427  "
428  (with-gensyms (outer inner)
429  `(block ,outer
430  (block ,inner
431  (let* ,(loop :for (symbol value) :in bindings
432  :collect `(,symbol (or ,value
433  (return-from ,inner nil))))
434  (return-from ,outer ,then)))
435  ,else)))
436 
437 
438 (defmacro def! (name &body body)
439  "`defun' without args."
440  `(defun ,name () ,@body))
441 
442 (defmacro eval-always (&body body)
443  `(eval-when (:compile-toplevel :load-toplevel :execute) ,@body))
444 
445 ;;; Franz
446 (defvar if*-keyword-list '("then" "thenret" "else" "elseif"))
447 
448 (defmacro if* (&rest args)
449  (do ((xx (reverse args) (cdr xx))
450  (state :init)
451  (elseseen nil)
452  (totalcol nil)
453  (lookat nil nil)
454  (col nil))
455  ((null xx)
456  (cond ((eq state :compl)
457  `(cond ,@totalcol))
458  (t (error "if*: illegal form ~s" args))))
459  (cond ((and (symbolp (car xx))
460  (member (symbol-name (car xx))
461  if*-keyword-list
462  :test #'string-equal))
463  (setq lookat (symbol-name (car xx)))))
464 
465  (cond ((eq state :init)
466  (cond (lookat (cond ((string-equal lookat "thenret")
467  (setq col nil
468  state :then))
469  (t (error
470  "if*: bad keyword ~a" lookat))))
471  (t (setq state :col
472  col nil)
473  (push (car xx) col))))
474  ((eq state :col)
475  (cond (lookat
476  (cond ((string-equal lookat "else")
477  (cond (elseseen
478  (error
479  "if*: multiples elses")))
480  (setq elseseen t)
481  (setq state :init)
482  (push `(t ,@col) totalcol))
483  ((string-equal lookat "then")
484  (setq state :then))
485  (t (error "if*: bad keyword ~s"
486  lookat))))
487  (t (push (car xx) col))))
488  ((eq state :then)
489  (cond (lookat
490  (error
491  "if*: keyword ~s at the wrong place " (car xx)))
492  (t (setq state :compl)
493  (push `(,(car xx) ,@col) totalcol))))
494  ((eq state :compl)
495  (cond ((not (string-equal lookat "elseif"))
496  (error "if*: missing elseif clause ")))
497  (setq state :init)))))
498 
499 (defmacro named-lambda (name lambda-list &body body)
500  "Expands into a lambda-expression within whose BODY NAME denotes the
501 corresponding function."
502  `(labels ((,name ,lambda-list ,@body))
503  #',name))
504 
505 ;;; Misc
506 (defmacro until (condition &body body)
507  (let ((block-name (gensym)))
508  `(block ,block-name
509  (loop
510  (if ,condition
511  (return-from ,block-name nil)
512  (progn ,@body))))))
513 
514 (defmacro! dlambda (&rest ds)
515  "Dynamic dispatch lambda."
516  `(lambda (&rest ,g!args)
517  (case (car ,g!args)
518  ,@(mapcar
519  (lambda (d)
520  `(,(if (eq t (car d))
521  t
522  (list (car d)))
523  (apply (lambda ,@(cdr d))
524  ,(if (eq t (car d))
525  g!args
526  `(cdr ,g!args)))))
527  ds))))
528 
529 ;; Graham's alambda
530 (defmacro alambda (parms &body body)
531  `(labels ((%a ,parms ,@body))
532  #'%a))
533 
534 ;; Graham's aif
535 (defmacro aif (test then &optional else)
536  `(let ((it ,test))
537  (if it ,then ,else)))
538 
539 ;; ;; TODO 2023-09-05: wrap, document, optimize, hack
540 ;; re-exported from SB-INT
541 (defmacro awhen (test &body body)
542  `(let ((it ,test))
543  (when it ,@body)))
544 
545 (defmacro acond (&rest clauses)
546  (if (null clauses)
547  `()
548  (destructuring-bind ((test &body body) &rest rest) clauses
549  (let ((it (copy-symbol 'it)))
550  `(let ((,it ,test))
551  (if ,it
552  ;; Just like COND - no body means return the tested value.
553  ,(if body
554  `(let ((it ,it)) (declare (ignorable it)) ,@body)
555  it)
556  (acond ,@rest)))))))
557 
558 (defmacro! nlet-tail (n letargs &body body)
559  (let ((gs (loop for i in letargs
560  collect (gensym))))
561  `(macrolet
562  ((,n ,gs
563  `(progn
564  (psetq
565  ,@(apply #'nconc
566  (mapcar
567  #'list
568  ',(mapcar #'car letargs)
569  (list ,@gs))))
570  (go ,',g!n))))
571  (block ,g!b
572  (let ,letargs
573  (tagbody
574  ,g!n (return-from
575  ,g!b (progn ,@body))))))))
576 
577 (defmacro alet% (letargs &rest body)
578  `(let ((%a) ,@letargs)
579  (setq %a ,@(last body))
580  ,@(butlast body)
581  %a))
582 
583 (defmacro alet (letargs &rest body)
584  `(let ((%a) ,@letargs)
585  (setq %a ,@(last body))
586  ,@(butlast body)
587  (lambda (&rest params)
588  (apply %a params))))
589 
590 ;; swiped from fiveam. This is just like acond except it assumes that
591 ;; the TEST in each element of CLAUSES returns two values as opposed
592 ;; to one.
593 (defmacro acond2 (&rest clauses)
594  (if (null clauses)
595  nil
596  (with-gensyms (val foundp)
597  (destructuring-bind ((test &rest progn) &rest others)
598  clauses
599  `(multiple-value-bind (,val ,foundp)
600  ,test
601  (if (or ,val ,foundp)
602  (let ((it ,val))
603  (declare (ignorable it))
604  ,@progn)
605  (acond2 ,@others)))))))