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   1468 use strict;
  4         7  
  4         111  
4 4     4   13 use warnings;
  4         5  
  4         70  
5 4     4   14 use boolean;
  4         5  
  4         20  
6              
7             our $VERSION = 0.06;
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 283 my ( $class, $str, $options ) = @_;
29              
30 256   50     386 $options //= {};
31              
32 256         417 $str =~ s/\s+/ /gs;
33 256         369 $str =~ s/^\s+//;
34 256         312 $str =~ s/\s+$//;
35              
36 256         937 my @chars = split( //, $str );
37 256         278 my @stack = ();
38 256         225 my $word = '';
39 256         183 my $parameterizable_type = '';
40              
41 256         428 while( defined( my $char = shift( @chars ) ) ) {
42              
43 2000 100       2164 next if( $char eq ' ' );
44              
45 1999 100 100     2604 if( ( $char eq '[' ) && ( $word ne 'Maybe' ) ) {
46              
47 51 50       81 if( $options -> { 'loose' } ) {
48              
49 0         0 $parameterizable_type = $word;
50              
51             } else {
52              
53 51         140 $parameterizable_type = $class -> load_parameterizable_type_class( $word );
54              
55 51 50       94 die( "Can't parameterize type ${word}" ) if( $parameterizable_type eq '' );
56             }
57             }
58              
59 1999 100       3360 if( $char eq '|' ) {
    100          
    100          
    100          
60              
61 10 100       15 if( $word eq '' ) {
62              
63 7 0 33     50 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         7 push( @stack, { type => $word } );
76              
77 3         8 $word = '';
78             }
79              
80             } elsif( $char eq '[' ) {
81              
82 53         47 my $cnt = 1;
83 53         52 my $substr = '';
84              
85 53         98 while( defined( my $subchar = shift( @chars ) ) ) {
86              
87 796 100       934 ++$cnt if( $subchar eq '[' );
88 796 100       829 --$cnt if( $subchar eq ']' );
89              
90 796 100       862 last if( $cnt == 0 );
91              
92 743         1006 $substr .= $subchar;
93             }
94              
95 53 50 33     168 die( 'Invalid type parameterization' ) if( ( $substr eq '' ) || ( $word eq '' ) );
96              
97 53 100       78 if( $parameterizable_type eq '' ) {
98              
99 2         6 push( @stack, { maybe => tokenize_type_str_impl( $class, $substr, $options ) } );
100              
101             } else {
102              
103 51         119 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         50 $parameterizable_type = '';
110             }
111              
112 53         97 $word = '';
113              
114             } elsif( $char eq '(' ) {
115              
116 12 100       16 if( $word eq '' ) {
117              
118 4 50       12 unless( exists $stack[ $#stack ] -> { 'class' } ) {
119              
120 0         0 die( 'Invalid type description' );
121             }
122              
123             } else {
124              
125 8         18 push( @stack, { type => $word } );
126 8         9 $word = '';
127             }
128              
129 12         11 my $cnt = 1;
130 12         11 my $substr = $char;
131              
132 12         26 while( defined( my $subchar = shift( @chars ) ) ) {
133              
134 239 100       261 ++$cnt if( $subchar eq '(' );
135 239 100       273 --$cnt if( $subchar eq ')' );
136              
137 239         141 $substr .= $subchar;
138              
139 239 100       436 last if( $cnt == 0 );
140             }
141              
142 12 50       23 die( 'Invalid signature' ) if( $substr eq '' );
143              
144 12         32 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       39 if( $word ne '' ) {
155              
156 23         45 push( @stack, { type => $word } );
157 23         21 $word = '';
158             }
159              
160 28         25 my $substr = '';
161              
162 28         39 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         33 $substr =~ s/\s+//g;
170              
171 28         26 my ( $min, $max ) = ( undef, undef );
172              
173 28 100       108 if( $substr =~ m/^(0|[1-9][0-9]*),(0|[1-9][0-9]*)$/ ) {
    100          
    50          
174              
175 6         11 ( $min, $max ) = ( $1, $2 );
176              
177             } elsif( $substr =~ m/^(0|[1-9][0-9]*),$/ ) {
178              
179 7         13 $min = $1;
180              
181             } elsif( $substr =~ m/^(0|[1-9][0-9]*)$/ ) {
182              
183 15         35 ( $min, $max ) = ( $1 )x2;
184              
185             } else {
186              
187 0         0 die( 'Invalid length limits' );
188             }
189              
190 28         116 push( @stack, {
191             length => {
192             type => pop( @stack ),
193             min => $min,
194             max => $max,
195             }
196             } );
197              
198             } else {
199              
200 1896         2714 $word .= $char;
201             }
202             }
203              
204 256 100       510 push( @stack, { type => $word } ) if( $word ne '' );
205              
206 256         712 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         45 $str =~ s/\s+/ /gs;
225              
226 12 50       68 die( "Invalid signature: ${str}" ) if( $str !~ m/^\s*\(\s*(.+?)\s*\)\s*$/ );
227              
228 12         22 $str = $1;
229              
230 12         53 my @chars = split( //, $str );
231 12         15 my @stack = ();
232 12         18 my @seq = ( 'type', 'name', 'delimiter' );
233 12         13 my %word = ();
234              
235 12         28 my %opened_parens = (
236             '{' => 0,
237             '(' => 0,
238             '[' => 0,
239             );
240              
241 12         27 my %closed_parens = (
242             '}' => '{',
243             ')' => '(',
244             ']' => '[',
245             );
246              
247 12         30 my $delimiter_re = qr/^[\s,]$/;
248              
249 12         27 while( defined( my $item_type = shift( @seq ) ) ) {
250              
251 45         34 my $word = '';
252              
253 45 100       85 if( $item_type eq 'type' ) {
    100          
    50          
254              
255 15         25 while( defined( my $char = shift( @chars ) ) ) {
256              
257 153 100       182 ++$opened_parens{ $char } if( exists $opened_parens{ $char } );
258 153 100       169 --$opened_parens{ $closed_parens{ $char } } if( exists $closed_parens{ $char } );
259              
260 153 100       318 if( $char =~ $delimiter_re ) {
261              
262 33         77 my $word_end = true;
263              
264 33         119 while( my ( $key, $value ) = each( %opened_parens ) ) {
265              
266 99 100 100     556 $word_end = false if( $word_end && $value != 0 );
267             }
268              
269 33 100       188 if( $word_end ) {
270              
271 15         79 while( defined( my $subchar = shift( @chars ) ) ) {
272              
273 15 50       44 if( $subchar !~ $delimiter_re ) {
274              
275 15         16 unshift( @chars, $subchar );
276 15         14 last;
277             }
278             }
279              
280 15         36 last;
281             }
282             }
283              
284 138         264 $word .= $char;
285             }
286              
287 15 50       29 die( 'Invalid type string' ) if( $word eq '' );
288              
289             } elsif( $item_type eq 'name' ) {
290              
291 15         27 while( defined( my $char = shift( @chars ) ) ) {
292              
293 63 100       120 if( $char =~ $delimiter_re ) {
294              
295 3         7 while( defined( my $subchar = shift( @chars ) ) ) {
296              
297 6 100       16 if( $subchar !~ $delimiter_re ) {
298              
299 3         3 unshift( @chars, $subchar );
300 3         2 last;
301             }
302             }
303              
304 3         3 last;
305             }
306              
307 60         98 $word .= $char;
308             }
309              
310 15 50       20 die( 'Invalid parameter name' ) if( $word eq '' );
311              
312             } elsif( $item_type eq 'delimiter' ) {
313              
314 15         27 my ( $type, $name ) = delete @word{ 'type', 'name' };
315              
316 15 50 33     60 die( 'Invalid signature' ) if( ( $type eq '' ) || ( $name eq '' ) );
317              
318 15         39 $type = tokenize_type_str_impl( $class, $type, $options );
319 15         24 $name = tokenize_signature_parameter_str( $name );
320              
321 15         28 push( @stack, {
322             type => $type,
323             param => $name,
324             } );
325              
326 15         11 push( @seq, $item_type );
327              
328 15 100       31 last if( scalar( @chars ) == 0 );
329 3         6 next;
330             }
331              
332 30         41 $word{ $item_type } = $word;
333              
334 30         54 push( @seq, $item_type );
335             }
336              
337 12         81 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 14 my ( $str ) = @_;
350              
351 15         15 my %out = ();
352 15         22 my $first_char = substr( $str, 0, 1 );
353              
354 15 100       20 if( $first_char eq ':' ) {
355              
356 9         16 $out{ 'named' } = true;
357 9         28 $str = substr( $str, 1 );
358              
359             } else {
360              
361 6         13 $out{ 'positional' } = true;
362             }
363              
364 15         27 my $last_char = substr( $str, -1, 1 );
365              
366 15 100       34 if( $last_char eq '!' ) {
    100          
    100          
    50          
367              
368 8         15 $out{ 'required' } = true;
369 8         21 $str = substr( $str, 0, -1 );
370              
371             } elsif( $last_char eq '?' ) {
372              
373 1         3 $out{ 'optional' } = true;
374 1         2 $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         6 $out{ 'optional' } = true;
383             }
384              
385 15         23 $out{ 'name' } = $str;
386              
387 15         21 return \%out;
388             }
389              
390             1;
391              
392             __END__