File Coverage

blib/lib/perl5i/2/equal.pm
Criterion Covered Total %
statement 73 77 94.8
branch 55 70 78.5
condition 28 44 63.6
subroutine 10 10 100.0
pod 0 1 0.0
total 166 202 82.1


line stmt bran cond sub pod time code
1             package perl5i::2::equal;
2              
3 4     4   17 use strict;
  4         6  
  4         169  
4 4     4   15 no if $] >= 5.018000, warnings => 'experimental::smartmatch';
  4         8  
  4         27  
5              
6 4     4   244 use perl5i::2::autobox;
  4         6  
  4         31  
7              
8             sub are_equal {
9 194     194 0 28923 my ($r1, $r2) = @_;
10              
11             # given two scalars, decide whether they are identical or not,
12             # recursing over deep data structures. Since it uses recursion,
13             # traversal is done depth-first.
14             # Warning: complex if-then-else decision tree ahead. It's ordered on
15             # my perceived and anecdotical take on the frequency of occurrence
16             # of each reftype: most popular on top, most rare on the bottom.
17             # This way we return as early as possible.
18              
19             # undef eq undef
20 194 100 66     435 return 1 if !defined $r1 and !defined $r2;
21              
22             # One is defined, one isn't
23 193 100 75     902 return if defined $r1 xor defined $r2;
24              
25 173         270 my( $ref1, $ref2 ) = (ref $r1, ref $r2);
26              
27 173 100 100     793 if( !$ref1 and !$ref2 ) {
    100 100        
    100          
28 24         114 my $is_num1 = $r1->is_number;
29 24         51 my $is_num2 = $r2->is_number;
30 24 100 100     94 if( $is_num1 xor $is_num2 ) {
    100          
31             # One's looks like a number, the other doesn't.
32             # Can't be equal.
33 2         9 return 0;
34             }
35             elsif( $is_num1 ) {
36             # They're both numbers
37 17         68 return $r1 == $r2;
38             }
39             else {
40             # They're both strings
41 5         19 return $r1 eq $r2;
42             }
43             }
44             elsif( $ref1 eq $ref2 ) {
45 24 100       140 if ( $ref1 ~~ [qw(Regexp GLOB CODE)] ) {
    100          
    100          
    100          
46 5         22 return $r1 eq $r2;
47             }
48             elsif ( $ref1 eq 'ARRAY' ) {
49 4         9 return _equal_arrays( $r1, $r2 );
50             }
51             elsif ( $ref1 eq 'HASH' ) {
52 7         18 return _equal_hashes( $r1, $r2 );
53             }
54             elsif ( $ref1 ~~ [qw(SCALAR REF)] ) {
55 4         11 return are_equal($$r1, $$r2);
56             }
57             else {
58             # Must be an object
59 4         8 return _equal_objects( $r1, $r2 );
60             }
61             }
62             elsif( $ref1 and $ref2 ) {
63             # They're both refs, but not of the same type
64 86         170 my $is_overloaded1 = overload::Overloaded($r1);
65 86         2261 my $is_overloaded2 = overload::Overloaded($r2);
66              
67 86 50 66     2045 if( $is_overloaded1 and $is_overloaded2 ) {
68             # Two overloaded objects
69 0         0 return _equal_overload( $r1, $r2 );
70             }
71             else {
72             # One's an overloaded object, the other is not or
73             # Two plain refs different type or
74             # non-overloaded objects of different type.
75 86         298 return 0;
76             }
77             }
78             else {
79             # One is a ref, one is not
80 39 100       106 my $is_overloaded = $ref1 ? overload::Overloaded($r1)
81             : overload::Overloaded($r2);
82              
83 39 100       1061 if( $is_overloaded ) {
84             # One's an overloaded object, one's a plain scalar
85 4 50       14 return $ref1 ? _equal_overload_vs_scalar($r1, $r2)
86             : _equal_overload_vs_scalar($r2, $r1);
87             }
88             else {
89             # One's a plain ref or object, one's a plain scalar
90 35         115 return 0;
91             }
92             }
93             }
94              
95             sub _equal_arrays {
96 4     4   5 my ($r1, $r2) = @_;
97             # They can only be equal if they have the same nº of elements.
98 4 100       14 return if @$r1 != @$r2;
99              
100 3         11 foreach my $i (0 .. @$r1 - 1) {
101 6 50       13 return unless are_equal($r1->[$i], $r2->[$i]);
102             }
103              
104 3         15 return 1;
105             }
106              
107             sub _equal_hashes {
108 7     7   7 my ($r1, $r2) = @_;
109             # Hashes can't be equal unless their keys are equal.
110 7 50       27 return unless ( %$r1 ~~ %$r2 );
111              
112             # Compare the equality of the values for each key.
113 7         17 foreach my $key (keys %$r1) {
114 7 100       27 return unless are_equal( $r1->{$key}, $r2->{$key} );
115             }
116              
117 6         22 return 1;
118             }
119              
120             # Returns the code which will run when the object is used as a string
121             require overload;
122             sub _overload_type {
123 8 50   8   16 return unless ref $_[0];
124 8         18 my $str = overload::Method($_[0], q[""]);
125 8         192 my $num = overload::Method($_[0], "0+");
126 8 50 66     173 return "both" if $str and $num;
127 8 50 66     20 return "" if !$str and !$num;
128 8 100       16 return "str" if $str;
129 4 50       18 return "num" if $num;
130             }
131              
132             # Two objects, possibly different classes, both overloaded.
133             sub _equal_overload {
134 2     2   3 my($obj1, $obj2) = @_;
135              
136 2         6 my $type1 = _overload_type($obj1);
137 2         6 my $type2 = _overload_type($obj2);
138              
139             # One of them is not overloaded
140 2 50 33     9 return if !$type1 or !$type2;
141              
142 2 50 33     32 if( $type1 eq 'both' and $type2 eq 'both' ) {
    50 66        
    100 66        
    50 33        
143 0   0     0 return $obj1 == $obj2 || $obj1 eq $obj2;
144             }
145             elsif(
146             ($type1 eq 'num' and $type2 eq 'str') or
147             ($type1 eq 'str' and $type2 eq 'num')
148             )
149             {
150             # They're not both numbers, not both strings, and not both both
151             # Must be str vs num.
152 0 0       0 return $type1 eq 'num' ? $obj1+0 eq "$obj2"
153             : $obj2+0 eq "$obj1";
154             }
155             elsif( 'num' ~~ [$type1, $type2] ) {
156 1         32 return $obj1 == $obj2;
157             }
158             elsif( 'str' ~~ [$type1, $type2] ) {
159 1         29 return $obj1 eq $obj2;
160             }
161             else {
162 0         0 die "Should never be reached";
163             }
164             }
165              
166             # Two objects, same class
167             sub _equal_objects {
168 4     4   6 my($r1, $r2) = @_;
169              
170             # No need to check both, they're the same class
171 4         12 my $is_overloaded = overload::Overloaded($r1);
172              
173 4 100       131 if( !$is_overloaded ) {
174             # Neither are overloaded, they're the same class, are they the same object?
175 2         10 return $r1 eq $r2;
176             }
177             else {
178 2         4 return _equal_overload( $r1, $r2 );
179             }
180             }
181              
182             # One overloaded object, one plain scalar
183             # STRING != OBJ
184             # NUMBER != OBJ
185             # STRING eq OBJeq
186             # STRING eq OBJboth
187             # STRING != OBJ== (using == will throw a warning)
188             # NUMBER == OBJ==
189             # NUMBER eq OBJeq
190             # NUMBER == OBJboth
191             sub _equal_overload_vs_scalar {
192 4     4   4 my($obj, $scalar) = @_;
193              
194 4         6 my $type = _overload_type($obj);
195 4 50       9 return unless $type;
196              
197 4 100       18 if( $scalar->is_number ) {
198 2 100       7 if( $type eq 'str' ) {
199 1         26 $obj eq $scalar;
200             }
201             else {
202 1         25 $obj == $scalar;
203             }
204             }
205             else {
206 2 100       5 if( $type eq 'num' ) {
207             # Can't reliably compare
208 1         7 return;
209             }
210             else {
211 1         26 $obj eq $scalar;
212             }
213             }
214             }
215              
216             1;