Updated 2016-09-06 07:17:34 by pooryorick

See do...until in Tcl

See also while...wend in Tcl

There is a difference between do..until and while..wend. By do..until the statement is executed at least once

We can simulate such behaviour in Tcl by:
while 1 {
    # ..statement
    if {!expression} break
}

DKF: Try this:
proc do {body keyword expression} {
    if {$keyword eq "while"} {
        set expression "!($expression)"
    } elseif {$keyword ne "until"} {
        return -code error "unknown keyword \"$keyword\": must be until or while"
    }
    set condition [list expr $expression]
    while 1 {
        uplevel 1 $body
        if {[uplevel 1 $condition]} {
            break
        }
    }
    return
}

fredderic: How about this version:
proc do {command while test} {
    if { $while eq "until" } {
        set test "!($test)"
    } elseif { $while ne "while" } {
        error "unknown keyword \"$while\": must be while or until"
    }
    set code [catch {uplevel 1 $command} result]
    switch -exact $code {
    0 - 4 {} 3 {return}
    default {return -code $code $result}
    }
    set code [catch {uplevel 1 [list while ${bool}($test) $command]} result]
    return -code $code $result
}

It's a version I wrote quite some time ago, augmented with DKFs somewhat neater keyword check. The advantage is that this one runs the first pass manually, and then runs the rest of the loop directly in-place. I think the catch handling may need a little attention, and I personally like returning the result of the last body iteration.

wdb This is my one which allows to use while as well as self-defined constructions such as until:
proc ::do {code while cond} {
    # nicht-abweisende Schleife
    uplevel 1 $code
    uplevel 1 [list $while $cond $code]
}

AMG: Here's wdb's example, rewritten to use [tailcall try] instead of [uplevel]:
proc do {code while cond} {
    tailcall try $code\n[list $while $cond $code]
}

This gives correct behavior even when $code contains [return], [error], [throw], and such.

APN: But not with [break]. Unlike dkf's version, both wdb's and AMG's version will not work with [break] and [continue].
% set i 2
2
% do {puts $i ; break} while {[incr i -1]}
2
invoked "break" outside of a loop

PYK 2016-09-06: Then how about this:
proc do {code while cond} {
    tailcall ::while 1 "
        ::try [list $code\n[list $while $cond $code]]
        ::break
    "
}

AMG: As an example of [tcl::unsupported::assemble], I'd be interested to see a bytecoded [do..while] command.

Last I checked, the bytecode for [while] starts by jumping to the end, immediately before the test for termination, and has the script body in the middle. The termination test jumps back to the start of the script body if it's okay to keep looping.

The only difference in the bytecode for [do..while] would be omission of the initial jump.

Let's make this concrete. Here's the bytecode for while {$x} {incr x -1}:
% tcl::unsupported::disassemble script {while {$x} {incr x -1}}
ByteCode 0x000000000299F130, refCt 1, epoch 16, interp 0x00000000025E65B0 (epoch 16)
  Source "while {$x} {incr x -1}"
  Cmds 2, src 22, inst 25, litObjs 2, aux 0, stkDepth 1, code/src 0.00
  Exception ranges 1, depth 1:
      0: level 0, loop, pc 2-14, continue 16, break 22
  Commands 2:
      1: pc 0-23, src 0-21        2: pc 2-14, src 12-20
  Command 1: "while {$x} {incr x -1}"
    (0) jump1 +16         # pc 16
  Command 2: "incr x -1"
    (2) startCommand +13 1         # next cmd at pc 15
    (11) push1 0         # "x"
    (13) incrStkImm -1 
    (15) pop 
    (16) push1 0         # "x"
    (18) loadStk 
    (19) nop 
    (20) jumpTrue1 -18         # pc 2
    (22) push1 1         # ""
    (24) done 

do {incr x -1} while {$x} would be the same but with (0) eliminated.