This file is indexed.

/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)"
                }
            }
        }
    }

}