1## -- Tcl Module -- -*- tcl -*- 2# # ## ### ##### ######## ############# 3 4# @@ Meta Begin 5# Package coroutine 1 6# Meta platform tcl 7# Meta require {Tcl 8.6} 8# Meta license BSD 9# Meta as::author {Andreas Kupries} 10# Meta as::author {Colin McCormack} 11# Meta as::author {Donal Fellows} 12# Meta as::author {Kevin Kenny} 13# Meta as::author {Neil Madden} 14# Meta as::author {Peter Spjuth} 15# Meta summary Coroutine Event and Channel Support 16# Meta description This package provides coroutine-aware 17# Meta description implementations of various event- and 18# Meta description channel related commands. It can be 19# Meta description in multiple modes: (1) Call the 20# Meta description commands through their ensemble, in 21# Meta description code which is explicitly written for 22# Meta description use within coroutines. (2) Import 23# Meta description the commands into a namespace, either 24# Meta description directly, or through 'namespace path'. 25# Meta description This allows the use from within code 26# Meta description which is not coroutine-aware per se 27# Meta description and restricted to specific namespaces. 28# Meta description A more agressive form of making code 29# Meta description coroutine-oblivious than (2) above is 30# Meta description available through the package 31# Meta description coroutine::auto, which intercepts 32# Meta description the relevant builtin commands and changes 33# Meta description their implementation dependending on the 34# Meta description context they are run in, i.e. inside or 35# Meta description outside of a coroutine. 36# @@ Meta End 37 38# Copyright (c) 2009 Andreas Kupries 39# Copyright (c) 2009 Colin McCormack 40# Copyright (c) 2009 Donal Fellows 41# Copyright (c) 2009 Kevin Kenny 42# Copyright (c) 2009 Neil Madden 43# Copyright (c) 2009 Peter Spjuth 44 45## $Id: coroutine.tcl,v 1.1 2009/11/10 21:04:39 andreas_kupries Exp $ 46# # ## ### ##### ######## ############# 47## Requisites, and ensemble setup. 48 49package require Tcl 8.6 50 51namespace eval ::coroutine { 52 53 namespace export \ 54 create global after exit vwait update gets read await 55 56 namespace ensemble create 57} 58 59# # ## ### ##### ######## ############# 60## API. Spawn coroutines, automatic naming 61## (like thread::create). 62 63proc ::coroutine::create {args} { 64 ::coroutine [ID] {*}$args 65} 66 67# # ## ### ##### ######## ############# 68## API. 69# 70# global (coroutine globals (like thread global storage)) 71# after (synchronous). 72# exit 73# update ?idletasks? [1] 74# vwait 75# gets [1] 76# read [1] 77# 78# [1] These commands call on their builtin counterparts to get some of 79# their functionality (like proper error messages for syntax errors). 80 81# - -- --- ----- -------- ------------- 82 83proc ::coroutine::global {args} { 84 # Frame #1 is the coroutine-specific stack frame at its 85 # bottom. Variables there are out of view of the main code, and 86 # can be made visible in the entire coroutine underneath. 87 88 set cmd [list upvar "#1"] 89 foreach var $args { 90 lappend cmd $var $var 91 } 92 tailcall $cmd 93} 94 95# - -- --- ----- -------- ------------- 96 97proc ::coroutine::after {delay} { 98 ::after $delay [info coroutine] 99 yield 100 return 101} 102 103# - -- --- ----- -------- ------------- 104 105proc ::coroutine::exit {{status 0}} { 106 return -level [info level] $status 107} 108 109# - -- --- ----- -------- ------------- 110 111proc ::coroutine::vwait {varname} { 112 upvar 1 $varname var 113 set callback [list [namespace current]::VWaitTrace [info coroutine]] 114 115 # Step 1. Wait for a write to the variable, using a trace to 116 # restart the coroutine 117 118 trace add variable var write $callback 119 yield 120 trace remove variable var write $callback 121 122 # Step 2. To prevent the next section of the coroutine code from 123 # running entirely within the variable trace (*) we now use an 124 # idle handler to defer it until the trace is definitely 125 # done. This trick by Peter Spjuth. 126 # 127 # (*) At this point we are in VWaitTrace running the coroutine. 128 129 ::after idle [info coroutine] 130 yield 131 return 132} 133 134proc ::coroutine::VWaitTrace {coroutine args} { 135 $coroutine 136 return 137} 138 139# - -- --- ----- -------- ------------- 140 141proc ::coroutine::update {{what {}}} { 142 if {$what eq "idletasks"} { 143 ::after idle [info coroutine] 144 } elseif {$what ne {}} { 145 # Force proper error message for bad call. 146 tailcall ::tcl::update $what 147 } else { 148 ::after 0 [info coroutine] 149 } 150 yield 151 return 152} 153 154# - -- --- ----- -------- ------------- 155 156proc ::coroutine::gets {args} { 157 # Process arguments. 158 # Acceptable syntax: 159 # * gets CHAN ?VARNAME? 160 161 if {[llength $args] > 2} { 162 # Calling the builtin gets command with the bogus arguments 163 # gives us the necessary error with the proper message. 164 tailcall ::chan gets {*}$args 165 } elseif {[llength $args] == 2} { 166 lassign $args chan varname 167 upvar 1 $varname line 168 } else { 169 # llength args == 1 170 lassign $args chan 171 } 172 173 # Loop until we have a complete line. Yield to the event loop 174 # where necessary. During 175 176 while {1} { 177 set blocking [::chan configure $chan -blocking] 178 ::chan configure $chan -blocking 0 179 180 try { 181 ::chan gets $chan line 182 } on error {result opts} { 183 ::chan configure $chan -blocking $blocking 184 return -code $result -options $opts 185 } 186 187 if {[::chan blocked $chan]} { 188 ::chan event $chan readable [list [info coroutine]] 189 yield 190 ::chan event $chan readable {} 191 } else { 192 ::chan configure $chan -blocking $blocking 193 194 if {[llength $args] == 2} { 195 return $result 196 } else { 197 return $line 198 } 199 } 200 } 201} 202 203# - -- --- ----- -------- ------------- 204 205proc ::coroutine::read {args} { 206 # Process arguments. 207 # Acceptable syntax: 208 # * read ?-nonewline ? CHAN 209 # * read CHAN ?n? 210 211 if {[llength $args] > 2} { 212 # Calling the builtin read command with the bogus arguments 213 # gives us the necessary error with the proper message. 214 ::chan read {*}$args 215 return 216 } 217 218 set total Inf ; # Number of characters to read. Here: Until eof. 219 set chop no ; # Boolean flag. Determines if we have to trim a 220 # # \n from the end of the read string. 221 222 if {[llength $args] == 2} { 223 lassign $args a b 224 if {$a eq "-nonewline"} { 225 set chan $b 226 set chop yes 227 } else { 228 lassign $args chan total 229 } 230 } else { 231 lassign $args chan 232 } 233 234 # Run the read loop. Yield to the event loop where 235 # necessary. Differentiate between loop until eof, and loop until 236 # n characters have been read (or eof reached). 237 238 set buf {} 239 240 if {$total eq "Inf"} { 241 # Loop until eof. 242 243 while {1} { 244 set blocking [::chan configure $chan -blocking] 245 ::chan configure $chan -blocking 0 246 247 try { 248 ::chan read $chan 249 } on error {result opts} { 250 ::chan configure $chan -blocking $blocking 251 return -code $result -options $opts 252 } 253 254 if {[fblocked $chan]} { 255 ::chan event $chan readable [list [info coroutine]] 256 yield 257 ::chan event $chan readable {} 258 } else { 259 ::chan configure $chan -blocking $blocking 260 append buf $result 261 262 if {[::chan eof $chan]} { 263 ::chan close $chan 264 break 265 } 266 } 267 } 268 } else { 269 # Loop until total characters have been read, or eof found, 270 # whichever is first. 271 272 set left $total 273 while {1} { 274 set blocking [::chan configure $chan -blocking] 275 ::chan configure $chan -blocking 0 276 277 try { 278 ::chan read $chan $left 279 } on error {result opts} { 280 ::chan configure $chan -blocking $blocking 281 return -code $result -options $opts 282 } 283 284 if {[::chan blocked $chan]} { 285 ::chan event $chan readable [list [info coroutine]] 286 yield 287 ::chan event $chan readable {} 288 } else { 289 ::chan configure $chan -blocking $blocking 290 append buf $result 291 incr left -[string length $result] 292 293 if {[::chan eof $chan]} { 294 ::chan close $chan 295 break 296 } elseif {!$left} { 297 break 298 } 299 } 300 } 301 } 302 303 if {$chop && [string index $buf end] eq "\n"} { 304 set buf [string range $buf 0 end-1] 305 } 306 307 return $buf 308} 309 310# - -- --- ----- -------- ------------- 311## This goes beyond the builtin vwait, wait for multiple variables, 312## result is the name of the variable which was written. 313## This code mainly by Neil Madden. 314 315proc ::coroutine::await args { 316 set callback [list [namespace current]::AWaitSignal [info coroutine]] 317 318 # Step 1. Wait for a write to any of the variable, using a trace 319 # to restart the coroutine, and the variable written to is 320 # propagated into it. 321 322 foreach varName $args { 323 upvar 1 $varName var 324 trace add variable var write $callback 325 } 326 327 set choice [yield] 328 329 foreach varName $args { 330 #checker exclude warnShadowVar 331 upvar 1 $varName var 332 trace remove variable var write $callback 333 } 334 335 # Step 2. To prevent the next section of the coroutine code from 336 # running entirely within the variable trace (*) we now use an 337 # idle handler to defer it until the trace is definitely 338 # done. This trick by Peter Spjuth. 339 # 340 # (*) At this point we are in AWaitSignal running the coroutine. 341 342 ::after idle [info coroutine] 343 yield 344 345 return $choice 346} 347 348proc ::coroutine::AWaitSignal {coroutine var index op} { 349 if {$op ne "write"} { return } 350 set fullvar $var 351 if {$index ne ""} { append fullvar ($index) } 352 $coroutine $fullvar 353} 354 355# # ## ### ##### ######## ############# 356## Internal (package specific) commands 357 358proc ::coroutine::ID {} { 359 variable counter 360 return [namespace current]::C[incr counter] 361} 362 363# # ## ### ##### ######## ############# 364## Internal (package specific) state 365 366namespace eval ::coroutine { 367 #checker exclude warnShadowVar 368 variable counter 0 369} 370 371# # ## ### ##### ######## ############# 372## Ready 373package provide coroutine 1 374return 375