'''for ... in''' is a [wrapping commands%|%drop-in replacement%|%] for [[`[for]`]. It supports an expanded syntax for looping over [iterator protocol%|%iterators], and is fully compatible with the standard [for] command. It can be downloaded as a part of [ycl], or copied straight from this page. The [[package require]] command is not required, but indicates how to use this command as part of [ycl]. ** Synopsis ** ======none package require ycl::iter for {varname1 ...} in iterator_name1 [{varname {varnameX ... } in iterator_nameX] ... script ====== ** Description ** This drop-in replacement for [[`[for]`] is distributed with [ycl], and is also presented below. like [foreach] does with list, it can operate on multiple iterators and take an arbitrary number of items each time from each iterator. Also like [foreach], empty strings are produced as necessary for any iterator which becomes depleted before the others. note that [[[return] -code break]] within an iterator will cause the [for] loop to break, so it should be used with care. ** Changes ** [PYK] 2012-12-12: use [[[set]]] instead of [[[variable]]]. This fixes "already exists" error. Added [[`[tailcall]`] per [DKF]'s suggestion. [PYK] 2013-10-28: fix to make relative procedure names work ** Code ** newer code may be available in the [ycl] repository tcllib is only needed for ErrorInfoAsCaller, which is trivial to remove. ====== proc for args { if {[lindex $args 1] eq "in"} { if {[llength $args] % 3 != 1} { return -code error "wrong # of arguments" } set iters [dict create] set vars [dict create] while {[llength $args] > 1} { set args [lassign $args[set args {}] varnames /dev/null iter] if {$iter ne {} \ && [set iter [uplevel [list namespace which $iter]]] eq {}} { return -code error \ "no such iterator: $iter. Was \[foreach] was intended?" } dict set iters $iter 1 dict set vars $varnames $iter } set body [lindex $args[set args {}] 0] while {[dict size $iters]} { set newvals[set newvals {}] [dict create] dict for {varnames iter} $vars { foreach varname $varnames { if {[namespace which $iter] eq {}} { dict unset iters $iter dict set newvals $varname {} } else { dict set newvals $varname [uplevel [list $iter]] if {[namespace which $iter] eq {}} { dict unset iters $iter dict set newvals $varname {} } } } } if {[dict size $iters]} { dict for {varname val} $newvals { uplevel [list set $varname $val] } set code [catch {uplevel $body} result] switch -exact -- $code { 0 {} 1 { return -errorinfo [ ::control::ErrorInfoAsCaller uplevel for...in] \ -errorcode $::errorCode -code error $result } 3 { # FRINK: nocheck return } 4 {} default { return -code $code $result } } } else { break } } } else { if {[namespace which ::tailcall] ne {}} { tailcall for_orig {*}$args } else { uplevel [list [namespace current]::for_orig {*}$args] } } } ====== ---- This older version didn't take care to leave variables in their last-used state ====== package require control namespace import ::control::ErrorInfoAsCaller rename ::for for_orig interp alias {} ::for {} [namespace current]::for proc for_old args { if {[lindex $args 1] eq "in"} { if {[llength $args] % 3 != 1} { return -code error "wrong # of arguments" } set assigns {} set assign_template { if {[namespace which $iter] eq {}} { variable $varname {} } else { variable $varname [$iter] if {[namespace which $iter] eq {}} { variable $varname {} } } } set conditions {} set condition_template {[namespace which $iter] ne {}} while {[llength $args] > 1} { set args [lassign $args[unset args] varnames /dev/null iter] if {$iter ne {} && [uplevel [::list namespace which $iter]] eq {}} { return -code error "no such iterator: $iter. Maybe \[foreach] was intended?" } foreach varname $varnames { lappend assigns [string map [list \$iter [list $iter] \$varname [list $varname]] \ $assign_template] } lappend conditions [string map [list \$iter [list $iter]] $condition_template] } set assigns [join $assigns \n] set args [join $args \n] set code [catch {uplevel "while {[join $conditions { || }]} { $assigns if {!([join $conditions { || }])} { break } $args }"} result] switch -exact -- $code { 0 {} 1 { return -errorinfo [::control::ErrorInfoAsCaller uplevel for...in] \ -errorcode $::errorCode -code error $result } 3 { # FRINK: nocheck return } 4 {} default { return -code $code $result } } } else { if {[namespace which ::tailcall] ne {}} { tailcall for_orig {*}$args } else { uplevel [list [namespace current]::for_orig {*}$args] } } } ====== ** Example: ** ====== namespace eval basket { set items [list apples oranges bananas kiwis pears] set current 0 proc next {} { variable items variable current if {$current < [llength $items]} { return [lindex $items [expr {[incr current]-1}]] } rename [info level 0] {} } } set result [list] for fruit in basket::next { lappend result $fruit } puts $result ====== produces ======none apples oranges bananas kiwis pears ====== ---- Example: ====== set result [list] for {key val} in [coroutine students apply {{} { yield [info coroutine] set db { Jack 97 Sally 89 Bob 83 Jill 77 John 72 } set index -1 foreach {name score} $db { yield $name yield $score } }}] prize in [coroutine prizes apply {{} { yield [info coroutine] foreach item { first second third } { yield $item } }}] { lappend result $key $val $prize } puts $result ====== produces: ======none Jack 97 first Sally 89 second Bob 83 third Jill 77 {} John 72 {} ====== ** See Also *** * [lcomp]: List comprehension package that also uses "`for` ... `in`" notation <> Category Control Structure