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