branch: elpa/llama
commit f4a59f215438243fbbdc103a6db11fb2e99cff99
Author: Jonas Bernoulli <jo...@bernoul.li>
Commit: Jonas Bernoulli <jo...@bernoul.li>

    Support &optional arguments
---
 README.md | 28 ++++++++++++-------
 llama.el  | 93 ++++++++++++++++++++++++++++++++++++++++++---------------------
 2 files changed, 81 insertions(+), 40 deletions(-)

diff --git a/README.md b/README.md
index 30189c2e12..caa7d2daf2 100644
--- a/README.md
+++ b/README.md
@@ -9,31 +9,39 @@ to a `lambda` expressions, which wraps around its arguments.
 
 This `lambda` expression calls the function FN with arguments
 ARGS and returns its value.  Its own arguments are derived from
-symbols found in ARGS.  Each symbol from `%1` through `%9`, which
-appears in ARGS, is treated as a positional argument.  Missing
-arguments are named `_%N`, which keeps the byte-compiler quiet.
-In place of `%1` the shorthand `%` can be used, but only one of
-these two can appear in ARGS.  `%*` represents extra `&rest`
-arguments.
+symbols found in ARGS.
+
+Each symbol from `%1` through `%9`, which appears in ARGS,
+specifies an argument.  Each symbol from `&1` through `&9`, which
+appears in ARGS, specifies an optional argument.  All arguments
+following an optional argument have to be optional as well, thus
+their names have to begin with `&`.  Symbol `&*` specifies extra
+(`&rest`) arguments.
+
+Instead of `%1`, the shorthand `%` can be used; but that should
+only be done if it is the only argument, and using both `%1` and
+`%` is not allowed.  Likewise `&` can be substituted for `&1`.
+Finally, for backward compatibility, `%*` can be used in place
+of `&*`, but only if there are no optional arguments.
 
 Instead of:
 
 ```elisp
-(lambda (a _ c &rest d)
+(lambda (a _ &optional c &rest d)
   (foo a (bar c) d))
 ```
 
 you can use this macro and write:
 
 ```elisp
-(##foo % (bar %3) %*)
+(##foo %1 (bar &3) &*)
 ```
 
 which expands to:
 
 ```elisp
-(lambda (% _%2 %3 &rest %*)
-  (foo % (bar %3) %*))
+(lambda (%1 _%2 &optional &3 &rest %*)
+  (foo %1 (bar &3) %*))
 ```
 
 The name `##` was choosen because that allows (optionally)
