fifo2 in pure Tcl

EG: Simple implementation of 'fifo2' channel driver in pure Tcl. See also memchan.

package require Tcl 8.5

namespace eval memchan {
    variable id 0

    # fifo2 handler. This is the body of a namespace ensemble.
    # Could be a tcloo class, but I want this code to run in 8.5 without extras too
    variable f2h {
        variable events ""
        variable data   ""
        variable peerinfo   ""

        proc initialize {chan mode} {
            return [list initialize finalize watch read write cget cgetall]
        }

        proc finalize {chan} {
            variable peerinfo

            set peerchan [dict get $peerinfo chan]
            if {$peerchan in [chan names]} {
                # close the peer channel
                chan close $peerchan
            }
            destroy
        }

        proc watch {chan evs} {
            variable events
            variable data

            set events $evs
            if {[string length $data] > 0 && "read" in $events} {
                chan postevent $chan read
            }
        }

        proc write {chan str} {
            variable peerinfo

            set peercmd  [dict get $peerinfo cmd]
            set peerchan [dict get $peerinfo chan]
            $peercmd add $peerchan $str
            return [string length $str]
        }

        proc read {chan count} {
            variable data
            variable events

            set result [string range $data 0 [expr {$count - 1}]]
            set data [string range $data $count end]
            if {[string length $data] > 0 && "read" in $events} {
                chan postevent $chan read
            }
            return $result
        }

        proc cget {chan opt} {
            variable data
            variable peerinfo

            switch -exact -- $opt {
                -rlength {
                    set result [string length $data]
                }
                -wlength {
                    set peercmd [dict get $peerinfo cmd]
                    set result  [$peercmd getsize]
                }
                default {
                    return -code error "bad option \"$opt\""
                }
            }
            return $result
        }

        proc cgetall {chan} {
            foreach opt {-rlength -wlength} {
                lappend result $opt [cget $chan $opt]
            }
            return $result
        }

        proc add {chan str} {
            variable events
            variable data

            append data $str
            if {[string length $data] > 0 && "read" in $events} {
                chan postevent $chan read
            }
        }

        proc setpeerinfo {peerchan peercmd} {
            variable peerinfo

            dict set peerinfo chan $peerchan
            dict set peerinfo cmd  $peercmd
        }

        proc getsize {} {
            variable data
            return [string length $data]
        }

        proc destroy {} {
            namespace delete [namespace current]
        }

        # we rely on these two lines being the last ones on the handler body
        namespace export -clear *
        namespace ensemble create
    }
}

proc memchan::fifo2 {} {
    variable f2h
    variable id

    set hd1 [namespace eval fifo2#[incr id] $f2h]
    set hd2 [namespace eval fifo2#[incr id] $f2h]
    set fd1 [chan create {read write} $hd1]
    set fd2 [chan create {read write} $hd2]
    $hd1 setpeerinfo $fd2 $hd2
    $hd2 setpeerinfo $fd1 $hd1
    # we want immediate delivery by default
    chan configure $fd1 -buffering none
    chan configure $fd2 -buffering none

    return [list $fd1 $fd2]
}

############################################################################
# testing

if {$argv0 eq [info script]} {
    proc eventHandler {fd} {
        chan puts "Reading data from $fd"
        chan puts **[chan gets $fd]**
    }
    lassign [memchan::fifo2] fd1 fd2
    chan event $fd1 readable [list eventHandler $fd1]
    chan event $fd2 readable [list eventHandler $fd2]
    
    after 1000 {
        chan puts "Writing data into $fd1"
        chan puts -nonewline $fd1 "Hello World"
    }
    after 2000 {
        chan puts "Writing data into $fd2"
        chan puts -nonewline $fd2 "Hello There !!"
    }

    after 4000 {
        chan puts "\nDisabling $fd2 events\nWriting five lines into $fd1"
        chan event $fd2 readable {}
        chan puts  $fd1 "line 1\nline 2\nline 3\nline 4\nline5"
    }
    after 5000 {
        chan puts "Enabling $fd2 events"
        chan event $fd2 readable [list eventHandler $fd2]
    }

    after 8000 exit

    if {[info commands tk] eq ""} {
        vwait forever
    }    
}