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.