Is there an easy way to unroll the stack in Tcl?
I have this strange problem where I have to get back to a particular stack frame, literally. I can get all the frame information using info
command, but to actually get to a particular frame I will have to set some local variables in the each procedure and check them accordingly. I was wondering if there is an easier way.
uplevel
with an integer argument larger than 1, or switching to 8.6 (which is actually a stackless Tcl implementation under the covers, a colossal change that it's taken quite a long time to bed in properly) - Donal Fellows 2012-04-06 05:34
If you need to get code to do a non-local return (i.e., skipping back several levels) you can do this from 8.5 onwards with the -level
option to return
. See this example:
proc foo-a {} {
puts a-in
foo-b
puts a-out
}
proc foo-b {} {
puts b-in
foo-c
puts b-out
}
proc foo-c {} {
puts c-in
foo-d
puts c-out
}
proc foo-d {} {
puts d-in
bar
puts d-out
}
proc bar {} {
puts bar-in
return -level 3
puts bar-out
}
foo-a
This works by throwing a special kind of exception; the details are quite hidden. Alternatively, you can also use try
and throw
if you've got 8.6 or a scripted implementation of them (see the Tcler's Wiki for Tcl code that was used during the discussion of the code in 8.6).
With older Tcl versions, the simplest mechanism is to use return -code 42
and to put some code in place up-stack to catch
the custom exception and determine if it is the magic value (42 here; it'll be the result of the catch
) and respond appropriately. This can be quite effective, but is also messy. Which is why 8.5 onwards give you tools that are easier to use.
Short answer is that you'd probably be better of rethinking your design.
Longer answer is that the only real way (I can think of) to do this is to throw an error and catch it back up at the level you need to stop it at. Other than, of course, the less flame-worthy way of checking variables all the way up the call stack. Using errors for control flow, however, is... bad form.
If you're willing to be bleeding-edge, coroutines (available in 8.6) might meet your needs.
Otherwise, what you might try is to note the level of the routine you need to get to when you're there (i.e., set a global variable to [info level]), and then in the deeper proc, [uplevel] to that absolute stack frame. Something like
proc need-to-be-here {} {
set ::myframe [info level]
deeper-calls
}
proc deepest-call {} {
uplevel #$::myframe {what I need to do}
}
(untested)
uplevel
. Just don't use it like that in any code I have to maintain! :- - Donal Fellows 2012-04-06 06:12