File Coverage

lib/Type/Parser.pm
Criterion Covered Total %
statement 214 218 99.0
branch 91 102 89.2
condition 59 69 85.5
subroutine 31 31 100.0
pod 3 3 100.0
total 398 423 94.5


line stmt bran cond sub pod time code
1             package Type::Parser;
2              
3 131     131   83280 use 5.008001;
  131         1622  
4 131     131   1356 use strict;
  131         294  
  131         4148  
5 131     131   1799 use warnings;
  131         311  
  131         271695  
6              
7 9     9   72 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  9         52  
8              
9             our $AUTHORITY = 'cpan:TOBYINK';
10             our $VERSION = '2.010001';
11              
12             $VERSION =~ tr/_//d;
13              
14             # Token types
15             #
16             sub TYPE () { "TYPE" }
17             sub QUOTELIKE () { "QUOTELIKE" }
18             sub STRING () { "STRING" }
19             sub HEXNUM () { "HEXNUM" }
20             sub CLASS () { "CLASS" }
21             sub L_BRACKET () { "L_BRACKET" }
22             sub R_BRACKET () { "R_BRACKET" }
23             sub COMMA () { "COMMA" }
24             sub SLURPY () { "SLURPY" }
25             sub UNION () { "UNION" }
26             sub INTERSECT () { "INTERSECT" }
27             sub SLASH () { "SLASH" }
28             sub NOT () { "NOT" }
29             sub L_PAREN () { "L_PAREN" }
30             sub R_PAREN () { "R_PAREN" }
31             sub MYSTERY () { "MYSTERY" }
32              
33             our @EXPORT_OK = qw( eval_type _std_eval parse extract_type );
34              
35             require Exporter::Tiny;
36             our @ISA = 'Exporter::Tiny';
37              
38             Evaluate: {
39              
40             sub parse {
41 351     351 1 14752 my $str = $_[0];
42 351         2946 my $parser = "Type::Parser::AstBuilder"->new( input => $str );
43 351         1530 $parser->build;
44 348 100       5800 wantarray ? ( $parser->ast, $parser->remainder ) : $parser->ast;
45             }
46            
47             sub extract_type {
48 1     1 1 7 my ( $str, $reg ) = @_;
49 1         3 my ( $parsed, $tail ) = parse( $str );
50             wantarray
51 1 50       6 ? ( _eval_type( $parsed, $reg ), $tail )
52             : _eval_type( $parsed, $reg );
53             }
54            
55             sub eval_type {
56 69     69 1 225 my ( $str, $reg ) = @_;
57 69         224 my ( $parsed, $tail ) = parse( $str );
58 66 100       218 _croak( "Unexpected tail on type expression: $tail" ) if $tail =~ /\S/sm;
59 64         208 return _eval_type( $parsed, $reg );
60             }
61            
62             my $std;
63            
64             sub _std_eval {
65 52     52   308807 require Type::Registry;
66 52 100       208 unless ( $std ) {
67 1         4 $std = "Type::Registry"->new;
68 1         3 $std->add_types( -Standard );
69             }
70 52         164 eval_type( $_[0], $std );
71             }
72            
73             sub _eval_type {
74 312     312   551 my ( $node, $reg ) = @_;
75            
76 312         636 $node = _simplify_expression( $node );
77            
78 312 100       702 if ( $node->{type} eq "list" ) {
79 59         80 return map _eval_type( $_, $reg ), @{ $node->{list} };
  59         301  
80             }
81            
82 253 100       462 if ( $node->{type} eq "union" ) {
83 18         33 return $reg->_make_union_by_overload( map _eval_type( $_, $reg ), @{ $node->{union} } );
  18         95  
84             }
85            
86 235 100       2575 if ( $node->{type} eq "intersect" ) {
87             return $reg->_make_intersection_by_overload(
88             map _eval_type( $_, $reg ),
89 8         15 @{ $node->{intersect} }
  8         30  
90             );
91             }
92            
93 227 100       459 if ( $node->{type} eq "slash" ) {
94 2         3 my @types = map _eval_type( $_, $reg ), @{ $node->{slash} };
  2         24  
95 2 50       38 _croak( "Expected exactly two types joined with slash operator" )
96             unless @types == 2;
97 2         12 return $types[0] / $types[1];
98             }
99            
100 225 100       446 if ( $node->{type} eq "slurpy" ) {
101 2         16 require Types::Standard;
102 2         8 return Types::Standard::Slurpy()->of( _eval_type( $node->{of}, $reg ) );
103             }
104            
105 223 100       479 if ( $node->{type} eq "complement" ) {
106 4         14 return _eval_type( $node->{of}, $reg )->complementary_type;
107             }
108            
109 219 100       465 if ( $node->{type} eq "parameterized" ) {
110 53         230 my $base = _eval_type( $node->{base}, $reg );
111            
112 53 50 66     204 return $base unless $base->is_parameterizable || $node->{params};
113             return $base->parameterize(
114 51 100       238 $node->{params} ? _eval_type( $node->{params}, $reg ) : () );
115             }
116            
117 166 100 66     582 if ( $node->{type} eq "primary" and $node->{token}->type eq CLASS ) {
118             my $class = substr(
119             $node->{token}->spelling,
120             0,
121 8         21 length( $node->{token}->spelling ) - 2
122             );
123 8         55 return $reg->make_class_type( $class );
124             }
125            
126 158 100 66     487 if ( $node->{type} eq "primary" and $node->{token}->type eq QUOTELIKE ) {
127 12         27 return eval( $node->{token}->spelling ); #ARGH
128             }
129            
130 146 100 66     417 if ( $node->{type} eq "primary" and $node->{token}->type eq STRING ) {
131 6         16 return $node->{token}->spelling;
132             }
133            
134 140 100 66     409 if ( $node->{type} eq "primary" and $node->{token}->type eq HEXNUM ) {
135 3         9 my $sign = '+';
136 3         8 my $spelling = $node->{token}->spelling;
137 3 100       30 if ( $spelling =~ /^[+-]/ ) {
138 1         5 $sign = substr( $spelling, 0, 1);
139 1         3 $spelling = substr( $spelling, 1 );
140             }
141             return (
142 3 100       35 ( $sign eq '-' ) ? ( 0 - hex($spelling) ) : hex($spelling)
143             );
144             }
145            
146 137 50 33     432 if ( $node->{type} eq "primary" and $node->{token}->type eq TYPE ) {
147 137         308 my $t = $node->{token}->spelling;
148 137 50       780 my $r =
149             ( $t =~ /^(.+)::(\w+)$/ )
150             ? $reg->foreign_lookup( $t, 1 )
151             : $reg->simple_lookup( $t, 1 );
152 137 100       604 $r or _croak( "%s is not a known type constraint", $node->{token}->spelling );
153 133         927 return $r;
154             }
155             } #/ sub _eval_type
156            
157             sub _simplify_expression {
158 312     312   460 my $expr = shift;
159            
160 312 100 100     812 if ( $expr->{type} eq "expression" and $expr->{op}[0] eq COMMA ) {
161 10         34 return _simplify( "list", COMMA, $expr );
162             }
163            
164 302 100 100     773 if ( $expr->{type} eq "expression" and $expr->{op}[0] eq UNION ) {
165 18         55 return _simplify( "union", UNION, $expr );
166             }
167            
168 284 100 100     679 if ( $expr->{type} eq "expression" and $expr->{op}[0] eq INTERSECT ) {
169 8         24 return _simplify( "intersect", INTERSECT, $expr );
170             }
171            
172 276 100 66     665 if ( $expr->{type} eq "expression" and $expr->{op}[0] eq SLASH ) {
173 2         11 return _simplify( "slash", SLASH, $expr );
174             }
175            
176 274         442 return $expr;
177             } #/ sub _simplify_expression
178            
179             sub _simplify {
180 131     131   1282 no warnings 'recursion';
  131         478  
  131         183413  
181 52     52   86 my $type = shift;
182 52         71 my $op = shift;
183            
184 52         71 my @list;
185 52         136 for my $expr ( $_[0]{lhs}, $_[0]{rhs} ) {
186 104 100 100     339 if ( $expr->{type} eq "expression" and $expr->{op}[0] eq $op ) {
187 14         45 my $simple = _simplify( $type, $op, $expr );
188 14         26 push @list, @{ $simple->{$type} };
  14         51  
189             }
190             else {
191 90         178 push @list, $expr;
192             }
193             }
194            
195 52         275 return { type => $type, $type => \@list };
196             } #/ sub _simplify
197             } #/ Evaluate:
198              
199             {
200             package Type::Parser::AstBuilder;
201            
202             our $AUTHORITY = 'cpan:TOBYINK';
203             our $VERSION = '2.010001';
204            
205             $VERSION =~ tr/_//d;
206            
207             sub new {
208 351     351   907 my $class = shift;
209 351         9202 bless {@_}, $class;
210             }
211            
212             our %precedence = (
213            
214             # Type::Parser::COMMA() , 1 ,
215             Type::Parser::SLASH(), 1,
216             Type::Parser::UNION(), 2,
217             Type::Parser::INTERSECT(), 3,
218             Type::Parser::NOT(), 4,
219             );
220            
221             sub _parse_primary {
222 4450     4450   8548 my $self = shift;
223 4450         7624 my $tokens = $self->{tokens};
224            
225 4450         10966 $tokens->assert_not_empty;
226            
227 4450 100       10681 if ( $tokens->peek( 0 )->type eq Type::Parser::NOT ) {
228 4         15 $tokens->eat( Type::Parser::NOT );
229 4         11 $tokens->assert_not_empty;
230             return {
231 4         14 type => "complement",
232             of => $self->_parse_primary,
233             };
234             }
235            
236 4446 100       9731 if ( $tokens->peek( 0 )->type eq Type::Parser::SLURPY ) {
237 2         5 $tokens->eat( Type::Parser::SLURPY );
238 2         4 $tokens->assert_not_empty;
239             return {
240 2         5 type => "slurpy",
241             of => $self->_parse_primary,
242             };
243             }
244            
245 4444 100       11080 if ( $tokens->peek( 0 )->type eq Type::Parser::L_PAREN ) {
246 9         29 $tokens->eat( Type::Parser::L_PAREN );
247 9         25 my $r = $self->_parse_expression;
248 9         20 $tokens->eat( Type::Parser::R_PAREN );
249 9         23 return $r;
250             }
251            
252 4435 100 100     9633 if ( $tokens->peek( 1 )
      100        
253             and $tokens->peek( 0 )->type eq Type::Parser::TYPE
254             and $tokens->peek( 1 )->type eq Type::Parser::L_BRACKET )
255             {
256 385         1349 my $base = { type => "primary", token => $tokens->eat( Type::Parser::TYPE ) };
257 385         1527 $tokens->eat( Type::Parser::L_BRACKET );
258 385         1123 $tokens->assert_not_empty;
259            
260 385         1433 local $precedence{ Type::Parser::COMMA() } = 1;
261            
262 385         834 my $params = undef;
263 385 100       3458 if ( $tokens->peek( 0 )->type eq Type::Parser::R_BRACKET ) {
264 4         12 $tokens->eat( Type::Parser::R_BRACKET );
265             }
266             else {
267 381         1682 $params = $self->_parse_expression;
268             $params = { type => "list", list => [$params] }
269 381 50       2573 unless $params->{type} eq "list";
270 381         3759 $tokens->eat( Type::Parser::R_BRACKET );
271             }
272             return {
273 385         3175 type => "parameterized",
274             base => $base,
275             params => $params,
276             };
277             } #/ if ( $tokens->peek( 1 ...))
278            
279 4050         9197 my $type = $tokens->peek( 0 )->type;
280 4050 100 100     18564 if ( $type eq Type::Parser::TYPE
      100        
      100        
      100        
281             or $type eq Type::Parser::QUOTELIKE
282             or $type eq Type::Parser::STRING
283             or $type eq Type::Parser::HEXNUM
284             or $type eq Type::Parser::CLASS )
285             {
286 4047         9658 return { type => "primary", token => $tokens->eat };
287             }
288            
289             Type::Parser::_croak(
290 3         11 "Unexpected token in primary type expression; got '%s'",
291             $tokens->peek( 0 )->spelling
292             );
293             } #/ sub _parse_primary
294            
295             sub _parse_expression_1 {
296 741     741   1487 my $self = shift;
297 741         1460 my $tokens = $self->{tokens};
298            
299 741         1822 my ( $lhs, $min_p ) = @_;
300 741   100     2193 while ( !$tokens->empty
      66        
301             and defined( $precedence{ $tokens->peek( 0 )->type } )
302             and $precedence{ $tokens->peek( 0 )->type } >= $min_p )
303             {
304 3703         11080 my $op = $tokens->eat;
305 3703         9941 my $rhs = $self->_parse_primary;
306            
307 3703   100     11124 while ( !$tokens->empty
      100        
308             and defined( $precedence{ $tokens->peek( 0 )->type } )
309             and $precedence{ $tokens->peek( 0 )->type } > $precedence{ $op->type } )
310             {
311 3         12 my $lookahead = $tokens->peek( 0 );
312 3         12 $rhs = $self->_parse_expression_1( $rhs, $precedence{ $lookahead->type } );
313             }
314            
315             $lhs = {
316 3703         20701 type => "expression",
317             op => $op,
318             lhs => $lhs,
319             rhs => $rhs,
320             };
321             } #/ while ( !$tokens->empty and...)
322 741         2594 return $lhs;
323             } #/ sub _parse_expression_1
324            
325             sub _parse_expression {
326 741     741   1258 my $self = shift;
327 741         1465 my $tokens = $self->{tokens};
328            
329 741         4779 return $self->_parse_expression_1( $self->_parse_primary, 0 );
330             }
331            
332             sub build {
333 351     351   839 my $self = shift;
334             $self->{tokens} =
335 351         4469 "Type::Parser::TokenStream"->new( remaining => $self->{input} );
336 351         1247 $self->{ast} = $self->_parse_expression;
337             }
338            
339             sub ast {
340 348     348   7721 $_[0]{ast};
341             }
342            
343             sub remainder {
344 69     69   226 $_[0]{tokens}->remainder;
345             }
346             }
347              
348             {
349             package Type::Parser::Token;
350            
351             our $AUTHORITY = 'cpan:TOBYINK';
352             our $VERSION = '2.010001';
353            
354             $VERSION =~ tr/_//d;
355            
356 50454     50454   225758 sub type { $_[0][0] }
357 4159     4159   221997 sub spelling { $_[0][1] }
358             }
359              
360             {
361             package Type::Parser::TokenStream;
362            
363             our $AUTHORITY = 'cpan:TOBYINK';
364             our $VERSION = '2.010001';
365            
366             $VERSION =~ tr/_//d;
367            
368 131     131   1299 use Scalar::Util qw(looks_like_number);
  131         330  
  131         253822  
369            
370             sub new {
371 351     351   1005 my $class = shift;
372 351         2556 bless { stack => [], done => [], @_ }, $class;
373             }
374            
375             sub peek {
376 55135     55135   81680 my $self = shift;
377 55135         75006 my $ahead = $_[0];
378            
379 55135   100     95178 while ( $self->_stack_size <= $ahead and length $self->{remaining} ) {
380 8937         21861 $self->_stack_extend;
381             }
382            
383 55135         80550 my @tokens = grep ref, @{ $self->{stack} };
  55135         112482  
384 55135         147207 return $tokens[$ahead];
385             } #/ sub peek
386            
387             sub empty {
388 12991     12991   19951 my $self = shift;
389 12991         23935 not $self->peek( 0 );
390             }
391            
392             sub eat {
393 8929     8929   13477 my $self = shift;
394 8929 50       15714 $self->_stack_extend unless $self->_stack_size;
395 8929         14592 my $r;
396 8929         12270 while ( defined( my $item = shift @{ $self->{stack} } ) ) {
  8955         24424  
397 8955         12418 push @{ $self->{done} }, $item;
  8955         25810  
398 8955 100       17671 if ( ref $item ) {
399 8929         12558 $r = $item;
400 8929         18152 last;
401             }
402             }
403            
404 8929 50 66     26357 if ( @_ and $_[0] ne $r->type ) {
405 0         0 unshift @{ $self->{stack} }, pop @{ $self->{done} }; # uncoverable statement
  0         0  
  0         0  
406 0         0 Type::Parser::_croak( "Expected $_[0]; got " . $r->type ); # uncoverable statement
407             }
408            
409 8929         32834 return $r;
410             } #/ sub eat
411            
412             sub assert_not_empty {
413 4841     4841   6937 my $self = shift;
414 4841 50       9463 Type::Parser::_croak( "Expected token; got empty string" ) if $self->empty;
415             }
416            
417             sub _stack_size {
418 73001     73001   99375 my $self = shift;
419 73001         96866 scalar grep ref, @{ $self->{stack} };
  73001         235052  
420             }
421            
422             sub _stack_extend {
423 8937     8937   15702 my $self = shift;
424 8937         12041 push @{ $self->{stack} }, $self->_read_token;
  8937         20443  
425 8937         37247 my ( $space ) = ( $self->{remaining} =~ m/^([\s\n\r]*)/sm );
426 8937 100       35662 return unless length $space;
427 32         35 push @{ $self->{stack} }, $space;
  32         60  
428 32         72 substr( $self->{remaining}, 0, length $space ) = "";
429             }
430            
431             sub remainder {
432 69     69   132 my $self = shift;
433             return join "",
434 80 100       773 map { ref( $_ ) ? $_->spelling : $_ }
435 69         175 ( @{ $self->{stack} }, $self->{remaining} );
  69         171  
436             }
437            
438             my %punctuation = (
439             '[' => bless( [ Type::Parser::L_BRACKET, "[" ], "Type::Parser::Token" ),
440             ']' => bless( [ Type::Parser::R_BRACKET, "]" ], "Type::Parser::Token" ),
441             '(' => bless( [ Type::Parser::L_PAREN, "[" ], "Type::Parser::Token" ),
442             ')' => bless( [ Type::Parser::R_PAREN, "]" ], "Type::Parser::Token" ),
443             ',' => bless( [ Type::Parser::COMMA, "," ], "Type::Parser::Token" ),
444             '=>' => bless( [ Type::Parser::COMMA, "=>" ], "Type::Parser::Token" ),
445             'slurpy' => bless( [ Type::Parser::SLURPY, "slurpy" ], "Type::Parser::Token" ),
446             '|' => bless( [ Type::Parser::UNION, "|" ], "Type::Parser::Token" ),
447             '&' => bless( [ Type::Parser::INTERSECT, "&" ], "Type::Parser::Token" ),
448             '/' => bless( [ Type::Parser::SLASH, "/" ], "Type::Parser::Token" ),
449             '~' => bless( [ Type::Parser::NOT, "~" ], "Type::Parser::Token" ),
450             );
451            
452             sub _read_token {
453 8937     8937   12855 my $self = shift;
454            
455 8937 50       20214 return if $self->{remaining} eq "";
456            
457             # Punctuation
458             #
459            
460 8937 100       38374 if ( $self->{remaining} =~ /^( => | [()\]\[|&~,\/] )/xsm ) {
461 4497         12170 my $spelling = $1;
462 4497         10656 substr( $self->{remaining}, 0, length $spelling ) = "";
463 4497         11712 return $punctuation{$spelling};
464             }
465            
466 4440 100       16297 if ( $self->{remaining} =~ /\A\s*[q'"]/sm ) {
467 3785         99054 require Text::Balanced;
468 3785 50       1195912 if ( my $quotelike = Text::Balanced::extract_quotelike( $self->{remaining} ) ) {
469 3785         387121 return bless( [ Type::Parser::QUOTELIKE, $quotelike ], "Type::Parser::Token" );
470             }
471             }
472            
473 655 100       3153 if ( $self->{remaining} =~ /^([+-]?[\w:.+]+)/sm ) {
474 652         1908 my $spelling = $1;
475 652         2229 substr( $self->{remaining}, 0, length $spelling ) = "";
476            
477 652 100       8240 if ( $spelling =~ /::$/sm ) {
    100          
    100          
    100          
    100          
478 10         36 return bless( [ Type::Parser::CLASS, $spelling ], "Type::Parser::Token" );
479             }
480             elsif ( $spelling =~ /^[+-]?0x[0-9A-Fa-f]+$/sm ) {
481 3         14 return bless( [ Type::Parser::HEXNUM, $spelling ], "Type::Parser::Token" );
482             }
483             elsif ( looks_like_number( $spelling ) ) {
484 5         47 return bless( [ Type::Parser::STRING, $spelling ], "Type::Parser::Token" );
485             }
486             elsif ( $self->{remaining} =~ /^\s*=>/sm ) # peek ahead
487             {
488 4         16 return bless( [ Type::Parser::STRING, $spelling ], "Type::Parser::Token" );
489             }
490             elsif ( $spelling eq "slurpy" ) {
491 2         8 return $punctuation{$spelling};
492             }
493            
494 628         3644 return bless( [ Type::Parser::TYPE, $spelling ], "Type::Parser::Token" );
495             } #/ if ( $self->{remaining...})
496            
497 3         12 my $rest = $self->{remaining};
498 3         8 $self->{remaining} = "";
499 3         22 return bless( [ Type::Parser::MYSTERY, $rest ], "Type::Parser::Token" );
500             } #/ sub _read_token
501             }
502              
503             1;
504              
505             __END__