File Coverage

blib/lib/Function/Parameters.pm
Criterion Covered Total %
statement 165 182 90.6
branch 76 102 74.5
condition 10 14 71.4
subroutine 23 25 92.0
pod 0 1 0.0
total 274 324 84.5


line stmt bran cond sub pod time code
1             package Function::Parameters;
2              
3 101     101   1423875 use v5.14.0;
  101         266  
4 101     101   373 use warnings;
  101         129  
  101         2889  
5              
6 101     101   349 use Carp qw(croak confess);
  101         136  
  101         6150  
7 101     101   418 use Scalar::Util qw(blessed);
  101         132  
  101         12058  
8              
9             sub _croak {
10 389     389   264625 my (undef, $file, $line) = caller 1;
11 389         3057 die @_, " at $file line $line.\n";
12             }
13              
14 101     101   446 use XSLoader;
  101         172  
  101         5746  
15             BEGIN {
16 101     101   171 our $VERSION = '2.000004-TRIAL';
17 101         511 $VERSION =~ s/-TRIAL[0-9]*\z//;
18 101         92685 XSLoader::load;
19             }
20              
21             sub _assert_valid_identifier {
22 451     451   487 my ($name, $with_dollar) = @_;
23 451 100       655 my $bonus = $with_dollar ? '\$' : '';
24 451 100       14089 $name =~ /\A${bonus}[^\W\d]\w*\z/
25             or confess qq{"$name" doesn't look like a valid identifier};
26             }
27              
28             sub _assert_valid_attributes {
29 138     138   836 my ($attrs) = @_;
30 138 100       3456 $attrs =~ m{
31             \A \s*+
32             : \s*+
33             (?&ident) (?! [^\s:(] ) (?¶m)?+ \s*+
34             (?:
35             (?: : \s*+ )?
36             (?&ident) (?! [^\s:(] ) (?¶m)?+ \s*+
37             )*+
38             \z
39              
40             (?(DEFINE)
41             (?
42             [^\W\d]
43             \w*+
44             )
45             (?
46             \(
47             [^()\\]*+
48             (?:
49             (?:
50             \\ .
51             |
52             (?¶m)
53             )
54             [^()\\]*+
55             )*+
56             \)
57             )
58             )
59             }sx or confess qq{"$attrs" doesn't look like valid attributes};
60             }
61              
62             sub _reify_type_moose {
63 0     0   0 require Moose::Util::TypeConstraints;
64 0         0 Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_[0])
65             }
66              
67             sub _malformed_type {
68 0     0   0 my ($type, $msg) = @_;
69 0         0 my $pos = pos $_[0];
70 0         0 substr $type, $pos, 0, ' <-- HERE ';
71 0         0 croak "Malformed type: $msg marked by <-- HERE in '$type'";
72             }
73              
74             sub _reify_type_auto_term {
75             # (str, caller)
76 32 50   32   81 $_[0] =~ /\G ( \w+ (?: :: \w+)* ) \s* /xgc or _malformed_type $_[0], "missing type name";
77 32         36 my $name = $1;
78 32 50       61 $name = "$_[1]::$name" unless $name =~ /::/;
79 32         22 my $fun = do {
80 101     101   584 no strict 'refs';
  101         126  
  101         181073  
81 32 100       370 defined &$name or croak "Undefined type name $name";
82 30         30 \&$name
83             };
84              
85 30 100       52 $_[0] =~ /\G \[ \s* /xgc
86             or return $fun;
87              
88 8         7 my @args;
89 8         9 until ($_[0] =~ /\G \] \s* /xgc) {
90 16 100 33     31 $_[0] =~ /\G , \s* /xgc or _malformed_type $_[0], "missing ',' or ']'"
91             if @args;
92 16         18 push @args, &_reify_type_auto_union;
93             }
94              
95 8     8   142 sub { $fun->([map $_->(), @args]) }
96 8         18 }
97              
98             sub _reify_type_auto_union {
99             # (str, caller)
100 24     24   29 my $fun = &_reify_type_auto_term;
101 22         33 while ($_[0] =~ /\G \| \s* /xgc) {
102 8         7 my $right = &_reify_type_auto_term;
103 8         5 my $left = $fun;
104 8     8   24 $fun = sub { $left->() | $right->() };
  8         82  
105             }
106             $fun
107 22         36 }
108              
109             sub _reify_type_auto {
110 8     8   531 my ($type) = @_;
111 8         13 my $caller = caller;
112              
113 8         19 $type =~ /\G \s+ /xgc;
114 8         17 my $tfun = _reify_type_auto_union $type, $caller;
115 6 50       13 $type =~ /\G \z/xgc or _malformed_type $type, "trailing garbage";
116 6         231 $tfun->()
117             }
118              
119             sub _delete_default {
120 2016     2016   1622 my ($href, $key, $default) = @_;
121 2016 100       3101 exists $href->{$key} ? delete $href->{$key} : $default
122             }
123              
124             sub _find_or_add_idx {
125 4     4   5 my ($array, $x) = @_;
126 4         4 my $index;
127 4         11 for my $i (0 .. $#$array) {
128 8 50       18 if ($array->[$i] == $x) {
129 0         0 $index = $i;
130 0         0 last;
131             }
132             }
133 4 50       9 unless (defined $index) {
134 4         4 $index = @$array;
135 4         6 push @$array, $x;
136             }
137             $index
138 4         7 }
139              
140             my %type_map = (
141             function_strict => {},
142             function_lax => {
143             defaults => 'function_strict',
144             strict => 0,
145             },
146             function => { defaults => 'function_strict' },
147              
148             method_strict => {
149             defaults => 'function_strict',
150             attributes => ':method',
151             shift => '$self',
152             invocant => 1,
153             },
154             method_lax => {
155             defaults => 'method_strict',
156             strict => 0,
157             },
158             method => { defaults => 'method_strict' },
159              
160             classmethod_strict => {
161             defaults => 'method_strict',
162             shift => '$class',
163             },
164             classmethod_lax => {
165             defaults => 'classmethod_strict',
166             strict => 0,
167             },
168             classmethod => { defaults => 'classmethod_strict' },
169              
170             around => {
171             defaults => 'method',
172             name => 'required',
173             install_sub => 'around',
174             shift => ['$orig', '$self'],
175             runtime => 1,
176             },
177             (
178             map +(
179             $_ => {
180             defaults => 'method',
181             name => 'required',
182             install_sub => $_,
183             runtime => 1,
184             }
185             ), qw(
186             before after augment override
187             ),
188             ),
189             );
190              
191             my %import_map = (
192             fun => 'function',
193             (
194             map +($_ => $_),
195             qw(
196             method
197             classmethod
198             before
199             after
200             around
201             augment
202             override
203             )
204             ),
205              
206             ':strict' => {
207             fun => 'function_strict',
208             method => 'method_strict',
209             },
210              
211             ':lax' => {
212             fun => 'function_lax',
213             method => 'method_lax',
214             },
215              
216             ':std' => [qw(fun method)],
217             ':modifiers' => [qw(
218             before
219             after
220             around
221             augment
222             override
223             )],
224             );
225             for my $v (values %import_map) {
226             if (ref $v eq 'ARRAY') {
227             $v = {
228             map +($_ => $import_map{$_} || die "Internal error: $v => $_"),
229             @$v
230             };
231             }
232             }
233              
234             our @type_reifiers = (
235             \&_reify_type_auto,
236             \&_reify_type_moose,
237             );
238              
239             our @sub_installers;
240              
241             our @shifty_types;
242              
243             sub import {
244 160     160   23294 my $class = shift;
245              
246 160         186 my %imports;
247 160 100       1197 @_ = qw(:std) if !@_;
248 160         1754 for my $item (@_) {
249 165         168 my $part;
250 165 100       1005 if (ref $item) {
251 56         57 $part = $item;
252             } else {
253 109 100       618 my $type = $import_map{$item}
254             or croak qq{"$item" is not exported by the $class module};
255 107 100       278 $part = ref $type
256             ? $type
257             : { $item => $type };
258             }
259 163         664 @imports{keys %$part} = values %$part;
260             }
261              
262 158         160 my %spec;
263              
264 158         706 for my $name (sort keys %imports) {
265 296         496 _assert_valid_identifier $name;
266 291         370 my $proto_type = $imports{$name};
267              
268 291 100       753 $proto_type = {defaults => $proto_type} unless ref $proto_type;
269              
270 291         740 my %type = %$proto_type;
271 291         729 while (my $defaults = delete $type{defaults}) {
272 559 100       2398 my $base = $type_map{$defaults}
273 1         183 or confess qq["$defaults" doesn't look like a valid type (one of ${\join ', ', sort keys %type_map})];
274 558         1912 %type = (%$base, %type);
275             }
276              
277 290 100       537 if (exists $type{strict}) {
278 25   66     118 $type{check_argument_count} ||= $type{strict};
279 25         26 delete $type{strict};
280             }
281              
282 290         243 my %clean;
283              
284 290   100     1829 $clean{name} = delete $type{name} // 'optional';
285 290 50       1860 $clean{name} =~ /\A(?:optional|required|prohibited)\z/
286             or confess qq["$clean{name}" doesn't look like a valid name attribute (one of optional, required, prohibited)];
287              
288 290   100     1537 $clean{attrs} = delete $type{attributes} // '';
289 290 100       2035 _assert_valid_attributes $clean{attrs} if $clean{attrs};
290              
291 288 100       525 if (!exists $type{reify_type}) {
292 284         315 $clean{reify_type} = 0;
293             } else {
294 4   50     10 my $rt = delete $type{reify_type} // '(undef)';
295 4 50       16 if (!ref $rt) {
    50          
296 0 0       0 $rt =
    0          
297             $rt eq 'auto' ? \&_reify_type_auto :
298             $rt eq 'moose' ? \&_reify_type_moose :
299             confess qq{"$rt" isn't a known predefined type reifier};
300             } elsif (ref $rt ne 'CODE') {
301 0         0 confess qq{"$rt" doesn't look like a type reifier};
302             }
303              
304 4         13 $clean{reify_type} = _find_or_add_idx \@type_reifiers, $rt;
305             }
306              
307 288 100       395 if (!exists $type{install_sub}) {
308 276         1009 $clean{install_sub} = '';
309             } else {
310 12         16 my $is = delete $type{install_sub};
311 12 50       18 if (!ref $is) {
    0          
312 12         19 _assert_valid_identifier $is;
313             } elsif (ref $is ne 'CODE') {
314 0         0 confess qq{"$is" doesn't look like a sub installer};
315             } else {
316 0         0 $is = _find_or_add_idx \@sub_installers, $is;
317             }
318              
319 12         12 $clean{install_sub} = $is;
320             }
321              
322 288         823 $clean{shift} = do {
323 288   100     1405 my $shift = delete $type{shift} // [];
324 288 100       3725 $shift = [$shift] if !ref $shift;
325 288         259 my $str = '';
326 288         1070 for my $item (@$shift) {
327 143         1556 my ($name, $type);
328 143 50       1811 if (ref $item) {
329 0 0       0 @$item == 2 or confess "A 'shift' item must have 2 elements, not " . @$item;
330 0         0 ($name, $type) = @$item;
331             } else {
332 143         147 $name = $item;
333             }
334 143         229 _assert_valid_identifier $name, 1;
335 143 50       1120 $name eq '$_' and confess q[Using "$_" as a parameter is not supported];
336 143         215 $str .= $name;
337 143 50       1688 if (defined $type) {
338 0 0       0 blessed($type) or confess "${name}'s type must be an object, not $type";
339 0         0 my $index = _find_or_add_idx \@shifty_types, $type;
340 0         0 $str .= "/$index";
341             }
342 143         936 $str .= ' ';
343             }
344             $str
345 288         527 };
346              
347 288         1354 $clean{default_arguments} = _delete_default \%type, 'default_arguments', 1;
348 288         1434 $clean{named_parameters} = _delete_default \%type, 'named_parameters', 1;
349 288         2565 $clean{types} = _delete_default \%type, 'types', 1;
350 288         427 $clean{invocant} = _delete_default \%type, 'invocant', 0;
351 288         1068 $clean{runtime} = _delete_default \%type, 'runtime', 0;
352 288         1022 $clean{check_argument_count} = _delete_default \%type, 'check_argument_count', 1;
353 288         1011 $clean{check_argument_types} = _delete_default \%type, 'check_argument_types', 1;
354              
355 288 100       1494 %type and confess "Invalid keyword property: @{[sort keys %type]}";
  1         171  
356              
357 287         753 $spec{$name} = \%clean;
358             }
359              
360 149         410 for my $kw (keys %spec) {
361 287         1072 my $type = $spec{$kw};
362              
363             my $flags =
364             $type->{name} eq 'prohibited' ? FLAG_ANON_OK :
365 287 100       2618 $type->{name} eq 'required' ? FLAG_NAME_OK :
    100          
366             FLAG_ANON_OK | FLAG_NAME_OK
367             ;
368 287 100       2316 $flags |= FLAG_DEFAULT_ARGS if $type->{default_arguments};
369 287 100       1449 $flags |= FLAG_CHECK_NARGS if $type->{check_argument_count};
370 287 50       451 $flags |= FLAG_CHECK_TARGS if $type->{check_argument_types};
371 287 100       1089 $flags |= FLAG_INVOCANT if $type->{invocant};
372 287 50       2203 $flags |= FLAG_NAMED_PARAMS if $type->{named_parameters};
373 287 50       1085 $flags |= FLAG_TYPES_OK if $type->{types};
374 287 100       1031 $flags |= FLAG_RUNTIME if $type->{runtime};
375 287         3802 $^H{HINTK_FLAGS_ . $kw} = $flags;
376 287         4921 $^H{HINTK_SHIFT_ . $kw} = $type->{shift};
377 287         1995 $^H{HINTK_ATTRS_ . $kw} = $type->{attrs};
378 287         2810 $^H{HINTK_REIFY_ . $kw} = $type->{reify_type};
379 287         2050 $^H{HINTK_INSTALL_ . $kw} = $type->{install_sub};
380 287         24704 $^H{+HINTK_KEYWORDS} .= "$kw ";
381             }
382             }
383              
384             sub unimport {
385 9     9   43 my $class = shift;
386              
387 9 100       23 if (!@_) {
388 4         11 delete $^H{+HINTK_KEYWORDS};
389 4         306 return;
390             }
391              
392 5         10 for my $kw (@_) {
393 5         431 $^H{+HINTK_KEYWORDS} =~ s/(?
394             }
395             }
396              
397              
398             our %metadata;
399              
400             sub _register_info {
401             my (
402 771     771   116594 $key,
403             $declarator,
404             $shift,
405             $positional_required,
406             $positional_optional,
407             $named_required,
408             $named_optional,
409             $slurpy,
410             $slurpy_type,
411             ) = @_;
412              
413 771 100       6832 my $info = {
414             declarator => $declarator,
415             shift => $shift,
416             positional_required => $positional_required,
417             positional_optional => $positional_optional,
418             named_required => $named_required,
419             named_optional => $named_optional,
420             slurpy => defined $slurpy ? [$slurpy, $slurpy_type] : undef,
421             };
422              
423 771         171620 $metadata{$key} = $info;
424             }
425              
426             sub _mkparam1 {
427 16     16   34 my ($pair) = @_;
428 16 100       14 my ($v, $t) = @{$pair || []} or return undef;
  16 100       112  
429 4         12 Function::Parameters::Param->new(
430             name => $v,
431             type => $t,
432             )
433             }
434              
435             sub _mkparams {
436 64     64   43 my @r;
437 64         127 while (my ($v, $t) = splice @_, 0, 2) {
438 38         82 push @r, Function::Parameters::Param->new(
439             name => $v,
440             type => $t,
441             );
442             }
443             \@r
444 64         149 }
445              
446             sub info {
447 18     18 0 4241 my ($func) = @_;
448 18 50       70 my $key = _cv_root $func or return undef;
449 18 100       55 my $info = $metadata{$key} or return undef;
450 16         1919 require Function::Parameters::Info;
451             Function::Parameters::Info->new(
452             keyword => $info->{declarator},
453             nshift => $info->{shift},
454             slurpy => _mkparam1($info->{slurpy}),
455             (
456 16         48 map +("_$_" => _mkparams @{$info->{$_}}),
  64         106  
457             qw(
458             positional_required
459             positional_optional
460             named_required
461             named_optional
462             )
463             )
464             )
465             }
466              
467             'ok'
468              
469             __END__