~ubuntu-branches/ubuntu/utopic/critcl/utopic

« back to all changes in this revision

Viewing changes to lib/snit/snit.tcl

  • Committer: Package Import Robot
  • Author(s): Andrew Shadura
  • Date: 2013-05-11 00:08:06 UTC
  • Revision ID: package-import@ubuntu.com-20130511000806-7hq1zc3fnn0gat79
Tags: upstream-3.1.9
ImportĀ upstreamĀ versionĀ 3.1.9

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#-----------------------------------------------------------------------
 
2
# TITLE:
 
3
#       snit.tcl
 
4
#
 
5
# AUTHOR:
 
6
#       Will Duquette
 
7
#
 
8
# DESCRIPTION:
 
9
#       Simple Now In Tcl, a simple object system in Pure Tcl.
 
10
#
 
11
#-----------------------------------------------------------------------
 
12
 
 
13
package provide snit 0.97
 
14
 
 
15
#-----------------------------------------------------------------------
 
16
# Namespace
 
17
 
 
18
namespace eval ::snit:: {
 
19
    namespace export \
 
20
        compile type widget widgetadaptor typemethod method macro
 
21
}
 
22
 
 
23
#-----------------------------------------------------------------------
 
24
# Some Snit variables
 
25
 
 
26
namespace eval ::snit:: {
 
27
    variable reservedArgs {type selfns win self}
 
28
 
 
29
    # If true, get a pretty, fixed-up stack trace.  Otherwise, get raw
 
30
    # stack trace.
 
31
    # NOTE: Not Yet Implemented
 
32
    variable prettyStackTrace 1
 
33
}
 
34
 
 
35
#-----------------------------------------------------------------------
 
36
# Snit Type Implementation template
 
37
 
 
38
namespace eval ::snit:: {
 
39
    # Template type definition: All internal and user-visible Snit
 
40
    # implementation code.
 
41
    #
 
42
    # The following placeholders will automatically be replaced with
 
43
    # the client's code, in two passes:
 
44
    #
 
45
    # First pass:
 
46
    # %COMPILEDDEFS%  The compiled type definition.
 
47
    #
 
48
    # Second pass:
 
49
    # %TYPE%          The fully qualified type name.
 
50
    # %IVARDECS%      Instance variable declarations
 
51
    # %TVARDECS%      Type variable declarations
 
52
    # %TCONSTBODY%    Type constructor body
 
53
    # %INSTANCEVARS%  The compiled instance variable initialization code.
 
54
    # %TYPEVARS%      The compiled type variable initialization code.
 
55
 
 
56
    # This is the overall type template.
 
57
    variable typeTemplate
 
58
 
 
59
    # This is the normal type proc
 
60
    variable nominalTypeProc
 
61
 
 
62
    # This is the "-hastypemethods no" type proc
 
63
    variable simpleTypeProc
 
64
}
 
65
 
 
66
set ::snit::typeTemplate {
 
67
 
 
68
    #-------------------------------------------------------------------
 
69
    # The type's namespace definition and the user's type variables
 
70
 
 
71
    namespace eval %TYPE% {%TYPEVARS%
 
72
    }
 
73
 
 
74
    #----------------------------------------------------------------
 
75
    # Commands for use in methods, typemethods, etc.
 
76
    #
 
77
    # These are implemented as aliases into the Snit runtime library.
 
78
 
 
79
    interp alias {} %TYPE%::installhull  {} ::snit::RT.installhull %TYPE%
 
80
    interp alias {} %TYPE%::install      {} ::snit::RT.install %TYPE%
 
81
    interp alias {} %TYPE%::typevariable {} ::variable
 
82
    interp alias {} %TYPE%::variable     {} ::snit::RT.variable
 
83
    interp alias {} %TYPE%::mytypevar    {} ::snit::RT.mytypevar %TYPE%
 
84
    interp alias {} %TYPE%::typevarname  {} ::snit::RT.mytypevar %TYPE%
 
85
    interp alias {} %TYPE%::myvar        {} ::snit::RT.myvar
 
86
    interp alias {} %TYPE%::varname      {} ::snit::RT.myvar
 
87
    interp alias {} %TYPE%::codename     {} ::snit::RT.codename %TYPE%
 
88
    interp alias {} %TYPE%::myproc       {} ::snit::RT.myproc %TYPE%
 
89
    interp alias {} %TYPE%::mymethod     {} ::snit::RT.mymethod 
 
90
    interp alias {} %TYPE%::mytypemethod {} ::snit::RT.mytypemethod %TYPE%
 
91
    interp alias {} %TYPE%::from         {} ::snit::RT.from %TYPE%
 
92
 
 
93
    #-------------------------------------------------------------------
 
94
    # Snit's internal variables
 
95
 
 
96
    namespace eval %TYPE% {
 
97
        # Array: General Snit Info
 
98
        #
 
99
        # ns:                The type's namespace
 
100
        # hasinstances:      T or F, from pragma -hasinstances.
 
101
        # simpledispatch:    T or F, from pragma -hasinstances.
 
102
        # canreplace:        T or F, from pragma -canreplace.
 
103
        # counter:           Count of instances created so far.
 
104
        # widgetclass:       Set by widgetclass statement.
 
105
        # hulltype:          Hull type (frame or toplevel) for widgets only.
 
106
        # exceptmethods:     Methods explicitly not delegated to *
 
107
        # excepttypemethods: Methods explicitly not delegated to *
 
108
        # tvardecs:          Type variable declarations--for dynamic methods
 
109
        # ivardecs:          Instance variable declarations--for dyn. methods
 
110
        typevariable Snit_info
 
111
        set Snit_info(ns)      %TYPE%::
 
112
        set Snit_info(hasinstances) 1
 
113
        set Snit_info(simpledispatch) 0
 
114
        set Snit_info(canreplace) 0
 
115
        set Snit_info(counter) 0
 
116
        set Snit_info(widgetclass) {}
 
117
        set Snit_info(hulltype) frame
 
118
        set Snit_info(exceptmethods) {}
 
119
        set Snit_info(excepttypemethods) {}
 
120
        set Snit_info(tvardecs) {%TVARDECS%}
 
121
        set Snit_info(ivardecs) {%IVARDECS%}
 
122
 
 
123
        # Array: Public methods of this type.
 
124
        # The index is the method name, or "*".
 
125
        # The value is [list $pattern $componentName], where
 
126
        # $componentName is "" for normal methods.
 
127
        typevariable Snit_typemethodInfo
 
128
        array unset Snit_typemethodInfo
 
129
 
 
130
        # Array: Public methods of instances of this type.
 
131
        # The index is the method name, or "*".
 
132
        # The value is [list $pattern $componentName], where
 
133
        # $componentName is "" for normal methods.
 
134
        typevariable Snit_methodInfo
 
135
        array unset Snit_methodInfo
 
136
 
 
137
        # Array: option information.  See dictionary.txt.
 
138
        typevariable Snit_optionInfo
 
139
        array unset Snit_optionInfo
 
140
        set Snit_optionInfo(local)     {}
 
141
        set Snit_optionInfo(delegated) {}
 
142
        set Snit_optionInfo(starcomp)  {}
 
143
        set Snit_optionInfo(except)    {}
 
144
    }
 
145
 
 
146
    #----------------------------------------------------------------
 
147
    # Compiled Procs
 
148
    #
 
149
    # These commands are created or replaced during compilation:
 
150
 
 
151
 
 
152
    # Snit_instanceVars selfns
 
153
    #
 
154
    # Initializes the instance variables, if any.  Called during
 
155
    # instance creation.
 
156
    
 
157
    proc %TYPE%::Snit_instanceVars {selfns} {
 
158
        %INSTANCEVARS%
 
159
    }
 
160
 
 
161
    # Type Constructor
 
162
    proc %TYPE%::Snit_typeconstructor {type} {
 
163
        %TVARDECS%
 
164
        %TCONSTBODY%
 
165
    }
 
166
 
 
167
    #----------------------------------------------------------------
 
168
    # Default Procs
 
169
    #
 
170
    # These commands might be replaced during compilation:
 
171
 
 
172
    # Snit_destructor type selfns win self
 
173
    #
 
174
    # Default destructor for the type.  By default, it does
 
175
    # nothing.  It's replaced by any user destructor.
 
176
    # For types, it's called by method destroy; for widgettypes,
 
177
    # it's called by a destroy event handler.
 
178
 
 
179
    proc %TYPE%::Snit_destructor {type selfns win self} { }
 
180
 
 
181
    #----------------------------------------------------------
 
182
    # Compiled Definitions
 
183
 
 
184
    %COMPILEDDEFS%
 
185
 
 
186
    #----------------------------------------------------------
 
187
    # Finally, call the Type Constructor
 
188
 
 
189
    %TYPE%::Snit_typeconstructor %TYPE%
 
190
}
 
191
 
 
192
#-----------------------------------------------------------------------
 
193
# Type procs
 
194
#
 
195
# These procs expect the fully-qualified type name to be 
 
196
# substituted in for %TYPE%.
 
197
 
 
198
# This is the nominal type proc.  It supports typemethods and
 
199
# delegated typemethods.
 
200
set ::snit::nominalTypeProc {
 
201
    # Type dispatcher function.  Note: This function lives
 
202
    # in the parent of the %TYPE% namespace!  All accesses to 
 
203
    # %TYPE% variables and methods must be qualified!
 
204
    proc %TYPE% {{method ""} args} {
 
205
        # First, if there's no method, and no args, and there's a create
 
206
        # method, and this isn't a widget, then method is "create" and 
 
207
        # "args" is %AUTO%.
 
208
        if {$method eq "" && [llength $args] == 0} {
 
209
            ::variable %TYPE%::Snit_info
 
210
 
 
211
            if {$Snit_info(hasinstances) && !$Snit_info(isWidget)} {
 
212
                set method create
 
213
                lappend args %AUTO%
 
214
            } else {
 
215
                error "wrong \# args: should be \"%TYPE% method args\""
 
216
            }
 
217
        }
 
218
 
 
219
        # Next, retrieve the command.
 
220
        while 1 {
 
221
            if {[catch {set %TYPE%::Snit_typemethodCache($method)} commandRec]} {
 
222
                set commandRec [::snit::RT.CacheTypemethodCommand %TYPE% $method]
 
223
 
 
224
                if {[llength $commandRec] == 0} {
 
225
                    return -code error  "\"%TYPE% $method\" is not defined"
 
226
                }
 
227
            }
 
228
 
 
229
            # If we've got a real command, break.
 
230
            if {[lindex $commandRec 0] == 0} {
 
231
                break
 
232
            }
 
233
 
 
234
            # Otherwise, we need to look up again...if we can.
 
235
            if {[llength $args] == 0} {
 
236
                return -code error \
 
237
                 "wrong number args: should be \"%TYPE% $method method args\""
 
238
            }
 
239
 
 
240
            lappend method [lindex $args 0]
 
241
            set args [lrange $args 1 end]
 
242
        }
 
243
 
 
244
        set command [lindex $commandRec 1]
 
245
 
 
246
        # Pass along the return code unchanged.
 
247
        set retval [catch {uplevel $command $args} result]
 
248
 
 
249
        if {$retval} {
 
250
            if {$retval == 1} {
 
251
                global errorInfo
 
252
                global errorCode
 
253
                return -code error -errorinfo $errorInfo \
 
254
                    -errorcode $errorCode $result
 
255
            } else {
 
256
                return -code $retval $result
 
257
            }
 
258
        }
 
259
 
 
260
        return $result
 
261
    }
 
262
}
 
263
 
 
264
# This is the simplified type proc for when there are no typemethods
 
265
# except create.  In this case, it doesn't take a method argument;
 
266
# the method is always "create".
 
267
set ::snit::simpleTypeProc {
 
268
    # Type dispatcher function.  Note: This function lives
 
269
    # in the parent of the %TYPE% namespace!  All accesses to 
 
270
    # %TYPE% variables and methods must be qualified!
 
271
    proc %TYPE% {args} {
 
272
        ::variable %TYPE%::Snit_info
 
273
 
 
274
        # FIRST, if the are no args, the single arg is %AUTO%
 
275
        if {[llength $args] == 0} {
 
276
            if {$Snit_info(isWidget)} {
 
277
                error "wrong \# args: should be \"%TYPE% name args\""
 
278
            }
 
279
            
 
280
            lappend args %AUTO%
 
281
        }
 
282
 
 
283
        # NEXT, we're going to call the create method.
 
284
        # Pass along the return code unchanged.
 
285
        if {$Snit_info(isWidget)} {
 
286
            set command [list ::snit::RT.widget.typemethod.create %TYPE%]
 
287
        } else {
 
288
            set command [list ::snit::RT.type.typemethod.create %TYPE%]
 
289
        }
 
290
 
 
291
        set retval [catch {uplevel $command $args} result]
 
292
 
 
293
        if {$retval} {
 
294
            if {$retval == 1} {
 
295
                global errorInfo
 
296
                global errorCode
 
297
                return -code error -errorinfo $errorInfo \
 
298
                    -errorcode $errorCode $result
 
299
            } else {
 
300
                return -code $retval $result
 
301
            }
 
302
        }
 
303
 
 
304
        return $result
 
305
    }
 
306
}
 
307
 
 
308
#-----------------------------------------------------------------------
 
309
# Instance procs
 
310
#
 
311
# The following must be substituted into these proc bodies:
 
312
#
 
313
# %SELFNS%       The instance namespace
 
314
# %WIN%          The original instance name
 
315
# %TYPE%         The fully-qualified type name
 
316
#
 
317
 
 
318
# Nominal instance proc body: supports method caching and delegation.
 
319
#
 
320
# proc $instanceName {method args} ....
 
321
set ::snit::nominalInstanceProc {
 
322
    set self [set %SELFNS%::Snit_instance]
 
323
 
 
324
    while {1} {
 
325
        if {[catch {set %SELFNS%::Snit_methodCache($method)} commandRec]} {
 
326
            set commandRec [snit::RT.CacheMethodCommand %TYPE% %SELFNS% %WIN% $self $method]
 
327
                
 
328
            if {[llength $commandRec] == 0} {
 
329
                return -code error \
 
330
                    "\"$self $method\" is not defined"
 
331
            }
 
332
        }
 
333
 
 
334
        # If we've got a real command, break.
 
335
        if {[lindex $commandRec 0] == 0} {
 
336
            break
 
337
        }
 
338
 
 
339
        # Otherwise, we need to look up again...if we can.
 
340
        if {[llength $args] == 0} {
 
341
            return -code error \
 
342
                "wrong number args: should be \"$self $method method args\""
 
343
        }
 
344
 
 
345
        lappend method [lindex $args 0]
 
346
        set args [lrange $args 1 end]
 
347
    }
 
348
 
 
349
    set command [lindex $commandRec 1]
 
350
 
 
351
    # Pass along the return code unchanged.
 
352
    set retval [catch {uplevel 1 $command $args} result]
 
353
 
 
354
    if {$retval} {
 
355
        if {$retval == 1} {
 
356
            global errorInfo
 
357
            global errorCode
 
358
            return -code error -errorinfo $errorInfo \
 
359
                -errorcode $errorCode $result
 
360
        } else {
 
361
            return -code $retval $result
 
362
        }
 
363
    }
 
364
 
 
365
    return $result
 
366
}
 
367
 
 
368
# Simplified method proc body: No delegation allowed; no support for
 
369
# upvar or exotic return codes or hierarchical methods.  Designed for 
 
370
# max speed for simple types.
 
371
#
 
372
# proc $instanceName {method args} ....
 
373
 
 
374
set ::snit::simpleInstanceProc {
 
375
    set self [set %SELFNS%::Snit_instance]
 
376
 
 
377
    if {[lsearch -exact ${%TYPE%::Snit_methods} $method] == -1} {
 
378
        set optlist [join ${%TYPE%::Snit_methods} ", "]
 
379
        set optlist [linsert $optlist "end-1" "or"]
 
380
        error "bad option \"$method\": must be $optlist"
 
381
    }
 
382
 
 
383
    eval [linsert $args 0 \
 
384
              %TYPE%::Snit_method$method %TYPE% %SELFNS% %WIN% $self] 
 
385
}
 
386
 
 
387
 
 
388
#=======================================================================
 
389
# Snit Type Definition
 
390
#
 
391
# These are the procs used to define Snit types, widgets, and 
 
392
# widgetadaptors.
 
393
 
 
394
 
 
395
#-----------------------------------------------------------------------
 
396
# Snit Compilation Variables
 
397
#
 
398
# The following variables are used while Snit is compiling a type,
 
399
# and are disposed afterwards.
 
400
 
 
401
namespace eval ::snit:: {
 
402
    # The compiler variable contains the name of the slave interpreter
 
403
    # used to compile type definitions.
 
404
    variable compiler ""
 
405
 
 
406
    # The compile array accumulates information about the type or
 
407
    # widgettype being compiled.  It is cleared before and after each
 
408
    # compilation.  It has these indices:
 
409
    #
 
410
    # type:                  The name of the type being compiled, for use
 
411
    #                        in compilation procs.
 
412
    # defs:                  Compiled definitions, both standard and client.
 
413
    # which:                 type, widget, widgetadaptor
 
414
    # instancevars:          Instance variable definitions and initializations.
 
415
    # ivprocdec:             Instance variable proc declarations.
 
416
    # tvprocdec:             Type variable proc declarations.
 
417
    # typeconstructor:       Type constructor body.
 
418
    # widgetclass:           The widgetclass, for snit::widgets, only
 
419
    # hasoptions:            False, initially; set to true when first
 
420
    #                        option is defined.
 
421
    # localoptions:          Names of local options.
 
422
    # delegatedoptions:      Names of delegated options.
 
423
    # localmethods:          Names of locally defined methods.
 
424
    # delegatesmethods:      no if no delegated methods, yes otherwise.
 
425
    # hashierarchic       :  no if no hierarchic methods, yes otherwise.
 
426
    # components:            Names of defined components.
 
427
    # typecomponents:        Names of defined typecomponents.
 
428
    # typevars:              Typevariable definitions and initializations.
 
429
    # varnames:              Names of instance variables
 
430
    # typevarnames           Names of type variables
 
431
    # hasconstructor         False, initially; true when constructor is
 
432
    #                        defined.
 
433
    # resource-$opt          The option's resource name
 
434
    # class-$opt             The option's class
 
435
    # -default-$opt          The option's default value
 
436
    # -validatemethod-$opt   The option's validate method
 
437
    # -configuremethod-$opt  The option's configure method
 
438
    # -cgetmethod-$opt       The option's cget method.
 
439
    # -hastypeinfo           The -hastypeinfo pragma
 
440
    # -hastypedestroy        The -hastypedestroy pragma
 
441
    # -hastypemethods        The -hastypemethods pragma
 
442
    # -hasinfo               The -hasinfo pragma
 
443
    # -hasinstances          The -hasinstances pragma
 
444
    # -simpledispatch        The -simpledispatch pragma
 
445
    # -canreplace            The -canreplace pragma
 
446
    variable compile
 
447
 
 
448
    # This variable accumulates method dispatch information; it has
 
449
    # the same structure as the %TYPE%::Snit_methodInfo array, and is
 
450
    # used to initialize it.
 
451
    variable methodInfo
 
452
 
 
453
    # This variable accumulates typemethod dispatch information; it has
 
454
    # the same structure as the %TYPE%::Snit_typemethodInfo array, and is
 
455
    # used to initialize it.
 
456
    variable typemethodInfo
 
457
 
 
458
    # The following variable lists the reserved type definition statement
 
459
    # names, e.g., the names you can't use as macros.  It's built at
 
460
    # compiler definition time using "info commands".
 
461
    variable reservedwords {}
 
462
}
 
