1 /*******************************************************************************
2 
3     Value comparison for structs and arbitrary types.
4 
5     Does a deep equality comparison of one type to another.
6 
7     'Deep' meaning:
8         * The _contents_ of dynamic arrays are compared
9         * Types are recursed, allowing multi-dimensional arrays to be compared
10         * All members of structs are compared (recursively, if needed).
11 
12     Copyright:
13         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
14         All rights reserved.
15 
16     License:
17         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
18         Alternatively, this file may be distributed under the terms of the Tango
19         3-Clause BSD License (see LICENSE_BSD.txt for details).
20 
21 *******************************************************************************/
22 
23 module ocean.core.DeepCompare;
24 
25 
26 
27 import ocean.meta.types.Qualifiers;
28 
29 version (unittest) import ocean.core.Test;
30 
31 /***************************************************************************
32 
33     Given a type T, returns true if the type contains a struct or
34     a floating-point type.
35 
36 ***************************************************************************/
37 
38 private template needsSpecialCompare(T)
39 {
40     static if ( is(T V : V[]) )
41     {
42         // T is an array. Strip off all of the [] to get
43         // the ultimate element type.
44 
45         enum
46         {
47             needsSpecialCompare = needsSpecialCompare!(V)
48         };
49     }
50     else
51     {
52         // It's special if it has NaN, or is a struct
53         enum
54         {
55             needsSpecialCompare = is ( typeof (T.nan) )  ||
56                                   is ( T == struct )
57         };
58     }
59 }
60 
61 /***************************************************************************
62 
63     Compares two values and returns true if they are equal.
64 
65     This differs from built-in == in two respects.
66 
67         1. Dynamic arrays are compared by value, even when they are struct members.
68 
69         2. Floating point numbers which are NaN are considered equal.
70            This preserves the important property that deepEquals(x, x) is true
71            for all x.
72 
73     Classes are compared in the normal way, using opEquals.
74 
75     Params:
76         a    = Struct to be compared with b
77         b    = Struct to be compared with a
78 
79     Returns:
80         true if equal
81 
82 ***************************************************************************/
83 
84 
85 public bool deepEquals(T)(T a, T b)
86 {
87     static if ( is (typeof(T.nan)) )
88     {
89         //pragma(msg, "Comparing float: " ~ T.stringof);
90 
91         // Deal with NaN.
92         // If x is NaN, then x == x is false
93         // So we return true if both a and b are NaN.
94         // (In D2, we'd just use "return a is b").
95 
96         return a == b  || ( a != a && b != b);
97     }
98     else static if ( is ( T == struct) )
99     {
100         //pragma(msg, "Comparing struct: " ~ T.stringof);
101 
102         foreach(i, U; typeof(a.tupleof) )
103         {
104             static if ( needsSpecialCompare!(U) )
105             {
106                 //pragma(msg, "Comparing special element " ~ U.stringof );
107 
108                 // It is a special type (struct or float),
109                 // or an array of special types
110 
111                 if ( !deepEquals(a.tupleof[i], b.tupleof[i]) )
112                 {
113                     return false;
114                 }
115             }
116             else
117             {
118                 //pragma(msg, "\t not a special case: " ~ typeof(a.tupleof[i]).stringof);
119 
120                 if ( a.tupleof[i] != b.tupleof[i] )
121                 {
122                     return false;
123                 }
124             }
125         }
126         return true;
127     }
128     else static if ( is(T V : V[]) )
129     {
130         // T is an array.
131         // If it is one of the special cases, we need to
132         // do an element-by-element compare.
133 
134         static if ( needsSpecialCompare!(V) )
135         {
136             //pragma(msg, "Comparing element-by-element of array " ~ T.stringof);
137 
138             // Compare element-by-element.
139 
140             if (a.length != b.length)
141             {
142                 return false;
143             }
144 
145             foreach ( j, m; a )
146             {
147                 if ( !deepEquals(m, b[j]) )
148                     return false;
149             }
150 
151             return true;
152         }
153         else
154         {
155             //pragma(msg, "Simple array compare " ~ T.stringof);
156 
157             // Not a special case, we can just use the builtin ==.
158             // Note that this works even for the multidimensional case.
159 
160             return a == b;
161         }
162     }
163     else
164     {
165         //pragma(msg, "\t not a special case" ~ T.stringof);
166         return a == b;
167     }
168 }
169 
170 
171 unittest
172 {
173     struct S0
174     {
175     }
176 
177     struct S1
178     {
179         int [] x;
180     }
181 
182     struct S2
183     {
184         S1 [] y;
185     }
186 
187     struct S3
188     {
189         S1 [][] z;
190     }
191 
192     struct S4
193     {
194        double x;
195     }
196 
197     struct S5
198     {
199        double[] x;
200     }
201 
202     S0 a, b;
203     test(deepEquals(a, b));
204     S1 a1, b1;
205     a1.x = [ 1, 2, 3];
206     b1.x = [ 1, 2, 3];
207     test(a1 !is b1);
208     test(deepEquals(a1, b1));
209     test(a1.x == b1.x);
210     S2 a2, b2;
211     a2.y = [a1];
212     b2.y = [b1];
213     test(deepEquals(a2, b2));
214     S3 a3, b3;
215     a3.z = [a2.y];
216     b3.z = [b2.y];
217     test(deepEquals(a3, b3));
218     b3.z = [null];
219     test(!deepEquals(a3, b3));
220     S4 a4;
221     a4.x = double.nan;
222     test(deepEquals(a4, a4));
223     S5 a5;
224     a5.x ~= double.nan;
225     test(deepEquals(a5, a5));
226 }