Tutorial_16

A Primer on Tcl/TK

This tutorial does not have an associated RAD application.


What are Tcl and TK?

Tcl (Tool Command Language) is a full-fledged programming language. This language, created by John K. Ousterhout at the University of California at Berkeley, is interpreted rather than compiled. This means that Tcl code is parsed and then executed line by line. TK is an extension to Tcl, which allows the building of graphical user interfaces.

RAD and the Speech Toolkit

The Rapid Application Developer (RAD) refers to the canvas, objects and everything you see when you build and use a spoken dialog. RAD, written in Tcl/TK, is only a visual interface to the various low-level processing modules such as audio, telephony, and speech recognition and speech synthesis. These low-level modules are what we refer to as the Speech Toolkit and are mostly written in C.

Several ways to use the Tcl/TK language.

One way is to type Tcl/Tk commands directly to the Tcl interpreter. Each command is executed as soon as you hit <enter>. This is referred to as the "command shell" Access the Command Shell by selecting Start -> Speech Toolkit -> CSLUsh.

Tcl may also be directed to read input from a source file. A source file can contain many Tcl/TK commands. They are executed when you <Double Click> the file or "source" the file from another Tcl/TK program. Simply, enter your Tcl/TK code in a text editor like Notepad or MS Word and save it with the .tcl extension. I.e. c:/.rad/myprogram.tcl

 

In this exercise we will learn the basics of Tcl by entering some Tcl commands into a command shell. Later we will include Tcl code with a RAD application.

RAD provides three places where to insert Tcl/Tk commands:

  1. Start-of-run action. This code is executed at the start of a RAD application, You enter the start-of-run commands by selecting
  2. System->Start of run action from the main menu.

  3. State action. Most RAD objects allow you to enter Tcl/TK code by selecting the object’s Preferences Menu-> OnEnter or OnExit Tabs. When the dialogue reaches the state, the Tcl/TK code in these windows is executed.
  4. End-of-run action. The Tcl/TK code is executed at the end of a dialog. (I.e. when a Goodbye Object is reached ) You enter the end-of-run commands by selecting the System->End of run action from the main menu.

In this lab, you will learn basic programming skills in Tcl. and then create several RAD applications which require you to supply Tcl commands. For more details, please consult the Tcl manual, Tcl and the Tk Toolkit, by John K. Ousterhout; or the Tcl/Tk HELP pages which accompany the Windows release of Tcl, included in the Speech Toolkit.

 

 

 

Lab: Starting the Tcl Shell

Start the Tcl shell, and work through as many of the following examples as time allows. To start the Tcl shell in WINDOWS, click:

Start ->Speech Toolkit ->CSLUsh

A new Tcl console window will be started.

The following sections highlight basic Tcl commands. Please type the following commands into the Tclsh window and pay attention to the normal output and error messages which the Tcl shell outputs in response to your command input.

Tcl Command Syntax

Each Tcl command occupies one line and ends with a newline character created by using the Enter key and represented below by the arrow symbol:

Each new line in the following tutorial are represented by a % sign

%puts "This is a normal Tcl command."¿

You may group several commands on one line by separating the commands by semicolons:

%puts "Command 1." ; puts "Command 2."¿

You many also use several lines for a single command as long as you terminate each line except the last with a backslash:

%puts "A very long string can be extended over several lines\¿

by using the backslash character at the end of each line\¿

except the last." ¿

A line beginning with a pound sign ‘#’ is a comment:

%# This is a comment in Tcl.¿

In the Tcl commands which we present in this tutorial from this point forward, we will not include the percent sign which is the Tcl prompt, nor will we include the arrow which symbolizes the newline character. You should assume that each line is typed exactly as shown, with a newline at the end of each line, unless otherwise specified.

Variables

All variables in Tcl contain strings of ASCII characters. There is no binary data. Numbers are stored as a sequence of digits. There is no difference between "123" and 123.

