how do I update a variable via a tk window by name - namespaces

Consider the following situation:
namespace eval ::mydialog {}
proc ::mydialog::show {w varName args} {
upvar 1 $varName theVar
# now I can access theVar
# (1)
# code defining/creating my window
# here some widgets for user interaction are created,
# some of which will call ::mydialog::_someCallback
wm protocol $w WM_DELETE_WINDOW [list ::mydialog::close $w]
}
proc ::mydialog::_someCallback {} {
# how do I access theVar here?
# (2)
}
proc ::mydialog::close { w } {
# here some changes are supposed to be written back into varName in the calling scope,
# how do I do that?!
# (3)
destroy $w
}
Im trying to figure out how to (a) get a variable from the calling scope (b) have it available in all three procs and (c) writing any changes back into said variable.
(a) I would normally solve using 'upvar 1 $varName theVar'
(b) I would normally solve with a namespace variable
(c) As long as we only have one proc that would happen automaticly with (a) due to the fact that we would be working on a local alias of that variable
The problem is that upvar only works (at least as intended) in (1).
I could use upvar in (1) and save/copy into a namespace variable, that would solve (a) and (b), but not (c).
I would be gratefull if someone could point me in the right direction here.
Also, as I'm relativly new to Tcl/Tk my concept might not be ideal, suggestions toward a better design are welcome too.

I suggest you use a namespace variable that keeps the name of the variable, and upvar using the global scope.
namespace eval ::mydialog {
variable varName
}
proc ::mydialog::show {w _varName args} {
variable varName $_varName
upvar #0 $varName theVar
}
proc ::mydialog::_someCallback {} {
variable varName
upvar #0 $varName theVar
puts $theVar
}
proc ::mydialog::close { w } {
variable varName
upvar #0 $varName theVar
set theVar newval
}
set globalvar oldval
# => oldval
::mydialog::show {} globalvar
::mydialog::_someCallback
# => oldval
::mydialog::close {}
# => newval
puts $globalvar
# => newval
Note that the syntax highlighting fails: #0 $varName theVar isn't really a comment.
This works with namespace variables too: if you have a variable called nsvar in the ::foobar namespace you can use it like this:
set ::foobar::nsvar oldval
::mydialog::show {} ::foobar::nsvar
::mydialog::_someCallback
::mydialog::close {}
puts $::foobar::nsvar
with the same effects.
You can't, however, use variables local to some procedure this way.
One way to make this really simple is to use Snit widgets instead of collections of Tcl procedures.
Documentation: namespace, proc, puts, set, upvar, variable
Snit documentation: man page, faq (the faq serves as a kind of introduction as well)

Related

How do you access a procedure's local variables in a namespace eval {} inside that same procedure?

I'm sure I'm just being stupid but would you please tell me how to get access to $name inside the namespace in order to set variable n to $name? I can only find how to do this when the procedure is in the namespace but not the other way 'round. No matter what I try, this errors stating no such variable name. Thank you.
proc getNS {name} {
namespace eval ns::$name {
variable n $name
}
}
This works but isn't really an answer unless the answer is simply that it cannot be done directly. Got it from this SO question. Bryan Oakley gave the answer but used [list set...] instead of [list variable...] and that will fail if there is a global variable of the same name. (It will modify the global rather than creating a new variable in the namespace.) It may have been different, of course, in 2009 when that answer was provided.
proc getNS {name} {
namespace eval ns::$name [list variable n $name]
namespace eval ns::$name {
variable a abc
}
}
set n xyz
getNS WEBS
chan puts stdout "ns n: $ns::WEBS::n; a $ns::WEBS::a, global n: $n"
# => ns n: WEBS; a: abc; global n: xyz
You can just use set with a fully qualified variable name that uses the desired namespace:
proc getNS {name} {
namespace eval ns::$name {} ;# Create namespace if it doesn't already exist
set ns::${name}::n $name
}
getNS foo
puts $ns::foo::n ;# foo
Another way is to use uplevel to refer to the scope of the proc that calls namespace eval:
proc getNS {name} {
namespace eval ns::$name {
set n [uplevel 1 {set name}]
}
}

Tcl object method called as variable

