;-*-Mode: Lisp; Package: SI; Base: 8; Lowercase: T-*- ;These things were written by RMS. You can use them, ;if you return all improvements for redistribution. ;;; Record warnings about objects processed by file-transducers, etc. ;;; (primarily the compiler). ;To perform an operation on a file and report warnings on "objects" in it, ;do something like this: ;(FILE-OPERATION-WITH-WARNINGS (generic-pathname operation-name whole-file-p) ; ... loop over the objects ; (OBJECT-OPERATION-WITH-WARNINGS (object-name location-funcion) ; ... do the operation, and maybe issue a warning with ; (RECORD-WARNING type severity location-info format-string args...) ; ...) ; ...) ;Operation names include :COMPILE and :EVAL. ;Location-function and location-info are features not really used yet; ;just use nil for now. ;Severity is a keyword; the meanings of severities are not yet defined. ;Whole-file-p should eval to T if you are processing everything in the file. ;It tells the warnings system to assume that any objects you don't mention ;should have their warnings forgotten. ;Warnings about files are on the :warnings property of a generic pathname; ;all the warnings about all objects not in files ;go in the variable non-file-warnings-operation-alist. (defvar non-file-warnings-operation-alist nil "Warnings datum for objects not in files.") (defvar warnings-pathnames '(t) "All generic pathnames that have warnings, plus T for non-file objects.") ;These are used in printing out objects mentioned in warnings (defconst warnings-prinlevel 4) (defconst warnings-prinlength 4) (defprop :compile "compilation" name-as-action) (defprop :compile "compiling" name-as-present-participle) (defprop :compile "compiled" name-as-past-participle) (defprop :compile "compiler" name-as-agent) (defprop :eval "evaluation" name-as-action) (defprop :eval "evaluating" name-as-present-participle) (defprop :eval "evaluated" name-as-past-participle) (defprop :eval "evaluator" name-as-agent) ;Wherever found, the file-warnings-operation-alist is a list of file-warnings-datums, ;each recording the information about one kind of operation ;(eg, :COMPILE for compilation). (defstruct (file-warnings-datum :list* (:conc-name file-warnings-) (:constructor make-file-warnings (operation)) (:alterant nil)) (operation nil :documentation "The file operation (such as :COMPILE) this is about") (editor-buffer nil :documentation "The editor buffer these warnings have been printed into") (object-alist nil :documentation "The alist of objects in the file and their warnings")) ;The object-alist is the warnings about that operation (such as, compilation) ;on objects in that file. It is a list of object-warnings-datums. ;This data type records the warnings on one object (eg, one function) in a file ;(or maybe, not in a file). (defstruct (object-warnings-datum :list* (:conc-name object-warnings-) (:constructor make-object-warnings (name location-function)) (:alterant nil)) (name nil :documentation "The name of the object this is about") (location-function nil :documentation "Information for finding this object's definition. If NIL, use Edit Definition on the object name. If any other symbol, use its :location-function property to visit the warning site(s)") (plist nil :documentation "Random other info, perhaps provided for the editor to use") (warnings nil :documentation "The warnings for this object")) ;This data type contains one warning. ;The type SI:PREMATURE-WARNINGS-MARKER ;(with severity NIL) is a marker that follows any premature warnings ;(for unnamed data before this object). (defstruct (warning-datum :list* (:conc-name warning-) (:constructor make-warning (type severity location-info format-string format-args)) (:alterant nil)) (type nil :documentation "A keyword saying what the warning is about") (severity nil :documentation "A keyword giving the severity level of this warning") location-info ;; The next two are used for printing the warning. format-string format-args) ;Given a generic pathname, or t or nil for non-file objects, ;return the file-warnings-operation-alist for it. (defun file-warnings-operation-alist (generic-pathname) "Returns the list of file-warnings-datums for the specified file. There is a file-warnings-datum in the value for each operation for which this file has any warnings. You can SETF this." (if (memq generic-pathname '(t nil)) non-file-warnings-operation-alist (send generic-pathname ':get ':warnings))) (defdecl file-warnings-operation-alist setf ((file-warnings-operation-alist pn) . (set-file-warnings-operation-alist pn value))) (defun set-file-warnings-operation-alist (generic-pathname new-alist) (and new-alist (not (memq generic-pathname warnings-pathnames)) (push generic-pathname warnings-pathnames)) (if (memq generic-pathname '(t nil)) (setq non-file-warnings-operation-alist new-alist) (send generic-pathname ':putprop new-alist ':warnings))) (defun examine-file-warnings (generic-pathname operation) "Return the file-warnings-datum for the specified file and operation, or NIL. T or NIL as the pathname refers to non-file objects. The operation is a keyword such as :COMPILE." (assq operation (file-warnings-operation-alist generic-pathname))) (defun file-warnings-operations (generic-pathname) "Returns a list of all operations for which warnings are recorded for the specified file. An operation is a keyword such as :COMPILE. T or NIL used as an argument refers to non-file objects." (loop for oper in (file-warnings-operation-alist generic-pathname) when (file-warnings-object-alist oper) collect (car oper))) (defun warnings-pathnames () "Returns a list of all generic pathnames that have warnings recorded for them. T or NIL as an element of the value refers to non-file objects." (subset #'file-has-warnings-p warnings-pathnames)) (defun file-has-warnings-p (generic-pathname) "Returns T if the specified file has any warnings recorded for it. T or NIL as an argument refers to non-file objects." (loop for oper in (file-warnings-operation-alist generic-pathname) when (file-warnings-object-alist oper) return t)) ;Copies the fixed part of an object-warnings-datum. ;This is the only part that is modified destructively. ;The warnings list itself is only pushed onto or changed wholesale. (defun copy-object-warnings (object-warnings) (list* (first object-warnings) (second object-warnings) (copylist (third object-warnings)) (object-warnings-warnings object-warnings))) ;; Macros for use by things that record warnings. ;This is the file-warnings-datum we are currently recording warnings in. (defvar file-warnings-datum nil) ;This is the generic pathname we are recording warnings for a file operation on, ;or T if we are recording warnings for an object not associated with any file. ;or NIL if we are not set up for recording warnings about anything. (defvar file-warnings-pathname nil) ;This is the link in the object-alist after which we are adding new objects. ;Everything up to here is the "front half" of the object alist. ;Everything after is the "back half". ;The back half is thrown away at the end of a whole-file operation. (defvar file-warnings-pushing-location nil) ;This is a list of warnings recorded when there was no object set up to warn about. ;They are put here, and the next time an object is started, they are attached to it. (defvar premature-warnings nil) ;This is a list of warnings recorded when there was no object set up, ;but which apply directly to the next object to be set up ;rather than to something anonymous that preceded it. (defvar premature-warnings-this-object nil) ;Macros FILE-OPERATION-WITH-WARNINGS and NON-FILE-OPERATION-WITH-WARNINGS ;are in LMMAC. ;At the beginning of an operation on a file, ;make an object for warnings on this file and operation if there isn't one, ;and also initialize the list of objects we have had warnings on this time thru. ;Specify T or NIL as the pathname for a non-file-associated operation. (defun begin-file-operation (generic-pathname operation-type &aux (default-cons-area background-cons-area)) (or generic-pathname (setq generic-pathname t)) (or (eq file-warnings-pathname generic-pathname) (let ((file-warnings-operation-alist (file-warnings-operation-alist generic-pathname))) (or (memq generic-pathname warnings-pathnames) (push generic-pathname warnings-pathnames)) (or (assq operation-type file-warnings-operation-alist) (progn (push (make-file-warnings operation-type) file-warnings-operation-alist) (set-file-warnings-operation-alist generic-pathname file-warnings-operation-alist))) (setq file-warnings-datum (assq operation-type file-warnings-operation-alist)) (setq file-warnings-pushing-location (locf (file-warnings-object-alist file-warnings-datum))) (setq file-warnings-pathname generic-pathname) (setq premature-warnings nil) (setq premature-warnings-this-object nil) t))) ;At the end of an operation on a file, ;flush the info on objects that didn't get warnings this time. (defun end-file-operation () (setf (cdr file-warnings-pushing-location) nil)) ;Macro OBJECT-OPERATION-WITH-WARNINGS is in LMMAC. ;This is the object-name of the object we are currently recording warnings on, ;or NIL if we are not set up to record warnings on an object. (defvar object-warnings-object-name nil) ;This is the location-info for the object we are going to record warnings on. (defvar object-warnings-location-function nil) ;This is the object-warnings-datum in which we are recording warnings. (defvar object-warnings-datum nil) (defvar object-warnings-pushing-location nil) ;At the beginning of an operation on an object, ;see if there is already an object-warnings-datum for this object, ;left around from previous operations on it. ;Also, initialize that we have had no warnings yet this time. (defun begin-object-operation (object-name location-function &aux (default-cons-area background-cons-area)) (if (or (equal object-warnings-object-name object-name) (null file-warnings-datum)) nil (setq object-name (copytree object-name)) ;Avoid temp area lossage. (setq object-warnings-datum (assoc object-name (file-warnings-object-alist file-warnings-datum))) (if object-warnings-datum (setq object-warnings-pushing-location (locf (object-warnings-warnings object-warnings-datum))) (setq object-warnings-pushing-location nil)) (setq object-warnings-object-name object-name) (setq object-warnings-location-function location-function) (cond (premature-warnings (print-object-warnings-header *standard-output* object-name (file-warnings-operation file-warnings-datum)) (dolist (w (reverse premature-warnings)) (apply 'record-warning w)) (record-and-print-warning 'premature-warning-marker nil nil (if (stringp object-name) "The problems described above were encountered processing ~A." "The problems described above were in data preceding the definition of ~S.") object-name))) (cond (premature-warnings-this-object (unless premature-warnings (print-object-warnings-header *standard-output* object-name (file-warnings-operation file-warnings-datum))) (dolist (w (reverse premature-warnings-this-object)) (apply 'record-warning w)) ;; This need not be a warning at all, ;; since printing the warnings from the data base ;; will look just right with nothing here. (format t (if (stringp object-name) "~% The problems described above were encountered processing ~A." (if premature-warnings "~% Some of the problems apply to the definition of ~S." "~% The problems described above apply to the definition of ~S.")) object-name))) (setq premature-warnings nil premature-warnings-this-object nil) t)) (defun print-object-warnings-header (stream object operation) (if (and (not (stringp object)) (send stream ':operation-handled-p ':item)) (progn (terpri stream) (send stream ':item 'zwei:function-name object "<< While ~A ~S >>" (get operation 'name-as-present-participle) object)) (format stream (if (stringp object) "~%<< While ~A ~A >>" "~%<< While ~A ~S >>") (get operation 'name-as-present-participle) object))) (defun dispose-of-warnings-after-last-object () (if (or premature-warnings premature-warnings-this-object) (object-operation-with-warnings ((string-append "the end of the data") 'zwei:go-to-end-of-file-possibility)))) ;At the end of an object operation, get rid of any warnings ;that were left over from previous operations on this object. ;Furthermore, if there are now no warnings for this object, ;delete the object from the list for this file. ;In that case we must update file-warnings-pushing-location, ;since chances are it is the link that was deleted from the list. (defun end-object-operation () (cond (object-warnings-datum (if object-warnings-pushing-location (setf (cdr object-warnings-pushing-location) nil)) (or (object-warnings-warnings object-warnings-datum) (progn (setf (file-warnings-object-alist file-warnings-datum) (delq object-warnings-datum (file-warnings-object-alist file-warnings-datum))) (if (eq (car file-warnings-pushing-location) object-warnings-datum) (do ((l (locf (file-warnings-object-alist file-warnings-datum)) (cdr l))) ((eq (cadr l) (cadr file-warnings-pushing-location)) (setq file-warnings-pushing-location l)))))))) ;; Flush any warnings about INTERNALs of this object ;; that were not seen during this run. (dolist (objw (cdr file-warnings-pushing-location)) (and (internal-object-of (car objw) object-warnings-object-name) (setf (cdr file-warnings-pushing-location) (delq objw (cdr file-warnings-pushing-location)))))) (defun internal-object-of (maybe-internal maybe-contains-it) (and (consp maybe-internal) (eq (car maybe-internal) ':internal) (or (equal (cadr maybe-internal) maybe-contains-it) (internal-object-of (cadr maybe-internal) maybe-contains-it)))) ;Record a warning and print it too. For an object's first warning, ;print the object's name as well. (defun record-and-print-warning (type severity location-info format-string &rest format-args) "Enter a warning in the warnings data base, and print the warning too. See RECORD-WARNING for calling information." (let ((default-cons-area working-storage-area)) ;Stream may cons (or (null object-warnings-object-name) (neq object-warnings-pushing-location (locf (object-warnings-warnings object-warnings-datum))) (print-object-warnings-header *standard-output* object-warnings-object-name (file-warnings-operation file-warnings-datum))) (terpri) (tyo #/space) (let ((prinlevel warnings-prinlevel) (prinlength warnings-prinlength)) (apply 'format t format-string format-args)) (apply 'record-warning type severity location-info format-string format-args))) (defun maybe-print-object-warnings-header () "If there is an object to record warnings on but no warnings yet, print <>." (or (null object-warnings-object-name) (neq object-warnings-pushing-location (locf (object-warnings-warnings object-warnings-datum))) (print-object-warnings-header *standard-output* object-warnings-object-name (file-warnings-operation file-warnings-datum)))) ;Record a warning on the current object in the current file. (defun record-warning (type severity location-info format-string &rest format-args &aux (default-cons-area background-cons-area)) "Enter a warning in the warnings data base. The file and object should have been specified by using the macros FILE-OPERATION-WITH-WARNINGS and OBJECT-OPERATION-WITH-WARNINGS. TYPE and SEVERITY are keywords with no standard meanings. FORMAT-STRING and FORMAT-ARGS are suitable for handing to FORMAT to print the warning." (if (null object-warnings-object-name) (push (make-warning type severity location-info format-string (copylist format-args)) premature-warnings) ;; Make sure we have ab object-warnings-datum for this object. (or object-warnings-datum (setq object-warnings-datum (make-object-warnings object-warnings-object-name object-warnings-location-function) object-warnings-pushing-location (locf (object-warnings-warnings object-warnings-datum)))) ;; The first time we push a warning on an object, ;; make sure this object is in the front half of the file's object alist ;; (the half that will be kept after this file operation). (or (neq object-warnings-pushing-location (locf (object-warnings-warnings object-warnings-datum))) (progn ;; Delete it from the second half if it is there. (setf (cdr file-warnings-pushing-location) (delq object-warnings-datum (cdr file-warnings-pushing-location))) ;; If not present now, add to end of front half. (or (memq object-warnings-datum (file-warnings-object-alist file-warnings-datum)) (progn (push object-warnings-datum (cdr file-warnings-pushing-location)) (pop file-warnings-pushing-location))))) ;; Now push on this warning. (let ((warning (make-warning type severity location-info format-string (copylist format-args)))) (push warning (cdr object-warnings-pushing-location)) (pop object-warnings-pushing-location)))) ;Filter all the warnings for a particular file, each according to the predicate ;associated with the operation. Thus, :compile warnings are filtered by ;the definition of (:property :compile warnings-filtering-predicate). (defun filter-warnings (generic-pathname) "Discard obsolete warnings for specified file from the data base." (dolist (op (file-warnings-operations generic-pathname)) (let ((pred (get op 'warnings-filtering-predicate))) (if pred (filter-operation-warnings generic-pathname op pred))))) ;Discard any warnings for specified pathname and operation that do not match the predicate. (defun filter-operation-warnings (generic-pathname operation predicate) (let ((file-warnings-datum (examine-file-warnings generic-pathname operation))) (dolist (objw (file-warnings-object-alist file-warnings-datum)) ;; Any warnings which are about previously undefined functions that are now defined, ;; delete from the list of warnings about this object. (dolist (warn (object-warnings-warnings objw)) (or (funcall predicate warn) (setf (object-warnings-warnings objw) (delq warn (object-warnings-warnings objw))))) ;; If this object now has no warnings, flush it from the file. (or (object-warnings-warnings objw) (setf (file-warnings-object-alist file-warnings-datum) (delq objw (file-warnings-object-alist file-warnings-datum))))))) ;This predicate rejects warnings about formerly undefined functions ; which are no longer undefined. (defun (:property :compile warnings-filtering-predicate) (warn) (not (and (eq (warning-type warn) 'compiler:undefined-function-used) (compiler:compilation-definedp (car (warning-format-args warn)))))) (defun print-warnings (pathnames stream) (dolist (file (or pathnames (warnings-pathnames))) (print-file-warnings file stream))) (defun print-file-warnings (pathname &optional (stream *standard-output*)) "Output warnings data base for one file to a stream, in machine-readable form." (if (stringp pathname) (setq pathname (fs:merge-pathname-defaults pathname))) (format stream "~&;-*-Mode: Lisp; Package: User; Base: 10. -*-") (format stream "~%(SI:RELOAD-FILE-WARNINGS~% '~S~% '(" pathname) (let ((generic-pathname (if (symbolp pathname) pathname (send pathname ':generic-pathname))) (*package* pkg-user-package) (*print-base* 10.) (*read-base* 10.) (*readtable* initial-readtable) file-vars file-vals (first-operation t)) ;T for the first operation in the operation-alist. ;; Get the file's property bindings, but use them only ;; when we construct the string which is the text of the warning. (multiple-value (file-vars file-vals) (and (not (symbolp generic-pathname)) (fs:file-attribute-bindings generic-pathname))) (filter-warnings generic-pathname) (dolist (alist-elt (file-warnings-operation-alist generic-pathname)) (if first-operation (setq first-operation nil) (format stream "~% ")) (format stream "(~S NIL" (car alist-elt)) (dolist (objw (file-warnings-object-alist alist-elt)) (apply 'format stream "~% (~S ~S ~S" objw) (dolist (w (object-warnings-warnings objw)) (multiple-value-bind (nil errorp) (catch-error (let ((print-readably t)) (print w 'si:null-stream))) (if errorp (format stream "~% (~S ~S ~S /"~~A/" ~S)" (first w) (second w) (third w) ;; Instead of outputting the warning's format-string and args, ;; run them through format now. Avoid problems if there is an ;; object in the args that can't print readably. (progv file-vars file-vals (apply 'format nil (fourth w) (nthcdr 4 w)))) ;; If we can print the list itself so it will read back, do so. (format stream "~% ~S" w)))) (tyo #/) stream)) (tyo #/) stream))) (format stream "))~%")) (defun reload-file-warnings (pathname operation-alist) (set-file-warnings-operation-alist (if (symbolp pathname) pathname (send pathname ':generic-pathname)) operation-alist)) (defun dump-warnings (output-file-pathname &rest warning-file-pathnames) "Write warnings data base to a file. Read the file back with LOAD." (with-open-file (stream output-file-pathname ':direction ':output) (print-warnings warning-file-pathnames stream) (close stream)))