Variable names start with an alphabetic character, and consist of alphabetic and digit characters. There are two types of variables: simple variables and associative arrays.

Simple variables contain only one value. To place a value into a simple variable, use the set command:

set count 0

Associative arrays consist of as many array entries as desired, each distinguished by a unique value within parentheses after the array name. The set command is used to place a value an associative array:

set countarray(start) 10

set countarray(end) 20

To retrieve the value of a variable, use a dollar sign character ‘$’ before the name of the variable. (Try leaving the dollar sign out and see what happens.)

puts $count

puts $countarray(start)

To get a list of the entries in an associative array, such as countarray above, you can use the array names command.

array names countarray

Substitution

The Tcl interpreter evaluates commands in two stages--parsing and executing. During the parsing stage, the interpreter performs any required substitutions. There are three types of substitution of interest:

Command substitution is invoked by the square brackets ‘[‘ and ‘]’. Everything within square brackets is parsed and evaluated as a Tcl command, and the result is substituted, as in the following example.

set counter 1
puts [incr counter]

because the incr command increments a variable by one, after command substitution the second line above becomes:

puts 2

Variable substitution is invoked by a dollar sign ‘$’ as we have seen above. Variable substitution means that the value of a variable in inserted in a command line rather than a reference to the variable. Remember that $counter refers to the value of the variable named counter. Some commands require the variable name instead of the value. Be wary when using the commands like incr, regsub, and set in which you usually want to use the variable name instead of its value.

set foo money
puts "Show me the $foo"

Character substitution is invoked by a backslash ‘\’. This allows characters which have a special meaning within Tcl to be used literally, as in the following example:

set cliche "\t Show \n\t me \n\t the \n\t \$money"
puts $cliche

What kinds of substitution occurs depends on the delimiters used. The three delimiter character pairs [] "" {} all have different effects:

The square brackets [ ] trigger command substitution. Within the brackets, both variable substitution and character substitution take place.

The double quotes "" can be used to delimit a string. Within the quotes, both variable substitution and character substitution take place.

Variable substitution and character substitution can be avoided entirely by using the curly braces. These braces are very handy for defining procedures, loops, and conditionals, as you will see.

set cliche {\n\t Show me the $money \n}
puts $cliche

The various types of substitution are often confusing for new Tcl programmers. As with most things, it becomes easier with practice. The following rules of thumb may be helpful as you get started:

  • Use quotes "" to delimit strings in which you want variable and character substitution only.
  • Use square brackets [ ] to delimit commands from which you need a value returned.
  • Use curly braces { } to delimit most everything else.

Decisions

Tcl provides the standard if-elseif-else construct. Here is an example. It prints out a hypothetical patrol officer's response to various speeds at which a motorist is clocked. Notice how you can prompt a user for keyin data:

set speed [puts "Speed? " ; gets stdin]

if { $speed < 45 } {

puts "You are going too slow."

} elseif { $speed < 55 } {

puts "Have a nice day."

} elseif { $speed < 65 } {

puts "I must give you a warning."

} else {

puts "I'm giving you a ticket for speeding."

}

Another handy decision making structure is the switch command. This example converts certain digits into words.

set digit 3

switch $digit {

0 {set digit zero}

1 {set digit one}

2 {set digit two}

3 {set digit three}

default {set digit "More than three"}

}

puts $digit

Lists

Lists are used in Tcl to deal with collections of things. In its simplest form, a list is a string of elements separated by spaces or tabs. In more complicated lists, each list element may itself be a list. Tcl provides numerous commands that operate on lists. A few of the essential ones will be illustrated.

To create a list, use the list command.

set names [list stephen ed andrew]

puts $names

The lindex command returns the specified element from a list.

puts [lindex names 1]

Note that the first element of the list is index 0.

Another very handy command, especially when tickling information out of a web page, is

lappend. This command appends a list item to the end of a list.

set names [lappend names Johan]

puts $names

