File Coverage

support/SigParse.yp
Criterion Covered Total %
statement 89 96 92.7
branch 31 36 86.1
condition 7 7 100.0
subroutine 17 18 94.4
pod 1 2 50.0
total 145 159 91.1


line stmt bran cond sub pod time code
1             # SigParse.yp: Parse::Yapp input to parse signatures in Sub::Multi::Tiny
2              
3             #############################################################################
4             # Header
5              
6             %{
7              
8             # Imports {{{1
9              
10 14     14   218 use 5.006;
  14         43  
11 14     14   67 use strict;
  14         19  
  14         236  
12 14     14   58 use warnings;
  14         18  
  14         341  
13              
14 14     14   8173 use Text::Balanced qw(extract_codeblock);
  14         164853  
  14         1152  
15              
16             # Types of constraints we've seen - bit offsets
17             use enum
18             # Flags set by the parser
19 14         92 'SEEN_WHERE', # `where` clause
20             'SEEN_TYPE', # Type constraint
21             'SEEN_POS', # Positional argument
22             'SEEN_NAMED', # Named argument
23             # future: SEEN_LITERAL for signatures holding literal values
24             # instead of name matches.
25              
26             # Flags set by later processing
27             'HAS_MULTIPLE_ARITIES', # Set if there are at least two
28             # different positional arities in a
29             # set of impls
30 14     14   5897 ;
  14         12611  
31              
32             # Set bits in YYData->{SEEN}
33             sub _seen {
34 84     84   259 vec($_[0]->YYData->{SEEN}, $_[$_], 1) = 1 foreach 1..$#_;
35             }
36              
37             # }}}1
38             # Documentation {{{1
39              
40             =head1 NAME
41              
42             Sub::Multi::Tiny::SigParse - Parse::Yapp input to parse signatures in Sub::Multi::Tiny
43              
44             =head1 SYNOPSIS
45              
46             Generate the .pm file:
47              
48             yapp -m Sub::Multi::Tiny::SigParse -o lib/Sub/Multi/Tiny/SigParse.pm support/SigParse.yp
49              
50             And then:
51              
52             use Sub::Multi::Tiny::SigParse;
53             my $ast = Sub::Multi::Tiny::SigParse::Parse($signature);
54              
55             =head1 FUNCTIONS
56              
57             =cut
58              
59             # }}}1
60              
61             %}
62              
63             #############################################################################
64 46     46 0 79 # Token and precedence definitions
65 46 50       92  
66             # TODO: slurpies (prefix *, +); trailing ?, !
67              
68             # Separator (usually a comma)
69             %token SEPAR
70              
71             # Type before a variable name
72             %token TYPE
73              
74             # Parameter, named or positional
75             %token PARAM
76              
77             # "where BLOCK"
78             %token WHERE
79              
80             %%
81              
82             #############################################################################
83             # Rules
84              
85             signature:
86 2     2   38 { [] } # always return arrayref
87 43     43   1148 | parameter { [ $_[1] ] }
88 11     11   322 | parameter SEPAR signature { [ $_[1], @{$_[3]} ] }
  11         41  
89             # Permit trailing comma
90 0     0   0 | parameter SEPAR signature SEPAR { [ $_[1], @{$_[3]} ] }
  0         0  
