Friday, February 26, 2010

Implementing a state machine in scala DSL

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:





The code is at http://code.google.com/p/razie/source/browse/trunk/razie/src/razie/SM.scala

The 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!


If you'd like to read more of my rants about scala, see http://wiki.homecloud.ca/thinking-in-scala

blog comments powered by Disqus