Of course, many other list commands exist. Their names largely indicate what they do. You can visit the on-line references for more details. Other list commands include: concat, join, linsert, llength, lreplace, lrange, lsearch, lsort, and split.

Strings

Tcl provides numerous commands to manipulate strings. Some of the most common operations are described here.

The string compare command takes two strings as arguments and returns -1, 0, or 1 depending on whether the first string is lexicographically less than, equal to, or greater than the second string.

set this "that"

if {[string compare $this "that"] == 0} {

puts "$this is equal to that"

} else {puts "$this is not equal to that"}

Since the string compare function returns a 0 if the strings are equal, you may also use the not operator ‘!’ in the if statement.

if {![string compare $this "that"]} {

The string length command returns the number of characters in a string:

set names "abcdef"

if {[string length $names] > 10} puts "Too long."

Another powerful set of string manipulation tools involves regular expressions. These commands include regsub, regexp, and findStringN. Regular expressions allow pattern matching in a string. We will demonstrate the use of some of these commands later in this lab.

Looping

Tcl provides several iteration commands, illustrated here.

In this example, the numbers 1, 2, 3, 4, and 5 are printed out by successive iteration through a while loop. The incr command is used to increment a counter by one on each iteration. Note that incr expects the first argument to be a variable name, so don't use ‘$’. The second argument may be any integer. If omitted, the increment value defaults to 1.

set counter 1

while { $counter <= 5 } {

puts $counter

incr counter 1

}

In this example, each item in a list is processed.

set thislist "a b c d e"

foreach listelement $thislist {

puts $listelement

}

And this example demonstrates another method of processing each item by explicitly accessing it.

set numlist "This is a test list of eight items"

set index 0

while {index < 8} {

puts [lindex $numlist index]

incr index

}

Subroutines

In this example, we define a procedure named add. It receives two parameters, which are formally named term1 and term2. It adds them together and returns the result. It makes use of a local variable sum. The expr command is used to calculate the sum of the two formal variables within the proc.

proc add {term1 term2}

set sum [expr $term1 + $term2]

puts "The sum of $term1 and $term2 is $sum"

return $sum

}

The procedure defined above is invoked as follows:

set ans [add 3 5]

Notice that square brackets [ ] are used to surround the call to this subroutine. In general, square brackets can be used any time a subroutine returns a value which is to be inserted in the command. Brackets can even be nested as in the following example:

puts "[set x1 [add 3 5]] and [set x2 [add 5 8]] makes [add $x1 $x2]"

I/O: File Input and Output in Tcl

This section illustrates basic file and console I/O. You have already seen how the gets command can be used to read user input and how the puts command can be used to print text to the screen. Now let’s see how to deal with basic file I/O.

We are going to write a procedure to a file and source it later. First set a variable to the text to be written to the file.

set output {proc hello {} {puts "Hello, world"}}

Now the file must be opened for writing. The open command returns a "handle" to the file. This handle is used to refer to the file in future commands.

set fhandle [open myproc w]

puts $fhandle $output

close $fhandle

The open also needs to be told the manner of the file access, e.g. reading, writing, or appending. In this example, the file was opened for writing as specified by the w parameter. Other file access modes include: r, r+, w+, a, a+. See the online documentation for details on these modes.

If the first argument to a puts command is a file handle, the string will be read or written to the file named in the second argument. In the absence of a file handle, the string will be read from standard input or written to standard output. It is always good programming practice to close a file after you are finished with it.

Sourcing

It is tiresome to key large amounts of code into RAD action box statements. Instead, we recommend that the code be organized into procedures in a separate text file. Then, simply source the file when you want the procedures defined, and call the procedures in a short way when you need them. In this example, we are going to source the file you generated in the previous section. Recall that the body of that file was a procedure called hello.

source "myproc"

Now the procedure hello should be defined. Test it out.

hello

While this procedure is not particularly useful, it illustrates how to employ Tcl code that exists outside of your application. This saves a lot of typing as you build your dialogues, and lets you use your favorite text editor while composing Tcl routines.

