Buckets:
| # | |
| # ttrace.tcl -- | |
| # | |
| # Copyright (C) 2003 Zoran Vasiljevic, Archiware GmbH. All Rights Reserved. | |
| # | |
| # See the file "license.terms" for information on usage and redistribution of | |
| # this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
| # ---------------------------------------------------------------------------- | |
| # | |
| # User level commands: | |
| # | |
| # ttrace::eval top-level wrapper (ttrace-savvy eval) | |
| # ttrace::enable activates registered Tcl command traces | |
| # ttrace::disable terminates tracing of Tcl commands | |
| # ttrace::isenabled returns true if ttrace is enabled | |
| # ttrace::cleanup bring the interp to a pristine state | |
| # ttrace::update update interp to the latest trace epoch | |
| # ttrace::config setup some configuration options | |
| # ttrace::getscript returns a script for initializing interps | |
| # | |
| # Commands used for/from trace callbacks: | |
| # | |
| # ttrace::atenable register callback to be done at trace enable | |
| # ttrace::atdisable register callback to be done at trace disable | |
| # ttrace::addtrace register user-defined tracer callback | |
| # ttrace::addscript register user-defined script generator | |
| # ttrace::addresolver register user-defined command resolver | |
| # ttrace::addcleanup register user-defined cleanup procedures | |
| # ttrace::addentry adds one entry into the named trace store | |
| # ttrace::getentry returns the entry value from the named store | |
| # ttrace::delentry removes the entry from the named store | |
| # ttrace::getentries returns all entries from the named store | |
| # ttrace::preload register procedures to be preloaded always | |
| # | |
| # | |
| # Limitations: | |
| # | |
| # o. [namespace forget] is still not implemented | |
| # o. [namespace origin cmd] breaks if cmd is not already defined | |
| # | |
| # I left this deliberately. I didn't want to override the [namespace] | |
| # command in order to avoid potential slowdown. | |
| # | |
| namespace eval ttrace { | |
| # Setup some compatibility wrappers | |
| if {[info commands nsv_set] != ""} { | |
| variable tvers 0 | |
| variable mutex ns_mutex | |
| variable elock [$mutex create traceepochmutex] | |
| # Import the underlying API; faster than recomputing | |
| interp alias {} [namespace current]::_array {} nsv_array | |
| interp alias {} [namespace current]::_incr {} nsv_incr | |
| interp alias {} [namespace current]::_lappend {} nsv_lappend | |
| interp alias {} [namespace current]::_names {} nsv_names | |
| interp alias {} [namespace current]::_set {} nsv_set | |
| interp alias {} [namespace current]::_unset {} nsv_unset | |
| } elseif {![catch { | |
| variable tvers [package require Thread] | |
| }]} { | |
| variable mutex thread::mutex | |
| variable elock [$mutex create] | |
| # Import the underlying API; faster than recomputing | |
| interp alias {} [namespace current]::_array {} tsv::array | |
| interp alias {} [namespace current]::_incr {} tsv::incr | |
| interp alias {} [namespace current]::_lappend {} tsv::lappend | |
| interp alias {} [namespace current]::_names {} tsv::names | |
| interp alias {} [namespace current]::_set {} tsv::set | |
| interp alias {} [namespace current]::_unset {} tsv::unset | |
| } else { | |
| error "requires NaviServer/AOLserver or Tcl threading extension" | |
| } | |
| # Keep in sync with the Thread package | |
| package provide Ttrace 2.8.9 | |
| # Package variables | |
| variable resolvers "" ; # List of registered resolvers | |
| variable tracers "" ; # List of registered cmd tracers | |
| variable scripts "" ; # List of registered script makers | |
| variable enables "" ; # List of trace-enable callbacks | |
| variable disables "" ; # List of trace-disable callbacks | |
| variable preloads "" ; # List of procedure names to preload | |
| variable enabled 0 ; # True if trace is enabled | |
| variable config ; # Array with config options | |
| variable epoch -1 ; # The initialization epoch | |
| variable cleancnt 0 ; # Counter of registered cleaners | |
| # Package private namespaces | |
| namespace eval resolve "" ; # Commands for resolving commands | |
| namespace eval trace "" ; # Commands registered for tracing | |
| namespace eval enable "" ; # Commands invoked at trace enable | |
| namespace eval disable "" ; # Commands invoked at trace disable | |
| namespace eval script "" ; # Commands for generating scripts | |
| # Exported commands | |
| namespace export unknown | |
| # Initialize ttrace shared state | |
| if {[_array exists ttrace] == 0} { | |
| _set ttrace lastepoch $epoch | |
| _set ttrace epochlist "" | |
| } | |
| # Initially, allow creation of epochs | |
| set config(-doepochs) 1 | |
| proc eval {cmd args} { | |
| enable | |
| set code [catch {uplevel 1 [concat $cmd $args]} result] | |
| disable | |
| if {$code == 0} { | |
| if {[llength [info commands ns_ictl]]} { | |
| ns_ictl save [getscript] | |
| } else { | |
| thread::broadcast { | |
| package require Ttrace | |
| ttrace::update | |
| } | |
| } | |
| } | |
| return -code $code \ | |
| -errorinfo $::errorInfo -errorcode $::errorCode $result | |
| } | |
| proc config {args} { | |
| variable config | |
| if {[llength $args] == 0} { | |
| array get config | |
| } elseif {[llength $args] == 1} { | |
| set opt [lindex $args 0] | |
| set config($opt) | |
| } else { | |
| set opt [lindex $args 0] | |
| set val [lindex $args 1] | |
| set config($opt) $val | |
| } | |
| } | |
| proc enable {} { | |
| variable config | |
| variable tracers | |
| variable enables | |
| variable enabled | |
| incr enabled 1 | |
| if {$enabled > 1} { | |
| return | |
| } | |
| if {$config(-doepochs) != 0} { | |
| variable epoch [_newepoch] | |
| } | |
| set nsp [namespace current] | |
| foreach enabler $enables { | |
| enable::_$enabler | |
| } | |
| foreach trace $tracers { | |
| if {[info commands $trace] != ""} { | |
| trace add execution $trace leave ${nsp}::trace::_$trace | |
| } | |
| } | |
| } | |
| proc disable {} { | |
| variable enabled | |
| variable tracers | |
| variable disables | |
| incr enabled -1 | |
| if {$enabled > 0} { | |
| return | |
| } | |
| set nsp [namespace current] | |
| foreach disabler $disables { | |
| disable::_$disabler | |
| } | |
| foreach trace $tracers { | |
| if {[info commands $trace] != ""} { | |
| trace remove execution $trace leave ${nsp}::trace::_$trace | |
| } | |
| } | |
| } | |
| proc isenabled {} { | |
| variable enabled | |
| expr {$enabled > 0} | |
| } | |
| proc update {{from -1}} { | |
| if {$from < 0} { | |
| variable epoch [_set ttrace lastepoch] | |
| } else { | |
| if {[lsearch [_set ttrace epochlist] $from] < 0} { | |
| error "no such epoch: $from" | |
| } | |
| variable epoch $from | |
| } | |
| uplevel 1 [getscript] | |
| } | |
| proc getscript {} { | |
| variable preloads | |
| variable epoch | |
| variable scripts | |
| append script [_serializensp] \n | |
| append script "::namespace eval [namespace current] {" \n | |
| append script "::namespace export unknown" \n | |
| append script "_useepoch $epoch" \n | |
| append script "}" \n | |
| foreach cmd $preloads { | |
| append script [_serializeproc $cmd] \n | |
| } | |
| foreach maker $scripts { | |
| append script [script::_$maker] | |
| } | |
| return $script | |
| } | |
| proc cleanup {args} { | |
| foreach cmd [info commands resolve::cleaner_*] { | |
| uplevel 1 $cmd $args | |
| } | |
| } | |
| proc preload {cmd} { | |
| variable preloads | |
| if {[lsearch $preloads $cmd] < 0} { | |
| lappend preloads $cmd | |
| } | |
| } | |
| proc atenable {cmd arglist body} { | |
| variable enables | |
| if {[lsearch $enables $cmd] < 0} { | |
| lappend enables $cmd | |
| set cmd [namespace current]::enable::_$cmd | |
| proc $cmd $arglist $body | |
| return $cmd | |
| } | |
| } | |
| proc atdisable {cmd arglist body} { | |
| variable disables | |
| if {[lsearch $disables $cmd] < 0} { | |
| lappend disables $cmd | |
| set cmd [namespace current]::disable::_$cmd | |
| proc $cmd $arglist $body | |
| return $cmd | |
| } | |
| } | |
| proc addtrace {cmd arglist body} { | |
| variable tracers | |
| if {[lsearch $tracers $cmd] < 0} { | |
| lappend tracers $cmd | |
| set tracer [namespace current]::trace::_$cmd | |
| proc $tracer $arglist $body | |
| if {[isenabled]} { | |
| trace add execution $cmd leave $tracer | |
| } | |
| return $tracer | |
| } | |
| } | |
| proc addscript {cmd body} { | |
| variable scripts | |
| if {[lsearch $scripts $cmd] < 0} { | |
| lappend scripts $cmd | |
| set cmd [namespace current]::script::_$cmd | |
| proc $cmd args $body | |
| return $cmd | |
| } | |
| } | |
| proc addresolver {cmd arglist body} { | |
| variable resolvers | |
| if {[lsearch $resolvers $cmd] < 0} { | |
| lappend resolvers $cmd | |
| set cmd [namespace current]::resolve::$cmd | |
| proc $cmd $arglist $body | |
| return $cmd | |
| } | |
| } | |
| proc addcleanup {body} { | |
| variable cleancnt | |
| set cmd [namespace current]::resolve::cleaner_[incr cleancnt] | |
| proc $cmd args $body | |
| return $cmd | |
| } | |
| proc addentry {cmd var val} { | |
| variable epoch | |
| _set ${epoch}-$cmd $var $val | |
| } | |
| proc delentry {cmd var} { | |
| variable epoch | |
| set ei $::errorInfo | |
| set ec $::errorCode | |
| catch {_unset ${epoch}-$cmd $var} | |
| set ::errorInfo $ei | |
| set ::errorCode $ec | |
| } | |
| proc getentry {cmd var} { | |
| variable epoch | |
| set ei $::errorInfo | |
| set ec $::errorCode | |
| if {[catch {_set ${epoch}-$cmd $var} val]} { | |
| set ::errorInfo $ei | |
| set ::errorCode $ec | |
| set val "" | |
| } | |
| return $val | |
| } | |
| proc getentries {cmd {pattern *}} { | |
| variable epoch | |
| _array names ${epoch}-$cmd $pattern | |
| } | |
| proc unknown {args} { | |
| set cmd [lindex $args 0] | |
| if {[uplevel 1 ttrace::_resolve [list $cmd]]} { | |
| set c [catch {uplevel 1 $cmd [lrange $args 1 end]} r] | |
| } else { | |
| set c [catch {uplevel 1 ::tcl::unknown $args} r] | |
| } | |
| return -code $c -errorcode $::errorCode -errorinfo $::errorInfo $r | |
| } | |
| proc _resolve {cmd} { | |
| variable resolvers | |
| foreach resolver $resolvers { | |
| if {[uplevel 1 [info comm resolve::$resolver] [list $cmd]]} { | |
| return 1 | |
| } | |
| } | |
| return 0 | |
| } | |
| proc _getthread {} { | |
| if {[info commands ns_thread] == ""} { | |
| thread::id | |
| } else { | |
| ns_thread getid | |
| } | |
| } | |
| proc _getthreads {} { | |
| if {[info commands ns_thread] == ""} { | |
| return [thread::names] | |
| } else { | |
| foreach entry [ns_info threads] { | |
| lappend threads [lindex $entry 2] | |
| } | |
| return $threads | |
| } | |
| } | |
| proc _newepoch {} { | |
| variable elock | |
| variable mutex | |
| $mutex lock $elock | |
| set old [_set ttrace lastepoch] | |
| set new [_incr ttrace lastepoch] | |
| _lappend ttrace $new [_getthread] | |
| if {$old >= 0} { | |
| _copyepoch $old $new | |
| _delepochs | |
| } | |
| _lappend ttrace epochlist $new | |
| $mutex unlock $elock | |
| return $new | |
| } | |
| proc _copyepoch {old new} { | |
| foreach var [_names $old-*] { | |
| set cmd [lindex [split $var -] 1] | |
| _array reset $new-$cmd [_array get $var] | |
| } | |
| } | |
| proc _delepochs {} { | |
| set tlist [_getthreads] | |
| set elist "" | |
| foreach epoch [_set ttrace epochlist] { | |
| if {[_dropepoch $epoch $tlist] == 0} { | |
| lappend elist $epoch | |
| } else { | |
| _unset ttrace $epoch | |
| } | |
| } | |
| _set ttrace epochlist $elist | |
| } | |
| proc _dropepoch {epoch threads} { | |
| set self [_getthread] | |
| foreach tid [_set ttrace $epoch] { | |
| if {$tid != $self && [lsearch $threads $tid] >= 0} { | |
| lappend alive $tid | |
| } | |
| } | |
| if {[info exists alive]} { | |
| _set ttrace $epoch $alive | |
| return 0 | |
| } else { | |
| foreach var [_names $epoch-*] { | |
| _unset $var | |
| } | |
| return 1 | |
| } | |
| } | |
| proc _useepoch {epoch} { | |
| if {$epoch >= 0} { | |
| set tid [_getthread] | |
| if {[lsearch [_set ttrace $epoch] $tid] == -1} { | |
| _lappend ttrace $epoch $tid | |
| } | |
| } | |
| } | |
| proc _serializeproc {cmd} { | |
| set dargs [info args $cmd] | |
| set pbody [info body $cmd] | |
| set pargs "" | |
| foreach arg $dargs { | |
| if {![info default $cmd $arg def]} { | |
| lappend pargs $arg | |
| } else { | |
| lappend pargs [list $arg $def] | |
| } | |
| } | |
| set nsp [namespace qual $cmd] | |
| if {$nsp == ""} { | |
| set nsp "::" | |
| } | |
| append res [list ::namespace eval $nsp] " {" \n | |
| append res [list ::proc [namespace tail $cmd] $pargs $pbody] \n | |
| append res "}" \n | |
| } | |
| proc _serializensp {{nsp ""} {result _}} { | |
| upvar $result res | |
| if {$nsp == ""} { | |
| set nsp [namespace current] | |
| } | |
| append res [list ::namespace eval $nsp] " {" \n | |
| foreach var [info vars ${nsp}::*] { | |
| set vname [namespace tail $var] | |
| if {[array exists $var] == 0} { | |
| append res [list ::variable $vname [set $var]] \n | |
| } else { | |
| append res [list ::variable $vname] \n | |
| append res [list ::array set $vname [array get $var]] \n | |
| } | |
| } | |
| foreach cmd [info procs ${nsp}::*] { | |
| append res [_serializeproc $cmd] \n | |
| } | |
| append res "}" \n | |
| foreach nn [namespace children $nsp] { | |
| _serializensp $nn res | |
| } | |
| return $res | |
| } | |
| } | |
| # | |
| # The code below is ment to be run once during the application start. It | |
| # provides implementation of tracing callbacks for some Tcl commands. Users | |
| # can supply their own tracer implementations on-the-fly. | |
| # | |
| # The code below will create traces for the following Tcl commands: | |
| # "namespace", "variable", "load", "proc" and "rename" | |
| # | |
| # Also, the Tcl object extension XOTcl 1.1.0 is handled and all XOTcl related | |
| # things, like classes and objects are traced (many thanks to Gustaf Neumann | |
| # from XOTcl for his kind help and support). | |
| # | |
| eval { | |
| # | |
| # Register the "load" trace. This will create the following key/value pair | |
| # in the "load" store: | |
| # | |
| # --- key ---- --- value --- | |
| # <path_of_loaded_image> <name_of_the_init_proc> | |
| # | |
| # We normally need only the name_of_the_init_proc for being able to load | |
| # the package in other interpreters, but we store the path to the image | |
| # file as well. | |
| # | |
| ttrace::addtrace load {cmdline code args} { | |
| if {$code != 0} { | |
| return | |
| } | |
| set image [lindex $cmdline 1] | |
| set initp [lindex $cmdline 2] | |
| if {$initp == ""} { | |
| foreach pkg [info loaded] { | |
| if {[lindex $pkg 0] == $image} { | |
| set initp [lindex $pkg 1] | |
| } | |
| } | |
| } | |
| ttrace::addentry load $image $initp | |
| } | |
| ttrace::addscript load { | |
| append res "\n" | |
| foreach entry [ttrace::getentries load] { | |
| set initp [ttrace::getentry load $entry] | |
| append res "::load {} $initp" \n | |
| } | |
| return $res | |
| } | |
| # | |
| # Register the "namespace" trace. This will create the following key/value | |
| # entry in "namespace" store: | |
| # | |
| # --- key ---- --- value --- | |
| # ::fully::qualified::namespace 1 | |
| # | |
| # It will also fill the "proc" store for procedures and commands imported | |
| # in this namespace with following: | |
| # | |
| # --- key ---- --- value --- | |
| # ::fully::qualified::proc [list <ns> "" ""] | |
| # | |
| # The <ns> is the name of the namespace where the command or procedure is | |
| # imported from. | |
| # | |
| ttrace::addtrace namespace {cmdline code args} { | |
| if {$code != 0} { | |
| return | |
| } | |
| set nop [lindex $cmdline 1] | |
| set cns [uplevel 1 namespace current] | |
| if {$cns == "::"} { | |
| set cns "" | |
| } | |
| switch -glob $nop { | |
| eva* { | |
| set nsp [lindex $cmdline 2] | |
| if {![string match "::*" $nsp]} { | |
| set nsp ${cns}::$nsp | |
| } | |
| ttrace::addentry namespace $nsp 1 | |
| } | |
| imp* { | |
| # - parse import arguments (skip opt "-force") | |
| set opts [lrange $cmdline 2 end] | |
| if {[string match "-fo*" [lindex $opts 0]]} { | |
| set opts [lrange $cmdline 3 end] | |
| } | |
| # - register all imported procs and commands | |
| foreach opt $opts { | |
| if {![string match "::*" [::namespace qual $opt]]} { | |
| set opt ${cns}::$opt | |
| } | |
| # - first import procs | |
| foreach entry [ttrace::getentries proc $opt] { | |
| set cmd ${cns}::[::namespace tail $entry] | |
| set nsp [::namespace qual $entry] | |
| set done($cmd) 1 | |
| set entry [list 0 $nsp "" ""] | |
| ttrace::addentry proc $cmd $entry | |
| } | |
| # - then import commands | |
| foreach entry [info commands $opt] { | |
| set cmd ${cns}::[::namespace tail $entry] | |
| set nsp [::namespace qual $entry] | |
| if {[info exists done($cmd)] == 0} { | |
| set entry [list 0 $nsp "" ""] | |
| ttrace::addentry proc $cmd $entry | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ttrace::addscript namespace { | |
| append res \n | |
| foreach entry [ttrace::getentries namespace] { | |
| append res "::namespace eval $entry {}" \n | |
| } | |
| return $res | |
| } | |
| # | |
| # Register the "variable" trace. This will create the following key/value | |
| # entry in the "variable" store: | |
| # | |
| # --- key ---- --- value --- | |
| # ::fully::qualified::variable 1 | |
| # | |
| # The variable value itself is ignored at the time of | |
| # trace/collection. Instead, we take the real value at the time of script | |
| # generation. | |
| # | |
| ttrace::addtrace variable {cmdline code args} { | |
| if {$code != 0} { | |
| return | |
| } | |
| set opts [lrange $cmdline 1 end] | |
| if {[llength $opts]} { | |
| set cns [uplevel 1 namespace current] | |
| if {$cns == "::"} { | |
| set cns "" | |
| } | |
| foreach {var val} $opts { | |
| if {![string match "::*" $var]} { | |
| set var ${cns}::$var | |
| } | |
| ttrace::addentry variable $var 1 | |
| } | |
| } | |
| } | |
| ttrace::addscript variable { | |
| append res \n | |
| foreach entry [ttrace::getentries variable] { | |
| set cns [namespace qual $entry] | |
| set var [namespace tail $entry] | |
| append res "::namespace eval $cns {" \n | |
| append res "::variable $var" | |
| if {[array exists $entry]} { | |
| append res "\n::array set $var [list [array get $entry]]" \n | |
| } elseif {[info exists $entry]} { | |
| append res " [list [set $entry]]" \n | |
| } else { | |
| append res \n | |
| } | |
| append res "}" \n | |
| } | |
| return $res | |
| } | |
| # | |
| # Register the "rename" trace. It will create the following key/value pair | |
| # in "rename" store: | |
| # | |
| # --- key ---- --- value --- | |
| # ::fully::qualified::old ::fully::qualified::new | |
| # | |
| # The "new" value may be empty, for commands that have been deleted. In | |
| # such cases we also remove any traced procedure definitions. | |
| # | |
| ttrace::addtrace rename {cmdline code args} { | |
| if {$code != 0} { | |
| return | |
| } | |
| set cns [uplevel 1 namespace current] | |
| if {$cns == "::"} { | |
| set cns "" | |
| } | |
| set old [lindex $cmdline 1] | |
| if {![string match "::*" $old]} { | |
| set old ${cns}::$old | |
| } | |
| set new [lindex $cmdline 2] | |
| if {$new != ""} { | |
| if {![string match "::*" $new]} { | |
| set new ${cns}::$new | |
| } | |
| ttrace::addentry rename $old $new | |
| } else { | |
| ttrace::delentry proc $old | |
| } | |
| } | |
| ttrace::addscript rename { | |
| append res \n | |
| foreach old [ttrace::getentries rename] { | |
| set new [ttrace::getentry rename $old] | |
| append res "::rename $old {$new}" \n | |
| } | |
| return $res | |
| } | |
| # | |
| # Register the "proc" trace. This will create the following key/value pair | |
| # in the "proc" store: | |
| # | |
| # --- key ---- --- value --- | |
| # ::fully::qualified::proc [list <epoch> <ns> <arglist> <body>] | |
| # | |
| # The <epoch> chages anytime one (re)defines a proc. The <ns> is the | |
| # namespace where the command was imported from. If empty, the <arglist> | |
| # and <body> will hold the actual procedure definition. See the | |
| # "namespace" tracer implementation also. | |
| # | |
| ttrace::addtrace proc {cmdline code args} { | |
| if {$code != 0} { | |
| return | |
| } | |
| set cns [uplevel 1 namespace current] | |
| if {$cns == "::"} { | |
| set cns "" | |
| } | |
| set cmd [lindex $cmdline 1] | |
| if {![string match "::*" $cmd]} { | |
| set cmd ${cns}::$cmd | |
| } | |
| set dargs [info args $cmd] | |
| set pbody [info body $cmd] | |
| set pargs "" | |
| foreach arg $dargs { | |
| if {![info default $cmd $arg def]} { | |
| lappend pargs $arg | |
| } else { | |
| lappend pargs [list $arg $def] | |
| } | |
| } | |
| set pdef [ttrace::getentry proc $cmd] | |
| if {$pdef == ""} { | |
| set epoch -1 ; # never traced before | |
| } else { | |
| set epoch [lindex $pdef 0] | |
| } | |
| ttrace::addentry proc $cmd [list [incr epoch] "" $pargs $pbody] | |
| } | |
| ttrace::addscript proc { | |
| return { | |
| if {[info command ::tcl::unknown] == ""} { | |
| rename ::unknown ::tcl::unknown | |
| namespace import -force ::ttrace::unknown | |
| } | |
| if {[info command ::tcl::info] == ""} { | |
| rename ::info ::tcl::info | |
| } | |
| proc ::info args { | |
| set cmd [lindex $args 0] | |
| set hit [lsearch -glob {commands procs args default body} $cmd*] | |
| if {$hit > 1} { | |
| if {[catch {uplevel 1 ::tcl::info $args}]} { | |
| uplevel 1 ttrace::_resolve [list [lindex $args 1]] | |
| } | |
| return [uplevel 1 ::tcl::info $args] | |
| } | |
| if {$hit == -1} { | |
| return [uplevel 1 ::tcl::info $args] | |
| } | |
| set cns [uplevel 1 namespace current] | |
| if {$cns == "::"} { | |
| set cns "" | |
| } | |
| set pat [lindex $args 1] | |
| if {![string match "::*" $pat]} { | |
| set pat ${cns}::$pat | |
| } | |
| set fns [ttrace::getentries proc $pat] | |
| if {[string match $cmd* commands]} { | |
| set fns [concat $fns [ttrace::getentries xotcl $pat]] | |
| } | |
| foreach entry $fns { | |
| if {$cns != [namespace qual $entry]} { | |
| set lazy($entry) 1 | |
| } else { | |
| set lazy([namespace tail $entry]) 1 | |
| } | |
| } | |
| foreach entry [uplevel 1 ::tcl::info $args] { | |
| set lazy($entry) 1 | |
| } | |
| array names lazy | |
| } | |
| } | |
| } | |
| # | |
| # Register procedure resolver. This will try to resolve the command in the | |
| # current namespace first, and if not found, in global namespace. It also | |
| # handles commands imported from other namespaces. | |
| # | |
| ttrace::addresolver resolveprocs {cmd {export 0}} { | |
| set cns [uplevel 1 namespace current] | |
| set name [namespace tail $cmd] | |
| if {$cns == "::"} { | |
| set cns "" | |
| } | |
| if {![string match "::*" $cmd]} { | |
| set ncmd ${cns}::$cmd | |
| set gcmd ::$cmd | |
| } else { | |
| set ncmd $cmd | |
| set gcmd $cmd | |
| } | |
| set pdef [ttrace::getentry proc $ncmd] | |
| if {$pdef == ""} { | |
| set pdef [ttrace::getentry proc $gcmd] | |
| if {$pdef == ""} { | |
| return 0 | |
| } | |
| set cmd $gcmd | |
| } else { | |
| set cmd $ncmd | |
| } | |
| set epoch [lindex $pdef 0] | |
| set pnsp [lindex $pdef 1] | |
| if {$pnsp != ""} { | |
| set nsp [namespace qual $cmd] | |
| if {$nsp == ""} { | |
| set nsp :: | |
| } | |
| set cmd ${pnsp}::$name | |
| if {[resolveprocs $cmd 1] == 0 && [info commands $cmd] == ""} { | |
| return 0 | |
| } | |
| namespace eval $nsp "namespace import -force $cmd" | |
| } else { | |
| uplevel 0 [list ::proc $cmd [lindex $pdef 2] [lindex $pdef 3]] | |
| if {$export} { | |
| set nsp [namespace qual $cmd] | |
| if {$nsp == ""} { | |
| set nsp :: | |
| } | |
| namespace eval $nsp "namespace export $name" | |
| } | |
| } | |
| variable resolveproc | |
| set resolveproc($cmd) $epoch | |
| return 1 | |
| } | |
| # | |
| # For XOTcl, the entire item introspection/tracing is delegated to XOTcl | |
| # itself. The xotcl store is filled with this: | |
| # | |
| # --- key ---- --- value --- | |
| # ::fully::qualified::item <body> | |
| # | |
| # The <body> is the script used to generate the entire item (class, | |
| # object). Note that we do not fill in this during code tracing. It is | |
| # done during the script generation. In this step, only the placeholder is | |
| # set. | |
| # | |
| # NOTE: we assume all XOTcl commands are imported in global namespace | |
| # | |
| ttrace::atenable XOTclEnabler {args} { | |
| if {[info commands ::xotcl::Class] == ""} { | |
| return | |
| } | |
| if {[info commands ::xotcl::_creator] == ""} { | |
| ::xotcl::Class create ::xotcl::_creator -instproc create {args} { | |
| set result [next] | |
| if {![string match ::xotcl::_* $result]} { | |
| ttrace::addentry xotcl $result "" | |
| } | |
| return $result | |
| } | |
| } | |
| ::xotcl::Class instmixin ::xotcl::_creator | |
| } | |
| ttrace::atdisable XOTclDisabler {args} { | |
| if { [info commands ::xotcl::Class] == "" | |
| || [info commands ::xotcl::_creator] == ""} { | |
| return | |
| } | |
| ::xotcl::Class instmixin "" | |
| ::xotcl::_creator destroy | |
| } | |
| set resolver [ttrace::addresolver resolveclasses {classname} { | |
| set cns [uplevel 1 namespace current] | |
| set script [ttrace::getentry xotcl $classname] | |
| if {$script == ""} { | |
| set name [namespace tail $classname] | |
| if {$cns == "::"} { | |
| set script [ttrace::getentry xotcl ::$name] | |
| } else { | |
| set script [ttrace::getentry xotcl ${cns}::$name] | |
| if {$script == ""} { | |
| set script [ttrace::getentry xotcl ::$name] | |
| } | |
| } | |
| if {$script == ""} { | |
| return 0 | |
| } | |
| } | |
| uplevel 1 [list namespace eval $cns $script] | |
| return 1 | |
| }] | |
| ttrace::addscript xotcl [subst -nocommands { | |
| if {![catch {Serializer new} ss]} { | |
| foreach entry [ttrace::getentries xotcl] { | |
| if {[ttrace::getentry xotcl \$entry] == ""} { | |
| ttrace::addentry xotcl \$entry [\$ss serialize \$entry] | |
| } | |
| } | |
| \$ss destroy | |
| return {::xotcl::Class proc __unknown name {$resolver \$name}} | |
| } | |
| }] | |
| # | |
| # Register callback to be called on cleanup. This will trash lazily loaded | |
| # procs which have changed since. | |
| # | |
| ttrace::addcleanup { | |
| variable resolveproc | |
| foreach cmd [array names resolveproc] { | |
| set def [ttrace::getentry proc $cmd] | |
| if {$def != ""} { | |
| set new [lindex $def 0] | |
| set old $resolveproc($cmd) | |
| if {[info command $cmd] != "" && $new != $old} { | |
| catch {rename $cmd ""} | |
| } | |
| } | |
| } | |
| } | |
| } | |
| # EOF | |
| return | |
| # Local Variables: | |
| # mode: tcl | |
| # fill-column: 78 | |
| # tab-width: 8 | |
| # indent-tabs-mode: nil | |
| # End: | |
Xet Storage Details
- Size:
- 29.6 kB
- Xet hash:
- ef8286b391bd5310717c987f3585b614e1e1449b97e180fdb9ac37f7901bb471
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.