/*  CSci 658, Software Language Engineering
    XML-Based Parser and State Machine Builder
    XML-Based External DSL for State Machine Controller (Secret Panel)
    H. Conrad Cunningham

1234567890123456789012345678901234567890123456789012345678901234567890

2009-04-14: (V1) Original
2009-04-14: (V2) Modified for StateMachine changes (setMachine).
2009-05-03: (V3) Changed name. Separated CommandChannel.
            Updated comments.
2009-05-13: (V4) Refactored to use IncrementalStateMachineBuilder.
            Added object XMLMachineBuilder and tracing.
            (Changes required a <resetEvent name=XXX > statement
	    after corresponding <event name=XXX /> statement)
2018-02-01: (V4a) Updated comments. Created compile script.

This is a Scala implementation of Martin Fowler's XML-based DSL for
the State Machine Controller given as a part of the Secret Panel
example in the chapter "An Introductory Example" of his book Domain
Specific Languages. (It was actually based on a preliminary version of
the example from 2009.)

The author of this code did not have Fowler's Java code.  This program
uses Scala's XML and other features.

Here is Fowler's example XML DSL input file:

<stateMachine start ="idle">
    <event name="doorClosed" code="D1CL"/>
    <event name="drawOpened" code="D2OP"/>
    <event name="lightOn" code="L1ON"/>
    <event name="doorOpened" code="D1OP"/>
    <event name="panelClosed" code="PNCL"/>

    <command name="unlockPanel" code="PNUL"/>
    <command name="lockPanel" code="PNLK"/>
    <command name="lockDoor" code="D1LK"/>
    <command name="unlockDoor" code="D1UL"/>

  <state name="idle">
    <transition event="doorClosed" target="active"/>
    <action command="unlockDoor"/>
    <action command="lockPanel"/>
  </state>

  <state name="active">
    <transition event="drawOpened" target="waitingForLight"/>
    <transition event="lightOn" target="waitingForDraw"/>
  </state>

  <state name="waitingForLight">
    <transition event="lightOn" target="unlockedPanel"/>
  </state>

  <state name="waitingForDraw">
    <transition event="drawOpened" target="unlockedPanel"/>
  </state>

  <state name="unlockedPanel">
    <action command="unlockPanel"/>
    <action command="lockDoor"/>    
    <transition event="panelClosed" target="idle"/>
   </state>

  <resetEvent name = "doorOpened"/>
</stateMachine>

*/


import scala.xml._
import java.io._


/* An Expression Builder to parse the DSL and build the State Machine
   using the Semantic Model.
*/

/* The class XMLMachineBuilder implements what is essentially a
   recursive descent parser for the XML-based External DSL for the
   State Machine Controller example. The parser uses Scala's XML
   library to build the XML document tree and then uses Scala's XML
   pattern-matching facilities to recognize the various statements and
   attributes to determine information needed to populate the semantic
   model.

   The singleton object XMLMachineBuilder provides a method to
   load the XML input file and parse it to configure a state machine.
*/

object XMLMachineBuilder {

  def loadFile(fileName: String): StateMachine  = {
    var loadNode: Elem = null
    try { 
      loadNode = XML.loadFile(fileName)
    }
    catch {
      case e: FileNotFoundException => 
        throw new RuntimeException(e.getMessage)
    }
    val builder = new XMLMachineBuilder
    builder.trace("Loaded XML file '" + fileName + "'.")
    builder.parseStateMachine(loadNode)
    val stateMachine = builder.getMachine
    builder.trace(
      "Completed definition of state machine with start state '"
       + stateMachine.getStart.name + "'.")
    stateMachine
  }
}

class XMLMachineBuilder extends IncrementalStateMachineBuilder {

  // Parse enclosing <stateMachine> statement (top level)
  def parseStateMachine(node: Node) { 
    node match {

      case <stateMachine>{subNodes @ _*}</stateMachine> =>
        val start = (node \ "@start").toString
        trace("Defining state machine with start state '" +
              start + "'.")
        addState(start)
        for (s  <- subNodes)
          parseMachineBody(s)
        finishMachine

      case el => 
        val tr = el.toString.trim // ignore white space
        if (!tr.isEmpty())
          syntaxError("Illegal element '"  + tr + "'.")
    }
  }

  // Parse body of <stateMachine> statement (second level)
  private def parseMachineBody(node: Node) {
    node match {

      case <event></event> =>
        val name = (node \ "@name").toString
        val code = (node \ "@code").toString
        trace("..Define event '" + name + "'.")
        addEvent(name,code)

      case <resetEvent></resetEvent> =>
        val name = (node \ "@name").toString
        trace("..Define resetEvent '" + name + "'.")
        addResetEvent(name)

      case <command></command> =>
        val name = (node \ "@name").toString
        val code = (node \ "@code").toString
        trace("..Define command '" + name + "'.")
        addCommand(name,code)

      case <state>{subNodes @ _*}</state> =>
        val name = (node \ "@name").toString
        trace("..Define state '" + name + "'.")
        addState(name)
        for (s <- subNodes)
          parseStateBody(s)

      case el => 
        val tr = el.toString.trim // ignore white space
        if (!tr.isEmpty())
          syntaxError("Illegal element '"  + tr + "'.")
    }
  }

  // Parse body of <state> statement (third level)
  private def parseStateBody(node: Node) {
    node match {

      case <action></action> =>
        val command = (node \ "@command").toString
        trace("....Add action '" + command + "'.")
        addAction(command)

      case <transition></transition> =>
        val event  = (node \ "@event").toString
        val target = (node \ "@target").toString
        trace("....Add transition on '" + event + "' to '" +
              target + "'.")
        addTransition(event,target)

      case el => 
        val tr = el.toString.trim // ignore white space
        if (!tr.isEmpty())
          syntaxError("Illegal element '"  + tr + "'.")
    }
  }

}


/*  Do some limited testing of the Secret Panel XML DSL  */

object StateMachineXMLTest {

  def main(args: Array[String]) {
    println("\nXML State Machine DSL test beginning.\n")

    // Load XML file, create builder, and call parser to build machine
    val stateMachine = XMLMachineBuilder.loadFile("SecretPanel.xml")

    println
    println(stateMachine)

    // Build the controller
    val commandsChannel = new CommandChannel
    val control         = new Controller(stateMachine,commandsChannel)

    println(control.toString)

    // Execute the model
    control.handle('D1CL)
    control.handle('L1ON)
    control.handle('D2OP)
    control.handle('PNCL)

    println("\nCommands output:  " + commandsChannel.getOutput)

    println("\nXML State Machine DSL test ending.\n")
  }
}
