File Coverage

blib/lib/Method/Signatures/Simple/ParseKeyword.pm
Criterion Covered Total %
statement 160 200 80.0
branch 27 66 40.9
condition 18 26 69.2
subroutine 24 26 92.3
pod 0 6 0.0
total 229 324 70.6


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