diff --git a/llama.el b/llama.el
index 85ae9ed2e7..ca7ffbfa9d 100644
--- a/llama.el
+++ b/llama.el
@@ -36,26 +36,34 @@
 
 ;; This `lambda' expression calls the function FN with arguments
 ;; ARGS and returns its value.  Its own arguments are derived from
-;; symbols found in ARGS.  Each symbol from `%1' through `%9', which
-;; appears in ARGS, is treated as a positional argument.  Missing
-;; arguments are named `_%N', which keeps the byte-compiler quiet.
-;; In place of `%1' the shorthand `%' can be used, but only one of
-;; these two can appear in ARGS.  `%*' represents extra `&rest'
-;; arguments.
+;; symbols found in ARGS.
+
+;; Each symbol from `%1' through `%9', which appears in ARGS,
+;; specifies an argument.  Each symbol from `&1' through `&9', which
+;; appears in ARGS, specifies an optional argument.  All arguments
+;; following an optional argument have to be optional as well, thus
+;; their names have to begin with `&'.  Symbol `&*' specifies extra
+;; (`&rest') arguments.
+
+;; Instead of `%1', the shorthand `%' can be used; but that should
+;; only be done if it is the only argument, and using both `%1' and
+;; `%' is not allowed.  Likewise `&' can be substituted for `&1'.
+;; Finally, for backward compatibility, `%*' can be used in place
+;; of `&*', but only if there are no optional arguments.
 
 ;; Instead of:
 ;;
-;;   (lambda (a _ c &rest d)
+;;   (lambda (a _ &optional c &rest d)
 ;;     (foo a (bar c) d))
 ;;
 ;; you can use this macro and write:
 ;;
-;;   (##foo % (bar %3) %*)
+;;   (##foo %1 (bar %3) %*)
 ;;
 ;; which expands to:
 ;;
-;;   (lambda (% _%2 %3 &rest %*)
-;;     (foo % (bar %3) %*))
+;;   (lambda (%1 _%2 &optional %3 &rest %*)
+;;     (foo %1 (bar %3) %*))
 
 ;; The name `##' was choosen because that allows (optionally)
 ;; omitting the whitespace between it and the following symbol.
@@ -71,26 +79,34 @@
 
 This `lambda' expression calls the function FN with arguments
 ARGS and returns its value.  Its own arguments are derived from
-symbols found in ARGS.  Each symbol from `%1' through `%9', which
-appears in ARGS, is treated as a positional argument.  Missing
-arguments are named `_%N', which keeps the byte-compiler quiet.
-In place of `%1' the shorthand `%' can be used, but only one of
-these two can appear in ARGS.  `%*' represents extra `&rest'
-arguments.
+symbols found in ARGS.
+
+Each symbol from `%1' through `%9', which appears in ARGS,
+specifies an argument.  Each symbol from `&1' through `&9', which
+appears in ARGS, specifies an optional argument.  All arguments
+following an optional argument have to be optional as well, thus
+their names have to begin with `&'.  Symbol `&*' specifies extra
+(`&rest') arguments.
+
+Instead of `%1', the shorthand `%' can be used; but that should
+only be done if it is the only argument, and using both `%1' and
+`%' is not allowed.  Likewise `&' can be substituted for `&1'.
+Finally, for backward compatibility, `%*' can be used in place
+of `&*', but only if there are no optional arguments.
 
 Instead of:
 
-  (lambda (a _ c &rest d)
+  (lambda (a _ &optional c &rest d)
     (foo a (bar c) d))
 
 you can use this macro and write:
 
-  (##foo % (bar %3) %*)
+  (##foo %1 (bar &3) &*)
 
 which expands to:
 
-  (lambda (% _%2 %3 &rest %*)
-    (foo % (bar %3) %*))
+  (lambda (%1 _%2 &optional &3 &rest %*)
+    (foo %1 (bar &3) %*))
 
 The name `##' was choosen because that allows (optionally)
 omitting the whitespace between it and the following symbol.
@@ -103,25 +119,42 @@ It also looks a bit like #\\='function."
 (defun llama--arguments (data)
   (let ((args (make-vector 10 nil)))
     (llama--collect data args)
-    `(,@(let ((n 0))
-          (mapcar (lambda (symbol)
-                    (setq n (1+ n))
-                    (or symbol (intern (format "_%%%s" n))))
-                  (reverse (seq-drop-while
-                            'null
-                            (reverse (seq-subseq args 1))))))
-      ,@(and (aref args 0) '(&rest %*)))))
+    (let ((optional nil)
+          (pos 0))
+      (apply #'nconc
+             (mapcar
+              (lambda (symbol)
+                (setq pos (1+ pos))
+                (cond
+                 ((not symbol)
+                  (list (intern (format "_%s%s" (if optional "&" "%") pos))))
+                 ((eq (aref (symbol-name symbol) 0) ?%)
+                  (cond (optional
+                         (error "%s cannot follow optional argument" symbol))
+                        ((eq symbol '%*)
+                         (list '&rest symbol))
+                        ((list symbol))))
+                 ((eq symbol '&*)
+                  (list '&rest symbol))
+                 (optional
+                  (list symbol))
+                 ((setq optional t)
+                  (list '&optional symbol))))
+              (vconcat (reverse (seq-drop-while
+                                 #'null
+                                 (reverse (seq-subseq args 1))))
+                       (and-let* ((rest (aref args 0))) (list rest))))))))
 
 (defun llama--collect (data args)
   (cond
    ((symbolp data)
     (let ((name (symbol-name data)) pos)
       (save-match-data
-        (when (string-match "\\`%\\([1-9*]\\)?\\'" name)
+        (when (string-match "\\`[%&]\\([1-9*]\\)?\\'" name)
           (setq pos (match-string 1 name))
           (setq pos (cond ((equal pos "*") 0)
                           ((not pos) 1)
-                          (t (string-to-number pos))))
+                          ((string-to-number pos))))
           (when (and (= pos 1)
                      (aref args 1)
                      (not (equal data (aref args 1))))

Reply via email to