Updated 2016-09-06 07:04:22 by pooryorick

uplevel, a built-in Tcl command, evaluates a script at a previous level.

Documentation  edit

official reference

Synopsis  edit

uplevel ?level? arg ?arg ...?

Description  edit

uplevel concatenates the arg arguments, in the same manner as concat, into a single script, changes the current level to $level, evaluates the script, and returns the result.

If level is positive integer, uplevel evaluates the script that many levels up from the current level. If level is an integer preceded by #, uplevel evaluates the script at the level identified by that number, where the first level is #0, the next level #1, and so on. If level is omitted, it defaults to 1, but it cannot be omitted if the first command argument starts with a digit or #.

AMG: The last sentence of the previous paragraph bears repeating. For safety's sake, always give the level explicitly, even though it's 1. This is because there are many situations where the script argument of uplevel comes from elsewhere, for instance when creating new control structures (a major use for uplevel). In those situations, the script argument could legitimately begin with a digit or #, such as when it starts with a comment.

PYK 2014-06-13: This advice only applies to uplevel with more than one argument. When there is only one argument to uplevel, such as the example in new control structures, there's no ambiguity.

AMG: I wish you were right about that, but it's not so. Also, please read the discussion surrounding the example you reference. It says, "We can make this a little more robust by specifying the exact number of levels to uplevel, which may prevent $body from being misinterpreted."
% proc 1 {} {return hello}
% uplevel 1
bad level "1"

PYK: Issue 1017884, "Make level arg to uplevel/upvar mandatory", discusses this. Jeff Hobbs states that fixing it breaks existing code, and MS points out that fixing it so that the 1-argument case did the right thing would only affect code that is currently a syntax error. So is the current behaviour a wart?

PYK 2016-05-04: There's another effect to passing only one argument to uplevel, i.e. not providing an explicit level: If the argument is a list, a string representation is generated for each item in the list. This could potentially have a significant memory/performance impact. See also this ticket.

