File Coverage

blib/lib/Parse/ABNF.pm
Criterion Covered Total %
statement 21 166 12.6
branch 0 96 0.0
condition 0 2 0.0
subroutine 7 12 58.3
pod 4 4 100.0
total 32 280 11.4


line stmt bran cond sub pod time code
1             package Parse::ABNF;
2 3     3   180080 use 5.012;
  3         21  
3 3     3   15 use strict;
  3         4  
  3         79  
4 3     3   14 use warnings;
  3         5  
  3         76  
5 3     3   3065 use Parse::RecDescent;
  3         112361  
  3         21  
6 3     3   136 use List::Util qw//;
  3         6  
  3         5088  
7              
8             our $VERSION = '0.30';
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 480 my $class = shift;
217 5         9 local $Parse::RecDescent::skip = '';
218 5         23 bless { _p => Parse::RecDescent->new($Grammar) }, $class;
219             }
220              
221             sub parse {
222 5     5 1 939721 my $self = shift;
223 5         19 my $string = shift;
224 5         81 my $result = $self->{_p}->parse($string);
225 5         2901893 return $result;
226             }
227              
228             sub _to_jet {
229 0     0     my ($p) = @_;
230              
231 0           for ($p->{class}) {
232 0 0         if ($_ eq "Group") {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
233 0           my @values = map { _to_jet($_) } @{ $p->{value} };
  0            
  0            
234 0           return ["group", {}, \@values];
235             }
236             elsif ($_ eq "Choice") {
237 0           my @values = map { _to_jet($_) } @{ $p->{value} };
  0            
  0            
238 0           return ["choice", {}, \@values];
239             }
240             elsif ($_ eq "Intersection") {
241 0           my @values = map { _to_jet($_) } @{ $p->{value} };
  0            
  0            
242 0           return ["conjunction", {}, \@values];
243             }
244             elsif ($_ eq "Subtraction") {
245 0           my @values = map { _to_jet($_) } @{ $p->{value} };
  0            
  0            
246 0           return ["exclusion", {}, \@values];
247             }
248             elsif ($_ eq "Repetition") {
249 0           my @values = map { _to_jet($_) } $p->{value};
  0            
250             return ["repetition", {
251             min => $p->{min},
252             max => $p->{max},
253 0           }, \@values];
254             }
255             elsif ($_ eq "Rule") {
256 0           my @values = map { _to_jet($_) } $p->{value};
  0            
257 0 0         if (exists $p->{combine}) {
258             ...
259 0           }
260             return ["rule", {
261             name => $p->{name},
262 0           }, \@values];
263             }
264             elsif ($_ eq "Reference") {
265             return ["ref", {
266             name => $p->{name},
267 0           }, []];
268             }
269             elsif ($_ eq "Literal") {
270             return ["asciiInsensitiveString", {
271             text => $p->{value}
272 0           }, []];
273             }
274             elsif ($_ eq "ProseValue") {
275             return ["prose", {
276             text => $p->{value},
277 0           }, []];
278             }
279             elsif ($_ eq "String") {
280 0           my @items;
281 0           for ($p->{type}) {
282 0 0         if ($_ eq "decimal") {
    0          
283 0           @items = map { $_ } @{ $p->{value} };
  0            
  0            
284             }
285             elsif ($_ eq "hex") {
286 0           @items = map { hex $_ } @{ $p->{value} };
  0            
  0            
287             }
288             else {
289             ...
290 0           }
291             }
292              
293             my @values = map {
294 0           ["range", {
  0            
295             first => $_,
296             last => $_,
297             }, [] ]
298             } @items;
299              
300 0 0         return $values[0] if @values == 1;
301              
302 0           return ["group", {}, \@values];
303             }
304             elsif ($_ eq "Range") {
305             return ["range", {
306             first => hex $p->{min},
307             last => hex $p->{max},
308 0 0         }, []] if $p->{type} eq 'hex';
309              
310             return ["range", {
311             first => $p->{min},
312             last => $p->{max},
313 0 0         }, []] if $p->{type} eq 'decimal';
314              
315             ...
316 0           }
317             }
318              
319             ...
320 0           }
321              
322             sub parse_to_jet {
323 0     0 1   my ($self, $string, %options) = @_;
324              
325 0           my $result = $self->{_p}->parse($string);
326              
327 0           my @core_rules = map { _to_jet($_) } @$CoreRules;
  0            
328 0           $_->[1]{combine} = 'fallback' for @core_rules;
329              
330             my $g = [
331             'grammar',
332             {},
333             [
334             ($options{core} ? @core_rules : ()),
335 0 0         map { _to_jet($_) } @$result
  0            
336             ]
337             ];
338              
339             # _make_jet_binary($g);
340              
341 0           return $g;
342             }
343              
344             sub parse_to_grammar_formal {
345 0     0 1   my ($self, $string, %options) = @_;
346 0           my $result = $self->{_p}->parse($string);
347            
348 0           require Grammar::Formal;
349 0           my $g = Grammar::Formal->new;
350 0           my $o = {};
351              
352 0           my @rules = map { _abnf2g($_, $g, $o) } @$result;
  0            
353              
354             ###################################################################
355             # Install all rules in the grammar
356             ###################################################################
357 0           for my $rule (@rules) {
358 0 0         if ($g->rules->{$rule->name}) {
359 0           my $old = $g->rules->{$rule->name};
360 0           my $new = Grammar::Formal::Rule->new(
361             name => $rule->name,
362             p => $g->Choice($old->p, $rule->p),
363             );
364 0           $g->set_rule($rule->name, $new);
365             } else {
366 0           $g->set_rule($rule->name, $rule);
367             }
368             }
369            
370             ###################################################################
371             # Add missing Core rules if requested
372             ###################################################################
373 0 0         if ($options{core}) {
374 0           my %referenced;
375 0           my @todo = values %{ $g->{rules} };
  0            
376 0           while (my $c = pop @todo) {
377 0 0         if ($c->isa('Grammar::Formal::Reference')) {
    0          
    0          
378 0           $referenced{$c->name}++;
379             } elsif ($c->isa('Grammar::Formal::Unary')) {
380 0           push @todo, $c->p;
381             } elsif ($c->isa('Grammar::Formal::Binary')) {
382 0           push @todo, $c->p1, $c->p2;
383             }
384             }
385            
386             # TODO: might be more sensible to convert the Core rules first
387             # and then simply throw them into the todo list above.
388            
389 0 0         $referenced{WSP}++ if $referenced{LWSP};
390 0 0         $referenced{CRLF}++ if $referenced{LWSP};
391 0 0         $referenced{HTAB}++ if $referenced{WSP};
392 0 0         $referenced{SP}++ if $referenced{WSP};
393 0 0         $referenced{CR}++ if $referenced{CRLF};
394 0 0         $referenced{LF}++ if $referenced{CRLF};
395 0 0         $referenced{DIGIT}++ if $referenced{HEXDIG};
396            
397 0           my @core_rules = map { _abnf2g($_, $g, $o) } @$CoreRules;
  0            
398              
399 0           for my $rule (@core_rules) {
400 0 0         next if $g->rules->{$rule->name};
401 0 0         next unless $referenced{$rule->name};
402 0           $g->set_rule($rule->name, $rule);
403             }
404             }
405            
406 0           return $g;
407             }
408              
409             sub _abnf2g {
410 0     0     my ($p, $g, $options) = @_;
411            
412 0   0       $options->{pos} //= 1;
413 0           my $pos = $options->{pos}++;
414            
415 0           for ($p->{class}) {
416 0 0         if ($_ eq "Group") {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
417 0           my @values = map { _abnf2g($_, $g, $options) } @{ $p->{value} };
  0            
  0            
418 0           my $group = $g->Empty;
419 0           while (@values) {
420 0           $group = $g->Group(pop(@values), $group, position => $pos);
421             }
422 0           return $group;
423             }
424             elsif ($_ eq "Choice") {
425 0           my @values = map { _abnf2g($_, $g, $options) } @{ $p->{value} };
  0            
  0            
426 0           my $choice = $g->NotAllowed;
427 0           while (@values) {
428 0           $choice = $g->Choice(pop(@values), $choice, position => $pos);
429             }
430 0           return $choice;
431             }
432             elsif ($_ eq "Repetition") {
433 0 0         if (defined $p->{max}) {
434             return Grammar::Formal::BoundedRepetition->new(
435             min => $p->{min},
436             max => $p->{max},
437 0           p => _abnf2g($p->{value}, $g, $options),
438             position => $pos,
439             );
440             } else {
441             return Grammar::Formal::SomeOrMore->new(
442             min => $p->{min},
443 0           p => _abnf2g($p->{value}, $g, $options),
444             position => $pos,
445             );
446             }
447             }
448             elsif ($_ eq "Rule") {
449             return Grammar::Formal::Rule->new(
450             name => $p->{name},
451 0           p => _abnf2g($p->{value}, $g, $options),
452             position => $pos,
453             );
454             }
455             elsif ($_ eq "Reference") {
456             return Grammar::Formal::Reference->new(
457             name => $p->{name},
458 0           position => $pos,
459             );
460             }
461             elsif ($_ eq "Literal") {
462             return Grammar::Formal::AsciiInsensitiveString->new(
463             value => $p->{value},
464 0           position => $pos,
465             );
466             }
467             elsif ($_ eq "ProseValue") {
468             return Grammar::Formal::ProseValue->new(
469             value => $p->{value},
470 0           position => $pos,
471             );
472             }
473             elsif ($_ eq "String") {
474 0           my @items;
475 0           for ($p->{type}) {
476 0 0         if ($_ eq "decimal") {
    0          
477 0           @items = map { $_ } @{ $p->{value} };
  0            
  0            
478             }
479             elsif ($_ eq "hex") {
480 0           @items = map { hex $_ } @{ $p->{value} };
  0            
  0            
481             }
482             else {
483             ...
484 0           }
485             }
486             my @values = map {
487 0           Grammar::Formal::Range->new(
  0            
488             min => $_,
489             max => $_,
490             position => $pos,
491             )
492             } @items;
493            
494 0           my $group = $g->Empty;
495 0           while (@values) {
496 0           $group = $g->Group(pop(@values), $group, position => $pos);
497             }
498              
499 0           return $group;
500             }
501             elsif ($_ eq "Range") {
502             return Grammar::Formal::Range->new(
503             min => hex $p->{min},
504             max => hex $p->{max},
505             position => $pos,
506 0 0         ) if $p->{type} eq 'hex';
507             return Grammar::Formal::Range->new(
508             min => $p->{min},
509             max => $p->{max},
510             position => $pos,
511 0 0         ) if $p->{type} eq 'decimal';
512 0           ...;
513             }
514             elsif ($_ eq "Intersection") {
515 0           my @values = map { _abnf2g($_, $g, $options) } @{ $p->{value} };
  0            
  0            
516 0     0     return List::Util::reduce { Grammar::Formal::Intersection->new(
517             p1 => $a,
518             p2 => $b,
519             position => $pos,
520 0           ) } @values;
521             ...
522 0           }
523             elsif ($_ eq "Subtraction") {
524             return Grammar::Formal::Subtraction->new(
525             p1 => _abnf2g($p->{value}[0], $g, $options),
526 0           p2 => _abnf2g($p->{value}[1], $g, $options),
527             position => $pos,
528             );
529             ...
530 0           }
531             }
532             }
533              
534             1;
535              
536             __END__