File Coverage

blib/lib/Regexp/Extended.pm
Criterion Covered Total %
statement 27 27 100.0
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 36 36 100.0


line stmt bran cond sub pod time code
1             package Regexp::Extended;
2              
3 2     2   20050 use strict;
  2         5  
  2         92  
4 2     2   1881 use open qw(:std :utf8);
  2         3091  
  2         13  
5 2     2   263 use Carp;
  2         8  
  2         151  
6 2     2   3701 use overload;
  2         2623  
  2         13  
7 2     2   1344 use Regexp::Extended::Match;
  2         5  
  2         108  
8 2     2   1294 use Regexp::Extended::MatchGroup;
  2         13  
  2         56  
9 2     2   2142 use Data::Dumper;
  2         16468  
  2         177  
10 2     2   15 use re 'eval';
  2         4  
  2         86  
11              
12 2     2   13 use vars qw(@VARS @MATCH_ARRAY $RXV $RXT $DEBUG $VERSION %EXPORT_TAGS @ISA @EXPORT @EXPORT_OK);
  2         3  
  2         7289  
13              
14             require Exporter;
15              
16             @ISA = qw(Exporter);
17             @EXPORT_OK = qw(rxt rebuild upto uptoAndIncluding);
18             %EXPORT_TAGS = (
19             "all" => \@EXPORT_OK,
20             );
21             $VERSION = '0.01';
22             $DEBUG = 0;
23              
24             @VARS = ();
25             @MATCH_ARRAY = ();
26             $RXV = {};
27             $RXT = [];
28              
29             # additional operators that are used in (?$op) constructs
30             my $ops = {
31             qr/\*/ => { head => '(??{Regexp::Extended::upto(\'', tail => '\')})', middle => \&escapeSlash },
32             qr/\+/ => { head => '(??{Regexp::Extended::uptoAndIncluding(\'', tail => '\')})', middle => \&escapeSlash },
33             qr/\&/ => { head => '(??{', tail => '})' },
34             qr/<([^>]+)>/ => { head => '(?:(', tail => ')(?{ local $n = $n + 1; $Regexp::Extended::MATCH_ARRAY[$n - 1] = new Regexp::Extended::Match("$1", $^N, pos()) }))' },
35             };
36              
37             my $const = {
38             qr/\\A/ => '(?{ $n = 0; })',
39             qr/\\Z/ => '(?{ splice(@Regexp::Extended::MATCH_ARRAY, $n); Regexp::Extended::analyse(); })',
40             };
41              
42             # Matches an even number of \'s
43             my $evenSlashes = qr/
44             (?
45             (?>
46             (?:\\\\)*
47             )
48             (?!\\)
49             /x;
50              
51             # Matches a complete group: (1,2,3) or (1, (2,3)) but not 1,2,(3)
52             our $parenGrp = qr/
53             \(
54             (?:
55             (?> [^()\\]+ )
56             |
57             \\.
58             |
59             (??{ $parenGrp })
60             )*
61             \)
62             /x;
63              
64             our $mixedParenGrp = qr/
65             (?>
66             (?:
67             (?>
68             (?:
69             [^()\\]*
70             (?:\\.)*
71             )*
72             )
73             (?:$parenGrp)?
74             )*
75             )
76             /x;
77              
78             my $currentLevel = 0;
79             my $currentOp = 0;
80             my @currentParams = ();
81              
82             sub escapeSlash {
83             my ($string) = @_;
84              
85             $string =~ s/\//\\\//g;
86             return $string;
87             }
88              
89             # Go upto the supplied pattern
90             sub upto {
91             my ($pattern) = @_;
92              
93             return qr/(?>(?:(?!$pattern).)*(?=$pattern))/;
94             }
95              
96             # Go upto and including the supplied pattern
97             sub uptoAndIncluding {
98             my ($pattern) = @_;
99              
100             return qr/(?>(?:(?!$pattern).)*$pattern)/;
101             }
102              
103             sub unbalancedLevel {
104             my ($string) = @_;
105             my $left = $string =~ y/\(//;
106             my $right = $string =~ y/\)//;
107            
108             return $left - $right;
109             }
110              
111             sub fillNumericalParams {
112             my ($replaceStr) = @_;
113              
114             $replaceStr =~ s/\$(\d+)/$currentParams[$1]/g;
115              
116             return $replaceStr;
117             }
118              
119             sub evaluateNumericalParams {
120             my ($origStr, $replaceStr) = @_;
121             my $nbParams = scalar @+ - 1;
122            
123             @currentParams = map(rg($origStr, $_), 1..$nbParams);
124              
125             $replaceStr =~ s/\$(\d+)/$currentParams[$1]/g;
126            
127             return $replaceStr;
128             }
129              
130             sub rg {
131             my ($origStr, $param) = @_;
132             my $start = $-[$param];
133             my $length = $+[$param] - $start;
134              
135             return substr($origStr, $start, $length);
136             }
137              
138             sub var {
139             my ($pattern) = @_;
140              
141             return qr/(?{ $n = 0; })$pattern(?{ splice(@Regexp::Extended::MATCH_ARRAY, $n); Regexp::Extended::analyse(); })/;
142             }
143              
144             sub rxt {
145             my ($pattern) = @_;
146            
147             foreach my $op (keys %{$ops}) {
148             my $head = $ops->{$op}->{'head'};
149             my $tail = $ops->{$op}->{'tail'};
150              
151             $pattern =~ s/
152             ($evenSlashes)
153             \(\?$op
154             ($mixedParenGrp)
155             \)
156             /evaluateNumericalParams($pattern, rg($pattern, 1) . $head . rg($pattern, -1) . $tail)/gex;
157             }
158              
159             return $pattern;
160             }
161              
162             sub analyse {
163             $RXV = {};
164             $RXT = [];
165              
166             foreach my $m (@MATCH_ARRAY) {
167             my $len = length($m->{'value'});
168             my $start = $m->{'end'} - $len;
169             $m->{'length'} = $len;
170             $m->{'start'} = $start;
171              
172             for(my $i = 0; $i < scalar @{$RXT}; $i++) {
173             my $match = $RXT->[$i];
174            
175             if ($m->{'start'} <= $match->{'start'}) {
176             my @group = splice(@{$RXT}, $i);
177             $m->{'childs'} = \@group;
178             last;
179             }
180             }
181              
182             push @{$RXT}, $m;
183              
184             my $name = $m->{'name'};
185             if (not exists $RXV->{$name}) {
186             $RXV->{$name} = new Regexp::Extended::MatchGroup(undef, $name);
187             eval("\$::$name = \$RXV->{$name}");
188             }
189              
190             push @{$RXV->{$name}}, $m;
191             }
192             }
193              
194             sub rebuildFromTree {
195             my ($string, $tree, $last_index, $result) = @_;
196              
197             foreach my $match (@{$tree}) {
198             if (defined $match->{'childs'}) {
199             if ($match->{'dirty'}) {
200             push @{$result}, substr($string, $last_index, $match->{'start'} - $last_index);
201             push @{$result}, $match->{'value'};
202             $last_index = $match->{'end'};
203             }
204             else {
205             $last_index = rebuildFromTree($string, $match->{'childs'}, $last_index, $result);
206             }
207             }
208             else {
209             push @{$result}, substr($string, $last_index, $match->{'start'} - $last_index);
210             push @{$result}, $match->{'value'};
211             $last_index = $match->{'end'};
212             }
213             }
214              
215             return $last_index;
216             }
217              
218             sub rebuild {
219             my ($string) = @_;
220             my @result = ();
221             my $last_index = rebuildFromTree($string, $RXT, 0, \@result);
222             push @result, substr($string, $last_index);
223             return join('', @result);
224             }
225              
226             sub import {
227             overload::constant(
228             qr => sub {
229             my ($orig, $interp, $context) = @_;
230            
231             print STDERR "input : $interp, orig: $orig\n" if $DEBUG;
232              
233             # Search for constants
234             foreach my $c (keys %{$const}) {
235             $interp =~ s/$c/$const->{$c}/g;
236             }
237            
238             # If we are in a partial match, check if the group can be closed.
239             if ($currentLevel != 0) {
240             my $l = $currentLevel - 1;
241             my $tail = $ops->{$currentOp}->{'tail'};
242             #my $func = exists $ops->{$currentOp}->{'middle'} ? $ops->{$currentOp}->{'middle'} : sub { return $_[0] };
243              
244             if ($interp =~ s/^((?:$mixedParenGrp\)){$l}$mixedParenGrp)\)/fillNumericalParams("$1$tail")/e) {
245             $currentLevel = 0;
246             }
247             else {
248             $currentLevel += unbalancedLevel($interp);
249             }
250             }
251              
252             if ($currentLevel == 0) {
253             # Search for complete groups (?op...)
254             foreach my $op (keys %{$ops}) {
255             my $head = $ops->{$op}->{'head'};
256             my $tail = $ops->{$op}->{'tail'};
257             #my $func = exists $ops->{$op}->{'middle'} ? $ops->{$op}->{'middle'} : sub { return $_[0] };
258              
259             $interp =~ s/
260             ($evenSlashes)
261             \(\?$op
262             ($mixedParenGrp)
263             \)
264             /evaluateNumericalParams($interp, rg($interp, 1) . $head . rg($interp, -1) . $tail)/gex;
265             }
266              
267             # Search for one and *only one* incomplete group (?op...
268             foreach my $op (keys %{$ops}) {
269             my $head = $ops->{$op}->{'head'};
270             my $tail = $ops->{$op}->{'tail'};
271             #my $func = exists $ops->{$op}->{'middle'} ? $ops->{$op}->{'middle'} : sub { return $_[0] };
272            
273             if ($interp =~ s/
274             ($evenSlashes)
275             \(\?$op
276             (.*)
277             /evaluateNumericalParams($interp, rg($interp, 1) . $head . rg($interp, -1))/gex) {
278             $currentLevel = unbalancedLevel($2) + 1; # How many ('s need to be closed
279             $currentOp = $op; # Which operator is incomplete
280             last;
281             }
282             }
283             }
284            
285             print STDERR "result: $interp\n" if $DEBUG;
286             return $interp;
287             },
288             );
289              
290             Regexp::Extended->export_to_level(1, @_);
291             }
292              
293             1;
294              
295             __END__