File Coverage

blib/lib/Function/Interface.pm
Criterion Covered Total %
statement 90 90 100.0
branch 22 22 100.0
condition n/a
subroutine 21 21 100.0
pod 1 1 100.0
total 134 134 100.0


line stmt bran cond sub pod time code
1             package Function::Interface;
2              
3 18     18   1552035 use v5.14.0;
  18         99  
4 18     18   103 use warnings;
  18         37  
  18         847  
5              
6             our $VERSION = "0.06";
7              
8 18     18   108 use Carp qw(croak);
  18         39  
  18         832  
9 18     18   7650 use Keyword::Simple;
  18         44452  
  18         611  
10 18     18   12533 use PPR;
  18         707618  
  18         900  
11              
12 18     18   9318 use Function::Interface::Info;
  18         56  
  18         617  
13 18     18   7115 use Function::Interface::Info::Function;
  18         51  
  18         616  
14 18     18   7089 use Function::Interface::Info::Function::Param;
  18         135  
  18         677  
15 18     18   7426 use Function::Interface::Info::Function::ReturnParam;
  18         52  
  18         19764  
16              
17             sub import {
18 35     35   5392 my $class = shift;
19 35         140 my %args = @_;
20              
21 35 100       274 my $pkg = $args{pkg} ? $args{pkg} : caller;
22              
23 35         158 Keyword::Simple::define 'fun' => _define_interface($pkg, 'fun');
24 35         1892 Keyword::Simple::define 'method' => _define_interface($pkg, 'method');
25             }
26              
27             sub unimport {
28 1     1   139 Keyword::Simple::undefine 'fun';
29 1         27 Keyword::Simple::undefine 'method';
30             }
31              
32             sub _define_interface {
33 70     70   200 my ($pkg, $keyword) = @_;
34              
35             return sub {
36 44     44   176693 my $ref = shift;
37              
38 44         205 my $match = _assert_valid_interface($$ref);
39 31         181 my $src = _render_src($pkg, $keyword, $match);
40              
41 31         3505 substr($$ref, 0, length $match->{statement}) = $src;
42             }
43 70         604 }
44              
45             sub _render_src {
46 31     31   124 my ($pkg, $keyword, $match) = @_;
47              
48 31         202 my $src = <<"```";
49             BEGIN {
50             Function::Interface::_register_info({
51             package => '$pkg',
52             keyword => '$keyword',
53             subname => '$match->{subname}',
54             params => [ @{[ join ',', map {
55 7 100       37 my $named = $_->{named} ? 1 : 0;
56 7 100       21 my $optional = $_->{optional} ? 1 : 0;
57 7         118 qq!{ type => $_->{type}, name => '$_->{name}', named => $named, optional => $optional }!
58 31         67 } @{$match->{params}} ]} ],
  31         195  
59 31         70 return => [ @{[ join ',', @{$match->{return}}] } ],
  31         134  
60             });
61             }
62             ```
63 31         117 return $src;
64             }
65              
66             our %metadata;
67             sub _register_info {
68 31     31   447 my ($args) = @_;
69              
70 31         9548 push @{$metadata{$args->{package}}} => +{
71             subname => $args->{subname},
72             keyword => $args->{keyword},
73             params => $args->{params},
74             return => $args->{return},
75 31         65 };
76             }
77              
78             sub info {
79 30     30 1 26265 my ($interface_package) = @_;
80 30 100       122 my $info = $metadata{$interface_package} or return undef;
81              
82             Function::Interface::Info->new(
83             package => $interface_package,
84             functions => [ map {
85             Function::Interface::Info::Function->new(
86             subname => $_->{subname},
87             keyword => $_->{keyword},
88 55         85 params => [ map { _make_function_param($_) } @{$_->{params}} ],
  151         308  
89 151         342 return => [ map { _make_function_return_param($_) } @{$_->{return}} ],
  28         49  
  151         519  
90             )
91 28         69 } @{$info}],
  28         74  
92             );
93             }
94              
95             sub _make_function_param {
96 55     55   85 my $param = shift;
97             Function::Interface::Info::Function::Param->new(
98             type => $param->{type},
99             name => $param->{name},
100             named => $param->{named},
101             optional => $param->{optional},
102             )
103 55         151 }
104              
105             sub _make_function_return_param {
106 28     28   45 my $type = shift;
107 28         73 Function::Interface::Info::Function::ReturnParam->new(
108             type => $type,
109             )
110             }
111              
112             sub _assert_valid_interface {
113 44     44   123 my $src = shift;
114              
115 44 100       1507453 $src =~ m{
116             \A
117             (?
118             (?&PerlOWS) (?(?&PerlIdentifier))
119             (?&PerlOWS) \((?.*?)\)
120             (?&PerlOWS) :Return\((?.*?)\)
121             ;
122             )
123             $PPR::GRAMMAR
124             }sx or croak "invalid interface";
125              
126 37         3199 my %match;
127 37         759 $match{statement} = $+{statement};
128 37         247 $match{subname} = $+{subname};
129 37 100       312 $match{params} = $+{params} ? _assert_valid_interface_params($+{params}) : [];
130 33 100       301 $match{return} = $+{return} ? _assert_valid_interface_return($+{return}) : [];
131              
132 31         678 return \%match;
133             }
134              
135             $Function::Interface::GRAMMAR = qr{
136             (?(DEFINE)
137             (?
138             (?&PerlIdentifier)
139             (?: \s* \[
140             \s* (?&PerlTypeParameter) \s*
141             (?: , \s* (?&PerlTypeParameter) \s* )*+
142             \] )?
143             )
144              
145             (?
146             (?&PerlString)|(?&PerlVariable)|(?&PerlType)
147             )
148             )
149              
150             $PPR::GRAMMAR
151             }x;
152              
153             sub _assert_valid_interface_params {
154 20     20   21204 my $src = shift;
155              
156 20         706857 my @list = grep { defined } $src =~ m{
  2227         3475  
157             ((?&PerlType)) \s*
158             (:?) # named \s*
159             ((?&PerlVariable)) \s*
160             (=?) # optional
161              
162             $Function::Interface::GRAMMAR
163             }xg;
164              
165 20         1970 my @params;
166 20         222 while (my ($type, $named, $name, $optional) = splice @list, 0, 4) {
167 17         249 push @params => {
168             type => $type,
169             named => !!$named,
170             name => $name,
171             optional => !!$optional,
172             }
173             }
174              
175             my $regex = join '\s*,\s*', map {
176 20         91 quotemeta sprintf('%s %s%s%s',
177             $_->{type},
178             $_->{named} ? ':' : '',
179             $_->{name},
180 17 100       319 $_->{optional} ? '=' : '',
    100          
181             )
182             } @params;
183              
184 20 100       2784 croak "invalid interface params: $src"
185             unless $src =~ m{ \A \s* $regex \s* \z }x;
186              
187 12         524 return \@params;
188             }
189              
190             sub _assert_valid_interface_return {
191 23     23   26437 my $src = shift;
192              
193 23         820551 my @list = grep { defined } $src =~ m{
  4224         6477  
194             ((?&PerlType))
195             $Function::Interface::GRAMMAR
196             }xg;
197              
198 23 100       2734 croak "invalid interface return: $src. It should be TYPELIST."
199             unless $src =~ m{
200 23         93 \A \s* @{[join '\s*,\s*', map { quotemeta $_ } @list]} \s* \z
  33         3828  
201             }x;
202              
203 16         627 return \@list;
204             }
205              
206             1;
207             __END__