File Coverage

xdr.yp
Criterion Covered Total %
statement 78 122 63.9
branch 18 30 60.0
condition 3 5 60.0
subroutine 37 61 60.6
pod 2 2 100.0
total 138 220 62.7


line stmt bran cond sub pod time code
1              
2              
3             %start specification
4              
5             %{
6              
7             %}
8              
9              
10 1     1 1 3 %%
11 1 50       4  
12             specification:
13             definitions
14 0     0   0 | definitions TRAILING_COMMENT { [ @{ $_[1] }, $_[2] ] }
  0         0  
15             ;
16              
17             definitions:
18 1     1   15 definition { [ $_[1] ] }
19 8     8   177 | definition definitions { unshift @{$_[2]}, $_[1]; $_[2] }
  8         10  
  8         8  
20             ;
21              
22             definition:
23 0     0   0 PASSTHROUGH { +{ def => 'passthrough', value => $_[1] } }
24 0     0   0 | PREPROC { +{ def => 'preprocessor', value => $_[1] } }
25             | typeDef
26             | constantDef
27             ;
28              
29             typeDef:
30             'typedef' declaration ';' {
31             +{ def => 'typedef',
32             name => delete $_[2]->{name},
33             definition => $_[2],
34             comments => $_[1]->{comments},
35             location => $_[1]->{location},
36 3     3   115 trailing_comments => $_[3]->{comments} } }
37             | 'enum' IDENT enumBody ';' {
38             +{ def => 'enum',
39             name => $_[2],
40             definition => {
41             type => { spec => 'enum', declaration => $_[3] }
42             },
43             comments => $_[1]->{comments},
44             location => $_[1]->{location},
45 1     1   45 trailing_comments => $_[4]->{comments} } }
46             | 'struct' IDENT structBody ';' {
47             +{ def => 'struct',
48             name => $_[2],
49             definition => {
50             type => { spec => 'struct', declaration => $_[3] }
51             },
52             comments => delete $_[1]->{comments},
53             location => $_[1]->{location},
54 1     1   30 trailing_comments => $_[4]->{comments} } }
55             | 'union' IDENT switch ';' {
56             +{ def => 'union',
57             name => $_[2],
58             definition => {
59             type => { spec => 'union', declaration => $_[3] }
60             },
61             comments => $_[1]->{comments},
62             location => $_[1]->{location},
63 1     1   27 trailing_comments => $_[4]->{comments} } }
64             ;
65              
66             constantDef:
67             'const' IDENT '=' CONST ';' {
68             # What to do with comments before the '=' sign?
69             +{ def => 'const',
70             name => $_[2],
71             value => $_[4],
72             type => 'numeric',
73             comments => $_[1]->{comments},
74             location => $_[1]->{location},
75 2     2   70 trailing_comments => $_[5]->{comments} } }
76             | 'const' IDENT '=' IDENT ';' {
77             # What to do with comments before the '=' sign?
78             +{ def => 'const',
79             name => $_[2],
80             value => $_[4],
81             type => 'symbolic',
82             comments => $_[1]->{comments},
83             location => $_[1]->{location},
84 1     1   29 trailing_comments => $_[5]->{comments} } }
85             ;
86              
87             switch:
88             'switch' '(' declaration ')' '{' caseBody '}' {
89             +{ discriminator => {
90             name => delete $_[3]->{name},
91             declaration => $_[3],
92             comments => $_[2]->{comments},
93             trailing_comments => $_[4]->{comments}
94             },
95             members => {
96             cases => $_[6]->{clauses},
97             default => $_[6]->{default},
98             comments => $_[5]->{comments},
99             location => $_[5]->{location},
100             trailing_comments => $_[7]->{comments}
101             },
102             comments => $_[1]->{comments},
103             location => $_[1]->{location},
104 2     2   73 trailing_comments => $_[7]->{comments} } }
105             ;
106              
107             caseBody:
108 1     1   22 caseClauses { +{ clauses => $_[1] } }
109 1     1   24 | caseClauses defaultClause { +{ clauses => $_[1], default => $_[2] } }
110             ;
111              
112             caseClauses:
113 2     2   31 caseClause { [ $_[1] ] }
114 3     3   65 | caseClause caseClauses { unshift @{ $_[2] }, $_[1]; $_[2] }
  3         6  
  3         3  
