master f856da36cd3: Allow evaluating Eshell forms using lexical binding

Jim Porter <[email protected]>
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit f856da36cd3b3406891bb81c48542a843424f442
Author: Jim Porter <[email protected]>
Commit: Jim Porter <[email protected]>

    Allow evaluating Eshell forms using lexical binding
    
    * lisp/eshell/esh-cmd.el (eshell-lexical-binding): New option.
    (eshell--eval): New defsubst...
    (eshell-do-eval, eshell-exec-lisp):
    * lisp/eshell/esh-var.el (eshell-parse-variable-ref):
    * lisp/eshell/em-banner.el (eshell-banner-initialize): ... use it.
    
    * lisp/eshell/em-ls.el (eshell-ls-applicable): Evaluate form using
    lexical binding.
    
    * test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test--binding-check): New
    function.
    (esh-cmd-test/lexical-binding, esh-cmd-test/dynamic-binding): New tests.
    
    * etc/NEWS: Announce this change.
---
 etc/NEWS                          |  5 +++++
 lisp/eshell/em-banner.el          |  3 ++-
 lisp/eshell/em-ls.el              |  2 +-
 lisp/eshell/esh-cmd.el            | 24 ++++++++++++++++--------
 lisp/eshell/esh-var.el            |  3 ++-
 test/lisp/eshell/esh-cmd-tests.el | 25 +++++++++++++++++++++++++
 6 files changed, 51 insertions(+), 11 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index c6da6290ac3..6f95672272b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -286,6 +286,11 @@ some other command:
 See the "(eshell) Lisp Pipelines" node in the Eshell manual for more
 details.
 
+---
+*** New user option 'eshell-lexical-binding'.
+When enabled, this causes Eshell to evaluate commands in Lisp syntax
+using lexical binding.
+
 
 * New Modes and Packages in Emacs 32.1
 
