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 10     10   1450072 use v5.14.0;
  10         61  
4 10     10   55 use warnings;
  10         17  
  10         451  
5              
6             our $VERSION = "0.05";
7              
8 10     10   56 use Carp qw(croak);
  10         26  
  10         517  
9 10     10   4355 use Keyword::Simple;
  10         24387  
  10         336  
10 10     10   7178 use PPR;
  10         384558  
  10         433  
11              
12 10     10   4952 use Function::Interface::Info;
  10         29  
  10         333  
13 10     10   4094 use Function::Interface::Info::Function;
  10         24  
  10         319  
14 10     10   4108 use Function::Interface::Info::Function::Param;
  10         25  
  10         321  
15 10     10   4001 use Function::Interface::Info::Function::ReturnParam;
  10         26  
  10         10762  
16              
17             sub import {
18 23     23   4061 my $class = shift;
19 23         79 my %args = @_;
20              
21 23 100       161 my $pkg = $args{pkg} ? $args{pkg} : caller;
22              
23 23         88 Keyword::Simple::define 'fun' => _define_interface($pkg, 'fun');
24 23         847 Keyword::Simple::define 'method' => _define_interface($pkg, 'method');
25             }
26              
27             sub unimport {
28 1     1   114 Keyword::Simple::undefine 'fun';
29 1         21 Keyword::Simple::undefine 'method';
30             }
31              
32             sub _define_interface {
33 46     46   115 my ($pkg, $keyword) = @_;
34              
35             return sub {
36 44     44   173945 my $ref = shift;
37              
38 44         192 my $match = _assert_valid_interface($$ref);
39 31         209 my $src = _render_src($pkg, $keyword, $match);
40              
41 31         3668 substr($$ref, 0, length $match->{statement}) = $src;
42             }
43 46         332 }
44              
45             sub _render_src {
46 31     31   135 my ($pkg, $keyword, $match) = @_;
47              
48 31         218 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       39 my $named = $_->{named} ? 1 : 0;
56 7 100       19 my $optional = $_->{optional} ? 1 : 0;
57 7         89 qq!{ type => $_->{type}, name => '$_->{name}', named => $named, optional => $optional }!
58 31         73 } @{$match->{params}} ]} ],
  31         208  
59 31         70 return => [ @{[ join ',', @{$match->{return}}] } ],
  31         146  
60             });
61             }
62             ```
63 31         127 return $src;
64             }
65              
66             our %metadata;
67             sub _register_info {
68 31     31   462 my ($args) = @_;
69              
70 31         8769 push @{$metadata{$args->{package}}} => +{
71             subname => $args->{subname},
72             keyword => $args->{keyword},
73             params => $args->{params},
74             return => $args->{return},
75 31         68 };
76             }
77              
78             sub info {
79 30     30 1 36171 my ($interface_package) = @_;
80 30 100       135 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         92 params => [ map { _make_function_param($_) } @{$_->{params}} ],
  151         318  
89 151         303 return => [ map { _make_function_return_param($_) } @{$_->{return}} ],
  28         52  
  151         495  
90             )
91 28         61 } @{$info}],
  28         73  
92             );
93             }
94              
95             sub _make_function_param {
96 55     55   79 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         154 }
104              
105             sub _make_function_return_param {
106 28     28   42 my $type = shift;
107 28         68 Function::Interface::Info::Function::ReturnParam->new(
108             type => $type,
109             )
110             }
111              
112             sub _assert_valid_interface {
113 44     44   119 my $src = shift;
114              
115 44 100       1506069 $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         3314 my %match;
127 37         739 $match{statement} = $+{statement};
128 37         238 $match{subname} = $+{subname};
129 37 100       315 $match{params} = $+{params} ? _assert_valid_interface_params($+{params}) : [];
130 33 100       325 $match{return} = $+{return} ? _assert_valid_interface_return($+{return}) : [];
131              
132 31         711 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   18428 my $src = shift;
155              
156 20         702319 my @list = grep { defined } $src =~ m{
  2227         3546  
157             ((?&PerlType)) \s*
158             (:?) # named \s*
159             ((?&PerlVariable)) \s*
160             (=?) # optional
161              
162             $Function::Interface::GRAMMAR
163             }xg;
164              
165 20         1575 my @params;
166 20         183 while (my ($type, $named, $name, $optional) = splice @list, 0, 4) {
167 17         189 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         67 quotemeta sprintf('%s %s%s%s',
177             $_->{type},
178             $_->{named} ? ':' : '',
179             $_->{name},
180 17 100       229 $_->{optional} ? '=' : '',
    100          
181             )
182             } @params;
183              
184 20 100       2219 croak "invalid interface params: $src"
185             unless $src =~ m{ \A \s* $regex \s* \z }x;
186              
187 12         484 return \@params;
188             }
189              
190             sub _assert_valid_interface_return {
191 23     23   21152 my $src = shift;
192              
193 23         801946 my @list = grep { defined } $src =~ m{
  4224         6368  
194             ((?&PerlType))
195             $Function::Interface::GRAMMAR
196             }xg;
197              
198 23 100       1778 croak "invalid interface return: $src. It should be TYPELIST."
199             unless $src =~ m{
200 23         64 \A \s* @{[join '\s*,\s*', map { quotemeta $_ } @list]} \s* \z
  33         2067  
201             }x;
202              
203 16         610 return \@list;
204             }
205              
206             1;
207             __END__