Programming in TclX: Useful Commands

Programming in TclX

TclX does not just add new "verbs" to your Tcl vocabulary. Certain Tcl commands enhance the process of code development, and others might influence your choice of coding strategy, offering new and more concise methods of flow control. A few more assist you to debug and streamline your code, and a set of code library management functions permit you to organize and package your code more efficiently.

Some Tcl commands are self-referential. Tcl can look at its own internal state using commands such as info; TclX adds a few more.

infox option returns the value of the specified configuration option, which can come in handy when your Tcl application wants to run on many different platforms:

tcl>infox version
7.4a-b4
tcl>infox patchlevel
0
tcl>infox have_fchown
1
tcl>infox have_sockets
1
One of the nice things about Tcl is the ability to test little snippets of code interactively while writing larger modules. With TclX, you can do a lot of development without ever leaving the interpreter. You can retrieve the definition of a Tcl procedure using showproc procName:

tcl>showproc for_file
proc for_file {var filename code} {
    upvar $var line
    set fp [open $filename r]
    while {[gets $fp line] >= 0} {
        uplevel $code
    }
    close $fp
}
You can edit a specific procedure or procedures directly from the interpreter prompt using
	edprocs ?procName [procName...]?
The named procedures, or by default all currently defined procedures, are written to a temporary file, and the editor specified by your EDITOR environment variable is started on that file. If EDITOR is not set, you get vi. When you write and quit, the changed proc definitions are loaded back into the running interpreter.

Using

	saveprocs fileName ?procName [procName...]?
you can write loaded procs out to a text file. You can, for example,
tcl> source myprocs.tcl
tcl> doMyProc arg1 arg2
(... get some error message)
tcl> edprocs doMyProc
(... do some editing)
tcl> doMyProc arg1 arg2
I worked this time!
tcl> saveprocs doMyProc.tcl doMyProc

The ability to test and edit without leaving the interpreter can sometimes speed up your code development significantly.

TclX might even change the way you structure your code, or the approach you take to flow control. Core Tcl provides the while and for statements for loop control. TclX enhances loop design and control with the loop command:

loop loopVar startVal limitVal ?incrAmount? execCode
where startVal, limitVal, and incrAmount must all be integers. incrAmount defaults to 1 if not specified. The loop statement runs faster than a for loop, and makes for less verbose coding when the start and end values are known and the integer is always the same fixed amount. Compare
tcl> for {set i 0} {$i < 5} {incr i} {echo "i is $i"}
i is 0
i is 1
i is 2
i is 3
i is 4
to
tcl>loop i 0 5 {echo "i is $i"}
i is 0
i is 1
i is 2
i is 3
i is 4
If you use variables for startVal and limitVal you can change their values within the loop -- but to no avail. They, and the incrAmount, are evaluated only once when the loop command is parsed. Note that the loop ends after the last iteration where the counter variable is less than the limit. Using the for syntax, some programmers might more naturally have written i <= 4; I wanted to make the two loops logically identical.

execCode is any syntactically correct block of Tcl code, as above. Both break and continue work within execCode, exactly the same as within the body of a while or for loop.

TclX also provides a couple of procs for convenient looping using arrays and the results of glob commands:

	for_array_keys varName arrayName execCode
loops once for each key of the array arrayName, putting the key value in varName and executing execCode:

tcl>set a(1) This
tcl>set a(2) is
tcl>set a(3) a
tcl>set a(4) test
tcl>for_array_keys k a {
=>echo value of a(k) is $a($k) and k is $k
=>}
value of a(k) is test and k is 4
value of a(k) is This and k is 1
value of a(k) is is and k is 2
value of a(k) is a and k is 3
tcl>
By the way, this also happens to illustrate the unordered nature of array indices, of which the novice Tcl programmer should beware. I personally prefer

tcl>foreach k [lsort [array names a]] {
=>echo k is $k and its value is $a($k)
=>}
k is 1 and its value is This
k is 2 and its value is is
k is 3 and its value is a
k is 4 and its value is test
tcl>

More useful and interesting is the recursive_glob enhancement, a proc that recursively glob's over a list of directories:

	recursive_glob dirList globList
All the directories specified in dirList are recursively searched (breadth-first), and each file found is compared against all the glob patterns in globList. This is a handy packaging of what one frequently has to do the hard way with the core Tcl glob command. Note that symbolic links are not followed!.

In the tradition of for_file,

	for_recursive_glob varName dirList globList execCode
simply loops over the filenames returned from a recursive_glob, and is equivalent to
	foreach varName [recursive_glob dirList globList] execCode
... it's a just little less typing. Here's an example of the difference between glob and recursive_glob:
tcl>foreach f [glob *] {
=>if {[file isdirectory $f]} {
=>      lappend dirs $f
=>}
=>}
tcl>echo $dirs
tcl dbase deimos doc text
tcl>
tcl>recursive_glob $dirs {*ail*}
tcl/Mail tcl/HINTS/mail.filt.Z tcl/HINTS/mailinglist 
tcl/w/wisql.monthly.mailing tcl/WOES/tpmail tcl/a/mail.acct ...
We used glob to get names of directories found in the current directory. Then we used that list of directories in the recursive_glob command to search the entire tree under each of those directories for matches to the regular expression.

