summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp')
-rw-r--r--lisp/emacs-lisp/cl-generic.el21
-rw-r--r--lisp/emacs-lisp/debug.el6
-rw-r--r--lisp/emacs-lisp/edebug.el6
-rw-r--r--lisp/emacs-lisp/ert.el129
-rw-r--r--lisp/emacs-lisp/lisp-mode.el82
-rw-r--r--lisp/emacs-lisp/memory-report.el3
-rw-r--r--lisp/emacs-lisp/subr-x.el42
7 files changed, 254 insertions, 35 deletions
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
index 4834fb13c6a..2c292415cfe 100644
--- a/lisp/emacs-lisp/cl-generic.el
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -100,6 +100,7 @@
(eval-when-compile (require 'cl-lib))
(eval-when-compile (require 'cl-macs)) ;For cl--find-class.
(eval-when-compile (require 'pcase))
+(eval-when-compile (require 'subr-x))
(cl-defstruct (cl--generic-generalizer
(:constructor nil)
@@ -589,19 +590,10 @@ The set of acceptable TYPEs (also called \"specializers\") is defined
;; e.g. for tracing/debug-on-entry.
(defalias sym gfun)))))
-(defmacro cl--generic-with-memoization (place &rest code)
- (declare (indent 1) (debug t))
- (gv-letplace (getter setter) place
- `(or ,getter
- ,(macroexp-let2 nil val (macroexp-progn code)
- `(progn
- ,(funcall setter val)
- ,val)))))
-
(defvar cl--generic-dispatchers (make-hash-table :test #'equal))
(defun cl--generic-get-dispatcher (dispatch)
- (cl--generic-with-memoization
+ (with-memoization
(gethash dispatch cl--generic-dispatchers)
;; (message "cl--generic-get-dispatcher (%S)" dispatch)
(let* ((dispatch-arg (car dispatch))
@@ -644,10 +636,13 @@ The set of acceptable TYPEs (also called \"specializers\") is defined
;; overkill: better just use a `cl-typep' test.
(byte-compile
`(lambda (generic dispatches-left methods)
+ ;; FIXME: We should find a way to expand `with-memoize' once
+ ;; and forall so we don't need `subr-x' when we get here.
+ (eval-when-compile (require 'subr-x))
(let ((method-cache (make-hash-table :test #'eql)))
(lambda (,@fixedargs &rest args)
(let ,bindings
- (apply (cl--generic-with-memoization
+ (apply (with-memoization
(gethash ,tag-exp method-cache)
(cl--generic-cache-miss
generic ',dispatch-arg dispatches-left methods
@@ -691,7 +686,7 @@ for all those different tags in the method-cache.")
;; Special case needed to fix a circularity during bootstrap.
(cl--generic-standard-method-combination generic methods)
(let ((f
- (cl--generic-with-memoization
+ (with-memoization
;; FIXME: Since the fields of `generic' are modified, this
;; hash-table won't work right, because the hashes will change!
;; It's not terribly serious, but reduces the effectiveness of
@@ -1143,7 +1138,7 @@ These match if the argument is a cons cell whose car is `eql' to VAL."
;; since we can't use the `head' specializer to implement itself.
(if (not (eq (car-safe specializer) 'head))
(cl-call-next-method)
- (cl--generic-with-memoization
+ (with-memoization
(gethash (cadr specializer) cl--generic-head-used)
specializer)
(list cl--generic-head-generalizer)))
diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el
index 0592db85df4..163528acf6f 100644
--- a/lisp/emacs-lisp/debug.el
+++ b/lisp/emacs-lisp/debug.el
@@ -701,7 +701,8 @@ To specify a nil argument interactively, exit with an empty minibuffer."
(interactive
(list (let ((name
(completing-read
- "Cancel debug on entry to function (default all functions): "
+ (format-prompt "Cancel debug on entry to function"
+ "all functions")
(mapcar #'symbol-name (debug--function-list)) nil t)))
(when name
(unless (string= name "")
@@ -804,7 +805,8 @@ To specify a nil argument interactively, exit with an empty minibuffer."
(interactive
(list (let ((name
(completing-read
- "Cancel debug on set for variable (default all variables): "
+ (format-prompt "Cancel debug on set for variable"
+ "all variables")
(mapcar #'symbol-name (debug--variable-list)) nil t)))
(when name
(unless (string= name "")
diff --git a/lisp/emacs-lisp/edebug.el b/lisp/emacs-lisp/edebug.el
index 4f3c05baa98..a38c8bd5ca9 100644
--- a/lisp/emacs-lisp/edebug.el
+++ b/lisp/emacs-lisp/edebug.el
@@ -3519,7 +3519,8 @@ The removes the effect of `edebug-on-entry'. If FUNCTION is
nil, remove `edebug-on-entry' on all functions."
(interactive
(list (let ((name (completing-read
- "Cancel edebug on entry to (default all functions): "
+ (format-prompt "Cancel edebug on entry to"
+ "all functions")
(let ((functions (edebug--edebug-on-entry-functions)))
(unless functions
(user-error "No functions have `edebug-on-entry'"))
@@ -4548,7 +4549,8 @@ instrumentation for, defaulting to all functions."
(user-error "Found no functions to remove instrumentation from"))
(let ((name
(completing-read
- "Remove instrumentation from (default all functions): "
+ (format-prompt "Remove instrumentation from"
+ "all functions")
functions)))
(if (and name
(not (equal name "")))
diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el
index 98cb1fd1cf6..21b3fbf98b3 100644
--- a/lisp/emacs-lisp/ert.el
+++ b/lisp/emacs-lisp/ert.el
@@ -63,6 +63,7 @@
(require 'ewoc)
(require 'find-func)
(require 'pp)
+(require 'map)
;;; UI customization options.
@@ -218,11 +219,7 @@ it has to be wrapped in `(eval (quote ...))'.
`(:expected-result-type ,expected-result))
,@(when tags-supplied-p
`(:tags ,tags))
- :body (lambda ()
- ;; Use the value of `lexical-binding' in
- ;; the source file when evaluating the body.
- (let ((lexical-binding ,lexical-binding))
- ,@body))))
+ :body (lambda () ,@body)))
',name))))
(defvar ert--find-test-regexp
@@ -779,7 +776,8 @@ This mainly sets up debugger-related bindings."
;; handle ert errors. Once that's done, remove
;; `ert--should-signal-hook'. See Bug#24402 and Bug#11218 for
;; details.
- (let ((debugger (lambda (&rest args)
+ (let ((lexical-binding t)
+ (debugger (lambda (&rest args)
(ert--run-test-debugger test-execution-info
args)))
(debug-on-error t)
@@ -2661,6 +2659,125 @@ To be used in the ERT results buffer."
'ert--activate-font-lock-keywords)
nil)
+(defun ert-test-erts-file (file &optional transform)
+ "Parse FILE as a file containing before/after parts.
+TRANSFORM will be called to get from before to after."
+ (with-temp-buffer
+ (insert-file-contents file)
+ (let ((gen-specs (list (cons 'dummy t)
+ (cons 'code transform))))
+ ;; The start of the "before" part starts with a form feed and then
+ ;; the name of the test.
+ (while (re-search-forward "^=-=\n" nil t)
+ (setq gen-specs (ert-test--erts-test gen-specs file))))))
+
+(defun ert-test--erts-test (gen-specs file)
+ (let* ((file-buffer (current-buffer))
+ (specs (ert--erts-specifications (match-beginning 0)))
+ (name (cdr (assq 'name specs)))
+ (start-before (point))
+ (end-after (if (re-search-forward "^=-=-=\n" nil t)
+ (match-beginning 0)
+ (point-max)))
+ (skip (cdr (assq 'skip specs)))
+ end-before start-after
+ after after-point)
+ (unless name
+ (error "No name for test case"))
+ (if (and skip
+ (eval (car (read-from-string skip))))
+ ;; Skipping this test.
+ ()
+ ;; Do the test.
+ (goto-char end-after)
+ ;; We have a separate after section.
+ (if (re-search-backward "^=-=\n" start-before t)
+ (setq end-before (match-beginning 0)
+ start-after (match-end 0))
+ (setq end-before end-after
+ start-after start-before))
+ ;; Update persistent specs.
+ (when-let ((point-char (assq 'point-char specs)))
+ (setq gen-specs
+ (map-insert gen-specs 'point-char (cdr point-char))))
+ (when-let ((code (cdr (assq 'code specs))))
+ (setq gen-specs
+ (map-insert gen-specs 'code (car (read-from-string code)))))
+ ;; Get the "after" strings.
+ (with-temp-buffer
+ (insert-buffer-substring file-buffer start-after end-after)
+ (ert--erts-unquote)
+ ;; Remove the newline at the end of the buffer.
+ (when-let ((no-newline (cdr (assq 'no-after-newline specs))))
+ (goto-char (point-min))
+ (when (re-search-forward "\n\\'" nil t)
+ (delete-region (match-beginning 0) (match-end 0))))
+ ;; Get the expected "after" point.
+ (when-let ((point-char (cdr (assq 'point-char gen-specs))))
+ (goto-char (point-min))
+ (when (search-forward point-char nil t)
+ (delete-region (match-beginning 0) (match-end 0))
+ (setq after-point (point))))
+ (setq after (buffer-string)))
+ ;; Do the test.
+ (with-temp-buffer
+ (insert-buffer-substring file-buffer start-before end-before)
+ (ert--erts-unquote)
+ ;; Remove the newline at the end of the buffer.
+ (when-let ((no-newline (cdr (assq 'no-before-newline specs))))
+ (goto-char (point-min))
+ (when (re-search-forward "\n\\'" nil t)
+ (delete-region (match-beginning 0) (match-end 0))))
+ (goto-char (point-min))
+ ;; Place point in the specified place.
+ (when-let ((point-char (cdr (assq 'point-char gen-specs))))
+ (when (search-forward point-char nil t)
+ (delete-region (match-beginning 0) (match-end 0))))
+ (let ((code (cdr (assq 'code gen-specs))))
+ (unless code
+ (error "No code to run the transform"))
+ (funcall code))
+ (unless (equal (buffer-string) after)
+ (ert-fail (list (format "Mismatch in test \"%s\", file %s"
+ name file)
+ (buffer-string)
+ after)))
+ (when (and after-point
+ (not (= after-point (point))))
+ (ert-fail (list (format "Point wrong in test \"%s\", expected point %d, actual %d, file %s"
+ name
+ after-point (point)
+ file)
+ (buffer-string)))))))
+ ;; Return the new value of the general specifications.
+ gen-specs)
+
+(defun ert--erts-unquote ()
+ (goto-char (point-min))
+ (while (re-search-forward "^\\=-=\\(-=\\)$" nil t)
+ (delete-region (match-beginning 0) (1+ (match-beginning 0)))))
+
+(defun ert--erts-specifications (end)
+ "Find specifications before point (back to the previous test)."
+ (save-excursion
+ (goto-char end)
+ (goto-char
+ (if (re-search-backward "^=-=-=\n" nil t)
+ (match-end 0)
+ (point-min)))
+ (let ((specs nil))
+ (while (< (point) end)
+ (if (looking-at "\\([^ \n\t:]+\\):\\([ \t]+\\)?\\(.*\\)")
+ (let ((name (intern (downcase (match-string 1))))
+ (value (match-string 3)))
+ (forward-line 1)
+ (while (looking-at "[ \t]+\\(.*\\)")
+ (setq value (concat value (match-string 1)))
+ (forward-line 1))
+ (push (cons name value) specs))
+ (forward-line 1)))
+ (nreverse specs))))
+
(defvar ert-unload-hook ())
(add-hook 'ert-unload-hook #'ert--unload-function)
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index fc7a7362cd7..b7b8f3a90c4 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -29,6 +29,7 @@
;;; Code:
(eval-when-compile (require 'cl-lib))
+(eval-when-compile (require 'subr-x))
(defvar font-lock-comment-face)
(defvar font-lock-doc-face)
@@ -1105,6 +1106,62 @@ is the buffer position of the start of the containing expression."
(t
normal-indent))))))
+(defun lisp--local-defform-body-p (state)
+ "Return non-nil when at local definition body according to STATE.
+STATE is the `parse-partial-sexp' state for current position."
+ (when-let ((start-of-innermost-containing-list (nth 1 state)))
+ (let* ((parents (nth 9 state))
+ (second-cons-after (cddr parents))
+ second-order-parent)
+ (while second-cons-after
+ (when (= start-of-innermost-containing-list
+ (car second-cons-after))
+ (setq second-order-parent (car parents)
+ ;; Leave the loop.
+ second-cons-after nil))
+ (pop second-cons-after)
+ (pop parents))
+ (when second-order-parent
+ (save-excursion
+ (goto-char (1+ second-order-parent))
+ (and (when-let ((head (ignore-errors
+ ;; FIXME: This does not distinguish
+ ;; between reading nil and a read error.
+ ;; We don't care but still, better fix this.
+ (read (current-buffer)))))
+ (memq head '( cl-flet cl-labels cl-macrolet cl-flet*
+ cl-symbol-macrolet)))
+ ;; Now we must check that we are
+ ;; in the second element of the flet-like form.
+ ;; It would be easier if `parse-partial-sexp' also recorded
+ ;; relative positions of subsexps in supersexps
+ ;; but it doesn't so we check manually.
+ ;;
+ ;; First, we must be looking at list now.
+ (ignore-errors (when (= (scan-lists (point) 1 0)
+ (scan-sexps (point) 1))
+ ;; Looking at list; descend into it:
+ (down-list 1)
+ t))
+ ;; In Wishful Lisp, the following form would be
+ ;; (cl-member start-of-innermost-containing-list
+ ;; (points-at-beginning-of-lists-at-this-level)
+ ;; :test #'=)
+ (cl-loop
+ with pos = (ignore-errors
+ ;; The first local definition may be indented
+ ;; with whitespace following open paren.
+ (goto-char (scan-lists (point) 1 0))
+ (goto-char (scan-lists (point) -1 0))
+ (point))
+ while pos
+ do (if (= start-of-innermost-containing-list pos)
+ (cl-return t)
+ (setq pos (ignore-errors
+ (goto-char (scan-lists (point) 2 0))
+ (goto-char (scan-lists (point) -1 0))
+ (point)))))))))))
+
(defun lisp-indent-function (indent-point state)
"This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine
@@ -1138,16 +1195,19 @@ Lisp function does not specify a special indentation."
(if (and (elt state 2)
(not (looking-at "\\sw\\|\\s_")))
;; car of form doesn't seem to be a symbol
- (progn
+ (if (lisp--local-defform-body-p state)
+ ;; We nevertheless check whether we are in flet-like form
+ ;; as we presume local function names could be non-symbols.
+ (lisp-indent-defform state indent-point)
(if (not (> (save-excursion (forward-line 1) (point))
calculate-lisp-indent-last-sexp))
- (progn (goto-char calculate-lisp-indent-last-sexp)
- (beginning-of-line)
- (parse-partial-sexp (point)
- calculate-lisp-indent-last-sexp 0 t)))
- ;; Indent under the list or under the first sexp on the same
- ;; line as calculate-lisp-indent-last-sexp. Note that first
- ;; thing on that line has to be complete sexp since we are
+ (progn (goto-char calculate-lisp-indent-last-sexp)
+ (beginning-of-line)
+ (parse-partial-sexp (point)
+ calculate-lisp-indent-last-sexp 0 t)))
+ ;; Indent under the list or under the first sexp on the same
+ ;; line as calculate-lisp-indent-last-sexp. Note that first
+ ;; thing on that line has to be complete sexp since we are
;; inside the innermost containing sexp.
(backward-prefix-chars)
(current-column))
@@ -1160,13 +1220,15 @@ Lisp function does not specify a special indentation."
(cond ((or (eq method 'defun)
(and (null method)
(> (length function) 3)
- (string-match "\\`def" function)))
+ (string-match "\\`def" function))
+ ;; Check whether we are in flet-like form.
+ (lisp--local-defform-body-p state))
(lisp-indent-defform state indent-point))
((integerp method)
(lisp-indent-specform method state
indent-point normal-indent))
(method
- (funcall method indent-point state)))))))
+ (funcall method indent-point state)))))))
(defcustom lisp-body-indent 2
"Number of columns to indent the second line of a `(def...)' form."
diff --git a/lisp/emacs-lisp/memory-report.el b/lisp/emacs-lisp/memory-report.el
index 3166d33e029..cda2dee6cbc 100644
--- a/lisp/emacs-lisp/memory-report.el
+++ b/lisp/emacs-lisp/memory-report.el
@@ -31,7 +31,7 @@
(require 'subr-x)
(require 'cl-lib)
-(defvar memory-report--type-size (make-hash-table))
+(defvar memory-report--type-size nil)
;;;###autoload
(defun memory-report ()
@@ -84,6 +84,7 @@ by counted more than once."
(gethash 'object memory-report--type-size)))
(defun memory-report--set-size (elems)
+ (setq memory-report--type-size (make-hash-table))
(setf (gethash 'string memory-report--type-size)
(cadr (assq 'strings elems)))
(setf (gethash 'cons memory-report--type-size)
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index 3de666682fa..8d6bb19fd49 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -208,7 +208,9 @@ The variable list SPEC is the same as in `if-let'."
(string= string ""))
(defsubst string-join (strings &optional separator)
- "Join all STRINGS using SEPARATOR."
+ "Join all STRINGS using SEPARATOR.
+Optional argument SEPARATOR must be a string, a vector, or a list of
+characters; nil stands for the empty string."
(mapconcat #'identity strings separator))
(define-obsolete-function-alias 'string-reverse 'reverse "25.1")
@@ -400,6 +402,44 @@ as the new values of the bound variables in the recursive invocation."
(cl-labels ((,name ,fargs . ,body)) #',name)
. ,aargs)))
+(defmacro with-memoization (place &rest code)
+ "Return the value of CODE and stash it in PLACE.
+If PLACE's value is non-nil, then don't bother evaluating CODE
+and return the value found in PLACE instead."
+ (declare (indent 1) (debug (gv-place body)))
+ (gv-letplace (getter setter) place
+ `(or ,getter
+ ,(macroexp-let2 nil val (macroexp-progn code)
+ `(progn
+ ,(funcall setter val)
+ ,val)))))
+
+;;;###autoload
+(defun ensure-empty-lines (&optional lines)
+ "Ensure that there's LINES number of empty lines before point.
+If LINES is nil or missing, a this ensures that there's a single
+empty line before point.
+
+Interactively, this command uses the numerical prefix for LINES.
+
+If there's already more empty lines before point than LINES, the
+number of blank lines will be reduced.
+
+If point is not at the beginning of a line, a newline character
+is inserted before adjusting the number of empty lines."
+ (interactive "p")
+ (unless (bolp)
+ (insert "\n"))
+ (let ((lines (or lines 1))
+ (start (save-excursion
+ (if (re-search-backward "[^\n]" nil t)
+ (+ (point) 2)
+ (point-min)))))
+ (cond
+ ((> (- (point) start) lines)
+ (delete-region (point) (- (point) (- (point) start lines))))
+ ((< (- (point) start) lines)
+ (insert (make-string (- lines (- (point) start)) ?\n))))))
(provide 'subr-x)