How can one execute an object method as a variable?
oo::class create handlerTest {
method onEvent {} {
puts "onEvent method"
}
}
set testObj [handlerTest new]
#set wrapper {$testObj onEvent}
#set wrapper {$testObj::my onEvent}
#set wrapper [namespace code {$testObj onEvent}]
#set wrapper "eval testObj onEvent
#set wrapper {[eval testObj onEvent]}
$wrapper
All of the above attempts appear to execute $wrapper as a single command, not a command with args.
As I am using an external library that calls the defined wrapper, I can't change how the wrapper is called (i.e. {*}$wrapper).
Is there a way to do this?
Or:
proc theWrapper {} [
upvar 1 testObj testObj
tailcall $testObj onEvent
}
set wrapper theWrapper
$wrapper
The only way to rewrite the command name itself is via an unknown handler (defaults to being called unknown in the global namespace; you probably want to use that default). Some care needs to be taken when doing this, as the default handler does things that some code needs to have present; a bit of shuffling around with rename should do the trick.
# only want special treatment for some commands
set autoexpanded [list $testObj]
# save for later
rename unknown _original_unknown
proc unknown args {
global autoexpanded
# if we want to expand the first word...
if {[catch {lindex $args 0 0} cmd] == 0 && $cmd in $autoexpanded} {
# delegate to the expanded command (tailcall is perfect here)
set args [lassign $args cmd]
tailcall {*}$cmd {*}$args
} else {
# delegate to the original unknown
tailcall _original_unknown {*}$args
}
}
Be aware that this is not a fast dispatch mechanism. It is only used when the only other alternative is throwing an error because the command doesn't exist (also slow, but error paths are never optimal or heavily optimized).
The easiest method that comes to mind, is to generate a name for an alias and put that in the variable:
set testObj [handlerTest new]
set wrapper [interp alias {} wrapper[incr wrapperid] {} $testObj onEvent]
$wrapper
=> onEvent method

How to effectively override a procedure-local variable in TCL

So I have the following situation:
$ ls -l
-r--r----- 1.tcl
-rw-rw---- 2.tcl
$ cat 1.tcl
proc foo {args} {
puts "$bar"
}
and I need to make 1.tcl print something other than "can't read \"bar\"". In a good programming language, the obvious solution would be
$ cat > 2.tcl
set -global bar "hello, world"
foo
What would be a reasonable workaround in TCL? Unfortunately the real foo is a long function that I can't really make a copy of or sed to a temporary file at runtime.
You can do this for your specific example
$ cat 2.tcl
source 1.tcl
set bar "Hello, bar!"
# add a "global bar" command to the foo procedure
proc foo [info args foo] "global bar; [info body foo]"
foo
$ tclsh 2.tcl
Hello, bar!
Clearly this doesn't scale very well.
If the variable is simply undefined, the easiest way would be to patch the procedure with a definition:
proc foo [info args foo] "set bar \"hello, world\" ; [info body foo]"
You can also accomplish this using a read trace and a helper command. This removes the problem I mentioned above, where local assignments destroy the value you wanted to inject.
The original procedure, with an added command that sets the local variable to a value which is later printed.
proc foo args {
set bar foobar
puts "$bar"
}
% foo
foobar
Create a global variable (it doesn't matter if the name is the same or not).
set bar "hello, world"
Create a helper command that gets the name of the local variable, links to it, and assigns the value of the global variable to it. Since we already know the name we could hardcode it in the procedure, but this is more flexible.
proc readbar {name args} {
upvar 1 $name var
global bar
set var $bar
}
Add the trace to the body of the foo procedure. The trace will fire whenever the local variable bar is read, i.e. something attempts to retrieve its value. When the trace fires, the command readbar is called: it overwrites the current value of the variable with the globally set value.
proc foo [info args foo] "trace add variable bar read readbar; [info body foo]"
% foo
hello, world
If one doesn't want to pollute the namespace with the helper command, one can use an anonymous function instead:
proc foo [info args foo] [format {trace add variable bar read {apply {{name args} {
upvar 1 $name var
global bar
set var $bar
}}} ; %s} [info body foo]]
Documentation:
apply,
format,
global,
info,
proc,
puts,
set,
trace,
upvar,
Syntax of Tcl regular expressions
source 1.tcl
try {
foo
} on error {err res} {
set einfo [dict get $res -errorinfo]
if { [regexp {no such variable} $einfo] } {
puts "hello, world"
return -code 0
} else {
puts $einfo
return -code [dict get $res -code]
}
}
Tcl's procedures do not resolve variables to anything other than local variables by default. You have to explicitly ask for them to refer to something else (e.g., with global, variable or upvar). This means that it's always possible to see at a glance whether non-local things are happening, and that the script won't work.
It's possible to override this behaviour with a variable resolver, but Tcl doesn't really expose that API in its script interface. Some extensions do more. For example, it might work to use [incr Tcl] (i.e., itcl) as that does that sort of thing for variables in its objects. I can't remember if Expect also does this, or if that uses special-cased code for handling its variables.
Of course, you could get really sneaky and override the behaviour of proc.
rename proc real_proc
real_proc proc {name arguments body} {
uplevel 1 [list real_proc $name $arguments "global bar;$body"]
}
That's rather nasty though.

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

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.