/usr/share/tcltk/tcllib1.17/stooop/stooop.tcl is in tcllib 1.17-dfsg-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 | # stooop
# Simple Tcl Only Object Oriented Programming
# An object oriented extension to the Tcl programming language
#
# Copyright (c) 2002 by Jean-Luc Fontaine <jfontain@free.fr>.
# This code may be distributed under the same terms as Tcl.
#
# $Id: stooop.tcl,v 1.9 2004/01/15 06:36:14 andreas_kupries Exp $
# check whether empty named arrays and array unset are supported:
package require Tcl 8.3
package provide stooop 4.4.1
# rename proc before it is overloaded, ignore error in case of multiple
# inclusion of this file:
catch {rename proc _proc}
namespace eval ::stooop {
variable check
variable trace
# no checking by default: use an empty instruction to avoid any performance
# hit:
set check(code) {}
if {[info exists ::env(STOOOPCHECKALL)]&&$::env(STOOOPCHECKALL)} {
array set ::env\
{STOOOPCHECKPROCEDURES 1 STOOOPCHECKDATA 1 STOOOPCHECKOBJECTS 1}
}
set check(procedures) [expr {\
[info exists ::env(STOOOPCHECKPROCEDURES)]&&\
$::env(STOOOPCHECKPROCEDURES)\
}]
set check(data) [expr {\
[info exists ::env(STOOOPCHECKDATA)]&&$::env(STOOOPCHECKDATA)\
}]
set check(objects) [expr {\
[info exists ::env(STOOOPCHECKOBJECTS)]&&$::env(STOOOPCHECKOBJECTS)\
}]
if {$check(procedures)} {
append check(code) {::stooop::checkProcedure;}
}
if {[info exists ::env(STOOOPTRACEALL)]} {
# use same channel for both traces
set ::env(STOOOPTRACEPROCEDURES) $::env(STOOOPTRACEALL)
set ::env(STOOOPTRACEDATA) $::env(STOOOPTRACEALL)
}
if {[info exists ::env(STOOOPTRACEPROCEDURES)]} {
set trace(procedureChannel) $::env(STOOOPTRACEPROCEDURES)
switch $trace(procedureChannel) {
stdout - stderr {}
default {
# eventually truncate output file if it exists:
set trace(procedureChannel) [open $::env(STOOOPTRACEPROCEDURES) w+]
}
}
# default format:
set trace(procedureFormat)\
{class: %C, procedure: %p, object: %O, arguments: %a}
# eventually override with user defined format:
catch {set trace(procedureFormat) $::env(STOOOPTRACEPROCEDURESFORMAT)}
append check(code) {::stooop::traceProcedure;}
}
if {[info exists ::env(STOOOPTRACEDATA)]} {
set trace(dataChannel) $::env(STOOOPTRACEDATA)
switch $trace(dataChannel) {
stdout - stderr {}
default {
# eventually truncate output file if it exists
set trace(dataChannel) [open $::env(STOOOPTRACEDATA) w+]
}
}
# default format:
set trace(dataFormat) {class: %C, procedure: %p, array: %A, object: %O, member: %m, operation: %o, value: %v}
# eventually override with user defined format:
catch {set trace(dataFormat) $::env(STOOOPTRACEDATAFORMAT)}
# trace all operations by default:
set trace(dataOperations) rwu
# eventually override with user defined operations:
catch {set trace(dataOperations) $::env(STOOOPTRACEDATAOPERATIONS)}
}
namespace export class virtual new delete classof ;# export public commands
if {![info exists newId]} {
# initialize object id counter only once even if this file is sourced
# several times:
variable newId 0
}
# create an object of specified class or copy an existing object:
_proc new {classOrId args} {
variable newId
variable fullClass
# use local variable for identifier because new can be invoked
# recursively:
if {[string is integer $classOrId]} {
# first argument is an object identifier (unsigned integer), copy
# source object to new object of identical class
if {[catch {\
set fullClass([set id [incr newId]]) $fullClass($classOrId)\
}]} {
error "invalid object identifier $classOrId"
}
# invoke the copy constructor for the class in caller's variable
# context so that object copy is transparent (see above):
uplevel 1 $fullClass($classOrId)::_copy $id $classOrId
} else { ;# first argument is a class
# generate constructor name:
set constructor ${classOrId}::[namespace tail $classOrId]
# we could detect here whether class was ever declared but that
# would prevent stooop packages to load properly, because
# constructor would not be invoked and thus class source file never
# sourced
# invoke the constructor for the class with optional arguments in
# caller's variable context so that object creation is transparent
# and that array names as constructor parameters work with a simple
# upvar
# note: if class is in a package, the class namespace code is loaded
# here, as the first object of the class is created
uplevel 1 $constructor [set id [incr newId]] $args
# generate fully qualified class namespace name now that we are sure
# that class namespace code has been invoked:
set fullClass($id) [namespace qualifiers\
[uplevel 1 namespace which -command $constructor]\
]
}
return $id ;# return a unique object identifier
}
_proc delete {args} { ;# delete one or more objects
variable fullClass
foreach id $args {
# destruct in caller's variable context so that object deletion is
# transparent:
uplevel 1 ::stooop::deleteObject $fullClass($id) $id
unset fullClass($id)
}
}
# delete object data starting at specified class layer and going up the base
# class hierarchy if any
# invoke the destructor for the object class and unset all the object data
# members for the class
# the destructor will in turn delete the base classes layers
_proc deleteObject {fullClass id} {
# invoke the destructor for the class in caller's variable context so
# that object deletion is transparent:
uplevel 1 ${fullClass}::~[namespace tail $fullClass] $id
# delete all this object data members if any (assume that they were
# stored as ${class}::($id,memberName)):
array unset ${fullClass}:: $id,*
# data member arrays deletion is left to the user
}
_proc classof {id} {
variable fullClass
return $fullClass($id) ;# return class of object
}
# copy object data members from one object to another:
_proc copy {fullClass from to} {
set index [string length $from]
# copy regular data members:
foreach {name value} [array get ${fullClass}:: $from,*] {
set ${fullClass}::($to[string range $name $index end]) $value
}
# if any, array data members copy is left to the class programmer
# through the then mandatory copy constructor
}
}
_proc ::stooop::class {args} {
variable declared
set class [lindex $args 0]
# register class using its fully qualified name:
set declared([uplevel 1 namespace eval $class {namespace current}]) {}
# create the empty name array used to hold all class objects so that static
# members can be directly initialized within the class declaration but
# outside member procedures
uplevel 1 namespace eval $class [list "::variable {}\n[lindex $args end]"]
}
# if procedure is a member of a known class, class and procedure names are set
# and true is returned, otherwise false is returned:
_proc ::stooop::parseProcedureName {\
namespace name fullClassVariable procedureVariable messageVariable\
} {
# namespace argument is the current namespace (fully qualified) in which the
# procedure is defined
variable declared
upvar 1 $fullClassVariable fullClass $procedureVariable procedure\
$messageVariable message
if {\
[info exists declared($namespace)]&&\
([string length [namespace qualifiers $name]]==0)\
} {
# a member procedure is being defined inside a class namespace
set fullClass $namespace
set procedure $name ;# member procedure name is full name
return 1
} else {
# procedure is either a member of a known class or a regular procedure
if {![string match ::* $name]} {
# eventually fully qualify procedure name
if {[string equal $namespace ::]} { ;# global namespace special case
set name ::$name
} else {
set name ${namespace}::$name
}
}
# eventual class name is leading part:
set fullClass [namespace qualifiers $name]
if {[info exists declared($fullClass)]} { ;# if class is known
set procedure [namespace tail $name] ;# procedure always is the tail
return 1
} else { ;# not a member procedure
if {[string length $fullClass]==0} {
set message "procedure $name class name is empty"
} else {
set message "procedure $name class $fullClass is unknown"
}
return 0
}
}
}
# virtual operator, to be placed before proc
# virtualize a member procedure, determine whether it is a pure virtual, check
# for procedures that cannot be virtualized
_proc ::stooop::virtual {keyword name arguments args} {
# set a flag so that proc knows it is acting upon a virtual procedure, also
# serves as a pure indicator:
variable pureVirtual
if {![string equal [uplevel 1 namespace which -command $keyword] ::proc]} {
error "virtual operator works only on proc, not $keyword"
}
if {![parseProcedureName\
[uplevel 1 namespace current] $name fullClass procedure message\
]} {
error $message ;# not in a member procedure definition
}
set class [namespace tail $fullClass]
if {[string equal $class $procedure]} {
error "cannot make class $fullClass constructor virtual"
}
if {[string equal ~$class $procedure]} {
error "cannot make class $fullClass destructor virtual"
}
if {![string equal [lindex $arguments 0] this]} {
error "cannot make static procedure $procedure of class $fullClass virtual"
}
# no procedure body means pure virtual:
set pureVirtual [expr {[llength $args]==0}]
# process procedure declaration, body being empty for pure virtual procedure
# make virtual transparent by using uplevel:
uplevel 1 ::proc [list $name $arguments [lindex $args 0]]
unset pureVirtual
}
_proc proc {name arguments args} {
if {![::stooop::parseProcedureName\
[uplevel 1 namespace current] $name fullClass procedure message\
]} {
# not in a member procedure definition, fall back to normal procedure
# declaration
# uplevel is required instead of eval here otherwise tcl seems to forget
# the procedure namespace if it exists
uplevel 1 _proc [list $name $arguments] $args
return
}
if {[llength $args]==0} { ;# check for procedure body presence
error "missing body for ${fullClass}::$procedure"
}
set class [namespace tail $fullClass]
if {[string equal $class $procedure]} { ;# class constructor definition
if {![string equal [lindex $arguments 0] this]} {
error "class $fullClass constructor first argument must be this"
}
if {[string equal [lindex $arguments 1] copy]} {
# user defined copy constructor definition
if {[llength $arguments]!=2} {
error "class $fullClass copy constructor must have 2 arguments exactly"
}
# make sure of proper declaration order:
if {[catch {info body ::${fullClass}::$class}]} {
error "class $fullClass copy constructor defined before constructor"
}
eval ::stooop::constructorDeclaration\
$fullClass $class 1 \{$arguments\} $args
} else { ;# main constructor
eval ::stooop::constructorDeclaration\
$fullClass $class 0 \{$arguments\} $args
# always generate default copy constructor:
::stooop::generateDefaultCopyConstructor $fullClass
}
} elseif {[string equal ~$class $procedure]} {
# class destructor declaration
if {[llength $arguments]!=1} {
error "class $fullClass destructor must have 1 argument exactly"
}
if {![string equal [lindex $arguments 0] this]} {
error "class $fullClass destructor argument must be this"
}
# make sure of proper declaration order
# (use fastest method for testing procedure existence):
if {[catch {info body ::${fullClass}::$class}]} {
error "class $fullClass destructor defined before constructor"
}
::stooop::destructorDeclaration\
$fullClass $class $arguments [lindex $args 0]
} else {
# regular member procedure, may be static if there is no this first
# argument
# make sure of proper declaration order:
if {[catch {info body ::${fullClass}::$class}]} {
error "class $fullClass member procedure $procedure defined before constructor"
}
::stooop::memberProcedureDeclaration\
$fullClass $class $procedure $arguments [lindex $args 0]
}
}
# copy flag is set for user defined copy constructor:
_proc ::stooop::constructorDeclaration {fullClass class copy arguments args} {
variable check
variable fullBases
variable variable
set number [llength $args]
# check that each base class constructor has arguments:
if {($number%2)==0} {
error "bad class $fullClass constructor declaration, a base class, contructor arguments or body may be missing"
}
if {[string equal [lindex $arguments end] args]} {
# remember that there is a variable number of arguments in class
# constructor
set variable($fullClass) {}
}
if {!$copy} {
# do not initialize (or reinitialize in case of multiple class file
# source statements) base classes for copy constructor
set fullBases($fullClass) {}
}
# check base classes and their constructor arguments:
foreach {base baseArguments} [lrange $args 0 [expr {$number-2}]] {
# fully qualify base class namespace by looking up constructor, which
# must exist
set constructor ${base}::[namespace tail $base]
# in case base class is defined in a file that is part of a package,
# make sure that file is sourced through the tcl package auto-loading
# mechanism by directly invoking the base class constructor while
# ignoring the resulting error
catch {$constructor}
# determine fully qualified base class name in user invocation level
# (up 2 levels from here since this procedure is invoked exclusively by
# proc)
set fullBase [namespace qualifiers\
[uplevel 2 namespace which -command $constructor]\
]
if {[string length $fullBase]==0} { ;# base constructor is not defined
if {[string match *$base $fullClass]} {
# if the specified base class name is included last in the fully
# qualified class name, assume that it was meant to be the same
error "class $fullClass cannot be derived from itself"
} else {
error "class $fullClass constructor defined before base class $base constructor"
}
}
# check and save base classes only for main constructor that defines
# them:
if {!$copy} {
if {[lsearch -exact $fullBases($fullClass) $fullBase]>=0} {
error "class $fullClass directly inherits from class $fullBase more than once"
}
lappend fullBases($fullClass) $fullBase
}
# replace new lines with blanks in base arguments part in case user has
# formatted long declarations with new lines
regsub -all {\n} $baseArguments { } constructorArguments($fullBase)
}
# setup access to class data (an empty named array)
# fully qualify tcl variable command for it may have been redefined within
# the class namespace
# since constructor is directly invoked by new, the object identifier must
# be valid, so debugging the procedure is pointless
set constructorBody \
"::variable {}
$check(code)
"
# base class(es) derivation specified:
if {[llength $fullBases($fullClass)]>0} {
# invoke base class constructors before evaluating constructor body
# then set base part hidden derived member so that virtual procedures
# are invoked at base class level as in C++
if {[info exists variable($fullClass)]} {
# variable number of arguments in derived class constructor
foreach fullBase $fullBases($fullClass) {
if {![info exists constructorArguments($fullBase)]} {
error "missing base class $fullBase constructor arguments from class $fullClass constructor"
}
set baseConstructor ${fullBase}::[namespace tail $fullBase]
if {\
[info exists variable($fullBase)]&&\
([string first {$args} $constructorArguments($fullBase)]>=0)\
} {
# variable number of arguments in base class constructor and
# in derived class base class constructor arguments
# use eval so that base class constructor sees arguments
# instead of a list
# only the last argument of the base class constructor
# arguments is considered as a variable list
# (it usually is $args but could be a procedure invocation,
# such as [filter $args])
# fully qualify tcl commands such as set, for they may have
# been redefined within the class namespace
append constructorBody \
"::set _list \[::list $constructorArguments($fullBase)\]
::eval $baseConstructor \$this \[::lrange \$_list 0 \[::expr {\[::llength \$_list\]-2}\]\] \[::lindex \$_list end\]
::unset _list
::set ${fullBase}::(\$this,_derived) $fullClass
"
} else {
# no special processing needed
# variable number of arguments in base class constructor or
# variable arguments list passed as is to base class
# constructor
append constructorBody \
"$baseConstructor \$this $constructorArguments($fullBase)
::set ${fullBase}::(\$this,_derived) $fullClass
"
}
}
} else { ;# constant number of arguments
foreach fullBase $fullBases($fullClass) {
if {![info exists constructorArguments($fullBase)]} {
error "missing base class $fullBase constructor arguments from class $fullClass constructor"
}
set baseConstructor ${fullBase}::[namespace tail $fullBase]
append constructorBody \
"$baseConstructor \$this $constructorArguments($fullBase)
::set ${fullBase}::(\$this,_derived) $fullClass
"
}
}
} ;# else no base class derivation specified
if {$copy} {
# for user defined copy constructor, copy derived class member if it
# exists
append constructorBody \
"::catch {::set (\$this,_derived) \$(\$[::lindex $arguments 1],_derived)}
"
}
# finally append user defined procedure body:
append constructorBody [lindex $args end]
if {$copy} {
_proc ${fullClass}::_copy $arguments $constructorBody
} else {
_proc ${fullClass}::$class $arguments $constructorBody
}
}
_proc ::stooop::destructorDeclaration {fullClass class arguments body} {
variable check
variable fullBases
# setup access to class data
# since the object identifier is always valid at this point, debugging the
# procedure is pointless
set body \
"::variable {}
$check(code)
$body
"
# if there are any, delete base classes parts in reverse order of
# construction
for {set index [expr {[llength $fullBases($fullClass)]-1}]} {$index>=0}\
{incr index -1}\
{
set fullBase [lindex $fullBases($fullClass) $index]
append body \
"::stooop::deleteObject $fullBase \$this
"
}
_proc ${fullClass}::~$class $arguments $body
}
_proc ::stooop::memberProcedureDeclaration {\
fullClass class procedure arguments body\
} {
variable check
variable pureVirtual
if {[info exists pureVirtual]} { ;# virtual declaration
if {$pureVirtual} { ;# pure virtual declaration
# setup access to class data
# evaluate derived procedure which must exists. derived procedure
# return value is automatically returned
_proc ${fullClass}::$procedure $arguments \
"::variable {}
$check(code)
::uplevel 1 \$(\$this,_derived)::$procedure \[::lrange \[::info level 0\] 1 end\]
"
} else { ;# regular virtual declaration
# setup access to class data
# evaluate derived procedure and return if it exists
# else evaluate the base class procedure which can be invoked from
# derived class procedure by prepending _
_proc ${fullClass}::_$procedure $arguments \
"::variable {}
$check(code)
$body
"
_proc ${fullClass}::$procedure $arguments \
"::variable {}
$check(code)
if {!\[::catch {::info body \$(\$this,_derived)::$procedure}\]} {
::return \[::uplevel 1 \$(\$this,_derived)::$procedure \[::lrange \[::info level 0\] 1 end\]\]
}
::uplevel 1 ${fullClass}::_$procedure \[::lrange \[::info level 0\] 1 end\]
"
}
} else { ;# non virtual declaration
# setup access to class data:
_proc ${fullClass}::$procedure $arguments \
"::variable {}
$check(code)
$body
"
}
}
# generate default copy procedure which may be overriden by the user for any
# class layer:
_proc ::stooop::generateDefaultCopyConstructor {fullClass} {
variable fullBases
# generate code for cloning base classes layers if there is at least one
# base class
foreach fullBase $fullBases($fullClass) {
append body \
"${fullBase}::_copy \$this \$sibling
"
}
append body \
"::stooop::copy $fullClass \$sibling \$this
"
_proc ${fullClass}::_copy {this sibling} $body
}
if {[llength [array names ::env STOOOP*]]>0} {
# if one or more environment variables are set, we are in debugging mode
# gracefully handle multiple sourcing of this file:
catch {rename ::stooop::class ::stooop::_class}
# use a new class procedure instead of adding debugging code to existing one
_proc ::stooop::class {args} {
variable trace
variable check
set class [lindex $args 0]
if {$check(data)} {
# check write and unset operations on empty named array holding
# class data
uplevel 1 namespace eval $class\
[list {::trace variable {} wu ::stooop::checkData}]
}
if {[info exists ::env(STOOOPTRACEDATA)]} {
# trace write and unset operations on empty named array holding
# class data
uplevel 1 namespace eval $class [list\
"::trace variable {} $trace(dataOperations) ::stooop::traceData"\
]
}
uplevel 1 ::stooop::_class $args
}
if {$::stooop::check(procedures)} {
# prevent the creation of any object of a pure interface class
# use a new virtual procedure instead of adding debugging code to
# existing one
# gracefully handle multiple sourcing of this file:
catch {rename ::stooop::virtual ::stooop::_virtual}
# keep track of interface classes (which have at least 1 pure virtual
# procedure):
_proc ::stooop::virtual {keyword name arguments args} {
variable interface
uplevel 1 ::stooop::_virtual [list $keyword $name $arguments] $args
parseProcedureName [uplevel 1 namespace current] $name\
fullClass procedure message
if {[llength $args]==0} { ;# no procedure body means pure virtual
set interface($fullClass) {}
}
}
}
if {$::stooop::check(objects)} {
_proc invokingProcedure {} {
if {[catch {set procedure [lindex [info level -2] 0]}]} {
# no invoking procedure
return {top level}
} elseif {\
([string length $procedure]==0)||\
[string equal $procedure namespace]\
} { ;# invoked from a namespace body
return "namespace [uplevel 2 namespace current]"
} else {
# store fully qualified name, visible from creator procedure
# invoking procedure
return [uplevel 3 namespace which -command $procedure]
}
}
}
if {$::stooop::check(procedures)||$::stooop::check(objects)} {
# gracefully handle multiple sourcing of this file:
catch {rename ::stooop::new ::stooop::_new}
# use a new new procedure instead of adding debugging code to existing
# one:
_proc ::stooop::new {classOrId args} {
variable newId
variable check
if {$check(procedures)} {
variable fullClass
variable interface
}
if {$check(objects)} {
variable creator
}
if {$check(procedures)} {
if {[string is integer $classOrId]} {
# first argument is an object identifier
# class code, if from a package, must already be loaded
set fullName $fullClass($classOrId)
} else { ;# first argument is a class
# generate constructor name:
set constructor ${classOrId}::[namespace tail $classOrId]
# force loading in case class is in a package so namespace
# commands work properly:
catch {$constructor}
set fullName [namespace qualifiers\
[uplevel 1 namespace which -command $constructor]\
]
# anticipate full class name storage in original new{} in
# order to avoid invalid object identifier error in
# checkProcedure{} when member procedure is invoked from
# within contructor, in which case full class name would
# have yet to be stored.
set fullClass([expr {$newId+1}]) $fullName
# new identifier is really incremented in original new{}
}
if {[info exists interface($fullName)]} {
error "class $fullName with pure virtual procedures should not be instanciated"
}
}
if {$check(objects)} {
# keep track of procedure in which creation occured (new
# identifier is really incremented in original new{})
set creator([expr {$newId+1}]) [invokingProcedure]
}
return [uplevel 1 ::stooop::_new $classOrId $args]
}
}
if {$::stooop::check(objects)} {
_proc ::stooop::delete {args} {
variable fullClass
variable deleter
# keep track of procedure in which deletion occured:
set procedure [invokingProcedure]
foreach id $args {
uplevel 1 ::stooop::deleteObject $fullClass($id) $id
unset fullClass($id)
set deleter($id) $procedure
}
}
}
# return the unsorted list of ancestors in class hierarchy:
_proc ::stooop::ancestors {fullClass} {
variable ancestors ;# use a cache for efficiency
variable fullBases
if {[info exists ancestors($fullClass)]} {
return $ancestors($fullClass) ;# found in the cache
}
set list {}
foreach class $fullBases($fullClass) {
set list [concat $list [list $class] [ancestors $class]]
}
set ancestors($fullClass) $list ;# save in cache
return $list
}
# since this procedure is always invoked from a debug procedure, take the
# extra level in the stack frame into account
# parameters (passed as references) that cannot be determined are not set
_proc ::stooop::debugInformation {\
className fullClassName procedureName fullProcedureName\
thisParameterName\
} {
upvar 1 $className class $fullClassName fullClass\
$procedureName procedure $fullProcedureName fullProcedure\
$thisParameterName thisParameter
variable declared
set namespace [uplevel 2 namespace current]
# not in a class namespace:
if {[lsearch -exact [array names declared] $namespace]<0} return
# remove redundant global qualifier:
set fullClass [string trimleft $namespace :]
set class [namespace tail $fullClass] ;# class name
set list [info level -2]
set first [lindex $list 0]
if {([llength $list]==0)||[string equal $first namespace]}\
return ;# not in a procedure, nothing else to do
set procedure $first
# procedure must be known at the invoker level:
set fullProcedure [uplevel 3 namespace which -command $procedure]
set procedure [namespace tail $procedure] ;# strip procedure name
if {[string equal $class $procedure]} { ;# constructor
set procedure constructor
} elseif {[string equal ~$class $procedure]} { ;# destructor
set procedure destructor
}
if {[string equal [lindex [info args $fullProcedure] 0] this]} {
# non static procedure
# object identifier is first argument:
set thisParameter [lindex $list 1]
}
}
# check that member procedure is valid for object passed as parameter:
_proc ::stooop::checkProcedure {} {
variable fullClass
debugInformation class qualifiedClass procedure qualifiedProcedure this
# static procedure, no checking possible:
if {![info exists this]} return
# in constructor, checking useless since object is not yet created:
if {[string equal $procedure constructor]} return
if {![info exists fullClass($this)]} {
error "$this is not a valid object identifier"
}
set fullName [string trimleft $fullClass($this) :]
# procedure and object classes match:
if {[string equal $fullName $qualifiedClass]} return
# restore global qualifiers to compare with internal full class array
# data
if {[lsearch -exact [ancestors ::$fullName] ::$qualifiedClass]<0} {
error "class $qualifiedClass of $qualifiedProcedure procedure not an ancestor of object $this class $fullName"
}
}
# gather current procedure data, perform substitutions and output to trace
# channel:
_proc ::stooop::traceProcedure {} {
variable trace
debugInformation class qualifiedClass procedure qualifiedProcedure this
# all debug data is available since we are for sure in a class procedure
set text $trace(procedureFormat)
regsub -all %C $text $qualifiedClass text ;# fully qualified class name
regsub -all %c $text $class text
# fully qualified procedure name:
regsub -all %P $text $qualifiedProcedure text
regsub -all %p $text $procedure text
if {[info exists this]} { ;# non static procedure
regsub -all %O $text $this text
# remaining arguments:
regsub -all %a $text [lrange [info level -1] 2 end] text
} else { ;# static procedure
regsub -all %O $text {} text
# remaining arguments:
regsub -all %a $text [lrange [info level -1] 1 end] text
}
puts $trace(procedureChannel) $text
}
# check that class data member is accessed within procedure of identical
# class
# then if procedure is not static, check that only data belonging to the
# object passed as parameter is accessed
_proc ::stooop::checkData {array name operation} {
scan $name %u,%s identifier member
# ignore internally defined members:
if {[info exists member]&&[string equal $member _derived]} return
debugInformation class qualifiedClass procedure qualifiedProcedure this
# no checking can be done outside of a class namespace:
if {![info exists class]} return
# determine array full name:
set array [uplevel 1 [list namespace which -variable $array]]
if {![info exists procedure]} { ;# inside a class namespace
# compare with empty named array fully qualified name:
if {![string equal $array ::${qualifiedClass}::]} {
# trace command error message is automatically prepended and
# indicates operation
error\
"class access violation in class $qualifiedClass namespace"
}
return ;# done
}
# ignore internal copy procedure:
if {[string equal $qualifiedProcedure ::stooop::copy]} return
if {![string equal $array ::${qualifiedClass}::]} {
# compare with empty named array fully qualified name
# trace command error message is automatically prepended and
# indicates operation
error "class access violation in procedure $qualifiedProcedure"
}
# static procedure, all objects can be accessed:
if {![info exists this]} return
# static data members can be accessed:
if {![info exists identifier]} return
# check that accessed data belongs to this object:
if {$this!=$identifier} {
error "object $identifier access violation in procedure $qualifiedProcedure acting on object $this"
}
}
# gather accessed data member information, perform substitutions and output
# to trace channel
_proc ::stooop::traceData {array name operation} {
variable trace
scan $name %u,%s identifier member
# ignore internally defined members:
if {[info exists member]&&[string equal $member _derived]} return
# ignore internal destruction:
if {\
![catch {lindex [info level -1] 0} procedure]&&\
[string equal ::stooop::deleteObject $procedure]\
} return
set class {} ;# in case we are outside a class
set qualifiedClass {}
set procedure {} ;# in case we are outside a class procedure
set qualifiedProcedure {}
debugInformation class qualifiedClass procedure qualifiedProcedure this
set text $trace(dataFormat)
regsub -all %C $text $qualifiedClass text ;# fully qualified class name
regsub -all %c $text $class text
if {[info exists member]} {
regsub -all %m $text $member text
} else {
regsub -all %m $text $name text ;# static member
}
# fully qualified procedure name:
regsub -all %P $text $qualifiedProcedure text
regsub -all %p $text $procedure text
# fully qualified array name with global qualifiers stripped:
regsub -all %A $text [string trimleft\
[uplevel 1 [list namespace which -variable $array]] :\
] text
if {[info exists this]} { ;# non static procedure
regsub -all %O $text $this text
} else { ;# static procedure
regsub -all %O $text {} text
}
array set string {r read w write u unset}
regsub -all %o $text $string($operation) text
if {[string equal $operation u]} {
regsub -all %v $text {} text ;# no value when unsetting
} else {
regsub -all %v $text [uplevel 1 set ${array}($name)] text
}
puts $trace(dataChannel) $text
}
if {$::stooop::check(objects)} {
# print existing objects along with creation procedure, with optional
# class pattern (see the string Tcl command manual)
_proc ::stooop::printObjects {{pattern *}} {
variable fullClass
variable creator
puts "stooop::printObjects invoked from [invokingProcedure]:"
foreach id [lsort -integer [array names fullClass]] {
if {[string match $pattern $fullClass($id)]} {
puts "$fullClass($id)\($id\) + $creator($id)"
}
}
}
# record all existing objects for later report:
_proc ::stooop::record {} {
variable fullClass
variable checkpointFullClass
puts "stooop::record invoked from [invokingProcedure]"
catch {unset checkpointFullClass}
array set checkpointFullClass [array get fullClass]
}
# print all new or deleted object since last record, with optional class
# pattern:
_proc ::stooop::report {{pattern *}} {
variable fullClass
variable checkpointFullClass
variable creator
variable deleter
puts "stooop::report invoked from [invokingProcedure]:"
set checkpointIds [lsort -integer [array names checkpointFullClass]]
set currentIds [lsort -integer [array names fullClass]]
foreach id $currentIds {
if {\
[string match $pattern $fullClass($id)]&&\
([lsearch -exact $checkpointIds $id]<0)\
} {
puts "+ $fullClass($id)\($id\) + $creator($id)"
}
}
foreach id $checkpointIds {
if {\
[string match $pattern $checkpointFullClass($id)]&&\
([lsearch -exact $currentIds $id]<0)\
} {
puts "- $checkpointFullClass($id)\($id\) - $deleter($id) + $creator($id)"
}
}
}
}
}
|