branch: elpa/with-simulated-input
commit 2f54a2550bd0555b719467f53af8ba030dcf554a
Author: Ryan C. Thompson <[email protected]>
Commit: Ryan C. Thompson <[email protected]>

    Clean up code for "with-simulated-input" macro
    
    Also improved warning messages a bit.
---
 with-simulated-input.el | 142 +++++++++++++++++++++++++++++++++---------------
 1 file changed, 99 insertions(+), 43 deletions(-)

diff --git a/with-simulated-input.el b/with-simulated-input.el
index 500c36b2ec..1f849157bf 100644
--- a/with-simulated-input.el
+++ b/with-simulated-input.el
@@ -158,6 +158,85 @@ functions, which are called only for their side effects)."
                           " "))))))
         (signal (car err) (cdr err))))))
 
+(defsubst wsi--looks-constant-p (expr)
+  "Return non-nil if EXPR looks like a constant expression.
+
+This function may return nil for some constant expressions, but if
+it returns non-nil, then EXPR is definitely constant.
+\"Constant\" means that EXPR will always evaluate to the same
+value and will never have side effects. In general, this means
+that EXPR consists only of calls to pure functions with constant
+arguments."
+  (pcase expr
+    ((pred hack-one-local-variable-constantp) t)
+    ;; Any symbol not matched by the above is a variable, i.e. not
+    ;; constant.
+    ((pred symbolp) nil)
+    ((pred atom) t)
+    ((pred functionp) t)
+    ;; Quoted expressions are constant
+    (`(quote ,x) t)
+    (`(function ,x) t)))
+
+(defsubst wsi--looks-pure-p (expr)
+  "Return non-nil if EXPR looks like a pure expression.
+
+In this context, \"pure\" means that the expression has no side
+effects and its value depends only on its arguments. In general,
+this means that EXPR consists only of calls to pure functions,
+constants, and variables. In particular, any constant expression
+
+This function may return nil for some pure expressions, but if it
+returns non-nil, then EXPR is definitely pure."
+  ;; TODO: Use the pure/side-effect-free symbol properties to more
+  ;; aggressively identify expressions that will not read input/have
+  ;; side effects.
+  (pcase expr
+    ((pred symbolp) t)
+    ((pred wsi--looks-constant-p) t)))
+
+(defsubst wsi--looks-input-free-p (expr)
+  "Return non-nil if EXPR definitely does not read input.
+
+This function may return nil for some expressions that don't read
+input, but if it returns non-nil, then EXPR definitely does not
+read input."
+  (wsi--looks-pure-p expr))
+
+(defun wsi--remove-irrelevant-keys (keys &optional quiet)
+  "Filter out irrelevant elements from KEYS.
+
+Helper function for `with-simulated-input'. The only relevant
+elements of KEYS are strings, nil, and expressions that will have
+side effects (e.g. `(insert \"hello\")'). Other elements are
+filtered out, and an appropriate warning is generated for each
+one unless QUIET is non-nil."
+  (cl-loop
+   for key in keys
+   if (stringp key) collect key
+   ;; It is occasionally useful to include nil as an element of
+   ;; KEYS, so we don't produce a warning for it.
+   else if (null key) do (ignore)
+   else if (wsi--looks-pure-p key) do
+   (unless quiet
+     (display-warning
+      'with-simulated-input-1
+      ;; Generate an appropriate warning message for the specific
+      ;; type of pure expression
+      (concat
+       "Non-string forms in KEYS are evaluated for side effects only. "
+       (cond
+        ((functionp key)
+         "Functions in KEYS have no effect unless they are called.")
+        ((wsi--looks-constant-p key)
+         "Non-string constants in KEYS have no effect.")
+        ((symbolp key)
+         "Variables in KEYS have no effect.")
+        (t
+         "Pure expressions in KEYS have no effect.")))))
+   ;; Anything else might be an expression with side effects.
+   else collect key))
+
 ;;;###autoload
 (defmacro with-simulated-input (keys &rest body)
   "Eval BODY forms with KEYS as simulated input.
@@ -210,13 +289,22 @@ in `progn'."
    ;; them except the last one, and there's no possibility that any
    ;; input will be read, so we can skip all the proprocessing and
    ;; just return the last element of BODY.
-   ((not (cl-find-if-not #'hack-one-local-variable-constantp body))
+   ((not (cl-find-if-not #'wsi--looks-constant-p body))
     (display-warning
      'with-simulated-input
      (if body
          "BODY consists of only constant expressions; KEYS will be ignored."
        "BODY is empty; KEYS will be ignored."))
     (car (last body)))
+   ;; This case applies when BODY is not constant, but *is* known not
+   ;; to contain any expressions that read input. In this case, all
+   ;; expressions in BODY need to be evaluated, but KEYS can still be
+   ;; ignored.
+   ((not (cl-find-if-not #'wsi--looks-input-free-p body))
+    (display-warning
+     'with-simulated-input
+     "BODY does not read input; KEYS will be ignored.")
+    `(progn ,@body))
    ;; If KEYS is nil, we don't have to do any pre-processing on it. We
    ;; still need to call `with-simulated-input-1', which will evaluate
    ;; BODY and throw an error if it tries to read input.
@@ -226,8 +314,9 @@ in `progn'."
         ,@body)
       nil))
    ;; If KEYS is a symbol, then it is a variable reference. This is
