aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2023-11-07 18:57:03 -0500
committerStefan Monnier2023-11-11 11:51:59 -0500
commitfbb897b7af53cdb43e18322c5cdfbfef7cdda1ee (patch)
treeff0f112ad184b3ac86e000e4d3c5badac851622d
parent8323394bc801e01dedd95e0ff8d573dd1f5e34ba (diff)
downloademacs-fbb897b7af53cdb43e18322c5cdfbfef7cdda1ee.tar.gz
emacs-fbb897b7af53cdb43e18322c5cdfbfef7cdda1ee.zip
Move EIEIO's C3 linearization code to `subr.el`
The code was used to linearize the EIEIO class hierarchy, since it results in saner results than things like BFS or DFS. By moving it to `subr.el` we get to benefit from that same advantage both in `cl--class-allparents` and in `derived-mode-all-parents`. * lisp/subr.el (merge-ordered-lists): New function. (derived-mode-all-parents): Use it to improve parent ordering. * lisp/emacs-lisp/eieio-core.el (eieio--c3-candidate) (eieio--c3-merge-lists): Delete functions, replaced by `merge-ordered-lists`. (eieio--class-precedence-c3): Use `merge-ordered-lists`. * lisp/emacs-lisp/cl-preloaded.el (cl--class-allparents): Use `merge-ordered-lists` to improve parent ordering. * lisp/emacs-lisp/cl-macs.el (cl--struct-all-parents): Delete function. (cl--pcase-mutually-exclusive-p): Use `cl--class-allparents` instead.
-rw-r--r--lisp/emacs-lisp/cl-macs.el17
-rw-r--r--lisp/emacs-lisp/cl-preloaded.el12
-rw-r--r--lisp/emacs-lisp/eieio-core.el61
-rw-r--r--lisp/simple.el2
-rw-r--r--lisp/subr.el74
5 files changed, 77 insertions, 89 deletions
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index e2c13534054..2431e658368 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -3337,19 +3337,6 @@ To see the documentation for a defined struct type, use
3337 3337
3338;;; Add cl-struct support to pcase 3338;;; Add cl-struct support to pcase
3339 3339
3340;;In use by comp.el
3341(defun cl--struct-all-parents (class) ;FIXME: Merge with `cl--class-allparents'
3342 (when (cl--struct-class-p class)
3343 (let ((res ())
3344 (classes (list class)))
3345 ;; BFS precedence.
3346 (while (let ((class (pop classes)))
3347 (push class res)
3348 (setq classes
3349 (append classes
3350 (cl--class-parents class)))))
3351 (nreverse res))))
3352
3353;;;###autoload 3340;;;###autoload
3354(pcase-defmacro cl-struct (type &rest fields) 3341(pcase-defmacro cl-struct (type &rest fields)
3355 "Pcase patterns that match cl-struct EXPVAL of type TYPE. 3342 "Pcase patterns that match cl-struct EXPVAL of type TYPE.
@@ -3395,8 +3382,8 @@ the form NAME which is a shorthand for (NAME NAME)."
3395 (let ((c1 (cl--find-class t1)) 3382 (let ((c1 (cl--find-class t1))
3396 (c2 (cl--find-class t2))) 3383 (c2 (cl--find-class t2)))
3397 (and c1 c2 3384 (and c1 c2
3398 (not (or (memq c1 (cl--struct-all-parents c2)) 3385 (not (or (memq t1 (cl--class-allparents c2))
3399 (memq c2 (cl--struct-all-parents c1))))))) 3386 (memq t2 (cl--class-allparents c1)))))))
3400 (let ((c1 (and (symbolp t1) (cl--find-class t1)))) 3387 (let ((c1 (and (symbolp t1) (cl--find-class t1))))
3401 (and c1 (cl--struct-class-p c1) 3388 (and c1 (cl--struct-class-p c1)
3402 (funcall orig (cl--defstruct-predicate t1) 3389 (funcall orig (cl--defstruct-predicate t1)
diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el
index 03068639575..3d0c2b54785 100644
--- a/lisp/emacs-lisp/cl-preloaded.el
+++ b/lisp/emacs-lisp/cl-preloaded.el
@@ -323,15 +323,9 @@ supertypes from the most specific to least specific.")
323(cl-assert (cl--class-p (cl--find-class 'cl-structure-object))) 323(cl-assert (cl--class-p (cl--find-class 'cl-structure-object)))
324 324
325(defun cl--class-allparents (class) 325(defun cl--class-allparents (class)
326 (let ((parents ()) 326 (cons (cl--class-name class)
327 (classes (list class))) 327 (merge-ordered-lists (mapcar #'cl--class-allparents
328 ;; BFS precedence. FIXME: Use a topological sort. 328 (cl--class-parents class)))))
329 (while (let ((class (pop classes)))
330 (cl-pushnew (cl--class-name class) parents)
331 (setq classes
332 (append classes
333 (cl--class-parents class)))))
334 (nreverse parents)))
335 329
336(eval-and-compile 330(eval-and-compile
337 (cl-assert (null (cl--class-parents (cl--find-class 'cl-structure-object))))) 331 (cl-assert (null (cl--class-parents (cl--find-class 'cl-structure-object)))))
diff --git a/lisp/emacs-lisp/eieio-core.el b/lisp/emacs-lisp/eieio-core.el
index f5ff04ff372..a394156c93a 100644
--- a/lisp/emacs-lisp/eieio-core.el
+++ b/lisp/emacs-lisp/eieio-core.el
@@ -964,49 +964,6 @@ need be... May remove that later...)"
964 (cdr tuple) 964 (cdr tuple)
965 nil))) 965 nil)))
966 966
967;;;
968;; Method Invocation order: C3
969(defun eieio--c3-candidate (class remaining-inputs)
970 "Return CLASS if it can go in the result now, otherwise nil."
971 ;; Ensure CLASS is not in any position but the first in any of the
972 ;; element lists of REMAINING-INPUTS.
973 (and (not (let ((found nil))
974 (while (and remaining-inputs (not found))
975 (setq found (member class (cdr (car remaining-inputs)))
976 remaining-inputs (cdr remaining-inputs)))
977 found))
978 class))
979
980(defun eieio--c3-merge-lists (reversed-partial-result remaining-inputs)
981 "Try to merge REVERSED-PARTIAL-RESULT REMAINING-INPUTS in a consistent order.
982If a consistent order does not exist, signal an error."
983 (setq remaining-inputs (delq nil remaining-inputs))
984 (if (null remaining-inputs)
985 ;; If all remaining inputs are empty lists, we are done.
986 (nreverse reversed-partial-result)
987 ;; Otherwise, we try to find the next element of the result. This
988 ;; is achieved by considering the first element of each
989 ;; (non-empty) input list and accepting a candidate if it is
990 ;; consistent with the rests of the input lists.
991 (let* ((found nil)
992 (tail remaining-inputs)
993 (next (progn
994 (while (and tail (not found))
995 (setq found (eieio--c3-candidate (caar tail)
996 remaining-inputs)
997 tail (cdr tail)))
998 found)))
999 (if next
1000 ;; The graph is consistent so far, add NEXT to result and
1001 ;; merge input lists, dropping NEXT from their heads where
1002 ;; applicable.
1003 (eieio--c3-merge-lists
1004 (cons next reversed-partial-result)
1005 (mapcar (lambda (l) (if (eq (cl-first l) next) (cl-rest l) l))
1006 remaining-inputs))
1007 ;; The graph is inconsistent, give up
1008 (signal 'inconsistent-class-hierarchy (list remaining-inputs))))))
1009
1010(defsubst eieio--class/struct-parents (class) 967(defsubst eieio--class/struct-parents (class)
1011 (or (eieio--class-parents class) 968 (or (eieio--class-parents class)
1012 `(,eieio-default-superclass))) 969 `(,eieio-default-superclass)))
@@ -1014,14 +971,16 @@ If a consistent order does not exist, signal an error."
1014(defun eieio--class-precedence-c3 (class) 971(defun eieio--class-precedence-c3 (class)
1015 "Return all parents of CLASS in c3 order." 972 "Return all parents of CLASS in c3 order."
1016 (let ((parents (eieio--class-parents class))) 973 (let ((parents (eieio--class-parents class)))
1017 (eieio--c3-merge-lists 974 (cons class
1018 (list class) 975 (merge-ordered-lists
1019 (append 976 (append
1020 (or 977 (or
1021 (mapcar #'eieio--class-precedence-c3 parents) 978 (mapcar #'eieio--class-precedence-c3 parents)
1022 `((,eieio-default-superclass))) 979 `((,eieio-default-superclass)))
1023 (list parents)))) 980 (list parents))
1024 ) 981 (lambda (remaining-inputs)
982 (signal 'inconsistent-class-hierarchy
983 (list remaining-inputs)))))))
1025;;; 984;;;
1026;; Method Invocation Order: Depth First 985;; Method Invocation Order: Depth First
1027 986
diff --git a/lisp/simple.el b/lisp/simple.el
index 266a66500cb..f79f1013669 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -1029,7 +1029,7 @@ that if you use overwrite mode as your normal editing mode, you can use
1029this function to insert characters when necessary. 1029this function to insert characters when necessary.
1030 1030
1031In binary overwrite mode, this function does overwrite, and octal 1031In binary overwrite mode, this function does overwrite, and octal
1032(or decimal or hex) digits are interpreted as a character code. This 1032\(or decimal or hex) digits are interpreted as a character code. This
1033is intended to be useful for editing binary files." 1033is intended to be useful for editing binary files."
1034 (interactive "*p") 1034 (interactive "*p")
1035 (let* ((char 1035 (let* ((char
diff --git a/lisp/subr.el b/lisp/subr.el
index b000787a5d6..75614f3c674 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -2678,16 +2678,68 @@ The variable list SPEC is the same as in `if-let*'."
2678 2678
2679;; PUBLIC: find if the current mode derives from another. 2679;; PUBLIC: find if the current mode derives from another.
2680 2680
2681(defun merge-ordered-lists (lists &optional error-function)
2682 "Merge LISTS in a consistent order.
2683LISTS is a list of lists of elements.
2684Merge them into a single list containing the same elements (removing
2685duplicates) using the C3 linearization, so as to obeying their relative
2686positions in each list. Equality of elements is tested with `eql'.
2687
2688If a consistent order does not exist, call ERROR-FUNCTION with
2689a remaining list of lists that we do not know how to merge.
2690It should return the candidate to use to continue the merge, which
2691has to be the head of one of the lists.
2692By default we choose the head of the first list."
2693 (let ((result '()))
2694 (while (cdr (setq lists (delq nil lists)))
2695 ;; Try to find the next element of the result. This
2696 ;; is achieved by considering the first element of each
2697 ;; (non-empty) input list and accepting a candidate if it is
2698 ;; consistent with the rests of the input lists.
2699 (let* ((next nil)
2700 (tail lists))
2701 (while tail
2702 (let ((candidate (caar tail))
2703 (other-lists lists))
2704 ;; Ensure CANDIDATE is not in any position but the first
2705 ;; in any of the element lists of LISTS.
2706 (while other-lists
2707 (if (not (memql candidate (cdr (car other-lists))))
2708 (setq other-lists (cdr other-lists))
2709 (setq candidate nil)
2710 (setq other-lists nil)))
2711 (if (not candidate)
2712 (setq tail (cdr tail))
2713 (setq next candidate)
2714 (setq tail nil))))
2715 (unless next ;; The graph is inconsistent.
2716 (setq next (funcall (or error-function #'caar) lists))
2717 (unless (assoc next lists #'eql)
2718 (error "Invalid candidate returned by error-function: %S" next)))
2719 ;; The graph is consistent so far, add NEXT to result and
2720 ;; merge input lists, dropping NEXT from their heads where
2721 ;; applicable.
2722 (push next result)
2723 (setq lists
2724 (mapcar (lambda (l) (if (eql (car l) next) (cdr l) l))
2725 lists))))
2726 (if (null result) (car lists) ;; Common case.
2727 (append (nreverse result) (car lists)))))
2728
2681(defun derived-mode-all-parents (mode &optional known-children) 2729(defun derived-mode-all-parents (mode &optional known-children)
2682 "Return all the parents of MODE, starting with MODE. 2730 "Return all the parents of MODE, starting with MODE.
2683The returned list is not fresh, don't modify it. 2731The returned list is not fresh, don't modify it.
2684\n(fn MODE)" ;`known-children' is for internal use only. 2732\n(fn MODE)" ;`known-children' is for internal use only.
2685 ;; Can't use `with-memoization' :-( 2733 ;; Can't use `with-memoization' :-(
2686 (let ((ps (get mode 'derived-mode--all-parents))) 2734 (let ((ps (get mode 'derived-mode--all-parents)))
2687 (if ps ps 2735 (cond
2688 (if (memq mode known-children) 2736 (ps ps)
2689 (error "Cycle in the major mode hierarchy: %S" mode) 2737 ((memq mode known-children)
2690 (push mode known-children)) 2738 ;; These things happen, better not get all worked up about it.
2739 ;;(error "Cycle in the major mode hierarchy: %S" mode)
2740 nil)
2741 (t
2742 (push mode known-children)
2691 ;; The mode hierarchy (or DAG, actually), is very static, but we 2743 ;; The mode hierarchy (or DAG, actually), is very static, but we
2692 ;; need to react to changes because `parent' may not be defined 2744 ;; need to react to changes because `parent' may not be defined
2693 ;; yet (e.g. it's still just an autoload), so the recursive call 2745 ;; yet (e.g. it's still just an autoload), so the recursive call
@@ -2708,17 +2760,13 @@ The returned list is not fresh, don't modify it.
2708 ;; If MODE is an alias, then follow the alias. 2760 ;; If MODE is an alias, then follow the alias.
2709 (let ((alias (symbol-function mode))) 2761 (let ((alias (symbol-function mode)))
2710 (and (symbolp alias) alias)))) 2762 (and (symbolp alias) alias))))
2711 (parents (cons mode (if parent (funcall all-parents parent))))
2712 (extras (get mode 'derived-mode-extra-parents))) 2763 (extras (get mode 'derived-mode-extra-parents)))
2713 (put mode 'derived-mode--all-parents 2764 (put mode 'derived-mode--all-parents
2714 (if (null extras) ;; Common case. 2765 (cons mode
2715 parents 2766 (merge-ordered-lists
2716 (delete-dups 2767 (cons (if (and parent (not (memq parent extras)))
2717 (apply #'append 2768 (funcall all-parents parent))
2718 parents (mapcar (lambda (extra) 2769 (mapcar all-parents extras))))))))))
2719 (copy-sequence
2720 (funcall all-parents extra)))
2721 extras)))))))))
2722 2770
2723(defun provided-mode-derived-p (mode &rest modes) 2771(defun provided-mode-derived-p (mode &rest modes)
2724 "Non-nil if MODE is derived from one of MODES. 2772 "Non-nil if MODE is derived from one of MODES.