Wednesday, August 1, 2012

match/when implemented with Groovy's GEP-3

//WOW! https://gist.github.com/3002724

// No commas
def a = 'tim'
def nocom = match( a ) {
  when 'dave' 'Hi Dave'
  when 'tim'  'Hi Tim'
  otherwise 'none of the above'
}
assert nocom == 'Hi Tim'

// Commas
a = 'William'
def com = match( a ) {
  when 'dave', 'Hi Dave'
  when 'tim',  'Hi Tim'
  otherwise 'none of the above'
}
assert com == 'none of the above'

// Lists (can't use commas)
a = [1,3]
def list = match( a ) {
  when [1,2] 'One and two'
  when [1,3] 'One and three'
  when [2,1] 'Two and one'
}
assert list == 'One and three'

// Wildcard Lists (can't use commas)
a = [1,2,3]
def wild = match( a ) {
  when [1,3,2] 'One, three and two'
  when [1,_]   'One and something'
  when [1,_,3] 'One, something and three'
  when [1,_,_] 'One, something and something'
  otherwise    'Something else'
}
assert wild == 'One, something and three'

// Closures as the when/results
String toRoman( int num ) {
  match( num ) {
    when { it >= 100 } { "C"  + toRoman( num - 100 ) }
    when { it >= 90  } { "XC" + toRoman( num - 90  ) }
    when { it >= 50  } { "L"  + toRoman( num - 50  ) }
    when { it >= 40  } { "XL" + toRoman( num - 40  ) }
    when { it >= 10  } { "X"  + toRoman( num - 10  ) }
    when { it >= 9   } { "IX" + toRoman( num - 9   ) }
    when { it >= 5   } { "V"  + toRoman( num - 5   ) }
    when { it >= 4   } { "IV" + toRoman( num - 4   ) }
    when { it >= 1   } { "I"  + toRoman( num - 1   ) }
    otherwise ""
  }
}

assert "I"       == toRoman( 1 )
assert "II"      == toRoman( 2 )
assert "IV"      == toRoman( 4 )
assert "V"       == toRoman( 5 )
assert "VI"      == toRoman( 6 )
assert "IX"      == toRoman( 9 )
assert "X"       == toRoman( 10 )
assert "XVII"    == toRoman( 17 )
assert "XXXVIII" == toRoman( 38 )
assert "CCCXCIX" == toRoman( 399 )
    
///////////////////////////
// Implementation below...

def match( v, c ) {
  new Matcher( var:v, closure:c ).match()
}

class Matcher {
  def var
  Closure closure
  List cases = []
  Otherwise otherwise

  def propertyMissing( name ) {
    if( name == '_' ) {
      new Any()
    }
    else {
      def w = new When()
      cases << w
      w
    }
  }
  def when( condition )         { cases << new When( condition:condition ) }
  def when( condition, result ) { cases << new When( condition:condition, result:result ) }
  def otherwise( result )       { this.otherwise = new Otherwise( result:result ) }
  public match() {
    closure.delegate = this
    closure.resolveStrategy = Closure.DELEGATE_ONLY
    closure()
    def ret = cases.find {
      it.condition instanceof Closure ?
        it.condition( var ) :
        it.condition == var
    }?.result ?: otherwise?.result
    ret instanceof Closure ? ret() : ret
  }
}

class When {
  def condition
  def result
  def getAt( List a )                  { condition = a ; this }
  def call( result )                   { this.result = result }
  def propertyMissing( String result ) { this.result = result }
}

class Otherwise {
  def result
}

class Any {
  boolean equals( other ) { true }
}

println 'DONE'