emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master 990f36fa10 4/5: Fix parsing of indices in Eshell expansions


From: Lars Ingebrigtsen
Subject: master 990f36fa10 4/5: Fix parsing of indices in Eshell expansions
Date: Thu, 3 Mar 2022 08:59:49 -0500 (EST)

branch: master
commit 990f36fa108092c8b93692bb80c6fbd0b6b8f391
Author: Jim Porter <jporterbugs@gmail.com>
Commit: Lars Ingebrigtsen <larsi@gnus.org>

    Fix parsing of indices in Eshell expansions
    
    Previously, more-complex index expansions, like '$var[":" 0]' or
    '$var[$(expr) 0]' failed to parse correctly.
    
    * lisp/eshell/esh-var.el (Commentary): Clarify indexing and length
    expansions.
    (eshell-parse-indices): Expand docstring and support parsing inside
    double-quotes.
    (eshell-eval-indices): New function.
    (eshell-parse-variable): Use it.
    
    * test/lisp/eshell/esh-var-tests.el (eshell-test-value): New defvar.
    (esh-var-test/interp-var-indices,
    (esh-var-test/interp-var-split-indices)
    (esh-var-test/interp-var-string-split-indices)
    (esh-var-test/interp-var-regexp-split-indices)
    (esh-var-test/interp-var-assoc, esh-var-test/interp-var-length-list)
    (esh-var-test/interp-var-length-string)
    (esh-var-test/interp-var-length-alist)
    (esh-var-test/quoted-interp-var-indices)
    (esh-var-test/quoted-interp-var-split-indices)
    (esh-var-test/quoted-interp-var-string-split-indices)
    (esh-var-test/quoted-interp-var-regexp-split-indices)
    (esh-var-test/quoted-interp-var-assoc)
    (esh-var-test/quoted-interp-var-length-list)
    (esh-var-test/quoted-interp-var-length-string)
    (esh-var-test/quoted-interp-var-length-alist): New tests.
    
    * doc/misc/eshell.texi (Dollars Expansion): Expand and reword
    documentation for indexing and length expansions.
---
 doc/misc/eshell.texi              |  62 ++++++++--------
 lisp/eshell/esh-var.el            |  59 ++++++++-------
 test/lisp/eshell/esh-var-tests.el | 152 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 212 insertions(+), 61 deletions(-)

diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index bbf8ca6b8b..3301a854eb 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -1022,11 +1022,6 @@ Expands to the value bound to @var{var}.  This is useful 
to
 disambiguate the variable name when concatenating it with another
 value, such as @samp{$"@var{var}"-suffix}.
 
-@item $#@var{var}
-Expands to the length of the value bound to @var{var}.  Raises an error
-if the value is not a sequence
-(@pxref{Sequences Arrays Vectors, Sequences, , elisp, The Emacs Lisp Reference 
Manual}).
-
 @item $(@var{lisp})
 Expands to the result of evaluating the S-expression @code{(@var{lisp})}.  On
 its own, this is identical to just @code{(@var{lisp})}, but with the @code{$},
@@ -1041,34 +1036,35 @@ As with @samp{$@{@var{command}@}}, evaluates the Eshell 
command invocation
 @command{@var{command}}, but writes the output to a temporary file and
 returns the file name.
 
-@item $@var{var}[i]
-Expands to the @code{i}th element of the value bound to @var{var}.  If
-the value is a string, it will be split at whitespace to make it a list.
-Again, raises an error if the value is not a sequence.
-
-@item $@var{var}[: i]
-As above, but now splitting occurs at the colon character.
-
-@item $@var{var}[: i j]
-As above, but instead of returning just a string, it now returns a list
-of two strings.  If the result is being interpolated into a larger
-string, this list will be flattened into one big string, with each
-element separated by a space.
-
-@item $@var{var}["\\\\" i]
-Separate on backslash characters.  Actually, the first argument -- if it
-doesn't have the form of a number, or a plain variable name -- can be
-any regular expression.  So to split on numbers, use
-@samp{$@var{var}["[0-9]+" 10 20]}.
-
-@item $@var{var}[hello]
-Calls @code{assoc} on @var{var} with @code{"hello"}, expecting it to be
-an alist (@pxref{Association List Type, Association Lists, , elisp,
-The Emacs Lisp Reference Manual}).
-
-@item $#@var{var}[hello]
-Returns the length of the @code{cdr} of the element of @var{var} whose
-car is equal to @code{"hello"}.
+@item $@var{expr}[@var{i...}]
+Expands to the @var{i}th element of the result of @var{expr}, an
+expression in one of the above forms listed here.  If multiple indices
+are supplied, this will return a list containing the elements for each
+index.  If @var{expr}'s value is a string, it will first be split at
+whitespace to make it a list.  If @var{expr}'s value is an alist
+(@pxref{Association List Type, Association Lists, , elisp, The Emacs
+Lisp Reference Manual}), this will call @code{assoc} on the result of
+@var{expr}, returning the @code{cdr} of the element of the result
+whose car is equal to @code{"i"}.  Raises an error if the value is not
+a sequence (@pxref{Sequences Arrays Vectors, Sequences, , elisp, The
+Emacs Lisp Reference Manual}).
+
+Multiple sets of indices can also be specified. For example, if
+@var{var} is a list of lists, @samp{$@var{var}[0][0]} is equivalent to
+@samp{(caar @var{var})}.
+
+@item $@var{expr}[@var{regexp} @var{i...}]
+As above (when @var{expr} expands to a string), but use @var{regexp}
+to split the string.  @var{regexp} can be any form other than a number
+or a plain variable name.  For example, @samp{$@var{var}[: 0]} will
+return the first element of a colon-delimited string.
+
+@item $#@var{expr}
+Expands to the length of the result of @var{expr}, an expression in
+one of the above forms.  For example, @samp{$#@var{var}} returns the
+length of the variable @var{var} and @samp{$#@var{var}[0]} returns the
+length of the first element of @var{var}.  Again, raises an error if
+the result of @var{expr} is not a sequence.
 
 @end table
 
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index 24fdbde3cf..6f08a3fbc4 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -39,11 +39,6 @@
 ;;
 ;; Only "MYVAR" is part of the variable name in this case.
 ;;
-;;   $#VARIABLE
-;;
-;; Returns the length of the value of VARIABLE.  This could also be
-;; done using the `length' Lisp function.
-;;
 ;;   $(lisp)
 ;;
 ;; Returns result of Lisp evaluation.  Note: Used alone like this, it
@@ -61,38 +56,36 @@
 ;; Evaluates an eshell subcommand, redirecting the output to a
 ;; temporary file, and returning the file name.
 ;;
-;;   $ANYVAR[10]
+;;   $EXPR[10]
 ;;
-;; Return the 10th element of ANYVAR.  If ANYVAR's value is a string,
-;; it will be split in order to make it a list.  The splitting will
-;; occur at whitespace.
+;; Return the 10th element of $EXPR, which can be any dollar
+;; expression.  If $EXPR's value is a string, it will be split in
+;; order to make it a list.  The splitting will occur at whitespace.
 ;;
-;;   $ANYVAR[: 10]
+;;   $EXPR[10 20]
 ;;
-;; As above, except that splitting occurs at the colon now.
+;; As above, but instead of returning a single element, it now returns a
+;; list of two elements.
 ;;
-;;   $ANYVAR[: 10 20]
+;;   $EXPR[: 10]
 ;;
-;; As above, but instead of returning just a string, it now returns a
-;; list of two strings.  If the result is being interpolated into a
-;; larger string, this list will be flattened into one big string,
-;; with each element separated by a space.
+;; Like $EXPR[10], except that splitting occurs at the colon now.
 ;;
-;;   $ANYVAR["\\\\" 10]
+;;   $EXPR["\\\\" 10]
 ;;
 ;; Separate on backslash characters.  Actually, the first argument --
 ;; if it doesn't have the form of a number, or a plain variable name
 ;; -- can be any regular expression.  So to split on numbers, use
-;; '$ANYVAR["[0-9]+" 10 20]'.
+;; '$EXPR["[0-9]+" 10 20]'.
 ;;
-;;   $ANYVAR[hello]
+;;   $EXPR[hello]
 ;;
-;; Calls `assoc' on ANYVAR with 'hello', expecting it to be an alist.
+;; Calls `assoc' on $EXPR with 'hello', expecting it to be an alist.
 ;;
-;;   $#ANYVAR[hello]
+;;   $#EXPR
 ;;
-;; Returns the length of the cdr of the element of ANYVAR who car is
-;; equal to "hello".
+;; Returns the length of the value of $EXPR.  This could also be
+;; done using the `length' Lisp function.
 ;;
 ;; There are also a few special variables defined by Eshell.  '$$' is
 ;; the value of the last command (t or nil, in the case of an external
@@ -416,7 +409,7 @@ process any indices that come after the variable reference."
                       (eshell-parse-indices))
           ;; This is an expression that will be evaluated by `eshell-do-eval',
           ;; which only support let-binding of dynamically-scoped vars
-         value `(let ((indices ',indices)) ,value))
+         value `(let ((indices (eshell-eval-indices ',indices))) ,value))
     (if get-len
        `(length ,value)
       value)))
@@ -504,19 +497,29 @@ Possible options are:
 (defvar eshell-glob-function)
 
 (defun eshell-parse-indices ()
-  "Parse and return a list of list of indices."
+  "Parse and return a list of index-lists.
+
+For example, \"[0 1][2]\" becomes:
+  ((\"0\" \"1\") (\"2\")."
   (let (indices)
     (while (eq (char-after) ?\[)
       (let ((end (eshell-find-delimiter ?\[ ?\])))
        (if (not end)
            (throw 'eshell-incomplete ?\[)
          (forward-char)
-         (let (eshell-glob-function)
-           (setq indices (cons (eshell-parse-arguments (point) end)
-                               indices)))
+          (eshell-with-temp-command (or (eshell-parse-inner-double-quote end)
+                                        (cons (point) end))
+           (let (eshell-glob-function (eshell-current-quoted nil))
+             (setq indices (cons (eshell-parse-arguments
+                                   (point-min) (point-max))
+                                 indices))))
          (goto-char (1+ end)))))
     (nreverse indices)))
 
+(defun eshell-eval-indices (indices)
+  "Evaluate INDICES, a list of index-lists generated by 
`eshell-parse-indices'."
+  (mapcar (lambda (i) (mapcar #'eval i)) indices))
+
 (defun eshell-get-variable (name &optional indices)
   "Get the value for the variable NAME."
   (let* ((alias (assoc name eshell-variable-aliases-list))
diff --git a/test/lisp/eshell/esh-var-tests.el 
b/test/lisp/eshell/esh-var-tests.el
index 7ec6a97519..e679174939 100644
--- a/test/lisp/eshell/esh-var-tests.el
+++ b/test/lisp/eshell/esh-var-tests.el
@@ -32,6 +32,8 @@
                            (file-name-directory (or load-file-name
                                                     default-directory))))
 
+(defvar eshell-test-value nil)
+
 ;;; Tests:
 
 
@@ -56,6 +58,76 @@
   (should (equal (eshell-test-command-result "echo $\"user-login-name\"-foo")
                  (concat user-login-name "-foo"))))
 
+(ert-deftest esh-var-test/interp-var-indices ()
+  "Interpolate list variable with indices"
+  (let ((eshell-test-value '("zero" "one" "two" "three" "four")))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[0]")
+                   "zero"))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2]")
+                   '("zero" "two")))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2 
4]")
+                   '("zero" "two" "four")))))
+
+(ert-deftest esh-var-test/interp-var-split-indices ()
+  "Interpolate string variable with indices"
+  (let ((eshell-test-value "zero one two three four"))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[0]")
+                   "zero"))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2]")
+                   '("zero" "two")))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2 
4]")
+                   '("zero" "two" "four")))))
+
+(ert-deftest esh-var-test/interp-var-string-split-indices ()
+  "Interpolate string variable with string splitter and indices"
+  (let ((eshell-test-value "zero:one:two:three:four"))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[: 0]")
+                   "zero"))
+    (should (equal (eshell-test-command-result "echo $eshell-test-value[: 0 
2]")
+                   '("zero" "two")))))
+
+(ert-deftest esh-var-test/interp-var-regexp-split-indices ()
+  "Interpolate string variable with regexp splitter and indices"
+  (let ((eshell-test-value "zero:one!two:three!four"))
+    (should (equal (eshell-test-command-result
+                    "echo $eshell-test-value['[:!]' 0]")
+                   "zero"))
+    (should (equal (eshell-test-command-result
+                    "echo $eshell-test-value['[:!]' 0 2]")
+                   '("zero" "two")))
+    (should (equal (eshell-test-command-result
+                    "echo $eshell-test-value[\"[:!]\" 0]")
+                   "zero"))
+    (should (equal (eshell-test-command-result
+                    "echo $eshell-test-value[\"[:!]\" 0 2]")
+                   '("zero" "two")))))
+
+(ert-deftest esh-var-test/interp-var-assoc ()
+  "Interpolate alist variable with index"
+  (let ((eshell-test-value '(("foo" . 1))))
+    (should (eq (eshell-test-command-result "echo $eshell-test-value[foo]")
+                1))))
+
+(ert-deftest esh-var-test/interp-var-length-list ()
+  "Interpolate length of list variable"
+  (let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9)))))
+    (should (eq (eshell-test-command-result "echo $#eshell-test-value") 3))
+    (should (eq (eshell-test-command-result "echo $#eshell-test-value[1]") 1))
+    (should (eq (eshell-test-command-result "echo $#eshell-test-value[2][1]")
+                4))))
+
+(ert-deftest esh-var-test/interp-var-length-string ()
+  "Interpolate length of string variable"
+  (let ((eshell-test-value "foobar"))
+    (should (eq (eshell-test-command-result "echo $#eshell-test-value") 6))))
+
+(ert-deftest esh-var-test/interp-var-length-alist ()
+  "Interpolate length of alist variable"
+  (let ((eshell-test-value '(("foo" . (1 2 3)))))
+    (should (eq (eshell-test-command-result "echo $#eshell-test-value") 1))
+    (should (eq (eshell-test-command-result "echo $#eshell-test-value[foo]")
+                3))))
+
 (ert-deftest esh-var-test/interp-lisp ()
   "Interpolate Lisp form evaluation"
   (should (equal (eshell-test-command-result "+ $(+ 1 2) 3") 6)))
@@ -112,6 +184,86 @@
                   "echo \"hi, $\\\"user-login-name\\\"\"")
                  (concat "hi, " user-login-name))))
 
+(ert-deftest esh-var-test/quoted-interp-var-indices ()
+  "Interpolate string variable with indices inside double-quotes"
+  (let ((eshell-test-value '("zero" "one" "two" "three" "four")))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[0]\"")
+                   "zero"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[0 2]\"")
+                   '("zero" "two")))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[0 2 4]\"")
+                   '("zero" "two" "four")))))
+
+(ert-deftest esh-var-test/quoted-interp-var-split-indices ()
+  "Interpolate string variable with indices inside double-quotes"
+  (let ((eshell-test-value "zero one two three four"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[0]\"")
+                   "zero"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[0 2]\"")
+                   '("zero" "two")))))
+
+(ert-deftest esh-var-test/quoted-interp-var-string-split-indices ()
+  "Interpolate string variable with string splitter and indices
+inside double-quotes"
+  (let ((eshell-test-value "zero:one:two:three:four"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[: 0]\"")
+                   "zero"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[: 0 2]\"")
+                   '("zero" "two")))))
+
+(ert-deftest esh-var-test/quoted-interp-var-regexp-split-indices ()
+  "Interpolate string variable with regexp splitter and indices"
+  (let ((eshell-test-value "zero:one!two:three!four"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value['[:!]' 0]\"")
+                   "zero"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value['[:!]' 0 2]\"")
+                   '("zero" "two")))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[\\\"[:!]\\\" 0]\"")
+                   "zero"))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[\\\"[:!]\\\" 0 2]\"")
+                   '("zero" "two")))))
+
+(ert-deftest esh-var-test/quoted-interp-var-assoc ()
+  "Interpolate alist variable with index inside double-quotes"
+  (let ((eshell-test-value '(("foo" . 1))))
+    (should (equal (eshell-test-command-result
+                    "echo \"$eshell-test-value[foo]\"")
+                   1))))
+
+(ert-deftest esh-var-test/quoted-interp-var-length-list ()
+  "Interpolate length of list variable inside double-quotes"
+  (let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9)))))
+    (should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"") 3))
+    (should (eq (eshell-test-command-result "echo \"$#eshell-test-value[1]\"")
+                1))
+    (should (eq (eshell-test-command-result
+                 "echo \"$#eshell-test-value[2][1]\"")
+                4))))
+
+(ert-deftest esh-var-test/quoted-interp-var-length-string ()
+  "Interpolate length of string variable inside double-quotes"
+  (let ((eshell-test-value "foobar"))
+    (should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"")
+                6))))
+
+(ert-deftest esh-var-test/quoted-interp-var-length-alist ()
+  "Interpolate length of alist variable inside double-quotes"
+  (let ((eshell-test-value '(("foo" . (1 2 3)))))
+    (should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"") 1))
+    (should (eq (eshell-test-command-result "echo 
\"$#eshell-test-value[foo]\"")
+                3))))
+
 (ert-deftest esh-var-test/quoted-interp-lisp ()
   "Interpolate Lisp form evaluation inside double-quotes"
   (should (equal (eshell-test-command-result



reply via email to

[Prev in Thread] Current Thread [Next in Thread]