Updated 2011-01-28 01:39:10 by RLE

The primary purpose of this problem was to expose the new [trace execution] command that was added in Tcl 8.4. Andrej Vckovski took the laurels on this problem, with the following solution, which even favored us with a test harness to demonstrate its execution:
 # display a call graph of the passed command line
 # AVckovski, 2002-11-20

 proc callGraph {cmdLine} {

     proc ::_myEnterstep {cmdString ops} {

         set cmdName [lindex $cmdString 0]
             if {[string length [info proc $cmdName]]} {
                 if {![info exists ::_traces($cmdName)]} {
                 trace add execution $cmdName enterstep ::_myEnterstep
                 # lazy cleanup; just remember all traces instead of using trace info
                         set ::_traces($cmdName) ::_myEnterstep
             }
          }
          # display command word, indented
          # if more than one line, just 1st, and limit line length
          set cmdString [lindex [split $cmdString \n] 0]
          if {[string length $cmdString]>40} {
              set cmdString "[string range $cmdString 0 40] ..."
          }
          puts "[string repeat -- [info level]] $cmdString"
     }

     # init some state
     #set ::_traces {}

     # setup and start
     set cmdName [lindex $cmdLine 0]
     uplevel [list ::_myEnterstep $cmdLine enterfirst]
     uplevel $cmdLine

     # cleanup our recorded traces
     foreach cmdName [array names ::_traces] {
        trace remove execution $cmdName enterstep $::_traces($cmdName)
     }

     # cleanup state
     rename ::_myEnterstep ""
     unset ::_traces
 }

 # test case
 proc sum {a b} {
    return [expr $a + $b]
 }

 proc z {} {return [sum [sum [sum [sum 5 6] 7] 8] [sum 11 22]]}

 proc fac {n} {
   if {$n==1} {
      return 1
   } else {
      return [expr $n*[fac [expr $n-1]]]
   }
 }

 callGraph z
 callGraph {fac 8}

which produces output that looks like:
 -- z
 ---- sum 5 6
 ------ expr 5 + 6
 ------ expr 5 + 6
 ------ return 11
 ------ return 11
 ---- sum 11 7
 ------ expr 11 + 7
 ------ expr 11 + 7
 ------ return 18
 ------ return 18
 ---- sum 18 8
 ------ expr 18 + 8
 ------ expr 18 + 8
 ------ return 26
 ------ return 26
    . . .

In preparation for the contest, KBK had developed the following solution:
 proc K { x y } { return $x }
 proc doit { command } {
     uplevel 1 $command
 }
 proc callgraph { command } {
     variable context
     variable children
     variable did
     set context {}
     trace add execution doit enterstep enter
     trace add execution doit leavestep leave
     uplevel 1 [list doit $command]
     trace remove execution doit enterstep enter
     trace remove execution doit leavestep leave
     display uplevel
     catch { unset children }
     catch { unset did }
     return
 }
 proc enter { commandStr op } {
     variable context
     variable children
     set command [lindex $commandStr 0]
     set children([lindex $context end],$command) {}
     lappend context $command
     return
 }
 proc leave { commandStr code result op } {
     variable context
     # There's a Tcl bug where we get extra 'leave' traces. Work around it.
     if { [info level] < [llength $context] } {
         set context [lreplace [K $context [set context {}]] [info level] end]
     }
     return
 }
 proc display { context { level 0 } } {
     variable children
     variable did
     set last [lindex $context end]
     if { [info exists did($last)] } {
         if { [llength [array names children $context,*]] > 0 } {
             puts [format %*s... [expr { 4 * $level}] {}]
         }
     } else {
         set did($last) {}
         foreach child [lsort [array names children $context,*]] {
             foreach { - childproc } [split $child ,] break
             puts [format %*s%s [expr { 4 * $level}] {} $childproc]
             display $childproc [expr { $level + 1 }]
         }
     }
     return
 }

which produces rather more compact output; on Andrej's test case, it shows
 % callgraph z
 z
     return
     sum
         expr
         return
 % callgraph {fac 8}
 fac
     expr
     fac
         ...
     if
         expr
     return

Tcl2002 programming contest: problem 3

The Great Canadian Tcl/Tk Programming Contest, eh?