Chapter 3. Doing Mathematics

This chapter introduces the most fundamental elements of the Tcl language such as comments, variables, expressions, and commands. You also get your first exposure to the two most difficult elements of Tcl: command substitution and grouping. Command substitution describes the manner in which programming statements are built from Tcl’s built-in commands and the results of those commands. Grouping refers to the way in which the operators "" and {} affect how command substitution works. This chapter also teaches you how to perform mathematical operations using Tcl’s expr command.

Guessing Numbers

This chapter’s game is a simple pick-a-number exercise. The game chooses a random number between 1 and 20, asks you to guess what it is, and then displays a message indicating whether you guessed correctly or not. To start the game, execute the script guess_rand.tcl in this chapter’s code directory on the Web site. Here are a few runs of the script:

$ ./guess_rand.tcl
Enter a number between 1 and 20: 5
Sorry! The number was 15.
$ ./guess_rand.tcl
Enter a number between 1 and 20: 5
Sorry! The number was 1.
$ ./guess_rand.tcl
Enter a number between 1 and 20: 5
Correct! The number was 5.

Although simple to play, guess_rand.tcl shows you how to do the following tasks:

  • Use Tcl’s expr command to perform mathematical calculations.

  • Use the puts command to display strings and variables.

  • Use the gets command to get input from the user.

  • Use the flush command to clear I/O channels.

  • Use the if command to evaluate a Boolean expression and execute different blocks of Tcl code, depending on the value of the Boolean expression.

  • Use mathematical and logical operators to perform numeric comparisons.

To understand the code, which you will be able to do by the time you get to the end of the chapter, you’ll need some background information. This introduction to key Tcl language features will give you the context you need to understand the code. Not to worry, though, as there are plenty of code snippets to digest and try out before you get to the guess_rand.tcl itself at the end of the chapter.

Language Fundamentals

Tcl possesses all the standard elements necessary to a programming language, such as comments, expressions, variables, loops, conditionals, and procedures. However, at least one feature you might expect to encounter, keywords, does not exist in Tcl. In place of keywords, Tcl uses commands. As you will see later in the chapter, this difference is more than one of terminology. Similarly, Tcl has at least two syntax elements, command substitution and grouping, which you rarely encounter in compiled programming languages. Command substitution refers to the process by which the results of one command can be obtained and used as the argument to a second command. The results of the first command replace, or substitute for, the arguments of the second command. Grouping combines multiple arguments into a single argument, which in turn affects how command substitution works. Together, you can use command substitution and grouping to build more complex commands. These two features pervade the Tcl language and exert a powerful influence on how you write a Tcl program. Command substitution and grouping are so important to understand and use properly that the next two chapters revisit them, pointing out potential pitfalls and illustrating how they are used. Before you get into the minutiae of Tcl, though, I should cover the basic language features.

Comments

Comments are explanatory text that the Tcl interpreter ignores at runtime. To put it more prosaically, comments exist for the programmer, not for the program. The Tcl interpreter does not execute comments. Rather, comments serve to provide information to someone reading the code. A Tcl comment is prefixed with the # character, colloquially referred to as the hash; must appear at the beginning of a command; and ends with the first unescaped newline (that is, a carriage return or end-of-line character). So, of the following two Tcl commands, only the first is a comment (see comments.tcl in the source code directory for this chapter):

# puts {Hello, Tcl/Tk World!}
set msg {Hello, Tcl/Tk World!} # set the msg variable

If you try to execute the second command, it results in a syntax error. If you really want to make the text following # in the second line a comment, use a semicolon at the end of the command, thus:

set msg {Hello, Tcl/Tk World!}; # set the msg variable

Tcl uses both newline characters and semicolons to terminate commands, so the trick here is that the Tcl interpreter parses the ;, interprets it as the end of the set command, then encounters the #, which it interprets as the beginning of a comment.

If you need to create multi-line comments, you have two options. The way I do it is to begin each comment line with #. For example:

# This is the first line of a two-line comment. It is not
# especially enlightening but it is two lines long

The alternative method is to escape the newlines at the end of all but the last line of a multi-line comment. The reason this works is that, as explained earlier, comments are terminated by the first unescaped newline. Escaping a character means to cause the interpreter to ignore the character or to treat it specially. In this case, you want to tell the Tcl interpreter to ignore the newline. How, then, do you escape the newline? Prefix it with a backslash. The following two lines are functionally equivalent to the example I just gave you:

# This is the first line of a two-line comment. It is not 
especially enlightening, but it is two lines long

I prefer the first form to the second for simple aesthetics and because I can immediately distinguish between comments and commands when scanning a source code file. My mental source code parser has a more difficult time initially recognizing the second form (with escaped newlines) as comments. Suit yourself, but I recommend prefixing each line of a multi-line comment with #.

