Typing out a "for" loop is just getting to be a drudge. Eventually, I decided to just tighten it up a bit.
The syntax is "do var range script" where "range" is specified like 1..10 - and you can add an 'x' and a step if needed: 1..10x2. It will also count backwards if the starting value is higher than the ending one, changing the sign of the step value for you.
do i 0..10 {puts "value of i is $i"}
do j 10..0x2 {puts "value of j is $j"}"break" and "continue" work as expected, since it's just translated to the equivalent "for" loop
I use split to handle the parsing and ran into a nasty limitation I didn't find at all obvious. Split splits on any of the split characters, it has no concept of using some character combination to split. So I fixed that, too, adding a means of translating multiple character strings in a list as a way to split. I called it "chop". It has a limit of no more than 9 different strings to use for the operation since I could only figure out 9 regular characters to map to that I thought wouldn't interfere.
Ideally, there should be a "parse" function to handle string->list conversions. In the case of ranges, something like "parse list {.. x} from to by" would handle parsing the range easily.
proc parse {str markers args} {
set l [chop $str $markers]
set l# [llength $l]
each item $l {> &var; set var $item; incr l# -1}
while {${l#}>=0} {> &arg; set arg ""; incr l# -1}
}Which uses:
proc > {args} {
upvar args argl
while {[llength $args]>0} {
set arg [lindex $args 0]; set args [lrange $args 1 end]
set val [lindex $argl 0]; set argl [lrange $argl 1 end]
if {[str1st $arg] eq "&"} {
set arg [string range $arg 1 end]
uplevel upvar $val $arg
} else {
upvar $arg local
set local $val
}
}
}and "do" itself. This version now runs entirely in the enclosing scope, so you can access the counter without difficulty.
proc do {var range loop} { # syntax x..y<,x1..y1><xStep>
if {[scan $range {%d..%dx%d} from to by] < 3} {set by 1}
if {$to<$from} {if {$by > 0} {set by -$by}; set cond >=} else {set cond "<"}
set script "for \{! $var $from\} \{\$$var $cond $to\} \{incr $var $by\} \{$loop\}"
uplevel $script
}
# fixing split - won't split using string of chars as markers
set splitchars {! @ # % ^ * | ~ `}
proc chop{list marks} {
set sp $::splitchars; set map {}
foreach mark $marks {set map "$map $mark [hd sp]"}
set list [string map $map $list]
return [split $list $::splitchars]
}EF: Where can we find map, ^^, each and ^?
Actually, they're built-ins. Forgot to remove my "smithisms" when I posted. Fixed above. I hope.
The command can also be written without special parsing commands:
proc do {varName range loop} {
upvar 1 $varName var
if {[scan $range {%d..%dx%d} from to by] < 3} {
set by 1
}
if {$to < $from} {
if {$by > 0} {
set by -$by
}
set cond >=
} else {
set cond <
}
for {set var $from} [concat \$var $cond $to] {incr var $by} {uplevel 1 $loop}
}ak - 2017-05-02 19:41:52
Tcllib provides a splitx command which is similar to chop. See textutil::split
ak - 2017-05-02 19:47:21
Question about the range syntax, i.e. 0..10. What where the reasons it was chosen ? I can currently see two disadvantages over specifying the range in two arguments, i.e. do 0 10 ..., which are related:
LarrySmith: True. I am probably channeling my childhood experience as a little baby programmer starting with Pascal... =) Although Pascal actually used "to" or "downto" keywords for the step, but they were optional. This way is more compact (but a little slower as noted) with the x$by parameter being parsed out - the step is often left off, whereas with parameters the step cannot be omitted since the last parameter must be the script. I supppose it might be put at the end, after the script, but for a large loop, putting the little afterthought of the step maybe on the next page just seems...odd. I was less concerned about speed and more about shrinking more utilities down to one-liners. I also alias set to "!" (which led to the idea of using the pencil in Unicode Commands. Hey, I never denied I was eccentric. ;)
ak - 2017-05-03 17:20:55
Fair enough.
I would most likely do(sic!) this via:
See example below, which integrates the last 3 items of the list into one switch command.
proc do {var from to args} {
switch -exact -- [llength $args] {
1 {
# ... compute default step ...
}
2 {
set step [lindex $args 0]
}
default {
error ...
}
}
set script [lindex $args end]
...
}Could be done with keywords as well, similar to processing of leading options, just without dashes.