Tcl Library Source Code

Check-in [6cda9ff384]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Added checks in oometa to prevent errors for classes that define no metadata Added better handling of ensembles for cases of mixins and morphs Added an automatic rebuild of metadata prior to building dynamic methods for a class Added tests for these new behaviors in tool
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 6cda9ff384ba979bf460bb054ec012fb7674b8bc
User & Date: hypnotoad 2016-10-19 12:31:34
Context
2016-10-19
16:12
Added a hook to ensure ancestors classes for every instanted object also cement their ensembles Added tests to ensure that ancestor ensembles are accessible from [next] and also that ensemble methods inherit in a method-like order check-in: f3b84e3415 user: tne tags: trunk
12:31
Added checks in oometa to prevent errors for classes that define no metadata Added better handling of ensembles for cases of mixins and morphs Added an automatic rebuild of metadata prior to building dynamic methods for a class Added tests for these new behaviors in tool check-in: 6cda9ff384 user: hypnotoad tags: trunk
2016-10-18
15:02
Added checks in oometa to prevent errors for classes that define no metadata Added better handling of ensembles for cases of mixins and morphs Added an automatic rebuild of metadata prior to building dynamic methods for a class Added tests for these new behaviors in tool check-in: 592f264a47 user: tne tags: odie
2016-10-17
16:44
Cron: Added error handling OOdialect: Fixed tabs and indentation (no code change) oometa: Added a mechanism for frameworks to intercept and detect when the metadata for a class has changed tool: Delays ensemble creation until object creation. Utilizes the new metadata modified method from oometa to invalidate the method ensembles for classed and their decendents. check-in: 182b1361cf user: hypnotoad tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to modules/oometa/oometa.tcl.

110
111
112
113
114
115
116

117

118
119
120
121

122
123
124
125
126
127
128
    branchset {
      ::oo::meta::rebuild $class
      foreach {field value} [lindex $args end] {
        ::dict set ::oo::meta::local_property($class) {*}[lrange $args 0 end-1] [string trimright $field :]: $value
      }
    }
    leaf_add {

      set result [dict getnull $::oo::meta::local_property($class) {*}[lindex $args 0]]

      ladd result {*}[lrange $args 1 end]
      dict set ::oo::meta::local_property($class) {*}[lindex $args 0] $result
    }
    leaf_remove {

      set result {}
      forearch element [dict getnull $::oo::meta::local_property($class) {*}[lindex $args 0]] {
        if { $element in [lrange $args 1 end]} continue
        lappend result $element
      }
      dict set ::oo::meta::local_property($class) {*}[lindex $args 0] $result
    }







>
|
>




>







110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
    branchset {
      ::oo::meta::rebuild $class
      foreach {field value} [lindex $args end] {
        ::dict set ::oo::meta::local_property($class) {*}[lrange $args 0 end-1] [string trimright $field :]: $value
      }
    }
    leaf_add {
      if {[::info exists ::oo::meta::local_property($class)]} {
        set result [dict getnull $::oo::meta::local_property($class) {*}[lindex $args 0]]
      }
      ladd result {*}[lrange $args 1 end]
      dict set ::oo::meta::local_property($class) {*}[lindex $args 0] $result
    }
    leaf_remove {
      if {![::info exists ::oo::meta::local_property($class)]} return
      set result {}
      forearch element [dict getnull $::oo::meta::local_property($class) {*}[lindex $args 0]] {
        if { $element in [lrange $args 1 end]} continue
        lappend result $element
      }
      dict set ::oo::meta::local_property($class) {*}[lindex $args 0] $result
    }
177
178
179
180
181
182
183

184
185
186
187
188
189
190
    foreach dclass $dirty_classes {
      foreach {cclass cancestors} [array get ::oo::meta::cached_hierarchy] {
        if {$dclass in $cancestors} {
          unset -nocomplain ::oo::meta::cached_property($cclass)
          unset -nocomplain ::oo::meta::cached_hierarchy($cclass)
        }
      }

      if {[dict getnull $::oo::meta::local_property($dclass) classinfo type:] eq "core"} {
        if {$dclass ni $::oo::meta::core_classes} {
          lappend ::oo::meta::core_classes $dclass
        }
      }
    }
    set dirty_classes {}







>