For what are comments used? As I quipped earlier, comments are for programmers, not programs. You might use them to explain how an especially subtle section of your program works. Another good reason to add a comment is to explain why a certain block of code works the way it does. Still another use of comments is to tell later readers of the code who wrote it (or, more likely, to tell later readers whom to blame). Perhaps the most common reason to use comments in your programs is to remind yourself what the code is doing and why. Six months from now, when you look at that throwaway piece of code you wrote, it will be very handy to have comments that refresh your memory. I don’t know about you, but I have trouble remembering what I was thinking last week, so all the hints I can get help.

Commands

Commands are the meat of Tcl. Like keywords in other languages, Tcl commands are reserved words, meaning that they have a predefined meaning and usage in the language. However, unlike the keywords in most programming languages, you can redefine Tcl commands and replace a built-in Tcl command with an implementation of your own. What makes Tcl commands and Tcl itself different from other programming languages is that commands are the core of the language. Tcl commands do the heavy lifting in the language, not the interpreter. Each command defines its own arguments and is responsible for processing them. All that the interpreter does is parse the program, perform Tcl-specific syntax checking, group arguments, process substitutions, and then invoke the specified command, passing the arguments to the command for processing.

For example, the puts command accepts up to two arguments, an optional channel ID and a string to display. The interpreter performs any grouping on the arguments that might be required and then executes the puts command, passing all of the arguments available to puts. puts, not tclsh, validates the number and type of arguments and, if they validate, writes the specified string to the specified I/O channel, or to stdout if no I/O channel is requested. If the arguments don’t validate, it is again puts, not tclsh, that displays the error message and syntax diagnostic.

So what precisely is a Tcl command? A Tcl command is a list of one or more words, the first of which is the command you want to execute. The second and following words are arguments. For the time being, consider a word as any group of consecutive characters surrounded by white space. In Tcl, a character is either a letter, digit, or one of a limited set of special symbols. White space consists of one or more tabs, one or more space characters, or a combination of the two. Each word is considered a separate argument, so if you want to pass multiple words as a single argument (say, to the puts command), then you have to enclose them in quotes ("") or braces ({}). The double quotes or braces are not treated as part of the argument, however. They merely combine, or group, the words into a single logical unit.

Note: Arguments to Tcl Commands

Note: Arguments to Tcl Commands

Most Tcl commands take multiple arguments, but some accept only one, and some don’t accept any arguments. Moreover, many Tcl commands have both optional and required arguments.

Consider the following three commands:

puts "Hello, Tcl/Tk World!"
puts stdout {Hello, Tcl/Tk World!}
puts stdout Hello, "Tcl/Tk World!"

The first command has one argument, Hello, Tcl/Tk World!, because the string is enclosed in double quotes. The second command has two arguments, stdout and Hello, Tcl/Tk World!, because the string Hello, Tcl/Tk World! is enclosed in braces and is considered a single argument. The third command (which is invalid, by the way) has three arguments, stdout, Hello, and Tcl/Tk World!.

Command arguments fall into one of two categories: parameters and options. In general, parameters (the fancy computer science term is formal parameters) are arguments on which a command operates, such as data to print or numbers to add. For example, in the three puts commands just shown, all of the arguments are parameters; stdout is the (optional) channel ID and the various renderings of “Hello, Tcl/Tk World!” are the string puts should display.

Options, on the other hand, are arguments that modify how commands operate. Not all commands have them. Returning once more to the puts command, it accepts one option, -nonewline, which disables appending a newline to the string that puts display (puts’ default behavior is to append a newline to the string), as shown in the following example (see options.tcl in this chapter’s code directory):

puts "puts appends a newline to this sentence.";
puts -nonewline "puts doesn't append a newline to this sentence.";

The output of this script should look like the following:

$ ./options.tcl
puts appends a newline to this sentence.
puts doesn't append a newline to this sentence.
$

Notice how the shell’s command prompt winds up at the end of the sentence rather than at the beginning of the next line. Demonstrating the -nonewline option is not the point, of course, but rather the point is that the -nonewline option changes puts’ default behavior.

Many, perhaps most, of Tcl’s commands accept one or more options in addition to the parameters they accept. To further confuse matters, while options are usually, um, optional (that is, not required), many Tcl commands have both required and optional parameters. Failing to specify a required parameter (or a required option, for that matter) generates a syntax error. If you do not use an optional argument (such as puts’ channel ID argument), you will get the default behavior (in puts’ case, the default behavior is to display the string argument to stdout).

Tip: Arguments, Options, or Parameters?

Tip: Arguments, Options, or Parameters?

To keep things simple, I use the term argument to refer to both options and parameters unless the distinction between them is important.

Command Substitution

To execute a command, Tcl evaluates the command in two steps or passes: substitution and evaluation. During the first pass, substitution, the interpreter parses the command and its arguments (moving left to right), looking for words that need to be replaced with another value. Situations in which words are replaced, or substituted, include substituting variables with their values (hang on, I’ll talk about variables later in the chapter) and substituting embedded commands with their results. During the second pass, evaluation, the interpreter processes the command and performs the action(s) associated with that command.

