File Coverage

blib/lib/Salvation/Method/Signatures.pm
Criterion Covered Total %
statement 86 98 87.7
branch 13 18 72.2
condition 0 4 0.0
subroutine 15 16 93.7
pod 6 6 100.0
total 120 142 84.5


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__