Tk : how to pass variable values with -command? - tcl

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.

Related

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

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

Why curly braces allow variable substitution?

Tcl manuals say that curly braces do not allow variable substitution.
However this works only with some commands but not with others.
What is the difference and how to identify the cases where the substitution will occur and the cases where it won't occur?
% set x 3
3
% puts {$x}
$x
% expr {$x}
3
Referring to the list of standard commands: any command that takes a "body" or "script" argument will eventually evaluate that body as code. With no guarantees about exhaustiveness:
after, apply, catch, eval, expr, fileevent (and chan event), for, foreach, if, interp eval, lmap, some namespace subcommands, some oo::* commands, proc, subst, switch, try, uplevel, while
This is truly one of Tcl's greatest strengths. It gives you the power to easily write your own control structures. For example, Tcl does not provide a do-while loop, but you can do this:
proc do {body while condition} {
if {$while ni {while until}} {
error "some message about usage..."
}
while true {
uplevel 1 $body
set status [uplevel 1 [list expr $condition]]
if {$while eq "while" && !$status} then break
if {$while eq "until" && $status} then break
}
}
so that
% set i 0; while {[incr i] < 3} {puts "$i"}
1
2
% set i 0; do {puts "$i"} while {[incr i] < 3}
0
1
2
% set i 0; do {puts "$i"} until {[incr i] == 3}
0
1
2
Some commands are explicitly described as treating an argument or arguments as a script or an expression; when evaluation of the script or expression happens (which might be immediately, or might be later, depending on the command) the substitutions described inside that string that is a script or expression are performed. (The subst command is a special case that can only apply a selected subset of substitutions.)
How do you know which is which? It depends on the command. Literally. Go and read the documentation. For example, in the documentation for catch we see:
SYNOPSIS
catch script ?resultVarName? ?optionsVarName?
DESCRIPTION
The catch command may be used to prevent errors from aborting command interpretation. The catch command calls the Tcl interpreter recursively to execute script, and always returns without raising an error, regardless of any errors that might occur while executing script. […]
In this case, we see that the first argument is always evaluated (immediately) as a Tcl script by calling the Tcl interpreter (or rather it's actually bytecode compiled in most cases, but that's an implementation detail).
Similarly, in the documentation for proc we see:
SYNOPSIS
proc name args body
DESCRIPTION
The proc command creates a new Tcl procedure named name, replacing any existing command or procedure there may have been by that name. Whenever the new command is invoked, the contents of body will be executed by the Tcl interpreter. […]
In this case, it's the body that is going to be evaluated as a script (“by the Tcl interpreter” is a form of language that means that) but later, when the procedure is called. (catch said nothing about that; by implication, it acts immediately.)
A third case is the documentation for while:
SYNOPSIS
while test body
DESCRIPTION
The while command evaluates test as an expression (in the same way that expr evaluates its argument). The value of the expression must a proper boolean value; if it is a true value then body is executed by passing it to the Tcl interpreter. […]
From this, we can see that the test argument is an expression (which uses expression rules) and body is a script.
If you want to create a substitution-free single-command script where you can use arbitrary values for everything (this perfect for setting up a callback) use the list command as that is defined to produce lists in canonical form, which happens (by design) to be exactly the form that single commands without substitution-surprises can take:
set xyz "123 456"
set callback [list puts $xyz]
set xyz {[crash bang wallop]}
puts "READY..."
eval $callback

tcl scripts, struggling with [...] and [expr ...]

I can't understand how assignments and use of variables work in Tcl.
Namely:
If I do something like
set a 5
set b 10
and I do
set c [$a + $b]
Following what internet says:
You obtain the results of a command by placing the command in square
brackets ([]). This is the functional equivalent of the back single
quote (`) in sh programming, or using the return value of a function
in C.
So my statement should set c to 15, right?
If yes, what's the difference with
set c [expr $a + $b]
?
If no, what does that statement do?
Tcl's a really strict language at its core; it always follows the rules. For your case, we can therefore analyse it like this:
set c [$a + $b]
That's three words, set (i.e., the standard “write to a variable” command), c, and what we get from evaluating the contents of the brackets in [$a + $b]. That in turn is a script formed by a single command invocation with another three words, the contents of the a variable (5), +, and the contents of the b variable (10). That the values look like numbers is irrelevant: the rules are the same in all cases.
Since you probably haven't got a command called 5, that will give you an error. On the other hand, if you did this beforehand:
proc 5 {x y} {
return "flarblegarble fleek"
}
then your script would “work”, writing some (clearly defined) utter nonsense words into the c variable. If you want to evaluate a somewhat mathematical expression, you use the expr command; that's it's one job in life, to concatenate all its arguments (with a space between them) and evaluate the result as an expression using the documented little expression language that it understands.
You virtually always want to put braces around the expression, FWIW.
There are other ways to make what you wrote do what you expect, but don't do them. They're slow. OTOH, if you are willing to put the + first, you can make stuff go fast with minimum interference:
# Get extra commands available for Lisp-like math...
namespace path ::tcl::mathop
set c [+ $a $b]
If you're not a fan of Lisp-style prefix math, use expr. It's what most Tcl programmers do, after all.
set c [$a + $b]
Running the above command, you will get invalid command name "5" error message.
For mathematical operations, we should rely on expr only as Tcl treats everything as string.
set c [expr $a + $b]
In this case, the value of a and b is passed and addition is performed.
Here, it is always safe and recommended to brace the expressions as,
set c [expr {$a+$b}]
To avoid any possible surprises in the evaluation.
Update 1 :
In Tcl, everything is based on commands. It can a user-defined proc or existing built-in commands such as lindex. Using a bare-word of string will trigger a command call. Similarly, usage of [ and ] will also trigger the same.
In your case, $a replaced with the value of the variable a and since they are enclosed within square brackets, it triggers command call and since there is no command with the name 5, you are getting the error.

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.

How tcl curly braces in ${variableName} is interpreted?

I am a newbie in TCL Programming. I was having confusion about curly braces, answer to this question tcl curly braces cleared most of my doubts.
I can understand $var, {var}, and {$var}, But recently I came across another use of curly braces, ${var}. How is this interpreted by TCL?
I have seen this is used when accessing variables in namespaces when namespaces name is in variable.
for example:
set x myNamespace ;#myNamespace is name of namespace
puts [set ${x}::var1] ;#var1 is variable in the namespace
It gives error when you don't use curly braces around 'x'.
And I also don't understand the difference between {a b c} and [list a b c], what is the difference in result of interpretation of these two commands by TCL interpretation.
elaborated explanation would be highly appreciated.
See rule 8 of the manual. It allows you to have variable names that might get mis-interpreted. For instance:
% set dotted.name 1
1
% puts $dotted.name
can't read "dotted": no such variable
% puts ${dotted.name}
1
Read section 8 carefully as it actually explains all this quite explicitly.
Update to answer edited question
In the example you provide using a namespace name in a variable you must consider section 8 part 1: a variable name includes letters, digits, underscores and namespace separators. This means that x::var1 is a valid variable name. So $x::var1 will attempt to dereference the var1 variable in the x namespace. As this is not what you meant, you must dereference your x variable separately. There are two ways to do this. You can either use the set command or the dollar operator.
set x myNamespace
puts [set ${x}::var1]
puts [set [set x]::var1]
The two puts statements are equivalent here with the second version showing an explicit separate pass to obtain the value of the x variable which is then substituted into the expression for the outer set command. The same occurs in the first version but just uses the grouping operator to restrict the effect of the dollar to the x variable name.