# 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
returnTcl2002 programming contest: problem 3The Great Canadian Tcl/Tk Programming Contest, eh?

