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