File Coverage

blib/lib/Parse/ABNF.pm
Criterion Covered Total %
statement 19 90 21.1
branch 0 40 0.0
condition n/a
subroutine 6 8 75.0
pod 3 3 100.0
total 28 141 19.8


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