(setq rcs-lxref-ident
   "$Header:lxref.l 12.0$")
   "$ACIS:lxref.l 12.0$")

;------   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
		  anno-on-marker))

(setq ignorelevel 50)
(setq callby-marker   (exploden ";.. ")	
      anno-off-marker (exploden ";.-")	
      anno-on-marker  (exploden ";.+"))	

;--- 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)
			     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)
		 ; 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)))))