AMG: Executing a script in a different level means only that the script is given local access to that level's variables, plus the attendant change to info level. It does not "make the caller do something." I know of only one circumstance where this distinction matters, but it's critical: uplevel return is the same as return with no uplevel; uplevel cannot be used to terminate the caller. (Instead use return's -level option.)

PYK: In addition to local access to that level's variables, uplevel also changes the command resolution context to that level. Although uplevel return causes the script uplevel is in to return, any substitutions in the script occur in the context specified by uplevel:
set x goodbye

apply {{} {
        set x hello 
        uplevel {return $x}
}} ;# -> goodbye

Another such circumstance is uplevel tailcall, which replaces the caller of uplevel, not the frame specified by uplevel. This is part of the explanation for the uplevel tailcall ... trick:
proc p1 {} {
    puts 2
    uplevel tailcall puts 3 
    puts 4 
}

proc main {} {
    puts 1
    p1
    puts 5
}


main

The output of which is
1
2
5
3

What just happened? tailcall replaced the invocation of p1 (not of main), and then... well, I don't really know what happened after that! This has been discussed in the Tcl chatroom, so maybe there's a good explanation somewhere in the history there.

MS (to clarify what's happening, without saying anything about if this is as it should or not): [tailcall a b c]'s action can be thought as two steps:

  1. set things up so that [a b c] is run after the frame where tailcall was invoked returns
  2. [return -code return] immediately

In the above example, uplevel mixes and decouples things a bit:

  1. due to uplevel, tailcall is invoked in main's scope: it sets things up so that [puts 3] is run on main's return
  2. [uplevel tailcall] behaves as [uplevel return] and returns with -code return, causing p1 to terminate
proc p1 {} {
    puts 2
    uplevel tailcall puts 3 
    puts 4 
}

proc main {} {
    puts 1
    p1
    puts 5
}


main

The documentation for tailcall currently says that apart from the difference in command resolution, it is equivalent to uplevel 1 [list command ?arg ...?], but that's not so in this case:
proc p1 {} {
    puts 2
    uplevel {puts 3; return}
    puts 4 
}

proc main {} {
    puts 1
    p1
    puts 5
}


main

Output:
1
2
5

PYK 2016-01-01: In nxs, this turned out to be a solution for the question of how to return from a deeply recursive command while still allowing all the recursive calls to do their cleanup and return.

APN 2016-09-06: Is use of tailcall within uplevel even legal? The manpage says
  This command may not be invoked from within an uplevel into a procedure or inside a catch inside a procedure or lambda.

Am I not interpreting it correctly?

PYK 2016-09-06: The documention is 99% descriptive and 1% prescriptive. That sentence is the sign on the mushroom that says, "Don't eat me."

AMG: uplevel 0 $script is the same as eval $script.

Possible uplevel deficiencies  edit

AMG: Here's a nice, simple C-style do ... while loop implemented using uplevel. Other implementations are available on the New Control Structures page.
proc do {body while condition} {
    if {$while ne {while}} {
        error "invalid argument \"$while\": must be while"
    }
    uplevel 1 $body
    uplevel 1 [list while $condition $body]
}

I'm curious about the bytecode compilation properties of this control structure. I don't think $condition and $body ever get compiled. Right? If so, is there any way to fix this? Putting the code to be executed into a randomly named proc would enable bytecode compilation, but running the code in a new level defeats the whole purpose of uplevel!

So, does there exist a Tcl_Obj type for bytecode-compiled script snippets which are not given their own level?

I have another question. Can do be enhanced such that return inside the do body (or condition!) will cause do's caller to return? Incrementing the -level option of return works, but this is not a change to do, it is a change to the arguments of do.
proc test1 {} {
    do {return success} while {1}
    return failure
}
proc test2 {} {
    do {return -level 2 success} while {1}
    return failure
}
test1 ;# returns failure
test2 ;# returns success

AMG: These are still burning questions... Anyone have answers?

steveb Here is how I do this with Tcl8.5+ and Jim Tcl. Needs some small tweaks if you want to support break and continue properly.
proc do {body while condition} {
    if {$while ne {while}} {
        error "invalid argument \"$while\": must be while"
    }
    set rc [catch {
        uplevel 1 $body
        uplevel 1 [list while $condition $body]
    } msg opts]
    if {$rc} {
            dict incr opts -level
    }
    return {*}$opts $msg
}

DKF: With 8.6 (specifically, tailcall) you can do this:
proc do {body while condition} {
    if {$while ne {while}} {
        error "invalid argument \"$while\": must be while"
    }
    tailcall while 1 $body\n[list if $condition {} else break]
}

AMG: Neat! These both work as-is in Tcl, and they pass the test1/test2 test case I posted above. My remaining question is: how can I ensure that the code is bytecoded? With DKF's approach it might happen, but the bytecodes will be regenerated each time [do] is called.

I need this for icc catch, currently implemented this way:
proc ::wibble::icc::catch script {
    try {
        uplevel 1 $script
    } on 7 events {
        return $events
    }
    return
}

I should be able to change it thus:
proc ::wibble::icc::catch {script} {
    tailcall try $script on 7 events {set events} on ok {} list
}

And hey! This gets bytecoded!
set script {puts before; puts >[wibble::icc get myfeed foo]<; puts after}
tcl::unsupported::representation $script

source wibble.tcl
wibble::icc configure myfeed accept foo
wibble::icc put myfeed foo
wibble::icc put myfeed exception
wibble::icc catch $script

tcl::unsupported::representation $script

The first tcl::unsupported::representation says that $script is a pure string; the second says that it's a bytecode. Success!

The script prints "before", then the [wibble::icc catch] returns "foo exception". There are no reverse angle brackets around it, nor is "after" printed, therefore the script gets interrupted inside [wibble::icc get], as expected.

Thanks, guys! The trick was to avoid uplevel. ;^)

Question: uplevel into Mini Langage  edit

[codeshot]: As a newcomer to Tcl wanting to build re-usable components with domain-specific mini languages, Tcl looks to be close to perfect. But there is a key feature missing from uplevel. I really want to be able to do:
namespace context-lift 1 ::ns $script

This would run each command in $script as normal, except that when the tcl interpreter performs expansion for each command in the script it would expand in the context of the caller, but then it would run the resulting command in the ::ns namespace. That would make it easy to create mini-languages:
minilang {
    cmd1 $a {
        $b
    }
    cmd2 $c [list $d $e]
}

In this example, two commands would be run in the ::ns namespace and they would each be able to uplevel into the minilanguage function uplevel 1 or into the caller with uplevel 2:
cmd1 {*}{value-of-a} {
    $b
}

and
cmd2 {*}{value-of-c} {*}{{{*}{value-of-d} {*}{value-of-e}}}

As you can see, the quoting to do this outside of the Tcl interpreter is messy. Implementing it outside of the interpreter is practically impossible as far as I can see.

What forums should I join to raise a feature request and discuss the requirements?

aspect: sounds like a job for namespace inscope - try something like:
uplevel 1 [list namespace inscope ::ns $body]

Note that namespace inscope takes a script as an argument, where uplevel takes a command - going by your examples, a script is what you really need.

PYK 2014-06-14: Most Tcl object systems provide some means to do roughly what codeshot seems to be requiring. I don't see the point in the {*} syntax above. Perhaps OP could explain what this syntax means to him. Community forums include the Tcl Chatroom and comp.lang.tcl.

When to use uplevel  edit

  • if tempted to use levels other than 1 or #0, make sure there is strong justification, then try to avoid other levels anyway
  • in a coroutine context, #1 accesses the first level in the coroutine context, which can be useful.
  • fully-qualify variables and commands to avoid trouble
  • try to avoid doing uplevel with anything other than a script passed in by the caller, or a fixed string

AMG: uplevel provides access to another level's local variables. In most cases, upvar is preferred over uplevel: it's simpler and it doesn't interfere with bytecode compilation. However, if the script to be executed comes from elsewhere (e.g. it was passed as an argument), uplevel is required.

As DKF points out below, tailcall may be a superior alternative, when level is 1 (default) and the uplevel'ed code is the last thing done by the proc. tailcall's argument is a command, which is different than a script. If you want to execute a script, I suggest using single-argument try as a shim. eval would also work, but it prevents bytecode compilation.

Historical: uplevel and return  edit

The following conversation took place in 2002, prior to the implementation in version Changes in Tcl/Tk 8.5 of TIP 90, "Enable return -code in Control Structure Procs".

Harald Kirsch wrote in c.l.t:

Question: Is it possible to have a command called macro as a counterpart to proc such that macro defines a "function" which is evaluated in the caller's level, i.e., which does not create its own level?

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: Due to some fundamental limitations in Tcl itself, macro is not able to handle a body that includes return. 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.

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

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%|%$::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 returncode}
if {$code == 2} { 
    # We caught a return 
    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 evaled 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 extended return codes, so there's 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://wiki.tcl.tk/1507 - real work calling...

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.

Lars H: No, but -code eval was considered as an alternative solution. Maybe it's not in the TIP itself, but in some of the surrounding discussion, however a good deal of thought was put into it.

See Also  edit

namespace
upvar
tailcall
downlevel