180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
    foreach dclass $dirty_classes {
      foreach {cclass cancestors} [array get ::oo::meta::cached_hierarchy] {
        if {$dclass in $cancestors} {
          unset -nocomplain ::oo::meta::cached_property($cclass)
          unset -nocomplain ::oo::meta::cached_hierarchy($cclass)
        }
      }
      if {![::info exists ::oo::meta::local_property($dclass)]} continue
      if {[dict getnull $::oo::meta::local_property($dclass) classinfo type:] eq "core"} {
        if {$dclass ni $::oo::meta::core_classes} {
          lappend ::oo::meta::core_classes $dclass
        }
      }
    }
    set dirty_classes {}

Changes to modules/tool/ensemble.tcl.

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

30
31
32
33
34
35
36

37
38
39
40
41

42
43
44
45
46
47
48
49
    }
    if {$class ni $::tool::dirty_classes} {
      lappend ::tool::dirty_classes $class
    }
  }
}


###
# topic: fb8d74e9c08db81ee6f1275dad4d7d6f
###
proc ::tool::dynamic_object_ensembles {thisobject thisclass} {
  variable trace
  set ensembledict {}
  foreach dclass $::tool::dirty_classes {

    foreach {cclass cancestors} [array get ::oo::meta::cached_hierarchy] {
      if {$dclass in $cancestors} {
        unset -nocomplain ::tool::obj_ensemble_cache($cclass)
      }
    }
  }
  set ::tool::dirty_classes {}

  ###
  # Only go through the motions for classes that have a locally defined
  # ensemble method implementation
  ###
  if {[info exists ::tool::obj_ensemble_cache($thisclass)]} return

  foreach {ensemble einfo} [::oo::meta::info $thisclass getnull method_ensemble] {
    #set einfo [dict getnull $einfo method_ensemble $ensemble]
    set eswitch {}
    set default standard
    if {[dict exists $einfo default:]} {
      set emethodinfo [dict get $einfo default:]
      set arglist     [lindex $emethodinfo 0]
      set realbody    [lindex $emethodinfo 1]







|
<
<
<
<
<
|
|
>
|
<
|



|
>
|
<
|
<
<
>
|







15
16
17
18
19
20
21
22





23
24
25
26

27
28
29
30
31
32
33

34


35
36
37
38
39
40
41
42
43
    }
    if {$class ni $::tool::dirty_classes} {
      lappend ::tool::dirty_classes $class
    }
  }
}

proc ::tool::ensemble_build_map args {





  set emap {}
  foreach thisclass $args {
    foreach {ensemble einfo} [::oo::meta::info $thisclass getnull method_ensemble] {
      foreach {submethod subinfo} $einfo {

        dict set emap $ensemble $submethod $subinfo
      }
    }
  }
  return $emap
}


proc ::tool::ensemble_methods emap {


  set result {}
  foreach {ensemble einfo} $emap {
    #set einfo [dict getnull $einfo method_ensemble $ensemble]
    set eswitch {}
    set default standard
    if {[dict exists $einfo default:]} {
      set emethodinfo [dict get $einfo default:]
      set arglist     [lindex $emethodinfo 0]
      set realbody    [lindex $emethodinfo 1]
88
89
90
91
92
93
94


























95
96

97
98
99
100
101
102
103
104
105
106
107
      set body [lindex [dict get $einfo _preamble:] 1]
    } else {
      set body {}
    }
    append body \n [list set methodlist $methodlist]
    append body \n "set code \[catch {switch -- \$method [list $eswitch]} result opts\]"
    append body \n {return -options $opts $result}


























    oo::define $thisclass method $ensemble {{method default} args} $body
    # Define a property for this ensemble for introspection

    ::oo::meta::info $thisclass set ensemble_methods $ensemble: [lsort -dictionary [dict keys $einfo]]
  }
  set ::tool::obj_ensemble_cache($thisclass) 1

}

###
# topic: ec9ca249b75e2667ad5bcb2f7cd8c568
# title: Define an ensemble method for this agent
###
::proc ::tool::define::method {rawmethod args} {







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>



<







82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

121
122
123
124
125
126
127
      set body [lindex [dict get $einfo _preamble:] 1]
    } else {
      set body {}
    }
    append body \n [list set methodlist $methodlist]
    append body \n "set code \[catch {switch -- \$method [list $eswitch]} result opts\]"
    append body \n {return -options $opts $result}
    append result \n [list method $ensemble {{method default} args} $body]    
  }
  return $result
}

