This chapter describes commands that give you a view into the interpreter. The history
command and a simple debugger are useful during development and debugging. The info
command provides a variety of information about the internal state of the Tcl interpreter. The time
command measures the time it takes to execute a command. Tcl commands discussed are: clock
, info
, history
, and time
.
Reflection provides feedback to a script about the internal state of the interpreter. This is useful in a variety of cases, from testing to see whether a variable exists to dumping the state of the interpreter. The info
command provides lots of different information about the interpreter.
The clock
command returns the time, formats time values, does time calculations, and parses time strings. It is a great tool all by itself. It also provides high-resolution timer information for precise measurements.
Interactive command history is the third topic of the chapter. The history facility can save you some typing if you spend a lot of time entering commands interactively.
Debugging is the last topic. The old-fashioned approach of adding puts
commands to your code is often quite useful. For tough problems, however, a real debugger is invaluable. The Tcl Dev Kit toolset from ActiveState include a high quality debugger and static code checker. The tkinspect program is an inspector that lets you look into the state of a Tk application. It can hook up to any Tk application dynamically, so it proves quite useful.
The clock
command has facilities for getting the current time, formatting time values, and scanning printed time strings to get an integer time value. Table 13-1 summarizes the clock
command:
Table 13-1. The clock
command
A high resolution counter. The precision is milliseconds, if specified (Tcl 8.4), or a system-dependent value. | |
Formats a clock value according to | |
Parses date | |
Returns the current time in seconds. |
The following command prints the current time:
clock format [clock seconds]
=> Fri Nov 22 4:09:14 PM PST 2002
The clock seconds
command returns the current time, in seconds since a starting epoch. The clock format
command formats an integer value into a date string. It takes an optional argument that controls the format. The format strings contains % keywords that are replaced with the year, month, day, date, hours, minutes, and seconds, in various formats. The default string is:
%a %b %d %H:%M:%S %Z %Y
Tables 13-2 summarizes the clock
formatting strings:
Table 13-2. clock format
keywords
Inserts a | |
| Abbreviated weekday name (Mon, Tue, etc.). |
Full weekday name (Monday, Tuesday, etc.). | |
| Abbreviated month name (Jan, Feb, etc.). |
| Full month name. |
| Locale specific date and time (e.g., |
| First two digits of the four-digit year (19 or 20). |
| Day of month (01 ā 31). |
| Date as |
| Day of month (1 ā 31), no leading zeros. |
| Abbreviated month name. |
| Hour in 24-hour format (00 ā 23). |
| Hour in 12-hour format (01 ā 12). |
| Day of year (001 ā 366). |
| Hour in 24-hour format, without leading zeros (0 - 23). |
| Hour in 12-hour format, without leading zeros (1 ā 12). |
| Month number (01 ā 12). |
| Minute (00 ā 59). |
| Inserts a newline. |
| AM/PM indicator. |
| Time as |
| Time as |
| Seconds since the epoch. |
| Seconds (00 ā 59). |
| Inserts a tab. |
| Time as |
| Weekday number (Monday = 1, Sunday = 7). |
| Week of year (00 ā 52) when Sunday starts the week. |
| Week of year according to ISO-8601 rules (Week 1 contains January 4). |
| Weekday number (Sunday = 0). |
| Week of year (00 ā 52) when Monday starts the week. |
| Locale specific date format (e.g., Feb 19 1997). |
| Locale specific time format (e.g., 20:10:13). |
| Year without century (00 ā 99). |
| Year with century (e.g. 1997). |
| Time zone name. |
The clock clicks
command returns the value of the system's highest resolution clock. The units of the clicks is milliseconds if -milliseconds
is specified, otherwise it is undefined. The main use of this command is to measure the relative time of different performance tuning trials. The -milliseconds
flag was added in Tcl 8.4. Example 13-1 shows how to calibrate the clicks value by counting the clicks per second over 10 seconds, which will vary from system to system:
Example 13-1. Calculating clicks per second
set t1 [clock clicks]
after 10000 ;# See page 228
set t2 [clock clicks]
puts "[expr ($t2 - $t1)/10] Clicks/second"
=> 1001313 Clicks/second
The clock scan
command parses a date string and returns a seconds value. The command handles a variety of date formats. If you leave off the year, the current year is assumed.
Tcl implements the standard interpretation of two-digit year values, which is that 70ā99 are 1970ā1999, 00ā69 are 2000ā2069. Versions of Tcl before 8.0 did not properly deal with two-digit years in all cases. Note, however, that Tcl is limited by your system's time epoch and the number of bits in an integer. On Windows, Macintosh, and most UNIX systems, the clock epoch is January 1, 1970. A 32-bit integer can count enough seconds to reach forward into the year 2037, and backward to the year 1903. If you try to clock scan
a date outside that range, Tcl will raise an error because the seconds counter will overflow or underflow. In this case, Tcl is just reflecting limitations of the underlying system. Some 64-bit systems (such as Solaris 8 64-bit) use 64-bit integers for the system clock, which Tcl 8.4 supports. This extends the recognized range into the billions of years.
If you leave out a date, clock scan
assumes the current date. You can also use the -base
option to specify a date. The following example uses the current time as the base, which is redundant:
clock scan "10:30:44 PM" -base [clock seconds]
=> 2931690644
The date parser allows these modifiers: year
, month
, fortnight
(two weeks), week
, day
, hour
, minute
, second
. You can put a positive or negative number in front of a modifier as a multiplier. For example:
clock format [clock scan "10:30:44 PM 1 week"] => Fri Nov 29 10:30:44 PM PST 2002 clock format [clock scan "10:30:44 PM -1 week"] Fri Nov 15 10:30:44 PM PST 2002
You can also use tomorrow
, yesterday
, today
, now
, last
, this
, next
, and ago
, as modifiers.
clock format [clock scan "3 years ago"]
=> Mon Nov 22 4:18:34 PM PST 1999
Both clock format
and clock scan
take a -gmt
option that uses Greenwich Mean Time. Otherwise, the local time zone is used.
clock format [clock seconds] -gmt true => Sat Nov 23 12:19:13 AM GMT 2002 clock format [clock seconds] -gmt false => Fri Nov 22 4:19:35 PM PST 2002
Table 13-3 summarizes the info
command. The operations are described in more detail later.
Table 13-3. The info
command
| A list of |
| The commands in the body of |
| The number of commands executed so far. |
| A list of all commands, or those matching |
| True if |
| True if |
| True if |
| A list of all math functions, or those matching |
| A list of all global variables, or those matching |
| The name of the machine. This may be the empty string if networking is not initialized. |
| The stack level of the current procedure, or 0 for the global scope. |
| A list of the command and its arguments at the specified level of the stack. |
| The pathname of the Tcl library directory. |
| A list of the libraries loaded into the interpreter named |
| A list of all local variables, or those matching |
| The file name of the program (e.g., of tclsh or wish). |
| The release patch level for Tcl. |
| A list of all Tcl procedures, or those that match |
| The name of the file being processed, or the empty string. |
| The file name suffix of shared libraries. |
| The version number of Tcl. |
| A list of all visible variables, or those matching |
There are three categories of variables: local, global, and visible. Information about these categories is returned by the locals
, globals
, and vars
operations, respectively. The local variables include procedure arguments as well as locally defined variables. The global variables include all variables defined at the global scope. The visible variables include locals, plus any variables made visible via global
or upvar
commands. A pattern can be specified to limit the returned list of variables to those that match the pattern. The pattern is interpreted according to the rules of string match
, which is described on page 53:
info globals auto*
=> auto_index auto_noexec auto_path
Namespaces, which are the topic of the next chapter, partition global variables into different scopes. You query the variables visible in a namespace with:
info vars namespace::*
Remember that a variable may not be defined yet even though a global
or upvar
command has declared it visible in the current scope. Use the info exists
command to test whether a variable or an array element is defined or not. An example is shown on page 96.
You can find out everything about a Tcl procedure with the args
, body
, and default
operations. This is illustrated in the following Proc_Show
example. The puts
commands use the -nonewline
flag because the newlines in the procedure body, if any, are retained:
Example 13-2. Printing a procedure definition
proc Proc_Show {{namepat *} {file stdout}} { foreach proc [info procs $namepat] { set space "" puts -nonewline $file "proc $proc {" foreach arg [info args $proc] { if [info default $proc $arg value] { puts -nonewline $file "$space{$arg $value}" } else { puts -nonewline $file $space$arg } set space " " } # Double quotes allow substitution # of [info body $proc] puts $file "} {[info body $proc]}" } }
Example 13-3 is a more elaborate example of procedure introspection that comes from the direct.tcl
file, which is part of the Tcl Web Server described in Chapter 18. This code is used to map URL requests and the associated query data directly into Tcl procedure calls. This is discussed in more detail on page 262. The Web server collects Web form data into an array called form
. Example 13-3 matches up elements of the form
array with procedure arguments, and it collects extra elements into an args
parameter. If a form value is missing, then the default argument value or the empty string is used:
Example 13-3. Mapping form data onto procedure arguments
# cmd is the name of the procedure to invoke # form is an array containing form values set cmdOrig $cmd set params [info args $cmdOrig] # Match elements of the form array to parameters foreach arg $params { if {![info exists form($arg)]} { if {[info default $cmdOrig $arg value]} { lappend cmd $value } elseif {[string equal $arg "args"]} { set needargs yes } else { lappend cmd {} } } else { lappend cmd $form($arg) } } # If args is a parameter, then append the form data # that does not match other parameters as extra parameters if {[info exists needargs]} { foreach {name value} [array get form] { if {[lsearch $params $name] < 0} { lappend cmd $name $value } } } # Eval the command set code [catch $cmd result]
The info commands
operation returns a list of all commands, which includes both built-in commands defined in C and Tcl procedures. There is no operation that just returns the list of built-in commands. Example 13-4 finds the built-in commands by removing all the procedures from the list of commands.
Example 13-4. Finding built-in commands
proc Command_Info {{pattern *}} { # Create a table of procedures for quick lookup foreach p [info procs $pattern] { set isproc($p) 1 } # Look for command not in the procedure table set result {} foreach c [info commands $pattern] { if {![info exists isproc($c)]} { lappend result $c } } return [lsort $result] }
The info level
operation returns information about the Tcl evaluation stack, or call stack. The global level is numbered zero. A procedure called from the global level is at level one in the call stack. A procedure it calls is at level two, and so on. The info level
command returns the current level number of the stack if no level number is specified.
If a positive level number is specified (e.g., info level 3
), then the command returns the procedure name and argument values at that level in the call stack. If a negative level is specified, then it is relative to the current call stack. Relative level -1 is the level of the current procedure's caller, and relative level 0 is the current procedure. The following example prints the call stack. The Call_trace
procedure avoids printing information about itself by starting at one less than the current call stack level:
If you want to know how many Tcl commands are executed, use the info cmdcount
command. This counts all commands, not just top-level commands. The counter is never reset, so you need to sample it before and after a test run if you want to know how many commands are executed during a test.
Command tracing provides detailed information about the execution of commands. It is described along with variable tracing on page 193.
The info complete
operation figures out whether a string is a complete Tcl command. This is useful for command interpreters that need to wait until the user has typed in a complete Tcl command before passing it to eval
. Example 13-6 defines Command_Process
that gets a line of input and builds up a command. When the command is complete, the command is executed at the global scope. Command_Process
takes two callbacks as arguments. The inCmd
is evaluated to get the line of input, and the outCmd
is evaluated to display the results. Chapter 10 describes callbacks why the curly braces are used with eval
as they are in this example:
Example 13-6. A procedure to read and evaluate commands
proc Command_Process {inCmd outCmd} { global command append command(line) [eval $inCmd] if {[info complete $command(line)]} { set code [catch {uplevel #0 $command(line)} result] eval $outCmd {$result $code} set command(line) {} } } proc Command_Read {{in stdin}} { if {[eof $in]} { if {$in != "stdin"} { close $in } return {} } return [gets $in] } proc Command_Display {file result code} { puts stdout $result } while {![eof stdin]} { Command_Process {Command_Read stdin} {Command_Display stdout} }
The name of the current script file is returned with the info script
command. For example, if you use the source
command to read commands from a file, then info script
returns the name of that file if it is called during execution of the commands in that script. This is true even if the info script
command is called from a procedure that is not defined in the script.
I often use info script
to source or process files stored in the same directory as the script that is running. A few examples are shown in Example 13-7.
Example 13-7. Using info script
to find related files
# Get the directory containing the current script. set dir [file dirname [info script]] # Source a file in the same directory source [file join $dir helper.tcl] # Add an adjacent script library directory to auto_path # The use of ../lib with file join is cross-platform safe. lappend auto_path [file join $dir ../lib]
The pathname of the Tcl library is stored in the tcl_library
variable, and it is also returned by the info library
command. While you could put scripts into this directory, it might be better to have a separate directory and use the script library facility described in Chapter 12. This makes it easier to deal with new releases of Tcl and to package up your code if you want other sites to use it.
Each Tcl release has a version number such as 7.4 or 8.0. This number is returned by the info tclversion
command. If you want your script to run on a variety of Tcl releases, you may need to test the version number and take different actions in the case of incompatibilities between releases.
The Tcl release cycle starts with one or two alpha and beta releases before the final release, and there may even be a patch release after that. The info patchlevel
command returns a qualified version number, like 8.0b1 for the first beta release of 8.0. We switched from using āpā (e.g., 8.0p2) to a three-level scheme (e.g., 8.0.3) for patch releases. The patch level is zero for the final release (e.g., 8.2.0). In general, you should be prepared for feature changes during the beta cycle, but there should only be bug fixes in the patch releases. Another rule of thumb is that the Tcl script interface remains quite compatible between releases; feature additions are upward compatible.
The file name of the program being executed is returned with info nameofexecutable
. This is more precise than the name in the argv0
variable, which could be a relative name or a name found in a command directory on your command search path. It is still possible for info nameofexecutable
to return a relative pathname if the user runs your program as ./foo
, for example. The following construct always returns the absolute pathname of the current program. If info nameofexecutable
returns an absolute pathname, then the value of the current directory is ignored. The pwd
command is described on page 122:
file join [pwd] [info nameofexecutable]
A few operations support dynamic loading of shared libraries, which are described in Chapter 47. The info sharedlibextension
returns the file name suffix of dynamic link libraries. The info loaded
command returns a list of libraries that have been loaded into an interpreter. Multiple interpreters are described in Chapter 19.
Tcl is designed so that you can write scripts that run unchanged on UNIX, Macintosh, and Windows platforms. In practice, you may need a small amount of code that is specific to a particular platform. You can find out information about the platform via the tcl_platform
variable. This is an array with these elements defined:
tcl_platform(platform)
is one of unix
, macintosh
, or windows
.
tcl_platform(os)
identifies the operating system. Examples include MacOS
, Solaris
, Linux
, Win32s
(Windows 3.1 with the Win32 subsystem), Windows 95
, Windows NT
, and SunOS
.
tcl_platform(osVersion)
gives the version number of the operating system.
tcl_platform(machine)
identifies the hardware. Examples include ppc
(Power PC), 68k
(68000 family), sparc
, intel
, mips
, and alpha
.
tcl_platform(byteOrder)
identifies the byte order of this machine and is one of littleEndian
or bigEndian
.
tcl_platform(wordSize)
identifies the size of the native machine word in bytes. This was introduced in Tcl 8.4.
tcl_platform(isWrapped)
indicates that the application has been wrapped up into a single executable with TclPro Wrapper. This is not defined in normal circumstances.
tcl_platform(user)
gives the login name of the current user.
tcl_platform(debug)
indicates that Tcl was compiled with debugging symbols.
tcl_platform(threaded)
indicates that Tcl was compiled with thread support enabled.
On some platforms a hostname is defined. If available, it is returned with the info hostname
command. This command may return an empty string.
One of the most significant areas affected by cross-platform portability is the file system and the way files are named. This topic is discussed on page 110.
The trace
command registers a command to be called whenever a variable is accessed, modified, or unset. Tcl 8.4 introduced an updated trace
command which includes support for command tracing. The original (and still supported) form of the command applies only to variable traces:
trace variable name ops command trace vdelete name ops command trace vinfo name
The name
is a Tcl variable name, which can be a simple variable, an array, or an array element. If a whole array is traced, the trace is invoked when any element is used according to ops
. The ops
argument is one or more of the letters r
, for read traces, w
, for write traces, u
, for unset traces, and a
for array traces. The command
is executed when one of these events occurs. It is invoked as:
command name1 name2 op
The name1
argument is the variable or array name. The name2
argument is the name of the array index, or null if the trace is on a simple variable. If there is an unset trace on an entire array and the array is unset, name2
is also null. The value of the variable is not passed to the procedure. The traced variable is one level up the Tcl call stack. The upvar
, uplevel
, or global
commands need to be used to make the variable visible in the scope of command
. These commands are described in more detail in Chapter 7.
A read trace is invoked before the value of the variable is returned, so if it changes the variable itself, the new value is returned. A write trace is called after the variable is modified. The unset trace is called after the variable is unset. The array trace, which was added in Tcl 8.4, is called before the array
command (e.g., array names
) is used on the variable. A variable trace is automatically deleted when the variable is unset.
The new form of trace
supports both variable and command tracing:
trace add type name ops command trace remove type name ops command trace info type name
The type
is one of command
, execution
or variable
. For command
, ops
is a list and may contain rename
, to trace the renaming of a Tcl command, or delete
, to trace the deletion of a command. Command tracing cannot be used to prevent the actual deletion of a command, it just receives the notification. No command traces are triggered when an interpreter is deleted. The command
is invoked as:
command oldName newName op
For execution
, the ops
may be any of enter
, leave
, enterstep
, and leavestep
. enter
invokes command
immediately before the command name
is executed, and leave
will invoke command immediately following each execution. enterstep
and leavestep
are similar but they operate on the Tcl procedure name
, invoking command
for each Tcl command inside the procedure. In order to do this, they prevent the bytecode compilation of that procedure. This allows you to create a simple debugger in pure Tcl. The enter
and enterstep
operations invoke command
as:
command command-string op
The leave
and leavestep
operations invoke command
as:
command command-string code result op
The command-string
is the current command being executed, code
is the result code of the execution and result
is the result string. Example 6-16 on page 84 illustrates the different result codes.
For variable
tracing, the ops
may be one or more of read
, write
, unset
, or array
. This is an alternate way to set up the variable traces described earlier.
Example 13-8 uses traces to implement a read-only variable. A variable is modified before the trace procedure is called, so the ReadOnly
variable is needed to preserve the original value. When a variable is unset, the traces are automatically removed, so the unset trace action reestablishes the trace explicitly. Note that the upvar
alias (e.g., var
) cannot be used to set up the trace. Instead, uplevel
is used to create the trace in the original context of the variable. In general, essentially all traces are on global or namespace variables.
Example 13-8. Tracing variables
proc ReadOnlyVar {varName} { upvar 1 $varName var global ReadOnly set ReadOnly($varName) $var uplevel 1 [list trace variable $varName wu ReadOnlyTrace] } proc ReadOnlyTrace { varName index op } { global ReadOnly upvar 1 $varName var switch $op { w { set var $ReadOnly($varName) } u { set var $ReadOnly($varName) # Re-establish the trace using the true name uplevel 1 [list ReadOnlyVar $varName] } } }
This example merely overrides the new value with the saved value. Another alternative is to raise an error with the error
command. This will cause the command that modified the variable to return the error. Another common use of trace
is to update a user interface widget in response to a variable change. Several of the Tk widgets have this feature built into them.
If more than one trace is set on a variable, then they are invoked in reverse order; the most recent trace is executed first. If there is a trace on an array and on an array element, then the trace on the array is invoked first.
Example 13-9 uses an array trace to dynamically create array elements:
Example 13-9. Creating array elements with array traces
# make sure variable is an array set dynamic() {} trace variable dynamic r FixupDynamic proc FixupDynamic {name index op} { upvar 1 $name dynArray if {![info exists dynArray($index)]} { set dynArray($index) 0 } }
Information about traces on a variable is returned with the vinfo
option:
trace vinfo dynamic
=> {r FixupDynamic}
A trace is deleted with the vdelete
option, which has the same form as the variable
option. The trace in the previous example can be removed with the following command:
trace vdelete dynamic r FixupDynamic
Table 13-4. The history
command
| Short for |
| Adds the command to the history list. If |
| Changes the command specified by |
| Returns the command specified by |
| Returns a formatted history list of the last |
| Limits the history to the last |
| Returns the number of the next event. |
| Repeats the specified command. |
The Tcl shell programs keep a log of the commands that you type by using a history facility. The log is controlled and accessed via the history
command. The history facility uses the term event to mean an entry in its history log. The events are just commands, and they have an event ID that is their index in the log. You can also specify an event with a negative index that counts backwards from the end of the log. Event -1 is the previous event. Table 13-4 summarizes the Tcl history
command. In the table, event
defaults to -1.
In practice you will want to take advantage of the ability to abbreviate the history options and even the name of the history
command itself. For the command, you need to type a unique prefix, and this depends on what other commands are already defined. For the options, there are unique one-letter abbreviations for all of them. For example, you could reuse the last word of the previous command with [history w $]
. This works because a $
that is not followed by alphanumerics or an open brace is treated as a literal $
.
Several of the history operations update the history list. They remove the actual history
command and replace it with the command that resulted from the history operation. The event
and redo
operations all behave in this manner. This makes perfect sense because you would rather have the actual command in the history, instead of the history command used to retrieve the command.
Some extra syntax is supported when running interactively to make the history facility more convenient to use. Table 13-5 shows the special history syntax supported by tclsh
and wish
.
Table 13-5. Special history
syntax
| Repeats the previous command. |
| Repeats command number |
| Repeats the last command that begins with |
| Repeats the last command that matches |
| Globally replaces |
The next example shows how some of the history operations work:
The history syntax shown in the previous example is simpler than the history syntax provided by the C shell. Not all of the history operations are supported with special syntax. The substitutions (using ^old^new
) are performed globally on the previous command. This is different from the quick-history of the C shell. Instead, it is like the !:gs/old/new/
history command. So, for example, if the example had included ^a^b
in an attempt to set b
to 39
, an error would have occurred because the command would have used b
before it was defined:
set b [expr $b+7]
If you want to improve the history syntax, you will need to modify the unknown
command, which is where it is implemented. This command is discussed in more detail in Chapter 12. Here is the code from the unknown
command that implements the extra history syntax. The main limitation in comparison with the C shell history syntax is that the !
substitutions are performed only when !
is at the beginning of the command:
Example 13-11. Implementing special history syntax
# Excerpts from the standard unknown command # uplevel is used to run the command in the right context if {$name == "!!"} { set newcmd [history event] } elseif {[regexp {^!(.+)$} $name dummy event]} { set newcmd [history event $event] } elseif {[regexp {^^([^^]*)^([^^]*)^?$} $name x old new]} { set newcmd [history event -1] catch {regsub -all -- $old $newcmd $new newcmd} } if {[info exists newcmd]} { history change $newcmd 0 return [uplevel $newcmd] }
The rapid turnaround with Tcl coding means that it is often sufficient to add a few puts
statements to your script to gain some insight about its behavior. This solution doesn't scale too well, however. A slight improvement is to add a Debug
procedure that can have its output controlled better. You can log the information to a file, or turn it off completely. In a Tk application, it is simple to create a text widget to hold the contents of the log so that you can view it from the application. Here is a simple Debug
procedure. To enable it you need to set the debug(enable)
variable. To have its output go to your terminal, set debug(file)
to stderr
.
Example 13-12. A Debug
procedure
proc Debug { args } { global debug if {![info exists debug(enabled)]} { # Default is to do nothing return } puts $debug(file) [join $args " "] } proc DebugOn {{file {}}} { global debug set debug(enabled) 1 if {[string length $file] == 0} { set debug(file) Stderr } else { if [catch {open $file w} fileID] { puts stderr "Cannot open $file: $fileID" set debug(file) stderr } else { puts stderr "Debug info to $file" set debug(file) $fileID } } } proc DebugOff {} { global debug if {[info exists debug(enabled)]} { unset debug(enabled) flush $debug(file) if {$debug(file) != "stderr" && $debug(file) != "stdout"} { close $debug(file) unset debug(file) } } }
Tcl Dev Kit is a commercial development environment for Tcl based on the original TclPro created by Scriptics. TclPro was released to the open-source community in November 2001. ActiveState has enhanced Tcl Dev Kit with new tools and more features. The development environment includes ActiveTcl[*], which is an extended Tcl platform that includes [incr Tcl], Expect, and TclX. These extensions and Tcl/Tk are distributed in source and binary form for Windows and a variety of UNIX platforms. More information is available at this URL:
The current version of the Tcl Dev Kit contains these tools:
The Debugger provides a nice graphical user interface with all the features you expect from a traditional debugger. You can set breakpoints, single step, examine variables, and look at the call stack. It understands a subtle issue that can arise from using the update
command: nested call stacks. It is possible to launch a new Tcl script as a side effect of the update
command, which pushes the current state onto the execution stack. This shows up clearly in the debugger stack trace. It maintains project state, so it will remember breakpoint settings and other preference items between runs. One of the most interesting features is that it can debug remotely running applications. The debugger also has built-in code coverage and hotspot profiling analysis. I use it regularly to debug Tcl code running inside the Tcl Web Server.
The Checker is a static code checker. This is a real win for large program development. It examines every line of your program looking for syntax errors and dubious coding practices. It has detailed knowledge of Tcl, Tk, Expect, [incr Tcl], and TclX commands and validates your use of them. It checks that you call Tcl procedures with the correct number of arguments, and can cross-check large groups of Tcl files. It knows about changes between Tcl versions, and it can warn you about old code that needs to be updated.
The Compiler is really just a reader and writer for the byte codes that the Tcl byte-code compiler generates internally. It lets you precompile scripts and save the results, and then load the byte-code later instead of raw source. This provides a great way to hide your source code, if that is important to you. It turns out to save less time than you might think, however. By the time it reads the file from disk, decodes it, and builds the necessary Tcl data structures, it is not much faster than reading a source file and compiling it on the fly.
TclApp assembles a collection of Tcl scripts, data files, and a Tcl/Tk interpreter into Starkits and Starpacks, which are described in Chapter 22. TclApp provides a more friendly user interface than the sdx command line tool described in that Chapter. The Tcl Dev Kit comes with pre-built Starkit runtimes that include Metakit, Expect, [incr Tcl], and TclX.
The Tcl Service Manager helps you turn your Tcl application into a service for Windows NT/2000/XP. Services have to implement special OS interfaces that are not supported by tclsh or wish. You can create services that use the DLLs and scripts from an existing Tcl/Tk installation, or create stand alone services that have no external dependencies.
The Inspector is an improved version of the tkinspect application that lets you look at the state of other Tk applications. It displays procedures, variables, and the Tk widget hierarchy. You can issue commands to another application to change variables or test out commands. This turns out to be a very useful way to debug Tk applications. The original tkinspect was written by Sam Shen.
The Tcl community has built many interesting and useful tools to help your Tcl development. Only two of them are mentioned below, but you can find many more at the Tcl Resource Center:
Tkcon is an enhanced Tk console application written purely in Tcl. It includes many useful interactive control features, and may be embedded in other Tcl applications. It was written by Jeff Hobbs and you can find it at:
Critcl is a tool that lets you mix C code right into your Tcl scripts. When the cproc
command encounters its code for the first time, it automatically compiles it with gcc and loads it into your application. This provides an easy way to recode small parts of your application in C to get a performance boost. It's home page is:
When a Tcl script encounters an error during background processing, such as handling file events or during the command associated with a button, it signals the error by calling the bgerror
procedure. A default implementation displays a dialog and gives you an opportunity to view the Tcl call stack at the point of the error. You can supply your own version of bgerror
. For example, when my exmh
mail application gets an error it offers to send mail to me with a few words of explanation from the user and a copy of the stack trace. I get interesting bug reports from all over the world!
The bgerror
command is called with one argument that is the error message. The global variable errorInfo
contains the stack trace information. There is an example tkerror
implementation in the on-line sources associated with this book.
The bgerror
command used to be called tkerror
. When event processing shifted from Tk into Tcl with Tcl 7.5 and Tk 4.1, the name tkerror
was changed to bgerror
. Backwards compatibility is provided so that if tkerror
is defined, then tkerror
is called instead of bgerror
. I have run into problems with the compatibility setup and have found it more reliable to update my applications to use bgerror
instead of tkerror
. If you have an application that runs under either Tk 4.0 or Tk 4.1, you can simply define both:
proc bgerror [info args tkerror] [info body tkerror]
The time
command measures the execution time of a Tcl command. It takes an optional parameter that is a repetition count:
If you need the result of the command being timed, use set
to capture the result:
puts $log "command: [time {set result [command]}]"
An extensive benchmark suite that compares various Tcl versions is available at:
Another way to gain insight into the performance of your script is to generate log records that contain time stamps. The clock seconds
value is too coarse, but you can couple it with the clock clicks
value to get higher resolution measurements. Use the code shown in Example 13-1 on page 185 to calibrate the clicks per second on your system. Example 13-13 writes log records that contain the current time and the number of clicks since the last record. There will be occasional glitches in the clicks value when the system counter wraps around or is reset by the system clock, but it will normally give pretty accurate results. The Log
procedure adds overhead, too, so you should take several measurements in a tight loop to see how long each Log
call takes:
Example 13-13. Time Stamps in log records
proc Log {args} { global log if [info exists log(file)] { set now [clock clicks] puts $log(file) [format "%s (%d) %s" [clock format [clock seconds]] [expr $now - $log(last)] [join $args " "]] set log(last) $now } } proc Log_Open {file} { global log catch {close $log(file)} set log(file) [open $file w] set log(last) [clock clicks] } proc Log_Flush {} { global log catch {flush $log(file)} } proc Log_Close {} { global log catch {close $log(file)} catch {unset log(file)} }
A more advanced profile
command is part of the Extended Tcl (TclX) package. The TclX profile
command monitors the number of calls, the CPU time, and the elapsed time spent in different procedures.
The built-in Tcl compiler improves performance in the following ways:
Tcl scripts are converted into an internal byte-code format that is efficient to process. The byte codes are saved so that cost of compiling is paid only the first time you execute a procedure or loop. After that, execution proceeds much faster. Compilation is done as needed, so unused code is never compiled. If you redefine a procedure, it is recompiled the next time it is executed.
Variables and command arguments are kept in a native format as long as possible and converted to strings only when necessary. There are several native types, including integers, floating point numbers, Tcl lists, byte codes, and arrays. There are C APIs for implementing new types. Tcl is still dynamically typed, so a variable can contain different types during its lifetime.
Expressions and control structures are compiled into special byte codes, so they are executed more efficiently. Because expr
does its own round of substitutions, the compiler generates better code if you group expressions with braces. This means that expressions go through only one round of substitutions. The compiler can generate efficient code because it does not have to worry about strange code like:
set subexpr {$x+$y} expr 5 * $subexpr
The previous expression is not fully defined until runtime, so it has to be parsed and executed each time it is used. If the expression is grouped with braces, then the compiler knows in advance what operations will be used and can generate byte codes to implement the expression more efficiently.
The operation of the compiler is essentially transparent to scripts, but there are some differences in lists and expressions. These are described in Chapter 54. With lists, the good news is that large lists are more efficient. The problem is that lists are parsed more aggressively, so syntax errors at the end of a list will be detected even if you access only the beginning of the list. There were also some bugs in the code generator in the widely used Tcl 8.0p2 release. Most of these were corner cases like unbraced expressions in if
and while
commands. Most of these bugs were fixed in the 8.0.3 patch release, and the rest were cleaned up in Tcl 8.1 with the addition of a new internal parsing package.
The internal compiler continues to improve over time, with 8.4 extending the core instruction table to significantly improve performance over previous versions.
18.226.169.94