Lab: Dates, Times and Tcl

Date: Basic Version

(FIGURE: Screen shot of DATE application with ACTION box)

In this exercise you will create a small RAD program using the programming techniques discussed above to retrieve and speak the date. You will accomplish the following:

  • use pattern matching commands like regsub
  • create a source file containing procedures defined with the Tcl proc keyword
  • source the file into your RAD program
  • call procedures defined in your sourced file from within your RAD program

1. Start with a clear canvas. Drop in three icons (names do not matter): an <<audio icon>>, an <<action icon>>, and a <<goodbye icon>>. Connect them in the order listed.

2. In the <<action icon>>, set the action to the follow code:

set mydate [clock format [clock seconds] -format "%a %b %d %Y"]

set mytime [clock format [clock seconds] -format "%X %p %Z"]

tts $mydate

tts $mytime

The clock command is a platform independent Tcl command that you can use to retrieve the date and time. The clock seconds command returns an integer value representing the current second. The clock format command allows you to format the current time in human-readable form. The string with percent signs (i.e. "%a %b %d %Y") dictates the format. See the on-line Tcl documentation pages for more details.

The WINDOWS operating system also has built-in date and time commands that can be executed from any command prompt. So an important alternative to using the clock commands is to pass the operating system a command by means of the Tcl exec command. For example, in WINDOWS, the following commands would also return the date and time:

set mydate [exec cmd "/c date /t"]

set mytime [exec cmd "/c time /t"]

The /c tells the WINDOWS operating system to invoke the command shell only long enough to execute the specified command and then terminate---without the /c option, the command program would be left running invisibly in the background and successive calls would eventually start so many copies of the command program that computer would have no memory space left.

3. Build and run your program.

When you run the program, it will tell you the date, but it will attempt to pronounce the abbreviations as though they were words. This will sound very strange. We will worry about that in the next section.

Date: Improved Version

Computer science has a powerful concept called regular expressions, which will come in handy in developing the improved version of the date application. In the original version of the application, we retrieved the date and time from the system and synthesized the pronunciation of the string so that the user could hear the result. However, it sounded like gobbledegook because of the abbreviations in the string returned by the system. We are about to fix that problem.

1. Modify the <<action icon>> code to the following, replacing the day of the week and the month with the current values:

set mydate [clock format [clock seconds] -format "%a %b %d %Y"]

set mytime [clock format [clock seconds] -format "%X %p %Z"]

regsub {Mon } $mydate {Monday } mydate

regsub {Aug } $mydate {August } mydate

tts $mydate

tts $mytime

The regsub command replaces the abbreviations with the complete form of the words. The first argument in curly braces { } is the regular expression to search for. In this example, the regular expression is a straightforward sequence of characters. However, regular expressions can be much more general by use of special characters. The following are some of the special characters permitted in regular expression patterns:

Special Character

Meaning

.

Matches any single character

^

Matches the start of a string

$

Matches the end of a string

\x

Matches the character ‘x’

[string]

Matches any single character in the string. May be a range as in [A-Z]. If the first character in the brackets is ^, only characters not in the string are matches

(regexp)

Matches the regular expression

*

Matches a sequence of zero or more of the previous regular expression component.

+

Matches a sequence of one or more of the previous regular expression component.

?

The preceding regular expression component is optional.

|

Logical operator OR.

By combining the above components, you can create regular expressions of arbitrary complexity. The -all option can follow the regsub command to indicate that all instances of the specified pattern should be changed, not just the first instance, which is the default behavior.

3. Build and run your program.

When you call, the date should sound better than before. Notice how each part is pronounced. Are there any other parts you would like to change?

4. Remove the seconds from the time.

Do this by adding the following line in the appropriate place. It replaces a colon, two digits, and a space with just a space. The only part of the string that matches will be the seconds in the time.

regsub {:[0-9][0-9] } $mytime { } mytime

5. Can you make the time sound more realistic?

