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   1397629 use v5.14.0;
  101         257  
4 101     101   368 use warnings;
  101         116  
  101         2796  
5              
6 101     101   339 use Carp qw(croak confess);
  101         127  
  101         6021  
7 101     101   412 use Scalar::Util qw(blessed);
  101         130  
  101         11521  
8              
9             sub _croak {
10 389     389   314812 my (undef, $file, $line) = caller 1;
11 389         3395 die @_, " at $file line $line.\n";
12             }
13              
14 101     101   405 use XSLoader;
  101         125  
  101         5825  
15             BEGIN {
16 101     101   165 our $VERSION = '2.000005-TRIAL';
17 101         505 $VERSION =~ s/-TRIAL[0-9]*\z//;
18 101         90212 XSLoader::load;
19             }
20              
21             sub _assert_valid_identifier {
22 451     451   485 my ($name, $with_dollar) = @_;
23 451 100       635 my $bonus = $with_dollar ? '\$' : '';
24 451 100       12476 $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   184 my ($attrs) = @_;
30 138 100       3497 $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   77 $_[0] =~ /\G ( \w+ (?: :: \w+)* ) \s* /xgc or _malformed_type $_[0], "missing type name";
77 32         33 my $name = $1;
78 32 50       76 $name = "$_[1]::$name" unless $name =~ /::/;
79 32         19 my $fun = do {
80 101     101   542 no strict 'refs';
  101         117  
  101         179067  
81 32 100       346 defined &$name or croak "Undefined type name $name";
82 30         29 \&$name
83             };
84              
85 30 100       57 $_[0] =~ /\G \[ \s* /xgc
86             or return $fun;
87              
88 8         7 my @args;
89 8         10 until ($_[0] =~ /\G \] \s* /xgc) {
90 16 100 33     29 $_[0] =~ /\G , \s* /xgc or _malformed_type $_[0], "missing ',' or ']'"
91             if @args;
92 16         20 push @args, &_reify_type_auto_union;
93             }
94              
95 8     8   119 sub { $fun->([map $_->(), @args]) }
96 8         19 }
97              
98             sub _reify_type_auto_union {
99             # (str, caller)
100 24     24   26 my $fun = &_reify_type_auto_term;
101 22         35 while ($_[0] =~ /\G \| \s* /xgc) {
102 8         8 my $right = &_reify_type_auto_term;
103 8         2 my $left = $fun;
104 8     8   24 $fun = sub { $left->() | $right->() };
  8         78  
105             }
106             $fun
107 22         38 }
108              
109             sub _reify_type_auto {
110 8     8   485 my ($type) = @_;
111 8         13 my $caller = caller;
112              
113 8         18 $type =~ /\G \s+ /xgc;
114 8         14 my $tfun = _reify_type_auto_union $type, $caller;
115 6 50       13 $type =~ /\G \z/xgc or _malformed_type $type, "trailing garbage";
116 6         254 $tfun->()
117             }
118              
119             sub _delete_default {
120 2016     2016   1651 my ($href, $key, $default) = @_;
121 2016 100       3767 exists $href->{$key} ? delete $href->{$key} : $default
122             }
123              
124             sub _find_or_add_idx {
125 4     4   4 my ($array, $x) = @_;
126 4         4 my $index;
127 4         11 for my $i (0 .. $#$array) {
128 8 50       20 if ($array->[$i] == $x) {
129 0         0 $index = $i;
130 0         0 last;
131             }
132             }
133 4 50       10 unless (defined $index) {
134 4         4 $index = @$array;
135 4         5 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   18762 my $class = shift;
245              
246 160         175 my %imports;
247 160 100       1956 @_ = qw(:std) if !@_;
248 160         941 for my $item (@_) {
249 165         157 my $part;
250 165 100       977 if (ref $item) {
251 56         56 $part = $item;
252             } else {
253 109 100       498 my $type = $import_map{$item}
254             or croak qq{"$item" is not exported by the $class module};
255 107 100       263 $part = ref $type
256             ? $type
257             : { $item => $type };
258             }
259 163         639 @imports{keys %$part} = values %$part;
260             }
261              
262 158         155 my %spec;
263              
264 158         675 for my $name (sort keys %imports) {
265 296         459 _assert_valid_identifier $name;
266 291         349 my $proto_type = $imports{$name};
267              
268 291 100       1649 $proto_type = {defaults => $proto_type} unless ref $proto_type;
269              
270 291         727 my %type = %$proto_type;
271 291         714 while (my $defaults = delete $type{defaults}) {
272 559 100       2354 my $base = $type_map{$defaults}
273 1         110 or confess qq["$defaults" doesn't look like a valid type (one of ${\join ', ', sort keys %type_map})];
274 558         1843 %type = (%$base, %type);
275             }
276              
277 290 100       507 if (exists $type{strict}) {
278 25   66     110 $type{check_argument_count} ||= $type{strict};
279 25         30 delete $type{strict};
280             }
281              
282 290         246 my %clean;
283              
284 290   100     1841 $clean{name} = delete $type{name} // 'optional';
285 290 50       2029 $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     1535 $clean{attrs} = delete $type{attributes} // '';
289 290 100       1233 _assert_valid_attributes $clean{attrs} if $clean{attrs};
290              
291 288 100       1151 if (!exists $type{reify_type}) {
292 284         297 $clean{reify_type} = 0;
293             } else {
294 4   50     10 my $rt = delete $type{reify_type} // '(undef)';
295 4 50       17 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         14 $clean{reify_type} = _find_or_add_idx \@type_reifiers, $rt;
305             }
306              
307 288 100       383 if (!exists $type{install_sub}) {
308 276         937 $clean{install_sub} = '';
309             } else {
310 12         12 my $is = delete $type{install_sub};
311 12 50       14 if (!ref $is) {
    0          
312 12         13 _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         13 $clean{install_sub} = $is;
320             }
321              
322 288         227 $clean{shift} = do {
323 288   100     2167 my $shift = delete $type{shift} // [];
324 288 100       2122 $shift = [$shift] if !ref $shift;
325 288         1679 my $str = '';
326 288         997 for my $item (@$shift) {
327 143         154 my ($name, $type);
328 143 50       2712 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         734 $name = $item;
333             }
334 143         239 _assert_valid_identifier $name, 1;
335 143 50       380 $name eq '$_' and confess q[Using "$_" as a parameter is not supported];
336 143         204 $str .= $name;
337 143 50       3864 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         225 $str .= ' ';
343             }
344             $str
345 288         590 };
346              
347 288         1329 $clean{default_arguments} = _delete_default \%type, 'default_arguments', 1;
348 288         1067 $clean{named_parameters} = _delete_default \%type, 'named_parameters', 1;
349 288         1635 $clean{types} = _delete_default \%type, 'types', 1;
350 288         975 $clean{invocant} = _delete_default \%type, 'invocant', 0;
351 288         1001 $clean{runtime} = _delete_default \%type, 'runtime', 0;
352 288         1327 $clean{check_argument_count} = _delete_default \%type, 'check_argument_count', 1;
353 288         954 $clean{check_argument_types} = _delete_default \%type, 'check_argument_types', 1;
354              
355 288 100       1200 %type and confess "Invalid keyword property: @{[sort keys %type]}";
  1         182  
356              
357 287         1692 $spec{$name} = \%clean;
358             }
359              
360 149         400 for my $kw (keys %spec) {
361 287         305 my $type = $spec{$kw};
362              
363             my $flags =
364             $type->{name} eq 'prohibited' ? FLAG_ANON_OK :
365 287 100       2075 $type->{name} eq 'required' ? FLAG_NAME_OK :
    100          
366             FLAG_ANON_OK | FLAG_NAME_OK
367             ;
368 287 100       2700 $flags |= FLAG_DEFAULT_ARGS if $type->{default_arguments};
369 287 100       438 $flags |= FLAG_CHECK_NARGS if $type->{check_argument_count};
370 287 50       1029 $flags |= FLAG_CHECK_TARGS if $type->{check_argument_types};
371 287 100       1126 $flags |= FLAG_INVOCANT if $type->{invocant};
372 287 50       1778 $flags |= FLAG_NAMED_PARAMS if $type->{named_parameters};
373 287 50       1800 $flags |= FLAG_TYPES_OK if $type->{types};
374 287 100       450 $flags |= FLAG_RUNTIME if $type->{runtime};
375 287         3982 $^H{HINTK_FLAGS_ . $kw} = $flags;
376 287         5357 $^H{HINTK_SHIFT_ . $kw} = $type->{shift};
377 287         2128 $^H{HINTK_ATTRS_ . $kw} = $type->{attrs};
378 287         3092 $^H{HINTK_REIFY_ . $kw} = $type->{reify_type};
379 287         2022 $^H{HINTK_INSTALL_ . $kw} = $type->{install_sub};
380 287         24146 $^H{+HINTK_KEYWORDS} .= "$kw ";
381             }
382             }
383              
384             sub unimport {
385 9     9   47 my $class = shift;
386              
387 9 100       20 if (!@_) {
388 4         10 delete $^H{+HINTK_KEYWORDS};
389 4         302 return;
390             }
391              
392 5         8 for my $kw (@_) {
393 5         449 $^H{+HINTK_KEYWORDS} =~ s/(?
394             }
395             }
396              
397              
398             our %metadata;
399              
400             sub _register_info {
401             my (
402 771     771   110235 $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       6507 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         169596 $metadata{$key} = $info;
424             }
425              
426             sub _mkparam1 {
427 16     16   40 my ($pair) = @_;
428 16 100       17 my ($v, $t) = @{$pair || []} or return undef;
  16 100       99  
429 4         13 Function::Parameters::Param->new(
430             name => $v,
431             type => $t,
432             )
433             }
434              
435             sub _mkparams {
436 64     64   46 my @r;
437 64         127 while (my ($v, $t) = splice @_, 0, 2) {
438 38         79 push @r, Function::Parameters::Param->new(
439             name => $v,
440             type => $t,
441             );
442             }
443             \@r
444 64         154 }
445              
446             sub info {
447 18     18 0 5548 my ($func) = @_;
448 18 50       71 my $key = _cv_root $func or return undef;
449 18 100       53 my $info = $metadata{$key} or return undef;
450 16         1784 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         42 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__