summaryrefslogblamecommitdiffstats
path: root/lib/smartmatch/engine/core.pm
blob: ee3c81e0cad8233ecc2aea0d0f3b29034b63404e (plain) (tree)
1
2
3
4
5
6
7


                                 
          

      
                                     








                                                       
                                                            





















                                           
                                                 











                                           
                            





                                                  









                                                                                          

                                     
                                    













                                                






                                               

                                     
                                                      












                                       
                                                      


                                     



                              
                                 



                                                               









                                      









                                                   














                                                  
                                                 
     
 

                                                                               




                                                     




                                                       





                        
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;