http://www.purl.org/tcl/home/man/tcl8.4/TclCmd/uplevel.htm [[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. [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. ---- See also [namespace], [upvar]. ---- [Tcl syntax help] - [Arts and crafts of Tcl-Tk programming] - [Category Command]