7 /* dictionary manager interface to PCRE regular expression library
9 /* #include <dict_pcre.h>
11 /* DICT *dict_pcre_open(name, dummy, dict_flags)
16 /* dict_pcre_open() opens the named file and compiles the contained
17 /* regular expressions. The result object can be used to match strings
20 /* dict(3) generic dictionary manager
23 /* andrewm@connect.com.au
24 /* connect.com.au Pty. Ltd.
25 /* Level 3, 213 Miller St
26 /* North Sydney, NSW, Australia
29 /* IBM T.J. Watson Research
31 /* Yorktown Heights, NY 10598, USA
40 #include <stdio.h> /* sprintf() prototype */
46 #ifdef STRCASECMP_IN_STRINGS_H
50 /* Utility library. */
57 #include "stringops.h"
58 #include "readlline.h"
60 #include "dict_pcre.h"
61 #include "mac_parse.h"
65 * Support for IF/ENDIF based on an idea by Bert Driehuis.
67 #define DICT_PCRE_OP_MATCH 1 /* Match this regexp */
68 #define DICT_PCRE_OP_IF 2 /* Increase if/endif nesting on match */
69 #define DICT_PCRE_OP_ENDIF 3 /* Decrease if/endif nesting on match */
72 * Max strings captured by regexp - essentially the max number of (..)
74 #define PCRE_MAX_CAPTURE 99
77 * Regular expression before and after compilation.
80 char *regexp
; /* regular expression */
81 int options
; /* options */
82 int match
; /* positive or negative match */
86 pcre
*pattern
; /* the compiled pattern */
87 pcre_extra
*hints
; /* hints to speed pattern execution */
91 * Compiled generic rule, and subclasses that derive from it.
93 typedef struct DICT_PCRE_RULE
{
94 int op
; /* DICT_PCRE_OP_MATCH/IF/ENDIF */
95 int nesting
; /* level of IF/ENDIF nesting */
96 int lineno
; /* source file line number */
97 struct DICT_PCRE_RULE
*next
; /* next rule in dict */
101 DICT_PCRE_RULE rule
; /* generic part */
102 pcre
*pattern
; /* compiled pattern */
103 pcre_extra
*hints
; /* hints to speed pattern execution */
104 char *replacement
; /* replacement string */
105 int match
; /* positive or negative match */
106 size_t max_sub
; /* largest $number in replacement */
107 } DICT_PCRE_MATCH_RULE
;
110 DICT_PCRE_RULE rule
; /* generic members */
111 pcre
*pattern
; /* compiled pattern */
112 pcre_extra
*hints
; /* hints to speed pattern execution */
113 int match
; /* positive or negative match */
120 DICT dict
; /* generic members */
121 DICT_PCRE_RULE
*head
;
122 VSTRING
*expansion_buf
; /* lookup result */
125 static int dict_pcre_init
= 0; /* flag need to init pcre library */
128 * Context for $number expansion callback.
131 DICT_PCRE
*dict_pcre
; /* the dictionary handle */
132 DICT_PCRE_MATCH_RULE
*match_rule
; /* the rule we matched */
133 const char *lookup_string
; /* string against which we match */
134 int offsets
[PCRE_MAX_CAPTURE
* 3]; /* Cut substrings */
135 int matches
; /* Count of cuts */
136 } DICT_PCRE_EXPAND_CONTEXT
;
139 * Context for $number pre-scan callback.
142 const char *mapname
; /* name of regexp map */
143 int lineno
; /* where in file */
144 size_t max_sub
; /* Largest $n seen */
145 char *literal
; /* constant result, $$ -> $ */
146 } DICT_PCRE_PRESCAN_CONTEXT
;
152 #define MAC_PARSE_OK 0
156 * Macros to make dense code more accessible.
158 #define NULL_STARTOFFSET (0)
159 #define NULL_EXEC_OPTIONS (0)
160 #define NULL_OVECTOR ((int *) 0)
161 #define NULL_OVECTOR_LENGTH (0)
163 /* dict_pcre_expand - replace $number with matched text */
165 static int dict_pcre_expand(int type
, VSTRING
*buf
, char *ptr
)
167 DICT_PCRE_EXPAND_CONTEXT
*ctxt
= (DICT_PCRE_EXPAND_CONTEXT
*) ptr
;
168 DICT_PCRE_MATCH_RULE
*match_rule
= ctxt
->match_rule
;
169 DICT_PCRE
*dict_pcre
= ctxt
->dict_pcre
;
175 * Replace $0-${99} with strings cut from matched text.
177 if (type
== MAC_PARSE_VARNAME
) {
178 n
= atoi(vstring_str(buf
));
179 ret
= pcre_get_substring(ctxt
->lookup_string
, ctxt
->offsets
,
180 ctxt
->matches
, n
, &pp
);
182 if (ret
== PCRE_ERROR_NOSUBSTRING
)
183 return (MAC_PARSE_UNDEF
);
185 msg_fatal("pcre map %s, line %d: pcre_get_substring error: %d",
186 dict_pcre
->dict
.name
, match_rule
->rule
.lineno
, ret
);
190 return (MAC_PARSE_UNDEF
);
192 vstring_strcat(dict_pcre
->expansion_buf
, pp
);
194 return (MAC_PARSE_OK
);
198 * Straight text - duplicate with no substitution.
201 vstring_strcat(dict_pcre
->expansion_buf
, vstring_str(buf
));
202 return (MAC_PARSE_OK
);
206 /* dict_pcre_exec_error - report matching error */
208 static void dict_pcre_exec_error(const char *mapname
, int lineno
, int errval
)
212 msg_warn("pcre map %s, line %d: too many (...)",
215 case PCRE_ERROR_NULL
:
216 case PCRE_ERROR_BADOPTION
:
217 msg_fatal("pcre map %s, line %d: bad args to re_exec",
219 case PCRE_ERROR_BADMAGIC
:
220 case PCRE_ERROR_UNKNOWN_NODE
:
221 msg_fatal("pcre map %s, line %d: corrupt compiled regexp",
223 #ifdef PCRE_ERROR_NOMEMORY
224 case PCRE_ERROR_NOMEMORY
:
225 msg_fatal("pcre map %s, line %d: out of memory",
228 #ifdef PCRE_ERROR_MATCHLIMIT
229 case PCRE_ERROR_MATCHLIMIT
:
230 msg_fatal("pcre map %s, line %d: matched text exceeds buffer limit",
233 #ifdef PCRE_ERROR_BADUTF8
234 case PCRE_ERROR_BADUTF8
:
235 msg_fatal("pcre map %s, line %d: bad UTF-8 sequence in search string",
238 #ifdef PCRE_ERROR_BADUTF8_OFFSET
239 case PCRE_ERROR_BADUTF8_OFFSET
:
240 msg_fatal("pcre map %s, line %d: bad UTF-8 start offset in search string",
244 msg_fatal("pcre map %s, line %d: unknown re_exec error: %d",
245 mapname
, lineno
, errval
);
249 /* dict_pcre_lookup - match string and perform optional substitution */
251 static const char *dict_pcre_lookup(DICT
*dict
, const char *lookup_string
)
253 DICT_PCRE
*dict_pcre
= (DICT_PCRE
*) dict
;
254 DICT_PCRE_RULE
*rule
;
255 DICT_PCRE_IF_RULE
*if_rule
;
256 DICT_PCRE_MATCH_RULE
*match_rule
;
257 int lookup_len
= strlen(lookup_string
);
258 DICT_PCRE_EXPAND_CONTEXT ctxt
;
264 msg_info("dict_pcre_lookup: %s: %s", dict
->name
, lookup_string
);
267 * Optionally fold the key.
269 if (dict
->flags
& DICT_FLAG_FOLD_MUL
) {
270 if (dict
->fold_buf
== 0)
271 dict
->fold_buf
= vstring_alloc(10);
272 vstring_strcpy(dict
->fold_buf
, lookup_string
);
273 lookup_string
= lowercase(vstring_str(dict
->fold_buf
));
275 for (rule
= dict_pcre
->head
; rule
; rule
= rule
->next
) {
278 * Skip rules inside failed IF/ENDIF.
280 if (nesting
< rule
->nesting
)
286 * Search for a matching expression.
288 case DICT_PCRE_OP_MATCH
:
289 match_rule
= (DICT_PCRE_MATCH_RULE
*) rule
;
290 ctxt
.matches
= pcre_exec(match_rule
->pattern
, match_rule
->hints
,
291 lookup_string
, lookup_len
,
292 NULL_STARTOFFSET
, NULL_EXEC_OPTIONS
,
293 ctxt
.offsets
, PCRE_MAX_CAPTURE
* 3);
295 if (ctxt
.matches
> 0) {
296 if (!match_rule
->match
)
297 continue; /* Negative rule matched */
298 } else if (ctxt
.matches
== PCRE_ERROR_NOMATCH
) {
299 if (match_rule
->match
)
300 continue; /* Positive rule did not
303 dict_pcre_exec_error(dict
->name
, rule
->lineno
, ctxt
.matches
);
304 continue; /* pcre_exec failed */
308 * Skip $number substitutions when the replacement text contains
309 * no $number strings, as learned during the compile time
310 * pre-scan. The pre-scan already replaced $$ by $.
312 if (match_rule
->max_sub
== 0)
313 return match_rule
->replacement
;
316 * We've got a match. Perform substitution on replacement string.
318 if (dict_pcre
->expansion_buf
== 0)
319 dict_pcre
->expansion_buf
= vstring_alloc(10);
320 VSTRING_RESET(dict_pcre
->expansion_buf
);
321 ctxt
.dict_pcre
= dict_pcre
;
322 ctxt
.match_rule
= match_rule
;
323 ctxt
.lookup_string
= lookup_string
;
325 if (mac_parse(match_rule
->replacement
, dict_pcre_expand
,
326 (char *) &ctxt
) & MAC_PARSE_ERROR
)
327 msg_fatal("pcre map %s, line %d: bad replacement syntax",
328 dict
->name
, rule
->lineno
);
330 VSTRING_TERMINATE(dict_pcre
->expansion_buf
);
331 return (vstring_str(dict_pcre
->expansion_buf
));
334 * Conditional. XXX We provide space for matched substring info
335 * because PCRE uses part of it as workspace for backtracking.
336 * PCRE will allocate memory if it runs out of backtracking
339 case DICT_PCRE_OP_IF
:
340 if_rule
= (DICT_PCRE_IF_RULE
*) rule
;
341 ctxt
.matches
= pcre_exec(if_rule
->pattern
, if_rule
->hints
,
342 lookup_string
, lookup_len
,
343 NULL_STARTOFFSET
, NULL_EXEC_OPTIONS
,
344 ctxt
.offsets
, PCRE_MAX_CAPTURE
* 3);
346 if (ctxt
.matches
> 0) {
348 continue; /* Negative rule matched */
349 } else if (ctxt
.matches
== PCRE_ERROR_NOMATCH
) {
351 continue; /* Positive rule did not
354 dict_pcre_exec_error(dict
->name
, rule
->lineno
, ctxt
.matches
);
355 continue; /* pcre_exec failed */
361 * ENDIF after successful IF.
363 case DICT_PCRE_OP_ENDIF
:
368 msg_panic("dict_pcre_lookup: impossible operation %d", rule
->op
);
374 /* dict_pcre_close - close pcre dictionary */
376 static void dict_pcre_close(DICT
*dict
)
378 DICT_PCRE
*dict_pcre
= (DICT_PCRE
*) dict
;
379 DICT_PCRE_RULE
*rule
;
380 DICT_PCRE_RULE
*next
;
381 DICT_PCRE_MATCH_RULE
*match_rule
;
382 DICT_PCRE_IF_RULE
*if_rule
;
384 for (rule
= dict_pcre
->head
; rule
; rule
= next
) {
387 case DICT_PCRE_OP_MATCH
:
388 match_rule
= (DICT_PCRE_MATCH_RULE
*) rule
;
389 if (match_rule
->pattern
)
390 myfree((char *) match_rule
->pattern
);
391 if (match_rule
->hints
)
392 myfree((char *) match_rule
->hints
);
393 if (match_rule
->replacement
)
394 myfree((char *) match_rule
->replacement
);
396 case DICT_PCRE_OP_IF
:
397 if_rule
= (DICT_PCRE_IF_RULE
*) rule
;
398 if (if_rule
->pattern
)
399 myfree((char *) if_rule
->pattern
);
401 myfree((char *) if_rule
->hints
);
403 case DICT_PCRE_OP_ENDIF
:
406 msg_panic("dict_pcre_close: unknown operation %d", rule
->op
);
408 myfree((char *) rule
);
410 if (dict_pcre
->expansion_buf
)
411 vstring_free(dict_pcre
->expansion_buf
);
413 vstring_free(dict
->fold_buf
);
417 /* dict_pcre_get_pattern - extract pattern from rule */
419 static int dict_pcre_get_pattern(const char *mapname
, int lineno
, char **bufp
,
420 DICT_PCRE_REGEXP
*pattern
)
426 * Process negation operators.
430 pattern
->match
= !pattern
->match
;
435 * Grr...aceful handling of whitespace after '!'.
437 while (*p
&& ISSPACE(*p
))
440 msg_warn("pcre map %s, line %d: no regexp: skipping this rule",
448 * Search for second delimiter, handling backslash escape.
455 } else if (*p
== re_delimiter
)
461 msg_warn("pcre map %s, line %d: no closing regexp delimiter \"%c\": "
462 "ignoring this rule", mapname
, lineno
, re_delimiter
);
465 *p
++ = 0; /* Null term the regexp */
468 * Parse any regexp options.
470 pattern
->options
= PCRE_CASELESS
| PCRE_DOTALL
;
471 while (*p
&& !ISSPACE(*p
)) {
474 pattern
->options
^= PCRE_CASELESS
;
477 pattern
->options
^= PCRE_MULTILINE
;
480 pattern
->options
^= PCRE_DOTALL
;
483 pattern
->options
^= PCRE_EXTENDED
;
486 pattern
->options
^= PCRE_ANCHORED
;
489 pattern
->options
^= PCRE_DOLLAR_ENDONLY
;
492 pattern
->options
^= PCRE_UNGREEDY
;
495 pattern
->options
^= PCRE_EXTRA
;
498 msg_warn("pcre map %s, line %d: unknown regexp option \"%c\": "
499 "skipping this rule", mapname
, lineno
, *p
);
508 /* dict_pcre_prescan - sanity check $number instances in replacement text */
510 static int dict_pcre_prescan(int type
, VSTRING
*buf
, char *context
)
512 DICT_PCRE_PRESCAN_CONTEXT
*ctxt
= (DICT_PCRE_PRESCAN_CONTEXT
*) context
;
516 * Keep a copy of literal text (with $$ already replaced by $) if and
517 * only if the replacement text contains no $number expression. This way
518 * we can avoid having to scan the replacement text at lookup time.
520 if (type
== MAC_PARSE_VARNAME
) {
522 myfree(ctxt
->literal
);
525 if (!alldig(vstring_str(buf
))) {
526 msg_warn("pcre map %s, line %d: non-numeric replacement index \"%s\"",
527 ctxt
->mapname
, ctxt
->lineno
, vstring_str(buf
));
528 return (MAC_PARSE_ERROR
);
530 n
= atoi(vstring_str(buf
));
532 msg_warn("pcre map %s, line %d: out of range replacement index \"%s\"",
533 ctxt
->mapname
, ctxt
->lineno
, vstring_str(buf
));
534 return (MAC_PARSE_ERROR
);
536 if (n
> ctxt
->max_sub
)
538 } else if (type
== MAC_PARSE_LITERAL
&& ctxt
->max_sub
== 0) {
540 msg_panic("pcre map %s, line %d: multiple literals but no $number",
541 ctxt
->mapname
, ctxt
->lineno
);
542 ctxt
->literal
= mystrdup(vstring_str(buf
));
544 return (MAC_PARSE_OK
);
547 /* dict_pcre_compile - compile pattern */
549 static int dict_pcre_compile(const char *mapname
, int lineno
,
550 DICT_PCRE_REGEXP
*pattern
,
551 DICT_PCRE_ENGINE
*engine
)
556 engine
->pattern
= pcre_compile(pattern
->regexp
, pattern
->options
,
557 &error
, &errptr
, NULL
);
558 if (engine
->pattern
== 0) {
559 msg_warn("pcre map %s, line %d: error in regex at offset %d: %s",
560 mapname
, lineno
, errptr
, error
);
563 engine
->hints
= pcre_study(engine
->pattern
, 0, &error
);
565 msg_warn("pcre map %s, line %d: error while studying regex: %s",
566 mapname
, lineno
, error
);
567 myfree((char *) engine
->pattern
);
573 /* dict_pcre_rule_alloc - fill in a generic rule structure */
575 static DICT_PCRE_RULE
*dict_pcre_rule_alloc(int op
, int nesting
,
579 DICT_PCRE_RULE
*rule
;
581 rule
= (DICT_PCRE_RULE
*) mymalloc(size
);
583 rule
->nesting
= nesting
;
584 rule
->lineno
= lineno
;
590 /* dict_pcre_parse_rule - parse and compile one rule */
592 static DICT_PCRE_RULE
*dict_pcre_parse_rule(const char *mapname
, int lineno
,
593 char *line
, int nesting
,
602 * An ordinary match rule takes one pattern and replacement text.
605 DICT_PCRE_REGEXP regexp
;
606 DICT_PCRE_ENGINE engine
;
607 DICT_PCRE_PRESCAN_CONTEXT prescan_context
;
608 DICT_PCRE_MATCH_RULE
*match_rule
;
611 * Get the pattern string and options.
613 if (dict_pcre_get_pattern(mapname
, lineno
, &p
, ®exp
) == 0)
617 * Get the replacement text.
619 while (*p
&& ISSPACE(*p
))
622 msg_warn("%s, line %d: no replacement text: using empty string",
626 * Sanity check the $number instances in the replacement text.
628 prescan_context
.mapname
= mapname
;
629 prescan_context
.lineno
= lineno
;
630 prescan_context
.max_sub
= 0;
631 prescan_context
.literal
= 0;
634 * The optimizer will eliminate code duplication and/or dead code.
636 #define CREATE_MATCHOP_ERROR_RETURN(rval) do { \
637 if (prescan_context.literal) \
638 myfree(prescan_context.literal); \
642 if (mac_parse(p
, dict_pcre_prescan
, (char *) &prescan_context
)
644 msg_warn("pcre map %s, line %d: bad replacement syntax: "
645 "skipping this rule", mapname
, lineno
);
646 CREATE_MATCHOP_ERROR_RETURN(0);
650 * Substring replacement not possible with negative regexps.
652 if (prescan_context
.max_sub
> 0 && regexp
.match
== 0) {
653 msg_warn("pcre map %s, line %d: $number found in negative match "
654 "replacement text: skipping this rule", mapname
, lineno
);
655 CREATE_MATCHOP_ERROR_RETURN(0);
657 if (prescan_context
.max_sub
> 0 && (dict_flags
& DICT_FLAG_NO_REGSUB
)) {
658 msg_warn("pcre map %s, line %d: "
659 "regular expression substitution is not allowed: "
660 "skipping this rule", mapname
, lineno
);
661 CREATE_MATCHOP_ERROR_RETURN(0);
665 * Compile the pattern.
667 if (dict_pcre_compile(mapname
, lineno
, ®exp
, &engine
) == 0)
668 CREATE_MATCHOP_ERROR_RETURN(0);
669 #ifdef PCRE_INFO_CAPTURECOUNT
670 if (pcre_fullinfo(engine
.pattern
, engine
.hints
,
671 PCRE_INFO_CAPTURECOUNT
,
672 (void *) &actual_sub
) != 0)
673 msg_panic("pcre map %s, line %d: pcre_fullinfo failed",
675 if (prescan_context
.max_sub
> actual_sub
) {
676 msg_warn("pcre map %s, line %d: out of range replacement index \"%d\": "
677 "skipping this rule", mapname
, lineno
,
678 (int) prescan_context
.max_sub
);
680 myfree((char *) engine
.pattern
);
682 myfree((char *) engine
.hints
);
683 CREATE_MATCHOP_ERROR_RETURN(0);
690 match_rule
= (DICT_PCRE_MATCH_RULE
*)
691 dict_pcre_rule_alloc(DICT_PCRE_OP_MATCH
, nesting
, lineno
,
692 sizeof(DICT_PCRE_MATCH_RULE
));
693 match_rule
->match
= regexp
.match
;
694 match_rule
->max_sub
= prescan_context
.max_sub
;
695 if (prescan_context
.literal
)
696 match_rule
->replacement
= prescan_context
.literal
;
698 match_rule
->replacement
= mystrdup(p
);
699 match_rule
->pattern
= engine
.pattern
;
700 match_rule
->hints
= engine
.hints
;
701 return ((DICT_PCRE_RULE
*) match_rule
);
705 * The IF operator takes one pattern but no replacement text.
707 else if (strncasecmp(p
, "IF", 2) == 0 && !ISALNUM(p
[2])) {
708 DICT_PCRE_REGEXP regexp
;
709 DICT_PCRE_ENGINE engine
;
710 DICT_PCRE_IF_RULE
*if_rule
;
717 while (*p
&& ISSPACE(*p
))
719 if (!dict_pcre_get_pattern(mapname
, lineno
, &p
, ®exp
))
723 * Warn about out-of-place text.
725 while (*p
&& ISSPACE(*p
))
728 msg_warn("pcre map %s, line %d: ignoring extra text after "
729 "IF statement: \"%s\"", mapname
, lineno
, p
);
730 msg_warn("pcre map %s, line %d: do not prepend whitespace"
731 " to statements between IF and ENDIF", mapname
, lineno
);
735 * Compile the pattern.
737 if (dict_pcre_compile(mapname
, lineno
, ®exp
, &engine
) == 0)
743 if_rule
= (DICT_PCRE_IF_RULE
*)
744 dict_pcre_rule_alloc(DICT_PCRE_OP_IF
, nesting
, lineno
,
745 sizeof(DICT_PCRE_IF_RULE
));
746 if_rule
->match
= regexp
.match
;
747 if_rule
->pattern
= engine
.pattern
;
748 if_rule
->hints
= engine
.hints
;
749 return ((DICT_PCRE_RULE
*) if_rule
);
753 * The ENDIF operator takes no patterns and no replacement text.
755 else if (strncasecmp(p
, "ENDIF", 5) == 0 && !ISALNUM(p
[5])) {
756 DICT_PCRE_RULE
*rule
;
761 * Warn about out-of-place ENDIFs.
764 msg_warn("pcre map %s, line %d: ignoring ENDIF without matching IF",
770 * Warn about out-of-place text.
772 while (*p
&& ISSPACE(*p
))
775 msg_warn("pcre map %s, line %d: ignoring extra text after ENDIF",
781 rule
= dict_pcre_rule_alloc(DICT_PCRE_OP_ENDIF
, nesting
, lineno
,
782 sizeof(DICT_PCRE_RULE
));
787 * Unrecognized input.
790 msg_warn("pcre map %s, line %d: ignoring unrecognized request",
796 /* dict_pcre_open - load and compile a file containing regular expressions */
798 DICT
*dict_pcre_open(const char *mapname
, int unused_flags
, int dict_flags
)
800 DICT_PCRE
*dict_pcre
;
802 VSTRING
*line_buffer
;
803 DICT_PCRE_RULE
*last_rule
= 0;
804 DICT_PCRE_RULE
*rule
;
809 line_buffer
= vstring_alloc(100);
811 dict_pcre
= (DICT_PCRE
*) dict_alloc(DICT_TYPE_PCRE
, mapname
,
813 dict_pcre
->dict
.lookup
= dict_pcre_lookup
;
814 dict_pcre
->dict
.close
= dict_pcre_close
;
815 dict_pcre
->dict
.flags
= dict_flags
| DICT_FLAG_PATTERN
;
816 if (dict_flags
& DICT_FLAG_FOLD_MUL
)
817 dict_pcre
->dict
.fold_buf
= vstring_alloc(10);
819 dict_pcre
->expansion_buf
= 0;
821 if (dict_pcre_init
== 0) {
822 pcre_malloc
= (void *(*) (size_t)) mymalloc
;
823 pcre_free
= (void (*) (void *)) myfree
;
828 * Parse the pcre table.
830 if ((map_fp
= vstream_fopen(mapname
, O_RDONLY
, 0)) == 0)
831 msg_fatal("open %s: %m", mapname
);
833 while (readlline(line_buffer
, map_fp
, &lineno
)) {
834 p
= vstring_str(line_buffer
);
835 trimblanks(p
, 0)[0] = 0; /* Trim space at end */
838 rule
= dict_pcre_parse_rule(mapname
, lineno
, p
, nesting
, dict_flags
);
841 if (rule
->op
== DICT_PCRE_OP_IF
) {
843 } else if (rule
->op
== DICT_PCRE_OP_ENDIF
) {
847 dict_pcre
->head
= rule
;
849 last_rule
->next
= rule
;
854 msg_warn("pcre map %s, line %d: more IFs than ENDIFs",
857 vstring_free(line_buffer
);
858 vstream_fclose(map_fp
);
860 return (DICT_DEBUG (&dict_pcre
->dict
));
863 #endif /* HAS_PCRE */