Chapter 6. Control Structure Commands

This chapter describes the Tcl commands that implement control structures: if, switch, foreach, while, for, break, continue, catch, error, and return.

Control structure in Tcl is achieved with commands, just like everything else. There are looping commands: while, foreach, and for. There are conditional commands: if and switch. There is an error handling command: catch. Finally, there are some commands to fine-tune control structures: break, continue, return, and error.

A control structure command often has a command body that is executed later, either conditionally or in a loop. In this case, it is important to group the command body with curly braces to avoid substitutions at the time the control structure command is invoked. Group with braces, and let the control structure command trigger evaluation at the proper time. A control structure command returns the value of the last command it chose to execute.

Another pleasant property of curly braces is that they group things together while including newlines. The examples use braces in a way that is both readable and convenient for extending the control structure commands across multiple lines.

Commands like if, for, and while involve boolean expressions. They use the expr command internally, so there is no need for you to invoke expr explicitly to evaluate their boolean test expressions.

If Then Else

The if command is the basic conditional command. If an expression is true, then execute one command body; otherwise, execute another command body. The second command body (the else clause) is optional. The syntax of the command is:

if expression ?then? body1 ?else? ?body2?

The then and else keywords are optional. In practice, I omit then but use else as illustrated in the next example. I always use braces around the command bodies, even in the simplest cases:

Example 6-1. A conditional if then else command

if {$x == 0} {
   puts stderr "Divide by zero!"
} else {
   set slope [expr $y/$x]
}

Note

A conditional if then else commandconditional, if then else.

Curly brace positioning is important.

The style of this example takes advantage of the way the Tcl interpreter parses commands. Recall that newlines are command terminators, except when the interpreter is in the middle of a group defined by braces or double quotes. The stylized placement of the opening curly brace at the end of the first and third lines exploits this property to extend the if command over multiple lines.

The first argument to if is a boolean expression. As a matter of style this expression is grouped with curly braces. The expression evaluator performs variable and command substitution on the expression. Using curly braces ensures that these substitutions are performed at the proper time. It is possible to be lax in this regard, with constructs such as:

if $x break continue

This is a sloppy, albeit legitimate, if command that will either break out of a loop or continue with the next iteration depending on the value of variable x. This style is fragile and error prone. Instead, always use braces around the command bodies to avoid trouble later when you modify the command. The following is much better (use then if it suits your taste):

if {$x} {
    break
} else {
    continue
}

When you are testing the result of a command, you can get away without using curly braces around the command, like this:

if [command] body1

However, it turns out that you can execute the if statement more efficiently if you always group the expression with braces, like this:

if {[command]} body1

You can create chained conditionals by using the elseif keyword. Again, note the careful placement of curly braces that create a single if command:

Example 6-2. Chained conditional with elseif

if {$key < 0} {
   incr range 1
} elseif {$key == 0} {
   return $range
} else {
   incr range -1
}

Any number of conditionals can be chained in this manner. However, the switch command provides a more powerful way to test multiple conditions.

Switch

The switch command is used to branch to one of many command bodies depending on the value of an expression. The choice can be made on the basis of pattern matching as well as simple comparisons. Pattern matching is discussed in more detail in Chapter 4 and Chapter 11. The general form of the command is:

switch flags value pat1 body1 pat2 body2 ...

Any number of pattern-body pairs can be specified. If multiple patterns match, only the body of the first matching pattern is evaluated. You can also group all the pattern-body pairs into one argument:

switch flags value { pat1 body1 pat2 body2 ... }

The first form allows substitutions on the patterns but will require backslashes to continue the command onto multiple lines. This is shown in Example 6-4 on page 78. The second form groups all the patterns and bodies into one argument. This makes it easy to group the whole command without worrying about newlines, but it suppresses any substitutions on the patterns. This is shown in Example 6-3. In either case, you should always group the command bodies with curly braces so that substitution occurs only on the body with the pattern that matches the value.

There are four possible flags that determine how value is matched.

-exact

Matches the value exactly to one of the patterns. This is the default.

-glob

Uses glob-style pattern matching. See page 53.

-regexp

Uses regular expression pattern matching. See page 144.

