fordon_greeman / voidfiles

Branch: main
Switch branches/tags
main
theidioticdev add emacs 2079ec5 22h ago
577 lines · 21.7 KB Raw Blame History
1 ;;; cond-let.el --- Additional and improved binding conditionals -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 2025-2026 Jonas Bernoulli
4
5 ;; May contain traces of Emacs, which is
6 ;; Copyright (C) 1985-2025 Free Software Foundation, Inc.
7
8 ;; Author: Jonas Bernoulli <emacs.cond-let@jonas.bernoulli.dev>
9 ;; Homepage: https://github.com/tarsius/cond-let
10 ;; Keywords: extensions
11
12 ;; Package-Version: 20261001.1537
13 ;; Package-Revision: 09292a770014
14 ;; Package-Requires: ((emacs "28.1"))
15
16 ;; SPDX-License-Identifier: GPL-3.0-or-later
17
18 ;; This file is free software: you can redistribute it and/or modify
19 ;; it under the terms of the GNU General Public License as published
20 ;; by the Free Software Foundation, either version 3 of the License,
21 ;; or (at your option) any later version.
22 ;;
23 ;; This file is distributed in the hope that it will be useful,
24 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
25 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 ;; GNU General Public License for more details.
27 ;;
28 ;; You should have received a copy of the GNU General Public License
29 ;; along with this file. If not, see <https://www.gnu.org/licenses/>.
30
31 ;;; Commentary:
32
33 ;; Emacs provides the binding conditionals `if-let', `if-let*',
34 ;; `when-let', `when-let*', `and-let*' and `while-let'.
35
36 ;; This package implements the missing `and-let' and `while-let*';
37 ;; and the original `cond-let', `cond-let*', `when$', `and$' and
38 ;; `thread$'.
39
40 ;; This package additionally provides more consistent and improved
41 ;; implementations of the binding conditionals already provided by
42 ;; Emacs. Merely loading this library does not shadow the built-in
43 ;; implementations; this can optionally be done in the context of
44 ;; an individual library, as described below.
45
46 ;; `cond-let' and `cond-let*' are provided exactly under these names.
47 ;; The names of all other macros implemented by this package begin
48 ;; with `cond-let--', the package's prefix for private symbol.
49
50 ;; Users of this package are not expected to use these unwieldy
51 ;; names. Instead one should use Emacs' shorthand feature to use
52 ;; all or some of these macros by their conceptual names. E.g., if
53 ;; you want to use all of the available macros, add this at the end
54 ;; of a library.
55
56 ;; Local Variables:
57 ;; read-symbol-shorthands: (
58 ;; ("and$" . "cond-let--and$")
59 ;; ("thread$" . "cond-let--thread$")
60 ;; ("when$" . "cond-let--when$")
61 ;; ("and-let*" . "cond-let--and-let*")
62 ;; ("and-let" . "cond-let--and-let")
63 ;; ("if-let*" . "cond-let--if-let*")
64 ;; ("if-let" . "cond-let--if-let")
65 ;; ("when-let*" . "cond-let--when-let*")
66 ;; ("when-let" . "cond-let--when-let")
67 ;; ("while-let*" . "cond-let--while-let*")
68 ;; ("while-let" . "cond-let--while-let"))
69 ;; End:
70
71 ;; You can think of these file-local settings as import statements of
72 ;; sorts. If you do this, then this package's implementations shadow
73 ;; the built-in implementations. Doing so does not affect any other
74 ;; libraries, which continue to use the built-in implementations.
75
76 ;; Due to limitations of the shorthand implementation this has to be
77 ;; done for each individual library. ".dir-locals.el" cannot be used.
78
79 ;; If you use `when$', `and$' and `thread$', you might want to add
80 ;; this to your configuration:
81
82 ;; (with-eval-after-load 'cond-let
83 ;; (font-lock-add-keywords 'emacs-lisp-mode
84 ;; cond-let-font-lock-keywords t))
85
86 ;; For information about the individual macros, please refer to their
87 ;; docstrings.
88
89 ;; See also https://github.com/tarsius/cond-let/wiki.
90
91 ;;; Code:
92 ;;; Cond
93
94 (defun cond-let--prepare-clauses (tag sequential clauses)
95 "Used by macros `cond-let*' and `cond-let'."
96 (let (body)
97 (dolist (clause (nreverse clauses))
98 (cond
99 ((vectorp clause)
100 (setq body
101 `((,(if (and sequential (length> clause 1)) 'let* 'let)
102 ,(mapcar (lambda (vec) (append vec nil)) clause)
103 ,@body))))
104 ((let (varlist)
105 (while (vectorp (car clause))
106 (push (append (pop clause) nil) varlist))
107 (push (cond
108 (varlist
109 `(,(pcase (list (and body t)
110 (and sequential (length> varlist 1)))
111 ('(t t ) 'cond-let--when-let*)
112 (`(t ,_) 'cond-let--when-let)
113 ('(nil t ) 'cond-let--and-let*)
114 (`(nil ,_) 'cond-let--and-let))
115 ,(nreverse varlist)
116 ,(if body
117 `(throw ',tag ,(macroexp-progn clause))
118 (macroexp-progn clause))))
119 ((length= clause 1)
120 (if body
121 (let ((a (gensym "anon")))
122 `(let ((,a ,(car clause)))
123 (when ,a (throw ',tag ,a))))
124 (car clause)))
125 ((and (eq (car clause) t) (not body))
126 (macroexp-progn (cdr clause)))
127 (t
128 `(when ,(pop clause)
129 (throw ',tag ,(macroexp-progn clause)))))
130 body)))))
131 body))
132
133 (defmacro cond-let* (&rest clauses)
134 "Try each clause until one succeeds.
135
136 Each clause has one of these forms:
137 - a plain clause (CONDITION BODY...)
138 - a binding clause ([SYMBOL VALUEFORM]... BODY...)
139 - a binding vector [[SYMBOL VALUEFORM]...]
140
141 A (CONDITION BODY...) clause works as for `cond'. Evaluate CONDITION,
142 and if it yields non-nil, the clause succeeds. Then evaluate BODY forms
143 sequentially and return the value of the last; or if there are no BODY
144 forms, return the value of CONDITION. If CONDITION yields nil, do not
145 evaluate the BODY forms and instead proceed to the next clause.
146
147 A ([SYMBOL VALUEFORM]... BODY...) clause begins with one or more binding
148 vectors, followed by one or more BODY forms. Bind SYMBOL to the value
149 of VALUEFORM. Each VALUEFORM can refer to symbols already bound by this
150 VARLIST (as for `let*').
151
152 If all VALUEFORMs yield non-nil, evaluate BODY forms sequentially, with
153 VARLIST's bindings in effect, and return the value of the last form.
154
155 If any VALUEFORM yields nil, evaluate neither the remaining VALUEFORMs
156 nor the BODY forms, and proceed to the next clause.
157
158 A [[SYMBOL VALUEFORM]...] form creates bindings, which extend to all
159 remaining clauses and binding vectors. Unlike for the previous form,
160 always bind all SYMBOLs, even if a VALUEFORM yields nil. Always proceed
161 to the next clause."
162 (declare (indent 0)
163 (debug (&rest [&or
164 (vector &rest (vector symbolp form))
165 ([&rest (vector symbolp form)] body)
166 (form body)])))
167 (let ((tag (gensym ":cond-let*")))
168 `(catch ',tag
169 ,@(cond-let--prepare-clauses tag t clauses))))
170
171 (defmacro cond-let (&rest clauses)
172 "Try each clause until one succeeds.
173
174 Each clause has one of these forms:
175 - a plain clause (CONDITION BODY...)
176 - a binding clause ([SYMBOL VALUEFORM]... BODY...)
177 - a binding vector [[SYMBOL VALUEFORM]...]
178
179 A (CONDITION BODY...) clause works as for `cond'. Evaluate CONDITION,
180 and if it yields non-nil, the clause succeeds. Then evaluate BODY forms
181 sequentially and return the value of the last; or if there are no BODY
182 forms, return the value of CONDITION. If CONDITION yields nil, do not
183 evaluate the BODY forms and instead proceed to the next clause.
184
185 A ([SYMBOL VALUEFORM]... BODY...) clause begins with one or more binding
186 vectors, followed by one or more BODY forms. Bind SYMBOL to the value
187 of VALUEFORM. Evaluate all VALUEFORMs before binding their respective
188 SYMBOLs (as for `let').
189
190 If all VALUEFORMs yield non-nil, evaluate BODY forms sequentially, with
191 VARLIST's bindings in effect, and return the value of the last form.
192
193 If any VALUEFORM yields nil, evaluate neither the remaining VALUEFORMs
194 nor the BODY forms, and proceed to the next clause.
195
196 A [[SYMBOL VALUEFORM]...] form creates bindings, which extend to all
197 remaining clauses and binding vectors. Evaluate all VALUEFORMs before
198 binding their respective SYMBOLs. Unlike for the previous form, bind
199 all SYMBOLs, even if a VALUEFORM yields nil. Always proceed to the
200 next clause."
201 (declare (indent 0)
202 (debug cond-let*))
203 (let ((tag (gensym ":cond-let")))
204 `(catch ',tag
205 ,@(cond-let--prepare-clauses tag nil clauses))))
206
207 ;;; Common
208
209 (defun cond-let--prepare-varlist (varlist)
210 "Used by Cond-Let's `when-let*', `and-let*' and `while-let*'.
211 Also used by other macros via `cond-let--prepare-varforms'.
212 Return (VARLIST LASTVAR)."
213 (let (prevvar)
214 (list (mapcar (lambda (binding)
215 (unless (length= binding 2)
216 (signal 'error (cons "Invalid binding" binding)))
217 (pcase-let ((`(,var ,form) binding))
218 (when (string-prefix-p "_" (symbol-name var))
219 (setq var (gensym "anon")))
220 (prog1 (if prevvar
221 `(,var (and ,prevvar ,form))
222 (list var form))
223 (setq prevvar var))))
224 varlist)
225 prevvar)))
226
227 (defun cond-let--prepare-varforms (varlist &optional if-let)
228 "Used by Cond-Let's `when-let', `and-let', `while-let' and `if-let'.
229 Return (ANON-VARLIST ANON-SETQ VARLIST LASTVAR), or if the length of
230 VARLIST is 1 and IF-LET is nil, return (nil nil VARLIST LASTVAR)."
231 (if (and (not if-let)
232 (length= varlist 1))
233 `(nil nil ,@(cond-let--prepare-varlist varlist))
234 (let ((triples
235 (mapcar (lambda (binding)
236 (unless (length= binding 2)
237 (signal 'error (cons "Invalid binding" binding)))
238 (pcase-let ((`(,var ,form) binding))
239 (when (string-prefix-p "_" (symbol-name var))
240 (setq var nil))
241 (list (and var (gensym "anon"))
242 var
243 form)))
244 varlist)))
245 (list (mapcan (pcase-lambda (`(,anon ,_ ,_))
246 (and anon (list anon)))
247 triples)
248 (mapcar (pcase-lambda (`(,anon ,_ ,form))
249 (if anon
250 `(setq ,anon ,form)
251 form))
252 triples)
253 (mapcan (pcase-lambda (`(,anon ,var ,_))
254 (and var `((,var ,anon))))
255 triples)
256 (cadr (car (last triples)))))))
257
258 ;;; And
259
260 (defmacro cond-let--and-let* (varlist &optional bodyform)
261 "Bind according to VARLIST until one yields nil, else evaluate BODYFORM.
262
263 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
264 to the value of VALUEFORM. Each VALUEFORM can refer to symbols already
265 bound by this VARLIST (as for `let*').
266
267 Evaluate VALUEFORMs until on of them yields nil. If that happens return
268 nil, and evaluate neither the remaining VALUEFORMs nor BODYFORM. If all
269 VALUEFORMs yield non-nil, evaluate BODYFORM with the bindings in effect,
270 and return its value; or if there is no BODYFORM, the value of the last
271 VALUEFORM."
272 (declare (indent 1)
273 (debug ((&rest (symbolp form)) form)))
274 (pcase-let ((`(,varlist ,lastvar)
275 (cond-let--prepare-varlist varlist)))
276 `(let* ,varlist
277 ,(if bodyform
278 `(and ,lastvar ,bodyform)
279 lastvar))))
280
281 (defmacro cond-let--and-let (varlist &optional bodyform)
282 "Bind according to VARLIST until one yields nil, else evaluate BODYFORM.
283
284 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
285 to the value of VALUEFORM. Evaluate all VALUEFORMs before binding their
286 respective SYMBOLs (as for `let').
287
288 Evaluate VALUEFORMs until on of them yields nil. If that happens return
289 nil, and evaluate neither the remaining VALUEFORMs nor BODYFORM. If all
290 VALUEFORMs yield non-nil, evaluate BODYFORM with the bindings in effect,
291 and return its value; or if there is no BODYFORM, the value of the last
292 VALUEFORM."
293 (declare (indent 1)
294 (debug cond-let--and-let*))
295 (pcase-let ((`(,anon ,set ,bind ,lastvar)
296 (cond-let--prepare-varforms varlist)))
297 (cond (anon
298 `(let ,anon
299 (and ,@set
300 (let ,bind
301 ,(or bodyform lastvar)))))
302 (t
303 `(let ,bind
304 ,(if bodyform
305 `(and ,lastvar ,bodyform)
306 lastvar))))))
307
308 ;;; Thread
309
310 (defmacro cond-let--and$ (form form2 &rest forms)
311 "Bind variables according to each FORM until one of them yields nil.
312
313 Evaluate the first FORM and if that yields a non-nil value, bind the
314 symbol `$' to that value, and evaluate the next FORM with that binding
315 in effect. Repeat this process with subsequent FORMs until one yields
316 nil, then return nil without evaluate the remaining FORMs. If all
317 FORMs yield non-nil, return the value of the last FORM.
318
319 \(fn FORM FORM...)"
320 (declare (indent 0)
321 (debug t))
322 `(,(if forms 'let* 'let)
323 (($ ,form)
324 ,@(and forms
325 (mapcar (lambda (form)
326 `($ (and $ ,form)))
327 (cons form2 (butlast forms)))))
328 (and $
329 ,(or (car (last forms))
330 form2))))
331
332 (defmacro cond-let--thread$ (form form2 &rest forms)
333 "Bind variable `$' to value of nth FORM before evaluating nth+1 FORM.
334
335 Evaluate the first FORM and bind the symbol `$' to its value.
336 Then evaluate the next FORM with that binding in effect. Repeat this
337 process with subsequent FORMs, and return the value of the last FORM.
338
339 \(fn FORM FORM...)"
340 (declare (indent 0)
341 (debug t))
342 `(,(if forms 'let* 'let)
343 (($ ,form)
344 ,@(and forms
345 (mapcar (lambda (form)
346 `($ ,form))
347 (cons form2 (butlast forms)))))
348 ,(or (car (last forms))
349 form2)))
350
351 ;;; If
352
353 (defmacro cond-let--if-let* (varlist then &rest else)
354 "Bind variables according to VARLIST and evaluate THEN or ELSE.
355
356 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
357 to the value of VALUEFORM. Each VALUEFORM can refer to symbols already
358 bound by this VARLIST (as for `let*').
359
360 If all VALUEFORMs yield non-nil, evaluate THEN with VARLIST's bindings
361 in effect, and return its value. THEN must be one expression.
362
363 If any VALUEFORM yields nil, evaluate ELSE sequentially and return the
364 value of the last form; or if there are no ELSE forms return nil. The
365 bindings from VARLIST do _not_ extend to the ELSE forms.
366
367 \(fn VARLIST THEN [ELSE...])"
368 (declare (indent 2)
369 (debug ((&rest (symbolp form)) form body)))
370 (pcase-let ((`(,varlist ,lastvar)
371 (cond-let--prepare-varlist varlist))
372 (tag (gensym ":if-let*")))
373 `(catch ',tag
374 (let* ,varlist
375 (when ,lastvar
376 (throw ',tag ,then)))
377 ,@else)))
378
379 (defmacro cond-let--if-let (varlist then &rest else)
380 "Bind variables according to VARLIST and evaluate THEN or ELSE.
381
382 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
383 to the value of VALUEFORM. Evaluate all VALUEFORMs before binding their
384 respective SYMBOLs (as for `let').
385
386 If all VALUEFORMs yield non-nil, evaluate THEN with VARLIST's bindings
387 in effect, and return its value. THEN must be one expression.
388
389 If any VALUEFORM yields nil, evaluate ELSE sequentially and return the
390 value of the last form; or if there are no ELSE forms return nil. The
391 bindings from VARLIST do _not_ extend to the ELSE forms.
392
393 \(fn VARLIST THEN [ELSE...])"
394 (declare (indent 2)
395 (debug cond-let--if-let*))
396 (pcase-let* ((`(,anon ,set ,bind ,_)
397 (cond-let--prepare-varforms varlist t))
398 (set (if (length= set 1) (car set) (cons 'and set))))
399 `(let ,anon
400 (if ,set
401 (let ,bind
402 ,then)
403 ,@else))))
404
405 ;;; When
406
407 (defmacro cond-let--when-let* (varlist bodyform &rest body)
408 "Bind variables according to VARLIST and conditionally evaluate BODY.
409
410 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
411 to the value of VALUEFORM. Each VALUEFORM can refer to symbols already
412 bound by this VARLIST (as for `let*').
413
414 If all VALUEFORMs yield non-nil, evaluate BODY forms sequentially, with
415 VARLIST's bindings in effect, and return the value of the last form.
416
417 If any VALUEFORM yields nil, evaluate neither the remaining VALUEFORMs
418 nor the BODY forms, and instead return nil.
419
420 BODY must be one or more expressions. If VARLIST is empty, do nothing
421 and return nil.
422
423 \(fn VARLIST BODY...)"
424 (declare (indent 1)
425 (debug ((&rest (symbolp form)) form body)))
426 (pcase-let ((`(,varlist ,lastvar)
427 (cond-let--prepare-varlist varlist)))
428 `(let* ,varlist
429 (when ,lastvar
430 ,bodyform ,@body))))
431
432 (defmacro cond-let--when-let (varlist bodyform &rest body)
433 "Bind variables according to VARLIST and conditionally evaluate BODY.
434
435 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
436 to the value of VALUEFORM. Evaluate all VALUEFORMs before binding their
437 respective SYMBOLs (as for `let').
438
439 If all VALUEFORMs yield non-nil, evaluate BODY forms sequentially, with
440 VARLIST's bindings in effect, and return the value of the last form.
441
442 If any VALUEFORM yields nil, evaluate neither the remaining VALUEFORMs
443 nor the BODY forms, and instead return nil.
444
445 BODY must be one or more expressions. If VARLIST is empty, do nothing
446 and return nil.
447
448 \(fn VARLIST BODY...)"
449 (declare (indent 1)
450 (debug cond-let--when-let*))
451 (pcase-let ((`(,anon ,set ,bind ,lastvar)
452 (cond-let--prepare-varforms varlist)))
453 (cond (anon
454 `(let ,anon
455 (when (and ,@set)
456 (let ,bind
457 ,bodyform ,@body))))
458 (t
459 `(let ,bind
460 (when ,lastvar
461 ,bodyform ,@body))))))
462
463 (defmacro cond-let--when$ (varform bodyform &rest body)
464 "Bind variable `$' to value of VARFORM and conditionally evaluate BODY.
465
466 If VARFORM yields a non-nil value, bind the symbol `$' to that value,
467 evaluate BODY with that binding in effect, and return the value of the
468 last form. If VARFORM yields nil, do not evaluate BODY, and return nil.
469 BODY must be one or more expressions. If VARLIST is empty, do nothing
470 and return nil.
471
472 \(fn VARFORM BODY...)"
473 (declare (indent 1)
474 (debug t))
475 `(let (($ ,varform))
476 (when $
477 ,bodyform ,@body)))
478
479 ;;; While
480
481 (defmacro cond-let--while-let* (varlist &rest body)
482 "Bind variables according to VARLIST, conditionally evaluate BODY, and repeat.
483
484 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
485 to the value of VALUEFORM. Each VALUEFORM can refer to symbols already
486 bound by this VARLIST (as for `let*').
487
488 If all VALUEFORMs yield non-nil, evaluate BODY forms sequentially, with
489 VARLIST's bindings in effect, and repeat the loop.
490
491 If any VALUEFORM yields nil, evaluate neither the remaining VALUEFORMs
492 nor the BODY forms, and instead return, always yielding nil.
493
494 BODY can be zero or more expressions.
495
496 \(fn VARLIST [BODY...])"
497 (declare (indent 1)
498 (debug ((&rest (symbolp form)) body)))
499 (pcase-let ((`(,varlist ,lastvar)
500 (cond-let--prepare-varlist varlist))
501 (tag (gensym ":while-let*")))
502 `(catch ',tag
503 (while t
504 (let* ,varlist
505 (if ,lastvar
506 ,(macroexp-progn body)
507 (throw ',tag nil)))))))
508
509 (defmacro cond-let--while-let (varlist bodyform &rest body)
510 "Bind variables according to VARLIST, conditionally evaluate BODY, and repeat.
511
512 Each element of VARLIST is a list (SYMBOL VALUEFORM), which binds SYMBOL
513 to the value of VALUEFORM. Evaluate all VALUEFORMs before binding their
514 respective SYMBOLs (as for `let').
515
516 If all VALUEFORMs yield non-nil, evaluate BODY forms sequentially, with
517 VARLIST's bindings in effect, and repeat the loop.
518
519 If any VALUEFORM yields nil, evaluate neither the remaining VALUEFORMs
520 nor the BODY forms, and instead return, always yielding nil.
521
522 BODY can be one or more expressions.
523
524 \(fn VARLIST BODY...)"
525 (declare (indent 1)
526 (debug ((&rest (symbolp form)) form body)))
527 (pcase-let ((`(,anon ,set ,bind ,lastvar)
528 (cond-let--prepare-varforms varlist))
529 (tag (gensym ":while-let")))
530 (cond (anon
531 `(catch ',tag
532 (while t
533 (let ,anon
534 (if (and ,@set)
535 (let ,bind
536 ,bodyform ,@body)
537 (throw ',tag nil))))))
538 (t
539 `(catch ',tag
540 (while t
541 (let ,bind
542 (if ,lastvar
543 ,(macroexp-progn (cons bodyform body))
544 (throw ',tag nil)))))))))
545
546 ;;; Font-Lock
547
548 (defvar cond-let-font-lock-keywords
549 '(("\\_<\\$\\_>" 0 'font-lock-variable-name-face))
550 "Highlight `$' using `font-lock-variable-name-face'.
551 To add these keywords, add this to your configuration:
552 \(font-lock-add-keywords \\='emacs-lisp-mode cond-let-font-lock-keywords t)")
553
554 ;;;###autoload
555 (define-minor-mode cond-let-fontify-mode
556 "In Emacs Lisp mode, highlight `$' using `font-lock-variable-name-face'."
557 :global t
558 :group 'font-lock-extra-types
559 (if cond-let-fontify-mode
560 (font-lock-add-keywords 'emacs-lisp-mode cond-let-font-lock-keywords t)
561 (font-lock-remove-keywords 'emacs-lisp-mode cond-let-font-lock-keywords)))
562
563 ;;; Compatibility
564
565 (defalias 'cond-let--and> #'cond-let--and$
566 "Instead of this alias, use `cond-let--and$' via `and$' shorthand.
567
568 This alias will likely be declared obsolete in 2027. If you would like
569 to continue to use it, please get in contact before then. This alias
570 might eventually be removed altogether; again subject to user feedback.
571
572 If you do not care about the symbol `cond-let--and>', but want to keep
573 using the respective `and>' shorthand, you can future-proof that now,
574 by changing the shorthand definition to (\"and>\" . \"cond-let--and$\").")
575
576 (provide 'cond-let)
577 ;;; cond-let.el ends here