115             ;
116              
117             caseClause:
118             'case' value ':' declaration ';' {
119 5     5   208 $_[2]->{trailing_comments} = $_[3]->{comments};
120             +{ value => $_[2],
121             name => delete $_[4]->{name},
122             declaration => $_[4],
123             comments => $_[1]->{comments},
124             location => $_[1]->{location},
125 5         31 trailing_comments => $_[5]->{comments} } }
126             ;
127              
128             defaultClause:
129             'default' ':' declaration ';' {
130             # What to do with comments on the ':'?
131             +{ name => delete $_[3]->{name},
132             declaration => $_[3],
133             comments => $_[1]->{comments},
134             location => $_[1]->{location},
135 1     1   36 trailing_comments => $_[4]->{comments} } }
136             ;
137              
138             structBody:
139             '{' structItems '}' { +{ members => $_[2],
140             comments => $_[1]->{comments},
141             location => $_[1]->{location},
142 2     2   118 trailing_comments => $_[3]->{comments} } }
143             ;
144              
145             structItems:
146 2     2   33 structItem { [ $_[1] ] }
147 3     3   76 | structItem structItems { unshift @{ $_[2] }, $_[1]; $_[2] }
  3         5  
  3         3  
148             ;
149              
150             structItem:
151             declaration ';' {
152             +{ name => delete $_[1]->{name},
153             declaration => $_[1],
154 5     5   188 trailing_comments => $_[2]->{comments} } }
155             ;
156              
157             enumBody:
158             '{' enumItems '}' { +{ elements => $_[2],
159             comments => $_[1]->{comments},
160             location => $_[1]->{location},
161 2     2   128 trailing_comments => $_[3]->{comments} } }
162             ;
163              
164             enumItems:
165 2     2   34 enumItem { [ $_[1] ] }
166 2     2   99 | enumItem ',' enumItems { $_[1]->{trailing_comments} = $_[2]->{comments};
167 2         3 unshift @{ $_[3] }, $_[1]; $_[3] }
  2         6  
  2         4  
