BSD 4_4 development
[unix-history] / usr / src / old / lisp / liszt / lxref.l
(setq rcs-lxref-ident
"$Header: lxref.l,v 1.2 84/02/03 08:04:37 jkf Exp $")
;------ lxref: lisp cross reference program
;-- author: j foderaro
; This program generates a cross reference listing of a set of one or
; more lisp files. It reads the output of cross reference files
; generated by the compiler. These files usually have the extension .x .
; the .x files are lisp readable. There format is:
; The first s-expression is (File <filename>) where <filename> is the
; name of the lisp source file.
; Then there is one s-expression for each function (including macros)
; which is defined in the file. The car of each expression is the function
; name, the cadr is the function type and the cddr is a list of those
; functions called
;
; lxref can be run from the command level
; % lxref foo.x bar.x
; or in this way
; % lxref
; -> (lxref foo.x bar.x)
;
; There is one option, that is changing the ignorelevel. If a function
; is called by more than ignorelevel functions then all those functions
; are listed, instead a summary of the number of calls is printed. This
; is useful for preventing the printing of massive lists for common
; system functions such as setq.
; To change the ignorelevel to 40 you would type:
;
; % lxref -40 foo.x bar.x
;
;; internal data structures used in lxref:
; funcs : list of functions mentioned either as caller or as callee
; on each function in funcs, the property list contains some of these
; indicators:
; i-seen : always contains t [this is so we can avoid (memq foo funcs)
; i-type : list of the types this function was declared as. In 1-1
; corresp with i-home
; i-home : list of files this function was declared in. In 1-1 corresp
; with i-type
; i-callers: list of functions calling this function
; insure we have plenty of space to grow into
(opval 'pagelimit 9999)
(declare (special xref-readtable width ignorefuncs ignorelevel readtable
user-top-level poport i-seen i-type i-callers docseen
i-Chome i-Doc i-home funcs
callby-marker debug-mode
anno-off-marker liszt-internal
anno-on-marker))
(setq ignorelevel 50)
(setq callby-marker (exploden ";.. ")
anno-off-marker (exploden ";.-")
anno-on-marker (exploden ";.+"))
; internal liszt functions
(setq liszt-internal '(Internal-bcdcall liszt-internal-do))
;--- xrefinit :: called automatically upon startup
;
(def xrefinit
(lambda nil
(let ((args (command-line-args))
(retval))
; readtable should be the same as it was when liszt wrote
; the xref file
(if args
then (signal 2 'exit) ; die on interrupt
(signal 15 'exit) ; die on sigterm
(setq user-top-level nil)
(let ((retval (car (errset (funcall 'lxref args)))))
(exit (if retval thenret else -1)))
else (patom "Lxref - lisp cross reference program")
(terpr poport)
(setq user-top-level nil)))))
(setq user-top-level 'xrefinit)
;--- lxref :: main function
;
(defun lxref fexpr (files)
(prog (p funcs i-seen i-home i-type i-callers filenm caller callee name
home type caller temp fname callers clength i-Chome i-Doc docseen
Chome Doc anno-mode debug-mode)
(setq xref-readtable (makereadtable t))
(setq i-seen (gensym) i-home (gensym) i-type (gensym)
i-callers (gensym) i-Chome (gensym) i-Doc (gensym))
; check for the ignorelevel option
; it must be the first option given.
;
(If (and files (eq #/- (getcharn (car files) 1)))
then (If (fixp
(setq temp (readlist (cdr (explode (car files))))))
then (setq ignorelevel temp)
(setq files (cdr files))))
; process all files. if a -a is seen, go into annotate mode.
; otherwise generate an xref file.
;
(do ((ii files (cdr ii)))
((null ii))
(if (eq '-d (car ii))
then (setq debug-mode t)
elseif anno-mode
then (process-annotate-file (car ii))
elseif (eq '-a (car ii))
then (setq anno-mode t)
else (process-xref-file (car ii))))
(if (not anno-mode) (generate-xref-file))
(return 0)))
;.. process-xref-file
(defun illegal-file (name)
(msg "File " name " is not a valid cross reference file" N))
;--- process-xref-file :: scan the information in an xref file
; if the name ends in .l then change it to .x
;
;.. lxref
(defun process-xref-file (name)
(if debug-mode then (msg "process-xref-file: " name N))
(let (p fname filenm)
; convert foo.l to foo.x
(setq fname (nreverse (exploden name)))
(If (and (eq #/l (car fname)) (eq #/. (cadr fname)))
then (setq fname (implode (nreverse (cons #/x (cdr fname)))))
else (setq fname name))
; now look for foo or foo.x
(If (and (null (errset (setq p (infile fname)) nil))
(null (errset (setq p (infile (concat fname ".x"))) nil)))
then (msg "Couldn't open " name N)
else (setq filenm (car (errset (read p))))
(If (dtpr filenm)
then (If (eq 'File (car filenm))
then (setq filenm (cadr filenm))
(process-File p filenm)
elseif (eq 'Chome (car filenm))
then (process-Chome p)
elseif (eq 'Doc (car filenm))
then (setq docseen t) (process-Doc p)
else (illegal-file name))
else (illegal-file name))
(close p))))
;--- process-File :: process an xref file from liszt
;
;.. process-xref-file
(defun process-File (p filenm)
(let ((readtable xref-readtable))
(do ((jj (read p) (read p))
(caller)
(callee))
((null jj) (close p))
(setq caller (car jj))
(If (not (get caller i-seen))
then (putprop caller t i-seen)
(push caller funcs)) ; add to global list
; remember home of this function (and allow multiple homes)
(push filenm (get caller i-home))
; remember type of this function (and allow multiple types)
(push (cadr jj) (get caller i-type))
; for each function the caller calls
(do ((kk (cddr jj) (cdr kk)))
((null kk))
(setq callee (car kk))
(If (not (get callee i-seen)) then (putprop callee t i-seen)
(push callee funcs))
(push (cons caller filenm) (get callee i-callers))))))
;.. process-xref-file
(defun process-Chome (p)
(do ((jj (read p) (read p))
(caller))
((null jj) (close p))
(setq caller (car jj))
(If (not (get caller i-seen))
then (putprop caller t i-seen)
(push caller funcs)) ; add to global list
; remember home of this function (and allow multiple homes)
(putprop caller (cons (cdr jj) (get caller i-Chome)) i-Chome)))
;--- process-Doc :: process a Doc file
;
; A doc file begins with an entry (Doc).
; subsequent entries are (Name File) and this means that function
; Name is defined in file File. This type of file is generated
; by a sed and awk script passing over the franz manual. (see the
; Makefile in the doc directory).
;
;.. process-xref-file
(defun process-Doc (p)
(do ((jj (read p) (read p))
(caller))
((null jj) (close p))
(setq caller (car jj))
(If (not (get caller i-seen))
then (putprop caller t i-seen)
(push caller funcs)) ; add to global list
; remember home of this function (and allow multiple homes)
(putprop caller (cons (cadr jj) (get caller i-Doc)) i-Doc)))
;.. generate-xref-file
(defun terprchk (wid)
(cond ((> (setq width (+ wid width)) 78.)
(terpr)
(patom " ")
(setq width (+ 8 wid)))))
; determine type of function
;.. generate-xref-file
(defun typeit (fcn)
(cond ((bcdp fcn) (getdisc fcn))
((dtpr fcn) (car fcn))))
;.. lxref
(defun generate-xref-file ()
; sort alphabetically
(setq funcs (sort funcs 'alphalessp))
; now print out the cross reference
(do ((ii funcs (cdr ii))
(name) (home) (type) (callers) (Chome) (Doc) (clength))
((null ii))
(setq name (car ii)
home (get name i-home)
type (get name i-type)
callers (get name i-callers)
Chome (get name i-Chome)
Doc (get name i-Doc))
(If (lessp (setq clength (length callers)) ignorelevel)
then (setq callers (sortcar callers 'alphalessp)))
(do ((xx Chome (cdr xx)))
((null xx))
(setq home (cons (concat "<C-code>:" (caar xx))
home)
type (cons (cadar xx) type)))
(If (null home)
then (setq home (If (getd name)
then (setq type
(ncons (typeit (getd name))))
'(Franz-initial)
elseif (memq name liszt-internal)
then '(liszt-internal-function)
elseif (get name 'autoload)
then (list (concat "autoload: "
(get name 'autoload)))
else '(Undefined))))
(patom name)
(patom " ")
(If (null (cdr type))
then (patom (car type))
(patom " ")
(patom (car home))
else (patom "Mult def: ")
(mapcar '(lambda (typ hom)
(patom typ)
(patom " in ")
(patom hom)
(patom ", "))
type
home))
(If docseen
then (If Doc then (msg " [Doc: " (If (cdr Doc) then Doc
else (car Doc)) "]")
else (msg " [**undoc**]")))
(If (null callers) then (msg " *** Unreferenced ***"))
(terpr)
(patom " ")
(cond ((null callers))
((not (lessp clength ignorelevel))
(patom "Called by ")
(print clength)
(patom " functions"))
(t (do ((jj callers (cdr jj))
(calle)
(width 8))
((null jj))
; only print name if in same file
(setq calle (caar jj))
(cond ((memq (cdar jj) home)
(terprchk (+ (flatc calle) 2))
(patom calle))
(t (terprchk (+ (flatc calle) 6 (flatc (cdar jj))))
(patom calle)
(patom " in ")
(patom (cdar jj))))
(If (cdr jj) then (patom ", ")))))
(terpr)
(terpr)
botloop ))
;--- annotate code
;--- process-annotate-file :: anotate a file
;
;.. lxref
(defun process-annotate-file (filename)
(let (sourcep outp)
; make sure file exists and write annotate file as a
; file with the prefix #,
(if (null (errset (setq sourcep (infile filename))))
then (msg "will ignore that file " N)
else ; will write to file.A (erasing the final l)
(let ((filen (concat "#," filename)))
(setq outp (outfile filen))
(anno-it sourcep outp)
(close outp)
(close sourcep)
; now mv the original filename to #dfilename
; and the annotated file to the original file
(let ((oldcopy (concat "#." filename)))
(if (null (errset
(progn (if (probef oldcopy)
then (sys:unlink oldcopy))
(sys:link filename oldcopy)
(sys:unlink filename)
(sys:link filen filename)
(sys:unlink filen))))
then (msg "An error occured while mving files around "
N
"files possibly affected "
filename oldcopy filen)))))))
;.. process-annotate-file
(defun anno-it (inp outp)
(do ((xx (read-a-line inp) (read-a-line inp))
(anno-it t))
((null xx))
(if (match xx 1 callby-marker) ; flush anno lines
then (flush-a-line outp inp)
elseif (match xx 1 anno-off-marker)
then (setq anno-it nil) ; ';#-' turns off annotating
(write-a-line xx outp inp)
elseif (match xx 1 anno-on-marker)
then (setq anno-it t)
(write-a-line xx outp inp)
else (if anno-it then (anno-check xx outp))
(write-a-line xx outp inp))))
;;; file reading code for annotate function
; lines are read with (read-a-line port). It will read up to the
; first 127 characters in the line, returning a hunk whose cxr 0 is the
; max(index) + 1 of the characters in the hunk. the oversize-line flag
; will be set if there are still more character to be read from this line.
;
; the line should be printed by calling (print-a-line buffer) or if it isn't
; to be printed, (flush-a-line) should be called (which will check the
; oversize-line flag and flush unread input too).
;
(declare (special inp-buffer oversize-line))
(setq inp-buffer (makhunk 128))
;.. anno-it
(defun read-a-line (port)
(setq oversize-line nil)
(do ((i 1 (1+ i))
(ch (tyi port) (tyi port)))
((or (eq #\newline ch)
(eq #\eof ch))
(if (or (eq #\newline ch) (>& i 1))
then (rplacx 0 inp-buffer i) ; store size
inp-buffer ; return buffer
else nil)) ; return nil upon eof
(rplacx i inp-buffer ch)
(if (>& i 126)
then (setq oversize-line t)
(rplacx 0 inp-buffer (1+ i))
(return inp-buffer))))
;--- write-a-line :: write the given buffer and check for oversize-line
;
;.. anno-it
(defun write-a-line (buf oport iport)
(do ((max (cxr 0 buf))
(i 1 (1+ i)))
((not (<& i max))
(if oversize-line
then (oversize-check oport iport t)
else (terpr oport)))
(tyo (cxr i buf) oport)))
;.. anno-it
(defun flush-a-line (oport iport)
(oversize-check oport iport nil))
;.. flush-a-line, write-a-line
(defun oversize-check (oport iport printp)
(if oversize-line
then (do ((ch (tyi iport) (tyi iport)))
((or (eq ch #\eof) (eq ch #\newline))
(cond ((and printp (eq ch #\newline))
(tyo ch oport))))
(if printp then (tyo ch oport)))))
;.. anno-it
(defun anno-check (buffer outp)
(if (match buffer 1 '(#\lpar #/d #/e #/f))
then (let (funcname)
(if (setq funcname (find-func buffer))
(let ((recd (get funcname i-callers)))
(if recd
then (printrcd recd outp)))))))
;--- printrcd :: print a description
;
;.. anno-check
(defun printrcd (fcns port)
(let ((functions (sortcar fcns 'alphalessp)))
(print-rec functions port 0)))
;.. print-rec, printrcd
(defun print-rec (fcns p wide)
(if fcns
then (let ((size (flatc (caar fcns))))
(if (>& (+ size wide 2) 78)
then (msg (P p) N )
(setq wide 0))
(if (=& wide 0)
then (mapc '(lambda (x) (tyo x p)) callby-marker)
(setq wide (length callby-marker)))
(if (not (=& wide 4))
then (msg (P p) ", ")
(setq wide (+ wide 2)))
(msg (P p) (caar fcns))
(print-rec (cdr fcns) p (+ wide size 2)))
else (msg (P p) N)))
;--- match :: try to locate pattern in buffer
; start at 'start' in buf.
;.. anno-check, anno-it, match
(defun match (buf start pattern)
(if (null pattern)
then t
elseif (and (<& start (cxr 0 buf))
(eq (car pattern) (cxr start buf)))
then (match buf (1+ start) (cdr pattern))))
;--- find-func :: locate function name on line
;
;.. anno-check
(defun find-func (buf)
; first locate first space or tab
(do ((i 1 (1+ i))
(max (cxr 0 buf))
(die))
((or (setq die (not (<& i max)))
(memq (cxr i buf) '(#\space #\tab)))
(if die
then nil ; can find it, so give up
else ; find first non blank
(do ((ii i (1+ ii)))
((or (setq die (not (<& ii max)))
(not (memq (cxr ii buf) '(#\space #\tab))))
(if (or die (eq (cxr ii buf) #\lpar))
then nil
else ; fid first sep or left paren
(do ((iii (1+ ii) (1+ iii)))
((or (not (<& iii max))
(memq (cxr iii buf)
'(#\space #\tab #\lpar)))
(implode-fun buf ii (1- iii)))))))))))
;--- implode-fun :: return implode of everything between from and to in buf
;
;.. find-func
(defun implode-fun (buf from to)
(do ((xx (1- to) (1- xx))
(res (list (cxr to buf)) (cons (cxr xx buf) res)))
((not (<& from xx))
(implode (cons (cxr from buf) res)))))