-   ;; kind of janky and should probably be deprecated, except possibly
-   ;; in the case where it evaluates to a string.
+   ;; supported if the value is a string or nil. (Other values are
+   ;; currently supported for backwards-compatibility, but are
+   ;; deprecated.)
    ((and keys (symbolp keys))
     `(cond
       ((null ,keys)
@@ -249,20 +338,9 @@ in `progn'."
         (lambda ()
           ,@body)
         (cl-loop
-           for key in ,keys
-           if (stringp key) collect key
-           ;; It is occasionally useful to include nil as an element of
-           ;; KEYS, so we don't produce a warning for it.
-           else if (null key) do (ignore)
-           else if (hack-one-local-variable-constantp key) do
-           (display-warning
-            'with-simulated-input-1
-            "Non-string forms in KEYS are evaluated for side effects only. 
Non-string constants in KEYS have no effect.")
-           else if (symbolp key) do
-           (display-warning
-            'with-simulated-input-1
-            "Non-string forms in KEYS are evaluated for side effects only. 
Variables in KEYS have no effect.")
-           else collect `(lambda () ,key))))
+         for key in (wsi--remove-irrelevant-keys ,keys)
+         if (stringp key) collect key
+         else if key collect `(lambda () ,key))))
       (t
        (error "KEYS must be a string or list, not %s: %s = %S"
               (type-of ,keys) ',keys ,keys))))
@@ -290,20 +368,9 @@ in `progn'."
           (lambda ()
             ,@body)
           (cl-loop
-           for key in ,evaluated-keys-sym
+           for key in (wsi--remove-irrelevant-keys ,evaluated-keys-sym)
            if (stringp key) collect key
-           ;; It is occasionally useful to include nil as an element of
-           ;; KEYS, so we don't produce a warning for it.
-           else if (null key) do (ignore)
-           else if (hack-one-local-variable-constantp key) do
-           (display-warning
-            'with-simulated-input-1
-            "Non-string forms in KEYS are evaluated for side effects only. 
Non-string constants in KEYS have no effect.")
-           else if (symbolp key) do
-           (display-warning
-            'with-simulated-input-1
-            "Non-string forms in KEYS are evaluated for side effects only. 
Variables in KEYS have no effect.")
-           else collect `(lambda () ,key))))))
+           else if key collect `(lambda () ,key))))))
    ;; The primary supported KEYS syntax: either a string, or an
    ;; un-quoted list of strings and list expressions to execute as
    ;; input.
@@ -319,20 +386,9 @@ in `progn'."
       (lambda ()
         ,@body)
       ,@(cl-loop
-         for key in keys
+         for key in (wsi--remove-irrelevant-keys keys)
          if (stringp key) collect key
-         ;; It is occasionally useful to include nil as an element of
-         ;; KEYS, so we don't produce a warning for it.
-         else if (null key) do (ignore)
-         else if (hack-one-local-variable-constantp key) do
-         (display-warning
-          'with-simulated-input-1
-          "Non-string forms in KEYS are evaluated for side effects only. 
Non-string constants in KEYS have no effect.")
-         else if (symbolp key) do
-         (display-warning
-          'with-simulated-input-1
-          "Non-string forms in KEYS are evaluated for side effects only. 
Variables in KEYS have no effect.")
-         else collect `(lambda () ,key))))))
+         else if key collect `(lambda () ,key))))))
 
 (defvar wsi-simulated-idle-time nil
   "The current simulated idle time.

Reply via email to