One of Tcl’s language features that often confuses beginners is that substitution is nonrecursive. That is, only a single round of substitution occurs. If, for example, a variable’s value is a second variable, that second variable is passed unmolested to the command; a second round of substitution does not occur (a variable’s value can also be a command). Likewise, if an embedded command evaluates to yet another command or to a variable, the second command or the variable will not undergo further substitution.

The Tcl interpreter performs four types of substitutions during the substitution phase:

  1. Variable expansion: Variables are replaced with their values.

  2. Escape sequence substitution: Escape sequences (character strings preceded with ) are replaced with their hexadecimal values (see Table 3.1).

  3. Command replacement: Commands within brackets, what I have referred to as embedded commands, are replaced with the results of those commands.

  4. Line continuation: If a line ends with ewline (that is, a backslash followed by a newline), the ewline is replaced with a space, the current line is merged with the following line, and any leading white space in the following line is discarded.

Although I haven’t talked about variables yet, they look like this: $variable. In variable expansion, the value of $variable is replaced by its value. If the variable $name has the value Kurt Wall, the command puts $name; becomes puts "Kurt Wall"; (see varsub.tcl in this chapter’s code directory).

With command replacement, embedded commands are evaluated, and their results are substituted. For example, consider the following command (see cmd_repl.tcl in this chapter’s code directory):

puts "2 + 3 = [expr 2 + 3]"

During the substitution phase, the embedded command [expr 2 + 3], which adds 2 and 3, will be replaced by the result of the addition, so after substitution and before evaluation, the statement becomes:

puts "2 + 3 = 5"

Of all the substitution operations performed, command replacement is the most important because it is the method by which the results of a command are substituted in the command. It is this feature of Tcl that gives Tcl its power and its utility as a glue language. You needn’t take my word for it, though. As you read this book, work through the examples, and start writing your own programs, you will see how completely command replacement (widely and somewhat confusingly referred to as command substitution in Tcl documentation) pervades the language.

Escape sequence substitution, often referred to as backslash substitution, replaces a select set of escape sequences with their hexadecimal equivalent. Generally, escape sequences are used to display special characters, such as tabs or the carriage return character, but Tcl, like other programming languages, also uses escape sequences to display octal and hexadecimal values. The following example (escape.tcl in this chapter’s code directory) shows how you can insert tab characters (horizontal tabs, to be specific) into output printed with puts (the escape sequence represents a horizontal tab):

puts "A	tab	separates	each	of	these	words."

Here’s what the output looks like:

$ ./escape.tcl
A       tab     separates     each    of    these    words.

Escape sequences make the command slightly more difficult to read, but the output is crystal clear, if oddly formatted. Table 3.1 lists all of the escape sequences Tcl replaces during substitution.

Table 3.1. Tcl Escape Sequences

Escape Sequence

Character

Hexadecimal Value

a

Bell

x07



Backspace

x08

f

Form Feed

x0c

Newline

x0a

Carriage Return

x0d

Horizontal Tab

x09

v

Vertical Tab

x0b

ooo

Octal Value

0(values 0 to 7)

x hh

Hexadecimal Value

h(values 0 to 9 and/or A to F)

Here’s a bit of perversity for you. The backslash character is used both to cause characters to be treated specially and to prevent characters from being treated specially. As you just read, causes certain characters, the escape sequences shown in Table 3.1, to take on a new meaning. However, you can also escape sequences using , as it were. If you precede an escape sequence with a backslash, the escape sequence loses it special meaning. So, in the following command, I used to inhibit the substitution of the escape sequence (see no_escape.tcl in this chapter’s code directory):

puts "A\ttab\tseparates\teach\tof\tthese\twords."

The output is:

A	tab	separates	each	of	these	words.

Now, both the command and its output are difficult to read! The value of escaping escape sequences is that it enables you to use as normal characters, characters that otherwise have special meaning, such as $, which Tcl uses as the operator for obtaining the value of a variable.

Using the character to create multi-line comments is an example of line continuation (with the key difference that comments aren’t executed). Here’s another example of line continuation that uses a command, found in line_cont.tcl in this chapter’s code directory):

puts "This line is continued below
but appears as a single line."

After the substitution phase, the puts command would look like the following:

./line_cont.tcl
puts "This line is continued below but appears as a single line."

As you can see, the ewline sequence was replaced by a space. Tcl beginners often make the mistake of adding a space before the , which creates two spaces in the resulting command and ungainly (and probably unintended) spacing in the resulting output:

puts "This line is continued below 
but appears as a single line."

When executed, the output of this script is:

$ ./line_cont_oops.tcl
This line is continued below  but appears as a single line.

Notice the two spaces between the words below and but.

If all of this seems confusing, it will make more sense when you start writing programs a little later in this chapter. Think of it like learning a new card game. Someone has to describe the rules to you before you start playing or the actual card play will make little sense. Yet, once you have played a few hands (and, if you’re like me, gotten soundly defeated), the rules start making sense. The mechanics of command substitution become more meaningful when you see them in action. Separating this discussion of command substitution from other fundamentals of the Tcl language, particularly grouping, is artificial because in actual practice, command substitution and grouping work together. You don’t have much context, yet. Nonetheless, you have to start somewhere, so bear with me.

