Function: eshell-do-eval
eshell-do-eval is a byte-compiled function defined in esh-cmd.el.gz.
Signature
(eshell-do-eval FORM &optional SYNCHRONOUS-P)
Documentation
Evaluate FORM, simplifying it as we go.
Unless SYNCHRONOUS-P is non-nil, throws eshell-defer if it needs to
be finished later after the completion of an asynchronous subprocess.
As this function evaluates FORM, it will gradually replace
subforms with the (quoted) result of evaluating them. For
example, a function call is replaced with the result of the call.
This allows us to resume evaluation of FORM after something
inside throws eshell-defer simply by calling this function
again. Any forms preceding one that throw eshell-defer will
have been replaced by constants.
Source Code
;; Defined in /usr/src/emacs/lisp/eshell/esh-cmd.el.gz
(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
be finished later after the completion of an asynchronous subprocess.
As this function evaluates FORM, it will gradually replace
subforms with the (quoted) result of evaluating them. For
example, a function call is replaced with the result of the call.
This allows us to resume evaluation of FORM after something
inside throws `eshell-defer' simply by calling this function
again. Any forms preceding one that throw `eshell-defer' will
have been replaced by constants."
(cond
((not (listp form))
(list 'quote (eval form)))
((memq (car form) '(quote function))
form)
(t
;; skip past the call to `eshell-do-eval'
(when (eq (car form) 'eshell-do-eval)
(setq form (cadr (cadr form))))
;; expand any macros directly into the form. This is done so that
;; we can modify any `let' forms to evaluate only once.
(if (macrop (car form))
(let ((exp (copy-tree (macroexpand form))))
(eshell-manipulate form
(format-message "expanding macro `%s'" (symbol-name (car form)))
(setcar form (car exp))
(setcdr form (cdr exp)))))
(let ((args (cdr form)))
(cond
((eq (car form) 'while)
;; Wrap the `while' form with let-bindings for the command and
;; test bodies. This helps us resume evaluation midway
;; through the loop.
(let ((new-form (copy-tree `(let ((eshell--command-body nil)
(eshell--test-body nil))
(eshell--wrapped-while ,@args)))))
(eshell-manipulate form "modifying while form"
(setcar form (car new-form))
(setcdr form (cdr new-form)))
(eshell-do-eval form synchronous-p)))
((eq (car form) 'eshell--wrapped-while)
(when eshell--command-body
(cl-assert (not synchronous-p))
(eshell-do-eval eshell--command-body)
(setq eshell--command-body nil
eshell--test-body nil))
;; `copy-tree' is needed here so that the test argument
;; doesn't get modified and thus always yield the same result.
(unless eshell--test-body
(setq eshell--test-body (copy-tree (car args))))
(while (cadr (eshell-do-eval eshell--test-body synchronous-p))
(setq eshell--command-body
(if (cddr args)
`(progn ,@(copy-tree (cdr args)))
(copy-tree (cadr args))))
(eshell-do-eval eshell--command-body synchronous-p)
(setq eshell--command-body nil
eshell--test-body (copy-tree (car args)))))
((eq (car form) 'if)
(eshell-manipulate form "evaluating if condition"
;; Evaluate the condition and replace our `if' form with
;; THEN or ELSE as appropriate.
(let ((new-form
(cond
((cadr (eshell-do-eval (car args) synchronous-p))
(cadr args)) ; COND is non-nil
((cdddr args)
`(progn ,@(cddr args))) ; Multiple ELSE forms
(t
(caddr args))))) ; Zero or one ELSE forms
(unless (consp new-form)
(setq new-form (cons 'progn new-form)))
(setcar form (car new-form))
(setcdr form (cdr new-form))))
(eshell-do-eval form synchronous-p))
((eq (car form) 'setcar)
(setcar (cdr args) (eshell-do-eval (cadr args) synchronous-p))
(eval form))
((eq (car form) 'setcdr)
(setcar (cdr args) (eshell-do-eval (cadr args) synchronous-p))
(eval form))
((eq (car form) 'let)
(unless (eq (car-safe (cadr args)) 'eshell-do-eval)
(eshell-manipulate form "evaluating let args"
(dolist (letarg (car args))
(when (and (listp letarg)
(not (eq (cadr letarg) 'quote)))
(setcdr letarg
(list (eshell-do-eval
(cadr letarg) synchronous-p)))))))
(cl-progv
(mapcar (lambda (binding)
(if (consp binding) (car binding) binding))
(car args))
;; These expressions should all be constants now.
(mapcar (lambda (binding)
(when (consp binding) (eval (cadr binding))))
(car args))
(let (deferred result)
;; Evaluate the `let' body, catching `eshell-defer' so we
;; can handle it below.
(setq deferred
(catch 'eshell-defer
(ignore (setq result (eshell-do-eval
(macroexp-progn (cdr args))
synchronous-p)))))
;; If something threw `eshell-defer', we need to update
;; the let-bindings' values so that those values are
;; correct when we resume evaluation of this form.
(when deferred
(eshell-manipulate form "rebinding let args after `eshell-defer'"
(let ((bindings (car args)))
(while bindings
(let ((binding (if (consp (car bindings))
(caar bindings)
(car bindings))))
(setcar bindings
(list binding
(list 'quote (symbol-value binding)))))
(pop bindings))))
(throw 'eshell-defer deferred))
;; If we get here, there was no `eshell-defer' thrown, so
;; just return the `let' body's result.
result)))
((memq (car form) '(catch condition-case))
;; `catch' and `condition-case' have to be handled specially,
;; because we only want to call `eshell-do-eval' on their
;; second forms.
;;
;; NOTE: This requires obedience by all forms which this
;; function might encounter, that they do not contain
;; other special forms.
(setq args (cdr args))
(unless (eq (caar args) 'eshell-do-eval)
(eshell-manipulate form "handling special form"
(setcar args `(eshell-do-eval ',(car args) ,synchronous-p))))
(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
;; we need to ensure we let `eshell-defer' through without
;; evaluating the unwind forms.
(let (deferred)
(unwind-protect
(eshell-manipulate form "handling `unwind-protect' body form"
(setq deferred
(catch 'eshell-defer
(ignore
(setcar args (eshell-do-eval
(car args) synchronous-p)))))
(car args))
(if deferred
(throw 'eshell-defer deferred)
(eshell-manipulate form "handling `unwind-protect' unwind forms"
(pop args)
(while args
(setcar args (eshell-do-eval (car args) synchronous-p))
(pop args)))))))
((eq (car form) 'setq)
(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)))
(t
(if (and args (not (memq (car form) '(run-hooks))))
(eshell-manipulate form
(format-message "evaluating arguments to `%s'"
(car form))
(while args
(setcar args (eshell-do-eval (car args) synchronous-p))
(setq args (cdr args)))))
(cond
((eq (car form) 'progn)
(car (last (cdr form))))
((eq (car form) 'prog1)
(cadr form))
(t
;; If a command desire to replace its execution form with
;; another command form, all it needs to do is throw the new
;; form using the exception tag `eshell-replace-command'.
;; For example, let's say that the form currently being
;; eval'd is:
;;
;; (eshell-named-command "hello")
;;
;; Now, let's assume the 'hello' command is an Eshell alias,
;; the definition of which yields the command:
;;
;; (eshell-named-command "echo" (list "Hello" "world"))
;;
;; What the alias code would like to do is simply substitute
;; the alias form for the original form. To accomplish
;; this, all it needs to do is to throw the substitution
;; form with the `eshell-replace-command' tag, and the form
;; will be replaced within the current command, and
;; execution will then resume (iteratively) as before.
;; Thus, aliases can even contain references to asynchronous
;; sub-commands, and things will still work out as they
;; should.
(let* (result
(new-form
(catch 'eshell-replace-command
(ignore
(setq result (eval form))))))
(if new-form
(progn
(eshell-manipulate form "substituting replacement form"
(setcar form (car new-form))
(setcdr form (cdr new-form)))
(eshell-do-eval form synchronous-p))
(if-let (((memq (car form) eshell-deferrable-commands))
(procs (eshell-make-process-list result)))
(if synchronous-p
(apply #'eshell/wait procs)
(eshell-manipulate form "inserting ignore form"
(setcar form 'ignore)
(setcdr form nil))
(when (seq-some #'eshell-process-active-p procs)
(throw 'eshell-defer procs)))
(list 'quote result))))))))))))