summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/coroutine/coro_auto.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/coroutine/coro_auto.tcl')
-rw-r--r--tcllib/modules/coroutine/coro_auto.tcl316
1 files changed, 316 insertions, 0 deletions
diff --git a/tcllib/modules/coroutine/coro_auto.tcl b/tcllib/modules/coroutine/coro_auto.tcl
new file mode 100644
index 0000000..e1e87f5
--- /dev/null
+++ b/tcllib/modules/coroutine/coro_auto.tcl
@@ -0,0 +1,316 @@
+## -- Tcl Module -- -*- tcl -*-
+# # ## ### ##### ######## #############
+
+# @@ Meta Begin
+# Package coroutine::auto 1.1.2
+# Meta platform tcl
+# Meta require {Tcl 8.6}
+# Meta require {coroutine 1.1}
+# Meta license BSD
+# Meta as::author {Andreas Kupries}
+# Meta as::origin http://wiki.tcl.tk/21555
+# Meta summary Coroutine Event and Channel Support
+# Meta description Built on top of coroutine, this
+# Meta description package intercepts various builtin
+# Meta description commands to make the code using them
+# Meta description coroutine-oblivious, i.e. able to run
+# Meta description inside and outside of a coroutine
+# Meta description without changes.
+# @@ Meta End
+
+# Copyright (c) 2009-2014 Andreas Kupries
+
+## $Id: coro_auto.tcl,v 1.3 2011/11/17 08:00:45 andreas_kupries Exp $
+# # ## ### ##### ######## #############
+## Requisites, and ensemble setup.
+
+package require Tcl 8.6
+package require coroutine
+
+namespace eval ::coroutine::auto {}
+
+# # ## ### ##### ######## #############
+## API implementations. Uses the coroutine commands where
+## possible.
+
+proc ::coroutine::auto::wrap_global {args} {
+ if {[info coroutine] eq {}} {
+ tailcall ::coroutine::auto::core_global {*}$args
+ }
+
+ tailcall ::coroutine::util::global {*}$args
+}
+
+# - -- --- ----- -------- -------------
+
+proc ::coroutine::auto::wrap_after {delay args} {
+ if {
+ ([info coroutine] eq {}) ||
+ ([llength $args] > 0)
+ } {
+ # We use the core builtin when called from either outside of a
+ # coroutine, or for an asynchronous delay.
+
+ tailcall ::coroutine::auto::core_after $delay {*}$args
+ }
+
+ # Inside of coroutine, and synchronous delay (args == "").
+ tailcall ::coroutine::util::after $delay
+}
+
+# - -- --- ----- -------- -------------
+
+proc ::coroutine::auto::wrap_exit {{status 0}} {
+ if {[info coroutine] eq {}} {
+ tailcall ::coroutine::auto::core_exit $status
+ }
+
+ tailcall ::coroutine::util::exit $status
+}
+
+# - -- --- ----- -------- -------------
+
+proc ::coroutine::auto::wrap_vwait {varname} {
+ if {[info coroutine] eq {}} {
+ tailcall ::coroutine::auto::core_vwait $varname
+ }
+
+ tailcall ::coroutine::util::vwait $varname
+}
+
+# - -- --- ----- -------- -------------
+
+proc ::coroutine::auto::wrap_update {{what {}}} {
+ if {[info coroutine] eq {}} {
+ tailcall ::coroutine::auto::core_update {*}$what
+ }
+
+ # This is a full re-implementation of mode (1), because the
+ # coroutine-aware part uses the builtin itself for some
+ # functionality, and this part cannot be taken as is.
+
+ if {$what eq "idletasks"} {
+ after idle [info coroutine]
+ } elseif {$what ne {}} {
+ # Force proper error message for bad call.
+ tailcall ::coroutine::auto::core_update $what
+ } else {
+ after 0 [info coroutine]
+ }
+ yield
+ return
+}
+
+# - -- --- ----- -------- -------------
+
+proc ::coroutine::auto::wrap_gets {args} {
+ # Process arguments.
+ # Acceptable syntax:
+ # * gets CHAN ?VARNAME?
+
+ if {[info coroutine] eq {}} {
+ tailcall ::coroutine::auto::core_gets {*}$args
+ }
+
+ # This is a full re-implementation of mode (1), because the
+ # coroutine-aware part uses the builtin itself for some
+ # functionality, and this part cannot be taken as is.
+
+ if {[llength $args] == 2} {
+ # gets CHAN VARNAME
+ lassign $args chan varname
+ upvar 1 $varname line
+ } elseif {[llength $args] == 1} {
+ # gets CHAN
+ lassign $args chan
+ } else {
+ # not enough, or too many arguments (0, or > 2): Calling the
+ # builtin gets command with the bogus arguments gives us the
+ # necessary error with the proper message.
+ tailcall ::coroutine::auto::core_gets {*}$args
+ }
+
+ # Loop until we have a complete line. Yield to the event loop
+ # where necessary. During
+
+ while {1} {
+ set blocking [::chan configure $chan -blocking]
+ ::chan configure $chan -blocking 0
+
+ try {
+ set result [::coroutine::auto::core_gets $chan line]
+ } on error {result opts} {
+ ::chan configure $chan -blocking $blocking
+ return -code $result -options $opts
+ }
+
+ if {[::chan blocked $chan]} {
+ ::chan event $chan readable [list [info coroutine]]
+ yield
+ ::chan event $chan readable {}
+ } else {
+ ::chan configure $chan -blocking $blocking
+
+ if {[llength $args] == 2} {
+ return $result
+ } else {
+ return $line
+ }
+ }
+ }
+}
+
+# - -- --- ----- -------- -------------
+
+proc ::coroutine::auto::wrap_read {args} {
+ # Process arguments.
+ # Acceptable syntax:
+ # * read ?-nonewline ? CHAN
+ # * read CHAN ?n?
+
+ if {[info coroutine] eq {}} {
+ tailcall ::coroutine::auto::core_read {*}$args
+ }
+
+ # This is a full re-implementation of mode (1), because the
+ # coroutine-aware part uses the builtin itself for some
+ # functionality, and this part cannot be taken as is.
+
+ if {[llength $args] > 2} {
+ # Calling the builtin read command with the bogus arguments
+ # gives us the necessary error with the proper message.
+ ::coroutine::auto::core_read {*}$args
+ return
+ }
+
+ set total Inf ; # Number of characters to read. Here: Until eof.
+ set chop no ; # Boolean flag. Determines if we have to trim a
+ # # \n from the end of the read string.
+
+ if {[llength $args] == 2} {
+ lassign $args a b
+ if {$a eq "-nonewline"} {
+ set chan $b
+ set chop yes
+ } else {
+ lassign $args chan total
+ }
+ } else {
+ lassign $args chan
+ }
+
+ # Run the read loop. Yield to the event loop where
+ # necessary. Differentiate between loop until eof, and loop until
+ # n characters have been read (or eof reached).
+
+ set buf {}
+
+ if {$total eq "Inf"} {
+ # Loop until eof.
+
+ while {1} {
+ set blocking [::chan configure $chan -blocking]
+ ::chan configure $chan -blocking 0
+
+ try {
+ set result [::coroutine::auto::core_read $chan]
+ } on error {result opts} {
+ ::chan configure $chan -blocking $blocking
+ return -code $result -options $opts
+ }
+
+ if {[::chan blocked $chan]} {
+ ::chan event $chan readable [list [info coroutine]]
+ yield
+ ::chan event $chan readable {}
+ } else {
+ ::chan configure $chan -blocking $blocking
+ append buf $result
+
+ if {[::chan eof $chan]} {
+ ::chan close $chan
+ break
+ }
+ }
+ }
+ } else {
+ # Loop until total characters have been read, or eof found,
+ # whichever is first.
+
+ set left $total
+ while {1} {
+ set blocking [::chan configure $chan -blocking]
+ ::chan configure $chan -blocking 0
+
+ try {
+ set result [::coroutine::auto::core_read $chan $left]
+ } on error {result opts} {
+ ::chan configure $chan -blocking $blocking
+ return -code $result -options $opts
+ }
+
+ if {[::chan blocked $chan]} {
+ ::chan event $chan readable [list [info coroutine]]
+ yield
+ ::chan event $chan readable {}
+ } else {
+ ::chan configure $chan -blocking $blocking
+ append buf $result
+ incr left -[string length $result]
+
+ if {[::chan eof $chan]} {
+ ::chan close $chan
+ break
+ } elseif {!$left} {
+ break
+ }
+ }
+ }
+ }
+
+ if {$chop && [string index $buf end] eq "\n"} {
+ set buf [string range $buf 0 end-1]
+ }
+
+ return $buf
+}
+
+# # ## ### ##### ######## #############
+## Internal. Setup.
+
+::apply {{} {
+ # Replaces the builtin commands with coroutine-aware
+ # counterparts. We cannot use the coroutine commands directly,
+ # because the replacements have to use the saved builtin commands
+ # when called outside of a coroutine. And some (read, gets,
+ # update) even need full re-implementations, as they use the
+ # builtin command they replace themselves to implement their
+ # functionality.
+
+ foreach cmd {
+ global
+ exit
+ after
+ vwait
+ update
+ } {
+ rename ::$cmd [namespace current]::core_$cmd
+ rename [namespace current]::wrap_$cmd ::$cmd
+ }
+
+ foreach cmd {
+ gets
+ read
+ } {
+ rename ::tcl::chan::$cmd [namespace current]::core_$cmd
+ rename [namespace current]::wrap_$cmd ::tcl::chan::$cmd
+ }
+
+ return
+} ::coroutine::auto}
+
+# # ## ### ##### ######## #############
+## Ready
+
+package provide coroutine::auto 1.1.3
+return