File Coverage

blib/lib/perl5i/2/equal.pm
Criterion Covered Total %
statement 75 77 97.4
branch 61 70 87.1
condition 32 44 72.7
subroutine 10 10 100.0
pod 0 1 0.0
total 178 202 88.1


line stmt bran cond sub pod time code
1             package perl5i::2::equal;
2              
3 6     6   36 use strict;
  6         14  
  6         618  
4 6     6   39 no if $] >= 5.018000, warnings => 'experimental::smartmatch';
  6         12  
  6         63  
5              
6 6     6   746 use perl5i::2::autobox;
  6         15  
  6         66  
7              
8             sub are_equal {
9 411     411 0 59194 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 411 100 66     1141 return 1 if !defined $r1 and !defined $r2;
21              
22             # One is defined, one isn't
23 410 100 75     1914 return if defined $r1 xor defined $r2;
24              
25 390         859 my( $ref1, $ref2 ) = (ref $r1, ref $r2);
26              
27 390 100 100     1978 if( !$ref1 and !$ref2 ) {
    100 100        
    100          
28 90         476 my $is_num1 = $r1->is_number;
29 90         357 my $is_num2 = $r2->is_number;
30 90 100 100     452 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         10 return 0;
34             }
35             elsif( $is_num1 ) {
36             # They're both numbers
37 53         288 return $r1 == $r2;
38             }
39             else {
40             # They're both strings
41 35         162 return $r1 eq $r2;
42             }
43             }
44             elsif( $ref1 eq $ref2 ) {
45 77 100       584 if ( $ref1 ~~ [qw(Regexp GLOB CODE)] ) {
    100          
    100          
    100          
46 12         84 return $r1 eq $r2;
47             }
48             elsif ( $ref1 eq 'ARRAY' ) {
49 12         33 return _equal_arrays( $r1, $r2 );
50             }
51             elsif ( $ref1 eq 'HASH' ) {
52 27         70 return _equal_hashes( $r1, $r2 );
53             }
54             elsif ( $ref1 ~~ [qw(SCALAR REF)] ) {
55 14         57 return are_equal($$r1, $$r2);
56             }
57             else {
58             # Must be an object
59 12         48 return _equal_objects( $r1, $r2 );
60             }
61             }
62             elsif( $ref1 and $ref2 ) {
63             # They're both refs, but not of the same type
64 116         319 my $is_overloaded1 = overload::Overloaded($r1);
65 116         4450 my $is_overloaded2 = overload::Overloaded($r2);
66              
67 116 100 100     3799 if( $is_overloaded1 and $is_overloaded2 ) {
68             # Two overloaded objects
69 6         30 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 110         474 return 0;
76             }
77             }
78             else {
79             # One is a ref, one is not
80 107 100       380 my $is_overloaded = $ref1 ? overload::Overloaded($r1)
81             : overload::Overloaded($r2);
82              
83 107 100       3840 if( $is_overloaded ) {
84             # One's an overloaded object, one's a plain scalar
85 20 50       66 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 87         334 return 0;
91             }
92             }
93             }
94              
95             sub _equal_arrays {
96 12     12   25 my ($r1, $r2) = @_;
97             # They can only be equal if they have the same nÂș of elements.
98 12 100       52 return if @$r1 != @$r2;
99              
100 9         162 foreach my $i (0 .. @$r1 - 1) {
101 24 100       73 return unless are_equal($r1->[$i], $r2->[$i]);
102             }
103              
104 7         81 return 1;
105             }
106              
107             sub _equal_hashes {
108 27     27   53 my ($r1, $r2) = @_;
109             # Hashes can't be equal unless their keys are equal.
110 27 100       132 return unless ( %$r1 ~~ %$r2 );
111              
112             # Compare the equality of the values for each key.
113 23         73 foreach my $key (keys %$r1) {
114 23 100       87 return unless are_equal( $r1->{$key}, $r2->{$key} );
115             }
116              
117 16         84 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 48 50   48   125 return unless ref $_[0];
124 48         143 my $str = overload::Method($_[0], q[""]);
125 48         1691 my $num = overload::Method($_[0], "0+");
126 48 50 66     1738 return "both" if $str and $num;
127 48 50 66     167 return "" if !$str and !$num;
128 48 100       127 return "str" if $str;
129 27 50       92 return "num" if $num;
130             }
131              
132             # Two objects, possibly different classes, both overloaded.
133             sub _equal_overload {
134 14     14   22 my($obj1, $obj2) = @_;
135              
136 14         27 my $type1 = _overload_type($obj1);
137 14         35 my $type2 = _overload_type($obj2);
138              
139             # One of them is not overloaded
140 14 50 33     59 return if !$type1 or !$type2;
141              
142 14 50 33     176 if( $type1 eq 'both' and $type2 eq 'both' ) {
    100 100        
    100 100        
    50 66        
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 6 100       228 return $type1 eq 'num' ? $obj1+0 eq "$obj2"
153             : $obj2+0 eq "$obj1";
154             }
155             elsif( 'num' ~~ [$type1, $type2] ) {
156 5         3098 return $obj1 == $obj2;
157             }
158             elsif( 'str' ~~ [$type1, $type2] ) {
159 3         118 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 12     12   24 my($r1, $r2) = @_;
169              
170             # No need to check both, they're the same class
171 12         44 my $is_overloaded = overload::Overloaded($r1);
172              
173 12 100       408 if( !$is_overloaded ) {
174             # Neither are overloaded, they're the same class, are they the same object?
175 4         34 return $r1 eq $r2;
176             }
177             else {
178 8         25 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 20     20   34 my($obj, $scalar) = @_;
193              
194 20         43 my $type = _overload_type($obj);
195 20 50       44 return unless $type;
196              
197 20 100       145 if( $scalar->is_number ) {
198 10 100       29 if( $type eq 'str' ) {
199 4         147 $obj eq $scalar;
200             }
201             else {
202 6         287 $obj == $scalar;
203             }
204             }
205             else {
206 10 100       28 if( $type eq 'num' ) {
207             # Can't reliably compare
208 5         28 return;
209             }
210             else {
211 5         241 $obj eq $scalar;
212             }
213             }
214             }
215              
216             1;