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" loopI 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:52Tcllib provides a splitx command which is similar to chop. See textutil::split
ak - 2017-05-02 19:47:21Question 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:
- The range is stored as string and has to be parsed every time into the two ints. Using two arguments the conversion to int happens once and is stored in the Tcl_Obj.
- When iterating over a dynamic range the it has to be constructed as string ("${from}..${to}"), only to be deconstructed immediately again in the procedure.
ak - 2017-05-03 17:20:55Fair enough.I would most likely do(sic!) this via:
- Use args after the fixed part (var from to).
- Check the number of arguments (1 or 2, i.e. with and without step)
- Take the last argument as the script
- Take the possible left-over as the optional step, or compute a default step.
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.