The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

use strict;
use Test::More tests => 79;
use Set::Object qw(ish_int is_int is_double is_string is_object
		   blessed reftype refaddr is_key);

is(is_int(0),         1, "is_int(0)");
is(is_int(7),         1, "is_int(7)");
is(is_key(7),         1, "is_key(7)");
is(is_int(7.0),   undef, "!is_int(7.0)");
is(is_key(7.0),       1, "is_key(7.0)");
is(is_int('7'),   undef, "!is_int('7')");
is(is_key('7'),       1, "is_key('7')");

is(is_string(7),     undef, "!is_string()");
is(is_string(7.0),   undef, "!is_string(7.0)");
is(is_string("7"),       1, "is_string('7')");

is(is_double(7),     undef, "!is_double(7)");
is(is_double(7.0),       1, "is_double(7.0)");
is(is_double("7"),   undef, "!is_double('7')");

# behvaiour for numeric strings
my $foo;
is(is_int($foo="7"), undef, "!is_int(\$foo = '7')");
is(is_double($foo),  undef, "!is_double($foo)");
is(ish_int($foo),    undef, "!ish_int($foo)");

# behaviour changes between Perls
#is(is_int($foo+0),       1, "is_int(\$foo + 0)");
is(is_int(int($foo)),    1, "is_int(int(\$foo))");
# behaviour changes between Perls
#is(is_double($foo),  undef, "is_double($foo)");
is(ish_int($foo),        7, "ish_int($foo)");

is(is_double($foo+0.01-0.01), 1, "is_double(\$foo + 0)");
is(is_double($foo),      1, "is_double($foo)");
is(is_int(int($foo)),    1, "is_int(int(\$foo))");
is(ish_int($foo),        7, "ish_int($foo)");

{
# no warnings for brevity
local($^W) = 0;

is(ish_int($foo = "7am"), undef,
   "!defined(ish_int($foo = '7am'))");
is(ish_int($foo + 0),   7, "ish_int(\$foo + 0) == 7");
# behaviour changes between Perls
#is(is_int($foo),    undef, "!is_int($foo)");
is(is_double($foo),     1, "is_double($foo)");
#diag("foo is $foo");
is(ish_int($foo),   undef, "!defined(ish_int($foo))");

is(ish_int($foo = "7.0"), undef,
   "!defined(ish_int($foo = '7.0'))");
is(ish_int($foo + 0),   7, "ish_int($foo + 0) == 7");
# behaviour changes between Perls
# is(is_int($foo),    undef, "!is_int($foo)");
is(is_double($foo),     1, "is_double($foo)");
is(ish_int($foo),   undef, "!defined(ish_int($foo))");

is(ish_int($foo = "7e6"), undef,
   "!defined(ish_int($foo = '7e6'))");
is(ish_int($foo + 0), 7e6, "ish_int($foo + 0) == 7e6");
# behaviour changes between Perls
# is(is_int($foo),    undef, "!is_int($foo)");
is(is_double($foo),     1, "is_double($foo)");
is(ish_int($foo),   undef, "!defined(ish_int($foo))");

is(ish_int($foo = "7"), undef,
   "!defined(ish_int($foo = '7'))");
is(ish_int($foo + 0.001 - 0.001),   7, "ish_int($foo + 0) == 7");
is(is_double($foo),     1, "is_double($foo)");
# behaviour changes between Perls
# is(is_int($foo),    undef, "is_int($foo)");
is(ish_int($foo),       7, "ish_int($foo) == 7");

is(ish_int($foo = "0"), undef,
   "!defined(ish_int($foo = '0'))");
is(ish_int($foo + 0.001 - 0.001),   0, "ish_int($foo + 0) == 0");
is(is_double($foo),     1, "is_double($foo)");
# behaviour changes between Perls
# is(is_int($foo),    undef, "is_int($foo)");
is(ish_int($foo),       0, "ish_int($foo) == 7");

# value must be within 1e-9 of an int
is(ish_int(7.000000001234), undef,
   "!ish_int(7.000000001234)");
is(ish_int(7.0000000001234), 7,
   "ish_int(7.0000000001234) == 7");

}

is(blessed($foo = []), undef, "!blessed(\$foo = [])");
is(is_key($foo), undef, "is_key([])");
is(reftype($foo), "ARRAY",
   "reftype(\$foo) eq 'ARRAY'");

bless $foo, "This";
is(blessed($foo), "This", "blessed(\$foo) eq 'This'");
is(reftype($foo), "ARRAY", "reftype(\$foo) eq 'ARRAY'");
is(is_key($foo), undef, "is_key(blessed array)");

$foo = {};
bless $foo, "This";
is(reftype({}),    "HASH", "reftype({})");
is(reftype($foo),  "HASH", "reftype(\$foo)");
is(is_key($foo), undef, "is_key(blessed hash)");

my %foo;
my $tiehandle = tie %foo, "This";

is(reftype(\%foo), "HASH", "reftype(\%foo) - tied");
is(reftype($tiehandle),
   "ARRAY", "reftype(\$tiehandle)");
