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