How can I callback Tcl commands from different namespaces without explicitly qualifying? - namespaces

Basically the problem is to execute Tcl code defined in one namespace, using calls to functions in that namespace, in an eval inside another namespace.
The following code works fine:
namespace eval ::eggs {
namespace export e1 eeval
proc e1 {} { puts pe1 }
proc eeval body { puts "in cb enter"; eval $body; puts "in cb exit" }
}
namespace import ::eggs::*
namespace eval ::spam {
namespace export s1 scall
proc scb {} { puts pscb }
proc scall {} { puts "in call enter"; eeval {::spam::scb}; puts "in call exit" }
e1
scall
}
namespace import ::spam::*
and prints:
% % % % % % pe1
in call enter
in cb enter
pscb
in cb exit
in call exit
Now I want to replace the ::spam::scb with a plain scb. I would trade in a wrapper for eeval.
My use case:
The namespace eggs is a very basic library for regression testing. The namespace spam is a small library implementing nice to have functions. They shall be tested upon reload. For this scall is called and uses a special test function in eggs called eeval.
The eeval is the unit test. For convenience and "do-not-repeat-yourself" reasons I'd like to not have to use the fully qualified namespace name of any function defined in spam.
Ideally scall would look like this:
proc scall {} { puts "in call enter"; eeval {scb}; puts "in call exit" }
However, during execution the eval in ::eggs::eeval does not know where to find scb. Since eggs is just a test library I cannot import the spam namespace.
Is there any way one could e.g. devise a wrapper for e.g. eeval to make it run in the other namespace?

If you're going to pass the code to eval (possibly appending arguments) then the simplest method to generate the callback script is with namespace code:
eeval [namespace code {scb}]
That generates all the wrapping code to ensure that things work correctly, including handling all sorts of cases you've not dealt with:
% namespace eval boo {
puts [namespace code {foo bar}]
}
::namespace inscope ::boo {foo bar}
However, if you're doing the callback immediately then the most common approach is not the above, but rather to use uplevel 1 to do the callback:
proc eeval body {
puts "in cb enter"
uplevel 1 $body
puts "in cb exit"
}
This has the advantage that the called-back code really is in the stack context of the caller, allowing it to do useful things like access local variables. Since the code is running in the same context that you define it[*], it's fine using that for resolving command names.
[*] That's a big fat lie — Tcl's semantics are rather more complex than that — but everything works like what I've said is true.

namespace current is the DRYer you're probably looking for.
namespace eval ::spam {
proc scall {} {
puts {in call 2 enter}
eeval [namespace current]::scb
puts {in call 2 exit}
}
}
Or pass the current namespace to the eeval proc
proc ::eggs::eeval {body {namespace ""}} {
puts "in cb enter"
if {$namespace == ""} {
eval $body
} else {
namespace eval $namespace $body
}
puts "in cb exit"
}
proc ::spam::scall {} {
puts "in call 3 enter"
eeval scb [namespace current]
puts "in call 3 exit"
}

Related

Tcl: How to get namespace and procedures of calling namespace?

I have some generic procedure. I would like this procedure to be able to get the name of the namespace and names of the procedures within the namespace where this procedure is called.
I have tried following code:
proc register {} {
puts [info procs]
puts [namespace current]
}
namespace eval Foo {
proc bar {} {
puts bar
}
proc _baz {} {
puts baz
}
register
}
However, this prints results for the namespace where register is defined, not for the namespace where it is executed. It looks like there are no dedicated commands for these tasks or at least these are not info or namespace commands.
To get information about the calling context, use uplevel:
proc register {} {
puts [uplevel 1 [list info procs]]
puts [uplevel 1 [list namespace current]]
}

Meaning of a proc name ending with ::

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?

What is a function equivalent of source in Tcl?

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.

Code that works in 8.4 is causing crash in 8.6, is there a better way to implement the functionality

