view lisp/eshell/esh-cmd.el @ 30842:955c8548cf76

*** empty log message ***
author Dave Love <fx@gnu.org>
date Wed, 16 Aug 2000 09:23:20 +0000
parents 0179b2540cf1
children 3099993cba0f
line wrap: on
line source

;;; esh-cmd --- command invocation

;; Copyright (C) 1999, 2000 Free Software Foundation

;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

(provide 'esh-cmd)

(eval-when-compile (require 'esh-maint))

(defgroup eshell-cmd nil
  "Executing an Eshell command is as simple as typing it in and
pressing <RET>.  There are several different kinds of commands,
however."
  :tag "Command invocation"
  :link '(info-link "(eshell)Command invocation")
  :group 'eshell)

;;; Commentary:

;;;_* Invoking external commands
;;
;; External commands cause processes to be created, by loading
;; external executables into memory.  This is what most normal shells
;; do, most of the time.  For more information, see [External commands].
;;
;;;_* Invoking Lisp functions
;;
;; A Lisp function can be invoked using Lisp syntax, or command shell
;; syntax.  For example, to run `dired' to edit the current directory:
;;
;;   /tmp $ (dired ".")
;;
;; Or:
;;
;;   /tmp $ dired .
;;
;; The latter form is preferable, but the former is more precise,
;; since it involves no translations.  See [Argument parsing], to
;; learn more about how arguments are transformed before passing them
;; to commands.
;;
;; Ordinarily, if 'dired' were also available as an external command,
;; the external version would be called in preference to any Lisp
;; function of the same name.  To change this behavior so that Lisp
;; functions always take precedence, set
;; `eshell-prefer-lisp-functions' to t.

(defcustom eshell-prefer-lisp-functions nil
  "*If non-nil, prefer Lisp functions to external commands."
  :type 'boolean
  :group 'eshell-cmd)

;;;_* Alias functions
;;
;; Whenever a command is specified using a simple name, such as 'ls',
;; Eshell will first look for a Lisp function of the name `eshell/ls'.
;; If it exists, it will be called in preference to any other command
;; which might have matched the name 'ls' (such as command aliases,
;; external commands, Lisp functions of that name, etc).
;;
;; This is the most flexible mechanism for creating new commands,
;; since it does not pollute the global namespace, yet allows you to
;; use all of Lisp's facilities to define that piece of functionality.
;; Most of Eshell's "builtin" commands are defined as alias functions.
;;
;;;_* Lisp arguments
;;
;; It is possible to invoke a Lisp form as an argument.  This can be
;; done either by specifying the form as you might in Lisp, or by
;; using the '$' character to introduce a value-interpolation:
;;
;;   echo (+ 1 2)
;;
;; Or
;;
;;   echo $(+ 1 2)
;;
;; The two forms are equivalent.  The second is required only if the
;; form being interpolated is within a string, or is a subexpression
;; of a larger argument:
;;
;;   echo x$(+ 1 2) "String $(+ 1 2)"
;;
;; To pass a Lisp symbol as a argument, use the alternate quoting
;; syntax, since the single quote character is far too overused in
;; shell syntax:
;;
;;   echo #'lisp-symbol
;;
;; Backquote can also be used:
;;
;;   echo `(list ,lisp-symbol)
;;
;; Lisp arguments are identified using the following regexp:

(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."
  :type 'regexp
  :group 'eshell-cmd)

;;;_* Command hooks
;;
;; There are several hooks involved with command execution, which can
;; be used either to change or augment Eshell's behavior.

(defcustom eshell-pre-command-hook nil
  "*A hook run before each interactive command is invoked."
  :type 'hook
  :group 'eshell-cmd)

(defcustom eshell-post-command-hook nil
  "*A hook run after each interactive command is invoked."
  :type 'hook
  :group 'eshell-cmd)

(defcustom eshell-prepare-command-hook nil
  "*A set of functions called to prepare a named command.
The command name and its argument are in `eshell-last-command-name'
and `eshell-last-arguments'.  The functions on this hook can change
the value of these symbols if necessary.

To prevent a command from executing at all, set
`eshell-last-command-name' to nil."
  :type 'hook
  :group 'eshell-cmd)

(defcustom eshell-named-command-hook nil
  "*A set of functions called before a named command is invoked.
Each function will be passed the command name and arguments that were
passed to `eshell-named-command'.

If any of the functions returns a non-nil value, the named command
will not be invoked, and that value will be returned from
`eshell-named-command'.

In order to substitute an alternate command form for execution, the
hook function should throw it using the tag `eshell-replace-command'.
For example:

  (add-hook 'eshell-named-command-hook 'subst-with-cd)
  (defun subst-with-cd (command args)
    (throw 'eshell-replace-command
	   (eshell-parse-command \"cd\" args)))

Although useless, the above code will cause any non-glob, non-Lisp
command (i.e., 'ls' as opposed to '*ls' or '(ls)') to be replaced by a
call to `cd' using the arguments that were passed to the function."
  :type 'hook
  :group 'eshell-cmd)

(defcustom eshell-pre-rewrite-command-hook
  '(eshell-no-command-conversion
    eshell-subcommand-arg-values)
  "*A hook run before command rewriting begins.
The terms of the command to be rewritten is passed as arguments, and
may be modified in place.  Any return value is ignored."
  :type 'hook
  :group 'eshell-cmd)

(defcustom eshell-rewrite-command-hook
  '(eshell-rewrite-for-command
    eshell-rewrite-while-command
    eshell-rewrite-if-command
    eshell-rewrite-sexp-command
    eshell-rewrite-initial-subcommand
    eshell-rewrite-named-command)
  "*A set of functions used to rewrite the command argument.
Once parsing of a command line is completed, the next step is to
rewrite the initial argument into something runnable.

A module may wish to associate special behavior with certain argument
syntaxes at the beginning of a command line.  They are welcome to do
so by adding a function to this hook.  The first function to return a
substitute command form is the one used.  Each function is passed the
command's full argument list, which is a list of sexps (typically
forms or strings)."
  :type 'hook
  :group 'eshell-cmd)

(defcustom eshell-post-rewrite-command-hook nil
  "*A hook run after command rewriting is finished.
Each function is passed the symbol containing the rewritten command,
which may be modified directly.  Any return value is ignored."
  :type 'hook
  :group 'eshell-cmd)

;;; Code:

(require 'esh-util)
(unless (eshell-under-xemacs-p)
  (require 'eldoc))
(require 'esh-arg)
(require 'esh-proc)
(require 'esh-ext)

;;; User Variables:

(defcustom eshell-cmd-load-hook '(eshell-cmd-initialize)
  "*A hook that gets run when `eshell-cmd' is loaded."
  :type 'hook
  :group 'eshell-cmd)

(defcustom eshell-debug-command nil
  "*If non-nil, enable debugging code.  SSLLOOWW.
This option is only useful for reporting bugs.  If you enable it, you
will have to visit the file 'eshell-cmd.el' and run the command
\\[eval-buffer]."
  :type 'boolean
  :group 'eshell-cmd)

(defcustom eshell-deferrable-commands
  '(eshell-named-command
    eshell-lisp-command
    eshell-process-identity)
  "*A list of functions which might return an ansychronous process.
If they return a process object, execution of the calling Eshell
command will wait for completion (in the background) before finishing
the command."
  :type '(repeat function)
  :group 'eshell-cmd)

(defcustom eshell-subcommand-bindings
  '((eshell-in-subcommand-p t)
    (default-directory default-directory)
    (process-environment (eshell-copy-environment)))
  "*A list of `let' bindings for subcommand environments."
  :type 'sexp
  :group 'eshell-cmd)

(put 'risky-local-variable 'eshell-subcommand-bindings t)

(defvar eshell-ensure-newline-p nil
  "If non-nil, ensure that a newline is emitted after a Lisp form.
This can be changed by Lisp forms that are evaluated from the Eshell
command line.")

;;; Internal Variables:

(defvar eshell-current-command nil)
(defvar eshell-command-name nil)
(defvar eshell-command-arguments nil)
(defvar eshell-in-pipeline-p nil)
(defvar eshell-in-subcommand-p nil)
(defvar eshell-last-arguments nil)
(defvar eshell-last-command-name nil)
(defvar eshell-last-async-proc nil
  "When this foreground process completes, resume command evaluation.")

;;; Functions:

(defsubst eshell-interactive-process ()
  "Return currently running command process, if non-Lisp."
  eshell-last-async-proc)

(defun eshell-cmd-initialize ()
  "Initialize the Eshell command processing module."
  (set (make-local-variable 'eshell-current-command) nil)
  (set (make-local-variable 'eshell-command-name) nil)
  (set (make-local-variable 'eshell-command-arguments) nil)
  (set (make-local-variable 'eshell-last-arguments) nil)
  (set (make-local-variable 'eshell-last-command-name) nil)
  (set (make-local-variable 'eshell-last-async-proc) nil)

  (make-local-hook 'eshell-kill-hook)
  (add-hook 'eshell-kill-hook 'eshell-resume-command nil t)

  ;; make sure that if a command is over, and no process is being
  ;; waited for, that `eshell-current-command' is set to nil.  This
  ;; situation can occur, for example, if a Lisp function results in
  ;; `debug' being called, and the user then types \\[top-level]
  (make-local-hook 'eshell-post-command-hook)
  (add-hook 'eshell-post-command-hook
	    (function
	     (lambda ()
	       (setq eshell-current-command nil
		     eshell-last-async-proc nil))) nil t)

  (make-local-hook 'eshell-parse-argument-hook)
  (add-hook 'eshell-parse-argument-hook
	    'eshell-parse-subcommand-argument nil t)
  (add-hook 'eshell-parse-argument-hook
	    'eshell-parse-lisp-argument nil t)

  (when (eshell-using-module 'eshell-cmpl)
    (make-local-hook 'pcomplete-try-first-hook)
    (add-hook 'pcomplete-try-first-hook
	      'eshell-complete-lisp-symbols nil t)))

(eshell-deftest var last-result-var
  "\"last result\" variable"
  (eshell-command-result-p "+ 1 2; + $$ 2" "3\n5\n"))

(eshell-deftest var last-result-var2
  "\"last result\" variable"
  (eshell-command-result-p "+ 1 2; + $$ $$" "3\n6\n"))

(eshell-deftest var last-arg-var
  "\"last arg\" variable"
  (eshell-command-result-p "+ 1 2; + $_ 4" "3\n6\n"))

(defun eshell-complete-lisp-symbols ()
  "If there is a user reference, complete it."
  (let ((arg (pcomplete-actual-arg)))
    (when (string-match (concat "\\`" eshell-lisp-regexp) arg)
      (setq pcomplete-stub (substring arg (match-end 0))
	    pcomplete-last-completion-raw t)
      (throw 'pcomplete-completions
	     (all-completions pcomplete-stub obarray 'boundp)))))

;; Command parsing

(defun eshell-parse-command (command &optional args top-level)
  "Parse the COMMAND, adding ARGS if given.
COMMAND can either be a string, or a cons cell demarcating a buffer
region.  TOP-LEVEL, if non-nil, means that the outermost command (the
user's input command) is being parsed, and that pre and post command
hooks should be run before and after the command."
  (let* (sep-terms
	 (terms
	  (append
	   (if (consp command)
	       (eshell-parse-arguments (car command) (cdr command))
	     (let ((here (point))
		   (inhibit-point-motion-hooks t)
		   after-change-functions)
	       (insert command)
	       (prog1
		   (eshell-parse-arguments here (point))
		 (delete-region here (point)))))
	   args))
	 (commands
	  (mapcar
	   (function
	    (lambda (cmd)
	      (if (or (not (car sep-terms))
		      (string= (car sep-terms) ";"))
		  (setq cmd
			(eshell-parse-pipeline cmd (not (car sep-terms))))
		(setq cmd
		      (list 'eshell-do-subjob
			    (list 'list (eshell-parse-pipeline cmd)))))
	      (setq sep-terms (cdr sep-terms))
	      (if eshell-in-pipeline-p
		  cmd
		(list 'eshell-trap-errors cmd))))
	   (eshell-separate-commands terms "[&;]" nil 'sep-terms))))
    (let ((cmd commands))
      (while cmd
	(if (cdr cmd)
	    (setcar cmd (list 'eshell-commands (car cmd))))
	(setq cmd (cdr cmd))))
    (setq commands
	  (append (list 'progn)
		  (if top-level
		      (list '(run-hooks 'eshell-pre-command-hook)))
		  (if (not top-level)
		      commands
		    (list
		     (list 'catch (quote 'top-level)
			   (append (list 'progn) commands))
		     '(run-hooks 'eshell-post-command-hook)))))
    (if top-level
	(list 'eshell-commands commands)
      commands)))

(defun eshell-debug-show-parsed-args (terms)
  "Display parsed arguments in the debug buffer."
  (ignore
   (if eshell-debug-command
       (eshell-debug-command "parsed arguments" terms))))

(defun eshell-no-command-conversion (terms)
  "Don't convert the command argument."
  (ignore
   (if (and (listp (car terms))
	    (eq (caar terms) 'eshell-convert))
       (setcar terms (cadr (car terms))))))

(defun eshell-subcommand-arg-values (terms)
  "Convert subcommand arguments {x} to ${x}, in order to take their values."
  (setq terms (cdr terms))		; skip command argument
  (while terms
    (if (and (listp (car terms))
	     (eq (caar terms) 'eshell-as-subcommand))
	(setcar terms (list 'eshell-convert
			    (list 'eshell-command-to-value
				  (car terms)))))
    (setq terms (cdr terms))))

(defun eshell-rewrite-sexp-command (terms)
  "Rewrite a sexp in initial position, such as '(+ 1 2)'."
  ;; this occurs when a Lisp expression is in first position
  (if (and (listp (car terms))
	   (eq (caar terms) 'eshell-command-to-value))
      (car (cdar terms))))

(eshell-deftest cmd lisp-command
  "Evaluate Lisp command"
  (eshell-command-result-p "(+ 1 2)" "3"))

(eshell-deftest cmd lisp-command-args
  "Evaluate Lisp command (ignore args)"
  (eshell-command-result-p "(+ 1 2) 3" "3"))

(defun eshell-rewrite-initial-subcommand (terms)
  "Rewrite a subcommand in initial position, such as '{+ 1 2}'."
  (if (and (listp (car terms))
	   (eq (caar terms) 'eshell-as-subcommand))
      (car terms)))

(eshell-deftest cmd subcommand
  "Run subcommand"
  (eshell-command-result-p "{+ 1 2}" "3\n"))

(eshell-deftest cmd subcommand-args
  "Run subcommand (ignore args)"
  (eshell-command-result-p "{+ 1 2} 3" "3\n"))

(eshell-deftest cmd subcommand-lisp
  "Run subcommand + Lisp form"
  (eshell-command-result-p "{(+ 1 2)}" "3\n"))

(defun eshell-rewrite-named-command (terms)
  "If no other rewriting rule transforms TERMS, assume a named command."
  (list (if eshell-in-pipeline-p
	    'eshell-named-command*
	  'eshell-named-command)
	(car terms)
	(and (cdr terms)
	     (append (list 'list) (cdr terms)))))

(eshell-deftest cmd named-command
  "Execute named command"
  (eshell-command-result-p "+ 1 2" "3\n"))

(eval-when-compile
  (defvar eshell-command-body)
  (defvar eshell-test-body))

(defsubst eshell-invokify-arg (arg &optional share-output silent)
  "Change ARG so it can be invoked from a structured command.

SHARE-OUTPUT, if non-nil, means this invocation should share the
current output stream, which is separately redirectable.  SILENT
means the user and/or any redirections shouldn't see any output
from this command.  If both SHARE-OUTPUT and SILENT are non-nil,
the second is ignored."
  ;; something that begins with `eshell-convert' means that it
  ;; intends to return a Lisp value.  We want to get past this,
  ;; but if it's not _actually_ a value interpolation -- in which
  ;; we leave it alone.  In fact, the only time we muck with it
  ;; is in the case of a {subcommand} that has been turned into
  ;; the interpolation, ${subcommand}, by the parser because it
  ;; didn't know better.
  (if (and (listp arg)
	   (eq (car arg) 'eshell-convert)
	   (eq (car (cadr arg)) 'eshell-command-to-value))
      (if share-output
	  (cadr (cadr arg))
	(list 'eshell-commands (cadr (cadr arg))
	      silent))
    arg))

(defun eshell-rewrite-for-command (terms)
  "Rewrite a `for' command into its equivalent Eshell command form.
Because the implementation of `for' relies upon conditional evaluation
of its argumbent (i.e., use of a Lisp special form), it must be
implemented via rewriting, rather than as a function."
  (if (and (stringp (car terms))
	   (string= (car terms) "for")
	   (stringp (nth 2 terms))
	   (string= (nth 2 terms) "in"))
      (let ((body (car (last terms))))
	(setcdr (last terms 2) nil)
	(list
	 'let (list (list 'for-items
			  (append
			   (list 'append)
			   (mapcar
			    (function
			     (lambda (elem)
			       (if (listp elem)
				   elem
				 (list 'list elem))))
			    (cdr (cddr terms)))))
		    (list 'eshell-command-body
			  (list 'quote (list nil)))
		    (list 'eshell-test-body
			  (list 'quote (list nil))))
	 (list
	  'progn
	  (list
	   'while (list 'car (list 'symbol-value
				   (list 'quote 'for-items)))
	   (list
	    'progn
	    (list 'let
		  (list (list (intern (cadr terms))
			      (list 'car
				    (list 'symbol-value
					  (list 'quote 'for-items)))))
		  (list 'eshell-protect
			(eshell-invokify-arg body t)))
	    (list 'setcar 'for-items
		  (list 'cadr
			(list 'symbol-value
			      (list 'quote 'for-items))))
	    (list 'setcdr 'for-items
		  (list 'cddr
			(list 'symbol-value
			      (list 'quote 'for-items))))))
	  (list 'eshell-close-handles
		'eshell-last-command-status
		(list 'list (quote 'quote)
		      'eshell-last-command-result)))))))

(defun eshell-structure-basic-command (func names keyword test body
					    &optional else vocal-test)
  "With TERMS, KEYWORD, and two NAMES, structure a basic command.
The first of NAMES should be the positive form, and the second the
negative.  It's not likely that users should ever need to call this
function.

If VOCAL-TEST is non-nil, it means output from the test should be
shown, as well as output from the body."
  ;; If the test form begins with `eshell-convert', it means
  ;; something data-wise will be returned, and we should let
  ;; that determine the truth of the statement.
  (unless (eq (car test) 'eshell-convert)
    (setq test
	  (list 'progn test
		(list 'eshell-exit-success-p))))

  ;; should we reverse the sense of the test?  This depends
  ;; on the `names' parameter.  If it's the symbol nil, yes.
  ;; Otherwise, it can be a pair of strings; if the keyword
  ;; we're using matches the second member of that pair (a
  ;; list), we should reverse it.
  (if (or (eq names nil)
	  (and (listp names)
	       (string= keyword (cadr names))))
      (setq test (list 'not test)))

  ;; finally, create the form that represents this structured
  ;; command
  (list
   'let (list (list 'eshell-command-body
		    (list 'quote (list nil)))
	      (list 'eshell-test-body
		    (list 'quote (list nil))))
   (list func test body else)
   (list 'eshell-close-handles
	 'eshell-last-command-status
	 (list 'list (quote 'quote)
	       'eshell-last-command-result))))

(defun eshell-rewrite-while-command (terms)
  "Rewrite a `while' command into its equivalent Eshell command form.
Because the implementation of `while' relies upon conditional
evaluation of its argument (i.e., use of a Lisp special form), it
must be implemented via rewriting, rather than as a function."
  (if (and (stringp (car terms))
	   (member (car terms) '("while" "until")))
      (eshell-structure-basic-command
       'while '("while" "until") (car terms)
       (eshell-invokify-arg (cadr terms) nil t)
       (list 'eshell-protect
	     (eshell-invokify-arg (car (last terms)) t)))))

(defun eshell-rewrite-if-command (terms)
  "Rewrite an `if' command into its equivalent Eshell command form.
Because the implementation of `if' relies upon conditional
evaluation of its argument (i.e., use of a Lisp special form), it
must be implemented via rewriting, rather than as a function."
  (if (and (stringp (car terms))
	   (member (car terms) '("if" "unless")))
      (eshell-structure-basic-command
       'if '("if" "unless") (car terms)
       (eshell-invokify-arg (cadr terms) nil t)
       (eshell-invokify-arg
	(if (= (length terms) 5)
	    (car (last terms 3))
	  (car (last terms))) t)
       (eshell-invokify-arg
	(if (= (length terms) 5)
	    (car (last terms))) t))))

(defun eshell-exit-success-p ()
  "Return non-nil if the last command was \"successful\".
For a bit of Lisp code, this means a return value of non-nil.
For an external command, it means an exit code of 0."
  (if (string= eshell-last-command-name "#<Lisp>")
      eshell-last-command-result
    (= eshell-last-command-status 0)))

(defun eshell-parse-pipeline (terms &optional final-p)
  "Parse a pipeline from TERMS, return the appropriate Lisp forms."
  (let* (sep-terms
	 (bigpieces (eshell-separate-commands terms "\\(&&\\|||\\)"
					      nil 'sep-terms))
	 (bp bigpieces)
	 (results (list t))
	 final)
    (while bp
      (let ((subterms (car bp)))
	(let* ((pieces (eshell-separate-commands subterms "|"))
	       (p pieces))
	  (while p
	    (let ((cmd (car p)))
	      (run-hook-with-args 'eshell-pre-rewrite-command-hook cmd)
	      (setq cmd (run-hook-with-args-until-success
			 'eshell-rewrite-command-hook cmd))
	      (run-hook-with-args 'eshell-post-rewrite-command-hook 'cmd)
	      (setcar p cmd))
	    (setq p (cdr p)))
	  (nconc results
		 (list
		  (if (<= (length pieces) 1)
		      (car pieces)
		    (assert (not eshell-in-pipeline-p))
		    (list 'eshell-execute-pipeline
			  (list 'quote pieces))))))
	(setq bp (cdr bp))))
    ;; `results' might be empty; this happens in the case of
    ;; multi-line input
    (setq results (cdr results)
	  results (nreverse results)
	  final (car results)
	  results (cdr results)
	  sep-terms (nreverse sep-terms))
    (while results
      (assert (car sep-terms))
      (setq final (eshell-structure-basic-command
		   'if (string= (car sep-terms) "&&") "if"
		   (list 'eshell-commands (car results))
		   final
		   nil t)
	    results (cdr results)
	    sep-terms (cdr sep-terms)))
    final))

(defun eshell-parse-subcommand-argument ()
  "Parse a subcommand argument of the form '{command}'."
  (if (and (not eshell-current-argument)
	   (not eshell-current-quoted)
	   (eq (char-after) ?\{)
	   (or (= (point-max) (1+ (point)))
	       (not (eq (char-after (1+ (point))) ?\}))))
      (let ((end (eshell-find-delimiter ?\{ ?\})))
	(if (not end)
	    (throw 'eshell-incomplete ?\{)
	  (when (eshell-arg-delimiter (1+ end))
	    (prog1
		(list 'eshell-as-subcommand
		      (eshell-parse-command (cons (1+ (point)) end)))
	      (goto-char (1+ end))))))))

(defun eshell-parse-lisp-argument ()
  "Parse a Lisp expression which is specified as an argument."
  (if (and (not eshell-current-argument)
	   (not eshell-current-quoted)
	   (looking-at eshell-lisp-regexp))
      (let* ((here (point))
	     (obj
	      (condition-case err
		  (read (current-buffer))
		(end-of-file
		 (throw 'eshell-incomplete ?\()))))
	(if (eshell-arg-delimiter)
	    (list 'eshell-command-to-value
		  (list 'eshell-lisp-command (list 'quote obj)))
	  (ignore (goto-char here))))))

(defun eshell-separate-commands
  (terms separator &optional reversed last-terms-sym)
  "Separate TERMS using SEPARATOR.
If REVERSED is non-nil, the list of separated term groups will be
returned in reverse order.  If LAST-TERMS-SYM is a symbol, it's value
will be set to a list of all the separator operators found (or '(list
nil)' if none)."
  (let ((sub-terms (list t))
	(eshell-sep-terms (list t))
	subchains)
    (while terms
      (if (and (consp (car terms))
	       (eq (caar terms) 'eshell-operator)
	       (string-match (concat "^" separator "$")
			     (nth 1 (car terms))))
	  (progn
	    (nconc eshell-sep-terms (list (nth 1 (car terms))))
	    (setq subchains (cons (cdr sub-terms) subchains)
		  sub-terms (list t)))
	(nconc sub-terms (list (car terms))))
      (setq terms (cdr terms)))
    (if (> (length sub-terms) 1)
	(setq subchains (cons (cdr sub-terms) subchains)))
    (if reversed
	(progn
	  (if last-terms-sym
	      (set last-terms-sym (reverse (cdr eshell-sep-terms))))
	  subchains)                    ; already reversed
      (if last-terms-sym
	  (set last-terms-sym (cdr eshell-sep-terms)))
      (nreverse subchains))))

;;_* Command evaluation macros
;;
;; The structure of the following macros is very important to
;; `eshell-do-eval' [Iterative evaluation]:
;;
;; @ Don't use forms that conditionally evaluate their arguments, such
;;   as `setq', `if', `while', `let*', etc.  The only special forms
;;   that can be used are `let', `condition-case' and
;;   `unwind-protect'.
;;
;; @ The main body of a `let' can contain only one form.  Use `progn'
;;   if necessary.
;;
;; @ The two `special' variables are `eshell-current-handles' and
;;   `eshell-current-subjob-p'.  Bind them locally with a `let' if you
;;   need to change them.  Change them directly only if your intention
;;   is to change the calling environment.

(defmacro eshell-do-subjob (object)
  "Evaluate a command OBJECT as a subjob.
We indicate thet the process was run in the background by returned it
ensconced in a list."
  `(let ((eshell-current-subjob-p t))
     ,object))

(defmacro eshell-commands (object &optional silent)
  "Place a valid set of handles, and context, around command OBJECT."
  `(let ((eshell-current-handles
	  (eshell-create-handles ,(not silent) 'append))
	 eshell-current-subjob-p)
     ,object))

(defmacro eshell-trap-errors (object)
  "Trap any errors that occur, so they are not entirely fatal.
Also, the variable `eshell-this-command-hook' is available for the
duration of OBJECT's evaluation.  Note that functions should be added
to this hook using `nconc', and *not* `add-hook'.

Someday, when Scheme will become the dominant Emacs language, all of
this grossness will be made to disappear by using `call/cc'..."
  `(let ((eshell-this-command-hook (list 'ignore)))
     (eshell-condition-case err
	 (prog1
	     ,object
	   (run-hooks 'eshell-this-command-hook))
       (error
	(run-hooks 'eshell-this-command-hook)
	(eshell-errorn (error-message-string err))
	(eshell-close-handles 1)))))

(defmacro eshell-protect (object)
  "Protect I/O handles, so they aren't get closed after eval'ing OBJECT."
  `(progn
     (eshell-protect-handles eshell-current-handles)
     ,object))

(defmacro eshell-do-pipelines (pipeline)
  "Execute the commands in PIPELINE, connecting each to one another."
  (when (setq pipeline (cadr pipeline))
    `(let ((eshell-current-handles
	    (eshell-create-handles
	     (car (aref eshell-current-handles
			eshell-output-handle)) nil
	     (car (aref eshell-current-handles
			eshell-error-handle)) nil)))
       (progn
	 ,(when (cdr pipeline)
	    `(let (nextproc)
	       (progn
		 (set 'nextproc
		      (eshell-do-pipelines (quote ,(cdr pipeline))))
		 (eshell-set-output-handle ,eshell-output-handle
					   'append nextproc)
		 (eshell-set-output-handle ,eshell-error-handle
					   'append nextproc)
		 (set 'tailproc (or tailproc nextproc)))))
	 ,(let ((head (car pipeline)))
	    (if (memq (car head) '(let progn))
		(setq head (car (last head))))
	    (when (memq (car head) eshell-deferrable-commands)
	      (ignore
	       (setcar head
		       (intern-soft
			(concat (symbol-name (car head)) "*"))))))
	 ,(car pipeline)))))

(defalias 'eshell-process-identity 'identity)

(defmacro eshell-execute-pipeline (pipeline)
  "Execute the commands in PIPELINE, connecting each to one another."
  `(let ((eshell-in-pipeline-p t) tailproc)
     (progn
       (eshell-do-pipelines ,pipeline)
       (eshell-process-identity tailproc))))

(defmacro eshell-as-subcommand (command)
  "Execute COMMAND using a temp buffer.
This is used so that certain Lisp commands, such as `cd', when
executed in a subshell, do not disturb the environment of the main
Eshell buffer."
  `(let ,eshell-subcommand-bindings
     ,command))

(defmacro eshell-do-command-to-value (object)
  "Run a subcommand prepared by `eshell-command-to-value'.
This avoids the need to use `let*'."
  `(let ((eshell-current-handles
	  (eshell-create-handles value 'overwrite)))
     (progn
       ,object
       (symbol-value value))))

(defmacro eshell-command-to-value (object)
  "Run OBJECT synchronously, returning its result as a string.
Returns a string comprising the output from the command."
  `(let ((value (make-symbol "eshell-temp")))
     (eshell-do-command-to-value ,object)))

;;;_* Iterative evaluation
;;
;; Eshell runs all of its external commands asynchronously, so that
;; Emacs is not blocked while the operation is being performed.
;; However, this introduces certain synchronization difficulties,
;; since the Lisp code, once it returns, will not "go back" to finish
;; executing the commands which haven't yet been started.
;;
;; What Eshell does to work around this problem (basically, the lack
;; of threads in Lisp), is that it evaluates the command sequence
;; iteratively.  Whenever an asynchronous process is begun, evaluation
;; terminates and control is given back to Emacs.  When that process
;; finishes, it will resume the evaluation using the remainder of the
;; command tree.

(defun eshell/eshell-debug (&rest args)
  "A command for toggling certain debug variables."
  (ignore
   (cond
    ((not args)
     (if eshell-handle-errors
	 (eshell-print "errors\n"))
     (if eshell-debug-command
	 (eshell-print "commands\n")))
    ((or (string= (car args) "-h")
	 (string= (car args) "--help"))
     (eshell-print "usage: eshell-debug [kinds]

This command is used to aid in debugging problems related to Eshell
itself.  It is not useful for anything else.  The recognized `kinds'
at the moment are:

  errors       stops Eshell from trapping errors
  commands     shows command execution progress in `*eshell last cmd*'
"))
    (t
     (while args
       (cond
	((string= (car args) "errors")
	 (setq eshell-handle-errors (not eshell-handle-errors)))
	((string= (car args) "commands")
	 (setq eshell-debug-command (not eshell-debug-command))))
       (setq args (cdr args)))))))

(defun pcomplete/eshell-mode/eshell-debug ()
  "Completion for the `debug' command."
  (while (pcomplete-here '("errors" "commands"))))

(defun eshell-debug-command (tag subform)
  "Output a debugging message to '*eshell last cmd*'."
  (let ((buf (get-buffer-create "*eshell last cmd*"))
	(text (eshell-stringify eshell-current-command)))
    (save-excursion
      (set-buffer buf)
      (if (not tag)
	  (erase-buffer)
	(insert "\n\C-l\n" tag "\n\n" text
		(if subform
		    (concat "\n\n" (eshell-stringify subform)) ""))))))

(defun eshell-eval-command (command &optional input)
  "Evaluate the given COMMAND iteratively."
  (if eshell-current-command
      ;; we can just stick the new command at the end of the current
      ;; one, and everything will happen as it should
      (setcdr (last (cdr eshell-current-command))
	      (list (list 'let '((here (and (eobp) (point))))
			  (and input
			       (list 'insert-and-inherit
				     (concat input "\n")))
			  '(if here
			       (eshell-update-markers here))
			  (list 'eshell-do-eval
				(list 'quote command)))))
    (and eshell-debug-command
	 (save-excursion
	   (let ((buf (get-buffer-create "*eshell last cmd*")))
	     (set-buffer buf)
	     (erase-buffer)
	     (insert "command: \"" input "\"\n"))))
    (setq eshell-current-command command)
    (eshell-resume-eval)))

(defun eshell-resume-command (proc status)
  "Resume the current command when a process ends."
  (when proc
    (unless (or (string= "stopped" status)
		(string-match eshell-reset-signals status))
      (if (eq proc (eshell-interactive-process))
	  (eshell-resume-eval)))))

(defun eshell-resume-eval ()
  "Destructively evaluate a form which may need to be deferred."
  (eshell-condition-case err
      (progn
	(setq eshell-last-async-proc nil)
	(when eshell-current-command
	  (let* (retval
		 (proc (catch 'eshell-defer
			 (ignore
			  (setq retval
				(eshell-do-eval
				 eshell-current-command))))))
	    (if proc
		(ignore (setq eshell-last-async-proc proc))
	      (cadr retval)))))
    (error
     (error (error-message-string err)))))

(defmacro eshell-manipulate (tag &rest commands)
  "Manipulate a COMMAND form, with TAG as a debug identifier."
  (if (not eshell-debug-command)
      `(progn ,@commands)
    `(progn
       (eshell-debug-command ,(eval tag) form)
       ,@commands
       (eshell-debug-command ,(concat "done " (eval tag)) form))))

(put 'eshell-manipulate 'lisp-indent-function 1)

;; eshell-lookup-function, eshell-functionp, and eshell-macrop taken
;; from edebug

(defsubst eshell-lookup-function (object)
  "Return the ultimate function definition of OBJECT."
  (while (and (symbolp object) (fboundp object))
    (setq object (symbol-function object)))
  object)

(defconst function-p-func
  (if (eshell-under-xemacs-p)
      'compiled-function-p
    'byte-code-function-p))

(defsubst eshell-functionp (object)
  "Returns the function named by OBJECT, or nil if it is not a function."
  (setq object (eshell-lookup-function object))
  (if (or (subrp object)
	  (funcall function-p-func object)
	  (and (listp object)
	       (eq (car object) 'lambda)
	       (listp (car (cdr object)))))
      object))

(defsubst eshell-macrop (object)
  "Return t if OBJECT is a macro or nil otherwise."
  (setq object (eshell-lookup-function object))
  (if (and (listp object)
	   (eq 'macro (car object))
	   (eshell-functionp (cdr object)))
      t))

(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."
  (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 (eshell-macrop (car form))
	(let ((exp (eshell-copy-tree (macroexpand form))))
	  (eshell-manipulate (format "expanding macro `%s'"
				     (symbol-name (car form)))
	    (setcar form (car exp))
	    (setcdr form (cdr exp)))))
    (let ((args (cdr form)))
      (cond
       ((eq (car form) 'while)
	;; `eshell-copy-tree' is needed here so that the test argument
	;; doesn't get modified and thus always yield the same result.
	(when (car eshell-command-body)
	  (assert (not synchronous-p))
	  (eshell-do-eval (car eshell-command-body))
	  (setcar eshell-command-body nil))
	(unless (car eshell-test-body)
	  (setcar eshell-test-body (eshell-copy-tree (car args))))
	(if (and (car eshell-test-body)
		 (not (eq (car eshell-test-body) 0)))
	    (while (cadr (eshell-do-eval (car eshell-test-body)))
	      (setcar eshell-test-body 0)
	      (setcar eshell-command-body (eshell-copy-tree (cadr args)))
	      (eshell-do-eval (car eshell-command-body) synchronous-p)
	      (setcar eshell-command-body nil)
	      (setcar eshell-test-body (eshell-copy-tree (car args)))))
	(setcar eshell-command-body nil))
       ((eq (car form) 'if)
	;; `eshell-copy-tree' is needed here so that the test argument
	;; doesn't get modified and thus always yield the same result.
	(when (car eshell-command-body)
	  (assert (not synchronous-p))
	  (eshell-do-eval (car eshell-command-body))
	  (setcar eshell-command-body nil))
	(unless (car eshell-test-body)
	  (setcar eshell-test-body (eshell-copy-tree (car args))))
	(if (and (car eshell-test-body)
		 (not (eq (car eshell-test-body) 0)))
	    (if (cadr (eshell-do-eval (car eshell-test-body)))
		(progn
		  (setcar eshell-test-body 0)
		  (setcar eshell-command-body (eshell-copy-tree (cadr args)))
		  (eshell-do-eval (car eshell-command-body) synchronous-p))
	      (setcar eshell-test-body 0)
	      (setcar eshell-command-body (eshell-copy-tree (car (cddr args))))
	      (eshell-do-eval (car eshell-command-body) synchronous-p)))
	(setcar eshell-command-body nil))
       ((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))
       ((memq (car form) '(let catch condition-case unwind-protect))
	;; `let', `condition-case' and `unwind-protect' have to be
	;; handled specially, because we only want to call
	;; `eshell-do-eval' on their first form.
	;;
	;; NOTE: This requires obedience by all forms which this
	;; function might encounter, that they do not contain
	;; other special forms.
	(if (and (eq (car form) 'let)
		 (not (eq (car (cadr args)) 'eshell-do-eval)))
	    (eshell-manipulate "evaluating let args"
	      (eshell-for letarg (car args)
		(if (and (listp letarg)
			 (not (eq (cadr letarg) 'quote)))
		    (setcdr letarg
			    (list (eshell-do-eval
				   (cadr letarg) synchronous-p)))))))
	(unless (eq (car form) 'unwind-protect)
	  (setq args (cdr args)))
	(unless (eq (caar args) 'eshell-do-eval)
	  (eshell-manipulate "handling special form"
	    (setcar args (list 'eshell-do-eval
			       (list 'quote (car args))
			       synchronous-p))))
	(eval form))
       (t
	(if (and args (not (memq (car form) '(run-hooks))))
	    (eshell-manipulate
		(format "evaluating arguments to `%s'"
			(symbol-name (car form)))
	      (while args
		(setcar args (eshell-do-eval (car args) synchronous-p))
		(setq args (cdr args)))))
	(cond
	 ((eq (car form) 'progn)
	  (car (last form)))
	 ((eq (car form) 'prog1)
	  (cadr form))
	 (t
	  (let (result new-form)
	    ;; 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.
	    (if (setq new-form
		      (catch 'eshell-replace-command
			(ignore
			 (setq result (eval form)))))
		(progn
		  (eshell-manipulate "substituting replacement form"
		    (setcar form (car new-form))
		    (setcdr form (cdr new-form)))
		  (eshell-do-eval form synchronous-p))
	      (if (and (memq (car form) eshell-deferrable-commands)
		       (not eshell-current-subjob-p)
		       result
		       (processp result))
		  (if synchronous-p
		      (eshell/wait result)
		    (eshell-manipulate "inserting ignore form"
		      (setcar form 'ignore)
		      (setcdr form nil))
		    (throw 'eshell-defer result))
		(list 'quote result))))))))))))

;; command invocation

(defun eshell/which (command &rest names)
  "Identify the COMMAND, and where it is located."
  (eshell-for name (cons command names)
    (let (program alias direct)
      (if (eq (aref name 0) ?*)
	  (setq name (substring name 1)
		direct t))
      (if (and (not direct)
	       (eshell-using-module 'eshell-alias)
	       (setq alias
		     (funcall (symbol-function 'eshell-lookup-alias)
			      name)))
	  (setq program
		(concat name " is an alias, defined as \""
			(cadr alias) "\"")))
      (unless program
	(setq program (eshell-search-path name))
	(let* ((esym (eshell-find-alias-function name))
	       (sym (or esym (intern-soft name))))
	  (if (and sym (fboundp sym)
		   (or esym eshell-prefer-lisp-functions
		       (not program)))
	      (let ((desc (let ((inhibit-redisplay t))
			    (save-window-excursion
			      (prog1
				  (describe-function sym)
				(message nil))))))
		(setq desc (substring desc 0
				      (1- (or (string-match "\n" desc)
					      (length desc)))))
		(kill-buffer "*Help*")
		(setq program (or desc name))))))
      (if (not program)
	  (eshell-error (format "which: no %s in (%s)\n"
				name (getenv "PATH")))
	(eshell-printn program)))))

(defun eshell-named-command (command &optional args)
  "Insert output from a plain COMMAND, using ARGS.
COMMAND may result in an alias being executed, or a plain command."
  (setq eshell-last-arguments args
	eshell-last-command-name (eshell-stringify command))
  (run-hook-with-args 'eshell-prepare-command-hook)
  (assert (stringp eshell-last-command-name))
  (if eshell-last-command-name
      (or (run-hook-with-args-until-success
	   'eshell-named-command-hook eshell-last-command-name
	   eshell-last-arguments)
	  (eshell-plain-command eshell-last-command-name
				eshell-last-arguments))))

(defalias 'eshell-named-command* 'eshell-named-command)

(defun eshell-find-alias-function (name)
  "Check whether a function called `eshell/NAME' exists."
  (let* ((sym (intern-soft (concat "eshell/" name)))
	 (file (symbol-file sym))
	 module-sym)
    (if (and file
	     (string-match "\\(em\\|esh\\)-\\(.*\\)\\(\\.el\\)?\\'" file))
	(setq file (concat "eshell-" (match-string 2 file))))
    (setq module-sym
	  (and sym file (fboundp 'symbol-file)
	       (intern (file-name-sans-extension
			(file-name-nondirectory file)))))
    (and sym (functionp sym)
	 (or (not module-sym)
	     (eshell-using-module module-sym)
	     (memq module-sym (eshell-subgroups 'eshell)))
	 sym)))

(defun eshell-plain-command (command args)
  "Insert output from a plain COMMAND, using ARGS.
COMMAND may result in either a Lisp function being executed by name,
or an external command."
  (let* ((esym (eshell-find-alias-function command))
	 (sym (or esym (intern-soft command))))
    (if (and sym (fboundp sym)
	     (or esym eshell-prefer-lisp-functions
		 (not (eshell-search-path command))))
	(eshell-lisp-command sym args)
      (eshell-external-command command args))))

(defun eshell-exec-lisp (printer errprint func-or-form args form-p)
  "Execute a lisp FUNC-OR-FORM, maybe passing ARGS.
PRINTER and ERRPRINT are functions to use for printing regular
messages, and errors.  FORM-P should be non-nil if FUNC-OR-FORM
represent a lisp form; ARGS will be ignored in that case."
  (let (result)
    (eshell-condition-case err
	(progn
	  (setq result
		(save-current-buffer
		  (if form-p
		      (eval func-or-form)
		    (apply func-or-form args))))
	  (and result (funcall printer result))
	  result)
      (error
       (let ((msg (error-message-string err)))
	 (if (and (not form-p)
		  (string-match "^Wrong number of arguments" msg)
		  (fboundp 'eldoc-get-fnsym-args-string))
	     (let ((func-doc (eldoc-get-fnsym-args-string func-or-form)))
	       (setq msg (format "usage: %s" func-doc))))
	 (funcall errprint msg))
       nil))))

(defsubst eshell-apply* (printer errprint func args)
  "Call FUNC, with ARGS, trapping errors and return them as output.
PRINTER and ERRPRINT are functions to use for printing regular
messages, and errors."
  (eshell-exec-lisp printer errprint func args nil))

(defsubst eshell-funcall* (printer errprint func &rest args)
  "Call FUNC, with ARGS, trapping errors and return them as output."
  (eshell-apply* printer errprint func args))

(defsubst eshell-eval* (printer errprint form)
  "Evaluate FORM, trapping errors and returning them."
  (eshell-exec-lisp printer errprint form nil t))

(defsubst eshell-apply (func args)
  "Call FUNC, with ARGS, trapping errors and return them as output.
PRINTER and ERRPRINT are functions to use for printing regular
messages, and errors."
  (eshell-apply* 'eshell-print 'eshell-error func args))

(defsubst eshell-funcall (func &rest args)
  "Call FUNC, with ARGS, trapping errors and return them as output."
  (eshell-apply func args))

(defsubst eshell-eval (form)
  "Evaluate FORM, trapping errors and returning them."
  (eshell-eval* 'eshell-print 'eshell-error form))

(defsubst eshell-applyn (func args)
  "Call FUNC, with ARGS, trapping errors and return them as output.
PRINTER and ERRPRINT are functions to use for printing regular
messages, and errors."
  (eshell-apply* 'eshell-printn 'eshell-errorn func args))

(defsubst eshell-funcalln (func &rest args)
  "Call FUNC, with ARGS, trapping errors and return them as output."
  (eshell-applyn func args))

(defsubst eshell-evaln (form)
  "Evaluate FORM, trapping errors and returning them."
  (eshell-eval* 'eshell-printn 'eshell-errorn form))

(defun eshell-lisp-command (object &optional args)
  "Insert Lisp OBJECT, using ARGS if a function."
  (setq eshell-last-arguments args
	eshell-last-command-name "#<Lisp>")
  (catch 'eshell-external               ; deferred to an external command
    (let* ((eshell-ensure-newline-p (eshell-interactive-output-p))
	   (result
	    (if (functionp object)
		(eshell-apply object args)
	      (eshell-eval object))))
      (if (and eshell-ensure-newline-p
	       (save-excursion
		 (goto-char eshell-last-output-end)
		 (not (bolp))))
	  (eshell-print "\n"))
      (eshell-close-handles 0 (list 'quote result)))))

(defalias 'eshell-lisp-command* 'eshell-lisp-command)

;;; esh-cmd.el ends here