| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Type::Parser; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 64 |  |  | 64 |  | 26444 | use 5.008001; | 
|  | 64 |  |  |  |  | 247 |  | 
| 4 | 64 |  |  | 64 |  | 349 | use strict; | 
|  | 64 |  |  |  |  | 147 |  | 
|  | 64 |  |  |  |  | 1452 |  | 
| 5 | 64 |  |  | 64 |  | 365 | use warnings; | 
|  | 64 |  |  |  |  | 185 |  | 
|  | 64 |  |  |  |  | 96248 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 9 |  |  | 9 |  | 1031 | sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak } | 
|  | 9 |  |  |  |  | 48 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | our $AUTHORITY = 'cpan:TOBYINK'; | 
| 10 |  |  |  |  |  |  | our $VERSION   = '2.002001'; | 
| 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 | 184 |  |  | 184 | 1 | 4581 | my $str    = $_[0]; | 
| 42 | 184 |  |  |  |  | 750 | my $parser = "Type::Parser::AstBuilder"->new( input => $str ); | 
| 43 | 184 |  |  |  |  | 567 | $parser->build; | 
| 44 | 181 | 100 |  |  |  | 818 | wantarray ? ( $parser->ast, $parser->remainder ) : $parser->ast; | 
| 45 |  |  |  |  |  |  | } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub extract_type { | 
| 48 | 1 |  |  | 1 | 1 | 7 | my ( $str,    $reg )  = @_; | 
| 49 | 1 |  |  |  |  | 4 | my ( $parsed, $tail ) = parse( $str ); | 
| 50 |  |  |  |  |  |  | wantarray | 
| 51 | 1 | 50 |  |  |  | 11 | ? ( _eval_type( $parsed, $reg ), $tail ) | 
| 52 |  |  |  |  |  |  | : _eval_type( $parsed, $reg ); | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | sub eval_type { | 
| 56 | 69 |  |  | 69 | 1 | 174 | my ( $str,    $reg )  = @_; | 
| 57 | 69 |  |  |  |  | 132 | my ( $parsed, $tail ) = parse( $str ); | 
| 58 | 66 | 100 |  |  |  | 204 | _croak( "Unexpected tail on type expression: $tail" ) if $tail =~ /\S/sm; | 
| 59 | 64 |  |  |  |  | 132 | return _eval_type( $parsed, $reg ); | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | my $std; | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | sub _std_eval { | 
| 65 | 52 |  |  | 52 |  | 3660 | require Type::Registry; | 
| 66 | 52 | 100 |  |  |  | 141 | unless ( $std ) { | 
| 67 | 1 |  |  |  |  | 4 | $std = "Type::Registry"->new; | 
| 68 | 1 |  |  |  |  | 5 | $std->add_types( -Standard ); | 
| 69 |  |  |  |  |  |  | } | 
| 70 | 52 |  |  |  |  | 106 | eval_type( $_[0], $std ); | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | sub _eval_type { | 
| 74 | 312 |  |  | 312 |  | 471 | my ( $node, $reg ) = @_; | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 312 |  |  |  |  | 467 | $node = _simplify_expression( $node ); | 
| 77 |  |  |  |  |  |  |  | 
| 78 | 312 | 100 |  |  |  | 533 | if ( $node->{type} eq "list" ) { | 
| 79 | 59 |  |  |  |  | 74 | return map _eval_type( $_, $reg ), @{ $node->{list} }; | 
|  | 59 |  |  |  |  | 235 |  | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 253 | 100 |  |  |  | 412 | if ( $node->{type} eq "union" ) { | 
| 83 | 18 |  |  |  |  | 33 | return $reg->_make_union_by_overload( map _eval_type( $_, $reg ), @{ $node->{union} } ); | 
|  | 18 |  |  |  |  | 54 |  | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 235 | 100 |  |  |  | 389 | if ( $node->{type} eq "intersect" ) { | 
| 87 |  |  |  |  |  |  | return $reg->_make_intersection_by_overload( | 
| 88 |  |  |  |  |  |  | map _eval_type( $_, $reg ), | 
| 89 | 8 |  |  |  |  | 11 | @{ $node->{intersect} } | 
|  | 8 |  |  |  |  | 23 |  | 
| 90 |  |  |  |  |  |  | ); | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 227 | 100 |  |  |  | 391 | if ( $node->{type} eq "slash" ) { | 
| 94 | 2 |  |  |  |  | 5 | my @types = map _eval_type( $_, $reg ), @{ $node->{slash} }; | 
|  | 2 |  |  |  |  | 19 |  | 
| 95 | 2 | 50 |  |  |  | 31 | _croak( "Expected exactly two types joined with slash operator" ) | 
| 96 |  |  |  |  |  |  | unless @types == 2; | 
| 97 | 2 |  |  |  |  | 14 | return $types[0] / $types[1]; | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 225 | 100 |  |  |  | 369 | if ( $node->{type} eq "slurpy" ) { | 
| 101 | 2 |  |  |  |  | 11 | require Types::Standard; | 
| 102 | 2 |  |  |  |  | 11 | return Types::Standard::Slurpy()->of( _eval_type( $node->{of}, $reg ) ); | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  |  | 
| 105 | 223 | 100 |  |  |  | 360 | if ( $node->{type} eq "complement" ) { | 
| 106 | 4 |  |  |  |  | 9 | return _eval_type( $node->{of}, $reg )->complementary_type; | 
| 107 |  |  |  |  |  |  | } | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 219 | 100 |  |  |  | 341 | if ( $node->{type} eq "parameterized" ) { | 
| 110 | 53 |  |  |  |  | 159 | my $base = _eval_type( $node->{base}, $reg ); | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 53 | 50 | 66 |  |  | 144 | return $base unless $base->is_parameterizable || $node->{params}; | 
| 113 |  |  |  |  |  |  | return $base->parameterize( | 
| 114 | 51 | 100 |  |  |  | 170 | $node->{params} ? _eval_type( $node->{params}, $reg ) : () ); | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 166 | 100 | 66 |  |  | 463 | if ( $node->{type} eq "primary" and $node->{token}->type eq CLASS ) { | 
| 118 |  |  |  |  |  |  | my $class = substr( | 
| 119 |  |  |  |  |  |  | $node->{token}->spelling, | 
| 120 |  |  |  |  |  |  | 0, | 
| 121 | 8 |  |  |  |  | 25 | length( $node->{token}->spelling ) - 2 | 
| 122 |  |  |  |  |  |  | ); | 
| 123 | 8 |  |  |  |  | 31 | return $reg->make_class_type( $class ); | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 158 | 100 | 66 |  |  | 422 | if ( $node->{type} eq "primary" and $node->{token}->type eq QUOTELIKE ) { | 
| 127 | 12 |  |  |  |  | 26 | return eval( $node->{token}->spelling );    #ARGH | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 146 | 100 | 66 |  |  | 418 | if ( $node->{type} eq "primary" and $node->{token}->type eq STRING ) { | 
| 131 | 6 |  |  |  |  | 13 | return $node->{token}->spelling; | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 140 | 100 | 66 |  |  | 341 | if ( $node->{type} eq "primary" and $node->{token}->type eq HEXNUM ) { | 
| 135 | 3 |  |  |  |  | 4 | my $sign = '+'; | 
| 136 | 3 |  |  |  |  | 7 | my $spelling = $node->{token}->spelling; | 
| 137 | 3 | 100 |  |  |  | 9 | if ( $spelling =~ /^[+-]/ ) { | 
| 138 | 1 |  |  |  |  | 5 | $sign = substr( $spelling, 0, 1); | 
| 139 | 1 |  |  |  |  | 2 | $spelling = substr( $spelling, 1 ); | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  | return ( | 
| 142 | 3 | 100 |  |  |  | 21 | ( $sign eq '-' ) ? ( 0 - hex($spelling) ) : hex($spelling) | 
| 143 |  |  |  |  |  |  | ); | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 | 137 | 50 | 33 |  |  | 354 | if ( $node->{type} eq "primary" and $node->{token}->type eq TYPE ) { | 
| 147 | 137 |  |  |  |  | 224 | my $t = $node->{token}->spelling; | 
| 148 | 137 | 50 |  |  |  | 471 | my $r = | 
| 149 |  |  |  |  |  |  | ( $t =~ /^(.+)::(\w+)$/ ) | 
| 150 |  |  |  |  |  |  | ? $reg->foreign_lookup( $t, 1 ) | 
| 151 |  |  |  |  |  |  | : $reg->simple_lookup( $t, 1 ); | 
| 152 | 137 | 100 |  |  |  | 436 | $r or _croak( "%s is not a known type constraint", $node->{token}->spelling ); | 
| 153 | 133 |  |  |  |  | 433 | return $r; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  | } #/ sub _eval_type | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | sub _simplify_expression { | 
| 158 | 312 |  |  | 312 |  | 352 | my $expr = shift; | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 312 | 100 | 100 |  |  | 711 | if ( $expr->{type} eq "expression" and $expr->{op}[0] eq COMMA ) { | 
| 161 | 10 |  |  |  |  | 22 | return _simplify( "list", COMMA, $expr ); | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 302 | 100 | 100 |  |  | 601 | if ( $expr->{type} eq "expression" and $expr->{op}[0] eq UNION ) { | 
| 165 | 18 |  |  |  |  | 39 | return _simplify( "union", UNION, $expr ); | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 284 | 100 | 100 |  |  | 520 | if ( $expr->{type} eq "expression" and $expr->{op}[0] eq INTERSECT ) { | 
| 169 | 8 |  |  |  |  | 17 | return _simplify( "intersect", INTERSECT, $expr ); | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 276 | 100 | 66 |  |  | 478 | if ( $expr->{type} eq "expression" and $expr->{op}[0] eq SLASH ) { | 
| 173 | 2 |  |  |  |  | 23 | return _simplify( "slash", SLASH, $expr ); | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  |  | 
| 176 | 274 |  |  |  |  | 342 | return $expr; | 
| 177 |  |  |  |  |  |  | } #/ sub _simplify_expression | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | sub _simplify { | 
| 180 | 64 |  |  | 64 |  | 623 | no warnings 'recursion'; | 
|  | 64 |  |  |  |  | 482 |  | 
|  | 64 |  |  |  |  | 78075 |  | 
| 181 | 52 |  |  | 52 |  | 81 | my $type = shift; | 
| 182 | 52 |  |  |  |  | 62 | my $op   = shift; | 
| 183 |  |  |  |  |  |  |  | 
| 184 | 52 |  |  |  |  | 61 | my @list; | 
| 185 | 52 |  |  |  |  | 100 | for my $expr ( $_[0]{lhs}, $_[0]{rhs} ) { | 
| 186 | 104 | 100 | 100 |  |  | 245 | if ( $expr->{type} eq "expression" and $expr->{op}[0] eq $op ) { | 
| 187 | 14 |  |  |  |  | 28 | my $simple = _simplify( $type, $op, $expr ); | 
| 188 | 14 |  |  |  |  | 17 | push @list, @{ $simple->{$type} }; | 
|  | 14 |  |  |  |  | 40 |  | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  | else { | 
| 191 | 90 |  |  |  |  | 144 | push @list, $expr; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 52 |  |  |  |  | 172 | 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.002001'; | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | $VERSION =~ tr/_//d; | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | sub new { | 
| 208 | 184 |  |  | 184 |  | 316 | my $class = shift; | 
| 209 | 184 |  |  |  |  | 598 | 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 | 614 |  |  | 614 |  | 860 | my $self   = shift; | 
| 223 | 614 |  |  |  |  | 2082 | my $tokens = $self->{tokens}; | 
| 224 |  |  |  |  |  |  |  | 
| 225 | 614 |  |  |  |  | 1250 | $tokens->assert_not_empty; | 
| 226 |  |  |  |  |  |  |  | 
| 227 | 614 | 100 |  |  |  | 1354 | if ( $tokens->peek( 0 )->type eq Type::Parser::NOT ) { | 
| 228 | 4 |  |  |  |  | 12 | $tokens->eat( Type::Parser::NOT ); | 
| 229 | 4 |  |  |  |  | 10 | $tokens->assert_not_empty; | 
| 230 |  |  |  |  |  |  | return { | 
| 231 | 4 |  |  |  |  | 11 | type => "complement", | 
| 232 |  |  |  |  |  |  | of   => $self->_parse_primary, | 
| 233 |  |  |  |  |  |  | }; | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 | 610 | 100 |  |  |  | 1089 | if ( $tokens->peek( 0 )->type eq Type::Parser::SLURPY ) { | 
| 237 | 2 |  |  |  |  | 5 | $tokens->eat( Type::Parser::SLURPY ); | 
| 238 | 2 |  |  |  |  | 6 | $tokens->assert_not_empty; | 
| 239 |  |  |  |  |  |  | return { | 
| 240 | 2 |  |  |  |  | 10 | type => "slurpy", | 
| 241 |  |  |  |  |  |  | of   => $self->_parse_primary, | 
| 242 |  |  |  |  |  |  | }; | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  |  | 
| 245 | 608 | 100 |  |  |  | 1154 | if ( $tokens->peek( 0 )->type eq Type::Parser::L_PAREN ) { | 
| 246 | 9 |  |  |  |  | 19 | $tokens->eat( Type::Parser::L_PAREN ); | 
| 247 | 9 |  |  |  |  | 20 | my $r = $self->_parse_expression; | 
| 248 | 9 |  |  |  |  | 21 | $tokens->eat( Type::Parser::R_PAREN ); | 
| 249 | 9 |  |  |  |  | 26 | return $r; | 
| 250 |  |  |  |  |  |  | } | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 599 | 100 | 100 |  |  | 1121 | 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 | 217 |  |  |  |  | 559 | my $base = { type => "primary", token => $tokens->eat( Type::Parser::TYPE ) }; | 
| 257 | 217 |  |  |  |  | 560 | $tokens->eat( Type::Parser::L_BRACKET ); | 
| 258 | 217 |  |  |  |  | 505 | $tokens->assert_not_empty; | 
| 259 |  |  |  |  |  |  |  | 
| 260 | 217 |  |  |  |  | 638 | local $precedence{ Type::Parser::COMMA() } = 1; | 
| 261 |  |  |  |  |  |  |  | 
| 262 | 217 |  |  |  |  | 383 | my $params = undef; | 
| 263 | 217 | 100 |  |  |  | 1836 | if ( $tokens->peek( 0 )->type eq Type::Parser::R_BRACKET ) { | 
| 264 | 4 |  |  |  |  | 16 | $tokens->eat( Type::Parser::R_BRACKET ); | 
| 265 |  |  |  |  |  |  | } | 
| 266 |  |  |  |  |  |  | else { | 
| 267 | 213 |  |  |  |  | 1723 | $params = $self->_parse_expression; | 
| 268 |  |  |  |  |  |  | $params = { type => "list", list => [$params] } | 
| 269 | 213 | 50 |  |  |  | 1536 | unless $params->{type} eq "list"; | 
| 270 | 213 |  |  |  |  | 541 | $tokens->eat( Type::Parser::R_BRACKET ); | 
| 271 |  |  |  |  |  |  | } | 
| 272 |  |  |  |  |  |  | return { | 
| 273 | 217 |  |  |  |  | 1243 | type   => "parameterized", | 
| 274 |  |  |  |  |  |  | base   => $base, | 
| 275 |  |  |  |  |  |  | params => $params, | 
| 276 |  |  |  |  |  |  | }; | 
| 277 |  |  |  |  |  |  | } #/ if ( $tokens->peek( 1 ...)) | 
| 278 |  |  |  |  |  |  |  | 
| 279 | 382 |  |  |  |  | 913 | my $type = $tokens->peek( 0 )->type; | 
| 280 | 382 | 100 | 100 |  |  | 1445 | 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 | 379 |  |  |  |  | 741 | return { type => "primary", token => $tokens->eat }; | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | Type::Parser::_croak( | 
| 290 | 3 |  |  |  |  | 12 | "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 | 406 |  |  | 406 |  | 611 | my $self   = shift; | 
| 297 | 406 |  |  |  |  | 608 | my $tokens = $self->{tokens}; | 
| 298 |  |  |  |  |  |  |  | 
| 299 | 406 |  |  |  |  | 727 | my ( $lhs, $min_p ) = @_; | 
| 300 | 406 |  | 100 |  |  | 738 | while ( !$tokens->empty | 
|  |  |  | 66 |  |  |  |  | 
| 301 |  |  |  |  |  |  | and defined( $precedence{ $tokens->peek( 0 )->type } ) | 
| 302 |  |  |  |  |  |  | and $precedence{ $tokens->peek( 0 )->type } >= $min_p ) | 
| 303 |  |  |  |  |  |  | { | 
| 304 | 202 |  |  |  |  | 467 | my $op  = $tokens->eat; | 
| 305 | 202 |  |  |  |  | 446 | my $rhs = $self->_parse_primary; | 
| 306 |  |  |  |  |  |  |  | 
| 307 | 202 |  | 100 |  |  | 529 | 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 |  |  |  |  | 10 | my $lookahead = $tokens->peek( 0 ); | 
| 312 | 3 |  |  |  |  | 14 | $rhs = $self->_parse_expression_1( $rhs, $precedence{ $lookahead->type } ); | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | $lhs = { | 
| 316 | 202 |  |  |  |  | 951 | type => "expression", | 
| 317 |  |  |  |  |  |  | op   => $op, | 
| 318 |  |  |  |  |  |  | lhs  => $lhs, | 
| 319 |  |  |  |  |  |  | rhs  => $rhs, | 
| 320 |  |  |  |  |  |  | }; | 
| 321 |  |  |  |  |  |  | } #/ while ( !$tokens->empty and...) | 
| 322 | 406 |  |  |  |  | 1041 | return $lhs; | 
| 323 |  |  |  |  |  |  | } #/ sub _parse_expression_1 | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | sub _parse_expression { | 
| 326 | 406 |  |  | 406 |  | 555 | my $self   = shift; | 
| 327 | 406 |  |  |  |  | 601 | my $tokens = $self->{tokens}; | 
| 328 |  |  |  |  |  |  |  | 
| 329 | 406 |  |  |  |  | 2119 | return $self->_parse_expression_1( $self->_parse_primary, 0 ); | 
| 330 |  |  |  |  |  |  | } | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | sub build { | 
| 333 | 184 |  |  | 184 |  | 282 | my $self = shift; | 
| 334 |  |  |  |  |  |  | $self->{tokens} = | 
| 335 | 184 |  |  |  |  | 875 | "Type::Parser::TokenStream"->new( remaining => $self->{input} ); | 
| 336 | 184 |  |  |  |  | 463 | $self->{ast} = $self->_parse_expression; | 
| 337 |  |  |  |  |  |  | } | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | sub ast { | 
| 340 | 181 |  |  | 181 |  | 1243 | $_[0]{ast}; | 
| 341 |  |  |  |  |  |  | } | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | sub remainder { | 
| 344 | 69 |  |  | 69 |  | 145 | $_[0]{tokens}->remainder; | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | { | 
| 349 |  |  |  |  |  |  | package Type::Parser::Token; | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | our $AUTHORITY = 'cpan:TOBYINK'; | 
| 352 |  |  |  |  |  |  | our $VERSION   = '2.002001'; | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | $VERSION =~ tr/_//d; | 
| 355 |  |  |  |  |  |  |  | 
| 356 | 6080 |  |  | 6080 |  | 20104 | sub type     { $_[0][0] } | 
| 357 | 490 |  |  | 490 |  | 9637 | sub spelling { $_[0][1] } | 
| 358 |  |  |  |  |  |  | } | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | { | 
| 361 |  |  |  |  |  |  | package Type::Parser::TokenStream; | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | our $AUTHORITY = 'cpan:TOBYINK'; | 
| 364 |  |  |  |  |  |  | our $VERSION   = '2.002001'; | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | $VERSION =~ tr/_//d; | 
| 367 |  |  |  |  |  |  |  | 
| 368 | 64 |  |  | 64 |  | 579 | use Scalar::Util qw(looks_like_number); | 
|  | 64 |  |  |  |  | 174 |  | 
|  | 64 |  |  |  |  | 89642 |  | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | sub new { | 
| 371 | 184 |  |  | 184 |  | 295 | my $class = shift; | 
| 372 | 184 |  |  |  |  | 849 | bless { stack => [], done => [], @_ }, $class; | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | sub peek { | 
| 376 | 6585 |  |  | 6585 |  | 7619 | my $self  = shift; | 
| 377 | 6585 |  |  |  |  | 7169 | my $ahead = $_[0]; | 
| 378 |  |  |  |  |  |  |  | 
| 379 | 6585 |  | 100 |  |  | 8907 | while ( $self->_stack_size <= $ahead and length $self->{remaining} ) { | 
| 380 | 1264 |  |  |  |  | 2208 | $self->_stack_extend; | 
| 381 |  |  |  |  |  |  | } | 
| 382 |  |  |  |  |  |  |  | 
| 383 | 6585 |  |  |  |  | 7799 | my @tokens = grep ref, @{ $self->{stack} }; | 
|  | 6585 |  |  |  |  | 11630 |  | 
| 384 | 6585 |  |  |  |  | 17748 | return $tokens[$ahead]; | 
| 385 |  |  |  |  |  |  | } #/ sub peek | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | sub empty { | 
| 388 | 1650 |  |  | 1650 |  | 2315 | my $self = shift; | 
| 389 | 1650 |  |  |  |  | 2505 | not $self->peek( 0 ); | 
| 390 |  |  |  |  |  |  | } | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | sub eat { | 
| 393 | 1256 |  |  | 1256 |  | 1531 | my $self = shift; | 
| 394 | 1256 | 50 |  |  |  | 1791 | $self->_stack_extend unless $self->_stack_size; | 
| 395 | 1256 |  |  |  |  | 1609 | my $r; | 
| 396 | 1256 |  |  |  |  | 1603 | while ( defined( my $item = shift @{ $self->{stack} } ) ) { | 
|  | 1282 |  |  |  |  | 2667 |  | 
| 397 | 1282 |  |  |  |  | 1414 | push @{ $self->{done} }, $item; | 
|  | 1282 |  |  |  |  | 2114 |  | 
| 398 | 1282 | 100 |  |  |  | 2266 | if ( ref $item ) { | 
| 399 | 1256 |  |  |  |  | 1420 | $r = $item; | 
| 400 | 1256 |  |  |  |  | 1781 | last; | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  | } | 
| 403 |  |  |  |  |  |  |  | 
| 404 | 1256 | 50 | 66 |  |  | 2984 | 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 | 1256 |  |  |  |  | 3068 | return $r; | 
| 410 |  |  |  |  |  |  | } #/ sub eat | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | sub assert_not_empty { | 
| 413 | 837 |  |  | 837 |  | 989 | my $self = shift; | 
| 414 | 837 | 50 |  |  |  | 1317 | Type::Parser::_croak( "Expected token; got empty string" ) if $self->empty; | 
| 415 |  |  |  |  |  |  | } | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | sub _stack_size { | 
| 418 | 9105 |  |  | 9105 |  | 9921 | my $self = shift; | 
| 419 | 9105 |  |  |  |  | 9335 | scalar grep ref, @{ $self->{stack} }; | 
|  | 9105 |  |  |  |  | 26459 |  | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | sub _stack_extend { | 
| 423 | 1264 |  |  | 1264 |  | 1572 | my $self = shift; | 
| 424 | 1264 |  |  |  |  | 1417 | push @{ $self->{stack} }, $self->_read_token; | 
|  | 1264 |  |  |  |  | 2247 |  | 
| 425 | 1264 |  |  |  |  | 4408 | my ( $space ) = ( $self->{remaining} =~ m/^([\s\n\r]*)/sm ); | 
| 426 | 1264 | 100 |  |  |  | 4033 | return unless length $space; | 
| 427 | 32 |  |  |  |  | 44 | push @{ $self->{stack} }, $space; | 
|  | 32 |  |  |  |  | 62 |  | 
| 428 | 32 |  |  |  |  | 84 | substr( $self->{remaining}, 0, length $space ) = ""; | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | sub remainder { | 
| 432 | 69 |  |  | 69 |  | 95 | my $self = shift; | 
| 433 |  |  |  |  |  |  | return join "", | 
| 434 | 80 | 100 |  |  |  | 541 | map { ref( $_ ) ? $_->spelling : $_ } | 
| 435 | 69 |  |  |  |  | 90 | ( @{ $self->{stack} }, $self->{remaining} ); | 
|  | 69 |  |  |  |  | 121 |  | 
| 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 | 1264 |  |  | 1264 |  | 1501 | my $self = shift; | 
| 454 |  |  |  |  |  |  |  | 
| 455 | 1264 | 50 |  |  |  | 2298 | return if $self->{remaining} eq ""; | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | # Punctuation | 
| 458 |  |  |  |  |  |  | # | 
| 459 |  |  |  |  |  |  |  | 
| 460 | 1264 | 100 |  |  |  | 3929 | if ( $self->{remaining} =~ /^( => | [()\]\[|&~,\/] )/xsm ) { | 
| 461 | 660 |  |  |  |  | 1401 | my $spelling = $1; | 
| 462 | 660 |  |  |  |  | 1283 | substr( $self->{remaining}, 0, length $spelling ) = ""; | 
| 463 | 660 |  |  |  |  | 1472 | return $punctuation{$spelling}; | 
| 464 |  |  |  |  |  |  | } | 
| 465 |  |  |  |  |  |  |  | 
| 466 | 604 | 100 |  |  |  | 1695 | if ( $self->{remaining} =~ /\A\s*[q'"]/sm ) { | 
| 467 | 130 |  |  |  |  | 17780 | require Text::Balanced; | 
| 468 | 130 | 50 |  |  |  | 381400 | if ( my $quotelike = Text::Balanced::extract_quotelike( $self->{remaining} ) ) { | 
| 469 | 130 |  |  |  |  | 10893 | return bless( [ Type::Parser::QUOTELIKE, $quotelike ], "Type::Parser::Token" ); | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  | } | 
| 472 |  |  |  |  |  |  |  | 
| 473 | 474 | 100 |  |  |  | 1727 | if ( $self->{remaining} =~ /^([+-]?[\w:.+]+)/sm ) { | 
| 474 | 471 |  |  |  |  | 1116 | my $spelling = $1; | 
| 475 | 471 |  |  |  |  | 1058 | substr( $self->{remaining}, 0, length $spelling ) = ""; | 
| 476 |  |  |  |  |  |  |  | 
| 477 | 471 | 100 |  |  |  | 3297 | if ( $spelling =~ /::$/sm ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 478 | 10 |  |  |  |  | 60 | return bless( [ Type::Parser::CLASS, $spelling ], "Type::Parser::Token" ); | 
| 479 |  |  |  |  |  |  | } | 
| 480 |  |  |  |  |  |  | elsif ( $spelling =~ /^[+-]?0x[0-9A-Fa-f]+$/sm ) { | 
| 481 | 3 |  |  |  |  | 10 | return bless( [ Type::Parser::HEXNUM, $spelling ], "Type::Parser::Token" ); | 
| 482 |  |  |  |  |  |  | } | 
| 483 |  |  |  |  |  |  | elsif ( looks_like_number( $spelling ) ) { | 
| 484 | 5 |  |  |  |  | 20 | return bless( [ Type::Parser::STRING, $spelling ], "Type::Parser::Token" ); | 
| 485 |  |  |  |  |  |  | } | 
| 486 |  |  |  |  |  |  | elsif ( $self->{remaining} =~ /^\s*=>/sm )    # peek ahead | 
| 487 |  |  |  |  |  |  | { | 
| 488 | 4 |  |  |  |  | 13 | return bless( [ Type::Parser::STRING, $spelling ], "Type::Parser::Token" ); | 
| 489 |  |  |  |  |  |  | } | 
| 490 |  |  |  |  |  |  | elsif ( $spelling eq "slurpy" ) { | 
| 491 | 2 |  |  |  |  | 7 | return $punctuation{$spelling}; | 
| 492 |  |  |  |  |  |  | } | 
| 493 |  |  |  |  |  |  |  | 
| 494 | 447 |  |  |  |  | 1882 | return bless( [ Type::Parser::TYPE, $spelling ], "Type::Parser::Token" ); | 
| 495 |  |  |  |  |  |  | } #/ if ( $self->{remaining...}) | 
| 496 |  |  |  |  |  |  |  | 
| 497 | 3 |  |  |  |  | 11 | my $rest = $self->{remaining}; | 
| 498 | 3 |  |  |  |  | 8 | $self->{remaining} = ""; | 
| 499 | 3 |  |  |  |  | 13 | return bless( [ Type::Parser::MYSTERY, $rest ], "Type::Parser::Token" ); | 
| 500 |  |  |  |  |  |  | } #/ sub _read_token | 
| 501 |  |  |  |  |  |  | } | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | 1; | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | __END__ |