3
votes

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?

2
I apologize for choosing stupid names in my example code. I wanted to simplify and abstract/extract the idea, but it would have been more understandable if I had kept the names sigh I don't want to edit the Q now that the answers are so good and detailed. When my reputation has grown up I may choose to edit the post and the answers for more clarity... - cfi

2 Answers

2
votes

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.

2
votes

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"
}