stooop.tcl

Go to the documentation of this file.
00001 /*  stooop*/
00002 /*  Simple Tcl Only Object Oriented Programming*/
00003 /*  An object oriented extension to the Tcl programming language*/
00004 /* */
00005 /*  Copyright (c) 2002 by Jean-Luc Fontaine <jfontain@free.fr>.*/
00006 /*  This code may be distributed under the same terms as Tcl.*/
00007 /* */
00008 /*  $Id: stooop.tcl,v 1.9 2004/01/15 06:36:14 andreas_kupries Exp $*/
00009 
00010 
00011 /*  check whether empty named arrays and array unset are supported:*/
00012 package require Tcl 8.3
00013 
00014 package provide stooop 4.4.1
00015 
00016 /*  rename proc before it is overloaded, ignore error in case of multiple*/
00017 /*  inclusion of this file:*/
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         /*  use same channel for both traces*/
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         /*  eventually override with user defined format:*/
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         /*  default format:*/
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  ;/*  export public commands*/
00085 
00086     if {![info exists newId]} {
00087         /*  initialize object id counter only once even if this file is sourced*/
00088         /*  several times:*/
00089         variable newId 0
00090     }
00091 
00092     /*  create an object of specified class or copy an existing object:*/
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     /*  delete object data starting at specified class layer and going up the base*/
00145     /*  class hierarchy if any*/
00146     /*  invoke the destructor for the object class and unset all the object data*/
00147     /*  members for the class*/
00148     /*  the destructor will in turn delete the base classes layers*/
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     /*  copy object data members from one object to another:*/
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 /*  virtual operator, to be placed before proc*/
00235 /*  virtualize a member procedure, determine whether it is a pure virtual, check*/
00236 /*  for procedures that cannot be virtualized*/
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 /*  copy flag is set for user defined copy constructor:*/
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 /*  generate default copy procedure which may be overriden by the user for any*/
00541 /*  class layer:*/
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     /*  if one or more environment variables are set, we are in debugging mode*/
00561 
00562     /*  gracefully handle multiple sourcing of this file:*/
00563     catch {rename ::stooop::class ::stooop::_class}
00564     /*  use a new class procedure instead of adding debugging code to existing one*/
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 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1