Index: modules/cron/cron.tcl
==================================================================
--- modules/cron/cron.tcl
+++ modules/cron/cron.tcl
@@ -5,11 +5,13 @@
###
#
# Author: Sean Woods (for T&E Solutions)
package require Tcl 8.6 ;# See coroutine
package require coroutine
-package require dicttool
+
+package provide cron 2.1.1
+
::namespace eval ::cron {}
proc ::cron::task {command args} {
if {$::cron::trace > 1} {
puts [list ::cron::task $command $args]
@@ -399,13 +401,21 @@
if {[dict exists processTable($task) foreground] && [dict set processTable($task) foreground]} continue
if {[dict exists processTable($task) running] && [dict set processTable($task) running]} continue
if {$::cron::trace > 2} {
puts [list RUNNING $task [task info $task]]
}
- set coro [dict getnull $processTable($task) coroutine]
+ if {[dict exists $processTable($task) coroutine]} {
+ set coro [dict get $processTable($task) coroutine]
+ } else {
+ set coro {}
+ }
dict set processTable($task) running 1
- set command [dict getnull $processTable($task) command]
+ if {[dict exists $processTable($task) command]} {
+ set command [dict get $processTable($task) command]
+ } else {
+ set command {}
+ }
if {$command eq {} && $coro eq {}} {
# Task has nothing to do. Slot it for destruction
lappend cancellist $task
} elseif {$coro ne {}} {
if {[info command $coro] eq {}} {
@@ -613,7 +623,6 @@
}
}
}
::cron::wake STARTUP
-package provide cron 2.1
Index: modules/cron/cron.test
==================================================================
--- modules/cron/cron.test
+++ modules/cron/cron.test
@@ -14,11 +14,10 @@
package require tcltest
testsNeedTcl 8.6
testsNeedTcltest 1.0
support {
- use dicttool/dicttool.tcl dicttool
}
testing {
useLocal cron.tcl cron
}
@@ -34,11 +33,11 @@
11235 11000
1241241 1241000
} {
test cron-step-$val [list test clock_step function for $val] {
::cron::clock_step $val
- } $testval
+ } $testval
}
proc test_elapsed_time {start target} {
set now [::cron::current_time]
set value [expr {$now-$start}]
@@ -212,11 +211,11 @@
set doloop 0
if {$::cron::trace} {
puts [list [self] SIGNAL TO EXIT]
}
}
-
+
method DoLoop {} {
if {$::cron::trace} {
puts "[self] CORO START"
}
my variable doloop
@@ -360,11 +359,11 @@
puts "TIME DELAY OUT OF RANGE: $delay"
return 1
} else {
return 0
}
-
+
} 0
###
# Tests after this point test interactions with the Tcl event loop
# We need to be slaved to the real time clock to work properly
Index: modules/cron/pkgIndex.tcl
==================================================================
--- modules/cron/pkgIndex.tcl
+++ modules/cron/pkgIndex.tcl
@@ -1,2 +1,2 @@
if {![package vsatisfies [package provide Tcl] 8.6]} {return}
-package ifneeded cron 2.1 [list source [file join $dir cron.tcl]]
+package ifneeded cron 2.1.1 [list source [file join $dir cron.tcl]]
Index: modules/dicttool/dicttool.tcl
==================================================================
--- modules/dicttool/dicttool.tcl
+++ modules/dicttool/dicttool.tcl
@@ -1,155 +1,155 @@
-###
-# This package enhances the stock dict implementation with some
-# creature comforts
-###
-if {[info commands ::ladd] eq {}} {
- proc ladd {varname args} {
- upvar 1 $varname var
- if ![info exists var] {
- set var {}
- }
- foreach item $args {
- if {$item in $var} continue
- lappend var $item
- }
- return $var
- }
-}
-
-if {[info command ::ldelete] eq {}} {
- proc ::ldelete {varname args} {
- upvar 1 $varname var
- if ![info exists var] {
- return
- }
- foreach item [lsort -unique $args] {
- while {[set i [lsearch $var $item]]>=0} {
- set var [lreplace $var $i $i]
- }
- }
- return $var
- }
-}
-
-if {[::info commands ::tcl::dict::getnull] eq {}} {
- proc ::tcl::dict::getnull {dictionary args} {
- if {[exists $dictionary {*}$args]} {
- get $dictionary {*}$args
- }
- }
- namespace ensemble configure dict -map [dict replace\
- [namespace ensemble configure dict -map] getnull ::tcl::dict::getnull]
-}
-if {[::info commands ::tcl::dict::print] eq {}} {
- ###
- # Test if element is a dict
- ###
- proc ::tcl::dict::_putb {buffervar indent field value} {
- ::upvar 1 $buffervar buffer
- ::append buffer \n [::string repeat " " $indent] [::list $field] " "
- if {[string index $field end] eq "/"} {
- ::incr indent 2
- ::append buffer "\{"
- foreach item $value {
- if [catch {
- if {![is_dict $item]} {
- ::append buffer \n [::string repeat " " $indent] [list $item]
- } else {
- ::append buffer \n "[::string repeat " " $indent]\{"
- ::incr indent 2
- foreach {sf sv} $item {
- _putb buffer $indent $sf $sv
- }
- ::incr indent -2
- ::append buffer \n "[::string repeat " " $indent]\}"
- }
- } err] {
- puts [list FAILED $indent $field $item]
- puts $err
- puts "$::errorInfo"
- }
- }
- ::incr indent -2
- ::append buffer \n [::string repeat " " $indent] "\}"
- } elseif {[string index $field end] eq ":" || ![is_dict $value]} {
- ::append buffer [::list $value]
- } else {
- ::incr indent 2
- ::append buffer "\{"
- foreach {f v} $value {
- _putb buffer $indent $f $v
- }
- ::incr indent -2
- ::append buffer \n [::string repeat " " $indent] "\}"
- }
- }
- proc ::tcl::dict::print dict {
- ::set buffer {}
- ::foreach {field value} $dict {
- _putb buffer 0 $field $value
- }
- return $buffer
- }
-
- namespace ensemble configure dict -map [dict replace\
- [namespace ensemble configure dict -map] print ::tcl::dict::print]
-}
-if {[::info commands ::tcl::dict::is_dict] eq {}} {
- ###
- # Test if element is a dict
- ###
- proc ::tcl::dict::is_dict { d } {
- # is it a dict, or can it be treated like one?
- if {[catch {dict size $d} err]} {
- #::set ::errorInfo {}
- return 0
- }
- return 1
- }
- namespace ensemble configure dict -map [dict replace\
- [namespace ensemble configure dict -map] is_dict ::tcl::dict::is_dict]
-}
-if {[::info commands ::tcl::dict::rmerge] eq {}} {
- ###
- # title: A recursive form of dict merge
- # description:
- # A routine to recursively dig through dicts and merge
- # adapted from http://stevehavelka.com/tcl-dict-operation-nested-merge/
- ###
- proc ::tcl::dict::rmerge {a args} {
- ::set result $a
- # Merge b into a, and handle nested dicts appropriately
- ::foreach b $args {
- for { k v } $b {
- if {[string index $k end] eq ":"} {
- # Element names that end in ":" are assumed to be literals
- set result $k $v
- } elseif { [dict exists $result $k] } {
- # key exists in a and b? let's see if both values are dicts
- # both are dicts, so merge the dicts
- if { [is_dict [get $result $k]] && [is_dict $v] } {
- set result $k [rmerge [get $result $k] $v]
- } else {
- set result $k $v
- }
- } else {
- set result $k $v
- }
- }
- }
- return $result
- }
- namespace ensemble configure dict -map [dict replace\
- [namespace ensemble configure dict -map] rmerge ::tcl::dict::rmerge]
-}
-
-if {[::info commands ::tcl::dict::isnull] eq {}} {
- proc ::tcl::dict::isnull {dictionary args} {
- if {![exists $dictionary {*}$args]} {return 1}
- return [expr {[get $dictionary {*}$args] in {{} NULL null}}]
- }
- namespace ensemble configure dict -map [dict replace\
- [namespace ensemble configure dict -map] isnull ::tcl::dict::isnull]
-}
-
-package provide dicttool 1.1
+###
+# This package enhances the stock dict implementation with some
+# creature comforts
+###
+if {[info commands ::ladd] eq {}} {
+ proc ladd {varname args} {
+ upvar 1 $varname var
+ if ![info exists var] {
+ set var {}
+ }
+ foreach item $args {
+ if {$item in $var} continue
+ lappend var $item
+ }
+ return $var
+ }
+}
+
+if {[info command ::ldelete] eq {}} {
+ proc ::ldelete {varname args} {
+ upvar 1 $varname var
+ if ![info exists var] {
+ return
+ }
+ foreach item [lsort -unique $args] {
+ while {[set i [lsearch $var $item]]>=0} {
+ set var [lreplace $var $i $i]
+ }
+ }
+ return $var
+ }
+}
+
+if {[::info commands ::tcl::dict::getnull] eq {}} {
+ proc ::tcl::dict::getnull {dictionary args} {
+ if {[exists $dictionary {*}$args]} {
+ get $dictionary {*}$args
+ }
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] getnull ::tcl::dict::getnull]
+}
+if {[::info commands ::tcl::dict::print] eq {}} {
+ ###
+ # Test if element is a dict
+ ###
+ proc ::tcl::dict::_putb {buffervar indent field value} {
+ ::upvar 1 $buffervar buffer
+ ::append buffer \n [::string repeat " " $indent] [::list $field] " "
+ if {[string index $field end] eq "/"} {
+ ::incr indent 2
+ ::append buffer "\{"
+ foreach item $value {
+ if [catch {
+ if {![is_dict $item]} {
+ ::append buffer \n [::string repeat " " $indent] [list $item]
+ } else {
+ ::append buffer \n "[::string repeat " " $indent]\{"
+ ::incr indent 2
+ foreach {sf sv} $item {
+ _putb buffer $indent $sf $sv
+ }
+ ::incr indent -2
+ ::append buffer \n "[::string repeat " " $indent]\}"
+ }
+ } err] {
+ puts [list FAILED $indent $field $item]
+ puts $err
+ puts "$::errorInfo"
+ }
+ }
+ ::incr indent -2
+ ::append buffer \n [::string repeat " " $indent] "\}"
+ } elseif {[string index $field end] eq ":" || ![is_dict $value]} {
+ ::append buffer [::list $value]
+ } else {
+ ::incr indent 2
+ ::append buffer "\{"
+ foreach {f v} $value {
+ _putb buffer $indent $f $v
+ }
+ ::incr indent -2
+ ::append buffer \n [::string repeat " " $indent] "\}"
+ }
+ }
+ proc ::tcl::dict::print dict {
+ ::set buffer {}
+ ::foreach {field value} $dict {
+ _putb buffer 0 $field $value
+ }
+ return $buffer
+ }
+
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] print ::tcl::dict::print]
+}
+if {[::info commands ::tcl::dict::is_dict] eq {}} {
+ ###
+ # Test if element is a dict
+ ###
+ proc ::tcl::dict::is_dict { d } {
+ # is it a dict, or can it be treated like one?
+ if {[catch {dict size $d} err]} {
+ #::set ::errorInfo {}
+ return 0
+ }
+ return 1
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] is_dict ::tcl::dict::is_dict]
+}
+if {[::info commands ::tcl::dict::rmerge] eq {}} {
+ ###
+ # title: A recursive form of dict merge
+ # description:
+ # A routine to recursively dig through dicts and merge
+ # adapted from http://stevehavelka.com/tcl-dict-operation-nested-merge/
+ ###
+ proc ::tcl::dict::rmerge {a args} {
+ ::set result $a
+ # Merge b into a, and handle nested dicts appropriately
+ ::foreach b $args {
+ for { k v } $b {
+ if {[string index $k end] eq ":"} {
+ # Element names that end in ":" are assumed to be literals
+ set result $k $v
+ } elseif { [dict exists $result $k] } {
+ # key exists in a and b? let's see if both values are dicts
+ # both are dicts, so merge the dicts
+ if { [is_dict [get $result $k]] && [is_dict $v] } {
+ set result $k [rmerge [get $result $k] $v]
+ } else {
+ set result $k $v
+ }
+ } else {
+ set result $k $v
+ }
+ }
+ }
+ return $result
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] rmerge ::tcl::dict::rmerge]
+}
+
+if {[::info commands ::tcl::dict::isnull] eq {}} {
+ proc ::tcl::dict::isnull {dictionary args} {
+ if {![exists $dictionary {*}$args]} {return 1}
+ return [expr {[get $dictionary {*}$args] in {{} NULL null}}]
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] isnull ::tcl::dict::isnull]
+}
+
+#package provide dicttool 1.1
Index: modules/tool/build/build.tcl
==================================================================
--- modules/tool/build/build.tcl
+++ modules/tool/build/build.tcl
@@ -1,9 +1,9 @@
set srcdir [file dirname [file normalize [file join [pwd] [info script]]]]
set moddir [file dirname $srcdir]
-set version 0.7
+set version 0.7.1
set module [file tail $moddir]
set fout [open [file join $moddir ${module}.tcl] w]
dict set map %module% $module
dict set map %version% $version
@@ -24,10 +24,14 @@
set loaded {}
lappend loaded build.tcl
# These files must be loaded in a particular order
foreach file {
+ dicttool.tcl
+ dialect.tcl
+ oometa.tcl
+ cron.tcl
core.tcl
uuid.tcl
ensemble.tcl
metaclass.tcl
option.tcl
Index: modules/tool/build/core.tcl
==================================================================
--- modules/tool/build/core.tcl
+++ modules/tool/build/core.tcl
@@ -1,12 +1,11 @@
package require Tcl 8.6 ;# try in pipeline.tcl. Possibly other things.
-package require dicttool
+package require coroutine
package require TclOO
package require sha1
#package require cron 2.0
-package require oo::meta 0.5.1
-package require oo::dialect
+#package require oo::dialect
::oo::dialect::create ::tool
::namespace eval ::tool {}
set ::tool::trace 0
ADDED modules/tool/build/cron.tcl
Index: modules/tool/build/cron.tcl
==================================================================
--- /dev/null
+++ modules/tool/build/cron.tcl
@@ -0,0 +1,624 @@
+###
+# This file implements a process table
+# Instead of having individual components try to maintain their own timers
+# we centrally manage how often tasks should be kicked off here.
+###
+#
+# Author: Sean Woods (for T&E Solutions)
+
+::namespace eval ::cron {}
+
+proc ::cron::task {command args} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::task $command $args]
+ }
+ variable processTable
+ switch $command {
+ TEMPLATE {
+ return [list object {} lastevent 0 lastrun 0 err 0 result {} \
+ running 0 coroutine {} scheduled 0 frequency 0 command {}]
+ }
+ delete {
+ unset -nocomplain ::cron::processTable([lindex $args 0])
+ }
+ exists {
+ return [::info exists ::cron::processTable([lindex $args 0])]
+ }
+ info {
+ set process [lindex $args 0]
+ if {![::info exists ::cron::processTable($process)]} {
+ error "Process $process does not exist"
+ }
+ return $::cron::processTable($process)
+ }
+ frequency {
+ set process [lindex $args 0]
+ set time [lindex $args 1]
+ if {![info exists ::cron::processTable($process)]} return
+ dict with ::cron::processTable($process) {
+ set now [clock_step [current_time]]
+ set frequency [expr {0+$time}]
+ if {$scheduled>($now+$time)} {
+ dict set ::cron::processTable($process) scheduled [expr {$now+$time}]
+ }
+ }
+ }
+ sleep {
+ set process [lindex $args 0]
+ set time [lindex $args 1]
+ if {![info exists ::cron::processTable($process)]} return
+ dict with ::cron::processTable($process) {
+ set now [clock_step [current_time]]
+ set frequency 0
+ set scheduled [expr {$now+$time}]
+ }
+ }
+ create -
+ set {
+ set process [lindex $args 0]
+ if {![::info exists ::cron::processTable($process)]} {
+ set ::cron::processTable($process) [task TEMPLATE]
+ }
+ if {[llength $args]==2} {
+ foreach {field value} [lindex $args 1] {
+ dict set ::cron::processTable($process) $field $value
+ }
+ } else {
+ foreach {field value} [lrange $args 1 end] {
+ dict set ::cron::processTable($process) $field $value
+ }
+ }
+ }
+ }
+}
+
+proc ::cron::at args {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::at $args]
+ }
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args timecode command
+ }
+ 3 {
+ lassign $args process timecode command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ variable processTable
+ if {[string is integer -strict $timecode]} {
+ set scheduled [expr {$timecode*1000}]
+ } else {
+ set scheduled [expr {[clock scan $timecode]*1000}]
+ }
+ ::cron::task set $process \
+ frequency -1 \
+ command $command \
+ scheduled $scheduled \
+ coroutine {}
+
+ if {$::cron::trace > 1} {
+ puts [list ::cron::task info $process - > [::cron::task info $process]]
+ }
+ ::cron::wake NEW
+ return $process
+}
+
+proc ::cron::idle args {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::idle $args]
+ }
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args command
+ }
+ 3 {
+ lassign $args process command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ ::cron::task set $process \
+ scheduled 0 \
+ frequency 0 \
+ command $command
+ ::cron::wake NEW
+ return $process
+}
+
+proc ::cron::in args {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::in $args]
+ }
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args timecode command
+ }
+ 3 {
+ lassign $args process timecode command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ set now [clock_step [current_time]]
+ set scheduled [expr {$timecode*1000+$now}]
+ ::cron::task set $process \
+ frequency -1 \
+ command $command \
+ scheduled $scheduled
+ ::cron::wake NEW
+ return $process
+}
+
+proc ::cron::cancel {process} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::cancel $process]
+ }
+ ::cron::task delete $process
+}
+
+###
+# topic: 0776dccd7e84530fa6412e507c02487c
+###
+proc ::cron::every {process frequency command} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::every $process $frequency $command]
+ }
+ variable processTable
+ set mnow [clock_step [current_time]]
+ set frequency [expr {$frequency*1000}]
+ ::cron::task set $process \
+ frequency $frequency \
+ command $command \
+ scheduled [expr {$mnow + $frequency}]
+ ::cron::wake NEW
+}
+
+
+proc ::cron::object_coroutine {objname coroutine {info {}}} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::object_coroutine $objname $coroutine $info]
+ }
+ task set $coroutine \
+ {*}$info \
+ object $objname \
+ coroutine $coroutine
+
+ return $coroutine
+}
+
+# Notification that an object has been destroyed, and that
+# it should give up any toys associated with events
+proc ::cron::object_destroy {objname} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::object_destroy $objname]
+ }
+ variable processTable
+ set dat [array get processTable]
+ foreach {process info} $dat {
+ if {[dict exists $info object] && [dict get $info object] eq $objname} {
+ unset -nocomplain processTable($process)
+ }
+ }
+}
+
+###
+# topic: 97015814408714af539f35856f85bce6
+###
+proc ::cron::run process {
+ variable processTable
+ set mnow [clock_step [current_time]]
+ if {[dict exists processTable($process) scheduled] && [dict exists processTable($process) scheduled]>0} {
+ dict set processTable($process) scheduled [expr {$mnow-1000}]
+ } else {
+ dict set processTable($process) lastrun 0
+ }
+ ::cron::wake PROCESS
+}
+
+proc ::cron::clock_step timecode {
+ return [expr {$timecode-($timecode%1000)}]
+}
+
+proc ::cron::clock_delay {delay} {
+ set now [current_time]
+ set then [clock_step [expr {$delay+$now}]]
+ return [expr {$then-$now}]
+}
+
+# Sleep for X seconds, wake up at the top
+proc ::cron::clock_sleep {{sec 1} {offset 0}} {
+ set now [current_time]
+ set delay [expr {[clock_delay [expr {$sec*1000}]]+$offset}]
+ sleep $delay
+}
+
+proc ::cron::current_time {} {
+ if {$::cron::time < 0} {
+ return [clock milliseconds]
+ }
+ return $::cron::time
+}
+
+proc ::cron::clock_set newtime {
+ variable time
+ for {} {$time < $newtime} {incr time 100} {
+ uplevel #0 {::cron::do_one_event CLOCK_ADVANCE}
+ }
+ set time $newtime
+ uplevel #0 {::cron::do_one_event CLOCK_ADVANCE}
+}
+
+proc ::cron::once_in_a_while body {
+ set script {set _eventid_ $::cron::current_event}
+ append script $body
+ # Add a safety to allow this while to only execute once per call
+ append script {if {$_eventid_==$::cron::current_event} yield}
+ uplevel 1 [list while 1 $script]
+}
+
+proc ::cron::sleep ms {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::sleep $ms [info coroutine]]
+ }
+
+ set coro [info coroutine]
+ # When the clock is being externally
+ # controlled, advance the clock when
+ # a sleep is called
+ variable time
+ if {$time >= 0 && $coro eq {}} {
+ ::cron::clock_set [expr {$time+$ms}]
+ return
+ }
+ if {$coro ne {}} {
+ set mnow [current_time]
+ set start $mnow
+ set end [expr {$start+$ms}]
+ set eventid $coro
+ if {$::cron::trace} {
+ puts "::cron::sleep $ms $coro"
+ }
+ # Mark as running
+ task set $eventid scheduled $end coroutine $coro running 1
+ ::cron::wake WAKE_IN_CORO
+ yield 2
+ while {$end >= $mnow} {
+ if {$::cron::trace} {
+ puts "::cron::sleep $ms $coro (loop)"
+ }
+ set mnow [current_time]
+ yield 2
+ }
+ # Mark as not running to resume idle computation
+ task set $eventid running 0
+ if {$::cron::trace} {
+ puts "/::cron::sleep $ms $coro"
+ }
+ } else {
+ set eventid [incr ::cron::eventcount]
+ set var ::cron::event_#$eventid
+ set $var 0
+ if {$::cron::trace} {
+ puts "::cron::sleep $ms $eventid waiting for $var"
+ ::after $ms "set $var 1 ; puts \"::cron::sleep - $eventid - FIRED\""
+ } else {
+ ::after $ms "set $var 1"
+ }
+ ::vwait $var
+ if {$::cron::trace} {
+ puts "/::cron::sleep $ms $eventid"
+ }
+ unset $var
+ }
+}
+
+###
+# topic: 21de7bb8db019f3a2fd5a6ae9b38fd55
+# description:
+# Called once per second, and timed to ensure
+# we run in roughly realtime
+###
+proc ::cron::runTasksCoro {} {
+ ###
+ # Do this forever
+ ###
+ variable processTable
+ variable processing
+ variable all_coroutines
+ variable coroutine_object
+ variable coroutine_busy
+ variable nextevent
+ variable current_event
+
+ while 1 {
+ incr current_event
+ set lastevent 0
+ set now [current_time]
+ # Wake me up in 5 minute intervals, just out of principle
+ set nextevent [expr {$now-($now % 300000) + 300000}]
+ set next_idle_event [expr {$now+250}]
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER nextevent $nextevent]
+ }
+ ###
+ # Determine what tasks to run this timestep
+ ###
+ set tasks {}
+ set cancellist {}
+ set nexttask {}
+
+ foreach {process} [lsort -dictionary [array names processTable]] {
+ dict with processTable($process) {
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER process $process frequency: $frequency scheduled: $scheduled]
+ }
+ if {$scheduled==0 && $frequency==0} {
+ set lastrun $now
+ set lastevent $now
+ lappend tasks $process
+ } else {
+ if { $scheduled <= $now } {
+ lappend tasks $process
+ if { $frequency < 0 } {
+ lappend cancellist $process
+ } elseif {$frequency==0} {
+ set scheduled 0
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER process $process demoted to idle]
+ }
+ } else {
+ set scheduled [clock_step [expr {$frequency+$lastrun}]]
+ if { $scheduled <= $now } {
+ set scheduled [clock_step [expr {$frequency+$now}]]
+ }
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER process $process rescheduled to $scheduled]
+ }
+ }
+ set lastrun $now
+ }
+ set lastevent $now
+ }
+ }
+ }
+ foreach task $tasks {
+ dict set processTable($task) lastrun $now
+ if {[dict exists processTable($task) foreground] && [dict set processTable($task) foreground]} continue
+ if {[dict exists processTable($task) running] && [dict set processTable($task) running]} continue
+ if {$::cron::trace > 2} {
+ puts [list RUNNING $task [task info $task]]
+ }
+ if {[dict exists $processTable($task) coroutine]} {
+ set coro [dict get $processTable($task) coroutine]
+ } else {
+ set coro {}
+ }
+ dict set processTable($task) running 1
+ if {[dict exists $processTable($task) command]} {
+ set command [dict get $processTable($task) command]
+ } else {
+ set command {}
+ }
+ if {$command eq {} && $coro eq {}} {
+ # Task has nothing to do. Slot it for destruction
+ lappend cancellist $task
+ } elseif {$coro ne {}} {
+ if {[info command $coro] eq {}} {
+ set object [dict get $processTable($task) object]
+ # Trigger coroutine again if a command was given
+ # If this coroutine is associated with an object, ensure
+ # the object still exists before invoking its method
+ if {$command eq {} || ($object ne {} && [info command $object] eq {})} {
+ lappend cancellist $task
+ dict set processTable($task) running 0
+ continue
+ }
+ if {$::cron::trace} {
+ puts [list RESTARTING $task - coroutine $coro - with $command]
+ }
+ ::coroutine $coro {*}$command
+ }
+ try $coro on return {} {
+ # Terminate the coroutine
+ lappend cancellist $task
+ } on break {} {
+ # Terminate the coroutine
+ lappend cancellist $task
+ } on error {errtxt errdat} {
+ # Coroutine encountered an error
+ lappend cancellist $task
+ puts "ERROR $coro"
+ set errorinfo [dict get $errdat -errorinfo]
+ if {[info exists coroutine_object($coro)] && $coroutine_object($coro) ne {}} {
+ catch {
+ puts "OBJECT: $coroutine_object($coro)"
+ puts "CLASS: [info object class $coroutine_object($coro)]"
+ }
+ }
+ puts "$errtxt"
+ puts ***
+ puts $errorinfo
+ } on continue {result opts} {
+ # Ignore continue
+ if { $result eq "done" } {
+ lappend cancellist $task
+ }
+ } on ok {result opts} {
+ if { $result eq "done" } {
+ lappend cancellist $task
+ }
+ }
+ } else {
+ dict with processTable($task) {
+ set err [catch {uplevel #0 $command} result errdat]
+ if $err {
+ puts "CRON TASK FAILURE:"
+ puts "PROCESS: $task"
+ puts $result
+ puts ***
+ puts [dict get $errdat -errorinfo]
+ }
+ }
+ yield 0
+ }
+ dict set processTable($task) running 0
+ }
+ foreach {task} $cancellist {
+ unset -nocomplain processTable($task)
+ }
+ foreach {process} [lsort -dictionary [array names processTable]] {
+ set scheduled 0
+ set frequency 0
+ dict with processTable($process) {
+ if {$scheduled==0 && $frequency==0} {
+ if {$next_idle_event < $nextevent} {
+ set nexttask $task
+ set nextevent $next_idle_event
+ }
+ } elseif {$scheduled < $nextevent} {
+ set nexttask $process
+ set nextevent $scheduled
+ }
+ set lastevent $now
+ }
+ }
+ foreach {eventid msec} [array get ::cron::coro_sleep] {
+ if {$msec < 0} continue
+ if {$msec<$nextevent} {
+ set nexttask "CORO $eventid"
+ set nextevent $scheduled
+ }
+ }
+ set delay [expr {$nextevent-$now}]
+ if {$delay <= 0} {
+ yield 0
+ } else {
+ if {$::cron::trace > 1} {
+ puts "NEXT EVENT $delay - NEXT TASK $nexttask"
+ }
+ yield $delay
+ }
+ }
+}
+
+proc ::cron::wake {{who ???}} {
+ ##
+ # Only triggered by cron jobs kicking off other cron jobs within
+ # the script body
+ ##
+ if {$::cron::trace} {
+ puts "::cron::wake $who"
+ }
+ if {$::cron::busy} {
+ return
+ }
+ after cancel $::cron::next_event
+ set ::cron::next_event [after idle [list ::cron::do_one_event $who]]
+}
+
+proc ::cron::do_one_event {{who ???}} {
+ if {$::cron::trace} {
+ puts "::cron::do_one_event $who"
+ }
+ after cancel $::cron::next_event
+ set now [current_time]
+ set ::cron::busy 1
+ while {$::cron::busy} {
+ if {[info command ::cron::COROUTINE] eq {}} {
+ ::coroutine ::cron::COROUTINE ::cron::runTasksCoro
+ }
+ set cron_delay [::cron::COROUTINE]
+ if {$cron_delay==0} {
+ if {[incr loops]>10} {
+ if {$::cron::trace} {
+ puts "Breaking out of 10 recursive loops"
+ }
+ set ::cron::wake_time 1000
+ break
+ }
+ set ::cron::wake_time 0
+ incr ::cron::loops(active)
+ } else {
+ set ::cron::busy 0
+ incr ::cron::loops(idle)
+ }
+ }
+ ###
+ # Try to get the event to fire off on the border of the
+ # nearest second
+ ###
+ if {$cron_delay < 10} {
+ set cron_delay 250
+ }
+ set ctime [current_time]
+ set next [expr {$ctime+$cron_delay}]
+ set ::cron::wake_time [expr {$next/1000}]
+ if {$::cron::trace} {
+ puts [list EVENT LOOP WILL WAKE IN $cron_delay ms next: [clock format $::cron::wake_time -format "%H:%M:%S"] active: $::cron::loops(active) idle: $::cron::loops(idle) woken_by: $who]
+ }
+ set ::cron::next_event [after $cron_delay {::cron::do_one_event TIMER}]
+}
+
+
+proc ::cron::main {} {
+ # Never launch from a coroutine
+ if {[info coroutine] ne {}} {
+ return
+ }
+ set ::cron::forever 1
+ while {$::cron::forever} {
+ ::after 120000 {set ::cron::forever 1}
+ # Call an update just to give the rest of the event loop a chance
+ incr ::cron::loops(main)
+ ::after cancel $::cron::next_event
+ set ::cron::next_event [::after idle {::cron::wake MAIN}]
+ set ::cron::forever 1
+ set ::cron::busy 0
+ ::vwait ::cron::forever
+ if {$::cron::trace} {
+ puts "MAIN LOOP CYCLE $::cron::loops(main)"
+ }
+ }
+}
+
+###
+# topic: 4a891d0caabc6e25fbec9514ea8104dd
+# description:
+# This file implements a process table
+# Instead of having individual components try to maintain their own timers
+# we centrally manage how often tasks should be kicked off here.
+###
+namespace eval ::cron {
+ variable lastcall 0
+ variable processTable
+ variable busy 0
+ variable next_event {}
+ variable trace 0
+ variable current_event
+ variable time -1
+ if {![info exists current_event]} {
+ set current_event 0
+ }
+ if {![info exists ::cron::loops]} {
+ array set ::cron::loops {
+ active 0
+ main 0
+ idle 0
+ wake 0
+ }
+ }
+}
+
+::cron::wake STARTUP
+
ADDED modules/tool/build/dialect.tcl
Index: modules/tool/build/dialect.tcl
==================================================================
--- /dev/null
+++ modules/tool/build/dialect.tcl
@@ -0,0 +1,264 @@
+###
+# oodialect.tcl
+#
+# Copyright (c) 2015-2018 Sean Woods
+# Copyright (c) 2015 Donald K Fellows
+#
+# BSD License
+###
+# @@ Meta Begin
+# Package oo::dialect 0.3.4
+# Meta platform tcl
+# Meta summary A utility for defining a domain specific language for TclOO systems
+# Meta description This package allows developers to generate
+# Meta description domain specific languages to describe TclOO
+# Meta description classes and objects.
+# Meta category TclOO
+# Meta subject oodialect
+# Meta require {Tcl 8.6}
+# Meta author Sean Woods
+# Meta author Donald K. Fellows
+# Meta license BSD
+# @@ Meta End
+namespace eval ::oo::dialect {
+ namespace export create
+}
+
+# A stack of class names
+proc ::oo::dialect::Push {class} {
+ ::variable class_stack
+ lappend class_stack $class
+}
+proc ::oo::dialect::Peek {} {
+ ::variable class_stack
+ return [lindex $class_stack end]
+}
+proc ::oo::dialect::Pop {} {
+ ::variable class_stack
+ set class_stack [lrange $class_stack 0 end-1]
+}
+
+###
+# This proc will generate a namespace, a "mother of all classes", and a
+# rudimentary set of policies for this dialect.
+###
+proc ::oo::dialect::create {name {parent ""}} {
+ set NSPACE [NSNormalize [uplevel 1 {namespace current}] $name]
+ ::namespace eval $NSPACE {::namespace eval define {}}
+ ###
+ # Build the "define" namespace
+ ###
+ if {$parent eq ""} {
+ ###
+ # With no "parent" language, begin with all of the keywords in
+ # oo::define
+ ###
+ foreach command [info commands ::oo::define::*] {
+ set procname [namespace tail $command]
+ interp alias {} ${NSPACE}::define::$procname {} \
+ ::oo::dialect::DefineThunk $procname
+ }
+ # Create an empty dynamic_methods proc
+ proc ${NSPACE}::dynamic_methods {class} {}
+ namespace eval $NSPACE {
+ ::namespace export dynamic_methods
+ ::namespace eval define {::namespace export *}
+ }
+ set ANCESTORS {}
+ } else {
+ ###
+ # If we have a parent language, that language already has the
+ # [oo::define] keywords as well as additional keywords and behaviors.
+ # We should begin with that
+ ###
+ set pnspace [NSNormalize [uplevel 1 {namespace current}] $parent]
+ apply [list parent {
+ ::namespace export dynamic_methods
+ ::namespace import -force ${parent}::dynamic_methods
+ } $NSPACE] $pnspace
+
+ apply [list parent {
+ ::namespace import -force ${parent}::define::*
+ ::namespace export *
+ } ${NSPACE}::define] $pnspace
+ set ANCESTORS [list ${pnspace}::object]
+ }
+ ###
+ # Build our dialect template functions
+ ###
+ proc ${NSPACE}::define {oclass args} [string map [list %NSPACE% $NSPACE] {
+ ###
+ # To facilitate library reloading, allow
+ # a dialect to create a class from DEFINE
+ ###
+ set class [::oo::dialect::NSNormalize [uplevel 1 {namespace current}] $oclass]
+ if {[info commands $class] eq {}} {
+ %NSPACE%::class create $class {*}${args}
+ } else {
+ ::oo::dialect::Define %NSPACE% $class {*}${args}
+ }
+}]
+ interp alias {} ${NSPACE}::define::current_class {} \
+ ::oo::dialect::Peek
+ interp alias {} ${NSPACE}::define::aliases {} \
+ ::oo::dialect::Aliases $NSPACE
+ interp alias {} ${NSPACE}::define::superclass {} \
+ ::oo::dialect::SuperClass $NSPACE
+
+ if {[info command ${NSPACE}::class] ne {}} {
+ ::rename ${NSPACE}::class {}
+ }
+ ###
+ # Build the metaclass for our language
+ ###
+ ::oo::class create ${NSPACE}::class {
+ superclass ::oo::dialect::MotherOfAllMetaClasses
+ }
+ # Wire up the create method to add in the extra argument we need; the
+ # MotherOfAllMetaClasses will know what to do with it.
+ ::oo::objdefine ${NSPACE}::class \
+ method create {name {definitionScript ""}} \
+ "next \$name [list ${NSPACE}::define] \$definitionScript"
+
+ ###
+ # Build the mother of all classes. Note that $ANCESTORS is already
+ # guaranteed to be a list in canonical form.
+ ###
+ uplevel #0 [string map [list %NSPACE% [list $NSPACE] %name% [list $name] %ANCESTORS% $ANCESTORS] {
+ %NSPACE%::class create %NSPACE%::object {
+ superclass %ANCESTORS%
+ # Put MOACish stuff in here
+ }
+ }]
+ if { "${NSPACE}::class" ni $::oo::dialect::core_classes } {
+ lappend ::oo::dialect::core_classes "${NSPACE}::class"
+ }
+ if { "${NSPACE}::object" ni $::oo::dialect::core_classes } {
+ lappend ::oo::dialect::core_classes "${NSPACE}::object"
+ }
+}
+
+# Support commands; not intended to be called directly.
+proc ::oo::dialect::NSNormalize {namespace qualname} {
+ if {![string match ::* $qualname]} {
+ set qualname ${namespace}::$qualname
+ }
+ regsub -all {::+} $qualname "::"
+}
+
+proc ::oo::dialect::DefineThunk {target args} {
+ tailcall ::oo::define [Peek] $target {*}$args
+}
+
+proc ::oo::dialect::Canonical {namespace NSpace class} {
+ namespace upvar $namespace cname cname
+ #if {[string match ::* $class]} {
+ # return $class
+ #}
+ if {[info exists cname($class)]} {
+ return $cname($class)
+ }
+ if {[info exists ::oo::dialect::cname($class)]} {
+ return $::oo::dialect::cname($class)
+ }
+ if {[info exists ::oo::dialect::cname(${NSpace}::${class})]} {
+ return $::oo::dialect::cname(${NSpace}::${class})
+ }
+ foreach item [list "${NSpace}::$class" "::$class"] {
+ if {[info commands $item] ne {}} {
+ return $item
+ }
+ }
+ return ${NSpace}::$class
+}
+
+###
+# Implementation of the languages' define command
+###
+proc ::oo::dialect::Define {namespace class args} {
+ Push $class
+ try {
+ if {[llength $args]==1} {
+ namespace eval ${namespace}::define [lindex $args 0]
+ } else {
+ ${namespace}::define::[lindex $args 0] {*}[lrange $args 1 end]
+ }
+ ${namespace}::dynamic_methods $class
+ } finally {
+ Pop
+ }
+}
+
+###
+# Implementation of how we specify the other names that this class will answer
+# to
+###
+
+proc ::oo::dialect::Aliases {namespace args} {
+ set class [Peek]
+ namespace upvar $namespace cname cname
+ set NSpace [join [lrange [split $class ::] 1 end-2] ::]
+ set cname($class) $class
+ foreach name $args {
+ set cname($name) $class
+ #set alias $name
+ set alias [NSNormalize $NSpace $name]
+ # Add a local metaclass reference
+ if {![info exists ::oo::dialect::cname($alias)]} {
+ lappend ::oo::dialect::aliases($class) $alias
+ ##
+ # Add a global reference, first come, first served
+ ##
+ set ::oo::dialect::cname($alias) $class
+ }
+ }
+}
+
+###
+# Implementation of a superclass keyword which will enforce the inheritance of
+# our language's mother of all classes
+###
+
+proc ::oo::dialect::SuperClass {namespace args} {
+ set class [Peek]
+ namespace upvar $namespace class_info class_info
+ dict set class_info($class) superclass 1
+ set ::oo::dialect::cname($class) $class
+ set NSpace [join [lrange [split $class ::] 1 end-2] ::]
+ set unique {}
+ foreach item $args {
+ set Item [Canonical $namespace $NSpace $item]
+ dict set unique $Item $item
+ }
+ set root ${namespace}::object
+ if {$class ne $root} {
+ dict set unique $root $root
+ }
+ tailcall ::oo::define $class superclass {*}[dict keys $unique]
+}
+
+###
+# Implementation of the common portions of the the metaclass for our
+# languages.
+###
+
+if {[info commands ::oo::dialect::MotherOfAllMetaClasses] eq {}} {
+ ::oo::class create ::oo::dialect::MotherOfAllMetaClasses {
+ superclass ::oo::class
+ constructor {define definitionScript} {
+ $define [self] {
+ superclass
+ }
+ $define [self] $definitionScript
+ }
+ method aliases {} {
+ if {[info exists ::oo::dialect::aliases([self])]} {
+ return $::oo::dialect::aliases([self])
+ }
+ }
+ }
+}
+
+namespace eval ::oo::dialect {
+ variable core_classes {::oo::class ::oo::object}
+}
ADDED modules/tool/build/dicttool.tcl
Index: modules/tool/build/dicttool.tcl
==================================================================
--- /dev/null
+++ modules/tool/build/dicttool.tcl
@@ -0,0 +1,153 @@
+###
+# This package enhances the stock dict implementation with some
+# creature comforts
+###
+if {[info commands ::ladd] eq {}} {
+ proc ladd {varname args} {
+ upvar 1 $varname var
+ if ![info exists var] {
+ set var {}
+ }
+ foreach item $args {
+ if {$item in $var} continue
+ lappend var $item
+ }
+ return $var
+ }
+}
+
+if {[info command ::ldelete] eq {}} {
+ proc ::ldelete {varname args} {
+ upvar 1 $varname var
+ if ![info exists var] {
+ return
+ }
+ foreach item [lsort -unique $args] {
+ while {[set i [lsearch $var $item]]>=0} {
+ set var [lreplace $var $i $i]
+ }
+ }
+ return $var
+ }
+}
+
+if {[::info commands ::tcl::dict::getnull] eq {}} {
+ proc ::tcl::dict::getnull {dictionary args} {
+ if {[exists $dictionary {*}$args]} {
+ get $dictionary {*}$args
+ }
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] getnull ::tcl::dict::getnull]
+}
+if {[::info commands ::tcl::dict::print] eq {}} {
+ ###
+ # Test if element is a dict
+ ###
+ proc ::tcl::dict::_putb {buffervar indent field value} {
+ ::upvar 1 $buffervar buffer
+ ::append buffer \n [::string repeat " " $indent] [::list $field] " "
+ if {[string index $field end] eq "/"} {
+ ::incr indent 2
+ ::append buffer "\{"
+ foreach item $value {
+ if [catch {
+ if {![is_dict $item]} {
+ ::append buffer \n [::string repeat " " $indent] [list $item]
+ } else {
+ ::append buffer \n "[::string repeat " " $indent]\{"
+ ::incr indent 2
+ foreach {sf sv} $item {
+ _putb buffer $indent $sf $sv
+ }
+ ::incr indent -2
+ ::append buffer \n "[::string repeat " " $indent]\}"
+ }
+ } err] {
+ puts [list FAILED $indent $field $item]
+ puts $err
+ puts "$::errorInfo"
+ }
+ }
+ ::incr indent -2
+ ::append buffer \n [::string repeat " " $indent] "\}"
+ } elseif {[string index $field end] eq ":" || ![is_dict $value]} {
+ ::append buffer [::list $value]
+ } else {
+ ::incr indent 2
+ ::append buffer "\{"
+ foreach {f v} $value {
+ _putb buffer $indent $f $v
+ }
+ ::incr indent -2
+ ::append buffer \n [::string repeat " " $indent] "\}"
+ }
+ }
+ proc ::tcl::dict::print dict {
+ ::set buffer {}
+ ::foreach {field value} $dict {
+ _putb buffer 0 $field $value
+ }
+ return $buffer
+ }
+
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] print ::tcl::dict::print]
+}
+if {[::info commands ::tcl::dict::is_dict] eq {}} {
+ ###
+ # Test if element is a dict
+ ###
+ proc ::tcl::dict::is_dict { d } {
+ # is it a dict, or can it be treated like one?
+ if {[catch {dict size $d} err]} {
+ #::set ::errorInfo {}
+ return 0
+ }
+ return 1
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] is_dict ::tcl::dict::is_dict]
+}
+if {[::info commands ::tcl::dict::rmerge] eq {}} {
+ ###
+ # title: A recursive form of dict merge
+ # description:
+ # A routine to recursively dig through dicts and merge
+ # adapted from http://stevehavelka.com/tcl-dict-operation-nested-merge/
+ ###
+ proc ::tcl::dict::rmerge {a args} {
+ ::set result $a
+ # Merge b into a, and handle nested dicts appropriately
+ ::foreach b $args {
+ for { k v } $b {
+ if {[string index $k end] eq ":"} {
+ # Element names that end in ":" are assumed to be literals
+ set result $k $v
+ } elseif { [dict exists $result $k] } {
+ # key exists in a and b? let's see if both values are dicts
+ # both are dicts, so merge the dicts
+ if { [is_dict [get $result $k]] && [is_dict $v] } {
+ set result $k [rmerge [get $result $k] $v]
+ } else {
+ set result $k $v
+ }
+ } else {
+ set result $k $v
+ }
+ }
+ }
+ return $result
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] rmerge ::tcl::dict::rmerge]
+}
+
+if {[::info commands ::tcl::dict::isnull] eq {}} {
+ proc ::tcl::dict::isnull {dictionary args} {
+ if {![exists $dictionary {*}$args]} {return 1}
+ return [expr {[get $dictionary {*}$args] in {{} NULL null}}]
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] isnull ::tcl::dict::isnull]
+}
Index: modules/tool/build/metaclass.tcl
==================================================================
--- modules/tool/build/metaclass.tcl
+++ modules/tool/build/metaclass.tcl
@@ -1,7 +1,7 @@
#-------------------------------------------------------------------------
-# TITLE:
+# TITLE:
# tool.tcl
#
# PROJECT:
# tool: TclOO Helper Library
#
@@ -323,33 +323,33 @@
variable DestroyEvent 0
constructor args {
my Config_merge [::tool::args_to_options {*}$args]
}
-
+
destructor {}
-
+
method ancestors {{reverse 0}} {
set result [::oo::meta::ancestors [info object class [self]]]
if {$reverse} {
return [lreverse $result]
}
return $result
}
-
+
method DestroyEvent {} {
my variable DestroyEvent
return $DestroyEvent
}
-
+
###
# title: Forward a method
###
method forward {method args} {
oo::objdefine [self] forward $method {*}$args
}
-
+
###
# title: Direct a series of sub-functions to a seperate object
###
method graft args {
my variable organs
@@ -366,14 +366,14 @@
oo::objdefine [self] forward <${stub}> $object
oo::objdefine [self] export <${stub}>
}
return $object
}
-
+
# Called after all options and public variables are initialized
method initialize {} {}
-
+
###
# topic: 3c4893b65a1c79b2549b9ee88f23c9e3
# description:
# Provide a default value for all options and
# publically declared variables, and locks the
@@ -391,20 +391,26 @@
# while the contructor is still running.
# Note, by default an odie object will ignore
# signals until a later call to my lock remove pipeline
###
method InitializePublic {} {
- my variable config meta
+ my variable config meta mixinmap mixins
+ if {![info exists mixins]} {
+ set mixins {}
+ }
+ if {![info exists mixinmap]} {
+ set mixinmap {}
+ }
if {![info exists meta]} {
set meta {}
}
if {![info exists config]} {
set config {}
}
my ClassPublicApply {}
}
-
+
class_method info {which} {
my variable cache
if {![info exists cache($which)]} {
set cache($which) {}
switch $which {
@@ -430,19 +436,19 @@
}
}
}
return $cache($which)
}
-
+
###
# Incorporate the class's variables, arrays, and options
###
method ClassPublicApply class {
my variable config
set integrate 0
if {$class eq {}} {
- set class [info object class [self]]
+ set class [info object class [self]]
} else {
set integrate 1
}
set public [$class info public]
foreach {var value} [dict getnull $public variable] {
@@ -492,11 +498,11 @@
if {$script ne {}} {
{*}[string map [list %field% [list $field] %value% [list $val] %self% [namespace which my]] $script]
}
}
}
-
+
###
# topic: 3c4893b65a1c79b2549b9ee88f23c9e3
# description:
# Provide a default value for all options and
# publically declared variables, and locks the
@@ -526,22 +532,22 @@
my meta mixin $class
}
my ClassPublicApply $class
}
foreach class $prior {
- if {$class ni $mixins } {
+ if {$class ni $mixins } {
my meta mixout $class
}
}
}
- method mixinmap args {
+ method mixinmap args {
my variable mixinmap
set priorlist {}
foreach {slot classes} $args {
if {[dict exists $mixinmap $slot]} {
- lappend priorlist {*}[dict get $mixinmap $slot]
+ lappend priorlist {*}[dict get $mixinmap $slot]
foreach class [dict get $mixinmap $slot] {
if {$class ni $classes && [$class meta exists mixin unmap-script:]} {
if {[catch [$class meta get mixin unmap-script:] err errdat]} {
puts stderr "[self] MIXIN ERROR POPPING $class:\n[dict get $errdat -errorinfo]"
}
@@ -584,11 +590,11 @@
lappend classlist $class
}
}
my mixin {*}$classlist
}
-
+
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 {}} {
@@ -612,11 +618,11 @@
method Morph_leave {} {}
###
# Commands to perform as this object transitions into this class as a new class
###
method Morph_enter {} {}
-
+
###
# title: List which objects are forwarded as organs
###
method organ {{stub all}} {
my variable organs
ADDED modules/tool/build/oometa.tcl
Index: modules/tool/build/oometa.tcl
==================================================================
--- /dev/null
+++ modules/tool/build/oometa.tcl
@@ -0,0 +1,493 @@
+
+namespace eval ::oo::meta {
+ set dirty_classes {}
+}
+
+proc ::oo::meta::args_to_dict args {
+ if {[llength $args]==1} {
+ return [lindex $args 0]
+ }
+ return $args
+}
+
+proc ::oo::meta::args_to_options args {
+ set result {}
+ foreach {var val} [args_to_dict {*}$args] {
+ lappend result [string trimleft $var -] $val
+ }
+ return $result
+}
+
+proc ::oo::meta::ancestors class {
+ set class [::oo::meta::normalize $class]
+ set core_result {}
+ set queue $class
+ set result {}
+ # Rig things such that that the top superclasses
+ # are evaluated first
+ while {[llength $queue]} {
+ set tqueue $queue
+ set queue {}
+ foreach qclass $tqueue {
+ if {$qclass in $::oo::dialect::core_classes} {
+ if {$qclass ni $core_result} {
+ lappend core_result $qclass
+ }
+ continue
+ }
+ foreach aclass [::info class superclasses $qclass] {
+ if { $aclass in $result } continue
+ if { $aclass in $core_result } continue
+ if { $aclass in $queue } continue
+ lappend queue $aclass
+ }
+ }
+ foreach item $tqueue {
+ if {$item in $core_result} continue
+ if { $item ni $result } {
+ set result [linsert $result 0 $item]
+ }
+ }
+ }
+ # Handle core classes last
+ set queue $core_result
+ while {[llength $queue]} {
+ set tqueue $queue
+ set queue {}
+ foreach qclass $tqueue {
+ foreach aclass [::info class superclasses $qclass] {
+ if { $aclass in $result } continue
+ if { $aclass in $queue } continue
+ lappend queue $aclass
+ }
+ }
+ foreach item $tqueue {
+ if { $item ni $result } {
+ set result [linsert $result 0 $item]
+ }
+ }
+ }
+ return $result
+}
+
+proc oo::meta::info {class submethod args} {
+ set class [::oo::meta::normalize $class]
+ switch $submethod {
+ cget {
+ ###
+ # submethod: cget
+ # arguments: ?*path* ...? *field*
+ # format: markdown
+ # description:
+ # Retrieve a value from the class' meta data. Values are searched in the
+ # following order:
+ # 1. From class meta data as const **path** **field:**
+ # 2. From class meta data as const **path** **field**
+ # 3. From class meta data as **path** **field:**
+ # 4. From class meta data as **path** **field**
+ ###
+ set path [lrange $args 0 end-1]
+ set field [string trimright [lindex $args end] :]
+ foreach mclass [lreverse [::oo::meta::ancestors $class]] {
+ if {![::info exists ::oo::meta::local_property($mclass)]} continue
+ set class_metadata $::oo::meta::local_property($mclass)
+ if {[dict exists $class_metadata const {*}$path $field:]} {
+ return [dict get $class_metadata const {*}$path $field:]
+ }
+ if {[dict exists $class_metadata const {*}$path $field]} {
+ return [dict get $class_metadata const {*}$path $field]
+ }
+ if {[dict exists $class_metadata {*}$path $field:]} {
+ return [dict get $class_metadata {*}$path $field:]
+ }
+ if {[dict exists $class_metadata {*}$path $field]} {
+ return [dict get $class_metadata {*}$path $field]
+ }
+ }
+ return {}
+ }
+ rebuild {
+ ::oo::meta::rebuild $class
+ }
+ is {
+ set info [metadata $class]
+ return [string is [lindex $args 0] -strict [dict getnull $info {*}[lrange $args 1 end]]]
+ }
+ for -
+ map {
+ set info [metadata $class]
+ uplevel 1 [list ::dict $submethod [lindex $args 0] [dict get $info {*}[lrange $args 1 end-1]] [lindex $args end]]
+ }
+ with {
+ upvar 1 TEMPVAR info
+ set info [metadata $class]
+ return [uplevel 1 [list ::dict with TEMPVAR {*}$args]]
+ }
+ branchget {
+ set info [metadata $class]
+ set result {}
+ foreach {field value} [dict getnull $info {*}$args] {
+ dict set result [string trimright $field :] $value
+ }
+ return $result
+ }
+ 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
+ }
+ append -
+ incr -
+ lappend -
+ set -
+ unset -
+ update {
+ ::oo::meta::rebuild $class
+ ::dict $submethod ::oo::meta::local_property($class) {*}$args
+ }
+ merge {
+ ::oo::meta::rebuild $class
+ set ::oo::meta::local_property($class) [dict rmerge $::oo::meta::local_property($class) {*}$args]
+ }
+ dump {
+ set info [metadata $class]
+ return $info
+ }
+ default {
+ set info [metadata $class]
+ return [::dict $submethod $info {*}$args]
+ }
+ }
+}
+
+proc ::oo::meta::localdata {class args} {
+ if {![::info exists ::oo::meta::local_property($class)]} {
+ return {}
+ }
+ if {[::llength $args]==0} {
+ return $::oo::meta::local_property($class)
+ }
+ return [::dict getnull $::oo::meta::local_property($class) {*}$args]
+}
+
+proc ::oo::meta::normalize class {
+ set class ::[string trimleft $class :]
+}
+
+proc ::oo::meta::metadata {class {force 0}} {
+ set class [::oo::meta::normalize $class]
+ ###
+ # Destroy the cache of all derivitive classes
+ ###
+ if {$force} {
+ unset -nocomplain ::oo::meta::cached_property
+ unset -nocomplain ::oo::meta::cached_hierarchy
+ } else {
+ variable dirty_classes
+ 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::dialect::core_classes} {
+ lappend ::oo::dialect::core_classes $dclass
+ }
+ }
+ }
+ set dirty_classes {}
+ }
+
+ ###
+ # If the cache is available, use it
+ ###
+ variable cached_property
+ if {[::info exists cached_property($class)]} {
+ return $cached_property($class)
+ }
+ ###
+ # Build a cache of the hierarchy and the
+ # aggregate metadata for this class and store
+ # them for future use
+ ###
+ variable cached_hierarchy
+ set metadata {}
+ set stack {}
+ variable local_property
+ set cached_hierarchy($class) [::oo::meta::ancestors $class]
+ foreach class $cached_hierarchy($class) {
+ if {[::info exists local_property($class)]} {
+ lappend metadata $local_property($class)
+ }
+ }
+ #foreach aclass [lreverse [::info class superclasses $class]] {
+ # lappend metadata [::oo::meta::metadata $aclass]
+ #}
+
+ lappend metadata {classinfo {type: {}}}
+ if {[::info exists local_property($class)]} {
+ lappend metadata $local_property($class)
+ }
+ set metadata [dict rmerge {*}$metadata]
+ set cached_property($class) $metadata
+ return $metadata
+}
+
+proc ::oo::meta::rebuild args {
+ foreach class $args {
+ if {$class ni $::oo::meta::dirty_classes} {
+ lappend ::oo::meta::dirty_classes $class
+ }
+ }
+}
+
+proc ::oo::meta::search args {
+ variable local_property
+
+ set path [lrange $args 0 end-1]
+ set value [lindex $args end]
+
+ set result {}
+ foreach {class info} [array get local_property] {
+ if {[dict exists $info {*}$path:]} {
+ if {[string match [dict get $info {*}$path:] $value]} {
+ lappend result $class
+ }
+ continue
+ }
+ if {[dict exists $info {*}$path]} {
+ if {[string match [dict get $info {*}$path] $value]} {
+ lappend result $class
+ }
+ }
+ }
+ return $result
+}
+
+proc ::oo::define::meta {args} {
+ set class [lindex [::info level -1] 1]
+ if {[lindex $args 0] in "cget set branchset"} {
+ ::oo::meta::info $class {*}$args
+ } else {
+ ::oo::meta::info $class set {*}$args
+ }
+}
+
+oo::define oo::class {
+ method meta {submethod args} {
+ tailcall ::oo::meta::info [self] $submethod {*}$args
+ }
+}
+
+oo::define oo::object {
+ ###
+ # title: Provide access to meta data
+ # format: markdown
+ # description:
+ # The *meta* method allows an object access
+ # to a combination of its own meta data as
+ # well as to that of its class
+ ###
+ method meta {submethod args} {
+ my variable meta MetaMixin
+ if {![info exists MetaMixin]} {
+ set MetaMixin {}
+ }
+ set class [::info object class [self object]]
+ set classlist [list $class {*}$MetaMixin]
+ switch $submethod {
+ cget {
+ ###
+ # submethod: cget
+ # arguments: ?*path* ...? *field*
+ # format: markdown
+ # description:
+ # Retrieve a value from the local objects **meta** dict
+ # or from the class' meta data. Values are searched in the
+ # following order:
+ # 0. (If path length==1) From the _config array
+ # 1. From the local dict as **path** **field:**
+ # 2. From the local dict as **path** **field**
+ # 3. From class meta data as const **path** **field:**
+ # 4. From class meta data as const **path** **field**
+ # 5. From class meta data as **path** **field:**
+ # 6. From class meta data as **path** **field**
+ ###
+ set path [lrange $args 0 end-1]
+ set field [string trim [lindex $args end] :]
+ if {[dict exists $meta {*}$path $field:]} {
+ return [dict get $meta {*}$path $field:]
+ }
+ if {[dict exists $meta {*}$path $field]} {
+ return [dict get $meta {*}$path $field]
+ }
+ foreach mclass [lreverse $classlist] {
+ set class_metadata [::oo::meta::metadata $mclass]
+ if {[dict exists $class_metadata const {*}$path $field:]} {
+ return [dict get $class_metadata const {*}$path $field:]
+ }
+ if {[dict exists $class_metadata const {*}$path $field]} {
+ return [dict get $class_metadata const {*}$path $field]
+ }
+ if {[dict exists $class_metadata {*}$path $field:]} {
+ return [dict get $class_metadata {*}$path $field:]
+ }
+ if {[dict exists $class_metadata {*}$path $field]} {
+ return [dict get $class_metadata {*}$path $field]
+ }
+ }
+ return {}
+ }
+ is {
+ set value [my meta cget {*}[lrange $args 1 end]]
+ return [string is [lindex $args 0] -strict $value]
+ }
+ for -
+ map {
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ set info [dict rmerge {*}$mdata $meta]
+ uplevel 1 [list ::dict $submethod [lindex $args 0] [dict get $info {*}[lrange $args 1 end-1]] [lindex $args end]]
+ }
+ with {
+ upvar 1 TEMPVAR info
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ set info [dict rmerge {*}$mdata $meta]
+ return [uplevel 1 [list dict with TEMPVAR {*}$args]]
+ }
+ dump {
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ return [dict rmerge {*}$mdata $meta]
+ }
+ append -
+ incr -
+ lappend -
+ set -
+ unset -
+ update {
+ return [dict $submethod meta {*}$args]
+ }
+ branchset {
+ foreach {field value} [lindex $args end] {
+ dict set meta {*}[lrange $args 0 end-1] [string trimright $field :]: $value
+ }
+ }
+ rmerge -
+ merge {
+ set meta [dict rmerge $meta {*}$args]
+ return $meta
+ }
+ exists {
+ foreach mclass $classlist {
+ if {[dict exists [::oo::meta::metadata $mclass] {*}$args]} {
+ return 1
+ }
+ }
+ if {[dict exists $meta {*}$args]} {
+ return 1
+ }
+ return 0
+ }
+ get -
+ getnull {
+ if {[string index [lindex $args end] end]==":"} {
+ # Looking for a leaf node
+ if {[dict exists $meta {*}$args]} {
+ return [dict get $meta {*}$args]
+ }
+ foreach mclass [lreverse $classlist] {
+ set mdata [::oo::meta::metadata $mclass]
+ if {[dict exists $mdata {*}$args]} {
+ return [dict get $mdata {*}$args]
+ }
+ }
+ if {$submethod == "get"} {
+ error "key \"$args\" not known in metadata"
+ }
+ return {}
+ }
+ # Looking for a branch node
+ # So we need to composite the result
+ set found 0
+ foreach mclass $classlist {
+ set mdata [::oo::meta::metadata $mclass]
+ if {[dict exists $mdata {*}$args]} {
+ set found 1
+ lappend result [dict get $mdata {*}$args]
+ }
+ }
+ if {[dict exists $meta {*}$args]} {
+ set found 1
+ lappend result [dict get $meta {*}$args]
+ }
+ if {!$found} {
+ if {$submethod == "get"} {
+ error "key \"$args\" not known in metadata"
+ }
+ return {}
+ }
+ return [dict rmerge {*}$result]
+ }
+ branchget {
+ set result {}
+ foreach mclass [lreverse $classlist] {
+ foreach {field value} [dict getnull [::oo::meta::metadata $mclass] {*}$args] {
+ dict set result [string trimright $field :] $value
+ }
+ }
+ foreach {field value} [dict getnull $meta {*}$args] {
+ dict set result [string trimright $field :] $value
+ }
+ return $result
+ }
+ mixin {
+ foreach mclass $args {
+ set mclass [::oo::meta::normalize $mclass]
+ if {$mclass ni $MetaMixin} {
+ lappend MetaMixin $mclass
+ }
+ }
+ }
+ mixout {
+ foreach mclass $args {
+ set mclass [::oo::meta::normalize $mclass]
+ while {[set i [lsearch $MetaMixin $mclass]]>=0} {
+ set MetaMixin [lreplace $MetaMixin $i $i]
+ }
+ }
+ }
+ default {
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ set info [dict rmerge {*}$mdata $meta]
+ return [dict $submethod $info {*}$args]
+ }
+ }
+ }
+}
Index: modules/tool/pkgIndex.tcl
==================================================================
--- modules/tool/pkgIndex.tcl
+++ modules/tool/pkgIndex.tcl
@@ -7,7 +7,7 @@
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
if {![package vsatisfies [package provide Tcl] 8.6]} {return}
-package ifneeded tool 0.7 [list source [file join $dir tool.tcl]]
+package ifneeded tool 0.7.1 [list source [file join $dir tool.tcl]]
Index: modules/tool/tool.tcl
==================================================================
--- modules/tool/tool.tcl
+++ modules/tool/tool.tcl
@@ -1,23 +1,1584 @@
###
# Amalgamated package for tool
# Do not edit directly, tweak the source in src/ and rerun
# build.tcl
###
-package provide tool 0.7
+package provide tool 0.7.1
namespace eval ::tool {}
+###
+# START: dicttool.tcl
+###
+###
+# This package enhances the stock dict implementation with some
+# creature comforts
+###
+if {[info commands ::ladd] eq {}} {
+ proc ladd {varname args} {
+ upvar 1 $varname var
+ if ![info exists var] {
+ set var {}
+ }
+ foreach item $args {
+ if {$item in $var} continue
+ lappend var $item
+ }
+ return $var
+ }
+}
+
+if {[info command ::ldelete] eq {}} {
+ proc ::ldelete {varname args} {
+ upvar 1 $varname var
+ if ![info exists var] {
+ return
+ }
+ foreach item [lsort -unique $args] {
+ while {[set i [lsearch $var $item]]>=0} {
+ set var [lreplace $var $i $i]
+ }
+ }
+ return $var
+ }
+}
+
+if {[::info commands ::tcl::dict::getnull] eq {}} {
+ proc ::tcl::dict::getnull {dictionary args} {
+ if {[exists $dictionary {*}$args]} {
+ get $dictionary {*}$args
+ }
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] getnull ::tcl::dict::getnull]
+}
+if {[::info commands ::tcl::dict::print] eq {}} {
+ ###
+ # Test if element is a dict
+ ###
+ proc ::tcl::dict::_putb {buffervar indent field value} {
+ ::upvar 1 $buffervar buffer
+ ::append buffer \n [::string repeat " " $indent] [::list $field] " "
+ if {[string index $field end] eq "/"} {
+ ::incr indent 2
+ ::append buffer "\{"
+ foreach item $value {
+ if [catch {
+ if {![is_dict $item]} {
+ ::append buffer \n [::string repeat " " $indent] [list $item]
+ } else {
+ ::append buffer \n "[::string repeat " " $indent]\{"
+ ::incr indent 2
+ foreach {sf sv} $item {
+ _putb buffer $indent $sf $sv
+ }
+ ::incr indent -2
+ ::append buffer \n "[::string repeat " " $indent]\}"
+ }
+ } err] {
+ puts [list FAILED $indent $field $item]
+ puts $err
+ puts "$::errorInfo"
+ }
+ }
+ ::incr indent -2
+ ::append buffer \n [::string repeat " " $indent] "\}"
+ } elseif {[string index $field end] eq ":" || ![is_dict $value]} {
+ ::append buffer [::list $value]
+ } else {
+ ::incr indent 2
+ ::append buffer "\{"
+ foreach {f v} $value {
+ _putb buffer $indent $f $v
+ }
+ ::incr indent -2
+ ::append buffer \n [::string repeat " " $indent] "\}"
+ }
+ }
+ proc ::tcl::dict::print dict {
+ ::set buffer {}
+ ::foreach {field value} $dict {
+ _putb buffer 0 $field $value
+ }
+ return $buffer
+ }
+
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] print ::tcl::dict::print]
+}
+if {[::info commands ::tcl::dict::is_dict] eq {}} {
+ ###
+ # Test if element is a dict
+ ###
+ proc ::tcl::dict::is_dict { d } {
+ # is it a dict, or can it be treated like one?
+ if {[catch {dict size $d} err]} {
+ #::set ::errorInfo {}
+ return 0
+ }
+ return 1
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] is_dict ::tcl::dict::is_dict]
+}
+if {[::info commands ::tcl::dict::rmerge] eq {}} {
+ ###
+ # title: A recursive form of dict merge
+ # description:
+ # A routine to recursively dig through dicts and merge
+ # adapted from http://stevehavelka.com/tcl-dict-operation-nested-merge/
+ ###
+ proc ::tcl::dict::rmerge {a args} {
+ ::set result $a
+ # Merge b into a, and handle nested dicts appropriately
+ ::foreach b $args {
+ for { k v } $b {
+ if {[string index $k end] eq ":"} {
+ # Element names that end in ":" are assumed to be literals
+ set result $k $v
+ } elseif { [dict exists $result $k] } {
+ # key exists in a and b? let's see if both values are dicts
+ # both are dicts, so merge the dicts
+ if { [is_dict [get $result $k]] && [is_dict $v] } {
+ set result $k [rmerge [get $result $k] $v]
+ } else {
+ set result $k $v
+ }
+ } else {
+ set result $k $v
+ }
+ }
+ }
+ return $result
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] rmerge ::tcl::dict::rmerge]
+}
+
+if {[::info commands ::tcl::dict::isnull] eq {}} {
+ proc ::tcl::dict::isnull {dictionary args} {
+ if {![exists $dictionary {*}$args]} {return 1}
+ return [expr {[get $dictionary {*}$args] in {{} NULL null}}]
+ }
+ namespace ensemble configure dict -map [dict replace\
+ [namespace ensemble configure dict -map] isnull ::tcl::dict::isnull]
+}
+
+###
+# END: dicttool.tcl
+###
+###
+# START: dialect.tcl
+###
+###
+# oodialect.tcl
+#
+# Copyright (c) 2015-2018 Sean Woods
+# Copyright (c) 2015 Donald K Fellows
+#
+# BSD License
+###
+# @@ Meta Begin
+# Package oo::dialect 0.3.4
+# Meta platform tcl
+# Meta summary A utility for defining a domain specific language for TclOO systems
+# Meta description This package allows developers to generate
+# Meta description domain specific languages to describe TclOO
+# Meta description classes and objects.
+# Meta category TclOO
+# Meta subject oodialect
+# Meta require {Tcl 8.6}
+# Meta author Sean Woods
+# Meta author Donald K. Fellows
+# Meta license BSD
+# @@ Meta End
+namespace eval ::oo::dialect {
+ namespace export create
+}
+
+# A stack of class names
+proc ::oo::dialect::Push {class} {
+ ::variable class_stack
+ lappend class_stack $class
+}
+proc ::oo::dialect::Peek {} {
+ ::variable class_stack
+ return [lindex $class_stack end]
+}
+proc ::oo::dialect::Pop {} {
+ ::variable class_stack
+ set class_stack [lrange $class_stack 0 end-1]
+}
+
+###
+# This proc will generate a namespace, a "mother of all classes", and a
+# rudimentary set of policies for this dialect.
+###
+proc ::oo::dialect::create {name {parent ""}} {
+ set NSPACE [NSNormalize [uplevel 1 {namespace current}] $name]
+ ::namespace eval $NSPACE {::namespace eval define {}}
+ ###
+ # Build the "define" namespace
+ ###
+ if {$parent eq ""} {
+ ###
+ # With no "parent" language, begin with all of the keywords in
+ # oo::define
+ ###
+ foreach command [info commands ::oo::define::*] {
+ set procname [namespace tail $command]
+ interp alias {} ${NSPACE}::define::$procname {} \
+ ::oo::dialect::DefineThunk $procname
+ }
+ # Create an empty dynamic_methods proc
+ proc ${NSPACE}::dynamic_methods {class} {}
+ namespace eval $NSPACE {
+ ::namespace export dynamic_methods
+ ::namespace eval define {::namespace export *}
+ }
+ set ANCESTORS {}
+ } else {
+ ###
+ # If we have a parent language, that language already has the
+ # [oo::define] keywords as well as additional keywords and behaviors.
+ # We should begin with that
+ ###
+ set pnspace [NSNormalize [uplevel 1 {namespace current}] $parent]
+ apply [list parent {
+ ::namespace export dynamic_methods
+ ::namespace import -force ${parent}::dynamic_methods
+ } $NSPACE] $pnspace
+
+ apply [list parent {
+ ::namespace import -force ${parent}::define::*
+ ::namespace export *
+ } ${NSPACE}::define] $pnspace
+ set ANCESTORS [list ${pnspace}::object]
+ }
+ ###
+ # Build our dialect template functions
+ ###
+ proc ${NSPACE}::define {oclass args} [string map [list %NSPACE% $NSPACE] {
+ ###
+ # To facilitate library reloading, allow
+ # a dialect to create a class from DEFINE
+ ###
+ set class [::oo::dialect::NSNormalize [uplevel 1 {namespace current}] $oclass]
+ if {[info commands $class] eq {}} {
+ %NSPACE%::class create $class {*}${args}
+ } else {
+ ::oo::dialect::Define %NSPACE% $class {*}${args}
+ }
+}]
+ interp alias {} ${NSPACE}::define::current_class {} \
+ ::oo::dialect::Peek
+ interp alias {} ${NSPACE}::define::aliases {} \
+ ::oo::dialect::Aliases $NSPACE
+ interp alias {} ${NSPACE}::define::superclass {} \
+ ::oo::dialect::SuperClass $NSPACE
+
+ if {[info command ${NSPACE}::class] ne {}} {
+ ::rename ${NSPACE}::class {}
+ }
+ ###
+ # Build the metaclass for our language
+ ###
+ ::oo::class create ${NSPACE}::class {
+ superclass ::oo::dialect::MotherOfAllMetaClasses
+ }
+ # Wire up the create method to add in the extra argument we need; the
+ # MotherOfAllMetaClasses will know what to do with it.
+ ::oo::objdefine ${NSPACE}::class \
+ method create {name {definitionScript ""}} \
+ "next \$name [list ${NSPACE}::define] \$definitionScript"
+
+ ###
+ # Build the mother of all classes. Note that $ANCESTORS is already
+ # guaranteed to be a list in canonical form.
+ ###
+ uplevel #0 [string map [list %NSPACE% [list $NSPACE] %name% [list $name] %ANCESTORS% $ANCESTORS] {
+ %NSPACE%::class create %NSPACE%::object {
+ superclass %ANCESTORS%
+ # Put MOACish stuff in here
+ }
+ }]
+ if { "${NSPACE}::class" ni $::oo::dialect::core_classes } {
+ lappend ::oo::dialect::core_classes "${NSPACE}::class"
+ }
+ if { "${NSPACE}::object" ni $::oo::dialect::core_classes } {
+ lappend ::oo::dialect::core_classes "${NSPACE}::object"
+ }
+}
+
+# Support commands; not intended to be called directly.
+proc ::oo::dialect::NSNormalize {namespace qualname} {
+ if {![string match ::* $qualname]} {
+ set qualname ${namespace}::$qualname
+ }
+ regsub -all {::+} $qualname "::"
+}
+
+proc ::oo::dialect::DefineThunk {target args} {
+ tailcall ::oo::define [Peek] $target {*}$args
+}
+
+proc ::oo::dialect::Canonical {namespace NSpace class} {
+ namespace upvar $namespace cname cname
+ #if {[string match ::* $class]} {
+ # return $class
+ #}
+ if {[info exists cname($class)]} {
+ return $cname($class)
+ }
+ if {[info exists ::oo::dialect::cname($class)]} {
+ return $::oo::dialect::cname($class)
+ }
+ if {[info exists ::oo::dialect::cname(${NSpace}::${class})]} {
+ return $::oo::dialect::cname(${NSpace}::${class})
+ }
+ foreach item [list "${NSpace}::$class" "::$class"] {
+ if {[info commands $item] ne {}} {
+ return $item
+ }
+ }
+ return ${NSpace}::$class
+}
+
+###
+# Implementation of the languages' define command
+###
+proc ::oo::dialect::Define {namespace class args} {
+ Push $class
+ try {
+ if {[llength $args]==1} {
+ namespace eval ${namespace}::define [lindex $args 0]
+ } else {
+ ${namespace}::define::[lindex $args 0] {*}[lrange $args 1 end]
+ }
+ ${namespace}::dynamic_methods $class
+ } finally {
+ Pop
+ }
+}
+
+###
+# Implementation of how we specify the other names that this class will answer
+# to
+###
+
+proc ::oo::dialect::Aliases {namespace args} {
+ set class [Peek]
+ namespace upvar $namespace cname cname
+ set NSpace [join [lrange [split $class ::] 1 end-2] ::]
+ set cname($class) $class
+ foreach name $args {
+ set cname($name) $class
+ #set alias $name
+ set alias [NSNormalize $NSpace $name]
+ # Add a local metaclass reference
+ if {![info exists ::oo::dialect::cname($alias)]} {
+ lappend ::oo::dialect::aliases($class) $alias
+ ##
+ # Add a global reference, first come, first served
+ ##
+ set ::oo::dialect::cname($alias) $class
+ }
+ }
+}
+
+###
+# Implementation of a superclass keyword which will enforce the inheritance of
+# our language's mother of all classes
+###
+
+proc ::oo::dialect::SuperClass {namespace args} {
+ set class [Peek]
+ namespace upvar $namespace class_info class_info
+ dict set class_info($class) superclass 1
+ set ::oo::dialect::cname($class) $class
+ set NSpace [join [lrange [split $class ::] 1 end-2] ::]
+ set unique {}
+ foreach item $args {
+ set Item [Canonical $namespace $NSpace $item]
+ dict set unique $Item $item
+ }
+ set root ${namespace}::object
+ if {$class ne $root} {
+ dict set unique $root $root
+ }
+ tailcall ::oo::define $class superclass {*}[dict keys $unique]
+}
+
+###
+# Implementation of the common portions of the the metaclass for our
+# languages.
+###
+
+if {[info commands ::oo::dialect::MotherOfAllMetaClasses] eq {}} {
+ ::oo::class create ::oo::dialect::MotherOfAllMetaClasses {
+ superclass ::oo::class
+ constructor {define definitionScript} {
+ $define [self] {
+ superclass
+ }
+ $define [self] $definitionScript
+ }
+ method aliases {} {
+ if {[info exists ::oo::dialect::aliases([self])]} {
+ return $::oo::dialect::aliases([self])
+ }
+ }
+ }
+}
+
+namespace eval ::oo::dialect {
+ variable core_classes {::oo::class ::oo::object}
+}
+
+###
+# END: dialect.tcl
+###
+###
+# START: oometa.tcl
+###
+
+namespace eval ::oo::meta {
+ set dirty_classes {}
+}
+
+proc ::oo::meta::args_to_dict args {
+ if {[llength $args]==1} {
+ return [lindex $args 0]
+ }
+ return $args
+}
+
+proc ::oo::meta::args_to_options args {
+ set result {}
+ foreach {var val} [args_to_dict {*}$args] {
+ lappend result [string trimleft $var -] $val
+ }
+ return $result
+}
+
+proc ::oo::meta::ancestors class {
+ set class [::oo::meta::normalize $class]
+ set core_result {}
+ set queue $class
+ set result {}
+ # Rig things such that that the top superclasses
+ # are evaluated first
+ while {[llength $queue]} {
+ set tqueue $queue
+ set queue {}
+ foreach qclass $tqueue {
+ if {$qclass in $::oo::dialect::core_classes} {
+ if {$qclass ni $core_result} {
+ lappend core_result $qclass
+ }
+ continue
+ }
+ foreach aclass [::info class superclasses $qclass] {
+ if { $aclass in $result } continue
+ if { $aclass in $core_result } continue
+ if { $aclass in $queue } continue
+ lappend queue $aclass
+ }
+ }
+ foreach item $tqueue {
+ if {$item in $core_result} continue
+ if { $item ni $result } {
+ set result [linsert $result 0 $item]
+ }
+ }
+ }
+ # Handle core classes last
+ set queue $core_result
+ while {[llength $queue]} {
+ set tqueue $queue
+ set queue {}
+ foreach qclass $tqueue {
+ foreach aclass [::info class superclasses $qclass] {
+ if { $aclass in $result } continue
+ if { $aclass in $queue } continue
+ lappend queue $aclass
+ }
+ }
+ foreach item $tqueue {
+ if { $item ni $result } {
+ set result [linsert $result 0 $item]
+ }
+ }
+ }
+ return $result
+}
+
+proc oo::meta::info {class submethod args} {
+ set class [::oo::meta::normalize $class]
+ switch $submethod {
+ cget {
+ ###
+ # submethod: cget
+ # arguments: ?*path* ...? *field*
+ # format: markdown
+ # description:
+ # Retrieve a value from the class' meta data. Values are searched in the
+ # following order:
+ # 1. From class meta data as const **path** **field:**
+ # 2. From class meta data as const **path** **field**
+ # 3. From class meta data as **path** **field:**
+ # 4. From class meta data as **path** **field**
+ ###
+ set path [lrange $args 0 end-1]
+ set field [string trimright [lindex $args end] :]
+ foreach mclass [lreverse [::oo::meta::ancestors $class]] {
+ if {![::info exists ::oo::meta::local_property($mclass)]} continue
+ set class_metadata $::oo::meta::local_property($mclass)
+ if {[dict exists $class_metadata const {*}$path $field:]} {
+ return [dict get $class_metadata const {*}$path $field:]
+ }
+ if {[dict exists $class_metadata const {*}$path $field]} {
+ return [dict get $class_metadata const {*}$path $field]
+ }
+ if {[dict exists $class_metadata {*}$path $field:]} {
+ return [dict get $class_metadata {*}$path $field:]
+ }
+ if {[dict exists $class_metadata {*}$path $field]} {
+ return [dict get $class_metadata {*}$path $field]
+ }
+ }
+ return {}
+ }
+ rebuild {
+ ::oo::meta::rebuild $class
+ }
+ is {
+ set info [metadata $class]
+ return [string is [lindex $args 0] -strict [dict getnull $info {*}[lrange $args 1 end]]]
+ }
+ for -
+ map {
+ set info [metadata $class]
+ uplevel 1 [list ::dict $submethod [lindex $args 0] [dict get $info {*}[lrange $args 1 end-1]] [lindex $args end]]
+ }
+ with {
+ upvar 1 TEMPVAR info
+ set info [metadata $class]
+ return [uplevel 1 [list ::dict with TEMPVAR {*}$args]]
+ }
+ branchget {
+ set info [metadata $class]
+ set result {}
+ foreach {field value} [dict getnull $info {*}$args] {
+ dict set result [string trimright $field :] $value
+ }
+ return $result
+ }
+ 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
+ }
+ append -
+ incr -
+ lappend -
+ set -
+ unset -
+ update {
+ ::oo::meta::rebuild $class
+ ::dict $submethod ::oo::meta::local_property($class) {*}$args
+ }
+ merge {
+ ::oo::meta::rebuild $class
+ set ::oo::meta::local_property($class) [dict rmerge $::oo::meta::local_property($class) {*}$args]
+ }
+ dump {
+ set info [metadata $class]
+ return $info
+ }
+ default {
+ set info [metadata $class]
+ return [::dict $submethod $info {*}$args]
+ }
+ }
+}
+
+proc ::oo::meta::localdata {class args} {
+ if {![::info exists ::oo::meta::local_property($class)]} {
+ return {}
+ }
+ if {[::llength $args]==0} {
+ return $::oo::meta::local_property($class)
+ }
+ return [::dict getnull $::oo::meta::local_property($class) {*}$args]
+}
+
+proc ::oo::meta::normalize class {
+ set class ::[string trimleft $class :]
+}
+
+proc ::oo::meta::metadata {class {force 0}} {
+ set class [::oo::meta::normalize $class]
+ ###
+ # Destroy the cache of all derivitive classes
+ ###
+ if {$force} {
+ unset -nocomplain ::oo::meta::cached_property
+ unset -nocomplain ::oo::meta::cached_hierarchy
+ } else {
+ variable dirty_classes
+ 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::dialect::core_classes} {
+ lappend ::oo::dialect::core_classes $dclass
+ }
+ }
+ }
+ set dirty_classes {}
+ }
+
+ ###
+ # If the cache is available, use it
+ ###
+ variable cached_property
+ if {[::info exists cached_property($class)]} {
+ return $cached_property($class)
+ }
+ ###
+ # Build a cache of the hierarchy and the
+ # aggregate metadata for this class and store
+ # them for future use
+ ###
+ variable cached_hierarchy
+ set metadata {}
+ set stack {}
+ variable local_property
+ set cached_hierarchy($class) [::oo::meta::ancestors $class]
+ foreach class $cached_hierarchy($class) {
+ if {[::info exists local_property($class)]} {
+ lappend metadata $local_property($class)
+ }
+ }
+ #foreach aclass [lreverse [::info class superclasses $class]] {
+ # lappend metadata [::oo::meta::metadata $aclass]
+ #}
+
+ lappend metadata {classinfo {type: {}}}
+ if {[::info exists local_property($class)]} {
+ lappend metadata $local_property($class)
+ }
+ set metadata [dict rmerge {*}$metadata]
+ set cached_property($class) $metadata
+ return $metadata
+}
+
+proc ::oo::meta::rebuild args {
+ foreach class $args {
+ if {$class ni $::oo::meta::dirty_classes} {
+ lappend ::oo::meta::dirty_classes $class
+ }
+ }
+}
+
+proc ::oo::meta::search args {
+ variable local_property
+
+ set path [lrange $args 0 end-1]
+ set value [lindex $args end]
+
+ set result {}
+ foreach {class info} [array get local_property] {
+ if {[dict exists $info {*}$path:]} {
+ if {[string match [dict get $info {*}$path:] $value]} {
+ lappend result $class
+ }
+ continue
+ }
+ if {[dict exists $info {*}$path]} {
+ if {[string match [dict get $info {*}$path] $value]} {
+ lappend result $class
+ }
+ }
+ }
+ return $result
+}
+
+proc ::oo::define::meta {args} {
+ set class [lindex [::info level -1] 1]
+ if {[lindex $args 0] in "cget set branchset"} {
+ ::oo::meta::info $class {*}$args
+ } else {
+ ::oo::meta::info $class set {*}$args
+ }
+}
+
+oo::define oo::class {
+ method meta {submethod args} {
+ tailcall ::oo::meta::info [self] $submethod {*}$args
+ }
+}
+
+oo::define oo::object {
+ ###
+ # title: Provide access to meta data
+ # format: markdown
+ # description:
+ # The *meta* method allows an object access
+ # to a combination of its own meta data as
+ # well as to that of its class
+ ###
+ method meta {submethod args} {
+ my variable meta MetaMixin
+ if {![info exists MetaMixin]} {
+ set MetaMixin {}
+ }
+ set class [::info object class [self object]]
+ set classlist [list $class {*}$MetaMixin]
+ switch $submethod {
+ cget {
+ ###
+ # submethod: cget
+ # arguments: ?*path* ...? *field*
+ # format: markdown
+ # description:
+ # Retrieve a value from the local objects **meta** dict
+ # or from the class' meta data. Values are searched in the
+ # following order:
+ # 0. (If path length==1) From the _config array
+ # 1. From the local dict as **path** **field:**
+ # 2. From the local dict as **path** **field**
+ # 3. From class meta data as const **path** **field:**
+ # 4. From class meta data as const **path** **field**
+ # 5. From class meta data as **path** **field:**
+ # 6. From class meta data as **path** **field**
+ ###
+ set path [lrange $args 0 end-1]
+ set field [string trim [lindex $args end] :]
+ if {[dict exists $meta {*}$path $field:]} {
+ return [dict get $meta {*}$path $field:]
+ }
+ if {[dict exists $meta {*}$path $field]} {
+ return [dict get $meta {*}$path $field]
+ }
+ foreach mclass [lreverse $classlist] {
+ set class_metadata [::oo::meta::metadata $mclass]
+ if {[dict exists $class_metadata const {*}$path $field:]} {
+ return [dict get $class_metadata const {*}$path $field:]
+ }
+ if {[dict exists $class_metadata const {*}$path $field]} {
+ return [dict get $class_metadata const {*}$path $field]
+ }
+ if {[dict exists $class_metadata {*}$path $field:]} {
+ return [dict get $class_metadata {*}$path $field:]
+ }
+ if {[dict exists $class_metadata {*}$path $field]} {
+ return [dict get $class_metadata {*}$path $field]
+ }
+ }
+ return {}
+ }
+ is {
+ set value [my meta cget {*}[lrange $args 1 end]]
+ return [string is [lindex $args 0] -strict $value]
+ }
+ for -
+ map {
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ set info [dict rmerge {*}$mdata $meta]
+ uplevel 1 [list ::dict $submethod [lindex $args 0] [dict get $info {*}[lrange $args 1 end-1]] [lindex $args end]]
+ }
+ with {
+ upvar 1 TEMPVAR info
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ set info [dict rmerge {*}$mdata $meta]
+ return [uplevel 1 [list dict with TEMPVAR {*}$args]]
+ }
+ dump {
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ return [dict rmerge {*}$mdata $meta]
+ }
+ append -
+ incr -
+ lappend -
+ set -
+ unset -
+ update {
+ return [dict $submethod meta {*}$args]
+ }
+ branchset {
+ foreach {field value} [lindex $args end] {
+ dict set meta {*}[lrange $args 0 end-1] [string trimright $field :]: $value
+ }
+ }
+ rmerge -
+ merge {
+ set meta [dict rmerge $meta {*}$args]
+ return $meta
+ }
+ exists {
+ foreach mclass $classlist {
+ if {[dict exists [::oo::meta::metadata $mclass] {*}$args]} {
+ return 1
+ }
+ }
+ if {[dict exists $meta {*}$args]} {
+ return 1
+ }
+ return 0
+ }
+ get -
+ getnull {
+ if {[string index [lindex $args end] end]==":"} {
+ # Looking for a leaf node
+ if {[dict exists $meta {*}$args]} {
+ return [dict get $meta {*}$args]
+ }
+ foreach mclass [lreverse $classlist] {
+ set mdata [::oo::meta::metadata $mclass]
+ if {[dict exists $mdata {*}$args]} {
+ return [dict get $mdata {*}$args]
+ }
+ }
+ if {$submethod == "get"} {
+ error "key \"$args\" not known in metadata"
+ }
+ return {}
+ }
+ # Looking for a branch node
+ # So we need to composite the result
+ set found 0
+ foreach mclass $classlist {
+ set mdata [::oo::meta::metadata $mclass]
+ if {[dict exists $mdata {*}$args]} {
+ set found 1
+ lappend result [dict get $mdata {*}$args]
+ }
+ }
+ if {[dict exists $meta {*}$args]} {
+ set found 1
+ lappend result [dict get $meta {*}$args]
+ }
+ if {!$found} {
+ if {$submethod == "get"} {
+ error "key \"$args\" not known in metadata"
+ }
+ return {}
+ }
+ return [dict rmerge {*}$result]
+ }
+ branchget {
+ set result {}
+ foreach mclass [lreverse $classlist] {
+ foreach {field value} [dict getnull [::oo::meta::metadata $mclass] {*}$args] {
+ dict set result [string trimright $field :] $value
+ }
+ }
+ foreach {field value} [dict getnull $meta {*}$args] {
+ dict set result [string trimright $field :] $value
+ }
+ return $result
+ }
+ mixin {
+ foreach mclass $args {
+ set mclass [::oo::meta::normalize $mclass]
+ if {$mclass ni $MetaMixin} {
+ lappend MetaMixin $mclass
+ }
+ }
+ }
+ mixout {
+ foreach mclass $args {
+ set mclass [::oo::meta::normalize $mclass]
+ while {[set i [lsearch $MetaMixin $mclass]]>=0} {
+ set MetaMixin [lreplace $MetaMixin $i $i]
+ }
+ }
+ }
+ default {
+ foreach mclass $classlist {
+ lappend mdata [::oo::meta::metadata $mclass]
+ }
+ set info [dict rmerge {*}$mdata $meta]
+ return [dict $submethod $info {*}$args]
+ }
+ }
+ }
+}
+
+###
+# END: oometa.tcl
+###
+###
+# START: cron.tcl
+###
+###
+# This file implements a process table
+# Instead of having individual components try to maintain their own timers
+# we centrally manage how often tasks should be kicked off here.
+###
+#
+# Author: Sean Woods (for T&E Solutions)
+
+::namespace eval ::cron {}
+
+proc ::cron::task {command args} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::task $command $args]
+ }
+ variable processTable
+ switch $command {
+ TEMPLATE {
+ return [list object {} lastevent 0 lastrun 0 err 0 result {} \
+ running 0 coroutine {} scheduled 0 frequency 0 command {}]
+ }
+ delete {
+ unset -nocomplain ::cron::processTable([lindex $args 0])
+ }
+ exists {
+ return [::info exists ::cron::processTable([lindex $args 0])]
+ }
+ info {
+ set process [lindex $args 0]
+ if {![::info exists ::cron::processTable($process)]} {
+ error "Process $process does not exist"
+ }
+ return $::cron::processTable($process)
+ }
+ frequency {
+ set process [lindex $args 0]
+ set time [lindex $args 1]
+ if {![info exists ::cron::processTable($process)]} return
+ dict with ::cron::processTable($process) {
+ set now [clock_step [current_time]]
+ set frequency [expr {0+$time}]
+ if {$scheduled>($now+$time)} {
+ dict set ::cron::processTable($process) scheduled [expr {$now+$time}]
+ }
+ }
+ }
+ sleep {
+ set process [lindex $args 0]
+ set time [lindex $args 1]
+ if {![info exists ::cron::processTable($process)]} return
+ dict with ::cron::processTable($process) {
+ set now [clock_step [current_time]]
+ set frequency 0
+ set scheduled [expr {$now+$time}]
+ }
+ }
+ create -
+ set {
+ set process [lindex $args 0]
+ if {![::info exists ::cron::processTable($process)]} {
+ set ::cron::processTable($process) [task TEMPLATE]
+ }
+ if {[llength $args]==2} {
+ foreach {field value} [lindex $args 1] {
+ dict set ::cron::processTable($process) $field $value
+ }
+ } else {
+ foreach {field value} [lrange $args 1 end] {
+ dict set ::cron::processTable($process) $field $value
+ }
+ }
+ }
+ }
+}
+
+proc ::cron::at args {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::at $args]
+ }
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args timecode command
+ }
+ 3 {
+ lassign $args process timecode command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ variable processTable
+ if {[string is integer -strict $timecode]} {
+ set scheduled [expr {$timecode*1000}]
+ } else {
+ set scheduled [expr {[clock scan $timecode]*1000}]
+ }
+ ::cron::task set $process \
+ frequency -1 \
+ command $command \
+ scheduled $scheduled \
+ coroutine {}
+
+ if {$::cron::trace > 1} {
+ puts [list ::cron::task info $process - > [::cron::task info $process]]
+ }
+ ::cron::wake NEW
+ return $process
+}
+
+proc ::cron::idle args {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::idle $args]
+ }
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args command
+ }
+ 3 {
+ lassign $args process command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ ::cron::task set $process \
+ scheduled 0 \
+ frequency 0 \
+ command $command
+ ::cron::wake NEW
+ return $process
+}
+
+proc ::cron::in args {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::in $args]
+ }
+ switch [llength $args] {
+ 2 {
+ variable processuid
+ set process event#[incr processuid]
+ lassign $args timecode command
+ }
+ 3 {
+ lassign $args process timecode command
+ }
+ default {
+ error "Usage: ?process? timecode command"
+ }
+ }
+ set now [clock_step [current_time]]
+ set scheduled [expr {$timecode*1000+$now}]
+ ::cron::task set $process \
+ frequency -1 \
+ command $command \
+ scheduled $scheduled
+ ::cron::wake NEW
+ return $process
+}
+
+proc ::cron::cancel {process} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::cancel $process]
+ }
+ ::cron::task delete $process
+}
+
+###
+# topic: 0776dccd7e84530fa6412e507c02487c
+###
+proc ::cron::every {process frequency command} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::every $process $frequency $command]
+ }
+ variable processTable
+ set mnow [clock_step [current_time]]
+ set frequency [expr {$frequency*1000}]
+ ::cron::task set $process \
+ frequency $frequency \
+ command $command \
+ scheduled [expr {$mnow + $frequency}]
+ ::cron::wake NEW
+}
+
+
+proc ::cron::object_coroutine {objname coroutine {info {}}} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::object_coroutine $objname $coroutine $info]
+ }
+ task set $coroutine \
+ {*}$info \
+ object $objname \
+ coroutine $coroutine
+
+ return $coroutine
+}
+
+# Notification that an object has been destroyed, and that
+# it should give up any toys associated with events
+proc ::cron::object_destroy {objname} {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::object_destroy $objname]
+ }
+ variable processTable
+ set dat [array get processTable]
+ foreach {process info} $dat {
+ if {[dict exists $info object] && [dict get $info object] eq $objname} {
+ unset -nocomplain processTable($process)
+ }
+ }
+}
+
+###
+# topic: 97015814408714af539f35856f85bce6
+###
+proc ::cron::run process {
+ variable processTable
+ set mnow [clock_step [current_time]]
+ if {[dict exists processTable($process) scheduled] && [dict exists processTable($process) scheduled]>0} {
+ dict set processTable($process) scheduled [expr {$mnow-1000}]
+ } else {
+ dict set processTable($process) lastrun 0
+ }
+ ::cron::wake PROCESS
+}
+
+proc ::cron::clock_step timecode {
+ return [expr {$timecode-($timecode%1000)}]
+}
+
+proc ::cron::clock_delay {delay} {
+ set now [current_time]
+ set then [clock_step [expr {$delay+$now}]]
+ return [expr {$then-$now}]
+}
+
+# Sleep for X seconds, wake up at the top
+proc ::cron::clock_sleep {{sec 1} {offset 0}} {
+ set now [current_time]
+ set delay [expr {[clock_delay [expr {$sec*1000}]]+$offset}]
+ sleep $delay
+}
+
+proc ::cron::current_time {} {
+ if {$::cron::time < 0} {
+ return [clock milliseconds]
+ }
+ return $::cron::time
+}
+
+proc ::cron::clock_set newtime {
+ variable time
+ for {} {$time < $newtime} {incr time 100} {
+ uplevel #0 {::cron::do_one_event CLOCK_ADVANCE}
+ }
+ set time $newtime
+ uplevel #0 {::cron::do_one_event CLOCK_ADVANCE}
+}
+
+proc ::cron::once_in_a_while body {
+ set script {set _eventid_ $::cron::current_event}
+ append script $body
+ # Add a safety to allow this while to only execute once per call
+ append script {if {$_eventid_==$::cron::current_event} yield}
+ uplevel 1 [list while 1 $script]
+}
+
+proc ::cron::sleep ms {
+ if {$::cron::trace > 1} {
+ puts [list ::cron::sleep $ms [info coroutine]]
+ }
+
+ set coro [info coroutine]
+ # When the clock is being externally
+ # controlled, advance the clock when
+ # a sleep is called
+ variable time
+ if {$time >= 0 && $coro eq {}} {
+ ::cron::clock_set [expr {$time+$ms}]
+ return
+ }
+ if {$coro ne {}} {
+ set mnow [current_time]
+ set start $mnow
+ set end [expr {$start+$ms}]
+ set eventid $coro
+ if {$::cron::trace} {
+ puts "::cron::sleep $ms $coro"
+ }
+ # Mark as running
+ task set $eventid scheduled $end coroutine $coro running 1
+ ::cron::wake WAKE_IN_CORO
+ yield 2
+ while {$end >= $mnow} {
+ if {$::cron::trace} {
+ puts "::cron::sleep $ms $coro (loop)"
+ }
+ set mnow [current_time]
+ yield 2
+ }
+ # Mark as not running to resume idle computation
+ task set $eventid running 0
+ if {$::cron::trace} {
+ puts "/::cron::sleep $ms $coro"
+ }
+ } else {
+ set eventid [incr ::cron::eventcount]
+ set var ::cron::event_#$eventid
+ set $var 0
+ if {$::cron::trace} {
+ puts "::cron::sleep $ms $eventid waiting for $var"
+ ::after $ms "set $var 1 ; puts \"::cron::sleep - $eventid - FIRED\""
+ } else {
+ ::after $ms "set $var 1"
+ }
+ ::vwait $var
+ if {$::cron::trace} {
+ puts "/::cron::sleep $ms $eventid"
+ }
+ unset $var
+ }
+}
+
+###
+# topic: 21de7bb8db019f3a2fd5a6ae9b38fd55
+# description:
+# Called once per second, and timed to ensure
+# we run in roughly realtime
+###
+proc ::cron::runTasksCoro {} {
+ ###
+ # Do this forever
+ ###
+ variable processTable
+ variable processing
+ variable all_coroutines
+ variable coroutine_object
+ variable coroutine_busy
+ variable nextevent
+ variable current_event
+
+ while 1 {
+ incr current_event
+ set lastevent 0
+ set now [current_time]
+ # Wake me up in 5 minute intervals, just out of principle
+ set nextevent [expr {$now-($now % 300000) + 300000}]
+ set next_idle_event [expr {$now+250}]
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER nextevent $nextevent]
+ }
+ ###
+ # Determine what tasks to run this timestep
+ ###
+ set tasks {}
+ set cancellist {}
+ set nexttask {}
+
+ foreach {process} [lsort -dictionary [array names processTable]] {
+ dict with processTable($process) {
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER process $process frequency: $frequency scheduled: $scheduled]
+ }
+ if {$scheduled==0 && $frequency==0} {
+ set lastrun $now
+ set lastevent $now
+ lappend tasks $process
+ } else {
+ if { $scheduled <= $now } {
+ lappend tasks $process
+ if { $frequency < 0 } {
+ lappend cancellist $process
+ } elseif {$frequency==0} {
+ set scheduled 0
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER process $process demoted to idle]
+ }
+ } else {
+ set scheduled [clock_step [expr {$frequency+$lastrun}]]
+ if { $scheduled <= $now } {
+ set scheduled [clock_step [expr {$frequency+$now}]]
+ }
+ if {$::cron::trace > 1} {
+ puts [list CRON TASK RUNNER process $process rescheduled to $scheduled]
+ }
+ }
+ set lastrun $now
+ }
+ set lastevent $now
+ }
+ }
+ }
+ foreach task $tasks {
+ dict set processTable($task) lastrun $now
+ if {[dict exists processTable($task) foreground] && [dict set processTable($task) foreground]} continue
+ if {[dict exists processTable($task) running] && [dict set processTable($task) running]} continue
+ if {$::cron::trace > 2} {
+ puts [list RUNNING $task [task info $task]]
+ }
+ if {[dict exists $processTable($task) coroutine]} {
+ set coro [dict get $processTable($task) coroutine]
+ } else {
+ set coro {}
+ }
+ dict set processTable($task) running 1
+ if {[dict exists $processTable($task) command]} {
+ set command [dict get $processTable($task) command]
+ } else {
+ set command {}
+ }
+ if {$command eq {} && $coro eq {}} {
+ # Task has nothing to do. Slot it for destruction
+ lappend cancellist $task
+ } elseif {$coro ne {}} {
+ if {[info command $coro] eq {}} {
+ set object [dict get $processTable($task) object]
+ # Trigger coroutine again if a command was given
+ # If this coroutine is associated with an object, ensure
+ # the object still exists before invoking its method
+ if {$command eq {} || ($object ne {} && [info command $object] eq {})} {
+ lappend cancellist $task
+ dict set processTable($task) running 0
+ continue
+ }
+ if {$::cron::trace} {
+ puts [list RESTARTING $task - coroutine $coro - with $command]
+ }
+ ::coroutine $coro {*}$command
+ }
+ try $coro on return {} {
+ # Terminate the coroutine
+ lappend cancellist $task
+ } on break {} {
+ # Terminate the coroutine
+ lappend cancellist $task
+ } on error {errtxt errdat} {
+ # Coroutine encountered an error
+ lappend cancellist $task
+ puts "ERROR $coro"
+ set errorinfo [dict get $errdat -errorinfo]
+ if {[info exists coroutine_object($coro)] && $coroutine_object($coro) ne {}} {
+ catch {
+ puts "OBJECT: $coroutine_object($coro)"
+ puts "CLASS: [info object class $coroutine_object($coro)]"
+ }
+ }
+ puts "$errtxt"
+ puts ***
+ puts $errorinfo
+ } on continue {result opts} {
+ # Ignore continue
+ if { $result eq "done" } {
+ lappend cancellist $task
+ }
+ } on ok {result opts} {
+ if { $result eq "done" } {
+ lappend cancellist $task
+ }
+ }
+ } else {
+ dict with processTable($task) {
+ set err [catch {uplevel #0 $command} result errdat]
+ if $err {
+ puts "CRON TASK FAILURE:"
+ puts "PROCESS: $task"
+ puts $result
+ puts ***
+ puts [dict get $errdat -errorinfo]
+ }
+ }
+ yield 0
+ }
+ dict set processTable($task) running 0
+ }
+ foreach {task} $cancellist {
+ unset -nocomplain processTable($task)
+ }
+ foreach {process} [lsort -dictionary [array names processTable]] {
+ set scheduled 0
+ set frequency 0
+ dict with processTable($process) {
+ if {$scheduled==0 && $frequency==0} {
+ if {$next_idle_event < $nextevent} {
+ set nexttask $task
+ set nextevent $next_idle_event
+ }
+ } elseif {$scheduled < $nextevent} {
+ set nexttask $process
+ set nextevent $scheduled
+ }
+ set lastevent $now
+ }
+ }
+ foreach {eventid msec} [array get ::cron::coro_sleep] {
+ if {$msec < 0} continue
+ if {$msec<$nextevent} {
+ set nexttask "CORO $eventid"
+ set nextevent $scheduled
+ }
+ }
+ set delay [expr {$nextevent-$now}]
+ if {$delay <= 0} {
+ yield 0
+ } else {
+ if {$::cron::trace > 1} {
+ puts "NEXT EVENT $delay - NEXT TASK $nexttask"
+ }
+ yield $delay
+ }
+ }
+}
+
+proc ::cron::wake {{who ???}} {
+ ##
+ # Only triggered by cron jobs kicking off other cron jobs within
+ # the script body
+ ##
+ if {$::cron::trace} {
+ puts "::cron::wake $who"
+ }
+ if {$::cron::busy} {
+ return
+ }
+ after cancel $::cron::next_event
+ set ::cron::next_event [after idle [list ::cron::do_one_event $who]]
+}
+
+proc ::cron::do_one_event {{who ???}} {
+ if {$::cron::trace} {
+ puts "::cron::do_one_event $who"
+ }
+ after cancel $::cron::next_event
+ set now [current_time]
+ set ::cron::busy 1
+ while {$::cron::busy} {
+ if {[info command ::cron::COROUTINE] eq {}} {
+ ::coroutine ::cron::COROUTINE ::cron::runTasksCoro
+ }
+ set cron_delay [::cron::COROUTINE]
+ if {$cron_delay==0} {
+ if {[incr loops]>10} {
+ if {$::cron::trace} {
+ puts "Breaking out of 10 recursive loops"
+ }
+ set ::cron::wake_time 1000
+ break
+ }
+ set ::cron::wake_time 0
+ incr ::cron::loops(active)
+ } else {
+ set ::cron::busy 0
+ incr ::cron::loops(idle)
+ }
+ }
+ ###
+ # Try to get the event to fire off on the border of the
+ # nearest second
+ ###
+ if {$cron_delay < 10} {
+ set cron_delay 250
+ }
+ set ctime [current_time]
+ set next [expr {$ctime+$cron_delay}]
+ set ::cron::wake_time [expr {$next/1000}]
+ if {$::cron::trace} {
+ puts [list EVENT LOOP WILL WAKE IN $cron_delay ms next: [clock format $::cron::wake_time -format "%H:%M:%S"] active: $::cron::loops(active) idle: $::cron::loops(idle) woken_by: $who]
+ }
+ set ::cron::next_event [after $cron_delay {::cron::do_one_event TIMER}]
+}
+
+
+proc ::cron::main {} {
+ # Never launch from a coroutine
+ if {[info coroutine] ne {}} {
+ return
+ }
+ set ::cron::forever 1
+ while {$::cron::forever} {
+ ::after 120000 {set ::cron::forever 1}
+ # Call an update just to give the rest of the event loop a chance
+ incr ::cron::loops(main)
+ ::after cancel $::cron::next_event
+ set ::cron::next_event [::after idle {::cron::wake MAIN}]
+ set ::cron::forever 1
+ set ::cron::busy 0
+ ::vwait ::cron::forever
+ if {$::cron::trace} {
+ puts "MAIN LOOP CYCLE $::cron::loops(main)"
+ }
+ }
+}
+
+###
+# topic: 4a891d0caabc6e25fbec9514ea8104dd
+# description:
+# This file implements a process table
+# Instead of having individual components try to maintain their own timers
+# we centrally manage how often tasks should be kicked off here.
+###
+namespace eval ::cron {
+ variable lastcall 0
+ variable processTable
+ variable busy 0
+ variable next_event {}
+ variable trace 0
+ variable current_event
+ variable time -1
+ if {![info exists current_event]} {
+ set current_event 0
+ }
+ if {![info exists ::cron::loops]} {
+ array set ::cron::loops {
+ active 0
+ main 0
+ idle 0
+ wake 0
+ }
+ }
+}
+
+::cron::wake STARTUP
+
+
+###
+# END: cron.tcl
+###
###
# START: core.tcl
###
package require Tcl 8.6 ;# try in pipeline.tcl. Possibly other things.
-package require dicttool
+package require coroutine
package require TclOO
package require sha1
#package require cron 2.0
-package require oo::meta 0.5.1
-package require oo::dialect
+#package require oo::dialect
::oo::dialect::create ::tool
::namespace eval ::tool {}
set ::tool::trace 0
@@ -576,11 +2137,11 @@
###
###
# START: metaclass.tcl
###
#-------------------------------------------------------------------------
-# TITLE:
+# TITLE:
# tool.tcl
#
# PROJECT:
# tool: TclOO Helper Library
#
@@ -902,33 +2463,33 @@
variable DestroyEvent 0
constructor args {
my Config_merge [::tool::args_to_options {*}$args]
}
-
+
destructor {}
-
+
method ancestors {{reverse 0}} {
set result [::oo::meta::ancestors [info object class [self]]]
if {$reverse} {
return [lreverse $result]
}
return $result
}
-
+
method DestroyEvent {} {
my variable DestroyEvent
return $DestroyEvent
}
-
+
###
# title: Forward a method
###
method forward {method args} {
oo::objdefine [self] forward $method {*}$args
}
-
+
###
# title: Direct a series of sub-functions to a seperate object
###
method graft args {
my variable organs
@@ -945,14 +2506,14 @@
oo::objdefine [self] forward <${stub}> $object
oo::objdefine [self] export <${stub}>
}
return $object
}
-
+
# Called after all options and public variables are initialized
method initialize {} {}
-
+
###
# topic: 3c4893b65a1c79b2549b9ee88f23c9e3
# description:
# Provide a default value for all options and
# publically declared variables, and locks the
@@ -970,20 +2531,26 @@
# while the contructor is still running.
# Note, by default an odie object will ignore
# signals until a later call to my lock remove pipeline
###
method InitializePublic {} {
- my variable config meta
+ my variable config meta mixinmap mixins
+ if {![info exists mixins]} {
+ set mixins {}
+ }
+ if {![info exists mixinmap]} {
+ set mixinmap {}
+ }
if {![info exists meta]} {
set meta {}
}
if {![info exists config]} {
set config {}
}
my ClassPublicApply {}
}
-
+
class_method info {which} {
my variable cache
if {![info exists cache($which)]} {
set cache($which) {}
switch $which {
@@ -1009,19 +2576,19 @@
}
}
}
return $cache($which)
}
-
+
###
# Incorporate the class's variables, arrays, and options
###
method ClassPublicApply class {
my variable config
set integrate 0
if {$class eq {}} {
- set class [info object class [self]]
+ set class [info object class [self]]
} else {
set integrate 1
}
set public [$class info public]
foreach {var value} [dict getnull $public variable] {
@@ -1071,11 +2638,11 @@
if {$script ne {}} {
{*}[string map [list %field% [list $field] %value% [list $val] %self% [namespace which my]] $script]
}
}
}
-
+
###
# topic: 3c4893b65a1c79b2549b9ee88f23c9e3
# description:
# Provide a default value for all options and
# publically declared variables, and locks the
@@ -1105,22 +2672,22 @@
my meta mixin $class
}
my ClassPublicApply $class
}
foreach class $prior {
- if {$class ni $mixins } {
+ if {$class ni $mixins } {
my meta mixout $class
}
}
}
- method mixinmap args {
+ method mixinmap args {
my variable mixinmap
set priorlist {}
foreach {slot classes} $args {
if {[dict exists $mixinmap $slot]} {
- lappend priorlist {*}[dict get $mixinmap $slot]
+ lappend priorlist {*}[dict get $mixinmap $slot]
foreach class [dict get $mixinmap $slot] {
if {$class ni $classes && [$class meta exists mixin unmap-script:]} {
if {[catch [$class meta get mixin unmap-script:] err errdat]} {
puts stderr "[self] MIXIN ERROR POPPING $class:\n[dict get $errdat -errorinfo]"
}
@@ -1163,11 +2730,11 @@
lappend classlist $class
}
}
my mixin {*}$classlist
}
-
+
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 {}} {
@@ -1191,11 +2758,11 @@
method Morph_leave {} {}
###
# Commands to perform as this object transitions into this class as a new class
###
method Morph_enter {} {}
-
+
###
# title: List which objects are forwarded as organs
###
method organ {{stub all}} {
my variable organs
Index: modules/tool/tool.test
==================================================================
--- modules/tool/tool.test
+++ modules/tool/tool.test
@@ -7,16 +7,13 @@
testsNeedTcl 8.6
testsNeedTcltest 2
testsNeed TclOO 1
-
+# use oodialect/oodialect.tcl oo::dialect
support {
- use oodialect/oodialect.tcl oo::dialect
- use dicttool/dicttool.tcl dicttool
use cron/cron.tcl cron
- use oometa/oometa.tcl oo::meta
use sha1/sha1.tcl sha1
}
testing {
useLocal tool.tcl tool
}
@@ -102,11 +99,11 @@
superclass OptionClass
property mass 1400kg
option color {default: blue}
}
-OptionClass create ObjectOptionTest1
+OptionClass create ObjectOptionTest1
OptionClass create ObjectOptionTest2 bodystyle wagon transmission standard
OptionClass2 create ObjectOptionTest3
OptionClass2 create ObjectOptionTest4 bodystyle SUV transmission cvt color white
###
@@ -414,17 +411,17 @@
$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]
@@ -467,11 +464,11 @@
}
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}
+} {::DbObj}
test tool-constructor-args-002 {Test that organs passed as options map correctly} {
OrganObject cget db
} {::DbObj}
tool::object create MorphOrganObject#1
@@ -577,16 +574,16 @@
###
# Set of tests to exercise the mixinmap system
###
tool::define MixinMainClass {
variable mainvar unchanged
-
+
method test::which {} {
my variable mainvar
return $mainvar
}
-
+
method test::main args {
puts [list this is main $method $args]
}
}
@@ -598,21 +595,21 @@
}
meta set mixin map-script: {
my test tool $class
}
meta set mixin name: {Generic Tool}
-
- method test::untool class {
+
+ method test::untool class {
my variable toolvar mainvar
set mainvar {}
- set toolvar {}
+ set toolvar {}
}
-
+
method test::tool class {
my variable toolvar mainvar
- set mainvar [$class meta get mixin name:]
- set toolvar [$class meta get mixin name:]
+ set mainvar [$class meta get mixin name:]
+ set toolvar [$class meta get mixin name:]
}
}
tool::define MixinToolA {
superclass MixinTool
@@ -655,21 +652,21 @@
###
# Coroutine tests
###
tool::define coro_example {
-
+
dict_ensemble coro_a_info coro_a_info {
initialize {
restart 0
phase 0
loop 0
event 0
idle 0
}
}
-
+
coroutine coro_a {
my coro_a_info merge {
phase 0
loop 0
event 0
@@ -700,21 +697,21 @@
}
}
}
}
}
-
+
dict_ensemble coro_b_info coro_b_info {
initialize {
restart 0
phase 0
loop 0
event 0
idle 0
}
}
-
+
coroutine coro_b {
my coro_b_info merge {
phase 0
loop 0
event 0
@@ -741,11 +738,11 @@
}
}
}
}
}
-
+
dict_ensemble coro_yodawg_info coro_yodawg_info {
initialize {
restart 0
phase 0
@@ -753,11 +750,11 @@
event 0
idle 0
yodawg 0
}
}
-
+
coroutine coro_yodawg {
my coro_yodawg_info merge {
phase 0
loop 0
event 0