File Coverage

blib/lib/PPIx/Regexp/Structure.pm
Criterion Covered Total %
statement 128 151 84.7
branch 45 70 64.2
condition 23 33 69.7
subroutine 17 18 94.4
pod 8 8 100.0
total 221 280 78.9


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             PPIx::Regexp::Structure - Represent a structure.
4              
5             =head1 SYNOPSIS
6              
7             use PPIx::Regexp::Dumper;
8             PPIx::Regexp::Dumper->new( 'qr{(foo)}' )->print();
9              
10             =head1 INHERITANCE
11              
12             C is a
13             L.
14              
15             C is the parent of
16             L,
17             L,
18             L,
19             L,
20             L,
21             L,
22             L,
23             L,
24             L,
25             L,
26             L and
27             L.
28              
29             =head1 DESCRIPTION
30              
31             This class represents a bracketed construction of some sort. The
32             brackets are considered part of the structure, but not inside it. So the
33             C method returns the brackets if they are defined, but the
34             C method does not.
35              
36             =head1 METHODS
37              
38             This class provides the following public methods. Methods not documented
39             here are private, and unsupported in the sense that the author reserves
40             the right to change or remove them without notice.
41              
42             =cut
43              
44             package PPIx::Regexp::Structure;
45              
46 9     9   58 use strict;
  9         26  
  9         251  
47 9     9   46 use warnings;
  9         19  
  9         236  
48              
49 9     9   55 use base qw{ PPIx::Regexp::Node };
  9         17  
  9         708  
50              
51 9     9   63 use Carp qw{ confess };
  9         16  
  9         459  
52 9         878 use PPIx::Regexp::Constant qw{
53             ARRAY_REF
54             HASH_REF
55             STRUCTURE_UNKNOWN
56             @CARP_NOT
57 9     9   52 };
  9         29  
58 9     9   66 use PPIx::Regexp::Util qw{ __instance };
  9         28  
  9         424  
59 9     9   58 use Scalar::Util qw{ refaddr };
  9         24  
  9         643  
60              
61             our $VERSION = '0.088';
62              
63 9     9   61 use constant ELEMENT_UNKNOWN => STRUCTURE_UNKNOWN;
  9         17  
  9         14376  
