/* CSci 658, Software Language Engineering
   Incremental State Machine Builder Embedment Helper trait
     ..builds State Machine Semantic Model
     ..used by various Secret Panel DSL Examples
   H. Conrad Cunningham

1234567890123456789012345678901234567890123456789012345678901234567890

2009-05-13: (V1) Original
2018-02-01: (V1a) Updated comments,. Created compile script.

The design and use of this trait is similar in concept to Martin
Fowler's Embedment Helper DSL pattern, especially when used in the
Combinator-based parser.

This code was factored out of three example parsers for the Custom
External State Machine DSL:

  * Hand-coded Recursive Descent Parser
  * Combinator-based Parser and Embedded State Machine Builder 
  * Delimiter-Directed Parser

The approach had started as a Scala reimplementation of Fowler's Java
code in his Delimiter-Directed Translation example.  

*/


import scala.io.Source
import scala.collection.mutable.Map
import scala.collection.mutable.ArrayBuffer


/* Trait IncrementalStateMachineBuilder defines the basic operations
   and attributes for building StateMachine objects incrementally
   during parsing.  It includes the symbol table and the basic context
   variables. 
*/

trait IncrementalStateMachineBuilder {

  // Context variables
  private var stateMachine: StateMachine = null
  private var curState: State            = null

  // Symbol table
  private val events      = Map[String,Event]()
  private val commands    = Map[String,Command]()
  private val resetEvents = new ArrayBuffer[Event]
  private val states      = Map[String,State]()

  // Options
  private var traceEnabled = true

  // Enable/Disable trace output capability
  def enableTrace  { traceEnabled = true  }
  def disableTrace { traceEnabled = false }

  // Create State Machine with argument start state unless 
  // already done
  def primeMachine(state: State) {
    if (stateMachine == null) stateMachine = new StateMachine(state)
    state.setMachine(stateMachine)  // register machine with state
  }  

  // Define a new Event
  def addEvent(name: String, code: String) { 
    getEvent(name) match {
      case Some(e) => 
        syntaxError("Attempt to redefine event '" + name + "'.")
      case None => 
        events(name) = Event(name,Symbol(code))
    }
  }

  // Define a new ResetEvent
  def addResetEvent(name: String) {
    getEvent(name) match {
      case Some(e) => 
        resetEvents += e 
      case None => 
        syntaxError("Undefined resetEvent '" + name + "'.")
    }
  }

  // Define a new Command
  def addCommand(name: String, code: String) { 
    getCommand(name) match {
      case Some(c) => 
        syntaxError("Attempt to redefine command '" + name + "'.")
      case None => 
        commands(name) = Command(name,Symbol(code)) 
    }
  }

  /* Define a new State.  If this is the first state, create a new
     state machine with this as the start state.  Also make this the
     "current state".  The first two steps can be done by directly
     calling obtainState and primeMachine, both of which are also in
     the public interface.
  */
  def addState(name: String) {
    curState = obtainState(name)  // might be adding to existing state
    primeMachine(curState)
  }

  // Add a Command to the current state's actions
  def addAction(name: String) {
    getCommand(name) match {
      case Some(ac) => 
        curState.addAction(ac)
      case None => 
        syntaxError("Attempt to add undefined action '" + name 
                    + "' to state '" + curState.name + "'.")
    }
  }

  // Add a transition to the current state
  def addTransition(trigger: String, target: String) {
    getEvent(trigger) match {
      case Some(ev) => 
        curState.addTransition(ev,obtainState(target)) 
          // target might be forward reference to valid state
      case None => 
        syntaxError("Undefined event '" + trigger +
                    "' in transition.")
    }
  }

  // Create or look up existing state
  def obtainState(name: String): State = 
    states.getOrElseUpdate(name, new State(name))

  // Accessors for the attributes 
  def getMachine: StateMachine =
    stateMachine

  def getEvent(name: String): Option[Event] = 
    events.get(name)

  def getCommand(name: String): Option[Command] = 
    commands.get(name)

  def getState(name: String): Option[State] = 
    states.get(name)

  def getCurState: State =
    curState

  // Complete any remaining actions to build state machine
  def finishMachine {
    stateMachine.addResetEvents(resetEvents.toArray: _*)
  }

  // Error condition when illegal syntax encountered
  def syntaxError(msg: String) { 
    throw new RecognitionException("[Error] " + msg) 
  }

  // Trace output 
  def trace(msg: String) { if (traceEnabled) println(msg) }

}


/* Exception RecognitionException denotes an error in the 
   input DSL. 
*/

class RecognitionException(ex: String) extends Exception(ex)