I have a Tcl utility that makes it easy to ensure a snippet of code run at the time control flow leaves the current scope (of the proc). It crashes in Tcl 8.6.6, so I'm wondering if there is a "better" way to implement the functionality in Tcl 8.6?
An example usage is:
proc test {file} {
set fh [open $file]
::Util::Defer [list close $fh]
# ... do a bunch of stuff
# and even if we hit an error
# [close $fh] will be evaluated as we return
# from the proc
}
It's worked great in Tcl 8.4, and I use it all over my code.
As I'm still coming up to speed on all the functionality available in Tcl 8.6, I'm asking how should the ::Util::Defer proc be written to best take advantage of Tcl 8.6?
Here is the 8.4 implementation:
namespace eval ::Util {}
proc ::Util::Defer_impl {cmd args} {
uplevel 1 $cmd
}
proc ::Util::Defer {cmd} {
set vname _u_defer_var
# look for a unique variable name
while {[uplevel 1 [list info vars $vname]] != ""} {
set vname ${vname}_
}
uplevel 1 [list set $vname $cmd]
# when the variable is unset, trigger a call to the command
uplevel 1 [list trace add variable $vname unset [list ::Util::Defer_impl $cmd]]
# return a chunk of code enabling the user to cancel this if desired
return [list variable $vname unset [list ::Util::Defer_impl $cmd]]
}
Edited to add:
I appreciate the answers. To be honest, I already have other syntactic sugar for a file handle, this:
proc test {file} {
set fh [::Util::LocalFileHandle $file]
# do stuff
}
I was just hoping more for a generic solution to the ::Util::Defer - because I occasionally have two or three uses (at different locations) in the same proc. Yes, I'm leaving out the error handling if the doesn't exist or isn't readable.
Note: I have reported the bug to ActiveState and filed a bug at core.tcl.tk.
Edited to add buggy code: This is the Tcl code that causes a crash for me, it is slightly pared down to the essence (as opposed to being the full-blown ::Util::Defer).
# ---------------begin script-------------------
package require Itcl
proc ::try_uplevel {} {
return [uplevel 1 [list ::info vars _u_defer_var]]
}
itcl::class ::test_class {
constructor {} {}
public proc test_via_proc {} {
::try_uplevel
}
}
::test_class::test_via_proc
# ---------------end script-------------------
The pattern you describe is a supported one; it shouldn't crash (and indeed I can't reproduce the crash with 8.6.3 or the tip of the 8.6 support branch). The only problem it has is that if you have an error during the close (or any other deferred script) it won't report it, as you can see from this snippet (% is prompt):
% apply {{} {
::Util::Defer [list error boo]
puts hi
}}
hi
%
This is part of why I went to quite a bit of effort to provide a try command in 8.6. With that, you can do this:
proc test {filename} {
set f [open $filename]
try {
# Do stuff with $f
} finally {
close $f
}
}
It also takes care of tricky things like stitching errors thrown inside the body and the finally clause together (the body exception info is in the -during option of the finally clause's error exception info) so that if both places error you can find out about both.
% catch {
try {
error a
} finally {
error b
}
} x y
1
% puts $x
b
% puts $y
-errorstack {INNER {returnImm b {}}} -errorcode NONE -errorinfo {b
while executing
"error b"} -errorline 5 -during {-code 1 -level 0 -errorstack {INNER {returnImm a {}}} -errorcode NONE -errorinfo {a
while executing
"error a"} -errorline 3} -code 1 -level 0
Personally, I'd be more inclined to write this:
proc withreadfile {varName filename body} {
upvar 1 $varName f
set f [open $filename]
try {
return [uplevel 1 $body]
} finally {
close $f
}
}
proc test {file} {
withreadfile fh $file {
# Do stuff with $fh
}
}
Your mileage may vary.
Untested code (this exact snippet, I've used this pattern many times):
proc test file {
try {
open $file
} on ok fh {
# do stuff with fh
# more stuff
} finally {
catch {close $fh}
}
}
should be about the same. Regardless of whether you handle errors with the try structure or not, (or whether you get errors or not) the code in the finally clause is run when it ends. If you want to be able to cancel the action, use a simple if inside the clause.
Edit
In case one wants to see any errors generated when the channel is closed, it's a bad idea to just wrap it in a catch, which is necessary if the file couldn't be opened and the channel-id variable wasn't created. Alternatives include:
Checking for existence: if {[info exists fh]} {close $fh}
Propagate the closing error: using the result and options variable name arguments to catch.
Over the weekend this heavyweight solution came to mind. It leverages the itcl::local functionality to achieve the same effect. It does depend on Itcl - but since the problem is an interaction with Itcl, that seems a reasonable solution, even though it is not purely Tcl.
itcl::class Defer_impl {
constructor {cmd} {} {
set _to_eval $cmd
}
destructor {
uplevel 1 $_to_eval
}
private variable _to_eval {}
}
proc ::Util::Defer {cmd} {
uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}

Tcl: How to do a callback accross namespace borders?

Consider a library structured like this:
package provide ::mylib 1.0
namespace eval ::mylib {
namespace export f
proc f { action } {
# pass action around
g $action
}
proc g { action } {
eval $action
}
}
If I try to use it like this, it won't work:
....
namespace eval ::user {
set x 10
::mylib::f { puts $x }
}
The reason is that $x is not known in mylib. I can fix it like this:
namespace eval ::user {
set x 10
::mylib::f { puts $::user::x }
}
This works, but qualifying each variable in the argument to ::mylib::f is awkward. Another possibility is to wrap the code inside another namespace eval:
namespace eval ::user {
set x 10
::mylib::f { namespace eval { puts $x } }
}
Better, but still ugly. If it were Ruby or Perl, I would simply pass a closure to ::mylib::f. What is the best practice in Tcl?
BTW, I'm currently on Tcl 8.3, but hope to have the possibility to upgrade to a newer version soon, so solution in terms of 8.3 AND more recent versions are wellcome.
tl;dr version: namespace code
The namespace code command is intended for this situation. You pass it the script that you want to be encapsulated, and it hands you back a version that has magic in it so that it handles being called from anywhere. Here's how you might use it.
namespace eval ::user {
variable x 10
::mylib::f [namespace code { puts $x }]
}
Internally, it uses namespace inscope to do this. You're recommended to not use that directly; namespace code is the convenient way of doing this.
Note that it even works if the callback site passes arguments to it, so long as that site is doing:
eval $callback [list "the argument is this"]
(In fact, in 8.3 the eval isn't necessary because of a gross hack in unknown, but please do it the way I recommend above because we took that hack out in later versions. It was pretty awful.)