Use of "trace add variable read" in tcl - tcl

New to tcl and trying to understand the "trace add variable" command.
I understand the need to invoke a callback function when a variable is "written" to.
But what is the use of the "read" option? For debugging?

One example use might be a global counter:
proc init { } {
set ::globalcounter 0
trace add variable ::globalcounter read ::gcountUpdate
}
proc gcountUpdate { } {
incr ::globalcounter
}
proc main { } {
init
puts $::globalcounter
puts $::globalcounter
}
main
I'm sure there are other uses. As you pointed out, debugging.
It could be used for enforcement of variable access by specific procedures.

One of the uses for read callbacks (which are indeed quite a bit less common than write callbacks) is in linking a Tcl variable to a C variable; the read callback is used to enforce the reading of the C variable and synchronizing the Tcl variable to it. (The write callback would ensure that the update of the Tcl variable gets reflected to the C variable.) Tcl's got a built-in C API that uses this mechanism, though it's using the underlying C API for variable traces rather than the Tcl API that is built on top of it.
You could also use a read callback to make an apparently read-only variable:
trace add variable foo read {apply {args {
upvar "#0" foo v
set v "definitely this"
}}}
puts $foo
set foo "that"
puts $foo
I don't recommend using variable traces on local variables. They have a lot more overhead (and internal complexity) than for global/namespace variables.

Related

Global variable in Tcl

I am trying to use a global variable (gpio_out_set_3) by declaring it outside a function (because the variable might be used in other functions too in future). Inside the function, I have declared the same variable as 'global' and trying to access it through '$gpio_out_set_3'.
I am getting an error "can't read "gpio_out_set_3": no such variable"
set gpio_out_set_3 0x03
proc port2phy { device } {
global gpio_out_set_3
erf_wr devcpu_gcb $gpio_out_set_3 $phy_mdc_gate_en
}
Please help.
Declare all of your global variable at the beginning of your main file using variable command.
variable gpio_out_set_3 0x03
TIP: I dont like the global command. I always forget to use, and hard to see which variable is global and which variable is local. I prefer the $::<varname>, which points to the global namespace.
proc port2phy { device } {
erf_wr devcpu_gcb $::gpio_out_set_3 $phy_mdc_gate_en
}
My guess is that when you're creating variable gpio_out_set_3 you're not at top level. You are in some other procedure. So the gpio_out_set_3 in not really global, but instead local in some proc.
Your global variable is named gpio_out_set_0 (not the same as gpio_out_set_3).
Although newer versions of tcl allow for executable code to exist almost anywhere in your program, a good habit to form is to be sure that where you set the variable initially is in the main section of your program code. In order for your code to run properly on any version of tcl, there should be no executable code prior to, or in-between your procs (including set commands). All of your procs should go at the beginning of the program followed by your main code. Things that can/should go before your procs are comment lines and any necessary "package require ..." commands.

What does it mean proc argument is variable

I wonder if it is right semantics to have a variable as an argument, something like this:
proc p1 {$aa} {}
I tried it on tclsh, there is no complaint, but the following experiment fails:
% set aa bb
bb
% set bb 200
200
% proc p1 {$aa} {puts $bb}
% p1 bb
can't read "bb": no such variable
Do you see what is wrong?
[UPDATE - after seeing Peter's answer]
I know the upvar semantic, thanks.
My main curiosity is still around using variable as proc argument. I know it is not common, but just cannot help musing what it really can do if the language syntax allows it.
Yes, your upvar example is exactly what I want to explore using a variable as proc argument, but my exploration so far tells me, really, there is no way we can do this because "$" is interpreted as a plain char.
Do debunk me please if I am wrong.
Tcl does not support reference arguments as such: the usual pass-by-reference semantics is too static for Tcl. Instead, the logic of the command can, by use of upvar, dynamically create reference parameters including indirect reference parameters and calculated reference parameters, and also retarget the local name to another external variable. The upvar mechanism may look ungainly, but is very powerful indeed.
(The (edited) remains of my original answer follows:)
The usual idiom for doing this is
proc p varName {
upvar 1 $varName var
puts $var
}
The upvar command looks into another stack frame (in this case 1, which is the caller's stack frame) and makes a variable named $varName (i.e. the variable's name is the value of varName) in that stack frame and a variable named "var" in the command's stack frame refer to the same data object.
I won't explain this further since this is not useful to the asker.
Documentation: proc, puts, upvar
There are cases where it makes sense for the arguments in a procedure creation call to be supplied from a variable. The main example is where you are creating procedures dynamically, calculating the arguments you want to use as you go along.
That's actually a use-case that isn't done very frequently! It's not particularly easy to use well. But I have done it. (OK, that's a method call, but the syntax of formal arguments is shared.)
The main reason that the capability is there is that it's part of Tcl's general syntax. Tcl tries very hard to not have special cases in how it parses things (other than in how a command parses the strings passed into it) and this includes in things that would be very special cases in the enormous majority of other programming languages, such as formal parameter lists to procedures. In Tcl, these are just ordinary values and can be produced using any technique that gives ordinary values.
The usual thing with putting them in braces is just how you do it reliably and is easy to teach. It's also overwhelmingly what people want to do.
All this is independent of the facts that Tcl's commands (including its procedures) can handle variable numbers of arguments (check out the special args parameter and how to specify default values) and that $aa is a legal (but strange) name for a local variable.

