Practical Introduction to Upvar and Uplevel

marsd wrote this and he's not proud.

I had a lot of trouble with upvar and uplevel up till a year or so ago so I figured I'd give a short overview of what I've learned of dealing with them.

I'll just use two basic proc's for this "tutorial":


Number One:(upvar)

proc pass {val {bdy "puts $LOCAL"} {lvl "#0"}} {
    upvar $lvl $val LOCAL
    eval $bdy
}

Description: A flexible proc that accepts various arguments:

val = the upvar variable to work with.

bdy = the commands to be executed..default is just to print to stdout the pointed variable.

lvl = what stacklevel do we want to work with?


Number two:(uplevel)

proc putUp {cmd {lvl "#0"}} {
    uplevel $lvl "eval $cmd"
}

Description: A flexible proc that evaluates $cmd at the lvl pointed stack. Disclaimer: I've got some negative feedback for using uplevel this way. Using uplevel this way WILL affect your program differently than using uplevel {command}. The method "cmd" allows transparent use of local and global variables, the other {cmd} causes problems with local variable evaluation. Maybe an expert could discuss the evaluation issue.


escargo 22 Sep 2005 - Is this "eval" necessary?

Lars H: No, it will only lead to some quoting hell.


Notes on levels: The "lvl" default arg in both proc's refers to a "stack level", a reference to #0 means global, #1 is up one level, #2 up two. There is no converse. No downlevel. The examples given will work well enough for all stack levels.

Lars H: That's a bit confusingly written, really. #1 is the level called from the global level. #2 is the level called from level #1, etc., so in increasing these numbers you in a sense go down (i.e., not as high up). In order to go up from the current level use numbers without #:

uplevel 0 $cmd;   # Equivalent to [eval $cmd].
uplevel 1 $cmd;   # Evaluate $cmd in your caller, equivalent to [uplevel $cmd].
uplevel 2 $cmd;   # Evaluate $cmd in your caller's caller. Equivalent to [uplevel [list uplevel $cmd]].

There's of course no way to go "down" from the current level, because there's nothing there; you're always conceptually at the end of a stack growing downwards.


Just Jump in here: Try running this example in tkcon or tclsh after loading the above procs into them.

set x 12 

pass x 

putUp {puts $x}  

Well if you've worked with upvar and uplevel before you'll know that's what these commands do, and should probably be somewhere else ;)

You should get 12 for each.


Now we modify the values twice via both proc's:

pass x {set LOCAL 3 ; puts $LOCAL ; putUp {set x 12}}

puts $x

putUp {set x 3 ; puts $x ; pass x {set LOCAL 12}}

puts $x

Break the command down and try it piece by piece if you don't follow the above.


This one is simpler, unsetting x:

pass x {unset LOCAL}

set x 12

putUp {unset x}

This one may be a little confusing:

putUp {proc foo {{x} {puts $x ; pass x {set LOCAL 21} "#1"}}}

rename foo ""

This calls the pass proc from a "dynamically" created proc named foo, created at the global stack level by the putUp proc.

The pass proc resets a variable passed to foo from global space and resets it at stack level 1, inside foo.

Control passes back and we destroy foo. A one shot wonder.

This is the real power of tcl illustrated. The flexibility of the language is amazing.


Finally:

Be aware that the info commands are very helpful in dynamically creating and keeping straight your processes called at various levels.

set x 12

pass x {puts "[[info level]], [[info level 1]]" ; set LOCAL 2}

The command {info level} tells what stack level the calling process is coming from. With the additional {info level "level"} arg the interpreter looks for any operations at that stack level.


Happy Tcling!

Good God this formatting is a bear!!!!