| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Salvation::Method::Signatures; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | =head1 NAME | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | Salvation::Method::Signatures - Реализация сигнатур для методов | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | package Some::Package; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | use Salvation::Method::Signatures; | 
| 12 |  |  |  |  |  |  | # use Test::More tests => 3; | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | method process( ArrayRef[ArrayRef[Str]] :flags!, ArrayRef[HashRef(Int :id!)] :data! ) { | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | # isa_ok( $self, 'Some::Package' ); | 
| 17 |  |  |  |  |  |  | # is_deeply( $flags, [ [ 'something' ] ] ); | 
| 18 |  |  |  |  |  |  | # is_deeply( $data, [ { id => 1 } ] ); | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | ... | 
| 21 |  |  |  |  |  |  | } | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | package main; | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | Some::Package -> process( | 
| 26 |  |  |  |  |  |  | flags => [ [ 'something' ] ], | 
| 27 |  |  |  |  |  |  | data => [ { id => 1 } ], | 
| 28 |  |  |  |  |  |  | ); | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | Делает то же, что делают другие реализации сигнатур: проверяет тип аргументов | 
| 33 |  |  |  |  |  |  | метода, само разбирает C<@_> и инжектит переменные в блок. | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | http://perlcabal.org/syn/S06.html#Signatures | 
| 38 |  |  |  |  |  |  | L | 
| 39 |  |  |  |  |  |  | L | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | =cut | 
| 42 |  |  |  |  |  |  |  | 
| 43 | 1 |  |  | 1 |  | 610 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 31 |  | 
| 44 | 1 |  |  | 1 |  | 4 | use warnings; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 21 |  | 
| 45 | 1 |  |  | 1 |  | 432 | use boolean; | 
|  | 1 |  |  |  |  | 3521 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 46 |  |  |  |  |  |  |  | 
| 47 | 1 |  |  | 1 |  | 99 | use B (); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 28 |  | 
| 48 | 1 |  |  | 1 |  | 707 | use Module::Load 'load'; | 
|  | 1 |  |  |  |  | 956 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 49 | 1 |  |  | 1 |  | 574 | use Salvation::UpdateGvFLAGS (); | 
|  | 1 |  |  |  |  | 736 |  | 
|  | 1 |  |  |  |  | 26 |  | 
| 50 |  |  |  |  |  |  |  | 
| 51 | 1 |  |  | 1 |  | 5 | use base 'Devel::Declare::MethodInstaller::Simple'; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 9511 |  | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | our $VERSION = 0.03; | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | =head1 METHODS | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | =cut | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | =head2 type_system_class() | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | =cut | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | sub type_system_class { | 
| 64 |  |  |  |  |  |  |  | 
| 65 | 8 |  |  | 8 | 1 | 34 | return 'Salvation::TC'; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | =head2 token_str() | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | =cut | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | sub token_str { | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 1 |  |  | 1 | 1 | 10 | return 'method'; | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | =head2 self_var_name() | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | =cut | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | sub self_var_name { | 
| 82 |  |  |  |  |  |  |  | 
| 83 | 8 |  |  | 8 | 1 | 22 | return '$self'; | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | =head2 import() | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | Экспортирует магическое ключевое слово. | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | Подробнее: L. | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | =cut | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | sub import { | 
| 95 |  |  |  |  |  |  |  | 
| 96 | 1 |  |  | 1 |  | 11 | my ( $self ) = @_; | 
| 97 | 1 |  |  |  |  | 3 | my $caller = caller(); | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 1 |  |  |  |  | 4 | $self -> install_methodhandler( | 
| 100 |  |  |  |  |  |  | name => $self -> token_str(), | 
| 101 |  |  |  |  |  |  | into => $caller, | 
| 102 |  |  |  |  |  |  | ); | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 1 |  |  |  |  | 334 | return; | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | { | 
| 108 |  |  |  |  |  |  | my %installed_methods = (); | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | =head2 strip_name() | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | Обёртка вокруг L. | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | Делает всё то же самое, но дополнительно запоминает, в каком модуле какие | 
| 115 |  |  |  |  |  |  | методы были объявлены с использованием L. | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | Внутренний метод. | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | =cut | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | sub strip_name { | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 8 |  |  | 8 | 1 | 21909 | my ( $self, @rest ) = @_; | 
| 124 | 8 |  |  |  |  | 40 | my $name = $self -> SUPER::strip_name( @rest ); | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 8 |  |  |  |  | 232 | push( @{ $installed_methods{ $self -> { 'into' } } }, $name ); | 
|  | 8 |  |  |  |  | 28 |  | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 8 |  |  |  |  | 38 | return $name; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | =head2 mark_methods_as_not_imported( Str class ) | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | Маркирует все методы класса C, объявленные с использованием | 
| 134 |  |  |  |  |  |  | L, как "не импортированные". | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | Внутренний метод. | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | =cut | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | sub mark_methods_as_not_imported { | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 0 |  |  | 0 | 1 | 0 | my ( $self, $class ) = @_; | 
| 143 | 0 |  | 0 |  |  | 0 | my $imported_cv = ( eval { B::GVf_IMPORTED_CV() } || 0x80 ); | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 1 |  |  | 1 |  | 70823 | no strict 'refs'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 890 |  | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 0 |  | 0 |  |  | 0 | foreach my $method ( @{ $installed_methods{ $class } // [] } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 148 |  |  |  |  |  |  |  | 
| 149 | 0 |  |  |  |  | 0 | my $name = "${class}::${method}"; | 
| 150 | 0 |  |  |  |  | 0 | my $obj = B::svref_2object( \*$name ); | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 0 | 0 |  |  |  | 0 | if( $obj -> GvFLAGS() & $imported_cv ) { | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 0 |  |  |  |  | 0 | Salvation::UpdateGvFLAGS::toggle_glob_flag_by_name( $name, $imported_cv ); | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 0 |  |  |  |  | 0 | return; | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | =head2 parse_proto( Str $str ) | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | Разбирает прототип метода, генерирует код и инжектит этот код в метод. | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | Подробнее: L. | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | =cut | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | sub parse_proto { | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 8 |  |  | 8 | 1 | 409 | my ( $self, $str ) = @_; | 
| 173 | 8 |  |  |  |  | 32 | load my $type_system_class = $self -> type_system_class(); | 
| 174 | 8 |  |  |  |  | 141016 | my $type_parser = $type_system_class -> get_type_parser(); | 
| 175 | 8 | 100 |  |  |  | 102 | my $sig = ( ( $str =~ m/^\s*$/ ) ? [] : $type_parser -> tokenize_signature_str( "(${str})", {} ) ); | 
| 176 |  |  |  |  |  |  |  | 
| 177 | 8 |  |  |  |  | 2872 | my @positional_vars = ( $self -> self_var_name() ); | 
| 178 | 8 |  |  |  |  | 13 | my $code = ''; | 
| 179 | 8 |  |  |  |  | 13 | my $pos  = 0; | 
| 180 | 8 |  |  |  |  | 22 | my $prev_was_optional = false; | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | my $wrap_check = sub { | 
| 183 |  |  |  |  |  |  |  | 
| 184 | 15 |  |  | 15 |  | 21 | my ( $code, $param_name ) = @_; | 
| 185 |  |  |  |  |  |  |  | 
| 186 | 15 |  |  |  |  | 50 | return sprintf( | 
| 187 |  |  |  |  |  |  | '( eval{ local $Carp::CarpLevel = 2; %s } || die( "Validation for parameter \"%s\" failed because:\n$@" ) )', | 
| 188 |  |  |  |  |  |  | $code, | 
| 189 |  |  |  |  |  |  | $param_name, | 
| 190 |  |  |  |  |  |  | ); | 
| 191 | 8 |  |  |  |  | 56 | }; | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 8 |  |  |  |  | 32 | while( defined( my $item = shift( @$sig ) ) ) { | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 13 | 100 |  |  |  | 38 | if( $item -> { 'param' } -> { 'named' } ) { | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 3 | 50 |  |  |  | 24 | if( $prev_was_optional ) { | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 0 |  |  |  |  | 0 | die( "Error at signature (${str}): named parameter can't follow optional positional parameter" ); | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 | 3 |  |  |  |  | 22 | unshift( @$sig, $item ); | 
| 203 | 3 |  |  |  |  | 9 | last; | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 | 10 |  |  |  |  | 33 | my $type = $type_system_class -> materialize_type( $item -> { 'type' } ); | 
| 207 | 10 |  |  |  |  | 172 | my $arg  = $item -> { 'param' }; | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 10 |  |  |  |  | 21 | my $var = sprintf( '$%s', $arg -> { 'name' } ); | 
| 210 |  |  |  |  |  |  |  | 
| 211 | 10 |  |  |  |  | 15 | push( @positional_vars, $var ); | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 10 |  |  |  |  | 32 | my $check = sprintf( '%s -> assert( %s, \'%s\' )', $type_system_class, $var, $type -> name() ); | 
| 214 |  |  |  |  |  |  |  | 
| 215 | 10 |  |  |  |  | 53 | $check = $wrap_check -> ( $check, $arg -> { 'name' } ); | 
| 216 |  |  |  |  |  |  |  | 
| 217 | 10 | 100 |  |  |  | 34 | if( $arg -> { 'optional' } ) { | 
|  |  | 50 |  |  |  |  |  | 
| 218 |  |  |  |  |  |  |  | 
| 219 | 3 |  |  |  |  | 20 | $prev_was_optional = true; | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 3 |  |  |  |  | 16 | $check = sprintf( '( ( scalar( @_ ) > %d ) ? %s : 1 )', 1 + $pos, $check ); | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | } elsif( $prev_was_optional ) { | 
| 224 |  |  |  |  |  |  |  | 
| 225 | 0 |  |  |  |  | 0 | die( "Error at signature (${str}): required positional parameter can't follow optional one" ); | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 10 |  |  |  |  | 49 | $code .= $check; | 
| 229 | 10 |  |  |  |  | 12 | $code .= ';'; | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 10 |  |  |  |  | 23 | $type_system_class -> get( $type -> name() ); # прогрев кэша | 
| 232 |  |  |  |  |  |  |  | 
| 233 | 10 |  |  |  |  | 414 | ++$pos; | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 | 8 |  |  |  |  | 13 | my @named_vars   = (); | 
| 237 | 8 |  |  |  |  | 10 | my @named_params = (); | 
| 238 | 8 |  |  |  |  | 14 | my $named_checks = ''; | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 8 |  |  |  |  | 19 | while( defined( my $item = shift( @$sig ) ) ) { | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 5 | 50 |  |  |  | 14 | if( $item -> { 'param' } -> { 'positional' } ) { | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 0 |  |  |  |  | 0 | die( "Error at signature (${str}): positional parameter can't follow named parameter" ); | 
| 245 |  |  |  |  |  |  | } | 
| 246 |  |  |  |  |  |  |  | 
| 247 | 5 |  |  |  |  | 22 | my $type = $type_system_class -> materialize_type( $item -> { 'type' } ); | 
| 248 | 5 |  |  |  |  | 116 | my $arg  = $item -> { 'param' }; | 
| 249 |  |  |  |  |  |  |  | 
| 250 | 5 |  |  |  |  | 17 | push( @named_vars, sprintf( '$%s', $arg -> { 'name' } ) ); | 
| 251 | 5 |  |  |  |  | 16 | push( @named_params, sprintf( '\'%s\'', $arg -> { 'name' } ) ); | 
| 252 |  |  |  |  |  |  |  | 
| 253 | 5 |  |  |  |  | 21 | my $check = sprintf( '%s -> assert( $args{ \'%s\' }, \'%s\' )', $type_system_class, $arg -> { 'name' }, $type -> name() ); | 
| 254 |  |  |  |  |  |  |  | 
| 255 | 5 |  |  |  |  | 38 | $check = $wrap_check -> ( $check, $arg -> { 'name' } ); | 
| 256 |  |  |  |  |  |  |  | 
| 257 | 5 | 100 |  |  |  | 17 | if( $arg -> { 'optional' } ) { | 
| 258 |  |  |  |  |  |  |  | 
| 259 | 3 |  |  |  |  | 25 | $prev_was_optional = true; | 
| 260 |  |  |  |  |  |  |  | 
| 261 | 3 |  |  |  |  | 20 | $check = sprintf( '( exists( $args{ \'%s\' } ) ? %s : 1 )', $arg -> { 'name' }, $check ); | 
| 262 |  |  |  |  |  |  | } | 
| 263 |  |  |  |  |  |  |  | 
| 264 | 5 |  |  |  |  | 12 | $named_checks .= $check; | 
| 265 | 5 |  |  |  |  | 27 | $named_checks .= ';'; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 8 | 100 |  |  |  | 33 | my $named_vars_code = ( $named_checks ? sprintf( '( my ( %s ) = do { | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | no warnings \'syntax\'; | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | my %%args = @_[ %d .. $#_ ]; %s @args{ %s }; | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | } );', join( ', ', @named_vars ), scalar( @positional_vars ), $named_checks, join( ', ', @named_params ) ) : '' ); | 
| 275 |  |  |  |  |  |  |  | 
| 276 | 8 |  |  |  |  | 31 | $code = sprintf( 'my ( %s ) = @_; %s %s local @_ = ();', join( ', ', @positional_vars ), $code, $named_vars_code ); | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 8 |  |  |  |  | 33 | $code =~ s/\n/ /g; | 
| 279 | 8 |  |  |  |  | 165 | $code =~ s/\s{2,}/ /g; | 
| 280 |  |  |  |  |  |  |  | 
| 281 | 8 |  |  |  |  | 71 | return $code; | 
| 282 |  |  |  |  |  |  | } | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | 1; | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | __END__ |