/*  Engr 691-6, Special Topics, Software Language Engineering
    Fowler's Computer Configuration DSL using Method Chaining
    H. C. Cunningham
    Version 1:  11 April 2009
    Version 1a: 13 May 2009   Minor Scala programming style updates.

123456789012345678901234567890123456789012345678901234567890123456789012345678

This is a Scala reimplementation of Martin Fowler's Computer
Configuration DSL given in the Method Chaining section of his DSL book
work in progress http://www.martinfowler.com/dslwip/MethodChaining.html.

The author of this code had the Java code for the DSL processing, but
did not have the code for the semantic model.  There are some guesses
at what was intended in the design of the Semantic Model (given in a
separate file).  The overall implementation in Scala can probably be
redesigned to take better advantage of Scala features.

Here is the example DSL program that Fowler gave in that section,
using Scala's parameterless methods instead of zero-argument methods.

  computer
    .processor
      .cores(2)
      .i386
    .disk
      .size(150)
    .disk
      .size(75)
      .speed(7200)
      .sata
    .end
*/


/* The object "ComputerBuilder" and classes "ComputerBuilder" and
   "DiskBuilder" form the parser for the internal DSL based on the
   fluent interface approach.

   The classes "ComputerBuilder" and "DiskBuilder" are designed
   according to the Expression Builder DSL pattern using the Method
   Chaining.  Object "ComputerBuilder" provides the top-level function
   for the DSL and other classes provide the other DSL methods.

   Internally, the Expression Builder classes use Context Variables to
   keep track of the state of the parse.
*/

object ComputerBuilder {
  def computer: ComputerBuilder = new ComputerBuilder 
  // divert chained method calls to ComputerBuilder
}

// Expression Builder for Computers, including single allowed Processor.
class ComputerBuilder {
 
  // Context variables
  private var currentProcessor: Processor   = null
  private var currentDisk:      DiskBuilder = null
  private var loadedDisks:      List[Disk]  = Nil

  // DSL statement "processor".
  def processor: ComputerBuilder = { 
    currentProcessor = new Processor(1,null)
    this 
  }

  // DSL statement "cores".
  def cores(arg: Int): ComputerBuilder = { 
    currentProcessor = new Processor(arg,currentProcessor.getType)
    this
  }

  // DSL statement "i386".
  def i386: ComputerBuilder = {
    currentProcessor = 
      new Processor(currentProcessor.getCores,Processor.Type.i386)
    this
  }

  // DSL statement "disk".
  def disk: DiskBuilder = {
    if (currentDisk != null) 
      loadedDisks = loadedDisks ::: List(currentDisk.getDisk)
    currentDisk = new DiskBuilder(this)
    currentDisk  // divert subsequent chained method calls to DiskBuilder
  }

  // DSL statement "end".
  def end: Computer = new Computer(currentProcessor, disks: _*)

  // Converts List to Array primarily for use in varargs call.
  private def disks: Array[Disk] = {
    if (currentDisk != null)
      loadedDisks = loadedDisks ::: List(currentDisk.getDisk)
    Array.concat(loadedDisks)
  }
}


// Expression Builder for Disks
class DiskBuilder(parent: ComputerBuilder) {

  // Context variable
  private var theDisk = new Disk(Disk.UNKNOWN_SIZE, Disk.UNKNOWN_SIZE, null)

  // DSL statement "size".
  def size(arg: Int): DiskBuilder  = {
    theDisk = new Disk(arg, theDisk.getSpeed, theDisk.getIface)
    this
  }

  // DSL statement "speed".
  def speed(arg: Int): DiskBuilder  = {
    theDisk = new Disk(theDisk.getSize, arg, theDisk.getIface)
    this
  }

  // DSL statement "sata".
  def sata: DiskBuilder  = {
    theDisk = new Disk(theDisk.getSize, theDisk.getSpeed, Disk.Interface.SATA)
    this
  }

  // DSL statements "disk" and "end" in the "DiskBuilder" context
  // delegate to the like-named methods on "ComputerBuilder".
  def disk: DiskBuilder = parent.disk
  def end: Computer     = parent.end 

  def getDisk: Disk     = theDisk
}


/* 
   Code to test the DSL processing partially.  
*/

// Import method name "computer" for use without object name.
import ComputerBuilder._

object CompConfigChaining {

  def main(args: Array[String]) {
    println("\nComputer Configuration DSL test beginning.\n")

    val myComp = computer
                   .processor
                     .cores(2)
                     .i386
                     .disk
                     .size(150)
                   .disk
                     .size(75)
                     .speed(7200)
                     .sata
                   .end

    println(myComp)    
    println("Computer Configuration DSL test ending.")
  }
}