The Tcl manual, available through the Toolkit under WINDOWS, contains more details of regular expressions. Try out as many options as possible.

7. Write a file that defines two procedures:

The procedures should be named getTTS_Time and getTTS_Date, and they should return time and date strings pronounceable by RAD's TTS engine.

The body of your proc definitions could be exactly the regsub and puts lines from above that you have already placed in your program. Just cut and paste them. Or you can be more ambitious and extend the code to cover all the cases: seven days, twelve months, etc. Source your procedure definition file into your program, call the procedures in the appropriate places, then build your program and run it.

 

 

 

Activities: Counts and Votes

(FIGURE – The Nth Caller Program with the Generic object)

In this exercise, you will build a dialogue that tells the caller what call number this is. The first caller hears, "You are caller number one." The ninth caller hears, "You are caller number nine." To keep track of the count we will need to store it in a place where it will not be lost when the program ends -- when the user terminates the RAD session, for example, or presses the Stop button. Values that exist only in your computer's main memory, such as the Tcl variables that we have been using up until now, are lost as soon as the current execution of RAD ends. However, values stored in a file, which resides on a disk rather than in your computer's memory, are not lost when their program ends.

So in this excercise you will:

  • learn basic routines for performing file input and output.
  • store the value of a count in a file so that it persists between calls to your program.

 

Activity 1: "Nth Caller"

Then you will use what you have learned by building a counting program that tallies votes for and against some proposition.

 

1. Start with a clear canvas. Drop in four icons (names do not matter): an <<Start object>>, an <<Action object>>, a <<Generic object>>, and a <<Stop object>> Connect them in the order listed.

2. In the <<Action object>> set the action to:

set numberFile h:/.rad/saved/mynumber

set number 0

This creates a variable numberFile, which holds the complete path name of the file in which the number of previous callers to your program will be held, and of course the number of callers itself is initialized as 0 in the variable number. Note that this sort of initialization could also be done in the Start-of-run action.

3. In the Action 1 box of the <<generic object>> set the action to:

if [file exists $numberFile] {

set fhandle [open $numberFile]

set number [gets $fhandle]

close $fhandle

}

incr number

If the file $numberFile exists, the value of number will be changed to the value currently held in $numberFile, then $numberFile will be closed. If the file $numberFile does not exist, then number will have the value, 0, to which it was initialized in the previous state. In either case, the Tcl command incr will increment the value of number by one.

4. In <<generic object>> set the prompt to:

You are caller number $number.

 

5. In the <<generic object>> type the following entry into the Action 2 dialogue box.

set fhandle [open $numberFile w]

puts $fhandle $number

close $fhandle

Recall that code entered in the Action 2 space of a state is executed after everything else has been done in that state. This code opens the count file with a mode of w (for write). The command puts writes the number to the file. Then we close the file.

6. Build and run the program.

What happens after nine? Does it say "one zero" or does it say "ten"? Consider modifying the dialogue, by adding some states and connections, so that the program flow will loop over and over through the same sequence of states letting you run up the number count as high as you like on any one call. To do this, connect the <<generic icon>> to the <<action icon>> and run the program. To make it stop, click on the Stop button in the upper left corner, or add a YES/NO decision point in the <<generic icon>>.

7. Change the increment value to five.

incr number 5

8. Make the 5th or 8th or 12th caller the winner of a prize and announce it to the lucky caller.

Activity 2: " Vote Tally"

In this task, we want to maintain two numbers; the votes "for" and "against" some proposition. Rather than tell you exactly how to do it, we will tell you what you should do. Develop a dialogue system to do the following.

1. Announce the proposition that is being voted upon. You can make something up or use a current event.

2. Ask whether the caller is in favor or not.

3. Increment the appropriate vote count.

4. Announce the running totals

5. Announce the proportion of the vote that is the same as the current vote. You might use a code fragment like the following.

set percent [expr 100 * $favor / $total]

6. Consider adding other propositions to the program, and then provide the user with statistics only about those propositions in which they indicate an interest.