Grouping

This section will help you grok the previous section’s coverage of command substitution. In the simplest terms, grouping converts multiple words into a single argument. As you’ve already learned, Tcl treats multiple words as multiple arguments; grouping alters this behavior. You’ve already seen how double quotes can be used to group words into a single string argument for the puts command. Tcl also uses pairs of braces, {}, to perform grouping. Why use two operators to perform the same operation? Because each operator has different effects on the words or other language elements being grouped.

Grouping with Double Quotes

When you group words with double quotes, the grouped items function as a single argument and will be treated as a single argument during the evaluation phase of Tcl command execution. In addition, double quotes allow any variables that appear in the group to be substituted with their value. If it helps you remember, think of grouping with double quotes as performing weak grouping. The grouping is weak because it doesn’t prevent variables from being expanded into their values, as you can see in the following example (quotes.tcl on the Web site):

set fname Kurt
set mname {Roland}
set lname "Wall"
puts "Full name: $fname $mname $lname"

The output of this script shows my full name because the variables $fname, $mname, and $lname are substituted with their values:

$ ./quotes.tcl
Full name: Kurt Roland Wall

Grouping with Braces

Think of grouping with braces as strong grouping because braces inhibit the substitution of variables. They are strong enough to prevent variable substitution. Thus, characters and words grouped with braces are passed to the command exactly as they are written, as you can see in the following example (see braces.tcl):

set fname Kurt
set mname {Roland}
set lname "Wall"
puts {Full name: $fname $mname $lname}

Unlike quotes.tcl, braces.tcl does not substitute the values of $fname, $mname, and $lname:

$ ./braces.tcl
Full name: $fname $mname $lname

Backslash or escape sequence substitution is also disabled when the escape sequences are grouped with braces. If you replace the double quotes in quotes.tcl with braces, the escape sequence will be left alone, and the output will include the literal characters (see brace_esc.tcl in this chapter’s code directory):

puts {A	tab	separates	each	of	these	words.}

The output is predictably awful:

$ ./brace_esc.tcl
A	tab	separates	each	of	these	words.

There is one exception to the previous rule, however. An escaped newline at the end of a line is still evaluated as a space, allowing you to use line continuation with brace grouping, as you can see in brace_cont.tcl in this chapter’s code samples):

puts {This line is continued below
but appears as a single line.}

As you can see in the output, the combination of and a newline is replaced by a space, and the two lines are merged into a single line:

$ ./brace_cont.tcl
This line is continued below but appears as a single line.

You will see many, many examples of grouping with both double quotes and braces in the coming chapters. Like command substitution, grouping is a key feature of Tcl programming.

Variables

Finally, you’re going to read about variables. I’ve already used them several times to highlight the behavior of command substitution, but it’s time for a more formal definition. Variables are names that refer to memory addresses, which contain data that a program or script needs. To assign a value to a variable, use the set command. The syntax of the set command is:

set varName ? value?

This statement assigns value to the variable varName. varName can be a scalar variable or an array. For the time being, I’ll focus on scalar variables (Chapter 6, “Creating and Using Arrays,” discusses array usage in detail). If you omit the value argument, set returns the current value of varName. You’ve already seen several examples of variable assignment. In quotes.tcl, for example, the following commands assign the values Kurt, Roland, and Wall to the variables fname, mname, and lname, respectively:

set fname Kurt
set mname {Roland}
set lname "Wall"

When you execute this script, you see the following:

$ ./quotes.tcl
Full name: Kurt Roland Wall

These examples are straightforward. The first command performs no grouping and, because the value is only a single word, it isn’t necessary to surround the argument Kurt with double quotes. The second and third examples use brace and double quote grouping.

To obtain a variable’s value, known as dereferencing the variable, you have two options. The most common method is to use the $ operator in front of the variable name. So, referring back to the previous variable assignments, $fname yields Kurt; $mname yields Roland; and $lname yields Wall. The other method for dereferencing a variable is to use the syntax set varName, which omits the value and causes set to return varname’s current value. If varName hasn’t been assigned a value, it is an error to attempt to dereference it via either method, as you can see in the next example:

% set min 1
1
% set min
1
% set max
can't read "max": no such variable

If you try to put these commands in a script, the script won’t execute because the interpreter detects the syntax error in the set max; command. Here’s what happens when you execute these commands as a script (see var_set.tcl in this chapter’s code directory):

$ ./var_set.tcl
can't read "max": no such variable
    while executing
"set max"
    (file "var_set.tcl" line 7)

Whether executed interactively or as a script, the error is the same. Because the variable max has not yet been assigned a value, it does not yet exist from the point of view of the Tcl interpreter, so any attempt to dereference it generates an error.

Tip: Tcl Creates Variables Dynamically

Tip: Tcl Creates Variables Dynamically

Although variables have to be assigned a value before you can dereference them, it is not necessary to declare variables before you assign values to them because the interpreter creates variables as they are needed.

