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             int i;
109             const U8 * utf;
110             const U8 * send;
111             STRLEN length;
112              
113 82 50         utf = (const U8 *) SvPV (text, length);
114 82           send = utf + length;
115 757 100         for (i = 0; i < tfs->ulength; i++) {
116             STRLEN len;
117 675 50         tfs->unicode[i] = (int) utf8_to_uvchr_buf (utf, send, & len);
118 675 50         if (len == -1 || tfs->unicode[i] == 0) {
    50          
119 0           return tfp_unicode_failure;
120             }
121 675           utf += len;
122             }
123 82           return tfp_ok;
124             }
125              
126             /* Convert a Perl SV into the text_fuzzy_t structure. */
127              
128             static tfp_status_t
129 43           sv_to_text_fuzzy (SV * text, text_fuzzy_t ** text_fuzzy_ptr)
130             {
131             STRLEN length;
132             const unsigned char * stuff;
133             text_fuzzy_t * text_fuzzy;
134             int i;
135             int is_utf8;
136             char * copy;
137              
138             /* Allocate memory for "text_fuzzy". */
139 43 50         get_memory (text_fuzzy, 1, text_fuzzy_t);
140 43           text_fuzzy->max_distance = NO_MAX_DISTANCE;
141              
142             /* Copy the string in "text" into "text_fuzzy". */
143 43 50         stuff = (const unsigned char *) SvPV (text, length);
144 43 50         get_memory (copy, length + 1, char);
145 427 100         for (i = 0; i < (int) length; i++) {
146 384           copy[i] = stuff[i];
147             }
148 43           copy[length] = '\0';
149 43           text_fuzzy->text.length = length;
150 43           text_fuzzy->text.text = copy;
151 43           is_utf8 = SvUTF8 (text);
152 43 100         if (is_utf8) {
153              
154             /* Put the Unicode version of the string into
155             "text_fuzzy->text". */
156              
157 15           text_fuzzy->unicode = 1;
158 15           text_fuzzy->text.ulength = sv_len_utf8 (text);
159              
160 15 50         get_memory (text_fuzzy->text.unicode, text_fuzzy->text.ulength, int);
    50          
161              
162 15 50         TFPCALL(sv_to_int_ptr(text, & text_fuzzy->text));
163              
164 15 50         if (text_fuzzy->text.ulength > 0) {
165             /* Generate the Unicode alphabet. */
166              
167 15 50         TEXT_FUZZY (generate_ualphabet (text_fuzzy));
168             }
169             }
170             else {
171 28 100         if (text_fuzzy->text.length > 0) {
172 26 50         TEXT_FUZZY (generate_alphabet (text_fuzzy));
173             }
174             }
175 43 50         TEXT_FUZZY (allocate_edits (text_fuzzy));
176 43           * text_fuzzy_ptr = text_fuzzy;
177 43           return tfp_ok;
178             }
179              
180             static tfp_status_t
181 149           sv_to_text_fuzzy_string (SV * word, text_fuzzy_t * text_fuzzy)
182             {
183             STRLEN length;
184             char * nonu;
185 149 50         text_fuzzy->b.text = SvPV (word, length);
186 149           text_fuzzy->b.allocated = 0;
187 149           text_fuzzy->b.length = length;
188 149 100         if (SvUTF8 (word) || text_fuzzy->unicode) {
    100          
189              
190             /* Make a Unicode version of b. */
191              
192 67           text_fuzzy->b.ulength = sv_len_utf8 (word);
193 67           allocate_b_unicode (text_fuzzy, text_fuzzy->b.ulength);
194 67           sv_to_int_ptr (word, & text_fuzzy->b);
195 67 100         if (! text_fuzzy->unicode) {
196              
197             /* Make a non-Unicode version of b. */
198              
199             int i;
200              
201 2           text_fuzzy->b.length = text_fuzzy->b.ulength;
202 2           text_fuzzy->b.allocated = 1;
203 2 50         get_memory (nonu, text_fuzzy->b.length + 1, char);
204 9 100         for (i = 0; i < text_fuzzy->b.ulength; i++) {
205             int c;
206              
207 7           c = text_fuzzy->b.unicode[i];
208 7 100         if (c <= 0x80) {
209 1           nonu[i] = c;
210             }
211             else {
212             /* Put a non-matching character in there. */
213              
214 6           nonu[i] = text_fuzzy->invalid_char;
215             }
216             }
217 2           text_fuzzy->b.text = nonu;
218             }
219             }
220 149           return tfp_ok;
221             }
222              
223             static void
224 149           free_text (text_fuzzy_t * text_fuzzy)
225             {
226 149 100         if (text_fuzzy->b.allocated) {
227 2           Safefree (text_fuzzy->b.text);
228 2           text_fuzzy->n_mallocs--;
229 2           text_fuzzy->b.text = 0;
230 2           text_fuzzy->b.allocated = 0;
231             }
232 149           }
233              
234             /* The following definitions relate to the macros "FAIL" and
235             "FAIL_MSG" in "text-fuzzy.c.in". */
236              
237             #undef TEXT_FUZZY_USER_ERROR
238             #define TEXT_FUZZY_USER_ERROR -1
239              
240             static int
241 42           text_fuzzy_sv_distance (text_fuzzy_t * text_fuzzy, SV * word)
242             {
243 42           sv_to_text_fuzzy_string (word, text_fuzzy);
244 42 50         TEXT_FUZZY (compare_single (text_fuzzy));
245 42           free_text (text_fuzzy);
246 42 100         if (text_fuzzy->found) {
247 40           return text_fuzzy->distance;
248             }
249             else {
250 2           return text_fuzzy->max_distance + 1;
251             }
252             }
253              
254             static int
255 17           text_fuzzy_av_distance (text_fuzzy_t * text_fuzzy, AV * words, AV * wantarray)
256             {
257             int i;
258             int n_words;
259             int nearest;
260              
261 17 100         if (wantarray) {
262 5           text_fuzzy->wantarray = 1;
263             }
264 17 50         TEXT_FUZZY (begin_scanning (text_fuzzy));
265              
266 17           nearest = -1;
267              
268 17           n_words = av_len (words) + 1;
269              
270             /* Check for empty array. */
271              
272 17 50         if (n_words == 0) {
273 0           return -1;
274             }
275              
276 124 100         for (i = 0; i < n_words; i++) {
277             SV * word;
278 107           word = * av_fetch (words, i, 0);
279 107           sv_to_text_fuzzy_string (word, text_fuzzy);
280 107           text_fuzzy->offset = i;
281 107 50         TEXT_FUZZY (compare_single (text_fuzzy));
282 107           free_text (text_fuzzy);
283 107 100         if (text_fuzzy->found) {
284 30           nearest = i;
285 30 100         if (! text_fuzzy->wantarray && text_fuzzy->distance == 0) {
    50          
286             /* Stop the search if there is an exact
287             match. Note that "no_exact" is checked in
288             "compare_single", so we don't need to check it
289             here. */
290 0           break;
291             }
292             }
293             }
294 17           text_fuzzy->distance = text_fuzzy->max_distance;
295              
296             /* Set the maximum distance back to the user's value. */
297              
298 17 50         TEXT_FUZZY (end_scanning (text_fuzzy));
299              
300             /* If the user wants an array of values, we go through the linked
301             list and collect them into "wantarray". Because we went through
302             the list of words from the top to the bottom, gathering
303             whatever was the minimum value at that point in the progress,
304             our list may contain false hits which must be discarded. */
305              
306 17 100         if (text_fuzzy->wantarray) {
307             int n_candidates;
308             int * candidates;
309             int i;
310 6 50         TEXT_FUZZY (get_candidates (text_fuzzy, & n_candidates,
311             & candidates));
312 6 100         if (n_candidates > 0) {
313 13 100         for (i = 0; i < n_candidates; i++) {
314             SV * offset;
315            
316 10           offset = newSViv (candidates[i]);
317 10           av_push (wantarray, offset);
318             }
319 6 50         TEXT_FUZZY (free_candidates (text_fuzzy, candidates));
320             }
321             }
322 17           return nearest;
323             }
324              
325              
326             /* Free the memory allocated to "text_fuzzy" and check that there has
327             not been a memory leak. */
328              
329 43           static int text_fuzzy_free (text_fuzzy_t * text_fuzzy)
330             {
331 43 100         if (text_fuzzy->b.unicode) {
332 17           Safefree (text_fuzzy->b.unicode);
333 17           text_fuzzy->n_mallocs--;
334             }
335              
336             /* See the comments in "text-fuzzy.c.in" about why this is
337             necessary. */
338              
339 43 50         TEXT_FUZZY (free_memory (text_fuzzy));
340              
341 43 100         if (text_fuzzy->unicode) {
342 15           Safefree (text_fuzzy->text.unicode);
343 15           text_fuzzy->n_mallocs--;
344             }
345              
346 43           Safefree (text_fuzzy->text.text);
347 43           text_fuzzy->n_mallocs--;
348              
349 43 50         if (text_fuzzy->n_mallocs != 1) {
350 0           warn ("memory leak: n_mallocs %d != 1", text_fuzzy->n_mallocs);
351             }
352 43           Safefree (text_fuzzy);
353              
354 43           return 0;
355             }
356