1 /*
2     Numdiff - compare putatively similar files,
3     ignoring small numeric differences
4     Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017  Ivano Primi  <ivprimi@libero.it>
5 
6     This program is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include "numdiff.h"
23 #include "xalloc.h"
24 
25 extern Real Zero;
26 
27 /* Care that internally the field numbers start from zero, not from one */
28 const long FIELDNO_MAX = (8 * FIELDMASK_SIZE);
29 const char separator = ':';
30 
31 static const struct numfmt defaults = {CURRENCY,
32 				       DP,
33 				       THSEP,
34 				       GROUPING,
35 				       POS_SIGN,
36 				       NEG_SIGN,
37 				       ECH,
38 				       IU};
39 
40 /*
41   Create a new stack of threshold values, then
42   push onto the stack the element
43 
44   {
45    threshold = 0.0, beg1 = 0, end1 = FIELDNO_MAX-1,
46                     beg2 = 0, end2 = FIELDNO_MAX-1,
47    double_range_spec = 0
48   }
49 
50   and return a pointer to the top of the stack.
51  */
52 
thrlist_new(void)53 thrlist thrlist_new (void)
54 {
55   thrlist list;
56 
57   list = (thrlist) xmalloc (sizeof (thrlist_node));
58   initR (&list->threshold);
59   /* copyR (&list->threshold, Zero); */
60   list->beg1 = list->beg2 = 0;
61   list->end1 = list->end2 = FIELDNO_MAX - 1;
62   list->double_range_spec = 0;
63   list->next = NULL;
64   return list;
65 }
66 
67 
68 /*
69   If the string STR begins with a valid range of positive integer values, then
70   set *B to (start value - 1), *E to (end value - 1), and, if TAIL != NULL,
71   set *TAIL so that it points to the first character of STR after the range
72   specification. Finally, return THRLIST_OK (0).
73   For example, if STR == "1-10#abc", then *B will be set to 0, *E to 9, and
74   *TAIL will point to the character '#'.
75   If the string STR does not begin with a valid range of positive integer values,
76   then leave *B, *E and *TAIL unchanged and return the error code THRLIST_INVALID_FORMAT.
77 
78   Remarks:
79 
80   1. The following abbreviated range specifications are admitted, where N is a positive
81      integer value and M == FIELDNO_MAX :
82 
83     N    stays for N-N
84    -N    stays for 1-N
85     N-   stays for N-M .
86 
87   2. The extremal values of a valid range are integer numbers between
88      1 and FIELDNO_MAX. In addition, the start value may not be greater than
89      the end value. In case these conditions are not both true
90      the function returns THRLIST_INVALID_FORMAT.
91 */
92 
set_interval(const char * str,unsigned long * b,unsigned long * e,char ** tail)93 static int set_interval (const char *str, unsigned long *b, unsigned long *e, char **tail)
94 {
95   long beg, end;
96   char *ptr, *endptr;
97 
98   beg = end = -1;
99   if (!str || !*str)
100     return THRLIST_INVALID_FORMAT; /* illegal input */
101 
102   /* If we arrive here we are sure that *str != '\0' ! */
103   if ((beg = strtol (str, &endptr, 10)) == 0
104       || beg > FIELDNO_MAX || beg < -FIELDNO_MAX)
105     return THRLIST_INVALID_FORMAT; /* illegal input */
106   else if (beg < 0)
107     {
108       if (*endptr == '\0' || *endptr == separator)
109 	{
110 	  end = -beg;
111 	  beg = 1;
112 	}
113       else
114 	return THRLIST_INVALID_FORMAT;
115     }
116   else if (*endptr == '\0' || *endptr == separator)
117     end = beg;
118   else if (*endptr == '-')
119     {
120       ptr = endptr + 1;
121       if (*ptr == '\0' || *ptr == separator)
122 	end = FIELDNO_MAX;
123       else
124 	{
125 	  if ((end = strtol (ptr, &endptr, 10)) <= 0
126 	      || (*endptr != '\0' && *endptr != separator) || end > FIELDNO_MAX)
127 	    return THRLIST_INVALID_FORMAT; /* illegal input */
128 	}
129     }
130   if (beg > end)
131     return THRLIST_INVALID_FORMAT;
132   else
133     {
134       *b = (unsigned long)(beg-1);
135       *e = (unsigned long)(end-1);
136       if (tail != NULL)
137 	*tail = endptr;
138       return THRLIST_OK;
139     }
140 }
141 
142 
143 /*
144   If the string DEF contains a specification of the form
145 
146   RANGE1:RANGE2,
147 
148   where the ranges RANGE1 and RANGE2 are both in the form accepted by
149   the function set_interval() and have the same length, then
150   set *BEG1, *END1, *BEG2 and *END2 to the extremal
151   values of RANGE1 and RANGE2, respectively, after
152   decrementing them of 1.
153   Then set *DBL_RNG_SPEC to 1 and return THRLIST_OK
154   to the calling function.
155 
156   If the string DEF contains a specification of the form
157 
158   RANGE,
159 
160   where RANGE is in the form accepted by the function
161   set_interval(), then set *BEG1 and *BEG2 to
162   (start_value_of_RANGE - 1), *END1 and *END2
163   (end_value_of_RANGE - 1), and *DBL_RNG_SPEC to zero.
164   Then return THRLIST_OK to the calling function.
165 
166   If DEF does not contain a valid specification of the form
167 
168   RANGE  or  RANGE1:RANGE2
169 
170   then return THRLIST_INVALID_FORMAT.
171   If the given specification is valid, but
172   RANGE1 and RANGE2 do not have the same length,
173   then return THRLIST_INVALID_RANGES.
174 
175   Remark: In the last two cases after returning
176   from the function the values of
177 
178   *BEG1, *BEG2, *END1, *END2 and *DBL_RNG_SPEC
179 
180   can not be trusted.
181  */
182 
set_intervals(const char * def,unsigned long * beg1,unsigned long * beg2,unsigned long * end1,unsigned long * end2,int * dbl_rng_spec)183 static int set_intervals (const char *def,
184 			  unsigned long *beg1, unsigned long *beg2,
185 			  unsigned long *end1, unsigned long *end2,
186 			  int *dbl_rng_spec)
187 {
188   char *endptr;
189   int rv;
190 
191   rv = set_interval (def, beg1, end1, &endptr);
192   if (rv != 0)
193     return rv;
194   else if (*endptr == separator)
195     {
196       rv = set_interval (endptr + 1, beg2, end2, &endptr);
197       if ((rv != 0) || (*endptr != '\0'))
198 	return THRLIST_INVALID_FORMAT;
199       else
200 	{
201 	  *dbl_rng_spec = 1;
202 	  return (*end2 + *beg1 == *end1 + *beg2) ? THRLIST_OK : THRLIST_INVALID_RANGES;
203 	}
204     }
205   else
206     {
207       /* *endptr == '\0' */
208       *beg2 = *beg1;
209       *end2 = *end1;
210       *dbl_rng_spec = 0;
211       return THRLIST_OK;
212     }
213 }
214 
215 
216 /*
217   Push onto the stack pointed to by PLIST a new element
218   according to the specification contained in the string
219   DEF.
220 
221   This specification must have the form
222 
223   1.          THRESHOLD             or
224 
225   2.          THRESHOLD:RANGE       or
226 
227   3.          THRESHOLD:RANGE1:RANGE2
228 
229   where THRESHOLD is a non-negative real (decimal) number,
230   and RANGE or RANGE1:RANGE2 must be a valid specification for
231   the function set_intervals().
232 
233   In case 1. and 2. set the field DOUBLE_RANGE_SPEC of
234   the just pushed element to zero, otherwise set it to 1.
235 
236   If DEF does not contain a valid specification, then
237   do nothing but return a suitable error code
238   (either THRLIST_INVALID_FORMAT or THRLIST_INVALID_RANGES).
239   Otherwise, return THRLIST_OK.
240  */
241 
thrlist_add(thrlist * plist,const char * def)242 int thrlist_add (thrlist *plist, const char* def)
243 {
244   int rv, dbl_rng_spec;
245   Real r;
246   unsigned long beg1, beg2, end1, end2;
247   thrlist_node *pnode;
248   char *endptr;
249 
250   if (!def || !*def)
251     return THRLIST_INVALID_FORMAT; /* no input */
252 
253   /* If we arrive here we are sure that *def != '\0' ! */
254   initR (&r);
255   str2R (def, &endptr, ISCALE, &defaults, &r);
256   if (endptr == def || cmp(r, Zero) < 0)
257     {
258       delR (&r);
259       return THRLIST_INVALID_FORMAT; /* no valid positive number */
260     }
261   else
262     {
263       if (*endptr == '\0')
264 	{
265 	  beg1 = beg2 = 0;
266 	  end1 = end2 = FIELDNO_MAX - 1;
267 	  dbl_rng_spec = 0;
268 	}
269       else if (*endptr == separator)
270 	{
271 	  if ( (rv = set_intervals (endptr + 1, &beg1, &beg2, &end1, &end2, &dbl_rng_spec)) != 0)
272 	    {
273 	      delR (&r);
274 	      return rv;
275 	    }
276 	}
277       else
278 	{
279 	  delR (&r);
280 	  return THRLIST_INVALID_FORMAT;
281 	}
282       pnode = (thrlist_node*) xmalloc (sizeof (thrlist_node));
283       initR (&pnode->threshold);
284       copyR (&pnode->threshold, r);
285       delR (&r);
286       pnode->beg1 = beg1;
287       pnode->beg2 = beg2;
288       pnode->end1 = end1;
289       pnode->end2 = end2;
290       pnode->double_range_spec = dbl_rng_spec;
291       pnode->next = *plist;
292       *plist = pnode;
293       return THRLIST_OK;
294     }
295 }
296 
297 
298 /*
299   Look in the stack LIST for the first element E from the top such that
300   either E.DOUBLE_RANGE_SPEC == 0 and
301   E.BEG1 <= FIELDNO1 <= E.END1, E.BEG2 <= FIELDNO2 <= E.END2, or
302   E.DOUBLE_RANGE_SPEC == 1 and
303   E.BEG1 <= FIELDNO1 <= E.END1, FIELDNO2 == E.BEG2 + FIELDNO1 - E.BEG1 .
304   Then return the value of the comparison test between R and E.THRESHOLD.
305 
306   If there is no element E in LIST satisfying one of the previous conditions
307   (this should never happen if LIST has been created by thrlist_new()),
308   then interrupt the main program via exit(EXIT_TROUBLE) after printing
309   a suitable error message on stdout.
310  */
311 
thrlist_cmp(Real r,thrlist list,unsigned long fieldno1,unsigned long fieldno2)312 int thrlist_cmp (Real r, thrlist list, unsigned long fieldno1, unsigned long fieldno2)
313 {
314   thrlist_node *pnode, *pnext;
315 
316   pnode = list;
317   while (pnode != NULL)
318     {
319       pnext = pnode->next;
320       if ( (pnode->double_range_spec) )
321 	{
322 	  if (fieldno1 >= pnode->beg1 && fieldno1 <= pnode->end1 &&
323 	      fieldno2 == pnode->beg2 + fieldno1 - pnode->beg1)
324 	    {
325 	      return cmp (r, pnode->threshold); /* found */
326 	    }
327 	}
328       else
329 	{
330 	  if (fieldno1 >= pnode->beg1 && fieldno1 <= pnode->end1 &&
331 	      fieldno2 >= pnode->beg2 && fieldno2 <= pnode->end2)
332 	    {
333 	      return cmp (r, pnode->threshold); /* found */
334 	    }
335 	}
336       pnode = pnext;
337     }
338   /*
339     The code execution should NEVER reach this point.
340    */
341   printf (_("Fatal error occurred during comparison of two numerical fields\n"));
342   exit (EXIT_TROUBLE);
343 }
344 
345 
346 /*
347   Dispose the stack pointed to by PLIST (free and clean the memory).
348  */
349 
thrlist_dispose(thrlist * plist)350 void thrlist_dispose (thrlist *plist)
351 {
352   thrlist_node *pnode, *pnext;
353 
354   if (plist != NULL)
355     {
356       pnode = *plist;
357       while (pnode != NULL)
358 	{
359 	  pnext = pnode->next;
360 	  delR (&pnode->threshold);
361 	  free ((void*)pnode);
362 	  pnode = pnext;
363 	}
364       *plist = NULL;
365     }
366 }
367