#!/usr/bin/ruby

#
## Subsets
#

func failtest(f, msg) {
    print "Testing: #{msg} -> "
    var i = 0
    try { f() }
    catch { ++i }
    assert_eq(i, 1)
    print "ok\n"
}

#
## Inheritance from user-defined types
#

class Example(Number size) {
    method display {
        say "Number #{size}"
    }
}

subset Special < Example { .size == 42 }

func bar(x < Special) {
    x.display
}

var wrong = Example(3)
var correct = Example(42)

bar(correct)
failtest(func{ bar(wrong) }, 'bar(wrong)')

#
## Union subset
#

subset Union < Number,String;

func concat(x < Union, y < Union) {
    x + y
}

say concat(1, 2)
say concat("a", "b")

failtest(func { concat([1], [2]) }, 'concat([],[])')

#
## Multiple subetting
#

subset Integer     < Number      { .is_int }
subset Natural     < Integer     { |n|  n >= 0 }
subset EvenNatural < Natural     { .is_even }
subset PowerOfTwo  < EvenNatural { |n| (n & (n-1)) == 0 }

func natural(n < Natural) {
    say "natural(): #{n}"
}

func even_natural(n < EvenNatural) {
    say "even_natural(): #{n}"
}

func pow_2(n < PowerOfTwo) {
    say "pow_2(): #{n}"
}

natural(5)
even_natural(42)
pow_2(64)

failtest(func { natural(-10) }, 'natural(-10)')
failtest(func { natural(3.5) }, 'natural(3.5)')

failtest(func { even_natural(21) }, 'even_natural(21)')
failtest(func { even_natural(42.5) }, 'even_natural(42.5)')

failtest(func { pow_2(5) }, 'pow_2(5)')
failtest(func { pow_2(42) }, 'pow_2(42)')
failtest(func { pow_2(128.1) }, 'pow_2(128.1)')

pow_2(128)
pow_2(2048)

even_natural(0)
even_natural(1_000_000)

natural(42)
natural(12345)

#
## Re-usage of subsets
#

func log_pow2(n < PowerOfTwo) {
    say "log_pow2(): #{n.log(2)}"
}

log_pow2(1024)
log_pow2(4096)

failtest(func { log_pow2(42) }, 'log_pow2(42)')
failtest(func { log_pow2(63) }, 'log_pow2(63)')

#
## Recomposition
#

subset FortyTwo < EvenNatural { |n| n == 42 }

func forty_two(n < FortyTwo) {
    say "forty_two(): #{n}"
}

forty_two(42)

failtest(func { forty_two(43) }, 'forty_two(43)')
failtest(func { forty_two(44) }, 'forty_two(44)')
failtest(func { forty_two("foo") }, 'forty_two("foo")')

#
## Subseting built-in types
#

func numeric(x < Number) {
    say "numeric(): #{x}"
}

numeric(-42)
numeric(1234)

failtest(func { numeric("foo") }, 'numeric("foo")')
failtest(func { numeric({}) }, 'numeric({})')

func only_string (s < String) {
    say s.dump
}

only_string("foo")

failtest(func { only_string(42) }, "only_string(42)")
failtest(func { only_string(File("foo")) }, "only_string(File('foo'))")

func filelish(f < File) {
    say "filelish(): #{f.dump}"
}

filelish("foo")             # File includes String
filelish(File("abc"))

failtest(func { filelish(42) }, "filelish(42)")

#
## This accepts any types that include the String class
#

func stringy(String s) {
    say "stringy(): #{s.dump}"
}

stringy("foo")
stringy(File("abc"))

failtest(func { stringy(42) }, "stringy(42)")

#
## Multiple class subsetting
#

class Hello(name) {
    method greet {
        say "Hello, #{self.name}!"
    }
}

class Hi < Hello {
    method greet {
        say "Hi, #{self.name}!"
    }
}

class Hey < Hi {
    method greet {
        say "Hey, #{self.name}"
    }
}

func subset_greet(obj < Hi) {        # `Hi` is the upper limit
    obj.greet
}

var hi_obj = Hi("Foo")
subset_greet(hi_obj)

var hello_obj = Hello("Bar")
subset_greet(hello_obj)

var hey_obj = Hey("Baz")

failtest(func { subset_greet(42) }, "subset_greet(42)")
failtest(func { subset_greet(hey_obj) }, "subset_greet(hey_obj)")

#
## Limited greeting
#

func type_greet(Hi obj) {     # HiGreeting is the lower limit
    obj.greet
}

type_greet(hi_obj)
type_greet(hey_obj)

failtest(func { type_greet(42) }, "type_greet(42)")
failtest(func { type_greet(hello_obj) }, "type_greet(hello_obj)")

#
## Multiple dispatch greeting
#

func multi_greet(obj < Hi) { obj.greet }       # `Hi` is the upper limit

multi_greet(Hi("Foo"))        # ok
multi_greet(Hello("Bar"))     # ok
multi_greet(Hey("Baz"))       # fail: `Hey` is too evolved

func multi_greet(Hi obj) { obj.greet }        # `Hi` is the lower limit

multi_greet(Hi("Foo"))        # ok
multi_greet(Hey("Baz"))       # ok
multi_greet(Hello("Bar"))     # fail: `Hello` is too primitive

#
## Subseting with `where` block
#

func my_even(Number n < EvenNatural {|n| 10 < n }) {
    say "my_even(): #{n}"
}

my_even(42)

failtest(func { my_even(8) }, "my_even(8)")
failtest(func { my_even(25) }, "my_even(25)")
failtest(func { my_even("foo") }, "my_even('foo')")

#
## PointLimit example
#

subset PointLimit < Number {|n| n ~~ (-10 .. 10) }

class Point(x < PointLimit, y < PointLimit) {
    method to_s { "[#{x},#{y}]" }
}

var p1 = Point(x: 5, y: -6)
var p2 = Point(y: 5, x: -6)
var p3 = Point(10, -10)

say p1
say p2
say p3

assert_eq("#{p1}", "[5,-6]")
assert_eq("#{p2}", "[-6,5]")
assert_eq("#{p3}", "[10,-10]")

failtest(func { Point(x: 5, y: 20) }, "Point(x:5, y:20)")
failtest(func { Point(x: -20, y: 5) }, "Point(x:-20, y:5)")
failtest(func { Point(-5, 20) }, "Point(-5, 20)")
failtest(func { Point(30, -4) }, "Point(30, -4)")

#
## Redeclarations
#

subset Scalar      < Number,String;
subset Integer     < Scalar  { |n| n.is_int }
subset Natural     < Integer { |n| n.is_pos }
subset EvenNatural < Natural { |n| n.is_even }

func f(n < EvenNatural) {
     say n
}

f(42)
f(2)

failtest(func { f(43) }, "f(43)")
failtest(func { f(-42) }, "f(-42)")
failtest(func { f("foo") }, "f('foo')")

#
## Subsets inside modules
#

module Greeting {

    class Hello(name) {
        method greet { say "Hello, #{self.name}!" }
    }

    class Hi < Hello {
        method greet { say "Hi, #{self.name}!" }
    }

    subset Hello < Hi;

    func greet(obj < Hi) { obj.greet }       # `Hi` is the upper limit

    greet(Hi("Foo"))        # ok
    greet(Hello("Bar"))     # ok
}