:class callingTree ⍝ ⍝ Lists references and calls made by a function. Original code DanB 1992. ⍝ ⍝ This class will allow to produce a list of all the calls made by a function ⍝ as well as cross reference the names in a namespace. ⍝ ⍝ Syntax: ⎕new callingTree 'namespace[.fn] -FULL -MAXLEVEL=n -ONLYFNS -TREEVIEW' ⍝ or: instance.Calls 'fn' - See for defn of each switch ⍝ ⍝ It will allow to see all objects referred to by a given function ⍝ with repeated procedure for each function called. ⍝ ⍝ Each name is preceded by a symbol. Its meaning is ⍝ ⍝ G global object (not a visible function) ⍝ F visible function ⍝ R recursive function call ⍝ * function already shown (only without FULL option) ⍝ ○ local object ⍝ ! unreferenced local object ⍝ l unreferenced label ⍝ L referenced label ⍝ ↑ global local (global localized at an upper level) ⍝ ⍝ Example: ⍝ Calls '#.Myfn -FULL' ⍝ ⍝ will show all objects in each fns called by 'Myfn', repeating the ⍝ the procedure for fns already shown (except recursive calls). ⍝ This may seem redondant but consider the following: ⍝ ⍝ ∇fn ∇f1;⎕io ∇f2 ⍝ [1] f1 [1] f2 [1] ⎕io ⍝ [2] f2∇ [2] ∇ [2] ∇ ⍝ ⍝ f2 refers to ⎕io globally. In 'fn', f2 refers to the global ⎕io in ⍝ the ws. When called by f1 it refers to the local ⎕io in f1. Calls 'fn' ⍝ will produce 2 different outputs for f2, one from 'fn' and one from 'f1' ⍝ because the -FULL switch as been set. ⍝ ⍝ Calls 'ns.fn' ⍝ ⍝ will show all objects in each fn called by 'fn' but won't show already ⍝ shown functions. In this case the second output for 'f2' won't be present ⍝ and therefore show the local reference to ⎕io. ⍝ ⍝ Calls 'ns.fnx -MAX=1' will only show the fn itself (1st level) ⍝ ⍝ Note that ⍎ is being taken in account but that its use may not be ⍝ interpreted properly. Text in functions with ⍎ is scanned between the ⍎ ⍝ character and the next ⋄ character on each line where it appears only. ⍝ This also applies to error trapping and some system functions. ⍝ ⍝ Calls 'x.fny -ONLYFNS' will only display the names of the functions ⍝ called, not the other objects (e.g. variables). ⍝ ⍝ Calls 'n1.fnz -TREEVIEW' will display the result in a treeview form. ⍝ ⍝ If no fn name is supplied (only a namespace is given) then the instance is ⍝ prepared and no display occurs immediatly. Subsequent calls to ⍝ ⍝ Calls 'fnname -report' will show a report on the calls made by ⍝ (by default a tree view object will be used) ⍝ ⍝ Other functions: ⍝ ⍝ Xref will produce a table of names cross-referenced ⍝ There are 3 choices of presentation: Square, Indented and Auto. Auto ⍝ attempts to pick the smallest presentation. Use xrefStyle to specify ⍝ which one to use. ⍝ ⍝ Caveat: ⍝ ⍝ The detection of local variables within dfns relies on ← and is wrong for ⍝ multiple assignments (e.g. "a b←1 2") or global (A∘←) ⍝ ⍝ The inclusion of text after ⍎, ⎕NC and ⎕TRAP relies on the presence of ⍝ visible strings and will be wrong if the text is incomplete (e.g. "⍎'a',⍕n). ⍝ In that later case you can force the inclusion of names otherwise missed by ⍝ using the ⍝∇∪ tag in the fn as in ⍝∇∪ a1 a2 a3 etc. :include textUtils ⍝∇:require tools\DanB\textUtils ALPHABET←'0123456789⎕#.',(0≤⎕NC 256 1⍴⎕AV)/⎕AV ⎕io←1 ⋄ CR←⎕av[4] ⋄ ⎕ml←2 Version←1.42 ⍝ allow fn refs and accept source as arg :field public FnRefs :field private FnCalls←⍬ ⍝ none yet ∇ ctor arg;run;src;b;⍙XREF;class;path;pa;vtv;t;fn;ns ⍝∇ DanB 840921 list all objects of all calls made by argument ⍝ -FULL is Full Search (repeat search for already processed fns) ⍝ -MAXLEVEL is maximum level to look for (default 9999) ⍝ -DETAILS include variables in reports ⍝ -TREEVIEW shows the output in a treeview form ⍝ There are 2 ways to initialize this instance: ⍝ 1: a string describing the class followed by switches, e.g. 'MyClass -FULL' ⍝ 2: a class (or its defn) followed by up to 5 numbers for TREEVIEW DETAILS MAXLEVEL FULL XREF :Access public :Implements constructor NSI←'.',⍨⎕IO⊃⎕NSI FnRefs←0 3⍴run←class←0 ⋄ fn←'' (⍙MAXLEVEL ⍙DETAILS ⍙FULL ⍙TREEVIEW ⍙XREF)←5↑9999 Xpath←arg ⍝ Xpath is the ref ⍝ Is it a string? :If isChar arg ⍝ strings need to be parsed pa←(⎕NEW ⎕SE.Parser('-MAXLEVEL= -DETAILS -FULL -TREEVIEW -XREF' 'upper')).Parse arg ⍝ There can only be a single reference ⎕SIGNAL 5 if 1≠⍴t←pa.Arguments ⋄ fn←0/ns←{'#'∊1↑⍵:⍵ ⋄ NSI,⍵}1⊃t ⍝ A fnname means 'run Calls' :If run←9≠⎕NC ns ⋄ (ns fn)←ns splitLast'.' ⋄ :EndIf Xpath←⍎ns (⍙DETAILS ⍙FULL ⍙TREEVIEW ⍙XREF)←pa.(DETAILS FULL TREEVIEW XREF) ⍙MAXLEVEL←9999 pa.Switch'MAXLEVEL' :Else ⍝ Has to be a ref or defn followed by TREEVIEW DETAILS MAXLEVEL FULL XREF (Xpath ⍙TREEVIEW ⍙DETAILS ⍙MAXLEVEL ⍙FULL ⍙XREF)←arg,(⍴,arg)↓0 0 0 9999 0 0 ns←(1+⍴⍴t)⊃(⍕t←Xpath)'"ns"' ⍝ remember name :EndIf ⎕SIGNAL 11 if(9≠⌊t←⎕NC⊂'Xpath')>vtv←∧/isChar¨Xpath ⍝ this has to be a ref or a VTV :If vtv∨class←9.4∊t Xpath←makeNS ⎕SRC⍣class⌷Xpath :EndIf class∨←9.1∊t ⍝ ns will do ⍝ At this point Xpath contains a ref which will be used for processing :If ⍙XREF∨class∨vtv ⋄ :AndIf 0<⍴t←Xpath.⎕NL-3 4 ⍝ For classes we first determine the reference of each function FnRefs←t,(t∘Xrf∘Xpath.⎕VR¨t),[1.1]Attributes¨Xpath∘,∘⊂¨t :EndIf ⎕DF'Tree of ',ns,(0<⍴fn)/'.',fn :If run ⋄ Calls fn ⍙TREEVIEW ⍙DETAILS ⍙MAXLEVEL ⍙FULL ⋄ :EndIf :If ⍙XREF ⋄ Xref ⋄ :EndIf ∇ ∇ newns←makeNS nssrc;src;b;t;swt;⎕IO;mask :Access public shared ⍝ Turn a class defn into a namespace by removing all classes' special statements ⎕IO←0 src←¯1↓1↓{((∧\b)⍱⌽∧\⌽b←' ⍝'∊⍨↑¨⍵)/⍵}leftJustify¨nssrc ⍝ trim off outside comments ⍝ We must remove all :include, :property and :field statements b←':field' ':inclu' ':endpr' ':prope' ':class' ':endcl' ':names' ':endna' ':inter' ':endin'⍳lowerCase¨6↑¨src mask←0=-/+⍀∨/b∘.=2 3⍴4 6 8 5 7 9 ⍝ exclude nested spaces mask←mask>≠\b∊2 3 ⍝ exclude properties mask←mask>b∊0 1 2 5 7 9 ⍝ exclude fields, etc. src←1⌽':endnamespace' ':namespace',mask/src ⍝((b∊⍳3)⍱≠\b∊2+⍳2) ⍝ There is no guarantee this will work as there may be some assignment or invalid ⍝ syntax that will prevent ⎕FIXing :Trap ⍴swt←⍬ swt←(⎕SE.⎕WG'StatusWindow').Text :EndTrap :Trap 11 ⍝ but we can try something newns←0 ⎕FIX src :Else ⍝ chk the status window's output t←('line('∘≡¨5↑¨t)/t←(¯1+⍴swt)↓(⎕SE.⎕WG'StatusWindow').Text b←⍎⍕(b⍳¨',')↑¨b←5↓¨t newns←0 ⎕FIX src/⍨~(⍳⍴src)∊b :EndTrap ∇ _MaxLines←5 0 ⍝ comment lines from top/bottom :property CommentsLines ⍝ This property determines the maximum comment lines from the top and bottom ⍝ for each of the functions displayed in Report. :access public ∇ set n;v 'must be 1 or 2 positive numbers'⎕SIGNAL 11↓⍨(∧/v≥0)∧(⍴v←,n.NewValue)∊1 2 _MaxLines←2↑v ∇ ∇ r←get r←_MaxLines ∇ :endproperty ∇ r←Calls Xnl;pa;RAW :Access public :If isChar Xnl ⍝ the order is the same as for the class: ⍝ treeview details maxlevel full pa←(⎕NEW ⎕SE.Parser('-MAXLEVEL= -DETAILS -FULL -TREEVIEW -XREF -RAW' 'upper')).Parse Xnl 'One fn name needed as argument'⎕SIGNAL 11 if 1≠⍴pa.Arguments Xnl←1⊃pa.Arguments ⍙DETAILS ⍙FULL ⍙TREEVIEW ⍙XREF ⍙RAW←pa.(DETAILS FULL TREEVIEW XREF RAW) ⍙MAXLEVEL←9999 pa.Switch'MAXLEVEL' :Else (Xnl ⍙TREEVIEW ⍙DETAILS ⍙MAXLEVEL ⍙FULL ⍙RAW)←Xnl,(⍴,Xnl)↓'' 0 0 9999 0 0 :EndIf :If 0<⍴,Xnl ⍝ there may be a previous result in FnCalls 'Unknown fn'⎕SIGNAL 11↓⍨(Xpath.⎕NC Xnl)∊3 4 ⍝ Initialize tree and output function FnCalls←0 4⍴'' Xtreegen(,⊂Xnl)⍬ ⍝ start the search :EndIf →⍙RAW⍴0{⍺}r←FnCalls :If ⍙TREEVIEW ⋄ View ⋄ r←0 0⍴'' ⋄ :Else ⋄ r←Report ⋄ :EndIf ∇ XREF_Styles←'Square' 'Indented' 'Auto' _Xstyle←'Auto' :property xrefStyle ⍝ This property determines the style of presentation of the cross-reference table ⍝ 'Square' means all the references are on top vertically and the fn names to the left ⍝ 'Indented' means all the references are horizontal and indented to align with their column ⍝ 'Auto' attempts to minimize presentation space by choosing the best format :access public ∇ set s;v ('Pick one of ',⍕XREF_Styles)⎕SIGNAL 11 if 1≠⍴XREF_Styles∩⊂v←s.NewValue _Xstyle←v ∇ ∇ r←get r←_Xstyle ∇ :endproperty ∇ x←Xref;fns;all;o;w;i;codes;objs;p;b;so;sb;line ⍝ Generate a Cross reference of all names recorded in the instance :Access public x←(1,⍴x)⍴x←'No fns found' →0↓⍨⍴fns←fns[o←⎕AV⍋⊃fns←FnRefs[;1]] ⍝ sort the fn names ⍝ All the references w←↑⍴all←all[⎕AV⍋⊃all←(~'⎕⍎'∊⍨↑¨all)/all←∪⊃,/2⊃¨FnRefs[;2]] x←⊂w⍴' - - - - :' ⍝ or '----:----|' :For i :In ⍳⍴fns (codes objs)←2⊃FnRefs[o[i];] b←w≥p←all⍳objs ⋄ line←w⍴' . . . . :' ⋄ line[b/p]←b/codes x,←⊂line :EndFor ⍝ Prepare output. If Square show the ref names transposed, ⍝ if Indented show the names on a diagonal, if Auto try to figure ⍝ out the best presentation: b←XREF_Styles∊⊂_Xstyle ⋄ line←(⍴all)⍴'....:....:' :If b[1]∨b[2]1⌽lp)>≠\lp∨rp :If 1↑b/lp ⋄ b←b\≠\b/sp ⋄ :EndIf ⍝ work the op's name using the spaces l←(~b)/l ⋄ b←~l∊'()' loc←∪loc,bl cut b\b/l :EndIf nl←CR=n←nl↓fn ⍝ Text, Body, Comments, statement Delimiters & ⍎ T←nl US n∊'''' ⋄ E←n='⍎' ⍝ Text & ⍎ B←C⍱T>C←nl OS T<('⍝∇∪'⍷n)dcode←×(+\B∧n∊'{')-+\B∧n∊'}' ⋄ D←nl∨B∧n∊'⋄' ⋄ l←0 :If ~dfn ⍝ Account for control structures T←':If' ':AndIf' ':OrIf' ':ElseIf' ':EndIf' ':ForEach' ':InEach' ':EndFor' T←T,':Select' ':CaseList' ':EndSelect' ':While' ':EndWhile' T←T,':Repeat' ':EndRepeat' ':Until' ':Trap' ':EndTrap' T←T,':GoTo' ':Return' ':Continue' ':Leave' :For c :In T l←l∨t∨(-⍴c)⌽t←c⍷n ⍝ find start/end of each :keyword :EndFor T←':Case' ':Else' ':End' ':For' ':In' ⍝ special case for other shorter names :For c :In T l←l∨t∨(-⍴c)⌽t←l≠\l ⍝ mask out from Body l←B∧n∊':' :EndIf E←B∧E ⋄ ec←E∨(¯12⌽n⍷⍨'⎕TRAP←')∨(n⍷⍨'⎕NC ')∨n⍷⍨'⎕NC''' ⋄ E←∨/E B←B∨Cl ⍝ exclude labels from Body :EndIf ⍝ Find global references t←(B⍲n∊Alf)∨'.⎕'⍷n ⍝ find delimiters n[t/⍳⍴t]←bl ⍝ replace by blank n←Xqz(('. '⍷n)⍱¯1⌽' .'⍷n)/n ⍝ remove 1st & last dot and squeeze text n←(¯1⌽~≠\(1+t)/t←'←'=n)\n←(~' ←'⍷n)/n t←'←'∊¨n←(~(↑¨n)∊⎕D)/n←∪bl cut n ⍝ remove any space before ← :If dfn ⋄ loc←(¯1↓¨t/n),(o∊n)/o←,¨'⍺⍵' ⋄ :EndIf n←(-t)↓¨n ⍝ remove ← (could gather info for line # here) :If 0=⎕NC'NSI' ⋄ nsi←'.',⍨⎕IO⊃⎕NSI :Else ⋄ nsi←NSI :EndIf t←⍴nsi ⋄ n←∪(t×nsi∘≡¨t↑¨n)↓¨n ⍝ account for full paths o←⊃,/l←loc lab fns type←(∊(⍴¨l)⍴¨'○LF'),'G' type←type[o⍳n] t←~loc∊n ⋄ n type,←t∘/¨loc'!' t←~lab∊n ⋄ n,←t/lab ⋄ type,←t/'l' →E↓0 n type←n type,¨'⍎ ' ∇ ∇ refs←{st}Xgensymb br;i;fn;cr ⍝ Generate new symbols of last function in br :If (i←FnRefs[;1]⍳fn←¯1↑br)>1↑⍴FnRefs cr←Xpath.⎕CR↑fn FnRefs⍪←fn,((Xpath.⎕NL-3 4)Xrf cr)(cr Attributes Xpath,fn) :EndIf refs←1⊃FnRefs[i;2] refs←Xcompare st refs br ⍝ correct references ⍝ Gather results FnCalls⍪←fn,(⍴br),refs(3⊃,FnRefs[i;]) ⍝ update Globals ∇ ∇ refs←Xcompare(stack refs branch);globals;codes;gnam;c;o;n;t;fn;nam;allf ⍝ Compare list and adjust global definitions ⍝ Any global or fn found localized up the stack will be marked as '↑' →0↓⍨+/globals←'GF'∊⍨1⊃refs ⋄ gnam←globals/2⊃refs c o←⌽¨⊃,¨/stack,⊂⍬ ⍬ :For nam :In gnam ⍝ is this global name localized at an upper level? :If '○'∊1↑(n←+/∧\t∊'↑G')↓t←(o∊fn←⊂nam)/c ⍝ then ((1,fn⍳⍨2⊃refs)⊃refs)←'↑' ⍝ mark it as such :EndIf :EndFor ⍝ Change F into R for all recursive calls :If 0<⍴fn←('F'=codes←1⊃refs)/allf←2⊃refs ⍝ We know these fns are NOT localized (cause we'd have found up there) ⍝ If we do not want a FULL search we replace F by * to denote 'Done, see above' :If ⍙FULL<0<⍴nam←(fn∊FnCalls[;1])/fn ⋄ codes[allf⍳nam]←'*' ⋄ :EndIf ⍝ For recursive fns all we need to do is look up the branch to see if it's there :If 0<⍴nam←(fn∊branch)/fn ⋄ codes[allf⍳nam]←'R' ⋄ :EndIf (1⊃refs)←codes :EndIf ∇ ∇ Xtreegen(Xbr Xst);symbs;i;fns ⍝ Generate symbols of the last fn in the BRanch and those of its sub-routines ⍝ semi-globals: Xnl, name list of all fns processed so far ⍝ symbs←Xst Xgensymb Xbr :If ⍙MAXLEVEL>⍴Xbr ⍝ max level reached ? fns←('F'=1⊃symbs)/2⊃symbs :For i :In ⍳⍴fns Xtreegen(Xbr,fns[i])(Xst,⊂symbs) :EndFor :EndIf ∇ ∇ at←{cr}Attributes(path fn);c;b ⍝ Return the attributes of a fn given its ⎕CR ⍝ Those are the top and bottom comments, its class attributes ⍝ and the size and header syntax (3∊) :If 0∊⎕NC'cr' :If 2>⍴⍴cr←path.⎕CR fn ⋄ cr←⍕(2∊¨⍴∘⍴¨cr)/cr ⋄ :EndIf ⍝ cover funny refs :EndIf at←0 ''((path.⎕SIZE fn),1⊃,path.⎕AT fn) (1⊃at)←2⍴⊂cr←1 0↓⎕FMT leftJustify cr ⍝ ⎕fmt because cr may be vector (eg {⍵}) :If 0<↑⍴cr (1⊃at)←trimEnds¨((∧\c)⌿cr)((b←⌽∧\⌽c←'⍝'=cr[;1])⌿cr) →0↓⍨4<1↓⍴c←lowerCase(':'=cr[;1])⌿cr c←' 'splitOn⍨,(':impl' ':acce'∊⍨↓c[;⍳5])⌿c (2⊃at)←('public' 'shared' 'constructor' 'destructor'∊c)/'PSCD' :EndIf ∇ ∇ rep←Report;fn;lev;refs;atts;tb;grab;i;from;⎕IO ⍝ Character based listing rep←⍴⎕IO←0 ⋄ tb←1 ¯1×_MaxLines ⋄ grab←{n←(×⍺)×(|⍺)⌊1↑⍴⍵ ⋄ ↓(n,1↓⍴⍵)↑⍵} :For i :In ⍳1↑⍴FnCalls fn lev refs atts←FnCalls[i;] from←'→',⍨(i-(⌽0,i↑FnCalls[;1])⍳lev-1)⊃' ',FnCalls[;0] rep,←CR,CR,('Level ',⍕lev),': ',from,fn,,CR,⊃⊃,/tb grab¨↑atts rep,←,CR,Xft refs :EndFor rep←2↓rep ∇ ∇ View;tree;form;font;rc;⎕WX;details ⍝ Display tree in a treeview window :Access public ⎕WX←1 ⍝ so we can see inside 'use Calls first to produce the object to view'⎕SIGNAL 11 if ⍬≡FnCalls tree←FnCalls[;⍳2] ⍝∇∪ form ⍝ we use this name locally ⍝ Produce details? :If ⍙DETAILS details←↓¨(99-3×FnCalls[;2])Xft¨FnCalls[;3] tree⍪←⊃⍪/(tree[;2]+1),[1.1]⍨¨details tree←tree[⍋⍒∊(1+∊⍴¨details)↑¨1;] :EndIf font←(1+80∊⎕DR'')⊃'Dyalog Std' 'APL385 Unicode' font←font 16 1 0 0 400 0 0 :If 2=⎕SE.⎕NC'FontObj' ⋄ font←⎕SE.FontObj ⋄ :EndIf rc←(999⌊99⌈18×1↑⍴tree),99⌈999⌊13×⌈⌿(∊⍴¨tree[;1])+2×tree[;2] 'form'⎕WC'form' 'Tree View of function calls'(20 20)rc('font'font)('coord' 'pixel') 'form.exit'⎕WC'button' ''(0 0)(1 1)('cancel' 1)('event' 'select' 1) 'form.tv'⎕WC'treeview'(tree[;1])(0 0)(100 100)('depth'(tree[;2]-1))('coord' 'prop') 2 ⎕NQ¨'form.tv' 302∘,¨⍳1↑⍴tree ⎕DQ'form' ∇ ∇ mat←{width}Xft(symb names);p;w;f;d ⍝ Reformat mat :If 2≠⎕NC'width' ⋄ width←⎕PW ⋄ :EndIf symb names←(⍙DETAILS∨symb∊'RF*')∘/¨symb names mat←⊃symb,¨names mat←mat[⎕AV⍋0 1↓mat;] w←5×⌈(1↓d←⍴mat←' ',' ',¯1⌽':',1⌽mat)÷5 ⍝ list's dims mat←((f×p←⌈d[⍳1]÷f←⌊width÷w),w)↑mat ⍝ adjust dims mat←(p,f×w)⍴mat ⍝ optimized formatting mat←(0,-⊥⍨' '∧.=mat)↓mat ⍝ remove extra columns ∇ ∇ r←Xqz s ⍝ danb 831012 squeeze out extra spaces r←1↓(r⍲1⌽r←s∊' ')/s←,' ',s ∇ :EndClass