###
# topic: fb8d74e9c08db81ee6f1275dad4d7d6f
###
proc ::tool::dynamic_object_ensembles {thisobject thisclass} {
  variable trace
  set ensembledict {}
  foreach dclass $::tool::dirty_classes {
    foreach {cclass cancestors} [array get ::oo::meta::cached_hierarchy] {
      if {$dclass in $cancestors} {
        unset -nocomplain ::tool::obj_ensemble_cache($cclass)
      }
    }
  }
  set ::tool::dirty_classes {}
  ###
  # Only go through the motions for classes that have a locally defined
  # ensemble method implementation
  ###
  if {[info exists ::tool::obj_ensemble_cache($thisclass)]} return
  set emap [::tool::ensemble_build_map $thisclass]
  set body [::tool::ensemble_methods $emap]
  oo::define $thisclass $body
  # Define a property for this ensemble for introspection
  foreach {ensemble einfo} $emap {
    ::oo::meta::info $thisclass set ensemble_methods $ensemble: [lsort -dictionary [dict keys $einfo]]
  }
  set ::tool::obj_ensemble_cache($thisclass) 1

}

###
# topic: ec9ca249b75e2667ad5bcb2f7cd8c568
# title: Define an ensemble method for this agent
###
::proc ::tool::define::method {rawmethod args} {

Changes to modules/tool/metaclass.tcl.

146
147
148
149
150
151
152

153
154
155
156
157
158
159
  return $result
}

###
# topic: a92cd258900010f656f4c6e7dbffae57
###
proc ::tool::dynamic_methods class {

  set metadata [::oo::meta::metadata $class]
  foreach command [info commands [namespace current]::dynamic_methods_*] {
    $command $class $metadata
  }
}

###







>







146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  return $result
}

###
# topic: a92cd258900010f656f4c6e7dbffae57
###
proc ::tool::dynamic_methods class {
  ::oo::meta::rebuild $class
  set metadata [::oo::meta::metadata $class]
  foreach command [info commands [namespace current]::dynamic_methods_*] {
    $command $class $metadata
  }
}

###
310
311
312
313
314
315
316

317
318
319
320
321
322
323
# This class is inherited by all classes that have options.
#

