summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp')
-rw-r--r--lisp/emacs-lisp/autoload.el5
-rw-r--r--lisp/emacs-lisp/avl-tree.el22
-rw-r--r--lisp/emacs-lisp/backtrace.el44
-rw-r--r--lisp/emacs-lisp/bytecomp.el41
-rw-r--r--lisp/emacs-lisp/checkdoc.el26
-rw-r--r--lisp/emacs-lisp/cl-generic.el4
-rw-r--r--lisp/emacs-lisp/cl-lib.el1
-rw-r--r--lisp/emacs-lisp/cl-macs.el39
-rw-r--r--lisp/emacs-lisp/cl-print.el21
-rw-r--r--lisp/emacs-lisp/debug.el20
-rw-r--r--lisp/emacs-lisp/easy-mmode.el1
-rw-r--r--lisp/emacs-lisp/edebug.el13
-rw-r--r--lisp/emacs-lisp/eieio-custom.el2
-rw-r--r--lisp/emacs-lisp/eieio-opt.el6
-rw-r--r--lisp/emacs-lisp/elint.el2
-rw-r--r--lisp/emacs-lisp/generator.el4
-rw-r--r--lisp/emacs-lisp/lisp.el6
-rw-r--r--lisp/emacs-lisp/package-x.el2
-rw-r--r--lisp/emacs-lisp/package.el153
-rw-r--r--lisp/emacs-lisp/re-builder.el9
-rw-r--r--lisp/emacs-lisp/rmc.el2
-rw-r--r--lisp/emacs-lisp/rx.el2069
-rw-r--r--lisp/emacs-lisp/subr-x.el5
-rw-r--r--lisp/emacs-lisp/syntax.el15
-rw-r--r--lisp/emacs-lisp/tabulated-list.el15
-rw-r--r--lisp/emacs-lisp/tcover-ses.el2
-rw-r--r--lisp/emacs-lisp/testcover.el4
-rw-r--r--lisp/emacs-lisp/text-property-search.el62
28 files changed, 1434 insertions, 1161 deletions
diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el
index 541b22e3eea..ce2827162b9 100644
--- a/lisp/emacs-lisp/autoload.el
+++ b/lisp/emacs-lisp/autoload.el
@@ -165,7 +165,7 @@ expression, in which case we want to handle forms differently."
define-globalized-minor-mode defun defmacro
easy-mmode-define-minor-mode define-minor-mode
define-inline cl-defun cl-defmacro cl-defgeneric
- pcase-defmacro))
+ cl-defstruct pcase-defmacro))
(macrop car)
(setq expand (let ((load-file-name file)) (macroexpand form)))
(memq (car expand) '(progn prog1 defalias)))
@@ -398,9 +398,8 @@ FILE's name."
;; Probably pointless, but replaces the old AUTOGEN_VCS in lisp/Makefile,
;; which was designed to handle CVSREAD=1 and equivalent.
(and autoload-ensure-writable
- (file-exists-p file)
(let ((modes (file-modes file)))
- (if (zerop (logand modes #o0200))
+ (if (and modes (zerop (logand modes #o0200)))
;; Ignore any errors here, and let subsequent attempts
;; to write the file raise any real error.
(ignore-errors (set-file-modes file (logior modes #o0200))))))
diff --git a/lisp/emacs-lisp/avl-tree.el b/lisp/emacs-lisp/avl-tree.el
index d2a3a131d16..a586fa02501 100644
--- a/lisp/emacs-lisp/avl-tree.el
+++ b/lisp/emacs-lisp/avl-tree.el
@@ -130,8 +130,8 @@ NODE is the node, and BRANCH is the branch.
(defun avl-tree--del-balance (node branch dir)
"Rebalance a tree after deleting a node.
-The deletion was done from the left (DIR=0) or right (DIR=1) sub-tree of the
-left (BRANCH=0) or right (BRANCH=1) child of NODE.
+The deletion was done from the left (DIR=0) or right (DIR=1) sub-tree
+of the left (BRANCH=0) or right (BRANCH=1) child of NODE.
Return t if the height of the tree has shrunk."
;; (or is it vice-versa for BRANCH?)
(let ((br (avl-tree--node-branch node branch))
@@ -477,11 +477,11 @@ value is non-nil."
Matching uses the comparison function previously specified in
`avl-tree-create' when TREE was created.
-If there is no such element in the tree, nil is
-returned. Optional argument NILFLAG specifies a value to return
-instead of nil in this case. This allows non-existent elements to
-be distinguished from a null element. (See also
-`avl-tree-member-p', which does this for you.)"
+If there is no such element in the tree, nil is returned.
+Optional argument NILFLAG specifies a value to return instead of nil
+in this case. This allows non-existent elements to be distinguished
+from a null element. (See also `avl-tree-member-p', which does this
+for you.)"
(let ((node (avl-tree--root tree))
(compare-function (avl-tree--cmpfun tree)))
(catch 'found
@@ -553,13 +553,13 @@ order, or descending order if REVERSE is non-nil."
(defun avl-tree-mapcar (fun tree &optional reverse)
- "Apply FUNCTION to all elements in AVL tree TREE,
+ "Apply function FUN to all elements in AVL tree TREE,
and make a list of the results.
-The FUNCTION is applied and the list constructed in ascending
+The function is applied and the list constructed in ascending
order, or descending order if REVERSE is non-nil.
-Note that if you don't care about the order in which FUNCTION is
+Note that if you don't care about the order in which FUN is
applied, just that the resulting list is in the correct order,
then
@@ -674,7 +674,7 @@ a null element stored in the AVL tree.)"
"Return an AVL tree iterator object.
Calling `iter-next' on this object will retrieve the next element
-from TREE. If REVERSE is non-nil, elements are returned in
+from TREE. If REVERSE is non-nil, elements are returned in
reverse order.
Note that any modification to TREE *immediately* invalidates all
diff --git a/lisp/emacs-lisp/backtrace.el b/lisp/emacs-lisp/backtrace.el
index 60d146e24a8..0c4c7987c3c 100644
--- a/lisp/emacs-lisp/backtrace.el
+++ b/lisp/emacs-lisp/backtrace.el
@@ -175,7 +175,8 @@ This should be a list of `backtrace-frame' objects.")
(defvar-local backtrace-view nil
"A plist describing how to render backtrace frames.
-Possible entries are :show-flags, :show-locals and :print-circle.")
+Possible entries are :show-flags, :show-locals, :print-circle
+and :print-gensym.")
(defvar-local backtrace-insert-header-function nil
"Function for inserting a header for the current Backtrace buffer.
@@ -205,6 +206,7 @@ frames where the source code location is known.")
(define-key map "p" 'backtrace-backward-frame)
(define-key map "v" 'backtrace-toggle-locals)
(define-key map "#" 'backtrace-toggle-print-circle)
+ (define-key map ":" 'backtrace-toggle-print-gensym)
(define-key map "s" 'backtrace-goto-source)
(define-key map "\C-m" 'backtrace-help-follow-symbol)
(define-key map "+" 'backtrace-multi-line)
@@ -224,6 +226,18 @@ frames where the source code location is known.")
:active (backtrace-get-index)
:selected (plist-get (backtrace-get-view) :show-locals)
:help "Show or hide the local variables for the frame at point"]
+ ["Show Circular Structures" backtrace-toggle-print-circle
+ :style toggle
+ :active (backtrace-get-index)
+ :selected (plist-get (backtrace-get-view) :print-circle)
+ :help
+ "Condense or expand shared or circular structures in the frame at point"]
+ ["Show Uninterned Symbols" backtrace-toggle-print-gensym
+ :style toggle
+ :active (backtrace-get-index)
+ :selected (plist-get (backtrace-get-view) :print-gensym)
+ :help
+ "Toggle unique printing of uninterned symbols in the frame at point"]
["Expand \"...\"s" backtrace-expand-ellipses
:help "Expand all the abbreviated forms in the current frame"]
["Show on Multiple Lines" backtrace-multi-line
@@ -339,6 +353,7 @@ It runs `backtrace-revert-hook', then calls `backtrace-print'."
`(let ((print-escape-control-characters t)
(print-escape-newlines t)
(print-circle (plist-get ,view :print-circle))
+ (print-gensym (plist-get ,view :print-gensym))
(standard-output (current-buffer)))
,@body))
@@ -420,12 +435,18 @@ Set it to VALUE unless the button is a `backtrace-ellipsis' button."
(defun backtrace-toggle-print-circle (&optional all)
"Toggle `print-circle' for the backtrace frame at point.
-With prefix argument ALL, toggle the value of :print-circle in
-`backtrace-view', which affects all of the backtrace frames in
-the buffer."
+With prefix argument ALL, toggle the default value bound to
+`print-circle' for all the frames in the buffer."
(interactive "P")
(backtrace--toggle-feature :print-circle all))
+(defun backtrace-toggle-print-gensym (&optional all)
+ "Toggle `print-gensym' for the backtrace frame at point.
+With prefix argument ALL, toggle the default value bound to
+`print-gensym' for all the frames in the buffer."
+ (interactive "P")
+ (backtrace--toggle-feature :print-gensym all))
+
(defun backtrace--toggle-feature (feature all)
"Toggle FEATURE for the current backtrace frame or for the buffer.
FEATURE should be one of the options in `backtrace-view'. If ALL
@@ -450,12 +471,15 @@ position point at the start of the frame it was in before."
(goto-char (point-min))
(while (and (not (eql index (backtrace-get-index)))
(< (point) (point-max)))
- (goto-char (backtrace-get-frame-end)))))
- (let ((index (backtrace-get-index)))
- (unless index
- (user-error "Not in a stack frame"))
- (backtrace--set-feature feature
- (not (plist-get (backtrace-get-view) feature))))))
+ (goto-char (backtrace-get-frame-end))))
+ (message "%s is now %s for all frames"
+ (substring (symbol-name feature) 1) value))
+ (unless (backtrace-get-index)
+ (user-error "Not in a stack frame"))
+ (let ((value (not (plist-get (backtrace-get-view) feature))))
+ (backtrace--set-feature feature value)
+ (message "%s is now %s for this frame"
+ (substring (symbol-name feature) 1) value))))
(defun backtrace--set-feature (feature value)
"Set FEATURE in the view plist of the frame at point to VALUE.
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 40b4e2f4671..1d0f07a9410 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -124,7 +124,7 @@
(require 'backquote)
(require 'macroexp)
(require 'cconv)
-(eval-when-compile (require 'compile))
+(require 'compile)
;; Refrain from using cl-lib at run-time here, since it otherwise prevents
;; us from emitting warnings when compiling files which use cl-lib without
;; requiring it! (bug#30635)
@@ -395,16 +395,16 @@ else the global value will be modified."
"Non-nil means collect call-graph information when compiling.
This records which functions were called and from where.
If the value is t, compilation displays the call graph when it finishes.
-If the value is neither t nor nil, compilation asks you whether to display
-the graph.
+If the value is neither t nor nil, compilation asks you whether to
+display the graph.
-The call tree only lists functions called, not macros used. Those functions
-which the byte-code interpreter knows about directly (eq, cons, etc.) are
-not reported.
+The call tree only lists functions called, not macros used. Those
+functions which the byte-code interpreter knows about directly (eq,
+cons, etc.) are not reported.
-The call tree also lists those functions which are not known to be called
-\(that is, to which no calls have been compiled). Functions which can be
-invoked interactively are excluded from this list."
+The call tree also lists those functions which are not known to be
+called (that is, to which no calls have been compiled). Functions
+which can be invoked interactively are excluded from this list."
:type '(choice (const :tag "Yes" t) (const :tag "No" nil)
(other :tag "Ask" lambda)))
@@ -1045,8 +1045,26 @@ message buffer `default-directory'."
:type '(repeat (choice (const :tag "Default" nil)
(string :tag "Directory"))))
+(defvar emacs-lisp-compilation-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map compilation-minor-mode-map)
+ (define-key map "g" 'emacs-lisp-compilation-recompile)
+ map))
+
+(defvar emacs-lisp-compilation--current-file nil)
+
(define-compilation-mode emacs-lisp-compilation-mode "elisp-compile"
- "The variant of `compilation-mode' used for emacs-lisp compilation buffers.")
+ "The variant of `compilation-mode' used for emacs-lisp compilation buffers."
+ (setq-local emacs-lisp-compilation--current-file nil))
+
+(defun emacs-lisp-compilation-recompile ()
+ "Recompile the previously byte-compiled file."
+ (interactive)
+ (unless emacs-lisp-compilation--current-file
+ (error "No previously compiled file"))
+ (unless (stringp emacs-lisp-compilation--current-file)
+ (error "Only files can be recompiled"))
+ (byte-compile-file emacs-lisp-compilation--current-file))
(defvar byte-compile-current-form nil)
(defvar byte-compile-dest-file nil)
@@ -1236,6 +1254,7 @@ message buffer `default-directory'."
;; Do this after setting default-directory.
(unless (derived-mode-p 'compilation-mode)
(emacs-lisp-compilation-mode))
+ (setq emacs-lisp-compilation--current-file byte-compile-current-file)
(compilation-forget-errors)
pt))))
@@ -4071,7 +4090,7 @@ that suppresses all warnings during execution of BODY."
,condition '(fboundp functionp)
byte-compile-unresolved-functions))
(bound-list (byte-compile-find-bound-condition
- ,condition '(boundp default-boundp)))
+ ,condition '(boundp default-boundp local-variable-p)))
;; Maybe add to the bound list.
(byte-compile-bound-variables
(append bound-list byte-compile-bound-variables)))
diff --git a/lisp/emacs-lisp/checkdoc.el b/lisp/emacs-lisp/checkdoc.el
index 8a88c5756f1..51fb75da691 100644
--- a/lisp/emacs-lisp/checkdoc.el
+++ b/lisp/emacs-lisp/checkdoc.el
@@ -194,7 +194,7 @@
"Non-nil means attempt auto-fixing of doc strings.
If this value is the symbol `query', then the user is queried before
any change is made. If the value is `automatic', then all changes are
-made without asking unless the change is very-complex. If the value
+made without asking unless the change is very complex. If the value
is `semiautomatic' or any other value, then simple fixes are made
without asking, and complex changes are made by asking the user first.
The value `never' is the same as nil, never ask or change anything."
@@ -238,10 +238,10 @@ This is automatically set to nil if Ispell does not exist on your
system. Possible values are:
nil - Don't spell-check during basic style checks.
- defun - Spell-check when style checking a single defun
- buffer - Spell-check when style checking the whole buffer
+ defun - Spell-check when style checking a single defun.
+ buffer - Spell-check when style checking the whole buffer.
interactive - Spell-check during any interactive check.
- t - Always spell-check"
+ t - Always spell-check."
:type '(choice (const nil)
(const defun)
(const buffer)
@@ -1050,7 +1050,7 @@ space at the end of each line."
(defun checkdoc-ispell ()
"Check the style and spelling of everything interactively.
Calls `checkdoc' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc'"
+Prefix argument is the same as for `checkdoc'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc)))
@@ -1059,7 +1059,7 @@ Prefix argument is the same as for `checkdoc'"
(defun checkdoc-ispell-current-buffer ()
"Check the style and spelling of the current buffer.
Calls `checkdoc-current-buffer' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-current-buffer'"
+Prefix argument is the same as for `checkdoc-current-buffer'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-current-buffer)))
@@ -1068,7 +1068,7 @@ Prefix argument is the same as for `checkdoc-current-buffer'"
(defun checkdoc-ispell-interactive ()
"Check the style and spelling of the current buffer interactively.
Calls `checkdoc-interactive' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-interactive'"
+Prefix argument is the same as for `checkdoc-interactive'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-interactive)))
@@ -1077,7 +1077,7 @@ Prefix argument is the same as for `checkdoc-interactive'"
(defun checkdoc-ispell-message-interactive ()
"Check the style and spelling of message text interactively.
Calls `checkdoc-message-interactive' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-message-interactive'"
+Prefix argument is the same as for `checkdoc-message-interactive'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-message-interactive
@@ -1087,7 +1087,7 @@ Prefix argument is the same as for `checkdoc-message-interactive'"
(defun checkdoc-ispell-message-text ()
"Check the style and spelling of message text interactively.
Calls `checkdoc-message-text' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-message-text'"
+Prefix argument is the same as for `checkdoc-message-text'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-message-text)))
@@ -1096,7 +1096,7 @@ Prefix argument is the same as for `checkdoc-message-text'"
(defun checkdoc-ispell-start ()
"Check the style and spelling of the current buffer.
Calls `checkdoc-start' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-start'"
+Prefix argument is the same as for `checkdoc-start'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-start)))
@@ -1105,7 +1105,7 @@ Prefix argument is the same as for `checkdoc-start'"
(defun checkdoc-ispell-continue ()
"Check the style and spelling of the current buffer after point.
Calls `checkdoc-continue' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-continue'"
+Prefix argument is the same as for `checkdoc-continue'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-continue)))
@@ -1114,7 +1114,7 @@ Prefix argument is the same as for `checkdoc-continue'"
(defun checkdoc-ispell-comments ()
"Check the style and spelling of the current buffer's comments.
Calls `checkdoc-comments' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-comments'"
+Prefix argument is the same as for `checkdoc-comments'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-comments)))
@@ -1123,7 +1123,7 @@ Prefix argument is the same as for `checkdoc-comments'"
(defun checkdoc-ispell-defun ()
"Check the style and spelling of the current defun with Ispell.
Calls `checkdoc-defun' with spell-checking turned on.
-Prefix argument is the same as for `checkdoc-defun'"
+Prefix argument is the same as for `checkdoc-defun'."
(interactive)
(let ((checkdoc-spellcheck-documentation-flag t))
(call-interactively #'checkdoc-defun)))
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
index 10190f49339..b0173dc991b 100644
--- a/lisp/emacs-lisp/cl-generic.el
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -22,7 +22,7 @@
;;; Commentary:
-;; This implements the most of CLOS's multiple-dispatch generic functions.
+;; This implements most of CLOS's multiple-dispatch generic functions.
;; To use it you need either (require 'cl-generic) or (require 'cl-lib).
;; The main entry points are: `cl-defgeneric' and `cl-defmethod'.
@@ -911,7 +911,7 @@ Can only be used from within the lexical body of a primary or around method."
;;; Add support for describe-function
(defun cl--generic-search-method (met-name)
- "For `find-function-regexp-alist'. Searches for a cl-defmethod.
+ "For `find-function-regexp-alist'. Searches for a cl-defmethod.
MET-NAME is as returned by `cl--generic-load-hist-format'."
(let ((base-re (concat "(\\(?:cl-\\)?defmethod[ \t]+"
(regexp-quote (format "%s" (car met-name)))
diff --git a/lisp/emacs-lisp/cl-lib.el b/lisp/emacs-lisp/cl-lib.el
index c09fcf51eba..ff096918173 100644
--- a/lisp/emacs-lisp/cl-lib.el
+++ b/lisp/emacs-lisp/cl-lib.el
@@ -110,6 +110,7 @@ a future Emacs interpreter will be able to use it.")
;; These macros are defined here so that they
;; can safely be used in init files.
+;;;###autoload
(defmacro cl-incf (place &optional x)
"Increment PLACE by X (1 by default).
PLACE may be a symbol, or any generalized variable allowed by `setf'.
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index 1ae72666244..a02fae391bc 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -2906,7 +2906,16 @@ Supported keywords for slots are:
(error "Duplicate slots named %s in %s" slot name))
(let ((accessor (intern (format "%s%s" conc-name slot)))
(default-value (pop desc))
- (doc (plist-get desc :documentation)))
+ (doc (plist-get desc :documentation))
+ (access-body
+ `(progn
+ ,@(and pred-check
+ (list `(or ,pred-check
+ (signal 'wrong-type-argument
+ (list ',name cl-x)))))
+ ,(if (memq type '(nil vector)) `(aref cl-x ,pos)
+ (if (= pos 0) '(car cl-x)
+ `(nth ,pos cl-x))))))
(push slot slots)
(push default-value defaults)
;; The arg "cl-x" is referenced by name in eg pred-form
@@ -2916,13 +2925,7 @@ Supported keywords for slots are:
slot name
(if doc (concat "\n" doc) ""))
(declare (side-effect-free t))
- ,@(and pred-check
- (list `(or ,pred-check
- (signal 'wrong-type-argument
- (list ',name cl-x)))))
- ,(if (memq type '(nil vector)) `(aref cl-x ,pos)
- (if (= pos 0) '(car cl-x)
- `(nth ,pos cl-x))))
+ ,access-body)
forms)
(when (cl-oddp (length desc))
(push
@@ -2942,11 +2945,18 @@ Supported keywords for slots are:
forms)
(push kw desc)
(setcar defaults nil))))
- (if (plist-get desc ':read-only)
- (push `(gv-define-expander ,accessor
- (lambda (_cl-do _cl-x)
- (error "%s is a read-only slot" ',accessor)))
- forms)
+ (cond
+ ((eq defsym 'defun)
+ (unless (plist-get desc ':read-only)
+ (push `(defun ,(gv-setter accessor) (val cl-x)
+ (setf ,access-body val))
+ forms)))
+ ((plist-get desc ':read-only)
+ (push `(gv-define-expander ,accessor
+ (lambda (_cl-do _cl-x)
+ (error "%s is a read-only slot" ',accessor)))
+ forms))
+ (t
;; For normal slots, we don't need to define a setf-expander,
;; since gv-get can use the compiler macro to get the
;; same result.
@@ -2964,7 +2974,7 @@ Supported keywords for slots are:
;; ,(and pred-check `',pred-check)
;; ,pos)))
;; forms)
- )
+ ))
(if print-auto
(nconc print-func
(list `(princ ,(format " %s" slot) cl-s)
@@ -3006,6 +3016,7 @@ Supported keywords for slots are:
`(progn
(defvar ,tag-symbol)
,@(nreverse forms)
+ :autoload-end
;; Call cl-struct-define during compilation as well, so that
;; a subsequent cl-defstruct in the same file can correctly include this
;; struct as a parent.
diff --git a/lisp/emacs-lisp/cl-print.el b/lisp/emacs-lisp/cl-print.el
index 5fe3dd1b912..530770128e6 100644
--- a/lisp/emacs-lisp/cl-print.el
+++ b/lisp/emacs-lisp/cl-print.el
@@ -548,21 +548,22 @@ limit."
;; call_debugger (bug#31919).
(let* ((print-length (when limit (min limit 50)))
(print-level (when limit (min 8 (truncate (log limit)))))
- (delta (when limit
- (max 1 (truncate (/ print-length print-level))))))
+ (delta-length (when limit
+ (max 1 (truncate (/ print-length print-level))))))
(with-temp-buffer
(catch 'done
(while t
(erase-buffer)
(funcall print-function value (current-buffer))
- ;; Stop when either print-level is too low or the value is
- ;; successfully printed in the space allowed.
- (when (or (not limit)
- (< (- (point-max) (point-min)) limit)
- (= print-level 2))
- (throw 'done (buffer-string)))
- (cl-decf print-level)
- (cl-decf print-length delta))))))
+ (let ((result (- (point-max) (point-min))))
+ ;; Stop when either print-level is too low or the value is
+ ;; successfully printed in the space allowed.
+ (when (or (not limit) (< result limit) (<= print-level 2))
+ (throw 'done (buffer-string)))
+ (let* ((ratio (/ result limit))
+ (delta-level (max 1 (min (- print-level 2) ratio))))
+ (cl-decf print-level delta-level)
+ (cl-decf print-length (* delta-length delta-level)))))))))
(provide 'cl-print)
;;; cl-print.el ends here
diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el
index 8989aa07196..f711971c181 100644
--- a/lisp/emacs-lisp/debug.el
+++ b/lisp/emacs-lisp/debug.el
@@ -163,7 +163,10 @@ of the evaluator.
You may call with no args, or you may pass nil as the first arg and
any other args you like. In that case, the list of args after the
-first will be printed into the backtrace buffer."
+first will be printed into the backtrace buffer.
+
+If `inhibit-redisplay' is non-nil when this function is called,
+the debugger will not be entered."
(interactive)
(cond
(inhibit-redisplay
@@ -585,12 +588,15 @@ The environment used is the one when entering the activation frame at point."
(define-derived-mode debugger-mode backtrace-mode "Debugger"
"Mode for debugging Emacs Lisp using a backtrace.
\\<debugger-mode-map>
-A line starts with `*' if exiting that frame will call the debugger.
-Type \\[debugger-frame] or \\[debugger-frame-clear] to set or remove the `*'.
-
-When in debugger due to frame being exited,
-use the \\[debugger-return-value] command to override the value
-being returned from that frame.
+A frame marked with `*' in the backtrace means that exiting that
+frame will enter the debugger. You can flag frames to enter the
+debugger when frame is exited with \\[debugger-frame], and remove
+the flag with \\[debugger-frame-clear].
+
+When in debugger invoked due to exiting a frame which was flagged
+with a `*', you can use the \\[debugger-return-value] command to
+override the value being returned from that frame when the debugger
+exits.
Use \\[debug-on-entry] and \\[cancel-debug-on-entry] to control
which functions will enter the debugger when called.
diff --git a/lisp/emacs-lisp/easy-mmode.el b/lisp/emacs-lisp/easy-mmode.el
index bbc3a27504c..5e7b29eddf8 100644
--- a/lisp/emacs-lisp/easy-mmode.el
+++ b/lisp/emacs-lisp/easy-mmode.el
@@ -418,6 +418,7 @@ on if the hook has explicitly disabled it.
`(progn
(progn
+ (put ',global-mode 'globalized-minor-mode t)
:autoload-end
(defvar ,MODE-major-mode nil)
(make-variable-buffer-local ',MODE-major-mode))
diff --git a/lisp/emacs-lisp/edebug.el b/lisp/emacs-lisp/edebug.el
index c898da3d39f..717026995a3 100644
--- a/lisp/emacs-lisp/edebug.el
+++ b/lisp/emacs-lisp/edebug.el
@@ -2165,6 +2165,9 @@ into `edebug--cl-macrolet-defs' which is checked in `edebug-list-form-args'."
;; but only at the top level inside unquotes.
(def-edebug-spec backquote-form
(&or
+ ;; Disallow instrumentation of , and ,@ inside a nested backquote, since
+ ;; these are likely to be forms generated by a macro being debugged.
+ ("`" nested-backquote-form)
([&or "," ",@"] &or ("quote" backquote-form) form)
;; The simple version:
;; (backquote-form &rest backquote-form)
@@ -2180,6 +2183,16 @@ into `edebug--cl-macrolet-defs' which is checked in `edebug-list-form-args'."
(vector &rest backquote-form)
sexp))
+(def-edebug-spec nested-backquote-form
+ (&or
+ ;; Allow instrumentation of any , or ,@ contained within the (\, ...) or
+ ;; (\,@ ...) matched on the next line.
+ ([&or "," ",@"] backquote-form)
+ (nested-backquote-form [&rest [&not "," ",@"] nested-backquote-form]
+ . [&or nil nested-backquote-form])
+ (vector &rest nested-backquote-form)
+ sexp))
+
;; Special version of backquote that instruments backquoted forms
;; destined to be evaluated, usually as the result of a
;; macroexpansion. Backquoted code can only have unquotes (, and ,@)
diff --git a/lisp/emacs-lisp/eieio-custom.el b/lisp/emacs-lisp/eieio-custom.el
index 350fca6d9ab..8fc74beca1e 100644
--- a/lisp/emacs-lisp/eieio-custom.el
+++ b/lisp/emacs-lisp/eieio-custom.el
@@ -454,7 +454,7 @@ Must return the created widget."
(cl-defmethod eieio-read-customization-group ((obj eieio-default-superclass))
"Do a completing read on the name of a customization group in OBJ.
-Return the symbol for the group, or nil"
+Return the symbol for the group, or nil."
(let ((g (eieio--class-option (eieio--object-class obj)
:custom-groups)))
(if (= (length g) 1)
diff --git a/lisp/emacs-lisp/eieio-opt.el b/lisp/emacs-lisp/eieio-opt.el
index f08c1de936b..fdbf05b3993 100644
--- a/lisp/emacs-lisp/eieio-opt.el
+++ b/lisp/emacs-lisp/eieio-opt.el
@@ -86,7 +86,7 @@ Argument CH-PREFIX is another character prefix to display."
(defun eieio-build-class-alist (&optional class instantiable-only buildlist)
"Return an alist of all currently active classes for completion purposes.
Optional argument CLASS is the class to start with.
-If INSTANTIABLE-ONLY is non nil, only allow names of classes which
+If INSTANTIABLE-ONLY is non-nil, only allow names of classes which
are not abstract, otherwise allow all classes.
Optional argument BUILDLIST is more list to attach and is used internally."
(let* ((cc (or class 'eieio-default-superclass))
@@ -107,7 +107,7 @@ Optional argument BUILDLIST is more list to attach and is used internally."
(defun eieio-read-class (prompt &optional histvar instantiable-only)
"Return a class chosen by the user using PROMPT.
Optional argument HISTVAR is a variable to use as history.
-If INSTANTIABLE-ONLY is non nil, only allow names of classes which
+If INSTANTIABLE-ONLY is non-nil, only allow names of classes which
are not abstract."
(intern (completing-read prompt (eieio-build-class-alist nil instantiable-only)
nil t nil
@@ -117,7 +117,7 @@ are not abstract."
"Return a class chosen by the user using PROMPT.
CLASS is the base class, and completion occurs across all subclasses.
Optional argument HISTVAR is a variable to use as history.
-If INSTANTIABLE-ONLY is non nil, only allow names of classes which
+If INSTANTIABLE-ONLY is non-nil, only allow names of classes which
are not abstract."
(intern (completing-read prompt
(eieio-build-class-alist class instantiable-only)
diff --git a/lisp/emacs-lisp/elint.el b/lisp/emacs-lisp/elint.el
index b7ef6eeb2ae..187d619f1bc 100644
--- a/lisp/emacs-lisp/elint.el
+++ b/lisp/emacs-lisp/elint.el
@@ -106,7 +106,7 @@ are as follows, and suppress messages about the indicated features:
:group 'elint)
(defcustom elint-directory-skip-re "\\(ldefs-boot\\|loaddefs\\)\\.el\\'"
- "If nil, a regexp matching files to skip when linting a directory."
+ "If non-nil, a regexp matching files to skip when linting a directory."
:type '(choice (const :tag "Lint all files" nil)
(regexp :tag "Regexp to skip"))
:safe 'string-or-null-p
diff --git a/lisp/emacs-lisp/generator.el b/lisp/emacs-lisp/generator.el
index caf5fed154b..dd709b32b09 100644
--- a/lisp/emacs-lisp/generator.el
+++ b/lisp/emacs-lisp/generator.el
@@ -541,7 +541,7 @@ don't yield.")
(defun cps--replace-variable-references (var new-var form)
"Replace all non-shadowed references to VAR with NEW-VAR in FORM.
-This routine does not modify FORM. Instead, it returns a
+This routine does not modify FORM. Instead, it returns a
modified copy."
(macroexpand-all
`(cl-symbol-macrolet ((,var ,new-var)) ,form)
@@ -760,7 +760,7 @@ Return the value with which ITERATOR finished iteration."
(cps--advance-for ,cs))))
(defun cps--handle-loop-for (var)
- "Support `iter-by' in `loop'. "
+ "Support `iter-by' in `loop'."
;; N.B. While the cl-loop-for-handler is a documented interface,
;; there's no documented way for cl-loop-for-handler callbacks to do
;; anything useful! Additionally, cl-loop currently lexbinds useful
diff --git a/lisp/emacs-lisp/lisp.el b/lisp/emacs-lisp/lisp.el
index ab0e853e9a4..93d43e319a5 100644
--- a/lisp/emacs-lisp/lisp.el
+++ b/lisp/emacs-lisp/lisp.el
@@ -60,8 +60,8 @@ Should take the same arguments and behave similarly to `forward-sexp'.")
With ARG, do it that many times. Negative arg -N means move
backward across N balanced expressions. This command assumes
point is not in a string or comment. Calls
-`forward-sexp-function' to do the work, if that is non-nil. If
-unable to move over a sexp, signal `scan-error' with three
+`forward-sexp-function' to do the work, if that is non-nil.
+If unable to move over a sexp, signal `scan-error' with three
arguments: a message, the start of the obstacle (usually a
parenthesis or list marker of some kind), and end of the
obstacle."
@@ -164,7 +164,7 @@ This command will also work on other parentheses-like expressions
defined by the current language mode. With ARG, do this that
many times. A negative argument means move backward but still to
a less deep spot. If ESCAPE-STRINGS is non-nil (as it is
-interactively), move out of enclosing strings as well. If
+interactively), move out of enclosing strings as well. If
NO-SYNTAX-CROSSING is non-nil (as it is interactively), prefer to
break out of any enclosing string instead of moving to the start
of a list broken across multiple strings. On error, location of
diff --git a/lisp/emacs-lisp/package-x.el b/lisp/emacs-lisp/package-x.el
index e26b6b99c17..2815be3fe65 100644
--- a/lisp/emacs-lisp/package-x.el
+++ b/lisp/emacs-lisp/package-x.el
@@ -124,7 +124,7 @@ Return the file contents, as a string, or nil if unsuccessful."
(buffer-substring-no-properties (point-min) (point-max)))))))
(defun package--archive-contents-from-file ()
- "Parse the archive-contents at `package-archive-upload-base'"
+ "Parse the archive-contents at `package-archive-upload-base'."
(let ((file (expand-file-name "archive-contents"
package-archive-upload-base)))
(if (not (file-exists-p file))
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index cd127e1a8e8..ab1fb8b90fc 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -120,9 +120,9 @@
;; - "installed" instead of a blank in the status column
;; - tramp needs its files to be compiled in a certain order.
;; how to handle this? fix tramp?
-;; - maybe we need separate .elc directories for various emacs versions
-;; and also emacs-vs-xemacs. That way conditional compilation can
-;; work. But would this break anything?
+;; - maybe we need separate .elc directories for various emacs
+;; versions. That way conditional compilation can work. But would
+;; this break anything?
;; - William Xu suggests being able to open a package file without
;; installing it
;; - Interface with desktop.el so that restarting after an install
@@ -214,7 +214,10 @@ Each element has the form (ID . LOCATION).
(Other types of URL are currently not supported.)
Only add locations that you trust, since fetching and installing
-a package can run arbitrary code."
+a package can run arbitrary code.
+
+HTTPS URLs should be used where possible, as they offer superior
+security."
:type '(alist :key-type (string :tag "Archive name")
:value-type (string :tag "URL or directory name"))
:risky t
@@ -354,9 +357,9 @@ contents of the archive."
(defun package-check-signature ()
"Check whether we have a usable OpenPGP configuration.
-If true, and `package-check-signature' is `allow-unsigned',
-return `allow-unsigned', otherwise return the value of
-`package-check-signature'."
+If so, and variable `package-check-signature' is
+`allow-unsigned', return `allow-unsigned', otherwise return the
+value of variable `package-check-signature'."
(if (eq package-check-signature 'allow-unsigned)
(progn
(require 'epg-config)
@@ -445,11 +448,11 @@ Slots:
`summary' Short description of the package, typically taken from
the first line of the file.
-`reqs' Requirements of the package. A list of (PACKAGE
+`reqs' Requirements of the package. A list of (PACKAGE
VERSION-LIST) naming the dependent package and the minimum
required version.
-`kind' The distribution format of the package. Currently, it is
+`kind' The distribution format of the package. Currently, it is
either `single' or `tar'.
`archive' The name of the archive (as a string) whence this
@@ -472,6 +475,8 @@ Slots:
signed)
(defun package--from-builtin (bi-desc)
+ "Create a `package-desc' object from BI-DESC.
+BI-DESC should be a `package--bi-desc' object."
(package-desc-create :name (pop bi-desc)
:version (package--bi-desc-version bi-desc)
:summary (package--bi-desc-summary bi-desc)
@@ -509,11 +514,21 @@ This is, approximately, the inverse of `version-to-list'.
(apply #'concat (nreverse str-list)))))
(defun package-desc-full-name (pkg-desc)
+ "Return full name of package-desc object PKG-DESC.
+This is the name of the package with its version appended."
(format "%s-%s"
(package-desc-name pkg-desc)
(package-version-join (package-desc-version pkg-desc))))
(defun package-desc-suffix (pkg-desc)
+ "Return file-name extension of package-desc object PKG-DESC.
+Depending on the `package-desc-kind' of PKG-DESC, this is one of:
+
+ 'single - \".el\"
+ 'tar - \".tar\"
+ 'dir - \"\"
+
+Signal an error if the kind is none of the above."
(pcase (package-desc-kind pkg-desc)
('single ".el")
('tar ".tar")
@@ -521,6 +536,10 @@ This is, approximately, the inverse of `version-to-list'.
(kind (error "Unknown package kind: %s" kind))))
(defun package-desc--keywords (pkg-desc)
+ "Return keywords of package-desc object PKG-DESC.
+These keywords come from the foo-pkg.el file, and in general
+corresponds to the keywords in the \"Keywords\" header of the
+package."
(let ((keywords (cdr (assoc :keywords (package-desc-extras pkg-desc)))))
(if (eq (car-safe keywords) 'quote)
(nth 1 keywords)
@@ -530,10 +549,10 @@ This is, approximately, the inverse of `version-to-list'.
"Return the priority of the archive of package-desc object P."
(package-archive-priority (package-desc-archive p)))
-;; Package descriptor format used in finder-inf.el and package--builtins.
(cl-defstruct (package--bi-desc
(:constructor package-make-builtin (version summary))
(:type vector))
+ "Package descriptor format used in finder-inf.el and package--builtins."
version
reqs
summary)
@@ -575,7 +594,15 @@ loaded and/or activated, customize `package-load-list'.")
;; The following functions are called on each installed package by
;; `package-load-all-descriptors', which ultimately populates the
;; `package-alist' variable.
+
(defun package-process-define-package (exp)
+ "Process define-package expression EXP and push it to `package-alist'.
+EXP should be a form read from a foo-pkg.el file.
+Convert EXP into a `package-desc' object using the
+`package-desc-from-define' constructor before pushing it to
+`package-alist'.
+If there already exists a package by that name in
+`package-alist', replace that definition with the new one."
(when (eq (car-safe exp) 'define-package)
(let* ((new-pkg-desc (apply #'package-desc-from-define (cdr exp)))
(name (package-desc-name new-pkg-desc))
@@ -866,6 +893,7 @@ untar into a directory named DIR; otherwise, signal an error."
(mapcar #'macroexp-quote
(apply #'nconc
(mapcar (lambda (pair) (list (car pair) (cdr pair))) alist))))
+
(defun package-unpack (pkg-desc)
"Install the contents of the current buffer as a package."
(let* ((name (package-desc-name pkg-desc))
@@ -977,7 +1005,7 @@ untar into a directory named DIR; otherwise, signal an error."
auto-name))
(defun package--make-autoloads-and-stuff (pkg-desc pkg-dir)
- "Generate autoloads, description file, etc.. for PKG-DESC installed at PKG-DIR."
+ "Generate autoloads, description file, etc., for PKG-DESC installed at PKG-DIR."
(package-generate-autoloads (package-desc-name pkg-desc) pkg-dir)
(let ((desc-file (expand-file-name (package--description-file pkg-dir)
pkg-dir)))
@@ -1028,6 +1056,7 @@ is wrapped around any parts requiring it."
deps))))
(declare-function lm-header "lisp-mnt" (header))
+(declare-function lm-header-multiline "lisp-mnt" (header))
(declare-function lm-homepage "lisp-mnt" (&optional file))
(declare-function lm-keywords-list "lisp-mnt" (&optional file))
(declare-function lm-maintainer "lisp-mnt" (&optional file))
@@ -1054,8 +1083,7 @@ boundaries."
(narrow-to-region start (point))
(require 'lisp-mnt)
;; Use some headers we've invented to drive the process.
- (let* ((requires-str (lm-header "package-requires"))
- ;; Prefer Package-Version; if defined, the package author
+ (let* (;; Prefer Package-Version; if defined, the package author
;; probably wants us to use it. Otherwise try Version.
(pkg-version
(or (package-strip-rcs-id (lm-header "package-version"))
@@ -1067,9 +1095,9 @@ boundaries."
"Package lacks a \"Version\" or \"Package-Version\" header"))
(package-desc-from-define
file-name pkg-version desc
- (if requires-str
- (package--prepare-dependencies
- (package-read-from-string requires-str)))
+ (and-let* ((require-lines (lm-header-multiline "package-requires")))
+ (package--prepare-dependencies
+ (package-read-from-string (mapconcat #'identity require-lines " "))))
:kind 'single
:url homepage
:keywords keywords
@@ -1899,12 +1927,13 @@ if all the in-between dependencies are also in PACKAGE-LIST."
;; installed in a variety of ways (archives, buffer, file), but
;; requirements (dependencies) are always satisfied by looking in
;; `package-archive-contents'.
+
(defun package-archive-base (desc)
- "Return the archive containing the package NAME."
+ "Return the package described by DESC."
(cdr (assoc (package-desc-archive desc) package-archives)))
(defun package-install-from-archive (pkg-desc)
- "Download and install a tar package."
+ "Download and install a tar package defined by PKG-DESC."
;; This won't happen, unless the archive is doing something wrong.
(when (eq (package-desc-kind pkg-desc) 'dir)
(error "Can't install directory package from archive"))
@@ -2081,7 +2110,7 @@ Downloads and installs required packages as needed."
;;;###autoload
(defun package-install-file (file)
- "Install a package from a file.
+ "Install a package from FILE.
The file can either be a tar file, an Emacs Lisp file, or a
directory."
(interactive "fPackage file name: ")
@@ -2217,7 +2246,7 @@ object."
;;;###autoload
(defun package-autoremove ()
- "Remove packages that are no more needed.
+ "Remove packages that are no longer needed.
Packages that are no more needed by other packages in
`package-selected-packages' and their dependencies
@@ -2334,6 +2363,8 @@ The description is read from the installed package files."
)))
(defun describe-package-1 (pkg)
+ "Insert the package description for PKG.
+Helper function for `describe-package'."
(require 'lisp-mnt)
(let* ((desc (or
(if (package-desc-p pkg) pkg)
@@ -2563,6 +2594,9 @@ The description is read from the installed package files."
(browse-url-add-buttons))))
(defun package-install-button-action (button)
+ "Run `package-install' on the package BUTTON points to.
+Used for the 'action property of buttons in the buffer created by
+`describe-package'."
(let ((pkg-desc (button-get button 'package-desc)))
(when (y-or-n-p (format-message "Install package `%s'? "
(package-desc-full-name pkg-desc)))
@@ -2571,6 +2605,9 @@ The description is read from the installed package files."
(goto-char (point-min)))))
(defun package-delete-button-action (button)
+ "Run `package-delete' on the package BUTTON points to.
+Used for the 'action property of buttons in the buffer created by
+`describe-package'."
(let ((pkg-desc (button-get button 'package-desc)))
(when (y-or-n-p (format-message "Delete package `%s'? "
(package-desc-full-name pkg-desc)))
@@ -2579,10 +2616,17 @@ The description is read from the installed package files."
(goto-char (point-min)))))
(defun package-keyword-button-action (button)
+ "Show filtered \"*Packages*\" buffer for BUTTON.
+The buffer is filtered by the `package-keyword' property of BUTTON.
+Used for the 'action property of buttons in the buffer created by
+`describe-package'."
(let ((pkg-keyword (button-get button 'package-keyword)))
(package-show-package-list t (list pkg-keyword))))
-(defun package-make-button (text &rest props)
+(defun package-make-button (text &rest properties)
+ "Insert button labeled TEXT with button PROPERTIES at point.
+PROPERTIES are passed to `insert-text-button', for which this
+function is a convenience wrapper used by `describe-package-1'."
(let ((button-text (if (display-graphic-p) text (concat "[" text "]")))
(button-face (if (display-graphic-p)
'(:box (:line-width 2 :color "dark grey")
@@ -2590,20 +2634,23 @@ The description is read from the installed package files."
:foreground "black")
'link)))
(apply #'insert-text-button button-text 'face button-face 'follow-link t
- props)))
-
-(defun package--print-email-button (name)
- (when (car name)
- (insert (car name)))
- (when (and (car name) (cdr name))
+ properties)))
+
+(defun package--print-email-button (recipient)
+ "Insert a button whose action will send an email to RECIPIENT.
+NAME should have the form (FULLNAME . EMAIL) where FULLNAME is
+either a full name or nil, and EMAIL is a valid email address."
+ (when (car recipient)
+ (insert (car recipient)))
+ (when (and (car recipient) (cdr recipient))
(insert " "))
- (when (cdr name)
+ (when (cdr recipient)
(insert "<")
- (insert-text-button (cdr name)
+ (insert-text-button (cdr recipient)
'follow-link t
'action (lambda (_)
(compose-mail
- (format "%s <%s>" (car name) (cdr name)))))
+ (format "%s <%s>" (car recipient) (cdr recipient)))))
(insert ">"))
(insert "\n"))
@@ -2678,11 +2725,11 @@ Letters do not insert themselves; instead, they are commands.
package-menu--transaction-status)))
(setq tabulated-list-format
`[("Package" 18 package-menu--name-predicate)
- ("Version" 13 nil)
+ ("Version" 13 package-menu--version-predicate)
("Status" 10 package-menu--status-predicate)
,@(if (cdr package-archives)
'(("Archive" 10 package-menu--archive-predicate)))
- ("Description" 0 nil)])
+ ("Description" 0 package-menu--description-predicate)])
(setq tabulated-list-padding 2)
(setq tabulated-list-sort-key (cons "Status" nil))
(add-hook 'tabulated-list-revert-hook #'package-menu--refresh nil t)
@@ -2701,13 +2748,13 @@ package PKG-DESC, add one. The alist is keyed with PKG-DESC."
(push (cons ,pkg-desc ,status) ,listname)))
(defvar package-list-unversioned nil
- "If non-nil include packages that don't have a version in `list-package'.")
+ "If non-nil, include packages that don't have a version in `list-packages'.")
(defvar package-list-unsigned nil
"If non-nil, mention in the list which packages were installed w/o signature.")
(defvar package--emacs-version-list (version-to-list emacs-version)
- "`emacs-version', as a list.")
+ "The value of variable `emacs-version' as a list.")
(defun package--incompatible-p (pkg &optional shallow)
"Return non-nil if PKG has no chance of being installable.
@@ -2782,7 +2829,7 @@ Can be toggled with \\<package-menu-mode-map> \\[package-menu-toggle-hiding].
Installed obsolete packages are always displayed.")
(defun package-menu-toggle-hiding ()
- "Toggle visibility of obsolete available packages."
+ "In Package Menu, toggle visibility of obsolete available packages."
(interactive)
(unless (derived-mode-p 'package-menu-mode)
(user-error "The current buffer is not a Package Menu"))
@@ -2840,7 +2887,7 @@ If the name of a package matches any of these regexps it is
omitted from the package menu. To toggle this, type \\[package-menu-toggle-hiding].
Values can be interactively added to this list by typing
-\\[package-menu-hide-package] on a package"
+\\[package-menu-hide-package] on a package."
:version "25.1"
:type '(repeat (regexp :tag "Hide packages with name matching")))
@@ -3100,7 +3147,7 @@ user-error if there is already a refresh running asynchronously."
(package-refresh-contents package-menu-async))
(defun package-menu-hide-package ()
- "Hide a package under point.
+ "Hide a package under point in Package Menu.
If optional arg BUTTON is non-nil, describe its associated package."
(interactive)
(declare (interactive-only "change `package-hidden-regexps' instead."))
@@ -3199,6 +3246,7 @@ The full list of keys can be viewed with \\[describe-mode]."
'package-menu-view-commentary 'package-menu-describe-package "24.1")
(defun package-menu-get-status ()
+ "Return status text of package at point in Package Menu."
(let* ((id (tabulated-list-get-id))
(entry (and id (assoc id tabulated-list-entries))))
(if entry
@@ -3224,6 +3272,10 @@ consideration."
(package-desc-version pkg-desc)))
(defun package-menu--find-upgrades ()
+ "In Package Menu, return an alist of packages that can be upgraded.
+The alist has the same form as `package-alist', namely a list
+of (PKG . DESCS), but where DESCS is the `package-desc' object
+corresponding to the newer version."
(let (installed available upgrades)
;; Build list of installed/available packages in this buffer.
(dolist (entry tabulated-list-entries)
@@ -3417,13 +3469,17 @@ Optional argument NOQUERY non-nil means do not ask the user to confirm."
(message "Operation %s finished" message-template))))))))
(defun package-menu--version-predicate (A B)
- (let ((vA (or (aref (cadr A) 1) '(0)))
- (vB (or (aref (cadr B) 1) '(0))))
+ "Predicate to sort \"*Packages*\" buffer by the version column.
+This is used for `tabulated-list-format' in `package-menu-mode'."
+ (let ((vA (or (version-to-list (aref (cadr A) 1)) '(0)))
+ (vB (or (version-to-list (aref (cadr B) 1)) '(0))))
(if (version-list-= vA vB)
(package-menu--name-predicate A B)
(version-list-< vA vB))))
(defun package-menu--status-predicate (A B)
+ "Predicate to sort \"*Packages*\" buffer by the status column.
+This is used for `tabulated-list-format' in `package-menu-mode'."
(let ((sA (aref (cadr A) 2))
(sB (aref (cadr B) 2)))
(cond ((string= sA sB)
@@ -3454,19 +3510,28 @@ Optional argument NOQUERY non-nil means do not ask the user to confirm."
(t (string< sA sB)))))
(defun package-menu--description-predicate (A B)
- (let ((dA (aref (cadr A) 3))
- (dB (aref (cadr B) 3)))
+ "Predicate to sort \"*Packages*\" buffer by the description column.
+This is used for `tabulated-list-format' in `package-menu-mode'."
+ (let ((dA (aref (cadr A) (if (cdr package-archives) 4 3)))
+ (dB (aref (cadr B) (if (cdr package-archives) 4 3))))
(if (string= dA dB)
(package-menu--name-predicate A B)
(string< dA dB))))
(defun package-menu--name-predicate (A B)
+ "Predicate to sort \"*Packages*\" buffer by the name column.
+This is used for `tabulated-list-format' in `package-menu-mode'."
(string< (symbol-name (package-desc-name (car A)))
(symbol-name (package-desc-name (car B)))))
(defun package-menu--archive-predicate (A B)
- (string< (or (package-desc-archive (car A)) "")
- (or (package-desc-archive (car B)) "")))
+ "Predicate to sort \"*Packages*\" buffer by the archive column.
+This is used for `tabulated-list-format' in `package-menu-mode'."
+ (let ((a (or (package-desc-archive (car A)) ""))
+ (b (or (package-desc-archive (car B)) "")))
+ (if (string= a b)
+ (package-menu--name-predicate A B)
+ (string< a b))))
(defun package-menu--populate-new-package-list ()
"Decide which packages are new in `package-archives-contents'.
@@ -3487,7 +3552,7 @@ Store this list in `package-menu--new-package-list'."
(defun package-menu--post-refresh ()
- "If there's a *Packages* buffer, revert it and check for new packages and upgrades.
+ "Revert \"*Packages*\" buffer and check for new packages and upgrades.
Do nothing if there's no *Packages* buffer.
This function is called after `package-refresh-contents' and it
diff --git a/lisp/emacs-lisp/re-builder.el b/lisp/emacs-lisp/re-builder.el
index 961d26a7212..1054f1453bc 100644
--- a/lisp/emacs-lisp/re-builder.el
+++ b/lisp/emacs-lisp/re-builder.el
@@ -816,13 +816,12 @@ If SUBEXP is non-nil mark only the corresponding sub-expressions."
(require 'rx)
(defconst reb-rx-font-lock-keywords
- (let ((constituents (mapcar (lambda (rec)
- (symbol-name (car rec)))
- rx-constituents))
- (syntax (mapcar (lambda (rec) (symbol-name (car rec))) rx-syntax))
+ (let ((constituents (mapcar #'symbol-name rx--builtin-forms))
+ (syntax (mapcar (lambda (rec) (symbol-name (car rec)))
+ rx--syntax-codes))
(categories (mapcar (lambda (rec)
(symbol-name (car rec)))
- rx-categories)))
+ rx--categories)))
`(
(,(concat "(" (regexp-opt (list "rx-to-string") t) "[[:space:]]")
(1 font-lock-function-name-face))
diff --git a/lisp/emacs-lisp/rmc.el b/lisp/emacs-lisp/rmc.el
index 47f3b8dc9cf..13cd1c0f42a 100644
--- a/lisp/emacs-lisp/rmc.el
+++ b/lisp/emacs-lisp/rmc.el
@@ -106,7 +106,7 @@ Usage example:
(setq tchar
(if (and (display-popup-menus-p)
last-input-event ; not during startup
- (listp last-nonmenu-event)
+ (consp last-nonmenu-event)
use-dialog-box)
(x-popup-dialog
t
diff --git a/lisp/emacs-lisp/rx.el b/lisp/emacs-lisp/rx.el
index 249529e54e3..45fec796cc3 100644
--- a/lisp/emacs-lisp/rx.el
+++ b/lisp/emacs-lisp/rx.el
@@ -1,11 +1,7 @@
-;;; rx.el --- sexp notation for regular expressions -*- lexical-binding: t -*-
+;;; rx.el --- S-exp notation for regexps --*- lexical-binding: t -*-
;; Copyright (C) 2001-2019 Free Software Foundation, Inc.
-;; Author: Gerd Moellmann <gerd@gnu.org>
-;; Maintainer: emacs-devel@gnu.org
-;; Keywords: strings, regexps, extensions
-
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
@@ -23,937 +19,964 @@
;;; Commentary:
-;; This is another implementation of sexp-form regular expressions.
-;; It was unfortunately written without being aware of the Sregex
-;; package coming with Emacs, but as things stand, Rx completely
-;; covers all regexp features, which Sregex doesn't, doesn't suffer
-;; from the bugs mentioned in the commentary section of Sregex, and
-;; uses a nicer syntax (IMHO, of course :-).
-
-;; This significantly extended version of the original, is almost
-;; compatible with Sregex. The only incompatibility I (fx) know of is
-;; that the `repeat' form can't have multiple regexp args.
-
-;; Now alternative forms are provided for a degree of compatibility
-;; with Olin Shivers' attempted definitive SRE notation. SRE forms
-;; not catered for include: dsm, uncase, w/case, w/nocase, ,@<exp>,
-;; ,<exp>, (word ...), word+, posix-string, and character class forms.
-;; Some forms are inconsistent with SRE, either for historical reasons
-;; or because of the implementation -- simple translation into Emacs
-;; regexp strings. These include: any, word. Also, case-sensitivity
-;; and greediness are controlled by variables external to the regexp,
-;; and you need to feed the forms to the `posix-' functions to get
-;; SRE's POSIX semantics. There are probably more difficulties.
-
-;; Rx translates a sexp notation for regular expressions into the
-;; usual string notation. The translation can be done at compile-time
-;; by using the `rx' macro. The `regexp' and `literal' forms accept
-;; non-constant expressions, in which case `rx' will translate to a
-;; `concat' expression. Translation can be done fully at run time by
-;; calling function `rx-to-string'. See the documentation of `rx' for
-;; a complete description of the sexp notation.
-;;
-;; Some examples of string regexps and their sexp counterparts:
-;;
-;; "^[a-z]*"
-;; (rx line-start (0+ (in "a-z")))
-;;
-;; "\n[^ \t]"
-;; (rx ?\n (not (in " \t")))
-;;
-;; "\\*\\*\\* EOOH \\*\\*\\*\n"
-;; (rx "*** EOOH ***\n")
-;;
-;; "\\<\\(catch\\|finally\\)\\>[^_]"
-;; (rx word-start (submatch (or "catch" "finally")) word-end
-;; (not (in ?_)))
-;;
-;; "[ \t\n]*:\\($\\|[^:]+\\)"
-;; (rx (* (in " \t\n")) ":"
-;; (submatch (or line-end (+ (not (in ?:))))))
+;; This facility allows writing regexps in a sexp-based language
+;; instead of strings. Regexps in the `rx' notation are easier to
+;; read, write and maintain; they can be indented and commented in a
+;; natural way, and are easily composed by program code.
+;; The translation to string regexp is done by a macro and does not
+;; incur any extra processing during run time. Example:
;;
-;; "^content-transfer-encoding:\\(?:\n?[\t ]\\)*quoted-printable\\(?:\n?[\t ]\\)*"
-;; (rx line-start
-;; "content-transfer-encoding:"
-;; (* (? ?\n) (in " \t"))
-;; "quoted-printable"
-;; (* (? ?\n) (in " \t")))
+;; (rx bos (or (not (any "^"))
+;; (seq "^" (or " *" "["))))
;;
-;; (concat "^\\(?:" something-else "\\)")
-;; (rx line-start (regexp something-else))
-;;
-;; (regexp-opt '(STRING1 STRING2 ...))
-;; (rx (or STRING1 STRING2 ...)), or in other words, `or' automatically
-;; calls `regexp-opt' as needed.
-;;
-;; "^;;\\s-*\n\\|^\n"
-;; (rx (or (seq line-start ";;" (0+ space) ?\n)
-;; (seq line-start ?\n)))
-;;
-;; "\\$[I]d: [^ ]+ \\([^ ]+\\) "
-;; (rx "$Id: "
-;; (1+ (not (in " ")))
-;; " "
-;; (submatch (1+ (not (in " "))))
-;; " ")
-;;
-;; "\\\\\\\\\\[\\w+"
-;; (rx "\\\\[" (1+ word))
-;;
-;; etc.
-
-;;; History:
+;; => "\\`\\(?:[^^]\\|\\^\\(?: \\*\\|\\[\\)\\)"
;;
+;; The notation is much influenced by and retains some compatibility with
+;; Olin Shivers's SRE, with concessions to Emacs regexp peculiarities,
+;; and the older Emacs package Sregex.
;;; Code:
-(require 'cl-lib)
-(require 'cl-extra)
-
-;; FIXME: support macros.
-
-(defvar rx-constituents ;Not `const' because some modes extend it.
- '((and . (rx-and 0 nil))
- (seq . and) ; SRE
- (: . and) ; SRE
- (sequence . and) ; sregex
- (or . (rx-or 0 nil))
- (| . or) ; SRE
- (not-newline . ".")
- (nonl . not-newline) ; SRE
- (anything . (rx-anything 0 nil))
- (any . (rx-any 1 nil rx-check-any)) ; inconsistent with SRE
- (any . ".") ; sregex
- (in . any)
- (char . any) ; sregex
- (not-char . (rx-not-char 1 nil rx-check-any)) ; sregex
- (not . (rx-not 1 1 rx-check-not))
- (repeat . (rx-repeat 2 nil))
- (= . (rx-= 2 nil)) ; SRE
- (>= . (rx->= 2 nil)) ; SRE
- (** . (rx-** 2 nil)) ; SRE
- (submatch . (rx-submatch 1 nil)) ; SRE
- (group . submatch) ; sregex
- (submatch-n . (rx-submatch-n 2 nil))
- (group-n . submatch-n)
- (zero-or-more . (rx-kleene 1 nil))
- (one-or-more . (rx-kleene 1 nil))
- (zero-or-one . (rx-kleene 1 nil))
- (\? . zero-or-one) ; SRE
- (\?? . zero-or-one)
- (* . zero-or-more) ; SRE
- (*? . zero-or-more)
- (0+ . zero-or-more)
- (+ . one-or-more) ; SRE
- (+? . one-or-more)
- (1+ . one-or-more)
- (optional . zero-or-one)
- (opt . zero-or-one) ; sregex
- (minimal-match . (rx-greedy 1 1))
- (maximal-match . (rx-greedy 1 1))
- (backref . (rx-backref 1 1 rx-check-backref))
- (line-start . "^")
- (bol . line-start) ; SRE
- (line-end . "$")
- (eol . line-end) ; SRE
- (string-start . "\\`")
- (bos . string-start) ; SRE
- (bot . string-start) ; sregex
- (string-end . "\\'")
- (eos . string-end) ; SRE
- (eot . string-end) ; sregex
- (buffer-start . "\\`")
- (buffer-end . "\\'")
- (point . "\\=")
- (word-start . "\\<")
- (bow . word-start) ; SRE
- (word-end . "\\>")
- (eow . word-end) ; SRE
- (word-boundary . "\\b")
- (not-word-boundary . "\\B") ; sregex
- (symbol-start . "\\_<")
- (symbol-end . "\\_>")
- (syntax . (rx-syntax 1 1))
- (not-syntax . (rx-not-syntax 1 1)) ; sregex
- (category . (rx-category 1 1 rx-check-category))
- (eval . (rx-eval 1 1))
- (literal . (rx-literal 1 1 stringp))
- (regexp . (rx-regexp 1 1 stringp))
- (regex . regexp) ; sregex
- (digit . "[[:digit:]]")
- (numeric . digit) ; SRE
- (num . digit) ; SRE
- (control . "[[:cntrl:]]") ; SRE
- (cntrl . control) ; SRE
- (hex-digit . "[[:xdigit:]]") ; SRE
- (hex . hex-digit) ; SRE
- (xdigit . hex-digit) ; SRE
- (blank . "[[:blank:]]") ; SRE
- (graphic . "[[:graph:]]") ; SRE
- (graph . graphic) ; SRE
- (printing . "[[:print:]]") ; SRE
- (print . printing) ; SRE
- (alphanumeric . "[[:alnum:]]") ; SRE
- (alnum . alphanumeric) ; SRE
- (letter . "[[:alpha:]]")
- (alphabetic . letter) ; SRE
- (alpha . letter) ; SRE
- (ascii . "[[:ascii:]]") ; SRE
- (nonascii . "[[:nonascii:]]")
- (lower . "[[:lower:]]") ; SRE
- (lower-case . lower) ; SRE
- (punctuation . "[[:punct:]]") ; SRE
- (punct . punctuation) ; SRE
- (space . "[[:space:]]") ; SRE
- (whitespace . space) ; SRE
- (white . space) ; SRE
- (upper . "[[:upper:]]") ; SRE
- (upper-case . upper) ; SRE
- (word . "[[:word:]]") ; inconsistent with SRE
- (wordchar . word) ; sregex
- (not-wordchar . "\\W"))
- "Alist of sexp form regexp constituents.
-Each element of the alist has the form (SYMBOL . DEFN).
-SYMBOL is a valid constituent of sexp regular expressions.
-If DEFN is a string, SYMBOL is translated into DEFN.
-If DEFN is a symbol, use the definition of DEFN, recursively.
-Otherwise, DEFN must be a list (FUNCTION MIN-ARGS MAX-ARGS PREDICATE).
-FUNCTION is used to produce code for SYMBOL. MIN-ARGS and MAX-ARGS
-are the minimum and maximum number of arguments the function-form
-sexp constituent SYMBOL may have in sexp regular expressions.
-MAX-ARGS nil means no limit. PREDICATE, if specified, means that
-all arguments must satisfy PREDICATE.")
-
-
-(defconst rx-syntax
- '((whitespace . ?-)
- (punctuation . ?.)
- (word . ?w)
- (symbol . ?_)
- (open-parenthesis . ?\()
- (close-parenthesis . ?\))
- (expression-prefix . ?\')
- (string-quote . ?\")
- (paired-delimiter . ?$)
- (escape . ?\\)
- (character-quote . ?/)
- (comment-start . ?<)
- (comment-end . ?>)
- (string-delimiter . ?|)
- (comment-delimiter . ?!))
- "Alist mapping Rx syntax symbols to syntax characters.
-Each entry has the form (SYMBOL . CHAR), where SYMBOL is a valid
-symbol in `(syntax SYMBOL)', and CHAR is the syntax character
-corresponding to SYMBOL, as it would be used with \\s or \\S in
-regular expressions.")
-
-
-(defconst rx-categories
- '((space-for-indent . ?\s)
- (base . ?.)
- (consonant . ?0)
- (base-vowel . ?1)
- (upper-diacritical-mark . ?2)
- (lower-diacritical-mark . ?3)
- (tone-mark . ?4)
- (symbol . ?5)
- (digit . ?6)
- (vowel-modifying-diacritical-mark . ?7)
- (vowel-sign . ?8)
- (semivowel-lower . ?9)
- (not-at-end-of-line . ?<)
- (not-at-beginning-of-line . ?>)
- (alpha-numeric-two-byte . ?A)
- (chinese-two-byte . ?C)
- (chinse-two-byte . ?C) ;; A typo in Emacs 21.1-24.3.
- (greek-two-byte . ?G)
- (japanese-hiragana-two-byte . ?H)
- (indian-two-byte . ?I)
- (japanese-katakana-two-byte . ?K)
- (strong-left-to-right . ?L)
- (korean-hangul-two-byte . ?N)
- (strong-right-to-left . ?R)
- (cyrillic-two-byte . ?Y)
- (combining-diacritic . ?^)
- (ascii . ?a)
- (arabic . ?b)
- (chinese . ?c)
- (ethiopic . ?e)
- (greek . ?g)
- (korean . ?h)
- (indian . ?i)
- (japanese . ?j)
- (japanese-katakana . ?k)
- (latin . ?l)
- (lao . ?o)
- (tibetan . ?q)
- (japanese-roman . ?r)
- (thai . ?t)
- (vietnamese . ?v)
- (hebrew . ?w)
- (cyrillic . ?y)
- (can-break . ?|))
- "Alist mapping symbols to category characters.
-Each entry has the form (SYMBOL . CHAR), where SYMBOL is a valid
-symbol in `(category SYMBOL)', and CHAR is the category character
-corresponding to SYMBOL, as it would be used with `\\c' or `\\C' in
-regular expression strings.")
-
-
-(defvar rx-greedy-flag t
- "Non-nil means produce greedy regular expressions for `zero-or-one',
-`zero-or-more', and `one-or-more'. Dynamically bound.")
-
-(defvar rx--compile-to-lisp nil
- "Nil means return a regexp as a string.
-Non-nil means we may return a lisp form which produces a
-string (used for `rx' macro).")
-
-(defun rx-info (op head)
- "Return parsing/code generation info for OP.
-If OP is the space character ASCII 32, return info for the symbol `?'.
-If OP is the character `?', return info for the symbol `??'.
-See also `rx-constituents'.
-If HEAD is non-nil, then OP is the head of a sexp, otherwise it's
-a standalone symbol."
- (cond ((eq op ? ) (setq op '\?))
- ((eq op ??) (setq op '\??)))
- (let (old-op)
- (while (and (not (null op)) (symbolp op))
- (setq old-op op)
- (setq op (cdr (assq op rx-constituents)))
- (when (if head (stringp op) (consp op))
- ;; We found something but of the wrong kind. Let's look for an
- ;; alternate definition for the other case.
- (let ((new-op
- (cdr (assq old-op (cdr (memq (assq old-op rx-constituents)
- rx-constituents))))))
- (if (and new-op (not (if head (stringp new-op) (consp new-op))))
- (setq op new-op))))))
- op)
-
-
-(defun rx-check (form)
- "Check FORM according to its car's parsing info."
- (unless (listp form)
- (error "rx `%s' needs argument(s)" form))
- (let* ((rx (rx-info (car form) 'head))
- (nargs (1- (length form)))
- (min-args (nth 1 rx))
- (max-args (nth 2 rx))
- (type-pred (nth 3 rx)))
- (when (and (not (null min-args))
- (< nargs min-args))
- (error "rx form `%s' requires at least %d args"
- (car form) min-args))
- (when (and (not (null max-args))
- (> nargs max-args))
- (error "rx form `%s' accepts at most %d args"
- (car form) max-args))
- (when type-pred
- (dolist (sub-form (cdr form))
- (unless (funcall type-pred sub-form)
- (error "rx form `%s' requires args satisfying `%s'"
- (car form) type-pred))))))
-
-
-(defun rx-group-if (regexp group)
- "Put shy groups around REGEXP if seemingly necessary when GROUP
-is non-nil."
+;; The `rx--translate...' functions below return (REGEXP . PRECEDENCE),
+;; where REGEXP is a list of string expressions that will be
+;; concatenated into a regexp, and PRECEDENCE is one of
+;;
+;; t -- can be used as argument to postfix operators (eg. "a")
+;; seq -- can be concatenated in sequence with other seq or higher (eg. "ab")
+;; lseq -- can be concatenated to the left of rseq or higher (eg. "^a")
+;; rseq -- can be concatenated to the right of lseq or higher (eg. "a$")
+;; nil -- can only be used in alternatives (eg. "a\\|b")
+;;
+;; They form a lattice:
+;;
+;; t highest precedence
+;; |
+;; seq
+;; / \
+;; lseq rseq
+;; \ /
+;; nil lowest precedence
+
+
+(defconst rx--char-classes
+ '((digit . digit)
+ (numeric . digit)
+ (num . digit)
+ (control . cntrl)
+ (cntrl . cntrl)
+ (hex-digit . xdigit)
+ (hex . xdigit)
+ (xdigit . xdigit)
+ (blank . blank)
+ (graphic . graph)
+ (graph . graph)
+ (printing . print)
+ (print . print)
+ (alphanumeric . alnum)
+ (alnum . alnum)
+ (letter . alpha)
+ (alphabetic . alpha)
+ (alpha . alpha)
+ (ascii . ascii)
+ (nonascii . nonascii)
+ (lower . lower)
+ (lower-case . lower)
+ (punctuation . punct)
+ (punct . punct)
+ (space . space)
+ (whitespace . space)
+ (white . space)
+ (upper . upper)
+ (upper-case . upper)
+ (word . word)
+ (wordchar . word)
+ (unibyte . unibyte)
+ (multibyte . multibyte))
+ "Alist mapping rx symbols to character classes.
+Most of the names are from SRE.")
+
+(defvar rx-constituents nil
+ "Alist of old-style rx extensions, for compatibility.
+For new code, use `rx-define', `rx-let' or `rx-let-eval'.
+
+Each element is (SYMBOL . DEF).
+
+If DEF is a symbol, then SYMBOL is an alias of DEF.
+
+If DEF is a string, then SYMBOL is a plain rx symbol defined as the
+ regexp string DEF.
+
+If DEF is a list on the form (FUN MIN-ARGS MAX-ARGS PRED), then
+ SYMBOL is an rx form with at least MIN-ARGS and at most
+ MAX-ARGS arguments. If MAX-ARGS is nil, then there is no upper limit.
+ FUN is a function taking the entire rx form as single argument
+ and returning the translated regexp string.
+ If PRED is non-nil, it is a predicate that all actual arguments must
+ satisfy.")
+
+(defvar rx--local-definitions nil
+ "Alist of dynamic local rx definitions.
+Each entry is:
+ (NAME DEF) -- NAME is an rx symbol defined as the rx form DEF.
+ (NAME ARGS DEF) -- NAME is an rx form with arglist ARGS, defined
+ as the rx form DEF (which can contain members of ARGS).")
+
+(defsubst rx--lookup-def (name)
+ (or (cdr (assq name rx--local-definitions))
+ (get name 'rx-definition)))
+
+;; TODO: Additions to consider:
+;; - A better name for `anything', like `any-char' or `anychar'.
+;; - A name for (or), maybe `unmatchable'.
+;; - A construct like `or' but without the match order guarantee,
+;; maybe `unordered-or'. Useful for composition or generation of
+;; alternatives; permits more effective use of regexp-opt.
+
+(defun rx--translate-symbol (sym)
+ "Translate an rx symbol. Return (REGEXP . PRECEDENCE)."
+ (pcase sym
+ ;; Use `list' instead of a quoted list to wrap the strings here,
+ ;; since the return value may be mutated.
+ ((or 'nonl 'not-newline 'any) (cons (list ".") t))
+ ('anything (rx--translate-form '(or nonl "\n")))
+ ((or 'bol 'line-start) (cons (list "^") 'lseq))
+ ((or 'eol 'line-end) (cons (list "$") 'rseq))
+ ((or 'bos 'string-start 'bot 'buffer-start) (cons (list "\\`") t))
+ ((or 'eos 'string-end 'eot 'buffer-end) (cons (list "\\'") t))
+ ('point (cons (list "\\=") t))
+ ((or 'bow 'word-start) (cons (list "\\<") t))
+ ((or 'eow 'word-end) (cons (list "\\>") t))
+ ('word-boundary (cons (list "\\b") t))
+ ('not-word-boundary (cons (list "\\B") t))
+ ('symbol-start (cons (list "\\_<") t))
+ ('symbol-end (cons (list "\\_>") t))
+ ('not-wordchar (cons (list "\\W") t))
+ (_
+ (cond
+ ((let ((class (cdr (assq sym rx--char-classes))))
+ (and class (cons (list (concat "[[:" (symbol-name class) ":]]")) t))))
+
+ ((let ((definition (rx--lookup-def sym)))
+ (and definition
+ (if (cdr definition)
+ (error "Not an `rx' symbol definition: %s" sym)
+ (rx--translate (nth 0 definition))))))
+
+ ;; For compatibility with old rx.
+ ((let ((entry (assq sym rx-constituents)))
+ (and (progn
+ (while (and entry (not (stringp (cdr entry))))
+ (setq entry
+ (if (symbolp (cdr entry))
+ ;; Alias for another entry.
+ (assq (cdr entry) rx-constituents)
+ ;; Wrong type, try further down the list.
+ (assq (car entry)
+ (cdr (memq entry rx-constituents))))))
+ entry)
+ (cons (list (cdr entry)) nil))))
+ (t (error "Unknown rx symbol `%s'" sym))))))
+
+(defun rx--enclose (left-str rexp right-str)
+ "Bracket REXP by LEFT-STR and RIGHT-STR."
+ (append (list left-str) rexp (list right-str)))
+
+(defun rx--bracket (rexp)
+ (rx--enclose "\\(?:" rexp "\\)"))
+
+(defun rx--sequence (left right)
+ "Return the sequence (concatenation) of two translated items,
+each on the form (REGEXP . PRECEDENCE), returning (REGEXP . PRECEDENCE)."
+ ;; Concatenation rules:
+ ;; seq ++ seq -> seq
+ ;; lseq ++ seq -> lseq
+ ;; seq ++ rseq -> rseq
+ ;; lseq ++ rseq -> nil
+ (cond ((not (car left)) right)
+ ((not (car right)) left)
+ (t
+ (let ((l (if (memq (cdr left) '(nil rseq))
+ (cons (rx--bracket (car left)) t)
+ left))
+ (r (if (memq (cdr right) '(nil lseq))
+ (cons (rx--bracket (car right)) t)
+ right)))
+ (cons (append (car l) (car r))
+ (if (eq (cdr l) 'lseq)
+ (if (eq (cdr r) 'rseq)
+ nil ; lseq ++ rseq
+ 'lseq) ; lseq ++ seq
+ (if (eq (cdr r) 'rseq)
+ 'rseq ; seq ++ rseq
+ 'seq))))))) ; seq ++ seq
+
+(defun rx--translate-seq (body)
+ "Translate a sequence of one or more rx items. Return (REGEXP . PRECEDENCE)."
+ (if body
+ (let* ((items (mapcar #'rx--translate body))
+ (result (car items)))
+ (dolist (item (cdr items))
+ (setq result (rx--sequence result item)))
+ result)
+ (cons nil 'seq)))
+
+(defun rx--empty ()
+ "Regexp that never matches anything."
+ (cons (list regexp-unmatchable) 'seq))
+
+;; `cl-every' replacement to avoid bootstrapping problems.
+(defun rx--every (pred list)
+ "Whether PRED is true for every element of LIST."
+ (while (and list (funcall pred (car list)))
+ (setq list (cdr list)))
+ (null list))
+
+(defun rx--translate-or (body)
+ "Translate an or-pattern of one of more rx items.
+Return (REGEXP . PRECEDENCE)."
+ ;; FIXME: Possible improvements:
+ ;;
+ ;; - Turn single characters to strings: (or ?a ?b) -> (or "a" "b"),
+ ;; so that they can be candidates for regexp-opt.
+ ;;
+ ;; - Translate compile-time strings (`eval' forms), again for regexp-opt.
+ ;;
+ ;; - Flatten sub-patterns first: (or (or A B) (or C D)) -> (or A B C D)
+ ;; in order to improve effectiveness of regexp-opt.
+ ;; This would also help composability.
+ ;;
+ ;; - Use associativity to run regexp-opt on contiguous subsets of arguments
+ ;; if not all of them are strings. Example:
+ ;; (or (+ digit) "CHARLIE" "CHAN" (+ blank))
+ ;; -> (or (+ digit) (or "CHARLIE" "CHAN") (+ blank))
+ ;;
+ ;; - Fuse patterns into a single character alternative if they fit.
+ ;; regexp-opt will do that if all are strings, but we want to do that for:
+ ;; * symbols that expand to classes: space, alpha, ...
+ ;; * character alternatives: (any ...)
+ ;; * (syntax S), for some S (whitespace, word)
+ ;; so that (or "@" "%" digit (any "A-Z" space) (syntax word))
+ ;; -> (any "@" "%" digit "A-Z" space word)
+ ;; -> "[A-Z@%[:digit:][:space:][:word:]]"
+ ;;
+ ;; Problem: If a subpattern is carefully written to to be
+ ;; optimisable by regexp-opt, how do we prevent the transforms
+ ;; above from destroying that property?
+ ;; Example: (or "a" (or "abc" "abd" "abe"))
(cond
- ;; for some repetition
- ((eq group '*) (if (rx-atomic-p regexp) (setq group nil)))
- ;; for concatenation
- ((eq group ':)
- (if (rx-atomic-p
- (if (and (stringp regexp)
- (string-match
- "\\(?:[?*+]\\??\\|\\\\{[0-9]*,?[0-9]*\\\\}\\)\\'" regexp))
- (substring regexp 0 (match-beginning 0))
- regexp))
- (setq group nil)))
- ;; for OR
- ((eq group '|) (setq group nil))
- ;; do anyway
- ((eq group t))
- ((rx-atomic-p regexp t) (setq group nil)))
- (cond ((and group (stringp regexp))
- (concat "\\(?:" regexp "\\)"))
- (group `("\\(?:" ,@regexp "\\)"))
- (t regexp)))
-
-
-(defvar rx-parent)
-;; dynamically bound in some functions.
-
-
-(defun rx-and (form)
- "Parse and produce code from FORM.
-FORM is of the form `(and FORM1 ...)'."
- (rx-check form)
- (rx-group-if
- (rx--subforms (cdr form) ':)
- (and (memq rx-parent '(* t)) rx-parent)))
-
-
-(defun rx-or (form)
- "Parse and produce code from FORM, which is `(or FORM1 ...)'."
- (rx-check form)
- (rx-group-if
- (cond
- ((null (cdr form)) regexp-unmatchable)
- ((cl-every #'stringp (cdr form))
- (regexp-opt (cdr form) nil t))
- (t (rx--subforms (cdr form) '| "\\|")))
- (and (memq rx-parent '(: * t)) rx-parent)))
-
-
-(defun rx-anything (form)
- "Match any character."
- (if (consp form)
- (error "rx `anything' syntax error: %s" form))
- (rx-or (list 'or 'not-newline ?\n)))
-
-
-(defun rx-any-delete-from-range (char ranges)
- "Delete by side effect character CHAR from RANGES.
-Only both edges of each range is checked."
- (let (m)
- (cond
- ((memq char ranges) (setq ranges (delq char ranges)))
- ((setq m (assq char ranges))
- (if (eq (1+ char) (cdr m))
- (setcar (memq m ranges) (1+ char))
- (setcar m (1+ char))))
- ((setq m (rassq char ranges))
- (if (eq (1- char) (car m))
- (setcar (memq m ranges) (1- char))
- (setcdr m (1- char)))))
- ranges))
-
-
-(defun rx-any-condense-range (args)
- "Condense by side effect ARGS as range for Rx `any'."
- (let (str
- l)
- ;; set STR list of all strings
- ;; set L list of all ranges
- (mapc (lambda (e) (cond ((stringp e) (push e str))
- ((numberp e) (push (cons e e) l))
- ;; Ranges between ASCII and raw bytes are split,
- ;; to prevent accidental inclusion of Unicode
- ;; characters later on.
- ((and (<= (car e) #x7f)
- (>= (cdr e) #x3fff80))
- (push (cons (car e) #x7f) l)
- (push (cons #x3fff80 (cdr e)) l))
- (t (push e l))))
- args)
- ;; condense overlapped ranges in L
- (let ((tail (setq l (sort l #'car-less-than-car)))
- d)
- (while (setq d (cdr tail))
- (if (>= (cdar tail) (1- (caar d)))
- (progn
- (setcdr (car tail) (max (cdar tail) (cdar d)))
- (setcdr tail (cdr d)))
- (setq tail d))))
- ;; Separate small ranges to single number, and delete dups.
- (nconc
- (apply #'nconc
- (mapcar (lambda (e)
- (cond
- ((= (car e) (cdr e)) (list (car e)))
- ((= (1+ (car e)) (cdr e)) (list (car e) (cdr e)))
- ((list e))))
- l))
- (delete-dups str))))
-
-
-(defun rx-check-any-string (str)
- "Turn the `any' argument string STR into a list of characters.
-The original order is not preserved. Ranges, \"A-Z\", become pairs, (?A . ?Z)."
+ ((null body) ; No items: a never-matching regexp.
+ (rx--empty))
+ ((null (cdr body)) ; Single item.
+ (rx--translate (car body)))
+ ((rx--every #'stringp body) ; All strings.
+ (cons (list (regexp-opt body nil t))
+ t))
+ (t
+ (cons (append (car (rx--translate (car body)))
+ (mapcan (lambda (item)
+ (cons "\\|" (car (rx--translate item))))
+ (cdr body)))
+ nil))))
+
+(defun rx--string-to-intervals (str)
+ "Decode STR as intervals: A-Z becomes (?A . ?Z), and the single
+character X becomes (?X . ?X). Return the intervals in a list."
+ ;; We could just do string-to-multibyte on the string and work with
+ ;; that instead of this `decode-char' workaround.
(let ((decode-char
- ;; Make sure raw bytes are decoded as such, to avoid confusion with
- ;; U+0080..U+00FF.
(if (multibyte-string-p str)
#'identity
- (lambda (c) (if (<= #x80 c #xff)
- (+ c #x3fff00)
- c))))
+ #'unibyte-char-to-multibyte))
(len (length str))
(i 0)
- (ret nil))
- (if (= 0 len)
- (error "String arg for Rx `any' must not be empty"))
+ (intervals nil))
(while (< i len)
(cond ((and (< i (- len 2))
- (= (aref str (+ i 1)) ?-))
+ (= (aref str (1+ i)) ?-))
;; Range.
(let ((start (funcall decode-char (aref str i)))
(end (funcall decode-char (aref str (+ i 2)))))
- (cond ((< start end) (push (cons start end) ret))
- ((= start end) (push start ret))
+ (cond ((and (<= start #x7f) (>= end #x3fff80))
+ ;; Ranges between ASCII and raw bytes are split to
+ ;; avoid having them absorb Unicode characters
+ ;; caught in-between.
+ (push (cons start #x7f) intervals)
+ (push (cons #x3fff80 end) intervals))
+ ((<= start end)
+ (push (cons start end) intervals))
(t
- (error "Rx character range `%c-%c' is reversed"
- start end)))
+ (error "Invalid rx `any' range: %s"
+ (substring str i 3))))
(setq i (+ i 3))))
(t
;; Single character.
- (push (funcall decode-char (aref str i)) ret)
+ (let ((char (funcall decode-char (aref str i))))
+ (push (cons char char) intervals))
(setq i (+ i 1)))))
- ret))
-
-
-(defun rx-check-any (arg)
- "Check arg ARG for Rx `any'."
- (cond
- ((integerp arg) (list arg))
- ((symbolp arg)
- (let ((translation (condition-case nil
- (rx-form arg)
- (error nil))))
- (if (or (null translation)
- (null (string-match "\\`\\[\\[:[-a-z]+:\\]\\]\\'" translation)))
- (error "Invalid char class `%s' in Rx `any'" arg))
- (list (substring translation 1 -1)))) ; strip outer brackets
- ((and (characterp (car-safe arg)) (characterp (cdr-safe arg)))
- (unless (<= (car arg) (cdr arg))
- (error "Rx character range `%c-%c' is reversed"
- (car arg) (cdr arg)))
- (list arg))
- ((stringp arg) (rx-check-any-string arg))
- ((error
- "rx `any' requires string, character, char pair or char class args"))))
-
-
-(defun rx-any (form)
- "Parse and produce code from FORM, which is `(any ARG ...)'.
-ARG is optional."
- (rx-check form)
- (let* ((args (rx-any-condense-range
- (apply
- #'nconc
- (mapcar #'rx-check-any (cdr form)))))
- m
- s)
- (cond
- ;; single close bracket
- ;; => "[]...-]" or "[]...--.]"
- ((memq ?\] args)
- ;; set ] at the beginning
- (setq args (cons ?\] (delq ?\] args)))
- ;; set - at the end
- (if (or (memq ?- args) (assq ?- args))
- (setq args (nconc (rx-any-delete-from-range ?- args)
- (list ?-)))))
- ;; close bracket starts a range
- ;; => "[]-....-]" or "[]-.--....]"
- ((setq m (assq ?\] args))
- ;; bring it to the beginning
- (setq args (cons m (delq m args)))
- (cond ((memq ?- args)
- ;; to the end
- (setq args (nconc (delq ?- args) (list ?-))))
- ((setq m (assq ?- args))
- ;; next to the bracket's range, make the second range
- (setcdr args (cons m (delq m (cdr args)))))))
- ;; bracket in the end range
- ;; => "[]...-]"
- ((setq m (rassq ?\] args))
- ;; set ] at the beginning
- (setq args (cons ?\] (rx-any-delete-from-range ?\] args)))
- ;; set - at the end
- (if (or (memq ?- args) (assq ?- args))
- (setq args (nconc (rx-any-delete-from-range ?- args)
- (list ?-)))))
- ;; {no close bracket appears}
- ;;
- ;; bring single bar to the beginning
- ((memq ?- args)
- (setq args (cons ?- (delq ?- args))))
- ;; bar start a range, bring it to the beginning
- ((setq m (assq ?- args))
- (setq args (cons m (delq m args))))
- ;;
- ;; hat at the beginning?
- ((or (eq (car args) ?^) (eq (car-safe (car args)) ?^))
- (setq args (if (cdr args)
- `(,(cadr args) ,(car args) ,@(cddr args))
- (nconc (rx-any-delete-from-range ?^ args)
- (list ?^))))))
- ;; some 1-char?
- (if (and (null (cdr args)) (numberp (car args))
- (or (= 1 (length
- (setq s (regexp-quote (string (car args))))))
- (and (equal (car args) ?^) ;; unnecessary predicate?
- (null (eq rx-parent '!)))))
- s
- (concat "["
- (mapconcat
- (lambda (e) (cond
- ((numberp e) (string e))
- ((consp e)
- (if (and (= (1+ (car e)) (cdr e))
- ;; rx-any-condense-range should
- ;; prevent this case from happening.
- (null (memq (car e) '(?\] ?-)))
- (null (memq (cdr e) '(?\] ?-))))
- (string (car e) (cdr e))
- (string (car e) ?- (cdr e))))
- (e)))
- args
- nil)
- "]"))))
-
-
-(defun rx-check-not (arg)
- "Check arg ARG for Rx `not'."
- (unless (or (and (symbolp arg)
- (string-match "\\`\\[\\[:[-a-z]+:\\]\\]\\'"
- (condition-case nil
- (rx-form arg)
- (error ""))))
- (eq arg 'word-boundary)
- (and (consp arg)
- (memq (car arg) '(not any in syntax category))))
- (error "rx `not' syntax error: %s" arg))
- t)
-
-
-(defun rx-not (form)
- "Parse and produce code from FORM. FORM is `(not ...)'."
- (rx-check form)
- (let ((result (rx-form (cadr form) '!))
- case-fold-search)
- (cond ((string-match "\\`\\[\\^" result)
- (cond
- ((equal result "[^]") "[^^]")
- ((and (= (length result) 4) (null (eq rx-parent '!)))
- (regexp-quote (substring result 2 3)))
- ((concat "[" (substring result 2)))))
- ((eq ?\[ (aref result 0))
- (concat "[^" (substring result 1)))
- ((string-match "\\`\\\\[scbw]" result)
- (concat (upcase (substring result 0 2))
- (substring result 2)))
- ((string-match "\\`\\\\[SCBW]" result)
- (concat (downcase (substring result 0 2))
- (substring result 2)))
- (t
- (concat "[^" result "]")))))
-
-
-(defun rx-not-char (form)
- "Parse and produce code from FORM. FORM is `(not-char ...)'."
- (rx-check form)
- (rx-not `(not (in ,@(cdr form)))))
-
-
-(defun rx-not-syntax (form)
- "Parse and produce code from FORM. FORM is `(not-syntax SYNTAX)'."
- (rx-check form)
- (rx-not `(not (syntax ,@(cdr form)))))
-
-
-(defun rx-trans-forms (form &optional skip)
- "If FORM's length is greater than two, transform it to length two.
-A form (HEAD REST ...) becomes (HEAD (and REST ...)).
-If SKIP is non-nil, allow that number of items after the head, i.e.
-`(= N REST ...)' becomes `(= N (and REST ...))' if SKIP is 1."
- (unless skip (setq skip 0))
- (let ((tail (nthcdr (1+ skip) form)))
- (if (= (length tail) 1)
- form
- (let ((form (copy-sequence form)))
- (setcdr (nthcdr skip form) (list (cons 'and tail)))
- form))))
-
-
-(defun rx-= (form)
- "Parse and produce code from FORM `(= N ...)'."
- (rx-check form)
- (setq form (rx-trans-forms form 1))
- (unless (and (integerp (nth 1 form))
- (> (nth 1 form) 0))
- (error "rx `=' requires positive integer first arg"))
- (let ((subform (rx-form (nth 2 form) '*)))
- (if (stringp subform)
- (format "%s\\{%d\\}" subform (nth 1 form))
- `(,@subform ,(format "\\{%d\\}" (nth 1 form))))))
-
-
-(defun rx->= (form)
- "Parse and produce code from FORM `(>= N ...)'."
- (rx-check form)
- (setq form (rx-trans-forms form 1))
- (unless (and (integerp (nth 1 form))
- (> (nth 1 form) 0))
- (error "rx `>=' requires positive integer first arg"))
- (let ((subform (rx-form (nth 2 form) '*)))
- (if (stringp subform)
- (format "%s\\{%d,\\}" subform (nth 1 form))
- `(,@subform ,(format "\\{%d,\\}" (nth 1 form))))))
-
-
-(defun rx-** (form)
- "Parse and produce code from FORM `(** N M ...)'."
- (rx-check form)
- (rx-form (cons 'repeat (cdr (rx-trans-forms form 2))) '*))
-
-
-(defun rx-repeat (form)
- "Parse and produce code from FORM.
-FORM is either `(repeat N FORM1)' or `(repeat N M FORMS...)'."
- (rx-check form)
- (if (> (length form) 4)
- (setq form (rx-trans-forms form 2)))
- (if (null (nth 2 form))
- (setq form (cons (nth 0 form) (cons (nth 1 form) (nthcdr 3 form)))))
- (cond ((= (length form) 3)
- (unless (and (integerp (nth 1 form))
- (> (nth 1 form) 0))
- (error "rx `repeat' requires positive integer first arg"))
- (let ((subform (rx-form (nth 2 form) '*)))
- (if (stringp subform)
- (format "%s\\{%d\\}" subform (nth 1 form))
- `(,@subform ,(format "\\{%d\\}" (nth 1 form))))))
- ((or (not (integerp (nth 2 form)))
- (< (nth 2 form) 0)
- (not (integerp (nth 1 form)))
- (< (nth 1 form) 0)
- (< (nth 2 form) (nth 1 form)))
- (error "rx `repeat' range error"))
- (t
- (let ((subform (rx-form (nth 3 form) '*)))
- (if (stringp subform)
- (format "%s\\{%d,%d\\}" subform (nth 1 form) (nth 2 form))
- `(,@subform ,(format "\\{%d,%d\\}" (nth 1 form) (nth 2 form))))))))
-
-
-(defun rx-submatch (form)
- "Parse and produce code from FORM, which is `(submatch ...)'."
- (let ((subforms (rx--subforms (cdr form) ':)))
- (if (stringp subforms)
- (concat "\\(" subforms "\\)")
- `("\\(" ,@subforms "\\)"))))
-
-(defun rx-submatch-n (form)
- "Parse and produce code from FORM, which is `(submatch-n N ...)'."
- (let ((n (nth 1 form))
- (subforms (rx--subforms (cddr form) ':)))
- (unless (and (integerp n) (> n 0))
- (error "rx `submatch-n' argument must be positive"))
- (if (stringp subforms)
- (concat "\\(?" (number-to-string n) ":" subforms "\\)")
- `("\\(?" ,(number-to-string n) ":" ,@subforms "\\)"))))
-
-(defun rx-backref (form)
- "Parse and produce code from FORM, which is `(backref N)'."
- (rx-check form)
- (format "\\%d" (nth 1 form)))
-
-(defun rx-check-backref (arg)
- "Check arg ARG for Rx `backref'."
- (or (and (integerp arg) (>= arg 1) (<= arg 9))
- (error "rx `backref' requires numeric 1<=arg<=9: %s" arg)))
-
-(defun rx-kleene (form)
- "Parse and produce code from FORM.
-FORM is `(OP FORM1)', where OP is one of the `zero-or-one',
-`zero-or-more' etc. operators.
-If OP is one of `*', `+', `?', produce a greedy regexp.
-If OP is one of `*?', `+?', `??', produce a non-greedy regexp.
-If OP is anything else, produce a greedy regexp if `rx-greedy-flag'
-is non-nil."
- (rx-check form)
- (setq form (rx-trans-forms form))
- (let ((suffix (cond ((memq (car form) '(* + \? ?\s)) "")
- ((memq (car form) '(*? +? \?? ??)) "?")
- (rx-greedy-flag "")
- (t "?")))
- (op (cond ((memq (car form) '(* *? 0+ zero-or-more)) "*")
- ((memq (car form) '(+ +? 1+ one-or-more)) "+")
- (t "?")))
- (subform (rx-form (cadr form) '*)))
- (rx-group-if
- (if (stringp subform)
- (concat subform op suffix)
- `(,@subform ,(concat op suffix)))
- (and (memq rx-parent '(t *)) rx-parent))))
-
-
-(defun rx-atomic-p (r &optional lax)
- "Return non-nil if regexp string R is atomic.
-An atomic regexp R is one such that a suffix operator
-appended to R will apply to all of R. For example, \"a\"
-\"[abc]\" and \"\\(ab\\|ab*c\\)\" are atomic and \"ab\",
-\"[ab]c\", and \"ab\\|ab*c\" are not atomic.
-
-This function may return false negatives, but it will not
-return false positives. It is nevertheless useful in
-situations where an efficiency shortcut can be taken only if a
-regexp is atomic. The function can be improved to detect
-more cases of atomic regexps. Presently, this function
-detects the following categories of atomic regexp;
-
- a group or shy group: \\(...\\)
- a character class: [...]
- a single character: a
-
-On the other hand, false negatives will be returned for
-regexps that are atomic but end in operators, such as
-\"a+\". I think these are rare. Probably such cases could
-be detected without much effort. A guarantee of no false
-negatives would require a theoretic specification of the set
-of all atomic regexps."
- (if (and rx--compile-to-lisp
- (not (stringp r)))
- nil ;; Runtime value, we must assume non-atomic.
- (let ((l (length r)))
+ intervals))
+
+(defun rx--condense-intervals (intervals)
+ "Merge adjacent and overlapping intervals by mutation, preserving the order.
+INTERVALS is a list of (START . END) with START ≤ END, sorted by START."
+ (let ((tail intervals)
+ d)
+ (while (setq d (cdr tail))
+ (if (>= (cdar tail) (1- (caar d)))
+ (progn
+ (setcdr (car tail) (max (cdar tail) (cdar d)))
+ (setcdr tail (cdr d)))
+ (setq tail d)))
+ intervals))
+
+;; FIXME: Consider expanding definitions inside (any ...) and (not ...),
+;; and perhaps allow (any ...) inside (any ...).
+;; It would be benefit composability (build a character alternative by pieces)
+;; and be handy for obtaining the complement of a defined set of
+;; characters. (See, for example, python.el:421, `not-simple-operator'.)
+;; (Expansion in other non-rx positions is probably not a good idea:
+;; syntax, category, backref, and the integer parameters of group-n,
+;; =, >=, **, repeat)
+;; Similar effect could be attained by ensuring that
+;; (or (any X) (any Y)) -> (any X Y), and find a way to compose negative
+;; sets. `and' is taken, but we could add
+;; (intersection (not (any X)) (not (any Y))) -> (not (any X Y)).
+
+(defun rx--translate-any (negated body)
+ "Translate an (any ...) construct. Return (REGEXP . PRECEDENCE).
+If NEGATED, negate the sense."
+ (let ((classes nil)
+ (strings nil)
+ (conses nil))
+ ;; Collect strings, conses and characters, and classes in separate bins.
+ (dolist (arg body)
+ (cond ((stringp arg)
+ (push arg strings))
+ ((and (consp arg)
+ (characterp (car arg))
+ (characterp (cdr arg))
+ (<= (car arg) (cdr arg)))
+ ;; Copy the cons, in case we need to modify it.
+ (push (cons (car arg) (cdr arg)) conses))
+ ((characterp arg)
+ (push (cons arg arg) conses))
+ ((and (symbolp arg)
+ (let ((class (cdr (assq arg rx--char-classes))))
+ (and class (push class classes)))))
+ (t (error "Invalid rx `any' argument: %s" arg))))
+ (let ((items
+ ;; Translate strings and conses into nonoverlapping intervals,
+ ;; and add classes as symbols at the end.
+ (append
+ (rx--condense-intervals
+ (sort (append conses
+ (mapcan #'rx--string-to-intervals strings))
+ #'car-less-than-car))
+ (reverse classes))))
+
+ ;; Move lone ] and range ]-x to the start.
+ (let ((rbrac-l (assq ?\] items)))
+ (when rbrac-l
+ (setq items (cons rbrac-l (delq rbrac-l items)))))
+
+ ;; Split x-] and move the lone ] to the start.
+ (let ((rbrac-r (rassq ?\] items)))
+ (when (and rbrac-r (not (eq (car rbrac-r) ?\])))
+ (setcdr rbrac-r ?\\)
+ (setq items (cons '(?\] . ?\]) items))))
+
+ ;; Split ,-- (which would end up as ,- otherwise).
+ (let ((dash-r (rassq ?- items)))
+ (when (eq (car dash-r) ?,)
+ (setcdr dash-r ?,)
+ (setq items (nconc items '((?- . ?-))))))
+
+ ;; Remove - (lone or at start of interval)
+ (let ((dash-l (assq ?- items)))
+ (when dash-l
+ (if (eq (cdr dash-l) ?-)
+ (setq items (delq dash-l items)) ; Remove lone -
+ (setcar dash-l ?.)) ; Reduce --x to .-x
+ (setq items (nconc items '((?- . ?-))))))
+
+ ;; Deal with leading ^ and range ^-x.
+ (when (and (consp (car items))
+ (eq (caar items) ?^)
+ (cdr items))
+ ;; Move ^ and ^-x to second place.
+ (setq items (cons (cadr items)
+ (cons (car items) (cddr items)))))
+
(cond
- ((<= l 1))
- ((= l 2) (= (aref r 0) ?\\))
- ((= l 3) (string-match "\\`\\(?:\\\\[cCsS_]\\|\\[[^^]\\]\\)" r))
- ((null lax)
- (cond
- ((string-match "\\`\\[\\^?]?\\(?:\\[:[a-z]+:]\\|[^]]\\)*]\\'" r))
- ((string-match "\\`\\\\(\\(?:[^\\]\\|\\\\[^)]\\)*\\\\)\\'" r))))))))
-
-
-(defun rx-syntax (form)
- "Parse and produce code from FORM, which is `(syntax SYMBOL)'."
- (rx-check form)
- (let* ((sym (cadr form))
- (syntax (cdr (assq sym rx-syntax))))
+ ;; Empty set: if negated, any char, otherwise match-nothing.
+ ((null items)
+ (if negated
+ (rx--translate-symbol 'anything)
+ (rx--empty)))
+ ;; Single non-negated character.
+ ((and (null (cdr items))
+ (consp (car items))
+ (eq (caar items) (cdar items))
+ (not negated))
+ (cons (list (regexp-quote (char-to-string (caar items))))
+ t))
+ ;; At least one character or class, possibly negated.
+ (t
+ (cons
+ (list
+ (concat
+ "["
+ (and negated "^")
+ (mapconcat (lambda (item)
+ (cond ((symbolp item)
+ (format "[:%s:]" item))
+ ((eq (car item) (cdr item))
+ (char-to-string (car item)))
+ ((eq (1+ (car item)) (cdr item))
+ (string (car item) (cdr item)))
+ (t
+ (string (car item) ?- (cdr item)))))
+ items nil)
+ "]"))
+ t))))))
+
+(defun rx--translate-not (negated body)
+ "Translate a (not ...) construct. Return (REGEXP . PRECEDENCE).
+If NEGATED, negate the sense (thus making it positive)."
+ (unless (and body (null (cdr body)))
+ (error "rx `not' form takes exactly one argument"))
+ (let ((arg (car body)))
+ (cond
+ ((consp arg)
+ (pcase (car arg)
+ ((or 'any 'in 'char) (rx--translate-any (not negated) (cdr arg)))
+ ('syntax (rx--translate-syntax (not negated) (cdr arg)))
+ ('category (rx--translate-category (not negated) (cdr arg)))
+ ('not (rx--translate-not (not negated) (cdr arg)))
+ (_ (error "Illegal argument to rx `not': %S" arg))))
+ ((eq arg 'word-boundary)
+ (rx--translate-symbol
+ (if negated 'word-boundary 'not-word-boundary)))
+ (t
+ (let ((class (cdr (assq arg rx--char-classes))))
+ (if class
+ (rx--translate-any (not negated) (list class))
+ (error "Illegal argument to rx `not': %s" arg)))))))
+
+(defun rx--atomic-regexp (item)
+ "ITEM is (REGEXP . PRECEDENCE); return a regexp of precedence t."
+ (if (eq (cdr item) t)
+ (car item)
+ (rx--bracket (car item))))
+
+(defun rx--translate-counted-repetition (min-count max-count body)
+ (let ((operand (rx--translate-seq body)))
+ (if (car operand)
+ (cons (append
+ (rx--atomic-regexp operand)
+ (list (concat "\\{"
+ (number-to-string min-count)
+ (cond ((null max-count) ",")
+ ((< min-count max-count)
+ (concat "," (number-to-string max-count))))
+ "\\}")))
+ t)
+ operand)))
+
+(defun rx--check-repeat-arg (name min-args body)
+ (unless (>= (length body) min-args)
+ (error "rx `%s' requires at least %d argument%s"
+ name min-args (if (= min-args 1) "" "s")))
+ ;; There seems to be no reason to disallow zero counts.
+ (unless (natnump (car body))
+ (error "rx `%s' first argument must be nonnegative" name)))
+
+(defun rx--translate-bounded-repetition (name body)
+ (let ((min-count (car body))
+ (max-count (cadr body))
+ (items (cddr body)))
+ (unless (and (natnump min-count)
+ (natnump max-count)
+ (<= min-count max-count))
+ (error "rx `%s' range error" name))
+ (rx--translate-counted-repetition min-count max-count items)))
+
+(defun rx--translate-repeat (body)
+ (rx--check-repeat-arg 'repeat 2 body)
+ (if (= (length body) 2)
+ (rx--translate-counted-repetition (car body) (car body) (cdr body))
+ (rx--translate-bounded-repetition 'repeat body)))
+
+(defun rx--translate-** (body)
+ (rx--check-repeat-arg '** 2 body)
+ (rx--translate-bounded-repetition '** body))
+
+(defun rx--translate->= (body)
+ (rx--check-repeat-arg '>= 1 body)
+ (rx--translate-counted-repetition (car body) nil (cdr body)))
+
+(defun rx--translate-= (body)
+ (rx--check-repeat-arg '= 1 body)
+ (rx--translate-counted-repetition (car body) (car body) (cdr body)))
+
+(defvar rx--greedy t)
+
+(defun rx--translate-rep (op-string greedy body)
+ "Translate a repetition; OP-STRING is one of \"*\", \"+\" or \"?\".
+GREEDY is a boolean. Return (REGEXP . PRECEDENCE)."
+ (let ((operand (rx--translate-seq body)))
+ (if (car operand)
+ (cons (append (rx--atomic-regexp operand)
+ (list (concat op-string (unless greedy "?"))))
+ ;; The result has precedence seq to avoid (? (* "a")) -> "a*?"
+ 'seq)
+ operand)))
+
+(defun rx--control-greedy (greedy body)
+ "Translate the sequence BODY with greediness GREEDY.
+Return (REGEXP . PRECEDENCE)."
+ (let ((rx--greedy greedy))
+ (rx--translate-seq body)))
+
+(defun rx--translate-group (body)
+ "Translate the `group' form. Return (REGEXP . PRECEDENCE)."
+ (cons (rx--enclose "\\("
+ (car (rx--translate-seq body))
+ "\\)")
+ t))
+
+(defun rx--translate-group-n (body)
+ "Translate the `group-n' form. Return (REGEXP . PRECEDENCE)."
+ (unless (and (integerp (car body)) (> (car body) 0))
+ (error "rx `group-n' requires a positive number as first argument"))
+ (cons (rx--enclose (concat "\\(?" (number-to-string (car body)) ":")
+ (car (rx--translate-seq (cdr body)))
+ "\\)")
+ t))
+
+(defun rx--translate-backref (body)
+ "Translate the `backref' form. Return (REGEXP . PRECEDENCE)."
+ (unless (and (= (length body) 1) (integerp (car body)) (<= 1 (car body) 9))
+ (error "rx `backref' requires an argument in the range 1..9"))
+ (cons (list "\\" (number-to-string (car body))) t))
+
+(defconst rx--syntax-codes
+ '((whitespace . ?-) ; SPC also accepted
+ (punctuation . ?.)
+ (word . ?w) ; W also accepted
+ (symbol . ?_)
+ (open-parenthesis . ?\()
+ (close-parenthesis . ?\))
+ (expression-prefix . ?\')
+ (string-quote . ?\")
+ (paired-delimiter . ?$)
+ (escape . ?\\)
+ (character-quote . ?/)
+ (comment-start . ?<)
+ (comment-end . ?>)
+ (string-delimiter . ?|)
+ (comment-delimiter . ?!)))
+
+(defun rx--translate-syntax (negated body)
+ "Translate the `syntax' form. Return (REGEXP . PRECEDENCE)."
+ (unless (and body (null (cdr body)))
+ (error "rx `syntax' form takes exactly one argument"))
+ (let* ((sym (car body))
+ (syntax (cdr (assq sym rx--syntax-codes))))
(unless syntax
- ;; Try sregex compatibility.
(cond
- ((characterp sym) (setq syntax sym))
+ ;; Syntax character directly (sregex compatibility)
+ ((and (characterp sym) (rassq sym rx--syntax-codes))
+ (setq syntax sym))
+ ;; Syntax character as symbol (sregex compatibility)
((symbolp sym)
(let ((name (symbol-name sym)))
- (if (= 1 (length name))
- (setq syntax (aref name 0))))))
+ (when (= (length name) 1)
+ (let ((char (string-to-char name)))
+ (when (rassq char rx--syntax-codes)
+ (setq syntax char)))))))
(unless syntax
- (error "Unknown rx syntax `%s'" sym)))
- (format "\\s%c" syntax)))
-
-
-(defun rx-check-category (form)
- "Check the argument FORM of a `(category FORM)'."
- (unless (or (integerp form)
- (cdr (assq form rx-categories)))
- (error "Unknown category `%s'" form))
- t)
-
-
-(defun rx-category (form)
- "Parse and produce code from FORM, which is `(category SYMBOL)'."
- (rx-check form)
- (let ((char (if (integerp (cadr form))
- (cadr form)
- (cdr (assq (cadr form) rx-categories)))))
- (format "\\c%c" char)))
-
-
-(defun rx-eval (form)
- "Parse and produce code from FORM, which is `(eval FORM)'."
- (rx-check form)
- (rx-form (eval (cadr form)) rx-parent))
-
-
-(defun rx-greedy (form)
- "Parse and produce code from FORM.
-If FORM is `(minimal-match FORM1)', non-greedy versions of `*',
-`+', and `?' operators will be used in FORM1. If FORM is
-`(maximal-match FORM1)', greedy operators will be used."
- (rx-check form)
- (let ((rx-greedy-flag (eq (car form) 'maximal-match)))
- (rx-form (cadr form) rx-parent)))
-
-
-(defun rx-regexp (form)
- "Parse and produce code from FORM, which is `(regexp STRING)'."
- (cond ((stringp (cadr form))
- (rx-group-if (cadr form) rx-parent))
- (rx--compile-to-lisp
- ;; Always group non-string forms, since we can't be sure they
- ;; are atomic.
- (rx-group-if (cdr form) t))
- (t (rx-check form))))
-
-(defun rx-literal (form)
- "Parse and produce code from FORM, which is `(literal STRING-EXP)'."
- (cond ((stringp (cadr form))
- ;; This is allowed, but makes little sense, you could just
- ;; use STRING directly.
- (rx-group-if (regexp-quote (cadr form)) rx-parent))
- (rx--compile-to-lisp
- (rx-group-if `((regexp-quote ,(cadr form))) rx-parent))
- (t (rx-check form))))
-
-(defun rx-form (form &optional parent)
- "Parse and produce code for regular expression FORM.
-FORM is a regular expression in sexp form.
-PARENT shows which type of expression calls and controls putting of
-shy groups around the result and some more in other functions."
- (let ((rx-parent parent))
- (cond
- ((stringp form)
- (rx-group-if (regexp-quote form)
- (if (and (eq parent '*) (< 1 (length form)))
- parent)))
- ((integerp form)
- (regexp-quote (char-to-string form)))
- ((symbolp form)
- (let ((info (rx-info form nil)))
- (cond ((stringp info)
- info)
- ((null info)
- (error "Unknown rx form `%s'" form))
- (t
- (funcall (nth 0 info) form)))))
- ((consp form)
- (let ((info (rx-info (car form) 'head)))
- (unless (consp info)
- (error "Unknown rx form `%s'" (car form)))
- (funcall (nth 0 info) form)))
- (t
- (error "rx syntax error at `%s'" form)))))
-
-(defun rx--subforms (subforms &optional parent separator)
- "Produce code for regular expressions SUBFORMS.
-SUBFORMS is a list of regular expression sexps.
-PARENT controls grouping, as in `rx-form'.
-Insert SEPARATOR between the code from each of SUBFORMS."
- (if (null (cdr subforms))
- ;; Zero or one forms, no need for grouping.
- (and subforms (rx-form (car subforms)))
- (let ((listify (lambda (x)
- (if (listp x) (copy-sequence x)
- (list x)))))
- (setq subforms (mapcar (lambda (x) (rx-form x parent)) subforms))
- (cond ((or (not rx--compile-to-lisp)
- (cl-every #'stringp subforms))
- (mapconcat #'identity subforms separator))
- (separator
- (nconc (funcall listify (car subforms))
- (mapcan (lambda (x)
- (cons separator (funcall listify x)))
- (cdr subforms))))
- (t (mapcan listify subforms))))))
+ (error "Unknown rx syntax name `%s'" sym)))
+ (cons (list (string ?\\ (if negated ?S ?s) syntax))
+ t)))
+
+(defconst rx--categories
+ '((space-for-indent . ?\s)
+ (base . ?.)
+ (consonant . ?0)
+ (base-vowel . ?1)
+ (upper-diacritical-mark . ?2)
+ (lower-diacritical-mark . ?3)
+ (tone-mark . ?4)
+ (symbol . ?5)
+ (digit . ?6)
+ (vowel-modifying-diacritical-mark . ?7)
+ (vowel-sign . ?8)
+ (semivowel-lower . ?9)
+ (not-at-end-of-line . ?<)
+ (not-at-beginning-of-line . ?>)
+ (alpha-numeric-two-byte . ?A)
+ (chinese-two-byte . ?C)
+ (chinse-two-byte . ?C) ; A typo in Emacs 21.1-24.3.
+ (greek-two-byte . ?G)
+ (japanese-hiragana-two-byte . ?H)
+ (indian-two-byte . ?I)
+ (japanese-katakana-two-byte . ?K)
+ (strong-left-to-right . ?L)
+ (korean-hangul-two-byte . ?N)
+ (strong-right-to-left . ?R)
+ (cyrillic-two-byte . ?Y)
+ (combining-diacritic . ?^)
+ (ascii . ?a)
+ (arabic . ?b)
+ (chinese . ?c)
+ (ethiopic . ?e)
+ (greek . ?g)
+ (korean . ?h)
+ (indian . ?i)
+ (japanese . ?j)
+ (japanese-katakana . ?k)
+ (latin . ?l)
+ (lao . ?o)
+ (tibetan . ?q)
+ (japanese-roman . ?r)
+ (thai . ?t)
+ (vietnamese . ?v)
+ (hebrew . ?w)
+ (cyrillic . ?y)
+ (can-break . ?|)))
+
+(defun rx--translate-category (negated body)
+ "Translate the `category' form. Return (REGEXP . PRECEDENCE)."
+ (unless (and body (null (cdr body)))
+ (error "rx `category' form takes exactly one argument"))
+ (let* ((arg (car body))
+ (category
+ (cond ((symbolp arg)
+ (let ((cat (assq arg rx--categories)))
+ (unless cat
+ (error "Unknown rx category `%s'" arg))
+ (cdr cat)))
+ ((characterp arg) arg)
+ (t (error "Invalid rx `category' argument `%s'" arg)))))
+ (cons (list (string ?\\ (if negated ?C ?c) category))
+ t)))
+
+(defvar rx--delayed-evaluation nil
+ "Whether to allow certain forms to be evaluated at runtime.")
+
+(defun rx--translate-literal (body)
+ "Translate the `literal' form. Return (REGEXP . PRECEDENCE)."
+ (unless (and body (null (cdr body)))
+ (error "rx `literal' form takes exactly one argument"))
+ (let ((arg (car body)))
+ (cond ((stringp arg)
+ (cons (list (regexp-quote arg)) (if (= (length arg) 1) t 'seq)))
+ (rx--delayed-evaluation
+ (cons (list (list 'regexp-quote arg)) 'seq))
+ (t (error "rx `literal' form with non-string argument")))))
+
+(defun rx--translate-eval (body)
+ "Translate the `eval' form. Return (REGEXP . PRECEDENCE)."
+ (unless (and body (null (cdr body)))
+ (error "rx `eval' form takes exactly one argument"))
+ (rx--translate (eval (car body))))
+
+(defvar rx--regexp-atomic-regexp nil)
+
+(defun rx--translate-regexp (body)
+ "Translate the `regexp' form. Return (REGEXP . PRECEDENCE)."
+ (unless (and body (null (cdr body)))
+ (error "rx `regexp' form takes exactly one argument"))
+ (let ((arg (car body)))
+ (cond ((stringp arg)
+ ;; Generate the regexp when needed, since rx isn't
+ ;; necessarily present in the byte-compilation environment.
+ (unless rx--regexp-atomic-regexp
+ (setq rx--regexp-atomic-regexp
+ ;; Match atomic (precedence t) regexps: may give
+ ;; false negatives but no false positives, assuming
+ ;; the target string is syntactically correct.
+ (rx-to-string
+ '(seq
+ bos
+ (or (seq "["
+ (opt "^")
+ (opt "]")
+ (* (or (seq "[:" (+ (any "a-z")) ":]")
+ (not (any "]"))))
+ "]")
+ anything
+ (seq "\\"
+ (or anything
+ (seq (any "sScC_") anything)
+ (seq "("
+ (* (or (not (any "\\"))
+ (seq "\\" (not (any ")")))))
+ "\\)"))))
+ eos)
+ t)))
+ (cons (list arg)
+ (if (string-match-p rx--regexp-atomic-regexp arg) t nil)))
+ (rx--delayed-evaluation
+ (cons (list arg) nil))
+ (t (error "rx `regexp' form with non-string argument")))))
+
+(defun rx--translate-compat-form (def form)
+ "Translate a compatibility form from `rx-constituents'.
+DEF is the definition tuple. Return (REGEXP . PRECEDENCE)."
+ (let* ((fn (nth 0 def))
+ (min-args (nth 1 def))
+ (max-args (nth 2 def))
+ (predicate (nth 3 def))
+ (nargs (1- (length form))))
+ (when (< nargs min-args)
+ (error "The `%s' form takes at least %d argument(s)"
+ (car form) min-args))
+ (when (and max-args (> nargs max-args))
+ (error "The `%s' form takes at most %d argument(s)"
+ (car form) max-args))
+ (when (and predicate (not (rx--every predicate (cdr form))))
+ (error "The `%s' form requires arguments satisfying `%s'"
+ (car form) predicate))
+ (let ((regexp (funcall fn form)))
+ (unless (stringp regexp)
+ (error "The `%s' form did not expand to a string" (car form)))
+ (cons (list regexp) nil))))
+
+(defun rx--substitute (bindings form)
+ "Substitute BINDINGS in FORM. BINDINGS is an alist of (NAME . VALUES)
+where VALUES is a list to splice into FORM wherever NAME occurs.
+Return the substitution result wrapped in a list, since a single value
+can expand to any number of values."
+ (cond ((symbolp form)
+ (let ((binding (assq form bindings)))
+ (if binding
+ (cdr binding)
+ (list form))))
+ ((consp form)
+ (if (listp (cdr form))
+ ;; Proper list. We substitute variables even in the head
+ ;; position -- who knows, might be handy one day.
+ (list (mapcan (lambda (x) (copy-sequence
+ (rx--substitute bindings x)))
+ form))
+ ;; Cons pair (presumably an interval).
+ (let ((first (rx--substitute bindings (car form)))
+ (second (rx--substitute bindings (cdr form))))
+ (if (and first (not (cdr first))
+ second (not (cdr second)))
+ (list (cons (car first) (car second)))
+ (error
+ "Cannot substitute a &rest parameter into a dotted pair")))))
+ (t (list form))))
+
+;; FIXME: Consider adding extensions in Lisp macro style, where
+;; arguments are passed unevaluated to code that returns the rx form
+;; to use. Example:
+;;
+;; (rx-let ((radix-digit (radix)
+;; :lisp (list 'any (cons ?0 (+ ?0 (eval radix) -1)))))
+;; (rx (radix-digit (+ 5 3))))
+;; =>
+;; "[0-7]"
+;;
+;; While this would permit more powerful extensions, it's unclear just
+;; how often they would be used in practice. Let's wait until there is
+;; demand for it.
+
+;; FIXME: An alternative binding syntax would be
+;;
+;; (NAME RXs...)
+;; and
+;; ((NAME ARGS...) RXs...)
+;;
+;; which would have two minor advantages: multiple RXs with implicit
+;; `seq' in the definition, and the arglist is no longer an optional
+;; element in the middle of the list. On the other hand, it's less
+;; like traditional lisp arglist constructs (defun, defmacro).
+;; Since it's a Scheme-like syntax, &rest parameters could be done using
+;; dotted lists:
+;; (rx-let (((name arg1 arg2 . rest) ...definition...)) ...)
+
+(defun rx--expand-template (op values arglist template)
+ "Return TEMPLATE with variables in ARGLIST replaced with VALUES."
+ (let ((bindings nil)
+ (value-tail values)
+ (formals arglist))
+ (while formals
+ (pcase (car formals)
+ ('&rest
+ (unless (cdr formals)
+ (error
+ "Expanding rx def `%s': missing &rest parameter name" op))
+ (push (cons (cadr formals) value-tail) bindings)
+ (setq formals nil)
+ (setq value-tail nil))
+ (name
+ (unless value-tail
+ (error
+ "Expanding rx def `%s': too few arguments (got %d, need %s%d)"
+ op (length values)
+ (if (memq '&rest arglist) "at least " "")
+ (- (length arglist) (length (memq '&rest arglist)))))
+ (push (cons name (list (car value-tail))) bindings)
+ (setq value-tail (cdr value-tail))))
+ (setq formals (cdr formals)))
+ (when value-tail
+ (error
+ "Expanding rx def `%s': too many arguments (got %d, need %d)"
+ op (length values) (length arglist)))
+ (let ((subst (rx--substitute bindings template)))
+ (if (and subst (not (cdr subst)))
+ (car subst)
+ (error "Expanding rx def `%s': must result in a single value" op)))))
+
+(defun rx--translate-form (form)
+ "Translate an rx form (list structure). Return (REGEXP . PRECEDENCE)."
+ (let ((body (cdr form)))
+ (pcase (car form)
+ ((or 'seq : 'and 'sequence) (rx--translate-seq body))
+ ((or 'or '|) (rx--translate-or body))
+ ((or 'any 'in 'char) (rx--translate-any nil body))
+ ('not-char (rx--translate-any t body))
+ ('not (rx--translate-not nil body))
+
+ ('repeat (rx--translate-repeat body))
+ ('= (rx--translate-= body))
+ ('>= (rx--translate->= body))
+ ('** (rx--translate-** body))
+
+ ((or 'zero-or-more '0+) (rx--translate-rep "*" rx--greedy body))
+ ((or 'one-or-more '1+) (rx--translate-rep "+" rx--greedy body))
+ ((or 'zero-or-one 'opt 'optional) (rx--translate-rep "?" rx--greedy body))
+
+ ('* (rx--translate-rep "*" t body))
+ ('+ (rx--translate-rep "+" t body))
+ ((or '\? ?\s) (rx--translate-rep "?" t body))
+
+ ('*? (rx--translate-rep "*" nil body))
+ ('+? (rx--translate-rep "+" nil body))
+ ((or '\?? ??) (rx--translate-rep "?" nil body))
+
+ ('minimal-match (rx--control-greedy nil body))
+ ('maximal-match (rx--control-greedy t body))
+
+ ((or 'group 'submatch) (rx--translate-group body))
+ ((or 'group-n 'submatch-n) (rx--translate-group-n body))
+ ('backref (rx--translate-backref body))
+
+ ('syntax (rx--translate-syntax nil body))
+ ('not-syntax (rx--translate-syntax t body))
+ ('category (rx--translate-category nil body))
+
+ ('literal (rx--translate-literal body))
+ ('eval (rx--translate-eval body))
+ ((or 'regexp 'regex) (rx--translate-regexp body))
+
+ (op
+ (unless (symbolp op)
+ (error "Bad rx operator `%S'" op))
+ (let ((definition (rx--lookup-def op)))
+ (if definition
+ (if (cdr definition)
+ (rx--translate
+ (rx--expand-template
+ op body (nth 0 definition) (nth 1 definition)))
+ (error "Not an `rx' form definition: %s" op))
+
+ ;; For compatibility with old rx.
+ (let ((entry (assq op rx-constituents)))
+ (if (progn
+ (while (and entry (not (consp (cdr entry))))
+ (setq entry
+ (if (symbolp (cdr entry))
+ ;; Alias for another entry.
+ (assq (cdr entry) rx-constituents)
+ ;; Wrong type, try further down the list.
+ (assq (car entry)
+ (cdr (memq entry rx-constituents))))))
+ entry)
+ (rx--translate-compat-form (cdr entry) form)
+ (error "Unknown rx form `%s'" op)))))))))
+
+(defconst rx--builtin-forms
+ '(seq sequence : and or | any in char not-char not
+ repeat = >= **
+ zero-or-more 0+ *
+ one-or-more 1+ +
+ zero-or-one opt optional \?
+ *? +? \??
+ minimal-match maximal-match
+ group submatch group-n submatch-n backref
+ syntax not-syntax category
+ literal eval regexp regex)
+ "List of built-in rx function-like symbols.")
+
+(defconst rx--builtin-symbols
+ (append '(nonl not-newline any anything
+ bol eol line-start line-end
+ bos eos string-start string-end
+ bow eow word-start word-end
+ symbol-start symbol-end
+ point word-boundary not-word-boundary not-wordchar)
+ (mapcar #'car rx--char-classes))
+ "List of built-in rx variable-like symbols.")
+
+(defconst rx--builtin-names
+ (append rx--builtin-forms rx--builtin-symbols)
+ "List of built-in rx names. These cannot be redefined by the user.")
+
+(defun rx--translate (item)
+ "Translate the rx-expression ITEM. Return (REGEXP . PRECEDENCE)."
+ (cond
+ ((stringp item)
+ (if (= (length item) 0)
+ (cons nil 'seq)
+ (cons (list (regexp-quote item)) (if (= (length item) 1) t 'seq))))
+ ((characterp item)
+ (cons (list (regexp-quote (char-to-string item))) t))
+ ((symbolp item)
+ (rx--translate-symbol item))
+ ((consp item)
+ (rx--translate-form item))
+ (t (error "Bad rx expression: %S" item))))
;;;###autoload
(defun rx-to-string (form &optional no-group)
- "Parse and produce code for regular expression FORM.
-FORM is a regular expression in sexp form.
-NO-GROUP non-nil means don't put shy groups around the result.
-
-In contrast to the `rx' macro, subforms `literal' and `regexp'
-will not accept non-string arguments, i.e., (literal STRING)
-becomes just a more verbose version of STRING."
- (rx-group-if (rx-form form) (null no-group)))
+ "Translate FORM from `rx' sexp syntax into a string regexp.
+The arguments to `literal' and `regexp' forms inside FORM must be
+constant strings.
+If NO-GROUP is non-nil, don't bracket the result in a non-capturing
+group.
+
+For extending the `rx' notation in FORM, use `rx-define' or `rx-let-eval'."
+ (let* ((item (rx--translate form))
+ (exprs (if no-group
+ (car item)
+ (rx--atomic-regexp item))))
+ (apply #'concat exprs)))
+
+(defun rx--to-expr (form)
+ "Translate the rx-expression FORM to a Lisp expression yielding a regexp."
+ (let* ((rx--delayed-evaluation t)
+ (elems (car (rx--translate form)))
+ (args nil))
+ ;; Merge adjacent strings.
+ (while elems
+ (let ((strings nil))
+ (while (and elems (stringp (car elems)))
+ (push (car elems) strings)
+ (setq elems (cdr elems)))
+ (let ((s (apply #'concat (nreverse strings))))
+ (unless (zerop (length s))
+ (push s args))))
+ (when elems
+ (push (car elems) args)
+ (setq elems (cdr elems))))
+ (cond ((null args) "") ; 0 args
+ ((cdr args) (cons 'concat (nreverse args))) ; ≥2 args
+ (t (car args))))) ; 1 arg
;;;###autoload
@@ -1054,78 +1077,186 @@ Zero-width assertions: these all match the empty string in specific places.
(literal EXPR) Match the literal string from evaluating EXPR at run time.
(regexp EXPR) Match the string regexp from evaluating EXPR at run time.
-(eval EXPR) Match the rx sexp from evaluating EXPR at compile time."
- (let* ((rx--compile-to-lisp t)
- (re (cond ((null regexps)
- (error "No regexp"))
- ((cdr regexps)
- (rx-to-string `(and ,@regexps) t))
- (t
- (rx-to-string (car regexps) t)))))
- (if (stringp re)
- re
- `(concat ,@re))))
+(eval EXPR) Match the rx sexp from evaluating EXPR at compile time.
+
+Additional constructs can be defined using `rx-define' and `rx-let',
+which see.
+
+\(fn REGEXPS...)"
+ ;; Retrieve local definitions from the macroexpansion environment.
+ ;; (It's unclear whether the previous value of `rx--local-definitions'
+ ;; should be included, and if so, in which order.)
+ (let ((rx--local-definitions
+ (cdr (assq :rx-locals macroexpand-all-environment))))
+ (rx--to-expr (cons 'seq regexps))))
+
+(defun rx--make-binding (name tail)
+ "Make a definitions entry out of TAIL.
+TAIL is on the form ([ARGLIST] DEFINITION)."
+ (unless (symbolp name)
+ (error "Bad `rx' definition name: %S" name))
+ ;; FIXME: Consider using a hash table or symbol property, for speed.
+ (when (memq name rx--builtin-names)
+ (error "Cannot redefine built-in rx name `%s'" name))
+ (pcase tail
+ (`(,def)
+ (list def))
+ (`(,args ,def)
+ (unless (and (listp args) (rx--every #'symbolp args))
+ (error "Bad argument list for `rx' definition %s: %S" name args))
+ (list args def))
+ (_ (error "Bad `rx' definition of %s: %S" name tail))))
+
+(defun rx--make-named-binding (bindspec)
+ "Make a definitions entry out of BINDSPEC.
+BINDSPEC is on the form (NAME [ARGLIST] DEFINITION)."
+ (unless (consp bindspec)
+ (error "Bad `rx-let' binding: %S" bindspec))
+ (cons (car bindspec)
+ (rx--make-binding (car bindspec) (cdr bindspec))))
+
+(defun rx--extend-local-defs (bindspecs)
+ (append (mapcar #'rx--make-named-binding bindspecs)
+ rx--local-definitions))
+;;;###autoload
+(defmacro rx-let-eval (bindings &rest body)
+ "Evaluate BODY with local BINDINGS for `rx-to-string'.
+BINDINGS, after evaluation, is a list of definitions each on the form
+(NAME [(ARGS...)] RX), in effect for calls to `rx-to-string'
+in BODY.
+
+For bindings without an ARGS list, NAME is defined as an alias
+for the `rx' expression RX. Where ARGS is supplied, NAME is
+defined as an `rx' form with ARGS as argument list. The
+parameters are bound from the values in the (NAME ...) form and
+are substituted in RX. ARGS can contain `&rest' parameters,
+whose values are spliced into RX where the parameter name occurs.
+
+Any previous definitions with the same names are shadowed during
+the expansion of BODY only.
+For extensions when using the `rx' macro, use `rx-let'.
+To make global rx extensions, use `rx-define'.
+For more details, see Info node `(elisp) Extending Rx'.
+
+\(fn BINDINGS BODY...)"
+ (declare (indent 1) (debug (form body)))
+ ;; FIXME: this way, `rx--extend-local-defs' may need to be autoloaded.
+ `(let ((rx--local-definitions (rx--extend-local-defs ,bindings)))
+ ,@body))
+
+;;;###autoload
+(defmacro rx-let (bindings &rest body)
+ "Evaluate BODY with local BINDINGS for `rx'.
+BINDINGS is an unevaluated list of bindings each on the form
+(NAME [(ARGS...)] RX).
+They are bound lexically and are available in `rx' expressions in
+BODY only.
+
+For bindings without an ARGS list, NAME is defined as an alias
+for the `rx' expression RX. Where ARGS is supplied, NAME is
+defined as an `rx' form with ARGS as argument list. The
+parameters are bound from the values in the (NAME ...) form and
+are substituted in RX. ARGS can contain `&rest' parameters,
+whose values are spliced into RX where the parameter name occurs.
+
+Any previous definitions with the same names are shadowed during
+the expansion of BODY only.
+For local extensions to `rx-to-string', use `rx-let-eval'.
+To make global rx extensions, use `rx-define'.
+For more details, see Info node `(elisp) Extending Rx'.
+
+\(fn BINDINGS BODY...)"
+ (declare (indent 1) (debug (sexp body)))
+ (let ((prev-locals (cdr (assq :rx-locals macroexpand-all-environment)))
+ (new-locals (mapcar #'rx--make-named-binding bindings)))
+ (macroexpand-all (cons 'progn body)
+ (cons (cons :rx-locals (append new-locals prev-locals))
+ macroexpand-all-environment))))
+
+;;;###autoload
+(defmacro rx-define (name &rest definition)
+ "Define NAME as a global `rx' definition.
+If the ARGS list is omitted, define NAME as an alias for the `rx'
+expression RX.
+
+If the ARGS list is supplied, define NAME as an `rx' form with
+ARGS as argument list. The parameters are bound from the values
+in the (NAME ...) form and are substituted in RX.
+ARGS can contain `&rest' parameters, whose values are spliced
+into RX where the parameter name occurs.
+
+Any previous global definition of NAME is overwritten with the new one.
+To make local rx extensions, use `rx-let' for `rx',
+`rx-let-eval' for `rx-to-string'.
+For more details, see Info node `(elisp) Extending Rx'.
+
+\(fn NAME [(ARGS...)] RX)"
+ (declare (indent 1))
+ `(eval-and-compile
+ (put ',name 'rx-definition ',(rx--make-binding name definition))
+ ',name))
+
+;; During `rx--pcase-transform', list of defined variables in right-to-left
+;; order.
+(defvar rx--pcase-vars)
+
+;; FIXME: The rewriting strategy for pcase works so-so with extensions;
+;; definitions cannot expand to `let' or named `backref'. If this ever
+;; becomes a problem, we can handle those forms in the ordinary parser,
+;; using a dynamic variable for activating the augmented forms.
+
+(defun rx--pcase-transform (rx)
+ "Transform RX, an rx-expression augmented with `let' and named `backref',
+into a plain rx-expression, collecting names into `rx--pcase-vars'."
+ (pcase rx
+ (`(let ,name . ,body)
+ (let* ((index (length (memq name rx--pcase-vars)))
+ (i (if (zerop index)
+ (length (push name rx--pcase-vars))
+ index)))
+ `(group-n ,i ,(rx--pcase-transform (cons 'seq body)))))
+ ((and `(backref ,ref)
+ (guard (symbolp ref)))
+ (let ((index (length (memq ref rx--pcase-vars))))
+ (when (zerop index)
+ (error "rx `backref' variable must be one of: %s"
+ (mapconcat #'symbol-name rx--pcase-vars " ")))
+ `(backref ,index)))
+ ((and `(,head . ,rest)
+ (guard (and (symbolp head)
+ (not (memq head '(literal regexp regex eval))))))
+ (cons head (mapcar #'rx--pcase-transform rest)))
+ (_ rx)))
(pcase-defmacro rx (&rest regexps)
- "Build a `pcase' pattern matching `rx' REGEXPS in sexp form.
-The REGEXPS are interpreted as in `rx'. The pattern matches any
-string that is a match for the regular expression so constructed,
-as if by `string-match'.
+ "A pattern that matches strings against `rx' REGEXPS in sexp form.
+REGEXPS are interpreted as in `rx'. The pattern matches any
+string that is a match for REGEXPS, as if by `string-match'.
-In addition to the usual `rx' constructs, REGEXPS can contain the
+In addition to the usual `rx' syntax, REGEXPS can contain the
following constructs:
- (let REF SEXP...) creates a new explicitly named reference to
- a submatch that matches regular expressions
- SEXP, and binds the match to REF.
- (backref REF) creates a backreference to the submatch
- introduced by a previous (let REF ...)
- construct. REF can be the same symbol
- in the first argument of the corresponding
- (let REF ...) construct, or it can be a
- submatch number. It matches the referenced
- submatch.
-
-The REFs are associated with explicitly named submatches starting
-from 1. Multiple occurrences of the same REF refer to the same
-submatch.
-
-If a case matches, the match data is modified as usual so you can
-use it in the case body, but you still have to pass the correct
-string as argument to `match-string'."
- (let* ((vars ())
- (rx-constituents
- `((let
- ,(lambda (form)
- (rx-check form)
- (let ((var (cadr form)))
- (cl-check-type var symbol)
- (let ((i (or (cl-position var vars :test #'eq)
- (prog1 (length vars)
- (setq vars `(,@vars ,var))))))
- (rx-form `(submatch-n ,(1+ i) ,@(cddr form))))))
- 1 nil)
- (backref
- ,(lambda (form)
- (rx-check form)
- (rx-backref
- `(backref ,(let ((var (cadr form)))
- (if (integerp var) var
- (1+ (cl-position var vars :test #'eq)))))))
- 1 1
- ,(lambda (var)
- (cond ((integerp var) (rx-check-backref var))
- ((memq var vars) t)
- (t (error "rx `backref' variable must be one of %s: %s"
- vars var)))))
- ,@rx-constituents))
- (regexp (rx-to-string `(seq ,@regexps) :no-group)))
+ (let REF RX...) binds the symbol REF to a submatch that matches
+ the regular expressions RX. REF is bound in
+ CODE to the string of the submatch or nil, but
+ can also be used in `backref'.
+ (backref REF) matches whatever the submatch REF matched.
+ REF can be a number, as usual, or a name
+ introduced by a previous (let REF ...)
+ construct."
+ (let* ((rx--pcase-vars nil)
+ (regexp (rx--to-expr (rx--pcase-transform (cons 'seq regexps)))))
`(and (pred (string-match ,regexp))
- ,@(cl-loop for i from 1
- for var in vars
- collect `(app (match-string ,i) ,var)))))
-
+ ,@(let ((i 0))
+ (mapcar (lambda (name)
+ (setq i (1+ i))
+ `(app (match-string ,i) ,name))
+ (reverse rx--pcase-vars))))))
+
+;; Obsolete internal symbol, used in old versions of the `flycheck' package.
+(define-obsolete-function-alias 'rx-submatch-n 'rx-to-string "27.1")
+
(provide 'rx)
;;; rx.el ends here
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index f76409c4de8..3da52418fe4 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -182,6 +182,7 @@ with an old syntax that accepted only one binding."
(setq spec (list spec)))
(list 'if-let* spec then (macroexp-progn else)))
+;;;###autoload
(defmacro when-let (spec &rest body)
"Bind variables according to SPEC and conditionally evaluate BODY.
Evaluate each binding in turn, stopping if a binding value is nil.
@@ -236,7 +237,9 @@ TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\"."
(string-trim-left (string-trim-right string trim-right) trim-left))
(defsubst string-blank-p (string)
- "Check whether STRING is either empty or only whitespace."
+ "Check whether STRING is either empty or only whitespace.
+The following characters count as whitespace here: space, tab, newline and
+carriage return."
(string-match-p "\\`[ \t\n\r]*\\'" string))
(defsubst string-remove-prefix (prefix string)
diff --git a/lisp/emacs-lisp/syntax.el b/lisp/emacs-lisp/syntax.el
index 6464e2a52db..3861b160cc9 100644
--- a/lisp/emacs-lisp/syntax.el
+++ b/lisp/emacs-lisp/syntax.el
@@ -638,21 +638,6 @@ running the hook."
(setq pt (car x)))
min-diffs))
-;; XEmacs compatibility functions
-
-;; (defun buffer-syntactic-context (&optional buffer)
-;; "Syntactic context at point in BUFFER.
-;; Either of `string', `comment' or nil.
-;; This is an XEmacs compatibility function."
-;; (with-current-buffer (or buffer (current-buffer))
-;; (syntax-ppss-context (syntax-ppss))))
-
-;; (defun buffer-syntactic-context-depth (&optional buffer)
-;; "Syntactic parenthesis depth at point in BUFFER.
-;; This is an XEmacs compatibility function."
-;; (with-current-buffer (or buffer (current-buffer))
-;; (syntax-ppss-depth (syntax-ppss))))
-
(provide 'syntax)
;;; syntax.el ends here
diff --git a/lisp/emacs-lisp/tabulated-list.el b/lisp/emacs-lisp/tabulated-list.el
index 63ae1f8c072..ade60285883 100644
--- a/lisp/emacs-lisp/tabulated-list.el
+++ b/lisp/emacs-lisp/tabulated-list.el
@@ -192,6 +192,19 @@ If ADVANCE is non-nil, move forward by one line afterwards."
(if advance
(forward-line)))
+(defun tabulated-list-clear-all-tags ()
+ "Clear all tags from the padding area in the current buffer."
+ (unless (> tabulated-list-padding 0)
+ (error "There can be no tags in current buffer"))
+ (save-excursion
+ (goto-char (point-min))
+ (let ((inhibit-read-only t)
+ ;; Match non-space in the first n characters.
+ (re (format "^ \\{0,%d\\}[^ ]" (1- tabulated-list-padding)))
+ (empty (make-string tabulated-list-padding ? )))
+ (while (re-search-forward re nil 'noerror)
+ (tabulated-list-put-tag empty)))))
+
(defvar tabulated-list-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map (make-composed-keymap
@@ -373,7 +386,7 @@ column. Negate the predicate that would be returned if
(if (stringp b) b (car b)))))))
;; Reversed order.
(if (cdr tabulated-list-sort-key)
- (lambda (a b) (not (funcall sorter a b)))
+ (lambda (a b) (funcall sorter b a))
sorter))))
(defsubst tabulated-list--col-local-max-widths (col)
diff --git a/lisp/emacs-lisp/tcover-ses.el b/lisp/emacs-lisp/tcover-ses.el
index d34d30ff0f9..e18cc58b642 100644
--- a/lisp/emacs-lisp/tcover-ses.el
+++ b/lisp/emacs-lisp/tcover-ses.el
@@ -627,7 +627,7 @@ spreadsheet files with invalid formatting."
(error nil)))))
(defun ses-exercise-startup ()
- "Prepare for coverage tests"
+ "Prepare for coverage tests."
;;Clean up from any previous runs
(condition-case nil (kill-buffer "ses-example.ses") (error nil))
(condition-case nil (kill-buffer "ses-test.ses") (error nil))
diff --git a/lisp/emacs-lisp/testcover.el b/lisp/emacs-lisp/testcover.el
index 20851805f5c..16f19f7d503 100644
--- a/lisp/emacs-lisp/testcover.el
+++ b/lisp/emacs-lisp/testcover.el
@@ -150,12 +150,12 @@ call to one of the `testcover-1value-functions'."
(defface testcover-nohits
'((t (:background "DeepPink2")))
- "Face for forms that had no hits during coverage test"
+ "Face for forms that had no hits during coverage test."
:group 'testcover)
(defface testcover-1value
'((t (:background "Wheat2")))
- "Face for forms that always produced the same value during coverage test"
+ "Face for forms that always produced the same value during coverage test."
:group 'testcover)
diff --git a/lisp/emacs-lisp/text-property-search.el b/lisp/emacs-lisp/text-property-search.el
index 41ca07057e0..a589d0915b9 100644
--- a/lisp/emacs-lisp/text-property-search.el
+++ b/lisp/emacs-lisp/text-property-search.el
@@ -30,29 +30,29 @@
beginning end value)
(defun text-property-search-forward (property &optional value predicate
- not-immediate)
- "Search for the next region that has text property PROPERTY set to VALUE.
-If not found, the return value is nil. If found, point will be
-placed at the end of the region and an object describing the
-match is returned.
-
-PREDICATE is called with two values. The first is the VALUE
-parameter. The second is the value of PROPERTY. This predicate
-should return non-nil if there is a match.
-
-Some convenience values for PREDICATE can also be used. `t'
-means the same as `equal'. `nil' means almost the same as \"not
-equal\", but will also end the match if the value of PROPERTY
-changes. See the manual for extensive examples.
-
-If `not-immediate', if the match is under point, it will not be
-returned, but instead the next instance is returned, if any.
-
-The return value (if a match is made) is a `prop-match'
-structure. The accessors available are
-`prop-match-beginning'/`prop-match-end' (the region in the buffer
-that's matching), and `prop-match-value' (the value of PROPERTY
-at the start of the region)."
+ not-current)
+ "Search for the next region of text whose PROPERTY matches VALUE.
+
+If not found, return nil and don't move point.
+If found, move point to end of the region and return a `prop-match'
+object describing the match. To access the details of the match,
+use `prop-match-beginning' and `prop-match-end' for the buffer
+positions that limit the region, and `prop-match-value' for the
+value of PROPERTY in the region.
+
+PREDICATE is used to decide whether a value of PROPERTY should be
+considered as matching VALUE.
+If PREDICATE is t, that means a value must `equal' VALUE to be
+considered a match.
+If PREDICATE is nil, a value will match if it is non-nil and
+is NOT `equal' to VALUE.
+If PREDICATE is a function, it will be called with two arguments:
+VALUE and the value of PROPERTY. The function should return
+non-nil if these two values are to be considered a match.
+
+If NOT-CURRENT is non-nil, the function will search for the first
+region that doesn't include point and has a value of PROPERTY
+that matches VALUE."
(interactive
(list
(let ((string (completing-read "Search for property: " obarray)))
@@ -66,13 +66,13 @@ at the start of the region)."
;; end.
((and (text-property--match-p value (get-text-property (point) property)
predicate)
- (not not-immediate))
+ (not not-current))
(text-property--find-end-forward (point) property value predicate))
(t
(let ((origin (point))
(ended nil)
pos)
- ;; Fix the next candidate.
+ ;; Find the next candidate.
(while (not ended)
(setq pos (next-single-property-change (point) property))
(if (not pos)
@@ -121,9 +121,11 @@ at the start of the region)."
(defun text-property-search-backward (property &optional value predicate
- not-immediate)
- "Search for the previous region that has text property PROPERTY set to VALUE.
-See `text-property-search-forward' for further documentation."
+ not-current)
+ "Search for the previous region of text whose PROPERTY matches VALUE.
+
+Like `text-property-search-forward', which see, but searches backward,
+and if a matching region is found, moves point to its beginning."
(interactive
(list
(let ((string (completing-read "Search for property: " obarray)))
@@ -138,14 +140,14 @@ See `text-property-search-forward' for further documentation."
((and (text-property--match-p
value (get-text-property (1- (point)) property)
predicate)
- (not not-immediate))
+ (not not-current))
(text-property--find-end-backward (1- (point)) property value predicate))
(t
(let ((origin (point))
(ended nil)
pos)
(forward-char -1)
- ;; Fix the next candidate.
+ ;; Find the previous candidate.
(while (not ended)
(setq pos (previous-single-property-change (point) property))
(if (not pos)