File Coverage

blib/lib/Google/ProtocolBuffers/Compiler.pm
Criterion Covered Total %
statement 255 276 92.3
branch 92 132 69.7
condition 10 27 37.0
subroutine 32 35 91.4
pod 0 6 0.0
total 389 476 81.7


line stmt bran cond sub pod time code
1             package Google::ProtocolBuffers::Compiler;
2 12     12   43 use strict;
  12         14  
  12         285  
3 12     12   35 use warnings;
  12         14  
  12         263  
4 12     12   12027 use Parse::RecDescent;
  12         332238  
  12         67  
5 12     12   416 use Data::Dumper;
  12         14  
  12         543  
6 12     12   48 use Google::ProtocolBuffers::Constants qw/:types :labels/;
  12         13  
  12         2167  
7 12     12   51 use Carp;
  12         12  
  12         518  
8 12     12   45 use Config qw/%Config/;
  12         14  
  12         340  
9 12     12   39 use File::Spec;
  12         12  
  12         3063  
10            
11             ##
12             ## Grammar is based on work by Alek Storm
13             ## http://groups.google.com/group/protobuf/browse_thread/thread/1cccfc624cd612da
14             ## http://groups.google.com/group/protobuf/attach/33102cfc0c57d449/proto2.ebnf?part=4
15             ##
16            
17             my $grammar = <<'END_OF_GRAMMAR';
18            
19             proto :
20             ## list of top level declarations.
21             ## Skip empty declarations and ";".
22             (message | extend | enum | import | package | option | service | syntax | ";")(s) /\Z/
23             { $return = [ grep {ref $_} @{$item[2]} ]; }
24             |
25            
26             import : "import" strLit ";"
27             { $return = [ import => $item{strLit} ]; }
28             ## error? reject pair means:
29             ## if rule was commited (i.e. "import" was found), then fail the entire parse
30             ## otherwise, just skip this production (and try another one)
31             |
32            
33             package : "package" qualifiedIdent ";"
34             { $return = [ package => $item{qualifiedIdent} ]; }
35             |
36            
37             option : ## so far, options are ignored
38             "option" optionBody ";"
39             { $return = '' }
40             |
41            
42             optionBody : qualifiedIdent "=" constant
43             { $return = '' }
44            
45             message : "message" ident messageBody
46             { $return = [ message => $item{ident}, $item{messageBody} ]; }
47             |
48            
49             extend : "extend" userType "{" ( field | group | ";" )(s?) "}"
50             { $return = [extend => $item{userType}, [ grep {ref $_} @{$item[5]}] ]}
51            
52             enum : "enum" ident "{" (option | enumField | ";")(s) "}"
53             { $return = [ enum => $item{ident}, [grep {ref $_} @{$item[5]}] ] }
54             |
55            
56             enumField : ident "=" intLit ";"
57             { $return = [ enumField => $item{ident}, $item{intLit} ] }
58            
59             service : ## services are ignored
60             "service" ident "{" ( option | rpc | ";" )(s?) "}"
61             { $return = '' }
62             |
63            
64             rpc : "rpc" ident "(" userType ")" "returns" "(" userType ")" rpcOptions(?) ";"
65             { $return = '' }
66             |
67            
68             rpcOptions : "{" option(s?) "}"
69            
70             messageBody : "{" ( field | enum | message | extend | extensions | group | option | oneof | ";" )(s?) "}"
71             { $return = [ grep {ref $_} @{$item[3]} ] }
72             |
73            
74             group : label "group" ident "=" intLit messageBody
75             { $return = [group => $item{label}, $item{ident}, $item{intLit}, $item{messageBody} ] }
76             |
77            
78             field : label type ident "=" intLit fOptList(?) ";"
79             { $return = [field => $item{label}, $item{type}, $item{ident}, $item{intLit}, $item[6][0] ] }
80            
81             oneof : "oneof" ident "{" ( oneofField | ";" )(s?) "}"
82             { $return = [ oneof => $item{ident}, [grep {ref $_} @{$item[5]}] ] }
83             |
84              
85             oneofField : type ident "=" intLit fOptList(?) ";"
86             { $return = [field => "optional", $item{type}, $item{ident}, $item{intLit}, $item[5][0] ] }
87              
88             fOptList : "[" fieldOption(s? /,/) "]"
89             { $return = (grep {length($_)} @{$item[2]})[0] || '' }
90            
91             fieldOption : "default" "=" constant
92             { $return = $item{constant} }
93             | optionBody
94             { $return = '' }
95             |
96            
97             extensions : "extensions" extension(s /,/) ";"
98             { $return = '' }
99             |
100            
101             extension : intLit ( "to" ( intLit | "max" ) )(s?)
102             { $return = '' }
103            
104             label : "required" | "optional" | "repeated"
105            
106            
107             type : "double" | "float" | "int32" | "int64" | "uint32" | "uint64"
108             | "sint32" | "sint64" | "fixed32" | "fixed64" | "sfixed32" | "sfixed64"
109             | "bool" | "string" | "bytes" | userType
110            
111             userType : (".")(?) qualifiedIdent
112             { $return = ($item[1] && @{$item[1]}) ? ".$item[2]" : $item[2] }
113            
114             constant : ident
115             { $return = $item[1]; }
116             | (floatLit | intLit | strLit | boolLit)
117             { $return = { value => $item[1] } }
118            
119             ident : /[a-z_]\w*/i
120            
121             qualifiedIdent:
122            
123             { $return = join(".", @{ $item[1] })}
124            
125             intLit : hexInt | octInt| decInt
126            
127             decInt : /[-+]?[1-9]\d*/
128             { $return = Google::ProtocolBuffers::Compiler::get_dec_int($item[1]) }
129            
130             hexInt : /[-+]?0[xX]([A-Fa-f0-9])+/
131             { $return = Google::ProtocolBuffers::Compiler::get_hex_int($item[1]) }
132            
133             octInt : /[-+]?0[0-7]*/
134             { $return = Google::ProtocolBuffers::Compiler::get_oct_int($item[1]) }
135            
136             floatLit : ## Make floatLit do not match integer literals,
137             ## so that it doesn't take off '0' from '0xFFF' or '012' (oct).
138             /[-+]?\d*\.\d+([Ee][\+-]?\d+)?/
139             | /[-+]?\d+[Ee][\+-]?\d+/
140            
141            
142             boolLit : "true"
143             { $return = 1 }
144             | "false"
145             { $return = 0 }
146            
147             strLit : /['"]/ ( hexEscape | octEscape | charEscape | regularChar)(s?) /['"]/
148             { $return = join('', @{$item[3]}) }
149            
150             regularChar : ## all chars exept chr(0) and "\n"
151             /[^\0\n'"]/
152            
153             hexEscape : /\\[Xx]/ /[A-Fa-f0-9]{1,2}/
154             { $return = chr(hex($item[2])) }
155            
156             octEscape : '\\' /^0?[0-7]{1,3}/
157             { $return = chr(oct("0$item[2]") & 0xFF); }
158            
159             charEscape : /\\[abfnrtv\\'"]/
160             {
161             my $s = substr($item[1], 1, 1);
162             $return = ($s eq 'a') ? "\a" :
163             ($s eq 'b') ? "\b" :
164             ($s eq 'f') ? "\f" :
165             ($s eq 'n') ? "\n" :
166             ($s eq 'r') ? "\r" :
167             ($s eq 't') ? "\t" :
168             ($s eq 'v') ? "\x0b" : $s;
169             }
170            
171            
172             syntax : "syntax" "=" strLit ## syntax = "proto2";
173             {
174             die "Unknown syntax" unless $item{strLit} eq 'proto2';
175             $return = '';
176             }
177            
178             END_OF_GRAMMAR
179            
180             my %primitive_types = (
181             "double" => TYPE_DOUBLE,
182             "float" => TYPE_FLOAT,
183             "int32" => TYPE_INT32,
184             "int64" => TYPE_INT64,
185             "uint32" => TYPE_UINT32,
186             "uint64" => TYPE_UINT64,
187             "sint32" => TYPE_SINT32,
188             "sint64" => TYPE_SINT64,
189             "fixed32" => TYPE_FIXED32,
190             "fixed64" => TYPE_FIXED64,
191             "sfixed32" => TYPE_SFIXED32,
192             "sfixed64" => TYPE_SFIXED64,
193             "bool" => TYPE_BOOL,
194             "string" => TYPE_STRING,
195             "bytes" => TYPE_BYTES,
196             );
197            
198             my %labels = (
199             'required' => LABEL_REQUIRED,
200             'optional' => LABEL_OPTIONAL,
201             'repeated' => LABEL_REPEATED,
202             );
203            
204             my $has_64bit = $Config{ivsize}>=8;
205            
206             sub _get_int_value {
207 2617     2617   2581 my $str = shift;
208 2617         2178 my $max_pos_str = shift;
209 2617         2042 my $max_neg_str = shift;
210 2617         2029 my $str_to_num = shift;
211 2617         1908 my $str_to_bigint = shift;
212            
213 2617         3548 my $is_negative = ($str =~/^-/);
214 2617         3528 $str =~ s/^[+-]//;
215            
216 2617 50       4825 if (!$has_64bit) {
217 0         0 my $l = length($str);
218 0 0 0     0 if (
      0        
      0        
      0        
      0        
219             !$is_negative &&
220             ($l>length($max_pos_str)
221             || ($l==length($max_pos_str) && uc($str) ge uc($max_pos_str)))
222             || $is_negative &&
223             ( $l>length($max_neg_str)
224             || ($l==length($max_neg_str) && uc($str) ge uc($max_neg_str)))
225             )
226             {
227 0         0 my $v = $str_to_bigint->($str);
228 0 0       0 return ($is_negative) ? -$v : $v;
229             }
230             }
231            
232 2617         3755 my $v = $str_to_num->($str);
233 2617 100       44838 return ($is_negative) ? -$v : $v;
234             }
235            
236             sub get_dec_int {
237 2570     2570 0 6653292 my $str = shift;
238            
239             return _get_int_value(
240             $str, "2147483647", "2147483648",
241             sub {
242 12     12   51 no warnings 'portable';
  12         13  
  12         1120  
243 2570     2570   4360 return $_[0]+0;
244             },
245             sub {
246 0     0   0 return Math::BigInt->new($_[0]);
247             }
248 2570         12519 );
249             }
250            
251             sub get_hex_int {
252 36     36 0 36310 my $str = shift;
253            
254             return _get_int_value(
255             $str, "0x7fffffff", "0x80000000",
256             sub {
257 12     12   41 no warnings 'portable';
  12         15  
  12         1045  
258 36     36   73 return hex($_[0]);
259             },
260             sub {
261 0     0   0 return Math::BigInt->new($_[0]);
262             }
263 36         195 );
264             }
265            
266             sub get_oct_int {
267 11     11 0 13241 my $str = shift;
268             return _get_int_value(
269             $str, "017777777777", "020000000000",
270             sub {
271 12     12   57 no warnings 'portable';
  12         15  
  12         4717  
272 11     11   30 return oct($_[0]);
273             },
274             sub {
275             ## oops, Math::BigInt doesn't accept strings of octal digits,
276             ## ... but accepts binary digits
277 0     0   0 my $v = shift;
278 0         0 my @oct_2_binary = qw(000 001 010 011 100 101 110 111);
279 0         0 $v =~ s/(.)/$oct_2_binary[$1]/g;
280 0         0 return Math::BigInt->new('0b' . $v);
281             }
282 11         92 );
283             }
284            
285            
286             sub parse {
287 22     22 0 35 my $class = shift;
288 22         28 my $source = shift;
289 22         27 my $opts = shift;
290            
291 22         47 my $self = bless { opts => $opts };
292            
293 22         36 $::RD_ERRORS = 1;
294 22         22 $::RD_WARN = 1;
295 22 50       80 my $parser = Parse::RecDescent->new($grammar) or die;
296            
297             ## all top level declarations from all files (files be included)
298             ## will be here
299 22         3876755 my @parse_tree;
300            
301 22         43 my (@import_files, $text);
302 22 100       261 if ($source->{text}) {
    50          
303 12         35 $text = $source->{text};
304             } elsif ($source->{file}) {
305 10         96 @import_files = ('', $source->{file});
306             } else {
307 0         0 die;
308             }
309            
310 22         41 my %already_included_files;
311            
312 22   100     165 while ($text || @import_files) {
313 31         48 my ($content, $filename);
314            
315 31 100       80 if ($text) {
316 12         23 $content = $text;
317 12         23 undef $text;
318             } else {
319             ## path may be relative to the path of the file, where
320             ## "import" directive. Also, root dir for proto files
321             ## may be specified in options
322 19         60 my ($root, $path) = splice(@import_files, 0, 2);
323 19         86 $filename = $self->_find_filename($root, $path);
324 19 50       87 next if $already_included_files{$filename}++;
325             {
326 19         28 my $fh;
  19         24  
327 19 50       803 open $fh, $filename or die "Can't read from $filename: $!";
328 19         83 local $/;
329 19         1077 $content = <$fh>;
330 19         666 close $fh;
331             }
332             }
333            
334 31         373 my $res = $parser->proto($content);
335 31 50       283494 die "" unless defined $res;
336            
337             ## start each file from empty package
338 31         105 push @parse_tree, [package=>''];
339 31         82 foreach my $decl (@$res) {
340 311 100       456 if ($decl->[0] eq 'import') {
341 9         29 push @import_files, ($filename, $decl->[1]);
342             } else {
343 302         518 push @parse_tree, $decl;
344             }
345             }
346             }
347            
348             ##
349             ## Pass #1.
350             ## Find names of messages and enums, including nested ones.
351             ##
352 22         175 my $symbol_table = Google::ProtocolBuffers::Compiler::SymbolTable->new;
353 22         89 $self->{symbol_table} = $symbol_table;
354 22         100 $self->collect_names('', \@parse_tree);
355            
356             ##
357             ## Pass #2.
358             ## Create complete descriptions of messages with extensions.
359             ## For each field of a user type a fully quilified type name must be found.
360             ## For each default value defined by a constant (enum), a f.q.n of enum value must be found
361             ##
362 22         53 foreach my $kind (qw/message group enum oneof/) {
363 88         166 foreach my $fqname ($symbol_table->lookup_names_of_kind($kind)) {
364 344         1010 $self->{types}->{$fqname} = { kind => $kind, fields => [], extensions => [], oneofs => [] };
365             }
366             }
367 22         97 $self->collect_fields('', \@parse_tree);
368            
369 22         1093 return $self->{types};
370             }
371            
372             sub _find_filename {
373 19     19   34 my $self = shift;
374 19         29 my $base_filename = shift;
375 19         29 my $path = shift;
376            
377             =comment
378             my $filename = File::Spec->rel2abs($path, $base_filename);
379             return $filename if -e $filename;
380            
381             if ($self->{opts}->{include_dir}) {
382             $filename = File::Spec->rel2abs($path, $self->{opts}->{include_dir});
383             return $filename if -e $filename;
384             }
385             =cut
386 12     12   50 use Cwd; my $d = getcwd();
  12         13  
  12         9489  
  19         140  
387            
388 19         45 my $filename = $path;
389 19 50       327 return $filename if -e $filename;
390            
391 19 50       146 if (my $inc_dirs = $self->{opts}->{include_dir}) {
392 19 50       78 $inc_dirs = [ $inc_dirs ] unless(ref($inc_dirs) eq 'ARRAY');
393 19         51 foreach my $d (@$inc_dirs){
394 19         323 $filename = File::Spec->catfile($d, $path);
395 19 50       504 return $filename if -e $filename;
396             }
397             }
398 0         0 die "Can't find proto file: '$path'";
399             }
400            
401            
402             sub collect_names {
403 402     402 0 302 my $self = shift;
404 402         303 my $context = shift;
405 402         272 my $nodes = shift;
406            
407 402         312 my $symbol_table = $self->{symbol_table};
408 402         477 foreach my $decl (@$nodes) {
409 2661         2585 my $kind = $decl->[0]; ## 'message', 'extent', 'enum' etc...
410 2661 100       7201 if ($kind eq 'package') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
411             ## package directive just set new context,
412             ## not related to previous one
413 57         126 $context = $symbol_table->set_package($decl->[1]);
414             } elsif ($kind eq 'message') {
415             ## message may include nested messages/enums/groups/oneofs
416 237         372 my $child_context = $symbol_table->add('message' => $decl->[1], $context);
417 237         419 $self->collect_names($child_context, $decl->[2]);
418             } elsif ($kind eq 'enum') {
419 49         369 my $child_context = $symbol_table->add('enum' => $decl->[1], $context);
420 49         86 $self->collect_names($child_context, $decl->[2]);
421             } elsif ($kind eq 'group') {
422             ## there may be nested messages/enums/groups/oneofs etc. inside group
423             ## [group => $label, $ident, $intLit, $messageBody ]
424 57         86 my $child_context = $symbol_table->add('group' => $decl->[2], $context);
425 57         102 $self->collect_names($child_context, $decl->[4]);
426             } elsif ($kind eq 'oneof') {
427             ## OneOfs may only contain fields, we add them to both
428             ## the current and oneof context
429 1         3 my $child_context = $symbol_table->add('oneof' => $decl->[1], $context);
430 1         1 foreach my $oneof (@{$decl->[2]}) {
  1         3  
431 2         3 $symbol_table->add('field' => $oneof->[3], $context);
432 2         4 $symbol_table->add('field' => $oneof->[3], $child_context);
433             }
434             } elsif ($kind eq 'extend') {
435             ## extend blocks are tricky:
436             ## 1) they don't create a new scope
437             ## 2) there may be a group inside extend block, and there may be everything inside the group
438 37         99 $self->collect_names($context, $decl->[2]);
439             } elsif ($kind eq 'field') {
440             ## we add fields into symbol table just to check their uniqueness
441             ## in several extension blocks or oneofs. Example:
442             ## .proto:
443             ## extend A { required int32 foo = 100 };
444             ## extend B { required int32 foo = 200 };
445             ## // Invalid! foo is already declared!
446             ##
447 2022         2175 $symbol_table->add('field' => $decl->[3], $context);
448             } elsif ($kind eq 'enumField') {
449 201         238 $symbol_table->add('enum_field' => $decl->[1], $context);
450             } else {
451 0         0 warn $kind;
452             }
453             }
454             }
455            
456             sub collect_fields {
457 403     403 0 279 my $self = shift;
458 403         273 my $context = shift;
459 403         672 my $nodes = shift;
460 403         271 my $destination_type_name = shift;
461 403         267 my $is_extension = shift;
462            
463 403         314 my $symbol_table = $self->{symbol_table};
464 403         417 foreach my $decl (@$nodes) {
465 2663         2152 my $kind = $decl->[0]; ## 'message', 'extent', 'enum' etc...
466 2663 100       7003 if ($kind eq 'package') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
467 57         83 $context = $decl->[1];
468             } elsif ($kind eq 'message') {
469 237 100       421 my $child_context = ($context) ? "$context.$decl->[1]" : $decl->[1];
470 237         499 $self->collect_fields($child_context, $decl->[2], $child_context);
471             } elsif ($kind eq 'enum') {
472 49 50       124 my $child_context = ($context) ? "$context.$decl->[1]" : $decl->[1];
473 49         86 $self->collect_fields($child_context, $decl->[2], $child_context);
474             } elsif ($kind eq 'group') {
475             ## groups are tricky: they are both definition of a field and type.
476             ## [group => $label, $ident, $intLit, $messageBody ]
477             ## first, collect fields inside the group
478 57 50       116 my $child_context = ($context) ? "$context.$decl->[2]" : $decl->[2];
479 57         90 $self->collect_fields($child_context, $decl->[4], $child_context);
480             ## second, add the group as one field to parent (destination) type
481 57 50       86 confess unless $destination_type_name;
482 57         112 my $name;
483             my $fields_list;
484 57 100       74 if ($is_extension) {
485             ## for extensions, fully quilified names of fields are used,
486             ## because they may be declared anywhere - even in another package
487 18         26 $fields_list = $self->{types}->{$destination_type_name}->{extensions};
488 18         54 $name = $symbol_table->lookup('group' => $decl->[2], $context);
489             } else {
490             ## regualar fields are always immediate children of their type
491 39         50 $fields_list = $self->{types}->{$destination_type_name}->{fields};
492 39         44 $name = $decl->[2];
493             }
494 57 50       112 my $label = (exists $labels{$decl->[1]}) ? $labels{$decl->[1]} : die;
495 57         96 my ($type_name, $kind) = $symbol_table->lookup_symbol($decl->[2], $context);
496 57 50       105 die unless $kind eq 'group';
497 57         49 my $field_number = $decl->[3];
498 57         137 push @$fields_list, [$label, $type_name, $name, $field_number];
499             } elsif ($kind eq 'oneof') {
500 1 50       4 my $child_context = ($context) ? "$context.$decl->[1]" : $decl->[1];
501 1         9 $self->collect_fields($child_context, $decl->[2], $child_context);
502 1         4 push @{$self->{types}->{$destination_type_name}->{oneofs}}, $child_context;
  1         5  
503             } elsif ($kind eq 'extend') {
504             ## what is the fqn of the message to be extended?
505 37         76 my $destination_message = $symbol_table->lookup('message' => $decl->[1], $context);
506 37         94 $self->collect_fields($context, $decl->[2], $destination_message, 1);
507             } elsif ($kind eq 'field') {
508 2024 50       2146 confess unless $destination_type_name;
509             # $decl = ['field' => $label, $type, $ident, $item{intLit}, $item{fOptList}] }
510 2024         1205 my $name;
511             my $fields_list;
512 2024 100       1696 if ($is_extension) {
513             ## for extensions, fully quilified names of fields are used,
514             ## because they may be declared anywhere - even in another package
515 640         621 $fields_list = $self->{types}->{$destination_type_name}->{extensions};
516 640         676 $name = $symbol_table->lookup('field' => $decl->[3], $context);
517             } else {
518             ## regualar fields are always immediate children of their type
519 1384         1218 $fields_list = $self->{types}->{$destination_type_name}->{fields};
520 1384         1197 $name = $decl->[3];
521             }
522            
523 2024 50       2601 my $label = (exists $labels{$decl->[1]}) ? $labels{$decl->[1]} : die;
524            
525 2024         1162 my ($type_name, $kind);
526 2024 100       2298 if (exists $primitive_types{$decl->[2]}) {
527 1619         1213 $type_name = $primitive_types{$decl->[2]};
528             } else {
529 405         442 ($type_name, $kind) = $symbol_table->lookup_symbol($decl->[2], $context);
530 405 50 66     1191 die unless $kind eq 'message' || $kind eq 'group' || $kind eq 'enum';
      66        
531             }
532            
533 2024         1499 my $field_number = $decl->[4];
534            
535 2024         1337 my $default_value = $decl->[5];
536 2024 100 100     3173 if ($default_value && !ref $default_value) {
537 74 100       121 if ($default_value eq 'true') {
    50          
538 18         42 $default_value = { value => 1 };
539             } elsif ($default_value eq 'false') {
540 0         0 $default_value = { value => 0 };
541             } else {
542             ## this default is enum value
543             ## type name must be fqn of enum type
544 56 50       79 die unless $kind eq 'enum';
545 56         82 $default_value = $symbol_table->lookup('enum_field' => $default_value, $type_name);
546             }
547             }
548 2024         3872 push @$fields_list, [$label, $type_name, $name, $field_number, $default_value];
549             } elsif ($kind eq 'enumField') {
550 201 50       239 confess unless $destination_type_name;
551 201         216 my $fields_list = $self->{types}->{$destination_type_name}->{fields};
552 201         124 push @{$fields_list}, [$decl->[1], $decl->[2]];
  201         410  
553             } else {
554 0         0 warn $kind;
555             }
556             }
557             }
558            
559             package Google::ProtocolBuffers::Compiler::SymbolTable;
560             ##
561             ## %$self - symbol name table, descriptions of fully qualified names like Foo.Bar:
562             ## $names{'foo'} = { kind => 'package' }
563             ## $names{'foo.Bar'} = { kind => 'message' }
564             ## $names{'foo.Bar.Baz'}={ kind => 'enum', }
565             ##
566 12     12   58 use Data::Dumper;
  12         16  
  12         436  
567 12     12   48 use Carp;
  12         14  
  12         6903  
568            
569             sub new {
570 22     22   43 my $class = shift;
571 22         66 return bless {}, $class;
572             }
573            
574             sub set_package {
575 57     57   63 my $self = shift;
576 57         66 my $package = shift;
577            
578 57 100       145 return '' unless $package;
579            
580 26         175 my @idents = split qr/\./, $package;
581 26         58 my $name = shift @idents;
582 26         34 while (1) {
583 34 50       125 if (exists $self->{$name}) {
584 0 0       0 die unless $self->{$name}->{kind} eq 'package';
585             } else {
586 34         98 $self->{$name} = {kind => 'package'}
587             }
588 34 100       89 last unless @idents;
589 8         20 $name .= '.' . shift(@idents);
590             }
591 26         61 return $name;
592             }
593            
594             sub _add {
595 2772     2772   1725 my $self = shift;
596 2772         1736 my $kind = shift;
597 2772         1663 my $name = shift;
598 2772         1693 my $context = shift;
599            
600             ## no fully quilified names are alowed to declare (so far)
601 2772 50       3478 die if $name =~ /\./;
602 2772         1595 my $fqn;
603 2772 100       2412 if ($context) {
604 2765 50       3559 die "$name, $context" unless $self->{$context};
605 2765         2622 $fqn = "$context.$name";
606             } else {
607 7         14 $fqn = $name;
608             }
609            
610 2772 50       2915 if (exists $self->{$fqn}) {
611 0         0 die "Name '$fqn' is already defined";
612             } else {
613 2772         5287 $self->{$fqn} = { kind=>$kind };
614             }
615            
616 2772         3722 return $fqn;
617             }
618            
619             sub add {
620 2571     2571   1755 my $self = shift;
621 2571         1527 my $kind = shift;
622 2571         2074 my $name = shift;
623 2571         1707 my $context = shift;
624            
625             ## tricky: enum values are both children and siblings of enums
626 2571 100       2486 if ($kind eq 'enum_field') {
627 201 50       331 die unless $self->{$context}->{kind} eq 'enum';
628 201         200 my $fqn = $self->_add($kind, $name, $context);
629 201         825 $context =~ s/(^|\.)\w+$//; ## parent context
630 201         238 $self->_add($kind, $name, $context);
631 201         289 return $fqn;
632             } else {
633 2370         2172 return $self->_add($kind, $name, $context);
634             }
635             }
636            
637             ## input: fully or partially qualified name
638             ## output: (fully qualified name, its kind - 'message', 'enum_field' etc.)
639             sub lookup_symbol {
640 1213     1213   763 my $self = shift;
641 1213         761 my $n = shift;
642 1213         708 my $c = shift;
643            
644 1213         771 my $context = $c;
645 1213         705 my $name = $n;
646 1213 50       1396 if ($name =~ s/^\.//) {
647             ## this is an fully quialified name
648 0 0       0 if (exists $self->{$name}) {
649 0         0 return ($name, $self->{$name}->{kind});
650             }
651             } else {
652             ## relative name - look it up in the current context and up
653 1213         720 while (1) {
654 1533 100       2121 my $fqn = ($context) ? "$context.$name" : $name;
655 1533 100       2171 if (exists $self->{$fqn}) {
656 1213         1995 return ($fqn, $self->{$fqn}->{kind});
657             }
658             ## one level up
659 320 50       367 last unless $context;
660 320         1121 $context =~ s/(^|\.)\w+$//;
661             }
662             }
663 0         0 die "Name '$name' ($c, $n) is not defined" . Data::Dumper::Dumper($self);
664             }
665            
666             ## input: kind, fully or partially qualified name, context
667             ## ouptut: fully qualified name
668             ## if found kind of the name doesn't match given kind, an exception is raised
669             sub lookup {
670 751     751   495 my $self = shift;
671 751         461 my $kind = shift;
672 751         516 my $name = shift;
673 751         461 my $context = shift;
674            
675 751         714 my ($fqn, $k) = $self->lookup_symbol($name, $context);
676 751 50       951 unless ($kind eq $k) {
677 0         0 confess "Error: while looking for '$kind' named '$name' in '$context', a '$k' named '$fqn' was found";
678             }
679 751         737 return $fqn;
680             }
681            
682             ## returns list of all fully qualified name of a given kind
683             sub lookup_names_of_kind {
684 88     88   89 my $self = shift;
685 88         112 my $kind = shift;
686            
687 88         918 return grep { $self->{$_}->{kind} eq $kind } keys %$self;
  11224         9498  
688             }
689            
690             1;