Steppin' out

Richard Suchenwirth 2002-12-17 - This is my first experiment with the command traces introduced in Tcl 8.4: step through certain procs, so that every command inside the body is displayed before execution, and its result after, while allowing to execute arbitrary Tcl commands at such step positions. This is of course a powerful debugging tool

The code below requires stdin/out, so under Windows is best run from a tclsh. At the stepping prompt, <Return> lets you advance through the code. What troubled me on my 8.4.0/W95 installation at home is that for all but the first command, leavestep traces fired even before enterstep traces (with empty string result) - wonder whether that's a bug (same on 8.4.1/W2k)... Also, when I provoked an error with the uncommented kaboodle command below, stepping continued into unknown, which was interesting to observe but not what the doctor ordered.

proc stepping name {
   trace add exec $name enterstep enterStep
   trace add exec $name leavestep leaveStep
}
proc noStepping name {
   trace remove exec $name enterstep enterStep
   trace remove exec $name leavestep leaveStep
}
proc enterStep {cmd _} {uplevel 1 [list bp "before $cmd"]}

proc leaveStep {cmd code result _} {
   uplevel 1 [list bp "result ($code):$result<=$cmd"]
}

Reusable breakpoint handler from A minimal debugger

proc bp s {
    if {[string length $s]>50} {set s [string range $s 0 49]...}
    while 1 {
        puts -nonewline "$s > "
        flush stdout
        gets stdin line
        if {$line==""} break
        catch {uplevel 1 $line} res
        puts $res
    }
}

Testing...

if {[file tail [info script]] == [file tail $argv0]} {
    proc foo x {
        puts "This is foo"
        set i 0
        while {$i < $x} {
            puts "i=$i"
            incr i
        }
        #kaboodle
        puts "That was foo"
    }
    stepping foo
    foo 3
}

Sample session:

before puts {This is foo} >
This is foo
result (0):<=puts {This is foo} >
result (0):<=set i 0 >
before set i 0 >
result (0):0<=set i 0 >
result (0):<=while {$i < $x} {
           puts "i... >
before while {$i < $x} {
           puts "i=$i"
... >
result (0):<=puts i=0 > info locals
x i
result (0):<=puts i=0 > set x
3
result (0):<=puts i=0 >
before puts i=0 >
i=0
result (0):<=puts i=0 >
result (0):<=incr i >
before incr i >
result (0):1<=incr i >
result (0):<=puts i=1 >
before puts i=1 >
i=1
result (0):<=puts i=1 >
result (0):<=incr i >
before incr i >
result (0):2<=incr i >
result (0):<=puts i=2 >
before puts i=2 > info pa
8.4.1
before puts i=2 >
i=2
result (0):<=puts i=2 >
result (0):<=incr i >
before incr i >
result (0):3<=incr i >
result (0):<=while {$i < $x} {
            puts "i... >
result (0):<=puts {That was foo} >
before puts {That was foo} >
That was foo
result (0):<=puts {That was foo} > 

The early leavestep is indeed a bug, registered at SF (655645). Until it gets fixed, here is a workaround that uses a global variable to suppress calls to leaveStep before the matching enterStep was called:

proc stepping name {
    trace add exec $name enterstep enterStep
    trace add exec $name leavestep leaveStep
    if {![info exists ::Entered]} {set ::Entered ""} ;# Workaround
}
proc enterStep {cmd _} {
    set ::Entered $cmd ;# Workaround
    uplevel 1 [list bp "before $cmd"]
}
 
proc leaveStep {cmd code result _} {
    if {$cmd eq $::Entered} { ;# Workaround
        uplevel 1 [list bp "result ($code):$result<=$cmd"]
    }
}

AM I have used the above technique to create A basic debugger that works under tclsh and wish, even under Windows.

RS A more recent take is under Steppin' out again.