Can you get the "proc name" inside a proc? - tcl

Within a proc can you get the proc name (without hardcoding it)? e.g.
proc my_proc { some_arg } {
puts "entering proc [some way of getting proc name]"
}

Of course you can!
Use info level command:
proc my_proc { some_arg } {
puts "entering proc [lindex [info level 0] 0]"
}
and you get exactly what you want
entering proc my_proc
Another way is to use info frame, which gives a dictionary with some other info, and read the proc key:
proc my_proc { some_arg } {
puts "entering proc [dict get [info frame 0] proc]"
}
this time, you'll get the fully qualified proc name:
entering proc ::my_proc

Related

Tcl: How to get namespace and procedures of calling namespace?

I have some generic procedure. I would like this procedure to be able to get the name of the namespace and names of the procedures within the namespace where this procedure is called.
I have tried following code:
proc register {} {
puts [info procs]
puts [namespace current]
}
namespace eval Foo {
proc bar {} {
puts bar
}
proc _baz {} {
puts baz
}
register
}
However, this prints results for the namespace where register is defined, not for the namespace where it is executed. It looks like there are no dedicated commands for these tasks or at least these are not info or namespace commands.
To get information about the calling context, use uplevel:
proc register {} {
puts [uplevel 1 [list info procs]]
puts [uplevel 1 [list namespace current]]
}

How to reference a variable within a proc in another proc

