I had a bit of fun in the past few days writing a telnet server implementation in #scala. Part of that was implementing a state machine, which I then cleaned up so the definitions are simple.
Here's a sample state machine:
class SampleSM extends razie.SM.StateMachine {
import razie.SM._
implicit val sm = this
val states @ (s1, s2, s3) = ("s1", "s2", "s3")
override def start = state("s1") // TODO there's an issue if i use s1 instead of "s1"
override val transitions : Seq[Transition] =
(s1, 2) -> s2 ::
(s1, 0) -> s1 :: // NOP
(s2, 1) -> s1 + echo ("back to s1") :: // CR
(s2, 3) -> s3 ::
(s3, Seq(4, 5, 6, 7)) -> s2 ::
(s3, AnyEvent) -> s1 ::
(s2, {_:Event=>true}) -> s2 + eatChar + echo ("s2 eats all others") :: // eating stuff
(""".*""".r, -1) -> s1 :: // some reset event applies to all states
Nil
def eatChar (sm:StateMachine, t:Transition, e:Event) =
println ("ate char: " + e.asInstanceOf[IEvent].i.toChar)
}
object SampleSMain extends Application {
implicit def e (i:Int) = SM.IEvent(i)
val sm = new SampleSM()
sm move 2 move 1 move 3 move -1 move 2 move 3 move 6 move 59
}
The code is at
http://code.google.com/p/razie/source/browse/trunk/razie/src/razie/SM.scalaThe basic structure is:
(state, event-matcher) -> newstate + callback ++ unitcalback... + callback :: Nil
The event-matcher can be:
- an event
- a sequence of events
- a function taking an event and returning...Boolean, of course
The callback is a function with either of these signatures:
- def push (sm:StateMachine, t:Transition, e:Event) - use + for these
- def reset : Unit - use ++ for these (hey, I'm still getting my head around advanced scala stuff)
Enjoy, and if you likee, contribute, eh?
Here's the definition (in-work) of the high-level telnet state machine, a little more complicated but not much.
The picture (thanks to
http://tomi.vanek.sk/index.php?page=telnet):

The state machine (when it's ready I'll post the link to the entire code):
// based on diagram at http://tomi.vanek.sk/index.php?page=telnet
implicit val sm = this
val sstates @ (data, cmd, app, param, neg, subneg) = ("data", "cmd", "app", "param", "neg", "subneg")
override def start = state("data")
override val transitions : Seq[Transition] =
(data, IAC) -> cmd ::
(data, 0) -> data :: // NOP - don't know why i get these after CR
(data, 10) -> data :: // NOP - LF ignored?
(data, 13) -> data + eatLine + echo ("") :: // CR
(data, {_:Event=>true}) -> data + eatChar + echo ("") :: // remaining chars
(cmd, IAC) -> data ::
(cmd, Seq(WILL, WONT, DO, DONT)) -> neg + push ::
// TODO 3-2 should negociate stuff, i.e. reply with will/won't
(neg, {_:Event=>last==SM(DO)}) -> data + mode(true) + pop ::
(neg, {_:Event=>last==SM(DONT)}) -> data + mode(false) + pop ::
(neg, AnyEvent) -> data + echo("interesting sequence...") + pop :: // What is this?
(cmd, SB) -> subneg ::
(""".*""".r, CR) -> data :: // it's important to reset the thing on ENTRE
Nil
def eatChar (sm:StateMachine, t:Transition, e:Event)
def eatLine (sm:StateMachine, t:Transition, e:Event)
Enjoy and, more importantly, have fun!