uplevel - Execute a script in a different stack frame
http://www.purl.org/tcl/home/man/tcl8.4/TclCmd/uplevel.htm
uplevel ?level? arg ?arg ...?
All of the arg arguments are concatenated as if they had been passed to concat; the result is then evaluated in the variable context indicated by level. Uplevel returns the result of that evaluation.
If level is an integer then it gives a distance (up the procedure calling stack) to move before executing the command. If level consists of # followed by a number then the number gives an absolute level number. If level is omitted then it defaults to 1. Level cannot be defaulted if the first command argument starts with a digit or #.
[Hopefully someone will provide some pointers to wiki and other resources relating to uplevel and when one would use it or should not use it.]
Harald Kirsch wrote in c.l.t: Question: Is it possible to have [macro] as a counterpart to proc such that [macro] defines a "function" which is evaluated in the caller's stack context, i.e. which does not create its own stack context?
So how's this:
proc macro {name body} { proc $name {} [list uplevel 1 $body] set name ;# not needed, but maybe helpful } ;# RS 0 % macro now {incr i; incr j} now 0 % set j 10; set i 1 1 0 % now 11 As a variation, you might also have the body executed in global scope, if you want to avoid [global] commands (not recommended normally, since the variables you use are not cleaned up): proc Sub {name body} { proc $name {} [list uplevel #0 $body] } % Sub nenv {array size env} % nenv 44 [DGP] Note that [[macro]] will not be able to handle a ''body'' that includes a [return] command, due to some fundamental limitations in Tcl itself. See tcllib::[control] documentation for some more details. [AMG]: `uplevel 0 $script` is the same as `eval $script`. [Tcl chatroom] transcript: '''suchenwi''': I'm not sure what you mean with "will not be able to handle a body that includes a return command". If there is one, it will cause the caller to return, I'd think. And that's like "#define BYE return;" in C - expected behavior for a macro. '''dgp''': No, it won't. Try it. ** Possible `uplevel` deficiencies ** '''suchenwi''': But Harald Kirsch already followed up, he wants 'macro' to take arguments and have local variables. This is tricky indeed - you can't do a "downlevel"... [AMG]: Here's a nice, simple [C]-style ''do ... while'' loop implemented using `uplevel`. Other implementations are available on the [New Control Structures] page. '''dgp''': Important to remember that '''uplevel''' adjusts only the command and variable substitution context. It can't really do anything to the calling stack.
suchenwi: Ah - I see. Tried " macro foo {incr i; incr j;return;puts "oops"}".
dgp: That doesn't test what needs testing
suchenwi: foo itself returns (the oops won't come), but does not terminate its caller. One would have to do
set body [string map {return {return -code return}} $body] ;-)
dgp:
macro mreturn return proc test {} {puts a; mreturn; puts b} test
suchenwi:
proc macro {name body} { proc $name {} [list uplevel 1 [string map { return {return -code return} } $body]]}
dgp: Yes, that string map will handle more cases, but can't achieve [return -code error], etc. One way to fix this limitation would be to add a new status code to Tcl that caused the result to be evaluated in the caller's context by the caller.
return -code eval {set var val}
Then you would just map all [return $arg $arg ...] to return -code eval {return $arg $arg ...} . Not completely sure what cans of worms get opened by that new feature though. Of course, with that status code available, [macro] would be much easier to write too.
kennykb: I like [return -code eval] -- except for deciding whether Tcl_EvalObj and its friends should interpret it or return it to the C caller.
rmax: Hi Kevin. I think [return -code eval] would also help [do] to become complete for all possible cases.
kennykb: Yes, dgp's comments make it obvious that it could do return -code eval { return -code eval { return -code whatever {... }}} to break out of multiple scopes.
dgp: I still would not recommend that though. Just as it should always be [uplevel 1 ...].
kennykb: Don: I wouldn't *recommend* it either, but a certain depth of nesting is needed for [control::do] to handle [return -code] within the loop body, right?
rmax: You won't of course create nested return -code eval constructs yourself, but they could emerge from nested do loops or macro calls.
dgp: I think catch needs extending too.
catch {return -code error foo} msg --> 2 set msg --> foo
But where is the '1' ? Add another argument to catch to get the '1'.
kennykb: Yes, also need a place to stash the -errorcode and -errorInfo options to [return]
dgp: I think you can just grab them from ::errorInfo and ::errorCode directly.
dgp: ...but yes, status code '1' needs special handling, as always, to deal with ::errorInfo and ::errorCode. For the rest, set code {catch $body msg}
if {$code == 2} { # We caught a return
D'oh! Make that
set code {catch $body msg returncode}
then...
return -code eval [list return -code $returncode $msg] }
dgp: Ah! I think I see what you were getting at!
kennykb: Hmmmm, it occurs to me that [return -code eval] subsumes all the others.
dgp: If $body itself contains [return -code eval $script].
kennykb: return -code error -errorcode code -errorinfo into message => return -code eval [list error message info code]
return -code break => return -code eval break
kennykb: ... and the same for continue and return.
suchenwi: Looks like it would actually make the language smaller...
dgp: ...except maybe for the differences in ::errorInfo construction.
kennykb: We'd have to keep the other syntax on return for back compatibility, but I think it could simplify the mechanism. Oh, yeah, ::errorInfo construction. OK, make it return -code eval [list error $message [doTheRightThingWith $info] $code].
suchenwi: ..and if eval is the last remaining value for -code, one could shorten that to -eval...
kennykb: Hmm, need a new command for those crazy enough to do [return -code 6].
dgp: How about "-code ok" ? I guess that's the default case where no eval script is done. Note: that's different from eval of an empty script.
Why a new command?
kennykb: I suppose if you wanted to be obsessive about it, you could say return -code ok $value -> return -code eval [list identity $value] where identity is a command that returns its arg, but I wouldn't go that far, myself.
dgp: We still need [return -code 6] if we want ability to create new status codes at the script level.
rmax: Isn't this possible to handle completely on the catch side? Doing it on the return side involves string operations on scripts which hurts performance.
dgp: proc niftyNewBreak {} {return -code 6}
kennykb: proc niftyNewBreak {} { return -code eval {::tcl::raiseException 6 }} ? 8^)
rmax: ... and there is the danger of substituting a "return" that isn't really a return.
dgp: Ah, I see.
kennykb: Where are we doing string operations on scripts?
dgp: No substitution. catch and re-raise.
rmax: The implementation of [do] would have to substitute "return -code ..." by "return -code eval {return -code ...}" or am I wrong?
kennykb: Reinhard: Ah, but there's no string processing there. The new command is a pure list.
dgp: [do] includes the line:
set code [[catch { uplevel 1 $body } result]]
Change to:
set code [catch { uplevel 1 $body } result returnCode]
Add a case to the switch
2 {return -code eval [list return -code $returnCode $result]}
kennykb: Yes.. and note that the thing to be 'eval'ed is a pure list.
dgp: The use of eval might prevent bytecode, so that's a performance issue, but there should be no need to reparse the strings.
rmax: wouldn't "2 {return -code $returnCode $result}" be sufficient?
dgp: No. The idea is that the return in $body should act as if it was evaluated in the caller. Hmmm. so far have taken care of the first pass through [do]. Remaining passes are handled by an uplevel 1 {while ...} That probably needs similar handling. Yes. Same changes there.
rmax: I still think it would be sufficient for [do] to be able to catch and pass on the -code argument to return.
kennykb: Gentleb[e]ings, I think we're excising one of the language's major warts here.
rmax: Can you give an example of usage for [do] where that wouldn't be enough?
* clock : 1011891602 : Jan 24,2002 Thursday 5pm GMT
kennykb: What if the body of the [do] contains [return -code eval] ?
dgp: I think that case is handled above.
rmax: do will pass on [return -code eval].
dgp: rmax: you don't want to return from [do], you want to return from the caller of [do].
rmax: Yes:
do { return -code return } inside do: set code [catch { uplevel 1 $body } result retCode] ... 2 { return -code [list return $retCode] $result }
What about that?
dgp: That will return from the caller of [do] when what is intended is to return from the caller of the caller of [do].
* rmax is totally confused now... has to try some things...
kennykb: That's yet another new syntax for return. How is that preferable to "return -code eval [list return -code $retCode $result]" ?
dgp: Oh. I mis-read part of that last one. Given what we've described, that should be an error:
rmax: it is a bit shorter, and I haven't thought much about it...
dgp: unknown code: "return 2"
kennykb: Since we need [return -code eval] anyway -- in order to implement [macro], I'm reluctant to invent still more syntax just to make [do] a tiny bit shorter.
rmax: Yep, Don. It implied another proposal of changing the syntax of return. OK, Kevin, you win.
dgp: The numeric code for eval should be -1.
kennykb: Why -1?
dgp: #define TCL_EVAL -1
dgp: It's an interesting status code in that it is completely internal. No command will actually return it, it will return the result of eval'ing the result instead. [return -code $integer] has been documented to allow any postive integer. -1 won't interfere with previous extended status codes.
kennykb: OK, we have a compatibility issue with C code that calls commandProc's directly. (There are examples doing stuff like that in Brent's book, IIRC).
dgp: hmmmm... take that back. I guess "positive" isn't in the docs after all. Are there any example doing that with *ObjCmd's ?
kennykb: I don't think we have too much of a problem as long as we avoid whatever code [exp_continue] uses.
dgp: Any existing code is calling existing command procedures. None of which return the status code TCL_EVAL, so is there really a problem?
bbh: exp_continue seems to be -101
kennykb: Don: Doesn't Brent's code have a Tcl_GetCommandInfo so that it's actually calling a user-supplied command proc?
dgp: Anyhow look for a section "Bypassing Tcl_Eval". His code doesn't handle any extende return codes, so there' no new problem here. His routine is called Tcl_Invoke. Tcl_Invoke("break", NULL); would cause problems now.
* suchenwi has to stop mirroring this debate on http://mini.net/cgi-bin/nph-wikit/1507.html - real work calling...
TIP #90 is a formalized follow-up of the above discussion. ...and is now incoroporated in Tcl 8.5 development.
17 October 2002: returneval provides a Tcl-only implementation of something very similar to the above [return -code eval].
See also: 2002-10-17: returneval provides a Tcl-only implementation of something very similar to the above return -code eval.
RHS: TIP 90 doesn't cover the -code eval idea, does it? I read through it and it didn't seem to.
Tcl syntax help - Arts and crafts of Tcl-Tk programming - Category Command - Category Introspection