The code below is self explaining. ====== # # tcltags.tcl # # plp / _abc_ @ irc.freenode.net # # Parses a tcl program partially to extract the call # tree and/or generates a ctags tags file compatible with vi/vim. # # Currently parses only one program per call. The output of # several calls can be concatenated into a single tags file # using cat or >> redirection etc. # # The program ONLY writes to stdout. The default output is a # textual representation of the call chart for human use. # # Options: # --sort-uniq sorts and uniqs called procs in each proc. # --tags generates a vim compatible tags file on stdout. # -- standard option to indicate end of options. # # Example: # # tcltags.tcl --tags somesource.tcl >tags # # Assumptions: # - Procs are defined with the word proc starting in column 0 and are followed # by a { on the same line (no \\n continuation). Proc names must match the # regexp /[_A-Za-z][_0-9A-Za-z]*/ . # - Each proc ends with a '}' in column 0. # - No nested procs (private procs) are recognized. # - No library include or sourced files are considered. # # Versions: # 0.1a fixes regexps used in vim tags files # #\ exec /usr/bin/env tclsh "$0" "$@" # script version set Version "0.1a" # parameter: 1 sorts and uniqs called procs in lists of called procs, # affects only Stdout output, cli --sort-uniq set SortUniq 0 # parameter: 1 writes a ctags compatible output file, cli --tags set Ctags 0 # parameter: 1 writes a representation of the data on stdout, # this is the default, and is turned off by --tags set Stdout 1 # start set Data "" ;# collect file data here # currently the program must be called with one argument # every time. The output tags files can be merged by # cat-ing them simply together, or >>to the output on each # call. if {[lindex $argv 0] eq "--tags"} { set Ctags 1 set Stdout 0 set argv [lrange $argv 1 end] } else { if {[lindex $argv 0] eq "--sort-uniq"} { set SortUniq 1 set argv [lrange $argv 1 end] } } # check there are no more options, standard -- is accepted if {[regexp {^--.+} [lindex $argv 0]]} { puts stderr "error: invalid option '[lindex $argv 0]'" exit 2 } if {[regexp {^--$} [lindex $argv 0]]} { set argv [lrange $argv 1 end] } if {[llength $argv] != 1} { puts stderr "usage: [file tail $argv0] filename.tcl" exit 2 } foreach arg $argv { if { [catch { set fd [open $arg "r"] append Data [read $fd] close $fd } err] } { puts stderr "Error: open [lindex $argv 1]: $err" exit 1 } } # determine the names of the functions catch {array unset Functions} array set Functions "" set procname0 "" set lineno 0 set Data1 [split $Data "\n"] foreach lin $Data1 { incr lineno if {[regexp {^proc[ \t]+([_A-Za-z][_0-9A-Za-z]*)[ \t]+\{} $lin \ dummy procname] \ } { set Functions($procname) $lineno set procname0 $procname continue } if {[regexp {^\}} $lin] } { if {$procname0 ne ""} { lappend Functions($procname0) $lineno set procname0 "" } } } # debug #puts [join [lsort [array names Functions] ] "\n"] # for each found function look inside it for the others foreach procname [array names Functions] { set t $Functions($procname) set flist "" foreach lin [lrange $Data1 [lindex $t 0] [lindex $t 1] ] { foreach pname [array names Functions] { if {$pname eq $procname} {continue} #if {$pname in [list $lin]} {lappend flist $pname} if {[regexp "\[\[:<:\]\]$pname\[\[:>:\]\]" $lin]} {lappend flist $pname} } } if {$flist ne ""} { if $SortUniq {set flist [lsort -uniq $flist]} lappend Functions($procname) {*}$flist } else { } } # sorted line list for in-procedure lines, format {x0 x1} ... # uses the fact that procs do not nest (local procs don't work with # this code!) set sll "" foreach pname [array names Functions] { lappend sll [lrange $Functions($pname) 0 1] } set sll [lsort -integer -index 0 $sll] set sll1 "" foreach x $sll { lappend sll1 {*}$x } set sll $sll1 unset sll1 # find toplevel function calls set lineno 0 set slli 0 ;# index into sll set Topf "" foreach lin $Data1 { incr lineno if {$lineno == [lindex $sll $slli]} { incr slli } if {($slli % 2) == 0} { ;# slli is even outside functions foreach fn [array names Functions] { if {[regexp "\[\[:<:\]\]$fn\[\[:>:\]\]" $lin]} { lappend Topf [list $fn $lineno] } } } } if $Stdout { # generate output puts "* tcltags version $Version plp 2011" puts "***INPUT" puts [join $argv "\n"] puts "*** FUNCTIONS" puts "* Format: Function Startline:Endline Length (in lines)" foreach fn [lsort [array names Functions] ] { set t $Functions($fn) puts "proc $fn [join [lrange $t 0 1] ":"] [expr {[lindex $t 1]-[lindex $t 0]}] L" foreach fn1 [lrange $t 2 end] { puts " $fn1" } } puts "*** TOP LEVEL CALLS" foreach t $Topf { foreach {fn lineno} $t { puts "$fn $lineno" } } puts "*** END" } if $Ctags { set t0 [lindex $argv 0] foreach fn [lsort [array names Functions] ] { puts "$fn\t$t0\t/^proc\\s\\+$fn\\s\\+\{/" } } # vim:ts=2:sw=2 ====== <>dev. tools | parsing