cfi
cfi

Reputation: 11290

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

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?

Upvotes: 3

Views: 1802

Answers (2)

Donal Fellows
Donal Fellows

Reputation: 137567

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.

Upvotes: 2

glenn jackman
glenn jackman

Reputation: 246807

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

Upvotes: 2

Related Questions