What if you want to print text that actually contains a $ character? You escape it, naturally (see print_ref.tcl):

set msg "You have won the game!"
puts $msg
puts $msg

Here’s the output from the script:

$ ./print_ref.tcl
You have won the game!
$msg

As mentioned earlier in the chapter, escaping the $ with a backslash causes the Tcl interpreter to ignore $’s special semantics and treat it as it if were a normal, merely mortal character with no special superpowers.

Just as you can use the set command to assign a value to a variable (and, consequently, bring a variable into existence), you can use the unset command to destroy a variable, as the following example, unset.tcl in this chapter’s code directory, shows:

% set msg "You have won the game!"
You have won the game!
% set msg
You have won the game!
% puts $msg
You have won the game!
% unset msg
% puts $msg
can't read "msg": no such variable
% set msg
can't read "msg" : no such variable

As you can see in the example, after you unset a variable name, attempting to dereference it generates an error. You won’t be able to execute unset.tcl unless you comment out the last two lines of the script (lines 9 and 10).

A final note before ending this section. The examples in this section use simple expressions to assign values to variables. However, as you might expect, it is common to use command substitutions to generate values for variables. In the following script, for instance (show_rand.tcl in this chapter’s code directory), I use the rand() function and the expr command to assign a random integer value between 1 and 20 to the variable randNum, truncate randNum with the round() function, and then pass randNum to puts:

set min 1
set max 20
set randNum [expr {round($min + (rand() * ($max - $min)))}]
puts "The random number is $randNum"

The output of this script will vary, but it should resemble the following:

$ ./show_rand.tcl
The random number is 20

The rand() function generates a random number between 0 and 1. The round() function rounds a floating point number (a number that contains a decimal or fractional component) to its nearest integral part. For example, round(10.1) yields 10; round(10.9) yields 11.

Procedures

Tcl procedures (known as subroutines or functions in other languages) are named blocks of code. Procedures enable you to create new commands and to redefine built-in Tcl commands. To define a procedure, use the proc command. The general syntax of the proc command is:

proc name args body
  • name is the name you assign to the procedure and by which you invoke it. A procedure name may consist of any combination of valid Tcl characters, but, as you might expect, the name is case-sensitive.

  • args is a space-separated list of zero or more parameters that you pass to the command. Parameters are optional, meaning that you can define procedures that do not accept any parameters. Later in this book, you will learn how to create procedures that accept variable-length argument lists and how to create procedures that assign default values to parameters.

  • body is the list of one or more commands that define the procedure. Actually, body doesn’t really define the procedure. Instead, body defines the command or commands that will be substituted (substitution, remember that?) for the name of the procedure when the procedure is invoked. Any valid Tcl language element can be used in the body, including other proc-defined procedures. You cannot, however, have nested procedures, that is, define a procedure within a procedure.

When a procedure terminates (upon reaching the outermost closing brace), the value it returns to the caller is the value of the last statement executed. You could call this a procedure’s default return value. Alternatively, you can define the value returned using the return command. My recommendation is to define the return value specifically. Doing so makes your code more readable and reduces the likelihood that later changes in the procedure’s definition unintentionally alter the return value.

You’ll get into the nuts and bolts of procedures in subsequent chapters, but you probably want to know what one looks like. The following example (from calc_percent.tcl in this chapter’s code directory) illustrates defining and using a procedure (the line numbers are not part of the procedure, just explanatory aids):

1    proc CalcPercent {part whole} {
2            set retVal [expr {double($part) / $whole * 100}]
3            return $retVal
4    }

Line 1 defines the procedure name, CalcPercent, and its formal parameters, part and whole. Tcl syntax requires that the opening brace appear on the same line as the proc command. The expression expr double($part) / $whole * 100 on line 2 calculates the percentage of whole which part represents. Because the outer command is a set command, the result of the calculation is stored in the variable retVal. On line 3, I use return $retVal to pass the result of the calculation back to the caller.

CalcPercent also serves as a good illustration of the importance of grouping. Recall that proc requires three arguments: the procedure’s name, its parameters (if any), and the body. In this case, CalcPercent accepts two parameters: part and whole. To present these parameters as a single argument to the proc command, they are grouped with braces. The same reasoning applies to the procedure’s body—by enclosing the two lines of code that make up the procedure body in braces, the interpreter parses them and passes them to the proc command as a single argument. In addition, because grouping occurs before substitution, the braces ensure that substitution doesn’t result in an unintended result, such as a value appearing in a command where a variable should be. This isn’t an issue in this particular procedure, but it can happen.

Getting User Input

Tcl has several commands for performing I/O (input/output). For interacting with users, the command is gets, which is the complement to the puts command you have been using. Although gets can be used to get input from files, network sockets, and stdin, this chapter only discusses using gets for stdin. The syntax for gets is:

gets channelID ? varName?

