Reading a line at a time from a channel is easy with gets:
while {[gets $chan line] >= 0} {
# Do something
}but what about doing the same thing with individual words? The following snippet defines functions to allow for writing the same kind of loop, just for a word at a time instead of a line:
package require Tcl 8.6
namespace eval word {
# This function, run in a coroutine, does all the work
proc read_words {chan} {
upvar [yield [info coroutine]] word
while {[::chan gets $chan line] >= 0} {
# Split on arbitrary whitespace
foreach w [regexp -all -inline {\S+} $line] {
set word $w
upvar [yield [string length $w]] word
}
}
return -1
}
# Return the name of a coroutine that reads a word at a time from
# the given channel. The coro takes one argument - the name of the
# variable to store the word in, and yields the length of the word
# or -1 at end of file. Basically like the two-argument form of
# `gets`
proc reader_from_chan {chan} {
variable counter
coroutine reader[incr counter] read_words $chan
}
proc close_chan {chan oldName newName op} {
if {$op eq "delete"} {
::chan close $chan
}
}
# Return the name of a coroutine that reads a word at a time from
# the given file (An an optional encoding argument). The coro takes
# one argument - the name of the variable to store the word in, and
# yields the length of the word or -1 at end of file. Basically like
# the two-argument form of `gets`.
proc reader_from_file {file {encoding {}}} {
set chan [open $file r]
try {
if {$encoding ne ""} {
::chan configure $chan -encoding $encoding
}
set coro [reader_from_chan $chan]
# Close the file when the coroutine finishes
trace add command $coro delete [list ::word::close_chan $chan]
return $coro
} on error {result options} {
::chan close $chan
return -options $options $result
}
}
namespace export reader_from_*
namespace ensemble create
}It works by creating coroutines that internally read a line at a time, split that line into individual words, and yield each word in turn to the caller until there is no more data left to read from the underlying channel.
Example usage:
set getw [word reader_from_file input.txt]
while {[$getw word] >= 0} {
puts $word
}HE 2021-12-30: The example has 4 lines. With 4 extra lines it is possible to achieve the same. This is useful for all who only needs the functionality in one place of a script or want to avoid the overhead of coroutines or ...
set srcfid [open input.txt r]
# fconfigure $srcfid ...
while {![eof $srcfid]} {
gets $srcfid line
foreach {-- el} [regexp -inline -all -- {([[:graph:]]+)} $line] {
puts $el
}
}
close $srcfidThe commented out line can be used in case you want to influence line ending, encoding and other things.