Previous section   Next section

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 10.  Quoting Issues and Eval

The uplevel Command

The uplevel command is similar to eval, except that it evaluates a command in a different scope than the current procedure. It is useful for defining new control structures entirely in Tcl. The syntax for uplevel is:

uplevel ?level? command ?list1 list2 ...?

As with upvar, the level parameter is optional and defaults to 1, which means to execute the command in the scope of the calling procedure. The other common use of level is #0, which means to evaluate the command in the global scope. You can count up farther than one (e.g., 2 or 3), or count down from the global level (e.g., #1 or #2), but these cases rarely make sense.

When you specify the command argument, you must be aware of any substitutions that might be performed by the Tcl interpreter before uplevel is called. If you are entering the command directly, protect it with curly braces so that substitutions occur in the other scope. The following affects the variable x in the caller's scope:

uplevel {set x [expr $x + 1]}

However, the following will use the value of x in the current scope to define the value of x in the calling scope, which is probably not what was intended:

uplevel "set x [expr $x + 1]"

If you are constructing the command dynamically, again use list. This fragment is used later in Example 10-4:

uplevel [list foreach $args $valueList {break}]

It is common to have the command in a variable. This is the case when the command has been passed into your new control flow procedure as an argument. In this case, you should evaluate the command one level up. Put the level in explicitly to avoid cases where $cmd looks like a number!

uplevel 1 $cmd

Another common scenario is reading commands from users as part of an application. In this case, you should evaluate the command at the global scope. Example 16-2 on page 220 illustrates this use of uplevel:

uplevel #0 $cmd

If you are assembling a command from a few different lists, such as the args parameter, then you can use concat to form the command:

uplevel [concat $cmd $args]

The lists in $cmd and $args are concatenated into a single list, which is a valid Tcl command. Like eval, uplevel uses concat internally if it is given extra arguments, so you can leave out the explicit use of concat. The following commands are equivalent:

uplevel [concat $cmd $args]
uplevel "$cmd $args"
uplevel $cmd $args

Example 10-4 shows list assignment using the foreach trick described on Page 75. List assignment is useful if a command returns several values in a list. The lassign procedure assigns the list elements to several variables. The lassign procedure hides the foreach trick, but it must use the uplevel command so that the loop variables get assigned in the correct scope. The list command is used to construct the foreach command that is executed in the caller's scope. This is necessary so that $variables and $values get substituted before the command is evaluated in the other scope.

Example 10-4 lassign: list assignment with foreach.
# Assign a set of variables from a list of values.
# If there are more values than variables, they are returned.
# If there are fewer values than variables,
# the variables get the empty string.

proc lassign {valueList args} {
   if {[llength $args] == 0} {
      error "wrong # args: lassign list varname ?varname..?"
   if {[llength $valueList] == 0} {
      # Ensure one trip through the foreach loop
      set valueList [list {}]
   uplevel 1 [list foreach $args $valueList {break}]
   return [lrange $valueList [llength $args] end]

Example 10-5 illustrates a new control structure with the File_Process procedure that applies a callback to each line in a file. The call to uplevel allows the callback to be concatenated with the line to form the command. The list command is used to quote any special characters in line, so it appears as a single argument to the command.

Example 10-5 The File_Process procedure applies a command to each line of a file.
proc File_Process {file callback} {
   set in [open $file]
   while {[gets $file line] >= 0} {
      uplevel 1 $callback [list $line]
   close $in

What is the difference between these two commands?

uplevel 1 [list $callback $line]
uplevel 1 $callback [list $line]

The first form limits callback to be the name of the command, while the second form allows callback to be a command prefix. Once again, what is the bug with this version?

uplevel 1 $callback $line

The arbitrary value of $line is concatenated to the callback command, and it is likely to be a malformed command when executed.

      Previous section   Next section