Version 9 of More compact "for" loop

Updated 2017-04-27 10:28:50 by foo

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:

  proc do {var range loop} {
    lassign [chop $range ".. x"] from to by
    set cond <
    if {$by eq ""} {set by 1}
    if {$to<$from} {set by [= {-$by}]; set cond >=}
    set script "for \{set $var $from\} \{\$$var$cond$to\} \{incr $var $by\} \{"
    set script "${script}$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 {var range loop} {
    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 <
    }
    uplevel 1 [list for [list set $var $from] \$$var$cond$to [list incr $var $by] $loop]
}