untie(%foo);

my $psuedonum = psuedonum->new(7);

ok($psuedonum == 7, "Pseudonum numifies OK");
ok($psuedonum == 7.0, "Pseudonum numifies OK");
ok($psuedonum eq "7", "Pseudonum stringifies OK");
is(blessed($psuedonum), "psuedonum", "Pseudonum is blessed");
is(ish_int($psuedonum), 7, "ish_int(Pseudonum)");
is(is_key($psuedonum), 1, "is_key(psuedonum)");
$psuedonum = [ ];
is(is_key($psuedonum), undef, "is_key(psuedonum/hash)");

my $nevernum = nevernum->new(7);

eval { if ($nevernum == 7) { } };
ok($@, "nevernum dies when numified");
eval { if ($nevernum == 7.0) { } };
ok($@, "nevernum dies when doublified");
ok($nevernum eq "7", "nevernum stringifies OK");
ok(blessed($nevernum) eq "nevernum", "nevernum is blessed");
is(ish_int($nevernum), undef, "ish_int(Nevernum)");
is(is_key($nevernum), 1, "is_key(nevernum)");

my $notreallynum = notreallynum->new(7);

ok($notreallynum == 7, "notreallynum numifies OK");
ok($notreallynum == 7.0, "notreallynum numifies OK");
ok($notreallynum eq "7", "notreallynum stringifies OK");
ok(blessed($notreallynum) eq "notreallynum", "nevernum is blessed");
is(ish_int($notreallynum), undef, "ish_int(notreallynum)");
is(is_key($nevernum), 1, "is_key(notreallynum)");

# now test tied scalars
$tiehandle = tie $foo, "This";
$foo = 7;

ok(tied $foo, "\$foo is tied");

# my @spells = detect_magic($foo);
# ok(@spells && $spells[0] =~ m/Magic type q/,
# "Foo is definitely tied");

#use Devel::Peek qw(Dump);
#print Dump $foo;

is(ish_int($foo), 7, "ish_int(tied var)");
eval { _ish_int($foo) };
like($@, qr/tie/, "ish_int(tied var)");
is(is_key($foo), 1, "is_key(tied var)");

ok(refaddr($notreallynum) > 0 && refaddr($notreallynum) != refaddr($nevernum),
   "refaddr()");

exit(0);

# unused debugging function
sub showit {
    my $var = shift;
    if (defined $var) {
	if (is_int($var)) {
	    return $var;
	} elsif (is_double($var)) {
	    return sprintf("%e",$var);
	} elsif (is_string($var)) {
	    return "`$var'";
	} elsif (my $b = blessed($var)) {
	    return "Object($b)(".reftype($var).")";
	} else {
	    return "onion";
	}
    } else {
	return "undef";
    }
}
package This;

# this class is an array pretending to be a hash

sub TIESCALAR {
    my $invocant = shift;
    my $test = [ ];
    return bless $test, $invocant;
}

sub TIEHASH {
    my $invocant = shift;
    my $test = [ { } ];
    return bless $test, $invocant;
}

sub FETCH {
    my $self = shift;

    if (@_) {
	my $key = shift;
	if (my $idx = ish_int($key)) {
	    return $self->[$idx+1];
	} else {
	    if (exists $self->[0]->{$key}) {
		return $self->[$self->[0]->{$key}];
	    } else {
		return undef;
	    }
	}
    } else {
	# scalar fetch
	return $self->[0];
    }
}

sub STORE {
    my $self = shift;
    if (@_ == 2) {
	# hash set
	my $key  = shift;

	if (!defined $key) {
	    $key = "";
	}
    } elsif (@_ == 1) {
	# scalar set
	$self->[0] = shift;
    }
}

sub UNTIE {
    my $self = shift;
    @$self=();
}

package psuedonum;

use overload
    '""' => \&stringify,
    '0+' => \&numify,
    fallback => 1;

sub new {
    my $self = shift;
    my $val = shift;
    return bless { val => $val }
}

sub set {
    my $self = shift;
    my $val = shift;
    $self->{val} = $val;
}

sub stringify {
    my $self = shift;
    return "$self->{val}";
}

sub numify {
    my $self = shift;
    return $self->{val} + 0;
}

package notreallynum;

use overload
    '""' => \&stringify,
    fallback => 1;

sub new {
    my $self = shift;
    my $val = shift;
    return bless { val => $val }
}

sub set {
    my $self = shift;
    my $val = shift;
    $self->{val} = $val;
}

sub stringify {
    my $self = shift;
    return "$self->{val}";
}

package nevernum;

use overload
    '""' => \&stringify,
    'eq' => \&equal,
    fallback => 0;

sub new {
    my $self = shift;
    my $val = shift;
    return bless { val => $val }
}

sub set {
    my $self = shift;
    my $val = shift;
    $self->{val} = $val;
}

sub stringify {
    my $self = shift;
    return "$self->{val}";
}

sub equal {
    my $self = shift;
    my $other = shift;
    return $self->{val} eq $other;
}