File Coverage

blib/lib/Regexp/List.pm
Criterion Covered Total %
statement 187 260 71.9
branch 85 142 59.8
condition 9 14 64.2
subroutine 21 26 80.7
pod 5 8 62.5
total 307 450 68.2


line stmt bran cond sub pod time code
1             #
2             # $Id: List.pm,v 0.13 2004/12/05 16:07:34 dankogai Exp dankogai $
3             #
4             package Regexp::List;
5 4     4   67 use 5.006; # qr/(??{}/ needed
  4         12  
  4         149  
6 4     4   18 use strict;
  4         5  
  4         125  
7 4     4   18 use warnings;
  4         5  
  4         121  
8 4     4   20 no warnings 'uninitialized';
  4         6  
  4         4964  
9             #use base qw/Exporter/;
10             our $VERSION = '0.16_001';
11              
12             #our @EXPORT = qw();
13             #our %EXPORT_TAGS = ( 'all' => [ qw() ] );
14             #our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
15             our $DEBUG = 0;
16              
17             our $FILLER = "\x{fffd}"; # fallback
18              
19             # According "perlre", a "^" immediately after the "?" is a shorthand
20             # equivalent to "d-imsx" since Perl 5.14.
21             my $options = 'imsx\-';
22             if ($^V gt v5.14.0) {
23             $options .= '^';
24             }
25              
26             our $RE_PAREN; # predeclear
27             our $RE_START =
28             qr{(?:
29             (?!\\)\((?:\?
30             (?:
31             ([$options]*:)| # options
32             \
33             \#[^\)]+ | # comments
34             #$RE_PAREN | # ( condtion )
35             #\??$RE_EXPR | # { expression }
36             \> # independent subexpression
37             ))?
38             )}xo;
39              
40             our $RE_XCHAR =
41             qr{
42             (?:\\
43             (?:
44             [^0xclupPNLUQEXC] | # ordinary escaped character
45             0[0-9][0-9] | # octal
46             x(?:[0-9A-Fa-f]{1,2} | # hex
47             \{[0-9A-Fa-f]+\}) | # unicode hex
48             c. | # control char
49             [pP]\{\w+\} | # unicode properties
50             N\{[\w\ ]+\} | # unicode name
51             )
52             )}xo;
53              
54             our $RE_PCHAR =
55             qr{
56             (?:\\
57             (?:
58             [XC] # unicode name
59             )
60             )}xo;
61              
62             our $RE_CHAR =
63             qr{(?:
64             (?!\\)[^\\] | # raw character (except \)
65             $RE_XCHAR | # extended character
66             )
67             }xo;
68              
69             our %PARAM =
70             (
71             _i => 0,
72             _m => 0,
73             _s => 0,
74             _x => 0,
75             _char => $RE_CHAR,
76             _token => $RE_CHAR,
77             _cclass => $RE_CHAR,
78             debug => $DEBUG,
79             capture => 0,
80             lookahead => 1,
81             modifiers => '',
82             optim_cc => 1,
83             optim_cq => 1,
84             optim_sx => 1,
85             po => '(?:',
86             pc => ')',
87             quotemeta => 1,
88             sort => 0,
89             );
90              
91             # aliases
92              
93             *l2r = \&list2re;
94             *list2regex = \&list2re;
95             *list2regexp = \&list2re;
96              
97             sub new{
98 7 100   7 1 39 my $class = ref $_[0] ? ref shift : shift;
99 7         96 my $self = bless { %PARAM } => $class;
100 7         41 $self->set(@_);
101             }
102              
103             sub clone{
104 26     26 0 32 my $self = shift;
105 26         326 my $clone = bless { %$self } => ref $self;
106 26         109 $clone->set(@_);
107             }
108              
109             sub set{
110 63     63 1 65 my $self = shift;
111 63         121 my %param = @_;
112 63         207 for (sort keys %param){
113 112         164 $self->{$_} = $param{$_};
114 112 50       186 if ($_ eq 'capture'){
115 0 0       0 $self->{op} = $self->{capture} ? '(' : '(?:';
116 0         0 $self->{cp} = ')';
117             }
118 112 100       202 if ($_ eq 'modifiers'){
119 4         11 map { $self->{'_' . $_} = 0 } qw/i m s x/;
  16         31  
120 4         17 map { $self->{'_' . $_} = 1 } split //, $self->{$_};
  0         0  
121             }
122             }
123 63         187 $self;
124             }
125              
126             sub tokens{
127 0     0 0 0 my $self = shift;
128 0         0 my $str = shift;
129 0         0 grep {$_ ne '' } split /($self->{_token})/, $str;
  0         0  
130             }
131              
132             sub regopt{
133 0     0 0 0 my $re = shift;
134             #ref $re eq 'Regexp' or return;
135 0 0       0 $re =~ /^($RE_START)/ or return; # die "malformed regexp : $re";
136 0         0 my $opt = $1;
137 0         0 $opt =~ s/\(\?//o; $opt =~ s/[-:].*//o;
  0         0  
138 0         0 $opt;
139             }
140              
141             sub expand{
142 0     0 1 0 my $self = shift;
143 0         0 my $re = shift;
144 0         0 my $isre = ref $re eq 'Regexp';
145             #$isre or $re = qr/$re/;
146 0         0 my $mod = regopt($re);
147 0 0       0 $mod =~ /x/ or $mod .= 'x';
148 0         0 my ($indent, @indent);
149 0         0 $re =~
150             s{
151             ( $RE_START | (?!\\)[\)|])
152             }{
153 0         0 my $paren = $1;
154 0         0 my $sub = $paren;
155 0 0       0 if ($paren eq ')'){ # close
    0          
156 0         0 $indent -= pop @indent;
157             }elsif($paren eq '|'){ # |
158 0         0 $sub = " | \n";
159 0         0 $sub .= " " x $indent;
160             }else{
161 0 0       0 $sub = $indent ? "\n" : '';
162 0         0 $sub .= " " x $indent . $paren;
163 0         0 $indent += length($paren);
164 0         0 push @indent, length($paren);
165             }
166 0         0 $sub;
167             }geox;
168 0 0       0 $isre ? qr/(?$mod:$re)/ : qq/(?$mod:$re)/;
169             }
170              
171             sub unexpand{
172 0     0 1 0 my $self = shift;
173 0         0 my $re = shift;
174 0         0 my $isre = ref $re eq 'Regexp';
175 0         0 my $mod = regopt($re);
176 0         0 $mod =~ s/x//o;
177 0         0 $re =~ s/\((?!\\)\?\#[^\)]+\)//o; # strip (?#comment)
178 0         0 $re =~ s/(?!\\)#.*$//mg; # strip comment
179 0         0 $re =~ s/(?!\\)[ \t]//g; # strip space
180             # $re =~ s/([^\x00-\xff])/sprintf('\x{%04x}', ord($1))/eg;
181             # and finally strip CRLF
182 0         0 $re =~ s/[\n\r]//g;
183 0 0       0 $isre ?
    0          
    0          
184             $mod ? qr/(?$mod:$re)/ : qr/$re/ :
185             $mod ? qq/(?$mod:$re)/ : $re;
186             }
187              
188             sub list2re {
189 4     4   2756 use utf8; # for substr
  4         44  
  4         27  
190 4     4   241 no warnings 'redefine'; # for cheats
  4         7  
  4         3661  
191 42     42 1 9694 my $self = shift;
192             # trie construction allows no duplicates
193             # so we make sure they are all unique
194 42         45 my (%list, @list);
195             # Unique with order preserved
196 42 0       165 if ($self->{_i}){ for (@_){ $_=lc($_); $list{$_}++ or push @list, $_ } }
  0 50       0  
  0         0  
  0         0  
197 42 100       64 else { for (@_){ $list{$_}++ or push @list, $_ } }
  936         1712  
198 42         108 undef %list; # to save memory
199             #$self->{sort} and @list = sort {length($b) <=> length($a) } @list;
200 42 50       93 $self->{sort} and @list = sort @list;
201 42         32 my $result;
202 42 100       82 if ($self->{quotemeta}){
203             # cheat
204 17     4087   120 *_head = sub{ substr($_[1], 0, $_[2]*2) };
  4087         8010  
205 17     247   69 *_tail = sub{ substr($_[1], $_[2]*2) };
  247         441  
206 17         28 $result = _trie_regex($self, map { _metaquote($_) } @list);
  217         272  
207 17         143 $result =~ tr/\x00//d;
208             #$result =~ tr/\x{FFFd}//d;
209             }else{
210 25         129 *_head = \&_head_re;
211 25         75 *_tail = \&_tail_re;
212 25 50       57 $self->{_x} and @list = map { s/\\? /\\ /g; $_ } @list;
  0         0  
  0         0  
213 25         49 $result = _trie_regex($self, @list);
214             }
215 42         49 my $lookahead;
216 42 50       100 if ($self->{lookahead}){
217 0         0 my %lookahead;
218 0         0 $lookahead{$self->_first($_)}++ for @list;
219 0         0 my @lookahead =
220             $self->{quotemeta}
221 0 0       0 ? map { tr/\x00//d; qq/\Q$_/ } keys %lookahead
  0         0  
222             #map { tr/\x{FFFd}//d; qq/\Q$_/ } keys %lookahead
223             : keys %lookahead;
224 0         0 @lookahead = sort sort grep {!/^\\[luLUEQXC]/} @lookahead;
  0         0  
225 0 0       0 if (@lookahead > 1){
226 0         0 my $lookahead = join('' => @lookahead);
227 0         0 $result = qq/(?=[$lookahead])$result/;
228             }
229 0         0 undef @lookahead;
230             }
231 42         61 my $mod = $self->{modifiers};
232 42 50       96 $mod =~ 'x' and return $self->expand($result);
233 42 50       2503 $result = $self->{as_string} ?
    50          
    100          
234             $mod ? qq/(?$mod:$result)/ : qq/$result/ :
235             $mod ? qr/(?$mod:$result)/ : qr/$result/;
236             }
237              
238             sub _metaquote{
239 1686         1286 my $str =
240             join '' =>
241 217 100   217   376 map { my $q=qq/\Q$_/; length($q) == 2 ? $q : "\x00$q" }
  1686         2578  
242             #map { my $q=qq/\Q$_/; length($q) == 2 ? $q : "\x{FFFd}$q" }
243             split // => shift;
244 217         412 $str;
245             }
246              
247             sub _first{
248 0     0   0 my $self = shift;
249 0         0 my $str = shift;
250 0         0 my $re = $self->{_char};
251 0         0 $str =~ /^($re)/o;
252 0         0 return $1;
253             }
254              
255             sub _head_re{
256 2682     2682   3514 my $self = shift;
257 2682         4103 my ($str, $pos) = @_;
258 2682 100       5390 $str eq '' and return '';
259 2620         4404 my $token = $self->{_token};
260 2620         10172 for (my $p = $pos, pos($str) = 0; $p > 0 ; $p--){
261 21686 100       5998437 $str =~ /\G$token/gc or last;
262             }
263 2620         21440 substr($str, 0, pos($str));
264            
265             }
266              
267             sub _tail_re{
268 4     4   23 use utf8;
  4         4  
  4         18  
269 239     239   322 my $self = shift;
270 239         344 my ($str, $pos) = @_;
271 239 50       510 $str eq '' and return '';
272 239         347 my $token = $self->{_token};
273 239         2180 for (my $p = $pos, pos($str) = 0; $p > 0 ; $p--){
274 335 50       99195 $str =~ /\G$token/gcs or last;
275             }
276 239         1225 substr($str,pos($str));
277             }
278              
279 4     4   3397 use Data::Dumper;
  4         34820  
  4         4949  
280             $Data::Dumper::Indent = 1;
281              
282             sub _prefixes {
283 424     424   441 my $self = shift;
284 424         364 my (@head, @prefix, %prefix);
285 424         607 for (@_) {
286 1494         2360 my $c = $self->_head($_, 1);
287 1494 100       3498 exists $prefix{$c} or push @prefix, $c; # to preserve order
288 1494   100     4905 $prefix{$c} ||= [];
289 1494         1209 push @{$prefix{$c}}, $_;
  1494         2948  
290             }
291 424         626 for (@prefix) {
292             # Find common substring
293 1179         1562 my $prefix = $prefix{$_}->[0];
294 1179 100       920 if (@{$prefix{$_}} == 1){
  1179         2219  
295 1008         1189 push @head, [ $prefix ]; next
296 1008         1230 }
297 171         280 my $l = length($prefix);
298 171         187 for (@{$prefix{$_}}) {
  171         287  
299 486         969 $l -= 1
300             while $self->_head($_, $l) ne $self->_head($prefix, $l);
301             }
302             # Return value
303 171         582 $prefix = $self->_head($prefix, $l);
304 171         209 my @suffix = map {$self->_tail($_, $l)} @{$prefix{$_}};
  486         1034  
  171         436  
305 171         618 push @head, [$prefix, @suffix];
306             }
307             #print Dumper \@head;
308             #sleep 1;
309 424         1577 @head;
310             }
311              
312              
313             sub _rev{
314 1143     1143   1087 my $self = shift;
315 1143         1033 my $str = shift;
316 1143 100       1923 if ($self->{quotemeta}){
317 549 100       3073 return length $str > 2 ?
318             join '' => reverse split /(..)/, $str : $str;
319             }else{
320 594         727 my $re = $self->{_token};
321             #return $str =~ /^$re?$/o ?
322             # $str : join '' => reverse split /($re)/, $str;
323 594 100       176549 $str =~ /^$self->{_token}$/ and return $str;
324 364         2280 my @token;
325 364         100037 $str =~ s{ ($re) }{ push @token, $1 }egx;
  4011         12485  
326 364         4602 return join '' => reverse @token;
327            
328            
329             }
330             }
331             sub _trie_regex {
332 213     213   345 my $self = shift;
333 213 50       424 @_ or return;
334 213 100       384 @_ == 1 and return shift;
335              
336 212 50       479 $self->{debug} and $self->{_indent}++;
337 212 50       389 $self->{debug} and
338             print STDERR '>'x $self->{_indent}, " ", join(',' => @_), "\n";
339              
340 212         225 my (@leaf, @result);
341              
342             #
343             # Suffixing Optimization
344             # - only leaf nodes in the same branch can be suffix-bundled
345             #
346 212 50       342 if ($self->{optim_sx}){
347 212         446 for ($self->_prefixes(@_)){
348 681         875 my ($prefix, @suffix) = @$_;
349 681 100       924 if (@suffix){
350 130         397 push @result, $prefix.$self->_trie_regex(@suffix);
351             }else{
352 551         713 push @leaf, $prefix;
353             }
354             }
355 212         463 for ($self->_prefixes(map { $self->_rev($_) } @leaf)){
  551         1137  
356 498         973 my ($suffix, @prefix) = @$_;
357 498         986 $suffix = $self->_rev($suffix);
358 498 100       1455 if (@prefix){
359 94         194 push @result,
360 41         73 $self->_trie_regex(map { $self->_rev($_) } @prefix)
361             . $suffix;
362             }else{
363 457         868 push @result, $suffix;
364             }
365             }
366             }else{
367 0         0 for ($self->_prefixes(@_)){
368 0         0 my ($prefix, @suffix) = @$_;
369 0 0       0 push @result, @suffix ? $prefix.$self->_trie_regex(@suffix) : $prefix;
370             }
371             }
372              
373 212         380 my $result;
374              
375 212 100 66     579 RESULT:
376             {
377 212         178 @result == 1 and $result = $result[0] and last RESULT;
378 187         247 my $q = '';
379             # alteration check
380             # we do linear seach here to preserve order.
381 187         471 for (my $i = 0; $i < @result; $i++){
382 553 100       1292 if ($result[$i] eq ''){
383 62         98 splice @result, $i, 1;
384 62         84 $q = '?';
385 62         66 last;
386             }
387             }
388             # if ($result[0] eq '') { $q = '?'; shift @result }
389             # extract character class
390 187 50       499 if ($self->{optim_cc}){
391 187         162 my @char; my $charpos = -1;
  187         234  
392 187         420 for (my $i = 0; $i < @result; $i++){
393 541 100       736 if ($self->{quotemeta}){
394 256 100       528 if (length($result[$i]) == 2){
395 49 100       76 $charpos < 0 and $charpos = $i;
396 49         115 push @char => splice(@result, $i, 1, "");
397             }
398             }else{
399 285 100       2223 if ($result[$i] =~ /^$self->{_cclass}$/){
400 69 100       158 $charpos < 0 and $charpos = $i;
401 69         230 push @char => splice(@result, $i, 1, "");
402             }
403             }
404             }
405 187 100       458 if (@char){
406 72         213 my $char = $self->_optim_cc(@char);
407 72         107 splice @result, $charpos, 0, $char;
408 72         106 @result = grep {$_} @result;
  370         432  
409 72 100       192 if (@result == 1){
410 46 50       160 $result = "$result[0]$q" and last RESULT;
411             }
412             }
413             }
414 141         167 my $joiner = '|' ;
415 141 100 66     3858 if ($self->{optim_cq} and @result == 1 and
    50 66        
416             ($self->{quotemeta}
417             ? length($result[0]) ==
418             1 : $result[0] =~ /^$self->{_token}$/))
419             {
420 0         0 $result = qq/$result[0]$q/;
421             }else{
422 141         599 $result =
423             $self->{po} . join($joiner => @result) . $self->{pc} . $q;
424             }
425             }
426 212 50       494 $self->{debug} and
427             print STDERR '<'x $self->{_indent}, " ", $result, "\n";
428 212 50 33     805 $self->{_x} || $self->{debug} and $self->{_indent}--;
429              
430 212         914 $result;
431             }
432              
433             sub _optim_cc{
434 72     72   841 my $self = shift;
435 72 50       147 @_ or return undef;
436 72 100       157 if ($self->{quotemeta}){
437 31 100       109 return @_ ? @_ > 1 ? "[".join("",@_)."]" : $_[0] : undef;
    50          
438             }
439             # check '.'
440 41         86 for (@_){
441 69 50       196 $_ eq '.' and return '.';
442             }
443 41         70 my @char = @_;
444 41         67 my ($positive, $negative) = ('','');
445 41         40 my ($npos, $nneg) = (0, 0);
446 41         55 for (@char){
447 69 50       139 if (s/^\[\^(.*)\]$/$1/){
448 0         0 $negative .= $_; $nneg += 2; next;
  0         0  
  0         0  
449             }
450 69 50       108 if (s/^\[(.*)\]$/$1/){
451 0         0 $positive .= $_; $npos += 2; next;
  0         0  
  0         0  
452             }else{
453             #$positive .= length($_) eq 1 ? qq/\Q$_/ : $_;
454 69 50       125 $positive .= $_ eq '-' ? '\-' : $_;
455 69         91 $npos++;
456             }
457             }
458 41 50       83 $nneg > 1 and $negative = qq/[^$negative]/;
459 41 100       120 $npos > 1 and $positive = qq/[$positive]/;
460 41 0       271 return $negative
    50          
461             ? $positive ? "(?:$positive|$negative)" : $negative
462             : $positive;
463             }
464              
465             1;
466             __END__