::tool::define ::tool::object {
  # Put MOACish stuff in here
  variable signals_pending create
  variable organs {}

  
  constructor args {
    my Config_merge [::tool::args_to_options {*}$args]
  }
  
  destructor {}
    







>







311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# This class is inherited by all classes that have options.
#

::tool::define ::tool::object {
  # Put MOACish stuff in here
  variable signals_pending create
  variable organs {}
  variable mixins {}
  
  constructor args {
    my Config_merge [::tool::args_to_options {*}$args]
  }
  
  destructor {}
    
449
450
451
452
453
454
455
456
457
458
459
460



461
462
463
464



465
466
467
468
469
470
471
        }
      }
    }
    set dat [dict getnull $public option_info]
    if {$integrate} {
      my meta rmerge [list option $dat]
    }
    #set field [my cget field]
    my variable option_canonical
    array set option_canonical [dict getnull $public option_canonical]
    set dictargs {}
    foreach {var getcmd} [dict getnull $public option_default_command] {



      if {[dict exists $config $var]} continue
      dict set dictargs $var [{*}[string map [list %field% $var %self% [namespace which my]] $getcmd]]
    }
    foreach {var value} [dict getnull $public option_default_value] {



      if {[dict exists $config $var]} continue
      dict set dictargs $var $value
    }
    ###
    # Apply all inputs with special rules
    ###
    foreach {field val} $dictargs {







<




>
>
>




>
>
>







451
452
453
454
455
456
457

458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
        }
      }
    }
    set dat [dict getnull $public option_info]
    if {$integrate} {
      my meta rmerge [list option $dat]
    }

    my variable option_canonical
    array set option_canonical [dict getnull $public option_canonical]
    set dictargs {}
    foreach {var getcmd} [dict getnull $public option_default_command] {
      if {[dict getnull $dat $var class:] eq "organ"} {
        if {[my organ $var] ne {}} continue
      }
      if {[dict exists $config $var]} continue
      dict set dictargs $var [{*}[string map [list %field% $var %self% [namespace which my]] $getcmd]]
    }
    foreach {var value} [dict getnull $public option_default_value] {
      if {[dict getnull $dat $var class:] eq "organ"} {
        if {[my organ $var] ne {}} continue
      }
      if {[dict exists $config $var]} continue
      dict set dictargs $var $value
    }
    ###
    # Apply all inputs with special rules
    ###
    foreach {field val} $dictargs {
484
485
486
487
488
489
490
491
492
493
494


495








496

497
498
499
500
501
502
503
504
505
506
507

508
509


510
511
512
513
514
515
516
  #    Provide a default value for all options and
  #    publically declared variables, and locks the
  #    pipeline mutex to prevent signal processing
  #    while the contructor is still running.
  #    Note, by default an odie object will ignore
  #    signals until a later call to <i>my lock remove pipeline</i>
  ###
  method mixin class {
    ###
    # Mix in the class
    ###


    ::oo::objdefine [self] mixin $class








    my ClassPublicApply $class

  }
  
  method morph newclass {
    if {$newclass eq {}} return
    set class [string trimleft [info object class [self]]]
    set newclass [string trimleft $newclass :]
    if {[info command ::$newclass] eq {}} {
      error "Class $newclass does not exist"
    }
    if { $class ne $newclass } {
      my Morph_leave

      oo::objdefine [self] class ::${newclass}
      my graft class ::${newclass}


      my InitializePublic
      my Morph_enter
    }
  }

  ###
  # Commands to perform as this object transitions out of the present class







|



>
>
|
>
>
>
>
>
>
>
>
|
>











>


>
>







491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
  #    Provide a default value for all options and
  #    publically declared variables, and locks the
  #    pipeline mutex to prevent signal processing
  #    while the contructor is still running.
  #    Note, by default an odie object will ignore
  #    signals until a later call to <i>my lock remove pipeline</i>
  ###
  method mixin args {
    ###
    # Mix in the class
    ###
    my variable mixins
    set mixins $args
    ::oo::objdefine [self] mixin {*}$args
    ###
    # Build a compsite map of all ensembles defined by the object's current
    # class as well as all of the classes being mixed in
    ###
    set emap [::tool::ensemble_build_map [::info object class [self]] {*}[lreverse $args]]
    set body [::tool::ensemble_methods $emap]
    oo::objdefine [self] $body
    foreach class $args {
      my ClassPublicApply $class
    }
  }
  
  method morph newclass {
    if {$newclass eq {}} return
    set class [string trimleft [info object class [self]]]
    set newclass [string trimleft $newclass :]
    if {[info command ::$newclass] eq {}} {
      error "Class $newclass does not exist"
    }
    if { $class ne $newclass } {
      my Morph_leave
      my variable mixins
      oo::objdefine [self] class ::${newclass}
      my graft class ::${newclass}
      # Reapply mixins
      my mixin {*}$mixins
      my InitializePublic
      my Morph_enter
    }
  }

  ###
  # Commands to perform as this object transitions out of the present class
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
      return {}
    }
    if { $stub eq "all" } {
      return $organs
    }
    return [dict getnull $organs $stub]
  }

  #class_method property args {
  #  if {[my meta exists {*}$args]} {
  #    return [my meta get {*}$args]
  #  }
  #  set field [string trimright [lindex $args end] :]:
  #  if {[my meta exists {*}[lrange $args 0 end-1] $field]} {
  #    return [my meta get {*}[lrange $args 0 end-1] $field]
  #  }
  #  if {[my meta exists const {*}[lrange $args 0 end-1] $field]} {
  #    return [my meta get const {*}[lrange $args 0 end-1] $field]
  #  }
  #  return {}
  #}
  
  #method property args {
  #  if {[my meta exists {*}$args]} {
  #    return [my meta get {*}$args]
  #  }
  #  set field [string trimright [lindex $args end] :]:
  #  if {[my meta exists {*}[lrange $args 0 end-1] $field]} {
  #    return [my meta get {*}[lrange $args 0 end-1] $field]
  #  }
  #  if {[my meta exists const {*}[lrange $args 0 end-1] $field]} {
  #    return [my meta get const {*}[lrange $args 0 end-1] $field]
  #  }
  #  set class [info object class [self]]
  #  if {[$class meta exists {*}[lrange $args 0 end-1] $field]} {
  #    set value [$class meta get {*}[lrange $args 0 end-1] $field]
  #    my meta set const {*}[lrange $args 0 end-1] $field $value
  #    return $value
  #  }
  #  if {[$class meta exists const {*}[lrange $args 0 end-1] $field]} {
  #    set value [$class meta get const {*}[lrange $args 0 end-1] $field]
  #    my meta set const {*}[lrange $args 0 end-1] $field $value
  #    return $value
  #  }
  #  return {}
  #}
}









