Downey_HUff
Downey_HUff

Reputation: 191

How to find the script location where the called proc resides?

The script have sourced N number of files..,

source file 1
source file 2
.
.
source file N

when particular procedure A called ., Its actually present in most of the sourced files., anyway the last sourced file containing that proc A will do the function., how to find which file containing the proc is used when i call the proc ? Any code i can use to achieve it ?

Upvotes: 2

Views: 1170

Answers (1)

Donal Fellows
Donal Fellows

Reputation: 137557

The simplest way (assuming Tcl 8.5 or 8.6) is to use an execution trace to call info frame to get the details of the call stack.

trace add execution A enter callingA
proc callingA args {
    set ctxt [info frame -1]
    if {[dict exists $ctxt file] && [dict exists $ctxt proc]} {
        puts "Called [lindex $args 0 0] from [dict get $ctxt proc] in [dict get $ctxt file]"
    } elseif {[dict exists $ctxt proc]} {
        puts "Called [lindex $args 0 0] from [dict get $ctxt proc] (unknown location)"
    } else {
        # Fallback
        puts "Called [lindex $args 0 0] from within [file normalize [info script]]"
    }
}

There's quite a bit of other information in the dictionary returned by info frame.


For Tcl 8.4

In Tcl 8.4, you don't have info frame and Tcl doesn't remember where procedures are defined by default. You still have execution traces though (they were a new feature of Tcl 8.4) so that's OK then. (We have to be a bit careful with info script as that's only valid during the source and not after it finishes; procedures tend to be called later.)

To get where every procedure is defined, you have to intercept proc itself, and to do so early in your script execution! (Procedures defined before you set up the interceptor aren't noticed; Tcl's semantics are purely operational.) Fortunately, you can use an execution trace for this.

proc procCalled {cmd code args} {
    if {$code==0} {
        global procInFile
        set procName [uplevel 1 [list namespace which [lindex $cmd 1]]]
        set procInFile($procName) [file normalize [info script]]
    }
}
# We use a leave trace for maximum correctness
trace add execution proc leave procCalled

Then, you use another execution trace on the command that you want to know the callers of to look up what that command is called, and hence where it was defined.

proc callingA args {
    # Wrap in a catch so a lookup failure doesn't cause problems
    if {[catch {
        set caller [lindex [info level -1] 0]
        global procInFile
        set file $procInFile($caller)
        puts "Calling [lindex $args 0 0] from $caller in $file"
    }]} {
        # Not called from procedure!
        puts "Calling [lindex $args 0 0] from within [file normalize [info script]]"
    }
}
trace add execution A enter callingA

Upvotes: 2

Related Questions