ycl

Artifact [cc436ae405]
Login

Artifact [cc436ae405]

Artifact cc436ae405d570207775bcfc4cd69f0d81c79a51:


#! /bin/env tclsh

package require {ycl proc}
variable proc [yclprefix]::proc
namespace import ${proc}::checkargs
package require ycl::yobj
variable yobj [yclprefix]::yobj

namespace eval doc {}

variable doc::open {
	description {
		open a new string I/O channel
	}
	args {
		access {
			description {
				same as the access argument of [open]
				if the "data" option is supplied the file will pass the
				criteria of already existing for access modes that require that
				criteria.
			}
			validate {
				$access in [list r r+ w w+ a a+]
			}
			default {
				set access [list r]
			}
		}
		attach {
			description {
				specify a channel to attach to.
			}
			default {}
		}
		size {
			description {
				maximum size limit for file, in bytes
			}
			default {
				set size -1
			}
		}
		pipe {
			description {
				specifies whether a channel should be treated as a pipe, e.g.
				no backwards, and discard data that has been read
			}
			validate {
				[string is boolean $pipe]
			}
			default {
				set pipe 0
			}

		}
		data {
			description {
				the initial string data value of the channel
			}
			default {}
		}
	}
}
proc open args {
	variable yobj
	variable buffermap
	variable chancount
	variable channels
	variable names
	if {[catch {checkargs doc::open} catchres catchopt]} {
		return -options $catchopt $catchres
	}
	set mode read
	set new 1
	set truncate 0
	set append 0
	set startatend 0
	switch -glob -- $access {
		r {
			set new 0
		}
		r+ {
			set mode [list read write]
			set new 0
		}
		w {
			set mode write
			set truncate 1
		}
		w+ {
			set mode [list read write]
			set truncate 1
		}
		a {
			set mode write
			set append 1
		}
		a+ {
			set mode [list read write]
			set startatend 1
		}
	}

	set state [$yobj object]
	set chan [chan create $mode [namespace current]]
	set channels($chan) $state
	set ${state}::chan $chan
	if {![info exists attach]} {
		set attach $chan
	}
	set ${state}::attach $attach
	namespace eval $state [list namespace upvar [namespace current] buffers::$attach data]
	if {[info exists data]} {
		if {$truncate} {
			set ${state}::data $data[set data {}]
		} else {
			if {[info exists ${state}::data]} {
				return -code error "data provided when buffer already exists for channel $attach"
			} else {
				set ${state}::data $data[set data {}]
			}
		}
	} elseif {[info exists ${state}::data]} {
		if {$truncate} {
			set ${state}::data {}
		}
	} else {
		if {$new} {
			set ${state}::data {}
		} else {
			finalize $chan
			if {$attach eq $chan} {
				return -code error "access is \"$access\", but no variable name was found and no data was found.  Either use \"data\" option or \"name\" an existing variable"
			} else {
				return -code error "file does not exist: $chan"
			}
		}
	}

	set ${state}::cursor 0
	if {$startatend} {
		set ${state}::cursor [string length [set ${state}::data]]
	}
	set ${state}::mode $mode
	set ${state}::append $append 
	set ${state}::blocking 1
	set ${state}::pipe $pipe
	variable ${state}::watch [dict create]
	variable ${state}::rythm [set [namespace current]::rythm]
	set ${state}::size $size
	lappend buffermap($attach) $chan 
	namespace eval $state [list variable mode $mode]
	return $chan 
}

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

proc finalize {chan} {
	variable channels
	variable buffermap
	variable buffer
	variable channels
	namespace upvar $channels($chan) attach attach 
	if {![catch {
	set buffermap($attach) [lsearch -exact -inline -all -not $buffermap($attach) $chan]
	}]} {
		if {[llength $buffermap($attach)] == 0} {
			unset buffers::$attach
		}
	}
	catch {unset channels($chan)}
}

proc watch {chan eventspec} {
	variable channels
	namespace upvar $channels($chan) watch watch rythm rythm
	if {$eventspec eq {}} {
		foreach key [dict keys $watch] {
			after cancel [dict get $watch $key]
			dict unset keys $watch
		}
		return
	}

	if {[dict exist $watch $eventspec]} {
		after cancel [dict get $watch $eventspec]
		dict unset keys $watch
	}
	dict set watch $eventspec [after $rythm \
		[namespace code [list watch $chan $eventspec]]]
	if {[${eventspec}able $chan]} {
		chan postevent $chan $eventspec
	}
}

