File Coverage

blib/lib/Function/Parameters.pm
Criterion Covered Total %
statement 202 214 94.3
branch 88 110 80.0
condition 12 16 75.0
subroutine 31 33 93.9
pod 0 1 0.0
total 333 374 89.0


line stmt bran cond sub pod time code
1             package Function::Parameters;
2              
3 49     49   3185412 use v5.14.0;
  49         598  
4 49     49   291 use warnings;
  49         98  
  49         1437  
5 49     49   287 use warnings::register;
  49         92  
  49         7358  
6              
7 49     49   356 use Carp qw(croak confess);
  49         109  
  49         2865  
8 49     49   316 use Scalar::Util qw(blessed);
  49         121  
  49         5409  
9              
10             sub _croak {
11 251     251   284433 my (undef, $file, $line) = caller 1;
12 251         1964 die @_, " at $file line $line.\n";
13             }
14              
15 49     49   368 use XSLoader;
  49         103  
  49         2106  
16             BEGIN {
17 49     49   162 our $VERSION = '2.002003';
18             #$VERSION =~ s/-TRIAL[0-9]*\z//;
19 49         56928 XSLoader::load;
20             }
21              
22             sub _warn_config_not_a_reference {
23 2     2   2020 warnings::warnif sprintf q{%s: $^H{'%s'} is not a reference; skipping: %s}, __PACKAGE__, HINTK_CONFIG, $^H{+HINTK_CONFIG};
24             }
25              
26             sub _assert_valid_identifier {
27 278     278   618 my ($name, $with_dollar) = @_;
28 278 100       627 my $bonus = $with_dollar ? '\$' : '';
29 278 100       11630 $name =~ /\A${bonus}[^\W\d]\w*\z/
30             or confess qq{"$name" doesn't look like a valid identifier};
31             }
32              
33             sub _assert_valid_attributes {
34 78     78   173 my ($attrs) = @_;
35 78 100       1102 $attrs =~ m{
36             \A \s*+
37             : \s*+
38             (?&ident) (?! [^\s:(] ) (?¶m)?+ \s*+
39             (?:
40             (?: : \s*+ )?
41             (?&ident) (?! [^\s:(] ) (?¶m)?+ \s*+
42             )*+
43             \z
44              
45             (?(DEFINE)
46             (?
47             [^\W\d]
48             \w*+
49             )
50             (?
51             \(
52             [^()\\]*+
53             (?:
54             (?:
55             \\ .
56             |
57             (?¶m)
58             )
59             [^()\\]*+
60             )*+
61             \)
62             )
63             )
64             }sx or confess qq{"$attrs" doesn't look like valid attributes};
65             }
66              
67             sub _reify_type_moose {
68 0     0   0 require Moose::Util::TypeConstraints;
69 0         0 Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_[0])
70             }
71              
72             sub _malformed_type {
73 0     0   0 my ($type, $msg) = @_;
74 0         0 my $pos = pos $_[0];
75 0         0 substr $type, $pos, 0, ' <-- HERE ';
76 0         0 croak "Malformed type: $msg marked by <-- HERE in '$type'";
77             }
78              
79             sub _reify_type_auto_parameterized {
80             # (str, caller)
81 72 50   72   287 $_[0] =~ /\G ( \w+ (?: :: \w+)* ) \s* /xgc or _malformed_type $_[0], "missing type name";
82 72         161 my $name = $1;
83 72 50       207 $name = "$_[1]::$name" unless $name =~ /::/;
84 72         99 my $fun = do {
85 49     49   407 no strict 'refs';
  49         101  
  49         133405  
86 72 100       667 defined &$name or croak "Undefined type name $name";
87 70         146 \&$name
88             };
89              
90 70 100       207 $_[0] =~ /\G \[ \s* /xgc
91             or return $fun;
92              
93 8         11 my @args;
94 8         15 until ($_[0] =~ /\G \] \s* /xgc) {
95 16 100 33     47 $_[0] =~ /\G , \s* /xgc or _malformed_type $_[0], "missing ',' or ']'"
96             if @args;
97 16         31 push @args, &_reify_type_auto_union;
98             }
99              
100 8     8   36 sub { $fun->([map $_->(), @args]) }
101 8         40 }
102              
103             sub _reify_type_auto_term {
104             # (str, caller)
105 76     76   108 my $compl = 0;
106 76         194 while ($_[0] =~ /\G ~ \s* /xgc) {
107 8         17 $compl++;
108             }
109              
110 76         101 my $inner;
111 76 100       150 if ($_[0] =~ /\G \( \s* /xgc) {
112 4         8 $inner = &_reify_type_auto_union;
113 4 50       14 $_[0] =~ /\G \) \s* /xgc or _malformed_type $_[0], "missing ')'";
114             } else {
115 72         126 $inner = &_reify_type_auto_parameterized;
116             }
117              
118             !$compl
119             ? $inner
120             : sub {
121 4     4   113 my $t = $inner->();
122 4         41 for my $i (1 .. $compl) {
123 8         52 $t = ~$t;
124             }
125             $t
126 4         46 }
127 74 100       179 }
128              
129             sub _reify_type_auto_alternative {
130             # (str, caller)
131 72     72   128 my $fun = &_reify_type_auto_term;
132 70         170 while ($_[0] =~ m!\G / \s* !xgc) {
133 4         16 my $right = &_reify_type_auto_term;
134 4         6 my $left = $fun;
135 4     4   17 $fun = sub { $left->() / $right->() };
  4         156  
136             }
137             $fun
138 70         129 }
139              
140             sub _reify_type_auto_intersection {
141             # (str, caller)
142 64     64   125 my $fun = &_reify_type_auto_alternative;
143 62         150 while ($_[0] =~ /\G & \s* /xgc) {
144 8         16 my $right = &_reify_type_auto_alternative;
145 8         13 my $left = $fun;
146 8     8   32 $fun = sub { $left->() & $right->() };
  8         141  
147             }
148             $fun
149 62         95 }
150              
151             sub _reify_type_auto_union {
152             # (str, caller)
153 50     50   98 my $fun = &_reify_type_auto_intersection;
154 48         111 while ($_[0] =~ /\G \| \s* /xgc) {
155 14         31 my $right = &_reify_type_auto_intersection;
156 14         17 my $left = $fun;
157 14     14   62 $fun = sub { $left->() | $right->() };
  14         225  
158             }
159             $fun
160 48         107 }
161              
162             sub _reify_type_auto {
163 30     30   71037 my ($type) = @_;
164 30         65 my $caller = caller;
165              
166 30         99 $type =~ /\G \s+ /xgc;
167 30         102 my $tfun = _reify_type_auto_union $type, $caller;
168 28 50       92 $type =~ /\G \z/xgc or _malformed_type $type, "trailing garbage";
169 28         1529 $tfun->()
170             }
171              
172             sub _delete_default {
173 1176     1176   1984 my ($href, $key, $default) = @_;
174 1176 100       2763 exists $href->{$key} ? delete $href->{$key} : $default
175             }
176              
177             sub _find_or_add_idx {
178 1     1   3 my ($array, $x) = @_;
179 1         2 my $index;
180 1         4 for my $i (0 .. $#$array) {
181 0 0       0 if ($array->[$i] == $x) {
182 0         0 $index = $i;
183 0         0 last;
184             }
185             }
186 1 50       2 unless (defined $index) {
187 1         2 $index = @$array;
188 1         3 push @$array, $x;
189             }
190             $index
191 1         2 }
192              
193             my %type_map = (
194             function_strict => {},
195             function_lax => {
196             defaults => 'function_strict',
197             strict => 0,
198             },
199             function => { defaults => 'function_strict' },
200              
201             method_strict => {
202             defaults => 'function_strict',
203             attributes => ':method',
204             shift => '$self',
205             invocant => 1,
206             },
207             method_lax => {
208             defaults => 'method_strict',
209             strict => 0,
210             },
211             method => { defaults => 'method_strict' },
212              
213             classmethod_strict => {
214             defaults => 'method_strict',
215             shift => '$class',
216             },
217             classmethod_lax => {
218             defaults => 'classmethod_strict',
219             strict => 0,
220             },
221             classmethod => { defaults => 'classmethod_strict' },
222              
223             around => {
224             defaults => 'method',
225             name => 'required',
226             install_sub => 'around',
227             shift => ['$orig', '$self'],
228             runtime => 1,
229             },
230             (
231             map +(
232             $_ => {
233             defaults => 'method',
234             name => 'required',
235             install_sub => $_,
236             runtime => 1,
237             }
238             ), qw(
239             before after augment override
240             ),
241             ),
242             );
243              
244             my %import_map = (
245             fun => 'function',
246             (
247             map +($_ => $_),
248             qw(
249             method
250             classmethod
251             before
252             after
253             around
254             augment
255             override
256             )
257             ),
258              
259             ':strict' => {
260             fun => 'function_strict',
261             method => 'method_strict',
262             },
263              
264             ':lax' => {
265             fun => 'function_lax',
266             method => 'method_lax',
267             },
268              
269             ':std' => [qw(fun method)],
270             ':modifiers' => [qw(
271             before
272             after
273             around
274             augment
275             override
276             )],
277             );
278             for my $v (values %import_map) {
279             if (ref $v eq 'ARRAY') {
280             $v = {
281             map +($_ => $import_map{$_} || die "Internal error: $v => $_"),
282             @$v
283             };
284             }
285             }
286              
287             sub import {
288 96     96   25707 my $class = shift;
289              
290 96         182 my %imports;
291 96 100       329 @_ = qw(:std) if !@_;
292 96         222 for my $item (@_) {
293 102         171 my $part;
294 102 100       293 if (ref $item) {
295 51         89 $part = $item;
296             } else {
297 51 100       417 my $type = $import_map{$item}
298             or croak qq{"$item" is not exported by the $class module};
299 49 100       173 $part = ref $type
300             ? $type
301             : { $item => $type };
302             }
303 100         508 @imports{keys %$part} = values %$part;
304             }
305              
306 94         172 my %spec;
307              
308 94         361 for my $name (sort keys %imports) {
309 176         499 _assert_valid_identifier $name;
310 171         493 my $proto_type = $imports{$name};
311              
312 171 100       563 $proto_type = {defaults => $proto_type} unless ref $proto_type;
313              
314 171         652 my %type = %$proto_type;
315 171         548 while (my $defaults = delete $type{defaults}) {
316 345 100       854 my $base = $type_map{$defaults}
317 1         200 or confess qq["$defaults" doesn't look like a valid type (one of ${\join ', ', sort keys %type_map})];
318 344         1551 %type = (%$base, %type);
319             }
320              
321 170 100       786 if (exists $type{strict}) {
322 21   66     133 $type{check_argument_count} ||= $type{strict};
323 21         38 delete $type{strict};
324             }
325              
326 170         262 my %clean;
327              
328 170   100     753 $clean{name} = delete $type{name} // 'optional';
329 170 50       832 $clean{name} =~ /\A(?:optional|required|prohibited)\z/
330             or confess qq["$clean{name}" doesn't look like a valid name attribute (one of optional, required, prohibited)];
331              
332 170   100     581 $clean{attrs} = delete $type{attributes} // '';
333 170 100       512 _assert_valid_attributes $clean{attrs} if $clean{attrs};
334              
335 168 100       408 if (!exists $type{reify_type}) {
336 164         372 $clean{reify_type} = \&_reify_type_auto;
337             } else {
338 4   50     14 my $rt = delete $type{reify_type} // '(undef)';
339 4 50       19 if (!ref $rt) {
    50          
340 0 0       0 $rt =
    0          
341             $rt eq 'auto' ? \&_reify_type_auto :
342             $rt eq 'moose' ? \&_reify_type_moose :
343             confess qq{"$rt" isn't a known predefined type reifier};
344             } elsif (ref $rt ne 'CODE') {
345 0         0 confess qq{"$rt" doesn't look like a type reifier};
346             }
347              
348 4         8 $clean{reify_type} = $rt;
349             }
350              
351 168 100       360 if (!exists $type{install_sub}) {
352 150         287 $clean{install_sub} = '';
353             } else {
354 18         31 my $is = delete $type{install_sub};
355 18 100       38 if (!ref $is) {
    50          
356 17         38 _assert_valid_identifier $is;
357             } elsif (ref $is ne 'CODE') {
358 0         0 confess qq{"$is" doesn't look like a sub installer};
359             }
360              
361 18         50 $clean{install_sub} = $is;
362             }
363              
364 168         233 $clean{shift} = do {
365 168   100     543 my $shift = delete $type{shift} // [];
366 168 100       502 $shift = [$shift] if !ref $shift;
367 168         309 my $str = '';
368 168         425 my @shifty_types;
369 168         471 for my $item (@$shift) {
370 85         153 my ($name, $type);
371 85 100       238 if (ref $item) {
372 1 50       3 @$item == 2 or confess "A 'shift' item must have 2 elements, not " . @$item;
373 1         2 ($name, $type) = @$item;
374             } else {
375 84         190 $name = $item;
376             }
377 85         233 _assert_valid_identifier $name, 1;
378 85 50       375 $name eq '$_' and confess q[Using "$_" as a parameter is not supported];
379 85         205 $str .= $name;
380 85 100       212 if (defined $type) {
381 1 50       6 blessed($type) or confess "${name}'s type must be an object, not $type";
382 1         3 my $index = _find_or_add_idx \@shifty_types, $type;
383 1         2 $str .= "/$index";
384             }
385 85         198 $str .= ' ';
386             }
387 168         349 $clean{shift_types} = \@shifty_types;
388 168         496 $str
389             };
390              
391 168         415 $clean{default_arguments} = _delete_default \%type, 'default_arguments', 1;
392 168         357 $clean{named_parameters} = _delete_default \%type, 'named_parameters', 1;
393 168         358 $clean{types} = _delete_default \%type, 'types', 1;
394 168         343 $clean{invocant} = _delete_default \%type, 'invocant', 0;
395 168         357 $clean{runtime} = _delete_default \%type, 'runtime', 0;
396 168         333 $clean{check_argument_count} = _delete_default \%type, 'check_argument_count', 1;
397 168         375 $clean{check_argument_types} = _delete_default \%type, 'check_argument_types', 1;
398              
399 168 100       412 %type and confess "Invalid keyword property: @{[sort keys %type]}";
  1         217  
400              
401 167         630 $spec{$name} = \%clean;
402             }
403              
404 85   100     165 my %config = %{$^H{+HINTK_CONFIG} // {}};
  85         509  
405 85         310 for my $kw (keys %spec) {
406 167         312 my $type = $spec{$kw};
407              
408             my $flags =
409             $type->{name} eq 'prohibited' ? FLAG_ANON_OK :
410 167 100       525 $type->{name} eq 'required' ? FLAG_NAME_OK :
    100          
411             FLAG_ANON_OK | FLAG_NAME_OK
412             ;
413 167 100       401 $flags |= FLAG_DEFAULT_ARGS if $type->{default_arguments};
414 167 100       363 $flags |= FLAG_CHECK_NARGS if $type->{check_argument_count};
415 167 50       360 $flags |= FLAG_CHECK_TARGS if $type->{check_argument_types};
416 167 100       337 $flags |= FLAG_INVOCANT if $type->{invocant};
417 167 50       337 $flags |= FLAG_NAMED_PARAMS if $type->{named_parameters};
418 167 50       381 $flags |= FLAG_TYPES_OK if $type->{types};
419 167 100       371 $flags |= FLAG_RUNTIME if $type->{runtime};
420             $config{$kw} = {
421             HINTSK_FLAGS, => $flags,
422             HINTSK_SHIFT, => $type->{shift},
423             HINTSK_ATTRS, => $type->{attrs},
424             HINTSK_REIFY, => $type->{reify_type},
425             HINTSK_INSTL, => $type->{install_sub},
426 167         859 !@{$type->{shift_types}} ? () : (
427             HINTSK_SHIF2, => $type->{shift_types},
428 167 100       304 ),
429             };
430             }
431 85         14625 $^H{+HINTK_CONFIG} = \%config;
432             }
433              
434             sub unimport {
435 8     8   74 my $class = shift;
436              
437 8 100       23 if (!@_) {
438 3         14 delete $^H{+HINTK_CONFIG};
439 3         274 return;
440             }
441              
442 5         9 my %config = %{$^H{+HINTK_CONFIG}};
  5         33  
443 5         17 delete @config{@_};
444 5         456 $^H{+HINTK_CONFIG} = \%config;
445             }
446              
447              
448             our %metadata;
449              
450             sub _register_info {
451             my (
452 556     556   114300 $key,
453             $declarator,
454             $shift,
455             $positional_required,
456             $positional_optional,
457             $named_required,
458             $named_optional,
459             $slurpy,
460             $slurpy_type,
461             ) = @_;
462              
463 556 100       2971 my $info = {
464             declarator => $declarator,
465             shift => $shift,
466             positional_required => $positional_required,
467             positional_optional => $positional_optional,
468             named_required => $named_required,
469             named_optional => $named_optional,
470             slurpy => defined $slurpy ? [$slurpy, $slurpy_type] : undef,
471             };
472              
473 556         120041 $metadata{$key} = $info;
474             }
475              
476             sub _mkparam1 {
477 16     16   66 my ($pair) = @_;
478 16 100       22 my ($v, $t) = @{$pair || []} or return undef;
  16 100       124  
479 4         13 Function::Parameters::Param->new(
480             name => $v,
481             type => $t,
482             )
483             }
484              
485             sub _mkparams {
486 64     64   86 my @r;
487 64         177 while (my ($v, $t) = splice @_, 0, 2) {
488 38         142 push @r, Function::Parameters::Param->new(
489             name => $v,
490             type => $t,
491             );
492             }
493             \@r
494 64         213 }
495              
496             sub info {
497 18     18 0 8273 my ($func) = @_;
498 18 50       87 my $key = _cv_root $func or return undef;
499 18 100       125 my $info = $metadata{$key} or return undef;
500 16         1974 require Function::Parameters::Info;
501             Function::Parameters::Info->new(
502             keyword => $info->{declarator},
503             nshift => $info->{shift},
504             slurpy => _mkparam1($info->{slurpy}),
505             (
506 16         64 map +("_$_" => _mkparams @{$info->{$_}}),
  64         200  
507             qw(
508             positional_required
509             positional_optional
510             named_required
511             named_optional
512             )
513             )
514             )
515             }
516              
517             'ok'
518              
519             __END__