--

No flag (or end of flags). Necessary when value can begin with -.

The switch command raises an error if any other flag is specified or if the value begins with -. In practice I always use the -- flag before value so that I don't have to worry about that problem.

If the pattern associated with the last body is default, then this command body is executed if no other patterns match. The default keyword works only on the last pattern-body pair. If you use the default pattern on an earlier body, it will be treated as a pattern to match the literal string default:

Example 6-3. Using switch for an exact match

switch -exact -- $value {
   foo { doFoo; incr count(foo) }
   bar { doBar; return $count(foo)}
   default { incr count(other) }
}

If you have variable references or backslash sequences in the patterns, then you cannot use braces around all the pattern-body pairs. You must use backslashes to escape the newlines in the command:

Example 6-4. Using switch with substitutions in the patterns

switch -regexp -- $value 
   ^$key { body1 }
   	### { body2 }
   {[0-9]*} { body3 }

In this example, the first and second patterns have substitutions performed to replace $key with its value and with a tab character. The third pattern is quoted with curly braces to prevent command substitution; square brackets are part of the regular expression syntax, too. (See page Chapter 11.)

If the body associated with a pattern is just a dash, -, then the switch command “falls through” to the body associated with the next pattern. You can tie together any number of patterns in this manner.

Example 6-5. A switch with "fall through" cases

switch -glob -- $value {
   X* -
   Y* { takeXorYaction $value }
}

Comments in switch Commands

Comments in switch Commands

Example 6-6. Comments in switch commands