I have a proc that evaluates an expr and appends to a particular list locally
proc a {} {
set mylist ""
set out [expr...
lappend mylist $out
}
I want to use the "mylist" list outside of the "a" proc without declaring it as global or without returning that list from within the proc using "return mylist". How do I go about doing that. I have two use cases, Use the variable within another proc:
proc b {} {
do something with the "mylist" from proc a
}
Use case 2 :
Just use it outside the proc [Not within another proc]
The "mylist" variable only exists as long as proc a is being executed. Whenever a proc finishes, all its local variables are cleaned up.
As long as a is in progress, you can access its variables using the upvar command.
For example: if you call b from a, b can access "mylist" using:
upvar 1 mylist othervar
puts $othervar
However, it is usually better practice to pass the variable (or at least its name) between procs, or make it a global or namespace variable.
Reference: https://www.tcl-lang.org/man/tcl/TclCmd/upvar.htm
Sample code snippet:
proc foo {ref_var} {
upvar $ref_var local_var
# do some operatins
lappend local_var 20
}
proc bar {} {
set a [list 10]
puts "Before: $a"
foo a
puts "After: $a"
}
# nested proc
bar
# without proc
set c [list 30]
puts "Initial: $c"
foo c
puts "Final: $c"
Output:
Before: 10
After: 10 20
Initial: 30
Final: 30 20

Renaming puts in namespace causes issue in TCL

When I am trying to rename the puts command inside name-space, It causes the problem. I am renaming puts because I don't want to display the echo statement of particular procedure.
namespace eval temp {
namespace export print_proc
proc replacement_puts args {}
proc silentEval {script} {
rename puts original_puts
interp alias {} puts {} temp::replacement_puts
catch [list uplevel 1 $script] msg opts
rename puts {}
rename original_puts puts
return -options $opts $msg
}
proc print_proc {} {
puts "before call"
silentEval {a}
puts "aftter call"
}
proc a {} {
puts "inner call"
}
}
package provide temp 1.0
In Example, I don't want to display the echo statement of proc a.
But after execution, It shows error that Invalid Command Name "puts"
Thanks
Glenn Jackman's answer should solve your problem. I'd just like to point out that you don't really need to rename anything. With this definition:
namespace eval temp {
namespace export print_proc
proc puts args {}
proc silentEval script {
catch [list uplevel 1 $script] msg opts
return -options $opts $msg
}
proc print_proc {} {
::puts "before call"
silentEval a
::puts "aftter call"
}
proc a {} {
puts "inner call"
}
}
when a is invoked and it calls puts, it will actually invoke the ::temp::puts command in preference to the global puts -- in effect, the namespace puts overrides the global puts. In print_proc we want the global puts to be invoked, so we add :: before the name.
Of course, all this goes for scripts defined within ::temp, like in the example.
(There isn't much point in calling catch if you're just going to re-throw the exception, but I suppose this is just placeholder code.)
Documentation: catch, list, namespace, proc, puts, return
You just have to be explicit that you're altering the global puts
namespace eval temp {
proc silentEval {script} {
rename ::puts ::original_puts
proc ::puts args {}
catch [list uplevel 1 $script] msg opts
rename ::puts {}
rename ::original_puts ::puts
return -options $opts $msg
}
}

How to find a procedure by using the code inside the proc?

Is it possible to find the procedure name by using the content of that procedure?
For example,
proc test {args} {
set varA "exam"
puts "test program"
}
Using the statement set varA, is it possible to find its procedure name test?
Because, I need to find a procedure for which i know the output [it's printing something, i need to find the procedure using that].
I tried many ways like info frame, command. But, nothing helps.
Is it possible to find the procedure name by using the content of that procedure?
Yes. You use info level 0 to get the argument words to the current procedure (or info level -1 to get its caller's argument words). The first word is the command name, as resolved in the caller's context. That might be enough, but if not, you can use namespace which inside an uplevel 1 to get the fully-qualified name.
proc foo {args} {
set name [lindex [info level 0] 0]
set FQname [uplevel 1 [list namespace which $name]]
# ...
}
Note that this does not give you the main name in all circumstances. If you're using aliases or imported commands, the name you'll get will vary. Mostly that doesn't matter too much.
With info proc, we can get the content of a procedure which may helps you in what you expect.
The following procedure will search for the given word in all the namespaces. You can change it to search in particular namespace as well. Also, the search word can also be case insensitive if altered in terms of regexp with -nocase. It will return the list of procedure names which contains the search word.
proc getProcNameByContent {searchWord} {
set resultProcList {}
set nslist [namespace children ::]; # Getting all Namespaces list
lappend nslist ::; # Adding 'global scope namespace as well
foreach ns $nslist {
if {$ns eq "::"} {
set currentScopeProcs [info proc $ns*]
} else {
set currentScopeProcs [info proc ${ns}::*]
}
foreach myProc $currentScopeProcs {
if {[regexp $searchWord [info body $myProc]]} {
puts "found in $myProc"
lappend resultProcList $myProc
}
}
}
return $resultProcList
}
Example
% proc x {} {
puts hai
}
% proc y {} {
puts hello
}
% proc z {} {
puts world
}
% namespace eval dinesh {
proc test {} {
puts "world is amazing"
}
}
%
% getProcNameByContent world
found in ::dinesh::test
found in ::z
::dinesh::test ::z
%

Unable to pass a variable to a procedure using upvar in Tcl

I need a procedure that will be able to access, read and change a variable from the namespace of the caller. The variable is called _current_selection. I have tried to do it using upvar in several different ways, but nothing worked. (I've written small test proc just to test the upvar mechanism). Here are my attempts:
call to proc:
select_shape $this _current_selection
proc:
proc select_shape {main_gui var_name} {
upvar $var_name curr_sel
puts " previously changed: $curr_sel"
set curr_sel [$curr_sel + 1]
}
For my second attempt:
call to proc:
select_shape $this
proc:
proc select_shape {main_gui} {
upvar _current_selection curr_sel
puts " previously changed: $curr_sel"
set curr_sel [$curr_sel + 1]
}
In all the attempts, once it reaches this area in the code it says can't read "curr_sel": no such variable
What am I doing wrong?
EDIT:
The call for the function is made from a bind command:
$this/zinc bind current <Button-1> [list select_shape $this _current_selection]
at start I thought that it doesn't matter. but maybe It does.
I believe that bind commands operate in the global namespace, so that's where the variable is expected to be found. This might work:
$this/zinc bind current <Button-1> \
[list select_shape $this [namespace current]::_current_selection]
for upvar to work the variable must exist in the scope that you are calling it in. consider the following:
proc t {varName} {
upvar $varName var
puts $var
}
#set x 1
t x
If you run it as it is you'll get the error you are reporting, uncomment the set x 1 line and it will work.
In the example below I've tried to cover the most variants of changing variables from other namespace. It 100% works for me. Maybe it will help.
proc select_shape {main_gui var_name} {
upvar $var_name curr_sel
puts " previously changed: $curr_sel"
incr curr_sel
}
namespace eval N {
variable _current_selection 1
variable this "some_value"
proc testN1 {} {
variable _current_selection
variable this
select_shape $this _current_selection
puts " new: $_current_selection"
}
# using absolute namespace name
proc testN2 {} {
select_shape [set [namespace current]::this] [namespace current]::_current_selection
puts " new: [set [namespace current]::_current_selection]"
}
select_shape $this _current_selection
puts " new: $_current_selection"
}
N::testN1
N::testN2
#-------------------------------------
# Example with Itcl class
package require Itcl
itcl::class C {
private variable _current_selection 10
public method testC {} {
select_shape $this [itcl::scope _current_selection]
puts " new: $_current_selection"
}
}
set c [C #auto]
$c testC