File Coverage

blib/lib/Method/Signatures/Simple/ParseKeyword.pm
Criterion Covered Total %
statement 167 207 80.6
branch 27 66 40.9
condition 20 29 68.9
subroutine 25 27 92.5
pod 0 6 0.0
total 239 335 71.3


line stmt bran cond sub pod time code
1             package Method::Signatures::Simple::ParseKeyword;
2             $Method::Signatures::Simple::ParseKeyword::VERSION = '1.12';
3 10     10   481771 use warnings;
  10         88  
  10         322  
4 10     10   50 use strict;
  10         18  
  10         277  
5              
6             =head1 NAME
7              
8             Method::Signatures::Simple::ParseKeyword - method and func keywords using Parse::Keyword
9              
10             =cut
11              
12 10     10   51 use base 'Exporter';
  10         16  
  10         1428  
13 10     10   64 use Carp qw(croak);
  10         18  
  10         518  
14 10     10   3963 use Sub::Name 'subname';
  10         5159  
  10         497  
15 10     10   3855 use Parse::Keyword {};
  10         34561  
  10         64  
16             our @EXPORT;
17             our %MAP;
18              
19             sub import {
20 18     18   2425 my $caller = caller;
21 18         35 my $class = shift;
22 18         53 my %args = @_;
23              
24 18         34 my %kwds;
25              
26 18   66     99 my $into = delete $args{into} || $caller;
27 18   100     86 my $inv = delete $args{invocant} || '$self';
28 18   100     77 my $meth = delete $args{name} || delete $args{method_keyword};
29 18         35 my $func = delete $args{function_keyword};
30              
31             # if no options are provided at all, then we supply defaults
32 18 100 100     86 unless (defined $meth || defined $func) {
33 13         24 $meth = 'method';
34 13         24 $func = 'func';
35             }
36              
37             # input validation
38 10 100   10   7953 $inv =~ m/^ \s* \$ [\p{ID_Start}_] \p{ID_Continue}* \s* $/x
  10         117  
  10         150  
  18         328  
39             or croak "invocant must be a valid scalar identifier >$inv<";
40              
41 17 100       50 if ($func) {
42 14 100       149 $func =~ m/^ \s* [\p{ID_Start}_] \p{ID_Continue}* \s* $/x
43             or croak "function_keyword must be a valid identifier >$func<";
44 10     10   208764 no strict 'refs';
  10         22  
  10         1659  
45 13 50   3   191 *$func = sub { @_ ? $_[0] : () };
  3         17  
46 13         50 my $parse = "parse_$func";
47 13     3   101 *$parse = sub { my ($kw) = @_; parse_mode($kw); };
  3         14  
  3         38  
48 13         43 $MAP{$func} = undef;
49 13         41 $kwds{ $func } = \&$parse;
50 13         44 push @EXPORT, $func;
51             }
52 16 100       154 if ($meth) {
53 15 100       165 $meth =~ m/^ \s* [\p{ID_Start}_] \p{ID_Continue}* \s* $/x
54             or croak "method_keyword must be a valid identifier >$meth<";
55 10     10   2334 no strict 'refs';
  10         22  
  10         1678  
56 14 50   16   111 *$meth = sub { @_ ? $_[0] : () };
  16         62  
57 14         43 my $parse = "parse_$meth";
58 14     16   98 *$parse = sub { my ($kw) = @_; parse_mode($kw); };
  16         63620  
  16         83  
59 14         42 $MAP{$meth} = $inv;
60 14         43 @kwds{ $meth } = \&$parse;
61 14         33 push @EXPORT, $meth;
62             }
63              
64 15         101 Parse::Keyword->import(\%kwds);
65 15         1299 for my $e (@EXPORT) {
66 40         260 my $n = $e =~ s/[$%@]//r;
67 40         80 my $fn = $into. '::' . $n;
68 10     10   64 no strict 'refs';
  10         17  
  10         1780  
69 40         123 *$fn = $e;
70 40         2059 my $k = *$fn; # avoid 'once' warning
71             }
72             }
73              
74             sub parse_mode {
75 19     19 0 45 my ($keyword, $invocant) = @_;
76 19   66     108 $invocant ||= $MAP{$keyword};
77              
78 19         44 my $name = parse_name();
79 19         47 my $sig = parse_signature($invocant);
80 19         47 my $attr = parse_attributes();
81 19         62 my $body = parse_body($sig);
82              
83 19 50       54 if (defined $name) {
84 19         72 my $full_name = join('::', compiling_package, $name);
85             {
86 10     10   66 no strict 'refs';
  10         19  
  10         397  
  19         35  
87 19         155 *$full_name = subname $full_name, $body;
88 19 50       57 if ($attr) {
89 10     10   4436 use attributes ();
  10         10591  
  10         1312  
90 0         0 attributes->import(compiling_package, $body, $_) for @$attr;
91             }
92             }
93 19     19   10026 return (sub {}, 1);
94             }
95             else {
96 0     0   0 return (sub { $body }, 0);
  0         0  
97             }
98             }
99              
100             my $start_rx = qr/^[\p{ID_Start}_]$/;
101             my $cont_rx = qr/^\p{ID_Continue}$/;
102              
103             sub parse_name {
104 41     41 0 69 my $name = '';
105              
106 41         137 lex_read_space;
107              
108 41         57 my $char_rx = $start_rx;
109              
110 41         58 while (1) {
111 226         394 my $char = lex_peek;
112 226 50       392 last unless length $char;
113 226 100       706 if ($char =~ $char_rx) {
114 185         262 $name .= $char;
115 185         352 lex_read;
116 185         242 $char_rx = $cont_rx;
117             }
118             else {
119 41         70 last;
120             }
121             }
122              
123 41 50       96 return length($name) ? $name : undef;
124             }
125              
126             sub parse_signature {
127 19     19 0 37 my ($invocant) = @_;
128 19         44 lex_read_space;
129              
130 19 100       63 my @vars = $invocant ? ({ index => 0, name => $invocant }) : ();
131 19 0       58 return \@vars unless lex_peek eq '(';
132              
133 19         52 my @attr = ();
134              
135 19         45 lex_read;
136 19         37 lex_read_space;
137              
138 19 0       38 if (lex_peek eq ')') {
139 4         15 lex_read;
140 4         22 return \@vars;
141             }
142              
143 15         33 my $seen_slurpy;
144 15         39 while ((my $sigil = lex_peek) ne ')') {
145 22         49 my $var = {};
146 22 50 100     74 die "syntax error"
      66        
147             unless $sigil eq '$' || $sigil eq '@' || $sigil eq '%';
148 22 50       45 die "Can't declare parameters after a slurpy parameter"
149             if $seen_slurpy;
150              
151 22 100 100     80 $seen_slurpy = 1 if $sigil eq '@' || $sigil eq '%';
152              
153 22         44 lex_read;
154 22         45 lex_read_space;
155 22         35 my $name = parse_name(0);
156 22         52 lex_read_space;
157              
158 22         57 $var->{name} = "$sigil$name";
159              
160 22 0       50 if (lex_peek eq '=') {
161 2         6 lex_read;
162 2         4 lex_read_space;
163 2         25 $var->{default} = parse_arithexpr;
164             }
165              
166 22         65 $var->{index} = @vars - 1;
167              
168 22 0       44 if (lex_peek eq ':') {
169 4         14 $vars[0] = $var;
170 4         10 lex_read;
171 4         9 lex_read_space;
172 4         10 next;
173             }
174              
175 18         41 push @vars, $var;
176              
177 18 0 0     35 die "syntax error"
178             unless lex_peek eq ')' || lex_peek eq ',';
179              
180 18 0       55 if (lex_peek eq ',') {
181 5         12 lex_read;
182 5         17 lex_read_space;
183             }
184             }
185              
186 15         57 lex_read;
187              
188 15         35 return \@vars;
189             }
190              
191             # grabbed these two functions from
192             # https://metacpan.org/release/PEVANS/XS-Parse-Keyword-0.22/source/hax/lexer-additions.c.inc#L74
193             sub parse_attribute {
194 0     0 0 0 my $name = parse_name;
195 0 0       0 if (lex_peek ne '(') {
196 0         0 return $name;
197             }
198 0         0 $name .= lex_peek;
199 0         0 lex_read;
200 0         0 my $count = 1;
201 0         0 my $c = lex_peek;
202 0   0     0 while($count && length $c) {
203 0 0       0 if($c eq '(') {
204 0         0 $count++;
205             }
206 0 0       0 if($c eq ')') {
207 0         0 $count--;
208             }
209 0 0       0 if($c eq '\\') {
210             # The next char does not bump count even if it is ( or );
211             # the \\ is still captured
212             #
213 0         0 $name .= $c;
214 0         0 lex_read;
215 0         0 $c = lex_peek;
216 0 0       0 if(! length $c) {
217 0         0 goto unterminated;
218             }
219             }
220              
221             # Don't append final closing ')' on split name/val
222 0         0 $name .= $c;
223 0         0 lex_read;
224              
225 0         0 $c = lex_peek;
226             }
227              
228 0 0       0 if(!length $c) {
229 0         0 return;
230             }
231              
232 0         0 return $name;
233              
234 0         0 unterminated:
235             croak("Unterminated attribute parameter in attribute list");
236 0         0 return;
237             }
238              
239             sub parse_attributes {
240 19     19 0 44 lex_read_space;
241 19 0       43 return unless lex_peek eq ':';
242 0         0 lex_read;
243 0         0 lex_read_space;
244 0         0 my @attrs;
245 0         0 while (my $attr = parse_attribute) {
246 0         0 push @attrs, $attr;
247 0         0 lex_read_space;
248 0 0       0 if (lex_peek eq ':') {
249 0         0 lex_read;
250 0         0 lex_read_space;
251             }
252             }
253              
254 0         0 return \@attrs;
255             }
256              
257             sub parse_body {
258 19     19 0 38 my ($sigs) = @_;
259 19         27 my $body;
260              
261 19         46 lex_read_space;
262              
263 19 0       38 if (lex_peek eq '{') {
264 19         54 local $CAPRPK::{'DEFAULTS::'};
265 19 50       47 if ($sigs) {
266 19         42 lex_read;
267              
268 19         26 my $preamble = '{';
269              
270             # arguments / query params
271 19         42 my @names = map { $_->{name} } @$sigs;
  34         90  
272 19         59 $preamble .= 'my (' . join(', ', @names) . ') = @_;';
273              
274 19         37 my $index = 0;
275 19         32 for my $var (grep { defined $_->{default} } @$sigs) {
  34         84  
276             {
277 10     10   10477 no strict 'refs';
  10         30  
  10         2752  
  2         4  
278 2         14 *{ 'CAPRPK::DEFAULTS::default_' . $index } = sub () {
279             $var->{default}
280 2     3   6 };
  3         3372  
281             }
282 2         6 $preamble .= $var->{name} . ' = CAPRPK::DEFAULTS::default_' . $index . '->()' . ' unless ' . $var->{name} . ';';
283              
284 2         4 $index++;
285             }
286              
287 19         37 $preamble .= "; ();"; # fix for empty method body
288 19         52 lex_stuff($preamble);
289             }
290 19         853 $body = parse_block;
291             }
292             else {
293 0         0 die "syntax error";
294             }
295 19         68 return $body;
296             }
297              
298             1;
299              
300             __END__