1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-15 18:40:39 -08:00

Implement and-let*

This also includes changes to if-let and when-let.  The single tuple
special case is ambiguous, and binding a symbol to nil is not as
useful as binding it to its value outside the lexical scope of the
binding.  (Bug#28254)
* etc/NEWS: Mention.
* lisp/emacs-lisp/subr-x.el (internal--listify):
(internal--build-binding-value-form): Extend to account for
solitary symbols and (EXPR) items in binding varlist.
(if-let*, when-let*): Nix single tuple case and incumbent
bind-symbol-to-nil behavior.
(and-let*): New macro.
(if-let, when-let): Mark obsolete.  Redefine in terms of if-let*, so
they implicitly gain the new features without breaking existing code.
* test/lisp/emacs-lisp/subr-x-tests.el: Adjust tests for: lack of
single-tuple special case, lack of binding solitary symbols to nil,
and the introduction of uninterned symbols for (EXPR) bindings.  Add
SRFI-2 test suite adapted to Elisp.
This commit is contained in:
Mark Oteiza 2017-09-12 12:44:45 -04:00
parent c87331a1c0
commit 4612b2a2b3
3 changed files with 237 additions and 189 deletions

View file

@ -83,10 +83,15 @@ threading."
`(internal--thread-argument nil ,@forms))
(defsubst internal--listify (elt)
"Wrap ELT in a list if it is not one."
(if (not (listp elt))
(list elt)
elt))
"Wrap ELT in a list if it is not one.
If ELT is of the form ((EXPR)), listify (EXPR) with a dummy symbol."
(cond
((symbolp elt) (list elt elt))
((and (null (cdr elt))
(let ((form (car elt)))
(or (listp form) (atom form))))
(list (make-symbol "s") (car elt)))
(t elt)))
(defsubst internal--check-binding (binding)
"Check BINDING is properly formed."
@ -98,7 +103,10 @@ threading."
(defsubst internal--build-binding-value-form (binding prev-var)
"Build the conditional value form for BINDING using PREV-VAR."
`(,(car binding) (and ,prev-var ,(cadr binding))))
(let ((var (car binding)))
(if (and (null (cdr binding)) (atom (car binding)) (not (symbolp (car binding))))
`(,var (and ,prev-var ,var))
`(,var (and ,prev-var ,(cadr binding))))))
(defun internal--build-binding (binding prev-var)
"Check and build a single BINDING with PREV-VAR."
@ -117,44 +125,68 @@ threading."
binding))
bindings)))
(defmacro if-let* (bindings then &rest else)
(defmacro if-let* (varlist then &rest else)
"Bind variables according to VARLIST and eval THEN or ELSE.
Each binding is evaluated in turn with `let*', and evaluation
stops if a binding value is nil. If all are non-nil, the value
of THEN is returned, or the last form in ELSE is returned.
Each element of VARLIST is a symbol (which is bound to nil)
or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM).
In the special case you only want to bind a single value,
VARLIST can just be a plain tuple.
\n(fn VARLIST THEN ELSE...)"
Each binding is evaluated in turn, and evaluation stops if a
binding value is nil. If all are non-nil, the value of THEN is
returned, or the last form in ELSE is returned.
Each element of VARLIST is a list (SYMBOL VALUEFORM) which binds
SYMBOL to the value of VALUEFORM).
An element can additionally be of the form (VALUEFORM), which is
evaluated and checked for nil."
(declare (indent 2)
(debug ([&or (&rest [&or symbolp (symbolp form)]) (symbolp form)]
(debug ((&rest [&or symbolp (symbolp form) (sexp)])
form body)))
(when (and (<= (length bindings) 2)
(not (listp (car bindings))))
;; Adjust the single binding case
(setq bindings (list bindings)))
`(let* ,(internal--build-bindings bindings)
(if ,(car (internal--listify (car (last bindings))))
,then
,@else)))
(if varlist
`(let* ,(setq varlist (internal--build-bindings varlist))
(if ,(caar (last varlist))
,then
,@else))
`(let* () ,@else)))
(defmacro when-let* (bindings &rest body)
(defmacro when-let* (varlist &rest body)
"Bind variables according to VARLIST and conditionally eval BODY.
Each binding is evaluated in turn with `let*', and evaluation
stops if a binding value is nil. If all are non-nil, the value
of the last form in BODY is returned.
Each element of VARLIST is a symbol (which is bound to nil)
or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM).
In the special case you only want to bind a single value,
VARLIST can just be a plain tuple.
\n(fn VARLIST BODY...)"
(declare (indent 1) (debug if-let))
(list 'if-let bindings (macroexp-progn body)))
Each binding is evaluated in turn, and evaluation stops if a
binding value is nil. If all are non-nil, the value of the last
form in BODY is returned.
(defalias 'if-let 'if-let*)
(defalias 'when-let 'when-let*)
(defalias 'and-let* 'when-let*)
VARLIST is the same as in `if-let*'."
(declare (indent 1) (debug if-let*))
(list 'if-let* varlist (macroexp-progn body)))
(defmacro and-let* (varlist &rest body)
"Bind variables according to VARLIST and conditionally eval BODY.
Like `when-let*', except if BODY is empty and all the bindings
are non-nil, then the result is non-nil."
(declare (indent 1) (debug when-let*))
(let (res)
(if varlist
`(let* ,(setq varlist (internal--build-bindings varlist))
(if ,(setq res (caar (last varlist)))
,@(or body `(,res))))
`(let* () ,@(or body '(t))))))
(defmacro if-let (spec then &rest else)
"Bind variables according to SPEC and eval THEN or ELSE.
Like `if-let*' except SPEC can have the form (SYMBOL VALUEFORM)."
(declare (indent 2)
(debug ([&or (&rest [&or symbolp (symbolp form) (sexp)])
(symbolp form)]
form body))
(obsolete "use `if-let*' instead." "26.1"))
(when (and (<= (length spec) 2)
(not (listp (car spec))))
;; Adjust the single binding case
(setq spec (list spec)))
(list 'if-let* spec then (macroexp-progn else)))
(defmacro when-let (spec &rest body)
"Bind variables according to SPEC and conditionally eval BODY.
Like `when-let*' except SPEC can have the form (SYMBOL VALUEFORM)."
(declare (indent 1) (debug if-let)
(obsolete "use `when-let*' instead." "26.1"))
(list 'if-let spec (macroexp-progn body)))
(defsubst hash-table-empty-p (hash-table)
"Check whether HASH-TABLE is empty (has 0 elements)."