64              
65             sub __new {
66 616     616   1883 my ( $class, @args ) = @_;
67 616         1192 my %brkt;
68 616 100       1796 if ( HASH_REF eq ref $args[0] ) {
69 38         85 %brkt = %{ shift @args };
  38         185  
70 38         137 foreach my $key ( qw{ start type finish } ) {
71             ARRAY_REF eq ref $brkt{$key}
72 114 50       448 or $brkt{$key} = [ $brkt{$key} ];
73             }
74             } else {
75 578 100       2211 $brkt{finish} = [ @args ? pop @args : () ];
76 578 100       1955 $brkt{start} = [ @args ? shift @args : () ];
77 578   100     2953 while ( @args && ! $args[0]->significant() ) {
78 28         85 push @{ $brkt{start} }, shift @args;
  28         145  
79             }
80 578         1391 $brkt{type} = [];
81 578 100       1813 if ( __instance( $args[0], 'PPIx::Regexp::Token::GroupType' ) ) {
82 110         252 push @{ $brkt{type} }, shift @args;
  110         313  
83 110   100     542 while ( @args && ! $args[0]->significant() ) {
84 1         2 push @{ $brkt{type} }, shift @args;
  1         5  
85             }
86             }
87             }
88              
89 616         3115 $class->_check_for_interpolated_match( \%brkt, \@args );
90              
91 616 50       3196 my $self = $class->SUPER::__new( @args )
92             or return;
93              
94 616 100       2114 if ( __instance( $brkt{type}[0], 'PPIx::Regexp::Token::GroupType' ) ) {
95 114         846 ( my $reclass = ref $brkt{type}[0] ) =~
96             s/ Token::GroupType /Structure/smx;
97 114 50       1420 $reclass->can( 'start' )
98             or confess "Programming error - $reclass not loaded";
99 114         289 bless $self, $reclass;
100             }
101              
102 616         1901 foreach my $key ( qw{ start type finish } ) {
103 1848         4311 $self->{$key} = [];
104 1848 50       4680 ARRAY_REF eq ref $brkt{$key}
105             or confess "Programming error - '$brkt{$key}' not an ARRAY";
106 1848         2588 foreach my $val ( @{ $brkt{$key} } ) {
  1848         3793  
107 1419 100       2740 defined $val or next;
108 1363 50       2991 __instance( $val, 'PPIx::Regexp::Element' )
109             or confess "Programming error - '$val' not a ",
110             "PPIx::Regexp::Element";
111 1363         2395 push @{ $self->{$key} }, $val;
  1363         2975  
112 1363         3745 $val->_parent( $self );
113             }
114             }
115              
116 616         1799 @{ $self->{finish} }
117 616 100       1105 or $self->{error} = 'Missing end delimiter';
118              
119 616         3297 return $self;
120             }
121              
122             =head2 elements
123              
124             This override returns all components of the structure, including those
125             that define it.
126              
127             =cut
128              
129             sub elements {
130 2943     2943 1 4901 my ( $self ) = @_;
131              
132 2943 100       5733 if ( wantarray ) {
    50          
133             return (
134 2942         5381 @{ $self->{start} },
135 2942         4722 @{ $self->{type} },
136 2942         4352 @{ $self->{children} },
137 2942         4103 @{ $self->{finish} },
  2942         8561  
138             );
139             } elsif ( defined wantarray ) {
140 1         3 my $size = scalar @{ $self->{start} };
  1         4  
141 1         3 $size += scalar @{ $self->{type} };
  1         3  
142 1         2 $size += scalar @{ $self->{children} };
  1         2  
143 1         3 $size += scalar @{ $self->{finish} };
  1         2  
144 1         3 return $size;
145             } else {
146 0         0 return;
147             }
148             }
149              
150             {
151             my %explanation = (
152             q<(> => 'Grouping', # )
153             );
154              
155             sub explain {
156 1     1 1 7 my ( $self ) = @_;
157 1 50       3 if ( my $type = $self->type() ) {
158 0         0 return $type->explain();
159             }
160 1 50       3 if ( my $start = $self->start() ) {
161             # The check for a left parenthesis before returning
162             # 'Grouping' is probably superflous, since it appears that
163             # this method is overridden in all other cases where we
164             # might get here (i.e. '[...]', '{...}'). But I'm paranoid.
165 1   33     4 return $explanation{ $start->content() } || $start->explain();
166             }
167 0         0 return $self->__no_explanation();
168             }
169             }
170              
171             =head2 finish
172              
173             my $elem = $struct->finish();
174             my @elem = $struct->finish();
175             my $elem = $struct->finish( 0 );
176              
177             Returns the finishing structure element. This is included in the
178             C but not in the C.
179              
180             The finishing element is actually an array, though it should never have
181             more than one element. Calling C in list context gets you all
182             elements of the array. Calling it in scalar context gets you an element
183             of the array, defaulting to element 0 if no argument is passed.
184              
185             =cut
186              
187             sub finish {
188 706     706 1 1786 my ( $self, $inx ) = @_;
189 706 100       2022 wantarray and return @{ $self->{finish} };
  191         902  
190 515 100       2680 return $self->{finish}[ defined $inx ? $inx : 0 ];
191             }
192              
193             sub first_element {
194 3     3 1 10 my ( $self ) = @_;
195              
196 3 50       18 $self->{start}[0] and return $self->{start}[0];
197              
198 0 0       0 $self->{type}[0] and return $self->{type}[0];
199              
200 0 0       0 if ( my $elem = $self->SUPER::first_element() ) {
201 0         0 return $elem;
202             }
203              
204 0 0       0 $self->{finish}[0] and return $self->{finish}[0];
205              
206 0         0 return;
207             }
208              
209             sub last_element {
210 45     45 1 92 my ( $self ) = @_;
211              
212 45 50       193 $self->{finish}[-1] and return $self->{finish}[-1];
213              
214 0 0       0 if ( my $elem = $self->SUPER::last_element() ) {
215 0         0 return $elem;
216             }
217              
218 0 0       0 $self->{type}[-1] and return $self->{type}[-1];
219              
220 0 0       0 $self->{start}[-1] and return $self->{start}[-1];
221              
222 0         0 return;
223             }
224              
225             sub remove_insignificant {
226 0     0 1 0 my ( $self ) = @_;
227             return $self->__new(
228 0         0 map { $_->remove_insignificant() } $self->elements() );
  0         0  
229             }
230              
231             =head2 start
232              
233             my $elem = $struct->start();
234             my @elem = $struct->start();
235             my $elem = $struct->start( 0 );
236              
237             Returns the starting structure element. This is included in the
238             C but not in the C.
239              
240             The starting element is actually an array. The first element (element 0)
241             is the actual starting delimiter. Subsequent elements, if any, are
242             insignificant elements (comments or white space) absorbed into the start
243             element for ease of parsing subsequent elements.
244              
245             Calling C in list context gets you all elements of the array.
246             Calling it in scalar context gets you an element of the array,
247             defaulting to element 0 if no argument is passed.
248              
249             =cut
250              
251             sub start {
252 733     733 1 1823 my ( $self, $inx ) = @_;
253 733 100       2061 wantarray and return @{ $self->{start} };
  218         1082  
254 515 100       2769 return $self->{start}[ defined $inx ? $inx : 0 ];
255             }
256              
257             =head2 type
258              
259             my $elem = $struct->type();
260             my @elem = $struct->type();
261             my $elem = $struct->type( 0 );
262              
263             Returns the group type if any. This will be the leading
264             L
265             token if any. This is included in C but not in C.
266              
267             The type is actually an array. The first element (element 0) is the
268             actual type determiner. Subsequent elements, if any, are insignificant
269             elements (comments or white space) absorbed into the type element for
270             consistency with the way the start element is handled.
271              
272             Calling C in list context gets you all elements of the array.
273             Calling it in scalar context gets you an element of the array,
274             defaulting to element 0 if no argument is passed.
275              
276             =cut
277              
278             sub type {
279 387     387 1 1237 my ( $self, $inx ) = @_;
280 387 100       1192 wantarray and return @{ $self->{type} };
  194         888  
281 193 100       1311 return $self->{type}[ defined $inx ? $inx : 0 ];
282             }
283              
284             # Check for things like (?$foo:...) or (?$foo)
285             sub _check_for_interpolated_match {
286 616     616   1401 my ( undef, $brkt, $args ) = @_; # Invocant unused
287              
288             # Everything we are interested in begins with a literal '?' followed
289             # by an interpolation.
290 616 100 100     1676 __instance( $args->[0], 'PPIx::Regexp::Token::Unknown' )
      66        
291             and $args->[0]->content() eq '?'
292             and __instance( $args->[1], 'PPIx::Regexp::Token::Interpolation' )
293             or return;
294              
295 4         12 my $hiwater = 2; # Record how far we got into the arguments for
296             # subsequent use detecting things like
297             # (?$foo).
298              
299             # If we have a literal ':' as the third argument:
300             # GroupType::Modifier, rebless the ':' so we know not to match
301             # against it, and splice all three tokens into the type.
302 4 100 100     14 if ( __instance( $args->[2], 'PPIx::Regexp::Token::Literal' )
303             && $args->[2]->content() eq ':' ) {
304              
305             # Rebless the '?' as a GroupType::Modifier.
306 1         16 PPIx::Regexp::Token::GroupType::Modifier->__PPIX_ELEM__rebless(
307             $args->[0] );
308              
309             # Rebless the ':' as a GroupType, just so it does not look like
310             # something to match against.
311 1         7 PPIx::Regexp::Token::GroupType->__PPIX_ELEM__rebless(
312             $args->[2] );
313              
314             # Shove our three significant tokens into the type.
315 1         2 push @{ $brkt->{type} }, splice @{ $args }, 0, 3;
  1         3  
  1         4  
316              
317             # Stuff all the immediately-following insignificant tokens into
318             # the type as well.
319 1   33     2 while ( @{ $args } && ! $args->[0]->significant() ) {
  1         9  
320 0         0 push @{ $brkt->{type} }, shift @{ $args };
  0         0  
  0         0  
321             }
322              
323             # Return to the caller, since we have done all the damage we
324             # can.
325 1         6 return;
326             }
327              
328             # If we have a literal '-' as the third argument, we might have
329             # something like (?$on-$off:$foo).
330 3 50 66     16 if ( __instance( $args->[2], 'PPIx::Regexp::Token::Literal' )
      66        
331             && $args->[2]->content() eq '-'
332             && __instance( $args->[3], 'PPIx::Regexp::Token::Interpolation' )
333             ) {
334 2         4 $hiwater = 4;
335              
336 2 100 66     7 if ( __instance( $args->[4], 'PPIx::Regexp::Token::Literal' )
337             && $args->[4]->content() eq ':' ) {
338              
339             # Rebless the '?' as a GroupType::Modifier.
340 1         8 PPIx::Regexp::Token::GroupType::Modifier->__PPIX_ELEM__rebless(
341             $args->[0] );
342              
343             # Rebless the '-' and ':' as GroupType, just so they do not
344             # look like something to match against.
345 1         4 PPIx::Regexp::Token::GroupType->__PPIX_ELEM__rebless(
346             $args->[2] );
347 1         5 PPIx::Regexp::Token::GroupType->__PPIX_ELEM__rebless(
348             $args->[4] );
349              
350             # Shove our five significant tokens into the type.
351 1         2 push @{ $brkt->{type} }, splice @{ $args }, 0, 5;
  1         5  
  1         4  
352              
353             # Stuff all the immediately-following insignificant tokens
354             # into the type as well.
355 1   33     4 while ( @{ $args } && ! $args->[0]->significant() ) {
  1         5  
356 0         0 push @{ $brkt->{type} }, shift @{ $args };
  0         0  
  0         0  
357             }
358              
359             # Return to the caller, since we have done all the damage we
360             # can.
361 1         2 return;
362             }
363             }
364              
365             # If the group contains _any_ significant tokens at this point, we
366             # do _not_ have something like (?$foo).
367 2         11 foreach my $inx ( $hiwater .. $#$args ) {
368 0 0       0 $args->[$inx]->significant() and return;
369             }
370              
371             # Rebless the '?' as a GroupType::Modifier.
372             PPIx::Regexp::Token::GroupType::Modifier->__PPIX_ELEM__rebless(
373 2         25 $args->[0] );
374              
375             # Shove all the contents of $args into type, using splice to leave
376             # @{ $args } empty after we do this.
377 2         3 push @{ $brkt->{type} }, splice @{ $args };
  2         8  
  2         6  
378              
379             # We have done all the damage we can.
380 2         5 return;
381             }
382              
383             1;
384              
385             __END__