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

1234567890123456789012345678901234567890123456789012345678901234567890

2009-04-28: (V1) Original
2009-05-12: (V2) Refactored to use IncrementalStateMachineBuilder.
2018-02-02: (V3) Replaced parse by parseAll in test.
            Updated comments. Created compile script.
      
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 program author that does not use Fowler's code
from his book, except for some aspects of the trait
IncrementalStateMachineBuilder.  So far, error checking and program
testing are minimal.

The Custom External DSL is defined in the chapter "Tree Construction"
of Fowler's book Domain-Specific Languages.

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")
//    new FileReader("CustomExternalStateMachineErr2.dsl")

    var stateMachine: StateMachine = null
    parseAll(machine,reader) match {
      case Success(res,_) => 
        stateMachine = res.asInstanceOf[StateMachine] // get?
      case NoSuccess(msg,inp) =>
        println("Error: " + msg)
        println("Input remaining:\n" + inp.source)
        System.exit(1)
    }

    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")
  }
}
