Creating Control Structures and Exception Handling

The uplevel Command

uplevel ?level? arg ...
The uplevel command executes arg ... as a Tcl script in the calling context. If level is given, it specifies the context in which to execute arg ... in the same manner as upvar.
proc myset {var value} {
    uplevel set $var $value
}
uplevel is used to create new control structures; most Tcl commands that take a Tcl scipt as an argument need to use uplevel to execute the script in the calling context. See below for examples.

Errors

Many Tcl commands generate an error condition when there is a problem (command invoked with invalid arguments, runtime error, etc). This error condition, or error for short, interrupts the running program and causes Tcl to print an error message before terminating. Tcl commands often generate errors in cases where, in most programming languages, a subroutine would return a special value that would have to checked for.
set foo
Error: can't read "foo": no such variable

The Stack Trace

When not running interactively, the Tcl interpreter generates a stack trace upon receipt of an error. This can help you find exactly where the error occurred:
$ megatcl -c "while 1 {if 1 {set foo}}"
Error: can't read "foo": no such variable
can't read "foo": no such variable
    while executing
"set foo"
    invoked from within
"if 1 {set foo}"
    ("while" body line 1)
    invoked from within
"while 1 {if 1 {set foo}}"
$ 
The stack trace indicates the nesting of the erroneous command within contriol structures (like while and if) and also within procedure bodys. If an error occurs deep within several levels of procedure invocations, the stack trace can be very long.

While the stack trace is useful during debugging, it's probably not desirable for a production program. You can turn off the stack trace with the -n argument to the megatcl (or Extended Tcl) interpreter:

$ megatcl -n -c "while 1 {if 1 {set foo}}"
Error: can't read "foo": no such variable
$ 
You can do this on the #! line in your script:
#!/local/bin/megatcl -nf

Generating Errors

You can generate your own error conditions with the error command.

The error Command

error message ?info? ?code?
This command takes one mandatory argument, the error message; when invoked, it generates an error condition and terminates execution.
if ![regexp {^[0-9]+$} $num] {
    error "num must be numeric"
}

error takes two optional arguments which we will discuss later.

Catching Errors

In addition to generating errors, you can also catch, or trap, them. In a production quality program, you should probably catch any possible Tcl errors.

The catch Command

catch body ?var?
The body argument to catch is a Tcl script, which catch executes. If an error occurs withing the script, it does not terminate execution; instead, catch returns 1 to indicate that an error occurred. If no error occurs, catch returns 0.

In this simple form, catch can be used to ignore errors, or to test for them. Here's an example of using catch to implement a version of the info exists command:

proc varexists {var} {
    upvar $var v
    if [catch {set v}] {
	return 0
    } else {
	return 1
    }
}
Or more compactly:
proc varexists {var} {
    upvar $var v
    expr ![catch {set v}]
}
catch takes an optional argument var, which serves two purposes:
  1. If an error occurred, var is set to contain the error message.
  2. If no error occurred, var is set to contain the result of executing the script.
For example, the open command can generate an error if a file to be opened for reading doesn't exist. You might think that the solution is to use file exists first, but open can fail for many other reasons (improper permissions, etc). Tcl's error message might be just the thing you want in this case, but you may want to continue execution or try to open another file. For this you need catch:
if [catch {open $file r} result] {
    # error!
    puts stderr "Warning: $result"
} else {
    set fp $result
}

Other Exceptional Conditions

The error condition is actually just one of several exceptional conditions or exceptions that Tcl can generate. Three other standard exceptions are: The catch command will catch all of these exceptions. How can you distinguish them? catch returns a different non-zero numeric code for each of them. See page 122 in Ousterhout.
catch {return hey!}
=> 2
For most purposes, you can simply treat any non-zero exception as an error condition; the only time you need to worry about the distinction is when you're writing very fancy control structures.

In addition the standard exceptions, Tcl lets you define your own. See the try command.

Unix Signals

Unix signals are similar to Tcl exceptions, in that they are received asynchronously and can be trapped or allowed to have their default behavior. You can program with Unix signals via the Extended Tcl signal command.

The signal Command

signal action siglist ?command?
The action argument can be one of: siglist is a list of signal names; the action is applied to all of the signals. Signal names can be given as either SIGINT or INT.

For example, the Unix signal 15 (SIGTERM) is the canonical signal to terminate a program; it's sent to all processes by the OS when the system is going down, for example. Signal 1 (SIGHUP) is the hangup signal, sent when a dial up or network connection is dropped. You may want to be sure to be able to perform some cleanup actions upon receipt of these signals; assuming you have a proc called cleanup that does what's necessary, you can arrange for it to be called as follows:

signal trap [list SIGTERM SIGHUP] cleanup

The kill Command

kill ?-pgroup? ?signal? idlist
The Extended Tcl kill command allows you to send Unix signals to other processes (including yourself). signal is the signal number or name of the signal you want to send (SIGTERM is the default), and idlist is a list of process id's.

The alarm Command

alarm seconds
The Extended Tcl alarm command arranges for the kernel to send your process a SIGALRM (signal 14) in seconds seconds (this is a floating point value, so seconds may be fractional). If seconds is 0.0, any previous alarm request is cancelled. The alarm command returns the number of seconds left in the previous alarm.

alarm can be used in conjunction with signal to generate timeouts. See the timeout command below.

Some New Control Structures

Here are some example of new control structures.

The errdefault Command

errdefault code ?default?
This command executes the Tcl script code, returning its result if there is no error. If there is an error, default is returned (or the empty string, if default isn't specified).
proc errdefault {code {default ""}} {
    if [catch {uplevel 1 $code} result] {
	return $default
    } else {
	return $result
    }
}
set fp [errdefault {open $file r} stdin]

The unwindProtect Command

unwindProtect body protected
The unwindProtect command is modelled after a similar command in Lisp. It executes the script body, guaranteeing that the script protected will be executed afterward even if an error occurs in body. Note that unwindProtect doesn't catch the error, it passes it on after executing protected.

A classic use of unwindProtect is to close files even if an error occurs in processing:

foreach file $filelist {
    if [catch {open $file} result] {
        puts stderr "Warning: $result"
    } else {
        set fp $result
        unwindProtect {
            # process file here...
        } {
            catch {close $fp}
        }
    }
}
If there are several hundred files in filelist, and there were errors in processing many of them, we would run out of file descriptors if it weren't for unwindProtect.

The timeout Command

timeout seconds body
The timeout command executes the script body with a timeout of seconds: if body doesn't finish execution within the specified time frame, it is interrupted and a TIMEOUT error is generated. timeout uses the SIGALRM alarm signal. timeout is often useful in network programming, where long delays can occur.
catch {
    timeout 30 {
	lassign [server_open $host $port] read write
    }
}

Keith Waclena
The University of Chicago Library
This page last updated: Thu Aug 25 13:28:18 CDT 1994
This page was generated from Extended HTML by xhtml.