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 ()