File Coverage

blib/lib/Method/Signatures/Simple/ParseKeyword.pm
Criterion Covered Total %
statement 170 210 80.9
branch 31 68 45.5
condition 23 32 71.8
subroutine 25 27 92.5
pod 0 6 0.0
total 249 343 72.5


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