Files
sics/tcl/inherit.tcl
2000-02-07 10:38:55 +00:00

298 lines
7.4 KiB
Tcl

#----------------------------------------------------------------------
# Method resolution and caching
#
proc otPrInherits {} {
global _obTcl_Classes
foreach i [array names _obTcl_Classes]\
{puts "$i inherits from: [$i inherit]"}
}
proc otInherit { class args } {
global _obTcl_Inherits
if ![string compare "" $args] {
return [set _obTcl_Inherits($class)]
}
if { [string compare "Base" $class] && [lsearch $args "Base"] == -1 } {
set args [concat $args "Base"]
}
if [info exists _obTcl_Inherits($class)] {
#
# This class is not new, invalidate caches
#
otInvalidateCaches 0 $class [otClassInfoCached ${class}]
} else {
set _obTcl_Inherits($class) {}
}
set _obTcl_Inherits($class) $args
}
proc otInvalidateCaches { level class methods } {
global _obTcl_CacheStop
foreach i $methods {
if ![string compare "unknown" $i] { set i "*" }
set _obTcl_CacheStop($i) 1
}
if [array exists _obTcl_CacheStop] { otDoInvalidate }
}
# There is a catch on rename and unset since current build of tmp
# does not guarantee that each element is unique.
proc otDoInvalidate {} {
global _obTcl_CacheStop _obTcl_Cached
if ![array exists _obTcl_Cached] {
unset _obTcl_CacheStop
return
}
if [info exists _obTcl_CacheStop(*)] {
set stoplist "*"
} else {
set stoplist [array names _obTcl_CacheStop]
}
foreach i $stoplist {
set tmp [array names _obTcl_Cached *::$i]
eval lappend tmp [array names _obTcl_Cached *::${i}_next]
foreach k $tmp {
catch {
rename $k {}
unset _obTcl_Cached($k)
}
}
}
if ![array size _obTcl_Cached] {
unset _obTcl_Cached
}
unset _obTcl_CacheStop
}
if ![string compare "" [info procs otUnknown]] {
rename unknown otUnknown
}
proc otResolve { class func } {
return [otGetFunc 0 $class $func]
}
#----------------------------------------------------------------------
#
# `unknown' and `next' both create cache methods.
#
#----------------------------------------------------------------------
#
# unknown -
# A missing function was found. See if it can be resolved
# from inheritance.
#
# If function name does not follow the *::* pattern, call the normal
# unknown handler.
#
# Umethod is for use by the "unknown" method. If the method is named
# `unknown' it will have $method set to $Umethod (the invokers method
# name).
#
setIfNew _obTcl_unknBarred() ""
proc unknown args {
global _obTcl_unknBarred
# Resolve inherited function calls
#
set name [lindex $args 0]
if [string match *::* $name] {
set tmp [split $name :]
set class [lindex $tmp 0]
set func [join [lrange $tmp 2 end] :]
set flist [otGetFunc 0 $class $func]
if ![string compare "" $flist] {
if [info exists _obTcl_unknBarred($name)] { return -code error }
set flist [otGetFunc 0 $class "unknown"]
}
if [string compare "" $flist] {
proc $name args "otGetSelf
set Umethod $func
eval [lindex $flist 0] \$args"
} else {
proc $name args "
return -code error\
-errorinfo \"Undefined method '$func' invoked\" \
\"Undefined method '$func' invoked\"
"
}
global _obTcl_Cached
set _obTcl_Cached(${class}::$func) $class
# Code below borrowed from init.tcl (tcl7.4)
#
global errorCode errorInfo
set code [catch {uplevel $args} msg]
if { $code == 1 } {
#
# Strip the last five lines off the error stack (they're
# from the "uplevel" command).
#
set new [split $errorInfo \n]
set new [join [lrange $new 0 [expr [llength $new] - 6]] \n]
return -code error -errorcode $errorCode \
-errorinfo $new $msg
} else {
return -code $code $msg
}
} else {
uplevel [concat otUnknown $args]
}
}
setIfNew _obTcl_Cnt 0
# 6/11/95 Added _obTcl_nextRet to allow propagation of return-values
# from `next' calls. I.e doing `return [next $args]' will
# be meaningful. It is only in simple cases that the return
# value is shure to make sense. With multiple inheritance
# it may be impossible to rely on!
#
# NOTE: This support is experimental and likely to be removed!!!
#
# Improved for lower overhead with big args-lists
# NOTE: It is understood that `args' is initialized from the `next'
# procedure.
#
proc otChkCall { cmd } {
global _obTcl_Trace _obTcl_Cnt _obTcl_nextRet
if ![info exists _obTcl_Trace($cmd)] {
set _obTcl_Trace($cmd) 1
catch {uplevel 1 "uplevel 1 \"$cmd \$args\""} _obTcl_nextRet
}
return $_obTcl_nextRet
}
# otNextPrepare is really just a part of proc `next' below.
#
proc otNextPrepare {} {
uplevel 1 {
set all [otGetNextFunc $class $method]
foreach i $all {
# Note: args is the literal _name_ of var to use, hence
# no $-sign!
append tmp "otChkCall $i\n"
}
if [info exists tmp] {
proc $class::${method}_next args $tmp
} else {
proc $class::${method}_next args return
}
set _obTcl_Cached(${class}::${method}_next) $class
}
}
# next -
# Invoke next shadowed method. Protect against multiple invocation.
# Multiple invocation would occur when several inherited classes inherit
# a common superclass.
#
# Note: I use `info exists' on _obTcl_Cached, rater than `info procs' on
# the corresponding procedure, since checking for a variable seems to be
# about three times faster (Tcl7.4).
#
proc next args {
global _obTcl_Cnt _obTcl_Cached _obTcl_nextRet
# otGetSelf inlined and modified
upvar 1 self self method method class class
if { $_obTcl_Cnt == 0 } {
set _obTcl_nextRet ""
}
if ![info exists _obTcl_Cached(${class}::${method}_next)] {
otNextPrepare
}
incr _obTcl_Cnt 1
set ret [catch {uplevel 1 {${class}::${method}_next} $args} val]
incr _obTcl_Cnt -1
if { $_obTcl_Cnt == 0 } {
global _obTcl_Trace
catch {unset _obTcl_Trace}
}
if { $ret != 0 } {
return -code error \
-errorinfo "$self: $val" "$self: $val"
} else {
return $val
}
}
# otGetNextFunc -
# Get a method by searching inherited classes, skipping the local
# class.
#
proc otGetNextFunc { class func } {
global _obTcl_Inherits
set all ""
foreach i [set _obTcl_Inherits($class)] {
foreach k [otGetFunc 0 $i $func] {
lappendUniq all $k
}
}
return $all
}
# otGetFunc -
# Locate a method by searching the inheritance tree.
# Cyclic inheritance is discovered and reported. A list of all
# found methods is returned, with the closest first in the list.
# Cache-methods are skipped, and will hence not figure in the list.
#
# 16/12/95 Added support for autoloading of classes.
#
proc otGetFunc { depth class func } {
global _obTcl_Inherits _obTcl_Cached _obTcl_NoClasses _obTcl_Classes
if { $depth > $_obTcl_NoClasses } {
otGetFuncErr $depth $class $func
return ""
}
incr depth
set all ""
if ![info exists _obTcl_Classes($class)] {
if ![auto_load $class] {
otGetFuncMissingClass $depth $class $func
return ""
}
}
if { [string compare "" [info procs $class::$func]] &&
![info exists _obTcl_Cached(${class}::$func)] } {
return "$class::$func"
}
foreach i [set _obTcl_Inherits($class)] {
set ret [otGetFunc $depth $i $func]
if [string compare "" $ret] {
foreach i $ret {
lappendUniq all $i
}
}
}
return $all
}
# Note: Real error handling should be added here!
# Specifically we need to report which object triggered the error.
proc otGetFuncErr { depth class func } {
puts stderr "GetFunc: depth=$depth, circular dependency!?"
puts stderr " class=$class func=$func"
}
proc otGetFuncMissingClass { depth class func } {
puts stderr "GetFunc: Unable to inherit from $class"
puts stderr " $class not defined (and auto load failed)"
puts stderr " Occurred while looking for $class::$func"
}