|
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
551
552
553
554
555
556
557
558













559
























560


      return {}
    }
    if { $stub eq "all" } {
      return $organs
    }
    return [dict getnull $organs $stub]
  }
}









































Changes to modules/tool/tool.test.

392
393
394
395
396
397
398









































































































399
400
401
402
403
404
405
test tool-option_class-002 {Test option class} {
  ObjectOptionTest5 organ master
} GNDN

test tool-option_class-003 {Test option class} {
  ObjectOptionTest5 <master> puts FOO
} {puts FOO}









































































































# -------------------------------------------------------------------------


testsuiteCleanup

# Local variables:
# mode: tcl







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
test tool-option_class-002 {Test option class} {
  ObjectOptionTest5 organ master
} GNDN

test tool-option_class-003 {Test option class} {
  ObjectOptionTest5 <master> puts FOO
} {puts FOO}

###
# Second round of testing
# Make sure the various and sundry ways to generate
# dynamic methods follow through morphs, mixins,
# and class method definitions
###

tool::class create WidgetClass {
  class_method unknown args {
    set tkpath [lindex $args 0]
    if {[string index $tkpath 0] eq "."} {
      set obj [my new $tkpath {*}[lrange $args 1 end]]
      $obj tkalias $tkpath
      return $tkpath
    }
    next {*}$args
  }
  
  constructor {TkPath args} {
    my variable hull
    set hull $TkPath
    my graft hull $TkPath
  }
    
  method tkalias tkname {
    set oldname $tkname
    my variable tkalias
    set tkalias $tkname
    set self [self]
    set hullwidget [::info object namespace $self]::tkwidget
    my graft tkwidget $hullwidget
    #rename ::$tkalias $hullwidget
    my graft hullwidget $hullwidget
    ::tool::object_rename [self] ::$tkalias
    #my Hull_Bind $tkname
    return $hullwidget
  }
}

test tool-class-method-001 {Test Tk style creator} {
  WidgetClass .foo
  .foo organ hull
} {.foo}

tool::class create WidgetNewClass {
  superclass WidgetClass
}

test tool-class-method-002 {Test Tk style creator inherited by morph} {
  WidgetNewClass .bar
  .bar organ hull
} {.bar}

tool::class create DummyClass {
  method i_am_here {} {
    return DummyClass
  }
}


tool::class create OrganClass {
  option db {class organ default ::noop}
  constructor args {
    my config set $args
  }
}
DummyClass create ::DbObj
OrganClass create OrganObject db ::DbObj
test tool-constructor-args-001 {Test that organs passed as options map correctly} {
  OrganObject organ db
} {::DbObj} 
test tool-constructor-args-002 {Test that organs passed as options map correctly} {
  OrganObject cget db
} {::DbObj}

tool::object create MorphOrganObject#1
tool::object create MorphOrganObject#2
MorphOrganObject#2 graft db ::DbObj

MorphOrganObject#1 morph OrganClass
test tool-constructor-args-003 {Test that a default for an organ option is applied after a morph} {
  MorphOrganObject#1  organ db
} {::noop}

MorphOrganObject#2 morph OrganClass
test tool-constructor-args-004 {Test that a default for an organ option is NOT applied if the graft exists following a morph} {
  MorphOrganObject#2  organ db
} {::DbObj}

tool::object create MorphOrganObject#3
tool::object create MorphOrganObject#4
MorphOrganObject#4 graft db ::DbObj
MorphOrganObject#3 mixin OrganClass
test tool-constructor-args-005 {Test that a default for an organ option is applied during a mixin} {
  MorphOrganObject#3  organ db
} {::noop}

MorphOrganObject#4 mixin OrganClass
test tool-constructor-args-006 {Test that a default for an organ option is NOT applied if the graft exists during a mixin} {
  MorphOrganObject#4  organ db
} {::DbObj}



# -------------------------------------------------------------------------


testsuiteCleanup

# Local variables:
# mode: tcl