Manipulation of variables inside a proc with bind

Sorry for the title couldn't think of anything better.
Heres my question:
Im trying to change a variable inside a proc only when the user hits space. the proc loops itself with after so if the user wish to hit space more then once the variable will increment.
Heres what I know:
There are many ways to go about this. You can pass variable inside the proc, you can link the variable with global or upvar and/or if your in a namespace then you can use variable. but the only one that seem to work with my is global. I'm getting a feeling it's because global makes a link but if thats true then variable should work too, right?
Here my test code:
proc test1 {} {
global testing
bind . <Key-a> {incr testing}
puts $testing
puts "test2"
after 100 test2
}
namespace eval test2 {
variable testing 0
namespace export {[a-z]*}
proc of1 {} {
variable testing
bind . <Key-a> {incr testing}
puts $testing
after 100 test3::of1
}
}
proc test3 {testing} {
bind . <Key-a> {incr testing}
puts $testing
puts "test4"
after 100 test4 $testing
}
set testing 0
#test1
test2::of1
#test3 0
grid .c
side question:
Why is it that in the global namespace we use set and global while in namespace we use variable (that seem to set and do global in one command). they seem to do the same job in different namespaces?
Variables in Callbacks
The scripts registered by the bind command — also in things like after events and fileevent callbacks — are evaluated in the global scope because they may be called long after the procedure that defined them returns; Tcl does not do scope capture (and that's actually a really complicated feature, so it isn't likely to come soon unless someone writes lots of code). This means that the variable that you want your procedure to notice changes to must also have global scope.
However, namespaced variables count just fine as global variables for the purpose of this discussion as they're nameable from a global context (real local variables are not). That means that we can do several ways to build a script that accesses a namespace variable from a bind-defined callback. Here's one of the nicer ones:
bind . <Key-a> [namespace code {incr testing}]
That's effectively the same as this:
bind . <Key-a> [list namespace eval [namespace current] {incr testing}]
(There are some strict differences that don't matter in this example.)
Another way to do the callback is this:
bind . <Key-a> [list incr [namespace which -variable testing]]
Which in this case is going to be much like:
bind . <Key-a> [list incr [namespace current]::testing]
If things are getting any more complicated than this toy example, it's time to stop updating variables directly in a binding script and instead write a helper procedure. That always simplifies things a lot. Or to use a class/object to encapsulate the details.
The variable Command: Why and Where to Use It
Why is it that in the global namespace we use set and global while in namespace we use variable (that seem to set and do global in one command). they seem to do the same job in different namespaces?
That's a good question. In fact, what global does is very much like upvar #0 (with the variable names doubled up), and set is a fundamental variable access command. They're commands that you can use regularly wherever you want their behaviour.
The variable command is rather stranger. What it does is three-fold:
If called in a namespace context and the variable does not exist in that namespace, it creates that variable in a present-but-unset state.
If called in a context with local variables, it links a local variable with the name (after stripping everything up to the last namespace separator) to a namespace variable with the name (using the whole supplied name if there are qualifiers, and resolving non-absolute names with respect to the current context namespace). This also forces the namespace variable to exist in the present-but-unset state.
If a value is given, the namespace variable is set to the value. This gets rid of the present-but-unset-ness.
The important behaviour is actually the creating of that present-but-unset state, since otherwise you can end up with a set (or array set) in the namespace escaping that namespace and instead working with a global variable, but not always. It all depends on the exact behaviour of the code that resolves variables, and that's deeply tricky. This is hard to explain properly, and ridiculously hard to justify. It's been the cause of quite a few outright bugs, and is definitely no better than a horrible misfeature.
The setting of the initial value is little more than a lollipop; you could instead put set straight afterwards without ill-effect. What's more, it inhibits using variable to pull in multiple variables from a namespace except in the case where you're setting them to a known value; great for initialisation, but sucky for other uses. (In case you hadn't guessed, I think this is an area of Tcl where the interface was got rather badly wrong back when it was introduced, back in Tcl 8.0. Not one bit of this is nice.)
The key take-away is this though:
Always use variable in your namespaces to declare variables as this is the only way you can be sure that the semantics is predictable. You can then initialise them any way you want. (You have to do it this way if you're creating arrays.)
Fully-qualified variable names have no craziness associated with them. Tcl always knows exactly how to look up the thing you're naming in that case.

tcl namespace error

I have several functions defined in namespace "b" which I export. I then import these functions to namespace ::x::Y, thusly:
namespace eval ::x::y "namespace import fun"
some time later I do:
namespace eval ::x::y fun
Where fun does:
proc fun {} {
puts "[namespace current]"
uplevel {puts "[namespace current]"}
}
What is printed is:
::b
::x::y
What I want and need is for 'fun' to happen in ::x::y and not in ::b. What am I doing wrong?
That's not how Tcl's namespaces work. Each procedure is associated with exactly one namespace, which is the one in which its name is located. When you use namespace import, an alias to the procedure is placed in the importing namespace that allows the procedure to be invoked from that other namespace, but the procedure itself remains in its original namespace and executes in that one.
If you want to know the caller's namespace, use uplevel namespace current (or uplevel 1 {namespace current} for a slightly windier but more efficient version). This doesn't actually tell you what namespace contained the command that was used to invoke the procedure though; for that, you need this monstrosity (in the invoked command):
namespace qualifiers [uplevel 1 [list namespace which [lindex [info level 0] 0]]]
Of course, if you're needing that a lot then you're probably doing something wrong. (That's obvious, given the length and complexity of code required to get the information.)
In particular, if you're pretending to do object orientation with this, please stop and use a real object system that gets all the tricky details right. Tcl 8.6.0 includes one (two, if you've got the contributed extensions), and there are many for older versions available as extension packages.

Adding help to the user defined functions in TCL

how can i add some kind of help to the user defined functions in TCL
Supposing if i have a function called runtest {ip_address test_time},
How to describe what the test or procedure is about in the TCL_shell?
How can i specify the information to the user, if he types in function_name --help in the TCL shell the user should be able to know what the function does and what exactly are the parameters.
how can i do this?
While it is true that it is not part of the language, it is fairly easy to implement something that adds this functionality to pre-existing functions. The only caveat being that the function then cannot take the string --help as a valid argument since that argument will trigger the feature.
Here's one simple implementation:
# Lets call the feature "document". As in, add documentation:
proc document {procname text} {
rename $procname __$procname
proc $procname args [string map [list %TEXT% $text %PROC% $procname] {
if {$args == "--help"} {
puts {%TEXT%}
} else {
set script [linsert $args 0 __%PROC%]
return [uplevel 1 $script]
}
}]
}
What this does is to override the function (by renaming and then declaring another function of the same name) and see if the function is called with the argument --help. If it is it prints the documentation otherwise it executes the original function. Just be careful not to call this twice on the same function (it can be modified for it to work though).
So you can do things like:
proc foo {} {puts 2}
document foo {Prints the number 2.}
Now if you call:
foo --help
and it would output:
Prints the number 2.
You don't have to touch the existing procedures:
proc help {procname} {
puts $::helptext($procname)
}
proc addhelp {procname text} {
set ::helptext($procname) $text
}
addhelp foo "this is the help text for procedure foo"
help foo
Without redefining the proc command, you cannot. Ie, that functionality is not built into the language, but would be possible to add if you so wished.
I will note that adding the capability yourself, while possible, is probably beyond the difficulty where it's worth doing, especially for someone that isn't intimately familiar with the language. That being said, you can check the tclers wiki for a preexisting implementation.
I tend to prefer to put help in a separate file (e.g., as HTML) which lets me browse it in another window. There are so many ways you can do that while still keeping the docs with the code, such as through doxygen.
There are also a number of ways of doing interactive documentation, several of which are described in the Tcler's Wiki; it would seem to me that some of the techniques mentioned on that page (together with #slebetman's answer) would give you what you wanted. (I think it would be easier to have a separate help command as that would avoid having the help syntax interfere with the command, but that's your call.)