:TITLE: More Debugging - trace
;#
;# RCSID: $Header: /home/clif/CVS_ROOT/TclTutor/Tcl37.lsn,v 1.4 2000/04/24 16:17:52 clif Exp $
;# Copyright (c) 1995 Clif Flynt
;# 9300 Fleming Rd.
;# Dexter, MI  48130
;# clif@cflynt.com
;# See file "NOTICE" for licensing terms.
;#
:LESSON_TEXT_START_LEVEL 0:
The Tcl interpreter supports a method for tracking when and how a
variable is accessed.  With the <B>trace</B> command, a procedure 
can be defined to be executed whenever a variable is read, written,
or unset.
<P>

The <B>trace</B> command executes at the same stack level as the access
to the variable.  The proc that trace invokes is one stack level lower.
Thus, with the uplevel command, a procedure called via a trace can report
on the conditions that were set when a variable was accessed.
<DL>
<B><DT>trace variable</B> <I>variableName operation procname</I>
<DD> Places a trace on the variable <I>variableName</I>.  Whenever
<I>variableName</I> is accessed for the operation specified in the
<I>operation</I> argument, the procedure <I>procname</I> will be called. 
<P>
<I>Operation is one of:</I>
<DL>
<DD>r ...... Read 
<DD>w .... Write
<DD>u ...... Unset
</DL>
<P>
A variable can be unset either explicitly with the <B>unset</B> command,
or implicitly when a procedure returns, and all of the local variables are
released.
<P>
When <I>variableName</I> is accessed, <I>procName</I> will be called with 
three arguments, <I>variableName, elementName</I> and <I>operation</I>.  If the
variable being accessed is an associative array, then <I>elementName</I>
will contain the name of the element being accessed.  If <I>variableName</I>
is a simple variable, then <I>elementName</I> will be an empty string.
<I>Operation</I> is the operation that was done on <I>variableName</I> to
invoke the <B>trace</B> action.
<B><DT>trace vdelete</B> <I>variableName operation procname</I>
<DD>Cancels the trace action for <I>operation</I> on <I>variableName</I>.
<B><DT>trace vinfo</B> <I>variableName</I>
<DD>Returns information about the traces applied to <I>variableName</I>
</DL>

:TEXT_END:

:LESSON_TEXT_START_LEVEL 1:

When you are debugging a program, it's sometimes useful to know when a
variable gets changed.  The Tcl interpreter supports a method for
tracking when and how a variable is accessed.  With the <B>trace</B>
command, a procedure can be defined to be executed whenever a variable
is read, written, or unset.  With this command you can determine what
proc a variable is modified within, what the value was changed to, and
what other variables were at the time.
<P>
The <B>trace</B> command executes at the same stack level as the access
to the variable.  The proc that trace invokes is one stack level lower.
Thus, with the uplevel command, a procedure called via a trace can report
on the conditions that were set when a variable was accessed.
<DL>
<B><DT>trace variable</B> <I>variableName operation procname</I>
<DD> Places a trace on the variable <I>variableName</I>.  Whenever
<I>variableName</I> is accessed for the operation specified in the
<I>operation</I> argument, the procedure <I>procname</I> will be called. 
<P>
<I>Operation is one of:</I>
<DL>
<DD>r ...... Read 
<DD>w .... Write
<DD>u ...... Unset
</DL>
<P>
A variable can be unset either explicitly with the <B>unset</B> command,
or implicitly when a procedure returns, and all of the local variables are
released.
<P>
When <I>variableName</I> is accessed, <I>procName</I> will be called with 
three arguments, <I>variableName, elementName</I> and <I>operation</I>.  If the
variable being accessed is an associative array, then <I>elementName</I>
will contain the name of the element being accessed.  If <I>variableName</I>
is a simple variable, then <I>elementName</I> will be an empty string.
<I>Operation</I> is the operation that was done on <I>variableName</I> to
invoke the <B>trace</B> action.
<B><DT>trace vdelete</B> <I>variableName operation procname</I>
<DD>Cancels the trace action for <I>operation</I> on <I>variableName</I>.
<B><DT>trace vinfo</B> <I>variableName</I>
<DD>Returns information about the traces applied to <I>variableName</I>
</DL>

:TEXT_END:

:LESSON_TEXT_START_LEVEL 2:

