How to use loop index variable inside command that will be invoked later - tcl

Let's consider the following code:
package require Tk
proc test {} {
foreach n {
1 2 3 4 5 6 7 8 9
} {
pack [button ._$n -text $n -command {puts $n}]
}
}
test
When one of the buttons invoked, "n" is unknown.
I found a away to address this by changing {puts $n} to "puts $n", but not sure this is a correct approach.

The callback for the button command is executed in the global scope, and there's no n variable there.
If you add global n command into that proc, then the error message won't appear, but each button will print the same value of n.
As you intent is to associate the value of n for each button, you need to pick a different quoting mechanism: braces prevent variable expansion. See https://www.tcl-lang.org/man/tcl8.6/TclCmd/Tcl.htm -- Shawn's comment gave you the answer.

You have to bind the current value of $n to the callback at the time you set it; your code as it stands uses whatever happens to be in the global n variable at the time that the callback is invoked (i.e., it is either the same for all the buttons or an error).
The list command is designed to be perfect for doing that binding; it generates a list, yes, but it also guarantees to generate a substitution-free command that has the words that are its arguments. That is, the script/command call:
eval [list $a $b $c]
is guaranteed to be the same as:
$a $b $c
for any values at all. This is an exceptionally useful property for almost any kind of code generation since it lets you make a script — that can be a call to a procedure for anything complicated — and pass any values over, entirely safely. In your case, this means that you should change change:
pack [button ._$n -text $n -command {puts $n}]
to:
pack [button ._$n -text $n -command [list puts $n]]

Thanks for your answers!
I ended up with the following possible ways:
"puts $n" - good for simple cases, but a bit non-intuitive
[list puts $n] - good, especially for lists
[subst {puts $n}] - good, straightforward substitution

Related

Do define/paramter(same as in verilog language) exist in TCL script?

Is there any command in TCL that is same as #define or parameter in C?
I want to reduce my code in script with define as below:
lappend links [lindex $all_list $i] $j $k 0
-> this command are used many times in my script.
How do I define it define lap_lk lappend links [lindex $all_list $i] $j $k 0 as verilog and using it in TCL script with short command lap_lk?
Thanks you very much :) .
Tcl isn't exactly the same as in C (or other languages that use that preprocessor). However, you can create procedures that have the sort of effects that you want. The type of procedure you want to make depends on whether you are passing in any arguments.
Simple, Parameterless
In the simplest case, without arguments, you can just use uplevel inside the procedure to run some code in the caller's context:
proc lap_lk {} {
uplevel 1 {
lappend links [lindex $all_list $i] $j $k 0
}
}
The code inside the uplevel (the 1 is optional, but I recommend it for clarity) is simply run as if it was run instead of the call to lap_lk; it uses the variables that are visible to the caller.
With Parameters
However, if you are taking arguments then things get more complicated. Let's assume that you are taking in the index into $all_list, $i, and the parts $j and $k, and let's assume that they're not too horrible…
# Using A B C to make it clear that these are different things
proc lap_lk {A B C} {
uplevel 1 [subst {
lappend links \[lindex \$all_list $A] [list $B] [list $C] 0
}]
}
The key here is that I'm using subst to inject things into the script (I could have also used double-quotes around the script instead) and I'm using list to add exactly the quoting to $B and $C to make them substitution-safe; I really ought to do so around $A, except that indices usually are substitution-safe anyway. You'd then call the code like this:
lap_lk $i $j $k
Toward the Tao of Tcl
However, in more complicated case (such as where a non-trivial loop is required) then you will use uplevel and upvar to do something more subtle. That's where you're going almost completely beyond what a C preprocessor can do elegantly. (Using a level-target other than 1 is where you go completely beyond the capabilities of the preprocessor.) In this case, using upvar lets us completely avoid using uplevel.
proc lap_lk {targetList sourceList A B C} {
upvar 1 $targetList tgt $sourceList src
lappend tgt [lindex $src $A] $B $C 0
}
# Calling pattern
lap_lk links all_list $i $j $k
It's recommended if you work this way to pass in the names of all (relevant) local variables as arguments rather than hard-coding the context. It's usually considered good practice to make procedures as independent of their calling context as possible. It's not always possible, of course.
Changing the above code to work with passing no explicit arguments (by hard-coding the names of the variables) is left as an exercise.

Tk : how to pass variable values with -command?

button .mltext.button -text "Apply" -command {set top_tlbl [update_text $top_tlbl $spc ] }
I get an error :
can't read "spc": no such variable
while executing
"update_text $top_tlbl $spc "
invoked from within
".mltext.button invoke"
How can I pass the values of the variables to the update_text function?
Maybe I can start by understanding this :
(System32) 3 % expr 2 + 2
4
(System32) 4 % list expr 2 + 2
expr 2 + 2
(System32) 5 % [list expr 2 + 2]
invalid command name "expr 2 + 2"
(System32) 6 %
According to me, the last one should generate expr 2 + 2, which is the same as the first command - so, why does TCL have a problem?
Thanks..
In your examples with expr, we start by counting the words.
expr 2 + 2 has four words: expr, 2, + and 2. The first one is the command name, expr, and the others are passed as arguments; the documentation for expr says that it concatenates its arguments and evaluates the resulting expression.
list expr 2 + 2 has five words: list, expr, 2, + and 2. The first one is the command name, list, and the others are passed as arguments; the documentation for list say that it returns the list that has its arguments as elements. Though we don't see it here explicitly, what this does is introduce exactly the quoting needed to make a single substitution-free command. Good Tcl code uses list quite a lot when generating code.
[list expr 2 + 2] has one word, which is the result of calling list expr 2 + 2. If you just feed that into Tcl directly, it has a weird but legal command name, and you probably don't have a command called that. Hence you get an error.
Now let's consider:
button .mltext.button -text "Apply" -command {set top_tlbl [update_text $top_tlbl $spc]}
From your comments, you're calling this inside a procedure. This means that you need to bind the variables inside the callback (which is evaluated inside uplevel #0, and probably long after your current procedure has returned) without doing the callback immediately. Curiously, it looks like you're changing top_tlbl at runtime too.
Let's start by thinking about the innermost part. We can generate it with list just fine (ignoring the different lifetime bindings):
list update_text $top_tlbl $spc
Now we've just got to make the other parts work as well. That's where it gets fiddly and you end up with something like this:
… -command "set top_tlbl \[[list update_text $top_tlbl $spc]\]"
Now let's fix the lifetime stuff:
… -command "set top_tlbl \[update_text \$top_tlbl [list $spc]\]"
This sort of thing is more than a bit error-prone in complicated callbacks! At that point, it's much easier (and good practice) to have a little helper procedure:
proc do_update_text {varname value} {
upvar #0 $varname var
set var [update_text $var $value]
}
Then you can do (note: passing the name of top_tlbl, not the value):
… -command [list do_update_text top_tlbl $spc]
The use of global variable names might be considered to be problematic, except you've got to remember that the global namespace is mainly owned by your application. If you want to store variables in globals for your app, do it! You have the complete right.
Library code needs to be a bit more careful of course. The usual techniques there include using variables in other namespaces (Tk does this internally) or in objects. The code doesn't really get that much more complicated; it's still building on the practice of using helper procedures listed above. Passing a namespaced variable name is pretty easy though:
… -command [list do_update_text ::mynamespace::top_tlbl $spc]
-command {set top_tlbl [update_text $top_tlbl $spc]} will be invoked in global context when the button is clicked. Is your variable spc accessible in global context? Try to specify the variable's namespace explicitly, for example $::spc.
expr 2 + 2 and "expr 2 + 2" are not the same. Tcl identifiers can contain spaces and "expr 2 + 2" is the whole identifier, which is not found.
You should probably just call update_text and have top_tlbl be a global and set from the called function rather than returned.
Its generally simplest to just try and call a simple function and use list to ensure everything is quoted correctly.
button .mltext.button -text "Apply" \
-command [list update_text $top_tlbl $spc]
This will capture the current values of top_tlbl and spc when you define this button and its command. If you want the values at the time you press the button then you should probably be passing the names of the variables and using the variable or global commands.
Anyway, the rules are that things inside curly braces {} have no substututions performed on them. So {$v} is passed to the callee as exactly that - dollar v. Using list or double quotes lets variable substitution be performed and so you pass the value of the variable. List properly handles quoting for cases where the variable value might contain spaces and so on.