Debugging Tcl applications is not always a trivial exercise. TclX offers you some useful tools for debugging and performance tuning.

The cmdtrace command lets you trace the path of execution through your source code:

	cmdtrace level|on ?noeval? ?notruncate? ?procs? ?fileID?
The level argument tells TclX at which calling level (as understood by upvar) to start tracing. All commands executed at that level or lower will be echoed to the tracefile (or to stdout if no tracefile is specified). If the word "on" is used instead of an integer level number, then all commands executed at any level will be echoed.

If noeval is specified, then variables will be echoed unevaluated; by default they are evaluated before printing. If notruncate is specified, command lines longer than 60 characters will be echoed in their entirety; by default, they are truncated to 60 characters with an appended ellipsis ("..."). If procs is specified, then only commands that invoke Tcl procedures (as opposed to compiled C code) will be echoed; by default, all commands will be echoed.

If you wish the trace output to be saved in a file, you can specify fileID, a file ID such as is returned by open.

tcl> cmdtrace on [open cmd.log w]
would begin complete command tracing and save the output in the file cmd.log.
tcl> cmdtrace off
turns command tracing off again, and
tcl> cmdtrace depth
returns the current maximum trace level, or zero if tracing is disabled.

The profile command allows you to make a performance profile of your TclX application. The profiling feature collects the number of calls to each procedure, and the number of real seconds and CPU seconds spent in the procedure. The syntax is

	profile ?-commands? on
	profile off arrayVarName
If the -commands option is specified, profile data are collected for all the commands within procedures as well as for the procedures themselves. When profiling is turned off again, an array variable must be specified; the collected data are moved into this array.

You could examine this array manually, but it might get tedious; TclX provides an output formatting command profrep to present the profiling data legibly.

	profrep arrayVarName sortKey stackDepth ?outFile? ?userTitle?
Sorting of the data is done according to sortKey, which must be calls, cpu, or real. The stackDepth parameter controls the amount of stack information reported: a value of 1 reports only by procedure, but a value of 2 would report both the procedure name and its caller, and so on. The output file specification in this case is a file name, not a file ID; if you omit this argument, stdout is the default. You can optionally add a title of your own choice to the formatted output written to the file.

Earlier in this chapter I mentioned a performance difficulty with large keyed lists. It would have been quite difficult to isolate this problem (in a 10,000 line Tcl/Tk application), had I not been able to use profile and profrep.

As your Tcl applications grow larger, you're challenged by more than just debugging the increasingly complex code; you begin to need ways of organizing your code into libraries, eliminating duplicate functions, and writing general-purpose routines to serve more than one application -- just as C programmers do. TclX provides some fairly sophisticated tools for doing this.

TclX supports the core Tcl tclIndex type of code library. It also introduces more powerful and flexible methods of library management, by means of ".tlib" and ".tndx" files. Collections of procedures can be stored in a "tlib" (Tcl LIBrary) file. A .tlib file is a plain text file containing Tcl procedure code and some "magic" comment lines which delimit "packages" within the library. We'll discuss the function of packages later, but basically they are intended to be groups of related procedures. You can edit a .tlib file with any common text editor. Tlib file names must always have the extension .tlib.

For TclX to use the .tlib file, there must be a corresponding index file: a library file mumble.tlib would have an index file mumble.tndx. The purpose of the index file is to speed up access to procedures in the .tlib file, by establishing byte offsets for the start and end of each package in the library. TclX will automatically re-create this index file if it is missing or out of date (provided of course that the user running TclX has write access to the library directory). When the TclX application "asks for" the library, TclX checks the dates on the index file and the .tlib file, and regenerates the index file if it is older than the .tlib file.

The global variable auto_path (analogous to the csh environment variable PATH) contains a list of directories to be searched, in order, for .tlib files. On the first unknown command trap during an execution of TclX code, the indices for libraries on this path are loaded into memory. A string search commences for the name of the missing procedure. The first package containing that procedure name is loaded in its entirety (all commands in that package are loaded). In other words, say you have a tlib file foo.tlib:

#@package: MyPackageName myproc1 myproc2 myproc3 myproc4 \
	myproc5 myproc6

myproc1 {foo faa} {

	...
}

myproc2 {fee fie} {

	...
}
and so forth. This .tlib file is in a directory found in the auto_path variable. Now, in your TclX code, you have a call to myproc1:
if {$err} {
	myproc1 azuki garbanzo
}
myproc1 has not been previously defined in your source, so TclX gets an unknown command trap. The interpreter responds to the trap by searching your auto_path and loading in the indices for all tlibs on that path. It then searches the loaded indices for the string myProc1, which it finds in the MyPackageName package. The interpreter loads in the entire package. Now it can resolve the missing command name, and execution continues.

