0x1949 Team - FAZEMRX - MANAGER
Edit File: Parser.pm
package Type::Parser; use strict; use warnings; sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak } our $AUTHORITY = 'cpan:TOBYINK'; our $VERSION = '1.012004'; $VERSION =~ tr/_//d; # Token types # sub TYPE () { "TYPE" } sub QUOTELIKE () { "QUOTELIKE" } sub STRING () { "STRING" } sub HEXNUM () { "HEXNUM" } sub CLASS () { "CLASS" } sub L_BRACKET () { "L_BRACKET" } sub R_BRACKET () { "R_BRACKET" } sub COMMA () { "COMMA" } sub SLURPY () { "SLURPY" } sub UNION () { "UNION" } sub INTERSECT () { "INTERSECT" } sub NOT () { "NOT" } sub L_PAREN () { "L_PAREN" } sub R_PAREN () { "R_PAREN" } sub MYSTERY () { "MYSTERY" } our @EXPORT_OK = qw( eval_type _std_eval parse extract_type ); require Exporter::Tiny; our @ISA = 'Exporter::Tiny'; Evaluate: { sub parse { my $str = $_[0]; my $parser = "Type::Parser::AstBuilder"->new( input => $str ); $parser->build; wantarray ? ( $parser->ast, $parser->remainder ) : $parser->ast; } sub extract_type { my ( $str, $reg ) = @_; my ( $parsed, $tail ) = parse( $str ); wantarray ? ( _eval_type( $parsed, $reg ), $tail ) : _eval_type( $parsed, $reg ); } sub eval_type { my ( $str, $reg ) = @_; my ( $parsed, $tail ) = parse( $str ); _croak( "Unexpected tail on type expression: $tail" ) if $tail =~ /\S/sm; return _eval_type( $parsed, $reg ); } my $std; sub _std_eval { require Type::Registry; unless ( $std ) { $std = "Type::Registry"->new; $std->add_types( -Standard ); } eval_type( $_[0], $std ); } sub _eval_type { my ( $node, $reg ) = @_; $node = _simplify_expression( $node ); if ( $node->{type} eq "list" ) { return map _eval_type( $_, $reg ), @{ $node->{list} }; } if ( $node->{type} eq "union" ) { return $reg->make_union( map _eval_type( $_, $reg ), @{ $node->{union} } ); } if ( $node->{type} eq "intersect" ) { return $reg->make_intersection( map _eval_type( $_, $reg ), @{ $node->{intersect} } ); } if ( $node->{type} eq "slurpy" ) { return +{ slurpy => _eval_type( $node->{of}, $reg ) }; } if ( $node->{type} eq "complement" ) { return _eval_type( $node->{of}, $reg )->complementary_type; } if ( $node->{type} eq "parameterized" ) { my $base = _eval_type( $node->{base}, $reg ); return $base unless $base->is_parameterizable || $node->{params}; return $base->parameterize( $node->{params} ? _eval_type( $node->{params}, $reg ) : () ); } if ( $node->{type} eq "primary" and $node->{token}->type eq CLASS ) { my $class = substr( $node->{token}->spelling, 0, length( $node->{token}->spelling ) - 2 ); return $reg->make_class_type( $class ); } if ( $node->{type} eq "primary" and $node->{token}->type eq QUOTELIKE ) { return eval( $node->{token}->spelling ); #ARGH } if ( $node->{type} eq "primary" and $node->{token}->type eq STRING ) { return $node->{token}->spelling; } if ( $node->{type} eq "primary" and $node->{token}->type eq HEXNUM ) { my $sign = '+'; my $spelling = $node->{token}->spelling; if ( $spelling =~ /^[+-]/ ) { $sign = substr( $spelling, 0, 1); $spelling = substr( $spelling, 1 ); } return ( ( $sign eq '-' ) ? ( 0 - hex($spelling) ) : hex($spelling) ); } if ( $node->{type} eq "primary" and $node->{token}->type eq TYPE ) { my $t = $node->{token}->spelling; my $r = ( $t =~ /^(.+)::(\w+)$/ ) ? $reg->foreign_lookup( $t, 1 ) : $reg->simple_lookup( $t, 1 ); $r or _croak( "%s is not a known type constraint", $node->{token}->spelling ); return $r; } } #/ sub _eval_type sub _simplify_expression { my $expr = shift; if ( $expr->{type} eq "expression" and $expr->{op}[0] eq COMMA ) { return _simplify( "list", COMMA, $expr ); } if ( $expr->{type} eq "expression" and $expr->{op}[0] eq UNION ) { return _simplify( "union", UNION, $expr ); } if ( $expr->{type} eq "expression" and $expr->{op}[0] eq INTERSECT ) { return _simplify( "intersect", INTERSECT, $expr ); } return $expr; } #/ sub _simplify_expression sub _simplify { no warnings 'recursion'; my $type = shift; my $op = shift; my @list; for my $expr ( $_[0]{lhs}, $_[0]{rhs} ) { if ( $expr->{type} eq "expression" and $expr->{op}[0] eq $op ) { my $simple = _simplify( $type, $op, $expr ); push @list, @{ $simple->{$type} }; } else { push @list, $expr; } } return { type => $type, $type => \@list }; } #/ sub _simplify } #/ Evaluate: { package Type::Parser::AstBuilder; sub new { my $class = shift; bless {@_}, $class; } our %precedence = ( # Type::Parser::COMMA() , 1 , Type::Parser::UNION(), 2, Type::Parser::INTERSECT(), 3, Type::Parser::NOT(), 4, ); sub _parse_primary { my $self = shift; my $tokens = $self->{tokens}; $tokens->assert_not_empty; if ( $tokens->peek( 0 )->type eq Type::Parser::NOT ) { $tokens->eat( Type::Parser::NOT ); $tokens->assert_not_empty; return { type => "complement", of => $self->_parse_primary, }; } if ( $tokens->peek( 0 )->type eq Type::Parser::SLURPY ) { $tokens->eat( Type::Parser::SLURPY ); $tokens->assert_not_empty; return { type => "slurpy", of => $self->_parse_primary, }; } if ( $tokens->peek( 0 )->type eq Type::Parser::L_PAREN ) { $tokens->eat( Type::Parser::L_PAREN ); my $r = $self->_parse_expression; $tokens->eat( Type::Parser::R_PAREN ); return $r; } if ( $tokens->peek( 1 ) and $tokens->peek( 0 )->type eq Type::Parser::TYPE and $tokens->peek( 1 )->type eq Type::Parser::L_BRACKET ) { my $base = { type => "primary", token => $tokens->eat( Type::Parser::TYPE ) }; $tokens->eat( Type::Parser::L_BRACKET ); $tokens->assert_not_empty; local $precedence{ Type::Parser::COMMA() } = 1; my $params = undef; if ( $tokens->peek( 0 )->type eq Type::Parser::R_BRACKET ) { $tokens->eat( Type::Parser::R_BRACKET ); } else { $params = $self->_parse_expression; $params = { type => "list", list => [$params] } unless $params->{type} eq "list"; $tokens->eat( Type::Parser::R_BRACKET ); } return { type => "parameterized", base => $base, params => $params, }; } #/ if ( $tokens->peek( 1 ...)) my $type = $tokens->peek( 0 )->type; if ( $type eq Type::Parser::TYPE or $type eq Type::Parser::QUOTELIKE or $type eq Type::Parser::STRING or $type eq Type::Parser::HEXNUM or $type eq Type::Parser::CLASS ) { return { type => "primary", token => $tokens->eat }; } Type::Parser::_croak( "Unexpected token in primary type expression; got '%s'", $tokens->peek( 0 )->spelling ); } #/ sub _parse_primary sub _parse_expression_1 { my $self = shift; my $tokens = $self->{tokens}; my ( $lhs, $min_p ) = @_; while ( !$tokens->empty and defined( $precedence{ $tokens->peek( 0 )->type } ) and $precedence{ $tokens->peek( 0 )->type } >= $min_p ) { my $op = $tokens->eat; my $rhs = $self->_parse_primary; while ( !$tokens->empty and defined( $precedence{ $tokens->peek( 0 )->type } ) and $precedence{ $tokens->peek( 0 )->type } > $precedence{ $op->type } ) { my $lookahead = $tokens->peek( 0 ); $rhs = $self->_parse_expression_1( $rhs, $precedence{ $lookahead->type } ); } $lhs = { type => "expression", op => $op, lhs => $lhs, rhs => $rhs, }; } #/ while ( !$tokens->empty and...) return $lhs; } #/ sub _parse_expression_1 sub _parse_expression { my $self = shift; my $tokens = $self->{tokens}; return $self->_parse_expression_1( $self->_parse_primary, 0 ); } sub build { my $self = shift; $self->{tokens} = "Type::Parser::TokenStream"->new( remaining => $self->{input} ); $self->{ast} = $self->_parse_expression; } sub ast { $_[0]{ast}; } sub remainder { $_[0]{tokens}->remainder; } } { package Type::Parser::Token; sub type { $_[0][0] } sub spelling { $_[0][1] } } { package Type::Parser::TokenStream; use Scalar::Util qw(looks_like_number); sub new { my $class = shift; bless { stack => [], done => [], @_ }, $class; } sub peek { my $self = shift; my $ahead = $_[0]; while ( $self->_stack_size <= $ahead and length $self->{remaining} ) { $self->_stack_extend; } my @tokens = grep ref, @{ $self->{stack} }; return $tokens[$ahead]; } #/ sub peek sub empty { my $self = shift; not $self->peek( 0 ); } sub eat { my $self = shift; $self->_stack_extend unless $self->_stack_size; my $r; while ( defined( my $item = shift @{ $self->{stack} } ) ) { push @{ $self->{done} }, $item; if ( ref $item ) { $r = $item; last; } } if ( @_ and $_[0] ne $r->type ) { unshift @{ $self->{stack} }, pop @{ $self->{done} }; # uncoverable statement Type::Parser::_croak( "Expected $_[0]; got " . $r->type ); # uncoverable statement } return $r; } #/ sub eat sub assert_not_empty { my $self = shift; Type::Parser::_croak( "Expected token; got empty string" ) if $self->empty; } sub _stack_size { my $self = shift; scalar grep ref, @{ $self->{stack} }; } sub _stack_extend { my $self = shift; push @{ $self->{stack} }, $self->_read_token; my ( $space ) = ( $self->{remaining} =~ m/^([\s\n\r]*)/sm ); return unless length $space; push @{ $self->{stack} }, $space; substr( $self->{remaining}, 0, length $space ) = ""; } sub remainder { my $self = shift; return join "", map { ref( $_ ) ? $_->spelling : $_ } ( @{ $self->{stack} }, $self->{remaining} ); } my %punctuation = ( '[' => bless( [ Type::Parser::L_BRACKET, "[" ], "Type::Parser::Token" ), ']' => bless( [ Type::Parser::R_BRACKET, "]" ], "Type::Parser::Token" ), '(' => bless( [ Type::Parser::L_PAREN, "[" ], "Type::Parser::Token" ), ')' => bless( [ Type::Parser::R_PAREN, "]" ], "Type::Parser::Token" ), ',' => bless( [ Type::Parser::COMMA, "," ], "Type::Parser::Token" ), '=>' => bless( [ Type::Parser::COMMA, "=>" ], "Type::Parser::Token" ), 'slurpy' => bless( [ Type::Parser::SLURPY, "slurpy" ], "Type::Parser::Token" ), '|' => bless( [ Type::Parser::UNION, "|" ], "Type::Parser::Token" ), '&' => bless( [ Type::Parser::INTERSECT, "&" ], "Type::Parser::Token" ), '~' => bless( [ Type::Parser::NOT, "~" ], "Type::Parser::Token" ), ); sub _read_token { my $self = shift; return if $self->{remaining} eq ""; # Punctuation # if ( $self->{remaining} =~ /^( => | [()\]\[|&~,] )/xsm ) { my $spelling = $1; substr( $self->{remaining}, 0, length $spelling ) = ""; return $punctuation{$spelling}; } if ( $self->{remaining} =~ /\A\s*[q'"]/sm ) { require Text::Balanced; if ( my $quotelike = Text::Balanced::extract_quotelike( $self->{remaining} ) ) { return bless( [ Type::Parser::QUOTELIKE, $quotelike ], "Type::Parser::Token" ); } } if ( $self->{remaining} =~ /^([+-]?[\w:.+]+)/sm ) { my $spelling = $1; substr( $self->{remaining}, 0, length $spelling ) = ""; if ( $spelling =~ /::$/sm ) { return bless( [ Type::Parser::CLASS, $spelling ], "Type::Parser::Token" ); } elsif ( $spelling =~ /^[+-]?0x[0-9A-Fa-f]+$/sm ) { return bless( [ Type::Parser::HEXNUM, $spelling ], "Type::Parser::Token" ); } elsif ( looks_like_number( $spelling ) ) { return bless( [ Type::Parser::STRING, $spelling ], "Type::Parser::Token" ); } elsif ( $self->{remaining} =~ /^\s*=>/sm ) # peek ahead { return bless( [ Type::Parser::STRING, $spelling ], "Type::Parser::Token" ); } elsif ( $spelling eq "slurpy" ) { return $punctuation{$spelling}; } return bless( [ Type::Parser::TYPE, $spelling ], "Type::Parser::Token" ); } #/ if ( $self->{remaining...}) my $rest = $self->{remaining}; $self->{remaining} = ""; return bless( [ Type::Parser::MYSTERY, $rest ], "Type::Parser::Token" ); } #/ sub _read_token } 1; __END__ =pod =encoding utf-8 =for stopwords non-whitespace =head1 NAME Type::Parser - parse type constraint strings =head1 SYNOPSIS use v5.10; use strict; use warnings; use Type::Parser qw( eval_type ); use Type::Registry; my $reg = Type::Registry->for_me; $reg->add_types("Types::Standard"); my $type = eval_type("Int | ArrayRef[Int]", $reg); $type->check(10); # true $type->check([1..4]); # true $type->check({foo=>1}); # false =head1 STATUS This module is covered by the L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">. =head1 DESCRIPTION Generally speaking, you probably don't want to be using this module directly. Instead use the C<< lookup >> method from L<Type::Registry> which wraps it. =head2 Functions =over =item C<< parse($string) >> Parse the type constraint string into something like an AST. If called in list context, also returns any "tail" found on the original string. =item C<< extract_type($string, $registry) >> Compile a type constraint string into a L<Type::Tiny> object. If called in list context, also returns any "tail" found on the original string. =item C<< eval_type($string, $registry) >> Compile a type constraint string into a L<Type::Tiny> object. Throws an error if the "tail" contains any non-whitespace character. =back =head2 Constants The following constants correspond to values returned by C<< $token->type >>. =over =item C<< TYPE >> =item C<< QUOTELIKE >> =item C<< STRING >> =item C<< HEXNUM >> =item C<< CLASS >> =item C<< L_BRACKET >> =item C<< R_BRACKET >> =item C<< COMMA >> =item C<< SLURPY >> =item C<< UNION >> =item C<< INTERSECT >> =item C<< NOT >> =item C<< L_PAREN >> =item C<< R_PAREN >> =item C<< MYSTERY >> =back =head1 BUGS Please report any bugs to L<https://github.com/tobyink/p5-type-tiny/issues>. =head1 SEE ALSO L<Type::Registry>. =head1 AUTHOR Toby Inkster E<lt>tobyink@cpan.orgE<gt>. =head1 COPYRIGHT AND LICENCE This software is copyright (c) 2013-2014, 2017-2021 by Toby Inkster. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =head1 DISCLAIMER OF WARRANTIES THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.