source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/stooop/stooop.tcl @ 8597867d

1941-docker-image-to-testdevel
Last change on this file since 8597867d was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

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