Coronet

# Coronet - a simple skeleton for coroutine networking
# CMcC and MS 2009-01-14

namespace eval Coronet {
    # if you are adventurous, you can redefine yield in terms of ::yield
    # proc yield {{retval ""}} {...}

    variable maxline 1024

    # provide access to variables defined in top level of coroutine
    proc corovars {args} {
        foreach n $args {lappend v $n $n}
        uplevel 1 [list upvar #1 {*}$v]
    }

    # terminate coroutine
    proc terminate {args} {
        return -level [info level] $args        ;# terminate consumer
    }

    # coroutine-enabled gets
    proc get {socket {reason ""}} {
        variable maxline
        set result [yield]
        set line ""
        while {[chan gets $socket line] == -1 && ![chan eof $socket]} {
            set result [yield]
            if {$maxline && [chan pending input $socket] > $maxline} {
                error "line length greater than $maxline"
            }
        }

        if {[chan eof $socket]} {
            terminate $reason        ;# check the socket for closure
        }

        # return the line
        return $line
    }

    # coroutine-enabled read
    proc read {socket size} {
        # read a chunk of $size bytes
        set chunk ""
        while {$size && ![chan eof $socket]} {
            set result [yield]
            set chunklet [chan read $socket $size]
            incr size [expr {-[string length $chunklet]}]
            append chunk $chunklet
        }
        
        if {[chan eof $socket]} {
            terminate entity        ;# check the socket for closure
        }
        
        # return the chunk
        return $chunk
    }

    proc do {args} {
        set x 1        ;# x is available via [corovars x]
        .... this is where your network code goes
    }

    proc init {name socket args} {
        set coro [coroutine $name ::Coronet::do {*}$args]
        fileevent $socket readable [list $coro INCOMING]
        return $coro
    }

    namespace export -clear *
    namespace ensemble create -subcommands {}
}

Note this uses the very important facility corovars.


AMG: A greatly simplified version of the above code appears in Wibble.


A slightly different form by kbk, snatched from the pastebin

proc cgets {chan args} {
    if {[llength $args] == 1} {
        upvar 1 [lindex $args 0] line
    } elseif {[llength $args] > 1} {
        return -code error "wrong \# args, should be \"[lindex [info level 0] 0] channel ?variable?\""
    }
    while {1} {
        set blocking [fconfigure $chan -blocking]
        fconfigure $chan -blocking 0
        set status [catch {gets $chan line} result opts]
        if {$status} {
            fconfigure $chan -blocking $blocking
            return -code $status -options $opts
        } elseif {[fblocked $chan]} {
            fileevent $chan readable [list [info coroutine]]
            yield
            fileevent $chan readable {}
        } else {
            fconfigure $chan -blocking $blocking
            if {[llength $args] == 1} {
                return $result
            } else {
                return $line
            }
        }
    }
}

coroutine foo apply {{} {
    while {[cgets stdin line] >= 0} {
        puts $line
    }
    set ::finished 1
}}

vwait finished