File Coverage

blib/lib/Parse/ABNF.pm
Criterion Covered Total %
statement 21 110 19.0
branch 0 60 0.0
condition 0 2 0.0
subroutine 7 10 70.0
pod 3 3 100.0
total 31 185 16.7


line stmt bran cond sub pod time code
1             package Parse::ABNF;
2 3     3   151833 use 5.012;
  3         25  
3 3     3   15 use strict;
  3         5  
  3         63  
4 3     3   13 use warnings;
  3         6  
  3         76  
5 3     3   2272 use Parse::RecDescent;
  3         92746  
  3         19  
6 3     3   122 use List::Util qw//;
  3         5  
  3         2946  
7              
8             our $VERSION = '0.20';
9             our $Grammar = q{
10              
11             {
12             sub Make {
13             my $class = shift;
14             my %opts = @_;
15              
16             # Unfold single item groups and choices
17             return $opts{value}->[0] if $class eq 'Group' and @{$opts{value}} == 1;
18             return $opts{value}->[0] if $class eq 'Choice' and @{$opts{value}} == 1;
19              
20             # These aswell
21             return $opts{value}->[0] if $class eq 'Intersection' and @{$opts{value}} == 1;
22             return $opts{value}->[0] if $class eq 'Subtraction' and @{$opts{value}} == 1;
23            
24             return { class => $class, %opts };
25             }
26             }
27              
28             parse: rulelist {
29             $return = $item[1];
30             }
31              
32             empty_line: c_wsp(s?) c_nl
33              
34             # The /^\Z/ ensures that we don't leave unparsed trailing content
35             # if there are errors in the grammar (Parse::RecDescent's default)
36              
37             rulelist: empty_line(s?) rule(s) /^\Z/ {
38             $return = $item[2];
39             }
40              
41             rule: rulename c_wsp(s?) "=" c_wsp(s?) elements c_nl empty_line(s?) {
42             $return = Make(Rule => name => $item[1], value => $item[5]);
43             }
44              
45             rule: rulename c_wsp(s?) "=/" c_wsp(s?) elements c_nl empty_line(s?) {
46             $return = Make(Rule => name => $item[1], value => $item[5], combine => 'choice');
47            
48             }
49              
50             # Generate an error message if the rule production is not matched.
51             rule:
52              
53             rulename: /[a-zA-Z][a-zA-Z0-9-]*/ {
54             $return = $item[1];
55             }
56              
57             # n exactly
58             repetition: /\d+/ element {
59             $return = Make(Repetition => min => $item[1], max => $item[1], value => $item[2]);
60             }
61              
62             # n to m
63             repetition: /\d+/ "*" /\d+/ element {
64             $return = Make(Repetition => min => $item[1], max => $item[3], value => $item[4]);
65             }
66              
67             # 0 to n
68             repetition: "*" /\d+/ element {
69             $return = Make(Repetition => min => 0, max => $item[2], value => $item[3]);
70             }
71              
72             # n or more
73             repetition: /\d+/ "*" element {
74             $return = Make(Repetition => min => $item[1], max => undef, value => $item[3]);
75             }
76              
77             # zero or more
78             repetition: "*" element {
79             $return = Make(Repetition => min => 0, max => undef, value => $item[2]);
80             }
81              
82             # exactly one
83             repetition: element {
84             $return = $item[1];
85             }
86              
87             #
88             elements: alternation c_wsp(s?) {
89             $return = $item[1];
90             }
91              
92             alt_op: "/"
93              
94             #
95             alternation: concatenation (c_wsp(s?) alt_op c_wsp(s?) concatenation)(s?) {
96             $return = Make(Choice => value => [$item[1], @{$item[2]}]);
97             }
98              
99             #
100             intersection: subtraction (c_wsp(s?) "&" c_wsp(s?) subtraction)(s?) {
101             $return = Make(Intersection => value => [$item[1], @{$item[2]}]);
102             }
103              
104             #
105             subtraction: repetition (c_wsp(s) "-" c_wsp(s) repetition)(?) {
106             $return = Make(Subtraction => value => [$item[1], @{$item[2]}]);
107             }
108            
109             #
110             concatenation_: intersection (c_wsp(s) intersection)(s?) {
111             $return = Make(Group => value => [$item[1], @{$item[2]}]);
112             }
113              
114             #
115             concatenation: repetition (c_wsp(s) repetition)(s?) {
116             $return = Make(Group => value => [$item[1], @{$item[2]}]);
117             }
118              
119             #
120             element: ref_val | group | option | char_val | num_val | prose_val {
121             $return = $item[1];
122             }
123              
124             ref_val: rulename {
125             $return = Make(Reference => name => $item[1]);
126             }
127              
128             #
129             group: "(" c_wsp(s?) alternation c_wsp(s?) ")" {
130             $return = $item[3];
131             }
132              
133             #
134             option: "[" c_wsp(s?) alternation c_wsp(s?) "]" {
135             $return = Make(Repetition => min => 0, max => 1, value => $item[3]);
136             }
137              
138             c_wsp: /[ \t]/
139              
140             c_wsp: c_nl /[ \t]/
141              
142             newline: "\n"
143              
144             c_nl: newline
145              
146             c_nl: comment
147              
148             comment: /;[ \t\x21-\x7e]*/ newline
149              
150             char_val: '"' /[\x20-\x21\x23-\x7E]*/ '"' {
151             $return = Make(Literal => value => $item[2]);
152             }
153              
154             num_val: bin_val | dec_val | hex_val {
155             $return = $item[1];
156             }
157              
158             bin_val: "%b" /[01]+/ "-" /[01]+/ {
159             $return = Make(Range => type => 'binary', min => $item[2], max => $item[4]);
160             }
161              
162             dec_val: "%d" /\d+/ "-" /\d+/ {
163             $return = Make(Range => type => 'decimal', min => $item[2], max => $item[4]);
164             }
165              
166             hex_val: "%x" /[0-9a-fA-F]+/ "-" /[0-9a-fA-F]+/ {
167             $return = Make(Range => type => 'hex', min => $item[2], max => $item[4]);
168             }
169              
170             bin_val: "%b" /[01]+/ /(?:\.[01]+)*/ {
171             $return = Make(String => type => 'binary', value => [split/\./, "$item[2]$item[3]"]);
172             }
173              
174             dec_val: "%d" /\d+/ /(?:\.\d+)*/ {
175             $return = Make(String => type => 'decimal', value => [split/\./, "$item[2]$item[3]"]);
176             }
177              
178             hex_val: "%x" /[0-9a-fA-F]+/ /(?:\.[0-9a-fA-F]+)*/ {
179             $return = Make(String => type => 'hex', value => [split/\./, "$item[2]$item[3]"]);
180             }
181              
182             prose_val: "<" /[\x20-\x3d\x3f-\x7e]*/ ">" {
183             $return = Make(ProseValue => value => $item[2]);
184             }
185              
186             };
187              
188             our $CoreRulesGrammar = q{
189              
190             ALPHA = %x41-5A / %x61-7A
191             BIT = "0" / "1"
192             CHAR = %x01-7F
193             CR = %x0D
194             CRLF = CR LF
195             CTL = %x00-1F / %x7F
196             DIGIT = %x30-39
197             DQUOTE = %x22
198             HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
199             HTAB = %x09
200             LF = %x0A
201             LWSP = *(WSP / CRLF WSP)
202             OCTET = %x00-FF
203             SP = %x20
204             VCHAR = %x21-7E
205             WSP = SP / HTAB
206              
207             };
208              
209             # TODO: Perhaps this is not such a good idea, users may attempt to
210             # modify the data and thus affect simultaneously running modules.
211             our $CoreRules = do {
212             __PACKAGE__->new->parse( $CoreRulesGrammar );
213             };
214              
215             sub new {
216 5     5 1 388 my $class = shift;
217 5         11 local $Parse::RecDescent::skip = '';
218 5         23 bless { _p => Parse::RecDescent->new($Grammar) }, $class;
219             }
220              
221             sub parse {
222 5     5 1 800506 my $self = shift;
223 5         16 my $string = shift;
224 5         76 my $result = $self->{_p}->parse($string);
225 5         2663892 return $result;
226             }
227              
228             sub parse_to_grammar_formal {
229 0     0 1   my ($self, $string, %options) = @_;
230 0           my $result = $self->{_p}->parse($string);
231            
232 0           require Grammar::Formal;
233 0           my $g = Grammar::Formal->new;
234 0           my $o = {};
235              
236 0           my @rules = map { _abnf2g($_, $g, $o) } @$result;
  0            
237              
238             ###################################################################
239             # Install all rules in the grammar
240             ###################################################################
241 0           for my $rule (@rules) {
242 0 0         if ($g->rules->{$rule->name}) {
243 0           my $old = $g->rules->{$rule->name};
244 0           my $new = Grammar::Formal::Rule->new(
245             name => $rule->name,
246             p => $g->Choice($old->p, $rule->p),
247             );
248 0           $g->set_rule($rule->name, $new);
249             } else {
250 0           $g->set_rule($rule->name, $rule);
251             }
252             }
253            
254             ###################################################################
255             # Add missing Core rules if requested
256             ###################################################################
257 0 0         if ($options{core}) {
258 0           my %referenced;
259 0           my @todo = values %{ $g->{rules} };
  0            
260 0           while (my $c = pop @todo) {
261 0 0         if ($c->isa('Grammar::Formal::Reference')) {
    0          
    0          
262 0           $referenced{$c->name}++;
263             } elsif ($c->isa('Grammar::Formal::Unary')) {
264 0           push @todo, $c->p;
265             } elsif ($c->isa('Grammar::Formal::Binary')) {
266 0           push @todo, $c->p1, $c->p2;
267             }
268             }
269            
270             # TODO: might be more sensible to convert the Core rules first
271             # and then simply throw them into the todo list above.
272            
273 0 0         $referenced{WSP}++ if $referenced{LWSP};
274 0 0         $referenced{CRLF}++ if $referenced{LWSP};
275 0 0         $referenced{HTAB}++ if $referenced{WSP};
276 0 0         $referenced{SP}++ if $referenced{WSP};
277 0 0         $referenced{CR}++ if $referenced{CRLF};
278 0 0         $referenced{LF}++ if $referenced{CRLF};
279 0 0         $referenced{DIGIT}++ if $referenced{HEXDIG};
280            
281 0           my @core_rules = map { _abnf2g($_, $g, $o) } @$CoreRules;
  0            
282              
283 0           for my $rule (@core_rules) {
284 0 0         next if $g->rules->{$rule->name};
285 0 0         next unless $referenced{$rule->name};
286 0           $g->set_rule($rule->name, $rule);
287             }
288             }
289            
290 0           return $g;
291             }
292              
293             sub _abnf2g {
294 0     0     my ($p, $g, $options) = @_;
295            
296 0   0       $options->{pos} //= 1;
297 0           my $pos = $options->{pos}++;
298            
299 0           for ($p->{class}) {
300 0 0         if ($_ eq "Group") {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
301 0           my @values = map { _abnf2g($_, $g, $options) } @{ $p->{value} };
  0            
  0            
302 0           my $group = $g->Empty;
303 0           while (@values) {
304 0           $group = $g->Group(pop(@values), $group, position => $pos);
305             }
306 0           return $group;
307             }
308             elsif ($_ eq "Choice") {
309 0           my @values = map { _abnf2g($_, $g, $options) } @{ $p->{value} };
  0            
  0            
310 0           my $choice = $g->NotAllowed;
311 0           while (@values) {
312 0           $choice = $g->Choice(pop(@values), $choice, position => $pos);
313             }
314 0           return $choice;
315             }
316             elsif ($_ eq "Repetition") {
317 0 0         if (defined $p->{max}) {
318             return Grammar::Formal::BoundedRepetition->new(
319             min => $p->{min},
320             max => $p->{max},
321 0           p => _abnf2g($p->{value}, $g, $options),
322             position => $pos,
323             );
324             } else {
325             return Grammar::Formal::SomeOrMore->new(
326             min => $p->{min},
327 0           p => _abnf2g($p->{value}, $g, $options),
328             position => $pos,
329             );
330             }
331             }
332             elsif ($_ eq "Rule") {
333             return Grammar::Formal::Rule->new(
334             name => $p->{name},
335 0           p => _abnf2g($p->{value}, $g, $options),
336             position => $pos,
337             );
338             }
339             elsif ($_ eq "Reference") {
340             return Grammar::Formal::Reference->new(
341             name => $p->{name},
342 0           position => $pos,
343             );
344             }
345             elsif ($_ eq "Literal") {
346             return Grammar::Formal::AsciiInsensitiveString->new(
347             value => $p->{value},
348 0           position => $pos,
349             );
350             }
351             elsif ($_ eq "ProseValue") {
352             return Grammar::Formal::ProseValue->new(
353             value => $p->{value},
354 0           position => $pos,
355             );
356             }
357             elsif ($_ eq "String") {
358 0           my @items;
359 0           for ($p->{type}) {
360 0 0         if ($_ eq "decimal") {
    0          
361 0           @items = map { $_ } @{ $p->{value} };
  0            
  0            
362             }
363             elsif ($_ eq "hex") {
364 0           @items = map { hex $_ } @{ $p->{value} };
  0            
  0            
365             }
366             else {
367             ...
368 0           }
369             }
370             my @values = map {
371 0           Grammar::Formal::Range->new(
  0            
372             min => $_,
373             max => $_,
374             position => $pos,
375             )
376             } @items;
377            
378 0           my $group = $g->Empty;
379 0           while (@values) {
380 0           $group = $g->Group(pop(@values), $group, position => $pos);
381             }
382              
383 0           return $group;
384             }
385             elsif ($_ eq "Range") {
386             return Grammar::Formal::Range->new(
387             min => hex $p->{min},
388             max => hex $p->{max},
389             position => $pos,
390 0 0         ) if $p->{type} eq 'hex';
391             return Grammar::Formal::Range->new(
392             min => $p->{min},
393             max => $p->{max},
394             position => $pos,
395 0 0         ) if $p->{type} eq 'decimal';
396 0           ...;
397             }
398             elsif ($_ eq "Intersection") {
399 0           my @values = map { _abnf2g($_, $g, $options) } @{ $p->{value} };
  0            
  0            
400 0     0     return List::Util::reduce { Grammar::Formal::Intersection->new(
401             p1 => $a,
402             p2 => $b,
403             position => $pos,
404 0           ) } @values;
405             ...
406 0           }
407             elsif ($_ eq "Subtraction") {
408             return Grammar::Formal::Subtraction->new(
409             p1 => _abnf2g($p->{value}[0], $g, $options),
410 0           p2 => _abnf2g($p->{value}[1], $g, $options),
411             position => $pos,
412             );
413             ...
414 0           }
415             }
416             }
417              
418             1;
419              
420             __END__