463
 
 
464
#-----------------------------------------------------------------------
 
465
# type compilation commands
 
466
#
 
467
# The type and widgettype commands use a slave interpreter to compile
 
468
# the type definition.  These are the procs
 
469
# that are aliased into it.
 
470
 
 
471
# Initialize the compiler
 
472
proc ::snit::Comp.Init {} {
 
473
    variable compiler
 
474
    variable reservedwords
 
475
 
 
476
    if {$compiler eq ""} {
 
477
        # Create the compiler's interpreter
 
478
        set compiler [interp create]
 
479
 
 
480
        # Initialize the interpreter
 
481
        $compiler eval {
 
482
            # Load package information
 
483
            # TBD: see if this can be moved outside.
 
484
            catch {package require ::snit::__does_not_exist__}
 
485
 
 
486
            # Protect some Tcl commands our type definitions
 
487
            # will shadow.
 
488
            rename proc _proc
 
489
            rename variable _variable
 
490
        }
 
491
 
 
492
        # Define compilation aliases.
 
493
        $compiler alias pragma          ::snit::Comp.statement.pragma
 
494
        $compiler alias widgetclass     ::snit::Comp.statement.widgetclass
 
495
        $compiler alias hulltype        ::snit::Comp.statement.hulltype
 
496
        $compiler alias constructor     ::snit::Comp.statement.constructor
 
497
        $compiler alias destructor      ::snit::Comp.statement.destructor
 
498
        $compiler alias option          ::snit::Comp.statement.option
 
499
        $compiler alias oncget          ::snit::Comp.statement.oncget
 
500
        $compiler alias onconfigure     ::snit::Comp.statement.onconfigure
 
501
        $compiler alias method          ::snit::Comp.statement.method
 
502
        $compiler alias typemethod      ::snit::Comp.statement.typemethod
 
503
        $compiler alias typeconstructor ::snit::Comp.statement.typeconstructor
 
504
        $compiler alias proc            ::snit::Comp.statement.proc
 
505
        $compiler alias typevariable    ::snit::Comp.statement.typevariable
 
506
        $compiler alias variable        ::snit::Comp.statement.variable
 
507
        $compiler alias typecomponent   ::snit::Comp.statement.typecomponent
 
508
        $compiler alias component       ::snit::Comp.statement.component
 
509
        $compiler alias delegate        ::snit::Comp.statement.delegate
 
510
        $compiler alias expose          ::snit::Comp.statement.expose
 
511
 
 
512
        # Get the list of reserved words
 
513
        set reservedwords [$compiler eval {info commands}]
 
514
    }
 
515
}
 
516
 
 
517
# Compile a type definition, and return the results as a list of two
 
518
# items: the fully-qualified type name, and a script that will define
 
519
# the type when executed.
 
520
#
 
521
# which         type, widget, or widgetadaptor
 
522
# type          the type name
 
523
# body          the type definition
 
524
proc ::snit::Comp.Compile {which type body} {
 
525
    variable typeTemplate
 
526
    variable nominalTypeProc
 
527
    variable simpleTypeProc
 
528
    variable compile
 
529
    variable compiler
 
530
    variable methodInfo
 
531
    variable typemethodInfo
 
532
 
 
533
    # FIRST, qualify the name.
 
534
    if {![string match "::*" $type]} {
 
535
        # Get caller's namespace; 
 
536
        # append :: if not global namespace.
 
537
        set ns [uplevel 2 namespace current]
 
538
        if {"::" != $ns} {
 
539
            append ns "::"
 
540
        }
 
541
        
 
542
        set type "$ns$type"
 
543
    }
 
544
 
 
545
    # NEXT, create and initialize the compiler, if needed.
 
546
    Comp.Init
 
547
 
 
548
    # NEXT, initialize the class data
 
549
    array unset methodInfo
 
550
    array unset typemethodInfo
 
551
 
 
552
    array unset compile
 
553
    set compile(type) $type
 
554
    set compile(defs) {}
 
555
    set compile(which) $which
 
556
    set compile(hasoptions) no
 
557
    set compile(localoptions) {}
 
558
    set compile(instancevars) {}
 
559
    set compile(typevars) {}
 
560
    set compile(delegatedoptions) {}
 
561
    set compile(ivprocdec) {}
 
562
    set compile(tvprocdec) {}
 
563
    set compile(typeconstructor) {}
 
564
    set compile(widgetclass) {}
 
565
    set compile(hulltype) {}
 
566
    set compile(localmethods) {}
 
567
    set compile(delegatesmethods) no
 
568
    set compile(hashierarchic) no
 
569
    set compile(components) {}
 
570
    set compile(typecomponents) {}
 
571
    set compile(varnames) {}
 
572
    set compile(typevarnames) {}
 
573
    set compile(hasconstructor) no
 
574
    set compile(-hastypedestroy) yes
 
575
    set compile(-hastypeinfo) yes
 
576
    set compile(-hastypemethods) yes
 
577
    set compile(-hasinfo) yes
 
578
    set compile(-hasinstances) yes
 
579
    set compile(-simpledispatch) no
 
580
    set compile(-canreplace) no
 
581
 
 
582
    set isWidget [string match widget* $which]
 
583
    set isWidgetAdaptor [string match widgetadaptor $which]
 
584
 
 
585
    # NEXT, Evaluate the type's definition in the class interpreter.
 
586
    $compiler eval $body
 
587
 
 
588
    # NEXT, Add the standard definitions
 
589
    append compile(defs) \
 
590
        "\nset %TYPE%::Snit_info(isWidget) $isWidget\n"
 
591
 
 
592
    append compile(defs) \
 
593
        "\nset %TYPE%::Snit_info(isWidgetAdaptor) $isWidgetAdaptor\n"
 
594
 
 
595
    # Indicate whether the type can create instances that replace
 
596
    # existing commands.
 
597
    append compile(defs) "\nset %TYPE%::Snit_info(canreplace) $compile(-canreplace)\n"
 
598
 
 
599
 
 
600
    # Check pragmas for conflict.
 
601
    
 
602
    if {!$compile(-hastypemethods) && !$compile(-hasinstances)} {
 
603
        error "$which $type has neither typemethods nor instances"
 
604
    }
 
605
 
 
606
    if {$compile(-simpledispatch) && $compile(delegatesmethods)} {
 
607
        error "$which $type requests -simpledispatch but delegates methods."
 
608
    }
 
609
 
 
610
    if {$compile(-simpledispatch) && $compile(hashierarchic)} {
 
611
        error "$which $type requests -simpledispatch but defines hierarchical methods."
 
612
    }
 
613
 
 
614
    # If there are typemethods, define the standard typemethods and
 
615
    # the nominal type proc.  Otherwise define the simple type proc.
 
616
    if {$compile(-hastypemethods)} {
 
617
        # Add the info typemethod unless the pragma forbids it.
 
618
        if {$compile(-hastypeinfo)} {
 
619
            Comp.statement.delegate typemethod info \
 
620
                using {::snit::RT.typemethod.info %t}
 
621
        }
 
622
 
 
623
        # Add the destroy typemethod unless the pragma forbids it.
 
624
        if {$compile(-hastypedestroy)} {
 
625
            Comp.statement.delegate typemethod destroy \
 
626
                using {::snit::RT.typemethod.destroy %t}
 
627
        }
 
628
 
 
629
        # Add the nominal type proc.
 
630
        append compile(defs) $nominalTypeProc
 
631
    } else {
 
632
        # Add the simple type proc.
 
633
        append compile(defs) $simpleTypeProc
 
634
    }
 
635
 
 
636
    # Add standard methods/typemethods that only make sense if the
 
637
    # type has instances.
 
638
    if {$compile(-hasinstances)} {
 
639
        # If we're using simple dispatch, remember that.
 
640
        if {$compile(-simpledispatch)} {
 
641
            append compile(defs) "\nset %TYPE%::Snit_info(simpledispatch) 1\n"
 
642
        }
 
643
 
 
644
        # Add the info method unless the pragma forbids it.
 
645
        if {$compile(-hasinfo)} {
 
646
            if {!$compile(-simpledispatch)} {
 
647
                Comp.statement.delegate method info \
 
648
                    using {::snit::RT.method.info %t %n %w %s}
 
649
            } else {
 
650
                Comp.statement.method info {args} {
 
651
                    eval [linsert $args 0 \
 
652
                              ::snit::RT.method.info $type $selfns $win $self]
 
653
                }
 
654
            }
 
655
        }
 
656
        
 
657
        # Add the option handling stuff if there are any options.
 
658
        if {$compile(hasoptions)} {
 
659
            Comp.statement.variable options
 
660
 
 
661
            if {!$compile(-simpledispatch)} {
 
662
                Comp.statement.delegate method cget \
 
663
                    using {::snit::RT.method.cget %t %n %w %s}
 
664
                Comp.statement.delegate method configurelist \
 
665
                    using {::snit::RT.method.configurelist %t %n %w %s}
 
666
                Comp.statement.delegate method configure \
 
667
                    using {::snit::RT.method.configure %t %n %w %s}
 
668
            } else {
 
669
                Comp.statement.method cget {args} {
 
670
                    eval [linsert $args 0 \
 
671
                              ::snit::RT.method.cget $type $selfns $win $self]
 
672
                }
 
673
                Comp.statement.method configurelist {args} {
 
674
                    eval [linsert $args 0 \
 
675
                              ::snit::RT.method.configurelist $type $selfns $win $self]
 
676
                }
 
677
                Comp.statement.method configure {args} {
 
678
                    eval [linsert $args 0 \
 
679
                              ::snit::RT.method.configure $type $selfns $win $self]
 
680
                }
 
681
            }
 
682
        }
 
683
 
 
684
        # Add a default constructor, if they haven't already defined one.
 
685
        # If there are options, it will configure args; otherwise it
 
686
        # will do nothing.
 
687
        if {!$compile(hasconstructor)} {
 
688
            if {$compile(hasoptions)} {
 
689
                Comp.statement.constructor {args} {
 
690
                    $self configurelist $args
 
691
                }
 
692
            } else {
 
693
                Comp.statement.constructor {} {}
 
694
            }
 
695
        }
 
696
        
 
697
        if {!$isWidget} {
 
698
            if {!$compile(-simpledispatch)} {
 
699
                Comp.statement.delegate method destroy \
 
700
                    using {::snit::RT.method.destroy %t %n %w %s}
 
701
            } else {
 
702
                Comp.statement.method destroy {args} {
 
703
                    eval [linsert $args 0 \
 
704
                              ::snit::RT.method.destroy $type $selfns $win $self]
 
705
                }
 
706
            }
 
707
 
 
708
            Comp.statement.delegate typemethod create \
 
709
                using {::snit::RT.type.typemethod.create %t}
 
710
        } else {
 
711
            Comp.statement.delegate typemethod create \
 
712
                using {::snit::RT.widget.typemethod.create %t}
 
713
        }
 
714
 
 
715
        # Save the list of method names, for -simpledispatch; otherwise,
 
716
        # save the method info. 
 
717
        if {$compile(-simpledispatch)} {
 
718
            append compile(defs) \
 
719
                "\nset %TYPE%::Snit_methods [list $compile(localmethods)]\n"
 
720
        } else {
 
721
            append compile(defs) \
 
722
                "\narray set %TYPE%::Snit_methodInfo [list [array get methodInfo]]\n"
 
723
        }
 
724
 
 
725
    } else {
 
726
        append compile(defs) "\nset %TYPE%::Snit_info(hasinstances) 0\n"
 
727
    }
 
728
 
 
729
    # NEXT, compiling the type definition built up a set of information
 
730
    # about the type's locally defined options; add this information to
 
731
    # the compiled definition.
 
732
    Comp.SaveOptionInfo
 
733
 
 
734
    # NEXT, compiling the type definition built up a set of information
 
735
    # about the typemethods; save the typemethod info.
 
736
    append compile(defs) \
 
737
        "\narray set %TYPE%::Snit_typemethodInfo [list [array get typemethodInfo]]\n"
 
738
 
 
739
    # NEXT, if this is a widget define the hull component if it isn't
 
740
    # already defined.
 
741
    if {$isWidget} {
 
742
        Comp.DefineComponent hull
 
743
    }
 
744
 
 
745
    # NEXT, substitute the compiled definition into the type template
 
746
    # to get the type definition script.
 
747
    set defscript [Expand $typeTemplate \
 
748
                       %COMPILEDDEFS% $compile(defs)]
 
749
 
 
750
    # NEXT, substitute the defined macros into the type definition script.
 
751
    # This is done as a separate step so that the compile(defs) can 
 
752
    # contain the macros defined below.
 
753
 
 
754
    set defscript [Expand $defscript \
 
755
                       %TYPE%         $type \
 
756
                       %IVARDECS%     $compile(ivprocdec) \
 
757
                       %TVARDECS%     $compile(tvprocdec) \
 
758
                       %TCONSTBODY%   $compile(typeconstructor) \
 
759
                       %INSTANCEVARS% $compile(instancevars) \
 
760
                       %TYPEVARS%     $compile(typevars) \
 
761
                       ]
 
762
 
 
763
    array unset compile
 
764
 
 
765
    return [list $type $defscript]
 
766
}
 
767
 
 
768
# Information about locally-defined options is accumulated during
 
769
# compilation, but not added to the compiled definition--the option
 
770
# statement can appear multiple times, so it's easier this way.
 
771
# This proc fills in Snit_optionInfo with the accumulated information.
 
772
#
 
773
# It also computes the option's resource and class names if needed.
 
774
#
 
775
# Note that the information for delegated options was put in 
 
776
# Snit_optionInfo during compilation.
 
777
 
 
778
proc ::snit::Comp.SaveOptionInfo {} {
 
779
    variable compile
 
780
 
 
781
    foreach option $compile(localoptions) {
 
782
        if {$compile(resource-$option) eq ""} {
 
783
            set compile(resource-$option) [string range $option 1 end]
 
784
        }
 
785
 
 
786
        if {$compile(class-$option) eq ""} {
 
787
            set compile(class-$option) [Capitalize $compile(resource-$option)]
 
788
        }
 
789
 
 
790
        # NOTE: Don't verify that the validate, configure, and cget 
 
791
        # values name real methods; the methods might be defined outside 
 
792
        # the typedefinition using snit::method.
 
793
        
 
794
        Mappend compile(defs) {
 
795
            # Option %OPTION%
 
796
            lappend %TYPE%::Snit_optionInfo(local) %OPTION%
 
797
 
 
798
            set %TYPE%::Snit_optionInfo(islocal-%OPTION%)   1
 
799
            set %TYPE%::Snit_optionInfo(resource-%OPTION%)  %RESOURCE%
 
800
            set %TYPE%::Snit_optionInfo(class-%OPTION%)     %CLASS%
 
801
            set %TYPE%::Snit_optionInfo(default-%OPTION%)   %DEFAULT%
 
802
            set %TYPE%::Snit_optionInfo(validate-%OPTION%)  %VALIDATE%
 
803
            set %TYPE%::Snit_optionInfo(configure-%OPTION%) %CONFIGURE%
 
804
            set %TYPE%::Snit_optionInfo(cget-%OPTION%)      %CGET%
 
805
            set %TYPE%::Snit_optionInfo(readonly-%OPTION%)  %READONLY%
 
806
        }   %OPTION%    $option \
 
807
            %RESOURCE%  $compile(resource-$option) \
 
808
            %CLASS%     $compile(class-$option) \
 
809
            %DEFAULT%   [list $compile(-default-$option)] \
 
810
            %VALIDATE%  [list $compile(-validatemethod-$option)] \
 
811
            %CONFIGURE% [list $compile(-configuremethod-$option)] \
 
812
            %CGET%      [list $compile(-cgetmethod-$option)] \
 
813
            %READONLY%  $compile(-readonly-$option)
 
814
    }
 
815
}
 
816
 
 
817
 
 
818
# Evaluates a compiled type definition, thus making the type available.
 
819
proc ::snit::Comp.Define {compResult} {
 
820
    # The compilation result is a list containing the fully qualified
 
821
    # type name and a script to evaluate to define the type.
 
822
    set type [lindex $compResult 0]
 
823
    set defscript [lindex $compResult 1]
 
824
 
 
825
    # Execute the type definition script.
 
826
    # Consider using namespace eval %TYPE%.  See if it's faster.
 
827
    if {[catch {eval $defscript} result]} {
 
828
        namespace delete $type
 
829
        catch {rename $type ""}
 
830
        error $result
 
831
    }
 
832
 
 
833
    return $type
 
834
}
 
835
 
 
836
# Sets pragma options which control how the type is defined.
 
837
proc ::snit::Comp.statement.pragma {args} {
 
838
    variable compile
 
839
 
 
840
    set errRoot "Error in \"pragma...\""
 
841
 
 
842
    foreach {opt val} $args {
 
843
        switch -exact -- $opt {
 
844
            -hastypeinfo    -
 
845
            -hastypedestroy -
 
846
            -hastypemethods -
 
847
            -hasinstances   -
 
848
            -simpledispatch -
 
849
            -hasinfo        -
 
850
            -canreplace     {
 
851
                if {![string is boolean -strict $val]} {
 
852
                    error "$errRoot, \"$opt\" requires a boolean value"
 
853
                }
 
854
                set compile($opt) $val
 
855
            }
 
856
            default {
 
857
                error "$errRoot, unknown pragma"
 
858
            }
 
859
        }
 
860
    }
 
861
}
 
862
 
 
863
# Defines a widget's option class name.  
 
864
# This statement is only available for snit::widgets,
 
865
# not for snit::types or snit::widgetadaptors.
 
866
proc ::snit::Comp.statement.widgetclass {name} {
 
867
    variable compile
 
868
 
 
869
    # First, widgetclass can only be set for true widgets
 
870
    if {"widget" != $compile(which)} {
 
871
        error "widgetclass cannot be set for snit::$compile(which)s"
 
872
    }
 
873
 
 
874
    # Next, validate the option name.  We'll require that it begin
 
875
    # with an uppercase letter.
 
876
    set initial [string index $name 0]
 
877
    if {![string is upper $initial]} {
 
878
        error "widgetclass \"$name\" does not begin with an uppercase letter"
 
879
    }
 
880
 
 
881
    if {"" != $compile(widgetclass)} {
 
882
        error "too many widgetclass statements"
 
883
    }
 
884
 
 
885
    # Next, save it.
 
886
    Mappend compile(defs) {
 
887
        set  %TYPE%::Snit_info(widgetclass) %WIDGETCLASS%
 
888
    } %WIDGETCLASS% [list $name]
 
889
 
 
890
    set compile(widgetclass) $name
 
891
}
 
