/* Engr 691-6, Special Topics, Software Language Engineering
   Scala Combinator-based Parser and Embedded State Machine Builder
     for Fowler's Custom External State Machine DSL (Secret Panel)
   H. Conrad Cunningham
   Version 1:  28 April 2009
   Version 2:  12 May 2009  
     Refactored to use trait IncrementalStateMachineBuilder.

123456789012345678901234567890123456789012345678901234567890123456789012345678

This is an implementation of Martin Fowler's Custom External State
Machine DSL (Secret Panel) that uses the Scala parser combinator
library.  It utilizes the Syntax-Directed Translation and Embedded
Translation (i.e., Scala combinator transformation functions) DSL
patterns to populate the State Machine Semantic Model directly.  The
trait IncrementalStateMachineBuilder is effectively an Embedment
Helper.

The program uses one parsing phase.  It reads the input and parses the
text using Scala's parser combinators.  As the program is doing the
parse, it applies "transformation functions" to the output returned by
the parsers for selected productions.  These "functions" are actually
procedures executed for their side-effects to make appropriate context
variable, symbol table, and semantic model updates as determined by
the parsing step.

This is new work by the author that does not use Fowler's code from
his book, except for some aspects of the IncrementalStateMachineBuilder. 
So far, error checking and program testing are minimal.

The Custom External DSL is defined in the "Tree Construction" chapter
of Fowler's in-progress DSL book 

    http://www.martinfowler.com/dslwip/intro.html.  

Below is Fowler's example input file from the Tree Construction
chapter of the in-progress DSL book.  Note that it differs slightly
from that used for Fowler''s Delimiter-Directed Translation chapter
example (and this author's Scala example)--the actions are enclosed in
braces.

events
  doorClosed  D1CL
  drawOpened  D2OP
  lightOn     L1ON
  doorOpened  D1OP
  panelClosed PNCL
end

resetEvents
  doorOpened
end

commands
  unlockPanel PNUL
  lockPanel   PNLK
  lockDoor    D1LK
  unlockDoor  D1UL
end

state idle
  actions { unlockDoor lockPanel }
  doorClosed => active
end

state active
  drawOpened => waitingForLight
  lightOn    => waitingForDraw
end

state waitingForLight
  lightOn => unlockedPanel
end

state waitingForDraw
  drawOpened => unlockedPanel
end

state unlockedPanel
  actions { unlockPanel lockDoor }
  panelClosed => idle
end

*/

/*

This version of the combinator-based parser uses the following version
of the DSL grammar expressed in BNF.  Items within (unquoted) square
brackets [ and ] are optional in (unquoted) braces { and } are
repeated 0 or more times.

Compared to the parser that builds a full abstract syntax tree, the
grammar for this version has been refactored further to add rules to
which semantic actions could be more conveniently attached.  (The
<State> rule was refactored to break out the <StateHead> and the
<ResetId> and <ActionId> rules were added.

<Machine>        ::= <EventList> <AfterEvent>.
<AfterEvent>     ::= <ResetEventList> <AfterReset> | <AfterReset>.
<AfterReset>     ::= <CommandList> {<State>} | {<State>}.

<EventList>      ::= "events" <EventEnd>.
<EventEnd>       ::= "end" | <EventDec> <EventEnd>.
<EventDec>       ::= <Identifier> <Identifier>.

<ResetEventList> ::= "resetEvents" <ResetEnd>.
<ResetEnd>       ::= "end" | <ResetId> <ResetEnd>.
<ResetId>        ::= <Identifier>

<CommandList>    ::= "commands" <CommandEnd>.
<CommandEnd>     ::= "end" | <CommandDec> <CommandEnd>.
<CommandDec>     ::= <Identifier> <Identifier>.

<State>          ::= <StateHead> <StateAction>.
<StateHead>      ::= "state" <Identifier>.
<StateAction>    ::= <ActionList> <StateTrans> | <StateTrans>.
<StateTrans>     ::= "end" | <Transition> <StateTrans>.

<ActionList>     ::= "actions"  "{" {<ActionId>} "}".
<ActionId>       ::= <Identifier>.

<Transition> ::= <Identifier> "=>" <Identifier>.

*/


import scala.util.parsing.combinator._
import java.io._


/* Class StateMachineBuilder parses the DSL input and builds the State
   Machine Semantic Model directly.  It uses the Scala parser
   combinator library and the IncrementalStateMachineBuilder trait.
*/

