diff options
| author | Stefan Monnier | 2023-11-07 18:57:03 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2023-11-11 11:51:59 -0500 |
| commit | fbb897b7af53cdb43e18322c5cdfbfef7cdda1ee (patch) | |
| tree | ff0f112ad184b3ac86e000e4d3c5badac851622d | |
| parent | 8323394bc801e01dedd95e0ff8d573dd1f5e34ba (diff) | |
| download | emacs-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.el | 17 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cl-preloaded.el | 12 | ||||
| -rw-r--r-- | lisp/emacs-lisp/eieio-core.el | 61 | ||||
| -rw-r--r-- | lisp/simple.el | 2 | ||||
| -rw-r--r-- | lisp/subr.el | 74 |
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. | ||
| 982 | If 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 | |||
| 1029 | this function to insert characters when necessary. | 1029 | this function to insert characters when necessary. |
| 1030 | 1030 | ||
| 1031 | In binary overwrite mode, this function does overwrite, and octal | 1031 | In 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 |
| 1033 | is intended to be useful for editing binary files." | 1033 | is 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. | ||
| 2683 | LISTS is a list of lists of elements. | ||
| 2684 | Merge them into a single list containing the same elements (removing | ||
| 2685 | duplicates) using the C3 linearization, so as to obeying their relative | ||
| 2686 | positions in each list. Equality of elements is tested with `eql'. | ||
| 2687 | |||
| 2688 | If a consistent order does not exist, call ERROR-FUNCTION with | ||
| 2689 | a remaining list of lists that we do not know how to merge. | ||
| 2690 | It should return the candidate to use to continue the merge, which | ||
| 2691 | has to be the head of one of the lists. | ||
| 2692 | By 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. |
| 2683 | The returned list is not fresh, don't modify it. | 2731 | The 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. |