How to pass an argument by reference in the TCL procedure?
Look the following block of code:
proc test{&a} {
set a 7
}
set b 5
test b
puts $b
I waited that the output should be 7, but it was 5.
What is wrong with this code?
The trick is to use the upvar command to map the variable with that name in the caller's scope into the procedure's internal scope.
proc test {&a} {
# It's a good idea to use an explicit level of 1
upvar 1 ${&a} a
set a 7
}
I'd not normally name a variable &a, as they're annoying to use with $ syntax. I mostly prefer to use something like varName so that when someone reads the wrong-arguments error for test they get this readable version:
wrong # args: should be "test varName"
Related
The following strained example, in attempt to match my real situation, completes successfully but not the way I'd like it to; nor do I understand why or can find an answer in one of my books.
I can see that proc func was called from the global level and, in turn, initially called proc cofunc; and, perhaps, that sets what upvar will use. But proc finalize calls the coroutine again and passes the variable name nbr to cofunc--which calls add. In this respect add "seems" to be two levels from finalize.
The upvar 2 $varName n in proc add is the variable name nbr and takes its value from the global variable of that name. I'd like to get it from proc finalize (so 15, not 85) which seems to be two levels away from proc add but apparently is not even in the list of caller contexts.
I assume [yield] is the reason; but would you please explain why and, more importantly, if the value in proc finalize was not a number but a "large" value that shouldn't be passed around, is it possible to upvar or uplevel from a coroutine?
Thank you for considering my question.
proc cofunc {callback vals} {
set var [yield]
$callback $vals $var
}
proc func {callback vals} {
set nbr 22
coroutine coro cofunc $callback $vals
}
proc add {vals varName} {
upvar 2 $varName n
chan puts stdout "varName: $varName; n: $n"
# => varName: nbr; n: 85
chan puts stdout [expr {[lindex $vals 0]+[lindex $vals 1]+$n}];
# => 115
}
proc finalize {nbr} {
coro nbr
}
set nbr 85
func add {10 20}
after 500 finalize 15
after 1000 set forever 1
vwait forever
The upvar (like the uplevel command) is defined in terms of the stack. The number argument says how many steps up the stack to take (or you can prefix the number with # to count down from the other end; that's a bit less common); it defaults to 1, the caller of the current procedure. There are two sorts of stack frame, one for procedures (and related entities, such as methods and lambda expressions) and another for direct namespaces (the global scope, namespace eval, and a few other things). All upvar does is go to the indicated stack frame, look up the named variable there, and make a local variable that is a link to it. By "link", I mean that the local variable is really a tagged C pointer to the implementation of the other variable. Once a linked variable is set up, access is very fast, as the slow part of access is resolving names to implementations, and local variables are usually compiled into indexed accesses.
The global and namespace upvar commands do something very similar, but with different lookup strategies. (Also, global assumes that the global and local names should match.) That's also part of what the variable command does.
In your code sample, the upvar 2 is to get past the cofunc procedure. I think it would be clearer if it was upvar #0, or if cofunc used tailcall $callback so a simple upvar could be used. And I'm pretty sure that the nbr accessed in add is not the same one as in func, as the latter's stack frame will be gone by then.
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
I cannot define variables in namespace (in TCL) which was defined previously in global scope. See my example:
xsct% $tcl_version
[8.5]
xsct% set foo 1
1
xsct% $foo
[1]
xsct% namespace eval ns {
set foo 2
set bar 3
}
3
xsct% $::ns::bar
[3]
xsct% $::ns::foo
can't read "::ns::foo": no such variable
xsct%
I have reproduced the issue online: http://tpcg.io/3SIBYG
How can I define variables in namespaces independently from global scope?
I use:
Win10
TCL 8.5 in Xilinx's XSCT TCL console
Always define variables in a namespace with the variable command at least the first time you access them, otherwise you end up with namespace variable resolution rules taking over and making your life unpleasant. They're weird (though actually very similar to how command resolution works) and you virtually never want them, and may get removed in Tcl 9. But until then, you're stuck doing:
namespace eval ns {
variable foo 2
variable bar 3
}
or:
namespace eval ns {
variable foo
set foo 2
variable bar
set bar 3
}
If you want to do arrays, you can. Do them like this (with only one argument to variable):
namespace eval ns {
variable ary
array set ary {foo 2 bar 3}
}
What the variable command actually does is make the variable in the namespace in unset state so that it still resolves when commands like set and array can find the variable and write to it.
I have process were variables are defined, and following that procedure the variables should be used after a delay.
The problem is that the delayed command process the variables when the command is executed instead of when the command is given. Consider the following example:
The code is not tested, but the point should be clear anyway:
for {set i 0} {$i < 100} {incr i} {
set outputItem $i
set time [expr 1000+100*$i]
after $time {puts "Output was $outputItem"}
}
Which I would hope print something like:
Output was 1
Output was 2
Output was 3
...
But actually it prints:
Output was 100
Output was 100
Output was 100
Which I guess shows that tcl keeps the parameter name (and not the value of the variable) when the after command is initiated.
Is there any way to substitute the variable name to the variable content, so that the delayed command (after xxx yyy) works as desired?
The problem is this line:
after $time {puts "Output was $outputItem"}
The substitution of $outputItem is happening when the after event fires, not at the time you defined it. (The braces prevent anything else.) To get what you want, you need list quoting, and that's done with the list command:
after $time [list puts "Output was $outputItem"]
The list command builds lists… and pre-substituted commands (because of the way Tcl's syntax is defined). It's great for building things that you're going to call later. I guess it could have been called make-me-a-callback too, but then people would have wondered about its use for creating lists. It does both.
If your callback needs to be two or more commands, use a helper procedure (or an apply) to wrap it up into a single command; the reduction in confusion at trying to make callbacks work with multiple direct commands is totally worth it.
I don't really understand how to use the eval arguments.
If the script I want to eval is :
set myscript { puts $::argv }
Then I want to call my script like this :
eval $myscript anArgument
And I expect the output to be "anArgument", but instead I have :
can not find channel named ""
while evaluating {eval $script vvv}
When you invoke eval, the command concatenates all its arguments and attempts to evaluate the resulting list (or string if you will, it's the same thing here). So, first the arguments { puts $::argv } and anArgument are concatenated into the list {puts $::argv anArgument}, and then the interpreter tries to evaluate that. If the value of the global variable argv is an empty list, the actual command invoked will be equivalent to puts {} anArgument. puts will try to use {} as a channel identifier to output to, fail and leave an error message.
Now, if what you wanted to do was to pass anArgument to myscript and then eval it as puts anArgument, you should instead write
set myscript {puts $myarg}
set myarg anArgument
eval $myscript
In the first line, the evaluation of $myarg is postponed because of the quoting braces which turn $ into a regular text character. The variable myarg is then set to a value (this can happen anywhere in the code as long as it comes before the eval). In the third line, the script is evaluated, and at that point the argument $myarg is replaced by the value anArgument, which is then printed.
The sort of invocation you attempted is possible, but then you need to use apply instead of eval, and a closure (anonymous function) instead of a script.
set myfunc {myarg {puts $myarg}}
apply $myfunc anArgument
The global variable argv does not pass arguments to a script which is passed to eval: when tclsh or wish is started by the operating system, any command line arguments given are placed in argv, and the value is never changed during execution unless you change it yourself (don't do that, it's just confusing).
Documentation: apply, eval, puts, set
argv is documented here.