File Coverage

blib/lib/Salvation/TC/Parser/PP.pm
Criterion Covered Total %
statement 139 143 97.2
branch 81 96 84.3
condition 13 25 52.0
subroutine 6 6 100.0
pod 3 3 100.0
total 242 273 88.6


line stmt bran cond sub pod time code
1             package Salvation::TC::Parser::PP;
2              
3 4     4   1666 use strict;
  4         5  
  4         111  
4 4     4   14 use warnings;
  4         5  
  4         79  
5 4     4   16 use boolean;
  4         6  
  4         24  
6              
7             our $VERSION = 0.10;
8              
9             =head2 tokenize_type_str_impl( Str str, Maybe[HashRef( Bool :loose )] options? )
10              
11             Разбирает строку с именем типа C и возвращает ArrayRef[HashRef]
12             с найденными токенами.
13              
14             Краткое описание опций в C:
15              
16             =over
17              
18             =item loose
19              
20             Не выполнять дополнительных проверок возможности токена на основе внешних данных.
21              
22             =back
23              
24             =cut
25              
26             sub tokenize_type_str_impl {
27              
28 256     256 1 251 my ( $class, $str, $options ) = @_;
29              
30 256   50     412 $options //= {};
31              
32 256         424 $str =~ s/\s+/ /gs;
33 256         363 $str =~ s/^\s+//;
34 256         313 $str =~ s/\s+$//;
35              
36 256         948 my @chars = split( //, $str );
37 256         333 my @stack = ();
38 256         192 my $word = '';
39 256         175 my $parameterizable_type = '';
40              
41 256         448 while( defined( my $char = shift( @chars ) ) ) {
42              
43 2000 100       2132 next if( $char eq ' ' );
44              
45 1999 100 100     2597 if( ( $char eq '[' ) && ( $word ne 'Maybe' ) ) {
46              
47 51 50       75 if( $options -> { 'loose' } ) {
48              
49 0         0 $parameterizable_type = $word;
50              
51             } else {
52              
53 51         119 $parameterizable_type = $class -> load_parameterizable_type_class( $word );
54              
55 51 50       83 die( "Can't parameterize type ${word}" ) if( $parameterizable_type eq '' );
56             }
57             }
58              
59 1999 100       3560 if( $char eq '|' ) {
    100          
    100          
    100          
60              
61 10 100       15 if( $word eq '' ) {
62              
63 7 0 33     41 unless(
      33        
      33        
64             exists $stack[ $#stack ] -> { 'maybe' }
65             || exists $stack[ $#stack ] -> { 'class' }
66             || exists $stack[ $#stack ] -> { 'signed' }
67             || exists $stack[ $#stack ] -> { 'length' }
68             ) {
69              
70 0         0 die( 'Invalid type string' );
71             }
72              
73             } else {
74              
75 3         6 push( @stack, { type => $word } );
76              
77 3         5 $word = '';
78             }
79              
80             } elsif( $char eq '[' ) {
81              
82 53         38 my $cnt = 1;
83 53         46 my $substr = '';
84              
85 53         88 while( defined( my $subchar = shift( @chars ) ) ) {
86              
87 796 100       825 ++$cnt if( $subchar eq '[' );
88 796 100       821 --$cnt if( $subchar eq ']' );
89              
90 796 100       805 last if( $cnt == 0 );
91              
92 743         917 $substr .= $subchar;
93             }
94              
95 53 50 33     154 die( 'Invalid type parameterization' ) if( ( $substr eq '' ) || ( $word eq '' ) );
96              
97 53 100       58 if( $parameterizable_type eq '' ) {
98              
99 2         7 push( @stack, { maybe => tokenize_type_str_impl( $class, $substr, $options ) } );
100              
101             } else {
102              
103 51         97 push( @stack, {
104             class => $parameterizable_type,
105             param => tokenize_type_str_impl( $class, $substr, $options ),
106             base => tokenize_type_str_impl( $class, $word, $options ),
107             } );
108              
109 51         53 $parameterizable_type = '';
110             }
111              
112 53         84 $word = '';
113              
114             } elsif( $char eq '(' ) {
115              
116 12 100       17 if( $word eq '' ) {
117              
118 4 50       14 unless( exists $stack[ $#stack ] -> { 'class' } ) {
119              
120 0         0 die( 'Invalid type description' );
121             }
122              
123             } else {
124              
125 8         15 push( @stack, { type => $word } );
126 8         8 $word = '';
127             }
128              
129 12         13 my $cnt = 1;
130 12         11 my $substr = $char;
131              
132 12         20 while( defined( my $subchar = shift( @chars ) ) ) {
133              
134 239 100       282 ++$cnt if( $subchar eq '(' );
135 239 100       246 --$cnt if( $subchar eq ')' );
136              
137 239         130 $substr .= $subchar;
138              
139 239 100       421 last if( $cnt == 0 );
140             }
141              
142 12 50       22 die( 'Invalid signature' ) if( $substr eq '' );
143              
144 12         36 push( @stack, {
145             signed => {
146             type => pop( @stack ),
147             signature => tokenize_signature_str_impl( $class, $substr, $options ),
148             source => $substr,
149             }
150             } );
151              
152             } elsif( $char eq '{' ) {
153              
154 28 100       41 if( $word ne '' ) {
155              
156 23         47 push( @stack, { type => $word } );
157 23         24 $word = '';
158             }
159              
160 28         24 my $substr = '';
161              
162 28         46 while( defined( my $subchar = shift( @chars ) ) ) {
163              
164 81 100       101 last if( $subchar eq '}' );
165              
166 53         78 $substr .= $subchar;
167             }
168              
169 28         35 $substr =~ s/\s+//g;
170              
171 28         28 my ( $min, $max ) = ( undef, undef );
172              
173 28 100       120 if( $substr =~ m/^(0|[1-9][0-9]*),(0|[1-9][0-9]*)$/ ) {
    100          
    50          
174              
175 6         16 ( $min, $max ) = ( $1, $2 );
176              
177             } elsif( $substr =~ m/^(0|[1-9][0-9]*),$/ ) {
178              
179 7         10 $min = $1;
180              
181             } elsif( $substr =~ m/^(0|[1-9][0-9]*)$/ ) {
182              
183 15         36 ( $min, $max ) = ( $1 )x2;
184              
185             } else {
186              
187 0         0 die( 'Invalid length limits' );
188             }
189              
190 28         124 push( @stack, {
191             length => {
192             type => pop( @stack ),
193             min => $min,
194             max => $max,
195             }
196             } );
197              
198             } else {
199              
200 1896         2799 $word .= $char;
201             }
202             }
203              
204 256 100       515 push( @stack, { type => $word } ) if( $word ne '' );
205              
206 256         679 return \@stack;
207             }
208              
209             =head2 tokenize_signature_str_impl( Str str, Maybe[HashRef] options? )
210              
211             Разбирает строку с подписью C и возвращает ArrayRef[HashRef]
212             с найденными токенами.
213              
214             Набор опций в C и их значений эквивалентен оному для C.
215              
216             =cut
217              
218             sub tokenize_signature_str_impl {
219              
220 12     12 1 14 my ( $class, $str, $options ) = @_;
221              
222 12   50     20 $options //= {};
223              
224 12         40 $str =~ s/\s+/ /gs;
225              
226 12 50       69 die( "Invalid signature: ${str}" ) if( $str !~ m/^\s*\(\s*(.+?)\s*\)\s*$/ );
227              
228 12         105 $str = $1;
229              
230 12         50 my @chars = split( //, $str );
231 12         18 my @stack = ();
232 12         15 my @seq = ( 'type', 'name', 'delimiter' );
233 12         14 my %word = ();
234              
235 12         24 my %opened_parens = (
236             '{' => 0,
237             '(' => 0,
238             '[' => 0,
239             );
240              
241 12         23 my %closed_parens = (
242             '}' => '{',
243             ')' => '(',
244             ']' => '[',
245             );
246              
247 12         32 my $delimiter_re = qr/^[\s,]$/;
248              
249 12         27 while( defined( my $item_type = shift( @seq ) ) ) {
250              
251 45         32 my $word = '';
252              
253 45 100       78 if( $item_type eq 'type' ) {
    100          
    50          
254              
255 15         24 while( defined( my $char = shift( @chars ) ) ) {
256              
257 153 100       165 ++$opened_parens{ $char } if( exists $opened_parens{ $char } );
258 153 100       226 --$opened_parens{ $closed_parens{ $char } } if( exists $closed_parens{ $char } );
259              
260 153 100       314 if( $char =~ $delimiter_re ) {
261              
262 33         52 my $word_end = true;
263              
264 33         97 while( my ( $key, $value ) = each( %opened_parens ) ) {
265              
266 99 100 100     538 $word_end = false if( $word_end && $value != 0 );
267             }
268              
269 33 100       174 if( $word_end ) {
270              
271 15         70 while( defined( my $subchar = shift( @chars ) ) ) {
272              
273 15 50       38 if( $subchar !~ $delimiter_re ) {
274              
275 15         16 unshift( @chars, $subchar );
276 15         15 last;
277             }
278             }
279              
280 15         17 last;
281             }
282             }
283              
284 138         253 $word .= $char;
285             }
286              
287 15 50       25 die( 'Invalid type string' ) if( $word eq '' );
288              
289             } elsif( $item_type eq 'name' ) {
290              
291 15         24 while( defined( my $char = shift( @chars ) ) ) {
292              
293 63 100       110 if( $char =~ $delimiter_re ) {
294              
295 3         6 while( defined( my $subchar = shift( @chars ) ) ) {
296              
297 6 100       15 if( $subchar !~ $delimiter_re ) {
298              
299 3         4 unshift( @chars, $subchar );
300 3         4 last;
301             }
302             }
303              
304 3         3 last;
305             }
306              
307 60         92 $word .= $char;
308             }
309              
310 15 50       30 die( 'Invalid parameter name' ) if( $word eq '' );
311              
312             } elsif( $item_type eq 'delimiter' ) {
313              
314 15         30 my ( $type, $name ) = delete @word{ 'type', 'name' };
315              
316 15 50 33     58 die( 'Invalid signature' ) if( ( $type eq '' ) || ( $name eq '' ) );
317              
318 15         36 $type = tokenize_type_str_impl( $class, $type, $options );
319 15         21 $name = tokenize_signature_parameter_str( $name );
320              
321 15         31 push( @stack, {
322             type => $type,
323             param => $name,
324             } );
325              
326 15         16 push( @seq, $item_type );
327              
328 15 100       28 last if( scalar( @chars ) == 0 );
329 3         7 next;
330             }
331              
332 30         33 $word{ $item_type } = $word;
333              
334 30         55 push( @seq, $item_type );
335             }
336              
337 12         76 return \@stack;
338             }
339              
340             =head2 tokenize_signature_parameter_str( Str $str )
341              
342             Разбирает строку с именем параметра C<$str> и возвращает HashRef, представляющий
343             токен.
344              
345             =cut
346              
347             sub tokenize_signature_parameter_str {
348              
349 15     15 1 16 my ( $str ) = @_;
350              
351 15         17 my %out = ();
352 15         21 my $first_char = substr( $str, 0, 1 );
353              
354 15 100       21 if( $first_char eq ':' ) {
355              
356 9         16 $out{ 'named' } = true;
357 9         24 $str = substr( $str, 1 );
358              
359             } else {
360              
361 6         12 $out{ 'positional' } = true;
362             }
363              
364 15         21 my $last_char = substr( $str, -1, 1 );
365              
366 15 100       37 if( $last_char eq '!' ) {
    100          
    100          
    50          
367              
368 8         11 $out{ 'required' } = true;
369 8         26 $str = substr( $str, 0, -1 );
370              
371             } elsif( $last_char eq '?' ) {
372              
373 1         2 $out{ 'optional' } = true;
374 1         3 $str = substr( $str, 0, -1 );
375              
376             } elsif( $out{ 'positional' } ) {
377              
378 5         24 $out{ 'required' } = true;
379              
380             } elsif( $out{ 'named' } ) {
381              
382 1         8 $out{ 'optional' } = true;
383             }
384              
385 15         22 $out{ 'name' } = $str;
386              
387 15         21 return \%out;
388             }
389              
390             1;
391              
392             __END__