892
 
 
893
# Defines a widget's hull type.
 
894
# This statement is only available for snit::widgets,
 
895
# not for snit::types or snit::widgetadaptors.
 
896
proc ::snit::Comp.statement.hulltype {name} {
 
897
    variable compile
 
898
 
 
899
    # First, hulltype can only be set for true widgets
 
900
    if {"widget" != $compile(which)} {
 
901
        error "hulltype cannot be set for snit::$compile(which)s"
 
902
    }
 
903
 
 
904
    # Next, it must be either "frame" or "toplevel"
 
905
    if {"frame" != $name && "toplevel" != $name} {
 
906
        error "invalid hulltype \"$name\", should be \"frame\" or \"toplevel\""
 
907
    }
 
908
 
 
909
    if {"" != $compile(hulltype)} {
 
910
        error "too many hulltype statements"
 
911
    }
 
912
 
 
913
    # Next, save it.
 
914
    Mappend compile(defs) {
 
915
        set  %TYPE%::Snit_info(hulltype) %HULLTYPE%
 
916
    } %HULLTYPE% $name
 
917
 
 
918
    set compile(hulltype) $name
 
919
}
 
920
 
 
921
# Defines a constructor.
 
922
proc ::snit::Comp.statement.constructor {arglist body} {
 
923
    variable compile
 
924
 
 
925
    CheckArgs "constructor" $arglist
 
926
 
 
927
    # Next, add a magic reference to self.
 
928
    set arglist [concat type selfns win self $arglist]
 
929
 
 
930
    # Next, add variable declarations to body:
 
931
    set body "%TVARDECS%%IVARDECS%\n$body"
 
932
 
 
933
    set compile(hasconstructor) yes
 
934
    append compile(defs) "proc %TYPE%::Snit_constructor [list $arglist] [list $body]\n"
 
935
 
936
 
 
937
# Defines a destructor.
 
938
proc ::snit::Comp.statement.destructor {body} {
 
939
    variable compile
 
940
 
 
941
    # Next, add variable declarations to body:
 
942
    set body "%TVARDECS%%IVARDECS%\n$body"
 
943
 
 
944
    append compile(defs) "proc %TYPE%::Snit_destructor {type selfns win self} [list $body]"
 
945
 
946
 
 
947
# Defines a type option.  The option value can be a triple, specifying
 
948
# the option's -name, resource name, and class name. 
 
949
proc ::snit::Comp.statement.option {optionDef args} {
 
950
    variable compile
 
951
 
 
952
    # First, get the three option names.
 
953
    set option [lindex $optionDef 0]
 
954
    set resourceName [lindex $optionDef 1]
 
955
    set className [lindex $optionDef 2]
 
956
 
 
957
    set errRoot "Error in \"option [list $optionDef]...\""
 
958
 
 
959
    # Next, validate the option name.
 
960
    if {![Comp.OptionNameIsValid $option]} {
 
961
        error "$errRoot, badly named option \"$option\""
 
962
    }
 
963
 
 
964
    if {[Contains $option $compile(delegatedoptions)]} {
 
965
        error "$errRoot, cannot define \"$option\" locally, it has been delegated"
 
966
    }
 
967
 
 
968
    if {![Contains $option $compile(localoptions)]} {
 
969
        # Remember that we've seen this one.
 
970
        set compile(hasoptions) yes
 
971
        lappend compile(localoptions) $option
 
972
        
 
973
        # Initialize compilation info for this option.
 
974
        set compile(resource-$option)         ""
 
975
        set compile(class-$option)            ""
 
976
        set compile(-default-$option)         ""
 
977
        set compile(-validatemethod-$option)  ""
 
978
        set compile(-configuremethod-$option) ""
 
979
        set compile(-cgetmethod-$option)      ""
 
980
        set compile(-readonly-$option)        0
 
981
    }
 
982
 
 
983
    # NEXT, see if we have a resource name.  If so, make sure it
 
984
    # isn't being redefined differently.
 
985
    if {$resourceName ne ""} {
 
986
        if {$compile(resource-$option) eq ""} {
 
987
            # If it's undefined, just save the value.
 
988
            set compile(resource-$option) $resourceName
 
989
        } elseif {$resourceName ne $compile(resource-$option)} {
 
990
            # It's been redefined differently.
 
991
            error "$errRoot, resource name redefined from \"$compile(resource-$option)\" to \"$resourceName\""
 
992
        }
 
993
    }
 
994
 
 
995
    # NEXT, see if we have a class name.  If so, make sure it
 
996
    # isn't being redefined differently.
 
997
    if {$className ne ""} {
 
998
        if {$compile(class-$option) eq ""} {
 
999
            # If it's undefined, just save the value.
 
1000
            set compile(class-$option) $className
 
1001
        } elseif {$className ne $compile(class-$option)} {
 
1002
            # It's been redefined differently.
 
1003
            error "$errRoot, class name redefined from \"$compile(class-$option)\" to \"$className\""
 
1004
        }
 
1005
    }
 
1006
 
 
1007
    # NEXT, handle the args; it's not an error to redefine these.
 
1008
    if {[llength $args] == 1} {
 
1009
        set compile(-default-$option) [lindex $args 0]
 
1010
    } else {
 
1011
        foreach {optopt val} $args {
 
1012
            switch -exact -- $optopt {
 
1013
                -default         -
 
1014
                -validatemethod  -
 
1015
                -configuremethod -
 
1016
                -cgetmethod      {
 
1017
                    set compile($optopt-$option) $val
 
1018
                }
 
1019
                -readonly        {
 
1020
                    if {![string is boolean -strict $val]} {
 
1021
                        error "$errRoot, -readonly requires a boolean, got \"$val\""
 
1022
                    }
 
1023
                    set compile($optopt-$option) $val
 
1024
                }
 
1025
                default {
 
1026
                    error "$errRoot, unknown option definition option \"$optopt\""
 
1027
                }
 
1028
            }
 
1029
        }
 
1030
    }
 
1031
}
 
1032
 
 
1033
# 1 if the option name is valid, 0 otherwise.
 
1034
proc ::snit::Comp.OptionNameIsValid {option} {
 
1035
    if {![string match {-*} $option] || [string match {*[A-Z ]*} $option]} {
 
1036
        return 0
 
1037
    }
 
1038
 
 
1039
    return 1
 
1040
}
 
1041
 
 
1042
# Defines an option's cget handler
 
1043
proc ::snit::Comp.statement.oncget {option body} {
 
1044
    variable compile
 
1045
 
 
1046
    set errRoot "Error in \"oncget $option...\""
 
1047
 
 
1048
    if {[lsearch $compile(delegatedoptions) $option] != -1} {
 
1049
        error "$errRoot, option \"$option\" is delegated"
 
1050
    }
 
1051
 
 
1052
    if {[lsearch $compile(localoptions) $option] == -1} {
 
1053
        error "$errRoot, option \"$option\" unknown"
 
1054
    }
 
1055
 
 
1056
    # Next, add variable declarations to body:
 
1057
    set body "%TVARDECS%%IVARDECS%\n$body"
 
1058
 
 
1059
    Comp.statement.method _cget$option {_option} $body
 
1060
    Comp.statement.option $option -cgetmethod _cget$option
 
1061
 
1062
 
 
1063
# Defines an option's configure handler.
 
1064
proc ::snit::Comp.statement.onconfigure {option arglist body} {
 
1065
    variable compile
 
1066
 
 
1067
    if {[lsearch $compile(delegatedoptions) $option] != -1} {
 
1068
        error "onconfigure $option: option \"$option\" is delegated"
 
1069
    }
 
1070
 
 
1071
    if {[lsearch $compile(localoptions) $option] == -1} {
 
1072
        error "onconfigure $option: option \"$option\" unknown"
 
1073
    }
 
1074
 
 
1075
    if {[llength $arglist] != 1} {
 
1076
        error \
 
1077
       "onconfigure $option handler should have one argument, got \"$arglist\""
 
1078
    }
 
1079
 
 
1080
    CheckArgs "onconfigure $option" $arglist
 
1081
 
 
1082
    # Next, add a magic reference to the option name
 
1083
    set arglist [concat _option $arglist]
 
1084
 
 
1085
    Comp.statement.method _configure$option $arglist $body
 
1086
    Comp.statement.option $option -configuremethod _configure$option
 
1087
 
1088
 
 
1089
# Defines an instance method.
 
1090
proc ::snit::Comp.statement.method {method arglist body} {
 
1091
    variable compile
 
1092
    variable methodInfo
 
1093
 
 
1094
    # FIRST, check the method name against previously defined 
 
1095
    # methods.
 
1096
    Comp.CheckMethodName $method 0 ::snit::methodInfo \
 
1097
        "Error in \"method [list $method]...\""
 
1098
 
 
1099
    if {[llength $method] > 1} {
 
1100
        set compile(hashierarchic) yes
 
1101
    }
 
1102
 
 
1103
    # Remeber this method
 
1104
    lappend compile(localmethods) $method
 
1105
 
 
1106
    CheckArgs "method [list $method]" $arglist
 
1107
 
 
1108
    # Next, add magic references to type and self.
 
1109
    set arglist [concat type selfns win self $arglist]
 
1110
 
 
1111
    # Next, add variable declarations to body:
 
1112
    set body "%TVARDECS%%IVARDECS%\n$body"
 
1113
 
 
1114
    # Next, save the definition script.
 
1115
    if {[llength $method] == 1} {
 
1116
        set methodInfo($method) {0 "%t::Snit_method%m %t %n %w %s" ""}
 
1117
        Mappend compile(defs) {
 
1118
            proc %TYPE%::Snit_method%METHOD% %ARGLIST% %BODY% 
 
1119
        } %METHOD% $method %ARGLIST% [list $arglist] %BODY% [list $body] 
 
1120
    } else {
 
1121
        set methodInfo($method) {0 "%t::Snit_hmethod%j %t %n %w %s" ""}
 
1122
 
 
1123
        Mappend compile(defs) {
 
1124
            proc %TYPE%::Snit_hmethod%JMETHOD% %ARGLIST% %BODY% 
 
1125
        } %JMETHOD% [join $method _] %ARGLIST% [list $arglist] \
 
1126
            %BODY% [list $body] 
 
1127
    }
 
1128
 
1129
 
 
1130
# Check for name collisions; save prefix information.
 
1131
#
 
1132
# method        The name of the method or typemethod.
 
1133
# delFlag       1 if delegated, 0 otherwise.
 
1134
# infoVar       The fully qualified name of the array containing 
 
1135
#               information about the defined methods.
 
1136
# errRoot       The root string for any error messages.
 
1137
 
 
1138
proc ::snit::Comp.CheckMethodName {method delFlag infoVar errRoot} {
 
1139
    upvar $infoVar methodInfo
 
1140
 
 
1141
    # FIRST, make sure the method name is a valid Tcl list.
 
1142
    if {[catch {lindex $method 0}]} {
 
1143
        error "$errRoot, the name \"$method\" must have list syntax."
 
1144
    }
 
1145
 
 
1146
    # NEXT, check whether we can define it.
 
1147
    if {![catch {set methodInfo($method)} data]} {
 
1148
        # We can't redefine methods with submethods.
 
1149
        if {[lindex $data 0] == 1} {
 
1150
            error "$errRoot, \"$method\" has submethods."
 
1151
        }
 
1152
       
 
1153
        # You can't delegate a method that's defined locally,
 
1154
        # and you can't define a method locally if it's been delegated.
 
1155
        if {$delFlag && [lindex $data 2] eq ""} {
 
1156
            error "$errRoot, \"$method\" has been defined locally."
 
1157
        } elseif {!$delFlag && [lindex $data 2] ne ""} {
 
1158
            error "$errRoot, \"$method\" has been delegated"
 
1159
        }
 
1160
    }
 
1161
 
 
1162
    # Handle hierarchical case.
 
1163
    if {[llength $method] > 1} {
 
1164
        set prefix {}
 
1165
        set tokens $method
 
1166
        while {[llength $tokens] > 1} {
 
1167
            lappend prefix [lindex $tokens 0]
 
1168
            set tokens [lrange $tokens 1 end]
 
1169
 
 
1170
            if {![catch {set methodInfo($prefix)} result]} {
 
1171
                # Prefix is known.  If it's not a prefix, throw an
 
1172
                # error.
 
1173
                if {[lindex $result 0] == 0} {
 
1174
                    error "$errRoot, \"$prefix\" has no submethods."
 
1175
                }
 
1176
            }
 
1177
            
 
1178
            set methodInfo($prefix) [list 1]
 
1179
        }
 
1180
    }
 
1181
}
 
1182
 
 
1183
# Defines a typemethod method.
 
1184
proc ::snit::Comp.statement.typemethod {method arglist body} {
 
1185
    variable compile
 
1186
    variable typemethodInfo
 
1187
 
 
1188
    # FIRST, check the typemethod name against previously defined 
 
1189
    # typemethods.
 
1190
    Comp.CheckMethodName $method 0 ::snit::typemethodInfo \
 
1191
        "Error in \"typemethod [list $method]...\""
 
1192
 
 
1193
    CheckArgs "typemethod $method" $arglist
 
1194
 
 
1195
    # First, add magic reference to type.
 
1196
    set arglist [concat type $arglist]
 
1197
 
 
1198
    # Next, add typevariable declarations to body:
 
1199
    set body "%TVARDECS%\n$body"
 
1200
 
 
1201
    # Next, save the definition script
 
1202
    if {[llength $method] == 1} {
 
1203
        set typemethodInfo($method) {0 "%t::Snit_typemethod%m %t" ""}
 
1204
 
 
1205
        Mappend compile(defs) {
 
1206
            proc %TYPE%::Snit_typemethod%METHOD% %ARGLIST% %BODY%
 
1207
        } %METHOD% $method %ARGLIST% [list $arglist] %BODY% [list $body]
 
1208
    } else {
 
1209
        set typemethodInfo($method) {0 "%t::Snit_htypemethod%j %t" ""}
 
1210
 
 
1211
        Mappend compile(defs) {
 
1212
            proc %TYPE%::Snit_htypemethod%JMETHOD% %ARGLIST% %BODY%
 
1213
        } %JMETHOD% [join $method _] \
 
1214
            %ARGLIST% [list $arglist] %BODY% [list $body]
 
1215
    }
 
1216
 
1217
 
 
1218
 
 
1219
# Defines a type constructor.
 
1220
proc ::snit::Comp.statement.typeconstructor {body} {
 
1221
    variable compile
 
1222
 
 
1223
    if {"" != $compile(typeconstructor)} {
 
1224
        error "too many typeconstructors"
 
1225
    }
 
1226
 
 
1227
    set compile(typeconstructor) $body
 
1228
 
1229
 
 
1230
# Defines a static proc in the type's namespace.
 
1231
proc ::snit::Comp.statement.proc {proc arglist body} {
 
1232
    variable compile
 
1233
 
 
1234
    # If "ns" is defined, the proc can see instance variables.
 
1235
    if {[lsearch -exact $arglist selfns] != -1} {
 
1236
        # Next, add instance variable declarations to body:
 
1237
        set body "%IVARDECS%\n$body"
 
1238
    }
 
1239
 
 
1240
    # The proc can always see typevariables.
 
1241
    set body "%TVARDECS%\n$body"
 
1242
 
 
1243
    append compile(defs) "
 
1244
 
 
1245
        # Proc $proc
 
1246
        proc [list %TYPE%::$proc $arglist $body]
 
1247
    "
 
1248
 
1249
 
 
1250
# Defines a static variable in the type's namespace.
 
1251
proc ::snit::Comp.statement.typevariable {name args} {
 
1252
    variable compile
 
1253
 
 
1254
    set errRoot "Error in \"typevariable $name...\""
 
1255
 
 
1256
    set len [llength $args]
 
1257
    
 
1258
    if {$len > 2 ||
 
1259
        ($len == 2 && [lindex $args 0] ne "-array")} {
 
1260
        error "$errRoot, too many initializers"
 
1261
    }
 
1262
 
 
1263
    if {[lsearch -exact $compile(varnames) $name] != -1} {
 
1264
        error "$errRoot, \"$name\" is already an instance variable"
 
1265
    }
 
1266
 
 
1267
    lappend compile(typevarnames) $name
 
1268
 
 
1269
    if {$len == 1} {
 
1270
        append compile(typevars) \
 
1271
                "\n\t    [list ::variable $name [lindex $args 0]]"
 
1272
    } elseif {$len == 2} {
 
1273
        append compile(typevars) \
 
1274
            "\n\t    [list ::variable $name]"
 
1275
        append compile(typevars) \
 
1276
            "\n\t    [list array set $name [lindex $args 1]]"
 
1277
    } else {
 
1278
        append compile(typevars) \
 
1279
                "\n\t    [list ::variable $name]"
 
1280
    }
 
1281
 
 
1282
    append compile(tvprocdec) "\n\t    typevariable ${name}"
 
1283
 
1284
 
 
1285
# Defines an instance variable; the definition will go in the
 
1286
# type's create typemethod.
 
1287
proc ::snit::Comp.statement.variable {name args} {
 
1288
    variable compile
 
1289
 
 
1290
    set errRoot "Error in \"variable $name...\""
 
1291
 
 
1292
    set len [llength $args]
 
1293
    
 
1294
    if {$len > 2 ||
 
1295
        ($len == 2 && [lindex $args 0] ne "-array")} {
 
1296
        error "$errRoot, too many initializers"
 
1297
    }
 
1298
 
 
1299
    if {[lsearch -exact $compile(typevarnames) $name] != -1} {
 
1300
        error "$errRoot, \"$name\" is already a typevariable"
 
1301
    }
 
1302
 
 
1303
    lappend compile(varnames) $name
 
1304
 
 
1305
    if {$len == 1} {
 
1306
        append compile(instancevars) \
 
1307
            "\nset \${selfns}::$name [list [lindex $args 0]]\n"
 
1308
    } elseif {$len == 2} {
 
1309
        append compile(instancevars) \
 
1310
            "\narray set \${selfns}::$name [list [lindex $args 1]]\n"
 
1311
    } 
 
1312
 
 
1313
    append  compile(ivprocdec) "\n\t    "
 
1314
    Mappend compile(ivprocdec) {::variable ${selfns}::%N} %N $name 
 
1315
 
1316
 
 
1317
# Defines a typecomponent, and handles component options.
 
1318
#
 
1319
# component     The logical name of the delegate
 
1320
# args          options.
 
1321
 
 
1322
proc ::snit::Comp.statement.typecomponent {component args} {
 
1323
    variable compile
 
1324
 
 
1325
    set errRoot "Error in \"typecomponent $component...\""
 
1326
 
 
1327
    # FIRST, define the component
 
1328
    Comp.DefineTypecomponent $component $errRoot
 
1329
 
 
1330
    # NEXT, handle the options.
 
1331
    set publicMethod ""
 
1332
    set inheritFlag 0
 
1333
 
 
1334
    foreach {opt val} $args {
 
1335
        switch -exact -- $opt {
 
1336
            -public {
 
1337
                set publicMethod $val
 
1338
            }
 
1339
            -inherit {
 
1340
                set inheritFlag $val
 
1341
                if {![string is boolean $inheritFlag]} {
 
1342
    error "typecomponent $component -inherit: expected boolean value, got \"$val\""
 
1343
                }
 
1344
            }
 
1345
            default {
 
1346
                error "typecomponent $component: Invalid option \"$opt\""
 
1347
            }
 
1348
        }
 
1349
    }
 
1350
 
 
1351
    # NEXT, if -public specified, define the method.  
 
1352
    if {$publicMethod ne ""} {
 
1353
        Comp.statement.delegate typemethod [list $publicMethod *] to $component
 
1354
    }
 
1355
 
 
1356
    # NEXT, if "-inherit 1" is specified, delegate typemethod * to 
 
1357
    # this component.
 
1358
    if {$inheritFlag} {
 
1359
        Comp.statement.delegate typemethod "*" to $component
 
1360
    }
 
1361
 
 
1362
}
 
1363
 
 
1364
 
 
1365
# Defines a name to be a typecomponent
 
1366
 
1367
# The name becomes a typevariable; in addition, it gets a 
 
1368
# write trace so that when it is set, all of the component mechanisms
 
1369
# get updated.
 
1370
#
 
1371
# component     The component name
 
1372
 
 
1373
proc ::snit::Comp.DefineTypecomponent {component {errRoot "Error"}} {
 
1374
    variable compile
 
1375
 
 
1376
    if {[lsearch -exact $compile(varnames) $component] != -1} {
 
1377
        error "$errRoot, \"$component\" is already an instance variable"
 
1378
    }
 
1379
 
 
1380
    if {[lsearch $compile(typecomponents) $component] == -1} {
 
1381
        # Remember we've done this.
 
1382
        lappend compile(typecomponents) $component
 
1383
 
 
1384
        # Make it a type variable with no initial value
 
1385
        Comp.statement.typevariable $component ""
 
1386
 
 
1387
        # Add a write trace to do the component thing.
 
1388
        Mappend compile(typevars) {
 
1389
            trace add variable %COMP% write \
 
1390
                [list ::snit::RT.TypecomponentTrace [list %TYPE%] %COMP%]
 
1391
        } %TYPE% $compile(type) %COMP% $component
 
1392
    }
 
1393
 
1394
 
 
1395
# Defines a component, and handles component options.
 
1396
#
 
1397
# component     The logical name of the delegate
 
1398
# args          options.
 
1399
#
 
1400
# TBD: Ideally, it should be possible to call this statement multiple
 
1401
# times, possibly changing the option values.  To do that, I'd need
 
1402
# to cache the option values and not act on them until *after* I'd
 
1403
# read the entire type definition.
 
1404
 
 
1405
proc ::snit::Comp.statement.component {component args} {
 
1406
    variable compile
 
1407
 
 
1408
    set errRoot "Error in \"component $component...\""
 
1409
 
 
1410
    # FIRST, define the component
 
1411
    Comp.DefineComponent $component $errRoot
 
1412
 
 
1413
    # NEXT, handle the options.
 
1414
    set publicMethod ""
 
1415
    set inheritFlag 0
 
1416
 
 
1417
    foreach {opt val} $args {
 
1418
        switch -exact -- $opt {
 
1419
            -public {
 
1420
                set publicMethod $val
 
1421
            }
 
1422
            -inherit {
 
1423
                set inheritFlag $val
 
1424
                if {![string is boolean $inheritFlag]} {
 
1425
    error "component $component -inherit: expected boolean value, got \"$val\""
 
1426
                }
 
1427
            }
 
1428
            default {
 
1429
                error "component $component: Invalid option \"$opt\""
 
1430
            }
 
1431
        }
 
1432
    }
 
1433
 
 
1434
    # NEXT, if -public specified, define the method.  
 
1435
    if {$publicMethod ne ""} {
 
1436
        Comp.statement.delegate method [list $publicMethod *] to $component
 
1437
    }
 
1438
 
 
1439
    # NEXT, if -inherit is specified, delegate method/option * to 
 
1440
    # this component.
 
1441
    if {$inheritFlag} {
 
1442
        Comp.statement.delegate method "*" to $component
 
1443
        Comp.statement.delegate option "*" to $component
 
1444
    }
 
1445
}
 
1446
 
 
1447
 
 
1448
# Defines a name to be a component
 
1449
 
1450
# The name becomes an instance variable; in addition, it gets a 
 
1451
# write trace so that when it is set, all of the component mechanisms
 
1452
# get updated.
 
1453
#
 
1454
# component     The component name
 
1455
 
 
1456
proc ::snit::Comp.DefineComponent {component {errRoot "Error"}} {
 
1457
    variable compile
 
1458
 
 
1459
    if {[lsearch -exact $compile(typevarnames) $component] != -1} {
 
1460
        error "$errRoot, \"$component\" is already a typevariable"
 
1461
    }
 
1462
 
 
1463
    if {[lsearch $compile(components) $component] == -1} {
 
1464
        # Remember we've done this.
 
1465
        lappend compile(components) $component
 
1466
 
 
1467
        # Make it an instance variable with no initial value
 
1468
        Comp.statement.variable $component ""
 
1469
 
 
1470
        # Add a write trace to do the component thing.
 
1471
        Mappend compile(instancevars) {
 
1472
            trace add variable ${selfns}::%COMP% write \
 
1473
                [list ::snit::RT.ComponentTrace [list %TYPE%] $selfns %COMP%]
 
1474
        } %TYPE% $compile(type) %COMP% $component
 
1475
    }
 
1476
 
1477
 
 
1478
# Creates a delegated method, typemethod, or option.
 
1479
proc ::snit::Comp.statement.delegate {what name args} {
 
1480
    # FIRST, dispatch to correct handler.
 
1481
    switch $what {
 
1482
        typemethod { Comp.DelegatedTypemethod $name $args }
 
1483
        method     { Comp.DelegatedMethod     $name $args }
 
1484
        option     { Comp.DelegatedOption     $name $args }
 
1485
        default {
 
1486
            error "Error in \"delegate $what $name...\", \"$what\"?"
 
1487
        }
 
1488
    }
 
1489
 
 
1490
    if {([llength $args] % 2) != 0} {
 
1491
        error "Error in \"delegate $what $name...\", invalid syntax"
 
1492
    }
 
1493
}
 
1494
 
 
1495
# Creates a delegated typemethod delegating it to a particular
 
1496
# typecomponent or an arbitrary command.
 
1497
#
 
1498
# method    The name of the method
 
1499
# arglist       Delegation options
 
1500
 
 
1501
proc ::snit::Comp.DelegatedTypemethod {method arglist} {
 
1502
    variable compile
 
1503
    variable typemethodInfo
 
1504
 
 
1505
    set errRoot "Error in \"delegate typemethod [list $method]...\""
 
1506
 
 
1507
    # Next, parse the delegation options.
 
1508
    set component ""
 
1509
    set target ""
 
1510
    set exceptions {}
 
1511
    set pattern ""
 
1512
    set methodTail [lindex $method end]
 
1513
 
 
1514
    foreach {opt value} $arglist {
 
1515
        switch -exact $opt {
 
1516
            to     { set component $value  }
 
1517
            as     { set target $value     }
 
1518
            except { set exceptions $value }
 
1519
            using  { set pattern $value    }
 
1520
            default {
 
1521
                error "$errRoot, unknown delegation option \"$opt\""
 
1522
            }
 
1523
        }
 
1524
    }
 
1525
 
 
1526
    if {$component eq "" && $pattern eq ""} {
 
1527
        error "$errRoot, missing \"to\""
 
1528
    }
 
1529
 
 
1530
    if {$methodTail eq "*" && $target ne ""} {
 
1531
        error "$errRoot, cannot specify \"as\" with \"*\""
 
1532
    }
 
1533
 
 
1534
    if {$methodTail ne "*" && $exceptions ne ""} {
 
1535
        error "$errRoot, can only specify \"except\" with \"*\"" 
 
1536
    }
 
1537
 
 
1538
    if {$pattern ne "" && $target ne ""} {
 
1539
        error "$errRoot, cannot specify both \"as\" and \"using\""
 
1540
    }
 
1541
 
 
1542
    foreach token [lrange $method 1 end-1] {
 
1543
        if {$token eq "*"} {
 
1544
            error "$errRoot, \"*\" must be the last token."
 
1545
        }
 
1546
    }
 
1547
 
 
1548
    # NEXT, define the component
 
1549
    if {$component ne ""} {
 
1550
        Comp.DefineTypecomponent $component $errRoot
 
1551
    }
 
1552
 
 
1553
    # NEXT, define the pattern.
 
1554
    if {$pattern eq ""} {
 
1555
        if {$methodTail eq "*"} {
 
1556
            set pattern "%c %m"
 
1557
        } elseif {$target ne ""} {
 
1558
            set pattern "%c $target"
 
1559
        } else {
 
1560
            set pattern "%c %m"
 
1561
        }
 
1562
    }
 
1563
 
 
1564
    # Make sure the pattern is a valid list.
 
1565
    if {[catch {lindex $pattern 0} result]} {
 
1566
        error "$errRoot, the using pattern, \"$pattern\", is not a valid list"
 
1567
    }
 
1568
 
 
1569
    # NEXT, check the method name against previously defined 
 
1570
    # methods.
 
1571
    Comp.CheckMethodName $method 1 ::snit::typemethodInfo $errRoot
 
1572
 
 
1573
    set typemethodInfo($method) [list 0 $pattern $component]
 
1574
 
 
1575
    if {[string equal $methodTail "*"]} {
 
1576
        Mappend compile(defs) {
 
1577
            set %TYPE%::Snit_info(excepttypemethods) %EXCEPT%
 
1578
        } %EXCEPT% [list $exceptions]
 
1579
    }
 
1580
}
 
1581
 
 
1582
 
 
1583
# Creates a delegated method delegating it to a particular
 
1584
# component or command.
 
1585
#
 
1586
# method        The name of the method
 
1587
# arglist       Delegation options.
 
1588
 
 
1589
proc ::snit::Comp.DelegatedMethod {method arglist} {
 
1590
    variable compile
 
1591
    variable methodInfo
 
1592
 
 
1593
    set errRoot "Error in \"delegate method [list $method]...\""
 
1594
 
 
1595
    # Next, parse the delegation options.
 
1596
    set component ""
 
1597
    set target ""
 
1598
    set exceptions {}
 
1599
    set pattern ""
 
1600
    set methodTail [lindex $method end]
 
1601
 
 
1602
    foreach {opt value} $arglist {
 
1603
        switch -exact $opt {
 
1604
            to     { set component $value  }
 
1605
            as     { set target $value     }
 
1606
            except { set exceptions $value }
 
1607
            using  { set pattern $value    }
 
1608
            default {
 
1609
                error "$errRoot, unknown delegation option \"$opt\""
 
1610
            }
 
1611
        }
 
1612
    }
 
1613
 
 
1614
    if {$component eq "" && $pattern eq ""} {
 
1615
        error "$errRoot, missing \"to\""
 
1616
    }
 
1617
 
 
1618
    if {$methodTail eq "*" && $target ne ""} {
 
1619
        error "$errRoot, cannot specify \"as\" with \"*\""
 
1620
    }
 
1621
 
 
1622
    if {$methodTail ne "*" && $exceptions ne ""} {
 
1623
        error "$errRoot, can only specify \"except\" with \"*\"" 
 
1624
    }
 
1625
 
 
1626
    if {$pattern ne "" && $target ne ""} {
 
1627
        error "$errRoot, cannot specify both \"as\" and \"using\""
 
1628
    }
 
1629
 
 
1630
    foreach token [lrange $method 1 end-1] {
 
1631
        if {$token eq "*"} {
 
1632
            error "$errRoot, \"*\" must be the last token."
 
1633
        }
 
1634
    }
 
1635
 
 
1636
    # NEXT, we delegate some methods
 
1637
    set compile(delegatesmethods) yes
 
1638
 
 
1639
    # NEXT, define the component.  Allow typecomponents.
 
1640
    if {$component ne ""} {
 
1641
        if {[lsearch -exact $compile(typecomponents) $component] == -1} {
 
1642
            Comp.DefineComponent $component $errRoot
 
1643
        }
 
1644
    }
 
1645
 
 
1646
    # NEXT, define the pattern.
 
1647
    if {$pattern eq ""} {
 
1648
        if {$methodTail eq "*"} {
 
1649
            set pattern "%c %m"
 
1650
        } elseif {$target ne ""} {
 
1651
            set pattern "%c $target"
 
1652
        } else {
 
1653
            set pattern "%c %m"
 
1654
        }
 
1655
    }
 
1656
 
 
1657
    # Make sure the pattern is a valid list.
 
1658
    if {[catch {lindex $pattern 0} result]} {
 
1659
        error "$errRoot, the using pattern, \"$pattern\", is not a valid list"
 
1660
    }
 
1661
 
 
1662
    # NEXT, check the method name against previously defined 
 
1663
    # methods.
 
1664
    Comp.CheckMethodName $method 1 ::snit::methodInfo $errRoot
 
1665
 
 
1666
    # NEXT, save the method info.
 
1667
    set methodInfo($method) [list 0 $pattern $component]
 
1668
 
 
1669
    if {[string equal $methodTail "*"]} {
 
1670
        Mappend compile(defs) {
 
1671
            set %TYPE%::Snit_info(exceptmethods) %EXCEPT%
 
1672
        } %EXCEPT% [list $exceptions]
 
1673
    }
 
1674
 
1675
 
 
1676
# Creates a delegated option, delegating it to a particular
 
1677
# component and, optionally, to a particular option of that
 
1678
# component.
 
1679
#
 
1680
# optionDef     The option definition
 
1681
# args          definition arguments.
 
1682
 
 
1683
proc ::snit::Comp.DelegatedOption {optionDef arglist} {
 
1684
    variable compile
 
1685
 
 
1686
    # First, get the three option names.
 
1687
    set option [lindex $optionDef 0]
 
1688
    set resourceName [lindex $optionDef 1]
 
1689
    set className [lindex $optionDef 2]
 
1690
 
 
1691
    set errRoot "Error in \"delegate option [list $optionDef]...\""
 
1692
 
 
1693
    # Next, parse the delegation options.
 
1694
    set component ""
 
1695
    set target ""
 
1696
    set exceptions {}
 
1697
 
 
1698
    foreach {opt value} $arglist {
 
1699
        switch -exact $opt {
 
1700
            to     { set component $value  }
 
1701
            as     { set target $value     }
 
1702
            except { set exceptions $value }
 
1703
            default {
 
1704
                error "$errRoot, unknown delegation option \"$opt\""
 
1705
            }
 
1706
        }
 
1707
    }
 
1708
 
 
1709
    if {$component eq ""} {
 
1710
        error "$errRoot, missing \"to\""
 
1711
    }
 
1712
 
 
1713
    if {$option eq "*" && $target ne ""} {
 
1714
        error "$errRoot, cannot specify \"as\" with \"delegate option *\""
 
1715
    }
 
1716
 
 
1717
    if {$option ne "*" && $exceptions ne ""} {
 
1718
        error "$errRoot, can only specify \"except\" with \"delegate option *\"" 
 
1719
    }
 
1720
 
 
1721
    # Next, validate the option name
 
1722
 
 
1723
    if {"*" != $option} {
 
1724
        if {![Comp.OptionNameIsValid $option]} {
 
1725
            error "$errRoot, badly named option \"$option\""
 
1726
        }
 
1727
    }
 
1728
 
 
1729
    if {[Contains $option $compile(localoptions)]} {
 
1730
        error "$errRoot, \"$option\" has been defined locally"
 
1731
    }
 
1732
 
 
1733
    if {[Contains $option $compile(delegatedoptions)]} {
 
1734
        error "$errRoot, \"$option\" is multiply delegated"
 
1735
    }
 
1736
 
 
1737
    # NEXT, define the component
 
1738
    Comp.DefineComponent $component $errRoot
 
1739
 
 
1740
    # Next, define the target option, if not specified.
 
1741
    if {![string equal $option "*"] &&
 
1742
        [string equal $target ""]} {
 
1743
        set target $option
 
1744
    }
 
1745
 
 
1746
    # NEXT, save the delegation data.
 
1747
    set compile(hasoptions) yes
 
1748
 
 
1749
    if {![string equal $option "*"]} {
 
1750
        lappend compile(delegatedoptions) $option
 
1751
 
 
1752
        # Next, compute the resource and class names, if they aren't
 
1753
        # already defined.
 
1754
 
 
1755
        if {"" == $resourceName} {
 
1756
            set resourceName [string range $option 1 end]
 
1757
        }
 
1758
 
 
1759
        if {"" == $className} {
 
1760
            set className [Capitalize $resourceName]
 
1761
        }
 
1762
 
 
1763
        Mappend  compile(defs) {
 
1764
            set %TYPE%::Snit_optionInfo(islocal-%OPTION%) 0
 
1765
            set %TYPE%::Snit_optionInfo(resource-%OPTION%) %RES%
 
1766
            set %TYPE%::Snit_optionInfo(class-%OPTION%) %CLASS%
 
1767
            lappend %TYPE%::Snit_optionInfo(delegated) %OPTION%
 
1768
            set %TYPE%::Snit_optionInfo(target-%OPTION%) [list %COMP% %TARGET%]
 
1769
            lappend %TYPE%::Snit_optionInfo(delegated-%COMP%) %OPTION%
 
1770
        }   %OPTION% $option \
 
1771
            %COMP% $component \
 
1772
            %TARGET% $target \
 
1773
            %RES% $resourceName \
 
1774
            %CLASS% $className 
 
1775
    } else {
 
1776
        Mappend  compile(defs) {
 
1777
            set %TYPE%::Snit_optionInfo(starcomp) %COMP%
 
1778
            set %TYPE%::Snit_optionInfo(except) %EXCEPT%
 
1779
        } %COMP% $component %EXCEPT% [list $exceptions]
 
1780
    }
 
1781
 
1782
 
 
1783
# Exposes a component, effectively making the component's command an
 
1784
# instance method.
 
1785
#
 
1786
# component     The logical name of the delegate
 
1787
# "as"          sugar; if not "", must be "as"
 
1788
# methodname    The desired method name for the component's command, or ""
 
1789
 
 
1790
proc ::snit::Comp.statement.expose {component {"as" ""} {methodname ""}} {
 
1791
    variable compile
 
1792
 
 
1793
 
 
1794
    # FIRST, define the component
 
1795
    Comp.DefineComponent $component
 
1796
 
 
1797
    # NEXT, define the method just as though it were in the type
 
1798
    # definition.
 
1799
    if {[string equal $methodname ""]} {
 
1800
        set methodname $component
 
1801
    }
 
1802
 
 
1803
    Comp.statement.method $methodname args [Expand {
 
1804
        if {[llength $args] == 0} {
 
1805
            return $%COMPONENT%
 
1806
        }
 
1807
 
 
1808
        if {[string equal $%COMPONENT% ""]} {
 
1809
            error "undefined component \"%COMPONENT%\""
 
1810
        }
 
1811
 
 
1812
 
 
1813
        set cmd [linsert $args 0 $%COMPONENT%]
 
1814
        return [uplevel 1 $cmd]
 
1815
    } %COMPONENT% $component]
 
1816
}
 
1817
 
 
1818
 
 
1819
 
 
1820
#-----------------------------------------------------------------------
 
1821
# Public commands
 
1822
 
 
1823
# Compile a type definition, and return the results as a list of two
 
1824
# items: the fully-qualified type name, and a script that will define
 
1825
# the type when executed.
 
1826
#
 
1827
# which         type, widget, or widgetadaptor
 
1828
# type          the type name
 
1829
# body          the type definition
 
1830
proc ::snit::compile {which type body} {
 
1831
    return [Comp.Compile $which $type $body]
 
1832
}
 
1833
 
 
1834
proc ::snit::type {type body} {
 
1835
    return [Comp.Define [Comp.Compile type $type $body]]
 
1836
}
 
1837
 
 
1838
proc ::snit::widget {type body} {
 
1839
    return [Comp.Define [Comp.Compile widget $type $body]]
 
1840
}
 
1841
 
 
1842
proc ::snit::widgetadaptor {type body} {
 
1843
    return [Comp.Define [Comp.Compile widgetadaptor $type $body]]
 
1844
}
 
1845
 
 
1846
proc ::snit::typemethod {type method arglist body} {
 
1847
    # Make sure the type exists.
 
1848
    if {![info exists ${type}::Snit_info]} {
 
1849
        error "no such type: \"$type\""
 
1850
    }
 
1851
 
 
1852
    upvar ${type}::Snit_info           Snit_info
 
1853
    upvar ${type}::Snit_typemethodInfo Snit_typemethodInfo
 
1854
 
 
1855
    # FIRST, check the typemethod name against previously defined 
 
1856
    # typemethods.
 
1857
    Comp.CheckMethodName $method 0 ${type}::Snit_typemethodInfo \
 
1858
        "Cannot define \"$method\""
 
1859
 
 
1860
    # NEXT, check the arguments
 
1861
    CheckArgs "snit::typemethod $type $method" $arglist
 
1862
 
 
1863
    # Next, add magic reference to type.
 
1864
    set arglist [concat type $arglist]
 
1865
 
 
1866
    # Next, add typevariable declarations to body:
 
1867
    set body "$Snit_info(tvardecs)\n$body"
 
1868
 
 
1869
    # Next, define it.
 
1870
    if {[llength $method] == 1} {
 
1871
        set Snit_typemethodInfo($method) {0 "%t::Snit_typemethod%m %t" ""}
 
1872
        uplevel [list proc ${type}::Snit_typemethod$method $arglist $body]
 
1873
    } else {
 
1874
        set Snit_typemethodInfo($method) {0 "%t::Snit_htypemethod%j %t" ""}
 
1875
        set suffix [join $method _]
 
1876
        uplevel [list proc ${type}::Snit_htypemethod$suffix $arglist $body]
 
1877
    }
 
1878
}
 
1879
 
 
1880
proc ::snit::method {type method arglist body} {
 
1881
    # Make sure the type exists.
 
1882
    if {![info exists ${type}::Snit_info]} {
 
1883
        error "no such type: \"$type\""
 
1884
    }
 
1885
 
 
1886
    upvar ${type}::Snit_methodInfo  Snit_methodInfo
 
1887
    upvar ${type}::Snit_info        Snit_info
 
1888
 
 
1889
    # FIRST, check the method name against previously defined 
 
1890
    # methods.
 
1891
    Comp.CheckMethodName $method 0 ${type}::Snit_methodInfo \
 
1892
        "Cannot define \"$method\""
 
1893
 
 
1894
    # NEXT, check the arguments
 
1895
    CheckArgs "snit::method $type $method" $arglist
 
1896
 
 
1897
    # Next, add magic references to type and self.
 
1898
    set arglist [concat type selfns win self $arglist]
 
1899
 
 
1900
    # Next, add variable declarations to body:
 
1901
    set body "$Snit_info(tvardecs)$Snit_info(ivardecs)\n$body"
 
1902
 
 
1903
    # Next, define it.
 
1904
    if {[llength $method] == 1} {
 
1905
        set Snit_methodInfo($method) {0 "%t::Snit_method%m %t %n %w %s" ""}
 
1906
        uplevel [list proc ${type}::Snit_method$method $arglist $body]
 
1907
    } else {
 
1908
        set Snit_methodInfo($method) {0 "%t::Snit_hmethod%j %t %n %w %s" ""}
 
1909
 
 
1910
        set suffix [join $method _]
 
1911
        uplevel [list proc ${type}::Snit_hmethod$suffix $arglist $body]
 
1912
    }
 
1913
}
 
1914
 
 
1915
# Defines a proc within the compiler; this proc can call other
 
1916
# type definition statements, and thus can be used for meta-programming.
 
1917
proc ::snit::macro {name arglist body} {
 
1918
    variable compiler
 
1919
    variable reservedwords
 
1920
 
 
1921
    # FIRST, make sure the compiler is defined.
 
1922
    Comp.Init
 
1923
 
 
1924
    # NEXT, check the macro name against the reserved words
 
1925
    if {[lsearch -exact $reservedwords $name] != -1} {
 
1926
        error "invalid macro name \"$name\""
 
1927
    }
 
1928
 
 
1929
    # NEXT, see if the name has a namespace; if it does, define the
 
1930
    # namespace.
 
1931
    set ns [namespace qualifiers $name]
 
1932
 
 
1933
    if {$ns ne ""} {
 
1934
        $compiler eval "namespace eval $ns {}"
 
1935
    }
 
1936
 
 
1937
    # NEXT, define the macro
 
1938
    $compiler eval [list _proc $name $arglist $body]
 
1939
}
 
1940
 
 
1941
#-----------------------------------------------------------------------
 
1942
# Utility Functions
 
1943
#
 
1944
# These are utility functions used while compiling Snit types.
 
1945
 
 
1946
# Builds a template from a tagged list of text blocks, then substitutes
 
1947
# all symbols in the mapTable, returning the expanded template.
 
1948
proc ::snit::Expand {template args} {
 
1949
    return [string map $args $template]
 
1950
}
 
1951
 
 
1952
# Expands a template and appends it to a variable.
 
1953
proc ::snit::Mappend {varname template args} {
 
1954
    upvar $varname myvar
 
1955
 
 
1956
    append myvar [string map $args $template]
 
1957
}
 
1958
 
 
1959
# Checks argument list against reserved args 
 
1960
proc ::snit::CheckArgs {which arglist} {
 
1961
    variable reservedArgs
 
1962
    
 
1963
    foreach name $reservedArgs {
 
1964
        if {[Contains $name $arglist]} {
 
1965
            error "$which's arglist may not contain \"$name\" explicitly"
 
1966
        }
 
1967
    }
 
1968
}
 
1969
 
 
1970
# Returns 1 if a value is in a list, and 0 otherwise.
 
1971
proc ::snit::Contains {value list} {
 
1972
    if {[lsearch -exact $list $value] != -1} {
 
1973
        return 1
 
1974
    } else {
 
1975
        return 0
 
1976
    }
 
1977
}
 
1978
 
 
1979
# Capitalizes the first letter of a string.
 
1980
proc ::snit::Capitalize {text} {
 
1981
    set first [string index $text 0]
 
1982
    set rest [string range $text 1 end]
 
1983
    return "[string toupper $first]$rest"
 
1984
}
 
1985
 
 
1986
# Converts an arbitrary white-space-delimited string into a list
 
1987
# by splitting on white-space and deleting empty tokens.
 
1988
 
 
1989
proc ::snit::Listify {str} {
 
1990
    set result {}
 
1991
    foreach token [split [string trim $str]] {
 
1992
        if {[string length $token] > 0} {
 
1993
            lappend result $token
 
1994
        }
 
1995
    }
 
1996
 
 
1997
    return $result
 
1998
}
 
1999
 
 
2000
 
 
2001
#=======================================================================
 
2002
# Snit Runtime Library
 
2003
#
 
2004
# These are procs used by Snit types and widgets at runtime.
 
2005
 
 
2006
#-----------------------------------------------------------------------
 
2007
# Object Creation
 
2008
 
 
2009
# Creates a new instance of the snit::type given its name and the args.
 
2010
#
 
2011
# type          The snit::type
 
2012
# name          The instance name
 
2013
# args          Args to pass to the constructor
 
2014
 
 
2015
proc ::snit::RT.type.typemethod.create {type name args} {
 
2016
    variable ${type}::Snit_info
 
2017
    variable ${type}::Snit_optionInfo
 
2018
 
 
2019
    # FIRST, qualify the name.
 
2020
    if {![string match "::*" $name]} {
 
2021
        # Get caller's namespace; 
 
2022
        # append :: if not global namespace.
 
2023
        set ns [uplevel 1 namespace current]
 
2024
        if {"::" != $ns} {
 
2025
            append ns "::"
 
2026
        }
 
2027
        
 
2028
        set name "$ns$name"
 
2029
    }
 
2030
 
 
2031
    # NEXT, if %AUTO% appears in the name, generate a unique 
 
2032
    # command name.  Otherwise, ensure that the name isn't in use.
 
2033
    if {[string match "*%AUTO%*" $name]} {
 
2034
        set name [::snit::RT.UniqueName Snit_info(counter) $type $name]
 
2035
    } elseif {!$Snit_info(canreplace) && [info commands $name] ne ""} {
 
2036
        error "command \"$name\" already exists"
 
2037
    }
 
2038
 
 
2039
    # NEXT, create the instance's namespace.
 
2040
    set selfns \
 
2041
        [::snit::RT.UniqueInstanceNamespace Snit_info(counter) $type]
 
2042
    namespace eval $selfns {}
 
2043
 
 
2044
    # NEXT, install the dispatcher
 
2045
    RT.MakeInstanceCommand $type $selfns $name
 
2046
 
 
2047
    # Initialize the options to their defaults. 
 
2048
    upvar ${selfns}::options options
 
2049
    foreach opt $Snit_optionInfo(local) {
 
2050
        set options($opt) $Snit_optionInfo(default-$opt)
 
2051
    }
 
2052
        
 
2053
    # Initialize the instance vars to their defaults.
 
2054
    # selfns must be defined, as it is used implicitly.
 
2055
    ${type}::Snit_instanceVars $selfns
 
2056
 
 
2057
    # Execute the type's constructor.
 
2058
    set errcode [catch {
 
2059
        RT.ConstructInstance $type $selfns $name $args
 
2060
    } result]
 
2061
 
 
2062
    if {$errcode} {
 
2063
        global errorInfo
 
2064
        global errorCode
 
2065
        
 
2066
        set theInfo $errorInfo
 
2067
        set theCode $errorCode
 
2068
        ::snit::RT.DestroyObject $type $selfns $name
 
2069
        error "Error in constructor: $result" $theInfo $theCode
 
2070
    }
 
2071
 
 
2072
    # NEXT, return the object's name.
 
2073
    return $name
 
2074
}
 
2075
 
 
2076
# Creates a new instance of the snit::widget or snit::widgetadaptor
 
2077
# given its name and the args.
 
2078
#
 
2079
# type          The snit::widget or snit::widgetadaptor
 
2080
# name          The instance name
 
2081
# args          Args to pass to the constructor
 
2082
 
 
2083
proc ::snit::RT.widget.typemethod.create {type name args} {
 
2084
    variable ${type}::Snit_info
 
2085
    variable ${type}::Snit_optionInfo
 
2086
 
 
2087
    # FIRST, if %AUTO% appears in the name, generate a unique 
 
2088
    # command name.
 
2089
    if {[string match "*%AUTO%*" $name]} {
 
2090
        set name [::snit::RT.UniqueName Snit_info(counter) $type $name]
 
2091
    }
 
2092
            
 
2093
    # NEXT, create the instance's namespace.
 
2094
    set selfns \
 
2095
        [::snit::RT.UniqueInstanceNamespace Snit_info(counter) $type]
 
2096
    namespace eval $selfns { }
 
2097
            
 
2098
    # NEXT, Initialize the widget's own options to their defaults.
 
2099
    upvar ${selfns}::options options
 
2100
    foreach opt $Snit_optionInfo(local) {
 
2101
        set options($opt) $Snit_optionInfo(default-$opt)
 
2102
    }
 
2103
 
 
2104
    # Initialize the instance vars to their defaults.
 
2105
    ${type}::Snit_instanceVars $selfns
 
2106
 
 
2107
    # NEXT, if this is a normal widget (not a widget adaptor) then 
 
2108
    # create a frame as its hull.  We set the frame's -class to
 
2109
    # the user's widgetclass, or, if none, to the basename of
 
2110
    # the $type with an initial upper case letter.
 
2111
    if {!$Snit_info(isWidgetAdaptor)} {
 
2112
        # FIRST, determine the class name
 
2113
        if {"" == $Snit_info(widgetclass)} {
 
2114
            set Snit_info(widgetclass) \
 
2115
                [::snit::Capitalize [namespace tail $type]]
 
2116
        }
 
2117
 
 
2118
        # NEXT, create the widget
 
2119
        set self $name
 
2120
        package require Tk
 
2121
        ${type}::installhull using \
 
2122
            $Snit_info(hulltype) -class $Snit_info(widgetclass)
 
2123
 
 
2124
        # NEXT, let's query the option database for our
 
2125
        # widget, now that we know that it exists.
 
2126
        foreach opt $Snit_optionInfo(local) {
 
2127
            set dbval [RT.OptionDbGet $type $name $opt]
 
2128
 
 
2129
            if {"" != $dbval} {
 
2130
                set options($opt) $dbval
 
2131
            }
 
2132
        }
 
2133
    }
 
2134
 
 
2135
    # Execute the type's constructor, and verify that it
 
2136
    # has a hull.
 
2137
    set errcode [catch {
 
2138
        RT.ConstructInstance $type $selfns $name $args
 
2139
            
 
2140
        ::snit::RT.Component $type $selfns hull
 
2141
            
 
2142
        # Prepare to call the object's destructor when the
 
2143
        # <Destroy> event is received.  Use a Snit-specific bindtag
 
2144
        # so that the widget name's tag is unencumbered.
 
2145
            
 
2146
        bind Snit$type$name <Destroy> [::snit::Expand {
 
2147
            ::snit::RT.DestroyObject %TYPE% %NS% %W
 
2148
        } %TYPE% $type %NS% $selfns]
 
2149
            
 
2150
        # Insert the bindtag into the list of bindtags right
 
2151
        # after the widget name.
 
2152
        set taglist [bindtags $name]
 
2153
        set ndx [lsearch $taglist $name]
 
2154
        incr ndx
 
2155
        bindtags $name [linsert $taglist $ndx Snit$type$name]
 
2156
    } result]
 
2157
        
 
2158
    if {$errcode} {
 
2159
        global errorInfo
 
2160
        global errorCode
 
2161
 
 
2162
        set theInfo $errorInfo
 
2163
        set theCode $errorCode
 
2164
        ::snit::RT.DestroyObject $type $selfns $name
 
2165
        error "Error in constructor: $result" $theInfo $theCode
 
2166
    }
 
2167
        
 
2168
    # NEXT, return the object's name.
 
2169
    return $name
 
2170
}
 
2171
 
 
2172
 
 
2173
# RT.MakeInstanceCommand type selfns instance
 
2174
#
 
2175
# type        The object type
 
2176
# selfns      The instance namespace
 
2177
# instance    The instance name
 
2178
#
 
2179
# Creates the instance proc.
 
2180
 
 
2181
proc ::snit::RT.MakeInstanceCommand {type selfns instance} {
 
2182
    variable ${type}::Snit_info
 
2183
        
 
2184
    # FIRST, remember the instance name.  The Snit_instance variable
 
2185
    # allows the instance to figure out its current name given the
 
2186
    # instance namespace.
 
2187
    upvar ${selfns}::Snit_instance Snit_instance
 
2188
    set Snit_instance $instance
 
2189
 
 
2190
    # NEXT, qualify the proc name if it's a widget.
 
2191
    if {$Snit_info(isWidget)} {
 
2192
        set procname ::$instance
 
2193
    } else {
 
2194
        set procname $instance
 
2195
    }
 
2196
 
 
2197
    # NEXT, install the new proc
 
2198
    if {!$Snit_info(simpledispatch)} {
 
2199
        set instanceProc $::snit::nominalInstanceProc
 
2200
    } else {
 
2201
        set instanceProc $::snit::simpleInstanceProc
 
2202
    }
 
2203
 
 
2204
    proc $procname {method args} \
 
2205
        [string map \
 
2206
             [list %SELFNS% $selfns %WIN% $instance %TYPE% $type] \
 
2207
             $instanceProc]
 
2208
 
 
2209
    # NEXT, add the trace.
 
2210
    trace add command $procname {rename delete} \
 
2211
        [list ::snit::RT.InstanceTrace $type $selfns $instance]
 
2212
}
 
2213
 
 
2214
# This proc is called when the instance command is renamed.
 
2215
# If op is delete, then new will always be "", so op is redundant.
 
2216
#
 
2217
# type          The fully-qualified type name
 
2218
# selfns        The instance namespace
 
2219
# win           The original instance/tk window name.
 
2220
# old           old instance command name
 
2221
# new           new instance command name
 
2222
# op            rename or delete
 
2223
#
 
2224
# If the op is delete, we need to clean up the object; otherwise,
 
2225
# we need to track the change.
 
2226
#
 
2227
# NOTE: In Tcl 8.4.2 there's a bug: errors in rename and delete
 
2228
# traces aren't propagated correctly.  Instead, they silently
 
2229
# vanish.  Add a catch to output any error message.
 
2230
 
 
2231
proc ::snit::RT.InstanceTrace {type selfns win old new op} {
 
2232
    variable ${type}::Snit_info
 
2233
 
 
2234
    # Note to developers ...
 
2235
    # For Tcl 8.4.0, errors thrown in trace handlers vanish silently.
 
2236
    # Therefore we catch them here and create some output to help in
 
2237
    # debugging such problems.
 
2238
 
 
2239
    if {[catch {
 
2240
        # FIRST, clean up if necessary
 
2241
        if {"" == $new} {
 
2242
            if {$Snit_info(isWidget)} {
 
2243
                destroy $win
 
2244
            } else {
 
2245
                ::snit::RT.DestroyObject $type $selfns $win
 
2246
            }
 
2247
        } else {
 
2248
            # Otherwise, track the change.
 
2249
            variable ${selfns}::Snit_instance
 
2250
            set Snit_instance [uplevel namespace which -command $new]
 
2251
            
 
2252
            # Also, clear the instance caches, as many cached commands
 
2253
            # might be invalid.
 
2254
            RT.ClearInstanceCaches $selfns
 
2255
        }
 
2256
    } result]} {
 
2257
        global errorInfo
 
2258
        # Pop up the console on Windows wish, to enable stdout.
 
2259
        # This clobbers errorInfo on unix, so save it so we can print it.
 
2260
        set ei $errorInfo
 
2261
        catch {console show}
 
2262
        puts "Error in ::snit::RT.InstanceTrace $type $selfns $win $old $new $op:"
 
2263
        puts $ei
 
2264
    }
 
2265
}
 
2266
 
 
2267
# Calls the instance constructor and handles related housekeeping.
 
2268
proc ::snit::RT.ConstructInstance {type selfns instance arglist} {
 
2269
    variable ${type}::Snit_optionInfo
 
2270
    variable ${selfns}::Snit_iinfo
 
2271
 
 
2272
    # Track whether we are constructed or not.
 
2273
    set Snit_iinfo(constructed) 0
 
2274
 
 
2275
    # Call the user's constructor
 
2276
    eval [linsert $arglist 0 \
 
2277
              ${type}::Snit_constructor $type $selfns $instance $instance]
 
2278
 
 
2279
    set Snit_iinfo(constructed) 1
 
2280
 
 
2281
    # Unset the configure cache for all -readonly options.
 
2282
    # This ensures that the next time anyone tries to 
 
2283
    # configure it, an error is thrown.
 
2284
    foreach opt $Snit_optionInfo(local) {
 
2285
        if {$Snit_optionInfo(readonly-$opt)} {
 
2286
            unset -nocomplain ${selfns}::Snit_configureCache($opt)
 
2287
        }
 
2288
    }
 
2289
 
 
2290
    return
 
2291
}
 
2292
 
 
2293
# Returns a unique command name.  
 
2294
#
 
2295
# REQUIRE: type is a fully qualified name.
 
2296
# REQUIRE: name contains "%AUTO%"
 
2297
# PROMISE: the returned command name is unused.
 
2298
proc ::snit::RT.UniqueName {countervar type name} {
 
2299
    upvar $countervar counter 
 
2300
    while 1 {
 
2301
        # FIRST, bump the counter and define the %AUTO% instance name;
 
2302
        # then substitute it into the specified name.  Wrap around at
 
2303
        # 2^31 - 2 to prevent overflow problems.
 
2304
        incr counter
 
2305
        if {$counter > 2147483646} {
 
2306
            set counter 0
 
2307
        }
 
2308
        set auto "[namespace tail $type]$counter"
 
2309
        set candidate [Expand $name %AUTO% $auto]
 
2310
        if {[info commands $candidate] eq ""} {
 
2311
            return $candidate
 
2312
        }
 
2313
    }
 
2314
}
 
2315
 
 
2316
# Returns a unique instance namespace, fully qualified.
 
2317
#
 
2318
# countervar     The name of a counter variable
 
2319
# type           The instance's type
 
2320
#
 
2321
# REQUIRE: type is fully qualified
 
2322
# PROMISE: The returned namespace name is unused.
 
2323
 
 
2324
proc ::snit::RT.UniqueInstanceNamespace {countervar type} {
 
2325
    upvar $countervar counter 
 
2326
    while 1 {
 
2327
        # FIRST, bump the counter and define the namespace name.
 
2328
        # Then see if it already exists.  Wrap around at
 
2329
        # 2^31 - 2 to prevent overflow problems.
 
2330
        incr counter
 
2331
        if {$counter > 2147483646} {
 
2332
            set counter 0
 
2333
        }
 
2334
        set ins "${type}::Snit_inst${counter}"
 
2335
        if {![namespace exists $ins]} {
 
2336
            return $ins
 
2337
        }
 
2338
    }
 
2339
}
 
2340
 
 
2341
# Retrieves an option's value from the option database.
 
2342
# Returns "" if no value is found.
 
2343
proc ::snit::RT.OptionDbGet {type self opt} {
 
2344
    variable ${type}::Snit_optionInfo
 
2345
        
 
2346
    return [option get $self \
 
2347
                $Snit_optionInfo(resource-$opt) \
 
2348
                $Snit_optionInfo(class-$opt)]
 
2349
}
 
2350
 
 
2351
#-----------------------------------------------------------------------
 
2352
# Object Destruction
 
2353
 
 
2354
# Implements the standard "destroy" method
 
2355
#
 
2356
# type          The snit type
 
2357
# selfns        The instance's instance namespace
 
2358
# win           The instance's original name
 
2359
# self          The instance's current name
 
2360
 
 
2361
proc ::snit::RT.method.destroy {type selfns win self} {
 
2362
    # Calls Snit_cleanup, which (among other things) calls the
 
2363
    # user's destructor.
 
2364
    ::snit::RT.DestroyObject $type $selfns $win
 
2365
}
 
2366
 
 
2367
# This is the function that really cleans up; it's automatically 
 
2368
# called when any instance is destroyed, e.g., by "$object destroy"
 
2369
# for types, and by the <Destroy> event for widgets.
 
2370
#
 
2371
# type          The fully-qualified type name.
 
2372
# selfns        The instance namespace
 
2373
# win           The original instance command name.
 
2374
 
 
2375
proc ::snit::RT.DestroyObject {type selfns win} {
 
2376
    variable ${type}::Snit_info
 
2377
 
 
2378
    # If the variable Snit_instance doesn't exist then there's no
 
2379
    # instance command for this object -- it's most likely a 
 
2380
    # widgetadaptor. Consequently, there are some things that
 
2381
    # we don't need to do.
 
2382
    if {[info exists ${selfns}::Snit_instance]} {
 
2383
        upvar ${selfns}::Snit_instance instance
 
2384
            
 
2385
        # First, remove the trace on the instance name, so that we
 
2386
        # don't call RT.DestroyObject recursively.
 
2387
        RT.RemoveInstanceTrace $type $selfns $win $instance
 
2388
            
 
2389
        # Next, call the user's destructor
 
2390
        ${type}::Snit_destructor $type $selfns $win $instance
 
2391
 
 
2392
        # Next, if this isn't a widget, delete the instance command.
 
2393
        # If it is a widget, get the hull component's name, and rename
 
2394
        # it back to the widget name
 
2395
                
 
2396
        # Next, delete the hull component's instance command,
 
2397
        # if there is one.
 
2398
        if {$Snit_info(isWidget)} {
 
2399
            set hullcmd [::snit::RT.Component $type $selfns hull]
 
2400
            
 
2401
            catch {rename $instance ""}
 
2402
 
 
2403
            # Clear the bind event
 
2404
            bind Snit$type$win <Destroy> ""
 
2405
 
 
2406
            if {[info command $hullcmd] != ""} {
 
2407
                rename $hullcmd ::$instance
 
2408
            }
 
2409
        } else {
 
2410
            catch {rename $instance ""}
 
2411
        }
 
2412
    }
 
2413
 
 
2414
    # Next, delete the instance's namespace.  This kills any
 
2415
    # instance variables.
 
2416
    namespace delete $selfns
 
2417
}
 
2418
 
 
2419
# Remove instance trace
 
2420
 
2421
# type           The fully qualified type name
 
2422
# selfns         The instance namespace
 
2423
# win            The original instance name/Tk window name
 
2424
# instance       The current instance name
 
2425
 
 
2426
proc ::snit::RT.RemoveInstanceTrace {type selfns win instance} {
 
2427
    variable ${type}::Snit_info
 
2428
 
 
2429
    if {$Snit_info(isWidget)} {
 
2430
        set procname ::$instance
 
2431
    } else {
 
2432
        set procname $instance
 
2433
    }
 
2434
        
 
2435
    # NEXT, remove any trace on this name
 
2436
    catch {
 
2437
        trace remove command $procname {rename delete} \
 
2438
            [list ::snit::RT.InstanceTrace $type $selfns $win]
 
2439
    }
 
2440
}
 
2441
 
 
2442
#-----------------------------------------------------------------------
 
2443
# Typecomponent Management and Method Caching
 
2444
 
 
2445
# Typecomponent trace; used for write trace on typecomponent 
 
2446
# variables.  Saves the new component object name, provided 
 
2447
# that certain conditions are met.  Also clears the typemethod
 
2448
# cache.
 
2449
 
 
2450
proc ::snit::RT.TypecomponentTrace {type component n1 n2 op} {
 
2451
    upvar ${type}::Snit_info Snit_info
 
2452
    upvar ${type}::${component} cvar
 
2453
    upvar ${type}::Snit_typecomponents Snit_typecomponents
 
2454
        
 
2455
    # Save the new component value.
 
2456
    set Snit_typecomponents($component) $cvar
 
2457
 
 
2458
    # Clear the typemethod cache.
 
2459
    # TBD: can we unset just the elements related to
 
2460
    # this component?
 
2461
    unset -nocomplain -- ${type}::Snit_typemethodCache
 
2462
}
 
2463
 
 
2464
# Generates and caches the command for a typemethod.
 
2465
#
 
2466
# type          The type
 
2467
# method        The name of the typemethod to call.
 
2468
#
 
2469
# The return value is one of the following lists:
 
2470
#
 
2471
#    {}              There's no such method.
 
2472
#    {1}             The method has submethods; look again.
 
2473
#    {0 <command>}   Here's the command to execute.
 
2474
 
 
2475
proc snit::RT.CacheTypemethodCommand {type method} {
 
2476
    upvar ${type}::Snit_typemethodInfo  Snit_typemethodInfo
 
2477
    upvar ${type}::Snit_typecomponents  Snit_typecomponents
 
2478
    upvar ${type}::Snit_typemethodCache Snit_typemethodCache
 
2479
    upvar ${type}::Snit_info            Snit_info
 
2480
    
 
2481
    # FIRST, get the pattern data and the typecomponent name.
 
2482
    set implicitCreate 0
 
2483
    set instanceName ""
 
2484
 
 
2485
    set starredMethod [lreplace $method end end *]
 
2486
    set methodTail [lindex $method end]
 
2487
 
 
2488
    if {[info exists Snit_typemethodInfo($method)]} {
 
2489
        set key $method
 
2490
    } elseif {[info exists Snit_typemethodInfo($starredMethod)]} {
 
2491
        if {[lsearch -exact $Snit_info(excepttypemethods) $methodTail] == -1} {
 
2492
            set key $starredMethod
 
2493
        } else {
 
2494
            return [list ]
 
2495
        }
 
2496
    } elseif {$Snit_info(hasinstances)} {
 
2497
        # Assume the unknown name is an instance name to create, unless
 
2498
        # this is a widget and the style of the name is wrong, or the
 
2499
        # name mimics a standard typemethod.
 
2500
 
 
2501
        if {[set ${type}::Snit_info(isWidget)] && 
 
2502
            ![string match ".*" $method]} {
 
2503
            return [list ]
 
2504
        }
 
2505
 
 
2506
        # Without this check, the call "$type info" will redefine the
 
2507
        # standard "::info" command, with disastrous results.  Since it's
 
2508
        # a likely thing to do if !-typeinfo, put in an explicit check.
 
2509
        if {$method eq "info" || $method eq "destroy"} {
 
2510
            return [list ]
 
2511
        }
 
2512
 
 
2513
        set implicitCreate 1
 
2514
        set instanceName $method
 
2515
        set key create
 
2516
        set method create
 
2517
    } else {
 
2518
        return [list ]
 
2519
    }
 
2520
    
 
2521
    foreach {flag pattern compName} $Snit_typemethodInfo($key) {}
 
2522
 
 
2523
    if {$flag == 1} {
 
2524
        return [list 1]
 
2525
    }
 
2526
 
 
2527
    # NEXT, build the substitution list
 
2528
    set subList [list \
 
2529
                     %% % \
 
2530
                     %t $type \
 
2531
                     %M $method \
 
2532
                     %m [lindex $method end] \
 
2533
                     %j [join $method _]]
 
2534
    
 
2535
    if {$compName ne ""} {
 
2536
        if {![info exists Snit_typecomponents($compName)]} {
 
2537
            error "$type delegates typemethod \"$method\" to undefined typecomponent \"$compName\""
 
2538
        }
 
2539
        
 
2540
        lappend subList %c [list $Snit_typecomponents($compName)]
 
2541
    }
 
2542
 
 
2543
    set command {}
 
2544
 
 
2545
    foreach subpattern $pattern {
 
2546
        lappend command [string map $subList $subpattern]
 
2547
    }
 
2548
 
 
2549
    if {$implicitCreate} {
 
2550
        # In this case, $method is the name of the instance to
 
2551
        # create.  Don't cache, as we usually won't do this one
 
2552
        # again.
 
2553
        lappend command $instanceName
 
2554
    } else {
 
2555
        set Snit_typemethodCache($method) [list 0 $command]
 
2556
    }
 
2557
 
 
2558
    return [list 0 $command]
 
2559
}
 
2560
 
 
2561
 
 
2562
#-----------------------------------------------------------------------
 
2563
# Component Management and Method Caching
 
2564
 
 
2565
# Retrieves the object name given the component name.
 
2566
proc ::snit::RT.Component {type selfns name} {
 
2567
    variable ${selfns}::Snit_components
 
2568
 
 
2569
    if {[catch {set Snit_components($name)} result]} {
 
2570
        variable ${selfns}::Snit_instance
 
2571
 
 
2572
        error "component \"$name\" is undefined in $type $Snit_instance"
 
2573
    }
 
2574
    
 
2575
    return $result
 
2576
}
 
2577
 
 
2578
# Component trace; used for write trace on component instance 
 
2579
# variables.  Saves the new component object name, provided 
 
2580
# that certain conditions are met.  Also clears the method
 
2581
# cache.
 
2582
 
 
2583
proc ::snit::RT.ComponentTrace {type selfns component n1 n2 op} {
 
2584
    upvar ${type}::Snit_info Snit_info
 
2585
    upvar ${selfns}::${component} cvar
 
2586
    upvar ${selfns}::Snit_components Snit_components
 
2587
        
 
2588
    # If they try to redefine the hull component after
 
2589
    # it's been defined, that's an error--but only if
 
2590
    # this is a widget or widget adaptor.
 
2591
    if {"hull" == $component && 
 
2592
        $Snit_info(isWidget) &&
 
2593
        [info exists Snit_components($component)]} {
 
2594
        set cvar $Snit_components($component)
 
2595
        error "The hull component cannot be redefined"
 
2596
    }
 
2597
 
 
2598
    # Save the new component value.
 
2599
    set Snit_components($component) $cvar
 
2600
 
 
2601
    # Clear the instance caches.
 
2602
    # TBD: can we unset just the elements related to
 
2603
    # this component?
 
2604
    RT.ClearInstanceCaches $selfns
 
2605
}
 
2606
 
 
2607
# Generates and caches the command for a method.
 
2608
#
 
2609
# type:         The instance's type
 
2610
# selfns:       The instance's private namespace
 
2611
# win:          The instance's original name (a Tk widget name, for
 
2612
#               snit::widgets.
 
2613
# self:         The instance's current name.
 
2614
# method:       The name of the method to call.
 
2615
#
 
2616
# The return value is one of the following lists:
 
2617
#
 
2618
#    {}              There's no such method.
 
2619
#    {1}             The method has submethods; look again.
 
2620
#    {0 <command>}   Here's the command to execute.
 
2621
 
 
2622
proc ::snit::RT.CacheMethodCommand {type selfns win self method} {
 
2623
    variable ${type}::Snit_info
 
2624
    variable ${type}::Snit_methodInfo
 
2625
    variable ${type}::Snit_typecomponents
 
2626
    variable ${selfns}::Snit_components
 
2627
    variable ${selfns}::Snit_methodCache
 
2628
 
 
2629
    # FIRST, get the pattern data and the component name.
 
2630
    set starredMethod [lreplace $method end end *]
 
2631
    set methodTail [lindex $method end]
 
2632
 
 
2633
    if {[info exists Snit_methodInfo($method)]} {
 
2634
        set key $method
 
2635
    } elseif {[info exists Snit_methodInfo($starredMethod)] &&
 
2636
              [lsearch -exact $Snit_info(exceptmethods) $methodTail] == -1} {
 
2637
        set key $starredMethod
 
2638
    } else {
 
2639
        return [list ]
 
2640
    }
 
2641
 
 
2642
    foreach {flag pattern compName} $Snit_methodInfo($key) {}
 
2643
 
 
2644
    if {$flag == 1} {
 
2645
        return [list 1]
 
2646
    }
 
2647
 
 
2648
    # NEXT, build the substitution list
 
2649
    set subList [list \
 
2650
                     %% % \
 
2651
                     %t $type \
 
2652
                     %M $method \
 
2653
                     %m [lindex $method end] \
 
2654
                     %j [join $method _] \
 
2655
                     %n [list $selfns] \
 
2656
                     %w [list $win] \
 
2657
                     %s [list $self]]
 
2658
 
 
2659
    if {$compName ne ""} {
 
2660
        if {[info exists Snit_components($compName)]} {
 
2661
            set compCmd $Snit_components($compName)
 
2662
        } elseif {[info exists Snit_typecomponents($compName)]} {
 
2663
            set compCmd $Snit_typecomponents($compName)
 
2664
        } else {
 
2665
            error "$type $self delegates method \"$method\" to undefined component \"$compName\""
 
2666
        }
 
2667
        
 
2668
        lappend subList %c [list $compCmd]
 
2669
    }
 
2670
 
 
2671
    # Note: The cached command will executed faster if it's
 
2672
    # already a list.
 
2673
    set command {}
 
2674
 
 
2675
    foreach subpattern $pattern {
 
2676
        lappend command [string map $subList $subpattern]
 
2677
    }
 
2678
 
 
2679
    set commandRec [list 0 $command]
 
2680
 
 
2681
    set Snit_methodCache($method) $commandRec
 
2682
        
 
2683
    return $commandRec
 
2684
}
 
2685
 
 
2686
 
 
2687
# Looks up a method's command.
 
2688
#
 
2689
# type:         The instance's type
 
2690
# selfns:       The instance's private namespace
 
2691
# win:          The instance's original name (a Tk widget name, for
 
2692
#               snit::widgets.
 
2693
# self:         The instance's current name.
 
2694
# method:       The name of the method to call.
 
2695
# errPrefix:    Prefix for any error method
 
2696
proc ::snit::RT.LookupMethodCommand {type selfns win self method errPrefix} {
 
2697
    set commandRec [snit::RT.CacheMethodCommand \
 
2698
                        $type $selfns $win $self \
 
2699
                        $method]
 
2700
 
 
2701
 
 
2702
    if {[llength $commandRec] == 0} {
 
2703
        return -code error \
 
2704
            "$errPrefix, \"$self $method\" is not defined"
 
2705
    } elseif {[lindex $commandRec 0] == 1} {
 
2706
        return -code error \
 
2707
            "$errPrefix, wrong number args: should be \"$self\" $method method args"
 
2708
    }
 
2709
 
 
2710
    return  [lindex $commandRec 1]
 
2711
}
 
2712
 
 
2713
 
 
2714
# Clears all instance command caches
 
2715
proc ::snit::RT.ClearInstanceCaches {selfns} {
 
2716
    unset -nocomplain -- ${selfns}::Snit_methodCache
 
2717
    unset -nocomplain -- ${selfns}::Snit_cgetCache
 
2718
    unset -nocomplain -- ${selfns}::Snit_configureCache
 
2719
    unset -nocomplain -- ${selfns}::Snit_validateCache
 
2720
}
 
2721
 
 
2722
 
 
2723
#-----------------------------------------------------------------------
 
2724
# Component Installation
 
2725
 
 
2726
# Implements %TYPE%::installhull.  The variables self and selfns
 
2727
# must be defined in the caller's context.
 
2728
#
 
2729
# Installs the named widget as the hull of a 
 
2730
# widgetadaptor.  Once the widget is hijacked, its new name
 
2731
# is assigned to the hull component.
 
2732
 
 
2733
proc ::snit::RT.installhull {type {using "using"} {widgetType ""} args} {
 
2734
    variable ${type}::Snit_info
 
2735
    variable ${type}::Snit_optionInfo
 
2736
    upvar self self
 
2737
    upvar selfns selfns
 
2738
    upvar ${selfns}::hull hull
 
2739
    upvar ${selfns}::options options
 
2740
 
 
2741
    # FIRST, make sure we can do it.
 
2742
    if {!$Snit_info(isWidget)} { 
 
2743
        error "installhull is valid only for snit::widgetadaptors"
 
2744
    }
 
2745
            
 
2746
    if {[info exists ${selfns}::Snit_instance]} {
 
2747
        error "hull already installed for $type $self"
 
2748
    }
 
2749
 
 
2750
    # NEXT, has it been created yet?  If not, create it using
 
2751
    # the specified arguments.
 
2752
    if {"using" == $using} {
 
2753
        # FIRST, create the widget
 
2754
        set cmd [concat [list $widgetType $self] $args]
 
2755
        set obj [uplevel 1 $cmd]
 
2756
            
 
2757
        # NEXT, for each option explicitly delegated to the hull
 
2758
        # that doesn't appear in the usedOpts list, get the
 
2759
        # option database value and apply it--provided that the
 
2760
        # real option name and the target option name are different.
 
2761
        # (If they are the same, then the option database was
 
2762
        # already queried as part of the normal widget creation.)
 
2763
        #
 
2764
        # Also, we don't need to worry about implicitly delegated
 
2765
        # options, as the option and target option names must be
 
2766
        # the same.
 
2767
        if {[info exists Snit_optionInfo(delegated-hull)]} {
 
2768
                
 
2769
            # FIRST, extract all option names from args
 
2770
            set usedOpts {}
 
2771
            set ndx [lsearch -glob $args "-*"]
 
2772
            foreach {opt val} [lrange $args $ndx end] {
 
2773
                lappend usedOpts $opt
 
2774
            }
 
2775
                
 
2776
            foreach opt $Snit_optionInfo(delegated-hull) {
 
2777
                set target [lindex $Snit_optionInfo(target-$opt) 1]
 
2778
                
 
2779
                if {"$target" == $opt} {
 
2780
                    continue
 
2781
                }
 
2782
                    
 
2783
                set result [lsearch -exact $usedOpts $target]
 
2784
                    
 
2785
                if {$result != -1} {
 
2786
                    continue
 
2787
                }
 
2788
 
 
2789
                set dbval [RT.OptionDbGet $type $self $opt]
 
2790
                $obj configure $target $dbval
 
2791
            }
 
2792
        }
 
2793
    } else {
 
2794
        set obj $using
 
2795
        
 
2796
        if {![string equal $obj $self]} {
 
2797
            error \
 
2798
                "hull name mismatch: \"$obj\" != \"$self\""
 
2799
        }
 
2800
    }
 
2801
 
 
2802
    # NEXT, get the local option defaults.
 
2803
    foreach opt $Snit_optionInfo(local) {
 
2804
        set dbval [RT.OptionDbGet $type $self $opt]
 
2805
            
 
2806
        if {"" != $dbval} {
 
2807
            set options($opt) $dbval
 
2808
        }
 
2809
    }
 
2810
 
 
2811
 
 
2812
    # NEXT, do the magic
 
2813
    set i 0
 
2814
    while 1 {
 
2815
        incr i
 
2816
        set newName "::hull${i}$self"
 
2817
        if {"" == [info commands $newName]} {
 
2818
            break
 
2819
        }
 
2820
    }
 
2821
        
 
2822
    rename ::$self $newName
 
2823
    RT.MakeInstanceCommand $type $selfns $self
 
2824
        
 
2825
    # Note: this relies on RT.ComponentTrace to do the dirty work.
 
2826
    set hull $newName
 
2827
        
 
2828
    return
 
2829
}
 
2830
 
 
2831
# Implements %TYPE%::install.
 
2832
#
 
2833
# Creates a widget and installs it as the named component.
 
2834
# It expects self and selfns to be defined in the caller's context.
 
2835
 
 
2836
proc ::snit::RT.install {type compName "using" widgetType winPath args} {
 
2837
    variable ${type}::Snit_optionInfo
 
2838
    variable ${type}::Snit_info
 
2839
    upvar self self
 
2840
    upvar selfns selfns
 
2841
    upvar ${selfns}::$compName comp
 
2842
    upvar ${selfns}::hull hull
 
2843
 
 
2844
    # We do the magic option database stuff only if $self is
 
2845
    # a widget.
 
2846
    if {$Snit_info(isWidget)} {
 
2847
        if {"" == $hull} {
 
2848
            error "tried to install \"$compName\" before the hull exists"
 
2849
        }
 
2850
            
 
2851
        # FIRST, query the option database and save the results 
 
2852
        # into args.  Insert them before the first option in the
 
2853
        # list, in case there are any non-standard parameters.
 
2854
        #
 
2855
        # Note: there might not be any delegated options; if so,
 
2856
        # don't bother.
 
2857
 
 
2858
        if {[info exists Snit_optionInfo(delegated-$compName)]} {
 
2859
            set ndx [lsearch -glob $args "-*"]
 
2860
                
 
2861
            foreach opt $Snit_optionInfo(delegated-$compName) {
 
2862
                set dbval [RT.OptionDbGet $type $self $opt]
 
2863
                    
 
2864
                if {"" != $dbval} {
 
2865
                    set target [lindex $Snit_optionInfo(target-$opt) 1]
 
2866
                    set args [linsert $args $ndx $target $dbval]
 
2867
                }
 
2868
            }
 
2869
        }
 
2870
    }
 
2871
             
 
2872
    # NEXT, create the component and save it.
 
2873
    set cmd [concat [list $widgetType $winPath] $args]
 
2874
    set comp [uplevel 1 $cmd]
 
2875
 
 
2876
    # NEXT, handle the option database for "delegate option *",
 
2877
    # in widgets only.
 
2878
    if {$Snit_info(isWidget) && $Snit_optionInfo(starcomp) eq $compName} {
 
2879
        # FIRST, get the list of option specs from the widget.
 
2880
        # If configure doesn't work, skip it.
 
2881
        if {[catch {$comp configure} specs]} {
 
2882
            return
 
2883
        }
 
2884
 
 
2885
        # NEXT, get the set of explicitly used options from args
 
2886
        set usedOpts {}
 
2887
        set ndx [lsearch -glob $args "-*"]
 
2888
        foreach {opt val} [lrange $args $ndx end] {
 
2889
            lappend usedOpts $opt
 
2890
        }
 
2891
 
 
2892
        # NEXT, "delegate option *" matches all options defined
 
2893
        # by this widget that aren't defined by the widget as a whole,
 
2894
        # and that aren't excepted.  Plus, we skip usedOpts.  So build 
 
2895
        # a list of the options it can't match.
 
2896
        set skiplist [concat \
 
2897
                          $usedOpts \
 
2898
                          $Snit_optionInfo(except) \
 
2899
                          $Snit_optionInfo(local) \
 
2900
                          $Snit_optionInfo(delegated)]
 
2901
        
 
2902
        # NEXT, loop over all of the component's options, and set
 
2903
        # any not in the skip list for which there is an option 
 
2904
        # database value.
 
2905
        foreach spec $specs {
 
2906
            # Skip aliases
 
2907
            if {[llength $spec] != 5} {
 
2908
                continue
 
2909
            }
 
2910
 
 
2911
            set opt [lindex $spec 0]
 
2912
 
 
2913
            if {[lsearch -exact $skiplist $opt] != -1} {
 
2914
                continue
 
2915
            }
 
2916
 
 
2917
            set res [lindex $spec 1]
 
2918
            set cls [lindex $spec 2]
 
2919
 
 
2920
            set dbvalue [option get $self $res $cls]
 
2921
 
 
2922
            if {"" != $dbvalue} {
 
2923
                $comp configure $opt $dbvalue
 
2924
            }
 
2925
        }
 
2926
    }
 
2927
 
 
2928
    return
 
2929
}
 
2930
 
 
2931
 
 
2932
#-----------------------------------------------------------------------
 
2933
# Method/Variable Name Qualification
 
2934
 
 
2935
# Implements %TYPE%::variable.  Requires selfns.
 
2936
proc ::snit::RT.variable {varname} {
 
2937
    upvar selfns selfns
 
2938
 
 
2939
    if {![string match "::*" $varname]} {
 
2940
        uplevel upvar ${selfns}::$varname $varname
 
2941
    } else {
 
2942
        # varname is fully qualified; let the standard
 
2943
        # "variable" command handle it.
 
2944
        uplevel ::variable $varname
 
2945
    }
 
2946
}
 
2947
 
 
2948
# Fully qualifies a typevariable name.
 
2949
#
 
2950
# This is used to implement the mytypevar command.
 
2951
 
 
2952
proc ::snit::RT.mytypevar {type name} {
 
2953
    return ${type}::$name
 
2954
}
 
2955
 
 
2956
# Fully qualifies an instance variable name.
 
2957
#
 
2958
# This is used to implement the myvar command.
 
2959
proc ::snit::RT.myvar {name} {
 
2960
    upvar selfns selfns
 
2961
    return ${selfns}::$name
 
2962
}
 
2963
 
 
2964
# Use this like "list" to convert a proc call into a command
 
2965
# string to pass to another object (e.g., as a -command).
 
2966
# Qualifies the proc name properly.
 
2967
#
 
2968
# This is used to implement the "myproc" command.
 
2969
 
 
2970
proc ::snit::RT.myproc {type procname args} {
 
2971
    set procname "${type}::$procname"
 
2972
    return [linsert $args 0 $procname]
 
2973
}
 
2974
 
 
2975
# DEPRECATED
 
2976
proc ::snit::RT.codename {type name} {
 
2977
    return "${type}::$name"
 
2978
}
 
2979
 
 
2980
# Use this like "list" to convert a typemethod call into a command
 
2981
# string to pass to another object (e.g., as a -command).
 
2982
# Inserts the type command at the beginning.
 
2983
#
 
2984
# This is used to implement the "mytypemethod" command.
 
2985
 
 
2986
proc ::snit::RT.mytypemethod {type args} {
 
2987
    return [linsert $args 0 $type]
 
2988
}
 
2989
 
 
2990
# Use this like "list" to convert a method call into a command
 
2991
# string to pass to another object (e.g., as a -command).
 
2992
# Inserts the code at the beginning to call the right object, even if
 
2993
# the object's name has changed.  Requires that selfns be defined
 
2994
# in the calling context, eg. can only be called in instance
 
2995
# code.
 
2996
#
 
2997
# This is used to implement the "mymethod" command.
 
2998
 
 
2999
proc ::snit::RT.mymethod {args} {
 
3000
    upvar selfns selfns
 
3001
    return [linsert $args 0 ::snit::RT.CallInstance ${selfns}]
 
3002
}
 
3003
 
 
3004
# Calls an instance method for an object given its
 
3005
# instance namespace and remaining arguments (the first of which
 
3006
# will be the method name.
 
3007
#
 
3008
# selfns                The instance namespace
 
3009
# args                  The arguments
 
3010
#
 
3011
# Uses the selfns to determine $self, and calls the method
 
3012
# in the normal way.
 
3013
#
 
3014
# This is used to implement the "mymethod" command.
 
3015
 
 
3016
proc ::snit::RT.CallInstance {selfns args} {
 
3017
    upvar ${selfns}::Snit_instance self
 
3018
 
 
3019
    set retval [catch {uplevel 1 [linsert $args 0 $self]} result]
 
3020
 
 
3021
    if {$retval} {
 
3022
        if {$retval == 1} {
 
3023
            global errorInfo
 
3024
            global errorCode
 
3025
            return -code error -errorinfo $errorInfo \
 
3026
                -errorcode $errorCode $result
 
3027
        } else {
 
3028
            return -code $retval $result
 
3029
        }
 
3030
    }
 
3031
 
 
3032
    return $result
 
3033
}
 
3034
 
 
3035
# Looks for the named option in the named variable.  If found,
 
3036
# it and its value are removed from the list, and the value
 
3037
# is returned.  Otherwise, the default value is returned.
 
3038
# If the option is undelegated, it's own default value will be
 
3039
# used if none is specified.
 
3040
#
 
3041
# Implements the "from" command.
 
3042
 
 
3043
proc ::snit::RT.from {type argvName option {defvalue ""}} {
 
3044
    variable ${type}::Snit_optionInfo
 
3045
    upvar $argvName argv
 
3046
 
 
3047
    set ioption [lsearch -exact $argv $option]
 
3048
 
 
3049
    if {$ioption == -1} {
 
3050
        if {"" == $defvalue &&
 
3051
            [info exists Snit_optionInfo(default-$option)]} {
 
3052
            return $Snit_optionInfo(default-$option)
 
3053
        } else {
 
3054
            return $defvalue
 
3055
        }
 
3056
    }
 
3057
 
 
3058
    set ivalue [expr {$ioption + 1}]
 
3059
    set value [lindex $argv $ivalue]
 
3060
    
 
3061
    set argv [lreplace $argv $ioption $ivalue] 
 
3062
 
 
3063
    return $value
 
3064
}
 
3065
 
 
3066
#-----------------------------------------------------------------------
 
3067
# Type Destruction
 
3068
 
 
3069
# Implements the standard "destroy" typemethod:
 
3070
# Destroys a type completely.
 
3071
#
 
3072
# type          The snit type
 
3073
 
 
3074
proc ::snit::RT.typemethod.destroy {type} {
 
3075
    variable ${type}::Snit_info
 
3076
        
 
3077
    # FIRST, destroy all instances
 
3078
    foreach selfns [namespace children $type] {
 
3079
        if {![namespace exists $selfns]} {
 
3080
            continue
 
3081
        }
 
3082
        upvar ${selfns}::Snit_instance obj
 
3083
            
 
3084
        if {$Snit_info(isWidget)} {
 
3085
            destroy $obj
 
3086
        } else {
 
3087
            if {"" != [info commands $obj]} {
 
3088
                $obj destroy
 
3089
            }
 
3090
        }
 
3091
    }
 
3092
 
 
3093
    # NEXT, destroy the type's data.
 
3094
    namespace delete $type
 
3095
 
 
3096
    # NEXT, get rid of the type command.
 
3097
    rename $type ""
 
3098
}
 
3099
 
 
3100
 
 
3101
 
 
3102
#-----------------------------------------------------------------------
 
3103
# Option Handling
 
3104
 
 
3105
# Implements the standard "cget" method
 
3106
#
 
3107
# type          The snit type
 
3108
# selfns        The instance's instance namespace
 
3109
# win           The instance's original name
 
3110
# self          The instance's current name
 
3111
# option        The name of the option
 
3112
 
 
3113
proc ::snit::RT.method.cget {type selfns win self option} {
 
3114
    if {[catch {set ${selfns}::Snit_cgetCache($option)} command]} {
 
3115
        set command [snit::RT.CacheCgetCommand $type $selfns $win $self $option]
 
3116
        
 
3117
        if {[llength $command] == 0} {
 
3118
            return -code error "unknown option \"$option\""
 
3119
        }
 
3120
    }
 
3121
            
 
3122
    uplevel 1 $command
 
3123
}
 
3124
 
 
3125
# Retrieves and caches the command that implements "cget" for the 
 
3126
# specified option.
 
3127
#
 
3128
# type          The snit type
 
3129
# selfns        The instance's instance namespace
 
3130
# win           The instance's original name
 
3131
# self          The instance's current name
 
3132
# option        The name of the option
 
3133
 
 
3134
proc ::snit::RT.CacheCgetCommand {type selfns win self option} {
 
3135
    variable ${type}::Snit_optionInfo
 
3136
    variable ${selfns}::Snit_cgetCache
 
3137
                
 
3138
    if {[info exists Snit_optionInfo(islocal-$option)]} {
 
3139
        # We know the item; it's either local, or explicitly delegated.
 
3140
        if {$Snit_optionInfo(islocal-$option)} {
 
3141
            # It's a local option.  If it has a cget method defined,
 
3142
            # use it; otherwise just return the value.
 
3143
 
 
3144
            if {$Snit_optionInfo(cget-$option) eq ""} {
 
3145
                set command [list set ${selfns}::options($option)]
 
3146
            } else {
 
3147
                set command [snit::RT.LookupMethodCommand \
 
3148
                                 $type $selfns $win $self \
 
3149
                                 $Snit_optionInfo(cget-$option) \
 
3150
                                 "can't cget $option"]
 
3151
 
 
3152
                lappend command $option
 
3153
            }
 
3154
 
 
3155
            set Snit_cgetCache($option) $command
 
3156
            return $command
 
3157
        }
 
3158
         
 
3159
        # Explicitly delegated option; get target
 
3160
        set comp [lindex $Snit_optionInfo(target-$option) 0]
 
3161
        set target [lindex $Snit_optionInfo(target-$option) 1]
 
3162
    } elseif {$Snit_optionInfo(starcomp) ne "" &&
 
3163
              [lsearch -exact $Snit_optionInfo(except) $option] == -1} {
 
3164
        # Unknown option, but unknowns are delegated; get target.
 
3165
        set comp $Snit_optionInfo(starcomp)
 
3166
        set target $option
 
3167
    } else {
 
3168
        return ""
 
3169
    }
 
3170
    
 
3171
    # Get the component's object.
 
3172
    set obj [RT.Component $type $selfns $comp]
 
3173
 
 
3174
    set command [list $obj cget $target]
 
3175
    set Snit_cgetCache($option) $command
 
3176
 
 
3177
    return $command
 
3178
}
 
3179
 
 
3180
# Implements the standard "configurelist" method
 
3181
#
 
3182
# type          The snit type
 
3183
# selfns        The instance's instance namespace
 
3184
# win           The instance's original name
 
3185
# self          The instance's current name
 
3186
# optionlist    A list of options and their values.
 
3187
 
 
3188
proc ::snit::RT.method.configurelist {type selfns win self optionlist} {
 
3189
    variable ${type}::Snit_optionInfo
 
3190
 
 
3191
    foreach {option value} $optionlist {
 
3192
        # FIRST, get the configure command, caching it if need be.
 
3193
        if {[catch {set ${selfns}::Snit_configureCache($option)} command]} {
 
3194
            set command [snit::RT.CacheConfigureCommand \
 
3195
                             $type $selfns $win $self $option]
 
3196
        
 
3197
            if {[llength $command] == 0} {
 
3198
                return -code error "unknown option \"$option\""
 
3199
            }
 
3200
        }
 
3201
 
 
3202
        # NEXT, the caching the configure command also cached the
 
3203
        # validate command, if any.  If we have one, run it.
 
3204
        set valcommand [set ${selfns}::Snit_validateCache($option)]
 
3205
 
 
3206
        if {$valcommand ne ""} {
 
3207
            lappend valcommand $value
 
3208
            uplevel 1 $valcommand
 
3209
        }
 
3210
 
 
3211
        # NEXT, configure the option with the value.
 
3212
        lappend command $value
 
3213
        uplevel 1 $command
 
3214
    }
 
3215
    
 
3216
    return
 
3217
}
 
3218
 
 
3219
# Retrieves and caches the command that stores the named option.
 
3220
# Also stores the command that validates the name option if any;
 
3221
# If none, the validate command is "", so that the cache is always
 
3222
# populated.
 
3223
#
 
3224
# type          The snit type
 
3225
# selfns        The instance's instance namespace
 
3226
# win           The instance's original name
 
3227
# self          The instance's current name
 
3228
# option        An option name
 
3229
 
 
3230
proc ::snit::RT.CacheConfigureCommand {type selfns win self option} {
 
3231
    variable ${type}::Snit_optionInfo
 
3232
    variable ${selfns}::Snit_configureCache
 
3233
    variable ${selfns}::Snit_validateCache
 
3234
 
 
3235
    if {[info exist Snit_optionInfo(islocal-$option)]} {
 
3236
        # We know the item; it's either local, or explicitly delegated.
 
3237
        
 
3238
        if {$Snit_optionInfo(islocal-$option)} {
 
3239
            # It's a local option.
 
3240
 
 
3241
            # If it's readonly, it throws an error if we're already 
 
3242
            # constructed.
 
3243
            if {$Snit_optionInfo(readonly-$option)} {
 
3244
                if {[set ${selfns}::Snit_iinfo(constructed)]} {
 
3245
                    error "option $option can only be set at instance creation"
 
3246
                }
 
3247
            }
 
3248
 
 
3249
            # If it has a validate method, cache that for later.
 
3250
            if {$Snit_optionInfo(validate-$option) ne ""} {
 
3251
                set command [snit::RT.LookupMethodCommand \
 
3252
                                 $type $selfns $win $self \
 
3253
                                 $Snit_optionInfo(validate-$option) \
 
3254
                                 "can't validate $option"]
 
3255
 
 
3256
                lappend command $option
 
3257
                set Snit_validateCache($option) $command
 
3258
            } else {
 
3259
                set Snit_validateCache($option) ""
 
3260
            }
 
3261
            
 
3262
            # If it has a configure method defined,
 
3263
            # cache it; otherwise, just set the value.
 
3264
 
 
3265
            if {$Snit_optionInfo(configure-$option) eq ""} {
 
3266
                set command [list set ${selfns}::options($option)]
 
3267
            } else {
 
3268
                set command [snit::RT.LookupMethodCommand \
 
3269
                                 $type $selfns $win $self \
 
3270
                                 $Snit_optionInfo(configure-$option) \
 
3271
                                 "can't configure $option"]
 
3272
 
 
3273
                lappend command $option
 
3274
            }
 
3275
 
 
3276
            set Snit_configureCache($option) $command
 
3277
            return $command
 
3278
        }
 
3279
 
 
3280
        # Delegated option: get target.
 
3281
        set comp [lindex $Snit_optionInfo(target-$option) 0]
 
3282
        set target [lindex $Snit_optionInfo(target-$option) 1]
 
3283
    } elseif {$Snit_optionInfo(starcomp) != "" &&
 
3284
              [lsearch -exact $Snit_optionInfo(except) $option] == -1} {
 
3285
        # Unknown option, but unknowns are delegated.
 
3286
        set comp $Snit_optionInfo(starcomp)
 
3287
        set target $option
 
3288
    } else {
 
3289
        return ""
 
3290
    }
 
3291
 
 
3292
    # There is no validate command in this case; save an empty string.
 
3293
    set Snit_validateCache($option) ""
 
3294
        
 
3295
    # Get the component's object
 
3296
    set obj [RT.Component $type $selfns $comp]
 
3297
    
 
3298
    set command [list $obj configure $target]
 
3299
    set Snit_configureCache($option) $command
 
3300
 
 
3301
    return $command
 
3302
}
 
3303
 
 
3304
# Implements the standard "configure" method
 
3305
#
 
3306
# type          The snit type
 
3307
# selfns        The instance's instance namespace
 
3308
# win           The instance's original name
 
3309
# self          The instance's current name
 
3310
# args          A list of options and their values, possibly empty.
 
3311
 
 
3312
proc ::snit::RT.method.configure {type selfns win self args} {
 
3313
    # If two or more arguments, set values as usual.
 
3314
    if {[llength $args] >= 2} {
 
3315
        ::snit::RT.method.configurelist $type $selfns $win $self $args
 
3316
        return
 
3317
    }
 
3318
 
 
3319
    # If zero arguments, acquire data for each known option
 
3320
    # and return the list
 
3321
    if {[llength $args] == 0} {
 
3322
        set result {}
 
3323
        foreach opt [RT.method.info.options $type $selfns $win $self] {
 
3324
            # Refactor this, so that we don't need to call via $self.
 
3325
            lappend result [RT.GetOptionDbSpec \
 
3326
                                $type $selfns $win $self $opt]
 
3327
        }
 
3328
        
 
3329
        return $result
 
3330
    }
 
3331
 
 
3332
    # They want it for just one.
 
3333
    set opt [lindex $args 0]
 
3334
 
 
3335
    return [RT.GetOptionDbSpec $type $selfns $win $self $opt]
 
3336
}
 
3337
 
 
3338
 
 
3339
# Retrieves the option database spec for a single option.
 
3340
#
 
3341
# type          The snit type
 
3342
# selfns        The instance's instance namespace
 
3343
# win           The instance's original name
 
3344
# self          The instance's current name
 
3345
# option        The name of an option
 
3346
#
 
3347
# TBD: This is a bad name.  What it's returning is the
 
3348
# result of the configure query.
 
3349
 
 
3350
proc ::snit::RT.GetOptionDbSpec {type selfns win self opt} {
 
3351
    variable ${type}::Snit_optionInfo
 
3352
 
 
3353
    upvar ${selfns}::Snit_components Snit_components
 
3354
    upvar ${selfns}::options         options
 
3355
    
 
3356
    if {[info exists options($opt)]} {
 
3357
        # This is a locally-defined option.  Just build the
 
3358
        # list and return it.
 
3359
        set res $Snit_optionInfo(resource-$opt)
 
3360
        set cls $Snit_optionInfo(class-$opt)
 
3361
        set def $Snit_optionInfo(default-$opt)
 
3362
 
 
3363
        return [list $opt $res $cls $def \
 
3364
                    [RT.method.cget $type $selfns $win $self $opt]]
 
3365
    } elseif {[info exists Snit_optionInfo(target-$opt)]} {
 
3366
        # This is an explicitly delegated option.  The only
 
3367
        # thing we don't have is the default.
 
3368
        set res $Snit_optionInfo(resource-$opt)
 
3369
        set cls $Snit_optionInfo(class-$opt)
 
3370
        
 
3371
        # Get the default
 
3372
        set logicalName [lindex $Snit_optionInfo(target-$opt) 0]
 
3373
        set comp $Snit_components($logicalName)
 
3374
        set target [lindex $Snit_optionInfo(target-$opt) 1]
 
3375
 
 
3376
        if {[catch {$comp configure $target} result]} {
 
3377
            set defValue {}
 
3378
        } else {
 
3379
            set defValue [lindex $result 3]
 
3380
        }
 
3381
 
 
3382
        return [list $opt $res $cls $defValue [$self cget $opt]]
 
3383
    } elseif {$Snit_optionInfo(starcomp) ne "" &&
 
3384
              [lsearch -exact $Snit_optionInfo(except) $opt] == -1} {
 
3385
        set logicalName $Snit_optionInfo(starcomp)
 
3386
        set target $opt
 
3387
        set comp $Snit_components($logicalName)
 
3388
 
 
3389
        if {[catch {set value [$comp cget $target]} result]} {
 
3390
            error "unknown option \"$opt\""
 
3391
        }
 
3392
 
 
3393
        if {![catch {$comp configure $target} result]} {
 
3394
            # Replace the delegated option name with the local name.
 
3395
            return [::snit::Expand $result $target $opt]
 
3396
        }
 
3397
 
 
3398
        # configure didn't work; return simple form.
 
3399
        return [list $opt "" "" "" $value]
 
3400
    } else {
 
3401
        error "unknown option \"$opt\""
 
3402
    }
 
3403
}
 
3404
 
 
3405
#-----------------------------------------------------------------------
 
3406
# Type Introspection
 
3407
 
 
3408
# Implements the standard "info" typemethod.
 
3409
#
 
3410
# type          The snit type
 
3411
# command       The info subcommand
 
3412
# args          All other arguments.
 
3413
 
 
3414
proc ::snit::RT.typemethod.info {type command args} {
 
3415
    global errorInfo
 
3416
    global errorCode
 
3417
 
 
3418
    switch -exact $command {
 
3419
        typevars    -
 
3420
        typemethods -
 
3421
        instances {
 
3422
            # TBD: it should be possible to delete this error
 
3423
            # handling.
 
3424
            set errflag [catch {
 
3425
                uplevel ::snit::RT.typemethod.info.$command \
 
3426
                    $type $args
 
3427
            } result]
 
3428
                
 
3429
            if {$errflag} {
 
3430
                return -code error -errorinfo $errorInfo \
 
3431
                    -errorcode $errorCode $result
 
3432
            } else {
 
3433
                return $result
 
3434
            }
 
3435
        }
 
3436
        default {
 
3437
            error "\"$type info $command\" is not defined"
 
3438
        }
 
3439
    }
 
3440
}
 
3441
 
 
3442
 
 
3443
# Returns a list of the type's typevariables whose names match a 
 
3444
# pattern, excluding Snit internal variables.
 
3445
#
 
3446
# type          A Snit type
 
3447
# pattern       Optional.  The glob pattern to match.  Defaults
 
3448
#               to *.
 
3449
 
 
3450
proc ::snit::RT.typemethod.info.typevars {type {pattern *}} {
 
3451
    set result {}
 
3452
    foreach name [info vars "${type}::$pattern"] {
 
3453
        set tail [namespace tail $name]
 
3454
        if {![string match "Snit_*" $tail]} {
 
3455
            lappend result $name
 
3456
        }
 
3457
    }
 
3458
    
 
3459
    return $result
 
3460
}
 
3461
 
 
3462
# Returns a list of the type's methods whose names match a 
 
3463
# pattern.  If "delegate typemethod *" is used, the list may
 
3464
# not be complete.
 
3465
#
 
3466
# type          A Snit type
 
3467
# pattern       Optional.  The glob pattern to match.  Defaults
 
3468
#               to *.
 
3469
 
 
3470
proc ::snit::RT.typemethod.info.typemethods {type {pattern *}} {
 
3471
    variable ${type}::Snit_typemethodInfo
 
3472
    variable ${type}::Snit_typemethodCache
 
3473
 
 
3474
    # FIRST, get the explicit names, skipping prefixes.
 
3475
    set result {}
 
3476
 
 
3477
    foreach name [array names Snit_typemethodInfo -glob $pattern] {
 
3478
        if {[lindex $Snit_typemethodInfo($name) 0] != 1} {
 
3479
            lappend result $name
 
3480
        }
 
3481
    }
 
3482
 
 
3483
    # NEXT, add any from the cache that aren't explicit.
 
3484
    if {[info exists Snit_typemethodInfo(*)]} {
 
3485
        # First, remove "*" from the list.
 
3486
        set ndx [lsearch -exact $result "*"]
 
3487
        if {$ndx != -1} {
 
3488
            set result [lreplace $result $ndx $ndx]
 
3489
        }
 
3490
 
 
3491
        foreach name [array names Snit_typemethodCache -glob $pattern] {
 
3492
            if {[lsearch -exact $result $name] == -1} {
 
3493
                lappend result $name
 
3494
            }
 
3495
        }
 
3496
    }
 
3497
 
 
3498
    return $result
 
3499
}
 
3500
 
 
3501
# Returns a list of the type's instances whose names match
 
3502
# a pattern.
 
3503
#
 
3504
# type          A Snit type
 
3505
# pattern       Optional.  The glob pattern to match
 
3506
#               Defaults to *
 
3507
#
 
3508
# REQUIRE: type is fully qualified.
 
3509
 
 
3510
proc ::snit::RT.typemethod.info.instances {type {pattern *}} {
 
3511
    set result {}
 
3512
 
 
3513
    foreach selfns [namespace children $type] {
 
3514
        upvar ${selfns}::Snit_instance instance
 
3515
 
 
3516
        if {[string match $pattern $instance]} {
 
3517
            lappend result $instance
 
3518
        }
 
3519
    }
 
3520
 
 
3521
    return $result
 
3522
}
 
3523
 
 
3524
#-----------------------------------------------------------------------
 
3525
# Instance Introspection
 
3526
 
 
3527
# Implements the standard "info" method.
 
3528
#
 
3529
# type          The snit type
 
3530
# selfns        The instance's instance namespace
 
3531
# win           The instance's original name
 
3532
# self          The instance's current name
 
3533
# command       The info subcommand
 
3534
# args          All other arguments.
 
3535
 
 
3536
proc ::snit::RT.method.info {type selfns win self command args} {
 
3537
    switch -exact $command {
 
3538
        type        -
 
3539
        vars        -
 
3540
        options     -
 
3541
        methods     -
 
3542
        typevars    -
 
3543
        typemethods {
 
3544
            set errflag [catch {
 
3545
                uplevel ::snit::RT.method.info.$command \
 
3546
                    $type $selfns $win $self $args
 
3547
            } result]
 
3548
            
 
3549
            if {$errflag} {
 
3550
                global errorInfo
 
3551
                return -code error -errorinfo $errorInfo $result
 
3552
            } else {
 
3553
                return $result
 
3554
            }
 
3555
        }
 
3556
        default {
 
3557
            # error "\"$self info $command\" is not defined"
 
3558
            return -code error "\"$self info $command\" is not defined"
 
3559
        }
 
3560
    }
 
3561
}
 
3562
 
 
3563
# $self info type
 
3564
#
 
3565
# Returns the instance's type
 
3566
proc ::snit::RT.method.info.type {type selfns win self} {
 
3567
    return $type
 
3568
}
 
3569
 
 
3570
# $self info typevars
 
3571
#
 
3572
# Returns the instance's type's typevariables
 
3573
proc ::snit::RT.method.info.typevars {type selfns win self {pattern *}} {
 
3574
    return [RT.typemethod.info.typevars $type $pattern]
 
3575
}
 
3576
 
 
3577
# $self info typemethods
 
3578
#
 
3579
# Returns the instance's type's typemethods
 
3580
proc ::snit::RT.method.info.typemethods {type selfns win self {pattern *}} {
 
3581
    return [RT.typemethod.info.typemethods $type $pattern]
 
3582
}
 
3583
 
 
3584
# Returns a list of the instance's methods whose names match a 
 
3585
# pattern.  If "delegate method *" is used, the list may
 
3586
# not be complete.
 
3587
#
 
3588
# type          A Snit type
 
3589
# selfns        The instance namespace
 
3590
# win           The original instance name
 
3591
# self          The current instance name
 
3592
# pattern       Optional.  The glob pattern to match.  Defaults
 
3593
#               to *.
 
3594
 
 
3595
proc ::snit::RT.method.info.methods {type selfns win self {pattern *}} {
 
3596
    variable ${type}::Snit_methodInfo
 
3597
    variable ${selfns}::Snit_methodCache
 
3598
 
 
3599
    # FIRST, get the explicit names, skipping prefixes.
 
3600
    set result {}
 
3601
 
 
3602
    foreach name [array names Snit_methodInfo -glob $pattern] {
 
3603
        if {[lindex $Snit_methodInfo($name) 0] != 1} {
 
3604
            lappend result $name
 
3605
        }
 
3606
    }
 
3607
 
 
3608
    # NEXT, add any from the cache that aren't explicit.
 
3609
    if {[info exists Snit_methodInfo(*)]} {
 
3610
        # First, remove "*" from the list.
 
3611
        set ndx [lsearch -exact $result "*"]
 
3612
        if {$ndx != -1} {
 
3613
            set result [lreplace $result $ndx $ndx]
 
3614
        }
 
3615
 
 
3616
        foreach name [array names Snit_methodCache -glob $pattern] {
 
3617
            if {[lsearch -exact $result $name] == -1} {
 
3618
                lappend result $name
 
3619
            }
 
3620
        }
 
3621
    }
 
3622
 
 
3623
    return $result
 
3624
}
 
3625
 
 
3626
# $self info vars
 
3627
#
 
3628
# Returns the instance's instance variables
 
3629
proc ::snit::RT.method.info.vars {type selfns win self {pattern *}} {
 
3630
    set result {}
 
3631
    foreach name [info vars "${selfns}::$pattern"] {
 
3632
        set tail [namespace tail $name]
 
3633
        if {![string match "Snit_*" $tail]} {
 
3634
            lappend result $name
 
3635
        }
 
3636
    }
 
3637
 
 
3638
    return $result
 
3639
}
 
3640
 
 
3641
# $self info options 
 
3642
#
 
3643
# Returns a list of the names of the instance's options
 
3644
proc ::snit::RT.method.info.options {type selfns win self {pattern *}} {
 
3645
    variable ${type}::Snit_optionInfo
 
3646
 
 
3647
    # First, get the local and explicitly delegated options
 
3648
    set result [concat $Snit_optionInfo(local) $Snit_optionInfo(delegated)]
 
3649
 
 
3650
    # If "configure" works as for Tk widgets, add the resulting
 
3651
    # options to the list.  Skip excepted options
 
3652
    if {$Snit_optionInfo(starcomp) ne ""} {
 
3653
        upvar ${selfns}::Snit_components Snit_components
 
3654
        set logicalName $Snit_optionInfo(starcomp)
 
3655
        set comp $Snit_components($logicalName)
 
3656
 
 
3657
        if {![catch {$comp configure} records]} {
 
3658
            foreach record $records {
 
3659
                set opt [lindex $record 0]
 
3660
                if {[lsearch -exact $result $opt] == -1 &&
 
3661
                    [lsearch -exact $Snit_optionInfo(except) $opt] == -1} {
 
3662
                    lappend result $opt
 
3663
                }
 
3664
            }
 
3665
        }
 
3666
    }
 
3667
 
 
3668
    # Next, apply the pattern
 
3669
    set names {}
 
3670
 
 
3671
    foreach name $result {
 
3672
        if {[string match $pattern $name]} {
 
3673
            lappend names $name
 
3674
        }
 
3675
    }
 
3676
 
 
3677
    return $names
 
3678
}