package smartmatch::engine::core;
use strict;
use warnings;
use 5.010;
use B;
use Hash::Util::FieldHash qw(idhash);
use Scalar::Util qw(blessed looks_like_number reftype);
use overload ();
sub type {
my ($thing) = @_;
if (!defined($thing)) {
return 'undef';
}
elsif (blessed($thing) && reftype($thing) ne 'REGEXP') {
return 'Object';
}
elsif (my $reftype = reftype($thing)) {
if ($reftype eq 'ARRAY') {
return 'Array';
}
elsif ($reftype eq 'HASH') {
return 'Hash';
}
elsif ($reftype eq 'REGEXP') {
return 'Regex';
}
elsif ($reftype eq 'CODE') {
return 'CodeRef';
}
else {
return 'unknown ref';
}
}
else {
my $b = B::svref_2object(\$thing);
my $flags = $b->FLAGS;
if ($flags & (B::SVf_IOK | B::SVf_NOK)) {
return 'Num';
}
elsif (looks_like_number($thing)) {
return 'numish';
}
else {
return 'unknown';
}
}
}
sub match {
my ($a, $b, $seen) = @_;
if (type($b) eq 'undef') {
return !defined($a);
}
elsif (type($b) eq 'Object') {
my $overload = overload::Method($b, '~~');
# XXX this is buggy behavior and may be changed
# see http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2011-07/msg00214.html
if (!$overload && overload::Overloaded($b)) {
$overload = overload::Method($a, '~~');
die "no ~~ overloading on $b"
unless $overload;
return $a->$overload($b, 0);
}
die "no ~~ overloading on $b"
unless $overload;
return $b->$overload($a, 1);
}
elsif (type($b) eq 'CodeRef') {
if (type($a) eq 'Hash') {
return !grep { !$b->($_) } keys %$a;
}
elsif (type($a) eq 'Array') {
return !grep { !$b->($_) } @$a;
}
else {
return $b->($a);
}
}
elsif (type($b) eq 'Hash') {
if (type($a) eq 'Hash') {
my @a = sort keys %$a;
my @b = sort keys %$b;
return unless @a == @b;
for my $i (0..$#a) {
return unless $a[$i] eq $b[$i];
}
return 1;
}
elsif (type($a) eq 'Array') {
return grep { exists $b->{$_ // ''} } @$a;
}
elsif (type($a) eq 'Regex') {
return grep /$a/, keys %$b;
}
elsif (type($a) eq 'undef') {
return;
}
else {
return exists $b->{$a};
}
}
elsif (type($b) eq 'Array') {
if (type($a) eq 'Hash') {
return grep { exists $a->{$_ // ''} } @$b;
}
elsif (type($a) eq 'Array') {
return unless @$a == @$b;
if (!$seen) {
$seen = {};
idhash %$seen;
}
for my $i (0..$#$a) {
if (defined($b->[$i]) && $seen->{$b->[$i]}++) {
return $a->[$i] == $b->[$i];
}
return unless match($a->[$i], $b->[$i], $seen);
}
return 1;
}
elsif (type($a) eq 'Regex') {
return grep /$a/, @$b;
}
elsif (type($a) eq 'undef') {
return grep !defined, @$b;
}
else {
if (!$seen) {
$seen = {};
idhash %$seen;
}
return grep {
if (defined($_) && $seen->{$_}++) {
return $a == $_;
}
match($a, $_, $seen)
} @$b;
}
}
elsif (type($b) eq 'Regex') {
if (type($a) eq 'Hash') {
return grep /$b/, keys %$a;
}
elsif (type($a) eq 'Array') {
return grep /$b/, @$a;
}
else {
return $a =~ $b;
}
}
elsif (type($a) eq 'Object') {
my $overload = overload::Method($a, '~~');
return $a->$overload($b, 0) if $overload;
}
# XXX perlsyn currently has this undef case after the Num cases, but that's
# not how it's currently implemented
if (type($a) eq 'undef') {
return !defined($b);
}
elsif (type($b) eq 'Num') {
no warnings 'uninitialized', 'numeric'; # ugh
return $a == $b;
}
elsif (type($a) eq 'Num' && type($b) eq 'numish') {
return $a == $b;
}
else {
return $a eq $b;
}
}
1;