#!/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}"
    }
}

do {
    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
#

do {
    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}"
    }
}

do {
    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
#

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

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

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

#
## Subseting with `where` block
#

do {
    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
#

do {
    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 }

do {
    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
}

#
## Subsets declared in modules and used outside
#

do {
    module Hello {
        subset NumStr < Num,Str
        subset Numeric < Num
    }

    class Foo { }
    class Subset::Test < Foo {
        method foo (arg < Hello::Numeric) {
            arg + 1
        }

        method bar (arg < Hello::NumStr) {
            arg + 2
        }
    }

    var obj = Subset::Test()
    assert_eq(obj.foo(41), 42)
    assert_eq(obj.bar(41), 43)
    assert_eq(obj.bar("foo"), "foo2")

    assert(Subset::Test.kind_of(Foo))

    module Subset {
        assert(Test.kind_of(main::Foo))
    }
}

#
## Subsets without type
#

do {
    subset Int      { .is_int }
    subset ShortLen { .len < 5 }

    func foo(a < Int)      { say a }
    func bar(s < ShortLen) { say s }

    foo(42)
    bar("foo")
    bar(1234)
    bar([1,2,3])

    failtest({ foo(sqrt(2)) },  "foo(sqrt(2))")
    failtest({ foo("string") }, "foo(string)")
    failtest({ bar("string") }, "bar(string)")
    failtest({ bar([1,2,3,4,5]) }, "bar([1,2,3,4,5])")
}

#
## Subsets without type, with inheritance
#

do {
    subset Int        { .is_int }
    subset Even < Int { .is_even }

    func foo(n < Even) {
        "#{n} -- ok"
    }

    assert_eq(foo(42), "42 -- ok")
    failtest({ foo(42.5) }, "foo(42.5)")
    failtest({ foo(43)   }, "foo(43)")
    failtest({ foo("string")   }, "foo('string')")
    assert_eq(foo(12), "12 -- ok")
}

do {

    subset Integer  < Number            {|x| x.is_int }
    subset Positive < Number            {|x| x.is_pos }
    subset Prime    < Positive, Integer {|x| x.is_prime }

    func foo(n < Prime)  {
        "#{n} -- ok"
    }

    assert_eq(foo(43), "43 -- ok")
    failtest({ foo(43.5) }, "foo(43.5)")
    failtest({ foo(42) }, "foo(42)")
    failtest({ foo(-43) }, "foo(-43)")
    failtest({ foo(-43.5) }, "foo(-43.5)")
    failtest({ foo("string") }, "foo('string')")
    assert_eq(foo(97), "97 -- ok")
}

do {
    subset NonEmptyString < String { .len > 0 };

    func bar(s < NonEmptyString) {
        ["bar", s]
    }

    func foo(NonEmptyString s) {
        ["foo", s]
    }

    assert_eq(bar("hi"), ["bar", "hi"])
    assert_eq(foo("hi"), ["foo", "hi"])

    assert_eq((try { bar("") } \\ 'bar("") error'), 'bar("") error')
    assert_eq((try { foo("") } \\ 'foo("") error'), 'foo("") error')
}