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 { <random bits of code> } 200 { <random bits of code> } ... 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 dIn 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:
- getting a handle on the current script
- scanning enough Tcl to be able to skip commands
- creation of the right execution context to resume command interpretation
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 [1] A better reference on comefrom might be intercal [2] -- 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[3].
proc goto {label args} { upvar 1 state state set state $label if {[llength $args]} { return {*}$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? (Thanks to Will Duquette for spurring me into writing this.)
LES: Eh. I declare all my procs right at the beginning of all my programs and call them whenever they are needed. Most of my procs are run only once. Under that light, I don't see how procs are any different from "goto". IMO, "goto" is forbidden in many languages for a simple matter of style and the pleasure of imposing one's style on everyone else. Like Python's author and his iron fist, successfully forcing everyone to indent code in the way he likes by turning it into a syntax rule. Finally, like I was told at the Tcl'ers Chat recently, "these Considered Harmful articles are considered harmful".
[Ethouris]: My small proposition is to provide some command, which can be simply implemented with existing command, but appropriate name will suggest the correct intention - something like in C/C++ the do { ... } while(0); is sometimes used:
block { do many things if { something } break ;# go to the end of block do other things if { something } continue ;# go to the beginning of block }This is, however, still "structuralized form" of goto. There are also unstructuralized forms of goto, for example: You allocate some resources in the beginning and you must release them before the proc ends. In the beginning you verify some conditions, which must be satisfied to continue with the proc body. In a very complicated case, the number of released resources is determined on the number of conditions to verify. What would you do in this case?The most famous use of 'goto', however, is searching. You can use a loop to search, you can break if you found, but how will you see outside the loop that you found? In short functions you can use 'return' instead of 'break'. Maybe add a default argument for break/continue commands like it is in bash?RS 2005-05-27: You can have block in a one-liner if you want (see my earlier note):
interp alias {} block {} foreach . .This only requires that you don't have a variable named "." - I expect so :) Also, the continue case isn't covered - but I'm not sure about its semantics in C's ''do ... while(0) either - is the condition never tested on continue?
Roman: An easy sample for a tcl goto implementation emulating a Finite State Machine: goto has been expanded by after/update to delay and - when running in tkcon - to abort the loop by calling 'stop'. Also, it's using global variables, which is not considered for production releases ;-)
package require Tclx; # # state machine implementation with goto # proc goto { _state } { global STATE; loop x 0 20 { update; update idletasks; after 50; } puts "state change: $STATE -> $_state"; set STATE $_state }; proc stop { } { global RUNNING; set RUNNING 0; } proc run { { condition "start" } } { global STATE RUNNING; set RUNNING 1; set STATE $condition; while { $RUNNING } { switch $STATE { start { goto connect; } connect { goto connected; } connected { goto disconnect; } disconnect { goto connect; } stop { puts "Stopping state machine"; break; } default { puts "unknown state '$STATE', exiting"; break; } }; # switch STATE }; # while # clean up code here puts "state machine terminated."; }
A different approach to the same problem.
#!/usr/bin/env tclsh8.4 proc label {lbl} { # do nothing } proc goto {label} { error $label "" magic_goto_code } set script { puts one puts two goto lbl1: puts three label lbl1: puts four label lbl2: puts five failx puts six } set piece $script while {[catch $piece label]} { if {$::errorCode eq "magic_goto_code"} { regsub ".*label\\s+$label" $script "" piece continue } error $label }
dkf - 2017-02-06 21:20:27You can implement goto using tcl::unsupported::assemble. It's fiddly, but the option is there.