File Coverage

text-fuzzy-perl.c
Criterion Covered Total %
statement 114 127 89.7
branch 72 106 67.9
condition n/a
subroutine n/a
pod n/a
total 186 233 79.8


line stmt bran cond sub pod time code
1             /* This file is included into "Fuzzy.xs". The reason for having it as
2             a separate file from "Fuzzy.xs" is so that this file can easily be
3             edited in Emacs C mode without the C mode causing problems when
4             editing "Fuzzy.xs". */
5              
6             /* Get memory via Perl. */
7              
8             #define get_memory(value, number, what) { \
9             Newxz (value, number, what); \
10             if (! value) { \
11             croak ("%s:%d: " \
12             "Could not allocate memory for %d %s", \
13             __FILE__, __LINE__, number, #what); \
14             } \
15             text_fuzzy->n_mallocs++; \
16             }
17              
18             typedef enum {
19             tfp_ok,
20             tfp_unicode_failure,
21             tfp_text_fuzzy_error,
22             }
23             tfp_status_t;
24              
25             #define TFPCALL(x) { \
26             tfp_status_t status = x; \
27             if (status != tfp_ok) { \
28             return status; \
29             } \
30             }
31              
32             /* Send a bad return value from one of the C routines in
33             "text-fuzzy.c" back to the user via Perl's error handlers. The
34             parameters "file_name" and "line_number" are the name of the C file
35             and the line number where the error occurred in the C file. These
36             are discarded by this error handler. */
37              
38 0           int perl_error_handler (const char * file_name, int line_number,
39             const char * format, ...)
40             {
41             va_list a;
42             // warn ("%s:%d: ", file_name, line_number);
43 0           va_start (a, format);
44 0           vcroak (format, & a);
45             va_end (a);
46             return 0;
47             }
48              
49             #define SMALL 0x1000
50              
51             /* Decide how many ints to allocate for "text_fuzzy->b.unicode". It
52             has to be bigger than "minimum", the actual length of the
53             string. Also, we don't want to keep reallocating it, so make it
54             large enough for most of the cases (SMALL). */
55              
56 17           static void fake_length (text_fuzzy_t * text_fuzzy, int minimum)
57             {
58 17           int r = SMALL;
59             again:
60 17 50         if (minimum < r) {
61 17           text_fuzzy->b_unicode_length = r;
62 17           return;
63             }
64 0           r *= 2;
65 0 0         if (r > STRING_MAX_CHARS) {
66              
67             /* Stupid value. */
68              
69 0           croak ("String length %d longer than maximum allowed for, %d.\n",
70             minimum, STRING_MAX_CHARS);
71             }
72 0           goto again;
73             }
74              
75             /* Allocate the memory for b. */
76              
77             static void
78 67           allocate_b_unicode (text_fuzzy_t * text_fuzzy, int b_length)
79             {
80              
81 67 100         if (! text_fuzzy->b.unicode) {
82              
83             /* We have not allocated any memory yet. */
84              
85 17           fake_length (text_fuzzy, b_length);
86 17 50         get_memory (text_fuzzy->b.unicode,
    50          
87             text_fuzzy->b_unicode_length, int);
88             }
89 50 50         else if (b_length > text_fuzzy->b_unicode_length) {
90              
91             /* "b" is bigger than what we allowed for. */
92              
93 0           fake_length (text_fuzzy, b_length);
94              
95             /* "Renew" is "realloc" for Perl. See "perldoc perlapi". */
96              
97 0 0         Renew (text_fuzzy->b.unicode, text_fuzzy->b_unicode_length, int);
98             }
99 67           }
100              
101             /* Given a Perl string in "text" which is marked as being Unicode
102             characters, use Perl's Unicode handlers to turn it into a string of
103             integers. */
104              
105             static tfp_status_t
106 82           sv_to_int_ptr (SV * text, text_fuzzy_string_t * tfs)
107             {
108             #if PERL_REVISION == 5 && PERL_VERSION < 16
109             int i;
110             const U8 * utf;
111             STRLEN curlen;
112             STRLEN length;
113             const unsigned char * stuff;
114            
115             stuff = (const unsigned char *) SvPV (text, length);
116            
117             utf = stuff;
118             curlen = length;
119             for (i = 0; i < tfs->ulength; i++) {
120             STRLEN len;
121              
122             /* The documentation for "utf8n_to_uvuni" can be found in
123             "perldoc perlapi". There is an online version here:
124             "http://perldoc.perl.org/perlapi.html#Unicode-Support". */
125            
126             tfs->unicode[i] = utf8n_to_uvuni (utf, curlen, & len, 0);
127             curlen -= len;
128             utf += len;
129             }
130             return tfp_ok;
131             #else /* Perl version/revision */
132             int i;
133             const U8 * utf;
134             const U8 * send;
135             STRLEN length;
136              
137 82 50         utf = (const U8 *) SvPV (text, length);
138 82           send = utf + length;
139 757 100         for (i = 0; i < tfs->ulength; i++) {
140             STRLEN len;
141 675 50         tfs->unicode[i] = (int) utf8_to_uvchr_buf (utf, send, & len);
142 675 50         if (len == -1 || tfs->unicode[i] == 0) {
    50          
143 0           return tfp_unicode_failure;
144             }
145 675           utf += len;
146             }
147 82           return tfp_ok;
148             #endif /* Perl version/revision */
149             }
150              
151             /* Convert a Perl SV into the text_fuzzy_t structure. */
152              
153             static tfp_status_t
154 43           sv_to_text_fuzzy (SV * text, text_fuzzy_t ** text_fuzzy_ptr)
155             {
156             STRLEN length;
157             const unsigned char * stuff;
158             text_fuzzy_t * text_fuzzy;
159             int i;
160             int is_utf8;
161             char * copy;
162              
163             /* Allocate memory for "text_fuzzy". */
164 43 50         get_memory (text_fuzzy, 1, text_fuzzy_t);
165 43           text_fuzzy->max_distance = NO_MAX_DISTANCE;
166              
167             /* Copy the string in "text" into "text_fuzzy". */
168 43 50         stuff = (const unsigned char *) SvPV (text, length);
169 43 50         get_memory (copy, length + 1, char);
170 427 100         for (i = 0; i < (int) length; i++) {
171 384           copy[i] = stuff[i];
172             }
173 43           copy[length] = '\0';
174 43           text_fuzzy->text.length = length;
175 43           text_fuzzy->text.text = copy;
176 43           is_utf8 = SvUTF8 (text);
177 43 100         if (is_utf8) {
178              
179             /* Put the Unicode version of the string into
180             "text_fuzzy->text". */
181              
182 15           text_fuzzy->unicode = 1;
183 15           text_fuzzy->text.ulength = sv_len_utf8 (text);
184              
185 15 50         get_memory (text_fuzzy->text.unicode, text_fuzzy->text.ulength, int);
    50          
186              
187 15 50         TFPCALL(sv_to_int_ptr(text, & text_fuzzy->text));
188              
189 15 50         if (text_fuzzy->text.ulength > 0) {
190             /* Generate the Unicode alphabet. */
191              
192 15 50         TEXT_FUZZY (generate_ualphabet (text_fuzzy));
193             }
194             }
195             else {
196 28 100         if (text_fuzzy->text.length > 0) {
197 26 50         TEXT_FUZZY (generate_alphabet (text_fuzzy));
198             }
199             }
200 43 50         TEXT_FUZZY (allocate_edits (text_fuzzy));
201 43           * text_fuzzy_ptr = text_fuzzy;
202 43           return tfp_ok;
203             }
204              
205             static tfp_status_t
206 149           sv_to_text_fuzzy_string (SV * word, text_fuzzy_t * text_fuzzy)
207             {
208             STRLEN length;
209             char * nonu;
210 149 50         text_fuzzy->b.text = SvPV (word, length);
211 149           text_fuzzy->b.allocated = 0;
212 149           text_fuzzy->b.length = length;
213 149 100         if (SvUTF8 (word) || text_fuzzy->unicode) {
    100          
214              
215             /* Make a Unicode version of b. */
216              
217 67           text_fuzzy->b.ulength = sv_len_utf8 (word);
218 67           allocate_b_unicode (text_fuzzy, text_fuzzy->b.ulength);
219 67           sv_to_int_ptr (word, & text_fuzzy->b);
220 67 100         if (! text_fuzzy->unicode) {
221              
222             /* Make a non-Unicode version of b. */
223              
224             int i;
225              
226 2           text_fuzzy->b.length = text_fuzzy->b.ulength;
227 2           text_fuzzy->b.allocated = 1;
228 2 50         get_memory (nonu, text_fuzzy->b.length + 1, char);
229 9 100         for (i = 0; i < text_fuzzy->b.ulength; i++) {
230             int c;
231              
232 7           c = text_fuzzy->b.unicode[i];
233 7 100         if (c <= 0x80) {
234 1           nonu[i] = c;
235             }
236             else {
237             /* Put a non-matching character in there. */
238              
239 6           nonu[i] = text_fuzzy->invalid_char;
240             }
241             }
242 2           text_fuzzy->b.text = nonu;
243             }
244             }
245 149           return tfp_ok;
246             }
247              
248             static void
249 149           free_text (text_fuzzy_t * text_fuzzy)
250             {
251 149 100         if (text_fuzzy->b.allocated) {
252 2           Safefree (text_fuzzy->b.text);
253 2           text_fuzzy->n_mallocs--;
254 2           text_fuzzy->b.text = 0;
255 2           text_fuzzy->b.allocated = 0;
256             }
257 149           }
258              
259             /* The following definitions relate to the macros "FAIL" and
260             "FAIL_MSG" in "text-fuzzy.c.in". */
261              
262             #undef TEXT_FUZZY_USER_ERROR
263             #define TEXT_FUZZY_USER_ERROR -1
264              
265             static int
266 42           text_fuzzy_sv_distance (text_fuzzy_t * text_fuzzy, SV * word)
267             {
268 42           sv_to_text_fuzzy_string (word, text_fuzzy);
269 42 50         TEXT_FUZZY (compare_single (text_fuzzy));
270 42           free_text (text_fuzzy);
271 42 100         if (text_fuzzy->found) {
272 40           return text_fuzzy->distance;
273             }
274             else {
275 2           return text_fuzzy->max_distance + 1;
276             }
277             }
278              
279             static int
280 17           text_fuzzy_av_distance (text_fuzzy_t * text_fuzzy, AV * words, AV * wantarray)
281             {
282             int i;
283             int n_words;
284             int nearest;
285              
286 17 100         if (wantarray) {
287 5           text_fuzzy->wantarray = 1;
288             }
289 17 50         TEXT_FUZZY (begin_scanning (text_fuzzy));
290              
291 17           nearest = -1;
292              
293 17           n_words = av_len (words) + 1;
294              
295             /* Check for empty array. */
296              
297 17 50         if (n_words == 0) {
298 0           return -1;
299             }
300              
301 124 100         for (i = 0; i < n_words; i++) {
302             SV * word;
303 107           word = * av_fetch (words, i, 0);
304 107           sv_to_text_fuzzy_string (word, text_fuzzy);
305 107           text_fuzzy->offset = i;
306 107 50         TEXT_FUZZY (compare_single (text_fuzzy));
307 107           free_text (text_fuzzy);
308 107 100         if (text_fuzzy->found) {
309 30           nearest = i;
310 30 100         if (! text_fuzzy->wantarray && text_fuzzy->distance == 0) {
    50          
311             /* Stop the search if there is an exact
312             match. Note that "no_exact" is checked in
313             "compare_single", so we don't need to check it
314             here. */
315 0           break;
316             }
317             }
318             }
319 17           text_fuzzy->distance = text_fuzzy->max_distance;
320              
321             /* Set the maximum distance back to the user's value. */
322              
323 17 50         TEXT_FUZZY (end_scanning (text_fuzzy));
324              
325             /* If the user wants an array of values, we go through the linked
326             list and collect them into "wantarray". Because we went through
327             the list of words from the top to the bottom, gathering
328             whatever was the minimum value at that point in the progress,
329             our list may contain false hits which must be discarded. */
330              
331 17 100         if (text_fuzzy->wantarray) {
332             int n_candidates;
333             int * candidates;
334             int i;
335 6 50         TEXT_FUZZY (get_candidates (text_fuzzy, & n_candidates,
336             & candidates));
337 6 100         if (n_candidates > 0) {
338 13 100         for (i = 0; i < n_candidates; i++) {
339             SV * offset;
340            
341 10           offset = newSViv (candidates[i]);
342 10           av_push (wantarray, offset);
343             }
344 6 50         TEXT_FUZZY (free_candidates (text_fuzzy, candidates));
345             }
346             }
347 17           return nearest;
348             }
349              
350              
351             /* Free the memory allocated to "text_fuzzy" and check that there has
352             not been a memory leak. */
353              
354 43           static int text_fuzzy_free (text_fuzzy_t * text_fuzzy)
355             {
356 43 100         if (text_fuzzy->b.unicode) {
357 17           Safefree (text_fuzzy->b.unicode);
358 17           text_fuzzy->n_mallocs--;
359             }
360              
361             /* See the comments in "text-fuzzy.c.in" about why this is
362             necessary. */
363              
364 43 50         TEXT_FUZZY (free_memory (text_fuzzy));
365              
366 43 100         if (text_fuzzy->unicode) {
367 15           Safefree (text_fuzzy->text.unicode);
368 15           text_fuzzy->n_mallocs--;
369             }
370              
371 43           Safefree (text_fuzzy->text.text);
372 43           text_fuzzy->n_mallocs--;
373              
374 43 50         if (text_fuzzy->n_mallocs != 1) {
375 0           warn ("memory leak: n_mallocs %d != 1", text_fuzzy->n_mallocs);
376             }
377 43           Safefree (text_fuzzy);
378              
379 43           return 0;
380             }
381