### CODE TO MAKE (INDEPENDENT) AUTOMATA DEFINITION EASY ###
proc automaton {name definition} {
global definingAutomaton
set definingAutomaton $name
upvar #0 automaton_$name a state($name) s
array set a {}
set s {}
uplevel #0 $definition
}
proc node {key definition} {
global definingAutomaton
upvar #0 automaton_$definingAutomaton a
set a($key) $definition
}
proc start {key} {
global definingAutomaton
upvar #0 state($definingAutomaton) s
set s $key
}
proc transition {key} {
global currentAutomaton
upvar #0 state($currentAutomaton) s
set s $key
}
proc step {automaton} {
global currentAutomaton errorInfo errorCode
set currentAutomaton $automaton
upvar #0 state($automaton) s automaton_$automaton a
if {![info exist s]} {return}
set code [catch [list uplevel 1 $a($s)] msg]
unset currentAutomaton
return -code $code -errorcode $errorCode -errorinfo $errorInfo $msg
}
proc halt {} {
global currentAutomaton
upvar #0 state($currentAutomaton) s
unset s
}
### DEMO CODE ###
automaton foo {
start 4
node 2 {
puts "foo is 2"
halt
}
node 7 {
puts "foo is 7"
transition 2
}
node 4 {
puts "foo is 4"
transition 7
}
}
automaton bar {
node 42 {
puts "bar is 42"
# Random transition!
transition [lindex {7 twentyone 42} [expr {int(rand()*3)}]]
}
start 42
node 7 {
puts "bar is 7"
halt
}
node twentyone {
puts "bar is twentyone"
transition 7
}
}
while {[array size state]} {
step foo
step bar
}There's only one restriction with this code: do not step an automaton as part of the definition of any of the steps of an automaton. Bad things will happen!Donal Fellows further answered the follow-up question: What kind of "bad things"?Well, the problem stems from the fact that a global variable is used to communicate what is the current automaton, so that inherently makes the automata created through that process non-nestable. Making the system reentrant is more interesting and can be done in several ways.
- Method 1: Use a stack of "current" automata
- Method 2: Examine the call stack using info level
- Method 3: Rewrite the node definitions to refer to the right automaton
- Method 4: Rewrite the procs called to refer to the right automaton
# The rest of the code is the same as my previous message, so I won't
# repeat it. :^)
proc transition {key} {
global currentAutomata
upvar #0 state([lindex $currentAutomata end]) s
set s $key
}
proc step {automaton} {
global currentAutomata errorInfo errorCode
set savedAutomata $currentAutomata
lappend currentAutomata $automaton
upvar #0 state($automaton) s automaton_$automaton a
if {![info exist s]} {
set currentAutomata $savedAutomata
return
}
set code [catch [list uplevel 1 $a($s)] msg]
set currentAutomata $savedAutomata
return -code $code -errorcode $errorCode -errorinfo $errorInfo $msg
}
proc halt {} {
global currentAutomata
upvar #0 state([lindex $currentAutomata end]) s
unset s
}[Volker Hetzer added this idea that does without a global state table - every node is a proc that nominates it successor: However, here's a totally different idea for an automaton:
proc state0 {} \
{
global NextState ExitFlag
do something, perhaps manipulate the ExitFlag
set NextState state1
}
proc state1 {} \
{
global Nextstate ExitFlag
do something, perhaps manipulate the ExitFlag
set NextState state0
}
set ExitFlag 0
set NextState state0
while {!$ExitFlag} \
{
$NextState
}See, you don't need any state table at all in this case. Even if you want a state table, you could still put the state procedures directly into the table thus saving that big switch. I don't know at which point the cost of recompiling is lower than the cost of a big switch.comp.lang.tcl has hosted many, many other implementations of automata, and there are quite a few others loose "in the wild". They're common throughout the whole area of [Tcl agents]. Also, they often appear under the rubric of "finite-state machine" (FSM--see the Acronym collection).For related discussion, see also:
- Why Tcl has no GOTO command - GOTO in Tcl
- BASIC in Tcl - Playing Assembler for GOTO/JMP implementations