gets reads a line of input from the input source specified by channelID. It reads the entire line of input up to the end-of-line (EOL) character and discards the EOL. For the purposes of this chapter, channelID must be stdin, which, as you’ve already learned, is (usually) the keyboard. As you can see in the syntax diagram, varName is optional. If you specify varName, gets stores the input line in varName and returns the number of characters it read, not including the EOL. If you omit varName, gets returns the input line. The following example illustrates gets’ usage (see gets.tcl in this chapter’s code directory):

puts -nonewline "Please Player 1's name: "
flush stdout
set count [gets stdin playerName]
puts "Player 1's name is $playerName."
puts "It has $count characters."

The output of this program should resemble the following:

$ ./gets.tcl
Enter Player 1's name: Bubba
Player 1's name is Bubba.
It has 5 characters.

If you don’t care how many characters the input line contains, you could rewrite this example as:

puts -nonewline "Please Player 1's name: "
flush stdout
set playerName [gets stdin]
puts "Player 1's name is $playerName."

The point I want to make is that the following two commands have identical results because both assign gets’ result to the variable playerName.

gets stdin playerName
set playerName [gets stdin]

You might be wondering what the command flush stdout does in these two scripts. Permit me to explain. In short, it is necessary to display a prompt and request input on the same line. Without flush stdout, get.tcl would not have prompted for Player 1’s name until after you entered it and pressed Enter (which generates a newline). Not quite the effect you’re looking for.

Why? By default, puts buffers, or stores, output until it encounters a newline. As you’ve already learned, though, puts’ -nonewline option prevents puts from appending a newline to the string it displays. As a result, puts won’t display the requested output. To force the display of a partial line (a line of output that doesn’t include a newline), use the flush command, which does what the name implies, flushes (displays) any buffered I/O. When used on the stdout channel (thus, flush stdout in the two examples), the partial line will be displayed, and you get the output you expected.

Basic Mathematical Operators

The Tcl command for performing mathematical operations is expr. For as powerful and capable a command as expr, its syntax is simple:

expr arg ? arg ...?

Each arg is either a math operator or operand. expr concatenates each arg (adding spaces for separation as necessary), evaluates the concatenated statement as a Tcl expression, and then returns the result. The result is either a numeric value (an integer or floating point value) or a Boolean value (0 for false and 1 for true). Operands, the values upon which operators work, can be integers, floating point values, variables, or strings. Operators can be one of the mathematical operators shown in Table 3.2 or one of the mathematical functions you learn about in a later chapter.

Table 3.2. Tcl Mathematical Operators

Group

Operator

Description

Float

Integer

String

1

Unary minus

Yes

Yes

No

1

+

Unary plus

Yes

Yes

No

1

~

Bitwise NOT

Yes

Yes

No

1

!

Logical NOT

Yes

Yes

No

2

*

Multiplication

Yes

Yes

No

2

/

Division

Yes

Yes

Yes

2

%

Modulus

No

Yes

No

3

+

Addition

Yes

Yes

No

3

Subtraction

Yes

Yes

No

4

<<

Left shift

No

Yes

No

4

>>

Right shift

No

Yes

No

5

<

Numeric less than

Yes

Yes

No

5

>

Numeric greater than

Yes

Yes

No

5

<=

Numeric less than or equal

Yes

Yes

No

5

>=

Numeric greater than or equal

Yes

Yes

No

6

==

Numeric equality

Yes

Yes

No

6

!=

Numeric inequality

Yes

Yes

No

7

eq

String equality

No

No

Yes

7

ne

String inequality

No

No

Yes

8

$

Bitwise AND

No

Yes

No

9

^

Bitwise EXCLUSIVE OR

No

Yes

No

10

|

Bitwise OR

No

Yes

No

11

&&

Logical AND

No

Yes

No

12

||

Logical OR

No

Yes

No

13

x ? y : z

If-Then-Else

Yes

Yes

Yes

Tcl supports the standard mathematical operators common to all programming languages. The complete list appears in Table 3.2.

Table 3.2 groups each operator in order of decreasing precedence. Operators that are in the same group have the same precedence.

Note: Operator Groups Don’t Really Exist

Note: Operator Groups Don’t Really Exist

The operator grouping in Table 3.2 is a device I’ve used to identify operators that have the same precedence. Tcl lacks this notion. If you’re at a cocktail party with other Tcl programmers and refer to “group 5 operators,” they probably won’t know what you mean.

Operator precedence is the term used to describe the order in which mathematical operators are evaluated when multiple operators exist in an expression. Consider the following expr command:

set x [expr {21 << 2 * 3 + 4}]

Depending on the order of evaluation, x might be assigned 256, 588, 21504, or 344064. Because you (and Tcl) know the relative precedence of the operators, multiplication, then addition, then right-shift, you can reliably predict the answer, 21504. If you need to modify the order of evaluation, use parentheses around the operation(s) that need to occur first. If there are multiple parenthetical operations, evaluation proceeds from the innermost parenthesized operations outward, as demonstrated by precedence.tcl in this chapter’s code directory:

set x [expr {(((21 << 2) * 3) + 4)}]
puts "<< then * then +: $x";