168             ;
169              
170             enumItem:
171             IDENT '=' value {
172             # What to do with comments on the '=' sign?
173 4     4   210 $_[1]->{trailing_comments} = $_[2]->{comments};
174             +{ name => $_[1],
175             value => $_[3],
176             comments => delete $_[1]->{comments},
177 4         13 location => $_[1]->{location} } }
178             ;
179              
180             typeSpecifier:
181 4     4   104 'int' { +{ spec => 'primitive', name => 'int', unsigned => 0, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
182 0     0   0 | 'unsigned' { +{ spec => 'primitive', name => 'int', unsigned => 1, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
183 0     0   0 | 'unsigned' 'int' { +{ spec => 'primitive', name => 'int', unsigned => 1, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
184 3     3   83 | 'char' { +{ spec => 'primitive', name => 'char', unsigned => 0, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
185 0     0   0 | 'unsigned' 'char' { +{ spec => 'primitive', name => 'char', unsigned => 1, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
186 0     0   0 | 'short' { +{ spec => 'primitive', name => 'short', unsigned => 0, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
187 0     0   0 | 'unsigned' 'short' { +{ spec => 'primitive', name => 'short', unsigned => 1, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
188 0     0   0 | 'long' { +{ spec => 'primitive', name => 'long', unsigned => 0, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
189 0     0   0 | 'unsigned' 'long' { +{ spec => 'primitive', name => 'long', unsigned => 1, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
190 0     0   0 | 'hyper' { +{ spec => 'primitive', name => 'hyper', unsigned => 0, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
191 0     0   0 | 'unsigned' 'hyper' { +{ spec => 'primitive', name => 'hyper', unsigned => 1, comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
192 0     0   0 | 'float' { +{ spec => 'primitive', name => 'float', comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
193 0     0   0 | 'double' { +{ spec => 'primitive', name => 'double', comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
194 0     0   0 | 'quadruple' { +{ spec => 'primitive', name => 'quadruple', comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
195 0     0   0 | 'bool' { +{ spec => 'primitive', name => 'bool', comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
196 1     1   45 | 'enum' enumBody { +{ spec => 'enum', declaration => $_[2], comments => $_[1]->{comments}, location => $_[1]->{location} } }
197 1     1   32 | 'struct' structBody { +{ spec => 'struct', declaration => $_[2], comments => $_[1]->{comments}, location => $_[1]->{location} } }
198 1     1   41 | 'union' switch { +{ spec => 'union', declaration => $_[2], comments => $_[1]->{comments}, location => $_[1]->{location} } }
199 3     3   92 | IDENT { +{ spec => 'named', name => $_[1], comments => delete $_[1]->{comments}, location => $_[1]->{location} } }
200             ;
201              
202             value:
203             CONST
204             | IDENT
205             ;
206              
207             declaration:
208             typeSpecifier IDENT {
209             +{ name => $_[2],
210             type => $_[1],
211             comments => delete $_[1]->{comments},
212 12     12   204 location => $_[1]->{location} } }
213             | typeSpecifier IDENT '[' value ']' {
214             +{ name => $_[2],
215             type => $_[1],
216             array => 1,
217             count => $_[4],
218             variable => 0,
219             comments => delete $_[1]->{comments},
220 0     0   0 location => $_[1]->{location} } }
221             | typeSpecifier IDENT '<' '>' {
222             +{ name => $_[2],
223             type => $_[1],
224             array => 1,
225             max => undef,
226             variable => 1,
227             comments => delete $_[1]->{comments},
228 0     0   0 location => $_[1]->{location} } }
229             | typeSpecifier IDENT '<' value '>' {
230             +{ name => $_[2],
231             type => $_[1],
232             array => 1,
233             max => $_[4],
234             variable => 1,
235             comments => delete $_[1]->{comments},
236 0     0   0 location => $_[1]->{location} } }
237             | 'opaque' IDENT '[' value ']' {
238             +{ name => $_[2],
239             type => { spec => 'primitive', name => $_[1] },
240             count => $_[4],
241             variable => 0,
242             comments => delete $_[1]->{comments},
243 1     1   30 location => $_[1]->{location} } }
244             | 'opaque' IDENT '<' '>' {
245             +{ name => $_[2],
246             type => { spec => 'primitive', name => $_[1] },
247             max => undef,
248             variable => 1,
249             comments => delete $_[1]->{comments},
250 0     0   0 location => $_[1]->{location} } }
251             | 'opaque' IDENT '<' value '>' {
252             +{ name => $_[2],
253             type => { spec => 'primitive', name => $_[1] },
254             max => $_[4],
255             variable => 1,
256             comments => delete $_[1]->{comments},
257 0     0   0 location => $_[1]->{location} } }
258             | 'string' IDENT '<' '>' {
259             +{ name => $_[2],
260             type => { spec => 'primitive', name => $_[1] },
261             max => undef,
262             variable => 1,
263             comments => delete $_[1]->{comments},
264 2     2   69 location => $_[1]->{location} } }
265             | 'string' IDENT '<' value '>' {
266             +{ name => $_[2],
267             type => { spec => 'primitive', name => $_[1] },
268             max => $_[4],
269             variable => 1,
270             comments => delete $_[1]->{comments},
271 0     0   0 location => $_[1]->{location} } }
272             | typeSpecifier '*' IDENT {
273             +{ name => $_[3],
274             type => $_[1],
275             pointer => 1,
276             comments => delete $_[1]->{comments},
277 1     1   61 location => $_[1]->{location} } }
278             | 'void' {
279             +{ type => { spec => 'primitive', name => $_[1] },
280             comments => delete $_[1]->{comments},
281 0     0   0 location => $_[1]->{location} } }
282 1         308 ;
283              
284              
285 1         73 %%
286              
287              
288             sub _Lexer {
289 131     131   117 my ($fh, $parser) = @_;
290 131         140 my $yydata = $parser->YYData;
291 131         580 my @comments;
292             my $comment;
293 131         0 my $comment_start;
294              
295 131   100     163 $yydata->{LINENO} //= 0;
296 131         85 while (1) {
297 172 100       177 unless ($yydata->{INPUT}) {
298 42         108 $yydata->{INPUT} = <$fh>;
299 42         34 $yydata->{LINENO}++;
300 42         37 $yydata->{COLNO} = 1;
301              
302 42 50 33     51 if (@comments and not $yydata->{INPUT}) {
303 0         0 return ('TRAILING_COMMENT', {
304             content => '',
305             comments => \@comments,
306             location => $comment_start
307             });
308             }
309              
310 42 100       46 return ('', undef) unless $yydata->{INPUT};
311              
312 41 50       51 if ($yydata->{INPUT} =~ s/^(%.*)//) {
313             return ('PASSTHROUGH', {
314             content => $1,
315             comments => \@comments,
316 0         0 location => [ $yydata->{LINENO}, 1 ]
317             });
318             }
319 41 50       53 if ($yydata->{INPUT} =~ s/^(#.*)//) {
320             return ('PREPROC', {
321             content => $1,
322             comments => \@comments,
323 0         0 location => [ $yydata->{LINENO}, 1 ]
324             });
325             }
326             }
327              
328 171         286 $yydata->{INPUT} =~ s/^\s+//;
329 171         216 $yydata->{COLNO} += length($&);
330 171 100       192 next unless $yydata->{INPUT};
331              
332 130         164 my $token_start = [ $yydata->{LINENO}, $yydata->{COLNO} ];
333 130 50       501 if ($yydata->{INPUT} =~ s|^/\*||) { # strip comments
    100          
    100          
    100          
    50          
334 0         0 $yydata->{COLNO} += length($&);
335 0         0 $comment = '';
336 0         0 while (1) {
337 0 0       0 if ($yydata->{INPUT} =~ s|(.*?)\*/||) {
338 0         0 $yydata->{COLNO} += length($&);
339 0         0 push @comments, { content => $comment . $1, location => $token_start };
340 0         0 last;
341             }
342 0         0 $comment .= $yydata->{INPUT};
343 0         0 $yydata->{INPUT} = <$fh>;
344 0         0 $yydata->{LINENO}++;
345 0         0 $yydata->{COLNO} = 1;
346 0 0       0 die "Unclosed comment" unless $yydata->{INPUT};
347             }
348             }
349             elsif ($yydata->{INPUT} =~ s/^(const|typedef|enum|union|struct|switch|case|default|unsigned|int|char|short|long|hyper|float|string|double|quadruple|bool|opaque|void)\b(?!_)//) {
350 30         49 $yydata->{COLNO} += length($&);
351 30         110 return ($1, {
352             content => $1,
353             comments => \@comments,
354             location => $token_start
355             });
356             }
357             elsif ($yydata->{INPUT} =~ s/^([a-z][a-z0-9_]*)//i) {
358 35         37 $yydata->{COLNO} += length($&);
359 35         141 return ('IDENT', {
360             content => $1,
361             comments => \@comments,
362             location => $token_start
363             });
364             }
365             elsif ($yydata->{INPUT} =~ s/^(-?\d+|0x[0-9a-f]+)(?=\b|$)//i) {
366 7         9 $yydata->{COLNO} += length($&);
367 7         27 return ('CONST', {
368             content => $1,
369             comments => \@comments,
370             location => $token_start
371             });
372             }
373             elsif ($yydata->{INPUT} =~ s/^(.)//) {
374 58         71 $yydata->{COLNO} += length($&);
375 58         215 return ($1, {
376             content => $1,
377             comments => \@comments,
378             location => $token_start
379             });
380             }
381             else {
382 0         0 die "Remaining input: '$yydata->{INPUT}'";
383             }
384             }
385             }
386              
387             sub _Error {
388 0     0   0 my $tok = $_[0]->YYCurtok;
389 0         0 my $val = $_[0]->YYCurval;
390 0 0       0 my $line = $tok ? "line: $val->{location}->[0]" : 'at ';
391              
392 0         0 print STDERR "Parse error at '$val->{content}' (line: $line)\n";
393             }
394              
395             sub parse {
396 1     1 1 1 my ($self, $fh) = @_;
397              
398 131     131   2257 $self->YYParse( yylex => sub { _Lexer( $fh, @_ ); },
399 1         7 yyerror => \&_Error );
400             }
401              
402             =head1 NAME
403              
404             XDR::Parse - Creation of an AST of an XDR specification (RFC4506)
405              
406             =head1 SYNOPSIS
407              
408             use XDR::Parse;
409             use Data::Dumper;
410              
411             my $p = XDR::Parse->new;
412             print Dumper( $p->parse( \*STDIN ) );
413              
414             =head1 DESCRIPTION
415              
416             This module contains a parser for the XDR (eXternal Data Representation)
417             language as defined in RFC4506. The result is an abstract syntax tree
418             (AST) which can be used for further processing.
419              
420             This module extends the supported integer types with C, C and
421             C, all of which seem to be supported by C, the tool consuming
422             XDR to generate remote applications.
423              
424             =head2 AST
425              
426             At the top level, the AST is an array of nodes which can be one of the
427             following, distinguished by the C key in the node's hash:
428              
429             =over 8
430              
431             =item * a 'pass through' instruction (C)
432              
433             This type of nodes contains a line which starts with '%'; the instruction
434             to C to copy that line to output verbatim
435              
436             =item * a preprocessor instruction (C)
437              
438             This type of nodes contains a line which starts with '#'; C typically
439             invokes C to preprocess its input -- this module simply takes input and
440             parses that; input which hasn't been preprocessed may contain this type of node
441              
442             =item * constant declarations (C)
443              
444             =item * type declarations
445              
446             Type definitions come in four subtypes C, C, C
447             and C
448              
449             =item * trailing comment
450              
451             Comments in the input are linked to the first syntax node following the comment;
452             files having comments between the last syntax and the end of the file, will
453             contain a special C node, which doesn't model syntax, but is
454             required to prevent loosing the last comments in the file.
455              
456             =back
457              
458             Each node in the tree -not just the toplevel - is a hash which may have any or
459             all of the following keys:
460              
461             =over 8
462              
463             =item * comments
464              
465             Is an array containing all comments following the previous syntax node and
466             preceeding the one to which the comment(s) are attached
467              
468             =item * location
469              
470             Is an array of two elements: the line and column number of the beginning of the
471             syntax captured by that node
472              
473             =item * trailing_comments
474              
475             Trailing comments happen when a node encloses a scope with a termination which
476             itself is not included in the AST representation. E.g. the closing ';' in a
477             C:
478              
479             typedef string our_string<> /* trailing comment */ ;
480              
481             =back
482              
483             =head3 Constant declarations
484              
485             Constant declarations exist in two types, distinguished by the C key in
486             the node's hash:
487              
488             =over 8
489              
490             =item * C
491              
492             const my_const = 0x123; # hexadecimal
493             const my_other_const = 123; # decimal
494             const my_third_const = 012; # octal
495              
496             =item * C
497              
498             const the_const = my_other_const;
499              
500             =back
501              
502             =head3 Type declarations
503              
504             Top level nodes with a C key valued C, C, C or
505             C define types of the named language construct. These nodes share the
506             following keys, in addition to the keys shared by all nodes:
507              
508             =over 8
509              
510             =item * name
511              
512             Name of the type being defined.
513              
514             =item * definition
515              
516             The node making up the definition of the type, holding a C node with
517             two keys, C and C. The value of the C key is one of
518             C, C or C. The elements are specified by the content of
519             the C key.
520              
521             =back
522              
523             =head4 'typedef' declarations
524              
525             This node is a 'declaration' node as documented in the section
526             'declaration' nodes below.
527              
528             =head4 'enum' declarations
529              
530             The C node of C definitions has a single key (C):
531             an array of nodes with C and C keys, one for each value defined
532             in the enum type.
533              
534             =head4 'struct' declarations
535              
536             Th C node of C definitions has a single key (C):
537             an array of nodes with C and C keys describing the members
538             of the struct type. For more details on the C node, see below.
539              
540             =head4 'union' declarations
541              
542             The C node of C definitions has a single key (C):
543             itself a node which contains a C and a C key. The
544             discriminator node has a C and a C key; the C node
545             contains one or two keys: C and optionally C. C is an
546             array of nodes defining the members of the union; each element consists of
547             three keys: C, C and . C is the value
548             associated with the discriminator, to indicate the current definition.
549             C is the name of the member. C contains the type declaration
550             for the member.
551              
552             =head4 'declaration' nodes
553              
554             These nodes contain a C key specifying the basic type of the declaration
555             as documented below under L,
556             with a number of modifiers:
557              
558             =over 8
559              
560             =item * pointer
561              
562             Optional. Mutually exclusive with the C indicator.
563              
564             =item * array
565              
566             Optional. Mutually exclusive with the C indicator.
567              
568             When the C boolean is true, the following additional keys may exist:
569              
570             =over 8
571              
572             =item * variable
573              
574             Indicates whether the array is of variable length.
575              
576             =item * max
577              
578             Indicates the maximum number of items in the array. May be C, if no
579             maximum was specified.
580              
581             Note: this value may be specified using symbolic constant.
582              
583             =item * count
584              
585             Indicates the exact number of items in the array, when C is false
586             or absent.
587              
588             Note: this value may be specified using symbolic constant.
589              
590             =back
591              
592             =back
593              
594             =head4 'type' nodes in declarations
595              
596             These nodes either define an inline C, C or C, or refer to any
597             of the types defined in the standard or at the toplevel, as indiceted by the C
598             key using these values:
599              
600             =over 8
601              
602             =item * primitive
603              
604             The value in the C key refers to a built-in type. When the named type is one
605             of the integer type (C, C, C, C or C), the type hash
606             contains the additional key C.
607              
608             The primitive types C and C support the same additional keys as
609             arrays (C, C and C). These apply to the data within them
610             and do not mean to define arrays of strings/"opaques".
611              
612             =item * named
613              
614             The value in the C key refers to a defined type.
615              
616             =item * enum
617              
618             Defines an inline enum through the type's C key.
619              
620             =item * struct
621              
622             Defines an inline struct through the type's C key.
623              
624             =item * union
625              
626             Defines an inline union through the type's C key.
627              
628             =back
629              
630             The node in the C key of the inline C, C and C
631             members follow the same pattern as documented in the respective sections on
632             declarations above.
633              
634             =head1 METHODS
635              
636             =head2 new
637              
638             my $parser = XDR::Parse->new;
639              
640             =head2 parse
641              
642             my $ast = $parser->parse( \*STDIN );
643              
644             =head2 YYParse (inherited)
645              
646             =head1 LICENSE
647              
648             This distribution may be used under the same terms as Perl itself.
649              
650             =head1 AUTHOR
651              
652             =over 8
653              
654             =item * Erik Huelsmann
655              
656             =back
657              
658             =head1 SEE ALSO
659              
660             L, L
661              
662             =cut