Chapter 13. Reflection and Debugging

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

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

clock clicks ?-milliseconds?

A high resolution counter. The precision is milliseconds, if specified (Tcl 8.4), or a system-dependent value.

clock format value ?-format str?

Formats a clock value according to str. See Table 13-2.

clock scan string ?-base clock? ?-gmt boolean?

Parses date string and return seconds value. The clock value determines the date.

clock seconds

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 %.

%a

Abbreviated weekday name (Mon, Tue, etc.).

%A

Full weekday name (Monday, Tuesday, etc.).

%b

Abbreviated month name (Jan, Feb, etc.).

%B

Full month name.

%c

Locale specific date and time (e.g., Nov 24 16:00:59 1996).

%C

First two digits of the four-digit year (19 or 20).

%d

Day of month (01 ā€“ 31).

%D

Date as %m/%d/%y (e.g., 02/19/97).

%e

Day of month (1 ā€“ 31), no leading zeros.

%h

Abbreviated month name.

%H

Hour in 24-hour format (00 ā€“ 23).

%I

Hour in 12-hour format (01 ā€“ 12).

%j

Day of year (001 ā€“ 366).

%k

Hour in 24-hour format, without leading zeros (0 - 23).

%l

Hour in 12-hour format, without leading zeros (1 ā€“ 12).

%m

Month number (01 ā€“ 12).

%M

Minute (00 ā€“ 59).

%n

Inserts a newline.

%p

AM/PM indicator.

%r

Time as %I:%M:%S %p (e.g., 02:39:29 PM).

%R

Time as %H:%M (e.g., 14:39).

%s

Seconds since the epoch.

%S

Seconds (00 ā€“ 59).

%t

Inserts a tab.

%T

Time as %H:%M:%S (e.g., 14:34:29).

%u

Weekday number (Monday = 1, Sunday = 7).

%U

Week of year (00 ā€“ 52) when Sunday starts the week.

%V

Week of year according to ISO-8601 rules (Week 1 contains January 4).

%w

Weekday number (Sunday = 0).

%W

Week of year (00 ā€“ 52) when Monday starts the week.

%x

Locale specific date format (e.g., Feb 19 1997).

%X

Locale specific time format (e.g., 20:10:13).

%y

Year without century (00 ā€“ 99).

%Y

Year with century (e.g. 1997).

%Z

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.

Note

Calculating clicks per secondtimer, high resolutionhigh resolution timerclicks per secondcalculating clicks per second

Year 2000 Compliance

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

The info Command

Table 13-3 summarizes the info command. The operations are described in more detail later.

Table 13-3. The info command

info args procedure

A list of procedure's arguments.

info body procedure

The commands in the body of procedure.

info cmdcount

The number of commands executed so far.

info commands ?pattern?

A list of all commands, or those matching pattern. Includes built-ins and Tcl procedures.

info complete string

True if string contains a complete Tcl command.

info default proc arg var

True if arg has a default parameter value in procedure proc. The default value is stored into var.

info exists variable

True if variable is defined.

info functions ?pattern?

A list of all math functions, or those matching pattern. (Tcl 8.4)

info globals ?pattern?

A list of all global variables, or those matching pattern.

info hostname

The name of the machine. This may be the empty string if networking is not initialized.

info level

The stack level of the current procedure, or 0 for the global scope.

info level number

A list of the command and its arguments at the specified level of the stack.

info library

The pathname of the Tcl library directory.

info loaded ?interp?

A list of the libraries loaded into the interpreter named interp, which defaults to the current one.

info locals ?pattern?

A list of all local variables, or those matching pattern.

info nameofexecutable

The file name of the program (e.g., of tclsh or wish).

info patchlevel

The release patch level for Tcl.

info procs ?pattern?

A list of all Tcl procedures, or those that match pattern.

info script ?filename?

The name of the file being processed, or the empty string.

info sharedlibextension

The file name suffix of shared libraries.

info tclversion

The version number of Tcl.

info vars ?pattern?

A list of all visible variables, or those matching pattern.

Variables

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.

Procedures

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 Call Stack

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:

Example 13-5. Getting a trace of the Tcl call stack

proc Call_Trace {{file stdout}} {
   puts $file "Tcl Call Trace"
   for {set x [expr [info level]-1]} {$x > 0} {incr x -1} {
      puts $file "$x: [info level $x]"
   }
}

Command Evaluation

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}
}

Scripts and the Library

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.

Note

Scripts and the Libraryscripts and the library

Use info script to find related files.

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.

Version Numbers

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.

Execution Environment

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.

Cross-Platform Support

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.

Tracing Variables and Commands

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.

Command Tracing

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.

Read-Only Variables

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.

Creating an Array with Traces

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

Interactive Command History

Table 13-4. The history command

history

Short for history info with no count.

history add command ?exec?

Adds the command to the history list. If exec is specified, then execute the command.

history change new ?event?

Changes the command specified by event to new in the command history.

history event ?event?

Returns the command specified by event.

history info ?count?

Returns a formatted history list of the last count commands, or of all commands.

history keep count

Limits the history to the last count commands.

history nextid

Returns the number of the next event.

history redo ?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.

History Syntax

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.

!n

Repeats command number n.If n is negative it counts backward from the current command. The previous command is event -1.

!prefix

Repeats the last command that begins with prefix.

!pattern

Repeats the last command that matches pattern.

^old^new

Globally replaces old with new in the last command.

The next example shows how some of the history operations work:

Example 13-10. Interactive history usage

% set a 5
5
% set a [expr $a+7]
12
% history
   1 set a 5
   2 set a [expr $a+7]
   3 history
% !2
19
% !!
26
% ^7^13
39
% !h
   1 set a 5
   2 set a [expr $a+7]
   3 history
   4 set a [expr $a+7]
   5 set a [expr $a+7]
   6 set a [expr $a+13]
   7 history

A Comparison to C Shell History Syntax

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]
}

Debugging

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

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:

Debugger with Coverage

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.

Checker

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.

Compiler

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

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.

Tcl Service Manager

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.

Inspector

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.

Other Tools

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:

The tkcon Console

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

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:

The bgerror Command

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 tkerror Command

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]

Performance Tuning

The time command measures the execution time of a Tcl command. It takes an optional parameter that is a repetition count:

time {set a "Hello, World!"} 1000
=> 28 microseconds per iteration

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:

Time stamps in a Log

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 Tcl Compiler

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.



[*] ActiveTcl is a trademark of ActiveState Corporation.

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

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