upvar and upval in TCL

What is the use ?
The Tcl upvar and uplevel commands allow a procedure to modify the local variables of any procedure on the call stack. They are very powerful, and it is easy to use them to create code that is very hard to understand and maintain.

  • upvar is used when we have to change the value of a global variable from inside a procedure’s scope. 
Guidelines for upvar :
 
Significance of arguments "1" or "#0" :
Only access variables in the caller's environment, or in the global environment. In other words, always pass either "1" or "#0" as the first argument to upvar. Otherwise you create a dependency on the program's call path. (That's a number one, not a lower-case "L".)

Only access variables using names that the caller passed in; do not make assumptions about the caller's variable names. (This does not apply if you're accessing a global variable.) Otherwise you create a dependency on the caller's variable names. For example, do this:

proc goodExample {a b qName rName} {
    upvar 1 $qName quotient
    upvar 1 $rName remainder
    set quotient [expr {$a / $b}]
    set remainder [expr {$a % $b}]
}
 
Do not do this:

proc badExample {a b} {
    upvar 1 quotient quotient
    upvar 1 remainder remainder
    set quotient [expr {$a / $b}]
    set remainder [expr {$a % $b}]
}


Another Example :

proc example {one two} {
upvar $one local1
upvar $two local2

set local1 Sachin   
set local2 Tendulkar
}

set glob1 David
set glob2 Beckham

puts $glob1
puts $glob2\n

example glob1 glob2
puts $glob1
puts $glob2

Output :
David
Beckham

Sachin
Tendulkar


To access an array in the caller's environment :
Use upvar if you need to access an array in the caller's environment. For example, this procedure takes the name of an array in the caller's environment and returns a list of the array values:

proc arrayValues {arrayName} {
    upvar 1 $arrayName a
    set values {}
    foreach {name value} [array get a] {
    lappend values $value
    }
    return $values
}

array set myarray {george bush william clinton ronald reagan}
set v [arrayValues myarray]
# $v = {reagan bush clinton}

 
To set multiple variables in the caller's environment :
Use upvar if you need to set multiple variables in the caller's environment. For example, this procedure takes a list and any number of variable names in the caller's environment, and sets the variables to successive elements from the list:


proc unlist {list args} {
    foreach value $list name $args {
    if {![string length $name]} return
    upvar 1 $name var
    set var $value
    }
}

 
# calling the proc
unlist {scrambled corned buttered} eggs beef toast
puts "eggs : $eggs"
puts "beef : $beef"
puts "toast : $toast"

Output :
eggs : scrambled
beef : corned
toast : buttered

 
To create an alias for a global (or namespace) variable :
Use upvar if you want to create an alias for a global (or namespace) variable. For example, this procedure retrieves a URL and returns a list containing the HTTP status code and the URL entity:

proc wget {url} {
    set token [::http::geturl $url]
    upvar #0 $token state
    return [list [lindex $state(http) 1] $state(body)]
}

 
The http::geturl command returns a token such as "::http::7". The token is actually the name of an array variable in the http namespace, and that array is part of the http package's public interface. Without upvar, accessing an element of the array would require an awkward expression such as "[set ${token}(element-name)]" instead of the more natural "$state(element-name)".


Guidelines for uplevel
  • Only access the caller's environment. In other words, always pass "1" as the first argument to uplevel. Otherwise you create a dependency on the program's call path. (That's a number one, not a lower-case "L".)
  • In an uplevel, only run code that the caller provided, or code that accesses no local variables. Otherwise you create a dependency on the caller's variable names. Remember that inside an uplevel block, you cannot access your own local variables; only the caller's local variables are visible.

  • Only use uplevel to create new control structures. The following example creates a try command that provides a simple error-catching mechanism. It also uses upvar, and follows all of the upvar guidelines.

proc try {block args} {
    if {[llength $args] % 3 != 0} {
    error "usage: try block [pattern resultvar block] ..."
    }

    set code [catch { uplevel 1 $block } result]

    if {$code != 1} {
    return -code $code $result
    }

    foreach {pattern name handler} $args {
    if {[string match $pattern $result]} {
        upvar 1 $name var
        set var $result
        uplevel 1 $handler
        return
    }
    }

    error $result $::errorInfo $::errorCode
}

try {
    expr {1 / 0}
} "divide by zero" e {
    puts "got divide-by-zero error"
} * e {
    puts "unexpected error: $e"
}
# prints "got divide-by-zero error" 

1 comment:

  1. This explanation of upvar and upval in TCL is really helpful! Just like Host ever offers reliable services, understanding these commands can improve your TCL scripting.

    ReplyDelete