diff --git a/lisp/eshell/em-banner.el b/lisp/eshell/em-banner.el
index 72c14a242f7..b1193953042 100644
--- a/lisp/eshell/em-banner.el
+++ b/lisp/eshell/em-banner.el
@@ -41,6 +41,7 @@
 (eval-when-compile
   (require 'cl-lib))
 
+(require 'esh-cmd)
 (require 'esh-util)
 (require 'esh-mode)
 
@@ -77,7 +78,7 @@ This can be any sexp, and should end with at least two newlines."
   (unless eshell-non-interactive-p
     (cl-assert eshell-mode)
     (cl-assert eshell-banner-message)
-    (let ((msg (eval eshell-banner-message)))
+    (let ((msg (eshell--eval eshell-banner-message)))
       (cl-assert msg)
       (eshell-interactive-print msg))))
 
diff --git a/lisp/eshell/em-ls.el b/lisp/eshell/em-ls.el
index 373af9ab3bf..ffa2594e78a 100644
--- a/lisp/eshell/em-ls.el
+++ b/lisp/eshell/em-ls.el
@@ -212,7 +212,7 @@ calling FUNC with FILE as an argument."
 	    (not (eq (aref modes (+ ,index 3)) ?-)))
 	   (t
 	    ;; Otherwise call FUNC.
-	    (,(eval func) ,file)))))
+	    (,(eval func t) ,file)))))
 
 (defcustom eshell-ls-highlight-alist nil
   "This alist correlates test functions to color.
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 000c9329987..bb84651fff9 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -126,6 +126,10 @@ There are several different kinds of commands, however."
   "If non-nil, prefer Lisp functions to external commands."
   :type 'boolean)
 
+(defcustom eshell-lexical-binding lexical-binding
+  "If non-nil, use lexical binding when evaluating Eshell forms."
+  :type 'boolean)
+
 (defcustom eshell-lisp-regexp "\\([(`]\\|#'\\)"
   "A regexp which, if matched at beginning of an argument, means Lisp.
 Such arguments will be passed to `read', and then evaluated."
@@ -1141,6 +1145,10 @@ the form (:eshell-background . PROCESSES)."
            (eshell-always-debug-command 'form
              "done %s\n\n%s" ,tag-symbol (eshell-stringify ,form)))))))
 
+(defsubst eshell--eval (form)
+  "Evaluate FORM, respecting `eshell-lexical-binding'."
+  (eval form eshell-lexical-binding))
+
 (defun eshell-do-eval (form &optional synchronous-p)
   "Evaluate FORM, simplifying it as we go.
 Unless SYNCHRONOUS-P is non-nil, throws `eshell-defer' if it needs to
@@ -1155,7 +1163,7 @@ again.  Any forms preceding one that throw `eshell-defer' will
 have been replaced by constants."
   (cond
    ((not (listp form))
-    (list 'quote (eval form)))
+    (list 'quote (eshell--eval form)))
    ((memq (car form) '(quote function))
     form)
    (t
@@ -1232,10 +1240,10 @@ have been replaced by constants."
               (eshell-do-eval form synchronous-p)))))
        ((eq (car form) 'setcar)
 	(setcar (cdr args) (eshell-do-eval (cadr args) synchronous-p))
-	(eval form))
+	(eshell--eval form))
        ((eq (car form) 'setcdr)
 	(setcar (cdr args) (eshell-do-eval (cadr args) synchronous-p))
-	(eval form))
+	(eshell--eval form))
        ((eq (car form) 'let)
         (unless (eq (car-safe (cadr args)) 'eshell-do-eval)
           (eshell-manipulate form "evaluating let args"
@@ -1251,7 +1259,7 @@ have been replaced by constants."
                     (car args))
             ;; These expressions should all be constants now.
             (mapcar (lambda (binding)
-                      (when (consp binding) (eval (cadr binding))))
+                      (when (consp binding) (eshell--eval (cadr binding))))
                     (car args))
           (let (deferred result)
             ;; Evaluate the `let' body, catching `eshell-defer' so we
@@ -1291,7 +1299,7 @@ have been replaced by constants."
 	(unless (eq (caar args) 'eshell-do-eval)
           (eshell-manipulate form "handling special form"
 	    (setcar args `(eshell-do-eval ',(car args) ,synchronous-p))))
-	(eval form))
+	(eshell--eval form))
        ((eq (car form) 'unwind-protect)
         ;; `unwind-protect' has to be handled specially, because we
         ;; only want to call `eshell-do-eval' on its first form, and
@@ -1317,7 +1325,7 @@ have been replaced by constants."
 	(if (cddr args) (error "Unsupported form (setq X1 E1 X2 E2..)"))
         (eshell-manipulate form "evaluating arguments to setq"
           (setcar (cdr args) (eshell-do-eval (cadr args) synchronous-p)))
-	(list 'quote (eval form)))
+	(list 'quote (eshell--eval form)))
        (t
 	(if (and args (not (memq (car form) '(run-hooks))))
             (eshell-manipulate form
@@ -1358,7 +1366,7 @@ have been replaced by constants."
                  (new-form
                   (catch 'eshell-replace-command
                     (ignore
-                     (setq result (eval form))))))
+                     (setq result (eshell--eval form))))))
 	    (if new-form
 		(progn
                   (eshell-manipulate form "substituting replacement form"
@@ -1484,7 +1492,7 @@ case."
       (let ((result
              (save-current-buffer
                (if form-p
-                   (eval func-or-form)
+                   (eshell--eval func-or-form)
                  (apply func-or-form args)))))
         (and result (funcall printer result))
         result)
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index d79977a6dff..39cfdc206d2 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -593,7 +593,8 @@ Possible variable references are:
                                             (eshell-parse-double-quote)))))))
           (throw 'eshell-incomplete (concat "$" delim)))
         (when name
-          `(eshell-get-variable ,(eval name) indices ,eshell-current-quoted)))))
+          `(eshell-get-variable ,(eshell--eval name) indices
+                                ,eshell-current-quoted)))))
    ((assoc (char-to-string (char-after))
            eshell-variable-aliases-list)
     (forward-char)
diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el
index fd7df7e1d81..dfb040d8c8d 100644
--- a/test/lisp/eshell/esh-cmd-tests.el
+++ b/test/lisp/eshell/esh-cmd-tests.el
@@ -628,6 +628,31 @@ NAME is the name of the test case."
 (esh-cmd-test--deftest-invoke-directly complex-subcmd "echo {ls .}" nil)
 
 
+;; Lexical/dynamic binding
+
+(defun esh-cmd-test--binding-check ()
+  "Run a sequence of Eshell commands that depend on the binding type."
+  (unwind-protect
+      (with-temp-eshell
+        (eshell-insert-command "(defun test-function () test-value)")
+        (eshell-insert-command "(setq test-value 1)")
+        (eshell-insert-command "(let ((test-value 2)) (test-function))")
+        (eshell-last-output))
+    (with-no-warnings
+      (fmakunbound #'test-function)
+      (makunbound 'test-value))))
+
+(ert-deftest esh-cmd-test/lexical-binding ()
+  "Test that enabling `eshell-lexical-binding' works."
+  (let ((eshell-lexical-binding t))
+    (should (string-match-p "\\`1\n\\'" (esh-cmd-test--binding-check)))))
+
+(ert-deftest esh-cmd-test/dynamic-binding ()
+  "Test that disabling `eshell-lexical-binding' works."
+  (let ((eshell-lexical-binding nil))
+    (should (string-match-p "\\`2\n\\'" (esh-cmd-test--binding-check)))))
+
+
 ;; Error handling
 
 (ert-deftest esh-cmd-test/empty-background-command ()
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.