Tcl is pronounced "tickle" and Tk is pronounced "tee-kay". Tcl/Tk is "tickle tee-kay".
Lens uses Tcl/Tk version 8.3.4. Tcl was written by John Ousterhout and is now owned by Sun, but the wonderful people there allow it to be used freely, even in commercial applications.
To fully understand the Tcl language, you may need to read a book like Tcl and the Tk Toolkit, by John Ousterhout, Addison-Wesley Publishing, 1994. However, this section plus the Tcl/Tk manual pages should help you get by.
As you read through this document, try typing in the examples to the Lens interpreter and see if you get the right results. Probably the best way to figure out a language, though, is to just look at sample code. So once you have a grasp of the basics, you may want to try reading through the scripts in the "Examples/" directory or the file Src/shell.tcl.
A Tcl script consists of a series of commands. Commands are separated
by newlines or semicolons. A command may be continued from one line to
the next by ending the first line with a backslash ("\
").
If the first character of a command is a pound or hash mark
("#
"), the rest of the line will be treated as a comment
and ignored. Note that the hash mark can't be just anywhere on a line,
it must be at the beginning of a command, which is generally the
beginning of the line.
Each command has a return code and a return value. Normally you need not be concerned with the code. It's used for errors, primarily. The return value is the important part. If the command is typed directly into the shell, the return value will be printed out. Otherwise, the return value of a command is used by surrounding the command with square brackets and using this as an argument to another command. In this case, the first command will be evaluated and replaced by its return value. See the next section for more on command substitution.
The words "command" and "procedure" will be used almost interchangeably. A procedure is really just a command that is written in Tcl, while some commands, including most of the Lens commands, are written in C. Procedures are executed by simply following the name of the procedure with its arguments. Parentheses are not used as in C. What we would write in C this way:
f(x, y);We write in Tcl this way:
f $x $y
The arguments to a command are separated by whitespace. An argument may contain whitespace if it is surrounded by quotation marks, curly braces, or square braces. Many commands take a fixed number of arguments but some take a flexible number.
Essentially, everything in Tcl is a string. Variables need not be given
types. Integers and reals will be converted to and from strings when it
is appropriate, but the user need not be concerned with this. The
null string is the universal empty value. It can be written as
""
or {}
. It is not the same as
0
. Think of 0 as the non-null string "0"
.
The previous example called the command f
with the
arguments $x
and $y
. The dollar sign
indicates variable substitution. It means to replace the variable name
x
with the value of variable x
. This is necessary because everything
in Tcl is a string and if we just wrote x
it would be
the string "x"
and not the value of the variable
x
. A variable is assigned a value with the set
command. set
also returns this value, as in the following:
lens> set x 5 5 (this is the return value here) lens> set y "hello there" hello there lens> set z $y$x hello there5
The first command sets x to the string 5. Later we might use $x in some arithmetic, but only then will it matter that x happens to hold an integer. The second command sets y to the string "hello there". Because of the quotes, "hello there" is treated as a single argument. The last command sets z to be the concatenation of the string held by y and the string held by x. Again, this is just one argument.
Now what if we wanted to set z to the value of y immediately followed by 5, but we didn't have 5 stored in a variable? We might try the following:
lens> set z $y5 can't read "y5": no such variable (this is an error)But this will try to dereference the variable "y5", which doesn't exist. We can avoid this problem by surrounding the y with curly braces:
lens> set z ${y}5 hello there5
If a variable has a funny name, such as one containing periods, we may also need to surround the variable name with curly braces when dereferencing it:
lens> set a.b c c lens> return $a.b can't read "a": no such variable lens> return ${a.b} c
Variables need not be declared explicitly. When a variable is first set it will be created automatically.
Along with simple variables, Tcl also supports associative arrays. Associative arrays don't have a finite number of integer indexes, like ordinary arrays, but arbitrary strings as indexes. Array elements are accessed using parentheses rather than square brackets, as in C. This is because square brackets were reserved for command substitution. Observe the following examples:
lens> set mother(Bill) Jane Jane lens> set brother(Jane) Bob Bob lens> set sister(Bob) $mother(Bill) Jane lens> set uncle(Bill) $brother($mother(Bill)) BobTcl does not really support multi-dimensional arrays, but you can pretend to have them by placing a comma or something else between the indexes, as in:
lens> set relationship(Bill,Jane) mother mother
It is often necessary to avoid substitutions or to delay them until later. This can become very complicated and is the weakest aspect of the Tcl language. The usual cases are not too bad, however. The simplest method of avoiding a substitution is using a backslash:
lens> set z $x 5 lens> set z \$x $x lens> set z "\$x is equal to $x" $x is equal to 5
The double quotes in the last command combine the separate words into a single argument, but they allow command substitutions to continue. Curly braces, {}, are similar to double quotes but stronger, in that they prevent command substitutions from going on inside them. However, putting quotes around the curly braces will turn them off:
lens> set z {\$x is equal to $x} \$x is equal to $x lens> set z "{\$x is equal to $x}" {$x is equal to 5}
The choice between double quotes and braces is important when setting variables to commands. Using quotes causes substitution to occur immediately while using braces defers it to later:
lens> set x 5 lens> set y "return $x" return 5 lens> set z {return $x} return $x lens> set x 6 6 lens> $y 5 lens> $z 6 (notice the difference) lens> set y "return \"old x = $x new x = \$x\"" return "old x = 6 new x = $x" lens> set x 7 7 lens> $y old x = 6 new x = 7
It is sometimes necessary to do an extra level of substitution on the arguments of a command, particularly after having avoided some substitutions. This can be done by using the eval command. In the following example, the value of y is the variable name x. We would like to set z to the value of the variable name that is stored in y. To do this in one step we need an extra level of substitutions:
lens> set x 5 5 lens> set y x x lens> set z $$y $x (oops) lens> eval set z $$y 5 lens> return "$x $y $z" 5 x 5
When you have variable names stored in other variables, sometimes it is
easier to use set
to access the value of the variable rather
than $
. Instead of "eval set z $$y
", you could
do:
lens> set z [set $y]
Just as the value of a variable can be substituted for its name, the result of a command can be substituted for the command. This is done by embedding the command in square brackets. For example, expr is a command for evaluating mathematical and logical expressions. It returns the value of the expression. We can store the result of a calculation in a variable as follows:
lens> set x [expr 3 + 4] 7Substitutions will occur within square brackets, just like they do within double quotes:
lens> set z [expr [set y [expr [set x 2] + 1]] * 2] 6 lens> set values "$x $y $z" 2 3 6
It is often necessary to escape command substitutions just as we escape variable substitutions. This can be done by preceding both the opening and closing brackets with a backslash or by enclosing the brackets in curly braces:
lens> set y "return [expr $x + 2]" return 4 lens> set z "return \[expr \$x + 2\]" return [expr $x + 2] lens> set w {return [expr $x + 2]} return [expr $x + 2] (equivalent but cleaner) lens> set x 10 10 lens> return "[$y] [$z] [$w]" 4 12 12
One related thing to note is that file name globbing does not occur
automatically in Tcl. The following does not work because
*.c
does not get expanded:
lens> rm *.c /bin/rm: *.c: No such file or directory child process exited abnormallyglob is a command for doing file name expansion:
lens> glob *.c foo.c bar.c
However, glob
returns all of the filenames as a single
argument so this will not work:
lens> rm [glob *.c] /bin/rm: foo.c bar.c: No such file or directory child process exited abnormallyThat is because "foo.c bar.c" is treated as a single file name. Therefore, an extra evaluation is needed so that the single argument becomes two arguments:
lens> eval rm [glob *.c]
When you combine the complexity of command substitutions, arrays and variables having strange names, variables containing other variable names, and the need to delay or force evaluation, substitutions can become very complex. In fact, there are a few things that are simply impossible given the Tcl syntax. This can sometimes be avoided by dividing a step into several sub-steps and storing the intermediate results. But when it comes down to it, sometimes trial and error is the only way to get a tricky piece of code correct.
lens> set x howdy howdy lens> set x "$x $x" howdy howdy lens> append x " $x" howdy howdy howdy howdyIf you want to get more sophisticated than concatenation, the format command can be used to form strings. It is similar to the C sprintf() function and is able to alter the representation of numeric variables, rounding the decimals or changing base:
lens> set y 3.14159 lens> format "%-10.3f %.0f %x" $y $y 100 3.142 3 64A number of other commands are used to manipulate strings. regexp does regular expression matching given an expression and a string. regsub does regular expression matching and substitution. scan parses a string in a similar way to
sscanf
. Finally, the string
command does a number of other operations on strings, such as returning a
string's length, comparing strings, doing substring lookup, and trimming
the head and tail of a string.
The puts
command prints strings to standard output (or to another channel). It is
like the echo
used in other shell languages. The string
given to puts
must be in a single argument, as follows:
lens> puts "$x. Look, I'm a cowboy!" howdy howdy howdy howdy. Look, I'm a cowboy!Note, that printing a string is very different from returning a string:
lens> set x [expr 3 + 4] 7 lens> set x 7 lens> set x [puts 7] 7 lens> set x (there is no value here because puts didn't return anything)
Lists are used to store collections of things. Lists are strings with a special structure. The simplest list is just a string with several words separated by spaces. Each word is an element in a list. We can make multiple word elements by enclosing the elements in braces or double quotes. The lindex command returns a selected element of a list. Observe the following examples:
lens> lindex {Snoopy Linus Sally "the girl with red hair"} 1 Linus lens> set x {Snoopy Linus Sally "the girl with red hair"} Snoopy Linus Sally "the girl with red hair" lens> lindex $x 3 the girl with red hair lens> lindex [lindex $x 3] 4 hair
There are a number of commands for handling lists. llength returns the length of a list. concat concatenates multiple lists. Note that this is can also be done using ordinary string concatenation. lappend adds new elements to the end of a list. linsert adds new elements to the middle of a list. lrange returns part of a list. split breaks a string into a list at specified characters. And there are a few more list commands described in the Tcl/Tk manual.
Expressions combine numeric or logical values to produce new values. Expressions are ordinarily evaluated using the expr command. Expressions are also evaluated automatically when they are in the "test" argument of if statements and other control structures.
Most C expressions are valid in Tcl. However, you must remember to use a dollar sign to perform variable substitution on each occurrence of a variable. All of the standard math library functions are available in Tcl expressions. Note that in Tcl these are not procedures as they are in C. Using these mathematical functions is the only case where C-like function syntax, f(), is used, rather than Tcl command syntax. Here are some expressions:
lens> set x 0.5 0.5 lens> set y 2 2 lens> expr ($x + $y) / $y 1.25 lens> expr pow(35 % 4, $y) 9.0 lens> expr 2 | 1 3 (bitwise operations!) lens> expr (3.14 > 3) && (round(3.14) == 3) 1 (logic!) lens> expr {[format "%d %d" 3 4] == "3 4"} 1 (easy string comparisons!)
Note that in the last example curly braces were used around the
expression. This is necessary to prevent an extra level of substitution
to occur before the expression itself is evaluated. Leaving them out
would have caused problems because the expression would have read
3 4 == "3 4"
rather than "3 4" == "3 4"
. This
is a bit tricky, but you will almost always be fine if you surround all
expressions with curly braces.
Logical true is 1 and logical false is 0. Note that the empty string is not logical false. To test if a string is empty, you should do:
lens> if {$s == {}} {...}
When performing division and possibly other procedures, Tcl will attempt
to detect whether the numerator is an integer or a real number. If the
numerator is an integer, integer division will be performed and the
result truncated. To prevent this, constants that should be treated as
real numbers should always be given at least one decimal, even if it is
zero. Furthermore, double()
should be used on variables to
ensure that they are not treated as integers. For example:
lens> expr 8 / 5 1 lens> expr 8.0 / 5 1.6 lens> set x 8 8 lens> expr $x / 5 1 lens> expr double($x) / 5 1.6
Control structures are implemented in Tcl like normal commands. There
is an if
statement, which uses the special arguments else
and
elseif
for alternatives. Note that there can't be a space
in elseif
:
if {$x < 0} {set x 0} if {$y == "hello"} { puts $y } if {(sin($x) < 0.5) || $x < 0} \ {puts hi} if {$x == $y} { puts y } elseif {$x == $z} { puts z } else {puts huh?}There is a for loop:
for {set i 0} {$i < 10} {incr i} { puts $i }and a while loop:
while {$i > 0} { puts [incr i -1] }The foreach command iterates over elements of a list:
foreach i {a b c d e} { puts $i }
break and continue can be used in loops in the same way they are used in C.
Finally, the switch
statement is like the C version but it takes a single argument
specifying the value and then a list of even length. The even elements
of the list are case values and the odd elements are commands that are
executed if the preceding case is met. Breaks are implicit following
any match, unlike in C. The special case default matches
everything. The special command - causes
switch
to fall through to the next command, avoiding the
break
. Here are some examples:
switch $x "a {puts hi} b {puts bye} $y {puts arghhhh}" switch [expr $x + 3] { 2 - 3 {puts "2 or 3"} 4 {puts 4!!!} default {puts "What you talkin' 'bout, Willis?"} }
If you want to get fancy, it is possible to make switch
do
regular expression matching rather than simple string matching.
The proc
command is used to define new procedures. It takes three arguments: the
name of the new procedure, a list of arguments, and the body of the
command. The list of arguments can be the name of a single argument,
foo
, or a list of arguments, {foo bar}
. An
argument can either be a single name or a pair consisting of a name and
a default value. If a default value is given, the argument is optional.
All optional arguments must be at the end of the argument list.
The return value of a procedure is the value of the last command executed. The return and error commands can be used to return normally or abnormally at any point in the execution of the procedure.
Here is a command that returns the natural log of a value if given one
argument or the log to a particular base if given two arguments. Note
that the default value for base
is the null string, but
there is no default value for x
so it must be specified:
proc superLog {x {base {}}} { if {$base == {}} { return [expr log($x)] } else { return [expr log($x) / log($base)] } } lens> superLog 100 4.60517018599 lens> superLog 100 10 2.0The scope of variables in procedures does not automatically extend to the global environment. Variables are by default local. The global command, when used inside a procedure, instructs the interpreter that one or more variables are to be global:
proc area radius { global PI expr $radius * $radius * $PI } lens> set PI 3.14159 3.14159 lens> area [expr sqrt(2)] 6.28.38
Procedures can deleted by renaming them to the empty string, as in:
lens> rename myproc {}
As mentioned before, commands return both a code and a value.
Ordinarily the code is 0 and is ignored. If an error occurs in the
command (or if the error
command is called inside a
procedure) it will return with the code 1. The interpreter will
ordinarily catch this error and propagate it up through the call stack
until the top level is reached. Then the value associated with the
error is reported to the user.
However, the catch
command can be used to intercept errors and to deal with them in a less
destructive way. For example, if you wanted to make sure that the
variable x
does not exist, you would use the command
unset x
. However, this will result in an error if
x
didn't already exist. This error would terminate any
scripts invoking this command. Therefore, it is good practice to
protect such commands as follows:
lens> catch {unset x}
catch
will return the code from the unset
command as its value. If x
existed it would return 0 and if
x
didn't exist, it would return 1.
The return
, break
, and continue
commands give the return codes 2, 3, and 4, respectively. These are
each handled differently by the interpreter. The return
command is also able to return with a particular code.
The one potentially useful variant is "return -code return
<string>
". If invoked anywhere inside a Tcl script, even
inside of a procedure called by the script, this will cause the script
to exit. This is somewhat like the quit
command in some
scripting languages.
Subprocesses may be started in one of several ways. If a command name is used that doesn't match a valid Tcl command, the interpreter will see if the name matches an executable program. If so, the program will be invoked with the specified arguments.
The normal way to run subprocesses, however, is with the exec
command. exec
supports input and output redirection as
well as pipes. The usage is best understood by reading the official
manual page. One thing of interest is that a single <
will make the command read from a file while a double
<<
will make the command read from a variable. The
exec
command will only return when the subprocesses
terminate, unless the last argument is &
.
Finally, open may be used to create perpetual pipelines that can be written to or read from and must be terminated with the close command.