make-closure-proc input_iterator {_type _target} {
variable type $_type
variable target {$_target}
variable pos -1
variable end -1
} {
variable type;
switch -glob -- $type {
-str* {
set end [string length $target]
set currentbody {
return [string index $target $pos]
}
set nextbody {
incr pos
if {$pos >= $end} {
return {}
}
return [string index $target $pos]
}
}
-li* {
set end [llength $target]
set currentbody {
return [lindex $target $pos]
}
set nextbody {
incr pos
if {$pos >= $end} {
return {}
}
return [lindex $target $pos]
}
}
-chan* {
set line ""
set end {}
set currentbody {
return $line
}
set nextbody {
if {[eof $target]} {
return {}
}
return [set line [gets $target]]
}
}
-rang* {
set end [lindex $target 1]
set pos [expr {[lindex $target 0] - 1}]
set currentbody {
return $pos
}
set nextbody {
if {$pos >= $end} {
return {}
}
return [incr pos]
}
}
default {
error "input_iterator: bad option \"$type\": must be -string, -list, -channel or -range"
}
}
proc get {{_next {}}} \
"variable target; variable pos; variable end;
if {\$_next != {}} {$nextbody} else {$currentbody}"
return [namespace current]::get
}
make-closure-proc output_iterator {_type _target} {
variable type $_type
variable target $_target
} {
variable type;
switch -glob -- $type {
-str* {
set nextbody {
append target_ref $val
}
}
-li* {
set nextbody {
lappend target_ref $val
}
}
-chan* {
set nextbody {
puts $target $val
}
}
default {
error "output_iterator: bad option \"$type\": must be -string, -list, or -channel"
}
}
proc out {val} \
"variable target; upvar \$target target_ref; $nextbody"
return [namespace current]::out
}Here are a few examples:
set i1 [input_iterator -string "hello world"]
set i2 [input_iterator -list {hello world}]
set i3 [input_iterator -channel [open somefile.txt r]]
set i4 [input_iterator -range {1 100}]
proc count {iter} {
set count 0
while {[$iter next] != {}} {
incr count
}
return $count
}
count $i1 ;# returns 11
count $i2 ;# returns 2
count $i3 ;# returns the number of characters in the file.
count $i4 ;# returns 100Now, let's define some generic functions!
proc copy {in out} {
while {[set v [$in next]] != {}} {
uplevel [list $out $v]
}
}
proc copy_if {in out cmd} {
while {[set v [$in next]] != {}} {
if {[$cmd $v]} {
uplevel [list $out $v]
}
}
}
proc find {in what} {
while {[set v [$in next]] != {}} {
if {$v == $what} {
return $v
}
}
return {}
}
proc map {in out cmd} {
while {[set v [$in next]] != {}} {
uplevel $out [eval $cmd $v]
}
}Here are examples on how to (ab)use them:
proc double {x} {expr {$x * 2}}
set out [output_iterator -chan stdout]
map [input_iterator -range {0 10}] $out double
map [input_iterator -list {1 2 3 4 5}] $out double
map [input_iterator -chan stdin] $out double ;# double every number entered on the console (1 per line)
map [input_iterator -string "12345"] $out double ;# this doesn't do what you may expect ;-)The map proc looks better using lambda:
map [input_iterator -range {0 10}] $out [lambda x {expr {$x * 2}}]copy is particularly interesting:
copy [input_iterator -chan $file1] [output_iterator -chan $file2]; # copy 1 file into another
copy [input_iterator -chan $file1] [output_iterator -list l]; # copy a file into a list
copy [input_iterator -range {0 10000}] [output_iterator -chan stdout]; # output the range
# copy a list into a file.
#
set ofile [open /tmp/input.dat w]
copy [input_iterator -list {
"# this is a test file with comments"
"First line"
"Second line"
" # another comment"
"Third line."}] [output_iterator -chan $ofile]
close $ofile
# Now, let's make a copy but without the comment lines
#
set ifile [open /tmp/input.dat r]
set ofile [open /tmp/output.dat w]
copy_if [input_iterator -chan $ifile] \
[output_iterator -chan $ofile] \
[lambda v { return [expr {![string match "\#*" [string trim $v]] }]}]
close $ifile
close $ofileTweaks, bug fixes and more interesting examples added 9/03/02 -- Todd CoramDKF: 8.6 has coroutines, making iterators simple.
proc iter {from to} {
set name iter[incr ::iterCounter]
tcl::unsupported::coroutine $name apply {{a b} {
tcl::unsupported::yield
for {set i $a} {$i <= $b} {incr i} {
tcl::unsupported::yield $i
}
return -code break
}} $from $to
return $name
}
set it [iter 1 10]
while 1 {
puts "got [$it]"
}
