Question Anna Golitsyna · Feb 4

How to find all globals in routines?

The task is to find all globals that are referenced in certain routines. I could search for ^ using class(%Studio.Project).FindInFiles  but that would also find ^ in comments and function calls. I can distinguish between a global and a function call visually, but it would be lovely to be able to skip function calls programmatically. Is it possible?

The Find and Replace screen in Studio has a capability of searching for ^ with Match Element Type set to Global Variable. Maybe I could use that but what is the ObjectScript function behind this screen if any?

Product version: IRIS 2022.1

Comments

Julius Kavay · Feb 4

With the help of a standard editor only - that will be a difficult and cumbersome task, for example, in the below snippet try to find the "^Test" global (not the routine ^Test):

set value = ^Test// take a value from the global ^Testdo^Test// call the routine ^Test// a more complex case//set ref = "^Test"// in this case you are luckyset ref = "^" _ "Test"// this could be a stumbling stone// few lines laterset value = @ref  // get the value of ^Test globaldo @ref           // call the routine ^Test

You will need some kind of an "intelligent" editor which can make a difference between a call (do) like operation and a "get-value" like operation. The one I know of (but I have never used it, so I don't know how good it works) is the RE/parser of GJS. Asking Google is a good starting point.

0
Yaron Munz · Feb 5

I recommend using visual studio code (vs-code) where you can search with regex. searching. Consider also seqdhing for 0-n while spaces to elimnate all spaces, tabs etc.
for example: a reference to a global could be:
set ^global( ... )= 
s ^global( ... )=
s:cond ^global( ... )=

If combined with other commands: then you should search for the comma (,) e.g.
set var=someting,^global( ...)=
 

However, use of indirection is very complex to find... (you need to skip 0-n of any characters including new lines between the set and the use of @)

0
Anna Golitsyna  Feb 5 to Yaron Munz

Thanks, Yaron. I'll skip looking for indirections for now.

0
Julius Kavay  Feb 7 to Yaron Munz

And do not forget, if the application has/uses parts of "older code" then the so called "naked syntax" may also be a issue (of course not, if you just want to know the name of the global).

Classmethod Test()
{
  kill^myGlobalkill^yourGlobalset^myGlobal(2)="some data"do..moreData("data1")
  set^yourGlobal(3)="other data"do..moreData("data2")
  
  // Now, the globals look like//// ^myGlobal(2)="some data"// ^myGlobal(9)="data1"//// ^yourGlobal(3)="other data"// ^yourGlobal(9)="data2"
}

ClassMethod moreData(data)
{
  set ^(9)=data	
}

Beside all the "nice" combinations of direct sets, indirections, naked synates etc. do not forget, your application may call routinies/methods which are in deployed mode (third party APIs and utilities - hopefully with  documentation)

0
Brett Saviano · Feb 5

@Anna.GolitsynaIf you goal is to find all globals referenced in a document, you can use a modified version of the code I included in this comment. The code uses the %SyntaxColor class to get a JSON array of semantic tokens for a document, and then loops through them looking for global references. Note that this will only find literal references, not naked references or indirection.

ClassMethod WriteAllGrefs()
{
    Set syn = ##class(%SyntaxColor).%New(), in = ##class(%Stream.TmpCharacter).%New(), out = ##class(%Stream.TmpCharacter).%New()
    #; TODO Put your document's contents into "in"Do syn.Color(in,out,"COS"/* or "INT" or "CLS" */,"KE"/* K means JSON output, E means keep empty lines */)
    #; Format of the JSON output:
    #; [
    #;     #; One array for each source line
    #;     [
    #;         {
    #;             #; Language of the token. See Languages() in %Library.SyntaxColor.
    #;             "l": %Integer,
    #;             #; Attribute of the token. See Attributes() in %Library.SyntaxColor.
    #;             "s": %Integer,
    #;             #; Zero-indexed start position of the token on the line
    #;             "p": %Integer,
    #;             #; Length of the token in characters
    #;             "c": %Integer
    #;         }
    #;     ]
    #; ]Set json = ##class(%DynamicArray).%FromJSON(out), lineIter = json.%GetIterator()
    While lineIter.%GetNext(.lineNum,.lineTokens) {
        Set tokensIter = lineTokens.%GetIterator()
        While tokensIter.%GetNext(,.token) {
            If (
                #; COS
                (token.l = 1) &&
                #; Global reference
                (token.s = 18)
            ) {
                Write"Gref starting in column ",token.p + 1," of line ",lineNum + 1,!
            }
        }
    }
}

You can combine this with the %Library.RoutineMgr_StudioOpenDialog query to make an index of all globals referenced in a subset of documents.

0
Anna Golitsyna  Feb 5 to Brett Saviano

Thanks, Brett. I did try using SyntaxColor but not with JSON and did not like the HTML results. I'll give your code a try.

0
Anna Golitsyna  Feb 5 to Brett Saviano

Excellent, just excellent. It did find correctly all globals in a routine (no indirection). I populated in = ##class(%Stream.TmpCharacter).%New() with this code:
NumLines=^ROUTINE(routineName,0,0)   ; Omit extension
n=0:1:NumLines {
    S line=$T(@routineName+n^@routineName)
    D in.Write(line),in.Write($c(13,10))
}
in.Rewind()

I also added this after your Write:
                Set line=$G(^ROUTINE(rtnName,0,lineNum+1))
                Write $C(9),$E(line,token.p+1,token.p+token.c),!
I did not try finding globals in classes, but I assume this would be very similar. Is there any online documentation that shows that token value for globals is 18? Would be curious about other token values.

0
Brett Saviano  Feb 5 to Anna Golitsyna

I'm glad you find this useful! Instead of looping through ^ROUTINE or ^rMAC to build the stream, you can open a %Routine object and pass that in as the first argument. That class implements the Stream interface. You can get a list of languages and attributes per language from a %SyntaxColor object using the Languages() and Attributes() methods.

0
Anna Golitsyna  Feb 5 to Brett Saviano

I have an odd problem. In vast majority of cases globals are defined by your code correctly. However, for some routines the lineNum as returned by GetNext is one line count off and the global with the stated token attributes is actually on the next line. I tried to slightly modify such routines and recompile, but it did not always help. Any advice?

0
Brett Saviano  Feb 5 to Anna Golitsyna

Can you give me an code snippet?

0
Anna Golitsyna  Feb 5 to Brett Saviano

I will have time to prepare such a snippet tomorrow, sorry. Meanwhile, it's not necessarily one line off but up to several lines off, a few routines only. In your code, is it possible to extract text of the line for which tokens are calculated? I looked around and did not see such a possibility.

0
Anna Golitsyna  Feb 6 to Brett Saviano

I just messaged you two snippets. Thanks again.

0
Brett Saviano  Feb 6 to Anna Golitsyna

The issue is that I forgot to add the E/e flag to the list of flags for the Color() method, so empty lines were removed from the JSON output. I will edit the accepted answer.

0
Anna Golitsyna  Feb 7 to Brett Saviano

Yes, the problem is gone! Thanks a lot for your help.

0