[Richard Suchenwirth] 2001-05-21 -- Of course one shouldn't use a sort of GOTO command in Tcl (see "Go To Statement Considered Harmful", [Edsger Dijkstra], http://www.acm.org/classics/oct95/ ), and few of us ever felt the need. But the question occasionally comes up in news:comp.lang.tcl : Can we do GOTO in Tcl? [Why Tcl has no GOTO command] Summarizing what several folks have written about state machines in news:comp.lang.tcl, here's what I have now: proc states body { proc goto {id} {uplevel set goto $id; return -code continue} uplevel set goto [lindex $body 0] set tmp [lindex $body 0] foreach {cmd label} [lrange $body 1 end] { if {$label==""} {set label default} lappend tmp "$cmd; goto [list $label]" $label } lappend tmp break ;# to match last "default" label uplevel while 1 "{switch -- \$goto [list $tmp]}" rename goto "" ;# so now Tcl has no GOTO command any more } And here comes a usage example: set n 0 states { 1 {puts 1:$n; incr n} 2 {puts 2:$n; if {$n>5} {goto hell}} 3 {puts 3:$n; goto 1} hell {puts hell!} } Looks good enough, runs good enough even if I put the limit at 50000. ''break'' terminates the state engine, as would an undefined label (''goto exit''). If there is no ''goto'' at end of a state body, control continues with the next in line. The last state body gets a ''break'' appended that fires if there's no ''goto'' in front of it. Label 3 is redundant here. Now comes DKF's example of a state machine from [Why Tcl has no GOTO command]: states { 1 {set x 1} 2 {incr x $x} 3 {if {$x < 10} {goto 2}} } I further use ''states'' as interpreter for [Basic in Tcl] ;-) ---- A much more minimal state machine is used in [Playing Assembler], where of course you do lots of jumping around: while {pc>0} { eval $mem($pc) incr pc } which assumes you have the state code in an array with integer keys. A ''goto'' (or rather ''jump'') is then implemented like this: proc JMP where {uplevel 1 set pc [expr $where-1]} ---- ''[Bryan Oakley] writes in news:comp.lang.tcl in reply to [Cameron Laird] who mentioned an earlier attempt in pure Tcl:'' I started to do that as well, just to prove a point. I'm slightly curious (in a rhetorical kind of way) what your solution looked like. I was thinking along the lines of the following: evalGOTOBlock { 100 { } 200 { } ... and so on } ... patterned after the switch statement. The theory being, in order to support goto, all code had to be labeled in some manner. In evalGOTOBlock, each block would be eval'd in turn, but a simple "goto N" statement would change the sequencing by breaking out of the block (possibly by throwing a magic error that evalGOTOBlock is looking for) and then resetting the execution order to begin again at the requested block. Never got beyond that, though. In my heart, I know it's possible. This would make for an interesting challenge at the next tcl conference. Useful for porting those old BASIC programs to tcl :-) ---- ''[Andreas Otto] adds in news:comp.lang.tcl:'' i like GOTO too, usually i use while {1} { ... some code ... or break ... some code ... or break ... ... break } to get a goto like behavior. ---- Here's [Cameron Laird]'s approach: My scheme looked more like assembler or, if you prefer, BASIC. It had labels, so that code looked like if $something { do a do b goto end_stuff } do c LABEL end_stuff do d or if $something { do a do b goto end_stuff } do c end_stuff: do d In the second, [[unknown]] took anything with a trailing colon as a NOP-acting label. In the first, of course, results could quickly become hilarious with (un)suitable redefinition of "proc LABEL ..." The rest of it involved: 1. getting a handle on the current script 2. scanning enough Tcl to be able to skip commands 3. creation of the right execution context to resume command interpretation Exception-handling seemed to be correct automatically. That's as much as I remember. ---- One thing which [CL] calls a "forward goto", jumping to the end of a code block, is easily done with a [break] in a [foreach] loop that iterates only once, e.g. foreach _ _ { # some processing if {$condition} break # some more processing } ---- [Arjen Markus] I once read about an alternative to GOTO, the COMEFROM construct. Want to try that in pure Tcl? [RS]: A "man page" for 1973 FORTRAN is at [http://www.fortran.com/fortran/come_from.html] ''A better reference on comefrom might be intercal [http://www.tuxedo.org/~esr/intercal/] -- in 1973, COME FROM had been documented as a part of the language.'' [Arjen Markus] I knew it was something with FORTRAN. Glad you could turn up the reference. (The famous paper by E. Dijkstra has engendered a whole bunch of papers pro and contra the claim). ---- [DKF]: You can use the state-machine techniques listed here to do some fairly complex stuff. For example, here is some code that I wrote after reading about coroutines[http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html]. proc goto {label args} { upvar 1 state state set state $label if {[llength $args]} { return {expand}$args -level 1} return -code 5 } proc stateengine {init vars body} { set contextKey [lindex [info level 1] 0] set cmd {upvar #0} lappend vars state foreach var $vars { lappend cmd state($var,$contextKey) $var } uplevel 1 $cmd upvar 1 state state if {![info exist state] || $state eq ""} { set state $init } set ret {} while {![llength $ret]} { catch [list uplevel 1 [dict get $body $state]] msg ret if {[dict get $ret -code] == 5} { set ret {} } } if {[dict get $ret -code] == 1} { dict append ret -errorinfo "\n (in state $state)" } return -options $ret } With these procedures (which require Tcl 8.5) we can build some coroutines like this (buiding off Simon Tatham's short paper): proc decompressor {chan} { stateengine loop {c len} { loop { set c [read $chan 1] if {[eof $chan]} { goto {} {} } if {$c == "\xff"} { set len [read $chan 1] set c [read $chan 1] goto emit } else { goto loop $c } } emit { while {[incr $len -1] >= 0} { return $c } goto loop } } } proc parser {c} { stateengine loop {} { loop { if {$c eq ""} { goto {} -code break } if {[string is alpha $c]} { goto accumulateToken } got_token PUNCT goto loop {} } accumulateToken { if {[string is alpha -strict $c]} { add_to_token $c goto accumulateToken {} } got_token WORD goto loop } } } These can then be coded like this: while 1 { parser [decompressor] } Isn't that wonderfully neat? ---- [Arts and crafts of Tcl-Tk programming]