Move more of Eshell range handling to the parser phase
* lisp/eshell/esh-util.el (eshell-range): New struct. (eshell--range-string-p, eshell--string-to-range): New functions. * lisp/eshell/esh-arg.el (eshell-parse-integer) (eshell-parse-range-token): New functions... (eshell-parse-argument-hook): ... add them. (eshell--after-range-token-regexp): New defsubst. (eshell-concat-1): Don't remove the 'number' property; we use that when handling range arguments. (eshell--range-token): New constant. (eshell-unmark-range-token): New function. * lisp/eshell/esh-var.el (eshell-parse-index): Update implementation to use parsed range argument. * test/lisp/eshell/esh-var-tests.el (esh-var-test/interp-var-indices): Test range index using variables.
This commit is contained in:
parent
4d69d3778a
commit
ed9ea57e57
4 changed files with 109 additions and 35 deletions
|
@ -92,6 +92,11 @@ If POS is nil, the location of point is checked."
|
|||
eshell-parse-special-reference
|
||||
;; Numbers convert to numbers if they stand alone.
|
||||
eshell-parse-number
|
||||
;; Integers convert to numbers if they stand alone or are part of a
|
||||
;; range expression.
|
||||
eshell-parse-integer
|
||||
;; Range tokens go between integers and denote a half-open range.
|
||||
eshell-parse-range-token
|
||||
;; Parse any non-special characters, based on the current context.
|
||||
eshell-parse-non-special
|
||||
;; Whitespace is an argument delimiter.
|
||||
|
@ -193,6 +198,15 @@ Eshell will expand special refs like \"#<ARG...>\" into
|
|||
(rx-to-string
|
||||
`(+ (not (any ,@eshell-special-chars-outside-quoting))) t))))
|
||||
|
||||
(defvar eshell--after-range-token-regexp nil)
|
||||
(defsubst eshell--after-range-token-regexp ()
|
||||
(or eshell--after-range-token-regexp
|
||||
(setq-local eshell--after-range-token-regexp
|
||||
(rx-to-string
|
||||
`(or (any ,@eshell-special-chars-outside-quoting)
|
||||
(regexp ,eshell-integer-regexp))
|
||||
t))))
|
||||
|
||||
(defsubst eshell-escape-arg (string)
|
||||
"Return STRING with the `escaped' property on it."
|
||||
(if (stringp string)
|
||||
|
@ -245,7 +259,6 @@ If QUOTED is nil and either FIRST or SECOND are numberlike, try to mark
|
|||
the result as a number as well."
|
||||
(let ((result (concat (eshell-stringify first quoted)
|
||||
(eshell-stringify second quoted))))
|
||||
(remove-text-properties 0 (length result) '(number) result)
|
||||
(when (and (not quoted)
|
||||
(or (numberp first) (eshell--numeric-string-p first)
|
||||
(numberp second) (eshell--numeric-string-p second)))
|
||||
|
@ -412,6 +425,8 @@ Point is left at the end of the arguments."
|
|||
"A stub function that generates an error if a floating splice is found."
|
||||
(error "Splice operator is not permitted in this context"))
|
||||
|
||||
(defconst eshell--range-token (propertize ".." 'eshell-range t))
|
||||
|
||||
(defun eshell-parse-number ()
|
||||
"Parse a numeric argument.
|
||||
Eshell can treat unquoted arguments matching `eshell-number-regexp' as
|
||||
|
@ -422,10 +437,50 @@ their numeric values."
|
|||
(eshell-arg-delimiter (match-end 0)))
|
||||
(goto-char (match-end 0))
|
||||
(let ((str (match-string 0)))
|
||||
(when (> (length str) 0)
|
||||
(add-text-properties 0 (length str) '(number t) str))
|
||||
(add-text-properties 0 (length str) '(number t) str)
|
||||
str)))
|
||||
|
||||
(defun eshell-parse-integer ()
|
||||
"Parse an integer argument."
|
||||
(unless eshell-current-quoted
|
||||
(let ((prev-token (if eshell-arg-listified
|
||||
(car (last eshell-current-argument))
|
||||
eshell-current-argument)))
|
||||
(when (and (memq prev-token `(nil ,eshell--range-token))
|
||||
(looking-at eshell-integer-regexp)
|
||||
(or (eshell-arg-delimiter (match-end 0))
|
||||
(save-excursion
|
||||
(goto-char (match-end 0))
|
||||
(looking-at-p (rx "..")))))
|
||||
(goto-char (match-end 0))
|
||||
(let ((str (match-string 0)))
|
||||
(add-text-properties 0 (length str) '(number t) str)
|
||||
str)))))
|
||||
|
||||
(defun eshell-unmark-range-token (string)
|
||||
(remove-text-properties 0 (length string) '(eshell-range) string))
|
||||
|
||||
(defun eshell-parse-range-token ()
|
||||
"Parse a range token.
|
||||
This separates two integers (possibly as dollar expansions) and denotes
|
||||
a half-open range."
|
||||
(when (and (not eshell-current-quoted)
|
||||
(looking-at (rx ".."))
|
||||
(or (eshell-arg-delimiter (match-end 0))
|
||||
(save-excursion
|
||||
(goto-char (match-end 0))
|
||||
(looking-at (eshell--after-range-token-regexp)))))
|
||||
;; If we parse multiple range tokens for a single argument, then
|
||||
;; they can't actually be range tokens. Unmark the result to
|
||||
;; indicate this.
|
||||
(when (memq eshell--range-token
|
||||
(if eshell-arg-listified
|
||||
eshell-current-argument
|
||||
(list eshell-current-argument)))
|
||||
(add-hook 'eshell-current-modifiers #'eshell-unmark-range-token))
|
||||
(forward-char 2)
|
||||
eshell--range-token))
|
||||
|
||||
(defun eshell-parse-non-special ()
|
||||
"Parse any non-special characters, depending on the current context."
|
||||
(when (looking-at (if eshell-current-quoted
|
||||
|
|
|
@ -369,6 +369,35 @@ unchanged."
|
|||
(string-to-number string)
|
||||
string))
|
||||
|
||||
(cl-defstruct (eshell-range
|
||||
(:constructor nil)
|
||||
(:constructor eshell-range-create (begin end)))
|
||||
"A half-open range from BEGIN to END."
|
||||
begin end)
|
||||
|
||||
(defsubst eshell--range-string-p (string)
|
||||
"Return non-nil if STRING has been marked as a range."
|
||||
(and (stringp string)
|
||||
(text-property-any 0 (length string) 'eshell-range t string)))
|
||||
|
||||
(defun eshell--string-to-range (string)
|
||||
"Convert STRING to an `eshell-range' object."
|
||||
(let* ((startpos (text-property-any 0 (length string) 'eshell-range t string))
|
||||
(endpos (next-single-property-change startpos 'eshell-range
|
||||
string (length string)))
|
||||
range-begin range-end)
|
||||
(unless (= startpos 0)
|
||||
(setq range-begin (substring string 0 startpos))
|
||||
(unless (eshell--numeric-string-p range-begin)
|
||||
(user-error "range begin `%s' is not a number" range-begin))
|
||||
(setq range-begin (string-to-number range-begin)))
|
||||
(unless (= endpos (length string))
|
||||
(setq range-end (substring string endpos))
|
||||
(unless (eshell--numeric-string-p range-end)
|
||||
(user-error "range end `%s' is not a number" range-end))
|
||||
(setq range-end (string-to-number range-end)))
|
||||
(eshell-range-create range-begin range-end)))
|
||||
|
||||
(defun eshell-convert (string &optional to-string)
|
||||
"Convert STRING into a more-native Lisp object.
|
||||
If TO-STRING is non-nil, always return a single string with
|
||||
|
|
|
@ -641,24 +641,13 @@ in the cons is nil.
|
|||
|
||||
Otherwise (including if INDEX is not a string), return
|
||||
the original value of INDEX."
|
||||
(save-match-data
|
||||
(cond
|
||||
((and (stringp index) (get-text-property 0 'number index))
|
||||
(string-to-number index))
|
||||
((and (stringp index)
|
||||
(not (text-property-any 0 (length index) 'escaped t index))
|
||||
(string-match (rx string-start
|
||||
(group-n 1 (? (regexp eshell-integer-regexp)))
|
||||
".."
|
||||
(group-n 2 (? (regexp eshell-integer-regexp)))
|
||||
string-end)
|
||||
index))
|
||||
(let ((begin (match-string 1 index))
|
||||
(end (match-string 2 index)))
|
||||
(cons (unless (string-empty-p begin) (string-to-number begin))
|
||||
(unless (string-empty-p end) (string-to-number end)))))
|
||||
(t
|
||||
index))))
|
||||
(cond
|
||||
((eshell--numeric-string-p index)
|
||||
(string-to-number index))
|
||||
((eshell--range-string-p index)
|
||||
(eshell--string-to-range index))
|
||||
(t
|
||||
index)))
|
||||
|
||||
(defun eshell-eval-indices (indices)
|
||||
"Evaluate INDICES, a list of index-lists generated by `eshell-parse-indices'."
|
||||
|
@ -795,14 +784,6 @@ For example, to retrieve the second element of a user's record in
|
|||
(push (eshell-index-value value ref) new-value))
|
||||
(setq value (nreverse new-value)))))))
|
||||
|
||||
(pcase-defmacro eshell-index-range (start end)
|
||||
"A pattern that matches an Eshell index range.
|
||||
EXPVAL should be a cons cell, with each slot containing either an
|
||||
integer or nil. If this matches, bind the values of the sltos to
|
||||
START and END."
|
||||
(list '\` (cons (list '\, `(and (or (pred integerp) (pred null)) ,start))
|
||||
(list '\, `(and (or (pred integerp) (pred null)) ,end)))))
|
||||
|
||||
(defun eshell-index-value (value index)
|
||||
"Reference VALUE using the given INDEX."
|
||||
(let ((parsed-index (eshell-parse-index index)))
|
||||
|
@ -810,15 +791,17 @@ START and END."
|
|||
(pcase parsed-index
|
||||
((pred integerp)
|
||||
(ring-ref value parsed-index))
|
||||
((eshell-index-range start end)
|
||||
((pred eshell-range-p)
|
||||
(let* ((len (ring-length value))
|
||||
(real-start (mod (or start 0) len))
|
||||
(begin (eshell-range-begin parsed-index))
|
||||
(end (eshell-range-end parsed-index))
|
||||
(real-begin (mod (or begin 0) len))
|
||||
(real-end (mod (or end len) len)))
|
||||
(when (and (eq real-end 0)
|
||||
(not (eq end 0)))
|
||||
(setq real-end len))
|
||||
(ring-convert-sequence-to-ring
|
||||
(seq-subseq (ring-elements value) real-start real-end))))
|
||||
(seq-subseq (ring-elements value) real-begin real-end))))
|
||||
(_
|
||||
(error "Invalid index for ring: %s" index)))
|
||||
(pcase parsed-index
|
||||
|
@ -826,8 +809,9 @@ START and END."
|
|||
(when (< parsed-index 0)
|
||||
(setq parsed-index (+ parsed-index (length value))))
|
||||
(seq-elt value parsed-index))
|
||||
((eshell-index-range start end)
|
||||
(seq-subseq value (or start 0) end))
|
||||
((pred eshell-range-p)
|
||||
(seq-subseq value (or (eshell-range-begin parsed-index) 0)
|
||||
(eshell-range-end parsed-index)))
|
||||
(_
|
||||
;; INDEX is some non-integer value, so treat VALUE as an alist.
|
||||
(cdr (assoc parsed-index value)))))))
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
default-directory))))
|
||||
|
||||
(defvar eshell-test-value nil)
|
||||
(defvar eshell-test-begin nil)
|
||||
(defvar eshell-test-end nil)
|
||||
|
||||
;;; Tests:
|
||||
|
||||
|
@ -111,7 +113,11 @@ nil, use FUNCTION instead."
|
|||
(eshell-command-result-equal
|
||||
"echo $eshell-test-value[1..4 -2..]"
|
||||
(list (funcall range-function '("one" "two" "three"))
|
||||
(funcall range-function '("three" "four"))))))
|
||||
(funcall range-function '("three" "four"))))
|
||||
(let ((eshell-test-begin 1) (eshell-test-end 4))
|
||||
(eshell-command-result-equal
|
||||
"echo $eshell-test-value[$eshell-test-begin..$eshell-test-end]"
|
||||
(funcall range-function '("one" "two" "three"))))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-indices/list ()
|
||||
"Interpolate list variable with indices."
|
||||
|
|
Loading…
Add table
Reference in a new issue