Question
Anna Golitsyna · Dec 14, 2022

How to get current routine line?

I am familiar with $TEXT which can get you any line in the current routine provided you know the offset. For example, $T(+1) will get you the first line of the current routine at the run time. In the same vein, how do I reference the current line number/offset at the run time? Something like $T(+$CURRENTLINENUMBER) where $CURRENTLINENUMBER is not yet known to me function. The sample below would write 3 as the line number.

RTNNAME
 S A=1
W $CURRENTLINENUMBER

Product version: Caché 2017.1
1
1 260
Discussion (13)4
Log in or sign up to continue

Hi Cristiano, 
This might do for my ultimate purposes, thanks, but strictly speaking $STACK does not return the line number which in this case is 7 (Teste+2). It returns line number relative to the enclosing function only. So I'll wait in case there is a different solution as well.

Hi Anna,

You can look at the property CurrentSrcLine of %SYS.ProcessQuery class (guess SMP uses it showing process details on Process page). Keep in mind that there is more sense to examine the state of some other process rather than the current one as in the last case you will see the very code line where you are examining the process :).

Not quite what I was looking for but very interesting in its own right. Thanks, Alexey!

Hi Anna. I have written  a function to work for MAC, INT and INT generated from Classes, based on stack comment earlier. For user to test and confirm suitable for purpose :)

Usage from a class method or routine. Returns the line number. Arguments optional to capture other context.  

// how to call
set linenumber=##class(DC.LineNumber).SrcLineNumberFromStack(.routine,.label,.offset,.src)
// debug
write !,"routine: ",routine
write !,"label: ",label
write !,"offset: ",offset
write !,"linenumber: ",linenumber
write !,"src:",src

Implementation:

Class DC.LineNumber [ Abstract ]
{ 
ClassMethod SrcLineNumber(routine As %String, label As %String, offset As %Integer = 0, Output src) As %Integer
{
if '$Data(^rMAC(routine)),$Data(^ROUTINE(routine)) {
// INT code only possibly generated
set globref="^ROUTINE" 
else {
set globref="^rMAC" 
}
set labelLen=$Length(label)
set line=0
for {
// Try use rMAC if available as ROUTINE may have subsequent imported includes changing original line offsets
set line=$Order(@globref@(routine,0,line),1,data)
// reached the end of the routine
quit:line=""
set matchTest=$Extract(data,1,labelLen)
// discard non-matching lines
continue:$Extract(data,1,labelLen)'=label
// Looking for an exact match eg: Myline
// skip linelabels with prefix same as exact line label
// eg: MyLableBefore, MyLableAfter
// ie comprised by following alphanumeric character 
continue:$Extract(data,labelLen+1)?1AN
// Found the line so quit here
quit:matchTest=label
}
set line=line+offset
set src=$Select(line>0:$Get(@globref@(routine,0,line)),1:"")
quit line
}
/// Unpack current location
ClassMethod SrcLineNumberFromStack(Output routine As %String, Output label As %Integer, Output offset As %Integer, Output src As %String) As %Integer
{
quit:$Stack=1 0
set place=$Stack(($Stack)-1,"PLACE")
set routine=$Piece($Piece(place,"^",2)," ")
set offset=+$P($Piece(place,"^"),"+",2)
set label=$P($Piece(place,"^"),"+")
set line=(..SrcLineNumber(routine,label,offset,.src))
quit line
}

Things are not so easy as they seem, you have to consider scopes too. Take the above class (DC.LineNumber) and add three more methods:

ClassMethod CaseA(x)
{
    if x goto zTest
    quit "A0"
    
zTest quit "A1"
}

ClassMethod CaseB(x)
{
    if x goto Test
    quit "B0"
    
Test quit "B1"
}

ClassMethod Test()
{
    write ..CaseA(0),..CaseA(1) set linenumber=..SrcLineNumberFromStack(.routine,.label,.offset,.src) do prt
    write ..CaseB(1),..CaseB(0) set linenumber=..SrcLineNumberFromStack(.routine,.label,.offset,.src) do prt
    quit
    
    // debug
prt	write !,"routine: ",routine
    write !,"label: ",label
    write !,"offset: ",offset
    write !,"linenumber: ",linenumber
    write !,"src:",src,!!
}

and now do the test:

do ##class(DC.LineNumber).Test()

and check the output... 

OK, I know, this is a (very) constructed case and shouldn't coincide with an everyday development style, but who knows, what a mad programer sometimes produces...

Write $Stack($Stack,"PLACE")

Or, if you're really interested in the code at that line:
$Stack($Stack,"MCODE")

ROUTINE ztest
line ; the entry point
  w $Stack($stack,"MCODE"),!
  q

 If run, it would apparently print the current source line... which does nothing but printing itself:

USER> do line^ztest
w $Stack($stack,"MCODE"),!

Oh yes, while the offset from the beginning is meaningful in a .mac or .int routine, within a .cls, it's a mostly meaningless number, whereas the offset from the start of the current method is the meaningful one there.

When using the "MCODE" feature, it does indeed help if there is something else in that line that is worth tracing...

Agree with you. IMHO, the original question has no definite answer: to get the executed code line offset of the current process, this (arbitrary!) line of code should contain the call of some "checker". Or there should be the limited number of selfsufficient "points of interest" in the code.