set x [expr {(21 << 2) * (3 + 4)}]
puts "+ then << then *: $x";

set x [expr {21 << 2 * (3 + 4)}]
puts "+ then * then <<: $x"

set x [expr {21 << 2 * 3 + 4}]
puts "Default (* then + then <<): $x"

Executing the program shows the effects of the parentheses:

$ ./precedence.tcl
<< then * then +: x = 256
+ then << then *: x = 588
+ then * then <<: x = 344064
Default (* then + then <<): x = 21504

If expr’s expression evaluator encounters operators that have same precedence in an expression and the order of evaluation isn’t modified by parentheses, expr resolves the ambiguity by reading and parsing the expression left to right. For example, given the command [expr {2 * 9 % 5}], the result would be 3 because, absent parentheses to force a specific evaluation order, expr performs the multiplication first, then the modulus. If your program’s logic requires the modulus operation to be executed first, you must use parentheses to specify that. Accordingly, the proper expression in this case would be [expr 2 * (9 % 5)], which yields 8.

You will learn more about expr in subsequent chapters. What you’ve learned in this chapter is enough for you to be productive without bogging you down with subtleties and potential gotchas.

Conditional Execution: The if Command

Tcl’s if command is used for conditional execution. Most of the scripts you have seen so far have been simple “fall-through” programs. Execution starts the first command and proceeds linearly through each subsequent command until the script terminates after the last command executes. Every command is executed. There are plenty of programming tasks suited to this simple sequential execution model. However, sometimes scripts need to execute a certain command or set of commands multiple times. Likewise, you might need to execute one command or set of commands in one situation, but, in the same script, execute a different set of commands in another situation. Executing commands multiple times is known as looping or repetition (see “Chapter 4, Strings, Strings, Everywhere Strings!”). Varying the commands executed depending on the situation is referred to as conditional execution.

Conditional execution boils down to making a choice and acting on it. You deal with conditionals all the time. If I win the lottery, I won’t go to work; otherwise, I’ll go to work. If I’m late to work, my boss will be unhappy; if I’m on time to work, my boss will be satisfied; if I’m early to work, my boss will be pleased. Although Tcl has several commands for expressing and dealing with conditionals, the one you’ll learn to use in this chapter is the if command. It’s true that if’s general syntax is somewhat imposing to look at in the abstract, but easy to understand in practice:

if {expr1} ?then? {
        body1
} elseif {expr2} ?then? {
        body2
} elseif
...
?else? {
        ?bodyN?
}

The if command starts by evaluating expr1. If expr1 is true, that is, expr1 evaluates to a nonzero value or a string value of true or yes, then the commands in body1 are executed. Otherwise, if expr1 is false (either a numeric value of 0 or a string value of false or no), the elseif causes expr2 to be evaluated. If expr2 is true, body2 is executed. If it is false, the next expression will be evaluated, and so forth. If none of the expressions evaluates to true, then the else causes the commands in bodyN to be executed.

