Normalize reversed bounds and validate SUBEXP in the pattern engine
SRC-1: tp--pattern-apply-single now swaps START > END bounds before branching on the object type, so the string path stops signaling a raw substring args-out-of-range while the buffer path keeps its historical narrow-to-region swap; the behavior is now uniform and documented in all six tp-match-*/tp-regexp-* docstrings. SRC-2: a SUBEXP larger than the pattern's capture-group count (per regexp-opt-depth) signals "Regexp X has no group N" instead of silently matching nothing, while legal non-participating and zero-width groups keep working quietly. Also corrects the return wording of tp-match-set/tp-regexp-set (a NEW string for string objects, not "the modified string") and adds the missing return sections to tp-match-add/tp-regexp-add, matching the verified tp-match-reset behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e870b87941
commit
575c99be88
@ -684,5 +684,46 @@ never registered the buffer (REG-1)."
|
|||||||
(tp-layer-reset)
|
(tp-layer-reset)
|
||||||
(setq tp-search-reg1b-color nil)))
|
(setq tp-search-reg1b-color nil)))
|
||||||
|
|
||||||
|
;;; SRC-1: reversed START/END bounds are swapped on both object paths
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-reversed-bounds-string-swaps ()
|
||||||
|
"String-path START > END is swapped instead of signaling out-of-range."
|
||||||
|
(let ((res (tp-match-set "o" '(face bold) "foo" 2 1)))
|
||||||
|
(should (eq (get-text-property 1 'face res) 'bold))
|
||||||
|
(should-not (get-text-property 2 'face res)))
|
||||||
|
;; Swapped bounds behave exactly like the same bounds in order.
|
||||||
|
(should (equal-including-properties
|
||||||
|
(tp-regexp-set "o+" '(face bold) "foo" 3 1)
|
||||||
|
(tp-regexp-set "o+" '(face bold) "foo" 1 3))))
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-reversed-bounds-buffer-swaps ()
|
||||||
|
"Buffer-path START > END keeps its historical swap behavior."
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert "foo")
|
||||||
|
(should (equal (tp-match-set "o" '(face bold) nil 3 2)
|
||||||
|
'((2 . 3))))
|
||||||
|
(should (eq (get-text-property 2 'face) 'bold))
|
||||||
|
(should-not (get-text-property 3 'face))))
|
||||||
|
|
||||||
|
;;; SRC-2: SUBEXP beyond the pattern's group count signals clearly
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-subexp-beyond-group-count-errors ()
|
||||||
|
"A SUBEXP larger than the pattern's group count errors on both paths."
|
||||||
|
(should-error (tp-regexp-set "abc" '(face bold)
|
||||||
|
(copy-sequence "abc") nil nil 5))
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert "abc")
|
||||||
|
(should-error (tp-regexp-add "a\\(b\\)c" '(face bold) nil nil nil 2))
|
||||||
|
;; Nothing was applied before the error.
|
||||||
|
(should-not (get-text-property 1 'face))))
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-subexp-non-participating-group-quiet ()
|
||||||
|
"A legal group that never participates still returns nil quietly."
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert "xbc")
|
||||||
|
(should (null (tp-regexp-set "\\(a\\)bc\\|xbc" '(face bold)
|
||||||
|
nil nil nil 1)))
|
||||||
|
(should-not (get-text-property 1 'face))))
|
||||||
|
|
||||||
(provide 'tp-search-tests)
|
(provide 'tp-search-tests)
|
||||||
;;; tp-search-tests.el ends here
|
;;; tp-search-tests.el ends here
|
||||||
|
|||||||
73
tp-search.el
73
tp-search.el
@ -49,7 +49,9 @@ is a regexp. APPLY-FN is called with (START END PROPS OBJECT) for
|
|||||||
each match.
|
each match.
|
||||||
START and END restrict matching to the [START, END) portion of
|
START and END restrict matching to the [START, END) portion of
|
||||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||||
buffers); nil means the object's bounds. Matching behaves as if
|
buffers); nil means the object's bounds. If START > END the bounds
|
||||||
|
are swapped (matching the buffer path's historical narrow-to-region
|
||||||
|
behavior, now uniform across object types). Matching behaves as if
|
||||||
OBJECT consisted only of that portion (the buffer path narrows, the
|
OBJECT consisted only of that portion (the buffer path narrows, the
|
||||||
string path matches against the substring), so no match crosses the
|
string path matches against the substring), so no match crosses the
|
||||||
boundaries.
|
boundaries.
|
||||||
@ -57,7 +59,9 @@ When SUBEXP is non-nil, it names a capture group of PATTERN: the
|
|||||||
properties and returned regions cover (match-beginning SUBEXP) to
|
properties and returned regions cover (match-beginning SUBEXP) to
|
||||||
\(match-end SUBEXP) of each match, and a match in which that group
|
\(match-end SUBEXP) of each match, and a match in which that group
|
||||||
does not participate contributes nothing. The scan still advances
|
does not participate contributes nothing. The scan still advances
|
||||||
past the whole match.
|
past the whole match. A SUBEXP larger than PATTERN's group count
|
||||||
|
\(per `regexp-opt-depth') signals an error instead of silently
|
||||||
|
matching nothing.
|
||||||
For strings, returns a NEW string with properties applied
|
For strings, returns a NEW string with properties applied
|
||||||
\(non-destructive).
|
\(non-destructive).
|
||||||
For buffers, modifies in-place and returns list of regions.
|
For buffers, modifies in-place and returns list of regions.
|
||||||
@ -66,6 +70,14 @@ Zero-width matches (an empty literal pattern, or a regexp that can
|
|||||||
match the empty string) are recorded and the scan advances one
|
match the empty string) are recorded and the scan advances one
|
||||||
position past them, so the search always terminates."
|
position past them, so the search always terminates."
|
||||||
(let ((regexp (if literal (regexp-quote pattern) pattern)))
|
(let ((regexp (if literal (regexp-quote pattern) pattern)))
|
||||||
|
;; Reversed bounds are swapped, not signaled: the buffer path's
|
||||||
|
;; narrow-to-region always did this, so the string path follows.
|
||||||
|
(when (and start end (> start end))
|
||||||
|
(cl-rotatef start end))
|
||||||
|
;; A group number beyond the pattern's group count could never
|
||||||
|
;; match; make the typo loud instead of a silent no-op.
|
||||||
|
(when (and subexp (> subexp (regexp-opt-depth regexp)))
|
||||||
|
(error "Regexp %S has no group %d" pattern subexp))
|
||||||
(cond
|
(cond
|
||||||
;; String object
|
;; String object
|
||||||
((stringp object)
|
((stringp object)
|
||||||
@ -247,12 +259,13 @@ or `define-tp-group'.
|
|||||||
OBJECT is a buffer or string; nil means current buffer.
|
OBJECT is a buffer or string; nil means current buffer.
|
||||||
START and END restrict matching to the [START, END) portion of
|
START and END restrict matching to the [START, END) portion of
|
||||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||||
buffers); nil means the object's bounds. Matching behaves as if
|
buffers); nil means the object's bounds. If START > END the bounds
|
||||||
OBJECT consisted only of that portion, so no match crosses the
|
are swapped. Matching behaves as if OBJECT consisted only of that
|
||||||
boundaries.
|
portion, so no match crosses the boundaries.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- For strings: the modified string
|
- For strings: a NEW string with properties applied (the original
|
||||||
|
string is not modified)
|
||||||
- For buffers: list of (START . END) pairs for all matches."
|
- For buffers: list of (START . END) pairs for all matches."
|
||||||
(tp--match-apply pattern (tp--ensure-props plist) #'tp-set object
|
(tp--match-apply pattern (tp--ensure-props plist) #'tp-set object
|
||||||
start end))
|
start end))
|
||||||
@ -269,12 +282,14 @@ or `define-tp-group'.
|
|||||||
OBJECT is a buffer or string; nil means current buffer.
|
OBJECT is a buffer or string; nil means current buffer.
|
||||||
START and END restrict matching to the [START, END) portion of
|
START and END restrict matching to the [START, END) portion of
|
||||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||||
buffers); nil means the object's bounds.
|
buffers); nil means the object's bounds. If START > END the bounds
|
||||||
|
are swapped.
|
||||||
|
|
||||||
Unlike `tp-match-set', this completely replaces all existing properties.
|
Unlike `tp-match-set', this completely replaces all existing properties.
|
||||||
|
|
||||||
For strings, returns a NEW string (original is not modified).
|
For strings, returns a NEW string (original is not modified).
|
||||||
For buffers, modifies in-place and returns list of regions."
|
For buffers, modifies in-place and returns list of (START . END)
|
||||||
|
regions."
|
||||||
(tp--match-apply pattern (tp--ensure-props plist)
|
(tp--match-apply pattern (tp--ensure-props plist)
|
||||||
#'tp--reset-apply
|
#'tp--reset-apply
|
||||||
object start end))
|
object start end))
|
||||||
@ -303,9 +318,14 @@ or `define-tp-group'.
|
|||||||
OBJECT is a buffer or string; nil means current buffer.
|
OBJECT is a buffer or string; nil means current buffer.
|
||||||
START and END restrict matching to the [START, END) portion of
|
START and END restrict matching to the [START, END) portion of
|
||||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||||
buffers); nil means the object's bounds.
|
buffers); nil means the object's bounds. If START > END the bounds
|
||||||
|
are swapped.
|
||||||
|
|
||||||
Unlike `tp-match-set', this deeply merges nested properties."
|
Unlike `tp-match-set', this deeply merges nested properties.
|
||||||
|
|
||||||
|
For strings, returns a NEW string (original is not modified).
|
||||||
|
For buffers, modifies in-place and returns list of (START . END)
|
||||||
|
regions."
|
||||||
(tp--match-apply pattern (tp--ensure-props plist) #'tp--deep-merge-apply
|
(tp--match-apply pattern (tp--ensure-props plist) #'tp--deep-merge-apply
|
||||||
object start end))
|
object start end))
|
||||||
|
|
||||||
@ -322,16 +342,18 @@ or `define-tp-group'.
|
|||||||
OBJECT is a buffer or string; nil means current buffer.
|
OBJECT is a buffer or string; nil means current buffer.
|
||||||
START and END restrict matching to the [START, END) portion of
|
START and END restrict matching to the [START, END) portion of
|
||||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||||
buffers); nil means the object's bounds. Matching behaves as if
|
buffers); nil means the object's bounds. If START > END the bounds
|
||||||
OBJECT consisted only of that portion, so no match crosses the
|
are swapped. Matching behaves as if OBJECT consisted only of that
|
||||||
boundaries.
|
portion, so no match crosses the boundaries.
|
||||||
When SUBEXP is non-nil, it names a capture group of PATTERN (1 for
|
When SUBEXP is non-nil, it names a capture group of PATTERN (1 for
|
||||||
the first group, like font-lock highlights): properties apply to that
|
the first group, like font-lock highlights): properties apply to that
|
||||||
group of each match instead of the whole match, and a match in which
|
group of each match instead of the whole match, and a match in which
|
||||||
the group does not participate contributes nothing.
|
the group does not participate contributes nothing. A SUBEXP larger
|
||||||
|
than PATTERN's group count signals an error.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- For strings: the modified string
|
- For strings: a NEW string with properties applied (the original
|
||||||
|
string is not modified)
|
||||||
- For buffers: list of (START . END) pairs for all matches."
|
- For buffers: list of (START . END) pairs for all matches."
|
||||||
(tp--regexp-apply pattern (tp--ensure-props plist) #'tp-set object
|
(tp--regexp-apply pattern (tp--ensure-props plist) #'tp-set object
|
||||||
start end subexp))
|
start end subexp))
|
||||||
@ -348,15 +370,18 @@ or `define-tp-group'.
|
|||||||
OBJECT is a buffer or string; nil means current buffer.
|
OBJECT is a buffer or string; nil means current buffer.
|
||||||
START and END restrict matching to the [START, END) portion of
|
START and END restrict matching to the [START, END) portion of
|
||||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||||
buffers); nil means the object's bounds.
|
buffers); nil means the object's bounds. If START > END the bounds
|
||||||
|
are swapped.
|
||||||
When SUBEXP is non-nil, properties apply to that capture group of
|
When SUBEXP is non-nil, properties apply to that capture group of
|
||||||
each match instead of the whole match; a match in which the group
|
each match instead of the whole match; a match in which the group
|
||||||
does not participate contributes nothing.
|
does not participate contributes nothing. A SUBEXP larger than
|
||||||
|
PATTERN's group count signals an error.
|
||||||
|
|
||||||
Unlike `tp-regexp-set', this completely replaces all existing properties.
|
Unlike `tp-regexp-set', this completely replaces all existing properties.
|
||||||
|
|
||||||
For strings, returns a NEW string (original is not modified).
|
For strings, returns a NEW string (original is not modified).
|
||||||
For buffers, modifies in-place and returns list of regions."
|
For buffers, modifies in-place and returns list of (START . END)
|
||||||
|
regions."
|
||||||
(tp--regexp-apply pattern (tp--ensure-props plist)
|
(tp--regexp-apply pattern (tp--ensure-props plist)
|
||||||
#'tp--reset-apply
|
#'tp--reset-apply
|
||||||
object start end subexp))
|
object start end subexp))
|
||||||
@ -373,12 +398,18 @@ or `define-tp-group'.
|
|||||||
OBJECT is a buffer or string; nil means current buffer.
|
OBJECT is a buffer or string; nil means current buffer.
|
||||||
START and END restrict matching to the [START, END) portion of
|
START and END restrict matching to the [START, END) portion of
|
||||||
OBJECT, in native coordinates (0-based for strings, 1-based for
|
OBJECT, in native coordinates (0-based for strings, 1-based for
|
||||||
buffers); nil means the object's bounds.
|
buffers); nil means the object's bounds. If START > END the bounds
|
||||||
|
are swapped.
|
||||||
When SUBEXP is non-nil, properties apply to that capture group of
|
When SUBEXP is non-nil, properties apply to that capture group of
|
||||||
each match instead of the whole match; a match in which the group
|
each match instead of the whole match; a match in which the group
|
||||||
does not participate contributes nothing.
|
does not participate contributes nothing. A SUBEXP larger than
|
||||||
|
PATTERN's group count signals an error.
|
||||||
|
|
||||||
Unlike `tp-regexp-set', this deeply merges nested properties."
|
Unlike `tp-regexp-set', this deeply merges nested properties.
|
||||||
|
|
||||||
|
For strings, returns a NEW string (original is not modified).
|
||||||
|
For buffers, modifies in-place and returns list of (START . END)
|
||||||
|
regions."
|
||||||
(tp--regexp-apply pattern (tp--ensure-props plist) #'tp--deep-merge-apply
|
(tp--regexp-apply pattern (tp--ensure-props plist) #'tp--deep-merge-apply
|
||||||
object start end subexp))
|
object start end subexp))
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user