Tcl : pass all variables from main to procedure - tcl

In my tcl script there is a part of the code that is repeated a lot, so I want to make a procedure out of it.
The thing is this part uses dozens of variables, which I would like to avoid passing as arguments to the procedure. Is there a way to make all variables visible to the procedure? (Practically I want the "main" to branch to the procedure like a "goto" and then return and continue in main).
Edited: It does not need to be a procedure, feel free to suggest other ways to do this. The important part is not need to declare all variables/arguments passing from main to the function/procedure.
Example:
proc dummy_proc {} {
set var1 $var2
set var2 $var3
}
set var2 2
set var3 3
dummy_proc
puts "$var1 $var2"
# should print "2 3"

This is possible but generally not advisable due to the fact that it can make code harder to read (there's no direct indication where the variables come from or how variable values suddenly change). However in some cases this can reduce repetitive code.
Use upvar (https://www.tcl.tk/man/tcl8.6/TclCmd/upvar.htm):
proc dummy_proc {} {
upvar var1 v1
upvar var2 v2
upvar var3 v3
set v1 $v2
set v2 $v3
}
What upvar does is create a variable in local scope that references another variable in the caller's scope.
Alternatively you can also try using uplevel (https://www.tcl.tk/man/tcl8.6/TclCmd/uplevel.htm):
proc dummy_proc {} {
uplevel {
set var1 $var2
set var2 $var3
}
}
What uplevel does is similar to upvar but instead of creating variable references it actually executes code in the caller's scope. It's as if you temporarily go back to the caller function without returning, execute some code and come back. Because you execute code in the caller's scope all variables visible in the caller's scope is visible in the code you upleveled. Uplevel behaves almost like a macro instead of a function.

Use global
proc dummy_proc {} {
global var2 var3
set var1 $var2
set var2 $var3
}
set var2 2
set var3 3
dummy_proc
puts "$var1 $var2"
If you have a lot of globals you want to pass, you can use some foreach, but than you'll have to have a way to find them all.
e.g. all globals are called GLOBAL_<SOMEARG>
proc dummy_proc {} {
foreach glb [info globals GLOBAL_*] {
global $glb
}
...
}

Related

Tcl - How to read a nested variable from inside a procedure

I am trying to do the following:
set myvar_key something
set a myvar
proc test {} {
# I am expecting mylocal_var to be "something", but it errors
set mylocal_var [set ${::a}_key]
}
# The proc is called like this
test
Thanks in advance,
Pedro
In these sorts of scenarios, it tends to be easier to use upvar to make a local alias for a variable in another scope. Yes, you can do trickery with set and such, but it tends to be harder to use, especially in real code. Once you've done the upvar, the local name is just another (highly efficient) way of accessing the named variable in another scope.
set myvar_key something
set a myvar
proc test {} {
# The #0 is quoted *just* for Stack Overflow's highlighting!
upvar "#0" ${::a}_key mylocal_var
}
If you were willing to rearrange your variables, you could instead do:
set key(myvar) something
set a myvar
proc test {} {
global a key
set mylocal_var $key($a)
}
But that does change what the main variable is so it isn't suitable in all cases. And you can do hybrids:
set key(myvar) something
set a myvar
proc test {} {
upvar "#0" key($::a) mylocal_var
}
You're almost there, but just missed something. As your code already shows, a is in the global namespace, thus you need ::a.
Same is true for myvar_key, thus you need to do
set myvar_key something
set a myvar
proc test {} {
set mylocal_var [set ::${::a}_key]
puts $mylocal_var
}
test
prints "something"

How to access a variable defined in proc 'a' from a different proc 'b' which does not call proc 'a'?

I am trying to execute a tcl script which makes exclusive calls to procs a and b. The two procs are not related to each other.
proc a {} {
set var1 "a"
}
proc b {} {
# Do something here with: $var1
}
# script.tcl
a
b
I do not have access to the script.tcl as well. When proc 'a' is called, I need to store the var1 somehow such that I can access it later within proc 'b' when it is called. How can I get the value of var1 in proc b? Doesn't seem like I can use 'global' and 'upvar' here?
A simple way is to define the variable in the global scope by preceding the variable name with ::
proc a {} {
set ::var1 "a"
}
proc b {} {
puts $::var1
}
Other methods would be to use the global command in each proc or to define the variable in a special namespace of its own.
Using variable instead of global offers a bit more flexibility if namespaces are involved
namespace eval n {
proc a {{value A}} {
variable var1
set var1 $value
return
}
proc b {} {
variable var1
puts "var1 is <$var1>"
}
}
n::a
n::b ;# => var1 is <A>
namespace eval n {a 42; b} ;# => var1 is <42>
puts $::var1 ;# => can't read "::var1": no such variable

What purpose does upvar serve?

In the TCL code that I currently work on, the arguments in each procedure is upvar'ed to a local variable so to speak and then used. Something like this:
proc configure_XXXX { params_name_abc params_name_xyz} {
upvar $params_name_abc abc
upvar $params_name_xyz xyz
}
From here on, abc and xyz will be used to do whatever. I read the upvar TCL wiki but could not understand the advantages. I mean why cant we just use the variables that have been received as the arguments in the procedure. Could anybody please elaborate?
I mean why cant we just use the variables that have been received as the arguments in the procedure.
You can. It just gets annoying.
Typically, when you pass the name of a variable to a command, it is so that command can modify that variable. The classic examples of this are the set and incr commands, both of which take the name of a variable as their first argument.
set thisVariable $thisValue
You can do this with procedures too, but then you need to access the variable from the context of the procedure when it is a variable that is defined in the context of the caller of the procedure, which might be a namespace or might be a different local variable frame. To do that, we usually use upvar, which makes an alias from a local variable to a variable in the other context.
For example, here's a reimplementation of incr:
proc myIncr {variable {increment 1}} {
upvar 1 $variable v
set v [expr {$v + $increment}]
}
Why does writing to the local variable v cause the variable in the caller's context to be updated? Because we've aliased it (internally, it set up via a pointer to the other variable's storage structure; it's very fast once the upvar has been done). The same underlying mechanism is used for global and variable; they're all boiled down to fast variable aliases.
You could do it without, provided you use uplevel instead, but that gets rather more annoying:
proc myIncr {variable {increment 1}} {
set v [uplevel 1 [list set $variable]]
set v [expr {$v + $increment}]
uplevel 1 [list set $variable $v]
}
That's pretty nasty!
Alternatively, supposing we didn't do this at all. Then we'd need to pass the variable in by its value and then assign the result afterwards:
proc myIncr {v {increment 1}} {
set v [expr {$v + $increment}]
return $v
}
# Called like this
set foo [myIncr $foo]
Sometimes the right thing, but a totally different way of working!
One of the core principles of Tcl is that pretty much anything you can do with a standard library command (such as if or puts or incr) could also be done with a command that you wrote yourself. There are no keywords. Naturally there might be some efficiency concerns and some of the commands might need to be done in another language such as C to work right, but the semantics don't make any command special. They all just plain commands.
The upvar command will allow you to modify a variable in a block and make this modification visible from parent block.
Try this:
# a function that will modify the variable passed
proc set_upvar { varname } {
upvar 1 $varname var
puts "var was $var\n"
set var 5
puts "var is now $var\n"
}
# a function that will use the variable but that will not change it
proc set_no_upvar { var } {
puts "var was $var\n"
set var 6
puts "var is now $var\n"
}
set foo 10
# note the lack of '$' here
set_upvar foo
puts "foo is $foo\n"
set_no_upvar $foo
puts "foo is $foo\n"
As it was mentioned in comment above, it is often used for passing function arguments by reference (call by reference). A picture costs a thousand words:
proc f1 {x} {
upvar $x value
set value 0
}
proc f2 {x} {
set x 0
}
set x 1
f1 x
puts $x
set x 1
f2 x
puts $x
will result in:
$ ./call-by-ref.tcl
0
1
With upvar we changed variable x outside of function (from 1 to 0), without upvar we didn't.

using variables assingned in the script inside the proc in TCL

i want to use the variable assigned outside (proc) to be used inside the proc . For example i tried the following thing
set a 10
proc myproc { } {
puts $a
}
myproc
I am expecting the above script to print 10 . But the above script is erroring out "can't read "a": no such variable"
I cannot pass $a as argument to script because i have lot such variables i want to use inside my proc inside my script . Could you please help me to solve this problem ?
Your help is appreciated
If the variable is declared at the same stack level as the call to myproc then you can do following in your proc:
upvar a a
like this:
set a 10
proc myproc { } {
upvar a a
puts $a
}
myproc
and then you can use $a locally in the procedure. The upvar command "links" a variable declared somewhere in the stack with a local variable. If the variable is declared more than 1 level deeper in the stack, thn you need to pass "2" to upvar, so it knows where to look for the variable:
upvar 2 a a
If you don't pass the "2" (or other value), the upvar assumes default lookup depth of 1.
You can read more details about upvar in Tcl documentation for that command.
If the variable a is always a global variable (declared at the script top level), then you can use:
global a
in your procedure, instead of upvar.
If you have namespaces you could always assign it there :
namespace eval blah {
variable a 10
}
proc blah::myproc { } {
variable a
puts $a
}
blah::myproc
This way you can avoid potential collisions with other global variables

what are the practical differences between upvar and global commands in tcl

I'm fairly new to TCL, and am providing QA on some code developed by others (no really!). There are lots and lots of global variables in this particular program, and I sometimes see upvar used, often in conjunction with global. I understand that upvar emulates pass-by-reference, but what would be the practical difference be between the two following procs?
set myBigFatGloblVariable "hello"
proc myFirstProc { var1 var2 } {
upvar 1 $var1 local
set local [expr $var2 * 3]
}
proc mySecondProc { var2 } {
global myBigFatGlobalVariable
set $myBigFatGlobalVariable [expr $var2 * 3]
}
myFirstProc $myBigFatGlobalVariable 3
mySecondProc 3
It seems to me that myFirstProc would be cleaner and . Am I missing something here?
They are similar but subtly different.
upvar allows you access variables up x levels in the call stack.
They don't necessarily need to be global variables.
You can use upvar to emulate global by passing upvar #0 varName localVarName
You will get the global variable with a local name in that case.
To emulate pass by reference, you are pass the name of the variable, then call upvar on that name.
If you know the name of the variable, you can use it as is.
Observe the following code:
# here there is only 1 global variable, but we also need to access to variables defined in the calling functions
proc p3 {} {
# upvar defaults to 1, so not needed to put in here
# also notice you can call upvar on more than one variable
upvar dog myDog horse myHorse cat myCat
upvar 2 cow myCow alpha myAlpha
upvar #0 samurai mySamurai
puts "Level 1: $myDog $myHorse $myCat"
puts "Level 2: $myCow $myAlpha"
puts "Global : $mySamurai"
}
proc p2 {} {
set dog "bowow"
set horse "niegh"
set cat "meow"
p3
}
proc p1 {} {
set cow "moo"
set alpha "beta"
p2
}
set samurai "japan"
p1
This returns
Level 1: bowow niegh meow
Level 2: moo beta
Global : japan
upvar is just a way to get at variables from the call stack. (calling functions) including the 'global' stack.
set myBigFatGlobalVariable "hello"
proc myFirstProc { var1 var2 } {
upvar 1 $var1 local
set local [expr $var2 * 3] }
proc mySecondProc { var2 } {
global myBigFatGlobalVariable
set $myBigFatGlobalVariable [expr $var2 * 3] }
myFirstProc $myBigFatGlobalVariable 3
mySecondProc 3
The big difference between your two procs is this: myFirstProc sets the global "hello", mySecondProc sets the local "hello".
mySecondProc references the global myBigFat... to get the value "hello", but does not alter the scope of the "hello" variable.
myFirstProc receives the value "hello" as a parameter, and then creates a link between a variable named "hello" one frame up the stack and the local variable "local". Setting "local" has the effect of setting "hello" one frame up the stack.
To see:
myFirstProc $myBigFatGlobalVariable 3
puts $hello ;# ==> 9
unset hello
mySecondProc 3
puts $hello ;# ==> can't read "hello": no such variable
If you really want to set the global "hello" from mySecondProc, you'll need to add global $myBigFatGlobalVariable
The difference is that upvar 1 $var local makes local take its value from the variable named in $var from the level above. So in myBigFatGlobalVariable $var does not have to be defined at the global scope.
proc p1 { var1 } {
upvar 1 $var1 local1
puts $local1
}
proc p2 { } {
set local2 "local2"
p1 local2
}
set global1 "global1"
p1 global1
p2
p1 will print out the value of var1 from the level 1 above it in the call stack. A global is always defined at the top level so upvar #0 does the same thing as global.
You are saying:
There are lots and lots of global
variables in this particular program
My experience with medium to very large Tcl applications (20k+ lines!) is that using namespaces will significantly help getting structure within the large amount of global variables.
The nice part is, is that you can add them iteratively everytime you create a new module to your code, or by refactoring some of your code.
namespace eval module1 {
variable counter
variable name
}
namespace eval module2 {
variable n
variable names
}
You can refer to them via module1::counter (just as you can refer to a global variable as ::counter
See the wiki namespace page and the Tcl manual page on namespaces for more information on namespaces.