91             ;
92              
93             parameter:
94             PARAM
95             {
96 24 100   24   526 _seen $_[0], $_[1]->{named} ? SEEN_NAMED : SEEN_POS;
97 24         200 return $_[1];
98             }
99              
100             | PARAM WHERE
101             {
102 6 100   6   225 _seen $_[0], $_[1]->{named} ? SEEN_NAMED : SEEN_POS;
103 6         55 _seen $_[0], SEEN_WHERE;
104 6         32 return +{%{$_[1]}, where=>$_[2]};
  6         51  
105             }
106              
107             | TYPE PARAM
108             {
109 15 100   15   363 _seen $_[0], $_[2]->{named} ? SEEN_NAMED : SEEN_POS;
110 15         129 _seen $_[0], SEEN_TYPE;
111 15         86 return +{%{$_[2]}, type => $_[1]};
  15         85  
112             }
113              
114             | TYPE PARAM WHERE
115             {
116 9 100   9   319 _seen $_[0], $_[2]->{named} ? SEEN_NAMED : SEEN_POS;
117 9         77 _seen $_[0], SEEN_TYPE, SEEN_WHERE;
118 9         88 return +{%{$_[2]}, where=>$_[3], type => $_[1]}
  9         60  
119             }
120 46         1262 ;
121              
122             %%
123 46         2500  
124             #############################################################################
125             # Footer
126              
127             # Tokenizer and error-reporting routine for Parse::Yapp {{{1
128              
129             # The lexer
130             sub _next_token {
131 222     222   66028 my $parser = shift;
132 222         398 my $text = $parser->YYData->{TEXT};
133              
134 222         1369 $$text =~ m/\G\s+/gc; # Skip H and V whitespace
135 222         421 $parser->YYData->{CURR_TOK_POS} = pos($$text);
136              
137 222 100 100     1406 return ('', undef) unless (pos($$text)||0) < length($$text); # EOF
138              
139 172 100       453 $$text =~ m/\G,/gc and return (SEPAR => 0); # 0 is a dummy value
140              
141 148 100       507 if($$text =~ m/\G([:]?)([\$\@\%\&\*]\w+)\b([?!]?)/gc) {
142 69         121 my $retval = {};
143 69         202 $retval->{name} = $2;
144 69         169 $retval->{named} = !!$1;
145             $retval->{reqd} = (
146             ($retval->{named} && $3 eq '!') || # Named: optional unless !
147 69   100     454 (!$retval->{named} && $3 ne '?') # Positional: reqd unless ?
148             );
149 69         213 return (PARAM => $retval);
150             }
151              
152 79 100       229 if($$text =~ m/\Gwhere\s*\{/gci) {
153 24         64 pos($$text) -= 1; # Get the lbrace back
154 24         78 my ($block) = extract_codeblock($$text); # Updates pos()
155 24 100       8914 return (WHERE => $block) if defined $block;
156 1         9 die "Saw a 'where' without a valid block after it";
157             }
158              
159             # Permit braced expressions for complex type checks
160 55 100       115 if($$text =~ m/\G\{/gc) {
161 16         42 pos($$text) -= 1; # Get the lbrace back
162 16         51 my ($block) = extract_codeblock($$text); # Updates pos()
163 16 100       4089 return (TYPE => $block) if defined $block;
164 4         32 die "Saw an opening brace without a valid block after it";
165             }
166              
167             # If the next thing is a backslash, die --- prohibit backslash to start
168             # a typecheck as a guard against '' vs "" confusion. If you want a
169             # backslash, use the {} form.
170 39 100       81 if($$text =~ m{\G\\}gc) {
171 1         9 die "Saw a backslash where I don't know what to do with it! ('' vs \"\" confusion?)";
172             }
173              
174             # Otherwise, assume a single word is a typecheck
175 38 50       227 $$text =~ m/\G(\S+)/gc and return (TYPE => $1);
176              
177 0         0 die "This should never happen! Unlexable text was: " .
178             substr($$text, pos($$text));
179             } #_next_token()
180              
181             # Report an error
182             sub _report_error {
183 2     2   28 my $parser = shift;
184 2         5 my $startpos = $parser->YYData->{CURR_TOK_POS};
185 2         10 my $endpos = pos(${ $parser->YYData->{TEXT} });
  2         5  
186 2   100     15 my $got = $parser->YYCurtok || '<end of input>';
187 2         19 my $val='';
188 2 100       6 $val = ' (' . $parser->YYCurval . ')' if $parser->YYCurval;
189              
190 2         23 my $errmsg = 'Syntax error: could not understand ' . $got . $val .
191             " at positions $startpos..$endpos";
192 2 50       7 if(ref($parser->YYExpect) eq 'ARRAY') {
193 0         0 $errmsg .= ".\nExpected one of: " . join(',', @{$parser->YYExpect});
  0         0  
194             } else {
195 2         15 $errmsg .= ':'
196             }
197              
198             # Print the text and flag the error
199 2         2 my $copy = ${ $parser->YYData->{TEXT} };
  2         5  
200 2         19 $copy =~ s/\s/ /g; # Normalize spaces so pos values line up
201 2         17 $errmsg .= "\n$copy";
202 2         7 $errmsg .= "\n" . (' ' x $startpos) . ('^' x ($endpos-$startpos));
203              
204 2         3 $errmsg .= "\n"; # No stack trace
205 2         42 die $errmsg;
206             } #_report_error()
207              
208             # }}}1
209             # Top-level parse function {{{1
210              
211             =head2 Parse
212              
213             Parse arguments. Usage:
214              
215             my $ast = Sub::Multi::Tiny::SigParse::Parse($signature);
216              
217             =cut
218              
219             sub Parse {
220 46     46 1 13911 my $text = shift;
221 46 50       108 unless(defined $text) {
222 0         0 require Carp;
223 0         0 Carp::croak 'Parse: Need a signature to parse';
224             }
225              
226 46         148 my $parser = __PACKAGE__->new;
227 46         158 my $hrData = $parser->YYData;
228              
229             # Data we use while parsing.
230              
231             # TEXT: The input text. Store it as a reference so pos() will
232             # be preserved across calls to _next_token.
233 46         357 $hrData->{TEXT} = \"$text";
234              
235             # CURR_TOK_POS: the pos() value where the current token started.
236             # Used in reporting errors.
237 46         80 $hrData->{CURR_TOK_POS} = -1;
238              
239             # SEEN: bit flags for which types of things we've seen
240 46         79 $hrData->{SEEN} = '';
241              
242 46 50       202 my $lrParms = $parser->YYParse(yylex => \&_next_token,
243             yyerror => \&_report_error,
244             (@_ ? (yydebug => $_[0]) : ()),
245             );
246 44         2423 my %retval = (seen => $hrData->{SEEN}, parms => $lrParms);
247              
248 44         829 return \%retval;
249             } #Parse()
250              
251             # }}}1
252             # Rest of the docs {{{1
253              
254             =head1 AUTHOR
255              
256             Chris White E<lt>cxw@cpan.orgE<gt>
257              
258             =head1 LICENSE
259              
260             Copyright (C) 2019 Chris White E<lt>cxw@cpan.orgE<gt>
261              
262             This library is free software; you can redistribute it and/or modify
263             it under the same terms as Perl itself.
264              
265             =cut
266              
267             # }}}1
268              
269             # vi: set fdm=marker: #