How to pass a variable value as an argument to -command option in tcl [duplicate]

button .mltext.button -text "Apply" -command {set top_tlbl [update_text $top_tlbl $spc ] }
I get an error :
can't read "spc": no such variable
while executing
"update_text $top_tlbl $spc "
invoked from within
".mltext.button invoke"
How can I pass the values of the variables to the update_text function?
Maybe I can start by understanding this :
(System32) 3 % expr 2 + 2
4
(System32) 4 % list expr 2 + 2
expr 2 + 2
(System32) 5 % [list expr 2 + 2]
invalid command name "expr 2 + 2"
(System32) 6 %
According to me, the last one should generate expr 2 + 2, which is the same as the first command - so, why does TCL have a problem?
Thanks..
In your examples with expr, we start by counting the words.
expr 2 + 2 has four words: expr, 2, + and 2. The first one is the command name, expr, and the others are passed as arguments; the documentation for expr says that it concatenates its arguments and evaluates the resulting expression.
list expr 2 + 2 has five words: list, expr, 2, + and 2. The first one is the command name, list, and the others are passed as arguments; the documentation for list say that it returns the list that has its arguments as elements. Though we don't see it here explicitly, what this does is introduce exactly the quoting needed to make a single substitution-free command. Good Tcl code uses list quite a lot when generating code.
[list expr 2 + 2] has one word, which is the result of calling list expr 2 + 2. If you just feed that into Tcl directly, it has a weird but legal command name, and you probably don't have a command called that. Hence you get an error.
Now let's consider:
button .mltext.button -text "Apply" -command {set top_tlbl [update_text $top_tlbl $spc]}
From your comments, you're calling this inside a procedure. This means that you need to bind the variables inside the callback (which is evaluated inside uplevel #0, and probably long after your current procedure has returned) without doing the callback immediately. Curiously, it looks like you're changing top_tlbl at runtime too.
Let's start by thinking about the innermost part. We can generate it with list just fine (ignoring the different lifetime bindings):
list update_text $top_tlbl $spc
Now we've just got to make the other parts work as well. That's where it gets fiddly and you end up with something like this:
… -command "set top_tlbl \[[list update_text $top_tlbl $spc]\]"
Now let's fix the lifetime stuff:
… -command "set top_tlbl \[update_text \$top_tlbl [list $spc]\]"
This sort of thing is more than a bit error-prone in complicated callbacks! At that point, it's much easier (and good practice) to have a little helper procedure:
proc do_update_text {varname value} {
upvar #0 $varname var
set var [update_text $var $value]
}
Then you can do (note: passing the name of top_tlbl, not the value):
… -command [list do_update_text top_tlbl $spc]
The use of global variable names might be considered to be problematic, except you've got to remember that the global namespace is mainly owned by your application. If you want to store variables in globals for your app, do it! You have the complete right.
Library code needs to be a bit more careful of course. The usual techniques there include using variables in other namespaces (Tk does this internally) or in objects. The code doesn't really get that much more complicated; it's still building on the practice of using helper procedures listed above. Passing a namespaced variable name is pretty easy though:
… -command [list do_update_text ::mynamespace::top_tlbl $spc]
-command {set top_tlbl [update_text $top_tlbl $spc]} will be invoked in global context when the button is clicked. Is your variable spc accessible in global context? Try to specify the variable's namespace explicitly, for example $::spc.
expr 2 + 2 and "expr 2 + 2" are not the same. Tcl identifiers can contain spaces and "expr 2 + 2" is the whole identifier, which is not found.
You should probably just call update_text and have top_tlbl be a global and set from the called function rather than returned.
Its generally simplest to just try and call a simple function and use list to ensure everything is quoted correctly.
button .mltext.button -text "Apply" \
-command [list update_text $top_tlbl $spc]
This will capture the current values of top_tlbl and spc when you define this button and its command. If you want the values at the time you press the button then you should probably be passing the names of the variables and using the variable or global commands.
Anyway, the rules are that things inside curly braces {} have no substututions performed on them. So {$v} is passed to the callee as exactly that - dollar v. Using list or double quotes lets variable substitution be performed and so you pass the value of the variable. List properly handles quoting for cases where the variable value might contain spaces and so on.