The package concept improves efficiency, provided that you define your packages sensibly. Generally speaking, procedures do fall into "families" of related function; an application that loads one member of the family is likely to require one or more of the others. The loading of the entire package ensures that no further unknown command traps and delays will be involved in accessing this family of procedures.

You can also explicitly load a particular .tlib file at the start of your source code, rather than relying on auto_path. Whichever method you use, beware of duplicate procedure names in your .tlib files. The first package containing the procedure with the name of the missing command is the one that will get loaded.

The command auto_packages will return a list of all the defined packages (established by the automatic loading of .tndx files along the auto_path, at startup). The command auto_commands will tell the names of all known (found in loaded indices) commands that can be auto-loaded. With the -loaders option it will specify as well the command that will be executed to load the command.:

tcl> auto_packages
TclX-convertlib TclX-buildhelp TclX-ucblib TclX-Compatibility ...
tcl> tcl>auto_commands -loaders
{write_file {auto_load_pkg {TclX-stringfile_functions}}} 
{sin {auto_load_pkg {TclX-fmath}}} 
{assign_fields {auto_load_pkg {TclX-Compatibility}}} 
{log10 {auto_load_pkg {TclX-fmath}}}  ...
(I have formatted this for legibility: in each case the output is actually just one long list without newlines). The output of auto_commands tells you which package each command belongs to. For example, the sin command belongs to the package TclX-fmath. If I delve into the TclX distribution, I find a .tlib file tcl.tlib, and looking inside that file I find a package header TclX-fmath:
#@package: TclX-fmath acos asin atan ceil cos cosh exp fabs floor log log10 \
           sin sinh sqrt tan tanh fmod pow atan2 abs double int round
This reveals the TclX authors' assumption with regard to their math functions: an application which uses one is likely to use the rest, but an application that did no heavy math might never use any of them; so they are loaded as a package. Unfortunately, the TclX package commands cannot tell you which .tlib file a package came from; this information is not preserved at load time.

The command

	auto_load commandName
attempts to resolve the reference to commandName by loading the entire package in which commandName is found. It searches along auto_path for .tlib files. This permits you to prevent the uknown command trap by manually loading the commands you know you will be using. The command auto_load_file fileName will attempt to source the file fileName, but will search for it along auto_path rather than in the current directory.

The command

	searchPath dirPath fileName
was developed to support TclX libraries and packages: dirPath is a list of directory specifications, which will be searched for fileName. The return value is the full path name of the file. You may find this command useful in other contexts, not just when dealing with libraries.

The command

	buildpackageindex listOfTlibs
allows you to force a rebuild of the index files for every .tlib file in the list. You might want to think of this as analogous to the ranlib Unix command. (Note: the specified filenames must include the .tlib suffix.) You might want to do this during interactive development, when you are editing .tlib files directly from the TclX shell prompt. Of course, you have to have write access to the directories where the .tlib files reside, because new .tndx files will be created there.

The command

	loadlibindex tlibFile
forces a load of the entire contents of tlibFile. This command allows you to load .tlib files not found on auto_path (or, looked at in a slightly different light, relieves you of the need to maintain a complex auto_path). You might also use it conditionally, to load different versions of a library depending on user choices or other conditions identified by the application. You might write a generic database interface application which loaded different versions of high-level query routines depending on the user's choice of database engine:
if {$db == "OR"} {
	loadlibindex /some/long/path/OracleProcs.tlib
} else {
	loadlibindex /some/long/path/IngresProcs.tlib
}

You can, in fact, use loadlibindex to override a previous load of the index for a named package, for example to write a local or experimental version over a production version; however, if any procedure has actually been invoked from the previously loaded version of the package, the override will fail and the procedure definitions from the earlier load will persist. Therefore, you cannot use loadlibindex iteratively during interactive development. However, all the "magic" package information in a .tlib file is embedded in comment lines, so you can source the .tlib file to override previous loaded procedure definitions.

Lastly,

	convert_lib tclIndexFile tlibFile ?ignoreList?
will convert an existing old-style tclIndex file and its associated source files into a TclX package library. In this case you can omit the .tlib extension from tlibFile and it will be appended for you. The optional list ignoreList specifies tcl source files to be ignored when the .tlib file is constructed. The file names in the ignoreList should be only base filenames, without paths.

The TclX method of maintaining code libraries is tidy and flexible. It is functionally analogous to object libraries, in that a single file is used to store an archive of functions and subroutines, which can be loaded at will into applications. I find .tlib files extremely useful for organizing procedures into general-purpose and specific functions; the general-purpose library ucosyb.tlib is loaded (via loadlibindex) in all my sybase/tcl applications, but each app has also its own private tlib full of functions and subroutines useful only to itself.

I usually maintain individual Tcl procedure modules in an RCS directory, with a Makefile that constructs a tlib out of them. This is comfortably analogous to the normal C development style, gives me individual revision control over each procedure, and yet makes my code easy to package and export (usually each application consists only of one "main" and one or two .tlib files). Users of CVS could make the .tlib files "modules" in CVS parlance.