You can have any number of elseif clauses, including none at all. The then and else words are optional; you can include them to make the expressions easier to read (my personal practice is to use else but not to use then. The opening brace of the body commands must appear on the same lines as their corresponding if, elseif, and else commands. I also strongly recommend using indentation and multi-line statements, as shown in the syntax diagram, to make conditional statements easier to read.

Confusing? It isn’t, it just sounds that way. In the simplest case, you can have a single if command with no elseif or else clauses

set diceValue 5;
if {$diceValue >= 5} {
     puts "You rolled $diceValue. Roll again!"
}

set diceValue 4;
if {$diceValue >= 5} {
     puts "You rolled $diceValue. Roll again!"
}

In this command, there is no else clause. If $diceValue is less than five, execution skips to the command immediately following the closing brace. Otherwise, the puts statement executes (see if_simple.tcl). When you execute this script, the second puts statement doesn’t execute:

$ ./if_simple.tcl
You rolled 5. Roll again!

The next simplest case adds the else clause (see if_else.tcl):

set diceValue 5;
if {$diceValue >= 5} {
     puts "You rolled $diceValue. Roll again!"
} else {
     puts "You rolled $diceValue. Sorry, you lose!"
}

set diceValue 4;
if {$diceValue >= 5} {
     puts "You rolled $diceValue. Roll again!"
} else {
     puts "You rolled $diceValue. Sorry, you lose!"
}

In this case, if $diceValue is greater than or equal to five, the user gets to roll again; otherwise, the puts message informs the user of her bitter defeat. The output is shown in the following example:

$ ./if_else.tcl
You rolled 5. Roll again!
You rolled 4. Sorry, you lose!

Adding one or more elseif clauses makes it possible to express more than simple either-or choices (see if_elseif.tcl):

set diceValue 5;
if {$diceValue >= 5} {
     puts "You rolled $diceValue. Roll again!"
} elseif {$diceValue < 5 && $diceValue > 1} {
     puts "You rolled $diceValue.Lose a turn!"
} else {
     puts "You rolled $diceValue. Game over!"
}

set diceValue 4;
if {$diceValue >= 5} {
     puts "You rolled $diceValue. Roll again!"
} elseif {$diceValue > 1 && $diceValue < 5} {
     puts "You rolled $diceValue. Lose a turn!"
} else {
     puts "You rolled $diceValue. Game over!"
}

set diceValue 1;
if {$diceValue >= 5} {
     puts "You rolled $diceValue. Roll again!"
} elseif {$diceValue > 1 && $diceValue < 5} {
     puts "You rolled $diceValue. Lose a turn!"
} else {
     puts "You rolled $diceValue. Game over!"
}

In if_elseif.tcl, I used an elseif clause to create an option for the case in which the user “rolls” a value greater than one and less than five. The following example shows if_elseif.tcl’s output:

$ ./if_elseif.tcl
You rolled 5. Roll again!
You rolled 4. Lose a turn!
You rolled 1. Game over!

You can have as many elseif clauses as necessary, but if you need very many, you’ll probably want to use the switch command, which you’ll learn about in Chapter 5, “Working with Lists.” More than four or five elseif clauses looks messy and can be difficult to maintain.

Tip: if and expr Use the Same Expression Evaluator

Tip: if and expr Use the Same Expression Evaluator

The if and expr commands use the same expression evaluator. This means that the rules you learned for using expr also apply when using if. The exception is that you don’t have to use expr in if’s condition. So instead of writing if {[expr $x < 6]} {...}, you can just write if {$x < 6} {...}.

As I suggested, if looks imposing in a syntax diagram, but is easy to use in practice because its structure neatly mirrors the way you handle decisions.

Analyzing the Guessing Numbers Program

Now that I have described most of Tcl’s language features and introduced a number of Tcl commands, this chapter’s game, guess_rand.tcl, should be easy to understand. Each of the demonstration game programs in this book is generously commented (perhaps too generously), and significant blocks of code are named Block 1, Block 2, and so forth. Naming the blocks will make it easier for me to refer to them and, hopefully, less confusing for you.

Looking at the Code

Here’s the code for guess_rand.tcl.

#!/usr/bin/tclsh
# guess_rand.tcl
# Guess a random number between 1 and 20

# Block 1
# Algorithm is "min + (random * (max - min))"
set target [expr {int(1 + (rand() * 19))}]

# Block 2
# Read the user's guess
puts -nonewline "Enter a number between 1 and 20: "
flush stdout
gets stdin guess

# Block 3
# Validate the input
if { $guess < 1 || $guess > 20 } {
     puts "Your guess must be between 1 and 20"
     exit 1
}

# Block 4
# Do we have a winner?
if { $guess == $target } {
     set msg "Correct"
} else {
     set msg "Sorry"
}
puts "$msg! The number was $target."

Understanding the Code

Block 1 generates the random number. Strictly speaking, the rand() function generates a pseudo-random number. It is “pseudo” because the random number generator uses the same pattern to generate a random number (it turns out that generating a truly random number is surprisingly difficult). Regardless, I use the rand() function to generate a random number. As you will recall, rand() generates a floating point value between zero and one, so I need a way to scale it (move the decimal point) and convert it to an integer.

While I don’t want to get into the mathematics to prove the algorithm, the principle behind my random number generator is just this: to generate a random value between and including the minimum value min and the maximum value max, use the formula min + (random * (max – min)). After generating that number, I use the int() function to truncate the generated number (which is a floating point value) to its integral component and then assign that value to the variable $target.

In Block 2, I use the puts command to create a prompt for the user to input a number. As described earlier, I used -nonewline option to create a more attractive prompt and the flush stdout command to make sure that the prompt appears. The gets command stores the value the user provides in the variable named guess.

Block 3 performs a basic sanity check to ensure that the user’s input is within the range specified (1 and 20 in this case). If the user’s input falls outside this range, I display an error message and exit the script.

Otherwise, the script falls through to Block 4, where I compare the user’s guess, $guess, to the generated value, $target. This is a comparison ideally suited for conditional execution because, depending on the result of the test $guess == $target, I want to execute different blocks of code (albeit that the “blocks” in this case are each single commands). Specifically, I set the variable $msg. Finally, I display a message, again with the puts command, telling the user whether her guess was correct or not and what the generated value was. Notice how the conditional expression makes it easy to build the string that is displayed with the puts command. This is a typical Tcl technique, one you will see throughout this book, in Tcl code elsewhere, and, as time goes on, increasingly frequently in your own code.

Modifying the Code

Here are some exercises you can try to practice what you learned in this chapter:

  • 3.1. Modify guess_rand.tcl to use variables for storing the upper and lower bounds for the random number generator instead of using “magic numbers” in Block 1.

  • 3.2 Modify the program in Exercise 3.1 to ask the user to input lower and upper bounds for the random number generator rather than hard-coding the values into the script.

  • 3.3 Modify the program in Exercise 3.2 by replacing one or more of the code blocks with a procedure that performs the same task.

Appendix A contains suggested solutions for each exercise.

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

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