File Coverage

blib/lib/Text/Balanced.pm
Criterion Covered Total %
statement 476 523 91.0
branch 297 346 85.8
condition 101 116 87.0
subroutine 31 32 96.8
pod 9 9 100.0
total 914 1026 89.0


line stmt bran cond sub pod time code
1             # Copyright (C) 1997-2001 Damian Conway. All rights reserved.
2             # Copyright (C) 2009 Adam Kennedy.
3             # Copyright (C) 2015, 2022 Steve Hay and other contributors. All rights
4             # reserved.
5              
6             # This module is free software; you can redistribute it and/or modify it under
7             # the same terms as Perl itself, i.e. under the terms of either the GNU General
8             # Public License or the Artistic License, as specified in the F file.
9              
10             package Text::Balanced;
11              
12             # EXTRACT VARIOUSLY DELIMITED TEXT SEQUENCES FROM STRINGS.
13             # FOR FULL DOCUMENTATION SEE Balanced.pod
14              
15 9     9   1117635 use 5.008001;
  9         36  
16 9     9   52 use strict;
  9         78  
  9         302  
17 9     9   54 use Exporter ();
  9         28  
  9         387  
18              
19 9     9   66 use vars qw { $VERSION @ISA %EXPORT_TAGS };
  9         40  
  9         1278  