proc readable chan {
	variable channels
	namespace upvar $channels($chan) cursor cursor data data
	if {$cursor <[string length $data] || ![llength [writers $chan]]} {
		return 1 
	} else {
		return 0 
	}
}

proc writeable chan {
	variable channels
	namespace upvar $channels($chan) cursor cursor data data
	return 1
}

### optional commands ###
proc read {chan count} {
	#this module assumes that the underlying data is in a single-byte encoding
	variable channels
	namespace upvar $channels($chan) blocking blocking cursor cursor data data \
		attach attach rythm rythm
	set newcursor [expr {min($cursor + $count ,[string length $data])}]
	set res [string range $data $cursor [expr {$newcursor - 1}]]
	if {$res eq {} && [info exists name]} {
		variable buffermap
		if {[llength [writers $chan]]} {
			if {$blocking} {
				while {[set res [string range $data $cursor [expr {$newcursor - 1}]]] \
					eq {}} {
					after $rythm
				}
			} else {
				#a writer exists.  Channel is still open
				return -code error EAGAIN
			}
		}
	}
	if {[ispipe $chan]} {
		set data [string range $data $newcursor end]
		set cursor 0
	} else {
		set cursor $newcursor
	}
	return $res
}

proc write {chan newdata} {
	variable channels
	namespace upvar $channels($chan) cursor cursor data data size size append append
	if {$append} {
		set cursor [string length $data]
	}
	set length [string length $newdata]
	if {$length} {
		set dlength [string length $data]
		if {[ispipe $chan]} {
			set cursor $dlength
		}
		set last [expr {$cursor + $length -1}]
		if {$size > -1 && $last >= $size} {
			if {[ispipe $chan]} {
				return -code error EAGAIN
			} else {
				return -code error \
					"write failed: would exceed file size limitation of $size bytes"
			}
		}
		if {$cursor > $dlength} {
			set pad [expr {$cursor - $dlength}]
			append data [string repeat \0 $pad]
		}
		
		if {$cursor < $dlength} {
			set data [string replace $data[set data {}] $cursor $last $newdata]
		} else {
			append data $newdata
		}
		set cursor $last
	}
	return $length
}

proc seek {chan offset base} {
	if 0 {
		seeking by itself does not add any bytes to a stream, but writing to a
		point beyond the end of the stream results in null bytes being added to
		pad the stream up until that point
	}
	variable channels
	namespace upvar $channels($chan) cursor cursor data data 
	switch $base {
		start {
			set base 0
		}
		current {
			set base $cursor
		}
		end {
			set base [string length $data]
		}
	}
	set newcursor [expr {$base + $offset}]
	if {[ispipe $chan]} {
		return -code error "channel $chan is a pipe.  Cannot seek"
	} else {
		set cursor [expr {$base + $offset}]
	}
	return $cursor
}

proc configure {chan option value} {
	variable channels
	case $option {
		-size {
			namespace upvar $channels($chan) size size
			set size $value
		}
		-mode {
			namespace upvar $channels($chan) mode mode
			set mode $value
		}
		-pipe {
			namespace upvar $channels($chan) pipe pipe
			set pipe [expr {!!$value}]
		}
		rythm {
			namespace upvar $channels($chan) rythm rythm
			set rythm $value
		}
		* {
			return -code error "no such configuration option for $chan: $option"
		}
	}
}

proc cget {chan option} {
	variable channels
	switch $option {
		pipe -
		size -
		rythm -
		attach {
			namespace upvar $channels($chan) $option $option
			set $option
		}
		default {
			return -code error "invalid option:  $option"
		}
	}
}

proc cgetall {chan} {
	variable channels
	namespace upvar $channels($chan) size size attach attach
	return [list attach attach size $size]
}

proc blocking {chan mode} {
	variable channels
	namespace upvar $channels($chan) blocking blocking 
	set blocking $mode
}

proc ispipe chan {
	variable channels
	namespace upvar $channels($chan) pipe pipe
	return $pipe
}

proc writers chan {
	set res [list]
	variable channels
	variable buffermap
	namespace upvar $channels($chan) attach attach mode mode
	foreach other $buffermap($attach)  {
		namespace upvar $channels($other) mode mode chan otherchan
		if {$otherchan ne $chan && "write" in $mode} {
			lappend res $otherchan
		}
	}
	return $res
}


variable buffermap
namespace eval buffers {}
variable channels
variable chancount
variable rythm 5

### private functions ###
proc K {x y} {set x}