File Coverage

blib/lib/Google/ProtocolBuffers/Compiler.pm
Criterion Covered Total %
statement 246 267 92.1
branch 87 126 69.0
condition 10 27 37.0
subroutine 32 35 91.4
pod 0 6 0.0
total 375 461 81.3


line stmt bran cond sub pod time code
1             package Google::ProtocolBuffers::Compiler;
2 11     11   66 use strict;
  11         22  
  11         450  
3 11     11   65 use warnings;
  11         23  
  11         355  
4 11     11   34963 use Parse::RecDescent;
  11         2774917  
  11         102  
5 11     11   710 use Data::Dumper;
  11         23  
  11         900  
6 11     11   65 use Google::ProtocolBuffers::Constants qw/:types :labels/;
  11         28  
  11         3213  
7 11     11   75 use Carp;
  11         27  
  11         708  
8 11     11   63 use Config qw/%Config/;
  11         23  
  11         431  
9 11     11   62 use File::Spec;
  11         28  
  11         4871  
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 | ";" )(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             fOptList : "[" fieldOption(s? /,/) "]"
82             { $return = (grep {length($_)} @{$item[2]})[0] || '' }
83            
84             fieldOption : "default" "=" constant
85             { $return = $item{constant} }
86             | optionBody
87             { $return = '' }
88             |
89            
90             extensions : "extensions" extension(s /,/) ";"
91             { $return = '' }
92             |
93            
94             extension : intLit ( "to" ( intLit | "max" ) )(s?)
95             { $return = '' }
96            
97             label : "required" | "optional" | "repeated"
98            
99            
100             type : "double" | "float" | "int32" | "int64" | "uint32" | "uint64"
101             | "sint32" | "sint64" | "fixed32" | "fixed64" | "sfixed32" | "sfixed64"
102             | "bool" | "string" | "bytes" | userType
103            
104             userType : (".")(?) qualifiedIdent
105             { $return = ($item[1] && @{$item[1]}) ? ".$item[2]" : $item[2] }
106            
107             constant : ident
108             { $return = $item[1]; }
109             | (floatLit | intLit | strLit | boolLit)
110             { $return = { value => $item[1] } }
111            
112             ident : /[a-z_]\w*/i
113            
114             qualifiedIdent:
115            
116             { $return = join(".", @{ $item[1] })}
117            
118             intLit : hexInt | octInt| decInt
119            
120             decInt : /[-+]?[1-9]\d*/
121             { $return = Google::ProtocolBuffers::Compiler::get_dec_int($item[1]) }
122            
123             hexInt : /[-+]?0[xX]([A-Fa-f0-9])+/
124             { $return = Google::ProtocolBuffers::Compiler::get_hex_int($item[1]) }
125            
126             octInt : /[-+]?0[0-7]*/
127             { $return = Google::ProtocolBuffers::Compiler::get_oct_int($item[1]) }
128            
129             floatLit : ## Make floatLit do not match integer literals,
130             ## so that it doesn't take off '0' from '0xFFF' or '012' (oct).
131             /[-+]?\d*\.\d+([Ee][\+-]?\d+)?/
132             | /[-+]?\d+[Ee][\+-]?\d+/
133            
134            
135             boolLit : "true"
136             { $return = 1 }
137             | "false"
138             { $return = 0 }
139            
140             strLit : /['"]/ ( hexEscape | octEscape | charEscape | regularChar)(s?) /['"]/
141             { $return = join('', @{$item[3]}) }
142            
143             regularChar : ## all chars exept chr(0) and "\n"
144             /[^\0\n'"]/
145            
146             hexEscape : /\\[Xx]/ /[A-Fa-f0-9]{1,2}/
147             { $return = chr(hex($item[2])) }
148            
149             octEscape : '\\' /^0?[0-7]{1,3}/
150             { $return = chr(oct("0$item[2]") & 0xFF); }
151            
152             charEscape : /\\[abfnrtv\\'"]/
153             {
154             my $s = substr($item[1], 1, 1);
155             $return = ($s eq 'a') ? "\a" :
156             ($s eq 'b') ? "\b" :
157             ($s eq 'f') ? "\f" :
158             ($s eq 'n') ? "\n" :
159             ($s eq 'r') ? "\r" :
160             ($s eq 't') ? "\t" :
161             ($s eq 'v') ? "\x0b" : $s;
162             }
163            
164            
165             syntax : "syntax" "=" strLit ## syntax = "proto2";
166             {
167             die "Unknown syntax" unless $item{strLit} eq 'proto2';
168             $return = '';
169             }
170            
171             END_OF_GRAMMAR
172            
173             my %primitive_types = (
174             "double" => TYPE_DOUBLE,
175             "float" => TYPE_FLOAT,
176             "int32" => TYPE_INT32,
177             "int64" => TYPE_INT64,
178             "uint32" => TYPE_UINT32,
179             "uint64" => TYPE_UINT64,
180             "sint32" => TYPE_SINT32,
181             "sint64" => TYPE_SINT64,
182             "fixed32" => TYPE_FIXED32,
183             "fixed64" => TYPE_FIXED64,
184             "sfixed32" => TYPE_SFIXED32,
185             "sfixed64" => TYPE_SFIXED64,
186             "bool" => TYPE_BOOL,
187             "string" => TYPE_STRING,
188             "bytes" => TYPE_BYTES,
189             );
190            
191             my %labels = (
192             'required' => LABEL_REQUIRED,
193             'optional' => LABEL_OPTIONAL,
194             'repeated' => LABEL_REPEATED,
195             );
196            
197             my $has_64bit = $Config{ivsize}>=8;
198            
199             sub _get_int_value {
200 2611     2611   5175 my $str = shift;
201 2611         4674 my $max_pos_str = shift;
202 2611         4946 my $max_neg_str = shift;
203 2611         4140 my $str_to_num = shift;
204 2611         4612 my $str_to_bigint = shift;
205            
206 2611         7409 my $is_negative = ($str =~/^-/);
207 2611         7278 $str =~ s/^[+-]//;
208            
209 2611 50       9232 if (!$has_64bit) {
210 0         0 my $l = length($str);
211 0 0 0     0 if (
      0        
      0        
      0        
      0        
212             !$is_negative &&
213             ($l>length($max_pos_str)
214             || ($l==length($max_pos_str) && uc($str) ge uc($max_pos_str)))
215             || $is_negative &&
216             ( $l>length($max_neg_str)
217             || ($l==length($max_neg_str) && uc($str) ge uc($max_neg_str)))
218             )
219             {
220 0         0 my $v = $str_to_bigint->($str);
221 0 0       0 return ($is_negative) ? -$v : $v;
222             }
223             }
224            
225 2611         7629 my $v = $str_to_num->($str);
226 2611 100       83985 return ($is_negative) ? -$v : $v;
227             }
228            
229             sub get_dec_int {
230 2565     2565 0 16715357 my $str = shift;
231            
232             return _get_int_value(
233             $str, "2147483647", "2147483648",
234             sub {
235 11     11   78 no warnings 'portable';
  11         119  
  11         1647  
236 2565     2565   10772 return $_[0]+0;
237             },
238             sub {
239 0     0   0 return Math::BigInt->new($_[0]);
240             }
241 2565         23819 );
242             }
243            
244             sub get_hex_int {
245 36     36 0 73673 my $str = shift;
246            
247             return _get_int_value(
248             $str, "0x7fffffff", "0x80000000",
249             sub {
250 11     11   66 no warnings 'portable';
  11         28  
  11         1478  
251 36     36   137 return hex($_[0]);
252             },
253             sub {
254 0     0   0 return Math::BigInt->new($_[0]);
255             }
256 36         408 );
257             }
258            
259             sub get_oct_int {
260 10     10 0 18058 my $str = shift;
261             return _get_int_value(
262             $str, "017777777777", "020000000000",
263             sub {
264 11     11   65 no warnings 'portable';
  11         23  
  11         7125  
265 10     10   44 return oct($_[0]);
266             },
267             sub {
268             ## oops, Math::BigInt doesn't accept strings of octal digits,
269             ## ... but accepts binary digits
270 0     0   0 my $v = shift;
271 0         0 my @oct_2_binary = qw(000 001 010 011 100 101 110 111);
272 0         0 $v =~ s/(.)/$oct_2_binary[$1]/g;
273 0         0 return Math::BigInt->new('0b' . $v);
274             }
275 10         116 );
276             }
277            
278            
279             sub parse {
280 20     20 0 54 my $class = shift;
281 20         48 my $source = shift;
282 20         50 my $opts = shift;
283            
284 20         81 my $self = bless { opts => $opts };
285            
286 20         62 $::RD_ERRORS = 1;
287 20         49 $::RD_WARN = 1;
288 20 50       154 my $parser = Parse::RecDescent->new($grammar) or die;
289            
290             ## all top level declarations from all files (files be included)
291             ## will be here
292 20         46488430 my @parse_tree;
293            
294 20         201 my (@import_files, $text);
295 20 100       223 if ($source->{text}) {
    50          
296 11         38 $text = $source->{text};
297             } elsif ($source->{file}) {
298 9         88 @import_files = ('', $source->{file});
299             } else {
300 0         0 die;
301             }
302            
303 20         51 my %already_included_files;
304            
305 20   100     197 while ($text || @import_files) {
306 29         65 my ($content, $filename);
307            
308 29 100       115 if ($text) {
309 11         20 $content = $text;
310 11         31 undef $text;
311             } else {
312             ## path may be relative to the path of the file, where
313             ## "import" directive. Also, root dir for proto files
314             ## may be specified in options
315 18         82 my ($root, $path) = splice(@import_files, 0, 2);
316 18         136 $filename = $self->_find_filename($root, $path);
317 18 50       121 next if $already_included_files{$filename}++;
318             {
319 18         34 my $fh;
  18         35  
320 18 50       1317 open $fh, $filename or die "Can't read from $filename: $!";
321 18         140 local $/;
322 18         4313 $content = <$fh>;
323 18         888 close $fh;
324             }
325             }
326            
327 29         468 my $res = $parser->proto($content);
328 29 50       479971 die "" unless defined $res;
329            
330             ## start each file from empty package
331 29         166 push @parse_tree, [package=>''];
332 29         99 foreach my $decl (@$res) {
333 308 100       825 if ($decl->[0] eq 'import') {
334 9         43 push @import_files, ($filename, $decl->[1]);
335             } else {
336 299         757 push @parse_tree, $decl;
337             }
338             }
339             }
340            
341             ##
342             ## Pass #1.
343             ## Find names of messages and enums, including nested ones.
344             ##
345 20         232 my $symbol_table = Google::ProtocolBuffers::Compiler::SymbolTable->new;
346 20         115 $self->{symbol_table} = $symbol_table;
347 20         120 $self->collect_names('', \@parse_tree);
348            
349             ##
350             ## Pass #2.
351             ## Create complete descriptions of messages with extensions.
352             ## For each field of a user type a fully quilified type name must be found.
353             ## For each default value defined by a constant (enum), a f.q.n of enum value must be found
354             ##
355 20         69 foreach my $kind (qw/message group enum/) {
356 60         195 foreach my $fqname ($symbol_table->lookup_names_of_kind($kind)) {
357 340         1893 $self->{types}->{$fqname} = { kind => $kind, fields => [], extensions => [] };
358             }
359             }
360 20         134 $self->collect_fields('', \@parse_tree);
361            
362 20         2419 return $self->{types};
363             }
364            
365             sub _find_filename {
366 18     18   49 my $self = shift;
367 18         46 my $base_filename = shift;
368 18         45 my $path = shift;
369            
370             =comment
371             my $filename = File::Spec->rel2abs($path, $base_filename);
372             return $filename if -e $filename;
373            
374             if ($self->{opts}->{include_dir}) {
375             $filename = File::Spec->rel2abs($path, $self->{opts}->{include_dir});
376             return $filename if -e $filename;
377             }
378             =cut
379 11     11   79 use Cwd; my $d = getcwd();
  11         24  
  11         18271  
  18         284  
380            
381 18         51 my $filename = $path;
382 18 50       473 return $filename if -e $filename;
383            
384 18 50       203 if (my $inc_dirs = $self->{opts}->{include_dir}) {
385 18 50       98 $inc_dirs = [ $inc_dirs ] unless(ref($inc_dirs) eq 'ARRAY');
386 18         62 foreach my $d (@$inc_dirs){
387 18         782 $filename = File::Spec->catfile($d, $path);
388 18 50       860 return $filename if -e $filename;
389             }
390             }
391 0         0 die "Can't find proto file: '$path'";
392             }
393            
394            
395             sub collect_names {
396 397     397 0 557 my $self = shift;
397 397         476 my $context = shift;
398 397         449 my $nodes = shift;
399            
400 397         564 my $symbol_table = $self->{symbol_table};
401 397         757 foreach my $decl (@$nodes) {
402 2650         4930 my $kind = $decl->[0]; ## 'message', 'extent', 'enum' etc...
403 2650 100       11018 if ($kind eq 'package') {
    100          
    100          
    100          
    100          
    100          
    50          
404             ## package directive just set new context,
405             ## not related to previous one
406 54         177 $context = $symbol_table->set_package($decl->[1]);
407             } elsif ($kind eq 'message') {
408             ## message may include nested messages/enums/groups
409 235         558 my $child_context = $symbol_table->add('message' => $decl->[1], $context);
410 235         770 $self->collect_names($child_context, $decl->[2]);
411             } elsif ($kind eq 'enum') {
412 48         129 my $child_context = $symbol_table->add('enum' => $decl->[1], $context);
413 48         145 $self->collect_names($child_context, $decl->[2]);
414             } elsif ($kind eq 'group') {
415             ## there may be nested messages/enums/groups etc. inside group
416             ## [group => $label, $ident, $intLit, $messageBody ]
417 57         178 my $child_context = $symbol_table->add('group' => $decl->[2], $context);
418 57         168 $self->collect_names($child_context, $decl->[4]);
419             } elsif ($kind eq 'extend') {
420             ## extend blocks are tricky:
421             ## 1) they don't create a new scope
422             ## 2) there may be a group inside extend block, and there may be everything inside the group
423 37         117 $self->collect_names($context, $decl->[2]);
424             } elsif ($kind eq 'field') {
425             ## we add fields into symbol table just to check their uniqueness
426             ## in several extension blocks. Example:
427             ## .proto:
428             ## extend A { required int32 foo = 100 };
429             ## extend B { required int32 foo = 200 };
430             ## // Invalid! foo is already declared!
431             ##
432 2021         4270 $symbol_table->add('field' => $decl->[3], $context);
433             } elsif ($kind eq 'enumField') {
434 198         433 $symbol_table->add('enum_field' => $decl->[1], $context);
435             } else {
436 0         0 warn $kind;
437             }
438             }
439             }
440            
441             sub collect_fields {
442 397     397 0 1369 my $self = shift;
443 397         471 my $context = shift;
444 397         427 my $nodes = shift;
445 397         954 my $destination_type_name = shift;
446 397         396 my $is_extension = shift;
447            
448 397         713 my $symbol_table = $self->{symbol_table};
449 397         711 foreach my $decl (@$nodes) {
450 2650         4572 my $kind = $decl->[0]; ## 'message', 'extent', 'enum' etc...
451 2650 100       10400 if ($kind eq 'package') {
    100          
    100          
    100          
    100          
    100          
    50          
452 54         120 $context = $decl->[1];
453             } elsif ($kind eq 'message') {
454 235 100       627 my $child_context = ($context) ? "$context.$decl->[1]" : $decl->[1];
455 235         686 $self->collect_fields($child_context, $decl->[2], $child_context);
456             } elsif ($kind eq 'enum') {
457 48 50       173 my $child_context = ($context) ? "$context.$decl->[1]" : $decl->[1];
458 48         154 $self->collect_fields($child_context, $decl->[2], $child_context);
459             } elsif ($kind eq 'group') {
460             ## groups are tricky: they are both definition of a field and type.
461             ## [group => $label, $ident, $intLit, $messageBody ]
462             ## first, collect fields inside the group
463 57 50       205 my $child_context = ($context) ? "$context.$decl->[2]" : $decl->[2];
464 57         161 $self->collect_fields($child_context, $decl->[4], $child_context);
465             ## second, add the group as one field to parent (destination) type
466 57 50       157 confess unless $destination_type_name;
467 57         78 my $name;
468             my $fields_list;
469 57 100       172 if ($is_extension) {
470             ## for extensions, fully quilified names of fields are used,
471             ## because they may be declared anywhere - even in another package
472 18         45 $fields_list = $self->{types}->{$destination_type_name}->{extensions};
473 18         69 $name = $symbol_table->lookup('group' => $decl->[2], $context);
474             } else {
475             ## regualar fields are always immediate children of their type
476 39         96 $fields_list = $self->{types}->{$destination_type_name}->{fields};
477 39         70 $name = $decl->[2];
478             }
479 57 50       174 my $label = (exists $labels{$decl->[1]}) ? $labels{$decl->[1]} : die;
480 57         162 my ($type_name, $kind) = $symbol_table->lookup_symbol($decl->[2], $context);
481 57 50       152 die unless $kind eq 'group';
482 57         90 my $field_number = $decl->[3];
483 57         224 push @$fields_list, [$label, $type_name, $name, $field_number];
484             } elsif ($kind eq 'extend') {
485             ## what is the fqn of the message to be extended?
486 37         122 my $destination_message = $symbol_table->lookup('message' => $decl->[1], $context);
487 37         133 $self->collect_fields($context, $decl->[2], $destination_message, 1);
488             } elsif ($kind eq 'field') {
489 2021 50       3550 confess unless $destination_type_name;
490             # $decl = ['field' => $label, $type, $ident, $item{intLit}, $item{fOptList}] }
491 2021         1889 my $name;
492             my $fields_list;
493 2021 100       2849 if ($is_extension) {
494             ## for extensions, fully quilified names of fields are used,
495             ## because they may be declared anywhere - even in another package
496 640         1311 $fields_list = $self->{types}->{$destination_type_name}->{extensions};
497 640         1344 $name = $symbol_table->lookup('field' => $decl->[3], $context);
498             } else {
499             ## regualar fields are always immediate children of their type
500 1381         5380 $fields_list = $self->{types}->{$destination_type_name}->{fields};
501 1381         2306 $name = $decl->[3];
502             }
503            
504 2021 50       19640 my $label = (exists $labels{$decl->[1]}) ? $labels{$decl->[1]} : die;
505            
506 2021         1951 my ($type_name, $kind);
507 2021 100       4999 if (exists $primitive_types{$decl->[2]}) {
508 1617         2178 $type_name = $primitive_types{$decl->[2]};
509             } else {
510 404         916 ($type_name, $kind) = $symbol_table->lookup_symbol($decl->[2], $context);
511 404 50 66     1819 die unless $kind eq 'message' || $kind eq 'group' || $kind eq 'enum';
      66        
512             }
513            
514 2021         2561 my $field_number = $decl->[4];
515            
516 2021         2428 my $default_value = $decl->[5];
517 2021 100 100     5134 if ($default_value && !ref $default_value) {
518 73 100       190 if ($default_value eq 'true') {
    50          
519 18         63 $default_value = { value => 1 };
520             } elsif ($default_value eq 'false') {
521 0         0 $default_value = { value => 0 };
522             } else {
523             ## this default is enum value
524             ## type name must be fqn of enum type
525 55 50       129 die unless $kind eq 'enum';
526 55         143 $default_value = $symbol_table->lookup('enum_field' => $default_value, $type_name);
527             }
528             }
529 2021         7929 push @$fields_list, [$label, $type_name, $name, $field_number, $default_value];
530             } elsif ($kind eq 'enumField') {
531 198 50       355 confess unless $destination_type_name;
532 198         416 my $fields_list = $self->{types}->{$destination_type_name}->{fields};
533 198         203 push @{$fields_list}, [$decl->[1], $decl->[2]];
  198         865  
534             } else {
535 0         0 warn $kind;
536             }
537             }
538             }
539            
540             package Google::ProtocolBuffers::Compiler::SymbolTable;
541             ##
542             ## %$self - symbol name table, descriptions of fully qualified names like Foo.Bar:
543             ## $names{'foo'} = { kind => 'package' }
544             ## $names{'foo.Bar'} = { kind => 'message' }
545             ## $names{'foo.Bar.Baz'}={ kind => 'enum', }
546             ##
547 11     11   89 use Data::Dumper;
  11         30  
  11         619  
548 11     11   76 use Carp;
  11         26  
  11         13181  
549            
550             sub new {
551 20     20   60 my $class = shift;
552 20         112 return bless {}, $class;
553             }
554            
555             sub set_package {
556 54     54   87 my $self = shift;
557 54         86 my $package = shift;
558            
559 54 100       214 return '' unless $package;
560            
561 25         300 my @idents = split qr/\./, $package;
562 25         93 my $name = shift @idents;
563 25         50 while (1) {
564 32 50       183 if (exists $self->{$name}) {
565 0 0       0 die unless $self->{$name}->{kind} eq 'package';
566             } else {
567 32         186 $self->{$name} = {kind => 'package'}
568             }
569 32 100       129 last unless @idents;
570 7         22 $name .= '.' . shift(@idents);
571             }
572 25         93 return $name;
573             }
574            
575             sub _add {
576 2757     2757   3050 my $self = shift;
577 2757         2772 my $kind = shift;
578 2757         3204 my $name = shift;
579 2757         2808 my $context = shift;
580            
581             ## no fully quilified names are alowed to declare (so far)
582 2757 50       5743 die if $name =~ /\./;
583 2757         2585 my $fqn;
584 2757 100       4104 if ($context) {
585 2751 50       5937 die "$name, $context" unless $self->{$context};
586 2751         4440 $fqn = "$context.$name";
587             } else {
588 6         15 $fqn = $name;
589             }
590            
591 2757 50       5513 if (exists $self->{$fqn}) {
592 0         0 die "Name '$fqn' is already defined";
593             } else {
594 2757         9809 $self->{$fqn} = { kind=>$kind };
595             }
596            
597 2757         7517 return $fqn;
598             }
599            
600             sub add {
601 2559     2559   3022 my $self = shift;
602 2559         2621 my $kind = shift;
603 2559         3836 my $name = shift;
604 2559         2748 my $context = shift;
605            
606             ## tricky: enum values are both children and siblings of enums
607 2559 100       4068 if ($kind eq 'enum_field') {
608 198 50       575 die unless $self->{$context}->{kind} eq 'enum';
609 198         377 my $fqn = $self->_add($kind, $name, $context);
610 198         1310 $context =~ s/(^|\.)\w+$//; ## parent context
611 198         433 $self->_add($kind, $name, $context);
612 198         531 return $fqn;
613             } else {
614 2361         4524 return $self->_add($kind, $name, $context);
615             }
616             }
617            
618             ## input: fully or partially qualified name
619             ## output: (fully qualified name, its kind - 'message', 'enum_field' etc.)
620             sub lookup_symbol {
621 1211     1211   1302 my $self = shift;
622 1211         1261 my $n = shift;
623 1211         1189 my $c = shift;
624            
625 1211         1243 my $context = $c;
626 1211         1543 my $name = $n;
627 1211 50       2258 if ($name =~ s/^\.//) {
628             ## this is an fully quialified name
629 0 0       0 if (exists $self->{$name}) {
630 0         0 return ($name, $self->{$name}->{kind});
631             }
632             } else {
633             ## relative name - look it up in the current context and up
634 1211         1394 while (1) {
635 1531 100       3420 my $fqn = ($context) ? "$context.$name" : $name;
636 1531 100       3754 if (exists $self->{$fqn}) {
637 1211         4637 return ($fqn, $self->{$fqn}->{kind});
638             }
639             ## one level up
640 320 50       651 last unless $context;
641 320         1882 $context =~ s/(^|\.)\w+$//;
642             }
643             }
644 0         0 die "Name '$name' ($c, $n) is not defined" . Data::Dumper::Dumper($self);
645             }
646            
647             ## input: kind, fully or partially qualified name, context
648             ## ouptut: fully qualified name
649             ## if found kind of the name doesn't match given kind, an exception is raised
650             sub lookup {
651 750     750   831 my $self = shift;
652 750         814 my $kind = shift;
653 750         901 my $name = shift;
654 750         751 my $context = shift;
655            
656 750         1552 my ($fqn, $k) = $self->lookup_symbol($name, $context);
657 750 50       1544 unless ($kind eq $k) {
658 0         0 confess "Error: while looking for '$kind' named '$name' in '$context', a '$k' named '$fqn' was found";
659             }
660 750         1378 return $fqn;
661             }
662            
663             ## returns list of all fully qualified name of a given kind
664             sub lookup_names_of_kind {
665 60     60   89 my $self = shift;
666 60         98 my $kind = shift;
667            
668 60         1227 return grep { $self->{$_}->{kind} eq $kind } keys %$self;
  8367         14063  
669             }
670            
671             1;