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