switch -- $value {
   # this comment confuses switch
   pattern { # this comment is ok }
}

While

The while command takes two arguments, a test and a command body:

while booleanExpr body

The while command repeatedly tests the boolean expression and then executes the body if the expression is true (nonzero). Because the test expression is evaluated again before each iteration of the loop, it is crucial to protect the expression from any substitutions before the while command is invoked. The following is an infinite loop (see also Example 1-13 on page 12):

set i 0 ; while $i<10 {incr i}

The following behaves as expected:

set i 0 ; while {$i<10} {incr i}

It is also possible to put nested commands in the boolean expression. The following example uses gets to read standard input. The gets command returns the number of characters read, returning -1 upon end of file. Each time through the loop, the variable line contains the next line in the file:

Example 6-7. A while loop to read standard input

set numLines 0 ; set numChars 0
while {[gets stdin line] >= 0} {
   incr numLines
   incr numChars [string length $line]
}

Foreach

The foreach command loops over a command body assigning one or more loop variables to each of the values in one or more lists. Multiple loop variables, which were introduced in Tcl 7.5, are a very useful feature. The syntax for the simple case of a single variable and a single list is:

foreach loopVar valueList commandBody

The first argument is the name of a variable, and the command body is executed once for each element in the list with the loop variable taking on successive values in the list. The list can be entered explicitly, as in the next example:

Example 6-8. Looping with foreach

set i 1
foreach value {1 3 5 7 11 13 17 19 23} {
   set i [expr $i*$value]
}
set i
=> 111546435

It is also common to use a list-valued variable or command result instead of a static list value. The next example loops through command-line arguments. The variable argv is set by the Tcl interpreter to be a list of the command-line arguments given when the interpreter was started:

Example 6-9. Parsing command-line arguments

# argv is set by the Tcl shells
# possible flags are:
# -max integer
# -force
# -verbose
set state flag
set force 0
set verbose 0
set max 10
foreach arg $argv {
   switch -- $state {
      flag {
         switch -glob -- $arg {
            -f*    {set force 1}
            -v*    {set verbose 1}
            -max   {set state max}
            default {error "unknown flag $arg"}
         }
      }
      max {
         set max $arg
         set state flag
      }
   }
}

The loop uses the state variable to keep track of what is expected next, which in this example is either a flag or the integer value for -max. The -- flag to switch is required in this example because the switch command complains about a bad flag if the pattern begins with a - character. The -glob option lets the user abbreviate the -force and -verbose options.

Parsing command-line argumentscommand-lineparsingparsingcommand-line arguments

Example 6-10. Using list with foreach

foreach x [list $a $b [foo]] {
   puts stdout "x = $x"
}

The loop variable x will take on the value of a, the value of b, and the result of the foo command, regardless of any special characters or whitespace in those values.

Multiple Loop Variables

You can have more than one loop variable with foreach. Suppose you have two loop variables x and y. In the first iteration of the loop, x gets the first value from the value list and y gets the second value. In the second iteration, x gets the third value and y gets the fourth value. This continues until there are no more values. If there are not enough values to assign to all the loop variables, the extra variables get the empty string as their value.

Example 6-11. Multiple loop variables with foreach

foreach {key value} {orange 55 blue 72 red 24 green} {
   puts "$key: $value"
}
orange: 55
blue: 72
red: 24
green:

If you have a command that returns a short list of values, then you can abuse the foreach command to assign the results of the commands to several variables all at once. For example, suppose the command MinMax returns two values as a list: the minimum and maximum values. Here is one way to get the values:

set result [MinMax $list]
set min [lindex $result 0]
set max [lindex $result 1]

The foreach command lets us do this much more compactly:

foreach {min max} [MinMax $list] {break}

The break in the body of the foreach loop guards against the case where the command returns more values than we expected. This trick is encapsulated into the lassign procedure in Example 10-4 on page 139.

Multiple Value Lists

The foreach command has the ability to loop over multiple value lists in parallel. In this case, each value list can also have one or more variables. The foreach command keeps iterating until all values are used from all value lists. If a value list runs out of values before the last iteration of the loop, its corresponding loop variables just get the empty string for their value.

Example 6-12. Multiple value lists with foreach

foreach {k1 k2} {orange blue red green black} value {55 72 24} {
   puts "$k1 $k2: $value"
}
orange blue: 55
red green: 72
black : 24

For

The for command is similar to the C for statement. It takes four arguments:

for initial test final body

The first argument is a command to initialize the loop. The second argument is a boolean expression that determines whether the loop body will execute. The third argument is a command to execute after the loop body:

Example 6-13. A for loop

for {set i 0} {$i < 10} {incr i 3} {
   lappend aList $i
}
set aList
=> 0 3 6 9

You could use for to iterate over a list, but you should really use foreach instead. Code like the following is slow and cluttered:

for {set i 0} {$i < [llength $list]} {incr i} {
    set value [lindex $list $i]
}

This is the same as:

foreach value $list {
}

Break and Continue

You can control loop execution with the break and continue commands. The break command causes immediate exit from a loop, while the continue command causes the loop to continue with the next iteration. There is no goto command in Tcl.

Catch

Until now we have ignored the possibility of errors. In practice, however, a command will raise an error if it is called with the wrong number of arguments, or if it detects some error condition particular to its implementation. An uncaught error aborts execution of a script.[*] The catch command is used to trap such errors. It takes two arguments:

catch command ?resultVar?

The first argument to catch is a command body. The second argument is the name of a variable that will contain the result of the command, or an error message if the command raises an error. catch returns zero if there was no error caught, or a nonzero error code if it did catch an error.

You should use curly braces to group the command instead of double quotes because catch invokes the full Tcl interpreter on the command. If double quotes are used, an extra round of substitutions occurs before catch is even called. The simplest use of catch looks like the following:

catch { command }

A more careful catch phrase saves the result and prints an error message:

Example 6-14. A standard catch phrase

if {[catch { command arg1 arg2 ... } result]} {
   puts stderr $result
} else {
   # command was ok, result contains the return value
}

A more general catch phrase is shown in the next example. Multiple commands are grouped into a command body. The errorInfo variable is set by the Tcl interpreter after an error to reflect the stack trace from the point of the error:

Example 6-15. A longer catch phrase

if {[catch {
   command1
   command2
   command3
} result]} {
   global errorInfo
   puts stderr $result
   puts stderr "*** Tcl TRACE ***"
   puts stderr $errorInfo
} else {
   # command body ok, result of last command is in result
}

These examples have not grouped the call to catch with curly braces. This is acceptable because catch always returns an integer, so the if command will parse correctly. However, if we had used while instead of if, then curly braces would be necessary to ensure that the catch phrase was evaluated repeatedly.

Catching More Than Errors

The catch command catches more than just errors. If the command body contains return, break, or continue commands, these terminate the command body and are reflected by catch as nonzero return codes. You need to be aware of this if you try to isolate troublesome code with a catch phrase. An innocent looking return command will cause the catch to signal an apparent error. The next example uses switch to find out exactly what catch returns. Nonerror cases are passed up to the surrounding code by invoking return, break, or continue:

Example 6-16. There are several possible return values from catch

switch [catch {
   command1
   command2
   ...
} result] {
   0 {                  # Normal completion }
   1 {                  # Error case }
   2 { return $result  ;# return from procedure}
   3 { break           ;# break out of the loop}
   4 { continue        ;# continue loop}
   default {            # User-defined error codes }
}

Error

The error command raises an error condition that terminates a script unless it is trapped with the catch command. The command takes up to three arguments:

error message ?info? ?code?

The message becomes the error message stored in the result variable of the catch command.

If the info argument is provided, then the Tcl interpreter uses this to initialize the errorInfo global variable. That variable is used to collect a stack trace from the point of the error. If the info argument is not provided, then the error command itself is used to initialize the errorInfo trace.

Example 6-17. Raising an error

proc foo {} {
   error bogus
}
foo
=> bogus
set errorInfo
=> bogus
   while executing
"error bogus"
   (procedure "foo" line 2)
   invoked from within
"foo"

In the previous example, the error command itself appears in the trace. One common use of the info argument is to preserve the errorInfo that is available after a catch. In the next example, the information from the original error is preserved:

Example 6-18. Preserving errorInfo when calling error

if {[catch {foo} result]} {
   global errorInfo
   set savedInfo $errorInfo
   # Attempt to handle the error here, but cannot...
   error $result $savedInfo
}

The code argument specifies a concise, machine-readable description of the error. It is stored into the global errorCode variable. It defaults to NONE. Many of the file system commands return an errorCode that has three elements: POSIX, the error name (e.g., ENOENT), and the associated error message:

POSIX ENOENT {No such file or directory}

In addition, your application can define error codes of its own. Catch phrases can examine the code in the global errorCode variable and decide how to respond to the error.

Return

The return command is used to return from a procedure. It is needed if return is to occur before the end of the procedure body, or if a constant value needs to be returned. As a matter of style, I also use return at the end of a procedure, even though a procedure returns the value of the last command executed in the body.

Exceptional return conditions can be specified with some optional arguments to return. The complete syntax is:

return ?-code c? ?-errorinfo i? ?-errorcode ec? string

The -code option value is one of ok, error, return, break, continue, or an integer. ok is the default if -code is not specified.

The -code error option makes return behave much like the error command. The -errorcode option sets the global errorCode variable, and the -errorinfo option initializes the errorInfo global variable. When you use return -code error, there is no error command in the stack trace. Compare Example 6-17 with Example 6-19:

Example 6-19. Raising an error with return

proc bar {} {
   return -code error bogus
}
catch {bar} result
=> 1
set result
=> bogus
set errorInfo
=> bogus
   while executing
"bar"

The return, break, and continue code options take effect in the caller of the procedure doing the exceptional return. If -code return is specified, then the calling procedure returns. If -code break is specified, then the calling procedure breaks out of a loop, and if -code continue is specified, then the calling procedure continues to the next iteration of the loop. These -code options to return enable the construction of new control structures entirely in Tcl. The following example implements the break command with a Tcl procedure:

proc break {} {
    return -code break
}

You can return integer-valued codes of your own with return -code, and trap them with catch in order to create your own control structures. There are also a number of exception packages available on the net that provide Java-like try-catch-except structures for Tcl, although the Tcl exception mechanism strikes a nice balance between simplicity and power.



[*] More precisely, the Tcl script unwinds and the current Tcl_Eval procedure in the C runtime library returns TCL_ERROR. There are three cases. In interactive use, the Tcl shell prints the error message. In Tk, errors that arise during event handling trigger a call to bgerror, a Tcl procedure you can implement in your application. In your own C code, you should check the result of Tcl_Eval and take appropriate action in the case of an error.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.27.45