TCL/TK How do I pass multiple arguments to a button call back function?

I have a code like this
proc press2 {v sbit} {
puts $v
puts $sbit
}
:
:
button .t.ok2 -text "OKI" -command "press2 $v $sbit"
with this I get the error wrong # args: should be "press2 v sbit"
if I change it to button .t.ok2 -text "OKI" -command {press2 $v $sbit} I get can't read "v": no such variable and finally I tried button .t.ok2 -text "OKI" -command [press2 $v $sbit] which doesn't give any errors but doesn't work also. Just asking is there any good documentation available for TCL/TK ? The usual man pages and googling isnt helping me much. I am doing much by trial and error.
Passing multiple arguments to a procedure is easy, but the "correct" way depends on what you want:
Early Binding: If you want to pass the current values later (e.g. if you create the widgets in a loop) you need to use list:
button .t.ok2 -text OKI -command [list press2 $v $sbit]
list creates a command that is free from any further substitution*.
Late Binding: If you want pass the value when this command is executed, simply brace it with {}
button .t.ok2 -text OKI -command {press2 $v $sbit}
The variables v and sbit are subsituted when the button is pressed. You only have access to global variables (or variables in a namespace, but not local variables).
* Tk's bind replaces % and a following character with something special. This is done using string substitution, not Tcl substitution, so list does not guard against this.

Getting unevaluated tcl arguments

What I want to do is parse an argument to a tcl proc as a string without any evaluation.
For example if I had a trivial proc that just prints out it's arguments:
proc test { args } {
puts "the args are $args"
}
What I'd like to do is call it with:
test [list [expr 1+1] [expr 2+2]]
And NOT have tcl evaluate the [list [expr 1+1] [expr 2+2]]. Or even if it evaluated
it I'd still like to have the original command line. Thus with the trivial "test"
proc above I'd like to be able to return:
the args are [list [expr 1+1] [expr 2+2]]
Is this possible in tcl 8.4?
You cannot do this with Tcl 8.4 (and before); the language design makes this impossible. The fix is to pass in arguments unevaluated (and enclosed in braces). You can then print them however you like. To get their evaluated form, you need to do this inside your procedure:
set evaluated_x [uplevel 1 [list subst $unevaluated_x]]
That's more than a bit messy!
If you were using Tcl 8.5, you'd have another alternative:
set calling_code [dict get [info frame -1] cmd]
The info frame -1 gets a dictionary holding a description of the current command in the context that called the current procedure, and its cmd key is the actual command string prior to substitution rules being applied. That should be about what you want (though be aware that it includes the command name itself).
This is not available for 8.4, nor will it ever be backported. You might want to upgrade!
When passing the arguments into test, enclose them in braces, e.g.:
test {[list [expr 1+1] [expr 2+2]]}