Re: eval-after-provision
Jeff Mincy <[email protected]> Thu, 25 Mar 2004 21:36:28 -0500
| Newsgroups | gmane.emacs.xemacs.design |
|---|---|
| Message-ID | <[email protected]> |
On 24 Mar 2004, [email protected] wrote: > I've been working for a while with a feature 'eval-after-provision' in > my init file which seems like it'd be a useful feature to integrate > into xemacs. From the comments in the source for eval-after-load and > eval-when-feature, it seems like others agree with me. To summarize, > the trouble with e-a-l and e-w-f is that they work with > after-load-alist. However, there are different names under which a > file can be loaded, which makes an alist a problematic structure to > use. Furthermore, in theory features provide for filename > independence, but without a mechanism for evaluating forms after a > feature has been provided, a user has to be aware of what files are > going to be loaded that could provide the feature. Many el files do the provides at the top. Consider what happens when you load a file that does the provide first. (eval-after-provision 'foo '(lambda () (message "foo value is %s" foo-value))) foo.el file in load-path: ================================================================ (provide 'foo) (defvar foo-value (list 'foo-value)) ================================================================ then do (require 'foo) Your code executes the provision form immediately after provide is executed. This means that your eval-after-provision does not allow the form to reference values or code defined in the same file after the provide. I think it should. > This is fairly straightforward; but it's sort of nasty, since it's > advising provide. It seems like it might be better to modify provide > in fns.c directly. I'd be interested in any feedback, of course. You may want to (re)consider living with eval-after-load. There are more subtleties than are immediately obvious when implementing this functionality. I have implemented similar functionality called after-provided which is attached. -jeff > (defvar eval-after-provision-alist nil > "Alist mapping features to functions that should be invoked the > feature is provided. That is, entries are of the form > (SYMBOL F1 F2 ... ) where each Fn accepts one argument, the SYMBOL > being provided. See eval-after-provision.") > > (defun eval-after-provision (symbol form) > "Arrange that, if SYMBOL is ever provided, that FORM will be > executed. When FORM is evaluated, SYMBOL will contain the feature > being provided. FORM can also be the name of a function expecting one > argument (the feature being provided). If the feature has already been > provided, FORM is evaluated immediately." > (let ((existing-funs (assq symbol eval-after-provision-alist)) > (eval-fun (cond ((symbolp form) form) > (t `(lambda (symbol) ,form))))) > ;; If the feature's been provided already, immediately evaluate > (if (featurep symbol) > (funcall eval-fun symbol) > ;; otherwise, stash it for later > (if existing-funs > (nconc existing-funs (list eval-fun)) > (setq eval-after-provision-alist > (cons (list symbol eval-fun) eval-after-provision-alist)))))) > > (defadvice provide (after support-eval-after-provision (feature) > activate) > "Run any forms that should be evaluated after FEATURE is > provided. These forms, if any, are stored in > eval-after-provision-alist. After evaluation, the entries associated > with SYMBOL will be removed" > (let* ((l (assq feature eval-after-provision-alist)) > (fs (cdr l))) > (while fs > (funcall (car fs) feature) > (setq fs (cdr fs))) > (setq eval-after-provision-alist (delete l eval-after-provision-alist))))
after-provided.el
(text/plain, 10.6 KB)
;; I could have possibly used eval-after-load defined in subr.el.
;; load defines load-history is a list of lists - each list looks like:
;; (loadfilename (require . <foo>)* symbols* (provide . <provide>))
;;
;; (featurep 'mmm-vars)
;; (file-provides (feature-file 'mmm-vars))
;; (file-requires (feature-file 'mmm-vars))
;; (file-dependents (feature-file 'mmm-vars))
;; (feature-symbols 'mmm-vars)
;; (feature-file 'mmm-vars)
;; (symbol-file 'mmm-global-mode)
;; helper function - see what features get loaded elsewhere.
(defun features-not-loaded ()
(let ((result nil))
(dolist (feature features)
(if (null (feature-file feature)) (push feature result)))
(nreverse result)))
;;The following features are not in load-history
;;These features are provided either in c source code or in the dumped lisp?
;;(defvar features-not-loaded (features-not-loaded))
;; Helper function - see all files that define symbol.
(defun all-symbol-files (symbol)
(let ((result ()))
(dolist (symbols load-history)
(if (memq symbol (cdr symbols)) (push (car symbols) result)))
(nreverse result)))
;debugging forms:
;(all-symbol-files 'require)
;(all-symbol-files 'provide)
;(all-symbol-files 'load)
; ("efs" "/opt/sfw/lib/xemacs-21.4.11/lisp/code-files.elc")
;(ad-get-advice-info 'load)
;(ad-get-advice-info 'provide)
;(ad-get-advice-info 'require)
;; Keep track of nesting level of require/load.
;; Do not do after-provide stuff until we know that we are done loading the files.
;; Some files do the provide at the top of the file.
(defvar load-level 0)
(defvar after-provided-sit-for nil)
;;****************************************************************
;;ADD-TO-AFTER-PROVIDEDS
;;Build after-provideds an alist of alists:
;; (foo . ((<fn1> . result) (<fn2> . result)))
;;****************************************************************
(defvar after-provideds ())
(defun add-to-after-provideds (symbol function result)
;;(add-to-list 'after-provideds (list symbol nil function))
(let ((list (assq symbol after-provideds)))
(if (null list)
(push (setq list (list symbol)) after-provideds))
(let ((element (assoc function (cdr list))))
(if (null element)
(setf (cdr list) (cons (setq element (cons function nil)) (cdr list))))
(setf (cdr element) result))))
;;****************************************************************
;;AFTER-PROVIDED 'feature (lambda () <forms>)
;; If "feature" is string, then use eval-after-load to provide 'feature
;; If feature is list, then do function after each feature in list is provided.
;; If feature has already been provided, then forms are immediately performed.
;;****************************************************************
(put 'after-provided 'lisp-indent-function 1)
(defun after-provided (symbol function)
;; check function - if not functionp then wrap lambda around it.
(if (not (functionp function))
(setq function `(lambda () ,function)))
;; If symbol is really a string, then use eval-after-load to provide symbol
;; This is necessary when file does not do a provide.
(if (stringp symbol)
(if (assoc symbol load-history)
(provide (setq symbol (intern symbol)))
(eval-after-load symbol `(provide ',(setq symbol (intern symbol))))))
(if (listp symbol)
(dolist (sym symbol symbol) (after-provided sym function))
(if (memq symbol features)
;; Package has already been loaded, just do it.
(progn
(message "%s already loaded" symbol)
(sit-for (or after-provided-sit-for 0))
(condition-case VAR
(progn
(funcall function)
(add-to-after-provideds symbol function 'immediate))
(error
(add-to-after-provideds symbol function `(error ,(car VAR) ,(cdr VAR)))
(signal (car VAR) (cdr VAR)))))
;; Keep function around until package gets provided.
(if (member function (get symbol 'after-provide))
(progn (message "after provided for %s already there" symbol)
(sit-for (or after-provided-sit-for 0)) nil)
(progn
(add-to-after-provideds symbol function nil)
(setf (get symbol 'after-provide)
(cons function (get symbol 'after-provide)))
symbol)))))
;;****************************************************************
;;PROVIDED
;; Execute any after-provided functions for each provided symbol.
;; Protect against errors when calling function.
;;****************************************************************
(defun provided (symbol)
(while (not (null (get symbol 'after-provide)))
(message "after %s" symbol)
(sit-for (or after-provided-sit-for 0))
(let ((function (pop (get symbol 'after-provide))))
(condition-case VAR
(progn
(funcall function)
(add-to-after-provideds symbol function 'executed))
(error
(add-to-after-provideds symbol function `(error ,(car VAR) ,(cdr VAR)))
(signal (car VAR) (cdr VAR)))))))
;; List of packages that have been recently provided.
;; Defer looking for after-provided forms until done loading (load-level is 0)
(defvar provided ())
;;****************************************************************
;;DO-PROVIDED
;; If at top-level (as far as load/require/provide is concerned),
;; run deferred after-provides.
;;****************************************************************
(defun do-provided (symbol)
(if (and symbol (not (memq symbol provided))) (push symbol provided))
;; If load has caused some packages to have been provided then do after stuff, when done loading.
(when (zerop load-level)
(while (not (null provided))
(provided (pop provided)))))
;;****************************************************************
;;Advise LOAD, PROVIDE, REQUIRE
;;Each advice does the same basic thing.
;; 1. Increments load-level while the real function runs
;; 2. restores load-level when done
;; 3. Calls do-provided when done.
;; 4. Protects against errors
;;****************************************************************
(defadvice load (around after-provided first activate protect)
(let ((original-load-level load-level))
(condition-case ERROR
(progn
(incf load-level)
ad-do-it
(decf load-level)
(do-provided nil))
(error
(setq load-level original-load-level)
(do-provided nil)
(signal (car ERROR) (cdr ERROR))))))
(defadvice provide (around after-provided first activate protect)
(interactive "Sprovides feature: ")
(let ((original-load-level load-level))
(condition-case ERROR
(progn
(incf load-level)
ad-do-it
(decf load-level)
;; Some packages to have been provided do after stuff, when done loading.
;; Doing a (provide 'foo) at top level will cause the after stuff to run.
(do-provided (ad-get-arg 0)))
(error
(setq load-level original-load-level)
(do-provided (ad-get-arg 0))
(signal (car ERROR) (cdr ERROR))))))
(defadvice require (around after-provided first activate protect)
(interactive "Srequire symbol: ")
(let ((original-load-level load-level))
(condition-case ERROR
(progn
(incf load-level)
ad-do-it
(decf load-level)
;; If package already loaded just do the cleanup, this also catches the case where
;; the package loaded didn't do a provide - underlying require already gave error.
;; We're done doing require, look for packages that have been provided.
(do-provided (ad-get-arg 0)))
(error
(setq load-level original-load-level)
(do-provided (ad-get-arg 0))
(signal (car ERROR) (cdr ERROR))))))
;;****************************************************************
;;SHOW-PROVIDES
;; show after-provided forms with status in popup buffer.
;;****************************************************************
(eval-when (compile) (require 'pp))
(defun show-provides ()
(interactive)
(require 'pp)
(let ((outbuf nil)
(name-of-mode "*after*"))
(save-excursion
(setq outbuf (get-buffer-create name-of-mode))
(set-buffer outbuf)
(kill-all-local-variables))
(let ((buffer-save (current-buffer))
outwin)
;; Pop up the buffer.
(setq outwin (display-buffer outbuf))
(unwind-protect
(progn
(set-buffer outbuf)
(setq buffer-read-only nil)
(buffer-disable-undo (current-buffer))
(erase-buffer)
(buffer-enable-undo (current-buffer))
(set-buffer-modified-p nil)
(save-window-excursion
(select-window outwin)
(goto-char (point-max)))
(dolist (after-provided after-provideds)
(let* ((provided (car after-provided))
(functions (cdr after-provided))
(isfeature (featurep provided))
(file (if isfeature (feature-file provided))))
(insert "\n\n;; PACKAGE: ") (insert (symbol-name provided))
(when file
(insert " provided by ") (insert (prin1-to-string file)))
(dolist (functionresult functions)
(let ((function (car functionresult))
(result (cdr functionresult)))
(if (and (consp function) (eq (car function) 'lambda))
(if (null (cdddr function))
(setq function (caddr function))
(setq function `(progn ,@(cddr function)))))
(insert "\n;;Result: ")
(cond ((null result)
(insert "Not executed"))
((eq result 'immediate)
(insert "Executed immediately"))
((eq result 'executed)
(insert "Executed"))
((and (consp result) (eq (car result) 'error))
(insert "ERROR: ") (insert (prin1-to-string (cadr result)))
(dolist (info (cddr result))
(insert " ") (insert (prin1-to-string info))))
(t (insert (prin1-to-string result))))
(insert "\n")
(insert (pp-to-string function))))))
(emacs-lisp-mode)
(font-lock-mode))
(set-buffer buffer-save)))))
(provide 'after-provided)
;;Don't load tinypath after this package. It breaks this?
(provide 'tinypath)