There’s no getting around it. If you intend to use IBM’s Full System Simulator (SystemSim) for nontrivial simulation, you need to know Tcl, the Tool Command Language. In the past, Tcl made me very uncomfortable: It’s wordy like a regular high-level language, but the words have no structure—no commas, parentheses, semicolons, or equal signs. But as I learned Tcl, I realized that its bothersome aspects were really strengths. I’ve since developed a great deal of respect for the language.
This appendix isn’t going to present Tcl in full. The goal is to explain enough of the topic so that you can read and write scripts that take full advantage of SystemSim’s capabilities. Throughout this treatment, I’ll do my best to compare Tcl to the C language that we’re familiar with. One application is based on the Sieve of Eratosthenes code from Chapter 4, “Debugging and Simulating Applications.” But first, let’s start with a simple Hello World! script.
The AppendixE folder has no subfolders, just two files: square.tcl and sieve.tcl. Change to this directory and execute the following command:
tclsh square.tcl
This script computes five squared and returns Result is 25!
. Listing E.1 presents the content of square.tcl.
This script might not look impressive, but it provides a wealth of information about Tcl:
set
assigns values to variables, expr
receives expressions and returns a value, and puts
prints strings to the console.
Simple syntax. Commands don’t have to be followed by semicolons, arguments aren’t enclosed in parentheses or separated by commas, no equals sign in assignment statements.
Variables are untyped, variable names can start with a number, and string constants are enclosed in double quotes.
A variable’s value is referenced by preceding the variable name with $
, and variables can be referenced directly inside a string.
Tcl comments start with #
.
The result of a command (for example, expr 5*5
) can be accessed like a variable if surrounded by square brackets.
No main
function or any specifically marked starting point; Tcl commands are interpreted one command after the next.
This last point is important. Every line in a Tcl script is a command followed by zero or more space-separated arguments. Nearly every command returns a value, and all values are processed as strings. For example, expr
doesn’t know or care that $num
holds a numeric value; all it knows is that 5 * 5
is a string that can be evaluated. It performs the evaluation and returns the result as a new string: 25
.
If the code in Listing E.1 makes sense, the rest of this discussion won’t provide any difficulty. The next sections present higher-level constructs in Tcl: conditional statements, lists, loops, and procedures.
Tcl control structures and aggregates operate like those in C/C++, but the syntax differs in many respects. This section presents Tcl conditional statements, lists, arrays, and loops. Throughout the discussion, keep in mind that every Tcl statement is a command and that Tcl syntax relies on curly braces rather than parentheses.
Tcl checks for conditions in much the same way that C does. The word if
is followed by an expression and statements to be executed if the expression is valid. The optional elseif
and else
are followed by statements to be executed if the expression is invalid.
An expression is valid if it produces a nonzero number or a string equaling true
or yes
as opposed to false
or no
. These expressions rely on ==
to compare for equality and other operators include !=
, >
, <
, >=
, and <=
. The following code presents a simple example:
set hour_of_day 1 if [expr $hour_of_day > 12] { puts "Evening" } elseif [expr $hour_of_day < 12] { puts "Morning" } else { puts "Noon" }
It’s important to see that elseif
and else
are not placed on separate lines; doing so will cause an error. This is because elseif
and else
are not separate commands. They are part of the overall if
command.
Like a C array, a Tcl list is a single structure containing multiple values. A Tcl list is created by the list
command followed by space-separated elements. List elements can be of any type, including other lists. For example, the following statement assigns to example_list
a list comprising four elements. The last element is a second list:
set example_list [list "Hi there!" abc $var [list 1 2 3]]
List elements are accessed with the lindex
command. For example,
puts [lindex $example_list 1]
prints abc
, the element of example_list
at index 1. Table E.1 presents lindex
and other Tcl list commands.
Table E.1. Basic Tcl List Commands
Command | Description |
---|---|
| Places element e1 at the end of |
| Returns the element of lname with index |
| Returns a list whose elements are those of |
| Creates a list from elements |
| Returns the number of elements in |
| Returns a list whose elements are those of |
| Returns a list of elements in |
| Replaces the element at index |
The following script uses a number of these commands to manipulate a list of integers:
set num_list [list 0 1 2 3 4] # Append {5 6 7} to the end lappend num_list 5 6 7 # Replace the first four values with {7 6 5 4} set num_list [lreplace $num_list 0 3 7 6 5 4] # Insert element {3} into the middle of the list set num_list [linsert $num_list [expr [llength $num_list]/2] 3] # Create a list from the middle elements set new_list [lrange $num_list 2 6]
The commands lreplace
and linsert
don’t change the input list. They return a second list whose values are those of the input list after modification. Also, lreplace
, linsert
, and lrange
operate on the values of the list ($num_list
), whereas lappend
operates on the list itself.
Arrays are particularly important because SystemSim only allows access to SPU statistics through Tcl arrays. A Tcl array is similar to a list, but each element of the array is a match between a string name and a string value. Arrays in Tcl serve the same role as hash maps in other languages.
Tcl arrays aren’t allocated in advance, but are populated as new elements are added. For example, an array arr
can be set to contain three elements with the following code:
set arr(first) = "First value" set arr(2) = "Second value" set arr("Third value") = "Third value"
Array indices are always strings, and array elements are referenced by enclosing the indices in parentheses. In this example, $arr(2)
will be replaced with "Second value"
whenever it appears in code. Table E.2 lists a number of Tcl commands that operate on arrays. In each case, aname
is the name of an array.
Name/value pairs are processed as two-element lists. array set
requires that the input list contain an even number of elements.
The following code shows how these commands are used in practice:
# Create and initialize array array set nums {first 1 second 2 third 3} # Access an element of the array (Result: 2) puts $nums(second) # Get size of array (Result: 3) puts [array size nums] # Get names in array (Result: second first third) puts [array names nums]
The array names
command doesn’t return the array names in order. This is because Tcl arrays are based on hash tables, and the name of the string has no relation to where it’s stored.
The for
and foreach
commands both create loop structures. A for
loop iterates as long as an expression remains valid, and foreach
iterates across the elements in one or more Tcl lists.
The syntax of the for
statement is given by the following:
for start test next body
The Tcl interpreter executes the start
command once, and then checks the test
expression. If the expression is valid, it performs the commands in body
and executes the next
command. The interpreter continues this process (check test
, run body
, run next)
until test
is invalid.
The for
command is commonly used to iterate a specific number of times. In this usage, the start
command assigns a number to a counter, the test
expression compares the counter to the final value, and the next
command increments or decrements the counter. This is shown in the following example:
for {set i 0} {$i < 10} {incr i} { puts "Count = $i" }
This example prints out the integers from 0 to 10. The command {incr i}
is short for the following:
{set i [expr $i+1]}
The foreach
command is simpler than the for
command, and iterates over the elements in one or more lists. The basic foreach
statement is given by this:
foreach var list body
When the command starts, the interpreter sets var
equal to the first element of list
and then processes the body
commands. Then it sets var
equal to each succeeding element in list
, and processes body
after each assignment. The following code iterates through each element in color_list
:
set color_list [list red orange yellow green] foreach color $color_list { puts "The current color is $color" }
When a Tcl script is invoked with command-line parameters, the parameters are placed in a list called argv
. The number of parameters is placed in a variable called argc
. The following code iterates through each of the parameters to see whether the script was called with the -v
option.
foreach param $argv { if {$param == "-v"} { puts "The -v option was used." } }
If the interpreter encounters continue
in a for
or foreach
construct, it skips the rest of the body
commands and proceeds to the next iteration. If it encounters a break
command, the interpreter stops processing the for
/foreach
command and interprets the next statement in the script.
Every high-level language provides a way to encapsulate functionality within subroutines, and Tcl is no exception. Tcl procedures are defined with the proc
command and each procedure must have a unique name. This name can be used outside the procedure like a regular Tcl command.
A basic procedure declaration consists of proc
, the procedure name, and zero or more arguments in braces. For example, the following declaration states that example_proc
accepts three parameters that will be accessed as first
, second
, and third
:
proc example_proc {first second third}
These parameters are untyped, so a call to this procedure might look like this:
example_proc xyz "10 9 8" forty-two
It’s up to the procedure to make sure the parameter values make sense.
A function can accept a variable number of arguments by using a special argument: args
. This serves as a list containing all the procedure’s parameters. For example, the following procedure prints an error message if it wasn’t called with two arguments:
proc two_arg {args} {
if {[llength $args] != 2} {
puts "This procedure must have two arguments."
return "failed"
}
puts "The procedure was called correctly"
}
The procedure’s return
statement is bolded to emphasize its importance. A procedure doesn’t have to have a return
, and if doesn’t, it will return the result of the last executed command. But in the preceding example, the procedure returns failed
in the event of a miscall. Then it finishes immediately. If two_arg
is invoked with a command like
set ret [two_arg a b c]
ret
will be set to failed
because two_arg
was called with three arguments. The final puts
statement won’t be executed.
Just as in C functions, variables declared within a procedure are local to that procedure. A global variable can be accessed if two conditions are met:
The variable is created outside the procedure.
The variable inside the procedure is declared with the global
keyword.
In Listing E.2, each procedure calls global num
to access the size of the list to be analyzed. This script finds all the prime numbers between two and num
and prints them to the screen. This uses the same Sieve of Eratosthenes algorithm as the C code in Chapter 4 but divides the process into three procedures.
Example E.2. Advanced Tcl: sieve.tcl
# Initialize the primes array proc init {} { global num for {set i 1} {$i <= $num+1} {incr i} { lappend primes 0 } sieve $primes } # Set composite numbers equal to 1 proc sieve {primes} { global num for {set i 2} {[expr $i * $i] <= $num} {incr i} { if {[lindex $primes $i] == 0} { for {set j [expr $i * $i]} {$j <= $num} {incr j $i} { set primes [lset primes $j 1] } } } disp $primes } # Print all prime numbers between 2 and num proc disp {primes} { global num for {set i 2} {$i <= $num} {incr i} { if {[lindex $primes $i] != 1} { puts $i } } } # Start the main processing set num 250 init
The second-to-last line creates the global variable for the procedures, and the last line starts the computation by calling init
. init
creates the primes
list and passes it to sieve
. sieve
determines which values are composite and passes the list to disp
. disp
prints out the prime values. Each procedure call passes parameters by value, which is how Tcl regularly passes parameters.
Tcl is the interface language for IBM’s Full System Simulator, so if you intend to perform simulations involving triggers or emitters, it’s the language to know. But aside from its importance, Tcl is fascinating in its own right.
This chapter began with a brief Tcl script and used it to derive conclusions about the language. Each Tcl statement is a command followed by arguments. Variable values are assigned with set
and are referenced by preceding the variable name with $
. expr
evaluates expressions, puts
prints strings, and comments are preceded with #
.
From there, the chapter presented Tcl conditional statements, lists, loops, and procedures. The if
statement in Tcl is similar to that in C, and the Tcl list bears a slight resemblance to a C array. However, a Tcl list can hold any type of data and can be dynamically increased and reduced in size. A Tcl procedure serves the same purpose as a C function, but global variables have to be specifically identified and a procedure can return any type of value.
A brief treatment such as this can’t do justice to the Tcl language, but it’s a fine starting place for further investigation. In particular, the Tcl/Tk (Tcl toolkit) extension makes it possible to build powerful graphical user interfaces that run on many operating systems. Tcl also has extensions for sockets, databases, and object-oriented development.
13.58.212.170