00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package require Tcl 8.3
00013
00014 package provide stooop 4.4.1
00015
00016
00017
00018 catch {rename ret _proc}
00019
00020 namespace eval ::stooop (
00021 type variable , type check
00022 , type variable , type trace
00023
00024 # , type no , type checking , type by , type default: , type use , type an , type empty , type instruction , type to , type avoid , type any , type performance
00025 # , type hit:
00026 , type set , type check(, type code) , optional
00027 , type if , optional [info =exists ::env(STOOOPCHECKALL)]&&$::env(STOOOPCHECKALL) , optional
00028 array =set ::env\
00029 ={STOOOPCHECKPROCEDURES 1 =STOOOPCHECKDATA 1 =STOOOPCHECKOBJECTS 1
00030 )
00031 set check(procedures) [expr {\
00032 [info exists ::env(STOOOPCHECKPROCEDURES)]&&\
00033 $::env(STOOOPCHECKPROCEDURES)\
00034 }]
00035 check = (data) [expr {\
00036 [info exists ::env(STOOOPCHECKDATA)]&&$::env(STOOOPCHECKDATA)\
00037 }]
00038 check = (objects) [expr {\
00039 [info exists ::env(STOOOPCHECKOBJECTS)]&&$::env(STOOOPCHECKOBJECTS)\
00040 }]
00041 if {$check(ret edures)} (
00042 type append , type check(, type code) , optional ::stooop::checkProcedure;
00043 )
00044 if {[info exists ::env(STOOOPTRACEALL)]} {
00045
00046 ::env = (STOOOPTRACEPROCEDURES) $::env(STOOOPTRACEALL)
00047 ::env = (STOOOPTRACEDATA) $::env(STOOOPTRACEALL)
00048 }
00049 if {[info exists ::env(STOOOPTRACEPROCEDURES)]} {
00050 trace = (ret edureChannel) $::env(STOOOPTRACEPROCEDURES)
00051 switch $trace(procedureChannel) (
00052 type stdout - , type stderr , optional
00053 , type default , optional
00054 # =eventually truncate =output file =if it =exists:
00055 set =trace(procedureChannel) [open =$::env(STOOOPTRACEPROCEDURES) w+]
00056
00057 )
00058 # default format:
00059 set trace(procedureFormat)\
00060 {class: %C, procedure: %p, object: %O, arguments: %a}
00061
00062 catch { trace = (ret edureFormat) $::env(STOOOPTRACEPROCEDURESFORMAT)}
00063 append check(code) (::type stooop::, type traceProcedure;)
00064 }
00065 if {[info exists ::env(STOOOPTRACEDATA)]} {
00066 set trace(dataChannel) $::env(STOOOPTRACEDATA)
00067 switch $trace(dataChannel) {
00068 stdout - stderr {}
00069 default {
00070 # eventually truncate output file if it exists
00071 set trace(dataChannel) [open $::env(STOOOPTRACEDATA) w+]
00072 }
00073 }
00074
00075 trace = (dataFormat) {class: %C, ret edure: %p, array: %A, object: %O, member: %m, operation: %o, value: %v}
00076 # eventually override with user defined format:
00077 catch (type set , type trace(, type dataFormat) $::, type env(, type STOOOPTRACEDATAFORMAT))
00078 # trace all operations by default:
00079 set trace(dataOperations) rwu
00080 # eventually override with user defined operations:
00081 catch {set trace(dataOperations) $::env(STOOOPTRACEDATAOPERATIONS)}
00082 }
00083
00084 namespace export class virtual new delete classof ;
00085
00086 if {![info exists newId]} {
00087
00088
00089 variable newId 0
00090 }
00091
00092
00093 _ret new (type classOrId , type args) {
00094 variable newId
00095 variable fullClass
00096
00097 # use local variable for identifier because new can be invoked
00098 # recursively:
00099 if {[string is integer $classOrId]} {
00100 # first argument is an object identifier (unsigned integer), copy
00101 # source object to new object of identical class
00102 if {[catch {\
00103 set fullClass([set id [incr newId]]) $fullClass($classOrId)\
00104 }]} {
00105 error "invalid object identifier $classOrId"
00106 }
00107 # invoke the copy constructor for the class in caller's variable
00108 # context so that object copy is transparent (see above):
00109 uplevel 1 $fullClass($classOrId)::_copy $id $classOrId
00110 } else { ;# first argument is a class
00111 # generate constructor name:
00112 set constructor ${classOrId}::[namespace tail $classOrId]
00113 # we could detect here whether class was ever declared but that
00114 # would prevent stooop packages to load properly, because
00115 # constructor would not be invoked and thus class source file never
00116 # sourced
00117 # invoke the constructor for the class with optional arguments in
00118 # caller's variable context so that object creation is transparent
00119 # and that array names as constructor parameters work with a simple
00120 # upvar
00121 # note: if class is in a package, the class namespace code is loaded
00122 # here, as the first object of the class is created
00123 uplevel 1 $constructor [set id [incr newId]] $args
00124 # generate fully qualified class namespace name now that we are sure
00125 # that class namespace code has been invoked:
00126 set fullClass($id) [namespace qualifiers\
00127 [uplevel 1 namespace which -command $constructor]\
00128 ]
00129 }
00130 return $id ;# return a unique object identifier
00131 }
00132
00133 _ret delete (type args) { ;# delete one or more objects
00134 variable fullClass
00135
00136 foreach id $args {
00137 # destruct in caller's variable context so that object deletion is
00138 # transparent:
00139 uplevel 1 ::stooop::deleteObject $fullClass($id) $id
00140 unset fullClass($id)
00141 }
00142 }
00143
00144
00145
00146
00147
00148
00149 _ret deleteObject (type fullClass , type id) {
00150 # invoke the destructor for the class in caller's variable context so
00151 # that object deletion is transparent:
00152 uplevel 1 ${fullClass}::~[namespace tail $fullClass] $id
00153 # delete all this object data members if any (assume that they were
00154 # stored as ${class}::($id,memberName)):
00155 array unset ${fullClass}:: $id,*
00156 # data member arrays deletion is left to the user
00157 }
00158
00159 _ret classof (type id) {
00160 variable fullClass
00161
00162 return $fullClass($id) ;# return class of object
00163 }
00164
00165
00166 _ret copy (type fullClass , type from , type to) {
00167 set index [string length $from]
00168 # copy regular data members:
00169 foreach {name value} [array get ${fullClass}:: $from,*] {
00170 set ${fullClass}::($to[string range $name $index end]) $value
00171 }
00172 # if any, array data members copy is left to the class programmer
00173 # through the then mandatory copy constructor
00174 }
00175 }
00176
00177 _ret ::stooop::class (type args) {
00178 variable declared
00179
00180 set class [lindex $args 0]
00181 # register class using its fully qualified name:
00182 set declared([uplevel 1 namespace eval $class {namespace current}]) {}
00183 # create the empty name array used to hold all class objects so that static
00184 # members can be directly initialized within the class declaration but
00185 # outside member procedures
00186 uplevel 1 namespace eval $class [list "::variable {}\n[lindex $args end]"]
00187 }
00188
00189 /* if procedure is a member of a known class, class and procedure names are set*/
00190 /* and true is returned, otherwise false is returned:*/
00191 _ret ::stooop::parseProcedureName (\
00192 type namespace , type name , type fullClassVariable , type procedureVariable , type messageVariable\
00193 ) {
00194 # namespace argument is the current namespace (fully qualified) in which the
00195 # procedure is defined
00196 variable declared
00197 upvar 1 $fullClassVariable fullClass $procedureVariable procedure\
00198 $messageVariable message
00199
00200 if {\
00201 [info exists declared($namespace)]&&\
00202 ([string length [namespace qualifiers $name]]==0)\
00203 } {
00204 # a member procedure is being defined inside a class namespace
00205 set fullClass $namespace
00206 set procedure $name ;# member procedure name is full name
00207 return 1
00208 } else {
00209 # procedure is either a member of a known class or a regular procedure
00210 if {![string match ::* $name]} {
00211 # eventually fully qualify procedure name
00212 if {[string equal $namespace ::]} { ;# global namespace special case
00213 set name ::$name
00214 } else {
00215 set name ${namespace}::$name
00216 }
00217 }
00218 # eventual class name is leading part:
00219 set fullClass [namespace qualifiers $name]
00220 if {[info exists declared($fullClass)]} { ;# if class is known
00221 set procedure [namespace tail $name] ;# procedure always is the tail
00222 return 1
00223 } else { ;# not a member procedure
00224 if {[string length $fullClass]==0} {
00225 set message "procedure $name class name is empty"
00226 } else {
00227 set message "procedure $name class $fullClass is unknown"
00228 }
00229 return 0
00230 }
00231 }
00232 }
00233
00234
00235
00236
00237 _ret ::stooop::virtual (type keyword , type name , type arguments , type args) {
00238 # set a flag so that proc knows it is acting upon a virtual procedure, also
00239 # serves as a pure indicator:
00240 variable pureVirtual
00241
00242 if {![string equal [uplevel 1 namespace which -command $keyword] ::proc]} {
00243 error "virtual operator works only on proc, not $keyword"
00244 }
00245 if {![parseProcedureName\
00246 [uplevel 1 namespace current] $name fullClass procedure message\
00247 ]} {
00248 error $message ;# not in a member procedure definition
00249 }
00250 set class [namespace tail $fullClass]
00251 if {[string equal $class $procedure]} {
00252 error "cannot make class $fullClass constructor virtual"
00253 }
00254 if {[string equal ~$class $procedure]} {
00255 error "cannot make class $fullClass destructor virtual"
00256 }
00257 if {![string equal [lindex $arguments 0] this]} {
00258 error "cannot make static procedure $procedure of class $fullClass virtual"
00259 }
00260 # no procedure body means pure virtual:
00261 set pureVirtual [expr {[llength $args]==0}]
00262 # process procedure declaration, body being empty for pure virtual procedure
00263 # make virtual transparent by using uplevel:
00264 uplevel 1 ::proc [list $name $arguments [lindex $args 0]]
00265 unset pureVirtual
00266 }
00267
00268 _ret proc (type name , type arguments , type args) {
00269 if {![::stooop::parseProcedureName\
00270 [uplevel 1 namespace current] $name fullClass procedure message\
00271 ]} {
00272 # not in a member procedure definition, fall back to normal procedure
00273 # declaration
00274 # uplevel is required instead of eval here otherwise tcl seems to forget
00275 # the procedure namespace if it exists
00276 uplevel 1 _proc [list $name $arguments] $args
00277 return
00278 }
00279 if {[llength $args]==0} { ;# check for procedure body presence
00280 error "missing body for ${fullClass}::$procedure"
00281 }
00282 set class [namespace tail $fullClass]
00283 if {[string equal $class $procedure]} { ;# class constructor definition
00284 if {![string equal [lindex $arguments 0] this]} {
00285 error "class $fullClass constructor first argument must be this"
00286 }
00287 if {[string equal [lindex $arguments 1] copy]} {
00288 # user defined copy constructor definition
00289 if {[llength $arguments]!=2} {
00290 error "class $fullClass copy constructor must have 2 arguments exactly"
00291 }
00292 # make sure of proper declaration order:
00293 if {[catch {info body ::${fullClass}::$class}]} {
00294 error "class $fullClass copy constructor defined before constructor"
00295 }
00296 eval ::stooop::constructorDeclaration\
00297 $fullClass $class 1 \{$arguments\} $args
00298 } else { ;# main constructor
00299 eval ::stooop::constructorDeclaration\
00300 $fullClass $class 0 \{$arguments\} $args
00301 # always generate default copy constructor:
00302 ::stooop::generateDefaultCopyConstructor $fullClass
00303 }
00304 } elseif {[string equal ~$class $procedure]} {
00305 # class destructor declaration
00306 if {[llength $arguments]!=1} {
00307 error "class $fullClass destructor must have 1 argument exactly"
00308 }
00309 if {![string equal [lindex $arguments 0] this]} {
00310 error "class $fullClass destructor argument must be this"
00311 }
00312 # make sure of proper declaration order
00313 # (use fastest method for testing procedure existence):
00314 if {[catch {info body ::${fullClass}::$class}]} {
00315 error "class $fullClass destructor defined before constructor"
00316 }
00317 ::stooop::destructorDeclaration\
00318 $fullClass $class $arguments [lindex $args 0]
00319 } else {
00320 # regular member procedure, may be static if there is no this first
00321 # argument
00322 # make sure of proper declaration order:
00323 if {[catch {info body ::${fullClass}::$class}]} {
00324 error "class $fullClass member procedure $procedure defined before constructor"
00325 }
00326 ::stooop::memberProcedureDeclaration\
00327 $fullClass $class $procedure $arguments [lindex $args 0]
00328 }
00329 }
00330
00331
00332 _ret ::stooop::constructorDeclaration (type fullClass , type class , type copy , type arguments , type args) {
00333 variable check
00334 variable fullBases
00335 variable variable
00336
00337 set number [llength $args]
00338 # check that each base class constructor has arguments:
00339 if {($number%2)==0} {
00340 error "bad class $fullClass constructor declaration, a base class, contructor arguments or body may be missing"
00341 }
00342 if {[string equal [lindex $arguments end] args]} {
00343 # remember that there is a variable number of arguments in class
00344 # constructor
00345 set variable($fullClass) {}
00346 }
00347 if {!$copy} {
00348 # do not initialize (or reinitialize in case of multiple class file
00349 # source statements) base classes for copy constructor
00350 set fullBases($fullClass) {}
00351 }
00352 # check base classes and their constructor arguments:
00353 foreach {base baseArguments} [lrange $args 0 [expr {$number-2}]] {
00354 # fully qualify base class namespace by looking up constructor, which
00355 # must exist
00356 set constructor ${base}::[namespace tail $base]
00357 # in case base class is defined in a file that is part of a package,
00358 # make sure that file is sourced through the tcl package auto-loading
00359 # mechanism by directly invoking the base class constructor while
00360 # ignoring the resulting error
00361 catch {$constructor}
00362 # determine fully qualified base class name in user invocation level
00363 # (up 2 levels from here since this procedure is invoked exclusively by
00364 # proc)
00365 set fullBase [namespace qualifiers\
00366 [uplevel 2 namespace which -command $constructor]\
00367 ]
00368 if {[string length $fullBase]==0} { ;# base constructor is not defined
00369 if {[string match *$base $fullClass]} {
00370 # if the specified base class name is included last in the fully
00371 # qualified class name, assume that it was meant to be the same
00372 error "class $fullClass cannot be derived from itself"
00373 } else {
00374 error "class $fullClass constructor defined before base class $base constructor"
00375 }
00376 }
00377 # check and save base classes only for main constructor that defines
00378 # them:
00379 if {!$copy} {
00380 if {[lsearch -exact $fullBases($fullClass) $fullBase]>=0} {
00381 error "class $fullClass directly inherits from class $fullBase more than once"
00382 }
00383 lappend fullBases($fullClass) $fullBase
00384 }
00385 # replace new lines with blanks in base arguments part in case user has
00386 # formatted long declarations with new lines
00387 regsub -all {\n} $baseArguments { } constructorArguments($fullBase)
00388 }
00389 # setup access to class data (an empty named array)
00390 # fully qualify tcl variable command for it may have been redefined within
00391 # the class namespace
00392 # since constructor is directly invoked by new, the object identifier must
00393 # be valid, so debugging the procedure is pointless
00394 set constructorBody \
00395 "::variable {}
00396 $check(code)
00397 "
00398 # base class(es) derivation specified:
00399 if {[llength $fullBases($fullClass)]>0} {
00400 # invoke base class constructors before evaluating constructor body
00401 # then set base part hidden derived member so that virtual procedures
00402 # are invoked at base class level as in C++
00403 if {[info exists variable($fullClass)]} {
00404 # variable number of arguments in derived class constructor
00405 foreach fullBase $fullBases($fullClass) {
00406 if {![info exists constructorArguments($fullBase)]} {
00407 error "missing base class $fullBase constructor arguments from class $fullClass constructor"
00408 }
00409 set baseConstructor ${fullBase}::[namespace tail $fullBase]
00410 if {\
00411 [info exists variable($fullBase)]&&\
00412 ([string first {$args} $constructorArguments($fullBase)]>=0)\
00413 } {
00414 # variable number of arguments in base class constructor and
00415 # in derived class base class constructor arguments
00416 # use eval so that base class constructor sees arguments
00417 # instead of a list
00418 # only the last argument of the base class constructor
00419 # arguments is considered as a variable list
00420 # (it usually is $args but could be a procedure invocation,
00421 # such as [filter $args])
00422 # fully qualify tcl commands such as set, for they may have
00423 # been redefined within the class namespace
00424 append constructorBody \
00425 "::set _list \[::list $constructorArguments($fullBase)\]
00426 ::eval $baseConstructor \$this \[::lrange \$_list 0 \[::expr {\[::llength \$_list\]-2}\]\] \[::lindex \$_list end\]
00427 ::unset _list
00428 ::set ${fullBase}::(\$this,_derived) $fullClass
00429 "
00430 } else {
00431 # no special processing needed
00432 # variable number of arguments in base class constructor or
00433 # variable arguments list passed as is to base class
00434 # constructor
00435 append constructorBody \
00436 "$baseConstructor \$this $constructorArguments($fullBase)
00437 ::set ${fullBase}::(\$this,_derived) $fullClass
00438 "
00439 }
00440 }
00441 } else { ;# constant number of arguments
00442 foreach fullBase $fullBases($fullClass) {
00443 if {![info exists constructorArguments($fullBase)]} {
00444 error "missing base class $fullBase constructor arguments from class $fullClass constructor"
00445 }
00446 set baseConstructor ${fullBase}::[namespace tail $fullBase]
00447 append constructorBody \
00448 "$baseConstructor \$this $constructorArguments($fullBase)
00449 ::set ${fullBase}::(\$this,_derived) $fullClass
00450 "
00451 }
00452 }
00453 } ;# else no base class derivation specified
00454 if {$copy} {
00455 # for user defined copy constructor, copy derived class member if it
00456 # exists
00457 append constructorBody \
00458 "::catch {::set (\$this,_derived) \$(\$[::lindex $arguments 1],_derived)}
00459 "
00460 }
00461 # finally append user defined procedure body:
00462 append constructorBody [lindex $args end]
00463 if {$copy} {
00464 _proc ${fullClass}::_copy $arguments $constructorBody
00465 } else {
00466 _proc ${fullClass}::$class $arguments $constructorBody
00467 }
00468 }
00469
00470 _ret ::stooop::destructorDeclaration (type fullClass , type class , type arguments , type body) {
00471 variable check
00472 variable fullBases
00473
00474 # setup access to class data
00475 # since the object identifier is always valid at this point, debugging the
00476 # procedure is pointless
00477 set body \
00478 "::variable {}
00479 $check(code)
00480 $body
00481 "
00482 # if there are any, delete base classes parts in reverse order of
00483 # construction
00484 for {set index [expr {[llength $fullBases($fullClass)]-1}]} {$index>=0}\
00485 {incr index -1}\
00486 {
00487 set fullBase [lindex $fullBases($fullClass) $index]
00488 append body \
00489 "::stooop::deleteObject $fullBase \$this
00490 "
00491 }
00492 _proc ${fullClass}::~$class $arguments $body
00493 }
00494
00495 _ret ::stooop::memberProcedureDeclaration (\
00496 type fullClass , type class , type procedure , type arguments , type body\
00497 ) {
00498 variable check
00499 variable pureVirtual
00500
00501 if {[info exists pureVirtual]} { ;# virtual declaration
00502 if {$pureVirtual} { ;# pure virtual declaration
00503 # setup access to class data
00504 # evaluate derived procedure which must exists. derived procedure
00505 # return value is automatically returned
00506 _proc ${fullClass}::$procedure $arguments \
00507 "::variable {}
00508 $check(code)
00509 ::uplevel 1 \$(\$this,_derived)::$procedure \[::lrange \[::info level 0\] 1 end\]
00510 "
00511 } else { ;# regular virtual declaration
00512 # setup access to class data
00513 # evaluate derived procedure and return if it exists
00514 # else evaluate the base class procedure which can be invoked from
00515 # derived class procedure by prepending _
00516 _proc ${fullClass}::_$procedure $arguments \
00517 "::variable {}
00518 $check(code)
00519 $body
00520 "
00521 _proc ${fullClass}::$procedure $arguments \
00522 "::variable {}
00523 $check(code)
00524 if {!\[::catch {::info body \$(\$this,_derived)::$procedure}\]} {
00525 ::return \[::uplevel 1 \$(\$this,_derived)::$procedure \[::lrange \[::info level 0\] 1 end\]\]
00526 }
00527 ::uplevel 1 ${fullClass}::_$procedure \[::lrange \[::info level 0\] 1 end\]
00528 "
00529 }
00530 } else { ;# non virtual declaration
00531 # setup access to class data:
00532 _proc ${fullClass}::$procedure $arguments \
00533 "::variable {}
00534 $check(code)
00535 $body
00536 "
00537 }
00538 }
00539
00540
00541
00542 _ret ::stooop::generateDefaultCopyConstructor (type fullClass) {
00543 variable fullBases
00544
00545 # generate code for cloning base classes layers if there is at least one
00546 # base class
00547 foreach fullBase $fullBases($fullClass) {
00548 append body \
00549 "${fullBase}::_copy \$this \$sibling
00550 "
00551 }
00552 append body \
00553 "::stooop::copy $fullClass \$sibling \$this
00554 "
00555 _proc ${fullClass}::_copy {this sibling} $body
00556 }
00557
00558
00559 if {[llength [array names ::env STOOOP*]]>0} {
00560
00561
00562
00563 catch {rename ::stooop::class ::stooop::_class}
00564
00565 _ret ::stooop::class (type args) {
00566 variable trace
00567 variable check
00568
00569 set class [lindex $args 0]
00570 if {$check(data)} {
00571 # check write and unset operations on empty named array holding
00572 # class data
00573 uplevel 1 namespace eval $class\
00574 [list {::trace variable {} wu ::stooop::checkData}]
00575 }
00576 if {[info exists ::env(STOOOPTRACEDATA)]} {
00577 # trace write and unset operations on empty named array holding
00578 # class data
00579 uplevel 1 namespace eval $class [list\
00580 "::trace variable {} $trace(dataOperations) ::stooop::traceData"\
00581 ]
00582 }
00583 uplevel 1 ::stooop::_class $args
00584 }
00585
00586 if {$::stooop::check(ret edures)} (
00587 # type prevent , type the , type creation , type of , type any , type object , type of , type a , type pure , type interface , type class
00588 # , type use , type a , type new , type virtual , type procedure , type instead , type of , type adding , type debugging , type code , type to
00589 # , type existing , type one
00590 # , type gracefully , type handle , type multiple , type sourcing , type of , type this , type file:
00591 , type catch , optional rename =::stooop::virtual ::stooop::_virtual
00592 # , type keep , type track , type of , type interface , type classes (, type which , type have , type at , type least 1 , type pure , type virtual
00593 # , type procedure):
00594 _, type proc ::, type stooop::, type virtual , optional keyword =name arguments =args , optional
00595 variable =interface
00596
00597 uplevel =1 ::stooop::_virtual =[list $keyword =$name $arguments] =$args
00598 parseProcedureName =[uplevel 1 =namespace current] =$name\
00599 fullClass =procedure message
00600 =if {[llength =$args]==0 , optional ;# =no procedure =body means =pure virtual
00601 =set interface($fullClass) ={
00602 )
00603 }
00604 }
00605
00606 if {$::stooop::check(objects)} {
00607 _proc invokingProcedure {} {
00608 if {[catch {set procedure [lindex [info level -2] 0]}]} {
00609 /* no invoking procedure*/
00610 return {top level}
00611 } elseif {\
00612 ([string length $ret edure]==0)||\
00613 [string equal $procedure namespace]\
00614 } ( ;# type invoked , type from , type a , type namespace , type body
00615 , type return ", type namespace [, type uplevel 2 , type namespace , type current]"
00616 ) else {
00617 # store fully qualified name, visible from creator procedure
00618 # invoking procedure
00619 return [uplevel 3 namespace which -command $procedure]
00620 }
00621 }
00622 }
00623
00624 if {$::stooop::check(ret edures)||$::stooop::check(objects)} (
00625 # type gracefully , type handle , type multiple , type sourcing , type of , type this , type file:
00626 , type catch , optional rename =::stooop::new ::stooop::_new
00627 # , type use , type a , type new , type new , type procedure , type instead , type of , type adding , type debugging , type code , type to , type existing
00628 # , type one:
00629 _, type proc ::, type stooop::, type new , optional classOrId =args , optional
00630 variable =newId
00631 variable =check
00632
00633 if ={$check(procedures) , optional
00634 variable =fullClass
00635 variable =interface
00636
00637 , type if , optional $check(objects) , optional
00638 variable =creator
00639
00640 , type if , optional $check(procedures) , optional
00641 if ={[string is =integer $classOrId] , optional
00642 # =first argument =is an =object identifier
00643 =# class =code, if =from a =package, must =already be =loaded
00644 set =fullName $fullClass($classOrId)
00645 , type else , optional ;# =first argument =is a =class
00646 # =generate constructor =name:
00647 set =constructor ${classOrId::[, type namespace , type tail $, type classOrId]
00648 # , type force , type loading , type in , type case , type class , type is , type in , type a , type package , type so , type namespace
00649 # , type commands , type work , type properly:
00650 , type catch , optional $constructor
00651 , type set , type fullName [, type namespace , type qualifiers\
00652 [, type uplevel 1 , type namespace , type which -, type command $, type constructor]\
00653 ]
00654 # , type anticipate , type full , type class , type name , type storage , type in , type original , type new, optional , type in
00655 # , type order , type to , type avoid , type invalid , type object , type identifier , type error , type in
00656 # , type checkProcedure, optional , type when , type member , type procedure , type is , type invoked , type from
00657 # , type within , type contructor, , type in , type which , type case , type full , type class , type name , type would
00658 # , type have , type yet , type to , type be , type stored.
00659 , type set , type fullClass([, type expr , optional $newId+1]) $, type fullName
00660 # , type new , type identifier , type is , type really , type incremented , type in , type original , type new, optional
00661 )
00662 if {[info exists interface($fullName)]} {
00663 error "class $fullName with pure virtual ret edures should not be instanciated"
00664 }
00665 }
00666 if ($type check(, type objects)) {
00667 # keep track of procedure in which creation occured (new
00668 # identifier is really incremented in original new{})
00669 set creator([expr {$newId+1}]) [invokingProcedure]
00670 }
00671 return [uplevel 1 ::stooop::_new $classOrId $args]
00672 }
00673 }
00674
00675 if {$::stooop::check(objects)} {
00676 _ret ::stooop::delete (type args) {
00677 variable fullClass
00678 variable deleter
00679
00680 # keep track of procedure in which deletion occured:
00681 set procedure [invokingProcedure]
00682 foreach id $args {
00683 uplevel 1 ::stooop::deleteObject $fullClass($id) $id
00684 unset fullClass($id)
00685 set deleter($id) $procedure
00686 }
00687 }
00688 }
00689
00690 /* return the unsorted list of ancestors in class hierarchy:*/
00691 _ret ::stooop::ancestors (type fullClass) {
00692 variable ancestors ;# use a cache for efficiency
00693 variable fullBases
00694
00695 if {[info exists ancestors($fullClass)]} {
00696 return $ancestors($fullClass) ;# found in the cache
00697 }
00698 set list {}
00699 foreach class $fullBases($fullClass) {
00700 set list [concat $list [list $class] [ancestors $class]]
00701 }
00702 set ancestors($fullClass) $list ;# save in cache
00703 return $list
00704 }
00705
00706 /* since this procedure is always invoked from a debug procedure, take the*/
00707 /* extra level in the stack frame into account*/
00708 /* parameters (passed as references) that cannot be determined are not set*/
00709 _ret ::stooop::debugInformation (\
00710 type className , type fullClassName , type procedureName , type fullProcedureName\
00711 , type thisParameterName\
00712 ) {
00713 upvar 1 $className class $fullClassName fullClass\
00714 $procedureName procedure $fullProcedureName fullProcedure\
00715 $thisParameterName thisParameter
00716 variable declared
00717
00718 set namespace [uplevel 2 namespace current]
00719 # not in a class namespace:
00720 if {[lsearch -exact [array names declared] $namespace]<0} return
00721 # remove redundant global qualifier:
00722 set fullClass [string trimleft $namespace :]
00723 set class [namespace tail $fullClass] ;# class name
00724 set list [info level -2]
00725 set first [lindex $list 0]
00726 if {([llength $list]==0)||[string equal $first namespace]}\
00727 return ;# not in a procedure, nothing else to do
00728 set procedure $first
00729 # procedure must be known at the invoker level:
00730 set fullProcedure [uplevel 3 namespace which -command $procedure]
00731 set procedure [namespace tail $procedure] ;# strip procedure name
00732 if {[string equal $class $procedure]} { ;# constructor
00733 set procedure constructor
00734 } elseif {[string equal ~$class $procedure]} { ;# destructor
00735 set procedure destructor
00736 }
00737 if {[string equal [lindex [info args $fullProcedure] 0] this]} {
00738 # non static procedure
00739 # object identifier is first argument:
00740 set thisParameter [lindex $list 1]
00741 }
00742 }
00743
00744 /* check that member procedure is valid for object passed as parameter:*/
00745 _ret ::stooop::checkProcedure () {
00746 variable fullClass
00747
00748 debugInformation class qualifiedClass procedure qualifiedProcedure this
00749 # static procedure, no checking possible:
00750 if {![info exists this]} return
00751 # in constructor, checking useless since object is not yet created:
00752 if {[string equal $procedure constructor]} return
00753 if {![info exists fullClass($this)]} {
00754 error "$this is not a valid object identifier"
00755 }
00756 set fullName [string trimleft $fullClass($this) :]
00757 # procedure and object classes match:
00758 if {[string equal $fullName $qualifiedClass]} return
00759 # restore global qualifiers to compare with internal full class array
00760 # data
00761 if {[lsearch -exact [ancestors ::$fullName] ::$qualifiedClass]<0} {
00762 error "class $qualifiedClass of $qualifiedProcedure procedure not an ancestor of object $this class $fullName"
00763 }
00764 }
00765
00766 /* gather current procedure data, perform substitutions and output to trace*/
00767 /* channel:*/
00768 _ret ::stooop::traceProcedure () {
00769 variable trace
00770
00771 debugInformation class qualifiedClass procedure qualifiedProcedure this
00772 # all debug data is available since we are for sure in a class procedure
00773 set text $trace(procedureFormat)
00774 regsub -all %C $text $qualifiedClass text ;# fully qualified class name
00775 regsub -all %c $text $class text
00776 # fully qualified procedure name:
00777 regsub -all %P $text $qualifiedProcedure text
00778 regsub -all %p $text $procedure text
00779 if {[info exists this]} { ;# non static procedure
00780 regsub -all %O $text $this text
00781 # remaining arguments:
00782 regsub -all %a $text [lrange [info level -1] 2 end] text
00783 } else { ;# static procedure
00784 regsub -all %O $text {} text
00785 # remaining arguments:
00786 regsub -all %a $text [lrange [info level -1] 1 end] text
00787 }
00788 puts $trace(procedureChannel) $text
00789 }
00790
00791 /* check that class data member is accessed within procedure of identical*/
00792 /* class*/
00793 /* then if procedure is not static, check that only data belonging to the*/
00794 /* object passed as parameter is accessed*/
00795 _ret ::stooop::checkData (type array , type name , type operation) {
00796 scan $name %u,%s identifier member
00797 # ignore internally defined members:
00798 if {[info exists member]&&[string equal $member _derived]} return
00799
00800 debugInformation class qualifiedClass procedure qualifiedProcedure this
00801 # no checking can be done outside of a class namespace:
00802 if {![info exists class]} return
00803 # determine array full name:
00804 set array [uplevel 1 [list namespace which -variable $array]]
00805 if {![info exists procedure]} { ;# inside a class namespace
00806 # compare with empty named array fully qualified name:
00807 if {![string equal $array ::${qualifiedClass}::]} {
00808 # trace command error message is automatically prepended and
00809 # indicates operation
00810 error\
00811 "class access violation in class $qualifiedClass namespace"
00812 }
00813 return ;# done
00814 }
00815 # ignore internal copy procedure:
00816 if {[string equal $qualifiedProcedure ::stooop::copy]} return
00817 if {![string equal $array ::${qualifiedClass}::]} {
00818 # compare with empty named array fully qualified name
00819 # trace command error message is automatically prepended and
00820 # indicates operation
00821 error "class access violation in procedure $qualifiedProcedure"
00822 }
00823 # static procedure, all objects can be accessed:
00824 if {![info exists this]} return
00825 # static data members can be accessed:
00826 if {![info exists identifier]} return
00827 # check that accessed data belongs to this object:
00828 if {$this!=$identifier} {
00829 error "object $identifier access violation in procedure $qualifiedProcedure acting on object $this"
00830 }
00831 }
00832
00833 /* gather accessed data member information, perform substitutions and output*/
00834 /* to trace channel*/
00835 _ret ::stooop::traceData (type array , type name , type operation) {
00836 variable trace
00837
00838 scan $name %u,%s identifier member
00839 # ignore internally defined members:
00840 if {[info exists member]&&[string equal $member _derived]} return
00841
00842 # ignore internal destruction:
00843 if {\
00844 ![catch {lindex [info level -1] 0} procedure]&&\
00845 [string equal ::stooop::deleteObject $procedure]\
00846 } return
00847 set class {} ;# in case we are outside a class
00848 set qualifiedClass {}
00849 set procedure {} ;# in case we are outside a class procedure
00850 set qualifiedProcedure {}
00851
00852 debugInformation class qualifiedClass procedure qualifiedProcedure this
00853 set text $trace(dataFormat)
00854 regsub -all %C $text $qualifiedClass text ;# fully qualified class name
00855 regsub -all %c $text $class text
00856 if {[info exists member]} {
00857 regsub -all %m $text $member text
00858 } else {
00859 regsub -all %m $text $name text ;# static member
00860 }
00861 # fully qualified procedure name:
00862 regsub -all %P $text $qualifiedProcedure text
00863 regsub -all %p $text $procedure text
00864 # fully qualified array name with global qualifiers stripped:
00865 regsub -all %A $text [string trimleft\
00866 [uplevel 1 [list namespace which -variable $array]] :\
00867 ] text
00868 if {[info exists this]} { ;# non static procedure
00869 regsub -all %O $text $this text
00870 } else { ;# static procedure
00871 regsub -all %O $text {} text
00872 }
00873 array set string {r read w write u unset}
00874 regsub -all %o $text $string($operation) text
00875 if {[string equal $operation u]} {
00876 regsub -all %v $text {} text ;# no value when unsetting
00877 } else {
00878 regsub -all %v $text [uplevel 1 set ${array}($name)] text
00879 }
00880 puts $trace(dataChannel) $text
00881 }
00882
00883 if {$::stooop::check(objects)} {
00884 /* print existing objects along with creation procedure, with optional*/
00885 /* class pattern (see the string Tcl command manual)*/
00886 _ret ::stooop::printObjects (optional pattern =*) {
00887 variable fullClass
00888 variable creator
00889
00890 puts "stooop::printObjects invoked from [invokingProcedure]:"
00891 foreach id [lsort -integer [array names fullClass]] {
00892 if {[string match $pattern $fullClass($id)]} {
00893 puts "$fullClass($id)\($id\) + $creator($id)"
00894 }
00895 }
00896 }
00897
00898 /* record all existing objects for later report:*/
00899 _ret ::stooop::record () {
00900 variable fullClass
00901 variable checkpointFullClass
00902
00903 puts "stooop::record invoked from [invokingProcedure]"
00904 catch {unset checkpointFullClass}
00905 array set checkpointFullClass [array get fullClass]
00906 }
00907
00908 /* print all new or deleted object since last record, with optional class*/
00909 /* pattern:*/
00910 _ret ::stooop::report (optional pattern =*) {
00911 variable fullClass
00912 variable checkpointFullClass
00913 variable creator
00914 variable deleter
00915
00916 puts "stooop::report invoked from [invokingProcedure]:"
00917 set checkpointIds [lsort -integer [array names checkpointFullClass]]
00918 set currentIds [lsort -integer [array names fullClass]]
00919 foreach id $currentIds {
00920 if {\
00921 [string match $pattern $fullClass($id)]&&\
00922 ([lsearch -exact $checkpointIds $id]<0)\
00923 } {
00924 puts "+ $fullClass($id)\($id\) + $creator($id)"
00925 }
00926 }
00927 foreach id $checkpointIds {
00928 if {\
00929 [string match $pattern $checkpointFullClass($id)]&&\
00930 ([lsearch -exact $currentIds $id]<0)\
00931 } {
00932 puts "- $checkpointFullClass($id)\($id\) - $deleter($id) + $creator($id)"
00933 }
00934 }
00935 }
00936 }
00937
00938 }
00939