In the tk code base I found the construct:
proc ::tk::dialog::file::chooseDir:: {args} {
Normally I would expect the procedure name after the last set of :: but here it is empty. Is this some sort of constructor in a namespace?
(Might look like a trivial question but I'm not a tcl programmer and need to know it to, automatically, generate some documentation.
Some more of the code (maybe gives some background, it is the beginning of the file)
namespace eval ::tk::dialog {}
namespace eval ::tk::dialog::file {}
namespace eval ::tk::dialog::file::chooseDir {
namespace import -force ::tk::msgcat::*
}
proc ::tk::dialog::file::chooseDir:: {args} {
variable ::tk::Priv
set dataName __tk_choosedir
upvar ::tk::dialog::file::$dataName data
Config $dataName $args
...
Normally I would expect the procedure name after the last set of ::
but here it is empty
The empty string is a valid name for a procedure in Tcl (as it for variables).
% namespace eval ::tk::dialog::file::chooseDir {}
% proc ::tk::dialog::file::chooseDir:: {args} { return "called!" }
% ::tk::dialog::file::chooseDir::
called!
% namespace eval ::tk::dialog::file::chooseDir { "" }
called!
% info procs ::tk::dialog::file::chooseDir::*
::tk::dialog::file::chooseDir::
I don't know the history behind these Tk internals, but a procedure named using the empty string might be the main procedure for the same-named namespace chooseDir (as a kind of naming convention), rather than just duplicating the name: proc ::tk::dialog::file::chooseDir::chooseDir {args} {;}. Or, it is because the entire directory-picking functionality is auto_loaded, which requires a proc (command) name rather than a namespace name?
automatically, generate some documentation.
Maybe, when harvesting a Tcl interpreter for pieces to document, take the containing namespace name chooseDir as the documented name of such a procedure?
Related
I have a series of Tcl scripts which are being executed in an interpreter (Cadence Innovus). I want a way to group commands and execute them with a single call. Functionally I can achieve this by writing each group of commands in a separate file and calling source <group_file_name>. However it is inconvenient to define so many files.
I have tried to do this with:
proc {} {
commands...
}
This doesn't give me the functionality I need though. I believe it executes the commands in a lower namespace and the variables that are set do not remain.
Is there a way to get this functionality (a single file with callable functions), but that execute in the same namespace?
Well, you can always use uplevel or namespace eval inside that procedure to change the current namespace. If you use uplevel, you would do this:
proc foo {} {
uplevel 1 {
TheFirstCommand
TheSecondCommand
TheThirdCommand
}
}
With namespace eval, you might instead do this:
proc foo {} {
namespace eval ::theTargetNamespace {
TheFirstCommand
TheSecondCommand
TheThirdCommand
}
}
Things get a bit more complicated if you're wanting to use local variables with the scope-changers, but the principle for how to manage things is about the same whichever mechanism you use.
proc foo {} {
set a [uplevel 1 { TheFirstCommand }]
set b [uplevel 1 { TheSecondCommand }]
# Dynamically-generate the script to run; it's trivial code generation
uplevel 1 [list TheThirdCommand $a $b]
}
Switching to namespace eval is pretty much a drop-in replacement.
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.
Is it possible to find the procedure name by using the content of that procedure?
For example,
proc test {args} {
set varA "exam"
puts "test program"
}
Using the statement set varA, is it possible to find its procedure name test?
Because, I need to find a procedure for which i know the output [it's printing something, i need to find the procedure using that].
I tried many ways like info frame, command. But, nothing helps.
Is it possible to find the procedure name by using the content of that procedure?
Yes. You use info level 0 to get the argument words to the current procedure (or info level -1 to get its caller's argument words). The first word is the command name, as resolved in the caller's context. That might be enough, but if not, you can use namespace which inside an uplevel 1 to get the fully-qualified name.
proc foo {args} {
set name [lindex [info level 0] 0]
set FQname [uplevel 1 [list namespace which $name]]
# ...
}
Note that this does not give you the main name in all circumstances. If you're using aliases or imported commands, the name you'll get will vary. Mostly that doesn't matter too much.
With info proc, we can get the content of a procedure which may helps you in what you expect.
The following procedure will search for the given word in all the namespaces. You can change it to search in particular namespace as well. Also, the search word can also be case insensitive if altered in terms of regexp with -nocase. It will return the list of procedure names which contains the search word.
proc getProcNameByContent {searchWord} {
set resultProcList {}
set nslist [namespace children ::]; # Getting all Namespaces list
lappend nslist ::; # Adding 'global scope namespace as well
foreach ns $nslist {
if {$ns eq "::"} {
set currentScopeProcs [info proc $ns*]
} else {
set currentScopeProcs [info proc ${ns}::*]
}
foreach myProc $currentScopeProcs {
if {[regexp $searchWord [info body $myProc]]} {
puts "found in $myProc"
lappend resultProcList $myProc
}
}
}
return $resultProcList
}
Example
% proc x {} {
puts hai
}
% proc y {} {
puts hello
}
% proc z {} {
puts world
}
% namespace eval dinesh {
proc test {} {
puts "world is amazing"
}
}
%
% getProcNameByContent world
found in ::dinesh::test
found in ::z
::dinesh::test ::z
%
First off: I could fix my problem by myself, but I don't understand why my original solution did not work, and this is what I am interested in. I tried to make a compact example here:
I am dynamically building arrays, each array value being a list. Let's start with the following program:
# 'collector' is a callback function, expecting a container array, and some
# data used to populate the array.
proc generate { collector arr_name } {
eval $collector $arr_name first XXX YYY
eval $collector $arr_name second UUU VVV
}
# This is the callback function used in our example
proc collect { container_name key valuex valuey } {
upvar $container_name container
lappend container($key) [list $valuex $valuey]
}
# Procedure to write out an array
proc dump { arr_name } {
upvar $arr_name arr
puts $arr_name:
foreach key [array names arr] {
puts "$key : $arr($key)"
}
}
# Main program
array set containerA {}
generate [namespace code { collect }] containerA
dump containerA
Up to this point, nothing spectacular. Running this program produces the output
containerA:
second : {UUU VVV}
first : {XXX YYY}
But now let's extend this program somewhat
# Wrapper function to call 'generate' using a fixed collector function
# ("Currying" the first argument to generate)
proc coll_gen { container_name } {
upvar $container_name container
generate [namespace code { collect }] $container_name ; # This works
# This would not work:
#generate [namespace code { collect }] container
}
array set containerB {}
coll_gen containerB
dump containerB
As written here, this would work too, and we get the output
containerB:
second : {UUU VVV}
first : {XXX YYY}
Now to my question: As you already can guess from the comments in the code, I had first written coll_gen as
proc coll_gen { container_name } {
upvar $container_name container
generate [namespace code { collect }] container
}
My reasoning was that, since container is an alias to the array, the name of which was passed via the parameter list, I could equally well pass on the name of this alias to the 'generate' function. However, when I run the code (Tcl 8.5), it turns out that containerB is empty.
Why is it that it didn't work this way too?
The issue is one of evaluation scope.
Let's write out the call stack at the point where you're inside collect in the case where things don't work:
::
coll_gen containerB
generate {namespace inscope :: { collect }} container
namespace inscope :: { collect } container first XXX YYY
collect container first XXX YYY
Whoops! What's that namespace inscope? Where are the inner layers upvaring to? The result of namespace code is a wrapping with namespace inscope (which you shouldn't write directly; use namespace code or namespace eval) that arranges for the script formed by appending the other arguments (with appropriate metacharacter protection) to be run in the given namespace (:: in your case, I assume). This “run in the given namespace” requires adding another stack frame, and that's what the upvar is then poking into (it's probably created a global array called container, since the namespace inscope frame is a namespace-coupled one, not a “procedure local” stack frame).
You could use upvar 2 or maybe even upvar 3 (I'm not quite sure which) inside collect to work around this, but that's horrific and fragile.
You're better off writing your code like this:
proc coll_gen { container_name } {
upvar $container_name container
generate [namespace which collect] container
}
proc generate { collector arr_name } {
upvar 1 $arr_name collectorVar
eval $collector collectorVar first XXX YYY
eval $collector collectorVar second UUU VVV
}
With that, the call stack will become this:
::
coll_gen containerB
generate ::collect container
::collect collectorVar first XXX YYY
Annotating with what the array is called inside each level…
:: ### containerB
coll_gen containerB ### container (→ containerB)
generate ::collect container ### collectorVar (→ container → containerB)
::collect collectorVar first XXX YYY ### container (→ collectorVar → container → containerB)
Tcl is very literal, and I find it helps to think in terms of strings as far as possible, similar to how you think in terms of symbols when using Lisp but even more pervasive. When you use upvar, what you get isn't anything like a reference variable in some other languages. You just get to refer to a Tcl_Obj that was originally referenced in another stack frame (or the same stack frame if you upvar 0) using a local name. In the invocation
generate [namespace code { collect }] container
the second argument to generate doesn't carry over any kind of reference to the Tcl_Obj that container referred to inside coll_gen: the argument is just a Tcl_Obj containing the string "container". If that string is equal to a valid name in one of the stack frames, you can upvar the name to get/be able to set a value in the associated object (and if you've managed the stack frames correctly, it will even be the object you wanted to access).
The commands upvar and uplevel have important uses, but you really don't need them here. If you just go with names and don't try to drag your objects with you through each stack frame, your code becomes easier to read and easier to maintain:
proc generate args {
# use eval $args first XXX YYY if you have Tcl 8.4 or earlier
{*}$args first XXX YYY
{*}$args second UUU VVV
}
proc collect {container_name key args} {
lappend ${container_name}($key) $args
}
proc dump arr_name {
puts $arr_name:
dict for {key val} [array get $arr_name] {
puts "$key : $val"
}
}
proc coll_gen container_name {
generate [namespace code collect] $container_name
}
array set containerB {}
set container_name [namespace which -variable containerB]
foreach cmd {coll_gen dump} {$cmd $container_name}
A variable created (by assignment or the variable command) in the global scope will be a namespace variable that exists independent of stack frames: every proc in the program will be able to reach it using an absolute reference (such as created by namespace which or simply prepending the namespace to the variable name).
Local variables, OTOH, are disambiguated by name and stack frame. Within a stack frame, every use of a certain variable name will reference the same object. In the simple case, a proc will execute in one stack frame only, but the uplevel command may cause some piece of code to execute in another stack frame. In that case, the same name may be used to refer to different objects in the same code body. There is no ambiguity, though: the level of execution determines what object a name refers to.
When using the upvar command, two different name + stack frame permutations can be used to reference the same object residing on some stack level, or the same name can be used to reference objects from different stack levels:
proc foo {} {set abc foo ; bar}
proc bar {} {set abc bar ; baz}
proc baz {} {set abc baz ; qux}
proc qux {} {
set abc qux
foreach n {3 2 1 0} {
upvar $n abc var
lappend res $var
}
puts [join $res { }]
}
foo
# => foo bar baz qux
Again, there is never any ambiguity, since the name + stack level designation makes the identity of the object clear.
The uplevel and upvar commands can be wonderfully convenient as long as you can keep the stack frames straight, and I for one use them all the time. As you saw in Donal's answer, though, even a Tcl ace can't always keep the stack frames straight, and in those cases namespace variables are much simpler and safer.
Documentation: array, dict, foreach, lappend, namespace, proc, puts, set, {*}, uplevel, upvar
Can any one help me in TCL programming, I am new to TCL
I would like to create functions like
employee_data Name() Dept() Tasks() ...
suppose i need execute from above function like employee_data Name() Tasks() ...
here i want skip Dept() arguments,
I tried to create function like but it does not work out..
proc employee_data {
Name (A B C....)
Dept (a b c....)
Tasks (s d f...)
} employee_data;
proc employee_data { Name($A $B $C) Dept($a $b $b) Tasks ($s $d $f) } {
Body...
}
Thank you very much.
I believe the basic misunderstanding is how you call/invoke Tcl functions/commands.
Unlike many other languages, where you invoke a function with func(arg1,arg2,"arg3",arg4) Tcl uses func $arg1 $arg2 "arg3" $arg4, where arg1, arg2 and arg4 are variables.
To define such a function, use proc. The syntax is
proc sum {a b} {
return [expr {$a + $b}]
}
a b is the arguments list. Note that you don't need to declare the function.
I don't exactly understand what you are trying to do here, but it looks more or less like a struct/class for me.
Just so as you know, Tcl and C++ have very different approaches to values and types. In particular, Tcl works almost entirely by logically-immutable references to values that are nevertheless type-mutable, which is very different to C++ (and many other languages, to be fair). True mutator operations (such as increments) actually create copies.
Another key difference is that in Tcl, everything is done by executing commands. That includes creating other commands. That proc? It's a call to a command (called proc) which immediately creates a procedure with the given name, arguments and body. There's nothing like declaration; stuff happens when you tell the code to make it happen. This sounds rather more complicated than it is.
How to pass list values into procedures
Suppose your Name was a list, you'd then pass the whole list value in (by immutable reference, so fast and safe) and the code inside could do whatever it wants without affecting the outside world. You'd then write that like this:
# Define
proc employee_data {Name ...} {
lassign $Name A B C
# ... do other processing ...
}
# Call
set Name {Aitem Bitem Citem}
employee_data $Name ...
You could also call with immediately-defined data; Tcl's entirely happy with this:
employee_data {Aitem Bitem Citem} ...
Passing in dictionary values
Dictionaries are very much like lists, except they map keys to values instead of being sequences.
proc employee_data {Name ...} {
dict with Name {}
# ...
}
set Name {A "foo" B "bar" C "grill"}
employee_data $Name ...
You can also pass a copy of the contents of an (associative) array as a dictionary value, like this:
employee_data [dict get Name] ...
Passing references
But suppose you wanted to mutate the outside world! To do that, you have to pass the name of the variable to change. You then use upvar to bind a local variable to the caller's variable so that you can do the modifications and have them stick. The upvar command lets you do in effect call-by-name, and it's highly magical.
proc employee_data {NameVar ...} {
upvar $NameVar name
# ... accessing $name as if it was whatever variable was passed ...
}
set Name {Aitem Bitem Citem}
# Note: *not* $Name! We want the name, not the contents!
employee_data Name ...
You can think of built-in commands like set and incr as working like this. And this works for associative arrays too.