A variable's value gets changed often during the execution of a computer
program.  Sometimes the value gets changed in a place or manner that the
programmer doesn't expect.  For example, if you have a variable that
you are using to keep a particular value, and then forget and use that same
variable name as a counter in a loop the value of the variable will be lost.
<P> 
You can avoid this sort of bug by using long, descriptive variable names,
instead of easy to type names like <B>i</B> or <B>x</B>.
<P>
You can track down this sort of bug by using the <B>trace</B> command.
<P>
The <B>trace</B> command lets you set a procedure to be executed whenever
a particular variable is read, written, or unset.
<P>
When a variable with a trace set on it is accessed, the proc declared in
the <B>trace</B> command is executed.  The <B>trace</B> command executes
at the same stack level as the access to the variable.  The proc that
trace invokes is one stack level lower.  Thus, with the uplevel command,
a procedure called via a trace can report on the conditions that were
set when a variable was accessed. 
<P>
When a trace is activated, it calls the procedure with three arguments.
These arguments are the variable name that invoked the trace, the element
in the array that invoked the trace, if the trace was set on an associative
array variable.  If the variable that invoked the <B>trace</B> was a normal
variable, this will be an empty string.  And finally the operation that was
done on the variable to cause the trace to be activated.
<P>
Run the example, and look at the example code now.
<P>
The proc <B>traceproc</B> is defined first.  This is the procedure that
will be called in this example when a trace is activated.  The three arguments
to this proc are the variableName, the arrayElement, and the operation.
<P>
The first line of the proc just sets a local associative array with the
verbose equivalent of the <B>w</B>, <B>u</B>, and <B>r</B> operations
that will be passed to this function.  This is simply to make the output
easier to read.
<P>
The next sequence of lines gets the current value of the stack, and
reduces it by one.  If this value is 0, then the trace was invoked from
the main level.  If the value is larger, then the trace was invoked from
a proc, and <CODE>info level $level</CODE> will return the proc name
and the arguments for later display.
<P>
The final section of this proc checks <B>arrayElement</B> to determine
if the variable that triggered the <B>trace</B> was a simple variable,
or an associative array.  If <CODE>$arrayElement</CODE> contains an
empty string, then the variable that triggered the trace was a simple
variable, and <CODE>$variableName</CODE> is reported in the output.  If
<CODE>$arrayElement</CODE> is not equal to the empty string, then <CODE>
$variableName</CODE> is an associative array, and the array element will
be reported.
<P>
Next, the <B>testProc</B> proc is defined.  This proc sets the value of
the first argument, and reads the second argument.  It does the
<B>upvar</B> commands because Tcl defaults to call by value, instead of
call by reference when a variable is passed to a proc.  Thus, a copy of
the contents of <CODE>i1</CODE> and <CODE>i2</CODE> would be passed to
testProc, instead of the actual variable.  In that case, the
<B>trace</B> would not be triggered, since the variables being traced
wouldn't be being accessed. 
<P>
Next, the <B>trace</B> command is invoked for variables <B>i1</B> and
<B>i2</B>.  Note that these variables haven't been defined yet.  The
<B>trace</B> command has three subcommands, one of which is <B>variable</B>.
This is the command that sets <B>trace</B> to activate when a particular
variable is accessed.  These three lines set a trace to be activated
when variable <B>i1</B> is written, and when variable <B>i2</B> is
read or written.
<P>
When variable <B>i2</B> is initialized to "testvalue", the trace is
invoked, and the output displays that i2 was written to at the main level.
<P>
Note that when <B>testProc</B> is invoked, the output shows the local 
names of the variables assigned in the upvar command, instead of the 
names that were used when the <B>trace</B> was set.
<P>
The <B>trace</B> subcommand <B>vinfo</B> will return a list of the traces
on a variable.  This is demonstrated in the next two lines of the example.
<P>
The third subcommand of <B>trace</B> is the <B>vdelete</B>.  This command
deletes a trace.  The last few lines of the example show the read trace
on <B>i2</B> being removed.  After the <B>vdelete</B>, the read trace doesn't
show in the list of traces from <B>vinfo</B>, and the trace isn't triggered
when <B>testProc</B> is called.
<DL>
<B><DT>trace variable</B> <I>variableName operation procname</I>
<DD> Places a trace on the variable <I>variableName</I>.  Whenever
<I>variableName</I> is accessed for the operation specified in the
<I>operation</I> argument, the procedure <I>procname</I> will be called. 
<P>
<I>Operation is one of:</I>
<DL>
<DD>r ...... Read 
<DD>w .... Write
<DD>u ...... Unset
</DL>
<P>
A variable can be unset either explicitly with the <B>unset</B> command,
or implicitly when a procedure returns, and all of the local variables are
released.
<P>
When <I>variableName</I> is accessed, <I>procName</I> will be called with 
three arguments, <I>variableName, elementName</I> and <I>operation</I>.  If the
variable being accessed is an associative array, then <I>elementName</I>
will contain the name of the element being accessed.  If <I>variableName</I>
is a simple variable, then <I>elementName</I> will be an empty string.
<I>Operation</I> is the operation that was done on <I>variableName</I> to
invoke the <B>trace</B> action.
<B><DT>trace vdelete</B> <I>variableName operation procname</I>
<DD>Cancels the trace action for <I>operation</I> on <I>variableName</I>.
<B><DT>trace vinfo</B> <I>variableName</I>
<DD>Returns information about the traces applied to <I>variableName</I>
</DL>
:TEXT_END:

:CODE_START:

proc traceproc {variableName arrayElement operation} {
  set op(w) "Write"; set op(u) "Unset"; set op(r) "Read"

  set level [info level]
  incr level -1;
  if {$level > 0} {
     set procid [info level $level]
     } else {set procid "main"}

  if {![string match $arrayElement ""]} {
    puts "TRACE: $op($operation) $variableName($arrayElement) in $procid"
    } else {
    puts "TRACE: $op($operation) $variableName in $procid"
    }
  }

proc testProc {input1 input2} {
  upvar $input1 i
  upvar $input2 j

  set i 2
  set k $j;
  }

trace variable i1 w traceproc
trace variable i2 r traceproc
trace variable i2 w traceproc

set i2 "testvalue"

puts "\ncall testProc"
testProc i1 i2

puts "\nTraces on i1: [trace vinfo i1]"
puts "Traces on i2: [trace vinfo i2]\n"

trace vdelete i2 r traceproc
puts "Traces on i2 after vdelete: [trace vinfo i2]"

puts "\ncall testProc again"
testProc i1 i2


:TEXT_END:

