diff options
Diffstat (limited to 'lisp/emacs-lisp/cl-extra.el')
-rw-r--r-- | lisp/emacs-lisp/cl-extra.el | 929 |
1 files changed, 929 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/cl-extra.el b/lisp/emacs-lisp/cl-extra.el new file mode 100644 index 00000000000..7c7f027d777 --- /dev/null +++ b/lisp/emacs-lisp/cl-extra.el @@ -0,0 +1,929 @@ +;;; cl-extra.el --- Common Lisp features, part 2 -*- lexical-binding: t -*- + +;; Copyright (C) 1993, 2000-2022 Free Software Foundation, Inc. + +;; Author: Dave Gillespie <daveg@synaptics.com> +;; Keywords: extensions +;; Package: emacs + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; These are extensions to Emacs Lisp that provide a degree of +;; Common Lisp compatibility, beyond what is already built-in +;; in Emacs Lisp. +;; +;; This package was written by Dave Gillespie; it is a complete +;; rewrite of Cesar Quiroz's original cl.el package of December 1986. +;; +;; Bug reports, comments, and suggestions are welcome! + +;; This file contains portions of the Common Lisp extensions +;; package which are autoloaded since they are relatively obscure. + +;;; Code: + +(require 'cl-lib) +(require 'seq) + +;;; Type coercion. + +;;;###autoload +(defun cl-coerce (x type) + "Coerce OBJECT to type TYPE. +TYPE is a Common Lisp type specifier. +\n(fn OBJECT TYPE)" + (cond ((eq type 'list) (if (listp x) x (append x nil))) + ((eq type 'vector) (if (vectorp x) x (vconcat x))) + ((eq type 'bool-vector) + (if (bool-vector-p x) x (apply #'bool-vector (cl-coerce x 'list)))) + ((eq type 'string) (if (stringp x) x (concat x))) + ((eq type 'array) (if (arrayp x) x (vconcat x))) + ((and (eq type 'character) (stringp x) (= (length x) 1)) (aref x 0)) + ((and (eq type 'character) (symbolp x)) + (cl-coerce (symbol-name x) type)) + ((eq type 'float) (float x)) + ((cl-typep x type) x) + (t (error "Can't coerce %s to type %s" x type)))) + + +;;; Predicates. + +;;;###autoload +(defun cl-equalp (x y) + "Return t if two Lisp objects have similar structures and contents. +This is like `equal', except that it accepts numerically equal +numbers of different types (float vs. integer), and also compares +strings case-insensitively." + (cond ((eq x y) t) + ((stringp x) + (and (stringp y) (string-equal-ignore-case x y))) + ((numberp x) + (and (numberp y) (= x y))) + ((consp x) + (while (and (consp x) (consp y) (cl-equalp (car x) (car y))) + (setq x (cdr x) y (cdr y))) + (and (not (consp x)) (cl-equalp x y))) + ((vectorp x) + (and (vectorp y) (= (length x) (length y)) + (let ((i (length x))) + (while (and (>= (setq i (1- i)) 0) + (cl-equalp (aref x i) (aref y i)))) + (< i 0)))) + (t (equal x y)))) + + +;;; Control structures. + +;;;###autoload +(defun cl--mapcar-many (cl-func cl-seqs &optional acc) + (if (cdr (cdr cl-seqs)) + (let* ((cl-res nil) + (cl-n (apply #'min (mapcar #'length cl-seqs))) + (cl-i 0) + (cl-args (copy-sequence cl-seqs)) + cl-p1 cl-p2) + (setq cl-seqs (copy-sequence cl-seqs)) + (while (< cl-i cl-n) + (setq cl-p1 cl-seqs cl-p2 cl-args) + (while cl-p1 + (setcar cl-p2 + (if (consp (car cl-p1)) + (prog1 (car (car cl-p1)) + (setcar cl-p1 (cdr (car cl-p1)))) + (aref (car cl-p1) cl-i))) + (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2))) + (if acc + (push (apply cl-func cl-args) cl-res) + (apply cl-func cl-args)) + (setq cl-i (1+ cl-i))) + (and acc (nreverse cl-res))) + (let ((cl-res nil) + (cl-x (car cl-seqs)) + (cl-y (nth 1 cl-seqs))) + (let ((cl-n (min (length cl-x) (length cl-y))) + (cl-i -1)) + (while (< (setq cl-i (1+ cl-i)) cl-n) + (let ((val (funcall cl-func + (if (consp cl-x) (pop cl-x) (aref cl-x cl-i)) + (if (consp cl-y) (pop cl-y) (aref cl-y cl-i))))) + (when acc + (push val cl-res))))) + (and acc (nreverse cl-res))))) + +;;;###autoload +(defun cl-map (cl-type cl-func cl-seq &rest cl-rest) + "Map a FUNCTION across one or more SEQUENCEs, returning a sequence. +TYPE is the sequence type to return. +\n(fn TYPE FUNCTION SEQUENCE...)" + (let ((cl-res (apply #'cl-mapcar cl-func cl-seq cl-rest))) + (and cl-type (cl-coerce cl-res cl-type)))) + +;;;###autoload +(defun cl-maplist (cl-func cl-list &rest cl-rest) + "Map FUNCTION to each sublist of LIST or LISTs. +Like `cl-mapcar', except applies to lists and their cdr's rather than to +the elements themselves. +\n(fn FUNCTION LIST...)" + (if cl-rest + (let ((cl-res nil) + (cl-args (cons cl-list (copy-sequence cl-rest))) + cl-p) + (while (not (memq nil cl-args)) + (push (apply cl-func cl-args) cl-res) + (setq cl-p cl-args) + (while cl-p (setcar cl-p (cdr (pop cl-p))))) + (nreverse cl-res)) + (let ((cl-res nil)) + (while cl-list + (push (funcall cl-func cl-list) cl-res) + (setq cl-list (cdr cl-list))) + (nreverse cl-res)))) + +;;;###autoload +(defun cl-mapc (cl-func cl-seq &rest cl-rest) + "Like `cl-mapcar', but does not accumulate values returned by the function. +\n(fn FUNCTION SEQUENCE...)" + (if cl-rest + (if (or (cdr cl-rest) (nlistp cl-seq) (nlistp (car cl-rest))) + (progn + (cl--mapcar-many cl-func (cons cl-seq cl-rest)) + cl-seq) + (let ((cl-x cl-seq) (cl-y (car cl-rest))) + (while (and cl-x cl-y) + (funcall cl-func (pop cl-x) (pop cl-y))) + cl-seq)) + (mapc cl-func cl-seq))) + +;;;###autoload +(defun cl-mapl (cl-func cl-list &rest cl-rest) + "Like `cl-maplist', but does not accumulate values returned by the function. +\n(fn FUNCTION LIST...)" + (if cl-rest + (let ((cl-args (cons cl-list (copy-sequence cl-rest))) + cl-p) + (while (not (memq nil cl-args)) + (apply cl-func cl-args) + (setq cl-p cl-args) + (while cl-p (setcar cl-p (cdr (pop cl-p)))))) + (let ((cl-p cl-list)) + (while cl-p (funcall cl-func cl-p) (setq cl-p (cdr cl-p))))) + cl-list) + +;;;###autoload +(defun cl-mapcan (cl-func cl-seq &rest cl-rest) + "Like `cl-mapcar', but nconc's together the values returned by the function. +\n(fn FUNCTION SEQUENCE...)" + (if cl-rest + (apply #'nconc (apply #'cl-mapcar cl-func cl-seq cl-rest)) + (mapcan cl-func cl-seq))) + +;;;###autoload +(defun cl-mapcon (cl-func cl-list &rest cl-rest) + "Like `cl-maplist', but nconc's together the values returned by the function. +\n(fn FUNCTION LIST...)" + (apply #'nconc (apply #'cl-maplist cl-func cl-list cl-rest))) + +;;;###autoload +(defun cl-some (cl-pred cl-seq &rest cl-rest) + "Say whether PREDICATE is true for any element in the SEQ sequences. +More specifically, the return value of this function will be the +same as the first return value of PREDICATE where PREDICATE has a +non-nil value. + +\n(fn PREDICATE SEQ...)" + (if (or cl-rest (nlistp cl-seq)) + (catch 'cl-some + (apply #'cl-map nil + (lambda (&rest cl-x) + (let ((cl-res (apply cl-pred cl-x))) + (if cl-res (throw 'cl-some cl-res)))) + cl-seq cl-rest) nil) + (let ((cl-x nil)) + (while (and cl-seq (not (setq cl-x (funcall cl-pred (pop cl-seq)))))) + cl-x))) + +;;;###autoload +(defun cl-every (cl-pred cl-seq &rest cl-rest) + "Return true if PREDICATE is true of every element of SEQ or SEQs. +\n(fn PREDICATE SEQ...)" + (if (or cl-rest (nlistp cl-seq)) + (catch 'cl-every + (apply #'cl-map nil + (lambda (&rest cl-x) + (or (apply cl-pred cl-x) (throw 'cl-every nil))) + cl-seq cl-rest) t) + (while (and cl-seq (funcall cl-pred (car cl-seq))) + (setq cl-seq (cdr cl-seq))) + (null cl-seq))) + +;;;###autoload +(defun cl-notany (cl-pred cl-seq &rest cl-rest) + "Return true if PREDICATE is false of every element of SEQ or SEQs. +\n(fn PREDICATE SEQ...)" + (not (apply #'cl-some cl-pred cl-seq cl-rest))) + +;;;###autoload +(defun cl-notevery (cl-pred cl-seq &rest cl-rest) + "Return true if PREDICATE is false of some element of SEQ or SEQs. +\n(fn PREDICATE SEQ...)" + (not (apply #'cl-every cl-pred cl-seq cl-rest))) + +;;;###autoload +(defun cl--map-keymap-recursively (cl-func-rec cl-map &optional cl-base) + (or cl-base + (setq cl-base (copy-sequence [0]))) + (map-keymap + (lambda (cl-key cl-bind) + (aset cl-base (1- (length cl-base)) cl-key) + (if (keymapp cl-bind) + (cl--map-keymap-recursively + cl-func-rec cl-bind + (vconcat cl-base (list 0))) + (funcall cl-func-rec cl-base cl-bind))) + cl-map)) + +;;;###autoload +(defun cl--map-intervals (cl-func &optional cl-what cl-prop cl-start cl-end) + (or cl-what (setq cl-what (current-buffer))) + (if (bufferp cl-what) + (let (cl-mark cl-mark2 (cl-next t) cl-next2) + (with-current-buffer cl-what + (setq cl-mark (copy-marker (or cl-start (point-min)))) + (setq cl-mark2 (and cl-end (copy-marker cl-end)))) + (while (and cl-next (or (not cl-mark2) (< cl-mark cl-mark2))) + (setq cl-next (if cl-prop (next-single-property-change + cl-mark cl-prop cl-what) + (next-property-change cl-mark cl-what)) + cl-next2 (or cl-next (with-current-buffer cl-what + (point-max)))) + (funcall cl-func (prog1 (marker-position cl-mark) + (set-marker cl-mark cl-next2)) + (if cl-mark2 (min cl-next2 cl-mark2) cl-next2))) + (set-marker cl-mark nil) (if cl-mark2 (set-marker cl-mark2 nil))) + (or cl-start (setq cl-start 0)) + (or cl-end (setq cl-end (length cl-what))) + (while (< cl-start cl-end) + (let ((cl-next (or (if cl-prop (next-single-property-change + cl-start cl-prop cl-what) + (next-property-change cl-start cl-what)) + cl-end))) + (funcall cl-func cl-start (min cl-next cl-end)) + (setq cl-start cl-next))))) + +;;;###autoload +(defun cl--map-overlays (cl-func &optional cl-buffer cl-start cl-end cl-arg) + (or cl-buffer (setq cl-buffer (current-buffer))) + (let (cl-ovl) + (with-current-buffer cl-buffer + (setq cl-ovl (overlay-lists)) + (if cl-start (setq cl-start (copy-marker cl-start))) + (if cl-end (setq cl-end (copy-marker cl-end)))) + (setq cl-ovl (nconc (car cl-ovl) (cdr cl-ovl))) + (while (and cl-ovl + (or (not (overlay-start (car cl-ovl))) + (and cl-end (>= (overlay-start (car cl-ovl)) cl-end)) + (and cl-start (<= (overlay-end (car cl-ovl)) cl-start)) + (not (funcall cl-func (car cl-ovl) cl-arg)))) + (setq cl-ovl (cdr cl-ovl))) + (if cl-start (set-marker cl-start nil)) + (if cl-end (set-marker cl-end nil)))) + +;;; Support for `setf'. +;;;###autoload +(defun cl--set-frame-visible-p (frame val) + (cond ((null val) (make-frame-invisible frame)) + ((eq val 'icon) (iconify-frame frame)) + (t (make-frame-visible frame))) + val) + + +;;; Numbers. + +;;;###autoload +(defun cl-gcd (&rest args) + "Return the greatest common divisor of the arguments." + (let ((a (or (pop args) 0))) + (dolist (b args) + (while (/= b 0) + (setq b (% a (setq a b))))) + (abs a))) + +;;;###autoload +(defun cl-lcm (&rest args) + "Return the least common multiple of the arguments." + (if (memq 0 args) + 0 + (let ((a (or (pop args) 1))) + (dolist (b args) + (setq a (* (/ a (cl-gcd a b)) b))) + (abs a)))) + +;;;###autoload +(defun cl-isqrt (x) + "Return the integer square root of the (integer) argument X." + (if (and (integerp x) (> x 0)) + (let ((g (ash 2 (/ (logb x) 2))) + g2) + (while (< (setq g2 (/ (+ g (/ x g)) 2)) g) + (setq g g2)) + g) + (if (eq x 0) 0 (signal 'arith-error nil)))) + +;;;###autoload +(defun cl-floor (x &optional y) + "Return a list of the floor of X and the fractional part of X. +With two arguments, return floor and remainder of their quotient." + (let ((q (floor x y))) + (list q (- x (if y (* y q) q))))) + +;;;###autoload +(defun cl-ceiling (x &optional y) + "Return a list of the ceiling of X and the fractional part of X. +With two arguments, return ceiling and remainder of their quotient." + (let ((res (cl-floor x y))) + (if (= (car (cdr res)) 0) res + (list (1+ (car res)) (- (car (cdr res)) (or y 1)))))) + +;;;###autoload +(defun cl-truncate (x &optional y) + "Return a list of the integer part of X and the fractional part of X. +With two arguments, return truncation and remainder of their quotient." + (if (eq (>= x 0) (or (null y) (>= y 0))) + (cl-floor x y) (cl-ceiling x y))) + +;;;###autoload +(defun cl-round (x &optional y) + "Return a list of X rounded to the nearest integer and the remainder. +With two arguments, return rounding and remainder of their quotient." + (if y + (if (and (integerp x) (integerp y)) + (let* ((hy (/ y 2)) + (res (cl-floor (+ x hy) y))) + (if (and (= (car (cdr res)) 0) + (= (+ hy hy) y) + (/= (% (car res) 2) 0)) + (list (1- (car res)) hy) + (list (car res) (- (car (cdr res)) hy)))) + (let ((q (round (/ x y)))) + (list q (- x (* q y))))) + (if (integerp x) (list x 0) + (let ((q (round x))) + (list q (- x q)))))) + +;;;###autoload +(defun cl-mod (x y) + "The remainder of X divided by Y, with the same sign as Y." + (nth 1 (cl-floor x y))) + +;;;###autoload +(defun cl-rem (x y) + "The remainder of X divided by Y, with the same sign as X." + (nth 1 (cl-truncate x y))) + +;;;###autoload +(defun cl-signum (x) + "Return 1 if X is positive, -1 if negative, 0 if zero." + (cond ((> x 0) 1) ((< x 0) -1) (t 0))) + +;;;###autoload +(cl-defun cl-parse-integer (string &key start end radix junk-allowed) + "Parse integer from the substring of STRING from START to END. +STRING may be surrounded by whitespace chars (chars with syntax ` '). +Other non-digit chars are considered junk. +RADIX is an integer between 2 and 36, the default is 10. Signal +an error if the substring between START and END cannot be parsed +as an integer unless JUNK-ALLOWED is non-nil." + (cl-check-type string string) + (let* ((start (or start 0)) + (len (length string)) + (end (or end len)) + (radix (or radix 10))) + (or (<= start end len) + (error "Bad interval: [%d, %d)" start end)) + (cl-flet ((skip-whitespace () + (while (and (< start end) + (= 32 (char-syntax (aref string start)))) + (setq start (1+ start))))) + (skip-whitespace) + (let ((sign (cl-case (and (< start end) (aref string start)) + (?+ (cl-incf start) +1) + (?- (cl-incf start) -1) + (t +1))) + digit sum) + (while (and (< start end) + (setq digit (cl-digit-char-p (aref string start) radix))) + (setq sum (+ (* (or sum 0) radix) digit) + start (1+ start))) + (skip-whitespace) + (cond ((and junk-allowed (null sum)) sum) + (junk-allowed (* sign sum)) + ((or (/= start end) (null sum)) + (error "Not an integer string: `%s'" string)) + (t (* sign sum))))))) + + +;; Random numbers. + +(defun cl--random-time () + (car (time-convert nil t))) + +;;;###autoload (autoload 'cl-random-state-p "cl-extra") +(cl-defstruct (cl--random-state + (:copier nil) + (:predicate cl-random-state-p) + (:constructor nil) + (:constructor cl--make-random-state (vec))) + (i -1) (j 30) vec) + +(defvar cl--random-state (cl--make-random-state (cl--random-time))) + +;;;###autoload +(defun cl-random (lim &optional state) + "Return a pseudo-random nonnegative number less than LIM, an integer or float. +Optional second arg STATE is a random-state object." + (or state (setq state cl--random-state)) + ;; Inspired by "ran3" from Numerical Recipes. Additive congruential method. + (let ((vec (cl--random-state-vec state))) + (if (integerp vec) + (let ((i 0) (j (- 1357335 (abs (% vec 1357333)))) (k 1)) + (setf (cl--random-state-vec state) + (setq vec (make-vector 55 nil))) + (aset vec 0 j) + (while (> (setq i (% (+ i 21) 55)) 0) + (aset vec i (setq j (prog1 k (setq k (- j k)))))) + (while (< (setq i (1+ i)) 200) (cl-random 2 state)))) + (let* ((i (cl-callf (lambda (x) (% (1+ x) 55)) (cl--random-state-i state))) + (j (cl-callf (lambda (x) (% (1+ x) 55)) (cl--random-state-j state))) + (n (aset vec i (logand 8388607 (- (aref vec i) (aref vec j)))))) + (if (integerp lim) + (if (<= lim 512) (% n lim) + (if (> lim 8388607) (setq n (+ (ash n 9) (cl-random 512 state)))) + (let ((mask 1023)) + (while (< mask (1- lim)) (setq mask (1+ (+ mask mask)))) + (if (< (setq n (logand n mask)) lim) n (cl-random lim state)))) + (* (/ n '8388608e0) lim))))) + +;;;###autoload +(defun cl-make-random-state (&optional state) + "Return a copy of random-state STATE, or of the internal state if omitted. +If STATE is t, return a new state object seeded from the time of day." + (unless state (setq state cl--random-state)) + (if (cl-random-state-p state) + (copy-sequence state) + (cl--make-random-state (if (integerp state) state (cl--random-time))))) + +;; Implementation limits. + +(defun cl--finite-do (func a b) + (condition-case _ + (let ((res (funcall func a b))) ; check for IEEE infinity + (and (numberp res) (/= res (/ res 2)) res)) + (arith-error nil))) + +;;;###autoload +(defun cl-float-limits () + "Initialize the Common Lisp floating-point parameters. +This sets the values of: `cl-most-positive-float', `cl-most-negative-float', +`cl-least-positive-float', `cl-least-negative-float', `cl-float-epsilon', +`cl-float-negative-epsilon', `cl-least-positive-normalized-float', and +`cl-least-negative-normalized-float'." + (or cl-most-positive-float (not (numberp '2e1)) + (let ((x '2e0) y z) + ;; Find maximum exponent (first two loops are optimizations) + (while (cl--finite-do '* x x) (setq x (* x x))) + (while (cl--finite-do '* x (/ x 2)) (setq x (* x (/ x 2)))) + (while (cl--finite-do '+ x x) (setq x (+ x x))) + (setq z x y (/ x 2)) + ;; Now cl-fill in 1's in the mantissa. + (while (and (cl--finite-do '+ x y) (/= (+ x y) x)) + (setq x (+ x y) y (/ y 2))) + (setq cl-most-positive-float x + cl-most-negative-float (- x)) + ;; Divide down until mantissa starts rounding. + (setq x (/ x z) y (/ 16 z) x (* x y)) + (while (condition-case _ (and (= x (* (/ x 2) 2)) (> (/ y 2) 0)) + (arith-error nil)) + (setq x (/ x 2) y (/ y 2))) + (setq cl-least-positive-normalized-float y + cl-least-negative-normalized-float (- y)) + ;; Divide down until value underflows to zero. + (setq x (/ z) y x) + (while (condition-case _ (> (/ x 2) 0) (arith-error nil)) + (setq x (/ x 2))) + (setq cl-least-positive-float x + cl-least-negative-float (- x)) + (setq x '1e0) + (while (/= (+ '1e0 x) '1e0) (setq x (/ x 2))) + (setq cl-float-epsilon (* x 2)) + (setq x '1e0) + (while (/= (- '1e0 x) '1e0) (setq x (/ x 2))) + (setq cl-float-negative-epsilon (* x 2)))) + nil) + + +;;; Sequence functions. + +;;;###autoload +(defun cl-subseq (seq start &optional end) + "Return the subsequence of SEQ from START to END. +If END is omitted, it defaults to the length of the sequence. +If START or END is negative, it counts from the end. +Signal an error if START or END are outside of the sequence (i.e +too large if positive or too small if negative)." + (declare (gv-setter + (lambda (new) + (macroexp-let2 nil new new + `(progn (cl-replace ,seq ,new :start1 ,start :end1 ,end) + ,new))))) + (seq-subseq seq start end)) + +;;; This isn't a defalias because autoloading defalises doesn't work +;;; very well. + +;;;###autoload +(defun cl-concatenate (type &rest sequences) + "Concatenate, into a sequence of type TYPE, the argument SEQUENCEs. +\n(fn TYPE SEQUENCE...)" + (apply #'seq-concatenate type sequences)) + +;;; List functions. + +;;;###autoload +(defun cl-revappend (x y) + "Equivalent to (append (reverse X) Y)." + (nconc (reverse x) y)) + +;;;###autoload +(defun cl-nreconc (x y) + "Equivalent to (nconc (nreverse X) Y)." + (nconc (nreverse x) y)) + +;;;###autoload +(defun cl-list-length (x) + "Return the length of list X. Return nil if list is circular." + (cl-check-type x list) + (condition-case nil + (length x) + (circular-list))) + +;;;###autoload +(defun cl-tailp (sublist list) + "Return true if SUBLIST is a tail of LIST." + (while (and (consp list) (not (eq sublist list))) + (setq list (cdr list))) + (if (numberp sublist) (equal sublist list) (eq sublist list))) + +;;; Property lists. + +;;;###autoload +(defun cl-get (sym tag &optional def) + "Return the value of SYMBOL's PROPNAME property, or DEFAULT if none. +\n(fn SYMBOL PROPNAME &optional DEFAULT)" + (declare (compiler-macro cl--compiler-macro-get) + (gv-setter (lambda (store) (ignore def) `(put ,sym ,tag ,store)))) + (cl-getf (symbol-plist sym) tag def)) +(autoload 'cl--compiler-macro-get "cl-macs") + +;;;###autoload +(defun cl-getf (plist tag &optional def) + "Search PROPLIST for property PROPNAME; return its value or DEFAULT. +PROPLIST is a list of the sort returned by `symbol-plist'. +\n(fn PROPLIST PROPNAME &optional DEFAULT)" + (declare (gv-expander + (lambda (do) + (gv-letplace (getter setter) plist + (macroexp-let2* nil ((k tag) (d def)) + (funcall do `(cl-getf ,getter ,k ,d) + (lambda (v) + (macroexp-let2 nil val v + `(progn + ,(funcall setter + `(cl--set-getf ,getter ,k ,val)) + ,val))))))))) + (let ((val-tail (cdr-safe (plist-member plist tag)))) + (if val-tail (car val-tail) def))) + +;;;###autoload +(defun cl--set-getf (plist tag val) + (let ((val-tail (cdr-safe (plist-member plist tag)))) + (if val-tail (progn (setcar val-tail val) plist) + (cl-list* tag val plist)))) + +;;;###autoload +(defun cl--do-remf (plist tag) + (let ((p (cdr plist))) + ;; Can't use `plist-member' here because it goes to the cons-cell + ;; of TAG and we need the one before. + (while (and (cdr p) (not (eq (car (cdr p)) tag))) (setq p (cdr (cdr p)))) + (and (cdr p) (progn (setcdr p (cdr (cdr (cdr p)))) t)))) + +;;;###autoload +(defun cl-remprop (sym tag) + "Remove from SYMBOL's plist the property PROPNAME and its value. +\n(fn SYMBOL PROPNAME)" + (let ((plist (symbol-plist sym))) + (if (and plist (eq tag (car plist))) + (progn (setplist sym (cdr (cdr plist))) t) + (cl--do-remf plist tag)))) + +;;; Streams. + +;;;###autoload +(defun cl-fresh-line (&optional stream) + "Output a newline unless already at the beginning of a line." + (terpri stream 'ensure)) + +;;; Some debugging aids. + +(defun cl-prettyprint (form) + "Insert a pretty-printed rendition of a Lisp FORM in current buffer." + (let ((pt (point)) last) + (insert "\n" (prin1-to-string form) "\n") + (setq last (point)) + (goto-char (1+ pt)) + (while (search-forward "(quote " last t) + (delete-char -7) + (insert "'") + (forward-sexp) + (delete-char 1)) + (goto-char (1+ pt)) + (cl--do-prettyprint))) + +(defun cl--do-prettyprint () + (skip-chars-forward " ") + (if (looking-at "(") + (let ((skip (or (looking-at "((") (looking-at "(prog") + (looking-at "(unwind-protect ") + (looking-at "(function (") + (looking-at "(cl--block-wrapper "))) + (two (or (looking-at "(defun ") (looking-at "(defmacro "))) + (let (or (looking-at "(let\\*? ") (looking-at "(while "))) + (set (looking-at "(p?set[qf] "))) + (if (or skip let + (progn + (forward-sexp) + (and (>= (current-column) 78) (progn (backward-sexp) t)))) + (let ((nl t)) + (forward-char 1) + (cl--do-prettyprint) + (or skip (looking-at ")") (cl--do-prettyprint)) + (or (not two) (looking-at ")") (cl--do-prettyprint)) + (while (not (looking-at ")")) + (if set (setq nl (not nl))) + (if nl (insert "\n")) + (lisp-indent-line) + (cl--do-prettyprint)) + (forward-char 1)))) + (forward-sexp))) + +;;;###autoload +(defun cl-prettyexpand (form &optional _full) + "Expand macros in FORM and insert the pretty-printed result." + (declare (advertised-calling-convention (form) "27.1")) + (message "Expanding...") + (setq form (macroexpand-all form)) + (message "Formatting...") + (prog1 + (cl-prettyprint form) + (message ""))) + +;;; Integration into the online help system. + +(eval-when-compile (require 'cl-macs)) ;Explicitly, for cl--find-class. +(require 'help-mode) + +;; FIXME: We could go crazy and add another entry so describe-symbol can be +;; used with the slot names of CL structs (and/or EIEIO objects). +(add-to-list 'describe-symbol-backends + `(nil ,#'cl-find-class ,(lambda (s _b _f) (cl-describe-type s)))) + +(defconst cl--typedef-regexp + (concat "(" (regexp-opt '("defclass" "defstruct" "cl-defstruct" + "cl-deftype" "deftype")) + "[ \t\r\n]+%s[ \t\r\n]+")) +(with-eval-after-load 'find-func + (defvar find-function-regexp-alist) + (add-to-list 'find-function-regexp-alist + '(define-type . cl--typedef-regexp))) + +(define-button-type 'cl-help-type + :supertype 'help-function-def + 'help-function #'cl-describe-type + 'help-echo (purecopy "mouse-2, RET: describe this type")) + +(define-button-type 'cl-type-definition + :supertype 'help-function-def + 'help-echo (purecopy "mouse-2, RET: find type definition")) + +(declare-function help-fns-short-filename "help-fns" (filename)) + +;;;###autoload +(defun cl-find-class (type) (cl--find-class type)) + +;;;###autoload +(defun cl-describe-type (type) + "Display the documentation for type TYPE (a symbol)." + (interactive + (let ((str (completing-read "Describe type: " obarray #'cl-find-class t))) + (if (<= (length str) 0) + (user-error "Abort!") + (list (intern str))))) + (help-setup-xref (list #'cl-describe-type type) + (called-interactively-p 'interactive)) + (save-excursion + (with-help-window (help-buffer) + (with-current-buffer standard-output + (let ((class (cl-find-class type))) + (if class + (cl--describe-class type class) + ;; FIXME: Describe other types (the built-in ones, or those from + ;; cl-deftype). + (user-error "Unknown type %S" type)))) + (with-current-buffer standard-output + ;; Return the text we displayed. + (buffer-string))))) + +(defun cl--describe-class (type &optional class) + (unless class (setq class (cl--find-class type))) + (let ((location (find-lisp-object-file-name type 'define-type)) + (metatype (type-of class))) + (insert (symbol-name type) + (substitute-command-keys " is a type (of kind `")) + (help-insert-xref-button (symbol-name metatype) + 'cl-help-type metatype) + (insert (substitute-command-keys "')")) + (when location + (insert (substitute-command-keys " in `")) + (help-insert-xref-button + (help-fns-short-filename location) + 'cl-type-definition type location 'define-type) + (insert (substitute-quotes "'"))) + (insert ".\n") + + ;; Parents. + (let ((pl (cl--class-parents class)) + cur) + (when pl + (insert " Inherits from ") + (while (setq cur (pop pl)) + (setq cur (cl--class-name cur)) + (insert (substitute-quotes "`")) + (help-insert-xref-button (symbol-name cur) + 'cl-help-type cur) + (insert (substitute-command-keys (if pl "', " "'")))) + (insert ".\n"))) + + ;; Children, if available. ¡For EIEIO! + (let ((ch (condition-case nil + (cl-struct-slot-value metatype 'children class) + (cl-struct-unknown-slot nil))) + cur) + (when ch + (insert " Children ") + (while (setq cur (pop ch)) + (insert (substitute-quotes "`")) + (help-insert-xref-button (symbol-name cur) + 'cl-help-type cur) + (insert (substitute-command-keys (if ch "', " "'")))) + (insert ".\n"))) + + ;; Type's documentation. + (let ((doc (cl--class-docstring class))) + (when doc + (insert "\n" doc "\n\n"))) + + ;; Describe all the slots in this class. + (cl--describe-class-slots class) + + ;; Describe all the methods specific to this class. + (let ((generics (cl-generic-all-functions type))) + (when generics + (insert (propertize "Specialized Methods:\n\n" 'face 'bold)) + (dolist (generic generics) + (insert (substitute-quotes "`")) + (help-insert-xref-button (symbol-name generic) + 'help-function generic) + (insert (substitute-quotes "'")) + (pcase-dolist (`(,qualifiers ,args ,doc) + (cl--generic-method-documentation generic type)) + (insert (format " %s%S\n" qualifiers args) + (or doc ""))) + (insert "\n\n")))))) + +(defun cl--describe-class-slot (slot) + (insert + (concat + (propertize "Slot: " 'face 'bold) + (prin1-to-string (cl--slot-descriptor-name slot)) + (unless (eq (cl--slot-descriptor-type slot) t) + (concat " type = " + (prin1-to-string (cl--slot-descriptor-type slot)))) + ;; FIXME: The default init form is treated differently for structs and for + ;; eieio objects: for structs, the default is nil, for eieio-objects + ;; it's a special "unbound" value. + (unless nil ;; (eq (cl--slot-descriptor-initform slot) eieio-unbound) + (concat " default = " + (prin1-to-string (cl--slot-descriptor-initform slot)))) + (when (alist-get :printer (cl--slot-descriptor-props slot)) + (concat " printer = " + (prin1-to-string + (alist-get :printer (cl--slot-descriptor-props slot))))) + (when (alist-get :documentation (cl--slot-descriptor-props slot)) + (concat "\n " + (substitute-command-keys + (alist-get :documentation (cl--slot-descriptor-props slot))) + "\n"))) + "\n")) + +(defun cl--print-table (header rows &optional last-slot-on-next-line) + ;; FIXME: Isn't this functionality already implemented elsewhere? + (let ((cols (apply #'vector (mapcar #'string-width header))) + (col-space 2)) + (dolist (row rows) + (dotimes (i (length cols)) + (let* ((x (pop row)) + (curwidth (aref cols i)) + (newwidth (if x (string-width x) 0))) + (if (> newwidth curwidth) + (setf (aref cols i) newwidth))))) + (let ((formats '()) + (col 0)) + (dotimes (i (length cols)) + (push (concat (propertize " " + 'display + `(space :align-to ,(+ col col-space))) + "%s") + formats) + (cl-incf col (+ col-space (aref cols i)))) + (let ((format (mapconcat #'identity (nreverse formats) ""))) + (insert (apply #'format format + (mapcar (lambda (str) (propertize str 'face 'italic)) + header)) + "\n") + (insert (apply #'format format + (mapcar (lambda (str) (make-string (string-width str) ?—)) + header)) + "\n") + (dolist (row rows) + (insert (apply #'format format row) "\n") + (when last-slot-on-next-line + (dolist (line (string-lines (car (last row)))) + (insert " " line "\n")) + (insert "\n"))))))) + +(defun cl--describe-class-slots (class) + "Print help description for the slots in CLASS. +Outputs to the current buffer." + (let* ((slots (cl--class-slots class)) + (metatype (type-of class)) + ;; ¡For EIEIO! + (cslots (condition-case nil + (cl-struct-slot-value metatype 'class-slots class) + (cl-struct-unknown-slot nil)))) + (insert (propertize "Instance Allocated Slots:\n\n" + 'face 'bold)) + (let* ((has-doc nil) + (slots-strings + (mapcar + (lambda (slot) + (list (cl-prin1-to-string (cl--slot-descriptor-name slot)) + (cl-prin1-to-string (cl--slot-descriptor-type slot)) + (cl-prin1-to-string (cl--slot-descriptor-initform slot)) + (let ((doc (alist-get :documentation + (cl--slot-descriptor-props slot)))) + (if (not doc) "" + (setq has-doc t) + (substitute-command-keys doc))))) + slots))) + (cl--print-table `("Name" "Type" "Default") slots-strings has-doc)) + (insert "\n") + (when (> (length cslots) 0) + (insert (propertize "\nClass Allocated Slots:\n\n" 'face 'bold)) + (mapc #'cl--describe-class-slot cslots)))) + + +(make-obsolete-variable 'cl-extra-load-hook + "use `with-eval-after-load' instead." "28.1") +(run-hooks 'cl-extra-load-hook) + +;; Local variables: +;; generated-autoload-file: "cl-loaddefs.el" +;; End: + +(provide 'cl-extra) +;;; cl-extra.el ends here |