20             BEGIN {
21 9     9   35 $VERSION = '2.07';
22 9         149 @ISA = 'Exporter';
23 9         33362 %EXPORT_TAGS = (
24             ALL => [ qw{
25             &extract_delimited
26             &extract_bracketed
27             &extract_quotelike
28             &extract_codeblock
29             &extract_variable
30             &extract_tagged
31             &extract_multiple
32             &gen_delimited_pat
33             &gen_extract_tagged
34             &delimited_pat
35             } ],
36             );
37             }
38              
39             Exporter::export_ok_tags('ALL');
40              
41             our $RE_PREREGEX_PAT = qr#(
42             [!=]~
43             | split|grep|map
44             | not|and|or|xor
45             )#x;
46             our $RE_EXPR_PAT = qr#(
47             (?:\*\*|&&|\|\||<<|>>|//|[-+*x%^&|.])=?
48             | /(?:[^/])
49             | =(?!>)
50             | return
51             | [\(\[]
52             )#x;
53             our $RE_NUM = qr/\s*[+\-.0-9][+\-.0-9e]*/i; # numerical constant
54              
55             our %ref2slashvalid; # is quotelike /.../ pattern valid here for given textref?
56             our %ref2qmarkvalid; # is quotelike ?...? pattern valid here for given textref?
57              
58             # HANDLE RETURN VALUES IN VARIOUS CONTEXTS
59              
60             sub _failmsg {
61 2697     2697   5250 my ($message, $pos) = @_;
62 2697         8910 $@ = bless {
63             error => $message,
64             pos => $pos,
65             }, 'Text::Balanced::ErrorMsg';
66             }
67              
68             sub _fail {
69 941     941   1461 my ($wantarray, $textref, $message, $pos) = @_;
70 941 100       1513 _failmsg $message, $pos if $message;
71 941 100       4609 return (undef, $$textref, undef) if $wantarray;
72 20         236 return;
73             }
74              
75             sub _succeed {
76 511     511   924 $@ = undef;
77 511         2234 my ($wantarray,$textref) = splice @_, 0, 2;
78 511 100       2888 my ($extrapos, $extralen) = @_ > 18
79             ? splice(@_, -2, 2)
80             : (0, 0);
81 511         1113 my ($startlen, $oppos) = @_[5,6];
82 511         920 my $remainderpos = $_[2];
83 511 100       1094 if ( $wantarray ) {
84 294         510 my @res;
85 294         959 while (my ($from, $len) = splice @_, 0, 2) {
86 1455         4641 push @res, substr($$textref, $from, $len);
87             }
88 294 100       617 if ( $extralen ) { # CORRECT FILLET
89 13         43 my $extra = substr($res[0], $extrapos-$oppos, $extralen, "\n");
90 13         35 $res[1] = "$extra$res[1]";
91 13         26 eval { substr($$textref,$remainderpos,0) = $extra;
  13         48  
92 12         32 substr($$textref,$extrapos,$extralen,"\n")} ;
93             #REARRANGE HERE DOC AND FILLET IF POSSIBLE
94 13         81 pos($$textref) = $remainderpos-$extralen+1; # RESET \G
95             } else {
96 281         729 pos($$textref) = $remainderpos; # RESET \G
97             }
98 294         4219 return @res;
99             } else {
100 217         662 my $match = substr($$textref,$_[0],$_[1]);
101 217 100       624 substr($match,$extrapos-$_[0]-$startlen,$extralen,"") if $extralen;
102 217 100       584 my $extra = $extralen
103             ? substr($$textref, $extrapos, $extralen)."\n" : "";
104 217         410 eval {substr($$textref,$_[4],$_[1]+$_[5])=$extra} ; #CHOP OUT PREFIX & MATCH, IF POSSIBLE
  217         771  
105 217         550 pos($$textref) = $_[4]; # RESET \G
106 217         3172 return $match;
107             }
108             }
109              
110             # BUILD A PATTERN MATCHING A SIMPLE DELIMITED STRING
111             ## no critic (Subroutines::ProhibitSubroutinePrototypes)
112              
113             sub gen_delimited_pat($;$) # ($delimiters;$escapes)
114             {
115 59     59 1 175 my ($dels, $escs) = @_;
116 59 50       340 return "" unless $dels =~ /\S/;
117 59 100       168 $escs = '\\' unless $escs;
118 59         233 $escs .= substr($escs,-1) x (length($dels)-length($escs));
119 59         124 my @pat = ();
120 59         114 my $i;
121 59         187 for ($i=0; $i
122             {
123 161         326 my $del = quotemeta substr($dels,$i,1);
124 161         345 my $esc = quotemeta substr($escs,$i,1);
125 161 100       411 if ($del eq $esc)
126             {
127 24         84 push @pat, "$del(?:[^$del]*(?:(?:$del$del)[^$del]*)*)$del";
128             }
129             else
130             {
131 137         571 push @pat, "$del(?:[^$esc$del]*(?:$esc.[^$esc$del]*)*)$del";
132             }
133             }
134 59         182 my $pat = join '|', @pat;
135 59         226 return "(?:$pat)";
136             }
137              
138             *delimited_pat = \&gen_delimited_pat;
139              
140             # THE EXTRACTION FUNCTIONS
141              
142             sub extract_delimited (;$$$$)
143             {
144 50 50   50 1 351516 my $textref = defined $_[0] ? \$_[0] : \$_;
145 50 100       306 $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0 if !pos($$textref); # reset
146 50         105 my $wantarray = wantarray;
147 50 100       115 my $del = defined $_[1] ? $_[1] : qq{\'\"\`};
148 50 100       113 my $pre = defined $_[2] ? $_[2] : '\s*';
149 50 100       106 my $esc = defined $_[3] ? $_[3] : qq{\\};
150 50         130 my $pat = gen_delimited_pat($del, $esc);
151 50   100     238 my $startpos = pos $$textref || 0;
152 50 100       2718 return _fail($wantarray, $textref, "Not a delimited pattern", 0)
153             unless $$textref =~ m/\G($pre)($pat)/gc;
154 43         158 my $prelen = length($1);
155 43         98 my $matchpos = $startpos+$prelen;
156 43         68 my $endpos = pos $$textref;
157 43         142 return _succeed $wantarray, $textref,
158             $matchpos, $endpos-$matchpos, # MATCH
159             $endpos, length($$textref)-$endpos, # REMAINDER
160             $startpos, $prelen; # PREFIX
161             }
162              
163             my %eb_delim_cache;
164             sub _eb_delims {
165 51     51   146 my ($ldel_orig) = @_;
166 51 100       129 return @{ $eb_delim_cache{$ldel_orig} } if $eb_delim_cache{$ldel_orig};
  45         144  
167 6         91 my $qdel = "";
168 6         15 my $quotelike;
169 6         12 my $ldel = $ldel_orig;
170 6 50       29 $ldel =~ s/'//g and $qdel .= q{'};
171 6 100       30 $ldel =~ s/"//g and $qdel .= q{"};
172 6 100       25 $ldel =~ s/`//g and $qdel .= q{`};
173 6 100       22 $ldel =~ s/q//g and $quotelike = 1;
174 6         17 $ldel =~ tr/[](){}<>\0-\377/[[(({{<
175 6         13 my $rdel = $ldel;
176 6 50       23 return @{ $eb_delim_cache{$ldel_orig} = [] } unless $rdel =~ tr/[({/;
  0         0  
177 6         14 my $posbug = pos;
178 6         25 $ldel = join('|', map { quotemeta $_ } split('', $ldel));
  12         44  
179 6         20 $rdel = join('|', map { quotemeta $_ } split('', $rdel));
  12         29  
180 6         25 pos = $posbug;
181 6   66     14 @{ $eb_delim_cache{$ldel_orig} = [
  6         537  
182             qr/\G($ldel)/, $qdel && qr/\G([$qdel])/, $quotelike, qr/\G($rdel)/
183             ] };
184             }
185             sub extract_bracketed (;$$$)
186             {
187 51 50   51 1 292508 my $textref = defined $_[0] ? \$_[0] : \$_;
188 51 100       212 $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0 if !pos($$textref); # reset
189 51 100       106 my $ldel = defined $_[1] ? $_[1] : '{([<';
190 51 50       180 my $pre = defined $_[2] ? qr/\G$_[2]/ : qr/\G\s*/;
191 51         91 my $wantarray = wantarray;
192 51         122 my @ret = _eb_delims($ldel);
193 51 50       119 unless (@ret)
194             {
195 0         0 return _fail $wantarray, $textref,
196             "Did not find a suitable bracket in delimiter: \"$_[1]\"",
197             0;
198             }
199              
200 51   100     158 my $startpos = pos $$textref || 0;
201 51         112 my @match = _match_bracketed($textref, $pre, @ret);
202              
203 51 100       151 return _fail ($wantarray, $textref) unless @match;
204              
205 18         75 return _succeed ( $wantarray, $textref,
206             $match[2], $match[5]+2, # MATCH
207             @match[8,9], # REMAINDER
208             @match[0,1], # PREFIX
209             );
210             }
211              
212             sub _match_bracketed # $textref, $pre, $ldel, $qdel, $quotelike, $rdel
213             {
214 93     93   272 my ($textref, $pre, $ldel, $qdel, $quotelike, $rdel) = @_;
215 93   100     521 my ($startpos, $ldelpos, $endpos) = (pos $$textref = pos $$textref||0);
216 93 50       635 unless ($$textref =~ m/$pre/gc)
217             {
218 0         0 _failmsg "Did not find prefix: /$pre/", $startpos;
219 0         0 return;
220             }
221              
222 93         156 $ldelpos = pos $$textref;
223              
224 93 100       721 unless ($$textref =~ m/$ldel/gc)
225             {
226 31         111 _failmsg "Did not find opening bracket after prefix: \"$pre\"",
227             pos $$textref;
228 31         86 pos $$textref = $startpos;
229 31         79 return;
230             }
231              
232 62         942 my @nesting = ( $1 );
233 62         115 my $textlen = length $$textref;
234 62         179 while (pos $$textref < $textlen)
235             {
236 636 100       5435 next if $$textref =~ m/\G\\./gcs;
237              
238 628 100 100     4739 if ($$textref =~ m/$ldel/gc)
    100 100        
    100          
    100          
239             {
240 18         66 push @nesting, $1;
241             }
242             elsif ($$textref =~ m/$rdel/gc)
243             {
244 78         294 my ($found, $brackettype) = ($1, $1);
245 78 50       347 if ($#nesting < 0)
246             {
247 0         0 _failmsg "Unmatched closing bracket: \"$found\"",
248             pos $$textref;
249 0         0 pos $$textref = $startpos;
250 0         0 return;
251             }
252 78         201 my $expected = pop(@nesting);
253 78         174 $expected =~ tr/({[/;
254 78 50       192 if ($expected ne $brackettype)
255             {
256 0         0 _failmsg qq{Mismatched closing bracket: expected "$expected" but found "$found"},
257             pos $$textref;
258 0         0 pos $$textref = $startpos;
259 0         0 return;
260             }
261 78 100       284 last if $#nesting < 0;
262             }
263             elsif ($qdel && $$textref =~ m/$qdel/gc)
264             {
265 10 50       818 $$textref =~ m/\G[^\\$1]*(?:\\.[^\\$1]*)*(\Q$1\E)/gsc and next;
266 0         0 _failmsg "Unmatched embedded quote ($1)",
267             pos $$textref;
268 0         0 pos $$textref = $startpos;
269 0         0 return;
270             }
271             elsif ($quotelike && _match_quotelike($textref,qr/\G()/,$ref2slashvalid{$textref},$ref2qmarkvalid{$textref}))
272             {
273 4         13 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 1; # back-compat
274 4         51 next;
275             }
276              
277 518         3189 else { $$textref =~ m/\G(?:[a-zA-Z0-9]+|.)/gcs }
278             }
279 62 100       194 if ($#nesting>=0)
280             {
281 2         19 _failmsg "Unmatched opening bracket(s): "
282             . join("..",@nesting)."..",
283             pos $$textref;
284 2         8 pos $$textref = $startpos;
285 2         9 return;
286             }
287              
288 60         142 $endpos = pos $$textref;
289              
290             return (
291 60         340 $startpos, $ldelpos-$startpos, # PREFIX
292             $ldelpos, 1, # OPENING BRACKET
293             $ldelpos+1, $endpos-$ldelpos-2, # CONTENTS
294             $endpos-1, 1, # CLOSING BRACKET
295             $endpos, length($$textref)-$endpos, # REMAINDER
296             );
297             }
298              
299             sub _revbracket($)
300             {
301 70     70   196 my $brack = reverse $_[0];
302 70         144 $brack =~ tr/[({/;
303 70         480 return $brack;
304             }
305              
306             my $XMLNAME = q{[a-zA-Z_:][a-zA-Z0-9_:.-]*};
307              
308             my $et_default_ldel = '<\w+(?:' . gen_delimited_pat(q{'"}) . '|[^>])*>';
309             sub extract_tagged (;$$$$$) # ($text, $opentag, $closetag, $pre, \%options)
310             {
311 48 50   48 1 98192 my $textref = defined $_[0] ? \$_[0] : \$_;
312 48 50       312 $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0 if !pos($$textref); # reset
313 48         95 my $ldel = $_[1];
314 48         84 my $rdel = $_[2];
315 48 100       290 my $pre = defined $_[3] ? qr/\G$_[3]/ : qr/\G\s*/;
316 48 100       130 my %options = defined $_[4] ? %{$_[4]} : ();
  16         61  
317 48 100       132 my $omode = defined $options{fail} ? $options{fail} : '';
318 6         21 my $bad = ref($options{reject}) eq 'ARRAY' ? join('|', @{$options{reject}})
319             : defined($options{reject}) ? $options{reject}
320 48 50       160 : ''
    100          
321             ;
322 10         34 my $ignore = ref($options{ignore}) eq 'ARRAY' ? join('|', @{$options{ignore}})
323             : defined($options{ignore}) ? $options{ignore}
324 48 50       136 : ''
    100          
325             ;
326              
327 48 100       135 $ldel = $et_default_ldel if !defined $ldel;
328 48         83 $@ = undef;
329              
330 48         130 my @match = _match_tagged($textref, $pre, $ldel, $rdel, $omode, $bad, $ignore);
331              
332 48 100       147 return _fail(wantarray, $textref) unless @match;
333 34         182 return _succeed wantarray, $textref,
334             $match[2], $match[3]+$match[5]+$match[7], # MATCH
335             @match[8..9,0..1,2..7]; # REM, PRE, BITS
336             }
337              
338             sub _match_tagged # ($$$$$$$)
339             {
340 120     120   501 my ($textref, $pre, $ldel, $rdel, $omode, $bad, $ignore) = @_;
341 120         206 my $rdelspec;
342              
343 120   100     934 my ($startpos, $opentagpos, $textpos, $parapos, $closetagpos, $endpos) = ( pos($$textref) = pos($$textref)||0 );
344              
345 120 50       1074 unless ($$textref =~ m/$pre/gc)
346             {
347 0         0 _failmsg "Did not find prefix: /$pre/", pos $$textref;
348 0         0 goto failed;
349             }
350              
351 120         233 $opentagpos = pos($$textref);
352              
353 120 100       2366 unless ($$textref =~ m/\G$ldel/gc)
354             {
355 8         42 _failmsg "Did not find opening tag: /$ldel/", pos $$textref;
356 8         52 goto failed;
357             }
358              
359 112         269 $textpos = pos($$textref);
360              
361 112 100       300 if (!defined $rdel)
362             {
363 70         439 $rdelspec = substr($$textref, $-[0], $+[0] - $-[0]);
364 70 50       633 unless ($rdelspec =~ s/\A([[(<{]+)($XMLNAME).*/ quotemeta "$1\/$2". _revbracket($1) /oes)
  70         344  
365             {
366 0         0 _failmsg "Unable to construct closing tag to match: $rdel",
367             pos $$textref;
368 0         0 goto failed;
369             }
370             }
371             else
372             {
373             ## no critic (BuiltinFunctions::ProhibitStringyEval)
374 42   66     4192 $rdelspec = eval "qq{$rdel}" || do {
375             my $del;
376             for (qw,~ ! ^ & * ) _ + - = } ] : " ; ' > . ? / | ',)
377             { next if $rdel =~ /\Q$_/; $del = $_; last }
378             unless ($del) {
379 9     9   87 use Carp;
  9         16  
  9         63021  
380             croak "Can't interpolate right delimiter $rdel"
381             }
382             eval "qq$del$rdel$del";
383             };
384             }
385              
386 112         468 while (pos($$textref) < length($$textref))
387             {
388 1240 50       3046 next if $$textref =~ m/\G\\./gc;
389              
390 1240 50 100     11960 if ($$textref =~ m/\G(\n[ \t]*\n)/gc )
    100 100        
    100          
    100          
    100          
391             {
392 0 0       0 $parapos = pos($$textref) - length($1)
393             unless defined $parapos;
394             }
395             elsif ($$textref =~ m/\G($rdelspec)/gc )
396             {
397 82         269 $closetagpos = pos($$textref)-length($1);
398 82         1170 goto matched;
399             }
400             elsif ($ignore && $$textref =~ m/\G(?:$ignore)/gc)
401             {
402 12         38 next;
403             }
404             elsif ($bad && $$textref =~ m/\G($bad)/gcs)
405             {
406 12         61 pos($$textref) -= length($1); # CUT OFF WHATEVER CAUSED THE SHORTNESS
407 12 100 100     194 goto short if ($omode eq 'PARA' || $omode eq 'MAX');
408 4         27 _failmsg "Found invalid nested tag: $1", pos $$textref;
409 4         209 goto failed;
410             }
411             elsif ($$textref =~ m/\G($ldel)/gc)
412             {
413 32         95 my $tag = $1;
414 32         105 pos($$textref) -= length($tag); # REWIND TO NESTED TAG
415 32 100       128 unless (_match_tagged(@_)) # MATCH NESTED TAG
416             {
417 4 50 33     26 goto short if $omode eq 'PARA' || $omode eq 'MAX';
418 4         21 _failmsg "Found unbalanced nested tag: $tag",
419             pos $$textref;
420 4         68 goto failed;
421             }
422             }
423 1102         3957 else { $$textref =~ m/./gcs }
424             }
425              
426             short:
427 22         47 $closetagpos = pos($$textref);
428 22 100       67 goto matched if $omode eq 'MAX';
429 18 100       76 goto failed unless $omode eq 'PARA';
430              
431 4 50       15 if (defined $parapos) { pos($$textref) = $parapos }
  0         0  
432 4         11 else { $parapos = pos($$textref) }
433              
434             return (
435 4         26 $startpos, $opentagpos-$startpos, # PREFIX
436             $opentagpos, $textpos-$opentagpos, # OPENING TAG
437             $textpos, $parapos-$textpos, # TEXT
438             $parapos, 0, # NO CLOSING TAG
439             $parapos, length($$textref)-$parapos, # REMAINDER
440             );
441              
442 86         202 matched:
443             $endpos = pos($$textref);
444             return (
445 86         558 $startpos, $opentagpos-$startpos, # PREFIX
446             $opentagpos, $textpos-$opentagpos, # OPENING TAG
447             $textpos, $closetagpos-$textpos, # TEXT
448             $closetagpos, $endpos-$closetagpos, # CLOSING TAG
449             $endpos, length($$textref)-$endpos, # REMAINDER
450             );
451              
452 30 100       188 failed:
453             _failmsg "Did not find closing tag", pos $$textref unless $@;
454 30         90 pos($$textref) = $startpos;
455 30         138 return;
456             }
457              
458             sub extract_variable (;$$)
459             {
460 625 50   625 1 544471 my $textref = defined $_[0] ? \$_[0] : \$_;
461 625 50       1311 return ("","","") unless defined $$textref;
462 625 100       1652 $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0 if !pos($$textref); # reset
463 625 100       1931 my $pre = defined $_[1] ? qr/\G$_[1]/ : qr/\G\s*/;
464              
465 625         1271 my @match = _match_variable($textref,$pre);
466              
467 625 100       1253 return _fail wantarray, $textref unless @match;
468              
469 234         736 return _succeed wantarray, $textref,
470             @match[2..3,4..5,0..1]; # MATCH, REMAINDER, PREFIX
471             }
472              
473             sub _match_variable
474             {
475             # $#
476             # $^
477             # $$
478 1345     1345   2168 my ($textref, $pre) = @_;
479 1345   100     4259 my $startpos = pos($$textref) = pos($$textref)||0;
480 1345 100       6414 unless ($$textref =~ m/$pre/gc)
481             {
482 341         1236 _failmsg "Did not find prefix: /$pre/", pos $$textref;
483 341         1166 return;
484             }
485 1004         1465 my $varpos = pos($$textref);
486 1004 100       2713 unless ($$textref =~ m{\G\$\s*(?!::)(\d+|[][&`'+*./|,";%=~:?!\@<>()-]|\^[a-z]?)}gci)
487             {
488 912 100       3857 unless ($$textref =~ m/\G((\$#?|[*\@\%]|\\&)+)/gc)
489             {
490 659         1354 _failmsg "Did not find leading dereferencer", pos $$textref;
491 659         1322 pos $$textref = $startpos;
492 659         2030 return;
493             }
494 253         546 my $deref = $1;
495              
496 253 50 100     1349 unless ($$textref =~ m/\G\s*(?:::|')?(?:[_a-z]\w*(?:::|'))*[_a-z]\w*/gci
      100        
      100        
      66        
497             or _match_codeblock($textref, qr/\G()/, '\{', qr/\G\s*(\})/, '\{', '\}', 0, 1)
498             or $deref eq '$#' or $deref eq '$$'
499             or pos($$textref) == length $$textref )
500             {
501 0         0 _failmsg "Bad identifier after dereferencer", pos $$textref;
502 0         0 pos $$textref = $startpos;
503 0         0 return;
504             }
505             }
506              
507 345         506 while (1)
508             {
509 423 100       1225 next if $$textref =~ m/\G\s*(?:->)?\s*[{]\w+[}]/gc;
510 396 100       2350 next if _match_codeblock($textref,
511             qr/\G\s*->\s*(?:[_a-zA-Z]\w+\s*)?/,
512             qr/[({[]/, qr/\G\s*([)}\]])/,
513             qr/[({[]/, qr/[)}\]]/, 0, 1);
514 373 100       2202 next if _match_codeblock($textref,
515             qr/\G\s*/, qr/[{[]/, qr/\G\s*([}\]])/,
516             qr/[{[]/, qr/[}\]]/, 0, 1);
517 357 50       1483 next if _match_variable($textref,qr/\G\s*->\s*/);
518 357 100       989 next if $$textref =~ m/\G\s*->\s*\w+(?![{([])/gc;
519 345         614 last;
520             }
521 345         936 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 0;
522              
523 345         561 my $endpos = pos($$textref);
524 345         1374 return ($startpos, $varpos-$startpos,
525             $varpos, $endpos-$varpos,
526             $endpos, length($$textref)-$endpos
527             );
528             }
529              
530             my %ec_delim_cache;
531             sub _ec_delims {
532 96     96   272 my ($ldel_inner, $ldel_outer) = @_;
533 0         0 return @{ $ec_delim_cache{$ldel_outer}{$ldel_inner} }
534 96 50       367 if $ec_delim_cache{$ldel_outer}{$ldel_inner};
535 96         230 my $rdel_inner = $ldel_inner;
536 96         168 my $rdel_outer = $ldel_outer;
537 96         202 my $posbug = pos;
538 96         261 for ($ldel_inner, $ldel_outer) { tr/[]()<>{}\0-\377/[[((<<{{/ds }
  192         507  
539 96         252 for ($rdel_inner, $rdel_outer) { tr/[]()<>{}\0-\377/]]))>>}}/ds }
  192         348  
540 96         215 for ($ldel_inner, $ldel_outer, $rdel_inner, $rdel_outer)
541             {
542 384         872 $_ = '('.join('|',map { quotemeta $_ } split('',$_)).')'
  400         1326  
543             }
544 96         365 pos = $posbug;
545 96         192 @{ $ec_delim_cache{$ldel_outer}{$ldel_inner} = [
  96         1754  
546             $ldel_outer, qr/\G\s*($rdel_outer)/, $ldel_inner, $rdel_inner
547             ] };
548             }
549             sub extract_codeblock (;$$$$$)
550             {
551 96 50   96 1 319232 my $textref = defined $_[0] ? \$_[0] : \$_;
552 96 100       509 $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0 if !pos($$textref); # reset
553 96         235 my $wantarray = wantarray;
554 96 100       259 my $ldel_inner = defined $_[1] ? $_[1] : '{';
555 96 100       577 my $pre = !defined $_[2] ? qr/\G\s*/ : qr/\G$_[2]/;
556 96 100       305 my $ldel_outer = defined $_[3] ? $_[3] : $ldel_inner;
557 96         178 my $rd = $_[4];
558 96         345 my @delims = _ec_delims($ldel_inner, $ldel_outer);
559              
560 96         318 my @match = _match_codeblock($textref, $pre, @delims, $rd, 1);
561 96 100       237 return _fail($wantarray, $textref) unless @match;
562 51         259 return _succeed($wantarray, $textref,
563             @match[2..3,4..5,0..1] # MATCH, REMAINDER, PREFIX
564             );
565             }
566              
567             sub _match_codeblock
568             {
569 1044     1044   4442 my ($textref, $pre, $ldel_outer, $rdel_outer, $ldel_inner, $rdel_inner, $rd, $no_backcompat) = @_;
570 1044 100       2054 $rdel_outer = qr/\G\s*($rdel_outer)/ if !$no_backcompat; # Switch calls this func directly
571 1044   100     3155 my $startpos = pos($$textref) = pos($$textref) || 0;
572 1044 100       4757 unless ($$textref =~ m/$pre/gc)
573             {
574 357         2093 _failmsg qq{Did not match prefix /$pre/ at"} .
575             substr($$textref,pos($$textref),20) .
576             q{..."},
577             pos $$textref;
578 357         1227 return;
579             }
580 687         1379 my $codepos = pos($$textref);
581 687 100       8508 unless ($$textref =~ m/\G($ldel_outer)/gc) # OUTERMOST DELIMITER
582             {
583 552         2750 _failmsg qq{Did not find expected opening bracket at "} .
584             substr($$textref,pos($$textref),20) .
585             q{..."},
586             pos $$textref;
587 552         1593 pos $$textref = $startpos;
588 552         1667 return;
589             }
590 135         419 my $closing = $1;
591 135         251 $closing =~ tr/([<{/)]>}/;
592 135         221 my $matched;
593             $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0
594 135 100 66     807 if !pos($$textref) or !defined $ref2slashvalid{$textref}; # default, or reset
595 135         374 while (pos($$textref) < length($$textref))
596             {
597 500 50 66     963 if ($rd && $$textref =~ m#\G(\Q(?)\E|\Q(s?)\E|\Q(s)\E)#gc)
598             {
599 0         0 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 0;
600 0         0 next;
601             }
602              
603 500 100       1131 if ($$textref =~ m/\G\s*#.*/gc)
604             {
605 6         11 next;
606             }
607              
608 494 100       2187 if ($$textref =~ m/$rdel_outer/gc)
609             {
610 131 100 66     644 unless ($matched = ($closing && $1 eq $closing) )
611             {
612 2 50       7 next if $1 eq '>'; # MIGHT BE A "LESS THAN"
613 0         0 _failmsg q{Mismatched closing bracket at "} .
614             substr($$textref,pos($$textref),20) .
615             qq{...". Expected '$closing'},
616             pos $$textref;
617             }
618 129         219 last;
619             }
620              
621 363 100 100     1034 if (_match_variable($textref,qr/\G\s*/) ||
622             _match_quotelike($textref,qr/\G\s*/,$ref2slashvalid{$textref},$ref2qmarkvalid{$textref}) )
623             {
624 138         315 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 0;
625 138         258 next;
626             }
627              
628 225 100       5749 if ($$textref =~ m#\G\s*(?!$ldel_inner)(?:$RE_PREREGEX_PAT|$RE_EXPR_PAT)#gc)
629             {
630 74         206 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 1;
631 74         156 next;
632             }
633              
634 151 100       1526 if ( _match_codeblock($textref, qr/\G\s*/, $ldel_inner, qr/\G\s*($rdel_inner)/, $ldel_inner, $rdel_inner, $rd, 1) )
635             {
636 17         32 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 1;
637 17         47 next;
638             }
639              
640 134 50       1257 if ($$textref =~ m/\G\s*$ldel_outer/gc)
641             {
642 0         0 _failmsg q{Improperly nested codeblock at "} .
643             substr($$textref,pos($$textref),20) .
644             q{..."},
645             pos $$textref;
646 0         0 last;
647             }
648              
649 134         379 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 0;
650 134         422 $$textref =~ m/\G\s*(\w+|[-=>]>|.|\Z)/gc;
651             }
652 371         1127 continue { $@ = undef }
653              
654 135 100       274 unless ($matched)
655             {
656 6 50       25 _failmsg 'No match found for opening bracket', pos $$textref
657             unless $@;
658 6         39 return;
659             }
660              
661 129         279 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = undef;
662 129         186 my $endpos = pos($$textref);
663 129         562 return ( $startpos, $codepos-$startpos,
664             $codepos, $endpos-$codepos,
665             $endpos, length($$textref)-$endpos,
666             );
667             }
668              
669              
670             my %mods = (
671             'none' => '[cgimsox]*',
672             'm' => '[cgimsox]*',
673             's' => '[cegimsox]*',
674             'tr' => '[cds]*',
675             'y' => '[cds]*',
676             'qq' => '',
677             'qx' => '',
678             'qw' => '',
679             'qr' => '[imsx]*',
680             'q' => '',
681             );
682              
683             sub extract_quotelike (;$$)
684             {
685 542 100   542 1 501455 my $textref = $_[0] ? \$_[0] : \$_;
686 542 100       1433 $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0 if !pos($$textref); # reset
687 542         759 my $wantarray = wantarray;
688 542 100       1462 my $pre = defined $_[1] ? qr/\G$_[1]/ : qr/\G\s*/;
689              
690 542         1455 my @match = _match_quotelike($textref,$pre,$ref2slashvalid{$textref},$ref2qmarkvalid{$textref});
691 542 100       1202 return _fail($wantarray, $textref) unless @match;
692 103         569 return _succeed($wantarray, $textref,
693             $match[2], $match[18]-$match[2], # MATCH
694             @match[18,19], # REMAINDER
695             @match[0,1], # PREFIX
696             @match[2..17], # THE BITS
697             @match[20,21], # ANY FILLET?
698             );
699             };
700              
701             my %maybe_quote = map +($_=>1), qw(" ' `);
702             sub _match_quotelike
703             {
704 848     848   1495 my ($textref, $pre, $allow_slash_match, $allow_qmark_match) = @_;
705             $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0
706 848 100 100     3146 if !pos($$textref) or !defined $ref2slashvalid{$textref}; # default, or reset
707              
708 848   100     3426 my ($textlen,$startpos,
709             $preld1pos,$ld1pos,$str1pos,$rd1pos,
710             $preld2pos,$ld2pos,$str2pos,$rd2pos,
711             $modpos) = ( length($$textref), pos($$textref) = pos($$textref) || 0 );
712              
713 848 50       3764 unless ($$textref =~ m/$pre/gc)
714             {
715 0         0 _failmsg qq{Did not find prefix /$pre/ at "} .
716             substr($$textref, pos($$textref), 20) .
717             q{..."},
718             pos $$textref;
719 0         0 return;
720             }
721 848         1139 my $oppos = pos($$textref);
722 848         1679 my $initial = substr($$textref,$oppos,1);
723 848 100 100     5041 if ($initial && $maybe_quote{$initial}
      100        
      100        
      100        
      100        
724             || $allow_slash_match && $initial eq '/'
725             || $allow_qmark_match && $initial eq '?')
726             {
727 60 100       1496 unless ($$textref =~ m/\G \Q$initial\E [^\\$initial]* (\\.[^\\$initial]*)* \Q$initial\E /gcsx)
728             {
729 2         14 _failmsg qq{Did not find closing delimiter to match '$initial' at "} .
730             substr($$textref, $oppos, 20) .
731             q{..."},
732             pos $$textref;
733 2         100 pos $$textref = $startpos;
734 2         15 return;
735             }
736 58         182 $modpos= pos($$textref);
737 58         95 $rd1pos = $modpos-1;
738              
739 58 100 66     248 if ($initial eq '/' || $initial eq '?')
740             {
741 17         184 $$textref =~ m/\G$mods{none}/gc
742             }
743              
744 58         104 my $endpos = pos($$textref);
745 58         161 $ref2qmarkvalid{$textref} = $ref2slashvalid{$textref} = 0;
746             return (
747 58         344 $startpos, $oppos-$startpos, # PREFIX
748             $oppos, 0, # NO OPERATOR
749             $oppos, 1, # LEFT DEL
750             $oppos+1, $rd1pos-$oppos-1, # STR/PAT
751             $rd1pos, 1, # RIGHT DEL
752             $modpos, 0, # NO 2ND LDEL
753             $modpos, 0, # NO 2ND STR
754             $modpos, 0, # NO 2ND RDEL
755             $modpos, $endpos-$modpos, # MODIFIERS
756             $endpos, $textlen-$endpos, # REMAINDER
757             );
758             }
759              
760 788 100       2352 unless ($$textref =~ m{\G(\b(?:m|s|qq|qx|qw|q|qr|tr|y)\b(?=\s*\S)|<<(?=[a-zA-Z]|\s*['"`;,]))}gc)
761             {
762 705         2451 _failmsg q{No quotelike operator found after prefix at "} .
763             substr($$textref, pos($$textref), 20) .
764             q{..."},
765             pos $$textref;
766 705         1836 pos $$textref = $startpos;
767 705         1947 return;
768             }
769              
770 83         234 my $op = $1;
771 83         180 $preld1pos = pos($$textref);
772 83 100       238 if ($op eq '<<') {
773 28         43 $ld1pos = pos($$textref);
774 28         45 my $label;
775 28 100       190 if ($$textref =~ m{\G([A-Za-z_]\w*)}gc) {
    100          
776 9         22 $label = $1;
777             }
778             elsif ($$textref =~ m{ \G ' ([^'\\]* (?:\\.[^'\\]*)*) '
779             | \G " ([^"\\]* (?:\\.[^"\\]*)*) "
780             | \G ` ([^`\\]* (?:\\.[^`\\]*)*) `
781             }gcsx) {
782 16         50 $label = $+;
783             }
784             else {
785 3         7 $label = "";
786             }
787 28         50 my $extrapos = pos($$textref);
788 28         107 $$textref =~ m{.*\n}gc;
789 28         122 $str1pos = pos($$textref)--;
790 28 100       509 unless ($$textref =~ m{.*?\n(?=\Q$label\E\n)}gc) {
791 4         29 _failmsg qq{Missing here doc terminator ('$label') after "} .
792             substr($$textref, $startpos, 20) .
793             q{..."},
794             pos $$textref;
795 4         13 pos $$textref = $startpos;
796 4         15 return;
797             }
798 24         52 $rd1pos = pos($$textref);
799 24         184 $$textref =~ m{\Q$label\E\n}gc;
800 24         45 $ld2pos = pos($$textref);
801 24         78 $ref2qmarkvalid{$textref} = $ref2slashvalid{$textref} = 0;
802             return (
803 24         201 $startpos, $oppos-$startpos, # PREFIX
804             $oppos, length($op), # OPERATOR
805             $ld1pos, $extrapos-$ld1pos, # LEFT DEL
806             $str1pos, $rd1pos-$str1pos, # STR/PAT
807             $rd1pos, $ld2pos-$rd1pos, # RIGHT DEL
808             $ld2pos, 0, # NO 2ND LDEL
809             $ld2pos, 0, # NO 2ND STR
810             $ld2pos, 0, # NO 2ND RDEL
811             $ld2pos, 0, # NO MODIFIERS
812             $ld2pos, $textlen-$ld2pos, # REMAINDER
813             $extrapos, $str1pos-$extrapos, # FILLETED BIT
814             );
815             }
816              
817 55         162 $$textref =~ m/\G\s*/gc;
818 55         106 $ld1pos = pos($$textref);
819 55         155 $str1pos = $ld1pos+1;
820              
821 55 50       318 if ($$textref !~ m/\G(\S)/gc) # SHOULD USE LOOKAHEAD
    100          
822             {
823 0         0 _failmsg "No block delimiter found after quotelike $op",
824             pos $$textref;
825 0         0 pos $$textref = $startpos;
826 0         0 return;
827             }
828             elsif (substr($$textref, $ld1pos, 2) eq '=>')
829             {
830 1         5 _failmsg "quotelike $op was actually quoted by '=>'",
831             pos $$textref;
832 1         3 pos $$textref = $startpos;
833 1         3 return;
834             }
835 54         155 pos($$textref) = $ld1pos; # HAVE TO DO THIS BECAUSE LOOKAHEAD BROKEN
836 54         235 my ($ldel1, $rdel1) = ("\Q$1","\Q$1");
837 54 100       206 if ($ldel1 =~ /[[(<{]/)
838             {
839 33         94 $rdel1 =~ tr/[({/;
840             defined(_match_bracketed($textref,qr/\G/,qr/\G($ldel1)/,"","",qr/\G($rdel1)/))
841 33 50       592 || do { pos $$textref = $startpos; return };
  0         0  
  0         0  
842 33         128 $ld2pos = pos($$textref);
843 33         67 $rd1pos = $ld2pos-1;
844             }
845             else
846             {
847             $$textref =~ /\G$ldel1[^\\$ldel1]*(\\.[^\\$ldel1]*)*$ldel1/gcs
848 21 50       615 || do { pos $$textref = $startpos; return };
  0         0  
  0         0  
849 21         63 $ld2pos = $rd1pos = pos($$textref)-1;
850             }
851              
852 54 100       312 my $second_arg = $op =~ /s|tr|y/ ? 1 : 0;
853 54 100       129 if ($second_arg)
854             {
855 23         55 my ($ldel2, $rdel2);
856 23 100       74 if ($ldel1 =~ /[[(<{]/)
857             {
858 11 50       53 unless ($$textref =~ /\G\s*(\S)/gc) # SHOULD USE LOOKAHEAD
859             {
860 0         0 _failmsg "Missing second block for quotelike $op",
861             pos $$textref;
862 0         0 pos $$textref = $startpos;
863 0         0 return;
864             }
865 11         37 $ldel2 = $rdel2 = "\Q$1";
866 11         40 $rdel2 =~ tr/[({/;
867             }
868             else
869             {
870 12         29 $ldel2 = $rdel2 = $ldel1;
871             }
872 23         66 $str2pos = $ld2pos+1;
873              
874 23 100       67 if ($ldel2 =~ /[[(<{]/)
875             {
876 9         30 pos($$textref)--; # OVERCOME BROKEN LOOKAHEAD
877             defined(_match_bracketed($textref,qr/\G/,qr/\G($ldel2)/,"","",qr/\G($rdel2)/))
878 9 50       118 || do { pos $$textref = $startpos; return };
  0         0  
  0         0  
879             }
880             else
881             {
882             $$textref =~ /[^\\$ldel2]*(\\.[^\\$ldel2]*)*$ldel2/gcs
883 14 50       229 || do { pos $$textref = $startpos; return };
  0         0  
  0         0  
884             }
885 23         64 $rd2pos = pos($$textref)-1;
886             }
887             else
888             {
889 31         65 $ld2pos = $str2pos = $rd2pos = $rd1pos;
890             }
891              
892 54         92 $modpos = pos $$textref;
893              
894 54         790 $$textref =~ m/\G($mods{$op})/gc;
895 54         131 my $endpos = pos $$textref;
896 54         222 $ref2qmarkvalid{$textref} = $ref2slashvalid{$textref} = undef;
897              
898             return (
899 54         364 $startpos, $oppos-$startpos, # PREFIX
900             $oppos, length($op), # OPERATOR
901             $ld1pos, 1, # LEFT DEL
902             $str1pos, $rd1pos-$str1pos, # STR/PAT
903             $rd1pos, 1, # RIGHT DEL
904             $ld2pos, $second_arg, # 2ND LDEL (MAYBE)
905             $str2pos, $rd2pos-$str2pos, # 2ND STR (MAYBE)
906             $rd2pos, $second_arg, # 2ND RDEL (MAYBE)
907             $modpos, $endpos-$modpos, # MODIFIERS
908             $endpos, $textlen-$endpos, # REMAINDER
909             );
910             }
911              
912             my $def_func = [
913             sub { extract_variable($_[0], '') },
914             sub { extract_quotelike($_[0],'') },
915             sub { extract_codeblock($_[0],'{}','') },
916             ];
917             my %ref_not_regex = map +($_=>1), qw(CODE Text::Balanced::Extractor);
918              
919             sub _update_patvalid {
920 591     591   1128 my ($textref, $text) = @_;
921 591 100 100     7780 if ($ref2slashvalid{$textref} && $text =~ m/(?:$RE_NUM|[\)\]])\s*$/)
    100 100        
    100 100        
922             {
923 26         76 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 0;
924             } elsif (!$ref2slashvalid{$textref} && $text =~ m/$RE_PREREGEX_PAT\s*$/)
925             {
926 2         7 $ref2slashvalid{$textref} = $ref2qmarkvalid{$textref} = 1;
927             } elsif (!$ref2slashvalid{$textref} && $text =~ m/$RE_EXPR_PAT\s*$/)
928             {
929 46         75 $ref2slashvalid{$textref} = 1;
930 46         134 $ref2qmarkvalid{$textref} = 0;
931             }
932             }
933             sub extract_multiple (;$$$$) # ($text, $functions_ref, $max_fields, $ignoreunknown)
934             {
935 41 100   41 1 201590 my $textref = defined($_[0]) ? \$_[0] : \$_;
936 41 50       269 $ref2slashvalid{$textref} = 1, $ref2qmarkvalid{$textref} = 0 if !pos($$textref); # reset
937 41         77 my $posbug = pos;
938 41         71 my ($lastpos, $firstpos);
939 41         71 my @fields = ();
940              
941             #for ($$textref)
942             {
943 41 100       67 my @func = defined $_[1] ? @{$_[1]} : @{$def_func};
  41         107  
  29         60  
  12         33  
944 41 100 66     163 my $max = defined $_[2] && $_[2]>0 ? $_[2] : 1_000_000_000;
945 41         62 my $igunk = $_[3];
946              
947 41   50     310 pos $$textref ||= 0;
948              
949 41 100       108 unless (wantarray)
950             {
951 9     9   108 use Carp;
  9         49  
  9         12406  
952 14 0 33     49 carp "extract_multiple reset maximal count to 1 in scalar context"
      33        
953             if $^W && defined($_[2]) && $max > 1;
954 14         32 $max = 1
955             }
956              
957 41         57 my @class;
958 41         82 foreach my $func ( @func )
959             {
960 81         147 push @class, undef;
961 81 100       205 ($class[-1], $func) = %$func if ref($func) eq 'HASH';
962 81 100       642 $func = qr/\G$func/ if !$ref_not_regex{ref $func};
963             }
964              
965 41         67 my $unkpos;
966 41         117 FIELD: while (pos($$textref) < length($$textref))
967             {
968 594         1392 foreach my $i ( 0..$#func )
969             {
970 1136         1354 my ($field, $pref);
971 1136         1628 my ($class, $func) = ($class[$i], $func[$i]);
972 1136         1337 $lastpos = pos $$textref;
973 1136 100       2352 if (ref($func) eq 'CODE')
    50          
    100          
974 977         1429 { ($field,undef,$pref) = $func->($$textref) }
975             elsif (ref($func) eq 'Text::Balanced::Extractor')
976 0         0 { $field = $func->extract($$textref) }
977             elsif( $$textref =~ m/$func[$i]/gc )
978 33 100       126 { $field = defined($1)
979             ? $1
980             : substr($$textref, $-[0], $+[0] - $-[0])
981             }
982 1136   100     3425 $pref ||= "";
983 1136 100 100     2401 if (defined($field) && length($field))
984             {
985 108 100       181 if (!$igunk) {
986 101 100 100     219 $unkpos = $lastpos
987             if length($pref) && !defined($unkpos);
988 101 100       178 if (defined $unkpos)
989             {
990 72         142 push @fields, substr($$textref, $unkpos, $lastpos-$unkpos).$pref;
991 72 100       109 $firstpos = $unkpos unless defined $firstpos;
992 72         69 undef $unkpos;
993 72 100       119 last FIELD if @fields == $max;
994             }
995             }
996 105 50       184 push @fields, $class ? bless(\$field, $class) : $field;
997 105         222 _update_patvalid($textref, $fields[-1]);
998 105 100       238 $firstpos = $lastpos unless defined $firstpos;
999 105         129 $lastpos = pos $$textref;
1000 105 100       191 last FIELD if @fields == $max;
1001 90         261 next FIELD;
1002             }
1003             }
1004 486 50       1319 if ($$textref =~ /\G(.)/gcs)
1005             {
1006 486 100 100     1200 $unkpos = pos($$textref)-1
1007             unless $igunk || defined $unkpos;
1008 486 100       1178 _update_patvalid($textref, substr $$textref, defined $unkpos ? $unkpos : 0,
    100          
1009             pos($$textref) - (defined $unkpos ? $unkpos : 0));
1010             }
1011             }
1012              
1013 41 100       85 if (defined $unkpos)
1014             {
1015 18         54 push @fields, substr($$textref, $unkpos);
1016 18 100       32 $firstpos = $unkpos unless defined $firstpos;
1017 18         23 $lastpos = length $$textref;
1018             }
1019 41         90 last;
1020             }
1021              
1022 41         78 pos $$textref = $lastpos;
1023 41 100       311 return @fields if wantarray;
1024              
1025 14   100     72 $firstpos ||= 0;
1026 14         26 eval { substr($$textref,$firstpos,$lastpos-$firstpos)="";
  14         65  
1027 14         26 pos $$textref = $firstpos };
1028 14         114 return $fields[0];
1029             }
1030              
1031             sub gen_extract_tagged # ($opentag, $closetag, $pre, \%options)
1032             {
1033 13     13 1 647035 my $ldel = $_[0];
1034 13         63 my $rdel = $_[1];
1035 13 100       102 my $pre = defined $_[2] ? qr/\G$_[2]/ : qr/\G\s*/;
1036 13 100       48 my %options = defined $_[3] ? %{$_[3]} : ();
  6         26  
1037 13 100       50 my $omode = defined $options{fail} ? $options{fail} : '';
1038 3         13 my $bad = ref($options{reject}) eq 'ARRAY' ? join('|', @{$options{reject}})
1039             : defined($options{reject}) ? $options{reject}
1040 13 50       61 : ''
    100          
1041             ;
1042 3         13 my $ignore = ref($options{ignore}) eq 'ARRAY' ? join('|', @{$options{ignore}})
1043             : defined($options{ignore}) ? $options{ignore}
1044 13 50       47 : ''
    100          
1045             ;
1046              
1047 13 100       45 $ldel = $et_default_ldel if !defined $ldel;
1048              
1049 13         33 my $posbug = pos;
1050 13 100       34 for ($ldel, $bad, $ignore) { $_ = qr/$_/ if $_ }
  39         789  
1051 13         49 pos = $posbug;
1052              
1053             my $closure = sub
1054             {
1055 40 50   40   77630 my $textref = defined $_[0] ? \$_[0] : \$_;
1056 40         152 my @match = _match_tagged($textref, $pre, $ldel, $rdel, $omode, $bad, $ignore);
1057              
1058 40 100       161 return _fail(wantarray, $textref) unless @match;
1059 28         154 return _succeed wantarray, $textref,
1060             $match[2], $match[3]+$match[5]+$match[7], # MATCH
1061             @match[8..9,0..1,2..7]; # REM, PRE, BITS
1062 13         108 };
1063              
1064 13         168 bless $closure, 'Text::Balanced::Extractor';
1065             }
1066              
1067             package Text::Balanced::Extractor;
1068              
1069             sub extract($$) # ($self, $text)
1070             {
1071 0     0     &{$_[0]}($_[1]);
  0            
1072             }
1073              
1074             package Text::Balanced::ErrorMsg;
1075              
1076             use overload
1077 33     33   217 '""' => sub { "$_[0]->{error}, detected at offset $_[0]->{pos}" },
1078 9     9   6057 fallback => 1;
  9         18360  
  9         112  
1079              
1080             1;
1081              
1082             __END__