class StateMachineBuilder 
  extends JavaTokenParsers 
  with    IncrementalStateMachineBuilder {

  /* Top-level statement parsers.  The side-effecting transformer
     functions associated with these parsers complete the population
     of the State Machine Semantic Model. 
  */

  // <Machine> ::= <EventList> <AfterEvent>
  def machine: Parser[Any] = 
    (eventList~afterEvent) ^^ { _ => finishMachine; getMachine }

  // <AfterEvent> ::= <ResetEventList> <AfterReset> | <AfterReset>
  def afterEvent: Parser[Any] = (resetEventList~afterReset) | afterReset

  // <AfterReset> ::= <CommandList> <State>* | <State>*
  def afterReset: Parser[Any] = (commandList~rep(state)) | rep(state)

  /* Events block parsers.  The side-effecting transformer functions
     associated with these parsers populate the State Machine Semantic
     Model with the needed Event objects and update the symbol table
     with information needed in other parts of the parser.
  */

  // <EventList> ::= "events" <EventEnd>
  def eventList: Parser[Any] = ("events"~>eventEnd)

  // <EventEnd> ::= "end" | <EventDec> <EventEnd>
  def eventEnd:  Parser[Any] = "end" | (eventDec~eventEnd)

  // <EventDec>  ::= <Identifier> <Identifier>
  def eventDec: Parser[Any] = 
    (ident~ident) ^^ { case n~c => addEvent(n,c); null }


  /* Reset events block parsers. The side-effecting transformer
     functions associated with these parsers update the symbol table
     with information needed in other parts of the parser.
  */

  // <ResetEventList> ::= "resetEvents" <ResetEnd>
  def resetEventList: Parser[Any] = ("resetEvents"~>resetEnd)

  // <ResetEnd> ::= "end" | <ResetId> <ResetEnd>
  def resetEnd: Parser[Any] = "end" | (resetId~resetEnd)

  // <ResetId> ::= <Identifier>
  def resetId: Parser[Any] = 
    ident ^^ { case id => addResetEvent(id); null } 


  /* Commands block parsers. The side-effecting transformer functions
     associated with these parsers populate the State Machine Semantic
     Model with the needed Command objects and update the symbol table
     with information needed in other parts of the parser.
  */

  // <CommandList> ::= "commands" <CommandEnd>
  def commandList: Parser[Any] = ("commands"~>commandEnd)

  // <CommandEnd>  ::= "end" | <CommandDec> <CommandEnd>
  def commandEnd:  Parser[Any] = "end" | (commandDec~commandEnd)

  // <CommandDec>  ::= <Identifier> <Identifier>
  def commandDec: Parser[Any] = 
    (ident~ident) ^^ { case n~c => addCommand(n,c); null }

 
  /* State block sequence parsers.  The side-effecting transformer
     functions associated with these parsers populate the State Machine
     Semantic model with the needed State and Transition objects and
     update the symbol table with information needed in other parts of
     the parser.  It also updates the "current state" and "state
     machine" context variables.
  */

  // <State> ::= <StateHead> <StateAction>
  def state: Parser[Any] = (stateHead~stateAction) 

  // <StateHead> ::= "state" <Identifier>
  def stateHead: Parser[Any] = 
    ("state"~>ident) ^^ { case n => addState(n); null }

  // <StateAction> ::= <ActionList> <StateTrans> | <StateTrans>
  def stateAction: Parser[Any] = (actionList~stateTrans) | stateTrans

  // <StateTrans> ::= "end" | <Transition> <StateTrans> 
  def stateTrans: Parser[Any] = "end" | (transition~stateTrans)

  // <ActionList> ::= "actions"  "{" <ActionId>* "}"
  def actionList: Parser[Any] = ("actions"~>"{"~>rep(actionId)<~"}")

  // <ActionId> ::= <Identifier>
  def actionId: Parser[Any] = 
    ident ^^ { case c => addAction(c); null }

  // <Transition> ::= <Identifier> "=>" <Identifier>
  def transition: Parser[Any] = 
    (ident~"=>"~ident) ^^ { case e~_~d => addTransition(e,d); null }
}


/*  Object to do some simple testing of the parser.  */

object CombinatorParserBuilderTest extends StateMachineBuilder {

  def main(args: Array[String]) {
    println("\nCombinator Parser-Builder External DSL test beginning.\n")

    // Load and parse the DSL to configure the State Machine
    println("Parse DSL and build state machine.")
    val reader       = new FileReader("CustomExternalStateMachineDSL2.dsl")
    val stateMachine = parse(machine,reader).get.asInstanceOf[StateMachine]
    println(stateMachine)

    // Build the controller for some testing
    println("\nBuild controller.")
    val commandsChannel = new CommandChannel
    val control = new Controller(stateMachine,commandsChannel)
    println(control.toString)

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

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

    println("\nCombinator Parser-Builder External DSL test ending.\n")
  }
}
